feat: add tabs to bot settings

This commit is contained in:
xmanrui 2026-09-01 02:31:57 +08:00
parent 08368c135c
commit 009f1abec9
7 changed files with 219 additions and 121 deletions

View file

@ -6,6 +6,11 @@ This file records the notable changes in each dsh-im release. Its format follows
## [Unreleased] ## [Unreleased]
### Changed / 变更
- 机器人卡片的设置页改为可扩展的横向 Tab 布局,现有 Bot ID、投递目标管理与专属使用文档统一归入“投递设置”页签。
Bot-card settings now use an extensible horizontal tab layout, with the existing Bot ID, delivery-target management, and dedicated guide grouped under the Delivery settings tab.
## [4.3.0] - 2026-09-01 ## [4.3.0] - 2026-09-01
### Added / 新增 ### Added / 新增

View file

@ -841,6 +841,9 @@ var EN = Object.freeze({
"IM\u673A\u5668\u4EBA": "IM bots", "IM\u673A\u5668\u4EBA": "IM bots",
"IM\u673A\u5668\u4EBA\u8BBE\u7F6E": "IM bot settings", "IM\u673A\u5668\u4EBA\u8BBE\u7F6E": "IM bot settings",
"\u66F4\u591A\u673A\u5668\u4EBA\u8BBE\u7F6E": "More bot settings", "\u66F4\u591A\u673A\u5668\u4EBA\u8BBE\u7F6E": "More bot settings",
"\u673A\u5668\u4EBA\u8BBE\u7F6E": "Bot settings",
"\u673A\u5668\u4EBA\u8BBE\u7F6E\u9875\u7B7E": "Bot settings tabs",
"\u6295\u9012\u8BBE\u7F6E": "Delivery settings",
"IM \u6E20\u9053": "IM channels", "IM \u6E20\u9053": "IM channels",
"\u8BA9 DeepSeek Harness \u89E6\u624B\u53EF\u53CA": "Connecting DeepSeek Harness", "\u8BA9 DeepSeek Harness \u89E6\u624B\u53EF\u53CA": "Connecting DeepSeek Harness",
"\u5F53\u524D\u7248\u672C": "Current version", "\u5F53\u524D\u7248\u672C": "Current version",
@ -11870,6 +11873,9 @@ var DELIVERY_ENDPOINTS = Object.freeze({
delete: "target.delete", delete: "target.delete",
test: "target.test" test: "target.test"
}); });
var BOT_SETTINGS_TABS = Object.freeze([
Object.freeze({ id: "delivery", label: "\u6295\u9012\u8BBE\u7F6E" })
]);
var CHANNEL_DEFINITIONS = Object.freeze({ var CHANNEL_DEFINITIONS = Object.freeze({
weixin: { weixin: {
label: "\u5FAE\u4FE1", label: "\u5FAE\u4FE1",
@ -12520,6 +12526,9 @@ function DeliveryTargetSettingsPage({ channel: channel4, account, rpcCall, onBac
setBotCopyState(presentError10(caught, "\u590D\u5236\u5931\u8D25\u3002")); setBotCopyState(presentError10(caught, "\u590D\u5236\u5931\u8D25\u3002"));
} }
}; };
const deliveryTab = BOT_SETTINGS_TABS[0];
const deliveryTabId = `dim-bot-settings-${deliveryTab.id}-tab`;
const deliveryPanelId = `dim-bot-settings-${deliveryTab.id}-panel`;
return h2( return h2(
"section", "section",
{ {
@ -12529,114 +12538,137 @@ function DeliveryTargetSettingsPage({ channel: channel4, account, rpcCall, onBac
h2( h2(
"header", "header",
{ className: "dim-deliveryHeader" }, { className: "dim-deliveryHeader" },
h2(DeliveryButton, { className: "dim-deliveryBack", onClick: onBack }, "\u2190 \u8FD4\u56DE\u673A\u5668\u4EBA\u5217\u8868"), h2(DeliveryButton, { className: "dim-deliveryBack", onClick: onBack }, "\u2190 \u8FD4\u56DE\u673A\u5668\u4EBA\u5217\u8868")
h2(
"div",
{ className: "dim-deliveryHeaderMeta" },
h2(
"a",
{
className: "dim-deliveryDocsLink",
href: isEnglish() ? DELIVERY_DOCS_URL.en : DELIVERY_DOCS_URL.zh,
target: "_blank",
rel: "noopener noreferrer",
"aria-label": "\u6253\u5F00\u4E3B\u52A8\u6295\u9012\u4F7F\u7528\u6587\u6863"
},
h2("span", null, "\u4F7F\u7528\u6587\u6863"),
h2("span", { "aria-hidden": "true" }, "\u2197")
)
)
), ),
h2( h2(
"section", "div",
{ className: "dim-deliveryIdentity", "aria-labelledby": "dim-delivery-bot-title" }, { className: "dim-botSettingsTabsBar" },
h2( h2("nav", {
"div", className: "dim-botSettingsTabs",
null, role: "tablist",
h2( "aria-label": "\u673A\u5668\u4EBA\u8BBE\u7F6E\u9875\u7B7E"
"h2", }, BOT_SETTINGS_TABS.map((tab) => h2("button", {
{ id: "dim-delivery-bot-title", className: "dim-deliveryBotName" }, key: tab.id,
account.botName || "\u673A\u5668\u4EBA\u8BBE\u7F6E" id: `dim-bot-settings-${tab.id}-tab`,
) type: "button",
), role: "tab",
h2( className: "dim-botSettingsTab",
"div", "aria-selected": tab.id === deliveryTab.id,
{ className: "dim-deliveryBotId" }, "aria-controls": `dim-bot-settings-${tab.id}-panel`,
h2("span", null, "Bot ID"), tabIndex: tab.id === deliveryTab.id ? 0 : -1
h2("code", { title: account.botId }, account.botId), }, tab.label)))
h2(DeliveryButton, { onClick: () => void copyBotId() }, "\u590D\u5236")
),
botCopyState ? h2("p", { className: "dim-targetFeedback", role: "status" }, botCopyState) : null
), ),
h2( h2(
"section", "div",
{ className: "dim-deliveryTargets", "aria-labelledby": "dim-delivery-targets-title" }, {
id: deliveryPanelId,
className: "dim-botSettingsTabPanel",
role: "tabpanel",
"aria-labelledby": deliveryTabId
},
h2( h2(
"div", "section",
{ className: "dim-deliverySectionHeading" }, { className: "dim-deliveryIdentity", "aria-labelledby": "dim-delivery-bot-title" },
h2( h2(
"div", "div",
null, { className: "dim-deliveryIdentityHeading" },
h2("h3", { id: "dim-delivery-targets-title" }, "\u6295\u9012\u76EE\u6807"), h2(
account.connected ? null : h2("p", null, "\u673A\u5668\u4EBA\u5F53\u524D\u79BB\u7EBF\uFF1B\u4ECD\u53EF\u914D\u7F6E\u76EE\u6807\uFF0C\u6062\u590D\u8FDE\u63A5\u540E\u518D\u6D4B\u8BD5\u3002") "h2",
{ id: "dim-delivery-bot-title", className: "dim-deliveryBotName" },
account.botName || "\u673A\u5668\u4EBA\u8BBE\u7F6E"
),
h2(
"a",
{
className: "dim-deliveryDocsLink",
href: isEnglish() ? DELIVERY_DOCS_URL.en : DELIVERY_DOCS_URL.zh,
target: "_blank",
rel: "noopener noreferrer",
"aria-label": "\u6253\u5F00\u4E3B\u52A8\u6295\u9012\u4F7F\u7528\u6587\u6863"
},
h2("span", null, "\u4F7F\u7528\u6587\u6863"),
h2("span", { "aria-hidden": "true" }, "\u2197")
)
), ),
h2(DeliveryButton, { h2(
kind: "primary", "div",
onClick: openSuggestionPicker, { className: "dim-deliveryBotId" },
disabled: Boolean(editor) || phase !== "ready", h2("span", null, "Bot ID"),
title: phase === "ready" ? void 0 : "\u8BF7\u5148\u5B8C\u6210\u6295\u9012\u76EE\u6807\u8BFB\u53D6" h2("code", { title: account.botId }, account.botId),
}, "\u65B0\u5EFA\u76EE\u6807") h2(DeliveryButton, { onClick: () => void copyBotId() }, "\u590D\u5236")
),
botCopyState ? h2("p", { className: "dim-targetFeedback", role: "status" }, botCopyState) : null
), ),
editor?.mode === "suggestions" ? h2(TargetSuggestionPicker, { h2(
definition, "section",
phase: suggestionPhase, { className: "dim-deliveryTargets", "aria-labelledby": "dim-delivery-targets-title" },
suggestions, h2(
error: suggestionError, "div",
targets, { className: "dim-deliverySectionHeading" },
onRefresh: loadSuggestions, h2(
onSelect: selectSuggestion, "div",
onManual: () => setEditor({ null,
mode: "create", h2("h3", { id: "dim-delivery-targets-title" }, "\u6295\u9012\u76EE\u6807"),
source: "manual", account.connected ? null : h2("p", null, "\u673A\u5668\u4EBA\u5F53\u524D\u79BB\u7EBF\uFF1B\u4ECD\u53EF\u914D\u7F6E\u76EE\u6807\uFF0C\u6062\u590D\u8FDE\u63A5\u540E\u518D\u6D4B\u8BD5\u3002")
draftKey: "manual", ),
initialValue: { targetId: randomTargetId(targets) } h2(DeliveryButton, {
}), kind: "primary",
onCancel: () => setEditor(null) onClick: openSuggestionPicker,
}) : editor ? h2(TargetForm, { disabled: Boolean(editor) || phase !== "ready",
key: editor.mode === "edit" ? editor.target.targetId : editor.draftKey, title: phase === "ready" ? void 0 : "\u8BF7\u5148\u5B8C\u6210\u6295\u9012\u76EE\u6807\u8BFB\u53D6"
definition, }, "\u65B0\u5EFA\u76EE\u6807")
mode: editor.mode, ),
initialValue: editor.mode === "edit" ? editor.target : editor.initialValue, editor?.mode === "suggestions" ? h2(TargetSuggestionPicker, {
source: editor.source, definition,
busy: saving, phase: suggestionPhase,
connected: account.connected, suggestions,
onCancel: () => setEditor(editor.source === "suggestion" ? { mode: "suggestions" } : null), error: suggestionError,
onSave: saveTarget, targets,
onTest: (target) => invoke(DELIVERY_ENDPOINTS.test, { onRefresh: loadSuggestions,
onSelect: selectSuggestion,
onManual: () => setEditor({
mode: "create",
source: "manual",
draftKey: "manual",
initialValue: { targetId: randomTargetId(targets) }
}),
onCancel: () => setEditor(null)
}) : editor ? h2(TargetForm, {
key: editor.mode === "edit" ? editor.target.targetId : editor.draftKey,
definition,
mode: editor.mode,
initialValue: editor.mode === "edit" ? editor.target : editor.initialValue,
source: editor.source,
busy: saving,
connected: account.connected,
onCancel: () => setEditor(editor.source === "suggestion" ? { mode: "suggestions" } : null),
onSave: saveTarget,
onTest: (target) => invoke(DELIVERY_ENDPOINTS.test, {
botId: account.botId,
target
})
}) : null,
phase === "loading" ? h2("div", { className: "dim-deliveryState", "aria-busy": "true" }, "\u6B63\u5728\u8BFB\u53D6\u6295\u9012\u76EE\u6807\u2026") : phase === "error" ? h2(
"div",
{ className: "dim-deliveryState", role: "alert" },
h2("p", null, error),
h2(DeliveryButton, { onClick: () => void loadTargets() }, "\u91CD\u65B0\u8BFB\u53D6")
) : targets.length === 0 ? h2(
"div",
{ className: "dim-deliveryState dim-deliveryEmpty" },
h2("strong", null, "\u5C1A\u672A\u914D\u7F6E\u6295\u9012\u76EE\u6807"),
h2("p", null, "\u70B9\u51FB\u201C\u65B0\u5EFA\u76EE\u6807\u201D\u53EF\u4ECE\u5DF2\u804A\u8FC7\u7684\u4F1A\u8BDD\u9009\u62E9\uFF0C\u4E5F\u53EF\u624B\u52A8\u586B\u5199\u3002")
) : h2("ul", { className: "dim-targetList" }, targets.map((target) => h2(TargetRow, {
key: target.targetId,
definition,
target,
botId: account.botId, botId: account.botId,
target connected: account.connected,
}) rpcCall: invoke,
}) : null, onChanged: () => loadTargets({ silent: true }),
phase === "loading" ? h2("div", { className: "dim-deliveryState", "aria-busy": "true" }, "\u6B63\u5728\u8BFB\u53D6\u6295\u9012\u76EE\u6807\u2026") : phase === "error" ? h2( onEdit: () => setEditor({ mode: "edit", target, source: "edit" })
"div", })))
{ className: "dim-deliveryState", role: "alert" }, )
h2("p", null, error),
h2(DeliveryButton, { onClick: () => void loadTargets() }, "\u91CD\u65B0\u8BFB\u53D6")
) : targets.length === 0 ? h2(
"div",
{ className: "dim-deliveryState dim-deliveryEmpty" },
h2("strong", null, "\u5C1A\u672A\u914D\u7F6E\u6295\u9012\u76EE\u6807"),
h2("p", null, "\u70B9\u51FB\u201C\u65B0\u5EFA\u76EE\u6807\u201D\u53EF\u4ECE\u5DF2\u804A\u8FC7\u7684\u4F1A\u8BDD\u9009\u62E9\uFF0C\u4E5F\u53EF\u624B\u52A8\u586B\u5199\u3002")
) : h2("ul", { className: "dim-targetList" }, targets.map((target) => h2(TargetRow, {
key: target.targetId,
definition,
target,
botId: account.botId,
connected: account.connected,
rpcCall: invoke,
onChanged: () => loadTargets({ silent: true }),
onEdit: () => setEditor({ mode: "edit", target, source: "edit" })
})))
) )
); );
} }
@ -13067,9 +13099,18 @@ var CSS11 = String.raw`
.dim-botSettingsButton:focus-visible { outline: 2px solid var(--dsw-alias-state-business-primary, #3370ff); outline-offset: 2px; } .dim-botSettingsButton:focus-visible { outline: 2px solid var(--dsw-alias-state-business-primary, #3370ff); outline-offset: 2px; }
.dim-botSettingsTooltip { position: absolute; top: calc(100% + 6px); right: 0; z-index: 30; width: max-content; padding: 6px 9px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 7px; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-layer-3, #fff); box-shadow: 0 8px 24px rgb(31 35 41 / 14%); font-size: 11px; line-height: 16px; opacity: 0; visibility: hidden; transform: translateY(-3px); pointer-events: none; transition: opacity .15s ease, transform .15s ease, visibility .15s ease; } .dim-botSettingsTooltip { position: absolute; top: calc(100% + 6px); right: 0; z-index: 30; width: max-content; padding: 6px 9px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 7px; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-layer-3, #fff); box-shadow: 0 8px 24px rgb(31 35 41 / 14%); font-size: 11px; line-height: 16px; opacity: 0; visibility: hidden; transform: translateY(-3px); pointer-events: none; transition: opacity .15s ease, transform .15s ease, visibility .15s ease; }
.dim-botSettingsAction:hover .dim-botSettingsTooltip, .dim-botSettingsAction:focus-within .dim-botSettingsTooltip { opacity: 1; visibility: visible; transform: translateY(0); } .dim-botSettingsAction:hover .dim-botSettingsTooltip, .dim-botSettingsAction:focus-within .dim-botSettingsTooltip { opacity: 1; visibility: visible; transform: translateY(0); }
.dim-deliveryPage { min-width: 0; display: grid; gap: 14px; } .dim-deliveryPage { min-width: 0; display: grid; }
.dim-deliveryHeader { min-width: 0; display: flex; align-items: center; justify-content: space-between; gap: 16px; } .dim-deliveryHeader { min-width: 0; display: flex; align-items: center; justify-content: space-between; gap: 16px; }
.dim-deliveryHeaderMeta { min-width: 0; display: flex; align-items: center; justify-content: flex-end; gap: 10px; text-align: right; } .dim-botSettingsTabsBar { min-width: 0; margin-top: 10px; border-bottom: 1px solid var(--dsw-alias-border-l2, #dfe1e5); }
.dim-botSettingsTabs { min-width: 0; display: flex; align-items: flex-end; gap: 24px; overflow-x: auto; scrollbar-width: none; }
.dim-botSettingsTabs::-webkit-scrollbar { display: none; }
.dim-botSettingsTab { position: relative; min-height: 38px; flex: none; display: inline-flex; align-items: center; justify-content: center; padding: 6px 2px 9px; border: 0; color: var(--dsw-alias-label-secondary, #646a73); background: transparent; font: inherit; font-size: 13px; line-height: 20px; font-weight: 560; white-space: nowrap; cursor: pointer; transition: color .15s ease; }
.dim-botSettingsTab::after { content: ''; position: absolute; right: 0; bottom: -1px; left: 0; height: 2px; border-radius: 2px 2px 0 0; background: transparent; transform: scaleX(.45); transition: background .15s ease, transform .15s ease; }
.dim-botSettingsTab:hover:not([aria-selected="true"]) { color: var(--dsw-alias-label-primary, #1f2329); }
.dim-botSettingsTab[aria-selected="true"] { color: var(--dsw-alias-state-business-primary, #3370ff); font-weight: 650; }
.dim-botSettingsTab[aria-selected="true"]::after { background: var(--dsw-alias-state-business-primary, #3370ff); transform: scaleX(1); }
.dim-botSettingsTab:focus-visible { outline: 2px solid var(--dsw-alias-state-business-primary, #3370ff); outline-offset: -2px; border-radius: 5px; }
.dim-botSettingsTabPanel { min-width: 0; display: grid; gap: 14px; padding-top: 14px; }
.dim-deliveryDocsLink { min-height: 30px; flex: none; display: inline-flex; align-items: center; gap: 4px; padding: 5px 8px; border-radius: 7px; color: var(--dsw-alias-state-business-primary, #3370ff); font-size: 12px; line-height: 18px; text-decoration: none; white-space: nowrap; } .dim-deliveryDocsLink { min-height: 30px; flex: none; display: inline-flex; align-items: center; gap: 4px; padding: 5px 8px; border-radius: 7px; color: var(--dsw-alias-state-business-primary, #3370ff); font-size: 12px; line-height: 18px; text-decoration: none; white-space: nowrap; }
.dim-deliveryDocsLink:hover { background: color-mix(in srgb, var(--dsw-alias-state-business-primary, #3370ff) 8%, transparent); } .dim-deliveryDocsLink:hover { background: color-mix(in srgb, var(--dsw-alias-state-business-primary, #3370ff) 8%, transparent); }
.dim-deliveryDocsLink:focus-visible { outline: 2px solid var(--dsw-alias-state-business-primary, #3370ff); outline-offset: 2px; } .dim-deliveryDocsLink:focus-visible { outline: 2px solid var(--dsw-alias-state-business-primary, #3370ff); outline-offset: 2px; }
@ -13081,6 +13122,7 @@ var CSS11 = String.raw`
.dim-deliveryButton[data-kind="danger"] { color: var(--dsw-alias-state-error-primary, #d54941); } .dim-deliveryButton[data-kind="danger"] { color: var(--dsw-alias-state-error-primary, #d54941); }
.dim-deliveryBack { flex: none; border-color: transparent; background: transparent; } .dim-deliveryBack { flex: none; border-color: transparent; background: transparent; }
.dim-deliveryIdentity, .dim-deliveryTargets { min-width: 0; padding: 16px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 12px; background: var(--dsw-alias-bg-layer-3, #fff); } .dim-deliveryIdentity, .dim-deliveryTargets { min-width: 0; padding: 16px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 12px; background: var(--dsw-alias-bg-layer-3, #fff); }
.dim-deliveryIdentityHeading { min-width: 0; display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.dim-deliveryBotName, .dim-deliverySectionHeading h3 { min-width: 0; overflow: hidden; margin: 0; font-size: 15px; line-height: 22px; font-weight: 650; text-overflow: ellipsis; white-space: nowrap; } .dim-deliveryBotName, .dim-deliverySectionHeading h3 { min-width: 0; overflow: hidden; margin: 0; font-size: 15px; line-height: 22px; font-weight: 650; text-overflow: ellipsis; white-space: nowrap; }
.dim-deliveryIdentity > div:first-child p, .dim-deliverySectionHeading p { margin: 2px 0 0; color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; line-height: 18px; } .dim-deliveryIdentity > div:first-child p, .dim-deliverySectionHeading p { margin: 2px 0 0; color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; line-height: 18px; }
.dim-deliveryBotId { min-width: 0; display: grid; grid-template-columns: max-content minmax(0, 1fr) max-content; align-items: center; gap: 10px; margin-top: 12px; padding: 10px 12px; border-radius: 9px; background: var(--dsw-alias-bg-module-platform, #f7f8fa); } .dim-deliveryBotId { min-width: 0; display: grid; grid-template-columns: max-content minmax(0, 1fr) max-content; align-items: center; gap: 10px; margin-top: 12px; padding: 10px 12px; border-radius: 9px; background: var(--dsw-alias-bg-module-platform, #f7f8fa); }
@ -13169,8 +13211,8 @@ var CSS11 = String.raw`
.dim-rail { grid-template-columns: minmax(0, 1fr); } .dim-rail { grid-template-columns: minmax(0, 1fr); }
.dim-loopbackRecovery { align-items: stretch; flex-direction: column; gap: 12px; } .dim-loopbackRecovery { align-items: stretch; flex-direction: column; gap: 12px; }
.dim-loopbackRecoveryAction { width: 100%; } .dim-loopbackRecoveryAction { width: 100%; }
.dim-deliveryHeader, .dim-deliverySectionHeading { align-items: stretch; flex-direction: column; } .dim-deliverySectionHeading { align-items: stretch; flex-direction: column; }
.dim-deliveryHeaderMeta { justify-content: space-between; text-align: left; } .dim-botSettingsTabs { gap: 18px; }
.dim-deliveryBotId { grid-template-columns: minmax(0, 1fr) max-content; } .dim-deliveryBotId { grid-template-columns: minmax(0, 1fr) max-content; }
.dim-deliveryBotId > span { grid-column: 1 / -1; } .dim-deliveryBotId > span { grid-column: 1 / -1; }
.dim-targetActions .dim-deliveryButton { flex: 1 1 auto; } .dim-targetActions .dim-deliveryButton { flex: 1 1 auto; }

File diff suppressed because one or more lines are too long

View file

@ -18,6 +18,10 @@ export const DELIVERY_ENDPOINTS = Object.freeze({
test: 'target.test', test: 'target.test',
}); });
export const BOT_SETTINGS_TABS = Object.freeze([
Object.freeze({ id: 'delivery', label: '投递设置' }),
]);
const CHANNEL_DEFINITIONS = Object.freeze({ const CHANNEL_DEFINITIONS = Object.freeze({
weixin: { weixin: {
label: '微信', label: '微信',
@ -654,13 +658,41 @@ export function DeliveryTargetSettingsPage({ channel, account, rpcCall, onBack }
} }
}; };
const deliveryTab = BOT_SETTINGS_TABS[0];
const deliveryTabId = `dim-bot-settings-${deliveryTab.id}-tab`;
const deliveryPanelId = `dim-bot-settings-${deliveryTab.id}-panel`;
return h('section', { return h('section', {
className: 'dim-deliveryPage', className: 'dim-deliveryPage',
'aria-label': `${account.botName || definition.label}机器人设置`, 'aria-label': `${account.botName || definition.label}机器人设置`,
}, },
h('header', { className: 'dim-deliveryHeader' }, h('header', { className: 'dim-deliveryHeader' },
h(DeliveryButton, { className: 'dim-deliveryBack', onClick: onBack }, '← 返回机器人列表'), h(DeliveryButton, { className: 'dim-deliveryBack', onClick: onBack }, '← 返回机器人列表')),
h('div', { className: 'dim-deliveryHeaderMeta' }, h('div', { className: 'dim-botSettingsTabsBar' },
h('nav', {
className: 'dim-botSettingsTabs',
role: 'tablist',
'aria-label': '机器人设置页签',
}, BOT_SETTINGS_TABS.map((tab) => h('button', {
key: tab.id,
id: `dim-bot-settings-${tab.id}-tab`,
type: 'button',
role: 'tab',
className: 'dim-botSettingsTab',
'aria-selected': tab.id === deliveryTab.id,
'aria-controls': `dim-bot-settings-${tab.id}-panel`,
tabIndex: tab.id === deliveryTab.id ? 0 : -1,
}, tab.label)))),
h('div', {
id: deliveryPanelId,
className: 'dim-botSettingsTabPanel',
role: 'tabpanel',
'aria-labelledby': deliveryTabId,
},
h('section', { className: 'dim-deliveryIdentity', 'aria-labelledby': 'dim-delivery-bot-title' },
h('div', { className: 'dim-deliveryIdentityHeading' },
h('h2', { id: 'dim-delivery-bot-title', className: 'dim-deliveryBotName' },
account.botName || '机器人设置'),
h('a', { h('a', {
className: 'dim-deliveryDocsLink', className: 'dim-deliveryDocsLink',
href: isEnglish() ? DELIVERY_DOCS_URL.en : DELIVERY_DOCS_URL.zh, href: isEnglish() ? DELIVERY_DOCS_URL.en : DELIVERY_DOCS_URL.zh,
@ -669,11 +701,7 @@ export function DeliveryTargetSettingsPage({ channel, account, rpcCall, onBack }
'aria-label': '打开主动投递使用文档', 'aria-label': '打开主动投递使用文档',
}, },
h('span', null, '使用文档'), h('span', null, '使用文档'),
h('span', { 'aria-hidden': 'true' }, '↗')))), h('span', { 'aria-hidden': 'true' }, '↗'))),
h('section', { className: 'dim-deliveryIdentity', 'aria-labelledby': 'dim-delivery-bot-title' },
h('div', null,
h('h2', { id: 'dim-delivery-bot-title', className: 'dim-deliveryBotName' },
account.botName || '机器人设置')),
h('div', { className: 'dim-deliveryBotId' }, h('div', { className: 'dim-deliveryBotId' },
h('span', null, 'Bot ID'), h('span', null, 'Bot ID'),
h('code', { title: account.botId }, account.botId), h('code', { title: account.botId }, account.botId),
@ -745,5 +773,5 @@ export function DeliveryTargetSettingsPage({ channel, account, rpcCall, onBack }
rpcCall: invoke, rpcCall: invoke,
onChanged: () => loadTargets({ silent: true }), onChanged: () => loadTargets({ silent: true }),
onEdit: () => setEditor({ mode: 'edit', target, source: 'edit' }), onEdit: () => setEditor({ mode: 'edit', target, source: 'edit' }),
}))))); }))))));
} }

View file

@ -7,6 +7,9 @@ const EN = Object.freeze({
'IM机器人': 'IM bots', 'IM机器人': 'IM bots',
'IM机器人设置': 'IM bot settings', 'IM机器人设置': 'IM bot settings',
'更多机器人设置': 'More bot settings', '更多机器人设置': 'More bot settings',
'机器人设置': 'Bot settings',
'机器人设置页签': 'Bot settings tabs',
'投递设置': 'Delivery settings',
'IM 渠道': 'IM channels', 'IM 渠道': 'IM channels',
'让 DeepSeek Harness 触手可及': 'Connecting DeepSeek Harness', '让 DeepSeek Harness 触手可及': 'Connecting DeepSeek Harness',
'当前版本': 'Current version', '当前版本': 'Current version',

View file

@ -369,9 +369,18 @@ const CSS = String.raw`
.dim-botSettingsButton:focus-visible { outline: 2px solid var(--dsw-alias-state-business-primary, #3370ff); outline-offset: 2px; } .dim-botSettingsButton:focus-visible { outline: 2px solid var(--dsw-alias-state-business-primary, #3370ff); outline-offset: 2px; }
.dim-botSettingsTooltip { position: absolute; top: calc(100% + 6px); right: 0; z-index: 30; width: max-content; padding: 6px 9px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 7px; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-layer-3, #fff); box-shadow: 0 8px 24px rgb(31 35 41 / 14%); font-size: 11px; line-height: 16px; opacity: 0; visibility: hidden; transform: translateY(-3px); pointer-events: none; transition: opacity .15s ease, transform .15s ease, visibility .15s ease; } .dim-botSettingsTooltip { position: absolute; top: calc(100% + 6px); right: 0; z-index: 30; width: max-content; padding: 6px 9px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 7px; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-layer-3, #fff); box-shadow: 0 8px 24px rgb(31 35 41 / 14%); font-size: 11px; line-height: 16px; opacity: 0; visibility: hidden; transform: translateY(-3px); pointer-events: none; transition: opacity .15s ease, transform .15s ease, visibility .15s ease; }
.dim-botSettingsAction:hover .dim-botSettingsTooltip, .dim-botSettingsAction:focus-within .dim-botSettingsTooltip { opacity: 1; visibility: visible; transform: translateY(0); } .dim-botSettingsAction:hover .dim-botSettingsTooltip, .dim-botSettingsAction:focus-within .dim-botSettingsTooltip { opacity: 1; visibility: visible; transform: translateY(0); }
.dim-deliveryPage { min-width: 0; display: grid; gap: 14px; } .dim-deliveryPage { min-width: 0; display: grid; }
.dim-deliveryHeader { min-width: 0; display: flex; align-items: center; justify-content: space-between; gap: 16px; } .dim-deliveryHeader { min-width: 0; display: flex; align-items: center; justify-content: space-between; gap: 16px; }
.dim-deliveryHeaderMeta { min-width: 0; display: flex; align-items: center; justify-content: flex-end; gap: 10px; text-align: right; } .dim-botSettingsTabsBar { min-width: 0; margin-top: 10px; border-bottom: 1px solid var(--dsw-alias-border-l2, #dfe1e5); }
.dim-botSettingsTabs { min-width: 0; display: flex; align-items: flex-end; gap: 24px; overflow-x: auto; scrollbar-width: none; }
.dim-botSettingsTabs::-webkit-scrollbar { display: none; }
.dim-botSettingsTab { position: relative; min-height: 38px; flex: none; display: inline-flex; align-items: center; justify-content: center; padding: 6px 2px 9px; border: 0; color: var(--dsw-alias-label-secondary, #646a73); background: transparent; font: inherit; font-size: 13px; line-height: 20px; font-weight: 560; white-space: nowrap; cursor: pointer; transition: color .15s ease; }
.dim-botSettingsTab::after { content: ''; position: absolute; right: 0; bottom: -1px; left: 0; height: 2px; border-radius: 2px 2px 0 0; background: transparent; transform: scaleX(.45); transition: background .15s ease, transform .15s ease; }
.dim-botSettingsTab:hover:not([aria-selected="true"]) { color: var(--dsw-alias-label-primary, #1f2329); }
.dim-botSettingsTab[aria-selected="true"] { color: var(--dsw-alias-state-business-primary, #3370ff); font-weight: 650; }
.dim-botSettingsTab[aria-selected="true"]::after { background: var(--dsw-alias-state-business-primary, #3370ff); transform: scaleX(1); }
.dim-botSettingsTab:focus-visible { outline: 2px solid var(--dsw-alias-state-business-primary, #3370ff); outline-offset: -2px; border-radius: 5px; }
.dim-botSettingsTabPanel { min-width: 0; display: grid; gap: 14px; padding-top: 14px; }
.dim-deliveryDocsLink { min-height: 30px; flex: none; display: inline-flex; align-items: center; gap: 4px; padding: 5px 8px; border-radius: 7px; color: var(--dsw-alias-state-business-primary, #3370ff); font-size: 12px; line-height: 18px; text-decoration: none; white-space: nowrap; } .dim-deliveryDocsLink { min-height: 30px; flex: none; display: inline-flex; align-items: center; gap: 4px; padding: 5px 8px; border-radius: 7px; color: var(--dsw-alias-state-business-primary, #3370ff); font-size: 12px; line-height: 18px; text-decoration: none; white-space: nowrap; }
.dim-deliveryDocsLink:hover { background: color-mix(in srgb, var(--dsw-alias-state-business-primary, #3370ff) 8%, transparent); } .dim-deliveryDocsLink:hover { background: color-mix(in srgb, var(--dsw-alias-state-business-primary, #3370ff) 8%, transparent); }
.dim-deliveryDocsLink:focus-visible { outline: 2px solid var(--dsw-alias-state-business-primary, #3370ff); outline-offset: 2px; } .dim-deliveryDocsLink:focus-visible { outline: 2px solid var(--dsw-alias-state-business-primary, #3370ff); outline-offset: 2px; }
@ -383,6 +392,7 @@ const CSS = String.raw`
.dim-deliveryButton[data-kind="danger"] { color: var(--dsw-alias-state-error-primary, #d54941); } .dim-deliveryButton[data-kind="danger"] { color: var(--dsw-alias-state-error-primary, #d54941); }
.dim-deliveryBack { flex: none; border-color: transparent; background: transparent; } .dim-deliveryBack { flex: none; border-color: transparent; background: transparent; }
.dim-deliveryIdentity, .dim-deliveryTargets { min-width: 0; padding: 16px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 12px; background: var(--dsw-alias-bg-layer-3, #fff); } .dim-deliveryIdentity, .dim-deliveryTargets { min-width: 0; padding: 16px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 12px; background: var(--dsw-alias-bg-layer-3, #fff); }
.dim-deliveryIdentityHeading { min-width: 0; display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.dim-deliveryBotName, .dim-deliverySectionHeading h3 { min-width: 0; overflow: hidden; margin: 0; font-size: 15px; line-height: 22px; font-weight: 650; text-overflow: ellipsis; white-space: nowrap; } .dim-deliveryBotName, .dim-deliverySectionHeading h3 { min-width: 0; overflow: hidden; margin: 0; font-size: 15px; line-height: 22px; font-weight: 650; text-overflow: ellipsis; white-space: nowrap; }
.dim-deliveryIdentity > div:first-child p, .dim-deliverySectionHeading p { margin: 2px 0 0; color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; line-height: 18px; } .dim-deliveryIdentity > div:first-child p, .dim-deliverySectionHeading p { margin: 2px 0 0; color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; line-height: 18px; }
.dim-deliveryBotId { min-width: 0; display: grid; grid-template-columns: max-content minmax(0, 1fr) max-content; align-items: center; gap: 10px; margin-top: 12px; padding: 10px 12px; border-radius: 9px; background: var(--dsw-alias-bg-module-platform, #f7f8fa); } .dim-deliveryBotId { min-width: 0; display: grid; grid-template-columns: max-content minmax(0, 1fr) max-content; align-items: center; gap: 10px; margin-top: 12px; padding: 10px 12px; border-radius: 9px; background: var(--dsw-alias-bg-module-platform, #f7f8fa); }
@ -471,8 +481,8 @@ const CSS = String.raw`
.dim-rail { grid-template-columns: minmax(0, 1fr); } .dim-rail { grid-template-columns: minmax(0, 1fr); }
.dim-loopbackRecovery { align-items: stretch; flex-direction: column; gap: 12px; } .dim-loopbackRecovery { align-items: stretch; flex-direction: column; gap: 12px; }
.dim-loopbackRecoveryAction { width: 100%; } .dim-loopbackRecoveryAction { width: 100%; }
.dim-deliveryHeader, .dim-deliverySectionHeading { align-items: stretch; flex-direction: column; } .dim-deliverySectionHeading { align-items: stretch; flex-direction: column; }
.dim-deliveryHeaderMeta { justify-content: space-between; text-align: left; } .dim-botSettingsTabs { gap: 18px; }
.dim-deliveryBotId { grid-template-columns: minmax(0, 1fr) max-content; } .dim-deliveryBotId { grid-template-columns: minmax(0, 1fr) max-content; }
.dim-deliveryBotId > span { grid-column: 1 / -1; } .dim-deliveryBotId > span { grid-column: 1 / -1; }
.dim-targetActions .dim-deliveryButton { flex: 1 1 auto; } .dim-targetActions .dim-deliveryButton { flex: 1 1 auto; }

View file

@ -6,6 +6,7 @@ import { renderToStaticMarkup } from 'react-dom/server';
import TestRenderer from 'react-test-renderer'; import TestRenderer from 'react-test-renderer';
import { import {
BOT_SETTINGS_TABS,
DELIVERY_CHANNEL_DEFINITIONS, DELIVERY_CHANNEL_DEFINITIONS,
DELIVERY_ENDPOINTS, DELIVERY_ENDPOINTS,
DELIVERY_RPC_CHANNEL, DELIVERY_RPC_CHANNEL,
@ -71,6 +72,7 @@ const connectedAccount = Object.freeze({
}); });
test('delivery settings define only the nine supported IM channel routes', () => { test('delivery settings define only the nine supported IM channel routes', () => {
assert.deepEqual(BOT_SETTINGS_TABS, [{ id: 'delivery', label: '投递设置' }]);
assert.equal(DELIVERY_RPC_CHANNEL, '/dsh-im-delivery'); assert.equal(DELIVERY_RPC_CHANNEL, '/dsh-im-delivery');
assert.deepEqual(Object.keys(DELIVERY_CHANNEL_DEFINITIONS), [ assert.deepEqual(Object.keys(DELIVERY_CHANNEL_DEFINITIONS), [
'weixin', 'feishu', 'dingtalk', 'wecom', 'qq', 'weixin', 'feishu', 'dingtalk', 'wecom', 'qq',
@ -183,19 +185,26 @@ test('the card gear opens a bot-scoped page in the current channel panel and ret
const page = renderer.root.findByProps({ className: 'dim-deliveryPage' }); const page = renderer.root.findByProps({ className: 'dim-deliveryPage' });
assert.match(textOf(page), /微信通知助手/); assert.match(textOf(page), /微信通知助手/);
assert.doesNotMatch(textOf(page), /调用标识/); assert.doesNotMatch(textOf(page), /调用标识/);
assert.equal(
page.findByProps({ className: 'dim-deliveryHeader' }).findAllByType('span').length,
2,
);
assert.equal( assert.equal(
page.findByProps({ className: 'dim-deliveryHeader' }).findAllByType('h2').length, page.findByProps({ className: 'dim-deliveryHeader' }).findAllByType('h2').length,
0, 0,
); );
const settingsTab = page.findByProps({ role: 'tab' });
const settingsPanel = page.findByProps({ role: 'tabpanel' });
assert.equal(textOf(settingsTab), '投递设置');
assert.equal(settingsTab.props['aria-selected'], true);
assert.equal(settingsTab.props.tabIndex, 0);
assert.equal(settingsTab.props['aria-controls'], settingsPanel.props.id);
assert.equal(settingsPanel.props['aria-labelledby'], settingsTab.props.id);
const identity = settingsPanel.findByProps({ className: 'dim-deliveryIdentity' });
assert.ok(identity);
assert.ok(settingsPanel.findByProps({ className: 'dim-deliveryTargets' }));
assert.equal(page.findByProps({ className: 'dim-botSettingsTabsBar' }).findAllByType('a').length, 0);
assert.equal( assert.equal(
textOf(page.findByProps({ className: 'dim-deliveryIdentity' }).findByType('h2')), textOf(identity.findByType('h2')),
'微信通知助手', '微信通知助手',
); );
const docsLink = page.findByProps({ className: 'dim-deliveryDocsLink' }); const docsLink = identity.findByProps({ className: 'dim-deliveryDocsLink' });
assert.equal(textOf(docsLink), '使用文档↗'); assert.equal(textOf(docsLink), '使用文档↗');
assert.equal( assert.equal(
docsLink.props.href, docsLink.props.href,
@ -471,6 +480,7 @@ test('recent conversation names remain platform data in the English UI', async (
docsLink.props.href, docsLink.props.href,
'https://github.com/xmanrui/dsh-im/blob/main/PROACTIVE_DELIVERY.en.md', 'https://github.com/xmanrui/dsh-im/blob/main/PROACTIVE_DELIVERY.en.md',
); );
assert.equal(textOf(renderer.root.findByProps({ role: 'tab' })), 'Delivery settings');
}); });
test('target create, edit, copy, and delete use the minimal RPC payloads', async (t) => { test('target create, edit, copy, and delete use the minimal RPC payloads', async (t) => {