去除全局 currentProvider 依赖,模型选择和 API 调用时直接传递模型参数。 优化模型选择状态同步逻辑,确保 UI 和服务层状态一致。 改进测试架构,支持多模型并行测试,并优化环境变量处理。 - 移除全局 currentProvider 状态 - API 调用时显式指定模型 - 模型选择时同步更新服务状态 - 优化测试用例,避免依赖全局状态 - 改进环境变量处理 - 添加测试模型配置 - 优化模型管理和状态同步 - 优化组件状态独立性,避免共享状态 - 统一使用 import.meta.env 读取环境变量 - 修复模型选择下拉框状态不同步问题
20 KiB
20 KiB
项目经验总结
架构与开发规范
API集成
- 配置与业务逻辑分离,便于扩展
- API配置统一接口格式
- 提示词模板独立管理
- 统一错误处理机制
- Header认证(Bearer Token)
- 统一认证处理机制
模块化设计
- 明确模块职责边界
- 避免功能重复实现
- 保持接口一致性
- 目录结构(src):
- api/: 外部API封装
- services/: 业务逻辑
- config/: 配置文件
- components/: UI组件
- prompts/: 提示词模板
LLM服务最佳实践
API标准化
- 使用OpenAI格式作为标准接口
- 不同模型只需配置baseURL
- 统一使用ChatOpenAI类
- 减少特殊适配代码
- gemini兼容:baseURL为https://generativelanguage.googleapis.com/v1beta/openai
配置管理
- 统一配置结构
- 模型选择和参数配置
- 提示词模板管理
- 错误处理和日志记录
Provider设计
-
唯一标识
- provider作为模型服务唯一标识符
- 用于区分不同AI模型服务提供商
-
配置完整性
- 包含完整配置信息
- 必要字段:name、baseURL、models、defaultModel、enabled、apiKey
-
安全性
- 保护默认模型防止误删除
- API密钥与provider绑定
- 使用环境变量管理敏感信息
-
扩展性
- 支持自定义模型提供商
- 允许动态更新配置
- 提供模型切换机制
-
实例管理
- 使用缓存管理模型实例
- 配置变更时清理缓存
- 统一实例创建和获取接口
错误处理最佳实践
2024-03-21更新
- 开发环境保留原始错误信息
- 避免过度包装错误信息
- 直接展示具体错误信息
- 包含足够的上下文信息
模板ID与模型key区分
-
问题描述
- 避免将模型key作为模板ID
- 防止加载不存在的模板文件
-
解决方案
- 明确区分模板ID和模型key
- 使用固定功能ID(如'optimize')
- 模型key仅用于API配置
测试规范
测试组织
- 按功能模块组织测试
- 测试正常和错误情况
- 验证状态变化
- 检查边界条件
环境变量处理
- 不强制要求所有环境变量
- 优雅跳过缺失环境变量
- 提供清晰的跳过原因
测试文件组织
- 按功能模块拆分测试文件
- 使用目录结构组织不同类型测试
- 测试运行器自动发现执行
- 每个文件关注特定功能
- 相关文件放同一目录
开发工具使用
NPM包版本管理
-
版本查看命令
npm list --depth=0 # 查看直接依赖 npm outdated # 查看可更新包 npm view [包名] versions # 查看可用版本 -
版本号规则
- ^: 兼容小版本更新(1.2.3 → 1.3.0)
- ~: 仅兼容补丁更新(1.2.3 → 1.2.4)
- *: 接受所有更新
-
更新最佳实践
- 更新前提交代码备份
- 更新后运行测试验证
- 问题时可回滚package.json
- 建议逐个包更新(ncu -u [包名])
Vue组件开发
Props和v-model
- Props是只读的,禁止直接修改
- 使用emit发送更新事件
- 计算属性处理双向绑定
UI设计规范
-
布局设计
- 使用grid实现响应式布局
- flex处理组件内部结构
- 统一使用tailwind间距
-
视觉系统
- CSS变量管理主题色
- 合理使用透明度创建层次
- 确保颜色对比度
- 统一的命名规范
-
深色主题
- 避免纯黑色背景
- 使用半透明提升层次
- 确保文本可读性
- 优化强调色使用
-
交互反馈
- 清晰的加载状态
- 及时的操作反馈
- 合理的禁用状态
- 适度的过渡动画
提示词迭代功能开发经验
1. 模板设计
- 迭代优化模板需要保持原有提示词的核心意图
- 模板结构要清晰,包含输入说明和输出格式
- 优化说明要包含保留部分和改进部分
- 避免过度修改导致偏离原意
2. UI交互设计
- 将迭代功能集成到现有界面,保持一致性
- 使用弹出式输入框收集优化需求
- 提供清晰的操作反馈和状态提示
- 优化按钮布局和交互流程
3. 数据结构设计
- 历史记录需要支持迭代关系
- 使用parentId追踪优化链
- 区分optimize和iterate两种类型
- 记录完整的优化过程
4. 最佳实践
-
模板管理
- 使用YAML格式存储模板
- 统一的模板索引管理
- 模板版本控制
- 清晰的模板命名规范
-
状态管理
- 区分不同的加载状态
- 合理的错误处理
- 用户友好的提示信息
- 状态重置的时机控制
-
历史记录
- 限制历史记录数量
- 提供迭代链查询
- 优化存储结构
- 异常处理机制
-
性能优化
- 合理使用缓存
- 避免重复API调用
- 优化组件更新逻辑
- 控制迭代深度
Vue 3 开发经验总结
Toast 组件和 Composable 使用
1. Composable 的正确使用
- 当使用 composable 返回的功能时,不需要用 ref 包装
- composable 应该在组件顶层直接调用,而不是在生命周期钩子中
- 如果 composable 返回的是函数,直接使用即可,不需要 .value
2. 模板引用的最佳实践
- 只在真正需要访问 DOM 元素时使用 ref 属性
- 使用 composable 时,优先使用其提供的方法而不是模板引用
- 如果组件提供了响应式 API,优先使用响应式 API 而不是模板引用
单元测试最佳实践
1. Composable 的模拟
// 正确的模拟方式
const mockSuccess = vi.fn()
const mockError = vi.fn()
vi.mock('./composables/useToast', () => ({
useToast: () => ({
success: mockSuccess,
error: mockError
})
}))
// 在 beforeEach 中重置
beforeEach(() => {
mockSuccess.mockClear()
mockError.mockClear()
})
2. 测试用例编写技巧
- 每个测试用例只测试一个功能点
- 使用具名的 mock 函数便于跟踪和验证
- 在 beforeEach 中重置所有状态
- 正确处理异步操作和错误情况
- 验证组件的所有可能状态
3. 错误处理和调试
- 使用 console.error 记录详细错误信息
- 在测试中验证错误处理逻辑
- 使用 try-catch 包装可能失败的操作
- 提供有意义的错误消息
代码组织和优化
1. 组件状态管理
- 将相关的状态和方法组织在一起
- 使用计算属性而不是方法来派生状态
- 避免在模板中使用复杂的表达式
2. 性能优化
- 避免不必要的响应式包装
- 及时清理不再需要的监听器和定时器
- 使用 computed 缓存计算结果
3. 代码可维护性
- 使用清晰的命名约定
- 将复杂逻辑抽取到 composable 中
- 保持组件的单一职责
数据结构一致性
1. 命名规范
- 在整个应用中保持一致的数据结构命名
- 修改数据结构时需要同步更新所有相关组件
- 使用TypeScript类型定义避免命名不一致
2. 最佳实践
-
数据接口定义
// 集中定义数据接口 interface HistoryRecord { id: string prompt: string result: string type: 'optimize' | 'iterate' parentId: string | null timestamp: number } -
组件通信
- 明确定义props和emit的数据格式
- 使用TypeScript类型检查
- 添加运行时验证
-
数据转换
- 在数据层统一处理格式转换
- 避免在组件中直接修改数据结构
- 提供清晰的数据转换方法
3. 常见问题
-
数据结构不一致
- 症状:组件显示空白或功能失效
- 原因:属性名不匹配
- 解决:统一命名规范,添加类型检查
-
数据转换错误
- 症状:数据显示异常
- 原因:格式转换逻辑错误
- 解决:集中管理数据转换逻辑
4. 测试策略
-
单元测试
- 测试数据结构的完整性
- 验证数据转换方法
- 检查边界情况
-
集成测试
- 测试组件间的数据传递
- 验证状态管理
- 测试用户交互流程
Vue响应式数据更新
1. 响应式数据同步
- 在数据变更后立即更新响应式引用
- 避免依赖页面刷新更新数据
- 确保UI状态与数据状态同步
2. 最佳实践
-
数据更新时机
// 正确的做法:立即更新响应式数据 const handleAction = async () => { try { const result = await someAction() // 立即更新响应式数据 data.value = result // 更新相关状态 relatedData.value = promptManager.getRelatedData() } catch (error) { console.error(error) } } -
组件通信
- 使用props传递数据时确保父组件数据更新
- 子组件emit事件后更新相关状态
- 考虑使用computed处理派生状态
-
状态管理
- 集中管理共享状态
- 提供清晰的状态更新方法
- 保持状态更新的一致性
3. 常见问题
-
数据更新不及时
- 症状:UI不能实时反映数据变化
- 原因:没有及时更新响应式引用
- 解决:在数据变更后立即更新
-
状态不同步
- 症状:组件间数据不一致
- 原因:更新不完整或时机不对
- 解决:确保所有相关状态同步更新
4. 调试技巧
-
Vue Devtools
- 监控组件状态变化
- 追踪数据流转过程
- 验证更新是否生效
-
响应式调试
- 使用watchEffect跟踪依赖
- 检查响应式引用是否正确
- 验证计算属性的依赖关系
Vue 测试经验总结
1. 测试框架选择
- 使用 Vitest 而不是 Jest 时,需要使用
vi而不是jestAPI - 常见的替换:
jest.clearAllMocks()->vi.clearAllMocks()jest.spyOn()->vi.spyOn()jest.fn()->vi.fn()
2. Vue 组件测试注意事项
2.1 数据修改
- 使用
setData可能会遇到对象不可扩展的问题 - 推荐直接通过
wrapper.vm修改数据,并使用nextTick等待更新:// 不推荐 await wrapper.setData({ prop: value }) // 推荐 wrapper.vm.prop = value await nextTick()
2.2 事件处理
- 异步事件处理需要使用
await - 验证事件处理结果时,应该等待所有异步操作完成
2.3 Mock 使用
- 在
beforeEach中重置所有 mock - 使用
mockResolvedValue和mockRejectedValue模拟异步操作 - 使用
mockReturnValue模拟同步操作 - 使用
mockImplementation自定义复杂的 mock 行为
2.4 断言最佳实践
- 验证函数调用时,使用
toHaveBeenCalledWith检查参数 - 验证函数是否被调用,使用
toHaveBeenCalled或not.toHaveBeenCalled - 验证异步操作后的状态变化,记得等待
nextTick
3. 常见问题及解决方案
3.1 对象不可扩展
问题:使用 setData 时遇到 "Cannot add property, object is not extensible" 错误
原因:Vue 3 的响应式系统使用 Proxy,某些对象可能被设置为不可扩展
解决方案:
- 直接通过
wrapper.vm修改数据 - 使用
nextTick等待更新 - 确保数据结构在组件中已经定义
3.2 异步操作处理
问题:测试异步操作时结果不符合预期 原因:没有正确等待异步操作完成 解决方案:
- 使用
async/await处理所有异步操作 - 在状态变化后使用
nextTick - 确保 mock 的异步操作返回 Promise
3.3 Mock 状态残留
问题:测试之间的 mock 状态互相影响 原因:没有正确重置 mock 状态 解决方案:
- 在
beforeEach中使用vi.clearAllMocks() - 使用
mockReset或mockRestore重置特定的 mock - 避免在测试套件级别共享可变的 mock 状态
2024-03-22 模型选择状态同步
-
问题描述
- UI选择的模型与服务实际使用的模型不同步
- 导致请求使用了错误的模型
-
解决方案
- 在模型选择变更时同步更新服务状态
- 在初始化时确保正确设置provider
- 添加专门的状态同步处理函数
-
最佳实践
- UI状态变更时及时同步到服务层
- 关键状态需要在所有相关组件间保持一致
- 初始化时注意检查并同步所有相关状态
- 使用专门的处理函数管理状态更新
-
代码实现
// 模型选择更新处理 const handleModelChange = (model) => { selectedModel.value = model llmService.setProvider(model) } // 初始化时同步状态 if (defaultModel) { selectedModel.value = defaultModel llmService.setProvider(defaultModel) }
2024-03-22 组件状态独立性
-
问题描述
- 不同功能区域共享同一个数据源导致状态混乱
- 组件属性配置不完整导致功能失效
-
解决方案
- 为不同功能区域使用独立的数据源
- 确保组件所需的所有必要属性都已配置
- 使用正确的状态变量和事件处理器
-
最佳实践
- 避免不相关组件共享状态
- 组件属性要完整配置
- 使用清晰的命名区分不同区域的状态
- 保持状态的独立性和隔离性
-
代码示例
<!-- 错误示例:共享状态 --> <InputPanel v-model="sharedContent" /> <InputPanel v-model="sharedContent" /> <!-- 正确示例:独立状态 --> <InputPanel v-model="promptContent" /> <InputPanel v-model="testContent" />
2024-03-22 API调用架构改进
-
问题描述
- 使用全局 currentProvider 导致状态管理复杂
- 模型切换需要同步多处状态
- 不够灵活,难以支持多模型并行使用
-
解决方案
- 移除全局 currentProvider
- 每次API调用时直接传入要使用的模型
- 简化状态管理,减少同步需求
-
最佳实践
- API调用时显式指定使用的模型
- 避免使用全局状态存储当前模型
- 保持服务层的无状态性
- 状态管理由调用方负责
-
代码示例
// 改进前:依赖全局状态 class LLMService { async sendMessage(messages) { const config = this.models[this.currentProvider]; // ... } } // 改进后:显式传入模型 class LLMService { async sendMessage(messages, model) { const config = this.models[model]; // ... } } -
优势
- 代码更清晰,依赖关系明确
- 更容易测试,不需要管理全局状态
- 支持不同组件使用不同模型
- 减少状态同步错误
开发经验总结
2024-03-22 测试架构改进
问题描述
在重构 LLM 服务架构后,测试用例出现了多个问题:
- 测试用例仍在使用已移除的
setApiKey和setProvider方法 - 测试提供商未在系统中注册,导致"未知的提供商"错误
- 环境变量处理方式不够优雅
解决方案
-
架构调整:
- 添加测试提供商到
DEFAULT_MODELS配置中 - 改进
buildRequestConfig函数以更灵活地处理 API 密钥
- 添加测试提供商到
-
测试用例改进:
- 移除对已废弃方法的依赖
- 使用
updateModelConfig统一管理配置 - 在 API 调用时直接传递提供商参数
- 优化环境变量检查的处理方式
代码示例
// 1. 添加测试提供商配置
export const DEFAULT_MODELS = {
// ... 其他提供商配置 ...
test: {
name: 'Test Model',
baseUrl: 'https://test.api/chat/completions',
models: ['test-model'],
defaultModel: 'test-model',
enabled: true // 测试模型默认启用
}
};
// 2. 改进 buildRequestConfig 函数
export function buildRequestConfig(provider, model, apiKey, messages) {
const config = DEFAULT_MODELS[provider];
if (!config) throw new Error(`未知的提供商: ${provider}`);
const headers = {
'Content-Type': 'application/json'
};
// 灵活处理 API 密钥
if (apiKey || config.apiKey) {
headers['Authorization'] = 'Bearer ' + (apiKey || config.apiKey);
}
return {
url: config.baseUrl,
headers,
body: {
model: model || config.defaultModel,
messages,
temperature: 0.7,
max_tokens: 2000
}
};
}
// 3. 测试用例改进示例
describe('API 测试', () => {
beforeEach(() => {
const apiKey = process.env.API_KEY;
if (!apiKey) {
console.log('跳过测试:未设置 API_KEY 环境变量');
return;
}
// 使用 updateModelConfig 更新配置
llmService.updateModelConfig(provider, { apiKey });
});
it('测试用例', async () => {
// 直接传递提供商参数
const response = await llmService.sendMessage(messages, provider);
expect(response).toBeDefined();
});
});
最佳实践
-
配置管理:
- 将所有提供商配置集中在一处管理
- 为测试场景预留专门的提供商配置
- 使用统一的配置更新机制
-
测试用例编写:
- 避免在测试中使用已废弃的方法
- 优雅处理环境变量依赖
- 使用清晰的跳过测试提示
- 保持测试用例的独立性
-
API 调用:
- 直接传递必要参数,避免依赖全局状态
- 灵活处理配置项,支持多种使用场景
- 提供清晰的错误提示
收获
- 测试架构应该随着主架构的演进而更新
- 好的测试设计可以帮助发现架构中的问题
- 环境变量处理需要在便利性和健壮性之间找到平衡
- 统一的配置管理可以简化测试用例的编写和维护
2024-03-22 Vite环境变量处理
问题描述
在集成测试中遇到环境变量读取问题:
- 测试用例使用
process.env读取环境变量 - 实际环境变量是以
VITE_前缀定义的 - 导致测试无法正确读取环境变量而被跳过
解决方案
- 在 Vite 项目中使用
import.meta.env读取环境变量 - 确保环境变量名称包含
VITE_前缀 - 更新测试用例以匹配正确的环境变量访问方式
代码示例
// 错误示例:使用 process.env
const apiKey = process.env.GEMINI_API_KEY;
// 正确示例:使用 import.meta.env
const apiKey = import.meta.env.VITE_GEMINI_API_KEY;
最佳实践
-
环境变量命名:
- Vite 项目中使用
VITE_前缀 - 使用大写字母和下划线
- 名称要清晰表达用途
- Vite 项目中使用
-
环境变量访问:
- 在 Vite 项目中统一使用
import.meta.env - 避免使用
process.env - 注意开发环境和生产环境的差异
- 在 Vite 项目中统一使用
-
测试用例编写:
- 正确处理环境变量依赖
- 提供清晰的跳过测试提示
- 验证环境变量存在性
收获
- Vite 项目有其特定的环境变量处理方式
- 需要注意开发框架的特性和约定
- 统一的环境变量访问方式有助于代码维护
- 清晰的错误提示有助于问题诊断
2024-03-22 组件状态同步
问题描述
在模型设置界面修改模型名称后,模型下拉选择框没有立即更新,需要页面刷新才能看到更改。这是由于:
- 组件间状态同步不完整
- 事件处理机制未正确实现
- 模型更新后的状态检查不够完善
解决方案
-
事件通信:
- 子组件触发
modelsUpdated事件 - 父组件监听并处理更新事件
- 确保所有模型操作后都触发更新
- 子组件触发
-
状态更新:
- 重新加载模型列表
- 检查当前选择的模型是否有效
- 必要时更新为默认模型
代码示例
<!-- 父组件 -->
<template>
<ModelManager
@modelsUpdated="loadModels"
/>
</template>
<script setup>
const loadModels = async () => {
models.value = llmService.getAllModels()
// 设置默认模型
const defaultModel = enabledModels.value[0]?.key
if (defaultModel) {
// 检查当前选择的模型是否有效
if (!enabledModels.value.find(m => m.key === currentModel.value)) {
currentModel.value = defaultModel
}
}
}
</script>
<!-- 子组件 -->
<script setup>
const emit = defineEmits(['modelsUpdated'])
const updateModel = () => {
// 更新模型配置
llmService.updateModelConfig(...)
// 触发更新事件
loadModels()
}
const loadModels = () => {
models.value = llmService.getAllModels()
emit('modelsUpdated', models.value)
}
</script>
最佳实践
-
组件通信:
- 使用事件机制进行组件间通信
- 明确定义事件名称和参数
- 确保事件在适当时机触发
-
状态管理:
- 集中管理共享状态
- 及时更新相关组件状态
- 处理无效状态的情况
-
数据校验:
- 检查数据有效性
- 提供合理的默认值
- 处理异常情况
收获
- 组件间的状态同步需要特别注意
- 事件机制是组件通信的重要方式
- 状态更新后需要进行有效性检查
- 提供良好的用户体验需要及时的状态更新