docs/archives/129-session-store-single-source-refactor/architecture-comparison.md
日期: 2025-01-08
分支: hapi-var-extract
目标: 对齐 Basic、Context、Image 三种模式的开发体验
架构图:
┌─────────────────────────────────────────────────────────────┐
│ BasicSystemWorkspace.vue │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ useBasicWorkspaceLogic.ts │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 1. 状态代理(Session Store 的包装) │ │
│ │ const testResults = computed({ │ │
│ │ get: () => sessionStore.testResults, │ │
│ │ set: (value) => sessionStore.updateTestResults(value)│
│ │ }) │ │
│ ├─────────────────────────────────────────────────────┤ │
│ │ 2. 过程态管理 │ │
│ │ const isOptimizing = ref(false) │ │
│ │ const isTestingOriginal = ref(false) │ │
│ ├─────────────────────────────────────────────────────┤ │
│ │ 3. 业务逻辑 │ │
│ │ const handleOptimize = async () => {...} │ │
│ │ const handleTest = async () => {...} │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ return { │
│ testResults, // ComputedRef<TestResults | null> │
│ isOptimizing, // Ref<boolean> │
│ handleOptimize // Function │
│ } │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ useBasicSystemSession.ts │
│ (Pinia Store - 持久化状态) │
└─────────────────────────────────────────────────────────────┘
组件中使用:
<script setup>
const logic = useBasicWorkspaceLogic({
sessionStore,
services,
optimizationMode: 'system'
})
// ❌ 问题:必须使用 .value 访问
const hasOriginalResult = computed(() =>
!!logic.testResults.value?.originalResult
)
// ❌ 需要手动解包传递给子组件
const unwrappedLogicProps = computed(() => ({
testResultsOriginalResult: logic.testResults.value?.originalResult || '',
isOptimizing: logic.isOptimizing.value
}))
</script>
<template>
<TestResultPanel
:originalResult="unwrappedLogicProps.testResultsOriginalResult"
/>
</template>
特点:
.value 解包对象属性.value 缺失架构图:
┌─────────────────────────────────────────────────────────────┐
│ ContextSystemWorkspace.vue │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ useConversationTester.ts │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ const state = reactive({ │ │
│ │ testResults: { │ │
│ │ originalResult: '', │ │
│ │ optimizedResult: '', │ │
│ │ isTestingOriginal: false, │ │
│ │ isTestingOptimized: false, │ │
│ │ }, │ │
│ │ executeTest: async (isCompareMode) => {...} │ │
│ │ }) │ │
│ │ │ │
│ │ return state // reactive 对象 │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ useProMultiSession.ts │
│ (Pinia Store - 持久化状态) │
│ │
│ ⚠️ 需要 watch 同步 Tester → Session Store │
│ watch( │
│ () => conversationTester.testResults, │
│ (stable) => { │
│ session.updateTestResults(stable) │
│ } │
│ ) │
└─────────────────────────────────────────────────────────────┘
组件中使用:
<script setup>
const conversationTester = useConversationTester(
services,
modelSelection.selectedTestModelKey,
optimizationContext,
optimizationContextToolsRef,
variableManager
)
// ✅ 无需 .value,reactive 自动解包
const hasOriginalResult = computed(() =>
!!conversationTester.testResults.originalResult
)
// ✅ 直接传递给子组件
</script>
<template>
<TestResultPanel
:originalResult="conversationTester.testResults.originalResult"
:isTesting="conversationTester.testResults.isTestingOriginal"
/>
</template>
特点:
.value,reactive 自动解包架构图:
┌─────────────────────────────────────────────────────────────┐
│ ImageText2ImageWorkspace.vue │
└─────────────────────────────────────────────────────────────┘
│
├────────────────────────────────┐
▼ ▼
┌──────────────────────────┐ ┌──────────────────────────────┐
│ useImageText2ImageSession│ │ useImageGeneration │
│ (Pinia Store) │ │ (Composable) │
│ │ │ │
│ - originalPrompt │ │ - imageModels: Ref(...) │
│ - optimizedPrompt │ │ - generating: Ref(...) │
│ - selectedModelKey │ │ - generate: Function │
│ - testResults │ │ │
└──────────────────────────┘ └──────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 组件内定义 computed 双向绑定 │
│ │
│ const originalPrompt = computed<string>({ │
│ get: () => session.originalPrompt || '', │
│ set: (value) => session.updatePrompt(value || '') │
│ }) │
│ │
│ const optimizedPrompt = computed<string>({ │
│ get: () => session.optimizedPrompt || '', │
│ set: (value) => { │
│ session.updateOptimizedResult({...}) │
│ } │
│ }) │
└─────────────────────────────────────────────────────────────┘
组件中使用:
<script setup>
const session = useImageText2ImageSession()
// ❌ 组件内定义大量双向 computed
const originalPrompt = computed<string>({
get: () => session.originalPrompt || '',
set: (value) => session.updatePrompt(value || '')
})
const optimizedPrompt = computed<string>({
get: () => session.optimizedPrompt || '',
set: (value) => {
session.updateOptimizedResult({
optimizedPrompt: value || '',
reasoning: session.reasoning || '',
chainId: session.chainId || '',
versionId: session.versionId || ''
})
}
})
const {
imageModels,
generating: isGenerating,
result: imageResult,
generate: generateImage
} = useImageGeneration()
</script>
<template>
<PromptPanel
v-model="originalPrompt"
v-model:optimized="optimizedPrompt"
/>
</template>
特点:
| 方面 | Basic 模式 | Context 模式 | Image 模式 |
|---|---|---|---|
| 持久化状态 | Session Store | Session Store | Session Store |
| 过程态 | Logic 层 ref | Tester reactive | 组件内 ref |
| 派生状态 | 组件内 computed | 组件内 computed | 组件内 computed |
| 状态同步 | Logic 代理 Store | watch 双向同步 | 直接访问 Store |
问题:
| 方面 | Basic 模式 | Context 模式 | Image 模式 |
|---|---|---|---|
| 读取数据 | logic.testResults.value | tester.testResults | session.xxx |
| 更新数据 | logic.testResults.value = ... | tester.testResults.xxx = ... | session.updateXxx() |
| 数据流向 | 双向 computed | 双向(Tester ↔️ Store) | 双向 computed |
问题:
| 方面 | Basic 模式 | Context 模式 | Image 模式 |
|---|---|---|---|
| 是否需要 .value | 是(对象属性) | 否(reactive) | 否(顶层 ref) |
| 代码简洁度 | 中 | 高 | 低(大量 computed) |
| 类型安全 | ⚠️ 运行时错误 | ✅ 编译时检查 | ✅ 编译时检查 |
| 样板代码 | 中(解包逻辑) | 少 | 多(双向 computed) |
| 可测试性 | 中 | 高 | 低(依赖组件) |
结论: Context 模式的开发体验最好
| 方面 | Basic 模式 | Context 模式 | Image 模式 |
|---|---|---|---|
| 中间层 | Logic 层 | Tester 层 | 无 |
| 状态包装 | ComputedRef | Reactive | 直接 Ref |
| 代码复用 | ✅ 高(System/User 共享) | ✅ 高(System/User 共享) | ❌ 低(各自独立) |
| 学习曲线 | 陡峭(理解 Logic 层) | 平坦 | 平坦 |
问题:
核心思路: 所有模式都使用 reactive 对象,不使用 computed 双向绑定
架构设计:
// ✅ 统一的 Workspace Composable
export function useWorkspace(options: {
mode: 'basic-system' | 'basic-user' | 'context-system' | 'context-user' | 'image-text2image'
}) {
const sessionStore = useSessionStore(options.mode)
const toast = useToast()
const { t } = useI18n()
// ✅ 使用 reactive 管理所有状态(自动解包,无需 .value)
const state = reactive({
// 持久化状态代理
prompt: sessionStore.prompt,
optimizedPrompt: sessionStore.optimizedPrompt,
testResults: sessionStore.testResults,
// 过程态(不持久化)
isOptimizing: false,
isTestingOriginal: false,
isTestingOptimized: false,
// 历史管理(不持久化)
currentVersions: [],
currentChainId: '',
currentVersionId: ''
})
// ✅ 业务逻辑方法
const handleOptimize = async () => {
state.isOptimizing = true
try {
// 业务逻辑...
// ✅ 单向更新:直接修改 state,watch 同步到 store
state.optimizedPrompt = newPrompt
sessionStore.updateOptimizedResult({
optimizedPrompt: newPrompt
})
} finally {
state.isOptimizing = false
}
}
const handleTest = async () => {
// ...
}
// ✅ 自动同步 state → sessionStore
watch(
() => state.optimizedPrompt,
(value) => {
sessionStore.updateOptimizedResult({ optimizedPrompt: value })
}
)
// ✅ 返回 reactive 对象(自动解包,无需 .value)
return state
}
组件中使用:
<script setup>
const workspace = useWorkspace({ mode: 'basic-system' })
// ✅ 无需 .value
const hasOriginalResult = computed(() =>
!!workspace.testResults.originalResult
)
// ✅ 直接调用方法
const handleOptimize = () => workspace.handleOptimize()
</script>
<template>
<TestResultPanel
:originalResult="workspace.testResults.originalResult"
:isTesting="workspace.testResults.isTestingOriginal"
/>
</template>
优点:
.value,开发体验最佳缺点:
核心思路: 保留 Logic 层,但使用 toRefs 自动解包
架构设计:
export function useWorkspaceLogic(options: { mode: string }) {
const sessionStore = useSessionStore(options.mode)
// 过程态
const isOptimizing = ref(false)
const isTestingOriginal = ref(false)
// 状态代理
const testResults = computed({
get: () => sessionStore.testResults,
set: (value) => sessionStore.updateTestResults(value)
})
// 业务逻辑
const handleTest = async () => {
// ...
}
// ✅ 使用 toRefs 自动解包
return {
...toRefs({
testResults,
isOptimizing,
isTestingOriginal
}),
handleTest
}
}
组件中使用:
<script setup>
const workspace = useWorkspaceLogic({ mode: 'basic-system' })
// ✅ 无需 .value
const hasOriginalResult = computed(() =>
!!workspace.testResults?.originalResult
)
</script>
优点:
.value缺点:
核心思路: 所有模式都直接使用 Store,业务逻辑分离到 Operations Composable
架构设计:
// ✅ Store: 只管理状态
const sessionStore = useSessionStore('basic-system')
const { prompt, testResults } = storeToRefs(sessionStore)
// ✅ Operations: 只包含业务逻辑
const { handleOptimize, handleTest } = useWorkspaceOperations({
sessionStore,
services
})
// ✅ 派生状态:组件内定义
const hasOriginalResult = computed(() =>
!!testResults.value?.originalResult
)
组件中使用:
<script setup>
const sessionStore = useSessionStore('basic-system')
const { prompt, testResults, optimizedPrompt } = storeToRefs(sessionStore)
const { handleOptimize, handleTest } = useWorkspaceOperations({
sessionStore,
services
})
// 派生状态
const hasOriginalResult = computed(() =>
!!testResults.value?.originalResult
)
</script>
<template>
<TestResultPanel
:originalResult="testResults.originalResult"
@test="handleTest"
/>
</template>
优点:
缺点:
| 维度 | 方案 A (Reactive) | 方案 B (Logic + toRefs) | 方案 C (Store + Operations) |
|---|---|---|---|
| 改动成本 | 大 | 中 | 大 |
| 开发体验 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| 代码简洁度 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ |
| 类型安全 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 数据流清晰度 | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 符合 Vue 3 规范 | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 维护成本 | 低 | 中 | 低 |
| 推荐指数 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
目标: 统一开发体验,减少类似 bug
实施方案: 方案 B(Logic + toRefs)
理由:
.value 问题实施步骤:
useBasicWorkspaceLogic.ts,使用 toRefs 自动解包.value 访问目标: 符合 Vue 3 最佳实践,提升代码质量
实施方案: 方案 C(Store + Operations)
理由:
实施步骤:
useWorkspaceOperations composable.value 缺失问题useBasicWorkspaceLogic.ts 使用 toRefs.valueuseWorkspaceOperations composable文档维护: 随着重构进展更新此文档