Files
llm-in-text/plans/ghost-text-markdown-rendering.md
“ydy0615” 7cddfaba30 feat(editor): add Markdown rendering for ghost text and optimize prompt system
- Implement Markdown parsing for ghost text using Milkdown parser
- Add support for both block and inline content in ghost text
- Refactor prompt system with comprehensive rules and examples
- Adjust LLM parameters: increase temperature to 0.7, add repeat_penalty
- Add CSS styles for formatted ghost text (bold, italic, code, links)
- Add documentation for Copilot prompt system and ghost text rendering
2026-02-13 21:17:45 +08:00

4.0 KiB
Raw Blame History

虚拟文本 Markdown 渲染解决方案

问题分析

当前虚拟文本(灰色字)无法正确渲染 Markdown 和换行符,根本原因是:

  1. 纯文本插入insertGhostText 使用 tr.insertText() 直接插入纯文本
  2. 绕过解析器:文本未经过 Milkdown 的 Markdown 解析流程
  3. 节点结构错误\n 字符被当作普通字符,而非创建新段落节点

解决方案架构

flowchart TB
    subgraph 当前流程
        A1[LLM 返回 Markdown] --> B1[insertText 直接插入]
        B1 --> C1[添加 copilot_ghost mark]
        C1 --> D1[显示为灰色纯文本]
    end
    
    subgraph 新流程
        A2[LLM 返回 Markdown] --> B2[调用 parserCtx 解析]
        B2 --> C2[生成 ProseMirror 节点]
        C2 --> D2[为所有节点添加 ghost 属性]
        D2 --> E2[插入到文档]
        E2 --> F2[显示为格式化灰色文本]
    end
    
    style D1 fill:#f99
    style F2 fill:#9f9

技术方案

方案一:使用 Milkdown Parser 解析(推荐)

优点

  • 完整支持 Markdown 语法
  • 与编辑器行为一致
  • 自动处理换行

实现步骤

  1. 获取 parserCtx 从 Milkdown 上下文
  2. 使用 parser 将 Markdown 解析为 ProseMirror Fragment
  3. 遍历所有节点,添加 copilot_ghost mark
  4. 使用 tr.replaceWith() 插入节点

方案二:使用 Decoration API备选

优点

  • 不修改实际文档内容
  • 更轻量级

缺点

  • 实现复杂
  • 可能与某些功能冲突

详细实现计划

步骤 1修改 copilotPlugin.ts

需要修改以下部分:

// 新增导入
import { parserCtx } from '@milkdown/kit/core'

// 修改 insertGhostText 函数
async function insertGhostText(view: EditorView, suggestion: string, from: number) {
  if (!currentCtx || !suggestion) return
  
  const schema = view.state.schema
  const markType = schema.marks.copilot_ghost
  
  if (!markType) return
  
  // 使用 parser 解析 Markdown
  const parser = currentCtx.get(parserCtx)
  const doc = await parser(suggestion)
  
  if (!doc) return
  
  // 为所有文本节点添加 ghost mark
  const ghostDoc = doc.descendants((node, pos) => {
    if (node.isText) {
      // 添加 mark
    }
  })
  
  // 插入节点
  const tr = view.state.tr
  tr.replaceWith(from, from, ghostDoc.content)
  tr.setMeta(COPILOT_PLUGIN_KEY, { from, to: from + doc.content.size, suggestion })
  view.dispatch(tr)
}

步骤 2处理换行符

换行符处理策略:

换行类型 处理方式
单个 \n 创建 hard_break 节点
双个 \n\n 创建新段落节点
列表项换行 创建新列表项节点

步骤 3样式处理

需要修改 CSS 以支持格式化的虚拟文本:

.copilot-ghost-text {
  color: #999;
  opacity: 0.6;
  pointer-events: none;
}

/* 虚拟文本内的格式化元素 */
.copilot-ghost-text strong,
.copilot-ghost-text em,
.copilot-ghost-text code {
  opacity: inherit;
}

步骤 4状态管理

需要跟踪虚拟节点的范围,以便:

  • Tab 键接受时正确移除 mark
  • 用户输入时正确清除虚拟内容
  • 导出时正确处理虚拟文本

文件修改清单

文件 修改内容
src/plugins/copilotPlugin.ts 重构 insertGhostText添加解析逻辑
src/components/MilkdownEditor.vue 更新 CSS 样式
src/plugins/types.ts 可能需要更新类型定义

风险与注意事项

  1. 性能考虑:解析 Markdown 可能有延迟,需要考虑用户体验
  2. 嵌套处理:复杂的 Markdown 结构(如嵌套列表)需要特殊处理
  3. 撤销/重做:确保虚拟文本的接受/拒绝正确处理 undo stack
  4. 光标位置:插入多段落内容后光标位置需要正确设置

验收标准

  • Markdown 语法正确渲染(粗体、斜体、代码等)
  • 换行符正确转换为段落
  • Tab 键接受功能正常
  • Escape 键拒绝功能正常
  • 导出时虚拟文本正确处理
  • 性能无明显下降