# @cnki\_frontend/agent-chat 中文文档

规范地址：https://npmcn.com/package/%40cnki_frontend/agent-chat/

版本：0\.3\.10
npm 元数据更新时间：2026-11-10T02:19:58.985Z
中文译文更新时间：2026-11-10T04:39:02.287Z
软件包许可：未声明

本文由上游 README 翻译，可能存在偏差，请结合原文核对 API、命令与配置。版权与许可归原作者所有。

原始 npm 页面：https://www.npmjs.com/package/%40cnki_frontend/agent-chat
原文对照：https://npmcn.com/package/%40cnki_frontend/agent-chat/?lang=original

## 关键信息（来自 npm 元数据）

- 包体积（解压后）：2.9 MB，90 个文件
- 模块格式：ES Module
- TypeScript 类型：包内自带
- Node.js 要求：\>=18
- 运行依赖：4 个
- peer 依赖：vue ^3\.4\.0，pinia ^2\.1\.0，vditor ^4\.0\.0，mermaid ^11\.0\.0，vue-i18n ^11\.0\.0，vue-router ^4\.2\.0，markstream-vue ^2\.0\.0，docx-vue-viewer ^1\.1\.0，pptx-vue-viewer ^4\.58\.0
- 首次发布：2026-11-05T10:16:44.117Z
- 最新版本发布：2026-11-10T02:19:58.807Z
- 近 30 天下载量：2210（2026-10-10 至 2026-11-08，npm 官方下载统计）
- 已发布版本数：16（近 12 个月 16 个）

## 中文简介

按场景驱动的可嵌入 AI 问答组件：流式过程编排、文档呈现、多实例隔离

## 安装

```sh
npm install @cnki_frontend/agent-chat
```

也可以使用：`pnpm add @cnki_frontend/agent-chat`、`yarn add @cnki_frontend/agent-chat`、`bun add @cnki_frontend/agent-chat`

## README

# Agent Chat

AI 问答前端：**左侧会话历史 + 中间问答流 + 右侧过程/文档/溯源分栏**，按场景（scene）驱动的多实例可嵌入组件。

核心特点：

-   **按 Agent 步骤编排 UI**：一条 assistant 消息内部拆 thinking → tool\_call → answer；流式过程中逐步展开，完成后过程区折叠成一行摘要（步数 / 工具数 / 耗时），可点开回看，回答正文常驻
-   **回答正文 `/ 过程` 双区 + 文档呈现区**：正文里的链接按需打开右侧面板，支持 Markdown / Word / PPT 内联预览、溯源清单，宽度可拖拽持久化
-   **单流控制**：每个实例同一时刻只允许一条流在跑，启动新的会先中断旧的并标记 `interrupted`；支持用户中断、复制回答、失败重试、重新生成、溯源查看
-   **完全隔离的 scope**：内部自建 pinia / router / api / 流控制器，**不 `app.use()`**，不占用宿主 URL（默认 memory history），同一页面可放多个实例互不干扰

## 快速开始

```bash
npm install
npm run dev          # http://localhost:5173
```

Demo（`playground/APP.vue`）复现宿主 `src/views/chatView.vue` 的接入方式（单实例 + 跨窗口 postMessage 协议），无需宿主工程即可联调。

Mock 开关走环境变量 `VITE_USE_MOCK`，**默认 false（走真实接口）**：

```bash
$env:VITE_USE_MOCK = "true"; npm run dev        # PowerShell
VITE_USE_MOCK=true npm run dev                  # bash / zsh
```

打开 Mock 后接口走本地假数据，右侧呈现面板、过程动画、失败注入都可以脱离后端直接验证。

### 会话写接口用 mock、其余走真实后端

后端往往先出列表 / 详情 / 流式，而新建、重命名、删除后补。这时把 `config.mockSessionApi` 打开（**默认 true**）： 新建 / 重命名 / 删除走本地假数据，会话列表（`POST /dsh/session/pageList`）、 详情（`GET /dsh/session/upstreamHistory` 历史回放）与提问流（`POST dsh/chat/stream`）都走真实后端。 后端写接口就绪后把它置为 `false` 即可，无需改代码。

```vue
<AgentChat scene="default" :config="{ mock: false, mockSessionApi: true }" />
```

## 本地联调示例（playground）

包内自带一个可独立运行的联调 demo（`playground/`），完整复现宿主 `src/views/chatView.vue` 的接入方式，便于不启动宿主工程就验证跨窗口协议、`AgentChatHandle` 会话桥与样式。该目录已纳入 `files` 白名单随包发布，安装后可在 `node_modules/@cnki_frontend/agent-chat/playground/` 看到源码。

### 运行

```bash
npm run dev          # 启动 playground，默认 http://localhost:5173
```

`npm run dev` 走 `vite.config.ts`（已挂 `@vitejs/plugin-vue` 且根目录设为 `playground`），无需宿主工程。

### 两种调试视图

playground 通过同一个 import 语句 `import AgentChat, { type AgentChatHandle } from '@cnki_frontend/agent-chat'` 切换视图，由环境变量 `AGENT_CHAT_USE_DIST` 控制：

| 模式 | 命令 | 解析到 | 用途 |
| --- | --- | --- | --- |
| 源码（默认） | `npm run dev` | 别名 → `./index.ts` | 改 `src/` 实时热更，调试最新实现 |
| 打包产物 | `AGENT_CHAT_USE_DIST=1 npm run dev` | 别名 → `./dist` | 验证「安装后的包」真实行为（需先 `npm run build` 出 dist） |

> 与宿主 `vite.config.ts` 的 `AGENT_CHAT_USE_DIST` 开关一致：开发态引源码、生产态引 dist。

### 目录内容

-   `playground/APP.vue`：`chatView.vue` 的接入骨架——`agentChatRef` 接组件 `defineExpose` 句柄、挂载后写入 `agentChatHandle`、消费 `pendingSessionId`、监听 `message`（`onHostMessage`）、向父页 `postMessage('ready')`、401 时 `postMessage('needLogin')`。
-   `playground/main.ts`：引入 `@cnki_frontend/agent-chat/style.css`（别名已覆盖 source / dist 两种来源）。
-   `playground/config/scenes/jgxkdmx.json`：示例场景配置。

> `agentChatBridge` 的两个变量（`agentChatHandle` / `pendingSessionId`）在 playground 里是**就地内联**的 `ref`，并未从宿主导入——它们只是两个响应式句柄，移交给宿主桥接层即可。

### 作为使用者如何运行

从 npm 安装本包后想直接跑这个 demo，需自行补上运行依赖（它们是包的 devDependencies，不随安装带入），二选一：

```bash
# 方案 A：在包目录内补装并运行
cd node_modules/@cnki_frontend/agent-chat
npm i -D vite @vitejs/plugin-vue
npm run dev

# 方案 B：把 playground/ 拷到自己的项目，再补装上述依赖后运行
```

## 开发约定（改本仓库前必读）

### 1\. 引用文件**必须**使用相对路径

```ts
import type { AgentStep } from '../../types/session'   // ✅
import { useScopedSessionStore } from '../../scope/useScope'

import type { AgentStep } from '@/types/session'       // ❌ 不许用别名
import { XXX } from '@pkg/scope/useScope'              // ❌ 同上
```

具体写法：

-   同一子树内部：`./MessageList.vue`、`./useScope`
-   跨顶层目录（`types` / `scope` / `stores` / `composables` / `api` / `utils` / `mocks`）：按文件所在深度取 `./` `../` `../../`，**最深只有两层**
-   不要绕到根再绕回来：`components/chat/` 里引文档面板应写 `../document/DocumentPanel.vue`，而不是 `../../components/document/DocumentPanel.vue`
-   代价是**移动或新建文件要同步改导入**，改完跑一次 `npm run build`

### 2\. agent-chat 的业务代码**一律在本仓库内修改**

> **本仓库是唯一真源。** 任何问答、过程、文档呈现、调用链路相关的改动，都在本仓库源码里做，改完再同步给宿主。 **不要**把 `src/` 拷进宿主后就地改一版了事——下次同步会把这些改动直接覆盖掉。

宿主侧允许做的事只有三件：安装依赖、引入样式、通过 props（`scene` / `config` / `routerMode` / `storage`）做定制。场景级差异优先走 props 与 `src/scope/scenes.ts` 里新增场景配置，而不是在宿主里 fork 一份组件改。

## 嵌入到宿主项目

**宿主工程只通过安装 `@cnki_frontend/agent-chat` 包的方式引入 agent-chat**：`npm i @cnki_frontend/agent-chat` 后，在宿主侧 `import AgentChat from '@cnki_frontend/agent-chat'`，并按下方「2）引入样式」在**宿主入口 `main.ts`** 引入样式即可。**不要**把 `src/` 整坨拷进宿主——包内已自包含全部代码与资源，宿主零源码改造。

**1）安装依赖**（包本身不内置运行时依赖，相关库作为对等依赖由宿主提供）：

```bash
npm i @cnki_frontend/agent-chat
# 以下为对等依赖，宿主需自行安装（版本见 @cnki_frontend/agent-chat 的 peerDependencies）
npm i vue pinia vue-router axios @microsoft/fetch-event-source markstream-vue vditor docx-vue-viewer pptx-vue-viewer
```

**2）引入样式**（宿主需自行安装 `@cnki_frontend/agent-chat` 包）：

```ts
// 宿主 src/main.ts
import '@cnki_frontend/agent-chat/style.css'
```

> 切勿用 `vite-plugin-lib-inject-css` 做运行时内联注入：它对懒加载 chunk 的 CSS 会迟于首帧应用，导致 pptx-vue-viewer 这类「按容器尺寸自适应」的 viewer 在首帧 无样式状态下测量布局、触发 ResizeObserver 循环 → 页面持续闪烁（word 不受影响， 因其为静态文档、不做 fit-to-container）。本包的 `exports` 已暴露 `./style.css`。

**3）使用组件**：

```vue
<script setup lang="ts">
import AgentChat from '@cnki_frontend/agent-chat'
</script>

<template>
  <!-- 父容器需要给定高度：组件根是 height:100% -->
  <div style="height: 100vh">
    <AgentChat scene="analysis" :config="{ placeholder: '输入你的问题' }" />
  </div>
</template>
```

> **不要在宿主 app 上 `app.use(pinia)` / `app.use(router)` 来"接管"组件**：scope 内部的 pinia 与 router 都下发在其组件子树上，宿主全局实例与它互不感知，这是刻意设计。

### 参数（AgentChatOptions）

| 参数 | 类型 | 默认 | 说明 |
| --- | --- | --- | --- |
| `scene` | `string` | 必填 | 业务场景标识，决定接口参数、文案与 mock 数据 |
| `scopeId` | `string` | 取 `scene` | 同场景同屏多实例时用于区分（影响 localStorage key） |
| `routerMode` | `'memory' | 'web'` | `'memory'` | `memory` 不占用宿主 URL（默认，多实例安全）；`web` 读写地址栏，**同页面只允许一个 web 实例**（`window.history` 是浏览器单例，base 冲突时开发模式会告警） |
| `base` | `string` | `web` 模式为 `/<scene>`，否则 `/` | router 基路径 |
| `config` | `Partial<SceneConfig>` | — | 覆盖该场景的 `title` / `placeholder` / `samples` / `apiBase` / `mock` / `mockSessionApi` |
| `storage` | `Storage` | `localStorage` | 呈现区宽度等本地持久化介质的注入点 |
| `request` | `RequestOptions` | — | 请求定制：`headers()`（可 async）/ `withCredentials` / `onError`（统一异常出口，见「统一鉴权」） |
| `api` | `Partial<ChatApi>` | — | 数据端口逃生舱，按 key 覆盖任意方法（见「接口契约」） |

`scene` 是**构造参数而非响应属性**：运行时切换请给组件加 `:key` 重建实例。

### 宿主操作句柄（AgentChatHandle）

通过组件 `ref` 拿到操作句柄，**只暴露方法、不暴露内部 store**，宿主据此驱动会话而不耦合 agent-chat 的 Pinia 实例：

```vue
<script setup lang="ts">
  import { ref } from 'vue'
  import AgentChat, { type AgentChatHandle } from '@cnki_frontend/agent-chat'  // 也可直接引 './packages/agent-chat/AgentChat.vue'
  const agentChatRef = ref<AgentChatHandle | null>(null)
  // 组件挂载后即可通过 ref 操作；方法内部自行同步 store / 路由
</script>

<template>
  <div style="height: 100vh">
    <AgentChat ref="agentChatRef" scene="analysis" />
  </div>
</template>
```

| 方法 | 返回 | 说明 |
| --- | --- | --- |
| `getSessions()` | `Ref<Session[]>` | 会话列表（响应式；宿主读 `.value` 随 agent-chat 内部变更实时更新） |
| `getCurrentId()` | `Ref<string | null>` | 当前选中会话 id（`null` = 首页 / 新对话态） |
| `getGeneratingIds()` | `Ref<Set<string>>` | 生成中会话 id 集合（**远端轮询 ∪ 本地流** 的并集）；宿主侧边栏据此显示「忙」图标。问答 `finish`/`fail`/`stop` 时会立即触发一次轮询刷新，无需等下一周期 |
| `openSession(id)` | `void` | 打开指定会话（走 agent-chat 内部路由跳转） |
| `startNew()` | `void` | 回首页 / 新对话态，清空当前会话 |
| `createSession(title?)` | `Promise<Session | null>` | 立即新建空会话并返回 |
| `loadMoreSessions()` | `Promise<void>` | 加载下一页（内部有 `loading`/`hasMore` 保护，重复调用无副作用） |
| `deleteSession(id, title?)` | `Promise<void>` | 删除会话（调后端 + 本地清理 + 提示，同一套逻辑） |
| `renameSession(id, title)` | `Promise<void>` | 重命名会话（同一套逻辑） |
| `ask(input)` | `void` | 在当前会话发起一轮提问（与输入框同一条流式链路；`sending` 中调用被内部守卫忽略） |
| `askNew(prompt, title?, model?)` | `Promise<Session | null>` | 传提示词**自动新建会话**（后端建会话须带 prompt + model）并进入该会话、流式补全首轮回答（链路同首页首问：`createSession(prompt, model)` → `setPendingPrompt` → 跳转由 ChatView 拉流）。`model` 省略用场景默认模型；可选 `title` 建完用 `renameSession` 补标题（库无「建会话带标题」API）。每次新建互不干扰，返回新建会话（失败 `null`） |
| `getPaging()` | `SessionPaging` | 分页状态（`hasMore` / `loading`），用于「加载中 / 没有更多了」 |
| `setSearchKeyword(keyword)` | `void` | 设置会话列表标题筛选关键字；空串取消筛选、回到「最近生成」 |

> **跨窗口下发提示词**：agent-chat 库**不内置**入站 `postMessage` 监听；iframe / 新窗口场景下，宿主应在自己的 `message` 处理器里，对拿到的 `AgentChatHandle` 调用 `askNew(prompt)`（参考 `playground/APP.vue` 的 `onHostMessage` 写法）。

### 跨窗口消息协议（iframe 嵌入）

当 agent-chat 被放进 `iframe` 嵌入宿主页面时，双方用 `window.postMessage` 通信（**不**依赖 `$bus`/`$emit`，跨 window 无效）。

**agent-chat / 嵌入页 → 宿主（父窗口）**：以下消息一律以 **JSON 字符串** 发出（`JSON.stringify`），宿主 `window` 监听后需 `JSON.parse`。注意宿主既有 `_onMessage` 对 `typeof === 'object'` 的消息直接 `return`，故 agent-chat 侧**必须发字符串**。

| cmd | payload | 含义 / 宿主动作 |
| --- | --- | --- |
| `agentChatSidebar` | `{ cmd:'agentChatSidebar', source:'agent-chat', collapsed: boolean }` | agent-chat 内部会话侧边栏收起 / 展开；宿主可据此隐藏 / 显示自己的会话侧边栏，避免两套并列 |
| `needLogin` | `{ source:'agent-chat', cmd:'needLogin' }` | 401 时由嵌入页 `dealError` 发出；宿主应触发登录流程 |
| `ready` | `{ source:'agent-chat', cmd:'ready' }` | 嵌入页 `onMounted` 注册完 message 监听后发出；**宿主收到后才可安全下发指令**（见下） |

**宿主 → agent-chat（经嵌入页监听器）**：以下消息可为**对象或字符串**（嵌入页 `onHostMessage` 两者都解析）；字段约定 `source` + `cmd`。

| cmd | payload | 含义 / 动作 |
| --- | --- | --- |
| `goHome` | `{ source:'chat_web', cmd:'goHome' }` | 触发 `agentChatRef.startNew()`，回首页（**不重载 iframe**，保留内存态） |

**握手（避免竞态）**：本应用 `src/main.ts` 有顶层 `await loadRuntimeConfig()`，`app.mount()` 在其后才执行，故嵌入页 `onMounted`（message 监听注册）晚于 iframe 的 `load` 事件。若宿主在自身 `onMounted` / `@load` 直接 `postMessage`，消息会在嵌入页监听就绪**之前**到达并被丢弃（`postMessage` 不缓存）。推荐做法：宿主监听 `ready`，**收到 `ready` 后再下发 `goHome`**；未就绪前的指令先排队，收到 `ready` 后补发——无论 iframe 加载多慢都 100% 收到。

## 关键约定

### 接口契约

**组件对外部世界的唯一依赖是 `ChatApi` 这 6 个方法**（定义在 `src/api/index.ts`），不含任何 HTTP / URL / 鉴权细节：

```ts
interface ChatApi {
  listSessions: () => Promise<Session[]>
  createSession: (title?: string) => Promise<Session>
  renameSession: (id: string, title: string) => Promise<void>
  deleteSession: (id: string) => Promise<void>
  fetchSessionDetail: (id: string) => Promise<SessionDetail>
  streamChat: (req: StreamChatRequest) => StreamHandle
}
```

默认实现：**REST 走 `axios`（`src/api/http.ts`），流式走 `@microsoft/fetch-event-source`（`src/api/sse.ts`）**。`scene` **不拼进 URL 路径**，一律作参数（GET 走 query、写操作与流式走 body）：

| 方法 | 路径 | 参数 | 说明 |
| --- | --- | --- | --- |
| `POST` | `{apiBase}/dsh/session/pageList` | `{ pageNum, pageSize }` | 会话列表 |
| `POST` | `{apiBase}/sessions` | `{ title }` | 新建会话，**由后端返回 id** |
| `PATCH` | `{apiBase}/sessions/:id` | `{ title }` | 重命名 |
| `DELETE` | `{apiBase}/sessions/:id` | — | 删除 |
| `GET` | `{apiBase}/dsh/session/upstreamHistory` | `sessionId` / `pageNum` / `pageSize` | 详情 = 历史回放：一行一轮（question + answer），翻页合并后还原成 messages |
| `POST` | `{apiBase}/dsh/chat/stream` | multipart：`prompt` / `sessionId` / `moduleCode` / `featureCode` | 流式问答，返回 SSE 流 |

历史回放**以 `messages[].evens` 为渲染源**：里面存的是当时那帧 SSE（与实时流返回一致），逐条喂给**与实时流同一个**解析器（`src/api/agentStream.ts`），因此历史消息的步骤构成、步骤 id 与实时一致。

同行的 `answer` / `artifacts` 只是**兜底**：只有当 `evens` 缺失或一条都没回放出来（老会话没存事件）时才使用，避免用字段值覆盖掉事件里更完整的思考 / 工具过程。

流式请求走 SSE，**事件名在 `event:` 上**（服务端不发 event 名时退回 payload 的 `type` 字段）。解析器（`src/api/agentStream.ts`）先把三种帧形态归一成 `{type, data}` 再分发：旧 `{payload:{event:{type,data}}}`、整体序列化 `{type,data}`、新 `event:<名称>` + 帧体即负载。

| event | data | 落到哪里 |
| --- | --- | --- |
| `start` | `{ session_id }` | 顶层：记录会话 id |
| `assistant/message` | `{ step, message:{content:[…]} }` | 一条 assistant 快照：`reasoning`/`thinking` → 思考 step、`text` → 回答正文、`tool-call` → 工具调用 step |
| `tool/call` | `{ step, callId, name, arguments }` | 工具调用（补全 `assistant/message` 里缺的工具名 / 入参） |
| `tool/result` | `{ callId, result`|`result_summary, isError }` | 工具回包：落到对应 tool\_call step 的 `toolOutput`，`isError` 时置 error |
| `todo/write` | `{ todos:[{ content, status }] }` | 任务清单 → todo step |
| `subagent/catalog` | `{ childId, mode, label }` | 子代理身份，挂到最近一次 subagent 工具调用上 |
| `session/title` | `{ title }` | 会话标题 |
| `step/end` | `{ step }` | 步骤边界：把该 step 分组的 step 标记 done |
| `compaction/start` | `compaction/end` | — | 上下文过长自动压缩的提示（notice） |
| `make-answer` | `MakeAnswerData` | 结构化确认卡 → `make_question` step（见「make-answer 结构化确认交互」） |
| `done` | `{ final_response?, artifacts`|`deliverables? }` | 顶层：`final_response` 为最终答案，产物名单写入 message 级注册表，随后收尾 |
| `error` | `{ code, detail, http_status }` | 顶层：置 error 态，出现重试入口 |
| `server/queued` | — | 顶层：实例繁忙排队提示 |

另有**仅识别、不产出 step** 的流水事件：`user/message`、`system/message`、`agent/inbox/spliced`、`request/header`、`request/context`、`session.status`、`turn/start`、`turn/end`、`step/start`、`tool/error`（工具错误走 `tool/result` 的 `isError`）。

后端形状对不上时，**用 props 注入自己的实现，而不是把源码拷过去改**：

```vue
<AgentChat
  scene="analysis"
  :request="{
    headers: async () => ({ Authorization: `Bearer ${await getToken()}` }),
    withCredentials: true,
    onError: (err) =>
      (err.status === 401 || err.status === 403) && router.push('/login'),
  }"
  :api="{ listSessions: myList, streamChat: mySse }"
/>
```

`api` 是按 key 覆盖（partial），未列出的方法仍走默认 HTTP 实现；流式要回调的领域事件见 `src/api/sse.ts`：`onStep`、`onStepUpdate`、`onDocuments`、`onTraces`、`onDone`、`onError`。

请求 / 流失败统一归一为 `ApiError`（带 `status` / `url` / `payload`），不需要宿主认识 axios / SSE 的结构。

store 侧的写操作统一走「乐观更新 → 调接口 → 失败回滚」：重命名/删除失败会自动还原，新建失败不会跳进一个后端并不存在的会话 URL。

### 统一鉴权

组件**不内置任何凭证逻辑**，鉴权全部由 `request` props 注入，而且只有一个出口：

```ts
interface RequestOptions {
  /** 每次请求前调用，返回要附加的 header；支持 async（便于过期再取 / 刷新 token） */
  headers?: () => Record<string, string> | Promise<Record<string, string>>
  /** 跨域请求携带 cookie */
  withCredentials?: boolean
  /**
   * 统一异常出口：所有请求 / 流失败（含 401 / 403、网络失败、服务端 error 帧）
   * 归一为 ApiError 后回调，由宿主统一处理（跳登录 / 刷新凭证 / 全局提示）。
   */
  onError?: (err: ApiError) => void
}
```

-   **REST**：axios 请求拦截器注入（见 `src/api/http.ts`）
-   **SSE**：`fetch-event-source` 内部自建 fetch、不过拦截器，但取的是同一个 `buildHeaders()` 钩子

因此不会出现「REST 带了 token、流式没带」这类漂移。每个 scope 一个 axios 实例（不用全局默认实例），多实例之间互不污染。

### 文档链接 `doc://<id>`

assistant 正文中的链接会被事件委托拦截并打开右侧呈现区，支持三种写法（`src/utils/docLink.ts`）：

-   `doc://<id>`：从本轮消息的 `documents` 注册表取实体
-   带扩展名的 URL：按扩展名推断类型（`.md/.txt` → markdown，`.docx` → word，`.pptx` → ppt）
-   其它：降级为「打开/下载」

回答末尾的产物列表每行带「预览 / 下载」两个按钮（`components/chat/AssistantMessage.vue`）：预览走与正文链接同一条路（锁定该产物后打开右侧呈现区），下载走 `utils/download.ts`（先 fetch 成 blob 再落盘，保证用原始文件名；地址不可 fetch 时退回直接打开）。后端没返回 `url` 的产物两个按钮都禁用。

### make-answer 结构化确认交互

后端可在流里发一个 `make-answer` 事件，让助手在生成前先与用户确认细节。前端把它渲染成一张**确认卡**（`src/components/chat/MakeAnswerCard.vue`），常驻在正文区、不进折叠过程区：

-   **载荷 `MakeAnswerData`**：`title`（引导文案）、`fields:[{label,value}]`（课程/章节/题型/知识点等键值明细）、`selectLabel`（可选项标题）、`options:[{id,label}]`（可选项）、`skipText`（动作按钮文案）、`multiple`（是否多选，默认 true）、`answerPrefix`（确认提问前缀）、`skipPrompt`（跳过提问文案）
-   **交互**：chips 多选 + 单个动作按钮（无独立确认按钮）。点按钮即**发起一轮新问答**：有选中 → 新问题为 `{answerPrefix}选中{label、label}`（如 `知识点选中需求曲线`）；未选 → 新问题为 `{skipPrompt}`（默认 `跳过`）。`answerPrefix` 缺省时从 `selectLabel` 推导（去括号、去尾部「范围」，如 `知识点范围（可多选）` → `知识点`）
-   **接线**：`make-answer` 在 `src/api/agentStream.ts` 解析为 `kind:'make_question'` 的 step（载荷落 `AgentStep.makeAnswer`）；`AssistantMessage.vue` 渲染卡片并经 `ask()` 以 prompt 文本通道发起新一轮（后端当前只有 prompt 这一个文本通道）
-   **Mock**：`src/mocks/stream.json` 的 `makeAnswer` 即该卡演示数据，首轮提问会出现

### 路由即真相源

`/` 首页，`/chat/:id?` 问答页（id 为空时自动落到会话并 replace 成带 id 的 URL）。store 单向跟随路由同步，不反向决定路由。

### 文档渲染器（按类型分流，不混用）

| 位置 | 渲染器 | 原因 |
| --- | --- | --- |
| 回答正文（历史消息） | `markstream-vue` | 需要流式增量渲染 |
| 呈现区 Markdown 文档 | `vditor`（`ir` 即时渲染） | 所见即所得，可直接在渲染结果上编辑 |
| 呈现区 Word 文档 | `docx-vue-viewer` 的 `WordEditor` | DOCX 内联预览 / 编辑 |
| 呈现区 PPT 文档 | `pptx-vue-viewer` 的 `PowerPointViewer` | PPTX 内联预览 |
| 呈现区 JSON | `<pre>` 纯文本（`JsonPreview.vue`） | 缩进后原样铺开，不做树形 / 高亮 |

## 常用命令

```bash
npm run dev       # 开发服务器
npm run build     # vue-tsc --noEmit && vite build
npm run preview   # 预览构建产物
```

## 常见问题

### @cnki\_frontend/agent-chat 是什么？

按场景驱动的可嵌入 AI 问答组件：流式过程编排、文档呈现、多实例隔离

### 如何安装 @cnki\_frontend/agent-chat？

在项目目录运行 npm install @cnki\_frontend/agent-chat，或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖：vue（^3\.4\.0）、pinia（^2\.1\.0）、vditor（^4\.0\.0）、vue-i18n（^11\.0\.0）、vue-router（^4\.2\.0）、markstream-vue（^2\.0\.0）、docx-vue-viewer（^1\.1\.0）、pptx-vue-viewer（^4\.58\.0）。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖，Yarn 需要手动安装。

### @cnki\_frontend/agent-chat 支持 TypeScript 吗？

支持。包内自带 TypeScript 类型声明（package\.json 声明了 types 入口），安装后即可获得类型提示，无需再安装 @types 包。

### @cnki\_frontend/agent-chat 支持 ESM 还是 CommonJS？

以 ES Module 形式发布，推荐使用 import 引入。在 CommonJS 项目中，Node\.js 20\.19\+ / 22\.12\+ 可以直接 require\(\) 不含顶层 await 的 ES Module，更早的版本需要改用动态 import\(\)。

### @cnki\_frontend/agent-chat 需要什么版本的 Node\.js？

package\.json 的 engines 字段要求 Node\.js \>=18。版本不符时 npm 默认只给出警告，开启 engine-strict 后会拒绝安装。

### @cnki\_frontend/agent-chat 可以商用吗？

@cnki\_frontend/agent-chat 使用 未声明 许可。package\.json 没有声明许可证。没有许可证的代码默认保留全部权利，商用前请联系作者或查看仓库中的 LICENSE 文件。以上为概括，不构成法律意见，请以许可原文为准。

### @cnki\_frontend/agent-chat 的下载量有多少？

根据 npm 官方下载统计，@cnki\_frontend/agent-chat 在 2026/10/10 – 2026/11/08 这 30 天内被下载 2,210 次，日均约 74 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入，适合横向比较热度，不能直接等同于使用人数。

### @cnki\_frontend/agent-chat 还在维护吗？

最新版本 0\.3\.10 发布于 2026/11/10（今天）；至今共发布 16 个版本，其中过去 12 个月发布了 16 个；首次发布于 2026/11/05。发布频率只是参考信号：功能稳定的工具可能很少需要更新，建议结合源码仓库的 issue 和提交记录判断。

