@langgraph-js/ui 5.6.0 → 5.7.1
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/dist/assets/arc-BN7eXDsr.js +1 -0
- package/dist/assets/architectureDiagram-VXUJARFQ-BlClJNg_.js +36 -0
- package/dist/assets/{blockDiagram-VD42YOAC-JpCtx8Fw.js → blockDiagram-VD42YOAC-B1yxKMBm.js} +4 -4
- package/dist/assets/{c4Diagram-YG6GDRKO-Ct0YUG6f.js → c4Diagram-YG6GDRKO-DYnk7yK1.js} +1 -1
- package/dist/assets/channel-W05vnIRi.js +1 -0
- package/dist/assets/{chunk-4BX2VUAB-O9tuPwJ2.js → chunk-4BX2VUAB-OBKPWQYL.js} +1 -1
- package/dist/assets/{chunk-55IACEB6-B1LOZAa5.js → chunk-55IACEB6-gYWv8Zf5.js} +1 -1
- package/dist/assets/{chunk-B4BG7PRW-CHB7g7y9.js → chunk-B4BG7PRW-C53GIfTN.js} +1 -1
- package/dist/assets/{chunk-DI55MBZ5-BZe7PkXl.js → chunk-DI55MBZ5-VE97R82T.js} +1 -1
- package/dist/assets/{chunk-FMBD7UC4-IV4Fy30-.js → chunk-FMBD7UC4-1vi-TjGz.js} +1 -1
- package/dist/assets/{chunk-QN33PNHL-DCcmoYki.js → chunk-QN33PNHL-BIwuKx1J.js} +1 -1
- package/dist/assets/{chunk-QZHKN3VN-BNYpxk7b.js → chunk-QZHKN3VN-voU2PjMs.js} +1 -1
- package/dist/assets/{chunk-TZMSLE5B-BgP3duP5.js → chunk-TZMSLE5B-DqFqeo6j.js} +1 -1
- package/dist/assets/classDiagram-2ON5EDUG-BAA0T-HR.js +1 -0
- package/dist/assets/classDiagram-v2-WZHVMYZB-BAA0T-HR.js +1 -0
- package/dist/assets/clone-BnfE7gLV.js +1 -0
- package/dist/assets/{cose-bilkent-S5V4N54A-B_7kmvVh.js → cose-bilkent-S5V4N54A-BG8aPbwR.js} +1 -1
- package/dist/assets/{dagre-6UL2VRFP-CAxkUiea.js → dagre-6UL2VRFP-B2SQQldX.js} +2 -2
- package/dist/assets/diagram-PSM6KHXK-DPT_FKfu.js +24 -0
- package/dist/assets/diagram-QEK2KX5R-BSIuCy1e.js +43 -0
- package/dist/assets/{diagram-S2PKOQOG-CZOKfwOI.js → diagram-S2PKOQOG-DAQpMvAq.js} +1 -1
- package/dist/assets/{erDiagram-Q2GNP2WA-D9v3M4EU.js → erDiagram-Q2GNP2WA-gKwnjOTf.js} +1 -1
- package/dist/assets/{flowDiagram-NV44I4VS-B9qnS72A.js → flowDiagram-NV44I4VS-DYhT5qks.js} +1 -1
- package/dist/assets/ganttDiagram-LVOFAZNH-WboqEMaA.js +267 -0
- package/dist/assets/{gitGraphDiagram-NY62KEGX-Cm36kSO3.js → gitGraphDiagram-NY62KEGX-BRtqtCxN.js} +1 -1
- package/dist/assets/{graph-C2p3i6RT.js → graph-BaHCYR4Y.js} +1 -1
- package/dist/assets/index-JVKUnJL6.css +1 -0
- package/dist/assets/{index-Cf3jkaWJ.js → index-yx5a2qjD.js} +312 -173
- package/dist/assets/{infoDiagram-F6ZHWCRC-DkBxmgNt.js → infoDiagram-F6ZHWCRC-DyYvFs8H.js} +1 -1
- package/dist/assets/isUndefined-CuNi_bm9.js +1 -0
- package/dist/assets/{journeyDiagram-XKPGCS4Q-CVukoP-j.js → journeyDiagram-XKPGCS4Q-BqtfhCdd.js} +1 -1
- package/dist/assets/{kanban-definition-3W4ZIXB7-0-SbO6tS.js → kanban-definition-3W4ZIXB7-CyDmlWOW.js} +4 -4
- package/dist/assets/layout-higHf58s.js +1 -0
- package/dist/assets/mermaid.core-3MG272Pv.js +197 -0
- package/dist/assets/min-BoUtT0GV.js +1 -0
- package/dist/assets/{mindmap-definition-VGOIOE7T-CDGdm5yw.js → mindmap-definition-VGOIOE7T-Y0yyOiIO.js} +5 -5
- package/dist/assets/pieDiagram-ADFJNKIX-D_nzBY3u.js +30 -0
- package/dist/assets/{quadrantDiagram-AYHSOK5B-DY0NltyG.js → quadrantDiagram-AYHSOK5B-CUBZfINn.js} +2 -2
- package/dist/assets/{requirementDiagram-UZGBJVZJ-DwhwO5lc.js → requirementDiagram-UZGBJVZJ-kfnK1mwU.js} +1 -1
- package/dist/assets/sankeyDiagram-TZEHDZUN-DALezZLf.js +10 -0
- package/dist/assets/{sequenceDiagram-WL72ISMW-CpTIKJGg.js → sequenceDiagram-WL72ISMW-Ph3PSHlU.js} +1 -1
- package/dist/assets/stateDiagram-FKZM4ZOC-BjIg4oDx.js +1 -0
- package/dist/assets/stateDiagram-v2-4FDKWEC3-nRC6pSc8.js +1 -0
- package/dist/assets/{timeline-definition-IT6M3QCI-1MkGCcpM.js → timeline-definition-IT6M3QCI-Cra1vVbB.js} +3 -3
- package/dist/assets/{treemap-KMMF4GRG-pRPfKbhX.js → treemap-KMMF4GRG-DihM0GUo.js} +1 -1
- package/dist/assets/xychartDiagram-PRI3JC2R-PhzqQQLa.js +7 -0
- package/dist/index.html +2 -2
- package/package.json +3 -2
- package/src/chat/Chat.tsx +3 -4
- package/src/chat/components/ErrorBoundary.tsx +23 -10
- package/src/chat/tools/{show_form.tsx → ask_user_to_fill_form.tsx} +283 -114
- package/src/chat/tools/ask_user_with_options.tsx +174 -0
- package/src/chat/tools/display_information_card.tsx +131 -0
- package/src/chat/tools/image_generation.tsx +140 -0
- package/src/chat/tools/index.ts +18 -6
- package/src/chat/tools/visualize_data_with_chart.tsx +274 -0
- package/src/chat/tools/wait_for_user_to_upload_file.tsx +324 -0
- package/dist/assets/arc-BNOCe7pe.js +0 -1
- package/dist/assets/architectureDiagram-VXUJARFQ-yf1p-wI_.js +0 -36
- package/dist/assets/channel-CizfE02g.js +0 -1
- package/dist/assets/classDiagram-2ON5EDUG-CFe5Iz8b.js +0 -1
- package/dist/assets/classDiagram-v2-WZHVMYZB-CFe5Iz8b.js +0 -1
- package/dist/assets/clone-C4H89Tb1.js +0 -1
- package/dist/assets/defaultLocale-C4B-KCzX.js +0 -1
- package/dist/assets/diagram-PSM6KHXK-BeM-KUrT.js +0 -24
- package/dist/assets/diagram-QEK2KX5R-DDl_pKlU.js +0 -43
- package/dist/assets/ganttDiagram-LVOFAZNH-C1Hjglog.js +0 -267
- package/dist/assets/index-DLwWU25h.css +0 -1
- package/dist/assets/init-Gi6I4Gst.js +0 -1
- package/dist/assets/isUndefined-DhAvAp_K.js +0 -1
- package/dist/assets/layout-fJUHcXGp.js +0 -1
- package/dist/assets/linear-RxTDErCI.js +0 -1
- package/dist/assets/mermaid.core-Y2zCEMvG.js +0 -197
- package/dist/assets/min-BGYgWBoZ.js +0 -1
- package/dist/assets/ordinal-Cboi1Yqb.js +0 -1
- package/dist/assets/pieDiagram-ADFJNKIX-EEa08coZ.js +0 -30
- package/dist/assets/sankeyDiagram-TZEHDZUN-awXHtkWF.js +0 -10
- package/dist/assets/stateDiagram-FKZM4ZOC-vNzQUjNr.js +0 -1
- package/dist/assets/stateDiagram-v2-4FDKWEC3-D7uk1o7q.js +0 -1
- package/dist/assets/xychartDiagram-PRI3JC2R-DaoAFPdZ.js +0 -7
|
@@ -1,34 +1,52 @@
|
|
|
1
1
|
import { createUITool, ToolManager } from "@langgraph-js/sdk";
|
|
2
2
|
import Form, { IChangeEvent } from "@rjsf/core";
|
|
3
3
|
import ErrorBoundary from "../components/ErrorBoundary";
|
|
4
|
-
import { FileText, Eye, EyeOff, ChevronDown, X, Plus,
|
|
4
|
+
import { FileText, Eye, EyeOff, ChevronDown, X, Plus, CheckCircle, AlertCircle } from "lucide-react";
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
import validator from "@rjsf/validator-ajv8";
|
|
7
7
|
import { useState } from "react";
|
|
8
|
+
import { Button } from "@/components/ui/button";
|
|
9
|
+
import { cn } from "@/lib/utils";
|
|
10
|
+
|
|
11
|
+
// 错误信息显示组件
|
|
12
|
+
const ErrorMessage = ({ errors }: { errors?: string[] }) => {
|
|
13
|
+
if (!errors || errors.length === 0) return null;
|
|
14
|
+
return (
|
|
15
|
+
<div className="flex items-start gap-1.5 mt-1">
|
|
16
|
+
<AlertCircle className="w-3.5 h-3.5 text-red-500 shrink-0 mt-0.5" />
|
|
17
|
+
<span className="text-xs text-red-600">{errors[0]}</span>
|
|
18
|
+
</div>
|
|
19
|
+
);
|
|
20
|
+
};
|
|
8
21
|
|
|
9
22
|
// 自定义文本输入组件
|
|
10
23
|
const CustomTextWidget = (props: any) => {
|
|
11
24
|
const hasMinLength = props.options?.minLength;
|
|
12
25
|
const hasMaxLength = props.options?.maxLength;
|
|
13
26
|
const currentLength = props.value?.length || 0;
|
|
27
|
+
const hasError = props.rawErrors && props.rawErrors.length > 0;
|
|
14
28
|
|
|
15
29
|
return (
|
|
16
30
|
<div className="mb-4">
|
|
17
31
|
{props.label && (
|
|
18
|
-
<label className="block text-sm font-medium text-gray-
|
|
32
|
+
<label className="block text-sm font-medium text-gray-900 mb-1">
|
|
19
33
|
{props.label}
|
|
20
34
|
{props.required && <span className="text-red-500 ml-1">*</span>}
|
|
21
35
|
</label>
|
|
22
36
|
)}
|
|
23
37
|
<input
|
|
24
38
|
type="text"
|
|
25
|
-
className=
|
|
39
|
+
className={cn(
|
|
40
|
+
"w-full rounded-lg border bg-gray-50/50 p-2 text-sm text-gray-900 placeholder:text-gray-400 focus:outline-none focus:ring-2 transition-all",
|
|
41
|
+
hasError ? "border-red-500 focus:ring-red-100 focus:border-red-500" : "border-gray-200 focus:ring-blue-100 focus:border-blue-400"
|
|
42
|
+
)}
|
|
26
43
|
value={props.value || ""}
|
|
27
44
|
onChange={(e) => props.onChange(e.target.value)}
|
|
28
45
|
placeholder={props.placeholder}
|
|
29
46
|
disabled={props.disabled}
|
|
30
47
|
maxLength={hasMaxLength ? props.options.maxLength : undefined}
|
|
31
48
|
/>
|
|
49
|
+
<ErrorMessage errors={props.rawErrors} />
|
|
32
50
|
{props.description && <p className="text-xs text-gray-500 mt-1">{props.description}</p>}
|
|
33
51
|
{(hasMinLength || hasMaxLength) && (
|
|
34
52
|
<div className="flex justify-end mt-1">
|
|
@@ -48,11 +66,12 @@ const CustomTextWidget = (props: any) => {
|
|
|
48
66
|
// 自定义密码输入组件
|
|
49
67
|
const CustomPasswordWidget = (props: any) => {
|
|
50
68
|
const [showPassword, setShowPassword] = useState(false);
|
|
69
|
+
const hasError = props.rawErrors && props.rawErrors.length > 0;
|
|
51
70
|
|
|
52
71
|
return (
|
|
53
72
|
<div className="mb-4">
|
|
54
73
|
{props.label && (
|
|
55
|
-
<label className="block text-sm font-medium text-gray-
|
|
74
|
+
<label className="block text-sm font-medium text-gray-900 mb-1">
|
|
56
75
|
{props.label}
|
|
57
76
|
{props.required && <span className="text-red-500 ml-1">*</span>}
|
|
58
77
|
</label>
|
|
@@ -60,7 +79,10 @@ const CustomPasswordWidget = (props: any) => {
|
|
|
60
79
|
<div className="relative">
|
|
61
80
|
<input
|
|
62
81
|
type={showPassword ? "text" : "password"}
|
|
63
|
-
className=
|
|
82
|
+
className={cn(
|
|
83
|
+
"w-full rounded-lg border bg-gray-50/50 p-2 pr-10 text-sm text-gray-900 placeholder:text-gray-400 focus:outline-none focus:ring-2 transition-all",
|
|
84
|
+
hasError ? "border-red-500 focus:ring-red-100 focus:border-red-500" : "border-gray-200 focus:ring-blue-100 focus:border-blue-400"
|
|
85
|
+
)}
|
|
64
86
|
value={props.value || ""}
|
|
65
87
|
onChange={(e) => props.onChange(e.target.value)}
|
|
66
88
|
placeholder={props.placeholder}
|
|
@@ -70,6 +92,7 @@ const CustomPasswordWidget = (props: any) => {
|
|
|
70
92
|
{showPassword ? <EyeOff size={16} /> : <Eye size={16} />}
|
|
71
93
|
</button>
|
|
72
94
|
</div>
|
|
95
|
+
<ErrorMessage errors={props.rawErrors} />
|
|
73
96
|
{props.description && <p className="text-xs text-gray-500 mt-1">{props.description}</p>}
|
|
74
97
|
</div>
|
|
75
98
|
);
|
|
@@ -80,17 +103,21 @@ const CustomTextareaWidget = (props: any) => {
|
|
|
80
103
|
const hasMinLength = props.options?.minLength;
|
|
81
104
|
const hasMaxLength = props.options?.maxLength;
|
|
82
105
|
const currentLength = props.value?.length || 0;
|
|
106
|
+
const hasError = props.rawErrors && props.rawErrors.length > 0;
|
|
83
107
|
|
|
84
108
|
return (
|
|
85
109
|
<div className="mb-4">
|
|
86
110
|
{props.label && (
|
|
87
|
-
<label className="block text-sm font-medium text-gray-
|
|
111
|
+
<label className="block text-sm font-medium text-gray-900 mb-1">
|
|
88
112
|
{props.label}
|
|
89
113
|
{props.required && <span className="text-red-500 ml-1">*</span>}
|
|
90
114
|
</label>
|
|
91
115
|
)}
|
|
92
116
|
<textarea
|
|
93
|
-
className=
|
|
117
|
+
className={cn(
|
|
118
|
+
"w-full rounded-lg border bg-gray-50/50 p-2 text-sm text-gray-900 placeholder:text-gray-400 focus:outline-none focus:ring-2 transition-all resize-none",
|
|
119
|
+
hasError ? "border-red-500 focus:ring-red-100 focus:border-red-500" : "border-gray-200 focus:ring-blue-100 focus:border-blue-400"
|
|
120
|
+
)}
|
|
94
121
|
value={props.value || ""}
|
|
95
122
|
onChange={(e) => props.onChange(e.target.value)}
|
|
96
123
|
placeholder={props.placeholder}
|
|
@@ -98,6 +125,7 @@ const CustomTextareaWidget = (props: any) => {
|
|
|
98
125
|
rows={props.options?.rows || 4}
|
|
99
126
|
maxLength={hasMaxLength ? props.options.maxLength : undefined}
|
|
100
127
|
/>
|
|
128
|
+
<ErrorMessage errors={props.rawErrors} />
|
|
101
129
|
{props.description && <p className="text-xs text-gray-500 mt-1">{props.description}</p>}
|
|
102
130
|
{(hasMinLength || hasMaxLength) && (
|
|
103
131
|
<div className="flex justify-end mt-1">
|
|
@@ -116,17 +144,22 @@ const CustomTextareaWidget = (props: any) => {
|
|
|
116
144
|
|
|
117
145
|
// 自定义选择框组件
|
|
118
146
|
const CustomSelectWidget = (props: any) => {
|
|
147
|
+
const hasError = props.rawErrors && props.rawErrors.length > 0;
|
|
148
|
+
|
|
119
149
|
return (
|
|
120
150
|
<div className="mb-4">
|
|
121
151
|
{props.label && (
|
|
122
|
-
<label className="block text-sm font-medium text-gray-
|
|
152
|
+
<label className="block text-sm font-medium text-gray-900 mb-1">
|
|
123
153
|
{props.label}
|
|
124
154
|
{props.required && <span className="text-red-500 ml-1">*</span>}
|
|
125
155
|
</label>
|
|
126
156
|
)}
|
|
127
157
|
<div className="relative">
|
|
128
158
|
<select
|
|
129
|
-
className=
|
|
159
|
+
className={cn(
|
|
160
|
+
"w-full rounded-lg border bg-gray-50/50 p-2 pr-10 text-sm text-gray-900 focus:outline-none focus:ring-2 transition-all appearance-none",
|
|
161
|
+
hasError ? "border-red-500 focus:ring-red-100 focus:border-red-500" : "border-gray-200 focus:ring-blue-100 focus:border-blue-400"
|
|
162
|
+
)}
|
|
130
163
|
value={props.value || ""}
|
|
131
164
|
onChange={(e) => props.onChange(e.target.value)}
|
|
132
165
|
disabled={props.disabled}
|
|
@@ -144,6 +177,7 @@ const CustomSelectWidget = (props: any) => {
|
|
|
144
177
|
</select>
|
|
145
178
|
<ChevronDown size={16} className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 pointer-events-none" />
|
|
146
179
|
</div>
|
|
180
|
+
<ErrorMessage errors={props.rawErrors} />
|
|
147
181
|
{props.description && <p className="text-xs text-gray-500 mt-1">{props.description}</p>}
|
|
148
182
|
</div>
|
|
149
183
|
);
|
|
@@ -151,15 +185,17 @@ const CustomSelectWidget = (props: any) => {
|
|
|
151
185
|
|
|
152
186
|
// 自定义多选框组件
|
|
153
187
|
const CustomCheckboxWidget = (props: any) => {
|
|
188
|
+
const hasError = props.rawErrors && props.rawErrors.length > 0;
|
|
189
|
+
|
|
154
190
|
return (
|
|
155
191
|
<div className="mb-4">
|
|
156
192
|
{props.label && (
|
|
157
|
-
<label className="block text-sm font-medium text-gray-
|
|
193
|
+
<label className="block text-sm font-medium text-gray-900 mb-2">
|
|
158
194
|
{props.label}
|
|
159
195
|
{props.required && <span className="text-red-500 ml-1">*</span>}
|
|
160
196
|
</label>
|
|
161
197
|
)}
|
|
162
|
-
<div className="space-y-2">
|
|
198
|
+
<div className={cn("space-y-2", hasError && "p-2 border border-red-200 rounded-lg bg-red-50/30")}>
|
|
163
199
|
{props.options?.enumOptions?.map((option: any, index: number) => (
|
|
164
200
|
<label key={index} className="flex items-center">
|
|
165
201
|
<input
|
|
@@ -180,6 +216,7 @@ const CustomCheckboxWidget = (props: any) => {
|
|
|
180
216
|
</label>
|
|
181
217
|
))}
|
|
182
218
|
</div>
|
|
219
|
+
<ErrorMessage errors={props.rawErrors} />
|
|
183
220
|
{props.description && <p className="text-xs text-gray-500 mt-1">{props.description}</p>}
|
|
184
221
|
</div>
|
|
185
222
|
);
|
|
@@ -187,15 +224,17 @@ const CustomCheckboxWidget = (props: any) => {
|
|
|
187
224
|
|
|
188
225
|
// 自定义单选按钮组件
|
|
189
226
|
const CustomRadioWidget = (props: any) => {
|
|
227
|
+
const hasError = props.rawErrors && props.rawErrors.length > 0;
|
|
228
|
+
|
|
190
229
|
return (
|
|
191
230
|
<div className="mb-4">
|
|
192
231
|
{props.label && (
|
|
193
|
-
<label className="block text-sm font-medium text-gray-
|
|
232
|
+
<label className="block text-sm font-medium text-gray-900 mb-2">
|
|
194
233
|
{props.label}
|
|
195
234
|
{props.required && <span className="text-red-500 ml-1">*</span>}
|
|
196
235
|
</label>
|
|
197
236
|
)}
|
|
198
|
-
<div className="space-y-2">
|
|
237
|
+
<div className={cn("space-y-2", hasError && "p-2 border border-red-200 rounded-lg bg-red-50/30")}>
|
|
199
238
|
{props.options?.enumOptions?.map((option: any, index: number) => (
|
|
200
239
|
<label key={index} className="flex items-center">
|
|
201
240
|
<input
|
|
@@ -211,6 +250,7 @@ const CustomRadioWidget = (props: any) => {
|
|
|
211
250
|
</label>
|
|
212
251
|
))}
|
|
213
252
|
</div>
|
|
253
|
+
<ErrorMessage errors={props.rawErrors} />
|
|
214
254
|
{props.description && <p className="text-xs text-gray-500 mt-1">{props.description}</p>}
|
|
215
255
|
</div>
|
|
216
256
|
);
|
|
@@ -218,17 +258,22 @@ const CustomRadioWidget = (props: any) => {
|
|
|
218
258
|
|
|
219
259
|
// 自定义数字输入组件
|
|
220
260
|
const CustomNumberWidget = (props: any) => {
|
|
261
|
+
const hasError = props.rawErrors && props.rawErrors.length > 0;
|
|
262
|
+
|
|
221
263
|
return (
|
|
222
264
|
<div className="mb-4">
|
|
223
265
|
{props.label && (
|
|
224
|
-
<label className="block text-sm font-medium text-gray-
|
|
266
|
+
<label className="block text-sm font-medium text-gray-900 mb-1">
|
|
225
267
|
{props.label}
|
|
226
268
|
{props.required && <span className="text-red-500 ml-1">*</span>}
|
|
227
269
|
</label>
|
|
228
270
|
)}
|
|
229
271
|
<input
|
|
230
272
|
type="number"
|
|
231
|
-
className=
|
|
273
|
+
className={cn(
|
|
274
|
+
"w-full rounded-lg border bg-gray-50/50 p-2 text-sm text-gray-900 placeholder:text-gray-400 focus:outline-none focus:ring-2 transition-all",
|
|
275
|
+
hasError ? "border-red-500 focus:ring-red-100 focus:border-red-500" : "border-gray-200 focus:ring-blue-100 focus:border-blue-400"
|
|
276
|
+
)}
|
|
232
277
|
value={props.value || ""}
|
|
233
278
|
onChange={(e) => props.onChange(Number(e.target.value))}
|
|
234
279
|
placeholder={props.placeholder}
|
|
@@ -237,6 +282,7 @@ const CustomNumberWidget = (props: any) => {
|
|
|
237
282
|
max={props.options?.max}
|
|
238
283
|
step={props.options?.step}
|
|
239
284
|
/>
|
|
285
|
+
<ErrorMessage errors={props.rawErrors} />
|
|
240
286
|
{props.description && <p className="text-xs text-gray-500 mt-1">{props.description}</p>}
|
|
241
287
|
</div>
|
|
242
288
|
);
|
|
@@ -244,15 +290,17 @@ const CustomNumberWidget = (props: any) => {
|
|
|
244
290
|
|
|
245
291
|
// 自定义滑块组件
|
|
246
292
|
const CustomRangeWidget = (props: any) => {
|
|
293
|
+
const hasError = props.rawErrors && props.rawErrors.length > 0;
|
|
294
|
+
|
|
247
295
|
return (
|
|
248
296
|
<div className="mb-4">
|
|
249
297
|
{props.label && (
|
|
250
|
-
<label className="block text-sm font-medium text-gray-
|
|
298
|
+
<label className="block text-sm font-medium text-gray-900 mb-2">
|
|
251
299
|
{props.label}
|
|
252
300
|
{props.required && <span className="text-red-500 ml-1">*</span>}
|
|
253
301
|
</label>
|
|
254
302
|
)}
|
|
255
|
-
<div className="px-2">
|
|
303
|
+
<div className={cn("px-2", hasError && "p-2 border border-red-200 rounded-lg bg-red-50/30")}>
|
|
256
304
|
<input
|
|
257
305
|
type="range"
|
|
258
306
|
className="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer slider"
|
|
@@ -269,6 +317,7 @@ const CustomRangeWidget = (props: any) => {
|
|
|
269
317
|
<span>{props.options?.max || 100}</span>
|
|
270
318
|
</div>
|
|
271
319
|
</div>
|
|
320
|
+
<ErrorMessage errors={props.rawErrors} />
|
|
272
321
|
{props.description && <p className="text-xs text-gray-500 mt-1">{props.description}</p>}
|
|
273
322
|
</div>
|
|
274
323
|
);
|
|
@@ -276,21 +325,27 @@ const CustomRangeWidget = (props: any) => {
|
|
|
276
325
|
|
|
277
326
|
// 自定义日期选择器组件
|
|
278
327
|
const CustomDateWidget = (props: any) => {
|
|
328
|
+
const hasError = props.rawErrors && props.rawErrors.length > 0;
|
|
329
|
+
|
|
279
330
|
return (
|
|
280
331
|
<div className="mb-4">
|
|
281
332
|
{props.label && (
|
|
282
|
-
<label className="block text-sm font-medium text-gray-
|
|
333
|
+
<label className="block text-sm font-medium text-gray-900 mb-1">
|
|
283
334
|
{props.label}
|
|
284
335
|
{props.required && <span className="text-red-500 ml-1">*</span>}
|
|
285
336
|
</label>
|
|
286
337
|
)}
|
|
287
338
|
<input
|
|
288
339
|
type="date"
|
|
289
|
-
className=
|
|
340
|
+
className={cn(
|
|
341
|
+
"w-full rounded-lg border bg-gray-50/50 p-2 text-sm text-gray-900 focus:outline-none focus:ring-2 transition-all",
|
|
342
|
+
hasError ? "border-red-500 focus:ring-red-100 focus:border-red-500" : "border-gray-200 focus:ring-blue-100 focus:border-blue-400"
|
|
343
|
+
)}
|
|
290
344
|
value={props.value || ""}
|
|
291
345
|
onChange={(e) => props.onChange(e.target.value)}
|
|
292
346
|
disabled={props.disabled}
|
|
293
347
|
/>
|
|
348
|
+
<ErrorMessage errors={props.rawErrors} />
|
|
294
349
|
{props.description && <p className="text-xs text-gray-500 mt-1">{props.description}</p>}
|
|
295
350
|
</div>
|
|
296
351
|
);
|
|
@@ -298,21 +353,27 @@ const CustomDateWidget = (props: any) => {
|
|
|
298
353
|
|
|
299
354
|
// 自定义日期时间选择器组件
|
|
300
355
|
const CustomDateTimeWidget = (props: any) => {
|
|
356
|
+
const hasError = props.rawErrors && props.rawErrors.length > 0;
|
|
357
|
+
|
|
301
358
|
return (
|
|
302
359
|
<div className="mb-4">
|
|
303
360
|
{props.label && (
|
|
304
|
-
<label className="block text-sm font-medium text-gray-
|
|
361
|
+
<label className="block text-sm font-medium text-gray-900 mb-1">
|
|
305
362
|
{props.label}
|
|
306
363
|
{props.required && <span className="text-red-500 ml-1">*</span>}
|
|
307
364
|
</label>
|
|
308
365
|
)}
|
|
309
366
|
<input
|
|
310
367
|
type="datetime-local"
|
|
311
|
-
className=
|
|
368
|
+
className={cn(
|
|
369
|
+
"w-full rounded-lg border bg-gray-50/50 p-2 text-sm text-gray-900 focus:outline-none focus:ring-2 transition-all",
|
|
370
|
+
hasError ? "border-red-500 focus:ring-red-100 focus:border-red-500" : "border-gray-200 focus:ring-blue-100 focus:border-blue-400"
|
|
371
|
+
)}
|
|
312
372
|
value={props.value || ""}
|
|
313
373
|
onChange={(e) => props.onChange(e.target.value)}
|
|
314
374
|
disabled={props.disabled}
|
|
315
375
|
/>
|
|
376
|
+
<ErrorMessage errors={props.rawErrors} />
|
|
316
377
|
{props.description && <p className="text-xs text-gray-500 mt-1">{props.description}</p>}
|
|
317
378
|
</div>
|
|
318
379
|
);
|
|
@@ -320,23 +381,28 @@ const CustomDateTimeWidget = (props: any) => {
|
|
|
320
381
|
|
|
321
382
|
// 自定义布尔值组件
|
|
322
383
|
const CustomBooleanWidget = (props: any) => {
|
|
384
|
+
const hasError = props.rawErrors && props.rawErrors.length > 0;
|
|
385
|
+
|
|
323
386
|
return (
|
|
324
387
|
<div className="mb-4">
|
|
325
|
-
{
|
|
326
|
-
|
|
327
|
-
<
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
388
|
+
<div className={cn(hasError && "p-2 border border-red-200 rounded-lg bg-red-50/30")}>
|
|
389
|
+
{props.label && (
|
|
390
|
+
<label className="flex items-center">
|
|
391
|
+
<input
|
|
392
|
+
type="checkbox"
|
|
393
|
+
className="w-4 h-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500 focus:ring-2 mr-2"
|
|
394
|
+
checked={props.value || false}
|
|
395
|
+
onChange={(e) => props.onChange(e.target.checked)}
|
|
396
|
+
disabled={props.disabled}
|
|
397
|
+
/>
|
|
398
|
+
<span className="text-sm text-gray-700">
|
|
399
|
+
{props.label}
|
|
400
|
+
{props.required && <span className="text-red-500 ml-1">*</span>}
|
|
401
|
+
</span>
|
|
402
|
+
</label>
|
|
403
|
+
)}
|
|
404
|
+
</div>
|
|
405
|
+
<ErrorMessage errors={props.rawErrors} />
|
|
340
406
|
{props.description && <p className="text-xs text-gray-500 mt-1 ml-6">{props.description}</p>}
|
|
341
407
|
</div>
|
|
342
408
|
);
|
|
@@ -344,6 +410,7 @@ const CustomBooleanWidget = (props: any) => {
|
|
|
344
410
|
|
|
345
411
|
// 自定义文件上传组件
|
|
346
412
|
const CustomFileWidget = (props: any) => {
|
|
413
|
+
const hasError = props.rawErrors && props.rawErrors.length > 0;
|
|
347
414
|
const handleFileChange = (e: any) => {
|
|
348
415
|
const files = e.target.files;
|
|
349
416
|
if (props.multiple) {
|
|
@@ -358,19 +425,23 @@ const CustomFileWidget = (props: any) => {
|
|
|
358
425
|
return (
|
|
359
426
|
<div className="mb-4">
|
|
360
427
|
{props.label && (
|
|
361
|
-
<label className="block text-sm font-medium text-gray-
|
|
428
|
+
<label className="block text-sm font-medium text-gray-900 mb-1">
|
|
362
429
|
{props.label}
|
|
363
430
|
{props.required && <span className="text-red-500 ml-1">*</span>}
|
|
364
431
|
</label>
|
|
365
432
|
)}
|
|
366
433
|
<input
|
|
367
434
|
type="file"
|
|
368
|
-
className=
|
|
435
|
+
className={cn(
|
|
436
|
+
"w-full rounded-lg border bg-gray-50/50 p-2 text-sm text-gray-900 focus:outline-none focus:ring-2 transition-all file:mr-3 file:py-1 file:px-3 file:border-0 file:bg-blue-50 file:text-blue-700 file:rounded file:font-medium",
|
|
437
|
+
hasError ? "border-red-500 focus:ring-red-100 focus:border-red-500" : "border-gray-200 focus:ring-blue-100 focus:border-blue-400"
|
|
438
|
+
)}
|
|
369
439
|
onChange={handleFileChange}
|
|
370
440
|
disabled={props.disabled}
|
|
371
441
|
multiple={props.multiple}
|
|
372
442
|
accept={props.accept}
|
|
373
443
|
/>
|
|
444
|
+
<ErrorMessage errors={props.rawErrors} />
|
|
374
445
|
{props.description && <p className="text-xs text-gray-500 mt-1">{props.description}</p>}
|
|
375
446
|
</div>
|
|
376
447
|
);
|
|
@@ -378,15 +449,17 @@ const CustomFileWidget = (props: any) => {
|
|
|
378
449
|
|
|
379
450
|
// 自定义数组字段组件
|
|
380
451
|
const CustomArrayField = (props: any) => {
|
|
452
|
+
const hasError = props.rawErrors && props.rawErrors.length > 0;
|
|
453
|
+
|
|
381
454
|
return (
|
|
382
455
|
<div className="mb-4">
|
|
383
456
|
{props.label && (
|
|
384
|
-
<label className="block text-sm font-medium text-gray-
|
|
457
|
+
<label className="block text-sm font-medium text-gray-900 mb-2">
|
|
385
458
|
{props.label}
|
|
386
459
|
{props.required && <span className="text-red-500 ml-1">*</span>}
|
|
387
460
|
</label>
|
|
388
461
|
)}
|
|
389
|
-
<div className="space-y-2">
|
|
462
|
+
<div className={cn("space-y-2", hasError && "p-2 border border-red-200 rounded-lg bg-red-50/30")}>
|
|
390
463
|
{props.items.map((item: any, index: number) => (
|
|
391
464
|
<div key={index} className="flex items-start gap-2 p-3 border border-gray-200 rounded-lg">
|
|
392
465
|
<div className="flex-1">{item.children}</div>
|
|
@@ -400,6 +473,7 @@ const CustomArrayField = (props: any) => {
|
|
|
400
473
|
添加项目
|
|
401
474
|
</button>
|
|
402
475
|
</div>
|
|
476
|
+
<ErrorMessage errors={props.rawErrors} />
|
|
403
477
|
{props.description && <p className="text-xs text-gray-500 mt-1">{props.description}</p>}
|
|
404
478
|
</div>
|
|
405
479
|
);
|
|
@@ -410,12 +484,12 @@ const CustomFieldTemplate = (props: any) => {
|
|
|
410
484
|
return <div className={`${props.className} mb-4`}>{props.children}</div>;
|
|
411
485
|
};
|
|
412
486
|
|
|
413
|
-
// 自定义对象字段模板
|
|
487
|
+
// 自定义对象字段模板 - 默认竖向排列
|
|
414
488
|
const CustomObjectFieldTemplate = (props: any) => {
|
|
415
489
|
return (
|
|
416
490
|
<div className="mb-4">
|
|
417
491
|
{props.title && <h3 className="text-sm font-medium text-gray-700 mb-2">{props.title}</h3>}
|
|
418
|
-
<div className="
|
|
492
|
+
<div className="space-y-3 pl-4 border-l-2 border-gray-200">
|
|
419
493
|
{props.properties.map((prop: any) => (
|
|
420
494
|
<div key={prop.name}>{prop.content}</div>
|
|
421
495
|
))}
|
|
@@ -428,89 +502,184 @@ const CustomObjectFieldTemplate = (props: any) => {
|
|
|
428
502
|
// 自定义按钮组件
|
|
429
503
|
const CustomSubmitButton = (props: any) => {
|
|
430
504
|
return (
|
|
431
|
-
<
|
|
505
|
+
<Button
|
|
432
506
|
type="submit"
|
|
433
|
-
className="px-4 py-2 rounded-lg bg-blue-500 hover:bg-blue-600 disabled:bg-gray-300 text-white font-medium focus:outline-none focus:ring-2 focus:ring-blue-400 transition-colors disabled:cursor-not-allowed"
|
|
434
507
|
disabled={props.disabled}
|
|
508
|
+
className={cn(
|
|
509
|
+
"px-5 rounded-full transition-all duration-200",
|
|
510
|
+
props.disabled ? "bg-gray-100 text-gray-400" : "bg-blue-600 hover:bg-blue-700 text-white shadow-md hover:shadow-lg hover:shadow-blue-200"
|
|
511
|
+
)}
|
|
435
512
|
>
|
|
436
|
-
{props.submitText || "
|
|
437
|
-
</
|
|
513
|
+
{props.submitText || "提交表单"}
|
|
514
|
+
</Button>
|
|
438
515
|
);
|
|
439
516
|
};
|
|
440
517
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
description: "
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
518
|
+
const AskUserToFillFormSchema = {
|
|
519
|
+
title: z.string().describe("Form title"),
|
|
520
|
+
description: z.string().optional().describe("Optional form description"),
|
|
521
|
+
schema: z.record(z.string(), z.any()).describe("JSON Schema for the form (react-jsonschema-form compatible)"),
|
|
522
|
+
};
|
|
523
|
+
|
|
524
|
+
export const ask_user_to_fill_form = createUITool({
|
|
525
|
+
name: "ask_user_to_fill_form",
|
|
526
|
+
description: "Present a form to the user for filling out. Schema follows react-jsonschema-form format.",
|
|
527
|
+
parameters: AskUserToFillFormSchema,
|
|
447
528
|
onlyRender: false,
|
|
448
529
|
handler: ToolManager.waitForUIDone,
|
|
449
530
|
render(tool) {
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
531
|
+
try {
|
|
532
|
+
const data = tool.getInputRepaired();
|
|
533
|
+
const output = tool.getJSONOutputSafe();
|
|
534
|
+
const formSchema = data?.schema || {};
|
|
535
|
+
const canInteract = tool.state === "interrupted" || tool.state === "loading";
|
|
536
|
+
|
|
537
|
+
const handleSubmit = (formData: any) => {
|
|
538
|
+
try {
|
|
539
|
+
tool.sendResumeData({
|
|
540
|
+
/** @ts-ignore */
|
|
541
|
+
type: "respond",
|
|
542
|
+
message: JSON.stringify(formData),
|
|
543
|
+
});
|
|
544
|
+
} catch (error) {
|
|
545
|
+
console.error("提交表单数据时出错:", error);
|
|
546
|
+
}
|
|
547
|
+
};
|
|
548
|
+
|
|
549
|
+
// 自定义控件配置
|
|
550
|
+
const customWidgets = {
|
|
551
|
+
text: CustomTextWidget,
|
|
552
|
+
password: CustomPasswordWidget,
|
|
553
|
+
textarea: CustomTextareaWidget,
|
|
554
|
+
select: CustomSelectWidget,
|
|
555
|
+
checkboxes: CustomCheckboxWidget,
|
|
556
|
+
radio: CustomRadioWidget,
|
|
557
|
+
number: CustomNumberWidget,
|
|
558
|
+
range: CustomRangeWidget,
|
|
559
|
+
date: CustomDateWidget,
|
|
560
|
+
datetime: CustomDateTimeWidget,
|
|
561
|
+
file: CustomFileWidget,
|
|
562
|
+
boolean: CustomBooleanWidget,
|
|
563
|
+
};
|
|
564
|
+
|
|
565
|
+
const customFields = {
|
|
566
|
+
array: CustomArrayField,
|
|
567
|
+
};
|
|
568
|
+
|
|
569
|
+
const customTemplates = {
|
|
570
|
+
FieldTemplate: CustomFieldTemplate,
|
|
571
|
+
ObjectFieldTemplate: CustomObjectFieldTemplate,
|
|
572
|
+
ButtonTemplates: {
|
|
573
|
+
SubmitButton: CustomSubmitButton,
|
|
574
|
+
},
|
|
575
|
+
};
|
|
576
|
+
|
|
577
|
+
// 完成状态视图
|
|
578
|
+
if (!canInteract && tool.output) {
|
|
579
|
+
let submittedData;
|
|
580
|
+
try {
|
|
581
|
+
submittedData = typeof tool.output === "string" ? JSON.parse(tool.output) : tool.output;
|
|
582
|
+
} catch (error) {
|
|
583
|
+
submittedData = tool.output;
|
|
584
|
+
}
|
|
585
|
+
return (
|
|
586
|
+
<div className="flex flex-col gap-2 my-1 p-3 bg-gray-50/50 border border-gray-200 rounded-xl">
|
|
587
|
+
<div className="flex items-center gap-2 text-xs text-gray-500">
|
|
588
|
+
<div className="w-5 h-5 rounded-full bg-gray-100 flex items-center justify-center border border-gray-200">
|
|
589
|
+
<CheckCircle className="w-3 h-3" />
|
|
590
|
+
</div>
|
|
591
|
+
<span className="font-medium">Form Submitted</span>
|
|
592
|
+
</div>
|
|
593
|
+
|
|
594
|
+
<div className="space-y-2">
|
|
595
|
+
<div className="text-sm text-gray-600 pl-1">{data?.title || "表单"}</div>
|
|
596
|
+
<div className="text-sm text-gray-900 bg-white px-3 py-2.5 rounded-xl border border-gray-200">
|
|
597
|
+
<pre className="text-xs whitespace-pre-wrap break-all">{JSON.stringify(submittedData, null, 2)}</pre>
|
|
598
|
+
</div>
|
|
599
|
+
</div>
|
|
600
|
+
</div>
|
|
601
|
+
);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// 交互状态视图
|
|
605
|
+
return (
|
|
492
606
|
<ErrorBoundary>
|
|
493
|
-
<
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
widgets={customWidgets}
|
|
504
|
-
fields={customFields}
|
|
505
|
-
templates={customTemplates}
|
|
506
|
-
className="custom-form"
|
|
507
|
-
>
|
|
508
|
-
<div className="flex gap-2 mt-4">
|
|
509
|
-
<CustomSubmitButton />
|
|
607
|
+
<div className="w-[70%] my-1 border border-gray-200 bg-white shadow-none rounded-xl overflow-hidden">
|
|
608
|
+
<div className="pb-2 p-3 border-b border-gray-50 bg-gray-50/30">
|
|
609
|
+
<div className="flex items-center justify-between">
|
|
610
|
+
<div className="flex items-center gap-2">
|
|
611
|
+
<div className="w-7 h-7 rounded-full bg-blue-50 text-blue-600 flex items-center justify-center">
|
|
612
|
+
<FileText className="w-3.5 h-3.5" />
|
|
613
|
+
</div>
|
|
614
|
+
<h3 className="text-sm font-medium text-gray-900">{data?.title || "表单"}</h3>
|
|
615
|
+
</div>
|
|
616
|
+
</div>
|
|
510
617
|
</div>
|
|
511
|
-
|
|
618
|
+
<div className="p-3 space-y-3">
|
|
619
|
+
{data?.description && <div className="text-sm font-medium text-gray-900">{data.description}</div>}
|
|
620
|
+
<ErrorBoundary>
|
|
621
|
+
<Form
|
|
622
|
+
readonly={!canInteract}
|
|
623
|
+
schema={formSchema}
|
|
624
|
+
formData={output}
|
|
625
|
+
onSubmit={(formData: IChangeEvent<any>) => {
|
|
626
|
+
try {
|
|
627
|
+
handleSubmit(formData.formData);
|
|
628
|
+
} catch (error) {
|
|
629
|
+
console.error("表单提交处理错误:", error);
|
|
630
|
+
}
|
|
631
|
+
}}
|
|
632
|
+
validator={validator}
|
|
633
|
+
onError={(errors) => {
|
|
634
|
+
console.error("表单校验错误:", errors);
|
|
635
|
+
}}
|
|
636
|
+
widgets={customWidgets}
|
|
637
|
+
fields={customFields}
|
|
638
|
+
templates={customTemplates}
|
|
639
|
+
className="custom-form"
|
|
640
|
+
>
|
|
641
|
+
<div className="flex justify-end pt-2">
|
|
642
|
+
<CustomSubmitButton disabled={!canInteract} />
|
|
643
|
+
</div>
|
|
644
|
+
</Form>
|
|
645
|
+
</ErrorBoundary>
|
|
646
|
+
</div>
|
|
647
|
+
</div>
|
|
512
648
|
</ErrorBoundary>
|
|
513
|
-
|
|
514
|
-
)
|
|
649
|
+
);
|
|
650
|
+
} catch (error) {
|
|
651
|
+
// 外层错误边界,捕获所有未处理的错误
|
|
652
|
+
console.error("表单组件渲染错误:", error);
|
|
653
|
+
const errorMessage = error instanceof Error ? error.message : "表单配置有误,请检查 schema 格式是否正确。";
|
|
654
|
+
|
|
655
|
+
// 如果可以交互,直接将错误响应到远端
|
|
656
|
+
const canInteract = tool.state === "interrupted" || tool.state === "loading";
|
|
657
|
+
if (canInteract) {
|
|
658
|
+
try {
|
|
659
|
+
tool.sendResumeData({
|
|
660
|
+
/** @ts-ignore */
|
|
661
|
+
type: "respond",
|
|
662
|
+
message: JSON.stringify({
|
|
663
|
+
error: "表单加载失败",
|
|
664
|
+
message: errorMessage,
|
|
665
|
+
}),
|
|
666
|
+
});
|
|
667
|
+
} catch (sendError) {
|
|
668
|
+
console.error("发送错误响应失败:", sendError);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
return (
|
|
673
|
+
<div className="w-full my-1 border border-red-200 bg-red-50 rounded-xl overflow-hidden">
|
|
674
|
+
<div className="p-4">
|
|
675
|
+
<div className="flex items-center gap-2 mb-2">
|
|
676
|
+
<AlertCircle className="w-5 h-5 text-red-600" />
|
|
677
|
+
<h3 className="text-sm font-medium text-red-900">表单加载失败</h3>
|
|
678
|
+
</div>
|
|
679
|
+
<p className="text-xs text-red-700">{errorMessage}</p>
|
|
680
|
+
</div>
|
|
681
|
+
</div>
|
|
682
|
+
);
|
|
683
|
+
}
|
|
515
684
|
},
|
|
516
685
|
});
|