@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.
- package/dist/index.css +0 -1
- package/dist/index.js +1 -5418
- package/package.json +4 -5
- package/dist/index.css.map +0 -1
- package/dist/index.js.map +0 -1
- package/src/adapter.ts +0 -68
- package/src/components/ChatPanel.tsx +0 -553
- package/src/components/common/ConfirmDialog.css +0 -136
- package/src/components/common/ConfirmDialog.tsx +0 -91
- package/src/components/common/CopyButton.css +0 -22
- package/src/components/common/CopyButton.tsx +0 -46
- package/src/components/common/IndexingSettings.css +0 -207
- package/src/components/common/IndexingSettings.tsx +0 -398
- package/src/components/common/SettingsPanel.css +0 -337
- package/src/components/common/SettingsPanel.tsx +0 -215
- package/src/components/common/Toast.css +0 -50
- package/src/components/common/Toast.tsx +0 -38
- package/src/components/common/ToggleSwitch.css +0 -52
- package/src/components/common/ToggleSwitch.tsx +0 -20
- package/src/components/header/ChatHeader.css +0 -285
- package/src/components/header/ChatHeader.tsx +0 -376
- package/src/components/input/AtFilePicker.css +0 -147
- package/src/components/input/AtFilePicker.tsx +0 -519
- package/src/components/input/ChatInput.css +0 -283
- package/src/components/input/ChatInput.tsx +0 -575
- package/src/components/input/DropdownSelector.css +0 -231
- package/src/components/input/DropdownSelector.tsx +0 -333
- package/src/components/input/ImagePreviewModal.css +0 -124
- package/src/components/input/ImagePreviewModal.tsx +0 -118
- package/src/components/input/at-views/AtBranchView.tsx +0 -34
- package/src/components/input/at-views/AtBrowserView.tsx +0 -34
- package/src/components/input/at-views/AtChatsView.tsx +0 -34
- package/src/components/input/at-views/AtDocsView.tsx +0 -34
- package/src/components/input/at-views/AtFilesView.tsx +0 -168
- package/src/components/input/at-views/AtTerminalsView.tsx +0 -34
- package/src/components/input/at-views/AtViewStyles.css +0 -143
- package/src/components/input/at-views/index.ts +0 -9
- package/src/components/message/ContentRenderer.css +0 -9
- package/src/components/message/MessageBubble.css +0 -193
- package/src/components/message/MessageBubble.tsx +0 -240
- package/src/components/message/PartsRenderer.css +0 -12
- package/src/components/message/PartsRenderer.tsx +0 -168
- package/src/components/message/WelcomeMessage.css +0 -221
- package/src/components/message/WelcomeMessage.tsx +0 -93
- package/src/components/message/parts/CollapsibleCard.css +0 -80
- package/src/components/message/parts/CollapsibleCard.tsx +0 -80
- package/src/components/message/parts/ErrorPart.css +0 -9
- package/src/components/message/parts/ErrorPart.tsx +0 -40
- package/src/components/message/parts/ImagePart.css +0 -49
- package/src/components/message/parts/ImagePart.tsx +0 -54
- package/src/components/message/parts/SearchPart.css +0 -44
- package/src/components/message/parts/SearchPart.tsx +0 -63
- package/src/components/message/parts/TextPart.css +0 -579
- package/src/components/message/parts/TextPart.tsx +0 -213
- package/src/components/message/parts/ThinkingPart.css +0 -9
- package/src/components/message/parts/ThinkingPart.tsx +0 -48
- package/src/components/message/parts/ToolCallPart.css +0 -246
- package/src/components/message/parts/ToolCallPart.tsx +0 -289
- package/src/components/message/parts/ToolResultPart.css +0 -67
- package/src/components/message/parts/index.ts +0 -13
- package/src/components/message/parts/visual-predicate.ts +0 -43
- package/src/components/message/parts/visual-render.ts +0 -19
- package/src/components/message/parts/visual.ts +0 -12
- package/src/components/message/welcome-types.ts +0 -46
- package/src/context/AutoRunConfigContext.tsx +0 -13
- package/src/context/ChatAdapterContext.tsx +0 -8
- package/src/context/ChatInputContext.tsx +0 -40
- package/src/context/RenderersContext.tsx +0 -35
- package/src/hooks/useChat.ts +0 -1569
- package/src/hooks/useImageUpload.ts +0 -345
- package/src/hooks/useVoiceInput.ts +0 -454
- package/src/hooks/useVoiceToTextInput.ts +0 -87
- package/src/index.ts +0 -151
- package/src/styles.css +0 -330
- package/src/types/index.ts +0 -196
- package/src/utils/fileIcon.ts +0 -49
|
@@ -1,289 +0,0 @@
|
|
|
1
|
-
import { useState, useMemo, useEffect, useCallback, useRef } from 'react'
|
|
2
|
-
import type { FC } from 'react'
|
|
3
|
-
import { Icon } from '@iconify/react'
|
|
4
|
-
import { DropdownSelector } from '../../input/DropdownSelector'
|
|
5
|
-
import { CollapsibleCard } from './CollapsibleCard'
|
|
6
|
-
import { CopyButton } from '../../common/CopyButton'
|
|
7
|
-
import { highlightCode } from '@huyooo/ai-chat-shared'
|
|
8
|
-
import type { ChatAdapter } from '../../../adapter'
|
|
9
|
-
import type { AutoRunConfig, AutoRunMode } from '@huyooo/ai-chat-bridge-electron/renderer'
|
|
10
|
-
import type { StepsExpandedType } from '../../../types'
|
|
11
|
-
import './ToolCallPart.css'
|
|
12
|
-
|
|
13
|
-
interface ToolCallPartProps {
|
|
14
|
-
id: string
|
|
15
|
-
name: string
|
|
16
|
-
args?: Record<string, unknown>
|
|
17
|
-
output?: {
|
|
18
|
-
stdout?: string
|
|
19
|
-
stderr?: string
|
|
20
|
-
}
|
|
21
|
-
status: 'pending' | 'running' | 'done' | 'error' | 'cancelled' | 'skipped'
|
|
22
|
-
expandedType?: StepsExpandedType
|
|
23
|
-
adapter?: ChatAdapter
|
|
24
|
-
onCancelToolCall?: (toolCallId: string) => void
|
|
25
|
-
autoRunConfig?: AutoRunConfig
|
|
26
|
-
onSaveConfig?: (config: AutoRunConfig) => Promise<void>
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const modeOptions = [
|
|
30
|
-
{ value: 'manual', label: '执行前询问我' },
|
|
31
|
-
{ value: 'run-everything', label: '自动执行' },
|
|
32
|
-
]
|
|
33
|
-
|
|
34
|
-
const statusIcons: Record<string, string> = {
|
|
35
|
-
error: 'lucide:x-circle',
|
|
36
|
-
done: 'lucide:check-circle',
|
|
37
|
-
running: 'lucide:loader-2',
|
|
38
|
-
pending: 'lucide:clock',
|
|
39
|
-
cancelled: 'lucide:ban',
|
|
40
|
-
skipped: 'lucide:skip-forward',
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const statusColors: Record<string, string> = {
|
|
44
|
-
error: 'var(--chat-error, #ef4444)',
|
|
45
|
-
done: 'var(--chat-success, #22c55e)',
|
|
46
|
-
running: 'var(--chat-accent, #3b82f6)',
|
|
47
|
-
pending: 'var(--chat-text-muted, #888)',
|
|
48
|
-
cancelled: 'var(--chat-warning, #f59e0b)',
|
|
49
|
-
skipped: 'var(--chat-text-muted, #888)',
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const statusDisplayIcons: Record<string, string> = {
|
|
53
|
-
done: 'lucide:check-circle-2',
|
|
54
|
-
error: 'lucide:x-circle',
|
|
55
|
-
cancelled: 'lucide:ban',
|
|
56
|
-
skipped: 'lucide:skip-forward',
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const statusTexts: Record<string, string> = {
|
|
60
|
-
done: '成功',
|
|
61
|
-
error: '错误',
|
|
62
|
-
cancelled: '已取消',
|
|
63
|
-
skipped: '已跳过',
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export const ToolCallPart: FC<ToolCallPartProps> = ({
|
|
67
|
-
id,
|
|
68
|
-
name,
|
|
69
|
-
args,
|
|
70
|
-
output,
|
|
71
|
-
status,
|
|
72
|
-
expandedType = 'auto',
|
|
73
|
-
adapter,
|
|
74
|
-
onCancelToolCall,
|
|
75
|
-
autoRunConfig,
|
|
76
|
-
onSaveConfig,
|
|
77
|
-
}) => {
|
|
78
|
-
const currentMode = autoRunConfig?.mode ?? 'run-everything'
|
|
79
|
-
|
|
80
|
-
// 参数类型:'json' | 'command' | 'text'
|
|
81
|
-
const argsType = useMemo(() => {
|
|
82
|
-
if (name === 'execute_command' && args?.command) {
|
|
83
|
-
return 'command'
|
|
84
|
-
}
|
|
85
|
-
if (args && Object.keys(args).length > 0) {
|
|
86
|
-
return 'json'
|
|
87
|
-
}
|
|
88
|
-
return 'text'
|
|
89
|
-
}, [name, args])
|
|
90
|
-
|
|
91
|
-
// 参数文本(格式化后的)
|
|
92
|
-
const argsText = useMemo(() => {
|
|
93
|
-
if (name === 'execute_command' && args?.command) {
|
|
94
|
-
return String(args.command)
|
|
95
|
-
}
|
|
96
|
-
if (args && Object.keys(args).length > 0) {
|
|
97
|
-
try {
|
|
98
|
-
// JSON 格式化:2 空格缩进
|
|
99
|
-
return JSON.stringify(args, null, 2)
|
|
100
|
-
} catch {
|
|
101
|
-
return String(args)
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
return name
|
|
105
|
-
}, [name, args])
|
|
106
|
-
|
|
107
|
-
// JSON 高亮后的 HTML
|
|
108
|
-
const highlightedJson = useMemo(() => {
|
|
109
|
-
if (argsType === 'json') {
|
|
110
|
-
return highlightCode(argsText, 'json')
|
|
111
|
-
}
|
|
112
|
-
return ''
|
|
113
|
-
}, [argsType, argsText])
|
|
114
|
-
|
|
115
|
-
// 折叠状态
|
|
116
|
-
const [expanded, setExpanded] = useState(() => {
|
|
117
|
-
if (expandedType === 'open') return true
|
|
118
|
-
if (expandedType === 'close') return false
|
|
119
|
-
return status === 'pending' || status === 'running'
|
|
120
|
-
})
|
|
121
|
-
|
|
122
|
-
useEffect(() => {
|
|
123
|
-
if (expandedType === 'auto') {
|
|
124
|
-
setExpanded(status === 'pending' || status === 'running')
|
|
125
|
-
}
|
|
126
|
-
}, [status, expandedType])
|
|
127
|
-
|
|
128
|
-
// 标题
|
|
129
|
-
const title = useMemo(() => {
|
|
130
|
-
const suffixes: Record<string, string> = {
|
|
131
|
-
pending: ' - 等待确认',
|
|
132
|
-
running: ' - 执行中...',
|
|
133
|
-
error: ' - 执行失败',
|
|
134
|
-
done: ' - 执行完成',
|
|
135
|
-
cancelled: ' - 已取消',
|
|
136
|
-
skipped: ' - 已跳过',
|
|
137
|
-
}
|
|
138
|
-
return name + (suffixes[status] ?? '')
|
|
139
|
-
}, [name, status])
|
|
140
|
-
|
|
141
|
-
// 操作
|
|
142
|
-
const handleModeChange = useCallback(async (value: string) => {
|
|
143
|
-
if (!onSaveConfig || !autoRunConfig) return
|
|
144
|
-
await onSaveConfig({ ...autoRunConfig, mode: value as AutoRunMode })
|
|
145
|
-
}, [onSaveConfig, autoRunConfig])
|
|
146
|
-
|
|
147
|
-
const handleSkip = useCallback(async () => {
|
|
148
|
-
await adapter?.respondToolApproval?.(id, false)
|
|
149
|
-
}, [adapter, id])
|
|
150
|
-
|
|
151
|
-
const handleRun = useCallback(async () => {
|
|
152
|
-
await adapter?.respondToolApproval?.(id, true)
|
|
153
|
-
}, [adapter, id])
|
|
154
|
-
|
|
155
|
-
const handleCancel = useCallback(() => {
|
|
156
|
-
if (onCancelToolCall) {
|
|
157
|
-
onCancelToolCall(id)
|
|
158
|
-
return
|
|
159
|
-
}
|
|
160
|
-
adapter?.cancel?.()
|
|
161
|
-
}, [adapter, id, onCancelToolCall])
|
|
162
|
-
|
|
163
|
-
const hasOutput = Boolean(output?.stdout || output?.stderr)
|
|
164
|
-
const [activeStream, setActiveStream] = useState<'stdout' | 'stderr'>('stdout')
|
|
165
|
-
const activeOutputText = useMemo(() => {
|
|
166
|
-
return activeStream === 'stdout' ? (output?.stdout ?? '') : (output?.stderr ?? '')
|
|
167
|
-
}, [activeStream, output])
|
|
168
|
-
|
|
169
|
-
const saveLog = useCallback(() => {
|
|
170
|
-
const stdout = output?.stdout ?? ''
|
|
171
|
-
const stderr = output?.stderr ?? ''
|
|
172
|
-
const content =
|
|
173
|
-
`# tool: ${name}\n` +
|
|
174
|
-
`# id: ${id}\n` +
|
|
175
|
-
`# time: ${new Date().toISOString()}\n\n` +
|
|
176
|
-
`===== STDOUT =====\n${stdout}\n\n` +
|
|
177
|
-
`===== STDERR =====\n${stderr}\n`
|
|
178
|
-
|
|
179
|
-
const blob = new Blob([content], { type: 'text/plain;charset=utf-8' })
|
|
180
|
-
const url = URL.createObjectURL(blob)
|
|
181
|
-
const a = document.createElement('a')
|
|
182
|
-
a.href = url
|
|
183
|
-
a.download = `tool-${name}-${id}.log`
|
|
184
|
-
document.body.appendChild(a)
|
|
185
|
-
a.click()
|
|
186
|
-
document.body.removeChild(a)
|
|
187
|
-
URL.revokeObjectURL(url)
|
|
188
|
-
}, [id, name, output])
|
|
189
|
-
|
|
190
|
-
// 模式切换时自动执行
|
|
191
|
-
const prevModeRef = useRef(currentMode)
|
|
192
|
-
useEffect(() => {
|
|
193
|
-
const prevMode = prevModeRef.current
|
|
194
|
-
if (prevMode === 'manual' && currentMode === 'run-everything' && status === 'pending') {
|
|
195
|
-
handleRun()
|
|
196
|
-
}
|
|
197
|
-
prevModeRef.current = currentMode
|
|
198
|
-
}, [currentMode, status, handleRun])
|
|
199
|
-
|
|
200
|
-
return (
|
|
201
|
-
<CollapsibleCard
|
|
202
|
-
icon={statusIcons[status] ?? 'lucide:clock'}
|
|
203
|
-
iconColor={statusColors[status] ?? 'var(--chat-text-muted, #888)'}
|
|
204
|
-
title={title}
|
|
205
|
-
expanded={expanded}
|
|
206
|
-
onExpandedChange={setExpanded}
|
|
207
|
-
spinning={status === 'running'}
|
|
208
|
-
headerActions={<CopyButton text={argsText} title="复制" />}
|
|
209
|
-
>
|
|
210
|
-
{/* 参数内容 */}
|
|
211
|
-
<pre className={`tool-args ${argsType}`}>
|
|
212
|
-
{argsType === 'json' ? (
|
|
213
|
-
<code dangerouslySetInnerHTML={{ __html: highlightedJson }} />
|
|
214
|
-
) : (
|
|
215
|
-
argsText
|
|
216
|
-
)}
|
|
217
|
-
</pre>
|
|
218
|
-
|
|
219
|
-
{/* 输出面板(stdout/stderr) */}
|
|
220
|
-
{hasOutput && (
|
|
221
|
-
<div className="output-panel">
|
|
222
|
-
<div className="output-header">
|
|
223
|
-
<div className="output-tabs">
|
|
224
|
-
<button
|
|
225
|
-
className={`tab${activeStream === 'stdout' ? ' active' : ''}`}
|
|
226
|
-
type="button"
|
|
227
|
-
onClick={() => setActiveStream('stdout')}
|
|
228
|
-
>
|
|
229
|
-
正常输出
|
|
230
|
-
</button>
|
|
231
|
-
<button
|
|
232
|
-
className={`tab${activeStream === 'stderr' ? ' active' : ''}`}
|
|
233
|
-
type="button"
|
|
234
|
-
onClick={() => setActiveStream('stderr')}
|
|
235
|
-
>
|
|
236
|
-
异常/告警输出
|
|
237
|
-
</button>
|
|
238
|
-
</div>
|
|
239
|
-
<div className="output-actions">
|
|
240
|
-
<button className="btn btn-save" type="button" onClick={saveLog}>
|
|
241
|
-
<Icon icon="lucide:download" width={14} />
|
|
242
|
-
保存日志
|
|
243
|
-
</button>
|
|
244
|
-
<CopyButton text={activeOutputText} title="复制输出" />
|
|
245
|
-
</div>
|
|
246
|
-
</div>
|
|
247
|
-
<pre className="output-body chat-scrollbar"><code>{activeOutputText || '(无输出)'}</code></pre>
|
|
248
|
-
</div>
|
|
249
|
-
)}
|
|
250
|
-
|
|
251
|
-
{/* 底部操作区 */}
|
|
252
|
-
<div className="tool-footer">
|
|
253
|
-
<DropdownSelector
|
|
254
|
-
value={currentMode}
|
|
255
|
-
options={modeOptions}
|
|
256
|
-
onSelect={handleModeChange}
|
|
257
|
-
/>
|
|
258
|
-
|
|
259
|
-
<div className="tool-actions">
|
|
260
|
-
{status === 'pending' ? (
|
|
261
|
-
<>
|
|
262
|
-
<button className="btn btn-skip" onClick={handleSkip}>跳过</button>
|
|
263
|
-
<button className="btn btn-run" onClick={handleRun}>
|
|
264
|
-
运行
|
|
265
|
-
<Icon icon="lucide:arrow-right" width={14} />
|
|
266
|
-
</button>
|
|
267
|
-
</>
|
|
268
|
-
) : status === 'running' ? (
|
|
269
|
-
<>
|
|
270
|
-
<span className="status-text running">
|
|
271
|
-
<Icon icon="lucide:loader-2" width={14} className="spinning" />
|
|
272
|
-
运行中
|
|
273
|
-
</span>
|
|
274
|
-
<button className="btn btn-cancel" onClick={handleCancel}>
|
|
275
|
-
<Icon icon="lucide:x" width={14} />
|
|
276
|
-
取消
|
|
277
|
-
</button>
|
|
278
|
-
</>
|
|
279
|
-
) : (
|
|
280
|
-
<span className={`status-text ${status}`}>
|
|
281
|
-
<Icon icon={statusDisplayIcons[status] ?? 'lucide:clock'} width={14} />
|
|
282
|
-
{statusTexts[status] ?? ''}
|
|
283
|
-
</span>
|
|
284
|
-
)}
|
|
285
|
-
</div>
|
|
286
|
-
</div>
|
|
287
|
-
</CollapsibleCard>
|
|
288
|
-
)
|
|
289
|
-
}
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
.tool-result-part {
|
|
2
|
-
}
|
|
3
|
-
|
|
4
|
-
.default-result {
|
|
5
|
-
border-radius: 8px;
|
|
6
|
-
background: var(--chat-muted, #2a2a2a);
|
|
7
|
-
border: 1px solid var(--chat-border, #333);
|
|
8
|
-
overflow: hidden;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
/* 将样式限定在 .default-result 内部,避免污染其他组件 */
|
|
12
|
-
.default-result .result-header {
|
|
13
|
-
display: flex;
|
|
14
|
-
align-items: center;
|
|
15
|
-
gap: 8px;
|
|
16
|
-
padding: 8px 12px;
|
|
17
|
-
cursor: pointer;
|
|
18
|
-
user-select: none;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
.default-result .result-header:hover {
|
|
22
|
-
background: var(--chat-hover, #333);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
.default-result .result-icon {
|
|
26
|
-
display: flex;
|
|
27
|
-
align-items: center;
|
|
28
|
-
justify-content: center;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
.default-result .result-icon .success {
|
|
32
|
-
color: var(--chat-success, #22c55e);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
.default-result .result-icon .error {
|
|
36
|
-
color: var(--chat-error, #ef4444);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
.default-result .result-name {
|
|
40
|
-
font-size: 13px;
|
|
41
|
-
font-weight: 500;
|
|
42
|
-
color: var(--chat-text, #ccc);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
.default-result .result-chevron {
|
|
46
|
-
margin-left: auto;
|
|
47
|
-
color: var(--chat-text-muted, #666);
|
|
48
|
-
transition: transform 0.2s;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
.default-result.expanded .result-chevron {
|
|
52
|
-
transform: rotate(180deg);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
.default-result .result-content {
|
|
56
|
-
padding: 12px;
|
|
57
|
-
padding-top: 0;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
.default-result .result-content pre {
|
|
61
|
-
margin: 0;
|
|
62
|
-
font-size: 12px;
|
|
63
|
-
font-family: "SF Mono", Monaco, "Cascadia Code", monospace;
|
|
64
|
-
color: var(--chat-code-text, var(--chat-text-muted, #999));
|
|
65
|
-
white-space: pre-wrap;
|
|
66
|
-
word-break: break-word;
|
|
67
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Part 渲染组件导出
|
|
3
|
-
*
|
|
4
|
-
* 内置 Part 类型:text, thinking, search, tool_call, image, error
|
|
5
|
-
* 自定义 Part 类型(如 weather, stock)通过 partRenderers 注册
|
|
6
|
-
*/
|
|
7
|
-
export { CollapsibleCard } from './CollapsibleCard'
|
|
8
|
-
export { TextPart } from './TextPart'
|
|
9
|
-
export { ThinkingPart } from './ThinkingPart'
|
|
10
|
-
export { SearchPart } from './SearchPart'
|
|
11
|
-
export { ToolCallPart } from './ToolCallPart'
|
|
12
|
-
export { ImagePart } from './ImagePart'
|
|
13
|
-
export { ErrorPart } from './ErrorPart'
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
function normalizeLanguage(lang?: string): string {
|
|
2
|
-
return (lang || '').trim().toLowerCase()
|
|
3
|
-
}
|
|
4
|
-
|
|
5
|
-
export function isMermaidLanguage(lang?: string): boolean {
|
|
6
|
-
return normalizeLanguage(lang) === 'mermaid'
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export function isLatexLanguage(lang?: string): boolean {
|
|
10
|
-
const l = normalizeLanguage(lang)
|
|
11
|
-
return l === 'latex' || l === 'katex' || l === 'tex'
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function isLatexDocument(code: string): boolean {
|
|
15
|
-
const t = (code || '').trim()
|
|
16
|
-
return /\\documentclass\b|\\usepackage\b|\\begin\{document\}|\\end\{document\}/.test(t)
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function canVisualizeLatex(code: string): boolean {
|
|
20
|
-
const t = (code || '').trim()
|
|
21
|
-
if (!t) return false
|
|
22
|
-
if (isLatexDocument(t)) return false
|
|
23
|
-
return true
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export function hasLatexDelimiters(code: string): boolean {
|
|
27
|
-
const t = (code || '').trim()
|
|
28
|
-
if (!t) return false
|
|
29
|
-
return (
|
|
30
|
-
/\$\$[\s\S]*\$\$/.test(t) ||
|
|
31
|
-
/\\\[[\s\S]*\\\]/.test(t) ||
|
|
32
|
-
/\\\([\s\S]*\\\)/.test(t) ||
|
|
33
|
-
/(?<!\$)\$(?!\$)[\s\S]*?\$(?!\$)/.test(t)
|
|
34
|
-
)
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export function shouldShowVisualToggle(lang?: string, code?: string): boolean {
|
|
38
|
-
if (isMermaidLanguage(lang)) return true
|
|
39
|
-
if (isLatexLanguage(lang)) return canVisualizeLatex(code || '')
|
|
40
|
-
return false
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { renderMarkdown } from '@huyooo/ai-chat-shared'
|
|
2
|
-
import { canVisualizeLatex, hasLatexDelimiters } from './visual-predicate'
|
|
3
|
-
|
|
4
|
-
export function renderFencedLatexToHtml(code: string): string {
|
|
5
|
-
const t = (code || '').trim()
|
|
6
|
-
if (!t) return ''
|
|
7
|
-
|
|
8
|
-
if (!canVisualizeLatex(t)) {
|
|
9
|
-
return renderMarkdown(
|
|
10
|
-
`> 不支持将完整 LaTeX 文档作为公式渲染,请切换到代码视图查看。\n\n\`\`\`latex\n${t}\n\`\`\``
|
|
11
|
-
)
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
if (hasLatexDelimiters(t)) return renderMarkdown(t)
|
|
15
|
-
|
|
16
|
-
return renderMarkdown(`$$\n${t}\n$$`)
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
/** 功能项 */
|
|
2
|
-
export interface WelcomeFeature {
|
|
3
|
-
name: string;
|
|
4
|
-
icon: string;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
/** 快捷任务 */
|
|
8
|
-
export interface WelcomeTask {
|
|
9
|
-
name: string;
|
|
10
|
-
desc: string;
|
|
11
|
-
prompt: string;
|
|
12
|
-
icon: string;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/** 欢迎页配置 */
|
|
16
|
-
export interface WelcomeConfig {
|
|
17
|
-
/** 标题 */
|
|
18
|
-
title: string;
|
|
19
|
-
/** 副标题 */
|
|
20
|
-
subtitle: string;
|
|
21
|
-
/** 图标 */
|
|
22
|
-
icon: string;
|
|
23
|
-
/** 功能列表 */
|
|
24
|
-
features: WelcomeFeature[];
|
|
25
|
-
/** 快捷任务 */
|
|
26
|
-
tasks: WelcomeTask[];
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/** 默认配置 */
|
|
30
|
-
export const defaultWelcomeConfig: WelcomeConfig = {
|
|
31
|
-
title: 'AI 助手',
|
|
32
|
-
subtitle: '智能对话 · 文档分析 · 内容创作',
|
|
33
|
-
icon: 'lucide:sparkles',
|
|
34
|
-
features: [
|
|
35
|
-
{ name: '上下文引用', icon: 'lucide:at-sign' },
|
|
36
|
-
{ name: '图片分析', icon: 'lucide:image' },
|
|
37
|
-
{ name: '深度思考', icon: 'lucide:sparkles' },
|
|
38
|
-
{ name: '联网搜索', icon: 'lucide:globe' },
|
|
39
|
-
],
|
|
40
|
-
tasks: [
|
|
41
|
-
{ name: '总结内容', desc: '快速总结文档要点', prompt: '帮我总结一下这段内容的主要要点', icon: 'lucide:file-text' },
|
|
42
|
-
{ name: '翻译文本', desc: '多语言翻译助手', prompt: '帮我翻译这段文字', icon: 'lucide:languages' },
|
|
43
|
-
{ name: '写作助手', desc: '帮助撰写文章', prompt: '帮我写一篇关于以下主题的文章:', icon: 'lucide:pen-tool' },
|
|
44
|
-
{ name: '问答对话', desc: '回答各种问题', prompt: '我想了解一下:', icon: 'lucide:message-circle' },
|
|
45
|
-
],
|
|
46
|
-
};
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { createContext, useContext } from 'react'
|
|
2
|
-
import type { AutoRunConfig } from '@huyooo/ai-chat-bridge-electron/renderer'
|
|
3
|
-
|
|
4
|
-
export interface AutoRunConfigContextValue {
|
|
5
|
-
autoRunConfig: AutoRunConfig
|
|
6
|
-
saveAutoRunConfig: (config: AutoRunConfig) => Promise<void>
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export const AutoRunConfigContext = createContext<AutoRunConfigContextValue | null>(null)
|
|
10
|
-
|
|
11
|
-
export function useAutoRunConfig(): AutoRunConfigContextValue | null {
|
|
12
|
-
return useContext(AutoRunConfigContext)
|
|
13
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { createContext, useContext } from 'react'
|
|
2
|
-
import type { ChatAdapter } from '../adapter'
|
|
3
|
-
|
|
4
|
-
export const ChatAdapterContext = createContext<ChatAdapter | null>(null)
|
|
5
|
-
|
|
6
|
-
export function useChatAdapter(): ChatAdapter | null {
|
|
7
|
-
return useContext(ChatAdapterContext)
|
|
8
|
-
}
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 全局 ChatInput 状态 Context
|
|
3
|
-
* 用于在 MessageBubble 中的 ChatInput 共享和修改全局状态
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { createContext, useContext, type ReactNode, type FC } from 'react'
|
|
7
|
-
import type { ChatMode, ModelOption } from '../types'
|
|
8
|
-
import type { ChatAdapter } from '@huyooo/ai-chat-bridge-electron/renderer'
|
|
9
|
-
|
|
10
|
-
export interface ChatInputContextValue {
|
|
11
|
-
mode: ChatMode
|
|
12
|
-
model: string
|
|
13
|
-
models: ModelOption[]
|
|
14
|
-
webSearch: boolean
|
|
15
|
-
thinking: boolean
|
|
16
|
-
isLoading: boolean
|
|
17
|
-
/** Electron adapter(用于 @ 文件选择) */
|
|
18
|
-
adapter?: ChatAdapter
|
|
19
|
-
// cwd 已解耦,不再通过 context 传递
|
|
20
|
-
// FileBrowser 会直接通过 adapter.setCwd() 同步到 Agent
|
|
21
|
-
setMode: (value: ChatMode) => void
|
|
22
|
-
setModel: (value: string) => void
|
|
23
|
-
setWebSearch: (value: boolean) => void
|
|
24
|
-
setThinking: (value: boolean) => void
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const ChatInputContext = createContext<ChatInputContextValue | null>(null)
|
|
28
|
-
|
|
29
|
-
interface ChatInputProviderProps {
|
|
30
|
-
value: ChatInputContextValue
|
|
31
|
-
children: ReactNode
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export const ChatInputProvider: FC<ChatInputProviderProps> = ({ value, children }) => {
|
|
35
|
-
return <ChatInputContext.Provider value={value}>{children}</ChatInputContext.Provider>
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export function useChatInputContext(): ChatInputContextValue | null {
|
|
39
|
-
return useContext(ChatInputContext)
|
|
40
|
-
}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Part 渲染器上下文
|
|
3
|
-
* 用于注入自定义 Part 类型渲染器(如 weather, stock 等)
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { createContext, type ComponentType, type ReactNode, type FC } from 'react'
|
|
7
|
-
|
|
8
|
-
/** Part 渲染器 Props(每个自定义渲染器接收的 props) */
|
|
9
|
-
export interface PartRendererProps {
|
|
10
|
-
[key: string]: unknown
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/** Part 渲染器映射(key: part.type, value: 渲染组件) */
|
|
14
|
-
export type PartRenderers = Record<string, ComponentType<PartRendererProps>>
|
|
15
|
-
|
|
16
|
-
/** Part 渲染器上下文 */
|
|
17
|
-
export const PartRenderersContext = createContext<PartRenderers>({})
|
|
18
|
-
|
|
19
|
-
/** Part 渲染器 Provider Props */
|
|
20
|
-
interface PartRenderersProviderProps {
|
|
21
|
-
partRenderers?: PartRenderers
|
|
22
|
-
children: ReactNode
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/** Part 渲染器 Provider */
|
|
26
|
-
export const PartRenderersProvider: FC<PartRenderersProviderProps> = ({
|
|
27
|
-
partRenderers = {},
|
|
28
|
-
children,
|
|
29
|
-
}) => {
|
|
30
|
-
return (
|
|
31
|
-
<PartRenderersContext.Provider value={partRenderers}>
|
|
32
|
-
{children}
|
|
33
|
-
</PartRenderersContext.Provider>
|
|
34
|
-
)
|
|
35
|
-
}
|