Files
crank/apps/ui/js/wizard-live.js
T
2026-06-21 01:42:45 +00:00

617 lines
21 KiB
JavaScript

function buildToolDescription() {
var snapshot = wizardCurrentVersion && wizardCurrentVersion.snapshot
? wizardCurrentVersion.snapshot
: wizardCurrentVersion;
var existing = snapshot && snapshot.tool_description ? snapshot.tool_description : {};
return {
title: textValue('tool-title') || textValue('tool-display-name') || textValue('tool-name'),
description: textValue('tool-description'),
tags: Array.isArray(existing.tags) ? existing.tags.slice() : [],
examples: Array.isArray(existing.examples) ? existing.examples.slice() : [],
};
}
function buildWizardState() {
return {
input_sample: parseStructuredText(textValue('wizard-input-sample')),
output_sample: parseStructuredText(textValue('wizard-output-sample')),
test_input: parseStructuredText(textValue('wizard-test-input')),
};
}
function collectWizardPayload() {
var name = textValue('tool-name');
if (!name) throw new Error(tKey('wizard.error.tool_name'));
var inputSchemaValue = parseStructuredText(textValue('tool-input-schema'));
var outputSchemaValue = parseStructuredText(textValue('tool-output-schema'));
var inputMappingValue = parseStructuredText(textValue('tool-input-mapping'));
var outputMappingValue = parseStructuredText(textValue('tool-output-mapping'));
return {
name: name,
display_name: textValue('tool-display-name') || name,
category: 'general',
protocol: wizardProtocol,
target: buildTarget(),
input_schema: convertJsonSchemaToCrankSchema(inputSchemaValue, []),
output_schema: convertJsonSchemaToCrankSchema(outputSchemaValue, []),
input_mapping: buildMappingSet(inputMappingValue, 'input'),
output_mapping: buildMappingSet(outputMappingValue, 'output'),
execution_config: parseExecutionConfig(textValue('tool-exec-config')),
tool_description: buildToolDescription(),
wizard_state: buildWizardState(),
};
}
async function saveOperation(stayOnPage) {
try {
await persistCurrentDraft(stayOnPage);
} catch (error) {
if (window.CrankUi) {
window.CrankUi.error(error.message || tKey('wizard.error.save'), tKey('wizard.error.save_title'));
}
}
}
async function loadOperationForEdit() {
if (!wizardWorkspaceId || !wizardEditId) return;
var detail = await window.CrankApi.getOperation(wizardWorkspaceId, wizardEditId);
wizardProtocol = detail.protocol || 'rest';
await loadWizardPanels([3]);
var draftVersion = await window.CrankApi.getOperationVersion(
wizardWorkspaceId,
wizardEditId,
detail.draft_version_ref.version
);
wizardCurrentOperation = detail;
wizardCurrentVersion = draftVersion;
prefillWizardFromEdit(detail, draftVersion);
renderAgentFacingPreview();
}
function setEditModePresentation() {
var progressLabel = document.querySelector('.progress-label');
if (progressLabel) progressLabel.textContent = tKey('wizard.progress.edit');
renderSidebarBrand('edit');
}
function selectProtocol(protocol) {
wizardProtocol = protocol || 'rest';
document.querySelectorAll('.protocol-card').forEach(function(card) {
var proto = card.dataset.protocol || 'rest';
var selected = proto === wizardProtocol;
card.classList.toggle('selected', selected);
card.setAttribute('aria-checked', selected ? 'true' : 'false');
});
var s3name = document.getElementById('sidebar-step-3-name');
if (s3name) {
var step3Labels = window.CrankWizardState.step3Labels();
s3name.textContent = step3Labels[wizardProtocol] || tKey('wizard.step3.rest.label');
}
updateWizardProtocolVisibility();
}
function bindWizardLiveActions() {
bindLiveAction('wizard-upload-input-sample', tKey('wizard.busy.input_sample'), uploadInputSampleFromWizard);
bindLiveAction('wizard-upload-output-sample', tKey('wizard.busy.output_sample'), uploadOutputSampleFromWizard);
bindLiveAction('wizard-generate-draft', tKey('wizard.busy.generate'), generateDraftFromWizard);
bindLiveAction('wizard-run-test', tKey('wizard.busy.test'), runWizardTest);
bindLiveAction('wizard-run-quality', tKey('wizard.busy.quality'), analyzeWizardQuality);
bindClick('wizard-copy-test-response', copyTestResponseToOutputSample);
bindClick('wizard-copy-agent-preview', copyAgentFacingPreview);
bindLiveAction('wizard-export-yaml', tKey('wizard.busy.export_yaml'), exportWizardYaml);
bindLiveAction('wizard-import-yaml', tKey('wizard.busy.import_yaml'), importWizardYaml);
bindLiveAction('wizard-publish-operation', tKey('wizard.busy.publish'), publishWizardOperation);
bindClick('wizard-import-yaml-file-trigger', function() {
var input = document.getElementById('wizard-import-yaml-file');
if (input) input.click();
});
var yamlInput = document.getElementById('wizard-import-yaml-file');
if (yamlInput) yamlInput.addEventListener('change', handleImportYamlFileSelection);
bindAgentFacingPreview();
}
function bindClick(id, handler) {
var element = document.getElementById(id);
if (!element) return;
element.addEventListener('click', function(event) {
event.preventDefault();
handler();
});
}
function bindLiveAction(id, busyLabel, handler) {
var element = document.getElementById(id);
if (!element) return;
element.addEventListener('click', function(event) {
event.preventDefault();
runWizardLiveAction(element, busyLabel, handler);
});
}
async function runWizardLiveAction(button, busyLabel, handler) {
if (!button || button.dataset.busy === 'true') {
return;
}
var originalLabel = button.textContent;
button.dataset.busy = 'true';
button.disabled = true;
button.classList.add('is-busy');
button.textContent = busyLabel;
try {
await handler();
} catch (error) {
showWizardLiveStatus(
tKey('wizard.live.failed_title'),
error && error.message ? error.message : tKey('wizard.live.failed_body'),
true
);
if (window.CrankUi) {
window.CrankUi.error(
error && error.message ? error.message : tKey('wizard.live.failed_body'),
tKey('wizard.live.failed_title')
);
}
} finally {
button.dataset.busy = 'false';
button.disabled = false;
button.classList.remove('is-busy');
button.textContent = originalLabel;
}
}
function updateWizardProtocolVisibility() {
if (typeof window.renderEditionCapabilityHints === 'function'
&& window.CrankWizardState
&& typeof window.CrankWizardState.currentEditionCapabilities === 'function') {
window.renderEditionCapabilityHints(window.CrankWizardState.currentEditionCapabilities());
}
}
function showWizardLiveStatus(title, text, isError) {
var root = document.getElementById('wizard-live-status');
var titleEl = document.getElementById('wizard-live-status-title');
var textEl = document.getElementById('wizard-live-status-text');
if (!root || !titleEl || !textEl) return;
titleEl.textContent = title;
textEl.textContent = text;
root.hidden = false;
root.classList.toggle('is-error', !!isError);
root.classList.toggle('is-success', !isError);
}
function showPendingImportGuidance() {
var raw = sessionStorage.getItem('crank_import_guidance');
if (!raw) return;
sessionStorage.removeItem('crank_import_guidance');
try {
var warnings = JSON.parse(raw);
if (!Array.isArray(warnings) || warnings.length === 0) return;
showWizardLiveStatus(
tKey('wizard.yaml.imported_with_warnings'),
warnings.map(function(warning) { return '• ' + warning; }).join('\n')
);
} catch (_) {
// Ignore invalid stale session data.
}
}
function currentDraftVersion() {
if (wizardCurrentOperation && wizardCurrentOperation.draft_version_ref) {
return wizardCurrentOperation.draft_version_ref.version;
}
if (wizardCurrentOperation && wizardCurrentOperation.current_draft_version) {
return wizardCurrentOperation.current_draft_version;
}
if (wizardCurrentVersion && wizardCurrentVersion.version) {
return wizardCurrentVersion.version;
}
return 1;
}
function updateOperationPayload(payload) {
return {
display_name: payload.display_name,
category: payload.category,
target: payload.target,
input_schema: payload.input_schema,
output_schema: payload.output_schema,
input_mapping: payload.input_mapping,
output_mapping: payload.output_mapping,
execution_config: payload.execution_config,
tool_description: payload.tool_description,
wizard_state: payload.wizard_state,
};
}
async function refreshCurrentOperationState() {
if (!wizardWorkspaceId || !wizardEditId) return null;
var detail = await window.CrankApi.getOperation(wizardWorkspaceId, wizardEditId);
var versionNumber = detail.draft_version_ref
? detail.draft_version_ref.version
: detail.current_draft_version;
var versionDocument = await window.CrankApi.getOperationVersion(
wizardWorkspaceId,
wizardEditId,
versionNumber
);
wizardCurrentOperation = detail;
wizardCurrentVersion = versionDocument;
updateWizardProtocolVisibility();
return {
detail: detail,
version: versionDocument,
};
}
async function persistCurrentDraft(stayOnPage) {
if (!wizardWorkspaceId) {
throw new Error(tKey('wizard.error.no_workspace'));
}
var payload = collectWizardPayload();
var response;
var createdNow = false;
if (wizardMode === 'edit' && wizardEditId) {
response = await window.CrankApi.updateOperation(
wizardWorkspaceId,
wizardEditId,
updateOperationPayload(payload)
);
} else {
response = await window.CrankApi.createOperation(wizardWorkspaceId, payload);
createdNow = true;
wizardEditId = response.operation_id;
wizardMode = 'edit';
history.replaceState(
{},
'',
window.location.pathname + '?mode=edit&operationId=' + encodeURIComponent(wizardEditId)
);
setEditModePresentation();
_doGoToStep(currentStep);
var toolNameInput = document.getElementById('tool-name');
if (toolNameInput) toolNameInput.disabled = true;
}
await refreshCurrentOperationState();
if (stayOnPage) {
var saveDraftBtn = document.querySelector('.btn-save-draft');
if (saveDraftBtn) {
var label = saveDraftBtn.textContent;
saveDraftBtn.textContent = tKey('wizard.save.saved');
setTimeout(function() {
saveDraftBtn.textContent = label;
}, 1400);
}
}
showWizardLiveStatus(
createdNow ? tKey('wizard.save.created') : tKey('wizard.save.updated'),
tKey('wizard.save.body')
);
return response;
}
function safeStringify(value) {
if (value === null || value === undefined) return '';
if (typeof value === 'string') return value;
return JSON.stringify(value, null, 2);
}
function downloadTextFile(fileName, content, mimeType) {
var blob = new Blob([content], { type: mimeType || 'text/plain;charset=utf-8' });
var url = URL.createObjectURL(blob);
var link = document.createElement('a');
link.href = url;
link.download = fileName;
link.click();
URL.revokeObjectURL(url);
}
function setTextareaValue(id, value) {
var element = document.getElementById(id);
if (!element) return;
element.value = safeStringify(value);
}
function setTextContent(id, value) {
var element = document.getElementById(id);
if (!element) return;
element.textContent = value || '';
}
function structuredTextOrFallback(id, fallback) {
try {
var value = parseStructuredText(textValue(id));
if (value === null || value === undefined || value === '') return fallback;
return value;
} catch (_error) {
return fallback;
}
}
function buildAgentFacingPreview() {
var inputSchema = structuredTextOrFallback('tool-input-schema', {});
var inputSample = structuredTextOrFallback('wizard-input-sample', {});
var outputSample = structuredTextOrFallback('wizard-output-sample', {});
var toolName = textValue('tool-name') || 'unnamed_tool';
var title = textValue('tool-title') || textValue('tool-display-name') || toolName;
var description = textValue('tool-description');
return {
manifest: {
name: toolName,
title: title,
description: description,
inputSchema: inputSchema,
},
toolCall: {
name: toolName,
arguments: inputSample,
},
successResponse: outputSample,
errorResponse: {
isError: true,
content: [
{
type: 'text',
text: 'Инструмент вернул ошибку. Проверьте входные параметры или состояние внешнего API.',
},
],
},
};
}
function renderAgentFacingPreview() {
var root = document.getElementById('agent-facing-preview-card');
if (!root) return;
var preview = buildAgentFacingPreview();
setTextContent('agent-preview-tool-name', preview.manifest.name);
setTextContent('agent-preview-tool-title', preview.manifest.title);
setTextContent('agent-preview-tool-description', preview.manifest.description);
setTextareaValue('agent-preview-input-schema', preview.manifest.inputSchema);
setTextareaValue('agent-preview-tool-call', preview.toolCall);
setTextareaValue('agent-preview-success-response', preview.successResponse);
setTextareaValue('agent-preview-error-response', preview.errorResponse);
}
function bindAgentFacingPreview() {
[
'tool-name',
'tool-title',
'tool-display-name',
'tool-description',
'tool-input-schema',
'wizard-input-sample',
'wizard-output-sample',
].forEach(function(id) {
var element = document.getElementById(id);
if (!element || element.dataset.agentPreviewBound === 'true') return;
element.dataset.agentPreviewBound = 'true';
element.addEventListener('input', renderAgentFacingPreview);
element.addEventListener('change', renderAgentFacingPreview);
});
renderAgentFacingPreview();
}
function copyAgentFacingPreview() {
var preview = buildAgentFacingPreview();
var content = JSON.stringify(preview.manifest, null, 2);
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(content).then(function() {
showWizardLiveStatus(tKey('wizard.agent_preview.copied'), tKey('wizard.agent_preview.copied_body'));
}).catch(function() {
setTextareaValue('agent-preview-input-schema', preview.manifest.inputSchema);
});
}
}
function severityLabel(severity) {
if (severity === 'error') return tKey('wizard.quality.severity_error');
if (severity === 'warning') return tKey('wizard.quality.severity_warning');
return tKey('wizard.quality.severity_info');
}
function renderQualityFindings(report) {
var empty = document.getElementById('wizard-quality-empty');
var list = document.getElementById('wizard-quality-findings');
if (!empty || !list) return;
list.replaceChildren();
var findings = report && Array.isArray(report.findings) ? report.findings : [];
if (!findings.length) {
empty.textContent = tKey('wizard.quality.no_findings');
empty.hidden = false;
list.hidden = true;
return;
}
empty.hidden = true;
list.hidden = false;
findings.forEach(function(finding) {
var item = document.createElement('div');
item.className = 'quality-finding ' + (finding.severity || 'info');
item.dataset.code = finding.code || '';
var title = document.createElement('div');
title.className = 'quality-finding-title';
title.textContent = severityLabel(finding.severity) + ': ' + (finding.message || finding.code || '');
item.appendChild(title);
if (finding.suggested_action) {
var action = document.createElement('div');
action.className = 'quality-finding-action';
action.textContent = finding.suggested_action;
item.appendChild(action);
}
if (finding.field_path) {
var path = document.createElement('div');
path.className = 'quality-finding-path';
path.textContent = finding.field_path;
item.appendChild(path);
}
list.appendChild(item);
});
}
async function analyzeWizardQuality() {
if (!wizardWorkspaceId) {
throw new Error(tKey('wizard.error.no_workspace'));
}
var report = await window.CrankApi.analyzeOperationQuality(
wizardWorkspaceId,
collectWizardPayload()
);
renderQualityFindings(report);
showWizardLiveStatus(
report.blocking ? tKey('wizard.quality.blocked_title') : tKey('wizard.quality.checked_title'),
report.blocking ? tKey('wizard.quality.blocked_body') : tKey('wizard.quality.checked_body'),
!!report.blocking
);
return report;
}
async function exportWizardYaml() {
await persistCurrentDraft(true);
var yaml = await window.CrankApi.exportOperation(wizardWorkspaceId, wizardEditId, {
mode: 'portable',
version: currentDraftVersion(),
});
downloadTextFile((textValue('tool-name') || 'operation') + '.yaml', yaml, 'application/yaml');
showWizardLiveStatus(tKey('wizard.yaml.exported'), tKey('wizard.yaml.exported_body'));
}
async function importWizardYaml() {
if (!wizardWorkspaceId) {
throw new Error(tKey('wizard.error.no_workspace'));
}
var yamlDocument = textValue('wizard-import-yaml-text');
if (!yamlDocument) {
showWizardLiveStatus(tKey('wizard.yaml.none'), tKey('wizard.yaml.none_body'), true);
return;
}
var imported = await window.CrankApi.importOperation(wizardWorkspaceId, yamlDocument, 'upsert');
if (Array.isArray(imported.warnings) && imported.warnings.length > 0) {
sessionStorage.setItem('crank_import_guidance', JSON.stringify(imported.warnings));
}
showWizardLiveStatus(tKey('wizard.yaml.imported'), tKey('wizard.yaml.imported_body'));
window.location.href = window.location.pathname + '?mode=edit&operationId=' + encodeURIComponent(imported.operation_id);
}
async function publishWizardOperation() {
var report = await analyzeWizardQuality();
if (report.blocking) {
throw new Error(tKey('wizard.quality.blocking_error'));
}
await persistCurrentDraft(true);
var published = await window.CrankApi.publishOperation(
wizardWorkspaceId,
wizardEditId,
currentDraftVersion()
);
await refreshCurrentOperationState();
showWizardLiveStatus(
tKey('wizard.publish.done'),
tfKey('wizard.publish.done_body', { version: published.published_version })
);
}
function handleImportYamlFileSelection(event) {
var file = event.target.files && event.target.files[0];
if (!file) return;
var reader = new FileReader();
reader.onload = function(loadEvent) {
setValue('wizard-import-yaml-text', loadEvent.target.result || '');
showWizardLiveStatus(tKey('wizard.yaml.loaded'), tKey('wizard.yaml.loaded_body'));
};
reader.readAsText(file);
}
function describeWizardTestResult(result) {
return {
title: result.ok ? tKey('wizard.test.completed') : tKey('wizard.test.failed'),
body: result.ok ? tKey('wizard.test.completed_body') : tKey('wizard.test.failed_body'),
isError: !result.ok,
};
}
async function uploadInputSampleFromWizard() {
await persistCurrentDraft(true);
await window.CrankApi.uploadInputSample(
wizardWorkspaceId,
wizardEditId,
parseStructuredText(textValue('wizard-input-sample'))
);
showWizardLiveStatus(tKey('wizard.sample.input_saved'), tKey('wizard.sample.input_saved_body'));
}
async function uploadOutputSampleFromWizard() {
await persistCurrentDraft(true);
await window.CrankApi.uploadOutputSample(
wizardWorkspaceId,
wizardEditId,
parseStructuredText(textValue('wizard-output-sample'))
);
showWizardLiveStatus(tKey('wizard.sample.output_saved'), tKey('wizard.sample.output_saved_body'));
}
async function generateDraftFromWizard() {
await persistCurrentDraft(true);
var generated = await window.CrankApi.generateDraft(wizardWorkspaceId, wizardEditId, {});
setValue('tool-input-schema', JSON.stringify(crankSchemaToJsonSchema(generated.input_schema), null, 2));
setValue('tool-output-schema', JSON.stringify(crankSchemaToJsonSchema(generated.output_schema), null, 2));
setValue('tool-input-mapping', mappingSetToEditorValue(generated.input_mapping, 'input', wizardProtocol));
setValue('tool-output-mapping', mappingSetToEditorValue(generated.output_mapping, 'output', wizardProtocol));
renderAgentFacingPreview();
showWizardLiveStatus(tKey('wizard.sample.generated'), tKey('wizard.sample.generated_body'));
}
async function runWizardTest() {
await persistCurrentDraft(true);
var result = await window.CrankApi.runOperationTest(wizardWorkspaceId, wizardEditId, {
version: currentDraftVersion(),
input: parseStructuredText(textValue('wizard-test-input')),
});
wizardTestResponsePreview = result.response_preview;
setTextareaValue('wizard-test-request-preview', result.request_preview);
setTextareaValue('wizard-test-response-preview', result.response_preview);
setTextareaValue('wizard-test-errors', result.errors && result.errors.length ? result.errors : []);
var status = describeWizardTestResult(result);
showWizardLiveStatus(
status.title,
status.body,
status.isError
);
}
function copyTestResponseToOutputSample() {
if (!wizardTestResponsePreview) {
showWizardLiveStatus(tKey('wizard.test.no_response'), tKey('wizard.test.no_response_body'), true);
return;
}
setTextareaValue('wizard-output-sample', wizardTestResponsePreview);
renderAgentFacingPreview();
showWizardLiveStatus(tKey('wizard.test.response_copied'), tKey('wizard.test.response_copied_body'));
}
window.CrankWizardLive = {
saveOperation: saveOperation,
loadOperationForEdit: loadOperationForEdit,
showPendingImportGuidance: showPendingImportGuidance,
bindWizardLiveActions: bindWizardLiveActions,
updateWizardProtocolVisibility: updateWizardProtocolVisibility,
renderAgentFacingPreview: renderAgentFacingPreview,
};