-
+
+
{!isStreaming && }
{isStreaming && (
@@ -31,3 +40,18 @@ export function AssistantMessage({ content, isStreaming }: Props) {
)
}
+
+function shouldUseDocumentLayout(content: string) {
+ const normalized = content.trim()
+ if (!normalized) return false
+
+ if (/```/.test(normalized)) return true
+ if (/^\s{0,3}(#{1,6}\s|[-*+]\s|\d+\.\s|>\s|\|.+\|)/m.test(normalized)) return true
+
+ const paragraphs = normalized
+ .split(/\n\s*\n/)
+ .map((chunk) => chunk.trim())
+ .filter(Boolean)
+
+ return paragraphs.length >= 2 || normalized.split('\n').filter((line) => line.trim()).length >= 8
+}
diff --git a/desktop/src/components/chat/MessageList.test.tsx b/desktop/src/components/chat/MessageList.test.tsx
index 9e46d5c8..a0b77b1d 100644
--- a/desktop/src/components/chat/MessageList.test.tsx
+++ b/desktop/src/components/chat/MessageList.test.tsx
@@ -371,10 +371,46 @@ describe('MessageList nested tool calls', () => {
expect(userShell?.className).toContain('items-end')
expect(assistantShell).toBeTruthy()
expect(assistantShell?.className).toContain('items-start')
+ expect(assistantShell?.className).not.toContain('ml-10')
expect(userActions?.getAttribute('data-align')).toBe('end')
expect(assistantActions?.getAttribute('data-align')).toBe('start')
})
+ it('uses the document column for markdown-heavy assistant replies', () => {
+ useChatStore.setState({
+ sessions: {
+ [ACTIVE_TAB]: makeSessionState({
+ messages: [
+ {
+ id: 'assistant-doc',
+ type: 'assistant_text',
+ content: [
+ '## 交付结果',
+ '',
+ '已完成以下内容:',
+ '',
+ '- 添加任务',
+ '- 删除任务',
+ '',
+ '```bash',
+ 'npm run build',
+ '```',
+ ].join('\n'),
+ timestamp: 1,
+ },
+ ],
+ }),
+ },
+ })
+
+ render(
)
+
+ const assistantShell = screen.getByText('交付结果').closest('[data-message-shell="assistant"]')
+ expect(assistantShell?.getAttribute('data-layout')).toBe('document')
+ expect(assistantShell?.className).toContain('w-full')
+ expect(assistantShell?.className).not.toContain('ml-10')
+ })
+
it('opens a rewind preview modal for user messages', async () => {
vi.spyOn(sessionsApi, 'rewind').mockResolvedValue({
target: {
diff --git a/desktop/src/components/chat/PermissionDialog.tsx b/desktop/src/components/chat/PermissionDialog.tsx
index e1de3566..8e12ac17 100644
--- a/desktop/src/components/chat/PermissionDialog.tsx
+++ b/desktop/src/components/chat/PermissionDialog.tsx
@@ -127,7 +127,7 @@ export function PermissionDialog({ requestId, toolName, input, description }: Pr
const allowRawToggle = !preview
return (
-
+
✦
{verb}...
{elapsedSeconds > 0 && (
diff --git a/desktop/src/components/chat/ThinkingBlock.tsx b/desktop/src/components/chat/ThinkingBlock.tsx
index dc97706e..3975525d 100644
--- a/desktop/src/components/chat/ThinkingBlock.tsx
+++ b/desktop/src/components/chat/ThinkingBlock.tsx
@@ -18,7 +18,7 @@ export function ThinkingBlock({ content, isActive = false }: { content: string;
const preview = firstLine.length > 80 ? firstLine.slice(0, 80) + '...' : firstLine
return (
-
+