Skip to content

Commit 6d8081d

Browse files
borealisalastor-byteulleo
authored andcommitted
fix(models): preserve parameter deletions when switching models
1 parent 8bd2d6e commit 6d8081d

2 files changed

Lines changed: 105 additions & 0 deletions

File tree

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
"""Behavioral guard for model-parameter deletion; requires Node and frontend npm deps."""
2+
3+
import shutil
4+
import subprocess
5+
import unittest
6+
from pathlib import Path
7+
8+
PROJECT_ROOT = Path(__file__).resolve().parents[2]
9+
10+
SCRIPT = r"""
11+
const assert = require('node:assert/strict');
12+
const fs = require('node:fs');
13+
const path = require('node:path');
14+
const frontend = path.join(process.cwd(), 'frontend');
15+
const ts = require(path.join(frontend, 'node_modules/typescript'));
16+
const { ref, reactive, computed } = require(path.join(frontend, 'node_modules/vue'));
17+
const component = fs.readFileSync(path.join(frontend, 'src/views/system/model/ModelForm.vue'), 'utf8');
18+
const script = component.match(/<script[^>]*>([\s\S]*?)<\/script>/)[1];
19+
const ast = ts.createSourceFile('ModelForm.ts', script, ts.ScriptTarget.Latest, true);
20+
const names = new Set([
21+
'modelForm', 'modelRef', 'advancedSetting', 'tempConfigMap', 'curId',
22+
'currentSupplier', 'modelList', 'initForm', 'handleParamsDel', 'supplierChang',
23+
'formatAdvancedSetting', 'baseModelChange', 'getSupplierDomain', 'getModelDefaultArgs',
24+
'submitModel',
25+
]);
26+
const selected = ast.statements.filter(statement => ts.isVariableStatement(statement) &&
27+
statement.declarationList.declarations.some(declaration => names.has(declaration.name.getText(ast))));
28+
assert.equal(selected.length, names.size, 'all tested component declarations must be extracted');
29+
const js = ts.transpileModule(selected.map(statement => statement.getText(ast)).join('\n'), {
30+
compilerOptions: { target: ts.ScriptTarget.ES2020 },
31+
}).outputText;
32+
const suppliers = [
33+
{id: 15, type: 'openai', model_config: {0: {api_domain: 'http://example.test/v1',
34+
common_args: [{key: 'temperature', val: 0.6}], model_options: []}}},
35+
{id: 16, type: 'openai', model_config: {0: {api_domain: 'http://other.test/v1',
36+
common_args: [{key: 'temperature', val: 0.3}], model_options: []}}},
37+
];
38+
let emitted;
39+
const getSupplier = id => suppliers.find(supplier => supplier.id === id);
40+
const api = new Function('ref', 'reactive', 'computed', 'get_supplier', 'base_model_options', 'emits',
41+
js + '\nreturn {modelForm, modelRef, advancedSetting, initForm, handleParamsDel, supplierChang, baseModelChange, submitModel};'
42+
)(ref, reactive, computed, getSupplier, id => getSupplier(id).model_config[0].model_options,
43+
(event, payload) => {assert.equal(event, 'submit'); emitted = payload;});
44+
api.modelRef.value = {clearValidate() {}, validate(callback) {callback(true);}};
45+
const original = {supplier: 15, base_model: 'gpt-4.1', model_type: 0, config_list: [
46+
{id: 1, name: 'Temperature', key: 'temperature', val: 0.8},
47+
{id: 2, name: 'Token limit', key: 'max_tokens', val: 4096},
48+
]};
49+
const untouched = JSON.stringify(original);
50+
const select = (supplierId, model) => {
51+
if (supplierId !== api.modelForm.supplier) api.supplierChang(getSupplier(supplierId));
52+
api.modelForm.base_model = model;
53+
api.baseModelChange(model);
54+
};
55+
const submit = () => {api.submitModel(); return emitted.config_list;};
56+
api.initForm(original);
57+
select(15, 'gpt-4o-mini');
58+
select(16, 'gpt-4.1');
59+
select(15, 'gpt-4.1');
60+
assert.deepEqual(submit(), [
61+
{key: 'temperature', name: 'Temperature', val: 0.8},
62+
{key: 'max_tokens', name: 'Token limit', val: 4096},
63+
], 'switching without deleting preserves model configuration');
64+
const deleteAll = process.argv[1] === 'all';
65+
const deleted = deleteAll ? [...api.advancedSetting.value] : [api.advancedSetting.value[1]];
66+
for (const item of deleted) api.handleParamsDel(item);
67+
const expected = deleteAll ? [] : [{key: 'temperature', name: 'Temperature', val: 0.8}];
68+
assert.deepEqual(submit(), expected, 'deletion updates the emitted payload immediately');
69+
select(15, 'gpt-4o-mini');
70+
assert.deepEqual(submit(), [{key: 'temperature', name: 'temperature', val: 0.6}], 'other model unchanged');
71+
select(16, 'gpt-4.1');
72+
assert.deepEqual(submit(), [{key: 'temperature', name: 'temperature', val: 0.3}], 'other supplier unchanged');
73+
select(15, 'gpt-4.1');
74+
assert.deepEqual(submit(), expected, 'deleted parameters must stay deleted after switching back');
75+
assert.equal(JSON.stringify(original), untouched, 'deleting must not mutate loaded input configuration');
76+
"""
77+
78+
79+
class ModelParameterCacheTestCase(unittest.TestCase):
80+
def check_deletion(self, mode: str) -> None:
81+
if not shutil.which("node"):
82+
self.skipTest("Node is required for the frontend behavior guard")
83+
if not (
84+
PROJECT_ROOT / "frontend/node_modules/typescript/package.json"
85+
).exists():
86+
self.skipTest("Run npm install in frontend before this behavior guard")
87+
result = subprocess.run(
88+
["node", "-e", SCRIPT, mode],
89+
cwd=PROJECT_ROOT,
90+
capture_output=True,
91+
text=True,
92+
timeout=30,
93+
)
94+
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
95+
96+
def test_deleted_parameter_stays_deleted_after_switching_models(self) -> None:
97+
self.check_deletion("one")
98+
99+
def test_deleting_all_parameters_stays_empty_after_switching_models(self) -> None:
100+
self.check_deletion("all")
101+
102+
103+
if __name__ == "__main__":
104+
unittest.main()

‎frontend/src/views/system/model/ModelForm.vue‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ const handleParamsCreate = () => {
8181
8282
const handleParamsDel = (item: any) => {
8383
advancedSetting.value = advancedSetting.value.filter((ele) => ele.id !== item.id)
84+
tempConfigMap.set(`${modelForm.supplier}-${modelForm.base_model}`, [...advancedSetting.value])
8485
}
8586
const currentPage = ref(1)
8687
const advancedSettingPagination = computed(() => {

0 commit comments

Comments
 (0)