@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.
Files changed (80) hide show
  1. package/dist/assets/arc-BN7eXDsr.js +1 -0
  2. package/dist/assets/architectureDiagram-VXUJARFQ-BlClJNg_.js +36 -0
  3. package/dist/assets/{blockDiagram-VD42YOAC-JpCtx8Fw.js → blockDiagram-VD42YOAC-B1yxKMBm.js} +4 -4
  4. package/dist/assets/{c4Diagram-YG6GDRKO-Ct0YUG6f.js → c4Diagram-YG6GDRKO-DYnk7yK1.js} +1 -1
  5. package/dist/assets/channel-W05vnIRi.js +1 -0
  6. package/dist/assets/{chunk-4BX2VUAB-O9tuPwJ2.js → chunk-4BX2VUAB-OBKPWQYL.js} +1 -1
  7. package/dist/assets/{chunk-55IACEB6-B1LOZAa5.js → chunk-55IACEB6-gYWv8Zf5.js} +1 -1
  8. package/dist/assets/{chunk-B4BG7PRW-CHB7g7y9.js → chunk-B4BG7PRW-C53GIfTN.js} +1 -1
  9. package/dist/assets/{chunk-DI55MBZ5-BZe7PkXl.js → chunk-DI55MBZ5-VE97R82T.js} +1 -1
  10. package/dist/assets/{chunk-FMBD7UC4-IV4Fy30-.js → chunk-FMBD7UC4-1vi-TjGz.js} +1 -1
  11. package/dist/assets/{chunk-QN33PNHL-DCcmoYki.js → chunk-QN33PNHL-BIwuKx1J.js} +1 -1
  12. package/dist/assets/{chunk-QZHKN3VN-BNYpxk7b.js → chunk-QZHKN3VN-voU2PjMs.js} +1 -1
  13. package/dist/assets/{chunk-TZMSLE5B-BgP3duP5.js → chunk-TZMSLE5B-DqFqeo6j.js} +1 -1
  14. package/dist/assets/classDiagram-2ON5EDUG-BAA0T-HR.js +1 -0
  15. package/dist/assets/classDiagram-v2-WZHVMYZB-BAA0T-HR.js +1 -0
  16. package/dist/assets/clone-BnfE7gLV.js +1 -0
  17. package/dist/assets/{cose-bilkent-S5V4N54A-B_7kmvVh.js → cose-bilkent-S5V4N54A-BG8aPbwR.js} +1 -1
  18. package/dist/assets/{dagre-6UL2VRFP-CAxkUiea.js → dagre-6UL2VRFP-B2SQQldX.js} +2 -2
  19. package/dist/assets/diagram-PSM6KHXK-DPT_FKfu.js +24 -0
  20. package/dist/assets/diagram-QEK2KX5R-BSIuCy1e.js +43 -0
  21. package/dist/assets/{diagram-S2PKOQOG-CZOKfwOI.js → diagram-S2PKOQOG-DAQpMvAq.js} +1 -1
  22. package/dist/assets/{erDiagram-Q2GNP2WA-D9v3M4EU.js → erDiagram-Q2GNP2WA-gKwnjOTf.js} +1 -1
  23. package/dist/assets/{flowDiagram-NV44I4VS-B9qnS72A.js → flowDiagram-NV44I4VS-DYhT5qks.js} +1 -1
  24. package/dist/assets/ganttDiagram-LVOFAZNH-WboqEMaA.js +267 -0
  25. package/dist/assets/{gitGraphDiagram-NY62KEGX-Cm36kSO3.js → gitGraphDiagram-NY62KEGX-BRtqtCxN.js} +1 -1
  26. package/dist/assets/{graph-C2p3i6RT.js → graph-BaHCYR4Y.js} +1 -1
  27. package/dist/assets/index-JVKUnJL6.css +1 -0
  28. package/dist/assets/{index-Cf3jkaWJ.js → index-yx5a2qjD.js} +312 -173
  29. package/dist/assets/{infoDiagram-F6ZHWCRC-DkBxmgNt.js → infoDiagram-F6ZHWCRC-DyYvFs8H.js} +1 -1
  30. package/dist/assets/isUndefined-CuNi_bm9.js +1 -0
  31. package/dist/assets/{journeyDiagram-XKPGCS4Q-CVukoP-j.js → journeyDiagram-XKPGCS4Q-BqtfhCdd.js} +1 -1
  32. package/dist/assets/{kanban-definition-3W4ZIXB7-0-SbO6tS.js → kanban-definition-3W4ZIXB7-CyDmlWOW.js} +4 -4
  33. package/dist/assets/layout-higHf58s.js +1 -0
  34. package/dist/assets/mermaid.core-3MG272Pv.js +197 -0
  35. package/dist/assets/min-BoUtT0GV.js +1 -0
  36. package/dist/assets/{mindmap-definition-VGOIOE7T-CDGdm5yw.js → mindmap-definition-VGOIOE7T-Y0yyOiIO.js} +5 -5
  37. package/dist/assets/pieDiagram-ADFJNKIX-D_nzBY3u.js +30 -0
  38. package/dist/assets/{quadrantDiagram-AYHSOK5B-DY0NltyG.js → quadrantDiagram-AYHSOK5B-CUBZfINn.js} +2 -2
  39. package/dist/assets/{requirementDiagram-UZGBJVZJ-DwhwO5lc.js → requirementDiagram-UZGBJVZJ-kfnK1mwU.js} +1 -1
  40. package/dist/assets/sankeyDiagram-TZEHDZUN-DALezZLf.js +10 -0
  41. package/dist/assets/{sequenceDiagram-WL72ISMW-CpTIKJGg.js → sequenceDiagram-WL72ISMW-Ph3PSHlU.js} +1 -1
  42. package/dist/assets/stateDiagram-FKZM4ZOC-BjIg4oDx.js +1 -0
  43. package/dist/assets/stateDiagram-v2-4FDKWEC3-nRC6pSc8.js +1 -0
  44. package/dist/assets/{timeline-definition-IT6M3QCI-1MkGCcpM.js → timeline-definition-IT6M3QCI-Cra1vVbB.js} +3 -3
  45. package/dist/assets/{treemap-KMMF4GRG-pRPfKbhX.js → treemap-KMMF4GRG-DihM0GUo.js} +1 -1
  46. package/dist/assets/xychartDiagram-PRI3JC2R-PhzqQQLa.js +7 -0
  47. package/dist/index.html +2 -2
  48. package/package.json +3 -2
  49. package/src/chat/Chat.tsx +3 -4
  50. package/src/chat/components/ErrorBoundary.tsx +23 -10
  51. package/src/chat/tools/{show_form.tsx → ask_user_to_fill_form.tsx} +283 -114
  52. package/src/chat/tools/ask_user_with_options.tsx +174 -0
  53. package/src/chat/tools/display_information_card.tsx +131 -0
  54. package/src/chat/tools/image_generation.tsx +140 -0
  55. package/src/chat/tools/index.ts +18 -6
  56. package/src/chat/tools/visualize_data_with_chart.tsx +274 -0
  57. package/src/chat/tools/wait_for_user_to_upload_file.tsx +324 -0
  58. package/dist/assets/arc-BNOCe7pe.js +0 -1
  59. package/dist/assets/architectureDiagram-VXUJARFQ-yf1p-wI_.js +0 -36
  60. package/dist/assets/channel-CizfE02g.js +0 -1
  61. package/dist/assets/classDiagram-2ON5EDUG-CFe5Iz8b.js +0 -1
  62. package/dist/assets/classDiagram-v2-WZHVMYZB-CFe5Iz8b.js +0 -1
  63. package/dist/assets/clone-C4H89Tb1.js +0 -1
  64. package/dist/assets/defaultLocale-C4B-KCzX.js +0 -1
  65. package/dist/assets/diagram-PSM6KHXK-BeM-KUrT.js +0 -24
  66. package/dist/assets/diagram-QEK2KX5R-DDl_pKlU.js +0 -43
  67. package/dist/assets/ganttDiagram-LVOFAZNH-C1Hjglog.js +0 -267
  68. package/dist/assets/index-DLwWU25h.css +0 -1
  69. package/dist/assets/init-Gi6I4Gst.js +0 -1
  70. package/dist/assets/isUndefined-DhAvAp_K.js +0 -1
  71. package/dist/assets/layout-fJUHcXGp.js +0 -1
  72. package/dist/assets/linear-RxTDErCI.js +0 -1
  73. package/dist/assets/mermaid.core-Y2zCEMvG.js +0 -197
  74. package/dist/assets/min-BGYgWBoZ.js +0 -1
  75. package/dist/assets/ordinal-Cboi1Yqb.js +0 -1
  76. package/dist/assets/pieDiagram-ADFJNKIX-EEa08coZ.js +0 -30
  77. package/dist/assets/sankeyDiagram-TZEHDZUN-awXHtkWF.js +0 -10
  78. package/dist/assets/stateDiagram-FKZM4ZOC-vNzQUjNr.js +0 -1
  79. package/dist/assets/stateDiagram-v2-4FDKWEC3-D7uk1o7q.js +0 -1
  80. package/dist/assets/xychartDiagram-PRI3JC2R-DaoAFPdZ.js +0 -7
@@ -0,0 +1,174 @@
1
+ import { createUITool, ToolManager } from "@langgraph-js/sdk";
2
+ import { useState } from "react";
3
+ import { z } from "zod";
4
+ import { ListChecks, MessageSquarePlus, RefreshCcw, Check, User } from "lucide-react";
5
+ import { Badge } from "@/components/ui/badge";
6
+ import { Button } from "@/components/ui/button";
7
+ import { cn } from "@/lib/utils";
8
+
9
+ const AskUserWithOptionsSchema = {
10
+ description: z.string().describe("Question text to display"),
11
+ type: z.enum(["single_select", "multi_select"]).optional().default("single_select").describe("Selection mode for this question"),
12
+ options: z
13
+ .array(
14
+ z.object({
15
+ index: z.number().describe("Index of the option"),
16
+ label: z.string().describe("Optional display label"),
17
+ })
18
+ )
19
+ .describe("Selectable options for the question"),
20
+ allow_custom_input: z.boolean().default(true).describe("Allow user to input custom text"),
21
+ };
22
+
23
+ export const ask_user_with_options = createUITool({
24
+ name: "ask_user_with_options",
25
+ description: "Render a single question with selectable options and optional custom input.",
26
+ parameters: AskUserWithOptionsSchema,
27
+ handler: ToolManager.waitForUIDone,
28
+ onlyRender: false,
29
+ render(tool) {
30
+ const data = tool.getInputRepaired();
31
+ const optionItems = data.options || [];
32
+ const isMulti = data.type === "multi_select";
33
+ const [selected, setSelected] = useState<number[]>([]);
34
+ const [customText, setCustomText] = useState<string>("");
35
+
36
+ const toggleOption = (idx: number) => {
37
+ if (isMulti) {
38
+ setSelected((prev) => (prev.includes(idx) ? prev.filter((i) => i !== idx) : [...prev, idx]));
39
+ } else {
40
+ setSelected([idx]);
41
+ }
42
+ };
43
+
44
+ const handleReset = () => {
45
+ setSelected([]);
46
+ setCustomText("");
47
+ };
48
+
49
+ const canInteract = tool.state === "interrupted";
50
+
51
+ const handleSubmit = () => {
52
+ const selectedLabels = selected.map((i) => optionItems[i]?.label).filter((i) => i !== undefined);
53
+ const customTextLabel = customText.trim() ? `, Custom Text: ${customText.trim()}` : "";
54
+ tool.sendResumeData({
55
+ /** @ts-ignore */
56
+ type: "respond",
57
+ message: `User Selected: ${selected.length > 0 ? selectedLabels.join(", ") : "none"} ${customTextLabel}`,
58
+ });
59
+ };
60
+
61
+ const isSubmitDisabled = !canInteract || (selected.length === 0 && (!data.allow_custom_input || !customText));
62
+
63
+ // 完成状态视图
64
+ if (!canInteract) {
65
+ return (
66
+ <div className="flex flex-col gap-2 my-1 p-3 bg-gray-50/50 border border-gray-200 rounded-xl">
67
+ <div className="flex items-center gap-2 text-xs text-gray-500">
68
+ <div className="w-5 h-5 rounded-full bg-gray-100 flex items-center justify-center border border-gray-200">
69
+ <ListChecks className="w-3 h-3" />
70
+ </div>
71
+ <span className="font-medium">Question Answered</span>
72
+ <Button variant="ghost" size="icon" className="h-7 w-7 text-gray-500 hover:text-blue-600 hover:bg-blue-50 rounded-full" onClick={handleReset} title="Reset selection">
73
+ <RefreshCcw className="h-3.5 w-3.5" />
74
+ </Button>
75
+ </div>
76
+
77
+ <div className="space-y-1">
78
+ <div className="text-sm text-gray-600 pl-1">{data.description}</div>
79
+ <div className="flex items-start gap-2 text-sm text-gray-900 bg-white px-3 py-2.5 rounded-xl border border-gray-200">
80
+ <User className="w-4 h-4 mt-0.5 text-blue-500 shrink-0" />
81
+ <div className="break-all leading-relaxed">{tool.output ? (typeof tool.output === "string" ? tool.output : JSON.stringify(tool.output)) : "Response submitted"}</div>
82
+ </div>
83
+ </div>
84
+ </div>
85
+ );
86
+ }
87
+
88
+ // 交互状态视图
89
+ return (
90
+ <div className="w-full my-1 border border-gray-200 bg-white shadow-none rounded-xl overflow-hidden">
91
+ <div className="pb-2 p-3 border-b border-gray-50 bg-gray-50/30">
92
+ <div className="flex items-center justify-between">
93
+ <div className="flex items-center gap-2">
94
+ <div className="w-7 h-7 rounded-full bg-blue-50 text-blue-600 flex items-center justify-center">
95
+ <ListChecks className="w-3.5 h-3.5" />
96
+ </div>
97
+ <h3 className="text-sm font-medium text-gray-900">User Input Required</h3>
98
+ </div>
99
+ <div className="flex items-center gap-2">
100
+ <Button variant="ghost" size="icon" className="h-7 w-7 text-gray-500 hover:text-blue-600 hover:bg-blue-50 rounded-full" onClick={handleReset} title="Reset selection">
101
+ <RefreshCcw className="h-3.5 w-3.5" />
102
+ </Button>
103
+ <Badge variant="outline" className="bg-white text-gray-600 border-gray-200 font-normal">
104
+ {isMulti ? "Multi Select" : "Single Select"}
105
+ </Badge>
106
+ </div>
107
+ </div>
108
+ </div>
109
+ <div className="p-3 space-y-3">
110
+ <div className="text-sm font-medium text-gray-900">{data.description}</div>
111
+
112
+ {optionItems.length > 0 && (
113
+ <div className="space-y-1.5">
114
+ <div className="space-y-1.5">
115
+ {optionItems.map((opt: any) => {
116
+ const isSelected = selected.includes(opt.index);
117
+ return (
118
+ <div
119
+ key={opt.index}
120
+ className={cn(
121
+ "flex items-center gap-2 p-2 rounded-lg border transition-all cursor-pointer group",
122
+ isSelected ? "bg-blue-50/50 border-blue-500 shadow-sm" : "bg-white border-gray-200 hover:border-blue-300"
123
+ )}
124
+ onClick={() => toggleOption(opt.index)}
125
+ >
126
+ <div
127
+ className={cn(
128
+ "w-5 h-5 rounded flex items-center justify-center border text-[10px] font-medium transition-colors shrink-0",
129
+ isSelected ? "bg-blue-500 border-blue-500 text-white" : "bg-gray-50 border-gray-200 text-gray-500 group-hover:border-blue-300"
130
+ )}
131
+ >
132
+ {isSelected ? <Check className="w-3 h-3" /> : opt.index}
133
+ </div>
134
+ <span className={cn("text-sm flex-1 leading-snug", isSelected ? "text-blue-900 font-medium" : "text-gray-700")}>{opt.label}</span>
135
+ </div>
136
+ );
137
+ })}
138
+ </div>
139
+ </div>
140
+ )}
141
+
142
+ {data.allow_custom_input && (
143
+ <div className="space-y-1.5 pt-1">
144
+ <div className="flex items-center gap-2 text-[10px] font-medium text-gray-500 uppercase tracking-wider">
145
+ <MessageSquarePlus className="w-3 h-3" />
146
+ <span>Additional Comments</span>
147
+ </div>
148
+ <textarea
149
+ className="w-full rounded-lg border border-gray-200 bg-gray-50/50 p-2 text-sm text-gray-900 placeholder:text-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-100 focus:border-blue-400 transition-all resize-none"
150
+ placeholder="Type your answer here..."
151
+ value={customText}
152
+ onChange={(e) => setCustomText(e.target.value)}
153
+ rows={2}
154
+ />
155
+ </div>
156
+ )}
157
+
158
+ <div className="flex justify-end pt-2">
159
+ <Button
160
+ onClick={handleSubmit}
161
+ disabled={isSubmitDisabled}
162
+ className={cn(
163
+ "px-5 rounded-full transition-all duration-200",
164
+ isSubmitDisabled ? "bg-gray-100 text-gray-400" : "bg-blue-600 hover:bg-blue-700 text-white shadow-md hover:shadow-lg hover:shadow-blue-200"
165
+ )}
166
+ >
167
+ Submit Response
168
+ </Button>
169
+ </div>
170
+ </div>
171
+ </div>
172
+ );
173
+ },
174
+ });
@@ -0,0 +1,131 @@
1
+ import { createUITool } from "@langgraph-js/sdk";
2
+ import { z } from "zod";
3
+ import { Info, CheckCircle, AlertTriangle, XCircle, ExternalLink } from "lucide-react";
4
+ import { Button } from "@/components/ui/button";
5
+ import { Badge } from "@/components/ui/badge";
6
+ import { cn } from "@/lib/utils";
7
+
8
+ const DisplayInformationCardSchema = {
9
+ image_url: z.url().optional().describe("Optional image URL (for info card type)"),
10
+ title: z.string().describe("Card title"),
11
+ content: z.string().describe("Main content text"),
12
+ type: z.enum(["info", "success", "warning", "error"]).optional().default("info").describe("Card style (for info card type)"),
13
+ actions: z
14
+ .array(
15
+ z.object({
16
+ label: z.string().describe("Button label"),
17
+ action_id: z.string().describe("Action identifier"),
18
+ link: z.url().optional(),
19
+ })
20
+ )
21
+ .optional()
22
+ .describe("Action buttons"),
23
+ };
24
+
25
+ const getTypeStyles = (type: "info" | "success" | "warning" | "error") => {
26
+ switch (type) {
27
+ case "success":
28
+ return {
29
+ icon: CheckCircle,
30
+ iconColor: "text-green-600",
31
+ iconBg: "bg-green-50",
32
+ badgeText: "Success",
33
+ badgeVariant: "outline" as const,
34
+ };
35
+ case "warning":
36
+ return {
37
+ icon: AlertTriangle,
38
+ iconColor: "text-yellow-600",
39
+ iconBg: "bg-yellow-50",
40
+ badgeText: "Warning",
41
+ badgeVariant: "outline" as const,
42
+ };
43
+ case "error":
44
+ return {
45
+ icon: XCircle,
46
+ iconColor: "text-red-600",
47
+ iconBg: "bg-red-50",
48
+ badgeText: "Error",
49
+ badgeVariant: "outline" as const,
50
+ };
51
+ case "info":
52
+ default:
53
+ return {
54
+ icon: Info,
55
+ iconColor: "text-blue-600",
56
+ iconBg: "bg-blue-50",
57
+ badgeText: "Info",
58
+ badgeVariant: "outline" as const,
59
+ };
60
+ }
61
+ };
62
+
63
+ export const display_information_card = createUITool({
64
+ name: "display_information_card",
65
+ description: "Display an information card to the user.",
66
+ parameters: DisplayInformationCardSchema,
67
+ onlyRender: true,
68
+ render(tool) {
69
+ const data = tool.getInputRepaired();
70
+ const typeStyles = getTypeStyles(data.type || "info");
71
+ const IconComponent = typeStyles.icon;
72
+ const hasActions = data.actions && data.actions.length > 0;
73
+
74
+ const handleActionClick = (action: { label: string; action_id: string; link?: string }) => {
75
+ if (action.link) {
76
+ window.open(action.link, "_blank", "noopener,noreferrer");
77
+ }
78
+ };
79
+
80
+ return (
81
+ <div className="w-[50%] my-1 border border-gray-200 bg-white shadow-none rounded-xl overflow-hidden">
82
+ <div className="pb-2 p-3 border-b border-gray-50 bg-gray-50/30">
83
+ <div className="flex items-center justify-between">
84
+ <div className="flex items-center gap-2">
85
+ <div className={cn("w-7 h-7 rounded-full flex items-center justify-center", typeStyles.iconBg)}>
86
+ <IconComponent className={cn("w-3.5 h-3.5", typeStyles.iconColor)} />
87
+ </div>
88
+ <h3 className="text-sm font-medium text-gray-900">{data.title}</h3>
89
+ </div>
90
+ <Badge variant={typeStyles.badgeVariant} className="bg-white text-gray-600 border-gray-200 font-normal">
91
+ {typeStyles.badgeText}
92
+ </Badge>
93
+ </div>
94
+ </div>
95
+ <div className="p-3 space-y-3">
96
+ <div className="text-sm leading-relaxed text-gray-700 whitespace-pre-wrap">{data.content}</div>
97
+
98
+ {data.image_url && (
99
+ <div className="rounded-lg overflow-hidden border border-gray-200 bg-gray-50">
100
+ <img src={data.image_url} alt={data.title} className="w-full h-auto object-cover max-h-[400px]" />
101
+ </div>
102
+ )}
103
+
104
+ {hasActions && (
105
+ <div className="flex flex-wrap gap-2 pt-2">
106
+ {data.actions!.map((action, index) => (
107
+ <Button
108
+ key={index}
109
+ onClick={() => handleActionClick(action)}
110
+ className={cn(
111
+ "px-4 py-2 rounded-lg transition-all duration-200 text-sm font-medium",
112
+ data.type === "success"
113
+ ? "bg-green-600 hover:bg-green-700 text-white shadow-sm hover:shadow-md"
114
+ : data.type === "warning"
115
+ ? "bg-yellow-600 hover:bg-yellow-700 text-white shadow-sm hover:shadow-md"
116
+ : data.type === "error"
117
+ ? "bg-red-600 hover:bg-red-700 text-white shadow-sm hover:shadow-md"
118
+ : "bg-blue-600 hover:bg-blue-700 text-white shadow-sm hover:shadow-md"
119
+ )}
120
+ >
121
+ {action.label}
122
+ {action.link && <ExternalLink className="w-3 h-3 ml-1.5" />}
123
+ </Button>
124
+ ))}
125
+ </div>
126
+ )}
127
+ </div>
128
+ </div>
129
+ );
130
+ },
131
+ });
@@ -0,0 +1,140 @@
1
+ import { createUITool } from "@langgraph-js/sdk";
2
+ import { z } from "zod";
3
+ import { Image, Loader2, Sparkles } from "lucide-react";
4
+ import { cn } from "@/lib/utils";
5
+ import { useState, useEffect } from "react";
6
+
7
+ const ImageGenerationSchema = {
8
+ prompt: z.string().describe("prompt description"),
9
+ input_image_urls: z.array(z.string()).optional().describe("input image urls"),
10
+ resolution: z.enum(["1K", "2K", "4K"]).optional().default("1K").describe("image resolution"),
11
+ aspectRatio: z.enum(["21:9", "16:9", "4:3", "3:2", "1:1", "9:16", "3:4", "2:3", "5:4", "4:5"]).optional().default("16:9").describe("image aspect ratio"),
12
+ model: z.string().optional().default("gemini-3-pro-image-preview").describe("model name"),
13
+ };
14
+
15
+ export const image_generation = createUITool({
16
+ name: "image_generation",
17
+ description: "Generate or edit an image.",
18
+ parameters: ImageGenerationSchema,
19
+ onlyRender: true,
20
+ render(tool) {
21
+ const data = tool.getInputRepaired();
22
+ const [imageUrls, setImageUrls] = useState<string[]>([]);
23
+ const [loading, setLoading] = useState(true);
24
+ const [error, setError] = useState<string>("");
25
+
26
+ useEffect(() => {
27
+ // 处理输出,可能是字符串或数组
28
+ const output = tool.getJSONOutputSafe();
29
+ if (output) {
30
+ try {
31
+ if (typeof output === "string") {
32
+ // 尝试解析 JSON
33
+ const parsed = JSON.parse(output);
34
+ if (parsed.image_url) {
35
+ setImageUrls(Array.isArray(parsed.image_url) ? parsed.image_url : [parsed.image_url]);
36
+ } else if (Array.isArray(parsed)) {
37
+ setImageUrls(parsed);
38
+ } else {
39
+ setImageUrls([output]);
40
+ }
41
+ } else if (Array.isArray(output)) {
42
+ setImageUrls(output);
43
+ } else if (output.image_url) {
44
+ setImageUrls(Array.isArray(output.image_url) ? output.image_url : [output.image_url]);
45
+ }
46
+ setLoading(false);
47
+ } catch (e) {
48
+ // 如果不是 JSON,直接作为 URL
49
+ setImageUrls([output]);
50
+ setLoading(false);
51
+ }
52
+ } else {
53
+ setLoading(false);
54
+ }
55
+ }, [tool.output]);
56
+
57
+ const isEditing = data.input_image_urls && data.input_image_urls.length > 0;
58
+
59
+ return (
60
+ <div className="w-[70%] my-1 border border-gray-200 bg-white shadow-none rounded-xl overflow-hidden">
61
+ <div className="pb-2 p-3 border-b border-gray-50 bg-gray-50/30">
62
+ <div className="flex items-center gap-2">
63
+ <div className="w-7 h-7 rounded-full bg-purple-50 text-purple-600 flex items-center justify-center">
64
+ <Sparkles className="w-3.5 h-3.5" />
65
+ </div>
66
+ <h3 className="text-sm font-medium text-gray-900">{isEditing ? "Image Editing" : "Image Generation"}</h3>
67
+ </div>
68
+ </div>
69
+ <div className="p-4 space-y-4">
70
+ {/* Prompt */}
71
+ <div>
72
+ <div className="text-xs font-medium text-gray-500 mb-1">Prompt</div>
73
+ <div className="text-sm text-gray-900 bg-gray-50 rounded-lg p-3 border border-gray-200">{data.prompt}</div>
74
+ </div>
75
+
76
+ {/* Reference Images */}
77
+ {isEditing && data.input_image_urls && data.input_image_urls.length > 0 && (
78
+ <div>
79
+ <div className="text-xs font-medium text-gray-500 mb-2">Reference Images</div>
80
+ <div className="flex flex-wrap gap-2">
81
+ {data.input_image_urls.map((url, index) => (
82
+ <div key={index} className="relative w-24 h-24 rounded-lg overflow-hidden border border-gray-200 bg-gray-50">
83
+ <img src={url} alt={`Reference ${index + 1}`} className="w-full h-full object-cover" />
84
+ </div>
85
+ ))}
86
+ </div>
87
+ </div>
88
+ )}
89
+
90
+ {/* Configuration */}
91
+ <div className="flex flex-wrap gap-3 text-xs text-gray-600">
92
+ <div className="flex items-center gap-1">
93
+ <span className="font-medium">Resolution:</span>
94
+ <span className="px-1.5 py-0.5 bg-blue-50 text-blue-700 rounded">{data.resolution || "1K"}</span>
95
+ </div>
96
+ <div className="flex items-center gap-1">
97
+ <span className="font-medium">Aspect Ratio:</span>
98
+ <span className="px-1.5 py-0.5 bg-green-50 text-green-700 rounded">{data.aspectRatio || "16:9"}</span>
99
+ </div>
100
+ <div className="flex items-center gap-1">
101
+ <span className="font-medium">Model:</span>
102
+ <span className="px-1.5 py-0.5 bg-purple-50 text-purple-700 rounded truncate max-w-[200px]">{data.model || "gemini-3-pro-image-preview"}</span>
103
+ </div>
104
+ </div>
105
+
106
+ {/* Generated Images */}
107
+ {loading ? (
108
+ <div className="flex flex-col items-center justify-center py-12 bg-gray-50 rounded-lg border border-gray-200">
109
+ <Loader2 className="w-8 h-8 text-purple-600 animate-spin mb-2" />
110
+ <div className="text-sm text-gray-600">Generating image...</div>
111
+ </div>
112
+ ) : error ? (
113
+ <div className="p-4 bg-red-50 border border-red-200 rounded-lg text-sm text-red-700">{error}</div>
114
+ ) : imageUrls.length > 0 ? (
115
+ <div>
116
+ <div className="text-xs font-medium text-gray-500 mb-2">Generated Images</div>
117
+ <div
118
+ className={cn(
119
+ "grid gap-4",
120
+ imageUrls.length === 1 ? "grid-cols-1" : imageUrls.length === 2 ? "grid-cols-1 md:grid-cols-2" : "grid-cols-1 md:grid-cols-2 lg:grid-cols-3"
121
+ )}
122
+ >
123
+ {imageUrls.map((url, index) => (
124
+ <div key={index} className="relative group rounded-lg overflow-hidden border border-gray-200 bg-gray-50">
125
+ <img src={url} alt={`Generated image ${index + 1}`} className="w-full h-auto object-contain" onError={() => setError(`Failed to load image ${index + 1}`)} />
126
+ </div>
127
+ ))}
128
+ </div>
129
+ </div>
130
+ ) : (
131
+ <div className="flex flex-col items-center justify-center py-12 bg-gray-50 rounded-lg border border-gray-200">
132
+ <Image className="w-8 h-8 text-gray-400 mb-2" />
133
+ <div className="text-sm text-gray-600">No images generated yet</div>
134
+ </div>
135
+ )}
136
+ </div>
137
+ </div>
138
+ );
139
+ },
140
+ });
@@ -1,6 +1,18 @@
1
- // export { ask_user_for_approve } from "./ask_user_for_approve";
2
- // export { update_plan } from "./update_plan";
3
- // export { web_search_tool } from "./web_search_tool";
4
- export { show_form } from "./show_form";
5
- // 在这里添加其他工具的导出
6
- // export { other_tool } from "./other_tool";
1
+ import { create_artifacts } from "./create_artifacts";
2
+ import { __default_tool__ } from "./human-in-the-loop";
3
+ import { ask_user_to_fill_form } from "./ask_user_to_fill_form";
4
+ import { ask_user_with_options } from "./ask_user_with_options";
5
+ import { display_information_card } from "./display_information_card";
6
+ import { wait_for_user_to_upload_file } from "./wait_for_user_to_upload_file";
7
+ import { visualize_data_with_chart } from "./visualize_data_with_chart";
8
+ import { image_generation } from "./image_generation";
9
+ export const default_tools = [
10
+ ask_user_to_fill_form,
11
+ create_artifacts,
12
+ __default_tool__,
13
+ ask_user_with_options,
14
+ display_information_card,
15
+ wait_for_user_to_upload_file,
16
+ visualize_data_with_chart,
17
+ image_generation,
18
+ ];