2026-02-07 08:53:37 +08:00
|
|
|
|
# LLM in Text - 智能写作助手
|
2026-01-11 14:11:14 +00:00
|
|
|
|
|
2026-02-14 18:28:37 +08:00
|
|
|
|
基于 Vue3 和 FastAPI 的智能 Markdown 编辑器,集成大语言模型(LLM)实时补全建议功能,提供类似 GitHub Copilot 的 Ghost Text 体验。
|
2026-01-12 12:23:44 +08:00
|
|
|
|
|
2026-02-13 09:24:50 +08:00
|
|
|
|
## 功能特性
|
2026-02-07 08:53:37 +08:00
|
|
|
|
|
2026-02-13 09:24:50 +08:00
|
|
|
|
### Markdown 编辑器
|
2026-02-07 08:53:37 +08:00
|
|
|
|
- 基于 Milkdown Crepe 的所见即所得编辑体验
|
2026-02-13 09:24:50 +08:00
|
|
|
|
- 支持完整 Markdown 语法和 LaTeX 公式
|
|
|
|
|
|
- 导入/导出 Markdown 文件
|
2026-02-07 08:53:37 +08:00
|
|
|
|
|
2026-02-13 09:24:50 +08:00
|
|
|
|
### AI 智能补全
|
|
|
|
|
|
- 实时生成文本补全建议(灰色显示)
|
|
|
|
|
|
- 流式响应,低延迟体验
|
|
|
|
|
|
- 多种交互方式:
|
|
|
|
|
|
- **Tab 键**:接受建议
|
|
|
|
|
|
- **Esc 键**:拒绝建议
|
|
|
|
|
|
- **点击灰色文本**:接受建议
|
|
|
|
|
|
- **继续输入**:自动拒绝建议
|
|
|
|
|
|
|
|
|
|
|
|
### AI 开关控制
|
|
|
|
|
|
- 右下角 AI 开关按钮
|
|
|
|
|
|
- 白色 = AI 启用,黑色 = AI 禁用
|
|
|
|
|
|
- 禁用时自动清除灰色文本并停止 API 调用
|
|
|
|
|
|
|
|
|
|
|
|
## 技术架构
|
|
|
|
|
|
|
|
|
|
|
|
```mermaid
|
2026-02-14 18:28:37 +08:00
|
|
|
|
flowchart TB
|
|
|
|
|
|
subgraph Frontend["前端 (Vue3 + Vite)"]
|
|
|
|
|
|
A[App.vue] --> B[MilkdownEditor.vue]
|
|
|
|
|
|
B --> C[Crepe Editor]
|
|
|
|
|
|
C --> D[ProseMirror]
|
|
|
|
|
|
D --> E[copilotPlugin.ts]
|
|
|
|
|
|
E --> F[copilotGhostMark]
|
|
|
|
|
|
E --> G[api.js]
|
2026-02-13 09:24:50 +08:00
|
|
|
|
end
|
|
|
|
|
|
|
2026-02-14 18:28:37 +08:00
|
|
|
|
subgraph Backend["后端 (FastAPI + Python)"]
|
|
|
|
|
|
H[main.py<br/>FastAPI Server] --> I[prompt.py<br/>Prompt 构建]
|
|
|
|
|
|
H --> J[llm.py<br/>Ollama 调用]
|
|
|
|
|
|
J --> K[Ollama API]
|
2026-02-13 09:24:50 +08:00
|
|
|
|
end
|
|
|
|
|
|
|
2026-02-14 18:28:37 +08:00
|
|
|
|
G -->|POST /v1/completions<br/>SSE 流式响应| H
|
|
|
|
|
|
K -->|LLM 响应| J
|
2026-02-13 09:24:50 +08:00
|
|
|
|
```
|
2026-02-07 08:53:37 +08:00
|
|
|
|
|
|
|
|
|
|
## 项目结构
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
llm-in-text/
|
|
|
|
|
|
├── src/
|
|
|
|
|
|
│ ├── components/
|
2026-02-13 09:24:50 +08:00
|
|
|
|
│ │ └── MilkdownEditor.vue # 主编辑器组件
|
2026-02-07 08:53:37 +08:00
|
|
|
|
│ ├── plugins/
|
2026-02-14 18:28:37 +08:00
|
|
|
|
│ │ ├── copilotPlugin.ts # ProseMirror AI 补全插件
|
|
|
|
|
|
│ │ ├── types.ts # 类型定义
|
|
|
|
|
|
│ │ └── index.ts # 插件导出
|
2026-02-13 09:24:50 +08:00
|
|
|
|
│ ├── utils/
|
|
|
|
|
|
│ │ ├── api.js # API 调用封装
|
|
|
|
|
|
│ │ └── config.js # 配置文件
|
|
|
|
|
|
│ ├── App.vue
|
|
|
|
|
|
│ └── main.js
|
2026-02-07 08:53:37 +08:00
|
|
|
|
├── backend/
|
2026-02-13 09:24:50 +08:00
|
|
|
|
│ ├── main.py # FastAPI 服务器
|
|
|
|
|
|
│ ├── llm.py # LLM API 调用
|
|
|
|
|
|
│ ├── prompt.py # Prompt 构建
|
|
|
|
|
|
│ └── requirements.txt
|
2026-02-07 08:53:37 +08:00
|
|
|
|
└── README.md
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 快速开始
|
|
|
|
|
|
|
2026-02-13 09:24:50 +08:00
|
|
|
|
### 环境要求
|
2026-02-07 08:53:37 +08:00
|
|
|
|
- Node.js 18+
|
|
|
|
|
|
- Python 3.8+
|
2026-02-14 18:28:37 +08:00
|
|
|
|
- Ollama 服务(或其他兼容 OpenAI API 的服务)
|
2026-02-07 08:53:37 +08:00
|
|
|
|
|
2026-02-13 09:24:50 +08:00
|
|
|
|
### 安装
|
2026-02-07 08:53:37 +08:00
|
|
|
|
|
|
|
|
|
|
```bash
|
2026-02-13 09:24:50 +08:00
|
|
|
|
# 前端
|
2026-02-07 08:53:37 +08:00
|
|
|
|
npm install
|
|
|
|
|
|
|
2026-02-13 09:24:50 +08:00
|
|
|
|
# 后端
|
2026-02-07 08:53:37 +08:00
|
|
|
|
cd backend
|
|
|
|
|
|
pip install -r requirements.txt
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-02-13 09:24:50 +08:00
|
|
|
|
### 配置
|
2026-02-07 08:53:37 +08:00
|
|
|
|
|
2026-02-13 09:24:50 +08:00
|
|
|
|
在 `backend/.env` 中配置:
|
2026-02-07 08:53:37 +08:00
|
|
|
|
|
|
|
|
|
|
```env
|
2026-02-14 18:28:37 +08:00
|
|
|
|
OLLAMA_MODEL=gpt-oss:20b
|
|
|
|
|
|
OLLAMA_HOST=http://localhost:11434
|
2026-02-07 08:53:37 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-02-13 09:24:50 +08:00
|
|
|
|
### 启动
|
2026-02-07 08:53:37 +08:00
|
|
|
|
|
|
|
|
|
|
```bash
|
2026-02-13 09:24:50 +08:00
|
|
|
|
# 后端(端口 8000)
|
2026-02-07 08:53:37 +08:00
|
|
|
|
cd backend
|
|
|
|
|
|
python main.py
|
|
|
|
|
|
|
2026-02-13 09:24:50 +08:00
|
|
|
|
# 前端(端口 5173)
|
2026-02-07 08:53:37 +08:00
|
|
|
|
npm run dev
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-02-13 09:24:50 +08:00
|
|
|
|
访问 http://localhost:5173
|
2026-02-07 08:53:37 +08:00
|
|
|
|
|
|
|
|
|
|
## API 接口
|
|
|
|
|
|
|
|
|
|
|
|
### POST /v1/completions
|
|
|
|
|
|
|
2026-02-13 09:24:50 +08:00
|
|
|
|
流式获取补全建议
|
2026-02-07 08:53:37 +08:00
|
|
|
|
|
2026-02-13 09:24:50 +08:00
|
|
|
|
**请求:**
|
2026-02-07 08:53:37 +08:00
|
|
|
|
```json
|
|
|
|
|
|
{
|
2026-02-13 09:24:50 +08:00
|
|
|
|
"prefix": "# Title\n\nContent ",
|
2026-02-07 08:53:37 +08:00
|
|
|
|
"suffix": "",
|
|
|
|
|
|
"languageId": "markdown"
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-02-13 09:24:50 +08:00
|
|
|
|
**响应(SSE):**
|
2026-02-07 08:53:37 +08:00
|
|
|
|
```
|
2026-02-13 09:24:50 +08:00
|
|
|
|
data: {"content": "here"}
|
|
|
|
|
|
data: {"content": "here is"}
|
2026-02-07 08:53:37 +08:00
|
|
|
|
data: {"done": true}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-02-13 09:24:50 +08:00
|
|
|
|
## 核心实现
|
2026-02-07 08:53:37 +08:00
|
|
|
|
|
2026-02-14 18:28:37 +08:00
|
|
|
|
### 后端设计
|
|
|
|
|
|
|
|
|
|
|
|
#### main.py - FastAPI 服务器
|
|
|
|
|
|
- 定义 `/v1/completions` 端点
|
|
|
|
|
|
- 使用 `StreamingResponse` 返回 SSE 流式响应
|
|
|
|
|
|
- CORS 配置允许跨域请求
|
|
|
|
|
|
|
|
|
|
|
|
#### llm.py - LLM 调用封装
|
|
|
|
|
|
- 使用 `ollama.AsyncClient` 异步调用
|
|
|
|
|
|
- 支持 `think='high'` 思考模式
|
|
|
|
|
|
- 返回 `content` 和 `thinking` 字段
|
|
|
|
|
|
|
|
|
|
|
|
#### prompt.py - Prompt 工程
|
|
|
|
|
|
精心设计的 Prompt 模板,包含 7 条核心规则:
|
|
|
|
|
|
|
|
|
|
|
|
| 规则 | 说明 |
|
|
|
|
|
|
|------|------|
|
|
|
|
|
|
| RULE #1 | 无缝连接 - 不重复 suffix 内容,避免"复读机"错误 |
|
|
|
|
|
|
| RULE #2 | 空白处理 - 避免双空格,正确对接标点 |
|
|
|
|
|
|
| RULE #3 | 缩进对齐 - 匹配当前缩进级别和类型 |
|
|
|
|
|
|
| RULE #4 | 列表维护 - 识别并继续任务列表、有序列表、无序列表 |
|
|
|
|
|
|
| RULE #5 | 语法闭合 - 自动闭合未完成的 Markdown 语法 |
|
|
|
|
|
|
| RULE #6 | 输出格式 - 仅输出续写文本,无解释无注释 |
|
|
|
|
|
|
| RULE #7 | 必须输出 - 始终提供有用的续写建议 |
|
|
|
|
|
|
|
|
|
|
|
|
### 前端设计
|
|
|
|
|
|
|
|
|
|
|
|
#### ProseMirror Mark 系统
|
2026-02-07 08:53:37 +08:00
|
|
|
|
|
2026-02-13 09:24:50 +08:00
|
|
|
|
使用 ProseMirror 的 Mark 系统实现灰色建议文本:
|
2026-02-07 08:53:37 +08:00
|
|
|
|
|
2026-02-13 09:24:50 +08:00
|
|
|
|
```typescript
|
|
|
|
|
|
// 定义 ghost mark
|
|
|
|
|
|
export const copilotGhostMark = $markSchema('copilot_ghost', () => ({
|
|
|
|
|
|
excludes: '_',
|
|
|
|
|
|
inclusive: true,
|
|
|
|
|
|
toDOM: () => ['span', {
|
|
|
|
|
|
'data-copilot-ghost': '',
|
|
|
|
|
|
class: 'copilot-ghost-text'
|
|
|
|
|
|
}, 0]
|
|
|
|
|
|
}))
|
2026-02-07 08:53:37 +08:00
|
|
|
|
|
2026-02-13 09:24:50 +08:00
|
|
|
|
// CSS 样式
|
|
|
|
|
|
.copilot-ghost-text {
|
|
|
|
|
|
color: #999;
|
|
|
|
|
|
opacity: 0.6;
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
2026-02-07 08:53:37 +08:00
|
|
|
|
|
2026-02-14 18:28:37 +08:00
|
|
|
|
#### copilotPlugin 核心逻辑
|
|
|
|
|
|
|
|
|
|
|
|
```mermaid
|
|
|
|
|
|
flowchart LR
|
|
|
|
|
|
A[用户输入] --> B{文档变化?}
|
|
|
|
|
|
B -->|是| C[清除旧建议]
|
|
|
|
|
|
C --> D[防抖 500ms]
|
|
|
|
|
|
D --> E[发送 API 请求]
|
|
|
|
|
|
E --> F[收到建议]
|
|
|
|
|
|
F --> G[插入 Ghost Text]
|
|
|
|
|
|
|
|
|
|
|
|
G --> H{用户操作}
|
|
|
|
|
|
H -->|Tab| I[接受建议<br/>移除 mark]
|
|
|
|
|
|
H -->|Esc| J[拒绝建议<br/>删除文本]
|
|
|
|
|
|
H -->|点击 Ghost| I
|
|
|
|
|
|
H -->|继续输入| J
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 关键函数
|
|
|
|
|
|
|
|
|
|
|
|
| 函数 | 作用 |
|
|
|
|
|
|
|------|------|
|
|
|
|
|
|
| `scheduleFetch` | 防抖调度 API 请求 |
|
|
|
|
|
|
| `insertGhostText` | 插入带 mark 的建议文本 |
|
|
|
|
|
|
| `acceptSuggestion` | Tab 接受建议 |
|
|
|
|
|
|
| `rejectSuggestion` | Esc 拒绝建议 |
|
|
|
|
|
|
| `clearGhostText` | 清除当前建议 |
|
|
|
|
|
|
|
|
|
|
|
|
### 数据流
|
|
|
|
|
|
|
|
|
|
|
|
```mermaid
|
|
|
|
|
|
sequenceDiagram
|
|
|
|
|
|
participant U as 用户
|
|
|
|
|
|
participant E as Editor (ProseMirror)
|
|
|
|
|
|
participant P as copilotPlugin
|
|
|
|
|
|
participant A as api.js
|
|
|
|
|
|
participant B as Backend
|
|
|
|
|
|
participant L as LLM
|
|
|
|
|
|
|
|
|
|
|
|
U->>E: 输入文本
|
|
|
|
|
|
E->>P: view.update()
|
|
|
|
|
|
P->>P: 清除旧建议
|
|
|
|
|
|
P->>P: 防抖 500ms
|
|
|
|
|
|
P->>A: fetchSuggestion(prefix, suffix)
|
|
|
|
|
|
A->>B: POST /v1/completions
|
|
|
|
|
|
B->>B: build_prompt()
|
|
|
|
|
|
B->>L: ollama.chat()
|
|
|
|
|
|
L-->>B: {content, thinking}
|
|
|
|
|
|
B-->>A: SSE stream
|
|
|
|
|
|
A-->>P: suggestion text
|
|
|
|
|
|
P->>E: insertGhostText()
|
|
|
|
|
|
E-->>U: 显示灰色建议
|
|
|
|
|
|
|
|
|
|
|
|
alt Tab 键
|
|
|
|
|
|
U->>P: Tab
|
|
|
|
|
|
P->>E: acceptSuggestion()
|
|
|
|
|
|
E-->>U: 建议变为正常文本
|
|
|
|
|
|
else Esc 键
|
|
|
|
|
|
U->>P: Esc
|
|
|
|
|
|
P->>E: rejectSuggestion()
|
|
|
|
|
|
E-->>U: 建议消失
|
|
|
|
|
|
else 继续输入
|
|
|
|
|
|
U->>E: 输入其他字符
|
|
|
|
|
|
E->>P: handleKeyDown()
|
|
|
|
|
|
P->>E: clearGhostText()
|
|
|
|
|
|
end
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 设计亮点
|
2026-02-07 08:53:37 +08:00
|
|
|
|
|
2026-02-14 18:28:37 +08:00
|
|
|
|
1. **前后端分离**:前端只负责渲染和数据回传,后端负责 LLM 调用、Prompt 构建和数据解析
|
|
|
|
|
|
2. **低延迟优化**:防抖机制 (500ms) + SSE 流式响应 + AbortController 取消过期请求
|
|
|
|
|
|
3. **ProseMirror Mark 系统**:与编辑器状态完美集成,支持 Undo/Redo
|
|
|
|
|
|
4. **多种交互方式**:Tab/Esc/点击/输入,用户体验友好
|
2026-02-07 08:53:37 +08:00
|
|
|
|
|
|
|
|
|
|
## 许可证
|
|
|
|
|
|
|
|
|
|
|
|
MIT License
|