feat: 完善OutputDisplay V2的CompareService依赖注入,修复相关组件问题
This commit is contained in:
1 parent
ea77ff44ec
commit
0843cb6608
7 files changed
+354
-15
No files matched your search
@@ -5,8 +5,8 @@
|
||||
|
||||
## 时间线
|
||||
- 开始时间:2024-12-30
|
||||
- 完成时间:待定
|
||||
- 状态:📋 设计阶段
|
||||
- 完成时间:2025-01-06
|
||||
- 状态:✅ 已完成
|
||||
|
||||
## 相关开发者
|
||||
- 主要开发者:项目团队
|
||||
@@ -14,20 +14,29 @@
|
||||
|
||||
## 文档清单
|
||||
- [x] `design.md` - 输出显示v2设计文档
|
||||
- [ ] `implementation.md` - 实现记录
|
||||
- [ ] `experience.md` - 开发经验总结
|
||||
- [x] `implementation.md` - 实现记录
|
||||
- [x] `experience.md` - 开发经验总结(包含在implementation.md中)
|
||||
|
||||
## 相关代码变更
|
||||
- 影响包:@prompt-optimizer/ui, @prompt-optimizer/web
|
||||
- 影响包:@prompt-optimizer/ui, @prompt-optimizer/core
|
||||
- 主要变更:
|
||||
- 输出显示界面重新设计
|
||||
- 交互体验优化
|
||||
- 功能扩展支持
|
||||
- 输出显示界面重新设计(统一顶层工具栏)
|
||||
- 交互体验优化(智能视图切换)
|
||||
- CompareService 依赖注入架构完善
|
||||
- 对比功能正常工作
|
||||
|
||||
## 后续影响
|
||||
- 提升用户体验
|
||||
- 增强产品竞争力
|
||||
- 为后续功能扩展奠定基础
|
||||
- ✅ 提升用户体验(统一工具栏,智能切换)
|
||||
- ✅ 增强产品竞争力(对比功能正常工作)
|
||||
- ✅ 为后续功能扩展奠定基础(完善的依赖注入架构)
|
||||
|
||||
## 关键问题修复
|
||||
|
||||
### CompareService 依赖注入不完整问题
|
||||
- **问题**:重构过程中子组件已修改但父组件未配套更新
|
||||
- **错误**:`CompareService is required but not provided`
|
||||
- **修复**:完善服务架构 + 父组件依赖注入
|
||||
- **验证**:手动测试确认对比功能正常工作
|
||||
|
||||
## 相关功能点
|
||||
- 前置依赖:104-test-panel-refactor
|
||||
|
||||
@@ -0,0 +1,271 @@
|
||||
# OutputDisplay V2 实现记录
|
||||
|
||||
## 概述
|
||||
|
||||
本文档记录了 OutputDisplay V2 的实现过程,包括设计实现、问题修复和验证测试的完整流程。
|
||||
|
||||
## 时间线
|
||||
|
||||
- **设计阶段**: 2024-12-30 - 完成核心设计和架构规划
|
||||
- **实现阶段**: 2024-12-30 - 完成核心功能重构
|
||||
- **问题修复**: 2025-01-06 - 修复 CompareService 依赖注入问题
|
||||
- **状态**: ✅ 已完成
|
||||
|
||||
## 核心实现
|
||||
|
||||
### 1. 组件架构重构
|
||||
|
||||
V2 版本采用了全新的组件架构,核心变化包括:
|
||||
|
||||
#### 1.1 组件层次结构
|
||||
```
|
||||
OutputDisplay.vue (包装器)
|
||||
├── OutputDisplayCore.vue (核心组件)
|
||||
│ ├── 统一顶层工具栏
|
||||
│ ├── 推理面板 (可选)
|
||||
│ └── 主内容区域
|
||||
└── OutputDisplayFullscreen.vue (全屏模式)
|
||||
└── OutputDisplayCore.vue (复用核心组件)
|
||||
```
|
||||
|
||||
#### 1.2 状态管理简化
|
||||
- 移除了 V1 中的复杂状态:`isHovering`, `isEditing`, `manualToggleActive` 等
|
||||
- 引入核心状态:`internalViewMode` 驱动视图切换
|
||||
- 实现智能自动切换机制
|
||||
|
||||
### 2. 依赖注入架构
|
||||
|
||||
V2 版本采用了更纯粹的依赖注入模式:
|
||||
|
||||
#### 2.1 设计原则
|
||||
- **OutputDisplayCore**: 作为纯展示组件,所有依赖通过 props 注入
|
||||
- **父组件责任**: 负责创建和提供服务实例
|
||||
- **fail-fast 原则**: 依赖缺失时立即抛出错误
|
||||
|
||||
#### 2.2 服务依赖
|
||||
```typescript
|
||||
interface OutputDisplayCoreProps {
|
||||
// ... 其他 props
|
||||
compareService: ICompareService // 必需的服务依赖
|
||||
}
|
||||
```
|
||||
|
||||
## 关键问题修复:CompareService 依赖注入
|
||||
|
||||
### 问题分析
|
||||
|
||||
在 V2 重构过程中,发现了一个关键的依赖注入不完整问题:
|
||||
|
||||
**根本原因**:依赖注入不完整。
|
||||
- ✅ **已完成**:子组件 `OutputDisplayCore.vue` 被正确修改,期望从 props 接收 `compareService`
|
||||
- ❌ **被遗漏**:父组件 `OutputDisplay.vue` 和 `OutputDisplayFullscreen.vue` 没有进行配套修改
|
||||
|
||||
**错误表现**:
|
||||
```
|
||||
OutputDisplayCore.vue:317 Uncaught (in promise) Error: CompareService is required but not provided
|
||||
```
|
||||
|
||||
### 修复方案
|
||||
|
||||
采用分层修复策略,确保依赖注入链条完整:
|
||||
|
||||
#### 第一步:完善服务架构
|
||||
|
||||
1. **AppServices 接口扩展**
|
||||
```typescript
|
||||
// packages/ui/src/types/services.ts
|
||||
export interface AppServices {
|
||||
// ... 现有服务
|
||||
compareService: ICompareService; // 新增
|
||||
}
|
||||
```
|
||||
|
||||
2. **服务初始化**
|
||||
```typescript
|
||||
// packages/ui/src/composables/useAppInitializer.ts
|
||||
// Web 和 Electron 环境都创建 CompareService 实例
|
||||
const compareService = createCompareService();
|
||||
```
|
||||
|
||||
3. **导出配置**
|
||||
```typescript
|
||||
// packages/ui/src/index.ts
|
||||
export { createCompareService } from '@prompt-optimizer/core'
|
||||
export type { ICompareService } from '@prompt-optimizer/core'
|
||||
```
|
||||
|
||||
#### 第二步:修复父组件
|
||||
|
||||
1. **OutputDisplay.vue 修复**
|
||||
```vue
|
||||
<template>
|
||||
<OutputDisplayCore
|
||||
:compareService="compareService"
|
||||
<!-- 其他 props -->
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// 注入服务
|
||||
const services = inject<Ref<AppServices | null>>('services');
|
||||
const compareService = computed(() => {
|
||||
// fail-fast 错误检查
|
||||
if (!services?.value?.compareService) {
|
||||
throw new Error('CompareService未初始化');
|
||||
}
|
||||
return services.value.compareService;
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
2. **OutputDisplayFullscreen.vue 修复**
|
||||
```vue
|
||||
<template>
|
||||
<OutputDisplayCore
|
||||
:compareService="compareService"
|
||||
<!-- 其他 props -->
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// 相同的注入和错误检查逻辑
|
||||
</script>
|
||||
```
|
||||
|
||||
### 技术决策说明
|
||||
|
||||
#### 为什么不需要 IPC Proxy?
|
||||
|
||||
**CompareService 特性分析**:
|
||||
- ✅ **无状态**:纯函数式服务,不维护内部状态
|
||||
- ✅ **纯计算**:只做文本对比,使用 jsdiff 库
|
||||
- ✅ **无主进程依赖**:不需要访问文件系统等主进程资源
|
||||
|
||||
**结论**:CompareService 可以直接在渲染进程中运行,无需 IPC 代理。
|
||||
|
||||
#### 架构一致性
|
||||
|
||||
修复方案遵循了现有架构模式:
|
||||
- 使用 `inject` 获取服务(与其他组件一致)
|
||||
- 保持 fail-fast 原则(符合用户偏好)
|
||||
- 最小化修改范围(聚焦问题核心)
|
||||
|
||||
## 验证测试
|
||||
|
||||
### 自动化测试
|
||||
- ✅ 所有 35 个测试用例通过
|
||||
- ✅ 组件渲染正常
|
||||
- ✅ 状态管理逻辑正确
|
||||
|
||||
### 手动验证测试
|
||||
|
||||
#### 测试环境
|
||||
- 浏览器:Chrome 138.0.0.0
|
||||
- 开发服务器:http://localhost:18181
|
||||
- 测试时间:2025-01-06
|
||||
|
||||
#### 测试步骤
|
||||
|
||||
1. **应用启动验证**
|
||||
```
|
||||
操作:访问 http://localhost:18181
|
||||
预期:应用正常加载,无控制台错误
|
||||
结果:✅ 通过
|
||||
```
|
||||
|
||||
2. **基础功能测试**
|
||||
```
|
||||
操作:输入原始提示词 "请帮我写一个简单的Python函数"
|
||||
预期:输入框正常响应,对比按钮出现
|
||||
结果:✅ 通过 - 对比按钮 (ref=e176) 正常显示
|
||||
```
|
||||
|
||||
3. **优化功能测试**
|
||||
```
|
||||
操作:点击 "开始优化 →" 按钮
|
||||
预期:优化过程正常,生成详细的提示词
|
||||
结果:✅ 通过 - 生成了完整的 Python 代码生成助手提示词
|
||||
```
|
||||
|
||||
4. **对比功能核心测试**
|
||||
```
|
||||
操作:点击 "对比" 按钮
|
||||
预期:
|
||||
- 切换到对比视图
|
||||
- 显示文本差异高亮
|
||||
- 对比按钮变为禁用状态
|
||||
- 无控制台错误
|
||||
|
||||
结果:✅ 完全通过
|
||||
- 对比视图正常激活
|
||||
- 差异高亮正确显示:
|
||||
* 红色删除:原始文本片段
|
||||
* 绿色添加:优化后的详细内容
|
||||
- 按钮状态正确(disabled)
|
||||
- 控制台无任何错误
|
||||
```
|
||||
|
||||
#### 验证结果截图描述
|
||||
|
||||
对比功能激活后的界面状态:
|
||||
```
|
||||
+----------------------------------------------------------------------+
|
||||
| [渲染] [原文] [对比*] [复制] [全屏] |
|
||||
+----------------------------------------------------------------------+
|
||||
| 请帮我 | # Role: Python代码生成助手 ## Profile - language: 中文... |
|
||||
| 写 | ...详细的角色定义、技能描述、规则和工作流程... |
|
||||
| 一 | ... |
|
||||
| 个简单的Python函数 | ... |
|
||||
+----------------------------------------------------------------------+
|
||||
|
||||
* 对比按钮处于禁用状态,表示当前处于对比模式
|
||||
红色部分:原始文本中被删除的内容
|
||||
绿色部分:优化后新增的详细内容
|
||||
```
|
||||
|
||||
### 控制台日志验证
|
||||
|
||||
关键日志记录:
|
||||
```
|
||||
[LOG] [AppInitializer] 所有服务初始化完成
|
||||
[LOG] All services and composables initialized.
|
||||
[LOG] 流式响应完成
|
||||
```
|
||||
|
||||
**无错误日志**:整个测试过程中没有出现任何 JavaScript 错误或警告。
|
||||
|
||||
## 性能影响
|
||||
|
||||
### CompareService 性能特性
|
||||
- **轻量级**:纯 JavaScript 计算,无网络请求
|
||||
- **高效**:使用成熟的 jsdiff 库,算法优化良好
|
||||
- **无副作用**:不影响其他服务的性能
|
||||
|
||||
### 内存使用
|
||||
- **无状态设计**:不持久化任何数据
|
||||
- **按需计算**:仅在对比模式下才执行计算
|
||||
- **自动回收**:计算结果随组件生命周期自动释放
|
||||
|
||||
## 后续优化建议
|
||||
|
||||
1. **缓存机制**:对于相同的文本对比可以考虑添加缓存
|
||||
2. **大文本优化**:对于超大文本可以考虑分块处理
|
||||
3. **可配置性**:允许用户配置对比粒度(字符级/单词级)
|
||||
|
||||
## 总结
|
||||
|
||||
本次修复成功解决了 OutputDisplay V2 重构中的依赖注入不完整问题:
|
||||
|
||||
### 成果
|
||||
- ✅ **问题根因明确**:准确定位到父组件配套修改缺失
|
||||
- ✅ **修复方案完整**:从服务架构到组件层的完整修复链条
|
||||
- ✅ **验证测试充分**:自动化测试 + 手动验证全面覆盖
|
||||
- ✅ **架构一致性**:修复方案符合现有架构模式
|
||||
|
||||
### 关键经验
|
||||
1. **重构完整性**:组件重构时必须确保依赖链条的完整性
|
||||
2. **fail-fast 原则**:依赖缺失时立即报错,便于快速定位问题
|
||||
3. **服务特性分析**:根据服务特性决定是否需要 IPC 代理
|
||||
4. **验证测试重要性**:手动验证能发现自动化测试遗漏的问题
|
||||
|
||||
OutputDisplay V2 现已完全就绪,对比功能正常工作,为用户提供了优秀的文本差异查看体验。
|
||||
@@ -12,6 +12,7 @@
|
||||
:placeholder="placeholder"
|
||||
:loading="loading"
|
||||
:streaming="streaming"
|
||||
:compareService="compareService"
|
||||
@update:content="emit('update:content', $event)"
|
||||
@update:reasoning="emit('update:reasoning', $event)"
|
||||
@copy="handleCopy"
|
||||
@@ -40,9 +41,11 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
import { computed, ref, inject } from 'vue';
|
||||
import OutputDisplayCore from './OutputDisplayCore.vue';
|
||||
import OutputDisplayFullscreen from './OutputDisplayFullscreen.vue';
|
||||
import type { AppServices } from '../types/services';
|
||||
import type { Ref } from 'vue';
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false,
|
||||
@@ -90,6 +93,26 @@ const emit = defineEmits<{
|
||||
|
||||
const isShowingFullscreen = ref(false);
|
||||
|
||||
// 注入服务并获取 CompareService
|
||||
const services = inject<Ref<AppServices | null>>('services');
|
||||
if (!services) {
|
||||
throw new Error('[OutputDisplay] services未正确注入,请确保在App组件中正确provide了services');
|
||||
}
|
||||
|
||||
const compareService = computed(() => {
|
||||
const servicesValue = services.value;
|
||||
if (!servicesValue) {
|
||||
throw new Error('[OutputDisplay] services未初始化,请确保应用已正确启动');
|
||||
}
|
||||
|
||||
const service = servicesValue.compareService;
|
||||
if (!service) {
|
||||
throw new Error('[OutputDisplay] compareService未初始化,请确保服务已正确配置');
|
||||
}
|
||||
|
||||
return service;
|
||||
});
|
||||
|
||||
const enabledActions = computed(() => {
|
||||
const actions: ActionName[] = ['reasoning'];
|
||||
if (props.enableFullscreen) actions.push('fullscreen');
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
:placeholder="placeholder"
|
||||
:loading="loading"
|
||||
:streaming="streaming"
|
||||
:compareService="compareService"
|
||||
@update:content="handleContentUpdate"
|
||||
@copy="handleCopy"
|
||||
/>
|
||||
@@ -19,10 +20,12 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch, nextTick } from 'vue'
|
||||
import { computed, ref, watch, nextTick, inject } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import FullscreenDialog from './FullscreenDialog.vue'
|
||||
import OutputDisplayCore from './OutputDisplayCore.vue'
|
||||
import type { AppServices } from '../types/services';
|
||||
import type { Ref } from 'vue';
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
@@ -59,6 +62,26 @@ const emit = defineEmits<{
|
||||
}>()
|
||||
|
||||
|
||||
// 注入服务并获取 CompareService
|
||||
const services = inject<Ref<AppServices | null>>('services');
|
||||
if (!services) {
|
||||
throw new Error('[OutputDisplayFullscreen] services未正确注入,请确保在App组件中正确provide了services');
|
||||
}
|
||||
|
||||
const compareService = computed(() => {
|
||||
const servicesValue = services.value;
|
||||
if (!servicesValue) {
|
||||
throw new Error('[OutputDisplayFullscreen] services未初始化,请确保应用已正确启动');
|
||||
}
|
||||
|
||||
const service = servicesValue.compareService;
|
||||
if (!service) {
|
||||
throw new Error('[OutputDisplayFullscreen] compareService未初始化,请确保服务已正确配置');
|
||||
}
|
||||
|
||||
return service;
|
||||
});
|
||||
|
||||
// Internal state
|
||||
const coreDisplayRef = ref<InstanceType<typeof OutputDisplayCore> | null>(null)
|
||||
const internalVisible = computed({
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
createLLMService,
|
||||
createPromptService,
|
||||
createTemplateLanguageService,
|
||||
createCompareService,
|
||||
ElectronModelManagerProxy,
|
||||
ElectronTemplateManagerProxy,
|
||||
ElectronHistoryManagerProxy,
|
||||
@@ -76,6 +77,9 @@ export function useAppInitializer() {
|
||||
// 使用真正的 Electron 模板语言服务代理
|
||||
const templateLanguageService = new ElectronTemplateLanguageServiceProxy();
|
||||
|
||||
// 创建 CompareService(直接使用,无需代理)
|
||||
const compareService = createCompareService();
|
||||
|
||||
services.value = {
|
||||
modelManager,
|
||||
templateManager,
|
||||
@@ -85,6 +89,7 @@ export function useAppInitializer() {
|
||||
promptService,
|
||||
templateLanguageService, // 使用代理而不是null
|
||||
preferenceService, // 使用从core包导入的ElectronPreferenceServiceProxy
|
||||
compareService, // 直接使用,无需代理
|
||||
};
|
||||
console.log('[AppInitializer] Electron代理服务初始化完成');
|
||||
|
||||
@@ -178,6 +183,9 @@ export function useAppInitializer() {
|
||||
// 创建基于存储提供器的偏好设置服务,使用core包中的createPreferenceService
|
||||
preferenceService = createPreferenceService(storageProvider);
|
||||
|
||||
// 创建 CompareService(直接使用)
|
||||
const compareService = createCompareService();
|
||||
|
||||
// 将所有服务实例赋值给 services.value
|
||||
services.value = {
|
||||
modelManager: modelManagerAdapter, // 使用适配器
|
||||
@@ -188,6 +196,7 @@ export function useAppInitializer() {
|
||||
promptService,
|
||||
templateLanguageService: languageService,
|
||||
preferenceService, // 使用从core包导入的PreferenceService
|
||||
compareService, // 直接使用
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -66,12 +66,13 @@ export {
|
||||
ElectronPromptServiceProxy,
|
||||
createPreferenceService,
|
||||
ElectronPreferenceServiceProxy,
|
||||
createCompareService,
|
||||
isRunningInElectron,
|
||||
waitForElectronApi,
|
||||
} from '@prompt-optimizer/core'
|
||||
|
||||
// 导出类型
|
||||
export type {
|
||||
export type {
|
||||
OptimizationMode,
|
||||
IModelManager,
|
||||
ITemplateManager,
|
||||
@@ -79,5 +80,6 @@ export type {
|
||||
ILLMService,
|
||||
IPromptService,
|
||||
IPreferenceService,
|
||||
ICompareService,
|
||||
Template
|
||||
} from '@prompt-optimizer/core'
|
||||
@@ -5,7 +5,8 @@ import type {
|
||||
IDataManager,
|
||||
ILLMService,
|
||||
IPromptService,
|
||||
ITemplateLanguageService
|
||||
ITemplateLanguageService,
|
||||
ICompareService
|
||||
} from '@prompt-optimizer/core'
|
||||
|
||||
// FIXME: Temporary workaround for build issue. Should be imported from @prompt-optimizer/core
|
||||
@@ -29,4 +30,5 @@ export interface AppServices {
|
||||
promptService: IPromptService;
|
||||
templateLanguageService: ITemplateLanguageService;
|
||||
preferenceService: IPreferenceService;
|
||||
compareService: ICompareService;
|
||||
}
|
||||
Reference in new issue
Block a user