From 4d8bc95063b018cc48562078ad529eb7e432b1c0 Mon Sep 17 00:00:00 2001 From: jiao Date: Mon, 1 Jun 2026 10:48:50 +0800 Subject: [PATCH] =?UTF-8?q?feat(sidebar&conversation):=20=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E5=8A=A8=E6=80=81=E8=8E=B7=E5=8F=96=E5=AE=A2=E6=88=B7?= =?UTF-8?q?=E5=8E=86=E5=8F=B2=E6=B6=88=E6=81=AF=E5=8A=9F=E8=83=BD=EF=BC=8C?= =?UTF-8?q?=E5=AE=8C=E5=96=84=E5=8F=82=E6=95=B0=E4=BC=A0=E9=80=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 为ScriptRecommend组件新增customerId参数,支持从父组件传入客户ID 2. 添加动态获取最后一条学员消息的逻辑,从后端API拉取会话历史 3. 为ArchiveMessageEvent类添加Jackson忽略未知属性注解 4. 新增部署文档和项目说明文档AGENTS.md、CLAUDE.md --- AGENTS.md | 111 ++++++++++++++++ CLAUDE.md | 120 ++++++++++++++++++ .../service/ConversationManager.java | 1 + ...versationManager$ArchiveMessageEvent.class | Bin 2309 -> 2433 bytes .../ConversationManager$ContextMessage.class | Bin 1343 -> 1343 bytes .../conversation-service-1.0.0-SNAPSHOT.jar | Bin 40804419 -> 40804502 bytes ...sation-service-1.0.0-SNAPSHOT.jar.original | Bin 22676 -> 22764 bytes frontend/sidebar/src/App.tsx | 2 +- .../src/components/ScriptRecommend.tsx | 47 ++++++- 9 files changed, 277 insertions(+), 4 deletions(-) create mode 100644 AGENTS.md create mode 100644 CLAUDE.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..c7f6389 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,111 @@ +# Agent Instructions for 九艺AI坐席辅助系统 + +## 部署规则(核心) + +### ⚠️ 任何代码修改后必须同步服务器部署 + +**无论前端、后台还是后端,任何代码修改完成后,都必须立即同步到服务器并重新部署,绝对不能只做本地修改就结束。** + +--- + +## 一、前端 Sidebar 部署 + +**模块路径**: `frontend/sidebar/` +**服务器部署路径**: `/www/wwwroot/deploy-package/frontend/sidebar/` +**容器名称**: `ai-assistant-sidebar` +**访问地址**: `https://ai.9artedu.com/sidebar/` + +### 部署流程 +```bash +# 1. 本地构建 +cd frontend/sidebar && npm run build + +# 2. 同步到服务器 +scp -r -o StrictHostKeyChecking=no -i root.pem frontend/sidebar/dist/* root@8.133.162.25:/www/wwwroot/deploy-package/frontend/sidebar/dist/ + +# 3. SSH 登录服务器,重新构建镜像并重启容器 +ssh -o StrictHostKeyChecking=no -i root.pem root@8.133.162.25 << 'EOF' +cd /www/wwwroot/deploy-package/frontend/sidebar +docker build -t deploy-package-sidebar . +docker stop ai-assistant-sidebar +docker rm ai-assistant-sidebar +cd /www/wwwroot/deploy-package +docker-compose up -d sidebar +EOF +``` + +--- + +## 二、后台 Admin 部署 + +**模块路径**: `frontend/admin/` +**服务器部署路径**: `/www/wwwroot/deploy-package/frontend/admin/` +**容器名称**: `ai-assistant-admin` +**访问地址**: `https://ai.9artedu.com/admin/` + +### 部署流程 +```bash +# 1. 本地构建 +cd frontend/admin && npm run build + +# 2. 同步到服务器 +scp -r -o StrictHostKeyChecking=no -i root.pem frontend/admin/dist/* root@8.133.162.25:/www/wwwroot/deploy-package/frontend/admin/dist/ + +# 3. SSH 登录服务器,重新构建镜像并重启容器 +ssh -o StrictHostKeyChecking=no -i root.pem root@8.133.162.25 << 'EOF' +cd /www/wwwroot/deploy-package/frontend/admin +docker build -t deploy-package-admin . +docker stop ai-assistant-admin +docker rm ai-assistant-admin +cd /www/wwwroot/deploy-package +docker-compose up -d admin +EOF +``` + +--- + +## 三、后端微服务部署 + +**模块路径**: `backend/*-service/` +**服务器部署路径**: `/www/wwwroot/deploy-package/backend/*-service/` +**技术栈**: Spring Boot + Docker Compose + +### 微服务列表 +| 服务名 | 容器名 | 端口 | 部署路径 | +|--------|--------|------|----------| +| gateway | ai-assistant-gateway | 8080 | `backend/gateway/` | +| auth-service | ai-assistant-auth | 8081 | `backend/auth-service/` | +| archive-service | ai-assistant-archive | 8082 | `backend/archive-service/` | +| conversation-service | ai-assistant-conversation | 8083 | `backend/conversation-service/` | +| intent-service | ai-assistant-intent | 8084 | `backend/intent-service/` | +| recommend-service | ai-assistant-recommend | 8085 | `backend/recommendation-service/` | +| generation-service | ai-assistant-generation | 8086 | `backend/generation-service/` | +| kb-admin-service | ai-assistant-kbadmin | 8087 | `backend/kb-admin-service/` | +| analytics-service | ai-assistant-analytics | 8088 | `backend/analytics-service/` | + +### 部署流程(以某个服务为例) +```bash +# 1. 本地构建 JAR 包(通常在对应服务目录下) +cd backend/auth-service && mvn clean package -DskipTests + +# 2. 将 JAR 包同步到服务器的 deploy-package 对应目录 +scp -o StrictHostKeyChecking=no -i root.pem target/*.jar root@8.133.162.25:/www/wwwroot/deploy-package/backend/auth-service/ + +# 3. SSH 登录服务器,重启对应容器 +ssh -o StrictHostKeyChecking=no -i root.pem root@8.133.162.25 << 'EOF' +cd /www/wwwroot/deploy-package +docker-compose up -d --build auth-service +EOF +``` + +--- + +## 服务器信息 + +- **IP**: `8.133.162.25` +- **SSH 私钥**: `root.pem`(项目根目录) +- **部署根目录**: `/www/wwwroot/deploy-package/` +- **Docker Compose 文件**: `/www/wwwroot/deploy-package/docker-compose.yml` +- **前端站点**: `https://ai.9artedu.com/sidebar/` +- **后台站点**: `https://ai.9artedu.com/admin/` +- **代理**: 宝塔 Nginx → Docker Nginx (8089) → 各容器 diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..c3ca356 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,120 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +九艺AI坐席辅助系统 — A real-time script recommendation system for course consultants/sales reps at 9artedu.com. It captures WeCom (企业微信) conversations, analyzes intent, builds customer profiles, and recommends sales scripts to agents via a sidebar H5 interface. + +## Architecture + +The system uses a microservices architecture with 10 backend services, all sharing a `common` module. An API Gateway routes requests by path prefix to individual services. + +``` +nginx (port 80) + → gateway (port 8080) — Spring Cloud Gateway, routes by /api/v1/* + → auth-service (8081) — WeCom OAuth, JWT, JS-SDK signing + → archive-service (8082) — WeCom archive C SDK via JNA, RSA decrypt, callbacks + → conversation-service (8083) — Session management, context windows (Redis List) + → intent-service (8084) — Intent recognition (40 scenarios), customer profiles + → recommendation-svc (8085) — 3-layer recall, multi-factor ranking, MMR reranking + → generation-service (8086) — Tongyi Qianwen (DashScope) API, prompt engine + → kb-admin-service (8087) — Script CRUD, category management, import/export + → analytics-service (8088) — Statistics, script effectiveness analysis + → admin frontend (port 5174) — React + Ant Design (script library management, dashboards) + → sidebar frontend (port 5173) — React + Ant Design Mobile (agent-facing H5 sidebar) +``` + +### Key Design Patterns + +- **Unified API response**: All services use `Result` from `common` module (`code: 0` = success, `code: -1` = failure, with `message` and `timestamp`) +- **Pagination**: `PageResult` for list responses +- **Database**: MyBatis-Plus with logical delete (`deleted` field: 0/1), auto-fill handler for timestamps +- **Config**: All services use environment variables for infra hosts (`MYSQL_HOST`, `REDIS_HOST`, `RABBITMQ_HOST`), allowing local dev with `localhost` and containerized with service names +- **Data pipeline**: WeCom callback → archive-service → RabbitMQ → conversation-service → intent-service → recommendation-service → WebSocket push to sidebar + +## Development Commands + +### Backend (Java 8 / Spring Boot 2.7.18) + +```bash +# Build all services from backend/ +cd backend && mvn clean package -DskipTests + +# Run a single service locally (e.g., conversation-service) +cd backend/conversation-service && mvn spring-boot:run + +# Run a specific test +cd backend/conversation-service && mvn test -Dtest=ConversationControllerTest + +# Run all tests +cd backend && mvn test +``` + +### Frontend + +```bash +# Sidebar (agent-facing H5) +cd frontend/sidebar && npm install && npm run dev + +# Admin dashboard +cd frontend/admin && npm install && npm run dev + +# Build for production +cd frontend/sidebar && npm run build +cd frontend/admin && npm run build +``` + +### Docker (full stack) + +```bash +# Start all services +docker compose up -d + +# Start only infrastructure (MySQL, Redis, RabbitMQ) +docker compose up -d mysql redis rabbitmq + +# View logs for a service +docker compose logs -f conversation-service + +# Rebuild and restart a specific service +docker compose up -d --build recommendation-service +``` + +### Database + +- `init.sql` — Full schema (15+ tables) + seed data. Loaded into MySQL on first `docker compose up`. +- `backend/utterances_cg.sql` — CG training script seed data +- `add_utterances.sql` — Additional utterance data + +## Service Ports Reference + +| Service | Port | +|---------|------| +| Gateway | 8080 | +| Auth | 8081 | +| Archive | 8082 | +| Conversation | 8083 | +| Intent | 8084 | +| Recommendation | 8085 | +| Generation | 8086 | +| KB Admin | 8087 | +| Analytics | 8088 | +| Admin Frontend | 5174 | +| Sidebar Frontend | 5173 | +| Nginx (unified entry) | 80 | + +## Tech Stack + +- **Backend**: Spring Boot 2.7.18, Spring Cloud 2021.0.8, MyBatis-Plus 3.5.5, Java 8 +- **Infra**: MySQL 5.7, Redis 6.x, RabbitMQ 3.x +- **Frontend**: React 18, TypeScript, Vite 5, Ant Design 5 / Ant Design Mobile 5 +- **LLM**: DashScope (Tongyi Qianwen qwen-turbo/qwen-plus) +- **Auth**: JWT (jjwt 0.11.5), WeCom OAuth2 +- **Deployment**: Docker + Docker Compose, Nginx + +## Important Constraints + +- **MySQL 5.7**: No native JSON functions beyond basics, no vector types. Vectors are stored in Redis. +- **Java 8**: All services compile to Java 1.8 (not the Dockerfile's Temurin 17 JRE, which is forward-compatible) +- **No Spring Cloud Discovery**: Services use static gateway routing, not Eureka/Consul diff --git a/backend/conversation-service/src/main/java/com/artedu/conversation/service/ConversationManager.java b/backend/conversation-service/src/main/java/com/artedu/conversation/service/ConversationManager.java index b096597..0ef87a6 100644 --- a/backend/conversation-service/src/main/java/com/artedu/conversation/service/ConversationManager.java +++ b/backend/conversation-service/src/main/java/com/artedu/conversation/service/ConversationManager.java @@ -257,6 +257,7 @@ public class ConversationManager { return content.length() > maxLength ? content.substring(0, maxLength) + "..." : content; } + @com.fasterxml.jackson.annotation.JsonIgnoreProperties(ignoreUnknown = true) public static class ArchiveMessageEvent { private String msgid; private String corpId; diff --git a/backend/conversation-service/target/classes/com/artedu/conversation/service/ConversationManager$ArchiveMessageEvent.class b/backend/conversation-service/target/classes/com/artedu/conversation/service/ConversationManager$ArchiveMessageEvent.class index e6258a95a04d4b558805f6856db8d5ad07579e6b..c3f38fa741eababa88174671f4743de4208b23f9 100644 GIT binary patch delta 386 zcmX}nJxc>Y5C-5GFL$>_12G}S;0FX0uUkPu!9rp*F;PJT73|Ki%9+ca+@5HqSO}Kt zLMnfRX)G*KT3A?E`&-0u1&d*49^PTt+uTh){r>&h2Y?0G?4|40b!qopCP7E+2P!xU zMMcUe@sl8u3Sn`p6?OQruM!boc0zvYx7sR_-2V^pCe4i#8O35Jj?P4!1VSw%WCCM* zvMr+vY0*7I7;Z=@;%ewCC6o&dg!z^h)*IUTffaNmHH%r(U8Ou6(LKAU`zek&W0vh< z-dNS<`q_4Lmu2-GX@Rtcqb8McJS>{jw!?AE*dtlV*r&twiu<=&bUhq5df;-LFv%yf zNn>ARQ^v~7)A!8LFLqA*jO!2TyKD)|zqz?-m;nR}D)hqvScNLRLnLcd>xWQ-IzgHS J1{=_X!5=GCX+!`3 delta 218 zcmZn^ZWZD>^>5cc1_lNb2KS9z?#u!_o_TqxMb0^i#l@+`>zzG0V*fxFu diff --git a/backend/conversation-service/target/classes/com/artedu/conversation/service/ConversationManager$ContextMessage.class b/backend/conversation-service/target/classes/com/artedu/conversation/service/ConversationManager$ContextMessage.class index 01809f27987eccd09f1128ad2f175d6b23df0b34..d8731ecec6035d5708a8664570f43f671269abf1 100644 GIT binary patch delta 93 zcmdnbwV!JPCo`kw81)#08TBWpF-J}Q$ILeQ7_$YV m(d7TkZj8oY)zK^(j3z+7_~h9v`i!Pvu?IjgGq9Kft2_WJAQ*iB delta 95 zcmdnbwV!JPCo`kQSmgmek{Fu+ diff --git a/backend/conversation-service/target/conversation-service-1.0.0-SNAPSHOT.jar b/backend/conversation-service/target/conversation-service-1.0.0-SNAPSHOT.jar index 88e2c14d2199095935600c2d47dea33e165c6d52..d8daf06a4efc0e363df112863770d1be447e94e2 100644 GIT binary patch delta 7069 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 CNI%N} delta 6920 zcmZA62|QHm|G;tQoUw++zJ;+xWLIS0WhrZtr9C&>l(^DzO(aQCsZ1)4RJfIBwX{f- zr4lVvT12I;J*~9-zu%$Py}y62*XMnn=Q(GZW1JZ|<7}9(~LPC6( zvo3{l+l)5OVc>`^cT^)Zlh<5aL#Q3o({n{i0!!_UeyZ{}`t8_b%Xq&LrO`n6%|>(O23H-)I=RGHs0x3c<` z_vFXGoq-=eM@QxOPTycF3M@4ujHxtM3u{y4=Q|r4;-7x{(op&a)YwhORaPC_*N;w zRz0~wKsxo%_t#a=cc-h>$5I}0-GUW$+bkmN7qJy8e^f?Lx*SIjme=q%ue7_0M%uHk zEZI`xcsb@e`OG&qfxF`QnWg4oso~71O8w9#vzYBQRzoUrL#@_>YhztHOsw3B{+P04 z$Jws>&kFsse)+pdu%5iFO$vH+HgX_5%=ku4x=z3vfrnY;)s^l}?V*8V%h?4wZPfCb)?KP+dE-;XjObbR=@XP zFcGkQOsOe-^Ma|&4sX@U-u#>Eqa7BiN7l^D9{#y0tM006!^e>*uj{k79^kCj^kbIR z4;k(#lV)wxu(diOmfpdNng1l^oYtz_&4K&#{AFuGE^JuZeYGc1j&s#AI4xoIh+uh{ zNxw?ga)lt#lNRPT_)2Qkg9SX-{&M|bK6i6lw&}_bR8U}3l)OG$c1h@%xbTm6r|%e~ zN$7^$Nqdtey1R(w8lUbf*{vpKTB7p(O~}I2 zti^kn{+7;P-uWF#2O~~B>DJ-`S||l-NnrqBjTCQ zEZp)yKIph{z> z_a{`pj_9QoK2iwqadmNfbX`|8t%GfmkSM+L*p8mG+5qS5h9r3lKWG1!GV|BhKItXC zIA3tT7PL8Q+|k3*bhWRA`h)(wN4Du2cEgKHBLAes7I>^bFf;wYP9ovmnN#~jk`Ihrp1ZS7*6_;0JPYb?q0FP7ze(5gH9;#W<2q}}-( z)zO9CH=Zf9?z5KQc#XB4v%D=SvPYZfo8uCYp?l(8%V8J6s>FiBKizL1>itkW&;M1i zL{y;4vQMld&Sw()^wKhqB=qY_Y#lKWa(<{BJAfJ59vl+CuF>S2-skn@W$uqHOaD>4<9PVXLN7Mbw{X}l9HI*yO*_N0+ zC00)r29GcE74CI>R4cS1pkR4 z7nBKQLA#+n&|WAT+6V224nR2&7di;#LWiKk&=DvP%7s1a&{nxWg!9q2C90^NgJp*H9~^ZQK_st5~FL(~xsL=(|MrX$*j4l)DLMf8xFh(2O~7$Qc9F=B$4B4)@eWHw@s zSRiu{OT-GXMr;sU#164X91usu32{bT5Ld(vnTxn19*8I6h0H_dBi@J);*0np{zw3_ z00~4EB0_he=2ap_uiyTC9kweH~el&htwlCky}Uu z(ug!6&B$%!4ssW1LGB@~NE>n=d4N1b9wF^W2l5zsf;>e!k!Q$rS~UdTLTKH`n|Aijtn;*SI%3y?r$ zArgcvLV}SHBoqll79&fLrN}a5IkEy-iL64xkw1{tNCXmzL?O{g3|Rvof8>}A{xiAe zR}h;NqHsuygtE!$oX&MmjhJaeUz_R|MPz%vy}F#3>#)~rnWt~^1N}?OIDQXj%DWl7V`!N> zn&xJg$}ib;O!&c5hHPqEzSnBs!ogiBN`kpMu64Rc`&1GiFJhhA_}8P5p0Gu`Ocyji z%w#OjT_HkR6poC37g(B19WxAZJtz~DCwlkVY{9z>eG~Uc-5#60;b)l}9Xy_Hd>ya< z<9vO9^;_c;a_%ShYRY%N;cTwHQD7L6>#UX9y7ryNU&$}@ZhidH-`hVHo3sBQ|IZk1 zNNn!>Gd(|V#>$>4U#@v6rAvO+hu-(I|A{L(c}MYRcScT+^oazSoICq2U2{2CFWB5% z-iivi~ewB-Cq&Y7vDfZ;-??%y zaKA-~%Uj9vi;r_-gTl4WcAH3@*2{P6zI*e0;=;xocLU!fXDv7&w4wABt1|40$ce+8 z3XQtiYd+cetlAp6uWCW##=4HREr)A$T1aAn!}B>Gr(~6%dhI)!A^2o_lJNVW)a;(m z2htvOiI*wg(sxxZ%>CrK`RCg=3{~T(6I~-oBZDzoi;J)CesN2dQ!#Vq-z618!;$z=Ack|C^Xj*^&D2Kn5Ru?pO^HF?!USjxLDM3n8 z|GA@C`>IlwUb2Kus`1`LTVZ6%nz+Szy}yExrCT{{QknOz8itWI-UXx$x3Ecj2Hw}C zjYCQaMLH7}W=`CRTzgnvk?w%`9Nq!kPFsIP+8~)tI#28ub+uNc_rTo#Kl4GH^Zsh% z6gC+Ow>HaGlFIZcn5a&i%!v7ULV@mu1-*&K?a^zKrwz8UNeABJ*1?E(mz2H^qfNXu z+f+80$UF5a7{%~LGq$nGSl%cPMsd86R2rLHFNR;v@$XDfh1l&B})|2;&ByDK+9oV(?`JcE{jU+h(= zJ#r*BRt5f<12<@=(J`@@Q%Yo3tdK zD$;f^@S`?FlH9>aQkT94Qyr=?M1X4(Dqu=a%fKl!S&^=Jeh$8kVK8?j{-g_FyO7dr zpT;d{pQb@8@5IA=UMkaS@q&a59R_nd>Q}by#GJchpRFR@!aHbWzWwAu;=ABgCo*Us z*v=<5(ll(n35p(qZ3vM>duFo9MU=YH#vGSaY5FqE9EqLuPu?ogzKgfY&Vnm~>Wj$a zE{)7IpbcRvLJxo{RJeRL9>N`x&o-Le5xN_9Y^$HxA$~JkVRFX>-i|}U2Y)|8gsU%d zP-=27a}PX>yiU_bFz}&cw z``Me5uf>D2HznvoSkxh6>D#dNp@u%Saqnr~SK@L$!_}C3h)qtRL$mP>QzCYsw}U;I$&ZB-2SSSVUdg-ie3H(!(${qvBX!xbIkB)M@wqa5zJ3 zCqKb$)x0l5XTaQ&AyOtl&|3gITJQiIK%S1HjbRW*wmQ&zVBpE1$~B0bHv!UgCJZF# zup{{Tis!)V!5FvHCFocyT|$ozfdw7LQ+XeP_EGR5)aVjipt_sbwAx{~;^~-XHe3}y zd8>x@X_KpVTzI<@w)7U*`Z6TdGbf(v2n-~6SBnVEI0#Qgtf4hv8^ACZD<^md7S#=sehONwHW8lk*{%TTxXVI+JrpILd-zUNV@Nb2EPg z3(<#Q(Twn=@4+^ZYAJG^JlH0mP1;h@{@#=Ge1C6vaVNehayo&ewD|A;A3C0VQ9F>N z^i1K__IUCMIueeWF7S3S8*XI&ADsMw9RL6T diff --git a/backend/conversation-service/target/conversation-service-1.0.0-SNAPSHOT.jar.original b/backend/conversation-service/target/conversation-service-1.0.0-SNAPSHOT.jar.original index a9c9372e3b63dc26683f4147705e5ed72fa4b422..221748dbfdeb4d0ca2bb09737a4d309087ac765d 100644 GIT binary patch delta 1939 zcmY+FdpMMN8^@oSp_m*dotznB$zmKP%^+%Kau|mhL*#V|DW{Ffq0peggdK-RSTe*E zI}u_{IaH>RR4bcekQ73WDW`oUuX*-;cdz$%UC;Hozt8o(e}CP-`9!r5han-o z4l@^B0^;BYAO^v)5ZQ2h#0^$k!weh;7hTlwgSg{%sECnLgo|1m@?>_Qpn|LVbO%&K z9U#J>qE(L!+qvcOvhZ}+ZHoYaBuM~p1hZgDAPb`mI_oRa^G9gC)~HT1>rb-DdHMNA zZ%~N(Wkhijj9Q&YqNJ%MeAegKNH*@%w!8h^9bYk)Df=Dvoyv;q4^0~TGP>3?JGUCW zaF`d@wX|AV`VjEj`A$%_qxo@91wrBxmRVZl*jx2WV@RtxUMs)$$O6|fobGsKp6`G| z?qsH-ZxQHfQ;K0HoQ5&prLKypAA7VTEqi$?A+dt+!M#T51p;rd$ZgcAwx#dt@ctIT z*Pf9xa_KFMX`eTD&SVSks|#1Xi;uet*=85y{ghTbS#dsGOfV%)3@leQU_9r&Myt3u z{H)yKw1F}zJXB4%r8$qo@�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 i$61hXgeR25H-Yc`l5dH85U!2MfiA diff --git a/frontend/sidebar/src/App.tsx b/frontend/sidebar/src/App.tsx index ff74622..a43bb16 100644 --- a/frontend/sidebar/src/App.tsx +++ b/frontend/sidebar/src/App.tsx @@ -215,7 +215,7 @@ function App() { - + diff --git a/frontend/sidebar/src/components/ScriptRecommend.tsx b/frontend/sidebar/src/components/ScriptRecommend.tsx index 864f6ba..b55d195 100644 --- a/frontend/sidebar/src/components/ScriptRecommend.tsx +++ b/frontend/sidebar/src/components/ScriptRecommend.tsx @@ -4,25 +4,66 @@ import type { Recommendation } from '../types' interface Props { userInfo?: any + customerId?: string } -export default function ScriptRecommend({ userInfo }: Props) { +interface ConversationTurn { + turnNumber: number + studentContent: string + seatContent: string +} + +export default function ScriptRecommend({ userInfo, customerId: propCustomerId }: Props) { const [scripts, setScripts] = useState([]) const [loading, setLoading] = useState(false) const [generating, setGenerating] = useState(false) - const [customerMsg, setCustomerMsg] = useState('我想学游戏美术,零基础可以吗?') + const [customerMsg, setCustomerMsg] = useState('') const [generated, setGenerated] = useState('') // 从 localStorage 或企微环境获取真实身份信息 const sessionId = localStorage.getItem('current_session_id') || `session_${Date.now()}` - const customerId = localStorage.getItem('current_customer_id') || 'wx_001' + const customerId = propCustomerId || localStorage.getItem('current_customer_id') || 'wx_001' const staffId = userInfo?.userId || 'staff_001' const corpId = userInfo?.corpId || 'wwd483c2fba24ae30a' + // 动态获取最后一条学员消息 + const fetchLastCustomerMessage = async () => { + if (!customerId || customerId === 'wx_001') return + try { + const res = await fetch(`/api/v1/conversations?customerId=${customerId}&corpId=${corpId}`) + const data = await res.json() + if (data.code === 0 && data.data && data.data.length > 0) { + // 取最新的会话 + const sessions = data.data as Array<{ sessionId: string; startTime: string }> + const latestSession = sessions.sort( + (a, b) => new Date(b.startTime).getTime() - new Date(a.startTime).getTime() + )[0] + const turnsRes = await fetch(`/api/v1/conversations/${latestSession.sessionId}/turns`) + const turnsData = await turnsRes.json() + if (turnsData.code === 0 && turnsData.data && turnsData.data.length > 0) { + const turns = turnsData.data as ConversationTurn[] + // 取最后一条有内容的学员消息 + for (let i = turns.length - 1; i >= 0; i--) { + if (turns[i].studentContent?.trim()) { + setCustomerMsg(turns[i].studentContent.trim()) + break + } + } + } + } + } catch (e) { + console.error('获取最后一条学员消息失败:', e) + } + } + useEffect(() => { loadRecommendations() }, []) + useEffect(() => { + fetchLastCustomerMessage() + }, [customerId]) + const loadRecommendations = async () => { setLoading(true) try {