fix(ui): allow unicode variable names in manager

This commit is contained in:
linshen committed 2026-06-06 15:42:17 +08:00
1 parent 74b06cea72
commit e039257871
4 files changed
+158 -16

No files matched your search

@@ -102,6 +102,10 @@ import {
type FormInst,
type FormRules,
} from "naive-ui";
import {
PREDEFINED_VARIABLES,
getVariableNameValidationError,
} from "../../types/variable";
const { t } = useI18n();
@@ -164,7 +168,8 @@ const formRules: FormRules = {
},
{
validator: (_rule: unknown, value: string) => {
if (value && !/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(value.trim())) {
const name = value?.trim();
if (name && getVariableNameValidationError(name)) {
return new Error(t("variables.editor.errors.nameInvalid"));
}
},
@@ -172,16 +177,12 @@ const formRules: FormRules = {
},
{
validator: (_rule: unknown, value: string) => {
const predefinedNames = [
"originalPrompt",
"lastOptimizedPrompt",
"iterateInput",
"currentPrompt",
"userQuestion",
"conversationContext",
"toolsContext",
];
if (value && predefinedNames.includes(value.trim())) {
if (
value &&
PREDEFINED_VARIABLES.includes(
value.trim() as typeof PREDEFINED_VARIABLES[number],
)
) {
return new Error(
t("variables.editor.errors.namePredefined"),
);
@@ -369,7 +369,7 @@ import { useConfirmDialog } from "../../composables/ui/useConfirmDialog";
import type {
VariableManagerModalProps,
} from "../../types/components";
import type { VariableSource } from "../../types/variable";
import { isValidVariableName, type VariableSource } from "../../types/variable";
import type { VariableManagerHooks } from '../../composables/prompt/useVariableManager';
import type { VariableExportData, VariableImportOptions } from '@prompt-optimizer/core';
import VariableEditor from "./VariableEditor.vue";
@@ -825,8 +825,7 @@ const cancelInlineEdit = () => {
// 快速添加功能
const quickAddVariable = async () => {
if (!props.variableManager?.variableManager.value) return;
if (!quickAddForm.value.name.trim() || !quickAddForm.value.value.trim())
return;
if (!canQuickAdd.value) return;
try {
loading.value = true;
@@ -859,8 +858,8 @@ const canQuickAdd = computed(() => {
if (!name || !value) return false;
// 验证变量名格式
if (!/^[a-zA-Z][a-zA-Z0-9_]*$/.test(name)) return false;
// Reuse the shared variable-name contract used by workspaces and storage.
if (!isValidVariableName(name)) return false;
// 检查是否与预定义变量重名(标准化)
if (
@@ -0,0 +1,71 @@
import { describe, expect, it } from 'vitest'
import { mount } from '@vue/test-utils'
import VariableEditor from '../../../src/components/variable/VariableEditor.vue'
const stubs = {
NModal: {
name: 'NModal',
template: '<div><slot /><slot name="footer" /></div>',
props: ['show', 'title', 'preset', 'style', 'maskClosable']
},
NForm: {
name: 'NForm',
template: '<form><slot /></form>',
props: ['model', 'rules']
},
NFormItem: {
name: 'NFormItem',
template: '<div><slot /><slot name="feedback" /></div>',
props: ['path', 'label', 'required']
},
NInput: {
name: 'NInput',
template: '<input :value="value" @input="$emit(\'update:value\', ($event.target as HTMLInputElement).value)" />',
props: ['value', 'placeholder', 'disabled', 'clearable', 'type', 'autosize'],
emits: ['update:value']
},
NButton: {
name: 'NButton',
template: '<button :disabled="disabled" @click="$emit(\'click\')"><slot /></button>',
props: ['type', 'disabled', 'loading'],
emits: ['click']
},
NSpace: {
name: 'NSpace',
template: '<div><slot /></div>',
props: ['justify']
},
NText: {
name: 'NText',
template: '<span><slot /></span>',
props: ['depth']
}
}
const mountEditor = () => mount(VariableEditor, {
props: {
show: true,
existingNames: []
},
global: {
stubs
}
})
describe('VariableEditor', () => {
it('uses the shared variable-name contract so Chinese names are valid', () => {
const wrapper = mountEditor()
const nameRules = (wrapper.vm as any).formRules.name
expect(nameRules[1].validator({}, '中文变量')).toBeUndefined()
expect(nameRules[1].validator({}, 'foo bar')).toBeInstanceOf(Error)
expect(nameRules[1].validator({}, '1name')).toBeInstanceOf(Error)
})
it('keeps predefined variable names blocked', () => {
const wrapper = mountEditor()
const nameRules = (wrapper.vm as any).formRules.name
expect(nameRules[2].validator({}, 'originalPrompt')).toBeInstanceOf(Error)
})
})
@@ -0,0 +1,71 @@
import { describe, expect, it, vi } from 'vitest'
import { shallowMount } from '@vue/test-utils'
import { ref } from 'vue'
import VariableManagerModal from '../../../src/components/variable/VariableManagerModal.vue'
const createVariableManager = () => {
const addVariable = vi.fn()
const variables: Record<string, string> = {}
const manager = {
resolveAllVariables: () => ({ ...variables }),
getVariableSource: (name: string) => name === 'originalPrompt' ? 'predefined' : 'custom',
isPredefinedVariable: (name: string) => name === 'originalPrompt'
}
return {
addVariable,
hook: {
variableManager: ref(manager),
addVariable: (name: string, value: string) => {
variables[name] = value
addVariable(name, value)
}
}
}
}
describe('VariableManagerModal', () => {
it('allows Chinese variable names in quick add', async () => {
const { hook, addVariable } = createVariableManager()
const wrapper = shallowMount(VariableManagerModal, {
props: {
visible: true,
variableManager: hook as any
}
})
;(wrapper.vm as any).quickAddForm = {
name: '中文变量',
value: '测试值'
}
expect((wrapper.vm as any).canQuickAdd).toBe(true)
await (wrapper.vm as any).quickAddVariable()
expect(addVariable).toHaveBeenCalledWith('中文变量', '测试值')
expect(wrapper.emitted('variableChange')?.[0]).toEqual(['中文变量', '测试值', 'add'])
})
it('blocks invalid and predefined names in quick add', () => {
const { hook } = createVariableManager()
const wrapper = shallowMount(VariableManagerModal, {
props: {
visible: true,
variableManager: hook as any
}
})
;(wrapper.vm as any).quickAddForm = {
name: 'foo bar',
value: 'value'
}
expect((wrapper.vm as any).canQuickAdd).toBe(false)
;(wrapper.vm as any).quickAddForm = {
name: 'originalPrompt',
value: 'value'
}
expect((wrapper.vm as any).canQuickAdd).toBe(false)
})
})