fix(ui): allow unicode variable names in manager
This commit is contained in:
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)
|
||||
})
|
||||
})
|
||||
Reference in new issue
Block a user