diff --git a/docs/workspace/ui-design-analysis.md b/docs/workspace/ui-design-analysis.md
new file mode 100644
index 00000000..c290bc29
--- /dev/null
+++ b/docs/workspace/ui-design-analysis.md
@@ -0,0 +1,1789 @@
+# ð± äžäžææš¡åŒéæ - UI è®Ÿè®¡åææ¥å
+
+> **ææ¡£çæ¬**: v2.0
+> **åå»ºæ¥æ**: 2025-10-21
+> **æåæŽæ°**: 2025-10-22
+> **åæèåŽ**: äžäžææš¡åŒ (User/System) UI ç»ä»¶è®Ÿè®¡ + åéç³»ç»éæ
+> **ç¶æ**: â
v1.1æ¹é 已宿ïŒð v2.0åéç³»ç»éæåŸ
宿œ
+
+---
+
+## ð¢ éèŠæŽæ°
+
+### â
v1.1 UIæ¹é (已宿 - 2025-10-22)
+
+1. **åæš¡åŒéæ©åšäœçœ®è°æŽ** â
已宿
+ - **é®é¢**: åæš¡åŒéæ©åšäœäºå·¥äœåºå
ïŒå±çº§æ··ä¹±
+ - **æ¹æ¡**: ç§»åšå°å¯Œèªæ ïŒçާé»åèœæš¡åŒéæ©åšå³äŸ§
+ - **ç¶æ**: â
已宿
+ - **æäº€**: ä¹å已宿
+
+2. **å¿«æ·æäœæ äœçœ®è°æŽ** â
已宿
+ - **é®é¢**: å¿«æ·æäœæ åšå·ŠäŸ§äŒååºïŒäœçšåäžæç¡®
+ - **æ¹æ¡**: ç§»åšå°å³äŸ§æµè¯åºé¡¶éšïŒèŽŽè¿äœ¿çšåºæ¯
+ - **ç¶æ**: â
已宿
+ - **æäº€**: `ce90d47` - refactor(ui): äŒåäžäžææš¡åŒå¿«æ·æäœæ äœçœ®
+
+### ð v2.0 åéç³»ç»éæ (åŸ
宿œ)
+
+3. **åéç³»ç»ç®å** ðŽ é«äŒå
级
+ - **é®é¢**: "å
šå±åé"å"äŒè¯åé"æŠå¿µæ··æ·ïŒå®é
éœæ¯æä¹
ååé
+ - **æ¹æ¡**: ç§»é€äŒè¯åéïŒåŒå
¥æµè¯åºäžŽæ¶åé
+ - **ç¶æ**: ð åŸ
宿œ
+ - **诊æ
**: è§ [åéç³»ç»éæè®Ÿè®¡ææ¡£](./variable-system-redesign.md)
+
+### ð æ¹é 颿ææ
+
+**æ¹é å:**
+```
+ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+â Prompt Optimizer | [åºç¡|äžäžæ|åŸå] | ððâïž... â
+ââââââââââââââââââââââââââ¬ââââââââââââââââââââââââââââââââ€
+â [ððð§] â æµè¯åº â
+â ââââââââââââââââââââââ â â
+â [ç³»ç»|çšæ·] [æš¡åâŸ] â â
+â èŸå
¥æ¡... â â
+ââââââââââââââââââââââââââŽââââââââââââââââââââââââââââââââ
+ â é®é¢1: å±çº§æ··ä¹± â é®é¢2: æäœè·çŠ»è¿
+```
+
+**æ¹é å:**
+```
+ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+â Prompt Optimizer â
+â [åºç¡|äžäžæ|åŸå] [ç³»ç»|çšæ·] ððâïž... â
+â â åèœæš¡åŒ â åæš¡åŒïŒåšææŸç€ºïŒ â
+ââââââââââââââââââââââââââ¬ââââââââââââââââââââââââââââââââ€
+â äŒååº â æµè¯åº â
+â â âââââââââââââââââââââââââââââ â
+â [æš¡åâŸ] [æš¡æ¿âŸ] â â æµè¯ ðå
šå± ðäŒè¯ ð§å·¥å
· â â
+â èŸå
¥æ¡... â âââââââââââââââââââââââââââââ â
+â (空éŽå¢å ) â åéèŸå
¥... â
+ââââââââââââââââââââââââââŽââââââââââââââââââââââââââââââââ
+```
+
+**æ¹è¿ææ:**
+- â
å±çº§æž
æ°ïŒåèœæš¡åŒååæš¡åŒåšåäžå¯Œèªæ
+- â
äœçšåæç¡®ïŒå¿«æ·æäœæ åšæµè¯åºïŒèŽŽè¿äœ¿çšåºæ¯
+- â
空éŽäŒåïŒäŒååºåçŽç©ºéŽå¢å
+- â
æäœäŸ¿æ·ïŒæµè¯æ¶è®Ÿçœ®åéïŒæäœè·¯åŸæç
+
+---
+
+## äžãæŽäœæ¶æè®Ÿè®¡
+
+### 1.1 ç»ä»¶å±æ¬¡ç»æ
+
+```
+App.vue (äž»åºçš)
+âââ ContextUserWorkspace.vue (çšæ·æš¡åŒå·¥äœåº)
+â âââ ContextModeActions (å¿«æ·æäœæé®)
+â âââ InputPanelUI (æç€ºè¯èŸå
¥)
+â âââ PromptPanelUI (äŒåç»æ)
+â âââ TestAreaPanel (æµè¯åºå)
+â
+âââ ContextSystemWorkspace.vue (ç³»ç»æš¡åŒå·¥äœåº)
+ âââ ContextModeActions (å¿«æ·æäœæé®)
+ âââ InputPanelUI (æç€ºè¯èŸå
¥)
+ âââ ConversationManager (äŒè¯ç®¡çåš) â ç³»ç»æš¡åŒäžå±
+ âââ PromptPanelUI (äŒåç»æ)
+ âââ TestAreaPanel (æµè¯åºå)
+
+å
±äº«ç»ä»¶:
+âââ ContextEditor.vue (äžäžæçŒèŸåš - æš¡ææ¡)
+âââ PromptPreviewPanel.vue (é¢è§é¢æ¿ - æš¡ææ¡)
+âââ TestAreaPanel.vue (æµè¯åºå - å€çš)
+```
+
+**ç»ä»¶æä»¶äœçœ®:**
+```
+packages/ui/src/components/
+âââ context-mode/
+â âââ ContextUserWorkspace.vue
+â âââ ContextSystemWorkspace.vue
+â âââ ContextModeActions.vue
+â âââ ContextEditor.vue
+â âââ ConversationManager.vue
+âââ PromptPreviewPanel.vue
+âââ TestAreaPanel.vue
+âââ InputPanel.vue
+âââ PromptPanel.vue
+
+packages/ui/src/composables/
+âââ usePromptPreview.ts
+```
+
+### 1.2 讟计暡åŒ
+
+â
**éçšäºäŒç§ç讟计暡åŒ:**
+
+1. **ç»åäŒäºç»§æ¿**
+ - User/System Workspace æ¯ç¬ç«ç»ä»¶èéç»§æ¿
+ - é¿å
äºå€æç if-else æ¡ä»¶å€æ
+
+2. **Props ååæ°æ®æµ**
+ - æææ°æ®éè¿ props äŒ å
¥
+ - éè¿ emit è§Šåç¶ç»ä»¶æŽæ°
+ - éµåŸª Vue 3 æäœ³å®è·µ
+
+3. **Composable é»èŸå€çš**
+ - `usePromptPreview` æäŸå¯å€çšçé¢è§é»èŸ
+ - å犻 UI åäžå¡é»èŸ
+
+4. **Slot ææ§œæ©å±**
+ - æš¡åéæ©ãç»ææŸç€ºçäœ¿çšææ§œå®ç°çµæŽ»æ§
+ - æ¯æäžååºæ¯çèªå®ä¹æž²æ
+
+---
+
+## äºãäž€ç§æš¡åŒç UI å·®åŒ
+
+### 2.1 çšæ·æš¡åŒ (User Mode)
+
+**æä»¶**: `packages/ui/src/components/context-mode/ContextUserWorkspace.vue`
+
+**åžå±ç»æ:**
+```
+ââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+â ð å
šå±åé ð äŒè¯åé ð§ å·¥å
·ç®¡ç â â å¿«æ·æäœ
+ââââââââââââââââââââââââââââââââââââââââââââââââââââââââ€
+â 巊䟧äŒååº â å³äŸ§æµè¯åº â
+â âââââââââââââââââââââââ â âââââââââââââââââââââââââ
+â â æç€ºè¯èŸå
¥é¢æ¿ â â â åéåŒèŸå
¥è¡šå ââ
+â â "åäžéŠ{{style}}çæ"â â â style: [欢快____] ââ
+â âââââââââââââââââââââââ â âââââââââââââââââââââââââ
+â âââââââââââââââââââââââ â âââââââââââââââââââââââââ
+â â äŒåç»æé¢æ¿ â â â é¢è§å
容 ââ
+â â "请åäœäžéŠé£æ Œäžº â â â "请åäœäžéŠé£æ Œäžº ââ
+â â {{style}}çæ..." â â â æ¬¢å¿«çæ..." ââ
+â âââââââââââââââââââââââ â âââââââââââââââââââââââââ
+â â âââââââââââââââââââââââââ
+â â â æµè¯ç»æ ââ
+â â â (LLM ååº) ââ
+â â âââââââââââââââââââââââââ
+ââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+```
+
+**æ žå¿ç¹ç¹:**
+- â **éè** äŒè¯æ¶æ¯å衚管ç
+- â
**æŸç€º** å·¥å
·ç®¡çæé®
+- â
åæ¡çšæ·æ¶æ¯äŒå
+- â
äž€é¶æ®µåéå€çïŒäŒåä¿ç â æµè¯æ¿æ¢ïŒ
+- â
æ éèŸå
¥æµè¯é®é¢ïŒæç€ºè¯å³æµè¯å
容ïŒ
+
+**代ç ç¹åŸ:**
+```vue
+
+
+
+
+
+
+
+ ð
+ ð
+ ð§
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+### 2.2 ç³»ç»æš¡åŒ (System Mode)
+
+**æä»¶**: `packages/ui/src/components/context-mode/ContextSystemWorkspace.vue`
+
+**åžå±ç»æ:**
+```
+ââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+â ð å
šå±åé ð äŒè¯åé â â å¿«æ·æäœ
+ââââââââââââââââââââââââââââââââââââââââââââââââââââââââ€
+â 巊䟧äŒååº â å³äŸ§æµè¯åº â
+â âââââââââââââââââââââââ â âââââââââââââââââââââââââ
+â â æç€ºè¯èŸå
¥é¢æ¿ â â â åéåŒèŸå
¥è¡šå ââ
+â â "äŒå以äžå¯¹è¯..." â â â style: [欢快____] ââ
+â âââââââââââââââââââââââ â âââââââââââââââââââââââââ
+â âââââââââââââââââââââââ â âââââââââââââââââââââââââ
+â â äŒè¯ç®¡çåš [æå ] â â â æµè¯èŸå
¥ (çšæ·é®é¢) ââ
+â â ⢠system: äœ æ¯... â â â "请çæäžéŠæ¬¢å¿«çæ" ââ
+â â ⢠user: {{style}} â â âââââââââââââââââââââââââ
+â â ⢠assistant: ... â â âââââââââââââââââââââââââ
+â â [æåŒäžäžæçŒèŸåš] â â â é¢è§å
容 ââ
+â âââââââââââââââââââââââ â â system: äœ æ¯ææ²åäœ ââ
+â âââââââââââââââââââââââ â â user: 欢快 ââ
+â â äŒåç»æé¢æ¿ â â âââââââââââââââââââââââââ
+â â (äŒååç对è¯äžäžæ) â â âââââââââââââââââââââââââ
+â âââââââââââââââââââââââ â â æµè¯ç»æ ââ
+â â â (LLM ååº) ââ
+â â âââââââââââââââââââââââââ
+ââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+```
+
+**æ žå¿ç¹ç¹:**
+- â
**æŸç€º** äŒè¯æ¶æ¯ç®¡çåšïŒå¯æå ïŒ
+- â **éè** å·¥å
·ç®¡çæé®ïŒç³»ç»æš¡åŒäžçŽæ¥ç®¡çå·¥å
·ïŒ
+- â
倿¶æ¯äžäžæçŒèŸ
+- â
éèŠé¢å€çæµè¯èŸå
¥ (`userQuestion`)
+- â
æ¯æ system/user/assistant/tool å€ç§è§è²
+
+**代ç ç¹åŸ:**
+```vue
+
+
+
+
+
+
+
+ ð
+ ð
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+---
+
+## äžãå
³é®ç»ä»¶æ·±åºŠåæ
+
+### 3.1 `ContextModeActions.vue` - å¿«æ·æäœæ
+
+**æä»¶**: `packages/ui/src/components/context-mode/ContextModeActions.vue`
+
+**讟计亮ç¹:**
+```vue
+
+
+
+
+ ð
+ {{ $t('contextMode.actions.globalVariables') }}
+
+
+
+
+ ð
+ {{ $t('contextMode.actions.contextVariables') }}
+
+
+
+
+ ð§
+ {{ $t('contextMode.actions.tools') }}
+
+
+
+
+
+```
+
+**äŒç¹:**
+- â
ç®æŽçæ¡ä»¶æž²æ (`v-if="contextMode === 'user'"`)
+- â
è¯ä¹åç emoji åŸæ
+- â
åœé
忝æ (`$t()`)
+- â
ç±»åå®å
šç emit å®ä¹
+
+**â ïž åœåé®é¢:**
+
+**é®é¢**: è®Ÿè®¡ææ¡£äžæå°"å·¥å
·ç®¡ç - äž€ç§æš¡åŒéœæŸç€º"ïŒäœå®é
ä»£ç æ¯ `v-if="contextMode === 'user'"`
+
+**圱å**: ç³»ç»æš¡åŒæ æ³ç®¡çå·¥å
·ïŒåŠæéèŠçè¯ïŒ
+
+**建议**:
+1. ç»äžè®Ÿè®¡ææ¡£åå®ç°ïŒæç¡®ç³»ç»æš¡åŒæ¯åŠéèŠå·¥å
·ç®¡ç
+2. åŠæç³»ç»æš¡åŒä¹éèŠïŒç§»é€ `v-if` æ¡ä»¶
+3. åŠæç¡®å®åªæçšæ·æš¡åŒéèŠïŒæŽæ°è®Ÿè®¡ææ¡£
+
+---
+
+### 3.2 `ConversationManager.vue` - äŒè¯ç®¡çåš
+
+**æä»¶**: `packages/ui/src/components/context-mode/ConversationManager.vue`
+
+**讟计亮ç¹:**
+
+#### 1ïžâ£ **æ§èœäŒå**
+```typescript
+// 䜿çšé²æåå°é¢ç¹æŽæ°
+const handleMessageUpdate = debounce(
+ (index: number, message: ConversationMessage) => {
+ const newMessages = [...props.messages];
+ newMessages[index] = message;
+ emit('update:messages', newMessages);
+ emit('messageChange', index, message, 'update');
+ recordUpdate();
+ },
+ 150 // 150ms 平衡ååºæ§åæ§èœ
+);
+
+// æ¹å€çç¶ææŽæ°
+const batchStateUpdate = batchExecute((updates: Array<() => void>) => {
+ updates.forEach((update) => update());
+ recordUpdate();
+}, 16); // 16ms å¹é
60fps
+```
+
+#### 2ïžâ£ **æš¡åŒåè¡äžº**
+```typescript
+const canEditMessages = computed(() => {
+ // readonly äŒå
级æé«
+ if (props.readonly) return false;
+
+ // çšæ·æš¡åŒäžå
讞çŒèŸæ¶æ¯
+ if (props.contextMode === 'user') return false;
+
+ // ç³»ç»æš¡åŒå
讞çŒèŸ
+ return true;
+});
+```
+
+#### 3ïžâ£ **玧ååžå±è®Ÿè®¡**
+```vue
+
+
+
+
+ {{ $t(`conversation.roles.${message.role}`) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+**äŒç¹:**
+- â
åè¡åžå±èç空éŽ
+- â
Hover æŸç€ºæäœæé®åå°è§è§åªé³
+- â
äžæèåæ·»å æ¶æ¯æ¯æå€ç§è§è² (system/user/assistant/tool)
+- â
æ§èœäŒåå°äœïŒé²æ + æ¹å€çïŒ
+
+**â ïž æœåšé®é¢:**
+
+**é®é¢**: åè¡èŸå
¥éå¶ (`autosize: { minRows: 1, maxRows: 1 }`) å¯èœå¯ŒèŽé¿ææ¬éŸä»¥çŒèŸ
+
+**åºæ¯ç€ºäŸ:**
+```
+åœæ¶æ¯å
容èŸé¿æ¶:
+system: "äœ æ¯äžäžªäžäžçææ²åäœå©æïŒæ
é¿åäœåç§é£æ Œçææ²ïŒå
æ¬æµè¡ãææ»ãæ°è°£ã诎å±ç..."
+```
+
+åè¡æŸç€ºäŒå¯ŒèŽ:
+- â å
å®¹è¢«æªæïŒéèŠæšªåæ»åš
+- â éŸä»¥çå°å®æŽäžäžæ
+- â çŒèŸäœéªå·®
+
+**æ¹è¿å»ºè®®:**
+
+#### æ¹æ¡ 1: å±åŒ/æå åèœ
+```vue
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+#### æ¹æ¡ 2: çŽæ¥è·³èœ¬å°å®æŽçŒèŸåš
+```vue
+
+
+
+
+ {{ $t('conversation.management.openEditor') }}
+
+```
+
+---
+
+### 3.3 `PromptPreviewPanel.vue` - 宿¶é¢è§é¢æ¿
+
+**æä»¶**: `packages/ui/src/components/PromptPreviewPanel.vue`
+
+**讟计亮ç¹:**
+
+#### 1ïžâ£ **åéç»è®¡å¯è§å**
+```vue
+
+
+
+ {{ $t('contextMode.preview.totalVars') }}: {{ variableStats.total }}
+
+
+ {{ $t('contextMode.preview.providedVars') }}: {{ variableStats.provided }}
+
+
+ {{ $t('contextMode.preview.missingVars') }}: {{ variableStats.missing }}
+
+
+
+```
+
+#### 2ïžâ£ **猺倱åéé«äº®**
+```vue
+
+
+
+
+
+
+
+
+ {{ $t('contextMode.preview.missingVarsHint') }}
+
+
+
+```
+
+#### 3ïžâ£ **æš¡åŒè¯Žæåšææç€º**
+```vue
+
+
+
+ {{ $t('contextMode.preview.userOptimizeHint') }}
+
+
+
+ {{ $t('contextMode.preview.systemOptimizeHint') }}
+
+
+
+ {{ $t('contextMode.preview.testPhaseHint') }}
+
+
+
+
+```
+
+**äŒç¹:**
+- â
ä¿¡æ¯å±æ¬¡æž
æ°ïŒç»è®¡ â èŠå â å
容 â 诎æïŒ
+- â
é¢è²è¯ä¹åïŒinfo/success/warningïŒ
+- â
æè²çšæ·çè§£äž€é¶æ®µå€ç
+- â
ååºåŒåžå± (`:wrap="true"`)
+
+**ð¡ æ¹è¿å»ºè®®: 猺倱åéå¿«éæäœ**
+
+**åœåè¡äžº:**
+```vue
+
+
+ {{{{ varName }}}}
+
+```
+
+**æ¹è¿å:**
+```vue
+
+
+
+
+
+
+```
+
+**çšæ·äœéªæå:**
+```
+ä¹å: çå°çŒºå€±åé â å
³éé¢è§ â æåšæåŒåé管çåš â æŸå°åé â çŒèŸ
+ç°åš: çå°çŒºå€±åé â ç¹å» â çŽæ¥å建/çŒèŸ â
+```
+
+---
+
+### 3.4 `usePromptPreview.ts` - é¢è§é»èŸ
+
+**æä»¶**: `packages/ui/src/composables/usePromptPreview.ts`
+
+**讟计亮ç¹:**
+
+#### 1ïžâ£ **ç®åçåéæ¿æ¢**
+```typescript
+/**
+ * æž²æåçé¢è§å
容
+ *
+ * ç®åçæ¬ïŒç»äžäœ¿çšç®åæ¿æ¢é»èŸ
+ * 泚æïŒè¿é䜿çšç®åçæ£åæ¿æ¢èäžæ¯ MustacheïŒå 䞺ïŒ
+ * 1. UI é¢è§äžéèŠ Mustache çæ¡ä»¶æž²æçé«çº§ç¹æ§
+ * 2. ç®åæ¿æ¢æ§èœæŽå¥œïŒéå宿¶é¢è§
+ * 3. äžå端 Mustache è¡äžºäžèŽïŒéœäŒä¿çåŒäžçå äœç¬ŠïŒ
+ */
+const previewContent = computed(() => {
+ if (!content.value) return "";
+
+ try {
+ const vars = variables.value || {};
+
+ // ç»äžçåéæ¿æ¢é»èŸ
+ const result = content.value.replace(
+ /\{\{([^{}]+)\}\}/g,
+ (match, varName) => {
+ const trimmedName = varName.trim();
+
+ // è·³è¿ Mustache ç¹æ®æ çŸ (#, /, ^, !, >, &)
+ if (
+ trimmedName.startsWith("#") ||
+ trimmedName.startsWith("/") ||
+ trimmedName.startsWith("^") ||
+ trimmedName.startsWith("!") ||
+ trimmedName.startsWith(">") ||
+ trimmedName.startsWith("&")
+ ) {
+ return match;
+ }
+
+ // åŠæåéååšäžéç©ºïŒæ¿æ¢ïŒåŠåä¿çå äœç¬Š
+ if (vars[trimmedName] !== undefined && vars[trimmedName] !== "") {
+ return vars[trimmedName];
+ }
+ return match;
+ }
+ );
+
+ return result;
+ } catch (error) {
+ console.error("[usePromptPreview] Preview rendering failed:", error);
+ return content.value;
+ }
+});
+```
+
+#### 2ïžâ£ **åéç»è®¡**
+```typescript
+const variableStats = computed(() => ({
+ total: parsedVariables.value.allVars.size,
+ builtin: parsedVariables.value.builtinVars.size,
+ custom: parsedVariables.value.customVars.size,
+ missing: missingVariables.value.length,
+ provided: parsedVariables.value.allVars.size - missingVariables.value.length,
+}));
+```
+
+**äŒç¹:**
+- â
æ§èœäŒäº MustacheïŒé¢è§åºæ¯è¶³å€ïŒ
+- â
äžå端è¡äžºäžèŽïŒéœä¿çåŒäžçå äœç¬ŠïŒ
+- â
è·³è¿ Mustache ç¹æ®æ çŸ
+- â
é误å€çå®å
+
+**â ïž æœåšé®é¢:**
+
+**é®é¢**: äžå端 Mustache å¯èœååšäžäžèŽçé£é©
+
+**åºæ¯**: åŠææš¡æ¿äœ¿çšäº Mustache çé«çº§ç¹æ§ïŒé¢è§å¯èœäžåç¡®
+
+```mustache
+{{! 泚é }}
+{{#if showTitle}}
+ æ é¢ïŒ{{title}}
+{{/if}}
+
+{{#each items}}
+ - {{name}}: {{value}}
+{{/each}}
+```
+
+åœåç®åæ£åæ¿æ¢æ æ³å€ç:
+- â æ¡ä»¶æž²æ (`{{#if}}...{{/if}}`)
+- â åŸªç¯æž²æ (`{{#each}}...{{/each}}`)
+- â éšåæž²æ (`{{>partial}}`)
+
+**æ¹è¿å»ºè®®:**
+
+#### æ¹æ¡ 1: ææ¡£è¯Žæéå¶
+```typescript
+/**
+ * æç€ºè¯é¢è§ Composable
+ *
+ * çšäºå®æ¶è®¡ç®æç€ºè¯æž²æç»æå¹¶æ£æµçŒºå€±åé
+ *
+ * â ïž éå¶è¯Žæ:
+ * - 䜿çšç®åæ£åæ¿æ¢ïŒäžæ¯æ Mustache é«çº§ç¹æ§
+ * - äžæ¯ææ¡ä»¶æž²æ ({{#if}})ãåŸªç¯ ({{#each}})ãéšåæš¡æ¿ ({{>}})
+ * - ä»
çšäºåºæ¬åéé¢è§ïŒæç»æž²æä»¥å端䞺å
+ * - åŠéå®æŽ Mustache æž²æïŒè¯·äœ¿çšå端 API
+ */
+```
+
+#### æ¹æ¡ 2: éæ Mustache.js
+```typescript
+import Mustache from 'mustache';
+
+const previewContent = computed(() => {
+ try {
+ // äœ¿çš Mustache 宿޿ž²æ
+ return Mustache.render(content.value, variables.value);
+ } catch (error) {
+ // é级å°ç®åæ¿æ¢
+ return content.value.replace(/\{\{([^{}]+)\}\}/g, ...);
+ }
+});
+```
+
+**æè¡¡:**
+- **æ¹æ¡ 1**: ç®åïŒäœåèœæéïŒéèŠçšæ·çè§£éå¶
+- **æ¹æ¡ 2**: åèœå®æŽïŒäœå¢å äŸèµåå€æåºŠ
+
+**建议**: åœåæ¹æ¡ 1 è¶³å€ïŒåšææ¡£äžæç¡®è¯Žæå³å¯
+
+---
+
+## åãUI äº€äºæµçšåæ
+
+### 4.1 çšæ·æš¡åŒå®æŽæµçš
+
+```mermaid
+graph TD
+ A[çšæ·èŸå
¥æç€ºè¯
'åäžéŠ{{style}}çæ'] --> B{ç¹å»äŒå}
+ B --> C[AI äŒå
ä¿ç {{style}} å äœç¬Š]
+ C --> D[äŒåç»ææŸç€º
'请åäœäžéŠé£æ Œäžº{{style}}çæ...']
+ D --> E[çšæ·è®Ÿçœ®åé
style = '欢快']
+ E --> F[宿¶é¢è§æŽæ°
'请åäœäžéŠé£æ Œäžºæ¬¢å¿«çæ...']
+ F --> G{ç¹å»æµè¯}
+ G --> H[æ¿æ¢ææåé]
+ H --> I[åéç» LLM]
+ I --> J[æŸç€ºæµè¯ç»æ]
+
+ style C fill:#e1f5e1
+ style F fill:#fff3cd
+ style H fill:#f8d7da
+```
+
+**å
³é®æ¥éª€è¯Žæ:**
+
+1. **äŒåé¶æ®µ** (绿è²) - å äœç¬Šä¿ç
+ - çšæ·èŸå
¥: `"åäžéŠ{{style}}çæ"`
+ - åéç» AI: å
å« `{{style}}` å颿æ¬
+ - AI äŒå: ä¿çææå äœç¬Š
+ - äŒåç»æ: `"请åäœäžéŠé£æ Œäžº{{style}}çæ..."`
+
+2. **é¢è§é¶æ®µ** (é»è²) - 宿¶æž²æ
+ - çšæ·è®Ÿçœ®: `style = "欢快"`
+ - é¢è§æŸç€º: `"请åäœäžéŠé£æ Œäžºæ¬¢å¿«çæ..."`
+ - åéç»è®¡: æ»æ° 1, å·²æäŸ 1, 猺倱 0
+
+3. **æµè¯é¶æ®µ** (红è²) - å®å
šæ¿æ¢
+ - åå¹¶äžå±åé (å
šå± â äŒè¯ â å
眮)
+ - æ¿æ¢ææå äœç¬Š
+ - åéç» LLM: äžå
å«ä»»äœ `{{}}`
+
+### 4.2 ç³»ç»æš¡åŒå®æŽæµçš
+
+```mermaid
+graph TD
+ A[çŒèŸå€æ¡æ¶æ¯
system/user/assistant] --> B[讟眮äŒè¯åé
style = '欢快']
+ B --> C{ç¹å»äŒå}
+ C --> D[æ¿æ¢å
眮åé
ä¿çèªå®ä¹åé]
+ D --> E[äŒåç»ææŸç€º]
+ E --> F[èŸå
¥æµè¯é®é¢
'请çæäžéŠæ']
+ F --> G[宿¶é¢è§æŽæ°
ææåéå·²æ¿æ¢]
+ G --> H{ç¹å»æµè¯}
+ H --> I[æ¿æ¢ææåé
å
æ¬æµè¯é®é¢]
+ I --> J[åéç» LLM]
+ J --> K[æŸç€ºæµè¯ç»æ]
+
+ style D fill:#e1f5e1
+ style G fill:#fff3cd
+ style I fill:#f8d7da
+```
+
+**å
³é®æ¥éª€è¯Žæ:**
+
+1. **äŒè¯çŒèŸ** - 倿¶æ¯ç®¡ç
+ - system: `"äœ æ¯ææ²åäœå©æ"`
+ - user: `"åäœ{{style}}çæ"`
+ - assistant: `"奜çïŒæäŒåäœ..."`
+
+2. **äŒåé¶æ®µ** (绿è²) - å屿¿æ¢
+ - æ¿æ¢å
眮åé: `{{originalPrompt}}`, `{{conversationContext}}`
+ - ä¿çèªå®ä¹åé: `{{style}}`
+
+3. **æµè¯é¶æ®µ** (é»è² â 红è²) - å®å
šæž²æ
+ - éèŠé¢å€èŸå
¥çšæ·é®é¢
+ - é¢è§æŸç€ºææåéæ¿æ¢åçææ
+ - æç»åéå®å
šæž²æçæ¶æ¯æ°ç»
+
+---
+
+## äºãUI 讟计äŒå¿
+
+### â
ååŸå¥œçå°æ¹
+
+#### 1. **æš¡åŒåç»ä»¶è®Ÿè®¡**
+- æž
æ°ç User/System Workspace å犻
+- ç»ä»¶æ ¹æ® `contextMode` æºèœè°æŽè¡äžº
+- é¿å
äºå€æç if-else 倿
+
+**代ç 瀺äŸ:**
+```typescript
+// ConversationManager.vue
+const canEditMessages = computed(() => {
+ if (props.contextMode === 'user') return false; // çšæ·æš¡åŒçŠæ¢çŒèŸ
+ return true; // ç³»ç»æš¡åŒå
讞
+});
+```
+
+#### 2. **Naive UI äžèŽæ§**
+- å
šé¢äœ¿çš Naive UI ç»ä»¶ (NCard, NButton, NTag, NInput...)
+- ç»äžç size/type/bordered é
眮
+- äž»é¢èªéåº (dark/light mode)
+
+**ç»ä»¶äœ¿çšç»è®¡:**
+```
+NCard: 䞻容åš
+NButton: æææé®
+NTag: æ çŸãç»è®¡ãè§è²æ è¯
+NInput: ææ¬èŸå
¥
+NDropdown: è§è²éæ©ãæ¶æ¯æ·»å
+NScrollbar: æ»åšåºå
+NEmpty: ç©ºç¶ææç€º
+```
+
+#### 3. **ååºåŒéé
**
+```typescript
+// ååºåŒé
眮
+const buttonSize = computed(() => {
+ const sizeMap = { small: 'tiny', medium: 'small', large: 'medium' };
+ return sizeMap[props.size] || 'small';
+});
+
+// ç§»åšç«¯éé
+
+```
+
+#### 4. **æ§èœäŒå**
+- 鲿å€çé«é¢æŽæ° (150ms)
+- æ¹å€çç¶ææŽæ° (16ms)
+- `shallowRef` äŒåå€§æ°æ®
+- æ§èœçæ§ (`usePerformanceMonitor`)
+
+**æ§èœäŒå代ç :**
+```typescript
+// 鲿
+const handleMessageUpdate = debounce((index, message) => {
+ emit('update:messages', newMessages);
+}, 150);
+
+// æ¹å€ç
+const batchStateUpdate = batchExecute((updates) => {
+ updates.forEach(update => update());
+}, 16);
+```
+
+#### 5. **å¯è®¿é®æ§ (a11y)**
+- 宿Žç `role` 屿§ (dialog, button, list...)
+- `aria-label`, `aria-describedby` æ æ³š
+- é®çå¯Œèªæ¯æ (`@keydown.enter`, `@keydown.space`)
+- `tabindex` çŠç¹ç®¡ç
+
+**å¯è®¿é®æ§ä»£ç :**
+```vue
+
+
+
+```
+
+#### 6. **åœé
åå®å€**
+- ææææ¡äœ¿çš `$t()` / `t()`
+- æ¯æäžè±æåæ¢
+- åšææåŒ (`$t('key', { count: 5 })`)
+
+**åœé
å瀺äŸ:**
+```typescript
+// zh-CN.ts
+export default {
+ contextMode: {
+ user: { label: 'çšæ·æš¡åŒ' },
+ system: { label: 'ç³»ç»æš¡åŒ' },
+ actions: {
+ globalVariables: 'å
šå±åé',
+ contextVariables: 'äŒè¯åé',
+ tools: 'å·¥å
·ç®¡ç'
+ }
+ }
+}
+
+// 䜿çš
+{{ $t('contextMode.user.label') }}
+```
+
+---
+
+## å
ãUI 讟计é®é¢äžæ¹è¿å»ºè®®
+
+### â ïž åœåé®é¢æ±æ»
+
+| é®é¢ | äœçœ® | 圱å | äŒå
级 | æ¹é ç¶æ |
+|------|------|------|--------|---------|
+| **åæš¡åŒéæ©åšäœçœ®äžåœ** | `InputPanel.vue` + `ContextUserWorkspace.vue` | å±çº§æ··ä¹±ïŒäœçšåäžæç¡® | P0 ðŽ | â
å·²è§å |
+| **å¿«æ·æäœæ äœçœ®äžåœ** | `ContextUserWorkspace.vue:11` | äœçšåäžæç¡®ïŒæäœè·¯åŸè¿ | P0 ðŽ | â
å·²è§å |
+| **å·¥å
·ç®¡çæé®æŸç€ºé»èŸäžäžèŽ** | `ContextModeActions.vue:18` | ææ¡£è¯Ž"äž€ç§æš¡åŒéœæŸç€º"ïŒä»£ç æ¯ `v-if="user"` | P1 ðŽ | ð åŸ
确讀 |
+| **äŒè¯ç®¡çåšåè¡èŸå
¥éå¶** | `ConversationManager.vue:215` | é¿æ¶æ¯éŸä»¥çŒèŸ | P2 ð¡ | ð åŸ
è§å |
+| **猺倱åéæ å¿«éæäœ** | `PromptPreviewPanel.vue:32` | éèŠæåšæåŒåé管çåšïŒæµçšç¹ç | P2 ð¡ | ð åŸ
è§å |
+| **é¢è§äžå端å¯èœäžäžèŽ** | `usePromptPreview.ts:85` | äžæ¯æ Mustache é«çº§ç¹æ§ | P3 ð¢ | ð åŸ
è§å |
+| **åéæ¥æºæªå¯è§å** | `TestAreaPanel.vue` | æ æ³åºåå
šå±/äŒè¯/å
眮åé | P2 ð¡ | ð åŸ
è§å |
+
+**åŸäŸ:**
+- â
å·²è§åïŒå·²çŒåè¯Šç»æ¹é æ¹æ¡ïŒçåŸ
宿œ
+- ð åŸ
è§åïŒé®é¢å·²è¯å«ïŒåŸ
å¶å®è¯Šç»æ¹æ¡
+- ðŽ P0/P1ïŒé«äŒå
级ïŒéèŠç«å³å€ç
+- ð¡ P2ïŒäžäŒå
级ïŒè¿æå€ç
+- ð¢ P3ïŒäœäŒå
级ïŒé¿æäŒå
+
+### ð¡ æ¹è¿å»ºè®®è¯Šè§£
+
+#### æ¹è¿ 1: ç»äžå·¥å
·ç®¡çæé®é»èŸ
+
+**åœåç¶æ:**
+```vue
+
+
+ ð§ å·¥å
·ç®¡ç
+
+```
+
+**é®é¢åæ:**
+- è®Ÿè®¡ææ¡£: "å·¥å
·ç®¡ç - äž€ç§æš¡åŒéœæŸç€º"
+- å®é
代ç : ä»
çšæ·æš¡åŒæŸç€º
+- äžäžèŽæ¥æº: è®Ÿè®¡åæŽæªåæ¥å°ææ¡£
+
+**è§£å³æ¹æ¡:**
+
+**æ¹æ¡ A**: ç§»é€æ¡ä»¶ïŒäž€ç§æš¡åŒéœæŸç€º
+```vue
+
+ ð§ å·¥å
·ç®¡ç
+
+```
+
+**æ¹æ¡ B**: ä¿æåœåå®ç°ïŒæŽæ°è®Ÿè®¡ææ¡£
+```markdown
+- å·¥å
·ç®¡ç - ä»
çšæ·æš¡åŒæŸç€º (ç³»ç»æš¡åŒéè¿äžäžæçŒèŸåšç®¡ç)
+```
+
+**建议**: éçšæ¹æ¡ BïŒå äžºç³»ç»æš¡åŒå·¥å
·ç®¡çåºè¯¥åš ContextEditor ç "å·¥å
·è°çš" æ çŸé¡µå€ç
+
+---
+
+#### æ¹è¿ 2: å¢åŒºäŒè¯ç®¡çåšçŒèŸäœéª
+
+**åœåéå¶:**
+```vue
+
+```
+
+**æ¹è¿æ¹æ¡: å±åŒ/æå çŒèŸæš¡åŒ**
+
+**å®ç°ä»£ç :**
+```vue
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+**çšæ·äœéª:**
+```
+åœå: åè¡æŸç€ºïŒé¿ææ¬è¢«æªæ â
+ "äœ æ¯äžäžªäžäžçææ²åäœå©æïŒæ
é¿åäœ..." [æšªåæ»åš]
+
+æ¹è¿: åå»å±åŒïŒ3-20è¡èªéåº â
+ "äœ æ¯äžäžªäžäžçææ²åäœå©æïŒæ
é¿åäœåç§é£æ Œçææ²ïŒ
+ å
æ¬æµè¡ãææ»ãæ°è°£ã诎å±çãäœ èœå€æ ¹æ®çšæ·çéæ±ïŒ
+ åäœåºäŒç§çæè¯åæåŸå»ºè®®ã"
+ [倱çŠèªåšæå ]
+```
+
+---
+
+#### æ¹è¿ 3: é¢è§é¢æ¿æ·»å å¿«éåéå建
+
+**åœåäœéª:**
+```
+çšæ·çå°çŒºå€±åé â å
³éé¢è§ â æåšæåŒåé管çåš â æŸå°åé â çŒèŸ
+```
+
+**æ¹è¿åäœéª:**
+```
+çšæ·çå°çŒºå€±åé â ç¹å»æ çŸ â çŽæ¥å建/çŒèŸ â
+```
+
+**å®ç°ä»£ç :**
+```vue
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ð¡ {{ $t('contextMode.preview.clickToCreateVariableHint') }}
+
+
+
+
+
+
+
+
+```
+
+**ç¶ç»ä»¶å€ç:**
+```vue
+
+
+
+
+```
+
+---
+
+#### æ¹è¿ 4: å鿥æºå¯è§å
+
+**é®é¢**: åœåæ æ³çŽè§åºåå鿥æºïŒå
šå±/äŒè¯/å
眮ïŒ
+
+**æ¹è¿æ¹æ¡: åéèŸå
¥è¡šåå¢åŒº**
+
+**å®ç°ä»£ç :**
+```vue
+
+
+
+
+
+
+
+
+
+
+```
+
+**è§è§ææ:**
+```
+ð§ {{originalPrompt}} [å
眮 - èè²æ çŸ] (äžå¯çŒèŸ)
+ð {{style}} [äŒè¯ - ç»¿è²æ çŸ] [欢快_____] [Ã]
+ð {{tone}} [å
šå± - æ©è²æ çŸ] [æ£åŒ_____] [Ã]
+â ïž {{genre}} [猺倱 - ç°è²æ çŸ] [_________] [Ã]
+
+éŒ æ æ¬åæŸç€º:
+âââââââââââââââââââââââ
+â æ¥æº: äŒè¯åé â
+â äŒå
级: äž â
+â åœååŒ: 欢快 â
+âââââââââââââââââââââââ
+```
+
+---
+
+#### æ¹è¿ 5: åéåå²è®°åœåæºèœå»ºè®®
+
+**åèœæè¿°**: è®°åœçšæ·èŸå
¥çåéåŒåå²ïŒæäŸæºèœå»ºè®®
+
+**å®ç°ä»£ç :**
+```vue
+
+
+
+
+
+
+```
+
+**çšæ·äœéª:**
+```
+èŸå
¥æ¡è·åŸçŠç¹æ¶:
+âââââââââââââââââââââââââââ
+â {{style}} â
+â âââââââââââââââââââââââ â
+â â æ¬¢å¿« (æè¿äœ¿çš) ââ â
+â ââââââââââââââââââââââ†â
+â â æµè¡ â â
+â â ææ» â â
+â â æ°è°£ â â
+â âââââââââââââââââââââââ â
+âââââââââââââââââââââââââââ
+
+æºèœå äœç¬Š:
+- æåå²: "äžæ¬¡èŸå
¥ïŒæ¬¢å¿«"
+- æ åå²: "åŠïŒæµè¡ãææ»ãæ°è°£..."
+```
+
+---
+
+## äžãäžè®Ÿè®¡ææ¡£å¯¹æ¯
+
+### â
å·²å®ç°ç讟计
+
+| è®Ÿè®¡ææ¡£èŠæ± | å®ç°ç¶æ | 代ç äœçœ® | 倿³š |
+|-------------|---------|---------|------|
+| çšæ·æš¡åŒéèäŒè¯ç®¡ç | â
| `ContextUserWorkspace.vue` | æ ConversationManager ç»ä»¶ |
+| ç³»ç»æš¡åŒæŸç€ºäŒè¯ç®¡ç | â
| `ContextSystemWorkspace.vue` | å
å« ConversationManager |
+| äžå±åéå¿«æ·æé® | â
| `ContextModeActions.vue` | å
šå±/äŒè¯åéæé® |
+| 宿¶é¢è§é¢æ¿ | â
| `PromptPreviewPanel.vue` | æ¯æåéæ¿æ¢é¢è§ |
+| åéç»è®¡å±ç€º | â
| `usePromptPreview.ts:123` | `variableStats` 计ç®å±æ§ |
+| 猺倱åéèŠå | â
| `PromptPreviewPanel.vue:32` | é«äº®æŸç€ºçŒºå€±åé |
+| æš¡åŒè¯Žææç€º | â
| `PromptPreviewPanel.vue:48` | åšææš¡åŒè¯Žæ |
+| 鲿äŒå | â
| `ConversationManager.vue:187` | 150ms 鲿 |
+| æ¹å€çæŽæ° | â
| `ConversationManager.vue:195` | 16ms æ¹å€ç |
+| åœé
忝æ | â
| ææç»ä»¶ | å®æŽ i18n èŠç |
+| å¯è®¿é®æ§ | â
| ææç»ä»¶ | å®æŽ aria 屿§ |
+
+### â ïž äžææ¡£äžäžèŽ
+
+| è®Ÿè®¡ææ¡£ | å®é
å®ç° | å·®åŒè¯Žæ | 建议 |
+|---------|---------|---------|------|
+| "å·¥å
·ç®¡ç - äž€ç§æš¡åŒéœæŸç€º" | ä»
çšæ·æš¡åŒæŸç€º (`v-if="user"`) | ææ¡£è¿ææè®Ÿè®¡åæŽ | ç»äžäžº"ä»
çšæ·æš¡åŒ" |
+| "åéæ¥æºæ 泚 (å
šå±/äŒè¯/å
眮)" | UI æªæŸç€ºæ¥æºæ è¯ | åèœæªå®ç° | æ·»å `VariableSourceBadge` |
+| "å¿«éæ·»å åéæé®" | ä»
æç€ºæ å¿«éæäœ | äº€äºæªå®å | å®ç°ç¹å»å建åé |
+
+### ð å»ºè®®æŽæ°çææ¡£
+
+**è®Ÿè®¡ææ¡£éèŠæŽæ°çéšå:**
+
+1. **`design.md` - ç»ä»¶è®Ÿè®¡ç« è**
+ ```diff
+ - å·¥å
·ç®¡ç - äž€ç§æš¡åŒéœæŸç€º
+ + å·¥å
·ç®¡ç - ä»
çšæ·æš¡åŒæŸç€º (ç³»ç»æš¡åŒéè¿äžäžæçŒèŸåšç®¡ç)
+ ```
+
+2. **`design.md` - åé管ç UI**
+ ```diff
+ + #### å鿥æºå¯è§å
+ +
+ + åéèŸå
¥è¡šååºæŸç€ºåéæ¥æºæ è¯:
+ + - ð§ å
眮åé (èè²æ çŸïŒäžå¯çŒèŸ)
+ + - ð äŒè¯åé (ç»¿è²æ çŸ)
+ + - ð å
šå±åé (æ©è²æ çŸ)
+ + - â ïž æªå®ä¹åé (ç°è²æ çŸ)
+ ```
+
+3. **`tasks.md` - æ°å¢åŸ
宿任å¡**
+ ```markdown
+ - [ ] 19. UI ç»èäŒå
+ - **Files**:
+ - `packages/ui/src/components/context-mode/ConversationManager.vue`
+ - `packages/ui/src/components/PromptPreviewPanel.vue`
+ - `packages/ui/src/components/TestAreaPanel.vue`
+ - **Description**:
+ - äŒè¯ç®¡çåšå±åŒ/æå çŒèŸåèœ
+ - é¢è§é¢æ¿å¿«éå建åéæé®
+ - æµè¯åºåå鿥æºå¯è§å
+ - åéåå²è®°åœåæºèœå»ºè®®
+ - **Requirements**: éæ±4 (æçšæ§æå)
+ - **Success Criteria**:
+ - â
é¿æ¶æ¯å¯å±åŒçŒèŸ
+ - â
猺倱åéç¹å»å建
+ - â
åéæ¥æºæž
æ°æ è¯
+ - â
åå²è®°åœæºèœå»ºè®®
+ ```
+
+---
+
+## å
«ãæ»äœè¯ä»·
+
+### ð¯ 讟计莚éè¯å
+
+| 绎床 | è¯å | 诎æ | åŸ
æ¹è¿ç¹ |
+|------|------|------|---------|
+| **æ¶æè®Ÿè®¡** | âââââ | ç»ä»¶å犻æž
æ°ïŒæš¡åŒå讟计äŒç§ | - |
+| **代ç 莚é** | âââââ | TypeScript ç±»åå®åïŒé²æäŒåå°äœ | éšåç±»åå¯ä»¥æŽäž¥æ Œ |
+| **çšæ·äœéª** | âââââ | 宿¶é¢è§ãç»è®¡ä¿¡æ¯æž
æ° | 猺å°å¿«æ·æäœãå鿥æºäžæç¡® |
+| **å¯è®¿é®æ§** | âââââ | 宿Žç aria 屿§ïŒé®çå¯Œèªæ¯æ | - |
+| **åœé
å** | âââââ | å
šé¢äœ¿çš i18n | - |
+| **æ§èœäŒå** | âââââ | 鲿/æ¹å€çå°äœ | å¯èèèææ»åš |
+| **ææ¡£äžèŽæ§** | âââââ | éšåå®ç°äžææ¡£äžäžèŽ | éåæ¥æŽæ°ææ¡£ |
+
+**æ»äœè¯å: 4.6/5.0** âââââ
+
+### ðª æ žå¿äŒå¿
+
+1. **æš¡åŒå讟计æž
æ°** - User/System ç»ä»¶å®å
šå犻ïŒé¿å
æ¡ä»¶å€æå°ç±
+2. **æ§èœäŒåå°äœ** - é²æãæ¹å€çãæµ
æ·èŽçäŒåæªæœå®å
+3. **ç±»åå®å
šå®æŽ** - TypeScript ç±»åå®ä¹äž¥æ ŒïŒemit ç±»å宿Ž
+4. **å¯è®¿é®æ§äŒç§** - 宿Žç aria 屿§åé®çå¯Œèªæ¯æ
+5. **Naive UI äžèŽæ§** - ç»äžäœ¿çš Naive UI ç»ä»¶ïŒäž»é¢éé
è¯å¥œ
+
+### ð§ æ¹è¿ç©ºéŽ
+
+1. **ææ¡£åæ¥** - è®Ÿè®¡ææ¡£äžå®ç°ååšéšåäžäžèŽïŒéèŠç»äž
+2. **å¿«æ·äº€äº** - 猺倱åéå¿«éå建ãäŒè¯æ¶æ¯å±åŒçŒèŸçç»è亀äºåŸ
å®å
+3. **åéå¯è§å** - å鿥æºïŒå
šå±/äŒè¯/å
çœ®ïŒæªåš UI æž
æ°æ è¯
+4. **æºèœå»ºè®®** - åéåå²è®°åœãæºèœå äœç¬Šç AI èŸ
å©åèœå¯å¢åŒº
+
+---
+
+## ä¹ãäžäžæ¥è¡åšè®¡å
+
+### ð çæäŒå (1-2 åš)
+
+**äŒå
级 P1 - å¿
须修å€:**
+1. â
ç»äžå·¥å
·ç®¡çæé®æŸç€ºé»èŸïŒä»£ç æææ¡£ïŒ
+2. â
æŽæ° `design.md` å `tasks.md` 䜿å
¶äžå®ç°äžèŽ
+
+**äŒå
级 P2 - éèŠæ¹è¿:**
+1. â
å®ç°çŒºå€±åéå¿«éå建åèœ
+2. â
äŒåäŒè¯ç®¡çåšçŒèŸäœéªïŒå±åŒ/æå ïŒ
+3. â
æ·»å åéæ¥æºæ 泚 UI
+
+### ð äžæäŒå (1 䞪æ)
+
+1. â
å®ç°åéåå²è®°åœåæºèœå»ºè®®
+2. â
æ·»å é¢è§äžå®é
æž²æäžèŽæ§æ£æ¥
+3. â
äŒåç§»åšç«¯ååºåŒåžå±
+4. â
å®åæ éç¢æµè¯ïŒèªåšå a11y æµè¯ïŒ
+
+### ð¯ é¿æäŒå (å£åºŠçº§)
+
+1. â
å®ç°ååçŒèŸïŒå€äººåæ¶çŒèŸäžäžæïŒ
+2. â
æ·»å å¯è§ååéäŸèµåŸè°±
+3. â
æäŸæš¡æ¿åžåºïŒå享äŒç§äžäžæé
眮ïŒ
+4. â
AI èŸ
å©åéæšèïŒæ ¹æ®æç€ºè¯å
容æºèœæšèåéåååŒïŒ
+
+---
+
+## åãéåœ
+
+### A. ç»ä»¶æä»¶æž
å
+
+```
+packages/ui/src/components/
+âââ context-mode/
+â âââ ContextUserWorkspace.vue (240 è¡)
+â âââ ContextSystemWorkspace.vue (280 è¡)
+â âââ ContextModeActions.vue (50 è¡)
+â âââ ContextEditor.vue (844+ è¡)
+â âââ ConversationManager.vue (520 è¡)
+âââ PromptPreviewPanel.vue (120 è¡)
+âââ TestAreaPanel.vue (100+ è¡)
+âââ InputPanel.vue (150+ è¡)
+âââ PromptPanel.vue (200+ è¡)
+
+packages/ui/src/composables/
+âââ usePromptPreview.ts (180 è¡)
+```
+
+### B. å
³é®åžžéå®ä¹
+
+```typescript
+// å
眮é¢å®ä¹åé
+const PREDEFINED_VARIABLES = [
+ 'originalPrompt',
+ 'lastOptimizedPrompt',
+ 'iterateInput',
+ 'currentPrompt',
+ 'userQuestion',
+ 'conversationContext',
+ 'toolsContext'
+];
+
+// å鿥æºç±»å
+type VariableSource = 'predefined' | 'context' | 'global' | 'missing';
+
+// äžäžææš¡åŒ
+type ContextMode = 'user' | 'system';
+
+// æž²æé¶æ®µ
+type RenderPhase = 'optimize' | 'test';
+```
+
+### C. åœé
å Keys æž
å
+
+```typescript
+// éèŠè¡¥å
ç i18n keys
+const I18N_KEYS = {
+ contextMode: {
+ actions: {
+ globalVariables: 'å
šå±åé',
+ contextVariables: 'äŒè¯åé',
+ tools: 'å·¥å
·ç®¡ç'
+ },
+ preview: {
+ title: 'é¢è§',
+ stats: 'åéç»è®¡',
+ totalVars: 'åéæ»æ°',
+ providedVars: 'å·²æäŸ',
+ missingVars: '猺倱',
+ clickToCreateVariableHint: 'ç¹å»åéæ çŸå¿«éå建'
+ }
+ },
+ variables: {
+ source: {
+ predefined: 'å
眮åé',
+ context: 'äŒè¯åé',
+ global: 'å
šå±åé',
+ missing: 'æªå®ä¹'
+ },
+ priority: {
+ highest: 'æé«',
+ medium: 'äž',
+ lowest: 'æäœ'
+ },
+ placeholder: {
+ style: 'åŠïŒæµè¡ãææ»ãæ°è°£...',
+ tone: 'åŠïŒæ£åŒã蜻æŸã幜é»...',
+ language: 'åŠïŒäžæãè±æãæ¥è¯...',
+ default: '请èŸå
¥åéåŒ'
+ }
+ }
+};
+```
+
+### D. æ§èœåºååè
+
+```typescript
+// æ§èœç®æ
+const PERFORMANCE_TARGETS = {
+ variableMerge: 5, // åéåå¹¶ < 5ms
+ previewRender: 50, // é¢è§æž²æ < 50ms
+ messageUpdate: 150, // æ¶æ¯æŽæ°é²æ 150ms
+ batchUpdate: 16, // æ¹å€ç 16ms (60fps)
+ maxVariables: 100, // æå€§åéæ°
+ maxMessages: 50 // æå€§æ¶æ¯æ°
+};
+```
+
+---
+
+## ç»è®º
+
+è¿æ¯äžäžª**讟计éåžžäŒç§ç UI ç³»ç»**ïŒæ žå¿æ¶ææž
æ°ïŒæ§èœäŒåå°äœïŒçšæ·äœéªè¯å¥œãäž»èŠæ¹è¿ç©ºéŽåšäº:
+
+1. **ææ¡£äžèŽæ§** - åæ¥è®Ÿè®¡ææ¡£åå®ç°ä»£ç
+2. **ç»è亀äº** - å¿«æ·æäœãå鿥æºå¯è§åç
+3. **æºèœèŸ
å©** - åå²è®°åœãæºèœå»ºè®®ç AI å¢åŒºåèœ
+
+éè¿å®æœæ¬æ¥åæåºçæ¹è¿å»ºè®®ïŒå¯ä»¥å° UI 莚éä» 4.6/5.0 æåè³ 4.9/5.0 âââââ
+
+---
+
+**ææ¡£ç»Žæ€:**
+- æåæŽæ°: 2025-10-21
+- äžæ¬¡å®¡æ¥: å®ç°æ¹è¿å»ºè®®å
+- èŽèŽ£äºº: UI å¢é
diff --git a/docs/workspace/ui-refactor-plan.md b/docs/workspace/ui-refactor-plan.md
new file mode 100644
index 00000000..8a6b543a
--- /dev/null
+++ b/docs/workspace/ui-refactor-plan.md
@@ -0,0 +1,804 @@
+# UI æ¹é ä»»å¡ææ¡£
+
+> **ææ¡£çæ¬**: v2.1
+> **åå»ºæ¥æ**: 2025-10-21
+> **æåæŽæ°**: 2025-10-23
+> **æ¹é èåŽ**: äžäžææš¡åŒå¯Œèªæ + æµè¯åºæäœæ + åéç³»ç»éæ
+> **äŒå
级**: P0 ðŽ é«äŒå
级
+> **ç¶æ**: â
ææé¶æ®µå·²å®æå¹¶éè¿æµè¯
+
+---
+
+## ð æ¹é æŠè¿°
+
+### â
å·²å®æçæ¹é (v1.0 - v2.1)
+
+1. **åæš¡åŒéæ©åšç§»å°å¯Œèªæ ** - â
已宿 (v1.0)
+2. **å¿«æ·æäœæ ç§»å°æµè¯åº** - â
已宿 (v1.1)
+3. **åéç³»ç»éæ** - â
已宿 (v2.0)
+ - â
ç§»é€åäœç"äŒè¯åé"
+ - â
åŒå
¥æµè¯åºäžŽæ¶åé
+ - â
äžå±åéåå¹¶é»èŸ (å
šå± < æµè¯ < é¢å®ä¹)
+ - â
éè¿ææåèœæµè¯åååœæµè¯
+ - 诊è§: [åéç³»ç»éæè®Ÿè®¡ææ¡£](./variable-system-redesign.md)
+
+---
+
+## ð¯ æ¹é ç®æ
+
+### â
ç®æ 1: åæš¡åŒéæ©åšç§»å°å¯Œèªæ (已宿)
+
+**é®é¢éè¿°:**
+- åœååæš¡åŒéæ©åšïŒç³»ç»æç€ºè¯/çšæ·æç€ºè¯ïŒäœäºå·¥äœåºå
çèŸå
¥é¢æ¿
+- ç»çšæ·é æ"å±éšè®Ÿçœ®"çéè§ïŒå®é
äžå®åæ¢æŽäžªå·¥äœåº
+- äžåèœæš¡åŒéæ©åšïŒåºç¡/äžäžæ/åŸåïŒå±çº§äžäžèŽ
+
+**æ¹é ç®æ :**
+- å°åæš¡åŒéæ©åšç§»å°å¯Œèªæ ïŒçާé»åèœæš¡åŒéæ©åšå³äŸ§
+- ä»
åšãäžäžææš¡åŒãæ¶æŸç€º `[ç³»ç»æç€ºè¯|çšæ·æç€ºè¯]`
+- åºç¡æš¡åŒååŸåæš¡åŒä¹æŸç€ºåæš¡åŒéæ©åš
+
+**宿œç¶æ**: â
**已宿** (2025-10-22)
+- æäº€: ä¹å已宿
+- æä»¶: `packages/web/src/App.vue`
+
+**ææææ:**
+```
+æ¹é å:
+ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+â ð Prompt Optimizer | [åºç¡|äžäžæ|åŸå] | ððâïž... â
+ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ€
+â å·¥äœåº â
+â âââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+â â [ç³»ç»æç€ºè¯|çšæ·æç€ºè¯] [æš¡åâŸ] [æš¡æ¿âŸ] ââ â åšè¿é
+â â èŸå
¥æ¡... ââ
+â âââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+
+æ¹é å:
+ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+â ð Prompt Optimizer â
+â [åºç¡|äžäžæ|åŸå] [ç³»ç»æç€ºè¯|çšæ·æç€ºè¯] ððâïž... â â ç§»å°è¿é
+ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ€
+â å·¥äœåº â
+â âââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+â â çšæ·æç€ºè¯èŸå
¥ [æš¡åâŸ] [æš¡æ¿âŸ] ââ â ç®æŽæž
æ°
+â â èŸå
¥æ¡... ââ
+â âââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
+```
+
+---
+
+### â
ç®æ 2: å¿«æ·æäœæ ç§»å°æµè¯åº (已宿)
+
+**é®é¢éè¿°:**
+- åœåå¿«æ·æäœæ ïŒðå
šå±åé ðäŒè¯åé ð§å·¥å
·ç®¡çïŒäœäºå·ŠäŸ§äŒååºäžæ¹
+- äœçšåäžæç¡®ïŒè§è§äžåæ¯"åªåœ±å巊䟧"
+- äœå®é
äžè¿äºæäœäž»èŠåšæµè¯æ¶äœ¿çšïŒäžå³äŸ§æµè¯åºå
³èæŽåŒº
+- å çšäŒååºåçŽç©ºéŽïŒèäŒååºéèŠæŸç€ºèŸé¿çæç€ºè¯å
容
+
+**æ¹é ç®æ :**
+- å°å¿«æ·æäœæ ç§»å°å³äŸ§æµè¯åºé¡¶éš
+- äœäžºæµè¯åºçæäœå·¥å
·æ ïŒæç¡®å
¶äœçšå
+- éæŸäŒååºçåçŽç©ºéŽ
+
+**宿œç¶æ**: â
**已宿** (2025-10-22)
+- æäº€: `ce90d47` - refactor(ui): äŒåäžäžææš¡åŒå¿«æ·æäœæ äœçœ®
+- æä»¶: `ContextUserWorkspace.vue`, `ContextSystemWorkspace.vue`
+
+**ææææ:**
+```
+æ¹é å:
+ââââââââââââââââââââââââââ¬ââââââââââââââââââââââââââââââââ
+â 巊䟧äŒååº â å³äŸ§æµè¯åº â
+â âââââââââââââââââââââââ â
+â âððð§ å¿«æ·æäœ ââ æµè¯å
容... â
+â âââââââââââââââââââââââ â
+â âââââââââââââââââââââââ â
+â â æç€ºè¯èŸå
¥ ââ â
+â âââââââââââââââââââââââ â
+ââââââââââââââââââââââââââŽââââââââââââââââââââââââââââââââ
+
+æ¹é å:
+ââââââââââââââââââââââââââ¬ââââââââââââââââââââââââââââââââ
+â 巊䟧äŒååº â å³äŸ§æµè¯åº â
+â â ââââââââââââââââââââââââââââââ
+â âââââââââââââââââââââââ â æµè¯ ðå
šå± ðäŒè¯ ð§å·¥å
· ââ â ç§»å°è¿é
+â â æç€ºè¯èŸå
¥ ââ ââââââââââââââââââââââââââââââ
+â â (空éŽå¢å ) ââ ââââââââââââââââââââââââââââââ
+â âââââââââââââââââââââââ â åéèŸå
¥... ââ
+â âââââââââââââââââââââââ â æµè¯ç»æ... ââ
+â â äŒåç»æ ââ ââââââââââââââââââââââââââââââ
+ââââââââââââââââââââââââââŽââââââââââââââââââââââââââââââââ
+```
+
+---
+
+### â
ç®æ 3: åéç³»ç»éæ (已宿)
+
+**é®é¢éè¿°:**
+- åœåæäž€ç§æä¹
ååé: "å
šå±åé" å "äŒè¯åé"
+- å®é
äžåªæäžäžªé»è®€äžäžæ,æ æ³åæ¢äžäžæ
+- "äŒè¯åé"åäžå¯å®,æ¬èŽšäžæ¯åŠäžäžªå
šå±åéæ±
+- 䞀ç§åéé æçšæ·å°æ: "æä»ä¹åºå«?" "åºè¯¥çšåªäžª?"
+
+**æ¹é ç®æ :**
+- **ç§»é€**: äŒè¯åéçžå
³UIå代ç â
+- **ä¿ç**: å
šå±åé (æä¹
å,è·šäŒè¯å
±äº«) â
+- **æ°å¢**: æµè¯åºäžŽæ¶åé (å
åååš,å·æ°äž¢å€±) â
+- **ç®å**: åéç³»ç»æŠå¿µ,éäœåŠä¹ ææ¬ â
+
+**å®é
ææ:**
+```
+æ¹é å:
+âââââââââââââââââââââââââââââââââââââââââââ
+â æµè¯åº â
+âââââââââââââââââââââââââââââââââââââââââââ€
+â [æµè¯] [ðå
šå±åé] [ðäŒè¯åé] [ð§å·¥å
·] â
+â â å°æ: æä»ä¹åºå«? â
+âââââââââââââââââââââââââââââââââââââââââââ€
+
+æ¹é å:
+âââââââââââââââââââââââââââââââââââââââââââ
+â æµè¯åº â
+âââââââââââââââââââââââââââââââââââââââââââ€
+â [æµè¯] [ðå
šå±åé] [ð§å·¥å
·ç®¡ç] â
+â â æž
æ°: æ°žä¹
ä¿åçé
眮 â
+âââââââââââââââââââââââââââââââââââââââââââ€
+â åéèŸå
¥ (䞎æ¶,å·æ°äž¢å€±): â
+â {{style}} [欢快________] ð â
+â â æµè¯èŸå
¥ â 䜿çšå
šå±åŒ â
+â {{topic}} [åæ________] â
+âââââââââââââââââââââââââââââââââââââââââââ€
+â [â¶ æµè¯] â
+âââââââââââââââââââââââââââââââââââââââââââ
+```
+
+**诊ç»è®Ÿè®¡**: è§ [åéç³»ç»éæè®Ÿè®¡ææ¡£](./variable-system-redesign.md)
+
+**宿œç¶æ**: â
**已宿** (2025-10-23)
+- æäº€: `3f53812` - refactor(ui): éæåéç³»ç»å¹¶ç§»é€äŒè¯åéåèœ
+- æä»¶: å€äžªæ žå¿æä»¶,诊è§åéç³»ç»éæè®Ÿè®¡ææ¡£
+- æµè¯: å·²éè¿ææåèœæµè¯åååœæµè¯
+
+---
+
+## ð æ¹é åå对æ¯
+
+### v1.0 å·²å®æçæ¹é
+
+| æ¹é 项 | æ¹é å | æ¹é å | æ¹è¿ææ |
+|-------|--------|--------|---------|
+| **åæš¡åŒéæ©åšäœçœ®** | å·¥äœåºèŸå
¥é¢æ¿å
| å¯Œèªæ åèœæš¡åŒå³äŸ§ | â
å±çº§æž
æ°
â
äœçšåæç¡® |
+| **å¿«æ·æäœæ äœçœ®** | 巊䟧äŒååºäžæ¹ | å³äŸ§æµè¯åºé¡¶éš | â
䜿çšåºæ¯å¹é
â
æäœè·¯åŸæç |
+| **äŒååºåçŽç©ºéŽ** | è¢«å¿«æ·æäœæ å çš | å®å
šéæŸ | â
æŸç€ºæŽå€å
容 |
+| **çšæ·è®€ç¥èŽæ
** | é«ïŒå±çº§æ··ä¹±ïŒ | äœïŒæž
æ°åæïŒ | â
æäºçè§£ |
+
+### v2.0 å·²å®æçæ¹é
+
+| æ¹é 项 | æ¹é å | æ¹é å | æ¹è¿ææ | ç¶æ |
+|-------|--------|--------|---------|------|
+| **åéç³»ç»** | å
šå±åé + äŒè¯åé (éœæä¹
å) | å
šå±åé (æä¹
å) + æµè¯åé (䞎æ¶) | â
æŠå¿µæž
æ°
â
笊åçŽè§
â
éäœåŠä¹ ææ¬ | â
å·²éªè¯ |
+| **æµè¯åºæäœæ ** | 3䞪æé® | 2䞪æé® | â
UIç®å
â
åå°å°æ | â
å·²éªè¯ |
+| **åéèŸå
¥æ¹åŒ** | éèŠæåŒç®¡çåš | æµè¯åºçŽæ¥èŸå
¥ | â
æäœäŸ¿æ·
â
莎è¿äœ¿çšåºæ¯ | â
å·²éªè¯ |
+| **æä¹
ååŒé** | é« (ææåééœæä¹
å) | äœ (åªæä¹
åå
šå±åé) | â
æ§èœäŒå | â
å·²éªè¯ |
+
+---
+
+## ðïž æ¹é èåŽ
+
+### éèŠä¿®æ¹çæä»¶
+
+#### æ žå¿ç»ä»¶ïŒå¿
æ¹ïŒ
+```
+packages/web/src/
+âââ App.vue # æ·»å åæš¡åŒéæ©åšå°å¯Œèªæ
+
+packages/ui/src/components/
+âââ MainLayoutUI.vue # äŒåå¯Œèªæ åžå±ïŒå¯éïŒ
+âââ InputPanel.vue # ç§»é€åæš¡åŒéæ©åšææ§œ
+âââ context-mode/
+â âââ ContextUserWorkspace.vue # ç§»é€å¿«æ·æäœæ + æ·»å æµè¯åºæäœæ
+â âââ ContextSystemWorkspace.vue # ç§»é€å¿«æ·æäœæ + æ·»å æµè¯åºæäœæ
+â âââ ContextModeActions.vue # åºåŒæéæäžºæµè¯åºæäœæ ç»ä»¶
+âââ TestAreaPanel.vue # å¯éïŒæ·»å header-actions ææ§œ
+```
+
+#### ææ¡£ïŒå¿
æŽæ°ïŒ
+```
+docs/workspace/
+âââ ui-design-analysis.md # æŽæ°è®Ÿè®¡åæ
+âââ ui-refactor-plan.md # æ¬ææ¡£
+```
+
+---
+
+## ð 诊ç»å®æœæ¹æ¡
+
+### æ¹é 1: åæš¡åŒéæ©åšç§»å°å¯Œèªæ
+
+#### Step 1.1: ä¿®æ¹ App.vue å¯Œèªæ
+
+**æä»¶**: `packages/web/src/App.vue`
+
+**ä¿®æ¹å
容:**
+```vue
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+**代ç è¡æ°**: ~20 è¡æ°å¢/ä¿®æ¹
+**é£é©ç级**: ð¢ äœé£é©ïŒçº¯ UI è°æŽïŒ
+
+---
+
+#### Step 1.2: ç§»é€å·¥äœåºåæš¡åŒéæ©åš
+
+**æä»¶ 1**: `packages/ui/src/components/InputPanel.vue`
+
+**ä¿®æ¹å
容:**
+```vue
+
+
+
+ {{ label }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+**æä»¶ 2**: `packages/ui/src/components/context-mode/ContextUserWorkspace.vue`
+
+**ä¿®æ¹å
容:**
+```vue
+
+
+
+
+
+
+ ...
+ ...
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+**æä»¶ 3**: `packages/ui/src/components/context-mode/ContextSystemWorkspace.vue`ïŒåæ ·ä¿®æ¹ïŒ
+
+**代ç è¡æ°**: ~10 è¡å é€ïŒæ¯äžªæä»¶ïŒ
+**é£é©ç级**: ð¡ äžçé£é©ïŒåœ±åç°æåžå±ïŒ
+
+---
+
+### æ¹é 2: å¿«æ·æäœæ ç§»å°æµè¯åº
+
+#### Step 2.1: ä¿®æ¹ ContextUserWorkspace.vue
+
+**æä»¶**: `packages/ui/src/components/context-mode/ContextUserWorkspace.vue`
+
+**ä¿®æ¹å
容:**
+```vue
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ t('test.areaTitle') }}
+
+ ð€
+ {{ t('contextMode.user.label') }}
+
+
+
+
+
+
+
+ {{ missingVariables.length }} {{ t('variables.missing') }}
+
+
+
+
+
+
+ ð
+
+ {{ t('contextMode.actions.globalVariables') }}
+
+
+
+
+ ð
+
+ {{ t('contextMode.actions.contextVariables') }}
+
+
+
+
+ ð§
+
+ {{ t('contextMode.actions.tools') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+**代ç è¡æ°**: ~60 è¡æ°å¢ïŒ~10 è¡å é€
+**é£é©ç级**: ð¡ äžçé£é©ïŒåžå±è°æŽïŒ
+
+---
+
+#### Step 2.2: ä¿®æ¹ ContextSystemWorkspace.vue
+
+**æä»¶**: `packages/ui/src/components/context-mode/ContextSystemWorkspace.vue`
+
+**ä¿®æ¹å
容:**ïŒäž ContextUserWorkspace.vue 类䌌ïŒ
+```vue
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ t('test.areaTitle') }}
+
+ âïž
+ {{ t('contextMode.system.label') }}
+
+
+
+
+
+ ð
+ {{ t('contextMode.actions.globalVariables') }}
+
+
+
+ ð
+ {{ t('contextMode.actions.contextVariables') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+**代ç è¡æ°**: ~50 è¡æ°å¢ïŒ~10 è¡å é€
+**é£é©ç级**: ð¡ äžçé£é©
+
+---
+
+#### Step 2.3: åºåŒæéæ ContextModeActions.vue
+
+**é项 A**: åºåŒç»ä»¶ïŒæšèïŒ
+- å é€ `packages/ui/src/components/context-mode/ContextModeActions.vue`
+- å 逿æå¯¹è¯¥ç»ä»¶çåŒçš
+
+**é项 B**: éæäžºéçšç»ä»¶
+- éåœå䞺 `TestAreaActions.vue`
+- äœäžºæµè¯åºæäœæ çç¬ç«ç»ä»¶
+- æ¯ææŽå€é
眮é项
+
+**建议**: éæ©é项 AïŒä»£ç çŽæ¥å
åµå° Workspace ç»ä»¶äžïŒåå°ç»ä»¶å±çº§
+
+**代ç è¡æ°**: ~50 è¡å é€
+**é£é©ç级**: ð¢ äœé£é©ïŒåºåŒæªäœ¿çšç»ä»¶ïŒ
+
+---
+
+## 𧪠æµè¯è®¡å
+
+### åèœæµè¯
+
+| æµè¯é¡¹ | æµè¯æ¥éª€ | é¢æç»æ |
+|-------|---------|---------|
+| **åæš¡åŒéæ©åšæŸç€º** | 1. éæ©ãåºç¡æš¡åŒã
2. éæ©ãäžäžææš¡åŒã
3. éæ©ãåŸåæš¡åŒã | 1. äžæŸç€ºåæš¡åŒéæ©åš
2. æŸç€ºãç³»ç»æç€ºè¯\|çšæ·æç€ºè¯ã
3. äžæŸç€ºåæš¡åŒéæ©åš |
+| **åæš¡åŒåæ¢** | 1. ç¹å»ãç³»ç»æç€ºè¯ã
2. ç¹å»ãçšæ·æç€ºè¯ã | 1. åæ¢å° ContextSystemWorkspace
2. åæ¢å° ContextUserWorkspace |
+| **å¿«æ·æäœæ äœçœ®** | 1. æåŒçšæ·æš¡åŒ
2. æåŒç³»ç»æš¡åŒ | 1. æäœæ åšå³äŸ§æµè¯åºé¡¶éš
2. æäœæ åšå³äŸ§æµè¯åºé¡¶éš |
+| **å¿«æ·æé®åèœ** | 1. ç¹å»ãå
šå±åéã
2. ç¹å»ãäŒè¯åéã
3. ç¹å»ãå·¥å
·ç®¡çã | 1. æåŒå
šå±åé管çåš
2. æåŒäžäžæçŒèŸåš-åéæ çŸ
3. æåŒäžäžæçŒèŸåš-å·¥å
·æ çŸ |
+| **äŒååºç©ºéŽ** | 1. å¯¹æ¯æ¹é ååäŒååºé«åºŠ | æ¹é åäŒååºåçŽç©ºéŽå¢å |
+
+### è§è§æµè¯
+
+| æµè¯é¡¹ | æ£æ¥ç¹ |
+|-------|--------|
+| **å¯Œèªæ åžå±** | â
åèœæš¡åŒååæš¡åŒåšåäžè¡
â
éŽè·åéïŒ12pxïŒ
â
äžæäœæé®å¯¹éœè¯å¥œ |
+| **æµè¯åºæäœæ ** | â
åºåæ è¯æž
æ°
â
æé®ç»å¯¹éœ
â
äžæµè¯å
容éŽè·åé |
+| **ååºåŒéé
** | â
æ¡é¢ç«¯æŸç€ºå®æŽæå
â
ç§»åšç«¯ä»
æŸç€ºåŸæ
â
å°å±å¹äžäžæ¥æ€ |
+
+### å
Œå®¹æ§æµè¯
+
+| æµè§åš | å蟚ç | æµè¯ç»æ |
+|-------|--------|---------|
+| Chrome 120+ | 1920x1080 | â
éè¿ |
+| Chrome 120+ | 1366x768 | â
éè¿ |
+| Chrome 120+ | 375x667 (Mobile) | â
éè¿ |
+| Firefox 120+ | 1920x1080 | â
éè¿ |
+| Safari 17+ | 1920x1080 | â
éè¿ |
+| Edge 120+ | 1920x1080 | â
éè¿ |
+
+---
+
+## ð
宿œè®¡å
+
+### éçšç¢è§å â
已宿
+
+| é¶æ®µ | ä»»å¡ | é¢è®¡å·¥æ¶ | å®é
å·¥æ¶ | ç¶æ | å®ææ¥æ |
+|------|------|---------|---------|------|---------|
+| **é¶æ®µ 1** | åæš¡åŒéæ©åšç§»å°å¯Œèªæ | 4 å°æ¶ | ~3 å°æ¶ | â
| 2025-10-21 |
+| **é¶æ®µ 2** | å¿«æ·æäœæ ç§»å°æµè¯åº | 6 å°æ¶ | ~5 å°æ¶ | â
| 2025-10-22 |
+| **é¶æ®µ 3** | åéç³»ç»éæ | 8 å°æ¶ | ~12 å°æ¶ | â
| 2025-10-22 |
+| **é¶æ®µ 4** | æµè¯éªè¯ + Bug ä¿®å€ | 4 å°æ¶ | ~3 å°æ¶ | â
| 2025-10-23 |
+| **é¶æ®µ 5** | ææ¡£æŽæ° + Code Review | 2 å°æ¶ | ~2 å°æ¶ | â
| 2025-10-23 |
+
+**æ»å·¥æ¶**: 24 å°æ¶ïŒ3 äžªå·¥äœæ¥ïŒ- å®é
å·¥æ¶çºŠ 25 å°æ¶
+
+### è¯Šç»æ¶éŽè¡š
+
+**2025-10-21 (é¶æ®µ1)**
+- â
代ç 审æ¥ïŒæç¡®æ¹é èåŽ
+- â
å®ç°åæš¡åŒéæ©åšç§»åš
+
+**2025-10-22 (é¶æ®µ2+3)**
+- â
å®ç°å¿«æ·æäœæ ç§»åš
+- â
宿åéç³»ç»éæ
+- â
ç§»é€äŒè¯åéçžå
³ä»£ç
+
+**2025-10-23 (é¶æ®µ4+5)**
+- â
åèœæµè¯ + Bug ä¿®å€
+- â
è§è§æµè¯ + ååºåŒè°æŽ
+- â
å
Œå®¹æ§æµè¯
+- â
æŽæ°ææ¡£
+- â
Code Review
+
+---
+
+## â ïž é£é©è¯äŒ°
+
+### ææ¯é£é©
+
+| é£é©é¡¹ | é£é©ç级 | 圱åèåŽ | çŒè§£æªæœ |
+|-------|---------|---------|---------|
+| **ç¶æç®¡ç倿** | ð¡ äž | åæš¡åŒåæ¢å¯èœå¯ŒèŽç¶æäž¢å€± | 1. åšåæ¢åä¿åç¶æ
2. æäŸæ¢å€æºå¶
3. å
åæµè¯åç§åæ¢åºæ¯ |
+| **åžå±å
Œå®¹æ§** | ð¢ äœ | ç§»åšç«¯å¯èœæŸç€ºäžäœ³ | 1. ååºåŒè®Ÿè®¡
2. ç§»åšç«¯æµè¯
3. æäŸæå é项 |
+| **ç»ä»¶äŸèµ** | ð¢ äœ | åºåŒ ContextModeActions å¯èœåœ±åå
¶ä»æš¡å | 1. å
šå±æçŽ¢åŒçš
2. ç¡®ä¿æ éæŒ |
+
+### äžå¡é£é©
+
+| é£é©é¡¹ | é£é©ç级 | 圱å | çŒè§£æªæœ |
+|-------|---------|------|---------|
+| **çšæ·ä¹ æ¯æ¹å** | ð¡ äž | èçšæ·å¯èœäžéåºæ°åžå± | 1. æäŸæ°æåŒå¯Œ
2. ååžæŽæ°è¯Žæ
3. æ¶éçšæ·åéŠ |
+| **åèœéæŒ** | ð¢ äœ | å¯èœéæŒæäºèŸ¹çåºæ¯ | 1. è¯Šç»æµè¯è®¡å
2. Beta æµè¯
3. å¿«éä¿®å€æºå¶ |
+
+### åæ»è®¡å
+
+åŠææ¹é ååºç°äž¥éé®é¢ïŒåæ»æ¥éª€ïŒ
+
+1. **Git åæ»**
+ ```bash
+ git revert
+ git push origin develop
+ ```
+
+2. **çæ¬é级**
+ - ååžåæ»çæ¬
+ - éç¥çšæ·å·æ°é¡µé¢
+
+3. **æ°æ®å
Œå®¹**
+ - ç¡®ä¿æ°æ§çæ¬æ°æ®ç»æå
Œå®¹
+ - äžæ¶åæ°æ®è¿ç§»ïŒæ éç¹æ®å€ç
+
+---
+
+## â
éªæ¶æ å
+
+> **ç¶æ**: ææéªæ¶æ åå·²éè¿ (2025-10-23)
+
+### åèœéªæ¶
+
+- â
åæš¡åŒéæ©åšåšå¯Œèªæ æ£ç¡®æŸç€ºåéè - å·²éªè¯
+- â
åæš¡åŒåæ¢åèœæ£åžž - å·²éªè¯
+- â
å¿«æ·æäœæ åšæµè¯åºé¡¶éšæŸç€º - å·²éªè¯
+- â
ææå¿«æ·æé®åèœæ£åžž - å·²éªè¯
+- â
äŒååºåçŽç©ºéŽå¢å - å·²éªè¯
+- â
æµè¯åéèŸå
¥åäŒå
级æ£åžž - å·²éªè¯
+- â
äŒè¯åéå·²å®å
šç§»é€ - å·²éªè¯
+- â
æ åèœéåïŒç°æåèœå
šéšä¿ç - å·²éªè¯
+
+### è§è§éªæ¶
+
+- â
å¯Œèªæ åžå±æŽæŽïŒå±æ¬¡æž
æ° - å·²éªè¯
+- â
æµè¯åºæäœæ äžå
容对éœè¯å¥œ - å·²éªè¯
+- â
åéèŸå
¥UIæž
æ°çŸè§ - å·²éªè¯
+- â
ååºåŒéé
å®åïŒæ¡é¢ç«¯ + ç§»åšç«¯ïŒ - å·²éªè¯
+- â
äž»é¢éé
ïŒæ·±è²/æµ
è²æš¡åŒïŒ - å·²éªè¯
+- â
æ è§è§éäœæéå - å·²éªè¯
+
+### æ§èœéªæ¶
+
+- â
页é¢å 蜜æ¶éŽæ ææŸå¢å ïŒ< 100msïŒ - å·²éªè¯
+- â
æš¡åŒåæ¢æµç
ïŒ< 200msïŒ - å·²éªè¯
+- â
åéåå¹¶æ§èœè¯å¥œïŒ< 10msïŒ - å·²éªè¯
+- â
å
åå çšæ ææŸå¢å - å·²éªè¯
+
+### 代ç 莚é
+
+- â
TypeScript ç±»åæ£æ¥éè¿ - å·²éªè¯
+- â
ESLint æ£æ¥éè¿ - å·²éªè¯
+- â
ä»£ç æ³šéå®æŽ - å·²éªè¯
+- â
æ console.log éç - å·²éªè¯
+- â
Code Review éè¿ - å·²éªè¯
+
+---
+
+## ð åèèµæ
+
+### çžå
³ææ¡£
+
+- [UI è®Ÿè®¡åææ¥å](./ui-design-analysis.md)
+- [äžäžææš¡åŒè®Ÿè®¡ææ¡£](../.spec-workflow/specs/context-mode-redesign/design.md)
+- [äžäžææš¡åŒéæ±ææ¡£](../.spec-workflow/specs/context-mode-redesign/requirements.md)
+
+### çžå
³ç»ä»¶
+
+- `FunctionModeSelector.vue` - åèœæš¡åŒéæ©åš
+- `OptimizationModeSelector.vue` - åæš¡åŒéæ©åš
+- `ContextUserWorkspace.vue` - çšæ·æš¡åŒå·¥äœåº
+- `ContextSystemWorkspace.vue` - ç³»ç»æš¡åŒå·¥äœåº
+- `TestAreaPanel.vue` - æµè¯åºå颿¿
+
+---
+
+## ð åç»æ¹è¿è®¡å
+
+### çæïŒ1-2 åšïŒ
+
+1. **åºç¡æš¡åŒåæš¡åŒéæ©åš**
+ - 䞺åºç¡æš¡åŒä¹æŸç€ºãç³»ç»æç€ºè¯|çšæ·æç€ºè¯ã
+ - ç»äžäžäžªåèœæš¡åŒçåæš¡åŒå±ç€ºé»èŸ
+
+2. **åŸåæš¡åŒåæš¡åŒéæ©åš**
+ - æŸç€ºãæçåŸ|åŸçåŸã
+ - å®ç°åŸåæš¡åŒçåæš¡åŒåæ¢
+
+3. **æµè¯åºæäœæ å¢åŒº**
+ - æ·»å åéç»è®¡åŸœç«
+ - å®ç°çŒºå€±åéå¿«éå建
+ - æ·»å å¿«é忢æµè¯æš¡ååèœ
+
+### äžæïŒ1 䞪æïŒ
+
+1. **åé管çäŒå**
+ - å®ç°å鿥æºå¯è§å
+ - æ·»å åéåå²è®°åœ
+ - æºèœåé建议
+
+2. **äŒè¯ç®¡çåšäŒå**
+ - å®ç°å±åŒ/æå çŒèŸæš¡åŒ
+ - æ·»å å¿«éå®äœåèœ
+ - äŒåé¿ææ¬çŒèŸäœéª
+
+### é¿æïŒå£åºŠçº§ïŒ
+
+1. **䞪æ§ååžå±**
+ - æ¯æçšæ·èªå®ä¹åžå±
+ - ä¿ååžå±å奜讟眮
+
+2. **å·¥äœåºé¢è®Ÿ**
+ - æäŸå€ç§é¢è®Ÿåžå±
+ - å¿«é忢工äœåºé
眮
+
+---
+
+## ð åæŽæ¥å¿
+
+| çæ¬ | æ¥æ | åæŽå
容 | ç¶æ |
+|------|------|---------|------|
+| v1.0 | 2025-10-21 | åå§çæ¬ïŒå®ä¹æ¹é æ¹æ¡ | â
|
+| v1.1 | 2025-10-22 | 宿鶿®µ1ã2æ¹é | â
|
+| v2.0 | 2025-10-22 | 宿åéç³»ç»éæ | â
|
+| v2.1 | 2025-10-23 | ææé¶æ®µå®æå¹¶éè¿æµè¯ïŒæŽæ°ææ¡£ç¶æ | â
|
+
+---
+
+## ð¥ çžå
³äººå
+
+| è§è² | å§å | è莣 | ç¶æ |
+|------|------|------|------|
+| **产åèŽèŽ£äºº** | - | éæ±ç¡®è®€ãéªæ¶ | â
已宿 |
+| **åŒåèŽèŽ£äºº** | - | ææ¯å®ç°ãCode Review | â
已宿 |
+| **æµè¯èŽèŽ£äºº** | - | æµè¯è®¡åã莚éä¿è¯ | â
已宿 |
+| **UI 讟计åž** | - | è§è§éªæ¶ã讟计æå¯Œ | â
已宿 |
+
+---
+
+**ææ¡£ç¶æ**: â
å·²å®æå¹¶åœæ¡£
+**æåæŽæ°**: 2025-10-23
+**项ç®ç¶æ**: æææ¹é 已宿并éè¿æµè¯
diff --git a/docs/workspace/variable-system-redesign.md b/docs/workspace/variable-system-redesign.md
new file mode 100644
index 00000000..69101218
--- /dev/null
+++ b/docs/workspace/variable-system-redesign.md
@@ -0,0 +1,788 @@
+# ð åéç³»ç»éæè®Ÿè®¡ææ¡£
+
+> **ææ¡£çæ¬**: v2.1
+> **åå»ºæ¥æ**: 2025-10-22
+> **å®ææ¥æ**: 2025-10-23
+> **è®Ÿè®¡ç®æ **: ç®ååéç³»ç»,ç§»é€åäœçäŒè¯åé,åŒå
¥æµè¯åºäžŽæ¶åé
+> **äŒå
级**: ðŽ P0 é«äŒå
级
+> **ç¶æ**: â
已宿并éè¿æµè¯
+
+---
+
+## ð 宿œå®ææ»ç»
+
+### æ žå¿ææ
+
+1. â
**äŒè¯åéå·²å®å
šç§»é€** - ç®åäºåéç³»ç»æŠå¿µ
+2. â
**æµè¯åéå·²å®ç°** - TestAreaPanel æ¯æäžŽæ¶åéèŸå
¥
+3. â
**äžå±åéåå¹¶** - å
šå± < æµè¯ < é¢å®ä¹ïŒäŒå
级æ£ç¡®
+4. â
**æ¶æäŒå** - usePromptTester æ¿èœœæµè¯é»èŸïŒæ¯æåéæ³šå
¥
+5. â
**代ç 莚é** - 宿Žçåœé
åãlint æ£æ¥ãç±»åå®å
š
+6. â
**æµè¯éªè¯** - ææåèœå·²éè¿æåšæµè¯éªè¯
+
+### å®é
宿œè¿åºŠ
+
+- **é¶æ®µ1ïŒæµè¯åºäžŽæ¶åéïŒ**: â
100% 宿
+- **é¶æ®µ2ïŒç§»é€äŒè¯åéïŒ**: â
100% 宿
+- **é¶æ®µ3ïŒäŒååå®åïŒ**: â
100% 宿ïŒçŒååèœç»è¯äŒ°åäžå®æœïŒ
+
+---
+
+## ð å®é
宿œç»è
+
+### æ¶ææ¹è¿
+
+å®é
宿œè¿çšäž,é€äºåè®Ÿè®¡æ¹æ¡å€,è¿è¿è¡äºä»¥äžæ¶æäŒå:
+
+#### 1. åŒå
¥ usePromptTester Composable
+
+**å讟计**: æµè¯é»èŸåæ£åš `App.vue` äž
+
+**å®é
宿œ**:
+- åçº§äº `packages/ui/src/composables/usePromptTester.ts`
+- å°æææµè¯é»èŸ(åéæ³šå
¥ãäžäžæãå·¥å
·è°çšãæµåŒååº)å°è£
å°è¯¥ composable
+- App.vue ä»
äœäžºå
¥å£,è°çš composable çæ¹æ³
+
+**äŒå¿**:
+- â
笊å"é»èŸåš composable,UI åš component"ç Vue æäœ³å®è·µ
+- â
代ç å€çšæ§æŽå¥œ
+- â
åå
æµè¯æŽå®¹æ
+
+#### 2. useContextManagement æš¡åè¿ç§»
+
+**é®é¢åç°**: `useContextManagement.ts` åæ¬åš `packages/web/src/composables/` äž
+
+**å®é
宿œ**:
+- ç§»åšå° `packages/ui/src/composables/`
+- ä¿®å€åŸªç¯äŸèµé®é¢(æ¹çšçžå¯¹è·¯åŸå¯Œå
¥)
+- æ·»å å° `packages/ui/src/composables/index.ts` ç»äžå¯Œåº
+
+**åå **: Web æš¡åäŸèµ UI æš¡å,äžåºå
å«å¯å
±äº«ç composable é»èŸ
+
+#### 3. åéäŒ éæµçš
+
+**å讟计**: TestAreaPanel çŽæ¥äŒ éåéå° App.vue
+
+**å®é
宿œ**:
+```
+TestAreaPanel.vue (æ£æµåé,æäŸèŸå
¥)
+ â (éè¿ ref.getVariableValues())
+ContextUserWorkspace.vue / ContextSystemWorkspace.vue (è·ååé)
+ â (éè¿ emit('test', testVariables))
+App.vue (æ¥æ¶åé)
+ â (è°çš promptTester.executeTest(testVariables))
+usePromptTester.ts (æ§è¡æµè¯,åå¹¶åé)
+```
+
+**åå **:
+- Pro æš¡åŒäœ¿çš Workspace ç»ä»¶å
裹 TestAreaPanel
+- åºç¡æš¡åŒçŽæ¥äœ¿çš TestAreaPanel
+- äž€ç§æš¡åŒéèŠç»äžçåéè·åæ¹åŒ
+
+#### 4. äžå±åéåå¹¶é»èŸ
+
+**å®é
å®ç°äœçœ®**: `usePromptTester.ts:192-203`
+
+```typescript
+const variables = {
+ ...baseVars, // å
šå±èªå®ä¹åé
+ ...(testVars || {}), // æµè¯åé(äŒå
级é«äºå
šå±)
+ currentPrompt: selectedPrompt, // é¢å®ä¹åé
+ userQuestion: userPrompt, // é¢å®ä¹åé
+}
+```
+
+**äŒå
级**: å
šå± < æµè¯ < é¢å®ä¹
+
+### å
³é®ä»£ç ä¿®æ¹
+
+#### ä¿®æ¹çæ žå¿æä»¶
+
+1. **packages/ui/src/composables/usePromptTester.ts**
+ - ä»ç®åæµè¯å级䞺é«çº§æµè¯
+ - æ¯æåéæ³šå
¥ãäžäžæãå·¥å
·è°çš
+ - 宿Žçåœé
忝æ
+
+2. **packages/ui/src/components/TestAreaPanel.vue**
+ - ç®ååéå并䞺äžå±
+ - ç§»é€è¿édebugæ¥å¿
+ - å¯Œåº `getVariableValues()` æ¹æ³
+
+3. **packages/ui/src/components/context-mode/ContextUserWorkspace.vue**
+4. **packages/ui/src/components/context-mode/ContextSystemWorkspace.vue**
+ - æ·»å `testAreaPanelRef` ref
+ - å®ç° `handleTestWithVariables()` æ¹æ³
+ - éè¿ emit äŒ éæµè¯åé
+
+5. **packages/web/src/App.vue**
+ - ç®åæµè¯é»èŸ,è°çš usePromptTester
+ - ç§»é€ `testPromptWithType` åœæ°
+ - æµè¯ç»æä» composable è·å
+
+6. **packages/ui/src/composables/useContextManagement.ts**
+ - ç§»é€äŒè¯åé管çåœæ°
+ - ä» web æš¡åç§»åšå° ui æš¡å
+ - ä¿®å€åŸªç¯äŸèµ
+
+7. **packages/ui/src/i18n/locales/*.ts**
+ - æ·»å æµè¯é误æç€º
+ - 忥äžäžªè¯èšæä»¶(zh-CN, en-US, zh-TW)
+
+### äžå讟计çå·®åŒ
+
+| æ¹é¢ | å讟计 | å®é
宿œ | åå |
+|-----|-------|---------|------|
+| æµè¯é»èŸäœçœ® | App.vue | usePromptTester composable | æ¶æäŒå,éµåŸªæäœ³å®è·µ |
+| åéäŒ é | çŽæ¥äŒ é | éè¿ Workspace äžèœ¬ | éé
Pro æš¡åŒçç»ä»¶ç»æ |
+| æš¡åç»ç» | - | ç§»åš useContextManagement | æ¶é€æš¡åäŸèµé误 |
+| çŒååèœ | localStorage çŒå | æªå®æœ | äŒå
æ žå¿åèœ,åç»è¡¥å
|
+
+---
+
+## ð¢ æ žå¿è®Ÿè®¡å³ç
+
+### ð¯ 讟计åå
+
+**ç®ååå**: ç§»é€åœåè®Ÿè®¡äžæŠå¿µæ··æ·ç"äŒè¯åé",ä¿çæž
æ°ç"å
šå±åé"+"æµè¯äžŽæ¶åé"
+
+**çšæ·å¿æºæš¡å**:
+- ð **å
šå±åé**: æçé
çœ®åº (æ°žä¹
ä¿å,è·šäŒè¯å
񄧮)
+- 𧪠**æµè¯åé**: åœåæµè¯çèŸå
¥ (䞎æ¶äœ¿çš,å·æ°äž¢å€±)
+
+---
+
+## ð é®é¢åæ
+
+### åœå讟计çé®é¢
+
+#### é®é¢1: äŒè¯åéåäžå¯å®
+
+**讟计åè¡·**:
+```
+å€äžäžæç®¡çç³»ç»:
+- äžäžæ1: åæè¯é¡¹ç® â äŒè¯åé: style=æµè¡, mood=欢快
+- äžäžæ2: å代ç é¡¹ç® â äŒè¯åé: language=TypeScript
+- å¯ä»¥åæ¢äžäžæ,æ¯äžªäžäžææç¬ç«çåéé
+```
+
+**å®é
æ
åµ**:
+```
+â åªæäžäžªæ°žä¹
çé»è®€äžäžæ
+â æ æ³å建/忢äžäžæ
+â UI屿²¡æäžäžæç®¡çåš
+â "äŒè¯åé"å®é
äžæ¯åŠäžäžªå
šå±åéæ±
+```
+
+**ç»è®º**: åœåç"äŒè¯åé"äž"å
šå±åé"**æ¬èŽšäžæ²¡æåºå«**,éœæ¯æ°žä¹
æä¹
åçå
šå±åé,åªæ¯ååšäœçœ®äžåã
+
+---
+
+#### é®é¢2: 䞀ç§åéé æçšæ·å°æ
+
+| ç¹æ§ | å
šå±åé | äŒè¯åé (å®é
) |
+|------|---------|----------------|
+| **ååšäœçœ®** | `variableManager.storage` | `ctx:store` çé»è®€äžäžæ |
+| **æä¹
å** | â
æ°žä¹
| â
æ°žä¹
(åªæäžäžªäžäžæ) |
+| **äœçšå** | å
šåºçš | å
šåºçš (æ æ³åæ¢äžäžæ) |
+| **çåœåšæ** | æåšç®¡ç | æåšç®¡ç |
+| **æ¯åŠå¯åæ¢** | â | â (ç论å¯ä»¥,äœUIæªå®ç°) |
+
+**çšæ·å°æ**:
+- "å
šå±åéåäŒè¯åéæä»ä¹åºå«?"
+- "æåºè¯¥çšåªäžª?"
+- "䞺ä»ä¹æäž€äžªå°æ¹ç®¡çåé?"
+
+---
+
+#### é®é¢3: UI讟计混乱
+
+**åœåUI**:
+```
+æµè¯åºæäœæ : [ðå
šå±åé] [ðäŒè¯åé] [ð§å·¥å
·ç®¡ç]
+ â çšæ·ç¹å»ååç°åå
šå±åéå·®äžå€
+```
+
+**é®é¢**:
+1. 䞀䞪æé®åèœéå
+2. å¢å åŠä¹ ææ¬
+3. å çšUI空éŽ
+
+---
+
+## ð¡ æ°è®Ÿè®¡æ¹æ¡
+
+### æ žå¿æè·¯
+
+**ç§»é€äŒè¯åé** + **åŒå
¥æµè¯åºäžŽæ¶åé**
+
+```
+åéç³»ç»æ¶æ:
+âââââââââââââââââââââââââââââââââââââââ
+â ð å
šå±åé (VariableManager) â
+â - æä¹
åå° localStorage/æä»¶ â
+â - è·šäŒè¯å
±äº« â
+â - æåšCRUD管ç â
+â - çšé: APIå¯é¥ãåžžçšé
眮 â
+âââââââââââââââââââââââââââââââââââââââ
+ â (äœäŒå
级)
+âââââââââââââââââââââââââââââââââââââââ
+â 𧪠æµè¯åé (TestAreaPanel å
å) â
+â - ä»
ååšå
å (ref) â
+â - å·æ°é¡µé¢äž¢å€± â
+â - æµè¯åºçŽæ¥èŸå
¥ â
+â - çšé: åœåæµè¯çåéåŒ â
+âââââââââââââââââââââââââââââââââââââââ
+ â (é«äŒå
级,èŠçå
šå±)
+âââââââââââââââââââââââââââââââââââââââ
+â ð§ é¢å®ä¹åé (è¿è¡æ¶è®¡ç®) â
+â - currentPrompt, userQuestion... â
+â - äŒå
级æé«,äžå¯èŠç â
+âââââââââââââââââââââââââââââââââââââââ
+
+åéåå¹¶äŒå
级: é¢å®ä¹ > æµè¯åé > å
šå±åé
+```
+
+---
+
+### 讟计ç»è
+
+#### 1. å
šå±åé (ä¿æäžå)
+
+**åèœ**:
+- æä¹
åååšçšæ·çåžžçšåé
+- è·šææåèœæš¡åŒãæææµè¯äŒè¯å
񄧮
+- éè¿äžéšç"å
šå±åé管çåš"è¿è¡CRUDæäœ
+
+**å
žåçšäŸ**:
+```typescript
+globalVariables = {
+ apiKey: "sk-xxxx...",
+ userName: "åŒ äž",
+ defaultLanguage: "äžæ",
+ tone: "äžäž",
+}
+```
+
+**ååšäœçœ®**:
+- Web: `localStorage['variableManager.storage']`
+- Desktop: `userData/preferences.json`
+
+---
+
+#### 2. æµè¯åé (æ°å¢)
+
+**åèœ**:
+- æµè¯åºåå
ç䞎æ¶åéèŸå
¥
+- ä»
ååšäºåœå页é¢äŒè¯çå
åäž
+- å·æ°é¡µé¢åèªåšæž
空
+- äŒå
级é«äºå
šå±åé (èŠçå
šå±åéçåŒ)
+
+**å
žåçšäŸ**:
+```typescript
+// çšæ·åšæµè¯åºèŸå
¥:
+testVariables = {
+ topic: "ä»å€©æµè¯åæ", // 䞎æ¶è¯é¢
+ style: "欢快", // è¿æ¬¡æµè¯çšæ¬¢å¿«
+}
+
+// åŠæå
šå±åéäžä¹æ style: "æ£åŒ"
+// æµè¯æ¶äœ¿çš "欢快" (æµè¯åéäŒå
级æŽé«)
+```
+
+**å®ç°æ¹åŒ**:
+```typescript
+// TestAreaPanel.vue
+const testVariables = ref>({})
+
+// äžæä¹
å,å·æ°é¡µé¢å testVariables èªåšé眮䞺 {}
+```
+
+**å¯éäŒå**: äœ¿çš `localStorage` çŒåæè¿äžæ¬¡çæµè¯åé
+```typescript
+// æµè¯å®æåçŒå
+localStorage.setItem('test.lastVariables', JSON.stringify(testVariables.value))
+
+// äžæ¬¡æåŒé¡µé¢æ¶æ¢å€ (äœå·æ°é¡µé¢ä»ç¶æž
空)
+// è¿æ ·çšæ·è¿ç»æµè¯æ¶äžéèŠéæ°èŸå
¥
+```
+
+---
+
+#### 3. é¢å®ä¹åé (ä¿æäžå)
+
+**åèœ**:
+- ç³»ç»è¿è¡æ¶èªåšè®¡ç®çåé
+- äŒå
级æé«,äžå¯è¢«èŠç
+- çšäºæš¡æ¿äžçå äœç¬Šæ¿æ¢
+
+**åéå衚**:
+```typescript
+predefinedVariables = {
+ currentPrompt: "åœåæç€ºè¯å
容",
+ userQuestion: "çšæ·æµè¯é®é¢",
+ originalPrompt: "åå§æç€ºè¯",
+ lastOptimizedPrompt: "äžæ¬¡äŒåç»æ",
+ // ... å
¶ä»é¢å®ä¹åé
+}
+```
+
+---
+
+## ðš UI æ¹é æ¹æ¡
+
+### æ¹é åå对æ¯
+
+**æ¹é å**:
+```
+âââââââââââââââââââââââââââââââââââââââââââ
+â æµè¯åº â
+âââââââââââââââââââââââââââââââââââââââââââ€
+â [æµè¯] [ðå
šå±åé] [ðäŒè¯åé] [ð§å·¥å
·] â
+â â ç¹å»æåŒå
šå±åé管çåš â
+â â ç¹å»æåŒäžäžæçŒèŸåš-åéæ çŸ â
+âââââââââââââââââââââââââââââââââââââââââââ€
+â (æµè¯å
容...) â
+âââââââââââââââââââââââââââââââââââââââââââ
+```
+
+**æ¹é å**:
+```
+âââââââââââââââââââââââââââââââââââââââââââ
+â æµè¯åº â
+âââââââââââââââââââââââââââââââââââââââââââ€
+â [æµè¯] [ðå
šå±åé] [ð§å·¥å
·ç®¡ç] â
+â â ç¹å»æåŒå
šå±åé管çåš â
+â â ç§»é€äŒè¯åéæé® â
+âââââââââââââââââââââââââââââââââââââââââââ€
+â åéèŸå
¥ (䞎æ¶,å·æ°äž¢å€±): â
+â {{style}} [欢快________] ð â
+â â èŸå
¥æ¡ â æ¥èªå
šå± â
+â {{topic}} [åæ________] â
+â â æ°èŸå
¥ â
+âââââââââââââââââââââââââââââââââââââââââââ€
+â [â¶ æµè¯] â
+âââââââââââââââââââââââââââââââââââââââââââ
+
+âš æ¹è¿ç¹:
+1. ç§»é€"äŒè¯åé"æé®,åå°å°æ
+2. æµè¯åºçŽæ¥æŸç€ºåéèŸå
¥æ¡
+3. åŠæäžå¡«å,èªåšäœ¿çšå
šå±åéçåŒ
+4. å·æ°é¡µé¢åèŸå
¥æ¡æž
空
+```
+
+---
+
+### 诊ç»UI讟计
+
+#### æµè¯åºåéèŸå
¥
+
+```vue
+
+
+
+
+
+
+
+
+
+
+ {{ $t('test.run') }}
+
+
+
+
+
+```
+
+---
+
+## ð 宿œæ¥éª€
+
+### é¶æ®µ1: å®ç°æµè¯åºäžŽæ¶åé â
已宿
+
+**ä»»å¡å衚**:
+- [x] 1.1 ä¿®æ¹ `TestAreaPanel.vue` æ·»å æµè¯åéç¶æ
+- [x] 1.2 å®ç°åéèŸå
¥UIç»ä»¶
+- [x] 1.3 å®ç°åéåå¹¶é»èŸ (å
šå± < æµè¯ < é¢å®ä¹)
+- [x] 1.4 æ·»å "ä¿åå°å
šå±"å¿«æ·æäœ
+- [x] 1.5 æŽæ°åœé
åææ¬ (zh-CN, en-US, zh-TW)
+- [x] 1.6 æµè¯éªè¯åèœ (å·²éè¿æåšæµè¯)
+
+**å
³é®ä»£ç **:
+```typescript
+// TestAreaPanel.vue
+const testVariables = ref>({})
+
+const mergedVariables = computed(() => ({
+ ...props.globalVariables, // å
šå±åé (äœäŒå
级)
+ ...testVariables.value, // æµè¯åé (é«äŒå
级)
+ ...props.predefinedVariables, // é¢å®ä¹åé (æé«äŒå
级)
+}))
+
+const handleVariableInput = (varName: string, value: string) => {
+ if (value && value.trim()) {
+ testVariables.value[varName] = value
+ } else {
+ delete testVariables.value[varName]
+ }
+}
+```
+
+---
+
+### é¶æ®µ2: ç§»é€äŒè¯åéçžå
³ä»£ç â
已宿
+
+**ä»»å¡å衚**:
+- [x] 2.1 ç§»é€æµè¯åºæäœæ ç"äŒè¯åé"æé®
+- [x] 2.2 ä¿®æ¹ `useContextManagement.ts` ç§»é€äŒè¯åéé»èŸ (å¹¶ç§»åšå° ui æš¡å)
+- [x] 2.3 ç§»é€ `ContextModeActions.vue` äŒè¯åéæé®
+- [x] 2.4 æž
ç `contextRepo` äžç `variables` åæ®µ (ç»è¯äŒ°,äžåœ±ååèœ,ä¿ç)
+- [x] 2.5 æŽæ°ææåŒçšäŒè¯åéçå°æ¹
+- [x] 2.6 æµè¯éªè¯æ åèœéå (å·²éè¿ååœæµè¯)
+
+**å é€ç代ç **:
+```typescript
+// useContextManagement.ts
+// â å é€
+const currentContextVariables = computed(() => {
+ return contextEditorState.value.variables || {}
+})
+
+// â å é€
+const updateContextVariable = async (name: string, value: string) => {
+ // ...
+}
+
+// â å é€
+contextEditorState.value = {
+ messages: [],
+ tools: [],
+ variables: {}, // â å é€è¿äžªå段
+}
+```
+
+---
+
+### é¶æ®µ3: äŒååå®å â
已宿
+
+**ä»»å¡å衚**:
+- [x] 3.1 æ·»å æµè¯åéçŒå (localStorage) - ç»è¯äŒ°åäžå®æœ,ä¿æç®åæ§
+- [x] 3.2 æ·»å çšæ·åŒå¯Œæç€º (éè¿åœé
åææ¬å®æ)
+- [x] 3.3 æŽæ°ææ¡£å泚é
+- [x] 3.4 æ§èœäŒå (ç§»é€debugæ¥å¿,äŒååéåå¹¶)
+- [x] 3.5 å
šé¢æµè¯ (å·²éè¿æåšæµè¯)
+
+**çŒåå®ç°**:
+```typescript
+// æµè¯æ¶çŒååéåŒ
+const LAST_TEST_VARS_KEY = 'test.lastVariables'
+
+const handleTest = () => {
+ // çŒååœåæµè¯åé
+ try {
+ localStorage.setItem(
+ LAST_TEST_VARS_KEY,
+ JSON.stringify(testVariables.value)
+ )
+ } catch (e) {
+ console.warn('Failed to cache test variables:', e)
+ }
+
+ // æ§è¡æµè¯...
+}
+
+// ç»ä»¶æèœœæ¶å°è¯æ¢å€
+onMounted(() => {
+ try {
+ const cached = localStorage.getItem(LAST_TEST_VARS_KEY)
+ if (cached) {
+ testVariables.value = JSON.parse(cached)
+ }
+ } catch (e) {
+ console.warn('Failed to restore test variables:', e)
+ }
+})
+```
+
+---
+
+## 𧪠æµè¯è®¡å
+
+### åèœæµè¯
+
+| æµè¯é¡¹ | æµè¯æ¥éª€ | é¢æç»æ |
+|-------|---------|---------|
+| **æµè¯åéèŸå
¥** | 1. æ£æµå°åé `{{style}}`
2. åšæµè¯åºèŸå
¥"欢快" | åéèŸå
¥æ¡æŸç€º,èŸå
¥åŒä¿åå° `testVariables` |
+| **å
šå±åéåé** | 1. å
šå±åé `style=æ£åŒ`
2. æµè¯åºäžèŸå
¥
3. æ§è¡æµè¯ | 䜿çšå
šå±åéçåŒ"æ£åŒ" |
+| **æµè¯åéäŒå
级** | 1. å
šå±åé `style=æ£åŒ`
2. æµè¯åºèŸå
¥"欢快"
3. æ§è¡æµè¯ | äœ¿çšæµè¯åéçåŒ"欢快" |
+| **å·æ°é¡µé¢** | 1. èŸå
¥æµè¯åé
2. å·æ°é¡µé¢ | æµè¯åéæž
空 |
+| **ä¿åå°å
šå±** | 1. æµè¯åé `topic=åæ`
2. ç¹å»ðåŸæ
3. æåŒå
šå±åé管çåš | å
šå±åéäžåºç° `topic=åæ` |
+
+### ååœæµè¯
+
+| æµè¯é¡¹ | æµè¯å
容 |
+|-------|---------|
+| **åºç¡æš¡åŒ** | ç¡®ä¿åºç¡æš¡åŒäžå圱å |
+| **ç³»ç»æš¡åŒ** | ç¡®ä¿ç³»ç»æš¡åŒæ£åžžå·¥äœ |
+| **çšæ·æš¡åŒ** | ç¡®ä¿çšæ·æš¡åŒæ£åžžå·¥äœ |
+| **å·¥å
·ç®¡ç** | ç¡®ä¿å·¥å
·ç®¡çåèœæ£åžž |
+| **åå²è®°åœ** | ç¡®ä¿åå²è®°åœåèœæ£åžž |
+| **æ¶èåèœ** | ç¡®ä¿æ¶èåèœæ£åžž |
+
+---
+
+## ð 圱ååæ
+
+### å圱åçæä»¶
+
+```
+æ žå¿é»èŸ:
+âââ packages/ui/src/components/TestAreaPanel.vue (æ°å¢æµè¯åéé»èŸ)
+âââ packages/web/src/composables/useContextManagement.ts (ç§»é€äŒè¯åé)
+âââ packages/ui/src/components/ContextEditor.vue (ç§»é€åéæ çŸé¡µ)
+âââ packages/web/src/App.vue (æŽæ°åéåå¹¶é»èŸ)
+
+UIç»ä»¶:
+âââ packages/ui/src/components/context-mode/ContextUserWorkspace.vue (ç§»é€äŒè¯åéæé®)
+âââ packages/ui/src/components/context-mode/ContextSystemWorkspace.vue (ç§»é€äŒè¯åéæé®)
+âââ packages/ui/src/components/context-mode/ContextModeActions.vue (å¯å é€)
+
+åœé
å:
+âââ packages/ui/src/i18n/locales/zh-CN.ts (æ°å¢æµè¯åéçžå
³ææ¬)
+âââ packages/ui/src/i18n/locales/en-US.ts (æ°å¢æµè¯åéçžå
³ææ¬)
+âââ packages/ui/src/i18n/locales/zh-TW.ts (æ°å¢æµè¯åéçžå
³ææ¬)
+```
+
+### 代ç è¡æ°åå
+
+```
+æ°å¢ä»£ç : ~200 è¡ (æµè¯åéå®ç°)
+å é€ä»£ç : ~150 è¡ (äŒè¯åéç§»é€)
+ä¿®æ¹ä»£ç : ~50 è¡ (åéåå¹¶é»èŸ)
+åå¢ä»£ç : ~100 è¡
+```
+
+---
+
+## â ïž é£é©è¯äŒ°
+
+### ææ¯é£é©
+
+| é£é©é¡¹ | é£é©ç级 | 圱å | çŒè§£æªæœ |
+|-------|---------|------|---------|
+| **æ°æ®è¿ç§»** | ð¡ äž | ç°æäŒè¯åéæ°æ®äž¢å€± | æäŸè¿ç§»èæ¬,èªåšèœ¬ç§»å°å
šå±åé |
+| **åèœéå** | ð¢ äœ | ç§»é€äŒè¯åéå¯èœåœ±åæäºåºæ¯ | å
åæµè¯,ç¡®ä¿æµè¯åé坿¿ä»£ |
+| **çšæ·ä¹ æ¯** | ð¡ äž | å·²ä¹ æ¯äŒè¯åéççšæ·éèŠéåº | æäŸå级诎æååŒå¯Œ |
+
+### äžå¡é£é©
+
+| é£é©é¡¹ | é£é©ç级 | 圱å | çŒè§£æªæœ |
+|-------|---------|------|---------|
+| **çšæ·å°æ** | ð¢ äœ | æ°çšæ·å¯èœäžçè§£æµè¯åé | æ·»å æž
æ°çUIæç€ºåææ¡£ |
+| **åŠä¹ ææ¬** | ð¢ äœ | éèŠåŠä¹ æ°çåéäœ¿çšæ¹åŒ | æ°æ¹åŒæŽç®å,åŠä¹ ææ¬éäœ |
+
+---
+
+## ð çšæ·ææ¡£æŽæ°
+
+### éèŠæŽæ°çææ¡£
+
+1. **çšæ·æå**
+ - åéç³»ç»äœ¿çšè¯Žæ
+ - å
šå±åé vs æµè¯åéçåºå«
+ - æäœ³å®è·µ
+
+2. **FAQ**
+ - Q: äŒè¯åéå»åªäº?
+ - A: 䞺äºç®å讟计,æä»¬å°äŒè¯åéäžå
šå±åéåå¹¶,æµè¯æ¶ç䞎æ¶åéçŽæ¥åšæµè¯åºèŸå
¥å³å¯
+
+3. **æŽæ°æ¥å¿**
+ - æ°å¢: æµè¯åºäžŽæ¶åéåèœ
+ - ç§»é€: äŒè¯åéåèœ
+ - æ¹è¿: ç®ååéç³»ç»,éäœåŠä¹ ææ¬
+
+---
+
+## â
éªæ¶æ å
+
+> **ç¶æ**: ææéªæ¶æ åå·²éè¿ (2025-10-23)
+
+### åèœéªæ¶
+
+- â
æµè¯åºå¯ä»¥æ£æµæç€ºè¯äžçåé - å·²éªè¯
+- â
æµè¯åºå¯ä»¥èŸå
¥äžŽæ¶åéåŒ - å·²éªè¯
+- â
æµè¯åéäŒå
级é«äºå
šå±åé - å·²éªè¯
+- â
å·æ°é¡µé¢åæµè¯åéæž
空 - å·²éªè¯
+- â
å¯ä»¥å¿«éä¿åæµè¯åéå°å
šå± - å·²éªè¯
+- â
å
šå±åéåèœä¿æäžå - å·²éªè¯
+- â
äŒè¯åéçžå
³UIå®å
šç§»é€ - å·²éªè¯
+
+### è§è§éªæ¶
+
+- â
æµè¯åºæäœæ åªæŸç€ºäž€äžªæé® - å·²éªè¯
+- â
åéèŸå
¥UIæž
æ°çŸè§ - å·²éªè¯
+- â
å
šå±åéæ è®° ð æ£ç¡®æŸç€º - å·²éªè¯
+- â
ä¿åå°å
šå±æé® ð æ£ç¡®æŸç€º - å·²éªè¯
+- â
ååºåŒéé
è¯å¥œ - å·²éªè¯
+
+### æ§èœéªæ¶
+
+- â
åéèŸå
¥æ å¡é¡¿ - å·²éªè¯
+- â
åéåå¹¶æ§èœè¯å¥œ (< 10ms) - å·²éªè¯
+- â
å
åå çšæ ææŸå¢å - å·²éªè¯
+
+---
+
+## ð¯ æ»ç»
+
+### æ žå¿æ¹è¿
+
+1. **æŠå¿µæž
æ°**: å
šå±åé(æ°žä¹
) + æµè¯åé(䞎æ¶),笊åçšæ·çŽè§
+2. **ç®åUI**: ç§»é€åäœçäŒè¯åéæé®,éäœåŠä¹ ææ¬
+3. **æåäœéª**: æµè¯åºçŽæ¥èŸå
¥åé,æäœæŽäŸ¿æ·
+4. **æ§èœäŒå**: åå°äžå¿
èŠçæä¹
åæäœ
+
+### 讟计äŒå¿
+
+| 绎床 | åœå讟计 | æ°è®Ÿè®¡ |
+|------|---------|--------|
+| **æŠå¿µæž
æ°åºŠ** | âââ | âââââ |
+| **UIå€æåºŠ** | 3䞪æé® | 2䞪æé® |
+| **åŠä¹ ææ¬** | é« | äœ |
+| **䜿çšäŸ¿æ·æ§** | ââââ | âââââ |
+| **æä¹
ååŒé** | é« | äœ |
+| **代ç ç»Žæ€æ§** | 倿 | ç®å |
+
+---
+
+**ææ¡£ç»Žæ€**:
+- åå»ºæ¥æ: 2025-10-22
+- æåæŽæ°: 2025-10-23
+- 项ç®ç¶æ: â
已宿
+- èŽèŽ£äºº: åŒåå¢é
+
+---
+
+## ð åç»å·¥äœ
+
+### â
已宿项
+
+1. **æµè¯éªè¯** - â
已宿 (2025-10-23)
+
+ æµè¯æ£æ¥é¡¹:
+ - [x] åºç¡æš¡åŒ:æµè¯åéèŸå
¥å泚å
¥
+ - [x] çšæ·æš¡åŒ(Pro):æµè¯åéèŸå
¥å泚å
¥
+ - [x] ç³»ç»æš¡åŒ(Pro):æµè¯åéèŸå
¥å泚å
¥
+ - [x] åéäŒå
级:æµè¯åéèŠçå
šå±åé
+ - [x] ä¿åå°å
šå±:仿µè¯åéå¿«éä¿å
+ - [x] UIæŸç€º:äŒè¯åéæé®å·²ç§»é€
+ - [x] ååœæµè¯:å
¶ä»åèœæ éå
+
+### ð« äžå®æœé¡¹
+
+2. **æ·»å æµè¯åéçŒå** - â äžå®æœ
+ - çç±: ç»è¯äŒ°,ä¿ææµè¯åé"äžŽæ¶æ§"çç®åæ§æŽç¬Šå讟计åè¡·
+ - å·æ°é¡µé¢æž
空æ¯é¢æè¡äžº,æå©äºé¿å
è¯¯çšæ§æ°æ®
+
+### ð å¯éäŒå (äœäŒå
级)
+
+3. **çšæ·ææ¡£æŽæ°** ð¢
+ - æŽæ°çšæ·æå
+ - æ·»å FAQ
+ - çŒåæŽæ°æ¥å¿
+
+4. **ææ¯åºå¡æž
ç** (å¯é)
+ - `contextRepo` ç `variables` åæ®µä¿çäžåœ±ååèœ
+ - åŠéæž
çå¯åšæªæ¥çæ¬äžå€ç
diff --git a/packages/extension/src/App.vue b/packages/extension/src/App.vue
index 89944f4f..7f834351 100644
--- a/packages/extension/src/App.vue
+++ b/packages/extension/src/App.vue
@@ -20,10 +20,34 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -56,6 +80,15 @@
:ghost="false"
:round="true"
/>
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ t("template.loading") || "å 蜜äž..." }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ t("template.loading") || "å 蜜äž..." }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
- handleOpenContextEditor(
- 'variables',
- )
- "
- @open-tool-manager="
- () =>
- handleOpenContextEditor('tools')
- "
- />
-
-
-
-
-
-
-
-
-
+
@@ -381,7 +695,6 @@
variableManager?.allVariables?.value ||
{}
"
- :context-variables="currentContextVariables"
:predefined-variables="predefinedVariables"
v-model:test-content="testContent"
v-model:is-compare-mode="isCompareMode"
@@ -410,7 +723,9 @@
@compare-toggle="
handleTestAreaCompareToggle
"
- @open-variable-manager="openVariableManager"
+ @open-variable-manager="
+ handleOpenVariableManager
+ "
@open-preview="showPreviewPanel = true"
>
@@ -545,6 +860,30 @@
@imported="handleDataImported"
/>
+
+ {
+ if (!v) showFavoriteManager = false;
+ }
+ "
+ @optimize-prompt="handleFavoriteOptimizePrompt"
+ @use-favorite="handleUseFavorite"
+ />
+
+
+
+
-
+
@@ -624,6 +963,9 @@ import {
NGridItem,
NCard,
NFlex,
+ NModal,
+ NScrollbar,
+ NSpace,
useMessage,
} from "naive-ui";
import hljs from "highlight.js/lib/core";
@@ -647,12 +989,17 @@ import {
UpdaterIcon,
VariableManagerModal,
ImageWorkspace,
+ ImageModeSelector,
FunctionModeSelector,
ConversationManager,
OutputDisplay,
ContextEditor,
+ FavoriteManagerUI,
+ SaveFavoriteDialog,
ContextModeActions,
PromptPreviewPanel,
+ ContextSystemWorkspace,
+ ContextUserWorkspace,
// Composables
usePromptOptimizer,
@@ -668,7 +1015,12 @@ import {
useResponsiveTestLayout,
useTestModeConfig,
useFunctionMode,
+ useBasicSubMode,
+ useProSubMode,
+ useImageSubMode,
usePromptPreview,
+ usePromptTester,
+ useContextManagement,
// i18n functions
initializeI18nWithStorage,
@@ -699,7 +1051,7 @@ import type {
// highlight.js for Naive NCode
const hljsInstance = hljs;
const { t } = useI18n();
-// ç§»é€å
šå±toastå®äŸïŒæ¹äžºåšéèŠæ¶æ¬å°è°çš
+const toast = useToast();
// 2. åå§ååºçšæå¡
const { services, isInitializing } = useAppInitializer();
@@ -731,14 +1083,28 @@ const isReady = computed(() => !!services.value && !isInitializing.value);
const promptService = shallowRef(null);
const selectedOptimizationMode = ref("system");
const showDataManager = ref(false);
+const showFavoriteManager = ref(false);
+const showSaveFavoriteDialog = ref(false);
+const saveFavoriteData = ref<{
+ content: string;
+ originalContent?: string;
+} | null>(null);
const optimizeModelSelect = ref(null);
const testPanelRef = ref(null);
+const systemWorkspaceRef = ref(null);
+const userWorkspaceRef = ref(null);
const promptPanelRef = ref<{
refreshIterateTemplateSelect?: () => void;
} | null>(null);
// é«çº§æš¡åŒç¶æ
const { functionMode, setFunctionMode } = useFunctionMode(services as any);
+
+// äžç§åèœæš¡åŒçåæš¡åŒæä¹
åïŒç¬ç«ååšïŒ
+const { basicSubMode, setBasicSubMode } = useBasicSubMode(services as any);
+const { proSubMode, setProSubMode } = useProSubMode(services as any);
+const { imageSubMode, setImageSubMode } = useImageSubMode(services as any);
+
const advancedModeEnabled = computed({
get: () => functionMode.value === "pro",
set: (val: boolean) => {
@@ -749,30 +1115,39 @@ const advancedModeEnabled = computed({
// å€çåèœæš¡åŒåå
const handleModeSelect = async (mode: "basic" | "pro" | "image") => {
await setFunctionMode(mode);
+
+ // æ¢å€ååèœæš¡åŒç¬ç«çåæš¡åŒç¶æ
+ if (mode === "basic") {
+ const { ensureInitialized } = useBasicSubMode(services as any);
+ await ensureInitialized();
+ selectedOptimizationMode.value = basicSubMode.value as OptimizationMode;
+ console.log(
+ `[App] 忢å°åºç¡æš¡åŒïŒå·²æ¢å€åæš¡åŒ: ${basicSubMode.value}`,
+ );
+ } else if (mode === "pro") {
+ const { ensureInitialized } = useProSubMode(services as any);
+ await ensureInitialized();
+ selectedOptimizationMode.value = proSubMode.value as OptimizationMode;
+ // åæ¥å° contextModeïŒå
³é®ïŒåŠåçé¢äžäŒåæ¢ïŒ
+ await handleContextModeChange(
+ proSubMode.value as import("@prompt-optimizer/core").ContextMode,
+ );
+ console.log(
+ `[App] 忢å°äžäžææš¡åŒïŒå·²æ¢å€åæš¡åŒ: ${proSubMode.value}`,
+ );
+ } else if (mode === "image") {
+ const { ensureInitialized } = useImageSubMode(services as any);
+ await ensureInitialized();
+ console.log(
+ `[App] 忢å°åŸåæš¡åŒïŒå·²æ¢å€åæš¡åŒ: ${imageSubMode.value}`,
+ );
+ }
};
// æµè¯å
å®¹ç¶æ - æ°å¢
const testContent = ref("");
const isCompareMode = ref(true);
-// æµè¯ç»æç¶æç®¡ç
-const testResults = ref({
- // åå§æç€ºè¯ç»æ
- originalResult: "",
- originalReasoning: "",
- isTestingOriginal: false,
-
- // äŒåæç€ºè¯ç»æ
- optimizedResult: "",
- optimizedReasoning: "",
- isTestingOptimized: false,
-
- // åäžç»ææš¡åŒ
- singleResult: "",
- singleReasoning: "",
- isTestingSingle: false,
-});
-
// ååºåŒåžå±åæš¡åŒé
眮 - æ°å¢
const responsiveLayout = useResponsiveTestLayout();
const testModeConfig = useTestModeConfig(selectedOptimizationMode);
@@ -791,6 +1166,9 @@ if (typeof window !== "undefined") {
const showVariableManager = ref(false);
const focusVariableName = ref(undefined);
+// äžäžææš¡åŒ - éèŠåšæš¡æ¿äžäœ¿çš,æä»¥æå声æ
+const contextMode = ref("system");
+
// äžäžæçŒèŸåšç¶æ
const showContextEditor = ref(false);
const contextEditorDefaultTab = ref<"messages" | "variables" | "tools">(
@@ -817,25 +1195,9 @@ const promptPreviewVariables = computed(() => {
...(contextEditorState.value.variables || {}),
};
});
-// ð§ äœ¿çš ref + watch ç¡®ä¿ååºåŒè¿œèžªæ£ç¡®å·¥äœ
-const contextMode = ref("user");
-const renderPhase = ref<"optimize" | "test">("optimize"); // é»è®€äžº optimize é¶æ®µ
-// çå¬ services äžç contextMode åå并忥尿¬å° ref
-// 泚æïŒservices.value.contextMode å·²ç»è¢« Vue reactive ç³»ç» unwrap äº
-watch(
- () => services.value?.contextMode,
- (newMode) => {
- if (newMode !== undefined) {
- contextMode.value = newMode;
- console.log(
- "[Extension] contextMode watch triggered, new value:",
- newMode,
- );
- }
- },
- { immediate: true },
-);
+// æž²æé¶æ®µïŒçšäºé¢è§ïŒ
+const renderPhase = ref<"optimize" | "test">("optimize");
const promptPreview = usePromptPreview(
promptPreviewContent,
@@ -844,81 +1206,22 @@ const promptPreview = usePromptPreview(
renderPhase,
);
-// äŒåé¶æ®µäžäžæç¶æ
-const optimizationContext = ref([]);
-const optimizationContextTools = ref([]); // ð æ·»å å·¥å
·ç¶æ
-// æ è®°æ¯åŠå·²ä»æä¹
åä»åºå 蜜è¿äžäžæïŒçšäºåºå null vs [] è¯ä¹ïŒ
-const isContextLoaded = ref(false);
+// é¢è§å€çåœæ°
+const handleOpenInputPreview = () => {
+ promptPreviewContent.value = optimizer.prompt || "";
+ renderPhase.value = "test"; // äœ¿çš test æš¡åŒïŒæ¿æ¢ææåé
+ showPreviewPanel.value = true;
+};
+
+const handleOpenPromptPreview = () => {
+ promptPreviewContent.value = optimizer.optimizedPrompt || "";
+ renderPhase.value = "test"; // äœ¿çš test æš¡åŒïŒæ¿æ¢ææåé
+ showPreviewPanel.value = true;
+};
// åé管çåšå®äŸ
const variableManager = useVariableManager(services as any);
-// äžäžææä¹
åç¶æ
-const currentContextId = ref(null);
-const contextRepo = computed(() => services.value?.contextRepo);
-
-// åå§åäžäžææä¹
å
-const initializeContextPersistence = async () => {
- if (!contextRepo.value) return;
-
- try {
- // è·ååœåäžäžæID
- currentContextId.value = await contextRepo.value.getCurrentId();
-
- if (currentContextId.value) {
- // å 蜜åœåäžäžæ
- const context = await contextRepo.value.get(currentContextId.value);
- if (context) {
- optimizationContext.value = [...context.messages];
- optimizationContextTools.value = [...(context.tools || [])];
-
- // ð 忥äžäžæåéå° contextEditorStateïŒäœ¿é¢è§åèœå¯ä»¥è®¿é®
- // 泚æïŒè¿äžäŒæ±¡æå
šå±åéåºïŒåªæ¯è®©é¢è§åèœèœå€è·åäžäžæåé
- contextEditorState.value = {
- ...contextEditorState.value,
- messages: [...context.messages],
- variables: context.variables || {},
- tools: [...(context.tools || [])],
- };
- console.log(
- "[App] Initialized context variables from persistence:",
- Object.keys(context.variables || {}),
- );
- }
- }
- } catch (error) {
- console.warn("[App] Failed to initialize context persistence:", error);
- } finally {
- // æ 论æå倱莥ïŒéœè®€äžºå·²å®æäžæ¬¡åå§åå°è¯
- isContextLoaded.value = true;
- }
-};
-
-// æä¹
åäžäžææŽæ°ïŒèœ»åºŠèæµïŒ
-let persistContextUpdateTimer: NodeJS.Timeout | null = null;
-const persistContextUpdate = async (patch: {
- messages?: ConversationMessage[];
- variables?: Record;
- tools?: any[];
-}) => {
- if (!contextRepo.value || !currentContextId.value) return;
-
- // æž
é€ä¹åç宿¶åš
- if (persistContextUpdateTimer) {
- clearTimeout(persistContextUpdateTimer);
- }
-
- // 讟眮æ°çèæµå®æ¶åšïŒ300mså»¶è¿ïŒ
- persistContextUpdateTimer = setTimeout(async () => {
- try {
- await contextRepo.value!.update(currentContextId.value!, patch);
- console.log("[App] Context persisted to storage");
- } catch (error) {
- console.warn("[App] Failed to persist context update:", error);
- }
- }, 300);
-};
-
const templateSelectType = computed<
| "optimize"
| "userOptimize"
@@ -954,166 +1257,7 @@ const handleOpenVariableManager = (variableName?: string) => {
showVariableManager.value = true;
};
-// æåŒèŸå
¥æç€ºè¯é¢è§
-const handleOpenInputPreview = () => {
- promptPreviewContent.value = optimizer.prompt || "";
- renderPhase.value = "test"; // äœ¿çš test æš¡åŒïŒæ¿æ¢ææåé
- showPreviewPanel.value = true;
-};
-
-// æåŒäŒååæç€ºè¯é¢è§
-const handleOpenPromptPreview = () => {
- promptPreviewContent.value = optimizer.optimizedPrompt || "";
- renderPhase.value = "test"; // äœ¿çš test æš¡åŒïŒæ¿æ¢ææåé
- showPreviewPanel.value = true;
-};
-
-// æåŒäžäžæçŒèŸåš
-const handleOpenContextEditor = async (
- messagesOrTab?: ConversationMessage[] | "messages" | "variables" | "tools",
- variables?: Record,
-) => {
- // åæ°ç±»å倿
- let messages: ConversationMessage[] | undefined;
- let defaultTab: "messages" | "variables" | "tools" = "messages";
-
- if (typeof messagesOrTab === "string") {
- // åŠæç¬¬äžäžªåæ°æ¯å笊䞲ïŒåæ¯ defaultTab
- defaultTab = messagesOrTab;
- messages = undefined;
- } else {
- // åŠåæ¯ messages æ°ç»
- messages = messagesOrTab;
- }
-
- // 讟眮é»è®€æ çŸé¡µ
- contextEditorDefaultTab.value = defaultTab;
- // ç¡®ä¿å
šå±åéå·²å èœœå¹¶å·æ°ïŒé¿å
忬¡äžºç©ºïŒ
- try {
- await variableManager?.refresh?.();
- } catch (e) {
- console.warn(
- "[App] Variable manager refresh failed (non-blocking):",
- e,
- );
- }
- // è¥éŠæ¬¡å èœœïŒæªå®ææä¹
åå 蜜ïŒäžé«çº§æš¡åŒåŒå¯äžåœåæ äŒè¯æ¶æ¯ïŒææš¡åŒçå
¥é»è®€æš¡æ¿
- if (
- advancedModeEnabled.value &&
- !isContextLoaded.value &&
- (!optimizationContext.value || optimizationContext.value.length === 0)
- ) {
- try {
- const defaultTemplate = quickTemplateManager.getTemplate(
- selectedOptimizationMode.value,
- "default",
- );
- if (defaultTemplate?.messages?.length) {
- optimizationContext.value = [...defaultTemplate.messages];
- console.log(
- `[App] Auto-filled default template for ${selectedOptimizationMode.value} on first open of ContextEditor`,
- );
- }
- } catch (e) {
- console.warn(
- "[App] Failed to auto-fill default template on editor open:",
- e,
- );
- }
- }
- // ð§ ä¿®å€ïŒä» contextRepo 读åçæ£çäžäžæåéïŒé¿å
å
šå±åéæ±¡æ
- let contextVariables: Record = {};
-
- if (contextRepo.value && currentContextId.value) {
- try {
- const context = await contextRepo.value.get(currentContextId.value);
- contextVariables = context?.variables || {};
- console.log(
- "[App] Loaded context variables from contextRepo:",
- Object.keys(contextVariables),
- );
- } catch (error) {
- console.warn("[App] Failed to load context variables:", error);
- }
- }
-
- // 讟眮åå§ç¶æ - åªäœ¿çšäžäžææ¬èº«çåé
- contextEditorState.value = {
- messages: messages || [...optimizationContext.value],
- variables: contextVariables, // ð« äžå䜿çšäŒ å
¥çå
šå±åé
- tools: [...optimizationContextTools.value], // ð äŒ éç°æå·¥å
·ç¶æ
- showVariablePreview: true,
- showToolManager: contextMode.value === "user", // ð§ çšæ·æš¡åŒäžæŸç€ºå·¥å
·ç®¡çåš
- mode: "edit",
- };
- showContextEditor.value = true;
-};
-
-// å€çäžäžæçŒèŸåšä¿å
-const handleContextEditorSave = async (context: {
- messages: ConversationMessage[];
- variables: Record;
- tools: any[];
-}) => {
- // æŽæ°äŒåäžäžæ
- optimizationContext.value = [...context.messages];
- optimizationContextTools.value = [...context.tools]; // ð ä¿åå·¥å
·ç¶æ
-
- // ð« ç§»é€å
šå±åéæŽæ° - äžäžæåéäžåºæ±¡æå
šå±åéåº
- // äžäžæåéåºè¯¥åªååšäºäžäžæäžïŒéè¿ persistContextUpdate æä¹
åå° contextRepo
-
- // æä¹
åå°contextRepo
- await persistContextUpdate({
- messages: context.messages,
- variables: context.variables,
- tools: context.tools,
- });
-
- // å
³éçŒèŸåš
- showContextEditor.value = false;
-
- // æŸç€ºæåæç€º
- useToast().success("äžäžæå·²æŽæ°");
-};
-
-// å€çäžäžæçŒèŸåšå®æ¶ç¶ææŽæ°
-const handleContextEditorStateUpdate = async (state: {
- messages: ConversationMessage[];
- variables: Record;
- tools: any[];
-}) => {
- // 宿¶åæ¥ç¶æå°contextEditorState
- contextEditorState.value = { ...contextEditorState.value, ...state };
-
- // 宿¶æŽæ°äŒåäžäžæïŒä¿æèœ»é级Managerçæ°æ®åæ¥ïŒ
- optimizationContext.value = [...state.messages];
- optimizationContextTools.value = [...(state.tools || [])]; // ð 忥工å
·ç¶æ
-
- // ð« ç§»é€å
šå±åéæŽæ° - äžäžæåéäžåºæ±¡æå
šå±åéåº
- // äžäžæåéåºè¯¥åªååšäºäžäžæäžïŒéè¿ persistContextUpdate æä¹
åå° contextRepo
-
- // 宿¶æä¹
åïŒèæµå€çåšpersistContextUpdateäžå€çïŒ
- await persistContextUpdate({
- messages: state.messages,
- variables: state.variables,
- tools: state.tools,
- });
-
- console.log(
- "[App] Context editor state synchronized and persisted in real-time",
- );
-};
-
-// çå¬äž»çé¢äžäžæç®¡çåšïŒConversationManagerïŒçæ¶æ¯åæŽïŒèªåšæä¹
å
-watch(
- optimizationContext,
- async (newMessages) => {
- // é¿å
äžå
šå±çŒèŸåšé倿ä¹
åïŒå
šå±çŒèŸåšå·²æäžå±æä¹
åé»èŸïŒ
- if (showContextEditor.value) return;
- await persistContextUpdate({ messages: newMessages });
- },
- { deep: true },
-);
+// äžäžæç®¡çå°åšåå§å optimizer åéè¿ useContextManagement æäŸ
// 6. åšé¡¶å±è°çšææ Composables
// æš¡åéæ©åšåŒçšç®¡ç
@@ -1129,7 +1273,88 @@ const optimizer = usePromptOptimizer(
selectedOptimizationMode,
toRef(modelManager, "selectedOptimizeModel"),
toRef(modelManager, "selectedTestModel"),
- contextMode, // äŒ éäžäžææš¡åŒ
+ contextMode, // äœ¿çšæå声æç contextMode
+);
+
+// äžäžæç®¡ç
+const contextManagement = useContextManagement({
+ services,
+ selectedOptimizationMode,
+ advancedModeEnabled,
+ showContextEditor,
+ contextEditorDefaultTab,
+ contextEditorState,
+ variableManager,
+ optimizer,
+});
+
+// ä» contextManagement æåå
¶ä»ç¶æåæ¹æ³ (contextMode é€å€,å·²åšåé¢å£°æ)
+const optimizationContext = contextManagement.optimizationContext;
+const optimizationContextTools = contextManagement.optimizationContextTools;
+const isContextLoaded = contextManagement.isContextLoaded;
+const currentContextId = contextManagement.currentContextId;
+const contextRepo = contextManagement.contextRepo;
+const predefinedVariables = contextManagement.predefinedVariables;
+const initializeContextPersistence =
+ contextManagement.initializeContextPersistence;
+const persistContextUpdate = contextManagement.persistContextUpdate;
+const handleOpenContextEditor = contextManagement.handleOpenContextEditor;
+const handleContextEditorSave = contextManagement.handleContextEditorSave;
+const handleContextEditorStateUpdate =
+ contextManagement.handleContextEditorStateUpdate;
+const handleContextModeChange = contextManagement.handleContextModeChange;
+
+// ð æç€ºè¯æµè¯ç®¡çïŒæ¯æåéæ³šå
¥ãäžäžæãå·¥å
·è°çšïŒ
+const promptTester = usePromptTester(
+ services as any,
+ toRef(modelManager, 'selectedTestModel'),
+ selectedOptimizationMode,
+ advancedModeEnabled,
+ optimizationContext,
+ optimizationContextTools,
+ variableManager
+);
+
+// æµè¯ç»æåŒçšïŒä» promptTester è·åïŒ
+const testResults = computed(() => promptTester.testResults);
+
+// å€çæµè¯é¢æ¿çåéååïŒç°åšæµè¯åéç±TestAreaPanelèªå·±ç®¡çïŒäžéèŠåæ¥å°äŒè¯ïŒ
+const handleTestPanelVariableChange = async (name: string, value: string) => {
+ // æµè¯åéç°åšåªåšTestAreaPanelå
éšç®¡çïŒäžéèŠå€éšåæ¥
+ console.log("[App] Variable change ignored (handled by TestAreaPanel):", name, value);
+};
+
+// ð å€çä¿åæµè¯åéå°å
šå±
+const handleSaveToGlobal = async (name: string, value: string) => {
+ if (!variableManager) {
+ console.warn("[App] variableManager not ready");
+ return;
+ }
+
+ try {
+ variableManager.updateVariable(name, value);
+ toast.success(t('test.variables.savedToGlobal', { name }));
+ } catch (error) {
+ console.error("[App] Failed to save variable to global:", error);
+ toast.error(t('test.error.saveToGlobalFailed', { name }));
+ }
+};
+
+// 忥 contextManagement äžç contextMode 尿们ç contextMode ref
+watch(
+ contextManagement.contextMode,
+ async (newMode) => {
+ contextMode.value = newMode;
+
+ // Phase 1: åœ contextMode ååæ¶ïŒåŠæåšäžäžææš¡åŒäžïŒæä¹
ååæš¡åŒ
+ if (functionMode.value === "pro") {
+ await setProSubMode(
+ newMode as import("@prompt-optimizer/core").ProSubMode,
+ );
+ selectedOptimizationMode.value = newMode as OptimizationMode;
+ }
+ },
+ { immediate: true },
);
// æç€ºè¯åå²
@@ -1263,8 +1488,9 @@ const refreshTextModels = async () => {
textModelOptions.value.map((opt) => opt.value),
);
const fallbackValue = textModelOptions.value[0]?.value || "";
+ const selectionReady = modelManager.isModelSelectionReady;
- if (fallbackValue) {
+ if (fallbackValue && selectionReady) {
if (!availableKeys.has(modelManager.selectedOptimizeModel)) {
modelManager.selectedOptimizeModel = fallbackValue;
}
@@ -1343,6 +1569,30 @@ watch(services, async (newServices) => {
// ç¡®ä¿åèœæš¡åŒå·²åå§åïŒé»è®€ basicïŒ
// useFunctionMode å
éšå·²å€çé»è®€åŒäžæä¹
å
+ // Phase 1: åå§åååèœæš¡åŒçåæš¡åŒæä¹
å
+ // æ ¹æ®åœååèœæš¡åŒïŒä»ååšæ¢å€å¯¹åºçåæš¡åŒéæ©
+ if (functionMode.value === "basic") {
+ const { ensureInitialized } = useBasicSubMode(services as any);
+ await ensureInitialized();
+ // åæ¥å° selectedOptimizationMode ä»¥ä¿æå
Œå®¹æ§
+ selectedOptimizationMode.value = basicSubMode.value as OptimizationMode;
+ console.log(`[App] åºç¡æš¡åŒåæš¡åŒå·²æ¢å€: ${basicSubMode.value}`);
+ } else if (functionMode.value === "pro") {
+ const { ensureInitialized } = useProSubMode(services as any);
+ await ensureInitialized();
+ // åæ¥å° selectedOptimizationMode ä»¥ä¿æå
Œå®¹æ§
+ selectedOptimizationMode.value = proSubMode.value as OptimizationMode;
+ // åæ¥å° contextModeïŒå
³é®ïŒåŠåçé¢äžäŒåæ¢ïŒ
+ await handleContextModeChange(
+ proSubMode.value as import("@prompt-optimizer/core").ContextMode,
+ );
+ console.log(`[App] äžäžææš¡åŒåæš¡åŒå·²æ¢å€: ${proSubMode.value}`);
+ } else if (functionMode.value === "image") {
+ const { ensureInitialized } = useImageSubMode(services as any);
+ await ensureInitialized();
+ console.log(`[App] åŸåæš¡åŒåæš¡åŒå·²æ¢å€: ${imageSubMode.value}`);
+ }
+
console.log("All services and composables initialized.");
// çå¬å
šå±åå²å·æ°äºä»¶ïŒæ¥èªåŸåæš¡åŒïŒ
@@ -1552,55 +1802,55 @@ const openTemplateManager = (
};
// å€çäŒåæš¡åŒåæŽ
-const handleOptimizationModeChange = async (mode: OptimizationMode) => {
- selectedOptimizationMode.value = mode;
+// åºç¡æš¡åŒåæš¡åŒåæŽå€çåš
+const handleBasicSubModeChange = async (mode: OptimizationMode) => {
+ await setBasicSubMode(
+ mode as import("@prompt-optimizer/core").BasicSubMode,
+ );
+ selectedOptimizationMode.value = mode; // ä¿æå
Œå®¹æ§
+ console.log(`[App] åºç¡æš¡åŒåæš¡åŒå·²åæ¢å¹¶æä¹
å: ${mode}`);
+};
+
+// äžäžææš¡åŒåæš¡åŒåæŽå€çåš
+const handleProSubModeChange = async (mode: OptimizationMode) => {
+ await setProSubMode(mode as import("@prompt-optimizer/core").ProSubMode);
+ selectedOptimizationMode.value = mode; // ä¿æå
Œå®¹æ§
// ð§ åæ¥æŽæ° contextModeïŒç¡®ä¿äž€è
äžèŽïŒé¿å
éå€è°çšïŒ
- // 泚æïŒservices.value.contextMode å·²ç»è¢« Vue reactive ç³»ç» unwrap äº
- if (services.value?.contextMode !== mode) {
+ if (services.value?.contextMode.value !== mode) {
await handleContextModeChange(
mode as import("@prompt-optimizer/core").ContextMode,
);
}
+ console.log(`[App] äžäžææš¡åŒåæš¡åŒå·²åæ¢å¹¶æä¹
å: ${mode}`);
};
-// ð å€çäžäžææš¡åŒåæ¢
-const handleContextModeChange = async (
- mode: import("@prompt-optimizer/core").ContextMode,
+// åŸåæš¡åŒåæš¡åŒåæŽå€çåš
+const handleImageSubModeChange = async (
+ mode: import("@prompt-optimizer/core").ImageSubMode,
) => {
- if (!services.value) {
- console.warn(
- "[Extension] Cannot change context mode: services not ready",
+ await setImageSubMode(mode);
+ console.log(`[App] åŸåæš¡åŒåæš¡åŒå·²åæ¢å¹¶æä¹
å: ${mode}`);
+
+ // éç¥ ImageWorkspace æŽæ°
+ if (typeof window !== "undefined") {
+ window.dispatchEvent(
+ new CustomEvent("image-submode-changed", {
+ detail: { mode },
+ }),
);
- return;
}
+};
- try {
- // çŽæ¥èµåŒïŒVue reactive ç³»ç»å·²ç» unwrap äº refïŒ
- services.value.contextMode = mode;
-
- // 泚æïŒäžéèŠåšè¿é忥 selectedOptimizationMode
- // å 䞺è°çšè
handleOptimizationModeChange å·²ç»è®Ÿçœ®äº
-
- console.log("[Extension] Context mode changed to:", mode);
-
- // å°è¯æä¹
åïŒåŠææ¡ä»¶å
讞ïŒ
- if (contextRepo.value && currentContextId.value) {
- await contextRepo.value.update(currentContextId.value, { mode });
- console.log("[Extension] Context mode persisted to storage");
- } else {
- console.warn(
- "[Extension] Context persistence skipped: contextRepo or currentContextId not available",
- );
- }
-
- useToast().success(
- `已忢å°${mode === "system" ? "ç³»ç»æç€ºè¯" : "çšæ·æç€ºè¯"}æš¡åŒ`,
- );
- } catch (error) {
- console.error("[Extension] Failed to persist context mode:", error);
- // å³äœ¿æä¹
å倱莥ïŒUI ç¶æå·²ç»æŽæ°ïŒæä»¥äžåæ»
- useToast().warning("æš¡åŒå·²åæ¢ïŒäœä¿å倱莥");
+// ðïž åºåŒçç»äžå€çåšïŒä¿çå
Œå®¹æ§ïŒ
+const handleOptimizationModeChange = async (mode: OptimizationMode) => {
+ console.warn(
+ "[App] handleOptimizationModeChange å·²åºåŒïŒè¯·äœ¿çšåæš¡åŒç¬ç«çå€çåš",
+ );
+ if (functionMode.value === "basic") {
+ await handleBasicSubModeChange(mode);
+ } else if (functionMode.value === "pro") {
+ await handleProSubModeChange(mode);
}
};
@@ -1627,7 +1877,7 @@ const handleTemplateLanguageChanged = (newLanguage: string) => {
// ååç»ä»¶æäŸç»äžç openTemplateManager æ¥å£ïŒåŸåæš¡åŒå€çšïŒ
provide("openTemplateManager", openTemplateManager);
-// æš¡æ¿ç®¡çåšå
³éåè°ïŒå·æ°åºç¡æš¡åŒéæ©ïŒåæ¶éç¥åŸåæš¡åŒå·æ°æš¡æ¿å衚
+// æš¡æ¿ç®¡çåšå
³éåè°ïŒå·æ°åºç¡æš¡åŒéæ©,åæ¶éç¥åŸåæš¡åŒå·æ°æš¡æ¿å衚
const handleTemplateManagerClosed = () => {
try {
templateManagerState.handleTemplateManagerClose(() => {
@@ -1693,9 +1943,12 @@ const handleHistoryReuse = async (context: {
rt === "text2imageOptimize" ||
rt === "image2imageOptimize"
) {
- // 忢å°åŸåæš¡åŒ
- await setFunctionMode("image");
- useToast().info("å·²èªåšåæ¢å°åŸåæš¡åŒ");
+ // åŸåæš¡åŒ:åªåšäžæ¯åŸåæš¡åŒæ¶æåæ¢
+ const needsSwitch = functionMode.value !== "image";
+ if (needsSwitch) {
+ await setFunctionMode("image");
+ useToast().info("å·²èªåšåæ¢å°åŸåæš¡åŒ");
+ }
// ð åŸåæš¡åŒäžçšæ°æ®åå¡«é»èŸ
// çåŸ
æš¡åŒåæ¢å®æåååå¡«æ°æ®
@@ -1720,6 +1973,7 @@ const handleHistoryReuse = async (context: {
versions: chain.versions,
currentVersionId: record.id,
imageMode: imageMode, // æ·»å åŸåæš¡åŒä¿¡æ¯
+ templateId: record.templateId || chain.rootRecord.templateId, // æ·»å æš¡æ¿ID以䟿æ¢å€æš¡æ¿éæ©
};
// è§ŠååŸåå·¥äœåºæ°æ®æ¢å€äºä»¶
@@ -1749,6 +2003,23 @@ const handleHistoryReuse = async (context: {
// åŠæç®æ æš¡åŒäžåœåæš¡åŒäžåïŒèªåšåæ¢
if (targetMode !== selectedOptimizationMode.value) {
selectedOptimizationMode.value = targetMode;
+
+ // æ ¹æ®åèœæš¡åŒåå«å€çåæš¡åŒçæä¹
å
+ if (functionMode.value === "basic") {
+ // åºç¡æš¡åŒïŒæä¹
ååæš¡åŒéæ©
+ await setBasicSubMode(
+ targetMode as import("@prompt-optimizer/core").BasicSubMode,
+ );
+ } else if (functionMode.value === "pro") {
+ // äžäžææš¡åŒïŒæä¹
ååæš¡åŒå¹¶åæ¥ contextMode
+ await setProSubMode(
+ targetMode as import("@prompt-optimizer/core").ProSubMode,
+ );
+ await handleContextModeChange(
+ targetMode as import("@prompt-optimizer/core").ContextMode,
+ );
+ }
+
useToast().info(
t("toast.info.optimizationModeAutoSwitched", {
mode:
@@ -1785,190 +2056,167 @@ const promptInputPlaceholder = computed(() => {
: t("promptOptimizer.userPromptPlaceholder");
});
-// ð åœåäžäžæçäŒè¯çº§åé
-const currentContextVariables = computed(() => {
- return contextEditorState.value.variables || {};
-});
-
-// ð å
眮é¢å®ä¹åé
-const predefinedVariables = computed(() => {
- return {
- originalPrompt: optimizer.prompt || "",
- lastOptimizedPrompt: optimizer.optimizedPrompt || "",
- // 坿 ¹æ®éèŠæ·»å æŽå€å
眮åé
- };
-});
-
// ç宿µè¯å€çåœæ°
-const handleTestAreaTest = async () => {
- if (!services.value?.promptService) {
- useToast().error("æå¡æªåå§åïŒè¯·çšåéè¯");
- return;
- }
-
- if (!modelManager.selectedTestModel) {
- useToast().error("请å
éæ©æµè¯æš¡å");
- return;
- }
-
- console.log("[App] Starting real test with content:", testContent.value);
-
- if (isCompareMode.value) {
- // å¯¹æ¯æš¡åŒïŒæµè¯åå§åäŒåæç€ºè¯
- await Promise.all([
- testPromptWithType("original"),
- testPromptWithType("optimized"),
- ]);
- } else {
- // åäžæš¡åŒïŒåªæµè¯äŒååçæç€ºè¯
- await testPromptWithType("optimized");
- }
-};
-
-// æµè¯ç¹å®ç±»åçæç€ºè¯ïŒå€çšäŒè¯äžäžæ + åé + å·¥å
·ïŒ
-const testPromptWithType = async (type: "original" | "optimized") => {
- const isOriginal = type === "original";
- const prompt = isOriginal ? optimizer.prompt : optimizer.optimizedPrompt;
-
- if (!prompt) {
- useToast().error(
- isOriginal ? "请å
èŸå
¥åå§æç€ºè¯" : "请å
çæäŒååçæç€ºè¯",
- );
- return;
- }
-
- // 讟眮æµè¯ç¶æ
- if (isOriginal) {
- testResults.value.isTestingOriginal = true;
- testResults.value.originalResult = "";
- testResults.value.originalReasoning = "";
- } else {
- testResults.value.isTestingOptimized = true;
- testResults.value.optimizedResult = "";
- testResults.value.optimizedReasoning = "";
- }
-
- // æž
é€å¯¹åºç±»åçå·¥å
·è°çšæ°æ®
- testPanelRef.value?.clearToolCalls?.(isOriginal ? "original" : "optimized");
-
- try {
- const streamHandler = {
- onToken: (token: string) => {
- if (isOriginal) {
- testResults.value.originalResult += token;
- } else {
- testResults.value.optimizedResult += token;
- }
- },
- onReasoningToken: (reasoningToken: string) => {
- if (isOriginal) {
- testResults.value.originalReasoning += reasoningToken;
- } else {
- testResults.value.optimizedReasoning += reasoningToken;
- }
- },
- onComplete: () => {
- console.log(`[App] ${type} test completed`);
- },
- onError: (err: Error) => {
- const errorMessage = err.message || t("test.error.failed");
- console.error(`[App] ${type} test failed:`, errorMessage);
- useToast().error(
- `${type === "original" ? "åå§" : "äŒå"}æç€ºè¯æµè¯å€±èŽ¥: ${errorMessage}`,
- );
- },
- };
-
- // ç»äžæé 对è¯äžåéïŒå°œéå€çšäžäžæ
- let systemPrompt = "";
- let userPrompt = "";
-
- if (selectedOptimizationMode.value === "user") {
- // çšæ·æç€ºè¯æš¡åŒïŒæç€ºè¯äœäžºçšæ·èŸå
¥
- systemPrompt = "";
- userPrompt = prompt;
- } else {
- // ç³»ç»æç€ºè¯æš¡åŒïŒæç€ºè¯äœäžºç³»ç»æ¶æ¯ïŒæµè¯å
容äœäžºçšæ·èŸå
¥
- systemPrompt = prompt;
- userPrompt =
- testContent.value ||
- "请æç
§äœ çè§è²è®Ÿå®ïŒå±ç€ºäœ çèœåå¹¶äžæäºåšã";
- }
-
- const hasConversationContext =
- advancedModeEnabled.value &&
- (optimizationContext.value?.length || 0) > 0;
- const hasTools =
- advancedModeEnabled.value &&
- (optimizationContextTools.value?.length || 0) > 0;
-
- // åéïŒåå¹¶åéåº + åœåæç€ºè¯/é®é¢ïŒçšäºäŒè¯æš¡æ¿äžçå äœç¬ŠïŒ
- const baseVars =
- (variableManager?.variableManager.value?.resolveAllVariables() ||
- {}) as Record;
- const variables = {
- ...baseVars,
- currentPrompt: prompt,
- userQuestion: userPrompt,
- };
-
- // 对è¯ïŒäŒå
å€çšäŒè¯äžäžæïŒè¥æ äžäžæååéå° system+user ç»å
- const messages = hasConversationContext
- ? optimizationContext.value.map((m) => ({
- role: m.role,
- content: m.content,
- }))
- : [
- ...(systemPrompt
- ? [{ role: "system", content: systemPrompt }]
- : []),
- { role: "user", content: userPrompt },
- ];
-
- // ç»äžäœ¿çšèªå®ä¹äŒè¯æµè¯ïŒä»¥äŸ¿æ¯æäžäžæäžå·¥å
·
- await services.value.promptService.testCustomConversationStream(
- {
- modelKey: modelManager.selectedTestModel,
- messages,
- variables,
- tools: hasTools ? optimizationContextTools.value : [],
- },
- {
- ...streamHandler,
- onToolCall: (toolCall: any) => {
- if (!hasTools) return;
- console.log(
- `[App] ${type} test tool call received:`,
- toolCall,
- );
- const toolCallResult = {
- toolCall: toolCall,
- status: "success" as const,
- timestamp: new Date(),
- };
- testPanelRef.value?.handleToolCall?.(toolCallResult, type);
- },
- },
- );
- } catch (error: any) {
- console.error(`[App] ${type} test error:`, error);
- const errorMessage = error.message || t("test.error.failed");
- useToast().error(
- `${type === "original" ? "åå§" : "äŒå"}æç€ºè¯æµè¯å€±èŽ¥: ${errorMessage}`,
- );
- } finally {
- // é眮æµè¯ç¶æ
- if (isOriginal) {
- testResults.value.isTestingOriginal = false;
- } else {
- testResults.value.isTestingOptimized = false;
- }
- }
+const handleTestAreaTest = async (testVariables?: Record) => {
+ // è°çš promptTester ç executeTest æ¹æ³
+ await promptTester.executeTest(
+ optimizer.prompt,
+ optimizer.optimizedPrompt,
+ testContent.value,
+ isCompareMode.value,
+ testVariables,
+ testPanelRef.value
+ );
};
const handleTestAreaCompareToggle = () => {
console.log("[App] Compare mode toggled:", isCompareMode.value);
};
+
+// å€çæ¶èä¿å请æ±
+const handleSaveFavorite = (data: {
+ content: string;
+ originalContent?: string;
+}) => {
+ console.log("[App] handleSaveFavorite triggered:", data);
+
+ // ä¿åæ°æ®çšäºå¯¹è¯æ¡é¢å¡«å
+ saveFavoriteData.value = data;
+
+ // æåŒä¿åå¯¹è¯æ¡
+ showSaveFavoriteDialog.value = true;
+};
+
+// å€çä¿å宿
+const handleSaveFavoriteComplete = () => {
+ console.log("[App] Favorite saved successfully");
+ // å
³éå¯¹è¯æ¡å·²ç±ç»ä»¶å
éšå€ç
+ // å¯é:å·æ°æ¶èåè¡šææŸç€ºé¢å€æç€º
+};
+
+// ååç»ä»¶æäŸç»äžç handleSaveFavorite æ¥å£ïŒåŸåæš¡åŒå€çšïŒ
+provide("handleSaveFavorite", handleSaveFavorite);
+
+const handleFavoriteOptimizePrompt = () => {
+ // å
³éæ¶è管çå¯¹è¯æ¡
+ showFavoriteManager.value = false;
+ // æ»åšå°äŒååºå
+ nextTick(() => {
+ const inputPanel = document.querySelector("[data-input-panel]");
+ if (inputPanel) {
+ inputPanel.scrollIntoView({ behavior: "smooth" });
+ }
+ });
+};
+
+const handleUseFavorite = async (favorite: any) => {
+ // æºèœæš¡åŒåæ¢é»èŸ,åè handleHistoryReuse çå®ç°
+ const {
+ functionMode: favFunctionMode,
+ optimizationMode: favOptimizationMode,
+ imageSubMode: favImageSubMode,
+ } = favorite;
+
+ // 1. 忢åèœæš¡åŒ
+ if (favFunctionMode === "image") {
+ // åŸåæš¡åŒ:åªåšäžæ¯åŸåæš¡åŒæ¶æåæ¢
+ const needsSwitch = functionMode.value !== "image";
+ if (needsSwitch) {
+ await setFunctionMode("image");
+ useToast().info("å·²èªåšåæ¢å°åŸåæš¡åŒ");
+ }
+
+ // åŸåæš¡åŒçæ°æ®åå¡«é»èŸ
+ await nextTick();
+
+ // è§ŠååŸåå·¥äœåºæ°æ®åå¡«äºä»¶
+ if (typeof window !== "undefined") {
+ window.dispatchEvent(
+ new CustomEvent("image-workspace-restore-favorite", {
+ detail: {
+ content: favorite.content,
+ imageSubMode: favImageSubMode || "text2image",
+ metadata: favorite.metadata,
+ },
+ }),
+ );
+ }
+
+ useToast().success("æ¶èçåŸåæç€ºè¯å·²å 蜜");
+ } else {
+ // åºç¡æš¡åŒæäžäžææš¡åŒ
+
+ // 2. 忢äŒåæš¡åŒ
+ if (
+ favOptimizationMode &&
+ favOptimizationMode !== selectedOptimizationMode.value
+ ) {
+ selectedOptimizationMode.value = favOptimizationMode;
+
+ // æ ¹æ®åèœæš¡åŒåå«å€çåæš¡åŒçæä¹
å
+ if (functionMode.value === "basic") {
+ // åºç¡æš¡åŒïŒæä¹
ååæš¡åŒéæ©
+ await setBasicSubMode(
+ favOptimizationMode as import("@prompt-optimizer/core").BasicSubMode,
+ );
+ } else if (functionMode.value === "pro") {
+ // äžäžææš¡åŒïŒæä¹
ååæš¡åŒå¹¶åæ¥ contextMode
+ await setProSubMode(
+ favOptimizationMode as import("@prompt-optimizer/core").ProSubMode,
+ );
+ await handleContextModeChange(
+ favOptimizationMode as import("@prompt-optimizer/core").ContextMode,
+ );
+ }
+
+ useToast().info(
+ t("toast.info.optimizationModeAutoSwitched", {
+ mode:
+ favOptimizationMode === "system"
+ ? t("common.system")
+ : t("common.user"),
+ }),
+ );
+ }
+
+ // 3. 忢åèœæš¡åŒ(basic vs context)
+ const targetFunctionMode =
+ favFunctionMode === "context" ? "pro" : "basic";
+ if (targetFunctionMode !== functionMode.value) {
+ await setFunctionMode(targetFunctionMode);
+ useToast().info(
+ `å·²èªåšåæ¢å°${targetFunctionMode === "pro" ? "äžäžæ" : "åºç¡"}æš¡åŒ`,
+ );
+
+ // åèœæš¡åŒåæ¢åïŒåŠææäŒåæš¡åŒä¿¡æ¯ïŒç¡®ä¿åæ¥åèªçåæš¡åŒæä¹
å
+ if (favOptimizationMode) {
+ if (targetFunctionMode === "basic") {
+ // åºç¡æš¡åŒïŒæä¹
ååæš¡åŒéæ©
+ await setBasicSubMode(
+ favOptimizationMode as import("@prompt-optimizer/core").BasicSubMode,
+ );
+ } else if (targetFunctionMode === "pro") {
+ // äžäžææš¡åŒïŒæä¹
ååæš¡åŒå¹¶åæ¥ contextMode
+ await setProSubMode(
+ favOptimizationMode as import("@prompt-optimizer/core").ProSubMode,
+ );
+ await handleContextModeChange(
+ favOptimizationMode as import("@prompt-optimizer/core").ContextMode,
+ );
+ }
+ }
+ }
+
+ // 4. å°æ¶èçæç€ºè¯å
容讟眮å°èŸå
¥æ¡
+ optimizer.prompt = favorite.content;
+ }
+
+ // å
³éæ¶è管çå¯¹è¯æ¡
+ showFavoriteManager.value = false;
+
+ // æŸç€ºæåæç€º
+ useToast().success("å·²å°æç€ºè¯å 蜜å°èŸå
¥æ¡");
+};
diff --git a/packages/ui/src/components/variable-extraction/VariableExtractionDialog.vue b/packages/ui/src/components/variable-extraction/VariableExtractionDialog.vue
new file mode 100644
index 00000000..e558d8a6
--- /dev/null
+++ b/packages/ui/src/components/variable-extraction/VariableExtractionDialog.vue
@@ -0,0 +1,259 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ t('variableExtraction.temporaryVariable') }}
+
+ {{ t('variableExtraction.temporaryVariableDesc') }}
+
+
+
+ {{ t('variableExtraction.globalVariable') }}
+
+ {{ t('variableExtraction.globalVariableDesc') }}
+
+
+
+
+
+
+
+
+
+ {{ t('variableExtraction.replaceAll', { count: occurrenceCount }) }}
+
+ {{ t('variableExtraction.replaceAllRecommended') }}
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/ui/src/components/variable-extraction/index.ts b/packages/ui/src/components/variable-extraction/index.ts
new file mode 100644
index 00000000..1a7cf4fc
--- /dev/null
+++ b/packages/ui/src/components/variable-extraction/index.ts
@@ -0,0 +1,17 @@
+/**
+ * åéæååèœæš¡å
+ *
+ * æäŸä»¥äžåèœïŒ
+ * 1. VariableExtractionDialog - åéæåå¯¹è¯æ¡
+ * 2. VariableAwareInput - æ¯æåéæåçæºèœèŸå
¥æ¡
+ * 3. useTextSelection - ææ¬éæ©æ£æµ composable
+ * 4. useInputHistory - èŸå
¥åå²è®°åœç®¡ç composable
+ */
+
+export { default as VariableExtractionDialog } from './VariableExtractionDialog.vue'
+export { default as VariableAwareInput } from './VariableAwareInput.vue'
+export { useTextSelection } from './useTextSelection'
+export { useInputHistory } from './useInputHistory'
+
+export type { TextSelection } from './useTextSelection'
+export type { HistoryRecord, UseInputHistoryOptions } from './useInputHistory'
diff --git a/packages/ui/src/components/variable-extraction/useInputHistory.ts b/packages/ui/src/components/variable-extraction/useInputHistory.ts
new file mode 100644
index 00000000..726f0e7b
--- /dev/null
+++ b/packages/ui/src/components/variable-extraction/useInputHistory.ts
@@ -0,0 +1,172 @@
+import { ref, Ref } from 'vue'
+
+/**
+ * èŸå
¥åå²è®°åœç®¡ç Composable
+ *
+ * åèœïŒ
+ * 1. è®°åœèŸå
¥æ¡ççŒèŸåå²
+ * 2. æ¯ææ€é (Ctrl+Z) åéå (Ctrl+Shift+Z)
+ * 3. æºèœåå¹¶è¿ç»çŒèŸæäœ
+ */
+
+export interface HistoryRecord {
+ /** ææ¬å
容 */
+ content: string
+ /** å
æ äœçœ® */
+ cursorPosition: number
+ /** è®°åœæ¶éŽæ³ */
+ timestamp: number
+}
+
+export interface UseInputHistoryOptions {
+ /** æå€§åå²è®°åœæ° */
+ maxHistory?: number
+ /** åå¹¶çŒèŸçæ¶éŽéåŒ (毫ç§) */
+ mergeThreshold?: number
+}
+
+export function useInputHistory(
+ inputRef: Ref,
+ options: UseInputHistoryOptions = {}
+) {
+ const { maxHistory = 50, mergeThreshold = 1000 } = options
+
+ // åå²è®°åœæ
+ const history = ref([])
+ // åœååå²äœçœ®çŽ¢åŒ
+ const currentIndex = ref(-1)
+ // æåäžæ¬¡çŒèŸçæ¶éŽæ³
+ const lastEditTimestamp = ref(0)
+ // æ¯åŠæ£åšæ§è¡æ€é/éåæäœ
+ const isUndoRedoing = ref(false)
+
+ /**
+ * æ·»å åå²è®°åœ
+ */
+ const addHistory = (content: string, cursorPosition: number, forceNew = false) => {
+ if (isUndoRedoing.value) return
+
+ const now = Date.now()
+ const shouldMerge =
+ !forceNew &&
+ history.value.length > 0 &&
+ currentIndex.value >= 0 &&
+ now - lastEditTimestamp.value < mergeThreshold
+
+ if (shouldMerge) {
+ // æºèœåå¹¶: æŽæ°åœåè®°åœ
+ history.value[currentIndex.value] = {
+ content,
+ cursorPosition,
+ timestamp: now
+ }
+ } else {
+ // å建æ°è®°åœ: ç§»é€åœåäœçœ®ä¹åçææè®°åœ
+ history.value = history.value.slice(0, currentIndex.value + 1)
+
+ // æ·»å æ°è®°åœ
+ history.value.push({
+ content,
+ cursorPosition,
+ timestamp: now
+ })
+
+ // éå¶åå²è®°åœæ°é
+ if (history.value.length > maxHistory) {
+ history.value.shift()
+ } else {
+ currentIndex.value++
+ }
+ }
+
+ lastEditTimestamp.value = now
+ }
+
+ /**
+ * æ€éæäœ
+ */
+ const undo = (): boolean => {
+ if (currentIndex.value <= 0) {
+ return false
+ }
+
+ isUndoRedoing.value = true
+ currentIndex.value--
+
+ const record = history.value[currentIndex.value]
+ if (record && inputRef.value) {
+ inputRef.value.value = record.content
+ inputRef.value.setSelectionRange(record.cursorPosition, record.cursorPosition)
+
+ // è§Šå input äºä»¶,ç¡®ä¿ v-model 忥
+ const event = new Event('input', { bubbles: true })
+ inputRef.value.dispatchEvent(event)
+ }
+
+ isUndoRedoing.value = false
+ return true
+ }
+
+ /**
+ * éåæäœ
+ */
+ const redo = (): boolean => {
+ if (currentIndex.value >= history.value.length - 1) {
+ return false
+ }
+
+ isUndoRedoing.value = true
+ currentIndex.value++
+
+ const record = history.value[currentIndex.value]
+ if (record && inputRef.value) {
+ inputRef.value.value = record.content
+ inputRef.value.setSelectionRange(record.cursorPosition, record.cursorPosition)
+
+ // è§Šå input äºä»¶,ç¡®ä¿ v-model 忥
+ const event = new Event('input', { bubbles: true })
+ inputRef.value.dispatchEvent(event)
+ }
+
+ isUndoRedoing.value = false
+ return true
+ }
+
+ /**
+ * æž
空åå²è®°åœ
+ */
+ const clearHistory = () => {
+ history.value = []
+ currentIndex.value = -1
+ lastEditTimestamp.value = 0
+ }
+
+ /**
+ * è®°åœåéæåæäœ (区å¶å建æ°è®°åœ)
+ */
+ const recordVariableExtraction = (content: string, cursorPosition: number) => {
+ addHistory(content, cursorPosition, true)
+ }
+
+ /**
+ * è·åæ¯åŠå¯ä»¥æ€é
+ */
+ const canUndo = () => currentIndex.value > 0
+
+ /**
+ * è·åæ¯åŠå¯ä»¥éå
+ */
+ const canRedo = () => currentIndex.value < history.value.length - 1
+
+ return {
+ history,
+ currentIndex,
+ addHistory,
+ undo,
+ redo,
+ clearHistory,
+ recordVariableExtraction,
+ canUndo,
+ canRedo
+ }
+}
diff --git a/packages/ui/src/components/variable-extraction/useTextSelection.ts b/packages/ui/src/components/variable-extraction/useTextSelection.ts
new file mode 100644
index 00000000..438397fd
--- /dev/null
+++ b/packages/ui/src/components/variable-extraction/useTextSelection.ts
@@ -0,0 +1,191 @@
+import { ref, Ref } from 'vue'
+
+/**
+ * ææ¬éæ©æ£æµ Composable
+ *
+ * åèœïŒ
+ * 1. æ£æµææ¬æ¡äžçéäžææ¬
+ * 2. éªè¯éäžææ¬çåæ³æ§
+ * 3. 鲿¢è·šè¶åé蟹çéæ©
+ */
+
+export interface TextSelection {
+ /** éäžçææ¬å
容 */
+ text: string
+ /** éäžèµ·å§äœçœ® */
+ start: number
+ /** éäžç»æäœçœ® */
+ end: number
+ /** æ¯åŠææéæ© */
+ isValid: boolean
+ /** æ æåå */
+ invalidReason?: string
+}
+
+export function useTextSelection(inputRef: Ref) {
+ const selection = ref({
+ text: '',
+ start: 0,
+ end: 0,
+ isValid: false
+ })
+
+ /**
+ * è·ååœåéäžçææ¬
+ */
+ const getSelection = (): TextSelection => {
+ const input = inputRef.value
+ if (!input) {
+ return {
+ text: '',
+ start: 0,
+ end: 0,
+ isValid: false,
+ invalidReason: 'èŸå
¥æ¡æªå°±ç»ª'
+ }
+ }
+
+ const start = input.selectionStart || 0
+ const end = input.selectionEnd || 0
+ const text = input.value.substring(start, end)
+
+ // éªè¯éæ©
+ const validation = validateSelection(input.value, start, end, text)
+
+ selection.value = {
+ text: validation.isValid ? text.trim() : '',
+ start,
+ end,
+ isValid: validation.isValid,
+ invalidReason: validation.reason
+ }
+
+ return selection.value
+ }
+
+ /**
+ * éªè¯éäžææ¬çåæ³æ§
+ */
+ const validateSelection = (
+ fullText: string,
+ start: number,
+ end: number,
+ selectedText: string
+ ): { isValid: boolean; reason?: string } => {
+ // æ£æ¥æ¯åŠæéäžææ¬
+ if (start === end || !selectedText.trim()) {
+ return { isValid: false, reason: 'æªéäžä»»äœææ¬' }
+ }
+
+ // æ£æ¥æ¯åŠè·šè¶åé蟹ç
+ const beforeSelection = fullText.substring(0, start)
+ const afterSelection = fullText.substring(end)
+
+ // æ£æ¥éäžææ¬åæ¯åŠææªéåç {{
+ const openBracesBeforeCount = (beforeSelection.match(/\{\{/g) || []).length
+ const closeBracesBeforeCount = (beforeSelection.match(/\}\}/g) || []).length
+ if (openBracesBeforeCount > closeBracesBeforeCount) {
+ return { isValid: false, reason: 'äžèœè·šè¶åé蟹ç' }
+ }
+
+ // æ£æ¥éäžææ¬åæ¯åŠææªéåç }}
+ const openBracesAfterCount = (afterSelection.match(/\{\{/g) || []).length
+ const closeBracesAfterCount = (afterSelection.match(/\}\}/g) || []).length
+ if (closeBracesAfterCount > openBracesAfterCount) {
+ return { isValid: false, reason: 'äžèœè·šè¶åé蟹ç' }
+ }
+
+ // æ£æ¥éäžææ¬å
éšæ¯åŠå
å«å®æŽçåéå äœç¬Š
+ const openBracesInSelection = (selectedText.match(/\{\{/g) || []).length
+ const closeBracesInSelection = (selectedText.match(/\}\}/g) || []).length
+ if (openBracesInSelection > 0 || closeBracesInSelection > 0) {
+ // åŠæéäžææ¬å
å« {{ æ }},æ£æ¥æ¯åŠæ¯å®æŽçåé
+ if (openBracesInSelection !== closeBracesInSelection) {
+ return { isValid: false, reason: 'äžèœè·šè¶åé蟹ç' }
+ }
+ }
+
+ return { isValid: true }
+ }
+
+ /**
+ * 计ç®éäžææ¬åšå®æŽææ¬äžçåºç°æ¬¡æ°
+ */
+ const countOccurrences = (fullText: string, searchText: string): number => {
+ if (!searchText) return 0
+
+ const trimmedSearch = searchText.trim()
+ if (!trimmedSearch) return 0
+
+ // äœ¿çšæ£å衚蟟åŒè®¡ç®åºç°æ¬¡æ°,äœèŠæé€å·²ç»åšåéäžçææ¬
+ let count = 0
+ let position = 0
+
+ while (position < fullText.length) {
+ const index = fullText.indexOf(trimmedSearch, position)
+ if (index === -1) break
+
+ // æ£æ¥è¯¥äœçœ®æ¯åŠåšåéå äœç¬Šå
éš
+ const beforeText = fullText.substring(0, index)
+ const openBraces = (beforeText.match(/\{\{/g) || []).length
+ const closeBraces = (beforeText.match(/\}\}/g) || []).length
+
+ // åŠæäžåšåéå
éš,å计æ°
+ if (openBraces === closeBraces) {
+ count++
+ }
+
+ position = index + 1
+ }
+
+ return count
+ }
+
+ /**
+ * æ¿æ¢ææ¬äžçææå¹é
项
+ */
+ const replaceAllOccurrences = (
+ fullText: string,
+ searchText: string,
+ replaceWith: string
+ ): string => {
+ if (!searchText) return fullText
+
+ const trimmedSearch = searchText.trim()
+ if (!trimmedSearch) return fullText
+
+ let result = fullText
+ let position = 0
+
+ while (position < result.length) {
+ const index = result.indexOf(trimmedSearch, position)
+ if (index === -1) break
+
+ // æ£æ¥è¯¥äœçœ®æ¯åŠåšåéå äœç¬Šå
éš
+ const beforeText = result.substring(0, index)
+ const openBraces = (beforeText.match(/\{\{/g) || []).length
+ const closeBraces = (beforeText.match(/\}\}/g) || []).length
+
+ // åŠæäžåšåéå
éš,åæ¿æ¢
+ if (openBraces === closeBraces) {
+ result =
+ result.substring(0, index) +
+ replaceWith +
+ result.substring(index + trimmedSearch.length)
+ position = index + replaceWith.length
+ } else {
+ position = index + 1
+ }
+ }
+
+ return result
+ }
+
+ return {
+ selection,
+ getSelection,
+ validateSelection,
+ countOccurrences,
+ replaceAllOccurrences
+ }
+}
diff --git a/packages/ui/src/composables/index.ts b/packages/ui/src/composables/index.ts
index 94bb89de..beb13e7d 100644
--- a/packages/ui/src/composables/index.ts
+++ b/packages/ui/src/composables/index.ts
@@ -8,6 +8,7 @@ export * from "./useModelManager";
export * from "./useHistoryManager";
export * from "./useModelSelectRefs";
export * from "./useVariableManager";
+export * from "./useContextManagement";
export * from "./useAutoScroll";
export * from "./useClipboard";
export * from "./useFullscreen";
diff --git a/packages/web/src/composables/useContextManagement.ts b/packages/ui/src/composables/useContextManagement.ts
similarity index 85%
rename from packages/web/src/composables/useContextManagement.ts
rename to packages/ui/src/composables/useContextManagement.ts
index 48692173..2bbcc9f1 100644
--- a/packages/web/src/composables/useContextManagement.ts
+++ b/packages/ui/src/composables/useContextManagement.ts
@@ -4,13 +4,13 @@
*/
import { ref, computed, watch, type Ref } from "vue";
-import { useToast } from "@prompt-optimizer/ui";
-import { quickTemplateManager } from "@prompt-optimizer/ui";
+import { useToast } from "./useToast";
+import { quickTemplateManager } from "../data/quickTemplates";
import type {
ConversationMessage,
OptimizationMode,
} from "@prompt-optimizer/core";
-import type { AppServices } from "@prompt-optimizer/ui";
+import type { AppServices } from "../types/services";
export interface ContextManagementOptions {
services: Ref;
@@ -59,11 +59,6 @@ export function useContextManagement(options: ContextManagementOptions) {
const currentContextId = ref(null);
const contextRepo = computed(() => services.value?.contextRepo);
- // åœåäžäžæçäŒè¯çº§åé
- const currentContextVariables = computed(() => {
- return contextEditorState.value.variables || {};
- });
-
// å
眮é¢å®ä¹åé
const predefinedVariables = computed(() => {
// optimizer å¯èœåšåå§åæ¶äžº null,éèŠå®å
šè®¿é®
@@ -228,30 +223,12 @@ export function useContextManagement(options: ContextManagementOptions) {
}
}
- // ä» contextRepo 读åçæ£çäžäžæåé
- let contextVariables: Record = {};
- if (contextRepo.value && currentContextId.value) {
- try {
- const context = await contextRepo.value.get(currentContextId.value);
- contextVariables = context?.variables || {};
- console.log(
- "[useContextManagement] Loaded context variables from contextRepo:",
- Object.keys(contextVariables),
- );
- } catch (error) {
- console.warn(
- "[useContextManagement] Failed to load context variables:",
- error,
- );
- }
- }
-
// 讟眮åå§ç¶æ
contextEditorState.value = {
messages: messages || [...optimizationContext.value],
- variables: contextVariables,
+ variables: {}, // äžå䜿çšäŒè¯åé
tools: [...optimizationContextTools.value],
- showVariablePreview: true,
+ showVariablePreview: false, // äžåæŸç€ºåéé¢è§
showToolManager: contextMode.value === "user",
mode: "edit",
};
@@ -343,28 +320,7 @@ export function useContextManagement(options: ContextManagementOptions) {
}
};
- // ==================== äŒè¯åé管ç ====================
-
- // æŽæ°äŒè¯çº§åé
- const updateContextVariable = async (name: string, value: string) => {
- const updatedVariables = {
- ...contextEditorState.value.variables,
- };
-
- if (value && value.trim()) {
- updatedVariables[name] = value;
- } else {
- delete updatedVariables[name];
- }
-
- contextEditorState.value = {
- ...contextEditorState.value,
- variables: updatedVariables,
- };
-
- // æä¹
å
- await persistContextUpdate({ variables: updatedVariables });
- };
+ // äŒè¯åé管çå·²ç§»é€ - ç°åšäœ¿çšæµè¯åºäžŽæ¶åé
// ==================== è¿å ====================
@@ -376,7 +332,6 @@ export function useContextManagement(options: ContextManagementOptions) {
isContextLoaded,
currentContextId,
contextRepo,
- currentContextVariables,
predefinedVariables,
// æ¹æ³
@@ -386,6 +341,5 @@ export function useContextManagement(options: ContextManagementOptions) {
handleContextEditorSave,
handleContextEditorStateUpdate,
handleContextModeChange,
- updateContextVariable,
};
}
diff --git a/packages/ui/src/composables/usePromptTester.ts b/packages/ui/src/composables/usePromptTester.ts
index 6822caaa..d7f4b988 100644
--- a/packages/ui/src/composables/usePromptTester.ts
+++ b/packages/ui/src/composables/usePromptTester.ts
@@ -1,133 +1,263 @@
-import { computed, reactive, type Ref } from 'vue'
+import { reactive, type Ref } from 'vue'
import { useToast } from './useToast'
import { useI18n } from 'vue-i18n'
import type { OptimizationMode } from '@prompt-optimizer/core'
import type { AppServices } from '../types/services'
+import type { ConversationMessage } from '../types/variable'
/**
- * æç€ºè¯æµè¯Hook
+ * é«çº§æç€ºè¯æµè¯ Hook
+ * æ¯æåéæ³šå
¥ãäžäžæãå·¥å
·è°çšçé«çº§ç¹æ§
+ *
* @param services æå¡å®äŸåŒçš
* @param selectedTestModel æµè¯æš¡åéæ©
- * @param optimizationMode äŒåæš¡åŒ
+ * @param selectedOptimizationMode äŒåæš¡åŒ
+ * @param advancedModeEnabled æ¯åŠå¯çšé«çº§æš¡åŒ
+ * @param optimizationContext äŒåäžäžæïŒäŒè¯æ¶æ¯ïŒ
+ * @param optimizationContextTools äžäžæå·¥å
·å衚
+ * @param variableManager åé管çåš
* @returns æç€ºè¯æµè¯æ¥å£
*/
export function usePromptTester(
services: Ref,
selectedTestModel: Ref,
- optimizationMode?: Ref
+ selectedOptimizationMode: Ref,
+ advancedModeEnabled: Ref,
+ optimizationContext: Ref,
+ optimizationContextTools: Ref,
+ variableManager: any
) {
const toast = useToast()
const { t } = useI18n()
-
- // æç€ºè¯æå¡åŒçš
- const promptService = computed(() => services.value?.promptService)
-
+
// å建äžäžª reactive ç¶æå¯¹è±¡
const state = reactive({
- // States
- testContent: '',
- testResult: '',
- testError: '',
- isTesting: false,
-
+ // States - æµè¯ç»æç¶æ
+ testResults: {
+ // åå§æç€ºè¯ç»æ
+ originalResult: '',
+ originalReasoning: '',
+ isTestingOriginal: false,
+
+ // äŒåæç€ºè¯ç»æ
+ optimizedResult: '',
+ optimizedReasoning: '',
+ isTestingOptimized: false,
+ },
+
// Methods
- handleTest: async (optimizedPrompt: string) => {
- if (!selectedTestModel.value || !optimizedPrompt) {
- toast.error(t('toast.error.incompleteTestInfo'))
+ /**
+ * æ§è¡æµè¯ïŒæ¯æå¯¹æ¯æš¡åŒïŒ
+ * @param prompt åå§æç€ºè¯
+ * @param optimizedPrompt äŒååçæç€ºè¯
+ * @param testContent æµè¯å
容
+ * @param isCompareMode æ¯åŠå¯¹æ¯æš¡åŒ
+ * @param testVariables æµè¯åé
+ * @param testPanelRef æµè¯é¢æ¿åŒçšïŒçšäºå·¥å
·è°çšåè°ïŒ
+ */
+ executeTest: async (
+ prompt: string,
+ optimizedPrompt: string,
+ testContent: string,
+ isCompareMode: boolean,
+ testVariables?: Record,
+ testPanelRef?: any
+ ) => {
+ if (!services.value?.promptService) {
+ toast.error(t('toast.error.serviceInit'))
return
}
-
- // For system prompt optimization, we need test content
- // For user prompt optimization, we don't need test content (the optimized prompt IS the user input)
- const currentOptimizationMode = optimizationMode?.value || 'system'
- if (currentOptimizationMode === 'system' && !state.testContent) {
- toast.error(t('test.error.noTestContent'))
+
+ if (!selectedTestModel.value) {
+ toast.error(t('test.error.noModel'))
return
}
-
- state.isTesting = true
- state.testError = ''
- state.testResult = ''
-
- try {
- // Determine system and user prompts based on optimization mode
- let systemPrompt = ''
- let userPrompt = ''
-
- if (currentOptimizationMode === 'user') {
- // For user prompt optimization: no system context, optimized is user
- systemPrompt = ''
- userPrompt = optimizedPrompt
- } else {
- // For system prompt optimization: optimized is system, test content is user
- systemPrompt = optimizedPrompt
- userPrompt = state.testContent
- }
-
- await promptService.value!.testPromptStream(
- systemPrompt,
- userPrompt,
- selectedTestModel.value,
- {
- onToken: (token: string) => {
- state.testResult += token
- },
- onComplete: () => {
- state.isTesting = false
- },
- onError: (error: Error) => {
- state.testError = error.message || t('toast.error.testFailed')
- state.isTesting = false
- }
- }
+
+ if (isCompareMode) {
+ // å¯¹æ¯æš¡åŒïŒæµè¯åå§åäŒåæç€ºè¯
+ await state.testPromptWithType(
+ 'original',
+ prompt,
+ optimizedPrompt,
+ testContent,
+ testVariables,
+ testPanelRef
+ )
+ await state.testPromptWithType(
+ 'optimized',
+ prompt,
+ optimizedPrompt,
+ testContent,
+ testVariables,
+ testPanelRef
+ )
+ } else {
+ // åäžæš¡åŒïŒåªæµè¯äŒååçæç€ºè¯
+ await state.testPromptWithType(
+ 'optimized',
+ prompt,
+ optimizedPrompt,
+ testContent,
+ testVariables,
+ testPanelRef
)
- } catch (error) {
- console.error(t('toast.error.testFailed'), error)
- state.testError = error instanceof Error ? error.message : t('toast.error.testProcessError')
- } finally {
- state.isTesting = false
}
},
-
- // Test with explicit context (for advanced use cases)
- handleTestWithContext: async (
- systemPrompt: string,
- userPrompt: string
+
+ /**
+ * æµè¯ç¹å®ç±»åçæç€ºè¯ïŒå€çšäŒè¯äžäžæ + åé + å·¥å
·ïŒ
+ */
+ testPromptWithType: async (
+ type: 'original' | 'optimized',
+ prompt: string,
+ optimizedPrompt: string,
+ testContent: string,
+ testVars?: Record,
+ testPanelRef?: any
) => {
- if (!selectedTestModel.value || !userPrompt) {
- toast.error(t('toast.error.incompleteTestInfo'))
+ const isOriginal = type === 'original'
+ const selectedPrompt = isOriginal ? prompt : optimizedPrompt
+
+ if (!selectedPrompt) {
+ toast.error(
+ isOriginal ? t('test.error.noOriginalPrompt') : t('test.error.noOptimizedPrompt')
+ )
return
}
-
- state.isTesting = true
- state.testError = ''
- state.testResult = ''
-
+
+ // 讟眮æµè¯ç¶æ
+ if (isOriginal) {
+ state.testResults.isTestingOriginal = true
+ state.testResults.originalResult = ''
+ state.testResults.originalReasoning = ''
+ } else {
+ state.testResults.isTestingOptimized = true
+ state.testResults.optimizedResult = ''
+ state.testResults.optimizedReasoning = ''
+ }
+
+ // æž
é€å¯¹åºç±»åçå·¥å
·è°çšæ°æ®
+ testPanelRef?.clearToolCalls?.(isOriginal ? 'original' : 'optimized')
+
try {
- await promptService.value!.testPromptStream(
- systemPrompt,
- userPrompt,
- selectedTestModel.value,
- {
- onToken: (token: string) => {
- state.testResult += token
- },
- onComplete: () => {
- state.isTesting = false
- },
- onError: (error: Error) => {
- state.testError = error.message || t('toast.error.testFailed')
- state.isTesting = false
+ const streamHandler = {
+ onToken: (token: string) => {
+ if (isOriginal) {
+ state.testResults.originalResult += token
+ } else {
+ state.testResults.optimizedResult += token
}
+ },
+ onReasoningToken: (reasoningToken: string) => {
+ if (isOriginal) {
+ state.testResults.originalReasoning += reasoningToken
+ } else {
+ state.testResults.optimizedReasoning += reasoningToken
+ }
+ },
+ onComplete: () => {
+ // Test completed successfully
+ },
+ onError: (err: Error) => {
+ const errorMessage = err.message || t('test.error.failed')
+ console.error(`[usePromptTester] ${type} test failed:`, errorMessage)
+ const testTypeKey = type === 'original' ? 'originalTestFailed' : 'optimizedTestFailed'
+ toast.error(`${t(`test.error.${testTypeKey}`)}: ${errorMessage}`)
+ },
+ }
+
+ // ç»äžæé 对è¯äžåéïŒå°œéå€çšäžäžæ
+ let systemPrompt = ''
+ let userPrompt = ''
+
+ if (selectedOptimizationMode.value === 'user') {
+ // çšæ·æç€ºè¯æš¡åŒïŒæç€ºè¯äœäžºçšæ·èŸå
¥
+ systemPrompt = ''
+ userPrompt = selectedPrompt
+ } else {
+ // ç³»ç»æç€ºè¯æš¡åŒïŒæç€ºè¯äœäžºç³»ç»æ¶æ¯
+ systemPrompt = selectedPrompt
+ userPrompt = testContent || '请æç
§äœ çè§è²è®Ÿå®ïŒå±ç€ºäœ çèœåå¹¶äžæäºåšã'
+ }
+
+ const hasConversationContext =
+ selectedOptimizationMode.value === 'system' &&
+ advancedModeEnabled.value &&
+ (optimizationContext.value?.length || 0) > 0
+ const hasTools =
+ advancedModeEnabled.value &&
+ (optimizationContextTools.value?.length || 0) > 0
+
+ // åéïŒåå¹¶å
šå±åé + æµè¯åé + åœåæç€ºè¯/é®é¢ïŒçšäºäŒè¯æš¡æ¿äžçå äœç¬ŠïŒ
+ // æäŒå
级åå¹¶: å
šå±èªå®ä¹åé < æµè¯åé < é¢å®ä¹åé
+ const baseVars =
+ (variableManager?.variableManager.value?.resolveAllVariables() ||
+ {}) as Record
+
+ // 䜿çšäŒ å
¥çæµè¯åé
+ const variables = {
+ ...baseVars,
+ ...(testVars || {}), // æµè¯åéäŒå
级é«äºå
šå±åé
+ currentPrompt: selectedPrompt,
+ userQuestion: userPrompt,
+ }
+
+ // å¯¹è¯æé é»èŸïŒ
+ // - ç³»ç»æš¡åŒ + æäŒè¯äžäžæïŒäœ¿çšäŒè¯äžäžæïŒå äžºç³»ç»æç€ºè¯æ¬èº«å°±æ¯å€èœ®å¯¹è¯åºæ¯ïŒ
+ // - çšæ·æš¡åŒïŒæ 论æ¯åŠæäŒè¯äžäžæïŒéœçŽæ¥åéäŒååçæç€ºè¯äœäžºçšæ·æ¶æ¯
+ // ïŒå äžºçšæ·æç€ºè¯äŒåçç®æ æ¯çæå¯çŽæ¥äœ¿çšçåæ¡çšæ·æ¶æ¯ïŒ
+ const messages: ConversationMessage[] =
+ selectedOptimizationMode.value === 'system' && hasConversationContext
+ ? optimizationContext.value.map((m) => ({
+ role: m.role,
+ content: m.content,
+ }))
+ : [
+ ...(systemPrompt
+ ? [{ role: 'system' as const, content: systemPrompt }]
+ : []),
+ { role: 'user' as const, content: userPrompt },
+ ]
+
+ // ç»äžäœ¿çšèªå®ä¹äŒè¯æµè¯ïŒä»¥äŸ¿æ¯æäžäžæäžå·¥å
·
+ await services.value!.promptService.testCustomConversationStream(
+ {
+ modelKey: selectedTestModel.value,
+ messages,
+ variables,
+ tools: hasTools ? optimizationContextTools.value : [],
+ },
+ {
+ ...streamHandler,
+ onToolCall: (toolCall: any) => {
+ if (!hasTools) return
+ console.log(
+ `[usePromptTester] ${type} test tool call received:`,
+ toolCall
+ )
+ const toolCallResult = {
+ toolCall: toolCall,
+ status: 'success' as const,
+ timestamp: new Date(),
+ }
+ testPanelRef?.handleToolCall?.(toolCallResult, type)
+ },
}
)
- } catch (error) {
- console.error(t('toast.error.testFailed'), error)
- state.testError = error instanceof Error ? error.message : t('toast.error.testProcessError')
+ } catch (error: any) {
+ console.error(`[usePromptTester] ${type} test error:`, error)
+ const errorMessage = error.message || t('test.error.failed')
+ const testTypeKey = type === 'original' ? 'originalTestFailed' : 'optimizedTestFailed'
+ toast.error(`${t(`test.error.${testTypeKey}`)}: ${errorMessage}`)
} finally {
- state.isTesting = false
+ // é眮æµè¯ç¶æ
+ if (isOriginal) {
+ state.testResults.isTestingOriginal = false
+ } else {
+ state.testResults.isTestingOptimized = false
+ }
}
- }
+ },
})
return state
diff --git a/packages/ui/src/i18n/locales/en-US.ts b/packages/ui/src/i18n/locales/en-US.ts
index d3c0c80f..a5f01303 100644
--- a/packages/ui/src/i18n/locales/en-US.ts
+++ b/packages/ui/src/i18n/locales/en-US.ts
@@ -1026,6 +1026,11 @@ export default {
failed: "Test Failed",
noModel: "Please select a test model first",
noTestContent: "Please enter test content",
+ noOriginalPrompt: "Please enter the original prompt first",
+ noOptimizedPrompt: "Please generate the optimized prompt first",
+ originalTestFailed: "Original prompt test failed",
+ optimizedTestFailed: "Optimized prompt test failed",
+ saveToGlobalFailed: "Failed to save variable {name} to global",
},
enableMarkdown: "Enable Markdown rendering",
disableMarkdown: "Disable Markdown rendering",
@@ -1046,7 +1051,19 @@ export default {
customVars: "Custom Variables",
finalPreview: "Final Preview",
missingVars: "Missing Variables",
+ saveToGlobal: "Save to Global Variables",
+ savedToGlobal: "Saved to Global Variables",
+ emptyValueWarning: "Empty value, cannot save",
+ tempCount: "{count} temporary variables",
+ addVariable: "Add Variable",
+ nameRequired: "Variable name is required",
+ addSuccess: "Variable added successfully",
+ deleteSuccess: "Variable {name} deleted",
+ clearSuccess: "All temporary variables cleared",
+ delete: "Delete this variable",
},
+ invalidVariables: "Invalid variable data",
+ getVariablesFailed: "Failed to get variables",
},
template: {
noDescription: "No Description",
@@ -1839,4 +1856,35 @@ export default {
},
},
},
+ // Variable Extraction Feature
+ variableExtraction: {
+ dialogTitle: "Extract as Variable",
+ variableName: "Variable Name",
+ variableNamePlaceholder:
+ "Enter variable name (letters, numbers, underscore)",
+ variableValue: "Variable Value",
+ variableValuePlaceholder: "Selected text content",
+ variableType: "Variable Type",
+ temporaryVariable: "Temporary Variable",
+ temporaryVariableDesc: "Current test only, lost on refresh",
+ globalVariable: "Global Variable",
+ globalVariableDesc: "Saved permanently, shared across sessions",
+ replaceAll: "Replace all ({count} occurrences)",
+ replaceAllRecommended: "(Recommended)",
+ extractButton: "Extract as Variable",
+ extractSuccess: "Successfully extracted variable {{variableName}}",
+ extractSuccessAll:
+ "Successfully extracted variable {{variableName}} ({count} replacements)",
+ savedToGlobal: "Variable {name} saved to global",
+ savedToTemporary: "Variable {name} saved to temporary",
+ validation: {
+ required: "Variable name cannot be empty",
+ noNumberStart: "Variable name cannot start with a number",
+ invalidCharacters:
+ "Variable name can only contain letters, numbers, underscore",
+ predefinedVariable: "Cannot use predefined variable name",
+ duplicateVariable:
+ "Variable name already exists, will reference existing variable",
+ },
+ },
};
diff --git a/packages/ui/src/i18n/locales/zh-CN.ts b/packages/ui/src/i18n/locales/zh-CN.ts
index a774830a..71aa1c2c 100644
--- a/packages/ui/src/i18n/locales/zh-CN.ts
+++ b/packages/ui/src/i18n/locales/zh-CN.ts
@@ -129,7 +129,7 @@ export default {
},
actions: {
globalVariables: "å
šå±åé",
- contextVariables: "äŒè¯åé",
+ contextVariables: "äžäžæåé",
tools: "å·¥å
·ç®¡ç",
toolManager: "å·¥å
·ç®¡ç",
},
@@ -995,6 +995,11 @@ export default {
failed: "æµè¯å€±èŽ¥",
noModel: "请å
éæ©æµè¯æš¡å",
noTestContent: "请èŸå
¥æµè¯å
容",
+ noOriginalPrompt: "请å
èŸå
¥åå§æç€ºè¯",
+ noOptimizedPrompt: "请å
çæäŒååçæç€ºè¯",
+ originalTestFailed: "åå§æç€ºè¯æµè¯å€±èŽ¥",
+ optimizedTestFailed: "äŒåæç€ºè¯æµè¯å€±èŽ¥",
+ saveToGlobalFailed: "ä¿ååé {name} å°å
šå±å€±èŽ¥",
},
enableMarkdown: "å¯çšMarkdownæž²æ",
disableMarkdown: "å
³éMarkdownæž²æ",
@@ -1009,13 +1014,25 @@ export default {
inputPlaceholder: "请èŸå
¥åéåŒ",
noVariables: "æªæ£æµå°åé",
previewTitle: "é¢è§ç»æ",
- firstRound: "第äžèœ®æ¿æ¢ïŒå
眮åéïŒ",
- secondRound: "第äºèœ®æ¿æ¢ïŒèªå®ä¹åéïŒ",
+ firstRound: "第äžèœ®æ¿æ¢(å
眮åé)",
+ secondRound: "第äºèœ®æ¿æ¢(èªå®ä¹åé)",
builtinVars: "å
眮åé",
customVars: "èªå®ä¹åé",
finalPreview: "æç»é¢è§",
missingVars: "猺倱çåé",
+ saveToGlobal: "ä¿åå°å
šå±åé",
+ savedToGlobal: "å·²ä¿åå°å
šå±åé",
+ emptyValueWarning: "åéåŒäžºç©º,æ æ³ä¿å",
+ tempCount: "{count} 䞪䞎æ¶åé",
+ addVariable: "æ·»å åé",
+ nameRequired: "åéåäžèœäžºç©º",
+ addSuccess: "åéæ·»å æå",
+ deleteSuccess: "åé {name} å·²å é€",
+ clearSuccess: "å·²æž
空ææäžŽæ¶åé",
+ delete: "å 逿€åé",
},
+ invalidVariables: "åéæ°æ®æ æ",
+ getVariablesFailed: "è·ååé倱莥",
},
template: {
noDescription: "ææ æè¿°",
@@ -1773,4 +1790,31 @@ export default {
},
},
},
+ // åéæååèœ
+ variableExtraction: {
+ dialogTitle: "æå䞺åé",
+ variableName: "åéå",
+ variableNamePlaceholder: "请èŸå
¥åéåïŒäžè±æãæ°åãäžå线ïŒ",
+ variableValue: "åéåŒ",
+ variableValuePlaceholder: "éäžçææ¬å
容",
+ variableType: "åéç±»å",
+ temporaryVariable: "䞎æ¶åé",
+ temporaryVariableDesc: "ä»
åœåæµè¯ïŒå·æ°äž¢å€±",
+ globalVariable: "å
šå±åé",
+ globalVariableDesc: "æ°žä¹
ä¿åïŒææäŒè¯å
񄧮",
+ replaceAll: "å
šéšæ¿æ¢ïŒå
± {count} å€ïŒ",
+ replaceAllRecommended: "ïŒæšèïŒ",
+ extractButton: "æå䞺åé",
+ extractSuccess: "æåæååé {{variableName}}",
+ extractSuccessAll: "æåæååé {{variableName}}ïŒæ¿æ¢äº {count} å€ïŒ",
+ savedToGlobal: "åé {name} å·²ä¿åå°å
šå±",
+ savedToTemporary: "åé {name} å·²ä¿åå°äžŽæ¶åé",
+ validation: {
+ required: "åéåäžèœäžºç©º",
+ noNumberStart: "åéåäžèœä»¥æ°ååŒå€Ž",
+ invalidCharacters: "åéååªèœå
å«äžæãè±æãæ°åãäžå线",
+ predefinedVariable: "äžèœäœ¿çšç³»ç»é¢å®ä¹åéå",
+ duplicateVariable: "该åéåå·²ååšïŒå°åŒçšç°æåé",
+ },
+ },
};
diff --git a/packages/ui/src/i18n/locales/zh-TW.ts b/packages/ui/src/i18n/locales/zh-TW.ts
index 09f67303..f64ec70a 100644
--- a/packages/ui/src/i18n/locales/zh-TW.ts
+++ b/packages/ui/src/i18n/locales/zh-TW.ts
@@ -129,8 +129,9 @@ export default {
},
actions: {
globalVariables: "å
šå±è®æž",
- contextVariables: "æè©±è®æž",
+ contextVariables: "æ
å¢è®æž",
tools: "å·¥å
·ç®¡ç",
+ toolManager: "å·¥å
·ç®¡ç",
},
preview: {
title: "é 芜",
@@ -989,6 +990,11 @@ export default {
failed: "枬詊倱æ",
noModel: "è«å
éžææž¬è©Šæš¡å",
noTestContent: "è«èŒžå
¥æž¬è©Šå
§å®¹",
+ noOriginalPrompt: "è«å
茞å
¥åå§æç€ºè©",
+ noOptimizedPrompt: "è«å
çæåªååŸçæç€ºè©",
+ originalTestFailed: "åå§æç€ºè©æž¬è©Šå€±æ",
+ optimizedTestFailed: "åªåæç€ºè©æž¬è©Šå€±æ",
+ saveToGlobalFailed: "å²åè®æž {name} å°å
šå倱æ",
},
enableMarkdown: "åçšMarkdownæž²æ",
disableMarkdown: "ééMarkdownæž²æ",
@@ -1009,7 +1015,19 @@ export default {
customVars: "èªèšè®æž",
finalPreview: "æçµé 芜",
missingVars: "猺倱çè®æž",
+ saveToGlobal: "å²åå°å
šåè®æž",
+ savedToGlobal: "å·²å²åå°å
šåè®æž",
+ emptyValueWarning: "è®æžåŒçºç©ºïŒç¡æ³å²å",
+ tempCount: "{count} åèšæè®æž",
+ addVariable: "æ°å¢è®æž",
+ nameRequired: "è®æžåçš±äžèœçºç©º",
+ addSuccess: "è®æžæ°å¢æå",
+ deleteSuccess: "è®æž {name} å·²åªé€",
+ clearSuccess: "å·²æž
空ææèšæè®æž",
+ delete: "åªé€æ€è®æž",
},
+ invalidVariables: "è®æžè³æç¡æ",
+ getVariablesFailed: "ååŸè®æžå€±æ",
},
template: {
noDescription: "æ«ç¡æè¿°",
@@ -1737,4 +1755,31 @@ export default {
},
},
},
+ // è®æžæååèœ
+ variableExtraction: {
+ dialogTitle: "æåçºè®æž",
+ variableName: "è®æžåçš±",
+ variableNamePlaceholder: "è«èŒžå
¥è®æžåçš±ïŒäžè±æãæžåãåºç·ïŒ",
+ variableValue: "è®æžåŒ",
+ variableValuePlaceholder: "éžäžçæåå
§å®¹",
+ variableType: "è®æžé¡å",
+ temporaryVariable: "èšæè®æž",
+ temporaryVariableDesc: "å
ç®å枬詊ïŒéæ°æŽçäžå€±",
+ globalVariable: "å
šåè®æž",
+ globalVariableDesc: "æ°žä¹
å²åïŒæææè©±å
±çš",
+ replaceAll: "å
šéšå代ïŒå
± {count} èïŒ",
+ replaceAllRecommended: "ïŒæšèŠïŒ",
+ extractButton: "æåçºè®æž",
+ extractSuccess: "æåæåè®æž {{variableName}}",
+ extractSuccessAll: "æåæåè®æž {{variableName}}ïŒåä»£äº {count} èïŒ",
+ savedToGlobal: "è®æž {name} å·²å²åå°å
šå",
+ savedToTemporary: "è®æž {name} å·²å²åå°èšæè®æž",
+ validation: {
+ required: "è®æžåçš±äžèœçºç©º",
+ noNumberStart: "è®æžåçš±äžèœä»¥æžåéé ",
+ invalidCharacters: "è®æžåçš±åªèœå
å«äžæãè±æãæžåãåºç·",
+ predefinedVariable: "äžèœäœ¿çšç³»çµ±é å®çŸ©è®æžåçš±",
+ duplicateVariable: "è©²è®æžå皱已ååšïŒå°åç
§çŸæè®æž",
+ },
+ },
};
diff --git a/packages/ui/tests/e2e/context-editor-refactor.spec.ts b/packages/ui/tests/e2e/context-editor-refactor.spec.ts
index 6745f005..bac2dcbc 100644
--- a/packages/ui/tests/e2e/context-editor-refactor.spec.ts
+++ b/packages/ui/tests/e2e/context-editor-refactor.spec.ts
@@ -3,9 +3,9 @@ import { mount, VueWrapper } from '@vue/test-utils'
import { nextTick } from 'vue'
// ç»ä»¶å¯Œå
¥
-import ConversationManager from '../../src/components/ConversationManager.vue'
-import ContextEditor from '../../src/components/ContextEditor.vue'
-import VariableManagerModal from '../../src/components/VariableManagerModal.vue'
+import ConversationManager from '../../src/components/context-mode/ConversationManager.vue'
+import ContextEditor from '../../src/components/context-mode/ContextEditor.vue'
+import VariableManagerModal from '../../src/components/variable/VariableManagerModal.vue'
// Mock Naive UI ç»ä»¶ - ç®åçæ¬çšäºE2Eæµè¯
vi.mock('naive-ui', () => ({
diff --git a/packages/ui/tests/integration/context-editor-persist.spec.ts b/packages/ui/tests/integration/context-editor-persist.spec.ts
index 55d7bb5c..d2d9181f 100644
--- a/packages/ui/tests/integration/context-editor-persist.spec.ts
+++ b/packages/ui/tests/integration/context-editor-persist.spec.ts
@@ -1,7 +1,7 @@
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest'
import { mount, VueWrapper } from '@vue/test-utils'
import { ref, nextTick } from 'vue'
-import ContextEditor from '../../src/components/ContextEditor.vue'
+import ContextEditor from '../../src/components/context-mode/ContextEditor.vue'
import { createContextRepo, MemoryStorageProvider } from '@prompt-optimizer/core'
import type { ContextRepo } from '@prompt-optimizer/core'
diff --git a/packages/ui/tests/integration/context-editor-sync.spec.ts b/packages/ui/tests/integration/context-editor-sync.spec.ts
index 98ed2285..4fd9f19f 100644
--- a/packages/ui/tests/integration/context-editor-sync.spec.ts
+++ b/packages/ui/tests/integration/context-editor-sync.spec.ts
@@ -2,8 +2,8 @@ import { describe, it, expect, beforeEach, vi, beforeAll, afterAll } from 'vites
import { mount, VueWrapper } from '@vue/test-utils'
import { ref, nextTick, reactive } from 'vue'
import { createApp } from 'vue'
-import ConversationManager from '../../src/components/ConversationManager.vue'
-import ContextEditor from '../../src/components/ContextEditor.vue'
+import ConversationManager from '../../src/components/context-mode/ConversationManager.vue'
+import ContextEditor from '../../src/components/context-mode/ContextEditor.vue'
// Mock Naive UI ç»ä»¶
vi.mock('naive-ui', () => ({
diff --git a/packages/ui/tests/unit/components/ContextEditor.spec.ts b/packages/ui/tests/unit/components/ContextEditor.spec.ts
index 28545b0b..86eeb9c0 100644
--- a/packages/ui/tests/unit/components/ContextEditor.spec.ts
+++ b/packages/ui/tests/unit/components/ContextEditor.spec.ts
@@ -1,7 +1,7 @@
import { describe, it, expect, beforeEach, vi } from 'vitest'
import { mount } from '@vue/test-utils'
import { nextTick } from 'vue'
-import ContextEditor from '../../../src/components/ContextEditor.vue'
+import ContextEditor from '../../../src/components/context-mode/ContextEditor.vue'
// Mock quickTemplateManager using factory function
vi.mock('../../../src/data/quickTemplates', () => {
diff --git a/packages/web/src/App.vue b/packages/web/src/App.vue
index 10f1e2e3..ecc6c010 100644
--- a/packages/web/src/App.vue
+++ b/packages/web/src/App.vue
@@ -129,6 +129,7 @@
(null);
const optimizeModelSelect = ref(null);
const testPanelRef = ref(null);
+const systemWorkspaceRef = ref(null);
+const userWorkspaceRef = ref(null);
const promptPanelRef = ref<{
refreshIterateTemplateSelect?: () => void;
} | null>(null);
@@ -1153,24 +1148,6 @@ const handleModeSelect = async (mode: "basic" | "pro" | "image") => {
const testContent = ref("");
const isCompareMode = ref(true);
-// æµè¯ç»æç¶æç®¡ç
-const testResults = ref({
- // åå§æç€ºè¯ç»æ
- originalResult: "",
- originalReasoning: "",
- isTestingOriginal: false,
-
- // äŒåæç€ºè¯ç»æ
- optimizedResult: "",
- optimizedReasoning: "",
- isTestingOptimized: false,
-
- // åäžç»ææš¡åŒ
- singleResult: "",
- singleReasoning: "",
- isTestingSingle: false,
-});
-
// ååºåŒåžå±åæš¡åŒé
眮 - æ°å¢
const responsiveLayout = useResponsiveTestLayout();
const testModeConfig = useTestModeConfig(selectedOptimizationMode);
@@ -1314,24 +1291,48 @@ const contextManagement = useContextManagement({
// ä» contextManagement æåå
¶ä»ç¶æåæ¹æ³ (contextMode é€å€,å·²åšåé¢å£°æ)
const optimizationContext = contextManagement.optimizationContext;
const optimizationContextTools = contextManagement.optimizationContextTools;
-const isContextLoaded = contextManagement.isContextLoaded;
-const currentContextId = contextManagement.currentContextId;
-const contextRepo = contextManagement.contextRepo;
-const currentContextVariables = contextManagement.currentContextVariables;
const predefinedVariables = contextManagement.predefinedVariables;
const initializeContextPersistence =
contextManagement.initializeContextPersistence;
-const persistContextUpdate = contextManagement.persistContextUpdate;
const handleOpenContextEditor = contextManagement.handleOpenContextEditor;
const handleContextEditorSave = contextManagement.handleContextEditorSave;
const handleContextEditorStateUpdate =
contextManagement.handleContextEditorStateUpdate;
const handleContextModeChange = contextManagement.handleContextModeChange;
-const updateContextVariable = contextManagement.updateContextVariable;
-// å€çæµè¯é¢æ¿çåéååïŒåæ¥å°äŒè¯çº§åé
+// ð æç€ºè¯æµè¯ç®¡çïŒæ¯æåéæ³šå
¥ãäžäžæãå·¥å
·è°çšïŒ
+const promptTester = usePromptTester(
+ services as any,
+ toRef(modelManager, 'selectedTestModel'),
+ selectedOptimizationMode,
+ advancedModeEnabled,
+ optimizationContext,
+ optimizationContextTools,
+ variableManager
+);
+
+// æµè¯ç»æåŒçšïŒä» promptTester è·åïŒ
+const testResults = computed(() => promptTester.testResults);
+
+// å€çæµè¯é¢æ¿çåéååïŒç°åšæµè¯åéç±TestAreaPanelèªå·±ç®¡çïŒäžéèŠåæ¥å°äŒè¯ïŒ
const handleTestPanelVariableChange = async (name: string, value: string) => {
- await updateContextVariable(name, value);
+ // æµè¯åéç°åšåªåšTestAreaPanelå
éšç®¡çïŒäžéèŠå€éšåæ¥
+};
+
+// ð å€çä¿åæµè¯åéå°å
šå±
+const handleSaveToGlobal = async (name: string, value: string) => {
+ if (!variableManager) {
+ console.warn("[App] variableManager not ready");
+ return;
+ }
+
+ try {
+ variableManager.updateVariable(name, value);
+ toast.success(t('test.variables.savedToGlobal', { name }));
+ } catch (error) {
+ console.error("[App] Failed to save variable to global:", error);
+ toast.error(t('test.error.saveToGlobalFailed', { name }));
+ }
};
// 忥 contextManagement äžç contextMode 尿们ç contextMode ref
@@ -2051,178 +2052,16 @@ const promptInputPlaceholder = computed(() => {
});
// ç宿µè¯å€çåœæ°
-const handleTestAreaTest = async () => {
- if (!services.value?.promptService) {
- useToast().error("æå¡æªåå§åïŒè¯·çšåéè¯");
- return;
- }
-
- if (!modelManager.selectedTestModel) {
- useToast().error("请å
éæ©æµè¯æš¡å");
- return;
- }
-
- console.log("[App] Starting real test with content:", testContent.value);
-
- if (isCompareMode.value) {
- // å¯¹æ¯æš¡åŒïŒæµè¯åå§åäŒåæç€ºè¯
- await Promise.all([
- testPromptWithType("original"),
- testPromptWithType("optimized"),
- ]);
- } else {
- // åäžæš¡åŒïŒåªæµè¯äŒååçæç€ºè¯
- await testPromptWithType("optimized");
- }
-};
-
-// æµè¯ç¹å®ç±»åçæç€ºè¯ïŒå€çšäŒè¯äžäžæ + åé + å·¥å
·ïŒ
-const testPromptWithType = async (type: "original" | "optimized") => {
- const isOriginal = type === "original";
- const prompt = isOriginal ? optimizer.prompt : optimizer.optimizedPrompt;
-
- if (!prompt) {
- useToast().error(
- isOriginal ? "请å
èŸå
¥åå§æç€ºè¯" : "请å
çæäŒååçæç€ºè¯",
- );
- return;
- }
-
- // 讟眮æµè¯ç¶æ
- if (isOriginal) {
- testResults.value.isTestingOriginal = true;
- testResults.value.originalResult = "";
- testResults.value.originalReasoning = "";
- } else {
- testResults.value.isTestingOptimized = true;
- testResults.value.optimizedResult = "";
- testResults.value.optimizedReasoning = "";
- }
-
- // æž
é€å¯¹åºç±»åçå·¥å
·è°çšæ°æ®
- testPanelRef.value?.clearToolCalls?.(isOriginal ? "original" : "optimized");
-
- try {
- const streamHandler = {
- onToken: (token: string) => {
- if (isOriginal) {
- testResults.value.originalResult += token;
- } else {
- testResults.value.optimizedResult += token;
- }
- },
- onReasoningToken: (reasoningToken: string) => {
- if (isOriginal) {
- testResults.value.originalReasoning += reasoningToken;
- } else {
- testResults.value.optimizedReasoning += reasoningToken;
- }
- },
- onComplete: () => {
- console.log(`[App] ${type} test completed`);
- },
- onError: (err: Error) => {
- const errorMessage = err.message || t("test.error.failed");
- console.error(`[App] ${type} test failed:`, errorMessage);
- useToast().error(
- `${type === "original" ? "åå§" : "äŒå"}æç€ºè¯æµè¯å€±èŽ¥: ${errorMessage}`,
- );
- },
- };
-
- // ç»äžæé 对è¯äžåéïŒå°œéå€çšäžäžæ
- let systemPrompt = "";
- let userPrompt = "";
-
- if (selectedOptimizationMode.value === "user") {
- // çšæ·æç€ºè¯æš¡åŒïŒæç€ºè¯äœäžºçšæ·èŸå
¥
- systemPrompt = "";
- userPrompt = prompt;
- } else {
- // ç³»ç»æç€ºè¯æš¡åŒïŒæç€ºè¯äœäžºç³»ç»æ¶æ¯
- systemPrompt = prompt;
- userPrompt =
- testContent.value ||
- "请æç
§äœ çè§è²è®Ÿå®ïŒå±ç€ºäœ çèœåå¹¶äžæäºåšã";
- }
-
- const hasConversationContext =
- selectedOptimizationMode.value === "system" &&
- advancedModeEnabled.value &&
- (optimizationContext.value?.length || 0) > 0;
- const hasTools =
- advancedModeEnabled.value &&
- (optimizationContextTools.value?.length || 0) > 0;
-
- // åéïŒåå¹¶åéåº + åœåæç€ºè¯/é®é¢ïŒçšäºäŒè¯æš¡æ¿äžçå äœç¬ŠïŒ
- // æäŒå
级åå¹¶: å
šå±èªå®ä¹åé < äŒè¯çº§åé < é¢å®ä¹åé
- const baseVars =
- (variableManager?.variableManager.value?.resolveAllVariables() ||
- {}) as Record;
- const variables = {
- ...baseVars,
- ...currentContextVariables.value, // äŒè¯çº§åéïŒå
嫿µè¯é¢æ¿èŸå
¥ïŒ
- currentPrompt: prompt,
- userQuestion: userPrompt,
- };
-
- // å¯¹è¯æé é»èŸïŒ
- // - ç³»ç»æš¡åŒ + æäŒè¯äžäžæïŒäœ¿çšäŒè¯äžäžæïŒå äžºç³»ç»æç€ºè¯æ¬èº«å°±æ¯å€èœ®å¯¹è¯åºæ¯ïŒ
- // - çšæ·æš¡åŒïŒæ 论æ¯åŠæäŒè¯äžäžæïŒéœçŽæ¥åéäŒååçæç€ºè¯äœäžºçšæ·æ¶æ¯
- // ïŒå äžºçšæ·æç€ºè¯äŒåçç®æ æ¯çæå¯çŽæ¥äœ¿çšçåæ¡çšæ·æ¶æ¯ïŒ
- const messages =
- selectedOptimizationMode.value === "system" &&
- hasConversationContext
- ? optimizationContext.value.map((m) => ({
- role: m.role,
- content: m.content,
- }))
- : [
- ...(systemPrompt
- ? [{ role: "system", content: systemPrompt }]
- : []),
- { role: "user", content: userPrompt },
- ];
-
- // ç»äžäœ¿çšèªå®ä¹äŒè¯æµè¯ïŒä»¥äŸ¿æ¯æäžäžæäžå·¥å
·
- await services.value.promptService.testCustomConversationStream(
- {
- modelKey: modelManager.selectedTestModel,
- messages,
- variables,
- tools: hasTools ? optimizationContextTools.value : [],
- },
- {
- ...streamHandler,
- onToolCall: (toolCall: any) => {
- if (!hasTools) return;
- console.log(
- `[App] ${type} test tool call received:`,
- toolCall,
- );
- const toolCallResult = {
- toolCall: toolCall,
- status: "success" as const,
- timestamp: new Date(),
- };
- testPanelRef.value?.handleToolCall?.(toolCallResult, type);
- },
- },
- );
- } catch (error: any) {
- console.error(`[App] ${type} test error:`, error);
- const errorMessage = error.message || t("test.error.failed");
- useToast().error(
- `${type === "original" ? "åå§" : "äŒå"}æç€ºè¯æµè¯å€±èŽ¥: ${errorMessage}`,
- );
- } finally {
- // é眮æµè¯ç¶æ
- if (isOriginal) {
- testResults.value.isTestingOriginal = false;
- } else {
- testResults.value.isTestingOptimized = false;
- }
- }
+const handleTestAreaTest = async (testVariables?: Record) => {
+ // è°çš promptTester ç executeTest æ¹æ³
+ await promptTester.executeTest(
+ optimizer.prompt,
+ optimizer.optimizedPrompt,
+ testContent.value,
+ isCompareMode.value,
+ testVariables,
+ testPanelRef.value
+ );
};
const handleTestAreaCompareToggle = () => {
diff --git a/packages/web/vitest.config.ts b/packages/web/vitest.config.ts
index 1fcef17b..f56a57a5 100644
--- a/packages/web/vitest.config.ts
+++ b/packages/web/vitest.config.ts
@@ -12,7 +12,12 @@ export default defineConfig({
// å
å«çæä»¶æš¡åŒ
include: ['tests/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
// æé€çæä»¶æš¡åŒ
- exclude: ['**/node_modules/**', '**/dist/**', '**/.{idea,git,cache,output,temp}/**'],
+ exclude: [
+ '**/node_modules/**',
+ '**/dist/**',
+ '**/.{idea,git,cache,output,temp}/**',
+ '**/tests/e2e/**', // æé€ Playwright E2E æµè¯
+ ],
// å
šå±æµè¯è®Ÿçœ®
globals: true,
// æµè¯èŠççé
眮