- 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
158 lines
4.0 KiB
Markdown
158 lines
4.0 KiB
Markdown
# 虚拟文本 Markdown 渲染解决方案
|
||
|
||
## 问题分析
|
||
|
||
当前虚拟文本(灰色字)无法正确渲染 Markdown 和换行符,根本原因是:
|
||
|
||
1. **纯文本插入**:`insertGhostText` 使用 `tr.insertText()` 直接插入纯文本
|
||
2. **绕过解析器**:文本未经过 Milkdown 的 Markdown 解析流程
|
||
3. **节点结构错误**:`\n` 字符被当作普通字符,而非创建新段落节点
|
||
|
||
## 解决方案架构
|
||
|
||
```mermaid
|
||
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
|
||
|
||
需要修改以下部分:
|
||
|
||
```typescript
|
||
// 新增导入
|
||
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 以支持格式化的虚拟文本:
|
||
|
||
```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 键拒绝功能正常
|
||
- [ ] 导出时虚拟文本正确处理
|
||
- [ ] 性能无明显下降
|