fix(ui): wire image workspace analysis actions
This commit is contained in:
1 parent
68adf69036
commit
efda7828b8
20 files changed
+837
-12
No files matched your search
+67
@@ -0,0 +1,67 @@
|
||||
import { createImageAnalysisTemplate } from '../builders';
|
||||
|
||||
export const template = createImageAnalysisTemplate(
|
||||
{
|
||||
id: 'evaluation-image-multiimage-prompt-only',
|
||||
name: '多图生图提示词直接评估',
|
||||
description: '直接评估多图生图提示词质量(多图模式)',
|
||||
language: 'zh',
|
||||
tags: ['evaluation', 'prompt-only', 'scoring', 'image', 'multiimage'],
|
||||
},
|
||||
{
|
||||
subjectLabel: {
|
||||
zh: '多图生图提示词',
|
||||
en: 'multi-image generation prompt',
|
||||
},
|
||||
roleName: {
|
||||
zh: '多图生图提示词分析专家',
|
||||
en: 'Multi_Image_Prompt_Analysis_Expert',
|
||||
},
|
||||
dimensions: [
|
||||
{
|
||||
key: 'referenceIntegration',
|
||||
label: {
|
||||
zh: '参考图融合明确性',
|
||||
en: 'Reference Integration',
|
||||
},
|
||||
description: {
|
||||
zh: '是否清晰说明多张参考图各自的作用、取舍和融合关系?',
|
||||
en: 'Does it clearly state each reference image role, priority, and integration relationship?',
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'generationIntent',
|
||||
label: {
|
||||
zh: '生成目标清晰度',
|
||||
en: 'Generation Intent',
|
||||
},
|
||||
description: {
|
||||
zh: '是否明确描述最终画面的主体、场景、构图和目标效果?',
|
||||
en: 'Does it clearly describe the final subject, scene, composition, and target effect?',
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'constraintClarity',
|
||||
label: {
|
||||
zh: '保留与修改约束',
|
||||
en: 'Preservation and Edit Constraints',
|
||||
},
|
||||
description: {
|
||||
zh: '是否准确说明哪些元素应保留、替换、强化或弱化?',
|
||||
en: 'Does it accurately specify which elements should be preserved, replaced, emphasized, or reduced?',
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'styleConsistency',
|
||||
label: {
|
||||
zh: '风格一致性',
|
||||
en: 'Style Consistency',
|
||||
},
|
||||
description: {
|
||||
zh: '是否给出足够的风格、光影、色彩和质量约束来统一多图来源?',
|
||||
en: 'Does it provide enough style, lighting, color, and quality constraints to unify multiple image sources?',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
import { createImageAnalysisTemplate } from '../builders';
|
||||
|
||||
export const template = createImageAnalysisTemplate(
|
||||
{
|
||||
id: 'evaluation-image-multiimage-prompt-only',
|
||||
name: 'Multi-Image Prompt Direct Evaluation',
|
||||
description: 'Direct evaluation of multi-image generation prompt quality',
|
||||
language: 'en',
|
||||
tags: ['evaluation', 'prompt-only', 'scoring', 'image', 'multiimage'],
|
||||
},
|
||||
{
|
||||
subjectLabel: {
|
||||
zh: '多图生图提示词',
|
||||
en: 'multi-image generation prompt',
|
||||
},
|
||||
roleName: {
|
||||
zh: '多图生图提示词分析专家',
|
||||
en: 'Multi_Image_Prompt_Analysis_Expert',
|
||||
},
|
||||
dimensions: [
|
||||
{
|
||||
key: 'referenceIntegration',
|
||||
label: {
|
||||
zh: '参考图融合明确性',
|
||||
en: 'Reference Integration',
|
||||
},
|
||||
description: {
|
||||
zh: '是否清晰说明多张参考图各自的作用、取舍和融合关系?',
|
||||
en: 'Does it clearly state each reference image role, priority, and integration relationship?',
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'generationIntent',
|
||||
label: {
|
||||
zh: '生成目标清晰度',
|
||||
en: 'Generation Intent',
|
||||
},
|
||||
description: {
|
||||
zh: '是否明确描述最终画面的主体、场景、构图和目标效果?',
|
||||
en: 'Does it clearly describe the final subject, scene, composition, and target effect?',
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'constraintClarity',
|
||||
label: {
|
||||
zh: '保留与修改约束',
|
||||
en: 'Preservation and Edit Constraints',
|
||||
},
|
||||
description: {
|
||||
zh: '是否准确说明哪些元素应保留、替换、强化或弱化?',
|
||||
en: 'Does it accurately specify which elements should be preserved, replaced, emphasized, or reduced?',
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'styleConsistency',
|
||||
label: {
|
||||
zh: '风格一致性',
|
||||
en: 'Style Consistency',
|
||||
},
|
||||
description: {
|
||||
zh: '是否给出足够的风格、光影、色彩和质量约束来统一多图来源?',
|
||||
en: 'Does it provide enough style, lighting, color, and quality constraints to unify multiple image sources?',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* 评估模板 - 图像模式/多图生图
|
||||
*/
|
||||
|
||||
export { template as evaluationImageMultiImagePromptOnly } from './evaluation-prompt-only';
|
||||
export { template as evaluationImageMultiImagePromptOnlyEn } from './evaluation-prompt-only_en';
|
||||
@@ -63,3 +63,9 @@ export {
|
||||
evaluationImageImage2ImagePromptOnly,
|
||||
evaluationImageImage2ImagePromptOnlyEn,
|
||||
} from './image/image2image';
|
||||
|
||||
// 图像模式 - 多图生图评估
|
||||
export {
|
||||
evaluationImageMultiImagePromptOnly,
|
||||
evaluationImageMultiImagePromptOnlyEn,
|
||||
} from './image/multiimage';
|
||||
@@ -120,6 +120,9 @@ import { template as evaluation_image_text2image_compare_en } from './evaluation
|
||||
// 评估模板 - 图像模式/图生图
|
||||
import { template as evaluation_image_image2image_prompt_only } from './evaluation/image/image2image/evaluation-prompt-only';
|
||||
import { template as evaluation_image_image2image_prompt_only_en } from './evaluation/image/image2image/evaluation-prompt-only_en';
|
||||
// 评估模板 - 图像模式/多图生图
|
||||
import { template as evaluation_image_multiimage_prompt_only } from './evaluation/image/multiimage/evaluation-prompt-only';
|
||||
import { template as evaluation_image_multiimage_prompt_only_en } from './evaluation/image/multiimage/evaluation-prompt-only_en';
|
||||
|
||||
// 变量提取模板
|
||||
import { variableExtractionTemplate } from './variable-extraction';
|
||||
@@ -257,6 +260,9 @@ export const ALL_TEMPLATES = {
|
||||
// 评估模板 - 图像模式/图生图
|
||||
evaluation_image_image2image_prompt_only,
|
||||
evaluation_image_image2image_prompt_only_en,
|
||||
// 评估模板 - 图像模式/多图生图
|
||||
evaluation_image_multiimage_prompt_only,
|
||||
evaluation_image_multiimage_prompt_only_en,
|
||||
// 变量提取模板
|
||||
variableExtractionTemplate,
|
||||
variableExtractionTemplateEn,
|
||||
|
||||
@@ -4,6 +4,8 @@ import { template as text2imagePromptOnlyZh } from '../../../src/services/templa
|
||||
import { template as text2imagePromptOnlyEn } from '../../../src/services/template/default-templates/evaluation/image/text2image/evaluation-prompt-only_en';
|
||||
import { template as image2imagePromptOnlyZh } from '../../../src/services/template/default-templates/evaluation/image/image2image/evaluation-prompt-only';
|
||||
import { template as image2imagePromptOnlyEn } from '../../../src/services/template/default-templates/evaluation/image/image2image/evaluation-prompt-only_en';
|
||||
import { template as multiimagePromptOnlyZh } from '../../../src/services/template/default-templates/evaluation/image/multiimage/evaluation-prompt-only';
|
||||
import { template as multiimagePromptOnlyEn } from '../../../src/services/template/default-templates/evaluation/image/multiimage/evaluation-prompt-only_en';
|
||||
|
||||
describe('image evaluation prompt-only templates JSON evidence injection', () => {
|
||||
const baseContext: TemplateContext = {
|
||||
@@ -23,6 +25,8 @@ describe('image evaluation prompt-only templates JSON evidence injection', () =>
|
||||
['en-text2image', text2imagePromptOnlyEn],
|
||||
['zh-image2image', image2imagePromptOnlyZh],
|
||||
['en-image2image', image2imagePromptOnlyEn],
|
||||
['zh-multiimage', multiimagePromptOnlyZh],
|
||||
['en-multiimage', multiimagePromptOnlyEn],
|
||||
])('should render %s image evaluation prompt-only template with JSON-wrapped evidence', (_label, template) => {
|
||||
const messages = TemplateProcessor.processTemplate(template, baseContext);
|
||||
|
||||
|
||||
@@ -66,6 +66,18 @@ describe('image text2image evaluation templates', () => {
|
||||
expect(promptOnlyTemplate?.content[0]?.content).toContain('只输出合法 JSON,禁止 Markdown')
|
||||
})
|
||||
|
||||
it('registers the zh-CN multiimage prompt-only template', async () => {
|
||||
vi.spyOn(languageService, 'getCurrentLanguage').mockReturnValue('zh-CN')
|
||||
|
||||
const promptOnlyTemplate = await templateManager.getTemplate('evaluation-image-multiimage-prompt-only')
|
||||
|
||||
expect(promptOnlyTemplate).toBeDefined()
|
||||
expect(promptOnlyTemplate?.content[0]?.content).toContain('当前工作区多图生图提示词')
|
||||
expect(promptOnlyTemplate?.content[0]?.content).toContain('参考图融合明确性')
|
||||
expect(promptOnlyTemplate?.content[1]?.content).toContain('"workspacePrompt":')
|
||||
expect(promptOnlyTemplate?.content[1]?.content).toContain('"analysisStage":')
|
||||
})
|
||||
|
||||
it('registers the zh-CN image prompt-iterate template with iteration evidence', async () => {
|
||||
vi.spyOn(languageService, 'getCurrentLanguage').mockReturnValue('zh-CN')
|
||||
|
||||
@@ -124,6 +136,18 @@ describe('image text2image evaluation templates', () => {
|
||||
expect(promptOnlyTemplate?.content[0]?.content).toContain('No Markdown, no code fences')
|
||||
})
|
||||
|
||||
it('registers the en-US multiimage prompt-only template', async () => {
|
||||
vi.spyOn(languageService, 'getCurrentLanguage').mockReturnValue('en-US')
|
||||
|
||||
const promptOnlyTemplate = await templateManager.getTemplate('evaluation-image-multiimage-prompt-only')
|
||||
|
||||
expect(promptOnlyTemplate).toBeDefined()
|
||||
expect(promptOnlyTemplate?.content[0]?.content).toContain('current workspace multi-image generation prompt')
|
||||
expect(promptOnlyTemplate?.content[0]?.content).toContain('Reference Integration')
|
||||
expect(promptOnlyTemplate?.content[1]?.content).toContain('"workspacePrompt":')
|
||||
expect(promptOnlyTemplate?.content[1]?.content).toContain('"analysisStage":')
|
||||
})
|
||||
|
||||
it('registers the en-US image prompt-iterate template with iteration evidence', async () => {
|
||||
vi.spyOn(languageService, 'getCurrentLanguage').mockReturnValue('en-US')
|
||||
|
||||
|
||||
@@ -135,6 +135,7 @@
|
||||
@open-preview="handleOpenPromptPreview"
|
||||
@apply-to-conversation="handleApplyToConversation"
|
||||
@apply-improvement="handleApplyImprovement"
|
||||
@apply-patch="handleApplyLocalPatch"
|
||||
@save-local-edit="handleSaveLocalEdit"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -63,6 +63,7 @@
|
||||
@show-primary-detail="emit('show-primary-detail')"
|
||||
@show-secondary-detail="emit('show-secondary-detail')"
|
||||
@apply-improvement="emit('apply-improvement', $event)"
|
||||
@apply-patch="emit('apply-patch', $event)"
|
||||
>
|
||||
<template #primary-result>
|
||||
<slot name="primary-result"></slot>
|
||||
@@ -105,7 +106,7 @@ import TestControlBar from "../TestControlBar.vue";
|
||||
import TestResultSection from "../TestResultSection.vue";
|
||||
import TemporaryVariablesPanel from "../variable/TemporaryVariablesPanel.vue";
|
||||
import VariableValuePreviewDialog from "../variable/VariableValuePreviewDialog.vue";
|
||||
import type { EvaluationResponse, EvaluationType } from '@prompt-optimizer/core';
|
||||
import type { EvaluationResponse, EvaluationType, PatchOperation } from '@prompt-optimizer/core';
|
||||
import type { ScoreLevel } from '../../composables/prompt/useEvaluation';
|
||||
import type { AppServices } from '../../types/services';
|
||||
|
||||
@@ -229,6 +230,7 @@ const emit = defineEmits<{
|
||||
"show-primary-detail": [];
|
||||
"show-secondary-detail": [];
|
||||
"apply-improvement": [payload: { improvement: string; type: EvaluationType }];
|
||||
"apply-patch": [payload: { operation: PatchOperation }];
|
||||
}>();
|
||||
|
||||
// 处理对比模式切换
|
||||
|
||||
@@ -185,9 +185,10 @@
|
||||
@openTemplateManager="handleOpenTemplateManager"
|
||||
@switchVersion="handleSwitchVersion"
|
||||
@switchToV0="handleSwitchToV0"
|
||||
@save-favorite="handleSaveFavorite"
|
||||
@save-favorite="handleSaveFavorite"
|
||||
@open-preview="handleOpenPromptPreview"
|
||||
@apply-improvement="handleApplyImprovement"
|
||||
@apply-patch="handleApplyLocalPatch"
|
||||
@save-local-edit="handleSaveLocalEdit"
|
||||
/>
|
||||
</NCard>
|
||||
|
||||
@@ -63,6 +63,7 @@
|
||||
@show-primary-detail="emit('show-primary-detail')"
|
||||
@show-secondary-detail="emit('show-secondary-detail')"
|
||||
@apply-improvement="emit('apply-improvement', $event)"
|
||||
@apply-patch="emit('apply-patch', $event)"
|
||||
>
|
||||
<template #primary-result>
|
||||
<div class="result-container">
|
||||
@@ -135,6 +136,7 @@ import type {
|
||||
ToolCallResult,
|
||||
EvaluationResponse,
|
||||
EvaluationType,
|
||||
PatchOperation,
|
||||
} from "@prompt-optimizer/core";
|
||||
import type { ScoreLevel } from '../../composables/prompt/useEvaluation';
|
||||
import { useResponsive } from '../../composables/ui/useResponsive';
|
||||
@@ -269,6 +271,7 @@ const emit = defineEmits<{
|
||||
"show-primary-detail": [];
|
||||
"show-secondary-detail": [];
|
||||
"apply-improvement": [payload: { improvement: string; type: EvaluationType }];
|
||||
"apply-patch": [payload: { operation: PatchOperation }];
|
||||
}>();
|
||||
|
||||
// 🆕 工具调用状态管理(按 variantId 分桶)
|
||||
|
||||
@@ -373,6 +373,24 @@
|
||||
<!-- 优化按钮 -->
|
||||
<NGridItem :span="6" :xs="24" :sm="6" class="flex items-end justify-end">
|
||||
<NSpace :size="8">
|
||||
<NButton
|
||||
type="default"
|
||||
size="medium"
|
||||
data-testid="image-image2image-analyze-button"
|
||||
:loading="isAnalyzing"
|
||||
@click="handleAnalyzePrompt"
|
||||
:disabled="
|
||||
isAnalyzing ||
|
||||
isOptimizing ||
|
||||
!originalPrompt.trim()
|
||||
"
|
||||
>
|
||||
{{
|
||||
isAnalyzing
|
||||
? t("promptOptimizer.analyzing")
|
||||
: t("promptOptimizer.analyze")
|
||||
}}
|
||||
</NButton>
|
||||
<NButton
|
||||
type="primary"
|
||||
size="medium"
|
||||
@@ -380,6 +398,7 @@
|
||||
:loading="isOptimizing"
|
||||
@click="handleOptimizePrompt"
|
||||
:disabled="
|
||||
isAnalyzing ||
|
||||
isOptimizing ||
|
||||
!originalPrompt.trim() ||
|
||||
!inputImageB64 ||
|
||||
@@ -419,12 +438,15 @@
|
||||
:optimization-mode="optimizationMode"
|
||||
:advanced-mode-enabled="advancedModeEnabled"
|
||||
:show-preview="true"
|
||||
evaluation-type-override="prompt-only"
|
||||
iterate-template-type="imageIterate"
|
||||
@iterate="handleIteratePrompt"
|
||||
@openTemplateManager="onOpenTemplateManager"
|
||||
@switchVersion="handleSwitchVersion"
|
||||
@save-favorite="handleSaveFavorite"
|
||||
@save-local-edit="handleSaveLocalEdit"
|
||||
@apply-improvement="handleApplyImprovement"
|
||||
@apply-patch="handleApplyPatch"
|
||||
@open-preview="handleOpenPromptPreview"
|
||||
/>
|
||||
</NCard>
|
||||
@@ -672,6 +694,29 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<EvaluationPanel
|
||||
v-model:show="evaluation.isPanelVisible.value"
|
||||
:is-evaluating="panelProps.isEvaluating"
|
||||
:result="panelProps.result"
|
||||
:stream-content="panelProps.streamContent"
|
||||
:error="panelProps.error"
|
||||
:current-type="panelProps.currentType"
|
||||
:score-level="panelProps.scoreLevel"
|
||||
:rewrite-recommendation="panelProps.rewriteRecommendation"
|
||||
:rewrite-reasons="panelProps.rewriteReasons"
|
||||
:stale="activeEvaluationStale"
|
||||
:stale-message="activeEvaluationStaleMessage"
|
||||
:disable-evaluate="activeEvaluationDisableEvaluate"
|
||||
:disable-evaluate-reason="activeEvaluationDisableReason"
|
||||
:can-rewrite-from-evaluation="false"
|
||||
@apply-local-patch="handleApplyPatch"
|
||||
@apply-improvement="handleApplyImprovement"
|
||||
@re-evaluate="handleReEvaluateActive"
|
||||
@evaluate-with-feedback="handleEvaluateActiveWithFeedback"
|
||||
@clear="handleClearEvaluation"
|
||||
@retry="handleReEvaluateActive"
|
||||
/>
|
||||
|
||||
<!-- 原始提示词 - 全屏编辑器 -->
|
||||
<FullscreenDialog
|
||||
v-model="isFullscreen"
|
||||
@@ -772,7 +817,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, onUnmounted, inject, ref, reactive, computed, watch, nextTick, type Ref } from 'vue'
|
||||
import { onMounted, onUnmounted, inject, ref, reactive, computed, watch, nextTick, toRef, type Ref } from 'vue'
|
||||
import { useRouter, type LocationQueryRaw } from 'vue-router'
|
||||
|
||||
import {
|
||||
@@ -806,6 +851,7 @@ import PromptGardenImportDialog from '../common/PromptGardenImportDialog.vue'
|
||||
import PromptPreviewPanel from "../PromptPreviewPanel.vue";
|
||||
import SelectWithConfig from "../SelectWithConfig.vue";
|
||||
import TestPanelVersionSelect from '../TestPanelVersionSelect.vue'
|
||||
import { EvaluationPanel } from '../evaluation'
|
||||
import { useLocalPromptPreviewPanel } from '../../composables/prompt/useLocalPromptPreviewPanel'
|
||||
import { OptionAccessors } from "../../utils/data-transformer";
|
||||
import type { AppServices } from "../../types/services";
|
||||
@@ -818,6 +864,7 @@ import { withHistorySourceBindingMetadata } from '../../utils/history-source-bin
|
||||
import { resolveSourceAssetRef } from '../../utils/source-asset'
|
||||
import { downloadImageSource } from '../../utils/image-download'
|
||||
import { openExternalUrl } from '../../utils/open-external-url'
|
||||
import { createImagePromptAnalysisVersion } from '../../utils/imagePromptAnalysis'
|
||||
import type { PromptGardenImportRequest } from '../../utils/prompt-garden-import'
|
||||
import { VariableAwareInput } from '../variable-extraction'
|
||||
import TemporaryVariablesPanel from '../variable/TemporaryVariablesPanel.vue'
|
||||
@@ -828,6 +875,8 @@ import { useTemporaryVariables } from '../../composables/variable/useTemporaryVa
|
||||
import { useVariableAwareInputBridge } from '../../composables/variable/useVariableAwareInputBridge'
|
||||
import { useTestVariableManager } from '../../composables/variable/useTestVariableManager'
|
||||
import { useSmartVariableValueGeneration } from '../../composables/variable/useSmartVariableValueGeneration'
|
||||
import { useEvaluationHandler } from '../../composables/prompt/useEvaluationHandler'
|
||||
import { provideEvaluation } from '../../composables/prompt/useEvaluationContext'
|
||||
import type { VariableManagerHooks } from '../../composables/prompt/useVariableManager'
|
||||
import {
|
||||
buildPromptExecutionContext,
|
||||
@@ -847,11 +896,13 @@ import {
|
||||
} from '../../stores/session/useImageImage2ImageSession'
|
||||
import { useImageGeneration } from '../../composables/image/useImageGeneration'
|
||||
import ImageTokenUsage from './ImageTokenUsage.vue'
|
||||
import { useFunctionModelManager } from '../../composables/model'
|
||||
import { useWorkspaceTemplateSelection } from '../../composables/workspaces/useWorkspaceTemplateSelection'
|
||||
import { useWorkspaceTextModelSelection } from '../../composables/workspaces/useWorkspaceTextModelSelection'
|
||||
import { useElementSize } from '@vueuse/core'
|
||||
import { runTasksWithExecutionMode } from '../../utils/runTasksSequentially'
|
||||
import {
|
||||
applyPatchOperationsToText,
|
||||
getEnvVar,
|
||||
type ContextMode,
|
||||
type ImageModelConfig,
|
||||
@@ -860,6 +911,7 @@ import {
|
||||
type ImageResultItem,
|
||||
type OptimizationMode,
|
||||
type OptimizationRequest,
|
||||
type PatchOperation,
|
||||
type PromptRecordChain,
|
||||
type PromptRecordType,
|
||||
type Template,
|
||||
@@ -965,6 +1017,7 @@ const promptService = computed(() => services.value?.promptService)
|
||||
|
||||
// 过程态(本地,不持久化)
|
||||
const isOptimizing = ref(false)
|
||||
const isAnalyzing = ref(false)
|
||||
const isIterating = ref(false)
|
||||
const uploadStatus = ref<'idle' | 'uploading' | 'success' | 'error'>('idle')
|
||||
const uploadProgress = ref(0)
|
||||
@@ -1007,6 +1060,7 @@ const optimizedReasoning = computed<string>({
|
||||
// Text 模型选择(与模板选择对齐:自动刷新 + 兜底写回 session store)
|
||||
const modelSelection = useWorkspaceTextModelSelection(services, session)
|
||||
const selectedTextModelKey = modelSelection.selectedTextModelKey
|
||||
const functionModelManager = useFunctionModelManager(services)
|
||||
|
||||
const selectedImageModelKey = computed<string>({
|
||||
get: () => session.selectedImageModelKey || '',
|
||||
@@ -1023,6 +1077,84 @@ const templateSelection = useWorkspaceTemplateSelection(
|
||||
const selectedTemplateId = templateSelection.selectedTemplateId
|
||||
const templateOptions = templateSelection.templateOptions
|
||||
|
||||
const evaluationHandler = useEvaluationHandler({
|
||||
services,
|
||||
analysisOptimizedPrompt: computed(() => optimizedPrompt.value || ''),
|
||||
analysisTargetResolver: (defaultTarget) => ({
|
||||
...defaultTarget,
|
||||
referencePrompt: (originalPrompt.value || '').trim() || undefined,
|
||||
}),
|
||||
evaluationModelKey: computed(() => selectedTextModelKey.value || ''),
|
||||
resolveEvaluationModelKey: async () => {
|
||||
await functionModelManager.initialize()
|
||||
return (
|
||||
functionModelManager.evaluationModel.value ||
|
||||
selectedTextModelKey.value ||
|
||||
functionModelManager.effectiveEvaluationModel.value ||
|
||||
''
|
||||
)
|
||||
},
|
||||
functionMode: computed(() => 'image'),
|
||||
subMode: computed(() => 'image2image'),
|
||||
persistedResults: toRef(session, 'evaluationResults'),
|
||||
})
|
||||
|
||||
provideEvaluation(evaluationHandler.evaluation)
|
||||
|
||||
const { evaluation, handleEvaluate: handleEvaluateInternal } = evaluationHandler
|
||||
const panelProps = evaluationHandler.panelProps
|
||||
|
||||
const activeEvaluationStale = computed(() => false)
|
||||
const activeEvaluationStaleMessage = computed(() => t('evaluation.stale.promptOnly'))
|
||||
const activeEvaluationDisableEvaluate = computed(() =>
|
||||
panelProps.value.currentType === 'prompt-only' &&
|
||||
!optimizedPrompt.value.trim(),
|
||||
)
|
||||
const activeEvaluationDisableReason = computed(() => '')
|
||||
|
||||
const handleAnalyzePrompt = async () => {
|
||||
const prompt = originalPrompt.value.trim()
|
||||
if (!prompt || isAnalyzing.value) return
|
||||
|
||||
isAnalyzing.value = true
|
||||
try {
|
||||
const virtualV0 = createImagePromptAnalysisVersion(
|
||||
prompt,
|
||||
'image2imageOptimize' as PromptRecordType,
|
||||
)
|
||||
currentChainId.value = ''
|
||||
currentVersions.value = [virtualV0]
|
||||
currentVersionId.value = virtualV0.id
|
||||
session.updateOptimizedResult({
|
||||
optimizedPrompt: prompt,
|
||||
reasoning: '',
|
||||
chainId: '',
|
||||
versionId: '',
|
||||
})
|
||||
evaluation.clearResult('prompt-only')
|
||||
evaluation.clearResult('prompt-iterate')
|
||||
await nextTick()
|
||||
await handleEvaluateInternal('prompt-only')
|
||||
} finally {
|
||||
isAnalyzing.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleReEvaluateActive = async () => {
|
||||
if (!evaluation.state.activeDetail) return
|
||||
await evaluationHandler.handleReEvaluate()
|
||||
}
|
||||
|
||||
const handleEvaluateActiveWithFeedback = async (payload: { feedback: string }) => {
|
||||
if (!evaluation.state.activeDetail) return
|
||||
await evaluationHandler.handleEvaluateActiveWithFeedback(payload.feedback)
|
||||
}
|
||||
|
||||
const handleClearEvaluation = () => {
|
||||
evaluation.closePanel()
|
||||
evaluation.clearAllResults()
|
||||
}
|
||||
|
||||
const showPromptGardenImport = ref(false)
|
||||
|
||||
const isPromptGardenEnabled = computed(() => {
|
||||
@@ -1770,6 +1902,20 @@ const handleSaveLocalEdit = async (payload: { note?: string }) => {
|
||||
// PromptPanel 引用,用于在语言切换后刷新迭代模板选择
|
||||
const promptPanelRef = ref<InstanceType<typeof PromptPanelUI> | null>(null);
|
||||
|
||||
const handleApplyImprovement = evaluationHandler.createApplyImprovementHandler(promptPanelRef)
|
||||
|
||||
const handleApplyPatch = (payload: { operation: PatchOperation }) => {
|
||||
if (!payload.operation) return
|
||||
const current = optimizedPrompt.value || ''
|
||||
const result = applyPatchOperationsToText(current, payload.operation)
|
||||
if (!result.ok) {
|
||||
toast.warning(t('toast.warning.patchApplyFailed'))
|
||||
return
|
||||
}
|
||||
optimizedPrompt.value = result.text
|
||||
toast.success(t('evaluation.diagnose.applyFix'))
|
||||
}
|
||||
|
||||
// 输入区折叠状态(初始展开)
|
||||
const isInputPanelCollapsed = ref(false);
|
||||
|
||||
|
||||
@@ -262,12 +262,22 @@
|
||||
|
||||
<NGridItem :span="6" :xs="24" :sm="6" class="flex items-end justify-end">
|
||||
<NSpace :size="8">
|
||||
<NButton
|
||||
type="default"
|
||||
size="medium"
|
||||
data-testid="image-multiimage-analyze-button"
|
||||
:loading="isAnalyzing"
|
||||
:disabled="isAnalyzing || optimizing || isIterating || !originalPrompt.trim()"
|
||||
@click="handleAnalyzePrompt"
|
||||
>
|
||||
{{ isAnalyzing ? t('promptOptimizer.analyzing') : t('promptOptimizer.analyze') }}
|
||||
</NButton>
|
||||
<NButton
|
||||
type="primary"
|
||||
size="medium"
|
||||
data-testid="image-multiimage-optimize"
|
||||
:loading="optimizing"
|
||||
:disabled="optimizing || isIterating || !canOptimize"
|
||||
:disabled="isAnalyzing || optimizing || isIterating || !canOptimize"
|
||||
@click="optimizePrompt"
|
||||
>
|
||||
{{ optimizing ? t('imageWorkspace.input.optimizing') : t('common.optimize') }}
|
||||
@@ -297,12 +307,15 @@
|
||||
:optimization-mode="optimizationMode"
|
||||
:advanced-mode-enabled="advancedModeEnabled"
|
||||
:show-preview="true"
|
||||
evaluation-type-override="prompt-only"
|
||||
iterate-template-type="imageIterate"
|
||||
@iterate="handleIteratePrompt"
|
||||
@openTemplateManager="onOpenTemplateManager"
|
||||
@switchVersion="handleSwitchVersion"
|
||||
@save-favorite="handleSaveFavorite"
|
||||
@save-local-edit="handleSaveLocalEdit"
|
||||
@apply-improvement="handleApplyImprovement"
|
||||
@apply-patch="handleApplyPatch"
|
||||
@open-preview="handleOpenPromptPreview"
|
||||
/>
|
||||
</NCard>
|
||||
@@ -517,6 +530,29 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<EvaluationPanel
|
||||
v-model:show="evaluation.isPanelVisible.value"
|
||||
:is-evaluating="panelProps.isEvaluating"
|
||||
:result="panelProps.result"
|
||||
:stream-content="panelProps.streamContent"
|
||||
:error="panelProps.error"
|
||||
:current-type="panelProps.currentType"
|
||||
:score-level="panelProps.scoreLevel"
|
||||
:rewrite-recommendation="panelProps.rewriteRecommendation"
|
||||
:rewrite-reasons="panelProps.rewriteReasons"
|
||||
:stale="activeEvaluationStale"
|
||||
:stale-message="activeEvaluationStaleMessage"
|
||||
:disable-evaluate="activeEvaluationDisableEvaluate"
|
||||
:disable-evaluate-reason="activeEvaluationDisableReason"
|
||||
:can-rewrite-from-evaluation="false"
|
||||
@apply-local-patch="handleApplyPatch"
|
||||
@apply-improvement="handleApplyImprovement"
|
||||
@re-evaluate="handleReEvaluateActive"
|
||||
@evaluate-with-feedback="handleEvaluateActiveWithFeedback"
|
||||
@clear="handleClearEvaluation"
|
||||
@retry="handleReEvaluateActive"
|
||||
/>
|
||||
|
||||
<FullscreenDialog
|
||||
v-model="isFullscreen"
|
||||
:title="t('imageWorkspace.input.originalPrompt')"
|
||||
@@ -552,10 +588,23 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, inject, nextTick, onMounted, onUnmounted, reactive, ref, watch, type Ref } from 'vue'
|
||||
import { computed, inject, nextTick, onMounted, onUnmounted, reactive, ref, watch, toRef, type Ref } from 'vue'
|
||||
import { NButton, NCard, NEmpty, NFlex, NGrid, NGridItem, NIcon, NInput, NRadioButton, NRadioGroup, NSpace, NTag, NText, NTooltip } from 'naive-ui'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import type { ContextMode, ImageInputRef, ImageModelConfig, ImageResult, ImageResultItem, MultiImageGenerationRequest, OptimizationMode, PromptRecordChain, PromptRecordType, Template } from '@prompt-optimizer/core'
|
||||
import {
|
||||
applyPatchOperationsToText,
|
||||
type ContextMode,
|
||||
type ImageInputRef,
|
||||
type ImageModelConfig,
|
||||
type ImageResult,
|
||||
type ImageResultItem,
|
||||
type MultiImageGenerationRequest,
|
||||
type OptimizationMode,
|
||||
type PatchOperation,
|
||||
type PromptRecordChain,
|
||||
type PromptRecordType,
|
||||
type Template,
|
||||
} from '@prompt-optimizer/core'
|
||||
import type { AppServices } from '../../types/services'
|
||||
import { useImageMultiImageSession, type TestColumnCount, type TestPanelVersionValue, type TestVariantId } from '../../stores/session/useImageMultiImageSession'
|
||||
import { useImageGeneration } from '../../composables/image/useImageGeneration'
|
||||
@@ -565,6 +614,9 @@ import { useVariableAwareInputBridge } from '../../composables/variable/useVaria
|
||||
import { useSmartVariableValueGeneration } from '../../composables/variable/useSmartVariableValueGeneration'
|
||||
import { useToast } from '../../composables/ui/useToast'
|
||||
import { useFullscreen } from '../../composables/ui/useFullscreen'
|
||||
import { useEvaluationHandler } from '../../composables/prompt/useEvaluationHandler'
|
||||
import { provideEvaluation } from '../../composables/prompt/useEvaluationContext'
|
||||
import { useFunctionModelManager } from '../../composables/model'
|
||||
import { useWorkspaceTextModelSelection } from '../../composables/workspaces/useWorkspaceTextModelSelection'
|
||||
import { useWorkspaceTemplateSelection } from '../../composables/workspaces/useWorkspaceTemplateSelection'
|
||||
import { useElementSize } from '@vueuse/core'
|
||||
@@ -576,6 +628,7 @@ import { buildMultiImageVariantFingerprint } from '../../utils/multiimage-worksp
|
||||
import { downloadImageSource } from '../../utils/image-download'
|
||||
import { getI18nErrorMessage } from '../../utils/error'
|
||||
import { withHistorySourceBindingMetadata } from '../../utils/history-source-binding'
|
||||
import { createImagePromptAnalysisVersion } from '../../utils/imagePromptAnalysis'
|
||||
import { resolveSourceAssetRef } from '../../utils/source-asset'
|
||||
import { OptionAccessors } from '../../utils/data-transformer'
|
||||
import type { VariableManagerHooks } from '../../composables/prompt/useVariableManager'
|
||||
@@ -591,6 +644,7 @@ import VariableValuePreviewDialog from '../variable/VariableValuePreviewDialog.v
|
||||
import TestPanelVersionSelect from '../TestPanelVersionSelect.vue'
|
||||
import ImageTokenUsage from './ImageTokenUsage.vue'
|
||||
import SaveTestResultExampleButton from '../SaveTestResultExampleButton.vue'
|
||||
import { EvaluationPanel } from '../evaluation'
|
||||
|
||||
const { t } = useI18n()
|
||||
const toast = useToast()
|
||||
@@ -624,6 +678,7 @@ const {
|
||||
|
||||
const fileInputRef = ref<HTMLInputElement | null>(null)
|
||||
const optimizing = ref(false)
|
||||
const isAnalyzing = ref(false)
|
||||
const isIterating = ref(false)
|
||||
const splitRootRef = ref<HTMLElement | null>(null)
|
||||
const testPaneRef = ref<HTMLElement | null>(null)
|
||||
@@ -700,6 +755,7 @@ const optimizedReasoning = computed<string>({
|
||||
const modelSelection = useWorkspaceTextModelSelection(services, session)
|
||||
const selectedTextModelKey = modelSelection.selectedTextModelKey
|
||||
const textModelOptions = modelSelection.textModelOptions
|
||||
const functionModelManager = useFunctionModelManager(services)
|
||||
|
||||
const templateSelection = useWorkspaceTemplateSelection(
|
||||
services,
|
||||
@@ -711,6 +767,98 @@ const selectedTemplate = templateSelection.selectedTemplate
|
||||
const templateOptions = templateSelection.templateOptions
|
||||
const selectedTemplateId = templateSelection.selectedTemplateId
|
||||
|
||||
const evaluationHandler = useEvaluationHandler({
|
||||
services,
|
||||
analysisOptimizedPrompt: computed(() => optimizedPrompt.value || ''),
|
||||
analysisTargetResolver: (defaultTarget) => ({
|
||||
...defaultTarget,
|
||||
referencePrompt: (originalPrompt.value || '').trim() || undefined,
|
||||
}),
|
||||
evaluationModelKey: computed(() => selectedTextModelKey.value || ''),
|
||||
resolveEvaluationModelKey: async () => {
|
||||
await functionModelManager.initialize()
|
||||
return (
|
||||
functionModelManager.evaluationModel.value ||
|
||||
selectedTextModelKey.value ||
|
||||
functionModelManager.effectiveEvaluationModel.value ||
|
||||
''
|
||||
)
|
||||
},
|
||||
functionMode: computed(() => 'image'),
|
||||
subMode: computed(() => 'multiimage'),
|
||||
persistedResults: toRef(session, 'evaluationResults'),
|
||||
})
|
||||
|
||||
provideEvaluation(evaluationHandler.evaluation)
|
||||
|
||||
const { evaluation, handleEvaluate: handleEvaluateInternal } = evaluationHandler
|
||||
const panelProps = evaluationHandler.panelProps
|
||||
|
||||
const activeEvaluationStale = computed(() => false)
|
||||
const activeEvaluationStaleMessage = computed(() => t('evaluation.stale.promptOnly'))
|
||||
const activeEvaluationDisableEvaluate = computed(() =>
|
||||
panelProps.value.currentType === 'prompt-only' &&
|
||||
!optimizedPrompt.value.trim(),
|
||||
)
|
||||
const activeEvaluationDisableReason = computed(() => '')
|
||||
|
||||
const handleAnalyzePrompt = async () => {
|
||||
const prompt = originalPrompt.value.trim()
|
||||
if (!prompt || isAnalyzing.value) return
|
||||
|
||||
isAnalyzing.value = true
|
||||
try {
|
||||
const virtualV0 = createImagePromptAnalysisVersion(
|
||||
prompt,
|
||||
'multiimageOptimize' as PromptRecordType,
|
||||
)
|
||||
currentChainId.value = ''
|
||||
currentVersions.value = [virtualV0]
|
||||
currentVersionId.value = virtualV0.id
|
||||
session.updateOptimizedResult({
|
||||
optimizedPrompt: prompt,
|
||||
reasoning: '',
|
||||
chainId: '',
|
||||
versionId: '',
|
||||
})
|
||||
evaluation.clearResult('prompt-only')
|
||||
evaluation.clearResult('prompt-iterate')
|
||||
await nextTick()
|
||||
await handleEvaluateInternal('prompt-only')
|
||||
} finally {
|
||||
isAnalyzing.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleReEvaluateActive = async () => {
|
||||
if (!evaluation.state.activeDetail) return
|
||||
await evaluationHandler.handleReEvaluate()
|
||||
}
|
||||
|
||||
const handleEvaluateActiveWithFeedback = async (payload: { feedback: string }) => {
|
||||
if (!evaluation.state.activeDetail) return
|
||||
await evaluationHandler.handleEvaluateActiveWithFeedback(payload.feedback)
|
||||
}
|
||||
|
||||
const handleClearEvaluation = () => {
|
||||
evaluation.closePanel()
|
||||
evaluation.clearAllResults()
|
||||
}
|
||||
|
||||
const handleApplyImprovement = evaluationHandler.createApplyImprovementHandler(promptPanelRef)
|
||||
|
||||
const handleApplyPatch = (payload: { operation: PatchOperation }) => {
|
||||
if (!payload.operation) return
|
||||
const current = optimizedPrompt.value || ''
|
||||
const result = applyPatchOperationsToText(current, payload.operation)
|
||||
if (!result.ok) {
|
||||
toast.warning(t('toast.warning.patchApplyFailed'))
|
||||
return
|
||||
}
|
||||
optimizedPrompt.value = result.text
|
||||
toast.success(t('evaluation.diagnose.applyFix'))
|
||||
}
|
||||
|
||||
const selectedIterateTemplate = computed<Template | null>({
|
||||
get: () => templateSelection.selectedIterateTemplate.value,
|
||||
set: (template) => {
|
||||
|
||||
@@ -455,6 +455,25 @@
|
||||
<!-- 优化按钮 -->
|
||||
<NGridItem :span="6" :xs="24" :sm="6" class="flex items-end justify-end">
|
||||
<NSpace :size="8">
|
||||
<NButton
|
||||
type="default"
|
||||
size="medium"
|
||||
data-testid="image-text2image-analyze-button"
|
||||
:loading="isAnalyzing"
|
||||
@click="handleAnalyzePrompt"
|
||||
:disabled="
|
||||
isAnalyzing ||
|
||||
isExtractingFromImage ||
|
||||
isOptimizing ||
|
||||
!originalPrompt.trim()
|
||||
"
|
||||
>
|
||||
{{
|
||||
isAnalyzing
|
||||
? t("promptOptimizer.analyzing")
|
||||
: t("promptOptimizer.analyze")
|
||||
}}
|
||||
</NButton>
|
||||
<NButton
|
||||
type="primary"
|
||||
size="medium"
|
||||
@@ -462,6 +481,7 @@
|
||||
:loading="isOptimizing"
|
||||
@click="handleOptimizePrompt"
|
||||
:disabled="
|
||||
isAnalyzing ||
|
||||
isExtractingFromImage ||
|
||||
isOptimizing ||
|
||||
!originalPrompt.trim() ||
|
||||
@@ -516,6 +536,8 @@
|
||||
@switchVersion="handleSwitchVersion"
|
||||
@save-favorite="handleSaveFavorite"
|
||||
@save-local-edit="handleSaveLocalEdit"
|
||||
@apply-improvement="handleApplyImprovement"
|
||||
@apply-patch="handleApplyPatch"
|
||||
@open-preview="handleOpenPromptPreview"
|
||||
/>
|
||||
</NCard>
|
||||
@@ -589,6 +611,8 @@
|
||||
@show-detail="showCompareDetail"
|
||||
@evaluate="handleEvaluateCompare"
|
||||
@evaluate-with-feedback="handleCompareEvaluateWithFeedback"
|
||||
@apply-improvement="handleApplyImprovement"
|
||||
@apply-patch="handleApplyPatch"
|
||||
/>
|
||||
<FocusAnalyzeButton
|
||||
v-else
|
||||
@@ -724,6 +748,8 @@
|
||||
@show-detail="() => showResultDetail(id)"
|
||||
@evaluate="() => handleEvaluateResult(id)"
|
||||
@evaluate-with-feedback="handleResultEvaluateWithFeedbackEvent(id, $event)"
|
||||
@apply-improvement="handleApplyImprovement"
|
||||
@apply-patch="handleApplyPatch"
|
||||
/>
|
||||
<FocusAnalyzeButton
|
||||
v-else
|
||||
@@ -847,6 +873,8 @@
|
||||
:disable-evaluate="activeEvaluationDisableEvaluate"
|
||||
:disable-evaluate-reason="activeEvaluationDisableReason"
|
||||
:can-rewrite-from-evaluation="false"
|
||||
@apply-local-patch="handleApplyPatch"
|
||||
@apply-improvement="handleApplyImprovement"
|
||||
@re-evaluate="handleReEvaluateActive"
|
||||
@evaluate-with-feedback="handleEvaluateActiveWithFeedback"
|
||||
@clear="handleClearEvaluation"
|
||||
@@ -938,6 +966,7 @@ import { withHistorySourceBindingMetadata } from '../../utils/history-source-bin
|
||||
import { resolveSourceAssetRef } from '../../utils/source-asset'
|
||||
import { downloadImageSource } from '../../utils/image-download'
|
||||
import { openExternalUrl } from '../../utils/open-external-url'
|
||||
import { createImagePromptAnalysisVersion } from '../../utils/imagePromptAnalysis'
|
||||
import type { PromptGardenImportRequest } from '../../utils/prompt-garden-import'
|
||||
import {
|
||||
resolveReferencePromptPreview,
|
||||
@@ -992,6 +1021,7 @@ import {
|
||||
shouldShowImageText2ImageResultAction,
|
||||
} from './imageText2ImageEvaluation'
|
||||
import {
|
||||
applyPatchOperationsToText,
|
||||
getEnvVar,
|
||||
type ContextMode,
|
||||
type ImageModelConfig,
|
||||
@@ -1000,6 +1030,7 @@ import {
|
||||
type ImageResultItem,
|
||||
type OptimizationMode,
|
||||
type OptimizationRequest,
|
||||
type PatchOperation,
|
||||
type PromptRecordChain,
|
||||
type PromptRecordType,
|
||||
type Template,
|
||||
@@ -1111,6 +1142,7 @@ const promptService = computed(() => services.value?.promptService)
|
||||
|
||||
// 过程态(本地,不持久化)
|
||||
const isOptimizing = ref(false)
|
||||
const isAnalyzing = ref(false)
|
||||
const isIterating = ref(false)
|
||||
const isExtractingFromImage = ref(false)
|
||||
const extractImageInputRef = ref<HTMLInputElement | null>(null)
|
||||
@@ -1730,6 +1762,34 @@ const panelProps = evaluationHandler.panelProps
|
||||
const getResultEvaluationProps = (variantId: string) =>
|
||||
evaluationHandler.getResultEvaluationProps(variantId)
|
||||
|
||||
const handleAnalyzePrompt = async () => {
|
||||
const prompt = originalPrompt.value.trim()
|
||||
if (!prompt || isAnalyzing.value) return
|
||||
|
||||
isAnalyzing.value = true
|
||||
try {
|
||||
const virtualV0 = createImagePromptAnalysisVersion(
|
||||
prompt,
|
||||
'text2imageOptimize' as PromptRecordType,
|
||||
)
|
||||
currentChainId.value = ''
|
||||
currentVersions.value = [virtualV0]
|
||||
currentVersionId.value = virtualV0.id
|
||||
session.updateOptimizedResult({
|
||||
optimizedPrompt: prompt,
|
||||
reasoning: '',
|
||||
chainId: '',
|
||||
versionId: '',
|
||||
})
|
||||
evaluation.clearResult('prompt-only')
|
||||
evaluation.clearResult('prompt-iterate')
|
||||
await nextTick()
|
||||
await handleEvaluateInternal('prompt-only')
|
||||
} finally {
|
||||
isAnalyzing.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const isEvaluatingCompare = evaluationHandler.compareEvaluation.isEvaluatingCompare
|
||||
const compareScore = computed(
|
||||
() => evaluationHandler.compareEvaluation.compareScore.value ?? 0,
|
||||
@@ -2254,6 +2314,20 @@ const handleSaveLocalEdit = async (payload: { note?: string }) => {
|
||||
// PromptPanel 引用,用于在语言切换后刷新迭代模板选择
|
||||
const promptPanelRef = ref<InstanceType<typeof PromptPanelUI> | null>(null);
|
||||
|
||||
const handleApplyImprovement = evaluationHandler.createApplyImprovementHandler(promptPanelRef)
|
||||
|
||||
const handleApplyPatch = (payload: { operation: PatchOperation }) => {
|
||||
if (!payload.operation) return
|
||||
const current = optimizedPrompt.value || ''
|
||||
const result = applyPatchOperationsToText(current, payload.operation)
|
||||
if (!result.ok) {
|
||||
toast.warning(t('toast.warning.patchApplyFailed'))
|
||||
return
|
||||
}
|
||||
optimizedPrompt.value = result.text
|
||||
toast.success(t('evaluation.diagnose.applyFix'))
|
||||
}
|
||||
|
||||
// 输入区折叠状态(初始展开)
|
||||
const isInputPanelCollapsed = ref(false);
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import type { PromptRecordChain, PromptRecordType } from '@prompt-optimizer/core'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
export const createImagePromptAnalysisVersion = (
|
||||
prompt: string,
|
||||
type: PromptRecordType,
|
||||
): PromptRecordChain['versions'][number] => {
|
||||
const id = uuidv4()
|
||||
|
||||
return {
|
||||
id,
|
||||
chainId: '',
|
||||
version: 0,
|
||||
originalPrompt: prompt,
|
||||
optimizedPrompt: prompt,
|
||||
type,
|
||||
timestamp: Date.now(),
|
||||
modelKey: '',
|
||||
templateId: '',
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { join } from 'node:path'
|
||||
|
||||
type ComponentRequirement = {
|
||||
component: string
|
||||
listeners: string[]
|
||||
}
|
||||
|
||||
type FileComponentRequirement = ComponentRequirement & {
|
||||
file: string
|
||||
requirePresence?: boolean
|
||||
}
|
||||
|
||||
const sourceRoot = join(process.cwd(), 'src')
|
||||
|
||||
const filesToCheck = [
|
||||
'components/PromptPanel.vue',
|
||||
'components/TestAreaPanel.vue',
|
||||
'components/TestResultSection.vue',
|
||||
'components/context-mode/ContextUserTestPanel.vue',
|
||||
'components/context-mode/ConversationTestPanel.vue',
|
||||
'components/basic-mode/BasicUserWorkspace.vue',
|
||||
'components/basic-mode/BasicSystemWorkspace.vue',
|
||||
'components/context-mode/ContextUserWorkspace.vue',
|
||||
'components/context-mode/ContextSystemWorkspace.vue',
|
||||
'components/image-mode/ImageText2ImageWorkspace.vue',
|
||||
'components/image-mode/ImageImage2ImageWorkspace.vue',
|
||||
'components/image-mode/ImageMultiImageWorkspace.vue',
|
||||
]
|
||||
|
||||
const requirements: ComponentRequirement[] = [
|
||||
{
|
||||
component: 'EvaluationPanel',
|
||||
listeners: ['@apply-local-patch', '@apply-improvement'],
|
||||
},
|
||||
{
|
||||
component: 'EvaluationScoreBadge',
|
||||
listeners: ['@apply-improvement', '@apply-patch'],
|
||||
},
|
||||
{
|
||||
component: 'PromptPanelUI',
|
||||
listeners: ['@apply-improvement', '@apply-patch'],
|
||||
},
|
||||
{
|
||||
component: 'TestResultSection',
|
||||
listeners: [
|
||||
'@evaluate-with-feedback',
|
||||
'@show-primary-detail',
|
||||
'@show-secondary-detail',
|
||||
'@apply-improvement',
|
||||
'@apply-patch',
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const workspaceFiles = [
|
||||
'components/basic-mode/BasicUserWorkspace.vue',
|
||||
'components/basic-mode/BasicSystemWorkspace.vue',
|
||||
'components/context-mode/ContextUserWorkspace.vue',
|
||||
'components/context-mode/ContextSystemWorkspace.vue',
|
||||
'components/image-mode/ImageText2ImageWorkspace.vue',
|
||||
'components/image-mode/ImageImage2ImageWorkspace.vue',
|
||||
'components/image-mode/ImageMultiImageWorkspace.vue',
|
||||
]
|
||||
|
||||
const promptPanelWorkspaceFiles = workspaceFiles
|
||||
|
||||
const textInputWorkspaceFiles = [
|
||||
'components/basic-mode/BasicUserWorkspace.vue',
|
||||
'components/basic-mode/BasicSystemWorkspace.vue',
|
||||
'components/context-mode/ContextUserWorkspace.vue',
|
||||
]
|
||||
|
||||
const promptPanelPreviewWorkspaceFiles = [
|
||||
'components/context-mode/ContextUserWorkspace.vue',
|
||||
'components/context-mode/ContextSystemWorkspace.vue',
|
||||
'components/image-mode/ImageText2ImageWorkspace.vue',
|
||||
'components/image-mode/ImageImage2ImageWorkspace.vue',
|
||||
'components/image-mode/ImageMultiImageWorkspace.vue',
|
||||
]
|
||||
|
||||
const promptPanelOriginalVersionWorkspaceFiles = [
|
||||
'components/basic-mode/BasicUserWorkspace.vue',
|
||||
'components/basic-mode/BasicSystemWorkspace.vue',
|
||||
'components/context-mode/ContextUserWorkspace.vue',
|
||||
'components/context-mode/ContextSystemWorkspace.vue',
|
||||
]
|
||||
|
||||
const buildFileRequirements = (
|
||||
files: string[],
|
||||
component: string,
|
||||
listeners: string[],
|
||||
): FileComponentRequirement[] =>
|
||||
files.map((file) => ({
|
||||
file,
|
||||
component,
|
||||
listeners,
|
||||
requirePresence: true,
|
||||
}))
|
||||
|
||||
const workspaceRequirements: FileComponentRequirement[] = [
|
||||
...buildFileRequirements(workspaceFiles, 'WorkspaceUtilityMenu', ['@clear']),
|
||||
...buildFileRequirements(textInputWorkspaceFiles, 'InputPanelUI', [
|
||||
'@submit',
|
||||
'@analyze',
|
||||
]),
|
||||
...buildFileRequirements(promptPanelWorkspaceFiles, 'PromptPanelUI', [
|
||||
'@iterate',
|
||||
'@openTemplateManager',
|
||||
'@switchVersion',
|
||||
'@save-favorite',
|
||||
'@apply-improvement',
|
||||
'@apply-patch',
|
||||
'@save-local-edit',
|
||||
]),
|
||||
...buildFileRequirements(promptPanelPreviewWorkspaceFiles, 'PromptPanelUI', [
|
||||
'@open-preview',
|
||||
]),
|
||||
...buildFileRequirements(promptPanelOriginalVersionWorkspaceFiles, 'PromptPanelUI', [
|
||||
'@switchToV0',
|
||||
]),
|
||||
]
|
||||
|
||||
const extractComponentBlocks = (source: string, component: string) => {
|
||||
const lines = source.split(/\r?\n/)
|
||||
const blocks: Array<{ line: number; text: string }> = []
|
||||
|
||||
for (let index = 0; index < lines.length; index += 1) {
|
||||
if (!lines[index]?.includes(`<${component}`)) continue
|
||||
|
||||
const start = index
|
||||
const collected: string[] = []
|
||||
for (let cursor = index; cursor < lines.length; cursor += 1) {
|
||||
const line = lines[cursor] || ''
|
||||
collected.push(line)
|
||||
if (line.includes('/>') || line.includes(`</${component}>`)) {
|
||||
index = cursor
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
blocks.push({
|
||||
line: start + 1,
|
||||
text: collected.join('\n'),
|
||||
})
|
||||
}
|
||||
|
||||
return blocks
|
||||
}
|
||||
|
||||
const collectMissingListeners = (requirementsToCheck: FileComponentRequirement[]) => {
|
||||
const missing: string[] = []
|
||||
|
||||
for (const requirement of requirementsToCheck) {
|
||||
const source = readFileSync(join(sourceRoot, requirement.file), 'utf8')
|
||||
const blocks = extractComponentBlocks(source, requirement.component)
|
||||
|
||||
if (blocks.length === 0) {
|
||||
if (requirement.requirePresence) {
|
||||
missing.push(`${requirement.file} missing <${requirement.component}>`)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
for (const block of blocks) {
|
||||
const missingListeners = requirement.listeners.filter(
|
||||
(listener) => !block.text.includes(listener),
|
||||
)
|
||||
|
||||
if (missingListeners.length) {
|
||||
missing.push(
|
||||
`${requirement.file}:${block.line} <${requirement.component}> missing ${missingListeners.join(', ')}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return missing
|
||||
}
|
||||
|
||||
describe('evaluation action wiring', () => {
|
||||
it('keeps apply action events connected at workspace boundaries', () => {
|
||||
const requirementsToCheck = filesToCheck.flatMap((file) =>
|
||||
requirements.map((requirement) => ({
|
||||
...requirement,
|
||||
file,
|
||||
})),
|
||||
)
|
||||
|
||||
const missing = collectMissingListeners(requirementsToCheck)
|
||||
|
||||
expect(missing).toEqual([])
|
||||
})
|
||||
|
||||
it('keeps shared workspace action controls wired consistently across modes', () => {
|
||||
const missing = collectMissingListeners(workspaceRequirements)
|
||||
|
||||
expect(missing).toEqual([])
|
||||
})
|
||||
})
|
||||
@@ -1,14 +1,25 @@
|
||||
import { test, expect } from '../fixtures'
|
||||
import { navigateToMode } from '../helpers/common'
|
||||
import {
|
||||
fillOriginalPrompt,
|
||||
verifyAnalyzeButtonDisabledWhenEmpty,
|
||||
} from '../helpers/analysis'
|
||||
|
||||
const MODE = 'image-image2image' as const
|
||||
|
||||
test.describe('Image Image2Image - 工作区入口', () => {
|
||||
test('单图生图工作区不提供单独的提示词分析按钮', async ({ page }) => {
|
||||
test.describe('Image Image2Image - 提示词分析入口', () => {
|
||||
test('图生图工作区提供输入区提示词分析按钮', async ({ page }) => {
|
||||
await navigateToMode(page, 'image', 'image2image')
|
||||
|
||||
const workspace = page.locator(`[data-testid="workspace"][data-mode="${MODE}"]`).first()
|
||||
await expect(workspace).toBeVisible()
|
||||
await expect(workspace.getByTestId(`${MODE}-analyze-button`)).toHaveCount(0)
|
||||
await verifyAnalyzeButtonDisabledWhenEmpty(page, MODE)
|
||||
|
||||
await fillOriginalPrompt(
|
||||
page,
|
||||
MODE,
|
||||
'Convert to watercolor painting style, soft colors, artistic brush strokes',
|
||||
)
|
||||
await expect(workspace.getByTestId(`${MODE}-analyze-button`)).toBeEnabled()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,25 @@
|
||||
import { test, expect } from '../fixtures'
|
||||
import { navigateToMode } from '../helpers/common'
|
||||
import {
|
||||
fillOriginalPrompt,
|
||||
verifyAnalyzeButtonDisabledWhenEmpty,
|
||||
} from '../helpers/analysis'
|
||||
|
||||
const MODE = 'image-multiimage' as const
|
||||
|
||||
test.describe('Image MultiImage - 提示词分析入口', () => {
|
||||
test('多图生图工作区提供输入区提示词分析按钮', async ({ page }) => {
|
||||
await navigateToMode(page, 'image', 'multiimage')
|
||||
|
||||
const workspace = page.locator(`[data-testid="workspace"][data-mode="${MODE}"]`).first()
|
||||
await expect(workspace).toBeVisible()
|
||||
await verifyAnalyzeButtonDisabledWhenEmpty(page, MODE)
|
||||
|
||||
await fillOriginalPrompt(
|
||||
page,
|
||||
MODE,
|
||||
'Combine the reference images into one cohesive editorial poster with consistent lighting',
|
||||
)
|
||||
await expect(workspace.getByTestId(`${MODE}-analyze-button`)).toBeEnabled()
|
||||
})
|
||||
})
|
||||
@@ -1,14 +1,25 @@
|
||||
import { test, expect } from '../fixtures'
|
||||
import { navigateToMode } from '../helpers/common'
|
||||
import {
|
||||
fillOriginalPrompt,
|
||||
verifyAnalyzeButtonDisabledWhenEmpty,
|
||||
} from '../helpers/analysis'
|
||||
|
||||
const MODE = 'image-text2image' as const
|
||||
|
||||
test.describe('Image Text2Image - 工作区入口', () => {
|
||||
test('文生图工作区不提供单独的提示词分析按钮', async ({ page }) => {
|
||||
test.describe('Image Text2Image - 提示词分析入口', () => {
|
||||
test('文生图工作区提供输入区提示词分析按钮', async ({ page }) => {
|
||||
await navigateToMode(page, 'image', 'text2image')
|
||||
|
||||
const workspace = page.locator(`[data-testid="workspace"][data-mode="${MODE}"]`).first()
|
||||
await expect(workspace).toBeVisible()
|
||||
await expect(workspace.getByTestId(`${MODE}-analyze-button`)).toHaveCount(0)
|
||||
await verifyAnalyzeButtonDisabledWhenEmpty(page, MODE)
|
||||
|
||||
await fillOriginalPrompt(
|
||||
page,
|
||||
MODE,
|
||||
'A beautiful sunset over the ocean with palm trees, golden hour lighting, photorealistic',
|
||||
)
|
||||
await expect(workspace.getByTestId(`${MODE}-analyze-button`)).toBeEnabled()
|
||||
})
|
||||
})
|
||||
@@ -9,6 +9,7 @@ export type WorkspaceMode =
|
||||
| 'basic-user'
|
||||
| 'image-text2image'
|
||||
| 'image-image2image'
|
||||
| 'image-multiimage'
|
||||
| 'pro-multi'
|
||||
| 'pro-variable'
|
||||
|
||||
|
||||
Reference in new issue
Block a user