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

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 (76) hide show
  1. package/dist/index.css +0 -1
  2. package/dist/index.js +1 -5418
  3. package/package.json +4 -5
  4. package/dist/index.css.map +0 -1
  5. package/dist/index.js.map +0 -1
  6. package/src/adapter.ts +0 -68
  7. package/src/components/ChatPanel.tsx +0 -553
  8. package/src/components/common/ConfirmDialog.css +0 -136
  9. package/src/components/common/ConfirmDialog.tsx +0 -91
  10. package/src/components/common/CopyButton.css +0 -22
  11. package/src/components/common/CopyButton.tsx +0 -46
  12. package/src/components/common/IndexingSettings.css +0 -207
  13. package/src/components/common/IndexingSettings.tsx +0 -398
  14. package/src/components/common/SettingsPanel.css +0 -337
  15. package/src/components/common/SettingsPanel.tsx +0 -215
  16. package/src/components/common/Toast.css +0 -50
  17. package/src/components/common/Toast.tsx +0 -38
  18. package/src/components/common/ToggleSwitch.css +0 -52
  19. package/src/components/common/ToggleSwitch.tsx +0 -20
  20. package/src/components/header/ChatHeader.css +0 -285
  21. package/src/components/header/ChatHeader.tsx +0 -376
  22. package/src/components/input/AtFilePicker.css +0 -147
  23. package/src/components/input/AtFilePicker.tsx +0 -519
  24. package/src/components/input/ChatInput.css +0 -283
  25. package/src/components/input/ChatInput.tsx +0 -575
  26. package/src/components/input/DropdownSelector.css +0 -231
  27. package/src/components/input/DropdownSelector.tsx +0 -333
  28. package/src/components/input/ImagePreviewModal.css +0 -124
  29. package/src/components/input/ImagePreviewModal.tsx +0 -118
  30. package/src/components/input/at-views/AtBranchView.tsx +0 -34
  31. package/src/components/input/at-views/AtBrowserView.tsx +0 -34
  32. package/src/components/input/at-views/AtChatsView.tsx +0 -34
  33. package/src/components/input/at-views/AtDocsView.tsx +0 -34
  34. package/src/components/input/at-views/AtFilesView.tsx +0 -168
  35. package/src/components/input/at-views/AtTerminalsView.tsx +0 -34
  36. package/src/components/input/at-views/AtViewStyles.css +0 -143
  37. package/src/components/input/at-views/index.ts +0 -9
  38. package/src/components/message/ContentRenderer.css +0 -9
  39. package/src/components/message/MessageBubble.css +0 -193
  40. package/src/components/message/MessageBubble.tsx +0 -240
  41. package/src/components/message/PartsRenderer.css +0 -12
  42. package/src/components/message/PartsRenderer.tsx +0 -168
  43. package/src/components/message/WelcomeMessage.css +0 -221
  44. package/src/components/message/WelcomeMessage.tsx +0 -93
  45. package/src/components/message/parts/CollapsibleCard.css +0 -80
  46. package/src/components/message/parts/CollapsibleCard.tsx +0 -80
  47. package/src/components/message/parts/ErrorPart.css +0 -9
  48. package/src/components/message/parts/ErrorPart.tsx +0 -40
  49. package/src/components/message/parts/ImagePart.css +0 -49
  50. package/src/components/message/parts/ImagePart.tsx +0 -54
  51. package/src/components/message/parts/SearchPart.css +0 -44
  52. package/src/components/message/parts/SearchPart.tsx +0 -63
  53. package/src/components/message/parts/TextPart.css +0 -579
  54. package/src/components/message/parts/TextPart.tsx +0 -213
  55. package/src/components/message/parts/ThinkingPart.css +0 -9
  56. package/src/components/message/parts/ThinkingPart.tsx +0 -48
  57. package/src/components/message/parts/ToolCallPart.css +0 -246
  58. package/src/components/message/parts/ToolCallPart.tsx +0 -289
  59. package/src/components/message/parts/ToolResultPart.css +0 -67
  60. package/src/components/message/parts/index.ts +0 -13
  61. package/src/components/message/parts/visual-predicate.ts +0 -43
  62. package/src/components/message/parts/visual-render.ts +0 -19
  63. package/src/components/message/parts/visual.ts +0 -12
  64. package/src/components/message/welcome-types.ts +0 -46
  65. package/src/context/AutoRunConfigContext.tsx +0 -13
  66. package/src/context/ChatAdapterContext.tsx +0 -8
  67. package/src/context/ChatInputContext.tsx +0 -40
  68. package/src/context/RenderersContext.tsx +0 -35
  69. package/src/hooks/useChat.ts +0 -1569
  70. package/src/hooks/useImageUpload.ts +0 -345
  71. package/src/hooks/useVoiceInput.ts +0 -454
  72. package/src/hooks/useVoiceToTextInput.ts +0 -87
  73. package/src/index.ts +0 -151
  74. package/src/styles.css +0 -330
  75. package/src/types/index.ts +0 -196
  76. package/src/utils/fileIcon.ts +0 -49
@@ -1,213 +0,0 @@
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'
23
- import './TextPart.css'
24
-
25
- // 初始化 Mermaid(模块加载时执行一次)
26
- initMermaid()
27
-
28
- interface TextPartProps {
29
- text: string
30
- }
31
-
32
- export const TextPart: FC<TextPartProps> = ({ text }) => {
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>>({})
43
-
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
- }, [])
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
-
127
- return (
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>
212
- )
213
- }
@@ -1,9 +0,0 @@
1
- .thinking-content {
2
- font-size: 13px;
3
- line-height: 1.5;
4
- color: var(--chat-text-muted, #999);
5
- white-space: pre-wrap;
6
- word-break: break-word;
7
- overflow-wrap: break-word;
8
- margin: 0;
9
- }
@@ -1,48 +0,0 @@
1
- import { useState, useEffect, type FC } from 'react'
2
- import { CollapsibleCard } from './CollapsibleCard'
3
- import type { StepsExpandedType } from '../../../types'
4
- import './ThinkingPart.css'
5
-
6
- interface ThinkingPartProps {
7
- text: string
8
- status: 'running' | 'done'
9
- duration?: number
10
- expandedType?: StepsExpandedType
11
- }
12
-
13
- export const ThinkingPart: FC<ThinkingPartProps> = ({
14
- text,
15
- status,
16
- duration,
17
- expandedType = 'auto'
18
- }) => {
19
- // 根据模式计算初始状态
20
- const getInitialExpanded = () => {
21
- if (expandedType === 'open') return true
22
- if (expandedType === 'close') return false
23
- // auto: 运行时展开
24
- return status === 'running'
25
- }
26
-
27
- const [expanded, setExpanded] = useState(getInitialExpanded)
28
-
29
- // auto 模式下:状态变化时自动折叠/展开
30
- useEffect(() => {
31
- if (expandedType === 'auto') {
32
- setExpanded(status === 'running')
33
- }
34
- }, [status, expandedType])
35
-
36
- return (
37
- <CollapsibleCard
38
- icon="lucide:lightbulb"
39
- iconColor="var(--chat-accent, #f59e0b)"
40
- title={status === 'running' ? '思考中...' : '思考完成'}
41
- subtitle={duration && status === 'done' ? `(${duration}s)` : undefined}
42
- expanded={expanded}
43
- onExpandedChange={setExpanded}
44
- >
45
- <div className="thinking-content">{text}</div>
46
- </CollapsibleCard>
47
- )
48
- }
@@ -1,246 +0,0 @@
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;
13
- }
14
-
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;
23
- }
24
-
25
- /* JSON 横向滚动条样式(与全局 chat-scrollbar 对齐) */
26
- .tool-args.json::-webkit-scrollbar {
27
- height: 6px;
28
- }
29
-
30
- .tool-args.json::-webkit-scrollbar-track {
31
- background: transparent;
32
- }
33
-
34
- .tool-args.json::-webkit-scrollbar-thumb {
35
- background: var(--chat-scrollbar, rgba(255, 255, 255, 0.2));
36
- border-radius: 3px;
37
- }
38
-
39
- .tool-args.json::-webkit-scrollbar-thumb:hover {
40
- background: var(--chat-scrollbar-hover, rgba(255, 255, 255, 0.3));
41
- }
42
-
43
- /* JSON 语法高亮样式(使用 CSS 变量,跟随主题) */
44
- .tool-args.json .hljs-attr,
45
- .tool-args.json .hljs-attribute {
46
- color: var(--chat-hljs-attr, #79c0ff); /* 键名 */
47
- }
48
-
49
- .tool-args.json .hljs-string,
50
- .tool-args.json .hljs-template-variable {
51
- color: var(--chat-hljs-string, #a5d6ff); /* 字符串值 */
52
- }
53
-
54
- .tool-args.json .hljs-number {
55
- color: var(--chat-hljs-number, #79c0ff); /* 数字 */
56
- }
57
-
58
- .tool-args.json .hljs-literal,
59
- .tool-args.json .hljs-null {
60
- color: var(--chat-hljs-literal, #79c0ff); /* true, false, null */
61
- }
62
-
63
- .tool-args.json .hljs-punctuation {
64
- color: var(--chat-hljs-punctuation, #e6edf3); /* 括号、逗号等 */
65
- }
66
-
67
- /* 命令格式:使用等宽字体 */
68
- .tool-args.command {
69
- font-family: "Monaco", "Menlo", "Courier New", monospace;
70
- font-size: 12px;
71
- line-height: 1.6;
72
- }
73
-
74
- /* 普通文本:保持默认字体 */
75
- .tool-args.text {
76
- font-size: 13px;
77
- line-height: 1.5;
78
- }
79
-
80
- .tool-footer {
81
- display: flex;
82
- align-items: center;
83
- justify-content: space-between;
84
- gap: 12px;
85
- }
86
-
87
- .tool-actions {
88
- display: flex;
89
- align-items: center;
90
- gap: 8px;
91
- }
92
-
93
- .status-text {
94
- display: flex;
95
- align-items: center;
96
- gap: 6px;
97
- font-size: 12px;
98
- font-weight: 500;
99
- }
100
-
101
- .status-text.done {
102
- color: var(--chat-success, #22c55e);
103
- }
104
- .status-text.error {
105
- color: var(--chat-error, #ef4444);
106
- }
107
- .status-text.running {
108
- color: var(--chat-accent, #3b82f6);
109
- }
110
- .status-text.cancelled {
111
- color: var(--chat-warning, #f59e0b);
112
- }
113
- .status-text.skipped {
114
- color: var(--chat-text-muted, #888);
115
- }
116
-
117
- .spinning {
118
- animation: spin 1s linear infinite;
119
- }
120
-
121
- @keyframes spin {
122
- from {
123
- transform: rotate(0deg);
124
- }
125
- to {
126
- transform: rotate(360deg);
127
- }
128
- }
129
-
130
- .btn {
131
- padding: 6px 12px;
132
- border-radius: 6px;
133
- font-size: 12px;
134
- font-weight: 500;
135
- cursor: pointer;
136
- border: none;
137
- display: flex;
138
- align-items: center;
139
- gap: 6px;
140
- transition: all 0.2s;
141
- }
142
-
143
- .btn-skip {
144
- background: var(--chat-muted, #2a2a2a);
145
- color: var(--chat-text, #ccc);
146
- border: 1px solid var(--chat-border, #333);
147
- }
148
- .btn-skip:hover {
149
- background: var(--chat-muted-hover, #333);
150
- }
151
-
152
- .btn-run {
153
- background: var(--chat-accent, #3b82f6);
154
- color: #fff;
155
- }
156
- .btn-run:hover {
157
- background: var(--chat-accent-hover, #2563eb);
158
- }
159
-
160
- .btn-cancel {
161
- background: transparent;
162
- color: var(--chat-text-muted, #888);
163
- border: 1px solid var(--chat-border, #333);
164
- }
165
- .btn-cancel:hover {
166
- background: rgba(239, 68, 68, 0.1);
167
- color: #ef4444;
168
- border-color: #ef4444;
169
- }
170
-
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;
246
- }