diff --git a/changelog.d/1636-thinking-row-duplicate-line.fixed.md b/changelog.d/1636-thinking-row-duplicate-line.fixed.md new file mode 100644 index 000000000..92ea94050 --- /dev/null +++ b/changelog.d/1636-thinking-row-duplicate-line.fixed.md @@ -0,0 +1 @@ +An opened thinking row no longer prints its first line twice: the folded row still names a line of its reasoning next to 思考过程, and once you open it that preview steps aside so only the full text below shows the sentence ([#1636](https://github.com/ThinkFlowLab/FlyBot/issues/1636)). diff --git a/core/client.js b/core/client.js index d30bc5c69..3414e1f00 100644 --- a/core/client.js +++ b/core/client.js @@ -4673,6 +4673,8 @@ var CONVERSATION_CSS = ` .bt-think-caret{display:inline-block;transition:transform .15s;font-size:14px;line-height:1} .bt-think[open] .bt-think-caret{transform:rotate(90deg)} .bt-think-peek{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--bt-ink-3)} +/* Opened, the body prints that same first line again, so the preview steps aside (#1636). */ +.bt-think[open] .bt-think-peek{display:none} .bt-person-mark{display:inline-flex;align-items:center;justify-content:center;border-radius:50%;background:var(--bt-ink-5,rgba(20,20,20,.08));color:var(--bt-ink-3,rgba(20,20,20,.45))} .bt-person-initial{color:#fff;font-weight:600;line-height:1;user-select:none} .bt-person-msg .bt-group-name{color:var(--bt-ink-2,rgba(20,20,20,.7))} diff --git a/core/src/client/styles.js b/core/src/client/styles.js index 9b0edec0e..9611d9a76 100644 --- a/core/src/client/styles.js +++ b/core/src/client/styles.js @@ -889,6 +889,8 @@ export const CONVERSATION_CSS = ` .bt-think-caret{display:inline-block;transition:transform .15s;font-size:14px;line-height:1} .bt-think[open] .bt-think-caret{transform:rotate(90deg)} .bt-think-peek{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--bt-ink-3)} +/* Opened, the body prints that same first line again, so the preview steps aside (#1636). */ +.bt-think[open] .bt-think-peek{display:none} .bt-person-mark{display:inline-flex;align-items:center;justify-content:center;border-radius:50%;background:var(--bt-ink-5,rgba(20,20,20,.08));color:var(--bt-ink-3,rgba(20,20,20,.45))} .bt-person-initial{color:#fff;font-weight:600;line-height:1;user-select:none} .bt-person-msg .bt-group-name{color:var(--bt-ink-2,rgba(20,20,20,.7))} diff --git a/core/test/thinking-row.test.js b/core/test/thinking-row.test.js index e0d1ccf25..ef3971cc4 100644 --- a/core/test/thinking-row.test.js +++ b/core/test/thinking-row.test.js @@ -1,5 +1,6 @@ import { test } from 'node:test' import assert from 'node:assert/strict' +import { readFileSync } from 'node:fs' import { fileURLToPath } from 'node:url' import { runInNewContext } from 'node:vm' import { build } from 'esbuild' @@ -8,7 +9,7 @@ import { build } from 'esbuild' // React whose components run as plain functions, as in tool-cells.test.js. const { outputFiles } = await build({ stdin: { - contents: "export { AssistantCell } from './src/client/cells.js'", + contents: "export { AssistantCell } from './src/client/cells.js';\nexport { CONVERSATION_CSS } from './src/client/styles.js'", resolveDir: fileURLToPath(new URL('..', import.meta.url)), }, bundle: true, format: 'cjs', platform: 'browser', write: false, logLevel: 'silent', @@ -36,7 +37,7 @@ runInNewContext(outputFiles[0].text, { throw new Error(`Unexpected platform module ${name}`) }, }) -const { AssistantCell } = module.exports +const { AssistantCell, CONVERSATION_CSS } = module.exports const expand = (node) => { if (node === null || node === undefined || typeof node === 'boolean') return null @@ -120,3 +121,18 @@ test('#1624: reasoning that is only whitespace draws no row at all', () => { const blocks = [think(' \n\t '), { kind: 'text', text: 'Answer' }] assert.equal(thinkingRow(render(step(1, blocks), [step(1, blocks)])), undefined) }) + +test('#1636: an opened row hides its preview, so its first line is not printed twice', () => { + // The folded row names its reasoning inside the summary (#1624). Opened, the body prints that + // same line again, so the open state drops the preview instead of repeating it. + const openRules = [...CONVERSATION_CSS.matchAll(/\.bt-think\[open\][^{]*\{[^}]*\}/g)].map(match => match[0]) + assert.ok( + openRules.some(rule => /\.bt-think-peek/.test(rule) && /display:\s*none/.test(rule)), + `the opened row hides .bt-think-peek; open-state rules: ${openRules.join(' ') || 'none'}`, + ) + // The app loads the committed bundle, not this source, so the bundle carries the rule too. + assert.match( + readFileSync(new URL('../client.js', import.meta.url), 'utf8'), + /\.bt-think\[open\] \.bt-think-peek\{display:none\}/, + ) +}) diff --git a/patches/ds-bot/0003-flybot-api-client.patch b/patches/ds-bot/0003-flybot-api-client.patch index a72056f0c..3f1a67494 100644 --- a/patches/ds-bot/0003-flybot-api-client.patch +++ b/patches/ds-bot/0003-flybot-api-client.patch @@ -3004,13 +3004,15 @@ diff --git a/src/client/styles.js b/src/client/styles.js .bt-mem-scrim{animation:bt-fade .2s ease-out} .bt-mem{position:relative;width:min(680px,calc(100vw - 48px));height:min(760px,calc(100vh - 2*max(32px,var(--dsh-frame-overlay-top,32px))));background:var(--bt-main);border-radius:20px;border:.5px solid var(--bt-line-2);box-shadow:0 24px 80px rgba(0,0,0,.24);display:flex;flex-direction:column;overflow:hidden;color:var(--bt-ink);animation:bt-dialog-rise ${SPRING_SHAPE.duration}ms ${SPRING_SHAPE.easing},bt-veil-in .2s ease-out} .bt-mem-head{position:relative;display:flex;align-items:center;gap:8px;height:60px;flex:none;padding:0 14px 0 24px;border-bottom:.5px solid var(--bt-line);box-sizing:border-box} -@@ -782,6 +782,15 @@ export const CONVERSATION_CSS = ` +@@ -782,6 +782,17 @@ export const CONVERSATION_CSS = ` .bt-think{font-size:13px;line-height:18px;color:var(--bt-ink-2);margin:2px 0 4px;max-width:min(560px,78%)} .bt-think summary{cursor:pointer;list-style:none;display:inline-flex;align-items:center;gap:6px} .bt-think summary::-webkit-details-marker{display:none} +.bt-think-caret{display:inline-block;transition:transform .15s;font-size:14px;line-height:1} +.bt-think[open] .bt-think-caret{transform:rotate(90deg)} +.bt-think-peek{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--bt-ink-3)} ++/* Opened, the body prints that same first line again, so the preview steps aside (#1636). */ ++.bt-think[open] .bt-think-peek{display:none} +.bt-person-mark{display:inline-flex;align-items:center;justify-content:center;border-radius:50%;background:var(--bt-ink-5,rgba(20,20,20,.08));color:var(--bt-ink-3,rgba(20,20,20,.45))} +.bt-person-initial{color:#fff;font-weight:600;line-height:1;user-select:none} +.bt-person-msg .bt-group-name{color:var(--bt-ink-2,rgba(20,20,20,.7))} diff --git a/patches/ds-bot/0008-client-bundle.patch b/patches/ds-bot/0008-client-bundle.patch index 68d0ad7f5..ba448a312 100644 --- a/patches/ds-bot/0008-client-bundle.patch +++ b/patches/ds-bot/0008-client-bundle.patch @@ -3533,13 +3533,15 @@ diff --git a/client.js b/client.js .bt-mem-scrim{animation:bt-fade .2s ease-out} .bt-mem{position:relative;width:min(680px,calc(100vw - 48px));height:min(760px,calc(100vh - 2*max(32px,var(--dsh-frame-overlay-top,32px))));background:var(--bt-main);border-radius:20px;border:.5px solid var(--bt-line-2);box-shadow:0 24px 80px rgba(0,0,0,.24);display:flex;flex-direction:column;overflow:hidden;color:var(--bt-ink);animation:bt-dialog-rise ${SPRING_SHAPE.duration}ms ${SPRING_SHAPE.easing},bt-veil-in .2s ease-out} .bt-mem-head{position:relative;display:flex;align-items:center;gap:8px;height:60px;flex:none;padding:0 14px 0 24px;border-bottom:.5px solid var(--bt-line);box-sizing:border-box} -@@ -2599,6 +2599,15 @@ var CONVERSATION_CSS = ` +@@ -2599,6 +2599,17 @@ var CONVERSATION_CSS = ` .bt-think{font-size:13px;line-height:18px;color:var(--bt-ink-2);margin:2px 0 4px;max-width:min(560px,78%)} .bt-think summary{cursor:pointer;list-style:none;display:inline-flex;align-items:center;gap:6px} .bt-think summary::-webkit-details-marker{display:none} +.bt-think-caret{display:inline-block;transition:transform .15s;font-size:14px;line-height:1} +.bt-think[open] .bt-think-caret{transform:rotate(90deg)} +.bt-think-peek{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--bt-ink-3)} ++/* Opened, the body prints that same first line again, so the preview steps aside (#1636). */ ++.bt-think[open] .bt-think-peek{display:none} +.bt-person-mark{display:inline-flex;align-items:center;justify-content:center;border-radius:50%;background:var(--bt-ink-5,rgba(20,20,20,.08));color:var(--bt-ink-3,rgba(20,20,20,.45))} +.bt-person-initial{color:#fff;font-weight:600;line-height:1;user-select:none} +.bt-person-msg .bt-group-name{color:var(--bt-ink-2,rgba(20,20,20,.7))} diff --git a/patches/ds-bot/files/test/thinking-row.test.js b/patches/ds-bot/files/test/thinking-row.test.js index e0d1ccf25..ef3971cc4 100644 --- a/patches/ds-bot/files/test/thinking-row.test.js +++ b/patches/ds-bot/files/test/thinking-row.test.js @@ -1,5 +1,6 @@ import { test } from 'node:test' import assert from 'node:assert/strict' +import { readFileSync } from 'node:fs' import { fileURLToPath } from 'node:url' import { runInNewContext } from 'node:vm' import { build } from 'esbuild' @@ -8,7 +9,7 @@ import { build } from 'esbuild' // React whose components run as plain functions, as in tool-cells.test.js. const { outputFiles } = await build({ stdin: { - contents: "export { AssistantCell } from './src/client/cells.js'", + contents: "export { AssistantCell } from './src/client/cells.js';\nexport { CONVERSATION_CSS } from './src/client/styles.js'", resolveDir: fileURLToPath(new URL('..', import.meta.url)), }, bundle: true, format: 'cjs', platform: 'browser', write: false, logLevel: 'silent', @@ -36,7 +37,7 @@ runInNewContext(outputFiles[0].text, { throw new Error(`Unexpected platform module ${name}`) }, }) -const { AssistantCell } = module.exports +const { AssistantCell, CONVERSATION_CSS } = module.exports const expand = (node) => { if (node === null || node === undefined || typeof node === 'boolean') return null @@ -120,3 +121,18 @@ test('#1624: reasoning that is only whitespace draws no row at all', () => { const blocks = [think(' \n\t '), { kind: 'text', text: 'Answer' }] assert.equal(thinkingRow(render(step(1, blocks), [step(1, blocks)])), undefined) }) + +test('#1636: an opened row hides its preview, so its first line is not printed twice', () => { + // The folded row names its reasoning inside the summary (#1624). Opened, the body prints that + // same line again, so the open state drops the preview instead of repeating it. + const openRules = [...CONVERSATION_CSS.matchAll(/\.bt-think\[open\][^{]*\{[^}]*\}/g)].map(match => match[0]) + assert.ok( + openRules.some(rule => /\.bt-think-peek/.test(rule) && /display:\s*none/.test(rule)), + `the opened row hides .bt-think-peek; open-state rules: ${openRules.join(' ') || 'none'}`, + ) + // The app loads the committed bundle, not this source, so the bundle carries the rule too. + assert.match( + readFileSync(new URL('../client.js', import.meta.url), 'utf8'), + /\.bt-think\[open\] \.bt-think-peek\{display:none\}/, + ) +})