diff --git a/TeXmacs/plugins/llm/goldfish/tm-llm.scm b/TeXmacs/plugins/llm/goldfish/tm-llm.scm index 16ccf5299e..1c1aafb7e9 100644 --- a/TeXmacs/plugins/llm/goldfish/tm-llm.scm +++ b/TeXmacs/plugins/llm/goldfish/tm-llm.scm @@ -27,6 +27,61 @@ (define *large-data-threshold* 1048576) +(define (localized key) + `(localize ,key) +) ;define + +(define (reasoning-title-tree) + `(with ,"color" + ,"dark grey" + ,"font-size" + ,"0.92" + ,"font-series" + ,"medium" + ,(localized "View reasoning")) +) ;define + +(define (reasoning-body-tree content) + `(with ,"color" + ,"dark grey" + ,"font-size" + ,"0.92" + ,"font-shape" + ,"italic" + (document ,content)) +) ;define + +(define *fake-reasoning-content* + "这是一段用于测试样式问题的推理过程文本。我们正在验证大语言模型插件在输出长文本推理内容时的排版与折叠表现,包括文字换行、行高、缩进、折叠按钮以及字体颜色等渲染细节是否正常。通过不换行的连续长文本输入,可以充分测试视图边界处的自动折叠换行机制,确保在各种窗口尺寸和缩放比例下,推理过程块的边框、背景及文字内容均能正确自适应布局,既不会超出容器边界,也不会出现文字截断或排版错乱的问题,从而为用户提供稳定美观的阅读体验。" +) ;define + +(define *llm-log-buf* "") + +(define (llm-log . parts) + (set! *llm-log-buf* + (string-append *llm-log-buf* (apply string-append parts) "\n") + ) ;set! + (path-write-text (path-join (path->string (path-temp-dir)) "llm-trace.log") + *llm-log-buf* + ) ;path-write-text +) ;define + +;; reasoning 主体必须内联在返回的 unfolded-explain 树里:reasoning-delta / +;; fold-explain-reasoning 是真实流式插件的边通道标记,需 mogan 侧拦截拼接, +;; 假插件经它们发出的 reasoning 不会进入最终返回的文档 + +(define (flush-fake-reasoning text) + (llm-log "plugin: flush-fake-reasoning len=" + (number->string (string-length text)) + ) ;llm-log + ;; 末尾空段:C++ 输入累积会把相邻非空节点并入同一 concat, + ;; 留空段让后续输出(%chat 回显)另起一段 + (flush-scheme `(document (unfolded-explain ,(reasoning-title-tree) + ,(reasoning-body-tree text)) + ,"") + ) ;flush-scheme +) ;define + (define (llm-write-temp-file data) (let* ((tmp-dir (path-temp-dir)) (tmp-name (uuid4)) @@ -73,6 +128,9 @@ (if (> (string-length data) *large-data-threshold*) (flush-verbatim (llm-write-temp-file data)) (let ((reply (and (string-starts? data "%chat ") (fake-llm-chat-reply data)))) + (when reply + (flush-fake-reasoning *fake-reasoning-content*) + ) ;when (flush-verbatim (or reply data)) ) ;let ) ;if diff --git a/devel/1295.md b/devel/1295.md new file mode 100644 index 0000000000..88a0a0df2e --- /dev/null +++ b/devel/1295.md @@ -0,0 +1,64 @@ +# [1295] 为 LLM 假插件增加推理过程回显以支持样式与排版验证 + +## 1 相关文档 +- [dddd.md](dddd.md) — 任务文档模板 +- [0957.md](0957.md) — chat 协议升级与假 goldfish 协议回传 +- [1262.md](1262.md) — 修复 chat-style 中插件样式包检测路径 +- `TeXmacs/plugins/llm/packages/session/llm.stem` — LLM 会话样式包定义 + +## 2 任务相关的代码文件 +- `TeXmacs/plugins/llm/goldfish/tm-llm.scm` — 假插件主程序:新增 `flush-fake-reasoning` 逻辑及 `unfolded-explain` 推理过程树构造,在响应 `%chat` 假回复前下发推理块。 + +## 3 如何测试 + +### 3.1 格式化与基础校验 +```bash +gf fmt --changed-since=main +``` + +### 3.2 手动测试 +1. 编译并启动 Mogan STEM: + ```bash + xmake b stem + xmake r stem + ``` +2. 打开 AI 聊天侧边栏,或通过菜单「插入 → 会话 → LLM」新建会话。 +3. 输入任意消息并发送(例如「你是谁」或「测试」)。 +4. 验证会话界面的样式渲染与排版行为: + - 消息回复上方正确出现「View reasoning」(或中文本地化「查看推理过程」)的可折叠/展开块; + - 标题样式为中等字重(`medium`)、字号 `0.92`、暗灰色(`dark grey`); + - 展开后的推理正文样式为斜体(`italic`)、字号 `0.92`、暗灰色; + - 在较窄的侧边栏窗口或改变主编辑区缩放比例时,长文本推理内容能自适应边界自动折叠换行,未出现容器溢出或文字截断错乱; + - 推理块与随后的 `%chat` 假回复文本之间有正确的分段间隔,未被拼接到同一行。 +5. 检查系统临时目录下的日志文件(如 `/tmp/llm-trace.log`),确认包含 `plugin: flush-fake-reasoning len=...` 记录。 + +## 4 如何提交 +```bash +gf fmt --changed-since=main +git add devel/1295.md +git commit -m "[1295] 补充任务文档" +``` + +## 5 What +1. 在 `TeXmacs/plugins/llm/goldfish/tm-llm.scm` 中为离线假插件增加 `flush-fake-reasoning` 逻辑,在返回 `%chat` 回显前模拟输出模型的思维链/推理过程(Reasoning)。 +2. 构建符合 Mogan 树规范的 `unfolded-explain` 折叠/展开树结构: + - 标题树 `reasoning-title-tree`:字体颜色为 `dark grey`,字号 `0.92`,字重 `medium`,文本支持本地化 `(localize "View reasoning")`; + - 正文树 `reasoning-body-tree`:字体颜色为 `dark grey`,字号 `0.92`,字形 `italic`,包裹长段中文测试文本 `*fake-reasoning-content*`; + - 在 `document` 节点末尾添加空字符串段落(`,""`),确保后续的 `%chat` 回复输出不会被 C++ 输入累积器并入同一个 `concat`。 +3. 增加轻量级临时调试日志输出(`llm-log`),将调用追踪写入系统临时目录的 `llm-trace.log`。 + +## 6 Why +1. **本地离线排版与样式验证**:具有思维链(Reasoning)的模型(如 DeepSeek-R1、Kimi 等)已成为主流,但在社区版或未配置网络 API 的本地开发环境中,LLM 插件仅能回显简短的纯文本,无法在无网或无 API Key 环境下测试推理折叠块(`unfolded-explain`)的样式表现。 +2. **布局与边界自适应测试**:推理过程往往包含长篇幅连续文字,需要测试在不同窗口尺寸(例如较窄的 Dock 侧边栏与宽幅编辑标签页)、不同缩放比例(zoom factor)下的自动折叠换行、行高、内边距与边框渲染,确保文本不会溢出容器边界或发生排版截断。 +3. **边通道流式协议与假插件的差异**:商业联网版本的流式推理输出依赖 `reasoning-delta` 与 `fold-explain-reasoning` 等边通道标记,由 C++ 层拦截并动态拼接;离线假插件属于单次同步交互,无法驱动流式拼接链路,必须以完整文档树(`unfolded-explain`)直接下发。 + +## 7 How +1. **构造折叠解释树(`unfolded-explain`)**: + - 标题使用 `(reasoning-title-tree)` 生成,嵌套 `with` 属性设置 `color`、`font-size` 和 `font-series`,标题文字经 `localize` 国际化处理。 + - 正文使用 `(reasoning-body-tree content)` 生成,通过 `(with ... (document content))` 保证样式与段落语义完整。 +2. **段落分隔机制**: + - Mogan 的 C++ 端 `texmacs_input_rep::scheme_flush` 会在输入累积过程中合并相邻的非空节点到同一 `concat` 中。 + - 若直接 `(flush-scheme '(document (unfolded-explain ...)))`,紧接着由 `flush-verbatim` 输出的回复文本会被拼接到同一个节点,导致推理块与回复粘连在同一行。 + - 通过在 `document` 末尾添加 `,""` 空段,告知排版引擎结束当前块并开启新段落,保证回复内容换行正常展示。 +3. **调用时机**: + - 仅当输入数据以 `%chat ` 开头且 `fake-llm-chat-reply` 成功解析生成回复时,触发 `(flush-fake-reasoning *fake-reasoning-content*)`,再执行 `(flush-verbatim (or reply data))`。普通非 `%chat` 请求保持原有处理逻辑不变。