@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
|
@@ -1,10 +1,12 @@
|
|
|
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";
|
|
8
10
|
|
|
9
11
|
// 自定义文本输入组件
|
|
10
12
|
const CustomTextWidget = (props: any) => {
|
|
@@ -428,89 +430,165 @@ const CustomObjectFieldTemplate = (props: any) => {
|
|
|
428
430
|
// 自定义按钮组件
|
|
429
431
|
const CustomSubmitButton = (props: any) => {
|
|
430
432
|
return (
|
|
431
|
-
<
|
|
433
|
+
<Button
|
|
432
434
|
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
435
|
disabled={props.disabled}
|
|
436
|
+
className={cn(
|
|
437
|
+
"px-5 rounded-full transition-all duration-200",
|
|
438
|
+
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"
|
|
439
|
+
)}
|
|
435
440
|
>
|
|
436
|
-
{props.submitText || "
|
|
437
|
-
</
|
|
441
|
+
{props.submitText || "提交表单"}
|
|
442
|
+
</Button>
|
|
438
443
|
);
|
|
439
444
|
};
|
|
440
445
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
description: "
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
446
|
+
const AskUserToFillFormSchema = {
|
|
447
|
+
title: z.string().describe("Form title"),
|
|
448
|
+
description: z.string().optional().describe("Optional form description"),
|
|
449
|
+
schema: z.record(z.string(), z.any()).describe("JSON Schema for the form (react-jsonschema-form compatible)"),
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
export const ask_user_to_fill_form = createUITool({
|
|
453
|
+
name: "ask_user_to_fill_form",
|
|
454
|
+
description: "Present a form to the user for filling out. Schema follows react-jsonschema-form format.",
|
|
455
|
+
parameters: AskUserToFillFormSchema,
|
|
447
456
|
onlyRender: false,
|
|
448
457
|
handler: ToolManager.waitForUIDone,
|
|
449
458
|
render(tool) {
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
459
|
+
try {
|
|
460
|
+
const data = tool.getInputRepaired();
|
|
461
|
+
const output = tool.getJSONOutputSafe();
|
|
462
|
+
const formSchema = data?.schema || {};
|
|
463
|
+
const canInteract = tool.state === "interrupted";
|
|
453
464
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
465
|
+
const handleSubmit = (formData: any) => {
|
|
466
|
+
try {
|
|
467
|
+
tool.sendResumeData({
|
|
468
|
+
/** @ts-ignore */
|
|
469
|
+
type: "respond",
|
|
470
|
+
message: JSON.stringify(formData),
|
|
471
|
+
});
|
|
472
|
+
} catch (error) {
|
|
473
|
+
console.error("提交表单数据时出错:", error);
|
|
474
|
+
}
|
|
475
|
+
};
|
|
458
476
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
477
|
+
// 自定义控件配置
|
|
478
|
+
const customWidgets = {
|
|
479
|
+
text: CustomTextWidget,
|
|
480
|
+
password: CustomPasswordWidget,
|
|
481
|
+
textarea: CustomTextareaWidget,
|
|
482
|
+
select: CustomSelectWidget,
|
|
483
|
+
checkboxes: CustomCheckboxWidget,
|
|
484
|
+
radio: CustomRadioWidget,
|
|
485
|
+
number: CustomNumberWidget,
|
|
486
|
+
range: CustomRangeWidget,
|
|
487
|
+
date: CustomDateWidget,
|
|
488
|
+
datetime: CustomDateTimeWidget,
|
|
489
|
+
file: CustomFileWidget,
|
|
490
|
+
boolean: CustomBooleanWidget,
|
|
491
|
+
};
|
|
474
492
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
493
|
+
const customFields = {
|
|
494
|
+
array: CustomArrayField,
|
|
495
|
+
};
|
|
478
496
|
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
497
|
+
const customTemplates = {
|
|
498
|
+
FieldTemplate: CustomFieldTemplate,
|
|
499
|
+
ObjectFieldTemplate: CustomObjectFieldTemplate,
|
|
500
|
+
ButtonTemplates: {
|
|
501
|
+
SubmitButton: CustomSubmitButton,
|
|
502
|
+
},
|
|
503
|
+
};
|
|
504
|
+
|
|
505
|
+
// 完成状态视图
|
|
506
|
+
if (!canInteract && tool.output) {
|
|
507
|
+
let submittedData;
|
|
508
|
+
try {
|
|
509
|
+
submittedData = typeof tool.output === "string" ? JSON.parse(tool.output) : tool.output;
|
|
510
|
+
} catch (error) {
|
|
511
|
+
submittedData = tool.output;
|
|
512
|
+
}
|
|
513
|
+
return (
|
|
514
|
+
<div className="flex flex-col gap-2 my-1 p-3 bg-gray-50/50 border border-gray-200 rounded-xl">
|
|
515
|
+
<div className="flex items-center gap-2 text-xs text-gray-500">
|
|
516
|
+
<div className="w-5 h-5 rounded-full bg-gray-100 flex items-center justify-center border border-gray-200">
|
|
517
|
+
<CheckCircle className="w-3 h-3" />
|
|
518
|
+
</div>
|
|
519
|
+
<span className="font-medium">Form Submitted</span>
|
|
520
|
+
</div>
|
|
521
|
+
|
|
522
|
+
<div className="space-y-2">
|
|
523
|
+
<div className="text-sm text-gray-600 pl-1">{data?.title || "表单"}</div>
|
|
524
|
+
<div className="text-sm text-gray-900 bg-white px-3 py-2.5 rounded-xl border border-gray-200">
|
|
525
|
+
<pre className="text-xs whitespace-pre-wrap break-all">{JSON.stringify(submittedData, null, 2)}</pre>
|
|
526
|
+
</div>
|
|
527
|
+
</div>
|
|
528
|
+
</div>
|
|
529
|
+
);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// 交互状态视图
|
|
533
|
+
return (
|
|
492
534
|
<ErrorBoundary>
|
|
493
|
-
<
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
535
|
+
<div className="w-[70%] my-1 border border-gray-200 bg-white shadow-none rounded-xl overflow-hidden">
|
|
536
|
+
<div className="pb-2 p-3 border-b border-gray-50 bg-gray-50/30">
|
|
537
|
+
<div className="flex items-center justify-between">
|
|
538
|
+
<div className="flex items-center gap-2">
|
|
539
|
+
<div className="w-7 h-7 rounded-full bg-blue-50 text-blue-600 flex items-center justify-center">
|
|
540
|
+
<FileText className="w-3.5 h-3.5" />
|
|
541
|
+
</div>
|
|
542
|
+
<h3 className="text-sm font-medium text-gray-900">{data?.title || "表单"}</h3>
|
|
543
|
+
</div>
|
|
544
|
+
</div>
|
|
545
|
+
</div>
|
|
546
|
+
<div className="p-4 space-y-3">
|
|
547
|
+
{data?.description && <div className="text-sm text-gray-600">{data.description}</div>}
|
|
548
|
+
<ErrorBoundary>
|
|
549
|
+
<Form
|
|
550
|
+
readonly={!canInteract}
|
|
551
|
+
schema={formSchema}
|
|
552
|
+
formData={output}
|
|
553
|
+
onSubmit={(formData: IChangeEvent<any>) => {
|
|
554
|
+
try {
|
|
555
|
+
handleSubmit(formData.formData);
|
|
556
|
+
} catch (error) {
|
|
557
|
+
console.error("表单提交处理错误:", error);
|
|
558
|
+
}
|
|
559
|
+
}}
|
|
560
|
+
validator={validator}
|
|
561
|
+
onError={(errors) => {
|
|
562
|
+
console.error("表单校验错误:", errors);
|
|
563
|
+
}}
|
|
564
|
+
widgets={customWidgets}
|
|
565
|
+
fields={customFields}
|
|
566
|
+
templates={customTemplates}
|
|
567
|
+
className="custom-form"
|
|
568
|
+
>
|
|
569
|
+
<div className="flex justify-end pt-2">
|
|
570
|
+
<CustomSubmitButton disabled={!canInteract} />
|
|
571
|
+
</div>
|
|
572
|
+
</Form>
|
|
573
|
+
</ErrorBoundary>
|
|
510
574
|
</div>
|
|
511
|
-
</
|
|
575
|
+
</div>
|
|
512
576
|
</ErrorBoundary>
|
|
513
|
-
|
|
514
|
-
)
|
|
577
|
+
);
|
|
578
|
+
} catch (error) {
|
|
579
|
+
// 外层错误边界,捕获所有未处理的错误
|
|
580
|
+
console.error("表单组件渲染错误:", error);
|
|
581
|
+
return (
|
|
582
|
+
<div className="w-full my-1 border border-red-200 bg-red-50 rounded-xl overflow-hidden">
|
|
583
|
+
<div className="p-4">
|
|
584
|
+
<div className="flex items-center gap-2 mb-2">
|
|
585
|
+
<AlertCircle className="w-5 h-5 text-red-600" />
|
|
586
|
+
<h3 className="text-sm font-medium text-red-900">表单加载失败</h3>
|
|
587
|
+
</div>
|
|
588
|
+
<p className="text-xs text-red-700">表单配置有误,请检查 schema 格式是否正确。</p>
|
|
589
|
+
</div>
|
|
590
|
+
</div>
|
|
591
|
+
);
|
|
592
|
+
}
|
|
515
593
|
},
|
|
516
594
|
});
|
|
@@ -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
|
+
});
|