@contractspec/module.ai-chat 4.3.17 → 4.3.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser/context/index.js +3 -415
- package/dist/browser/core/index.js +55 -1600
- package/dist/browser/index.js +58 -5251
- package/dist/browser/presentation/components/index.js +56 -4382
- package/dist/browser/presentation/hooks/index.js +32 -1638
- package/dist/browser/presentation/index.js +56 -4430
- package/dist/browser/providers/index.js +1 -51
- package/dist/context/index.js +3 -409
- package/dist/core/index.js +55 -1594
- package/dist/index.js +58 -5245
- package/dist/node/context/index.js +3 -410
- package/dist/node/core/index.js +55 -1595
- package/dist/node/index.js +58 -5246
- package/dist/node/presentation/components/index.js +56 -4377
- package/dist/node/presentation/hooks/index.js +32 -1633
- package/dist/node/presentation/index.js +56 -4425
- package/dist/node/providers/index.js +1 -46
- package/dist/presentation/components/index.js +56 -4376
- package/dist/presentation/hooks/index.js +32 -1632
- package/dist/presentation/index.js +56 -4424
- package/dist/providers/index.js +1 -45
- package/package.json +15 -15
|
@@ -1,3060 +1,91 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
var
|
|
3
|
-
|
|
4
|
-
// src/presentation/components/ChainOfThought.tsx
|
|
5
|
-
import {
|
|
6
|
-
Collapsible,
|
|
7
|
-
CollapsibleContent,
|
|
8
|
-
CollapsibleTrigger
|
|
9
|
-
} from "@contractspec/lib.ui-kit-web/ui/collapsible";
|
|
10
|
-
import { cn } from "@contractspec/lib.ui-kit-web/ui/utils";
|
|
11
|
-
import { ChevronDown, Dot } from "lucide-react";
|
|
12
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
13
|
-
"use client";
|
|
14
|
-
function ChainOfThought({
|
|
15
|
-
children,
|
|
16
|
-
open,
|
|
17
|
-
defaultOpen = false,
|
|
18
|
-
onOpenChange,
|
|
19
|
-
className
|
|
20
|
-
}) {
|
|
21
|
-
return /* @__PURE__ */ jsx(Collapsible, {
|
|
22
|
-
open,
|
|
23
|
-
defaultOpen,
|
|
24
|
-
onOpenChange,
|
|
25
|
-
className: cn("group/cot mt-2", className),
|
|
26
|
-
children
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
function ChainOfThoughtHeader({
|
|
30
|
-
children = "Chain of Thought",
|
|
31
|
-
className
|
|
32
|
-
}) {
|
|
33
|
-
return /* @__PURE__ */ jsxs(CollapsibleTrigger, {
|
|
34
|
-
className: cn("flex w-full cursor-pointer items-center gap-2 rounded-md px-3 py-2 font-medium text-sm transition-colors hover:bg-muted", className),
|
|
35
|
-
children: [
|
|
36
|
-
/* @__PURE__ */ jsx(ChevronDown, {
|
|
37
|
-
className: "h-4 w-4 shrink-0 text-muted-foreground transition-transform group-data-[state=open]/cot:rotate-180"
|
|
38
|
-
}),
|
|
39
|
-
children
|
|
40
|
-
]
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
function ChainOfThoughtStep({
|
|
44
|
-
label,
|
|
45
|
-
description,
|
|
46
|
-
status = "complete",
|
|
47
|
-
icon: Icon = Dot,
|
|
48
|
-
children,
|
|
49
|
-
className
|
|
50
|
-
}) {
|
|
51
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
52
|
-
className: cn("flex gap-3 border-border border-b py-2 last:border-b-0", className),
|
|
53
|
-
children: [
|
|
54
|
-
/* @__PURE__ */ jsx("div", {
|
|
55
|
-
className: cn("mt-1.5 flex h-5 w-5 shrink-0 items-center justify-center rounded-full", status === "complete" && "bg-green-500/20 text-green-700 dark:text-green-400", status === "active" && "bg-blue-500/20 text-blue-700 dark:text-blue-400", status === "pending" && "bg-muted text-muted-foreground"),
|
|
56
|
-
children: /* @__PURE__ */ jsx(Icon, {
|
|
57
|
-
className: "h-3 w-3"
|
|
58
|
-
})
|
|
59
|
-
}),
|
|
60
|
-
/* @__PURE__ */ jsxs("div", {
|
|
61
|
-
className: "min-w-0 flex-1",
|
|
62
|
-
children: [
|
|
63
|
-
/* @__PURE__ */ jsx("p", {
|
|
64
|
-
className: "font-medium",
|
|
65
|
-
children: label
|
|
66
|
-
}),
|
|
67
|
-
description && /* @__PURE__ */ jsx("p", {
|
|
68
|
-
className: "mt-0.5 text-muted-foreground text-xs",
|
|
69
|
-
children: description
|
|
70
|
-
}),
|
|
71
|
-
children && /* @__PURE__ */ jsx("div", {
|
|
72
|
-
className: "mt-2",
|
|
73
|
-
children
|
|
74
|
-
})
|
|
75
|
-
]
|
|
76
|
-
})
|
|
77
|
-
]
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
function ChainOfThoughtContent({
|
|
81
|
-
children,
|
|
82
|
-
className
|
|
83
|
-
}) {
|
|
84
|
-
return /* @__PURE__ */ jsx(CollapsibleContent, {
|
|
85
|
-
children: /* @__PURE__ */ jsx("div", {
|
|
86
|
-
className: cn("mt-1 rounded-md border border-border bg-muted px-3 py-2", className),
|
|
87
|
-
children
|
|
88
|
-
})
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
// src/presentation/components/ChatContainer.tsx
|
|
92
|
-
import { ScrollArea } from "@contractspec/lib.ui-kit-web/ui/scroll-area";
|
|
93
|
-
import { cn as cn2 } from "@contractspec/lib.ui-kit-web/ui/utils";
|
|
94
|
-
import * as React from "react";
|
|
95
|
-
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
96
|
-
"use client";
|
|
97
|
-
function ChatContainer({
|
|
98
|
-
children,
|
|
99
|
-
className,
|
|
100
|
-
showScrollButton = true,
|
|
101
|
-
headerContent
|
|
102
|
-
}) {
|
|
103
|
-
const scrollRef = React.useRef(null);
|
|
104
|
-
const [showScrollDown, setShowScrollDown] = React.useState(false);
|
|
105
|
-
React.useEffect(() => {
|
|
106
|
-
const container = scrollRef.current;
|
|
107
|
-
if (!container)
|
|
108
|
-
return;
|
|
109
|
-
const isAtBottom = container.scrollHeight - container.scrollTop <= container.clientHeight + 100;
|
|
110
|
-
if (isAtBottom) {
|
|
111
|
-
container.scrollTop = container.scrollHeight;
|
|
112
|
-
}
|
|
113
|
-
}, [children]);
|
|
114
|
-
const handleScroll = React.useCallback((event) => {
|
|
115
|
-
const container = event.currentTarget;
|
|
116
|
-
const isAtBottom = container.scrollHeight - container.scrollTop <= container.clientHeight + 100;
|
|
117
|
-
setShowScrollDown(!isAtBottom);
|
|
118
|
-
}, []);
|
|
119
|
-
const scrollToBottom = React.useCallback(() => {
|
|
120
|
-
const container = scrollRef.current;
|
|
121
|
-
if (container) {
|
|
122
|
-
container.scrollTo({
|
|
123
|
-
top: container.scrollHeight,
|
|
124
|
-
behavior: "smooth"
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
}, []);
|
|
128
|
-
return /* @__PURE__ */ jsxs2("div", {
|
|
129
|
-
className: cn2("relative flex flex-1 flex-col", className),
|
|
130
|
-
children: [
|
|
131
|
-
headerContent && /* @__PURE__ */ jsx2("div", {
|
|
132
|
-
className: "flex shrink-0 items-center justify-end gap-2 border-border border-b px-4 py-2",
|
|
133
|
-
children: headerContent
|
|
134
|
-
}),
|
|
135
|
-
/* @__PURE__ */ jsx2(ScrollArea, {
|
|
136
|
-
ref: scrollRef,
|
|
137
|
-
className: "flex-1",
|
|
138
|
-
onScroll: handleScroll,
|
|
139
|
-
children: /* @__PURE__ */ jsx2("div", {
|
|
140
|
-
className: "flex flex-col gap-4 p-4",
|
|
141
|
-
children
|
|
142
|
-
})
|
|
143
|
-
}),
|
|
144
|
-
showScrollButton && showScrollDown && /* @__PURE__ */ jsxs2("button", {
|
|
145
|
-
onClick: scrollToBottom,
|
|
146
|
-
className: cn2("absolute bottom-4 left-1/2 -translate-x-1/2", "bg-primary text-primary-foreground", "rounded-full px-3 py-1.5 font-medium text-sm shadow-lg", "transition-colors hover:bg-primary/90", "flex items-center gap-1.5"),
|
|
147
|
-
"aria-label": "Scroll to bottom",
|
|
148
|
-
children: [
|
|
149
|
-
/* @__PURE__ */ jsx2("svg", {
|
|
150
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
151
|
-
width: "16",
|
|
152
|
-
height: "16",
|
|
153
|
-
viewBox: "0 0 24 24",
|
|
154
|
-
fill: "none",
|
|
155
|
-
stroke: "currentColor",
|
|
156
|
-
strokeWidth: "2",
|
|
157
|
-
strokeLinecap: "round",
|
|
158
|
-
strokeLinejoin: "round",
|
|
159
|
-
children: /* @__PURE__ */ jsx2("path", {
|
|
160
|
-
d: "m6 9 6 6 6-6"
|
|
161
|
-
})
|
|
162
|
-
}),
|
|
163
|
-
"New messages"
|
|
164
|
-
]
|
|
165
|
-
})
|
|
166
|
-
]
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
// src/presentation/components/ChatExportToolbar.tsx
|
|
170
|
-
import { Button } from "@contractspec/lib.design-system";
|
|
171
|
-
import {
|
|
172
|
-
DropdownMenu,
|
|
173
|
-
DropdownMenuContent,
|
|
174
|
-
DropdownMenuItem,
|
|
175
|
-
DropdownMenuSeparator,
|
|
176
|
-
DropdownMenuTrigger
|
|
177
|
-
} from "@contractspec/lib.ui-kit-web/ui/dropdown-menu";
|
|
178
|
-
import { Check, Copy, Download, FileText, GitFork, Plus } from "lucide-react";
|
|
179
|
-
import * as React2 from "react";
|
|
180
|
-
|
|
181
|
-
// src/core/export-formatters.ts
|
|
182
|
-
function formatTimestamp(date) {
|
|
183
|
-
return date.toLocaleTimeString([], {
|
|
184
|
-
hour: "2-digit",
|
|
185
|
-
minute: "2-digit"
|
|
186
|
-
});
|
|
187
|
-
}
|
|
188
|
-
function toIsoString(date) {
|
|
189
|
-
return date.toISOString();
|
|
190
|
-
}
|
|
191
|
-
function messageToJsonSerializable(msg) {
|
|
192
|
-
return {
|
|
193
|
-
id: msg.id,
|
|
194
|
-
conversationId: msg.conversationId,
|
|
195
|
-
role: msg.role,
|
|
196
|
-
content: msg.content,
|
|
197
|
-
status: msg.status,
|
|
198
|
-
createdAt: toIsoString(msg.createdAt),
|
|
199
|
-
updatedAt: toIsoString(msg.updatedAt),
|
|
200
|
-
...msg.attachments && { attachments: msg.attachments },
|
|
201
|
-
...msg.codeBlocks && { codeBlocks: msg.codeBlocks },
|
|
202
|
-
...msg.toolCalls && { toolCalls: msg.toolCalls },
|
|
203
|
-
...msg.sources && { sources: msg.sources },
|
|
204
|
-
...msg.reasoning && { reasoning: msg.reasoning },
|
|
205
|
-
...msg.usage && { usage: msg.usage },
|
|
206
|
-
...msg.error && { error: msg.error },
|
|
207
|
-
...msg.metadata && { metadata: msg.metadata }
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
function formatSourcesMarkdown(sources) {
|
|
211
|
-
if (sources.length === 0)
|
|
212
|
-
return "";
|
|
213
|
-
return `
|
|
2
|
+
var j2=import.meta.require;import{Collapsible as x2,CollapsibleContent as v2,CollapsibleTrigger as u2}from"@contractspec/lib.ui-kit-web/ui/collapsible";import{cn as T0}from"@contractspec/lib.ui-kit-web/ui/utils";import{ChevronDown as p2,Dot as g2}from"lucide-react";import{jsx as G0,jsxs as r0}from"react/jsx-runtime";function m2({children:$,open:Z,defaultOpen:Y=!1,onOpenChange:J,className:Q}){return G0(x2,{open:Z,defaultOpen:Y,onOpenChange:J,className:T0("group/cot mt-2",Q),children:$})}function d2({children:$="Chain of Thought",className:Z}){return r0(u2,{className:T0("flex w-full cursor-pointer items-center gap-2 rounded-md px-3 py-2 font-medium text-sm transition-colors hover:bg-muted",Z),children:[G0(p2,{className:"h-4 w-4 shrink-0 text-muted-foreground transition-transform group-data-[state=open]/cot:rotate-180"}),$]})}function l2({label:$,description:Z,status:Y="complete",icon:J=g2,children:Q,className:X}){return r0("div",{className:T0("flex gap-3 border-border border-b py-2 last:border-b-0",X),children:[G0("div",{className:T0("mt-1.5 flex h-5 w-5 shrink-0 items-center justify-center rounded-full",Y==="complete"&&"bg-green-500/20 text-green-700 dark:text-green-400",Y==="active"&&"bg-blue-500/20 text-blue-700 dark:text-blue-400",Y==="pending"&&"bg-muted text-muted-foreground"),children:G0(J,{className:"h-3 w-3"})}),r0("div",{className:"min-w-0 flex-1",children:[G0("p",{className:"font-medium",children:$}),Z&&G0("p",{className:"mt-0.5 text-muted-foreground text-xs",children:Z}),Q&&G0("div",{className:"mt-2",children:Q})]})]})}function c2({children:$,className:Z}){return G0(v2,{children:G0("div",{className:T0("mt-1 rounded-md border border-border bg-muted px-3 py-2",Z),children:$})})}import{ScrollArea as i2}from"@contractspec/lib.ui-kit-web/ui/scroll-area";import{cn as w1}from"@contractspec/lib.ui-kit-web/ui/utils";import*as W0 from"react";import{jsx as b0,jsxs as S1}from"react/jsx-runtime";function a0({children:$,className:Z,showScrollButton:Y=!0,headerContent:J}){let Q=W0.useRef(null),[X,q]=W0.useState(!1);W0.useEffect(()=>{let W=Q.current;if(!W)return;if(W.scrollHeight-W.scrollTop<=W.clientHeight+100)W.scrollTop=W.scrollHeight},[$]);let H=W0.useCallback((W)=>{let G=W.currentTarget,V=G.scrollHeight-G.scrollTop<=G.clientHeight+100;q(!V)},[]),K=W0.useCallback(()=>{let W=Q.current;if(W)W.scrollTo({top:W.scrollHeight,behavior:"smooth"})},[]);return S1("div",{className:w1("relative flex flex-1 flex-col",Z),children:[J&&b0("div",{className:"flex shrink-0 items-center justify-end gap-2 border-border border-b px-4 py-2",children:J}),b0(i2,{ref:Q,className:"flex-1",onScroll:H,children:b0("div",{className:"flex flex-col gap-4 p-4",children:$})}),Y&&X&&S1("button",{onClick:K,className:w1("absolute bottom-4 left-1/2 -translate-x-1/2","bg-primary text-primary-foreground","rounded-full px-3 py-1.5 font-medium text-sm shadow-lg","transition-colors hover:bg-primary/90","flex items-center gap-1.5"),"aria-label":"Scroll to bottom",children:[b0("svg",{xmlns:"http://www.w3.org/2000/svg",width:"16",height:"16",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:b0("path",{d:"m6 9 6 6 6-6"})}),"New messages"]})]})}import{Button as k0}from"@contractspec/lib.design-system";import{DropdownMenu as J4,DropdownMenuContent as Q4,DropdownMenuItem as f0,DropdownMenuSeparator as X4,DropdownMenuTrigger as q4}from"@contractspec/lib.ui-kit-web/ui/dropdown-menu";import{Check as H4,Copy as G4,Download as W4,FileText as o0,GitFork as K4,Plus as _4}from"lucide-react";import*as K0 from"react";function I1($){return $.toLocaleTimeString([],{hour:"2-digit",minute:"2-digit"})}function C0($){return $.toISOString()}function r2($){return{id:$.id,conversationId:$.conversationId,role:$.role,content:$.content,status:$.status,createdAt:C0($.createdAt),updatedAt:C0($.updatedAt),...$.attachments&&{attachments:$.attachments},...$.codeBlocks&&{codeBlocks:$.codeBlocks},...$.toolCalls&&{toolCalls:$.toolCalls},...$.sources&&{sources:$.sources},...$.reasoning&&{reasoning:$.reasoning},...$.usage&&{usage:$.usage},...$.error&&{error:$.error},...$.metadata&&{metadata:$.metadata}}}function a2($){if($.length===0)return"";return`
|
|
214
3
|
|
|
215
4
|
**Sources:**
|
|
216
|
-
|
|
217
|
-
`);
|
|
218
|
-
}
|
|
219
|
-
function formatSourcesTxt(sources) {
|
|
220
|
-
if (sources.length === 0)
|
|
221
|
-
return "";
|
|
222
|
-
return `
|
|
5
|
+
`+$.map((Z)=>`- [${Z.title}](${Z.url??"#"})`).join(`
|
|
6
|
+
`)}function n2($){if($.length===0)return"";return`
|
|
223
7
|
|
|
224
8
|
Sources:
|
|
225
|
-
|
|
226
|
-
`);
|
|
227
|
-
}
|
|
228
|
-
function formatToolCallsMarkdown(toolCalls) {
|
|
229
|
-
if (toolCalls.length === 0)
|
|
230
|
-
return "";
|
|
231
|
-
return `
|
|
9
|
+
`+$.map((Z)=>`- ${Z.title}${Z.url?` - ${Z.url}`:""}`).join(`
|
|
10
|
+
`)}function o2($){if($.length===0)return"";return`
|
|
232
11
|
|
|
233
12
|
**Tool calls:**
|
|
234
|
-
|
|
13
|
+
`+$.map((Z)=>`**${Z.name}** (${Z.status})
|
|
235
14
|
\`\`\`json
|
|
236
|
-
${JSON.stringify(
|
|
237
|
-
|
|
15
|
+
${JSON.stringify(Z.args,null,2)}
|
|
16
|
+
\`\`\``+(Z.result!==void 0?`
|
|
238
17
|
Output:
|
|
239
18
|
\`\`\`json
|
|
240
|
-
${typeof
|
|
241
|
-
|
|
242
|
-
Error: ${
|
|
19
|
+
${typeof Z.result==="object"?JSON.stringify(Z.result,null,2):String(Z.result)}
|
|
20
|
+
\`\`\``:"")+(Z.error?`
|
|
21
|
+
Error: ${Z.error}`:"")).join(`
|
|
243
22
|
|
|
244
|
-
`);
|
|
245
|
-
}
|
|
246
|
-
function formatToolCallsTxt(toolCalls) {
|
|
247
|
-
if (toolCalls.length === 0)
|
|
248
|
-
return "";
|
|
249
|
-
return `
|
|
23
|
+
`)}function t2($){if($.length===0)return"";return`
|
|
250
24
|
|
|
251
25
|
Tool calls:
|
|
252
|
-
|
|
253
|
-
`);
|
|
254
|
-
}
|
|
255
|
-
function formatUsage(usage) {
|
|
256
|
-
const total = usage.inputTokens + usage.outputTokens;
|
|
257
|
-
return ` (${total} tokens)`;
|
|
258
|
-
}
|
|
259
|
-
function formatMessagesAsMarkdown(messages) {
|
|
260
|
-
const parts = [];
|
|
261
|
-
for (const msg of messages) {
|
|
262
|
-
const roleLabel = msg.role === "user" ? "User" : msg.role === "assistant" ? "Assistant" : "System";
|
|
263
|
-
const header = `## ${roleLabel}`;
|
|
264
|
-
const timestamp = `*${formatTimestamp(msg.createdAt)}*`;
|
|
265
|
-
const usageSuffix = msg.usage ? formatUsage(msg.usage) : "";
|
|
266
|
-
const meta = `${timestamp}${usageSuffix}
|
|
26
|
+
`+$.map((Z)=>`- ${Z.name} (${Z.status}): ${JSON.stringify(Z.args)}`+(Z.result!==void 0?` -> ${typeof Z.result==="object"?JSON.stringify(Z.result):String(Z.result)}`:"")+(Z.error?` [Error: ${Z.error}]`:"")).join(`
|
|
27
|
+
`)}function C1($){return` (${$.inputTokens+$.outputTokens} tokens)`}function n0($){let Z=[];for(let Y of $){let Q=`## ${Y.role==="user"?"User":Y.role==="assistant"?"Assistant":"System"}`,X=`*${I1(Y.createdAt)}*`,q=Y.usage?C1(Y.usage):"",H=`${X}${q}
|
|
267
28
|
|
|
268
|
-
|
|
269
|
-
let body = msg.content;
|
|
270
|
-
if (msg.error) {
|
|
271
|
-
body += `
|
|
29
|
+
`,K=Y.content;if(Y.error)K+=`
|
|
272
30
|
|
|
273
|
-
**Error:** ${
|
|
274
|
-
}
|
|
275
|
-
if (msg.reasoning) {
|
|
276
|
-
body += `
|
|
31
|
+
**Error:** ${Y.error.code} - ${Y.error.message}`;if(Y.reasoning)K+=`
|
|
277
32
|
|
|
278
33
|
> **Reasoning:**
|
|
279
|
-
> ${
|
|
280
|
-
> `)}`;
|
|
281
|
-
}
|
|
282
|
-
body += formatSourcesMarkdown(msg.sources ?? []);
|
|
283
|
-
body += formatToolCallsMarkdown(msg.toolCalls ?? []);
|
|
284
|
-
parts.push(`${header}
|
|
34
|
+
> ${Y.reasoning.replace(/\n/g,`
|
|
35
|
+
> `)}`;K+=a2(Y.sources??[]),K+=o2(Y.toolCalls??[]),Z.push(`${Q}
|
|
285
36
|
|
|
286
|
-
${
|
|
287
|
-
}
|
|
288
|
-
return parts.join(`
|
|
37
|
+
${H}${K}`)}return Z.join(`
|
|
289
38
|
|
|
290
39
|
---
|
|
291
40
|
|
|
292
|
-
`);
|
|
293
|
-
}
|
|
294
|
-
function formatMessagesAsTxt(messages) {
|
|
295
|
-
const parts = [];
|
|
296
|
-
for (const msg of messages) {
|
|
297
|
-
const roleLabel = msg.role === "user" ? "User" : msg.role === "assistant" ? "Assistant" : "System";
|
|
298
|
-
const timestamp = `(${formatTimestamp(msg.createdAt)})`;
|
|
299
|
-
const usageSuffix = msg.usage ? formatUsage(msg.usage) : "";
|
|
300
|
-
const header = `[${roleLabel}] ${timestamp}${usageSuffix}
|
|
41
|
+
`)}function s2($){let Z=[];for(let Y of $){let J=Y.role==="user"?"User":Y.role==="assistant"?"Assistant":"System",Q=`(${I1(Y.createdAt)})`,X=Y.usage?C1(Y.usage):"",q=`[${J}] ${Q}${X}
|
|
301
42
|
|
|
302
|
-
|
|
303
|
-
let body = msg.content;
|
|
304
|
-
if (msg.error) {
|
|
305
|
-
body += `
|
|
43
|
+
`,H=Y.content;if(Y.error)H+=`
|
|
306
44
|
|
|
307
|
-
Error: ${
|
|
308
|
-
}
|
|
309
|
-
if (msg.reasoning) {
|
|
310
|
-
body += `
|
|
45
|
+
Error: ${Y.error.code} - ${Y.error.message}`;if(Y.reasoning)H+=`
|
|
311
46
|
|
|
312
|
-
Reasoning: ${
|
|
313
|
-
}
|
|
314
|
-
body += formatSourcesTxt(msg.sources ?? []);
|
|
315
|
-
body += formatToolCallsTxt(msg.toolCalls ?? []);
|
|
316
|
-
parts.push(`${header}${body}`);
|
|
317
|
-
}
|
|
318
|
-
return parts.join(`
|
|
47
|
+
Reasoning: ${Y.reasoning}`;H+=n2(Y.sources??[]),H+=t2(Y.toolCalls??[]),Z.push(`${q}${H}`)}return Z.join(`
|
|
319
48
|
|
|
320
49
|
---
|
|
321
50
|
|
|
322
|
-
`);
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
workspacePath: conversation.workspacePath,
|
|
338
|
-
contextFiles: conversation.contextFiles,
|
|
339
|
-
summary: conversation.summary,
|
|
340
|
-
metadata: conversation.metadata
|
|
341
|
-
};
|
|
342
|
-
}
|
|
343
|
-
return JSON.stringify(payload, null, 2);
|
|
344
|
-
}
|
|
345
|
-
function getExportFilename(format, conversation) {
|
|
346
|
-
const timestamp = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
347
|
-
const base = conversation?.title ? conversation.title.replace(/[^a-zA-Z0-9-_]/g, "_").slice(0, 40) : "chat-export";
|
|
348
|
-
const ext = format === "markdown" ? "md" : format === "txt" ? "txt" : "json";
|
|
349
|
-
return `${base}-${timestamp}.${ext}`;
|
|
350
|
-
}
|
|
351
|
-
var MIME_TYPES = {
|
|
352
|
-
markdown: "text/markdown",
|
|
353
|
-
txt: "text/plain",
|
|
354
|
-
json: "application/json"
|
|
355
|
-
};
|
|
356
|
-
function downloadAsFile(content, filename, mimeType) {
|
|
357
|
-
const blob = new Blob([content], { type: mimeType });
|
|
358
|
-
const url = URL.createObjectURL(blob);
|
|
359
|
-
const a = document.createElement("a");
|
|
360
|
-
a.href = url;
|
|
361
|
-
a.download = filename;
|
|
362
|
-
document.body.appendChild(a);
|
|
363
|
-
a.click();
|
|
364
|
-
document.body.removeChild(a);
|
|
365
|
-
URL.revokeObjectURL(url);
|
|
366
|
-
}
|
|
367
|
-
function exportToFile(messages, format, conversation) {
|
|
368
|
-
let content;
|
|
369
|
-
if (format === "markdown") {
|
|
370
|
-
content = formatMessagesAsMarkdown(messages);
|
|
371
|
-
} else if (format === "txt") {
|
|
372
|
-
content = formatMessagesAsTxt(messages);
|
|
373
|
-
} else {
|
|
374
|
-
content = formatMessagesAsJson(messages, conversation);
|
|
375
|
-
}
|
|
376
|
-
const filename = getExportFilename(format, conversation);
|
|
377
|
-
const mimeType = MIME_TYPES[format];
|
|
378
|
-
downloadAsFile(content, filename, mimeType);
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
// src/presentation/components/ChatExportToolbar.tsx
|
|
382
|
-
import { jsx as jsx3, jsxs as jsxs3, Fragment } from "react/jsx-runtime";
|
|
383
|
-
"use client";
|
|
384
|
-
function ChatExportToolbar({
|
|
385
|
-
messages,
|
|
386
|
-
conversation,
|
|
387
|
-
selectedIds,
|
|
388
|
-
onExported,
|
|
389
|
-
showSelectionSummary = true,
|
|
390
|
-
onSelectAll,
|
|
391
|
-
onClearSelection,
|
|
392
|
-
selectedCount = selectedIds.size,
|
|
393
|
-
totalCount = messages.length,
|
|
394
|
-
onCreateNew,
|
|
395
|
-
onFork
|
|
396
|
-
}) {
|
|
397
|
-
const [copied, setCopied] = React2.useState(false);
|
|
398
|
-
const toExport = React2.useMemo(() => {
|
|
399
|
-
if (selectedIds.size > 0) {
|
|
400
|
-
const idSet = selectedIds;
|
|
401
|
-
return messages.filter((m) => idSet.has(m.id));
|
|
402
|
-
}
|
|
403
|
-
return messages;
|
|
404
|
-
}, [messages, selectedIds]);
|
|
405
|
-
const handleExport = React2.useCallback((format) => {
|
|
406
|
-
exportToFile(toExport, format, conversation);
|
|
407
|
-
onExported?.(format, toExport.length);
|
|
408
|
-
}, [toExport, conversation, onExported]);
|
|
409
|
-
const handleCopy = React2.useCallback(async () => {
|
|
410
|
-
const content = formatMessagesAsMarkdown(toExport);
|
|
411
|
-
await navigator.clipboard.writeText(content);
|
|
412
|
-
setCopied(true);
|
|
413
|
-
setTimeout(() => setCopied(false), 2000);
|
|
414
|
-
onExported?.("markdown", toExport.length);
|
|
415
|
-
}, [toExport, onExported]);
|
|
416
|
-
const disabled = messages.length === 0;
|
|
417
|
-
const [forking, setForking] = React2.useState(false);
|
|
418
|
-
const handleFork = React2.useCallback(async (upToMessageId) => {
|
|
419
|
-
if (!onFork)
|
|
420
|
-
return;
|
|
421
|
-
setForking(true);
|
|
422
|
-
try {
|
|
423
|
-
await onFork(upToMessageId);
|
|
424
|
-
} finally {
|
|
425
|
-
setForking(false);
|
|
426
|
-
}
|
|
427
|
-
}, [onFork]);
|
|
428
|
-
return /* @__PURE__ */ jsxs3("div", {
|
|
429
|
-
className: "flex items-center gap-2",
|
|
430
|
-
children: [
|
|
431
|
-
onCreateNew && /* @__PURE__ */ jsxs3(Button, {
|
|
432
|
-
variant: "outline",
|
|
433
|
-
size: "sm",
|
|
434
|
-
onPress: onCreateNew,
|
|
435
|
-
"aria-label": "New conversation",
|
|
436
|
-
children: [
|
|
437
|
-
/* @__PURE__ */ jsx3(Plus, {
|
|
438
|
-
className: "h-4 w-4"
|
|
439
|
-
}),
|
|
440
|
-
"New"
|
|
441
|
-
]
|
|
442
|
-
}),
|
|
443
|
-
onFork && messages.length > 0 && /* @__PURE__ */ jsxs3(Button, {
|
|
444
|
-
variant: "outline",
|
|
445
|
-
size: "sm",
|
|
446
|
-
disabled: forking,
|
|
447
|
-
onPress: () => handleFork(),
|
|
448
|
-
"aria-label": "Fork conversation",
|
|
449
|
-
children: [
|
|
450
|
-
/* @__PURE__ */ jsx3(GitFork, {
|
|
451
|
-
className: "h-4 w-4"
|
|
452
|
-
}),
|
|
453
|
-
"Fork"
|
|
454
|
-
]
|
|
455
|
-
}),
|
|
456
|
-
showSelectionSummary && selectedCount > 0 && /* @__PURE__ */ jsxs3("span", {
|
|
457
|
-
className: "text-muted-foreground text-sm",
|
|
458
|
-
children: [
|
|
459
|
-
selectedCount,
|
|
460
|
-
" message",
|
|
461
|
-
selectedCount !== 1 ? "s" : "",
|
|
462
|
-
" selected"
|
|
463
|
-
]
|
|
464
|
-
}),
|
|
465
|
-
onSelectAll && onClearSelection && totalCount > 0 && /* @__PURE__ */ jsxs3(Fragment, {
|
|
466
|
-
children: [
|
|
467
|
-
/* @__PURE__ */ jsx3(Button, {
|
|
468
|
-
variant: "ghost",
|
|
469
|
-
size: "sm",
|
|
470
|
-
onPress: onSelectAll,
|
|
471
|
-
className: "text-xs",
|
|
472
|
-
children: "Select all"
|
|
473
|
-
}),
|
|
474
|
-
selectedCount > 0 && /* @__PURE__ */ jsx3(Button, {
|
|
475
|
-
variant: "ghost",
|
|
476
|
-
size: "sm",
|
|
477
|
-
onPress: onClearSelection,
|
|
478
|
-
className: "text-xs",
|
|
479
|
-
children: "Clear"
|
|
480
|
-
})
|
|
481
|
-
]
|
|
482
|
-
}),
|
|
483
|
-
/* @__PURE__ */ jsxs3(DropdownMenu, {
|
|
484
|
-
children: [
|
|
485
|
-
/* @__PURE__ */ jsx3(DropdownMenuTrigger, {
|
|
486
|
-
asChild: true,
|
|
487
|
-
children: /* @__PURE__ */ jsxs3(Button, {
|
|
488
|
-
variant: "outline",
|
|
489
|
-
size: "sm",
|
|
490
|
-
disabled,
|
|
491
|
-
"aria-label": selectedCount > 0 ? "Export selected messages" : "Export conversation",
|
|
492
|
-
children: [
|
|
493
|
-
/* @__PURE__ */ jsx3(Download, {
|
|
494
|
-
className: "h-4 w-4"
|
|
495
|
-
}),
|
|
496
|
-
"Export"
|
|
497
|
-
]
|
|
498
|
-
})
|
|
499
|
-
}),
|
|
500
|
-
/* @__PURE__ */ jsxs3(DropdownMenuContent, {
|
|
501
|
-
align: "end",
|
|
502
|
-
children: [
|
|
503
|
-
/* @__PURE__ */ jsxs3(DropdownMenuItem, {
|
|
504
|
-
onSelect: () => handleExport("markdown"),
|
|
505
|
-
disabled,
|
|
506
|
-
children: [
|
|
507
|
-
/* @__PURE__ */ jsx3(FileText, {
|
|
508
|
-
className: "h-4 w-4"
|
|
509
|
-
}),
|
|
510
|
-
"Export as Markdown (.md)"
|
|
511
|
-
]
|
|
512
|
-
}),
|
|
513
|
-
/* @__PURE__ */ jsxs3(DropdownMenuItem, {
|
|
514
|
-
onSelect: () => handleExport("txt"),
|
|
515
|
-
disabled,
|
|
516
|
-
children: [
|
|
517
|
-
/* @__PURE__ */ jsx3(FileText, {
|
|
518
|
-
className: "h-4 w-4"
|
|
519
|
-
}),
|
|
520
|
-
"Export as Plain Text (.txt)"
|
|
521
|
-
]
|
|
522
|
-
}),
|
|
523
|
-
/* @__PURE__ */ jsxs3(DropdownMenuItem, {
|
|
524
|
-
onSelect: () => handleExport("json"),
|
|
525
|
-
disabled,
|
|
526
|
-
children: [
|
|
527
|
-
/* @__PURE__ */ jsx3(FileText, {
|
|
528
|
-
className: "h-4 w-4"
|
|
529
|
-
}),
|
|
530
|
-
"Export as JSON (.json)"
|
|
531
|
-
]
|
|
532
|
-
}),
|
|
533
|
-
/* @__PURE__ */ jsx3(DropdownMenuSeparator, {}),
|
|
534
|
-
/* @__PURE__ */ jsxs3(DropdownMenuItem, {
|
|
535
|
-
onSelect: () => handleCopy(),
|
|
536
|
-
disabled,
|
|
537
|
-
children: [
|
|
538
|
-
copied ? /* @__PURE__ */ jsx3(Check, {
|
|
539
|
-
className: "h-4 w-4 text-green-500"
|
|
540
|
-
}) : /* @__PURE__ */ jsx3(Copy, {
|
|
541
|
-
className: "h-4 w-4"
|
|
542
|
-
}),
|
|
543
|
-
copied ? "Copied to clipboard" : "Copy to clipboard"
|
|
544
|
-
]
|
|
545
|
-
})
|
|
546
|
-
]
|
|
547
|
-
})
|
|
548
|
-
]
|
|
549
|
-
})
|
|
550
|
-
]
|
|
551
|
-
});
|
|
552
|
-
}
|
|
553
|
-
// src/presentation/components/ChatInput.tsx
|
|
554
|
-
import { Button as Button2, Textarea } from "@contractspec/lib.design-system";
|
|
555
|
-
import { cn as cn3 } from "@contractspec/lib.ui-kit-web/ui/utils";
|
|
556
|
-
import {
|
|
557
|
-
Code,
|
|
558
|
-
FileText as FileText2,
|
|
559
|
-
ImageIcon,
|
|
560
|
-
Loader2,
|
|
561
|
-
Paperclip,
|
|
562
|
-
Send,
|
|
563
|
-
X
|
|
564
|
-
} from "lucide-react";
|
|
565
|
-
import * as React3 from "react";
|
|
566
|
-
import { jsx as jsx4, jsxs as jsxs4, Fragment as Fragment2 } from "react/jsx-runtime";
|
|
567
|
-
"use client";
|
|
568
|
-
var DEFAULT_MAX_FILE_SIZE_BYTES = 5 * 1024 * 1024;
|
|
569
|
-
var CODE_EXTENSIONS = [
|
|
570
|
-
"ts",
|
|
571
|
-
"tsx",
|
|
572
|
-
"js",
|
|
573
|
-
"jsx",
|
|
574
|
-
"py",
|
|
575
|
-
"go",
|
|
576
|
-
"rs",
|
|
577
|
-
"java",
|
|
578
|
-
"json",
|
|
579
|
-
"md",
|
|
580
|
-
"txt",
|
|
581
|
-
"yaml",
|
|
582
|
-
"yml"
|
|
583
|
-
];
|
|
584
|
-
function readFileAsContent(file) {
|
|
585
|
-
const extension = file.name.split(".").pop()?.toLowerCase() ?? "";
|
|
586
|
-
const isCode = CODE_EXTENSIONS.includes(extension);
|
|
587
|
-
const isImage = file.type.startsWith("image/");
|
|
588
|
-
if (isImage) {
|
|
589
|
-
return new Promise((resolve, reject) => {
|
|
590
|
-
const reader = new FileReader;
|
|
591
|
-
reader.onload = () => {
|
|
592
|
-
const result = reader.result;
|
|
593
|
-
resolve({
|
|
594
|
-
content: typeof result === "string" ? result : new TextDecoder().decode(result ?? new ArrayBuffer(0)),
|
|
595
|
-
type: "image"
|
|
596
|
-
});
|
|
597
|
-
};
|
|
598
|
-
reader.onerror = () => reject(new Error("Could not read file"));
|
|
599
|
-
reader.readAsDataURL(file);
|
|
600
|
-
});
|
|
601
|
-
}
|
|
602
|
-
return file.text().then((content) => ({
|
|
603
|
-
content,
|
|
604
|
-
type: isCode ? "code" : "file"
|
|
605
|
-
})).catch(() => {
|
|
606
|
-
throw new Error("Could not read file");
|
|
607
|
-
});
|
|
608
|
-
}
|
|
609
|
-
function ChatInput({
|
|
610
|
-
onSend,
|
|
611
|
-
disabled = false,
|
|
612
|
-
isLoading = false,
|
|
613
|
-
placeholder = "Type a message...",
|
|
614
|
-
className,
|
|
615
|
-
showAttachments = true,
|
|
616
|
-
maxAttachments = 5,
|
|
617
|
-
maxFileSizeBytes = DEFAULT_MAX_FILE_SIZE_BYTES
|
|
618
|
-
}) {
|
|
619
|
-
const [content, setContent] = React3.useState("");
|
|
620
|
-
const [attachments, setAttachments] = React3.useState([]);
|
|
621
|
-
const [fileError, setFileError] = React3.useState(null);
|
|
622
|
-
const textareaRef = React3.useRef(null);
|
|
623
|
-
const fileInputRef = React3.useRef(null);
|
|
624
|
-
const canSend = content.trim().length > 0 || attachments.length > 0;
|
|
625
|
-
const handleSubmit = React3.useCallback((e) => {
|
|
626
|
-
e?.preventDefault();
|
|
627
|
-
if (!canSend || disabled || isLoading)
|
|
628
|
-
return;
|
|
629
|
-
onSend(content.trim(), attachments.length > 0 ? attachments : undefined);
|
|
630
|
-
setContent("");
|
|
631
|
-
setAttachments([]);
|
|
632
|
-
setFileError(null);
|
|
633
|
-
textareaRef.current?.focus();
|
|
634
|
-
}, [canSend, content, attachments, disabled, isLoading, onSend]);
|
|
635
|
-
const handleKeyDown = React3.useCallback((e) => {
|
|
636
|
-
if (e.key === "Enter" && !e.shiftKey) {
|
|
637
|
-
e.preventDefault();
|
|
638
|
-
handleSubmit();
|
|
639
|
-
}
|
|
640
|
-
}, [handleSubmit]);
|
|
641
|
-
const handleFileSelect = React3.useCallback(async (e) => {
|
|
642
|
-
const files = e.target.files;
|
|
643
|
-
if (!files)
|
|
644
|
-
return;
|
|
645
|
-
setFileError(null);
|
|
646
|
-
const newAttachments = [];
|
|
647
|
-
const errors = [];
|
|
648
|
-
for (const file of Array.from(files)) {
|
|
649
|
-
if (attachments.length + newAttachments.length >= maxAttachments) {
|
|
650
|
-
errors.push(`Maximum ${maxAttachments} attachments allowed`);
|
|
651
|
-
break;
|
|
652
|
-
}
|
|
653
|
-
if (file.size > maxFileSizeBytes) {
|
|
654
|
-
errors.push(`${file.name} exceeds ${Math.round(maxFileSizeBytes / 1024 / 1024)}MB limit`);
|
|
655
|
-
continue;
|
|
656
|
-
}
|
|
657
|
-
try {
|
|
658
|
-
const { content: fileContent, type } = await readFileAsContent(file);
|
|
659
|
-
newAttachments.push({
|
|
660
|
-
id: `att_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`,
|
|
661
|
-
type,
|
|
662
|
-
name: file.name,
|
|
663
|
-
content: fileContent,
|
|
664
|
-
mimeType: file.type,
|
|
665
|
-
size: file.size
|
|
666
|
-
});
|
|
667
|
-
} catch {
|
|
668
|
-
errors.push(`Could not read ${file.name}`);
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
if (errors.length > 0) {
|
|
672
|
-
setFileError(errors[0] ?? "Could not add file");
|
|
673
|
-
}
|
|
674
|
-
if (newAttachments.length > 0) {
|
|
675
|
-
setAttachments((prev) => [...prev, ...newAttachments]);
|
|
676
|
-
}
|
|
677
|
-
e.target.value = "";
|
|
678
|
-
}, [attachments.length, maxAttachments, maxFileSizeBytes]);
|
|
679
|
-
const removeAttachment = React3.useCallback((id) => {
|
|
680
|
-
setAttachments((prev) => prev.filter((a) => a.id !== id));
|
|
681
|
-
}, []);
|
|
682
|
-
return /* @__PURE__ */ jsxs4("div", {
|
|
683
|
-
className: cn3("flex flex-col gap-2", className),
|
|
684
|
-
children: [
|
|
685
|
-
attachments.length > 0 && /* @__PURE__ */ jsx4("div", {
|
|
686
|
-
className: "flex flex-wrap gap-2",
|
|
687
|
-
children: attachments.map((attachment) => /* @__PURE__ */ jsxs4("div", {
|
|
688
|
-
className: cn3("flex items-center gap-1.5 rounded-md px-2 py-1", "bg-muted text-muted-foreground text-sm"),
|
|
689
|
-
children: [
|
|
690
|
-
attachment.type === "code" ? /* @__PURE__ */ jsx4(Code, {
|
|
691
|
-
className: "h-3.5 w-3.5"
|
|
692
|
-
}) : attachment.type === "image" ? /* @__PURE__ */ jsx4(ImageIcon, {
|
|
693
|
-
className: "h-3.5 w-3.5"
|
|
694
|
-
}) : /* @__PURE__ */ jsx4(FileText2, {
|
|
695
|
-
className: "h-3.5 w-3.5"
|
|
696
|
-
}),
|
|
697
|
-
/* @__PURE__ */ jsx4("span", {
|
|
698
|
-
className: "max-w-[150px] truncate",
|
|
699
|
-
children: attachment.name
|
|
700
|
-
}),
|
|
701
|
-
/* @__PURE__ */ jsx4("button", {
|
|
702
|
-
type: "button",
|
|
703
|
-
onClick: () => removeAttachment(attachment.id),
|
|
704
|
-
className: "hover:text-foreground",
|
|
705
|
-
"aria-label": `Remove ${attachment.name}`,
|
|
706
|
-
children: /* @__PURE__ */ jsx4(X, {
|
|
707
|
-
className: "h-3.5 w-3.5"
|
|
708
|
-
})
|
|
709
|
-
})
|
|
710
|
-
]
|
|
711
|
-
}, attachment.id))
|
|
712
|
-
}),
|
|
713
|
-
fileError && /* @__PURE__ */ jsx4("p", {
|
|
714
|
-
className: "text-destructive text-xs",
|
|
715
|
-
role: "alert",
|
|
716
|
-
children: fileError
|
|
717
|
-
}),
|
|
718
|
-
/* @__PURE__ */ jsxs4("form", {
|
|
719
|
-
onSubmit: handleSubmit,
|
|
720
|
-
className: "flex items-end gap-2",
|
|
721
|
-
children: [
|
|
722
|
-
showAttachments && /* @__PURE__ */ jsxs4(Fragment2, {
|
|
723
|
-
children: [
|
|
724
|
-
/* @__PURE__ */ jsx4("input", {
|
|
725
|
-
ref: fileInputRef,
|
|
726
|
-
type: "file",
|
|
727
|
-
multiple: true,
|
|
728
|
-
accept: ".ts,.tsx,.js,.jsx,.json,.md,.txt,.py,.go,.rs,.java,.yaml,.yml,image/*",
|
|
729
|
-
onChange: handleFileSelect,
|
|
730
|
-
className: "hidden",
|
|
731
|
-
"aria-label": "Attach files"
|
|
732
|
-
}),
|
|
733
|
-
/* @__PURE__ */ jsx4(Button2, {
|
|
734
|
-
type: "button",
|
|
735
|
-
variant: "ghost",
|
|
736
|
-
size: "sm",
|
|
737
|
-
onPress: () => fileInputRef.current?.click(),
|
|
738
|
-
disabled: disabled || attachments.length >= maxAttachments,
|
|
739
|
-
"aria-label": "Attach files",
|
|
740
|
-
children: /* @__PURE__ */ jsx4(Paperclip, {
|
|
741
|
-
className: "h-4 w-4"
|
|
742
|
-
})
|
|
743
|
-
})
|
|
744
|
-
]
|
|
745
|
-
}),
|
|
746
|
-
/* @__PURE__ */ jsx4("div", {
|
|
747
|
-
className: "relative flex-1",
|
|
748
|
-
children: /* @__PURE__ */ jsx4(Textarea, {
|
|
749
|
-
value: content,
|
|
750
|
-
onChange: (e) => setContent(e.target.value),
|
|
751
|
-
onKeyDown: handleKeyDown,
|
|
752
|
-
placeholder,
|
|
753
|
-
disabled,
|
|
754
|
-
className: cn3("max-h-[200px] min-h-[44px] resize-none pr-12", "focus-visible:ring-1"),
|
|
755
|
-
rows: 1,
|
|
756
|
-
"aria-label": "Chat message"
|
|
757
|
-
})
|
|
758
|
-
}),
|
|
759
|
-
/* @__PURE__ */ jsx4(Button2, {
|
|
760
|
-
type: "submit",
|
|
761
|
-
disabled: !canSend || disabled || isLoading,
|
|
762
|
-
size: "sm",
|
|
763
|
-
"aria-label": isLoading ? "Sending..." : "Send message",
|
|
764
|
-
children: isLoading ? /* @__PURE__ */ jsx4(Loader2, {
|
|
765
|
-
className: "h-4 w-4 animate-spin"
|
|
766
|
-
}) : /* @__PURE__ */ jsx4(Send, {
|
|
767
|
-
className: "h-4 w-4"
|
|
768
|
-
})
|
|
769
|
-
})
|
|
770
|
-
]
|
|
771
|
-
}),
|
|
772
|
-
/* @__PURE__ */ jsx4("p", {
|
|
773
|
-
className: "text-muted-foreground text-xs",
|
|
774
|
-
children: "Press Enter to send, Shift+Enter for new line"
|
|
775
|
-
})
|
|
776
|
-
]
|
|
777
|
-
});
|
|
778
|
-
}
|
|
779
|
-
// src/presentation/components/ChatMessage.tsx
|
|
780
|
-
import { Button as Button4 } from "@contractspec/lib.design-system";
|
|
781
|
-
import { Avatar, AvatarFallback } from "@contractspec/lib.ui-kit-web/ui/avatar";
|
|
782
|
-
import { Checkbox } from "@contractspec/lib.ui-kit-web/ui/checkbox";
|
|
783
|
-
import { Skeleton } from "@contractspec/lib.ui-kit-web/ui/skeleton";
|
|
784
|
-
import { cn as cn7 } from "@contractspec/lib.ui-kit-web/ui/utils";
|
|
785
|
-
import {
|
|
786
|
-
AlertCircle,
|
|
787
|
-
Bot,
|
|
788
|
-
Check as Check3,
|
|
789
|
-
Copy as Copy3,
|
|
790
|
-
Pencil,
|
|
791
|
-
User,
|
|
792
|
-
Wrench,
|
|
793
|
-
X as X2
|
|
794
|
-
} from "lucide-react";
|
|
795
|
-
import * as React6 from "react";
|
|
796
|
-
|
|
797
|
-
// src/presentation/components/CodePreview.tsx
|
|
798
|
-
import { Button as Button3 } from "@contractspec/lib.design-system";
|
|
799
|
-
import { cn as cn4 } from "@contractspec/lib.ui-kit-web/ui/utils";
|
|
800
|
-
import { Check as Check2, Copy as Copy2, Download as Download2, Play } from "lucide-react";
|
|
801
|
-
import * as React4 from "react";
|
|
802
|
-
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
803
|
-
"use client";
|
|
804
|
-
var LANGUAGE_NAMES = {
|
|
805
|
-
ts: "TypeScript",
|
|
806
|
-
tsx: "TypeScript (React)",
|
|
807
|
-
typescript: "TypeScript",
|
|
808
|
-
js: "JavaScript",
|
|
809
|
-
jsx: "JavaScript (React)",
|
|
810
|
-
javascript: "JavaScript",
|
|
811
|
-
json: "JSON",
|
|
812
|
-
md: "Markdown",
|
|
813
|
-
yaml: "YAML",
|
|
814
|
-
yml: "YAML",
|
|
815
|
-
bash: "Bash",
|
|
816
|
-
sh: "Shell",
|
|
817
|
-
sql: "SQL",
|
|
818
|
-
py: "Python",
|
|
819
|
-
python: "Python",
|
|
820
|
-
go: "Go",
|
|
821
|
-
rust: "Rust",
|
|
822
|
-
rs: "Rust"
|
|
823
|
-
};
|
|
824
|
-
function CodePreview({
|
|
825
|
-
code,
|
|
826
|
-
language = "text",
|
|
827
|
-
filename,
|
|
828
|
-
className,
|
|
829
|
-
showCopy = true,
|
|
830
|
-
showExecute = false,
|
|
831
|
-
onExecute,
|
|
832
|
-
showDownload = false,
|
|
833
|
-
maxHeight = 400
|
|
834
|
-
}) {
|
|
835
|
-
const [copied, setCopied] = React4.useState(false);
|
|
836
|
-
const displayLanguage = LANGUAGE_NAMES[language.toLowerCase()] ?? language;
|
|
837
|
-
const lines = code.split(`
|
|
838
|
-
`);
|
|
839
|
-
const handleCopy = React4.useCallback(async () => {
|
|
840
|
-
await navigator.clipboard.writeText(code);
|
|
841
|
-
setCopied(true);
|
|
842
|
-
setTimeout(() => setCopied(false), 2000);
|
|
843
|
-
}, [code]);
|
|
844
|
-
const handleDownload = React4.useCallback(() => {
|
|
845
|
-
const blob = new Blob([code], { type: "text/plain" });
|
|
846
|
-
const url = URL.createObjectURL(blob);
|
|
847
|
-
const a = document.createElement("a");
|
|
848
|
-
a.href = url;
|
|
849
|
-
a.download = filename ?? `code.${language}`;
|
|
850
|
-
document.body.appendChild(a);
|
|
851
|
-
a.click();
|
|
852
|
-
document.body.removeChild(a);
|
|
853
|
-
URL.revokeObjectURL(url);
|
|
854
|
-
}, [code, filename, language]);
|
|
855
|
-
return /* @__PURE__ */ jsxs5("div", {
|
|
856
|
-
className: cn4("overflow-hidden rounded-lg border", "bg-muted/50", className),
|
|
857
|
-
children: [
|
|
858
|
-
/* @__PURE__ */ jsxs5("div", {
|
|
859
|
-
className: cn4("flex items-center justify-between px-3 py-1.5", "border-b bg-muted/80"),
|
|
860
|
-
children: [
|
|
861
|
-
/* @__PURE__ */ jsxs5("div", {
|
|
862
|
-
className: "flex items-center gap-2 text-sm",
|
|
863
|
-
children: [
|
|
864
|
-
filename && /* @__PURE__ */ jsx5("span", {
|
|
865
|
-
className: "font-mono text-foreground",
|
|
866
|
-
children: filename
|
|
867
|
-
}),
|
|
868
|
-
/* @__PURE__ */ jsx5("span", {
|
|
869
|
-
className: "text-muted-foreground",
|
|
870
|
-
children: displayLanguage
|
|
871
|
-
})
|
|
872
|
-
]
|
|
873
|
-
}),
|
|
874
|
-
/* @__PURE__ */ jsxs5("div", {
|
|
875
|
-
className: "flex items-center gap-1",
|
|
876
|
-
children: [
|
|
877
|
-
showExecute && onExecute && /* @__PURE__ */ jsx5(Button3, {
|
|
878
|
-
variant: "ghost",
|
|
879
|
-
size: "sm",
|
|
880
|
-
onPress: () => onExecute(code),
|
|
881
|
-
className: "h-7 w-7 p-0",
|
|
882
|
-
"aria-label": "Execute code",
|
|
883
|
-
children: /* @__PURE__ */ jsx5(Play, {
|
|
884
|
-
className: "h-3.5 w-3.5"
|
|
885
|
-
})
|
|
886
|
-
}),
|
|
887
|
-
showDownload && /* @__PURE__ */ jsx5(Button3, {
|
|
888
|
-
variant: "ghost",
|
|
889
|
-
size: "sm",
|
|
890
|
-
onPress: handleDownload,
|
|
891
|
-
className: "h-7 w-7 p-0",
|
|
892
|
-
"aria-label": "Download code",
|
|
893
|
-
children: /* @__PURE__ */ jsx5(Download2, {
|
|
894
|
-
className: "h-3.5 w-3.5"
|
|
895
|
-
})
|
|
896
|
-
}),
|
|
897
|
-
showCopy && /* @__PURE__ */ jsx5(Button3, {
|
|
898
|
-
variant: "ghost",
|
|
899
|
-
size: "sm",
|
|
900
|
-
onPress: handleCopy,
|
|
901
|
-
className: "h-7 w-7 p-0",
|
|
902
|
-
"aria-label": copied ? "Copied" : "Copy code",
|
|
903
|
-
children: copied ? /* @__PURE__ */ jsx5(Check2, {
|
|
904
|
-
className: "h-3.5 w-3.5 text-green-500"
|
|
905
|
-
}) : /* @__PURE__ */ jsx5(Copy2, {
|
|
906
|
-
className: "h-3.5 w-3.5"
|
|
907
|
-
})
|
|
908
|
-
})
|
|
909
|
-
]
|
|
910
|
-
})
|
|
911
|
-
]
|
|
912
|
-
}),
|
|
913
|
-
/* @__PURE__ */ jsx5("div", {
|
|
914
|
-
className: "overflow-auto",
|
|
915
|
-
style: { maxHeight },
|
|
916
|
-
children: /* @__PURE__ */ jsx5("pre", {
|
|
917
|
-
className: "p-3",
|
|
918
|
-
children: /* @__PURE__ */ jsx5("code", {
|
|
919
|
-
className: "text-sm",
|
|
920
|
-
children: lines.map((line, i) => /* @__PURE__ */ jsxs5("div", {
|
|
921
|
-
className: "flex",
|
|
922
|
-
children: [
|
|
923
|
-
/* @__PURE__ */ jsx5("span", {
|
|
924
|
-
className: "mr-4 w-8 select-none text-right text-muted-foreground",
|
|
925
|
-
children: i + 1
|
|
926
|
-
}),
|
|
927
|
-
/* @__PURE__ */ jsx5("span", {
|
|
928
|
-
className: "flex-1",
|
|
929
|
-
children: line || " "
|
|
930
|
-
})
|
|
931
|
-
]
|
|
932
|
-
}, i))
|
|
933
|
-
})
|
|
934
|
-
})
|
|
935
|
-
})
|
|
936
|
-
]
|
|
937
|
-
});
|
|
938
|
-
}
|
|
939
|
-
|
|
940
|
-
// src/presentation/components/Reasoning.tsx
|
|
941
|
-
import {
|
|
942
|
-
Collapsible as Collapsible2,
|
|
943
|
-
CollapsibleContent as CollapsibleContent2,
|
|
944
|
-
CollapsibleTrigger as CollapsibleTrigger2
|
|
945
|
-
} from "@contractspec/lib.ui-kit-web/ui/collapsible";
|
|
946
|
-
import { cn as cn5 } from "@contractspec/lib.ui-kit-web/ui/utils";
|
|
947
|
-
import * as React5 from "react";
|
|
948
|
-
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
949
|
-
"use client";
|
|
950
|
-
function Reasoning({
|
|
951
|
-
isStreaming = false,
|
|
952
|
-
open,
|
|
953
|
-
defaultOpen = false,
|
|
954
|
-
onOpenChange,
|
|
955
|
-
children,
|
|
956
|
-
className
|
|
957
|
-
}) {
|
|
958
|
-
const [internalOpen, setInternalOpen] = React5.useState(defaultOpen);
|
|
959
|
-
const prevStreamingRef = React5.useRef(isStreaming);
|
|
960
|
-
const isControlled = open !== undefined;
|
|
961
|
-
React5.useEffect(() => {
|
|
962
|
-
if (isStreaming) {
|
|
963
|
-
if (isControlled) {
|
|
964
|
-
onOpenChange?.(true);
|
|
965
|
-
} else {
|
|
966
|
-
setInternalOpen(true);
|
|
967
|
-
}
|
|
968
|
-
} else if (prevStreamingRef.current) {
|
|
969
|
-
if (isControlled) {
|
|
970
|
-
onOpenChange?.(false);
|
|
971
|
-
} else {
|
|
972
|
-
setInternalOpen(false);
|
|
973
|
-
}
|
|
974
|
-
}
|
|
975
|
-
prevStreamingRef.current = isStreaming;
|
|
976
|
-
}, [isStreaming, isControlled, onOpenChange]);
|
|
977
|
-
const handleOpenChange = React5.useCallback((next) => {
|
|
978
|
-
if (isControlled) {
|
|
979
|
-
onOpenChange?.(next);
|
|
980
|
-
} else {
|
|
981
|
-
setInternalOpen(next);
|
|
982
|
-
}
|
|
983
|
-
}, [isControlled, onOpenChange]);
|
|
984
|
-
return /* @__PURE__ */ jsx6(Collapsible2, {
|
|
985
|
-
open: isControlled ? open : internalOpen,
|
|
986
|
-
onOpenChange: handleOpenChange,
|
|
987
|
-
className: cn5("w-full", className),
|
|
988
|
-
children
|
|
989
|
-
});
|
|
990
|
-
}
|
|
991
|
-
function ReasoningTrigger({
|
|
992
|
-
children,
|
|
993
|
-
isStreaming = false,
|
|
994
|
-
className
|
|
995
|
-
}) {
|
|
996
|
-
return /* @__PURE__ */ jsxs6(CollapsibleTrigger2, {
|
|
997
|
-
className: cn5("flex w-full cursor-pointer items-center gap-2 rounded-md px-2 py-1.5 text-muted-foreground text-sm transition-colors hover:bg-muted hover:text-foreground", className),
|
|
998
|
-
children: [
|
|
999
|
-
isStreaming && /* @__PURE__ */ jsx6("span", {
|
|
1000
|
-
className: "h-1.5 w-1.5 animate-pulse rounded-full bg-primary",
|
|
1001
|
-
"aria-hidden": true
|
|
1002
|
-
}),
|
|
1003
|
-
children ?? (isStreaming ? "Thinking..." : "View reasoning")
|
|
1004
|
-
]
|
|
1005
|
-
});
|
|
1006
|
-
}
|
|
1007
|
-
function ReasoningContent({
|
|
1008
|
-
children,
|
|
1009
|
-
className
|
|
1010
|
-
}) {
|
|
1011
|
-
return /* @__PURE__ */ jsx6(CollapsibleContent2, {
|
|
1012
|
-
children: /* @__PURE__ */ jsx6("div", {
|
|
1013
|
-
className: cn5("mt-1 rounded-md bg-muted p-2 text-muted-foreground text-sm", className),
|
|
1014
|
-
children: /* @__PURE__ */ jsx6("p", {
|
|
1015
|
-
className: "whitespace-pre-wrap",
|
|
1016
|
-
children
|
|
1017
|
-
})
|
|
1018
|
-
})
|
|
1019
|
-
});
|
|
1020
|
-
}
|
|
1021
|
-
|
|
1022
|
-
// src/presentation/components/Sources.tsx
|
|
1023
|
-
import {
|
|
1024
|
-
Collapsible as Collapsible3,
|
|
1025
|
-
CollapsibleContent as CollapsibleContent3,
|
|
1026
|
-
CollapsibleTrigger as CollapsibleTrigger3
|
|
1027
|
-
} from "@contractspec/lib.ui-kit-web/ui/collapsible";
|
|
1028
|
-
import { cn as cn6 } from "@contractspec/lib.ui-kit-web/ui/utils";
|
|
1029
|
-
import { ExternalLink } from "lucide-react";
|
|
1030
|
-
import { jsx as jsx7, jsxs as jsxs7, Fragment as Fragment3 } from "react/jsx-runtime";
|
|
1031
|
-
"use client";
|
|
1032
|
-
function Sources({ children, className }) {
|
|
1033
|
-
return /* @__PURE__ */ jsx7(Collapsible3, {
|
|
1034
|
-
className: cn6("mt-2", className),
|
|
1035
|
-
defaultOpen: false,
|
|
1036
|
-
children
|
|
1037
|
-
});
|
|
1038
|
-
}
|
|
1039
|
-
function SourcesTrigger({
|
|
1040
|
-
count,
|
|
1041
|
-
children,
|
|
1042
|
-
className
|
|
1043
|
-
}) {
|
|
1044
|
-
return /* @__PURE__ */ jsx7(CollapsibleTrigger3, {
|
|
1045
|
-
className: cn6("inline-flex cursor-pointer items-center gap-1.5 rounded-md px-2 py-1 text-muted-foreground text-xs transition-colors hover:bg-muted hover:text-foreground", className),
|
|
1046
|
-
children: children ?? /* @__PURE__ */ jsxs7(Fragment3, {
|
|
1047
|
-
children: [
|
|
1048
|
-
/* @__PURE__ */ jsx7(ExternalLink, {
|
|
1049
|
-
className: "h-3 w-3"
|
|
1050
|
-
}),
|
|
1051
|
-
count,
|
|
1052
|
-
" source",
|
|
1053
|
-
count !== 1 ? "s" : ""
|
|
1054
|
-
]
|
|
1055
|
-
})
|
|
1056
|
-
});
|
|
1057
|
-
}
|
|
1058
|
-
function SourcesContent({ children, className }) {
|
|
1059
|
-
return /* @__PURE__ */ jsx7(CollapsibleContent3, {
|
|
1060
|
-
children: /* @__PURE__ */ jsx7("div", {
|
|
1061
|
-
className: cn6("mt-2 flex flex-wrap gap-2", className),
|
|
1062
|
-
children
|
|
1063
|
-
})
|
|
1064
|
-
});
|
|
1065
|
-
}
|
|
1066
|
-
function Source({
|
|
1067
|
-
href,
|
|
1068
|
-
title,
|
|
1069
|
-
className,
|
|
1070
|
-
children,
|
|
1071
|
-
...props
|
|
1072
|
-
}) {
|
|
1073
|
-
return /* @__PURE__ */ jsx7("a", {
|
|
1074
|
-
href,
|
|
1075
|
-
target: "_blank",
|
|
1076
|
-
rel: "noopener noreferrer",
|
|
1077
|
-
className: cn6("inline-flex items-center gap-1 rounded-md bg-muted px-2 py-1 text-muted-foreground text-xs transition-colors hover:text-foreground", className),
|
|
1078
|
-
...props,
|
|
1079
|
-
children: children ?? /* @__PURE__ */ jsxs7(Fragment3, {
|
|
1080
|
-
children: [
|
|
1081
|
-
/* @__PURE__ */ jsx7(ExternalLink, {
|
|
1082
|
-
className: "h-3 w-3"
|
|
1083
|
-
}),
|
|
1084
|
-
title ?? href
|
|
1085
|
-
]
|
|
1086
|
-
})
|
|
1087
|
-
});
|
|
1088
|
-
}
|
|
1089
|
-
|
|
1090
|
-
// src/presentation/components/ToolResultRenderer.tsx
|
|
1091
|
-
import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1092
|
-
"use client";
|
|
1093
|
-
function isUIMessageLike(result) {
|
|
1094
|
-
return typeof result === "object" && result !== null && "parts" in result && Array.isArray(result.parts);
|
|
1095
|
-
}
|
|
1096
|
-
function isPresentationToolResult(result) {
|
|
1097
|
-
return typeof result === "object" && result !== null && "presentationKey" in result && typeof result.presentationKey === "string";
|
|
1098
|
-
}
|
|
1099
|
-
function isFormToolResult(result) {
|
|
1100
|
-
return typeof result === "object" && result !== null && "formKey" in result && typeof result.formKey === "string";
|
|
1101
|
-
}
|
|
1102
|
-
function isDataViewToolResult(result) {
|
|
1103
|
-
return typeof result === "object" && result !== null && "dataViewKey" in result && typeof result.dataViewKey === "string";
|
|
1104
|
-
}
|
|
1105
|
-
function UIMessagePartRenderer({
|
|
1106
|
-
part,
|
|
1107
|
-
presentationRenderer,
|
|
1108
|
-
formRenderer,
|
|
1109
|
-
dataViewRenderer,
|
|
1110
|
-
depth = 0
|
|
1111
|
-
}) {
|
|
1112
|
-
if (part === null || part === undefined)
|
|
1113
|
-
return null;
|
|
1114
|
-
const p = part;
|
|
1115
|
-
if (p.type === "text" && typeof p.text === "string") {
|
|
1116
|
-
return /* @__PURE__ */ jsx8("p", {
|
|
1117
|
-
className: "whitespace-pre-wrap text-sm",
|
|
1118
|
-
children: p.text
|
|
1119
|
-
}, depth);
|
|
1120
|
-
}
|
|
1121
|
-
if (p.type && String(p.type).startsWith("tool-") && p.output) {
|
|
1122
|
-
const output = p.output;
|
|
1123
|
-
if (isUIMessageLike(output)) {
|
|
1124
|
-
return /* @__PURE__ */ jsx8("div", {
|
|
1125
|
-
className: "ml-2 border-border border-l-2 pl-2",
|
|
1126
|
-
children: /* @__PURE__ */ jsx8(UIMessagePartsRenderer, {
|
|
1127
|
-
parts: output.parts ?? [],
|
|
1128
|
-
presentationRenderer,
|
|
1129
|
-
formRenderer,
|
|
1130
|
-
dataViewRenderer,
|
|
1131
|
-
depth: depth + 1
|
|
1132
|
-
})
|
|
1133
|
-
}, depth);
|
|
1134
|
-
}
|
|
1135
|
-
return /* @__PURE__ */ jsx8("pre", {
|
|
1136
|
-
className: "overflow-x-auto rounded bg-background p-2 text-xs",
|
|
1137
|
-
children: typeof output === "object" ? JSON.stringify(output, null, 2) : String(output)
|
|
1138
|
-
}, depth);
|
|
1139
|
-
}
|
|
1140
|
-
return null;
|
|
1141
|
-
}
|
|
1142
|
-
function UIMessagePartsRenderer({
|
|
1143
|
-
parts,
|
|
1144
|
-
presentationRenderer,
|
|
1145
|
-
formRenderer,
|
|
1146
|
-
dataViewRenderer,
|
|
1147
|
-
depth = 0
|
|
1148
|
-
}) {
|
|
1149
|
-
if (parts.length === 0)
|
|
1150
|
-
return null;
|
|
1151
|
-
return /* @__PURE__ */ jsx8("div", {
|
|
1152
|
-
className: "space-y-2",
|
|
1153
|
-
children: parts.map((part, i) => /* @__PURE__ */ jsx8(UIMessagePartRenderer, {
|
|
1154
|
-
part,
|
|
1155
|
-
presentationRenderer,
|
|
1156
|
-
formRenderer,
|
|
1157
|
-
dataViewRenderer,
|
|
1158
|
-
depth
|
|
1159
|
-
}, `${depth}-${i}`))
|
|
1160
|
-
});
|
|
1161
|
-
}
|
|
1162
|
-
function ToolResultRenderer({
|
|
1163
|
-
toolName: _toolName,
|
|
1164
|
-
result,
|
|
1165
|
-
presentationRenderer,
|
|
1166
|
-
formRenderer,
|
|
1167
|
-
dataViewRenderer,
|
|
1168
|
-
showRawFallback = true,
|
|
1169
|
-
renderNestedUIMessage = true
|
|
1170
|
-
}) {
|
|
1171
|
-
if (result === undefined || result === null) {
|
|
1172
|
-
return null;
|
|
1173
|
-
}
|
|
1174
|
-
if (renderNestedUIMessage && isUIMessageLike(result) && (result.parts?.length ?? 0) > 0) {
|
|
1175
|
-
return /* @__PURE__ */ jsx8("div", {
|
|
1176
|
-
className: "mt-2 rounded-md border border-border bg-background/50 p-3",
|
|
1177
|
-
children: /* @__PURE__ */ jsx8(UIMessagePartsRenderer, {
|
|
1178
|
-
parts: result.parts ?? [],
|
|
1179
|
-
presentationRenderer,
|
|
1180
|
-
formRenderer,
|
|
1181
|
-
dataViewRenderer
|
|
1182
|
-
})
|
|
1183
|
-
});
|
|
1184
|
-
}
|
|
1185
|
-
if (isPresentationToolResult(result) && presentationRenderer) {
|
|
1186
|
-
const rendered = presentationRenderer(result.presentationKey, result.data);
|
|
1187
|
-
if (rendered != null) {
|
|
1188
|
-
return /* @__PURE__ */ jsx8("div", {
|
|
1189
|
-
className: "mt-2 rounded-md border border-border bg-background/50 p-3",
|
|
1190
|
-
children: rendered
|
|
1191
|
-
});
|
|
1192
|
-
}
|
|
1193
|
-
}
|
|
1194
|
-
if (isFormToolResult(result) && formRenderer) {
|
|
1195
|
-
const rendered = formRenderer(result.formKey, result.defaultValues);
|
|
1196
|
-
if (rendered != null) {
|
|
1197
|
-
return /* @__PURE__ */ jsx8("div", {
|
|
1198
|
-
className: "mt-2 rounded-md border border-border bg-background/50 p-3",
|
|
1199
|
-
children: rendered
|
|
1200
|
-
});
|
|
1201
|
-
}
|
|
1202
|
-
}
|
|
1203
|
-
if (isDataViewToolResult(result) && dataViewRenderer) {
|
|
1204
|
-
const rendered = dataViewRenderer(result.dataViewKey, result.items);
|
|
1205
|
-
if (rendered != null) {
|
|
1206
|
-
return /* @__PURE__ */ jsx8("div", {
|
|
1207
|
-
className: "mt-2 rounded-md border border-border bg-background/50 p-3",
|
|
1208
|
-
children: rendered
|
|
1209
|
-
});
|
|
1210
|
-
}
|
|
1211
|
-
}
|
|
1212
|
-
if (!showRawFallback) {
|
|
1213
|
-
return null;
|
|
1214
|
-
}
|
|
1215
|
-
return /* @__PURE__ */ jsxs8("div", {
|
|
1216
|
-
children: [
|
|
1217
|
-
/* @__PURE__ */ jsx8("span", {
|
|
1218
|
-
className: "font-medium text-muted-foreground",
|
|
1219
|
-
children: "Output:"
|
|
1220
|
-
}),
|
|
1221
|
-
/* @__PURE__ */ jsx8("pre", {
|
|
1222
|
-
className: "mt-1 overflow-x-auto rounded bg-background p-2 text-xs",
|
|
1223
|
-
children: typeof result === "object" ? JSON.stringify(result, null, 2) : String(result)
|
|
1224
|
-
})
|
|
1225
|
-
]
|
|
1226
|
-
});
|
|
1227
|
-
}
|
|
1228
|
-
|
|
1229
|
-
// src/presentation/components/ChatMessage.tsx
|
|
1230
|
-
import { jsx as jsx9, jsxs as jsxs9, Fragment as Fragment4 } from "react/jsx-runtime";
|
|
1231
|
-
"use client";
|
|
1232
|
-
function extractCodeBlocks(content) {
|
|
1233
|
-
const codeBlockRegex = /```(\w+)?\n([\s\S]*?)```/g;
|
|
1234
|
-
const blocks = [];
|
|
1235
|
-
let match;
|
|
1236
|
-
while ((match = codeBlockRegex.exec(content)) !== null) {
|
|
1237
|
-
blocks.push({
|
|
1238
|
-
language: match[1] ?? "text",
|
|
1239
|
-
code: match[2] ?? "",
|
|
1240
|
-
raw: match[0]
|
|
1241
|
-
});
|
|
1242
|
-
}
|
|
1243
|
-
return blocks;
|
|
1244
|
-
}
|
|
1245
|
-
function renderInlineMarkdown(text) {
|
|
1246
|
-
const linkRegex = /\[([^\]]+)\]\(([^)]+)\)/g;
|
|
1247
|
-
const parts = [];
|
|
1248
|
-
let lastIndex = 0;
|
|
1249
|
-
let match;
|
|
1250
|
-
let key = 0;
|
|
1251
|
-
while ((match = linkRegex.exec(text)) !== null) {
|
|
1252
|
-
if (match.index > lastIndex) {
|
|
1253
|
-
parts.push(/* @__PURE__ */ jsx9("span", {
|
|
1254
|
-
children: text.slice(lastIndex, match.index)
|
|
1255
|
-
}, key++));
|
|
1256
|
-
}
|
|
1257
|
-
parts.push(/* @__PURE__ */ jsx9("a", {
|
|
1258
|
-
href: match[2],
|
|
1259
|
-
target: "_blank",
|
|
1260
|
-
rel: "noopener noreferrer",
|
|
1261
|
-
className: "text-primary underline hover:no-underline",
|
|
1262
|
-
children: match[1]
|
|
1263
|
-
}, key++));
|
|
1264
|
-
lastIndex = match.index + match[0].length;
|
|
1265
|
-
}
|
|
1266
|
-
if (lastIndex < text.length) {
|
|
1267
|
-
parts.push(/* @__PURE__ */ jsx9("span", {
|
|
1268
|
-
children: text.slice(lastIndex)
|
|
1269
|
-
}, key++));
|
|
1270
|
-
}
|
|
1271
|
-
return parts.length > 0 ? parts : [text];
|
|
1272
|
-
}
|
|
1273
|
-
function MessageContent({ content }) {
|
|
1274
|
-
const codeBlocks = extractCodeBlocks(content);
|
|
1275
|
-
if (codeBlocks.length === 0) {
|
|
1276
|
-
return /* @__PURE__ */ jsx9("p", {
|
|
1277
|
-
className: "whitespace-pre-wrap",
|
|
1278
|
-
children: renderInlineMarkdown(content)
|
|
1279
|
-
});
|
|
1280
|
-
}
|
|
1281
|
-
let remaining = content;
|
|
1282
|
-
const parts = [];
|
|
1283
|
-
let key = 0;
|
|
1284
|
-
for (const block of codeBlocks) {
|
|
1285
|
-
const [before, after] = remaining.split(block.raw);
|
|
1286
|
-
if (before) {
|
|
1287
|
-
parts.push(/* @__PURE__ */ jsx9("p", {
|
|
1288
|
-
className: "whitespace-pre-wrap",
|
|
1289
|
-
children: renderInlineMarkdown(before.trim())
|
|
1290
|
-
}, key++));
|
|
1291
|
-
}
|
|
1292
|
-
parts.push(/* @__PURE__ */ jsx9(CodePreview, {
|
|
1293
|
-
code: block.code,
|
|
1294
|
-
language: block.language,
|
|
1295
|
-
className: "my-2"
|
|
1296
|
-
}, key++));
|
|
1297
|
-
remaining = after ?? "";
|
|
1298
|
-
}
|
|
1299
|
-
if (remaining.trim()) {
|
|
1300
|
-
parts.push(/* @__PURE__ */ jsx9("p", {
|
|
1301
|
-
className: "whitespace-pre-wrap",
|
|
1302
|
-
children: renderInlineMarkdown(remaining.trim())
|
|
1303
|
-
}, key++));
|
|
1304
|
-
}
|
|
1305
|
-
return /* @__PURE__ */ jsx9(Fragment4, {
|
|
1306
|
-
children: parts
|
|
1307
|
-
});
|
|
1308
|
-
}
|
|
1309
|
-
function toolStatusToCotStatus(status) {
|
|
1310
|
-
if (status === "completed")
|
|
1311
|
-
return "complete";
|
|
1312
|
-
if (status === "running")
|
|
1313
|
-
return "active";
|
|
1314
|
-
return "pending";
|
|
1315
|
-
}
|
|
1316
|
-
function ChatMessage({
|
|
1317
|
-
message,
|
|
1318
|
-
className,
|
|
1319
|
-
showCopy = true,
|
|
1320
|
-
showAvatar = true,
|
|
1321
|
-
selectable = false,
|
|
1322
|
-
selected = false,
|
|
1323
|
-
onSelect,
|
|
1324
|
-
editable = false,
|
|
1325
|
-
onEdit,
|
|
1326
|
-
presentationRenderer,
|
|
1327
|
-
formRenderer,
|
|
1328
|
-
dataViewRenderer,
|
|
1329
|
-
components: comps
|
|
1330
|
-
}) {
|
|
1331
|
-
const [copied, setCopied] = React6.useState(false);
|
|
1332
|
-
const isUser = message.role === "user";
|
|
1333
|
-
const isError = message.status === "error";
|
|
1334
|
-
const isStreaming = message.status === "streaming";
|
|
1335
|
-
const handleCopy = React6.useCallback(async () => {
|
|
1336
|
-
await navigator.clipboard.writeText(message.content);
|
|
1337
|
-
setCopied(true);
|
|
1338
|
-
setTimeout(() => setCopied(false), 2000);
|
|
1339
|
-
}, [message.content]);
|
|
1340
|
-
const handleSelectChange = React6.useCallback((checked) => {
|
|
1341
|
-
if (checked !== "indeterminate")
|
|
1342
|
-
onSelect?.(message.id);
|
|
1343
|
-
}, [message.id, onSelect]);
|
|
1344
|
-
const [isEditing, setIsEditing] = React6.useState(false);
|
|
1345
|
-
const [editContent, setEditContent] = React6.useState(message.content);
|
|
1346
|
-
const editTextareaRef = React6.useRef(null);
|
|
1347
|
-
React6.useEffect(() => {
|
|
1348
|
-
setEditContent(message.content);
|
|
1349
|
-
}, [message.content]);
|
|
1350
|
-
React6.useEffect(() => {
|
|
1351
|
-
if (isEditing) {
|
|
1352
|
-
editTextareaRef.current?.focus();
|
|
1353
|
-
}
|
|
1354
|
-
}, [isEditing]);
|
|
1355
|
-
const handleStartEdit = React6.useCallback(() => {
|
|
1356
|
-
setEditContent(message.content);
|
|
1357
|
-
setIsEditing(true);
|
|
1358
|
-
}, [message.content]);
|
|
1359
|
-
const handleSaveEdit = React6.useCallback(async () => {
|
|
1360
|
-
const trimmed = editContent.trim();
|
|
1361
|
-
if (trimmed !== message.content) {
|
|
1362
|
-
await onEdit?.(message.id, trimmed);
|
|
1363
|
-
}
|
|
1364
|
-
setIsEditing(false);
|
|
1365
|
-
}, [editContent, message.id, message.content, onEdit]);
|
|
1366
|
-
const handleCancelEdit = React6.useCallback(() => {
|
|
1367
|
-
setEditContent(message.content);
|
|
1368
|
-
setIsEditing(false);
|
|
1369
|
-
}, [message.content]);
|
|
1370
|
-
return /* @__PURE__ */ jsxs9("div", {
|
|
1371
|
-
className: cn7("group flex gap-3", isUser && "flex-row-reverse", className),
|
|
1372
|
-
children: [
|
|
1373
|
-
selectable && /* @__PURE__ */ jsx9("div", {
|
|
1374
|
-
className: cn7("flex shrink-0 items-start pt-1", "opacity-0 transition-opacity group-hover:opacity-100"),
|
|
1375
|
-
children: /* @__PURE__ */ jsx9(Checkbox, {
|
|
1376
|
-
checked: selected,
|
|
1377
|
-
onCheckedChange: handleSelectChange,
|
|
1378
|
-
"aria-label": selected ? "Deselect message" : "Select message"
|
|
1379
|
-
})
|
|
1380
|
-
}),
|
|
1381
|
-
showAvatar && /* @__PURE__ */ jsx9(Avatar, {
|
|
1382
|
-
className: "h-8 w-8 shrink-0",
|
|
1383
|
-
children: /* @__PURE__ */ jsx9(AvatarFallback, {
|
|
1384
|
-
className: cn7(isUser ? "bg-primary text-primary-foreground" : "bg-muted"),
|
|
1385
|
-
children: isUser ? /* @__PURE__ */ jsx9(User, {
|
|
1386
|
-
className: "h-4 w-4"
|
|
1387
|
-
}) : /* @__PURE__ */ jsx9(Bot, {
|
|
1388
|
-
className: "h-4 w-4"
|
|
1389
|
-
})
|
|
1390
|
-
})
|
|
1391
|
-
}),
|
|
1392
|
-
/* @__PURE__ */ jsxs9("div", {
|
|
1393
|
-
className: cn7("flex max-w-[80%] flex-col gap-1", isUser && "items-end"),
|
|
1394
|
-
children: [
|
|
1395
|
-
/* @__PURE__ */ jsx9("div", {
|
|
1396
|
-
className: cn7("rounded-2xl px-4 py-2", isUser ? "bg-primary text-primary-foreground" : "bg-muted text-foreground", isError && "border border-destructive bg-destructive/10"),
|
|
1397
|
-
children: isError && message.error ? /* @__PURE__ */ jsxs9("div", {
|
|
1398
|
-
className: "flex items-start gap-2",
|
|
1399
|
-
children: [
|
|
1400
|
-
/* @__PURE__ */ jsx9(AlertCircle, {
|
|
1401
|
-
className: "mt-0.5 h-4 w-4 shrink-0 text-destructive"
|
|
1402
|
-
}),
|
|
1403
|
-
/* @__PURE__ */ jsxs9("div", {
|
|
1404
|
-
children: [
|
|
1405
|
-
/* @__PURE__ */ jsx9("p", {
|
|
1406
|
-
className: "font-medium text-destructive",
|
|
1407
|
-
children: message.error.code
|
|
1408
|
-
}),
|
|
1409
|
-
/* @__PURE__ */ jsx9("p", {
|
|
1410
|
-
className: "text-muted-foreground text-sm",
|
|
1411
|
-
children: message.error.message
|
|
1412
|
-
})
|
|
1413
|
-
]
|
|
1414
|
-
})
|
|
1415
|
-
]
|
|
1416
|
-
}) : isEditing ? /* @__PURE__ */ jsxs9("div", {
|
|
1417
|
-
className: "flex flex-col gap-2",
|
|
1418
|
-
children: [
|
|
1419
|
-
/* @__PURE__ */ jsx9("textarea", {
|
|
1420
|
-
ref: editTextareaRef,
|
|
1421
|
-
value: editContent,
|
|
1422
|
-
onChange: (e) => setEditContent(e.target.value),
|
|
1423
|
-
className: "min-h-[80px] w-full resize-y rounded-md border bg-background/50 px-3 py-2 text-sm",
|
|
1424
|
-
rows: 4,
|
|
1425
|
-
"aria-label": "Edit message"
|
|
1426
|
-
}),
|
|
1427
|
-
/* @__PURE__ */ jsxs9("div", {
|
|
1428
|
-
className: "flex gap-2",
|
|
1429
|
-
children: [
|
|
1430
|
-
/* @__PURE__ */ jsxs9(Button4, {
|
|
1431
|
-
variant: "default",
|
|
1432
|
-
size: "sm",
|
|
1433
|
-
onPress: handleSaveEdit,
|
|
1434
|
-
"aria-label": "Save edit",
|
|
1435
|
-
children: [
|
|
1436
|
-
/* @__PURE__ */ jsx9(Check3, {
|
|
1437
|
-
className: "h-3 w-3"
|
|
1438
|
-
}),
|
|
1439
|
-
"Save"
|
|
1440
|
-
]
|
|
1441
|
-
}),
|
|
1442
|
-
/* @__PURE__ */ jsxs9(Button4, {
|
|
1443
|
-
variant: "ghost",
|
|
1444
|
-
size: "sm",
|
|
1445
|
-
onPress: handleCancelEdit,
|
|
1446
|
-
"aria-label": "Cancel edit",
|
|
1447
|
-
children: [
|
|
1448
|
-
/* @__PURE__ */ jsx9(X2, {
|
|
1449
|
-
className: "h-3 w-3"
|
|
1450
|
-
}),
|
|
1451
|
-
"Cancel"
|
|
1452
|
-
]
|
|
1453
|
-
})
|
|
1454
|
-
]
|
|
1455
|
-
})
|
|
1456
|
-
]
|
|
1457
|
-
}) : isStreaming && !message.content ? /* @__PURE__ */ jsxs9("div", {
|
|
1458
|
-
className: "flex flex-col gap-2",
|
|
1459
|
-
children: [
|
|
1460
|
-
/* @__PURE__ */ jsx9(Skeleton, {
|
|
1461
|
-
className: "h-4 w-48"
|
|
1462
|
-
}),
|
|
1463
|
-
/* @__PURE__ */ jsx9(Skeleton, {
|
|
1464
|
-
className: "h-4 w-32"
|
|
1465
|
-
})
|
|
1466
|
-
]
|
|
1467
|
-
}) : /* @__PURE__ */ jsx9(MessageContent, {
|
|
1468
|
-
content: message.content
|
|
1469
|
-
})
|
|
1470
|
-
}),
|
|
1471
|
-
/* @__PURE__ */ jsxs9("div", {
|
|
1472
|
-
className: cn7("flex items-center gap-2 text-xs", "text-muted-foreground opacity-0 transition-opacity", "group-hover:opacity-100"),
|
|
1473
|
-
children: [
|
|
1474
|
-
/* @__PURE__ */ jsx9("span", {
|
|
1475
|
-
children: new Date(message.createdAt).toLocaleTimeString([], {
|
|
1476
|
-
hour: "2-digit",
|
|
1477
|
-
minute: "2-digit"
|
|
1478
|
-
})
|
|
1479
|
-
}),
|
|
1480
|
-
message.usage && /* @__PURE__ */ jsxs9("span", {
|
|
1481
|
-
children: [
|
|
1482
|
-
message.usage.inputTokens + message.usage.outputTokens,
|
|
1483
|
-
" tokens"
|
|
1484
|
-
]
|
|
1485
|
-
}),
|
|
1486
|
-
showCopy && !isUser && message.content && /* @__PURE__ */ jsx9(Button4, {
|
|
1487
|
-
variant: "ghost",
|
|
1488
|
-
size: "sm",
|
|
1489
|
-
className: "h-6 w-6 p-0",
|
|
1490
|
-
onPress: handleCopy,
|
|
1491
|
-
"aria-label": copied ? "Copied" : "Copy message",
|
|
1492
|
-
children: copied ? /* @__PURE__ */ jsx9(Check3, {
|
|
1493
|
-
className: "h-3 w-3"
|
|
1494
|
-
}) : /* @__PURE__ */ jsx9(Copy3, {
|
|
1495
|
-
className: "h-3 w-3"
|
|
1496
|
-
})
|
|
1497
|
-
}),
|
|
1498
|
-
editable && isUser && !isEditing && /* @__PURE__ */ jsx9(Button4, {
|
|
1499
|
-
variant: "ghost",
|
|
1500
|
-
size: "sm",
|
|
1501
|
-
className: "h-6 w-6 p-0",
|
|
1502
|
-
onPress: handleStartEdit,
|
|
1503
|
-
"aria-label": "Edit message",
|
|
1504
|
-
children: /* @__PURE__ */ jsx9(Pencil, {
|
|
1505
|
-
className: "h-3 w-3"
|
|
1506
|
-
})
|
|
1507
|
-
})
|
|
1508
|
-
]
|
|
1509
|
-
}),
|
|
1510
|
-
message.reasoning && (comps?.Reasoning ? /* @__PURE__ */ jsx9(comps.Reasoning, {
|
|
1511
|
-
isStreaming: isStreaming && !!message.reasoning,
|
|
1512
|
-
children: message.reasoning
|
|
1513
|
-
}) : /* @__PURE__ */ jsxs9(Reasoning, {
|
|
1514
|
-
isStreaming: isStreaming && !!message.reasoning,
|
|
1515
|
-
className: "mt-2",
|
|
1516
|
-
children: [
|
|
1517
|
-
/* @__PURE__ */ jsx9(ReasoningTrigger, {
|
|
1518
|
-
isStreaming
|
|
1519
|
-
}),
|
|
1520
|
-
/* @__PURE__ */ jsx9(ReasoningContent, {
|
|
1521
|
-
children: message.reasoning
|
|
1522
|
-
})
|
|
1523
|
-
]
|
|
1524
|
-
})),
|
|
1525
|
-
message.sources && message.sources.length > 0 && (() => {
|
|
1526
|
-
const SourcesComp = comps?.Sources;
|
|
1527
|
-
const SourcesTriggerComp = comps?.SourcesTrigger;
|
|
1528
|
-
const SourceComp = comps?.Source;
|
|
1529
|
-
if (SourcesComp && SourcesTriggerComp && SourceComp) {
|
|
1530
|
-
return /* @__PURE__ */ jsxs9(SourcesComp, {
|
|
1531
|
-
children: [
|
|
1532
|
-
/* @__PURE__ */ jsx9(SourcesTriggerComp, {
|
|
1533
|
-
count: message.sources.length
|
|
1534
|
-
}),
|
|
1535
|
-
message.sources.map((source) => /* @__PURE__ */ jsx9(SourceComp, {
|
|
1536
|
-
href: source.url ?? "#",
|
|
1537
|
-
title: source.title || source.url || source.id
|
|
1538
|
-
}, source.id))
|
|
1539
|
-
]
|
|
1540
|
-
});
|
|
1541
|
-
}
|
|
1542
|
-
return /* @__PURE__ */ jsxs9(Sources, {
|
|
1543
|
-
className: "mt-2",
|
|
1544
|
-
children: [
|
|
1545
|
-
/* @__PURE__ */ jsx9(SourcesTrigger, {
|
|
1546
|
-
count: message.sources.length
|
|
1547
|
-
}),
|
|
1548
|
-
/* @__PURE__ */ jsx9(SourcesContent, {
|
|
1549
|
-
children: message.sources.map((source) => /* @__PURE__ */ jsx9(Source, {
|
|
1550
|
-
href: source.url ?? "#",
|
|
1551
|
-
title: source.title || source.url || source.id
|
|
1552
|
-
}, source.id))
|
|
1553
|
-
})
|
|
1554
|
-
]
|
|
1555
|
-
});
|
|
1556
|
-
})(),
|
|
1557
|
-
message.toolCalls && message.toolCalls.length > 0 && (() => {
|
|
1558
|
-
const CotComp = comps?.ChainOfThought;
|
|
1559
|
-
const CotStepComp = comps?.ChainOfThoughtStep;
|
|
1560
|
-
if (CotComp && CotStepComp) {
|
|
1561
|
-
return /* @__PURE__ */ jsx9(CotComp, {
|
|
1562
|
-
defaultOpen: false,
|
|
1563
|
-
className: "mt-2",
|
|
1564
|
-
children: message.toolCalls.map((tc) => /* @__PURE__ */ jsxs9(CotStepComp, {
|
|
1565
|
-
label: tc.name,
|
|
1566
|
-
description: Object.keys(tc.args).length > 0 ? `Input: ${JSON.stringify(tc.args)}` : undefined,
|
|
1567
|
-
status: toolStatusToCotStatus(tc.status),
|
|
1568
|
-
children: [
|
|
1569
|
-
tc.preliminary && tc.status === "running" && /* @__PURE__ */ jsx9("p", {
|
|
1570
|
-
className: "mt-1 text-muted-foreground text-xs",
|
|
1571
|
-
children: "Running\u2026"
|
|
1572
|
-
}),
|
|
1573
|
-
(tc.result !== undefined || tc.nestedParts?.length) && /* @__PURE__ */ jsx9(ToolResultRenderer, {
|
|
1574
|
-
toolName: tc.name,
|
|
1575
|
-
result: tc.nestedParts?.length ? { parts: tc.nestedParts } : tc.result,
|
|
1576
|
-
presentationRenderer,
|
|
1577
|
-
formRenderer,
|
|
1578
|
-
dataViewRenderer,
|
|
1579
|
-
showRawFallback: true
|
|
1580
|
-
}),
|
|
1581
|
-
tc.error && /* @__PURE__ */ jsx9("p", {
|
|
1582
|
-
className: "mt-1 text-destructive text-xs",
|
|
1583
|
-
children: tc.error
|
|
1584
|
-
})
|
|
1585
|
-
]
|
|
1586
|
-
}, tc.id))
|
|
1587
|
-
});
|
|
1588
|
-
}
|
|
1589
|
-
return /* @__PURE__ */ jsx9("div", {
|
|
1590
|
-
className: "mt-2 space-y-2",
|
|
1591
|
-
children: message.toolCalls.map((tc) => /* @__PURE__ */ jsxs9("details", {
|
|
1592
|
-
className: "rounded-md border border-border bg-muted",
|
|
1593
|
-
children: [
|
|
1594
|
-
/* @__PURE__ */ jsxs9("summary", {
|
|
1595
|
-
className: "flex cursor-pointer items-center gap-2 px-3 py-2 font-medium text-sm",
|
|
1596
|
-
children: [
|
|
1597
|
-
/* @__PURE__ */ jsx9(Wrench, {
|
|
1598
|
-
className: "h-4 w-4 text-muted-foreground"
|
|
1599
|
-
}),
|
|
1600
|
-
tc.name,
|
|
1601
|
-
/* @__PURE__ */ jsx9("span", {
|
|
1602
|
-
className: cn7("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"),
|
|
1603
|
-
children: tc.status
|
|
1604
|
-
})
|
|
1605
|
-
]
|
|
1606
|
-
}),
|
|
1607
|
-
/* @__PURE__ */ jsxs9("div", {
|
|
1608
|
-
className: "border-border border-t px-3 py-2 text-xs",
|
|
1609
|
-
children: [
|
|
1610
|
-
Object.keys(tc.args).length > 0 && /* @__PURE__ */ jsxs9("div", {
|
|
1611
|
-
className: "mb-2",
|
|
1612
|
-
children: [
|
|
1613
|
-
/* @__PURE__ */ jsx9("span", {
|
|
1614
|
-
className: "font-medium text-muted-foreground",
|
|
1615
|
-
children: "Input:"
|
|
1616
|
-
}),
|
|
1617
|
-
/* @__PURE__ */ jsx9("pre", {
|
|
1618
|
-
className: "mt-1 overflow-x-auto rounded bg-background p-2",
|
|
1619
|
-
children: JSON.stringify(tc.args, null, 2)
|
|
1620
|
-
})
|
|
1621
|
-
]
|
|
1622
|
-
}),
|
|
1623
|
-
tc.preliminary && tc.status === "running" && /* @__PURE__ */ jsx9("p", {
|
|
1624
|
-
className: "mt-1 text-muted-foreground text-xs",
|
|
1625
|
-
children: "Running\u2026"
|
|
1626
|
-
}),
|
|
1627
|
-
(tc.result !== undefined || tc.nestedParts?.length) && /* @__PURE__ */ jsx9(ToolResultRenderer, {
|
|
1628
|
-
toolName: tc.name,
|
|
1629
|
-
result: tc.nestedParts?.length ? { parts: tc.nestedParts } : tc.result,
|
|
1630
|
-
presentationRenderer,
|
|
1631
|
-
formRenderer,
|
|
1632
|
-
dataViewRenderer,
|
|
1633
|
-
showRawFallback: true
|
|
1634
|
-
}),
|
|
1635
|
-
tc.error && /* @__PURE__ */ jsx9("p", {
|
|
1636
|
-
className: "mt-1 text-destructive",
|
|
1637
|
-
children: tc.error
|
|
1638
|
-
})
|
|
1639
|
-
]
|
|
1640
|
-
})
|
|
1641
|
-
]
|
|
1642
|
-
}, tc.id))
|
|
1643
|
-
});
|
|
1644
|
-
})()
|
|
1645
|
-
]
|
|
1646
|
-
})
|
|
1647
|
-
]
|
|
1648
|
-
});
|
|
1649
|
-
}
|
|
1650
|
-
// src/presentation/components/ChatSidebar.tsx
|
|
1651
|
-
import { Button as Button5 } from "@contractspec/lib.design-system";
|
|
1652
|
-
import { cn as cn8 } from "@contractspec/lib.ui-kit-web/ui/utils";
|
|
1653
|
-
import { MessageSquare, Plus as Plus2, Trash2 } from "lucide-react";
|
|
1654
|
-
import * as React8 from "react";
|
|
1655
|
-
|
|
1656
|
-
// src/presentation/hooks/useConversations.ts
|
|
1657
|
-
import * as React7 from "react";
|
|
1658
|
-
"use client";
|
|
1659
|
-
function useConversations(options) {
|
|
1660
|
-
const { store, projectId, tags, limit = 50 } = options;
|
|
1661
|
-
const [conversations, setConversations] = React7.useState([]);
|
|
1662
|
-
const [isLoading, setIsLoading] = React7.useState(true);
|
|
1663
|
-
const refresh = React7.useCallback(async () => {
|
|
1664
|
-
setIsLoading(true);
|
|
1665
|
-
try {
|
|
1666
|
-
const list = await store.list({
|
|
1667
|
-
status: "active",
|
|
1668
|
-
projectId,
|
|
1669
|
-
tags,
|
|
1670
|
-
limit
|
|
1671
|
-
});
|
|
1672
|
-
setConversations(list);
|
|
1673
|
-
} finally {
|
|
1674
|
-
setIsLoading(false);
|
|
1675
|
-
}
|
|
1676
|
-
}, [store, projectId, tags, limit]);
|
|
1677
|
-
React7.useEffect(() => {
|
|
1678
|
-
refresh();
|
|
1679
|
-
}, [refresh]);
|
|
1680
|
-
const deleteConversation = React7.useCallback(async (id) => {
|
|
1681
|
-
const ok = await store.delete(id);
|
|
1682
|
-
if (ok) {
|
|
1683
|
-
setConversations((prev) => prev.filter((c) => c.id !== id));
|
|
1684
|
-
}
|
|
1685
|
-
return ok;
|
|
1686
|
-
}, [store]);
|
|
1687
|
-
return {
|
|
1688
|
-
conversations,
|
|
1689
|
-
isLoading,
|
|
1690
|
-
refresh,
|
|
1691
|
-
deleteConversation
|
|
1692
|
-
};
|
|
1693
|
-
}
|
|
1694
|
-
|
|
1695
|
-
// src/presentation/components/ChatSidebar.tsx
|
|
1696
|
-
import { jsx as jsx10, jsxs as jsxs10, Fragment as Fragment5 } from "react/jsx-runtime";
|
|
1697
|
-
"use client";
|
|
1698
|
-
function formatDate(date) {
|
|
1699
|
-
const d = new Date(date);
|
|
1700
|
-
const now = new Date;
|
|
1701
|
-
const diff = now.getTime() - d.getTime();
|
|
1702
|
-
if (diff < 86400000) {
|
|
1703
|
-
return d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
|
|
1704
|
-
}
|
|
1705
|
-
if (diff < 604800000) {
|
|
1706
|
-
return d.toLocaleDateString([], { weekday: "short" });
|
|
1707
|
-
}
|
|
1708
|
-
return d.toLocaleDateString([], { month: "short", day: "numeric" });
|
|
1709
|
-
}
|
|
1710
|
-
function ConversationItem({
|
|
1711
|
-
conversation,
|
|
1712
|
-
selected,
|
|
1713
|
-
onSelect,
|
|
1714
|
-
onDelete
|
|
1715
|
-
}) {
|
|
1716
|
-
const title = conversation.title ?? conversation.messages[0]?.content?.slice(0, 50) ?? "New chat";
|
|
1717
|
-
const displayTitle = title.length > 40 ? `${title.slice(0, 40)}\u2026` : title;
|
|
1718
|
-
return /* @__PURE__ */ jsxs10("div", {
|
|
1719
|
-
role: "button",
|
|
1720
|
-
tabIndex: 0,
|
|
1721
|
-
onClick: onSelect,
|
|
1722
|
-
onKeyDown: (e) => {
|
|
1723
|
-
if (e.key === "Enter" || e.key === " ") {
|
|
1724
|
-
e.preventDefault();
|
|
1725
|
-
onSelect();
|
|
1726
|
-
}
|
|
1727
|
-
},
|
|
1728
|
-
className: cn8("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"),
|
|
1729
|
-
children: [
|
|
1730
|
-
/* @__PURE__ */ jsx10(MessageSquare, {
|
|
1731
|
-
className: "h-4 w-4 shrink-0 text-muted-foreground"
|
|
1732
|
-
}),
|
|
1733
|
-
/* @__PURE__ */ jsxs10("div", {
|
|
1734
|
-
className: "min-w-0 flex-1",
|
|
1735
|
-
children: [
|
|
1736
|
-
/* @__PURE__ */ jsx10("p", {
|
|
1737
|
-
className: "truncate",
|
|
1738
|
-
children: displayTitle
|
|
1739
|
-
}),
|
|
1740
|
-
/* @__PURE__ */ jsxs10("p", {
|
|
1741
|
-
className: "text-muted-foreground text-xs",
|
|
1742
|
-
children: [
|
|
1743
|
-
formatDate(conversation.updatedAt),
|
|
1744
|
-
conversation.projectName && ` \xB7 ${conversation.projectName}`,
|
|
1745
|
-
conversation.tags && conversation.tags.length > 0 && /* @__PURE__ */ jsxs10(Fragment5, {
|
|
1746
|
-
children: [
|
|
1747
|
-
" \xB7 ",
|
|
1748
|
-
conversation.tags.slice(0, 2).join(", ")
|
|
1749
|
-
]
|
|
1750
|
-
})
|
|
1751
|
-
]
|
|
1752
|
-
})
|
|
1753
|
-
]
|
|
1754
|
-
}),
|
|
1755
|
-
/* @__PURE__ */ jsx10("span", {
|
|
1756
|
-
role: "group",
|
|
1757
|
-
onClick: (e) => e.stopPropagation(),
|
|
1758
|
-
onKeyDown: (e) => e.stopPropagation(),
|
|
1759
|
-
children: /* @__PURE__ */ jsx10(Button5, {
|
|
1760
|
-
variant: "ghost",
|
|
1761
|
-
size: "sm",
|
|
1762
|
-
className: "h-6 w-6 shrink-0 p-0 opacity-0 group-hover:opacity-100",
|
|
1763
|
-
onPress: onDelete,
|
|
1764
|
-
"aria-label": "Delete conversation",
|
|
1765
|
-
children: /* @__PURE__ */ jsx10(Trash2, {
|
|
1766
|
-
className: "h-3 w-3"
|
|
1767
|
-
})
|
|
1768
|
-
})
|
|
1769
|
-
})
|
|
1770
|
-
]
|
|
1771
|
-
});
|
|
1772
|
-
}
|
|
1773
|
-
function ChatSidebar({
|
|
1774
|
-
store,
|
|
1775
|
-
selectedConversationId,
|
|
1776
|
-
onSelectConversation,
|
|
1777
|
-
onCreateNew,
|
|
1778
|
-
projectId,
|
|
1779
|
-
tags,
|
|
1780
|
-
limit = 50,
|
|
1781
|
-
className,
|
|
1782
|
-
collapsed = false,
|
|
1783
|
-
onUpdateConversation,
|
|
1784
|
-
selectedConversation
|
|
1785
|
-
}) {
|
|
1786
|
-
const { conversations, isLoading, deleteConversation } = useConversations({
|
|
1787
|
-
store,
|
|
1788
|
-
projectId,
|
|
1789
|
-
tags,
|
|
1790
|
-
limit
|
|
1791
|
-
});
|
|
1792
|
-
const handleDelete = React8.useCallback(async (id) => {
|
|
1793
|
-
const ok = await deleteConversation(id);
|
|
1794
|
-
if (ok && selectedConversationId === id) {
|
|
1795
|
-
onSelectConversation(null);
|
|
1796
|
-
}
|
|
1797
|
-
}, [deleteConversation, selectedConversationId, onSelectConversation]);
|
|
1798
|
-
if (collapsed)
|
|
1799
|
-
return null;
|
|
1800
|
-
return /* @__PURE__ */ jsxs10("div", {
|
|
1801
|
-
className: cn8("flex w-64 shrink-0 flex-col border-border border-r", className),
|
|
1802
|
-
children: [
|
|
1803
|
-
/* @__PURE__ */ jsxs10("div", {
|
|
1804
|
-
className: "flex shrink-0 items-center justify-between border-border border-b p-2",
|
|
1805
|
-
children: [
|
|
1806
|
-
/* @__PURE__ */ jsx10("span", {
|
|
1807
|
-
className: "font-medium text-muted-foreground text-sm",
|
|
1808
|
-
children: "Conversations"
|
|
1809
|
-
}),
|
|
1810
|
-
/* @__PURE__ */ jsx10(Button5, {
|
|
1811
|
-
variant: "ghost",
|
|
1812
|
-
size: "sm",
|
|
1813
|
-
className: "h-8 w-8 p-0",
|
|
1814
|
-
onPress: onCreateNew,
|
|
1815
|
-
"aria-label": "New conversation",
|
|
1816
|
-
children: /* @__PURE__ */ jsx10(Plus2, {
|
|
1817
|
-
className: "h-4 w-4"
|
|
1818
|
-
})
|
|
1819
|
-
})
|
|
1820
|
-
]
|
|
1821
|
-
}),
|
|
1822
|
-
/* @__PURE__ */ jsx10("div", {
|
|
1823
|
-
className: "flex-1 overflow-y-auto p-2",
|
|
1824
|
-
children: isLoading ? /* @__PURE__ */ jsx10("div", {
|
|
1825
|
-
className: "py-4 text-center text-muted-foreground text-sm",
|
|
1826
|
-
children: "Loading\u2026"
|
|
1827
|
-
}) : conversations.length === 0 ? /* @__PURE__ */ jsx10("div", {
|
|
1828
|
-
className: "py-4 text-center text-muted-foreground text-sm",
|
|
1829
|
-
children: "No conversations yet"
|
|
1830
|
-
}) : /* @__PURE__ */ jsx10("div", {
|
|
1831
|
-
className: "flex flex-col gap-1",
|
|
1832
|
-
children: conversations.map((conv) => /* @__PURE__ */ jsx10(ConversationItem, {
|
|
1833
|
-
conversation: conv,
|
|
1834
|
-
selected: conv.id === selectedConversationId,
|
|
1835
|
-
onSelect: () => onSelectConversation(conv.id),
|
|
1836
|
-
onDelete: () => handleDelete(conv.id)
|
|
1837
|
-
}, conv.id))
|
|
1838
|
-
})
|
|
1839
|
-
}),
|
|
1840
|
-
selectedConversation && onUpdateConversation && /* @__PURE__ */ jsx10(ConversationMeta, {
|
|
1841
|
-
conversation: selectedConversation,
|
|
1842
|
-
onUpdate: onUpdateConversation
|
|
1843
|
-
})
|
|
1844
|
-
]
|
|
1845
|
-
});
|
|
1846
|
-
}
|
|
1847
|
-
function ConversationMeta({
|
|
1848
|
-
conversation,
|
|
1849
|
-
onUpdate
|
|
1850
|
-
}) {
|
|
1851
|
-
const [projectName, setProjectName] = React8.useState(conversation.projectName ?? "");
|
|
1852
|
-
const [tagsStr, setTagsStr] = React8.useState(conversation.tags?.join(", ") ?? "");
|
|
1853
|
-
React8.useEffect(() => {
|
|
1854
|
-
setProjectName(conversation.projectName ?? "");
|
|
1855
|
-
setTagsStr(conversation.tags?.join(", ") ?? "");
|
|
1856
|
-
}, [conversation.id, conversation.projectName, conversation.tags]);
|
|
1857
|
-
const handleBlur = React8.useCallback(() => {
|
|
1858
|
-
const tags = tagsStr.split(",").map((t) => t.trim()).filter(Boolean);
|
|
1859
|
-
if (projectName !== (conversation.projectName ?? "") || JSON.stringify(tags) !== JSON.stringify(conversation.tags ?? [])) {
|
|
1860
|
-
onUpdate(conversation.id, {
|
|
1861
|
-
projectName: projectName || undefined,
|
|
1862
|
-
projectId: projectName ? projectName.replace(/\s+/g, "-") : undefined,
|
|
1863
|
-
tags: tags.length > 0 ? tags : undefined
|
|
1864
|
-
});
|
|
1865
|
-
}
|
|
1866
|
-
}, [
|
|
1867
|
-
conversation.id,
|
|
1868
|
-
conversation.projectName,
|
|
1869
|
-
conversation.tags,
|
|
1870
|
-
projectName,
|
|
1871
|
-
tagsStr,
|
|
1872
|
-
onUpdate
|
|
1873
|
-
]);
|
|
1874
|
-
return /* @__PURE__ */ jsxs10("div", {
|
|
1875
|
-
className: "shrink-0 border-border border-t p-2",
|
|
1876
|
-
children: [
|
|
1877
|
-
/* @__PURE__ */ jsx10("p", {
|
|
1878
|
-
className: "mb-1 font-medium text-muted-foreground text-xs",
|
|
1879
|
-
children: "Project"
|
|
1880
|
-
}),
|
|
1881
|
-
/* @__PURE__ */ jsx10("input", {
|
|
1882
|
-
type: "text",
|
|
1883
|
-
value: projectName,
|
|
1884
|
-
onChange: (e) => setProjectName(e.target.value),
|
|
1885
|
-
onBlur: handleBlur,
|
|
1886
|
-
placeholder: "Project name",
|
|
1887
|
-
className: "mb-2 w-full rounded border-input bg-background px-2 py-1 text-xs"
|
|
1888
|
-
}),
|
|
1889
|
-
/* @__PURE__ */ jsx10("p", {
|
|
1890
|
-
className: "mb-1 font-medium text-muted-foreground text-xs",
|
|
1891
|
-
children: "Tags"
|
|
1892
|
-
}),
|
|
1893
|
-
/* @__PURE__ */ jsx10("input", {
|
|
1894
|
-
type: "text",
|
|
1895
|
-
value: tagsStr,
|
|
1896
|
-
onChange: (e) => setTagsStr(e.target.value),
|
|
1897
|
-
onBlur: handleBlur,
|
|
1898
|
-
placeholder: "tag1, tag2",
|
|
1899
|
-
className: "w-full rounded border-input bg-background px-2 py-1 text-xs"
|
|
1900
|
-
})
|
|
1901
|
-
]
|
|
1902
|
-
});
|
|
1903
|
-
}
|
|
1904
|
-
// src/presentation/components/ChatWithExport.tsx
|
|
1905
|
-
import * as React12 from "react";
|
|
1906
|
-
|
|
1907
|
-
// src/presentation/hooks/useMessageSelection.ts
|
|
1908
|
-
import * as React9 from "react";
|
|
1909
|
-
"use client";
|
|
1910
|
-
function useMessageSelection(messageIds) {
|
|
1911
|
-
const [selectedIds, setSelectedIds] = React9.useState(() => new Set);
|
|
1912
|
-
const idSet = React9.useMemo(() => new Set(messageIds), [messageIds.join(",")]);
|
|
1913
|
-
React9.useEffect(() => {
|
|
1914
|
-
setSelectedIds((prev) => {
|
|
1915
|
-
const next = new Set;
|
|
1916
|
-
for (const id of prev) {
|
|
1917
|
-
if (idSet.has(id))
|
|
1918
|
-
next.add(id);
|
|
1919
|
-
}
|
|
1920
|
-
return next.size === prev.size ? prev : next;
|
|
1921
|
-
});
|
|
1922
|
-
}, [idSet]);
|
|
1923
|
-
const toggle = React9.useCallback((id) => {
|
|
1924
|
-
setSelectedIds((prev) => {
|
|
1925
|
-
const next = new Set(prev);
|
|
1926
|
-
if (next.has(id))
|
|
1927
|
-
next.delete(id);
|
|
1928
|
-
else
|
|
1929
|
-
next.add(id);
|
|
1930
|
-
return next;
|
|
1931
|
-
});
|
|
1932
|
-
}, []);
|
|
1933
|
-
const selectAll = React9.useCallback(() => {
|
|
1934
|
-
setSelectedIds(new Set(messageIds));
|
|
1935
|
-
}, [messageIds.join(",")]);
|
|
1936
|
-
const clearSelection = React9.useCallback(() => {
|
|
1937
|
-
setSelectedIds(new Set);
|
|
1938
|
-
}, []);
|
|
1939
|
-
const isSelected = React9.useCallback((id) => selectedIds.has(id), [selectedIds]);
|
|
1940
|
-
const selectedCount = selectedIds.size;
|
|
1941
|
-
return {
|
|
1942
|
-
selectedIds,
|
|
1943
|
-
toggle,
|
|
1944
|
-
selectAll,
|
|
1945
|
-
clearSelection,
|
|
1946
|
-
isSelected,
|
|
1947
|
-
selectedCount
|
|
1948
|
-
};
|
|
1949
|
-
}
|
|
1950
|
-
|
|
1951
|
-
// src/presentation/components/Suggestion.tsx
|
|
1952
|
-
import { Button as Button6 } from "@contractspec/lib.design-system";
|
|
1953
|
-
import { cn as cn9 } from "@contractspec/lib.ui-kit-web/ui/utils";
|
|
1954
|
-
import * as React10 from "react";
|
|
1955
|
-
import { jsx as jsx11 } from "react/jsx-runtime";
|
|
1956
|
-
"use client";
|
|
1957
|
-
function Suggestions({ children, className }) {
|
|
1958
|
-
return /* @__PURE__ */ jsx11("div", {
|
|
1959
|
-
className: cn9("flex flex-wrap gap-2", className),
|
|
1960
|
-
children
|
|
1961
|
-
});
|
|
1962
|
-
}
|
|
1963
|
-
function Suggestion({
|
|
1964
|
-
suggestion,
|
|
1965
|
-
onClick,
|
|
1966
|
-
className
|
|
1967
|
-
}) {
|
|
1968
|
-
const handleClick = React10.useCallback(() => {
|
|
1969
|
-
onClick?.(suggestion);
|
|
1970
|
-
}, [suggestion, onClick]);
|
|
1971
|
-
return /* @__PURE__ */ jsx11(Button6, {
|
|
1972
|
-
type: "button",
|
|
1973
|
-
variant: "outline",
|
|
1974
|
-
size: "sm",
|
|
1975
|
-
onPress: handleClick,
|
|
1976
|
-
className: cn9("text-muted-foreground hover:text-foreground", className),
|
|
1977
|
-
children: suggestion
|
|
1978
|
-
});
|
|
1979
|
-
}
|
|
1980
|
-
|
|
1981
|
-
// src/presentation/components/ThinkingLevelPicker.tsx
|
|
1982
|
-
import { Label } from "@contractspec/lib.ui-kit-web/ui/label";
|
|
1983
|
-
import {
|
|
1984
|
-
Select,
|
|
1985
|
-
SelectContent,
|
|
1986
|
-
SelectItem,
|
|
1987
|
-
SelectTrigger,
|
|
1988
|
-
SelectValue
|
|
1989
|
-
} from "@contractspec/lib.ui-kit-web/ui/select";
|
|
1990
|
-
import { cn as cn10 } from "@contractspec/lib.ui-kit-web/ui/utils";
|
|
1991
|
-
import * as React11 from "react";
|
|
1992
|
-
|
|
1993
|
-
// src/core/thinking-levels.ts
|
|
1994
|
-
var THINKING_LEVEL_LABELS = {
|
|
1995
|
-
instant: "Instant",
|
|
1996
|
-
thinking: "Thinking",
|
|
1997
|
-
extra_thinking: "Extra Thinking",
|
|
1998
|
-
max: "Max"
|
|
1999
|
-
};
|
|
2000
|
-
var THINKING_LEVEL_DESCRIPTIONS = {
|
|
2001
|
-
instant: "Fast responses, minimal reasoning",
|
|
2002
|
-
thinking: "Standard reasoning depth",
|
|
2003
|
-
extra_thinking: "More thorough reasoning",
|
|
2004
|
-
max: "Maximum reasoning depth"
|
|
2005
|
-
};
|
|
2006
|
-
function getProviderOptions(level, providerName) {
|
|
2007
|
-
if (!level || level === "instant") {
|
|
2008
|
-
return {};
|
|
2009
|
-
}
|
|
2010
|
-
switch (providerName) {
|
|
2011
|
-
case "anthropic": {
|
|
2012
|
-
const budgetMap = {
|
|
2013
|
-
thinking: 8000,
|
|
2014
|
-
extra_thinking: 16000,
|
|
2015
|
-
max: 32000
|
|
2016
|
-
};
|
|
2017
|
-
return {
|
|
2018
|
-
anthropic: {
|
|
2019
|
-
thinking: { type: "enabled", budgetTokens: budgetMap[level] }
|
|
2020
|
-
}
|
|
2021
|
-
};
|
|
2022
|
-
}
|
|
2023
|
-
case "openai": {
|
|
2024
|
-
const effortMap = {
|
|
2025
|
-
thinking: "low",
|
|
2026
|
-
extra_thinking: "medium",
|
|
2027
|
-
max: "high"
|
|
2028
|
-
};
|
|
2029
|
-
return {
|
|
2030
|
-
openai: {
|
|
2031
|
-
reasoningEffort: effortMap[level]
|
|
2032
|
-
}
|
|
2033
|
-
};
|
|
2034
|
-
}
|
|
2035
|
-
case "ollama":
|
|
2036
|
-
case "mistral":
|
|
2037
|
-
case "gemini":
|
|
2038
|
-
return {};
|
|
2039
|
-
default:
|
|
2040
|
-
return {};
|
|
2041
|
-
}
|
|
2042
|
-
}
|
|
2043
|
-
|
|
2044
|
-
// src/presentation/components/ThinkingLevelPicker.tsx
|
|
2045
|
-
import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
2046
|
-
"use client";
|
|
2047
|
-
var THINKING_LEVELS = [
|
|
2048
|
-
"instant",
|
|
2049
|
-
"thinking",
|
|
2050
|
-
"extra_thinking",
|
|
2051
|
-
"max"
|
|
2052
|
-
];
|
|
2053
|
-
function ThinkingLevelPicker({
|
|
2054
|
-
value,
|
|
2055
|
-
onChange,
|
|
2056
|
-
className,
|
|
2057
|
-
compact = false
|
|
2058
|
-
}) {
|
|
2059
|
-
const handleChange = React11.useCallback((v) => {
|
|
2060
|
-
onChange(v);
|
|
2061
|
-
}, [onChange]);
|
|
2062
|
-
if (compact) {
|
|
2063
|
-
return /* @__PURE__ */ jsxs11(Select, {
|
|
2064
|
-
value,
|
|
2065
|
-
onValueChange: handleChange,
|
|
2066
|
-
children: [
|
|
2067
|
-
/* @__PURE__ */ jsx12(SelectTrigger, {
|
|
2068
|
-
className: cn10("w-[140px]", className),
|
|
2069
|
-
children: /* @__PURE__ */ jsx12(SelectValue, {})
|
|
2070
|
-
}),
|
|
2071
|
-
/* @__PURE__ */ jsx12(SelectContent, {
|
|
2072
|
-
children: THINKING_LEVELS.map((level) => /* @__PURE__ */ jsx12(SelectItem, {
|
|
2073
|
-
value: level,
|
|
2074
|
-
children: THINKING_LEVEL_LABELS[level]
|
|
2075
|
-
}, level))
|
|
2076
|
-
})
|
|
2077
|
-
]
|
|
2078
|
-
});
|
|
2079
|
-
}
|
|
2080
|
-
return /* @__PURE__ */ jsxs11("div", {
|
|
2081
|
-
className: cn10("flex flex-col gap-1.5", className),
|
|
2082
|
-
children: [
|
|
2083
|
-
/* @__PURE__ */ jsx12(Label, {
|
|
2084
|
-
htmlFor: "thinking-level-picker",
|
|
2085
|
-
className: "font-medium text-sm",
|
|
2086
|
-
children: "Thinking Level"
|
|
2087
|
-
}),
|
|
2088
|
-
/* @__PURE__ */ jsxs11(Select, {
|
|
2089
|
-
name: "thinking-level-picker",
|
|
2090
|
-
value,
|
|
2091
|
-
onValueChange: handleChange,
|
|
2092
|
-
children: [
|
|
2093
|
-
/* @__PURE__ */ jsx12(SelectTrigger, {
|
|
2094
|
-
children: /* @__PURE__ */ jsx12(SelectValue, {
|
|
2095
|
-
placeholder: "Select thinking level"
|
|
2096
|
-
})
|
|
2097
|
-
}),
|
|
2098
|
-
/* @__PURE__ */ jsx12(SelectContent, {
|
|
2099
|
-
children: THINKING_LEVELS.map((level) => /* @__PURE__ */ jsx12(SelectItem, {
|
|
2100
|
-
value: level,
|
|
2101
|
-
title: THINKING_LEVEL_DESCRIPTIONS[level],
|
|
2102
|
-
children: THINKING_LEVEL_LABELS[level]
|
|
2103
|
-
}, level))
|
|
2104
|
-
})
|
|
2105
|
-
]
|
|
2106
|
-
})
|
|
2107
|
-
]
|
|
2108
|
-
});
|
|
2109
|
-
}
|
|
2110
|
-
|
|
2111
|
-
// src/presentation/components/ChatWithExport.tsx
|
|
2112
|
-
import { jsx as jsx13, jsxs as jsxs12, Fragment as Fragment6 } from "react/jsx-runtime";
|
|
2113
|
-
"use client";
|
|
2114
|
-
function ChatWithExport({
|
|
2115
|
-
messages,
|
|
2116
|
-
conversation,
|
|
2117
|
-
children,
|
|
2118
|
-
className,
|
|
2119
|
-
showExport = true,
|
|
2120
|
-
showMessageSelection = true,
|
|
2121
|
-
showScrollButton = true,
|
|
2122
|
-
onCreateNew,
|
|
2123
|
-
onFork,
|
|
2124
|
-
onEditMessage,
|
|
2125
|
-
thinkingLevel = "thinking",
|
|
2126
|
-
onThinkingLevelChange,
|
|
2127
|
-
presentationRenderer,
|
|
2128
|
-
formRenderer,
|
|
2129
|
-
dataViewRenderer,
|
|
2130
|
-
components,
|
|
2131
|
-
suggestions,
|
|
2132
|
-
onSuggestionClick,
|
|
2133
|
-
suggestionComponents: suggestionComps,
|
|
2134
|
-
showSuggestionsWhenEmpty = true
|
|
2135
|
-
}) {
|
|
2136
|
-
const messageIds = React12.useMemo(() => messages.map((m) => m.id), [messages]);
|
|
2137
|
-
const selection = useMessageSelection(messageIds);
|
|
2138
|
-
const showSuggestions = suggestions && suggestions.length > 0 && (messages.length === 0 || showSuggestionsWhenEmpty);
|
|
2139
|
-
const hasToolbar = showExport || showMessageSelection;
|
|
2140
|
-
const hasPicker = Boolean(onThinkingLevelChange);
|
|
2141
|
-
const headerContent = hasPicker || hasToolbar ? /* @__PURE__ */ jsxs12(Fragment6, {
|
|
2142
|
-
children: [
|
|
2143
|
-
hasPicker && onThinkingLevelChange && /* @__PURE__ */ jsx13(ThinkingLevelPicker, {
|
|
2144
|
-
value: thinkingLevel,
|
|
2145
|
-
onChange: onThinkingLevelChange,
|
|
2146
|
-
compact: true
|
|
2147
|
-
}),
|
|
2148
|
-
hasToolbar && /* @__PURE__ */ jsx13(ChatExportToolbar, {
|
|
2149
|
-
messages,
|
|
2150
|
-
conversation,
|
|
2151
|
-
selectedIds: selection.selectedIds,
|
|
2152
|
-
showSelectionSummary: showMessageSelection,
|
|
2153
|
-
onSelectAll: showMessageSelection ? selection.selectAll : undefined,
|
|
2154
|
-
onClearSelection: showMessageSelection ? selection.clearSelection : undefined,
|
|
2155
|
-
selectedCount: selection.selectedCount,
|
|
2156
|
-
totalCount: messages.length,
|
|
2157
|
-
onCreateNew,
|
|
2158
|
-
onFork
|
|
2159
|
-
})
|
|
2160
|
-
]
|
|
2161
|
-
}) : null;
|
|
2162
|
-
return /* @__PURE__ */ jsxs12(ChatContainer, {
|
|
2163
|
-
className,
|
|
2164
|
-
headerContent,
|
|
2165
|
-
showScrollButton,
|
|
2166
|
-
children: [
|
|
2167
|
-
messages.map((msg) => /* @__PURE__ */ jsx13(ChatMessage, {
|
|
2168
|
-
message: msg,
|
|
2169
|
-
selectable: showMessageSelection,
|
|
2170
|
-
selected: selection.isSelected(msg.id),
|
|
2171
|
-
onSelect: showMessageSelection ? selection.toggle : undefined,
|
|
2172
|
-
editable: msg.role === "user" && !!onEditMessage,
|
|
2173
|
-
onEdit: onEditMessage,
|
|
2174
|
-
presentationRenderer,
|
|
2175
|
-
formRenderer,
|
|
2176
|
-
dataViewRenderer,
|
|
2177
|
-
components
|
|
2178
|
-
}, msg.id)),
|
|
2179
|
-
showSuggestions && (() => {
|
|
2180
|
-
const SuggestionsComp = suggestionComps?.Suggestions;
|
|
2181
|
-
const SuggestionComp = suggestionComps?.Suggestion;
|
|
2182
|
-
if (SuggestionsComp && SuggestionComp) {
|
|
2183
|
-
return /* @__PURE__ */ jsx13(SuggestionsComp, {
|
|
2184
|
-
children: suggestions.map((s) => /* @__PURE__ */ jsx13(SuggestionComp, {
|
|
2185
|
-
suggestion: s,
|
|
2186
|
-
onClick: onSuggestionClick
|
|
2187
|
-
}, s))
|
|
2188
|
-
});
|
|
2189
|
-
}
|
|
2190
|
-
return /* @__PURE__ */ jsx13(Suggestions, {
|
|
2191
|
-
className: "mb-4",
|
|
2192
|
-
children: suggestions.map((s) => /* @__PURE__ */ jsx13(Suggestion, {
|
|
2193
|
-
suggestion: s,
|
|
2194
|
-
onClick: onSuggestionClick
|
|
2195
|
-
}, s))
|
|
2196
|
-
});
|
|
2197
|
-
})(),
|
|
2198
|
-
children
|
|
2199
|
-
]
|
|
2200
|
-
});
|
|
2201
|
-
}
|
|
2202
|
-
// src/presentation/components/ChatWithSidebar.tsx
|
|
2203
|
-
import * as React14 from "react";
|
|
2204
|
-
|
|
2205
|
-
// src/core/local-storage-conversation-store.ts
|
|
2206
|
-
var DEFAULT_KEY = "contractspec:ai-chat:conversations";
|
|
2207
|
-
function generateId(prefix) {
|
|
2208
|
-
return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
|
|
2209
|
-
}
|
|
2210
|
-
function toSerializable(conv) {
|
|
2211
|
-
return {
|
|
2212
|
-
...conv,
|
|
2213
|
-
createdAt: conv.createdAt.toISOString(),
|
|
2214
|
-
updatedAt: conv.updatedAt.toISOString(),
|
|
2215
|
-
messages: conv.messages.map((m) => ({
|
|
2216
|
-
...m,
|
|
2217
|
-
createdAt: m.createdAt.toISOString(),
|
|
2218
|
-
updatedAt: m.updatedAt.toISOString()
|
|
2219
|
-
}))
|
|
2220
|
-
};
|
|
2221
|
-
}
|
|
2222
|
-
function fromSerializable(raw) {
|
|
2223
|
-
const messages = raw.messages?.map((m) => ({
|
|
2224
|
-
...m,
|
|
2225
|
-
createdAt: new Date(m.createdAt),
|
|
2226
|
-
updatedAt: new Date(m.updatedAt)
|
|
2227
|
-
})) ?? [];
|
|
2228
|
-
return {
|
|
2229
|
-
...raw,
|
|
2230
|
-
createdAt: new Date(raw.createdAt),
|
|
2231
|
-
updatedAt: new Date(raw.updatedAt),
|
|
2232
|
-
messages
|
|
2233
|
-
};
|
|
2234
|
-
}
|
|
2235
|
-
function loadAll(key) {
|
|
2236
|
-
if (typeof window === "undefined")
|
|
2237
|
-
return new Map;
|
|
2238
|
-
try {
|
|
2239
|
-
const raw = window.localStorage.getItem(key);
|
|
2240
|
-
if (!raw)
|
|
2241
|
-
return new Map;
|
|
2242
|
-
const arr = JSON.parse(raw);
|
|
2243
|
-
const map = new Map;
|
|
2244
|
-
for (const item of arr) {
|
|
2245
|
-
const conv = fromSerializable(item);
|
|
2246
|
-
map.set(conv.id, conv);
|
|
2247
|
-
}
|
|
2248
|
-
return map;
|
|
2249
|
-
} catch {
|
|
2250
|
-
return new Map;
|
|
2251
|
-
}
|
|
2252
|
-
}
|
|
2253
|
-
function saveAll(key, map) {
|
|
2254
|
-
if (typeof window === "undefined")
|
|
2255
|
-
return;
|
|
2256
|
-
try {
|
|
2257
|
-
const arr = Array.from(map.values()).map(toSerializable);
|
|
2258
|
-
window.localStorage.setItem(key, JSON.stringify(arr));
|
|
2259
|
-
} catch {}
|
|
2260
|
-
}
|
|
2261
|
-
|
|
2262
|
-
class LocalStorageConversationStore {
|
|
2263
|
-
key;
|
|
2264
|
-
cache = null;
|
|
2265
|
-
constructor(storageKey = DEFAULT_KEY) {
|
|
2266
|
-
this.key = storageKey;
|
|
2267
|
-
}
|
|
2268
|
-
getMap() {
|
|
2269
|
-
if (!this.cache) {
|
|
2270
|
-
this.cache = loadAll(this.key);
|
|
2271
|
-
}
|
|
2272
|
-
return this.cache;
|
|
2273
|
-
}
|
|
2274
|
-
persist() {
|
|
2275
|
-
saveAll(this.key, this.getMap());
|
|
2276
|
-
}
|
|
2277
|
-
async get(conversationId) {
|
|
2278
|
-
return this.getMap().get(conversationId) ?? null;
|
|
2279
|
-
}
|
|
2280
|
-
async create(conversation) {
|
|
2281
|
-
const now = new Date;
|
|
2282
|
-
const full = {
|
|
2283
|
-
...conversation,
|
|
2284
|
-
id: generateId("conv"),
|
|
2285
|
-
createdAt: now,
|
|
2286
|
-
updatedAt: now
|
|
2287
|
-
};
|
|
2288
|
-
this.getMap().set(full.id, full);
|
|
2289
|
-
this.persist();
|
|
2290
|
-
return full;
|
|
2291
|
-
}
|
|
2292
|
-
async update(conversationId, updates) {
|
|
2293
|
-
const conv = this.getMap().get(conversationId);
|
|
2294
|
-
if (!conv)
|
|
2295
|
-
return null;
|
|
2296
|
-
const updated = {
|
|
2297
|
-
...conv,
|
|
2298
|
-
...updates,
|
|
2299
|
-
updatedAt: new Date
|
|
2300
|
-
};
|
|
2301
|
-
this.getMap().set(conversationId, updated);
|
|
2302
|
-
this.persist();
|
|
2303
|
-
return updated;
|
|
2304
|
-
}
|
|
2305
|
-
async appendMessage(conversationId, message) {
|
|
2306
|
-
const conv = this.getMap().get(conversationId);
|
|
2307
|
-
if (!conv)
|
|
2308
|
-
throw new Error(`Conversation ${conversationId} not found`);
|
|
2309
|
-
const now = new Date;
|
|
2310
|
-
const fullMessage = {
|
|
2311
|
-
...message,
|
|
2312
|
-
id: generateId("msg"),
|
|
2313
|
-
conversationId,
|
|
2314
|
-
createdAt: now,
|
|
2315
|
-
updatedAt: now
|
|
2316
|
-
};
|
|
2317
|
-
conv.messages.push(fullMessage);
|
|
2318
|
-
conv.updatedAt = now;
|
|
2319
|
-
this.persist();
|
|
2320
|
-
return fullMessage;
|
|
2321
|
-
}
|
|
2322
|
-
async updateMessage(conversationId, messageId, updates) {
|
|
2323
|
-
const conv = this.getMap().get(conversationId);
|
|
2324
|
-
if (!conv)
|
|
2325
|
-
return null;
|
|
2326
|
-
const idx = conv.messages.findIndex((m) => m.id === messageId);
|
|
2327
|
-
if (idx === -1)
|
|
2328
|
-
return null;
|
|
2329
|
-
const msg = conv.messages[idx];
|
|
2330
|
-
if (!msg)
|
|
2331
|
-
return null;
|
|
2332
|
-
const updated = {
|
|
2333
|
-
...msg,
|
|
2334
|
-
...updates,
|
|
2335
|
-
updatedAt: new Date
|
|
2336
|
-
};
|
|
2337
|
-
conv.messages[idx] = updated;
|
|
2338
|
-
conv.updatedAt = new Date;
|
|
2339
|
-
this.persist();
|
|
2340
|
-
return updated;
|
|
2341
|
-
}
|
|
2342
|
-
async delete(conversationId) {
|
|
2343
|
-
const deleted = this.getMap().delete(conversationId);
|
|
2344
|
-
if (deleted)
|
|
2345
|
-
this.persist();
|
|
2346
|
-
return deleted;
|
|
2347
|
-
}
|
|
2348
|
-
async list(options) {
|
|
2349
|
-
let results = Array.from(this.getMap().values());
|
|
2350
|
-
if (options?.status) {
|
|
2351
|
-
results = results.filter((c) => c.status === options.status);
|
|
2352
|
-
}
|
|
2353
|
-
if (options?.projectId) {
|
|
2354
|
-
results = results.filter((c) => c.projectId === options.projectId);
|
|
2355
|
-
}
|
|
2356
|
-
if (options?.tags && options.tags.length > 0) {
|
|
2357
|
-
const tagSet = new Set(options.tags);
|
|
2358
|
-
results = results.filter((c) => c.tags && c.tags.some((t) => tagSet.has(t)));
|
|
2359
|
-
}
|
|
2360
|
-
results.sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime());
|
|
2361
|
-
const offset = options?.offset ?? 0;
|
|
2362
|
-
const limit = options?.limit ?? 100;
|
|
2363
|
-
return results.slice(offset, offset + limit);
|
|
2364
|
-
}
|
|
2365
|
-
async fork(conversationId, upToMessageId) {
|
|
2366
|
-
const source = this.getMap().get(conversationId);
|
|
2367
|
-
if (!source)
|
|
2368
|
-
throw new Error(`Conversation ${conversationId} not found`);
|
|
2369
|
-
let messagesToCopy = source.messages;
|
|
2370
|
-
if (upToMessageId) {
|
|
2371
|
-
const idx = source.messages.findIndex((m) => m.id === upToMessageId);
|
|
2372
|
-
if (idx === -1)
|
|
2373
|
-
throw new Error(`Message ${upToMessageId} not found`);
|
|
2374
|
-
messagesToCopy = source.messages.slice(0, idx + 1);
|
|
2375
|
-
}
|
|
2376
|
-
const now = new Date;
|
|
2377
|
-
const forkedMessages = messagesToCopy.map((m) => ({
|
|
2378
|
-
...m,
|
|
2379
|
-
id: generateId("msg"),
|
|
2380
|
-
conversationId: "",
|
|
2381
|
-
createdAt: new Date(m.createdAt),
|
|
2382
|
-
updatedAt: new Date(m.updatedAt)
|
|
2383
|
-
}));
|
|
2384
|
-
const forked = {
|
|
2385
|
-
...source,
|
|
2386
|
-
id: generateId("conv"),
|
|
2387
|
-
title: source.title ? `${source.title} (fork)` : undefined,
|
|
2388
|
-
forkedFromId: source.id,
|
|
2389
|
-
createdAt: now,
|
|
2390
|
-
updatedAt: now,
|
|
2391
|
-
messages: forkedMessages
|
|
2392
|
-
};
|
|
2393
|
-
for (const m of forked.messages) {
|
|
2394
|
-
m.conversationId = forked.id;
|
|
2395
|
-
}
|
|
2396
|
-
this.getMap().set(forked.id, forked);
|
|
2397
|
-
this.persist();
|
|
2398
|
-
return forked;
|
|
2399
|
-
}
|
|
2400
|
-
async truncateAfter(conversationId, messageId) {
|
|
2401
|
-
const conv = this.getMap().get(conversationId);
|
|
2402
|
-
if (!conv)
|
|
2403
|
-
return null;
|
|
2404
|
-
const idx = conv.messages.findIndex((m) => m.id === messageId);
|
|
2405
|
-
if (idx === -1)
|
|
2406
|
-
return null;
|
|
2407
|
-
conv.messages = conv.messages.slice(0, idx + 1);
|
|
2408
|
-
conv.updatedAt = new Date;
|
|
2409
|
-
this.persist();
|
|
2410
|
-
return conv;
|
|
2411
|
-
}
|
|
2412
|
-
async search(query, limit = 20) {
|
|
2413
|
-
const lowerQuery = query.toLowerCase();
|
|
2414
|
-
const results = [];
|
|
2415
|
-
for (const conv of this.getMap().values()) {
|
|
2416
|
-
if (conv.title?.toLowerCase().includes(lowerQuery)) {
|
|
2417
|
-
results.push(conv);
|
|
2418
|
-
continue;
|
|
2419
|
-
}
|
|
2420
|
-
if (conv.messages.some((m) => m.content.toLowerCase().includes(lowerQuery))) {
|
|
2421
|
-
results.push(conv);
|
|
2422
|
-
}
|
|
2423
|
-
if (results.length >= limit)
|
|
2424
|
-
break;
|
|
2425
|
-
}
|
|
2426
|
-
return results;
|
|
2427
|
-
}
|
|
2428
|
-
}
|
|
2429
|
-
function createLocalStorageConversationStore(storageKey) {
|
|
2430
|
-
return new LocalStorageConversationStore(storageKey);
|
|
2431
|
-
}
|
|
2432
|
-
|
|
2433
|
-
// src/presentation/hooks/useChat.tsx
|
|
2434
|
-
import {
|
|
2435
|
-
createProvider
|
|
2436
|
-
} from "@contractspec/lib.ai-providers";
|
|
2437
|
-
import { tool as tool4 } from "ai";
|
|
2438
|
-
import * as React13 from "react";
|
|
2439
|
-
import { z as z4 } from "zod";
|
|
2440
|
-
|
|
2441
|
-
// src/core/chat-service.ts
|
|
2442
|
-
import { generateText, streamText } from "ai";
|
|
2443
|
-
import { compilePlannerPrompt } from "@contractspec/lib.surface-runtime/runtime/planner-prompt";
|
|
2444
|
-
|
|
2445
|
-
// src/core/agent-tools-adapter.ts
|
|
2446
|
-
import { tool } from "ai";
|
|
2447
|
-
import { z } from "zod";
|
|
2448
|
-
function getInputSchema(_schema) {
|
|
2449
|
-
return z.object({}).passthrough();
|
|
2450
|
-
}
|
|
2451
|
-
function agentToolConfigsToToolSet(configs, handlers) {
|
|
2452
|
-
const result = {};
|
|
2453
|
-
for (const config of configs) {
|
|
2454
|
-
const handler = handlers?.[config.name];
|
|
2455
|
-
const inputSchema = getInputSchema(config.schema);
|
|
2456
|
-
result[config.name] = tool({
|
|
2457
|
-
description: config.description ?? config.name,
|
|
2458
|
-
inputSchema,
|
|
2459
|
-
execute: async (input) => {
|
|
2460
|
-
if (!handler) {
|
|
2461
|
-
return {
|
|
2462
|
-
status: "unimplemented",
|
|
2463
|
-
message: "Wire handler in host",
|
|
2464
|
-
toolName: config.name
|
|
2465
|
-
};
|
|
2466
|
-
}
|
|
2467
|
-
try {
|
|
2468
|
-
const output = await Promise.resolve(handler(input));
|
|
2469
|
-
return typeof output === "string" ? output : output;
|
|
2470
|
-
} catch (err) {
|
|
2471
|
-
return {
|
|
2472
|
-
status: "error",
|
|
2473
|
-
error: err instanceof Error ? err.message : String(err),
|
|
2474
|
-
toolName: config.name
|
|
2475
|
-
};
|
|
2476
|
-
}
|
|
2477
|
-
}
|
|
2478
|
-
});
|
|
2479
|
-
}
|
|
2480
|
-
return result;
|
|
2481
|
-
}
|
|
2482
|
-
|
|
2483
|
-
// src/core/contracts-context.ts
|
|
2484
|
-
function buildContractsContextPrompt(config) {
|
|
2485
|
-
const parts = [];
|
|
2486
|
-
if (!config.agentSpecs?.length && !config.dataViewSpecs?.length && !config.formSpecs?.length && !config.presentationSpecs?.length && !config.operationRefs?.length) {
|
|
2487
|
-
return "";
|
|
2488
|
-
}
|
|
2489
|
-
parts.push(`
|
|
2490
|
-
|
|
2491
|
-
## Available resources`);
|
|
2492
|
-
if (config.agentSpecs?.length) {
|
|
2493
|
-
parts.push(`
|
|
2494
|
-
### Agent tools`);
|
|
2495
|
-
for (const agent of config.agentSpecs) {
|
|
2496
|
-
const toolNames = agent.tools?.map((t) => t.name).join(", ") ?? "none";
|
|
2497
|
-
parts.push(`- **${agent.key}**: tools: ${toolNames}`);
|
|
2498
|
-
}
|
|
2499
|
-
}
|
|
2500
|
-
if (config.dataViewSpecs?.length) {
|
|
2501
|
-
parts.push(`
|
|
2502
|
-
### Data views`);
|
|
2503
|
-
for (const dv of config.dataViewSpecs) {
|
|
2504
|
-
parts.push(`- **${dv.key}**: ${dv.meta.title ?? dv.key}`);
|
|
2505
|
-
}
|
|
2506
|
-
}
|
|
2507
|
-
if (config.formSpecs?.length) {
|
|
2508
|
-
parts.push(`
|
|
2509
|
-
### Forms`);
|
|
2510
|
-
for (const form of config.formSpecs) {
|
|
2511
|
-
parts.push(`- **${form.key}**: ${form.meta.title ?? form.key}`);
|
|
2512
|
-
}
|
|
2513
|
-
}
|
|
2514
|
-
if (config.presentationSpecs?.length) {
|
|
2515
|
-
parts.push(`
|
|
2516
|
-
### Presentations`);
|
|
2517
|
-
for (const pres of config.presentationSpecs) {
|
|
2518
|
-
parts.push(`- **${pres.key}**: ${pres.meta.title ?? pres.key} (targets: ${pres.targets?.join(", ") ?? "react"})`);
|
|
2519
|
-
}
|
|
2520
|
-
}
|
|
2521
|
-
if (config.operationRefs?.length) {
|
|
2522
|
-
parts.push(`
|
|
2523
|
-
### Operations`);
|
|
2524
|
-
for (const op of config.operationRefs) {
|
|
2525
|
-
parts.push(`- **${op.key}@${op.version}**`);
|
|
2526
|
-
}
|
|
2527
|
-
}
|
|
2528
|
-
parts.push(`
|
|
2529
|
-
Use the available tools to invoke operations, query data views, or propose surface changes when appropriate.`);
|
|
2530
|
-
return parts.join(`
|
|
2531
|
-
`);
|
|
2532
|
-
}
|
|
2533
|
-
|
|
2534
|
-
// src/core/conversation-store.ts
|
|
2535
|
-
function generateId2(prefix) {
|
|
2536
|
-
return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
|
|
2537
|
-
}
|
|
2538
|
-
|
|
2539
|
-
class InMemoryConversationStore {
|
|
2540
|
-
conversations = new Map;
|
|
2541
|
-
async get(conversationId) {
|
|
2542
|
-
return this.conversations.get(conversationId) ?? null;
|
|
2543
|
-
}
|
|
2544
|
-
async create(conversation) {
|
|
2545
|
-
const now = new Date;
|
|
2546
|
-
const fullConversation = {
|
|
2547
|
-
...conversation,
|
|
2548
|
-
id: generateId2("conv"),
|
|
2549
|
-
createdAt: now,
|
|
2550
|
-
updatedAt: now
|
|
2551
|
-
};
|
|
2552
|
-
this.conversations.set(fullConversation.id, fullConversation);
|
|
2553
|
-
return fullConversation;
|
|
2554
|
-
}
|
|
2555
|
-
async update(conversationId, updates) {
|
|
2556
|
-
const conversation = this.conversations.get(conversationId);
|
|
2557
|
-
if (!conversation)
|
|
2558
|
-
return null;
|
|
2559
|
-
const updated = {
|
|
2560
|
-
...conversation,
|
|
2561
|
-
...updates,
|
|
2562
|
-
updatedAt: new Date
|
|
2563
|
-
};
|
|
2564
|
-
this.conversations.set(conversationId, updated);
|
|
2565
|
-
return updated;
|
|
2566
|
-
}
|
|
2567
|
-
async appendMessage(conversationId, message) {
|
|
2568
|
-
const conversation = this.conversations.get(conversationId);
|
|
2569
|
-
if (!conversation) {
|
|
2570
|
-
throw new Error(`Conversation ${conversationId} not found`);
|
|
2571
|
-
}
|
|
2572
|
-
const now = new Date;
|
|
2573
|
-
const fullMessage = {
|
|
2574
|
-
...message,
|
|
2575
|
-
id: generateId2("msg"),
|
|
2576
|
-
conversationId,
|
|
2577
|
-
createdAt: now,
|
|
2578
|
-
updatedAt: now
|
|
2579
|
-
};
|
|
2580
|
-
conversation.messages.push(fullMessage);
|
|
2581
|
-
conversation.updatedAt = now;
|
|
2582
|
-
return fullMessage;
|
|
2583
|
-
}
|
|
2584
|
-
async updateMessage(conversationId, messageId, updates) {
|
|
2585
|
-
const conversation = this.conversations.get(conversationId);
|
|
2586
|
-
if (!conversation)
|
|
2587
|
-
return null;
|
|
2588
|
-
const messageIndex = conversation.messages.findIndex((m) => m.id === messageId);
|
|
2589
|
-
if (messageIndex === -1)
|
|
2590
|
-
return null;
|
|
2591
|
-
const message = conversation.messages[messageIndex];
|
|
2592
|
-
if (!message)
|
|
2593
|
-
return null;
|
|
2594
|
-
const updated = {
|
|
2595
|
-
...message,
|
|
2596
|
-
...updates,
|
|
2597
|
-
updatedAt: new Date
|
|
2598
|
-
};
|
|
2599
|
-
conversation.messages[messageIndex] = updated;
|
|
2600
|
-
conversation.updatedAt = new Date;
|
|
2601
|
-
return updated;
|
|
2602
|
-
}
|
|
2603
|
-
async delete(conversationId) {
|
|
2604
|
-
return this.conversations.delete(conversationId);
|
|
2605
|
-
}
|
|
2606
|
-
async list(options) {
|
|
2607
|
-
let results = Array.from(this.conversations.values());
|
|
2608
|
-
if (options?.status) {
|
|
2609
|
-
results = results.filter((c) => c.status === options.status);
|
|
2610
|
-
}
|
|
2611
|
-
if (options?.projectId) {
|
|
2612
|
-
results = results.filter((c) => c.projectId === options.projectId);
|
|
2613
|
-
}
|
|
2614
|
-
if (options?.tags && options.tags.length > 0) {
|
|
2615
|
-
const tagSet = new Set(options.tags);
|
|
2616
|
-
results = results.filter((c) => c.tags && c.tags.some((t) => tagSet.has(t)));
|
|
2617
|
-
}
|
|
2618
|
-
results.sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime());
|
|
2619
|
-
const offset = options?.offset ?? 0;
|
|
2620
|
-
const limit = options?.limit ?? 100;
|
|
2621
|
-
return results.slice(offset, offset + limit);
|
|
2622
|
-
}
|
|
2623
|
-
async fork(conversationId, upToMessageId) {
|
|
2624
|
-
const source = this.conversations.get(conversationId);
|
|
2625
|
-
if (!source) {
|
|
2626
|
-
throw new Error(`Conversation ${conversationId} not found`);
|
|
2627
|
-
}
|
|
2628
|
-
let messagesToCopy = source.messages;
|
|
2629
|
-
if (upToMessageId) {
|
|
2630
|
-
const idx = source.messages.findIndex((m) => m.id === upToMessageId);
|
|
2631
|
-
if (idx === -1) {
|
|
2632
|
-
throw new Error(`Message ${upToMessageId} not found`);
|
|
2633
|
-
}
|
|
2634
|
-
messagesToCopy = source.messages.slice(0, idx + 1);
|
|
2635
|
-
}
|
|
2636
|
-
const now = new Date;
|
|
2637
|
-
const forkedMessages = messagesToCopy.map((m) => ({
|
|
2638
|
-
...m,
|
|
2639
|
-
id: generateId2("msg"),
|
|
2640
|
-
conversationId: "",
|
|
2641
|
-
createdAt: new Date(m.createdAt),
|
|
2642
|
-
updatedAt: new Date(m.updatedAt)
|
|
2643
|
-
}));
|
|
2644
|
-
const forked = {
|
|
2645
|
-
...source,
|
|
2646
|
-
id: generateId2("conv"),
|
|
2647
|
-
title: source.title ? `${source.title} (fork)` : undefined,
|
|
2648
|
-
forkedFromId: source.id,
|
|
2649
|
-
createdAt: now,
|
|
2650
|
-
updatedAt: now,
|
|
2651
|
-
messages: forkedMessages
|
|
2652
|
-
};
|
|
2653
|
-
for (const m of forked.messages) {
|
|
2654
|
-
m.conversationId = forked.id;
|
|
2655
|
-
}
|
|
2656
|
-
this.conversations.set(forked.id, forked);
|
|
2657
|
-
return forked;
|
|
2658
|
-
}
|
|
2659
|
-
async truncateAfter(conversationId, messageId) {
|
|
2660
|
-
const conv = this.conversations.get(conversationId);
|
|
2661
|
-
if (!conv)
|
|
2662
|
-
return null;
|
|
2663
|
-
const idx = conv.messages.findIndex((m) => m.id === messageId);
|
|
2664
|
-
if (idx === -1)
|
|
2665
|
-
return null;
|
|
2666
|
-
conv.messages = conv.messages.slice(0, idx + 1);
|
|
2667
|
-
conv.updatedAt = new Date;
|
|
2668
|
-
return conv;
|
|
2669
|
-
}
|
|
2670
|
-
async search(query, limit = 20) {
|
|
2671
|
-
const lowerQuery = query.toLowerCase();
|
|
2672
|
-
const results = [];
|
|
2673
|
-
for (const conversation of this.conversations.values()) {
|
|
2674
|
-
if (conversation.title?.toLowerCase().includes(lowerQuery)) {
|
|
2675
|
-
results.push(conversation);
|
|
2676
|
-
continue;
|
|
2677
|
-
}
|
|
2678
|
-
const hasMatch = conversation.messages.some((m) => m.content.toLowerCase().includes(lowerQuery));
|
|
2679
|
-
if (hasMatch) {
|
|
2680
|
-
results.push(conversation);
|
|
2681
|
-
}
|
|
2682
|
-
if (results.length >= limit)
|
|
2683
|
-
break;
|
|
2684
|
-
}
|
|
2685
|
-
return results;
|
|
2686
|
-
}
|
|
2687
|
-
clear() {
|
|
2688
|
-
this.conversations.clear();
|
|
2689
|
-
}
|
|
2690
|
-
}
|
|
2691
|
-
function createInMemoryConversationStore() {
|
|
2692
|
-
return new InMemoryConversationStore;
|
|
2693
|
-
}
|
|
2694
|
-
|
|
2695
|
-
// src/core/surface-planner-tools.ts
|
|
2696
|
-
import { buildSurfacePatchProposal } from "@contractspec/lib.surface-runtime/runtime/planner-tools";
|
|
2697
|
-
import {
|
|
2698
|
-
validatePatchProposal
|
|
2699
|
-
} from "@contractspec/lib.surface-runtime/spec/validate-surface-patch";
|
|
2700
|
-
import { tool as tool2 } from "ai";
|
|
2701
|
-
import { z as z2 } from "zod";
|
|
2702
|
-
var VALID_OPS = [
|
|
2703
|
-
"insert-node",
|
|
2704
|
-
"replace-node",
|
|
2705
|
-
"remove-node",
|
|
2706
|
-
"move-node",
|
|
2707
|
-
"resize-panel",
|
|
2708
|
-
"set-layout",
|
|
2709
|
-
"reveal-field",
|
|
2710
|
-
"hide-field",
|
|
2711
|
-
"promote-action",
|
|
2712
|
-
"set-focus"
|
|
2713
|
-
];
|
|
2714
|
-
var DEFAULT_NODE_KINDS = [
|
|
2715
|
-
"entity-section",
|
|
2716
|
-
"entity-card",
|
|
2717
|
-
"data-view",
|
|
2718
|
-
"assistant-panel",
|
|
2719
|
-
"chat-thread",
|
|
2720
|
-
"action-bar",
|
|
2721
|
-
"timeline",
|
|
2722
|
-
"table",
|
|
2723
|
-
"rich-doc",
|
|
2724
|
-
"form",
|
|
2725
|
-
"chart",
|
|
2726
|
-
"custom-widget"
|
|
2727
|
-
];
|
|
2728
|
-
function collectSlotIdsFromRegion(node) {
|
|
2729
|
-
const ids = [];
|
|
2730
|
-
if (node.type === "slot") {
|
|
2731
|
-
ids.push(node.slotId);
|
|
2732
|
-
}
|
|
2733
|
-
if (node.type === "panel-group" || node.type === "stack") {
|
|
2734
|
-
for (const child of node.children) {
|
|
2735
|
-
ids.push(...collectSlotIdsFromRegion(child));
|
|
2736
|
-
}
|
|
2737
|
-
}
|
|
2738
|
-
if (node.type === "tabs") {
|
|
2739
|
-
for (const tab of node.tabs) {
|
|
2740
|
-
ids.push(...collectSlotIdsFromRegion(tab.child));
|
|
2741
|
-
}
|
|
2742
|
-
}
|
|
2743
|
-
if (node.type === "floating") {
|
|
2744
|
-
ids.push(node.anchorSlotId);
|
|
2745
|
-
ids.push(...collectSlotIdsFromRegion(node.child));
|
|
2746
|
-
}
|
|
2747
|
-
return ids;
|
|
2748
|
-
}
|
|
2749
|
-
function deriveConstraints(plan) {
|
|
2750
|
-
const slotIds = collectSlotIdsFromRegion(plan.layoutRoot);
|
|
2751
|
-
const uniqueSlots = [...new Set(slotIds)];
|
|
2752
|
-
return {
|
|
2753
|
-
allowedOps: VALID_OPS,
|
|
2754
|
-
allowedSlots: uniqueSlots.length > 0 ? uniqueSlots : ["assistant", "primary"],
|
|
2755
|
-
allowedNodeKinds: DEFAULT_NODE_KINDS
|
|
2756
|
-
};
|
|
2757
|
-
}
|
|
2758
|
-
var ProposePatchInputSchema = z2.object({
|
|
2759
|
-
proposalId: z2.string().describe("Unique proposal identifier"),
|
|
2760
|
-
ops: z2.array(z2.object({
|
|
2761
|
-
op: z2.enum([
|
|
2762
|
-
"insert-node",
|
|
2763
|
-
"replace-node",
|
|
2764
|
-
"remove-node",
|
|
2765
|
-
"move-node",
|
|
2766
|
-
"resize-panel",
|
|
2767
|
-
"set-layout",
|
|
2768
|
-
"reveal-field",
|
|
2769
|
-
"hide-field",
|
|
2770
|
-
"promote-action",
|
|
2771
|
-
"set-focus"
|
|
2772
|
-
]),
|
|
2773
|
-
slotId: z2.string().optional(),
|
|
2774
|
-
nodeId: z2.string().optional(),
|
|
2775
|
-
toSlotId: z2.string().optional(),
|
|
2776
|
-
index: z2.number().optional(),
|
|
2777
|
-
node: z2.object({
|
|
2778
|
-
nodeId: z2.string(),
|
|
2779
|
-
kind: z2.string(),
|
|
2780
|
-
title: z2.string().optional(),
|
|
2781
|
-
props: z2.record(z2.string(), z2.unknown()).optional(),
|
|
2782
|
-
children: z2.array(z2.unknown()).optional()
|
|
2783
|
-
}).optional(),
|
|
2784
|
-
persistKey: z2.string().optional(),
|
|
2785
|
-
sizes: z2.array(z2.number()).optional(),
|
|
2786
|
-
layoutId: z2.string().optional(),
|
|
2787
|
-
fieldId: z2.string().optional(),
|
|
2788
|
-
actionId: z2.string().optional(),
|
|
2789
|
-
placement: z2.enum(["header", "inline", "context", "assistant"]).optional(),
|
|
2790
|
-
targetId: z2.string().optional()
|
|
2791
|
-
}))
|
|
2792
|
-
});
|
|
2793
|
-
function createSurfacePlannerTools(config) {
|
|
2794
|
-
const { plan, constraints, onPatchProposal } = config;
|
|
2795
|
-
const resolvedConstraints = constraints ?? deriveConstraints(plan);
|
|
2796
|
-
const proposePatchTool = tool2({
|
|
2797
|
-
description: "Propose surface patches (layout changes, node insertions, etc.) for user approval. " + "Only use allowed ops, slots, and node kinds from the planner context.",
|
|
2798
|
-
inputSchema: ProposePatchInputSchema,
|
|
2799
|
-
execute: async (input) => {
|
|
2800
|
-
const ops = input.ops;
|
|
2801
|
-
try {
|
|
2802
|
-
validatePatchProposal(ops, resolvedConstraints);
|
|
2803
|
-
const proposal = buildSurfacePatchProposal(input.proposalId, ops);
|
|
2804
|
-
onPatchProposal?.(proposal);
|
|
2805
|
-
return {
|
|
2806
|
-
success: true,
|
|
2807
|
-
proposalId: proposal.proposalId,
|
|
2808
|
-
opsCount: proposal.ops.length,
|
|
2809
|
-
message: "Patch proposal validated; awaiting user approval"
|
|
2810
|
-
};
|
|
2811
|
-
} catch (err) {
|
|
2812
|
-
return {
|
|
2813
|
-
success: false,
|
|
2814
|
-
error: err instanceof Error ? err.message : String(err),
|
|
2815
|
-
proposalId: input.proposalId
|
|
2816
|
-
};
|
|
2817
|
-
}
|
|
2818
|
-
}
|
|
2819
|
-
});
|
|
2820
|
-
return {
|
|
2821
|
-
"propose-patch": proposePatchTool
|
|
2822
|
-
};
|
|
2823
|
-
}
|
|
2824
|
-
function buildPlannerPromptInput(plan) {
|
|
2825
|
-
const constraints = deriveConstraints(plan);
|
|
2826
|
-
return {
|
|
2827
|
-
bundleMeta: {
|
|
2828
|
-
key: plan.bundleKey,
|
|
2829
|
-
version: "0.0.0",
|
|
2830
|
-
title: plan.bundleKey
|
|
2831
|
-
},
|
|
2832
|
-
surfaceId: plan.surfaceId,
|
|
2833
|
-
allowedPatchOps: constraints.allowedOps,
|
|
2834
|
-
allowedSlots: [...constraints.allowedSlots],
|
|
2835
|
-
allowedNodeKinds: [...constraints.allowedNodeKinds],
|
|
2836
|
-
actions: plan.actions.map((a) => ({
|
|
2837
|
-
actionId: a.actionId,
|
|
2838
|
-
title: a.title
|
|
2839
|
-
})),
|
|
2840
|
-
preferences: {
|
|
2841
|
-
guidance: "hints",
|
|
2842
|
-
density: "standard",
|
|
2843
|
-
dataDepth: "detailed",
|
|
2844
|
-
control: "standard",
|
|
2845
|
-
media: "text",
|
|
2846
|
-
pace: "balanced",
|
|
2847
|
-
narrative: "top-down"
|
|
2848
|
-
}
|
|
2849
|
-
};
|
|
2850
|
-
}
|
|
2851
|
-
|
|
2852
|
-
// src/core/workflow-tools.ts
|
|
2853
|
-
import {
|
|
2854
|
-
validateExtension,
|
|
2855
|
-
WorkflowComposer
|
|
2856
|
-
} from "@contractspec/lib.workflow-composer";
|
|
2857
|
-
import { tool as tool3 } from "ai";
|
|
2858
|
-
import { z as z3 } from "zod";
|
|
2859
|
-
var StepTypeSchema = z3.enum(["human", "automation", "decision"]);
|
|
2860
|
-
var StepActionSchema = z3.object({
|
|
2861
|
-
operation: z3.object({
|
|
2862
|
-
name: z3.string(),
|
|
2863
|
-
version: z3.number()
|
|
2864
|
-
}).optional(),
|
|
2865
|
-
form: z3.object({
|
|
2866
|
-
key: z3.string(),
|
|
2867
|
-
version: z3.number()
|
|
2868
|
-
}).optional()
|
|
2869
|
-
}).optional();
|
|
2870
|
-
var StepSchema = z3.object({
|
|
2871
|
-
id: z3.string(),
|
|
2872
|
-
type: StepTypeSchema,
|
|
2873
|
-
label: z3.string(),
|
|
2874
|
-
description: z3.string().optional(),
|
|
2875
|
-
action: StepActionSchema
|
|
2876
|
-
});
|
|
2877
|
-
var StepInjectionSchema = z3.object({
|
|
2878
|
-
after: z3.string().optional(),
|
|
2879
|
-
before: z3.string().optional(),
|
|
2880
|
-
inject: StepSchema,
|
|
2881
|
-
transitionTo: z3.string().optional(),
|
|
2882
|
-
transitionFrom: z3.string().optional(),
|
|
2883
|
-
when: z3.string().optional()
|
|
2884
|
-
});
|
|
2885
|
-
var WorkflowExtensionInputSchema = z3.object({
|
|
2886
|
-
workflow: z3.string(),
|
|
2887
|
-
tenantId: z3.string().optional(),
|
|
2888
|
-
role: z3.string().optional(),
|
|
2889
|
-
priority: z3.number().optional(),
|
|
2890
|
-
customSteps: z3.array(StepInjectionSchema).optional(),
|
|
2891
|
-
hiddenSteps: z3.array(z3.string()).optional()
|
|
2892
|
-
});
|
|
2893
|
-
function createWorkflowTools(config) {
|
|
2894
|
-
const { baseWorkflows, composer } = config;
|
|
2895
|
-
const baseByKey = new Map(baseWorkflows.map((b) => [b.meta.key, b]));
|
|
2896
|
-
const createWorkflowExtensionTool = tool3({
|
|
2897
|
-
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.",
|
|
2898
|
-
inputSchema: WorkflowExtensionInputSchema,
|
|
2899
|
-
execute: async (input) => {
|
|
2900
|
-
const extension = {
|
|
2901
|
-
workflow: input.workflow,
|
|
2902
|
-
tenantId: input.tenantId,
|
|
2903
|
-
role: input.role,
|
|
2904
|
-
priority: input.priority,
|
|
2905
|
-
customSteps: input.customSteps,
|
|
2906
|
-
hiddenSteps: input.hiddenSteps
|
|
2907
|
-
};
|
|
2908
|
-
const base = baseByKey.get(input.workflow);
|
|
2909
|
-
if (!base) {
|
|
2910
|
-
return {
|
|
2911
|
-
success: false,
|
|
2912
|
-
error: `Base workflow "${input.workflow}" not found. Available: ${Array.from(baseByKey.keys()).join(", ")}`,
|
|
2913
|
-
extension
|
|
2914
|
-
};
|
|
2915
|
-
}
|
|
2916
|
-
try {
|
|
2917
|
-
validateExtension(extension, base);
|
|
2918
|
-
return {
|
|
2919
|
-
success: true,
|
|
2920
|
-
message: "Extension validated successfully",
|
|
2921
|
-
extension
|
|
2922
|
-
};
|
|
2923
|
-
} catch (err) {
|
|
2924
|
-
return {
|
|
2925
|
-
success: false,
|
|
2926
|
-
error: err instanceof Error ? err.message : String(err),
|
|
2927
|
-
extension
|
|
2928
|
-
};
|
|
2929
|
-
}
|
|
2930
|
-
}
|
|
2931
|
-
});
|
|
2932
|
-
const composeWorkflowInputSchema = z3.object({
|
|
2933
|
-
workflowKey: z3.string().describe("Base workflow meta.key"),
|
|
2934
|
-
tenantId: z3.string().optional(),
|
|
2935
|
-
role: z3.string().optional(),
|
|
2936
|
-
extensions: z3.array(WorkflowExtensionInputSchema).optional().describe("Extensions to register before composing")
|
|
2937
|
-
});
|
|
2938
|
-
const composeWorkflowTool = tool3({
|
|
2939
|
-
description: "Compose a workflow by applying registered extensions to a base workflow. Returns the composed WorkflowSpec.",
|
|
2940
|
-
inputSchema: composeWorkflowInputSchema,
|
|
2941
|
-
execute: async (input) => {
|
|
2942
|
-
const base = baseByKey.get(input.workflowKey);
|
|
2943
|
-
if (!base) {
|
|
2944
|
-
return {
|
|
2945
|
-
success: false,
|
|
2946
|
-
error: `Base workflow "${input.workflowKey}" not found. Available: ${Array.from(baseByKey.keys()).join(", ")}`
|
|
2947
|
-
};
|
|
2948
|
-
}
|
|
2949
|
-
const comp = composer ?? new WorkflowComposer;
|
|
2950
|
-
if (input.extensions?.length) {
|
|
2951
|
-
for (const ext of input.extensions) {
|
|
2952
|
-
comp.register({
|
|
2953
|
-
workflow: ext.workflow,
|
|
2954
|
-
tenantId: ext.tenantId,
|
|
2955
|
-
role: ext.role,
|
|
2956
|
-
priority: ext.priority,
|
|
2957
|
-
customSteps: ext.customSteps,
|
|
2958
|
-
hiddenSteps: ext.hiddenSteps
|
|
2959
|
-
});
|
|
2960
|
-
}
|
|
2961
|
-
}
|
|
2962
|
-
try {
|
|
2963
|
-
const composed = comp.compose({
|
|
2964
|
-
base,
|
|
2965
|
-
tenantId: input.tenantId,
|
|
2966
|
-
role: input.role
|
|
2967
|
-
});
|
|
2968
|
-
return {
|
|
2969
|
-
success: true,
|
|
2970
|
-
workflow: composed,
|
|
2971
|
-
meta: composed.meta,
|
|
2972
|
-
stepIds: composed.definition.steps.map((s) => s.id)
|
|
2973
|
-
};
|
|
2974
|
-
} catch (err) {
|
|
2975
|
-
return {
|
|
2976
|
-
success: false,
|
|
2977
|
-
error: err instanceof Error ? err.message : String(err)
|
|
2978
|
-
};
|
|
2979
|
-
}
|
|
2980
|
-
}
|
|
2981
|
-
});
|
|
2982
|
-
const generateWorkflowSpecCodeInputSchema = z3.object({
|
|
2983
|
-
workflowKey: z3.string().describe("Workflow meta.key"),
|
|
2984
|
-
composedSteps: z3.array(z3.object({
|
|
2985
|
-
id: z3.string(),
|
|
2986
|
-
type: z3.enum(["human", "automation", "decision"]),
|
|
2987
|
-
label: z3.string(),
|
|
2988
|
-
description: z3.string().optional()
|
|
2989
|
-
})).optional().describe("Steps to include; if omitted, uses the base workflow")
|
|
2990
|
-
});
|
|
2991
|
-
const generateWorkflowSpecCodeTool = tool3({
|
|
2992
|
-
description: "Generate TypeScript code for a workflow spec. Use after composing a workflow to output the spec as code the user can save.",
|
|
2993
|
-
inputSchema: generateWorkflowSpecCodeInputSchema,
|
|
2994
|
-
execute: async (input) => {
|
|
2995
|
-
const base = baseByKey.get(input.workflowKey);
|
|
2996
|
-
if (!base) {
|
|
2997
|
-
return {
|
|
2998
|
-
success: false,
|
|
2999
|
-
error: `Base workflow "${input.workflowKey}" not found. Available: ${Array.from(baseByKey.keys()).join(", ")}`,
|
|
3000
|
-
code: null
|
|
3001
|
-
};
|
|
3002
|
-
}
|
|
3003
|
-
const steps = input.composedSteps ?? base.definition.steps;
|
|
3004
|
-
const specVarName = toPascalCase((base.meta.key.split(".").pop() ?? "Workflow") + "") + "Workflow";
|
|
3005
|
-
const stepsCode = steps.map((s) => ` {
|
|
3006
|
-
id: '${s.id}',
|
|
3007
|
-
type: '${s.type}',
|
|
3008
|
-
label: '${escapeString(s.label)}',${s.description ? `
|
|
3009
|
-
description: '${escapeString(s.description)}',` : ""}
|
|
51
|
+
`)}function e2($,Z){let Y={messages:$.map(r2)};if(Z)Y.conversation={id:Z.id,title:Z.title,status:Z.status,createdAt:C0(Z.createdAt),updatedAt:C0(Z.updatedAt),provider:Z.provider,model:Z.model,workspacePath:Z.workspacePath,contextFiles:Z.contextFiles,summary:Z.summary,metadata:Z.metadata};return JSON.stringify(Y,null,2)}function $4($,Z){let Y=new Date().toISOString().replace(/[:.]/g,"-").slice(0,19);return`${Z?.title?Z.title.replace(/[^a-zA-Z0-9-_]/g,"_").slice(0,40):"chat-export"}-${Y}.${$==="markdown"?"md":$==="txt"?"txt":"json"}`}var Z4={markdown:"text/markdown",txt:"text/plain",json:"application/json"};function Y4($,Z,Y){let J=new Blob([$],{type:Y}),Q=URL.createObjectURL(J),X=document.createElement("a");X.href=Q,X.download=Z,document.body.appendChild(X),X.click(),document.body.removeChild(X),URL.revokeObjectURL(Q)}function f1($,Z,Y){let J;if(Z==="markdown")J=n0($);else if(Z==="txt")J=s2($);else J=e2($,Y);let Q=$4(Z,Y),X=Z4[Z];Y4(J,Q,X)}import{jsx as Q0,jsxs as X0,Fragment as V4}from"react/jsx-runtime";function t0({messages:$,conversation:Z,selectedIds:Y,onExported:J,showSelectionSummary:Q=!0,onSelectAll:X,onClearSelection:q,selectedCount:H=Y.size,totalCount:K=$.length,onCreateNew:W,onFork:G}){let[V,_]=K0.useState(!1),F=K0.useMemo(()=>{if(Y.size>0){let k=Y;return $.filter((L)=>k.has(L.id))}return $},[$,Y]),E=K0.useCallback((k)=>{f1(F,k,Z),J?.(k,F.length)},[F,Z,J]),A=K0.useCallback(async()=>{let k=n0(F);await navigator.clipboard.writeText(k),_(!0),setTimeout(()=>_(!1),2000),J?.("markdown",F.length)},[F,J]),P=$.length===0,[z,B]=K0.useState(!1),x=K0.useCallback(async(k)=>{if(!G)return;B(!0);try{await G(k)}finally{B(!1)}},[G]);return X0("div",{className:"flex items-center gap-2",children:[W&&X0(k0,{variant:"outline",size:"sm",onPress:W,"aria-label":"New conversation",children:[Q0(_4,{className:"h-4 w-4"}),"New"]}),G&&$.length>0&&X0(k0,{variant:"outline",size:"sm",disabled:z,onPress:()=>x(),"aria-label":"Fork conversation",children:[Q0(K4,{className:"h-4 w-4"}),"Fork"]}),Q&&H>0&&X0("span",{className:"text-muted-foreground text-sm",children:[H," message",H!==1?"s":""," selected"]}),X&&q&&K>0&&X0(V4,{children:[Q0(k0,{variant:"ghost",size:"sm",onPress:X,className:"text-xs",children:"Select all"}),H>0&&Q0(k0,{variant:"ghost",size:"sm",onPress:q,className:"text-xs",children:"Clear"})]}),X0(J4,{children:[Q0(q4,{asChild:!0,children:X0(k0,{variant:"outline",size:"sm",disabled:P,"aria-label":H>0?"Export selected messages":"Export conversation",children:[Q0(W4,{className:"h-4 w-4"}),"Export"]})}),X0(Q4,{align:"end",children:[X0(f0,{onSelect:()=>E("markdown"),disabled:P,children:[Q0(o0,{className:"h-4 w-4"}),"Export as Markdown (.md)"]}),X0(f0,{onSelect:()=>E("txt"),disabled:P,children:[Q0(o0,{className:"h-4 w-4"}),"Export as Plain Text (.txt)"]}),X0(f0,{onSelect:()=>E("json"),disabled:P,children:[Q0(o0,{className:"h-4 w-4"}),"Export as JSON (.json)"]}),Q0(X4,{}),X0(f0,{onSelect:()=>A(),disabled:P,children:[V?Q0(H4,{className:"h-4 w-4 text-green-500"}):Q0(G4,{className:"h-4 w-4"}),V?"Copied to clipboard":"Copy to clipboard"]})]})]})]})}import{Button as h1,Textarea as z4}from"@contractspec/lib.design-system";import{cn as s0}from"@contractspec/lib.ui-kit-web/ui/utils";import{Code as U4,FileText as D4,ImageIcon as O4,Loader2 as B4,Paperclip as A4,Send as F4,X as P4}from"lucide-react";import*as q0 from"react";import{jsx as c,jsxs as h0,Fragment as y4}from"react/jsx-runtime";var L4=5242880,N4=["ts","tsx","js","jsx","py","go","rs","java","json","md","txt","yaml","yml"];function E4($){let Z=$.name.split(".").pop()?.toLowerCase()??"",Y=N4.includes(Z);if($.type.startsWith("image/"))return new Promise((Q,X)=>{let q=new FileReader;q.onload=()=>{let H=q.result;Q({content:typeof H==="string"?H:new TextDecoder().decode(H??new ArrayBuffer(0)),type:"image"})},q.onerror=()=>X(Error("Could not read file")),q.readAsDataURL($)});return $.text().then((Q)=>({content:Q,type:Y?"code":"file"})).catch(()=>{throw Error("Could not read file")})}function e0({onSend:$,disabled:Z=!1,isLoading:Y=!1,placeholder:J="Type a message...",className:Q,showAttachments:X=!0,maxAttachments:q=5,maxFileSizeBytes:H=L4}){let[K,W]=q0.useState(""),[G,V]=q0.useState([]),[_,F]=q0.useState(null),E=q0.useRef(null),A=q0.useRef(null),P=K.trim().length>0||G.length>0,z=q0.useCallback((L)=>{if(L?.preventDefault(),!P||Z||Y)return;$(K.trim(),G.length>0?G:void 0),W(""),V([]),F(null),E.current?.focus()},[P,K,G,Z,Y,$]),B=q0.useCallback((L)=>{if(L.key==="Enter"&&!L.shiftKey)L.preventDefault(),z()},[z]),x=q0.useCallback(async(L)=>{let C=L.target.files;if(!C)return;F(null);let y=[],i=[];for(let v of Array.from(C)){if(G.length+y.length>=q){i.push(`Maximum ${q} attachments allowed`);break}if(v.size>H){i.push(`${v.name} exceeds ${Math.round(H/1024/1024)}MB limit`);continue}try{let{content:g,type:J0}=await E4(v);y.push({id:`att_${Date.now()}_${Math.random().toString(36).slice(2,9)}`,type:J0,name:v.name,content:g,mimeType:v.type,size:v.size})}catch{i.push(`Could not read ${v.name}`)}}if(i.length>0)F(i[0]??"Could not add file");if(y.length>0)V((v)=>[...v,...y]);L.target.value=""},[G.length,q,H]),k=q0.useCallback((L)=>{V((C)=>C.filter((y)=>y.id!==L))},[]);return h0("div",{className:s0("flex flex-col gap-2",Q),children:[G.length>0&&c("div",{className:"flex flex-wrap gap-2",children:G.map((L)=>h0("div",{className:s0("flex items-center gap-1.5 rounded-md px-2 py-1","bg-muted text-muted-foreground text-sm"),children:[L.type==="code"?c(U4,{className:"h-3.5 w-3.5"}):L.type==="image"?c(O4,{className:"h-3.5 w-3.5"}):c(D4,{className:"h-3.5 w-3.5"}),c("span",{className:"max-w-[150px] truncate",children:L.name}),c("button",{type:"button",onClick:()=>k(L.id),className:"hover:text-foreground","aria-label":`Remove ${L.name}`,children:c(P4,{className:"h-3.5 w-3.5"})})]},L.id))}),_&&c("p",{className:"text-destructive text-xs",role:"alert",children:_}),h0("form",{onSubmit:z,className:"flex items-end gap-2",children:[X&&h0(y4,{children:[c("input",{ref:A,type:"file",multiple:!0,accept:".ts,.tsx,.js,.jsx,.json,.md,.txt,.py,.go,.rs,.java,.yaml,.yml,image/*",onChange:x,className:"hidden","aria-label":"Attach files"}),c(h1,{type:"button",variant:"ghost",size:"sm",onPress:()=>A.current?.click(),disabled:Z||G.length>=q,"aria-label":"Attach files",children:c(A4,{className:"h-4 w-4"})})]}),c("div",{className:"relative flex-1",children:c(z4,{value:K,onChange:(L)=>W(L.target.value),onKeyDown:B,placeholder:J,disabled:Z,className:s0("max-h-[200px] min-h-[44px] resize-none pr-12","focus-visible:ring-1"),rows:1,"aria-label":"Chat message"})}),c(h1,{type:"submit",disabled:!P||Z||Y,size:"sm","aria-label":Y?"Sending...":"Send message",children:Y?c(B4,{className:"h-4 w-4 animate-spin"}):c(F4,{className:"h-4 w-4"})})]}),c("p",{className:"text-muted-foreground text-xs",children:"Press Enter to send, Shift+Enter for new line"})]})}import{Button as v0}from"@contractspec/lib.design-system";import{Avatar as u4,AvatarFallback as p4}from"@contractspec/lib.ui-kit-web/ui/avatar";import{Checkbox as g4}from"@contractspec/lib.ui-kit-web/ui/checkbox";import{Skeleton as c1}from"@contractspec/lib.ui-kit-web/ui/skeleton";import{cn as F0}from"@contractspec/lib.ui-kit-web/ui/utils";import{AlertCircle as m4,Bot as d4,Check as i1,Copy as l4,Pencil as c4,User as i4,Wrench as r4,X as a4}from"lucide-react";import*as n from"react";import{Button as $1}from"@contractspec/lib.design-system";import{cn as j1}from"@contractspec/lib.ui-kit-web/ui/utils";import{Check as R4,Copy as T4,Download as b4,Play as k4}from"lucide-react";import*as w0 from"react";import{jsx as t,jsxs as M0}from"react/jsx-runtime";var M4={ts:"TypeScript",tsx:"TypeScript (React)",typescript:"TypeScript",js:"JavaScript",jsx:"JavaScript (React)",javascript:"JavaScript",json:"JSON",md:"Markdown",yaml:"YAML",yml:"YAML",bash:"Bash",sh:"Shell",sql:"SQL",py:"Python",python:"Python",go:"Go",rust:"Rust",rs:"Rust"};function Z1({code:$,language:Z="text",filename:Y,className:J,showCopy:Q=!0,showExecute:X=!1,onExecute:q,showDownload:H=!1,maxHeight:K=400}){let[W,G]=w0.useState(!1),V=M4[Z.toLowerCase()]??Z,_=$.split(`
|
|
52
|
+
`),F=w0.useCallback(async()=>{await navigator.clipboard.writeText($),G(!0),setTimeout(()=>G(!1),2000)},[$]),E=w0.useCallback(()=>{let A=new Blob([$],{type:"text/plain"}),P=URL.createObjectURL(A),z=document.createElement("a");z.href=P,z.download=Y??`code.${Z}`,document.body.appendChild(z),z.click(),document.body.removeChild(z),URL.revokeObjectURL(P)},[$,Y,Z]);return M0("div",{className:j1("overflow-hidden rounded-lg border","bg-muted/50",J),children:[M0("div",{className:j1("flex items-center justify-between px-3 py-1.5","border-b bg-muted/80"),children:[M0("div",{className:"flex items-center gap-2 text-sm",children:[Y&&t("span",{className:"font-mono text-foreground",children:Y}),t("span",{className:"text-muted-foreground",children:V})]}),M0("div",{className:"flex items-center gap-1",children:[X&&q&&t($1,{variant:"ghost",size:"sm",onPress:()=>q($),className:"h-7 w-7 p-0","aria-label":"Execute code",children:t(k4,{className:"h-3.5 w-3.5"})}),H&&t($1,{variant:"ghost",size:"sm",onPress:E,className:"h-7 w-7 p-0","aria-label":"Download code",children:t(b4,{className:"h-3.5 w-3.5"})}),Q&&t($1,{variant:"ghost",size:"sm",onPress:F,className:"h-7 w-7 p-0","aria-label":W?"Copied":"Copy code",children:W?t(R4,{className:"h-3.5 w-3.5 text-green-500"}):t(T4,{className:"h-3.5 w-3.5"})})]})]}),t("div",{className:"overflow-auto",style:{maxHeight:K},children:t("pre",{className:"p-3",children:t("code",{className:"text-sm",children:_.map((A,P)=>M0("div",{className:"flex",children:[t("span",{className:"mr-4 w-8 select-none text-right text-muted-foreground",children:P+1}),t("span",{className:"flex-1",children:A||" "})]},P))})})})]})}import{Collapsible as w4,CollapsibleContent as S4,CollapsibleTrigger as I4}from"@contractspec/lib.ui-kit-web/ui/collapsible";import{cn as Y1}from"@contractspec/lib.ui-kit-web/ui/utils";import*as z0 from"react";import{jsx as S0,jsxs as C4}from"react/jsx-runtime";function J1({isStreaming:$=!1,open:Z,defaultOpen:Y=!1,onOpenChange:J,children:Q,className:X}){let[q,H]=z0.useState(Y),K=z0.useRef($),W=Z!==void 0;z0.useEffect(()=>{if($)if(W)J?.(!0);else H(!0);else if(K.current)if(W)J?.(!1);else H(!1);K.current=$},[$,W,J]);let G=z0.useCallback((V)=>{if(W)J?.(V);else H(V)},[W,J]);return S0(w4,{open:W?Z:q,onOpenChange:G,className:Y1("w-full",X),children:Q})}function Q1({children:$,isStreaming:Z=!1,className:Y}){return C4(I4,{className:Y1("flex w-full cursor-pointer items-center gap-2 rounded-md px-2 py-1.5 text-muted-foreground text-sm transition-colors hover:bg-muted hover:text-foreground",Y),children:[Z&&S0("span",{className:"h-1.5 w-1.5 animate-pulse rounded-full bg-primary","aria-hidden":!0}),$??(Z?"Thinking...":"View reasoning")]})}function X1({children:$,className:Z}){return S0(S4,{children:S0("div",{className:Y1("mt-1 rounded-md bg-muted p-2 text-muted-foreground text-sm",Z),children:S0("p",{className:"whitespace-pre-wrap",children:$})})})}import{Collapsible as f4,CollapsibleContent as h4,CollapsibleTrigger as j4}from"@contractspec/lib.ui-kit-web/ui/collapsible";import{cn as j0}from"@contractspec/lib.ui-kit-web/ui/utils";import{ExternalLink as x1}from"lucide-react";import{jsx as A0,jsxs as u1,Fragment as v1}from"react/jsx-runtime";function q1({children:$,className:Z}){return A0(f4,{className:j0("mt-2",Z),defaultOpen:!1,children:$})}function H1({count:$,children:Z,className:Y}){return A0(j4,{className:j0("inline-flex cursor-pointer items-center gap-1.5 rounded-md px-2 py-1 text-muted-foreground text-xs transition-colors hover:bg-muted hover:text-foreground",Y),children:Z??u1(v1,{children:[A0(x1,{className:"h-3 w-3"}),$," source",$!==1?"s":""]})})}function G1({children:$,className:Z}){return A0(h4,{children:A0("div",{className:j0("mt-2 flex flex-wrap gap-2",Z),children:$})})}function W1({href:$,title:Z,className:Y,children:J,...Q}){return A0("a",{href:$,target:"_blank",rel:"noopener noreferrer",className:j0("inline-flex items-center gap-1 rounded-md bg-muted px-2 py-1 text-muted-foreground text-xs transition-colors hover:text-foreground",Y),...Q,children:J??u1(v1,{children:[A0(x1,{className:"h-3 w-3"}),Z??$]})})}import{jsx as $0,jsxs as v4}from"react/jsx-runtime";function p1($){return typeof $==="object"&&$!==null&&"parts"in $&&Array.isArray($.parts)}function g1($){return typeof $==="object"&&$!==null&&"presentationKey"in $&&typeof $.presentationKey==="string"}function m1($){return typeof $==="object"&&$!==null&&"formKey"in $&&typeof $.formKey==="string"}function d1($){return typeof $==="object"&&$!==null&&"dataViewKey"in $&&typeof $.dataViewKey==="string"}function x4({part:$,presentationRenderer:Z,formRenderer:Y,dataViewRenderer:J,depth:Q=0}){if($===null||$===void 0)return null;let X=$;if(X.type==="text"&&typeof X.text==="string")return $0("p",{className:"whitespace-pre-wrap text-sm",children:X.text},Q);if(X.type&&String(X.type).startsWith("tool-")&&X.output){let q=X.output;if(p1(q))return $0("div",{className:"ml-2 border-border border-l-2 pl-2",children:$0(l1,{parts:q.parts??[],presentationRenderer:Z,formRenderer:Y,dataViewRenderer:J,depth:Q+1})},Q);return $0("pre",{className:"overflow-x-auto rounded bg-background p-2 text-xs",children:typeof q==="object"?JSON.stringify(q,null,2):String(q)},Q)}return null}function l1({parts:$,presentationRenderer:Z,formRenderer:Y,dataViewRenderer:J,depth:Q=0}){if($.length===0)return null;return $0("div",{className:"space-y-2",children:$.map((X,q)=>$0(x4,{part:X,presentationRenderer:Z,formRenderer:Y,dataViewRenderer:J,depth:Q},`${Q}-${q}`))})}function x0({toolName:$,result:Z,presentationRenderer:Y,formRenderer:J,dataViewRenderer:Q,showRawFallback:X=!0,renderNestedUIMessage:q=!0}){if(Z===void 0||Z===null)return null;if(q&&p1(Z)&&(Z.parts?.length??0)>0)return $0("div",{className:"mt-2 rounded-md border border-border bg-background/50 p-3",children:$0(l1,{parts:Z.parts??[],presentationRenderer:Y,formRenderer:J,dataViewRenderer:Q})});if(g1(Z)&&Y){let H=Y(Z.presentationKey,Z.data);if(H!=null)return $0("div",{className:"mt-2 rounded-md border border-border bg-background/50 p-3",children:H})}if(m1(Z)&&J){let H=J(Z.formKey,Z.defaultValues);if(H!=null)return $0("div",{className:"mt-2 rounded-md border border-border bg-background/50 p-3",children:H})}if(d1(Z)&&Q){let H=Q(Z.dataViewKey,Z.items);if(H!=null)return $0("div",{className:"mt-2 rounded-md border border-border bg-background/50 p-3",children:H})}if(!X)return null;return v4("div",{children:[$0("span",{className:"font-medium text-muted-foreground",children:"Output:"}),$0("pre",{className:"mt-1 overflow-x-auto rounded bg-background p-2 text-xs",children:typeof Z==="object"?JSON.stringify(Z,null,2):String(Z)})]})}import{jsx as U,jsxs as d,Fragment as s4}from"react/jsx-runtime";function n4($){let Z=/```(\w+)?\n([\s\S]*?)```/g,Y=[],J;while((J=Z.exec($))!==null)Y.push({language:J[1]??"text",code:J[2]??"",raw:J[0]});return Y}function K1($){let Z=/\[([^\]]+)\]\(([^)]+)\)/g,Y=[],J=0,Q,X=0;while((Q=Z.exec($))!==null){if(Q.index>J)Y.push(U("span",{children:$.slice(J,Q.index)},X++));Y.push(U("a",{href:Q[2],target:"_blank",rel:"noopener noreferrer",className:"text-primary underline hover:no-underline",children:Q[1]},X++)),J=Q.index+Q[0].length}if(J<$.length)Y.push(U("span",{children:$.slice(J)},X++));return Y.length>0?Y:[$]}function o4({content:$}){let Z=n4($);if(Z.length===0)return U("p",{className:"whitespace-pre-wrap",children:K1($)});let Y=$,J=[],Q=0;for(let X of Z){let[q,H]=Y.split(X.raw);if(q)J.push(U("p",{className:"whitespace-pre-wrap",children:K1(q.trim())},Q++));J.push(U(Z1,{code:X.code,language:X.language,className:"my-2"},Q++)),Y=H??""}if(Y.trim())J.push(U("p",{className:"whitespace-pre-wrap",children:K1(Y.trim())},Q++));return U(s4,{children:J})}function t4($){if($==="completed")return"complete";if($==="running")return"active";return"pending"}function _1({message:$,className:Z,showCopy:Y=!0,showAvatar:J=!0,selectable:Q=!1,selected:X=!1,onSelect:q,editable:H=!1,onEdit:K,presentationRenderer:W,formRenderer:G,dataViewRenderer:V,components:_}){let[F,E]=n.useState(!1),A=$.role==="user",P=$.status==="error",z=$.status==="streaming",B=n.useCallback(async()=>{await navigator.clipboard.writeText($.content),E(!0),setTimeout(()=>E(!1),2000)},[$.content]),x=n.useCallback((M)=>{if(M!=="indeterminate")q?.($.id)},[$.id,q]),[k,L]=n.useState(!1),[C,y]=n.useState($.content),i=n.useRef(null);n.useEffect(()=>{y($.content)},[$.content]),n.useEffect(()=>{if(k)i.current?.focus()},[k]);let v=n.useCallback(()=>{y($.content),L(!0)},[$.content]),g=n.useCallback(async()=>{let M=C.trim();if(M!==$.content)await K?.($.id,M);L(!1)},[C,$.id,$.content,K]),J0=n.useCallback(()=>{y($.content),L(!1)},[$.content]);return d("div",{className:F0("group flex gap-3",A&&"flex-row-reverse",Z),children:[Q&&U("div",{className:F0("flex shrink-0 items-start pt-1","opacity-0 transition-opacity group-hover:opacity-100"),children:U(g4,{checked:X,onCheckedChange:x,"aria-label":X?"Deselect message":"Select message"})}),J&&U(u4,{className:"h-8 w-8 shrink-0",children:U(p4,{className:F0(A?"bg-primary text-primary-foreground":"bg-muted"),children:A?U(i4,{className:"h-4 w-4"}):U(d4,{className:"h-4 w-4"})})}),d("div",{className:F0("flex max-w-[80%] flex-col gap-1",A&&"items-end"),children:[U("div",{className:F0("rounded-2xl px-4 py-2",A?"bg-primary text-primary-foreground":"bg-muted text-foreground",P&&"border border-destructive bg-destructive/10"),children:P&&$.error?d("div",{className:"flex items-start gap-2",children:[U(m4,{className:"mt-0.5 h-4 w-4 shrink-0 text-destructive"}),d("div",{children:[U("p",{className:"font-medium text-destructive",children:$.error.code}),U("p",{className:"text-muted-foreground text-sm",children:$.error.message})]})]}):k?d("div",{className:"flex flex-col gap-2",children:[U("textarea",{ref:i,value:C,onChange:(M)=>y(M.target.value),className:"min-h-[80px] w-full resize-y rounded-md border bg-background/50 px-3 py-2 text-sm",rows:4,"aria-label":"Edit message"}),d("div",{className:"flex gap-2",children:[d(v0,{variant:"default",size:"sm",onPress:g,"aria-label":"Save edit",children:[U(i1,{className:"h-3 w-3"}),"Save"]}),d(v0,{variant:"ghost",size:"sm",onPress:J0,"aria-label":"Cancel edit",children:[U(a4,{className:"h-3 w-3"}),"Cancel"]})]})]}):z&&!$.content?d("div",{className:"flex flex-col gap-2",children:[U(c1,{className:"h-4 w-48"}),U(c1,{className:"h-4 w-32"})]}):U(o4,{content:$.content})}),d("div",{className:F0("flex items-center gap-2 text-xs","text-muted-foreground opacity-0 transition-opacity","group-hover:opacity-100"),children:[U("span",{children:new Date($.createdAt).toLocaleTimeString([],{hour:"2-digit",minute:"2-digit"})}),$.usage&&d("span",{children:[$.usage.inputTokens+$.usage.outputTokens," tokens"]}),Y&&!A&&$.content&&U(v0,{variant:"ghost",size:"sm",className:"h-6 w-6 p-0",onPress:B,"aria-label":F?"Copied":"Copy message",children:F?U(i1,{className:"h-3 w-3"}):U(l4,{className:"h-3 w-3"})}),H&&A&&!k&&U(v0,{variant:"ghost",size:"sm",className:"h-6 w-6 p-0",onPress:v,"aria-label":"Edit message",children:U(c4,{className:"h-3 w-3"})})]}),$.reasoning&&(_?.Reasoning?U(_.Reasoning,{isStreaming:z&&!!$.reasoning,children:$.reasoning}):d(J1,{isStreaming:z&&!!$.reasoning,className:"mt-2",children:[U(Q1,{isStreaming:z}),U(X1,{children:$.reasoning})]})),$.sources&&$.sources.length>0&&(()=>{let M=_?.Sources,r=_?.SourcesTrigger,D=_?.Source;if(M&&r&&D)return d(M,{children:[U(r,{count:$.sources.length}),$.sources.map((s)=>U(D,{href:s.url??"#",title:s.title||s.url||s.id},s.id))]});return d(q1,{className:"mt-2",children:[U(H1,{count:$.sources.length}),U(G1,{children:$.sources.map((s)=>U(W1,{href:s.url??"#",title:s.title||s.url||s.id},s.id))})]})})(),$.toolCalls&&$.toolCalls.length>0&&(()=>{let M=_?.ChainOfThought,r=_?.ChainOfThoughtStep;if(M&&r)return U(M,{defaultOpen:!1,className:"mt-2",children:$.toolCalls.map((D)=>d(r,{label:D.name,description:Object.keys(D.args).length>0?`Input: ${JSON.stringify(D.args)}`:void 0,status:t4(D.status),children:[D.preliminary&&D.status==="running"&&U("p",{className:"mt-1 text-muted-foreground text-xs",children:"Running\u2026"}),(D.result!==void 0||D.nestedParts?.length)&&U(x0,{toolName:D.name,result:D.nestedParts?.length?{parts:D.nestedParts}:D.result,presentationRenderer:W,formRenderer:G,dataViewRenderer:V,showRawFallback:!0}),D.error&&U("p",{className:"mt-1 text-destructive text-xs",children:D.error})]},D.id))});return U("div",{className:"mt-2 space-y-2",children:$.toolCalls.map((D)=>d("details",{className:"rounded-md border border-border bg-muted",children:[d("summary",{className:"flex cursor-pointer items-center gap-2 px-3 py-2 font-medium text-sm",children:[U(r4,{className:"h-4 w-4 text-muted-foreground"}),D.name,U("span",{className:F0("ml-auto rounded px-1.5 py-0.5 text-xs",D.status==="completed"&&"bg-green-500/20 text-green-700 dark:text-green-400",D.status==="error"&&"bg-destructive/20 text-destructive",D.status==="running"&&"bg-blue-500/20 text-blue-700 dark:text-blue-400"),children:D.status})]}),d("div",{className:"border-border border-t px-3 py-2 text-xs",children:[Object.keys(D.args).length>0&&d("div",{className:"mb-2",children:[U("span",{className:"font-medium text-muted-foreground",children:"Input:"}),U("pre",{className:"mt-1 overflow-x-auto rounded bg-background p-2",children:JSON.stringify(D.args,null,2)})]}),D.preliminary&&D.status==="running"&&U("p",{className:"mt-1 text-muted-foreground text-xs",children:"Running\u2026"}),(D.result!==void 0||D.nestedParts?.length)&&U(x0,{toolName:D.name,result:D.nestedParts?.length?{parts:D.nestedParts}:D.result,presentationRenderer:W,formRenderer:G,dataViewRenderer:V,showRawFallback:!0}),D.error&&U("p",{className:"mt-1 text-destructive",children:D.error})]})]},D.id))})})()]})]})}import{Button as a1}from"@contractspec/lib.design-system";import{cn as n1}from"@contractspec/lib.ui-kit-web/ui/utils";import{MessageSquare as e4,Plus as $5,Trash2 as Z5}from"lucide-react";import*as D0 from"react";import*as U0 from"react";function r1($){let{store:Z,projectId:Y,tags:J,limit:Q=50}=$,[X,q]=U0.useState([]),[H,K]=U0.useState(!0),W=U0.useCallback(async()=>{K(!0);try{let V=await Z.list({status:"active",projectId:Y,tags:J,limit:Q});q(V)}finally{K(!1)}},[Z,Y,J,Q]);U0.useEffect(()=>{W()},[W]);let G=U0.useCallback(async(V)=>{let _=await Z.delete(V);if(_)q((F)=>F.filter((E)=>E.id!==V));return _},[Z]);return{conversations:X,isLoading:H,refresh:W,deleteConversation:G}}import{jsx as l,jsxs as P0,Fragment as X5}from"react/jsx-runtime";function Y5($){let Z=new Date($),J=new Date().getTime()-Z.getTime();if(J<86400000)return Z.toLocaleTimeString([],{hour:"2-digit",minute:"2-digit"});if(J<604800000)return Z.toLocaleDateString([],{weekday:"short"});return Z.toLocaleDateString([],{month:"short",day:"numeric"})}function J5({conversation:$,selected:Z,onSelect:Y,onDelete:J}){let Q=$.title??$.messages[0]?.content?.slice(0,50)??"New chat",X=Q.length>40?`${Q.slice(0,40)}\u2026`:Q;return P0("div",{role:"button",tabIndex:0,onClick:Y,onKeyDown:(q)=>{if(q.key==="Enter"||q.key===" ")q.preventDefault(),Y()},className:n1("group flex cursor-pointer items-center gap-2 rounded-md px-3 py-2 text-sm transition-colors",Z?"bg-accent text-accent-foreground":"hover:bg-accent/50"),children:[l(e4,{className:"h-4 w-4 shrink-0 text-muted-foreground"}),P0("div",{className:"min-w-0 flex-1",children:[l("p",{className:"truncate",children:X}),P0("p",{className:"text-muted-foreground text-xs",children:[Y5($.updatedAt),$.projectName&&` \xB7 ${$.projectName}`,$.tags&&$.tags.length>0&&P0(X5,{children:[" \xB7 ",$.tags.slice(0,2).join(", ")]})]})]}),l("span",{role:"group",onClick:(q)=>q.stopPropagation(),onKeyDown:(q)=>q.stopPropagation(),children:l(a1,{variant:"ghost",size:"sm",className:"h-6 w-6 shrink-0 p-0 opacity-0 group-hover:opacity-100",onPress:J,"aria-label":"Delete conversation",children:l(Z5,{className:"h-3 w-3"})})})]})}function V1({store:$,selectedConversationId:Z,onSelectConversation:Y,onCreateNew:J,projectId:Q,tags:X,limit:q=50,className:H,collapsed:K=!1,onUpdateConversation:W,selectedConversation:G}){let{conversations:V,isLoading:_,deleteConversation:F}=r1({store:$,projectId:Q,tags:X,limit:q}),E=D0.useCallback(async(A)=>{if(await F(A)&&Z===A)Y(null)},[F,Z,Y]);if(K)return null;return P0("div",{className:n1("flex w-64 shrink-0 flex-col border-border border-r",H),children:[P0("div",{className:"flex shrink-0 items-center justify-between border-border border-b p-2",children:[l("span",{className:"font-medium text-muted-foreground text-sm",children:"Conversations"}),l(a1,{variant:"ghost",size:"sm",className:"h-8 w-8 p-0",onPress:J,"aria-label":"New conversation",children:l($5,{className:"h-4 w-4"})})]}),l("div",{className:"flex-1 overflow-y-auto p-2",children:_?l("div",{className:"py-4 text-center text-muted-foreground text-sm",children:"Loading\u2026"}):V.length===0?l("div",{className:"py-4 text-center text-muted-foreground text-sm",children:"No conversations yet"}):l("div",{className:"flex flex-col gap-1",children:V.map((A)=>l(J5,{conversation:A,selected:A.id===Z,onSelect:()=>Y(A.id),onDelete:()=>E(A.id)},A.id))})}),G&&W&&l(Q5,{conversation:G,onUpdate:W})]})}function Q5({conversation:$,onUpdate:Z}){let[Y,J]=D0.useState($.projectName??""),[Q,X]=D0.useState($.tags?.join(", ")??"");D0.useEffect(()=>{J($.projectName??""),X($.tags?.join(", ")??"")},[$.id,$.projectName,$.tags]);let q=D0.useCallback(()=>{let H=Q.split(",").map((K)=>K.trim()).filter(Boolean);if(Y!==($.projectName??"")||JSON.stringify(H)!==JSON.stringify($.tags??[]))Z($.id,{projectName:Y||void 0,projectId:Y?Y.replace(/\s+/g,"-"):void 0,tags:H.length>0?H:void 0})},[$.id,$.projectName,$.tags,Y,Q,Z]);return P0("div",{className:"shrink-0 border-border border-t p-2",children:[l("p",{className:"mb-1 font-medium text-muted-foreground text-xs",children:"Project"}),l("input",{type:"text",value:Y,onChange:(H)=>J(H.target.value),onBlur:q,placeholder:"Project name",className:"mb-2 w-full rounded border-input bg-background px-2 py-1 text-xs"}),l("p",{className:"mb-1 font-medium text-muted-foreground text-xs",children:"Tags"}),l("input",{type:"text",value:Q,onChange:(H)=>X(H.target.value),onBlur:q,placeholder:"tag1, tag2",className:"w-full rounded border-input bg-background px-2 py-1 text-xs"})]})}import*as K2 from"react";import*as H0 from"react";function o1($){let[Z,Y]=H0.useState(()=>new Set),J=H0.useMemo(()=>new Set($),[$.join(",")]);H0.useEffect(()=>{Y((W)=>{let G=new Set;for(let V of W)if(J.has(V))G.add(V);return G.size===W.size?W:G})},[J]);let Q=H0.useCallback((W)=>{Y((G)=>{let V=new Set(G);if(V.has(W))V.delete(W);else V.add(W);return V})},[]),X=H0.useCallback(()=>{Y(new Set($))},[$.join(",")]),q=H0.useCallback(()=>{Y(new Set)},[]),H=H0.useCallback((W)=>Z.has(W),[Z]),K=Z.size;return{selectedIds:Z,toggle:Q,selectAll:X,clearSelection:q,isSelected:H,selectedCount:K}}import{Button as q5}from"@contractspec/lib.design-system";import{cn as t1}from"@contractspec/lib.ui-kit-web/ui/utils";import*as s1 from"react";import{jsx as e1}from"react/jsx-runtime";function z1({children:$,className:Z}){return e1("div",{className:t1("flex flex-wrap gap-2",Z),children:$})}function U1({suggestion:$,onClick:Z,className:Y}){let J=s1.useCallback(()=>{Z?.($)},[$,Z]);return e1(q5,{type:"button",variant:"outline",size:"sm",onPress:J,className:t1("text-muted-foreground hover:text-foreground",Y),children:$})}import{Label as H5}from"@contractspec/lib.ui-kit-web/ui/label";import{Select as Z2,SelectContent as Y2,SelectItem as J2,SelectTrigger as Q2,SelectValue as X2}from"@contractspec/lib.ui-kit-web/ui/select";import{cn as q2}from"@contractspec/lib.ui-kit-web/ui/utils";import*as G2 from"react";var D1={instant:"Instant",thinking:"Thinking",extra_thinking:"Extra Thinking",max:"Max"},$2={instant:"Fast responses, minimal reasoning",thinking:"Standard reasoning depth",extra_thinking:"More thorough reasoning",max:"Maximum reasoning depth"};function O1($,Z){if(!$||$==="instant")return{};switch(Z){case"anthropic":return{anthropic:{thinking:{type:"enabled",budgetTokens:{thinking:8000,extra_thinking:16000,max:32000}[$]}}};case"openai":return{openai:{reasoningEffort:{thinking:"low",extra_thinking:"medium",max:"high"}[$]}};case"ollama":case"mistral":case"gemini":return{};default:return{}}}import{jsx as _0,jsxs as B1}from"react/jsx-runtime";var H2=["instant","thinking","extra_thinking","max"];function A1({value:$,onChange:Z,className:Y,compact:J=!1}){let Q=G2.useCallback((X)=>{Z(X)},[Z]);if(J)return B1(Z2,{value:$,onValueChange:Q,children:[_0(Q2,{className:q2("w-[140px]",Y),children:_0(X2,{})}),_0(Y2,{children:H2.map((X)=>_0(J2,{value:X,children:D1[X]},X))})]});return B1("div",{className:q2("flex flex-col gap-1.5",Y),children:[_0(H5,{htmlFor:"thinking-level-picker",className:"font-medium text-sm",children:"Thinking Level"}),B1(Z2,{name:"thinking-level-picker",value:$,onValueChange:Q,children:[_0(Q2,{children:_0(X2,{placeholder:"Select thinking level"})}),_0(Y2,{children:H2.map((X)=>_0(J2,{value:X,title:$2[X],children:D1[X]},X))})]})]})}import{jsx as L0,jsxs as W2,Fragment as G5}from"react/jsx-runtime";function F1({messages:$,conversation:Z,children:Y,className:J,showExport:Q=!0,showMessageSelection:X=!0,showScrollButton:q=!0,onCreateNew:H,onFork:K,onEditMessage:W,thinkingLevel:G="thinking",onThinkingLevelChange:V,presentationRenderer:_,formRenderer:F,dataViewRenderer:E,components:A,suggestions:P,onSuggestionClick:z,suggestionComponents:B,showSuggestionsWhenEmpty:x=!0}){let k=K2.useMemo(()=>$.map((g)=>g.id),[$]),L=o1(k),C=P&&P.length>0&&($.length===0||x),y=Q||X,i=Boolean(V),v=i||y?W2(G5,{children:[i&&V&&L0(A1,{value:G,onChange:V,compact:!0}),y&&L0(t0,{messages:$,conversation:Z,selectedIds:L.selectedIds,showSelectionSummary:X,onSelectAll:X?L.selectAll:void 0,onClearSelection:X?L.clearSelection:void 0,selectedCount:L.selectedCount,totalCount:$.length,onCreateNew:H,onFork:K})]}):null;return W2(a0,{className:J,headerContent:v,showScrollButton:q,children:[$.map((g)=>L0(_1,{message:g,selectable:X,selected:L.isSelected(g.id),onSelect:X?L.toggle:void 0,editable:g.role==="user"&&!!W,onEdit:W,presentationRenderer:_,formRenderer:F,dataViewRenderer:E,components:A},g.id)),C&&(()=>{let g=B?.Suggestions,J0=B?.Suggestion;if(g&&J0)return L0(g,{children:P.map((M)=>L0(J0,{suggestion:M,onClick:z},M))});return L0(z1,{className:"mb-4",children:P.map((M)=>L0(U1,{suggestion:M,onClick:z},M))})})(),Y]})}import*as I0 from"react";function u0($){return`${$}_${Date.now()}_${Math.random().toString(36).slice(2,11)}`}function W5($){return{...$,createdAt:$.createdAt.toISOString(),updatedAt:$.updatedAt.toISOString(),messages:$.messages.map((Z)=>({...Z,createdAt:Z.createdAt.toISOString(),updatedAt:Z.updatedAt.toISOString()}))}}function K5($){let Z=$.messages?.map((Y)=>({...Y,createdAt:new Date(Y.createdAt),updatedAt:new Date(Y.updatedAt)}))??[];return{...$,createdAt:new Date($.createdAt),updatedAt:new Date($.updatedAt),messages:Z}}function _5($){if(typeof window>"u")return new Map;try{let Z=window.localStorage.getItem($);if(!Z)return new Map;let Y=JSON.parse(Z),J=new Map;for(let Q of Y){let X=K5(Q);J.set(X.id,X)}return J}catch{return new Map}}function V5($,Z){if(typeof window>"u")return;try{let Y=Array.from(Z.values()).map(W5);window.localStorage.setItem($,JSON.stringify(Y))}catch{}}class _2{key;cache=null;constructor($="contractspec:ai-chat:conversations"){this.key=$}getMap(){if(!this.cache)this.cache=_5(this.key);return this.cache}persist(){V5(this.key,this.getMap())}async get($){return this.getMap().get($)??null}async create($){let Z=new Date,Y={...$,id:u0("conv"),createdAt:Z,updatedAt:Z};return this.getMap().set(Y.id,Y),this.persist(),Y}async update($,Z){let Y=this.getMap().get($);if(!Y)return null;let J={...Y,...Z,updatedAt:new Date};return this.getMap().set($,J),this.persist(),J}async appendMessage($,Z){let Y=this.getMap().get($);if(!Y)throw Error(`Conversation ${$} not found`);let J=new Date,Q={...Z,id:u0("msg"),conversationId:$,createdAt:J,updatedAt:J};return Y.messages.push(Q),Y.updatedAt=J,this.persist(),Q}async updateMessage($,Z,Y){let J=this.getMap().get($);if(!J)return null;let Q=J.messages.findIndex((H)=>H.id===Z);if(Q===-1)return null;let X=J.messages[Q];if(!X)return null;let q={...X,...Y,updatedAt:new Date};return J.messages[Q]=q,J.updatedAt=new Date,this.persist(),q}async delete($){let Z=this.getMap().delete($);if(Z)this.persist();return Z}async list($){let Z=Array.from(this.getMap().values());if($?.status)Z=Z.filter((Q)=>Q.status===$.status);if($?.projectId)Z=Z.filter((Q)=>Q.projectId===$.projectId);if($?.tags&&$.tags.length>0){let Q=new Set($.tags);Z=Z.filter((X)=>X.tags&&X.tags.some((q)=>Q.has(q)))}Z.sort((Q,X)=>X.updatedAt.getTime()-Q.updatedAt.getTime());let Y=$?.offset??0,J=$?.limit??100;return Z.slice(Y,Y+J)}async fork($,Z){let Y=this.getMap().get($);if(!Y)throw Error(`Conversation ${$} not found`);let J=Y.messages;if(Z){let H=Y.messages.findIndex((K)=>K.id===Z);if(H===-1)throw Error(`Message ${Z} not found`);J=Y.messages.slice(0,H+1)}let Q=new Date,X=J.map((H)=>({...H,id:u0("msg"),conversationId:"",createdAt:new Date(H.createdAt),updatedAt:new Date(H.updatedAt)})),q={...Y,id:u0("conv"),title:Y.title?`${Y.title} (fork)`:void 0,forkedFromId:Y.id,createdAt:Q,updatedAt:Q,messages:X};for(let H of q.messages)H.conversationId=q.id;return this.getMap().set(q.id,q),this.persist(),q}async truncateAfter($,Z){let Y=this.getMap().get($);if(!Y)return null;let J=Y.messages.findIndex((Q)=>Q.id===Z);if(J===-1)return null;return Y.messages=Y.messages.slice(0,J+1),Y.updatedAt=new Date,this.persist(),Y}async search($,Z=20){let Y=$.toLowerCase(),J=[];for(let Q of this.getMap().values()){if(Q.title?.toLowerCase().includes(Y)){J.push(Q);continue}if(Q.messages.some((X)=>X.content.toLowerCase().includes(Y)))J.push(Q);if(J.length>=Z)break}return J}}function V2($){return new _2($)}import{createProvider as f5}from"@contractspec/lib.ai-providers";import{tool as h5}from"ai";import*as I from"react";import{z as j5}from"zod";import{generateText as M5,streamText as w5}from"ai";import{compilePlannerPrompt as S5}from"@contractspec/lib.surface-runtime/runtime/planner-prompt";import{tool as z5}from"ai";import{z as U5}from"zod";function D5($){return U5.object({}).passthrough()}function z2($,Z){let Y={};for(let J of $){let Q=Z?.[J.name],X=D5(J.schema);Y[J.name]=z5({description:J.description??J.name,inputSchema:X,execute:async(q)=>{if(!Q)return{status:"unimplemented",message:"Wire handler in host",toolName:J.name};try{let H=await Promise.resolve(Q(q));return typeof H==="string"?H:H}catch(H){return{status:"error",error:H instanceof Error?H.message:String(H),toolName:J.name}}}})}return Y}function U2($){let Z=[];if(!$.agentSpecs?.length&&!$.dataViewSpecs?.length&&!$.formSpecs?.length&&!$.presentationSpecs?.length&&!$.operationRefs?.length)return"";if(Z.push(`
|
|
53
|
+
|
|
54
|
+
## Available resources`),$.agentSpecs?.length){Z.push(`
|
|
55
|
+
### Agent tools`);for(let Y of $.agentSpecs){let J=Y.tools?.map((Q)=>Q.name).join(", ")??"none";Z.push(`- **${Y.key}**: tools: ${J}`)}}if($.dataViewSpecs?.length){Z.push(`
|
|
56
|
+
### Data views`);for(let Y of $.dataViewSpecs)Z.push(`- **${Y.key}**: ${Y.meta.title??Y.key}`)}if($.formSpecs?.length){Z.push(`
|
|
57
|
+
### Forms`);for(let Y of $.formSpecs)Z.push(`- **${Y.key}**: ${Y.meta.title??Y.key}`)}if($.presentationSpecs?.length){Z.push(`
|
|
58
|
+
### Presentations`);for(let Y of $.presentationSpecs)Z.push(`- **${Y.key}**: ${Y.meta.title??Y.key} (targets: ${Y.targets?.join(", ")??"react"})`)}if($.operationRefs?.length){Z.push(`
|
|
59
|
+
### Operations`);for(let Y of $.operationRefs)Z.push(`- **${Y.key}@${Y.version}**`)}return Z.push(`
|
|
60
|
+
Use the available tools to invoke operations, query data views, or propose surface changes when appropriate.`),Z.join(`
|
|
61
|
+
`)}function p0($){return`${$}_${Date.now()}_${Math.random().toString(36).slice(2,11)}`}class g0{conversations=new Map;async get($){return this.conversations.get($)??null}async create($){let Z=new Date,Y={...$,id:p0("conv"),createdAt:Z,updatedAt:Z};return this.conversations.set(Y.id,Y),Y}async update($,Z){let Y=this.conversations.get($);if(!Y)return null;let J={...Y,...Z,updatedAt:new Date};return this.conversations.set($,J),J}async appendMessage($,Z){let Y=this.conversations.get($);if(!Y)throw Error(`Conversation ${$} not found`);let J=new Date,Q={...Z,id:p0("msg"),conversationId:$,createdAt:J,updatedAt:J};return Y.messages.push(Q),Y.updatedAt=J,Q}async updateMessage($,Z,Y){let J=this.conversations.get($);if(!J)return null;let Q=J.messages.findIndex((H)=>H.id===Z);if(Q===-1)return null;let X=J.messages[Q];if(!X)return null;let q={...X,...Y,updatedAt:new Date};return J.messages[Q]=q,J.updatedAt=new Date,q}async delete($){return this.conversations.delete($)}async list($){let Z=Array.from(this.conversations.values());if($?.status)Z=Z.filter((Q)=>Q.status===$.status);if($?.projectId)Z=Z.filter((Q)=>Q.projectId===$.projectId);if($?.tags&&$.tags.length>0){let Q=new Set($.tags);Z=Z.filter((X)=>X.tags&&X.tags.some((q)=>Q.has(q)))}Z.sort((Q,X)=>X.updatedAt.getTime()-Q.updatedAt.getTime());let Y=$?.offset??0,J=$?.limit??100;return Z.slice(Y,Y+J)}async fork($,Z){let Y=this.conversations.get($);if(!Y)throw Error(`Conversation ${$} not found`);let J=Y.messages;if(Z){let H=Y.messages.findIndex((K)=>K.id===Z);if(H===-1)throw Error(`Message ${Z} not found`);J=Y.messages.slice(0,H+1)}let Q=new Date,X=J.map((H)=>({...H,id:p0("msg"),conversationId:"",createdAt:new Date(H.createdAt),updatedAt:new Date(H.updatedAt)})),q={...Y,id:p0("conv"),title:Y.title?`${Y.title} (fork)`:void 0,forkedFromId:Y.id,createdAt:Q,updatedAt:Q,messages:X};for(let H of q.messages)H.conversationId=q.id;return this.conversations.set(q.id,q),q}async truncateAfter($,Z){let Y=this.conversations.get($);if(!Y)return null;let J=Y.messages.findIndex((Q)=>Q.id===Z);if(J===-1)return null;return Y.messages=Y.messages.slice(0,J+1),Y.updatedAt=new Date,Y}async search($,Z=20){let Y=$.toLowerCase(),J=[];for(let Q of this.conversations.values()){if(Q.title?.toLowerCase().includes(Y)){J.push(Q);continue}if(Q.messages.some((q)=>q.content.toLowerCase().includes(Y)))J.push(Q);if(J.length>=Z)break}return J}clear(){this.conversations.clear()}}function S7(){return new g0}import{buildSurfacePatchProposal as O5}from"@contractspec/lib.surface-runtime/runtime/planner-tools";import{validatePatchProposal as B5}from"@contractspec/lib.surface-runtime/spec/validate-surface-patch";import{tool as A5}from"ai";import{z as w}from"zod";var F5=["insert-node","replace-node","remove-node","move-node","resize-panel","set-layout","reveal-field","hide-field","promote-action","set-focus"],P5=["entity-section","entity-card","data-view","assistant-panel","chat-thread","action-bar","timeline","table","rich-doc","form","chart","custom-widget"];function m0($){let Z=[];if($.type==="slot")Z.push($.slotId);if($.type==="panel-group"||$.type==="stack")for(let Y of $.children)Z.push(...m0(Y));if($.type==="tabs")for(let Y of $.tabs)Z.push(...m0(Y.child));if($.type==="floating")Z.push($.anchorSlotId),Z.push(...m0($.child));return Z}function D2($){let Z=m0($.layoutRoot),Y=[...new Set(Z)];return{allowedOps:F5,allowedSlots:Y.length>0?Y:["assistant","primary"],allowedNodeKinds:P5}}var L5=w.object({proposalId:w.string().describe("Unique proposal identifier"),ops:w.array(w.object({op:w.enum(["insert-node","replace-node","remove-node","move-node","resize-panel","set-layout","reveal-field","hide-field","promote-action","set-focus"]),slotId:w.string().optional(),nodeId:w.string().optional(),toSlotId:w.string().optional(),index:w.number().optional(),node:w.object({nodeId:w.string(),kind:w.string(),title:w.string().optional(),props:w.record(w.string(),w.unknown()).optional(),children:w.array(w.unknown()).optional()}).optional(),persistKey:w.string().optional(),sizes:w.array(w.number()).optional(),layoutId:w.string().optional(),fieldId:w.string().optional(),actionId:w.string().optional(),placement:w.enum(["header","inline","context","assistant"]).optional(),targetId:w.string().optional()}))});function O2($){let{plan:Z,constraints:Y,onPatchProposal:J}=$,Q=Y??D2(Z);return{"propose-patch":A5({description:"Propose surface patches (layout changes, node insertions, etc.) for user approval. Only use allowed ops, slots, and node kinds from the planner context.",inputSchema:L5,execute:async(q)=>{let H=q.ops;try{B5(H,Q);let K=O5(q.proposalId,H);return J?.(K),{success:!0,proposalId:K.proposalId,opsCount:K.ops.length,message:"Patch proposal validated; awaiting user approval"}}catch(K){return{success:!1,error:K instanceof Error?K.message:String(K),proposalId:q.proposalId}}}})}}function B2($){let Z=D2($);return{bundleMeta:{key:$.bundleKey,version:"0.0.0",title:$.bundleKey},surfaceId:$.surfaceId,allowedPatchOps:Z.allowedOps,allowedSlots:[...Z.allowedSlots],allowedNodeKinds:[...Z.allowedNodeKinds],actions:$.actions.map((Y)=>({actionId:Y.actionId,title:Y.title})),preferences:{guidance:"hints",density:"standard",dataDepth:"detailed",control:"standard",media:"text",pace:"balanced",narrative:"top-down"}}}import{validateExtension as N5,WorkflowComposer as E5}from"@contractspec/lib.workflow-composer";import{tool as P1}from"ai";import{z as N}from"zod";var y5=N.enum(["human","automation","decision"]),R5=N.object({operation:N.object({name:N.string(),version:N.number()}).optional(),form:N.object({key:N.string(),version:N.number()}).optional()}).optional(),T5=N.object({id:N.string(),type:y5,label:N.string(),description:N.string().optional(),action:R5}),b5=N.object({after:N.string().optional(),before:N.string().optional(),inject:T5,transitionTo:N.string().optional(),transitionFrom:N.string().optional(),when:N.string().optional()}),A2=N.object({workflow:N.string(),tenantId:N.string().optional(),role:N.string().optional(),priority:N.number().optional(),customSteps:N.array(b5).optional(),hiddenSteps:N.array(N.string()).optional()});function F2($){let{baseWorkflows:Z,composer:Y}=$,J=new Map(Z.map((W)=>[W.meta.key,W])),Q=P1({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.",inputSchema:A2,execute:async(W)=>{let G={workflow:W.workflow,tenantId:W.tenantId,role:W.role,priority:W.priority,customSteps:W.customSteps,hiddenSteps:W.hiddenSteps},V=J.get(W.workflow);if(!V)return{success:!1,error:`Base workflow "${W.workflow}" not found. Available: ${Array.from(J.keys()).join(", ")}`,extension:G};try{return N5(G,V),{success:!0,message:"Extension validated successfully",extension:G}}catch(_){return{success:!1,error:_ instanceof Error?_.message:String(_),extension:G}}}}),X=N.object({workflowKey:N.string().describe("Base workflow meta.key"),tenantId:N.string().optional(),role:N.string().optional(),extensions:N.array(A2).optional().describe("Extensions to register before composing")}),q=P1({description:"Compose a workflow by applying registered extensions to a base workflow. Returns the composed WorkflowSpec.",inputSchema:X,execute:async(W)=>{let G=J.get(W.workflowKey);if(!G)return{success:!1,error:`Base workflow "${W.workflowKey}" not found. Available: ${Array.from(J.keys()).join(", ")}`};let V=Y??new E5;if(W.extensions?.length)for(let _ of W.extensions)V.register({workflow:_.workflow,tenantId:_.tenantId,role:_.role,priority:_.priority,customSteps:_.customSteps,hiddenSteps:_.hiddenSteps});try{let _=V.compose({base:G,tenantId:W.tenantId,role:W.role});return{success:!0,workflow:_,meta:_.meta,stepIds:_.definition.steps.map((F)=>F.id)}}catch(_){return{success:!1,error:_ instanceof Error?_.message:String(_)}}}}),H=N.object({workflowKey:N.string().describe("Workflow meta.key"),composedSteps:N.array(N.object({id:N.string(),type:N.enum(["human","automation","decision"]),label:N.string(),description:N.string().optional()})).optional().describe("Steps to include; if omitted, uses the base workflow")}),K=P1({description:"Generate TypeScript code for a workflow spec. Use after composing a workflow to output the spec as code the user can save.",inputSchema:H,execute:async(W)=>{let G=J.get(W.workflowKey);if(!G)return{success:!1,error:`Base workflow "${W.workflowKey}" not found. Available: ${Array.from(J.keys()).join(", ")}`,code:null};let V=W.composedSteps??G.definition.steps,_=k5((G.meta.key.split(".").pop()??"Workflow")+"")+"Workflow",F=V.map((z)=>` {
|
|
62
|
+
id: '${z.id}',
|
|
63
|
+
type: '${z.type}',
|
|
64
|
+
label: '${d0(z.label)}',${z.description?`
|
|
65
|
+
description: '${d0(z.description)}',`:""}
|
|
3010
66
|
}`).join(`,
|
|
3011
|
-
`);
|
|
3012
|
-
const meta = base.meta;
|
|
3013
|
-
const transitionsJson = JSON.stringify(base.definition.transitions, null, 6);
|
|
3014
|
-
const code = `import type { WorkflowSpec } from '@contractspec/lib.contracts-spec/workflow/spec';
|
|
67
|
+
`),E=G.meta,A=JSON.stringify(G.definition.transitions,null,6);return{success:!0,code:`import type { WorkflowSpec } from '@contractspec/lib.contracts-spec/workflow/spec';
|
|
3015
68
|
|
|
3016
69
|
/**
|
|
3017
|
-
* Workflow: ${
|
|
70
|
+
* Workflow: ${G.meta.key}
|
|
3018
71
|
* Generated via AI chat workflow tools.
|
|
3019
72
|
*/
|
|
3020
|
-
export const ${
|
|
73
|
+
export const ${_}: WorkflowSpec = {
|
|
3021
74
|
meta: {
|
|
3022
|
-
key: '${
|
|
3023
|
-
version: '${String(
|
|
3024
|
-
title: '${
|
|
3025
|
-
description: '${
|
|
75
|
+
key: '${G.meta.key}',
|
|
76
|
+
version: '${String(G.meta.version)}',
|
|
77
|
+
title: '${d0(E.title??G.meta.key)}',
|
|
78
|
+
description: '${d0(E.description??"")}',
|
|
3026
79
|
},
|
|
3027
80
|
definition: {
|
|
3028
|
-
entryStepId: '${
|
|
81
|
+
entryStepId: '${G.definition.entryStepId??G.definition.steps[0]?.id??""}',
|
|
3029
82
|
steps: [
|
|
3030
|
-
${
|
|
83
|
+
${F}
|
|
3031
84
|
],
|
|
3032
|
-
transitions: ${
|
|
85
|
+
transitions: ${A},
|
|
3033
86
|
},
|
|
3034
87
|
};
|
|
3035
|
-
|
|
3036
|
-
return {
|
|
3037
|
-
success: true,
|
|
3038
|
-
code,
|
|
3039
|
-
workflowKey: input.workflowKey
|
|
3040
|
-
};
|
|
3041
|
-
}
|
|
3042
|
-
});
|
|
3043
|
-
return {
|
|
3044
|
-
create_workflow_extension: createWorkflowExtensionTool,
|
|
3045
|
-
compose_workflow: composeWorkflowTool,
|
|
3046
|
-
generate_workflow_spec_code: generateWorkflowSpecCodeTool
|
|
3047
|
-
};
|
|
3048
|
-
}
|
|
3049
|
-
function toPascalCase(value) {
|
|
3050
|
-
return value.split(/[-_.]/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
3051
|
-
}
|
|
3052
|
-
function escapeString(value) {
|
|
3053
|
-
return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
3054
|
-
}
|
|
3055
|
-
|
|
3056
|
-
// src/core/chat-service.ts
|
|
3057
|
-
var DEFAULT_SYSTEM_PROMPT = `You are ContractSpec AI, an expert coding assistant specialized in ContractSpec development.
|
|
88
|
+
`,workflowKey:W.workflowKey}}});return{create_workflow_extension:Q,compose_workflow:q,generate_workflow_spec_code:K}}function k5($){return $.split(/[-_.]/).filter(Boolean).map((Z)=>Z.charAt(0).toUpperCase()+Z.slice(1)).join("")}function d0($){return $.replace(/\\/g,"\\\\").replace(/'/g,"\\'")}var I5=`You are ContractSpec AI, an expert coding assistant specialized in ContractSpec development.
|
|
3058
89
|
|
|
3059
90
|
Your capabilities:
|
|
3060
91
|
- Help users create, modify, and understand ContractSpec specifications
|
|
@@ -3067,1366 +98,15 @@ Guidelines:
|
|
|
3067
98
|
- Provide code examples when helpful
|
|
3068
99
|
- Reference relevant ContractSpec concepts and patterns
|
|
3069
100
|
- Ask clarifying questions when the user's intent is unclear
|
|
3070
|
-
- When suggesting code changes, explain the rationale
|
|
3071
|
-
var WORKFLOW_TOOLS_PROMPT = `
|
|
3072
|
-
|
|
3073
|
-
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.`;
|
|
101
|
+
- When suggesting code changes, explain the rationale`,C5=`
|
|
3074
102
|
|
|
3075
|
-
class
|
|
3076
|
-
provider;
|
|
3077
|
-
context;
|
|
3078
|
-
store;
|
|
3079
|
-
systemPrompt;
|
|
3080
|
-
maxHistoryMessages;
|
|
3081
|
-
onUsage;
|
|
3082
|
-
tools;
|
|
3083
|
-
thinkingLevel;
|
|
3084
|
-
sendReasoning;
|
|
3085
|
-
sendSources;
|
|
3086
|
-
modelSelector;
|
|
3087
|
-
constructor(config) {
|
|
3088
|
-
this.provider = config.provider;
|
|
3089
|
-
this.context = config.context;
|
|
3090
|
-
this.store = config.store ?? new InMemoryConversationStore;
|
|
3091
|
-
this.systemPrompt = this.buildSystemPrompt(config);
|
|
3092
|
-
this.maxHistoryMessages = config.maxHistoryMessages ?? 20;
|
|
3093
|
-
this.onUsage = config.onUsage;
|
|
3094
|
-
this.tools = this.mergeTools(config);
|
|
3095
|
-
this.thinkingLevel = config.thinkingLevel;
|
|
3096
|
-
this.modelSelector = config.modelSelector;
|
|
3097
|
-
this.sendReasoning = config.sendReasoning ?? (config.thinkingLevel != null && config.thinkingLevel !== "instant");
|
|
3098
|
-
this.sendSources = config.sendSources ?? false;
|
|
3099
|
-
}
|
|
3100
|
-
buildSystemPrompt(config) {
|
|
3101
|
-
let base = config.systemPrompt ?? DEFAULT_SYSTEM_PROMPT;
|
|
3102
|
-
if (config.workflowToolsConfig?.baseWorkflows?.length) {
|
|
3103
|
-
base += WORKFLOW_TOOLS_PROMPT;
|
|
3104
|
-
}
|
|
3105
|
-
const contractsPrompt = buildContractsContextPrompt(config.contractsContext ?? {});
|
|
3106
|
-
if (contractsPrompt) {
|
|
3107
|
-
base += contractsPrompt;
|
|
3108
|
-
}
|
|
3109
|
-
if (config.surfacePlanConfig?.plan) {
|
|
3110
|
-
const plannerInput = buildPlannerPromptInput(config.surfacePlanConfig.plan);
|
|
3111
|
-
base += `
|
|
103
|
+
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.`;class l0{provider;context;store;systemPrompt;maxHistoryMessages;onUsage;tools;thinkingLevel;sendReasoning;sendSources;modelSelector;constructor($){this.provider=$.provider,this.context=$.context,this.store=$.store??new g0,this.systemPrompt=this.buildSystemPrompt($),this.maxHistoryMessages=$.maxHistoryMessages??20,this.onUsage=$.onUsage,this.tools=this.mergeTools($),this.thinkingLevel=$.thinkingLevel,this.modelSelector=$.modelSelector,this.sendReasoning=$.sendReasoning??($.thinkingLevel!=null&&$.thinkingLevel!=="instant"),this.sendSources=$.sendSources??!1}buildSystemPrompt($){let Z=$.systemPrompt??I5;if($.workflowToolsConfig?.baseWorkflows?.length)Z+=C5;let Y=U2($.contractsContext??{});if(Y)Z+=Y;if($.surfacePlanConfig?.plan){let J=B2($.surfacePlanConfig.plan);Z+=`
|
|
3112
104
|
|
|
3113
|
-
`
|
|
3114
|
-
}
|
|
3115
|
-
return base;
|
|
3116
|
-
}
|
|
3117
|
-
mergeTools(config) {
|
|
3118
|
-
let merged = config.tools ?? {};
|
|
3119
|
-
const wfConfig = config.workflowToolsConfig;
|
|
3120
|
-
if (wfConfig?.baseWorkflows?.length) {
|
|
3121
|
-
const workflowTools = createWorkflowTools({
|
|
3122
|
-
baseWorkflows: wfConfig.baseWorkflows,
|
|
3123
|
-
composer: wfConfig.composer
|
|
3124
|
-
});
|
|
3125
|
-
merged = { ...merged, ...workflowTools };
|
|
3126
|
-
}
|
|
3127
|
-
const contractsCtx = config.contractsContext;
|
|
3128
|
-
if (contractsCtx?.agentSpecs?.length) {
|
|
3129
|
-
const allTools = [];
|
|
3130
|
-
for (const agent of contractsCtx.agentSpecs) {
|
|
3131
|
-
if (agent.tools?.length)
|
|
3132
|
-
allTools.push(...agent.tools);
|
|
3133
|
-
}
|
|
3134
|
-
if (allTools.length > 0) {
|
|
3135
|
-
const agentTools = agentToolConfigsToToolSet(allTools);
|
|
3136
|
-
merged = { ...merged, ...agentTools };
|
|
3137
|
-
}
|
|
3138
|
-
}
|
|
3139
|
-
const surfaceConfig = config.surfacePlanConfig;
|
|
3140
|
-
if (surfaceConfig?.plan) {
|
|
3141
|
-
const plannerTools = createSurfacePlannerTools({
|
|
3142
|
-
plan: surfaceConfig.plan,
|
|
3143
|
-
onPatchProposal: surfaceConfig.onPatchProposal
|
|
3144
|
-
});
|
|
3145
|
-
merged = { ...merged, ...plannerTools };
|
|
3146
|
-
}
|
|
3147
|
-
if (config.mcpTools && Object.keys(config.mcpTools).length > 0) {
|
|
3148
|
-
merged = { ...merged, ...config.mcpTools };
|
|
3149
|
-
}
|
|
3150
|
-
return Object.keys(merged).length > 0 ? merged : undefined;
|
|
3151
|
-
}
|
|
3152
|
-
async resolveModel() {
|
|
3153
|
-
if (this.modelSelector) {
|
|
3154
|
-
const dimension = this.thinkingLevelToDimension(this.thinkingLevel);
|
|
3155
|
-
const { model, selection } = await this.modelSelector.selectAndCreate({
|
|
3156
|
-
taskDimension: dimension
|
|
3157
|
-
});
|
|
3158
|
-
return { model, providerName: selection.providerKey };
|
|
3159
|
-
}
|
|
3160
|
-
return {
|
|
3161
|
-
model: this.provider.getModel(),
|
|
3162
|
-
providerName: this.provider.name
|
|
3163
|
-
};
|
|
3164
|
-
}
|
|
3165
|
-
thinkingLevelToDimension(level) {
|
|
3166
|
-
if (!level || level === "instant")
|
|
3167
|
-
return "latency";
|
|
3168
|
-
return "reasoning";
|
|
3169
|
-
}
|
|
3170
|
-
async send(options) {
|
|
3171
|
-
let conversation;
|
|
3172
|
-
if (options.conversationId) {
|
|
3173
|
-
const existing = await this.store.get(options.conversationId);
|
|
3174
|
-
if (!existing) {
|
|
3175
|
-
throw new Error(`Conversation ${options.conversationId} not found`);
|
|
3176
|
-
}
|
|
3177
|
-
conversation = existing;
|
|
3178
|
-
} else {
|
|
3179
|
-
conversation = await this.store.create({
|
|
3180
|
-
status: "active",
|
|
3181
|
-
provider: this.provider.name,
|
|
3182
|
-
model: this.provider.model,
|
|
3183
|
-
messages: [],
|
|
3184
|
-
workspacePath: this.context?.workspacePath
|
|
3185
|
-
});
|
|
3186
|
-
}
|
|
3187
|
-
if (!options.skipUserAppend) {
|
|
3188
|
-
await this.store.appendMessage(conversation.id, {
|
|
3189
|
-
role: "user",
|
|
3190
|
-
content: options.content,
|
|
3191
|
-
status: "completed",
|
|
3192
|
-
attachments: options.attachments
|
|
3193
|
-
});
|
|
3194
|
-
}
|
|
3195
|
-
conversation = await this.store.get(conversation.id) ?? conversation;
|
|
3196
|
-
const messages = this.buildMessages(conversation, options);
|
|
3197
|
-
const { model, providerName } = await this.resolveModel();
|
|
3198
|
-
const providerOptions = getProviderOptions(this.thinkingLevel, providerName);
|
|
3199
|
-
try {
|
|
3200
|
-
const result = await generateText({
|
|
3201
|
-
model,
|
|
3202
|
-
messages,
|
|
3203
|
-
system: this.systemPrompt,
|
|
3204
|
-
tools: this.tools,
|
|
3205
|
-
providerOptions: Object.keys(providerOptions).length > 0 ? providerOptions : undefined
|
|
3206
|
-
});
|
|
3207
|
-
const assistantMessage = await this.store.appendMessage(conversation.id, {
|
|
3208
|
-
role: "assistant",
|
|
3209
|
-
content: result.text,
|
|
3210
|
-
status: "completed"
|
|
3211
|
-
});
|
|
3212
|
-
const updatedConversation = await this.store.get(conversation.id);
|
|
3213
|
-
if (!updatedConversation) {
|
|
3214
|
-
throw new Error("Conversation lost after update");
|
|
3215
|
-
}
|
|
3216
|
-
return {
|
|
3217
|
-
message: assistantMessage,
|
|
3218
|
-
conversation: updatedConversation
|
|
3219
|
-
};
|
|
3220
|
-
} catch (error) {
|
|
3221
|
-
await this.store.appendMessage(conversation.id, {
|
|
3222
|
-
role: "assistant",
|
|
3223
|
-
content: "",
|
|
3224
|
-
status: "error",
|
|
3225
|
-
error: {
|
|
3226
|
-
code: "generation_failed",
|
|
3227
|
-
message: error instanceof Error ? error.message : String(error)
|
|
3228
|
-
}
|
|
3229
|
-
});
|
|
3230
|
-
throw error;
|
|
3231
|
-
}
|
|
3232
|
-
}
|
|
3233
|
-
async stream(options) {
|
|
3234
|
-
let conversation;
|
|
3235
|
-
if (options.conversationId) {
|
|
3236
|
-
const existing = await this.store.get(options.conversationId);
|
|
3237
|
-
if (!existing) {
|
|
3238
|
-
throw new Error(`Conversation ${options.conversationId} not found`);
|
|
3239
|
-
}
|
|
3240
|
-
conversation = existing;
|
|
3241
|
-
} else {
|
|
3242
|
-
conversation = await this.store.create({
|
|
3243
|
-
status: "active",
|
|
3244
|
-
provider: this.provider.name,
|
|
3245
|
-
model: this.provider.model,
|
|
3246
|
-
messages: [],
|
|
3247
|
-
workspacePath: this.context?.workspacePath
|
|
3248
|
-
});
|
|
3249
|
-
}
|
|
3250
|
-
if (!options.skipUserAppend) {
|
|
3251
|
-
await this.store.appendMessage(conversation.id, {
|
|
3252
|
-
role: "user",
|
|
3253
|
-
content: options.content,
|
|
3254
|
-
status: "completed",
|
|
3255
|
-
attachments: options.attachments
|
|
3256
|
-
});
|
|
3257
|
-
}
|
|
3258
|
-
conversation = await this.store.get(conversation.id) ?? conversation;
|
|
3259
|
-
const assistantMessage = await this.store.appendMessage(conversation.id, {
|
|
3260
|
-
role: "assistant",
|
|
3261
|
-
content: "",
|
|
3262
|
-
status: "streaming"
|
|
3263
|
-
});
|
|
3264
|
-
const messages = this.buildMessages(conversation, options);
|
|
3265
|
-
const { model, providerName } = await this.resolveModel();
|
|
3266
|
-
const systemPrompt = this.systemPrompt;
|
|
3267
|
-
const tools = this.tools;
|
|
3268
|
-
const store = this.store;
|
|
3269
|
-
const onUsage = this.onUsage;
|
|
3270
|
-
const streamProviderOptions = getProviderOptions(this.thinkingLevel, providerName);
|
|
3271
|
-
async function* streamGenerator() {
|
|
3272
|
-
let fullContent = "";
|
|
3273
|
-
let fullReasoning = "";
|
|
3274
|
-
const toolCallsMap = new Map;
|
|
3275
|
-
const sources = [];
|
|
3276
|
-
try {
|
|
3277
|
-
const result = streamText({
|
|
3278
|
-
model,
|
|
3279
|
-
messages,
|
|
3280
|
-
system: systemPrompt,
|
|
3281
|
-
tools,
|
|
3282
|
-
providerOptions: Object.keys(streamProviderOptions).length > 0 ? streamProviderOptions : undefined
|
|
3283
|
-
});
|
|
3284
|
-
for await (const part of result.fullStream) {
|
|
3285
|
-
if (part.type === "text-delta") {
|
|
3286
|
-
const text = part.text ?? "";
|
|
3287
|
-
if (text) {
|
|
3288
|
-
fullContent += text;
|
|
3289
|
-
yield { type: "text", content: text };
|
|
3290
|
-
}
|
|
3291
|
-
} else if (part.type === "reasoning-delta") {
|
|
3292
|
-
const text = part.text ?? "";
|
|
3293
|
-
if (text) {
|
|
3294
|
-
fullReasoning += text;
|
|
3295
|
-
yield { type: "reasoning", content: text };
|
|
3296
|
-
}
|
|
3297
|
-
} else if (part.type === "source") {
|
|
3298
|
-
const src = part;
|
|
3299
|
-
const source = {
|
|
3300
|
-
id: src.id,
|
|
3301
|
-
title: src.title ?? "",
|
|
3302
|
-
url: src.url,
|
|
3303
|
-
type: "web"
|
|
3304
|
-
};
|
|
3305
|
-
sources.push(source);
|
|
3306
|
-
yield { type: "source", source };
|
|
3307
|
-
} else if (part.type === "tool-call") {
|
|
3308
|
-
const toolCall = {
|
|
3309
|
-
id: part.toolCallId,
|
|
3310
|
-
name: part.toolName,
|
|
3311
|
-
args: part.input ?? {},
|
|
3312
|
-
status: "running"
|
|
3313
|
-
};
|
|
3314
|
-
toolCallsMap.set(part.toolCallId, toolCall);
|
|
3315
|
-
yield { type: "tool_call", toolCall };
|
|
3316
|
-
} else if (part.type === "tool-result") {
|
|
3317
|
-
const tc = toolCallsMap.get(part.toolCallId);
|
|
3318
|
-
if (tc) {
|
|
3319
|
-
tc.result = part.output;
|
|
3320
|
-
tc.status = "completed";
|
|
3321
|
-
}
|
|
3322
|
-
yield {
|
|
3323
|
-
type: "tool_result",
|
|
3324
|
-
toolResult: {
|
|
3325
|
-
toolCallId: part.toolCallId,
|
|
3326
|
-
toolName: part.toolName,
|
|
3327
|
-
result: part.output
|
|
3328
|
-
}
|
|
3329
|
-
};
|
|
3330
|
-
} else if (part.type === "tool-error") {
|
|
3331
|
-
const tc = toolCallsMap.get(part.toolCallId);
|
|
3332
|
-
if (tc) {
|
|
3333
|
-
tc.status = "error";
|
|
3334
|
-
tc.error = part.error ?? "Tool execution failed";
|
|
3335
|
-
}
|
|
3336
|
-
} else if (part.type === "finish") {
|
|
3337
|
-
const usage = part.usage;
|
|
3338
|
-
const inputTokens = usage?.inputTokens ?? 0;
|
|
3339
|
-
const outputTokens = usage?.completionTokens ?? 0;
|
|
3340
|
-
await store.updateMessage(conversation.id, assistantMessage.id, {
|
|
3341
|
-
content: fullContent,
|
|
3342
|
-
status: "completed",
|
|
3343
|
-
reasoning: fullReasoning || undefined,
|
|
3344
|
-
sources: sources.length > 0 ? sources : undefined,
|
|
3345
|
-
toolCalls: toolCallsMap.size > 0 ? Array.from(toolCallsMap.values()) : undefined,
|
|
3346
|
-
usage: usage ? { inputTokens, outputTokens } : undefined
|
|
3347
|
-
});
|
|
3348
|
-
onUsage?.({ inputTokens, outputTokens });
|
|
3349
|
-
yield {
|
|
3350
|
-
type: "done",
|
|
3351
|
-
usage: usage ? { inputTokens, outputTokens } : undefined
|
|
3352
|
-
};
|
|
3353
|
-
return;
|
|
3354
|
-
}
|
|
3355
|
-
}
|
|
3356
|
-
await store.updateMessage(conversation.id, assistantMessage.id, {
|
|
3357
|
-
content: fullContent,
|
|
3358
|
-
status: "completed",
|
|
3359
|
-
reasoning: fullReasoning || undefined,
|
|
3360
|
-
sources: sources.length > 0 ? sources : undefined,
|
|
3361
|
-
toolCalls: toolCallsMap.size > 0 ? Array.from(toolCallsMap.values()) : undefined
|
|
3362
|
-
});
|
|
3363
|
-
yield { type: "done" };
|
|
3364
|
-
} catch (error) {
|
|
3365
|
-
await store.updateMessage(conversation.id, assistantMessage.id, {
|
|
3366
|
-
content: fullContent,
|
|
3367
|
-
status: "error",
|
|
3368
|
-
error: {
|
|
3369
|
-
code: "stream_failed",
|
|
3370
|
-
message: error instanceof Error ? error.message : String(error)
|
|
3371
|
-
}
|
|
3372
|
-
});
|
|
3373
|
-
yield {
|
|
3374
|
-
type: "error",
|
|
3375
|
-
error: {
|
|
3376
|
-
code: "stream_failed",
|
|
3377
|
-
message: error instanceof Error ? error.message : String(error)
|
|
3378
|
-
}
|
|
3379
|
-
};
|
|
3380
|
-
}
|
|
3381
|
-
}
|
|
3382
|
-
return {
|
|
3383
|
-
conversationId: conversation.id,
|
|
3384
|
-
messageId: assistantMessage.id,
|
|
3385
|
-
stream: streamGenerator()
|
|
3386
|
-
};
|
|
3387
|
-
}
|
|
3388
|
-
async getConversation(conversationId) {
|
|
3389
|
-
return this.store.get(conversationId);
|
|
3390
|
-
}
|
|
3391
|
-
async listConversations(options) {
|
|
3392
|
-
return this.store.list({
|
|
3393
|
-
status: "active",
|
|
3394
|
-
...options
|
|
3395
|
-
});
|
|
3396
|
-
}
|
|
3397
|
-
async updateConversation(conversationId, updates) {
|
|
3398
|
-
return this.store.update(conversationId, updates);
|
|
3399
|
-
}
|
|
3400
|
-
async forkConversation(conversationId, upToMessageId) {
|
|
3401
|
-
return this.store.fork(conversationId, upToMessageId);
|
|
3402
|
-
}
|
|
3403
|
-
async updateMessage(conversationId, messageId, updates) {
|
|
3404
|
-
return this.store.updateMessage(conversationId, messageId, updates);
|
|
3405
|
-
}
|
|
3406
|
-
async truncateAfter(conversationId, messageId) {
|
|
3407
|
-
return this.store.truncateAfter(conversationId, messageId);
|
|
3408
|
-
}
|
|
3409
|
-
async deleteConversation(conversationId) {
|
|
3410
|
-
return this.store.delete(conversationId);
|
|
3411
|
-
}
|
|
3412
|
-
buildMessages(conversation, _options) {
|
|
3413
|
-
const historyStart = Math.max(0, conversation.messages.length - this.maxHistoryMessages);
|
|
3414
|
-
const messages = [];
|
|
3415
|
-
for (let i = historyStart;i < conversation.messages.length; i++) {
|
|
3416
|
-
const msg = conversation.messages[i];
|
|
3417
|
-
if (!msg)
|
|
3418
|
-
continue;
|
|
3419
|
-
if (msg.role === "user") {
|
|
3420
|
-
let content = msg.content;
|
|
3421
|
-
if (msg.attachments?.length) {
|
|
3422
|
-
const attachmentInfo = msg.attachments.map((a) => {
|
|
3423
|
-
if (a.type === "file" || a.type === "code") {
|
|
3424
|
-
return `
|
|
105
|
+
`+S5(J)}return Z}mergeTools($){let Z=$.tools??{},Y=$.workflowToolsConfig;if(Y?.baseWorkflows?.length){let X=F2({baseWorkflows:Y.baseWorkflows,composer:Y.composer});Z={...Z,...X}}let J=$.contractsContext;if(J?.agentSpecs?.length){let X=[];for(let q of J.agentSpecs)if(q.tools?.length)X.push(...q.tools);if(X.length>0){let q=z2(X);Z={...Z,...q}}}let Q=$.surfacePlanConfig;if(Q?.plan){let X=O2({plan:Q.plan,onPatchProposal:Q.onPatchProposal});Z={...Z,...X}}if($.mcpTools&&Object.keys($.mcpTools).length>0)Z={...Z,...$.mcpTools};return Object.keys(Z).length>0?Z:void 0}async resolveModel(){if(this.modelSelector){let $=this.thinkingLevelToDimension(this.thinkingLevel),{model:Z,selection:Y}=await this.modelSelector.selectAndCreate({taskDimension:$});return{model:Z,providerName:Y.providerKey}}return{model:this.provider.getModel(),providerName:this.provider.name}}thinkingLevelToDimension($){if(!$||$==="instant")return"latency";return"reasoning"}async send($){let Z;if($.conversationId){let q=await this.store.get($.conversationId);if(!q)throw Error(`Conversation ${$.conversationId} not found`);Z=q}else Z=await this.store.create({status:"active",provider:this.provider.name,model:this.provider.model,messages:[],workspacePath:this.context?.workspacePath});if(!$.skipUserAppend)await this.store.appendMessage(Z.id,{role:"user",content:$.content,status:"completed",attachments:$.attachments});Z=await this.store.get(Z.id)??Z;let Y=this.buildMessages(Z,$),{model:J,providerName:Q}=await this.resolveModel(),X=O1(this.thinkingLevel,Q);try{let q=await M5({model:J,messages:Y,system:this.systemPrompt,tools:this.tools,providerOptions:Object.keys(X).length>0?X:void 0}),H=await this.store.appendMessage(Z.id,{role:"assistant",content:q.text,status:"completed"}),K=await this.store.get(Z.id);if(!K)throw Error("Conversation lost after update");return{message:H,conversation:K}}catch(q){throw await this.store.appendMessage(Z.id,{role:"assistant",content:"",status:"error",error:{code:"generation_failed",message:q instanceof Error?q.message:String(q)}}),q}}async stream($){let Z;if($.conversationId){let _=await this.store.get($.conversationId);if(!_)throw Error(`Conversation ${$.conversationId} not found`);Z=_}else Z=await this.store.create({status:"active",provider:this.provider.name,model:this.provider.model,messages:[],workspacePath:this.context?.workspacePath});if(!$.skipUserAppend)await this.store.appendMessage(Z.id,{role:"user",content:$.content,status:"completed",attachments:$.attachments});Z=await this.store.get(Z.id)??Z;let Y=await this.store.appendMessage(Z.id,{role:"assistant",content:"",status:"streaming"}),J=this.buildMessages(Z,$),{model:Q,providerName:X}=await this.resolveModel(),q=this.systemPrompt,H=this.tools,K=this.store,W=this.onUsage,G=O1(this.thinkingLevel,X);async function*V(){let _="",F="",E=new Map,A=[];try{let P=w5({model:Q,messages:J,system:q,tools:H,providerOptions:Object.keys(G).length>0?G:void 0});for await(let z of P.fullStream)if(z.type==="text-delta"){let B=z.text??"";if(B)_+=B,yield{type:"text",content:B}}else if(z.type==="reasoning-delta"){let B=z.text??"";if(B)F+=B,yield{type:"reasoning",content:B}}else if(z.type==="source"){let B=z,x={id:B.id,title:B.title??"",url:B.url,type:"web"};A.push(x),yield{type:"source",source:x}}else if(z.type==="tool-call"){let B={id:z.toolCallId,name:z.toolName,args:z.input??{},status:"running"};E.set(z.toolCallId,B),yield{type:"tool_call",toolCall:B}}else if(z.type==="tool-result"){let B=E.get(z.toolCallId);if(B)B.result=z.output,B.status="completed";yield{type:"tool_result",toolResult:{toolCallId:z.toolCallId,toolName:z.toolName,result:z.output}}}else if(z.type==="tool-error"){let B=E.get(z.toolCallId);if(B)B.status="error",B.error=z.error??"Tool execution failed"}else if(z.type==="finish"){let B=z.usage,x=B?.inputTokens??0,k=B?.completionTokens??0;await K.updateMessage(Z.id,Y.id,{content:_,status:"completed",reasoning:F||void 0,sources:A.length>0?A:void 0,toolCalls:E.size>0?Array.from(E.values()):void 0,usage:B?{inputTokens:x,outputTokens:k}:void 0}),W?.({inputTokens:x,outputTokens:k}),yield{type:"done",usage:B?{inputTokens:x,outputTokens:k}:void 0};return}await K.updateMessage(Z.id,Y.id,{content:_,status:"completed",reasoning:F||void 0,sources:A.length>0?A:void 0,toolCalls:E.size>0?Array.from(E.values()):void 0}),yield{type:"done"}}catch(P){await K.updateMessage(Z.id,Y.id,{content:_,status:"error",error:{code:"stream_failed",message:P instanceof Error?P.message:String(P)}}),yield{type:"error",error:{code:"stream_failed",message:P instanceof Error?P.message:String(P)}}}}return{conversationId:Z.id,messageId:Y.id,stream:V()}}async getConversation($){return this.store.get($)}async listConversations($){return this.store.list({status:"active",...$})}async updateConversation($,Z){return this.store.update($,Z)}async forkConversation($,Z){return this.store.fork($,Z)}async updateMessage($,Z,Y){return this.store.updateMessage($,Z,Y)}async truncateAfter($,Z){return this.store.truncateAfter($,Z)}async deleteConversation($){return this.store.delete($)}buildMessages($,Z){let Y=Math.max(0,$.messages.length-this.maxHistoryMessages),J=[];for(let Q=Y;Q<$.messages.length;Q++){let X=$.messages[Q];if(!X)continue;if(X.role==="user"){let q=X.content;if(X.attachments?.length){let H=X.attachments.map((K)=>{if(K.type==="file"||K.type==="code")return`
|
|
3425
106
|
|
|
3426
|
-
### ${
|
|
107
|
+
### ${K.name}
|
|
3427
108
|
\`\`\`
|
|
3428
|
-
${
|
|
3429
|
-
\`\`\``;
|
|
3430
|
-
}
|
|
3431
|
-
return `
|
|
3432
|
-
|
|
3433
|
-
[Attachment: ${a.name}]`;
|
|
3434
|
-
}).join("");
|
|
3435
|
-
content += attachmentInfo;
|
|
3436
|
-
}
|
|
3437
|
-
messages.push({ role: "user", content });
|
|
3438
|
-
} else if (msg.role === "assistant") {
|
|
3439
|
-
if (msg.toolCalls?.length) {
|
|
3440
|
-
messages.push({
|
|
3441
|
-
role: "assistant",
|
|
3442
|
-
content: msg.content || "",
|
|
3443
|
-
toolCalls: msg.toolCalls.map((tc) => ({
|
|
3444
|
-
type: "tool-call",
|
|
3445
|
-
toolCallId: tc.id,
|
|
3446
|
-
toolName: tc.name,
|
|
3447
|
-
args: tc.args
|
|
3448
|
-
}))
|
|
3449
|
-
});
|
|
3450
|
-
messages.push({
|
|
3451
|
-
role: "tool",
|
|
3452
|
-
content: msg.toolCalls.map((tc) => ({
|
|
3453
|
-
type: "tool-result",
|
|
3454
|
-
toolCallId: tc.id,
|
|
3455
|
-
toolName: tc.name,
|
|
3456
|
-
output: tc.result
|
|
3457
|
-
}))
|
|
3458
|
-
});
|
|
3459
|
-
} else {
|
|
3460
|
-
messages.push({ role: "assistant", content: msg.content });
|
|
3461
|
-
}
|
|
3462
|
-
}
|
|
3463
|
-
}
|
|
3464
|
-
return messages;
|
|
3465
|
-
}
|
|
3466
|
-
}
|
|
3467
|
-
function createChatService(config) {
|
|
3468
|
-
return new ChatService(config);
|
|
3469
|
-
}
|
|
3470
|
-
|
|
3471
|
-
// src/presentation/hooks/useChat.tsx
|
|
3472
|
-
"use client";
|
|
3473
|
-
function toolsToToolSet(defs) {
|
|
3474
|
-
const result = {};
|
|
3475
|
-
for (const def of defs) {
|
|
3476
|
-
result[def.name] = tool4({
|
|
3477
|
-
description: def.description ?? def.name,
|
|
3478
|
-
inputSchema: z4.object({}).passthrough(),
|
|
3479
|
-
execute: async () => ({})
|
|
3480
|
-
});
|
|
3481
|
-
}
|
|
3482
|
-
return result;
|
|
3483
|
-
}
|
|
3484
|
-
function useChat(options = {}) {
|
|
3485
|
-
const {
|
|
3486
|
-
provider = "openai",
|
|
3487
|
-
mode = "byok",
|
|
3488
|
-
model,
|
|
3489
|
-
apiKey,
|
|
3490
|
-
proxyUrl,
|
|
3491
|
-
conversationId: initialConversationId,
|
|
3492
|
-
store,
|
|
3493
|
-
systemPrompt,
|
|
3494
|
-
streaming = true,
|
|
3495
|
-
onSend,
|
|
3496
|
-
onResponse,
|
|
3497
|
-
onError,
|
|
3498
|
-
onUsage,
|
|
3499
|
-
tools: toolsDefs,
|
|
3500
|
-
thinkingLevel,
|
|
3501
|
-
workflowToolsConfig,
|
|
3502
|
-
modelSelector,
|
|
3503
|
-
contractsContext,
|
|
3504
|
-
surfacePlanConfig,
|
|
3505
|
-
mcpServers,
|
|
3506
|
-
agentMode
|
|
3507
|
-
} = options;
|
|
3508
|
-
const [messages, setMessages] = React13.useState([]);
|
|
3509
|
-
const [mcpTools, setMcpTools] = React13.useState(null);
|
|
3510
|
-
const mcpCleanupRef = React13.useRef(null);
|
|
3511
|
-
const [conversation, setConversation] = React13.useState(null);
|
|
3512
|
-
const [isLoading, setIsLoading] = React13.useState(false);
|
|
3513
|
-
const [error, setError] = React13.useState(null);
|
|
3514
|
-
const [conversationId, setConversationId] = React13.useState(initialConversationId ?? null);
|
|
3515
|
-
const abortControllerRef = React13.useRef(null);
|
|
3516
|
-
const chatServiceRef = React13.useRef(null);
|
|
3517
|
-
React13.useEffect(() => {
|
|
3518
|
-
if (!mcpServers?.length) {
|
|
3519
|
-
setMcpTools(null);
|
|
3520
|
-
return;
|
|
3521
|
-
}
|
|
3522
|
-
let cancelled = false;
|
|
3523
|
-
import("@contractspec/lib.ai-agent/tools/mcp-client.browser").then(({ createMcpToolsets }) => {
|
|
3524
|
-
createMcpToolsets(mcpServers).then(({ tools, cleanup }) => {
|
|
3525
|
-
if (!cancelled) {
|
|
3526
|
-
setMcpTools(tools);
|
|
3527
|
-
mcpCleanupRef.current = cleanup;
|
|
3528
|
-
} else {
|
|
3529
|
-
cleanup().catch(() => {
|
|
3530
|
-
return;
|
|
3531
|
-
});
|
|
3532
|
-
}
|
|
3533
|
-
}).catch(() => {
|
|
3534
|
-
if (!cancelled)
|
|
3535
|
-
setMcpTools(null);
|
|
3536
|
-
});
|
|
3537
|
-
});
|
|
3538
|
-
return () => {
|
|
3539
|
-
cancelled = true;
|
|
3540
|
-
const cleanup = mcpCleanupRef.current;
|
|
3541
|
-
mcpCleanupRef.current = null;
|
|
3542
|
-
if (cleanup)
|
|
3543
|
-
cleanup().catch(() => {
|
|
3544
|
-
return;
|
|
3545
|
-
});
|
|
3546
|
-
setMcpTools(null);
|
|
3547
|
-
};
|
|
3548
|
-
}, [mcpServers]);
|
|
3549
|
-
React13.useEffect(() => {
|
|
3550
|
-
const chatProvider = createProvider({
|
|
3551
|
-
provider,
|
|
3552
|
-
model,
|
|
3553
|
-
apiKey,
|
|
3554
|
-
proxyUrl
|
|
3555
|
-
});
|
|
3556
|
-
chatServiceRef.current = new ChatService({
|
|
3557
|
-
provider: chatProvider,
|
|
3558
|
-
store,
|
|
3559
|
-
systemPrompt,
|
|
3560
|
-
onUsage,
|
|
3561
|
-
tools: toolsDefs?.length ? toolsToToolSet(toolsDefs) : undefined,
|
|
3562
|
-
thinkingLevel,
|
|
3563
|
-
workflowToolsConfig,
|
|
3564
|
-
modelSelector,
|
|
3565
|
-
contractsContext,
|
|
3566
|
-
surfacePlanConfig,
|
|
3567
|
-
mcpTools
|
|
3568
|
-
});
|
|
3569
|
-
}, [
|
|
3570
|
-
provider,
|
|
3571
|
-
mode,
|
|
3572
|
-
model,
|
|
3573
|
-
apiKey,
|
|
3574
|
-
proxyUrl,
|
|
3575
|
-
store,
|
|
3576
|
-
systemPrompt,
|
|
3577
|
-
onUsage,
|
|
3578
|
-
toolsDefs,
|
|
3579
|
-
thinkingLevel,
|
|
3580
|
-
workflowToolsConfig,
|
|
3581
|
-
modelSelector,
|
|
3582
|
-
contractsContext,
|
|
3583
|
-
surfacePlanConfig,
|
|
3584
|
-
mcpTools
|
|
3585
|
-
]);
|
|
3586
|
-
React13.useEffect(() => {
|
|
3587
|
-
if (!conversationId || !chatServiceRef.current)
|
|
3588
|
-
return;
|
|
3589
|
-
const loadConversation = async () => {
|
|
3590
|
-
if (!chatServiceRef.current)
|
|
3591
|
-
return;
|
|
3592
|
-
const conv = await chatServiceRef.current.getConversation(conversationId);
|
|
3593
|
-
if (conv) {
|
|
3594
|
-
setConversation(conv);
|
|
3595
|
-
setMessages(conv.messages);
|
|
3596
|
-
}
|
|
3597
|
-
};
|
|
3598
|
-
loadConversation().catch(console.error);
|
|
3599
|
-
}, [conversationId]);
|
|
3600
|
-
const sendMessage = React13.useCallback(async (content, attachments, opts) => {
|
|
3601
|
-
if (agentMode?.agent) {
|
|
3602
|
-
setIsLoading(true);
|
|
3603
|
-
setError(null);
|
|
3604
|
-
abortControllerRef.current = new AbortController;
|
|
3605
|
-
try {
|
|
3606
|
-
if (!opts?.skipUserAppend) {
|
|
3607
|
-
const userMessage = {
|
|
3608
|
-
id: `msg_${Date.now()}`,
|
|
3609
|
-
conversationId: conversationId ?? "",
|
|
3610
|
-
role: "user",
|
|
3611
|
-
content,
|
|
3612
|
-
status: "completed",
|
|
3613
|
-
createdAt: new Date,
|
|
3614
|
-
updatedAt: new Date,
|
|
3615
|
-
attachments
|
|
3616
|
-
};
|
|
3617
|
-
setMessages((prev) => [...prev, userMessage]);
|
|
3618
|
-
onSend?.(userMessage);
|
|
3619
|
-
}
|
|
3620
|
-
const result = await agentMode.agent.generate({
|
|
3621
|
-
prompt: content,
|
|
3622
|
-
signal: abortControllerRef.current.signal
|
|
3623
|
-
});
|
|
3624
|
-
const toolCallsMap = new Map;
|
|
3625
|
-
for (const tc of result.toolCalls ?? []) {
|
|
3626
|
-
const tr = result.toolResults?.find((r) => r.toolCallId === tc.toolCallId);
|
|
3627
|
-
toolCallsMap.set(tc.toolCallId, {
|
|
3628
|
-
id: tc.toolCallId,
|
|
3629
|
-
name: tc.toolName,
|
|
3630
|
-
args: tc.args ?? {},
|
|
3631
|
-
result: tr?.output,
|
|
3632
|
-
status: "completed"
|
|
3633
|
-
});
|
|
3634
|
-
}
|
|
3635
|
-
const assistantMessage = {
|
|
3636
|
-
id: `msg_${Date.now()}_a`,
|
|
3637
|
-
conversationId: conversationId ?? "",
|
|
3638
|
-
role: "assistant",
|
|
3639
|
-
content: result.text,
|
|
3640
|
-
status: "completed",
|
|
3641
|
-
createdAt: new Date,
|
|
3642
|
-
updatedAt: new Date,
|
|
3643
|
-
toolCalls: toolCallsMap.size ? Array.from(toolCallsMap.values()) : undefined,
|
|
3644
|
-
usage: result.usage
|
|
3645
|
-
};
|
|
3646
|
-
setMessages((prev) => [...prev, assistantMessage]);
|
|
3647
|
-
onResponse?.(assistantMessage);
|
|
3648
|
-
onUsage?.(result.usage ?? { inputTokens: 0, outputTokens: 0 });
|
|
3649
|
-
if (store && !conversationId) {
|
|
3650
|
-
const conv = await store.create({
|
|
3651
|
-
status: "active",
|
|
3652
|
-
provider: "agent",
|
|
3653
|
-
model: "agent",
|
|
3654
|
-
messages: []
|
|
3655
|
-
});
|
|
3656
|
-
if (!opts?.skipUserAppend) {
|
|
3657
|
-
await store.appendMessage(conv.id, {
|
|
3658
|
-
role: "user",
|
|
3659
|
-
content,
|
|
3660
|
-
status: "completed",
|
|
3661
|
-
attachments
|
|
3662
|
-
});
|
|
3663
|
-
}
|
|
3664
|
-
await store.appendMessage(conv.id, {
|
|
3665
|
-
role: "assistant",
|
|
3666
|
-
content: result.text,
|
|
3667
|
-
status: "completed",
|
|
3668
|
-
toolCalls: assistantMessage.toolCalls,
|
|
3669
|
-
usage: result.usage
|
|
3670
|
-
});
|
|
3671
|
-
const updated = await store.get(conv.id);
|
|
3672
|
-
if (updated)
|
|
3673
|
-
setConversation(updated);
|
|
3674
|
-
setConversationId(conv.id);
|
|
3675
|
-
}
|
|
3676
|
-
} catch (err) {
|
|
3677
|
-
setError(err instanceof Error ? err : new Error(String(err)));
|
|
3678
|
-
onError?.(err instanceof Error ? err : new Error(String(err)));
|
|
3679
|
-
} finally {
|
|
3680
|
-
setIsLoading(false);
|
|
3681
|
-
}
|
|
3682
|
-
return;
|
|
3683
|
-
}
|
|
3684
|
-
if (!chatServiceRef.current) {
|
|
3685
|
-
throw new Error("Chat service not initialized");
|
|
3686
|
-
}
|
|
3687
|
-
setIsLoading(true);
|
|
3688
|
-
setError(null);
|
|
3689
|
-
abortControllerRef.current = new AbortController;
|
|
3690
|
-
try {
|
|
3691
|
-
if (!opts?.skipUserAppend) {
|
|
3692
|
-
const userMessage = {
|
|
3693
|
-
id: `msg_${Date.now()}`,
|
|
3694
|
-
conversationId: conversationId ?? "",
|
|
3695
|
-
role: "user",
|
|
3696
|
-
content,
|
|
3697
|
-
status: "completed",
|
|
3698
|
-
createdAt: new Date,
|
|
3699
|
-
updatedAt: new Date,
|
|
3700
|
-
attachments
|
|
3701
|
-
};
|
|
3702
|
-
setMessages((prev) => [...prev, userMessage]);
|
|
3703
|
-
onSend?.(userMessage);
|
|
3704
|
-
}
|
|
3705
|
-
if (streaming) {
|
|
3706
|
-
const result = await chatServiceRef.current.stream({
|
|
3707
|
-
conversationId: conversationId ?? undefined,
|
|
3708
|
-
content,
|
|
3709
|
-
attachments,
|
|
3710
|
-
skipUserAppend: opts?.skipUserAppend
|
|
3711
|
-
});
|
|
3712
|
-
if (!conversationId && !opts?.skipUserAppend) {
|
|
3713
|
-
setConversationId(result.conversationId);
|
|
3714
|
-
}
|
|
3715
|
-
const assistantMessage = {
|
|
3716
|
-
id: result.messageId,
|
|
3717
|
-
conversationId: result.conversationId,
|
|
3718
|
-
role: "assistant",
|
|
3719
|
-
content: "",
|
|
3720
|
-
status: "streaming",
|
|
3721
|
-
createdAt: new Date,
|
|
3722
|
-
updatedAt: new Date
|
|
3723
|
-
};
|
|
3724
|
-
setMessages((prev) => [...prev, assistantMessage]);
|
|
3725
|
-
let fullContent = "";
|
|
3726
|
-
let fullReasoning = "";
|
|
3727
|
-
const toolCallsMap = new Map;
|
|
3728
|
-
const sources = [];
|
|
3729
|
-
for await (const chunk of result.stream) {
|
|
3730
|
-
if (chunk.type === "text" && chunk.content) {
|
|
3731
|
-
fullContent += chunk.content;
|
|
3732
|
-
setMessages((prev) => prev.map((m) => m.id === result.messageId ? {
|
|
3733
|
-
...m,
|
|
3734
|
-
content: fullContent,
|
|
3735
|
-
reasoning: fullReasoning || undefined,
|
|
3736
|
-
sources: sources.length ? sources : undefined,
|
|
3737
|
-
toolCalls: toolCallsMap.size ? Array.from(toolCallsMap.values()) : undefined
|
|
3738
|
-
} : m));
|
|
3739
|
-
} else if (chunk.type === "reasoning" && chunk.content) {
|
|
3740
|
-
fullReasoning += chunk.content;
|
|
3741
|
-
setMessages((prev) => prev.map((m) => m.id === result.messageId ? { ...m, reasoning: fullReasoning } : m));
|
|
3742
|
-
} else if (chunk.type === "source" && chunk.source) {
|
|
3743
|
-
sources.push(chunk.source);
|
|
3744
|
-
setMessages((prev) => prev.map((m) => m.id === result.messageId ? { ...m, sources: [...sources] } : m));
|
|
3745
|
-
} else if (chunk.type === "tool_call" && chunk.toolCall) {
|
|
3746
|
-
const tc = chunk.toolCall;
|
|
3747
|
-
const chatTc = {
|
|
3748
|
-
id: tc.id,
|
|
3749
|
-
name: tc.name,
|
|
3750
|
-
args: tc.args,
|
|
3751
|
-
status: "running"
|
|
3752
|
-
};
|
|
3753
|
-
toolCallsMap.set(tc.id, chatTc);
|
|
3754
|
-
setMessages((prev) => prev.map((m) => m.id === result.messageId ? { ...m, toolCalls: Array.from(toolCallsMap.values()) } : m));
|
|
3755
|
-
} else if (chunk.type === "tool_result" && chunk.toolResult) {
|
|
3756
|
-
const tr = chunk.toolResult;
|
|
3757
|
-
const tc = toolCallsMap.get(tr.toolCallId);
|
|
3758
|
-
if (tc) {
|
|
3759
|
-
tc.result = tr.result;
|
|
3760
|
-
tc.status = "completed";
|
|
3761
|
-
}
|
|
3762
|
-
setMessages((prev) => prev.map((m) => m.id === result.messageId ? { ...m, toolCalls: Array.from(toolCallsMap.values()) } : m));
|
|
3763
|
-
} else if (chunk.type === "done") {
|
|
3764
|
-
setMessages((prev) => prev.map((m) => m.id === result.messageId ? {
|
|
3765
|
-
...m,
|
|
3766
|
-
content: fullContent,
|
|
3767
|
-
reasoning: fullReasoning || undefined,
|
|
3768
|
-
sources: sources.length ? sources : undefined,
|
|
3769
|
-
toolCalls: toolCallsMap.size ? Array.from(toolCallsMap.values()) : undefined,
|
|
3770
|
-
status: "completed",
|
|
3771
|
-
usage: chunk.usage,
|
|
3772
|
-
updatedAt: new Date
|
|
3773
|
-
} : m));
|
|
3774
|
-
onResponse?.(messages.find((m) => m.id === result.messageId) ?? assistantMessage);
|
|
3775
|
-
} else if (chunk.type === "error") {
|
|
3776
|
-
setMessages((prev) => prev.map((m) => m.id === result.messageId ? {
|
|
3777
|
-
...m,
|
|
3778
|
-
status: "error",
|
|
3779
|
-
error: chunk.error,
|
|
3780
|
-
updatedAt: new Date
|
|
3781
|
-
} : m));
|
|
3782
|
-
if (chunk.error) {
|
|
3783
|
-
const err = new Error(chunk.error.message);
|
|
3784
|
-
setError(err);
|
|
3785
|
-
onError?.(err);
|
|
3786
|
-
}
|
|
3787
|
-
}
|
|
3788
|
-
}
|
|
3789
|
-
} else {
|
|
3790
|
-
const result = await chatServiceRef.current.send({
|
|
3791
|
-
conversationId: conversationId ?? undefined,
|
|
3792
|
-
content,
|
|
3793
|
-
attachments,
|
|
3794
|
-
skipUserAppend: opts?.skipUserAppend
|
|
3795
|
-
});
|
|
3796
|
-
setConversation(result.conversation);
|
|
3797
|
-
setMessages(result.conversation.messages);
|
|
3798
|
-
if (!conversationId) {
|
|
3799
|
-
setConversationId(result.conversation.id);
|
|
3800
|
-
}
|
|
3801
|
-
onResponse?.(result.message);
|
|
3802
|
-
}
|
|
3803
|
-
} catch (err) {
|
|
3804
|
-
const error2 = err instanceof Error ? err : new Error(String(err));
|
|
3805
|
-
setError(error2);
|
|
3806
|
-
onError?.(error2);
|
|
3807
|
-
} finally {
|
|
3808
|
-
setIsLoading(false);
|
|
3809
|
-
abortControllerRef.current = null;
|
|
3810
|
-
}
|
|
3811
|
-
}, [
|
|
3812
|
-
conversationId,
|
|
3813
|
-
streaming,
|
|
3814
|
-
onSend,
|
|
3815
|
-
onResponse,
|
|
3816
|
-
onError,
|
|
3817
|
-
onUsage,
|
|
3818
|
-
messages,
|
|
3819
|
-
agentMode,
|
|
3820
|
-
store
|
|
3821
|
-
]);
|
|
3822
|
-
const clearConversation = React13.useCallback(() => {
|
|
3823
|
-
setMessages([]);
|
|
3824
|
-
setConversation(null);
|
|
3825
|
-
setConversationId(null);
|
|
3826
|
-
setError(null);
|
|
3827
|
-
}, []);
|
|
3828
|
-
const regenerate = React13.useCallback(async () => {
|
|
3829
|
-
let lastUserMessageIndex = -1;
|
|
3830
|
-
for (let i = messages.length - 1;i >= 0; i--) {
|
|
3831
|
-
const m = messages[i];
|
|
3832
|
-
if (m?.role === "user") {
|
|
3833
|
-
lastUserMessageIndex = i;
|
|
3834
|
-
break;
|
|
3835
|
-
}
|
|
3836
|
-
}
|
|
3837
|
-
if (lastUserMessageIndex === -1)
|
|
3838
|
-
return;
|
|
3839
|
-
const lastUserMessage = messages[lastUserMessageIndex];
|
|
3840
|
-
if (!lastUserMessage)
|
|
3841
|
-
return;
|
|
3842
|
-
setMessages((prev) => prev.slice(0, lastUserMessageIndex + 1));
|
|
3843
|
-
await sendMessage(lastUserMessage.content, lastUserMessage.attachments);
|
|
3844
|
-
}, [messages, sendMessage]);
|
|
3845
|
-
const stop = React13.useCallback(() => {
|
|
3846
|
-
abortControllerRef.current?.abort();
|
|
3847
|
-
setIsLoading(false);
|
|
3848
|
-
}, []);
|
|
3849
|
-
const createNewConversation = clearConversation;
|
|
3850
|
-
const editMessage = React13.useCallback(async (messageId, newContent) => {
|
|
3851
|
-
if (!chatServiceRef.current || !conversationId)
|
|
3852
|
-
return;
|
|
3853
|
-
const msg = messages.find((m) => m.id === messageId);
|
|
3854
|
-
if (!msg || msg.role !== "user")
|
|
3855
|
-
return;
|
|
3856
|
-
await chatServiceRef.current.updateMessage(conversationId, messageId, {
|
|
3857
|
-
content: newContent
|
|
3858
|
-
});
|
|
3859
|
-
const truncated = await chatServiceRef.current.truncateAfter(conversationId, messageId);
|
|
3860
|
-
if (truncated) {
|
|
3861
|
-
setMessages(truncated.messages);
|
|
3862
|
-
}
|
|
3863
|
-
await sendMessage(newContent, undefined, { skipUserAppend: true });
|
|
3864
|
-
}, [conversationId, messages, sendMessage]);
|
|
3865
|
-
const forkConversation = React13.useCallback(async (upToMessageId) => {
|
|
3866
|
-
if (!chatServiceRef.current)
|
|
3867
|
-
return null;
|
|
3868
|
-
const idToFork = conversationId ?? conversation?.id;
|
|
3869
|
-
if (!idToFork)
|
|
3870
|
-
return null;
|
|
3871
|
-
try {
|
|
3872
|
-
const forked = await chatServiceRef.current.forkConversation(idToFork, upToMessageId);
|
|
3873
|
-
setConversationId(forked.id);
|
|
3874
|
-
setConversation(forked);
|
|
3875
|
-
setMessages(forked.messages);
|
|
3876
|
-
return forked.id;
|
|
3877
|
-
} catch {
|
|
3878
|
-
return null;
|
|
3879
|
-
}
|
|
3880
|
-
}, [conversationId, conversation]);
|
|
3881
|
-
const updateConversationFn = React13.useCallback(async (updates) => {
|
|
3882
|
-
if (!chatServiceRef.current || !conversationId)
|
|
3883
|
-
return null;
|
|
3884
|
-
const updated = await chatServiceRef.current.updateConversation(conversationId, updates);
|
|
3885
|
-
if (updated)
|
|
3886
|
-
setConversation(updated);
|
|
3887
|
-
return updated;
|
|
3888
|
-
}, [conversationId]);
|
|
3889
|
-
const addToolApprovalResponse = React13.useCallback((_toolCallId, _result) => {
|
|
3890
|
-
throw new Error(`addToolApprovalResponse: Tool approval requires server route with toUIMessageStreamResponse. ` + `Use createChatRoute and @ai-sdk/react useChat for tools with requireApproval.`);
|
|
3891
|
-
}, []);
|
|
3892
|
-
const hasApprovalTools = toolsDefs?.some((t) => t.requireApproval) ?? false;
|
|
3893
|
-
return {
|
|
3894
|
-
messages,
|
|
3895
|
-
conversation,
|
|
3896
|
-
isLoading,
|
|
3897
|
-
error,
|
|
3898
|
-
sendMessage,
|
|
3899
|
-
clearConversation,
|
|
3900
|
-
setConversationId,
|
|
3901
|
-
regenerate,
|
|
3902
|
-
stop,
|
|
3903
|
-
createNewConversation,
|
|
3904
|
-
editMessage,
|
|
3905
|
-
forkConversation,
|
|
3906
|
-
updateConversation: updateConversationFn,
|
|
3907
|
-
...hasApprovalTools && { addToolApprovalResponse }
|
|
3908
|
-
};
|
|
3909
|
-
}
|
|
109
|
+
${K.content??""}
|
|
110
|
+
\`\`\``;return`
|
|
3910
111
|
|
|
3911
|
-
// src/presentation/components/ChatWithSidebar.tsx
|
|
3912
|
-
import { jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
3913
|
-
"use client";
|
|
3914
|
-
var defaultStore = createLocalStorageConversationStore();
|
|
3915
|
-
function ChatWithSidebar({
|
|
3916
|
-
store = defaultStore,
|
|
3917
|
-
projectId,
|
|
3918
|
-
tags,
|
|
3919
|
-
className,
|
|
3920
|
-
thinkingLevel: initialThinkingLevel = "thinking",
|
|
3921
|
-
presentationRenderer,
|
|
3922
|
-
formRenderer,
|
|
3923
|
-
dataViewRenderer,
|
|
3924
|
-
components,
|
|
3925
|
-
suggestions,
|
|
3926
|
-
suggestionComponents,
|
|
3927
|
-
showSuggestionsWhenEmpty = false,
|
|
3928
|
-
...useChatOptions
|
|
3929
|
-
}) {
|
|
3930
|
-
const effectiveStore = store;
|
|
3931
|
-
const [thinkingLevel, setThinkingLevel] = React14.useState(initialThinkingLevel);
|
|
3932
|
-
const chat = useChat({
|
|
3933
|
-
...useChatOptions,
|
|
3934
|
-
store: effectiveStore,
|
|
3935
|
-
thinkingLevel
|
|
3936
|
-
});
|
|
3937
|
-
const {
|
|
3938
|
-
messages,
|
|
3939
|
-
conversation,
|
|
3940
|
-
sendMessage,
|
|
3941
|
-
isLoading,
|
|
3942
|
-
setConversationId,
|
|
3943
|
-
createNewConversation,
|
|
3944
|
-
editMessage,
|
|
3945
|
-
forkConversation,
|
|
3946
|
-
updateConversation
|
|
3947
|
-
} = chat;
|
|
3948
|
-
const selectedConversationId = conversation?.id ?? null;
|
|
3949
|
-
const handleSelectConversation = React14.useCallback((id) => {
|
|
3950
|
-
setConversationId(id);
|
|
3951
|
-
}, [setConversationId]);
|
|
3952
|
-
const handleSuggestionClick = React14.useCallback((suggestion) => {
|
|
3953
|
-
sendMessage(suggestion);
|
|
3954
|
-
}, [sendMessage]);
|
|
3955
|
-
return /* @__PURE__ */ jsxs13("div", {
|
|
3956
|
-
className: className ?? "flex h-full w-full",
|
|
3957
|
-
children: [
|
|
3958
|
-
/* @__PURE__ */ jsx14(ChatSidebar, {
|
|
3959
|
-
store: effectiveStore,
|
|
3960
|
-
selectedConversationId,
|
|
3961
|
-
onSelectConversation: handleSelectConversation,
|
|
3962
|
-
onCreateNew: createNewConversation,
|
|
3963
|
-
projectId,
|
|
3964
|
-
tags,
|
|
3965
|
-
selectedConversation: conversation,
|
|
3966
|
-
onUpdateConversation: updateConversation ? async (id, updates) => {
|
|
3967
|
-
if (id === selectedConversationId) {
|
|
3968
|
-
await updateConversation(updates);
|
|
3969
|
-
}
|
|
3970
|
-
} : undefined
|
|
3971
|
-
}),
|
|
3972
|
-
/* @__PURE__ */ jsx14("div", {
|
|
3973
|
-
className: "flex min-w-0 flex-1 flex-col",
|
|
3974
|
-
children: /* @__PURE__ */ jsx14(ChatWithExport, {
|
|
3975
|
-
messages,
|
|
3976
|
-
conversation,
|
|
3977
|
-
onCreateNew: createNewConversation,
|
|
3978
|
-
onFork: forkConversation,
|
|
3979
|
-
onEditMessage: editMessage,
|
|
3980
|
-
thinkingLevel,
|
|
3981
|
-
onThinkingLevelChange: setThinkingLevel,
|
|
3982
|
-
presentationRenderer,
|
|
3983
|
-
formRenderer,
|
|
3984
|
-
dataViewRenderer,
|
|
3985
|
-
components,
|
|
3986
|
-
suggestions,
|
|
3987
|
-
onSuggestionClick: handleSuggestionClick,
|
|
3988
|
-
suggestionComponents,
|
|
3989
|
-
showSuggestionsWhenEmpty,
|
|
3990
|
-
children: /* @__PURE__ */ jsx14(ChatInput, {
|
|
3991
|
-
onSend: (content, att) => sendMessage(content, att),
|
|
3992
|
-
disabled: isLoading,
|
|
3993
|
-
isLoading
|
|
3994
|
-
})
|
|
3995
|
-
})
|
|
3996
|
-
})
|
|
3997
|
-
]
|
|
3998
|
-
});
|
|
3999
|
-
}
|
|
4000
|
-
// src/presentation/components/ContextIndicator.tsx
|
|
4001
|
-
import { Badge } from "@contractspec/lib.ui-kit-web/ui/badge";
|
|
4002
|
-
import {
|
|
4003
|
-
Tooltip,
|
|
4004
|
-
TooltipContent,
|
|
4005
|
-
TooltipProvider,
|
|
4006
|
-
TooltipTrigger
|
|
4007
|
-
} from "@contractspec/lib.ui-kit-web/ui/tooltip";
|
|
4008
|
-
import { cn as cn11 } from "@contractspec/lib.ui-kit-web/ui/utils";
|
|
4009
|
-
import { FileCode, FolderOpen, Info, Zap } from "lucide-react";
|
|
4010
|
-
import { jsx as jsx15, jsxs as jsxs14, Fragment as Fragment7 } from "react/jsx-runtime";
|
|
4011
|
-
"use client";
|
|
4012
|
-
function ContextIndicator({
|
|
4013
|
-
summary,
|
|
4014
|
-
active = false,
|
|
4015
|
-
className,
|
|
4016
|
-
showDetails = true
|
|
4017
|
-
}) {
|
|
4018
|
-
if (!summary && !active) {
|
|
4019
|
-
return /* @__PURE__ */ jsxs14("div", {
|
|
4020
|
-
className: cn11("flex items-center gap-1.5 text-sm", "text-muted-foreground", className),
|
|
4021
|
-
children: [
|
|
4022
|
-
/* @__PURE__ */ jsx15(Info, {
|
|
4023
|
-
className: "h-4 w-4"
|
|
4024
|
-
}),
|
|
4025
|
-
/* @__PURE__ */ jsx15("span", {
|
|
4026
|
-
children: "No workspace context"
|
|
4027
|
-
})
|
|
4028
|
-
]
|
|
4029
|
-
});
|
|
4030
|
-
}
|
|
4031
|
-
const content = /* @__PURE__ */ jsxs14("div", {
|
|
4032
|
-
className: cn11("flex items-center gap-2", active ? "text-foreground" : "text-muted-foreground", className),
|
|
4033
|
-
children: [
|
|
4034
|
-
/* @__PURE__ */ jsxs14(Badge, {
|
|
4035
|
-
variant: active ? "default" : "secondary",
|
|
4036
|
-
className: "flex items-center gap-1",
|
|
4037
|
-
children: [
|
|
4038
|
-
/* @__PURE__ */ jsx15(Zap, {
|
|
4039
|
-
className: "h-3 w-3"
|
|
4040
|
-
}),
|
|
4041
|
-
"Context"
|
|
4042
|
-
]
|
|
4043
|
-
}),
|
|
4044
|
-
summary && showDetails && /* @__PURE__ */ jsxs14(Fragment7, {
|
|
4045
|
-
children: [
|
|
4046
|
-
/* @__PURE__ */ jsxs14("div", {
|
|
4047
|
-
className: "flex items-center gap-1 text-xs",
|
|
4048
|
-
children: [
|
|
4049
|
-
/* @__PURE__ */ jsx15(FolderOpen, {
|
|
4050
|
-
className: "h-3.5 w-3.5"
|
|
4051
|
-
}),
|
|
4052
|
-
/* @__PURE__ */ jsx15("span", {
|
|
4053
|
-
children: summary.name
|
|
4054
|
-
})
|
|
4055
|
-
]
|
|
4056
|
-
}),
|
|
4057
|
-
/* @__PURE__ */ jsxs14("div", {
|
|
4058
|
-
className: "flex items-center gap-1 text-xs",
|
|
4059
|
-
children: [
|
|
4060
|
-
/* @__PURE__ */ jsx15(FileCode, {
|
|
4061
|
-
className: "h-3.5 w-3.5"
|
|
4062
|
-
}),
|
|
4063
|
-
/* @__PURE__ */ jsxs14("span", {
|
|
4064
|
-
children: [
|
|
4065
|
-
summary.specs.total,
|
|
4066
|
-
" specs"
|
|
4067
|
-
]
|
|
4068
|
-
})
|
|
4069
|
-
]
|
|
4070
|
-
})
|
|
4071
|
-
]
|
|
4072
|
-
})
|
|
4073
|
-
]
|
|
4074
|
-
});
|
|
4075
|
-
if (!summary) {
|
|
4076
|
-
return content;
|
|
4077
|
-
}
|
|
4078
|
-
return /* @__PURE__ */ jsx15(TooltipProvider, {
|
|
4079
|
-
children: /* @__PURE__ */ jsxs14(Tooltip, {
|
|
4080
|
-
children: [
|
|
4081
|
-
/* @__PURE__ */ jsx15(TooltipTrigger, {
|
|
4082
|
-
asChild: true,
|
|
4083
|
-
children: content
|
|
4084
|
-
}),
|
|
4085
|
-
/* @__PURE__ */ jsx15(TooltipContent, {
|
|
4086
|
-
side: "bottom",
|
|
4087
|
-
className: "max-w-[300px]",
|
|
4088
|
-
children: /* @__PURE__ */ jsxs14("div", {
|
|
4089
|
-
className: "flex flex-col gap-2 text-sm",
|
|
4090
|
-
children: [
|
|
4091
|
-
/* @__PURE__ */ jsx15("div", {
|
|
4092
|
-
className: "font-medium",
|
|
4093
|
-
children: summary.name
|
|
4094
|
-
}),
|
|
4095
|
-
/* @__PURE__ */ jsx15("div", {
|
|
4096
|
-
className: "text-muted-foreground text-xs",
|
|
4097
|
-
children: summary.path
|
|
4098
|
-
}),
|
|
4099
|
-
/* @__PURE__ */ jsx15("div", {
|
|
4100
|
-
className: "border-t pt-2",
|
|
4101
|
-
children: /* @__PURE__ */ jsxs14("div", {
|
|
4102
|
-
className: "grid grid-cols-2 gap-1 text-xs",
|
|
4103
|
-
children: [
|
|
4104
|
-
/* @__PURE__ */ jsx15("span", {
|
|
4105
|
-
children: "Commands:"
|
|
4106
|
-
}),
|
|
4107
|
-
/* @__PURE__ */ jsx15("span", {
|
|
4108
|
-
className: "text-right",
|
|
4109
|
-
children: summary.specs.commands
|
|
4110
|
-
}),
|
|
4111
|
-
/* @__PURE__ */ jsx15("span", {
|
|
4112
|
-
children: "Queries:"
|
|
4113
|
-
}),
|
|
4114
|
-
/* @__PURE__ */ jsx15("span", {
|
|
4115
|
-
className: "text-right",
|
|
4116
|
-
children: summary.specs.queries
|
|
4117
|
-
}),
|
|
4118
|
-
/* @__PURE__ */ jsx15("span", {
|
|
4119
|
-
children: "Events:"
|
|
4120
|
-
}),
|
|
4121
|
-
/* @__PURE__ */ jsx15("span", {
|
|
4122
|
-
className: "text-right",
|
|
4123
|
-
children: summary.specs.events
|
|
4124
|
-
}),
|
|
4125
|
-
/* @__PURE__ */ jsx15("span", {
|
|
4126
|
-
children: "Presentations:"
|
|
4127
|
-
}),
|
|
4128
|
-
/* @__PURE__ */ jsx15("span", {
|
|
4129
|
-
className: "text-right",
|
|
4130
|
-
children: summary.specs.presentations
|
|
4131
|
-
})
|
|
4132
|
-
]
|
|
4133
|
-
})
|
|
4134
|
-
}),
|
|
4135
|
-
/* @__PURE__ */ jsxs14("div", {
|
|
4136
|
-
className: "border-t pt-2 text-xs",
|
|
4137
|
-
children: [
|
|
4138
|
-
/* @__PURE__ */ jsxs14("span", {
|
|
4139
|
-
children: [
|
|
4140
|
-
summary.files.total,
|
|
4141
|
-
" files"
|
|
4142
|
-
]
|
|
4143
|
-
}),
|
|
4144
|
-
/* @__PURE__ */ jsx15("span", {
|
|
4145
|
-
className: "mx-1",
|
|
4146
|
-
children: "\u2022"
|
|
4147
|
-
}),
|
|
4148
|
-
/* @__PURE__ */ jsxs14("span", {
|
|
4149
|
-
children: [
|
|
4150
|
-
summary.files.specFiles,
|
|
4151
|
-
" spec files"
|
|
4152
|
-
]
|
|
4153
|
-
})
|
|
4154
|
-
]
|
|
4155
|
-
})
|
|
4156
|
-
]
|
|
4157
|
-
})
|
|
4158
|
-
})
|
|
4159
|
-
]
|
|
4160
|
-
})
|
|
4161
|
-
});
|
|
4162
|
-
}
|
|
4163
|
-
// src/presentation/components/ModelPicker.tsx
|
|
4164
|
-
import {
|
|
4165
|
-
getModelsForProvider
|
|
4166
|
-
} from "@contractspec/lib.ai-providers";
|
|
4167
|
-
import { Button as Button7 } from "@contractspec/lib.design-system";
|
|
4168
|
-
import { Badge as Badge2 } from "@contractspec/lib.ui-kit-web/ui/badge";
|
|
4169
|
-
import { Label as Label2 } from "@contractspec/lib.ui-kit-web/ui/label";
|
|
4170
|
-
import {
|
|
4171
|
-
Select as Select2,
|
|
4172
|
-
SelectContent as SelectContent2,
|
|
4173
|
-
SelectItem as SelectItem2,
|
|
4174
|
-
SelectTrigger as SelectTrigger2,
|
|
4175
|
-
SelectValue as SelectValue2
|
|
4176
|
-
} from "@contractspec/lib.ui-kit-web/ui/select";
|
|
4177
|
-
import { cn as cn12 } from "@contractspec/lib.ui-kit-web/ui/utils";
|
|
4178
|
-
import { Bot as Bot2, Cloud, Cpu, Sparkles } from "lucide-react";
|
|
4179
|
-
import * as React15 from "react";
|
|
4180
|
-
import { jsx as jsx16, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
4181
|
-
"use client";
|
|
4182
|
-
var PROVIDER_ICONS = {
|
|
4183
|
-
ollama: /* @__PURE__ */ jsx16(Cpu, {
|
|
4184
|
-
className: "h-4 w-4"
|
|
4185
|
-
}),
|
|
4186
|
-
openai: /* @__PURE__ */ jsx16(Bot2, {
|
|
4187
|
-
className: "h-4 w-4"
|
|
4188
|
-
}),
|
|
4189
|
-
anthropic: /* @__PURE__ */ jsx16(Sparkles, {
|
|
4190
|
-
className: "h-4 w-4"
|
|
4191
|
-
}),
|
|
4192
|
-
mistral: /* @__PURE__ */ jsx16(Cloud, {
|
|
4193
|
-
className: "h-4 w-4"
|
|
4194
|
-
}),
|
|
4195
|
-
gemini: /* @__PURE__ */ jsx16(Sparkles, {
|
|
4196
|
-
className: "h-4 w-4"
|
|
4197
|
-
})
|
|
4198
|
-
};
|
|
4199
|
-
var PROVIDER_NAMES = {
|
|
4200
|
-
ollama: "Ollama (Local)",
|
|
4201
|
-
openai: "OpenAI",
|
|
4202
|
-
anthropic: "Anthropic",
|
|
4203
|
-
mistral: "Mistral",
|
|
4204
|
-
gemini: "Google Gemini"
|
|
4205
|
-
};
|
|
4206
|
-
var MODE_BADGES = {
|
|
4207
|
-
local: { label: "Local", variant: "secondary" },
|
|
4208
|
-
byok: { label: "BYOK", variant: "outline" },
|
|
4209
|
-
managed: { label: "Managed", variant: "default" }
|
|
4210
|
-
};
|
|
4211
|
-
function ModelPicker({
|
|
4212
|
-
value,
|
|
4213
|
-
onChange,
|
|
4214
|
-
availableProviders,
|
|
4215
|
-
className,
|
|
4216
|
-
compact = false
|
|
4217
|
-
}) {
|
|
4218
|
-
const providers = availableProviders ?? [
|
|
4219
|
-
{ provider: "ollama", available: true, mode: "local" },
|
|
4220
|
-
{ provider: "openai", available: true, mode: "byok" },
|
|
4221
|
-
{ provider: "anthropic", available: true, mode: "byok" },
|
|
4222
|
-
{ provider: "mistral", available: true, mode: "byok" },
|
|
4223
|
-
{ provider: "gemini", available: true, mode: "byok" }
|
|
4224
|
-
];
|
|
4225
|
-
const models = getModelsForProvider(value.provider);
|
|
4226
|
-
const selectedModel = models.find((m) => m.id === value.model);
|
|
4227
|
-
const handleProviderChange = React15.useCallback((providerName) => {
|
|
4228
|
-
const provider = providerName;
|
|
4229
|
-
const providerInfo = providers.find((p) => p.provider === provider);
|
|
4230
|
-
const providerModels = getModelsForProvider(provider);
|
|
4231
|
-
const defaultModel = providerModels[0]?.id ?? "";
|
|
4232
|
-
onChange({
|
|
4233
|
-
provider,
|
|
4234
|
-
model: defaultModel,
|
|
4235
|
-
mode: providerInfo?.mode ?? "byok"
|
|
4236
|
-
});
|
|
4237
|
-
}, [onChange, providers]);
|
|
4238
|
-
const handleModelChange = React15.useCallback((modelId) => {
|
|
4239
|
-
onChange({
|
|
4240
|
-
...value,
|
|
4241
|
-
model: modelId
|
|
4242
|
-
});
|
|
4243
|
-
}, [onChange, value]);
|
|
4244
|
-
if (compact) {
|
|
4245
|
-
return /* @__PURE__ */ jsxs15("div", {
|
|
4246
|
-
className: cn12("flex items-center gap-2", className),
|
|
4247
|
-
children: [
|
|
4248
|
-
/* @__PURE__ */ jsxs15(Select2, {
|
|
4249
|
-
value: value.provider,
|
|
4250
|
-
onValueChange: handleProviderChange,
|
|
4251
|
-
children: [
|
|
4252
|
-
/* @__PURE__ */ jsx16(SelectTrigger2, {
|
|
4253
|
-
className: "w-[140px]",
|
|
4254
|
-
children: /* @__PURE__ */ jsx16(SelectValue2, {})
|
|
4255
|
-
}),
|
|
4256
|
-
/* @__PURE__ */ jsx16(SelectContent2, {
|
|
4257
|
-
children: providers.map((p) => /* @__PURE__ */ jsx16(SelectItem2, {
|
|
4258
|
-
value: p.provider,
|
|
4259
|
-
disabled: !p.available,
|
|
4260
|
-
children: /* @__PURE__ */ jsxs15("div", {
|
|
4261
|
-
className: "flex items-center gap-2",
|
|
4262
|
-
children: [
|
|
4263
|
-
PROVIDER_ICONS[p.provider],
|
|
4264
|
-
/* @__PURE__ */ jsx16("span", {
|
|
4265
|
-
children: PROVIDER_NAMES[p.provider]
|
|
4266
|
-
})
|
|
4267
|
-
]
|
|
4268
|
-
})
|
|
4269
|
-
}, p.provider))
|
|
4270
|
-
})
|
|
4271
|
-
]
|
|
4272
|
-
}),
|
|
4273
|
-
/* @__PURE__ */ jsxs15(Select2, {
|
|
4274
|
-
value: value.model,
|
|
4275
|
-
onValueChange: handleModelChange,
|
|
4276
|
-
children: [
|
|
4277
|
-
/* @__PURE__ */ jsx16(SelectTrigger2, {
|
|
4278
|
-
className: "w-[160px]",
|
|
4279
|
-
children: /* @__PURE__ */ jsx16(SelectValue2, {})
|
|
4280
|
-
}),
|
|
4281
|
-
/* @__PURE__ */ jsx16(SelectContent2, {
|
|
4282
|
-
children: models.map((m) => /* @__PURE__ */ jsx16(SelectItem2, {
|
|
4283
|
-
value: m.id,
|
|
4284
|
-
children: m.name
|
|
4285
|
-
}, m.id))
|
|
4286
|
-
})
|
|
4287
|
-
]
|
|
4288
|
-
})
|
|
4289
|
-
]
|
|
4290
|
-
});
|
|
4291
|
-
}
|
|
4292
|
-
return /* @__PURE__ */ jsxs15("div", {
|
|
4293
|
-
className: cn12("flex flex-col gap-3", className),
|
|
4294
|
-
children: [
|
|
4295
|
-
/* @__PURE__ */ jsxs15("div", {
|
|
4296
|
-
className: "flex flex-col gap-1.5",
|
|
4297
|
-
children: [
|
|
4298
|
-
/* @__PURE__ */ jsx16(Label2, {
|
|
4299
|
-
htmlFor: "provider-selection",
|
|
4300
|
-
className: "font-medium text-sm",
|
|
4301
|
-
children: "Provider"
|
|
4302
|
-
}),
|
|
4303
|
-
/* @__PURE__ */ jsx16("div", {
|
|
4304
|
-
className: "flex flex-wrap gap-2",
|
|
4305
|
-
id: "provider-selection",
|
|
4306
|
-
children: providers.map((p) => /* @__PURE__ */ jsxs15(Button7, {
|
|
4307
|
-
variant: value.provider === p.provider ? "default" : "outline",
|
|
4308
|
-
size: "sm",
|
|
4309
|
-
onPress: () => p.available && handleProviderChange(p.provider),
|
|
4310
|
-
disabled: !p.available,
|
|
4311
|
-
className: cn12(!p.available && "opacity-50"),
|
|
4312
|
-
children: [
|
|
4313
|
-
PROVIDER_ICONS[p.provider],
|
|
4314
|
-
/* @__PURE__ */ jsx16("span", {
|
|
4315
|
-
children: PROVIDER_NAMES[p.provider]
|
|
4316
|
-
}),
|
|
4317
|
-
/* @__PURE__ */ jsx16(Badge2, {
|
|
4318
|
-
variant: MODE_BADGES[p.mode].variant,
|
|
4319
|
-
className: "ml-1",
|
|
4320
|
-
children: MODE_BADGES[p.mode].label
|
|
4321
|
-
})
|
|
4322
|
-
]
|
|
4323
|
-
}, p.provider))
|
|
4324
|
-
})
|
|
4325
|
-
]
|
|
4326
|
-
}),
|
|
4327
|
-
/* @__PURE__ */ jsxs15("div", {
|
|
4328
|
-
className: "flex flex-col gap-1.5",
|
|
4329
|
-
children: [
|
|
4330
|
-
/* @__PURE__ */ jsx16(Label2, {
|
|
4331
|
-
htmlFor: "model-picker",
|
|
4332
|
-
className: "font-medium text-sm",
|
|
4333
|
-
children: "Model"
|
|
4334
|
-
}),
|
|
4335
|
-
/* @__PURE__ */ jsxs15(Select2, {
|
|
4336
|
-
name: "model-picker",
|
|
4337
|
-
value: value.model,
|
|
4338
|
-
onValueChange: handleModelChange,
|
|
4339
|
-
children: [
|
|
4340
|
-
/* @__PURE__ */ jsx16(SelectTrigger2, {
|
|
4341
|
-
children: /* @__PURE__ */ jsx16(SelectValue2, {
|
|
4342
|
-
placeholder: "Select a model"
|
|
4343
|
-
})
|
|
4344
|
-
}),
|
|
4345
|
-
/* @__PURE__ */ jsx16(SelectContent2, {
|
|
4346
|
-
children: models.map((m) => /* @__PURE__ */ jsx16(SelectItem2, {
|
|
4347
|
-
value: m.id,
|
|
4348
|
-
children: /* @__PURE__ */ jsxs15("div", {
|
|
4349
|
-
className: "flex items-center gap-2",
|
|
4350
|
-
children: [
|
|
4351
|
-
/* @__PURE__ */ jsx16("span", {
|
|
4352
|
-
children: m.name
|
|
4353
|
-
}),
|
|
4354
|
-
/* @__PURE__ */ jsxs15("span", {
|
|
4355
|
-
className: "text-muted-foreground text-xs",
|
|
4356
|
-
children: [
|
|
4357
|
-
Math.round(m.contextWindow / 1000),
|
|
4358
|
-
"K"
|
|
4359
|
-
]
|
|
4360
|
-
}),
|
|
4361
|
-
m.capabilities.vision && /* @__PURE__ */ jsx16(Badge2, {
|
|
4362
|
-
variant: "outline",
|
|
4363
|
-
className: "text-xs",
|
|
4364
|
-
children: "Vision"
|
|
4365
|
-
}),
|
|
4366
|
-
m.capabilities.reasoning && /* @__PURE__ */ jsx16(Badge2, {
|
|
4367
|
-
variant: "outline",
|
|
4368
|
-
className: "text-xs",
|
|
4369
|
-
children: "Reasoning"
|
|
4370
|
-
})
|
|
4371
|
-
]
|
|
4372
|
-
})
|
|
4373
|
-
}, m.id))
|
|
4374
|
-
})
|
|
4375
|
-
]
|
|
4376
|
-
})
|
|
4377
|
-
]
|
|
4378
|
-
}),
|
|
4379
|
-
selectedModel && /* @__PURE__ */ jsxs15("div", {
|
|
4380
|
-
className: "flex flex-wrap gap-2 text-muted-foreground text-xs",
|
|
4381
|
-
children: [
|
|
4382
|
-
/* @__PURE__ */ jsxs15("span", {
|
|
4383
|
-
children: [
|
|
4384
|
-
"Context: ",
|
|
4385
|
-
Math.round(selectedModel.contextWindow / 1000),
|
|
4386
|
-
"K tokens"
|
|
4387
|
-
]
|
|
4388
|
-
}),
|
|
4389
|
-
selectedModel.capabilities.vision && /* @__PURE__ */ jsx16("span", {
|
|
4390
|
-
children: "\u2022 Vision"
|
|
4391
|
-
}),
|
|
4392
|
-
selectedModel.capabilities.tools && /* @__PURE__ */ jsx16("span", {
|
|
4393
|
-
children: "\u2022 Tools"
|
|
4394
|
-
}),
|
|
4395
|
-
selectedModel.capabilities.reasoning && /* @__PURE__ */ jsx16("span", {
|
|
4396
|
-
children: "\u2022 Reasoning"
|
|
4397
|
-
})
|
|
4398
|
-
]
|
|
4399
|
-
})
|
|
4400
|
-
]
|
|
4401
|
-
});
|
|
4402
|
-
}
|
|
4403
|
-
export {
|
|
4404
|
-
isPresentationToolResult,
|
|
4405
|
-
isFormToolResult,
|
|
4406
|
-
isDataViewToolResult,
|
|
4407
|
-
ToolResultRenderer,
|
|
4408
|
-
ThinkingLevelPicker,
|
|
4409
|
-
Suggestions,
|
|
4410
|
-
Suggestion,
|
|
4411
|
-
SourcesTrigger,
|
|
4412
|
-
SourcesContent,
|
|
4413
|
-
Sources,
|
|
4414
|
-
Source,
|
|
4415
|
-
ReasoningTrigger,
|
|
4416
|
-
ReasoningContent,
|
|
4417
|
-
Reasoning,
|
|
4418
|
-
ModelPicker,
|
|
4419
|
-
ContextIndicator,
|
|
4420
|
-
CodePreview,
|
|
4421
|
-
ChatWithSidebar,
|
|
4422
|
-
ChatWithExport,
|
|
4423
|
-
ChatSidebar,
|
|
4424
|
-
ChatMessage,
|
|
4425
|
-
ChatInput,
|
|
4426
|
-
ChatExportToolbar,
|
|
4427
|
-
ChatContainer,
|
|
4428
|
-
ChainOfThoughtStep,
|
|
4429
|
-
ChainOfThoughtHeader,
|
|
4430
|
-
ChainOfThoughtContent,
|
|
4431
|
-
ChainOfThought
|
|
4432
|
-
};
|
|
112
|
+
[Attachment: ${K.name}]`}).join("");q+=H}J.push({role:"user",content:q})}else if(X.role==="assistant")if(X.toolCalls?.length)J.push({role:"assistant",content:X.content||"",toolCalls:X.toolCalls.map((q)=>({type:"tool-call",toolCallId:q.id,toolName:q.name,args:q.args}))}),J.push({role:"tool",content:X.toolCalls.map((q)=>({type:"tool-result",toolCallId:q.id,toolName:q.name,output:q.result}))});else J.push({role:"assistant",content:X.content})}return J}}function o7($){return new l0($)}function x5($){let Z={};for(let Y of $)Z[Y.name]=h5({description:Y.description??Y.name,inputSchema:j5.object({}).passthrough(),execute:async()=>({})});return Z}function P2($={}){let{provider:Z="openai",mode:Y="byok",model:J,apiKey:Q,proxyUrl:X,conversationId:q,store:H,systemPrompt:K,streaming:W=!0,onSend:G,onResponse:V,onError:_,onUsage:F,tools:E,thinkingLevel:A,workflowToolsConfig:P,modelSelector:z,contractsContext:B,surfacePlanConfig:x,mcpServers:k,agentMode:L}=$,[C,y]=I.useState([]),[i,v]=I.useState(null),g=I.useRef(null),[J0,M]=I.useState(null),[r,D]=I.useState(!1),[s,N0]=I.useState(null),[u,E0]=I.useState(q??null),y0=I.useRef(null),o=I.useRef(null);I.useEffect(()=>{if(!k?.length){v(null);return}let R=!1;return import("@contractspec/lib.ai-agent/tools/mcp-client.browser").then(({createMcpToolsets:T})=>{T(k).then(({tools:f,cleanup:O})=>{if(!R)v(f),g.current=O;else O().catch(()=>{return})}).catch(()=>{if(!R)v(null)})}),()=>{R=!0;let T=g.current;if(g.current=null,T)T().catch(()=>{return});v(null)}},[k]),I.useEffect(()=>{let R=f5({provider:Z,model:J,apiKey:Q,proxyUrl:X});o.current=new l0({provider:R,store:H,systemPrompt:K,onUsage:F,tools:E?.length?x5(E):void 0,thinkingLevel:A,workflowToolsConfig:P,modelSelector:z,contractsContext:B,surfacePlanConfig:x,mcpTools:i})},[Z,Y,J,Q,X,H,K,F,E,A,P,z,B,x,i]),I.useEffect(()=>{if(!u||!o.current)return;(async()=>{if(!o.current)return;let T=await o.current.getConversation(u);if(T)M(T),y(T.messages)})().catch(console.error)},[u]);let R0=I.useCallback(async(R,T,f)=>{if(L?.agent){D(!0),N0(null),y0.current=new AbortController;try{if(!f?.skipUserAppend){let m={id:`msg_${Date.now()}`,conversationId:u??"",role:"user",content:R,status:"completed",createdAt:new Date,updatedAt:new Date,attachments:T};y((a)=>[...a,m]),G?.(m)}let O=await L.agent.generate({prompt:R,signal:y0.current.signal}),e=new Map;for(let m of O.toolCalls??[]){let a=O.toolResults?.find((V0)=>V0.toolCallId===m.toolCallId);e.set(m.toolCallId,{id:m.toolCallId,name:m.toolName,args:m.args??{},result:a?.output,status:"completed"})}let O0={id:`msg_${Date.now()}_a`,conversationId:u??"",role:"assistant",content:O.text,status:"completed",createdAt:new Date,updatedAt:new Date,toolCalls:e.size?Array.from(e.values()):void 0,usage:O.usage};if(y((m)=>[...m,O0]),V?.(O0),F?.(O.usage??{inputTokens:0,outputTokens:0}),H&&!u){let m=await H.create({status:"active",provider:"agent",model:"agent",messages:[]});if(!f?.skipUserAppend)await H.appendMessage(m.id,{role:"user",content:R,status:"completed",attachments:T});await H.appendMessage(m.id,{role:"assistant",content:O.text,status:"completed",toolCalls:O0.toolCalls,usage:O.usage});let a=await H.get(m.id);if(a)M(a);E0(m.id)}}catch(O){N0(O instanceof Error?O:Error(String(O))),_?.(O instanceof Error?O:Error(String(O)))}finally{D(!1)}return}if(!o.current)throw Error("Chat service not initialized");D(!0),N0(null),y0.current=new AbortController;try{if(!f?.skipUserAppend){let O={id:`msg_${Date.now()}`,conversationId:u??"",role:"user",content:R,status:"completed",createdAt:new Date,updatedAt:new Date,attachments:T};y((e)=>[...e,O]),G?.(O)}if(W){let O=await o.current.stream({conversationId:u??void 0,content:R,attachments:T,skipUserAppend:f?.skipUserAppend});if(!u&&!f?.skipUserAppend)E0(O.conversationId);let e={id:O.messageId,conversationId:O.conversationId,role:"assistant",content:"",status:"streaming",createdAt:new Date,updatedAt:new Date};y((h)=>[...h,e]);let O0="",m="",a=new Map,V0=[];for await(let h of O.stream)if(h.type==="text"&&h.content)O0+=h.content,y((j)=>j.map((S)=>S.id===O.messageId?{...S,content:O0,reasoning:m||void 0,sources:V0.length?V0:void 0,toolCalls:a.size?Array.from(a.values()):void 0}:S));else if(h.type==="reasoning"&&h.content)m+=h.content,y((j)=>j.map((S)=>S.id===O.messageId?{...S,reasoning:m}:S));else if(h.type==="source"&&h.source)V0.push(h.source),y((j)=>j.map((S)=>S.id===O.messageId?{...S,sources:[...V0]}:S));else if(h.type==="tool_call"&&h.toolCall){let j=h.toolCall,S={id:j.id,name:j.name,args:j.args,status:"running"};a.set(j.id,S),y((i0)=>i0.map((B0)=>B0.id===O.messageId?{...B0,toolCalls:Array.from(a.values())}:B0))}else if(h.type==="tool_result"&&h.toolResult){let j=h.toolResult,S=a.get(j.toolCallId);if(S)S.result=j.result,S.status="completed";y((i0)=>i0.map((B0)=>B0.id===O.messageId?{...B0,toolCalls:Array.from(a.values())}:B0))}else if(h.type==="done")y((j)=>j.map((S)=>S.id===O.messageId?{...S,content:O0,reasoning:m||void 0,sources:V0.length?V0:void 0,toolCalls:a.size?Array.from(a.values()):void 0,status:"completed",usage:h.usage,updatedAt:new Date}:S)),V?.(C.find((j)=>j.id===O.messageId)??e);else if(h.type==="error"){if(y((j)=>j.map((S)=>S.id===O.messageId?{...S,status:"error",error:h.error,updatedAt:new Date}:S)),h.error){let j=Error(h.error.message);N0(j),_?.(j)}}}else{let O=await o.current.send({conversationId:u??void 0,content:R,attachments:T,skipUserAppend:f?.skipUserAppend});if(M(O.conversation),y(O.conversation.messages),!u)E0(O.conversation.id);V?.(O.message)}}catch(O){let e=O instanceof Error?O:Error(String(O));N0(e),_?.(e)}finally{D(!1),y0.current=null}},[u,W,G,V,_,F,C,L,H]),M1=I.useCallback(()=>{y([]),M(null),E0(null),N0(null)},[]),k2=I.useCallback(async()=>{let R=-1;for(let f=C.length-1;f>=0;f--)if(C[f]?.role==="user"){R=f;break}if(R===-1)return;let T=C[R];if(!T)return;y((f)=>f.slice(0,R+1)),await R0(T.content,T.attachments)},[C,R0]),M2=I.useCallback(()=>{y0.current?.abort(),D(!1)},[]),w2=M1,S2=I.useCallback(async(R,T)=>{if(!o.current||!u)return;let f=C.find((e)=>e.id===R);if(!f||f.role!=="user")return;await o.current.updateMessage(u,R,{content:T});let O=await o.current.truncateAfter(u,R);if(O)y(O.messages);await R0(T,void 0,{skipUserAppend:!0})},[u,C,R0]),I2=I.useCallback(async(R)=>{if(!o.current)return null;let T=u??J0?.id;if(!T)return null;try{let f=await o.current.forkConversation(T,R);return E0(f.id),M(f),y(f.messages),f.id}catch{return null}},[u,J0]),C2=I.useCallback(async(R)=>{if(!o.current||!u)return null;let T=await o.current.updateConversation(u,R);if(T)M(T);return T},[u]),f2=I.useCallback((R,T)=>{throw Error("addToolApprovalResponse: Tool approval requires server route with toUIMessageStreamResponse. Use createChatRoute and @ai-sdk/react useChat for tools with requireApproval.")},[]),h2=E?.some((R)=>R.requireApproval)??!1;return{messages:C,conversation:J0,isLoading:r,error:s,sendMessage:R0,clearConversation:M1,setConversationId:E0,regenerate:k2,stop:M2,createNewConversation:w2,editMessage:S2,forkConversation:I2,updateConversation:C2,...h2&&{addToolApprovalResponse:f2}}}import{jsx as c0,jsxs as p5}from"react/jsx-runtime";var v5=V2();function u5({store:$=v5,projectId:Z,tags:Y,className:J,thinkingLevel:Q="thinking",presentationRenderer:X,formRenderer:q,dataViewRenderer:H,components:K,suggestions:W,suggestionComponents:G,showSuggestionsWhenEmpty:V=!1,..._}){let F=$,[E,A]=I0.useState(Q),P=P2({..._,store:F,thinkingLevel:E}),{messages:z,conversation:B,sendMessage:x,isLoading:k,setConversationId:L,createNewConversation:C,editMessage:y,forkConversation:i,updateConversation:v}=P,g=B?.id??null,J0=I0.useCallback((r)=>{L(r)},[L]),M=I0.useCallback((r)=>{x(r)},[x]);return p5("div",{className:J??"flex h-full w-full",children:[c0(V1,{store:F,selectedConversationId:g,onSelectConversation:J0,onCreateNew:C,projectId:Z,tags:Y,selectedConversation:B,onUpdateConversation:v?async(r,D)=>{if(r===g)await v(D)}:void 0}),c0("div",{className:"flex min-w-0 flex-1 flex-col",children:c0(F1,{messages:z,conversation:B,onCreateNew:C,onFork:i,onEditMessage:y,thinkingLevel:E,onThinkingLevelChange:A,presentationRenderer:X,formRenderer:q,dataViewRenderer:H,components:K,suggestions:W,onSuggestionClick:M,suggestionComponents:G,showSuggestionsWhenEmpty:V,children:c0(e0,{onSend:(r,D)=>x(r,D),disabled:k,isLoading:k})})})]})}import{Badge as g5}from"@contractspec/lib.ui-kit-web/ui/badge";import{Tooltip as m5,TooltipContent as d5,TooltipProvider as l5,TooltipTrigger as c5}from"@contractspec/lib.ui-kit-web/ui/tooltip";import{cn as L2}from"@contractspec/lib.ui-kit-web/ui/utils";import{FileCode as i5,FolderOpen as r5,Info as a5,Zap as n5}from"lucide-react";import{jsx as p,jsxs as Z0,Fragment as t5}from"react/jsx-runtime";function o5({summary:$,active:Z=!1,className:Y,showDetails:J=!0}){if(!$&&!Z)return Z0("div",{className:L2("flex items-center gap-1.5 text-sm","text-muted-foreground",Y),children:[p(a5,{className:"h-4 w-4"}),p("span",{children:"No workspace context"})]});let Q=Z0("div",{className:L2("flex items-center gap-2",Z?"text-foreground":"text-muted-foreground",Y),children:[Z0(g5,{variant:Z?"default":"secondary",className:"flex items-center gap-1",children:[p(n5,{className:"h-3 w-3"}),"Context"]}),$&&J&&Z0(t5,{children:[Z0("div",{className:"flex items-center gap-1 text-xs",children:[p(r5,{className:"h-3.5 w-3.5"}),p("span",{children:$.name})]}),Z0("div",{className:"flex items-center gap-1 text-xs",children:[p(i5,{className:"h-3.5 w-3.5"}),Z0("span",{children:[$.specs.total," specs"]})]})]})]});if(!$)return Q;return p(l5,{children:Z0(m5,{children:[p(c5,{asChild:!0,children:Q}),p(d5,{side:"bottom",className:"max-w-[300px]",children:Z0("div",{className:"flex flex-col gap-2 text-sm",children:[p("div",{className:"font-medium",children:$.name}),p("div",{className:"text-muted-foreground text-xs",children:$.path}),p("div",{className:"border-t pt-2",children:Z0("div",{className:"grid grid-cols-2 gap-1 text-xs",children:[p("span",{children:"Commands:"}),p("span",{className:"text-right",children:$.specs.commands}),p("span",{children:"Queries:"}),p("span",{className:"text-right",children:$.specs.queries}),p("span",{children:"Events:"}),p("span",{className:"text-right",children:$.specs.events}),p("span",{children:"Presentations:"}),p("span",{className:"text-right",children:$.specs.presentations})]})}),Z0("div",{className:"border-t pt-2 text-xs",children:[Z0("span",{children:[$.files.total," files"]}),p("span",{className:"mx-1",children:"\u2022"}),Z0("span",{children:[$.files.specFiles," spec files"]})]})]})})]})})}import{getModelsForProvider as N2}from"@contractspec/lib.ai-providers";import{Button as s5}from"@contractspec/lib.design-system";import{Badge as L1}from"@contractspec/lib.ui-kit-web/ui/badge";import{Label as E2}from"@contractspec/lib.ui-kit-web/ui/label";import{Select as N1,SelectContent as E1,SelectItem as y1,SelectTrigger as R1,SelectValue as T1}from"@contractspec/lib.ui-kit-web/ui/select";import{cn as b1}from"@contractspec/lib.ui-kit-web/ui/utils";import{Bot as e5,Cloud as $6,Cpu as Z6,Sparkles as y2}from"lucide-react";import*as k1 from"react";import{jsx as b,jsxs as Y0}from"react/jsx-runtime";var R2={ollama:b(Z6,{className:"h-4 w-4"}),openai:b(e5,{className:"h-4 w-4"}),anthropic:b(y2,{className:"h-4 w-4"}),mistral:b($6,{className:"h-4 w-4"}),gemini:b(y2,{className:"h-4 w-4"})},T2={ollama:"Ollama (Local)",openai:"OpenAI",anthropic:"Anthropic",mistral:"Mistral",gemini:"Google Gemini"},b2={local:{label:"Local",variant:"secondary"},byok:{label:"BYOK",variant:"outline"},managed:{label:"Managed",variant:"default"}};function Y6({value:$,onChange:Z,availableProviders:Y,className:J,compact:Q=!1}){let X=Y??[{provider:"ollama",available:!0,mode:"local"},{provider:"openai",available:!0,mode:"byok"},{provider:"anthropic",available:!0,mode:"byok"},{provider:"mistral",available:!0,mode:"byok"},{provider:"gemini",available:!0,mode:"byok"}],q=N2($.provider),H=q.find((G)=>G.id===$.model),K=k1.useCallback((G)=>{let V=G,_=X.find((A)=>A.provider===V),E=N2(V)[0]?.id??"";Z({provider:V,model:E,mode:_?.mode??"byok"})},[Z,X]),W=k1.useCallback((G)=>{Z({...$,model:G})},[Z,$]);if(Q)return Y0("div",{className:b1("flex items-center gap-2",J),children:[Y0(N1,{value:$.provider,onValueChange:K,children:[b(R1,{className:"w-[140px]",children:b(T1,{})}),b(E1,{children:X.map((G)=>b(y1,{value:G.provider,disabled:!G.available,children:Y0("div",{className:"flex items-center gap-2",children:[R2[G.provider],b("span",{children:T2[G.provider]})]})},G.provider))})]}),Y0(N1,{value:$.model,onValueChange:W,children:[b(R1,{className:"w-[160px]",children:b(T1,{})}),b(E1,{children:q.map((G)=>b(y1,{value:G.id,children:G.name},G.id))})]})]});return Y0("div",{className:b1("flex flex-col gap-3",J),children:[Y0("div",{className:"flex flex-col gap-1.5",children:[b(E2,{htmlFor:"provider-selection",className:"font-medium text-sm",children:"Provider"}),b("div",{className:"flex flex-wrap gap-2",id:"provider-selection",children:X.map((G)=>Y0(s5,{variant:$.provider===G.provider?"default":"outline",size:"sm",onPress:()=>G.available&&K(G.provider),disabled:!G.available,className:b1(!G.available&&"opacity-50"),children:[R2[G.provider],b("span",{children:T2[G.provider]}),b(L1,{variant:b2[G.mode].variant,className:"ml-1",children:b2[G.mode].label})]},G.provider))})]}),Y0("div",{className:"flex flex-col gap-1.5",children:[b(E2,{htmlFor:"model-picker",className:"font-medium text-sm",children:"Model"}),Y0(N1,{name:"model-picker",value:$.model,onValueChange:W,children:[b(R1,{children:b(T1,{placeholder:"Select a model"})}),b(E1,{children:q.map((G)=>b(y1,{value:G.id,children:Y0("div",{className:"flex items-center gap-2",children:[b("span",{children:G.name}),Y0("span",{className:"text-muted-foreground text-xs",children:[Math.round(G.contextWindow/1000),"K"]}),G.capabilities.vision&&b(L1,{variant:"outline",className:"text-xs",children:"Vision"}),G.capabilities.reasoning&&b(L1,{variant:"outline",className:"text-xs",children:"Reasoning"})]})},G.id))})]})]}),H&&Y0("div",{className:"flex flex-wrap gap-2 text-muted-foreground text-xs",children:[Y0("span",{children:["Context: ",Math.round(H.contextWindow/1000),"K tokens"]}),H.capabilities.vision&&b("span",{children:"\u2022 Vision"}),H.capabilities.tools&&b("span",{children:"\u2022 Tools"}),H.capabilities.reasoning&&b("span",{children:"\u2022 Reasoning"})]})]})}export{g1 as isPresentationToolResult,m1 as isFormToolResult,d1 as isDataViewToolResult,x0 as ToolResultRenderer,A1 as ThinkingLevelPicker,z1 as Suggestions,U1 as Suggestion,H1 as SourcesTrigger,G1 as SourcesContent,q1 as Sources,W1 as Source,Q1 as ReasoningTrigger,X1 as ReasoningContent,J1 as Reasoning,Y6 as ModelPicker,o5 as ContextIndicator,Z1 as CodePreview,u5 as ChatWithSidebar,F1 as ChatWithExport,V1 as ChatSidebar,_1 as ChatMessage,e0 as ChatInput,t0 as ChatExportToolbar,a0 as ChatContainer,l2 as ChainOfThoughtStep,d2 as ChainOfThoughtHeader,c2 as ChainOfThoughtContent,m2 as ChainOfThought};
|