Files
prompt-optimizer/packages/ui/src/components/context-mode/ContextUserWorkspace.vue
linshen 947ede8a1d feat(ui): 实现三大功能模式的独立子模式持久化功能
- 新增三个子模式管理Composable (useBasicSubMode/useProSubMode/useImageSubMode)
- 实现基础/上下文/图像模式的完全独立状态存储
- 添加UI_SETTINGS_KEYS常量用于子模式存储键管理
- 更新App.vue初始化逻辑支持三模式独立恢复
- 修复图像模式刷新后文件上传按钮不显示的bug
- 完善历史记录和收藏恢复时的子模式持久化
- 新增国际化文本支持子模式切换提示
- 归档完整开发文档到126-submode-persistence

核心特性:
- 状态隔离: 三个功能模式维护完全独立的子模式状态
- 跨页面同步: 使用自定义事件实现组件间状态同步
- 双层状态一致性: 导航层和组件层状态保持同步
- 异步初始化: 非阻塞式状态恢复机制
2025-10-25 10:31:14 +08:00

352 lines
13 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<NFlex
justify="space-between"
:style="{
display: 'flex',
flexDirection: 'row',
width: '100%',
'max-height': '100%',
gap: '16px',
}"
>
<!-- 左侧优化区域 -->
<NFlex
vertical
:style="{
flex: 1,
overflow: 'auto',
height: '100%',
}"
>
<!-- 上下文模式快捷操作 (用户模式全局变量 + 会话变量 + 工具管理) -->
<NCard :style="{ flexShrink: 0 }" size="small">
<NFlex align="center" :wrap="false" :size="12">
<NButton
size="small"
type="default"
@click="emit('open-global-variables')"
:title="t('contextMode.actions.globalVariables')"
>
<template #icon>
<span>📊</span>
</template>
{{ t("contextMode.actions.globalVariables") }}
</NButton>
<NButton
size="small"
type="default"
@click="emit('open-context-variables')"
:title="t('contextMode.actions.contextVariables')"
>
<template #icon>
<span>📝</span>
</template>
{{ t("contextMode.actions.contextVariables") }}
</NButton>
<NButton
size="small"
type="default"
@click="emit('open-tool-manager')"
:title="t('contextMode.actions.tools')"
>
<template #icon>
<span>🔧</span>
</template>
{{ t("contextMode.actions.tools") }}
</NButton>
</NFlex>
</NCard>
<!-- 提示词输入面板 -->
<NCard
:style="{
flexShrink: 0,
minHeight: '200px',
}"
>
<InputPanelUI
:modelValue="prompt"
@update:modelValue="emit('update:prompt', $event)"
:label="t('promptOptimizer.userPromptInput')"
:placeholder="t('promptOptimizer.userPromptPlaceholder')"
:model-label="t('promptOptimizer.optimizeModel')"
:template-label="t('promptOptimizer.templateLabel')"
:button-text="t('promptOptimizer.optimize')"
:loading-text="t('common.loading')"
:loading="isOptimizing"
:disabled="isOptimizing"
:show-preview="true"
@submit="emit('optimize')"
@configModel="emit('config-model')"
@open-preview="emit('open-input-preview')"
>
<template #model-select>
<slot name="optimize-model-select"></slot>
</template>
<template #template-select>
<slot name="template-select"></slot>
</template>
</InputPanelUI>
</NCard>
<!-- 用户模式没有会话管理器 -->
<!-- 优化结果面板 -->
<NCard
:style="{
flex: 1,
minHeight: '200px',
overflow: 'hidden',
}"
content-style="height: 100%; max-height: 100%; overflow: hidden;"
>
<PromptPanelUI
:optimized-prompt="optimizedPrompt"
@update:optimizedPrompt="
emit('update:optimizedPrompt', $event)
"
:reasoning="optimizedReasoning"
:original-prompt="prompt"
:is-optimizing="isOptimizing"
:is-iterating="isIterating"
:selectedIterateTemplate="selectedIterateTemplate"
@update:selectedIterateTemplate="
emit('update:selectedIterateTemplate', $event)
"
:versions="versions"
:current-version-id="currentVersionId"
:optimization-mode="optimizationMode"
:services="services"
:advanced-mode-enabled="true"
:show-preview="true"
@iterate="emit('iterate', $event)"
@openTemplateManager="emit('open-template-manager', $event)"
@switchVersion="emit('switch-version', $event)"
@save-favorite="emit('save-favorite', $event)"
@open-preview="emit('open-prompt-preview')"
/>
</NCard>
</NFlex>
<!-- 右侧测试区域 -->
<NFlex
vertical
:style="{
flex: 1,
overflow: 'auto',
height: '100%',
gap: '12px',
}"
>
<!-- 测试区域操作栏 -->
<NCard size="small" :style="{ flexShrink: 0 }">
<NFlex justify="space-between" align="center">
<!-- 左侧区域标识 -->
<NFlex align="center" :size="8">
<NText strong>{{ $t("test.areaTitle") }}</NText>
<NTag type="info" size="small">
<template #icon><span>👤</span></template>
{{ $t("contextMode.user.label") }}
</NTag>
</NFlex>
<!-- 右侧快捷操作按钮 -->
<NFlex :size="8">
<NButton
size="small"
quaternary
@click="emit('open-global-variables')"
:title="$t('contextMode.actions.globalVariables')"
>
<template #icon><span>📊</span></template>
<span v-if="!isMobile">{{
$t("contextMode.actions.globalVariables")
}}</span>
</NButton>
<NButton
size="small"
quaternary
@click="emit('open-context-variables')"
:title="$t('contextMode.actions.contextVariables')"
>
<template #icon><span>📝</span></template>
<span v-if="!isMobile">{{
$t("contextMode.actions.contextVariables")
}}</span>
</NButton>
<NButton
size="small"
quaternary
@click="emit('open-tool-manager')"
:title="$t('contextMode.actions.toolManager')"
>
<template #icon><span>🔧</span></template>
<span v-if="!isMobile">{{
$t("contextMode.actions.toolManager")
}}</span>
</NButton>
</NFlex>
</NFlex>
</NCard>
<!-- 测试区域主内容 -->
<NCard
:style="{ flex: 1, overflow: 'auto' }"
content-style="height: 100%; max-height: 100%; overflow: hidden;"
>
<TestAreaPanel
:optimization-mode="optimizationMode"
context-mode="user"
:optimized-prompt="optimizedPrompt"
:is-test-running="isTestRunning"
:global-variables="globalVariables"
:context-variables="contextVariables"
:predefined-variables="predefinedVariables"
:testContent="testContent"
@update:testContent="emit('update:testContent', $event)"
:isCompareMode="isCompareMode"
@update:isCompareMode="emit('update:isCompareMode', $event)"
:enable-compare-mode="true"
:enable-fullscreen="true"
:input-mode="inputMode"
:control-bar-layout="controlBarLayout"
:button-size="buttonSize"
:conversation-max-height="conversationMaxHeight"
:show-original-result="true"
:result-vertical-layout="resultVerticalLayout"
@test="emit('test')"
@compare-toggle="emit('compare-toggle')"
@open-variable-manager="emit('open-variable-manager')"
@open-preview="emit('open-test-preview')"
@variable-change="
emit('variable-change', $event[0], $event[1])
"
>
<!-- 模型选择插槽 -->
<template #model-select>
<slot name="test-model-select"></slot>
</template>
<!-- 结果显示插槽 -->
<template #original-result>
<slot name="original-result"></slot>
</template>
<template #optimized-result>
<slot name="optimized-result"></slot>
</template>
<template #single-result>
<slot name="single-result"></slot>
</template>
</TestAreaPanel>
</NCard>
</NFlex>
</NFlex>
</template>
<script setup lang="ts">
import { computed } from "vue";
import { useI18n } from "vue-i18n";
import { NCard, NFlex, NButton, NText, NTag } from "naive-ui";
import { useBreakpoints } from "@vueuse/core";
import InputPanelUI from "../InputPanel.vue";
import PromptPanelUI from "../PromptPanel.vue";
import TestAreaPanel from "../TestAreaPanel.vue";
import type { OptimizationMode } from "../../types";
import type { IServices } from "@prompt-optimizer/core";
// 响应式断点
const breakpoints = useBreakpoints({
mobile: 640,
tablet: 1024,
});
const isMobile = breakpoints.smaller("mobile");
// Props 定义 (移除 contextMode 和 会话管理器相关的 props)
interface Props {
// 核心状态
prompt: string;
optimizedPrompt: string;
optimizedReasoning?: string;
optimizationMode: OptimizationMode;
// 优化状态
isOptimizing: boolean;
isIterating: boolean;
isTestRunning?: boolean;
// 版本管理
versions: any[];
currentVersionId: string | null;
selectedIterateTemplate: any;
// 测试数据
testContent: string;
isCompareMode: boolean;
// 变量数据
globalVariables: Record<string, string>;
contextVariables: Record<string, string>;
predefinedVariables: Record<string, string>;
// 服务
services: IServices | null;
// 响应式布局配置
inputMode?: "compact" | "normal";
controlBarLayout?: "default" | "compact" | "minimal";
buttonSize?: "small" | "medium" | "large";
conversationMaxHeight?: number;
resultVerticalLayout?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
optimizedReasoning: "",
isTestRunning: false,
inputMode: "normal",
controlBarLayout: "default",
buttonSize: "medium",
conversationMaxHeight: 300,
resultVerticalLayout: false,
});
// Emits 定义 (移除会话管理器相关的 emits)
const emit = defineEmits<{
// 数据更新
"update:prompt": [value: string];
"update:optimizedPrompt": [value: string];
"update:selectedIterateTemplate": [value: any];
"update:testContent": [value: string];
"update:isCompareMode": [value: boolean];
// 操作事件
optimize: [];
iterate: [payload: any];
test: [];
"compare-toggle": [];
"switch-version": [versionId: any];
"save-favorite": [data: any];
// 打开面板/管理器
"open-global-variables": [];
"open-context-variables": [];
"open-tool-manager": [];
"open-variable-manager": [];
"open-template-manager": [type?: string];
"config-model": [];
// 预览相关
"open-input-preview": [];
"open-prompt-preview": [];
"open-test-preview": [];
// 变量管理
"variable-change": [name: string, value: string];
}>();
const { t } = useI18n();
</script>