@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.
- package/CHANGELOG.md +8 -0
- package/dist/chat-sdk.js +16293 -15784
- package/dist/chat-sdk.js.map +1 -1
- package/dist/chat-sdk.umd.cjs +149 -149
- package/dist/chat-sdk.umd.cjs.map +1 -1
- package/dist/index.css +1 -1
- package/package.json +4 -4
- package/src/chat.tsx +15 -1
- package/src/components/biz-comp/FieldChecker.tsx +49 -7
- package/src/components/biz-comp/FieldCheckerListMsg.tsx +101 -22
- package/src/components/biz-comp/chat-client.tsx +7 -2
- package/src/components/biz-comp/error-msg.tsx +10 -0
- package/src/components/biz-comp/messages.tsx +10 -0
- package/src/components/biz-comp/multi-modal-input/clear-btn.tsx +3 -1
- package/src/components/biz-comp/multi-modal-input/index.tsx +58 -38
- package/src/components/biz-comp/multi-modal-input/prompt-input.tsx +13 -10
- package/src/components/biz-comp/preview-message-wrapper.tsx +4 -4
- package/src/components/biz-comp/preview-message.tsx +3 -1
- package/src/components/biz-comp/suggestions.tsx +5 -1
- package/src/components/bs-ui/attachments-previewer.tsx +4 -1
- package/src/components/bs-ui/base-button.tsx +7 -2
- package/src/components/bs-ui/bs-icons.tsx +29 -0
- package/src/components/bs-ui/card.tsx +4 -3
- package/src/components/bs-ui/chat-area-header.tsx +7 -3
- package/src/components/bs-ui/fields-design-info-table.tsx +160 -0
- package/src/components/bs-ui/fields-previewer.tsx +2 -0
- package/src/components/bs-ui/form-info-editor.tsx +2 -42
- package/src/components/bs-ui/generate-animation.tsx +7 -5
- package/src/components/bs-ui/img-part.tsx +1 -1
- package/src/components/bs-ui/line-checker.tsx +19 -5
- package/src/components/bs-ui/previewer-header.tsx +28 -3
- package/src/components/bs-ui/square-checker.tsx +30 -5
- package/src/components/bs-ui/tooltip.tsx +1 -1
- package/src/components/ui/dialog.tsx +1 -1
- package/src/components/ui/tooltip.tsx +1 -1
- package/src/const/ui.ts +42 -0
- package/src/hooks/use-frame-mode.ts +15 -0
- package/src/lib/parse-design-doc.ts +60 -0
- package/src/lib/utils.ts +19 -0
- package/src/plugins/form-builder-base-plugin/const.ts +3 -0
- package/src/plugins/form-builder-plugin/components/create-form-confirm.tsx +14 -1
- package/src/plugins/form-builder-plugin/components/design-info.tsx +47 -0
- package/src/plugins/form-builder-plugin/components/entry-btn.tsx +10 -2
- package/src/plugins/form-builder-plugin/components/follow-up.tsx +21 -6
- package/src/plugins/form-builder-plugin/components/msg-part.tsx +29 -9
- package/src/plugins/form-builder-plugin/components/opening-lines.tsx +11 -6
- package/src/plugins/form-builder-plugin/index.ts +73 -5
- package/src/plugins/form-builder-plugin/types.ts +3 -0
- package/src/plugins/form-builder-plugin/utils/index.ts +33 -6
- package/src/plugins/form-filling-plugin/components/batch-generator-action.tsx +44 -34
- package/src/plugins/form-filling-plugin/components/first-batch-generating-animation.tsx +21 -0
- package/src/plugins/form-filling-plugin/components/generated-data-counter.tsx +17 -0
- package/src/plugins/form-filling-plugin/components/non-first-batch-generating-animation.tsx +28 -0
- package/src/plugins/form-filling-plugin/index.ts +14 -0
- package/src/plugins/form-filling-plugin/types.ts +2 -0
- package/src/plugins/report-query-plugin/components/query-msg-part.tsx +16 -18
- package/src/plugins/report-query-plugin/components/result-cards/CreatedSourceMsg.tsx +36 -0
- package/src/plugins/report-query-plugin/components/result-cards/DataTableCard.tsx +15 -2
- package/src/plugins/report-query-plugin/const.ts +22 -0
- package/src/plugins/report-query-plugin/index.ts +30 -3
- package/src/plugins/report-query-plugin/types.ts +6 -0
- package/src/sdk.impl.tsx +4 -0
- package/src/store/index.ts +11 -0
- package/src/stories/FormInfoEditor.stories.tsx +19 -28
- 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
|
|
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 = ({
|
|
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={
|
|
48
|
+
<PrimaryEntryBtn style={style} minimal={minimal} onClick={onClick}>
|
|
41
49
|
AI表单搭建
|
|
42
50
|
</PrimaryEntryBtn>
|
|
43
51
|
);
|
|
@@ -1,15 +1,30 @@
|
|
|
1
|
-
import
|
|
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] : '';
|
|
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
|
-
<
|
|
7
|
-
|
|
19
|
+
<BaseButton
|
|
20
|
+
icon={iconJsx}
|
|
21
|
+
className="self-start w-full px-[10px]"
|
|
22
|
+
hoverTextCls="text-[#121111]"
|
|
8
23
|
onClick={() => {
|
|
9
|
-
props.onSelect?.(
|
|
24
|
+
props.onSelect?.(titleWithoutIcon, code || '');
|
|
10
25
|
}}
|
|
11
26
|
>
|
|
12
|
-
{
|
|
13
|
-
</
|
|
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 {
|
|
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
|
|
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
|
-
|
|
152
|
-
|
|
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
|
-
</
|
|
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
|
-
|
|
3
|
-
// import { FormBuilderContext } from '@baishuyun/agents';
|
|
2
|
+
import { usePluginCtx } from '@/hooks/use-plugin-ctx';
|
|
4
3
|
import { OpeningLinesProps } from '@baishuyun/types';
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
76
|
+
const fields: IFieldDesignInfo[] = [];
|
|
62
77
|
|
|
63
78
|
lines.forEach((line) => {
|
|
64
79
|
// 分割字段名和属性(如:合同编号 | 类型:流水号 | 必填:是 | 说明:xxx)
|
|
65
|
-
|
|
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:
|
|
128
|
+
function extractLinkInfo(fields: IFieldDesignInfo[]): Map<string, LinkInfo> {
|
|
102
129
|
// 存储提取结果
|
|
103
130
|
const linkInfoMap: Map<string, LinkInfo> = new Map();
|
|
104
131
|
|