Files
ai-desktop/CODE_ANALYSIS_REPORT.md
T

1313 lines
32 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# AI Desktop 项目代码结构分析报告
## 项目概述
- **类型**: Electron + Vue 3 + TypeScript 桌面应用
- **功能**: AI对话助手,支持工具调用(搜索、抓取文章)
- **平台**: 小黑盒游戏社区集成
- **核心文件**:
- 主进程: `src/main/index.ts` (865行)
- Chat视图: `src/renderer/src/views/Chat.vue` (609行)
- 工具定义: `src/renderer/src/services/tools.ts` (304行)
---
## 一、性能优化点
### 🔴 P0 - 关键性能问题
#### 1. **Chat.vue 中的无限重渲染问题**
**文件**: `src/renderer/src/views/Chat.vue: Line 267-420`
**问题描述**:
- 在工具调用循环中,每次迭代都会创建新的Message对象并推送到messages数组
- 每次创建都会触发`scrollToBottom()`,导致频繁的DOM更新
- `currentContent``toolCall`的更新直接修改lastMessage,造成过度渲染
**代码示例**:
```typescript
// 第279-284行:每个token都触发scrollToBottom
onToken: (token: string) => {
currentContent += token
const lastMessage = messages.value[messages.value.length - 1]
if (lastMessage && lastMessage.role === 'assistant') {
lastMessage.content = currentContent
scrollToBottom() // 频繁调用!
}
}
// 第384-392行:循环中多次创建message
const nextAssistantMessage: Message = {
id: (Date.now() + iteration + 1000).toString(),
role: 'assistant',
content: '',
timestamp: new Date(),
toolCalls: []
}
messages.value.push(nextAssistantMessage)
scrollToBottom()
```
**影响**: ⚠️ 高,导致UI卡顿,特别是在工具调用次数多时
**优化方案**:
```typescript
// 1. 防抖scrollToBottom
let scrollTimer: ReturnType<typeof setTimeout> | null = null
const debouncedScrollToBottom = () => {
if (scrollTimer) clearTimeout(scrollTimer)
scrollTimer = setTimeout(() => {
scrollToBottom()
}, 50)
}
// 2. 批量更新消息而不是逐token更新
let tokenBuffer = ''
const flushTokenBuffer = () => {
if (tokenBuffer) {
const lastMessage = messages.value[messages.value.length - 1]
if (lastMessage) lastMessage.content += tokenBuffer
tokenBuffer = ''
debouncedScrollToBottom()
}
}
```
#### 2. **localStorage 同步写入性能瓶颈**
**文件**: `src/renderer/src/views/Chat.vue: Line 467-485`
**问题描述**:
- `saveMessages()`直接写入localStorage,没有压缩或序列化优化
- 每次发送消息都会序列化整个messages数组
- 对于大型对话历史(50条消息),JSON.stringify性能下降明显
**代码**:
```typescript
// 第470行
localStorage.setItem('chat-messages', JSON.stringify(messages.value))
```
**优化方案**:
```typescript
// 1. 只保存最近100条消息
const saveMessages = (immediate = false) => {
const messagesToSave = messages.value.slice(-100)
// 2. 压缩工具调用结果(只保存必要信息)
const compressed = messagesToSave.map(msg => ({
...msg,
toolCalls: msg.toolCalls?.map(tc => ({
name: tc.name,
status: tc.status,
// 不保存完整result,只保存摘要
result: tc.result?.substring?.(0, 200)
}))
}))
localStorage.setItem('chat-messages', JSON.stringify(compressed))
}
```
#### 3. **主进程中的 Playwright 浏览器上下文持久化问题**
**文件**: `src/main/index.ts: Line 396-406, 632-641`
**问题描述**:
- `getPersistentContext()`每次调用都检查并创建新上下文
- 多个搜索请求会复用同一个chromium进程,但内存泄漏风险大
- 没有上下文生命周期管理,可能导致内存持续增长
**代码**:
```typescript
// 第396-406行
async function getPersistentContext(headless = true): Promise<BrowserContext> {
if (!persistentContext) {
persistentContext = await chromium.launchPersistentContext(userDataDir, {
headless,
viewport: { width: 1280, height: 800 },
userAgent: '...'
})
}
return persistentContext
}
```
**优化方案**:
```typescript
// 1. 添加上下文大小限制
class ContextManager {
private maxPages = 5
private pages: Map<string, Page> = new Map()
async getPage(): Promise<Page> {
if (this.pages.size >= this.maxPages) {
const oldestKey = this.pages.keys().next().value
const oldPage = this.pages.get(oldestKey)
await oldPage?.close()
this.pages.delete(oldestKey)
}
// ...
}
}
// 2. 定期清理内存
setInterval(async () => {
if (persistentContext) {
// 清理超过1小时的页面
}
}, 1000 * 60 * 5)
```
---
### 🟡 P1 - 中等性能问题
#### 4. **AI Service 中的 SSE 流处理问题**
**文件**: `src/renderer/src/services/aiService.ts: Line 240-317`
**问题描述**:
- 每行都通过`JSON.parse()`单独解析,没有缓存
- 工具调用参数是字符串,需要多次`JSON.parse()`
- 没有流式处理优化(应该使用ReadableStream
**代码**:
```typescript
// 第260-265行:逐行JSON.parse
for (const line of lines) {
const trimmedLine = line.trim()
if (!trimmedLine || trimmedLine === 'data: [DONE]') continue
if (trimmedLine.startsWith('data: ')) {
try {
const jsonStr = trimmedLine.substring(6)
const data = JSON.parse(jsonStr) // 每行都parse
```
**优化方案**:
```typescript
// 缓存JSON parse结果,使用Try Catch外层
const parseCache = new Map()
const safeParse = (str: string) => {
if (parseCache.has(str)) return parseCache.get(str)
const result = JSON.parse(str)
parseCache.set(str, result)
return result
}
```
#### 5. **MessageCard 组件不必要的重渲染**
**文件**: `src/renderer/src/components/MessageCard.vue: Line 34-80`
**问题描述**:
- 每个消息卡片都有完整的MarkdownContent和ToolCallCard渲染
- 没有使用`computed`缓存格式化时间
- `formatTime()`在每次渲染时重新计算相对时间
**代码**:
```typescript
// 第62-79行:formatTime在每次render都计算
const formatTime = (date: Date): string => {
const now = new Date()
const diff = now.getTime() - date.getTime()
// ...
}
```
**优化方案**:
```typescript
// 使用computed缓存,只在Date改变时重新计算
const formattedTime = computed(() => {
if (!props.message.timestamp) return ''
return formatTime(props.message.timestamp)
})
// 添加v-if提前退出
<MarkdownContent
v-if="message.content"
:content="message.content"
/>
```
#### 6. **ToolsPanel.vue 中的重复 API 调用**
**文件**: `src/renderer/src/views/ToolsPanel.vue: Line 123-215`
**问题描述**:
- 搜索和获取文章都调用`executeToolCalls()`,没有缓存
- 同一个搜索结果被用户点击时会重新fetch整个文章
- 没有实现请求去重
**代码**:
```typescript
// 第212-214行:article-click触发新的fetch
const handleArticleClick = (url: string) => {
articleUrl.value = url
handleFetchArticle() // 每次点击都fetch,即使之前fetch过
}
```
**优化方案**:
```typescript
const articleCache = new Map<string, any>()
const handleArticleClick = async (url: string) => {
if (articleCache.has(url)) {
articleResult.value = articleCache.get(url)
return
}
// 否则 fetch 并缓存
}
```
---
## 二、用户体验改进
### 🔴 P0 - 关键UX问题
#### 7. **缺少加载状态提示和错误处理**
**文件**: `src/renderer/src/views/Chat.vue: Line 220-445`
**问题描述**:
- 工具调用失败时,用户不知道是否可以重试
- 没有进度提示(第几个工具调用在执行)
- 错误消息不够详细,用户难以了解失败原因
**现象**:
```typescript
// 第364-371行:错误只是显示generic message
} catch (error) {
console.error(`Tool call ${i + 1} error:`, error)
if (lastMessage && lastMessage.toolCalls && lastMessage.toolCalls[i]) {
lastMessage.toolCalls[i].status = 'error'
lastMessage.toolCalls[i].result = JSON.stringify({ error: '工具执行失败' })
// 没有详细错误信息给用户
}
}
```
**优化方案**:
```typescript
// 1. 增加详细错误信息
lastMessage.toolCalls[i].result = JSON.stringify({
error: '工具执行失败',
reason: error.message,
suggestion: getSuggestion(error),
retryable: isRetryable(error)
})
// 2. 添加重试按钮
<ToolCallCard
:tool-call="toolCall"
:can-retry="toolCall.status === 'error' && toolCall.retryable"
@retry="retryToolCall"
/>
```
#### 8. **工具调用进度反馈不清晰**
**文件**: `src/renderer/src/views/Chat.vue: Line 314-373`
**问题描述**:
- 工具调用完成后没有总结或完成提示
- 用户不知道是还有更多工具调用还是已经完成
- 加载指示器停留时间不确定
**代码现象**:
```typescript
// 第321-373行:没有进度指示
for (let i = 0; i < currentResponse.tool_calls.length; i++) {
// 执行工具
// 但没有显示 "执行了 i 个/共 total 个工具"
}
```
**优化**:
```typescript
// 添加进度计数器
<div class="tool-progress">
: {{ currentToolIndex + 1 }} / {{ totalTools }}
</div>
```
#### 9. **消息历史加载卡顿**
**文件**: `src/renderer/src/views/Chat.vue: Line 117-149`
**问题描述**:
- 初始化时只加载最近50条消息,但没有给用户"加载更多"选项
- 没有提示用户历史消息被截断
- localStorage中大量数据未被利用
**代码**:
```typescript
// 第133-134行:硬编码50条限制
const recentMessages = parsed.slice(-50)
messages.value = recentMessages.map(...)
```
**优化**:
```typescript
// 1. 添加"查看更多历史"功能
const showMoreHistory = async () => {
const offset = messages.value.length - 50
if (offset > 0) {
const moreMessages = parsed.slice(offset, offset + 50)
messages.value.unshift(...moreMessages)
}
}
// 2. UI提示
<div v-if="hasMoreHistory" class="load-more-button">
<el-button @click="showMoreHistory"></el-button>
</div>
```
#### 10. **登录状态提示不及时**
**文件**: `src/renderer/src/services/tools.ts: Line 190-241`
**问题描述**:
- 搜索失败时才告知用户未登录
- Settings中登录状态检查不实时(需要点击refresh)
- 没有自动登录状态同步
**代码**:
```typescript
// 第202-211行:搜索时才返回NOT_LOGGED_IN
if (result.error === 'NOT_LOGGED_IN') {
return {
success: false,
error: 'NOT_LOGGED_IN',
message: `搜索 ${platform} 需要登录。请先登录小黑盒账号。`
}
}
```
**优化**:
```typescript
// 1. 在Chat初始化时检查登录状态
onMounted(async () => {
const loginStatus = await window.electron.ipcRenderer.invoke('check-platform-login', {
platform: 'xiaoheihe'
})
if (!loginStatus.isLoggedIn) {
ElMessage.warning('请登录小黑盒账号以使用搜索功能')
}
})
// 2. 定期同步登录状态(每5分钟)
setInterval(syncLoginStatus, 300000)
```
---
### 🟡 P1 - 中等UX问题
#### 11. **搜索结果没有加载更多功能**
**文件**: `src/renderer/src/components/SearchResultCard.vue`
**问题描述**:
- 只展示返回的所有结果,没有分页
- 结果多时UI会很长,难以查找
**优化**:
```typescript
// 只显示前10条,添加"查看更多"
const displayResults = computed(() =>
data.results?.slice(0, 10) || []
)
const hasMore = computed(() =>
data.results && data.results.length > 10
)
```
#### 12. **文章内容显示不完整**
**文件**: `src/renderer/src/components/ArticleResultCard.vue: Line 54`
**问题描述**:
- 文章内容使用`white-space: pre-wrap`显示,不支持HTML或Markdown
- 长文章显示时没有分页或折叠
**代码**:
```typescript
// 第54行:纯文本显示,丢失格式
<div class="content-text">{{ data.article.content }}</div>
```
**优化**:
```vue
<!-- 支持Markdown格式 -->
<MarkdownContent
v-if="data.article.content"
:content="data.article.content"
/>
<!-- 长文章添加折叠 -->
<div v-if="contentLength > 1000" class="content-preview">
<el-collapse>
<el-collapse-item name="full-content">
<template #title>查看完整内容 ({{ contentLength }} )</template>
<MarkdownContent :content="data.article.content" />
</el-collapse-item>
</el-collapse>
</div>
```
---
## 三、代码质量问题
### 🔴 P0 - 关键质量问题
#### 13. **重复的类型定义**
**文件**:
- `Chat.vue: Line 75-88` (ToolCallInfo, Message)
- `tools.ts: Line 16-30` (ToolCall, ToolResult)
- `aiService.ts: Line 12-18` (Message)
**问题描述**:
- Message接口定义了3次
- ToolCall接口定义了2次
- 没有单一的真实源头
**优化**:
```typescript
// 创建 src/renderer/src/types/index.ts
export interface Message {
id?: string
role: 'user' | 'assistant' | 'system' | 'tool'
content: string | null
timestamp?: Date
tool_calls?: ToolCall[]
tool_call_id?: string
name?: string
toolCalls?: ToolCallInfo[] // 兼容旧格式
}
export interface ToolCall {
id: string
type: 'function'
function: {
name: string
arguments: string
}
}
export interface ToolCallInfo {
name: string
args?: Record<string, any>
result?: any
status: 'loading' | 'success' | 'error'
}
// 在各文件中导入
import type { Message, ToolCall, ToolCallInfo } from '@/types'
```
#### 14. **错误处理不一致**
**文件**:
- `Chat.vue: Line 256-445` (复杂的error handling)
- `ToolsPanel.vue: Line 156-165` (简单的error handling)
- `tools.ts: Line 200-241` (多层error handling)
**问题描述**:
- 有些地方使用try-catch,有些使用if检查
- 错误消息格式不一致
- 没有统一的错误处理策略
**代码示例**:
```typescript
// Chat.vue: 复杂的error handling
try {
// ...
} catch (error: any) {
console.error('AI request failed:', error)
ElMessage.error(error.message || '请求失败,请检查配置')
messages.value = messages.value.filter((msg) => msg.id !== userMessage.id)
}
// ToolsPanel.vue: 简单的error handling
} catch (error: any) {
console.error('Search failed:', error)
ElMessage.error(error.message || '搜索失败')
}
// tools.ts: if检查
if (!result.success) {
return {
success: false,
error: result.error || '搜索失败'
}
}
```
**优化**:
```typescript
// 创建统一的错误处理工具
export class AppError extends Error {
constructor(
message: string,
public code: string,
public recoverable: boolean = false
) {
super(message)
}
}
// 使用工厂函数创建特定错误
export const createError = {
notLoggedIn: () => new AppError('需要登录', 'NOT_LOGGED_IN', true),
networkError: (msg?: string) => new AppError(msg || '网络连接失败', 'NETWORK_ERROR', true),
toolExecutionFailed: () => new AppError('工具执行失败', 'TOOL_ERROR', true)
}
// 统一错误处理
const handleError = (error: Error) => {
if (error instanceof AppError) {
ElMessage.error(error.message)
if (error.recoverable) {
// 显示重试按钮
}
}
}
```
#### 15. **缺少TypeScript类型安全**
**文件**: `Chat.vue: Line 182-199`
**问题描述**:
- `getActiveModel()`返回类型为`Promise<ModelConfig | null>`,但ModelConfig接口与实际settings中的类型不匹配
- 没有在主进程中定义和导出类型
- IPC调用的返回类型是any
**代码**:
```typescript
// 第182-199行:类型不安全
const getActiveModel = async (): Promise<ModelConfig | null> => {
try {
const settings = await window.electron.ipcRenderer.invoke('read-settings')
// settings类型是any,无法推断出modelConfigs
if (!settings.activeModelId) {
return null
}
// ...
}
}
```
**优化**:
```typescript
// 创建共享类型文件 src/shared/types.ts
export interface ModelConfig {
id: string
name: string
provider: 'openai' | 'deepseek'
model: string
apiKey: string
baseUrl: string
}
export interface Settings {
activeModelId: string | null
modelConfigs: ModelConfig[]
}
// 在Chat.vue中使用
const getActiveModel = async (): Promise<ModelConfig | null> => {
const settings = await window.electron.ipcRenderer.invoke('read-settings') as Settings
// 现在settings有正确的类型
}
```
---
### 🟡 P1 - 中等质量问题
#### 16. **魔法字符串和数字遍布代码**
**文件**: `Chat.vue, index.ts, tools.ts`
**问题描述**:
- 硬编码的延迟时间:`setTimeout(..., 100)`, `setTimeout(..., 200)`, `setTimeout(..., 300)`
- 硬编码的常量:`maxRows: 4`, `minRows: 1`, `-50` (slice)
- 硬编码的工具名称:`'search_platform'`, `'fetch_article'`
**代码**:
```typescript
// Chat.vue: 魔法数字
setTimeout(() => { ... }, 0) // 第148行
setTimeout(() => { ... }, 100) // 第230行
setTimeout(() => { ... }, 200) // 第274行
setTimeout(() => { ... }, 300) // 第480行
await page.goto(url, { waitUntil: 'networkidle', timeout: 30000 }) // 30000?
const recentMessages = parsed.slice(-50) // 为什么是50条?
let maxIterations = 10 // 为什么是10次?
```
**优化**:
```typescript
// 创建 src/renderer/src/constants.ts
export const TIMEOUTS = {
DEFER_MESSAGE_LOAD: 0,
ENSURE_IME_READY: 100,
ENSURE_RENDERER_READY: 200,
DEBOUNCE_SAVE: 300,
TOOL_EXECUTION_TIMEOUT: 30000
} as const
export const LIMITS = {
MAX_RECENT_MESSAGES: 50,
MAX_TOOL_ITERATIONS: 10,
MAX_SEARCH_RESULTS_DISPLAY: 10,
ARTICLE_PREVIEW_LENGTH: 1000
} as const
export const TOOL_NAMES = {
CHECK_LOGIN: 'check_platform_login',
SEARCH: 'search_platform',
FETCH_ARTICLE: 'fetch_article'
} as const
// 在代码中使用
setTimeout(() => { ... }, TIMEOUTS.DEFER_MESSAGE_LOAD)
const recentMessages = parsed.slice(-LIMITS.MAX_RECENT_MESSAGES)
```
#### 17. **过度复杂的条件检查**
**文件**: `src/main/index.ts: Line 42-72`
**问题描述**:
- `SearchRateLimiter.canSearch()`的逻辑可以简化
- 时间比较逻辑混乱
**代码**:
```typescript
// 第42-72行:过度复杂
canSearch(): { allowed: boolean; waitTime?: number; reason?: string } {
const now = Date.now()
const timeSinceLastSearch = now - this.lastSearchTime
if (timeSinceLastSearch > this.resetInterval) {
this.searchCount = 0
}
if (this.searchCount >= this.maxSearchPerMinute) {
const waitTime = this.resetInterval - timeSinceLastSearch
return {
allowed: false,
waitTime: Math.ceil(waitTime / 1000),
reason: '搜索过于频繁,请稍后再试'
}
}
if (timeSinceLastSearch < this.minInterval) {
const waitTime = this.minInterval - timeSinceLastSearch
return {
allowed: false,
waitTime: Math.ceil(waitTime / 1000),
reason: '请求过快,请稍后再试'
}
}
return { allowed: true }
}
```
**优化**:
```typescript
canSearch(): RateLimitResult {
const now = Date.now()
const elapsed = now - this.lastSearchTime
// 检查是否需要重置计数
if (elapsed > this.resetInterval) {
this.searchCount = 0
}
// 检查频率限制(优先级最高)
if (this.searchCount >= this.maxSearchPerMinute) {
return this.createRateLimitResult(
this.resetInterval - elapsed,
'RATE_LIMIT_EXCEEDED'
)
}
// 检查最小间隔
if (elapsed < this.minInterval) {
return this.createRateLimitResult(
this.minInterval - elapsed,
'INTERVAL_LIMIT'
)
}
return { allowed: true }
}
private createRateLimitResult(waitMs: number, reason: string): RateLimitResult {
return {
allowed: false,
waitTime: Math.ceil(waitMs / 1000),
reason: this.getReason(reason)
}
}
```
#### 18. **缺少单元测试和错误覆盖**
**文件**: 项目整体
**问题描述**:
- 没有单元测试文件
- 没有集成测试
- 错误情况未经过测试
**优化方案**:
```typescript
// 创建 src/renderer/src/services/__tests__/tools.test.ts
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { ToolExecutor } from '../tools'
describe('ToolExecutor', () => {
let executor: ToolExecutor
beforeEach(() => {
executor = new ToolExecutor()
})
it('should handle platform login check', async () => {
const toolCall = {
id: 'test-1',
type: 'function' as const,
function: {
name: 'check_platform_login',
arguments: JSON.stringify({ platform: 'xiaoheihe' })
}
}
const result = await executor.execute(toolCall)
expect(result.role).toBe('tool')
expect(result.name).toBe('check_platform_login')
})
it('should handle invalid arguments', async () => {
const toolCall = {
id: 'test-2',
type: 'function' as const,
function: {
name: 'search_platform',
arguments: '{invalid json}'
}
}
const result = await executor.execute(toolCall)
expect(result.content).toContain('参数解析失败')
})
})
```
---
## 四、功能完善
### 🔴 P0 - 缺失的关键功能
#### 19. **缺少对话导出功能**
**问题描述**:
- 用户无法导出对话历史
- 没有分享功能
- 数据只存在localStorage中,易丢失
**建议**:
```typescript
// 添加导出功能
const exportChat = () => {
const data = {
version: '1.0',
exportDate: new Date().toISOString(),
messages: messages.value
}
const json = JSON.stringify(data, null, 2)
const blob = new Blob([json], { type: 'application/json' })
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = `chat-${Date.now()}.json`
a.click()
URL.revokeObjectURL(url)
}
// 添加导入功能
const importChat = async (file: File) => {
const text = await file.text()
const data = JSON.parse(text)
messages.value = [
...messages.value,
...data.messages
]
saveMessages(true)
}
```
#### 20. **缺少对话收藏/标记功能**
**问题描述**:
- 用户无法标记重要对话
- 没有分类功能
- 对话历史平铺,难以找到之前的对话
**建议**:
```typescript
interface ChatSession {
id: string
title: string
createdAt: Date
messages: Message[]
starred: boolean
tags: string[]
}
// 在localStorage中分别存储sessions和当前session
const currentSessionId = ref<string>()
const sessions = ref<ChatSession[]>([])
const createNewSession = () => {
const session: ChatSession = {
id: Date.now().toString(),
title: `对话 ${new Date().toLocaleDateString()}`,
createdAt: new Date(),
messages: [],
starred: false,
tags: []
}
sessions.value.push(session)
currentSessionId.value = session.id
}
```
#### 21. **缺少Markdown语法高亮**
**文件**: `src/renderer/src/components/MarkdownContent.vue`
**问题描述**:
- markdown中的代码块没有语法高亮
- 使用了marked库但没有配置高亮器
**优化**:
```typescript
// 安装 npm install highlight.js
import { marked } from 'marked'
import { markedHighlight } from 'marked-highlight'
import hljs from 'highlight.js'
import 'highlight.js/styles/atom-one-dark.css'
marked.use(
markedHighlight({
langPrefix: 'hljs language-',
highlight(code, lang) {
const language = hljs.getLanguage(lang) ? lang : 'plaintext'
return hljs.highlight(code, { language }).value
}
})
)
```
#### 22. **缺少快捷键帮助**
**问题描述**:
- 用户不知道有Command+K快捷键
- 没有快捷键提示菜单
- 可发现性差
**建议**:
```typescript
// 添加快捷键帮助面板
const showShortcutHelp = () => {
ElDialog.confirm('快捷键列表', 'OK').then(() => {
// 显示快捷键列表
})
}
// 在Chat组件中按 ? 显示帮助
window.addEventListener('keydown', (e) => {
if (e.key === '?') {
showShortcutHelp()
}
})
```
---
### 🟡 P1 - 建议添加的功能
#### 23. **添加消息搜索功能**
```typescript
const searchMessages = (query: string) => {
return messages.value.filter(msg =>
msg.content.toLowerCase().includes(query.toLowerCase())
)
}
```
#### 24. **添加对话主题切换**
```typescript
const themes = {
light: { /* ... */ },
dark: { /* ... */ }
}
const setTheme = (name: keyof typeof themes) => {
localStorage.setItem('app-theme', name)
// 应用主题
}
```
#### 25. **添加消息撤回/编辑功能**
```typescript
interface Message {
id: string
// ...
editable: boolean
editHistory?: string[]
}
const editMessage = (messageId: string, newContent: string) => {
const msg = messages.value.find(m => m.id === messageId)
if (msg) {
msg.editHistory?.push(msg.content)
msg.content = newContent
}
}
```
---
## 五、错误处理和异常情况
### 🔴 P0 - 关键错误处理缺陷
#### 26. **Playwright 浏览器崩溃未处理**
**文件**: `src/main/index.ts: Line 352-393`
**问题描述**:
- 浏览器进程意外结束时没有恢复机制
- fetchArticleContent()不能重试
- persistentContext崩溃后不会重新创建
**代码**:
```typescript
// 第352-393行:没有错误恢复
async function fetchArticleContent(url: string): Promise<{ /* ... */ }> {
let browser
try {
browser = await chromium.launch({ headless: true })
// ... 如果这里崩溃,browser会是undefined
} finally {
if (browser) {
// 但如果browser进程已经死了呢?
await browser.close()
}
}
}
```
**优化**:
```typescript
// 添加重试逻辑和错误恢复
async function fetchArticleContentWithRetry(
url: string,
maxRetries = 3
): Promise<ArticleContent> {
for (let i = 0; i < maxRetries; i++) {
try {
return await fetchArticleContent(url)
} catch (error) {
if (i === maxRetries - 1) throw error
// 等待后重试
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)))
}
}
}
// 添加crash检测
persistentContext?.on?.('disconnected', () => {
console.log('Browser context disconnected, will recreate on next use')
persistentContext = null
})
```
#### 27. **网络错误未区分**
**文件**: `src/renderer/src/services/aiService.ts: Line 230-234`
**问题描述**:
- 所有失败都用同一个错误消息
- 无法区分网络错误、API错误、超时等
- 用户无法采取相应的行动
**代码**:
```typescript
// 第230-234行:所有错误都一样
const response = await fetch(endpoint, {
// ...
})
if (!response.ok) {
const errorText = await response.text()
throw new Error(`API 请求失败: ${response.status} ${errorText}`)
}
```
**优化**:
```typescript
class APIError extends Error {
constructor(
message: string,
public status: number,
public responseText: string
) {
super(message)
this.name = 'APIError'
}
}
class NetworkError extends Error {
constructor(message: string) {
super(message)
this.name = 'NetworkError'
}
}
// 更好的错误处理
const response = await fetch(endpoint, {
// ...
}).catch((error) => {
if (error instanceof TypeError) {
throw new NetworkError('网络连接失败,请检查网络设置')
}
throw error
})
if (!response.ok) {
const errorText = await response.text()
if (response.status === 401) {
throw new APIError('API Key 无效或已过期', 401, errorText)
} else if (response.status === 429) {
throw new APIError('请求过于频繁,请稍后再试', 429, errorText)
} else if (response.status >= 500) {
throw new APIError('服务器错误,请稍后重试', response.status, errorText)
}
throw new APIError('API 请求失败', response.status, errorText)
}
```
#### 28. **工具调用死循环未防护**
**文件**: `src/renderer/src/views/Chat.vue: Line 320-424`
**问题描述**:
- maxIterations硬编码为10
- 如果AI不断生成工具调用,会浪费资源
- 没有检测无限循环的模式
**代码**:
```typescript
// 第320-424行:简单的maxIterations检查
while (currentResponse.tool_calls && currentResponse.tool_calls.length > 0 && iteration < maxIterations) {
// ...
if (iteration >= maxIterations) {
console.warn('Reached maximum tool call iterations')
ElMessage.warning('工具调用次数过多,已停止')
}
}
```
**优化**:
```typescript
// 检测模式:如果连续的工具调用相同,则认为可能是循环
const toolCallHistory: string[] = []
while (currentResponse.tool_calls && /* ... */) {
const toolNames = currentResponse.tool_calls.map(tc => tc.function.name)
const currentPattern = toolNames.join(',')
// 检查是否重复相同的工具调用
if (toolCallHistory[toolCallHistory.length - 1] === currentPattern &&
toolCallHistory[toolCallHistory.length - 2] === currentPattern) {
ElMessage.error('检测到可能的死循环,已停止')
break
}
toolCallHistory.push(currentPattern)
if (toolCallHistory.length > 10) {
toolCallHistory.shift()
}
// ...
iteration++
}
```
#### 29. **未登录状态处理不完善**
**文件**: `src/renderer/src/services/tools.ts: Line 201-241`
**问题描述**:
- 搜索时才发现未登录,用户已输入查询
- Settings中登录状态检查不够频繁
- 没有自动刷新登录状态
**优化**:
```typescript
// 在Chat.vue中启动时检查
onMounted(() => {
// ... 现有代码
// 检查登录状态
checkLoginStatus()
// 定期检查登录状态(每5分钟)
const checkInterval = setInterval(() => {
checkLoginStatus()
}, 300000)
onUnmounted(() => {
clearInterval(checkInterval)
})
})
const checkLoginStatus = async () => {
const result = await window.electron.ipcRenderer.invoke('check-platform-login', {
platform: 'xiaoheihe'
})
if (!result.isLoggedIn) {
isLoggedIn.value = false
// 禁用搜索相关功能
} else {
isLoggedIn.value = true
}
}
// 在模板中
<el-button
type="primary"
:disabled="!isLoggedIn"
@click="handleSearch"
>
</el-button>
<div v-if="!isLoggedIn" class="login-warning">
使
<el-button link @click="openSettings"></el-button>
</div>
```
#### 30. **工具执行结果验证缺失**
**文件**: `src/renderer/src/services/tools.ts: Line 98-161`
**问题描述**:
- 工具执行结果没有验证
- 返回的数据格式可能不符合预期
- 没有null/undefined检查
**代码**:
```typescript
// 第104-116行:直接返回result,没有验证
let args: any
try {
args = JSON.parse(argsStr)
console.log('Parsed arguments:', args)
} catch (error) {
console.error('Failed to parse tool arguments:', error)
return {
tool_call_id: id,
role: 'tool',
name,
content: JSON.stringify({ error: '参数解析失败', details: String(error) })
}
}
```
**优化**:
```typescript
// 使用Zod进行运行时验证
import { z } from 'zod'
const ToolResultSchema = z.object({
success: z.boolean(),
error: z.string().optional(),
results: z.array(z.any()).optional(),
message: z.string().optional()
})
async execute(toolCall: ToolCall): Promise<ToolResult> {
// ... 执行工具
try {
// 验证结果格式
const validated = ToolResultSchema.parse(result)
return {
tool_call_id: id,
role: 'tool',
name,
content: JSON.stringify(validated)
}
} catch (error) {
console.error('Tool result validation failed:', error)
return {
tool_call_id: id,
role: 'tool',
name,
content: JSON.stringify({
error: '工具结果格式错误',
details: error.message
})
}
}
}
```
---
## 优化建议优先级排序
### 第一阶段(高优先级)- 立即修复
1. **P0-01**: 防抖scrollToBottom,减少re-render
2. **P0-02**: 优化localStorage写入
3. **P0-03**: 统一错误处理和类型定义
4. **P0-04**: 改进登录状态提示
5. **P0-05**: 处理Playwright进程崩溃
### 第二阶段(中优先级)- 1-2周内
6. **P1-01**: 缓存搜索结果和文章
7. **P1-02**: 添加工具调用进度提示
8. **P1-03**: 改进文章显示(支持Markdown)
9. **P1-04**: 提取魔法字符串为常量
10. **P1-05**: 工具调用死循环检测
### 第三阶段(低优先级)- 1个月内
11. **P2-01**: 添加对话导出/导入功能
12. **P2-02**: 实现多对话会话管理
13. **P2-03**: 添加消息搜索功能
14. **P2-04**: 语法高亮
15. **P2-05**: 单元测试
---
## 检查清单
- [ ] 实现防抖scrollToBottom
- [ ] 优化localStorage存储结构
- [ ] 创建统一的types/index.ts
- [ ] 创建AppError错误类
- [ ] 创建constants.ts文件
- [ ] 添加登录状态检查
- [ ] 实现搜索结果缓存
- [ ] 添加工具调用重试逻辑
- [ ] 改进文章预览显示
- [ ] 添加单元测试框架
---
## 总结
该项目是一个功能完整的AI对话工具,但存在以下主要问题:
1. **性能**: 频繁重渲染、localStorage同步写入、浏览器进程管理不善
2. **UX**: 缺少加载状态、错误恢复、登录状态提示
3. **代码质量**: 类型定义重复、魔法字符串、错误处理不一致
4. **功能完善度**: 缺少导出、会话管理、语法高亮
建议按照优先级逐步改进,特别是第一阶段的5个关键问题应该立即修复。