@baishuyun/chat-sdk 0.0.16 → 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 (65) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/chat-sdk.js +16293 -15784
  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 +28 -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/fields-design-info-table.stories.tsx +203 -0
@@ -0,0 +1,60 @@
1
+ interface FormParseResult {
2
+ formInfo: string;
3
+ fieldsListInfo: string;
4
+ designDescInfo: string;
5
+ }
6
+
7
+ /**
8
+ * 动态解析表单设计 Markdown,支持不完整文本
9
+ * 可处理以下不完整场景:
10
+ * 1. 仅包含表单基本信息(无字段清单标记)
11
+ * 2. 包含表单信息+部分字段(无设计说明)
12
+ * 3. 字段列表未完结(正在输入中)
13
+ * 4. 缺少结尾确认标记
14
+ */
15
+ export function parseFormMarkdownDynamic(markdown: string): FormParseResult {
16
+ const text = markdown.trim();
17
+
18
+ // 定位关键标记位置(使用正则匹配中文冒号或英文冒号)
19
+ const fieldsMatch = text.match(/字段清单[::]\s*/);
20
+ const designMatch = text.match(/设计说明[::]\s*/);
21
+ const confirmMatch = text.match(/【确认搭建[::][^】]*】\s*$/);
22
+
23
+ const fieldsIndex = fieldsMatch ? fieldsMatch.index! : -1;
24
+ const designIndex = designMatch ? designMatch.index! : -1;
25
+ const confirmIndex = confirmMatch ? confirmMatch.index! : text.length; // 默认到结尾
26
+
27
+ let formInfo = '';
28
+ let fieldsListInfo = '';
29
+ let designDescInfo = '';
30
+
31
+ // 1. 提取 formInfo:开头到"字段清单"之前(如果没有字段清单则取全部)
32
+ if (fieldsIndex !== -1) {
33
+ formInfo = text.substring(0, fieldsIndex);
34
+ } else {
35
+ formInfo = text; // 不完整场景:还没有输入字段清单
36
+ }
37
+ // 清理开头可能的标记文本
38
+ formInfo = formInfo.replace(/^待搭建表单信息\s*/, '').trim();
39
+
40
+ // 2. 提取 fieldsListInfo:字段清单标记后到设计说明之前(或结尾)
41
+ if (fieldsIndex !== -1) {
42
+ const start = fieldsIndex + fieldsMatch![0].length;
43
+ // 结束位置:如果有设计说明则从设计说明前结束,否则到确认标记前或文本结尾
44
+ const end = designIndex !== -1 ? designIndex : confirmMatch ? confirmIndex : text.length;
45
+ fieldsListInfo = text.substring(start, end).trim();
46
+ }
47
+
48
+ // 3. 提取 designDescInfo:设计说明标记后到确认标记前(或结尾)
49
+ if (designIndex !== -1) {
50
+ const start = designIndex + designMatch![0].length;
51
+ const end = confirmMatch ? confirmIndex : text.length;
52
+ designDescInfo = text.substring(start, end).trim();
53
+ }
54
+
55
+ return {
56
+ formInfo,
57
+ fieldsListInfo,
58
+ designDescInfo,
59
+ };
60
+ }
package/src/lib/utils.ts CHANGED
@@ -3,6 +3,7 @@ import { twMerge } from 'tailwind-merge';
3
3
  import { UIDataTypes, UIMessagePart, UITools } from 'ai';
4
4
  import { Field } from '@/plugins/form-builder-base-plugin/types';
5
5
  import { IQueryResult } from '@baishuyun/types';
6
+ import { FORM_ICON_OPTIONS } from '@/const/ui';
6
7
 
7
8
  export function cn(...inputs: ClassValue[]) {
8
9
  return twMerge(clsx(inputs));
@@ -307,3 +308,21 @@ export const mergeFileParts = (parts: Array<UIMessagePart<UIDataTypes, UITools>>
307
308
  2
308
309
  );
309
310
  };
311
+
312
+ export const getIconValue = (iconCode?: string): number => {
313
+ if (!iconCode) {
314
+ return 33;
315
+ }
316
+
317
+ const options = FORM_ICON_OPTIONS;
318
+ const option = options.find((opt) => opt.code === iconCode);
319
+
320
+ return option ? option.value : 33;
321
+ };
322
+
323
+ export const getIconOption = (iconValue?: number) => {
324
+ const options = FORM_ICON_OPTIONS;
325
+ const option = options.find((opt) => opt.value === iconValue);
326
+
327
+ return option || options[8];
328
+ };
@@ -19,6 +19,9 @@ export const EVENTS: Record<string, FormBuilderEventKey> = {
19
19
  // 字段被生成
20
20
  FIELD_CREATED: 'form-builder-FieldCreated',
21
21
 
22
+ // 字段被清空
23
+ FIELDS_CLEARED: 'form-builder-FieldsCleared',
24
+
22
25
  // fields created finish
23
26
  FIELDS_CREATED_FINISH: 'form-builder-FieldsCreatedFinish',
24
27
 
@@ -8,10 +8,14 @@ import { useEvt } from '@/hooks/use-evt';
8
8
  import { parseFormDesignInfo } from '../utils';
9
9
  import { useEvtBus } from '@/hooks/use-evt-bus';
10
10
  import { PrimaryConfirmBtn } from '@/components/bs-ui/primary-confirm-btn';
11
- import FormInfoEditor, { FORM_ICON_OPTIONS } from '@/components/bs-ui/form-info-editor';
11
+ import FormInfoEditor from '@/components/bs-ui/form-info-editor';
12
+ import { FORM_ICON_OPTIONS } from '@/const/ui';
12
13
  import { FakeBotMessage } from '@/components/biz-comp/FakeBotMsg';
13
14
  import { GenerateAnimation } from '@/components/bs-ui/generate-animation';
14
15
  import { WarningMessage } from '@/components/bs-ui/warning-msg';
16
+ import { usePluginCtx } from '@/hooks/use-plugin-ctx';
17
+ import { FormInfo, McpFormBuilderPluginCtx } from '../types';
18
+ import { getIconOption } from '@/lib/utils';
15
19
 
16
20
  export const CreateFormConfirm = memo(
17
21
  ({
@@ -23,6 +27,7 @@ export const CreateFormConfirm = memo(
23
27
  designDoc: string;
24
28
  }) => {
25
29
  const plugin = usePlugin(MCP_PLUGIN_NAME) as FormBuilderPlugin;
30
+ const ctx = usePluginCtx<McpFormBuilderPluginCtx, FormBuilderPlugin>(MCP_PLUGIN_NAME);
26
31
 
27
32
  const [confirm, setConfirm] = useState(false);
28
33
 
@@ -60,12 +65,17 @@ export const CreateFormConfirm = memo(
60
65
  <GenerateAnimation>创建中</GenerateAnimation>
61
66
  );
62
67
 
68
+ console.log('current working form', plugin?.getCurrentWorkingForm());
69
+
63
70
  const afterConfirm = (
64
71
  <FakeBotMessage>
65
72
  {success ? (
66
73
  <>
67
74
  <span className="text-[#4d609f]">已为你创建{designInfo.formName}</span>
68
75
  <FormInfoEditor
76
+ selectedIcon={getIconOption(
77
+ plugin.getCurrentWorkingForm()?.iconValue ?? ctx?.pickedIconValue
78
+ )}
69
79
  onConfirm={(name, icon) => {
70
80
  if (!formRef.current) {
71
81
  return;
@@ -134,6 +144,9 @@ export const CreateFormConfirm = memo(
134
144
  setConfirm(true);
135
145
  }}
136
146
  />
147
+ {confirm
148
+ ? null
149
+ : `如果您希望“${designInfo.formName}”更具体地应用于某个行业(如教育、金融)、某种视图类型(如仪表盘视图、报表视图)或特定的权限管理策略,请补充说明,我可以帮您进一步细化字段设计和关联管理。`}
137
150
  {confirm ? afterConfirm : null}
138
151
  </>
139
152
  );
@@ -0,0 +1,47 @@
1
+ import { MemoizedMarkdown } from '@/components/biz-comp/markdown';
2
+ import { MessageContent } from '@/components/biz-comp/message-content';
3
+ import { AnalysisIcon } from '@/components/bs-ui/bs-icons';
4
+ import { CollapsibleTxtMsg } from '@/components/bs-ui/collapsible-txt-msg';
5
+ import { cn, sanitizeText } from '@/lib/utils';
6
+ import { parseRawFormText } from '../utils';
7
+ import { FieldsDesignInfoTable } from '@/components/bs-ui/fields-design-info-table';
8
+ import { parseFormMarkdownDynamic } from '@/lib/parse-design-doc';
9
+
10
+ export const DesignInfo = ({
11
+ designMarkdown,
12
+ id,
13
+ children,
14
+ }: {
15
+ designMarkdown: string;
16
+ id: string;
17
+ children?: React.ReactNode;
18
+ }) => {
19
+ if (!designMarkdown) {
20
+ return null;
21
+ }
22
+
23
+ const parsed = parseFormMarkdownDynamic(designMarkdown);
24
+ const fields = parseRawFormText(parsed.fieldsListInfo);
25
+
26
+ return (
27
+ <MessageContent
28
+ className={cn('text-[14px]', {
29
+ // "w-fit wrap-break-words rounded-2 px-3 py-2 text-right text-white":
30
+ // role === "user",
31
+ 'bg-transparent px-0 py-0 text-left': true,
32
+ })}
33
+ data-testid="message-content"
34
+ >
35
+ <CollapsibleTxtMsg title={'表单设计'} icon={<AnalysisIcon />} defaultOpen>
36
+ <article className="prose" style={{ fontSize: 14 }}>
37
+ <MemoizedMarkdown id={id + 'pre'} content={sanitizeText(parsed.formInfo)} />
38
+ </article>
39
+ {fields.length ? <FieldsDesignInfoTable fields={fields} /> : null}
40
+ <article className="prose" style={{ fontSize: 14 }}>
41
+ <MemoizedMarkdown id={id + 'suff'} content={sanitizeText(parsed.designDescInfo)} />
42
+ </article>
43
+ </CollapsibleTxtMsg>
44
+ {children}
45
+ </MessageContent>
46
+ );
47
+ };
@@ -5,8 +5,16 @@ import { usePlugin } from '@/hooks/use-plugin';
5
5
  import { EntryButtonProps } from '@baishuyun/types';
6
6
  import { FormBuilderPlugin } from '..';
7
7
  import { MCP_PLUGIN_NAME } from '../const';
8
- export const EntryButton = ({ style, chatVisible, onClick, variant, client }: EntryButtonProps) => {
8
+ export const EntryButton = ({
9
+ style,
10
+ chatStatus,
11
+ chatVisible,
12
+ onClick,
13
+ variant,
14
+ client,
15
+ }: EntryButtonProps) => {
9
16
  const p = usePlugin<FormBuilderPlugin>(MCP_PLUGIN_NAME);
17
+ const minimal = chatStatus === 'dock';
10
18
 
11
19
  if (variant === 'build-fields') {
12
20
  return (
@@ -37,7 +45,7 @@ export const EntryButton = ({ style, chatVisible, onClick, variant, client }: En
37
45
  }
38
46
 
39
47
  return (
40
- <PrimaryEntryBtn style={style} minimal={chatVisible} onClick={onClick}>
48
+ <PrimaryEntryBtn style={style} minimal={minimal} onClick={onClick}>
41
49
  AI表单搭建
42
50
  </PrimaryEntryBtn>
43
51
  );
@@ -1,15 +1,30 @@
1
- import LinearGradientBorderBtn from '@/components/bs-ui/linear-gradient-border-btn';
1
+ import { BaseButton } from '@/components/bs-ui/base-button';
2
+ import { FontIcon } from '@/components/bs-ui/font-icon';
2
3
  import { SuggestionCardProps } from '@baishuyun/types';
3
4
 
5
+ const extractFontIconCodeFrom = (desc: string) => {
6
+ const match = desc.match(/&#x[a-fA-F0-9]+;/);
7
+ return match ? match[0] : '&#xe817;';
8
+ };
9
+
4
10
  export const FollowUp = (props: SuggestionCardProps) => {
11
+ const code = extractFontIconCodeFrom(props.description);
12
+ const iconJsx = code ? <FontIcon code={code} className="text-[16px] text-[#999]" /> : null;
13
+ const descriptionWithoutIcon = code
14
+ ? props.description.replace(code, '').trim()
15
+ : props.description;
16
+
17
+ const titleWithoutIcon = code ? props.title.replace(code, '').trim() : props.title;
5
18
  return (
6
- <LinearGradientBorderBtn
7
- className="self-start"
19
+ <BaseButton
20
+ icon={iconJsx}
21
+ className="self-start w-full px-[10px]"
22
+ hoverTextCls="text-[#121111]"
8
23
  onClick={() => {
9
- props.onSelect?.(props.title);
24
+ props.onSelect?.(titleWithoutIcon, code || '');
10
25
  }}
11
26
  >
12
- {props.description}
13
- </LinearGradientBorderBtn>
27
+ {descriptionWithoutIcon}
28
+ </BaseButton>
14
29
  );
15
30
  };
@@ -4,6 +4,7 @@ import { FollowUp } from './follow-up';
4
4
 
5
5
  import {
6
6
  GetFieldJsonInfo,
7
+ getIconValue,
7
8
  IsFieldsJsonPart,
8
9
  IsSuggesstionPart,
9
10
  NeedToAppendFieldsSaveConfirm,
@@ -17,13 +18,17 @@ import { MCP_PLUGIN_NAME } from '../const';
17
18
  import { FormBuilderPlugin } from '..';
18
19
  import { CreateFormConfirm } from './create-form-confirm';
19
20
  import { FakeBotMessage } from '@/components/biz-comp/FakeBotMsg';
20
- import { FieldCheckerListMsg } from '@/components/biz-comp/FieldCheckerListMsg';
21
+ import {
22
+ FieldCheckerListMsg,
23
+ FieldCheckerListMsgRef,
24
+ } from '@/components/biz-comp/FieldCheckerListMsg';
21
25
  import { PrimaryConfirmBtn } from '@/components/bs-ui/primary-confirm-btn';
22
26
  import { FieldIcon } from '../../../components/biz-comp/field-icon';
23
27
  import { CheckerIcon } from '@/components/bs-ui/bs-icons';
24
28
  import { usePluginCtx } from '@/hooks/use-plugin-ctx';
25
- import { FormBuilderContext } from '@baishuyun/agents/src/form-builder/types';
26
29
  import { McpFormBuilderPluginCtx } from '../types';
30
+ import { DesignInfo } from './design-info';
31
+ import { useRef } from 'react';
27
32
 
28
33
  export const McpMessagePart = (props: MsgPartCompProps) => {
29
34
  const { part: p, sendMessage, status } = props;
@@ -40,6 +45,8 @@ export const McpMessagePart = (props: MsgPartCompProps) => {
40
45
 
41
46
  const plugin = usePlugin<FormBuilderPlugin>(MCP_PLUGIN_NAME);
42
47
 
48
+ const checkerRef = useRef<FieldCheckerListMsgRef>(null);
49
+
43
50
  const ctx = usePluginCtx<McpFormBuilderPluginCtx, FormBuilderPlugin>(MCP_PLUGIN_NAME);
44
51
 
45
52
  const fieldsParts = Array.isArray(part) && part.length ? part : isFieldsJson ? [part] : [];
@@ -60,6 +67,7 @@ export const McpMessagePart = (props: MsgPartCompProps) => {
60
67
  },
61
68
  icon: <FieldIcon type={field.widget?.type} />,
62
69
  onChange: (check: boolean, field: any, parentFieldName?: string, index?: number) => {
70
+ console.log('field checked changed:', { check, field, parentFieldName, index });
63
71
  evtBus.emit('form-builder-FieldCheckedChanged', {
64
72
  field,
65
73
  checked: check,
@@ -79,6 +87,7 @@ export const McpMessagePart = (props: MsgPartCompProps) => {
79
87
  {` 已生成${fieldPropsArray.length}个字段`}
80
88
  </div>
81
89
  <FieldCheckerListMsg
90
+ ref={checkerRef}
82
91
  fields={fieldPropsArray}
83
92
  streaming={status === 'streaming'}
84
93
  confirmed={fieldsConfirmed}
@@ -92,7 +101,6 @@ export const McpMessagePart = (props: MsgPartCompProps) => {
92
101
  'form-builder-FieldsConfirmed',
93
102
  plugin?.getCurrentWorkingForm() as RealFormInfo
94
103
  );
95
-
96
104
  plugin.onAfterFieldsSave((processedInfo) => {
97
105
  evtBus.emit('form-builder-FieldsConfirmed', processedInfo as RealFormInfo);
98
106
  });
@@ -100,7 +108,8 @@ export const McpMessagePart = (props: MsgPartCompProps) => {
100
108
  >
101
109
  {fieldsConfirmed ? (
102
110
  <>
103
- <CheckerIcon className="text-[#0265ff]" /> 已保存
111
+ <CheckerIcon className="text-[#0265ff]" /> 已保存{' '}
112
+ {checkerRef.current?.getCheckedFieldsLength() ?? 0} 个字段
104
113
  </>
105
114
  ) : (
106
115
  '保存字段'
@@ -121,8 +130,15 @@ export const McpMessagePart = (props: MsgPartCompProps) => {
121
130
  <FollowUp
122
131
  title={part.text}
123
132
  description={part.text}
124
- onSelect={(title) => {
125
- ctx?.workStage === 'design';
133
+ onSelect={(title, icon) => {
134
+ if (ctx) {
135
+ ctx.workStage === 'design';
136
+
137
+ console.log('follow up icon code', icon);
138
+ console.log('follow up icon value', getIconValue(icon));
139
+ ctx.pickedIconValue = getIconValue(icon);
140
+ }
141
+
126
142
  sendMessage(
127
143
  { text: title },
128
144
  {
@@ -148,11 +164,15 @@ export const McpMessagePart = (props: MsgPartCompProps) => {
148
164
  // TODO: 特殊文本解析在 node 端处理,前端只负责渲染
149
165
  part.text?.includes('【确认搭建');
150
166
 
151
- return (
152
- <MarkdownMsgpart {...props}>
167
+ const isDesignDoc = part.text?.startsWith('待搭建表单信息');
168
+
169
+ return isDesignDoc ? (
170
+ <DesignInfo designMarkdown={part.text} id={props.id}>
153
171
  {appendConfirmBtn ? (
154
172
  <CreateFormConfirm sendMessage={sendMessage} designDoc={part.text} />
155
173
  ) : null}
156
- </MarkdownMsgpart>
174
+ </DesignInfo>
175
+ ) : (
176
+ <MarkdownMsgpart {...props}></MarkdownMsgpart>
157
177
  );
158
178
  };
@@ -1,18 +1,23 @@
1
1
  import { Suggestions } from '@/components/biz-comp/suggestions';
2
- // import { usePluginCtx } from '@/hooks/use-plugin-ctx';
3
- // import { FormBuilderContext } from '@baishuyun/agents';
2
+ import { usePluginCtx } from '@/hooks/use-plugin-ctx';
4
3
  import { OpeningLinesProps } from '@baishuyun/types';
5
- // import { FormBuilderPlugin } from '..';
6
- // import { MCP_PLUGIN_NAME } from '../const';
4
+ import { FormBuilderPlugin } from '..';
5
+ import { MCP_PLUGIN_NAME } from '../const';
6
+ import { McpFormBuilderPluginCtx } from '../types';
7
+ import { getIconValue } from '@/lib/utils';
7
8
 
8
9
  export const FormBuilderOpeningLines = ({ sendMessage, setMessages }: OpeningLinesProps) => {
9
- // const ctx = usePluginCtx<FormBuilderContext, FormBuilderPlugin>(MCP_PLUGIN_NAME);
10
+ const ctx = usePluginCtx<McpFormBuilderPluginCtx, FormBuilderPlugin>(MCP_PLUGIN_NAME);
10
11
 
11
12
  return (
12
13
  <>
13
14
  👋 你好,你想创建什么表单呢,不知道的话,可以试试下面的建议~
14
15
  <Suggestions
15
- onSelect={(title) => {
16
+ onSelect={(title, iconCode) => {
17
+ if (ctx) {
18
+ ctx.pickedIconValue = getIconValue(iconCode);
19
+ }
20
+
16
21
  sendMessage(
17
22
  {
18
23
  text: title,
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  FieldType,
3
+ IChatSDK,
3
4
  MsgPartCompProps,
4
5
  PluginCustomComponent,
5
6
  PluginHooks,
@@ -30,6 +31,12 @@ type part = ValueOf<Pick<MsgPartCompProps, 'part'>>;
30
31
  export class FormBuilderPlugin extends FormBuilderBasePlugin<McpFormBuilderPluginCtx> {
31
32
  public pluginName: string = MCP_PLUGIN_NAME;
32
33
 
34
+ public init?: ((sdk: IChatSDK) => void) | undefined = (sdk: IChatSDK) => {
35
+ this._chatInstance = sdk;
36
+ };
37
+
38
+ private _chatInstance: IChatSDK | null = null;
39
+
33
40
  private currentWorkingFormName: string = '';
34
41
 
35
42
  public customComponents?: Partial<PluginCustomComponent> | undefined = {
@@ -38,11 +45,54 @@ export class FormBuilderPlugin extends FormBuilderBasePlugin<McpFormBuilderPlugi
38
45
  OpeningLines: FormBuilderOpeningLines,
39
46
  };
40
47
 
48
+ // clear cwf(current working form) when workStage is build, which means rebuild fields from scratch.
49
+ // 1. clear fields.
50
+ // 2. clear fieldMap.
51
+ // 3. rebuild linkInfo based on rebuild fields?
52
+ private clearCurrentWorkingForm = () => {
53
+ if (this.Context.workStage !== 'build') {
54
+ // 仅在搭建阶段清空当前表单信息
55
+ return;
56
+ }
57
+
58
+ const cwf = this.getCurrentWorkingForm();
59
+ if (!cwf) {
60
+ return;
61
+ }
62
+
63
+ // 清空当前表单字段信息
64
+ cwf.fields = [];
65
+ cwf.fieldMap.clear();
66
+
67
+ // TODO: 清空当前表单关联信息
68
+ // cwf.designInfo.linkInfoMap.clear();
69
+
70
+ // 触发字段清空事件,通知更新状态
71
+ const evtBus = this._chatInstance?.eventBus;
72
+ if (evtBus) {
73
+ evtBus.emit<'form-builder-FieldsCleared'>(EVENTS.FIELDS_CLEARED, {});
74
+ }
75
+ };
76
+
41
77
  public lifecycleHooks?: Partial<PluginHooks> | undefined = {
78
+ onBeforeMessagesClear: (conversationId) => {
79
+ const req = async () => {
80
+ await fetch(`https://agent.online-office.net/web/api/form/conversation/clear`, {
81
+ method: 'POST',
82
+ body: JSON.stringify({ conversationId }),
83
+ });
84
+ };
85
+
86
+ req();
87
+
88
+ this.Context.workStage = 'design';
89
+ },
42
90
  onBeforeMessagePartsRender(parts: Array<part>) {
43
91
  return mergeConsecutiveTargets<part>(parts, IsFieldsJsonPart) as part[];
44
92
  },
45
93
  onBeforeMessageSend: ({ text }) => {
94
+ this.clearCurrentWorkingForm();
95
+
46
96
  return {
47
97
  text,
48
98
  options: {
@@ -60,6 +110,15 @@ export class FormBuilderPlugin extends FormBuilderBasePlugin<McpFormBuilderPlugi
60
110
  return {
61
111
  appendLoadingIndicator: true,
62
112
  placeholder: '描述你的需求,如:搭建一张供应商管理表',
113
+ acceptAttachmentFileType: [
114
+ '.csv',
115
+ 'text/csv',
116
+ 'text/plain',
117
+ '.txt',
118
+ 'image/*',
119
+ '.xls',
120
+ '.xlsx',
121
+ ],
63
122
  };
64
123
  },
65
124
  };
@@ -153,7 +212,7 @@ export class FormBuilderPlugin extends FormBuilderBasePlugin<McpFormBuilderPlugi
153
212
  };
154
213
 
155
214
  // 记录表单设计信息
156
- public onBeforeFormCreate(designInfo: DesignInfo) {
215
+ public onBeforeFormCreate = (designInfo: DesignInfo) => {
157
216
  const formName = designInfo.formName || 'Unnamed Form';
158
217
 
159
218
  console.log('表单设计信息提取完毕,结构化数据如下:', designInfo);
@@ -163,16 +222,17 @@ export class FormBuilderPlugin extends FormBuilderBasePlugin<McpFormBuilderPlugi
163
222
  this.Context.formMap.set(formName, {
164
223
  designInfo,
165
224
  fields: [],
225
+ iconValue: this.getPickedIconValue(),
166
226
  fieldMap: new Map(),
167
227
  formName,
168
228
  });
169
229
  }
170
230
 
171
231
  this.currentWorkingFormName = formName;
172
- }
232
+ };
173
233
 
174
234
  // handleFormCreate
175
- public onFormCreate(formInfo: RealFormInfo) {
235
+ public onFormCreate = (formInfo: RealFormInfo) => {
176
236
  // 更新表单信息
177
237
  const existingFormInfo = this.Context.formMap.get(formInfo.formName!);
178
238
 
@@ -184,11 +244,19 @@ export class FormBuilderPlugin extends FormBuilderBasePlugin<McpFormBuilderPlugi
184
244
  existingFormInfo.formId = formInfo.formId;
185
245
  existingFormInfo.appId = formInfo.appId;
186
246
 
247
+ this.resetPickedIconValue();
248
+
187
249
  // 进入搭建阶段
188
250
  this.Context.workStage = 'build';
251
+ };
189
252
 
190
- // this.onAfterFieldsSave(() => {});
191
- }
253
+ public getPickedIconValue = () => {
254
+ return this.Context.pickedIconValue;
255
+ };
256
+
257
+ public resetPickedIconValue = () => {
258
+ this.Context.pickedIconValue = 33;
259
+ };
192
260
 
193
261
  public onAfterFieldsSave(onFormProcessed: (processedForm: FormInfo) => void) {
194
262
  // 取当前正在处理的表单 current working form
@@ -8,6 +8,7 @@ export type Field = ValueOf<Pick<ToolUIPart<FieldsTools>, 'output'>>;
8
8
  export interface FormInfo {
9
9
  appId?: string;
10
10
  formId?: string;
11
+ iconValue?: number;
11
12
  formName?: string;
12
13
  fields: Field[];
13
14
 
@@ -20,4 +21,6 @@ export interface McpFormBuilderPluginCtx {
20
21
  formMap: Map<string, FormInfo>; // Keyed by formId
21
22
 
22
23
  workStage: FormBuilderWorkStage;
24
+
25
+ pickedIconValue?: number;
23
26
  }
@@ -1,6 +1,6 @@
1
1
  import { extractFormName } from '@/lib/utils';
2
2
  import { Field, FormInfo } from '../types';
3
- import { FieldType } from '@baishuyun/types';
3
+ import { FieldType, IFieldDesignInfo } from '@baishuyun/types';
4
4
  import { DEFAULT_DATA_LOAD_CONF } from '../const';
5
5
 
6
6
  export interface FormField {
@@ -55,14 +55,29 @@ function extraFormNameFromFieldDesc(desc?: string) {
55
55
  return '';
56
56
  }
57
57
 
58
- function parseRawFormText(rawText: string): FormField[] {
58
+ export const parseDesc = (desc: string) => {
59
+ const splitDesc = desc.split('-');
60
+ if (splitDesc.length > 1) {
61
+ return {
62
+ isSubForm: splitDesc[0].trim().toLowerCase() === '子表单',
63
+ formName: splitDesc[1].trim(),
64
+ };
65
+ }
66
+
67
+ return {
68
+ isSubForm: false,
69
+ formName: '',
70
+ };
71
+ };
72
+
73
+ export function parseRawFormText(rawText: string): IFieldDesignInfo[] {
59
74
  // 按行分割原始文本
60
75
  const lines = rawText.split('\n').filter((line) => line.trim());
61
- const fields: FormField[] = [];
76
+ const fields: IFieldDesignInfo[] = [];
62
77
 
63
78
  lines.forEach((line) => {
64
79
  // 分割字段名和属性(如:合同编号 | 类型:流水号 | 必填:是 | 说明:xxx)
65
- const [fieldName, typePart, requiredPart, descPart, relationPart = ''] = line
80
+ let [fieldName, typePart, requiredPart, descPart, relationPart = ''] = line
66
81
  .split('|')
67
82
  .map((item) => item.trim());
68
83
 
@@ -86,19 +101,31 @@ function parseRawFormText(rawText: string): FormField[] {
86
101
  // 关系:(关联来源:供应商信息管理表,关联字段:供应商ID、供应商名称、联系人、联系方式)
87
102
  const relationDesc = getMatchResult(relationPart, /关系:(.+)/);
88
103
 
104
+ // 名称清理,去掉可能的序号(如"1. 合同编号" -> "合同编号")
105
+ fieldName = fieldName.replace(/^\d+\.\s*/, '');
106
+
107
+ const parsedDesc = parseDesc(description);
108
+
109
+ const splitFieldName = fieldName.split('-');
110
+ const isFieldInSubForm = splitFieldName.length > 1 || parsedDesc.isSubForm;
111
+ const parentFormName = isFieldInSubForm ? splitFieldName[0] || parsedDesc.formName : undefined;
112
+
89
113
  fields.push({
90
- fieldName,
114
+ fieldName: isFieldInSubForm ? splitFieldName[splitFieldName.length - 1] : fieldName,
91
115
  type,
92
116
  required,
93
117
  description,
94
118
  relationDesc,
119
+ isSubForm: type === '子表单' || parsedDesc.isSubForm,
120
+ isFieldInSubForm,
121
+ parentFormName,
95
122
  });
96
123
  });
97
124
 
98
125
  return fields;
99
126
  }
100
127
 
101
- function extractLinkInfo(fields: FormField[]): Map<string, LinkInfo> {
128
+ function extractLinkInfo(fields: IFieldDesignInfo[]): Map<string, LinkInfo> {
102
129
  // 存储提取结果
103
130
  const linkInfoMap: Map<string, LinkInfo> = new Map();
104
131