@alicloud/appflow-chat 0.0.4-alpha.2 → 0.0.4-alpha.3
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/appflow-chat.cjs.js +241 -159
- package/dist/appflow-chat.esm.js +7869 -7602
- package/dist/types/index.d.ts +17 -6
- package/package.json +1 -1
- package/src/App.tsx +182 -0
- package/src/components/ChatSender.tsx +253 -92
- package/src/components/MessageAttachments.tsx +216 -0
- package/src/components/MessageBubble.tsx +18 -5
- package/src/main.tsx +9 -0
- package/src/services/ChatService.ts +1 -0
package/dist/types/index.d.ts
CHANGED
|
@@ -124,7 +124,7 @@ export declare interface ChatSenderProps {
|
|
|
124
124
|
placeholder?: string;
|
|
125
125
|
/** 提交方式:'enter' 回车发送 | 'shiftEnter' Shift+回车发送 */
|
|
126
126
|
submitType?: 'enter' | 'shiftEnter';
|
|
127
|
-
/**
|
|
127
|
+
/** 可用模型列表,传入且长度>1时显示模型选择下拉框 */
|
|
128
128
|
models?: ModelInfo[];
|
|
129
129
|
/** 当前选中的模型ID */
|
|
130
130
|
modelId?: string;
|
|
@@ -134,15 +134,13 @@ export declare interface ChatSenderProps {
|
|
|
134
134
|
onModelChange?: (modelId: string) => void;
|
|
135
135
|
/**
|
|
136
136
|
* 模型能力配置,控制功能按钮的显隐
|
|
137
|
-
*
|
|
137
|
+
* 不传时默认所有功能关闭
|
|
138
138
|
*/
|
|
139
139
|
capabilities?: ModelCapabilities;
|
|
140
140
|
/** 提交消息回调 */
|
|
141
141
|
onSubmit?: (data: ChatSenderSubmitData) => void;
|
|
142
142
|
/** 取消当前请求 */
|
|
143
143
|
onCancel?: () => void;
|
|
144
|
-
/** 清除会话回调 */
|
|
145
|
-
onClear?: () => void;
|
|
146
144
|
/** 文件上传方法,返回下载URL */
|
|
147
145
|
onUpload?: (file: File) => Promise<string>;
|
|
148
146
|
/** 自定义类名 */
|
|
@@ -157,8 +155,13 @@ export declare interface ChatSenderSubmitData {
|
|
|
157
155
|
text: string;
|
|
158
156
|
/** 图片URL列表 */
|
|
159
157
|
images: string[];
|
|
160
|
-
/**
|
|
161
|
-
files:
|
|
158
|
+
/** 文件列表(包含文件名和URL) */
|
|
159
|
+
files: {
|
|
160
|
+
name: string;
|
|
161
|
+
url: string;
|
|
162
|
+
}[];
|
|
163
|
+
/** 语音文件URL(录音上传后的下载地址) */
|
|
164
|
+
audio?: string;
|
|
162
165
|
/** 选中的模型ID */
|
|
163
166
|
modelId?: string;
|
|
164
167
|
/** 是否启用联网搜索 */
|
|
@@ -613,6 +616,13 @@ export declare interface MessageBubbleProps {
|
|
|
613
616
|
status?: 'Running' | 'Success' | 'Error';
|
|
614
617
|
/** 参考资料列表 */
|
|
615
618
|
references?: DocReferenceItem[];
|
|
619
|
+
/** 图片URL列表(用户消息中上传的图片) */
|
|
620
|
+
images?: string[];
|
|
621
|
+
/** 文件列表(用户消息中上传的文件) */
|
|
622
|
+
files?: {
|
|
623
|
+
name: string;
|
|
624
|
+
url: string;
|
|
625
|
+
}[];
|
|
616
626
|
/** 自定义类名 */
|
|
617
627
|
className?: string;
|
|
618
628
|
/** 自定义样式 */
|
|
@@ -649,6 +659,7 @@ export declare interface ModelInfo {
|
|
|
649
659
|
config?: {
|
|
650
660
|
image?: boolean;
|
|
651
661
|
file?: boolean;
|
|
662
|
+
audio?: boolean;
|
|
652
663
|
webSearch?: boolean;
|
|
653
664
|
fileConfig?: string;
|
|
654
665
|
};
|
package/package.json
CHANGED
package/src/App.tsx
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { ConfigProvider } from 'antd';
|
|
3
|
+
import { ChatSender } from './components/ChatSender';
|
|
4
|
+
import { MessageBubble } from './components/MessageBubble';
|
|
5
|
+
import type { ChatSenderSubmitData } from './components/ChatSender';
|
|
6
|
+
import type { ModelInfo, ModelCapabilities } from './services/ChatService';
|
|
7
|
+
|
|
8
|
+
// 模拟模型列表
|
|
9
|
+
const mockModels: ModelInfo[] = [
|
|
10
|
+
{ id: 'model-1', name: '通义千问-VL', config: { image: true, file: true, webSearch: true , audio: true } },
|
|
11
|
+
{ id: 'model-2', name: '通义千问-Max', config: { image: false, file: false, webSearch: true } },
|
|
12
|
+
{ id: 'model-3', name: 'DeepSeek-R1', config: { image: false, file: false, webSearch: false } },
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
// 模拟上传
|
|
16
|
+
const mockUpload = async (file: File): Promise<string> => {
|
|
17
|
+
return new Promise((resolve) => {
|
|
18
|
+
setTimeout(() => {
|
|
19
|
+
resolve(`https://example.com/files/${file.name}`);
|
|
20
|
+
}, 1500);
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
function App() {
|
|
25
|
+
const [loading, setLoading] = useState(false);
|
|
26
|
+
const [selectedModelId, setSelectedModelId] = useState(mockModels[0].id);
|
|
27
|
+
const [logs, setLogs] = useState<string[]>([]);
|
|
28
|
+
|
|
29
|
+
// 根据选中模型计算能力
|
|
30
|
+
const currentModel = mockModels.find(m => m.id === selectedModelId) || mockModels[0];
|
|
31
|
+
const capabilities: ModelCapabilities = {
|
|
32
|
+
image: currentModel.config?.image ?? false,
|
|
33
|
+
file: currentModel.config?.file ?? false,
|
|
34
|
+
audio: currentModel.config?.audio ?? false,
|
|
35
|
+
webSearch: currentModel.config?.webSearch ?? false,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const addLog = (message: string) => {
|
|
39
|
+
setLogs(prev => [`[${new Date().toLocaleTimeString()}] ${message}`, ...prev].slice(0, 50));
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// 消息列表
|
|
43
|
+
interface Message {
|
|
44
|
+
id: string;
|
|
45
|
+
role: 'user' | 'bot';
|
|
46
|
+
content: string;
|
|
47
|
+
status: 'Running' | 'Success' | 'Error';
|
|
48
|
+
images?: string[];
|
|
49
|
+
files?: { name: string; url: string }[];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const [messages, setMessages] = useState<Message[]>([]);
|
|
53
|
+
|
|
54
|
+
const handleSubmit = (data: ChatSenderSubmitData) => {
|
|
55
|
+
addLog(`发送消息: text="${data.text}", model=${data.modelId}, images=${data.images.length}, files=${data.files.length}, audio=${data.audio || 'none'}, webSearch=${data.webSearch}`);
|
|
56
|
+
|
|
57
|
+
// 构建用户消息(包含图片和文件)
|
|
58
|
+
const userMsg: Message = {
|
|
59
|
+
id: `msg-${Date.now()}`,
|
|
60
|
+
role: 'user',
|
|
61
|
+
content: data.text,
|
|
62
|
+
status: 'Success',
|
|
63
|
+
images: data.images.length > 0 ? data.images : undefined,
|
|
64
|
+
files: data.files.length > 0 ? data.files : undefined,
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// 构建 bot 占位消息
|
|
68
|
+
const botMsg: Message = {
|
|
69
|
+
id: `msg-${Date.now() + 1}`,
|
|
70
|
+
role: 'bot',
|
|
71
|
+
content: '',
|
|
72
|
+
status: 'Running',
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
setMessages(prev => [...prev, userMsg, botMsg]);
|
|
76
|
+
setLoading(true);
|
|
77
|
+
|
|
78
|
+
// 模拟 AI 回复
|
|
79
|
+
setTimeout(() => {
|
|
80
|
+
setMessages(prev => prev.map(m =>
|
|
81
|
+
m.id === botMsg.id
|
|
82
|
+
? { ...m, content: '收到你的消息!这是一条模拟的 AI 回复。', status: 'Success' as const }
|
|
83
|
+
: m
|
|
84
|
+
));
|
|
85
|
+
setLoading(false);
|
|
86
|
+
addLog('AI 回复完成');
|
|
87
|
+
}, 2000);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const handleCancel = () => {
|
|
91
|
+
setLoading(false);
|
|
92
|
+
addLog('取消请求');
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
// 切换能力的控制面板
|
|
96
|
+
const [showUpload, setShowUpload] = useState(true);
|
|
97
|
+
const [showAudio, setShowAudio] = useState(false);
|
|
98
|
+
|
|
99
|
+
const adjustedCapabilities: ModelCapabilities = {
|
|
100
|
+
...capabilities,
|
|
101
|
+
image: showUpload && capabilities.image,
|
|
102
|
+
file: showUpload && capabilities.file,
|
|
103
|
+
audio: showAudio,
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
return (
|
|
107
|
+
<ConfigProvider>
|
|
108
|
+
<div style={{ maxWidth: 800, margin: '40px auto', padding: '0 20px', fontFamily: '-apple-system, BlinkMacSystemFont, sans-serif' }}>
|
|
109
|
+
<h2 style={{ marginBottom: 24, color: '#333' }}>ChatSender 组件预览</h2>
|
|
110
|
+
|
|
111
|
+
{/* 控制面板 */}
|
|
112
|
+
<div style={{ marginBottom: 24, padding: 16, background: '#f5f5f5', borderRadius: 8 }}>
|
|
113
|
+
<h4 style={{ margin: '0 0 12px 0', color: '#666' }}>功能开关(模拟 capabilities 控制)</h4>
|
|
114
|
+
<div style={{ display: 'flex', gap: 16, flexWrap: 'wrap' }}>
|
|
115
|
+
<label style={{ display: 'flex', alignItems: 'center', gap: 4, cursor: 'pointer' }}>
|
|
116
|
+
<input type="checkbox" checked={showUpload} onChange={e => setShowUpload(e.target.checked)} />
|
|
117
|
+
文件/图片上传
|
|
118
|
+
</label>
|
|
119
|
+
<label style={{ display: 'flex', alignItems: 'center', gap: 4, cursor: 'pointer' }}>
|
|
120
|
+
<input type="checkbox" checked={showAudio} onChange={e => setShowAudio(e.target.checked)} />
|
|
121
|
+
语音输入
|
|
122
|
+
</label>
|
|
123
|
+
<span style={{ color: '#999', fontSize: 13 }}>
|
|
124
|
+
当前模型: <strong>{currentModel.name}</strong>
|
|
125
|
+
{capabilities.image && ' | 支持图片'}
|
|
126
|
+
{capabilities.file && ' | 支持文件'}
|
|
127
|
+
</span>
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
130
|
+
|
|
131
|
+
{/* 消息列表 */}
|
|
132
|
+
<div style={{ marginBottom: 24, padding: 16, background: '#fff', borderRadius: 8, minHeight: 200, maxHeight: 500, overflow: 'auto', border: '1px solid #f0f0f0' }}>
|
|
133
|
+
{messages.length === 0 && (
|
|
134
|
+
<div style={{ color: '#999', textAlign: 'center', padding: 40 }}>
|
|
135
|
+
发送消息后,消息气泡将在此展示(支持图片和文件)
|
|
136
|
+
</div>
|
|
137
|
+
)}
|
|
138
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
|
|
139
|
+
{messages.map(msg => (
|
|
140
|
+
<MessageBubble
|
|
141
|
+
key={msg.id}
|
|
142
|
+
content={msg.content}
|
|
143
|
+
role={msg.role}
|
|
144
|
+
status={msg.status}
|
|
145
|
+
images={msg.images}
|
|
146
|
+
files={msg.files}
|
|
147
|
+
/>
|
|
148
|
+
))}
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
|
|
152
|
+
{/* ChatSender 组件 */}
|
|
153
|
+
<div style={{ marginBottom: 24 }}>
|
|
154
|
+
<ChatSender
|
|
155
|
+
loading={loading}
|
|
156
|
+
models={mockModels}
|
|
157
|
+
modelId={selectedModelId}
|
|
158
|
+
onModelChange={setSelectedModelId}
|
|
159
|
+
capabilities={adjustedCapabilities}
|
|
160
|
+
placeholder="输入消息,按 Enter 发送..."
|
|
161
|
+
onSubmit={handleSubmit}
|
|
162
|
+
onCancel={handleCancel}
|
|
163
|
+
onUpload={mockUpload}
|
|
164
|
+
/>
|
|
165
|
+
</div>
|
|
166
|
+
|
|
167
|
+
{/* 日志区域 */}
|
|
168
|
+
<div style={{ padding: 16, background: '#1e1e1e', borderRadius: 8, maxHeight: 300, overflow: 'auto' }}>
|
|
169
|
+
<h4 style={{ margin: '0 0 8px 0', color: '#888', fontSize: 13 }}>事件日志</h4>
|
|
170
|
+
{logs.length === 0 && <div style={{ color: '#666', fontSize: 13 }}>暂无日志,尝试发送消息或上传文件...</div>}
|
|
171
|
+
{logs.map((log, index) => (
|
|
172
|
+
<div key={index} style={{ color: '#4ec9b0', fontSize: 13, lineHeight: 1.6, fontFamily: 'monospace' }}>
|
|
173
|
+
{log}
|
|
174
|
+
</div>
|
|
175
|
+
))}
|
|
176
|
+
</div>
|
|
177
|
+
</div>
|
|
178
|
+
</ConfigProvider>
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export default App;
|