优化模型配置和节回显

This commit is contained in:
2026-06-05 17:33:18 +08:00
parent 56e1517743
commit ee8ba0a5d9
3 changed files with 99 additions and 125 deletions

View File

@@ -141,6 +141,15 @@ These rules capture long-term repository preferences confirmed by the user and s
- When code changes appear not to take effect, check in this order: compile/HMR status, render conditions, API data, then style overrides. - When code changes appear not to take effect, check in this order: compile/HMR status, render conditions, API data, then style overrides.
- Turn repeated low-level mistakes into explicit workflow checks and follow those checks in future edits. - Turn repeated low-level mistakes into explicit workflow checks and follow those checks in future edits.
### Environment and Tool Selection Rules
- Before running environment-dependent commands, first follow the known runtime context already provided by the IDE, such as OS and default shell.
- On Windows projects with PowerShell as the active shell, prefer PowerShell-native commands and scripts by default.
- Do not assume Python, bash, sed, awk, or other non-default tooling is installed locally unless the user or repository explicitly indicates that they are available.
- For reading and editing repository files, prefer dedicated file tools first; use shell-based text replacement only when file tools are not suitable for the change.
- When a replacement fails once, do not keep retrying the same method blindly. Re-read the exact target content and switch to a smaller or more reliable edit strategy that matches the current environment.
- Avoid trial-and-error probing for basic tooling when the available environment is already known from context.
### Componentization and Structure Rules ### Componentization and Structure Rules
- If a view block has an independent responsibility, prefer extracting it into a component instead of keeping it inside a large page file. - If a view block has an independent responsibility, prefer extracting it into a component instead of keeping it inside a large page file.

View File

@@ -118,15 +118,22 @@ interface ModelItem {
apiKey?: string; apiKey?: string;
isPrivate?: number; isPrivate?: number;
isChatModel?: number; isChatModel?: number;
headMsg?: string; headMsg?: string | Record<string, string>;
operatorName?: string; operatorName?: string;
responseBody?: Record<string, unknown>; responseBody?: Record<string, unknown>;
responseTokenField?: string;
tokenConfig?: Record<string, unknown> | string; tokenConfig?: Record<string, unknown> | string;
extendMapping?: Record<string, unknown> | string; extendMapping?: Record<string, unknown> | string;
queryConfig?: Record<string, unknown>; queryConfig?: Record<string, unknown>;
streamConfig?: Record<string, unknown>;
form?: ModelFormEntry[] | Record<string, unknown>; form?: ModelFormEntry[] | Record<string, unknown>;
requestMapping?: Record<string, unknown>; requestMapping?: Record<string, unknown>;
requiredFields?: string[];
firstFrame?: string;
lastFrame?: string;
responseMapping?: Record<string, unknown>; responseMapping?: Record<string, unknown>;
callMode?: number;
isAsync?: number;
maxConcurrency?: number; maxConcurrency?: number;
queueLimit?: number; queueLimit?: number;
timeoutSeconds?: number; timeoutSeconds?: number;
@@ -271,74 +278,71 @@ const fieldsToUnknownObject = (fields: Array<{ key: string; value: string }>) =>
return obj; return obj;
}; };
const flattenNestedObject = (obj: Record<string, unknown>, prefix = ''): Array<{ key: string; value: string }> => { const parseFormEntries = (raw: unknown): ModelFormEntry[] => {
const rows: Array<{ key: string; value: string }> = []; if (Array.isArray(raw)) {
Object.entries(obj || {}).forEach(([k, v]) => { return (raw as ModelFormEntry[])
const fk = prefix ? `${prefix}.${k}` : k; .filter((item) => item && item.key !== undefined)
if (v && typeof v === 'object' && !Array.isArray(v)) { .map((item) => ({
rows.push(...flattenNestedObject(v as Record<string, unknown>, fk)); key: String(item.key ?? '').trim(),
return; value: String(item.value ?? ''),
}))
.filter((item) => item.key !== '');
} }
rows.push({ key: fk, value: String(v ?? '') });
if (raw && typeof raw === 'object') {
return Object.entries(raw as Record<string, unknown>).map(([key, value]) => {
let nextValue = value;
if (nextValue && typeof nextValue === 'object' && !Array.isArray(nextValue) && 'value' in nextValue) {
nextValue = (nextValue as { value: unknown }).value;
}
return {
key: String(key).trim(),
value: String(nextValue ?? ''),
};
}); });
return rows; }
return [];
}; };
const nestFieldsToObject = (fields: Array<{ key: string; value: string }>) => { const normalizeQueryConfig = (raw: unknown): Record<string, unknown> => {
const root: Record<string, unknown> = {}; if (raw && typeof raw === 'object' && !Array.isArray(raw)) {
fields.forEach((f) => { return raw as Record<string, unknown>;
const path = String(f.key || '').trim();
if (!path) return;
const parts = path
.split('.')
.map((p) => p.trim())
.filter(Boolean);
if (!parts.length) return;
let cur: Record<string, unknown> = root;
parts.forEach((part, idx) => {
if (idx === parts.length - 1) {
cur[part] = String(f.value ?? '');
return;
} }
if (!cur[part] || typeof cur[part] !== 'object' || Array.isArray(cur[part])) { return {};
cur[part] = {};
}
cur = cur[part] as Record<string, unknown>;
});
});
return root;
}; };
const buildQueryConfigFromRaw = (rawQc: Record<string, unknown> | null): Record<string, unknown> => { const parseHeadMsgRecord = (raw: ModelItem['headMsg']) => {
if (!rawQc) return { responseType: 'sync', callbackUrl: '' }; const headMsgRecord: Record<string, string> = {};
const rt = String(rawQc.responseType || 'sync'); if (typeof raw === 'string') {
if (rt === 'callback') return { responseType: 'callback', callbackUrl: String(rawQc.callbackUrl || '') }; try {
if (rt === 'pull') { const parsed = JSON.parse(raw);
const hFields = Object.entries((rawQc.headers as Record<string, unknown>) || {}).map(([k, v]) => ({ key: k, value: String(v ?? '') })); if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
const bFields = flattenNestedObject((rawQc.body as Record<string, unknown>) || {}); Object.entries(parsed).forEach(([k, v]) => {
return { headMsgRecord[k] = String(v);
responseType: 'pull', });
callbackUrl: '', return headMsgRecord;
method: String(rawQc.method || 'GET'),
url: String(rawQc.url || ''),
headers: fieldsToUnknownObject(hFields),
body: nestFieldsToObject(bFields),
response: ((rawQc.response as unknown[]) || [])
.map((item) => {
if (typeof item === 'string') {
return { value: item, isTokenField: false, isMainBody: false };
} }
const row = item as Record<string, unknown>; } catch {
return { const pairs = raw.split(',');
value: String(row.value ?? ''), pairs.forEach((pair) => {
isTokenField: Boolean(row.isTokenField), const idx = pair.indexOf(':');
isMainBody: Boolean(row.isMainBody), if (idx === -1) return;
}; const key = pair.slice(0, idx).trim();
}) const value = pair.slice(idx + 1).trim();
.filter((row) => row.value !== ''), if (key) {
}; headMsgRecord[key] = value;
} }
return { responseType: 'sync', callbackUrl: '' }; });
return headMsgRecord;
}
}
if (raw && typeof raw === 'object' && !Array.isArray(raw)) {
Object.entries(raw).forEach(([k, v]) => {
headMsgRecord[k] = String(v);
});
}
return headMsgRecord;
}; };
const fetchModelList = async () => { const fetchModelList = async () => {
@@ -396,43 +400,7 @@ const handleCreatePrivateModel = async () => {
creatingModel.value = true; creatingModel.value = true;
const builtInModel = builtInModelToClone.value; const builtInModel = builtInModelToClone.value;
const formList: ModelFormEntry[] = Array.isArray(builtInModel.form) const formList = parseFormEntries(builtInModel.form);
? (builtInModel.form as ModelFormEntry[])
: Object.entries((builtInModel.form as Record<string, unknown>) || {}).map(([key, value]) => ({
key: String(key),
value: String(value ?? ''),
}));
// Parse headMsg to Record<string, string> - it might be stored as string or already as object
let headMsgRecord: Record<string, string> = {};
if (builtInModel.headMsg && typeof builtInModel.headMsg === 'string') {
// Try to parse as JSON first (new format stored as string)
try {
const parsed = JSON.parse(builtInModel.headMsg);
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
Object.entries(parsed).forEach(([k, v]) => {
headMsgRecord[k] = String(v);
});
}
} catch {
// If JSON parse fails, parse as old format "key1:value1,key2:value2"
const pairs = builtInModel.headMsg.split(',');
pairs.forEach((pair) => {
const idx = pair.indexOf(':');
if (idx === -1) return;
const key = pair.slice(0, idx).trim();
const value = pair.slice(idx + 1).trim();
if (key) {
headMsgRecord[key] = value;
}
});
}
} else if (builtInModel.headMsg && typeof builtInModel.headMsg === 'object' && !Array.isArray(builtInModel.headMsg)) {
// Already an object
Object.entries(builtInModel.headMsg).forEach(([k, v]) => {
headMsgRecord[k] = String(v);
});
}
const createParams: CreateModelParams = { const createParams: CreateModelParams = {
modelName: apiKeyForm.modelName, modelName: apiKeyForm.modelName,
@@ -440,7 +408,7 @@ const handleCreatePrivateModel = async () => {
operatorName: builtInModel.operatorName || '', operatorName: builtInModel.operatorName || '',
baseUrl: builtInModel.baseUrl, baseUrl: builtInModel.baseUrl,
httpMethod: builtInModel.httpMethod || 'POST', httpMethod: builtInModel.httpMethod || 'POST',
headMsg: headMsgRecord, headMsg: parseHeadMsgRecord(builtInModel.headMsg),
isPrivate: builtInModel.isPrivate ?? 1, isPrivate: builtInModel.isPrivate ?? 1,
enabled: builtInModel.enabled ?? 1, enabled: builtInModel.enabled ?? 1,
isChatModel: builtInModel.isChatModel || 0, isChatModel: builtInModel.isChatModel || 0,
@@ -466,14 +434,13 @@ const handleCreatePrivateModel = async () => {
tokenConfig: fieldsToUnknownObject( tokenConfig: fieldsToUnknownObject(
Object.entries(parseJsonObjectField(builtInModel.tokenConfig)).map(([k, v]) => ({ key: k, value: String(v ?? '') })) Object.entries(parseJsonObjectField(builtInModel.tokenConfig)).map(([k, v]) => ({ key: k, value: String(v ?? '') }))
), ),
queryConfig: buildQueryConfigFromRaw( queryConfig: normalizeQueryConfig(builtInModel.queryConfig),
builtInModel.queryConfig && typeof builtInModel.queryConfig === 'object' && !Array.isArray(builtInModel.queryConfig) responseTokenField: String(builtInModel.responseTokenField || ''),
? (builtInModel.queryConfig as Record<string, unknown>)
: null
),
streamConfig: streamConfig:
builtInModel.streamConfig && typeof builtInModel.streamConfig === 'object' && !Array.isArray(builtInModel.streamConfig) Number(builtInModel.callMode ?? builtInModel.isAsync ?? 0) === 2
? builtInModel.streamConfig && typeof builtInModel.streamConfig === 'object' && !Array.isArray(builtInModel.streamConfig)
? (builtInModel.streamConfig as Record<string, unknown>) ? (builtInModel.streamConfig as Record<string, unknown>)
: {}
: undefined, : undefined,
}; };

View File

@@ -1116,6 +1116,11 @@ const availableParentParams = computed(() => {
const allParentIds = findAllParentNodes(selectedElement.value.id); const allParentIds = findAllParentNodes(selectedElement.value.id);
const params: Array<{ label: string; value: string }> = []; const params: Array<{ label: string; value: string }> = [];
const pushParentParam = (label: string, value: string) => {
if (!label || !value) return;
if (params.some((item) => item.value === value)) return;
params.push({ label, value });
};
// 遍历所有上级节点 // 遍历所有上级节点
allParentIds.forEach((parentId) => { allParentIds.forEach((parentId) => {
@@ -1133,15 +1138,14 @@ const availableParentParams = computed(() => {
// 如果是判断节点,跳过不添加其字段 // 如果是判断节点,跳过不添加其字段
if (isJudge) return; if (isJudge) return;
pushParentParam(`${parentNodeName}.输出结果`, `\${${parentId}.nodeOutputResult}`);
const modelOutputFields = Array.isArray(parentProps.modelOutputFields) ? parentProps.modelOutputFields : []; const modelOutputFields = Array.isArray(parentProps.modelOutputFields) ? parentProps.modelOutputFields : [];
if (modelOutputFields.length > 0) { if (modelOutputFields.length > 0) {
modelOutputFields.forEach((field: any) => { modelOutputFields.forEach((field: any) => {
const fieldName = String(field || '').trim(); const fieldName = String(field || '').trim();
if (!fieldName) return; if (!fieldName) return;
params.push({ pushParentParam(`${parentNodeName}.${fieldName}`, `\${${parentId}.${fieldName}}`);
label: `${parentNodeName}.${fieldName}`,
value: `\${${parentId}.${fieldName}}`,
});
}); });
} }
@@ -1163,10 +1167,7 @@ const availableParentParams = computed(() => {
if (responseValue && typeof responseValue === 'object' && !Array.isArray(responseValue)) { if (responseValue && typeof responseValue === 'object' && !Array.isArray(responseValue)) {
Object.keys(responseValue).forEach((key) => { Object.keys(responseValue).forEach((key) => {
if (!key || key.startsWith('_temp_')) return; if (!key || key.startsWith('_temp_')) return;
params.push({ pushParentParam(`${parentNodeName}.${key}`, `\${${parentId}.${key}}`);
label: `${parentNodeName}.${key}`,
value: `\${${parentId}.${key}}`,
});
}); });
} }
return; return;
@@ -1174,10 +1175,7 @@ const availableParentParams = computed(() => {
parentProps.formConfig.forEach((field: any) => { parentProps.formConfig.forEach((field: any) => {
if (field.label) { if (field.label) {
params.push({ pushParentParam(`${parentNodeName}.${field.label}`, `\${${parentId}.${field.label}}`);
label: `${parentNodeName}.${field.label}`,
value: `\${${parentId}.${field.label}}`,
});
} }
}); });
} }