docs/archives/128-context-ui-and-variable-system-refactor/design.md
ææ¡£çæ¬: v2.1 åå»ºæ¥æ: 2025-10-22 å®ææ¥æ: 2025-10-23 è®Ÿè®¡ç®æ : ç®ååéç³»ç»,ç§»é€åäœçäŒè¯åé,åŒå ¥æµè¯åºäžŽæ¶åé äŒå 级: ðŽ P0 é«äŒå 级 ç¶æ: â 已宿并éè¿æµè¯
å®é 宿œè¿çšäž,é€äºåè®Ÿè®¡æ¹æ¡å€,è¿è¿è¡äºä»¥äžæ¶æäŒå:
å讟计: æµè¯é»èŸåæ£åš App.vue äž
å®é 宿œ:
packages/ui/src/composables/usePromptTester.tsäŒå¿:
é®é¢åç°: useContextManagement.ts åæ¬åš packages/web/src/composables/ äž
å®é 宿œ:
packages/ui/src/composables/packages/ui/src/composables/index.ts ç»äžå¯Œåºåå : Web æš¡åäŸèµ UI æš¡å,äžåºå å«å¯å ±äº«ç composable é»èŸ
å讟计: TestAreaPanel çŽæ¥äŒ éåéå° App.vue
å®é 宿œ:
TestAreaPanel.vue (æ£æµåé,æäŸèŸå
¥)
â (éè¿ ref.getVariableValues())
ContextUserWorkspace.vue / ContextSystemWorkspace.vue (è·ååé)
â (éè¿ emit('test', testVariables))
App.vue (æ¥æ¶åé)
â (è°çš promptTester.executeTest(testVariables))
usePromptTester.ts (æ§è¡æµè¯,åå¹¶åé)
åå :
å®é
å®ç°äœçœ®: usePromptTester.ts:192-203
const variables = {
...baseVars, // å
šå±èªå®ä¹åé
...(testVars || {}), // æµè¯åé(äŒå
级é«äºå
šå±)
currentPrompt: selectedPrompt, // é¢å®ä¹åé
userQuestion: userPrompt, // é¢å®ä¹åé
}
äŒå 级: å šå± < æµè¯ < é¢å®ä¹
packages/ui/src/composables/usePromptTester.ts
packages/ui/src/components/TestAreaPanel.vue
getVariableValues() æ¹æ³packages/ui/src/components/context-mode/ContextUserWorkspace.vue
packages/ui/src/components/context-mode/ContextSystemWorkspace.vue
testAreaPanelRef refhandleTestWithVariables() æ¹æ³packages/web/src/App.vue
testPromptWithType åœæ°packages/ui/src/composables/useContextManagement.ts
packages/ui/src/i18n/locales/*.ts
| æ¹é¢ | å讟计 | å®é 宿œ | åå |
|---|---|---|---|
| æµè¯é»èŸäœçœ® | App.vue | usePromptTester composable | æ¶æäŒå,éµåŸªæäœ³å®è·µ |
| åéäŒ é | çŽæ¥äŒ é | éè¿ Workspace äžèœ¬ | éé Pro æš¡åŒçç»ä»¶ç»æ |
| æš¡åç»ç» | - | ç§»åš useContextManagement | æ¶é€æš¡åäŸèµé误 |
| çŒååèœ | localStorage çŒå | æªå®æœ | äŒå æ žå¿åèœ,åç»è¡¥å |
ç®ååå: ç§»é€åœåè®Ÿè®¡äžæŠå¿µæ··æ·ç"äŒè¯åé",ä¿çæž æ°ç"å šå±åé"+"æµè¯äžŽæ¶åé"
çšæ·å¿æºæš¡å:
讟计åè¡·:
å€äžäžæç®¡çç³»ç»:
- äžäžæ1: åæè¯é¡¹ç® â äŒè¯åé: style=æµè¡, mood=欢快
- äžäžæ2: å代ç é¡¹ç® â äŒè¯åé: language=TypeScript
- å¯ä»¥åæ¢äžäžæ,æ¯äžªäžäžææç¬ç«çåéé
å®é æ åµ:
â åªæäžäžªæ°žä¹
çé»è®€äžäžæ
â æ æ³å建/忢äžäžæ
â UI屿²¡æäžäžæç®¡çåš
â "äŒè¯åé"å®é
äžæ¯åŠäžäžªå
šå±åéæ±
ç»è®º: åœåç"äŒè¯åé"äž"å šå±åé"æ¬èŽšäžæ²¡æåºå«,éœæ¯æ°žä¹ æä¹ åçå šå±åé,åªæ¯ååšäœçœ®äžåã
| ç¹æ§ | å šå±åé | äŒè¯åé (å®é ) |
|---|---|---|
| ååšäœçœ® | variableManager.storage | ctx:store çé»è®€äžäžæ |
| æä¹ å | â æ°žä¹ | â æ°žä¹ (åªæäžäžªäžäžæ) |
| äœçšå | å šåºçš | å šåºçš (æ æ³åæ¢äžäžæ) |
| çåœåšæ | æåšç®¡ç | æåšç®¡ç |
| æ¯åŠå¯åæ¢ | â | â (ç论å¯ä»¥,äœUIæªå®ç°) |
çšæ·å°æ:
åœåUI:
æµè¯åºæäœæ : [ðå
šå±åé] [ðäŒè¯åé] [ð§å·¥å
·ç®¡ç]
â çšæ·ç¹å»ååç°åå
šå±åéå·®äžå€
é®é¢:
ç§»é€äŒè¯åé + åŒå ¥æµè¯åºäžŽæ¶åé
åéç³»ç»æ¶æ:
âââââââââââââââââââââââââââââââââââââââ
â ð å
šå±åé (VariableManager) â
â - æä¹
åå° localStorage/æä»¶ â
â - è·šäŒè¯å
±äº« â
â - æåšCRUD管ç â
â - çšé: APIå¯é¥ãåžžçšé
眮 â
âââââââââââââââââââââââââââââââââââââââ
â (äœäŒå
级)
âââââââââââââââââââââââââââââââââââââââ
â 𧪠æµè¯åé (TestAreaPanel å
å) â
â - ä»
ååšå
å (ref) â
â - å·æ°é¡µé¢äž¢å€± â
â - æµè¯åºçŽæ¥èŸå
¥ â
â - çšé: åœåæµè¯çåéåŒ â
âââââââââââââââââââââââââââââââââââââââ
â (é«äŒå
级,èŠçå
šå±)
âââââââââââââââââââââââââââââââââââââââ
â ð§ é¢å®ä¹åé (è¿è¡æ¶è®¡ç®) â
â - currentPrompt, userQuestion... â
â - äŒå
级æé«,äžå¯èŠç â
âââââââââââââââââââââââââââââââââââââââ
åéåå¹¶äŒå
级: é¢å®ä¹ > æµè¯åé > å
šå±åé
åèœ:
å žåçšäŸ:
globalVariables = {
apiKey: "sk-xxxx...",
userName: "åŒ äž",
defaultLanguage: "äžæ",
tone: "äžäž",
}
ååšäœçœ®:
localStorage['variableManager.storage']userData/preferences.jsonåèœ:
å žåçšäŸ:
// çšæ·åšæµè¯åºèŸå
¥:
testVariables = {
topic: "ä»å€©æµè¯åæ", // 䞎æ¶è¯é¢
style: "欢快", // è¿æ¬¡æµè¯çšæ¬¢å¿«
}
// åŠæå
šå±åéäžä¹æ style: "æ£åŒ"
// æµè¯æ¶äœ¿çš "欢快" (æµè¯åéäŒå
级æŽé«)
å®ç°æ¹åŒ:
// TestAreaPanel.vue
const testVariables = ref<Record<string, string>>({})
// äžæä¹
å,å·æ°é¡µé¢å testVariables èªåšé眮䞺 {}
å¯éäŒå: äœ¿çš localStorage çŒåæè¿äžæ¬¡çæµè¯åé
// æµè¯å®æåçŒå
localStorage.setItem('test.lastVariables', JSON.stringify(testVariables.value))
// äžæ¬¡æåŒé¡µé¢æ¶æ¢å€ (äœå·æ°é¡µé¢ä»ç¶æž
空)
// è¿æ ·çšæ·è¿ç»æµè¯æ¶äžéèŠéæ°èŸå
¥
åèœ:
åéå衚:
predefinedVariables = {
currentPrompt: "åœåæç€ºè¯å
容",
userQuestion: "çšæ·æµè¯é®é¢",
originalPrompt: "åå§æç€ºè¯",
lastOptimizedPrompt: "äžæ¬¡äŒåç»æ",
// ... å
¶ä»é¢å®ä¹åé
}
æ¹é å:
âââââââââââââââââââââââââââââââââââââââââââ
â æµè¯åº â
âââââââââââââââââââââââââââââââââââââââââââ€
â [æµè¯] [ðå
šå±åé] [ðäŒè¯åé] [ð§å·¥å
·] â
â â ç¹å»æåŒå
šå±åé管çåš â
â â ç¹å»æåŒäžäžæçŒèŸåš-åéæ çŸ â
âââââââââââââââââââââââââââââââââââââââââââ€
â (æµè¯å
容...) â
âââââââââââââââââââââââââââââââââââââââââââ
æ¹é å:
âââââââââââââââââââââââââââââââââââââââââââ
â æµè¯åº â
âââââââââââââââââââââââââââââââââââââââââââ€
â [æµè¯] [ðå
šå±åé] [ð§å·¥å
·ç®¡ç] â
â â ç¹å»æåŒå
šå±åé管çåš â
â â ç§»é€äŒè¯åéæé® â
âââââââââââââââââââââââââââââââââââââââââââ€
â åéèŸå
¥ (䞎æ¶,å·æ°äž¢å€±): â
â {{style}} [欢快________] ð â
â â èŸå
¥æ¡ â æ¥èªå
šå± â
â {{topic}} [åæ________] â
â â æ°èŸå
¥ â
âââââââââââââââââââââââââââââââââââââââââââ€
â [â¶ æµè¯] â
âââââââââââââââââââââââââââââââââââââââââââ
âš æ¹è¿ç¹:
1. ç§»é€"äŒè¯åé"æé®,åå°å°æ
2. æµè¯åºçŽæ¥æŸç€ºåéèŸå
¥æ¡
3. åŠæäžå¡«å,èªåšäœ¿çšå
šå±åéçåŒ
4. å·æ°é¡µé¢åèŸå
¥æ¡æž
空
<template>
<div class="test-area-panel">
<!-- æ 颿 -->
<div class="test-header">
<NText strong>{{ $t('test.areaTitle') }}</NText>
<NFlex :size="8">
<NButton
size="small"
quaternary
@click="emit('open-global-variables')"
>
<template #icon><span>ð</span></template>
{{ $t('contextMode.actions.globalVariables') }}
</NButton>
<NButton
size="small"
quaternary
@click="emit('open-tool-manager')"
>
<template #icon><span>ð§</span></template>
{{ $t('contextMode.actions.tools') }}
</NButton>
</NFlex>
</div>
<!-- åéèŸå
¥åº -->
<div v-if="detectedVariables.length > 0" class="variable-inputs">
<NAlert type="info" size="small" closable>
<template #icon>ð¡</template>
{{ $t('test.variableInputHint') }}
<!-- "æµè¯åéä»
çšäºåœåæµè¯,å·æ°é¡µé¢åäŒæž
空ãåŠéä¿å,请添å å°å
šå±åéã" -->
</NAlert>
<div
v-for="varName in detectedVariables"
:key="varName"
class="variable-input-row"
>
<!-- åéåæ çŸ -->
<NTag size="small" :bordered="false">
<template #icon>
<span v-if="globalVariables[varName]">ð</span>
<span v-else>ð§ª</span>
</template>
{{ `{{${varName}}}` }}
</NTag>
<!-- åéåŒèŸå
¥ -->
<NInput
:value="testVariables[varName] || ''"
@update:value="handleVariableInput(varName, $event)"
:placeholder="getPlaceholder(varName)"
size="small"
>
<!-- å¿«éä¿åå°å
šå±åé -->
<template #suffix>
<NTooltip v-if="testVariables[varName] && !globalVariables[varName]">
<template #trigger>
<NButton
text
size="tiny"
@click="saveToGlobal(varName)"
>
ð
</NButton>
</template>
{{ $t('test.saveToGlobal') }}
</NTooltip>
<!-- æŸç€ºäœ¿çšå
šå±åé -->
<NTag
v-else-if="!testVariables[varName] && globalVariables[varName]"
size="tiny"
type="info"
>
ð
</NTag>
</template>
</NInput>
</div>
</div>
<!-- æµè¯æé®åº -->
<NButton @click="handleTest" type="primary" block>
{{ $t('test.run') }}
</NButton>
</div>
</template>
<script setup lang="ts">
const getPlaceholder = (varName: string) => {
if (globalVariables[varName]) {
return `å
šå±é»è®€åŒ: ${globalVariables[varName]}`
}
return $t('test.enterVariableValue')
}
const saveToGlobal = (varName: string) => {
const value = testVariables.value[varName]
if (!value) return
emit('save-to-global', varName, value)
window.$message?.success(
$t('test.savedToGlobal', { name: varName })
)
}
</script>
ä»»å¡å衚:
TestAreaPanel.vue æ·»å æµè¯åéç¶æå ³é®ä»£ç :
// TestAreaPanel.vue
const testVariables = ref<Record<string, string>>({})
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]
}
}
ä»»å¡å衚:
useContextManagement.ts ç§»é€äŒè¯åéé»èŸ (å¹¶ç§»åšå° ui æš¡å)ContextModeActions.vue äŒè¯åéæé®contextRepo äžç variables åæ®µ (ç»è¯äŒ°,äžåœ±ååèœ,ä¿ç)å é€ç代ç :
// useContextManagement.ts
// â å é€
const currentContextVariables = computed(() => {
return contextEditorState.value.variables || {}
})
// â å é€
const updateContextVariable = async (name: string, value: string) => {
// ...
}
// â å é€
contextEditorState.value = {
messages: [],
tools: [],
variables: {}, // â å é€è¿äžªå段
}
ä»»å¡å衚:
çŒåå®ç°:
// æµè¯æ¶çŒååéåŒ
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}} |
testVariables |
| å
šå±åéåé | 1. å
šå±åé style=æ£åŒstyle=æ£åŒtopic=åæ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æç€ºåææ¡£ |
| åŠä¹ ææ¬ | ð¢ äœ | éèŠåŠä¹ æ°çåéäœ¿çšæ¹åŒ | æ°æ¹åŒæŽç®å,åŠä¹ ææ¬éäœ |
çšæ·æå
FAQ
æŽæ°æ¥å¿
ç¶æ: ææéªæ¶æ åå·²éè¿ (2025-10-23)
| 绎床 | åœå讟计 | æ°è®Ÿè®¡ |
|---|---|---|
| æŠå¿µæž æ°åºŠ | âââ | âââââ |
| UIå€æåºŠ | 3䞪æé® | 2䞪æé® |
| åŠä¹ ææ¬ | é« | äœ |
| 䜿çšäŸ¿æ·æ§ | ââââ | âââââ |
| æä¹ ååŒé | é« | äœ |
| 代ç ç»Žæ€æ§ | 倿 | ç®å |
ææ¡£ç»Žæ€:
æµè¯éªè¯ - â 已宿 (2025-10-23)
æµè¯æ£æ¥é¡¹:
çšæ·ææ¡£æŽæ° ð¢
ææ¯åºå¡æž ç (å¯é)
contextRepo ç variables åæ®µä¿çäžåœ±ååèœ