diff --git a/CLAUDE.md b/CLAUDE.md index 19e6043..97b6276 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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. - 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 - If a view block has an independent responsibility, prefer extracting it into a component instead of keeping it inside a large page file. diff --git a/src/components/model/ModelSelector.vue b/src/components/model/ModelSelector.vue index 68dc0df..a0a0807 100644 --- a/src/components/model/ModelSelector.vue +++ b/src/components/model/ModelSelector.vue @@ -118,15 +118,22 @@ interface ModelItem { apiKey?: string; isPrivate?: number; isChatModel?: number; - headMsg?: string; + headMsg?: string | Record; operatorName?: string; responseBody?: Record; + responseTokenField?: string; tokenConfig?: Record | string; extendMapping?: Record | string; queryConfig?: Record; + streamConfig?: Record; form?: ModelFormEntry[] | Record; requestMapping?: Record; + requiredFields?: string[]; + firstFrame?: string; + lastFrame?: string; responseMapping?: Record; + callMode?: number; + isAsync?: number; maxConcurrency?: number; queueLimit?: number; timeoutSeconds?: number; @@ -271,74 +278,71 @@ const fieldsToUnknownObject = (fields: Array<{ key: string; value: string }>) => return obj; }; -const flattenNestedObject = (obj: Record, prefix = ''): Array<{ key: string; value: string }> => { - const rows: Array<{ key: string; value: string }> = []; - Object.entries(obj || {}).forEach(([k, v]) => { - const fk = prefix ? `${prefix}.${k}` : k; - if (v && typeof v === 'object' && !Array.isArray(v)) { - rows.push(...flattenNestedObject(v as Record, fk)); - return; - } - rows.push({ key: fk, value: String(v ?? '') }); - }); - return rows; -}; - -const nestFieldsToObject = (fields: Array<{ key: string; value: string }>) => { - const root: Record = {}; - fields.forEach((f) => { - 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 = 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])) { - cur[part] = {}; - } - cur = cur[part] as Record; - }); - }); - return root; -}; - -const buildQueryConfigFromRaw = (rawQc: Record | null): Record => { - if (!rawQc) return { responseType: 'sync', callbackUrl: '' }; - const rt = String(rawQc.responseType || 'sync'); - if (rt === 'callback') return { responseType: 'callback', callbackUrl: String(rawQc.callbackUrl || '') }; - if (rt === 'pull') { - const hFields = Object.entries((rawQc.headers as Record) || {}).map(([k, v]) => ({ key: k, value: String(v ?? '') })); - const bFields = flattenNestedObject((rawQc.body as Record) || {}); - return { - responseType: 'pull', - callbackUrl: '', - 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; - return { - value: String(row.value ?? ''), - isTokenField: Boolean(row.isTokenField), - isMainBody: Boolean(row.isMainBody), - }; - }) - .filter((row) => row.value !== ''), - }; +const parseFormEntries = (raw: unknown): ModelFormEntry[] => { + if (Array.isArray(raw)) { + return (raw as ModelFormEntry[]) + .filter((item) => item && item.key !== undefined) + .map((item) => ({ + key: String(item.key ?? '').trim(), + value: String(item.value ?? ''), + })) + .filter((item) => item.key !== ''); } - return { responseType: 'sync', callbackUrl: '' }; + + if (raw && typeof raw === 'object') { + return Object.entries(raw as Record).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 []; +}; + +const normalizeQueryConfig = (raw: unknown): Record => { + if (raw && typeof raw === 'object' && !Array.isArray(raw)) { + return raw as Record; + } + return {}; +}; + +const parseHeadMsgRecord = (raw: ModelItem['headMsg']) => { + const headMsgRecord: Record = {}; + if (typeof raw === 'string') { + try { + const parsed = JSON.parse(raw); + if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) { + Object.entries(parsed).forEach(([k, v]) => { + headMsgRecord[k] = String(v); + }); + return headMsgRecord; + } + } catch { + const pairs = raw.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; + } + }); + 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 () => { @@ -396,43 +400,7 @@ const handleCreatePrivateModel = async () => { creatingModel.value = true; const builtInModel = builtInModelToClone.value; - const formList: ModelFormEntry[] = Array.isArray(builtInModel.form) - ? (builtInModel.form as ModelFormEntry[]) - : Object.entries((builtInModel.form as Record) || {}).map(([key, value]) => ({ - key: String(key), - value: String(value ?? ''), - })); - - // Parse headMsg to Record - it might be stored as string or already as object - let headMsgRecord: Record = {}; - 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 formList = parseFormEntries(builtInModel.form); const createParams: CreateModelParams = { modelName: apiKeyForm.modelName, @@ -440,7 +408,7 @@ const handleCreatePrivateModel = async () => { operatorName: builtInModel.operatorName || '', baseUrl: builtInModel.baseUrl, httpMethod: builtInModel.httpMethod || 'POST', - headMsg: headMsgRecord, + headMsg: parseHeadMsgRecord(builtInModel.headMsg), isPrivate: builtInModel.isPrivate ?? 1, enabled: builtInModel.enabled ?? 1, isChatModel: builtInModel.isChatModel || 0, @@ -466,14 +434,13 @@ const handleCreatePrivateModel = async () => { tokenConfig: fieldsToUnknownObject( Object.entries(parseJsonObjectField(builtInModel.tokenConfig)).map(([k, v]) => ({ key: k, value: String(v ?? '') })) ), - queryConfig: buildQueryConfigFromRaw( - builtInModel.queryConfig && typeof builtInModel.queryConfig === 'object' && !Array.isArray(builtInModel.queryConfig) - ? (builtInModel.queryConfig as Record) - : null - ), + queryConfig: normalizeQueryConfig(builtInModel.queryConfig), + responseTokenField: String(builtInModel.responseTokenField || ''), streamConfig: - builtInModel.streamConfig && typeof builtInModel.streamConfig === 'object' && !Array.isArray(builtInModel.streamConfig) - ? (builtInModel.streamConfig as Record) + Number(builtInModel.callMode ?? builtInModel.isAsync ?? 0) === 2 + ? builtInModel.streamConfig && typeof builtInModel.streamConfig === 'object' && !Array.isArray(builtInModel.streamConfig) + ? (builtInModel.streamConfig as Record) + : {} : undefined, }; diff --git a/src/views/settings/creation/index.vue b/src/views/settings/creation/index.vue index 1469266..80656c9 100644 --- a/src/views/settings/creation/index.vue +++ b/src/views/settings/creation/index.vue @@ -1116,6 +1116,11 @@ const availableParentParams = computed(() => { const allParentIds = findAllParentNodes(selectedElement.value.id); 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) => { @@ -1133,15 +1138,14 @@ const availableParentParams = computed(() => { // 如果是判断节点,跳过不添加其字段 if (isJudge) return; + pushParentParam(`${parentNodeName}.输出结果`, `\${${parentId}.nodeOutputResult}`); + const modelOutputFields = Array.isArray(parentProps.modelOutputFields) ? parentProps.modelOutputFields : []; if (modelOutputFields.length > 0) { modelOutputFields.forEach((field: any) => { const fieldName = String(field || '').trim(); if (!fieldName) return; - params.push({ - label: `${parentNodeName}.${fieldName}`, - value: `\${${parentId}.${fieldName}}`, - }); + pushParentParam(`${parentNodeName}.${fieldName}`, `\${${parentId}.${fieldName}}`); }); } @@ -1163,10 +1167,7 @@ const availableParentParams = computed(() => { if (responseValue && typeof responseValue === 'object' && !Array.isArray(responseValue)) { Object.keys(responseValue).forEach((key) => { if (!key || key.startsWith('_temp_')) return; - params.push({ - label: `${parentNodeName}.${key}`, - value: `\${${parentId}.${key}}`, - }); + pushParentParam(`${parentNodeName}.${key}`, `\${${parentId}.${key}}`); }); } return; @@ -1174,10 +1175,7 @@ const availableParentParams = computed(() => { parentProps.formConfig.forEach((field: any) => { if (field.label) { - params.push({ - label: `${parentNodeName}.${field.label}`, - value: `\${${parentId}.${field.label}}`, - }); + pushParentParam(`${parentNodeName}.${field.label}`, `\${${parentId}.${field.label}}`); } }); }