@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.
- package/README.md +368 -0
- package/dist/index.css +2575 -0
- package/dist/index.css.map +1 -0
- package/dist/index.d.ts +378 -135
- package/dist/index.js +3956 -1042
- package/dist/index.js.map +1 -1
- package/dist/style.css +48 -987
- package/package.json +7 -4
- package/src/adapter.ts +10 -70
- package/src/components/ChatPanel.tsx +373 -117
- package/src/components/common/ConfirmDialog.css +136 -0
- package/src/components/common/ConfirmDialog.tsx +91 -0
- package/src/components/common/CopyButton.css +22 -0
- package/src/components/common/CopyButton.tsx +46 -0
- package/src/components/common/IndexingSettings.css +207 -0
- package/src/components/common/IndexingSettings.tsx +398 -0
- package/src/components/common/SettingsPanel.css +256 -0
- package/src/components/common/SettingsPanel.tsx +120 -0
- package/src/components/common/Toast.css +50 -0
- package/src/components/common/Toast.tsx +38 -0
- package/src/components/common/ToggleSwitch.css +52 -0
- package/src/components/common/ToggleSwitch.tsx +20 -0
- package/src/components/header/ChatHeader.css +285 -0
- package/src/components/header/ChatHeader.tsx +376 -0
- package/src/components/input/AtFilePicker.css +147 -0
- package/src/components/input/AtFilePicker.tsx +519 -0
- package/src/components/input/ChatInput.css +204 -0
- package/src/components/input/ChatInput.tsx +506 -0
- package/src/components/input/DropdownSelector.css +159 -0
- package/src/components/input/DropdownSelector.tsx +195 -0
- package/src/components/input/ImagePreviewModal.css +124 -0
- package/src/components/input/ImagePreviewModal.tsx +118 -0
- package/src/components/input/at-views/AtBranchView.tsx +34 -0
- package/src/components/input/at-views/AtBrowserView.tsx +34 -0
- package/src/components/input/at-views/AtChatsView.tsx +34 -0
- package/src/components/input/at-views/AtDocsView.tsx +34 -0
- package/src/components/input/at-views/AtFilesView.tsx +168 -0
- package/src/components/input/at-views/AtTerminalsView.tsx +34 -0
- package/src/components/input/at-views/AtViewStyles.css +143 -0
- package/src/components/input/at-views/index.ts +9 -0
- package/src/components/message/ContentRenderer.css +9 -0
- package/src/components/message/ContentRenderer.tsx +63 -0
- package/src/components/message/MessageBubble.css +190 -0
- package/src/components/message/MessageBubble.tsx +231 -0
- package/src/components/message/PartsRenderer.css +4 -0
- package/src/components/message/PartsRenderer.tsx +114 -0
- package/src/components/message/ToolResultRenderer.tsx +21 -0
- package/src/components/message/WelcomeMessage.css +221 -0
- package/src/components/message/WelcomeMessage.tsx +93 -0
- package/src/components/message/blocks/CodeBlock.tsx +60 -0
- package/src/components/message/blocks/TextBlock.tsx +15 -0
- package/src/components/message/blocks/blocks.css +141 -0
- package/src/components/message/blocks/index.ts +6 -0
- package/src/components/message/parts/CollapsibleCard.css +78 -0
- package/src/components/message/parts/CollapsibleCard.tsx +77 -0
- package/src/components/message/parts/ErrorPart.css +9 -0
- package/src/components/message/parts/ErrorPart.tsx +40 -0
- package/src/components/message/parts/ImagePart.css +50 -0
- package/src/components/message/parts/ImagePart.tsx +54 -0
- package/src/components/message/parts/SearchPart.css +44 -0
- package/src/components/message/parts/SearchPart.tsx +63 -0
- package/src/components/message/parts/TextPart.css +10 -0
- package/src/components/message/parts/TextPart.tsx +20 -0
- package/src/components/message/parts/ThinkingPart.css +9 -0
- package/src/components/message/parts/ThinkingPart.tsx +48 -0
- package/src/components/message/parts/ToolCallPart.css +220 -0
- package/src/components/message/parts/ToolCallPart.tsx +285 -0
- package/src/components/message/parts/ToolResultPart.css +68 -0
- package/src/components/message/parts/ToolResultPart.tsx +96 -0
- package/src/components/message/parts/index.ts +11 -0
- package/src/components/message/tool-results/DefaultToolResult.tsx +26 -0
- package/src/components/message/tool-results/SearchResults.tsx +69 -0
- package/src/components/message/tool-results/WeatherCard.tsx +63 -0
- package/src/components/message/tool-results/index.ts +7 -0
- package/src/components/message/tool-results/tool-results.css +179 -0
- package/src/components/message/welcome-types.ts +46 -0
- package/src/context/AutoRunConfigContext.tsx +13 -0
- package/src/context/ChatAdapterContext.tsx +8 -0
- package/src/context/ChatInputContext.tsx +40 -0
- package/src/context/RenderersContext.tsx +41 -0
- package/src/hooks/useChat.ts +855 -237
- package/src/hooks/useImageUpload.ts +253 -0
- package/src/index.ts +96 -39
- package/src/styles.css +48 -987
- package/src/types/index.ts +172 -103
- package/src/utils/fileIcon.ts +49 -0
- package/src/components/ChatInput.tsx +0 -368
- package/src/components/chat/messages/ExecutionSteps.tsx +0 -234
- package/src/components/chat/messages/MessageBubble.tsx +0 -130
- package/src/components/chat/ui/ChatHeader.tsx +0 -301
- package/src/components/chat/ui/WelcomeMessage.tsx +0 -107
package/src/types/index.ts
CHANGED
|
@@ -1,26 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* AI Chat 前端类型定义
|
|
3
|
-
*
|
|
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
|
|
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
|
-
|
|
37
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
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:
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
+
}
|