Files
Auto-Finance/src/web/static/js/chat

chat/ 模块说明

目录


架构概览

chat/
├── renderer.js      # 底层 DOM 渲染工具
├── persistent.js    # 持久消息(永久保留在聊天历史中)
├── ephemeral.js     # 瞬态消息(只显示最新一条,新状态覆盖旧状态)
└── stream.js        # LLM 流式消息(处理中显示思考过程,结束后固化)

chat.js              # 入口文件,统一 re-export 所有公开函数

设计原则:将持久消息瞬态消息分离,避免聊天历史被中间状态消息堆积。


模块职责

renderer.js

最底层渲染工具,不维护任何状态。提供:

  • getChatMessages() — 获取 #chat-messages 容器
  • scrollToBottom() — 滚动到底部
  • createChatBubble(text, type, isUser) — 创建消息气泡 DOM
  • createSystemBubble(text, type) — 创建系统气泡 DOM
  • appendMessage(wrapper) — 将消息追加到容器并滚动

persistent.js

管理永久保留在聊天历史中的消息:

  • addChatMessage(text, type, isUser) — 添加普通聊天消息
  • addTypingIndicator() / removeTypingIndicator() — 加载三点动画
  • addFileMessage(filename) — 添加文件上传消息
  • setFileProcessing(filename) — 文件处理中状态
  • setFileDone(filename, summary) — 文件处理完成,展开提取摘要
  • setFileCached(filename) — 文件使用缓存
  • setFileError(filename, errorMsg) — 文件处理错误

依赖 App.fileMessageMap(定义在 state.js)维护文件名到 DOM 元素的映射。

ephemeral.js

管理瞬态状态消息,同一时刻只显示最新一条:

  • showStatus(text, type) — 显示/更新状态消息
  • clearStatus() — 清除当前状态消息

内部维护 ephemeralState 对象记录当前活跃的状态气泡引用。重复调用 showStatus 时直接更新已有气泡的文本和样式,不创建新 DOM。

stream.js

管理 LLM 流式响应的气泡生命周期:

  • handleLLMStream(msg) — 根据 SSE 事件阶段分发处理

支持的阶段:

phase 说明
start 创建流式气泡,初始化思考过程区域(默认展开)
reasoning 追加思考过程文本
chunk 追加正式回复文本
end 关闭气泡,切换为 done 样式,折叠思考过程区域,气泡保留在历史中
error 切换为 error 样式,显示错误信息

内部维护 llmStreamState 对象记录当前活跃的流式气泡引用。


消息类型

持久消息

永久保留在聊天历史中,不会被自动清除:

  • 用户输入的文字
  • 文件上传记录及其处理状态
  • LLM 流式响应的最终结果(end 阶段后固化)
  • 系统通知(如"信息完整,可以提交"
  • 错误消息

调用 addChatMessage()addFileMessage() 创建。

瞬态消息

只显示最新一条,新状态覆盖旧状态:

  • "正在分析文件..."
  • "正在校验信息完整性..."
  • "请输入登录账号:"
  • "配置信息已完整,开始处理发票……"
  • "收到补充文件,正在重新分析..."

调用 showStatus() 创建/更新,调用 clearStatus() 清除。


数据流

外部模块 (agent.js / config.js / process.js / upload.js / sync.js)
    │
    ├── import { addChatMessage, addFileMessage, ... } from './chat.js'
    ├── import { showStatus, clearStatus } from './chat.js'
    └── import { handleLLMStream } from './chat.js'
    │
    ▼
chat.js (re-export)
    │
    ├── → chat/persistent.js ──→ chat/renderer.js
    ├── → chat/ephemeral.js   ──→ chat/renderer.js
    └── → chat/stream.js      ──→ chat/renderer.js
  • 外部模块统一从 chat.js 导入函数
  • chat.js 只做 re-export不引入循环依赖
  • 三个子模块通过 renderer.js 共享底层 DOM 操作
  • persistent.js 额外依赖 state.jsApp.fileMessageMap

状态管理

ephemeralState (ephemeral.js)

{
  wrapper: HTMLElement | null,   // 状态消息的 wrapper 元素
  bubble: HTMLElement | null,    // 状态气泡元素
}
  • 初始为 null
  • showStatus 首次调用时创建并记录引用
  • 后续调用直接更新 bubble.textContentbubble.className
  • clearStatus 时移除 DOM 并重置为 null

llmStreamState (stream.js)

{
  wrapper: HTMLElement | null,           // 流式消息 wrapper
  bubble: HTMLElement | null,            // 流式气泡
  textContent: HTMLElement | null,       // 正式文本容器
  accumulated: string,                   // 累积的正式文本
  reasoningContent: HTMLElement | null,  // 思考过程容器
  reasoningAccumulated: string,          // 累积的思考文本
}
  • start 阶段创建并记录引用
  • chunk / reasoning 阶段追加文本
  • end / error 阶段重置为 nullDOM 保留在历史中)

App.fileMessageMap (state.js)

Map<string, { wrapper, bubble, nameRow, detailRow }>
  • 文件名 → DOM 元素映射
  • addFileMessage 创建时写入
  • setFileProcessing / setFileDone / setFileCached / setFileError 读取并更新对应文件的状态

注意事项

  1. 不要直接操作 #chat-messages 容器。所有消息创建都通过本模块的 API 进行。

  2. 瞬态消息和持久消息不要混用。中间处理状态用 showStatus,最终结果用 addChatMessage

  3. showStatus 不需要手动清除。调用 showStatus 显示新状态时会自动覆盖旧状态;在处理流程结束时,后续的消息或状态会自然覆盖。

  4. LLM 流式气泡在 end 阶段后变为持久消息。不需要额外调用 addChatMessage 来保留结果。

  5. SSE 事件必须包含 startend 阶段。缺少 start 会导致气泡未创建,缺少 end 会导致气泡一直处于处理中状态。详见 .agents/docs/error-experience/2026-06-13-llm_query_text缺少start-end事件导致前端不显示.md

  6. handleLLMStream 不处理普通日志行。SSE 的 message 事件中,只有 type === 'llm_stream' 的事件才会被转发到此模块。

  7. 文件消息的 DOM 生命周期由 fileMessageMap 管理。文件处理完成后,摘要信息会展开显示在文件气泡下方。

  8. 所有模块通过 chat.js 统一导入,不要直接从 chat/ 子目录导入(外部模块层面)。子模块之间的内部导入不受此限制。