bi-sdk-react 0.0.3 → 0.0.5

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 (51) hide show
  1. package/dist/es/css/bi-sdk.css +1 -1
  2. package/dist/es/js/bi-sdk.es.js +295 -67
  3. package/dist/types/components/PageDesigner.d.ts +9 -1
  4. package/dist/types/components/context/DesignerContext.d.ts +5 -2
  5. package/dist/types/components/context/EnvContext.d.ts +2 -1
  6. package/dist/types/components/icon/IconFont.d.ts +2 -1
  7. package/dist/types/components/layout/PageCanvas.d.ts +2 -0
  8. package/dist/types/components/panel/AiPanel.d.ts +4 -0
  9. package/dist/types/components/panel/ChatInput.d.ts +13 -6
  10. package/dist/types/components/panel/DatasetPanel.d.ts +11 -0
  11. package/dist/types/components/panel/PaneHeader.d.ts +1 -0
  12. package/dist/types/components/panel/PropertiesPanel.d.ts +3 -1
  13. package/dist/types/components/plugins/@antd/item-props/CheckboxProps.d.ts +3 -3
  14. package/dist/types/components/plugins/@antd/item-props/ColProps.d.ts +2 -2
  15. package/dist/types/components/plugins/@antd/item-props/EchartsProps.d.ts +2 -2
  16. package/dist/types/components/plugins/@antd/item-props/TextProps.d.ts +1 -0
  17. package/dist/types/components/plugins/@antd/items/TableRender.d.ts +1 -0
  18. package/dist/types/components/plugins/@antd/items/TextRender.d.ts +1 -0
  19. package/dist/types/components/typing.d.ts +97 -2
  20. package/dist/types/components/utils.d.ts +1 -0
  21. package/dist/umd/css/bi-sdk.css +1 -1
  22. package/dist/umd/js/bi-sdk.umd.min.js +299 -71
  23. package/package.json +1 -1
  24. package/src/components/PageDesigner.tsx +293 -35
  25. package/src/components/context/DesignerContext.tsx +15 -3
  26. package/src/components/context/EnvContext.tsx +4 -1
  27. package/src/components/icon/IconFont.tsx +15 -11
  28. package/src/components/layout/PageCanvas.tsx +4 -2
  29. package/src/components/layout/PageItem.tsx +1 -1
  30. package/src/components/panel/AiPanel.tsx +609 -43
  31. package/src/components/panel/ChatInput.tsx +322 -180
  32. package/src/components/panel/DatasetPanel.tsx +65 -0
  33. package/src/components/panel/PaneHeader.tsx +3 -2
  34. package/src/components/panel/PropertiesPanel.tsx +334 -127
  35. package/src/components/plugins/@antd/index.ts +12 -9
  36. package/src/components/plugins/@antd/item-props/CapsuleProps.tsx +1 -1
  37. package/src/components/plugins/@antd/item-props/CheckboxProps.tsx +90 -42
  38. package/src/components/plugins/@antd/item-props/ColProps.tsx +139 -25
  39. package/src/components/plugins/@antd/item-props/EchartsProps.tsx +52 -22
  40. package/src/components/plugins/@antd/item-props/HtmlProps.tsx +8 -9
  41. package/src/components/plugins/@antd/item-props/SelectProps.tsx +1 -1
  42. package/src/components/plugins/@antd/item-props/TextProps.tsx +14 -3
  43. package/src/components/plugins/@antd/items/EchartsRender.tsx +9 -1
  44. package/src/components/plugins/@antd/items/HtmlRender.tsx +13 -1
  45. package/src/components/plugins/@antd/items/ListRender.tsx +18 -1
  46. package/src/components/plugins/@antd/items/TableRender.tsx +16 -1
  47. package/src/components/plugins/@antd/items/TextRender.tsx +3 -1
  48. package/src/components/styles.css +20 -0
  49. package/src/components/typing.ts +111 -2
  50. package/src/components/utils.ts +40 -0
  51. package/src/example.tsx +314 -13
@@ -1,3 +1,17 @@
1
+ import {
2
+ AudioOutlined,
3
+ DeleteOutlined as DelIcon, DeleteOutlined, FileExcelOutlined, InboxOutlined, SendOutlined
4
+ } from "@ant-design/icons";
5
+ import {
6
+ Button,
7
+ Dropdown,
8
+ Input,
9
+ Modal,
10
+ Tooltip,
11
+ Typography,
12
+ Upload,
13
+ message
14
+ } from "antd";
1
15
  import React, {
2
16
  useEffect,
3
17
  useImperativeHandle,
@@ -6,9 +20,8 @@ import React, {
6
20
  useState,
7
21
  } from "react";
8
22
  import styled from "styled-components";
9
- import { Input, Button, Space, Tooltip, Dropdown, Switch, Upload } from "antd";
10
- import { PaperClipOutlined, SendOutlined } from "@ant-design/icons";
11
- import * as XLSX from "xlsx";
23
+ import { IconFont } from "../icon/IconFont";
24
+ import { FetchType } from "../typing";
12
25
 
13
26
  export type ChatInputProps = {
14
27
  value?: string;
@@ -17,22 +30,25 @@ export type ChatInputProps = {
17
30
  sending?: boolean;
18
31
  rows?: number;
19
32
  agentList?: { id: string | number; name: string }[];
20
- title?: string;
21
33
  agentId?: string | number | null;
22
34
  attachments?: any[];
23
35
  onInput?: (text: string) => void;
24
36
  onSubmit?: (data: {
25
- demand: string;
26
- title: string;
27
- agentId?: string | number;
28
- csvData: Record<string, string>;
37
+ message: string;
38
+ agents?: { id: string | number; name: string }[] | null;
39
+ files?: { id: string; name: string; extension: string }[] | null;
29
40
  }) => void;
30
- onUpdateTitle?: (title: string) => void;
31
41
  onUpdateAgentId?: (id: string | number | null) => void;
32
42
  onUpdateAttachments?: (list: any[]) => void;
43
+ onUploading?: FetchType["upload"];
44
+ style?: React.CSSProperties;
33
45
  };
34
46
 
35
- const Wrapper = styled.div`
47
+ const AgentListDropdownContent = styled.div`
48
+ max-height: 400px;
49
+ overflow: auto;
50
+ background: #fff;
51
+
36
52
  ul.agent-list {
37
53
  margin: 0;
38
54
  padding: 0;
@@ -57,6 +73,9 @@ const Wrapper = styled.div`
57
73
  text-overflow: ellipsis;
58
74
  white-space: nowrap;
59
75
  }
76
+ `;
77
+
78
+ const Wrapper = styled.div`
60
79
  .chat-input {
61
80
  display: flex;
62
81
  flex-direction: column;
@@ -93,6 +112,11 @@ const Wrapper = styled.div`
93
112
  background-color: #fafafa;
94
113
  font-size: 12px;
95
114
  position: relative;
115
+
116
+ .anticon {
117
+ font-size: 36px;
118
+ color: var(--ant-green-8);
119
+ }
96
120
  }
97
121
  .attachment-card .name {
98
122
  max-width: 80px;
@@ -108,9 +132,19 @@ const Wrapper = styled.div`
108
132
  position: absolute;
109
133
  top: 4px;
110
134
  right: 2px;
135
+
136
+ .anticon {
137
+ font-size: 12px;
138
+ color: var(--ant-color-text-tertiary);
139
+ }
111
140
  }
112
141
  .attachment-card .remove:hover {
113
142
  color: #666;
143
+
144
+ .anticon {
145
+ font-size: 12px;
146
+ color: var(--ant-color-text-secondary);
147
+ }
114
148
  }
115
149
  .agent-info {
116
150
  box-sizing: border-box;
@@ -120,11 +154,17 @@ const Wrapper = styled.div`
120
154
  border-bottom: none;
121
155
  background-color: #eff1f9;
122
156
  font-size: 14px;
123
- color: #555;
157
+ color: var(--ant-color-text-label);
124
158
  display: flex;
125
159
  align-items: center;
126
- justify-content: space-between;
160
+ flex-wrap: wrap;
127
161
  user-select: none;
162
+ gap: 8px;
163
+
164
+ a {
165
+ color: var(--ant-color-text-disabled);
166
+ margin-left: 4px;
167
+ }
128
168
  }
129
169
  .attachment-bar + .agent-info {
130
170
  border-radius: 0;
@@ -207,21 +247,6 @@ const Wrapper = styled.div`
207
247
  .agent-trigger:hover {
208
248
  background-color: #f7f7f7;
209
249
  }
210
- .title-toggle {
211
- display: none;
212
- align-items: center;
213
- gap: 6px;
214
- background-color: #ffffff;
215
- border: solid 1px #dcdcdc;
216
- border-radius: 5px;
217
- padding: 0 4px;
218
- height: 26px;
219
- }
220
- .title-toggle .label {
221
- color: #666;
222
- font-size: 12px;
223
- user-select: none;
224
- }
225
250
  .attach-upload {
226
251
  display: inline-flex;
227
252
  align-items: center;
@@ -235,6 +260,9 @@ const Wrapper = styled.div`
235
260
  font-size: 12px;
236
261
  cursor: pointer;
237
262
  }
263
+ .ant-btn .anticon {
264
+ color: var(--ant-color-text-label);
265
+ }
238
266
  .tips {
239
267
  color: #999;
240
268
  font-size: 12px;
@@ -249,7 +277,7 @@ const Wrapper = styled.div`
249
277
  width: 50px;
250
278
  height: 26px;
251
279
  color: #fff;
252
- font-size: 22px;
280
+ font-size: 14px;
253
281
  background-color: rgb(184, 184, 191);
254
282
  border: unset;
255
283
  border-radius: 14px;
@@ -257,13 +285,161 @@ const Wrapper = styled.div`
257
285
  cursor: pointer;
258
286
  }
259
287
  .send-btn.send-btn-active {
260
- background-color: var(--chat-blue);
288
+ background-color: var(--ant-color-primary);
261
289
  }
262
290
  `;
263
291
 
264
292
  const DEFAULT_AGENT_LIST: any[] = [];
265
293
  const DEFAULT_ATTACHMENTS: any[] = [];
266
294
 
295
+ const ALLOWED_MIME_TYPES = [
296
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
297
+ "audio/wav",
298
+ ];
299
+
300
+ const ALLOWED_EXTENSIONS = [".xlsx", ".wav"];
301
+
302
+ const FileUploadModal: React.FC<{
303
+ open: boolean;
304
+ onCancel: () => void;
305
+ onOk: (files: File[]) => void;
306
+ allowedExtensions: string[];
307
+ allowedMimeTypes: string[];
308
+ }> = ({ open, onCancel, onOk, allowedExtensions, allowedMimeTypes }) => {
309
+ const [fileList, setFileList] = useState<File[]>([]);
310
+
311
+ useEffect(() => {
312
+ if (open) {
313
+ setFileList([]);
314
+ }
315
+ }, [open]);
316
+
317
+ const handleOk = () => {
318
+ onOk(fileList);
319
+ onCancel();
320
+ };
321
+ const acceptFiles = [...ALLOWED_EXTENSIONS, ...ALLOWED_MIME_TYPES].join(",");
322
+
323
+ const props = {
324
+ name: "file",
325
+ multiple: true,
326
+ accept: acceptFiles,
327
+ fileList: [], // Managed manually
328
+ beforeUpload: (file: File) => {
329
+ const mimeOk = allowedMimeTypes.includes(file.type) || !file.type;
330
+ const ext = (file.name || "")
331
+ .substring(file.name.lastIndexOf("."))
332
+ .toLowerCase();
333
+ const extOk = allowedExtensions.includes(ext);
334
+
335
+ if (!mimeOk && !extOk) {
336
+ message.error(`不支持的文件类型: ${file.name}`);
337
+ return Upload.LIST_IGNORE;
338
+ }
339
+
340
+ setFileList((prev) => [...prev, file]);
341
+ return false;
342
+ },
343
+ };
344
+
345
+ const removeFile = (index: number) => {
346
+ setFileList((prev) => prev.filter((_, i) => i !== index));
347
+ };
348
+
349
+ return (
350
+ <Modal
351
+ title="上传文件"
352
+ open={open}
353
+ okText="上传"
354
+ cancelText="取消"
355
+ onOk={handleOk}
356
+ onCancel={onCancel}
357
+ width={600}
358
+ >
359
+ <div style={{ marginBottom: 16 }}>
360
+ <Typography.Text type="secondary">
361
+ 支持的文件类型:{allowedExtensions.join(", ")}
362
+ <br />
363
+ 单次支持上传多个文件,请确保文件内容合规。
364
+ <br />
365
+ Excel 暂不支持加密文件。
366
+ <br />
367
+ 不支持 xls 电子表格,请自行转换为 xlsx 格式。
368
+ <br />
369
+ Excel 仅支持第一行为表头、第二行开始为数据,不支持合并单元格。
370
+ </Typography.Text>
371
+ </div>
372
+ <Upload.Dragger {...props}>
373
+ <p className="ant-upload-drag-icon">
374
+ <InboxOutlined />
375
+ </p>
376
+ <p className="ant-upload-text">点击或将文件拖拽到此处上传</p>
377
+ <p className="ant-upload-hint">
378
+ 支持上传Excel表格数据或音频文件进行分析
379
+ </p>
380
+ </Upload.Dragger>
381
+ {fileList.length > 0 && (
382
+ <div style={{ marginTop: 16 }}>
383
+ <Typography.Title level={5} style={{ fontSize: 14, marginBottom: 8 }}>
384
+ 已选文件 ({fileList.length})
385
+ </Typography.Title>
386
+ <div
387
+ style={{
388
+ display: "flex",
389
+ flexDirection: "column",
390
+ gap: 8,
391
+ maxHeight: 200,
392
+ overflowY: "auto",
393
+ }}
394
+ >
395
+ {fileList.map((file, index) => (
396
+ <div
397
+ key={`${file.name}-${index}`}
398
+ style={{
399
+ display: "flex",
400
+ alignItems: "center",
401
+ justifyContent: "space-between",
402
+ padding: "8px 12px",
403
+ background: "#f5f5f5",
404
+ borderRadius: 4,
405
+ }}
406
+ >
407
+ <div
408
+ style={{
409
+ display: "flex",
410
+ alignItems: "center",
411
+ gap: 8,
412
+ overflow: "hidden",
413
+ }}
414
+ >
415
+ {file.name.endsWith(".xlsx") ? (
416
+ <FileExcelOutlined style={{ color: "#52c41a" }} />
417
+ ) : (
418
+ <AudioOutlined style={{ color: "#1890ff" }} />
419
+ )}
420
+ <Typography.Text ellipsis style={{ maxWidth: 300 }}>
421
+ {file.name}
422
+ </Typography.Text>
423
+ <Typography.Text type="secondary" style={{ fontSize: 12 }}>
424
+ ({(file.size / 1024).toFixed(1)} KB)
425
+ </Typography.Text>
426
+ </div>
427
+ <Button
428
+ type="text"
429
+ size="small"
430
+ icon={<DelIcon />}
431
+ onClick={() => removeFile(index)}
432
+ danger
433
+ />
434
+ </div>
435
+ ))}
436
+ </div>
437
+ </div>
438
+ )}
439
+ </Modal>
440
+ );
441
+ };
442
+
267
443
  export const ChatInput = React.forwardRef<
268
444
  { focus: () => void },
269
445
  ChatInputProps
@@ -276,35 +452,35 @@ export const ChatInput = React.forwardRef<
276
452
  sending = false,
277
453
  rows = 6,
278
454
  agentList = DEFAULT_AGENT_LIST,
279
- title = "",
280
455
  agentId = null,
281
456
  attachments = DEFAULT_ATTACHMENTS,
282
457
  onInput,
283
458
  onSubmit,
284
- onUpdateTitle,
285
459
  onUpdateAgentId,
286
460
  onUpdateAttachments,
461
+ onUploading,
462
+ style = {},
287
463
  },
288
464
  ref
289
465
  ) => {
290
466
  const inputRef = useRef<any>(null);
291
- const [localTitle, setLocalTitle] = useState(title || "");
292
- const [localAgentId, setLocalAgentId] = useState<string | number | null>(
293
- agentId || null
294
- );
295
- const [selectedAgent, setSelectedAgent] = useState<{
296
- id: string | number;
297
- name: string;
298
- } | null>(null);
467
+ const [selectedAgent, setSelectedAgent] = useState<
468
+ {
469
+ id: string | number;
470
+ name: string;
471
+ }[]
472
+ >([]);
299
473
  const [localValue, setLocalValue] = useState(value || "");
300
474
  const [lastSubmitAt, setLastSubmitAt] = useState(0);
301
475
  const minIntervalMs = 400;
302
- const [showTitle, setShowTitle] = useState(false);
303
- const acceptXlsx =
304
- ".xlsx,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
305
- const [localAttachments, setLocalAttachments] = useState<any[]>(
306
- Array.isArray(attachments) ? attachments : []
307
- );
476
+ const [localAttachments, setLocalAttachments] = useState<
477
+ {
478
+ id: string;
479
+ name: string;
480
+ size: number;
481
+ }[]
482
+ >(Array.isArray(attachments) ? attachments : []);
483
+ const [uploadModalOpen, setUploadModalOpen] = useState<boolean>(false);
308
484
  const isMobile = useMemo(() => false, []);
309
485
  useImperativeHandle(ref, () => ({
310
486
  focus() {
@@ -315,37 +491,22 @@ export const ChatInput = React.forwardRef<
315
491
  useEffect(() => {
316
492
  onInput && onInput(localValue);
317
493
  }, [localValue]);
318
- useEffect(() => setLocalTitle(title || ""), [title]);
319
- useEffect(() => {
320
- setLocalAgentId(agentId || null);
321
- setSelectedAgent(
322
- agentId ? agentList.find((s) => s.id === agentId) || null : null
323
- );
324
- }, [agentId, agentList]);
494
+
325
495
  useEffect(
326
496
  () => setLocalAttachments(Array.isArray(attachments) ? attachments : []),
327
497
  [attachments]
328
498
  );
499
+
329
500
  const canSubmit = !!(localValue && localValue.trim());
330
- const agent = useMemo(() => {
331
- if (!localAgentId) return null;
332
- return agentList.find((it) => it.id === localAgentId) || null;
333
- }, [localAgentId, agentList]);
334
- const onTitleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
335
- const v = e.target.value;
336
- setLocalTitle(v);
337
- onUpdateTitle && onUpdateTitle(v);
338
- };
501
+
339
502
  const onAgentSelect = (item: { id: string | number; name: string }) => {
340
503
  const id = item.id || null;
341
- setLocalAgentId(id);
342
504
  onUpdateAgentId && onUpdateAgentId(id);
343
- setSelectedAgent(item);
505
+ setSelectedAgent((prev) => [...prev, item]);
344
506
  };
345
- const clearAgent = () => {
346
- setLocalAgentId(null);
507
+ const onRemoveAgent = (index: number) => {
347
508
  onUpdateAgentId && onUpdateAgentId(null);
348
- setSelectedAgent(null);
509
+ setSelectedAgent((prev) => prev.filter((_, i) => i !== index));
349
510
  };
350
511
  const onKeydown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
351
512
  if (e.key === "Enter") {
@@ -356,52 +517,30 @@ export const ChatInput = React.forwardRef<
356
517
  submit();
357
518
  }
358
519
  };
359
- const convertXlsxToCsv = async (arrayBuffer: ArrayBuffer) => {
360
- const workbook = XLSX.read(arrayBuffer, {
361
- type: "array",
362
- cellDates: true,
363
- dateNF: "yyyy-mm-dd hh:mm:ss",
364
- });
365
- const result: Record<string, string> = {};
366
- workbook.SheetNames.forEach((name) => {
367
- const sheet = workbook.Sheets[name];
368
- const csv = XLSX.utils.sheet_to_csv(sheet);
369
- result[name] = csv;
370
- });
371
- return result;
372
- };
373
- const beforeUploadXlsx = (file: File) => {
374
- const mimeOk =
375
- file.type ===
376
- "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" ||
377
- !file.type;
378
- const extOk = (file.name || "").toLowerCase().endsWith(".xlsx");
379
- if (!mimeOk || !extOk) {
380
- return Upload.LIST_IGNORE;
381
- }
382
- const reader = new FileReader();
383
- reader.onload = async (e) => {
520
+ const handleUploadFiles = async (files: File[]) => {
521
+ setUploadModalOpen(false);
522
+ if (!onUploading || !files.length) return;
523
+ const newAttachments: typeof localAttachments = [];
524
+ for (const file of files) {
384
525
  try {
385
- const buffer = e.target?.result as ArrayBuffer;
386
- const converted = await convertXlsxToCsv(buffer);
387
- const next = [
388
- ...localAttachments,
389
- {
390
- uid: `${Date.now()}_${Math.random()}`,
526
+ const res = await onUploading(file);
527
+ if (res) {
528
+ newAttachments.push({
529
+ id: res.id,
391
530
  name: file.name,
392
531
  size: (file as any).size,
393
- csvBySheet: converted,
394
- },
395
- ];
396
- setLocalAttachments(next);
397
- onUpdateAttachments && onUpdateAttachments(next);
398
- } catch {}
399
- };
400
- reader.readAsArrayBuffer(file);
401
- return false;
532
+ });
533
+ }
534
+ } catch (err) {
535
+ console.error(err);
536
+ }
537
+ }
538
+ setLocalAttachments([...localAttachments, ...newAttachments]);
539
+ onUpdateAttachments &&
540
+ onUpdateAttachments([...localAttachments, ...newAttachments]);
402
541
  };
403
- const removeAttachment = (uid: string) => {
404
- const next = localAttachments.filter((a) => a.uid !== uid);
542
+ const removeAttachment = (id: string) => {
543
+ const next = localAttachments.filter((a) => a.id !== id);
405
544
  setLocalAttachments(next);
406
545
  onUpdateAttachments && onUpdateAttachments(next);
407
546
  };
@@ -413,19 +552,19 @@ export const ChatInput = React.forwardRef<
413
552
  const text = (localValue || "").trim();
414
553
  if (!text) return;
415
554
  const data = {
416
- demand: text,
417
- title: showTitle ? localTitle : "",
418
- agentId: localAgentId || undefined,
419
- csvData: localAttachments.reduce(
420
- (acc, a) => ({ ...acc, [a.name]: a.csvBySheet }),
421
- {}
422
- ),
555
+ message: text,
556
+ agents: selectedAgent.map((it) => ({ id: it.id, name: it.name })),
557
+ files: localAttachments.map((file) => ({
558
+ id: file.id,
559
+ name: file.name,
560
+ extension: file.name.substring(file.name.lastIndexOf(".")),
561
+ })),
423
562
  };
424
563
  onSubmit && onSubmit(data);
425
564
  setLocalValue("");
426
565
  };
427
566
  const overlay = (
428
- <div style={{ maxHeight: 400, overflow: "auto", background: "#fff" }}>
567
+ <AgentListDropdownContent>
429
568
  <ul className="agent-list">
430
569
  {agentList.map((item) => (
431
570
  <li key={String(item.id)}>
@@ -433,51 +572,50 @@ export const ChatInput = React.forwardRef<
433
572
  </li>
434
573
  ))}
435
574
  </ul>
436
- </div>
575
+ </AgentListDropdownContent>
437
576
  );
438
577
  return (
439
- <Wrapper>
578
+ <Wrapper style={style}>
440
579
  <div className="chat-input">
441
580
  <div className="input-stack">
442
581
  {localAttachments.length > 0 && (
443
582
  <div className="attachment-bar">
444
583
  {localAttachments.map((a) => (
445
- <Tooltip
446
- key={a.uid}
447
- title={a.name}
448
- className="attachment-card"
449
- >
450
- <svg style={{ width: 25, height: 25 }} viewBox="0 0 24 24">
451
- <rect x="3" y="3" width="18" height="18" fill="#1f6f43" />
452
- </svg>
453
- <span className="name">{a.name}</span>
584
+ <div key={a.id} className="attachment-card">
585
+ {a.name.endsWith(".xlsx") ? (
586
+ <IconFont type="icon-excel" />
587
+ ) : (
588
+ <IconFont type="icon-audio" />
589
+ )}
590
+
591
+ <Typography.Text
592
+ className="name"
593
+ ellipsis={{ tooltip: a.name }}
594
+ >
595
+ {a.name}
596
+ </Typography.Text>
454
597
  <a
455
598
  className="remove"
456
- onClick={() => removeAttachment(a.uid)}
599
+ onClick={() => removeAttachment(a.id)}
457
600
  >
458
- ×
601
+ <DeleteOutlined />
459
602
  </a>
460
- </Tooltip>
603
+ </div>
461
604
  ))}
462
605
  </div>
463
606
  )}
464
- {selectedAgent && (
607
+ {selectedAgent.length > 0 && (
465
608
  <div className="agent-info">
466
- <span className="name">@ {selectedAgent.name}</span>
467
- <a className="remove" onClick={clearAgent}>
468
- ×
469
- </a>
609
+ {selectedAgent.map((item, index) => (
610
+ <div key={String(item.id)}>
611
+ <span className="name">@ {item.name}</span>
612
+ <a className="remove" onClick={() => onRemoveAgent(index)}>
613
+ ×
614
+ </a>
615
+ </div>
616
+ ))}
470
617
  </div>
471
618
  )}
472
- {showTitle && (
473
- <Input
474
- className="title-input"
475
- placeholder="请输入报告标题(可选)"
476
- disabled={disabled || sending}
477
- value={localTitle}
478
- onChange={onTitleChange}
479
- />
480
- )}
481
619
  <Input.TextArea
482
620
  ref={inputRef}
483
621
  className="input textarea-stacked"
@@ -491,43 +629,47 @@ export const ChatInput = React.forwardRef<
491
629
  </div>
492
630
  <div className="footer">
493
631
  <div className="left">
494
- <Dropdown
495
- trigger={["click"]}
496
- popupRender={() => overlay}
497
- placement="topLeft"
498
- >
499
- <Tooltip title="选择助理">
500
- <Button
501
- className="agent-trigger"
502
- onClick={(e) => e.preventDefault()}
503
- >
504
- @
505
- </Button>
506
- </Tooltip>
507
- </Dropdown>
508
- <div className="title-toggle">
509
- <span className="label">标题</span>
510
- <Switch
511
- size="small"
512
- checked={showTitle}
513
- disabled={disabled || sending}
514
- onChange={(v) => setShowTitle(v)}
515
- />
516
- </div>
517
- <Upload
518
- multiple
519
- accept={acceptXlsx}
520
- beforeUpload={beforeUploadXlsx}
521
- showUploadList={false}
522
- disabled={disabled || sending}
523
- >
524
- <Tooltip title="上传 Excel 数据">
525
- <Button
526
- className="attach-upload"
527
- icon={<PaperClipOutlined />}
632
+ {!!agentList?.length && (
633
+ <Dropdown
634
+ trigger={["click"]}
635
+ popupRender={() => overlay}
636
+ placement="topLeft"
637
+ styles={{
638
+ root: {
639
+ boxShadow: "var(--ant-box-shadow-card)",
640
+ },
641
+ }}
642
+ >
643
+ <Tooltip title="选择助理">
644
+ <Button
645
+ className="agent-trigger"
646
+ onClick={(e) => e.preventDefault()}
647
+ >
648
+ <IconFont type="icon-at" />
649
+ </Button>
650
+ </Tooltip>
651
+ </Dropdown>
652
+ )}
653
+ {onUploading && (
654
+ <>
655
+ <Tooltip title="上传文件">
656
+ <Button
657
+ className="attach-upload"
658
+ onClick={() => setUploadModalOpen(true)}
659
+ disabled={disabled || sending}
660
+ >
661
+ <IconFont type="icon-paper-clip" />
662
+ </Button>
663
+ </Tooltip>
664
+ <FileUploadModal
665
+ open={uploadModalOpen}
666
+ onCancel={() => setUploadModalOpen(false)}
667
+ onOk={handleUploadFiles}
668
+ allowedExtensions={ALLOWED_EXTENSIONS}
669
+ allowedMimeTypes={ALLOWED_MIME_TYPES}
528
670
  />
529
- </Tooltip>
530
- </Upload>
671
+ </>
672
+ )}
531
673
  </div>
532
674
  <div style={{ display: "flex", alignItems: "center", gap: 6 }}>
533
675
  {!isMobile && (