@gientech/modual 1.2.8 → 1.2.9-fix

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 (115) hide show
  1. package/README.md +593 -79
  2. package/USAGE.md +56 -0
  3. package/dist/README.md +593 -79
  4. package/dist/assets/GientechStreamReader-C21-q_Qv.js +449 -0
  5. package/dist/assets/chevron-down-DjLtKwcs.js +280 -0
  6. package/dist/assets/databse.svg +6 -0
  7. package/dist/assets/graph.svg +4 -0
  8. package/dist/assets/homeBg.png +0 -0
  9. package/dist/assets/index-BMz4lcjQ.js +1 -0
  10. package/dist/assets/index-C3Viu8Oj.js +1 -0
  11. package/dist/assets/index-C9GlPyHu.js +13 -0
  12. package/dist/assets/index-CRbX3ZA1.js +1 -0
  13. package/dist/assets/index-CTwzi_v2.js +21 -0
  14. package/dist/assets/index-DQlLDleQ.js +11 -0
  15. package/dist/assets/index-DRU1P9R0.js +1150 -0
  16. package/dist/assets/index-Dqej68NT.js +585 -0
  17. package/dist/assets/index-ECprhahs.js +157 -0
  18. package/dist/assets/index-i7qcZOwY.js +1088 -0
  19. package/dist/assets/knowledge.svg +4 -0
  20. package/dist/assets/left.jpg +0 -0
  21. package/dist/assets/logoImg.png +0 -0
  22. package/dist/assets/{plus-omCUN0e3.js → plus-CvJRSbOe.js} +1 -1
  23. package/dist/assets/sensitive.svg +5 -0
  24. package/dist/assets/style.css +1 -1
  25. package/dist/assets/style3.css +1 -1
  26. package/dist/assets/worker-BbpylX7l.js +13 -0
  27. package/dist/assets/{x-vPcWt3fC.js → x-DKPeLdlu.js} +1 -1
  28. package/dist/assistantConfig.d.ts +31 -0
  29. package/dist/assistantConfig.js +1 -0
  30. package/dist/chat.d.ts +25 -1
  31. package/dist/chat.js +563 -369
  32. package/dist/database.js +2 -2
  33. package/dist/databaseId.js +1 -11
  34. package/dist/databaseTable.js +2 -2
  35. package/dist/index.d.ts +85 -0
  36. package/dist/index.js +1 -0
  37. package/dist/modelManage.js +1 -1
  38. package/dist/package.json +13 -1
  39. package/dist/sensitive.js +1 -1
  40. package/dist/streamFilesReader.d.ts +3 -0
  41. package/dist/streamFilesReader.js +1 -442
  42. package/doc_assets//346/226/271/346/241/210//344/274/230/345/214/226/346/226/271/346/241/210-/345/244/232/344/274/232/350/257/235SSE/350/277/236/346/216/245/347/256/241/347/220/206.md +504 -0
  43. package/package.json +125 -99
  44. package/package.json.demo-backup +109 -0
  45. package/scripts/README.md +133 -133
  46. package/scripts/build-demo.js +88 -88
  47. package/scripts/demo-selector.js +216 -216
  48. package/scripts/preview-demo.js +130 -130
  49. package/scripts/run-demo.bat +34 -34
  50. package/src/assets/img/close.png +0 -0
  51. package/src/assets/img/database.png +0 -0
  52. package/src/assets/img/downLoad.png +0 -0
  53. package/src/assets/img/graphIcon.png +0 -0
  54. package/src/assets/img/pdf.png +0 -0
  55. package/src/assets/img/singleQa.png +0 -0
  56. package/src/assets/img/webSearch.png +0 -0
  57. package/src/examples/ConversationAssistantPage/index.tsx +37 -0
  58. package/src/examples/Demo/index.tsx +12 -0
  59. package/src/examples/chat/components/DrawerGraphPreview.tsx +78 -0
  60. package/src/examples/chat/index.tsx +112 -99
  61. package/src/examples/chat/logo03.png +0 -0
  62. package/src/examples/gientechStreamFilesReader/index.tsx +4 -69
  63. package/src/lib_enter.ts +11 -6
  64. package/src/modules/assistantConfig/assets/databse.svg +6 -0
  65. package/src/modules/assistantConfig/assets/empty.png +0 -0
  66. package/src/modules/assistantConfig/assets/graph.svg +4 -0
  67. package/src/modules/assistantConfig/assets/knowledge.svg +4 -0
  68. package/src/modules/assistantConfig/assets/sensitive.svg +5 -0
  69. package/src/modules/assistantConfig/components/Database.tsx +144 -0
  70. package/src/modules/assistantConfig/components/Graph.tsx +156 -0
  71. package/src/modules/assistantConfig/components/Knowledge.tsx +266 -0
  72. package/src/modules/assistantConfig/components/NotFoundContent.tsx +21 -0
  73. package/src/modules/assistantConfig/components/Paragraph.tsx +51 -0
  74. package/src/modules/assistantConfig/components/ParamsItem.tsx +39 -0
  75. package/src/modules/assistantConfig/components/ResourceBinderItem.tsx +132 -0
  76. package/src/modules/assistantConfig/components/SearchableSelector.tsx +500 -0
  77. package/src/modules/assistantConfig/components/Sensitive.tsx +179 -0
  78. package/src/modules/assistantConfig/components/SliderInput.tsx +65 -0
  79. package/src/modules/assistantConfig/constants.tsx +74 -0
  80. package/src/modules/assistantConfig/index.tsx +700 -0
  81. package/src/modules/assistantConfig/server.ts +262 -0
  82. package/src/modules/chat/Conversations/List.tsx +76 -9
  83. package/src/modules/chat/Conversations/index.tsx +37 -19
  84. package/src/modules/chat/ReferenceBar.tsx +592 -0
  85. package/src/modules/chat/constants.tsx +29 -6
  86. package/src/modules/chat/data.txt +82 -0
  87. package/src/modules/chat/index.tsx +357 -113
  88. package/src/modules/chat/referenceCom/DeleteModal.tsx +75 -0
  89. package/src/modules/chat/referenceCom/DrawerContent.tsx +136 -0
  90. package/src/modules/chat/referenceCom/DrawerDatabase.tsx +110 -0
  91. package/src/modules/chat/referenceCom/DrawerGraphPreview.tsx +86 -0
  92. package/src/modules/chat/referenceCom/DrawerPreview.tsx +73 -0
  93. package/src/modules/chat/referenceCom/DrawerTitle.tsx +26 -0
  94. package/src/modules/chat/referenceCom/RenameModal.tsx +86 -0
  95. package/src/modules/chat/referenceCom/TagCom.tsx +30 -0
  96. package/src/modules/chat/style.less +3 -0
  97. package/src/modules/chat/utils/index.ts +326 -0
  98. package/src/modules/database/CreateModal.tsx +1 -1
  99. package/src/modules/headlessChat/index.tsx +1 -3
  100. package/src/modules/nodegraph/index.tsx +1 -0
  101. package/src/modules/search/components/ResultContent.tsx +2 -2
  102. package/src/modules/streamFilesReader/GientechStreamReader.tsx +436 -367
  103. package/src/modules/streamFilesReader/index.tsx +1 -1
  104. package/src/utils/gientechCommon/components/AppLoading.tsx +10 -10
  105. package/src/utils/gientechCommon/components/Messages/GientechNewChatWelcome.tsx +312 -27
  106. package/src/utils/gientechCommon/hooks/AichatUseController.tsx +84 -6
  107. package/src/utils/testconfigs/index.ts +7 -1
  108. package/stats.html +1 -1
  109. package/vite.config.ts +69 -20
  110. package/dist/assets/_commonjsHelpers-gnU0ypJ3.js +0 -1
  111. package/dist/assets/circle-alert-g2Y6zAjt.js +0 -6
  112. package/dist/assets/index-97TKgPKE.js +0 -1284
  113. package/dist/assets/index-CEK88UzR.js +0 -26
  114. package/dist/assets/index-DIm7RgkM.js +0 -1709
  115. package/dist/assets/styled-components.browser.esm-DPkS13KC.js +0 -2
@@ -0,0 +1,500 @@
1
+ import React, { useMemo, useState, useEffect, useImperativeHandle, forwardRef } from 'react';
2
+ import styled from 'styled-components';
3
+ import {
4
+ defaultTheme,
5
+ deepMergeTheme,
6
+ useTheme,
7
+ type AppTheme,
8
+ type Styles,
9
+ } from '@mxmweb/zui-theme';
10
+ import { Search, CircleX } from 'lucide-react';
11
+
12
+ export interface Item {
13
+ key: string | number;
14
+ label: string;
15
+ disabled?: boolean;
16
+ tags?: string[];
17
+ [key: string]: any; // 允许其他自定义属性
18
+ }
19
+
20
+ export interface SearchableSelectorProps {
21
+ /** 可选数据源 */
22
+ dataSource: Item[];
23
+ /** 外部已选择的值(这些项在左侧不可选) */
24
+ value?: Item[];
25
+ /** 确认回调,点击确定时调用 */
26
+ onConfirm?: (selectedItems: Item[]) => void;
27
+ /** 最大选择数量,0表示不限制 */
28
+ maxCount?: number;
29
+ /** 搜索框占位符 */
30
+ searchPlaceholder?: string;
31
+ /** 标题 */
32
+ title?: string;
33
+
34
+ /** 是否显示搜索框 */
35
+ showSearch?: boolean;
36
+ /** 自定义搜索过滤函数 */
37
+ filterOption?: (inputValue: string, item: Item) => boolean;
38
+ /** 自定义渲染项 */
39
+ renderItem?: (item: Item) => React.ReactNode;
40
+ /** 自定义渲染已选项 */
41
+
42
+ renderSelectedItem?: (
43
+ item: Item,
44
+ updateItemData: (data: Record<string, any>) => void
45
+ ) => React.ReactNode;
46
+ className?: string;
47
+ style?: React.CSSProperties;
48
+ styles?: Styles;
49
+ /** 高度 */
50
+ height?: string;
51
+ /** 宽度 */
52
+ width?: string;
53
+ }
54
+
55
+ export interface SearchableSelectorRef {
56
+ getSelectedItems: () => Item[];
57
+ confirm: () => void;
58
+ }
59
+
60
+ const Container = styled.div<{ $theme: AppTheme; $width?: string; $height?: string }>`
61
+ display: flex;
62
+ background-color: #ffffff;
63
+ border: 1px solid ${p => p.$theme?.colors?.border?.default || '#e5e7eb'};
64
+ border-radius: ${p => p.$theme?.space?.radius || '8px'};
65
+ overflow: hidden;
66
+ width: ${p => p.$width || '600px'};
67
+ height: ${p => p.$height || '400px'};
68
+ position: relative;
69
+ `;
70
+
71
+ const Pane = styled.div<{ $theme: AppTheme }>`
72
+ display: flex;
73
+ flex-direction: column;
74
+ border-right: 1px solid ${p => p.$theme?.colors?.border?.default || '#e5e7eb'};
75
+ overflow: hidden;
76
+
77
+ &:last-child {
78
+ border-right: none;
79
+ }
80
+ `;
81
+
82
+ const PaneHeader = styled.div<{ $theme: AppTheme }>`
83
+ padding: 12px 16px;
84
+ font-weight: 400;
85
+ font-size: 14px;
86
+ display: flex;
87
+ align-items: center;
88
+ justify-content: space-between;
89
+ color: ${p => p.$theme?.colors?.text?.primary || '#4E5969'};
90
+ `;
91
+
92
+ const PaneHeaderClear = styled.div<{ $theme: AppTheme }>`
93
+ font-weight: 400;
94
+ font-size: 14px;
95
+ color: ${p => p.$theme?.colors?.text?.primary || '#4E6EF2'};
96
+ `;
97
+
98
+ const SearchWrapper = styled.div<{ $theme: AppTheme }>`
99
+ padding: 12px;
100
+ background-color: ${p => p.$theme?.colors?.background?.default || '#ffffff'};
101
+ `;
102
+
103
+ const SearchInput = styled.input<{ $theme: AppTheme }>`
104
+ width: 100%;
105
+ padding: 8px 12px 8px 36px;
106
+ border: 1px solid ${p => p.$theme?.colors?.border?.default || '#e5e7eb'};
107
+ border-radius: ${p => p.$theme?.space?.radius || '6px'};
108
+ font-size: 14px;
109
+ outline: none;
110
+ transition: all 0.2s;
111
+
112
+ &:focus {
113
+ border-color: ${p => p.$theme?.colors?.primary?.default || '#3b82f6'};
114
+ box-shadow: 0 0 0 3px ${p => p.$theme?.colors?.primary?.default || '#3b82f6'}20;
115
+ }
116
+
117
+ &::placeholder {
118
+ color: ${p => p.$theme?.colors?.text?.tertiary || '#9ca3af'};
119
+ }
120
+ `;
121
+
122
+ const SearchIcon = styled.div`
123
+ position: absolute;
124
+ left: 20px;
125
+ top: 50%;
126
+ transform: translateY(-50%);
127
+ color: #9ca3af;
128
+ pointer-events: none;
129
+ `;
130
+
131
+ const ListContainer = styled.div`
132
+ flex: 1;
133
+ overflow-y: auto;
134
+ overflow-x: hidden;
135
+
136
+ &::-webkit-scrollbar {
137
+ width: 6px;
138
+ }
139
+
140
+ &::-webkit-scrollbar-track {
141
+ background: #f1f1f1;
142
+ }
143
+
144
+ &::-webkit-scrollbar-thumb {
145
+ background: #c1c1c1;
146
+ border-radius: 3px;
147
+
148
+ &:hover {
149
+ background: #a8a8a8;
150
+ }
151
+ }
152
+ `;
153
+
154
+ const ItemWrapper = styled.div<{ $theme: AppTheme; $disabled?: boolean; $selected?: boolean }>`
155
+ padding: 10px 16px;
156
+ cursor: ${p => (p.$disabled ? 'not-allowed' : 'pointer')};
157
+ transition: all 0.2s;
158
+ display: flex;
159
+ align-items: center;
160
+ justify-content: space-between;
161
+ opacity: ${p => (p.$disabled ? 0.5 : 1)};
162
+
163
+ &:hover {
164
+ background-color: ${p =>
165
+ p.$disabled ? 'transparent' : p.$theme?.colors?.background?.hover || '#f3f4f6'};
166
+ }
167
+
168
+ &:last-child {
169
+ border-bottom: none;
170
+ }
171
+ `;
172
+
173
+ const ItemLabel = styled.span<{ $theme: AppTheme }>`
174
+ flex: 1;
175
+ font-size: 14px;
176
+ color: ${p => p.$theme?.colors?.text?.primary || '#111827'};
177
+ overflow: hidden;
178
+ text-overflow: ellipsis;
179
+ white-space: nowrap;
180
+ `;
181
+
182
+ const ActionButton = styled.button<{ $theme: AppTheme }>`
183
+ display: flex;
184
+ align-items: center;
185
+ justify-content: center;
186
+ width: 68px;
187
+ border: none;
188
+ background-color: #ebecf3;
189
+ border-radius: 4px;
190
+ cursor: pointer;
191
+ font-size: 12px;
192
+ color: ${p => p.$theme?.colors?.text?.secondary || '#1D2129'};
193
+ transition: all 0.2s;
194
+
195
+ &:disabled {
196
+ cursor: not-allowed;
197
+ color: ${p => p.$theme?.colors?.text?.secondary || '#C9CDD4'};
198
+ }
199
+ `;
200
+
201
+ const EmptyState = styled.div<{ $theme: AppTheme }>`
202
+ padding: 40px 20px;
203
+ text-align: center;
204
+ color: ${p => p.$theme?.colors?.text?.tertiary || '#9ca3af'};
205
+ font-size: 14px;
206
+ `;
207
+
208
+ const CountBadge = styled.span<{ $theme: AppTheme }>`
209
+ display: inline-flex;
210
+ align-items: center;
211
+ justify-content: center;
212
+ min-width: 20px;
213
+ height: 20px;
214
+ padding: 0 6px;
215
+ background-color: ${p => p.$theme?.colors?.primary?.default || '#3b82f6'};
216
+ color: white;
217
+ border-radius: 10px;
218
+ font-size: 12px;
219
+ font-weight: 500;
220
+ margin-left: 8px;
221
+ `;
222
+
223
+ const SearchableSelector = forwardRef<SearchableSelectorRef, SearchableSelectorProps>(
224
+ (
225
+ {
226
+ dataSource,
227
+ value = [],
228
+ onConfirm,
229
+ maxCount = 0,
230
+ searchPlaceholder = '搜索...',
231
+ title = '',
232
+ showSearch = true,
233
+ filterOption,
234
+ renderItem,
235
+ renderSelectedItem,
236
+ className,
237
+ style,
238
+ styles,
239
+ height,
240
+ width,
241
+ },
242
+ ref
243
+ ) => {
244
+ // 1. 获取全局主题
245
+ const globalTheme = useTheme();
246
+
247
+ // 2. 优先级合并:用户styles > useTheme > 默认主题
248
+ const theme = deepMergeTheme(
249
+ deepMergeTheme({ theme: defaultTheme, mode: 'light' }, globalTheme),
250
+ styles
251
+ );
252
+
253
+ // 内部状态管理 - 使用 Item[] 数组,初始值为外部传入的 value
254
+ const [internalSelectedItems, setInternalSelectedItems] = useState<Item[]>(value);
255
+ const [searchValue, setSearchValue] = useState<string>('');
256
+
257
+ // 当外部 value 变化时,同步到内部状态(但只在初始化时,后续由内部管理)
258
+ useEffect(() => {
259
+ setInternalSelectedItems(value);
260
+ }, [value]);
261
+
262
+ // 获取外部已选择项的 key 集合(这些项在左侧不可选)
263
+ const externalSelectedKeys = useMemo(() => {
264
+ return new Set(value.map(item => item.key));
265
+ }, [value]);
266
+
267
+ // 获取已选择项的 key 集合,用于快速查找
268
+ const selectedKeys = useMemo(() => {
269
+ return new Set(internalSelectedItems.map(item => item.key));
270
+ }, [internalSelectedItems]);
271
+
272
+ // 暴露方法给外部调用
273
+ useImperativeHandle(
274
+ ref,
275
+ () => ({
276
+ getSelectedItems: () => internalSelectedItems,
277
+ confirm: () => {
278
+ onConfirm?.(internalSelectedItems);
279
+ },
280
+ }),
281
+ [internalSelectedItems, onConfirm]
282
+ );
283
+
284
+ // 默认搜索过滤函数
285
+ const defaultFilterOption = (inputValue: string, item: Item): boolean => {
286
+ if (!inputValue) return true;
287
+ const searchLower = inputValue.toLowerCase();
288
+ return (
289
+ item.label.toLowerCase().includes(searchLower) ||
290
+ item.tags?.some(tag => tag.toLowerCase().includes(searchLower)) ||
291
+ false
292
+ );
293
+ };
294
+
295
+ const filterFn = filterOption || defaultFilterOption;
296
+
297
+ // 过滤后的可选列表
298
+ const filteredDataSource = useMemo(() => {
299
+ return dataSource.filter(item => filterFn(searchValue, item));
300
+ }, [dataSource, searchValue, filterFn]);
301
+
302
+ // 处理选择
303
+ const handleSelect = (item: Item) => {
304
+ if (item.disabled) return;
305
+ // 如果该项是外部传入的(已在 value 中),则不可选
306
+ if (externalSelectedKeys.has(item.key)) {
307
+ return;
308
+ }
309
+
310
+ const isSelected = selectedKeys.has(item.key);
311
+ let newSelectedItems: Item[];
312
+
313
+ if (isSelected) {
314
+ // 已选中,取消选择(只从内部选中项中移除)
315
+ newSelectedItems = internalSelectedItems.filter(
316
+ selectedItem => selectedItem.key !== item.key
317
+ );
318
+ } else {
319
+ // 未选中,检查是否达到最大限制
320
+ if (maxCount > 0 && internalSelectedItems.length >= maxCount) {
321
+ return; // 达到最大限制,不添加
322
+ }
323
+ newSelectedItems = [...internalSelectedItems, item];
324
+ }
325
+
326
+ setInternalSelectedItems(newSelectedItems);
327
+ };
328
+
329
+ // 更新已选项数据的函数
330
+ const updateItemData = (key: string | number, data: Record<string, any>) => {
331
+ setInternalSelectedItems((pre: any) => {
332
+ const newSelectedItems = pre.map(selectedItem => {
333
+ if (selectedItem.key == key) {
334
+ return {
335
+ ...selectedItem,
336
+ tags: data,
337
+ };
338
+ } else {
339
+ return selectedItem;
340
+ }
341
+ });
342
+ return newSelectedItems;
343
+ });
344
+ };
345
+ // 处理移除
346
+ const handleRemove = (item: Item) => {
347
+ // 只从内部选中项中移除
348
+ const newSelectedItems = internalSelectedItems.filter(
349
+ selectedItem => selectedItem.key !== item.key
350
+ );
351
+
352
+ setInternalSelectedItems(newSelectedItems);
353
+ };
354
+
355
+ // 判断是否已选中
356
+ const isSelected = (key: string | number) => {
357
+ return selectedKeys.has(key);
358
+ };
359
+
360
+ // 判断是否达到最大限制
361
+ const isMaxReached = maxCount > 0 && internalSelectedItems.length >= maxCount;
362
+
363
+ return (
364
+ <Container
365
+ $theme={theme.theme || defaultTheme}
366
+ $width={width}
367
+ $height={height}
368
+ className={className}
369
+ style={style}
370
+ >
371
+ {/* 左侧:可选列表 */}
372
+ <Pane $theme={theme.theme || defaultTheme} style={{ flex: 1 }}>
373
+ {showSearch && (
374
+ <SearchWrapper $theme={theme.theme || defaultTheme}>
375
+ <div style={{ position: 'relative' }}>
376
+ <SearchIcon>
377
+ <Search size={16} />
378
+ </SearchIcon>
379
+ <SearchInput
380
+ $theme={theme.theme || defaultTheme}
381
+ type="text"
382
+ placeholder={searchPlaceholder}
383
+ value={searchValue}
384
+ onChange={e => setSearchValue(e.target.value)}
385
+ />
386
+ </div>
387
+ </SearchWrapper>
388
+ )}
389
+
390
+ <ListContainer>
391
+ {filteredDataSource.length === 0 ? (
392
+ <EmptyState $theme={theme.theme || defaultTheme}>
393
+ {searchValue ? '未找到匹配项' : '暂无数据'}
394
+ </EmptyState>
395
+ ) : (
396
+ filteredDataSource.map(item => {
397
+ const selected = isSelected(item.key);
398
+ // 外部传入的项不可选,或者达到最大限制且未选中
399
+ const isExternalSelected = externalSelectedKeys.has(item.key);
400
+ const disabled = item.disabled || isExternalSelected || (isMaxReached && !selected);
401
+
402
+ return (
403
+ <ItemWrapper
404
+ key={item.key}
405
+ $theme={theme.theme || defaultTheme}
406
+ $disabled={disabled}
407
+ $selected={selected}
408
+ onClick={() => handleSelect(item)}
409
+ >
410
+ <ItemLabel $theme={theme.theme || defaultTheme}>
411
+ {renderItem ? renderItem(item) : item.label}
412
+ </ItemLabel>
413
+ {selected && (
414
+ <ActionButton
415
+ $theme={theme.theme || defaultTheme}
416
+ onClick={e => {
417
+ e.stopPropagation();
418
+ handleRemove(item);
419
+ }}
420
+ disabled
421
+ >
422
+ 已添加
423
+ </ActionButton>
424
+ )}
425
+ {!selected && (
426
+ <ActionButton
427
+ $theme={theme.theme || defaultTheme}
428
+ disabled={disabled}
429
+ onClick={e => {
430
+ e.stopPropagation();
431
+ handleSelect(item);
432
+ }}
433
+ >
434
+ 添加
435
+ </ActionButton>
436
+ )}
437
+ </ItemWrapper>
438
+ );
439
+ })
440
+ )}
441
+ </ListContainer>
442
+ </Pane>
443
+
444
+ {/* 右侧:已选列表 */}
445
+ <Pane $theme={theme.theme || defaultTheme} style={{ flex: 1 }}>
446
+ <PaneHeader $theme={theme.theme || defaultTheme}>
447
+ {maxCount > 0
448
+ ? `已选:${internalSelectedItems.length}/${maxCount}个${title}`
449
+ : `已选:${internalSelectedItems.length}个${title}`}
450
+
451
+ <PaneHeaderClear
452
+ $theme={theme.theme || defaultTheme}
453
+ onClick={e => {
454
+ e.stopPropagation();
455
+ setInternalSelectedItems([]);
456
+ }}
457
+ >
458
+ 清空
459
+ </PaneHeaderClear>
460
+ </PaneHeader>
461
+
462
+ <ListContainer>
463
+ {internalSelectedItems.length === 0 ? (
464
+ <EmptyState $theme={theme.theme || defaultTheme}>暂无已选项</EmptyState>
465
+ ) : (
466
+ internalSelectedItems.map(item => {
467
+ const updateData = (data: Record<string, any>) => updateItemData(item.key, data);
468
+
469
+ return (
470
+ <ItemWrapper key={item.key} $theme={theme.theme || defaultTheme}>
471
+ <ItemLabel $theme={theme.theme || defaultTheme}>
472
+ {renderSelectedItem ? renderSelectedItem(item, updateData) : item.label}
473
+ </ItemLabel>
474
+
475
+ <CircleX
476
+ size={14}
477
+ color={theme.theme?.colors?.text?.secondary || '#86909C'}
478
+ style={{
479
+ cursor: 'pointer',
480
+ // marginLeft: '8px',
481
+ }}
482
+ onClick={e => {
483
+ e.stopPropagation();
484
+ handleRemove(item);
485
+ }}
486
+ />
487
+ </ItemWrapper>
488
+ );
489
+ })
490
+ )}
491
+ </ListContainer>
492
+ </Pane>
493
+ </Container>
494
+ );
495
+ }
496
+ );
497
+
498
+ SearchableSelector.displayName = 'SearchableSelector';
499
+
500
+ export default SearchableSelector;
@@ -0,0 +1,179 @@
1
+ import { useState, useEffect, useRef } from 'react';
2
+ import ResourceBinderItem from './ResourceBinderItem';
3
+ import SearchableSelector, { type SearchableSelectorRef } from './SearchableSelector';
4
+ import { Button, Modal, Form, Checkbox, Input } from 'antd';
5
+ import { Plus } from 'lucide-react';
6
+ import Icon from '../assets/sensitive.svg';
7
+ import AxiosInstance from '../server';
8
+ const { Item } = Form;
9
+ const SensitiveSection = ({ url, token, role, eventsEmit, sectionData }: any) => {
10
+ const [options, setOptions]: any = useState([]);
11
+ const [data, setData]: any = useState([]);
12
+ const [isModalShow, setIsModalShow] = useState(false);
13
+ const [showDefaultAnswer, setShowDefaultAnswer] = useState(false);
14
+ const selectorRef = useRef<SearchableSelectorRef>(null);
15
+
16
+ const { fetchSensitiveLibrary } = AxiosInstance({
17
+ url,
18
+ token,
19
+ role,
20
+ eventsEmit,
21
+ });
22
+
23
+ useEffect(() => {
24
+ getList();
25
+ }, []);
26
+
27
+ useEffect(() => {
28
+ if (sectionData && options?.length > 0) {
29
+ let _sensitiveWordIds = sectionData
30
+ ? Array.isArray(sectionData)
31
+ ? sectionData
32
+ : sectionData.split(',')
33
+ : [];
34
+ let _data: any[] = [];
35
+ options.forEach((item: any) => {
36
+ if (_sensitiveWordIds.includes(item.key)) {
37
+ _data.push({
38
+ key: item.key,
39
+ label: item.label,
40
+ });
41
+ }
42
+ });
43
+
44
+ setData(_data);
45
+ }
46
+ }, [sectionData, options]);
47
+
48
+ const getList = async () => {
49
+ try {
50
+ const res = await fetchSensitiveLibrary({
51
+ pageNo: 1,
52
+ pageSize: 9999,
53
+ });
54
+ if (res?.records) {
55
+ const options = (res?.records || []).map((item: any) => {
56
+ return {
57
+ key: item.id + '',
58
+ label: item.name,
59
+ };
60
+ });
61
+ setOptions(options);
62
+ }
63
+ } catch (e) {
64
+ console.error(e);
65
+ }
66
+ };
67
+
68
+ const handleEventsEmit = (name: string, eventsData?: any) => {
69
+ switch (name) {
70
+ case 'removeItem':
71
+ const _data = data.filter((p: any) => p.key !== eventsData.key);
72
+ setData([..._data]);
73
+ handleDatatoSubmit(_data);
74
+ break;
75
+ }
76
+ };
77
+
78
+ const handleDatatoSubmit = (selectedItems: any) => {
79
+ const item = selectedItems
80
+ .map((s: any) => s?.key || '')
81
+ .filter(key => key)
82
+ .join(',');
83
+ eventsEmit && eventsEmit('sensitive_selected', item);
84
+ };
85
+
86
+ const handleOk = () => {
87
+ // 通过 ref 获取选中的项
88
+ const selectedItems = selectorRef.current?.getSelectedItems() || [];
89
+ setData(selectedItems);
90
+ setIsModalShow(false);
91
+ handleDatatoSubmit(selectedItems);
92
+ };
93
+
94
+ return (
95
+ <div style={{ borderBottom: '1px solid #E5E6EB', padding: '12px 0 ' }}>
96
+ <div className="flex items-center justify-between">
97
+ <div className="flex items-center">
98
+ <img src={Icon} />
99
+ <span
100
+ className="font-medium "
101
+ style={{
102
+ marginLeft: '10px',
103
+ }}
104
+ >
105
+ 敏感词
106
+ </span>
107
+ </div>
108
+ <Button
109
+ color="primary"
110
+ variant="outlined"
111
+ size="small"
112
+ icon={<Plus size={14} />}
113
+ onClick={() => setIsModalShow(true)}
114
+ >
115
+ 添加
116
+ </Button>
117
+ </div>
118
+ <Checkbox
119
+ value={showDefaultAnswer}
120
+ onChange={(e: any) => setShowDefaultAnswer(e.target.checked)}
121
+ style={{
122
+ marginBottom: 8,
123
+ marginTop: 8,
124
+ }}
125
+ >
126
+ 自定义回复
127
+ </Checkbox>
128
+ {showDefaultAnswer && (
129
+ <Item label="自定义回复内容" name="defaultAnswer" layout="vertical">
130
+ <Input.TextArea style={{ height: '55px', resize: 'none' }} />
131
+ </Item>
132
+ )}
133
+ {data?.length > 0 && (
134
+ <ResourceBinderItem
135
+ countText={data?.length > 0 ? `已选:${data?.length}个敏感词库` : undefined}
136
+ eventsEmit={handleEventsEmit}
137
+ data={data}
138
+ />
139
+ )}
140
+
141
+ <Modal
142
+ width={740}
143
+ title="选择敏感词"
144
+ okText="确定"
145
+ cancelText="取消"
146
+ open={isModalShow}
147
+ onOk={handleOk}
148
+ onCancel={() => setIsModalShow(false)}
149
+ destroyOnHidden
150
+ >
151
+ <SearchableSelector
152
+ ref={selectorRef}
153
+ dataSource={options}
154
+ value={data}
155
+ searchPlaceholder="搜索敏感词..."
156
+ width="700px"
157
+ height="500px"
158
+ title="敏感词库"
159
+ renderItem={item => {
160
+ return (
161
+ <div className="flex ">
162
+ <img src={Icon} alt="add" />
163
+ <span
164
+ style={{
165
+ marginLeft: 8,
166
+ }}
167
+ >
168
+ {item.label}
169
+ </span>
170
+ </div>
171
+ );
172
+ }}
173
+ />
174
+ </Modal>
175
+ </div>
176
+ );
177
+ };
178
+
179
+ export default SensitiveSection;