@baishuyun/chat-sdk 0.0.16 → 0.0.18

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 (95) hide show
  1. package/.turbo/turbo-build.log +178 -115
  2. package/CHANGELOG.md +16 -0
  3. package/dist/chat-sdk.js +37309 -44637
  4. package/dist/chat-sdk.js.map +1 -1
  5. package/dist/chat-sdk.umd.cjs +218 -295
  6. package/dist/chat-sdk.umd.cjs.map +1 -1
  7. package/dist/index.css +1 -1
  8. package/package.json +4 -4
  9. package/src/chat.tsx +19 -2
  10. package/src/components/biz-comp/FieldChecker.tsx +49 -7
  11. package/src/components/biz-comp/FieldCheckerListMsg.tsx +101 -22
  12. package/src/components/biz-comp/chat-client.tsx +8 -3
  13. package/src/components/biz-comp/error-msg.tsx +10 -0
  14. package/src/components/biz-comp/messages.tsx +10 -0
  15. package/src/components/biz-comp/multi-modal-input/clear-btn.tsx +3 -1
  16. package/src/components/biz-comp/multi-modal-input/index.tsx +80 -43
  17. package/src/components/biz-comp/multi-modal-input/prompt-input.tsx +13 -10
  18. package/src/components/biz-comp/preview-message-wrapper.tsx +4 -4
  19. package/src/components/biz-comp/preview-message.tsx +26 -2
  20. package/src/components/biz-comp/suggestions.tsx +5 -1
  21. package/src/components/bs-ui/attachment-part-group.tsx +5 -2
  22. package/src/components/bs-ui/attachment-part.tsx +14 -9
  23. package/src/components/bs-ui/attachments-previewer.tsx +6 -3
  24. package/src/components/bs-ui/base-button.tsx +20 -5
  25. package/src/components/bs-ui/border-animation.tsx +107 -0
  26. package/src/components/bs-ui/bs-icons.tsx +35 -0
  27. package/src/components/bs-ui/card.tsx +4 -3
  28. package/src/components/bs-ui/chat-area-header.tsx +7 -3
  29. package/src/components/bs-ui/confirm-dialog.tsx +17 -11
  30. package/src/components/bs-ui/fields-design-info-table.tsx +160 -0
  31. package/src/components/bs-ui/fields-previewer.tsx +18 -8
  32. package/src/components/bs-ui/form-info-editor.tsx +2 -42
  33. package/src/components/bs-ui/generate-animation.tsx +7 -5
  34. package/src/components/bs-ui/img-part.tsx +4 -2
  35. package/src/components/bs-ui/line-checker.tsx +19 -5
  36. package/src/components/bs-ui/previewer-header.tsx +48 -4
  37. package/src/components/bs-ui/primary-entry-btn.tsx +2 -1
  38. package/src/components/bs-ui/square-checker.tsx +30 -5
  39. package/src/components/bs-ui/tooltip.tsx +1 -1
  40. package/src/components/ui/dialog.tsx +1 -1
  41. package/src/components/ui/tooltip.tsx +1 -1
  42. package/src/const/ui.ts +42 -0
  43. package/src/hooks/use-frame-mode.ts +15 -0
  44. package/src/index.tsx +0 -1
  45. package/src/lib/parse-design-doc.ts +60 -0
  46. package/src/lib/utils.ts +79 -2
  47. package/src/plugins/form-builder-base-plugin/const.ts +3 -0
  48. package/src/plugins/form-builder-plugin/components/create-form-confirm.tsx +12 -1
  49. package/src/plugins/form-builder-plugin/components/design-doc-part.tsx +19 -0
  50. package/src/plugins/form-builder-plugin/components/design-info.tsx +47 -0
  51. package/src/plugins/form-builder-plugin/components/entry-btn.tsx +10 -2
  52. package/src/plugins/form-builder-plugin/components/fields-part.tsx +78 -0
  53. package/src/plugins/form-builder-plugin/components/follow-up.tsx +21 -6
  54. package/src/plugins/form-builder-plugin/components/msg-part.tsx +20 -145
  55. package/src/plugins/form-builder-plugin/components/opening-lines.tsx +11 -6
  56. package/src/plugins/form-builder-plugin/components/suggestion-part.tsx +62 -0
  57. package/src/plugins/form-builder-plugin/hooks/index.tsx +50 -0
  58. package/src/plugins/form-builder-plugin/index.ts +91 -14
  59. package/src/plugins/form-builder-plugin/types.ts +22 -2
  60. package/src/plugins/form-builder-plugin/utils/get-render-strategy.ts +27 -0
  61. package/src/plugins/form-builder-plugin/utils/index.ts +75 -41
  62. package/src/plugins/form-filling-plugin/components/FormFillingOpeningLines.tsx +4 -0
  63. package/src/plugins/form-filling-plugin/components/batch-fill-part.tsx +17 -0
  64. package/src/plugins/form-filling-plugin/components/batch-generator-action.tsx +50 -35
  65. package/src/plugins/form-filling-plugin/components/entry-btn.tsx +1 -1
  66. package/src/plugins/form-filling-plugin/components/first-batch-generating-animation.tsx +11 -0
  67. package/src/plugins/form-filling-plugin/components/generated-data-counter.tsx +17 -0
  68. package/src/plugins/form-filling-plugin/components/msg-part.tsx +39 -122
  69. package/src/plugins/form-filling-plugin/components/non-first-batch-generating-animation.tsx +18 -0
  70. package/src/plugins/form-filling-plugin/components/single-fill-part.tsx +42 -0
  71. package/src/plugins/form-filling-plugin/hooks/use-conversation-id-in-ctx.ts +13 -0
  72. package/src/plugins/form-filling-plugin/hooks/use-fields-data.ts +110 -0
  73. package/src/plugins/form-filling-plugin/index.ts +62 -42
  74. package/src/plugins/form-filling-plugin/types.ts +21 -1
  75. package/src/plugins/report-query-plugin/components/query-msg-part.tsx +25 -18
  76. package/src/plugins/report-query-plugin/components/result-cards/CreatedSourceMsg.tsx +36 -0
  77. package/src/plugins/report-query-plugin/components/result-cards/DataTableCard.tsx +15 -2
  78. package/src/plugins/report-query-plugin/const.ts +22 -0
  79. package/src/plugins/report-query-plugin/index.ts +41 -5
  80. package/src/plugins/report-query-plugin/types.ts +6 -0
  81. package/src/sdk.impl.tsx +4 -0
  82. package/src/store/index.ts +11 -1
  83. package/src/stories/BorderAnimation.stories.tsx +116 -0
  84. package/src/stories/FormInfoEditor.stories.tsx +19 -28
  85. package/src/stories/PreviewerHeader.stories.tsx +24 -0
  86. package/src/stories/fields-design-info-table.stories.tsx +203 -0
  87. package/src/style.css +25 -0
  88. package/src/plugins/form-builder-plugin/hooks/index.ts +0 -0
  89. package/src/plugins/general-model-form-builder-plugin/components/confirmer.tsx +0 -90
  90. package/src/plugins/general-model-form-builder-plugin/components/ghost-evt-dispatcher.tsx +0 -69
  91. package/src/plugins/general-model-form-builder-plugin/components/msg-part.tsx +0 -147
  92. package/src/plugins/general-model-form-builder-plugin/components/new-confirmer.tsx +0 -191
  93. package/src/plugins/general-model-form-builder-plugin/const.ts +0 -3
  94. package/src/plugins/general-model-form-builder-plugin/index.ts +0 -20
  95. package/src/plugins/general-model-form-builder-plugin/types.ts +0 -1
@@ -1,5 +1,3 @@
1
- import { BorderColorAnimation } from '@/components/bs-ui/border-color-animation';
2
- import { GenerateAnimation } from '@/components/bs-ui/generate-animation';
3
1
  import { PrimaryConfirmBtn } from '@/components/bs-ui/primary-confirm-btn';
4
2
  import { TextUIPart } from 'ai';
5
3
  import { memo, useCallback, useEffect, useRef, useState } from 'react';
@@ -11,7 +9,11 @@ import { DATA_COUNT_PER_BATCH, FILLING_EVENTS, PLUGIN_NAME } from '../const';
11
9
  import { useEvtBus } from '@/hooks/use-evt-bus';
12
10
  import { ChatMessage, UseChatHelpers } from '@baishuyun/types';
13
11
  import { type Field } from '@/plugins/form-builder-base-plugin/types';
14
- import { CircleChecker } from '@/components/bs-ui/circle-checker';
12
+ import { GeneratedDataCounter } from './generated-data-counter';
13
+ import { FirstBatchGeneratingAnimation } from './first-batch-generating-animation';
14
+ import { NonFirstBatchGeneratingAnimation } from './non-first-batch-generating-animation';
15
+ import { usePlugin } from '@/hooks/use-plugin';
16
+ import { useFakeGlobalLoadingMessage } from '@/hooks/use-frame-mode';
15
17
 
16
18
  type FilledField = {
17
19
  fieldName: string;
@@ -23,7 +25,6 @@ type FilledField = {
23
25
 
24
26
  const useFillingEvtEmitter = (id: string) => {
25
27
  const evtBus = useEvtBus();
26
-
27
28
  return useCallback((f: FilledField) => {
28
29
  const normalizedValue = valueNormalizer(f.value as string | string[]);
29
30
 
@@ -93,30 +94,52 @@ const BaseBatchGeneratorAction = ({
93
94
  id,
94
95
  fields,
95
96
  }: BatchGeneratorActionProps) => {
97
+ const pluginInst = usePlugin<FormFillingPlugin>(PLUGIN_NAME);
98
+
96
99
  const [streamingState, setStreamingState] = useState(status === 'streaming');
97
100
 
101
+ const [totalCount, setTotalCount] = useState(pluginInst?.getCount(id) || 0);
102
+
103
+ const [next, setNext] = useState(totalCount > 0);
104
+
98
105
  const evtBus = useEvtBus();
99
106
 
107
+
108
+ const getRowNum = useCallback(() => {
109
+ if (!fields || fields.length === 0) {
110
+ return 0;
111
+ }
112
+
113
+ const lastField = fields[fields.length - 1];
114
+ return lastField.row || 0;
115
+ }, [fields]);
116
+
100
117
  useEffect(() => {
101
118
  if (status === 'ready' || status === 'error') {
102
119
  setStreamingState(false);
103
120
  }
104
121
  }, [status]);
105
122
 
123
+ useEffect(() => {
124
+ if (!pluginInst) {
125
+ return;
126
+ }
127
+
128
+ if (streamingState) {
129
+ return;
130
+ }
131
+
132
+ const newRows = getRowNum();
133
+ pluginInst.setCount(id, newRows);
134
+
135
+ setTotalCount(pluginInst.getCount(id) || 0);
136
+ }, [streamingState, getRowNum, pluginInst, id]);
137
+
106
138
  const formFillingCtx: FormFillingPluginCtx | null = usePluginCtx<
107
139
  FormFillingPluginCtx,
108
140
  FormFillingPlugin
109
141
  >(PLUGIN_NAME);
110
142
 
111
- const getRowNum = () => {
112
- if (!fields || fields.length === 0) {
113
- return 0;
114
- }
115
-
116
- const lastField = fields[fields.length - 1];
117
- return lastField.row || 0;
118
- };
119
-
120
143
  const handleCheckChange = useCallback(
121
144
  (checked: boolean) => {
122
145
  evtBus.emit('form-filling-fields-checked-update', {
@@ -132,20 +155,19 @@ const BaseBatchGeneratorAction = ({
132
155
  if (!streamingState) {
133
156
  return (
134
157
  <div className="rounded-[10px] border border-[var(--e0e0e0,#E0E0E0)] w-full flex items-center flex-col gap-[10px] p-[4px] pb-[20px]">
135
- <div className="flex items-center w-full py-[10px] px-[16px] gap-[6px]">
136
- <CircleChecker defaultChecked onCheckedChange={handleCheckChange}>
137
- 已生成{getRowNum()}条数据
138
- </CircleChecker>
139
- </div>
158
+ <GeneratedDataCounter count={totalCount} handleCheckChange={handleCheckChange} />
140
159
 
141
160
  <PrimaryConfirmBtn
142
161
  text={`继续生成${DATA_COUNT_PER_BATCH}条`}
143
162
  className="w-[300px]"
163
+ light
144
164
  onClick={() => {
145
165
  if (status === 'streaming' || streamingState) {
146
166
  return;
147
167
  }
148
168
 
169
+ setNext(true);
170
+
149
171
  setStreamingState(true);
150
172
 
151
173
  const formStructure = Array.from(formFillingCtx?.fieldMap.values() || []);
@@ -169,22 +191,12 @@ const BaseBatchGeneratorAction = ({
169
191
  );
170
192
  }
171
193
 
172
- return (
173
- <BorderColorAnimation
174
- width="100%"
175
- height={40}
176
- padding={10}
177
- borderRadius={4}
178
- bgInset={1000}
179
- borderWidth={1.5}
180
- style={{
181
- display: 'flex',
182
- alignItems: 'center',
183
- }}
184
- >
185
- <GenerateAnimation>数据生成中,可点击下方按钮暂停</GenerateAnimation>
186
- </BorderColorAnimation>
187
- );
194
+ const isFirstBatch = totalCount <= DATA_COUNT_PER_BATCH && !next;
195
+ if (isFirstBatch) {
196
+ return <FirstBatchGeneratingAnimation />;
197
+ }
198
+
199
+ return <NonFirstBatchGeneratingAnimation />;
188
200
  };
189
201
 
190
202
  const PartParser = ({ parts, ...rest }: BatchGeneratorActionProps) => {
@@ -194,9 +206,12 @@ const PartParser = ({ parts, ...rest }: BatchGeneratorActionProps) => {
194
206
 
195
207
  const emit = useFillingEvtEmitter(rest.id);
196
208
 
209
+ const loadingMsg = useFakeGlobalLoadingMessage();
210
+
197
211
  usePartParser(lastPart, (all, latestField) => {
198
212
  setFields(all);
199
213
  emit(latestField);
214
+ loadingMsg.clearGlobalFakeLoadingMessage();
200
215
  });
201
216
 
202
217
  if (!lastPart) {
@@ -218,7 +233,7 @@ export const BatchGeneratorAction = memo(PartParser, (prevProps, nextProps) => {
218
233
  prevProps.parts?.length === nextProps.parts?.length &&
219
234
  prevLastPart?.text === lastPart?.text &&
220
235
  prevProps.status === nextProps.status &&
221
- prevProps.sendMessage === nextProps.sendMessage &&
236
+ // prevProps.sendMessage === nextProps.sendMessage &&
222
237
  prevProps.id === nextProps.id
223
238
  );
224
239
  });
@@ -39,7 +39,7 @@ export const EntryButton = ({ style, onClick, variant }: EntryButtonProps) => {
39
39
  }
40
40
  onClick?.();
41
41
  }}
42
- entryVariant="ghost"
42
+ entryVariant="light"
43
43
  icon={<ColorfulEditIcon />}
44
44
  >
45
45
  AI填写
@@ -0,0 +1,11 @@
1
+ import BorderAnimation from '@/components/bs-ui/border-animation';
2
+ import { GenerateAnimation } from '@/components/bs-ui/generate-animation';
3
+ import { memo } from 'react';
4
+
5
+ export const FirstBatchGeneratingAnimation = memo(() => {
6
+ return (
7
+ <BorderAnimation borderRadius={4} height={40} className="flex items-center px-[10px]">
8
+ <GenerateAnimation>数据生成中,可点击下方按钮暂停</GenerateAnimation>
9
+ </BorderAnimation>
10
+ );
11
+ });
@@ -0,0 +1,17 @@
1
+ import { CircleChecker } from '@/components/bs-ui/circle-checker';
2
+
3
+ export const GeneratedDataCounter = ({
4
+ count,
5
+ handleCheckChange,
6
+ }: {
7
+ count: number;
8
+ handleCheckChange: (checked: boolean) => void;
9
+ }) => {
10
+ return (
11
+ <div className="flex items-center w-full py-[10px] px-[16px] gap-[6px]">
12
+ <CircleChecker defaultChecked onCheckedChange={handleCheckChange}>
13
+ 已生成{count}条数据
14
+ </CircleChecker>
15
+ </div>
16
+ );
17
+ };
@@ -1,141 +1,58 @@
1
- import { useState } from 'react';
2
1
  import { MarkdownMsgpart } from '@/components/biz-comp/markdown-part';
3
- import { MessageContent } from '@/components/biz-comp/message-content';
4
2
  import { usePluginCtx } from '@/hooks/use-plugin-ctx';
5
-
6
- import { GetFieldJsonInfo, IsFieldsJsonPart, NeedToAppendFieldsSaveConfirm } from '@/lib/utils';
7
3
  import { MsgPartCompProps } from '@baishuyun/types';
8
4
  import { TextUIPart } from 'ai';
9
- import { FILLING_EVENTS, PLUGIN_NAME } from '../const';
10
- import { FormFillingPluginCtx } from '../types';
11
- import { useEvtBus } from '@/hooks/use-evt-bus';
12
- import { FormFillingPlugin } from '..';
13
- import { valueNormalizer } from '../utils';
14
- import { FieldValueCheckerProps } from '@/components/biz-comp/FieldValueChecker';
15
- import { FieldValueCheckerListMsg } from '@/components/biz-comp/FieldCheckerListMsg';
16
- import { PrimaryConfirmBtn } from '@/components/bs-ui/primary-confirm-btn';
17
- import { BatchGeneratorAction } from './batch-generator-action';
18
- import { useEvt } from '@/hooks/use-evt';
19
-
20
- export const FillMessagePart = (props: MsgPartCompProps) => {
21
- const { part: p, id, sendMessage, setMessages, status } = props;
22
-
23
- const part = p as TextUIPart;
24
5
 
25
- const isFieldsJson = IsFieldsJsonPart(part);
26
-
27
- const formFillingCtx = usePluginCtx<FormFillingPluginCtx, FormFillingPlugin>(PLUGIN_NAME);
28
-
29
- const showConfirmBtn = NeedToAppendFieldsSaveConfirm(part);
30
-
31
- const [confirmed, setConfirmed] = useState(false);
6
+ import { PLUGIN_NAME } from '../const';
7
+ import { FillPartRenderStrategy, FillPartRenderStrategyMap, FormFillingPluginCtx, IFieldsData } from '../types';
8
+ import { FormFillingPlugin } from '..';
9
+ import { useConversationIdInCtx } from '../hooks/use-conversation-id-in-ctx';
10
+ import { useFieldsData } from '../hooks/use-fields-data';
11
+ import { BatchFillPart } from './batch-fill-part';
12
+ import { SingleFillPart } from './single-fill-part';
13
+
14
+ const RENDER_STRATEGY_COMPONENT_MAP: FillPartRenderStrategyMap = {
15
+ batch: BatchFillPart,
16
+ single: SingleFillPart,
17
+ markdown: MarkdownMsgpart,
18
+ unknown: (_) => null,
19
+ };
32
20
 
33
- if (formFillingCtx) {
34
- formFillingCtx.conversationId = id;
21
+ const getRenderStrategy = (ctx: FormFillingPluginCtx | null, fieldsData: IFieldsData, part: any): FillPartRenderStrategy => {
22
+ if (fieldsData.fieldsParts.length && ctx && ctx.fillMode !== 'batch') {
23
+ return 'single';
35
24
  }
36
25
 
37
- useEvt('chat-area-visibility-change', (data) => {
38
- const visible = (data as { visible?: boolean } | undefined)?.visible;
26
+ if (ctx?.fillMode === 'batch' && fieldsData.fieldsParts.length) {
27
+ return 'batch';
28
+ }
39
29
 
40
- if (visible === false && formFillingCtx?.fillMode === 'batch') {
41
- setMessages(() => []);
42
- }
43
- });
30
+ if (part.text !== undefined && part.text.trim() !== '') {
31
+ return 'markdown';
32
+ }
44
33
 
45
- const evtBus = useEvtBus();
34
+ return 'unknown';
35
+ }
46
36
 
47
- const fieldsParts = Array.isArray(part) && part.length ? part : isFieldsJson ? [part] : [];
48
- if (fieldsParts.length && formFillingCtx && formFillingCtx.fillMode !== 'batch') {
49
- const fieldPropsArray: Array<FieldValueCheckerProps | null> = fieldsParts.map((p) => {
50
- const field = GetFieldJsonInfo(p);
51
- if (
52
- !formFillingCtx.fieldMap?.has(field.fieldName) &&
53
- !formFillingCtx.subFieldsNameMap?.has(field.fieldName)
54
- ) {
55
- console.warn('unregistered field:', field.fieldName, formFillingCtx.fieldMap);
56
- return null;
57
- }
37
+ export const FillMessagePart = (props: MsgPartCompProps) => {
38
+ const { part: p, id } = props;
58
39
 
59
- const normalizedValue = valueNormalizer(field.value);
60
- return {
61
- field: {
62
- label: field.label,
63
- widget: {
64
- widgetName: field.fieldName,
65
- type: field.fieldType,
66
- value: field.value,
67
- },
68
- },
69
- onLoaded: (f: any) => {
70
- f.widget.value = normalizedValue;
71
- formFillingCtx.valueMap.set(f.widget?.widgetName, normalizedValue);
72
- evtBus.emit(FILLING_EVENTS.FIELD_FILLED_UPDATE, {
73
- field: f,
74
- checked: true,
75
- });
76
- },
77
- onChange: (check: boolean, f: any, parentFieldName?: string, row?: number) => {
78
- if (check) {
79
- formFillingCtx.valueMap.set(f.widget?.widgetName, normalizedValue);
80
- }
81
- evtBus.emit(FILLING_EVENTS.FIELD_FILLED_UPDATE, {
82
- field: f,
83
- checked: check,
84
- parentFieldName,
85
- rowIndex: row,
86
- });
87
- },
88
- };
89
- });
40
+ const part = p as TextUIPart;
90
41
 
91
- const filled = fieldPropsArray.filter((f) => f !== null);
42
+ const formFillingCtx = usePluginCtx<FormFillingPluginCtx, FormFillingPlugin>(PLUGIN_NAME);
92
43
 
93
- return (
94
- <MessageContent className="gap-0 py-0" compact>
95
- {status === 'ready' ? `已为你填写${filled.length}个字段` : null}
96
- <FieldValueCheckerListMsg
97
- props={filled}
98
- readonly={confirmed}
99
- streaming={status === 'streaming'}
100
- >
101
- {status === 'ready' ? (
102
- <PrimaryConfirmBtn
103
- className="mt-[20px] mb-[10px]"
104
- disabled={confirmed}
105
- onClick={() => {
106
- setConfirmed(true);
107
- }}
108
- >
109
- {confirmed ? '已确认' : '确认'}
110
- </PrimaryConfirmBtn>
111
- ) : null}
112
- </FieldValueCheckerListMsg>
113
- {status === 'ready'
114
- ? '请选择需要的字段添加到表单。你可以补充描述后重新生成,表单中已添加字段在下次生成时保留。'
115
- : null}
116
- </MessageContent>
117
- );
118
- }
44
+ useConversationIdInCtx(formFillingCtx, id);
119
45
 
120
- if (formFillingCtx?.fillMode === 'batch' && fieldsParts.length) {
121
- return (
122
- <BatchGeneratorAction
123
- id={id}
124
- setMessages={setMessages}
125
- status={status}
126
- parts={fieldsParts}
127
- sendMessage={sendMessage}
128
- />
129
- );
130
- }
46
+ const fieldsData = useFieldsData(part, formFillingCtx);
131
47
 
132
- if (showConfirmBtn) {
133
- return <PrimaryConfirmBtn>保存</PrimaryConfirmBtn>;
134
- }
48
+ const renderStrategy = getRenderStrategy(formFillingCtx, fieldsData, part);
135
49
 
136
- if (part.text !== undefined && part.text.trim() !== '') {
137
- return <MarkdownMsgpart {...props} />;
138
- }
50
+ const Component = RENDER_STRATEGY_COMPONENT_MAP[renderStrategy];
139
51
 
140
- return null;
52
+ return (
53
+ <Component
54
+ {...props}
55
+ {...fieldsData}
56
+ />
57
+ )
141
58
  };
@@ -0,0 +1,18 @@
1
+ import { GenerateAnimation } from '@/components/bs-ui/generate-animation';
2
+ import { PrimaryConfirmBtn } from '@/components/bs-ui/primary-confirm-btn';
3
+ import { DATA_COUNT_PER_BATCH } from '../const';
4
+ import { memo } from 'react';
5
+ import BorderAnimation from '@/components/bs-ui/border-animation';
6
+
7
+ export const NonFirstBatchGeneratingAnimation = memo(() => {
8
+ return (
9
+ <BorderAnimation borderRadius={10} height={110}>
10
+ <div className="flex flex-col gap-[10px] w-full pt-[4px]">
11
+ <GenerateAnimation className='ml-[16px] py-[10px]'>继续生成中,可点击下方暂停按钮暂停</GenerateAnimation>
12
+ <PrimaryConfirmBtn
13
+ className="w-[300px] mx-auto"
14
+ text={`继续生成${DATA_COUNT_PER_BATCH}条`} disabled />
15
+ </div>
16
+ </BorderAnimation>
17
+ );
18
+ });
@@ -0,0 +1,42 @@
1
+ import { MsgPartCompProps } from "@baishuyun/types";
2
+ import { IFieldsData } from "../types";
3
+ import { MessageContent } from "@/components/biz-comp/message-content";
4
+ import { FieldValueCheckerListMsg } from "@/components/biz-comp/FieldCheckerListMsg";
5
+ import { PrimaryConfirmBtn } from "@/components/bs-ui/primary-confirm-btn";
6
+ import { useState } from "react";
7
+
8
+ export const SingleFillPart: React.FC<MsgPartCompProps & IFieldsData> = (props) => {
9
+ const { status, filledFields} = props;
10
+ const [confirmed, setConfirmed] = useState(false);
11
+
12
+ const prefixTips = status === 'ready' ? `已为你填写${filledFields.length}个字段` : null;
13
+ const suffixTips = status === 'ready'
14
+ ? '请选择需要的字段添加到表单。你可以补充描述后重新生成,表单中已添加字段在下次生成时保留。'
15
+ : null;
16
+
17
+ const confirmedText = confirmed ? '已确认' : '确认';
18
+
19
+ return (
20
+ <MessageContent className="gap-0 py-0" compact>
21
+ {prefixTips}
22
+ <FieldValueCheckerListMsg
23
+ props={filledFields}
24
+ readonly={confirmed}
25
+ streaming={status === 'streaming'}
26
+ >
27
+ {status === 'ready' ? (
28
+ <PrimaryConfirmBtn
29
+ className="mt-[20px] mb-[10px]"
30
+ disabled={confirmed}
31
+ onClick={() => {
32
+ setConfirmed(true);
33
+ }}
34
+ >
35
+ {confirmedText}
36
+ </PrimaryConfirmBtn>
37
+ ) : null}
38
+ </FieldValueCheckerListMsg>
39
+ {suffixTips}
40
+ </MessageContent>
41
+ )
42
+ }
@@ -0,0 +1,13 @@
1
+ import { useEffect } from 'react';
2
+ import { FormFillingPluginCtx } from '../types';
3
+
4
+ export const useConversationIdInCtx = (
5
+ ctx: FormFillingPluginCtx | null,
6
+ conversationId?: string
7
+ ) => {
8
+ useEffect(() => {
9
+ if (ctx && conversationId) {
10
+ ctx.conversationId = conversationId;
11
+ }
12
+ }, [ctx, conversationId]);
13
+ };
@@ -0,0 +1,110 @@
1
+ import { TextUIPart } from 'ai';
2
+ import { FormFillingPluginCtx, IFieldsData } from '../types';
3
+ import { GetFieldJsonInfo, IsFieldsJsonPart } from '@/lib/utils';
4
+ import { FieldValueCheckerProps } from '@/components/biz-comp/FieldValueChecker';
5
+ import { valueNormalizer } from '../utils';
6
+ import { useEvtBus } from '@/hooks/use-evt-bus';
7
+ import { FILLING_EVENTS } from '../const';
8
+ import { IEvtBus } from '@baishuyun/types';
9
+ import { useMemo } from 'react';
10
+
11
+ // 解析消息中的字段数据,生成表单填写组件所需的数据结构
12
+ const extractFieldParts = (part: TextUIPart): TextUIPart[] => {
13
+ if (Array.isArray(part) && part.length > 0) return part;
14
+ if (IsFieldsJsonPart(part)) return [part];
15
+ return [];
16
+ };
17
+
18
+ // 判断是否为单条填写模式
19
+ const isSingleFillMode = (parts: TextUIPart[], ctx: FormFillingPluginCtx | null): boolean => {
20
+ return parts.length > 0 && ctx !== null && ctx.fillMode !== 'batch';
21
+ };
22
+
23
+ // 原问题:onLoaded/onChange 在 render 中定义,导致子组件每次都会收到新的函数引用
24
+ // 改进:若性能敏感,应使用 useMemo 或拆分为独立组件
25
+
26
+ interface FieldConfig {
27
+ field: FieldValueCheckerProps['field'];
28
+ onLoaded: (f: any) => void;
29
+ onChange: FieldValueCheckerProps['onChange'];
30
+ }
31
+
32
+ const createFieldConfig = (
33
+ part: TextUIPart,
34
+ ctx: FormFillingPluginCtx,
35
+ evtBus: IEvtBus
36
+ ): FieldConfig | null => {
37
+ const field = GetFieldJsonInfo(part);
38
+ if (!field) return null;
39
+
40
+ const isRegistered =
41
+ ctx.fieldMap?.has(field.fieldName) || ctx.subFieldsNameMap?.has(field.fieldName);
42
+
43
+ if (!isRegistered) {
44
+ console.warn('[useFieldsData] Unregistered field skipped:', field.fieldName);
45
+ return null;
46
+ }
47
+
48
+ const normalizedValue = valueNormalizer(field.value as string | string[]);
49
+
50
+ return {
51
+ field: {
52
+ label: field.label,
53
+ widget: {
54
+ widgetName: field.fieldName,
55
+ type: field.fieldType,
56
+ value: field.value,
57
+ },
58
+ },
59
+ onLoaded: (instance) => {
60
+ instance.widget.value = normalizedValue;
61
+ ctx.valueMap.set(instance.widget?.widgetName, normalizedValue);
62
+ evtBus.emit(FILLING_EVENTS.FIELD_FILLED_UPDATE, {
63
+ field: instance,
64
+ checked: true,
65
+ });
66
+ },
67
+ onChange: (checked, instance, parentFieldName, rowIndex) => {
68
+ if (checked) {
69
+ ctx.valueMap.set(instance.widget?.widgetName, normalizedValue);
70
+ }
71
+ evtBus.emit(FILLING_EVENTS.FIELD_FILLED_UPDATE, {
72
+ field: instance,
73
+ checked,
74
+ parentFieldName,
75
+ rowIndex,
76
+ });
77
+ },
78
+ };
79
+ };
80
+
81
+ export const useFieldsData = (
82
+ part: TextUIPart,
83
+ formFillingCtx: FormFillingPluginCtx | null
84
+ ): IFieldsData => {
85
+ const evtBus = useEvtBus();
86
+
87
+ const rawFieldParts = useMemo(() => extractFieldParts(part), [part]);
88
+
89
+ const isSingleMode = useMemo(
90
+ () => isSingleFillMode(rawFieldParts, formFillingCtx),
91
+ [rawFieldParts, formFillingCtx]
92
+ );
93
+
94
+ const fieldConfigs = useMemo(() => {
95
+ if (!isSingleMode || !formFillingCtx) return [];
96
+
97
+ return rawFieldParts
98
+ .map((p) => createFieldConfig(p, formFillingCtx, evtBus))
99
+ .filter((config): config is FieldConfig => config !== null);
100
+ }, [rawFieldParts, formFillingCtx, evtBus, isSingleMode]);
101
+
102
+ return useMemo(
103
+ () => ({
104
+ fieldsParts: rawFieldParts,
105
+ fieldPropsArray: fieldConfigs,
106
+ filledFields: fieldConfigs.filter(Boolean),
107
+ }),
108
+ [rawFieldParts, fieldConfigs]
109
+ );
110
+ };