@contractspec/module.ai-chat 1.56.1 → 1.58.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai-chat.capability.d.ts +2 -0
- package/dist/ai-chat.capability.d.ts.map +1 -0
- package/dist/ai-chat.feature.d.ts +1 -7
- package/dist/ai-chat.feature.d.ts.map +1 -1
- package/dist/ai-chat.operations.d.ts +217 -223
- package/dist/ai-chat.operations.d.ts.map +1 -1
- package/dist/browser/context/index.js +415 -0
- package/dist/browser/core/index.js +336 -0
- package/dist/browser/index.js +2291 -0
- package/dist/browser/presentation/components/index.js +974 -0
- package/dist/browser/presentation/hooks/index.js +556 -0
- package/dist/browser/presentation/index.js +1520 -0
- package/dist/browser/providers/index.js +51 -0
- package/dist/context/chat.test.d.ts +2 -0
- package/dist/context/chat.test.d.ts.map +1 -0
- package/dist/context/context-builder.d.ts +37 -37
- package/dist/context/context-builder.d.ts.map +1 -1
- package/dist/context/file-operations.d.ts +64 -67
- package/dist/context/file-operations.d.ts.map +1 -1
- package/dist/context/index.d.ts +7 -4
- package/dist/context/index.d.ts.map +1 -0
- package/dist/context/index.js +409 -4
- package/dist/context/workspace-context.d.ts +84 -87
- package/dist/context/workspace-context.d.ts.map +1 -1
- package/dist/core/chat-service.d.ts +56 -61
- package/dist/core/chat-service.d.ts.map +1 -1
- package/dist/core/conversation-store.d.ts +60 -62
- package/dist/core/conversation-store.d.ts.map +1 -1
- package/dist/core/index.d.ts +7 -4
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +330 -3
- package/dist/core/message-types.d.ts +94 -97
- package/dist/core/message-types.d.ts.map +1 -1
- package/dist/docs/ai-chat.docblock.d.ts +2 -0
- package/dist/docs/ai-chat.docblock.d.ts.map +1 -0
- package/dist/docs/index.d.ts +7 -0
- package/dist/docs/index.d.ts.map +1 -0
- package/dist/events.d.ts +103 -109
- package/dist/events.d.ts.map +1 -1
- package/dist/index.d.ts +16 -21
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2286 -23
- package/dist/node/context/index.js +410 -0
- package/dist/node/core/index.js +331 -0
- package/dist/node/index.js +2286 -0
- package/dist/node/presentation/components/index.js +969 -0
- package/dist/node/presentation/hooks/index.js +551 -0
- package/dist/node/presentation/index.js +1515 -0
- package/dist/node/providers/index.js +46 -0
- package/dist/presentation/components/ChatContainer.d.ts +7 -16
- package/dist/presentation/components/ChatContainer.d.ts.map +1 -1
- package/dist/presentation/components/ChatInput.d.ts +17 -30
- package/dist/presentation/components/ChatInput.d.ts.map +1 -1
- package/dist/presentation/components/ChatMessage.d.ts +9 -19
- package/dist/presentation/components/ChatMessage.d.ts.map +1 -1
- package/dist/presentation/components/CodePreview.d.ts +20 -35
- package/dist/presentation/components/CodePreview.d.ts.map +1 -1
- package/dist/presentation/components/ContextIndicator.d.ts +11 -21
- package/dist/presentation/components/ContextIndicator.d.ts.map +1 -1
- package/dist/presentation/components/ModelPicker.d.ts +21 -32
- package/dist/presentation/components/ModelPicker.d.ts.map +1 -1
- package/dist/presentation/components/index.d.ts +10 -7
- package/dist/presentation/components/index.d.ts.map +1 -0
- package/dist/presentation/components/index.js +968 -7
- package/dist/presentation/hooks/index.d.ts +6 -3
- package/dist/presentation/hooks/index.d.ts.map +1 -0
- package/dist/presentation/hooks/index.js +550 -3
- package/dist/presentation/hooks/use-chat.test.d.ts +2 -0
- package/dist/presentation/hooks/use-chat.test.d.ts.map +1 -0
- package/dist/presentation/hooks/useChat.d.ts +50 -55
- package/dist/presentation/hooks/useChat.d.ts.map +1 -1
- package/dist/presentation/hooks/useProviders.d.ts +21 -26
- package/dist/presentation/hooks/useProviders.d.ts.map +1 -1
- package/dist/presentation/index.d.ts +8 -11
- package/dist/presentation/index.d.ts.map +1 -0
- package/dist/presentation/index.js +1515 -12
- package/dist/providers/chat-utilities.d.ts +18 -8
- package/dist/providers/chat-utilities.d.ts.map +1 -1
- package/dist/providers/index.d.ts +8 -3
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +45 -3
- package/dist/schema.d.ts +195 -200
- package/dist/schema.d.ts.map +1 -1
- package/package.json +126 -37
- package/dist/ai-chat.feature.js +0 -102
- package/dist/ai-chat.feature.js.map +0 -1
- package/dist/ai-chat.operations.js +0 -172
- package/dist/ai-chat.operations.js.map +0 -1
- package/dist/context/context-builder.js +0 -148
- package/dist/context/context-builder.js.map +0 -1
- package/dist/context/file-operations.js +0 -175
- package/dist/context/file-operations.js.map +0 -1
- package/dist/context/workspace-context.js +0 -124
- package/dist/context/workspace-context.js.map +0 -1
- package/dist/core/chat-service.js +0 -227
- package/dist/core/chat-service.js.map +0 -1
- package/dist/core/conversation-store.js +0 -109
- package/dist/core/conversation-store.js.map +0 -1
- package/dist/events.js +0 -98
- package/dist/events.js.map +0 -1
- package/dist/presentation/components/ChatContainer.js +0 -63
- package/dist/presentation/components/ChatContainer.js.map +0 -1
- package/dist/presentation/components/ChatInput.js +0 -149
- package/dist/presentation/components/ChatInput.js.map +0 -1
- package/dist/presentation/components/ChatMessage.js +0 -136
- package/dist/presentation/components/ChatMessage.js.map +0 -1
- package/dist/presentation/components/CodePreview.js +0 -127
- package/dist/presentation/components/CodePreview.js.map +0 -1
- package/dist/presentation/components/ContextIndicator.js +0 -97
- package/dist/presentation/components/ContextIndicator.js.map +0 -1
- package/dist/presentation/components/ModelPicker.js +0 -202
- package/dist/presentation/components/ModelPicker.js.map +0 -1
- package/dist/presentation/hooks/useChat.js +0 -172
- package/dist/presentation/hooks/useChat.js.map +0 -1
- package/dist/presentation/hooks/useProviders.js +0 -41
- package/dist/presentation/hooks/useProviders.js.map +0 -1
- package/dist/providers/chat-utilities.js +0 -17
- package/dist/providers/chat-utilities.js.map +0 -1
- package/dist/schema.js +0 -100
- package/dist/schema.js.map +0 -1
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import * as React from "react";
|
|
4
|
-
import { cn } from "@contractspec/lib.ui-kit-web/ui/utils";
|
|
5
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
-
import { Check, Copy, Download, Play } from "lucide-react";
|
|
7
|
-
import { Button } from "@contractspec/lib.design-system";
|
|
8
|
-
|
|
9
|
-
//#region src/presentation/components/CodePreview.tsx
|
|
10
|
-
/**
|
|
11
|
-
* Language display names
|
|
12
|
-
*/
|
|
13
|
-
const LANGUAGE_NAMES = {
|
|
14
|
-
ts: "TypeScript",
|
|
15
|
-
tsx: "TypeScript (React)",
|
|
16
|
-
typescript: "TypeScript",
|
|
17
|
-
js: "JavaScript",
|
|
18
|
-
jsx: "JavaScript (React)",
|
|
19
|
-
javascript: "JavaScript",
|
|
20
|
-
json: "JSON",
|
|
21
|
-
md: "Markdown",
|
|
22
|
-
yaml: "YAML",
|
|
23
|
-
yml: "YAML",
|
|
24
|
-
bash: "Bash",
|
|
25
|
-
sh: "Shell",
|
|
26
|
-
sql: "SQL",
|
|
27
|
-
py: "Python",
|
|
28
|
-
python: "Python",
|
|
29
|
-
go: "Go",
|
|
30
|
-
rust: "Rust",
|
|
31
|
-
rs: "Rust"
|
|
32
|
-
};
|
|
33
|
-
/**
|
|
34
|
-
* Code preview component with syntax highlighting placeholder
|
|
35
|
-
*/
|
|
36
|
-
function CodePreview({ code, language = "text", filename, className, showCopy = true, showExecute = false, onExecute, showDownload = false, maxHeight = 400 }) {
|
|
37
|
-
const [copied, setCopied] = React.useState(false);
|
|
38
|
-
const displayLanguage = LANGUAGE_NAMES[language.toLowerCase()] ?? language;
|
|
39
|
-
const lines = code.split("\n");
|
|
40
|
-
const handleCopy = React.useCallback(async () => {
|
|
41
|
-
await navigator.clipboard.writeText(code);
|
|
42
|
-
setCopied(true);
|
|
43
|
-
setTimeout(() => setCopied(false), 2e3);
|
|
44
|
-
}, [code]);
|
|
45
|
-
const handleDownload = React.useCallback(() => {
|
|
46
|
-
const blob = new Blob([code], { type: "text/plain" });
|
|
47
|
-
const url = URL.createObjectURL(blob);
|
|
48
|
-
const a = document.createElement("a");
|
|
49
|
-
a.href = url;
|
|
50
|
-
a.download = filename ?? `code.${language}`;
|
|
51
|
-
document.body.appendChild(a);
|
|
52
|
-
a.click();
|
|
53
|
-
document.body.removeChild(a);
|
|
54
|
-
URL.revokeObjectURL(url);
|
|
55
|
-
}, [
|
|
56
|
-
code,
|
|
57
|
-
filename,
|
|
58
|
-
language
|
|
59
|
-
]);
|
|
60
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
61
|
-
className: cn("overflow-hidden rounded-lg border", "bg-muted/50", className),
|
|
62
|
-
children: [/* @__PURE__ */ jsxs("div", {
|
|
63
|
-
className: cn("flex items-center justify-between px-3 py-1.5", "bg-muted/80 border-b"),
|
|
64
|
-
children: [/* @__PURE__ */ jsxs("div", {
|
|
65
|
-
className: "flex items-center gap-2 text-sm",
|
|
66
|
-
children: [filename && /* @__PURE__ */ jsx("span", {
|
|
67
|
-
className: "text-foreground font-mono",
|
|
68
|
-
children: filename
|
|
69
|
-
}), /* @__PURE__ */ jsx("span", {
|
|
70
|
-
className: "text-muted-foreground",
|
|
71
|
-
children: displayLanguage
|
|
72
|
-
})]
|
|
73
|
-
}), /* @__PURE__ */ jsxs("div", {
|
|
74
|
-
className: "flex items-center gap-1",
|
|
75
|
-
children: [
|
|
76
|
-
showExecute && onExecute && /* @__PURE__ */ jsx(Button, {
|
|
77
|
-
variant: "ghost",
|
|
78
|
-
size: "sm",
|
|
79
|
-
onPress: () => onExecute(code),
|
|
80
|
-
className: "h-7 w-7 p-0",
|
|
81
|
-
"aria-label": "Execute code",
|
|
82
|
-
children: /* @__PURE__ */ jsx(Play, { className: "h-3.5 w-3.5" })
|
|
83
|
-
}),
|
|
84
|
-
showDownload && /* @__PURE__ */ jsx(Button, {
|
|
85
|
-
variant: "ghost",
|
|
86
|
-
size: "sm",
|
|
87
|
-
onPress: handleDownload,
|
|
88
|
-
className: "h-7 w-7 p-0",
|
|
89
|
-
"aria-label": "Download code",
|
|
90
|
-
children: /* @__PURE__ */ jsx(Download, { className: "h-3.5 w-3.5" })
|
|
91
|
-
}),
|
|
92
|
-
showCopy && /* @__PURE__ */ jsx(Button, {
|
|
93
|
-
variant: "ghost",
|
|
94
|
-
size: "sm",
|
|
95
|
-
onPress: handleCopy,
|
|
96
|
-
className: "h-7 w-7 p-0",
|
|
97
|
-
"aria-label": copied ? "Copied" : "Copy code",
|
|
98
|
-
children: copied ? /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5 text-green-500" }) : /* @__PURE__ */ jsx(Copy, { className: "h-3.5 w-3.5" })
|
|
99
|
-
})
|
|
100
|
-
]
|
|
101
|
-
})]
|
|
102
|
-
}), /* @__PURE__ */ jsx("div", {
|
|
103
|
-
className: "overflow-auto",
|
|
104
|
-
style: { maxHeight },
|
|
105
|
-
children: /* @__PURE__ */ jsx("pre", {
|
|
106
|
-
className: "p-3",
|
|
107
|
-
children: /* @__PURE__ */ jsx("code", {
|
|
108
|
-
className: "text-sm",
|
|
109
|
-
children: lines.map((line, i) => /* @__PURE__ */ jsxs("div", {
|
|
110
|
-
className: "flex",
|
|
111
|
-
children: [/* @__PURE__ */ jsx("span", {
|
|
112
|
-
className: "text-muted-foreground mr-4 w-8 text-right select-none",
|
|
113
|
-
children: i + 1
|
|
114
|
-
}), /* @__PURE__ */ jsx("span", {
|
|
115
|
-
className: "flex-1",
|
|
116
|
-
children: line || " "
|
|
117
|
-
})]
|
|
118
|
-
}, i))
|
|
119
|
-
})
|
|
120
|
-
})
|
|
121
|
-
})]
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
//#endregion
|
|
126
|
-
export { CodePreview };
|
|
127
|
-
//# sourceMappingURL=CodePreview.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"CodePreview.js","names":[],"sources":["../../../src/presentation/components/CodePreview.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport { cn } from '@contractspec/lib.ui-kit-web/ui/utils';\nimport { Button } from '@contractspec/lib.design-system';\nimport { Copy, Check, Play, Download } from 'lucide-react';\n\nexport interface CodePreviewProps {\n /** Code content */\n code: string;\n /** Programming language */\n language?: string;\n /** File name */\n filename?: string;\n /** Additional class name */\n className?: string;\n /** Show copy button */\n showCopy?: boolean;\n /** Show execute button (for applicable languages) */\n showExecute?: boolean;\n /** Called when execute is clicked */\n onExecute?: (code: string) => void;\n /** Show download button */\n showDownload?: boolean;\n /** Max height before scroll */\n maxHeight?: number;\n}\n\n/**\n * Language display names\n */\nconst LANGUAGE_NAMES: Record<string, string> = {\n ts: 'TypeScript',\n tsx: 'TypeScript (React)',\n typescript: 'TypeScript',\n js: 'JavaScript',\n jsx: 'JavaScript (React)',\n javascript: 'JavaScript',\n json: 'JSON',\n md: 'Markdown',\n yaml: 'YAML',\n yml: 'YAML',\n bash: 'Bash',\n sh: 'Shell',\n sql: 'SQL',\n py: 'Python',\n python: 'Python',\n go: 'Go',\n rust: 'Rust',\n rs: 'Rust',\n};\n\n/**\n * Code preview component with syntax highlighting placeholder\n */\nexport function CodePreview({\n code,\n language = 'text',\n filename,\n className,\n showCopy = true,\n showExecute = false,\n onExecute,\n showDownload = false,\n maxHeight = 400,\n}: CodePreviewProps) {\n const [copied, setCopied] = React.useState(false);\n\n const displayLanguage = LANGUAGE_NAMES[language.toLowerCase()] ?? language;\n const lines = code.split('\\n');\n\n const handleCopy = React.useCallback(async () => {\n await navigator.clipboard.writeText(code);\n setCopied(true);\n setTimeout(() => setCopied(false), 2000);\n }, [code]);\n\n const handleDownload = React.useCallback(() => {\n const blob = new Blob([code], { type: 'text/plain' });\n const url = URL.createObjectURL(blob);\n const a = document.createElement('a');\n a.href = url;\n a.download = filename ?? `code.${language}`;\n document.body.appendChild(a);\n a.click();\n document.body.removeChild(a);\n URL.revokeObjectURL(url);\n }, [code, filename, language]);\n\n return (\n <div\n className={cn(\n 'overflow-hidden rounded-lg border',\n 'bg-muted/50',\n className\n )}\n >\n {/* Header */}\n <div\n className={cn(\n 'flex items-center justify-between px-3 py-1.5',\n 'bg-muted/80 border-b'\n )}\n >\n <div className=\"flex items-center gap-2 text-sm\">\n {filename && (\n <span className=\"text-foreground font-mono\">{filename}</span>\n )}\n <span className=\"text-muted-foreground\">{displayLanguage}</span>\n </div>\n\n <div className=\"flex items-center gap-1\">\n {showExecute && onExecute && (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onPress={() => onExecute(code)}\n className=\"h-7 w-7 p-0\"\n aria-label=\"Execute code\"\n >\n <Play className=\"h-3.5 w-3.5\" />\n </Button>\n )}\n\n {showDownload && (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onPress={handleDownload}\n className=\"h-7 w-7 p-0\"\n aria-label=\"Download code\"\n >\n <Download className=\"h-3.5 w-3.5\" />\n </Button>\n )}\n\n {showCopy && (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onPress={handleCopy}\n className=\"h-7 w-7 p-0\"\n aria-label={copied ? 'Copied' : 'Copy code'}\n >\n {copied ? (\n <Check className=\"h-3.5 w-3.5 text-green-500\" />\n ) : (\n <Copy className=\"h-3.5 w-3.5\" />\n )}\n </Button>\n )}\n </div>\n </div>\n\n {/* Code content */}\n <div className=\"overflow-auto\" style={{ maxHeight }}>\n <pre className=\"p-3\">\n <code className=\"text-sm\">\n {lines.map((line, i) => (\n <div key={i} className=\"flex\">\n <span className=\"text-muted-foreground mr-4 w-8 text-right select-none\">\n {i + 1}\n </span>\n <span className=\"flex-1\">{line || ' '}</span>\n </div>\n ))}\n </code>\n </pre>\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;AA+BA,MAAM,iBAAyC;CAC7C,IAAI;CACJ,KAAK;CACL,YAAY;CACZ,IAAI;CACJ,KAAK;CACL,YAAY;CACZ,MAAM;CACN,IAAI;CACJ,MAAM;CACN,KAAK;CACL,MAAM;CACN,IAAI;CACJ,KAAK;CACL,IAAI;CACJ,QAAQ;CACR,IAAI;CACJ,MAAM;CACN,IAAI;CACL;;;;AAKD,SAAgB,YAAY,EAC1B,MACA,WAAW,QACX,UACA,WACA,WAAW,MACX,cAAc,OACd,WACA,eAAe,OACf,YAAY,OACO;CACnB,MAAM,CAAC,QAAQ,aAAa,MAAM,SAAS,MAAM;CAEjD,MAAM,kBAAkB,eAAe,SAAS,aAAa,KAAK;CAClE,MAAM,QAAQ,KAAK,MAAM,KAAK;CAE9B,MAAM,aAAa,MAAM,YAAY,YAAY;AAC/C,QAAM,UAAU,UAAU,UAAU,KAAK;AACzC,YAAU,KAAK;AACf,mBAAiB,UAAU,MAAM,EAAE,IAAK;IACvC,CAAC,KAAK,CAAC;CAEV,MAAM,iBAAiB,MAAM,kBAAkB;EAC7C,MAAM,OAAO,IAAI,KAAK,CAAC,KAAK,EAAE,EAAE,MAAM,cAAc,CAAC;EACrD,MAAM,MAAM,IAAI,gBAAgB,KAAK;EACrC,MAAM,IAAI,SAAS,cAAc,IAAI;AACrC,IAAE,OAAO;AACT,IAAE,WAAW,YAAY,QAAQ;AACjC,WAAS,KAAK,YAAY,EAAE;AAC5B,IAAE,OAAO;AACT,WAAS,KAAK,YAAY,EAAE;AAC5B,MAAI,gBAAgB,IAAI;IACvB;EAAC;EAAM;EAAU;EAAS,CAAC;AAE9B,QACE,qBAAC;EACC,WAAW,GACT,qCACA,eACA,UACD;aAGD,qBAAC;GACC,WAAW,GACT,iDACA,uBACD;cAED,qBAAC;IAAI,WAAU;eACZ,YACC,oBAAC;KAAK,WAAU;eAA6B;MAAgB,EAE/D,oBAAC;KAAK,WAAU;eAAyB;MAAuB;KAC5D,EAEN,qBAAC;IAAI,WAAU;;KACZ,eAAe,aACd,oBAAC;MACC,SAAQ;MACR,MAAK;MACL,eAAe,UAAU,KAAK;MAC9B,WAAU;MACV,cAAW;gBAEX,oBAAC,QAAK,WAAU,gBAAgB;OACzB;KAGV,gBACC,oBAAC;MACC,SAAQ;MACR,MAAK;MACL,SAAS;MACT,WAAU;MACV,cAAW;gBAEX,oBAAC,YAAS,WAAU,gBAAgB;OAC7B;KAGV,YACC,oBAAC;MACC,SAAQ;MACR,MAAK;MACL,SAAS;MACT,WAAU;MACV,cAAY,SAAS,WAAW;gBAE/B,SACC,oBAAC,SAAM,WAAU,+BAA+B,GAEhD,oBAAC,QAAK,WAAU,gBAAgB;OAE3B;;KAEP;IACF,EAGN,oBAAC;GAAI,WAAU;GAAgB,OAAO,EAAE,WAAW;aACjD,oBAAC;IAAI,WAAU;cACb,oBAAC;KAAK,WAAU;eACb,MAAM,KAAK,MAAM,MAChB,qBAAC;MAAY,WAAU;iBACrB,oBAAC;OAAK,WAAU;iBACb,IAAI;QACA,EACP,oBAAC;OAAK,WAAU;iBAAU,QAAQ;QAAW;QAJrC,EAKJ,CACN;MACG;KACH;IACF;GACF"}
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import "react";
|
|
4
|
-
import { cn } from "@contractspec/lib.ui-kit-web/ui/utils";
|
|
5
|
-
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
-
import { FileCode, FolderOpen, Info, Zap } from "lucide-react";
|
|
7
|
-
import { Badge } from "@contractspec/lib.ui-kit-web/ui/badge";
|
|
8
|
-
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@contractspec/lib.ui-kit-web/ui/tooltip";
|
|
9
|
-
|
|
10
|
-
//#region src/presentation/components/ContextIndicator.tsx
|
|
11
|
-
/**
|
|
12
|
-
* Indicator showing active workspace context
|
|
13
|
-
*/
|
|
14
|
-
function ContextIndicator({ summary, active = false, className, showDetails = true }) {
|
|
15
|
-
if (!summary && !active) return /* @__PURE__ */ jsxs("div", {
|
|
16
|
-
className: cn("flex items-center gap-1.5 text-sm", "text-muted-foreground", className),
|
|
17
|
-
children: [/* @__PURE__ */ jsx(Info, { className: "h-4 w-4" }), /* @__PURE__ */ jsx("span", { children: "No workspace context" })]
|
|
18
|
-
});
|
|
19
|
-
const content = /* @__PURE__ */ jsxs("div", {
|
|
20
|
-
className: cn("flex items-center gap-2", active ? "text-foreground" : "text-muted-foreground", className),
|
|
21
|
-
children: [/* @__PURE__ */ jsxs(Badge, {
|
|
22
|
-
variant: active ? "default" : "secondary",
|
|
23
|
-
className: "flex items-center gap-1",
|
|
24
|
-
children: [/* @__PURE__ */ jsx(Zap, { className: "h-3 w-3" }), "Context"]
|
|
25
|
-
}), summary && showDetails && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("div", {
|
|
26
|
-
className: "flex items-center gap-1 text-xs",
|
|
27
|
-
children: [/* @__PURE__ */ jsx(FolderOpen, { className: "h-3.5 w-3.5" }), /* @__PURE__ */ jsx("span", { children: summary.name })]
|
|
28
|
-
}), /* @__PURE__ */ jsxs("div", {
|
|
29
|
-
className: "flex items-center gap-1 text-xs",
|
|
30
|
-
children: [/* @__PURE__ */ jsx(FileCode, { className: "h-3.5 w-3.5" }), /* @__PURE__ */ jsxs("span", { children: [summary.specs.total, " specs"] })]
|
|
31
|
-
})] })]
|
|
32
|
-
});
|
|
33
|
-
if (!summary) return content;
|
|
34
|
-
return /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsxs(Tooltip, { children: [/* @__PURE__ */ jsx(TooltipTrigger, {
|
|
35
|
-
asChild: true,
|
|
36
|
-
children: content
|
|
37
|
-
}), /* @__PURE__ */ jsx(TooltipContent, {
|
|
38
|
-
side: "bottom",
|
|
39
|
-
className: "max-w-[300px]",
|
|
40
|
-
children: /* @__PURE__ */ jsxs("div", {
|
|
41
|
-
className: "flex flex-col gap-2 text-sm",
|
|
42
|
-
children: [
|
|
43
|
-
/* @__PURE__ */ jsx("div", {
|
|
44
|
-
className: "font-medium",
|
|
45
|
-
children: summary.name
|
|
46
|
-
}),
|
|
47
|
-
/* @__PURE__ */ jsx("div", {
|
|
48
|
-
className: "text-muted-foreground text-xs",
|
|
49
|
-
children: summary.path
|
|
50
|
-
}),
|
|
51
|
-
/* @__PURE__ */ jsx("div", {
|
|
52
|
-
className: "border-t pt-2",
|
|
53
|
-
children: /* @__PURE__ */ jsxs("div", {
|
|
54
|
-
className: "grid grid-cols-2 gap-1 text-xs",
|
|
55
|
-
children: [
|
|
56
|
-
/* @__PURE__ */ jsx("span", { children: "Commands:" }),
|
|
57
|
-
/* @__PURE__ */ jsx("span", {
|
|
58
|
-
className: "text-right",
|
|
59
|
-
children: summary.specs.commands
|
|
60
|
-
}),
|
|
61
|
-
/* @__PURE__ */ jsx("span", { children: "Queries:" }),
|
|
62
|
-
/* @__PURE__ */ jsx("span", {
|
|
63
|
-
className: "text-right",
|
|
64
|
-
children: summary.specs.queries
|
|
65
|
-
}),
|
|
66
|
-
/* @__PURE__ */ jsx("span", { children: "Events:" }),
|
|
67
|
-
/* @__PURE__ */ jsx("span", {
|
|
68
|
-
className: "text-right",
|
|
69
|
-
children: summary.specs.events
|
|
70
|
-
}),
|
|
71
|
-
/* @__PURE__ */ jsx("span", { children: "Presentations:" }),
|
|
72
|
-
/* @__PURE__ */ jsx("span", {
|
|
73
|
-
className: "text-right",
|
|
74
|
-
children: summary.specs.presentations
|
|
75
|
-
})
|
|
76
|
-
]
|
|
77
|
-
})
|
|
78
|
-
}),
|
|
79
|
-
/* @__PURE__ */ jsxs("div", {
|
|
80
|
-
className: "border-t pt-2 text-xs",
|
|
81
|
-
children: [
|
|
82
|
-
/* @__PURE__ */ jsxs("span", { children: [summary.files.total, " files"] }),
|
|
83
|
-
/* @__PURE__ */ jsx("span", {
|
|
84
|
-
className: "mx-1",
|
|
85
|
-
children: "•"
|
|
86
|
-
}),
|
|
87
|
-
/* @__PURE__ */ jsxs("span", { children: [summary.files.specFiles, " spec files"] })
|
|
88
|
-
]
|
|
89
|
-
})
|
|
90
|
-
]
|
|
91
|
-
})
|
|
92
|
-
})] }) });
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
//#endregion
|
|
96
|
-
export { ContextIndicator };
|
|
97
|
-
//# sourceMappingURL=ContextIndicator.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ContextIndicator.js","names":[],"sources":["../../../src/presentation/components/ContextIndicator.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport { cn } from '@contractspec/lib.ui-kit-web/ui/utils';\nimport { Badge } from '@contractspec/lib.ui-kit-web/ui/badge';\nimport {\n Tooltip,\n TooltipContent,\n TooltipProvider,\n TooltipTrigger,\n} from '@contractspec/lib.ui-kit-web/ui/tooltip';\nimport { FolderOpen, FileCode, Zap, Info } from 'lucide-react';\nimport type { WorkspaceSummary } from '../../context/workspace-context';\n\nexport interface ContextIndicatorProps {\n /** Workspace summary */\n summary?: WorkspaceSummary;\n /** Whether context is active */\n active?: boolean;\n /** Additional class name */\n className?: string;\n /** Show details */\n showDetails?: boolean;\n}\n\n/**\n * Indicator showing active workspace context\n */\nexport function ContextIndicator({\n summary,\n active = false,\n className,\n showDetails = true,\n}: ContextIndicatorProps) {\n if (!summary && !active) {\n return (\n <div\n className={cn(\n 'flex items-center gap-1.5 text-sm',\n 'text-muted-foreground',\n className\n )}\n >\n <Info className=\"h-4 w-4\" />\n <span>No workspace context</span>\n </div>\n );\n }\n\n const content = (\n <div\n className={cn(\n 'flex items-center gap-2',\n active ? 'text-foreground' : 'text-muted-foreground',\n className\n )}\n >\n <Badge\n variant={active ? 'default' : 'secondary'}\n className=\"flex items-center gap-1\"\n >\n <Zap className=\"h-3 w-3\" />\n Context\n </Badge>\n\n {summary && showDetails && (\n <>\n <div className=\"flex items-center gap-1 text-xs\">\n <FolderOpen className=\"h-3.5 w-3.5\" />\n <span>{summary.name}</span>\n </div>\n\n <div className=\"flex items-center gap-1 text-xs\">\n <FileCode className=\"h-3.5 w-3.5\" />\n <span>{summary.specs.total} specs</span>\n </div>\n </>\n )}\n </div>\n );\n\n if (!summary) {\n return content;\n }\n\n return (\n <TooltipProvider>\n <Tooltip>\n <TooltipTrigger asChild>{content}</TooltipTrigger>\n <TooltipContent side=\"bottom\" className=\"max-w-[300px]\">\n <div className=\"flex flex-col gap-2 text-sm\">\n <div className=\"font-medium\">{summary.name}</div>\n <div className=\"text-muted-foreground text-xs\">{summary.path}</div>\n\n <div className=\"border-t pt-2\">\n <div className=\"grid grid-cols-2 gap-1 text-xs\">\n <span>Commands:</span>\n <span className=\"text-right\">{summary.specs.commands}</span>\n <span>Queries:</span>\n <span className=\"text-right\">{summary.specs.queries}</span>\n <span>Events:</span>\n <span className=\"text-right\">{summary.specs.events}</span>\n <span>Presentations:</span>\n <span className=\"text-right\">\n {summary.specs.presentations}\n </span>\n </div>\n </div>\n\n <div className=\"border-t pt-2 text-xs\">\n <span>{summary.files.total} files</span>\n <span className=\"mx-1\">•</span>\n <span>{summary.files.specFiles} spec files</span>\n </div>\n </div>\n </TooltipContent>\n </Tooltip>\n </TooltipProvider>\n );\n}\n"],"mappings":";;;;;;;;;;;;;AA4BA,SAAgB,iBAAiB,EAC/B,SACA,SAAS,OACT,WACA,cAAc,QACU;AACxB,KAAI,CAAC,WAAW,CAAC,OACf,QACE,qBAAC;EACC,WAAW,GACT,qCACA,yBACA,UACD;aAED,oBAAC,QAAK,WAAU,YAAY,EAC5B,oBAAC,oBAAK,yBAA2B;GAC7B;CAIV,MAAM,UACJ,qBAAC;EACC,WAAW,GACT,2BACA,SAAS,oBAAoB,yBAC7B,UACD;aAED,qBAAC;GACC,SAAS,SAAS,YAAY;GAC9B,WAAU;cAEV,oBAAC,OAAI,WAAU,YAAY;IAErB,EAEP,WAAW,eACV,4CACE,qBAAC;GAAI,WAAU;cACb,oBAAC,cAAW,WAAU,gBAAgB,EACtC,oBAAC,oBAAM,QAAQ,OAAY;IACvB,EAEN,qBAAC;GAAI,WAAU;cACb,oBAAC,YAAS,WAAU,gBAAgB,EACpC,qBAAC,qBAAM,QAAQ,MAAM,OAAM,YAAa;IACpC,IACL;GAED;AAGR,KAAI,CAAC,QACH,QAAO;AAGT,QACE,oBAAC,6BACC,qBAAC,sBACC,oBAAC;EAAe;YAAS;GAAyB,EAClD,oBAAC;EAAe,MAAK;EAAS,WAAU;YACtC,qBAAC;GAAI,WAAU;;IACb,oBAAC;KAAI,WAAU;eAAe,QAAQ;MAAW;IACjD,oBAAC;KAAI,WAAU;eAAiC,QAAQ;MAAW;IAEnE,oBAAC;KAAI,WAAU;eACb,qBAAC;MAAI,WAAU;;OACb,oBAAC,oBAAK,cAAgB;OACtB,oBAAC;QAAK,WAAU;kBAAc,QAAQ,MAAM;SAAgB;OAC5D,oBAAC,oBAAK,aAAe;OACrB,oBAAC;QAAK,WAAU;kBAAc,QAAQ,MAAM;SAAe;OAC3D,oBAAC,oBAAK,YAAc;OACpB,oBAAC;QAAK,WAAU;kBAAc,QAAQ,MAAM;SAAc;OAC1D,oBAAC,oBAAK,mBAAqB;OAC3B,oBAAC;QAAK,WAAU;kBACb,QAAQ,MAAM;SACV;;OACH;MACF;IAEN,qBAAC;KAAI,WAAU;;MACb,qBAAC,qBAAM,QAAQ,MAAM,OAAM,YAAa;MACxC,oBAAC;OAAK,WAAU;iBAAO;QAAQ;MAC/B,qBAAC,qBAAM,QAAQ,MAAM,WAAU,iBAAkB;;MAC7C;;IACF;GACS,IACT,GACM"}
|
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import * as React from "react";
|
|
4
|
-
import { cn } from "@contractspec/lib.ui-kit-web/ui/utils";
|
|
5
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
-
import { Bot, Cloud, Cpu, Sparkles } from "lucide-react";
|
|
7
|
-
import { Button } from "@contractspec/lib.design-system";
|
|
8
|
-
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@contractspec/lib.ui-kit-web/ui/select";
|
|
9
|
-
import { Badge } from "@contractspec/lib.ui-kit-web/ui/badge";
|
|
10
|
-
import { Label } from "@contractspec/lib.ui-kit-web/ui/label";
|
|
11
|
-
import { getModelsForProvider } from "@contractspec/lib.ai-providers";
|
|
12
|
-
|
|
13
|
-
//#region src/presentation/components/ModelPicker.tsx
|
|
14
|
-
const PROVIDER_ICONS = {
|
|
15
|
-
ollama: /* @__PURE__ */ jsx(Cpu, { className: "h-4 w-4" }),
|
|
16
|
-
openai: /* @__PURE__ */ jsx(Bot, { className: "h-4 w-4" }),
|
|
17
|
-
anthropic: /* @__PURE__ */ jsx(Sparkles, { className: "h-4 w-4" }),
|
|
18
|
-
mistral: /* @__PURE__ */ jsx(Cloud, { className: "h-4 w-4" }),
|
|
19
|
-
gemini: /* @__PURE__ */ jsx(Sparkles, { className: "h-4 w-4" })
|
|
20
|
-
};
|
|
21
|
-
const PROVIDER_NAMES = {
|
|
22
|
-
ollama: "Ollama (Local)",
|
|
23
|
-
openai: "OpenAI",
|
|
24
|
-
anthropic: "Anthropic",
|
|
25
|
-
mistral: "Mistral",
|
|
26
|
-
gemini: "Google Gemini"
|
|
27
|
-
};
|
|
28
|
-
const MODE_BADGES = {
|
|
29
|
-
local: {
|
|
30
|
-
label: "Local",
|
|
31
|
-
variant: "secondary"
|
|
32
|
-
},
|
|
33
|
-
byok: {
|
|
34
|
-
label: "BYOK",
|
|
35
|
-
variant: "outline"
|
|
36
|
-
},
|
|
37
|
-
managed: {
|
|
38
|
-
label: "Managed",
|
|
39
|
-
variant: "default"
|
|
40
|
-
}
|
|
41
|
-
};
|
|
42
|
-
/**
|
|
43
|
-
* Model picker component for selecting AI provider and model
|
|
44
|
-
*/
|
|
45
|
-
function ModelPicker({ value, onChange, availableProviders, className, compact = false }) {
|
|
46
|
-
const providers = availableProviders ?? [
|
|
47
|
-
{
|
|
48
|
-
provider: "ollama",
|
|
49
|
-
available: true,
|
|
50
|
-
mode: "local"
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
provider: "openai",
|
|
54
|
-
available: true,
|
|
55
|
-
mode: "byok"
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
provider: "anthropic",
|
|
59
|
-
available: true,
|
|
60
|
-
mode: "byok"
|
|
61
|
-
},
|
|
62
|
-
{
|
|
63
|
-
provider: "mistral",
|
|
64
|
-
available: true,
|
|
65
|
-
mode: "byok"
|
|
66
|
-
},
|
|
67
|
-
{
|
|
68
|
-
provider: "gemini",
|
|
69
|
-
available: true,
|
|
70
|
-
mode: "byok"
|
|
71
|
-
}
|
|
72
|
-
];
|
|
73
|
-
const models = getModelsForProvider(value.provider);
|
|
74
|
-
const selectedModel = models.find((m) => m.id === value.model);
|
|
75
|
-
const handleProviderChange = React.useCallback((providerName) => {
|
|
76
|
-
const provider = providerName;
|
|
77
|
-
const providerInfo = providers.find((p) => p.provider === provider);
|
|
78
|
-
onChange({
|
|
79
|
-
provider,
|
|
80
|
-
model: getModelsForProvider(provider)[0]?.id ?? "",
|
|
81
|
-
mode: providerInfo?.mode ?? "byok"
|
|
82
|
-
});
|
|
83
|
-
}, [onChange, providers]);
|
|
84
|
-
const handleModelChange = React.useCallback((modelId) => {
|
|
85
|
-
onChange({
|
|
86
|
-
...value,
|
|
87
|
-
model: modelId
|
|
88
|
-
});
|
|
89
|
-
}, [onChange, value]);
|
|
90
|
-
if (compact) return /* @__PURE__ */ jsxs("div", {
|
|
91
|
-
className: cn("flex items-center gap-2", className),
|
|
92
|
-
children: [/* @__PURE__ */ jsxs(Select, {
|
|
93
|
-
value: value.provider,
|
|
94
|
-
onValueChange: handleProviderChange,
|
|
95
|
-
children: [/* @__PURE__ */ jsx(SelectTrigger, {
|
|
96
|
-
className: "w-[140px]",
|
|
97
|
-
children: /* @__PURE__ */ jsx(SelectValue, {})
|
|
98
|
-
}), /* @__PURE__ */ jsx(SelectContent, { children: providers.map((p) => /* @__PURE__ */ jsx(SelectItem, {
|
|
99
|
-
value: p.provider,
|
|
100
|
-
disabled: !p.available,
|
|
101
|
-
children: /* @__PURE__ */ jsxs("div", {
|
|
102
|
-
className: "flex items-center gap-2",
|
|
103
|
-
children: [PROVIDER_ICONS[p.provider], /* @__PURE__ */ jsx("span", { children: PROVIDER_NAMES[p.provider] })]
|
|
104
|
-
})
|
|
105
|
-
}, p.provider)) })]
|
|
106
|
-
}), /* @__PURE__ */ jsxs(Select, {
|
|
107
|
-
value: value.model,
|
|
108
|
-
onValueChange: handleModelChange,
|
|
109
|
-
children: [/* @__PURE__ */ jsx(SelectTrigger, {
|
|
110
|
-
className: "w-[160px]",
|
|
111
|
-
children: /* @__PURE__ */ jsx(SelectValue, {})
|
|
112
|
-
}), /* @__PURE__ */ jsx(SelectContent, { children: models.map((m) => /* @__PURE__ */ jsx(SelectItem, {
|
|
113
|
-
value: m.id,
|
|
114
|
-
children: m.name
|
|
115
|
-
}, m.id)) })]
|
|
116
|
-
})]
|
|
117
|
-
});
|
|
118
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
119
|
-
className: cn("flex flex-col gap-3", className),
|
|
120
|
-
children: [
|
|
121
|
-
/* @__PURE__ */ jsxs("div", {
|
|
122
|
-
className: "flex flex-col gap-1.5",
|
|
123
|
-
children: [/* @__PURE__ */ jsx(Label, {
|
|
124
|
-
htmlFor: "provider-selection",
|
|
125
|
-
className: "text-sm font-medium",
|
|
126
|
-
children: "Provider"
|
|
127
|
-
}), /* @__PURE__ */ jsx("div", {
|
|
128
|
-
className: "flex flex-wrap gap-2",
|
|
129
|
-
id: "provider-selection",
|
|
130
|
-
children: providers.map((p) => /* @__PURE__ */ jsxs(Button, {
|
|
131
|
-
variant: value.provider === p.provider ? "default" : "outline",
|
|
132
|
-
size: "sm",
|
|
133
|
-
onPress: () => p.available && handleProviderChange(p.provider),
|
|
134
|
-
disabled: !p.available,
|
|
135
|
-
className: cn(!p.available && "opacity-50"),
|
|
136
|
-
children: [
|
|
137
|
-
PROVIDER_ICONS[p.provider],
|
|
138
|
-
/* @__PURE__ */ jsx("span", { children: PROVIDER_NAMES[p.provider] }),
|
|
139
|
-
/* @__PURE__ */ jsx(Badge, {
|
|
140
|
-
variant: MODE_BADGES[p.mode].variant,
|
|
141
|
-
className: "ml-1",
|
|
142
|
-
children: MODE_BADGES[p.mode].label
|
|
143
|
-
})
|
|
144
|
-
]
|
|
145
|
-
}, p.provider))
|
|
146
|
-
})]
|
|
147
|
-
}),
|
|
148
|
-
/* @__PURE__ */ jsxs("div", {
|
|
149
|
-
className: "flex flex-col gap-1.5",
|
|
150
|
-
children: [/* @__PURE__ */ jsx(Label, {
|
|
151
|
-
htmlFor: "model-picker",
|
|
152
|
-
className: "text-sm font-medium",
|
|
153
|
-
children: "Model"
|
|
154
|
-
}), /* @__PURE__ */ jsxs(Select, {
|
|
155
|
-
name: "model-picker",
|
|
156
|
-
value: value.model,
|
|
157
|
-
onValueChange: handleModelChange,
|
|
158
|
-
children: [/* @__PURE__ */ jsx(SelectTrigger, { children: /* @__PURE__ */ jsx(SelectValue, { placeholder: "Select a model" }) }), /* @__PURE__ */ jsx(SelectContent, { children: models.map((m) => /* @__PURE__ */ jsx(SelectItem, {
|
|
159
|
-
value: m.id,
|
|
160
|
-
children: /* @__PURE__ */ jsxs("div", {
|
|
161
|
-
className: "flex items-center gap-2",
|
|
162
|
-
children: [
|
|
163
|
-
/* @__PURE__ */ jsx("span", { children: m.name }),
|
|
164
|
-
/* @__PURE__ */ jsxs("span", {
|
|
165
|
-
className: "text-muted-foreground text-xs",
|
|
166
|
-
children: [Math.round(m.contextWindow / 1e3), "K"]
|
|
167
|
-
}),
|
|
168
|
-
m.capabilities.vision && /* @__PURE__ */ jsx(Badge, {
|
|
169
|
-
variant: "outline",
|
|
170
|
-
className: "text-xs",
|
|
171
|
-
children: "Vision"
|
|
172
|
-
}),
|
|
173
|
-
m.capabilities.reasoning && /* @__PURE__ */ jsx(Badge, {
|
|
174
|
-
variant: "outline",
|
|
175
|
-
className: "text-xs",
|
|
176
|
-
children: "Reasoning"
|
|
177
|
-
})
|
|
178
|
-
]
|
|
179
|
-
})
|
|
180
|
-
}, m.id)) })]
|
|
181
|
-
})]
|
|
182
|
-
}),
|
|
183
|
-
selectedModel && /* @__PURE__ */ jsxs("div", {
|
|
184
|
-
className: "text-muted-foreground flex flex-wrap gap-2 text-xs",
|
|
185
|
-
children: [
|
|
186
|
-
/* @__PURE__ */ jsxs("span", { children: [
|
|
187
|
-
"Context: ",
|
|
188
|
-
Math.round(selectedModel.contextWindow / 1e3),
|
|
189
|
-
"K tokens"
|
|
190
|
-
] }),
|
|
191
|
-
selectedModel.capabilities.vision && /* @__PURE__ */ jsx("span", { children: "• Vision" }),
|
|
192
|
-
selectedModel.capabilities.tools && /* @__PURE__ */ jsx("span", { children: "• Tools" }),
|
|
193
|
-
selectedModel.capabilities.reasoning && /* @__PURE__ */ jsx("span", { children: "• Reasoning" })
|
|
194
|
-
]
|
|
195
|
-
})
|
|
196
|
-
]
|
|
197
|
-
});
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
//#endregion
|
|
201
|
-
export { ModelPicker };
|
|
202
|
-
//# sourceMappingURL=ModelPicker.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ModelPicker.js","names":[],"sources":["../../../src/presentation/components/ModelPicker.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport { cn } from '@contractspec/lib.ui-kit-web/ui/utils';\nimport { Button } from '@contractspec/lib.design-system';\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@contractspec/lib.ui-kit-web/ui/select';\nimport { Badge } from '@contractspec/lib.ui-kit-web/ui/badge';\nimport { Label } from '@contractspec/lib.ui-kit-web/ui/label';\nimport { Bot, Cloud, Cpu, Sparkles } from 'lucide-react';\nimport {\n getModelsForProvider,\n type ModelInfo,\n type ProviderMode,\n type ProviderName,\n} from '@contractspec/lib.ai-providers';\n\nexport interface ModelSelection {\n provider: ProviderName;\n model: string;\n mode: ProviderMode;\n}\n\nexport interface ModelPickerProps {\n /** Currently selected provider/model */\n value: ModelSelection;\n /** Called when selection changes */\n onChange: (value: ModelSelection) => void;\n /** Available providers (with availability info) */\n availableProviders?: {\n provider: ProviderName;\n available: boolean;\n mode: ProviderMode;\n reason?: string;\n }[];\n /** Additional class name */\n className?: string;\n /** Compact mode (smaller) */\n compact?: boolean;\n}\n\nconst PROVIDER_ICONS: Record<ProviderName, React.ReactNode> = {\n ollama: <Cpu className=\"h-4 w-4\" />,\n openai: <Bot className=\"h-4 w-4\" />,\n anthropic: <Sparkles className=\"h-4 w-4\" />,\n mistral: <Cloud className=\"h-4 w-4\" />,\n gemini: <Sparkles className=\"h-4 w-4\" />,\n};\n\nconst PROVIDER_NAMES: Record<ProviderName, string> = {\n ollama: 'Ollama (Local)',\n openai: 'OpenAI',\n anthropic: 'Anthropic',\n mistral: 'Mistral',\n gemini: 'Google Gemini',\n};\n\nconst MODE_BADGES: Record<\n ProviderMode,\n { label: string; variant: 'default' | 'secondary' | 'outline' }\n> = {\n local: { label: 'Local', variant: 'secondary' },\n byok: { label: 'BYOK', variant: 'outline' },\n managed: { label: 'Managed', variant: 'default' },\n};\n\n/**\n * Model picker component for selecting AI provider and model\n */\nexport function ModelPicker({\n value,\n onChange,\n availableProviders,\n className,\n compact = false,\n}: ModelPickerProps) {\n const providers = availableProviders ?? [\n { provider: 'ollama' as const, available: true, mode: 'local' as const },\n { provider: 'openai' as const, available: true, mode: 'byok' as const },\n { provider: 'anthropic' as const, available: true, mode: 'byok' as const },\n { provider: 'mistral' as const, available: true, mode: 'byok' as const },\n { provider: 'gemini' as const, available: true, mode: 'byok' as const },\n ];\n\n const models: ModelInfo[] = getModelsForProvider(value.provider);\n const selectedModel = models.find((m) => m.id === value.model);\n\n const handleProviderChange = React.useCallback(\n (providerName: string) => {\n const provider = providerName as ProviderName;\n const providerInfo = providers.find((p) => p.provider === provider);\n const providerModels = getModelsForProvider(provider);\n const defaultModel = providerModels[0]?.id ?? '';\n\n onChange({\n provider,\n model: defaultModel,\n mode: providerInfo?.mode ?? 'byok',\n });\n },\n [onChange, providers]\n );\n\n const handleModelChange = React.useCallback(\n (modelId: string) => {\n onChange({\n ...value,\n model: modelId,\n });\n },\n [onChange, value]\n );\n\n if (compact) {\n return (\n <div className={cn('flex items-center gap-2', className)}>\n <Select value={value.provider} onValueChange={handleProviderChange}>\n <SelectTrigger className=\"w-[140px]\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {providers.map((p) => (\n <SelectItem\n key={p.provider}\n value={p.provider}\n disabled={!p.available}\n >\n <div className=\"flex items-center gap-2\">\n {PROVIDER_ICONS[p.provider]}\n <span>{PROVIDER_NAMES[p.provider]}</span>\n </div>\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n\n <Select value={value.model} onValueChange={handleModelChange}>\n <SelectTrigger className=\"w-[160px]\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {models.map((m) => (\n <SelectItem key={m.id} value={m.id}>\n {m.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n );\n }\n\n return (\n <div className={cn('flex flex-col gap-3', className)}>\n {/* Provider selection */}\n <div className=\"flex flex-col gap-1.5\">\n <Label htmlFor=\"provider-selection\" className=\"text-sm font-medium\">\n Provider\n </Label>\n <div className=\"flex flex-wrap gap-2\" id=\"provider-selection\">\n {providers.map((p) => (\n <Button\n key={p.provider}\n variant={value.provider === p.provider ? 'default' : 'outline'}\n size=\"sm\"\n onPress={() => p.available && handleProviderChange(p.provider)}\n disabled={!p.available}\n className={cn(!p.available && 'opacity-50')}\n >\n {PROVIDER_ICONS[p.provider]}\n <span>{PROVIDER_NAMES[p.provider]}</span>\n <Badge variant={MODE_BADGES[p.mode].variant} className=\"ml-1\">\n {MODE_BADGES[p.mode].label}\n </Badge>\n </Button>\n ))}\n </div>\n </div>\n\n {/* Model selection */}\n <div className=\"flex flex-col gap-1.5\">\n <Label htmlFor=\"model-picker\" className=\"text-sm font-medium\">\n Model\n </Label>\n <Select\n name=\"model-picker\"\n value={value.model}\n onValueChange={handleModelChange}\n >\n <SelectTrigger>\n <SelectValue placeholder=\"Select a model\" />\n </SelectTrigger>\n <SelectContent>\n {models.map((m) => (\n <SelectItem key={m.id} value={m.id}>\n <div className=\"flex items-center gap-2\">\n <span>{m.name}</span>\n <span className=\"text-muted-foreground text-xs\">\n {Math.round(m.contextWindow / 1000)}K\n </span>\n {m.capabilities.vision && (\n <Badge variant=\"outline\" className=\"text-xs\">\n Vision\n </Badge>\n )}\n {m.capabilities.reasoning && (\n <Badge variant=\"outline\" className=\"text-xs\">\n Reasoning\n </Badge>\n )}\n </div>\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n {/* Model info */}\n {selectedModel && (\n <div className=\"text-muted-foreground flex flex-wrap gap-2 text-xs\">\n <span>\n Context: {Math.round(selectedModel.contextWindow / 1000)}K tokens\n </span>\n {selectedModel.capabilities.vision && <span>• Vision</span>}\n {selectedModel.capabilities.tools && <span>• Tools</span>}\n {selectedModel.capabilities.reasoning && <span>• Reasoning</span>}\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { ChatService } from "../../core/chat-service.js";
|
|
4
|
-
import * as React from "react";
|
|
5
|
-
import { createProvider } from "@contractspec/lib.ai-providers";
|
|
6
|
-
|
|
7
|
-
//#region src/presentation/hooks/useChat.tsx
|
|
8
|
-
/**
|
|
9
|
-
* Hook for managing AI chat state
|
|
10
|
-
*/
|
|
11
|
-
function useChat(options = {}) {
|
|
12
|
-
const { provider = "openai", mode = "byok", model, apiKey, proxyUrl, conversationId: initialConversationId, systemPrompt, streaming = true, onSend, onResponse, onError, onUsage } = options;
|
|
13
|
-
const [messages, setMessages] = React.useState([]);
|
|
14
|
-
const [conversation, setConversation] = React.useState(null);
|
|
15
|
-
const [isLoading, setIsLoading] = React.useState(false);
|
|
16
|
-
const [error, setError] = React.useState(null);
|
|
17
|
-
const [conversationId, setConversationId] = React.useState(initialConversationId ?? null);
|
|
18
|
-
const abortControllerRef = React.useRef(null);
|
|
19
|
-
const chatServiceRef = React.useRef(null);
|
|
20
|
-
React.useEffect(() => {
|
|
21
|
-
chatServiceRef.current = new ChatService({
|
|
22
|
-
provider: createProvider({
|
|
23
|
-
provider,
|
|
24
|
-
model,
|
|
25
|
-
apiKey,
|
|
26
|
-
proxyUrl
|
|
27
|
-
}),
|
|
28
|
-
systemPrompt,
|
|
29
|
-
onUsage
|
|
30
|
-
});
|
|
31
|
-
}, [
|
|
32
|
-
provider,
|
|
33
|
-
mode,
|
|
34
|
-
model,
|
|
35
|
-
apiKey,
|
|
36
|
-
proxyUrl,
|
|
37
|
-
systemPrompt,
|
|
38
|
-
onUsage
|
|
39
|
-
]);
|
|
40
|
-
React.useEffect(() => {
|
|
41
|
-
if (!conversationId || !chatServiceRef.current) return;
|
|
42
|
-
const loadConversation = async () => {
|
|
43
|
-
if (!chatServiceRef.current) return;
|
|
44
|
-
const conv = await chatServiceRef.current.getConversation(conversationId);
|
|
45
|
-
if (conv) {
|
|
46
|
-
setConversation(conv);
|
|
47
|
-
setMessages(conv.messages);
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
loadConversation().catch(console.error);
|
|
51
|
-
}, [conversationId]);
|
|
52
|
-
const sendMessage = React.useCallback(async (content, attachments) => {
|
|
53
|
-
if (!chatServiceRef.current) throw new Error("Chat service not initialized");
|
|
54
|
-
setIsLoading(true);
|
|
55
|
-
setError(null);
|
|
56
|
-
abortControllerRef.current = new AbortController();
|
|
57
|
-
try {
|
|
58
|
-
const userMessage = {
|
|
59
|
-
id: `msg_${Date.now()}`,
|
|
60
|
-
conversationId: conversationId ?? "",
|
|
61
|
-
role: "user",
|
|
62
|
-
content,
|
|
63
|
-
status: "completed",
|
|
64
|
-
createdAt: /* @__PURE__ */ new Date(),
|
|
65
|
-
updatedAt: /* @__PURE__ */ new Date(),
|
|
66
|
-
attachments
|
|
67
|
-
};
|
|
68
|
-
setMessages((prev) => [...prev, userMessage]);
|
|
69
|
-
onSend?.(userMessage);
|
|
70
|
-
if (streaming) {
|
|
71
|
-
const result = await chatServiceRef.current.stream({
|
|
72
|
-
conversationId: conversationId ?? void 0,
|
|
73
|
-
content,
|
|
74
|
-
attachments
|
|
75
|
-
});
|
|
76
|
-
if (!conversationId) setConversationId(result.conversationId);
|
|
77
|
-
const assistantMessage = {
|
|
78
|
-
id: result.messageId,
|
|
79
|
-
conversationId: result.conversationId,
|
|
80
|
-
role: "assistant",
|
|
81
|
-
content: "",
|
|
82
|
-
status: "streaming",
|
|
83
|
-
createdAt: /* @__PURE__ */ new Date(),
|
|
84
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
85
|
-
};
|
|
86
|
-
setMessages((prev) => [...prev, assistantMessage]);
|
|
87
|
-
let fullContent = "";
|
|
88
|
-
for await (const chunk of result.stream) if (chunk.type === "text" && chunk.content) {
|
|
89
|
-
fullContent += chunk.content;
|
|
90
|
-
setMessages((prev) => prev.map((m) => m.id === result.messageId ? {
|
|
91
|
-
...m,
|
|
92
|
-
content: fullContent
|
|
93
|
-
} : m));
|
|
94
|
-
} else if (chunk.type === "done") {
|
|
95
|
-
setMessages((prev) => prev.map((m) => m.id === result.messageId ? {
|
|
96
|
-
...m,
|
|
97
|
-
status: "completed",
|
|
98
|
-
usage: chunk.usage,
|
|
99
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
100
|
-
} : m));
|
|
101
|
-
onResponse?.(messages.find((m) => m.id === result.messageId) ?? assistantMessage);
|
|
102
|
-
} else if (chunk.type === "error") {
|
|
103
|
-
setMessages((prev) => prev.map((m) => m.id === result.messageId ? {
|
|
104
|
-
...m,
|
|
105
|
-
status: "error",
|
|
106
|
-
error: chunk.error,
|
|
107
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
108
|
-
} : m));
|
|
109
|
-
if (chunk.error) {
|
|
110
|
-
const err = new Error(chunk.error.message);
|
|
111
|
-
setError(err);
|
|
112
|
-
onError?.(err);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
} else {
|
|
116
|
-
const result = await chatServiceRef.current.send({
|
|
117
|
-
conversationId: conversationId ?? void 0,
|
|
118
|
-
content,
|
|
119
|
-
attachments
|
|
120
|
-
});
|
|
121
|
-
setConversation(result.conversation);
|
|
122
|
-
setMessages(result.conversation.messages);
|
|
123
|
-
if (!conversationId) setConversationId(result.conversation.id);
|
|
124
|
-
onResponse?.(result.message);
|
|
125
|
-
}
|
|
126
|
-
} catch (err) {
|
|
127
|
-
const error$1 = err instanceof Error ? err : new Error(String(err));
|
|
128
|
-
setError(error$1);
|
|
129
|
-
onError?.(error$1);
|
|
130
|
-
} finally {
|
|
131
|
-
setIsLoading(false);
|
|
132
|
-
abortControllerRef.current = null;
|
|
133
|
-
}
|
|
134
|
-
}, [
|
|
135
|
-
conversationId,
|
|
136
|
-
streaming,
|
|
137
|
-
onSend,
|
|
138
|
-
onResponse,
|
|
139
|
-
onError,
|
|
140
|
-
messages
|
|
141
|
-
]);
|
|
142
|
-
return {
|
|
143
|
-
messages,
|
|
144
|
-
conversation,
|
|
145
|
-
isLoading,
|
|
146
|
-
error,
|
|
147
|
-
sendMessage,
|
|
148
|
-
clearConversation: React.useCallback(() => {
|
|
149
|
-
setMessages([]);
|
|
150
|
-
setConversation(null);
|
|
151
|
-
setConversationId(null);
|
|
152
|
-
setError(null);
|
|
153
|
-
}, []),
|
|
154
|
-
setConversationId,
|
|
155
|
-
regenerate: React.useCallback(async () => {
|
|
156
|
-
const lastUserMessageIndex = messages.findLastIndex((m) => m.role === "user");
|
|
157
|
-
if (lastUserMessageIndex === -1) return;
|
|
158
|
-
const lastUserMessage = messages[lastUserMessageIndex];
|
|
159
|
-
if (!lastUserMessage) return;
|
|
160
|
-
setMessages((prev) => prev.slice(0, lastUserMessageIndex + 1));
|
|
161
|
-
await sendMessage(lastUserMessage.content, lastUserMessage.attachments);
|
|
162
|
-
}, [messages, sendMessage]),
|
|
163
|
-
stop: React.useCallback(() => {
|
|
164
|
-
abortControllerRef.current?.abort();
|
|
165
|
-
setIsLoading(false);
|
|
166
|
-
}, [])
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
//#endregion
|
|
171
|
-
export { useChat };
|
|
172
|
-
//# sourceMappingURL=useChat.js.map
|