Skip to content

Commit 6783f25

Browse files
committed
feat(ui): 为 ConversationManager 集成 VariableAwareInput 组件并统一临时变量处理逻辑
- 将 NInput 替换为 VariableAwareInput,支持变量感知输入 - 添加 temporaryVariables props 支持临时变量自动补全 - 添加 variable-extracted 和 add-missing-variable 事件,用于变量提取与补全 - 在 ConversationManager、ContextEditor 及 ConversationTestPanel 中集成 VariableAwareInput 组件 - 初始化 useTemporaryVariables 在 ContextSystemWorkspace,确保与 ContextUserWorkspace 共享同一实例 - 修复 ContextSystemWorkspace 临时变量不显示问题:传递临时变量并添加变更/移除/清空事件处理 - 确保 ContextEditor 提取的变量可实时显示在测试区域,行为与 ContextUserWorkspace 保持一致 - 修复缺失变量计算未包含临时变量的问题,确保临时变量参与计算 - 修复 v-else 条件逻辑导致的内容重复显示问题 - 统一 toast 提示逻辑,避免重复提示 - 重构 CodeMirror 自动补全配置:替换 basicSetup 为手动扩展配置以减小打包体积 - 使用 Compartment 管理自动补全配置,替代原有嵌套 override 方式 - 移除未使用的导入和冗余变量,确保变量动态更新功能正常 - 移除 handleTestVariableRemove 中多余的 hasVariable 检查,与 ContextSystemWorkspace 逻辑对齐 - deleteVariable 方法已内部处理变量不存在情况,无需额外判断 - 修改 i18n 提示文本:将“缺失”改为“变量缺失”,避免语义歧义
1 parent 3bc03c3 commit 6783f25

File tree

9 files changed

+210
-76
lines changed

9 files changed

+210
-76
lines changed

packages/ui/src/components/context-mode/ContextEditor.vue

Lines changed: 38 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -392,28 +392,26 @@
392392

393393
<!-- 消息内容 -->
394394
<div v-if="!previewMode.get(index)">
395-
<NInput
396-
v-model:value="message.content"
397-
type="textarea"
398-
:placeholder="
399-
getPlaceholderText(
400-
message.role,
401-
)
395+
<VariableAwareInput
396+
:model-value="message.content"
397+
@update:model-value="
398+
handleMessageUpdate(index, {
399+
...message,
400+
content: $event,
401+
})
402402
"
403-
:autosize="{
404-
minRows: 1,
405-
maxRows: 20,
406-
}"
407-
:size="inputSize"
403+
:placeholder="getPlaceholderText(message.role)"
404+
:autosize="{ minRows: 1, maxRows: 20 }"
408405
:disabled="disabled"
409-
@update:value="
410-
handleMessageUpdate(
411-
index,
412-
message,
413-
)
414-
"
415-
/>
416-
<!-- 缺失变量提示与快捷操作 -->
406+
:existing-global-variables="Object.keys(aggregatedVars.variablesBySource.value.global)"
407+
:existing-temporary-variables="Object.keys(aggregatedVars.variablesBySource.value.temporary)"
408+
:predefined-variables="Object.keys(aggregatedVars.variablesBySource.value.predefined)"
409+
:global-variable-values="aggregatedVars.variablesBySource.value.global"
410+
:temporary-variable-values="aggregatedVars.variablesBySource.value.temporary"
411+
:predefined-variable-values="aggregatedVars.variablesBySource.value.predefined"
412+
@variable-extracted="handleVariableExtracted"
413+
@add-missing-variable="handleCreateVariableAndOpenManager"
414+
/> <!-- 缺失变量提示与快捷操作 -->
417415
<NCard
418416
v-if="
419417
getMessageVariables(
@@ -1441,6 +1439,7 @@ import { usePerformanceMonitor } from "../../composables/performance/usePerforma
14411439
import { useDebounceThrottle } from '../../composables/performance/useDebounceThrottle';
14421440
import { useAccessibility } from "../../composables/accessibility/useAccessibility";
14431441
import { useTemporaryVariables } from '../../composables/variable/useTemporaryVariables';
1442+
import { VariableAwareInput } from "../variable-extraction";
14441443
import ImportExportDialog from './ImportExportDialog.vue';
14451444
import { useAggregatedVariables } from '../../composables/variable/useAggregatedVariables';
14461445
import {
@@ -1517,7 +1516,6 @@ const aggregatedVars = useAggregatedVariables(variableManager);
15171516
const {
15181517
modalWidth,
15191518
buttonSize: responsiveButtonSize,
1520-
inputSize: responsiveInputSize,
15211519
isMobile,
15221520
} = useResponsive();
15231521
@@ -1633,10 +1631,6 @@ const cardSize = computed(() => {
16331631
return sizeMap[responsiveButtonSize.value] || "small";
16341632
});
16351633
1636-
const inputSize = computed(() => {
1637-
return responsiveInputSize.value;
1638-
});
1639-
16401634
const modalStyle = computed(() => ({
16411635
width: modalWidth.value,
16421636
height: isMobile.value ? "95vh" : props.height || "85vh",
@@ -1784,6 +1778,25 @@ const handleMessageUpdate = debounce(
17841778
"messageUpdate",
17851779
);
17861780
1781+
// 变量提取处理
1782+
const handleVariableExtracted = (data: {
1783+
variableName: string;
1784+
variableValue: string;
1785+
variableType: "global" | "temporary";
1786+
}) => {
1787+
if (data.variableType === "global") {
1788+
props.variableManager.addVariable(data.variableName, data.variableValue);
1789+
window.$message?.success(
1790+
t("variableExtraction.savedToGlobal", { name: data.variableName })
1791+
);
1792+
} else {
1793+
tempVars.setVariable(data.variableName, data.variableValue);
1794+
window.$message?.success(
1795+
t("variableExtraction.savedToTemporary", { name: data.variableName })
1796+
);
1797+
}
1798+
};
1799+
17871800
const togglePreview = throttle(
17881801
(index: number) => {
17891802
const currentMode = previewMode.value.get(index) || false;

packages/ui/src/components/context-mode/ContextSystemWorkspace.vue

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
emit('update:optimizationContext', $event)
3131
"
3232
:available-variables="availableVariables"
33+
:temporary-variables="tempVars.temporaryVariables.value"
3334
:scan-variables="scanVariables"
3435
:optimization-mode="optimizationMode"
3536
:tool-count="toolCount"
@@ -45,6 +46,8 @@
4546
@message-select="conversationOptimization.selectMessage"
4647
@optimize-message="handleOptimizeClick"
4748
@message-change="(index, message, action) => emit('message-change', index, message, action)"
49+
@variable-extracted="handleVariableExtracted"
50+
@add-missing-variable="handleAddMissingVariable"
4851
/>
4952
</NCard>
5053

@@ -177,18 +180,19 @@
177180
@compare-toggle="emit('compare-toggle')"
178181
:global-variables="globalVariables"
179182
:predefined-variables="predefinedVariables"
183+
:temporary-variables="tempVars.temporaryVariables.value"
180184
:input-mode="inputMode"
181185
:control-bar-layout="controlBarLayout"
182186
:button-size="buttonSize"
183187
:result-vertical-layout="resultVerticalLayout"
184188
@test="handleTestWithVariables"
185189
@open-variable-manager="emit('open-variable-manager')"
186-
@variable-change="
187-
(name: string, value: string) => emit('variable-change', name, value)
188-
"
190+
@variable-change="handleVariableChange"
189191
@save-to-global="
190192
(name: string, value: string) => emit('save-to-global', name, value)
191193
"
194+
@temporary-variable-remove="handleVariableRemove"
195+
@temporary-variables-clear="handleVariablesClear"
192196
>
193197
<!-- 模型选择插槽 -->
194198
<template #model-select>
@@ -248,6 +252,7 @@ import OutputDisplay from "../OutputDisplay.vue";
248252
import { useConversationTester } from '../../composables/prompt/useConversationTester'
249253
import { useConversationOptimization } from '../../composables/prompt/useConversationOptimization'
250254
import { usePromptDisplayAdapter } from '../../composables/prompt/usePromptDisplayAdapter'
255+
import { useTemporaryVariables } from '../../composables/variable/useTemporaryVariables'
251256
import type { OptimizationMode, ConversationMessage } from "../../types";
252257
import type {
253258
PromptRecord,
@@ -375,6 +380,9 @@ const { t } = useI18n();
375380
const services = inject<Ref<AppServices | null>>('services')
376381
const variableManager = inject<VariableManagerHooks | null>('variableManager')
377382
383+
// 🆕 初始化临时变量管理器(与 ContextEditor 共享)
384+
const tempVars = useTemporaryVariables()
385+
378386
// 🆕 初始化本地会话优化逻辑
379387
const conversationOptimization = useConversationOptimization(
380388
services || ref(null),
@@ -496,6 +504,45 @@ const handleApplyToConversation = () => {
496504
conversationOptimization.applyCurrentVersion();
497505
};
498506
507+
// 🆕 处理变量提取
508+
// 注意:toast 已在 VariableAwareInput 中显示,这里不重复(参考 ContextUserWorkspace 的实现)
509+
const handleVariableExtracted = (data: {
510+
variableName: string;
511+
variableValue: string;
512+
variableType: "global" | "temporary";
513+
}) => {
514+
if (data.variableType === "global") {
515+
variableManager?.addVariable(data.variableName, data.variableValue);
516+
} else {
517+
tempVars.setVariable(data.variableName, data.variableValue);
518+
}
519+
};
520+
521+
// 🆕 处理添加缺失变量
522+
// 注意:toast 已在 VariableAwareInput 中显示,这里不重复(参考 ContextUserWorkspace 的实现)
523+
const handleAddMissingVariable = (varName: string) => {
524+
tempVars.setVariable(varName, "");
525+
};
526+
527+
// 🆕 处理临时变量变更
528+
const handleVariableChange = (name: string, value: string) => {
529+
tempVars.setVariable(name, value);
530+
emit('variable-change', name, value);
531+
};
532+
533+
// 🆕 处理临时变量移除
534+
const handleVariableRemove = (name: string) => {
535+
tempVars.deleteVariable(name);
536+
emit('variable-change', name, '');
537+
};
538+
539+
// 🆕 处理清空所有临时变量
540+
const handleVariablesClear = () => {
541+
const removedNames = Object.keys(tempVars.temporaryVariables.value);
542+
tempVars.clearAll();
543+
removedNames.forEach(name => emit('variable-change', name, ''));
544+
};
545+
499546
// 🆕 处理测试事件
500547
const handleTestWithVariables = async () => {
501548
const testVariables = testAreaPanelRef.value?.getVariableValues?.() || {};

packages/ui/src/components/context-mode/ContextUserWorkspace.vue

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -524,10 +524,7 @@ const handleTestVariableChange = (name: string, value: string) => {
524524
* 🆕 测试区域移除临时变量时的处理
525525
*/
526526
const handleTestVariableRemove = (name: string) => {
527-
// 🆕 使用 composable 方法删除变量
528-
if (tempVarsManager.hasVariable(name)) {
529-
tempVarsManager.deleteVariable(name);
530-
}
527+
tempVarsManager.deleteVariable(name);
531528
emit("variable-change", name, "");
532529
};
533530

packages/ui/src/components/context-mode/ConversationManager.vue

Lines changed: 76 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -159,30 +159,25 @@
159159
</NDropdown>
160160
</NSpace>
161161

162-
<!-- 内容输入,单行自增高 -->
162+
<!-- 内容输入 -->
163163
<div class="content">
164-
<NInput
164+
<VariableAwareInput
165165
v-if="canEditMessages"
166-
:value="message.content"
167-
@update:value="
168-
(value) =>
169-
handleMessageUpdate(index, {
170-
...message,
171-
content: value,
172-
})
173-
"
174-
type="textarea"
175-
:placeholder="
176-
t(
177-
`conversation.placeholders.${message.role}`,
178-
)
179-
"
180-
:autosize="{ minRows: 1, maxRows: 1 }"
181-
:resizable="false"
182-
:size="inputSize"
183-
:style="{ width: '100%' }"
166+
:model-value="message.content"
167+
@update:model-value="(value) => handleMessageUpdate(index, { ...message, content: value })"
168+
:placeholder="t(`conversation.placeholders.${message.role}`)"
169+
:autosize="{ minRows: 1, maxRows: 10 }"
170+
:existing-global-variables="Object.keys(props.availableVariables || {})"
171+
:existing-temporary-variables="Object.keys(props.temporaryVariables || {})"
172+
:predefined-variables="PREDEFINED_VARIABLES"
173+
:global-variable-values="props.availableVariables || {}"
174+
:temporary-variable-values="props.temporaryVariables || {}"
175+
:predefined-variable-values="{}"
176+
@variable-extracted="handleVariableExtracted"
177+
@add-missing-variable="handleAddMissingVariable"
184178
/>
185-
<NText v-else>{{ message.content }}</NText>
179+
<!-- 只读模式下显示纯文本 -->
180+
<NText v-if="!canEditMessages">{{ message.content }}</NText>
186181
</div>
187182

188183
<!-- 操作按钮(选择/上/下/删) -->
@@ -364,12 +359,13 @@ import {
364359
NScrollbar,
365360
NList,
366361
NListItem,
367-
NInput,
368362
NDropdown,
369363
} from "naive-ui";
370364
import { usePerformanceMonitor } from "../../composables/performance/usePerformanceMonitor";
371365
import { useDebounceThrottle } from '../../composables/performance/useDebounceThrottle';
372366
import { useToast } from "../../composables/ui/useToast";
367+
import { VariableAwareInput } from "../variable-extraction";
368+
import { PREDEFINED_VARIABLES } from "../../types/variable";
373369
import type {
374370
ConversationManagerProps,
375371
ConversationManagerEvents,
@@ -399,6 +395,8 @@ const props = withDefaults(defineProps<ConversationManagerProps>(), {
399395
scanVariables: () => [],
400396
replaceVariables: (content: string) => content,
401397
isPredefinedVariable: () => false,
398+
// 🆕 临时变量
399+
temporaryVariables: () => ({}),
402400
// 🆕 消息优化相关
403401
selectedMessageId: undefined,
404402
enableMessageOptimization: false,
@@ -447,15 +445,6 @@ const cardSize = computed(() => {
447445
return sizeMap[props.size] || "small";
448446
});
449447
450-
const inputSize = computed(() => {
451-
const sizeMap = {
452-
small: "small",
453-
medium: "medium",
454-
large: "large",
455-
} as const;
456-
return sizeMap[props.size] || "medium";
457-
});
458-
459448
const contentStyle = computed(() => {
460449
const style: Record<string, string | number> = {};
461450
if (props.maxHeight && !isCollapsed.value) {
@@ -492,9 +481,10 @@ const allUsedVariables = computed(() => {
492481
});
493482
494483
const allMissingVariables = computed(() => {
495-
const available = props.availableVariables || {};
484+
const globalVars = props.availableVariables || {};
485+
const tempVars = props.temporaryVariables || {};
496486
return allUsedVariables.value.filter(
497-
(name) => available[name] === undefined,
487+
(name) => globalVars[name] === undefined && tempVars[name] === undefined,
498488
);
499489
});
500490
@@ -681,6 +671,21 @@ const handleRoleSelect = (index: number, role: ConversationMessage["role"]) => {
681671
emit("messageChange", index, updated, "update");
682672
};
683673
674+
// 处理变量提取
675+
// 注意:只 emit 事件,由父组件处理保存和显示 toast(参考 ContextEditor 的实现)
676+
const handleVariableExtracted = (data: {
677+
variableName: string;
678+
variableValue: string;
679+
variableType: "global" | "temporary";
680+
}) => {
681+
emit('variable-extracted', data);
682+
};
683+
684+
// 处理添加缺失变量
685+
const handleAddMissingVariable = (varName: string) => {
686+
emit('add-missing-variable', varName);
687+
};
688+
684689
// 🆕 消息优化功能
685690
// 判断消息是否可以被优化(只有 user 和 system 角色可优化)
686691
const canOptimizeMessage = (message: ConversationMessage): boolean => {
@@ -754,10 +759,9 @@ watch(
754759
755760
.cm-row {
756761
display: flex;
757-
align-items: center;
762+
align-items: flex-start; /* 改为顶部对齐 */
758763
gap: 8px;
759764
flex-wrap: nowrap;
760-
white-space: nowrap;
761765
}
762766
763767
.cm-row .actions {
@@ -783,6 +787,43 @@ watch(
783787
min-width: 0;
784788
}
785789
790+
/* VariableAwareInput 样式适配 */
791+
.cm-row .content :deep(.variable-aware-input-wrapper) {
792+
width: 100%;
793+
}
794+
795+
.cm-row .content :deep(.codemirror-container) {
796+
border: 1px solid var(--n-border-color);
797+
border-radius: var(--n-border-radius);
798+
transition: border-color 0.3s;
799+
}
800+
801+
.cm-row .content :deep(.codemirror-container:hover) {
802+
border-color: var(--n-border-color-hover);
803+
}
804+
805+
.cm-row .content :deep(.codemirror-container:focus-within) {
806+
border-color: var(--n-primary-color);
807+
box-shadow: 0 0 0 2px var(--n-primary-color-suppl);
808+
}
809+
810+
/* CodeMirror 高度控制 */
811+
.cm-row .content :deep(.cm-scroller) {
812+
min-height: 1.5em;
813+
max-height: 15em; /* 约 10 行 */
814+
}
815+
816+
/* 移动端适配 */
817+
@media (max-width: 768px) {
818+
.cm-row {
819+
gap: 4px;
820+
}
821+
822+
.cm-row .content :deep(.cm-scroller) {
823+
max-height: 12em;
824+
}
825+
}
826+
786827
/* 🆕 消息优化功能样式 */
787828
.message-card {
788829
transition: all 0.2s ease;

0 commit comments

Comments
 (0)