Clarify request mapping UI
This commit is contained in:
+40
-1
@@ -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%;
|
||||
}
|
||||
|
||||
@@ -114,24 +114,13 @@
|
||||
</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">
|
||||
<option value="60">1 минута</option>
|
||||
<option value="180">3 минуты</option>
|
||||
<option value="300" selected>5 минут</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">
|
||||
<option value="60">1 минута</option>
|
||||
<option value="180">3 минуты</option>
|
||||
<option value="300" selected>5 минут</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
|
||||
@@ -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': 'Этот текст увидит внешний интерфейс подтверждения.',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user