Add agent-facing wizard preview
This commit is contained in:
@@ -1446,6 +1446,44 @@
|
|||||||
}
|
}
|
||||||
.upstream-preview-change:hover { border-color: var(--text-secondary); color: var(--text-primary); }
|
.upstream-preview-change:hover { border-color: var(--text-secondary); color: var(--text-primary); }
|
||||||
|
|
||||||
|
.agent-preview-summary {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1fr) minmax(0, 1.3fr);
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-preview-label {
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: .04em;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-preview-value,
|
||||||
|
.agent-preview-description {
|
||||||
|
background: var(--surface-subtle);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 8px;
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.5;
|
||||||
|
min-height: 38px;
|
||||||
|
padding: 9px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-preview-description {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
min-height: 54px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 760px) {
|
||||||
|
.agent-preview-summary {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* ── Register new upstream inline form ── */
|
/* ── Register new upstream inline form ── */
|
||||||
.upstream-new-form {
|
.upstream-new-form {
|
||||||
background: var(--bg-canvas);
|
background: var(--bg-canvas);
|
||||||
|
|||||||
@@ -103,6 +103,57 @@ tls:
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="config-card" id="agent-facing-preview-card" style="margin-bottom: 20px;">
|
||||||
|
<div class="config-card-header">
|
||||||
|
<div class="config-card-header-icon">
|
||||||
|
<svg width="13" height="13" viewBox="0 0 16 16" fill="none" stroke="var(--text-secondary)" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path d="M3 4h10M3 8h10M3 12h6"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="config-card-title" data-i18n="wizard.step5.agent_preview_title">Как инструмент увидит MCP клиент</div>
|
||||||
|
<div class="config-card-subtitle" data-i18n="wizard.step5.agent_preview_subtitle">Проверьте имя, описание, входную схему и примеры до публикации.</div>
|
||||||
|
</div>
|
||||||
|
<button id="wizard-copy-agent-preview" class="btn-ghost-sm" type="button" data-i18n="wizard.step5.copy_agent_preview">Копировать схему</button>
|
||||||
|
</div>
|
||||||
|
<div class="config-card-body" style="gap: 16px;">
|
||||||
|
<div class="agent-preview-summary">
|
||||||
|
<div>
|
||||||
|
<div class="agent-preview-label" data-i18n="wizard.step5.preview_tool_name">Имя инструмента</div>
|
||||||
|
<div class="agent-preview-value input-mono" id="agent-preview-tool-name"></div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="agent-preview-label" data-i18n="wizard.step5.preview_tool_title">Короткое описание</div>
|
||||||
|
<div class="agent-preview-value" id="agent-preview-tool-title"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label" data-i18n="wizard.step5.preview_tool_description">Описание для модели</label>
|
||||||
|
<div class="agent-preview-description" id="agent-preview-tool-description"></div>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label" data-i18n="wizard.step5.preview_input_schema">Input schema</label>
|
||||||
|
<textarea id="agent-preview-input-schema" class="form-textarea code-textarea" rows="10" spellcheck="false" readonly></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label" data-i18n="wizard.step5.preview_tool_call">Пример вызова tools/call</label>
|
||||||
|
<textarea id="agent-preview-tool-call" class="form-textarea code-textarea" rows="10" spellcheck="false" readonly></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label" data-i18n="wizard.step5.preview_success_response">Пример успешного ответа</label>
|
||||||
|
<textarea id="agent-preview-success-response" class="form-textarea code-textarea" rows="8" spellcheck="false" readonly></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label" data-i18n="wizard.step5.preview_error_response">Пример ошибки</label>
|
||||||
|
<textarea id="agent-preview-error-response" class="form-textarea code-textarea" rows="8" spellcheck="false" readonly></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="config-card" style="margin-bottom: 20px;">
|
<div class="config-card" style="margin-bottom: 20px;">
|
||||||
<div class="config-card-header">
|
<div class="config-card-header">
|
||||||
<div class="config-card-header-icon">
|
<div class="config-card-header-icon">
|
||||||
|
|||||||
@@ -566,6 +566,16 @@ var TRANSLATIONS = {
|
|||||||
'wizard.step5.draft_actions': 'Action result',
|
'wizard.step5.draft_actions': 'Action result',
|
||||||
'wizard.step5.live_save_title': 'Saving before action',
|
'wizard.step5.live_save_title': 'Saving before action',
|
||||||
'wizard.step5.live_save_body': 'Crank saves the current settings before running checks or publishing.',
|
'wizard.step5.live_save_body': 'Crank saves the current settings before running checks or publishing.',
|
||||||
|
'wizard.step5.agent_preview_title': 'How the MCP client sees this tool',
|
||||||
|
'wizard.step5.agent_preview_subtitle': 'Check the name, description, input schema and examples before publication.',
|
||||||
|
'wizard.step5.copy_agent_preview': 'Copy schema',
|
||||||
|
'wizard.step5.preview_tool_name': 'Tool name',
|
||||||
|
'wizard.step5.preview_tool_title': 'Short description',
|
||||||
|
'wizard.step5.preview_tool_description': 'Description for the model',
|
||||||
|
'wizard.step5.preview_input_schema': 'Input schema',
|
||||||
|
'wizard.step5.preview_tool_call': 'Example tools/call',
|
||||||
|
'wizard.step5.preview_success_response': 'Successful response example',
|
||||||
|
'wizard.step5.preview_error_response': 'Error example',
|
||||||
'wizard.step5.samples_title': 'Examples and automatic setup',
|
'wizard.step5.samples_title': 'Examples and automatic setup',
|
||||||
'wizard.step5.samples_subtitle': 'Save example input and output data, then let Crank rebuild schemas and field bindings from them.',
|
'wizard.step5.samples_subtitle': 'Save example input and output data, then let Crank rebuild schemas and field bindings from them.',
|
||||||
'wizard.step5.input_sample': 'Input example',
|
'wizard.step5.input_sample': 'Input example',
|
||||||
@@ -652,6 +662,8 @@ var TRANSLATIONS = {
|
|||||||
'wizard.test.no_response_body': 'Run a test first, then copy the response into the output example.',
|
'wizard.test.no_response_body': 'Run a test first, then copy the response into the output example.',
|
||||||
'wizard.test.response_copied': 'Response copied',
|
'wizard.test.response_copied': 'Response copied',
|
||||||
'wizard.test.response_copied_body': 'The latest test response was copied into the output example.',
|
'wizard.test.response_copied_body': 'The latest test response was copied into the output example.',
|
||||||
|
'wizard.agent_preview.copied': 'Schema copied',
|
||||||
|
'wizard.agent_preview.copied_body': 'The MCP tool schema was copied to the clipboard.',
|
||||||
'wizard.boolean.yes': 'yes',
|
'wizard.boolean.yes': 'yes',
|
||||||
'wizard.boolean.no': 'no',
|
'wizard.boolean.no': 'no',
|
||||||
'wizard.yaml.exported': 'YAML exported',
|
'wizard.yaml.exported': 'YAML exported',
|
||||||
@@ -1352,6 +1364,16 @@ var TRANSLATIONS = {
|
|||||||
'wizard.step5.draft_actions': 'Результат действия',
|
'wizard.step5.draft_actions': 'Результат действия',
|
||||||
'wizard.step5.live_save_title': 'Сохранение перед действием',
|
'wizard.step5.live_save_title': 'Сохранение перед действием',
|
||||||
'wizard.step5.live_save_body': 'Перед проверкой или публикацией Crank сохраняет текущие настройки.',
|
'wizard.step5.live_save_body': 'Перед проверкой или публикацией Crank сохраняет текущие настройки.',
|
||||||
|
'wizard.step5.agent_preview_title': 'Как инструмент увидит MCP клиент',
|
||||||
|
'wizard.step5.agent_preview_subtitle': 'Проверьте имя, описание, входную схему и примеры до публикации.',
|
||||||
|
'wizard.step5.copy_agent_preview': 'Копировать схему',
|
||||||
|
'wizard.step5.preview_tool_name': 'Имя инструмента',
|
||||||
|
'wizard.step5.preview_tool_title': 'Короткое описание',
|
||||||
|
'wizard.step5.preview_tool_description': 'Описание для модели',
|
||||||
|
'wizard.step5.preview_input_schema': 'Входная схема',
|
||||||
|
'wizard.step5.preview_tool_call': 'Пример вызова tools/call',
|
||||||
|
'wizard.step5.preview_success_response': 'Пример успешного ответа',
|
||||||
|
'wizard.step5.preview_error_response': 'Пример ошибки',
|
||||||
'wizard.step5.samples_title': 'Примеры и автоматическая настройка',
|
'wizard.step5.samples_title': 'Примеры и автоматическая настройка',
|
||||||
'wizard.step5.samples_subtitle': 'Сохраните пример входных и выходных данных, затем Crank сможет пересобрать схемы и связи полей на их основе.',
|
'wizard.step5.samples_subtitle': 'Сохраните пример входных и выходных данных, затем Crank сможет пересобрать схемы и связи полей на их основе.',
|
||||||
'wizard.step5.input_sample': 'Пример входных данных',
|
'wizard.step5.input_sample': 'Пример входных данных',
|
||||||
@@ -1438,6 +1460,8 @@ var TRANSLATIONS = {
|
|||||||
'wizard.test.no_response_body': 'Сначала выполните тест, затем скопируйте ответ в выходной пример.',
|
'wizard.test.no_response_body': 'Сначала выполните тест, затем скопируйте ответ в выходной пример.',
|
||||||
'wizard.test.response_copied': 'Ответ скопирован',
|
'wizard.test.response_copied': 'Ответ скопирован',
|
||||||
'wizard.test.response_copied_body': 'Последний тестовый ответ скопирован в выходной пример.',
|
'wizard.test.response_copied_body': 'Последний тестовый ответ скопирован в выходной пример.',
|
||||||
|
'wizard.agent_preview.copied': 'Схема скопирована',
|
||||||
|
'wizard.agent_preview.copied_body': 'Схема MCP инструмента скопирована в буфер обмена.',
|
||||||
'wizard.boolean.yes': 'да',
|
'wizard.boolean.yes': 'да',
|
||||||
'wizard.boolean.no': 'нет',
|
'wizard.boolean.no': 'нет',
|
||||||
'wizard.yaml.exported': 'YAML экспортирован',
|
'wizard.yaml.exported': 'YAML экспортирован',
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ async function loadOperationForEdit() {
|
|||||||
wizardCurrentOperation = detail;
|
wizardCurrentOperation = detail;
|
||||||
wizardCurrentVersion = draftVersion;
|
wizardCurrentVersion = draftVersion;
|
||||||
prefillWizardFromEdit(detail, draftVersion);
|
prefillWizardFromEdit(detail, draftVersion);
|
||||||
|
renderAgentFacingPreview();
|
||||||
}
|
}
|
||||||
|
|
||||||
function setEditModePresentation() {
|
function setEditModePresentation() {
|
||||||
@@ -98,6 +99,7 @@ function bindWizardLiveActions() {
|
|||||||
bindLiveAction('wizard-generate-draft', tKey('wizard.busy.generate'), generateDraftFromWizard);
|
bindLiveAction('wizard-generate-draft', tKey('wizard.busy.generate'), generateDraftFromWizard);
|
||||||
bindLiveAction('wizard-run-test', tKey('wizard.busy.test'), runWizardTest);
|
bindLiveAction('wizard-run-test', tKey('wizard.busy.test'), runWizardTest);
|
||||||
bindClick('wizard-copy-test-response', copyTestResponseToOutputSample);
|
bindClick('wizard-copy-test-response', copyTestResponseToOutputSample);
|
||||||
|
bindClick('wizard-copy-agent-preview', copyAgentFacingPreview);
|
||||||
bindLiveAction('wizard-export-yaml', tKey('wizard.busy.export_yaml'), exportWizardYaml);
|
bindLiveAction('wizard-export-yaml', tKey('wizard.busy.export_yaml'), exportWizardYaml);
|
||||||
bindLiveAction('wizard-import-yaml', tKey('wizard.busy.import_yaml'), importWizardYaml);
|
bindLiveAction('wizard-import-yaml', tKey('wizard.busy.import_yaml'), importWizardYaml);
|
||||||
bindLiveAction('wizard-publish-operation', tKey('wizard.busy.publish'), publishWizardOperation);
|
bindLiveAction('wizard-publish-operation', tKey('wizard.busy.publish'), publishWizardOperation);
|
||||||
@@ -108,6 +110,7 @@ function bindWizardLiveActions() {
|
|||||||
|
|
||||||
var yamlInput = document.getElementById('wizard-import-yaml-file');
|
var yamlInput = document.getElementById('wizard-import-yaml-file');
|
||||||
if (yamlInput) yamlInput.addEventListener('change', handleImportYamlFileSelection);
|
if (yamlInput) yamlInput.addEventListener('change', handleImportYamlFileSelection);
|
||||||
|
bindAgentFacingPreview();
|
||||||
}
|
}
|
||||||
|
|
||||||
function bindClick(id, handler) {
|
function bindClick(id, handler) {
|
||||||
@@ -302,6 +305,99 @@ function setTextareaValue(id, value) {
|
|||||||
element.value = safeStringify(value);
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function exportWizardYaml() {
|
async function exportWizardYaml() {
|
||||||
await persistCurrentDraft(true);
|
await persistCurrentDraft(true);
|
||||||
var yaml = await window.CrankApi.exportOperation(wizardWorkspaceId, wizardEditId, {
|
var yaml = await window.CrankApi.exportOperation(wizardWorkspaceId, wizardEditId, {
|
||||||
@@ -386,6 +482,7 @@ async function generateDraftFromWizard() {
|
|||||||
setValue('tool-output-schema', JSON.stringify(crankSchemaToJsonSchema(generated.output_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-input-mapping', mappingSetToEditorValue(generated.input_mapping, 'input', wizardProtocol));
|
||||||
setValue('tool-output-mapping', mappingSetToEditorValue(generated.output_mapping, 'output', wizardProtocol));
|
setValue('tool-output-mapping', mappingSetToEditorValue(generated.output_mapping, 'output', wizardProtocol));
|
||||||
|
renderAgentFacingPreview();
|
||||||
showWizardLiveStatus(tKey('wizard.sample.generated'), tKey('wizard.sample.generated_body'));
|
showWizardLiveStatus(tKey('wizard.sample.generated'), tKey('wizard.sample.generated_body'));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -413,6 +510,7 @@ function copyTestResponseToOutputSample() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setTextareaValue('wizard-output-sample', wizardTestResponsePreview);
|
setTextareaValue('wizard-output-sample', wizardTestResponsePreview);
|
||||||
|
renderAgentFacingPreview();
|
||||||
showWizardLiveStatus(tKey('wizard.test.response_copied'), tKey('wizard.test.response_copied_body'));
|
showWizardLiveStatus(tKey('wizard.test.response_copied'), tKey('wizard.test.response_copied_body'));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -421,4 +519,5 @@ window.CrankWizardLive = {
|
|||||||
loadOperationForEdit: loadOperationForEdit,
|
loadOperationForEdit: loadOperationForEdit,
|
||||||
bindWizardLiveActions: bindWizardLiveActions,
|
bindWizardLiveActions: bindWizardLiveActions,
|
||||||
updateWizardProtocolVisibility: updateWizardProtocolVisibility,
|
updateWizardProtocolVisibility: updateWizardProtocolVisibility,
|
||||||
|
renderAgentFacingPreview: renderAgentFacingPreview,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -339,6 +339,171 @@ test('wizard edit mode hydrates fields from operation version snapshot', async (
|
|||||||
await expect(page.locator('#wizard-test-input')).not.toHaveValue(/Ada/);
|
await expect(page.locator('#wizard-test-input')).not.toHaveValue(/Ada/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('wizard shows agent-facing MCP preview from current draft fields', async ({ page }) => {
|
||||||
|
await login(page);
|
||||||
|
const workspace = await getCurrentWorkspace(page);
|
||||||
|
const operationId = 'frankfurter_monthly_rates';
|
||||||
|
const version = 7;
|
||||||
|
|
||||||
|
await page.route(
|
||||||
|
`/api/admin/workspaces/${encodeURIComponent(workspace.id)}/operations/${operationId}`,
|
||||||
|
async (route) => {
|
||||||
|
await route.fulfill({
|
||||||
|
contentType: 'application/json',
|
||||||
|
body: JSON.stringify({
|
||||||
|
id: operationId,
|
||||||
|
workspace_id: workspace.id,
|
||||||
|
name: operationId,
|
||||||
|
display_name: 'Frankfurter monthly rates',
|
||||||
|
category: 'finance',
|
||||||
|
protocol: 'rest',
|
||||||
|
security_level: 'standard',
|
||||||
|
status: 'draft',
|
||||||
|
current_draft_version: version,
|
||||||
|
latest_published_version: null,
|
||||||
|
created_at: '2026-06-19T00:00:00Z',
|
||||||
|
updated_at: '2026-06-19T00:00:00Z',
|
||||||
|
published_at: null,
|
||||||
|
draft_version_ref: { version, status: 'draft' },
|
||||||
|
published_version_ref: null,
|
||||||
|
agent_refs: [],
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
await page.route(
|
||||||
|
`/api/admin/workspaces/${encodeURIComponent(workspace.id)}/operations/${operationId}/versions/${version}`,
|
||||||
|
async (route) => {
|
||||||
|
await route.fulfill({
|
||||||
|
contentType: 'application/json',
|
||||||
|
body: JSON.stringify({
|
||||||
|
operation_id: operationId,
|
||||||
|
workspace_id: workspace.id,
|
||||||
|
version,
|
||||||
|
status: 'draft',
|
||||||
|
change_note: null,
|
||||||
|
created_at: '2026-06-19T00:00:00Z',
|
||||||
|
created_by: null,
|
||||||
|
snapshot: {
|
||||||
|
id: operationId,
|
||||||
|
name: operationId,
|
||||||
|
display_name: 'Frankfurter monthly rates',
|
||||||
|
category: 'finance',
|
||||||
|
protocol: 'rest',
|
||||||
|
security_level: 'standard',
|
||||||
|
status: 'draft',
|
||||||
|
version,
|
||||||
|
target: {
|
||||||
|
kind: 'rest',
|
||||||
|
base_url: 'https://api.frankfurter.app',
|
||||||
|
method: 'GET',
|
||||||
|
path_template: '/2024-01..2024-03',
|
||||||
|
static_headers: { Accept: 'application/json' },
|
||||||
|
},
|
||||||
|
input_schema: {
|
||||||
|
type: 'object',
|
||||||
|
description: null,
|
||||||
|
required: false,
|
||||||
|
nullable: false,
|
||||||
|
default_value: null,
|
||||||
|
fields: {
|
||||||
|
from: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Base currency',
|
||||||
|
required: true,
|
||||||
|
nullable: false,
|
||||||
|
default_value: 'USD',
|
||||||
|
fields: {},
|
||||||
|
items: null,
|
||||||
|
enum_values: [],
|
||||||
|
variants: [],
|
||||||
|
},
|
||||||
|
to: {
|
||||||
|
type: 'string',
|
||||||
|
description: 'Quote currency',
|
||||||
|
required: false,
|
||||||
|
nullable: false,
|
||||||
|
default_value: 'EUR',
|
||||||
|
fields: {},
|
||||||
|
items: null,
|
||||||
|
enum_values: [],
|
||||||
|
variants: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
items: null,
|
||||||
|
enum_values: [],
|
||||||
|
variants: [],
|
||||||
|
},
|
||||||
|
output_schema: {
|
||||||
|
type: 'object',
|
||||||
|
description: null,
|
||||||
|
required: false,
|
||||||
|
nullable: false,
|
||||||
|
default_value: null,
|
||||||
|
fields: {
|
||||||
|
rates: {
|
||||||
|
type: 'object',
|
||||||
|
description: 'Rates by date',
|
||||||
|
required: true,
|
||||||
|
nullable: false,
|
||||||
|
default_value: null,
|
||||||
|
fields: {},
|
||||||
|
items: null,
|
||||||
|
enum_values: [],
|
||||||
|
variants: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
items: null,
|
||||||
|
enum_values: [],
|
||||||
|
variants: [],
|
||||||
|
},
|
||||||
|
input_mapping: { rules: [] },
|
||||||
|
output_mapping: {
|
||||||
|
rules: [{ source: '$.response.body.rates', target: '$.output.rates' }],
|
||||||
|
},
|
||||||
|
execution_config: {
|
||||||
|
timeout_ms: 9000,
|
||||||
|
retry_policy: null,
|
||||||
|
auth_profile_ref: null,
|
||||||
|
headers: {},
|
||||||
|
protocol_options: null,
|
||||||
|
streaming: null,
|
||||||
|
},
|
||||||
|
tool_description: {
|
||||||
|
title: 'Получить историю курсов за месяц',
|
||||||
|
description: 'Возвращает историю курсов валют Frankfurter за заданный период.',
|
||||||
|
tags: [],
|
||||||
|
examples: [],
|
||||||
|
},
|
||||||
|
wizard_state: {
|
||||||
|
input_sample: { from: 'USD', to: 'EUR' },
|
||||||
|
output_sample: { rates: { '2024-01': { EUR: 0.91 } } },
|
||||||
|
test_input: { from: 'GBP', to: 'USD' },
|
||||||
|
},
|
||||||
|
samples: [],
|
||||||
|
generated_draft: null,
|
||||||
|
config_export: null,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
await page.goto(`/wizard/?mode=edit&operationId=${operationId}`);
|
||||||
|
await page.locator('#btn-continue').click();
|
||||||
|
await page.locator('#btn-continue').click();
|
||||||
|
await page.locator('#btn-continue').click();
|
||||||
|
await page.locator('#btn-continue').click();
|
||||||
|
|
||||||
|
await expect(page.locator('#agent-preview-tool-name')).toHaveText(operationId);
|
||||||
|
await expect(page.locator('#agent-preview-tool-description')).toContainText('Frankfurter');
|
||||||
|
await expect(page.locator('#agent-preview-input-schema')).toHaveValue(/"from"/);
|
||||||
|
await expect(page.locator('#agent-preview-tool-call')).toHaveValue(/"name": "frankfurter_monthly_rates"/);
|
||||||
|
await expect(page.locator('#agent-preview-tool-call')).toHaveValue(/"from": "USD"/);
|
||||||
|
await expect(page.locator('#agent-preview-success-response')).toHaveValue(/0\.91/);
|
||||||
|
});
|
||||||
|
|
||||||
test('wizard edit mode preserves explicit request mapping targets on save', async ({ page }) => {
|
test('wizard edit mode preserves explicit request mapping targets on save', async ({ page }) => {
|
||||||
await login(page);
|
await login(page);
|
||||||
const workspace = await getCurrentWorkspace(page);
|
const workspace = await getCurrentWorkspace(page);
|
||||||
|
|||||||
Reference in New Issue
Block a user