@baishuyun/chat-sdk 0.0.15 → 0.0.17

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 (66) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/chat-sdk.js +16307 -15780
  3. package/dist/chat-sdk.js.map +1 -1
  4. package/dist/chat-sdk.umd.cjs +149 -149
  5. package/dist/chat-sdk.umd.cjs.map +1 -1
  6. package/dist/index.css +1 -1
  7. package/package.json +4 -4
  8. package/src/chat.tsx +15 -1
  9. package/src/components/biz-comp/FieldChecker.tsx +49 -7
  10. package/src/components/biz-comp/FieldCheckerListMsg.tsx +101 -22
  11. package/src/components/biz-comp/chat-client.tsx +7 -2
  12. package/src/components/biz-comp/error-msg.tsx +10 -0
  13. package/src/components/biz-comp/messages.tsx +10 -0
  14. package/src/components/biz-comp/multi-modal-input/clear-btn.tsx +3 -1
  15. package/src/components/biz-comp/multi-modal-input/index.tsx +58 -38
  16. package/src/components/biz-comp/multi-modal-input/prompt-input.tsx +13 -10
  17. package/src/components/biz-comp/preview-message-wrapper.tsx +4 -4
  18. package/src/components/biz-comp/preview-message.tsx +3 -1
  19. package/src/components/biz-comp/suggestions.tsx +5 -1
  20. package/src/components/bs-ui/attachments-previewer.tsx +4 -1
  21. package/src/components/bs-ui/base-button.tsx +7 -2
  22. package/src/components/bs-ui/bs-icons.tsx +29 -0
  23. package/src/components/bs-ui/card.tsx +4 -3
  24. package/src/components/bs-ui/chat-area-header.tsx +7 -3
  25. package/src/components/bs-ui/fields-design-info-table.tsx +160 -0
  26. package/src/components/bs-ui/fields-previewer.tsx +2 -0
  27. package/src/components/bs-ui/form-info-editor.tsx +2 -42
  28. package/src/components/bs-ui/generate-animation.tsx +7 -5
  29. package/src/components/bs-ui/img-part.tsx +1 -1
  30. package/src/components/bs-ui/line-checker.tsx +19 -5
  31. package/src/components/bs-ui/previewer-header.tsx +31 -3
  32. package/src/components/bs-ui/square-checker.tsx +30 -5
  33. package/src/components/bs-ui/tooltip.tsx +1 -1
  34. package/src/components/ui/dialog.tsx +1 -1
  35. package/src/components/ui/tooltip.tsx +1 -1
  36. package/src/const/ui.ts +42 -0
  37. package/src/hooks/use-frame-mode.ts +15 -0
  38. package/src/lib/parse-design-doc.ts +60 -0
  39. package/src/lib/utils.ts +19 -0
  40. package/src/plugins/form-builder-base-plugin/const.ts +3 -0
  41. package/src/plugins/form-builder-plugin/components/create-form-confirm.tsx +14 -1
  42. package/src/plugins/form-builder-plugin/components/design-info.tsx +47 -0
  43. package/src/plugins/form-builder-plugin/components/entry-btn.tsx +10 -2
  44. package/src/plugins/form-builder-plugin/components/follow-up.tsx +21 -6
  45. package/src/plugins/form-builder-plugin/components/msg-part.tsx +29 -9
  46. package/src/plugins/form-builder-plugin/components/opening-lines.tsx +11 -6
  47. package/src/plugins/form-builder-plugin/index.ts +73 -5
  48. package/src/plugins/form-builder-plugin/types.ts +3 -0
  49. package/src/plugins/form-builder-plugin/utils/index.ts +33 -6
  50. package/src/plugins/form-filling-plugin/components/batch-generator-action.tsx +44 -34
  51. package/src/plugins/form-filling-plugin/components/first-batch-generating-animation.tsx +21 -0
  52. package/src/plugins/form-filling-plugin/components/generated-data-counter.tsx +17 -0
  53. package/src/plugins/form-filling-plugin/components/non-first-batch-generating-animation.tsx +28 -0
  54. package/src/plugins/form-filling-plugin/index.ts +14 -0
  55. package/src/plugins/form-filling-plugin/types.ts +2 -0
  56. package/src/plugins/report-query-plugin/components/query-msg-part.tsx +16 -18
  57. package/src/plugins/report-query-plugin/components/result-cards/CreatedSourceMsg.tsx +36 -0
  58. package/src/plugins/report-query-plugin/components/result-cards/DataTableCard.tsx +15 -2
  59. package/src/plugins/report-query-plugin/const.ts +22 -0
  60. package/src/plugins/report-query-plugin/index.ts +30 -3
  61. package/src/plugins/report-query-plugin/types.ts +6 -0
  62. package/src/sdk.impl.tsx +4 -0
  63. package/src/store/index.ts +11 -0
  64. package/src/stories/FormInfoEditor.stories.tsx +19 -28
  65. package/src/stories/PreviewerHeader.stories.tsx +14 -0
  66. package/src/stories/fields-design-info-table.stories.tsx +203 -0
@@ -1,38 +1,117 @@
1
- import { memo, ReactNode } from 'react';
1
+ import {
2
+ forwardRef,
3
+ memo,
4
+ ReactNode,
5
+ useCallback,
6
+ useImperativeHandle,
7
+ useMemo,
8
+ useState,
9
+ } from 'react';
2
10
  import { CollapsibleTxtMsg } from '../bs-ui/collapsible-txt-msg';
3
11
  import { FieldChecker, FieldCheckerProps } from './FieldChecker';
4
12
  import { EditIcon, LetterAIcon } from '../bs-ui/bs-icons';
5
13
  import { FieldValueChecker, FieldValueCheckerProps } from './FieldValueChecker';
14
+ import { SquareChecker } from '../bs-ui/square-checker';
15
+ import { cn } from '@/lib/utils';
16
+
17
+ export interface FieldCheckerListMsgRef {
18
+ getCheckedFieldsLength: () => number;
19
+ }
6
20
 
7
21
  export const FieldCheckerListMsg = memo(
8
- ({
9
- fields,
10
- children,
11
- streaming,
12
- confirmed,
13
- }: {
14
- fields: Array<FieldCheckerProps>;
15
- children?: ReactNode;
16
- streaming: boolean;
17
- confirmed?: boolean;
18
- }) => {
22
+ forwardRef<
23
+ FieldCheckerListMsgRef,
24
+ {
25
+ fields: Array<FieldCheckerProps>;
26
+ children?: ReactNode;
27
+ streaming: boolean;
28
+ confirmed?: boolean;
29
+ }
30
+ >(({ fields, children, streaming, confirmed }, ref) => {
31
+ // Track individual field checked states (all default to true)
32
+ const [checkedMap, setCheckedMap] = useState<Record<string, boolean>>(() =>
33
+ Object.fromEntries(fields.map((f) => [f.field?.label ?? '', true]))
34
+ );
35
+
36
+ const allChecked = useMemo(
37
+ () => fields.length > 0 && fields.every((f) => checkedMap[f.field?.label ?? ''] !== false),
38
+ [checkedMap, fields]
39
+ );
40
+
41
+ const someChecked = useMemo(
42
+ () => fields.some((f) => checkedMap[f.field?.label ?? ''] !== false),
43
+ [checkedMap, fields]
44
+ );
45
+
46
+ const intermediate = streaming || (!allChecked && someChecked);
47
+
48
+ useImperativeHandle(
49
+ ref,
50
+ () => ({
51
+ getCheckedFieldsLength: () =>
52
+ fields.filter((f) => checkedMap[f.field?.label ?? ''] !== false).length,
53
+ }),
54
+ [checkedMap, fields]
55
+ );
56
+
57
+ const handleToggleAll = useCallback(
58
+ (checked: boolean | 'indeterminate') => {
59
+ const value = checked === true;
60
+ setCheckedMap(Object.fromEntries(fields.map((f) => [f.field?.label ?? '', value])));
61
+ fields.forEach((f) => f.onChange?.(value, f.field));
62
+ },
63
+ [fields]
64
+ );
65
+
19
66
  return (
20
67
  <CollapsibleTxtMsg title="字段生成" icon={<LetterAIcon />} defaultOpen>
68
+ {
69
+ <div
70
+ className={cn(
71
+ 'flex gap-[10px] px-4 py-[10px] rounded-[4px] w-full transition-colors cursor-pointer hover:bg-[#EFF0F6] items-center',
72
+ {
73
+ 'pointer-events-none': confirmed || streaming,
74
+ }
75
+ )}
76
+ onClick={() => {
77
+ if (confirmed) {
78
+ return;
79
+ }
80
+
81
+ const newChecked = !allChecked;
82
+ handleToggleAll(newChecked);
83
+ }}
84
+ >
85
+ <SquareChecker
86
+ readonly={confirmed || streaming}
87
+ checked={allChecked && !streaming}
88
+ onCheckedChange={handleToggleAll}
89
+ intermediate={intermediate}
90
+ >
91
+ <span className="!text-[#0265ff] !font-[700]">全选</span>
92
+ </SquareChecker>
93
+ </div>
94
+ }
21
95
  {fields.map((f) => (
22
- <FieldChecker {...f} streaming={streaming} confirmed={confirmed} />
96
+ <FieldChecker
97
+ {...f}
98
+ key={f.field?.label}
99
+ checked={checkedMap[f.field?.label ?? '']}
100
+ streaming={streaming}
101
+ confirmed={confirmed}
102
+ onChange={(checked, field, parentFieldName, index) => {
103
+ const isSubField = index !== undefined && parentFieldName !== undefined;
104
+ if (!isSubField) {
105
+ setCheckedMap((prev) => ({ ...prev, [f.field?.label ?? '']: checked }));
106
+ }
107
+ f.onChange?.(checked, field, parentFieldName, index);
108
+ }}
109
+ />
23
110
  ))}
24
111
  {children}
25
112
  </CollapsibleTxtMsg>
26
113
  );
27
- },
28
- (p1, p2) => {
29
- return (
30
- p1.fields.length === p2.fields.length &&
31
- p1.streaming === p2.streaming &&
32
- p1.confirmed === p2.confirmed &&
33
- p1.children === p2.children
34
- );
35
- }
114
+ })
36
115
  );
37
116
 
38
117
  export const FieldValueCheckerListMsg = memo(
@@ -109,12 +109,13 @@ const ChatContent: React.FC<{ client?: ChatSDK }> = ({ client }): ReactPortal |
109
109
  export const ChatEntryBtn = ({ client }: { client: ChatSDK }) => {
110
110
  const { bottom = 10, right = 10 } = client?.options?.btnOffset || {};
111
111
 
112
- const { entryVisible, entryWrapper, setStatus, isVisible, entryVariant } = useChatStore(
112
+ const { entryVisible, status, entryWrapper, setStatus, isVisible, entryVariant } = useChatStore(
113
113
  (store) => ({
114
114
  setStatus: store.setChatStatus,
115
115
  entryVariant: store.chatEntryVariant,
116
116
  entryWrapper: store.chatEntryElWrapper,
117
117
  isVisible: store.chatStatus !== 'hide',
118
+ status: store.chatStatus,
118
119
  entryVisible: store.chatEntryVisible,
119
120
  })
120
121
  );
@@ -138,6 +139,7 @@ export const ChatEntryBtn = ({ client }: { client: ChatSDK }) => {
138
139
 
139
140
  const EntryJsx = EntryCom ? (
140
141
  <EntryCom
142
+ chatStatus={status}
141
143
  client={client}
142
144
  variant={entryVariant}
143
145
  chatVisible={isVisible}
@@ -191,9 +193,10 @@ const MultiEntryButtonItem = ({
191
193
  client: ChatSDK;
192
194
  config: ChatEntryButtonConfig;
193
195
  }) => {
194
- const { setStatus, isVisible } = useChatStore((store) => ({
196
+ const { setStatus, isVisible, status } = useChatStore((store) => ({
195
197
  setStatus: store.setChatStatus,
196
198
  isVisible: store.chatStatus !== 'hide',
199
+ status: store.chatStatus,
197
200
  }));
198
201
 
199
202
  const EntryInfo = useDefaultPluginCustomComponent('EntryButton');
@@ -215,6 +218,7 @@ const MultiEntryButtonItem = ({
215
218
 
216
219
  const EntryJsx = EntryCom ? (
217
220
  <EntryCom
221
+ chatStatus={status}
218
222
  client={client}
219
223
  variant={config.variant}
220
224
  chatVisible={isVisible}
@@ -284,6 +288,7 @@ export const ChatClient = ({ store, client }: { client: ChatSDK; store: ChatStor
284
288
  return (
285
289
  <ChatStoreProvider store={store}>
286
290
  <TooltipProvider
291
+ delayDuration={100}
287
292
  zIndex={client?.options?.safeZIndex != null ? client.options.safeZIndex + 1 : 50}
288
293
  >
289
294
  <ChatWidgets client={client} />
@@ -1,6 +1,16 @@
1
+ import { useEffect } from 'react';
1
2
  import { FakeBotMessage } from './FakeBotMsg';
3
+ import { useFakeGlobalLoadingMessage } from '@/hooks/use-frame-mode';
2
4
 
3
5
  export const ErrorFallbackMsg = ({ error }: { error?: string }) => {
6
+ const loading = useFakeGlobalLoadingMessage();
7
+
8
+ useEffect(() => {
9
+ if (loading) {
10
+ loading.clearGlobalFakeLoadingMessage();
11
+ }
12
+ }, [loading]);
13
+
4
14
  return (
5
15
  <FakeBotMessage headless>
6
16
  {error || '出现了一些问题,无法显示预览内容,请稍后重试。'}
@@ -4,12 +4,16 @@ import { PreviewMessage } from './preview-message';
4
4
  import { FakeBotMessage } from './FakeBotMsg';
5
5
  import { OpeningLines } from './opening-lines';
6
6
  import { useRef } from 'react';
7
+ import { useFakeGlobalLoadingMessage } from '@/hooks/use-frame-mode';
8
+ import { GenerateAnimation } from '../bs-ui/generate-animation';
7
9
 
8
10
  export const Messages = (props: MessagesProps) => {
9
11
  const messagesContainerRef = useRef(null);
10
12
 
11
13
  const { messages, setMessages, addToolOutput, sendMessage, regenerate } = props;
12
14
 
15
+ const { globalFakeLoadingMessage } = useFakeGlobalLoadingMessage();
16
+
13
17
  return (
14
18
  <div
15
19
  className="relative overscroll-behavior-contain -webkit-overflow-scrolling-touch flex-1 touch-pan-y overflow-y-auto m-h-0 h-full"
@@ -38,6 +42,12 @@ export const Messages = (props: MessagesProps) => {
38
42
  />
39
43
  );
40
44
  })}
45
+
46
+ {globalFakeLoadingMessage && (
47
+ <FakeBotMessage key={'fake'}>
48
+ <GenerateAnimation>{globalFakeLoadingMessage}</GenerateAnimation>
49
+ </FakeBotMessage>
50
+ )}
41
51
  </ConversationContent>
42
52
  <ConversationScrollButton />
43
53
  </Conversation>
@@ -9,15 +9,17 @@ import { IconBtn } from '@/components/bs-ui/icon-btn';
9
9
  export const ClearBtn = ({
10
10
  setMessages,
11
11
  status,
12
+ chatId,
12
13
  }: {
13
14
  setMessages: UseChatHelpers<ChatMessage>['setMessages'];
14
15
  status: UseChatHelpers<ChatMessage>['status'];
16
+ chatId: string;
15
17
  }) => {
16
18
  const { exec: onBeforeClearMsg = () => {} } =
17
19
  usePluginLifeCycleChainRunner('onBeforeMessagesClear');
18
20
 
19
21
  const clearMsg = useCallback(() => {
20
- onBeforeClearMsg(null);
22
+ onBeforeClearMsg(chatId);
21
23
  setMessages(() => []);
22
24
  }, [onBeforeClearMsg, setMessages]);
23
25
 
@@ -28,6 +28,8 @@ import { IconBtn } from '@/components/bs-ui/icon-btn';
28
28
  import { DividerIcon, PlaneIcon, UploadIcon } from '@/components/bs-ui/bs-icons';
29
29
  import { useChatPreference } from '@/hooks/use-chat-preference';
30
30
  import { useToast } from '@/hooks/use-toast';
31
+ import { BsTooltip } from '@/components/bs-ui/tooltip';
32
+ import { useChatStatus } from '@/hooks/use-frame-mode';
31
33
 
32
34
  function PureMultimodalInput({
33
35
  chatId,
@@ -59,19 +61,28 @@ function PureMultimodalInput({
59
61
  const textareaRef = useRef<HTMLTextAreaElement>(null);
60
62
  const adjustHeight = useCallback(() => {
61
63
  if (textareaRef.current) {
62
- textareaRef.current.style.height = '44px';
64
+ textareaRef.current.style.height = '';
63
65
  }
64
66
  }, []);
65
67
 
68
+ const { status: chatUIStatus } = useChatStatus();
69
+
66
70
  useEffect(() => {
67
71
  if (textareaRef.current) {
68
72
  adjustHeight();
69
73
  }
70
74
  }, [adjustHeight]);
71
75
 
76
+ useEffect(() => {
77
+ if (chatUIStatus === 'hide') {
78
+ clearAttachmentsAndUploadQueue();
79
+ setInput('');
80
+ }
81
+ }, [chatUIStatus]);
82
+
72
83
  const resetHeight = useCallback(() => {
73
84
  if (textareaRef.current) {
74
- textareaRef.current.style.height = '44px';
85
+ textareaRef.current.style.height = '';
75
86
  }
76
87
  }, []);
77
88
 
@@ -92,38 +103,48 @@ function PureMultimodalInput({
92
103
  const [uploadQueue, setUploadQueue] = useState<string[]>([]);
93
104
 
94
105
  const { exec: onBeforeSendMsg } = usePluginLifeCycleChainRunner('onBeforeMessageSend');
106
+ const { exec: onAfterSendMsg } = usePluginLifeCycleChainRunner('onAfterMessageSend');
95
107
 
96
108
  const placeholder = useChatPreference()?.placeholder || 'Send a message...';
97
109
  const maxCount = useChatPreference()?.acceptAttachmentMaxCount || 5;
98
110
  const maxSizeMb = useChatPreference()?.acceptAttachmentMaxSizeMb || 100;
99
111
  const tooltip = `上传附件(仅识别文字),最多${maxCount}个,单个文件最大${maxSizeMb}MB`;
100
112
 
113
+ const clearAttachmentsAndUploadQueue = useCallback(() => {
114
+ setAttachments([]);
115
+ setUploadQueue([]);
116
+ if (fileInputRef.current) {
117
+ fileInputRef.current.value = '';
118
+ }
119
+ }, [setAttachments]);
120
+
101
121
  const submitForm = useCallback(() => {
102
122
  const finalMsg = onBeforeSendMsg({
103
123
  text: input,
104
124
  });
105
125
 
106
- sendMessage(
107
- {
108
- role: 'user',
109
- parts: [
110
- ...attachments.map((attachment) => ({
111
- type: 'file' as const,
112
- url: attachment.url,
113
- name: attachment.name,
114
- mediaType: attachment.contentType,
115
- providerMetadata: {
116
- uploadMetadata: attachment,
117
- },
118
- })),
119
- {
120
- type: 'text',
121
- text: input,
126
+ const msg = {
127
+ role: 'user',
128
+ parts: [
129
+ ...attachments.map((attachment) => ({
130
+ type: 'file' as const,
131
+ url: attachment.url,
132
+ name: attachment.name,
133
+ mediaType: attachment.contentType,
134
+ providerMetadata: {
135
+ uploadMetadata: attachment,
122
136
  },
123
- ],
124
- },
125
- finalMsg.options
126
- );
137
+ })),
138
+ {
139
+ type: 'text',
140
+ text: input,
141
+ },
142
+ ],
143
+ } as ChatMessage;
144
+
145
+ sendMessage(msg, finalMsg.options);
146
+
147
+ onAfterSendMsg?.(msg);
127
148
 
128
149
  setAttachments([]);
129
150
  resetHeight();
@@ -310,15 +331,12 @@ function PureMultimodalInput({
310
331
  )}
311
332
  <div className="flex flex-row items-start gap-1 sm:gap-2">
312
333
  <PromptInputTextarea
313
- className="grow resize-none border-0! border-none! bg-transparent p-0 text-base outline-none ring-0 [-ms-overflow-style:none] [scrollbar-width:none] placeholder:text-[#999] focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0 [&::-webkit-scrollbar]:hidden text-[#12111]"
334
+ className="grow resize-none border-0! border-none! bg-transparent p-0 text-base outline-none ring-0 placeholder:text-[#999] focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0 text-[#12111]"
314
335
  data-testid="multimodal-input"
315
- disableAutoResize={true}
316
- maxHeight={200}
317
- minHeight={44}
318
336
  onChange={handleInput}
319
337
  placeholder={placeholder}
320
338
  ref={textareaRef}
321
- rows={1}
339
+ rows={3}
322
340
  value={input}
323
341
  />
324
342
  </div>
@@ -328,7 +346,7 @@ function PureMultimodalInput({
328
346
  {hasAcceptableFileType ? (
329
347
  <AttachmentsButton fileInputRef={fileInputRef} status={status} tooltip={tooltip} />
330
348
  ) : null}
331
- <ClearBtn setMessages={setMessages} status={status} />
349
+ <ClearBtn setMessages={setMessages} status={status} chatId={chatId} />
332
350
  <DividerIcon />
333
351
  {status === 'submitted' || status === 'streaming' ? (
334
352
  <StopButton setMessages={setMessages} stop={stop} />
@@ -396,16 +414,18 @@ function PureStopButton({
396
414
  setMessages: UseChatHelpers<ChatMessage>['setMessages'];
397
415
  }) {
398
416
  return (
399
- <Button
400
- className="cursor-pointer rounded-full bg-[#0265FF] text-primary-foreground transition-colors duration-200 hover:bg-[#0265FF]/90 disabled:bg-[#E5E6EB] disabled:text-white h-[26px] w-[26px] flex items-center justify-center aspect-square p-0 disabled:!opacity-100"
401
- onClick={(event: any) => {
402
- event.preventDefault();
403
- stop();
404
- setMessages((messages) => messages);
405
- }}
406
- >
407
- <div className="rounded-[2px] w-[8px] h-[8px] bg-white flex-shrink-0" />
408
- </Button>
417
+ <BsTooltip content="停止生成">
418
+ <Button
419
+ className="cursor-pointer rounded-full bg-[#0265FF] text-primary-foreground transition-colors duration-200 hover:bg-[#0265FF]/90 disabled:bg-[#E5E6EB] disabled:text-white h-[26px] w-[26px] flex items-center justify-center aspect-square p-0 disabled:!opacity-100"
420
+ onClick={(event: any) => {
421
+ event.preventDefault();
422
+ stop();
423
+ setMessages((messages) => messages);
424
+ }}
425
+ >
426
+ <div className="rounded-[2px] w-[8px] h-[8px] bg-white flex-shrink-0" />
427
+ </Button>
428
+ </BsTooltip>
409
429
  );
410
430
  }
411
431
 
@@ -14,6 +14,7 @@ import {
14
14
  import { Textarea } from '@/components/ui/textarea';
15
15
  import { cn } from '@/lib/utils';
16
16
  import { StopIcon } from '@/components/bs-ui/bs-icons';
17
+ import { BsTooltip } from '@/components/bs-ui/tooltip';
17
18
 
18
19
  export type PromptInputProps = HTMLAttributes<HTMLFormElement>;
19
20
 
@@ -77,7 +78,7 @@ export const PromptInputTextarea = react.forwardRef<HTMLTextAreaElement, PromptI
77
78
  ? 'field-sizing-fixed'
78
79
  : resizeOnNewLinesOnly
79
80
  ? 'field-sizing-fixed'
80
- : 'field-sizing-content max-h-[6lh]',
81
+ : 'field-sizing-content min-h-[3lh] max-h-[8lh]',
81
82
  'bg-transparent dark:bg-transparent',
82
83
  'focus-visible:ring-0',
83
84
  className
@@ -171,15 +172,17 @@ export const PromptInputSubmit = ({
171
172
  let Icon = getIconForStatus(status);
172
173
 
173
174
  return (
174
- <Button
175
- className={cn('gap-1.5 rounded-full', className)}
176
- size={size}
177
- type="submit"
178
- variant={variant}
179
- {...props}
180
- >
181
- {children ?? Icon}
182
- </Button>
175
+ <BsTooltip content="发送">
176
+ <Button
177
+ className={cn('gap-1.5 rounded-full', className)}
178
+ size={size}
179
+ type="submit"
180
+ variant={variant}
181
+ {...props}
182
+ >
183
+ {children ?? Icon}
184
+ </Button>
185
+ </BsTooltip>
183
186
  );
184
187
  };
185
188
 
@@ -38,9 +38,9 @@ export const PreviewMessageWrapper = ({
38
38
  avatarComInfo?.Component;
39
39
  const avatarJsx = Avatar ? <Avatar /> : <BotAvatarAndName name={'AI表单搭建'} />;
40
40
 
41
- const [isHovered, setIsHovered] = useState(false);
41
+ // const [isHovered, setIsHovered] = useState(false);
42
42
 
43
- const showAction = isBot && isHovered && !isLoading;
43
+ const showAction = isBot && /* isHovered &&*/ !isLoading;
44
44
 
45
45
  const visibilityCls = showAction ? 'opacity-100' : 'opacity-0';
46
46
  const pointerEventsCls = showAction ? 'pointer-events-auto' : 'pointer-events-none';
@@ -77,8 +77,8 @@ export const PreviewMessageWrapper = ({
77
77
  className="group/message w-full"
78
78
  data-role={role}
79
79
  data-testid={`message-${role}`}
80
- onMouseEnter={() => setIsHovered(true)}
81
- onMouseLeave={() => setIsHovered(false)}
80
+ // onMouseEnter={() => setIsHovered(true)}
81
+ // onMouseLeave={() => setIsHovered(false)}
82
82
  >
83
83
  <div
84
84
  className={cn('flex w-full justify-start flex-col gap-2.5', {
@@ -20,7 +20,7 @@ import { WarningMessage } from '../bs-ui/warning-msg';
20
20
  import { GenerateAnimation } from '../bs-ui/generate-animation';
21
21
  import { AttachmentPartGroup } from '../bs-ui/attachment-part-group';
22
22
  import { AttachmentPart } from '../bs-ui/attachment-part';
23
- import { useChatStatus } from '@/hooks/use-frame-mode';
23
+ import { useChatStatus, useFakeGlobalLoadingMessage } from '@/hooks/use-frame-mode';
24
24
  import { useEvtBus } from '@/hooks/use-evt-bus';
25
25
 
26
26
  const PurePreviewMessage = ({
@@ -73,6 +73,7 @@ const PurePreviewMessage = ({
73
73
  const evtBus = useEvtBus();
74
74
 
75
75
  const { exec: onBeforeSendMsg } = usePluginLifeCycleChainRunner('onBeforeMessageSend');
76
+ const { exec: onAfterSendMsg } = usePluginLifeCycleChainRunner('onAfterMessageSend');
76
77
 
77
78
  const handleCopy = () => {
78
79
  const textParts = message.parts
@@ -97,6 +98,7 @@ const PurePreviewMessage = ({
97
98
  messageId: message.id,
98
99
  ...options,
99
100
  });
101
+ onAfterSendMsg(message);
100
102
  };
101
103
 
102
104
  if (chatStatus.isHide) {
@@ -31,12 +31,14 @@ export const SuggestionCard = ({
31
31
  const defaultSuggestions: ISuggestion[] = [
32
32
  {
33
33
  icon: <UserIcon />,
34
+ iconCode: '&#xe814;',
34
35
  title: '供应商管理,子表单为产品信息',
35
36
  description:
36
37
  '用于记录和管理企业供应商的基本信息、资质认证、合作状态等,便于采购部门进行供应商评估和选择。',
37
38
  },
38
39
  {
39
40
  icon: <BagIcon />,
41
+ iconCode: '&#xe806;',
40
42
  title: '搭建订单表',
41
43
  description:
42
44
  '子表单为商品列表(包含:商品ID、商品名称、商品供应商为关联数据字段、商品原价、商品销售价、商品型号为关联查询字段)',
@@ -48,6 +50,7 @@ const defaultSuggestions: ISuggestion[] = [
48
50
  // },
49
51
  {
50
52
  icon: <BuildingIcon />,
53
+ iconCode: '&#xe81d;',
51
54
  title: '创建合同表关联供应商信息管理表',
52
55
  description:
53
56
  '创建合同表用于记录与供应商签订的合同信息,并与供应商信息管理表进行关联,方便查询和管理合同相关的供应商信息。',
@@ -61,13 +64,14 @@ export const Suggestions = ({
61
64
  }: {
62
65
  items?: ISuggestion[];
63
66
  iconColor?: string;
64
- onSelect?: (title: string) => void;
67
+ onSelect?: (title: string, iconCode?: string) => void;
65
68
  }) => {
66
69
  return (
67
70
  <>
68
71
  {items.map((item, index) => (
69
72
  <BsCard
70
73
  icon={<div style={{ color: iconColor }}>{item.icon}</div>}
74
+ iconCode={item.iconCode}
71
75
  index={index + 1}
72
76
  key={index}
73
77
  title={item.title}
@@ -238,7 +238,10 @@ export const AttachmentsPreviewer = ({
238
238
  return (
239
239
  <div
240
240
  key={attachment.id}
241
- className={cn('relative shrink-0 cursor-pointer group', transCls)}
241
+ className={cn(
242
+ 'relative shrink-0 cursor-pointer group border border-[#e0e0e0] rounded-[10px]',
243
+ transCls
244
+ )}
242
245
  style={{ width: imageSize, height: imageSize }}
243
246
  onClick={() => onImageClick?.(attachment, index)}
244
247
  >
@@ -5,10 +5,11 @@ import { transCls } from '@/const/ui';
5
5
 
6
6
  export interface BaseButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
7
7
  icon?: ReactNode;
8
+ hoverTextCls?: string;
8
9
  }
9
10
 
10
11
  export const BaseButton = forwardRef<HTMLButtonElement, BaseButtonProps>(
11
- ({ className, children, icon, type, ...rest }, ref) => {
12
+ ({ className, children, hoverTextCls, icon, type, ...rest }, ref) => {
12
13
  return (
13
14
  <button
14
15
  ref={ref}
@@ -17,8 +18,12 @@ export const BaseButton = forwardRef<HTMLButtonElement, BaseButtonProps>(
17
18
  'group inline-flex items-center gap-[6px] px-[16px] rounded-[4px] h-[34px]',
18
19
  'border border-[#E0E0E0] bg-transparent',
19
20
  'text-[14px] font-[400] leading-[normal] text-[#030303]',
20
- 'hover:bg-[#EFF0F6] hover:text-[#0265FF] hover:border-transparent',
21
+ 'hover:bg-[#EFF0F6] hover:border-transparent',
21
22
  'cursor-pointer',
23
+ {
24
+ 'hover:text-[#0265FF]': !hoverTextCls,
25
+ [`hover:${hoverTextCls}`]: hoverTextCls,
26
+ },
22
27
  transCls,
23
28
  className
24
29
  )}
@@ -1065,3 +1065,32 @@ export const DashLayoutContainerIcon = () => (
1065
1065
  />
1066
1066
  </svg>
1067
1067
  );
1068
+
1069
+ export const DataViewIcon = () => (
1070
+ <svg
1071
+ xmlns="http://www.w3.org/2000/svg"
1072
+ width="20"
1073
+ height="20"
1074
+ viewBox="0 0 20 20"
1075
+ fill="currentColor"
1076
+ >
1077
+ <path d="M16.25 1.25C16.9404 1.25 17.5 1.80964 17.5 2.5V5C17.5 5.69036 16.9404 6.25 16.25 6.25H3.75C3.05964 6.25 2.5 5.69036 2.5 5V2.5C2.5 1.80964 3.05964 1.25 3.75 1.25H16.25ZM6.25 2.5C5.55964 2.5 5 3.05964 5 3.75C5 4.44036 5.55964 5 6.25 5C6.94036 5 7.5 4.44036 7.5 3.75C7.5 3.05964 6.94036 2.5 6.25 2.5Z" />
1078
+ <path d="M16.25 7.5C16.9404 7.5 17.5 8.05964 17.5 8.75V11.25C17.5 11.9404 16.9404 12.5 16.25 12.5H3.75C3.05964 12.5 2.5 11.9404 2.5 11.25V8.75C2.5 8.05964 3.05964 7.5 3.75 7.5H16.25ZM6.25 8.75C5.55964 8.75 5 9.30964 5 10C5 10.6904 5.55964 11.25 6.25 11.25C6.94036 11.25 7.5 10.6904 7.5 10C7.5 9.30964 6.94036 8.75 6.25 8.75Z" />
1079
+ <path d="M16.25 13.75C16.9404 13.75 17.5 14.3096 17.5 15V17.5C17.5 18.1904 16.9404 18.75 16.25 18.75H3.75C3.05964 18.75 2.5 18.1904 2.5 17.5V15C2.5 14.3096 3.05964 13.75 3.75 13.75H16.25ZM6.25 15C5.55964 15 5 15.5596 5 16.25C5 16.9404 5.55964 17.5 6.25 17.5C6.94036 17.5 7.5 16.9404 7.5 16.25C7.5 15.5596 6.94036 15 6.25 15Z" />
1080
+ </svg>
1081
+ );
1082
+
1083
+ export const DataReportIcon = () => (
1084
+ <svg
1085
+ xmlns="http://www.w3.org/2000/svg"
1086
+ width="20"
1087
+ height="20"
1088
+ viewBox="0 0 20 20"
1089
+ fill="currentColor"
1090
+ >
1091
+ <path
1092
+ d="M1.25 18.2377V15.9502H18.75V18.2377H1.25ZM16.4075 5.4427L16.4062 6.6677L16.4075 6.66895V14.7227L16.4062 14.7214L16.4075 14.7239H12.8912V5.4427H16.4075ZM11.7212 1.7627L11.72 3.1177H11.7212V14.7239H9.4875V14.7227H8.205V1.7627H11.7212ZM7.035 7.76644L7.03375 9.08395L7.035 9.0852V14.7239L3.52 14.7227V7.76644H7.035Z"
1093
+ // fill=""
1094
+ />
1095
+ </svg>
1096
+ );
@@ -7,10 +7,11 @@ export interface CardProps {
7
7
  desc?: string;
8
8
  index?: number;
9
9
  icon?: ReactNode;
10
- onClick?: (title: string) => void;
10
+ iconCode?: string;
11
+ onClick?: (title: string, iconCode?: string) => void;
11
12
  }
12
13
 
13
- export const Card = ({ title, desc, index, icon, onClick }: CardProps) => {
14
+ export const Card = ({ title, desc, index, icon, onClick, iconCode }: CardProps) => {
14
15
  return (
15
16
  <div
16
17
  className={cn(
@@ -20,7 +21,7 @@ export const Card = ({ title, desc, index, icon, onClick }: CardProps) => {
20
21
  }
21
22
  )}
22
23
  onClick={() => {
23
- onClick?.(title);
24
+ onClick?.(title, iconCode);
24
25
  }}
25
26
  >
26
27
  <div className="px-2.5 flex items-center gap-1.5 rounded-[4px] h-[34px] bg-white">