Files
crank/apps/ui/js/wizard-model.js
T
2026-06-24 10:45:09 +00:00

507 lines
18 KiB
JavaScript

function inferSchemaKind(typeName) {
if (typeName === 'object') return 'object';
if (typeName === 'array') return 'array';
if (typeName === 'string') return 'string';
if (typeName === 'integer') return 'integer';
if (typeName === 'number') return 'number';
if (typeName === 'boolean') return 'boolean';
if (typeName === 'null') return 'null';
return 'string';
}
function convertJsonSchemaToCrankSchema(node, requiredNames) {
var schemaNode = node || {};
var typeName = Array.isArray(schemaNode.type)
? schemaNode.type.find(function(value) { return value !== 'null'; }) || 'string'
: schemaNode.type || (schemaNode.properties ? 'object' : 'string');
var nullable = Array.isArray(schemaNode.type) && schemaNode.type.indexOf('null') >= 0;
var kind = schemaNode.enum ? 'enum' : inferSchemaKind(typeName);
var schema = {
type: kind,
description: schemaNode.description || null,
required: false,
nullable: nullable,
default_value: Object.prototype.hasOwnProperty.call(schemaNode, 'default') ? schemaNode.default : null,
fields: {},
items: null,
enum_values: schemaNode.enum || [],
variants: [],
};
if (kind === 'object') {
var requiredSet = new Set(schemaNode.required || requiredNames || []);
Object.keys(schemaNode.properties || {}).forEach(function(fieldName) {
var field = convertJsonSchemaToCrankSchema(schemaNode.properties[fieldName], []);
field.required = requiredSet.has(fieldName);
schema.fields[fieldName] = field;
});
}
if (kind === 'array' && schemaNode.items) {
schema.items = convertJsonSchemaToCrankSchema(schemaNode.items, []);
}
return schema;
}
function mappingRootForProtocol(protocol) {
return '$.request.body';
}
var REQUEST_MAPPING_TARGET_PREFIXES = [
'path',
'query',
'headers',
'body',
];
function normalizeInputSource(path) {
if (!path) return '$.mcp';
if (path === '$') return '$.mcp';
if (path.indexOf('$.mcp') === 0) return path;
if (path.indexOf('$.input') === 0) return '$.mcp' + path.slice('$.input'.length);
return '$.mcp.' + path.replace(/^\$\./, '');
}
function normalizeInputTarget(target) {
if (!target) return mappingRootForProtocol(wizardProtocol);
if (target.indexOf('$.request.') === 0) return target;
if (target.indexOf('request.') === 0) return '$.' + target;
var normalized = target.replace(/^\$\./, '');
var prefix = normalized.split('.')[0];
if (REQUEST_MAPPING_TARGET_PREFIXES.indexOf(prefix) >= 0) {
return '$.request.' + normalized;
}
return mappingRootForProtocol(wizardProtocol) + '.' + normalized;
}
function normalizeOutputSource(path) {
if (!path) return '$.response.body';
if (path.indexOf('$.') === 0) return path;
return '$.' + path.replace(/^\$\./, '');
}
function normalizeOutputTarget(target) {
if (!target) return '$.output';
if (target.indexOf('$.output') === 0) return target;
if (target.indexOf('output') === 0) return '$.' + target;
return '$.output.' + target.replace(/^\$\./, '');
}
function currentOperationSnapshot() {
return wizardCurrentVersion && wizardCurrentVersion.snapshot
? wizardCurrentVersion.snapshot
: wizardCurrentVersion;
}
function existingMappingRuleByTarget(mode, target) {
var snapshot = currentOperationSnapshot();
var mapping = mode === 'input'
? snapshot && snapshot.input_mapping
: snapshot && snapshot.output_mapping;
var rules = mapping && Array.isArray(mapping.rules) ? mapping.rules : [];
return rules.find(function(rule) {
return rule.target === target;
}) || null;
}
function preserveMappingRuleMetadata(rule, existingRule) {
if (!existingRule) {
rule.required = true;
return rule;
}
rule.required = existingRule.required !== undefined ? existingRule.required : true;
['default_value', 'transform', 'condition', 'notes'].forEach(function(field) {
if (existingRule[field] !== undefined && existingRule[field] !== null) {
rule[field] = existingRule[field];
}
});
return rule;
}
function mappingEntrySource(entry) {
if (entry && typeof entry === 'object' && !Array.isArray(entry)) {
return entry.source;
}
return entry;
}
function normalizeTransformKind(value) {
if (!value) return null;
var kind = value && typeof value === 'object' ? value.kind : value;
kind = String(kind || '').trim();
var allowed = [
'identity',
'to_string',
'to_number',
'to_boolean',
'join',
'split',
'wrap_array',
'unwrap_singleton',
];
return allowed.indexOf(kind) >= 0 ? kind : null;
}
function applyMappingEntryMetadata(rule, entry, existingRule) {
preserveMappingRuleMetadata(rule, existingRule);
if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
return rule;
}
if (Object.prototype.hasOwnProperty.call(entry, 'required')) {
rule.required = entry.required !== false;
}
if (Object.prototype.hasOwnProperty.call(entry, 'default_value')) {
rule.default_value = entry.default_value;
}
if (entry.transform !== undefined && entry.transform !== null) {
var transformKind = normalizeTransformKind(entry.transform);
if (transformKind) rule.transform = { kind: transformKind };
}
['condition', 'notes'].forEach(function(field) {
if (entry[field] !== undefined && entry[field] !== null) {
rule[field] = entry[field];
}
});
return rule;
}
function buildMappingSet(rawValue, mode) {
var value = rawValue || {};
var rules = [];
Object.keys(value).forEach(function(target) {
var entry = value[target];
var source = mappingEntrySource(entry);
if (mode === 'input') {
var inputTarget = normalizeInputTarget(target);
rules.push(applyMappingEntryMetadata({
source: normalizeInputSource(source),
target: inputTarget,
}, entry, existingMappingRuleByTarget(mode, inputTarget)));
return;
}
var outputTarget = normalizeOutputTarget(target);
rules.push(applyMappingEntryMetadata({
source: normalizeOutputSource(source),
target: outputTarget,
}, entry, existingMappingRuleByTarget(mode, outputTarget)));
});
return { rules: rules };
}
function parseExecutionConfig(text) {
var value = parseStructuredText(text);
var retry = value.retry || {};
var headers = value.headers && typeof value.headers === 'object' ? value.headers : {};
var selectedUpstream = currentUpstream();
if (!selectedUpstream && wizardMode !== 'edit') {
throw new Error(tKey('wizard.error.save_upstream_first'));
}
var authProfileRef = selectedUpstream && selectedUpstream.authProfileId
? selectedUpstream.authProfileId
: (value.auth && value.auth.profile ? value.auth.profile : null);
var config = {
timeout_ms: Number(value.timeout_ms || 10000),
retry_policy: retry && retry.max_attempts ? { max_attempts: Number(retry.max_attempts) } : null,
auth_profile_ref: authProfileRef,
headers: headers,
protocol_options: null,
streaming: null,
};
return config;
}
function currentUpstream() {
var selected = upstreams.find(function(item) { return item.id === selectedUpstreamId; });
if (selected) return selected;
var customName = textValue('new-upstream-name');
var customUrl = textValue('new-upstream-url');
if (!customUrl) return null;
var authMode = textValue('new-upstream-auth-mode') || 'none';
var authProfileId = authMode === 'existing' ? textValue('new-upstream-auth-profile') : null;
var authProfile = authProfileById(authProfileId);
return {
id: 'custom',
name: customName || 'custom-upstream',
url: customUrl,
staticHeaders: textValue('new-upstream-static-headers') || '{\n}',
authMode: authMode,
authProfileId: authProfileId || null,
authProfileName: authProfile ? authProfile.name : '',
authKind: authProfile ? authProfile.kind : null,
};
}
function joinUrl(baseUrl, path) {
if (!baseUrl) return path || '';
if (!path) return baseUrl;
return baseUrl.replace(/\/+$/, '') + '/' + path.replace(/^\/+/, '');
}
function parseHeaderMap(text) {
if (!text || !text.trim()) return {};
var value = parseStructuredText(text);
return value && typeof value === 'object' ? value : {};
}
function buildTarget() {
var upstream = currentUpstream();
if (!upstream || !upstream.url) {
throw new Error(tKey('wizard.error.select_upstream'));
}
var pathTemplate = textValue('endpoint-path') || '/';
var activeMethod = document.querySelector('.method-card.active');
return {
kind: 'rest',
base_url: upstream.url,
method: activeMethod ? activeMethod.dataset.method : 'POST',
path_template: pathTemplate,
static_headers: parseHeaderMap(upstream.staticHeaders),
};
}
function crankSchemaToJsonSchema(schema) {
var result = {};
var typeName = schema.type === 'enum' ? 'string' : schema.type;
result.type = schema.nullable ? [typeName, 'null'] : typeName;
if (schema.description) result.description = schema.description;
if (schema.default_value !== null && schema.default_value !== undefined) result.default = schema.default_value;
if (schema.enum_values && schema.enum_values.length) result.enum = schema.enum_values.slice();
if (schema.type === 'object') {
result.type = schema.nullable ? ['object', 'null'] : 'object';
result.properties = {};
var required = [];
Object.keys(schema.fields || {}).forEach(function(fieldName) {
var field = schema.fields[fieldName];
result.properties[fieldName] = crankSchemaToJsonSchema(field);
if (field.required) required.push(fieldName);
});
if (required.length) result.required = required;
result.additionalProperties = false;
}
if (schema.type === 'array' && schema.items) {
result.items = crankSchemaToJsonSchema(schema.items);
}
return result;
}
function exampleValueForSchema(schema) {
if (!schema) return {};
if (schema.default_value !== null && schema.default_value !== undefined) {
return schema.default_value;
}
if (schema.enum_values && schema.enum_values.length) {
return schema.enum_values[0];
}
if (schema.type === 'object') {
var object = {};
Object.keys(schema.fields || {}).forEach(function(fieldName) {
object[fieldName] = exampleValueForSchema(schema.fields[fieldName]);
});
return object;
}
if (schema.type === 'array') {
return [exampleValueForSchema(schema.items)];
}
if (schema.type === 'integer') return 1;
if (schema.type === 'number') return 1.23;
if (schema.type === 'boolean') return true;
if (schema.type === 'null') return null;
return 'string';
}
function firstToolExampleValue(toolDescription, fieldName) {
var examples = toolDescription && Array.isArray(toolDescription.examples)
? toolDescription.examples
: [];
if (!examples.length) return null;
var value = examples[0] && examples[0][fieldName];
return value === undefined ? null : value;
}
function prefillWizardSamples(snapshot) {
var state = snapshot.wizard_state || {};
var inputExample = firstToolExampleValue(snapshot.tool_description, 'input');
var inputSample = state.input_sample !== undefined && state.input_sample !== null
? state.input_sample
: inputExample !== null ? inputExample : exampleValueForSchema(snapshot.input_schema);
var outputSample = state.output_sample !== undefined && state.output_sample !== null
? state.output_sample
: exampleValueForSchema(snapshot.output_schema);
var testInput = state.test_input !== undefined && state.test_input !== null
? state.test_input
: inputSample;
setValue('wizard-input-sample', JSON.stringify(inputSample, null, 2));
setValue('wizard-test-input', JSON.stringify(testInput, null, 2));
setValue('wizard-output-sample', JSON.stringify(outputSample, null, 2));
}
function mappingSetToEditorValue(mappingSet, mode, protocol) {
var rules = mappingSet && mappingSet.rules ? mappingSet.rules : [];
var object = {};
rules.forEach(function(rule) {
if (mode === 'input') {
var key = rule.target.indexOf(mappingRootForProtocol(protocol) + '.') === 0
? rule.target.replace(mappingRootForProtocol(protocol) + '.', '')
: rule.target.replace('$.request.', '');
var inputSource = rule.source.replace('$.mcp.', '$.input.');
var inputEntry = { source: inputSource };
var hasMetadata = false;
if (rule.required === false) {
inputEntry.required = false;
hasMetadata = true;
}
if (rule.default_value !== undefined && rule.default_value !== null) {
inputEntry.default_value = rule.default_value;
hasMetadata = true;
}
if (rule.transform && rule.transform.kind) {
inputEntry.transform = rule.transform.kind;
hasMetadata = true;
}
['condition', 'notes'].forEach(function(field) {
if (rule[field] !== undefined && rule[field] !== null) {
inputEntry[field] = rule[field];
hasMetadata = true;
}
});
object[key] = hasMetadata ? inputEntry : inputSource;
return;
}
var outputKey = rule.target.replace('$.output.', '');
var outputEntry = { source: rule.source };
var outputHasMetadata = false;
if (rule.required === false) {
outputEntry.required = false;
outputHasMetadata = true;
}
if (rule.default_value !== undefined && rule.default_value !== null) {
outputEntry.default_value = rule.default_value;
outputHasMetadata = true;
}
if (rule.transform && rule.transform.kind) {
outputEntry.transform = rule.transform.kind;
outputHasMetadata = true;
}
['condition', 'notes'].forEach(function(field) {
if (rule[field] !== undefined && rule[field] !== null) {
outputEntry[field] = rule[field];
outputHasMetadata = true;
}
});
object[outputKey] = outputHasMetadata ? outputEntry : rule.source;
});
return window.jsyaml ? window.jsyaml.dump(object, { lineWidth: -1 }) : JSON.stringify(object, null, 2);
}
function executionConfigToEditorValue(config) {
var value = {
timeout_ms: config.timeout_ms,
};
if (config.retry_policy) {
value.retry = { max_attempts: config.retry_policy.max_attempts };
}
if (config.auth_profile_ref) {
value.auth = { profile: config.auth_profile_ref };
}
if (config.headers && Object.keys(config.headers).length) {
value.headers = config.headers;
}
return window.jsyaml ? window.jsyaml.dump(value, { lineWidth: -1 }) : JSON.stringify(value, null, 2);
}
function setApprovalPolicyFromSnapshot(config) {
if (!window.CrankWizardLive || typeof window.CrankWizardLive.setApprovalPolicyEditor !== 'function') {
return;
}
window.CrankWizardLive.setApprovalPolicyEditor(config && config.approval_policy ? config.approval_policy : null);
}
function operationSnapshot(versionDocument) {
if (!versionDocument) return {};
return versionDocument.snapshot || versionDocument;
}
function prefillUpstream(baseUrl, headers, authProfileRef) {
var known = upstreams.find(function(item) {
return item.url === baseUrl && (item.authProfileId || null) === (authProfileRef || null);
});
if (known) {
pickUpstream(known.id);
return;
}
selectedUpstreamId = null;
var trigger = document.getElementById('upstream-new-trigger');
var form = document.getElementById('upstream-new-form');
if (trigger) trigger.classList.add('active');
if (form) form.hidden = false;
setValue('new-upstream-name', 'imported-upstream');
setValue('new-upstream-url', baseUrl);
setValue('new-upstream-static-headers', headers && Object.keys(headers).length ? JSON.stringify(headers, null, 2) : '{\n}');
setValue('new-upstream-auth-mode', authProfileRef ? 'existing' : 'none');
setValue('new-upstream-auth-profile', authProfileRef || '');
updateUpstreamAuthUi();
var valueEl = document.getElementById('upstream-combobox-value');
if (valueEl) {
valueEl.innerHTML = '';
valueEl.appendChild(buildUpstreamTriggerContent('imported-upstream', baseUrl));
}
}
function prefillWizardFromEdit(detail, versionDocument) {
if (!detail || !versionDocument) return;
var snapshot = operationSnapshot(versionDocument);
setEditModePresentation();
selectProtocol(snapshot.protocol || detail.protocol);
var target = snapshot.target || {};
if (target.kind === 'rest') {
prefillUpstream(target.base_url, target.static_headers, snapshot.execution_config ? snapshot.execution_config.auth_profile_ref : null);
setValue('endpoint-path', target.path_template);
document.querySelectorAll('.method-card').forEach(function(card) {
card.classList.toggle('active', card.dataset.method === target.method);
});
}
setValue('tool-name', snapshot.name || detail.name);
setValue('tool-display-name', snapshot.display_name || detail.display_name);
setValue('tool-title', snapshot.tool_description ? snapshot.tool_description.title : (snapshot.display_name || detail.display_name));
setValue('tool-description', snapshot.tool_description ? snapshot.tool_description.description : '');
setValue('tool-input-schema', JSON.stringify(crankSchemaToJsonSchema(snapshot.input_schema), null, 2));
setValue('tool-output-schema', JSON.stringify(crankSchemaToJsonSchema(snapshot.output_schema), null, 2));
setValue('tool-input-mapping', mappingSetToEditorValue(snapshot.input_mapping, 'input', snapshot.protocol || detail.protocol));
setValue('tool-output-mapping', mappingSetToEditorValue(snapshot.output_mapping, 'output', snapshot.protocol || detail.protocol));
setValue('tool-exec-config', executionConfigToEditorValue(snapshot.execution_config || {}));
setApprovalPolicyFromSnapshot(snapshot.execution_config || {});
prefillWizardSamples(snapshot);
if (window.CrankWizardMapping && typeof window.CrankWizardMapping.renderFromEditors === 'function') {
window.CrankWizardMapping.renderFromEditors();
}
var toolNameInput = document.getElementById('tool-name');
if (toolNameInput) toolNameInput.disabled = true;
updateWizardProtocolVisibility();
}