@contractspec/module.ai-chat 4.0.3 → 4.1.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/README.md +130 -10
- package/dist/adapters/ai-sdk-bundle-adapter.d.ts +18 -0
- package/dist/adapters/index.d.ts +4 -0
- package/dist/browser/core/index.js +1138 -21
- package/dist/browser/index.js +2816 -651
- package/dist/browser/presentation/components/index.js +3143 -358
- package/dist/browser/presentation/hooks/index.js +961 -43
- package/dist/browser/presentation/index.js +2784 -666
- package/dist/core/agent-adapter.d.ts +53 -0
- package/dist/core/agent-tools-adapter.d.ts +12 -0
- package/dist/core/chat-service.d.ts +49 -1
- package/dist/core/contracts-context.d.ts +46 -0
- package/dist/core/contracts-context.test.d.ts +1 -0
- package/dist/core/conversation-store.d.ts +16 -2
- package/dist/core/create-chat-route.d.ts +3 -0
- package/dist/core/export-formatters.d.ts +29 -0
- package/dist/core/export-formatters.test.d.ts +1 -0
- package/dist/core/index.d.ts +8 -0
- package/dist/core/index.js +1138 -21
- package/dist/core/local-storage-conversation-store.d.ts +33 -0
- package/dist/core/message-types.d.ts +6 -0
- package/dist/core/surface-planner-tools.d.ts +23 -0
- package/dist/core/surface-planner-tools.test.d.ts +1 -0
- package/dist/core/thinking-levels.d.ts +38 -0
- package/dist/core/thinking-levels.test.d.ts +1 -0
- package/dist/core/workflow-tools.d.ts +18 -0
- package/dist/core/workflow-tools.test.d.ts +1 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +2816 -651
- package/dist/node/core/index.js +1138 -21
- package/dist/node/index.js +2816 -651
- package/dist/node/presentation/components/index.js +3143 -358
- package/dist/node/presentation/hooks/index.js +961 -43
- package/dist/node/presentation/index.js +2787 -669
- package/dist/presentation/components/ChatContainer.d.ts +3 -1
- package/dist/presentation/components/ChatExportToolbar.d.ts +25 -0
- package/dist/presentation/components/ChatMessage.d.ts +16 -1
- package/dist/presentation/components/ChatSidebar.d.ts +26 -0
- package/dist/presentation/components/ChatWithExport.d.ts +34 -0
- package/dist/presentation/components/ChatWithSidebar.d.ts +19 -0
- package/dist/presentation/components/ThinkingLevelPicker.d.ts +16 -0
- package/dist/presentation/components/ToolResultRenderer.d.ts +33 -0
- package/dist/presentation/components/index.d.ts +6 -0
- package/dist/presentation/components/index.js +3143 -358
- package/dist/presentation/hooks/index.d.ts +2 -0
- package/dist/presentation/hooks/index.js +961 -43
- package/dist/presentation/hooks/useChat.d.ts +44 -2
- package/dist/presentation/hooks/useConversations.d.ts +18 -0
- package/dist/presentation/hooks/useMessageSelection.d.ts +13 -0
- package/dist/presentation/index.js +2787 -669
- package/package.json +14 -18
package/dist/node/index.js
CHANGED
|
@@ -404,12 +404,13 @@ function createNodeFileOperations(workspacePath, allowWrites = false) {
|
|
|
404
404
|
import * as React from "react";
|
|
405
405
|
import { ScrollArea } from "@contractspec/lib.ui-kit-web/ui/scroll-area";
|
|
406
406
|
import { cn } from "@contractspec/lib.ui-kit-web/ui/utils";
|
|
407
|
-
import {
|
|
407
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
408
408
|
"use client";
|
|
409
409
|
function ChatContainer({
|
|
410
410
|
children,
|
|
411
411
|
className,
|
|
412
|
-
showScrollButton = true
|
|
412
|
+
showScrollButton = true,
|
|
413
|
+
headerContent
|
|
413
414
|
}) {
|
|
414
415
|
const scrollRef = React.useRef(null);
|
|
415
416
|
const [showScrollDown, setShowScrollDown] = React.useState(false);
|
|
@@ -436,24 +437,28 @@ function ChatContainer({
|
|
|
436
437
|
});
|
|
437
438
|
}
|
|
438
439
|
}, []);
|
|
439
|
-
return /* @__PURE__ */
|
|
440
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
440
441
|
className: cn("relative flex flex-1 flex-col", className),
|
|
441
442
|
children: [
|
|
442
|
-
/* @__PURE__ */
|
|
443
|
+
headerContent && /* @__PURE__ */ jsx("div", {
|
|
444
|
+
className: "border-border flex shrink-0 items-center justify-end gap-2 border-b px-4 py-2",
|
|
445
|
+
children: headerContent
|
|
446
|
+
}),
|
|
447
|
+
/* @__PURE__ */ jsx(ScrollArea, {
|
|
443
448
|
ref: scrollRef,
|
|
444
449
|
className: "flex-1",
|
|
445
450
|
onScroll: handleScroll,
|
|
446
|
-
children: /* @__PURE__ */
|
|
451
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
447
452
|
className: "flex flex-col gap-4 p-4",
|
|
448
453
|
children
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
showScrollButton && showScrollDown && /* @__PURE__ */
|
|
454
|
+
})
|
|
455
|
+
}),
|
|
456
|
+
showScrollButton && showScrollDown && /* @__PURE__ */ jsxs("button", {
|
|
452
457
|
onClick: scrollToBottom,
|
|
453
458
|
className: cn("absolute bottom-4 left-1/2 -translate-x-1/2", "bg-primary text-primary-foreground", "rounded-full px-3 py-1.5 text-sm font-medium shadow-lg", "hover:bg-primary/90 transition-colors", "flex items-center gap-1.5"),
|
|
454
459
|
"aria-label": "Scroll to bottom",
|
|
455
460
|
children: [
|
|
456
|
-
/* @__PURE__ */
|
|
461
|
+
/* @__PURE__ */ jsx("svg", {
|
|
457
462
|
xmlns: "http://www.w3.org/2000/svg",
|
|
458
463
|
width: "16",
|
|
459
464
|
height: "16",
|
|
@@ -463,15 +468,15 @@ function ChatContainer({
|
|
|
463
468
|
strokeWidth: "2",
|
|
464
469
|
strokeLinecap: "round",
|
|
465
470
|
strokeLinejoin: "round",
|
|
466
|
-
children: /* @__PURE__ */
|
|
471
|
+
children: /* @__PURE__ */ jsx("path", {
|
|
467
472
|
d: "m6 9 6 6 6-6"
|
|
468
|
-
}
|
|
469
|
-
}
|
|
473
|
+
})
|
|
474
|
+
}),
|
|
470
475
|
"New messages"
|
|
471
476
|
]
|
|
472
|
-
}
|
|
477
|
+
})
|
|
473
478
|
]
|
|
474
|
-
}
|
|
479
|
+
});
|
|
475
480
|
}
|
|
476
481
|
// src/presentation/components/ChatMessage.tsx
|
|
477
482
|
import * as React3 from "react";
|
|
@@ -485,16 +490,19 @@ import {
|
|
|
485
490
|
Copy as Copy2,
|
|
486
491
|
Check as Check2,
|
|
487
492
|
ExternalLink,
|
|
488
|
-
Wrench
|
|
493
|
+
Wrench,
|
|
494
|
+
Pencil,
|
|
495
|
+
X
|
|
489
496
|
} from "lucide-react";
|
|
490
497
|
import { Button as Button2 } from "@contractspec/lib.design-system";
|
|
498
|
+
import { Checkbox } from "@contractspec/lib.ui-kit-web/ui/checkbox";
|
|
491
499
|
|
|
492
500
|
// src/presentation/components/CodePreview.tsx
|
|
493
501
|
import * as React2 from "react";
|
|
494
502
|
import { cn as cn2 } from "@contractspec/lib.ui-kit-web/ui/utils";
|
|
495
503
|
import { Button } from "@contractspec/lib.design-system";
|
|
496
504
|
import { Copy, Check, Play, Download } from "lucide-react";
|
|
497
|
-
import {
|
|
505
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
498
506
|
"use client";
|
|
499
507
|
var LANGUAGE_NAMES = {
|
|
500
508
|
ts: "TypeScript",
|
|
@@ -547,93 +555,147 @@ function CodePreview({
|
|
|
547
555
|
document.body.removeChild(a);
|
|
548
556
|
URL.revokeObjectURL(url);
|
|
549
557
|
}, [code, filename, language]);
|
|
550
|
-
return /* @__PURE__ */
|
|
558
|
+
return /* @__PURE__ */ jsxs2("div", {
|
|
551
559
|
className: cn2("overflow-hidden rounded-lg border", "bg-muted/50", className),
|
|
552
560
|
children: [
|
|
553
|
-
/* @__PURE__ */
|
|
561
|
+
/* @__PURE__ */ jsxs2("div", {
|
|
554
562
|
className: cn2("flex items-center justify-between px-3 py-1.5", "bg-muted/80 border-b"),
|
|
555
563
|
children: [
|
|
556
|
-
/* @__PURE__ */
|
|
564
|
+
/* @__PURE__ */ jsxs2("div", {
|
|
557
565
|
className: "flex items-center gap-2 text-sm",
|
|
558
566
|
children: [
|
|
559
|
-
filename && /* @__PURE__ */
|
|
567
|
+
filename && /* @__PURE__ */ jsx2("span", {
|
|
560
568
|
className: "text-foreground font-mono",
|
|
561
569
|
children: filename
|
|
562
|
-
}
|
|
563
|
-
/* @__PURE__ */
|
|
570
|
+
}),
|
|
571
|
+
/* @__PURE__ */ jsx2("span", {
|
|
564
572
|
className: "text-muted-foreground",
|
|
565
573
|
children: displayLanguage
|
|
566
|
-
}
|
|
574
|
+
})
|
|
567
575
|
]
|
|
568
|
-
}
|
|
569
|
-
/* @__PURE__ */
|
|
576
|
+
}),
|
|
577
|
+
/* @__PURE__ */ jsxs2("div", {
|
|
570
578
|
className: "flex items-center gap-1",
|
|
571
579
|
children: [
|
|
572
|
-
showExecute && onExecute && /* @__PURE__ */
|
|
580
|
+
showExecute && onExecute && /* @__PURE__ */ jsx2(Button, {
|
|
573
581
|
variant: "ghost",
|
|
574
582
|
size: "sm",
|
|
575
583
|
onPress: () => onExecute(code),
|
|
576
584
|
className: "h-7 w-7 p-0",
|
|
577
585
|
"aria-label": "Execute code",
|
|
578
|
-
children: /* @__PURE__ */
|
|
586
|
+
children: /* @__PURE__ */ jsx2(Play, {
|
|
579
587
|
className: "h-3.5 w-3.5"
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
showDownload && /* @__PURE__ */
|
|
588
|
+
})
|
|
589
|
+
}),
|
|
590
|
+
showDownload && /* @__PURE__ */ jsx2(Button, {
|
|
583
591
|
variant: "ghost",
|
|
584
592
|
size: "sm",
|
|
585
593
|
onPress: handleDownload,
|
|
586
594
|
className: "h-7 w-7 p-0",
|
|
587
595
|
"aria-label": "Download code",
|
|
588
|
-
children: /* @__PURE__ */
|
|
596
|
+
children: /* @__PURE__ */ jsx2(Download, {
|
|
589
597
|
className: "h-3.5 w-3.5"
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
showCopy && /* @__PURE__ */
|
|
598
|
+
})
|
|
599
|
+
}),
|
|
600
|
+
showCopy && /* @__PURE__ */ jsx2(Button, {
|
|
593
601
|
variant: "ghost",
|
|
594
602
|
size: "sm",
|
|
595
603
|
onPress: handleCopy,
|
|
596
604
|
className: "h-7 w-7 p-0",
|
|
597
605
|
"aria-label": copied ? "Copied" : "Copy code",
|
|
598
|
-
children: copied ? /* @__PURE__ */
|
|
606
|
+
children: copied ? /* @__PURE__ */ jsx2(Check, {
|
|
599
607
|
className: "h-3.5 w-3.5 text-green-500"
|
|
600
|
-
}
|
|
608
|
+
}) : /* @__PURE__ */ jsx2(Copy, {
|
|
601
609
|
className: "h-3.5 w-3.5"
|
|
602
|
-
}
|
|
603
|
-
}
|
|
610
|
+
})
|
|
611
|
+
})
|
|
604
612
|
]
|
|
605
|
-
}
|
|
613
|
+
})
|
|
606
614
|
]
|
|
607
|
-
}
|
|
608
|
-
/* @__PURE__ */
|
|
615
|
+
}),
|
|
616
|
+
/* @__PURE__ */ jsx2("div", {
|
|
609
617
|
className: "overflow-auto",
|
|
610
618
|
style: { maxHeight },
|
|
611
|
-
children: /* @__PURE__ */
|
|
619
|
+
children: /* @__PURE__ */ jsx2("pre", {
|
|
612
620
|
className: "p-3",
|
|
613
|
-
children: /* @__PURE__ */
|
|
621
|
+
children: /* @__PURE__ */ jsx2("code", {
|
|
614
622
|
className: "text-sm",
|
|
615
|
-
children: lines.map((line, i) => /* @__PURE__ */
|
|
623
|
+
children: lines.map((line, i) => /* @__PURE__ */ jsxs2("div", {
|
|
616
624
|
className: "flex",
|
|
617
625
|
children: [
|
|
618
|
-
/* @__PURE__ */
|
|
626
|
+
/* @__PURE__ */ jsx2("span", {
|
|
619
627
|
className: "text-muted-foreground mr-4 w-8 text-right select-none",
|
|
620
628
|
children: i + 1
|
|
621
|
-
}
|
|
622
|
-
/* @__PURE__ */
|
|
629
|
+
}),
|
|
630
|
+
/* @__PURE__ */ jsx2("span", {
|
|
623
631
|
className: "flex-1",
|
|
624
632
|
children: line || " "
|
|
625
|
-
}
|
|
633
|
+
})
|
|
626
634
|
]
|
|
627
|
-
}, i
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
}
|
|
635
|
+
}, i))
|
|
636
|
+
})
|
|
637
|
+
})
|
|
638
|
+
})
|
|
639
|
+
]
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
// src/presentation/components/ToolResultRenderer.tsx
|
|
644
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
645
|
+
"use client";
|
|
646
|
+
function isPresentationToolResult(result) {
|
|
647
|
+
return typeof result === "object" && result !== null && "presentationKey" in result && typeof result.presentationKey === "string";
|
|
648
|
+
}
|
|
649
|
+
function isFormToolResult(result) {
|
|
650
|
+
return typeof result === "object" && result !== null && "formKey" in result && typeof result.formKey === "string";
|
|
651
|
+
}
|
|
652
|
+
function ToolResultRenderer({
|
|
653
|
+
toolName,
|
|
654
|
+
result,
|
|
655
|
+
presentationRenderer,
|
|
656
|
+
formRenderer,
|
|
657
|
+
showRawFallback = true
|
|
658
|
+
}) {
|
|
659
|
+
if (result === undefined || result === null) {
|
|
660
|
+
return null;
|
|
661
|
+
}
|
|
662
|
+
if (isPresentationToolResult(result) && presentationRenderer) {
|
|
663
|
+
const rendered = presentationRenderer(result.presentationKey, result.data);
|
|
664
|
+
if (rendered != null) {
|
|
665
|
+
return /* @__PURE__ */ jsx3("div", {
|
|
666
|
+
className: "mt-2 rounded-md border border-border bg-background/50 p-3",
|
|
667
|
+
children: rendered
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
if (isFormToolResult(result) && formRenderer) {
|
|
672
|
+
const rendered = formRenderer(result.formKey, result.defaultValues);
|
|
673
|
+
if (rendered != null) {
|
|
674
|
+
return /* @__PURE__ */ jsx3("div", {
|
|
675
|
+
className: "mt-2 rounded-md border border-border bg-background/50 p-3",
|
|
676
|
+
children: rendered
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
if (!showRawFallback) {
|
|
681
|
+
return null;
|
|
682
|
+
}
|
|
683
|
+
return /* @__PURE__ */ jsxs3("div", {
|
|
684
|
+
children: [
|
|
685
|
+
/* @__PURE__ */ jsx3("span", {
|
|
686
|
+
className: "text-muted-foreground font-medium",
|
|
687
|
+
children: "Output:"
|
|
688
|
+
}),
|
|
689
|
+
/* @__PURE__ */ jsx3("pre", {
|
|
690
|
+
className: "bg-background mt-1 overflow-x-auto rounded p-2 text-xs",
|
|
691
|
+
children: typeof result === "object" ? JSON.stringify(result, null, 2) : String(result)
|
|
692
|
+
})
|
|
631
693
|
]
|
|
632
|
-
}
|
|
694
|
+
});
|
|
633
695
|
}
|
|
634
696
|
|
|
635
697
|
// src/presentation/components/ChatMessage.tsx
|
|
636
|
-
import {
|
|
698
|
+
import { jsx as jsx4, jsxs as jsxs4, Fragment } from "react/jsx-runtime";
|
|
637
699
|
"use client";
|
|
638
700
|
function extractCodeBlocks(content) {
|
|
639
701
|
const codeBlockRegex = /```(\w+)?\n([\s\S]*?)```/g;
|
|
@@ -656,33 +718,33 @@ function renderInlineMarkdown(text) {
|
|
|
656
718
|
let key = 0;
|
|
657
719
|
while ((match = linkRegex.exec(text)) !== null) {
|
|
658
720
|
if (match.index > lastIndex) {
|
|
659
|
-
parts.push(/* @__PURE__ */
|
|
721
|
+
parts.push(/* @__PURE__ */ jsx4("span", {
|
|
660
722
|
children: text.slice(lastIndex, match.index)
|
|
661
|
-
}, key
|
|
723
|
+
}, key++));
|
|
662
724
|
}
|
|
663
|
-
parts.push(/* @__PURE__ */
|
|
725
|
+
parts.push(/* @__PURE__ */ jsx4("a", {
|
|
664
726
|
href: match[2],
|
|
665
727
|
target: "_blank",
|
|
666
728
|
rel: "noopener noreferrer",
|
|
667
729
|
className: "text-primary underline hover:no-underline",
|
|
668
730
|
children: match[1]
|
|
669
|
-
}, key
|
|
731
|
+
}, key++));
|
|
670
732
|
lastIndex = match.index + match[0].length;
|
|
671
733
|
}
|
|
672
734
|
if (lastIndex < text.length) {
|
|
673
|
-
parts.push(/* @__PURE__ */
|
|
735
|
+
parts.push(/* @__PURE__ */ jsx4("span", {
|
|
674
736
|
children: text.slice(lastIndex)
|
|
675
|
-
}, key
|
|
737
|
+
}, key++));
|
|
676
738
|
}
|
|
677
739
|
return parts.length > 0 ? parts : [text];
|
|
678
740
|
}
|
|
679
741
|
function MessageContent({ content }) {
|
|
680
742
|
const codeBlocks = extractCodeBlocks(content);
|
|
681
743
|
if (codeBlocks.length === 0) {
|
|
682
|
-
return /* @__PURE__ */
|
|
744
|
+
return /* @__PURE__ */ jsx4("p", {
|
|
683
745
|
className: "whitespace-pre-wrap",
|
|
684
746
|
children: renderInlineMarkdown(content)
|
|
685
|
-
}
|
|
747
|
+
});
|
|
686
748
|
}
|
|
687
749
|
let remaining = content;
|
|
688
750
|
const parts = [];
|
|
@@ -690,33 +752,40 @@ function MessageContent({ content }) {
|
|
|
690
752
|
for (const block of codeBlocks) {
|
|
691
753
|
const [before, after] = remaining.split(block.raw);
|
|
692
754
|
if (before) {
|
|
693
|
-
parts.push(/* @__PURE__ */
|
|
755
|
+
parts.push(/* @__PURE__ */ jsx4("p", {
|
|
694
756
|
className: "whitespace-pre-wrap",
|
|
695
757
|
children: renderInlineMarkdown(before.trim())
|
|
696
|
-
}, key
|
|
758
|
+
}, key++));
|
|
697
759
|
}
|
|
698
|
-
parts.push(/* @__PURE__ */
|
|
760
|
+
parts.push(/* @__PURE__ */ jsx4(CodePreview, {
|
|
699
761
|
code: block.code,
|
|
700
762
|
language: block.language,
|
|
701
763
|
className: "my-2"
|
|
702
|
-
}, key
|
|
764
|
+
}, key++));
|
|
703
765
|
remaining = after ?? "";
|
|
704
766
|
}
|
|
705
767
|
if (remaining.trim()) {
|
|
706
|
-
parts.push(/* @__PURE__ */
|
|
768
|
+
parts.push(/* @__PURE__ */ jsx4("p", {
|
|
707
769
|
className: "whitespace-pre-wrap",
|
|
708
770
|
children: renderInlineMarkdown(remaining.trim())
|
|
709
|
-
}, key
|
|
771
|
+
}, key++));
|
|
710
772
|
}
|
|
711
|
-
return /* @__PURE__ */
|
|
773
|
+
return /* @__PURE__ */ jsx4(Fragment, {
|
|
712
774
|
children: parts
|
|
713
|
-
}
|
|
775
|
+
});
|
|
714
776
|
}
|
|
715
777
|
function ChatMessage({
|
|
716
778
|
message,
|
|
717
779
|
className,
|
|
718
780
|
showCopy = true,
|
|
719
|
-
showAvatar = true
|
|
781
|
+
showAvatar = true,
|
|
782
|
+
selectable = false,
|
|
783
|
+
selected = false,
|
|
784
|
+
onSelect,
|
|
785
|
+
editable = false,
|
|
786
|
+
onEdit,
|
|
787
|
+
presentationRenderer,
|
|
788
|
+
formRenderer
|
|
720
789
|
}) {
|
|
721
790
|
const [copied, setCopied] = React3.useState(false);
|
|
722
791
|
const isUser = message.role === "user";
|
|
@@ -727,185 +796,262 @@ function ChatMessage({
|
|
|
727
796
|
setCopied(true);
|
|
728
797
|
setTimeout(() => setCopied(false), 2000);
|
|
729
798
|
}, [message.content]);
|
|
730
|
-
|
|
799
|
+
const handleSelectChange = React3.useCallback((checked) => {
|
|
800
|
+
if (checked !== "indeterminate")
|
|
801
|
+
onSelect?.(message.id);
|
|
802
|
+
}, [message.id, onSelect]);
|
|
803
|
+
const [isEditing, setIsEditing] = React3.useState(false);
|
|
804
|
+
const [editContent, setEditContent] = React3.useState(message.content);
|
|
805
|
+
React3.useEffect(() => {
|
|
806
|
+
setEditContent(message.content);
|
|
807
|
+
}, [message.content]);
|
|
808
|
+
const handleStartEdit = React3.useCallback(() => {
|
|
809
|
+
setEditContent(message.content);
|
|
810
|
+
setIsEditing(true);
|
|
811
|
+
}, [message.content]);
|
|
812
|
+
const handleSaveEdit = React3.useCallback(async () => {
|
|
813
|
+
const trimmed = editContent.trim();
|
|
814
|
+
if (trimmed !== message.content) {
|
|
815
|
+
await onEdit?.(message.id, trimmed);
|
|
816
|
+
}
|
|
817
|
+
setIsEditing(false);
|
|
818
|
+
}, [editContent, message.id, message.content, onEdit]);
|
|
819
|
+
const handleCancelEdit = React3.useCallback(() => {
|
|
820
|
+
setEditContent(message.content);
|
|
821
|
+
setIsEditing(false);
|
|
822
|
+
}, [message.content]);
|
|
823
|
+
return /* @__PURE__ */ jsxs4("div", {
|
|
731
824
|
className: cn3("group flex gap-3", isUser && "flex-row-reverse", className),
|
|
732
825
|
children: [
|
|
733
|
-
|
|
826
|
+
selectable && /* @__PURE__ */ jsx4("div", {
|
|
827
|
+
className: cn3("flex shrink-0 items-start pt-1", "opacity-0 transition-opacity group-hover:opacity-100"),
|
|
828
|
+
children: /* @__PURE__ */ jsx4(Checkbox, {
|
|
829
|
+
checked: selected,
|
|
830
|
+
onCheckedChange: handleSelectChange,
|
|
831
|
+
"aria-label": selected ? "Deselect message" : "Select message"
|
|
832
|
+
})
|
|
833
|
+
}),
|
|
834
|
+
showAvatar && /* @__PURE__ */ jsx4(Avatar, {
|
|
734
835
|
className: "h-8 w-8 shrink-0",
|
|
735
|
-
children: /* @__PURE__ */
|
|
836
|
+
children: /* @__PURE__ */ jsx4(AvatarFallback, {
|
|
736
837
|
className: cn3(isUser ? "bg-primary text-primary-foreground" : "bg-muted"),
|
|
737
|
-
children: isUser ? /* @__PURE__ */
|
|
838
|
+
children: isUser ? /* @__PURE__ */ jsx4(User, {
|
|
738
839
|
className: "h-4 w-4"
|
|
739
|
-
}
|
|
840
|
+
}) : /* @__PURE__ */ jsx4(Bot, {
|
|
740
841
|
className: "h-4 w-4"
|
|
741
|
-
}
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
/* @__PURE__ */
|
|
842
|
+
})
|
|
843
|
+
})
|
|
844
|
+
}),
|
|
845
|
+
/* @__PURE__ */ jsxs4("div", {
|
|
745
846
|
className: cn3("flex max-w-[80%] flex-col gap-1", isUser && "items-end"),
|
|
746
847
|
children: [
|
|
747
|
-
/* @__PURE__ */
|
|
848
|
+
/* @__PURE__ */ jsx4("div", {
|
|
748
849
|
className: cn3("rounded-2xl px-4 py-2", isUser ? "bg-primary text-primary-foreground" : "bg-muted text-foreground", isError && "border-destructive bg-destructive/10 border"),
|
|
749
|
-
children: isError && message.error ? /* @__PURE__ */
|
|
850
|
+
children: isError && message.error ? /* @__PURE__ */ jsxs4("div", {
|
|
750
851
|
className: "flex items-start gap-2",
|
|
751
852
|
children: [
|
|
752
|
-
/* @__PURE__ */
|
|
853
|
+
/* @__PURE__ */ jsx4(AlertCircle, {
|
|
753
854
|
className: "text-destructive mt-0.5 h-4 w-4 shrink-0"
|
|
754
|
-
}
|
|
755
|
-
/* @__PURE__ */
|
|
855
|
+
}),
|
|
856
|
+
/* @__PURE__ */ jsxs4("div", {
|
|
756
857
|
children: [
|
|
757
|
-
/* @__PURE__ */
|
|
858
|
+
/* @__PURE__ */ jsx4("p", {
|
|
758
859
|
className: "text-destructive font-medium",
|
|
759
860
|
children: message.error.code
|
|
760
|
-
}
|
|
761
|
-
/* @__PURE__ */
|
|
861
|
+
}),
|
|
862
|
+
/* @__PURE__ */ jsx4("p", {
|
|
762
863
|
className: "text-muted-foreground text-sm",
|
|
763
864
|
children: message.error.message
|
|
764
|
-
}
|
|
865
|
+
})
|
|
866
|
+
]
|
|
867
|
+
})
|
|
868
|
+
]
|
|
869
|
+
}) : isEditing ? /* @__PURE__ */ jsxs4("div", {
|
|
870
|
+
className: "flex flex-col gap-2",
|
|
871
|
+
children: [
|
|
872
|
+
/* @__PURE__ */ jsx4("textarea", {
|
|
873
|
+
value: editContent,
|
|
874
|
+
onChange: (e) => setEditContent(e.target.value),
|
|
875
|
+
className: "bg-background/50 min-h-[80px] w-full resize-y rounded-md border px-3 py-2 text-sm",
|
|
876
|
+
rows: 4,
|
|
877
|
+
autoFocus: true
|
|
878
|
+
}),
|
|
879
|
+
/* @__PURE__ */ jsxs4("div", {
|
|
880
|
+
className: "flex gap-2",
|
|
881
|
+
children: [
|
|
882
|
+
/* @__PURE__ */ jsxs4(Button2, {
|
|
883
|
+
variant: "default",
|
|
884
|
+
size: "sm",
|
|
885
|
+
onPress: handleSaveEdit,
|
|
886
|
+
"aria-label": "Save edit",
|
|
887
|
+
children: [
|
|
888
|
+
/* @__PURE__ */ jsx4(Check2, {
|
|
889
|
+
className: "h-3 w-3"
|
|
890
|
+
}),
|
|
891
|
+
"Save"
|
|
892
|
+
]
|
|
893
|
+
}),
|
|
894
|
+
/* @__PURE__ */ jsxs4(Button2, {
|
|
895
|
+
variant: "ghost",
|
|
896
|
+
size: "sm",
|
|
897
|
+
onPress: handleCancelEdit,
|
|
898
|
+
"aria-label": "Cancel edit",
|
|
899
|
+
children: [
|
|
900
|
+
/* @__PURE__ */ jsx4(X, {
|
|
901
|
+
className: "h-3 w-3"
|
|
902
|
+
}),
|
|
903
|
+
"Cancel"
|
|
904
|
+
]
|
|
905
|
+
})
|
|
765
906
|
]
|
|
766
|
-
}
|
|
907
|
+
})
|
|
767
908
|
]
|
|
768
|
-
}
|
|
909
|
+
}) : isStreaming && !message.content ? /* @__PURE__ */ jsxs4("div", {
|
|
769
910
|
className: "flex flex-col gap-2",
|
|
770
911
|
children: [
|
|
771
|
-
/* @__PURE__ */
|
|
912
|
+
/* @__PURE__ */ jsx4(Skeleton, {
|
|
772
913
|
className: "h-4 w-48"
|
|
773
|
-
}
|
|
774
|
-
/* @__PURE__ */
|
|
914
|
+
}),
|
|
915
|
+
/* @__PURE__ */ jsx4(Skeleton, {
|
|
775
916
|
className: "h-4 w-32"
|
|
776
|
-
}
|
|
917
|
+
})
|
|
777
918
|
]
|
|
778
|
-
}
|
|
919
|
+
}) : /* @__PURE__ */ jsx4(MessageContent, {
|
|
779
920
|
content: message.content
|
|
780
|
-
}
|
|
781
|
-
}
|
|
782
|
-
/* @__PURE__ */
|
|
921
|
+
})
|
|
922
|
+
}),
|
|
923
|
+
/* @__PURE__ */ jsxs4("div", {
|
|
783
924
|
className: cn3("flex items-center gap-2 text-xs", "text-muted-foreground opacity-0 transition-opacity", "group-hover:opacity-100"),
|
|
784
925
|
children: [
|
|
785
|
-
/* @__PURE__ */
|
|
926
|
+
/* @__PURE__ */ jsx4("span", {
|
|
786
927
|
children: new Date(message.createdAt).toLocaleTimeString([], {
|
|
787
928
|
hour: "2-digit",
|
|
788
929
|
minute: "2-digit"
|
|
789
930
|
})
|
|
790
|
-
}
|
|
791
|
-
message.usage && /* @__PURE__ */
|
|
931
|
+
}),
|
|
932
|
+
message.usage && /* @__PURE__ */ jsxs4("span", {
|
|
792
933
|
children: [
|
|
793
934
|
message.usage.inputTokens + message.usage.outputTokens,
|
|
794
935
|
" tokens"
|
|
795
936
|
]
|
|
796
|
-
}
|
|
797
|
-
showCopy && !isUser && message.content && /* @__PURE__ */
|
|
937
|
+
}),
|
|
938
|
+
showCopy && !isUser && message.content && /* @__PURE__ */ jsx4(Button2, {
|
|
798
939
|
variant: "ghost",
|
|
799
940
|
size: "sm",
|
|
800
941
|
className: "h-6 w-6 p-0",
|
|
801
942
|
onPress: handleCopy,
|
|
802
943
|
"aria-label": copied ? "Copied" : "Copy message",
|
|
803
|
-
children: copied ? /* @__PURE__ */
|
|
944
|
+
children: copied ? /* @__PURE__ */ jsx4(Check2, {
|
|
945
|
+
className: "h-3 w-3"
|
|
946
|
+
}) : /* @__PURE__ */ jsx4(Copy2, {
|
|
804
947
|
className: "h-3 w-3"
|
|
805
|
-
}
|
|
948
|
+
})
|
|
949
|
+
}),
|
|
950
|
+
editable && isUser && !isEditing && /* @__PURE__ */ jsx4(Button2, {
|
|
951
|
+
variant: "ghost",
|
|
952
|
+
size: "sm",
|
|
953
|
+
className: "h-6 w-6 p-0",
|
|
954
|
+
onPress: handleStartEdit,
|
|
955
|
+
"aria-label": "Edit message",
|
|
956
|
+
children: /* @__PURE__ */ jsx4(Pencil, {
|
|
806
957
|
className: "h-3 w-3"
|
|
807
|
-
}
|
|
808
|
-
}
|
|
958
|
+
})
|
|
959
|
+
})
|
|
809
960
|
]
|
|
810
|
-
}
|
|
811
|
-
message.reasoning && /* @__PURE__ */
|
|
961
|
+
}),
|
|
962
|
+
message.reasoning && /* @__PURE__ */ jsxs4("details", {
|
|
812
963
|
className: "text-muted-foreground mt-2 text-sm",
|
|
813
964
|
children: [
|
|
814
|
-
/* @__PURE__ */
|
|
965
|
+
/* @__PURE__ */ jsx4("summary", {
|
|
815
966
|
className: "cursor-pointer hover:underline",
|
|
816
967
|
children: "View reasoning"
|
|
817
|
-
}
|
|
818
|
-
/* @__PURE__ */
|
|
968
|
+
}),
|
|
969
|
+
/* @__PURE__ */ jsx4("div", {
|
|
819
970
|
className: "bg-muted mt-1 rounded-md p-2",
|
|
820
|
-
children: /* @__PURE__ */
|
|
971
|
+
children: /* @__PURE__ */ jsx4("p", {
|
|
821
972
|
className: "whitespace-pre-wrap",
|
|
822
973
|
children: message.reasoning
|
|
823
|
-
}
|
|
824
|
-
}
|
|
974
|
+
})
|
|
975
|
+
})
|
|
825
976
|
]
|
|
826
|
-
}
|
|
827
|
-
message.sources && message.sources.length > 0 && /* @__PURE__ */
|
|
977
|
+
}),
|
|
978
|
+
message.sources && message.sources.length > 0 && /* @__PURE__ */ jsx4("div", {
|
|
828
979
|
className: "mt-2 flex flex-wrap gap-2",
|
|
829
|
-
children: message.sources.map((source) => /* @__PURE__ */
|
|
980
|
+
children: message.sources.map((source) => /* @__PURE__ */ jsxs4("a", {
|
|
830
981
|
href: source.url ?? "#",
|
|
831
982
|
target: "_blank",
|
|
832
983
|
rel: "noopener noreferrer",
|
|
833
984
|
className: "text-muted-foreground hover:text-foreground bg-muted inline-flex items-center gap-1 rounded-md px-2 py-1 text-xs transition-colors",
|
|
834
985
|
children: [
|
|
835
|
-
/* @__PURE__ */
|
|
986
|
+
/* @__PURE__ */ jsx4(ExternalLink, {
|
|
836
987
|
className: "h-3 w-3"
|
|
837
|
-
}
|
|
988
|
+
}),
|
|
838
989
|
source.title || source.url || source.id
|
|
839
990
|
]
|
|
840
|
-
}, source.id
|
|
841
|
-
}
|
|
842
|
-
message.toolCalls && message.toolCalls.length > 0 && /* @__PURE__ */
|
|
991
|
+
}, source.id))
|
|
992
|
+
}),
|
|
993
|
+
message.toolCalls && message.toolCalls.length > 0 && /* @__PURE__ */ jsx4("div", {
|
|
843
994
|
className: "mt-2 space-y-2",
|
|
844
|
-
children: message.toolCalls.map((tc) => /* @__PURE__ */
|
|
995
|
+
children: message.toolCalls.map((tc) => /* @__PURE__ */ jsxs4("details", {
|
|
845
996
|
className: "bg-muted border-border rounded-md border",
|
|
846
997
|
children: [
|
|
847
|
-
/* @__PURE__ */
|
|
998
|
+
/* @__PURE__ */ jsxs4("summary", {
|
|
848
999
|
className: "flex cursor-pointer items-center gap-2 px-3 py-2 text-sm font-medium",
|
|
849
1000
|
children: [
|
|
850
|
-
/* @__PURE__ */
|
|
1001
|
+
/* @__PURE__ */ jsx4(Wrench, {
|
|
851
1002
|
className: "text-muted-foreground h-4 w-4"
|
|
852
|
-
}
|
|
1003
|
+
}),
|
|
853
1004
|
tc.name,
|
|
854
|
-
/* @__PURE__ */
|
|
1005
|
+
/* @__PURE__ */ jsx4("span", {
|
|
855
1006
|
className: cn3("ml-auto rounded px-1.5 py-0.5 text-xs", tc.status === "completed" && "bg-green-500/20 text-green-700 dark:text-green-400", tc.status === "error" && "bg-destructive/20 text-destructive", tc.status === "running" && "bg-blue-500/20 text-blue-700 dark:text-blue-400"),
|
|
856
1007
|
children: tc.status
|
|
857
|
-
}
|
|
1008
|
+
})
|
|
858
1009
|
]
|
|
859
|
-
}
|
|
860
|
-
/* @__PURE__ */
|
|
1010
|
+
}),
|
|
1011
|
+
/* @__PURE__ */ jsxs4("div", {
|
|
861
1012
|
className: "border-border border-t px-3 py-2 text-xs",
|
|
862
1013
|
children: [
|
|
863
|
-
Object.keys(tc.args).length > 0 && /* @__PURE__ */
|
|
1014
|
+
Object.keys(tc.args).length > 0 && /* @__PURE__ */ jsxs4("div", {
|
|
864
1015
|
className: "mb-2",
|
|
865
1016
|
children: [
|
|
866
|
-
/* @__PURE__ */
|
|
1017
|
+
/* @__PURE__ */ jsx4("span", {
|
|
867
1018
|
className: "text-muted-foreground font-medium",
|
|
868
1019
|
children: "Input:"
|
|
869
|
-
}
|
|
870
|
-
/* @__PURE__ */
|
|
1020
|
+
}),
|
|
1021
|
+
/* @__PURE__ */ jsx4("pre", {
|
|
871
1022
|
className: "bg-background mt-1 overflow-x-auto rounded p-2",
|
|
872
1023
|
children: JSON.stringify(tc.args, null, 2)
|
|
873
|
-
}
|
|
1024
|
+
})
|
|
874
1025
|
]
|
|
875
|
-
}
|
|
876
|
-
tc.result !== undefined && /* @__PURE__ */
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
children: typeof tc.result === "object" ? JSON.stringify(tc.result, null, 2) : String(tc.result)
|
|
885
|
-
}, undefined, false, undefined, this)
|
|
886
|
-
]
|
|
887
|
-
}, undefined, true, undefined, this),
|
|
888
|
-
tc.error && /* @__PURE__ */ jsxDEV3("p", {
|
|
1026
|
+
}),
|
|
1027
|
+
tc.result !== undefined && /* @__PURE__ */ jsx4(ToolResultRenderer, {
|
|
1028
|
+
toolName: tc.name,
|
|
1029
|
+
result: tc.result,
|
|
1030
|
+
presentationRenderer,
|
|
1031
|
+
formRenderer,
|
|
1032
|
+
showRawFallback: true
|
|
1033
|
+
}),
|
|
1034
|
+
tc.error && /* @__PURE__ */ jsx4("p", {
|
|
889
1035
|
className: "text-destructive mt-1",
|
|
890
1036
|
children: tc.error
|
|
891
|
-
}
|
|
1037
|
+
})
|
|
892
1038
|
]
|
|
893
|
-
}
|
|
1039
|
+
})
|
|
894
1040
|
]
|
|
895
|
-
}, tc.id
|
|
896
|
-
}
|
|
1041
|
+
}, tc.id))
|
|
1042
|
+
})
|
|
897
1043
|
]
|
|
898
|
-
}
|
|
1044
|
+
})
|
|
899
1045
|
]
|
|
900
|
-
}
|
|
1046
|
+
});
|
|
901
1047
|
}
|
|
902
1048
|
// src/presentation/components/ChatInput.tsx
|
|
903
1049
|
import * as React4 from "react";
|
|
904
1050
|
import { cn as cn4 } from "@contractspec/lib.ui-kit-web/ui/utils";
|
|
905
1051
|
import { Textarea } from "@contractspec/lib.design-system";
|
|
906
1052
|
import { Button as Button3 } from "@contractspec/lib.design-system";
|
|
907
|
-
import { Send, Paperclip, X, Loader2, FileText, Code } from "lucide-react";
|
|
908
|
-
import {
|
|
1053
|
+
import { Send, Paperclip, X as X2, Loader2, FileText, Code } from "lucide-react";
|
|
1054
|
+
import { jsx as jsx5, jsxs as jsxs5, Fragment as Fragment2 } from "react/jsx-runtime";
|
|
909
1055
|
"use client";
|
|
910
1056
|
function ChatInput({
|
|
911
1057
|
onSend,
|
|
@@ -971,42 +1117,42 @@ function ChatInput({
|
|
|
971
1117
|
const removeAttachment = React4.useCallback((id) => {
|
|
972
1118
|
setAttachments((prev) => prev.filter((a) => a.id !== id));
|
|
973
1119
|
}, []);
|
|
974
|
-
return /* @__PURE__ */
|
|
1120
|
+
return /* @__PURE__ */ jsxs5("div", {
|
|
975
1121
|
className: cn4("flex flex-col gap-2", className),
|
|
976
1122
|
children: [
|
|
977
|
-
attachments.length > 0 && /* @__PURE__ */
|
|
1123
|
+
attachments.length > 0 && /* @__PURE__ */ jsx5("div", {
|
|
978
1124
|
className: "flex flex-wrap gap-2",
|
|
979
|
-
children: attachments.map((attachment) => /* @__PURE__ */
|
|
1125
|
+
children: attachments.map((attachment) => /* @__PURE__ */ jsxs5("div", {
|
|
980
1126
|
className: cn4("flex items-center gap-1.5 rounded-md px-2 py-1", "bg-muted text-muted-foreground text-sm"),
|
|
981
1127
|
children: [
|
|
982
|
-
attachment.type === "code" ? /* @__PURE__ */
|
|
1128
|
+
attachment.type === "code" ? /* @__PURE__ */ jsx5(Code, {
|
|
983
1129
|
className: "h-3.5 w-3.5"
|
|
984
|
-
}
|
|
1130
|
+
}) : /* @__PURE__ */ jsx5(FileText, {
|
|
985
1131
|
className: "h-3.5 w-3.5"
|
|
986
|
-
}
|
|
987
|
-
/* @__PURE__ */
|
|
1132
|
+
}),
|
|
1133
|
+
/* @__PURE__ */ jsx5("span", {
|
|
988
1134
|
className: "max-w-[150px] truncate",
|
|
989
1135
|
children: attachment.name
|
|
990
|
-
}
|
|
991
|
-
/* @__PURE__ */
|
|
1136
|
+
}),
|
|
1137
|
+
/* @__PURE__ */ jsx5("button", {
|
|
992
1138
|
type: "button",
|
|
993
1139
|
onClick: () => removeAttachment(attachment.id),
|
|
994
1140
|
className: "hover:text-foreground",
|
|
995
1141
|
"aria-label": `Remove ${attachment.name}`,
|
|
996
|
-
children: /* @__PURE__ */
|
|
1142
|
+
children: /* @__PURE__ */ jsx5(X2, {
|
|
997
1143
|
className: "h-3.5 w-3.5"
|
|
998
|
-
}
|
|
999
|
-
}
|
|
1144
|
+
})
|
|
1145
|
+
})
|
|
1000
1146
|
]
|
|
1001
|
-
}, attachment.id
|
|
1002
|
-
}
|
|
1003
|
-
/* @__PURE__ */
|
|
1147
|
+
}, attachment.id))
|
|
1148
|
+
}),
|
|
1149
|
+
/* @__PURE__ */ jsxs5("form", {
|
|
1004
1150
|
onSubmit: handleSubmit,
|
|
1005
1151
|
className: "flex items-end gap-2",
|
|
1006
1152
|
children: [
|
|
1007
|
-
showAttachments && /* @__PURE__ */
|
|
1153
|
+
showAttachments && /* @__PURE__ */ jsxs5(Fragment2, {
|
|
1008
1154
|
children: [
|
|
1009
|
-
/* @__PURE__ */
|
|
1155
|
+
/* @__PURE__ */ jsx5("input", {
|
|
1010
1156
|
ref: fileInputRef,
|
|
1011
1157
|
type: "file",
|
|
1012
1158
|
multiple: true,
|
|
@@ -1014,23 +1160,23 @@ function ChatInput({
|
|
|
1014
1160
|
onChange: handleFileSelect,
|
|
1015
1161
|
className: "hidden",
|
|
1016
1162
|
"aria-label": "Attach files"
|
|
1017
|
-
}
|
|
1018
|
-
/* @__PURE__ */
|
|
1163
|
+
}),
|
|
1164
|
+
/* @__PURE__ */ jsx5(Button3, {
|
|
1019
1165
|
type: "button",
|
|
1020
1166
|
variant: "ghost",
|
|
1021
1167
|
size: "sm",
|
|
1022
1168
|
onPress: () => fileInputRef.current?.click(),
|
|
1023
1169
|
disabled: disabled || attachments.length >= maxAttachments,
|
|
1024
1170
|
"aria-label": "Attach files",
|
|
1025
|
-
children: /* @__PURE__ */
|
|
1171
|
+
children: /* @__PURE__ */ jsx5(Paperclip, {
|
|
1026
1172
|
className: "h-4 w-4"
|
|
1027
|
-
}
|
|
1028
|
-
}
|
|
1173
|
+
})
|
|
1174
|
+
})
|
|
1029
1175
|
]
|
|
1030
|
-
}
|
|
1031
|
-
/* @__PURE__ */
|
|
1176
|
+
}),
|
|
1177
|
+
/* @__PURE__ */ jsx5("div", {
|
|
1032
1178
|
className: "relative flex-1",
|
|
1033
|
-
children: /* @__PURE__ */
|
|
1179
|
+
children: /* @__PURE__ */ jsx5(Textarea, {
|
|
1034
1180
|
value: content,
|
|
1035
1181
|
onChange: (e) => setContent(e.target.value),
|
|
1036
1182
|
onKeyDown: handleKeyDown,
|
|
@@ -1039,32 +1185,418 @@ function ChatInput({
|
|
|
1039
1185
|
className: cn4("max-h-[200px] min-h-[44px] resize-none pr-12", "focus-visible:ring-1"),
|
|
1040
1186
|
rows: 1,
|
|
1041
1187
|
"aria-label": "Chat message"
|
|
1042
|
-
}
|
|
1043
|
-
}
|
|
1044
|
-
/* @__PURE__ */
|
|
1188
|
+
})
|
|
1189
|
+
}),
|
|
1190
|
+
/* @__PURE__ */ jsx5(Button3, {
|
|
1045
1191
|
type: "submit",
|
|
1046
1192
|
disabled: !canSend || disabled || isLoading,
|
|
1047
1193
|
size: "sm",
|
|
1048
1194
|
"aria-label": isLoading ? "Sending..." : "Send message",
|
|
1049
|
-
children: isLoading ? /* @__PURE__ */
|
|
1195
|
+
children: isLoading ? /* @__PURE__ */ jsx5(Loader2, {
|
|
1050
1196
|
className: "h-4 w-4 animate-spin"
|
|
1051
|
-
}
|
|
1197
|
+
}) : /* @__PURE__ */ jsx5(Send, {
|
|
1052
1198
|
className: "h-4 w-4"
|
|
1053
|
-
}
|
|
1054
|
-
}
|
|
1199
|
+
})
|
|
1200
|
+
})
|
|
1055
1201
|
]
|
|
1056
|
-
}
|
|
1057
|
-
/* @__PURE__ */
|
|
1202
|
+
}),
|
|
1203
|
+
/* @__PURE__ */ jsx5("p", {
|
|
1058
1204
|
className: "text-muted-foreground text-xs",
|
|
1059
1205
|
children: "Press Enter to send, Shift+Enter for new line"
|
|
1060
|
-
}
|
|
1206
|
+
})
|
|
1061
1207
|
]
|
|
1062
|
-
}
|
|
1208
|
+
});
|
|
1063
1209
|
}
|
|
1064
|
-
// src/presentation/components/
|
|
1210
|
+
// src/presentation/components/ChatExportToolbar.tsx
|
|
1065
1211
|
import * as React5 from "react";
|
|
1066
|
-
import {
|
|
1212
|
+
import { Download as Download2, FileText as FileText2, Copy as Copy3, Check as Check3, Plus, GitFork } from "lucide-react";
|
|
1067
1213
|
import { Button as Button4 } from "@contractspec/lib.design-system";
|
|
1214
|
+
import {
|
|
1215
|
+
DropdownMenu,
|
|
1216
|
+
DropdownMenuContent,
|
|
1217
|
+
DropdownMenuItem,
|
|
1218
|
+
DropdownMenuSeparator,
|
|
1219
|
+
DropdownMenuTrigger
|
|
1220
|
+
} from "@contractspec/lib.ui-kit-web/ui/dropdown-menu";
|
|
1221
|
+
|
|
1222
|
+
// src/core/export-formatters.ts
|
|
1223
|
+
function formatTimestamp(date) {
|
|
1224
|
+
return date.toLocaleTimeString([], {
|
|
1225
|
+
hour: "2-digit",
|
|
1226
|
+
minute: "2-digit"
|
|
1227
|
+
});
|
|
1228
|
+
}
|
|
1229
|
+
function toIsoString(date) {
|
|
1230
|
+
return date.toISOString();
|
|
1231
|
+
}
|
|
1232
|
+
function messageToJsonSerializable(msg) {
|
|
1233
|
+
return {
|
|
1234
|
+
id: msg.id,
|
|
1235
|
+
conversationId: msg.conversationId,
|
|
1236
|
+
role: msg.role,
|
|
1237
|
+
content: msg.content,
|
|
1238
|
+
status: msg.status,
|
|
1239
|
+
createdAt: toIsoString(msg.createdAt),
|
|
1240
|
+
updatedAt: toIsoString(msg.updatedAt),
|
|
1241
|
+
...msg.attachments && { attachments: msg.attachments },
|
|
1242
|
+
...msg.codeBlocks && { codeBlocks: msg.codeBlocks },
|
|
1243
|
+
...msg.toolCalls && { toolCalls: msg.toolCalls },
|
|
1244
|
+
...msg.sources && { sources: msg.sources },
|
|
1245
|
+
...msg.reasoning && { reasoning: msg.reasoning },
|
|
1246
|
+
...msg.usage && { usage: msg.usage },
|
|
1247
|
+
...msg.error && { error: msg.error },
|
|
1248
|
+
...msg.metadata && { metadata: msg.metadata }
|
|
1249
|
+
};
|
|
1250
|
+
}
|
|
1251
|
+
function formatSourcesMarkdown(sources) {
|
|
1252
|
+
if (sources.length === 0)
|
|
1253
|
+
return "";
|
|
1254
|
+
return `
|
|
1255
|
+
|
|
1256
|
+
**Sources:**
|
|
1257
|
+
` + sources.map((s) => `- [${s.title}](${s.url ?? "#"})`).join(`
|
|
1258
|
+
`);
|
|
1259
|
+
}
|
|
1260
|
+
function formatSourcesTxt(sources) {
|
|
1261
|
+
if (sources.length === 0)
|
|
1262
|
+
return "";
|
|
1263
|
+
return `
|
|
1264
|
+
|
|
1265
|
+
Sources:
|
|
1266
|
+
` + sources.map((s) => `- ${s.title}${s.url ? ` - ${s.url}` : ""}`).join(`
|
|
1267
|
+
`);
|
|
1268
|
+
}
|
|
1269
|
+
function formatToolCallsMarkdown(toolCalls) {
|
|
1270
|
+
if (toolCalls.length === 0)
|
|
1271
|
+
return "";
|
|
1272
|
+
return `
|
|
1273
|
+
|
|
1274
|
+
**Tool calls:**
|
|
1275
|
+
` + toolCalls.map((tc) => `**${tc.name}** (${tc.status})
|
|
1276
|
+
\`\`\`json
|
|
1277
|
+
${JSON.stringify(tc.args, null, 2)}
|
|
1278
|
+
\`\`\`` + (tc.result !== undefined ? `
|
|
1279
|
+
Output:
|
|
1280
|
+
\`\`\`json
|
|
1281
|
+
${typeof tc.result === "object" ? JSON.stringify(tc.result, null, 2) : String(tc.result)}
|
|
1282
|
+
\`\`\`` : "") + (tc.error ? `
|
|
1283
|
+
Error: ${tc.error}` : "")).join(`
|
|
1284
|
+
|
|
1285
|
+
`);
|
|
1286
|
+
}
|
|
1287
|
+
function formatToolCallsTxt(toolCalls) {
|
|
1288
|
+
if (toolCalls.length === 0)
|
|
1289
|
+
return "";
|
|
1290
|
+
return `
|
|
1291
|
+
|
|
1292
|
+
Tool calls:
|
|
1293
|
+
` + toolCalls.map((tc) => `- ${tc.name} (${tc.status}): ${JSON.stringify(tc.args)}` + (tc.result !== undefined ? ` -> ${typeof tc.result === "object" ? JSON.stringify(tc.result) : String(tc.result)}` : "") + (tc.error ? ` [Error: ${tc.error}]` : "")).join(`
|
|
1294
|
+
`);
|
|
1295
|
+
}
|
|
1296
|
+
function formatUsage(usage) {
|
|
1297
|
+
const total = usage.inputTokens + usage.outputTokens;
|
|
1298
|
+
return ` (${total} tokens)`;
|
|
1299
|
+
}
|
|
1300
|
+
function formatMessagesAsMarkdown(messages) {
|
|
1301
|
+
const parts = [];
|
|
1302
|
+
for (const msg of messages) {
|
|
1303
|
+
const roleLabel = msg.role === "user" ? "User" : msg.role === "assistant" ? "Assistant" : "System";
|
|
1304
|
+
const header = `## ${roleLabel}`;
|
|
1305
|
+
const timestamp = `*${formatTimestamp(msg.createdAt)}*`;
|
|
1306
|
+
const usageSuffix = msg.usage ? formatUsage(msg.usage) : "";
|
|
1307
|
+
const meta = `${timestamp}${usageSuffix}
|
|
1308
|
+
|
|
1309
|
+
`;
|
|
1310
|
+
let body = msg.content;
|
|
1311
|
+
if (msg.error) {
|
|
1312
|
+
body += `
|
|
1313
|
+
|
|
1314
|
+
**Error:** ${msg.error.code} - ${msg.error.message}`;
|
|
1315
|
+
}
|
|
1316
|
+
if (msg.reasoning) {
|
|
1317
|
+
body += `
|
|
1318
|
+
|
|
1319
|
+
> **Reasoning:**
|
|
1320
|
+
> ${msg.reasoning.replace(/\n/g, `
|
|
1321
|
+
> `)}`;
|
|
1322
|
+
}
|
|
1323
|
+
body += formatSourcesMarkdown(msg.sources ?? []);
|
|
1324
|
+
body += formatToolCallsMarkdown(msg.toolCalls ?? []);
|
|
1325
|
+
parts.push(`${header}
|
|
1326
|
+
|
|
1327
|
+
${meta}${body}`);
|
|
1328
|
+
}
|
|
1329
|
+
return parts.join(`
|
|
1330
|
+
|
|
1331
|
+
---
|
|
1332
|
+
|
|
1333
|
+
`);
|
|
1334
|
+
}
|
|
1335
|
+
function formatMessagesAsTxt(messages) {
|
|
1336
|
+
const parts = [];
|
|
1337
|
+
for (const msg of messages) {
|
|
1338
|
+
const roleLabel = msg.role === "user" ? "User" : msg.role === "assistant" ? "Assistant" : "System";
|
|
1339
|
+
const timestamp = `(${formatTimestamp(msg.createdAt)})`;
|
|
1340
|
+
const usageSuffix = msg.usage ? formatUsage(msg.usage) : "";
|
|
1341
|
+
const header = `[${roleLabel}] ${timestamp}${usageSuffix}
|
|
1342
|
+
|
|
1343
|
+
`;
|
|
1344
|
+
let body = msg.content;
|
|
1345
|
+
if (msg.error) {
|
|
1346
|
+
body += `
|
|
1347
|
+
|
|
1348
|
+
Error: ${msg.error.code} - ${msg.error.message}`;
|
|
1349
|
+
}
|
|
1350
|
+
if (msg.reasoning) {
|
|
1351
|
+
body += `
|
|
1352
|
+
|
|
1353
|
+
Reasoning: ${msg.reasoning}`;
|
|
1354
|
+
}
|
|
1355
|
+
body += formatSourcesTxt(msg.sources ?? []);
|
|
1356
|
+
body += formatToolCallsTxt(msg.toolCalls ?? []);
|
|
1357
|
+
parts.push(`${header}${body}`);
|
|
1358
|
+
}
|
|
1359
|
+
return parts.join(`
|
|
1360
|
+
|
|
1361
|
+
---
|
|
1362
|
+
|
|
1363
|
+
`);
|
|
1364
|
+
}
|
|
1365
|
+
function formatMessagesAsJson(messages, conversation) {
|
|
1366
|
+
const payload = {
|
|
1367
|
+
messages: messages.map(messageToJsonSerializable)
|
|
1368
|
+
};
|
|
1369
|
+
if (conversation) {
|
|
1370
|
+
payload.conversation = {
|
|
1371
|
+
id: conversation.id,
|
|
1372
|
+
title: conversation.title,
|
|
1373
|
+
status: conversation.status,
|
|
1374
|
+
createdAt: toIsoString(conversation.createdAt),
|
|
1375
|
+
updatedAt: toIsoString(conversation.updatedAt),
|
|
1376
|
+
provider: conversation.provider,
|
|
1377
|
+
model: conversation.model,
|
|
1378
|
+
workspacePath: conversation.workspacePath,
|
|
1379
|
+
contextFiles: conversation.contextFiles,
|
|
1380
|
+
summary: conversation.summary,
|
|
1381
|
+
metadata: conversation.metadata
|
|
1382
|
+
};
|
|
1383
|
+
}
|
|
1384
|
+
return JSON.stringify(payload, null, 2);
|
|
1385
|
+
}
|
|
1386
|
+
function getExportFilename(format, conversation) {
|
|
1387
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
1388
|
+
const base = conversation?.title ? conversation.title.replace(/[^a-zA-Z0-9-_]/g, "_").slice(0, 40) : "chat-export";
|
|
1389
|
+
const ext = format === "markdown" ? "md" : format === "txt" ? "txt" : "json";
|
|
1390
|
+
return `${base}-${timestamp}.${ext}`;
|
|
1391
|
+
}
|
|
1392
|
+
var MIME_TYPES = {
|
|
1393
|
+
markdown: "text/markdown",
|
|
1394
|
+
txt: "text/plain",
|
|
1395
|
+
json: "application/json"
|
|
1396
|
+
};
|
|
1397
|
+
function downloadAsFile(content, filename, mimeType) {
|
|
1398
|
+
const blob = new Blob([content], { type: mimeType });
|
|
1399
|
+
const url = URL.createObjectURL(blob);
|
|
1400
|
+
const a = document.createElement("a");
|
|
1401
|
+
a.href = url;
|
|
1402
|
+
a.download = filename;
|
|
1403
|
+
document.body.appendChild(a);
|
|
1404
|
+
a.click();
|
|
1405
|
+
document.body.removeChild(a);
|
|
1406
|
+
URL.revokeObjectURL(url);
|
|
1407
|
+
}
|
|
1408
|
+
function exportToFile(messages, format, conversation) {
|
|
1409
|
+
let content;
|
|
1410
|
+
if (format === "markdown") {
|
|
1411
|
+
content = formatMessagesAsMarkdown(messages);
|
|
1412
|
+
} else if (format === "txt") {
|
|
1413
|
+
content = formatMessagesAsTxt(messages);
|
|
1414
|
+
} else {
|
|
1415
|
+
content = formatMessagesAsJson(messages, conversation);
|
|
1416
|
+
}
|
|
1417
|
+
const filename = getExportFilename(format, conversation);
|
|
1418
|
+
const mimeType = MIME_TYPES[format];
|
|
1419
|
+
downloadAsFile(content, filename, mimeType);
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
// src/presentation/components/ChatExportToolbar.tsx
|
|
1423
|
+
import { jsx as jsx6, jsxs as jsxs6, Fragment as Fragment3 } from "react/jsx-runtime";
|
|
1424
|
+
"use client";
|
|
1425
|
+
function ChatExportToolbar({
|
|
1426
|
+
messages,
|
|
1427
|
+
conversation,
|
|
1428
|
+
selectedIds,
|
|
1429
|
+
onExported,
|
|
1430
|
+
showSelectionSummary = true,
|
|
1431
|
+
onSelectAll,
|
|
1432
|
+
onClearSelection,
|
|
1433
|
+
selectedCount = selectedIds.size,
|
|
1434
|
+
totalCount = messages.length,
|
|
1435
|
+
onCreateNew,
|
|
1436
|
+
onFork
|
|
1437
|
+
}) {
|
|
1438
|
+
const [copied, setCopied] = React5.useState(false);
|
|
1439
|
+
const toExport = React5.useMemo(() => {
|
|
1440
|
+
if (selectedIds.size > 0) {
|
|
1441
|
+
const idSet = selectedIds;
|
|
1442
|
+
return messages.filter((m) => idSet.has(m.id));
|
|
1443
|
+
}
|
|
1444
|
+
return messages;
|
|
1445
|
+
}, [messages, selectedIds]);
|
|
1446
|
+
const handleExport = React5.useCallback((format) => {
|
|
1447
|
+
exportToFile(toExport, format, conversation);
|
|
1448
|
+
onExported?.(format, toExport.length);
|
|
1449
|
+
}, [toExport, conversation, onExported]);
|
|
1450
|
+
const handleCopy = React5.useCallback(async () => {
|
|
1451
|
+
const content = formatMessagesAsMarkdown(toExport);
|
|
1452
|
+
await navigator.clipboard.writeText(content);
|
|
1453
|
+
setCopied(true);
|
|
1454
|
+
setTimeout(() => setCopied(false), 2000);
|
|
1455
|
+
onExported?.("markdown", toExport.length);
|
|
1456
|
+
}, [toExport, onExported]);
|
|
1457
|
+
const disabled = messages.length === 0;
|
|
1458
|
+
const [forking, setForking] = React5.useState(false);
|
|
1459
|
+
const handleFork = React5.useCallback(async (upToMessageId) => {
|
|
1460
|
+
if (!onFork)
|
|
1461
|
+
return;
|
|
1462
|
+
setForking(true);
|
|
1463
|
+
try {
|
|
1464
|
+
await onFork(upToMessageId);
|
|
1465
|
+
} finally {
|
|
1466
|
+
setForking(false);
|
|
1467
|
+
}
|
|
1468
|
+
}, [onFork]);
|
|
1469
|
+
return /* @__PURE__ */ jsxs6("div", {
|
|
1470
|
+
className: "flex items-center gap-2",
|
|
1471
|
+
children: [
|
|
1472
|
+
onCreateNew && /* @__PURE__ */ jsxs6(Button4, {
|
|
1473
|
+
variant: "outline",
|
|
1474
|
+
size: "sm",
|
|
1475
|
+
onPress: onCreateNew,
|
|
1476
|
+
"aria-label": "New conversation",
|
|
1477
|
+
children: [
|
|
1478
|
+
/* @__PURE__ */ jsx6(Plus, {
|
|
1479
|
+
className: "h-4 w-4"
|
|
1480
|
+
}),
|
|
1481
|
+
"New"
|
|
1482
|
+
]
|
|
1483
|
+
}),
|
|
1484
|
+
onFork && messages.length > 0 && /* @__PURE__ */ jsxs6(Button4, {
|
|
1485
|
+
variant: "outline",
|
|
1486
|
+
size: "sm",
|
|
1487
|
+
disabled: forking,
|
|
1488
|
+
onPress: () => handleFork(),
|
|
1489
|
+
"aria-label": "Fork conversation",
|
|
1490
|
+
children: [
|
|
1491
|
+
/* @__PURE__ */ jsx6(GitFork, {
|
|
1492
|
+
className: "h-4 w-4"
|
|
1493
|
+
}),
|
|
1494
|
+
"Fork"
|
|
1495
|
+
]
|
|
1496
|
+
}),
|
|
1497
|
+
showSelectionSummary && selectedCount > 0 && /* @__PURE__ */ jsxs6("span", {
|
|
1498
|
+
className: "text-muted-foreground text-sm",
|
|
1499
|
+
children: [
|
|
1500
|
+
selectedCount,
|
|
1501
|
+
" message",
|
|
1502
|
+
selectedCount !== 1 ? "s" : "",
|
|
1503
|
+
" selected"
|
|
1504
|
+
]
|
|
1505
|
+
}),
|
|
1506
|
+
onSelectAll && onClearSelection && totalCount > 0 && /* @__PURE__ */ jsxs6(Fragment3, {
|
|
1507
|
+
children: [
|
|
1508
|
+
/* @__PURE__ */ jsx6(Button4, {
|
|
1509
|
+
variant: "ghost",
|
|
1510
|
+
size: "sm",
|
|
1511
|
+
onPress: onSelectAll,
|
|
1512
|
+
className: "text-xs",
|
|
1513
|
+
children: "Select all"
|
|
1514
|
+
}),
|
|
1515
|
+
selectedCount > 0 && /* @__PURE__ */ jsx6(Button4, {
|
|
1516
|
+
variant: "ghost",
|
|
1517
|
+
size: "sm",
|
|
1518
|
+
onPress: onClearSelection,
|
|
1519
|
+
className: "text-xs",
|
|
1520
|
+
children: "Clear"
|
|
1521
|
+
})
|
|
1522
|
+
]
|
|
1523
|
+
}),
|
|
1524
|
+
/* @__PURE__ */ jsxs6(DropdownMenu, {
|
|
1525
|
+
children: [
|
|
1526
|
+
/* @__PURE__ */ jsx6(DropdownMenuTrigger, {
|
|
1527
|
+
asChild: true,
|
|
1528
|
+
children: /* @__PURE__ */ jsxs6(Button4, {
|
|
1529
|
+
variant: "outline",
|
|
1530
|
+
size: "sm",
|
|
1531
|
+
disabled,
|
|
1532
|
+
"aria-label": selectedCount > 0 ? "Export selected messages" : "Export conversation",
|
|
1533
|
+
children: [
|
|
1534
|
+
/* @__PURE__ */ jsx6(Download2, {
|
|
1535
|
+
className: "h-4 w-4"
|
|
1536
|
+
}),
|
|
1537
|
+
"Export"
|
|
1538
|
+
]
|
|
1539
|
+
})
|
|
1540
|
+
}),
|
|
1541
|
+
/* @__PURE__ */ jsxs6(DropdownMenuContent, {
|
|
1542
|
+
align: "end",
|
|
1543
|
+
children: [
|
|
1544
|
+
/* @__PURE__ */ jsxs6(DropdownMenuItem, {
|
|
1545
|
+
onSelect: () => handleExport("markdown"),
|
|
1546
|
+
disabled,
|
|
1547
|
+
children: [
|
|
1548
|
+
/* @__PURE__ */ jsx6(FileText2, {
|
|
1549
|
+
className: "h-4 w-4"
|
|
1550
|
+
}),
|
|
1551
|
+
"Export as Markdown (.md)"
|
|
1552
|
+
]
|
|
1553
|
+
}),
|
|
1554
|
+
/* @__PURE__ */ jsxs6(DropdownMenuItem, {
|
|
1555
|
+
onSelect: () => handleExport("txt"),
|
|
1556
|
+
disabled,
|
|
1557
|
+
children: [
|
|
1558
|
+
/* @__PURE__ */ jsx6(FileText2, {
|
|
1559
|
+
className: "h-4 w-4"
|
|
1560
|
+
}),
|
|
1561
|
+
"Export as Plain Text (.txt)"
|
|
1562
|
+
]
|
|
1563
|
+
}),
|
|
1564
|
+
/* @__PURE__ */ jsxs6(DropdownMenuItem, {
|
|
1565
|
+
onSelect: () => handleExport("json"),
|
|
1566
|
+
disabled,
|
|
1567
|
+
children: [
|
|
1568
|
+
/* @__PURE__ */ jsx6(FileText2, {
|
|
1569
|
+
className: "h-4 w-4"
|
|
1570
|
+
}),
|
|
1571
|
+
"Export as JSON (.json)"
|
|
1572
|
+
]
|
|
1573
|
+
}),
|
|
1574
|
+
/* @__PURE__ */ jsx6(DropdownMenuSeparator, {}),
|
|
1575
|
+
/* @__PURE__ */ jsxs6(DropdownMenuItem, {
|
|
1576
|
+
onSelect: () => handleCopy(),
|
|
1577
|
+
disabled,
|
|
1578
|
+
children: [
|
|
1579
|
+
copied ? /* @__PURE__ */ jsx6(Check3, {
|
|
1580
|
+
className: "h-4 w-4 text-green-500"
|
|
1581
|
+
}) : /* @__PURE__ */ jsx6(Copy3, {
|
|
1582
|
+
className: "h-4 w-4"
|
|
1583
|
+
}),
|
|
1584
|
+
copied ? "Copied to clipboard" : "Copy to clipboard"
|
|
1585
|
+
]
|
|
1586
|
+
})
|
|
1587
|
+
]
|
|
1588
|
+
})
|
|
1589
|
+
]
|
|
1590
|
+
})
|
|
1591
|
+
]
|
|
1592
|
+
});
|
|
1593
|
+
}
|
|
1594
|
+
// src/presentation/components/ChatWithExport.tsx
|
|
1595
|
+
import * as React8 from "react";
|
|
1596
|
+
|
|
1597
|
+
// src/presentation/components/ThinkingLevelPicker.tsx
|
|
1598
|
+
import * as React6 from "react";
|
|
1599
|
+
import { cn as cn5 } from "@contractspec/lib.ui-kit-web/ui/utils";
|
|
1068
1600
|
import {
|
|
1069
1601
|
Select,
|
|
1070
1602
|
SelectContent,
|
|
@@ -1072,402 +1604,487 @@ import {
|
|
|
1072
1604
|
SelectTrigger,
|
|
1073
1605
|
SelectValue
|
|
1074
1606
|
} from "@contractspec/lib.ui-kit-web/ui/select";
|
|
1075
|
-
import { Badge } from "@contractspec/lib.ui-kit-web/ui/badge";
|
|
1076
1607
|
import { Label } from "@contractspec/lib.ui-kit-web/ui/label";
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
"
|
|
1083
|
-
|
|
1084
|
-
ollama: /* @__PURE__ */ jsxDEV5(Cpu, {
|
|
1085
|
-
className: "h-4 w-4"
|
|
1086
|
-
}, undefined, false, undefined, this),
|
|
1087
|
-
openai: /* @__PURE__ */ jsxDEV5(Bot2, {
|
|
1088
|
-
className: "h-4 w-4"
|
|
1089
|
-
}, undefined, false, undefined, this),
|
|
1090
|
-
anthropic: /* @__PURE__ */ jsxDEV5(Sparkles, {
|
|
1091
|
-
className: "h-4 w-4"
|
|
1092
|
-
}, undefined, false, undefined, this),
|
|
1093
|
-
mistral: /* @__PURE__ */ jsxDEV5(Cloud, {
|
|
1094
|
-
className: "h-4 w-4"
|
|
1095
|
-
}, undefined, false, undefined, this),
|
|
1096
|
-
gemini: /* @__PURE__ */ jsxDEV5(Sparkles, {
|
|
1097
|
-
className: "h-4 w-4"
|
|
1098
|
-
}, undefined, false, undefined, this)
|
|
1099
|
-
};
|
|
1100
|
-
var PROVIDER_NAMES = {
|
|
1101
|
-
ollama: "Ollama (Local)",
|
|
1102
|
-
openai: "OpenAI",
|
|
1103
|
-
anthropic: "Anthropic",
|
|
1104
|
-
mistral: "Mistral",
|
|
1105
|
-
gemini: "Google Gemini"
|
|
1608
|
+
|
|
1609
|
+
// src/core/thinking-levels.ts
|
|
1610
|
+
var THINKING_LEVEL_LABELS = {
|
|
1611
|
+
instant: "Instant",
|
|
1612
|
+
thinking: "Thinking",
|
|
1613
|
+
extra_thinking: "Extra Thinking",
|
|
1614
|
+
max: "Max"
|
|
1106
1615
|
};
|
|
1107
|
-
var
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1616
|
+
var THINKING_LEVEL_DESCRIPTIONS = {
|
|
1617
|
+
instant: "Fast responses, minimal reasoning",
|
|
1618
|
+
thinking: "Standard reasoning depth",
|
|
1619
|
+
extra_thinking: "More thorough reasoning",
|
|
1620
|
+
max: "Maximum reasoning depth"
|
|
1111
1621
|
};
|
|
1112
|
-
function
|
|
1622
|
+
function getProviderOptions(level, providerName) {
|
|
1623
|
+
if (!level || level === "instant") {
|
|
1624
|
+
return {};
|
|
1625
|
+
}
|
|
1626
|
+
switch (providerName) {
|
|
1627
|
+
case "anthropic": {
|
|
1628
|
+
const budgetMap = {
|
|
1629
|
+
thinking: 8000,
|
|
1630
|
+
extra_thinking: 16000,
|
|
1631
|
+
max: 32000
|
|
1632
|
+
};
|
|
1633
|
+
return {
|
|
1634
|
+
anthropic: {
|
|
1635
|
+
thinking: { type: "enabled", budgetTokens: budgetMap[level] }
|
|
1636
|
+
}
|
|
1637
|
+
};
|
|
1638
|
+
}
|
|
1639
|
+
case "openai": {
|
|
1640
|
+
const effortMap = {
|
|
1641
|
+
thinking: "low",
|
|
1642
|
+
extra_thinking: "medium",
|
|
1643
|
+
max: "high"
|
|
1644
|
+
};
|
|
1645
|
+
return {
|
|
1646
|
+
openai: {
|
|
1647
|
+
reasoningEffort: effortMap[level]
|
|
1648
|
+
}
|
|
1649
|
+
};
|
|
1650
|
+
}
|
|
1651
|
+
case "ollama":
|
|
1652
|
+
case "mistral":
|
|
1653
|
+
case "gemini":
|
|
1654
|
+
return {};
|
|
1655
|
+
default:
|
|
1656
|
+
return {};
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
|
|
1660
|
+
// src/presentation/components/ThinkingLevelPicker.tsx
|
|
1661
|
+
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1662
|
+
"use client";
|
|
1663
|
+
var THINKING_LEVELS = [
|
|
1664
|
+
"instant",
|
|
1665
|
+
"thinking",
|
|
1666
|
+
"extra_thinking",
|
|
1667
|
+
"max"
|
|
1668
|
+
];
|
|
1669
|
+
function ThinkingLevelPicker({
|
|
1113
1670
|
value,
|
|
1114
1671
|
onChange,
|
|
1115
|
-
availableProviders,
|
|
1116
1672
|
className,
|
|
1117
1673
|
compact = false
|
|
1118
1674
|
}) {
|
|
1119
|
-
const
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
{ provider: "anthropic", available: true, mode: "byok" },
|
|
1123
|
-
{ provider: "mistral", available: true, mode: "byok" },
|
|
1124
|
-
{ provider: "gemini", available: true, mode: "byok" }
|
|
1125
|
-
];
|
|
1126
|
-
const models = getModelsForProvider(value.provider);
|
|
1127
|
-
const selectedModel = models.find((m) => m.id === value.model);
|
|
1128
|
-
const handleProviderChange = React5.useCallback((providerName) => {
|
|
1129
|
-
const provider = providerName;
|
|
1130
|
-
const providerInfo = providers.find((p) => p.provider === provider);
|
|
1131
|
-
const providerModels = getModelsForProvider(provider);
|
|
1132
|
-
const defaultModel = providerModels[0]?.id ?? "";
|
|
1133
|
-
onChange({
|
|
1134
|
-
provider,
|
|
1135
|
-
model: defaultModel,
|
|
1136
|
-
mode: providerInfo?.mode ?? "byok"
|
|
1137
|
-
});
|
|
1138
|
-
}, [onChange, providers]);
|
|
1139
|
-
const handleModelChange = React5.useCallback((modelId) => {
|
|
1140
|
-
onChange({
|
|
1141
|
-
...value,
|
|
1142
|
-
model: modelId
|
|
1143
|
-
});
|
|
1144
|
-
}, [onChange, value]);
|
|
1675
|
+
const handleChange = React6.useCallback((v) => {
|
|
1676
|
+
onChange(v);
|
|
1677
|
+
}, [onChange]);
|
|
1145
1678
|
if (compact) {
|
|
1146
|
-
return /* @__PURE__ */
|
|
1147
|
-
|
|
1679
|
+
return /* @__PURE__ */ jsxs7(Select, {
|
|
1680
|
+
value,
|
|
1681
|
+
onValueChange: handleChange,
|
|
1148
1682
|
children: [
|
|
1149
|
-
/* @__PURE__ */
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
value: p.provider,
|
|
1160
|
-
disabled: !p.available,
|
|
1161
|
-
children: /* @__PURE__ */ jsxDEV5("div", {
|
|
1162
|
-
className: "flex items-center gap-2",
|
|
1163
|
-
children: [
|
|
1164
|
-
PROVIDER_ICONS[p.provider],
|
|
1165
|
-
/* @__PURE__ */ jsxDEV5("span", {
|
|
1166
|
-
children: PROVIDER_NAMES[p.provider]
|
|
1167
|
-
}, undefined, false, undefined, this)
|
|
1168
|
-
]
|
|
1169
|
-
}, undefined, true, undefined, this)
|
|
1170
|
-
}, p.provider, false, undefined, this))
|
|
1171
|
-
}, undefined, false, undefined, this)
|
|
1172
|
-
]
|
|
1173
|
-
}, undefined, true, undefined, this),
|
|
1174
|
-
/* @__PURE__ */ jsxDEV5(Select, {
|
|
1175
|
-
value: value.model,
|
|
1176
|
-
onValueChange: handleModelChange,
|
|
1177
|
-
children: [
|
|
1178
|
-
/* @__PURE__ */ jsxDEV5(SelectTrigger, {
|
|
1179
|
-
className: "w-[160px]",
|
|
1180
|
-
children: /* @__PURE__ */ jsxDEV5(SelectValue, {}, undefined, false, undefined, this)
|
|
1181
|
-
}, undefined, false, undefined, this),
|
|
1182
|
-
/* @__PURE__ */ jsxDEV5(SelectContent, {
|
|
1183
|
-
children: models.map((m) => /* @__PURE__ */ jsxDEV5(SelectItem, {
|
|
1184
|
-
value: m.id,
|
|
1185
|
-
children: m.name
|
|
1186
|
-
}, m.id, false, undefined, this))
|
|
1187
|
-
}, undefined, false, undefined, this)
|
|
1188
|
-
]
|
|
1189
|
-
}, undefined, true, undefined, this)
|
|
1683
|
+
/* @__PURE__ */ jsx7(SelectTrigger, {
|
|
1684
|
+
className: cn5("w-[140px]", className),
|
|
1685
|
+
children: /* @__PURE__ */ jsx7(SelectValue, {})
|
|
1686
|
+
}),
|
|
1687
|
+
/* @__PURE__ */ jsx7(SelectContent, {
|
|
1688
|
+
children: THINKING_LEVELS.map((level) => /* @__PURE__ */ jsx7(SelectItem, {
|
|
1689
|
+
value: level,
|
|
1690
|
+
children: THINKING_LEVEL_LABELS[level]
|
|
1691
|
+
}, level))
|
|
1692
|
+
})
|
|
1190
1693
|
]
|
|
1191
|
-
}
|
|
1694
|
+
});
|
|
1192
1695
|
}
|
|
1193
|
-
return /* @__PURE__ */
|
|
1194
|
-
className: cn5("flex flex-col gap-
|
|
1696
|
+
return /* @__PURE__ */ jsxs7("div", {
|
|
1697
|
+
className: cn5("flex flex-col gap-1.5", className),
|
|
1195
1698
|
children: [
|
|
1196
|
-
/* @__PURE__ */
|
|
1197
|
-
|
|
1699
|
+
/* @__PURE__ */ jsx7(Label, {
|
|
1700
|
+
htmlFor: "thinking-level-picker",
|
|
1701
|
+
className: "text-sm font-medium",
|
|
1702
|
+
children: "Thinking Level"
|
|
1703
|
+
}),
|
|
1704
|
+
/* @__PURE__ */ jsxs7(Select, {
|
|
1705
|
+
name: "thinking-level-picker",
|
|
1706
|
+
value,
|
|
1707
|
+
onValueChange: handleChange,
|
|
1198
1708
|
children: [
|
|
1199
|
-
/* @__PURE__ */
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
}
|
|
1204
|
-
/* @__PURE__ */
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
disabled: !p.available,
|
|
1212
|
-
className: cn5(!p.available && "opacity-50"),
|
|
1213
|
-
children: [
|
|
1214
|
-
PROVIDER_ICONS[p.provider],
|
|
1215
|
-
/* @__PURE__ */ jsxDEV5("span", {
|
|
1216
|
-
children: PROVIDER_NAMES[p.provider]
|
|
1217
|
-
}, undefined, false, undefined, this),
|
|
1218
|
-
/* @__PURE__ */ jsxDEV5(Badge, {
|
|
1219
|
-
variant: MODE_BADGES[p.mode].variant,
|
|
1220
|
-
className: "ml-1",
|
|
1221
|
-
children: MODE_BADGES[p.mode].label
|
|
1222
|
-
}, undefined, false, undefined, this)
|
|
1223
|
-
]
|
|
1224
|
-
}, p.provider, true, undefined, this))
|
|
1225
|
-
}, undefined, false, undefined, this)
|
|
1226
|
-
]
|
|
1227
|
-
}, undefined, true, undefined, this),
|
|
1228
|
-
/* @__PURE__ */ jsxDEV5("div", {
|
|
1229
|
-
className: "flex flex-col gap-1.5",
|
|
1230
|
-
children: [
|
|
1231
|
-
/* @__PURE__ */ jsxDEV5(Label, {
|
|
1232
|
-
htmlFor: "model-picker",
|
|
1233
|
-
className: "text-sm font-medium",
|
|
1234
|
-
children: "Model"
|
|
1235
|
-
}, undefined, false, undefined, this),
|
|
1236
|
-
/* @__PURE__ */ jsxDEV5(Select, {
|
|
1237
|
-
name: "model-picker",
|
|
1238
|
-
value: value.model,
|
|
1239
|
-
onValueChange: handleModelChange,
|
|
1240
|
-
children: [
|
|
1241
|
-
/* @__PURE__ */ jsxDEV5(SelectTrigger, {
|
|
1242
|
-
children: /* @__PURE__ */ jsxDEV5(SelectValue, {
|
|
1243
|
-
placeholder: "Select a model"
|
|
1244
|
-
}, undefined, false, undefined, this)
|
|
1245
|
-
}, undefined, false, undefined, this),
|
|
1246
|
-
/* @__PURE__ */ jsxDEV5(SelectContent, {
|
|
1247
|
-
children: models.map((m) => /* @__PURE__ */ jsxDEV5(SelectItem, {
|
|
1248
|
-
value: m.id,
|
|
1249
|
-
children: /* @__PURE__ */ jsxDEV5("div", {
|
|
1250
|
-
className: "flex items-center gap-2",
|
|
1251
|
-
children: [
|
|
1252
|
-
/* @__PURE__ */ jsxDEV5("span", {
|
|
1253
|
-
children: m.name
|
|
1254
|
-
}, undefined, false, undefined, this),
|
|
1255
|
-
/* @__PURE__ */ jsxDEV5("span", {
|
|
1256
|
-
className: "text-muted-foreground text-xs",
|
|
1257
|
-
children: [
|
|
1258
|
-
Math.round(m.contextWindow / 1000),
|
|
1259
|
-
"K"
|
|
1260
|
-
]
|
|
1261
|
-
}, undefined, true, undefined, this),
|
|
1262
|
-
m.capabilities.vision && /* @__PURE__ */ jsxDEV5(Badge, {
|
|
1263
|
-
variant: "outline",
|
|
1264
|
-
className: "text-xs",
|
|
1265
|
-
children: "Vision"
|
|
1266
|
-
}, undefined, false, undefined, this),
|
|
1267
|
-
m.capabilities.reasoning && /* @__PURE__ */ jsxDEV5(Badge, {
|
|
1268
|
-
variant: "outline",
|
|
1269
|
-
className: "text-xs",
|
|
1270
|
-
children: "Reasoning"
|
|
1271
|
-
}, undefined, false, undefined, this)
|
|
1272
|
-
]
|
|
1273
|
-
}, undefined, true, undefined, this)
|
|
1274
|
-
}, m.id, false, undefined, this))
|
|
1275
|
-
}, undefined, false, undefined, this)
|
|
1276
|
-
]
|
|
1277
|
-
}, undefined, true, undefined, this)
|
|
1278
|
-
]
|
|
1279
|
-
}, undefined, true, undefined, this),
|
|
1280
|
-
selectedModel && /* @__PURE__ */ jsxDEV5("div", {
|
|
1281
|
-
className: "text-muted-foreground flex flex-wrap gap-2 text-xs",
|
|
1282
|
-
children: [
|
|
1283
|
-
/* @__PURE__ */ jsxDEV5("span", {
|
|
1284
|
-
children: [
|
|
1285
|
-
"Context: ",
|
|
1286
|
-
Math.round(selectedModel.contextWindow / 1000),
|
|
1287
|
-
"K tokens"
|
|
1288
|
-
]
|
|
1289
|
-
}, undefined, true, undefined, this),
|
|
1290
|
-
selectedModel.capabilities.vision && /* @__PURE__ */ jsxDEV5("span", {
|
|
1291
|
-
children: "• Vision"
|
|
1292
|
-
}, undefined, false, undefined, this),
|
|
1293
|
-
selectedModel.capabilities.tools && /* @__PURE__ */ jsxDEV5("span", {
|
|
1294
|
-
children: "• Tools"
|
|
1295
|
-
}, undefined, false, undefined, this),
|
|
1296
|
-
selectedModel.capabilities.reasoning && /* @__PURE__ */ jsxDEV5("span", {
|
|
1297
|
-
children: "• Reasoning"
|
|
1298
|
-
}, undefined, false, undefined, this)
|
|
1709
|
+
/* @__PURE__ */ jsx7(SelectTrigger, {
|
|
1710
|
+
children: /* @__PURE__ */ jsx7(SelectValue, {
|
|
1711
|
+
placeholder: "Select thinking level"
|
|
1712
|
+
})
|
|
1713
|
+
}),
|
|
1714
|
+
/* @__PURE__ */ jsx7(SelectContent, {
|
|
1715
|
+
children: THINKING_LEVELS.map((level) => /* @__PURE__ */ jsx7(SelectItem, {
|
|
1716
|
+
value: level,
|
|
1717
|
+
title: THINKING_LEVEL_DESCRIPTIONS[level],
|
|
1718
|
+
children: THINKING_LEVEL_LABELS[level]
|
|
1719
|
+
}, level))
|
|
1720
|
+
})
|
|
1299
1721
|
]
|
|
1300
|
-
}
|
|
1722
|
+
})
|
|
1301
1723
|
]
|
|
1302
|
-
}
|
|
1724
|
+
});
|
|
1303
1725
|
}
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
import
|
|
1307
|
-
import {
|
|
1308
|
-
Tooltip,
|
|
1309
|
-
TooltipContent,
|
|
1310
|
-
TooltipProvider,
|
|
1311
|
-
TooltipTrigger
|
|
1312
|
-
} from "@contractspec/lib.ui-kit-web/ui/tooltip";
|
|
1313
|
-
import { FolderOpen, FileCode, Zap, Info } from "lucide-react";
|
|
1314
|
-
import { jsxDEV as jsxDEV6, Fragment as Fragment3 } from "react/jsx-dev-runtime";
|
|
1726
|
+
|
|
1727
|
+
// src/presentation/hooks/useMessageSelection.ts
|
|
1728
|
+
import * as React7 from "react";
|
|
1315
1729
|
"use client";
|
|
1316
|
-
function
|
|
1317
|
-
|
|
1318
|
-
|
|
1730
|
+
function useMessageSelection(messageIds) {
|
|
1731
|
+
const [selectedIds, setSelectedIds] = React7.useState(() => new Set);
|
|
1732
|
+
const idSet = React7.useMemo(() => new Set(messageIds), [messageIds.join(",")]);
|
|
1733
|
+
React7.useEffect(() => {
|
|
1734
|
+
setSelectedIds((prev) => {
|
|
1735
|
+
const next = new Set;
|
|
1736
|
+
for (const id of prev) {
|
|
1737
|
+
if (idSet.has(id))
|
|
1738
|
+
next.add(id);
|
|
1739
|
+
}
|
|
1740
|
+
return next.size === prev.size ? prev : next;
|
|
1741
|
+
});
|
|
1742
|
+
}, [idSet]);
|
|
1743
|
+
const toggle = React7.useCallback((id) => {
|
|
1744
|
+
setSelectedIds((prev) => {
|
|
1745
|
+
const next = new Set(prev);
|
|
1746
|
+
if (next.has(id))
|
|
1747
|
+
next.delete(id);
|
|
1748
|
+
else
|
|
1749
|
+
next.add(id);
|
|
1750
|
+
return next;
|
|
1751
|
+
});
|
|
1752
|
+
}, []);
|
|
1753
|
+
const selectAll = React7.useCallback(() => {
|
|
1754
|
+
setSelectedIds(new Set(messageIds));
|
|
1755
|
+
}, [messageIds.join(",")]);
|
|
1756
|
+
const clearSelection = React7.useCallback(() => {
|
|
1757
|
+
setSelectedIds(new Set);
|
|
1758
|
+
}, []);
|
|
1759
|
+
const isSelected = React7.useCallback((id) => selectedIds.has(id), [selectedIds]);
|
|
1760
|
+
const selectedCount = selectedIds.size;
|
|
1761
|
+
return {
|
|
1762
|
+
selectedIds,
|
|
1763
|
+
toggle,
|
|
1764
|
+
selectAll,
|
|
1765
|
+
clearSelection,
|
|
1766
|
+
isSelected,
|
|
1767
|
+
selectedCount
|
|
1768
|
+
};
|
|
1769
|
+
}
|
|
1770
|
+
|
|
1771
|
+
// src/presentation/components/ChatWithExport.tsx
|
|
1772
|
+
import { jsx as jsx8, jsxs as jsxs8, Fragment as Fragment4 } from "react/jsx-runtime";
|
|
1773
|
+
"use client";
|
|
1774
|
+
function ChatWithExport({
|
|
1775
|
+
messages,
|
|
1776
|
+
conversation,
|
|
1777
|
+
children,
|
|
1319
1778
|
className,
|
|
1320
|
-
|
|
1779
|
+
showExport = true,
|
|
1780
|
+
showMessageSelection = true,
|
|
1781
|
+
showScrollButton = true,
|
|
1782
|
+
onCreateNew,
|
|
1783
|
+
onFork,
|
|
1784
|
+
onEditMessage,
|
|
1785
|
+
thinkingLevel = "thinking",
|
|
1786
|
+
onThinkingLevelChange,
|
|
1787
|
+
presentationRenderer,
|
|
1788
|
+
formRenderer
|
|
1321
1789
|
}) {
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1790
|
+
const messageIds = React8.useMemo(() => messages.map((m) => m.id), [messages]);
|
|
1791
|
+
const selection = useMessageSelection(messageIds);
|
|
1792
|
+
const hasToolbar = showExport || showMessageSelection;
|
|
1793
|
+
const hasPicker = Boolean(onThinkingLevelChange);
|
|
1794
|
+
const headerContent = hasPicker || hasToolbar ? /* @__PURE__ */ jsxs8(Fragment4, {
|
|
1795
|
+
children: [
|
|
1796
|
+
hasPicker && /* @__PURE__ */ jsx8(ThinkingLevelPicker, {
|
|
1797
|
+
value: thinkingLevel,
|
|
1798
|
+
onChange: onThinkingLevelChange,
|
|
1799
|
+
compact: true
|
|
1800
|
+
}),
|
|
1801
|
+
hasToolbar && /* @__PURE__ */ jsx8(ChatExportToolbar, {
|
|
1802
|
+
messages,
|
|
1803
|
+
conversation,
|
|
1804
|
+
selectedIds: selection.selectedIds,
|
|
1805
|
+
showSelectionSummary: showMessageSelection,
|
|
1806
|
+
onSelectAll: showMessageSelection ? selection.selectAll : undefined,
|
|
1807
|
+
onClearSelection: showMessageSelection ? selection.clearSelection : undefined,
|
|
1808
|
+
selectedCount: selection.selectedCount,
|
|
1809
|
+
totalCount: messages.length,
|
|
1810
|
+
onCreateNew,
|
|
1811
|
+
onFork
|
|
1812
|
+
})
|
|
1813
|
+
]
|
|
1814
|
+
}) : null;
|
|
1815
|
+
return /* @__PURE__ */ jsxs8(ChatContainer, {
|
|
1816
|
+
className,
|
|
1817
|
+
headerContent,
|
|
1818
|
+
showScrollButton,
|
|
1819
|
+
children: [
|
|
1820
|
+
messages.map((msg) => /* @__PURE__ */ jsx8(ChatMessage, {
|
|
1821
|
+
message: msg,
|
|
1822
|
+
selectable: showMessageSelection,
|
|
1823
|
+
selected: selection.isSelected(msg.id),
|
|
1824
|
+
onSelect: showMessageSelection ? selection.toggle : undefined,
|
|
1825
|
+
editable: msg.role === "user" && !!onEditMessage,
|
|
1826
|
+
onEdit: onEditMessage,
|
|
1827
|
+
presentationRenderer,
|
|
1828
|
+
formRenderer
|
|
1829
|
+
}, msg.id)),
|
|
1830
|
+
children
|
|
1831
|
+
]
|
|
1832
|
+
});
|
|
1833
|
+
}
|
|
1834
|
+
// src/presentation/components/ChatSidebar.tsx
|
|
1835
|
+
import * as React10 from "react";
|
|
1836
|
+
import { Plus as Plus2, Trash2, MessageSquare } from "lucide-react";
|
|
1837
|
+
import { Button as Button5 } from "@contractspec/lib.design-system";
|
|
1838
|
+
import { cn as cn6 } from "@contractspec/lib.ui-kit-web/ui/utils";
|
|
1839
|
+
|
|
1840
|
+
// src/presentation/hooks/useConversations.ts
|
|
1841
|
+
import * as React9 from "react";
|
|
1842
|
+
"use client";
|
|
1843
|
+
function useConversations(options) {
|
|
1844
|
+
const { store, projectId, tags, limit = 50 } = options;
|
|
1845
|
+
const [conversations, setConversations] = React9.useState([]);
|
|
1846
|
+
const [isLoading, setIsLoading] = React9.useState(true);
|
|
1847
|
+
const refresh = React9.useCallback(async () => {
|
|
1848
|
+
setIsLoading(true);
|
|
1849
|
+
try {
|
|
1850
|
+
const list = await store.list({
|
|
1851
|
+
status: "active",
|
|
1852
|
+
projectId,
|
|
1853
|
+
tags,
|
|
1854
|
+
limit
|
|
1855
|
+
});
|
|
1856
|
+
setConversations(list);
|
|
1857
|
+
} finally {
|
|
1858
|
+
setIsLoading(false);
|
|
1859
|
+
}
|
|
1860
|
+
}, [store, projectId, tags, limit]);
|
|
1861
|
+
React9.useEffect(() => {
|
|
1862
|
+
refresh();
|
|
1863
|
+
}, [refresh]);
|
|
1864
|
+
const deleteConversation = React9.useCallback(async (id) => {
|
|
1865
|
+
const ok = await store.delete(id);
|
|
1866
|
+
if (ok) {
|
|
1867
|
+
setConversations((prev) => prev.filter((c) => c.id !== id));
|
|
1868
|
+
}
|
|
1869
|
+
return ok;
|
|
1870
|
+
}, [store]);
|
|
1871
|
+
return {
|
|
1872
|
+
conversations,
|
|
1873
|
+
isLoading,
|
|
1874
|
+
refresh,
|
|
1875
|
+
deleteConversation
|
|
1876
|
+
};
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1879
|
+
// src/presentation/components/ChatSidebar.tsx
|
|
1880
|
+
import { jsx as jsx9, jsxs as jsxs9, Fragment as Fragment5 } from "react/jsx-runtime";
|
|
1881
|
+
"use client";
|
|
1882
|
+
function formatDate(date) {
|
|
1883
|
+
const d = new Date(date);
|
|
1884
|
+
const now = new Date;
|
|
1885
|
+
const diff = now.getTime() - d.getTime();
|
|
1886
|
+
if (diff < 86400000) {
|
|
1887
|
+
return d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
|
|
1888
|
+
}
|
|
1889
|
+
if (diff < 604800000) {
|
|
1890
|
+
return d.toLocaleDateString([], { weekday: "short" });
|
|
1334
1891
|
}
|
|
1335
|
-
|
|
1336
|
-
|
|
1892
|
+
return d.toLocaleDateString([], { month: "short", day: "numeric" });
|
|
1893
|
+
}
|
|
1894
|
+
function ConversationItem({
|
|
1895
|
+
conversation,
|
|
1896
|
+
selected,
|
|
1897
|
+
onSelect,
|
|
1898
|
+
onDelete
|
|
1899
|
+
}) {
|
|
1900
|
+
const title = conversation.title ?? conversation.messages[0]?.content?.slice(0, 50) ?? "New chat";
|
|
1901
|
+
const displayTitle = title.length > 40 ? `${title.slice(0, 40)}…` : title;
|
|
1902
|
+
return /* @__PURE__ */ jsxs9("div", {
|
|
1903
|
+
role: "button",
|
|
1904
|
+
tabIndex: 0,
|
|
1905
|
+
onClick: onSelect,
|
|
1906
|
+
onKeyDown: (e) => {
|
|
1907
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
1908
|
+
e.preventDefault();
|
|
1909
|
+
onSelect();
|
|
1910
|
+
}
|
|
1911
|
+
},
|
|
1912
|
+
className: cn6("group flex cursor-pointer items-center gap-2 rounded-md px-3 py-2 text-sm transition-colors", selected ? "bg-accent text-accent-foreground" : "hover:bg-accent/50"),
|
|
1337
1913
|
children: [
|
|
1338
|
-
/* @__PURE__ */
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
className: "h-3 w-3"
|
|
1344
|
-
}, undefined, false, undefined, this),
|
|
1345
|
-
"Context"
|
|
1346
|
-
]
|
|
1347
|
-
}, undefined, true, undefined, this),
|
|
1348
|
-
summary && showDetails && /* @__PURE__ */ jsxDEV6(Fragment3, {
|
|
1914
|
+
/* @__PURE__ */ jsx9(MessageSquare, {
|
|
1915
|
+
className: "text-muted-foreground h-4 w-4 shrink-0"
|
|
1916
|
+
}),
|
|
1917
|
+
/* @__PURE__ */ jsxs9("div", {
|
|
1918
|
+
className: "min-w-0 flex-1",
|
|
1349
1919
|
children: [
|
|
1350
|
-
/* @__PURE__ */
|
|
1351
|
-
className: "
|
|
1352
|
-
children:
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
/* @__PURE__ */ jsxDEV6("span", {
|
|
1357
|
-
children: summary.name
|
|
1358
|
-
}, undefined, false, undefined, this)
|
|
1359
|
-
]
|
|
1360
|
-
}, undefined, true, undefined, this),
|
|
1361
|
-
/* @__PURE__ */ jsxDEV6("div", {
|
|
1362
|
-
className: "flex items-center gap-1 text-xs",
|
|
1920
|
+
/* @__PURE__ */ jsx9("p", {
|
|
1921
|
+
className: "truncate",
|
|
1922
|
+
children: displayTitle
|
|
1923
|
+
}),
|
|
1924
|
+
/* @__PURE__ */ jsxs9("p", {
|
|
1925
|
+
className: "text-muted-foreground text-xs",
|
|
1363
1926
|
children: [
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
/* @__PURE__ */ jsxDEV6("span", {
|
|
1927
|
+
formatDate(conversation.updatedAt),
|
|
1928
|
+
conversation.projectName && ` · ${conversation.projectName}`,
|
|
1929
|
+
conversation.tags && conversation.tags.length > 0 && /* @__PURE__ */ jsxs9(Fragment5, {
|
|
1368
1930
|
children: [
|
|
1369
|
-
|
|
1370
|
-
"
|
|
1931
|
+
" · ",
|
|
1932
|
+
conversation.tags.slice(0, 2).join(", ")
|
|
1371
1933
|
]
|
|
1372
|
-
}
|
|
1934
|
+
})
|
|
1373
1935
|
]
|
|
1374
|
-
}
|
|
1936
|
+
})
|
|
1375
1937
|
]
|
|
1376
|
-
},
|
|
1938
|
+
}),
|
|
1939
|
+
/* @__PURE__ */ jsx9("span", {
|
|
1940
|
+
onClick: (e) => e.stopPropagation(),
|
|
1941
|
+
children: /* @__PURE__ */ jsx9(Button5, {
|
|
1942
|
+
variant: "ghost",
|
|
1943
|
+
size: "sm",
|
|
1944
|
+
className: "h-6 w-6 shrink-0 p-0 opacity-0 group-hover:opacity-100",
|
|
1945
|
+
onPress: onDelete,
|
|
1946
|
+
"aria-label": "Delete conversation",
|
|
1947
|
+
children: /* @__PURE__ */ jsx9(Trash2, {
|
|
1948
|
+
className: "h-3 w-3"
|
|
1949
|
+
})
|
|
1950
|
+
})
|
|
1951
|
+
})
|
|
1377
1952
|
]
|
|
1378
|
-
}
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1953
|
+
});
|
|
1954
|
+
}
|
|
1955
|
+
function ChatSidebar({
|
|
1956
|
+
store,
|
|
1957
|
+
selectedConversationId,
|
|
1958
|
+
onSelectConversation,
|
|
1959
|
+
onCreateNew,
|
|
1960
|
+
projectId,
|
|
1961
|
+
tags,
|
|
1962
|
+
limit = 50,
|
|
1963
|
+
className,
|
|
1964
|
+
collapsed = false,
|
|
1965
|
+
onUpdateConversation,
|
|
1966
|
+
selectedConversation
|
|
1967
|
+
}) {
|
|
1968
|
+
const { conversations, isLoading, refresh, deleteConversation } = useConversations({ store, projectId, tags, limit });
|
|
1969
|
+
const handleDelete = React10.useCallback(async (id) => {
|
|
1970
|
+
const ok = await deleteConversation(id);
|
|
1971
|
+
if (ok && selectedConversationId === id) {
|
|
1972
|
+
onSelectConversation(null);
|
|
1973
|
+
}
|
|
1974
|
+
}, [deleteConversation, selectedConversationId, onSelectConversation]);
|
|
1975
|
+
if (collapsed)
|
|
1976
|
+
return null;
|
|
1977
|
+
return /* @__PURE__ */ jsxs9("div", {
|
|
1978
|
+
className: cn6("border-border flex w-64 shrink-0 flex-col border-r", className),
|
|
1979
|
+
children: [
|
|
1980
|
+
/* @__PURE__ */ jsxs9("div", {
|
|
1981
|
+
className: "border-border flex shrink-0 items-center justify-between border-b p-2",
|
|
1982
|
+
children: [
|
|
1983
|
+
/* @__PURE__ */ jsx9("span", {
|
|
1984
|
+
className: "text-muted-foreground text-sm font-medium",
|
|
1985
|
+
children: "Conversations"
|
|
1986
|
+
}),
|
|
1987
|
+
/* @__PURE__ */ jsx9(Button5, {
|
|
1988
|
+
variant: "ghost",
|
|
1989
|
+
size: "sm",
|
|
1990
|
+
className: "h-8 w-8 p-0",
|
|
1991
|
+
onPress: onCreateNew,
|
|
1992
|
+
"aria-label": "New conversation",
|
|
1993
|
+
children: /* @__PURE__ */ jsx9(Plus2, {
|
|
1994
|
+
className: "h-4 w-4"
|
|
1995
|
+
})
|
|
1996
|
+
})
|
|
1997
|
+
]
|
|
1998
|
+
}),
|
|
1999
|
+
/* @__PURE__ */ jsx9("div", {
|
|
2000
|
+
className: "flex-1 overflow-y-auto p-2",
|
|
2001
|
+
children: isLoading ? /* @__PURE__ */ jsx9("div", {
|
|
2002
|
+
className: "text-muted-foreground py-4 text-center text-sm",
|
|
2003
|
+
children: "Loading…"
|
|
2004
|
+
}) : conversations.length === 0 ? /* @__PURE__ */ jsx9("div", {
|
|
2005
|
+
className: "text-muted-foreground py-4 text-center text-sm",
|
|
2006
|
+
children: "No conversations yet"
|
|
2007
|
+
}) : /* @__PURE__ */ jsx9("div", {
|
|
2008
|
+
className: "flex flex-col gap-1",
|
|
2009
|
+
children: conversations.map((conv) => /* @__PURE__ */ jsx9(ConversationItem, {
|
|
2010
|
+
conversation: conv,
|
|
2011
|
+
selected: conv.id === selectedConversationId,
|
|
2012
|
+
onSelect: () => onSelectConversation(conv.id),
|
|
2013
|
+
onDelete: () => handleDelete(conv.id)
|
|
2014
|
+
}, conv.id))
|
|
2015
|
+
})
|
|
2016
|
+
}),
|
|
2017
|
+
selectedConversation && onUpdateConversation && /* @__PURE__ */ jsx9(ConversationMeta, {
|
|
2018
|
+
conversation: selectedConversation,
|
|
2019
|
+
onUpdate: onUpdateConversation
|
|
2020
|
+
})
|
|
2021
|
+
]
|
|
2022
|
+
});
|
|
2023
|
+
}
|
|
2024
|
+
function ConversationMeta({
|
|
2025
|
+
conversation,
|
|
2026
|
+
onUpdate
|
|
2027
|
+
}) {
|
|
2028
|
+
const [projectName, setProjectName] = React10.useState(conversation.projectName ?? "");
|
|
2029
|
+
const [tagsStr, setTagsStr] = React10.useState(conversation.tags?.join(", ") ?? "");
|
|
2030
|
+
React10.useEffect(() => {
|
|
2031
|
+
setProjectName(conversation.projectName ?? "");
|
|
2032
|
+
setTagsStr(conversation.tags?.join(", ") ?? "");
|
|
2033
|
+
}, [conversation.id, conversation.projectName, conversation.tags]);
|
|
2034
|
+
const handleBlur = React10.useCallback(() => {
|
|
2035
|
+
const tags = tagsStr.split(",").map((t) => t.trim()).filter(Boolean);
|
|
2036
|
+
if (projectName !== (conversation.projectName ?? "") || JSON.stringify(tags) !== JSON.stringify(conversation.tags ?? [])) {
|
|
2037
|
+
onUpdate(conversation.id, {
|
|
2038
|
+
projectName: projectName || undefined,
|
|
2039
|
+
projectId: projectName ? projectName.replace(/\s+/g, "-") : undefined,
|
|
2040
|
+
tags: tags.length > 0 ? tags : undefined
|
|
2041
|
+
});
|
|
2042
|
+
}
|
|
2043
|
+
}, [
|
|
2044
|
+
conversation.id,
|
|
2045
|
+
conversation.projectName,
|
|
2046
|
+
conversation.tags,
|
|
2047
|
+
projectName,
|
|
2048
|
+
tagsStr,
|
|
2049
|
+
onUpdate
|
|
2050
|
+
]);
|
|
2051
|
+
return /* @__PURE__ */ jsxs9("div", {
|
|
2052
|
+
className: "border-border shrink-0 border-t p-2",
|
|
2053
|
+
children: [
|
|
2054
|
+
/* @__PURE__ */ jsx9("p", {
|
|
2055
|
+
className: "text-muted-foreground mb-1 text-xs font-medium",
|
|
2056
|
+
children: "Project"
|
|
2057
|
+
}),
|
|
2058
|
+
/* @__PURE__ */ jsx9("input", {
|
|
2059
|
+
type: "text",
|
|
2060
|
+
value: projectName,
|
|
2061
|
+
onChange: (e) => setProjectName(e.target.value),
|
|
2062
|
+
onBlur: handleBlur,
|
|
2063
|
+
placeholder: "Project name",
|
|
2064
|
+
className: "border-input bg-background mb-2 w-full rounded px-2 py-1 text-xs"
|
|
2065
|
+
}),
|
|
2066
|
+
/* @__PURE__ */ jsx9("p", {
|
|
2067
|
+
className: "text-muted-foreground mb-1 text-xs font-medium",
|
|
2068
|
+
children: "Tags"
|
|
2069
|
+
}),
|
|
2070
|
+
/* @__PURE__ */ jsx9("input", {
|
|
2071
|
+
type: "text",
|
|
2072
|
+
value: tagsStr,
|
|
2073
|
+
onChange: (e) => setTagsStr(e.target.value),
|
|
2074
|
+
onBlur: handleBlur,
|
|
2075
|
+
placeholder: "tag1, tag2",
|
|
2076
|
+
className: "border-input bg-background w-full rounded px-2 py-1 text-xs"
|
|
2077
|
+
})
|
|
2078
|
+
]
|
|
2079
|
+
});
|
|
1466
2080
|
}
|
|
2081
|
+
// src/presentation/components/ChatWithSidebar.tsx
|
|
2082
|
+
import * as React12 from "react";
|
|
2083
|
+
|
|
1467
2084
|
// src/presentation/hooks/useChat.tsx
|
|
1468
|
-
import * as
|
|
1469
|
-
import { tool } from "ai";
|
|
1470
|
-
import { z } from "zod";
|
|
2085
|
+
import * as React11 from "react";
|
|
2086
|
+
import { tool as tool4 } from "ai";
|
|
2087
|
+
import { z as z4 } from "zod";
|
|
1471
2088
|
|
|
1472
2089
|
// src/core/chat-service.ts
|
|
1473
2090
|
import { generateText, streamText } from "ai";
|
|
@@ -1544,42 +2161,542 @@ class InMemoryConversationStore {
|
|
|
1544
2161
|
async delete(conversationId) {
|
|
1545
2162
|
return this.conversations.delete(conversationId);
|
|
1546
2163
|
}
|
|
1547
|
-
async list(options) {
|
|
1548
|
-
let results = Array.from(this.conversations.values());
|
|
1549
|
-
if (options?.status) {
|
|
1550
|
-
results = results.filter((c) => c.status === options.status);
|
|
2164
|
+
async list(options) {
|
|
2165
|
+
let results = Array.from(this.conversations.values());
|
|
2166
|
+
if (options?.status) {
|
|
2167
|
+
results = results.filter((c) => c.status === options.status);
|
|
2168
|
+
}
|
|
2169
|
+
if (options?.projectId) {
|
|
2170
|
+
results = results.filter((c) => c.projectId === options.projectId);
|
|
2171
|
+
}
|
|
2172
|
+
if (options?.tags && options.tags.length > 0) {
|
|
2173
|
+
const tagSet = new Set(options.tags);
|
|
2174
|
+
results = results.filter((c) => c.tags && c.tags.some((t) => tagSet.has(t)));
|
|
2175
|
+
}
|
|
2176
|
+
results.sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime());
|
|
2177
|
+
const offset = options?.offset ?? 0;
|
|
2178
|
+
const limit = options?.limit ?? 100;
|
|
2179
|
+
return results.slice(offset, offset + limit);
|
|
2180
|
+
}
|
|
2181
|
+
async fork(conversationId, upToMessageId) {
|
|
2182
|
+
const source = this.conversations.get(conversationId);
|
|
2183
|
+
if (!source) {
|
|
2184
|
+
throw new Error(`Conversation ${conversationId} not found`);
|
|
2185
|
+
}
|
|
2186
|
+
let messagesToCopy = source.messages;
|
|
2187
|
+
if (upToMessageId) {
|
|
2188
|
+
const idx = source.messages.findIndex((m) => m.id === upToMessageId);
|
|
2189
|
+
if (idx === -1) {
|
|
2190
|
+
throw new Error(`Message ${upToMessageId} not found`);
|
|
2191
|
+
}
|
|
2192
|
+
messagesToCopy = source.messages.slice(0, idx + 1);
|
|
2193
|
+
}
|
|
2194
|
+
const now = new Date;
|
|
2195
|
+
const forkedMessages = messagesToCopy.map((m) => ({
|
|
2196
|
+
...m,
|
|
2197
|
+
id: generateId("msg"),
|
|
2198
|
+
conversationId: "",
|
|
2199
|
+
createdAt: new Date(m.createdAt),
|
|
2200
|
+
updatedAt: new Date(m.updatedAt)
|
|
2201
|
+
}));
|
|
2202
|
+
const forked = {
|
|
2203
|
+
...source,
|
|
2204
|
+
id: generateId("conv"),
|
|
2205
|
+
title: source.title ? `${source.title} (fork)` : undefined,
|
|
2206
|
+
forkedFromId: source.id,
|
|
2207
|
+
createdAt: now,
|
|
2208
|
+
updatedAt: now,
|
|
2209
|
+
messages: forkedMessages
|
|
2210
|
+
};
|
|
2211
|
+
for (const m of forked.messages) {
|
|
2212
|
+
m.conversationId = forked.id;
|
|
2213
|
+
}
|
|
2214
|
+
this.conversations.set(forked.id, forked);
|
|
2215
|
+
return forked;
|
|
2216
|
+
}
|
|
2217
|
+
async truncateAfter(conversationId, messageId) {
|
|
2218
|
+
const conv = this.conversations.get(conversationId);
|
|
2219
|
+
if (!conv)
|
|
2220
|
+
return null;
|
|
2221
|
+
const idx = conv.messages.findIndex((m) => m.id === messageId);
|
|
2222
|
+
if (idx === -1)
|
|
2223
|
+
return null;
|
|
2224
|
+
conv.messages = conv.messages.slice(0, idx + 1);
|
|
2225
|
+
conv.updatedAt = new Date;
|
|
2226
|
+
return conv;
|
|
2227
|
+
}
|
|
2228
|
+
async search(query, limit = 20) {
|
|
2229
|
+
const lowerQuery = query.toLowerCase();
|
|
2230
|
+
const results = [];
|
|
2231
|
+
for (const conversation of this.conversations.values()) {
|
|
2232
|
+
if (conversation.title?.toLowerCase().includes(lowerQuery)) {
|
|
2233
|
+
results.push(conversation);
|
|
2234
|
+
continue;
|
|
2235
|
+
}
|
|
2236
|
+
const hasMatch = conversation.messages.some((m) => m.content.toLowerCase().includes(lowerQuery));
|
|
2237
|
+
if (hasMatch) {
|
|
2238
|
+
results.push(conversation);
|
|
2239
|
+
}
|
|
2240
|
+
if (results.length >= limit)
|
|
2241
|
+
break;
|
|
2242
|
+
}
|
|
2243
|
+
return results;
|
|
2244
|
+
}
|
|
2245
|
+
clear() {
|
|
2246
|
+
this.conversations.clear();
|
|
2247
|
+
}
|
|
2248
|
+
}
|
|
2249
|
+
function createInMemoryConversationStore() {
|
|
2250
|
+
return new InMemoryConversationStore;
|
|
2251
|
+
}
|
|
2252
|
+
|
|
2253
|
+
// src/core/workflow-tools.ts
|
|
2254
|
+
import { tool } from "ai";
|
|
2255
|
+
import { z } from "zod";
|
|
2256
|
+
import {
|
|
2257
|
+
WorkflowComposer,
|
|
2258
|
+
validateExtension
|
|
2259
|
+
} from "@contractspec/lib.workflow-composer";
|
|
2260
|
+
var StepTypeSchema = z.enum(["human", "automation", "decision"]);
|
|
2261
|
+
var StepActionSchema = z.object({
|
|
2262
|
+
operation: z.object({
|
|
2263
|
+
name: z.string(),
|
|
2264
|
+
version: z.number()
|
|
2265
|
+
}).optional(),
|
|
2266
|
+
form: z.object({
|
|
2267
|
+
key: z.string(),
|
|
2268
|
+
version: z.number()
|
|
2269
|
+
}).optional()
|
|
2270
|
+
}).optional();
|
|
2271
|
+
var StepSchema = z.object({
|
|
2272
|
+
id: z.string(),
|
|
2273
|
+
type: StepTypeSchema,
|
|
2274
|
+
label: z.string(),
|
|
2275
|
+
description: z.string().optional(),
|
|
2276
|
+
action: StepActionSchema
|
|
2277
|
+
});
|
|
2278
|
+
var StepInjectionSchema = z.object({
|
|
2279
|
+
after: z.string().optional(),
|
|
2280
|
+
before: z.string().optional(),
|
|
2281
|
+
inject: StepSchema,
|
|
2282
|
+
transitionTo: z.string().optional(),
|
|
2283
|
+
transitionFrom: z.string().optional(),
|
|
2284
|
+
when: z.string().optional()
|
|
2285
|
+
});
|
|
2286
|
+
var WorkflowExtensionInputSchema = z.object({
|
|
2287
|
+
workflow: z.string(),
|
|
2288
|
+
tenantId: z.string().optional(),
|
|
2289
|
+
role: z.string().optional(),
|
|
2290
|
+
priority: z.number().optional(),
|
|
2291
|
+
customSteps: z.array(StepInjectionSchema).optional(),
|
|
2292
|
+
hiddenSteps: z.array(z.string()).optional()
|
|
2293
|
+
});
|
|
2294
|
+
function createWorkflowTools(config) {
|
|
2295
|
+
const { baseWorkflows, composer } = config;
|
|
2296
|
+
const baseByKey = new Map(baseWorkflows.map((b) => [b.meta.key, b]));
|
|
2297
|
+
const createWorkflowExtensionTool = tool({
|
|
2298
|
+
description: "Create or validate a workflow extension. Use when the user asks to add steps, modify a workflow, or create a tenant-specific extension. The extension targets an existing base workflow.",
|
|
2299
|
+
inputSchema: WorkflowExtensionInputSchema,
|
|
2300
|
+
execute: async (input) => {
|
|
2301
|
+
const extension = {
|
|
2302
|
+
workflow: input.workflow,
|
|
2303
|
+
tenantId: input.tenantId,
|
|
2304
|
+
role: input.role,
|
|
2305
|
+
priority: input.priority,
|
|
2306
|
+
customSteps: input.customSteps,
|
|
2307
|
+
hiddenSteps: input.hiddenSteps
|
|
2308
|
+
};
|
|
2309
|
+
const base = baseByKey.get(input.workflow);
|
|
2310
|
+
if (!base) {
|
|
2311
|
+
return {
|
|
2312
|
+
success: false,
|
|
2313
|
+
error: `Base workflow "${input.workflow}" not found. Available: ${Array.from(baseByKey.keys()).join(", ")}`,
|
|
2314
|
+
extension
|
|
2315
|
+
};
|
|
2316
|
+
}
|
|
2317
|
+
try {
|
|
2318
|
+
validateExtension(extension, base);
|
|
2319
|
+
return {
|
|
2320
|
+
success: true,
|
|
2321
|
+
message: "Extension validated successfully",
|
|
2322
|
+
extension
|
|
2323
|
+
};
|
|
2324
|
+
} catch (err) {
|
|
2325
|
+
return {
|
|
2326
|
+
success: false,
|
|
2327
|
+
error: err instanceof Error ? err.message : String(err),
|
|
2328
|
+
extension
|
|
2329
|
+
};
|
|
2330
|
+
}
|
|
2331
|
+
}
|
|
2332
|
+
});
|
|
2333
|
+
const composeWorkflowInputSchema = z.object({
|
|
2334
|
+
workflowKey: z.string().describe("Base workflow meta.key"),
|
|
2335
|
+
tenantId: z.string().optional(),
|
|
2336
|
+
role: z.string().optional(),
|
|
2337
|
+
extensions: z.array(WorkflowExtensionInputSchema).optional().describe("Extensions to register before composing")
|
|
2338
|
+
});
|
|
2339
|
+
const composeWorkflowTool = tool({
|
|
2340
|
+
description: "Compose a workflow by applying registered extensions to a base workflow. Returns the composed WorkflowSpec.",
|
|
2341
|
+
inputSchema: composeWorkflowInputSchema,
|
|
2342
|
+
execute: async (input) => {
|
|
2343
|
+
const base = baseByKey.get(input.workflowKey);
|
|
2344
|
+
if (!base) {
|
|
2345
|
+
return {
|
|
2346
|
+
success: false,
|
|
2347
|
+
error: `Base workflow "${input.workflowKey}" not found. Available: ${Array.from(baseByKey.keys()).join(", ")}`
|
|
2348
|
+
};
|
|
2349
|
+
}
|
|
2350
|
+
const comp = composer ?? new WorkflowComposer;
|
|
2351
|
+
if (input.extensions?.length) {
|
|
2352
|
+
for (const ext of input.extensions) {
|
|
2353
|
+
comp.register({
|
|
2354
|
+
workflow: ext.workflow,
|
|
2355
|
+
tenantId: ext.tenantId,
|
|
2356
|
+
role: ext.role,
|
|
2357
|
+
priority: ext.priority,
|
|
2358
|
+
customSteps: ext.customSteps,
|
|
2359
|
+
hiddenSteps: ext.hiddenSteps
|
|
2360
|
+
});
|
|
2361
|
+
}
|
|
2362
|
+
}
|
|
2363
|
+
try {
|
|
2364
|
+
const composed = comp.compose({
|
|
2365
|
+
base,
|
|
2366
|
+
tenantId: input.tenantId,
|
|
2367
|
+
role: input.role
|
|
2368
|
+
});
|
|
2369
|
+
return {
|
|
2370
|
+
success: true,
|
|
2371
|
+
workflow: composed,
|
|
2372
|
+
meta: composed.meta,
|
|
2373
|
+
stepIds: composed.definition.steps.map((s) => s.id)
|
|
2374
|
+
};
|
|
2375
|
+
} catch (err) {
|
|
2376
|
+
return {
|
|
2377
|
+
success: false,
|
|
2378
|
+
error: err instanceof Error ? err.message : String(err)
|
|
2379
|
+
};
|
|
2380
|
+
}
|
|
2381
|
+
}
|
|
2382
|
+
});
|
|
2383
|
+
const generateWorkflowSpecCodeInputSchema = z.object({
|
|
2384
|
+
workflowKey: z.string().describe("Workflow meta.key"),
|
|
2385
|
+
composedSteps: z.array(z.object({
|
|
2386
|
+
id: z.string(),
|
|
2387
|
+
type: z.enum(["human", "automation", "decision"]),
|
|
2388
|
+
label: z.string(),
|
|
2389
|
+
description: z.string().optional()
|
|
2390
|
+
})).optional().describe("Steps to include; if omitted, uses the base workflow")
|
|
2391
|
+
});
|
|
2392
|
+
const generateWorkflowSpecCodeTool = tool({
|
|
2393
|
+
description: "Generate TypeScript code for a workflow spec. Use after composing a workflow to output the spec as code the user can save.",
|
|
2394
|
+
inputSchema: generateWorkflowSpecCodeInputSchema,
|
|
2395
|
+
execute: async (input) => {
|
|
2396
|
+
const base = baseByKey.get(input.workflowKey);
|
|
2397
|
+
if (!base) {
|
|
2398
|
+
return {
|
|
2399
|
+
success: false,
|
|
2400
|
+
error: `Base workflow "${input.workflowKey}" not found. Available: ${Array.from(baseByKey.keys()).join(", ")}`,
|
|
2401
|
+
code: null
|
|
2402
|
+
};
|
|
2403
|
+
}
|
|
2404
|
+
const steps = input.composedSteps ?? base.definition.steps;
|
|
2405
|
+
const specVarName = toPascalCase((base.meta.key.split(".").pop() ?? "Workflow") + "") + "Workflow";
|
|
2406
|
+
const stepsCode = steps.map((s) => ` {
|
|
2407
|
+
id: '${s.id}',
|
|
2408
|
+
type: '${s.type}',
|
|
2409
|
+
label: '${escapeString(s.label)}',${s.description ? `
|
|
2410
|
+
description: '${escapeString(s.description)}',` : ""}
|
|
2411
|
+
}`).join(`,
|
|
2412
|
+
`);
|
|
2413
|
+
const meta = base.meta;
|
|
2414
|
+
const transitionsJson = JSON.stringify(base.definition.transitions, null, 6);
|
|
2415
|
+
const code = `import type { WorkflowSpec } from '@contractspec/lib.contracts-spec/workflow';
|
|
2416
|
+
|
|
2417
|
+
/**
|
|
2418
|
+
* Workflow: ${base.meta.key}
|
|
2419
|
+
* Generated via AI chat workflow tools.
|
|
2420
|
+
*/
|
|
2421
|
+
export const ${specVarName}: WorkflowSpec = {
|
|
2422
|
+
meta: {
|
|
2423
|
+
key: '${base.meta.key}',
|
|
2424
|
+
version: '${String(base.meta.version)}',
|
|
2425
|
+
title: '${escapeString(meta.title ?? base.meta.key)}',
|
|
2426
|
+
description: '${escapeString(meta.description ?? "")}',
|
|
2427
|
+
},
|
|
2428
|
+
definition: {
|
|
2429
|
+
entryStepId: '${base.definition.entryStepId ?? base.definition.steps[0]?.id ?? ""}',
|
|
2430
|
+
steps: [
|
|
2431
|
+
${stepsCode}
|
|
2432
|
+
],
|
|
2433
|
+
transitions: ${transitionsJson},
|
|
2434
|
+
},
|
|
2435
|
+
};
|
|
2436
|
+
`;
|
|
2437
|
+
return {
|
|
2438
|
+
success: true,
|
|
2439
|
+
code,
|
|
2440
|
+
workflowKey: input.workflowKey
|
|
2441
|
+
};
|
|
2442
|
+
}
|
|
2443
|
+
});
|
|
2444
|
+
return {
|
|
2445
|
+
create_workflow_extension: createWorkflowExtensionTool,
|
|
2446
|
+
compose_workflow: composeWorkflowTool,
|
|
2447
|
+
generate_workflow_spec_code: generateWorkflowSpecCodeTool
|
|
2448
|
+
};
|
|
2449
|
+
}
|
|
2450
|
+
function toPascalCase(value) {
|
|
2451
|
+
return value.split(/[-_.]/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
2452
|
+
}
|
|
2453
|
+
function escapeString(value) {
|
|
2454
|
+
return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
2455
|
+
}
|
|
2456
|
+
|
|
2457
|
+
// src/core/contracts-context.ts
|
|
2458
|
+
function buildContractsContextPrompt(config) {
|
|
2459
|
+
const parts = [];
|
|
2460
|
+
if (!config.agentSpecs?.length && !config.dataViewSpecs?.length && !config.formSpecs?.length && !config.presentationSpecs?.length && !config.operationRefs?.length) {
|
|
2461
|
+
return "";
|
|
2462
|
+
}
|
|
2463
|
+
parts.push(`
|
|
2464
|
+
|
|
2465
|
+
## Available resources`);
|
|
2466
|
+
if (config.agentSpecs?.length) {
|
|
2467
|
+
parts.push(`
|
|
2468
|
+
### Agent tools`);
|
|
2469
|
+
for (const agent of config.agentSpecs) {
|
|
2470
|
+
const toolNames = agent.tools?.map((t) => t.name).join(", ") ?? "none";
|
|
2471
|
+
parts.push(`- **${agent.key}**: tools: ${toolNames}`);
|
|
2472
|
+
}
|
|
2473
|
+
}
|
|
2474
|
+
if (config.dataViewSpecs?.length) {
|
|
2475
|
+
parts.push(`
|
|
2476
|
+
### Data views`);
|
|
2477
|
+
for (const dv of config.dataViewSpecs) {
|
|
2478
|
+
parts.push(`- **${dv.key}**: ${dv.meta.title ?? dv.key}`);
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2481
|
+
if (config.formSpecs?.length) {
|
|
2482
|
+
parts.push(`
|
|
2483
|
+
### Forms`);
|
|
2484
|
+
for (const form of config.formSpecs) {
|
|
2485
|
+
parts.push(`- **${form.key}**: ${form.meta.title ?? form.key}`);
|
|
2486
|
+
}
|
|
2487
|
+
}
|
|
2488
|
+
if (config.presentationSpecs?.length) {
|
|
2489
|
+
parts.push(`
|
|
2490
|
+
### Presentations`);
|
|
2491
|
+
for (const pres of config.presentationSpecs) {
|
|
2492
|
+
parts.push(`- **${pres.key}**: ${pres.meta.title ?? pres.key} (targets: ${pres.targets?.join(", ") ?? "react"})`);
|
|
2493
|
+
}
|
|
2494
|
+
}
|
|
2495
|
+
if (config.operationRefs?.length) {
|
|
2496
|
+
parts.push(`
|
|
2497
|
+
### Operations`);
|
|
2498
|
+
for (const op of config.operationRefs) {
|
|
2499
|
+
parts.push(`- **${op.key}@${op.version}**`);
|
|
2500
|
+
}
|
|
2501
|
+
}
|
|
2502
|
+
parts.push(`
|
|
2503
|
+
Use the available tools to invoke operations, query data views, or propose surface changes when appropriate.`);
|
|
2504
|
+
return parts.join(`
|
|
2505
|
+
`);
|
|
2506
|
+
}
|
|
2507
|
+
|
|
2508
|
+
// src/core/agent-tools-adapter.ts
|
|
2509
|
+
import { tool as tool2 } from "ai";
|
|
2510
|
+
import { z as z2 } from "zod";
|
|
2511
|
+
function getInputSchema(_schema) {
|
|
2512
|
+
return z2.object({}).passthrough();
|
|
2513
|
+
}
|
|
2514
|
+
function agentToolConfigsToToolSet(configs, handlers) {
|
|
2515
|
+
const result = {};
|
|
2516
|
+
for (const config of configs) {
|
|
2517
|
+
const handler = handlers?.[config.name];
|
|
2518
|
+
const inputSchema = getInputSchema(config.schema);
|
|
2519
|
+
result[config.name] = tool2({
|
|
2520
|
+
description: config.description ?? config.name,
|
|
2521
|
+
inputSchema,
|
|
2522
|
+
execute: async (input) => {
|
|
2523
|
+
if (!handler) {
|
|
2524
|
+
return {
|
|
2525
|
+
status: "unimplemented",
|
|
2526
|
+
message: "Wire handler in host",
|
|
2527
|
+
toolName: config.name
|
|
2528
|
+
};
|
|
2529
|
+
}
|
|
2530
|
+
try {
|
|
2531
|
+
const output = await Promise.resolve(handler(input));
|
|
2532
|
+
return typeof output === "string" ? output : output;
|
|
2533
|
+
} catch (err) {
|
|
2534
|
+
return {
|
|
2535
|
+
status: "error",
|
|
2536
|
+
error: err instanceof Error ? err.message : String(err),
|
|
2537
|
+
toolName: config.name
|
|
2538
|
+
};
|
|
2539
|
+
}
|
|
2540
|
+
}
|
|
2541
|
+
});
|
|
2542
|
+
}
|
|
2543
|
+
return result;
|
|
2544
|
+
}
|
|
2545
|
+
|
|
2546
|
+
// src/core/surface-planner-tools.ts
|
|
2547
|
+
import { tool as tool3 } from "ai";
|
|
2548
|
+
import { z as z3 } from "zod";
|
|
2549
|
+
import { validatePatchProposal } from "@contractspec/lib.surface-runtime/spec/validate-surface-patch";
|
|
2550
|
+
import { buildSurfacePatchProposal } from "@contractspec/lib.surface-runtime/runtime/planner-tools";
|
|
2551
|
+
var VALID_OPS = [
|
|
2552
|
+
"insert-node",
|
|
2553
|
+
"replace-node",
|
|
2554
|
+
"remove-node",
|
|
2555
|
+
"move-node",
|
|
2556
|
+
"resize-panel",
|
|
2557
|
+
"set-layout",
|
|
2558
|
+
"reveal-field",
|
|
2559
|
+
"hide-field",
|
|
2560
|
+
"promote-action",
|
|
2561
|
+
"set-focus"
|
|
2562
|
+
];
|
|
2563
|
+
var DEFAULT_NODE_KINDS = [
|
|
2564
|
+
"entity-section",
|
|
2565
|
+
"entity-card",
|
|
2566
|
+
"data-view",
|
|
2567
|
+
"assistant-panel",
|
|
2568
|
+
"chat-thread",
|
|
2569
|
+
"action-bar",
|
|
2570
|
+
"timeline",
|
|
2571
|
+
"table",
|
|
2572
|
+
"rich-doc",
|
|
2573
|
+
"form",
|
|
2574
|
+
"chart",
|
|
2575
|
+
"custom-widget"
|
|
2576
|
+
];
|
|
2577
|
+
function collectSlotIdsFromRegion(node) {
|
|
2578
|
+
const ids = [];
|
|
2579
|
+
if (node.type === "slot") {
|
|
2580
|
+
ids.push(node.slotId);
|
|
2581
|
+
}
|
|
2582
|
+
if (node.type === "panel-group" || node.type === "stack") {
|
|
2583
|
+
for (const child of node.children) {
|
|
2584
|
+
ids.push(...collectSlotIdsFromRegion(child));
|
|
1551
2585
|
}
|
|
1552
|
-
results.sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime());
|
|
1553
|
-
const offset = options?.offset ?? 0;
|
|
1554
|
-
const limit = options?.limit ?? 100;
|
|
1555
|
-
return results.slice(offset, offset + limit);
|
|
1556
2586
|
}
|
|
1557
|
-
|
|
1558
|
-
const
|
|
1559
|
-
|
|
1560
|
-
for (const conversation of this.conversations.values()) {
|
|
1561
|
-
if (conversation.title?.toLowerCase().includes(lowerQuery)) {
|
|
1562
|
-
results.push(conversation);
|
|
1563
|
-
continue;
|
|
1564
|
-
}
|
|
1565
|
-
const hasMatch = conversation.messages.some((m) => m.content.toLowerCase().includes(lowerQuery));
|
|
1566
|
-
if (hasMatch) {
|
|
1567
|
-
results.push(conversation);
|
|
1568
|
-
}
|
|
1569
|
-
if (results.length >= limit)
|
|
1570
|
-
break;
|
|
2587
|
+
if (node.type === "tabs") {
|
|
2588
|
+
for (const tab of node.tabs) {
|
|
2589
|
+
ids.push(...collectSlotIdsFromRegion(tab.child));
|
|
1571
2590
|
}
|
|
1572
|
-
return results;
|
|
1573
2591
|
}
|
|
1574
|
-
|
|
1575
|
-
|
|
2592
|
+
if (node.type === "floating") {
|
|
2593
|
+
ids.push(node.anchorSlotId);
|
|
2594
|
+
ids.push(...collectSlotIdsFromRegion(node.child));
|
|
1576
2595
|
}
|
|
2596
|
+
return ids;
|
|
1577
2597
|
}
|
|
1578
|
-
function
|
|
1579
|
-
|
|
2598
|
+
function deriveConstraints(plan) {
|
|
2599
|
+
const slotIds = collectSlotIdsFromRegion(plan.layoutRoot);
|
|
2600
|
+
const uniqueSlots = [...new Set(slotIds)];
|
|
2601
|
+
return {
|
|
2602
|
+
allowedOps: VALID_OPS,
|
|
2603
|
+
allowedSlots: uniqueSlots.length > 0 ? uniqueSlots : ["assistant", "primary"],
|
|
2604
|
+
allowedNodeKinds: DEFAULT_NODE_KINDS
|
|
2605
|
+
};
|
|
2606
|
+
}
|
|
2607
|
+
var ProposePatchInputSchema = z3.object({
|
|
2608
|
+
proposalId: z3.string().describe("Unique proposal identifier"),
|
|
2609
|
+
ops: z3.array(z3.object({
|
|
2610
|
+
op: z3.enum([
|
|
2611
|
+
"insert-node",
|
|
2612
|
+
"replace-node",
|
|
2613
|
+
"remove-node",
|
|
2614
|
+
"move-node",
|
|
2615
|
+
"resize-panel",
|
|
2616
|
+
"set-layout",
|
|
2617
|
+
"reveal-field",
|
|
2618
|
+
"hide-field",
|
|
2619
|
+
"promote-action",
|
|
2620
|
+
"set-focus"
|
|
2621
|
+
]),
|
|
2622
|
+
slotId: z3.string().optional(),
|
|
2623
|
+
nodeId: z3.string().optional(),
|
|
2624
|
+
toSlotId: z3.string().optional(),
|
|
2625
|
+
index: z3.number().optional(),
|
|
2626
|
+
node: z3.object({
|
|
2627
|
+
nodeId: z3.string(),
|
|
2628
|
+
kind: z3.string(),
|
|
2629
|
+
title: z3.string().optional(),
|
|
2630
|
+
props: z3.record(z3.string(), z3.unknown()).optional(),
|
|
2631
|
+
children: z3.array(z3.unknown()).optional()
|
|
2632
|
+
}).optional(),
|
|
2633
|
+
persistKey: z3.string().optional(),
|
|
2634
|
+
sizes: z3.array(z3.number()).optional(),
|
|
2635
|
+
layoutId: z3.string().optional(),
|
|
2636
|
+
fieldId: z3.string().optional(),
|
|
2637
|
+
actionId: z3.string().optional(),
|
|
2638
|
+
placement: z3.enum(["header", "inline", "context", "assistant"]).optional(),
|
|
2639
|
+
targetId: z3.string().optional()
|
|
2640
|
+
}))
|
|
2641
|
+
});
|
|
2642
|
+
function createSurfacePlannerTools(config) {
|
|
2643
|
+
const { plan, constraints, onPatchProposal } = config;
|
|
2644
|
+
const resolvedConstraints = constraints ?? deriveConstraints(plan);
|
|
2645
|
+
const proposePatchTool = tool3({
|
|
2646
|
+
description: "Propose surface patches (layout changes, node insertions, etc.) for user approval. " + "Only use allowed ops, slots, and node kinds from the planner context.",
|
|
2647
|
+
inputSchema: ProposePatchInputSchema,
|
|
2648
|
+
execute: async (input) => {
|
|
2649
|
+
const ops = input.ops;
|
|
2650
|
+
try {
|
|
2651
|
+
validatePatchProposal(ops, resolvedConstraints);
|
|
2652
|
+
const proposal = buildSurfacePatchProposal(input.proposalId, ops);
|
|
2653
|
+
onPatchProposal?.(proposal);
|
|
2654
|
+
return {
|
|
2655
|
+
success: true,
|
|
2656
|
+
proposalId: proposal.proposalId,
|
|
2657
|
+
opsCount: proposal.ops.length,
|
|
2658
|
+
message: "Patch proposal validated; awaiting user approval"
|
|
2659
|
+
};
|
|
2660
|
+
} catch (err) {
|
|
2661
|
+
return {
|
|
2662
|
+
success: false,
|
|
2663
|
+
error: err instanceof Error ? err.message : String(err),
|
|
2664
|
+
proposalId: input.proposalId
|
|
2665
|
+
};
|
|
2666
|
+
}
|
|
2667
|
+
}
|
|
2668
|
+
});
|
|
2669
|
+
return {
|
|
2670
|
+
"propose-patch": proposePatchTool
|
|
2671
|
+
};
|
|
2672
|
+
}
|
|
2673
|
+
function buildPlannerPromptInput(plan) {
|
|
2674
|
+
const constraints = deriveConstraints(plan);
|
|
2675
|
+
return {
|
|
2676
|
+
bundleMeta: {
|
|
2677
|
+
key: plan.bundleKey,
|
|
2678
|
+
version: "0.0.0",
|
|
2679
|
+
title: plan.bundleKey
|
|
2680
|
+
},
|
|
2681
|
+
surfaceId: plan.surfaceId,
|
|
2682
|
+
allowedPatchOps: constraints.allowedOps,
|
|
2683
|
+
allowedSlots: [...constraints.allowedSlots],
|
|
2684
|
+
allowedNodeKinds: [...constraints.allowedNodeKinds],
|
|
2685
|
+
actions: plan.actions.map((a) => ({ actionId: a.actionId, title: a.title })),
|
|
2686
|
+
preferences: {
|
|
2687
|
+
guidance: "hints",
|
|
2688
|
+
density: "standard",
|
|
2689
|
+
dataDepth: "detailed",
|
|
2690
|
+
control: "standard",
|
|
2691
|
+
media: "text",
|
|
2692
|
+
pace: "balanced",
|
|
2693
|
+
narrative: "top-down"
|
|
2694
|
+
}
|
|
2695
|
+
};
|
|
1580
2696
|
}
|
|
1581
2697
|
|
|
1582
2698
|
// src/core/chat-service.ts
|
|
2699
|
+
import { compilePlannerPrompt } from "@contractspec/lib.surface-runtime/runtime/planner-prompt";
|
|
1583
2700
|
var DEFAULT_SYSTEM_PROMPT = `You are ContractSpec AI, an expert coding assistant specialized in ContractSpec development.
|
|
1584
2701
|
|
|
1585
2702
|
Your capabilities:
|
|
@@ -1594,6 +2711,9 @@ Guidelines:
|
|
|
1594
2711
|
- Reference relevant ContractSpec concepts and patterns
|
|
1595
2712
|
- Ask clarifying questions when the user's intent is unclear
|
|
1596
2713
|
- When suggesting code changes, explain the rationale`;
|
|
2714
|
+
var WORKFLOW_TOOLS_PROMPT = `
|
|
2715
|
+
|
|
2716
|
+
Workflow creation: You can create and modify workflows. Use create_workflow_extension when the user asks to add steps, change a workflow, or create a tenant-specific extension. Use compose_workflow to apply extensions to a base workflow. Use generate_workflow_spec_code to output TypeScript for the user to save.`;
|
|
1597
2717
|
|
|
1598
2718
|
class ChatService {
|
|
1599
2719
|
provider;
|
|
@@ -1603,19 +2723,93 @@ class ChatService {
|
|
|
1603
2723
|
maxHistoryMessages;
|
|
1604
2724
|
onUsage;
|
|
1605
2725
|
tools;
|
|
2726
|
+
thinkingLevel;
|
|
1606
2727
|
sendReasoning;
|
|
1607
2728
|
sendSources;
|
|
2729
|
+
modelSelector;
|
|
1608
2730
|
constructor(config) {
|
|
1609
2731
|
this.provider = config.provider;
|
|
1610
2732
|
this.context = config.context;
|
|
1611
2733
|
this.store = config.store ?? new InMemoryConversationStore;
|
|
1612
|
-
this.systemPrompt = config
|
|
2734
|
+
this.systemPrompt = this.buildSystemPrompt(config);
|
|
1613
2735
|
this.maxHistoryMessages = config.maxHistoryMessages ?? 20;
|
|
1614
2736
|
this.onUsage = config.onUsage;
|
|
1615
|
-
this.tools = config
|
|
1616
|
-
this.
|
|
2737
|
+
this.tools = this.mergeTools(config);
|
|
2738
|
+
this.thinkingLevel = config.thinkingLevel;
|
|
2739
|
+
this.modelSelector = config.modelSelector;
|
|
2740
|
+
this.sendReasoning = config.sendReasoning ?? (config.thinkingLevel != null && config.thinkingLevel !== "instant");
|
|
1617
2741
|
this.sendSources = config.sendSources ?? false;
|
|
1618
2742
|
}
|
|
2743
|
+
buildSystemPrompt(config) {
|
|
2744
|
+
let base = config.systemPrompt ?? DEFAULT_SYSTEM_PROMPT;
|
|
2745
|
+
if (config.workflowToolsConfig?.baseWorkflows?.length) {
|
|
2746
|
+
base += WORKFLOW_TOOLS_PROMPT;
|
|
2747
|
+
}
|
|
2748
|
+
const contractsPrompt = buildContractsContextPrompt(config.contractsContext ?? {});
|
|
2749
|
+
if (contractsPrompt) {
|
|
2750
|
+
base += contractsPrompt;
|
|
2751
|
+
}
|
|
2752
|
+
if (config.surfacePlanConfig?.plan) {
|
|
2753
|
+
const plannerInput = buildPlannerPromptInput(config.surfacePlanConfig.plan);
|
|
2754
|
+
base += `
|
|
2755
|
+
|
|
2756
|
+
` + compilePlannerPrompt(plannerInput);
|
|
2757
|
+
}
|
|
2758
|
+
return base;
|
|
2759
|
+
}
|
|
2760
|
+
mergeTools(config) {
|
|
2761
|
+
let merged = config.tools ?? {};
|
|
2762
|
+
const wfConfig = config.workflowToolsConfig;
|
|
2763
|
+
if (wfConfig?.baseWorkflows?.length) {
|
|
2764
|
+
const workflowTools = createWorkflowTools({
|
|
2765
|
+
baseWorkflows: wfConfig.baseWorkflows,
|
|
2766
|
+
composer: wfConfig.composer
|
|
2767
|
+
});
|
|
2768
|
+
merged = { ...merged, ...workflowTools };
|
|
2769
|
+
}
|
|
2770
|
+
const contractsCtx = config.contractsContext;
|
|
2771
|
+
if (contractsCtx?.agentSpecs?.length) {
|
|
2772
|
+
const allTools = [];
|
|
2773
|
+
for (const agent of contractsCtx.agentSpecs) {
|
|
2774
|
+
if (agent.tools?.length)
|
|
2775
|
+
allTools.push(...agent.tools);
|
|
2776
|
+
}
|
|
2777
|
+
if (allTools.length > 0) {
|
|
2778
|
+
const agentTools = agentToolConfigsToToolSet(allTools);
|
|
2779
|
+
merged = { ...merged, ...agentTools };
|
|
2780
|
+
}
|
|
2781
|
+
}
|
|
2782
|
+
const surfaceConfig = config.surfacePlanConfig;
|
|
2783
|
+
if (surfaceConfig?.plan) {
|
|
2784
|
+
const plannerTools = createSurfacePlannerTools({
|
|
2785
|
+
plan: surfaceConfig.plan,
|
|
2786
|
+
onPatchProposal: surfaceConfig.onPatchProposal
|
|
2787
|
+
});
|
|
2788
|
+
merged = { ...merged, ...plannerTools };
|
|
2789
|
+
}
|
|
2790
|
+
if (config.mcpTools && Object.keys(config.mcpTools).length > 0) {
|
|
2791
|
+
merged = { ...merged, ...config.mcpTools };
|
|
2792
|
+
}
|
|
2793
|
+
return Object.keys(merged).length > 0 ? merged : undefined;
|
|
2794
|
+
}
|
|
2795
|
+
async resolveModel() {
|
|
2796
|
+
if (this.modelSelector) {
|
|
2797
|
+
const dimension = this.thinkingLevelToDimension(this.thinkingLevel);
|
|
2798
|
+
const { model, selection } = await this.modelSelector.selectAndCreate({
|
|
2799
|
+
taskDimension: dimension
|
|
2800
|
+
});
|
|
2801
|
+
return { model, providerName: selection.providerKey };
|
|
2802
|
+
}
|
|
2803
|
+
return {
|
|
2804
|
+
model: this.provider.getModel(),
|
|
2805
|
+
providerName: this.provider.name
|
|
2806
|
+
};
|
|
2807
|
+
}
|
|
2808
|
+
thinkingLevelToDimension(level) {
|
|
2809
|
+
if (!level || level === "instant")
|
|
2810
|
+
return "latency";
|
|
2811
|
+
return "reasoning";
|
|
2812
|
+
}
|
|
1619
2813
|
async send(options) {
|
|
1620
2814
|
let conversation;
|
|
1621
2815
|
if (options.conversationId) {
|
|
@@ -1633,20 +2827,25 @@ class ChatService {
|
|
|
1633
2827
|
workspacePath: this.context?.workspacePath
|
|
1634
2828
|
});
|
|
1635
2829
|
}
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
2830
|
+
if (!options.skipUserAppend) {
|
|
2831
|
+
await this.store.appendMessage(conversation.id, {
|
|
2832
|
+
role: "user",
|
|
2833
|
+
content: options.content,
|
|
2834
|
+
status: "completed",
|
|
2835
|
+
attachments: options.attachments
|
|
2836
|
+
});
|
|
2837
|
+
}
|
|
2838
|
+
conversation = await this.store.get(conversation.id) ?? conversation;
|
|
1642
2839
|
const messages = this.buildMessages(conversation, options);
|
|
1643
|
-
const model = this.
|
|
2840
|
+
const { model, providerName } = await this.resolveModel();
|
|
2841
|
+
const providerOptions = getProviderOptions(this.thinkingLevel, providerName);
|
|
1644
2842
|
try {
|
|
1645
2843
|
const result = await generateText({
|
|
1646
2844
|
model,
|
|
1647
2845
|
messages,
|
|
1648
2846
|
system: this.systemPrompt,
|
|
1649
|
-
tools: this.tools
|
|
2847
|
+
tools: this.tools,
|
|
2848
|
+
providerOptions: Object.keys(providerOptions).length > 0 ? providerOptions : undefined
|
|
1650
2849
|
});
|
|
1651
2850
|
const assistantMessage = await this.store.appendMessage(conversation.id, {
|
|
1652
2851
|
role: "assistant",
|
|
@@ -1691,23 +2890,27 @@ class ChatService {
|
|
|
1691
2890
|
workspacePath: this.context?.workspacePath
|
|
1692
2891
|
});
|
|
1693
2892
|
}
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
2893
|
+
if (!options.skipUserAppend) {
|
|
2894
|
+
await this.store.appendMessage(conversation.id, {
|
|
2895
|
+
role: "user",
|
|
2896
|
+
content: options.content,
|
|
2897
|
+
status: "completed",
|
|
2898
|
+
attachments: options.attachments
|
|
2899
|
+
});
|
|
2900
|
+
}
|
|
2901
|
+
conversation = await this.store.get(conversation.id) ?? conversation;
|
|
1700
2902
|
const assistantMessage = await this.store.appendMessage(conversation.id, {
|
|
1701
2903
|
role: "assistant",
|
|
1702
2904
|
content: "",
|
|
1703
2905
|
status: "streaming"
|
|
1704
2906
|
});
|
|
1705
2907
|
const messages = this.buildMessages(conversation, options);
|
|
1706
|
-
const model = this.
|
|
2908
|
+
const { model, providerName } = await this.resolveModel();
|
|
1707
2909
|
const systemPrompt = this.systemPrompt;
|
|
1708
2910
|
const tools = this.tools;
|
|
1709
2911
|
const store = this.store;
|
|
1710
2912
|
const onUsage = this.onUsage;
|
|
2913
|
+
const streamProviderOptions = getProviderOptions(this.thinkingLevel, providerName);
|
|
1711
2914
|
async function* streamGenerator() {
|
|
1712
2915
|
let fullContent = "";
|
|
1713
2916
|
let fullReasoning = "";
|
|
@@ -1718,7 +2921,8 @@ class ChatService {
|
|
|
1718
2921
|
model,
|
|
1719
2922
|
messages,
|
|
1720
2923
|
system: systemPrompt,
|
|
1721
|
-
tools
|
|
2924
|
+
tools,
|
|
2925
|
+
providerOptions: Object.keys(streamProviderOptions).length > 0 ? streamProviderOptions : undefined
|
|
1722
2926
|
});
|
|
1723
2927
|
for await (const part of result.fullStream) {
|
|
1724
2928
|
if (part.type === "text-delta") {
|
|
@@ -1833,6 +3037,18 @@ class ChatService {
|
|
|
1833
3037
|
...options
|
|
1834
3038
|
});
|
|
1835
3039
|
}
|
|
3040
|
+
async updateConversation(conversationId, updates) {
|
|
3041
|
+
return this.store.update(conversationId, updates);
|
|
3042
|
+
}
|
|
3043
|
+
async forkConversation(conversationId, upToMessageId) {
|
|
3044
|
+
return this.store.fork(conversationId, upToMessageId);
|
|
3045
|
+
}
|
|
3046
|
+
async updateMessage(conversationId, messageId, updates) {
|
|
3047
|
+
return this.store.updateMessage(conversationId, messageId, updates);
|
|
3048
|
+
}
|
|
3049
|
+
async truncateAfter(conversationId, messageId) {
|
|
3050
|
+
return this.store.truncateAfter(conversationId, messageId);
|
|
3051
|
+
}
|
|
1836
3052
|
async deleteConversation(conversationId) {
|
|
1837
3053
|
return this.store.delete(conversationId);
|
|
1838
3054
|
}
|
|
@@ -1903,9 +3119,9 @@ import {
|
|
|
1903
3119
|
function toolsToToolSet(defs) {
|
|
1904
3120
|
const result = {};
|
|
1905
3121
|
for (const def of defs) {
|
|
1906
|
-
result[def.name] =
|
|
3122
|
+
result[def.name] = tool4({
|
|
1907
3123
|
description: def.description ?? def.name,
|
|
1908
|
-
inputSchema:
|
|
3124
|
+
inputSchema: z4.object({}).passthrough(),
|
|
1909
3125
|
execute: async () => ({})
|
|
1910
3126
|
});
|
|
1911
3127
|
}
|
|
@@ -1919,22 +3135,64 @@ function useChat(options = {}) {
|
|
|
1919
3135
|
apiKey,
|
|
1920
3136
|
proxyUrl,
|
|
1921
3137
|
conversationId: initialConversationId,
|
|
3138
|
+
store,
|
|
1922
3139
|
systemPrompt,
|
|
1923
3140
|
streaming = true,
|
|
1924
3141
|
onSend,
|
|
1925
3142
|
onResponse,
|
|
1926
3143
|
onError,
|
|
1927
3144
|
onUsage,
|
|
1928
|
-
tools: toolsDefs
|
|
3145
|
+
tools: toolsDefs,
|
|
3146
|
+
thinkingLevel,
|
|
3147
|
+
workflowToolsConfig,
|
|
3148
|
+
modelSelector,
|
|
3149
|
+
contractsContext,
|
|
3150
|
+
surfacePlanConfig,
|
|
3151
|
+
mcpServers,
|
|
3152
|
+
agentMode
|
|
1929
3153
|
} = options;
|
|
1930
|
-
const [messages, setMessages] =
|
|
1931
|
-
const [
|
|
1932
|
-
const
|
|
1933
|
-
const [
|
|
1934
|
-
const [
|
|
1935
|
-
const
|
|
1936
|
-
const
|
|
1937
|
-
|
|
3154
|
+
const [messages, setMessages] = React11.useState([]);
|
|
3155
|
+
const [mcpTools, setMcpTools] = React11.useState(null);
|
|
3156
|
+
const mcpCleanupRef = React11.useRef(null);
|
|
3157
|
+
const [conversation, setConversation] = React11.useState(null);
|
|
3158
|
+
const [isLoading, setIsLoading] = React11.useState(false);
|
|
3159
|
+
const [error, setError] = React11.useState(null);
|
|
3160
|
+
const [conversationId, setConversationId] = React11.useState(initialConversationId ?? null);
|
|
3161
|
+
const abortControllerRef = React11.useRef(null);
|
|
3162
|
+
const chatServiceRef = React11.useRef(null);
|
|
3163
|
+
React11.useEffect(() => {
|
|
3164
|
+
if (!mcpServers?.length) {
|
|
3165
|
+
setMcpTools(null);
|
|
3166
|
+
return;
|
|
3167
|
+
}
|
|
3168
|
+
let cancelled = false;
|
|
3169
|
+
import("@contractspec/lib.ai-agent/tools/mcp-client").then(({ createMcpToolsets }) => {
|
|
3170
|
+
createMcpToolsets(mcpServers).then(({ tools, cleanup }) => {
|
|
3171
|
+
if (!cancelled) {
|
|
3172
|
+
setMcpTools(tools);
|
|
3173
|
+
mcpCleanupRef.current = cleanup;
|
|
3174
|
+
} else {
|
|
3175
|
+
cleanup().catch(() => {
|
|
3176
|
+
return;
|
|
3177
|
+
});
|
|
3178
|
+
}
|
|
3179
|
+
}).catch(() => {
|
|
3180
|
+
if (!cancelled)
|
|
3181
|
+
setMcpTools(null);
|
|
3182
|
+
});
|
|
3183
|
+
});
|
|
3184
|
+
return () => {
|
|
3185
|
+
cancelled = true;
|
|
3186
|
+
const cleanup = mcpCleanupRef.current;
|
|
3187
|
+
mcpCleanupRef.current = null;
|
|
3188
|
+
if (cleanup)
|
|
3189
|
+
cleanup().catch(() => {
|
|
3190
|
+
return;
|
|
3191
|
+
});
|
|
3192
|
+
setMcpTools(null);
|
|
3193
|
+
};
|
|
3194
|
+
}, [mcpServers]);
|
|
3195
|
+
React11.useEffect(() => {
|
|
1938
3196
|
const chatProvider = createProvider({
|
|
1939
3197
|
provider,
|
|
1940
3198
|
model,
|
|
@@ -1943,9 +3201,16 @@ function useChat(options = {}) {
|
|
|
1943
3201
|
});
|
|
1944
3202
|
chatServiceRef.current = new ChatService({
|
|
1945
3203
|
provider: chatProvider,
|
|
3204
|
+
store,
|
|
1946
3205
|
systemPrompt,
|
|
1947
3206
|
onUsage,
|
|
1948
|
-
tools: toolsDefs?.length ? toolsToToolSet(toolsDefs) : undefined
|
|
3207
|
+
tools: toolsDefs?.length ? toolsToToolSet(toolsDefs) : undefined,
|
|
3208
|
+
thinkingLevel,
|
|
3209
|
+
workflowToolsConfig,
|
|
3210
|
+
modelSelector,
|
|
3211
|
+
contractsContext,
|
|
3212
|
+
surfacePlanConfig,
|
|
3213
|
+
mcpTools
|
|
1949
3214
|
});
|
|
1950
3215
|
}, [
|
|
1951
3216
|
provider,
|
|
@@ -1953,11 +3218,18 @@ function useChat(options = {}) {
|
|
|
1953
3218
|
model,
|
|
1954
3219
|
apiKey,
|
|
1955
3220
|
proxyUrl,
|
|
3221
|
+
store,
|
|
1956
3222
|
systemPrompt,
|
|
1957
3223
|
onUsage,
|
|
1958
|
-
toolsDefs
|
|
3224
|
+
toolsDefs,
|
|
3225
|
+
thinkingLevel,
|
|
3226
|
+
workflowToolsConfig,
|
|
3227
|
+
modelSelector,
|
|
3228
|
+
contractsContext,
|
|
3229
|
+
surfacePlanConfig,
|
|
3230
|
+
mcpTools
|
|
1959
3231
|
]);
|
|
1960
|
-
|
|
3232
|
+
React11.useEffect(() => {
|
|
1961
3233
|
if (!conversationId || !chatServiceRef.current)
|
|
1962
3234
|
return;
|
|
1963
3235
|
const loadConversation = async () => {
|
|
@@ -1971,7 +3243,90 @@ function useChat(options = {}) {
|
|
|
1971
3243
|
};
|
|
1972
3244
|
loadConversation().catch(console.error);
|
|
1973
3245
|
}, [conversationId]);
|
|
1974
|
-
const sendMessage =
|
|
3246
|
+
const sendMessage = React11.useCallback(async (content, attachments, opts) => {
|
|
3247
|
+
if (agentMode?.agent) {
|
|
3248
|
+
setIsLoading(true);
|
|
3249
|
+
setError(null);
|
|
3250
|
+
abortControllerRef.current = new AbortController;
|
|
3251
|
+
try {
|
|
3252
|
+
if (!opts?.skipUserAppend) {
|
|
3253
|
+
const userMessage = {
|
|
3254
|
+
id: `msg_${Date.now()}`,
|
|
3255
|
+
conversationId: conversationId ?? "",
|
|
3256
|
+
role: "user",
|
|
3257
|
+
content,
|
|
3258
|
+
status: "completed",
|
|
3259
|
+
createdAt: new Date,
|
|
3260
|
+
updatedAt: new Date,
|
|
3261
|
+
attachments
|
|
3262
|
+
};
|
|
3263
|
+
setMessages((prev) => [...prev, userMessage]);
|
|
3264
|
+
onSend?.(userMessage);
|
|
3265
|
+
}
|
|
3266
|
+
const result = await agentMode.agent.generate({
|
|
3267
|
+
prompt: content,
|
|
3268
|
+
signal: abortControllerRef.current.signal
|
|
3269
|
+
});
|
|
3270
|
+
const toolCallsMap = new Map;
|
|
3271
|
+
for (const tc of result.toolCalls ?? []) {
|
|
3272
|
+
const tr = result.toolResults?.find((r) => r.toolCallId === tc.toolCallId);
|
|
3273
|
+
toolCallsMap.set(tc.toolCallId, {
|
|
3274
|
+
id: tc.toolCallId,
|
|
3275
|
+
name: tc.toolName,
|
|
3276
|
+
args: tc.args ?? {},
|
|
3277
|
+
result: tr?.output,
|
|
3278
|
+
status: "completed"
|
|
3279
|
+
});
|
|
3280
|
+
}
|
|
3281
|
+
const assistantMessage = {
|
|
3282
|
+
id: `msg_${Date.now()}_a`,
|
|
3283
|
+
conversationId: conversationId ?? "",
|
|
3284
|
+
role: "assistant",
|
|
3285
|
+
content: result.text,
|
|
3286
|
+
status: "completed",
|
|
3287
|
+
createdAt: new Date,
|
|
3288
|
+
updatedAt: new Date,
|
|
3289
|
+
toolCalls: toolCallsMap.size ? Array.from(toolCallsMap.values()) : undefined,
|
|
3290
|
+
usage: result.usage
|
|
3291
|
+
};
|
|
3292
|
+
setMessages((prev) => [...prev, assistantMessage]);
|
|
3293
|
+
onResponse?.(assistantMessage);
|
|
3294
|
+
onUsage?.(result.usage ?? { inputTokens: 0, outputTokens: 0 });
|
|
3295
|
+
if (store && !conversationId) {
|
|
3296
|
+
const conv = await store.create({
|
|
3297
|
+
status: "active",
|
|
3298
|
+
provider: "agent",
|
|
3299
|
+
model: "agent",
|
|
3300
|
+
messages: []
|
|
3301
|
+
});
|
|
3302
|
+
if (!opts?.skipUserAppend) {
|
|
3303
|
+
await store.appendMessage(conv.id, {
|
|
3304
|
+
role: "user",
|
|
3305
|
+
content,
|
|
3306
|
+
status: "completed",
|
|
3307
|
+
attachments
|
|
3308
|
+
});
|
|
3309
|
+
}
|
|
3310
|
+
await store.appendMessage(conv.id, {
|
|
3311
|
+
role: "assistant",
|
|
3312
|
+
content: result.text,
|
|
3313
|
+
status: "completed",
|
|
3314
|
+
toolCalls: assistantMessage.toolCalls,
|
|
3315
|
+
usage: result.usage
|
|
3316
|
+
});
|
|
3317
|
+
const updated = await store.get(conv.id);
|
|
3318
|
+
if (updated)
|
|
3319
|
+
setConversation(updated);
|
|
3320
|
+
setConversationId(conv.id);
|
|
3321
|
+
}
|
|
3322
|
+
} catch (err) {
|
|
3323
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
3324
|
+
onError?.(err instanceof Error ? err : new Error(String(err)));
|
|
3325
|
+
} finally {
|
|
3326
|
+
setIsLoading(false);
|
|
3327
|
+
}
|
|
3328
|
+
return;
|
|
3329
|
+
}
|
|
1975
3330
|
if (!chatServiceRef.current) {
|
|
1976
3331
|
throw new Error("Chat service not initialized");
|
|
1977
3332
|
}
|
|
@@ -1979,25 +3334,28 @@ function useChat(options = {}) {
|
|
|
1979
3334
|
setError(null);
|
|
1980
3335
|
abortControllerRef.current = new AbortController;
|
|
1981
3336
|
try {
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
3337
|
+
if (!opts?.skipUserAppend) {
|
|
3338
|
+
const userMessage = {
|
|
3339
|
+
id: `msg_${Date.now()}`,
|
|
3340
|
+
conversationId: conversationId ?? "",
|
|
3341
|
+
role: "user",
|
|
3342
|
+
content,
|
|
3343
|
+
status: "completed",
|
|
3344
|
+
createdAt: new Date,
|
|
3345
|
+
updatedAt: new Date,
|
|
3346
|
+
attachments
|
|
3347
|
+
};
|
|
3348
|
+
setMessages((prev) => [...prev, userMessage]);
|
|
3349
|
+
onSend?.(userMessage);
|
|
3350
|
+
}
|
|
1994
3351
|
if (streaming) {
|
|
1995
3352
|
const result = await chatServiceRef.current.stream({
|
|
1996
3353
|
conversationId: conversationId ?? undefined,
|
|
1997
3354
|
content,
|
|
1998
|
-
attachments
|
|
3355
|
+
attachments,
|
|
3356
|
+
skipUserAppend: opts?.skipUserAppend
|
|
1999
3357
|
});
|
|
2000
|
-
if (!conversationId) {
|
|
3358
|
+
if (!conversationId && !opts?.skipUserAppend) {
|
|
2001
3359
|
setConversationId(result.conversationId);
|
|
2002
3360
|
}
|
|
2003
3361
|
const assistantMessage = {
|
|
@@ -2078,7 +3436,8 @@ function useChat(options = {}) {
|
|
|
2078
3436
|
const result = await chatServiceRef.current.send({
|
|
2079
3437
|
conversationId: conversationId ?? undefined,
|
|
2080
3438
|
content,
|
|
2081
|
-
attachments
|
|
3439
|
+
attachments,
|
|
3440
|
+
skipUserAppend: opts?.skipUserAppend
|
|
2082
3441
|
});
|
|
2083
3442
|
setConversation(result.conversation);
|
|
2084
3443
|
setMessages(result.conversation.messages);
|
|
@@ -2095,14 +3454,14 @@ function useChat(options = {}) {
|
|
|
2095
3454
|
setIsLoading(false);
|
|
2096
3455
|
abortControllerRef.current = null;
|
|
2097
3456
|
}
|
|
2098
|
-
}, [conversationId, streaming, onSend, onResponse, onError, messages]);
|
|
2099
|
-
const clearConversation =
|
|
3457
|
+
}, [conversationId, streaming, onSend, onResponse, onError, onUsage, messages, agentMode, store]);
|
|
3458
|
+
const clearConversation = React11.useCallback(() => {
|
|
2100
3459
|
setMessages([]);
|
|
2101
3460
|
setConversation(null);
|
|
2102
3461
|
setConversationId(null);
|
|
2103
3462
|
setError(null);
|
|
2104
3463
|
}, []);
|
|
2105
|
-
const regenerate =
|
|
3464
|
+
const regenerate = React11.useCallback(async () => {
|
|
2106
3465
|
const lastUserMessageIndex = messages.findLastIndex((m) => m.role === "user");
|
|
2107
3466
|
if (lastUserMessageIndex === -1)
|
|
2108
3467
|
return;
|
|
@@ -2112,11 +3471,49 @@ function useChat(options = {}) {
|
|
|
2112
3471
|
setMessages((prev) => prev.slice(0, lastUserMessageIndex + 1));
|
|
2113
3472
|
await sendMessage(lastUserMessage.content, lastUserMessage.attachments);
|
|
2114
3473
|
}, [messages, sendMessage]);
|
|
2115
|
-
const stop =
|
|
3474
|
+
const stop = React11.useCallback(() => {
|
|
2116
3475
|
abortControllerRef.current?.abort();
|
|
2117
3476
|
setIsLoading(false);
|
|
2118
3477
|
}, []);
|
|
2119
|
-
const
|
|
3478
|
+
const createNewConversation = clearConversation;
|
|
3479
|
+
const editMessage = React11.useCallback(async (messageId, newContent) => {
|
|
3480
|
+
if (!chatServiceRef.current || !conversationId)
|
|
3481
|
+
return;
|
|
3482
|
+
const msg = messages.find((m) => m.id === messageId);
|
|
3483
|
+
if (!msg || msg.role !== "user")
|
|
3484
|
+
return;
|
|
3485
|
+
await chatServiceRef.current.updateMessage(conversationId, messageId, { content: newContent });
|
|
3486
|
+
const truncated = await chatServiceRef.current.truncateAfter(conversationId, messageId);
|
|
3487
|
+
if (truncated) {
|
|
3488
|
+
setMessages(truncated.messages);
|
|
3489
|
+
}
|
|
3490
|
+
await sendMessage(newContent, undefined, { skipUserAppend: true });
|
|
3491
|
+
}, [conversationId, messages, sendMessage]);
|
|
3492
|
+
const forkConversation = React11.useCallback(async (upToMessageId) => {
|
|
3493
|
+
if (!chatServiceRef.current)
|
|
3494
|
+
return null;
|
|
3495
|
+
const idToFork = conversationId ?? conversation?.id;
|
|
3496
|
+
if (!idToFork)
|
|
3497
|
+
return null;
|
|
3498
|
+
try {
|
|
3499
|
+
const forked = await chatServiceRef.current.forkConversation(idToFork, upToMessageId);
|
|
3500
|
+
setConversationId(forked.id);
|
|
3501
|
+
setConversation(forked);
|
|
3502
|
+
setMessages(forked.messages);
|
|
3503
|
+
return forked.id;
|
|
3504
|
+
} catch {
|
|
3505
|
+
return null;
|
|
3506
|
+
}
|
|
3507
|
+
}, [conversationId, conversation]);
|
|
3508
|
+
const updateConversationFn = React11.useCallback(async (updates) => {
|
|
3509
|
+
if (!chatServiceRef.current || !conversationId)
|
|
3510
|
+
return null;
|
|
3511
|
+
const updated = await chatServiceRef.current.updateConversation(conversationId, updates);
|
|
3512
|
+
if (updated)
|
|
3513
|
+
setConversation(updated);
|
|
3514
|
+
return updated;
|
|
3515
|
+
}, [conversationId]);
|
|
3516
|
+
const addToolApprovalResponse = React11.useCallback((_toolCallId, _result) => {
|
|
2120
3517
|
throw new Error(`addToolApprovalResponse: Tool approval requires server route with toUIMessageStreamResponse. ` + `Use createChatRoute and @ai-sdk/react useChat for tools with requireApproval.`);
|
|
2121
3518
|
}, []);
|
|
2122
3519
|
const hasApprovalTools = toolsDefs?.some((t) => t.requireApproval) ?? false;
|
|
@@ -2130,20 +3527,731 @@ function useChat(options = {}) {
|
|
|
2130
3527
|
setConversationId,
|
|
2131
3528
|
regenerate,
|
|
2132
3529
|
stop,
|
|
3530
|
+
createNewConversation,
|
|
3531
|
+
editMessage,
|
|
3532
|
+
forkConversation,
|
|
3533
|
+
updateConversation: updateConversationFn,
|
|
2133
3534
|
...hasApprovalTools && { addToolApprovalResponse }
|
|
2134
3535
|
};
|
|
2135
3536
|
}
|
|
3537
|
+
|
|
3538
|
+
// src/core/local-storage-conversation-store.ts
|
|
3539
|
+
var DEFAULT_KEY = "contractspec:ai-chat:conversations";
|
|
3540
|
+
function generateId2(prefix) {
|
|
3541
|
+
return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
|
|
3542
|
+
}
|
|
3543
|
+
function toSerializable(conv) {
|
|
3544
|
+
return {
|
|
3545
|
+
...conv,
|
|
3546
|
+
createdAt: conv.createdAt.toISOString(),
|
|
3547
|
+
updatedAt: conv.updatedAt.toISOString(),
|
|
3548
|
+
messages: conv.messages.map((m) => ({
|
|
3549
|
+
...m,
|
|
3550
|
+
createdAt: m.createdAt.toISOString(),
|
|
3551
|
+
updatedAt: m.updatedAt.toISOString()
|
|
3552
|
+
}))
|
|
3553
|
+
};
|
|
3554
|
+
}
|
|
3555
|
+
function fromSerializable(raw) {
|
|
3556
|
+
const messages = raw.messages?.map((m) => ({
|
|
3557
|
+
...m,
|
|
3558
|
+
createdAt: new Date(m.createdAt),
|
|
3559
|
+
updatedAt: new Date(m.updatedAt)
|
|
3560
|
+
})) ?? [];
|
|
3561
|
+
return {
|
|
3562
|
+
...raw,
|
|
3563
|
+
createdAt: new Date(raw.createdAt),
|
|
3564
|
+
updatedAt: new Date(raw.updatedAt),
|
|
3565
|
+
messages
|
|
3566
|
+
};
|
|
3567
|
+
}
|
|
3568
|
+
function loadAll(key) {
|
|
3569
|
+
if (typeof window === "undefined")
|
|
3570
|
+
return new Map;
|
|
3571
|
+
try {
|
|
3572
|
+
const raw = window.localStorage.getItem(key);
|
|
3573
|
+
if (!raw)
|
|
3574
|
+
return new Map;
|
|
3575
|
+
const arr = JSON.parse(raw);
|
|
3576
|
+
const map = new Map;
|
|
3577
|
+
for (const item of arr) {
|
|
3578
|
+
const conv = fromSerializable(item);
|
|
3579
|
+
map.set(conv.id, conv);
|
|
3580
|
+
}
|
|
3581
|
+
return map;
|
|
3582
|
+
} catch {
|
|
3583
|
+
return new Map;
|
|
3584
|
+
}
|
|
3585
|
+
}
|
|
3586
|
+
function saveAll(key, map) {
|
|
3587
|
+
if (typeof window === "undefined")
|
|
3588
|
+
return;
|
|
3589
|
+
try {
|
|
3590
|
+
const arr = Array.from(map.values()).map(toSerializable);
|
|
3591
|
+
window.localStorage.setItem(key, JSON.stringify(arr));
|
|
3592
|
+
} catch {}
|
|
3593
|
+
}
|
|
3594
|
+
|
|
3595
|
+
class LocalStorageConversationStore {
|
|
3596
|
+
key;
|
|
3597
|
+
cache = null;
|
|
3598
|
+
constructor(storageKey = DEFAULT_KEY) {
|
|
3599
|
+
this.key = storageKey;
|
|
3600
|
+
}
|
|
3601
|
+
getMap() {
|
|
3602
|
+
if (!this.cache) {
|
|
3603
|
+
this.cache = loadAll(this.key);
|
|
3604
|
+
}
|
|
3605
|
+
return this.cache;
|
|
3606
|
+
}
|
|
3607
|
+
persist() {
|
|
3608
|
+
saveAll(this.key, this.getMap());
|
|
3609
|
+
}
|
|
3610
|
+
async get(conversationId) {
|
|
3611
|
+
return this.getMap().get(conversationId) ?? null;
|
|
3612
|
+
}
|
|
3613
|
+
async create(conversation) {
|
|
3614
|
+
const now = new Date;
|
|
3615
|
+
const full = {
|
|
3616
|
+
...conversation,
|
|
3617
|
+
id: generateId2("conv"),
|
|
3618
|
+
createdAt: now,
|
|
3619
|
+
updatedAt: now
|
|
3620
|
+
};
|
|
3621
|
+
this.getMap().set(full.id, full);
|
|
3622
|
+
this.persist();
|
|
3623
|
+
return full;
|
|
3624
|
+
}
|
|
3625
|
+
async update(conversationId, updates) {
|
|
3626
|
+
const conv = this.getMap().get(conversationId);
|
|
3627
|
+
if (!conv)
|
|
3628
|
+
return null;
|
|
3629
|
+
const updated = {
|
|
3630
|
+
...conv,
|
|
3631
|
+
...updates,
|
|
3632
|
+
updatedAt: new Date
|
|
3633
|
+
};
|
|
3634
|
+
this.getMap().set(conversationId, updated);
|
|
3635
|
+
this.persist();
|
|
3636
|
+
return updated;
|
|
3637
|
+
}
|
|
3638
|
+
async appendMessage(conversationId, message) {
|
|
3639
|
+
const conv = this.getMap().get(conversationId);
|
|
3640
|
+
if (!conv)
|
|
3641
|
+
throw new Error(`Conversation ${conversationId} not found`);
|
|
3642
|
+
const now = new Date;
|
|
3643
|
+
const fullMessage = {
|
|
3644
|
+
...message,
|
|
3645
|
+
id: generateId2("msg"),
|
|
3646
|
+
conversationId,
|
|
3647
|
+
createdAt: now,
|
|
3648
|
+
updatedAt: now
|
|
3649
|
+
};
|
|
3650
|
+
conv.messages.push(fullMessage);
|
|
3651
|
+
conv.updatedAt = now;
|
|
3652
|
+
this.persist();
|
|
3653
|
+
return fullMessage;
|
|
3654
|
+
}
|
|
3655
|
+
async updateMessage(conversationId, messageId, updates) {
|
|
3656
|
+
const conv = this.getMap().get(conversationId);
|
|
3657
|
+
if (!conv)
|
|
3658
|
+
return null;
|
|
3659
|
+
const idx = conv.messages.findIndex((m) => m.id === messageId);
|
|
3660
|
+
if (idx === -1)
|
|
3661
|
+
return null;
|
|
3662
|
+
const msg = conv.messages[idx];
|
|
3663
|
+
if (!msg)
|
|
3664
|
+
return null;
|
|
3665
|
+
const updated = {
|
|
3666
|
+
...msg,
|
|
3667
|
+
...updates,
|
|
3668
|
+
updatedAt: new Date
|
|
3669
|
+
};
|
|
3670
|
+
conv.messages[idx] = updated;
|
|
3671
|
+
conv.updatedAt = new Date;
|
|
3672
|
+
this.persist();
|
|
3673
|
+
return updated;
|
|
3674
|
+
}
|
|
3675
|
+
async delete(conversationId) {
|
|
3676
|
+
const deleted = this.getMap().delete(conversationId);
|
|
3677
|
+
if (deleted)
|
|
3678
|
+
this.persist();
|
|
3679
|
+
return deleted;
|
|
3680
|
+
}
|
|
3681
|
+
async list(options) {
|
|
3682
|
+
let results = Array.from(this.getMap().values());
|
|
3683
|
+
if (options?.status) {
|
|
3684
|
+
results = results.filter((c) => c.status === options.status);
|
|
3685
|
+
}
|
|
3686
|
+
if (options?.projectId) {
|
|
3687
|
+
results = results.filter((c) => c.projectId === options.projectId);
|
|
3688
|
+
}
|
|
3689
|
+
if (options?.tags && options.tags.length > 0) {
|
|
3690
|
+
const tagSet = new Set(options.tags);
|
|
3691
|
+
results = results.filter((c) => c.tags && c.tags.some((t) => tagSet.has(t)));
|
|
3692
|
+
}
|
|
3693
|
+
results.sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime());
|
|
3694
|
+
const offset = options?.offset ?? 0;
|
|
3695
|
+
const limit = options?.limit ?? 100;
|
|
3696
|
+
return results.slice(offset, offset + limit);
|
|
3697
|
+
}
|
|
3698
|
+
async fork(conversationId, upToMessageId) {
|
|
3699
|
+
const source = this.getMap().get(conversationId);
|
|
3700
|
+
if (!source)
|
|
3701
|
+
throw new Error(`Conversation ${conversationId} not found`);
|
|
3702
|
+
let messagesToCopy = source.messages;
|
|
3703
|
+
if (upToMessageId) {
|
|
3704
|
+
const idx = source.messages.findIndex((m) => m.id === upToMessageId);
|
|
3705
|
+
if (idx === -1)
|
|
3706
|
+
throw new Error(`Message ${upToMessageId} not found`);
|
|
3707
|
+
messagesToCopy = source.messages.slice(0, idx + 1);
|
|
3708
|
+
}
|
|
3709
|
+
const now = new Date;
|
|
3710
|
+
const forkedMessages = messagesToCopy.map((m) => ({
|
|
3711
|
+
...m,
|
|
3712
|
+
id: generateId2("msg"),
|
|
3713
|
+
conversationId: "",
|
|
3714
|
+
createdAt: new Date(m.createdAt),
|
|
3715
|
+
updatedAt: new Date(m.updatedAt)
|
|
3716
|
+
}));
|
|
3717
|
+
const forked = {
|
|
3718
|
+
...source,
|
|
3719
|
+
id: generateId2("conv"),
|
|
3720
|
+
title: source.title ? `${source.title} (fork)` : undefined,
|
|
3721
|
+
forkedFromId: source.id,
|
|
3722
|
+
createdAt: now,
|
|
3723
|
+
updatedAt: now,
|
|
3724
|
+
messages: forkedMessages
|
|
3725
|
+
};
|
|
3726
|
+
for (const m of forked.messages) {
|
|
3727
|
+
m.conversationId = forked.id;
|
|
3728
|
+
}
|
|
3729
|
+
this.getMap().set(forked.id, forked);
|
|
3730
|
+
this.persist();
|
|
3731
|
+
return forked;
|
|
3732
|
+
}
|
|
3733
|
+
async truncateAfter(conversationId, messageId) {
|
|
3734
|
+
const conv = this.getMap().get(conversationId);
|
|
3735
|
+
if (!conv)
|
|
3736
|
+
return null;
|
|
3737
|
+
const idx = conv.messages.findIndex((m) => m.id === messageId);
|
|
3738
|
+
if (idx === -1)
|
|
3739
|
+
return null;
|
|
3740
|
+
conv.messages = conv.messages.slice(0, idx + 1);
|
|
3741
|
+
conv.updatedAt = new Date;
|
|
3742
|
+
this.persist();
|
|
3743
|
+
return conv;
|
|
3744
|
+
}
|
|
3745
|
+
async search(query, limit = 20) {
|
|
3746
|
+
const lowerQuery = query.toLowerCase();
|
|
3747
|
+
const results = [];
|
|
3748
|
+
for (const conv of this.getMap().values()) {
|
|
3749
|
+
if (conv.title?.toLowerCase().includes(lowerQuery)) {
|
|
3750
|
+
results.push(conv);
|
|
3751
|
+
continue;
|
|
3752
|
+
}
|
|
3753
|
+
if (conv.messages.some((m) => m.content.toLowerCase().includes(lowerQuery))) {
|
|
3754
|
+
results.push(conv);
|
|
3755
|
+
}
|
|
3756
|
+
if (results.length >= limit)
|
|
3757
|
+
break;
|
|
3758
|
+
}
|
|
3759
|
+
return results;
|
|
3760
|
+
}
|
|
3761
|
+
}
|
|
3762
|
+
function createLocalStorageConversationStore(storageKey) {
|
|
3763
|
+
return new LocalStorageConversationStore(storageKey);
|
|
3764
|
+
}
|
|
3765
|
+
|
|
3766
|
+
// src/presentation/components/ChatWithSidebar.tsx
|
|
3767
|
+
import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
3768
|
+
"use client";
|
|
3769
|
+
var defaultStore = createLocalStorageConversationStore();
|
|
3770
|
+
function ChatWithSidebar({
|
|
3771
|
+
store = defaultStore,
|
|
3772
|
+
projectId,
|
|
3773
|
+
tags,
|
|
3774
|
+
className,
|
|
3775
|
+
thinkingLevel: initialThinkingLevel = "thinking",
|
|
3776
|
+
presentationRenderer,
|
|
3777
|
+
formRenderer,
|
|
3778
|
+
...useChatOptions
|
|
3779
|
+
}) {
|
|
3780
|
+
const effectiveStore = store;
|
|
3781
|
+
const [thinkingLevel, setThinkingLevel] = React12.useState(initialThinkingLevel);
|
|
3782
|
+
const chat = useChat({
|
|
3783
|
+
...useChatOptions,
|
|
3784
|
+
store: effectiveStore,
|
|
3785
|
+
thinkingLevel
|
|
3786
|
+
});
|
|
3787
|
+
const {
|
|
3788
|
+
messages,
|
|
3789
|
+
conversation,
|
|
3790
|
+
sendMessage,
|
|
3791
|
+
isLoading,
|
|
3792
|
+
setConversationId,
|
|
3793
|
+
createNewConversation,
|
|
3794
|
+
editMessage,
|
|
3795
|
+
forkConversation,
|
|
3796
|
+
updateConversation
|
|
3797
|
+
} = chat;
|
|
3798
|
+
const selectedConversationId = conversation?.id ?? null;
|
|
3799
|
+
const handleSelectConversation = React12.useCallback((id) => {
|
|
3800
|
+
setConversationId(id);
|
|
3801
|
+
}, [setConversationId]);
|
|
3802
|
+
return /* @__PURE__ */ jsxs10("div", {
|
|
3803
|
+
className: className ?? "flex h-full w-full",
|
|
3804
|
+
children: [
|
|
3805
|
+
/* @__PURE__ */ jsx10(ChatSidebar, {
|
|
3806
|
+
store: effectiveStore,
|
|
3807
|
+
selectedConversationId,
|
|
3808
|
+
onSelectConversation: handleSelectConversation,
|
|
3809
|
+
onCreateNew: createNewConversation,
|
|
3810
|
+
projectId,
|
|
3811
|
+
tags,
|
|
3812
|
+
selectedConversation: conversation,
|
|
3813
|
+
onUpdateConversation: updateConversation ? async (id, updates) => {
|
|
3814
|
+
if (id === selectedConversationId) {
|
|
3815
|
+
await updateConversation(updates);
|
|
3816
|
+
}
|
|
3817
|
+
} : undefined
|
|
3818
|
+
}),
|
|
3819
|
+
/* @__PURE__ */ jsx10("div", {
|
|
3820
|
+
className: "flex min-w-0 flex-1 flex-col",
|
|
3821
|
+
children: /* @__PURE__ */ jsx10(ChatWithExport, {
|
|
3822
|
+
messages,
|
|
3823
|
+
conversation,
|
|
3824
|
+
onCreateNew: createNewConversation,
|
|
3825
|
+
onFork: forkConversation,
|
|
3826
|
+
onEditMessage: editMessage,
|
|
3827
|
+
thinkingLevel,
|
|
3828
|
+
onThinkingLevelChange: setThinkingLevel,
|
|
3829
|
+
presentationRenderer,
|
|
3830
|
+
formRenderer,
|
|
3831
|
+
children: /* @__PURE__ */ jsx10(ChatInput, {
|
|
3832
|
+
onSend: (content, att) => sendMessage(content, att),
|
|
3833
|
+
disabled: isLoading,
|
|
3834
|
+
isLoading
|
|
3835
|
+
})
|
|
3836
|
+
})
|
|
3837
|
+
})
|
|
3838
|
+
]
|
|
3839
|
+
});
|
|
3840
|
+
}
|
|
3841
|
+
// src/presentation/components/ModelPicker.tsx
|
|
3842
|
+
import * as React13 from "react";
|
|
3843
|
+
import { cn as cn7 } from "@contractspec/lib.ui-kit-web/ui/utils";
|
|
3844
|
+
import { Button as Button6 } from "@contractspec/lib.design-system";
|
|
3845
|
+
import {
|
|
3846
|
+
Select as Select2,
|
|
3847
|
+
SelectContent as SelectContent2,
|
|
3848
|
+
SelectItem as SelectItem2,
|
|
3849
|
+
SelectTrigger as SelectTrigger2,
|
|
3850
|
+
SelectValue as SelectValue2
|
|
3851
|
+
} from "@contractspec/lib.ui-kit-web/ui/select";
|
|
3852
|
+
import { Badge } from "@contractspec/lib.ui-kit-web/ui/badge";
|
|
3853
|
+
import { Label as Label2 } from "@contractspec/lib.ui-kit-web/ui/label";
|
|
3854
|
+
import { Bot as Bot2, Cloud, Cpu, Sparkles } from "lucide-react";
|
|
3855
|
+
import {
|
|
3856
|
+
getModelsForProvider
|
|
3857
|
+
} from "@contractspec/lib.ai-providers";
|
|
3858
|
+
import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
3859
|
+
"use client";
|
|
3860
|
+
var PROVIDER_ICONS = {
|
|
3861
|
+
ollama: /* @__PURE__ */ jsx11(Cpu, {
|
|
3862
|
+
className: "h-4 w-4"
|
|
3863
|
+
}),
|
|
3864
|
+
openai: /* @__PURE__ */ jsx11(Bot2, {
|
|
3865
|
+
className: "h-4 w-4"
|
|
3866
|
+
}),
|
|
3867
|
+
anthropic: /* @__PURE__ */ jsx11(Sparkles, {
|
|
3868
|
+
className: "h-4 w-4"
|
|
3869
|
+
}),
|
|
3870
|
+
mistral: /* @__PURE__ */ jsx11(Cloud, {
|
|
3871
|
+
className: "h-4 w-4"
|
|
3872
|
+
}),
|
|
3873
|
+
gemini: /* @__PURE__ */ jsx11(Sparkles, {
|
|
3874
|
+
className: "h-4 w-4"
|
|
3875
|
+
})
|
|
3876
|
+
};
|
|
3877
|
+
var PROVIDER_NAMES = {
|
|
3878
|
+
ollama: "Ollama (Local)",
|
|
3879
|
+
openai: "OpenAI",
|
|
3880
|
+
anthropic: "Anthropic",
|
|
3881
|
+
mistral: "Mistral",
|
|
3882
|
+
gemini: "Google Gemini"
|
|
3883
|
+
};
|
|
3884
|
+
var MODE_BADGES = {
|
|
3885
|
+
local: { label: "Local", variant: "secondary" },
|
|
3886
|
+
byok: { label: "BYOK", variant: "outline" },
|
|
3887
|
+
managed: { label: "Managed", variant: "default" }
|
|
3888
|
+
};
|
|
3889
|
+
function ModelPicker({
|
|
3890
|
+
value,
|
|
3891
|
+
onChange,
|
|
3892
|
+
availableProviders,
|
|
3893
|
+
className,
|
|
3894
|
+
compact = false
|
|
3895
|
+
}) {
|
|
3896
|
+
const providers = availableProviders ?? [
|
|
3897
|
+
{ provider: "ollama", available: true, mode: "local" },
|
|
3898
|
+
{ provider: "openai", available: true, mode: "byok" },
|
|
3899
|
+
{ provider: "anthropic", available: true, mode: "byok" },
|
|
3900
|
+
{ provider: "mistral", available: true, mode: "byok" },
|
|
3901
|
+
{ provider: "gemini", available: true, mode: "byok" }
|
|
3902
|
+
];
|
|
3903
|
+
const models = getModelsForProvider(value.provider);
|
|
3904
|
+
const selectedModel = models.find((m) => m.id === value.model);
|
|
3905
|
+
const handleProviderChange = React13.useCallback((providerName) => {
|
|
3906
|
+
const provider = providerName;
|
|
3907
|
+
const providerInfo = providers.find((p) => p.provider === provider);
|
|
3908
|
+
const providerModels = getModelsForProvider(provider);
|
|
3909
|
+
const defaultModel = providerModels[0]?.id ?? "";
|
|
3910
|
+
onChange({
|
|
3911
|
+
provider,
|
|
3912
|
+
model: defaultModel,
|
|
3913
|
+
mode: providerInfo?.mode ?? "byok"
|
|
3914
|
+
});
|
|
3915
|
+
}, [onChange, providers]);
|
|
3916
|
+
const handleModelChange = React13.useCallback((modelId) => {
|
|
3917
|
+
onChange({
|
|
3918
|
+
...value,
|
|
3919
|
+
model: modelId
|
|
3920
|
+
});
|
|
3921
|
+
}, [onChange, value]);
|
|
3922
|
+
if (compact) {
|
|
3923
|
+
return /* @__PURE__ */ jsxs11("div", {
|
|
3924
|
+
className: cn7("flex items-center gap-2", className),
|
|
3925
|
+
children: [
|
|
3926
|
+
/* @__PURE__ */ jsxs11(Select2, {
|
|
3927
|
+
value: value.provider,
|
|
3928
|
+
onValueChange: handleProviderChange,
|
|
3929
|
+
children: [
|
|
3930
|
+
/* @__PURE__ */ jsx11(SelectTrigger2, {
|
|
3931
|
+
className: "w-[140px]",
|
|
3932
|
+
children: /* @__PURE__ */ jsx11(SelectValue2, {})
|
|
3933
|
+
}),
|
|
3934
|
+
/* @__PURE__ */ jsx11(SelectContent2, {
|
|
3935
|
+
children: providers.map((p) => /* @__PURE__ */ jsx11(SelectItem2, {
|
|
3936
|
+
value: p.provider,
|
|
3937
|
+
disabled: !p.available,
|
|
3938
|
+
children: /* @__PURE__ */ jsxs11("div", {
|
|
3939
|
+
className: "flex items-center gap-2",
|
|
3940
|
+
children: [
|
|
3941
|
+
PROVIDER_ICONS[p.provider],
|
|
3942
|
+
/* @__PURE__ */ jsx11("span", {
|
|
3943
|
+
children: PROVIDER_NAMES[p.provider]
|
|
3944
|
+
})
|
|
3945
|
+
]
|
|
3946
|
+
})
|
|
3947
|
+
}, p.provider))
|
|
3948
|
+
})
|
|
3949
|
+
]
|
|
3950
|
+
}),
|
|
3951
|
+
/* @__PURE__ */ jsxs11(Select2, {
|
|
3952
|
+
value: value.model,
|
|
3953
|
+
onValueChange: handleModelChange,
|
|
3954
|
+
children: [
|
|
3955
|
+
/* @__PURE__ */ jsx11(SelectTrigger2, {
|
|
3956
|
+
className: "w-[160px]",
|
|
3957
|
+
children: /* @__PURE__ */ jsx11(SelectValue2, {})
|
|
3958
|
+
}),
|
|
3959
|
+
/* @__PURE__ */ jsx11(SelectContent2, {
|
|
3960
|
+
children: models.map((m) => /* @__PURE__ */ jsx11(SelectItem2, {
|
|
3961
|
+
value: m.id,
|
|
3962
|
+
children: m.name
|
|
3963
|
+
}, m.id))
|
|
3964
|
+
})
|
|
3965
|
+
]
|
|
3966
|
+
})
|
|
3967
|
+
]
|
|
3968
|
+
});
|
|
3969
|
+
}
|
|
3970
|
+
return /* @__PURE__ */ jsxs11("div", {
|
|
3971
|
+
className: cn7("flex flex-col gap-3", className),
|
|
3972
|
+
children: [
|
|
3973
|
+
/* @__PURE__ */ jsxs11("div", {
|
|
3974
|
+
className: "flex flex-col gap-1.5",
|
|
3975
|
+
children: [
|
|
3976
|
+
/* @__PURE__ */ jsx11(Label2, {
|
|
3977
|
+
htmlFor: "provider-selection",
|
|
3978
|
+
className: "text-sm font-medium",
|
|
3979
|
+
children: "Provider"
|
|
3980
|
+
}),
|
|
3981
|
+
/* @__PURE__ */ jsx11("div", {
|
|
3982
|
+
className: "flex flex-wrap gap-2",
|
|
3983
|
+
id: "provider-selection",
|
|
3984
|
+
children: providers.map((p) => /* @__PURE__ */ jsxs11(Button6, {
|
|
3985
|
+
variant: value.provider === p.provider ? "default" : "outline",
|
|
3986
|
+
size: "sm",
|
|
3987
|
+
onPress: () => p.available && handleProviderChange(p.provider),
|
|
3988
|
+
disabled: !p.available,
|
|
3989
|
+
className: cn7(!p.available && "opacity-50"),
|
|
3990
|
+
children: [
|
|
3991
|
+
PROVIDER_ICONS[p.provider],
|
|
3992
|
+
/* @__PURE__ */ jsx11("span", {
|
|
3993
|
+
children: PROVIDER_NAMES[p.provider]
|
|
3994
|
+
}),
|
|
3995
|
+
/* @__PURE__ */ jsx11(Badge, {
|
|
3996
|
+
variant: MODE_BADGES[p.mode].variant,
|
|
3997
|
+
className: "ml-1",
|
|
3998
|
+
children: MODE_BADGES[p.mode].label
|
|
3999
|
+
})
|
|
4000
|
+
]
|
|
4001
|
+
}, p.provider))
|
|
4002
|
+
})
|
|
4003
|
+
]
|
|
4004
|
+
}),
|
|
4005
|
+
/* @__PURE__ */ jsxs11("div", {
|
|
4006
|
+
className: "flex flex-col gap-1.5",
|
|
4007
|
+
children: [
|
|
4008
|
+
/* @__PURE__ */ jsx11(Label2, {
|
|
4009
|
+
htmlFor: "model-picker",
|
|
4010
|
+
className: "text-sm font-medium",
|
|
4011
|
+
children: "Model"
|
|
4012
|
+
}),
|
|
4013
|
+
/* @__PURE__ */ jsxs11(Select2, {
|
|
4014
|
+
name: "model-picker",
|
|
4015
|
+
value: value.model,
|
|
4016
|
+
onValueChange: handleModelChange,
|
|
4017
|
+
children: [
|
|
4018
|
+
/* @__PURE__ */ jsx11(SelectTrigger2, {
|
|
4019
|
+
children: /* @__PURE__ */ jsx11(SelectValue2, {
|
|
4020
|
+
placeholder: "Select a model"
|
|
4021
|
+
})
|
|
4022
|
+
}),
|
|
4023
|
+
/* @__PURE__ */ jsx11(SelectContent2, {
|
|
4024
|
+
children: models.map((m) => /* @__PURE__ */ jsx11(SelectItem2, {
|
|
4025
|
+
value: m.id,
|
|
4026
|
+
children: /* @__PURE__ */ jsxs11("div", {
|
|
4027
|
+
className: "flex items-center gap-2",
|
|
4028
|
+
children: [
|
|
4029
|
+
/* @__PURE__ */ jsx11("span", {
|
|
4030
|
+
children: m.name
|
|
4031
|
+
}),
|
|
4032
|
+
/* @__PURE__ */ jsxs11("span", {
|
|
4033
|
+
className: "text-muted-foreground text-xs",
|
|
4034
|
+
children: [
|
|
4035
|
+
Math.round(m.contextWindow / 1000),
|
|
4036
|
+
"K"
|
|
4037
|
+
]
|
|
4038
|
+
}),
|
|
4039
|
+
m.capabilities.vision && /* @__PURE__ */ jsx11(Badge, {
|
|
4040
|
+
variant: "outline",
|
|
4041
|
+
className: "text-xs",
|
|
4042
|
+
children: "Vision"
|
|
4043
|
+
}),
|
|
4044
|
+
m.capabilities.reasoning && /* @__PURE__ */ jsx11(Badge, {
|
|
4045
|
+
variant: "outline",
|
|
4046
|
+
className: "text-xs",
|
|
4047
|
+
children: "Reasoning"
|
|
4048
|
+
})
|
|
4049
|
+
]
|
|
4050
|
+
})
|
|
4051
|
+
}, m.id))
|
|
4052
|
+
})
|
|
4053
|
+
]
|
|
4054
|
+
})
|
|
4055
|
+
]
|
|
4056
|
+
}),
|
|
4057
|
+
selectedModel && /* @__PURE__ */ jsxs11("div", {
|
|
4058
|
+
className: "text-muted-foreground flex flex-wrap gap-2 text-xs",
|
|
4059
|
+
children: [
|
|
4060
|
+
/* @__PURE__ */ jsxs11("span", {
|
|
4061
|
+
children: [
|
|
4062
|
+
"Context: ",
|
|
4063
|
+
Math.round(selectedModel.contextWindow / 1000),
|
|
4064
|
+
"K tokens"
|
|
4065
|
+
]
|
|
4066
|
+
}),
|
|
4067
|
+
selectedModel.capabilities.vision && /* @__PURE__ */ jsx11("span", {
|
|
4068
|
+
children: "• Vision"
|
|
4069
|
+
}),
|
|
4070
|
+
selectedModel.capabilities.tools && /* @__PURE__ */ jsx11("span", {
|
|
4071
|
+
children: "• Tools"
|
|
4072
|
+
}),
|
|
4073
|
+
selectedModel.capabilities.reasoning && /* @__PURE__ */ jsx11("span", {
|
|
4074
|
+
children: "• Reasoning"
|
|
4075
|
+
})
|
|
4076
|
+
]
|
|
4077
|
+
})
|
|
4078
|
+
]
|
|
4079
|
+
});
|
|
4080
|
+
}
|
|
4081
|
+
// src/presentation/components/ContextIndicator.tsx
|
|
4082
|
+
import { cn as cn8 } from "@contractspec/lib.ui-kit-web/ui/utils";
|
|
4083
|
+
import { Badge as Badge2 } from "@contractspec/lib.ui-kit-web/ui/badge";
|
|
4084
|
+
import {
|
|
4085
|
+
Tooltip,
|
|
4086
|
+
TooltipContent,
|
|
4087
|
+
TooltipProvider,
|
|
4088
|
+
TooltipTrigger
|
|
4089
|
+
} from "@contractspec/lib.ui-kit-web/ui/tooltip";
|
|
4090
|
+
import { FolderOpen, FileCode, Zap, Info } from "lucide-react";
|
|
4091
|
+
import { jsx as jsx12, jsxs as jsxs12, Fragment as Fragment6 } from "react/jsx-runtime";
|
|
4092
|
+
"use client";
|
|
4093
|
+
function ContextIndicator({
|
|
4094
|
+
summary,
|
|
4095
|
+
active = false,
|
|
4096
|
+
className,
|
|
4097
|
+
showDetails = true
|
|
4098
|
+
}) {
|
|
4099
|
+
if (!summary && !active) {
|
|
4100
|
+
return /* @__PURE__ */ jsxs12("div", {
|
|
4101
|
+
className: cn8("flex items-center gap-1.5 text-sm", "text-muted-foreground", className),
|
|
4102
|
+
children: [
|
|
4103
|
+
/* @__PURE__ */ jsx12(Info, {
|
|
4104
|
+
className: "h-4 w-4"
|
|
4105
|
+
}),
|
|
4106
|
+
/* @__PURE__ */ jsx12("span", {
|
|
4107
|
+
children: "No workspace context"
|
|
4108
|
+
})
|
|
4109
|
+
]
|
|
4110
|
+
});
|
|
4111
|
+
}
|
|
4112
|
+
const content = /* @__PURE__ */ jsxs12("div", {
|
|
4113
|
+
className: cn8("flex items-center gap-2", active ? "text-foreground" : "text-muted-foreground", className),
|
|
4114
|
+
children: [
|
|
4115
|
+
/* @__PURE__ */ jsxs12(Badge2, {
|
|
4116
|
+
variant: active ? "default" : "secondary",
|
|
4117
|
+
className: "flex items-center gap-1",
|
|
4118
|
+
children: [
|
|
4119
|
+
/* @__PURE__ */ jsx12(Zap, {
|
|
4120
|
+
className: "h-3 w-3"
|
|
4121
|
+
}),
|
|
4122
|
+
"Context"
|
|
4123
|
+
]
|
|
4124
|
+
}),
|
|
4125
|
+
summary && showDetails && /* @__PURE__ */ jsxs12(Fragment6, {
|
|
4126
|
+
children: [
|
|
4127
|
+
/* @__PURE__ */ jsxs12("div", {
|
|
4128
|
+
className: "flex items-center gap-1 text-xs",
|
|
4129
|
+
children: [
|
|
4130
|
+
/* @__PURE__ */ jsx12(FolderOpen, {
|
|
4131
|
+
className: "h-3.5 w-3.5"
|
|
4132
|
+
}),
|
|
4133
|
+
/* @__PURE__ */ jsx12("span", {
|
|
4134
|
+
children: summary.name
|
|
4135
|
+
})
|
|
4136
|
+
]
|
|
4137
|
+
}),
|
|
4138
|
+
/* @__PURE__ */ jsxs12("div", {
|
|
4139
|
+
className: "flex items-center gap-1 text-xs",
|
|
4140
|
+
children: [
|
|
4141
|
+
/* @__PURE__ */ jsx12(FileCode, {
|
|
4142
|
+
className: "h-3.5 w-3.5"
|
|
4143
|
+
}),
|
|
4144
|
+
/* @__PURE__ */ jsxs12("span", {
|
|
4145
|
+
children: [
|
|
4146
|
+
summary.specs.total,
|
|
4147
|
+
" specs"
|
|
4148
|
+
]
|
|
4149
|
+
})
|
|
4150
|
+
]
|
|
4151
|
+
})
|
|
4152
|
+
]
|
|
4153
|
+
})
|
|
4154
|
+
]
|
|
4155
|
+
});
|
|
4156
|
+
if (!summary) {
|
|
4157
|
+
return content;
|
|
4158
|
+
}
|
|
4159
|
+
return /* @__PURE__ */ jsx12(TooltipProvider, {
|
|
4160
|
+
children: /* @__PURE__ */ jsxs12(Tooltip, {
|
|
4161
|
+
children: [
|
|
4162
|
+
/* @__PURE__ */ jsx12(TooltipTrigger, {
|
|
4163
|
+
asChild: true,
|
|
4164
|
+
children: content
|
|
4165
|
+
}),
|
|
4166
|
+
/* @__PURE__ */ jsx12(TooltipContent, {
|
|
4167
|
+
side: "bottom",
|
|
4168
|
+
className: "max-w-[300px]",
|
|
4169
|
+
children: /* @__PURE__ */ jsxs12("div", {
|
|
4170
|
+
className: "flex flex-col gap-2 text-sm",
|
|
4171
|
+
children: [
|
|
4172
|
+
/* @__PURE__ */ jsx12("div", {
|
|
4173
|
+
className: "font-medium",
|
|
4174
|
+
children: summary.name
|
|
4175
|
+
}),
|
|
4176
|
+
/* @__PURE__ */ jsx12("div", {
|
|
4177
|
+
className: "text-muted-foreground text-xs",
|
|
4178
|
+
children: summary.path
|
|
4179
|
+
}),
|
|
4180
|
+
/* @__PURE__ */ jsx12("div", {
|
|
4181
|
+
className: "border-t pt-2",
|
|
4182
|
+
children: /* @__PURE__ */ jsxs12("div", {
|
|
4183
|
+
className: "grid grid-cols-2 gap-1 text-xs",
|
|
4184
|
+
children: [
|
|
4185
|
+
/* @__PURE__ */ jsx12("span", {
|
|
4186
|
+
children: "Commands:"
|
|
4187
|
+
}),
|
|
4188
|
+
/* @__PURE__ */ jsx12("span", {
|
|
4189
|
+
className: "text-right",
|
|
4190
|
+
children: summary.specs.commands
|
|
4191
|
+
}),
|
|
4192
|
+
/* @__PURE__ */ jsx12("span", {
|
|
4193
|
+
children: "Queries:"
|
|
4194
|
+
}),
|
|
4195
|
+
/* @__PURE__ */ jsx12("span", {
|
|
4196
|
+
className: "text-right",
|
|
4197
|
+
children: summary.specs.queries
|
|
4198
|
+
}),
|
|
4199
|
+
/* @__PURE__ */ jsx12("span", {
|
|
4200
|
+
children: "Events:"
|
|
4201
|
+
}),
|
|
4202
|
+
/* @__PURE__ */ jsx12("span", {
|
|
4203
|
+
className: "text-right",
|
|
4204
|
+
children: summary.specs.events
|
|
4205
|
+
}),
|
|
4206
|
+
/* @__PURE__ */ jsx12("span", {
|
|
4207
|
+
children: "Presentations:"
|
|
4208
|
+
}),
|
|
4209
|
+
/* @__PURE__ */ jsx12("span", {
|
|
4210
|
+
className: "text-right",
|
|
4211
|
+
children: summary.specs.presentations
|
|
4212
|
+
})
|
|
4213
|
+
]
|
|
4214
|
+
})
|
|
4215
|
+
}),
|
|
4216
|
+
/* @__PURE__ */ jsxs12("div", {
|
|
4217
|
+
className: "border-t pt-2 text-xs",
|
|
4218
|
+
children: [
|
|
4219
|
+
/* @__PURE__ */ jsxs12("span", {
|
|
4220
|
+
children: [
|
|
4221
|
+
summary.files.total,
|
|
4222
|
+
" files"
|
|
4223
|
+
]
|
|
4224
|
+
}),
|
|
4225
|
+
/* @__PURE__ */ jsx12("span", {
|
|
4226
|
+
className: "mx-1",
|
|
4227
|
+
children: "•"
|
|
4228
|
+
}),
|
|
4229
|
+
/* @__PURE__ */ jsxs12("span", {
|
|
4230
|
+
children: [
|
|
4231
|
+
summary.files.specFiles,
|
|
4232
|
+
" spec files"
|
|
4233
|
+
]
|
|
4234
|
+
})
|
|
4235
|
+
]
|
|
4236
|
+
})
|
|
4237
|
+
]
|
|
4238
|
+
})
|
|
4239
|
+
})
|
|
4240
|
+
]
|
|
4241
|
+
})
|
|
4242
|
+
});
|
|
4243
|
+
}
|
|
2136
4244
|
// src/presentation/hooks/useProviders.tsx
|
|
2137
|
-
import * as
|
|
4245
|
+
import * as React14 from "react";
|
|
2138
4246
|
import {
|
|
2139
4247
|
getAvailableProviders,
|
|
2140
4248
|
getModelsForProvider as getModelsForProvider2
|
|
2141
4249
|
} from "@contractspec/lib.ai-providers";
|
|
2142
4250
|
"use client";
|
|
2143
4251
|
function useProviders() {
|
|
2144
|
-
const [providers, setProviders] =
|
|
2145
|
-
const [isLoading, setIsLoading] =
|
|
2146
|
-
const loadProviders =
|
|
4252
|
+
const [providers, setProviders] = React14.useState([]);
|
|
4253
|
+
const [isLoading, setIsLoading] = React14.useState(true);
|
|
4254
|
+
const loadProviders = React14.useCallback(async () => {
|
|
2147
4255
|
setIsLoading(true);
|
|
2148
4256
|
try {
|
|
2149
4257
|
const available = getAvailableProviders();
|
|
@@ -2158,12 +4266,12 @@ function useProviders() {
|
|
|
2158
4266
|
setIsLoading(false);
|
|
2159
4267
|
}
|
|
2160
4268
|
}, []);
|
|
2161
|
-
|
|
4269
|
+
React14.useEffect(() => {
|
|
2162
4270
|
loadProviders();
|
|
2163
4271
|
}, [loadProviders]);
|
|
2164
|
-
const availableProviders =
|
|
2165
|
-
const isAvailable =
|
|
2166
|
-
const getModelsCallback =
|
|
4272
|
+
const availableProviders = React14.useMemo(() => providers.filter((p) => p.available), [providers]);
|
|
4273
|
+
const isAvailable = React14.useCallback((provider) => providers.some((p) => p.provider === provider && p.available), [providers]);
|
|
4274
|
+
const getModelsCallback = React14.useCallback((provider) => providers.find((p) => p.provider === provider)?.models ?? [], [providers]);
|
|
2167
4275
|
return {
|
|
2168
4276
|
providers,
|
|
2169
4277
|
availableProviders,
|
|
@@ -2507,15 +4615,64 @@ var ChatErrorEvent = defineEvent({
|
|
|
2507
4615
|
}
|
|
2508
4616
|
})
|
|
2509
4617
|
});
|
|
4618
|
+
// src/adapters/ai-sdk-bundle-adapter.ts
|
|
4619
|
+
import { compilePlannerPrompt as compilePlannerPrompt2 } from "@contractspec/lib.surface-runtime/runtime/planner-prompt";
|
|
4620
|
+
function createAiSdkBundleAdapter(deps) {
|
|
4621
|
+
const { provider, onPatchProposal } = deps;
|
|
4622
|
+
return {
|
|
4623
|
+
startThread(_args) {
|
|
4624
|
+
return null;
|
|
4625
|
+
},
|
|
4626
|
+
async requestPatches(args) {
|
|
4627
|
+
const proposals = [];
|
|
4628
|
+
const captureProposal = (p) => {
|
|
4629
|
+
proposals.push(p);
|
|
4630
|
+
onPatchProposal?.(p);
|
|
4631
|
+
};
|
|
4632
|
+
const plannerInput = buildPlannerPromptInput(args.currentPlan);
|
|
4633
|
+
const systemPrompt = compilePlannerPrompt2(plannerInput);
|
|
4634
|
+
const service = new ChatService({
|
|
4635
|
+
provider,
|
|
4636
|
+
systemPrompt,
|
|
4637
|
+
surfacePlanConfig: {
|
|
4638
|
+
plan: args.currentPlan,
|
|
4639
|
+
onPatchProposal: captureProposal
|
|
4640
|
+
}
|
|
4641
|
+
});
|
|
4642
|
+
await service.send({
|
|
4643
|
+
content: args.userMessage
|
|
4644
|
+
});
|
|
4645
|
+
return proposals;
|
|
4646
|
+
}
|
|
4647
|
+
};
|
|
4648
|
+
}
|
|
4649
|
+
// src/core/agent-adapter.ts
|
|
4650
|
+
function createChatAgentAdapter(agent) {
|
|
4651
|
+
return {
|
|
4652
|
+
async generate({ prompt, signal }) {
|
|
4653
|
+
const result = await agent.generate({ prompt, signal });
|
|
4654
|
+
return {
|
|
4655
|
+
text: result.text,
|
|
4656
|
+
toolCalls: result.toolCalls,
|
|
4657
|
+
toolResults: result.toolResults,
|
|
4658
|
+
usage: result.usage
|
|
4659
|
+
};
|
|
4660
|
+
}
|
|
4661
|
+
};
|
|
4662
|
+
}
|
|
2510
4663
|
export {
|
|
2511
4664
|
validateProvider,
|
|
2512
4665
|
useProviders,
|
|
4666
|
+
useMessageSelection,
|
|
4667
|
+
useConversations,
|
|
2513
4668
|
useCompletion,
|
|
2514
4669
|
useChat,
|
|
2515
4670
|
supportsLocalMode,
|
|
2516
4671
|
listOllamaModels,
|
|
2517
4672
|
isStudioAvailable,
|
|
4673
|
+
isPresentationToolResult,
|
|
2518
4674
|
isOllamaRunning,
|
|
4675
|
+
isFormToolResult,
|
|
2519
4676
|
hasCredentials,
|
|
2520
4677
|
getRecommendedModels,
|
|
2521
4678
|
getModelsForProvider3 as getModelsForProvider,
|
|
@@ -2528,7 +4685,11 @@ export {
|
|
|
2528
4685
|
createProvider2 as createProvider,
|
|
2529
4686
|
createNodeFileOperations,
|
|
2530
4687
|
createContextBuilder,
|
|
4688
|
+
createChatAgentAdapter,
|
|
4689
|
+
createAiSdkBundleAdapter,
|
|
2531
4690
|
WorkspaceContext,
|
|
4691
|
+
ToolResultRenderer,
|
|
4692
|
+
ThinkingLevelPicker,
|
|
2532
4693
|
StreamMessageContract,
|
|
2533
4694
|
SendMessageOutputModel,
|
|
2534
4695
|
SendMessageInputModel,
|
|
@@ -2550,10 +4711,14 @@ export {
|
|
|
2550
4711
|
ContextIndicator,
|
|
2551
4712
|
ContextBuilder,
|
|
2552
4713
|
CodePreview,
|
|
4714
|
+
ChatWithSidebar,
|
|
4715
|
+
ChatWithExport,
|
|
4716
|
+
ChatSidebar,
|
|
2553
4717
|
ChatMessageModel,
|
|
2554
4718
|
ChatMessage as ChatMessageComponent,
|
|
2555
4719
|
ChatMessage,
|
|
2556
4720
|
ChatInput,
|
|
4721
|
+
ChatExportToolbar,
|
|
2557
4722
|
ChatErrorEvent,
|
|
2558
4723
|
ChatConversationModel,
|
|
2559
4724
|
ChatContainer,
|