266 lines
6.4 KiB
Plaintext
266 lines
6.4 KiB
Plaintext
# LLM in Text - 智能写作助手
|
||
|
||
基于 Vue3 和 FastAPI 的智能 Markdown 编辑器,集成大语言模型(LLM)实时补全建议功能,提供类似 GitHub Copilot 的 Ghost Text 体验。
|
||
|
||
## 功能特性
|
||
|
||
### Markdown 编辑器
|
||
- 基于 Milkdown Crepe 的所见即所得编辑体验
|
||
- 支持完整 Markdown 语法和 LaTeX 公式
|
||
- 导入/导出 Markdown 文件
|
||
|
||
### AI 智能补全
|
||
- 实时生成文本补全建议(灰色显示)
|
||
- 流式响应,低延迟体验
|
||
- 多种交互方式:
|
||
- **Tab 键**:接受建议
|
||
- **Esc 键**:拒绝建议
|
||
- **点击灰色文本**:接受建议
|
||
- **继续输入**:自动拒绝建议
|
||
|
||
### AI 开关控制
|
||
- 右下角 AI 开关按钮
|
||
- 白色 = AI 启用,黑色 = AI 禁用
|
||
- 禁用时自动清除灰色文本并停止 API 调用
|
||
|
||
## 技术架构
|
||
|
||
```mermaid
|
||
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]
|
||
end
|
||
|
||
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]
|
||
end
|
||
|
||
G -->|POST /v1/completions<br/>SSE 流式响应| H
|
||
K -->|LLM 响应| J
|
||
```
|
||
|
||
## 项目结构
|
||
|
||
```
|
||
llm-in-text/
|
||
├── src/
|
||
│ ├── components/
|
||
│ │ └── MilkdownEditor.vue # 主编辑器组件
|
||
│ ├── plugins/
|
||
│ │ ├── copilotPlugin.ts # ProseMirror AI 补全插件
|
||
│ │ ├── types.ts # 类型定义
|
||
│ │ └── index.ts # 插件导出
|
||
│ ├── utils/
|
||
│ │ ├── api.js # API 调用封装
|
||
│ │ ├── config.js # 配置文件
|
||
│ │ └── ocrCache.js # OCR 缓存管理
|
||
│ ├── App.vue
|
||
│ └── main.js
|
||
├── backend/
|
||
│ ├── main.py # FastAPI 服务器
|
||
│ ├── llm.py # LLM API 调用
|
||
│ ├── prompt.py # Prompt 构建
|
||
│ └── requirements.txt
|
||
└── README.md
|
||
```
|
||
|
||
## 快速开始
|
||
|
||
### 环境要求
|
||
- Node.js 18+
|
||
- Python 3.8+
|
||
- Ollama 服务(或其他兼容 OpenAI API 的服务)
|
||
|
||
### 安装
|
||
|
||
```bash
|
||
# 前端
|
||
npm install
|
||
|
||
# 后端
|
||
cd backend
|
||
pip install -r requirements.txt
|
||
```
|
||
|
||
### 配置
|
||
|
||
在 `backend/.env` 中配置:
|
||
|
||
```env
|
||
OLLAMA_MODEL=gpt-oss:20b
|
||
OLLAMA_HOST=http://localhost:11434
|
||
```
|
||
|
||
### 启动
|
||
|
||
```bash
|
||
# 后端(端口 8000)
|
||
cd backend
|
||
python main.py
|
||
|
||
# 前端(端口 5173)
|
||
npm run dev
|
||
```
|
||
|
||
访问 http://localhost:5173
|
||
|
||
## API 接口
|
||
|
||
### POST /v1/completions
|
||
|
||
流式获取补全建议
|
||
|
||
**请求:**
|
||
```json
|
||
{
|
||
"prefix": "# Title\n\nContent ",
|
||
"suffix": "",
|
||
"languageId": "markdown"
|
||
}
|
||
```
|
||
|
||
**响应(SSE):**
|
||
```
|
||
data: {"content": "here"}
|
||
data: {"content": "here is"}
|
||
data: {"done": true}
|
||
```
|
||
|
||
## 核心实现
|
||
|
||
### 后端设计
|
||
|
||
#### 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 系统
|
||
|
||
使用 ProseMirror 的 Mark 系统实现灰色建议文本:
|
||
|
||
```typescript
|
||
// 定义 ghost mark
|
||
export const copilotGhostMark = $markSchema('copilot_ghost', () => ({
|
||
excludes: '_',
|
||
inclusive: true,
|
||
toDOM: () => ['span', {
|
||
'data-copilot-ghost': '',
|
||
class: 'copilot-ghost-text'
|
||
}, 0]
|
||
}))
|
||
|
||
// CSS 样式
|
||
.copilot-ghost-text {
|
||
color: #999;
|
||
opacity: 0.6;
|
||
}
|
||
```
|
||
|
||
#### copilotPlugin 核心逻辑
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
A[用户输入] --> B{文档变化?}
|
||
B -->|是| C[清除旧建议]
|
||
C --> D[防抖 1000ms]
|
||
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: 防抖 1000ms
|
||
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
|
||
```
|
||
|
||
## 设计亮点
|
||
|
||
1. **前后端分离**:前端只负责渲染和数据回传,后端负责 LLM 调用、Prompt 构建和数据解析
|
||
2. **低延迟优化**:防抖机制 (1000ms) + SSE 流式响应 + AbortController 取消过期请求
|
||
3. **ProseMirror Mark 系统**:与编辑器状态完美集成,支持 Undo/Redo
|
||
4. **多种交互方式**:Tab/Esc/点击/输入,用户体验友好
|
||
5. **智能大小限制**:文档超过 32KB 自动禁用 AI 功能
|
||
|
||
## 许可证
|
||
|
||
MIT License
|