@huyooo/ai-chat-frontend-react 0.1.6 → 0.1.8

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 (91) hide show
  1. package/README.md +368 -0
  2. package/dist/index.css +2575 -0
  3. package/dist/index.css.map +1 -0
  4. package/dist/index.d.ts +378 -135
  5. package/dist/index.js +3956 -1042
  6. package/dist/index.js.map +1 -1
  7. package/dist/style.css +48 -987
  8. package/package.json +7 -4
  9. package/src/adapter.ts +10 -70
  10. package/src/components/ChatPanel.tsx +373 -117
  11. package/src/components/common/ConfirmDialog.css +136 -0
  12. package/src/components/common/ConfirmDialog.tsx +91 -0
  13. package/src/components/common/CopyButton.css +22 -0
  14. package/src/components/common/CopyButton.tsx +46 -0
  15. package/src/components/common/IndexingSettings.css +207 -0
  16. package/src/components/common/IndexingSettings.tsx +398 -0
  17. package/src/components/common/SettingsPanel.css +256 -0
  18. package/src/components/common/SettingsPanel.tsx +120 -0
  19. package/src/components/common/Toast.css +50 -0
  20. package/src/components/common/Toast.tsx +38 -0
  21. package/src/components/common/ToggleSwitch.css +52 -0
  22. package/src/components/common/ToggleSwitch.tsx +20 -0
  23. package/src/components/header/ChatHeader.css +285 -0
  24. package/src/components/header/ChatHeader.tsx +376 -0
  25. package/src/components/input/AtFilePicker.css +147 -0
  26. package/src/components/input/AtFilePicker.tsx +519 -0
  27. package/src/components/input/ChatInput.css +204 -0
  28. package/src/components/input/ChatInput.tsx +506 -0
  29. package/src/components/input/DropdownSelector.css +159 -0
  30. package/src/components/input/DropdownSelector.tsx +195 -0
  31. package/src/components/input/ImagePreviewModal.css +124 -0
  32. package/src/components/input/ImagePreviewModal.tsx +118 -0
  33. package/src/components/input/at-views/AtBranchView.tsx +34 -0
  34. package/src/components/input/at-views/AtBrowserView.tsx +34 -0
  35. package/src/components/input/at-views/AtChatsView.tsx +34 -0
  36. package/src/components/input/at-views/AtDocsView.tsx +34 -0
  37. package/src/components/input/at-views/AtFilesView.tsx +168 -0
  38. package/src/components/input/at-views/AtTerminalsView.tsx +34 -0
  39. package/src/components/input/at-views/AtViewStyles.css +143 -0
  40. package/src/components/input/at-views/index.ts +9 -0
  41. package/src/components/message/ContentRenderer.css +9 -0
  42. package/src/components/message/ContentRenderer.tsx +63 -0
  43. package/src/components/message/MessageBubble.css +190 -0
  44. package/src/components/message/MessageBubble.tsx +231 -0
  45. package/src/components/message/PartsRenderer.css +4 -0
  46. package/src/components/message/PartsRenderer.tsx +114 -0
  47. package/src/components/message/ToolResultRenderer.tsx +21 -0
  48. package/src/components/message/WelcomeMessage.css +221 -0
  49. package/src/components/message/WelcomeMessage.tsx +93 -0
  50. package/src/components/message/blocks/CodeBlock.tsx +60 -0
  51. package/src/components/message/blocks/TextBlock.tsx +15 -0
  52. package/src/components/message/blocks/blocks.css +141 -0
  53. package/src/components/message/blocks/index.ts +6 -0
  54. package/src/components/message/parts/CollapsibleCard.css +78 -0
  55. package/src/components/message/parts/CollapsibleCard.tsx +77 -0
  56. package/src/components/message/parts/ErrorPart.css +9 -0
  57. package/src/components/message/parts/ErrorPart.tsx +40 -0
  58. package/src/components/message/parts/ImagePart.css +50 -0
  59. package/src/components/message/parts/ImagePart.tsx +54 -0
  60. package/src/components/message/parts/SearchPart.css +44 -0
  61. package/src/components/message/parts/SearchPart.tsx +63 -0
  62. package/src/components/message/parts/TextPart.css +10 -0
  63. package/src/components/message/parts/TextPart.tsx +20 -0
  64. package/src/components/message/parts/ThinkingPart.css +9 -0
  65. package/src/components/message/parts/ThinkingPart.tsx +48 -0
  66. package/src/components/message/parts/ToolCallPart.css +220 -0
  67. package/src/components/message/parts/ToolCallPart.tsx +285 -0
  68. package/src/components/message/parts/ToolResultPart.css +68 -0
  69. package/src/components/message/parts/ToolResultPart.tsx +96 -0
  70. package/src/components/message/parts/index.ts +11 -0
  71. package/src/components/message/tool-results/DefaultToolResult.tsx +26 -0
  72. package/src/components/message/tool-results/SearchResults.tsx +69 -0
  73. package/src/components/message/tool-results/WeatherCard.tsx +63 -0
  74. package/src/components/message/tool-results/index.ts +7 -0
  75. package/src/components/message/tool-results/tool-results.css +179 -0
  76. package/src/components/message/welcome-types.ts +46 -0
  77. package/src/context/AutoRunConfigContext.tsx +13 -0
  78. package/src/context/ChatAdapterContext.tsx +8 -0
  79. package/src/context/ChatInputContext.tsx +40 -0
  80. package/src/context/RenderersContext.tsx +41 -0
  81. package/src/hooks/useChat.ts +855 -237
  82. package/src/hooks/useImageUpload.ts +253 -0
  83. package/src/index.ts +96 -39
  84. package/src/styles.css +48 -987
  85. package/src/types/index.ts +172 -103
  86. package/src/utils/fileIcon.ts +49 -0
  87. package/src/components/ChatInput.tsx +0 -368
  88. package/src/components/chat/messages/ExecutionSteps.tsx +0 -234
  89. package/src/components/chat/messages/MessageBubble.tsx +0 -130
  90. package/src/components/chat/ui/ChatHeader.tsx +0 -301
  91. package/src/components/chat/ui/WelcomeMessage.tsx +0 -107
@@ -1,26 +1,29 @@
1
1
  /**
2
2
  * AI Chat 前端类型定义
3
- * 核心类型(ChatAdapter, SessionRecord, MessageRecord 等)从 bridge-electron 导出
4
- * 这里只定义前端特有的类型
3
+ * 核心类型从 bridge-electron 导出,保持类型一致性
4
+ *
5
+ * 架构:消息内容使用 ContentPart 数组,支持流式渲染和自定义 UI
5
6
  */
6
7
 
7
- // 从 bridge-electron 导入类型(用于本文件)
8
+ // 从 bridge-electron 导入类型
8
9
  import type {
9
10
  ChatMode as ChatModeType,
10
- ModelConfig as ModelConfigType,
11
- ModelProvider as ModelProviderType,
12
11
  ThinkingMode as ThinkingModeType,
13
12
  SessionRecord as SessionRecordType,
14
13
  MessageRecord as MessageRecordType,
14
+ ModelOption as ModelOptionType,
15
+ ProviderType as ProviderTypeType,
15
16
  } from '@huyooo/ai-chat-bridge-electron/renderer'
16
17
 
17
18
  // 重新导出核心类型
18
19
  export type ChatMode = ChatModeType
19
- export type ModelConfig = ModelConfigType
20
- export type ModelProvider = ModelProviderType
21
20
  export type ThinkingMode = ThinkingModeType
22
21
  export type SessionRecord = SessionRecordType
23
22
  export type MessageRecord = MessageRecordType
23
+ export type ModelOption = ModelOptionType
24
+ export type ProviderType = ProviderTypeType
25
+
26
+ // ==================== Content Part 类型 ====================
24
27
 
25
28
  /** 搜索结果 */
26
29
  export interface SearchResult {
@@ -29,115 +32,181 @@ export interface SearchResult {
29
32
  snippet: string
30
33
  }
31
34
 
32
- /** 工具调用 */
33
- export interface ToolCall {
35
+ /** 文本内容 Part */
36
+ export interface TextPart {
37
+ type: 'text'
38
+ text: string
39
+ }
40
+
41
+ /** 思考过程 Part */
42
+ export interface ThinkingPart {
43
+ type: 'thinking'
44
+ text: string
45
+ status: 'running' | 'done'
46
+ duration?: number // 秒
47
+ }
48
+
49
+ /** 搜索 Part */
50
+ export interface SearchPart {
51
+ type: 'search'
52
+ query?: string
53
+ results?: SearchResult[]
54
+ status: 'running' | 'done'
55
+ }
56
+
57
+ /** 工具调用 Part */
58
+ export interface ToolCallPart {
59
+ type: 'tool_call'
60
+ id: string
34
61
  name: string
35
62
  args?: Record<string, unknown>
36
- result?: string
37
- status: 'running' | 'success' | 'error'
63
+ status: 'pending' | 'running' | 'done' | 'error' | 'cancelled' | 'skipped' // pending: 等待用户批准, cancelled: 已取消, skipped: 已跳过
64
+ result: unknown | null // 执行结果,null 表示尚未执行或没有结果
65
+ }
66
+
67
+ /** 工具结果 Part - 用于自定义 UI 渲染(备用,主要使用 ToolCallPart) */
68
+ export interface ToolResultPart {
69
+ type: 'tool_result'
70
+ id: string
71
+ name: string
72
+ args?: Record<string, unknown>
73
+ result: unknown // 解析后的结构化数据
74
+ status: 'done' | 'error' | 'cancelled' | 'skipped'
75
+ }
76
+
77
+ /** 图片 Part */
78
+ export interface ImagePart {
79
+ type: 'image'
80
+ url: string
81
+ }
82
+
83
+ /** 错误 Part */
84
+ export interface ErrorPart {
85
+ type: 'error'
86
+ message: string
87
+ category?: string
88
+ retryable?: boolean
89
+ }
90
+
91
+ /** 内容 Part 联合类型 */
92
+ export type ContentPart =
93
+ | TextPart
94
+ | ThinkingPart
95
+ | SearchPart
96
+ | ToolCallPart
97
+ | ToolResultPart
98
+ | ImagePart
99
+ | ErrorPart
100
+
101
+ /** 内容 Part 类型字符串 */
102
+ export type ContentPartType = ContentPart['type']
103
+
104
+ /** 步骤折叠模式 */
105
+ export type StepsExpandedType = 'open' | 'close' | 'auto'
106
+
107
+ // ==================== 消息类型 ====================
108
+
109
+ /** 错误详情(结构化错误信息) */
110
+ export interface ErrorDetails {
111
+ category?: string
112
+ message: string
113
+ code?: string
114
+ statusCode?: number
115
+ retryable?: boolean
116
+ retryAfter?: number
38
117
  }
39
118
 
40
- /** 聊天消息(前端显示用) */
119
+ /** 聊天消息(前端显示用)- 新架构:基于 parts 数组 */
41
120
  export interface ChatMessage {
42
121
  id: string
43
122
  role: 'user' | 'assistant'
44
- content: string
123
+ /** 内容 parts 数组 - 核心渲染数据 */
124
+ parts: ContentPart[]
125
+ /** 生成此消息时使用的模型 */
126
+ model?: string
127
+ /** 生成此消息时使用的模式 (ask/agent) */
128
+ mode?: string
129
+ /** 生成此消息时是否启用 web 搜索 */
130
+ webSearchEnabled?: boolean
131
+ /** 生成此消息时是否启用深度思考 */
132
+ thinkingEnabled?: boolean
133
+ /** 用户上传的图片(仅用户消息) */
45
134
  images?: string[]
46
- thinking?: string
47
- thinkingComplete?: boolean
48
- searchResults?: SearchResult[]
49
- searching?: boolean
50
- toolCalls?: ToolCall[]
51
- copied?: boolean
135
+ /** 是否正在加载 */
52
136
  loading?: boolean
137
+ /** 是否已复制 */
138
+ copied?: boolean
139
+ /** 消息时间戳 */
53
140
  timestamp?: Date
141
+ /** 错误详情(如果有错误) */
142
+ error?: ErrorDetails
143
+ /** 是否被用户中止 */
144
+ aborted?: boolean
54
145
  }
55
146
 
56
- /** 默认模型列表 */
57
- export const DEFAULT_MODELS: ModelConfigType[] = [
58
- {
59
- provider: 'openrouter',
60
- model: 'anthropic/claude-opus-4.5',
61
- displayName: 'Claude Opus 4.5',
62
- supportsTools: true,
63
- supportsWebSearch: true,
64
- supportedThinkingModes: ['enabled', 'disabled'],
65
- },
66
- {
67
- provider: 'doubao',
68
- model: 'doubao-seed-1-6-251015',
69
- displayName: 'Doubao Seed',
70
- supportsTools: true,
71
- supportsWebSearch: true,
72
- supportedThinkingModes: ['enabled', 'disabled'],
73
- },
74
- {
75
- provider: 'deepseek',
76
- model: 'deepseek-v3-1-terminus',
77
- displayName: 'DeepSeek V3',
78
- supportsTools: true,
79
- supportsWebSearch: true,
80
- supportedThinkingModes: ['enabled', 'disabled'],
81
- },
82
- {
83
- provider: 'qwen',
84
- model: 'qwen3-max-preview',
85
- displayName: 'Qwen Max',
86
- supportsTools: true,
87
- supportsWebSearch: true,
88
- supportedThinkingModes: ['enabled', 'disabled'],
89
- },
90
- {
91
- provider: 'gemini',
92
- model: 'gemini-3-pro-preview',
93
- displayName: 'Gemini 3 Pro',
94
- supportsTools: true,
95
- supportsWebSearch: true,
96
- supportedThinkingModes: ['enabled', 'disabled'],
97
- },
98
- ]
99
-
100
- // ================ 以下为向后兼容的类型 ================
101
-
102
- /** @deprecated 使用 SessionRecord */
103
- export interface ChatSession {
104
- id: string
105
- title: string
106
- messages: ChatMessage[]
107
- createdAt: Date
108
- updatedAt: Date
147
+ /** 获取消息的纯文本内容(用于复制、保存等) */
148
+ export function getMessageText(message: ChatMessage): string {
149
+ return message.parts
150
+ .filter((p): p is TextPart => p.type === 'text')
151
+ .map(p => p.text)
152
+ .join('')
153
+ }
154
+
155
+ /** 输入区配置快照(用于历史回溯/重发) */
156
+ export interface ChatInputOptions {
157
+ mode: ChatMode
158
+ model: string
159
+ webSearchEnabled: boolean
160
+ thinkingEnabled: boolean
109
161
  }
110
162
 
111
- /** 音视频操作类型 */
112
- export interface MediaOperation {
163
+ // ==================== 兼容旧类型(逐步废弃)====================
164
+
165
+ /** @deprecated 使用 ContentPart 替代 */
166
+ export type ExecutionStepType = 'thinking' | 'search' | 'tool_call' | 'error'
167
+
168
+ /** @deprecated 使用 ContentPart 替代 */
169
+ export type ExecutionStepStatus = 'running' | 'completed' | 'error'
170
+
171
+ /** @deprecated 使用 ContentPart 替代 */
172
+ export type ExecutionStep = {
113
173
  id: string
114
- type: 'clip' | 'transcode' | 'merge' | 'extract_audio' | 'add_subtitle' | 'analyze'
115
- description: string
116
- sourceFiles: string[]
117
- targetFile?: string
118
- parameters?: Record<string, unknown>
119
- status?: 'pending' | 'applied' | 'rejected'
120
- preview?: string
121
- }
122
-
123
- /** @deprecated 使用字符串枚举 */
124
- export type AiModel = 'gemini-3-pro-preview' | 'gemini-3-pro-image-preview'
125
-
126
- export enum FileType {
127
- FOLDER = 'folder',
128
- IMAGE = 'image',
129
- VIDEO = 'video',
130
- AUDIO = 'audio',
131
- TEXT = 'text',
132
- PDF = 'pdf',
133
- CODE = 'code',
134
- ARCHIVE = 'archive',
135
- OTHER = 'other'
136
- }
137
-
138
- export interface DiffStat {
139
- file: string
140
- additions: number
141
- deletions: number
142
- type: 'modified' | 'added' | 'deleted'
174
+ type: ExecutionStepType
175
+ status: ExecutionStepStatus
176
+ startedAt?: number
177
+ completedAt?: number
178
+ duration?: number
179
+ content?: string
180
+ query?: string
181
+ results?: SearchResult[]
182
+ callId?: string
183
+ name?: string
184
+ args?: Record<string, unknown>
185
+ result?: string
186
+ message?: string
187
+ category?: string
188
+ }
189
+
190
+ /** @deprecated 使用 ContentPart 替代 - 思考步骤类型 */
191
+ export type ThinkingStep = ExecutionStep & {
192
+ type: 'thinking'
193
+ status: 'running' | 'completed'
194
+ }
195
+
196
+ /** @deprecated 使用 ContentPart 替代 - 搜索步骤类型 */
197
+ export type SearchStep = ExecutionStep & {
198
+ type: 'search'
199
+ status: 'running' | 'completed'
200
+ }
201
+
202
+ /** @deprecated 使用 ContentPart 替代 - 工具调用步骤类型 */
203
+ export type ToolCallStep = ExecutionStep & {
204
+ type: 'tool_call'
205
+ status: 'running' | 'completed' | 'error'
206
+ }
207
+
208
+ /** @deprecated 使用 ContentPart 替代 - 错误步骤类型 */
209
+ export type ErrorStep = ExecutionStep & {
210
+ type: 'error'
211
+ status: 'error'
143
212
  }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * 根据文件名或路径获取对应的图标
3
+ */
4
+ export function getFileIcon(nameOrPath: string): string {
5
+ const name = basename(nameOrPath).toLowerCase();
6
+ const ext = name.includes('.') ? name.slice(name.lastIndexOf('.')) : '';
7
+
8
+ if (ext === '.vue') return 'vscode-icons:file-type-vue';
9
+ if (ext === '.tsx') return 'vscode-icons:file-type-reactts';
10
+ if (ext === '.jsx') return 'vscode-icons:file-type-reactjs';
11
+ if (ext === '.ts') return 'vscode-icons:file-type-typescript';
12
+ if (ext === '.js') return 'vscode-icons:file-type-js';
13
+ if (ext === '.css') return 'vscode-icons:file-type-css';
14
+ if (ext === '.scss' || ext === '.sass') return 'vscode-icons:file-type-scss';
15
+ if (ext === '.less') return 'vscode-icons:file-type-less';
16
+ if (ext === '.json') return 'vscode-icons:file-type-json';
17
+ if (ext === '.yaml' || ext === '.yml') return 'vscode-icons:file-type-yaml';
18
+ if (ext === '.xml') return 'vscode-icons:file-type-xml';
19
+ if (ext === '.md') return 'vscode-icons:file-type-markdown';
20
+ if (ext === '.txt') return 'vscode-icons:file-type-text';
21
+ if (['.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg', '.ico'].includes(ext)) {
22
+ return 'vscode-icons:file-type-image';
23
+ }
24
+ if (ext === '.html' || ext === '.htm') return 'vscode-icons:file-type-html';
25
+ if (ext === '.py') return 'vscode-icons:file-type-python';
26
+ if (ext === '.go') return 'vscode-icons:file-type-go';
27
+ if (ext === '.rs') return 'vscode-icons:file-type-rust';
28
+ if (ext === '.sh' || ext === '.bash' || ext === '.zsh') return 'vscode-icons:file-type-shell';
29
+
30
+ return 'lucide:file';
31
+ }
32
+
33
+ /**
34
+ * 获取路径的文件名部分
35
+ */
36
+ export function basename(p: string): string {
37
+ const normalized = p.replace(/\\/g, '/');
38
+ const idx = normalized.lastIndexOf('/');
39
+ return idx >= 0 ? normalized.slice(idx + 1) || normalized : normalized;
40
+ }
41
+
42
+ /**
43
+ * 获取路径的目录部分
44
+ */
45
+ export function dirname(p: string): string {
46
+ const normalized = p.replace(/\\/g, '/');
47
+ const idx = normalized.lastIndexOf('/');
48
+ return idx > 0 ? normalized.slice(0, idx) : '';
49
+ }