feat: 优化ElectronProxy层职责,修复fetchModelList类型定义,简化数据流与事件处理
This commit is contained in:
1 parent
c0cc629542
commit
98507e63ec
4 files changed
+59
-13
No files matched your search
@@ -146,3 +146,56 @@ await modelManager.addModel(key, config) // 自动序列化
|
||||
- ❌ 忽视开发体验
|
||||
|
||||
这次架构演进是一个很好的例子,展示了如何通过合理的架构设计来解决技术问题,同时提升开发体验。
|
||||
|
||||
## 🔄 第三阶段:代理层职责边界优化 (2025-07)
|
||||
|
||||
### 问题发现
|
||||
在解决Vue组件类型错误时,发现了一个重要的架构问题:ElectronProxy层承担了过多的数据格式转换职责。
|
||||
|
||||
**现象**:
|
||||
- Web版运行正常,桌面版出现`[object Object]`错误
|
||||
- InputWithSelect组件期望String类型,但接收到Object类型
|
||||
- 同样的代码在不同环境下表现不同
|
||||
|
||||
### 根本原因分析
|
||||
1. **类型定义不一致**:`global.d.ts`中定义`fetchModelList`返回`string[]`,但实际返回`ModelOption[]`
|
||||
2. **职责边界模糊**:ElectronProxy既负责IPC通信,又处理复杂的数据格式转换
|
||||
3. **异步性放大问题**:桌面版的IPC异步性暴露了Web版中被掩盖的竞态条件
|
||||
|
||||
**Web版 vs 桌面版的关键差异**:
|
||||
- **Web版**:同步数据流,事件循环掩盖了竞态条件
|
||||
- **桌面版**:IPC异步通信创造了race condition,使潜在问题显现
|
||||
|
||||
### 解决方案
|
||||
1. **修复类型定义**:
|
||||
```typescript
|
||||
// 修复前
|
||||
fetchModelList: (provider: string, customConfig?: any) => Promise<string[]>;
|
||||
|
||||
// 修复后
|
||||
fetchModelList: (provider: string, customConfig?: any) => Promise<Array<{value: string, label: string}>>;
|
||||
```
|
||||
|
||||
2. **简化代理层**:
|
||||
```typescript
|
||||
// ElectronProxy只负责IPC通信,不做数据转换
|
||||
async fetchModelList(provider: string, customConfig?: Partial<any>): Promise<ModelOption[]> {
|
||||
const safeCustomConfig = customConfig ? safeSerializeForIPC(customConfig) : customConfig;
|
||||
return this.electronAPI.llm.fetchModelList(provider, safeCustomConfig);
|
||||
}
|
||||
```
|
||||
|
||||
3. **移除冗余事件**:删除不必要的`@select`事件处理,简化数据流
|
||||
|
||||
### 架构原则确立
|
||||
- **单一职责**:每层只负责自己的核心功能,代理层专注IPC通信
|
||||
- **类型安全**:TypeScript类型定义必须与实际实现保持严格一致
|
||||
- **数据流简洁**:避免不必要的中间转换层,减少出错可能性
|
||||
|
||||
### 经验总结
|
||||
1. **异步性是双刃剑**:它会放大架构设计中的潜在问题
|
||||
2. **类型安全的重要性**:类型定义不仅是文档,更是架构约束
|
||||
3. **职责边界要清晰**:特别是在跨进程通信的场景下
|
||||
4. **环境差异要重视**:同样的代码在不同环境下可能表现不同
|
||||
|
||||
这次经验强化了我们对IPC架构设计的理解,为未来的跨进程功能开发提供了重要指导。
|
||||
@@ -57,8 +57,6 @@ export class ElectronLLMProxy implements ILLMService {
|
||||
): Promise<ModelOption[]> {
|
||||
// 自动序列化,防止Vue响应式对象IPC传递错误
|
||||
const safeCustomConfig = customConfig ? safeSerializeForIPC(customConfig) : customConfig;
|
||||
const modelNames = await this.electronAPI.llm.fetchModelList(provider, safeCustomConfig);
|
||||
// Convert string array to ModelOption array
|
||||
return modelNames.map(name => ({ value: name, label: name }));
|
||||
return this.electronAPI.llm.fetchModelList(provider, safeCustomConfig);
|
||||
}
|
||||
}
|
||||
Vendored
+1
-1
@@ -26,7 +26,7 @@ interface Window {
|
||||
}
|
||||
) => Promise<void>;
|
||||
testConnection: (provider: string) => Promise<void>;
|
||||
fetchModelList: (provider: string, customConfig?: any) => Promise<string[]>;
|
||||
fetchModelList: (provider: string, customConfig?: any) => Promise<Array<{value: string, label: string}>>;
|
||||
};
|
||||
model: {
|
||||
getModels: () => Promise<any[]>;
|
||||
|
||||
@@ -145,7 +145,6 @@
|
||||
:hint-text="t('modelManager.clickToFetchModels')"
|
||||
required
|
||||
:placeholder="t('modelManager.defaultModelPlaceholder')"
|
||||
@select="handleModelSelect"
|
||||
@fetch-options="handleFetchEditingModels"
|
||||
/>
|
||||
</div>
|
||||
@@ -339,7 +338,6 @@
|
||||
:hint-text="t('modelManager.clickToFetchModels')"
|
||||
required
|
||||
:placeholder="t('modelManager.defaultModelPlaceholder')"
|
||||
@select="handleModelSelect"
|
||||
@fetch-options="handleFetchNewModels"
|
||||
/>
|
||||
</div>
|
||||
@@ -715,7 +713,7 @@ const editModel = async (key) => {
|
||||
originalKey: key, // 保存原始key
|
||||
name: model.name,
|
||||
baseURL: model.baseURL,
|
||||
defaultModel: model.defaultModel,
|
||||
defaultModel: typeof model.defaultModel === 'string' ? model.defaultModel : (model.defaultModel?.value || model.defaultModel?.id || ''),
|
||||
apiKey: maskedApiKey,
|
||||
displayMaskedKey: true,
|
||||
originalApiKey: model.apiKey,
|
||||
@@ -808,10 +806,7 @@ const addDefaultModelOptions = (providerKey) => {
|
||||
modelOptions.value = [];
|
||||
}
|
||||
};
|
||||
const handleModelSelect = (option) => {
|
||||
// This can be expanded if additional logic is needed when model is selected
|
||||
console.log('Selected model:', option);
|
||||
};
|
||||
|
||||
const handleFetchEditingModels = async () => {
|
||||
if (!editingModel.value) {
|
||||
return;
|
||||
@@ -858,7 +853,7 @@ const handleFetchEditingModels = async () => {
|
||||
if (models.length > 0) {
|
||||
modelOptions.value = models;
|
||||
toast.success(t('modelManager.fetchModelsSuccess', {count: models.length}));
|
||||
|
||||
|
||||
// 如果当前选择的模型不在列表中,默认选择第一个
|
||||
if (!models.some(m => m.value === editingModel.value.defaultModel)) {
|
||||
editingModel.value.defaultModel = models[0].value;
|
||||
@@ -916,7 +911,7 @@ const handleFetchNewModels = async () => {
|
||||
if (models.length > 0) {
|
||||
modelOptions.value = models;
|
||||
toast.success(t('modelManager.fetchModelsSuccess', {count: models.length}));
|
||||
|
||||
|
||||
// 默认选择第一个模型
|
||||
newModel.value.defaultModel = models[0].value;
|
||||
} else {
|
||||
|
||||
Reference in new issue
Block a user