diff --git a/changelog.d/1486-workflow-composer.fixed.md b/changelog.d/1486-workflow-composer.fixed.md new file mode 100644 index 000000000..9c3d2aa19 --- /dev/null +++ b/changelog.d/1486-workflow-composer.fixed.md @@ -0,0 +1 @@ +Workflow stage no longer shows the shell's message composer. Tasks are handed to the graph; the stage provides only pause/resume/progress controls. ([#1486](https://github.com/ThinkFlowLab/FlyBot/issues/1486)) diff --git a/changelog.d/1487-workflow-header.fixed.md b/changelog.d/1487-workflow-header.fixed.md new file mode 100644 index 000000000..7b9474ef9 --- /dev/null +++ b/changelog.d/1487-workflow-header.fixed.md @@ -0,0 +1 @@ +Workflow stage header titles no longer duplicate the issue number, and task titles are shown without mid-text truncation. ([#1487](https://github.com/ThinkFlowLab/FlyBot/issues/1487)) diff --git a/changelog.d/1488-workflow-sidebar-locale.fixed.md b/changelog.d/1488-workflow-sidebar-locale.fixed.md new file mode 100644 index 000000000..8210d6b6b --- /dev/null +++ b/changelog.d/1488-workflow-sidebar-locale.fixed.md @@ -0,0 +1 @@ +Workflow sidebar rows show schedule previews in English when the UI locale is English. ([#1488](https://github.com/ThinkFlowLab/FlyBot/issues/1488)) diff --git a/changelog.d/1489-workflow-timeline-locale.fixed.md b/changelog.d/1489-workflow-timeline-locale.fixed.md new file mode 100644 index 000000000..1e9b90a09 --- /dev/null +++ b/changelog.d/1489-workflow-timeline-locale.fixed.md @@ -0,0 +1 @@ +Workflow stage timeline shows step badges and event text in English when the UI locale is English. ([#1489](https://github.com/ThinkFlowLab/FlyBot/issues/1489)) diff --git a/changelog.d/1491-workflow-progress-button.fixed.md b/changelog.d/1491-workflow-progress-button.fixed.md new file mode 100644 index 000000000..b9a8fffbf --- /dev/null +++ b/changelog.d/1491-workflow-progress-button.fixed.md @@ -0,0 +1 @@ +Workflow stage Progress button now ensures the drawer opens and switches to the Runs tab. ([#1491](https://github.com/ThinkFlowLab/FlyBot/issues/1491)) diff --git a/changelog.d/1492-notifications-title.fixed.md b/changelog.d/1492-notifications-title.fixed.md new file mode 100644 index 000000000..f371d56f9 --- /dev/null +++ b/changelog.d/1492-notifications-title.fixed.md @@ -0,0 +1 @@ +Settings → General → Notifications: the first toggle row now shows the title "In other chats" alongside its description. ([#1492](https://github.com/ThinkFlowLab/FlyBot/issues/1492)) diff --git a/changelog.d/1493-workflow-failed-runs.fixed.md b/changelog.d/1493-workflow-failed-runs.fixed.md new file mode 100644 index 000000000..68f74e4f7 --- /dev/null +++ b/changelog.d/1493-workflow-failed-runs.fixed.md @@ -0,0 +1 @@ +Workflow stage now shows failed sibling runs in the header and timeline, not only in the Runs tab. ([#1493](https://github.com/ThinkFlowLab/FlyBot/issues/1493)) diff --git a/core/client.js b/core/client.js index 1482d2479..d16a95135 100644 --- a/core/client.js +++ b/core/client.js @@ -904,6 +904,7 @@ var ZH = { "{count} updates": "{count} 条更新", "Once more if it still waits after 15 min.": "等了 15 分钟还没批准时再提醒一次。", Notifications: "通知", + "In other chats": "在其他对话时", "When your Bots finish, fail, or wait for your approval while you are in another chat or app.": "你的 Bot 完成、失败或等你批准时提醒你(你在别的对话或应用里时)。", "{first} and {second} are waiting for your approval": "{first}、{second} 等你批准", "{first}, {second} and {count} more are waiting for your approval": "{first}、{second} 等 {total} 个等你批准", @@ -4745,6 +4746,7 @@ body[data-bt-friend-chat] [data-composer-stats]{display:none!important} body[data-bt-room] .bt-voice{display:none!important} body[data-bt-room] [data-composer-card] [class*="_activity"]{display:none!important} body[data-bt-room] [data-composer-card]:has(button[class*="_primary"]:disabled) .bt-mic:not([aria-pressed=true]){background:var(--bt-accent);color:var(--bt-accent-ink,#fff)} +body[data-bt-workflow] [data-composer-card]{display:none!important} .bt-mic-note{position:absolute;right:12px;bottom:calc(100% + 8px);padding:5px 10px;border-radius:8px;background:var(--bt-user);color:var(--bt-user-ink);font-size:12px;line-height:16px;white-space:nowrap;pointer-events:none;animation:bt-rise .16s ease-out} [data-chain-overlay-fallback="conversation.composer"]:has(~ .bt-q){display:block!important;order:2} .bt-q{order:1} @@ -16698,8 +16700,7 @@ function GeneralPage({ roster, actions, run }) { SetSection, { key: "notify", title: t("Notifications") }, (0, import_react39.createElement)(SwitchRow, { - label: t("Notifications"), - showLabel: false, + label: t("In other chats"), hint: t("When your Bots finish, fail, or wait for your approval while you are in another chat or app."), on: prefs.notifications !== false, onChange: (on) => run(actions.setPrefs({ notifications: on })) diff --git a/core/src/client/i18n.js b/core/src/client/i18n.js index cfc10888f..eddd4bf95 100644 --- a/core/src/client/i18n.js +++ b/core/src/client/i18n.js @@ -851,6 +851,7 @@ const ZH = { '{count} updates': '{count} 条更新', 'Once more if it still waits after 15 min.': '等了 15 分钟还没批准时再提醒一次。', Notifications: '通知', + 'In other chats': '在其他对话时', 'When your Bots finish, fail, or wait for your approval while you are in another chat or app.': '你的 Bot 完成、失败或等你批准时提醒你(你在别的对话或应用里时)。', '{first} and {second} are waiting for your approval': '{first}、{second} 等你批准', '{first}, {second} and {count} more are waiting for your approval': '{first}、{second} 等 {total} 个等你批准', diff --git a/core/src/client/settings.js b/core/src/client/settings.js index d06fdf615..828f84a40 100644 --- a/core/src/client/settings.js +++ b/core/src/client/settings.js @@ -74,7 +74,7 @@ function GeneralPage({ roster, actions, run }) { // One switch for every system notification (#353), then one per kind (#469). h(SetSection, { key: 'notify', title: t('Notifications') }, h(SwitchRow, { - label: t('Notifications'), showLabel: false, + label: t('In other chats'), hint: t('When your Bots finish, fail, or wait for your approval while you are in another chat or app.'), on: prefs.notifications !== false, onChange: on => run(actions.setPrefs({ notifications: on })), }), diff --git a/core/src/client/styles.js b/core/src/client/styles.js index 1c1e7b4fa..91bb48e9f 100644 --- a/core/src/client/styles.js +++ b/core/src/client/styles.js @@ -1051,6 +1051,7 @@ body[data-bt-friend-chat] [data-composer-stats]{display:none!important} body[data-bt-room] .bt-voice{display:none!important} body[data-bt-room] [data-composer-card] [class*="_activity"]{display:none!important} body[data-bt-room] [data-composer-card]:has(button[class*="_primary"]:disabled) .bt-mic:not([aria-pressed=true]){background:var(--bt-accent);color:var(--bt-accent-ink,#fff)} +body[data-bt-workflow] [data-composer-card]{display:none!important} .bt-mic-note{position:absolute;right:12px;bottom:calc(100% + 8px);padding:5px 10px;border-radius:8px;background:var(--bt-user);color:var(--bt-user-ink);font-size:12px;line-height:16px;white-space:nowrap;pointer-events:none;animation:bt-rise .16s ease-out} [data-chain-overlay-fallback="conversation.composer"]:has(~ .bt-q){display:block!important;order:2} .bt-q{order:1} diff --git a/patches/ds-bot/0001-core-fixes.patch b/patches/ds-bot/0001-core-fixes.patch index 73efcedb1..5ef2c267d 100644 --- a/patches/ds-bot/0001-core-fixes.patch +++ b/patches/ds-bot/0001-core-fixes.patch @@ -1187,7 +1187,7 @@ diff --git a/src/client/i18n.js b/src/client/i18n.js "{name} didn't answer in time": '{name} 没有及时回应', '{name} paused for your approval': '{name} 停下来等你批准', '{name} started on it': '{name} 开始做了', -@@ -450,12 +450,30 @@ const ZH = { +@@ -450,12 +450,31 @@ const ZH = { 'Asked to make the tool call it wrote as text': '工具调用被写成了文字,已让它重新调用', 'A Bot': '一个 Bot', '{name} is waiting for your approval': '{name} 等你批准', @@ -1197,6 +1197,7 @@ diff --git a/src/client/i18n.js b/src/client/i18n.js + '{count} updates': '{count} 条更新', + 'Once more if it still waits after 15 min.': '等了 15 分钟还没批准时再提醒一次。', + Notifications: '通知', ++ 'In other chats': '在其他对话时', + 'When your Bots finish, fail, or wait for your approval while you are in another chat or app.': '你的 Bot 完成、失败或等你批准时提醒你(你在别的对话或应用里时)。', '{first} and {second} are waiting for your approval': '{first}、{second} 等你批准', '{first}, {second} and {count} more are waiting for your approval': '{first}、{second} 等 {total} 个等你批准', diff --git a/patches/ds-bot/0003-flybot-api-client.patch b/patches/ds-bot/0003-flybot-api-client.patch index e11c3b0fa..105c41233 100644 --- a/patches/ds-bot/0003-flybot-api-client.patch +++ b/patches/ds-bot/0003-flybot-api-client.patch @@ -2988,14 +2988,16 @@ diff --git a/src/client/styles.js b/src/client/styles.js [data-composer-placeholder]::after{content:var(--bt-placeholder,"Message");display:block;font-size:14px;line-height:24px;color:color-mix(in srgb,var(--bt-ink) 36%,transparent)} /* The team's "+" (ComposerPlus) takes the seat of the shell's command-menu button. */ [data-composer-card] button[class*="_add"]{display:none!important} -@@ -905,6 +905,7 @@ header[class*="_header"] .bt-head{flex:1 1 auto;width:100%} +@@ -905,7 +905,9 @@ header[class*="_header"] .bt-head{flex:1 1 auto;width:100%} .bt-mic:active,.bt-voice:active{scale:.9} [data-composer-card]:has(button[class*="_primary"]:disabled) .bt-voice{display:inline-grid} body[data-bt-room] .bt-voice{display:none!important} +body[data-bt-room] [data-composer-card] [class*="_activity"]{display:none!important} body[data-bt-room] [data-composer-card]:has(button[class*="_primary"]:disabled) .bt-mic:not([aria-pressed=true]){background:var(--bt-accent);color:var(--bt-accent-ink,#fff)} ++body[data-bt-workflow] [data-composer-card]{display:none!important} .bt-mic-note{position:absolute;right:12px;bottom:calc(100% + 8px);padding:5px 10px;border-radius:8px;background:var(--bt-user);color:var(--bt-user-ink);font-size:12px;line-height:16px;white-space:nowrap;pointer-events:none;animation:bt-rise .16s ease-out} [data-chain-overlay-fallback="conversation.composer"]:has(~ .bt-q){display:block!important;order:2} + .bt-q{order:1} @@ -959,6 +959,7 @@ div:has(> nav [class*="_marks"]){display:none!important} .bt-earlier-top{display:flex;justify-content:center;align-items:center;height:34px;flex:none} .bt-earlier-top .bt-soft{height:28px;font-size:12px;padding:0 12px} diff --git a/patches/ds-bot/0008-client-bundle.patch b/patches/ds-bot/0008-client-bundle.patch index 8b969be5c..827afc879 100644 --- a/patches/ds-bot/0008-client-bundle.patch +++ b/patches/ds-bot/0008-client-bundle.patch @@ -607,7 +607,7 @@ diff --git a/client.js b/client.js "{name} didn't answer in time": "{name} 没有及时回应", "{name} paused for your approval": "{name} 停下来等你批准", "{name} started on it": "{name} 开始做了", -@@ -506,12 +506,30 @@ var ZH = { +@@ -506,12 +506,31 @@ var ZH = { "Asked to make the tool call it wrote as text": "工具调用被写成了文字,已让它重新调用", "A Bot": "一个 Bot", "{name} is waiting for your approval": "{name} 等你批准", @@ -617,6 +617,7 @@ diff --git a/client.js b/client.js + "{count} updates": "{count} 条更新", + "Once more if it still waits after 15 min.": "等了 15 分钟还没批准时再提醒一次。", + Notifications: "通知", ++ "In other chats": "在其他对话时", + "When your Bots finish, fail, or wait for your approval while you are in another chat or app.": "你的 Bot 完成、失败或等你批准时提醒你(你在别的对话或应用里时)。", "{first} and {second} are waiting for your approval": "{first}、{second} 等你批准", "{first}, {second} and {count} more are waiting for your approval": "{first}、{second} 等 {total} 个等你批准", @@ -4363,7 +4364,7 @@ diff --git a/client.js b/client.js .bt-account{flex:none;width:36px;height:36px;border:0;padding:0;background:none;border-radius:50%;cursor:pointer;display:inline-flex} .bt-account:hover .bt-you,.bt-account[aria-expanded=true] .bt-you{background:color-mix(in srgb,var(--bt-ink) 9%,var(--bt-sidebar))} /* Popover keyframes move translate and scale, which compose with a transform the popover -@@ -2488,2292 +2488,4418 @@ body[data-ds-dark-theme] .bt-theme-chip i{background:var(--da)} +@@ -2488,2292 +2488,4419 @@ body[data-ds-dark-theme] .bt-theme-chip i{background:var(--da)} @keyframes bt-rise{from{opacity:0;transform:translateY(-3px)}to{opacity:1;transform:none}} @media (prefers-reduced-motion:reduce){.bt-member,.bt-addlist{animation:none}} @@ -4728,6 +4729,7 @@ diff --git a/client.js b/client.js +body[data-bt-room] .bt-voice{display:none!important} +body[data-bt-room] [data-composer-card] [class*="_activity"]{display:none!important} +body[data-bt-room] [data-composer-card]:has(button[class*="_primary"]:disabled) .bt-mic:not([aria-pressed=true]){background:var(--bt-accent);color:var(--bt-accent-ink,#fff)} ++body[data-bt-workflow] [data-composer-card]{display:none!important} +.bt-mic-note{position:absolute;right:12px;bottom:calc(100% + 8px);padding:5px 10px;border-radius:8px;background:var(--bt-user);color:var(--bt-user-ink);font-size:12px;line-height:16px;white-space:nowrap;pointer-events:none;animation:bt-rise .16s ease-out} +[data-chain-overlay-fallback="conversation.composer"]:has(~ .bt-q){display:block!important;order:2} +.bt-q{order:1} @@ -18636,7 +18638,7 @@ diff --git a/client.js b/client.js key: ink, type: "button", className: "bt-swatch", -@@ -10626,75 +10626,87 @@ function GeneralPage({ roster, actions, run }) { +@@ -10626,75 +10626,86 @@ function GeneralPage({ roster, actions, run }) { style: { background: INKS[ink][0] }, onClick: () => run(actions.setPrefs({ accent: INKS[ink][0] })) })), @@ -18662,8 +18664,7 @@ diff --git a/client.js b/client.js - ) + { key: "notify", title: t("Notifications") }, + (0, import_react39.createElement)(SwitchRow, { -+ label: t("Notifications"), -+ showLabel: false, ++ label: t("In other chats"), + hint: t("When your Bots finish, fail, or wait for your approval while you are in another chat or app."), + on: prefs.notifications !== false, + onChange: (on) => run(actions.setPrefs({ notifications: on })) diff --git a/patches/ds-bot/0014-flybot-branding.patch b/patches/ds-bot/0014-flybot-branding.patch index 8f96ec390..6de72fa6d 100644 --- a/patches/ds-bot/0014-flybot-branding.patch +++ b/patches/ds-bot/0014-flybot-branding.patch @@ -303,7 +303,7 @@ diff --git a/src/client/settings.js b/src/client/settings.js + // One switch for every system notification (#353), then one per kind (#469). + h(SetSection, { key: 'notify', title: t('Notifications') }, + h(SwitchRow, { -+ label: t('Notifications'), showLabel: false, ++ label: t('In other chats'), + hint: t('When your Bots finish, fail, or wait for your approval while you are in another chat or app.'), + on: prefs.notifications !== false, onChange: on => run(actions.setPrefs({ notifications: on })), + }), diff --git a/plugins/workflow/client.js b/plugins/workflow/client.js index 8a3982f04..9c22aa64a 100644 --- a/plugins/workflow/client.js +++ b/plugins/workflow/client.js @@ -108,6 +108,9 @@ window.__ModuleLoader__.load({ 'Could not delete this workflow': '无法删除该 Workflow', 'Reading workflows…': '正在读取 Workflow…', 'Changes are saved automatically': '更改会自动保存', + 'Next run: {time}': '下次运行:{time}', + 'Step {step}': '步骤 {step}', + 'Parallel branches · Merge after all pass': '并行分支 · 全部通过后汇合', } function zhUi() { @@ -194,8 +197,12 @@ window.__ModuleLoader__.load({ if (!id && !title) return '' if (!title) return id if (!id) return title.length > 72 ? `${title.slice(0, 69)}…` : title + // Remove issue number prefix from title if it duplicates the id + let cleanTitle = title + const issueMatch = title.match(/^issue\s+#?\d+\s*[:\-–—]\s*/i) + if (issueMatch && id) cleanTitle = title.slice(issueMatch[0].length) const room = Math.max(24, 72 - id.length - 3) - const short = title.length > room ? `${title.slice(0, room - 1)}…` : title + const short = cleanTitle.length > room ? `${cleanTitle.slice(0, room - 1)}…` : cleanTitle return `${id} · ${short}` } function chatPreview(text) { @@ -206,7 +213,7 @@ window.__ModuleLoader__.load({ function sidebarPreview(item) { const busy = (item.activeCount ?? 0) > 0 && item.run?.live === true if (busy) return item.preview || tr('Waiting for a task') - if (item.trigger === 'schedule' && item.scheduleLabel) return `下次运行:${item.scheduleLabel}` + if (item.trigger === 'schedule' && item.scheduleLabel) return tr('Next run: {time}', { time: item.scheduleLabel }) return tr('Waiting for a task') } function glyph() { @@ -375,8 +382,8 @@ window.__ModuleLoader__.load({ ? h('button', { type: 'button', className: 'wf-btn wf-sm wf-pri', onClick: (event) => { stopBubble(event); onResume?.(run.id) } }, tr('Resume')) : h('button', { type: 'button', className: 'wf-btn wf-sm', onClick: (event) => { stopBubble(event); onPause?.(run.id) } }, tr('Pause'))) : null, run.status === 'running' ? h('button', { type: 'button', className: 'wf-btn wf-sm wf-danger-outline', onClick: (event) => { stopBubble(event); onStop?.(run.id) } }, tr('Stop')) : null, - h('button', { type: 'button', className: 'wf-btn wf-sm', onClick: (event) => { stopBubble(event); focus(); onTab('runs'); onOpenDrawer?.() } }, tr('Progress')), - h('button', { type: 'button', className: 'wf-btn wf-sm', onClick: (event) => { stopBubble(event); focus(); onTab('flow'); onOpenDrawer?.() } }, tr('Flow')))) + h('button', { type: 'button', className: 'wf-btn wf-sm', onClick: (event) => { stopBubble(event); focus(); onOpenDrawer?.(); onTab('runs') } }, tr('Progress')), + h('button', { type: 'button', className: 'wf-btn wf-sm', onClick: (event) => { stopBubble(event); focus(); onOpenDrawer?.(); onTab('flow') } }, tr('Flow')))) } // Stage: view/control only — progress and flowchart; no message composer (#1459). @@ -423,7 +430,7 @@ window.__ModuleLoader__.load({ return h('div', { className: 'wf-msg' }, h(Mark, { node, roster, size: 28 }), h('div', null, - h('div', { className: 'wf-who' }, node.name, message.step ? h('span', { className: 'wf-step' }, `步骤 ${message.step}`) : null), + h('div', { className: 'wf-who' }, node.name, message.step ? h('span', { className: 'wf-step' }, tr('Step {step}', { step: message.step })) : null), h('div', { className: 'wf-bub', title: message.text }, chatPreview(message.text), message.attachments?.length ? h('div', { className: 'wf-att' }, message.attachments.map(name => h('span', { key: name }, name))) : null))) } @@ -488,8 +495,8 @@ window.__ModuleLoader__.load({ } function liveStageRuns(workflow) { - let liveRuns = (workflow.runs ?? []).filter(run => run?.live === true && run.status === 'running') - if (liveRuns.length === 0 && workflow.run?.live === true && workflow.run.status === 'running') { + let liveRuns = (workflow.runs ?? []).filter(run => run?.live === true && (run.status === 'running' || run.status === 'failed')) + if (liveRuns.length === 0 && workflow.run?.live === true && (workflow.run.status === 'running' || workflow.run.status === 'failed')) { liveRuns = [workflow.run] } return liveRuns @@ -501,7 +508,7 @@ window.__ModuleLoader__.load({ const picked = live.find(run => run.id === focusedRunId) if (picked) return picked } - if (workflow.run?.live === true && workflow.run.status === 'running') return workflow.run + if (workflow.run?.live === true && (workflow.run.status === 'running' || workflow.run.status === 'failed')) return workflow.run return live[0] ?? null } @@ -614,7 +621,7 @@ window.__ModuleLoader__.load({ return h('div', { key: layer.join('+'), className: 'wf-par wf-abs', style: parallelFrameStyle(members), - }, h('div', { className: 'wf-plabel' }, '并行分支 · 全部通过后汇合')) + }, h('div', { className: 'wf-plabel' }, tr('Parallel branches · Merge after all pass'))) }), nodes.map(node => h('div', { key: node.id, @@ -1140,6 +1147,14 @@ window.__ModuleLoader__.load({ if (!live.some(run => run.id === focusedRunId)) setFocusedRunId(live[0].id) }, [open?.id, open?.activeCount, open?.runs?.length, focusedRunId]) + // Hide the shell's composer when a Workflow stage is open (#1486) + useEffect(() => { + if (typeof document === 'undefined' || !document.body || !document.body.dataset) return undefined + if (!open) return undefined + document.body.dataset.btWorkflow = '' + return () => { delete document.body.dataset.btWorkflow } + }, [open?.id]) + const loadTasks = async () => { const host = globalThis.__flybot__?.host if (typeof host !== 'function') { setTasks([]); return } diff --git a/plugins/workflow/test/client.test.js b/plugins/workflow/test/client.test.js index a4ed8a7f9..5cf701538 100644 --- a/plugins/workflow/test/client.test.js +++ b/plugins/workflow/test/client.test.js @@ -188,7 +188,7 @@ test('the sidebar opens the flowchart, and a drag, a wire and a dropped Bot are findAll(view.tree, node => node.type === 'button' && textOf(node).includes('发布交付流水线'))[0].props.onClick() findAll(view.tree, node => node.type === 'button' && textOf(node) === 'Flow')[0].props.onClick() const opened = textOf(view.tree) - assert.ok(opened.includes('并行分支 · 全部通过后汇合')) + assert.ok(opened.includes('Parallel branches · Merge after all pass')) assert.ok(opened.includes('Reviewer')) assert.ok(opened.includes('Tester')) const html = htmlOf(view.tree)