@alicloud/appflow-chat 0.0.4-alpha.1 → 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 +321 -861
- package/dist/appflow-chat.esm.js +18816 -19647
- package/dist/types/index.d.ts +104 -74
- package/package.json +2 -3
- package/src/App.tsx +182 -0
- package/src/components/ChatSender.tsx +677 -0
- package/src/components/MessageAttachments.tsx +216 -0
- package/src/components/MessageBubble.tsx +20 -27
- package/src/index.ts +2 -6
- package/src/main.tsx +9 -0
- package/src/services/ChatService.ts +1 -0
- package/src/components/A2UIRenderer/A2UIRenderer.tsx +0 -181
- package/src/components/A2UIRenderer/index.ts +0 -1
package/dist/types/index.d.ts
CHANGED
|
@@ -1,73 +1,5 @@
|
|
|
1
|
-
import { A2UIViewerProps } from '@a2ui/react';
|
|
2
|
-
import { ComponentRegistry } from '@a2ui/react';
|
|
3
1
|
import { default as default_2 } from 'react';
|
|
4
|
-
import { OnActionCallback } from '@a2ui/react';
|
|
5
2
|
import { Provider } from 'react';
|
|
6
|
-
import { ServerToClientMessage } from '@a2ui/react';
|
|
7
|
-
|
|
8
|
-
/** A2UI 协议消息(v0.8 格式,透传给 @a2ui/react 处理) */
|
|
9
|
-
export declare type A2UIMessage = ServerToClientMessage;
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* A2UIStaticViewer - A2UI 静态 JSON 渲染组件
|
|
13
|
-
*
|
|
14
|
-
* 用于直接从静态的组件定义和数据渲染 UI,无需流式消息。
|
|
15
|
-
* 适用于已有完整 A2UI 组件树的场景。
|
|
16
|
-
*
|
|
17
|
-
* @example
|
|
18
|
-
* ```tsx
|
|
19
|
-
* const components = [
|
|
20
|
-
* { id: 'root', component: { Card: { child: 'text' } } },
|
|
21
|
-
* { id: 'text', component: { Text: { text: { path: '/message' } } } },
|
|
22
|
-
* ];
|
|
23
|
-
*
|
|
24
|
-
* <A2UIStaticViewer
|
|
25
|
-
* root="root"
|
|
26
|
-
* components={components}
|
|
27
|
-
* data={{ message: 'Hello World!' }}
|
|
28
|
-
* onAction={(action) => console.log('Action:', action)}
|
|
29
|
-
* />
|
|
30
|
-
* ```
|
|
31
|
-
*/
|
|
32
|
-
export declare const A2UIStaticViewer: default_2.FC<A2UIStaticViewerProps>;
|
|
33
|
-
|
|
34
|
-
/** A2UIViewer 的 Props,用于静态 JSON 渲染场景 */
|
|
35
|
-
export declare type A2UIStaticViewerProps = A2UIViewerProps;
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* A2UISurface - A2UI 声明式 UI 渲染组件(流式消息场景)
|
|
39
|
-
*
|
|
40
|
-
* 接收 Agent 发送的 A2UI 协议消息数组,增量处理并渲染对应的 Surface。
|
|
41
|
-
* 内部封装了 A2UIProvider,可直接作为 BubbleContent 的 children 使用。
|
|
42
|
-
*
|
|
43
|
-
* @example
|
|
44
|
-
* ```tsx
|
|
45
|
-
* // 作为 BubbleContent 的 children 使用
|
|
46
|
-
* <BubbleContent content={content} status={status}>
|
|
47
|
-
* <A2UISurface
|
|
48
|
-
* messages={a2uiMessages}
|
|
49
|
-
* surfaceId="main"
|
|
50
|
-
* onAction={(msg) => console.log('用户操作:', msg)}
|
|
51
|
-
* />
|
|
52
|
-
* </BubbleContent>
|
|
53
|
-
* ```
|
|
54
|
-
*/
|
|
55
|
-
export declare const A2UISurface: default_2.FC<A2UISurfaceProps>;
|
|
56
|
-
|
|
57
|
-
export declare interface A2UISurfaceProps {
|
|
58
|
-
/** A2UI JSON 消息数组(来自 Agent 的响应) */
|
|
59
|
-
messages: A2UIMessage[];
|
|
60
|
-
/** 渲染的 Surface ID(默认 'main') */
|
|
61
|
-
surfaceId?: string;
|
|
62
|
-
/** 用户在 A2UI 组件上的交互回调 */
|
|
63
|
-
onAction?: OnActionCallback;
|
|
64
|
-
/** 自定义组件注册表(可选,默认使用内置组件) */
|
|
65
|
-
registry?: ComponentRegistry;
|
|
66
|
-
/** 自定义类名 */
|
|
67
|
-
className?: string;
|
|
68
|
-
/** 自定义样式 */
|
|
69
|
-
style?: default_2.CSSProperties;
|
|
70
|
-
}
|
|
71
3
|
|
|
72
4
|
/**
|
|
73
5
|
* AssociationPropertyMetadata 类型定义
|
|
@@ -119,6 +51,24 @@ export declare interface BubbleContentProps {
|
|
|
119
51
|
waitingMessage?: string;
|
|
120
52
|
}
|
|
121
53
|
|
|
54
|
+
/** 附件信息(上传完成后携带下载URL) */
|
|
55
|
+
export declare interface ChatAttachment {
|
|
56
|
+
/** 文件唯一标识 */
|
|
57
|
+
uid: string;
|
|
58
|
+
/** 文件名 */
|
|
59
|
+
name: string;
|
|
60
|
+
/** 上传状态 */
|
|
61
|
+
status: 'uploading' | 'done' | 'error';
|
|
62
|
+
/** 文件类型:image 或 file */
|
|
63
|
+
type: 'image' | 'file';
|
|
64
|
+
/** 上传后的下载URL */
|
|
65
|
+
url?: string;
|
|
66
|
+
/** 本地预览URL(图片) */
|
|
67
|
+
thumbUrl?: string;
|
|
68
|
+
/** 原始文件对象 */
|
|
69
|
+
originFile?: File;
|
|
70
|
+
}
|
|
71
|
+
|
|
122
72
|
export declare interface ChatConfig {
|
|
123
73
|
welcome: string;
|
|
124
74
|
questions: string[];
|
|
@@ -142,6 +92,82 @@ export declare interface ChatMessage {
|
|
|
142
92
|
webSearch?: boolean;
|
|
143
93
|
}
|
|
144
94
|
|
|
95
|
+
/**
|
|
96
|
+
* ChatSender - 聊天输入框组件
|
|
97
|
+
*
|
|
98
|
+
* 集成了文本输入、文件/图片上传、语音输入、联网搜索、模型选择等功能。
|
|
99
|
+
* 根据模型能力自动控制功能按钮的显隐。
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* ```tsx
|
|
103
|
+
* <ChatSender
|
|
104
|
+
* loading={isLoading}
|
|
105
|
+
* models={config.models}
|
|
106
|
+
* capabilities={chatService.getModelCapabilities(modelId)}
|
|
107
|
+
* onSubmit={({ text, images, files, modelId, webSearch }) => {
|
|
108
|
+
* chatService.chat({ text, images, files, modelId, webSearch });
|
|
109
|
+
* }}
|
|
110
|
+
* onCancel={() => chatService.cancel()}
|
|
111
|
+
* onClear={() => chatService.clear()}
|
|
112
|
+
* onUpload={(file) => chatService.upload(file)}
|
|
113
|
+
* />
|
|
114
|
+
* ```
|
|
115
|
+
*/
|
|
116
|
+
export declare const ChatSender: default_2.FC<ChatSenderProps>;
|
|
117
|
+
|
|
118
|
+
export declare interface ChatSenderProps {
|
|
119
|
+
/** 是否处于加载状态(AI正在回复) */
|
|
120
|
+
loading?: boolean;
|
|
121
|
+
/** 是否禁用 */
|
|
122
|
+
disabled?: boolean;
|
|
123
|
+
/** 输入框占位文本 */
|
|
124
|
+
placeholder?: string;
|
|
125
|
+
/** 提交方式:'enter' 回车发送 | 'shiftEnter' Shift+回车发送 */
|
|
126
|
+
submitType?: 'enter' | 'shiftEnter';
|
|
127
|
+
/** 可用模型列表,传入且长度>1时显示模型选择下拉框 */
|
|
128
|
+
models?: ModelInfo[];
|
|
129
|
+
/** 当前选中的模型ID */
|
|
130
|
+
modelId?: string;
|
|
131
|
+
/** 默认选中的模型ID(非受控) */
|
|
132
|
+
defaultModelId?: string;
|
|
133
|
+
/** 模型切换回调 */
|
|
134
|
+
onModelChange?: (modelId: string) => void;
|
|
135
|
+
/**
|
|
136
|
+
* 模型能力配置,控制功能按钮的显隐
|
|
137
|
+
* 不传时默认所有功能关闭
|
|
138
|
+
*/
|
|
139
|
+
capabilities?: ModelCapabilities;
|
|
140
|
+
/** 提交消息回调 */
|
|
141
|
+
onSubmit?: (data: ChatSenderSubmitData) => void;
|
|
142
|
+
/** 取消当前请求 */
|
|
143
|
+
onCancel?: () => void;
|
|
144
|
+
/** 文件上传方法,返回下载URL */
|
|
145
|
+
onUpload?: (file: File) => Promise<string>;
|
|
146
|
+
/** 自定义类名 */
|
|
147
|
+
className?: string;
|
|
148
|
+
/** 自定义样式 */
|
|
149
|
+
style?: default_2.CSSProperties;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** 提交时的消息数据 */
|
|
153
|
+
export declare interface ChatSenderSubmitData {
|
|
154
|
+
/** 文本内容 */
|
|
155
|
+
text: string;
|
|
156
|
+
/** 图片URL列表 */
|
|
157
|
+
images: string[];
|
|
158
|
+
/** 文件列表(包含文件名和URL) */
|
|
159
|
+
files: {
|
|
160
|
+
name: string;
|
|
161
|
+
url: string;
|
|
162
|
+
}[];
|
|
163
|
+
/** 语音文件URL(录音上传后的下载地址) */
|
|
164
|
+
audio?: string;
|
|
165
|
+
/** 选中的模型ID */
|
|
166
|
+
modelId?: string;
|
|
167
|
+
/** 是否启用联网搜索 */
|
|
168
|
+
webSearch: boolean;
|
|
169
|
+
}
|
|
170
|
+
|
|
145
171
|
export declare class ChatService {
|
|
146
172
|
private config;
|
|
147
173
|
private setupConfig;
|
|
@@ -590,6 +616,13 @@ export declare interface MessageBubbleProps {
|
|
|
590
616
|
status?: 'Running' | 'Success' | 'Error';
|
|
591
617
|
/** 参考资料列表 */
|
|
592
618
|
references?: DocReferenceItem[];
|
|
619
|
+
/** 图片URL列表(用户消息中上传的图片) */
|
|
620
|
+
images?: string[];
|
|
621
|
+
/** 文件列表(用户消息中上传的文件) */
|
|
622
|
+
files?: {
|
|
623
|
+
name: string;
|
|
624
|
+
url: string;
|
|
625
|
+
}[];
|
|
593
626
|
/** 自定义类名 */
|
|
594
627
|
className?: string;
|
|
595
628
|
/** 自定义样式 */
|
|
@@ -598,18 +631,14 @@ export declare interface MessageBubbleProps {
|
|
|
598
631
|
onReferenceClick?: (item: DocReferenceItem) => void;
|
|
599
632
|
/** 点击网页搜索结果回调(不传则使用默认实现) */
|
|
600
633
|
onWebSearchClick?: (items: DocReferenceItem[]) => void;
|
|
601
|
-
/** 事件类型(用于特殊消息如 humanVerify、historyCard
|
|
602
|
-
eventType?: 'humanVerify' | 'historyCard'
|
|
634
|
+
/** 事件类型(用于特殊消息如 humanVerify、historyCard) */
|
|
635
|
+
eventType?: 'humanVerify' | 'historyCard';
|
|
603
636
|
/** HumanVerify 相关数据 */
|
|
604
637
|
humanVerifyData?: HumanVerifyData;
|
|
605
638
|
/** HistoryCard 相关数据(历史对话中的审核卡片) */
|
|
606
639
|
historyCardData?: HistoryCardData;
|
|
607
640
|
/** HumanVerify 提交回调 */
|
|
608
641
|
onHumanVerifySubmit?: (data: HumanVerifySubmitData) => void;
|
|
609
|
-
/** A2UI 消息数组(Agent 生成的声明式 UI 描述) */
|
|
610
|
-
a2uiMessages?: A2UIMessage[];
|
|
611
|
-
/** A2UI 用户交互回调 */
|
|
612
|
-
onA2UIAction?: (action: any) => void;
|
|
613
642
|
}
|
|
614
643
|
|
|
615
644
|
export declare interface ModelCapabilities {
|
|
@@ -630,6 +659,7 @@ export declare interface ModelInfo {
|
|
|
630
659
|
config?: {
|
|
631
660
|
image?: boolean;
|
|
632
661
|
file?: boolean;
|
|
662
|
+
audio?: boolean;
|
|
633
663
|
webSearch?: boolean;
|
|
634
664
|
fileConfig?: string;
|
|
635
665
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alicloud/appflow-chat",
|
|
3
|
-
"version": "0.0.4-alpha.
|
|
3
|
+
"version": "0.0.4-alpha.3",
|
|
4
4
|
"description": "Appflow-Chat AI聊天机器人组件库,提供聊天服务和UI组件",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/appflow-chat.cjs.js",
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
"@fortawesome/react-fontawesome": "^0.2.0"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
|
+
"@ant-design/x": "^1.6.1",
|
|
43
44
|
"@eslint/js": "^9.13.0",
|
|
44
45
|
"@types/js-cookie": "^3.0.6",
|
|
45
46
|
"@types/lodash-es": "^4.17.12",
|
|
@@ -58,8 +59,6 @@
|
|
|
58
59
|
"vite-plugin-dts": "^4.3.0"
|
|
59
60
|
},
|
|
60
61
|
"peerDependencies": {
|
|
61
|
-
"@a2ui/react": "^0.8.0",
|
|
62
|
-
"@a2ui/web_core": "^0.8.0",
|
|
63
62
|
"antd": "^5.0.0",
|
|
64
63
|
"react": "^18.0.0",
|
|
65
64
|
"react-dom": "^18.0.0"
|
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;
|