diff --git a/packages/ui/src/components/app-layout/PromptOptimizerApp.vue b/packages/ui/src/components/app-layout/PromptOptimizerApp.vue index c1f78bb0..a5927ebb 100644 --- a/packages/ui/src/components/app-layout/PromptOptimizerApp.vue +++ b/packages/ui/src/components/app-layout/PromptOptimizerApp.vue @@ -1866,6 +1866,16 @@ const openModelManager = (tab: "text" | "image" | "function" = "text") => { }; provide("openModelManager", openModelManager); +// 提供 openContextEditor 接口(供 Pro Multi 等工作区直接调用) +type ContextEditorOpenArg = ConversationMessage[] | "messages" | "variables" | "tools"; +const openContextEditor = ( + messagesOrTab?: ContextEditorOpenArg, + variables?: Record, +) => { + void contextManagement.handleOpenContextEditor(messagesOrTab, variables); +}; +provide("openContextEditor", openContextEditor); + // 模型管理器关闭回调 const handleModelManagerClosed = async () => { try { diff --git a/packages/ui/src/components/context-mode/ContextSystemWorkspace.vue b/packages/ui/src/components/context-mode/ContextSystemWorkspace.vue index 0aa659f9..2e9656ce 100644 --- a/packages/ui/src/components/context-mode/ContextSystemWorkspace.vue +++ b/packages/ui/src/components/context-mode/ContextSystemWorkspace.vue @@ -19,7 +19,7 @@ > @@ -79,7 +79,7 @@ :getPrimary="OptionAccessors.getPrimary" :getSecondary="OptionAccessors.getSecondary" :getValue="OptionAccessors.getValue" - @config="emit('open-template-manager')" + @config="handleOpenTemplateManager" /> @@ -119,14 +119,14 @@ :show-apply-button="displayAdapter.isInMessageOptimizationMode.value" :optimization-mode="optimizationMode" :advanced-mode-enabled="true" - :show-preview="true" + :show-preview="true" @iterate="handleIterate" - @openTemplateManager="emit('open-template-manager', $event)" + @openTemplateManager="handleOpenTemplateManager" @switchVersion="handleSwitchVersion" - @switchToV0="handleSwitchToV0" - @save-favorite="emit('save-favorite', $event)" - @open-preview="handleOpenPromptPreview" - @apply-to-conversation="handleApplyToConversation" + @switchToV0="handleSwitchToV0" + @save-favorite="emit('save-favorite', $event)" + @open-preview="handleOpenPromptPreview" + @apply-to-conversation="handleApplyToConversation" @apply-improvement="handleApplyImprovement" @save-local-edit="handleSaveLocalEdit" /> @@ -605,9 +605,58 @@ const injectedServices = inject>('services') const servicesRef = injectedServices ?? ref(null) const variableManager = inject('variableManager', null) +// 注入 App 层统一的 open* 接口(避免 Pro 工作区 emit 链断导致按钮无响应) +const appOpenModelManager = inject< + ((tab?: 'text' | 'image' | 'function') => void) | null +>('openModelManager', null) +const appOpenTemplateManager = inject<((type?: string) => void) | null>( + 'openTemplateManager', + null, +) +type ContextEditorOpenArg = ConversationMessage[] | 'messages' | 'variables' | 'tools' +const appOpenContextEditor = inject< + ((messagesOrTab?: ContextEditorOpenArg, variables?: Record) => void) | null +>('openContextEditor', null) + // 🆕 注入优化上下文(多轮对话消息) const optimizationContext = inject>('optimizationContext', ref([])) +const handleConversationMessagesUpdated = (messages: ConversationMessage[]) => { + optimizationContext.value = messages + // 兜底:保留对外 emits,供非 PromptOptimizerApp 宿主集成使用 + emit('update:optimizationContext', messages) +} + +const handleOpenModelManager = () => { + if (appOpenModelManager) { + appOpenModelManager('text') + return + } + emit('config-model') +} + +const handleOpenTemplateManager = (typeOrPayload?: string | Record) => { + // SelectWithConfig 的 @config 可能会传入 payload(非字符串),这里统一兜底处理。 + const type = typeof typeOrPayload === 'string' ? typeOrPayload : undefined + if (appOpenTemplateManager) { + appOpenTemplateManager(type || 'optimize') + return + } + emit('open-template-manager', type) +} + +const handleOpenContextEditor = ( + messages: ConversationMessage[], + variables: Record, +) => { + if (appOpenContextEditor) { + appOpenContextEditor(messages, variables) + return + } + // 兜底:旧链路(如果宿主仍通过 emit 打开编辑器) + emit('open-context-editor') +} + // ✅ 优化模式:固定为 'system'(此组件专门用于系统模式优化) const optimizationMode: OptimizationMode = 'system'; @@ -737,19 +786,15 @@ onMounted(() => { // ✅ 刷新模型列表 modelSelection.refreshTextModels() - // Pro Multi:自动选中最新一条可优化消息(system/user),以便直接启用“优化” - const latestSelectable = [...(optimizationContext.value || [])] - .reverse() - .find((msg) => msg && (msg.role === 'system' || msg.role === 'user') && !!msg.id) - if (latestSelectable) { - void conversationOptimization.selectMessage(latestSelectable) - } - - // 兜底:如果还没有消息可选,但 session store 已有选中消息(刷新/恢复场景),尝试同步一次 - if (!latestSelectable && proMultiSession.selectedMessageId) { + // Pro Multi:初始态保持“未选择消息”,让用户明确选择要优化的消息。 + // 仅在 session store 有选中记录时尝试恢复(刷新/恢复场景)。 + if (proMultiSession.selectedMessageId) { const restored = (optimizationContext.value || []).find((m) => m.id === proMultiSession.selectedMessageId) if (restored) { void conversationOptimization.selectMessage(restored) + } else { + // 防止选中 ID 指向已不存在的消息,导致 UI 误判为“已选中”。 + proMultiSession.selectMessage('') } } diff --git a/packages/ui/src/components/context-mode/ContextUserWorkspace.vue b/packages/ui/src/components/context-mode/ContextUserWorkspace.vue index 048d4931..1bbeb8b7 100644 --- a/packages/ui/src/components/context-mode/ContextUserWorkspace.vue +++ b/packages/ui/src/components/context-mode/ContextUserWorkspace.vue @@ -80,11 +80,11 @@ :show-preview="true" :show-analyze-button="true" :analyze-loading="isAnalyzing" - @submit="handleOptimize" - @analyze="handleAnalyze" - @configModel="emit('config-model')" - @open-preview="handleOpenInputPreview" - :enable-variable-extraction="true" + @submit="handleOptimize" + @analyze="handleAnalyze" + @configModel="handleOpenModelManager" + @open-preview="handleOpenInputPreview" + :enable-variable-extraction="true" :show-extract-button="true" :extracting="props.isExtracting" v-bind="inputPanelVariableData || {}" @@ -100,7 +100,7 @@ :getPrimary="OptionAccessors.getPrimary" :getSecondary="OptionAccessors.getSecondary" :getValue="OptionAccessors.getValue" - @config="emit('config-model')" + @config="handleOpenModelManager" /> @@ -112,7 +112,7 @@ :getPrimary="OptionAccessors.getPrimary" :getSecondary="OptionAccessors.getSecondary" :getValue="OptionAccessors.getValue" - @config="emit('open-template-manager')" + @config="handleOpenTemplateManager" /> @@ -170,14 +170,14 @@ " :versions="contextUserOptimization.currentVersions" :current-version-id="contextUserOptimization.currentVersionId" - :optimization-mode="optimizationMode" - :advanced-mode-enabled="true" - :show-preview="true" - @iterate="handleIterate" - @openTemplateManager="emit('open-template-manager', $event)" - @switchVersion="handleSwitchVersion" - @switchToV0="handleSwitchToV0" - @save-favorite="emit('save-favorite', $event)" + :optimization-mode="optimizationMode" + :advanced-mode-enabled="true" + :show-preview="true" + @iterate="handleIterate" + @openTemplateManager="handleOpenTemplateManager" + @switchVersion="handleSwitchVersion" + @switchToV0="handleSwitchToV0" + @save-favorite="emit('save-favorite', $event)" @open-preview="handleOpenPromptPreview" @apply-improvement="handleApplyImprovement" @save-local-edit="handleSaveLocalEdit" @@ -639,6 +639,33 @@ const injectedServices = inject>('services'); const servicesRef = injectedServices ?? ref(null) const variableManager = inject('variableManager', null); +// 注入 App 层统一的 open* 接口(与 Basic/Image 工作区保持一致) +const appOpenModelManager = inject< + ((tab?: 'text' | 'image' | 'function') => void) | null +>('openModelManager', null) +const appOpenTemplateManager = inject<((type?: string) => void) | null>( + 'openTemplateManager', + null, +) + +const handleOpenModelManager = () => { + if (appOpenModelManager) { + appOpenModelManager('text') + return + } + emit('config-model') +} + +const handleOpenTemplateManager = (typeOrPayload?: string | Record) => { + // SelectWithConfig 的 @config 可能会传入 payload(非字符串),这里统一兜底处理。 + const type = typeof typeOrPayload === 'string' ? typeOrPayload : undefined + if (appOpenTemplateManager) { + appOpenTemplateManager(type || 'optimize') + return + } + emit('open-template-manager', type) +} + // ======================== // 内部状态管理 // ======================== diff --git a/packages/ui/src/components/context-mode/ConversationManager.vue b/packages/ui/src/components/context-mode/ConversationManager.vue index 045ac6f9..653f267d 100644 --- a/packages/ui/src/components/context-mode/ConversationManager.vue +++ b/packages/ui/src/components/context-mode/ConversationManager.vue @@ -51,6 +51,7 @@ :size="buttonSize" type="primary" :loading="loading" + data-testid="pro-multi-open-context-editor" >