@drscrewdriver/dsh-better-display 0.3.3-fork.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +259 -0
- package/DESIGN.md +72 -0
- package/LICENSE +24 -0
- package/README.en.md +55 -0
- package/README.md +55 -0
- package/THIRD_PARTY_NOTICES.md +65 -0
- package/compat/harness-rc2.json +99 -0
- package/cordis.patch.yml +5 -0
- package/cordis.yml +4 -0
- package/docs/official-rendering-bridge.md +65 -0
- package/dshx.yml +5 -0
- package/lib/client.js +49292 -0
- package/lib/client.js.map +1 -0
- package/lib/dsh-better-display.js +101 -0
- package/lib/types/client/Blocks.d.ts +49 -0
- package/lib/types/client/Blocks.d.ts.map +1 -0
- package/lib/types/client/Blocks.js +143 -0
- package/lib/types/client/Blocks.js.map +1 -0
- package/lib/types/client/ChoreographedFlow.d.ts +22 -0
- package/lib/types/client/ChoreographedFlow.d.ts.map +1 -0
- package/lib/types/client/ChoreographedFlow.js +218 -0
- package/lib/types/client/ChoreographedFlow.js.map +1 -0
- package/lib/types/client/ClosedProcessSummary.d.ts +9 -0
- package/lib/types/client/ClosedProcessSummary.d.ts.map +1 -0
- package/lib/types/client/ClosedProcessSummary.js +16 -0
- package/lib/types/client/ClosedProcessSummary.js.map +1 -0
- package/lib/types/client/DiffPanel.d.ts +13 -0
- package/lib/types/client/DiffPanel.d.ts.map +1 -0
- package/lib/types/client/DiffPanel.js +115 -0
- package/lib/types/client/DiffPanel.js.map +1 -0
- package/lib/types/client/LiveFold.d.ts +10 -0
- package/lib/types/client/LiveFold.d.ts.map +1 -0
- package/lib/types/client/LiveFold.js +39 -0
- package/lib/types/client/LiveFold.js.map +1 -0
- package/lib/types/client/McpAppFrame.d.ts +32 -0
- package/lib/types/client/McpAppFrame.d.ts.map +1 -0
- package/lib/types/client/McpAppFrame.js +193 -0
- package/lib/types/client/McpAppFrame.js.map +1 -0
- package/lib/types/client/OfficialContent.d.ts +25 -0
- package/lib/types/client/OfficialContent.d.ts.map +1 -0
- package/lib/types/client/OfficialContent.js +53 -0
- package/lib/types/client/OfficialContent.js.map +1 -0
- package/lib/types/client/Reader.d.ts +3 -0
- package/lib/types/client/Reader.d.ts.map +1 -0
- package/lib/types/client/Reader.js +603 -0
- package/lib/types/client/Reader.js.map +1 -0
- package/lib/types/client/ReasoningCard.d.ts +11 -0
- package/lib/types/client/ReasoningCard.d.ts.map +1 -0
- package/lib/types/client/ReasoningCard.js +322 -0
- package/lib/types/client/ReasoningCard.js.map +1 -0
- package/lib/types/client/SettingsSection.d.ts +37 -0
- package/lib/types/client/SettingsSection.d.ts.map +1 -0
- package/lib/types/client/SettingsSection.js +63 -0
- package/lib/types/client/SettingsSection.js.map +1 -0
- package/lib/types/client/StickyLane.d.ts +8 -0
- package/lib/types/client/StickyLane.d.ts.map +1 -0
- package/lib/types/client/StickyLane.js +39 -0
- package/lib/types/client/StickyLane.js.map +1 -0
- package/lib/types/client/TimelineRail.d.ts +10 -0
- package/lib/types/client/TimelineRail.d.ts.map +1 -0
- package/lib/types/client/TimelineRail.js +168 -0
- package/lib/types/client/TimelineRail.js.map +1 -0
- package/lib/types/client/ToolActivity.d.ts +17 -0
- package/lib/types/client/ToolActivity.d.ts.map +1 -0
- package/lib/types/client/ToolActivity.js +244 -0
- package/lib/types/client/ToolActivity.js.map +1 -0
- package/lib/types/client/TurnMetrics.d.ts +11 -0
- package/lib/types/client/TurnMetrics.d.ts.map +1 -0
- package/lib/types/client/TurnMetrics.js +44 -0
- package/lib/types/client/TurnMetrics.js.map +1 -0
- package/lib/types/client/WaitClock.d.ts +16 -0
- package/lib/types/client/WaitClock.d.ts.map +1 -0
- package/lib/types/client/WaitClock.js +29 -0
- package/lib/types/client/WaitClock.js.map +1 -0
- package/lib/types/client/WaitingClock.d.ts +15 -0
- package/lib/types/client/WaitingClock.d.ts.map +1 -0
- package/lib/types/client/WaitingClock.js +30 -0
- package/lib/types/client/WaitingClock.js.map +1 -0
- package/lib/types/client/WaitingStatus.d.ts +6 -0
- package/lib/types/client/WaitingStatus.d.ts.map +1 -0
- package/lib/types/client/WaitingStatus.js +9 -0
- package/lib/types/client/WaitingStatus.js.map +1 -0
- package/lib/types/client/conversation-scroll.d.ts +19 -0
- package/lib/types/client/conversation-scroll.d.ts.map +1 -0
- package/lib/types/client/conversation-scroll.js +25 -0
- package/lib/types/client/conversation-scroll.js.map +1 -0
- package/lib/types/client/deliverables.d.ts +24 -0
- package/lib/types/client/deliverables.d.ts.map +1 -0
- package/lib/types/client/deliverables.js +104 -0
- package/lib/types/client/deliverables.js.map +1 -0
- package/lib/types/client/entry-policy.d.ts +10 -0
- package/lib/types/client/entry-policy.d.ts.map +1 -0
- package/lib/types/client/entry-policy.js +24 -0
- package/lib/types/client/entry-policy.js.map +1 -0
- package/lib/types/client/entry.d.ts +4 -0
- package/lib/types/client/entry.d.ts.map +1 -0
- package/lib/types/client/entry.js +34 -0
- package/lib/types/client/entry.js.map +1 -0
- package/lib/types/client/fold-choreography.d.ts +40 -0
- package/lib/types/client/fold-choreography.d.ts.map +1 -0
- package/lib/types/client/fold-choreography.js +48 -0
- package/lib/types/client/fold-choreography.js.map +1 -0
- package/lib/types/client/fold-intensity.d.ts +11 -0
- package/lib/types/client/fold-intensity.d.ts.map +1 -0
- package/lib/types/client/fold-intensity.js +32 -0
- package/lib/types/client/fold-intensity.js.map +1 -0
- package/lib/types/client/index.d.ts +7 -0
- package/lib/types/client/index.d.ts.map +1 -0
- package/lib/types/client/index.js +195 -0
- package/lib/types/client/index.js.map +1 -0
- package/lib/types/client/live-turn.d.ts +58 -0
- package/lib/types/client/live-turn.d.ts.map +1 -0
- package/lib/types/client/live-turn.js +190 -0
- package/lib/types/client/live-turn.js.map +1 -0
- package/lib/types/client/markdown/MarkdownText.d.ts +41 -0
- package/lib/types/client/markdown/MarkdownText.d.ts.map +1 -0
- package/lib/types/client/markdown/MarkdownText.js +178 -0
- package/lib/types/client/markdown/MarkdownText.js.map +1 -0
- package/lib/types/client/markdown/cjkFriendlyStrong.d.ts +9 -0
- package/lib/types/client/markdown/cjkFriendlyStrong.d.ts.map +1 -0
- package/lib/types/client/markdown/cjkFriendlyStrong.js +73 -0
- package/lib/types/client/markdown/cjkFriendlyStrong.js.map +1 -0
- package/lib/types/client/markdown/incremental.d.ts +69 -0
- package/lib/types/client/markdown/incremental.d.ts.map +1 -0
- package/lib/types/client/markdown/incremental.js +108 -0
- package/lib/types/client/markdown/incremental.js.map +1 -0
- package/lib/types/client/markdown/katex.d.ts +27 -0
- package/lib/types/client/markdown/katex.d.ts.map +1 -0
- package/lib/types/client/markdown/katex.js +88 -0
- package/lib/types/client/markdown/katex.js.map +1 -0
- package/lib/types/client/markdown/mathCompatibility.d.ts +11 -0
- package/lib/types/client/markdown/mathCompatibility.d.ts.map +1 -0
- package/lib/types/client/markdown/mathCompatibility.js +293 -0
- package/lib/types/client/markdown/mathCompatibility.js.map +1 -0
- package/lib/types/client/markdown/parse.d.ts +25 -0
- package/lib/types/client/markdown/parse.d.ts.map +1 -0
- package/lib/types/client/markdown/parse.js +42 -0
- package/lib/types/client/markdown/parse.js.map +1 -0
- package/lib/types/client/markdown/render.d.ts +121 -0
- package/lib/types/client/markdown/render.d.ts.map +1 -0
- package/lib/types/client/markdown/render.js +452 -0
- package/lib/types/client/markdown/render.js.map +1 -0
- package/lib/types/client/mcp-app.d.ts +50 -0
- package/lib/types/client/mcp-app.d.ts.map +1 -0
- package/lib/types/client/mcp-app.js +328 -0
- package/lib/types/client/mcp-app.js.map +1 -0
- package/lib/types/client/message-chrome.d.ts +19 -0
- package/lib/types/client/message-chrome.d.ts.map +1 -0
- package/lib/types/client/message-chrome.js +42 -0
- package/lib/types/client/message-chrome.js.map +1 -0
- package/lib/types/client/motion.d.ts +38 -0
- package/lib/types/client/motion.d.ts.map +1 -0
- package/lib/types/client/motion.js +453 -0
- package/lib/types/client/motion.js.map +1 -0
- package/lib/types/client/native/ContextBody.d.ts +130 -0
- package/lib/types/client/native/ContextBody.d.ts.map +1 -0
- package/lib/types/client/native/ContextBody.js +398 -0
- package/lib/types/client/native/ContextBody.js.map +1 -0
- package/lib/types/client/native/ContextInjectionRow.d.ts +35 -0
- package/lib/types/client/native/ContextInjectionRow.d.ts.map +1 -0
- package/lib/types/client/native/ContextInjectionRow.js +42 -0
- package/lib/types/client/native/ContextInjectionRow.js.map +1 -0
- package/lib/types/client/native/ReferenceIcon.d.ts +16 -0
- package/lib/types/client/native/ReferenceIcon.d.ts.map +1 -0
- package/lib/types/client/native/ReferenceIcon.js +16 -0
- package/lib/types/client/native/ReferenceIcon.js.map +1 -0
- package/lib/types/client/native/tool-call-model.d.ts +58 -0
- package/lib/types/client/native/tool-call-model.d.ts.map +1 -0
- package/lib/types/client/native/tool-call-model.js +219 -0
- package/lib/types/client/native/tool-call-model.js.map +1 -0
- package/lib/types/client/native/tool-update.d.ts +23 -0
- package/lib/types/client/native/tool-update.d.ts.map +1 -0
- package/lib/types/client/native/tool-update.js +22 -0
- package/lib/types/client/native/tool-update.js.map +1 -0
- package/lib/types/client/official-slots.d.ts +71 -0
- package/lib/types/client/official-slots.d.ts.map +1 -0
- package/lib/types/client/official-slots.js +183 -0
- package/lib/types/client/official-slots.js.map +1 -0
- package/lib/types/client/open-file.d.ts +29 -0
- package/lib/types/client/open-file.d.ts.map +1 -0
- package/lib/types/client/open-file.js +71 -0
- package/lib/types/client/open-file.js.map +1 -0
- package/lib/types/client/pending-submission.d.ts +28 -0
- package/lib/types/client/pending-submission.d.ts.map +1 -0
- package/lib/types/client/pending-submission.js +28 -0
- package/lib/types/client/pending-submission.js.map +1 -0
- package/lib/types/client/platform-media.d.ts +6 -0
- package/lib/types/client/platform-media.d.ts.map +1 -0
- package/lib/types/client/platform-media.js +18 -0
- package/lib/types/client/platform-media.js.map +1 -0
- package/lib/types/client/primitive-labels.d.ts +10 -0
- package/lib/types/client/primitive-labels.d.ts.map +1 -0
- package/lib/types/client/primitive-labels.js +38 -0
- package/lib/types/client/primitive-labels.js.map +1 -0
- package/lib/types/client/projection.d.ts +41 -0
- package/lib/types/client/projection.d.ts.map +1 -0
- package/lib/types/client/projection.js +112 -0
- package/lib/types/client/projection.js.map +1 -0
- package/lib/types/client/reasoning-follow.d.ts +10 -0
- package/lib/types/client/reasoning-follow.d.ts.map +1 -0
- package/lib/types/client/reasoning-follow.js +21 -0
- package/lib/types/client/reasoning-follow.js.map +1 -0
- package/lib/types/client/resource-address.d.ts +3 -0
- package/lib/types/client/resource-address.d.ts.map +1 -0
- package/lib/types/client/resource-address.js +33 -0
- package/lib/types/client/resource-address.js.map +1 -0
- package/lib/types/client/settings-copy.d.ts +7 -0
- package/lib/types/client/settings-copy.d.ts.map +1 -0
- package/lib/types/client/settings-copy.js +53 -0
- package/lib/types/client/settings-copy.js.map +1 -0
- package/lib/types/client/settings-slots.d.ts +23 -0
- package/lib/types/client/settings-slots.d.ts.map +1 -0
- package/lib/types/client/settings-slots.js +2 -0
- package/lib/types/client/settings-slots.js.map +1 -0
- package/lib/types/client/settings.d.ts +4 -0
- package/lib/types/client/settings.d.ts.map +1 -0
- package/lib/types/client/settings.js +60 -0
- package/lib/types/client/settings.js.map +1 -0
- package/lib/types/client/skill-status.d.ts +25 -0
- package/lib/types/client/skill-status.d.ts.map +1 -0
- package/lib/types/client/skill-status.js +44 -0
- package/lib/types/client/skill-status.js.map +1 -0
- package/lib/types/client/store.d.ts +35 -0
- package/lib/types/client/store.d.ts.map +1 -0
- package/lib/types/client/store.js +37 -0
- package/lib/types/client/store.js.map +1 -0
- package/lib/types/client/stream-buffer.d.ts +37 -0
- package/lib/types/client/stream-buffer.d.ts.map +1 -0
- package/lib/types/client/stream-buffer.js +150 -0
- package/lib/types/client/stream-buffer.js.map +1 -0
- package/lib/types/client/streaming.d.ts +18 -0
- package/lib/types/client/streaming.d.ts.map +1 -0
- package/lib/types/client/streaming.js +84 -0
- package/lib/types/client/streaming.js.map +1 -0
- package/lib/types/client/timeline.d.ts +26 -0
- package/lib/types/client/timeline.d.ts.map +1 -0
- package/lib/types/client/timeline.js +48 -0
- package/lib/types/client/timeline.js.map +1 -0
- package/lib/types/client/tool-activity.d.ts +98 -0
- package/lib/types/client/tool-activity.d.ts.map +1 -0
- package/lib/types/client/tool-activity.js +301 -0
- package/lib/types/client/tool-activity.js.map +1 -0
- package/lib/types/client/types.d.ts +91 -0
- package/lib/types/client/types.d.ts.map +1 -0
- package/lib/types/client/types.js +2 -0
- package/lib/types/client/types.js.map +1 -0
- package/lib/types/client/waiting-clock.d.ts +49 -0
- package/lib/types/client/waiting-clock.d.ts.map +1 -0
- package/lib/types/client/waiting-clock.js +80 -0
- package/lib/types/client/waiting-clock.js.map +1 -0
- package/lib/types/client/word-motion.d.ts +16 -0
- package/lib/types/client/word-motion.d.ts.map +1 -0
- package/lib/types/client/word-motion.js +90 -0
- package/lib/types/client/word-motion.js.map +1 -0
- package/lib/types/client/word-timeline.d.ts +32 -0
- package/lib/types/client/word-timeline.d.ts.map +1 -0
- package/lib/types/client/word-timeline.js +119 -0
- package/lib/types/client/word-timeline.js.map +1 -0
- package/lib/types/dsh-better-display.d.ts +17 -0
- package/lib/types/dsh-better-display.d.ts.map +1 -0
- package/lib/types/dsh-better-display.js +93 -0
- package/lib/types/dsh-better-display.js.map +1 -0
- package/lib/types/skill-roots.d.ts +22 -0
- package/lib/types/skill-roots.d.ts.map +1 -0
- package/lib/types/skill-roots.js +126 -0
- package/lib/types/skill-roots.js.map +1 -0
- package/lib/types/skill-status.d.ts +36 -0
- package/lib/types/skill-status.d.ts.map +1 -0
- package/lib/types/skill-status.js +51 -0
- package/lib/types/skill-status.js.map +1 -0
- package/package.json +133 -0
- package/scripts/build-motion-fixture.mjs +26 -0
- package/scripts/check-harness-compat.mjs +71 -0
- package/scripts/link-harness-dependencies.mjs +54 -0
- package/scripts/verify-auto-fold.mjs +114 -0
- package/scripts/verify-official-bridge.mjs +121 -0
- package/scripts/verify-tool-shimmer.mjs +62 -0
- package/skills/generative-mcpapps/SKILL.md +47 -0
- package/skills/generative-mcpapps/examples/interactive_quiz.html +153 -0
- package/skills/generative-mcpapps/references/html_boilerplate.md +122 -0
- package/skills/generative-mcpapps/references/sep1865_protocol.md +54 -0
- package/src/client/Blocks.tsx +213 -0
- package/src/client/ChoreographedFlow.tsx +232 -0
- package/src/client/ClosedProcessSummary.tsx +23 -0
- package/src/client/DiffPanel.tsx +124 -0
- package/src/client/LiveFold.tsx +43 -0
- package/src/client/McpAppFrame.module.css +210 -0
- package/src/client/McpAppFrame.tsx +317 -0
- package/src/client/OfficialContent.tsx +69 -0
- package/src/client/Reader.module.css +692 -0
- package/src/client/Reader.tsx +878 -0
- package/src/client/ReasoningCard.tsx +306 -0
- package/src/client/SettingsSection.module.css +184 -0
- package/src/client/SettingsSection.tsx +194 -0
- package/src/client/StickyLane.tsx +34 -0
- package/src/client/TimelineRail.module.css +170 -0
- package/src/client/TimelineRail.tsx +262 -0
- package/src/client/ToolActivity.tsx +274 -0
- package/src/client/TurnMetrics.module.css +139 -0
- package/src/client/TurnMetrics.tsx +160 -0
- package/src/client/WaitClock.tsx +33 -0
- package/src/client/WaitingStatus.tsx +13 -0
- package/src/client/conversation-scroll.ts +28 -0
- package/src/client/deliverables.ts +122 -0
- package/src/client/entry-policy.ts +20 -0
- package/src/client/entry.tsx +40 -0
- package/src/client/fold-choreography.ts +55 -0
- package/src/client/fold-intensity.ts +36 -0
- package/src/client/index.tsx +222 -0
- package/src/client/live-turn.ts +198 -0
- package/src/client/markdown/MarkdownText.module.css +304 -0
- package/src/client/markdown/MarkdownText.tsx +195 -0
- package/src/client/markdown/cjkFriendlyStrong.ts +84 -0
- package/src/client/markdown/incremental.ts +131 -0
- package/src/client/markdown/katex.tsx +91 -0
- package/src/client/markdown/mathCompatibility.ts +350 -0
- package/src/client/markdown/parse.ts +45 -0
- package/src/client/markdown/render.tsx +647 -0
- package/src/client/markdown/upstream.json +14 -0
- package/src/client/mcp-app.ts +323 -0
- package/src/client/message-chrome.ts +47 -0
- package/src/client/motion.tsx +410 -0
- package/src/client/native/ContextBody.module.css +161 -0
- package/src/client/native/ContextBody.tsx +591 -0
- package/src/client/native/ContextInjectionRow.module.css +66 -0
- package/src/client/native/ContextInjectionRow.tsx +106 -0
- package/src/client/native/ReferenceIcon.tsx +35 -0
- package/src/client/native/tool-call-model.ts +248 -0
- package/src/client/native/tool-update.ts +32 -0
- package/src/client/native/upstream.json +41 -0
- package/src/client/official-slots.tsx +211 -0
- package/src/client/open-file.ts +94 -0
- package/src/client/pending-submission.ts +49 -0
- package/src/client/platform-media.ts +17 -0
- package/src/client/primitive-labels.ts +54 -0
- package/src/client/projection.ts +118 -0
- package/src/client/reasoning-follow.ts +28 -0
- package/src/client/settings-copy.ts +80 -0
- package/src/client/settings-slots.ts +20 -0
- package/src/client/settings.tsx +73 -0
- package/src/client/skill-status.ts +78 -0
- package/src/client/store.ts +75 -0
- package/src/client/stream-buffer.ts +136 -0
- package/src/client/streaming.tsx +70 -0
- package/src/client/style.d.ts +4 -0
- package/src/client/timeline.ts +77 -0
- package/src/client/tool-activity.ts +298 -0
- package/src/client/types.ts +89 -0
- package/src/client/waiting-clock.ts +83 -0
- package/src/client/word-motion.tsx +92 -0
- package/src/client/word-timeline.ts +113 -0
- package/src/dsh-better-display.ts +108 -0
- package/src/skill-roots.ts +138 -0
- package/src/skill-status.ts +81 -0
- package/tests/browser-auto-fold.tsx +64 -0
- package/tests/browser-closed-gap.tsx +20 -0
- package/tests/browser-motion.tsx +65 -0
- package/tests/browser-official-bridge.tsx +152 -0
- package/tests/browser-sticky-lanes.tsx +22 -0
- package/tests/browser-waiting-clock.tsx +14 -0
- package/tests/chat-flow-hook.test.ts +66 -0
- package/tests/cloud-gate.test.ts +34 -0
- package/tests/context-tool-update.test.ts +33 -0
- package/tests/conversation-scroll.test.ts +47 -0
- package/tests/deliverables.test.ts +128 -0
- package/tests/entry.test.ts +42 -0
- package/tests/fixtures/maid-composer-hide.html +31 -0
- package/tests/fold-choreography.test.ts +54 -0
- package/tests/fold-intensity.test.ts +94 -0
- package/tests/footer-precedence.test.ts +129 -0
- package/tests/history-word-regression.test.ts +16 -0
- package/tests/live-turn.test.ts +218 -0
- package/tests/mcp-apps.test.ts +157 -0
- package/tests/message-chrome.test.ts +29 -0
- package/tests/official-slots.test.ts +112 -0
- package/tests/open-file.test.ts +114 -0
- package/tests/pending-submission.test.ts +45 -0
- package/tests/platform-media.test.ts +23 -0
- package/tests/projection.test.ts +184 -0
- package/tests/reasoning-follow.test.ts +34 -0
- package/tests/settings-copy.test.ts +63 -0
- package/tests/skill-status.test.ts +137 -0
- package/tests/stock-install.test.ts +58 -0
- package/tests/stream-buffer.test.ts +102 -0
- package/tests/timeline.test.ts +59 -0
- package/tests/tool-diff.test.ts +57 -0
- package/tests/waiting-clock.test.ts +83 -0
- package/tests/word-timeline.test.ts +146 -0
- package/tsconfig.json +11 -0
- package/tsdown.config.ts +51 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import type { AssistantChatData, ChatConversationViewNode } from '@deepseek-ai/dsh-client-ui-chat/client';
|
|
2
|
+
import type { AssistantBlock } from '@deepseek-ai/dsh-client-ui-conversation/client';
|
|
3
|
+
import { assistantSegments, hasVisibleBody } from './projection.js';
|
|
4
|
+
import type { TurnBoundary } from './projection.js';
|
|
5
|
+
import { activitySummary, stringValue } from './tool-activity.js';
|
|
6
|
+
import type { ReaderFlowEntry, ToolActivityEntry } from './tool-activity.js';
|
|
7
|
+
|
|
8
|
+
export type LiveStep =
|
|
9
|
+
| { kind: 'reasoning'; key: string; nodeKey: string; start: number; blocks: AssistantBlock[]; step: number }
|
|
10
|
+
| { kind: 'body'; key: string; nodeKey: string; start: number; blocks: AssistantBlock[]; step: number }
|
|
11
|
+
| { kind: 'tool'; key: string; entry: ToolActivityEntry }
|
|
12
|
+
| { kind: 'user'; key: string; nodeKey: string }
|
|
13
|
+
| { kind: 'other'; key: string; nodeKey: string };
|
|
14
|
+
|
|
15
|
+
export type LiveTurnItem =
|
|
16
|
+
| { kind: 'user'; key: string; step: Extract<LiveStep, { kind: 'user' }> }
|
|
17
|
+
| { kind: 'fold'; key: string; steps: readonly LiveStep[]; summary: string }
|
|
18
|
+
| { kind: 'open'; key: string; step: LiveStep };
|
|
19
|
+
|
|
20
|
+
export function liveFoldEnabled(boundary: TurnBoundary): boolean {
|
|
21
|
+
return boundary.status === 'open';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function foldSummary(steps: readonly LiveStep[]): string {
|
|
25
|
+
let reasoning = 0;
|
|
26
|
+
let body = 0;
|
|
27
|
+
let tool = 0;
|
|
28
|
+
let extra = 0;
|
|
29
|
+
for (const step of steps) {
|
|
30
|
+
if (step.kind === 'reasoning') reasoning += 1;
|
|
31
|
+
else if (step.kind === 'body') body += 1;
|
|
32
|
+
else if (step.kind === 'tool') tool += 1;
|
|
33
|
+
else if (step.kind !== 'user') extra += 1;
|
|
34
|
+
}
|
|
35
|
+
const parts: string[] = [];
|
|
36
|
+
if (reasoning) parts.push(`思考×${reasoning}`);
|
|
37
|
+
if (body) parts.push(`输出×${body}`);
|
|
38
|
+
if (tool) parts.push(`工具×${tool}`);
|
|
39
|
+
if (extra) parts.push(`记录×${extra}`);
|
|
40
|
+
return parts.join(' · ') || '此前步骤';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** One chain: fold only when a new reasoning step has prior body/tool/reasoning. */
|
|
44
|
+
export function splitChain(chain: readonly LiveStep[]): { fold: readonly LiveStep[] | null; open: readonly LiveStep[] } {
|
|
45
|
+
const lastReasoning = chain.findLastIndex(step => step.kind === 'reasoning');
|
|
46
|
+
if (lastReasoning < 0) return { fold: null, open: chain };
|
|
47
|
+
const prior = chain.slice(0, lastReasoning);
|
|
48
|
+
if (!prior.length) return { fold: null, open: chain };
|
|
49
|
+
const trigger = prior.some(step => step.kind === 'reasoning' || step.kind === 'body' || step.kind === 'tool');
|
|
50
|
+
if (!trigger) return { fold: null, open: chain };
|
|
51
|
+
return { fold: prior, open: chain.slice(lastReasoning) };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function skipReasoning(part: { kind: string; blocks: AssistantBlock[] }): boolean {
|
|
55
|
+
return part.kind === 'reasoning' && !part.blocks.some(block => block.kind === 'reasoning' && block.text.trim() !== '');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function skipBody(part: { kind: string; blocks: AssistantBlock[] }): boolean {
|
|
59
|
+
return part.kind === 'body' && !hasVisibleBody(part.blocks);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function stepsFromAssistant(
|
|
63
|
+
nodeKey: string,
|
|
64
|
+
data: AssistantChatData,
|
|
65
|
+
toolsByCallId: Map<string, ToolActivityEntry>,
|
|
66
|
+
consumed: Set<string>,
|
|
67
|
+
): LiveStep[] {
|
|
68
|
+
const marks: { at: number; step: LiveStep }[] = [];
|
|
69
|
+
for (const part of assistantSegments(data.blocks)) {
|
|
70
|
+
if (skipReasoning(part) || skipBody(part)) continue;
|
|
71
|
+
marks.push({
|
|
72
|
+
at: part.start,
|
|
73
|
+
step: {
|
|
74
|
+
kind: part.kind,
|
|
75
|
+
key: `${nodeKey}:${part.kind}:${part.start}`,
|
|
76
|
+
nodeKey,
|
|
77
|
+
start: part.start,
|
|
78
|
+
blocks: part.blocks,
|
|
79
|
+
step: data.step,
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
data.blocks.forEach((block, index) => {
|
|
84
|
+
if (block.kind !== 'tool-call' || !block.callId) return;
|
|
85
|
+
const tool = toolsByCallId.get(block.callId);
|
|
86
|
+
if (!tool || consumed.has(tool.callId)) return;
|
|
87
|
+
consumed.add(tool.callId);
|
|
88
|
+
marks.push({ at: index, step: { kind: 'tool', key: tool.key, entry: tool } });
|
|
89
|
+
});
|
|
90
|
+
marks.sort((left, right) => left.at - right.at || left.step.key.localeCompare(right.step.key));
|
|
91
|
+
return marks.map(mark => mark.step);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Expand readerFlow into source-ordered live steps using existing block boundaries. */
|
|
95
|
+
export function segmentLiveTurn(
|
|
96
|
+
flow: readonly ReaderFlowEntry[],
|
|
97
|
+
get: (key: string) => ChatConversationViewNode | undefined,
|
|
98
|
+
): LiveStep[] {
|
|
99
|
+
const steps: LiveStep[] = [];
|
|
100
|
+
const consumed = new Set<string>();
|
|
101
|
+
const toolsByCallId = new Map<string, ToolActivityEntry>();
|
|
102
|
+
for (const entry of flow) {
|
|
103
|
+
if (entry.kind === 'tool') toolsByCallId.set(entry.callId, entry);
|
|
104
|
+
}
|
|
105
|
+
for (const entry of flow) {
|
|
106
|
+
if (entry.kind === 'tool') {
|
|
107
|
+
if (!consumed.has(entry.callId)) {
|
|
108
|
+
steps.push({ kind: 'tool', key: entry.key, entry });
|
|
109
|
+
consumed.add(entry.callId);
|
|
110
|
+
}
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
const node = get(entry.nodeKey);
|
|
114
|
+
if (!node || node.visibility === 'hidden' || node.kind === 'turn-tail') continue;
|
|
115
|
+
if (node.kind === 'user' || node.kind === 'steering') {
|
|
116
|
+
steps.push({ kind: 'user', key: entry.key, nodeKey: entry.nodeKey });
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
if (node.kind === 'assistant-step') {
|
|
120
|
+
steps.push(...stepsFromAssistant(entry.nodeKey, node.data as AssistantChatData, toolsByCallId, consumed));
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
steps.push({ kind: 'other', key: entry.key, nodeKey: entry.nodeKey });
|
|
124
|
+
}
|
|
125
|
+
return steps;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** One finished tool call, described by what it actually did. */
|
|
129
|
+
function toolSummary(entry: ToolActivityEntry): string {
|
|
130
|
+
const info = activitySummary(entry);
|
|
131
|
+
const clip = (value: string | undefined, max = 38): string | undefined => {
|
|
132
|
+
if (!value) return undefined;
|
|
133
|
+
const flat = value.replace(/\s+/gu, ' ').trim();
|
|
134
|
+
if (!flat) return undefined;
|
|
135
|
+
return flat.length > max ? flat.slice(0, max - 1) + '\u2026' : flat;
|
|
136
|
+
};
|
|
137
|
+
const base = info.target ? clip(info.target.split(/[/\\]/u).at(-1), 28) : undefined;
|
|
138
|
+
const said = clip(stringValue(info.args, 'description'), 40);
|
|
139
|
+
switch (info.category) {
|
|
140
|
+
case 'read': return '读取 ' + (base ?? '文件');
|
|
141
|
+
case 'write': return clip(info.title, 40) ?? '写入文件';
|
|
142
|
+
case 'terminal': {
|
|
143
|
+
const cmd = clip(info.command, 32);
|
|
144
|
+
return said ?? (cmd ? '运行 ' + cmd : '运行命令');
|
|
145
|
+
}
|
|
146
|
+
case 'search': {
|
|
147
|
+
const needle = clip(stringValue(info.args, 'pattern', 'query'), 26);
|
|
148
|
+
return info.name === 'glob' ? '查找 ' + (needle ?? '文件') : '搜索 ' + (needle ?? '内容');
|
|
149
|
+
}
|
|
150
|
+
case 'web': {
|
|
151
|
+
const query = clip(info.target, 30);
|
|
152
|
+
return (info.name === 'web_search' ? '搜索网页' : '读取网页') + (query ? ' ' + query : '');
|
|
153
|
+
}
|
|
154
|
+
default: {
|
|
155
|
+
// Schema-less tools (run_code and friends) describe themselves in an
|
|
156
|
+
// argument rather than a known field: prefer the model's own
|
|
157
|
+
// description, then the first meaningful line of the code it ran.
|
|
158
|
+
if (said) return said;
|
|
159
|
+
const code = stringValue(info.args, 'code', 'source', 'script');
|
|
160
|
+
if (code) {
|
|
161
|
+
const line = code.split('\n').map(part => part.trim())
|
|
162
|
+
.find(part => part && !/^[)\]}]/.test(part) && !/^(\/\/|\*|\/\*|#)/.test(part));
|
|
163
|
+
if (line) return clip(line.replace(/\s+/gu, ' '), 44) ?? '运行代码';
|
|
164
|
+
}
|
|
165
|
+
return clip(info.title ?? info.name, 32) ?? '工具调用';
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function presentLiveTurn(steps: readonly LiveStep[], boundary: TurnBoundary, autoFold = true): LiveTurnItem[] {
|
|
171
|
+
const live = autoFold && liveFoldEnabled(boundary);
|
|
172
|
+
const items: LiveTurnItem[] = [];
|
|
173
|
+
let chain: LiveStep[] = [];
|
|
174
|
+
const flush = () => {
|
|
175
|
+
if (!chain.length) return;
|
|
176
|
+
if (!live) {
|
|
177
|
+
for (const step of chain) items.push({ kind: 'open', key: step.key, step });
|
|
178
|
+
chain = [];
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
const { fold, open } = splitChain(chain);
|
|
182
|
+
if (fold?.length) {
|
|
183
|
+
items.push({ kind: 'fold', key: `live-fold:${chain[0]!.key}`, steps: fold, summary: foldSummary(fold) });
|
|
184
|
+
}
|
|
185
|
+
for (const step of open) items.push({ kind: 'open', key: step.key, step });
|
|
186
|
+
chain = [];
|
|
187
|
+
};
|
|
188
|
+
for (const step of steps) {
|
|
189
|
+
if (step.kind === 'user') {
|
|
190
|
+
flush();
|
|
191
|
+
items.push({ kind: 'user', key: step.key, step });
|
|
192
|
+
} else {
|
|
193
|
+
chain.push(step);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
flush();
|
|
197
|
+
return items;
|
|
198
|
+
}
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
/* Visual baseline: deepsuite `@deepseek/md` markdown.css, adapted to CSS
|
|
2
|
+
Modules. Cite pills, header anchors, and thinking-small variants are
|
|
3
|
+
intentionally absent (no matching DOM). Token names match that sheet. */
|
|
4
|
+
|
|
5
|
+
.markdown {
|
|
6
|
+
min-width: 0;
|
|
7
|
+
overflow-wrap: anywhere;
|
|
8
|
+
font: var(--dsw-font-markdown-base);
|
|
9
|
+
color: var(--dsw-alias-label-primary);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.markdown strong {
|
|
13
|
+
font-weight: 600;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.markdown h1 {
|
|
17
|
+
font: var(--dsw-font-markdown-h1);
|
|
18
|
+
margin: 32px 0 16px;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.markdown h2 {
|
|
22
|
+
font: var(--dsw-font-markdown-h2);
|
|
23
|
+
margin: 32px 0 16px;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.markdown h3 {
|
|
27
|
+
font: var(--dsw-font-markdown-h3);
|
|
28
|
+
margin: 32px 0 16px;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.markdown h4 {
|
|
32
|
+
font: var(--dsw-font-markdown-h4);
|
|
33
|
+
margin: 16px 0;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.markdown :where(h5, h6) {
|
|
37
|
+
font: var(--dsw-font-markdown-base-strong);
|
|
38
|
+
margin: 16px 0;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.markdown :where(h1, h2, h3, h4, h5, h6) strong {
|
|
42
|
+
font-weight: inherit;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.markdown p {
|
|
46
|
+
margin: 16px 0;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/* Tighten h4–h6 against a following list (design: 8px gap). */
|
|
50
|
+
.markdown :where(h4, h5, h6) + :where(ul, ol) {
|
|
51
|
+
margin-top: 8px;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.markdown :where(h4, h5, h6):has(+ :where(ul, ol)) {
|
|
55
|
+
margin-bottom: 8px;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.markdown a {
|
|
59
|
+
/* deepsuite markdown.css uses brand-text (blue in newDesign); this sheet
|
|
60
|
+
keeps design-platform brand-text as near-black, so links use the blue
|
|
61
|
+
business-primary alias instead. */
|
|
62
|
+
color: var(--dsw-alias-state-business-primary);
|
|
63
|
+
transition: box-shadow var(--ds-transition-duration) var(--ds-ease-in-out);
|
|
64
|
+
position: relative;
|
|
65
|
+
text-decoration: none;
|
|
66
|
+
/* Transparent hit-area padding; literal zero-alpha only (no painted color). */
|
|
67
|
+
border-left: 3px solid rgb(255 255 255 / 0);
|
|
68
|
+
border-right: 3px solid rgb(255 255 255 / 0);
|
|
69
|
+
border-top: 2px solid rgb(255 255 255 / 0);
|
|
70
|
+
border-bottom: 2px solid rgb(255 255 255 / 0);
|
|
71
|
+
margin-left: -3px;
|
|
72
|
+
margin-right: -3px;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.markdown a:hover,
|
|
76
|
+
.markdown a:focus {
|
|
77
|
+
outline: none;
|
|
78
|
+
text-decoration: underline var(--dsw-alias-state-business-primary);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.markdown a:focus-visible {
|
|
82
|
+
box-shadow: 0 0 0 2px var(--dsw-alias-state-business-primary);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.markdown :where(ul, ol) {
|
|
86
|
+
margin: 16px 0;
|
|
87
|
+
padding-left: 18px;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.markdown li:not(:first-child) {
|
|
91
|
+
margin-top: 6px;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.markdown li > :where(ul, ol) {
|
|
95
|
+
margin-top: 4px;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.markdown li::marker {
|
|
99
|
+
line-height: 28px;
|
|
100
|
+
color: var(--dsw-alias-label-secondary);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/* Nested ol under ul/ol: markers inside (models sometimes emit this shape). */
|
|
104
|
+
.markdown :where(ul, ol) ol {
|
|
105
|
+
list-style-position: inside;
|
|
106
|
+
padding-left: 0;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.markdown :where(ul, ol) ol li p {
|
|
110
|
+
display: inline;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.markdown li > p {
|
|
114
|
+
margin: 8px 0;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.markdown li > *:first-child {
|
|
118
|
+
margin-top: 0;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/* Keep list-nested code-block vertical margins (design: +4px vs other last children). */
|
|
122
|
+
.markdown li > *:last-child:not(:global(.md-code-block)) {
|
|
123
|
+
margin-bottom: 0;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.markdown hr {
|
|
127
|
+
display: block;
|
|
128
|
+
border: none;
|
|
129
|
+
height: 1px;
|
|
130
|
+
margin: 32px 0;
|
|
131
|
+
background: var(--dsw-alias-border-l2);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.markdown blockquote {
|
|
135
|
+
border-left: 2px solid var(--dsw-alias-label-caption);
|
|
136
|
+
margin: 16px 0 0;
|
|
137
|
+
padding-left: 14px;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.markdown pre {
|
|
141
|
+
margin: 16px 0;
|
|
142
|
+
font-family: var(--ds-font-family-code);
|
|
143
|
+
overflow: auto;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.markdown :not(pre) > code {
|
|
147
|
+
display: inline-flex;
|
|
148
|
+
align-items: center;
|
|
149
|
+
box-sizing: border-box;
|
|
150
|
+
font: var(--dsw-font-markdown-code);
|
|
151
|
+
font-family: var(--ds-font-family-code);
|
|
152
|
+
font-size: 0.875em !important;
|
|
153
|
+
background-color: var(--dsw-alias-markdown-inline-code);
|
|
154
|
+
border-radius: 6px;
|
|
155
|
+
padding: 0 5px;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.markdown :where(h1, h2, h3, h4, h5, h6) code {
|
|
159
|
+
font: inherit;
|
|
160
|
+
font-family: var(--ds-font-family-code);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.markdown :global(.katex-display) {
|
|
164
|
+
max-width: 100%;
|
|
165
|
+
overflow-x: auto;
|
|
166
|
+
overflow-y: hidden;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.markdown input[type='checkbox'] {
|
|
170
|
+
margin: 0 8px 0 0;
|
|
171
|
+
accent-color: var(--dsw-alias-label-secondary);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.tableScroll {
|
|
175
|
+
max-width: 100%;
|
|
176
|
+
overflow-x: auto;
|
|
177
|
+
overscroll-behavior-x: contain;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/* Wide tables reveal their horizontal bar on hover (or keyboard focus)
|
|
181
|
+
instead of keeping it painted. Chromium never repaints state-conditioned
|
|
182
|
+
scrollbar STYLES (neither hover-conditioned `::-webkit-scrollbar*` rules
|
|
183
|
+
nor a :hover `scrollbar-color` change reaches the painted bar), so the
|
|
184
|
+
toggle is `overflow-x` itself — a layout change repaints reliably. The
|
|
185
|
+
resting padding matches the themed bar's height, so on an overflowing
|
|
186
|
+
table the appearing bar exactly replaces it and nothing below shifts.
|
|
187
|
+
Wheel and trackpad scrolling need the pointer over the table, which is
|
|
188
|
+
already the hover that re-enables `auto`. */
|
|
189
|
+
.tableScroll:global(.md-table-wide) {
|
|
190
|
+
overflow-x: hidden;
|
|
191
|
+
padding-bottom: var(--dsh-scrollbar-width, 8px);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.tableScroll:global(.md-table-wide):hover,
|
|
195
|
+
.tableScroll:global(.md-table-wide):focus-visible {
|
|
196
|
+
overflow-x: auto;
|
|
197
|
+
padding-bottom: 0;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/* Chromium keyboard-focuses scrollable containers by default; the ring uses
|
|
201
|
+
the sheet's link focus color. */
|
|
202
|
+
.tableScroll:focus-visible {
|
|
203
|
+
outline: none;
|
|
204
|
+
box-shadow: 0 0 0 2px var(--dsw-alias-state-business-primary);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/* Many-column tables (the renderer's `md-table-wide` hook) keep their
|
|
208
|
+
natural width and scroll inside the wrapper; a hosting layout may widen
|
|
209
|
+
the wrapper through the hook (deepsuite chat TableWrapper parity). */
|
|
210
|
+
.tableScroll table {
|
|
211
|
+
border-collapse: collapse;
|
|
212
|
+
width: max-content;
|
|
213
|
+
max-width: max-content;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/* Tables under four columns, and any table inside a blockquote, fill the
|
|
217
|
+
column instead: cells wrap down to their minimum readable width, and the
|
|
218
|
+
wrapper's scroll only remains for the below-floor case (deepsuite chat
|
|
219
|
+
parity: `.wrapper:not(:has(th:nth-child(4), td:nth-child(4)))`). */
|
|
220
|
+
.tableFill table {
|
|
221
|
+
width: 100%;
|
|
222
|
+
max-width: none;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.tableScroll th {
|
|
226
|
+
text-align: start;
|
|
227
|
+
padding: 10px 16px;
|
|
228
|
+
border-bottom: 1px solid var(--dsw-alias-border-l3);
|
|
229
|
+
border-top: none;
|
|
230
|
+
font: var(--dsw-font-markdown-table-head);
|
|
231
|
+
max-width: 320px;
|
|
232
|
+
max-width: min(30vw, 320px);
|
|
233
|
+
min-width: 100px;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.tableScroll td {
|
|
237
|
+
padding: 10px 16px;
|
|
238
|
+
border-bottom: 1px solid var(--dsw-alias-border-l2);
|
|
239
|
+
font: var(--dsw-font-markdown-table);
|
|
240
|
+
max-width: 320px;
|
|
241
|
+
max-width: min(30vw, 320px);
|
|
242
|
+
min-width: 100px;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
.tableScroll th:first-child,
|
|
246
|
+
.tableScroll td:first-child {
|
|
247
|
+
padding-left: 0;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
.tableScroll td:last-child {
|
|
251
|
+
padding-right: 0;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
.tableScroll table code {
|
|
255
|
+
font-size: 13px;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.markdown > *:first-child,
|
|
259
|
+
.markdown p:first-child {
|
|
260
|
+
margin-top: 0 !important;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
.markdown > *:last-child,
|
|
264
|
+
.markdown p:last-child {
|
|
265
|
+
margin-bottom: 0 !important;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.imageAlt {
|
|
269
|
+
color: var(--dsw-alias-label-tertiary);
|
|
270
|
+
font-style: italic;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
.image {
|
|
274
|
+
display: block;
|
|
275
|
+
width: auto;
|
|
276
|
+
max-width: 100%;
|
|
277
|
+
height: auto;
|
|
278
|
+
margin: 0;
|
|
279
|
+
border-radius: 8px;
|
|
280
|
+
background: var(--dsw-alias-bg-base);
|
|
281
|
+
object-fit: contain;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/* Inline file mention: the same link language this sheet gives anchors (and
|
|
285
|
+
thereby URL-promoted inline code) — link-blue at rest, underline only on
|
|
286
|
+
hover/focus. An underline at rest reads badly inside the code chip, where
|
|
287
|
+
it collides with monospace descenders and the pill background. */
|
|
288
|
+
.fileMention {
|
|
289
|
+
margin: 0;
|
|
290
|
+
padding: 0;
|
|
291
|
+
border: none;
|
|
292
|
+
background: none;
|
|
293
|
+
font: inherit;
|
|
294
|
+
color: var(--dsw-alias-state-business-primary);
|
|
295
|
+
text-decoration: none;
|
|
296
|
+
cursor: pointer;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.fileMention:hover,
|
|
300
|
+
.fileMention:focus {
|
|
301
|
+
outline: none;
|
|
302
|
+
text-decoration: underline var(--dsw-alias-state-business-primary);
|
|
303
|
+
text-underline-offset: 3px;
|
|
304
|
+
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
// Adapted from DeepSeek Harness b150a551b8d465e31e418e1b2eaf5e79bbb7d28e (MIT). See THIRD_PARTY_NOTICES.md.
|
|
2
|
+
/**
|
|
3
|
+
* Untrusted assistant-Markdown renderer over the direct mdast pipeline:
|
|
4
|
+
* `parse.ts` grammars, the incremental streaming parser, and `render.tsx`.
|
|
5
|
+
* While a message streams, all but the trailing two blocks freeze as cached
|
|
6
|
+
* React elements and only the source tail behind them re-parses per chunk,
|
|
7
|
+
* so per-chunk work tracks the tail size instead of the whole reply. Frozen
|
|
8
|
+
* blocks keep their source-offset keys when they cross the freeze boundary,
|
|
9
|
+
* so React reconciles instead of remounting. Known deviation while
|
|
10
|
+
* streaming: a reference-style link or footnote whose definition sits on the
|
|
11
|
+
* other side of the freeze boundary renders literally until the settled
|
|
12
|
+
* full parse self-heals it.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { memo, useMemo, useRef } from 'react'
|
|
16
|
+
import type { ReactNode } from 'react'
|
|
17
|
+
import type { MarkdownPathImages } from '@deepseek-ai/dsh-client-ui-primitives'
|
|
18
|
+
import { IncrementalMarkdownParser } from './incremental.js'
|
|
19
|
+
import { parseGfm, parseGfmWithMath } from './parse.js'
|
|
20
|
+
import {
|
|
21
|
+
collectReferenceTargets, createReferenceTargets, renderBlocks, renderFootnoteSection,
|
|
22
|
+
wrapBlockChildren,
|
|
23
|
+
} from './render.js'
|
|
24
|
+
import type { MarkdownCodeLabels, MarkdownFileMentions, MarkdownRenderContext, ReferenceTargets } from './render.js'
|
|
25
|
+
// KaTeX CSS is supplied once by the shared DSH UI-primitives client bundle.
|
|
26
|
+
import css from './MarkdownText.module.css'
|
|
27
|
+
|
|
28
|
+
export type { MarkdownCodeLabels, MarkdownFileMentions } from './render.js'
|
|
29
|
+
|
|
30
|
+
/** One settled full render: parse with math, resolve references, append the footnote section. */
|
|
31
|
+
function renderSettled(
|
|
32
|
+
text: string,
|
|
33
|
+
codeLabels: MarkdownCodeLabels | undefined,
|
|
34
|
+
fileMentions: MarkdownFileMentions | undefined,
|
|
35
|
+
pathImages: MarkdownPathImages | undefined,
|
|
36
|
+
renderText: MarkdownRenderContext['renderText'],
|
|
37
|
+
renderAtom: MarkdownRenderContext['renderAtom'],
|
|
38
|
+
): ReactNode[] {
|
|
39
|
+
const root = parseGfmWithMath(text)
|
|
40
|
+
const targets = createReferenceTargets()
|
|
41
|
+
collectReferenceTargets(root.children, targets)
|
|
42
|
+
const context: MarkdownRenderContext = {
|
|
43
|
+
renderText,
|
|
44
|
+
renderAtom,
|
|
45
|
+
streaming: false,
|
|
46
|
+
codeLabels,
|
|
47
|
+
fileMentions,
|
|
48
|
+
pathImages,
|
|
49
|
+
targets,
|
|
50
|
+
footnoteOrder: [],
|
|
51
|
+
footnoteCounts: new Map(),
|
|
52
|
+
}
|
|
53
|
+
const blocks = wrapBlockChildren(
|
|
54
|
+
renderBlocks(root.children.map((node, index) => ({ node, key: node.position?.start.offset ?? -(index + 1) })), context),
|
|
55
|
+
false,
|
|
56
|
+
)
|
|
57
|
+
const section = renderFootnoteSection(context)
|
|
58
|
+
return section === null ? blocks : [...blocks, '\n', section]
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Streaming render state for one growing message: the incremental parser,
|
|
63
|
+
* the frozen blocks' cached elements, and the reference/footnote state their
|
|
64
|
+
* rendering consumed (footnote numbering assigned to frozen references is
|
|
65
|
+
* final, so the tail continues from a copy of it each frame).
|
|
66
|
+
*/
|
|
67
|
+
class StreamingRenderer {
|
|
68
|
+
private readonly parser = new IncrementalMarkdownParser(parseGfm)
|
|
69
|
+
private generation = -1
|
|
70
|
+
private frozenCount = 0
|
|
71
|
+
private frozenElements: ReactNode[] = []
|
|
72
|
+
private frozenTargets: ReferenceTargets = createReferenceTargets()
|
|
73
|
+
private frozenFootnoteOrder: string[] = []
|
|
74
|
+
private frozenFootnoteCounts = new Map<string, number>()
|
|
75
|
+
private lastText: string | null = null
|
|
76
|
+
private lastRendered: ReactNode[] = []
|
|
77
|
+
|
|
78
|
+
/** @param codeLabels - Fence copy labels baked into cached elements; the owner replaces the renderer when they change. */
|
|
79
|
+
constructor(private readonly codeLabels: MarkdownCodeLabels | undefined, private readonly renderText: MarkdownRenderContext['renderText'], private readonly renderAtom: MarkdownRenderContext['renderAtom'], private readonly pathImages: MarkdownPathImages | undefined) {}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Render the current accumulated text. Idempotent per text value, so React
|
|
83
|
+
* may re-execute the calling render freely.
|
|
84
|
+
* @param text - The full accumulated markdown source.
|
|
85
|
+
* @returns Frozen elements, re-rendered tail, and the footnote section.
|
|
86
|
+
*/
|
|
87
|
+
render(text: string): ReactNode[] {
|
|
88
|
+
if (text === this.lastText) return this.lastRendered
|
|
89
|
+
const { frozen, tail, generation } = this.parser.update(text)
|
|
90
|
+
if (generation !== this.generation) {
|
|
91
|
+
this.generation = generation
|
|
92
|
+
this.frozenCount = 0
|
|
93
|
+
this.frozenElements = []
|
|
94
|
+
this.frozenTargets = createReferenceTargets()
|
|
95
|
+
this.frozenFootnoteOrder = []
|
|
96
|
+
this.frozenFootnoteCounts = new Map()
|
|
97
|
+
}
|
|
98
|
+
const newlyFrozen = frozen.slice(this.frozenCount)
|
|
99
|
+
collectReferenceTargets(newlyFrozen.map(block => block.node), this.frozenTargets)
|
|
100
|
+
// Targets visible this frame: everything frozen so far plus the current
|
|
101
|
+
// tail parse — a newly frozen block's references resolved against the
|
|
102
|
+
// same parse tree its definitions came from.
|
|
103
|
+
const frameTargets: ReferenceTargets = {
|
|
104
|
+
definitions: new Map(this.frozenTargets.definitions),
|
|
105
|
+
footnotes: new Map(this.frozenTargets.footnotes),
|
|
106
|
+
}
|
|
107
|
+
collectReferenceTargets(tail.map(block => block.node), frameTargets)
|
|
108
|
+
if (newlyFrozen.length > 0) {
|
|
109
|
+
const frozenContext: MarkdownRenderContext = {
|
|
110
|
+
renderText: this.renderText,
|
|
111
|
+
renderAtom: this.renderAtom,
|
|
112
|
+
streaming: true,
|
|
113
|
+
codeLabels: this.codeLabels,
|
|
114
|
+
fileMentions: undefined,
|
|
115
|
+
pathImages: this.pathImages,
|
|
116
|
+
targets: frameTargets,
|
|
117
|
+
footnoteOrder: this.frozenFootnoteOrder,
|
|
118
|
+
footnoteCounts: this.frozenFootnoteCounts,
|
|
119
|
+
}
|
|
120
|
+
// Separator newlines are cached alongside the elements so the
|
|
121
|
+
// assembled children match the settled pipeline's block wrapping.
|
|
122
|
+
const batch = [...this.frozenElements]
|
|
123
|
+
for (const element of renderBlocks(newlyFrozen, frozenContext)) {
|
|
124
|
+
if (batch.length > 0) batch.push('\n')
|
|
125
|
+
batch.push(element)
|
|
126
|
+
}
|
|
127
|
+
this.frozenElements = batch
|
|
128
|
+
this.frozenCount = frozen.length
|
|
129
|
+
}
|
|
130
|
+
const tailContext: MarkdownRenderContext = {
|
|
131
|
+
renderText: this.renderText,
|
|
132
|
+
renderAtom: this.renderAtom,
|
|
133
|
+
streaming: true,
|
|
134
|
+
codeLabels: this.codeLabels,
|
|
135
|
+
fileMentions: undefined,
|
|
136
|
+
pathImages: this.pathImages,
|
|
137
|
+
targets: frameTargets,
|
|
138
|
+
footnoteOrder: [...this.frozenFootnoteOrder],
|
|
139
|
+
footnoteCounts: new Map(this.frozenFootnoteCounts),
|
|
140
|
+
}
|
|
141
|
+
const children = [...this.frozenElements]
|
|
142
|
+
for (const element of renderBlocks(tail, tailContext)) {
|
|
143
|
+
if (children.length > 0) children.push('\n')
|
|
144
|
+
children.push(element)
|
|
145
|
+
}
|
|
146
|
+
const section = renderFootnoteSection(tailContext)
|
|
147
|
+
if (section !== null) children.push('\n', section)
|
|
148
|
+
this.lastText = text
|
|
149
|
+
this.lastRendered = children
|
|
150
|
+
return this.lastRendered
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Render untrusted assistant-authored Markdown as semantic React elements.
|
|
156
|
+
* @param props - Markdown source text preserved by the session projection;
|
|
157
|
+
* `streaming` renders fences and TeX plain (highlighting and KaTeX land on
|
|
158
|
+
* the finalize swap) and parses incrementally across chunks; `codeLabels`
|
|
159
|
+
* forwards localized copy-button labels to fence CodeBlocks — pass a
|
|
160
|
+
* reference-stable object (memoized per locale revision), because a new
|
|
161
|
+
* identity discards the streaming render cache mid-message. `fileMentions`
|
|
162
|
+
* links inline-code tokens its resolver recognizes as real files; this is
|
|
163
|
+
* the single streaming gate — it applies to settled renders only, because a
|
|
164
|
+
* streaming message's vocabulary is not final and frozen cached elements
|
|
165
|
+
* must not bake in handlers that could go stale.
|
|
166
|
+
* @returns A GFM document with TeX math rendered through KaTeX; raw HTML,
|
|
167
|
+
* relative links, and unsafe protocols are disabled, while absolute HTTP(S)
|
|
168
|
+
* images render directly.
|
|
169
|
+
*/
|
|
170
|
+
export const MarkdownText = memo(function MarkdownText({ text, streaming = false, codeLabels, fileMentions, pathImages, renderText, renderAtom }: {
|
|
171
|
+
text: string
|
|
172
|
+
streaming?: boolean
|
|
173
|
+
codeLabels?: MarkdownCodeLabels | undefined
|
|
174
|
+
fileMentions?: MarkdownFileMentions | undefined
|
|
175
|
+
pathImages?: MarkdownPathImages | undefined
|
|
176
|
+
renderText?: MarkdownRenderContext['renderText']
|
|
177
|
+
renderAtom?: MarkdownRenderContext['renderAtom']
|
|
178
|
+
}) {
|
|
179
|
+
const streamRef = useRef<StreamingRenderer | null>(null)
|
|
180
|
+
const streamImagesRef = useRef(pathImages)
|
|
181
|
+
const streamLabelsRef = useRef<MarkdownCodeLabels | undefined>(codeLabels)
|
|
182
|
+
const children = useMemo(() => {
|
|
183
|
+
if (!streaming) {
|
|
184
|
+
streamRef.current = null
|
|
185
|
+
return renderSettled(text, codeLabels, fileMentions, pathImages, renderText, renderAtom)
|
|
186
|
+
}
|
|
187
|
+
if (streamRef.current === null || streamLabelsRef.current !== codeLabels || streamImagesRef.current !== pathImages) {
|
|
188
|
+
streamRef.current = new StreamingRenderer(codeLabels, renderText, renderAtom, pathImages)
|
|
189
|
+
streamLabelsRef.current = codeLabels
|
|
190
|
+
streamImagesRef.current = pathImages
|
|
191
|
+
}
|
|
192
|
+
return streamRef.current.render(text)
|
|
193
|
+
}, [text, streaming, codeLabels, fileMentions, pathImages, renderText, renderAtom])
|
|
194
|
+
return <div className={css.markdown}>{children}</div>
|
|
195
|
+
})
|