Clarify request mapping UI
CI / Rust Checks (push) Successful in 5m37s
CI / UI Checks (push) Successful in 5s
CI / Deployment Manifests (push) Successful in 3s
CI / Frontend E2E (push) Successful in 3m28s
CI / Deploy (push) Successful in 1m31s

This commit is contained in:
github-ops
2026-06-25 04:59:12 +00:00
parent 209b3e1485
commit 9ba2aa3f38
6 changed files with 86 additions and 43 deletions
+40 -1
View File
@@ -202,6 +202,7 @@
display: grid;
grid-template-columns:
minmax(130px, 1fr)
24px
minmax(105px, 0.65fr)
minmax(130px, 1fr)
minmax(120px, 0.8fr)
@@ -216,7 +217,39 @@
}
.response-mapping-row {
grid-template-columns: minmax(160px, 1fr) minmax(140px, 1fr) 34px;
grid-template-columns: minmax(160px, 1fr) 24px minmax(140px, 1fr) 34px;
}
.mapping-field {
display: grid;
gap: 5px;
min-width: 0;
}
.mapping-field-label {
color: var(--text-muted);
font-size: 10.5px;
font-weight: 800;
letter-spacing: 0.04em;
line-height: 1;
text-transform: uppercase;
}
.mapping-arrow {
display: inline-flex;
align-items: center;
justify-content: center;
align-self: end;
width: 24px;
height: 34px;
color: var(--accent);
font-size: 18px;
font-weight: 900;
line-height: 1;
}
.mapping-default-value {
border-style: dashed;
}
.mapping-row-remove {
@@ -331,6 +364,12 @@
grid-template-columns: 1fr;
}
.mapping-arrow {
width: 100%;
height: 18px;
transform: rotate(90deg);
}
.mapping-row-remove {
width: 100%;
}
-11
View File
@@ -114,16 +114,6 @@
</label>
<div id="approval-config-fields" class="approval-config-fields" hidden>
<div class="form-row">
<div class="form-group">
<label class="form-label" for="approval-risk-level" data-i18n="wizard.approval.risk_level">Уровень риска</label>
<select id="approval-risk-level" class="form-select">
<option value="normal" data-i18n="wizard.approval.risk.normal">Обычное действие</option>
<option value="dangerous" data-i18n="wizard.approval.risk.dangerous">Опасное действие</option>
<option value="financial" data-i18n="wizard.approval.risk.financial">Финансовое действие</option>
<option value="irreversible" data-i18n="wizard.approval.risk.irreversible">Необратимое действие</option>
</select>
</div>
<div class="form-group">
<label class="form-label" for="approval-ttl-seconds" data-i18n="wizard.approval.ttl">Сколько ждать подтверждение</label>
<select id="approval-ttl-seconds" class="form-select">
@@ -132,7 +122,6 @@
<option value="300" selected>5 минут</option>
</select>
</div>
</div>
<div class="form-group">
<label class="form-label" for="approval-title" data-i18n="wizard.approval.confirmation_title">Заголовок подтверждения</label>
-10
View File
@@ -598,11 +598,6 @@ var TRANSLATIONS = {
'wizard.approval.subtitle': 'Enable this for actions that must not run without an explicit user decision.',
'wizard.approval.required_label': 'Require confirmation before execution',
'wizard.approval.required_desc': 'The MCP client receives a pending request, and the action runs only after confirmation through a separate approval endpoint.',
'wizard.approval.risk_level': 'Risk level',
'wizard.approval.risk.normal': 'Normal action',
'wizard.approval.risk.dangerous': 'Dangerous action',
'wizard.approval.risk.financial': 'Financial action',
'wizard.approval.risk.irreversible': 'Irreversible action',
'wizard.approval.ttl': 'How long to wait for confirmation',
'wizard.approval.confirmation_title': 'Confirmation title',
'wizard.approval.confirmation_title_hint': 'This text will be shown by the external confirmation interface.',
@@ -1465,11 +1460,6 @@ var TRANSLATIONS = {
'wizard.approval.subtitle': 'Включайте для действий, которые нельзя выполнять без явного решения пользователя.',
'wizard.approval.required_label': 'Требовать подтверждение перед выполнением',
'wizard.approval.required_desc': 'MCP клиент получит ожидающий запрос, а действие выполнится только после подтверждения через отдельный эндпоинт подтверждения.',
'wizard.approval.risk_level': 'Уровень риска',
'wizard.approval.risk.normal': 'Обычное действие',
'wizard.approval.risk.dangerous': 'Опасное действие',
'wizard.approval.risk.financial': 'Финансовое действие',
'wizard.approval.risk.irreversible': 'Необратимое действие',
'wizard.approval.ttl': 'Сколько ждать подтверждение',
'wizard.approval.confirmation_title': 'Заголовок подтверждения',
'wizard.approval.confirmation_title_hint': 'Этот текст увидит внешний интерфейс подтверждения.',
+1 -3
View File
@@ -46,7 +46,7 @@ function buildApprovalPolicy() {
return {
required: true,
risk_level: textValue('approval-risk-level') || 'normal',
risk_level: 'normal',
confirmation_title: title,
confirmation_body_template: body,
ttl_seconds: normalizeApprovalTtlSeconds(textValue('approval-ttl-seconds')),
@@ -191,7 +191,6 @@ function setApprovalPolicyEditor(policy) {
var enabled = !!(policy && policy.required);
var required = document.getElementById('approval-required');
if (required) required.checked = enabled;
setValue('approval-risk-level', policy && policy.risk_level ? policy.risk_level : 'normal');
setValue('approval-ttl-seconds', policy && policy.ttl_seconds ? String(policy.ttl_seconds) : '300');
setValue('approval-title', policy && policy.confirmation_title ? policy.confirmation_title : tKey('wizard.approval.default_title'));
setValue('approval-body', policy && policy.confirmation_body_template ? policy.confirmation_body_template : tKey('wizard.approval.default_body'));
@@ -219,7 +218,6 @@ function updateApprovalPolicyUi() {
function bindApprovalPolicyControls() {
[
'approval-required',
'approval-risk-level',
'approval-ttl-seconds',
'approval-title',
'approval-body',
+36 -9
View File
@@ -346,6 +346,25 @@
return button;
}
function wrapMappingControl(labelText, control) {
var wrapper = document.createElement('label');
wrapper.className = 'mapping-field';
var label = document.createElement('span');
label.className = 'mapping-field-label';
label.textContent = labelText;
wrapper.appendChild(label);
wrapper.appendChild(control);
return wrapper;
}
function makeMappingArrow() {
var arrow = document.createElement('span');
arrow.className = 'mapping-arrow';
arrow.setAttribute('aria-hidden', 'true');
arrow.textContent = '→';
return arrow;
}
function renderRequestRows(rows) {
var root = field('wizard-request-mapping-rows');
if (!root) return;
@@ -364,7 +383,10 @@
var input = makeInput(row.input, 'form-input input-mono mapping-source', 'base');
input.dataset.role = 'input';
item.appendChild(input);
input.title = 'Поле, которое MCP клиент передает инструменту';
item.appendChild(wrapMappingControl('Из инструмента', input));
item.appendChild(makeMappingArrow());
var select = document.createElement('select');
select.className = 'form-select mapping-target';
@@ -378,16 +400,17 @@
select.appendChild(makeOption(entry[0], entry[1], entry[0] === row.target));
});
select.addEventListener('change', syncVisualMappingsToYaml);
item.appendChild(select);
item.appendChild(wrapMappingControl('Куда в API', select));
var apiName = makeInput(row.apiName || row.input, 'form-input input-mono mapping-api-name', 'base');
apiName.dataset.role = 'apiName';
item.appendChild(apiName);
apiName.title = 'Имя path, query, header или body-поля в API-запросе';
item.appendChild(wrapMappingControl('Имя в API', apiName));
var defaultValue = makeInput(row.defaultValue, 'form-input input-mono mapping-default-value', 'по умолчанию');
var defaultValue = makeInput(row.defaultValue, 'form-input input-mono mapping-default-value', 'если не передано');
defaultValue.dataset.role = 'defaultValue';
defaultValue.title = 'Значение по умолчанию, если поле не передано';
item.appendChild(defaultValue);
defaultValue.title = 'Необязательно. Это значение уйдет в API, если агент не передал поле инструмента.';
item.appendChild(wrapMappingControl('Если пусто', defaultValue));
var transform = document.createElement('select');
transform.className = 'form-select mapping-transform';
@@ -398,7 +421,7 @@
});
transform.title = 'Простое преобразование перед отправкой в API';
transform.addEventListener('change', syncVisualMappingsToYaml);
item.appendChild(transform);
item.appendChild(wrapMappingControl('Преобразование', transform));
item.appendChild(makeRemoveButton(item));
return item;
@@ -422,11 +445,15 @@
var responsePath = makeInput(row.responsePath, 'form-input input-mono mapping-response-path', 'rates.EUR');
responsePath.dataset.role = 'responsePath';
item.appendChild(responsePath);
responsePath.title = 'Поле из ответа API';
item.appendChild(wrapMappingControl('Из ответа API', responsePath));
item.appendChild(makeMappingArrow());
var output = makeInput(row.output, 'form-input input-mono mapping-output-field', 'rate');
output.dataset.role = 'output';
item.appendChild(output);
output.title = 'Поле результата, которое получит MCP клиент';
item.appendChild(wrapMappingControl('В результат инструмента', output));
item.appendChild(makeRemoveButton(item));
return item;
}
+2 -2
View File
@@ -831,7 +831,7 @@ test('wizard edit mode preserves explicit request mapping targets on save', asyn
await page.evaluate(() => window.CrankWizardShell.doGoToStep(3));
await expect(page.locator('#approval-required')).toBeChecked();
await expect(page.locator('#approval-config-fields')).toBeVisible();
await expect(page.locator('#approval-risk-level')).toHaveValue('financial');
await expect(page.locator('#approval-risk-level')).toHaveCount(0);
await expect(page.locator('#approval-ttl-seconds')).toHaveValue('180');
await expect(page.locator('#approval-title')).toHaveValue('Подтвердите обмен валюты');
await expect(page.locator('#approval-body')).toHaveValue('Проверьте валюты и подтвердите выполнение операции.');
@@ -863,7 +863,7 @@ test('wizard edit mode preserves explicit request mapping targets on save', asyn
streaming: null,
approval_policy: {
required: true,
risk_level: 'financial',
risk_level: 'normal',
confirmation_title: 'Подтвердите обмен валюты',
confirmation_body_template: 'Проверьте валюты и подтвердите выполнение операции.',
ttl_seconds: 180,