@langgraph-js/ui 5.6.0 → 5.7.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.
- package/dist/assets/arc-Cxpt6B73.js +1 -0
- package/dist/assets/architectureDiagram-VXUJARFQ-DEy8uu79.js +36 -0
- package/dist/assets/{blockDiagram-VD42YOAC-JpCtx8Fw.js → blockDiagram-VD42YOAC-yNezEuFm.js} +4 -4
- package/dist/assets/{c4Diagram-YG6GDRKO-Ct0YUG6f.js → c4Diagram-YG6GDRKO-CFr-QKSf.js} +1 -1
- package/dist/assets/channel-ohUHNNLS.js +1 -0
- package/dist/assets/{chunk-4BX2VUAB-O9tuPwJ2.js → chunk-4BX2VUAB-DKYgHx9z.js} +1 -1
- package/dist/assets/{chunk-55IACEB6-B1LOZAa5.js → chunk-55IACEB6-Dp0pEp1k.js} +1 -1
- package/dist/assets/{chunk-B4BG7PRW-CHB7g7y9.js → chunk-B4BG7PRW-B9ne889x.js} +1 -1
- package/dist/assets/{chunk-DI55MBZ5-BZe7PkXl.js → chunk-DI55MBZ5-nz3xKTAU.js} +1 -1
- package/dist/assets/{chunk-FMBD7UC4-IV4Fy30-.js → chunk-FMBD7UC4-BEqW4wjD.js} +1 -1
- package/dist/assets/{chunk-QN33PNHL-DCcmoYki.js → chunk-QN33PNHL-DdYfOS87.js} +1 -1
- package/dist/assets/{chunk-QZHKN3VN-BNYpxk7b.js → chunk-QZHKN3VN--fzHnGin.js} +1 -1
- package/dist/assets/{chunk-TZMSLE5B-BgP3duP5.js → chunk-TZMSLE5B-wYv5WbFJ.js} +1 -1
- package/dist/assets/classDiagram-2ON5EDUG-DD3S7Z_T.js +1 -0
- package/dist/assets/classDiagram-v2-WZHVMYZB-DD3S7Z_T.js +1 -0
- package/dist/assets/clone-BL1RNKEm.js +1 -0
- package/dist/assets/{cose-bilkent-S5V4N54A-B_7kmvVh.js → cose-bilkent-S5V4N54A-Do_6LUNg.js} +1 -1
- package/dist/assets/{dagre-6UL2VRFP-CAxkUiea.js → dagre-6UL2VRFP-1iQhW5EH.js} +2 -2
- package/dist/assets/diagram-PSM6KHXK-BzAvoA1a.js +24 -0
- package/dist/assets/diagram-QEK2KX5R-BruzodAN.js +43 -0
- package/dist/assets/{diagram-S2PKOQOG-CZOKfwOI.js → diagram-S2PKOQOG-DxCLvX73.js} +1 -1
- package/dist/assets/{erDiagram-Q2GNP2WA-D9v3M4EU.js → erDiagram-Q2GNP2WA-VZD44fiG.js} +1 -1
- package/dist/assets/{flowDiagram-NV44I4VS-B9qnS72A.js → flowDiagram-NV44I4VS-BW9G6Z5Y.js} +1 -1
- package/dist/assets/ganttDiagram-LVOFAZNH-BE5hhtQW.js +267 -0
- package/dist/assets/{gitGraphDiagram-NY62KEGX-Cm36kSO3.js → gitGraphDiagram-NY62KEGX-D2xrVqfs.js} +1 -1
- package/dist/assets/{graph-C2p3i6RT.js → graph-BOje8BQW.js} +1 -1
- package/dist/assets/index-DKzqRWAz.css +1 -0
- package/dist/assets/{index-Cf3jkaWJ.js → index-Dyga3lv6.js} +312 -173
- package/dist/assets/{infoDiagram-F6ZHWCRC-DkBxmgNt.js → infoDiagram-F6ZHWCRC-DuvG7CMo.js} +1 -1
- package/dist/assets/isUndefined-Jm66YXc7.js +1 -0
- package/dist/assets/{journeyDiagram-XKPGCS4Q-CVukoP-j.js → journeyDiagram-XKPGCS4Q-DppY-OqU.js} +1 -1
- package/dist/assets/{kanban-definition-3W4ZIXB7-0-SbO6tS.js → kanban-definition-3W4ZIXB7-DWFZ7d_a.js} +4 -4
- package/dist/assets/layout-BrYg1BPZ.js +1 -0
- package/dist/assets/mermaid.core-Brj2E_sv.js +197 -0
- package/dist/assets/min-OyaaRTus.js +1 -0
- package/dist/assets/{mindmap-definition-VGOIOE7T-CDGdm5yw.js → mindmap-definition-VGOIOE7T-Ys6gAj49.js} +5 -5
- package/dist/assets/pieDiagram-ADFJNKIX-CDawS5d5.js +30 -0
- package/dist/assets/{quadrantDiagram-AYHSOK5B-DY0NltyG.js → quadrantDiagram-AYHSOK5B-BwaRQW9j.js} +2 -2
- package/dist/assets/{requirementDiagram-UZGBJVZJ-DwhwO5lc.js → requirementDiagram-UZGBJVZJ-xZhlkqne.js} +1 -1
- package/dist/assets/sankeyDiagram-TZEHDZUN-CNXqnJNS.js +10 -0
- package/dist/assets/{sequenceDiagram-WL72ISMW-CpTIKJGg.js → sequenceDiagram-WL72ISMW-DuVqNtcK.js} +1 -1
- package/dist/assets/stateDiagram-FKZM4ZOC-kjDVpzFt.js +1 -0
- package/dist/assets/stateDiagram-v2-4FDKWEC3-Bt6o2Z8q.js +1 -0
- package/dist/assets/{timeline-definition-IT6M3QCI-1MkGCcpM.js → timeline-definition-IT6M3QCI-BrZan_9K.js} +3 -3
- package/dist/assets/{treemap-KMMF4GRG-pRPfKbhX.js → treemap-KMMF4GRG-Bc2NrK0Z.js} +1 -1
- package/dist/assets/xychartDiagram-PRI3JC2R-Dh5NbHlS.js +7 -0
- package/dist/index.html +2 -2
- package/package.json +2 -1
- 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} +147 -69
- 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
|
@@ -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
|
+
});
|
package/src/chat/tools/index.ts
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
+
];
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { createUITool } from "@langgraph-js/sdk";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { BarChart3, TrendingUp, PieChart, ScatterChart, AreaChart, Radar, Grid3x3, BarChart } from "lucide-react";
|
|
4
|
+
import {
|
|
5
|
+
LineChart,
|
|
6
|
+
Line,
|
|
7
|
+
BarChart as RechartsBarChart,
|
|
8
|
+
Bar,
|
|
9
|
+
PieChart as RechartsPieChart,
|
|
10
|
+
Pie,
|
|
11
|
+
Cell,
|
|
12
|
+
ScatterChart as RechartsScatterChart,
|
|
13
|
+
Scatter,
|
|
14
|
+
AreaChart as RechartsAreaChart,
|
|
15
|
+
Area,
|
|
16
|
+
RadarChart as RechartsRadarChart,
|
|
17
|
+
PolarGrid,
|
|
18
|
+
PolarAngleAxis,
|
|
19
|
+
PolarRadiusAxis,
|
|
20
|
+
Radar as RechartsRadar,
|
|
21
|
+
XAxis,
|
|
22
|
+
YAxis,
|
|
23
|
+
CartesianGrid,
|
|
24
|
+
Tooltip,
|
|
25
|
+
Legend,
|
|
26
|
+
ResponsiveContainer,
|
|
27
|
+
ComposedChart,
|
|
28
|
+
} from "recharts";
|
|
29
|
+
|
|
30
|
+
const VisualizeDataWithChartSchema = {
|
|
31
|
+
title: z.string().describe("Chart title"),
|
|
32
|
+
description: z.string().optional().describe("Optional chart description"),
|
|
33
|
+
chart_type: z.enum(["line", "bar", "pie", "scatter", "area", "radar", "heatmap", "histogram"]).describe("Type of chart to render"),
|
|
34
|
+
data: z.array(z.record(z.string(), z.any())).describe("Chart data points"),
|
|
35
|
+
x_axis: z
|
|
36
|
+
.object({
|
|
37
|
+
label: z.string().describe("X-axis label"),
|
|
38
|
+
field: z.string().describe("Data field for X-axis"),
|
|
39
|
+
})
|
|
40
|
+
.optional()
|
|
41
|
+
.describe("X-axis configuration"),
|
|
42
|
+
y_axis: z
|
|
43
|
+
.object({
|
|
44
|
+
label: z.string().describe("Y-axis label"),
|
|
45
|
+
field: z.string().describe("Data field for Y-axis"),
|
|
46
|
+
})
|
|
47
|
+
.optional()
|
|
48
|
+
.describe("Y-axis configuration"),
|
|
49
|
+
options: z.record(z.string(), z.any()).optional().describe("Additional chart configuration options"),
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const getChartIcon = (chartType: string) => {
|
|
53
|
+
switch (chartType) {
|
|
54
|
+
case "line":
|
|
55
|
+
return TrendingUp;
|
|
56
|
+
case "bar":
|
|
57
|
+
return BarChart3;
|
|
58
|
+
case "pie":
|
|
59
|
+
return PieChart;
|
|
60
|
+
case "scatter":
|
|
61
|
+
return ScatterChart;
|
|
62
|
+
case "area":
|
|
63
|
+
return AreaChart;
|
|
64
|
+
case "radar":
|
|
65
|
+
return Radar;
|
|
66
|
+
case "heatmap":
|
|
67
|
+
return Grid3x3;
|
|
68
|
+
case "histogram":
|
|
69
|
+
return BarChart;
|
|
70
|
+
default:
|
|
71
|
+
return BarChart3;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const COLORS = ["#3b82f6", "#10b981", "#f59e0b", "#ef4444", "#8b5cf6", "#06b6d4", "#ec4899", "#84cc16"];
|
|
76
|
+
|
|
77
|
+
const renderChart = (chartType: string, data: Record<string, any>[], xAxis?: { label: string; field: string }, yAxis?: { label: string; field: string }, options?: Record<string, any>) => {
|
|
78
|
+
if (!data || data.length === 0) {
|
|
79
|
+
return <div className="text-sm text-gray-500 p-8 text-center">No data available</div>;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const commonProps = {
|
|
83
|
+
data,
|
|
84
|
+
margin: { top: 5, right: 30, left: 20, bottom: 5 },
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
switch (chartType) {
|
|
88
|
+
case "line":
|
|
89
|
+
return (
|
|
90
|
+
<ResponsiveContainer width="100%" height={400}>
|
|
91
|
+
<LineChart {...commonProps}>
|
|
92
|
+
<CartesianGrid strokeDasharray="3 3" />
|
|
93
|
+
<XAxis dataKey={xAxis?.field || "name"} label={{ value: xAxis?.label, position: "insideBottom", offset: -5 }} />
|
|
94
|
+
<YAxis label={{ value: yAxis?.label, angle: -90, position: "insideLeft" }} />
|
|
95
|
+
<Tooltip />
|
|
96
|
+
<Legend />
|
|
97
|
+
{yAxis?.field && <Line type="monotone" dataKey={yAxis.field} stroke={COLORS[0]} strokeWidth={2} />}
|
|
98
|
+
</LineChart>
|
|
99
|
+
</ResponsiveContainer>
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
case "bar":
|
|
103
|
+
return (
|
|
104
|
+
<ResponsiveContainer width="100%" height={400}>
|
|
105
|
+
<RechartsBarChart {...commonProps}>
|
|
106
|
+
<CartesianGrid strokeDasharray="3 3" />
|
|
107
|
+
<XAxis dataKey={xAxis?.field || "name"} label={{ value: xAxis?.label, position: "insideBottom", offset: -5 }} />
|
|
108
|
+
<YAxis label={{ value: yAxis?.label, angle: -90, position: "insideLeft" }} />
|
|
109
|
+
<Tooltip />
|
|
110
|
+
<Legend />
|
|
111
|
+
{yAxis?.field && <Bar dataKey={yAxis.field} fill={COLORS[0]} />}
|
|
112
|
+
</RechartsBarChart>
|
|
113
|
+
</ResponsiveContainer>
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
case "pie":
|
|
117
|
+
// Pie chart uses different data structure
|
|
118
|
+
const pieData =
|
|
119
|
+
xAxis?.field && yAxis?.field
|
|
120
|
+
? data.map((item) => ({
|
|
121
|
+
name: item[xAxis.field],
|
|
122
|
+
value: item[yAxis.field],
|
|
123
|
+
}))
|
|
124
|
+
: data;
|
|
125
|
+
return (
|
|
126
|
+
<ResponsiveContainer width="100%" height={400}>
|
|
127
|
+
<RechartsPieChart>
|
|
128
|
+
<Pie
|
|
129
|
+
data={pieData}
|
|
130
|
+
cx="50%"
|
|
131
|
+
cy="50%"
|
|
132
|
+
labelLine={false}
|
|
133
|
+
label={(props: any) => {
|
|
134
|
+
const { name, percent } = props;
|
|
135
|
+
return `${name}: ${((percent as number) * 100).toFixed(0)}%`;
|
|
136
|
+
}}
|
|
137
|
+
outerRadius={120}
|
|
138
|
+
fill="#8884d8"
|
|
139
|
+
dataKey={yAxis?.field || "value"}
|
|
140
|
+
>
|
|
141
|
+
{pieData.map((entry, index) => (
|
|
142
|
+
<Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />
|
|
143
|
+
))}
|
|
144
|
+
</Pie>
|
|
145
|
+
<Tooltip />
|
|
146
|
+
<Legend />
|
|
147
|
+
</RechartsPieChart>
|
|
148
|
+
</ResponsiveContainer>
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
case "scatter":
|
|
152
|
+
return (
|
|
153
|
+
<ResponsiveContainer width="100%" height={400}>
|
|
154
|
+
<RechartsScatterChart {...commonProps}>
|
|
155
|
+
<CartesianGrid strokeDasharray="3 3" />
|
|
156
|
+
<XAxis type="number" dataKey={xAxis?.field || "x"} name={xAxis?.label || "X"} />
|
|
157
|
+
<YAxis type="number" dataKey={yAxis?.field || "y"} name={yAxis?.label || "Y"} />
|
|
158
|
+
<Tooltip cursor={{ strokeDasharray: "3 3" }} />
|
|
159
|
+
<Scatter dataKey={yAxis?.field || "y"} fill={COLORS[0]} />
|
|
160
|
+
</RechartsScatterChart>
|
|
161
|
+
</ResponsiveContainer>
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
case "area":
|
|
165
|
+
return (
|
|
166
|
+
<ResponsiveContainer width="100%" height={400}>
|
|
167
|
+
<RechartsAreaChart {...commonProps}>
|
|
168
|
+
<CartesianGrid strokeDasharray="3 3" />
|
|
169
|
+
<XAxis dataKey={xAxis?.field || "name"} label={{ value: xAxis?.label, position: "insideBottom", offset: -5 }} />
|
|
170
|
+
<YAxis label={{ value: yAxis?.label, angle: -90, position: "insideLeft" }} />
|
|
171
|
+
<Tooltip />
|
|
172
|
+
<Legend />
|
|
173
|
+
{yAxis?.field && <Area type="monotone" dataKey={yAxis.field} stroke={COLORS[0]} fill={COLORS[0]} fillOpacity={0.6} />}
|
|
174
|
+
</RechartsAreaChart>
|
|
175
|
+
</ResponsiveContainer>
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
case "radar":
|
|
179
|
+
const radarData = data.map((item) => {
|
|
180
|
+
const result: Record<string, any> = {};
|
|
181
|
+
if (xAxis?.field) {
|
|
182
|
+
result[xAxis.field] = item[xAxis.field];
|
|
183
|
+
}
|
|
184
|
+
if (yAxis?.field) {
|
|
185
|
+
result[yAxis.field] = item[yAxis.field];
|
|
186
|
+
}
|
|
187
|
+
// Include all numeric fields
|
|
188
|
+
Object.keys(item).forEach((key) => {
|
|
189
|
+
if (typeof item[key] === "number" && key !== xAxis?.field && key !== yAxis?.field) {
|
|
190
|
+
result[key] = item[key];
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
return result;
|
|
194
|
+
});
|
|
195
|
+
return (
|
|
196
|
+
<ResponsiveContainer width="100%" height={400}>
|
|
197
|
+
<RechartsRadarChart data={radarData}>
|
|
198
|
+
<PolarGrid />
|
|
199
|
+
<PolarAngleAxis dataKey={xAxis?.field || "name"} />
|
|
200
|
+
<PolarRadiusAxis />
|
|
201
|
+
<RechartsRadar name={yAxis?.label || "Value"} dataKey={yAxis?.field || "value"} stroke={COLORS[0]} fill={COLORS[0]} fillOpacity={0.6} />
|
|
202
|
+
<Tooltip />
|
|
203
|
+
<Legend />
|
|
204
|
+
</RechartsRadarChart>
|
|
205
|
+
</ResponsiveContainer>
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
case "heatmap":
|
|
209
|
+
// Heatmap is not directly supported by recharts, use a composed chart as approximation
|
|
210
|
+
return (
|
|
211
|
+
<ResponsiveContainer width="100%" height={400}>
|
|
212
|
+
<ComposedChart {...commonProps}>
|
|
213
|
+
<CartesianGrid strokeDasharray="3 3" />
|
|
214
|
+
<XAxis dataKey={xAxis?.field || "name"} label={{ value: xAxis?.label, position: "insideBottom", offset: -5 }} />
|
|
215
|
+
<YAxis label={{ value: yAxis?.label, angle: -90, position: "insideLeft" }} />
|
|
216
|
+
<Tooltip />
|
|
217
|
+
<Legend />
|
|
218
|
+
{yAxis?.field && (
|
|
219
|
+
<Bar dataKey={yAxis.field} fill={COLORS[0]} radius={[4, 4, 0, 0]}>
|
|
220
|
+
{data.map((entry, index) => (
|
|
221
|
+
<Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />
|
|
222
|
+
))}
|
|
223
|
+
</Bar>
|
|
224
|
+
)}
|
|
225
|
+
</ComposedChart>
|
|
226
|
+
</ResponsiveContainer>
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
case "histogram":
|
|
230
|
+
return (
|
|
231
|
+
<ResponsiveContainer width="100%" height={400}>
|
|
232
|
+
<RechartsBarChart {...commonProps}>
|
|
233
|
+
<CartesianGrid strokeDasharray="3 3" />
|
|
234
|
+
<XAxis dataKey={xAxis?.field || "name"} label={{ value: xAxis?.label, position: "insideBottom", offset: -5 }} />
|
|
235
|
+
<YAxis label={{ value: yAxis?.label, angle: -90, position: "insideLeft" }} />
|
|
236
|
+
<Tooltip />
|
|
237
|
+
<Legend />
|
|
238
|
+
{yAxis?.field && <Bar dataKey={yAxis.field} fill={COLORS[0]} />}
|
|
239
|
+
</RechartsBarChart>
|
|
240
|
+
</ResponsiveContainer>
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
default:
|
|
244
|
+
return <div className="text-sm text-gray-500 p-8 text-center">Unsupported chart type: {chartType}</div>;
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
export const visualize_data_with_chart = createUITool({
|
|
249
|
+
name: "visualize_data_with_chart",
|
|
250
|
+
description: "Generate a dynamic chart for data visualization and user review.",
|
|
251
|
+
parameters: VisualizeDataWithChartSchema,
|
|
252
|
+
onlyRender: true,
|
|
253
|
+
render(tool) {
|
|
254
|
+
const data = tool.getInputRepaired();
|
|
255
|
+
const ChartIcon = getChartIcon(data.chart_type || "line");
|
|
256
|
+
|
|
257
|
+
return (
|
|
258
|
+
<div className="w-[70%] my-1 border border-gray-200 bg-white shadow-none rounded-xl overflow-hidden">
|
|
259
|
+
<div className="pb-2 p-3 border-b border-gray-50 bg-gray-50/30">
|
|
260
|
+
<div className="flex items-center gap-2">
|
|
261
|
+
<div className="w-7 h-7 rounded-full bg-blue-50 text-blue-600 flex items-center justify-center">
|
|
262
|
+
<ChartIcon className="w-3.5 h-3.5" />
|
|
263
|
+
</div>
|
|
264
|
+
<h3 className="text-sm font-medium text-gray-900">{data.title}</h3>
|
|
265
|
+
</div>
|
|
266
|
+
</div>
|
|
267
|
+
<div className="p-4">
|
|
268
|
+
{data.description && <div className="text-sm text-gray-600 mb-4">{data.description}</div>}
|
|
269
|
+
<div className="w-full">{renderChart(data.chart_type || "line", (data.data || []) as Record<string, any>[], data.x_axis, data.y_axis, data.options)}</div>
|
|
270
|
+
</div>
|
|
271
|
+
</div>
|
|
272
|
+
);
|
|
273
|
+
},
|
|
274
|
+
});
|