Skip to content

Commit 285b2c5

Browse files
committed
refactor(ui): 重构组件架构并新增上下文模板类型支持
- feat(TemplateManager): 新增3个上下文相关模板类型 - contextSystemOptimize: 上下文系统优化模板 - contextUserOptimize: 上下文用户优化模板 - contextIterate: 上下文迭代模板 - 完善类型映射和选择逻辑 - refactor(App): 组件统一化重构 - 将ModelSelectUI和TemplateSelectUI替换为SelectWithConfig - 实现统一的选择器组件,提高代码复用性 - 新增响应式数据管理和状态同步机制 - 优化模板和模型选择的刷新逻辑 - 改进v-model绑定方式,符合Vue 3最佳实践 - improve: 状态管理优化 - 新增templateOptions和textModelOptions响应式数据 - 实现自动状态同步和选择验证 - 优化错误处理和边界情况处理
1 parent 0d2322b commit 285b2c5

File tree

2 files changed

+226
-45
lines changed

2 files changed

+226
-45
lines changed

packages/ui/src/components/TemplateManager.vue

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -593,7 +593,16 @@ const props = defineProps<{
593593
selectedSystemOptimizeTemplate?: Template,
594594
selectedUserOptimizeTemplate?: Template,
595595
selectedIterateTemplate?: Template,
596-
templateType: 'optimize' | 'userOptimize' | 'iterate' | 'text2imageOptimize' | 'image2imageOptimize' | 'imageIterate',
596+
templateType:
597+
| 'optimize'
598+
| 'userOptimize'
599+
| 'iterate'
600+
| 'text2imageOptimize'
601+
| 'image2imageOptimize'
602+
| 'imageIterate'
603+
| 'contextSystemOptimize'
604+
| 'contextUserOptimize'
605+
| 'contextIterate',
597606
show: boolean
598607
}>()
599608
@@ -638,10 +647,13 @@ const migrationDialog = ref<{
638647
const selectedTemplate = computed(() => {
639648
switch (props.templateType) {
640649
case 'optimize':
650+
case 'contextSystemOptimize':
641651
return props.selectedSystemOptimizeTemplate
642652
case 'userOptimize':
653+
case 'contextUserOptimize':
643654
return props.selectedUserOptimizeTemplate
644655
case 'iterate':
656+
case 'contextIterate':
645657
return props.selectedIterateTemplate
646658
default:
647659
return null
@@ -663,6 +675,12 @@ function getCategoryFromProps() {
663675
return 'image-image2image-optimize'
664676
case 'imageIterate':
665677
return 'image-iterate'
678+
case 'contextSystemOptimize':
679+
return 'context-system-optimize'
680+
case 'contextUserOptimize':
681+
return 'context-user-optimize'
682+
case 'contextIterate':
683+
return 'context-iterate'
666684
default:
667685
return 'system-optimize'
668686
}

packages/web/src/App.vue

Lines changed: 207 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@
1717
<!-- Core Navigation Slot -->
1818
<template #core-nav>
1919
<FunctionModeSelector
20-
v-model="functionMode"
21-
@change="handleModeSelect"
20+
:modelValue="functionMode"
21+
@update:modelValue="handleModeSelect"
2222
/>
2323
</template>
2424

@@ -125,23 +125,44 @@
125125
/>
126126
</template>
127127
<template #model-select>
128-
<ModelSelectUI
129-
:ref="(el) => modelSelectRefs.optimizeModelSelect = el"
130-
:modelValue="modelManager.selectedOptimizeModel"
131-
@update:modelValue="modelManager.selectedOptimizeModel = $event"
128+
<SelectWithConfig
129+
v-model="modelManager.selectedOptimizeModel"
130+
:options="textModelOptions"
131+
:getPrimary="(opt: any) => typeof opt.label === 'string' ? opt.label.replace(/\s*\(.*\)\s*$/, '') : ''"
132+
:getSecondary="(opt: any) => {
133+
const s = typeof opt.label === 'string' ? opt.label : ''
134+
const m = s.match(/\((.*)\)\s*$/)
135+
return m ? m[1] : ''
136+
}"
137+
:getValue="(opt: any) => opt.value"
138+
:placeholder="t('model.select.placeholder')"
139+
size="medium"
132140
:disabled="optimizer.isOptimizing"
141+
filterable
142+
:show-config-action="true"
143+
:show-empty-config-c-t-a="true"
144+
@focus="refreshTextModels"
133145
@config="modelManager.showConfig = true"
134146
/>
135147
</template>
136148
<template #template-select>
137-
<TemplateSelectUI
138-
v-if="services && services.templateManager"
139-
ref="templateSelectRef"
140-
v-model="currentSelectedTemplate"
141-
:type="templateSelectType"
142-
:optimization-mode="selectedOptimizationMode"
143-
@manage="openTemplateManager"
144-
/>
149+
<template v-if="services && services.templateManager">
150+
<SelectWithConfig
151+
v-model="selectedTemplateIdForSelect"
152+
:options="templateOptions as any"
153+
:getPrimary="(tpl: any) => tpl?.name || ''"
154+
:getSecondary="(tpl: any) => tpl?.metadata?.description || ''"
155+
:getValue="(tpl: any) => tpl?.id"
156+
:placeholder="t('template.select')"
157+
size="medium"
158+
:disabled="optimizer.isOptimizing"
159+
filterable
160+
:show-config-action="true"
161+
:show-empty-config-c-t-a="true"
162+
@focus="refreshOptimizeTemplates"
163+
@config="handleOpenOptimizeTemplateManager"
164+
/>
165+
</template>
145166
<NText v-else depth="3" class="p-2 text-sm">
146167
{{ t('template.loading') || '加载中...' }}
147168
</NText>
@@ -214,11 +235,22 @@
214235
>
215236
<!-- 模型选择插槽 -->
216237
<template #model-select>
217-
<ModelSelectUI
218-
:ref="(el) => modelSelectRefs.testModelSelect = el"
219-
:modelValue="modelManager.selectedTestModel"
220-
@update:modelValue="modelManager.selectedTestModel = $event"
221-
:disabled="false"
238+
<SelectWithConfig
239+
v-model="modelManager.selectedTestModel"
240+
:options="textModelOptions"
241+
:getPrimary="(opt: any) => typeof opt.label === 'string' ? opt.label.replace(/\s*\(.*\)\s*$/, '') : ''"
242+
:getSecondary="(opt: any) => {
243+
const s = typeof opt.label === 'string' ? opt.label : ''
244+
const m = s.match(/\((.*)\)\s*$/)
245+
return m ? m[1] : ''
246+
}"
247+
:getValue="(opt: any) => opt.value"
248+
:placeholder="t('model.select.placeholder')"
249+
size="medium"
250+
filterable
251+
:show-config-action="true"
252+
:show-empty-config-c-t-a="true"
253+
@focus="refreshTextModels"
222254
@config="modelManager.showConfig = true"
223255
/>
224256
</template>
@@ -335,7 +367,7 @@ hljs.registerLanguage('json', jsonLang)
335367
// UI Components
336368
MainLayoutUI, ThemeToggleUI, ActionButtonUI, ModelManagerUI, TemplateManagerUI, HistoryDrawerUI,
337369
LanguageSwitchDropdown, DataManagerUI, InputPanelUI, PromptPanelUI, OptimizationModeSelectorUI,
338-
ModelSelectUI, TemplateSelectUI, TestAreaPanel, UpdaterIcon, VariableManagerModal,
370+
SelectWithConfig, TestAreaPanel, UpdaterIcon, VariableManagerModal,
339371
ImageWorkspace, FunctionModeSelector,
340372
ConversationManager, OutputDisplay, ContextEditor,
341373
@@ -365,7 +397,7 @@ hljs.registerLanguage('json', jsonLang)
365397
// Quick Template Manager
366398
quickTemplateManager,
367399
} from '@prompt-optimizer/ui'
368-
import type { IPromptService } from '@prompt-optimizer/core'
400+
import type { IPromptService, Template, ModelConfig } from '@prompt-optimizer/core'
369401
370402
// 1. 基础 composables
371403
// highlight.js for Naive NCode
@@ -401,7 +433,6 @@ hljs.registerLanguage('json', jsonLang)
401433
const showDataManager = ref(false)
402434
const optimizeModelSelect = ref(null)
403435
const testPanelRef = ref(null)
404-
const templateSelectRef = ref<{ refresh?: () => void } | null>(null)
405436
const promptPanelRef = ref<{ refreshIterateTemplateSelect?: () => void } | null>(null)
406437
407438
// 高级模式状态
@@ -710,7 +741,156 @@ hljs.registerLanguage('json', jsonLang)
710741
selectedIterateTemplate: toRef(optimizer, 'selectedIterateTemplate')
711742
}
712743
)
713-
744+
745+
const currentSelectedTemplate = computed({
746+
get() {
747+
return selectedOptimizationMode.value === 'system'
748+
? optimizer.selectedOptimizeTemplate
749+
: optimizer.selectedUserOptimizeTemplate
750+
},
751+
set(newValue) {
752+
if (!newValue) return
753+
if (selectedOptimizationMode.value === 'system') {
754+
optimizer.selectedOptimizeTemplate = newValue
755+
} else {
756+
optimizer.selectedUserOptimizeTemplate = newValue
757+
}
758+
}
759+
})
760+
761+
const templateOptions = ref<Template[]>([])
762+
const textModelOptions = ref<Array<{ label: string; value: string; raw: ModelConfig & { key: string } }>>([])
763+
764+
const handleOpenOptimizeTemplateManager = () => {
765+
const type = templateSelectType.value
766+
console.log('[App] Opening template manager for type:', type)
767+
openTemplateManager(type as any)
768+
}
769+
770+
const clearCurrentTemplateSelection = () => {
771+
if (selectedOptimizationMode.value === 'system') {
772+
optimizer.selectedOptimizeTemplate = null
773+
} else {
774+
optimizer.selectedUserOptimizeTemplate = null
775+
}
776+
}
777+
778+
const ensureTemplateSelection = () => {
779+
const current = currentSelectedTemplate.value
780+
const available = templateOptions.value
781+
782+
if (current) {
783+
const matched = available.find(t => t.id === current.id)
784+
if (matched) {
785+
if (matched !== current) {
786+
currentSelectedTemplate.value = matched
787+
}
788+
return
789+
}
790+
}
791+
792+
if (available.length > 0) {
793+
currentSelectedTemplate.value = available[0]
794+
} else {
795+
clearCurrentTemplateSelection()
796+
}
797+
}
798+
799+
const refreshOptimizeTemplates = async () => {
800+
if (!services.value?.templateManager) {
801+
templateOptions.value = []
802+
clearCurrentTemplateSelection()
803+
return
804+
}
805+
806+
try {
807+
const list = await services.value.templateManager.listTemplatesByType(templateSelectType.value as any)
808+
templateOptions.value = list || []
809+
} catch (error) {
810+
console.warn('[App] Failed to refresh optimize templates:', error)
811+
templateOptions.value = []
812+
}
813+
814+
ensureTemplateSelection()
815+
}
816+
817+
const refreshTextModels = async () => {
818+
if (!services.value?.modelManager) {
819+
textModelOptions.value = []
820+
return
821+
}
822+
823+
try {
824+
const manager = services.value.modelManager
825+
if (typeof (manager as any).ensureInitialized === 'function') {
826+
await (manager as any).ensureInitialized()
827+
}
828+
const enabledModels = await manager.getEnabledModels()
829+
textModelOptions.value = enabledModels.map((m: ModelConfig & { key: string }) => {
830+
const providerName = (m as any)?.provider ?? (m as any)?.providerId ?? 'Unknown'
831+
return {
832+
label: `${m.name} (${providerName})`,
833+
value: m.key,
834+
raw: m
835+
}
836+
})
837+
838+
const availableKeys = new Set(textModelOptions.value.map(opt => opt.value))
839+
const fallbackValue = textModelOptions.value[0]?.value || ''
840+
841+
if (fallbackValue) {
842+
if (!availableKeys.has(modelManager.selectedOptimizeModel)) {
843+
modelManager.selectedOptimizeModel = fallbackValue
844+
}
845+
if (!availableKeys.has(modelManager.selectedTestModel)) {
846+
modelManager.selectedTestModel = fallbackValue
847+
}
848+
}
849+
} catch (error) {
850+
console.warn('[App] Failed to refresh text models:', error)
851+
textModelOptions.value = []
852+
}
853+
}
854+
855+
const selectedTemplateIdForSelect = computed<string>({
856+
get() {
857+
const current = currentSelectedTemplate.value
858+
if (!current) return ''
859+
return templateOptions.value.some(t => t.id === current.id) ? current.id : ''
860+
},
861+
set(id: string) {
862+
if (!id) {
863+
clearCurrentTemplateSelection()
864+
return
865+
}
866+
const tpl = templateOptions.value.find(t => t.id === id)
867+
if (tpl) {
868+
currentSelectedTemplate.value = tpl
869+
}
870+
}
871+
})
872+
873+
watch(() => services.value?.templateManager, async (manager) => {
874+
if (manager) {
875+
await refreshOptimizeTemplates()
876+
} else {
877+
templateOptions.value = []
878+
clearCurrentTemplateSelection()
879+
}
880+
}, { immediate: true })
881+
882+
watch(() => services.value?.modelManager, async (manager) => {
883+
if (manager) {
884+
await refreshTextModels()
885+
} else {
886+
textModelOptions.value = []
887+
}
888+
}, { immediate: true })
889+
890+
watch(() => templateSelectType.value, async () => {
891+
await refreshOptimizeTemplates()
892+
})
893+
714894
// 7. 监听服务初始化
715895
watch(services, async (newServices) => {
716896
if (!newServices) return
@@ -746,23 +926,6 @@ hljs.registerLanguage('json', jsonLang)
746926
}, 1500)
747927
}
748928
749-
// 8. 计算属性和方法
750-
const currentSelectedTemplate = computed({
751-
get() {
752-
return selectedOptimizationMode.value === 'system'
753-
? optimizer.selectedOptimizeTemplate
754-
: optimizer.selectedUserOptimizeTemplate
755-
},
756-
set(newValue) {
757-
if (!newValue) return
758-
if (selectedOptimizationMode.value === 'system') {
759-
optimizer.selectedOptimizeTemplate = newValue
760-
} else {
761-
optimizer.selectedUserOptimizeTemplate = newValue
762-
}
763-
}
764-
})
765-
766929
// 处理优化提示词
767930
const handleOptimizePrompt = () => {
768931
// 检查是否需要传递高级上下文
@@ -912,9 +1075,7 @@ hljs.registerLanguage('json', jsonLang)
9121075
console.log('[App] 模板语言已切换:', newLanguage)
9131076
9141077
// 刷新主界面的模板选择组件
915-
if (templateSelectRef.value?.refresh) {
916-
templateSelectRef.value.refresh()
917-
}
1078+
refreshOptimizeTemplates()
9181079
9191080
// 刷新迭代页面的模板选择组件
9201081
if (promptPanelRef.value?.refreshIterateTemplateSelect) {
@@ -933,10 +1094,11 @@ hljs.registerLanguage('json', jsonLang)
9331094
// 模板管理器关闭回调:刷新基础模式选择,同时通知图像模式刷新模板列表
9341095
const handleTemplateManagerClosed = () => {
9351096
try {
936-
templateManagerState.handleTemplateManagerClose(() => templateSelectRef.value?.refresh?.())
1097+
templateManagerState.handleTemplateManagerClose(() => { refreshOptimizeTemplates() })
9371098
} catch (e) {
9381099
console.warn('[App] Failed to run template manager close handler:', e)
9391100
}
1101+
refreshOptimizeTemplates()
9401102
if (typeof window !== 'undefined') {
9411103
window.dispatchEvent(new Event('image-workspace-refresh-templates'))
9421104
}
@@ -962,6 +1124,7 @@ hljs.registerLanguage('json', jsonLang)
9621124
} catch (e) {
9631125
console.warn('[App] Failed to refresh text models after manager close:', e)
9641126
}
1127+
await refreshTextModels()
9651128
// 图像模式:广播刷新图像模型事件(ImageWorkspace 监听并执行刷新)
9661129
if (typeof window !== 'undefined') {
9671130
window.dispatchEvent(new Event('image-workspace-refresh-image-models'))

0 commit comments

Comments
 (0)