|
@@ -12,6 +12,8 @@
|
|
|
* - item → <!-- POLICY_TABLE {json} POLICY_TABLE -->(原政策卡片 + 原详情面板)
|
|
* - item → <!-- POLICY_TABLE {json} POLICY_TABLE -->(原政策卡片 + 原详情面板)
|
|
|
* - source → <ref_links>[…]</ref_links>(原“参考资料”)
|
|
* - source → <ref_links>[…]</ref_links>(原“参考资料”)
|
|
|
* - interrupt → <question-cards>{…}</question-cards>(原选项卡)
|
|
* - interrupt → <question-cards>{…}</question-cards>(原选项卡)
|
|
|
|
|
+ * - result.cipa(布尔) → <image-scope>…</image-scope>(企业微信二维码,追加在最后;
|
|
|
|
|
+ * 仅当后端判定问的是 CIPA / 海外服务平台 / 出海服务时为 true)
|
|
|
* - answer / summary → 纯文本
|
|
* - answer / summary → 纯文本
|
|
|
*
|
|
*
|
|
|
* 正文与卡片在 done 时按原顺序一次性输出:新接口本身不逐字流式返回,
|
|
* 正文与卡片在 done 时按原顺序一次性输出:新接口本身不逐字流式返回,
|
|
@@ -23,6 +25,9 @@
|
|
|
* - 未收到 done/error 就断开 → 按“连接中断”处理,不自动重试
|
|
* - 未收到 done/error 就断开 → 按“连接中断”处理,不自动重试
|
|
|
*/
|
|
*/
|
|
|
import mitt, { Emitter } from 'mitt';
|
|
import mitt, { Emitter } from 'mitt';
|
|
|
|
|
+// 企业微信二维码:后端 cipa=true(问的是 CIPA / 海外服务平台 / 出海服务)时展示。
|
|
|
|
|
+// 走 Vite 资源导入 → 构建时变成带 hash 的产物地址,不要写死路径。
|
|
|
|
|
+import cipaQrcodeUrl from '@/assets/qiyeweixin.png';
|
|
|
|
|
|
|
|
/* ------------------------------------------------------------------ *
|
|
/* ------------------------------------------------------------------ *
|
|
|
* 事件负载类型(全部字段可选,保证与后端降级/缺字段时兼容)
|
|
* 事件负载类型(全部字段可选,保证与后端降级/缺字段时兼容)
|
|
@@ -161,6 +166,11 @@ export interface ChatResultPayload {
|
|
|
* `syncCompanyInfoFromChat` 去调分类接口、落 DMS(见 company-info-sync.ts)。
|
|
* `syncCompanyInfoFromChat` 去调分类接口、落 DMS(见 company-info-sync.ts)。
|
|
|
*/
|
|
*/
|
|
|
company_info?: unknown;
|
|
company_info?: unknown;
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 是否需要展示企业微信二维码(2026-09-20 后端新增):
|
|
|
|
|
+ * 问的是 CIPA / 海外服务平台 / 出海服务时为 `true`,其他情况 `false`。
|
|
|
|
|
+ */
|
|
|
|
|
+ cipa?: unknown;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
export interface ChatDonePayload {
|
|
export interface ChatDonePayload {
|
|
@@ -209,6 +219,16 @@ export function mapChatErrorText(code?: string, message?: string): string {
|
|
|
return CHAT_ERROR_TEXT[code || ''] || '服务异常,请稍后再试';
|
|
return CHAT_ERROR_TEXT[code || ''] || '服务异常,请稍后再试';
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/**
|
|
|
|
|
+ * 企业微信二维码的内容标记。
|
|
|
|
|
+ *
|
|
|
|
|
+ * 渲染层本来就认 `<image-scope>` 块(→ `ImageContent` 行类型,支持点击放大),
|
|
|
|
|
+ * 所以这里不需要新增组件,只要按它的约定把图片地址放进去。
|
|
|
|
|
+ */
|
|
|
|
|
+export function buildCipaQrcodeContent(): string {
|
|
|
|
|
+ return `<image-scope>\n${cipaQrcodeUrl}\n</image-scope>`;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
export const CHAT_ENDPOINT_PATH = '/api/chat';
|
|
export const CHAT_ENDPOINT_PATH = '/api/chat';
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -819,6 +839,8 @@ export class ApiChatCoordinator {
|
|
|
private pendingSummary: ChatSummaryPayload | null = null;
|
|
private pendingSummary: ChatSummaryPayload | null = null;
|
|
|
private pendingInterrupt: ChatInterruptPayload | null = null;
|
|
private pendingInterrupt: ChatInterruptPayload | null = null;
|
|
|
private pendingCompanyInfo: unknown = null;
|
|
private pendingCompanyInfo: unknown = null;
|
|
|
|
|
+ /** 本轮是否需要展示企业微信二维码(result.cipa) */
|
|
|
|
|
+ private pendingCipa = false;
|
|
|
/** 正文是否已输出(输出后不再发送 scope 进度卡片) */
|
|
/** 正文是否已输出(输出后不再发送 scope 进度卡片) */
|
|
|
private contentFlushed = false;
|
|
private contentFlushed = false;
|
|
|
/** 最近一次补问的原始数据,供提交时反查候选用 */
|
|
/** 最近一次补问的原始数据,供提交时反查候选用 */
|
|
@@ -1013,6 +1035,7 @@ export class ApiChatCoordinator {
|
|
|
this.pendingSummary = null;
|
|
this.pendingSummary = null;
|
|
|
this.pendingInterrupt = null;
|
|
this.pendingInterrupt = null;
|
|
|
this.pendingCompanyInfo = null;
|
|
this.pendingCompanyInfo = null;
|
|
|
|
|
+ this.pendingCipa = false;
|
|
|
this.contentFlushed = false;
|
|
this.contentFlushed = false;
|
|
|
this.scopeEmitted = false;
|
|
this.scopeEmitted = false;
|
|
|
this.doneReceived = false;
|
|
this.doneReceived = false;
|
|
@@ -1085,6 +1108,11 @@ export class ApiChatCoordinator {
|
|
|
parts.push(refLinks);
|
|
parts.push(refLinks);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // 企业微信二维码:后端标记为 CIPA / 出海类问题时追加(放在整条回答的最末尾)
|
|
|
|
|
+ if (this.pendingCipa) {
|
|
|
|
|
+ parts.push(buildCipaQrcodeContent());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
const content = parts.filter((part) => String(part || '').trim().length > 0).join('\n\n');
|
|
const content = parts.filter((part) => String(part || '').trim().length > 0).join('\n\n');
|
|
|
if (!content) {
|
|
if (!content) {
|
|
|
return;
|
|
return;
|
|
@@ -1175,9 +1203,13 @@ export class ApiChatCoordinator {
|
|
|
|
|
|
|
|
// result.response 与 answer.text 相同,是最终快照,不再输出;
|
|
// result.response 与 answer.text 相同,是最终快照,不再输出;
|
|
|
// 但 company_info 只有这里有,暂存下来等 done 时随 totalResponse 交给应用侧
|
|
// 但 company_info 只有这里有,暂存下来等 done 时随 totalResponse 交给应用侧
|
|
|
- case 'result':
|
|
|
|
|
- this.pendingCompanyInfo = (data as ChatResultPayload).company_info ?? null;
|
|
|
|
|
|
|
+ case 'result': {
|
|
|
|
|
+ const result = data as ChatResultPayload;
|
|
|
|
|
+ this.pendingCompanyInfo = result.company_info ?? null;
|
|
|
|
|
+ // 后端只在 CIPA / 海外服务平台 / 出海服务的问题上给 true,其余为 false
|
|
|
|
|
+ this.pendingCipa = result.cipa === true;
|
|
|
break;
|
|
break;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
case 'done': {
|
|
case 'done': {
|
|
|
const turn = this.turnToken;
|
|
const turn = this.turnToken;
|