feat(external-systems): 优化管理端配置表单

This commit is contained in:
caoqianming 2026-08-11 08:58:30 +08:00
parent 2b4316ed25
commit 8bda11f683
5 changed files with 131 additions and 40 deletions

View File

@ -5,6 +5,10 @@
> 所以不是每个版本号都有条目。条目格式 `## <版本> — <日期>`,新条目加在最上面。 > 所以不是每个版本号都有条目。条目格式 `## <版本> — <日期>`,新条目加在最上面。
> 工程口径的完整记录见 `PROGRESS.md` / git log。 > 工程口径的完整记录见 `PROGRESS.md` / git log。
## 0.63.5 — 2026-08-11
- 管理员配置外部系统时改用独立弹框,并会根据 OpenAPI、MCP 和认证方式只展示对应配置项;外部系统列表同步显示各连接类型真实的能力模式。
## 0.63.4 — 2026-08-10 ## 0.63.4 — 2026-08-10
- 外部系统管理统一为通用 OpenAPI 和 MCP 配置,不再需要选择特定 MES 类型;已有 MES 定义可通过随版本提供的一次性脚本转换,用户连接和加密凭据保持不变。 - 外部系统管理统一为通用 OpenAPI 和 MCP 配置,不再需要选择特定 MES 类型;已有 MES 定义可通过随版本提供的一次性脚本转换,用户连接和加密凭据保持不变。

View File

@ -2,7 +2,7 @@
> 配合 `DESIGN.md`。本文件只记 phase 状态、决策偏差、文件量、下一步。每条 1-2 句:做了啥 + 关键判断;细节查 `git log` / `git diff` / `DESIGN §7.9` > 配合 `DESIGN.md`。本文件只记 phase 状态、决策偏差、文件量、下一步。每条 1-2 句:做了啥 + 关键判断;细节查 `git log` / `git diff` / `DESIGN §7.9`
最后更新:2026-08-10(外部系统统一为通用 OpenAPI/MCP,bump 0.63.4) 最后更新:2026-08-11(外部系统配置弹框与动态字段,bump 0.63.5)
--- ---
@ -21,6 +21,10 @@
## 已完成关键能力 ## 已完成关键能力
### 2026-08-11
- **08-11 / 0.63.5 / 外部系统配置弹框 + 动态字段**:管理后台外部系统定义由页面平铺表单改为新增/编辑弹框,按 OpenAPI/MCP connector 和认证方式只展示、提交当前适用字段MCP URL 自动提供认证同源基址,新建时不再重复要求 Base URL编辑存量定义继续保留既有基址列表能力模式对 MCP 改为动态 `tools/list`。Web 无 DB 路由 23 项、JavaScript 语法、动态 DOM 引用及 diff 检查通过;当前运行时未发现可连接浏览器,真实点击冒烟待浏览器配置恢复后补验。无 schema、migration、HTTP API、依赖或运行方式变化未连接生产 DB。
### 2026-08-10 ### 2026-08-10
- **08-10 / 0.63.4 / 移除 Factory preset + 通用 OpenAPI 契约收敛**:运行态和管理端删除 `factory_mes` provider、兼容包装类及业务默认文案只保留通用 OpenAPI/MCP definition新增显式 `ZCBOT_MIGRATION_DB_URL`、默认 dry-run 的一次性脚本,将存量 definition 原子转换为 `generic_openapi + query` 并同步 active connection revision不解密或重写凭据。OpenAPI 参数直接执行规格中的数值、长度、数组和枚举约束,移除按 `page/page_size/pageoff` 名称猜测分页语义及无效 `max_page_size` 配置。完整 524 项 unittest 全绿17 skip外部系统/迁移/无 DB 路由专项 80 项、Python 编译、Ruff 致命规则、JavaScript 语法及 diff 检查通过;迁移脚本仅验证缺少显式 URL 时拒绝运行,未连接或写入任何数据库。 - **08-10 / 0.63.4 / 移除 Factory preset + 通用 OpenAPI 契约收敛**:运行态和管理端删除 `factory_mes` provider、兼容包装类及业务默认文案只保留通用 OpenAPI/MCP definition新增显式 `ZCBOT_MIGRATION_DB_URL`、默认 dry-run 的一次性脚本,将存量 definition 原子转换为 `generic_openapi + query` 并同步 active connection revision不解密或重写凭据。OpenAPI 参数直接执行规格中的数值、长度、数组和枚举约束,移除按 `page/page_size/pageoff` 名称猜测分页语义及无效 `max_page_size` 配置。完整 524 项 unittest 全绿17 skip外部系统/迁移/无 DB 路由专项 80 项、Python 编译、Ruff 致命规则、JavaScript 语法及 diff 检查通过;迁移脚本仅验证缺少显式 URL 时拒绝运行,未连接或写入任何数据库。

View File

@ -1,3 +1,3 @@
# zcbot 版本号单一事实源:web/app.py 的 FastAPI version、/healthz 返回、前端展示都引这里。 # zcbot 版本号单一事实源:web/app.py 的 FastAPI version、/healthz 返回、前端展示都引这里。
# 改版本只动这一行。 # 改版本只动这一行。
__version__ = "0.63.4" __version__ = "0.63.5"

View File

@ -109,10 +109,33 @@
width: 100%; padding: 6px 8px; border: 1px solid var(--border); width: 100%; padding: 6px 8px; border: 1px solid var(--border);
border-radius: var(--r-md); color: var(--text); background: #fff; border-radius: var(--r-md); color: var(--text); background: #fff;
} }
#ext-admin-form button, #s-external td button { #ext-admin-form button, #s-external button {
font-size: 12px; padding: 4px 9px; border: 1px solid var(--border); font-size: 12px; padding: 4px 9px; border: 1px solid var(--border);
border-radius: var(--r-md); background: #fff; cursor: pointer; border-radius: var(--r-md); background: #fff; cursor: pointer;
} }
#s-external button.primary, #ext-admin-form button.primary {
color: #fff; border-color: var(--accent); background: var(--accent);
}
#external-definition-modal .card {
width: min(820px, calc(100vw - 32px)); max-height: calc(100vh - 32px);
margin: 0; padding: 0; display: flex; flex-direction: column;
}
.ext-modal-head {
display: flex; align-items: center; gap: 12px; padding: 12px 16px;
border-bottom: 1px solid var(--border);
}
.ext-modal-head h3 { flex: 1; margin: 0; font-size: 14px; }
.ext-modal-head button { font-size: 18px !important; line-height: 1; border: 0 !important; }
.ext-form-body { padding: 16px; overflow-y: auto; }
.ext-form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px 12px; }
.ext-form-section {
grid-column: 1 / -1; margin: 4px 0 0; padding-top: 10px;
border-top: 1px solid var(--border-soft); color: var(--text); font-weight: 600;
}
.ext-form-actions {
display: flex; justify-content: flex-end; gap: 8px; padding: 10px 16px;
border-top: 1px solid var(--border); background: #fff;
}
/* 复用主控制台 dialog.js 的单例页内弹框。 */ /* 复用主控制台 dialog.js 的单例页内弹框。 */
.modal { .modal {
@ -156,6 +179,8 @@
.card-head { flex-wrap: wrap; } .card-head { flex-wrap: wrap; }
.ctrl { flex-wrap: wrap; } .ctrl { flex-wrap: wrap; }
.ctrl select { min-width: 0; } .ctrl select { min-width: 0; }
.ext-form-grid { grid-template-columns: 1fr; }
.ext-form-grid > label, .ext-form-section { grid-column: 1 !important; }
/* 窄屏:目录变顶部横向 chip 条 */ /* 窄屏:目录变顶部横向 chip 条 */
#layout { grid-template-columns: 1fr; gap: 10px; } #layout { grid-template-columns: 1fr; gap: 10px; }
#toc { #toc {

View File

@ -161,32 +161,47 @@ function renderByDay(rows) {
function renderExternalDefinitions() { function renderExternalDefinitions() {
const rows = externalDefinitions.map(r => { const rows = externalDefinitions.map(r => {
const cfg = r.config || {}; const cfg = r.config || {};
const capability = r.provider === "generic_mcp"
? "动态工具tools/list"
: (cfg.operation_mode === "upstream_managed"
? "上游托管"
: `查询(${Object.keys(cfg.operation_policies || {}).length} 个 POST`);
return `<tr data-definition-id="${escapeHtml(r.definition_id)}">` return `<tr data-definition-id="${escapeHtml(r.definition_id)}">`
+ `<td>${escapeHtml(r.name)} <span class="chip">${escapeHtml(r.provider_title || r.provider)}</span>${r.enabled ? "" : ' <span class="chip">停用</span>'}` + `<td>${escapeHtml(r.name)} <span class="chip">${escapeHtml(r.provider_title || r.provider)}</span>${r.enabled ? "" : ' <span class="chip">停用</span>'}`
+ ` <span class="chip">${r.visibility === "organization" ? "全部用户" : `指定 ${((r.selected_user_ids || []).length)}`}</span></td>` + ` <span class="chip">${r.visibility === "organization" ? "全部用户" : `指定 ${((r.selected_user_ids || []).length)}`}</span></td>`
+ `<td class="email" title="${escapeHtml(cfg.base_url || "")}">${escapeHtml(r.host || cfg.base_url || "—")}</td>` + `<td class="email" title="${escapeHtml(cfg.base_url || "")}">${escapeHtml(r.host || cfg.base_url || "—")}</td>`
+ `<td>${cfg.operation_mode === "upstream_managed" ? "上游托管" : `查询(${Object.keys(cfg.operation_policies || {}).length} 个 POST`}</td>` + `<td>${capability}</td>`
+ `<td><button data-ext-edit>编辑</button> <button data-ext-delete>删除</button></td>` + `<td><button data-ext-edit>编辑</button> <button data-ext-delete>删除</button></td>`
+ `</tr>`; + `</tr>`;
}).join("") || `<tr><td colspan="4" class="empty">尚未配置外部系统</td></tr>`; }).join("") || `<tr><td colspan="4" class="empty">尚未配置外部系统</td></tr>`;
$("s-external").innerHTML = `<div class="card"><div class="card-head"><h2>外部系统目录</h2>` $("s-external").innerHTML = `<div class="card"><div class="card-head"><h2>外部系统目录</h2>`
+ `<span class="sublabel">OpenAPI 与 Streamable HTTP MCP 系统均可配置;用户只提交该系统要求的凭据</span></div>` + `<div><span class="sublabel">OpenAPI 与 Streamable HTTP MCP 系统均可配置;用户只提交该系统要求的凭据</span> `
+ `<form id="ext-admin-form" style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:14px;">` + `<button id="exa-add" class="primary" type="button">新增外部系统</button></div></div>`
+ `<div class="scroll-x"><table><thead><tr><th>系统</th><th>主机</th><th>能力模式</th><th>操作</th></tr></thead>`
+ `<tbody>${rows}</tbody></table></div></div>`
+ `<div id="external-definition-modal" class="modal">`
+ `<div class="card" role="dialog" aria-modal="true" aria-labelledby="exa-dialog-title">`
+ `<div class="ext-modal-head"><h3 id="exa-dialog-title">新增外部系统</h3><button id="exa-close" type="button" aria-label="关闭">×</button></div>`
+ `<form id="ext-admin-form">`
+ `<div class="ext-form-body"><div class="ext-form-grid">`
+ `<div class="ext-form-section">连接方式</div>`
+ `<label>系统类型<select id="exa-provider"><option value="generic_openapi">通用 OpenAPI 系统</option><option value="generic_mcp">通用 MCP 系统</option></select></label>` + `<label>系统类型<select id="exa-provider"><option value="generic_openapi">通用 OpenAPI 系统</option><option value="generic_mcp">通用 MCP 系统</option></select></label>`
+ `<label>认证方式<select id="exa-auth"><option value="password_jwt">用户名密码换取 Token</option><option value="api_key">API Key</option><option value="bearer_token">Bearer Token</option></select></label>` + `<label>认证方式<select id="exa-auth"><option value="password_jwt">用户名密码换取 Token</option><option value="api_key">API Key</option><option value="bearer_token">Bearer Token</option></select></label>`
+ `<label id="exa-operation-mode-wrap" style="grid-column:1/-1;">接口执行模式<select id="exa-operation-mode"><option value="upstream_managed">上游托管(开放规格中的全部方法)</option><option value="query">查询模式GET/HEAD + 允许的只读 POST</option></select><span id="exa-operation-mode-hint" class="sublabel"></span></label>`
+ `<label>系统名称<input id="exa-name" required placeholder="ERP / LIMS / 其他系统"></label>` + `<label>系统名称<input id="exa-name" required placeholder="ERP / LIMS / 其他系统"></label>`
+ `<label>Base URL<input id="exa-base" required placeholder="https://api.example.com"></label>` + `<label id="exa-base-wrap">Base URL<input id="exa-base" placeholder="https://api.example.com"></label>`
+ `<label id="exa-spec-wrap">Swagger / OpenAPI URL<input id="exa-spec" placeholder="https://api.example.com/openapi.json"></label>` + `<label id="exa-spec-wrap">Swagger / OpenAPI URL<input id="exa-spec" placeholder="https://api.example.com/openapi.json"></label>`
+ `<label id="exa-mcp-wrap">MCP URL<input id="exa-mcp" placeholder="https://api.example.com/mcp"><span class="sublabel">连接后Server 通过 tools/list 暴露的全部工具均可使用。</span></label>` + `<label id="exa-mcp-wrap">MCP URL<input id="exa-mcp" placeholder="https://api.example.com/mcp"><span class="sublabel">连接后Server 通过 tools/list 暴露的全部工具均可使用。</span></label>`
+ `<label id="exa-mcp-server-wrap">期望的 MCP Server 名称<input id="exa-mcp-server" placeholder="server-name可选"></label>` + `<label id="exa-mcp-server-wrap">期望的 MCP Server 名称<input id="exa-mcp-server" placeholder="server-name可选"></label>`
+ `<label id="exa-operation-mode-wrap" style="grid-column:1/-1;">接口执行模式<select id="exa-operation-mode"><option value="upstream_managed">上游托管(开放规格中的全部方法)</option><option value="query">查询模式GET/HEAD + 允许的只读 POST</option></select><span id="exa-operation-mode-hint" class="sublabel"></span></label>`
+ `<label id="exa-operations-wrap" style="grid-column:1/-1;">允许的只读 POST operationId逗号分隔GET/HEAD 默认可查询)<input id="exa-operations" placeholder="query_records"></label>`
+ `<div class="ext-form-section">认证参数</div>`
+ `<label>登录路径<input id="exa-login" value="/api/auth/token/"></label>` + `<label>登录路径<input id="exa-login" value="/api/auth/token/"></label>`
+ `<label>Token 字段路径<input id="exa-token-field" value="access" placeholder="data.access_token"></label>` + `<label>Token 字段路径<input id="exa-token-field" value="access" placeholder="data.access_token"></label>`
+ `<label>用户名字段<input id="exa-username-field" value="username"></label>` + `<label>用户名字段<input id="exa-username-field" value="username"></label>`
+ `<label>密码字段<input id="exa-password-field" value="password"></label>` + `<label>密码字段<input id="exa-password-field" value="password"></label>`
+ `<label>认证 Header<input id="exa-auth-header" value="Authorization"></label>` + `<label>认证 Header<input id="exa-auth-header" value="Authorization"></label>`
+ `<label>Header 模板<input id="exa-auth-template" value="Bearer {token}"></label>` + `<label>Header 模板<input id="exa-auth-template" value="Bearer {token}"></label>`
+ `<label id="exa-operations-wrap" style="grid-column:1/-1;">允许的只读 POST operationId逗号分隔GET/HEAD 默认可查询)<input id="exa-operations" placeholder="query_records"></label>` + `<div class="ext-form-section">查询与授权</div>`
+ `<label style="grid-column:1/-1;">推荐查询入口 operationId逗号分隔<input id="exa-recommended" placeholder="list_datasets, query_dataset"></label>` + `<label style="grid-column:1/-1;">推荐查询入口 operationId逗号分隔<input id="exa-recommended" placeholder="list_datasets, query_dataset"></label>`
+ `<label style="grid-column:1/-1;">查询规划提示` + `<label style="grid-column:1/-1;">查询规划提示`
+ `<input id="exa-guidance" type="hidden">` + `<input id="exa-guidance" type="hidden">`
@ -199,10 +214,9 @@ function renderExternalDefinitions() {
+ `<label id="exa-users-wrap" style="grid-column:1/-1;">授权用户Ctrl/Command 可多选)<select id="exa-users" multiple size="6">` + `<label id="exa-users-wrap" style="grid-column:1/-1;">授权用户Ctrl/Command 可多选)<select id="exa-users" multiple size="6">`
+ externalUsers.map(u => `<option value="${escapeHtml(u.user_id)}">${escapeHtml(u.label)}${u.email ? " · " + escapeHtml(u.email) : ""}</option>`).join("") + externalUsers.map(u => `<option value="${escapeHtml(u.user_id)}">${escapeHtml(u.label)}${u.email ? " · " + escapeHtml(u.email) : ""}</option>`).join("")
+ `</select><span class="sublabel">撤销选择会删除该用户为此外部系统保存的密文凭据。</span></label>` + `</select><span class="sublabel">撤销选择会删除该用户为此外部系统保存的密文凭据。</span></label>`
+ `<div style="grid-column:1/-1;display:flex;gap:8px;justify-content:flex-end;">` + `</div></div><div class="ext-form-actions">`
+ `<button id="exa-cancel" type="button" hidden>取消编辑</button><button type="submit">保存系统定义</button></div></form>` + `<button id="exa-cancel" type="button">取消</button><button class="primary" type="submit">保存系统定义</button>`
+ `<div class="scroll-x"><table><thead><tr><th>系统</th><th>主机</th><th>执行模式</th><th>操作</th></tr></thead>` + `</div></form></div></div>`;
+ `<tbody>${rows}</tbody></table></div></div>`;
$("ext-admin-form").onsubmit = saveExternalDefinition; $("ext-admin-form").onsubmit = saveExternalDefinition;
$("exa-guidance").value = ""; $("exa-guidance").value = "";
@ -214,7 +228,15 @@ function renderExternalDefinitions() {
$("exa-access").onchange = () => { $("exa-access").onchange = () => {
$("exa-users-wrap").hidden = $("exa-access").value !== "selected"; $("exa-users-wrap").hidden = $("exa-access").value !== "selected";
}; };
$("exa-cancel").onclick = () => { externalEditingId = ""; renderExternalDefinitions(); }; $("exa-add").onclick = () => openExternalDefinitionModal();
$("exa-cancel").onclick = closeExternalDefinitionModal;
$("exa-close").onclick = closeExternalDefinitionModal;
$("external-definition-modal").onclick = event => {
if (event.target.id === "external-definition-modal") closeExternalDefinitionModal();
};
$("external-definition-modal").onkeydown = event => {
if (event.key === "Escape") closeExternalDefinitionModal();
};
updateExternalAuthForm(); updateExternalAuthForm();
updateExternalOperationMode(); updateExternalOperationMode();
$("s-external").onclick = (e) => { $("s-external").onclick = (e) => {
@ -222,7 +244,7 @@ function renderExternalDefinitions() {
if (!tr) return; if (!tr) return;
const row = externalDefinitions.find(x => x.definition_id === tr.dataset.definitionId); const row = externalDefinitions.find(x => x.definition_id === tr.dataset.definitionId);
if (!row) return; if (!row) return;
if (e.target.closest("[data-ext-edit]")) fillExternalDefinition(row); if (e.target.closest("[data-ext-edit]")) openExternalDefinitionModal(row);
if (e.target.closest("[data-ext-delete]")) deleteExternalDefinition(row); if (e.target.closest("[data-ext-delete]")) deleteExternalDefinition(row);
}; };
} }
@ -237,11 +259,13 @@ function updateExternalAuthForm() {
function updateExternalConnectorForm() { function updateExternalConnectorForm() {
const mcp = $("exa-provider").value === "generic_mcp"; const mcp = $("exa-provider").value === "generic_mcp";
$("exa-base-wrap").hidden = mcp;
$("exa-spec-wrap").hidden = mcp; $("exa-spec-wrap").hidden = mcp;
$("exa-mcp-wrap").hidden = !mcp; $("exa-mcp-wrap").hidden = !mcp;
$("exa-mcp-server-wrap").hidden = !mcp; $("exa-mcp-server-wrap").hidden = !mcp;
$("exa-operation-mode-wrap").hidden = mcp; $("exa-operation-mode-wrap").hidden = mcp;
$("exa-operations-wrap").hidden = mcp || $("exa-operation-mode").value === "upstream_managed"; $("exa-operations-wrap").hidden = mcp || $("exa-operation-mode").value === "upstream_managed";
$("exa-base").required = !mcp;
$("exa-spec").required = !mcp; $("exa-spec").required = !mcp;
$("exa-mcp").required = mcp; $("exa-mcp").required = mcp;
} }
@ -298,8 +322,29 @@ async function editExternalGuidance() {
updateExternalGuidanceSummary(); updateExternalGuidanceSummary();
} }
function openExternalDefinitionModal(row = null) {
externalEditingId = row ? row.definition_id : "";
$("exa-dialog-title").textContent = row ? `编辑外部系统:${row.name}` : "新增外部系统";
if (row) {
fillExternalDefinition(row);
} else {
$("ext-admin-form").reset();
$("exa-provider").disabled = false;
$("exa-provider").value = "generic_openapi";
$("exa-access").value = "selected";
$("exa-users-wrap").hidden = false;
applyExternalProviderDefaults();
}
$("external-definition-modal").classList.add("show");
$("exa-name").focus();
}
function closeExternalDefinitionModal() {
externalEditingId = "";
$("external-definition-modal").classList.remove("show");
}
function fillExternalDefinition(row) { function fillExternalDefinition(row) {
externalEditingId = row.definition_id;
const cfg = row.config || {}; const cfg = row.config || {};
$("exa-provider").value = row.provider || "generic_openapi"; $("exa-provider").value = row.provider || "generic_openapi";
$("exa-provider").disabled = true; $("exa-provider").disabled = true;
@ -331,31 +376,21 @@ function fillExternalDefinition(row) {
const selected = new Set(row.selected_user_ids || []); const selected = new Set(row.selected_user_ids || []);
Array.from($("exa-users").options).forEach(o => { o.selected = selected.has(o.value); }); Array.from($("exa-users").options).forEach(o => { o.selected = selected.has(o.value); });
$("exa-users-wrap").hidden = $("exa-access").value !== "selected"; $("exa-users-wrap").hidden = $("exa-access").value !== "selected";
$("exa-cancel").hidden = false;
$("exa-name").focus();
} }
async function saveExternalDefinition(e) { async function saveExternalDefinition(e) {
e.preventDefault(); e.preventDefault();
const provider = $("exa-provider").value;
const authType = $("exa-auth").value;
const mcp = provider === "generic_mcp";
const current = externalDefinitions.find(x => x.definition_id === externalEditingId);
const currentConfig = (current || {}).config || {};
const body = { const body = {
provider: $("exa-provider").value, provider,
name: $("exa-name").value.trim(), name: $("exa-name").value.trim(),
base_url: $("exa-base").value.trim(), auth_type: authType,
openapi_url: $("exa-spec").value.trim(),
mcp_url: $("exa-mcp").value.trim(),
expected_server_name: $("exa-mcp-server").value.trim(),
login_path: $("exa-login").value.trim() || "/api/auth/token/",
auth_type: $("exa-auth").value,
username_field: $("exa-username-field").value.trim() || "username",
password_field: $("exa-password-field").value.trim() || "password",
token_field: $("exa-token-field").value.trim() || "access",
auth_header_name: $("exa-auth-header").value.trim() || "Authorization", auth_header_name: $("exa-auth-header").value.trim() || "Authorization",
auth_header_template: $("exa-auth-template").value || "Bearer {token}", auth_header_template: $("exa-auth-template").value || "Bearer {token}",
operation_mode: $("exa-operation-mode").value,
operation_policies: Object.fromEntries(
$("exa-operations").value.split(",").map(x => x.trim()).filter(Boolean)
.map(operationId => [operationId, "read"]),
),
recommended_operation_ids: $("exa-recommended").value.split(",").map(x => x.trim()).filter(Boolean), recommended_operation_ids: $("exa-recommended").value.split(",").map(x => x.trim()).filter(Boolean),
query_guidance: $("exa-guidance").value.trim(), query_guidance: $("exa-guidance").value.trim(),
verify_tls: $("exa-tls").checked, verify_tls: $("exa-tls").checked,
@ -363,15 +398,38 @@ async function saveExternalDefinition(e) {
visibility: $("exa-access").value, visibility: $("exa-access").value,
selected_user_ids: Array.from($("exa-users").selectedOptions).map(o => o.value), selected_user_ids: Array.from($("exa-users").selectedOptions).map(o => o.value),
}; };
const current = externalDefinitions.find(x => x.definition_id === externalEditingId); if (authType === "password_jwt") {
body.timeout_seconds = current ? (current.config || {}).timeout_seconds || 15 : 15; Object.assign(body, {
body.max_result_bytes = current ? (current.config || {}).max_result_bytes || 65536 : 65536; login_path: $("exa-login").value.trim() || "/api/auth/token/",
username_field: $("exa-username-field").value.trim() || "username",
password_field: $("exa-password-field").value.trim() || "password",
token_field: $("exa-token-field").value.trim() || "access",
});
}
if (mcp) {
Object.assign(body, {
// 编辑时保留既有同源认证基址;新建时由后端从 MCP URL 推导 origin。
base_url: currentConfig.base_url || "",
mcp_url: $("exa-mcp").value.trim(),
expected_server_name: $("exa-mcp-server").value.trim(),
max_response_bytes: currentConfig.max_response_bytes || 10485760,
});
} else {
Object.assign(body, {
base_url: $("exa-base").value.trim(),
openapi_url: $("exa-spec").value.trim(),
operation_mode: $("exa-operation-mode").value,
operation_policies: Object.fromEntries(
$("exa-operations").value.split(",").map(x => x.trim()).filter(Boolean)
.map(operationId => [operationId, "read"]),
),
});
}
body.timeout_seconds = currentConfig.timeout_seconds || 15;
body.max_result_bytes = currentConfig.max_result_bytes || 65536;
body.max_total_result_bytes = current body.max_total_result_bytes = current
? (current.config || {}).max_total_result_bytes || 262144 ? currentConfig.max_total_result_bytes || 262144
: 262144; : 262144;
body.max_response_bytes = current
? (current.config || {}).max_response_bytes || 10485760
: 10485760;
try { try {
await apiSend( await apiSend(
externalEditingId ? "PUT" : "POST", externalEditingId ? "PUT" : "POST",