@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,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,12 +0,0 @@
1
- export {
2
- isMermaidLanguage,
3
- isLatexLanguage,
4
- isLatexDocument,
5
- canVisualizeLatex,
6
- hasLatexDelimiters,
7
- shouldShowVisualToggle,
8
- } from './visual-predicate'
9
-
10
- export { renderFencedLatexToHtml } from './visual-render'
11
-
12
-
@@ -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
- }