@huyooo/ai-chat-frontend-react 0.2.12 → 0.2.14

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.
Files changed (110) hide show
  1. package/README.md +99 -84
  2. package/dist/KaTeX_AMS-Regular-CYEKBG2K.woff +0 -0
  3. package/dist/KaTeX_AMS-Regular-JKX5W2C4.ttf +0 -0
  4. package/dist/KaTeX_AMS-Regular-U6PRYMIZ.woff2 +0 -0
  5. package/dist/KaTeX_Caligraphic-Bold-5QL5CMTE.woff2 +0 -0
  6. package/dist/KaTeX_Caligraphic-Bold-WZ3QSGD3.woff +0 -0
  7. package/dist/KaTeX_Caligraphic-Bold-ZTS3R3HK.ttf +0 -0
  8. package/dist/KaTeX_Caligraphic-Regular-3LKEU76G.woff +0 -0
  9. package/dist/KaTeX_Caligraphic-Regular-A7XRTZ5Q.ttf +0 -0
  10. package/dist/KaTeX_Caligraphic-Regular-KX5MEWCF.woff2 +0 -0
  11. package/dist/KaTeX_Fraktur-Bold-2QVFK6NQ.woff2 +0 -0
  12. package/dist/KaTeX_Fraktur-Bold-T4SWXBMT.woff +0 -0
  13. package/dist/KaTeX_Fraktur-Bold-WGHVTYOR.ttf +0 -0
  14. package/dist/KaTeX_Fraktur-Regular-2PEIFJSJ.woff2 +0 -0
  15. package/dist/KaTeX_Fraktur-Regular-5U4OPH2X.ttf +0 -0
  16. package/dist/KaTeX_Fraktur-Regular-PQMHCIK6.woff +0 -0
  17. package/dist/KaTeX_Main-Bold-2GA4IZIN.woff +0 -0
  18. package/dist/KaTeX_Main-Bold-W5FBVCZM.ttf +0 -0
  19. package/dist/KaTeX_Main-Bold-YP5VVQRP.woff2 +0 -0
  20. package/dist/KaTeX_Main-BoldItalic-4P4C7HJH.woff +0 -0
  21. package/dist/KaTeX_Main-BoldItalic-N4V3DX7S.woff2 +0 -0
  22. package/dist/KaTeX_Main-BoldItalic-ODMLBJJQ.ttf +0 -0
  23. package/dist/KaTeX_Main-Italic-I43T2HSR.ttf +0 -0
  24. package/dist/KaTeX_Main-Italic-RELBIK7M.woff2 +0 -0
  25. package/dist/KaTeX_Main-Italic-SASNQFN2.woff +0 -0
  26. package/dist/KaTeX_Main-Regular-ARRPAO67.woff2 +0 -0
  27. package/dist/KaTeX_Main-Regular-P5I74A2A.woff +0 -0
  28. package/dist/KaTeX_Main-Regular-W74P5G27.ttf +0 -0
  29. package/dist/KaTeX_Math-BoldItalic-6EBV3DK5.woff +0 -0
  30. package/dist/KaTeX_Math-BoldItalic-K4WTGH3J.woff2 +0 -0
  31. package/dist/KaTeX_Math-BoldItalic-VB447A4D.ttf +0 -0
  32. package/dist/KaTeX_Math-Italic-6KGCHLFN.woff2 +0 -0
  33. package/dist/KaTeX_Math-Italic-KKK3USB2.woff +0 -0
  34. package/dist/KaTeX_Math-Italic-SON4MRCA.ttf +0 -0
  35. package/dist/KaTeX_SansSerif-Bold-RRNVJFFW.woff2 +0 -0
  36. package/dist/KaTeX_SansSerif-Bold-STQ6RXC7.ttf +0 -0
  37. package/dist/KaTeX_SansSerif-Bold-X5M5EMOD.woff +0 -0
  38. package/dist/KaTeX_SansSerif-Italic-HMPFTM52.woff2 +0 -0
  39. package/dist/KaTeX_SansSerif-Italic-PSN4QKYX.woff +0 -0
  40. package/dist/KaTeX_SansSerif-Italic-WTBAZBGY.ttf +0 -0
  41. package/dist/KaTeX_SansSerif-Regular-2TL3USAE.ttf +0 -0
  42. package/dist/KaTeX_SansSerif-Regular-OQCII6EP.woff +0 -0
  43. package/dist/KaTeX_SansSerif-Regular-XIQ62X4E.woff2 +0 -0
  44. package/dist/KaTeX_Script-Regular-72OLXYNA.ttf +0 -0
  45. package/dist/KaTeX_Script-Regular-A5IFOEBS.woff +0 -0
  46. package/dist/KaTeX_Script-Regular-APUWIHLP.woff2 +0 -0
  47. package/dist/KaTeX_Size1-Regular-4HRHTS65.woff +0 -0
  48. package/dist/KaTeX_Size1-Regular-5LRUTBFT.woff2 +0 -0
  49. package/dist/KaTeX_Size1-Regular-7K6AASVL.ttf +0 -0
  50. package/dist/KaTeX_Size2-Regular-222HN3GT.ttf +0 -0
  51. package/dist/KaTeX_Size2-Regular-K5ZHAIS6.woff +0 -0
  52. package/dist/KaTeX_Size2-Regular-LELKET5D.woff2 +0 -0
  53. package/dist/KaTeX_Size3-Regular-TLFPAHDE.woff +0 -0
  54. package/dist/KaTeX_Size3-Regular-UFCO6WCA.ttf +0 -0
  55. package/dist/KaTeX_Size3-Regular-WQRQ47UD.woff2 +0 -0
  56. package/dist/KaTeX_Size4-Regular-7PGNVPQK.ttf +0 -0
  57. package/dist/KaTeX_Size4-Regular-CDMV7U5C.woff2 +0 -0
  58. package/dist/KaTeX_Size4-Regular-PKMWZHNC.woff +0 -0
  59. package/dist/KaTeX_Typewriter-Regular-3F5K6SQ6.ttf +0 -0
  60. package/dist/KaTeX_Typewriter-Regular-MJMFSK64.woff +0 -0
  61. package/dist/KaTeX_Typewriter-Regular-VBYJ4NRC.woff2 +0 -0
  62. package/dist/index.css +2156 -603
  63. package/dist/index.css.map +1 -1
  64. package/dist/index.d.ts +126 -92
  65. package/dist/index.js +1605 -976
  66. package/dist/index.js.map +1 -1
  67. package/dist/style.css +130 -0
  68. package/package.json +3 -3
  69. package/src/components/ChatPanel.tsx +82 -19
  70. package/src/components/common/SettingsPanel.css +81 -0
  71. package/src/components/common/SettingsPanel.tsx +96 -1
  72. package/src/components/input/ChatInput.css +0 -1
  73. package/src/components/input/ChatInput.tsx +48 -26
  74. package/src/components/input/DropdownSelector.css +66 -0
  75. package/src/components/input/DropdownSelector.tsx +157 -19
  76. package/src/components/message/MessageBubble.css +5 -2
  77. package/src/components/message/MessageBubble.tsx +44 -35
  78. package/src/components/message/PartsRenderer.css +8 -0
  79. package/src/components/message/PartsRenderer.tsx +137 -83
  80. package/src/components/message/parts/CollapsibleCard.css +4 -2
  81. package/src/components/message/parts/CollapsibleCard.tsx +4 -1
  82. package/src/components/message/parts/ImagePart.css +0 -1
  83. package/src/components/message/parts/TextPart.css +574 -5
  84. package/src/components/message/parts/TextPart.tsx +201 -8
  85. package/src/components/message/parts/ToolCallPart.css +139 -115
  86. package/src/components/message/parts/ToolCallPart.tsx +138 -134
  87. package/src/components/message/parts/ToolResultPart.css +0 -1
  88. package/src/components/message/parts/index.ts +3 -1
  89. package/src/components/message/parts/visual-predicate.ts +43 -0
  90. package/src/components/message/parts/visual-render.ts +19 -0
  91. package/src/components/message/parts/visual.ts +12 -0
  92. package/src/context/RenderersContext.tsx +19 -25
  93. package/src/hooks/useChat.ts +567 -79
  94. package/src/hooks/useImageUpload.ts +104 -12
  95. package/src/hooks/useVoiceInput.ts +17 -0
  96. package/src/index.ts +19 -16
  97. package/src/styles.css +130 -0
  98. package/src/types/index.ts +52 -68
  99. package/src/components/message/ContentRenderer.tsx +0 -63
  100. package/src/components/message/ToolResultRenderer.tsx +0 -21
  101. package/src/components/message/blocks/CodeBlock.tsx +0 -60
  102. package/src/components/message/blocks/TextBlock.tsx +0 -15
  103. package/src/components/message/blocks/blocks.css +0 -141
  104. package/src/components/message/blocks/index.ts +0 -6
  105. package/src/components/message/parts/ToolResultPart.tsx +0 -96
  106. package/src/components/message/tool-results/DefaultToolResult.tsx +0 -26
  107. package/src/components/message/tool-results/SearchResults.tsx +0 -69
  108. package/src/components/message/tool-results/WeatherCard.tsx +0 -63
  109. package/src/components/message/tool-results/index.ts +0 -7
  110. package/src/components/message/tool-results/tool-results.css +0 -181
@@ -1,20 +1,213 @@
1
- import type { FC } from 'react'
2
- import { renderMarkdown } from '@huyooo/ai-chat-shared'
1
+ import { useEffect, useState, useCallback, useRef, type FC } from 'react'
2
+ import {
3
+ createStreamParseState,
4
+ finishStreamParse,
5
+ parseContentStream,
6
+ highlightCode,
7
+ renderMarkdown,
8
+ initMermaid,
9
+ renderMermaidDiagrams,
10
+ encodeMermaidCodeToBase64Url,
11
+ type ContentBlock,
12
+ type StreamParseState,
13
+ } from '@huyooo/ai-chat-shared'
14
+ import {
15
+ canVisualizeLatex,
16
+ isLatexLanguage,
17
+ isMermaidLanguage,
18
+ renderFencedLatexToHtml,
19
+ shouldShowVisualToggle,
20
+ } from './visual'
21
+ // KaTeX CSS for LaTeX rendering
22
+ import 'katex/dist/katex.min.css'
3
23
  import './TextPart.css'
4
24
 
25
+ // 初始化 Mermaid(模块加载时执行一次)
26
+ initMermaid()
27
+
5
28
  interface TextPartProps {
6
29
  text: string
7
30
  }
8
31
 
9
32
  export const TextPart: FC<TextPartProps> = ({ text }) => {
10
- if (!text) return null
33
+ const containerRef = useRef<HTMLDivElement>(null)
34
+ const streamStateRef = useRef<StreamParseState>(createStreamParseState())
35
+ const lastTextRef = useRef<string>('')
36
+ const autoSwitchedVisualRef = useRef<Set<string>>(new Set())
37
+
38
+ // 渲染块(流式解析:代码块未闭合时也以 code block 展示)
39
+ const [blocks, setBlocks] = useState<ContentBlock[]>([])
40
+
41
+ // 记录哪些 mermaid block 要显示图表(默认 undefined/false = 显示代码)
42
+ const [visualShowMap, setVisualShowMap] = useState<Record<string, boolean>>({})
11
43
 
12
- const html = renderMarkdown(text)
44
+ // 复制状态
45
+ const [copiedId, setCopiedId] = useState<string | null>(null)
46
+
47
+ // 复制代码
48
+ const copyCode = useCallback(async (content: string, id: string) => {
49
+ try {
50
+ await navigator.clipboard.writeText(content)
51
+ setCopiedId(id)
52
+ setTimeout(() => setCopiedId(null), 2000)
53
+ } catch (err) {
54
+ console.error('复制失败:', err)
55
+ }
56
+ }, [])
13
57
 
58
+ if (!text) return null
59
+
60
+ // text 变化:增量喂给流式解析器,并在 mermaid 代码块闭合后自动切换到图表视图
61
+ useEffect(() => {
62
+ const prev = lastTextRef.current
63
+ const current = text || ''
64
+
65
+ let nextState = streamStateRef.current
66
+ if (prev && current.startsWith(prev)) {
67
+ const delta = current.slice(prev.length)
68
+ if (delta) nextState = parseContentStream(delta, nextState)
69
+ } else {
70
+ nextState = createStreamParseState()
71
+ nextState = parseContentStream(current, nextState)
72
+ }
73
+
74
+ streamStateRef.current = nextState
75
+ lastTextRef.current = current
76
+
77
+ const nextBlocks = finishStreamParse(nextState)
78
+ setBlocks(nextBlocks)
79
+
80
+ // mermaid:未闭合时保持代码视图;闭合后自动切到图表视图并触发渲染
81
+ const inProgressVisualId =
82
+ nextState.inCodeBlock &&
83
+ (['mermaid', 'latex', 'katex', 'tex'].includes((nextState.codeLanguage || '').toLowerCase()))
84
+ ? nextState.codeBlockId
85
+ : null
86
+
87
+ const completedVisualIds: string[] = []
88
+ for (const b of nextState.blocks) {
89
+ if (b.type !== 'code') continue
90
+ const lang = (b.language || '').toLowerCase()
91
+ if (!['mermaid', 'latex', 'katex', 'tex'].includes(lang)) continue
92
+ if (inProgressVisualId && b.id === inProgressVisualId) continue
93
+ if (['latex', 'katex', 'tex'].includes(lang) && !canVisualizeLatex(b.content)) continue
94
+ if (!autoSwitchedVisualRef.current.has(b.id)) completedVisualIds.push(b.id)
95
+ }
96
+
97
+ if (completedVisualIds.length > 0) {
98
+ for (const id of completedVisualIds) autoSwitchedVisualRef.current.add(id)
99
+ setVisualShowMap((prevMap) => {
100
+ const nextMap = { ...prevMap }
101
+ for (const id of completedVisualIds) {
102
+ if (nextMap[id] === undefined) nextMap[id] = true
103
+ }
104
+ return nextMap
105
+ })
106
+
107
+ window.setTimeout(() => {
108
+ if (containerRef.current) renderMermaidDiagrams(containerRef.current)
109
+ }, 0)
110
+ }
111
+ }, [text])
112
+
113
+ const toggleVisualView = (id: string) => {
114
+ const willShowVisual = !visualShowMap[id]
115
+ setVisualShowMap((prev) => ({ ...prev, [id]: willShowVisual }))
116
+
117
+ // 切换到图表模式时,需要在 DOM 更新后触发渲染
118
+ if (willShowVisual) {
119
+ window.setTimeout(() => {
120
+ if (containerRef.current) {
121
+ renderMermaidDiagrams(containerRef.current)
122
+ }
123
+ }, 0)
124
+ }
125
+ }
126
+
14
127
  return (
15
- <div
16
- className="text-part markdown-content"
17
- dangerouslySetInnerHTML={{ __html: html }}
18
- />
128
+ <div className="text-part" ref={containerRef}>
129
+ {blocks.map((block) => {
130
+ if (block.type === 'text') {
131
+ return (
132
+ <div
133
+ key={block.id}
134
+ className="text-block"
135
+ dangerouslySetInnerHTML={{ __html: renderMarkdown(block.content) }}
136
+ />
137
+ )
138
+ } else if (block.type === 'code') {
139
+ const isMermaid = isMermaidLanguage(block.language)
140
+ const isLatex = isLatexLanguage(block.language)
141
+ const canVisualizeLatexBlock = isLatex && canVisualizeLatex(block.content)
142
+ // 默认显示代码(undefined/false),闭合后自动切到可视化(true)
143
+ const showVisual = !!visualShowMap[block.id]
144
+ return (
145
+ <div key={block.id} className="code-block-wrapper">
146
+ <div className="code-header">
147
+ <span className="code-language">{block.language || 'plaintext'}</span>
148
+ <div className="code-actions">
149
+ {shouldShowVisualToggle(block.language, block.content) && (
150
+ <button
151
+ className="code-action-btn"
152
+ onClick={() => toggleVisualView(block.id)}
153
+ title={showVisual ? '切换为代码视图' : '切换为可视化视图'}
154
+ >
155
+ {/* 切换图标 */}
156
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
157
+ <polyline points="17 1 21 5 17 9"></polyline>
158
+ <path d="M3 11V9a4 4 0 0 1 4-4h14"></path>
159
+ <polyline points="7 23 3 19 7 15"></polyline>
160
+ <path d="M21 13v2a4 4 0 0 1-4 4H3"></path>
161
+ </svg>
162
+ <span>切换视图</span>
163
+ </button>
164
+ )}
165
+ <button
166
+ className="code-action-btn"
167
+ onClick={() => copyCode(block.content, block.id)}
168
+ title={copiedId === block.id ? '已复制' : '复制代码'}
169
+ >
170
+ {copiedId !== block.id ? (
171
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
172
+ <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
173
+ <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
174
+ </svg>
175
+ ) : (
176
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
177
+ <polyline points="20 6 9 17 4 12"></polyline>
178
+ </svg>
179
+ )}
180
+ <span>{copiedId === block.id ? '已复制' : '复制'}</span>
181
+ </button>
182
+ </div>
183
+ </div>
184
+ {/* Mermaid: 闭合后自动切换到图表视图 */}
185
+ {isMermaid && showVisual && (
186
+ <div
187
+ className="mermaid-placeholder"
188
+ data-mermaid-code-b64={encodeMermaidCodeToBase64Url(block.content)}
189
+ >
190
+ <div className="mermaid-loading">加载图表中...</div>
191
+ </div>
192
+ )}
193
+ {/* LaTeX: 闭合后自动切换到渲染视图 */}
194
+ {isLatex && showVisual && canVisualizeLatexBlock && (
195
+ <div
196
+ className="latex-rendered"
197
+ dangerouslySetInnerHTML={{ __html: renderFencedLatexToHtml(block.content) }}
198
+ />
199
+ )}
200
+ {(!(isMermaid || isLatex) || !showVisual || (isLatex && !canVisualizeLatexBlock)) && (
201
+ <pre className="code-block"><code
202
+ className={block.language ? `language-${block.language}` : ''}
203
+ dangerouslySetInnerHTML={{ __html: highlightCode(block.content, block.language) }}
204
+ /></pre>
205
+ )}
206
+ </div>
207
+ )
208
+ }
209
+ return null
210
+ })}
211
+ </div>
19
212
  )
20
213
  }
@@ -1,122 +1,96 @@
1
- .tool-call-content {
2
- display: flex;
3
- flex-direction: column;
1
+ .tool-args {
2
+ margin: 0 0 12px 0;
3
+ padding: 0.75em 1em;
4
+ background: var(--chat-bg, #1e1e1e);
5
+ border: 1px solid var(--chat-border, rgba(255, 255, 255, 0.08));
6
+ border-radius: 8px;
7
+ font-size: 13px;
8
+ line-height: 1.6;
9
+ color: var(--chat-text-muted, #999);
10
+ white-space: pre-wrap;
11
+ word-break: break-word;
12
+ overflow-x: auto;
4
13
  }
5
14
 
6
- .tool-call-command {
7
- margin-bottom: 12px;
15
+ /* JSON 格式:使用等宽字体,更好的可读性,支持横向滚动 */
16
+ .tool-args.json {
17
+ font-family: "Monaco", "Menlo", "Courier New", monospace;
18
+ font-size: 12px;
19
+ line-height: 1.8;
20
+ white-space: pre;
21
+ word-break: normal;
22
+ overflow-x: auto;
8
23
  }
9
24
 
10
- .command-header,
11
- .result-header {
12
- display: block;
13
- margin-bottom: 8px;
14
- padding: 0;
15
- cursor: default;
16
- user-select: none;
17
- font-size: 12px;
18
- line-height: 1.5;
25
+ /* JSON 横向滚动条样式(与全局 chat-scrollbar 对齐) */
26
+ .tool-args.json::-webkit-scrollbar {
27
+ height: 6px;
19
28
  }
20
29
 
21
- /* 确保没有 hover 效果 - 明确覆盖其他地方的样式 */
22
- .command-header:hover,
23
- .result-header:hover,
24
- .command-label:hover,
25
- .result-label:hover,
26
- .command-name:hover,
27
- .result-name:hover {
28
- background: transparent !important;
30
+ .tool-args.json::-webkit-scrollbar-track {
31
+ background: transparent;
29
32
  }
30
33
 
31
- .command-code {
32
- display: block;
33
- padding: 10px;
34
- /* 浅色主题下 rgba(0,0,0,0.3) 会显得“脏黑”,这里统一走 code token */
35
- background: var(--chat-code-bg, rgba(0, 0, 0, 0.3));
36
- border: 1px solid var(--chat-border, #333);
37
- border-radius: 6px;
38
- font-family: "Monaco", "Menlo", "Courier New", monospace;
39
- font-size: 13px;
40
- color: var(--chat-code-text, var(--chat-text, #ccc));
41
- white-space: pre-wrap;
42
- word-break: break-all;
43
- overflow-x: auto;
34
+ .tool-args.json::-webkit-scrollbar-thumb {
35
+ background: var(--chat-scrollbar, rgba(255, 255, 255, 0.2));
36
+ border-radius: 3px;
44
37
  }
45
38
 
46
- .tool-call-result {
47
- margin-bottom: 12px;
39
+ .tool-args.json::-webkit-scrollbar-thumb:hover {
40
+ background: var(--chat-scrollbar-hover, rgba(255, 255, 255, 0.3));
48
41
  }
49
42
 
50
- .result-label,
51
- .command-label {
52
- color: var(--chat-text-muted, #888);
53
- font-weight: 500;
43
+ /* JSON 语法高亮样式(使用 CSS 变量,跟随主题) */
44
+ .tool-args.json .hljs-attr,
45
+ .tool-args.json .hljs-attribute {
46
+ color: var(--chat-hljs-attr, #79c0ff); /* 键名 */
54
47
  }
55
48
 
56
- .result-name,
57
- .command-name {
58
- color: var(--chat-text, #ccc);
49
+ .tool-args.json .hljs-string,
50
+ .tool-args.json .hljs-template-variable {
51
+ color: var(--chat-hljs-string, #a5d6ff); /* 字符串值 */
59
52
  }
60
53
 
61
- .result-content {
62
- display: block;
63
- padding: 10px;
64
- /* 浅色主题下 rgba(0,0,0,0.3) 会显得“脏黑”,这里统一走 code token */
65
- background: var(--chat-code-bg, rgba(0, 0, 0, 0.3));
66
- border: 1px solid var(--chat-border, #333);
67
- border-radius: 6px;
68
- font-family: "Monaco", "Menlo", "Courier New", monospace;
69
- font-size: 13px;
70
- color: var(--chat-code-text, var(--chat-text, #ccc));
71
- white-space: pre-wrap;
72
- word-break: break-all;
73
- overflow-x: auto;
54
+ .tool-args.json .hljs-number {
55
+ color: var(--chat-hljs-number, #79c0ff); /* 数字 */
74
56
  }
75
57
 
76
- .execution-stats {
77
- display: flex;
78
- gap: 16px;
79
- margin-bottom: 12px;
80
- padding-top: 8px;
81
- border-top: 1px solid var(--chat-border, #333);
58
+ .tool-args.json .hljs-literal,
59
+ .tool-args.json .hljs-null {
60
+ color: var(--chat-hljs-literal, #79c0ff); /* true, false, null */
82
61
  }
83
62
 
84
- .stat-item {
85
- display: flex;
86
- gap: 4px;
87
- font-size: 12px;
63
+ .tool-args.json .hljs-punctuation {
64
+ color: var(--chat-hljs-punctuation, #e6edf3); /* 括号、逗号等 */
88
65
  }
89
66
 
90
- .stat-label {
91
- color: var(--chat-text-muted, #888);
67
+ /* 命令格式:使用等宽字体 */
68
+ .tool-args.command {
69
+ font-family: "Monaco", "Menlo", "Courier New", monospace;
70
+ font-size: 12px;
71
+ line-height: 1.6;
92
72
  }
93
73
 
94
- .stat-value {
95
- color: var(--chat-text, #ccc);
74
+ /* 普通文本:保持默认字体 */
75
+ .tool-args.text {
76
+ font-size: 13px;
77
+ line-height: 1.5;
96
78
  }
97
79
 
98
- .tool-call-footer {
80
+ .tool-footer {
99
81
  display: flex;
100
82
  align-items: center;
101
83
  justify-content: space-between;
102
84
  gap: 12px;
103
- padding-top: 12px;
104
- border-top: 1px solid var(--chat-border, #333);
105
85
  }
106
86
 
107
- .footer-left {
108
- flex-shrink: 0;
109
- position: relative;
110
- z-index: 10;
111
- }
112
-
113
- .footer-right {
87
+ .tool-actions {
114
88
  display: flex;
115
89
  align-items: center;
116
- gap: 12px;
90
+ gap: 8px;
117
91
  }
118
92
 
119
- .execution-status {
93
+ .status-text {
120
94
  display: flex;
121
95
  align-items: center;
122
96
  gap: 6px;
@@ -124,37 +98,22 @@
124
98
  font-weight: 500;
125
99
  }
126
100
 
127
- .execution-status.success {
101
+ .status-text.done {
128
102
  color: var(--chat-success, #22c55e);
129
103
  }
130
-
131
- .execution-status.error {
132
- color: #ef4444;
104
+ .status-text.error {
105
+ color: var(--chat-error, #ef4444);
133
106
  }
134
-
135
- .execution-status.running {
107
+ .status-text.running {
136
108
  color: var(--chat-accent, #3b82f6);
137
109
  }
138
-
139
- .execution-status.pending {
140
- color: var(--chat-text-muted, #888);
141
- }
142
-
143
- .mode-display {
144
- display: flex;
145
- align-items: center;
146
- gap: 6px;
147
- font-size: 12px;
110
+ .status-text.cancelled {
111
+ color: var(--chat-warning, #f59e0b);
148
112
  }
149
-
150
- .mode-label {
113
+ .status-text.skipped {
151
114
  color: var(--chat-text-muted, #888);
152
115
  }
153
116
 
154
- .mode-value {
155
- color: var(--chat-text, #ccc);
156
- }
157
-
158
117
  .spinning {
159
118
  animation: spin 1s linear infinite;
160
119
  }
@@ -168,11 +127,6 @@
168
127
  }
169
128
  }
170
129
 
171
- .action-buttons {
172
- display: flex;
173
- gap: 8px;
174
- }
175
-
176
130
  .btn {
177
131
  padding: 6px 12px;
178
132
  border-radius: 6px;
@@ -191,7 +145,6 @@
191
145
  color: var(--chat-text, #ccc);
192
146
  border: 1px solid var(--chat-border, #333);
193
147
  }
194
-
195
148
  .btn-skip:hover {
196
149
  background: var(--chat-muted-hover, #333);
197
150
  }
@@ -200,7 +153,6 @@
200
153
  background: var(--chat-accent, #3b82f6);
201
154
  color: #fff;
202
155
  }
203
-
204
156
  .btn-run:hover {
205
157
  background: var(--chat-accent-hover, #2563eb);
206
158
  }
@@ -210,13 +162,85 @@
210
162
  color: var(--chat-text-muted, #888);
211
163
  border: 1px solid var(--chat-border, #333);
212
164
  }
213
-
214
165
  .btn-cancel:hover {
215
166
  background: rgba(239, 68, 68, 0.1);
216
167
  color: #ef4444;
217
168
  border-color: #ef4444;
218
169
  }
219
170
 
220
- .execution-status.cancelled {
221
- color: var(--chat-warning, #f59e0b);
171
+ .output-panel {
172
+ margin: 0 0 12px 0;
173
+ border: 1px solid var(--chat-border, rgba(255, 255, 255, 0.08));
174
+ border-radius: 8px;
175
+ background: var(--chat-bg, #1e1e1e);
176
+ overflow: hidden;
177
+ }
178
+
179
+ .output-header {
180
+ display: flex;
181
+ align-items: center;
182
+ justify-content: space-between;
183
+ gap: 12px;
184
+ padding: 8px 10px;
185
+ border-bottom: 1px solid var(--chat-border, rgba(255, 255, 255, 0.08));
186
+ }
187
+
188
+ .output-tabs {
189
+ display: flex;
190
+ gap: 6px;
191
+ }
192
+
193
+ .tab {
194
+ padding: 4px 8px;
195
+ border-radius: 6px;
196
+ border: 1px solid var(--chat-border, #333);
197
+ background: var(--chat-muted, #2a2a2a);
198
+ color: var(--chat-text-muted, #999);
199
+ font-size: 11px;
200
+ cursor: pointer;
201
+ }
202
+
203
+ .tab.active {
204
+ background: rgba(59, 130, 246, 0.15);
205
+ border-color: rgba(59, 130, 246, 0.35);
206
+ color: var(--chat-text, #ccc);
207
+ }
208
+
209
+ .output-actions {
210
+ display: flex;
211
+ align-items: center;
212
+ gap: 8px;
213
+ }
214
+
215
+ .btn-save {
216
+ padding: 6px 10px;
217
+ border-radius: 6px;
218
+ font-size: 12px;
219
+ font-weight: 500;
220
+ cursor: pointer;
221
+ border: 1px solid var(--chat-border, #333);
222
+ background: transparent;
223
+ color: var(--chat-text-muted, #888);
224
+ display: flex;
225
+ align-items: center;
226
+ gap: 6px;
227
+ transition: all 0.2s;
228
+ }
229
+
230
+ .btn-save:hover {
231
+ background: var(--chat-muted-hover, #333);
232
+ color: var(--chat-text, #ccc);
233
+ }
234
+
235
+ .output-body {
236
+ margin: 0;
237
+ padding: 10px 12px;
238
+ font-family: "Monaco", "Menlo", "Courier New", monospace;
239
+ font-size: 12px;
240
+ line-height: 1.6;
241
+ color: var(--chat-text-muted, #999);
242
+ white-space: pre-wrap;
243
+ word-break: break-word;
244
+ max-height: 260px;
245
+ overflow: auto;
222
246
  }