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