Skip to content

feat(template): 修复在切换模板语言后,模板选择器不响应式更新 #120

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions docs/experience.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,39 @@ for (const [key, value] of Object.entries(importData)) {
- **用户模板**:可修改,导入时生成新ID
- **导入规则**:跳过与内置模板ID重复的模板

### 4. 数组内容深度比较修复(2025-01-27)
**问题**:BugBot 发现模板内容比较使用引用比较而非深度比较
```typescript
// ❌ 错误:数组引用比较
if (updatedTemplate.content !== currentTemplate.content) {
// 数组内容相同但引用不同时会触发不必要更新
}

// ✅ 正确:深度比较函数
const deepCompareTemplateContent = (content1, content2) => {
if (typeof content1 !== typeof content2) return false;

if (typeof content1 === 'string') return content1 === content2;

if (Array.isArray(content1) && Array.isArray(content2)) {
if (content1.length !== content2.length) return false;
return content1.every((item1, index) => {
const item2 = content2[index];
return item1.role === item2.role && item1.content === item2.content;
});
}

return JSON.stringify(content1) === JSON.stringify(content2);
};

// 使用深度比较
if (!deepCompareTemplateContent(updatedTemplate.content, currentTemplate.content)) {
// 只有内容真正改变时才更新
}
```

**关键**:Template 接口的 content 可以是 `string | Array<{role: string; content: string}>`,必须支持两种类型的正确比较。

---

## 📝 文档更新规范
Expand Down
10 changes: 10 additions & 0 deletions packages/extension/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
</template>
<template #template-select>
<TemplateSelectUI
ref="templateSelectRef"
v-model="currentSelectedTemplate"
:type="selectedOptimizationMode === 'system' ? 'optimize' : 'userOptimize'"
:optimization-mode="selectedOptimizationMode"
Expand Down Expand Up @@ -346,8 +347,17 @@ const openTemplateManager = (type: string) => {
showTemplates.value = true
}

// 模板选择器引用
const templateSelectRef = ref()

const handleTemplateManagerClose = () => {
showTemplates.value = false

// 刷新模板选择器以反映语言变更后的模板
// 子组件会通过 v-model 自动更新父组件的状态
if (templateSelectRef.value?.refresh) {
templateSelectRef.value.refresh()
}
}

// 数据管理器
Expand Down
95 changes: 81 additions & 14 deletions packages/ui/src/components/TemplateSelect.vue
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ const { t } = useI18n()
interface Template {
id: string;
name: string;
content: string | Array<{role: string; content: string}>;
isBuiltin?: boolean;
metadata: {
description?: string;
Expand Down Expand Up @@ -189,7 +190,7 @@ watch(
// 添加对模板列表变化的监听
watch(
templates, // 监听模板列表
(newTemplates, oldTemplates) => {
(newTemplates) => {
const currentTemplate = props.modelValue
// 只有在模板列表真正发生变化,且当前模板不在新列表中时才自动切换
if (currentTemplate && !newTemplates.find(t => t.id === currentTemplate.id)) {
Expand All @@ -205,32 +206,98 @@ watch(
{ deep: true }
)

// 改进刷新方法
/**
* 深度比较模板内容
* 支持 string 和 Array<{role: string; content: string}> 两种类型
* 修复 BugBot 发现的数组引用比较问题
*/
const deepCompareTemplateContent = (content1: any, content2: any): boolean => {
// 类型相同性检查
if (typeof content1 !== typeof content2) {
return false
}

// 字符串类型直接比较
if (typeof content1 === 'string') {
return content1 === content2
}

// 数组类型深度比较
if (Array.isArray(content1) && Array.isArray(content2)) {
if (content1.length !== content2.length) {
return false
}

return content1.every((item1, index) => {
const item2 = content2[index]
return item1.role === item2.role && item1.content === item2.content
})
}

// 其他情况使用 JSON 序列化比较(兜底方案)
return JSON.stringify(content1) === JSON.stringify(content2)
}

/**
* 刷新模板列表和当前选中的模板
* 职责:
* 1. 刷新模板列表显示
* 2. 检查当前选中模板是否需要更新(如语言切换)
* 3. 处理模板不存在的情况(自动选择默认模板)
*/
const refreshTemplates = () => {
const oldTrigger = refreshTrigger.value
refreshTrigger.value++

// 检查模板管理器是否已初始化
if (!templateManager.isInitialized()) {
return
}

// 只在初始化时检查和自动选择模板,避免重复触发

const currentTemplates = templateManager.listTemplatesByType(props.type)
const currentTemplate = props.modelValue

// 仅在当前没有选中模板或选中的模板不在列表中时才自动选择

// 处理当前选中模板的更新(主要用于语言切换场景)
if (currentTemplate) {
try {
const updatedTemplate = templateManager.getTemplate(currentTemplate.id)
// 使用深度比较检查模板内容是否发生变化(修复 BugBot 发现的数组比较问题)
if (updatedTemplate && (
updatedTemplate.name !== currentTemplate.name ||
!deepCompareTemplateContent(updatedTemplate.content, currentTemplate.content)
)) {
// 通过 v-model 更新父组件状态
emit('update:modelValue', updatedTemplate)
// 静默更新,不显示用户提示
emit('select', updatedTemplate, false)
return
}
} catch (error) {
console.warn('[TemplateSelect] Failed to get updated template:', error)
}
}

// 处理模板不存在的情况:当前模板已被删除或不在当前类型列表中
if (!currentTemplate || !currentTemplates.find(t => t.id === currentTemplate.id)) {
const firstTemplate = currentTemplates[0] || null
if (firstTemplate && firstTemplate.id !== currentTemplate?.id) {
emit('update:modelValue', firstTemplate)
// 静默选择,不显示toast(通过传递false参数)
emit('select', firstTemplate, false)
const defaultTemplate = currentTemplates[0] || null
if (defaultTemplate && defaultTemplate.id !== currentTemplate?.id) {
emit('update:modelValue', defaultTemplate)
// 静默选择,不显示用户提示
emit('select', defaultTemplate, false)
}
}
}

// 暴露刷新方法给父组件
/**
* 暴露给父组件的接口
*
* refresh(): 当外部状态变化(如语言切换、模板管理操作)时,
* 父组件可以调用此方法通知子组件刷新数据。
* 子组件负责检查数据变化并通过 v-model 更新父组件状态。
*
* 职责分工:
* - 父组件:检测需要刷新的时机,调用 refresh()
* - 子组件:执行具体的刷新逻辑,管理自身状态,通过事件通知父组件
*/
defineExpose({
refresh: refreshTemplates
})
Expand Down
10 changes: 10 additions & 0 deletions packages/web/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
</template>
<template #template-select>
<TemplateSelectUI
ref="templateSelectRef"
v-model="currentSelectedTemplate"
:type="selectedOptimizationMode === 'system' ? 'optimize' : 'userOptimize'"
:optimization-mode="selectedOptimizationMode"
Expand Down Expand Up @@ -346,8 +347,17 @@ const openTemplateManager = (type: string) => {
showTemplates.value = true
}

// 模板选择器引用
const templateSelectRef = ref()

const handleTemplateManagerClose = () => {
showTemplates.value = false

// 刷新模板选择器以反映语言变更后的模板
// 子组件会通过 v-model 自动更新父组件的状态
if (templateSelectRef.value?.refresh) {
templateSelectRef.value.refresh()
}
}

// 数据管理器
Expand Down