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