# 灵犀 AI Agent：打造极致对话体验 —— 多模态交互、RAG 引用、规划模式深度解析

> 本文是灵犀 AI Agent 系列专栏的第四篇，将深入探讨灵犀在对话体验上的全方位设计——从基础的流式输出到多模态输入（图片粘贴、文件拖入、语音输入、截屏），从知识库 RAG 引用可视化到两阶段规划模式，再到消息编辑、反馈、固定等精细化功能的实现细节。
>
> GitHub 地址：[https://github.com/OdysseyFather/lingxi](https://github.com/OdysseyFather/lingxi)

---

## 一、流式输出：不只是逐字显示

灵犀的流式输出不是简单的"把文本一个字一个字显示出来"。它是一套完整的**多层内容块渲染系统**。

### 1.1 三种内容块

每条 AI 回复由多个内容块（Block）组成，有三种类型：

```
┌─────────────────────────────────────────────┐
│ Assistant 消息                                │
│                                              │
│ ┌──────────────────────────────────────────┐ │
│ │ 💭 思考块 (thinking)                      │ │
│ │ "用户想了解项目结构，我需要先看一下..."    │ │
│ │                               [折叠/展开] │ │
│ └──────────────────────────────────────────┘ │
│                                              │
│ ┌──────────────────────────────────────────┐ │
│ │ 🔧 工具块 (tool)                          │ │
│ │ 执行技能：文件读取                         │ │
│ │ 耗时：120ms ✅                            │ │
│ └──────────────────────────────────────────┘ │
│                                              │
│ ┌──────────────────────────────────────────┐ │
│ │ 📝 文本块 (text)                          │ │
│ │ 你的项目结构如下：                         │ │
│ │ ```                                       │ │
│ │ src/                                      │ │
│ │ ├── components/                           │ │
│ │ └── utils/                                │ │
│ │ ```                                       │ │
│ └──────────────────────────────────────────┘ │
└─────────────────────────────────────────────┘
```

### 1.2 WebSocket 事件到 UI 的映射

前端 Zustand Store 维护一个 `liveBlocks` 数组，实时追加新的 token：

```javascript
// WS 事件处理（简化版）
case 'thinking': {
    const blocks = [...state.liveBlocks];
    const last = blocks[blocks.length - 1];
    // 如果最后一个块也是 thinking，直接追加
    if (last && last.type === 'thinking') {
        last.text += text;
    } else {
        // 否则新建一个 thinking 块
        blocks.push({ type: 'thinking', text });
    }
    set({ liveBlocks: blocks });
    break;
}

case 'tool_start': {
    const blocks = [...state.liveBlocks];
    blocks.push({
        type: 'tool',
        name: payload?.name || '',
        label: payload?.label || '执行技能',
        startedAt: Date.now(),
        done: false,
    });
    set({ liveBlocks: blocks });
    break;
}
```

当收到 `message_usage` 事件时，流式结束，`liveBlocks` 被固化为一条完整的 `messages` 记录。

### 1.3 思考过程折叠

思考块（thinking）默认以折叠状态显示。用户点击可以展开查看 AI 的完整思考过程。这个设计参考了 Claude 的 Extended Thinking 功能——让高级用户可以了解 AI 的推理过程，同时不干扰普通用户的阅读体验。

### 1.4 代码块语法高亮

灵犀使用 `prism-react-renderer` 实现代码语法高亮，支持 50+ 种编程语言。每个代码块右上角有一个复制按钮，点击即可复制代码内容。

```jsx
// 代码块渲染（简化版）
<Highlight code={code} language={language} theme={theme}>
    {({ tokens, getLineProps, getTokenProps }) => (
        <pre>
            {tokens.map((line, i) => (
                <div {...getLineProps({ line, key: i })}>
                    {line.map((token, key) => (
                        <span {...getTokenProps({ token, key })} />
                    ))}
                </div>
            ))}
        </pre>
    )}
</Highlight>
```

<!-- 截图占位：流式输出效果，包含思考块、工具块、代码块 -->
![流式输出效果](images/流式输出.png)

---

## 二、多模态输入：五种方式发送内容

灵犀的输入框（Composer 组件）支持五种输入方式，覆盖了桌面应用的所有交互场景。

### 2.1 文本输入 + 斜杠命令

最基础的文本输入，支持 12 个斜杠命令快捷输入：

| 命令 | 功能 | 自动填充的 Prompt |
|------|------|------------------|
| `/translate` | 翻译 | "请将以下内容翻译为{目标语言}：" |
| `/summarize` | 总结 | "请总结以下内容的要点：" |
| `/explain` | 解释 | "请用通俗易懂的语言解释：" |
| `/code` | 写代码 | "请根据以下描述编写代码：" |
| `/review` | 代码审查 | "请审查以下代码，指出问题并提出改进建议：" |
| `/refactor` | 重构 | "请重构以下代码，使其更简洁、高效、易读：" |
| `/fix` | 修复 | "请分析以下错误并提供修复方案：" |
| `/email` | 写邮件 | "请帮我撰写一封邮件：" |
| `/brainstorm` | 头脑风暴 | "请围绕以下主题进行头脑风暴：" |
| `/compare` | 对比分析 | "请对比以下方案的优缺点：" |
| `/sql` | SQL | "请根据以下描述生成 SQL 查询：" |
| `/test` | 写测试 | "请为以下代码编写单元测试：" |

输入 `/` 后，命令列表自动弹出，支持模糊搜索和键盘上下选择。

### 2.2 图片粘贴（Cmd+V）

直接从剪贴板粘贴图片到输入框。灵犀会将图片转为 Base64，显示缩略预览，发送时作为 `images` 参数传递给后端。

```javascript
// Composer.jsx 中的粘贴处理
const handlePaste = useCallback((e) => {
    const items = e.clipboardData.items;
    for (const item of items) {
        if (item.type.startsWith('image/')) {
            e.preventDefault();
            const blob = item.getAsFile();
            const reader = new FileReader();
            reader.onload = () => {
                const base64 = reader.result.split(',')[1];
                const mediaType = item.type;
                setImages(prev => [...prev, {
                    mediaType,
                    data: base64,
                    preview: reader.result,
                }].slice(0, 6)); // 最多 6 张图片
            };
            reader.readAsDataURL(blob);
        }
    }
}, []);
```

### 2.3 文件拖入

支持拖入文本类文件（.md、.py、.go、.json、.csv 等 60+ 种格式），文件内容作为消息附件发送。

```javascript
// 支持的文件扩展名
const TEXT_EXTENSIONS = new Set([
    'md', 'txt', 'json', 'csv', 'tsv', 'xml', 'yaml', 'yml',
    'py', 'js', 'jsx', 'ts', 'tsx', 'go', 'rs', 'java', 'kt',
    'c', 'cpp', 'h', 'hpp', 'cs', 'rb', 'php', 'swift',
    'sh', 'bash', 'zsh', 'sql', 'r', 'lua',
    'html', 'css', 'scss', 'less', 'vue', 'svelte',
    'dockerfile', 'makefile', 'gitignore', 'env',
]);
```

### 2.4 语音输入（离线 whisper.cpp）

这是灵犀的一大特色功能——**完全离线的语音识别**。

点击麦克风按钮开始录音 → 再次点击停止 → 音频发送到后端 → 本地 whisper.cpp 识别 → 文字填入输入框。

整个过程不需要网络连接，不需要调用任何云端 API。

**技术实现**：

前端使用 `MediaRecorder API` 录音：

```javascript
const startRecording = useCallback(async () => {
    const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
    const recorder = new MediaRecorder(stream, {
        mimeType: 'audio/webm;codecs=opus'
    });
    recorder.ondataavailable = (e) => {
        audioChunksRef.current.push(e.data);
    };
    recorder.start(100); // 每 100ms 收集一次数据
}, []);
```

后端使用内置的 whisper-cli（whisper.cpp 编译产物）进行语音识别：

```go
// handler/transcribe.go
func TranscribeAudio(c *gin.Context) {
    whisperBin := os.Getenv("WHISPER_BIN")
    whisperModel := os.Getenv("WHISPER_MODEL")
    
    if whisperBin != "" && whisperModel != "" {
        // 本地 whisper.cpp 离线识别（优先）
        cmd := exec.Command(whisperBin, 
            "-m", whisperModel,
            "-f", audioFilePath,
            "--language", "auto",
            "--output-txt",
        )
        // ... 执行并解析结果
    } else {
        // 回退到远端 OpenAI Whisper API
    }
}
```

whisper.cpp 使用 Apple Metal 加速，在 M 系列 Mac 上识别速度非常快。

### 2.5 快捷截屏（Cmd+Shift+S）

全局快捷键 `Cmd+Shift+S` 触发截屏，截图自动填入输入框。

```javascript
// Electron 主进程注册全局快捷键
globalShortcut.register('CommandOrControl+Shift+S', async () => {
    const sources = await desktopCapturer.getSources({
        types: ['screen'],
        thumbnailSize: { width: 1920, height: 1080 },
    });
    const png = sources[0].thumbnail.toPNG();
    mainWindow.webContents.send('screenshot-captured', {
        data: png.toString('base64'),
        mediaType: 'image/png',
    });
});
```

前端监听 `screenshot-captured` 事件，将截图自动添加到图片列表。

<!-- 截图占位：Composer 组件，展示图片预览、文件附件、录音按钮 -->
![多模态输入](images/多模态输入.png)

---

## 三、知识库 RAG 引用可视化

灵犀的知识库检索增强生成（RAG）不只是在后台查询文档——它实现了**完整的引用可视化**。

### 3.1 引用标注

当 AI 回复中使用了知识库内容时，会按照 System Prompt 的指令进行引用标注：

```markdown
根据项目文档 [1]，灵犀采用 Electron + React + Go 三层架构 [2]。
前端使用 React 19 和 Tailwind CSS 3.4 [1]。

<!-- KB_CITATIONS: [
    {"id":1,"file":"docs/architecture.md","title":"架构文档","excerpt":"灵犀采用三层架构..."},
    {"id":2,"file":"docs/tech-stack.md","title":"技术栈","excerpt":"Electron 36 + React 19..."}
] -->
```

### 3.2 前端渲染

前端解析 `KB_CITATIONS` 注释块，将 `[N]` 标记渲染为可交互的上角标：

- **Hover** → 弹出引用卡片，显示文件名、文档标题、引用原文
- **点击** → 展开完整引用详情
- **底部** → 折叠式引用列表，点击展开查看所有引用来源

```
AI 回复文本中的 [1] ─── hover ──► ┌─────────────────┐
                                    │ 📄 architecture.md│
                                    │ 架构文档          │
                                    │ "灵犀采用三层..."  │
                                    └─────────────────┘
```

### 3.3 检索流程

在 System Prompt 中，灵犀为 AI 引擎注入了知识库检索指令：

```
每次收到用户消息，在做任何回答之前：
1. Read 工具读取 {{KB_PATH}}/INDEX.md 了解知识库索引
2. 使用 grep 搜索相关关键词定位文件
3. Read 工具读取命中文件的内容
4. 将知识库内容融入回答，并标注引用
```

这种"让 AI 自己检索"的方式比传统的 embedding 向量检索更灵活——AI 可以根据上下文动态决定搜索什么关键词，而不是依赖固定的向量相似度。

<!-- 截图占位：RAG 引用可视化效果 -->
![RAG 引用可视化](images/rag引用.png)

---

## 四、两阶段规划模式

面对复杂任务（如"帮我开发一个博客系统"），灵犀不会直接动手，而是先进入**规划模式**。

### 4.1 第一阶段：确认是否规划

当 AI 判断用户的请求属于复杂项目时，它会先给用户一个选择：

```
用户：帮我搭建一个个人博客

灵犀：这是一个比较复杂的项目，我建议先做一个规划。
      你希望：
      ○ 直接动手（快速给出方案）
      ● 进入规划模式（详细讨论各方面需求后再执行）
```

### 4.2 第二阶段：多维度选择面板

进入规划模式后，灵犀会展示一系列选择面板，逐一收集需求：

```
┌─────────────────────────────────────┐
│ 规划面板  [1/4]                      │
├─────────────────────────────────────┤
│ 技术栈选择：                          │
│                                      │
│ ○ React + Next.js                    │
│ ○ Vue + Nuxt                         │
│ ○ 纯 HTML + CSS + JS                │
│ ● 让 AI 决定                          │
│                                      │
│              [← 上一步]  [下一步 →]   │
└─────────────────────────────────────┘
```

所有维度确认后，AI 会汇总所有选择，生成完整的执行计划，用户确认后才开始执行。

### 4.3 交互式信息收集块

这背后的技术是灵犀独创的**结构化交互 JSON 代码块**。AI 可以在回复中嵌入选择题或输入题：

```json
```json:interaction
{
    "type": "wizard",
    "questions": [
        {
            "id": "tech_stack",
            "question": "你希望使用什么技术栈？",
            "type": "single_choice",
            "options": [
                {"value": "react", "label": "React + Next.js"},
                {"value": "vue", "label": "Vue + Nuxt"},
                {"value": "html", "label": "纯 HTML + CSS + JS"},
                {"value": "auto", "label": "让 AI 决定"}
            ]
        }
    ]
}
```

前端识别到这种 JSON 代码块后，会渲染为交互式 UI 组件。用户的选择会被收集起来，作为下一轮对话的输入。

---

## 五、消息管理：编辑、反馈、固定

灵犀在消息层面提供了丰富的管理能力。

### 5.1 消息编辑/重发

Hover 在用户消息上会出现编辑按钮。点击后，消息变为可编辑的 textarea。保存后：

1. 后端 `PUT /api/messages/:id` 更新消息内容
2. 删除该消息之后的所有消息
3. 用编辑后的内容重新发送，触发新的 AI 回复

```javascript
editAndResend: async (messageId, newContent) => {
    // 1. 更新消息
    await api.updateMessage(messageId, newContent);
    // 2. 截断后续消息
    const updated = { ...messages[idx], content: newContent };
    set({ messages: [...messages.slice(0, idx), updated] });
    // 3. 重新发送
    await api.sendChat({ message: newContent, sessionId });
}
```

### 5.2 消息反馈

每条 AI 回复都有 thumbs up / thumbs down 按钮。反馈数据持久化到 SQLite，选中状态高亮显示。

```javascript
setFeedback: async (messageId, feedback) => {
    await api.setMessageFeedback(messageId, feedback);
    set({
        messages: get().messages.map((m) =>
            m.id === messageId ? { ...m, feedback } : m
        ),
    });
}
```

### 5.3 消息固定

重要消息可以 Pin 固定。用户消息和 AI 消息均支持。固定的消息在侧边栏有特殊标记。

### 5.4 快捷回复建议

每条 AI 回复后，灵犀会自动推荐 2-3 个后续问题，显示为胶囊按钮。建议基于回复内容的关键词匹配：

```javascript
function generateQuickReplies(text) {
    if (/代码|函数|编程/.test(text)) {
        return [
            '请解释这段代码的工作原理',
            '能否优化一下这段代码？',
            '请为这段代码添加注释'
        ];
    }
    if (/步骤|方案|计划/.test(text)) {
        return [
            '请详细展开第一步',
            '有没有其他替代方案？',
            '请总结一下要点'
        ];
    }
    // ... 更多场景
    return ['继续', '请详细说明', '帮我总结一下'];
}
```

后端也可能通过 WebSocket 推送 `suggested_replies` 事件，提供更精准的建议。

<!-- 截图占位：消息管理功能，展示编辑、反馈、固定按钮 -->
![消息管理](images/消息管理.png)

---

## 六、虚拟滚动：100+ 条消息流畅不卡

当对话消息超过 100 条时，灵犀自动启用 **@tanstack/react-virtual** 虚拟滚动。

虚拟滚动的核心原理：只渲染可视区域内的消息（通常 10-20 条），超出屏幕的消息不创建 DOM 节点。这样即使有 1000 条消息，DOM 中也只有几十个节点，保持 60fps 流畅滚动。

```jsx
const virtualizer = useVirtualizer({
    count: messages.length,
    getScrollElement: () => scrollRef.current,
    estimateSize: (index) => estimateMessageHeight(messages[index]),
    overscan: 5,  // 上下各多渲染 5 条，减少白屏
});
```

---

## 七、6 套主题：CSS 变量的魔法

灵犀提供 6 套精心设计的视觉主题：

| 主题 | 风格 | 主色调 |
|------|------|--------|
| Light | 清新明亮 | 紫色 `#7c5cff` |
| Dark | 经典暗色 | 淡紫 `#9b85ff` |
| Midnight | 深夜黑 | 同 Dark |
| Cyber | 赛博朋克 | 青色 `#00e5ff` + 粉色 `#ff00aa` |
| Aurora | 极光 | 绿色 `#4ade80` + 青色 `#22d3ee` |
| Cosmos | 宇宙 | 紫色 `#c084fc` + 粉色 `#f472b6` |

切换主题只需要改变一个 `data-theme` 属性：

```javascript
setTheme: (t) => {
    localStorage.setItem('lingxi-theme', t);
    document.documentElement.setAttribute('data-theme', t);
    set({ theme: t });
}
```

所有颜色通过 CSS 变量定义，Tailwind 中使用 `[color:var(--xxx)]` 语法引用。这意味着主题切换是**零 JS 计算、零 DOM 操作**的纯 CSS 行为，切换瞬间完成，无闪烁。

```css
/* index.css */
:root {
    --bg: #f6f7fc;
    --accent: #7c5cff;
    --bubble-user: linear-gradient(135deg, #8a6dff 0%, #5e3df2 100%);
}

[data-theme="cyber"] {
    --bg: #050510;
    --accent: #00e5ff;
    --bubble-user: linear-gradient(135deg, #00e5ff 0%, #ff00aa 100%);
}
```

每个主题还有独立的极光背景效果（Aurora）——三层径向渐变叠加，营造沉浸式的视觉氛围。

<!-- 截图占位：6 套主题对比展示 -->
![主题对比](images/主题对比.png)

---

## 八、对话中止

在 AI 生成回复的过程中，用户可以随时点击"中止"按钮，立即停止 AI 的输出。

```javascript
abort: async () => {
    const sid = get().activeSessionId;
    if (!sid) return;
    await api.abortChat(sid); // POST /api/chat/abort
    set({ isStreaming: false, agentState: 'IDLE' });
}
```

后端收到 abort 请求后，会向 AI 引擎进程发送中断信号，终止当前的推理过程。已经产生的部分内容会保留在对话中。

---

## 九、搜索与导航

### 9.1 全文消息搜索（Cmd+K）

`Cmd+K` 快捷键唤起搜索弹窗，支持在所有会话的所有消息中进行全文搜索。

```
GET /api/messages/search?q=关键词

→ 返回匹配的消息列表，按时间排序
→ 每条结果显示所属会话标题、消息内容摘要、发送时间
→ 点击结果跳转到对应会话和消息
```

### 9.2 对话导出

支持将当前对话导出为 Markdown 文件，包含所有消息的完整内容。

### 9.3 会话管理

- **会话置顶**（Pin）——重要对话永远在列表顶部
- **会话重命名**——双击会话标题即可编辑
- **批量删除**——多选后一键删除

---

## 十、TTS 朗读

每条 AI 回复都有一个朗读按钮。灵犀使用 Web Speech API 实现 TTS，支持中文和英文：

```javascript
const speak = (text) => {
    const utterance = new SpeechSynthesisUtterance(text);
    utterance.lang = detectLanguage(text); // 'zh-CN' 或 'en-US'
    utterance.rate = 1.0;
    speechSynthesis.speak(utterance);
};
```

朗读过程中可以随时停止。这个功能在阅读长文档、学习外语等场景下特别有用。

---

## 十一、总结

灵犀的对话体验设计体现了一个核心理念：**让 AI 交互像使用一个精心打磨的工具，而不是面对一个冷冰冰的接口**。

每个功能都经过反复打磨：

1. **流式输出**不是简单的逐字显示，而是三种内容块的精确渲染
2. **多模态输入**覆盖五种方式，键盘、鼠标、语音全面支持
3. **RAG 引用**不是后台默默检索，而是完整的引用可视化体验
4. **规划模式**不是粗暴地列计划，而是人机协作的两阶段决策流程
5. **主题系统**不是简单换色，而是 CSS 变量驱动的零闪烁切换

在下一篇也是最后一篇文章中，我们将探讨灵犀的**技能生态、知识库管理、定时任务调度和企业 IM 集成**。

---

> **灵犀** —— 让 AI 成为你的工作伙伴，而不只是聊天对象。
>
> GitHub：[https://github.com/OdysseyFather/lingxi](https://github.com/OdysseyFather/lingxi)
>
> 如果觉得项目有价值，欢迎 Star 支持！
