fix(ui): unify channel card feedback layout

This commit is contained in:
xmanrui 2026-08-22 18:27:19 +08:00
parent 9a5a7941af
commit eadfc7372f
12 changed files with 315 additions and 209 deletions

View file

@ -2142,24 +2142,28 @@ function AccountCard({
h2( h2(
"div", "div",
{ className: "ddt-accountFooter dim-cardFooter" }, { className: "ddt-accountFooter dim-cardFooter" },
summary ? h2("div", { className: "ddt-summary dim-cardSummary" }, summary) : null,
feedback ? h2("div", {
className: "ddt-summary dim-cardSummary",
role: "status"
}, feedback) : null,
h2( h2(
"div", "div",
{ className: "ddt-actions dim-cardActions" }, { className: "dim-cardFooterLayout" },
h2( h2(
Button, "div",
{ className: "dim-cardAction", onClick: onReconnect, disabled: Boolean(busy) }, { className: "ddt-actions dim-cardActions" },
busy === "reconnect" ? "\u68C0\u67E5\u4E2D\u2026" : account.connected ? "\u68C0\u67E5\u8FDE\u63A5" : "\u91CD\u8BD5\u8FDE\u63A5" h2(
Button,
{ className: "dim-cardAction", onClick: onReconnect, disabled: Boolean(busy) },
busy === "reconnect" ? "\u68C0\u67E5\u4E2D\u2026" : account.connected ? "\u68C0\u67E5\u8FDE\u63A5" : "\u91CD\u8BD5\u8FDE\u63A5"
),
h2(
Button,
{ className: "dim-cardAction", kind: "danger", onClick: onRequestRemove, disabled: Boolean(busy) },
"\u79FB\u9664\u63A5\u5165"
)
), ),
h2( summary ? h2("div", { className: "ddt-summary dim-cardSummary" }, summary) : null,
Button, feedback ? h2("div", {
{ className: "dim-cardAction", kind: "danger", onClick: onRequestRemove, disabled: Boolean(busy) }, className: "ddt-summary dim-cardFeedback",
"\u79FB\u9664\u63A5\u5165" role: "status"
) }, feedback) : null
) )
) )
), ),
@ -2953,22 +2957,29 @@ function createTokenChannelSettings(definition) {
h2( h2(
"div", "div",
{ className: "ddt-accountFooter dim-cardFooter" }, { className: "ddt-accountFooter dim-cardFooter" },
summary ? h2("div", { className: "ddt-summary dim-cardSummary" }, summary) : null,
testNotice ? h2("div", { className: "ddt-summary dim-cardSummary", role: "status" }, testNotice) : null,
h2( h2(
"div", "div",
{ className: "ddt-actions dim-cardActions" }, { className: "dim-cardFooterLayout" },
h2(Button3, { h2(
className: "dim-cardAction", "div",
onClick: onReconnect, { className: "ddt-actions dim-cardActions" },
disabled: Boolean(busy) h2(Button3, {
}, busy === "reconnect" ? "\u68C0\u67E5\u4E2D\u2026" : account.connected ? "\u68C0\u67E5\u8FDE\u63A5" : "\u91CD\u8BD5\u8FDE\u63A5"), className: "dim-cardAction",
h2(Button3, { onClick: onReconnect,
className: "dim-cardAction", disabled: Boolean(busy)
kind: "danger", }, busy === "reconnect" ? "\u68C0\u67E5\u4E2D\u2026" : account.connected ? "\u68C0\u67E5\u8FDE\u63A5" : "\u91CD\u8BD5\u8FDE\u63A5"),
onClick: onRequestRemove, h2(Button3, {
disabled: Boolean(busy) className: "dim-cardAction",
}, "\u79FB\u9664\u63A5\u5165") kind: "danger",
onClick: onRequestRemove,
disabled: Boolean(busy)
}, "\u79FB\u9664\u63A5\u5165")
),
summary ? h2("div", { className: "ddt-summary dim-cardSummary" }, summary) : null,
testNotice ? h2("div", {
className: "ddt-summary dim-cardFeedback",
role: "status"
}, testNotice) : null
) )
) )
), ),
@ -4808,40 +4819,44 @@ function BotCard({
h2( h2(
"div", "div",
{ className: "bxf-connectedFooter dim-cardFooter" }, { className: "bxf-connectedFooter dim-cardFooter" },
summary ? h2(
"div",
{ className: "bxf-healthSummary dim-cardSummary", "data-error": actionError || connection.error ? "true" : void 0 },
summary
) : null,
testNotice ? h2("div", {
className: "bxf-healthSummary dim-cardSummary",
role: "status"
}, testNotice) : null,
h2( h2(
"div", "div",
{ className: "bxf-actions bxf-botActions dim-cardActions" }, { className: "dim-cardFooterLayout" },
h2(Button5, { h2(
className: "dim-cardAction", "div",
onClick: onReconnect, { className: "bxf-actions bxf-botActions dim-cardActions" },
disabled: Boolean(busy), h2(Button5, {
"aria-busy": busy === "reconnect" ? "true" : void 0, className: "dim-cardAction",
"aria-label": `${connected ? "\u68C0\u67E5\u8FDE\u63A5" : "\u91CD\u8BD5\u8FDE\u63A5"}${bot.name}` onClick: onReconnect,
}, busy === "reconnect" ? connected ? "\u68C0\u67E5\u4E2D\u2026" : "\u6B63\u5728\u8FDE\u63A5\u2026" : connected ? "\u68C0\u67E5\u8FDE\u63A5" : "\u91CD\u8BD5\u8FDE\u63A5"), disabled: Boolean(busy),
h2(Button5, { "aria-busy": busy === "reconnect" ? "true" : void 0,
className: "bxf-repairButton dim-cardAction", "aria-label": `${connected ? "\u68C0\u67E5\u8FDE\u63A5" : "\u91CD\u8BD5\u8FDE\u63A5"}${bot.name}`
onClick: onRepairCallback, }, busy === "reconnect" ? connected ? "\u68C0\u67E5\u4E2D\u2026" : "\u6B63\u5728\u8FDE\u63A5\u2026" : connected ? "\u68C0\u67E5\u8FDE\u63A5" : "\u91CD\u8BD5\u8FDE\u63A5"),
disabled: Boolean(busy) || repairDisabled, h2(Button5, {
"aria-busy": busy === "callback-repair" ? "true" : void 0, className: "bxf-repairButton dim-cardAction",
"aria-label": `\u4FEE\u590D${bot.name}\u7684\u5361\u7247\u6309\u94AE` onClick: onRepairCallback,
}, busy === "callback-repair" ? "\u7B49\u5F85\u626B\u7801\u2026" : "\u4FEE\u590D\u5361\u7247\u6309\u94AE"), disabled: Boolean(busy) || repairDisabled,
h2(Button5, { "aria-busy": busy === "callback-repair" ? "true" : void 0,
className: "dim-cardAction", "aria-label": `\u4FEE\u590D${bot.name}\u7684\u5361\u7247\u6309\u94AE`
kind: "danger", }, busy === "callback-repair" ? "\u7B49\u5F85\u626B\u7801\u2026" : "\u4FEE\u590D\u5361\u7247\u6309\u94AE"),
onClick: onRequestRemove, h2(Button5, {
disabled: Boolean(busy), className: "dim-cardAction",
ref: removeButtonRef, kind: "danger",
"aria-label": `\u4ECE DeepSeek Harness \u79FB\u9664${bot.name}` onClick: onRequestRemove,
}, "\u79FB\u9664\u63A5\u5165") disabled: Boolean(busy),
ref: removeButtonRef,
"aria-label": `\u4ECE DeepSeek Harness \u79FB\u9664${bot.name}`
}, "\u79FB\u9664\u63A5\u5165")
),
summary ? h2(
"div",
{ className: "bxf-healthSummary dim-cardSummary", "data-error": actionError || connection.error ? "true" : void 0 },
summary
) : null,
testNotice ? h2("div", {
className: "bxf-healthSummary dim-cardFeedback",
role: "status"
}, testNotice) : null
) )
) )
), ),
@ -6049,17 +6064,21 @@ function AccountCard2({
h2( h2(
"div", "div",
{ className: "ddt-accountFooter dim-cardFooter" }, { className: "ddt-accountFooter dim-cardFooter" },
summary ? h2("div", { className: "ddt-summary dim-cardSummary" }, summary) : null,
feedback ? h2("div", {
className: "ddt-summary dim-cardSummary",
role: "status",
"aria-live": "polite"
}, feedback) : null,
h2( h2(
"div", "div",
{ className: "ddt-actions dim-cardActions" }, { className: "dim-cardFooterLayout" },
h2(Button7, { className: "dim-cardAction", onClick: onReconnect, disabled: Boolean(busy) }, busy === "reconnect" ? "\u68C0\u67E5\u4E2D\u2026" : account.connected ? "\u68C0\u67E5\u8FDE\u63A5" : "\u91CD\u8BD5\u8FDE\u63A5"), h2(
h2(Button7, { className: "dim-cardAction", kind: "danger", onClick: onRequestRemove, disabled: Boolean(busy) }, "\u79FB\u9664\u63A5\u5165") "div",
{ className: "ddt-actions dim-cardActions" },
h2(Button7, { className: "dim-cardAction", onClick: onReconnect, disabled: Boolean(busy) }, busy === "reconnect" ? "\u68C0\u67E5\u4E2D\u2026" : account.connected ? "\u68C0\u67E5\u8FDE\u63A5" : "\u91CD\u8BD5\u8FDE\u63A5"),
h2(Button7, { className: "dim-cardAction", kind: "danger", onClick: onRequestRemove, disabled: Boolean(busy) }, "\u79FB\u9664\u63A5\u5165")
),
summary ? h2("div", { className: "ddt-summary dim-cardSummary" }, summary) : null,
feedback ? h2("div", {
className: "ddt-summary dim-cardFeedback",
role: "status",
"aria-live": "polite"
}, feedback) : null
) )
) )
), ),
@ -7588,17 +7607,21 @@ function AccountCard3({
h2( h2(
"div", "div",
{ className: "ddt-accountFooter dim-cardFooter" }, { className: "ddt-accountFooter dim-cardFooter" },
summary ? h2("div", { className: "ddt-summary dim-cardSummary" }, summary) : null,
feedback ? h2("div", {
className: "ddt-summary dim-cardSummary",
role: "status",
"aria-live": "polite"
}, feedback) : null,
h2( h2(
"div", "div",
{ className: "ddt-actions dim-cardActions" }, { className: "dim-cardFooterLayout" },
h2(Button10, { className: "dim-cardAction", onClick: onReconnect, disabled: Boolean(busy) }, busy === "reconnect" ? "\u68C0\u67E5\u4E2D\u2026" : account.connected ? "\u68C0\u67E5\u8FDE\u63A5" : "\u91CD\u8BD5\u8FDE\u63A5"), h2(
h2(Button10, { className: "dim-cardAction", kind: "danger", onClick: onRequestRemove, disabled: Boolean(busy) }, "\u79FB\u9664\u63A5\u5165") "div",
{ className: "ddt-actions dim-cardActions" },
h2(Button10, { className: "dim-cardAction", onClick: onReconnect, disabled: Boolean(busy) }, busy === "reconnect" ? "\u68C0\u67E5\u4E2D\u2026" : account.connected ? "\u68C0\u67E5\u8FDE\u63A5" : "\u91CD\u8BD5\u8FDE\u63A5"),
h2(Button10, { className: "dim-cardAction", kind: "danger", onClick: onRequestRemove, disabled: Boolean(busy) }, "\u79FB\u9664\u63A5\u5165")
),
summary ? h2("div", { className: "ddt-summary dim-cardSummary" }, summary) : null,
feedback ? h2("div", {
className: "ddt-summary dim-cardFeedback",
role: "status",
"aria-live": "polite"
}, feedback) : null
) )
) )
), ),
@ -8543,25 +8566,29 @@ function AccountCard4({
h2( h2(
"div", "div",
{ className: "dxw-accountFooter dim-cardFooter" }, { className: "dxw-accountFooter dim-cardFooter" },
summary ? h2("div", { className: "dxw-summary dim-cardSummary" }, summary) : null,
account.lastMessageError ? h2("div", {
className: "dxw-summary dim-cardSummary",
role: "status"
}, `\u6700\u8FD1\u4E00\u6761\u6D88\u606F\u5904\u7406\u5931\u8D25\uFF1A${account.lastMessageError.message}`) : null,
feedback ? h2("div", {
className: "dxw-summary dim-cardSummary",
role: "status",
"aria-live": "polite"
}, feedback) : null,
h2( h2(
"div", "div",
{ className: "dxw-actions dim-cardActions" }, { className: "dim-cardFooterLayout" },
h2( h2(
Button12, "div",
{ className: "dim-cardAction", onClick: onReconnect, disabled: Boolean(busy) }, { className: "dxw-actions dim-cardActions" },
busy === "reconnect" ? "\u68C0\u67E5\u4E2D\u2026" : account.connected ? "\u68C0\u67E5\u8FDE\u63A5" : "\u91CD\u8BD5\u8FDE\u63A5" h2(
Button12,
{ className: "dim-cardAction", onClick: onReconnect, disabled: Boolean(busy) },
busy === "reconnect" ? "\u68C0\u67E5\u4E2D\u2026" : account.connected ? "\u68C0\u67E5\u8FDE\u63A5" : "\u91CD\u8BD5\u8FDE\u63A5"
),
h2(Button12, { className: "dim-cardAction", kind: "danger", onClick: onRequestRemove, disabled: Boolean(busy) }, "\u79FB\u9664\u63A5\u5165")
), ),
h2(Button12, { className: "dim-cardAction", kind: "danger", onClick: onRequestRemove, disabled: Boolean(busy) }, "\u79FB\u9664\u63A5\u5165") summary ? h2("div", { className: "dxw-summary dim-cardSummary" }, summary) : null,
account.lastMessageError ? h2("div", {
className: "dxw-summary dim-cardSummary",
role: "status"
}, `\u6700\u8FD1\u4E00\u6761\u6D88\u606F\u5904\u7406\u5931\u8D25\uFF1A${account.lastMessageError.message}`) : null,
feedback ? h2("div", {
className: "dxw-summary dim-cardFeedback",
role: "status",
"aria-live": "polite"
}, feedback) : null
) )
) )
), ),
@ -9459,25 +9486,29 @@ function WhatsappAccountCard({
h2( h2(
"div", "div",
{ className: "ddt-accountFooter dim-cardFooter" }, { className: "ddt-accountFooter dim-cardFooter" },
summary ? h2("div", { className: "ddt-summary dim-cardSummary" }, summary) : null,
testNotice ? h2("div", {
className: "ddt-summary dim-cardSummary",
role: "status"
}, testNotice) : null,
h2( h2(
"div", "div",
{ className: "ddt-actions dim-cardActions" }, { className: "dim-cardFooterLayout" },
h2(Button14, { h2(
className: "dim-cardAction", "div",
onClick: onReconnect, { className: "ddt-actions dim-cardActions" },
disabled: Boolean(busy) h2(Button14, {
}, busy === "reconnect" ? "\u68C0\u67E5\u4E2D\u2026" : account.connected ? "\u68C0\u67E5\u8FDE\u63A5" : "\u91CD\u8BD5\u8FDE\u63A5"), className: "dim-cardAction",
h2(Button14, { onClick: onReconnect,
className: "dim-cardAction", disabled: Boolean(busy)
kind: "danger", }, busy === "reconnect" ? "\u68C0\u67E5\u4E2D\u2026" : account.connected ? "\u68C0\u67E5\u8FDE\u63A5" : "\u91CD\u8BD5\u8FDE\u63A5"),
onClick: onRequestRemove, h2(Button14, {
disabled: Boolean(busy) className: "dim-cardAction",
}, "\u79FB\u9664\u63A5\u5165") kind: "danger",
onClick: onRequestRemove,
disabled: Boolean(busy)
}, "\u79FB\u9664\u63A5\u5165")
),
summary ? h2("div", { className: "ddt-summary dim-cardSummary" }, summary) : null,
testNotice ? h2("div", {
className: "ddt-summary dim-cardFeedback",
role: "status"
}, testNotice) : null
) )
) )
), ),
@ -9968,6 +9999,9 @@ var CSS11 = String.raw`
.dim-directoryPickerActions button:hover:not(:disabled) { filter: brightness(.97); } .dim-directoryPickerActions button:hover:not(:disabled) { filter: brightness(.97); }
.dim-directoryPickerActions button:disabled { cursor: not-allowed; opacity: .52; } .dim-directoryPickerActions button:disabled { cursor: not-allowed; opacity: .52; }
.dim-panel .dim-cardSummary { min-width: 0; color: var(--dsw-alias-label-secondary, #646a73); font: inherit; font-size: 12px; font-weight: 400; line-height: normal; } .dim-panel .dim-cardSummary { min-width: 0; color: var(--dsw-alias-label-secondary, #646a73); font: inherit; font-size: 12px; font-weight: 400; line-height: normal; }
.dim-panel .dim-cardFooterLayout { min-width: 0; width: 100%; display: flex; flex-direction: column; align-items: stretch; gap: 9px; }
.dim-panel .dim-cardFooterLayout > .dim-cardActions { align-self: flex-end; }
.dim-panel .dim-cardFeedback { width: 100%; padding: 8px 10px; border-radius: 8px; color: var(--dsw-alias-label-secondary, #646a73); background: var(--dsw-alias-bg-module-platform, #f7f8fa); font: inherit; font-size: 12px; font-weight: 400; line-height: 18px; overflow-wrap: anywhere; white-space: normal; }
.dim-panel .dim-cardActions { flex: none; display: flex; align-items: center; flex-wrap: nowrap; gap: 8px; margin: 0 0 0 auto; } .dim-panel .dim-cardActions { flex: none; display: flex; align-items: center; flex-wrap: nowrap; gap: 8px; margin: 0 0 0 auto; }
.dim-panel .dim-cardActions .dim-cardAction { flex: none; min-height: 32px; padding: 0 11px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 8px; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-layer-1, #fff); font: inherit; font-size: 13px; font-weight: 560; line-height: normal; white-space: nowrap; } .dim-panel .dim-cardActions .dim-cardAction { flex: none; min-height: 32px; padding: 0 11px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 8px; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-layer-1, #fff); font: inherit; font-size: 13px; font-weight: 560; line-height: normal; white-space: nowrap; }
.dim-panel .dim-cardActions .dim-cardAction:hover:not(:disabled) { border-color: #aeb3bb; background: var(--dsw-alias-interactive-bg-hover, #f7f8fa); } .dim-panel .dim-cardActions .dim-cardAction:hover:not(:disabled) { border-color: #aeb3bb; background: var(--dsw-alias-interactive-bg-hover, #f7f8fa); }

View file

@ -253,16 +253,17 @@ export function AccountCard({
onSave: onAgentPresetSave, onSave: onAgentPresetSave,
}), }),
h('div', { className: 'ddt-accountFooter dim-cardFooter' }, h('div', { className: 'ddt-accountFooter dim-cardFooter' },
summary ? h('div', { className: 'ddt-summary dim-cardSummary' }, summary) : null, h('div', { className: 'dim-cardFooterLayout' },
feedback ? h('div', { h('div', { className: 'ddt-actions dim-cardActions' },
className: 'ddt-summary dim-cardSummary', h(Button, { className: 'dim-cardAction', onClick: onReconnect, disabled: Boolean(busy) },
role: 'status', busy === 'reconnect' ? '检查中…' : account.connected ? '检查连接' : '重试连接'),
}, feedback) : null, h(Button, { className: 'dim-cardAction', kind: 'danger', onClick: onRequestRemove, disabled: Boolean(busy) },
h('div', { className: 'ddt-actions dim-cardActions' }, '移除接入')),
h(Button, { className: 'dim-cardAction', onClick: onReconnect, disabled: Boolean(busy) }, summary ? h('div', { className: 'ddt-summary dim-cardSummary' }, summary) : null,
busy === 'reconnect' ? '检查中…' : account.connected ? '检查连接' : '重试连接'), feedback ? h('div', {
h(Button, { className: 'dim-cardAction', kind: 'danger', onClick: onRequestRemove, disabled: Boolean(busy) }, className: 'ddt-summary dim-cardFeedback',
'移除接入')))), role: 'status',
}, feedback) : null))),
removing ? h(RemoveConfirmation, { removing ? h(RemoveConfirmation, {
account, account,
busy: busy === 'delete', busy: busy === 'delete',

View file

@ -629,30 +629,31 @@ export function BotCard({
}, provisionContent) }, provisionContent)
: null, : null,
h("div", { className: "bxf-connectedFooter dim-cardFooter" }, h("div", { className: "bxf-connectedFooter dim-cardFooter" },
summary ? h("div", { className: "bxf-healthSummary dim-cardSummary", "data-error": actionError || connection.error ? "true" : undefined }, h("div", { className: "dim-cardFooterLayout" },
summary) : null, h("div", { className: "bxf-actions bxf-botActions dim-cardActions" },
testNotice ? h("div", { h(Button, {
className: "bxf-healthSummary dim-cardSummary", className: "dim-cardAction", onClick: onReconnect,
role: "status", disabled: Boolean(busy), "aria-busy": busy === "reconnect" ? "true" : undefined,
}, testNotice) : null, "aria-label": `${connected ? "检查连接" : "重试连接"}${bot.name}`,
h("div", { className: "bxf-actions bxf-botActions dim-cardActions" }, }, busy === "reconnect" ? (connected ? "检查中…" : "正在连接…") : connected ? "检查连接" : "重试连接"),
h(Button, { h(Button, {
className: "dim-cardAction", onClick: onReconnect, className: "bxf-repairButton dim-cardAction",
disabled: Boolean(busy), "aria-busy": busy === "reconnect" ? "true" : undefined, onClick: onRepairCallback,
"aria-label": `${connected ? "检查连接" : "重试连接"}${bot.name}`, disabled: Boolean(busy) || repairDisabled,
}, busy === "reconnect" ? (connected ? "检查中…" : "正在连接…") : connected ? "检查连接" : "重试连接"), "aria-busy": busy === "callback-repair" ? "true" : undefined,
h(Button, { "aria-label": `修复${bot.name}的卡片按钮`,
className: "bxf-repairButton dim-cardAction", }, busy === "callback-repair" ? "等待扫码…" : "修复卡片按钮"),
onClick: onRepairCallback, h(Button, {
disabled: Boolean(busy) || repairDisabled, className: "dim-cardAction", kind: "danger", onClick: onRequestRemove,
"aria-busy": busy === "callback-repair" ? "true" : undefined, disabled: Boolean(busy), ref: removeButtonRef,
"aria-label": `修复${bot.name}的卡片按钮`, "aria-label": `从 DeepSeek Harness 移除${bot.name}`,
}, busy === "callback-repair" ? "等待扫码…" : "修复卡片按钮"), }, "移除接入")),
h(Button, { summary ? h("div", { className: "bxf-healthSummary dim-cardSummary", "data-error": actionError || connection.error ? "true" : undefined },
className: "dim-cardAction", kind: "danger", onClick: onRequestRemove, summary) : null,
disabled: Boolean(busy), ref: removeButtonRef, testNotice ? h("div", {
"aria-label": `从 DeepSeek Harness 移除${bot.name}`, className: "bxf-healthSummary dim-cardFeedback",
}, "移除接入")), role: "status",
}, testNotice) : null),
), ),
), ),
removing removing

View file

@ -192,15 +192,16 @@ export function AccountCard({
onSave: onAgentPresetSave, onSave: onAgentPresetSave,
}), }),
h('div', { className: 'ddt-accountFooter dim-cardFooter' }, h('div', { className: 'ddt-accountFooter dim-cardFooter' },
summary ? h('div', { className: 'ddt-summary dim-cardSummary' }, summary) : null, h('div', { className: 'dim-cardFooterLayout' },
feedback ? h('div', { h('div', { className: 'ddt-actions dim-cardActions' },
className: 'ddt-summary dim-cardSummary', h(Button, { className: 'dim-cardAction', onClick: onReconnect, disabled: Boolean(busy) }, busy === 'reconnect' ? '检查中…' : account.connected ? '检查连接' : '重试连接'),
role: 'status', h(Button, { className: 'dim-cardAction', kind: 'danger', onClick: onRequestRemove, disabled: Boolean(busy) }, '移除接入')),
'aria-live': 'polite', summary ? h('div', { className: 'ddt-summary dim-cardSummary' }, summary) : null,
}, feedback) : null, feedback ? h('div', {
h('div', { className: 'ddt-actions dim-cardActions' }, className: 'ddt-summary dim-cardFeedback',
h(Button, { className: 'dim-cardAction', onClick: onReconnect, disabled: Boolean(busy) }, busy === 'reconnect' ? '检查中…' : account.connected ? '检查连接' : '重试连接'), role: 'status',
h(Button, { className: 'dim-cardAction', kind: 'danger', onClick: onRequestRemove, disabled: Boolean(busy) }, '移除接入')))), 'aria-live': 'polite',
}, feedback) : null))),
removing ? h(RemoveConfirmation, { removing ? h(RemoveConfirmation, {
account, busy: busy === 'delete', onConfirm: onConfirmRemove, onCancel: onCancelRemove, account, busy: busy === 'delete', onConfirm: onConfirmRemove, onCancel: onCancelRemove,
}) : null); }) : null);

View file

@ -106,20 +106,24 @@ export function createTokenChannelSettings(definition) {
onSave: onAccountSettingsSave, onSave: onAccountSettingsSave,
}) : null, }) : null,
h('div', { className: 'ddt-accountFooter dim-cardFooter' }, h('div', { className: 'ddt-accountFooter dim-cardFooter' },
summary ? h('div', { className: 'ddt-summary dim-cardSummary' }, summary) : null, h('div', { className: 'dim-cardFooterLayout' },
testNotice ? h('div', { className: 'ddt-summary dim-cardSummary', role: 'status' }, testNotice) : null, h('div', { className: 'ddt-actions dim-cardActions' },
h('div', { className: 'ddt-actions dim-cardActions' }, h(Button, {
h(Button, { className: 'dim-cardAction',
className: 'dim-cardAction', onClick: onReconnect,
onClick: onReconnect, disabled: Boolean(busy),
disabled: Boolean(busy), }, busy === 'reconnect' ? '检查中…' : account.connected ? '检查连接' : '重试连接'),
}, busy === 'reconnect' ? '检查中…' : account.connected ? '检查连接' : '重试连接'), h(Button, {
h(Button, { className: 'dim-cardAction',
className: 'dim-cardAction', kind: 'danger',
kind: 'danger', onClick: onRequestRemove,
onClick: onRequestRemove, disabled: Boolean(busy),
disabled: Boolean(busy), }, '移除接入')),
}, '移除接入')))), summary ? h('div', { className: 'ddt-summary dim-cardSummary' }, summary) : null,
testNotice ? h('div', {
className: 'ddt-summary dim-cardFeedback',
role: 'status',
}, testNotice) : null))),
removing ? h('div', { className: 'ddt-confirm dim-confirm', role: 'alertdialog' }, removing ? h('div', { className: 'ddt-confirm dim-confirm', role: 'alertdialog' },
h('strong', null, `从 DeepSeek Harness 移除“${account.bot.name}”?`), h('strong', null, `从 DeepSeek Harness 移除“${account.bot.name}”?`),
h('p', null, `这会停止消息连接,并删除本机保存的 ${credentialNoun}、机器人配置及会话映射。${platformLabel}中的机器人不会被自动删除。`), h('p', null, `这会停止消息连接,并删除本机保存的 ${credentialNoun}、机器人配置及会话映射。${platformLabel}中的机器人不会被自动删除。`),

View file

@ -191,15 +191,16 @@ export function AccountCard({
onSave: onAgentPresetSave, onSave: onAgentPresetSave,
}), }),
h('div', { className: 'ddt-accountFooter dim-cardFooter' }, h('div', { className: 'ddt-accountFooter dim-cardFooter' },
summary ? h('div', { className: 'ddt-summary dim-cardSummary' }, summary) : null, h('div', { className: 'dim-cardFooterLayout' },
feedback ? h('div', { h('div', { className: 'ddt-actions dim-cardActions' },
className: 'ddt-summary dim-cardSummary', h(Button, { className: 'dim-cardAction', onClick: onReconnect, disabled: Boolean(busy) }, busy === 'reconnect' ? '检查中…' : account.connected ? '检查连接' : '重试连接'),
role: 'status', h(Button, { className: 'dim-cardAction', kind: 'danger', onClick: onRequestRemove, disabled: Boolean(busy) }, '移除接入')),
'aria-live': 'polite', summary ? h('div', { className: 'ddt-summary dim-cardSummary' }, summary) : null,
}, feedback) : null, feedback ? h('div', {
h('div', { className: 'ddt-actions dim-cardActions' }, className: 'ddt-summary dim-cardFeedback',
h(Button, { className: 'dim-cardAction', onClick: onReconnect, disabled: Boolean(busy) }, busy === 'reconnect' ? '检查中…' : account.connected ? '检查连接' : '重试连接'), role: 'status',
h(Button, { className: 'dim-cardAction', kind: 'danger', onClick: onRequestRemove, disabled: Boolean(busy) }, '移除接入')))), 'aria-live': 'polite',
}, feedback) : null))),
removing ? h(RemoveConfirmation, { removing ? h(RemoveConfirmation, {
account, busy: busy === 'delete', onConfirm: onConfirmRemove, onCancel: onCancelRemove, account, busy: busy === 'delete', onConfirm: onConfirmRemove, onCancel: onCancelRemove,
}) : null); }) : null);

View file

@ -240,20 +240,21 @@ export function AccountCard({
onSave: onAgentPresetSave, onSave: onAgentPresetSave,
}), }),
h('div', { className: 'dxw-accountFooter dim-cardFooter' }, h('div', { className: 'dxw-accountFooter dim-cardFooter' },
summary ? h('div', { className: 'dxw-summary dim-cardSummary' }, summary) : null, h('div', { className: 'dim-cardFooterLayout' },
account.lastMessageError ? h('div', { h('div', { className: 'dxw-actions dim-cardActions' },
className: 'dxw-summary dim-cardSummary', h(Button, { className: 'dim-cardAction', onClick: onReconnect, disabled: Boolean(busy) },
role: 'status', busy === 'reconnect' ? '检查中…' : account.connected ? '检查连接' : '重试连接'),
}, `最近一条消息处理失败:${account.lastMessageError.message}`) : null, h(Button, { className: 'dim-cardAction', kind: 'danger', onClick: onRequestRemove, disabled: Boolean(busy) }, '移除接入')),
feedback ? h('div', { summary ? h('div', { className: 'dxw-summary dim-cardSummary' }, summary) : null,
className: 'dxw-summary dim-cardSummary', account.lastMessageError ? h('div', {
role: 'status', className: 'dxw-summary dim-cardSummary',
'aria-live': 'polite', role: 'status',
}, feedback) : null, }, `最近一条消息处理失败:${account.lastMessageError.message}`) : null,
h('div', { className: 'dxw-actions dim-cardActions' }, feedback ? h('div', {
h(Button, { className: 'dim-cardAction', onClick: onReconnect, disabled: Boolean(busy) }, className: 'dxw-summary dim-cardFeedback',
busy === 'reconnect' ? '检查中…' : account.connected ? '检查连接' : '重试连接'), role: 'status',
h(Button, { className: 'dim-cardAction', kind: 'danger', onClick: onRequestRemove, disabled: Boolean(busy) }, '移除接入')))), 'aria-live': 'polite',
}, feedback) : null))),
removing ? h('div', { className: 'dxw-confirm dim-confirm', role: 'alertdialog' }, removing ? h('div', { className: 'dxw-confirm dim-confirm', role: 'alertdialog' },
h('strong', null, '从此 Harness 移除这个微信账号?'), h('strong', null, '从此 Harness 移除这个微信账号?'),
h('p', null, '这会停止消息连接,并删除本机保存的 bot_token、账号配置和会话映射。其他微信账号不受影响。'), h('p', null, '这会停止消息连接,并删除本机保存的 bot_token、账号配置和会话映射。其他微信账号不受影响。'),

View file

@ -217,18 +217,19 @@ export function WhatsappAccountCard({
onSave: onAgentPresetSave, onSave: onAgentPresetSave,
}), }),
h('div', { className: 'ddt-accountFooter dim-cardFooter' }, h('div', { className: 'ddt-accountFooter dim-cardFooter' },
summary ? h('div', { className: 'ddt-summary dim-cardSummary' }, summary) : null, h('div', { className: 'dim-cardFooterLayout' },
testNotice ? h('div', { h('div', { className: 'ddt-actions dim-cardActions' },
className: 'ddt-summary dim-cardSummary', h(Button, {
role: 'status', className: 'dim-cardAction', onClick: onReconnect, disabled: Boolean(busy),
}, testNotice) : null, }, busy === 'reconnect' ? '检查中…' : account.connected ? '检查连接' : '重试连接'),
h('div', { className: 'ddt-actions dim-cardActions' }, h(Button, {
h(Button, { className: 'dim-cardAction', kind: 'danger', onClick: onRequestRemove, disabled: Boolean(busy),
className: 'dim-cardAction', onClick: onReconnect, disabled: Boolean(busy), }, '移除接入')),
}, busy === 'reconnect' ? '检查中…' : account.connected ? '检查连接' : '重试连接'), summary ? h('div', { className: 'ddt-summary dim-cardSummary' }, summary) : null,
h(Button, { testNotice ? h('div', {
className: 'dim-cardAction', kind: 'danger', onClick: onRequestRemove, disabled: Boolean(busy), className: 'ddt-summary dim-cardFeedback',
}, '移除接入')))), role: 'status',
}, testNotice) : null))),
removing ? h(RemoveConfirmation, { removing ? h(RemoveConfirmation, {
account, account,
busy: busy === 'delete', busy: busy === 'delete',

View file

@ -192,6 +192,9 @@ const CSS = String.raw`
.dim-directoryPickerActions button:hover:not(:disabled) { filter: brightness(.97); } .dim-directoryPickerActions button:hover:not(:disabled) { filter: brightness(.97); }
.dim-directoryPickerActions button:disabled { cursor: not-allowed; opacity: .52; } .dim-directoryPickerActions button:disabled { cursor: not-allowed; opacity: .52; }
.dim-panel .dim-cardSummary { min-width: 0; color: var(--dsw-alias-label-secondary, #646a73); font: inherit; font-size: 12px; font-weight: 400; line-height: normal; } .dim-panel .dim-cardSummary { min-width: 0; color: var(--dsw-alias-label-secondary, #646a73); font: inherit; font-size: 12px; font-weight: 400; line-height: normal; }
.dim-panel .dim-cardFooterLayout { min-width: 0; width: 100%; display: flex; flex-direction: column; align-items: stretch; gap: 9px; }
.dim-panel .dim-cardFooterLayout > .dim-cardActions { align-self: flex-end; }
.dim-panel .dim-cardFeedback { width: 100%; padding: 8px 10px; border-radius: 8px; color: var(--dsw-alias-label-secondary, #646a73); background: var(--dsw-alias-bg-module-platform, #f7f8fa); font: inherit; font-size: 12px; font-weight: 400; line-height: 18px; overflow-wrap: anywhere; white-space: normal; }
.dim-panel .dim-cardActions { flex: none; display: flex; align-items: center; flex-wrap: nowrap; gap: 8px; margin: 0 0 0 auto; } .dim-panel .dim-cardActions { flex: none; display: flex; align-items: center; flex-wrap: nowrap; gap: 8px; margin: 0 0 0 auto; }
.dim-panel .dim-cardActions .dim-cardAction { flex: none; min-height: 32px; padding: 0 11px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 8px; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-layer-1, #fff); font: inherit; font-size: 13px; font-weight: 560; line-height: normal; white-space: nowrap; } .dim-panel .dim-cardActions .dim-cardAction { flex: none; min-height: 32px; padding: 0 11px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 8px; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-layer-1, #fff); font: inherit; font-size: 13px; font-weight: 560; line-height: normal; white-space: nowrap; }
.dim-panel .dim-cardActions .dim-cardAction:hover:not(:disabled) { border-color: #aeb3bb; background: var(--dsw-alias-interactive-bg-hover, #f7f8fa); } .dim-panel .dim-cardActions .dim-cardAction:hover:not(:disabled) { border-color: #aeb3bb; background: var(--dsw-alias-interactive-bg-hover, #f7f8fa); }

View file

@ -235,7 +235,11 @@ test('connection-check failure stays on the matching card with locale-safe wordi
assert.match(nodeText(targetCard), /现有连接错误/); assert.match(nodeText(targetCard), /现有连接错误/);
assert.equal( assert.equal(
targetCard.findAll((node) => node.props.className?.includes('dim-cardSummary')).length, targetCard.findAll((node) => node.props.className?.includes('dim-cardSummary')).length,
2, 1,
);
assert.equal(
targetCard.findAll((node) => node.props.className?.includes('dim-cardFeedback')).length,
1,
); );
assert.doesNotMatch(nodeText(targetCard), /provider-specific failure/); assert.doesNotMatch(nodeText(targetCard), /provider-specific failure/);
const announcement = [...clock.frames.entries()].at(-1); const announcement = [...clock.frames.entries()].at(-1);

View file

@ -34,7 +34,7 @@ test('Feishu connection check requests and displays test-message feedback', asyn
assert.match(source, /机器人尚未收到可用于测试的私聊消息/); assert.match(source, /机器人尚未收到可用于测试的私聊消息/);
assert.doesNotMatch(source, /请先私聊机器人发送 \/status/); assert.doesNotMatch(source, /请先私聊机器人发送 \/status/);
const markup = renderToStaticMarkup(React.createElement(BotCard, { const cardProps = {
connection: { connection: {
botId: 'bot-feishu-test', botId: 'bot-feishu-test',
state: 'connected', state: 'connected',
@ -47,8 +47,21 @@ test('Feishu connection check requests and displays test-message feedback', asyn
onRequestRemove() {}, onRequestRemove() {},
onConfirmRemove() {}, onConfirmRemove() {},
onCancelRemove() {}, onCancelRemove() {},
})); };
const markup = renderToStaticMarkup(React.createElement(BotCard, cardProps));
assert.match(markup, /role="status"[^>]*>测试消息已发送/); assert.match(markup, /role="status"[^>]*>测试消息已发送/);
assert.match(markup, /class="dim-cardFooterLayout"[^]*class="bxf-actions bxf-botActions dim-cardActions"[^]*class="bxf-healthSummary dim-cardFeedback"/);
let renderer;
await act(async () => {
renderer = create(React.createElement(BotCard, cardProps));
});
const footerChildren = renderer.root.findByProps({ className: 'dim-cardFooterLayout' }).children;
assert.match(footerChildren[0].props.className, /\bbxf-botActions\b/);
assert.equal(footerChildren[1].props.role, 'status');
assert.match(footerChildren[1].props.className, /\bdim-cardFeedback\b/);
await act(async () => renderer.unmount());
assert.match(markup, /修复卡片按钮/); assert.match(markup, /修复卡片按钮/);
assert.match(markup, /aria-label="修复飞书测试机器人的卡片按钮"/); assert.match(markup, /aria-label="修复飞书测试机器人的卡片按钮"/);
assert.match(markup, /<select[^>]*aria-label="群聊响应方式"/); assert.match(markup, /<select[^>]*aria-label="群聊响应方式"/);

View file

@ -493,7 +493,7 @@ test('DingTalk bot cards omit the redundant received and replied metric', () =>
assert.doesNotMatch(markup, /收到 \/ 回复/); assert.doesNotMatch(markup, /收到 \/ 回复/);
}); });
test('all channel card action buttons stay on one row', async () => { test('all IM channel cards place one-row actions above full-width feedback', async () => {
const [imStyles, feishuStyles, weixinStyles, dingtalkStyles] = await Promise.all([ const [imStyles, feishuStyles, weixinStyles, dingtalkStyles] = await Promise.all([
readFile(STYLES_URL, 'utf8'), readFile(STYLES_URL, 'utf8'),
readFile(FEISHU_STYLES_URL, 'utf8'), readFile(FEISHU_STYLES_URL, 'utf8'),
@ -505,11 +505,53 @@ test('all channel card action buttons stay on one row', async () => {
assert.match(weixinStyles, /\.dxw-accountFooter \.dxw-actions \{[^}]*flex-wrap: nowrap;/); assert.match(weixinStyles, /\.dxw-accountFooter \.dxw-actions \{[^}]*flex-wrap: nowrap;/);
assert.match(dingtalkStyles, /\.ddt-accountFooter \.ddt-actions \{[^}]*flex-wrap: nowrap;/); assert.match(dingtalkStyles, /\.ddt-accountFooter \.ddt-actions \{[^}]*flex-wrap: nowrap;/);
assert.match(imStyles, /\.dim-panel \.dim-cardFooter \{[^}]*gap: 12px;[^}]*padding-top: 6px;[^}]*border-top: 1px solid/); assert.match(imStyles, /\.dim-panel \.dim-cardFooter \{[^}]*gap: 12px;[^}]*padding-top: 6px;[^}]*border-top: 1px solid/);
assert.match(imStyles, /\.dim-panel \.dim-cardFooterLayout \{[^}]*width: 100%;[^}]*flex-direction: column;[^}]*align-items: stretch;/);
assert.match(imStyles, /\.dim-panel \.dim-cardFooterLayout > \.dim-cardActions \{[^}]*align-self: flex-end;/);
assert.match(imStyles, /\.dim-panel \.dim-cardFeedback \{[^}]*width: 100%;[^}]*overflow-wrap: anywhere;[^}]*white-space: normal;/);
assert.match(imStyles, /\.dim-panel \.dim-cardActions \.dim-cardAction \{[^}]*min-height: 32px;[^}]*border-radius: 8px;[^}]*font-size: 13px;/); assert.match(imStyles, /\.dim-panel \.dim-cardActions \.dim-cardAction \{[^}]*min-height: 32px;[^}]*border-radius: 8px;[^}]*font-size: 13px;/);
assert.match(imStyles, /\.dim-panel \.dim-cardActions \.dim-cardAction\[data-kind="danger"\] \{[^}]*#d54941/); assert.match(imStyles, /\.dim-panel \.dim-cardActions \.dim-cardAction\[data-kind="danger"\] \{[^}]*#d54941/);
assert.doesNotMatch(feishuStyles, /\.bxf-connectedFooter \{[^}]*flex-direction: column/);
assert.doesNotMatch(weixinStyles, /\.dxw-accountFooter \{[^}]*flex-direction: column/); const account = {
assert.doesNotMatch(dingtalkStyles, /\.ddt-accountFooter \{[^}]*flex-direction: column/); botId: 'footer-layout-bot',
connected: true,
configured: true,
state: 'connected',
groupResponseMode: 'mention',
bot: {
name: '布局测试机器人',
username: 'layout_bot',
appIdMasked: 'cli_test••••1234',
accountIdMasked: 'wx_test••••1234',
clientIdMasked: 'ding_test••••1234',
idMasked: 'bot_test••••1234',
},
health: { summary: '连接运行正常', lastCheckedAt: Date.now() },
error: null,
};
const callbacks = {
onReconnect() {},
onRequestRemove() {},
onConfirmRemove() {},
onCancelRemove() {},
};
const notice = '测试消息已发送,请到对应机器人会话中确认。';
const cards = [
['飞书', FeishuBotCard, { connection: account, testNotice: notice }],
['微信', WeixinAccountCard, { account, feedback: notice }],
['钉钉', DingtalkAccountCard, { account, feedback: notice }],
['企业微信', WecomAccountCard, { account, feedback: notice }],
['QQ', QqAccountCard, { account, feedback: notice }],
['Slack', SlackAccountCard, { account, testNotice: notice }],
['Telegram', TelegramAccountCard, { account, testNotice: notice }],
['Discord', DiscordAccountCard, { account, testNotice: notice }],
['WhatsApp', WhatsappAccountCard, { account, testNotice: notice }],
];
for (const [channel, Card, props] of cards) {
const markup = renderToStaticMarkup(React.createElement(Card, { ...callbacks, ...props }));
assert.match(markup, /class="dim-cardFooterLayout"><div class="[^"]*dim-cardActions[^"]*">[^]*?<\/div><div class="[^"]*dim-cardFeedback[^"]*" role="status"/, channel);
assert.ok(markup.indexOf('dim-cardActions') < markup.indexOf('dim-cardFeedback'), channel);
}
}); });
test('all channel bot cards use the DingTalk card treatment', async () => { test('all channel bot cards use the DingTalk card treatment', async () => {