@baishuyun/ai-design 2.0.37 → 2.1.0-alpha.0

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 (269) hide show
  1. package/.storybook/main.ts +31 -31
  2. package/.storybook/preview.ts +16 -16
  3. package/CHANGELOG.md +512 -492
  4. package/components.json +22 -22
  5. package/package.json +9 -6
  6. package/scripts/strip-layers.cjs +41 -41
  7. package/src/components/abs-fullscreen-gradient-bg.tsx +35 -35
  8. package/src/components/agent-helper/agent-category-select-menu/index.tsx +57 -57
  9. package/src/components/agent-helper/agent-entry-card/index.tsx +94 -94
  10. package/src/components/agent-helper/agent-helper-card/index.tsx +179 -179
  11. package/src/components/agent-helper/agent-helper-card-list/index.tsx +59 -59
  12. package/src/components/agent-helper/agent-helper-main-layout/index.tsx +60 -60
  13. package/src/components/agent-helper/agent-sort-select/index.tsx +89 -89
  14. package/src/components/agent-helper/agent-tag/index.tsx +29 -29
  15. package/src/components/agent-helper/app-select/index.tsx +559 -559
  16. package/src/components/agent-helper/condition-group/index.tsx +75 -75
  17. package/src/components/agent-helper/condition-input/index.tsx +233 -233
  18. package/src/components/agent-helper/condition-item/index.tsx +237 -237
  19. package/src/components/agent-helper/condition-quantifier-select/index.tsx +142 -142
  20. package/src/components/agent-helper/condition-trigger-description/index.tsx +49 -49
  21. package/src/components/agent-helper/date-picker/calendar-panel.tsx +164 -164
  22. package/src/components/agent-helper/date-picker/date-input.tsx +91 -91
  23. package/src/components/agent-helper/date-picker/date-picker.tsx +327 -327
  24. package/src/components/agent-helper/date-picker/footer-panel.tsx +44 -44
  25. package/src/components/agent-helper/date-picker/index.ts +3 -3
  26. package/src/components/agent-helper/date-picker/month-panel.tsx +82 -82
  27. package/src/components/agent-helper/date-picker/time-panel.tsx +146 -146
  28. package/src/components/agent-helper/date-picker/types.ts +37 -37
  29. package/src/components/agent-helper/date-picker/utils.ts +121 -121
  30. package/src/components/agent-helper/date-picker/year-panel.tsx +86 -86
  31. package/src/components/agent-helper/exec-log-filter/index.tsx +232 -232
  32. package/src/components/agent-helper/field-edit-preference-select/index.tsx +291 -291
  33. package/src/components/agent-helper/field-select/index.tsx +466 -466
  34. package/src/components/agent-helper/field-value-input/china-address-data.ts +4117 -4117
  35. package/src/components/agent-helper/field-value-input/display.tsx +71 -71
  36. package/src/components/agent-helper/field-value-input/icons.tsx +38 -38
  37. package/src/components/agent-helper/field-value-input/index.tsx +144 -144
  38. package/src/components/agent-helper/field-value-input/inputs.tsx +247 -247
  39. package/src/components/agent-helper/field-value-input/picker-inputs.tsx +1141 -1141
  40. package/src/components/agent-helper/flow-node-action-trigger-select/index.tsx +57 -57
  41. package/src/components/agent-helper/form-action-trigger-select/index.tsx +60 -60
  42. package/src/components/agent-helper/form-divider/index.tsx +44 -44
  43. package/src/components/agent-helper/form-select/index.tsx +732 -732
  44. package/src/components/agent-helper/monthdays-select/index.tsx +378 -378
  45. package/src/components/agent-helper/offset-preference-select/index.tsx +268 -268
  46. package/src/components/agent-helper/offset-type-select/index.tsx +66 -66
  47. package/src/components/agent-helper/offset-unit-select/index.tsx +54 -54
  48. package/src/components/agent-helper/placeholder-card/index.tsx +75 -75
  49. package/src/components/agent-helper/prompt-input/index.tsx +646 -640
  50. package/src/components/agent-helper/relational-operator-select/index.tsx +179 -179
  51. package/src/components/agent-helper/repeat-by-type-select/index.tsx +53 -53
  52. package/src/components/agent-helper/repeat-preference-select/index.tsx +162 -162
  53. package/src/components/agent-helper/repeat-preference-type-select/index.tsx +58 -58
  54. package/src/components/agent-helper/search-input/index.tsx +35 -35
  55. package/src/components/agent-helper/table/index.tsx +114 -114
  56. package/src/components/agent-helper/trigger-select/index.tsx +121 -121
  57. package/src/components/agent-helper/trigger-time-preference-select/index.tsx +189 -189
  58. package/src/components/agent-helper/trigger-time-source-type-select/index.tsx +53 -53
  59. package/src/components/agent-helper/weekdays-select/index.tsx +230 -230
  60. package/src/components/agent-scroll-selector.tsx +193 -193
  61. package/src/components/agent-select.tsx +154 -154
  62. package/src/components/attachment-part-group.tsx +35 -35
  63. package/src/components/attachment-part.tsx +296 -296
  64. package/src/components/attachments-previewer.tsx +272 -272
  65. package/src/components/base-button.tsx +49 -49
  66. package/src/components/border-animation.tsx +107 -107
  67. package/src/components/border-color-animation.tsx +66 -66
  68. package/src/components/bot-avatar-name.tsx +36 -36
  69. package/src/components/bot-icon.tsx +23 -23
  70. package/src/components/bs-entry-select/app-group-folder.tsx +61 -61
  71. package/src/components/bs-entry-select/bs-entry-select.tsx +296 -296
  72. package/src/components/bs-entry-select/build-tree.ts +265 -265
  73. package/src/components/bs-entry-select/entry-item.tsx +45 -45
  74. package/src/components/bs-entry-select/entry-popover.tsx +99 -99
  75. package/src/components/bs-entry-select/index.ts +7 -7
  76. package/src/components/bs-entry-select/other-app-section.tsx +96 -96
  77. package/src/components/bs-entry-select/scroll-slider.tsx +872 -872
  78. package/src/components/bs-entry-select/search-input.tsx +4 -4
  79. package/src/components/bs-entry-select/tree-item-node.tsx +84 -84
  80. package/src/components/bs-entry-select/types.ts +46 -46
  81. package/src/components/bs-icons.tsx +1362 -1362
  82. package/src/components/card-field.tsx +23 -23
  83. package/src/components/card.tsx +36 -36
  84. package/src/components/chat-area-header.tsx +65 -65
  85. package/src/components/check-card.tsx +77 -77
  86. package/src/components/circle-checker.tsx +66 -66
  87. package/src/components/collapsible-txt-msg.tsx +75 -75
  88. package/src/components/collapsible.tsx +9 -9
  89. package/src/components/confirm-dialog.tsx +139 -139
  90. package/src/components/date-range-picker.tsx +91 -91
  91. package/src/components/fake-bot-msg.tsx +51 -51
  92. package/src/components/field-checker-list-msg.tsx +146 -146
  93. package/src/components/field-checker.tsx +185 -185
  94. package/src/components/field-icon.tsx +13 -13
  95. package/src/components/field-value-checker.tsx +116 -116
  96. package/src/components/fields-design-info-table.tsx +160 -160
  97. package/src/components/fields-generating-indicator.tsx +95 -95
  98. package/src/components/fields-portal.tsx +18 -18
  99. package/src/components/fields-previewer.tsx +216 -216
  100. package/src/components/font-icon.tsx +65 -65
  101. package/src/components/form-info-editor.tsx +263 -263
  102. package/src/components/formula-tag.tsx +32 -32
  103. package/src/components/generate-animation.tsx +56 -56
  104. package/src/components/icon-btn.tsx +43 -43
  105. package/src/components/icon-card.tsx +67 -67
  106. package/src/components/icons/calendar-icon.tsx +5 -5
  107. package/src/components/icons/clear-all-icon.tsx +5 -5
  108. package/src/components/icons/empty-agent-icon.tsx +93 -93
  109. package/src/components/icons/empty-table-icon.tsx +104 -104
  110. package/src/components/icons/grids-icon.tsx +5 -5
  111. package/src/components/img-part.tsx +27 -27
  112. package/src/components/legacy-slot.tsx +110 -110
  113. package/src/components/line-checker.tsx +168 -168
  114. package/src/components/linear-gradient-border-btn.tsx +48 -48
  115. package/src/components/linear-gradient-color-bg-animation.tsx +103 -103
  116. package/src/components/memoized-markdown.tsx +32 -32
  117. package/src/components/message-content.tsx +21 -21
  118. package/src/components/number-part.tsx +46 -46
  119. package/src/components/office-container.tsx +289 -265
  120. package/src/components/option-item.tsx +27 -27
  121. package/src/components/output-config-editor.tsx +478 -478
  122. package/src/components/previewer-header.tsx +270 -270
  123. package/src/components/primary-confirm-btn.tsx +58 -58
  124. package/src/components/primary-entry-btn.tsx +48 -48
  125. package/src/components/radio-card.tsx +63 -63
  126. package/src/components/scroll-to-bottom-btn.tsx +28 -28
  127. package/src/components/split-line.tsx +9 -9
  128. package/src/components/square-checker.tsx +87 -87
  129. package/src/components/subform-fields-value-checker.tsx +184 -184
  130. package/src/components/tab-radio-group.tsx +83 -83
  131. package/src/components/tooltip.tsx +53 -53
  132. package/src/components/ui/button.tsx +51 -51
  133. package/src/components/ui/calendar.tsx +57 -57
  134. package/src/components/ui/dialog.tsx +104 -104
  135. package/src/components/ui/drawer.tsx +279 -279
  136. package/src/components/ui/form.tsx +202 -202
  137. package/src/components/ui/icons.tsx +1267 -1267
  138. package/src/components/ui/input.tsx +28 -28
  139. package/src/components/ui/label.tsx +30 -30
  140. package/src/components/ui/popover.tsx +85 -85
  141. package/src/components/ui/select.tsx +236 -236
  142. package/src/components/ui/spinner.tsx +17 -17
  143. package/src/components/ui/switch.tsx +33 -33
  144. package/src/components/ui/tabs.tsx +62 -62
  145. package/src/components/ui/textarea.tsx +28 -28
  146. package/src/components/ui/tooltip.tsx +53 -53
  147. package/src/components/user-txt-msg-bubble.tsx +47 -47
  148. package/src/components/warning-msg.tsx +27 -27
  149. package/src/const/ui.ts +119 -119
  150. package/src/css.d.ts +4 -4
  151. package/src/hooks/use-column-appear.ts +78 -78
  152. package/src/hooks/use-portal-container.ts +27 -27
  153. package/src/hooks/use-resizable-width.ts +129 -129
  154. package/src/hooks/use-select-popover-position.ts +74 -74
  155. package/src/hooks/use-shadow.ts +127 -127
  156. package/src/index.ts +226 -226
  157. package/src/lib/utils.ts +138 -138
  158. package/src/stories/AbsFullscreenGradientBg.stories.tsx +36 -36
  159. package/src/stories/AgentCategorySelectMenu.stories.tsx +33 -33
  160. package/src/stories/AgentEntryCard.stories.tsx +90 -90
  161. package/src/stories/AgentHelperCard.stories.tsx +105 -105
  162. package/src/stories/AgentHelperCardList.stories.tsx +120 -120
  163. package/src/stories/AgentHelperMainLayout.stories.tsx +71 -71
  164. package/src/stories/AgentScrollSelector.stories.tsx +95 -95
  165. package/src/stories/AgentSelect.stories.tsx +134 -134
  166. package/src/stories/AgentTag.stories.tsx +44 -44
  167. package/src/stories/AppSelect.stories.tsx +250 -250
  168. package/src/stories/AttachmentPart.stories.tsx +122 -122
  169. package/src/stories/AttachmentPartGroup.stories.tsx +152 -152
  170. package/src/stories/AttachmentsPreviewer.stories.tsx +306 -306
  171. package/src/stories/BaseButton.stories.tsx +33 -33
  172. package/src/stories/BorderAnimation.stories.tsx +116 -116
  173. package/src/stories/BorderColorAnimation.stories.tsx +63 -63
  174. package/src/stories/BotAvatarAndName.stories.ts +24 -24
  175. package/src/stories/BotIcon.stories.ts +22 -22
  176. package/src/stories/BsConditionInput.stories.tsx +460 -460
  177. package/src/stories/BsEntrySelect.stories.tsx +343 -343
  178. package/src/stories/BsTooltip.stories.tsx +32 -32
  179. package/src/stories/Card.stories.tsx +28 -28
  180. package/src/stories/CardField.stories.tsx +87 -87
  181. package/src/stories/ChatAreaHeader.stories.ts +26 -26
  182. package/src/stories/CheckCard.stories.tsx +114 -114
  183. package/src/stories/CircleChecker.stories.tsx +89 -89
  184. package/src/stories/CollapsibleTextMsg.stories.ts +44 -44
  185. package/src/stories/ConditionGroup.stories.tsx +216 -216
  186. package/src/stories/ConditionInput.stories.tsx +502 -502
  187. package/src/stories/ConditionItem.stories.tsx +163 -163
  188. package/src/stories/ConditonQuantifierSelect.stories.tsx +54 -54
  189. package/src/stories/ConditonTriggerDescription.stories.tsx +61 -61
  190. package/src/stories/ConfirmDialog.stories.tsx +123 -123
  191. package/src/stories/DataTable.stories.tsx +191 -191
  192. package/src/stories/DatePicker.stories.tsx +174 -174
  193. package/src/stories/ExecLogFilter.stories.tsx +145 -145
  194. package/src/stories/FakeBotMsg.stories.tsx +36 -36
  195. package/src/stories/FieldEditPreferenceSelect.stories.tsx +161 -161
  196. package/src/stories/FieldSelect.stories.tsx +228 -228
  197. package/src/stories/FieldValueInput.stories.tsx +442 -442
  198. package/src/stories/FlowNodeActionTriggerSelect.stories.tsx +80 -80
  199. package/src/stories/Form.stories.tsx +302 -302
  200. package/src/stories/FormActionTriggerSelect.stories.tsx +80 -80
  201. package/src/stories/FormDivider.stories.tsx +31 -31
  202. package/src/stories/FormInfoEditor.stories.tsx +173 -173
  203. package/src/stories/FormSelect.stories.tsx +222 -222
  204. package/src/stories/FormulaTag.stories.tsx +68 -68
  205. package/src/stories/GenerateAnimation.stories.ts +20 -20
  206. package/src/stories/IconBtn.stories.tsx +32 -32
  207. package/src/stories/IconCard.stories.tsx +35 -35
  208. package/src/stories/IconLib.tsx +45 -45
  209. package/src/stories/Icons.stories.ts +22 -22
  210. package/src/stories/ImgMsgPart.stories.tsx +42 -42
  211. package/src/stories/Input.stories.tsx +102 -102
  212. package/src/stories/Label.stories.tsx +66 -66
  213. package/src/stories/LegacySlot.stories.tsx +56 -56
  214. package/src/stories/LineChecker.stories.tsx +133 -133
  215. package/src/stories/LinearGradientBorderBtn.stories.tsx +86 -86
  216. package/src/stories/LinearGradientColorBgAnimation.stories.tsx +60 -60
  217. package/src/stories/MemoizedMarkdown.stories.tsx +102 -102
  218. package/src/stories/MessageContent.stories.tsx +36 -36
  219. package/src/stories/MonthdaysSelect.stories.tsx +78 -78
  220. package/src/stories/NumbersMsgPart.stories.tsx +42 -42
  221. package/src/stories/OfficeContainer.stories.tsx +287 -287
  222. package/src/stories/OffsetPreferenceSelect.stories.tsx +118 -118
  223. package/src/stories/OffsetTypeSelect.stories.tsx +75 -75
  224. package/src/stories/OffsetUnitSelect.stories.tsx +61 -61
  225. package/src/stories/OptionItem.stories.tsx +77 -77
  226. package/src/stories/OutputConfigEditor.stories.tsx +96 -96
  227. package/src/stories/PlaceholderCard.stories.tsx +35 -35
  228. package/src/stories/PreviewerHeader.stories.tsx +91 -91
  229. package/src/stories/PrimaryConfirmBtn.stories.tsx +60 -60
  230. package/src/stories/PrimaryEntryBtn.stories.tsx +74 -74
  231. package/src/stories/PromptInput.stories.tsx +228 -228
  232. package/src/stories/RadioCard.stories.tsx +103 -103
  233. package/src/stories/RelationalOperatorSelect.stories.tsx +61 -61
  234. package/src/stories/RepeatByTypeSelect.stories.tsx +58 -58
  235. package/src/stories/RepeatPreferenceSelect.stories.tsx +107 -107
  236. package/src/stories/RepeatPreferenceTypeSelect.stories.tsx +58 -58
  237. package/src/stories/ScrollToBottom.stories.tsx +24 -24
  238. package/src/stories/SearchInput.stories.tsx +50 -50
  239. package/src/stories/Select.stories.tsx +128 -128
  240. package/src/stories/SplitLine.stories.tsx +40 -40
  241. package/src/stories/SquareChecker.stories.tsx +96 -96
  242. package/src/stories/TabRadioGroup.stories.tsx +84 -84
  243. package/src/stories/Textarea.stories.tsx +67 -67
  244. package/src/stories/TriggerSelect.stories.tsx +48 -48
  245. package/src/stories/TriggerTimePreferenceSelect.stories.tsx +106 -106
  246. package/src/stories/TriggerTimeSourceTypeSelect.stories.tsx +68 -68
  247. package/src/stories/UserTextMsgBubble.stories.ts +24 -24
  248. package/src/stories/WarningMessage.stories.tsx +23 -23
  249. package/src/stories/WeekdaysSelect.stories.tsx +66 -66
  250. package/src/stories/assets/demo.css +539 -539
  251. package/src/stories/assets/demo_index.html +7988 -7988
  252. package/src/stories/assets/iconfont.css +1377 -1377
  253. package/src/stories/assets/iconfont.json +2382 -2382
  254. package/src/stories/assets/iconfont.svg +697 -697
  255. package/src/stories/collapsible.stories.tsx +47 -47
  256. package/src/stories/drawer.stories.tsx +196 -196
  257. package/src/stories/field-checker-list-msg.stories.tsx +95 -95
  258. package/src/stories/field-checker.stories.tsx +117 -117
  259. package/src/stories/field-icon.stories.tsx +70 -70
  260. package/src/stories/field-value-checker.stories.tsx +113 -113
  261. package/src/stories/fields-design-info-table.stories.tsx +203 -203
  262. package/src/stories/fields-generating.stories.tsx +41 -41
  263. package/src/stories/fields-previewer.stories.tsx +77 -77
  264. package/src/stories/subform-fields-value-checker.stories.tsx +98 -98
  265. package/src/style.css +226 -226
  266. package/src/types.ts +8 -8
  267. package/style.css +3652 -3652
  268. package/tsconfig.json +13 -13
  269. package/vite.config.ts +44 -44
@@ -1,478 +1,478 @@
1
- import { useState, useCallback } from 'react';
2
- import type { FieldType, IAgentHelperOutputConfig, OutputParam, IField, ISubformField } from '@baishuyun/types';
3
- import { cn } from '../lib/utils';
4
- import { transCls } from '../const/ui';
5
- import { TrashIcon, SearchIcon } from '../components/bs-icons';
6
- import { Popover, PopoverTrigger, PopoverContent } from './ui/popover';
7
- import { usePortalContainer } from '../hooks/use-portal-container';
8
-
9
- const PlusIcon = () => (
10
- <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
11
- <path d="M8 3.5v9M3.5 8h9" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
12
- </svg>
13
- );
14
-
15
- const CaretIcon = ({ className }: { className?: string }) => (
16
- <svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
17
- <path d="M3 4.5l3 3 3-3" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
18
- </svg>
19
- );
20
-
21
- const ExpandArrowIcon = ({ className }: { className?: string }) => (
22
- <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
23
- <path d="M6 4l4 4-4 4" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
24
- </svg>
25
- );
26
-
27
- interface OutputFieldOption {
28
- fieldName: string;
29
- fieldDesc: string;
30
- type: FieldType | '';
31
- children?: OutputFieldOption[];
32
- }
33
-
34
- type DropdownTarget = { index: number; field: 'source' | 'target' } | null;
35
-
36
- export interface OutputConfigEditorProps {
37
- value: Array<IAgentHelperOutputConfig>;
38
- onChange?: (value: Array<IAgentHelperOutputConfig>) => void;
39
-
40
- sourceFields?: OutputParam[];
41
- targetFields?: IField[];
42
- className?: string;
43
- }
44
-
45
- function outputParamsToOptions(params: OutputParam[]): OutputFieldOption[] {
46
- return params.map(p => ({
47
- fieldName: p.name,
48
- fieldDesc: p.description || p.name,
49
- type: p.type,
50
- children: p.subformParams?.map(s => ({
51
- fieldName: s.name,
52
- fieldDesc: s.name,
53
- type: s.type,
54
- })),
55
- }));
56
- }
57
-
58
- function fieldsToOptions(fields: IField[]): OutputFieldOption[] {
59
- return fields.map(f => {
60
- const subformItems = f.widget.items?.filter(
61
- (item): item is ISubformField => 'widget' in item
62
- );
63
- return {
64
- fieldName: f.widget.widgetName,
65
- fieldDesc: f.label,
66
- type: f.widget.type,
67
- children: subformItems?.length ? subformItems.map(s => ({
68
- fieldName: `${f.widget.widgetName}.${s.widget.widgetName}`,
69
- fieldDesc: `${s.label}`,
70
- type: s.widget.type,
71
- })) : undefined,
72
- };
73
- });
74
- }
75
-
76
- function flattenSourceFields(fields: OutputFieldOption[]): OutputFieldOption[] {
77
- const result: OutputFieldOption[] = [];
78
- for (const field of fields) {
79
- result.push(field);
80
- if (field.children) {
81
- for (const child of field.children) {
82
- result.push({
83
- ...child,
84
- fieldName: `${field.fieldName}.${child.fieldName}`,
85
- fieldDesc: `${field.fieldDesc}.${child.fieldDesc}`,
86
- });
87
- }
88
- }
89
- }
90
- return result;
91
- }
92
-
93
- function findFieldByName(fields: OutputFieldOption[], fieldName: string): OutputFieldOption | undefined {
94
- for (const f of fields) {
95
- if (f.fieldName === fieldName) return f;
96
- if (f.children) {
97
- const found = findFieldByName(f.children, fieldName);
98
- if (found) return found;
99
- }
100
- }
101
- return undefined;
102
- }
103
-
104
-
105
- const VALID_TYPE_MAP: Partial<Record<FieldType, Set<FieldType>>> = {
106
- 'text': new Set(['text', 'textarea']),
107
- 'number': new Set(['text', 'textarea']),
108
- 'radio': new Set(['radio', 'radiogroup']),
109
- 'signature': new Set(['signature', 'image', 'upload']),
110
- 'image': new Set(['signature', 'image', 'upload']),
111
- }
112
-
113
- const EMPTY_SET = new Set();
114
-
115
- function filterFieldsByType(fields: OutputFieldOption[], targetType: FieldType | ''): OutputFieldOption[] {
116
- return fields.reduce<OutputFieldOption[]>((acc, f) => {
117
- const selfMatches = f.type === targetType;
118
- const validTypeSet = VALID_TYPE_MAP[targetType as FieldType] ?? EMPTY_SET;
119
- const generalMatches = validTypeSet.has(f.type);
120
-
121
- const filteredChildren = f.children ? filterFieldsByType(f.children, targetType) : [];
122
- if (selfMatches || generalMatches || filteredChildren.length > 0) {
123
- acc.push({ ...f, children: filteredChildren.length > 0 ? filteredChildren : f.children });
124
- }
125
- return acc;
126
- }, []);
127
- }
128
-
129
- export function OutputConfigEditor({ value, onChange, sourceFields, targetFields, className }: OutputConfigEditorProps) {
130
- const [dropdownTarget, setDropdownTarget] = useState<DropdownTarget>(null);
131
- const [searchText, setSearchText] = useState('');
132
- const [collapsedParents, setCollapsedParents] = useState<Set<string>>(new Set());
133
-
134
- const { portalContainer, setTriggerRef } = usePortalContainer('data-bs-output-config-portal');
135
-
136
- const sourceOptions = sourceFields ? outputParamsToOptions(sourceFields) : [];
137
- const targetOptions = targetFields ? fieldsToOptions(targetFields) : [];
138
-
139
- const handleDropdownOpenChange = useCallback((index: number, field: 'source' | 'target', open: boolean) => {
140
- if (open) {
141
- setDropdownTarget({ index, field });
142
- setSearchText('');
143
- setCollapsedParents(new Set());
144
- } else {
145
- setDropdownTarget(null);
146
- setSearchText('');
147
- setCollapsedParents(new Set());
148
- }
149
- }, []);
150
-
151
- const handleAdd = useCallback(() => {
152
- onChange?.([...value, { sourceField: { fieldName: '', fieldDesc: '' }, targetField: { fieldName: '', fieldDesc: '' } }]);
153
- }, [value, onChange]);
154
-
155
- const handleDelete = useCallback((index: number) => {
156
- onChange?.(value.filter((_, i) => i !== index));
157
- }, [value, onChange]);
158
-
159
- const handleSelectSourceField = useCallback((index: number, field: OutputFieldOption) => {
160
- const newValue = [...value];
161
- const newItem = { ...newValue[index], sourceField: { fieldName: field.fieldName, fieldDesc: field.fieldDesc } };
162
- if (newItem.targetField.fieldName) {
163
- const targetOption = findFieldByName(targetOptions, newItem.targetField.fieldName);
164
- if (targetOption && targetOption.type !== field.type) {
165
- newItem.targetField = { fieldName: '', fieldDesc: '' };
166
- }
167
- }
168
- newValue[index] = newItem;
169
- onChange?.(newValue);
170
- setDropdownTarget(null);
171
- setSearchText('');
172
- }, [value, onChange, targetOptions]);
173
-
174
- const handleSelectTargetField = useCallback((index: number, field: OutputFieldOption) => {
175
- const newValue = [...value];
176
- newValue[index] = { ...newValue[index], targetField: { fieldName: field.fieldName, fieldDesc: field.fieldDesc } };
177
- onChange?.(newValue);
178
- setDropdownTarget(null);
179
- setSearchText('');
180
- }, [value, onChange]);
181
-
182
- const toggleParent = useCallback((fieldName: string) => {
183
- setCollapsedParents(prev => {
184
- const next = new Set(prev);
185
- if (next.has(fieldName)) {
186
- next.delete(fieldName);
187
- } else {
188
- next.add(fieldName);
189
- }
190
- return next;
191
- });
192
- }, []);
193
-
194
- const matchSearch = (text: string) =>
195
- !searchText || text?.toLowerCase().includes(searchText.toLowerCase());
196
-
197
- const filterFields = (fields: OutputFieldOption[]): OutputFieldOption[] =>
198
- fields.reduce<OutputFieldOption[]>((acc, f) => {
199
- const selfMatches = matchSearch(f.fieldDesc) || matchSearch(f.fieldName);
200
- const filteredChildren = f.children ? filterFields(f.children) : [];
201
- if (selfMatches || filteredChildren.length > 0) {
202
- acc.push({ ...f, children: filteredChildren.length > 0 ? filteredChildren : f.children });
203
- }
204
- return acc;
205
- }, []);
206
-
207
- const flatSourceFields = flattenSourceFields(sourceOptions);
208
- const filteredSourceFields = flatSourceFields.filter(
209
- (f) => (matchSearch(f.fieldDesc) || matchSearch(f.fieldName)) && f.type !== 'subform'
210
- );
211
-
212
- const searchedTargetFields = filterFields(targetOptions);
213
-
214
- const fieldRowCls = cn(
215
- 'bg-white border border-[#e0e0e0] rounded-[4px] h-[34px] px-[10px] py-[7px]',
216
- 'flex items-center min-w-0',
217
- 'text-[14px] text-[#030303] leading-normal',
218
- 'hover:border-[#0265ff]',
219
- transCls,
220
- );
221
-
222
- const isDropdownOpen = (index: number, field: 'source' | 'target') =>
223
- dropdownTarget?.index === index && dropdownTarget?.field === field;
224
-
225
- if (value.length === 0) {
226
- return (
227
- <div className={className}>
228
- <button
229
- type="button"
230
- onClick={handleAdd}
231
- className={cn(
232
- 'inline-flex items-center gap-[6px]',
233
- 'bg-transparent border-0 cursor-pointer p-0',
234
- 'text-[#0265ff] text-[14px] leading-normal',
235
- 'hover:opacity-80',
236
- transCls,
237
- )}
238
- >
239
- <PlusIcon />
240
- <span>添加出参名称</span>
241
- </button>
242
- </div>
243
- );
244
- }
245
-
246
- return (
247
- <div ref={setTriggerRef} className={cn(
248
- 'bg-[#f5f5f5] border border-[#e0e0e0] border-dashed rounded-[4px] p-[10px]',
249
- 'flex flex-col gap-[10px]',
250
- className,
251
- )}>
252
- {value.map((item, index) => {
253
- const selectedSourceType = item.sourceField.fieldName
254
- ? flatSourceFields.find(f => f.fieldName === item.sourceField.fieldName)?.type
255
- : undefined;
256
- const rowTargetFields = selectedSourceType
257
- ? filterFieldsByType(searchedTargetFields, selectedSourceType)
258
- : searchedTargetFields;
259
- return (
260
- <div key={index} className="flex gap-[10px] items-center">
261
- {/* Source field selector (JSON output field) */}
262
- <Popover open={isDropdownOpen(index, 'source')} onOpenChange={(open) => handleDropdownOpenChange(index, 'source', open)}>
263
- <PopoverTrigger asChild>
264
- <button
265
- type="button"
266
- className={cn(
267
- fieldRowCls,
268
- 'w-full justify-between cursor-pointer',
269
- isDropdownOpen(index, 'source') ? 'border-[#0265ff]' : 'border-[#e0e0e0]',
270
- )}
271
- >
272
- <span className="text-[14px] text-[#030303] leading-normal truncate">
273
- {item.sourceField.fieldDesc || <span className="text-[#999]">请选择字段</span>}
274
- </span>
275
- <CaretIcon className={cn(
276
- 'shrink-0 text-[#666]',
277
- isDropdownOpen(index, 'source') && 'rotate-180',
278
- transCls,
279
- )} />
280
- </button>
281
- </PopoverTrigger>
282
- <PopoverContent
283
- align="start"
284
- sideOffset={4}
285
- portalContainer={portalContainer}
286
- className="w-[216px] rounded-[4px] p-[4px] shadow-[0px_0px_16px_0px_rgba(0,0,0,0.1)]"
287
- >
288
- <div className="relative flex items-center border border-[#e0e0e0] rounded-[4px] mb-[4px]">
289
- <span className="absolute left-[10px] top-1/2 -translate-y-1/2">
290
- <SearchIcon />
291
- </span>
292
- <input
293
- type="text"
294
- value={searchText}
295
- onChange={e => setSearchText(e.target.value)}
296
- placeholder="搜索"
297
- className={cn(
298
- 'w-full h-[34px] pl-[32px] pr-[10px]',
299
- 'text-[14px] text-[#030303] leading-normal outline-none',
300
- 'bg-transparent border-0 rounded-[4px]',
301
- 'placeholder:text-[#999]',
302
- )}
303
- />
304
- </div>
305
- <div className="max-h-[210px] overflow-auto">
306
- {filteredSourceFields.map((field) => (
307
- <button
308
- key={field.fieldName}
309
- type="button"
310
- onClick={() => handleSelectSourceField(index, field)}
311
- className={cn(
312
- 'flex items-center w-full px-[10px] py-[7px] h-[34px] rounded-[4px]',
313
- 'text-[14px] text-[#030303] leading-normal',
314
- 'hover:bg-[#EFF0F6] bg-transparent border-0 cursor-pointer',
315
- item.sourceField.fieldDesc === field.fieldDesc && 'bg-[#EFF0F6] text-[#0265ff]',
316
- transCls,
317
- )}
318
- >
319
- <span className="truncate">{field.fieldDesc}</span>
320
- </button>
321
- ))}
322
- {filteredSourceFields.length === 0 && (
323
- <div className="px-[10px] py-[7px] text-[14px] text-[#999] leading-normal">
324
- 无匹配结果
325
- </div>
326
- )}
327
- </div>
328
- </PopoverContent>
329
- </Popover>
330
-
331
- {/* "设置为" label */}
332
- <div className="bg-[#d9d9d9] rounded-[4px] h-[34px] w-[98px] shrink-0 flex items-center justify-center">
333
- <span className="text-[14px] text-[#030303] leading-normal whitespace-nowrap">设置为</span>
334
- </div>
335
-
336
- {/* Target field selector (Form field) */}
337
- <Popover open={isDropdownOpen(index, 'target')} onOpenChange={(open) => handleDropdownOpenChange(index, 'target', open)}>
338
- <PopoverTrigger asChild>
339
- <button
340
- type="button"
341
- className={cn(
342
- fieldRowCls,
343
- 'w-full justify-between cursor-pointer',
344
- isDropdownOpen(index, 'target') ? 'border-[#0265ff]' : 'border-[#e0e0e0]',
345
- )}
346
- >
347
- <span className="text-[14px] text-[#030303] leading-normal truncate">
348
- {item.targetField.fieldDesc || <span className="text-[#999]">请选择表单字段</span>}
349
- </span>
350
- <CaretIcon className={cn(
351
- 'shrink-0 text-[#666]',
352
- isDropdownOpen(index, 'target') && 'rotate-180',
353
- transCls,
354
- )} />
355
- </button>
356
- </PopoverTrigger>
357
- <PopoverContent
358
- align="start"
359
- sideOffset={4}
360
- portalContainer={portalContainer}
361
- className="w-[216px] rounded-[4px] p-[4px] shadow-[0px_0px_16px_0px_rgba(0,0,0,0.1)]"
362
- >
363
- <div className="relative flex items-center border border-[#e0e0e0] rounded-[4px] mb-[4px]">
364
- <span className="absolute left-[10px] top-1/2 -translate-y-1/2">
365
- <SearchIcon />
366
- </span>
367
- <input
368
- type="text"
369
- value={searchText}
370
- onChange={e => setSearchText(e.target.value)}
371
- placeholder="搜索"
372
- className={cn(
373
- 'w-full h-[34px] pl-[32px] pr-[10px]',
374
- 'text-[14px] text-[#030303] leading-normal outline-none',
375
- 'bg-transparent border-0 rounded-[4px]',
376
- 'placeholder:text-[#999]',
377
- )}
378
- />
379
- </div>
380
- <div className="max-h-[210px] overflow-auto">
381
- {rowTargetFields.map((field) => {
382
- const isParent = field.children && field.children.length > 0;
383
- const isCollapsed = collapsedParents.has(field.fieldName);
384
-
385
- return (
386
- <div key={field.fieldName}>
387
- {isParent ? (
388
- <button
389
- type="button"
390
- onClick={() => toggleParent(field.fieldName)}
391
- className={cn(
392
- 'flex items-center gap-[6px] w-full px-[10px] py-[7px] h-[34px] rounded-[4px]',
393
- 'text-[14px] text-[#030303] leading-normal',
394
- 'hover:bg-[#EFF0F6] bg-transparent border-0 cursor-pointer',
395
- transCls,
396
- )}
397
- >
398
- <ExpandArrowIcon className={cn(
399
- 'shrink-0 text-[#999]',
400
- !isCollapsed && 'rotate-90',
401
- transCls,
402
- )} />
403
- <span className="truncate">{field.fieldDesc}</span>
404
- </button>
405
- ) : (
406
- <button
407
- type="button"
408
- onClick={() => handleSelectTargetField(index, field)}
409
- className={cn(
410
- 'flex items-center w-full px-[10px] py-[7px] h-[34px] rounded-[4px]',
411
- 'text-[14px] text-[#030303] leading-normal',
412
- 'hover:bg-[#EFF0F6] bg-transparent border-0 cursor-pointer',
413
- item.targetField.fieldName === field.fieldName && 'bg-[#EFF0F6] text-[#0265ff]',
414
- transCls,
415
- )}
416
- >
417
- <span className="truncate">{field.fieldDesc}</span>
418
- </button>
419
- )}
420
- {isParent && !isCollapsed && field.children!.map((child) => (
421
- <button
422
- key={child.fieldName}
423
- type="button"
424
- onClick={() => handleSelectTargetField(index, child)}
425
- className={cn(
426
- 'flex items-center w-full pl-[34px] pr-[10px] py-[7px] h-[34px] rounded-[4px]',
427
- 'text-[14px] text-[#030303] leading-normal',
428
- 'hover:bg-[#EFF0F6] bg-transparent border-0 cursor-pointer',
429
- item.targetField.fieldName === child.fieldName && 'bg-[#EFF0F6] text-[#0265ff]',
430
- transCls,
431
- )}
432
- >
433
- <span className="truncate">{child.fieldDesc}</span>
434
- </button>
435
- ))}
436
- </div>
437
- );
438
- })}
439
- {rowTargetFields.length === 0 && (
440
- <div className="px-[10px] py-[7px] text-[14px] text-[#999] leading-normal">
441
- 无匹配结果
442
- </div>
443
- )}
444
- </div>
445
- </PopoverContent>
446
- </Popover>
447
-
448
- {/* Delete button */}
449
- <button
450
- type="button"
451
- onClick={() => handleDelete(index)}
452
- className="shrink-0 cursor-pointer bg-transparent border-0 p-0"
453
- >
454
- <span className="inline-flex items-center justify-center size-[16px] text-[#666] hover:text-[#FF4D4F] [&_svg_path]:fill-[#666] [&_svg_path]:hover:fill-[#FF4D4F]">
455
- <TrashIcon />
456
- </span>
457
- </button>
458
- </div>
459
- );
460
- })}
461
-
462
- <button
463
- type="button"
464
- onClick={handleAdd}
465
- className={cn(
466
- 'inline-flex items-center gap-[6px] w-fit',
467
- 'bg-transparent border-0 cursor-pointer p-0',
468
- 'text-[#0265ff] text-[14px] leading-normal',
469
- 'hover:opacity-80',
470
- transCls,
471
- )}
472
- >
473
- <PlusIcon />
474
- <span>添加出参名称</span>
475
- </button>
476
- </div>
477
- );
478
- }
1
+ import { useState, useCallback } from 'react';
2
+ import type { FieldType, IAgentHelperOutputConfig, OutputParam, IField, ISubformField } from '@baishuyun/types';
3
+ import { cn } from '../lib/utils';
4
+ import { transCls } from '../const/ui';
5
+ import { TrashIcon, SearchIcon } from '../components/bs-icons';
6
+ import { Popover, PopoverTrigger, PopoverContent } from './ui/popover';
7
+ import { usePortalContainer } from '../hooks/use-portal-container';
8
+
9
+ const PlusIcon = () => (
10
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
11
+ <path d="M8 3.5v9M3.5 8h9" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
12
+ </svg>
13
+ );
14
+
15
+ const CaretIcon = ({ className }: { className?: string }) => (
16
+ <svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
17
+ <path d="M3 4.5l3 3 3-3" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
18
+ </svg>
19
+ );
20
+
21
+ const ExpandArrowIcon = ({ className }: { className?: string }) => (
22
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
23
+ <path d="M6 4l4 4-4 4" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
24
+ </svg>
25
+ );
26
+
27
+ interface OutputFieldOption {
28
+ fieldName: string;
29
+ fieldDesc: string;
30
+ type: FieldType | '';
31
+ children?: OutputFieldOption[];
32
+ }
33
+
34
+ type DropdownTarget = { index: number; field: 'source' | 'target' } | null;
35
+
36
+ export interface OutputConfigEditorProps {
37
+ value: Array<IAgentHelperOutputConfig>;
38
+ onChange?: (value: Array<IAgentHelperOutputConfig>) => void;
39
+
40
+ sourceFields?: OutputParam[];
41
+ targetFields?: IField[];
42
+ className?: string;
43
+ }
44
+
45
+ function outputParamsToOptions(params: OutputParam[]): OutputFieldOption[] {
46
+ return params.map(p => ({
47
+ fieldName: p.name,
48
+ fieldDesc: p.description || p.name,
49
+ type: p.type,
50
+ children: p.subformParams?.map(s => ({
51
+ fieldName: s.name,
52
+ fieldDesc: s.name,
53
+ type: s.type,
54
+ })),
55
+ }));
56
+ }
57
+
58
+ function fieldsToOptions(fields: IField[]): OutputFieldOption[] {
59
+ return fields.map(f => {
60
+ const subformItems = f.widget.items?.filter(
61
+ (item): item is ISubformField => 'widget' in item
62
+ );
63
+ return {
64
+ fieldName: f.widget.widgetName,
65
+ fieldDesc: f.label,
66
+ type: f.widget.type,
67
+ children: subformItems?.length ? subformItems.map(s => ({
68
+ fieldName: `${f.widget.widgetName}.${s.widget.widgetName}`,
69
+ fieldDesc: `${s.label}`,
70
+ type: s.widget.type,
71
+ })) : undefined,
72
+ };
73
+ });
74
+ }
75
+
76
+ function flattenSourceFields(fields: OutputFieldOption[]): OutputFieldOption[] {
77
+ const result: OutputFieldOption[] = [];
78
+ for (const field of fields) {
79
+ result.push(field);
80
+ if (field.children) {
81
+ for (const child of field.children) {
82
+ result.push({
83
+ ...child,
84
+ fieldName: `${field.fieldName}.${child.fieldName}`,
85
+ fieldDesc: `${field.fieldDesc}.${child.fieldDesc}`,
86
+ });
87
+ }
88
+ }
89
+ }
90
+ return result;
91
+ }
92
+
93
+ function findFieldByName(fields: OutputFieldOption[], fieldName: string): OutputFieldOption | undefined {
94
+ for (const f of fields) {
95
+ if (f.fieldName === fieldName) return f;
96
+ if (f.children) {
97
+ const found = findFieldByName(f.children, fieldName);
98
+ if (found) return found;
99
+ }
100
+ }
101
+ return undefined;
102
+ }
103
+
104
+
105
+ const VALID_TYPE_MAP: Partial<Record<FieldType, Set<FieldType>>> = {
106
+ 'text': new Set(['text', 'textarea']),
107
+ 'number': new Set(['text', 'textarea']),
108
+ 'radio': new Set(['radio', 'radiogroup']),
109
+ 'signature': new Set(['signature', 'image', 'upload']),
110
+ 'image': new Set(['signature', 'image', 'upload']),
111
+ }
112
+
113
+ const EMPTY_SET = new Set();
114
+
115
+ function filterFieldsByType(fields: OutputFieldOption[], targetType: FieldType | ''): OutputFieldOption[] {
116
+ return fields.reduce<OutputFieldOption[]>((acc, f) => {
117
+ const selfMatches = f.type === targetType;
118
+ const validTypeSet = VALID_TYPE_MAP[targetType as FieldType] ?? EMPTY_SET;
119
+ const generalMatches = validTypeSet.has(f.type);
120
+
121
+ const filteredChildren = f.children ? filterFieldsByType(f.children, targetType) : [];
122
+ if (selfMatches || generalMatches || filteredChildren.length > 0) {
123
+ acc.push({ ...f, children: filteredChildren.length > 0 ? filteredChildren : f.children });
124
+ }
125
+ return acc;
126
+ }, []);
127
+ }
128
+
129
+ export function OutputConfigEditor({ value, onChange, sourceFields, targetFields, className }: OutputConfigEditorProps) {
130
+ const [dropdownTarget, setDropdownTarget] = useState<DropdownTarget>(null);
131
+ const [searchText, setSearchText] = useState('');
132
+ const [collapsedParents, setCollapsedParents] = useState<Set<string>>(new Set());
133
+
134
+ const { portalContainer, setTriggerRef } = usePortalContainer('data-bs-output-config-portal');
135
+
136
+ const sourceOptions = sourceFields ? outputParamsToOptions(sourceFields) : [];
137
+ const targetOptions = targetFields ? fieldsToOptions(targetFields) : [];
138
+
139
+ const handleDropdownOpenChange = useCallback((index: number, field: 'source' | 'target', open: boolean) => {
140
+ if (open) {
141
+ setDropdownTarget({ index, field });
142
+ setSearchText('');
143
+ setCollapsedParents(new Set());
144
+ } else {
145
+ setDropdownTarget(null);
146
+ setSearchText('');
147
+ setCollapsedParents(new Set());
148
+ }
149
+ }, []);
150
+
151
+ const handleAdd = useCallback(() => {
152
+ onChange?.([...value, { sourceField: { fieldName: '', fieldDesc: '' }, targetField: { fieldName: '', fieldDesc: '' } }]);
153
+ }, [value, onChange]);
154
+
155
+ const handleDelete = useCallback((index: number) => {
156
+ onChange?.(value.filter((_, i) => i !== index));
157
+ }, [value, onChange]);
158
+
159
+ const handleSelectSourceField = useCallback((index: number, field: OutputFieldOption) => {
160
+ const newValue = [...value];
161
+ const newItem = { ...newValue[index], sourceField: { fieldName: field.fieldName, fieldDesc: field.fieldDesc } };
162
+ if (newItem.targetField.fieldName) {
163
+ const targetOption = findFieldByName(targetOptions, newItem.targetField.fieldName);
164
+ if (targetOption && targetOption.type !== field.type) {
165
+ newItem.targetField = { fieldName: '', fieldDesc: '' };
166
+ }
167
+ }
168
+ newValue[index] = newItem;
169
+ onChange?.(newValue);
170
+ setDropdownTarget(null);
171
+ setSearchText('');
172
+ }, [value, onChange, targetOptions]);
173
+
174
+ const handleSelectTargetField = useCallback((index: number, field: OutputFieldOption) => {
175
+ const newValue = [...value];
176
+ newValue[index] = { ...newValue[index], targetField: { fieldName: field.fieldName, fieldDesc: field.fieldDesc } };
177
+ onChange?.(newValue);
178
+ setDropdownTarget(null);
179
+ setSearchText('');
180
+ }, [value, onChange]);
181
+
182
+ const toggleParent = useCallback((fieldName: string) => {
183
+ setCollapsedParents(prev => {
184
+ const next = new Set(prev);
185
+ if (next.has(fieldName)) {
186
+ next.delete(fieldName);
187
+ } else {
188
+ next.add(fieldName);
189
+ }
190
+ return next;
191
+ });
192
+ }, []);
193
+
194
+ const matchSearch = (text: string) =>
195
+ !searchText || text?.toLowerCase().includes(searchText.toLowerCase());
196
+
197
+ const filterFields = (fields: OutputFieldOption[]): OutputFieldOption[] =>
198
+ fields.reduce<OutputFieldOption[]>((acc, f) => {
199
+ const selfMatches = matchSearch(f.fieldDesc) || matchSearch(f.fieldName);
200
+ const filteredChildren = f.children ? filterFields(f.children) : [];
201
+ if (selfMatches || filteredChildren.length > 0) {
202
+ acc.push({ ...f, children: filteredChildren.length > 0 ? filteredChildren : f.children });
203
+ }
204
+ return acc;
205
+ }, []);
206
+
207
+ const flatSourceFields = flattenSourceFields(sourceOptions);
208
+ const filteredSourceFields = flatSourceFields.filter(
209
+ (f) => (matchSearch(f.fieldDesc) || matchSearch(f.fieldName)) && f.type !== 'subform'
210
+ );
211
+
212
+ const searchedTargetFields = filterFields(targetOptions);
213
+
214
+ const fieldRowCls = cn(
215
+ 'bg-white border border-[#e0e0e0] rounded-[4px] h-[34px] px-[10px] py-[7px]',
216
+ 'flex items-center min-w-0',
217
+ 'text-[14px] text-[#030303] leading-normal',
218
+ 'hover:border-[#0265ff]',
219
+ transCls,
220
+ );
221
+
222
+ const isDropdownOpen = (index: number, field: 'source' | 'target') =>
223
+ dropdownTarget?.index === index && dropdownTarget?.field === field;
224
+
225
+ if (value.length === 0) {
226
+ return (
227
+ <div className={className}>
228
+ <button
229
+ type="button"
230
+ onClick={handleAdd}
231
+ className={cn(
232
+ 'inline-flex items-center gap-[6px]',
233
+ 'bg-transparent border-0 cursor-pointer p-0',
234
+ 'text-[#0265ff] text-[14px] leading-normal',
235
+ 'hover:opacity-80',
236
+ transCls,
237
+ )}
238
+ >
239
+ <PlusIcon />
240
+ <span>添加出参名称</span>
241
+ </button>
242
+ </div>
243
+ );
244
+ }
245
+
246
+ return (
247
+ <div ref={setTriggerRef} className={cn(
248
+ 'bg-[#f5f5f5] border border-[#e0e0e0] border-dashed rounded-[4px] p-[10px]',
249
+ 'flex flex-col gap-[10px]',
250
+ className,
251
+ )}>
252
+ {value.map((item, index) => {
253
+ const selectedSourceType = item.sourceField.fieldName
254
+ ? flatSourceFields.find(f => f.fieldName === item.sourceField.fieldName)?.type
255
+ : undefined;
256
+ const rowTargetFields = selectedSourceType
257
+ ? filterFieldsByType(searchedTargetFields, selectedSourceType)
258
+ : searchedTargetFields;
259
+ return (
260
+ <div key={index} className="flex gap-[10px] items-center">
261
+ {/* Source field selector (JSON output field) */}
262
+ <Popover open={isDropdownOpen(index, 'source')} onOpenChange={(open) => handleDropdownOpenChange(index, 'source', open)}>
263
+ <PopoverTrigger asChild>
264
+ <button
265
+ type="button"
266
+ className={cn(
267
+ fieldRowCls,
268
+ 'w-full justify-between cursor-pointer',
269
+ isDropdownOpen(index, 'source') ? 'border-[#0265ff]' : 'border-[#e0e0e0]',
270
+ )}
271
+ >
272
+ <span className="text-[14px] text-[#030303] leading-normal truncate">
273
+ {item.sourceField.fieldDesc || <span className="text-[#999]">请选择字段</span>}
274
+ </span>
275
+ <CaretIcon className={cn(
276
+ 'shrink-0 text-[#666]',
277
+ isDropdownOpen(index, 'source') && 'rotate-180',
278
+ transCls,
279
+ )} />
280
+ </button>
281
+ </PopoverTrigger>
282
+ <PopoverContent
283
+ align="start"
284
+ sideOffset={4}
285
+ portalContainer={portalContainer}
286
+ className="w-[216px] rounded-[4px] p-[4px] shadow-[0px_0px_16px_0px_rgba(0,0,0,0.1)]"
287
+ >
288
+ <div className="relative flex items-center border border-[#e0e0e0] rounded-[4px] mb-[4px]">
289
+ <span className="absolute left-[10px] top-1/2 -translate-y-1/2">
290
+ <SearchIcon />
291
+ </span>
292
+ <input
293
+ type="text"
294
+ value={searchText}
295
+ onChange={e => setSearchText(e.target.value)}
296
+ placeholder="搜索"
297
+ className={cn(
298
+ 'w-full h-[34px] pl-[32px] pr-[10px]',
299
+ 'text-[14px] text-[#030303] leading-normal outline-none',
300
+ 'bg-transparent border-0 rounded-[4px]',
301
+ 'placeholder:text-[#999]',
302
+ )}
303
+ />
304
+ </div>
305
+ <div className="max-h-[210px] overflow-auto">
306
+ {filteredSourceFields.map((field) => (
307
+ <button
308
+ key={field.fieldName}
309
+ type="button"
310
+ onClick={() => handleSelectSourceField(index, field)}
311
+ className={cn(
312
+ 'flex items-center w-full px-[10px] py-[7px] h-[34px] rounded-[4px]',
313
+ 'text-[14px] text-[#030303] leading-normal',
314
+ 'hover:bg-[#EFF0F6] bg-transparent border-0 cursor-pointer',
315
+ item.sourceField.fieldDesc === field.fieldDesc && 'bg-[#EFF0F6] text-[#0265ff]',
316
+ transCls,
317
+ )}
318
+ >
319
+ <span className="truncate">{field.fieldDesc}</span>
320
+ </button>
321
+ ))}
322
+ {filteredSourceFields.length === 0 && (
323
+ <div className="px-[10px] py-[7px] text-[14px] text-[#999] leading-normal">
324
+ 无匹配结果
325
+ </div>
326
+ )}
327
+ </div>
328
+ </PopoverContent>
329
+ </Popover>
330
+
331
+ {/* "设置为" label */}
332
+ <div className="bg-[#d9d9d9] rounded-[4px] h-[34px] w-[98px] shrink-0 flex items-center justify-center">
333
+ <span className="text-[14px] text-[#030303] leading-normal whitespace-nowrap">设置为</span>
334
+ </div>
335
+
336
+ {/* Target field selector (Form field) */}
337
+ <Popover open={isDropdownOpen(index, 'target')} onOpenChange={(open) => handleDropdownOpenChange(index, 'target', open)}>
338
+ <PopoverTrigger asChild>
339
+ <button
340
+ type="button"
341
+ className={cn(
342
+ fieldRowCls,
343
+ 'w-full justify-between cursor-pointer',
344
+ isDropdownOpen(index, 'target') ? 'border-[#0265ff]' : 'border-[#e0e0e0]',
345
+ )}
346
+ >
347
+ <span className="text-[14px] text-[#030303] leading-normal truncate">
348
+ {item.targetField.fieldDesc || <span className="text-[#999]">请选择表单字段</span>}
349
+ </span>
350
+ <CaretIcon className={cn(
351
+ 'shrink-0 text-[#666]',
352
+ isDropdownOpen(index, 'target') && 'rotate-180',
353
+ transCls,
354
+ )} />
355
+ </button>
356
+ </PopoverTrigger>
357
+ <PopoverContent
358
+ align="start"
359
+ sideOffset={4}
360
+ portalContainer={portalContainer}
361
+ className="w-[216px] rounded-[4px] p-[4px] shadow-[0px_0px_16px_0px_rgba(0,0,0,0.1)]"
362
+ >
363
+ <div className="relative flex items-center border border-[#e0e0e0] rounded-[4px] mb-[4px]">
364
+ <span className="absolute left-[10px] top-1/2 -translate-y-1/2">
365
+ <SearchIcon />
366
+ </span>
367
+ <input
368
+ type="text"
369
+ value={searchText}
370
+ onChange={e => setSearchText(e.target.value)}
371
+ placeholder="搜索"
372
+ className={cn(
373
+ 'w-full h-[34px] pl-[32px] pr-[10px]',
374
+ 'text-[14px] text-[#030303] leading-normal outline-none',
375
+ 'bg-transparent border-0 rounded-[4px]',
376
+ 'placeholder:text-[#999]',
377
+ )}
378
+ />
379
+ </div>
380
+ <div className="max-h-[210px] overflow-auto">
381
+ {rowTargetFields.map((field) => {
382
+ const isParent = field.children && field.children.length > 0;
383
+ const isCollapsed = collapsedParents.has(field.fieldName);
384
+
385
+ return (
386
+ <div key={field.fieldName}>
387
+ {isParent ? (
388
+ <button
389
+ type="button"
390
+ onClick={() => toggleParent(field.fieldName)}
391
+ className={cn(
392
+ 'flex items-center gap-[6px] w-full px-[10px] py-[7px] h-[34px] rounded-[4px]',
393
+ 'text-[14px] text-[#030303] leading-normal',
394
+ 'hover:bg-[#EFF0F6] bg-transparent border-0 cursor-pointer',
395
+ transCls,
396
+ )}
397
+ >
398
+ <ExpandArrowIcon className={cn(
399
+ 'shrink-0 text-[#999]',
400
+ !isCollapsed && 'rotate-90',
401
+ transCls,
402
+ )} />
403
+ <span className="truncate">{field.fieldDesc}</span>
404
+ </button>
405
+ ) : (
406
+ <button
407
+ type="button"
408
+ onClick={() => handleSelectTargetField(index, field)}
409
+ className={cn(
410
+ 'flex items-center w-full px-[10px] py-[7px] h-[34px] rounded-[4px]',
411
+ 'text-[14px] text-[#030303] leading-normal',
412
+ 'hover:bg-[#EFF0F6] bg-transparent border-0 cursor-pointer',
413
+ item.targetField.fieldName === field.fieldName && 'bg-[#EFF0F6] text-[#0265ff]',
414
+ transCls,
415
+ )}
416
+ >
417
+ <span className="truncate">{field.fieldDesc}</span>
418
+ </button>
419
+ )}
420
+ {isParent && !isCollapsed && field.children!.map((child) => (
421
+ <button
422
+ key={child.fieldName}
423
+ type="button"
424
+ onClick={() => handleSelectTargetField(index, child)}
425
+ className={cn(
426
+ 'flex items-center w-full pl-[34px] pr-[10px] py-[7px] h-[34px] rounded-[4px]',
427
+ 'text-[14px] text-[#030303] leading-normal',
428
+ 'hover:bg-[#EFF0F6] bg-transparent border-0 cursor-pointer',
429
+ item.targetField.fieldName === child.fieldName && 'bg-[#EFF0F6] text-[#0265ff]',
430
+ transCls,
431
+ )}
432
+ >
433
+ <span className="truncate">{child.fieldDesc}</span>
434
+ </button>
435
+ ))}
436
+ </div>
437
+ );
438
+ })}
439
+ {rowTargetFields.length === 0 && (
440
+ <div className="px-[10px] py-[7px] text-[14px] text-[#999] leading-normal">
441
+ 无匹配结果
442
+ </div>
443
+ )}
444
+ </div>
445
+ </PopoverContent>
446
+ </Popover>
447
+
448
+ {/* Delete button */}
449
+ <button
450
+ type="button"
451
+ onClick={() => handleDelete(index)}
452
+ className="shrink-0 cursor-pointer bg-transparent border-0 p-0"
453
+ >
454
+ <span className="inline-flex items-center justify-center size-[16px] text-[#666] hover:text-[#FF4D4F] [&_svg_path]:fill-[#666] [&_svg_path]:hover:fill-[#FF4D4F]">
455
+ <TrashIcon />
456
+ </span>
457
+ </button>
458
+ </div>
459
+ );
460
+ })}
461
+
462
+ <button
463
+ type="button"
464
+ onClick={handleAdd}
465
+ className={cn(
466
+ 'inline-flex items-center gap-[6px] w-fit',
467
+ 'bg-transparent border-0 cursor-pointer p-0',
468
+ 'text-[#0265ff] text-[14px] leading-normal',
469
+ 'hover:opacity-80',
470
+ transCls,
471
+ )}
472
+ >
473
+ <PlusIcon />
474
+ <span>添加出参名称</span>
475
+ </button>
476
+ </div>
477
+ );
478
+ }