@contractspec/module.ai-chat 0.0.0-canary-20260113170453
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/LICENSE +21 -0
- package/README.md +169 -0
- package/dist/ai-chat.feature.d.ts +12 -0
- package/dist/ai-chat.feature.d.ts.map +1 -0
- package/dist/ai-chat.feature.js +102 -0
- package/dist/ai-chat.feature.js.map +1 -0
- package/dist/ai-chat.operations.d.ts +243 -0
- package/dist/ai-chat.operations.d.ts.map +1 -0
- package/dist/ai-chat.operations.js +172 -0
- package/dist/ai-chat.operations.js.map +1 -0
- package/dist/context/context-builder.d.ts +57 -0
- package/dist/context/context-builder.d.ts.map +1 -0
- package/dist/context/context-builder.js +148 -0
- package/dist/context/context-builder.js.map +1 -0
- package/dist/context/file-operations.d.ts +100 -0
- package/dist/context/file-operations.d.ts.map +1 -0
- package/dist/context/file-operations.js +175 -0
- package/dist/context/file-operations.js.map +1 -0
- package/dist/context/index.d.ts +4 -0
- package/dist/context/index.js +5 -0
- package/dist/context/workspace-context.d.ts +117 -0
- package/dist/context/workspace-context.d.ts.map +1 -0
- package/dist/context/workspace-context.js +124 -0
- package/dist/context/workspace-context.js.map +1 -0
- package/dist/core/chat-service.d.ts +73 -0
- package/dist/core/chat-service.d.ts.map +1 -0
- package/dist/core/chat-service.js +227 -0
- package/dist/core/chat-service.js.map +1 -0
- package/dist/core/conversation-store.d.ts +74 -0
- package/dist/core/conversation-store.d.ts.map +1 -0
- package/dist/core/conversation-store.js +109 -0
- package/dist/core/conversation-store.js.map +1 -0
- package/dist/core/index.d.ts +4 -0
- package/dist/core/index.js +4 -0
- package/dist/core/message-types.d.ts +150 -0
- package/dist/core/message-types.d.ts.map +1 -0
- package/dist/events.d.ts +115 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +98 -0
- package/dist/events.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +23 -0
- package/dist/presentation/components/ChatContainer.d.ts +21 -0
- package/dist/presentation/components/ChatContainer.d.ts.map +1 -0
- package/dist/presentation/components/ChatContainer.js +63 -0
- package/dist/presentation/components/ChatContainer.js.map +1 -0
- package/dist/presentation/components/ChatInput.d.ts +35 -0
- package/dist/presentation/components/ChatInput.d.ts.map +1 -0
- package/dist/presentation/components/ChatInput.js +149 -0
- package/dist/presentation/components/ChatInput.js.map +1 -0
- package/dist/presentation/components/ChatMessage.d.ts +24 -0
- package/dist/presentation/components/ChatMessage.d.ts.map +1 -0
- package/dist/presentation/components/ChatMessage.js +136 -0
- package/dist/presentation/components/ChatMessage.js.map +1 -0
- package/dist/presentation/components/CodePreview.d.ts +40 -0
- package/dist/presentation/components/CodePreview.d.ts.map +1 -0
- package/dist/presentation/components/CodePreview.js +127 -0
- package/dist/presentation/components/CodePreview.js.map +1 -0
- package/dist/presentation/components/ContextIndicator.d.ts +26 -0
- package/dist/presentation/components/ContextIndicator.d.ts.map +1 -0
- package/dist/presentation/components/ContextIndicator.js +97 -0
- package/dist/presentation/components/ContextIndicator.js.map +1 -0
- package/dist/presentation/components/ModelPicker.d.ts +39 -0
- package/dist/presentation/components/ModelPicker.d.ts.map +1 -0
- package/dist/presentation/components/ModelPicker.js +202 -0
- package/dist/presentation/components/ModelPicker.js.map +1 -0
- package/dist/presentation/components/index.d.ts +7 -0
- package/dist/presentation/components/index.js +8 -0
- package/dist/presentation/hooks/index.d.ts +3 -0
- package/dist/presentation/hooks/index.js +4 -0
- package/dist/presentation/hooks/useChat.d.ts +67 -0
- package/dist/presentation/hooks/useChat.d.ts.map +1 -0
- package/dist/presentation/hooks/useChat.js +172 -0
- package/dist/presentation/hooks/useChat.js.map +1 -0
- package/dist/presentation/hooks/useProviders.d.ts +38 -0
- package/dist/presentation/hooks/useProviders.d.ts.map +1 -0
- package/dist/presentation/hooks/useProviders.js +41 -0
- package/dist/presentation/hooks/useProviders.js.map +1 -0
- package/dist/presentation/index.d.ts +11 -0
- package/dist/presentation/index.js +12 -0
- package/dist/providers/chat-utilities.d.ts +15 -0
- package/dist/providers/chat-utilities.d.ts.map +1 -0
- package/dist/providers/chat-utilities.js +17 -0
- package/dist/providers/chat-utilities.js.map +1 -0
- package/dist/providers/index.d.ts +3 -0
- package/dist/providers/index.js +4 -0
- package/dist/schema.d.ts +222 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +100 -0
- package/dist/schema.js.map +1 -0
- package/package.json +87 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { CodePreview } from "./CodePreview.js";
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
import { cn } from "@contractspec/lib.ui-kit-web/ui/utils";
|
|
6
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
import { Avatar, AvatarFallback } from "@contractspec/lib.ui-kit-web/ui/avatar";
|
|
8
|
+
import { Skeleton } from "@contractspec/lib.ui-kit-web/ui/skeleton";
|
|
9
|
+
import { AlertCircle, Bot, Check, Copy, User } from "lucide-react";
|
|
10
|
+
import { Button } from "@contractspec/lib.design-system";
|
|
11
|
+
|
|
12
|
+
//#region src/presentation/components/ChatMessage.tsx
|
|
13
|
+
/**
|
|
14
|
+
* Extract code blocks from message content
|
|
15
|
+
*/
|
|
16
|
+
function extractCodeBlocks(content) {
|
|
17
|
+
const codeBlockRegex = /```(\w+)?\n([\s\S]*?)```/g;
|
|
18
|
+
const blocks = [];
|
|
19
|
+
let match;
|
|
20
|
+
while ((match = codeBlockRegex.exec(content)) !== null) blocks.push({
|
|
21
|
+
language: match[1] ?? "text",
|
|
22
|
+
code: match[2] ?? "",
|
|
23
|
+
raw: match[0]
|
|
24
|
+
});
|
|
25
|
+
return blocks;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Render message content with code blocks
|
|
29
|
+
*/
|
|
30
|
+
function MessageContent({ content }) {
|
|
31
|
+
const codeBlocks = extractCodeBlocks(content);
|
|
32
|
+
if (codeBlocks.length === 0) return /* @__PURE__ */ jsx("p", {
|
|
33
|
+
className: "whitespace-pre-wrap",
|
|
34
|
+
children: content
|
|
35
|
+
});
|
|
36
|
+
let remaining = content;
|
|
37
|
+
const parts = [];
|
|
38
|
+
let key = 0;
|
|
39
|
+
for (const block of codeBlocks) {
|
|
40
|
+
const [before, after] = remaining.split(block.raw);
|
|
41
|
+
if (before) parts.push(/* @__PURE__ */ jsx("p", {
|
|
42
|
+
className: "whitespace-pre-wrap",
|
|
43
|
+
children: before.trim()
|
|
44
|
+
}, key++));
|
|
45
|
+
parts.push(/* @__PURE__ */ jsx(CodePreview, {
|
|
46
|
+
code: block.code,
|
|
47
|
+
language: block.language,
|
|
48
|
+
className: "my-2"
|
|
49
|
+
}, key++));
|
|
50
|
+
remaining = after ?? "";
|
|
51
|
+
}
|
|
52
|
+
if (remaining.trim()) parts.push(/* @__PURE__ */ jsx("p", {
|
|
53
|
+
className: "whitespace-pre-wrap",
|
|
54
|
+
children: remaining.trim()
|
|
55
|
+
}, key++));
|
|
56
|
+
return /* @__PURE__ */ jsx(Fragment, { children: parts });
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Chat message component
|
|
60
|
+
*/
|
|
61
|
+
function ChatMessage({ message, className, showCopy = true, showAvatar = true }) {
|
|
62
|
+
const [copied, setCopied] = React.useState(false);
|
|
63
|
+
const isUser = message.role === "user";
|
|
64
|
+
const isError = message.status === "error";
|
|
65
|
+
const isStreaming = message.status === "streaming";
|
|
66
|
+
const handleCopy = React.useCallback(async () => {
|
|
67
|
+
await navigator.clipboard.writeText(message.content);
|
|
68
|
+
setCopied(true);
|
|
69
|
+
setTimeout(() => setCopied(false), 2e3);
|
|
70
|
+
}, [message.content]);
|
|
71
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
72
|
+
className: cn("group flex gap-3", isUser && "flex-row-reverse", className),
|
|
73
|
+
children: [showAvatar && /* @__PURE__ */ jsx(Avatar, {
|
|
74
|
+
className: "h-8 w-8 shrink-0",
|
|
75
|
+
children: /* @__PURE__ */ jsx(AvatarFallback, {
|
|
76
|
+
className: cn(isUser ? "bg-primary text-primary-foreground" : "bg-muted"),
|
|
77
|
+
children: isUser ? /* @__PURE__ */ jsx(User, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(Bot, { className: "h-4 w-4" })
|
|
78
|
+
})
|
|
79
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
80
|
+
className: cn("flex max-w-[80%] flex-col gap-1", isUser && "items-end"),
|
|
81
|
+
children: [
|
|
82
|
+
/* @__PURE__ */ jsx("div", {
|
|
83
|
+
className: cn("rounded-2xl px-4 py-2", isUser ? "bg-primary text-primary-foreground" : "bg-muted text-foreground", isError && "border-destructive bg-destructive/10 border"),
|
|
84
|
+
children: isError && message.error ? /* @__PURE__ */ jsxs("div", {
|
|
85
|
+
className: "flex items-start gap-2",
|
|
86
|
+
children: [/* @__PURE__ */ jsx(AlertCircle, { className: "text-destructive mt-0.5 h-4 w-4 shrink-0" }), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("p", {
|
|
87
|
+
className: "text-destructive font-medium",
|
|
88
|
+
children: message.error.code
|
|
89
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
90
|
+
className: "text-muted-foreground text-sm",
|
|
91
|
+
children: message.error.message
|
|
92
|
+
})] })]
|
|
93
|
+
}) : isStreaming && !message.content ? /* @__PURE__ */ jsxs("div", {
|
|
94
|
+
className: "flex flex-col gap-2",
|
|
95
|
+
children: [/* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-48" }), /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-32" })]
|
|
96
|
+
}) : /* @__PURE__ */ jsx(MessageContent, { content: message.content })
|
|
97
|
+
}),
|
|
98
|
+
/* @__PURE__ */ jsxs("div", {
|
|
99
|
+
className: cn("flex items-center gap-2 text-xs", "text-muted-foreground opacity-0 transition-opacity", "group-hover:opacity-100"),
|
|
100
|
+
children: [
|
|
101
|
+
/* @__PURE__ */ jsx("span", { children: new Date(message.createdAt).toLocaleTimeString([], {
|
|
102
|
+
hour: "2-digit",
|
|
103
|
+
minute: "2-digit"
|
|
104
|
+
}) }),
|
|
105
|
+
message.usage && /* @__PURE__ */ jsxs("span", { children: [message.usage.inputTokens + message.usage.outputTokens, " tokens"] }),
|
|
106
|
+
showCopy && !isUser && message.content && /* @__PURE__ */ jsx(Button, {
|
|
107
|
+
variant: "ghost",
|
|
108
|
+
size: "sm",
|
|
109
|
+
className: "h-6 w-6 p-0",
|
|
110
|
+
onPress: handleCopy,
|
|
111
|
+
"aria-label": copied ? "Copied" : "Copy message",
|
|
112
|
+
children: copied ? /* @__PURE__ */ jsx(Check, { className: "h-3 w-3" }) : /* @__PURE__ */ jsx(Copy, { className: "h-3 w-3" })
|
|
113
|
+
})
|
|
114
|
+
]
|
|
115
|
+
}),
|
|
116
|
+
message.reasoning && /* @__PURE__ */ jsxs("details", {
|
|
117
|
+
className: "text-muted-foreground mt-2 text-sm",
|
|
118
|
+
children: [/* @__PURE__ */ jsx("summary", {
|
|
119
|
+
className: "cursor-pointer hover:underline",
|
|
120
|
+
children: "View reasoning"
|
|
121
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
122
|
+
className: "bg-muted mt-1 rounded-md p-2",
|
|
123
|
+
children: /* @__PURE__ */ jsx("p", {
|
|
124
|
+
className: "whitespace-pre-wrap",
|
|
125
|
+
children: message.reasoning
|
|
126
|
+
})
|
|
127
|
+
})]
|
|
128
|
+
})
|
|
129
|
+
]
|
|
130
|
+
})]
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
//#endregion
|
|
135
|
+
export { ChatMessage };
|
|
136
|
+
//# sourceMappingURL=ChatMessage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ChatMessage.js","names":[],"sources":["../../../src/presentation/components/ChatMessage.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport { cn } from '@contractspec/lib.ui-kit-web/ui/utils';\nimport { Avatar, AvatarFallback } from '@contractspec/lib.ui-kit-web/ui/avatar';\nimport { Skeleton } from '@contractspec/lib.ui-kit-web/ui/skeleton';\nimport { Bot, User, AlertCircle, Copy, Check } from 'lucide-react';\nimport { Button } from '@contractspec/lib.design-system';\nimport type { ChatMessage as ChatMessageType } from '../../core/message-types';\nimport { CodePreview } from './CodePreview';\n\nexport interface ChatMessageProps {\n message: ChatMessageType;\n className?: string;\n /** Show copy button */\n showCopy?: boolean;\n /** Show avatar */\n showAvatar?: boolean;\n}\n\n/**\n * Extract code blocks from message content\n */\nfunction extractCodeBlocks(\n content: string\n): { language: string; code: string; raw: string }[] {\n const codeBlockRegex = /```(\\w+)?\\n([\\s\\S]*?)```/g;\n const blocks: { language: string; code: string; raw: string }[] = [];\n let match;\n\n while ((match = codeBlockRegex.exec(content)) !== null) {\n blocks.push({\n language: match[1] ?? 'text',\n code: match[2] ?? '',\n raw: match[0],\n });\n }\n\n return blocks;\n}\n\n/**\n * Render message content with code blocks\n */\nfunction MessageContent({ content }: { content: string }) {\n const codeBlocks = extractCodeBlocks(content);\n\n if (codeBlocks.length === 0) {\n return <p className=\"whitespace-pre-wrap\">{content}</p>;\n }\n\n // Split content by code blocks and render\n let remaining = content;\n const parts: React.ReactNode[] = [];\n let key = 0;\n\n for (const block of codeBlocks) {\n const [before, after] = remaining.split(block.raw);\n if (before) {\n parts.push(\n <p key={key++} className=\"whitespace-pre-wrap\">\n {before.trim()}\n </p>\n );\n }\n parts.push(\n <CodePreview\n key={key++}\n code={block.code}\n language={block.language}\n className=\"my-2\"\n />\n );\n remaining = after ?? '';\n }\n\n if (remaining.trim()) {\n parts.push(\n <p key={key++} className=\"whitespace-pre-wrap\">\n {remaining.trim()}\n </p>\n );\n }\n\n return <>{parts}</>;\n}\n\n/**\n * Chat message component\n */\nexport function ChatMessage({\n message,\n className,\n showCopy = true,\n showAvatar = true,\n}: ChatMessageProps) {\n const [copied, setCopied] = React.useState(false);\n\n const isUser = message.role === 'user';\n const isError = message.status === 'error';\n const isStreaming = message.status === 'streaming';\n\n const handleCopy = React.useCallback(async () => {\n await navigator.clipboard.writeText(message.content);\n setCopied(true);\n setTimeout(() => setCopied(false), 2000);\n }, [message.content]);\n\n return (\n <div\n className={cn(\n 'group flex gap-3',\n isUser && 'flex-row-reverse',\n className\n )}\n >\n {showAvatar && (\n <Avatar className=\"h-8 w-8 shrink-0\">\n <AvatarFallback\n className={cn(\n isUser ? 'bg-primary text-primary-foreground' : 'bg-muted'\n )}\n >\n {isUser ? (\n <User className=\"h-4 w-4\" />\n ) : (\n <Bot className=\"h-4 w-4\" />\n )}\n </AvatarFallback>\n </Avatar>\n )}\n\n <div\n className={cn('flex max-w-[80%] flex-col gap-1', isUser && 'items-end')}\n >\n <div\n className={cn(\n 'rounded-2xl px-4 py-2',\n isUser\n ? 'bg-primary text-primary-foreground'\n : 'bg-muted text-foreground',\n isError && 'border-destructive bg-destructive/10 border'\n )}\n >\n {isError && message.error ? (\n <div className=\"flex items-start gap-2\">\n <AlertCircle className=\"text-destructive mt-0.5 h-4 w-4 shrink-0\" />\n <div>\n <p className=\"text-destructive font-medium\">\n {message.error.code}\n </p>\n <p className=\"text-muted-foreground text-sm\">\n {message.error.message}\n </p>\n </div>\n </div>\n ) : isStreaming && !message.content ? (\n <div className=\"flex flex-col gap-2\">\n <Skeleton className=\"h-4 w-48\" />\n <Skeleton className=\"h-4 w-32\" />\n </div>\n ) : (\n <MessageContent content={message.content} />\n )}\n </div>\n\n {/* Message meta */}\n <div\n className={cn(\n 'flex items-center gap-2 text-xs',\n 'text-muted-foreground opacity-0 transition-opacity',\n 'group-hover:opacity-100'\n )}\n >\n <span>\n {new Date(message.createdAt).toLocaleTimeString([], {\n hour: '2-digit',\n minute: '2-digit',\n })}\n </span>\n\n {message.usage && (\n <span>\n {message.usage.inputTokens + message.usage.outputTokens} tokens\n </span>\n )}\n\n {showCopy && !isUser && message.content && (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-6 w-6 p-0\"\n onPress={handleCopy}\n aria-label={copied ? 'Copied' : 'Copy message'}\n >\n {copied ? (\n <Check className=\"h-3 w-3\" />\n ) : (\n <Copy className=\"h-3 w-3\" />\n )}\n </Button>\n )}\n </div>\n\n {/* Reasoning (for models that support it) */}\n {message.reasoning && (\n <details className=\"text-muted-foreground mt-2 text-sm\">\n <summary className=\"cursor-pointer hover:underline\">\n View reasoning\n </summary>\n <div className=\"bg-muted mt-1 rounded-md p-2\">\n <p className=\"whitespace-pre-wrap\">{message.reasoning}</p>\n </div>\n </details>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import * as react_jsx_runtime0 from "react/jsx-runtime";
|
|
2
|
+
|
|
3
|
+
//#region src/presentation/components/CodePreview.d.ts
|
|
4
|
+
interface CodePreviewProps {
|
|
5
|
+
/** Code content */
|
|
6
|
+
code: string;
|
|
7
|
+
/** Programming language */
|
|
8
|
+
language?: string;
|
|
9
|
+
/** File name */
|
|
10
|
+
filename?: string;
|
|
11
|
+
/** Additional class name */
|
|
12
|
+
className?: string;
|
|
13
|
+
/** Show copy button */
|
|
14
|
+
showCopy?: boolean;
|
|
15
|
+
/** Show execute button (for applicable languages) */
|
|
16
|
+
showExecute?: boolean;
|
|
17
|
+
/** Called when execute is clicked */
|
|
18
|
+
onExecute?: (code: string) => void;
|
|
19
|
+
/** Show download button */
|
|
20
|
+
showDownload?: boolean;
|
|
21
|
+
/** Max height before scroll */
|
|
22
|
+
maxHeight?: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Code preview component with syntax highlighting placeholder
|
|
26
|
+
*/
|
|
27
|
+
declare function CodePreview({
|
|
28
|
+
code,
|
|
29
|
+
language,
|
|
30
|
+
filename,
|
|
31
|
+
className,
|
|
32
|
+
showCopy,
|
|
33
|
+
showExecute,
|
|
34
|
+
onExecute,
|
|
35
|
+
showDownload,
|
|
36
|
+
maxHeight
|
|
37
|
+
}: CodePreviewProps): react_jsx_runtime0.JSX.Element;
|
|
38
|
+
//#endregion
|
|
39
|
+
export { CodePreview };
|
|
40
|
+
//# sourceMappingURL=CodePreview.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CodePreview.d.ts","names":[],"sources":["../../../src/presentation/components/CodePreview.tsx"],"sourcesContent":[],"mappings":";;;UAOiB,gBAAA;;;EAAA;EAgDD,QAAA,CAAA,EAAA,MAAW;EACzB;EACA,QAAA,CAAA,EAAA,MAAA;EACA;EACA,SAAA,CAAA,EAAA,MAAA;EACA;EACA,QAAA,CAAA,EAAA,OAAA;EACA;EACA,WAAA,CAAA,EAAA,OAAA;EACA;EACC,SAAA,CAAA,EAAA,CAAA,IAAA,EAAA,MAAA,EAAA,GAAA,IAAA;EAAgB;EAAA,YAAA,CAAA,EAAA,OAAA;;;;;;;iBAVH,WAAA;;;;;;;;;;GAUb,mBAAgB,kBAAA,CAAA,GAAA,CAAA"}
|
|
@@ -0,0 +1,127 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { WorkspaceSummary } from "../../context/workspace-context.js";
|
|
2
|
+
import * as react_jsx_runtime4 from "react/jsx-runtime";
|
|
3
|
+
|
|
4
|
+
//#region src/presentation/components/ContextIndicator.d.ts
|
|
5
|
+
interface ContextIndicatorProps {
|
|
6
|
+
/** Workspace summary */
|
|
7
|
+
summary?: WorkspaceSummary;
|
|
8
|
+
/** Whether context is active */
|
|
9
|
+
active?: boolean;
|
|
10
|
+
/** Additional class name */
|
|
11
|
+
className?: string;
|
|
12
|
+
/** Show details */
|
|
13
|
+
showDetails?: boolean;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Indicator showing active workspace context
|
|
17
|
+
*/
|
|
18
|
+
declare function ContextIndicator({
|
|
19
|
+
summary,
|
|
20
|
+
active,
|
|
21
|
+
className,
|
|
22
|
+
showDetails
|
|
23
|
+
}: ContextIndicatorProps): react_jsx_runtime4.JSX.Element;
|
|
24
|
+
//#endregion
|
|
25
|
+
export { ContextIndicator };
|
|
26
|
+
//# sourceMappingURL=ContextIndicator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ContextIndicator.d.ts","names":[],"sources":["../../../src/presentation/components/ContextIndicator.tsx"],"sourcesContent":[],"mappings":";;;;UAciB,qBAAA;;YAEL;EAFK;EAcD,MAAA,CAAA,EAAA,OAAA;EACd;EACA,SAAA,CAAA,EAAA,MAAA;EACA;EACA,WAAA,CAAA,EAAA,OAAA;;;;;iBAJc,gBAAA;;;;;GAKb,wBAAqB,kBAAA,CAAA,GAAA,CAAA"}
|
|
@@ -0,0 +1,97 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import * as react_jsx_runtime3 from "react/jsx-runtime";
|
|
2
|
+
import { ProviderMode, ProviderName } from "@contractspec/lib.ai-providers";
|
|
3
|
+
|
|
4
|
+
//#region src/presentation/components/ModelPicker.d.ts
|
|
5
|
+
interface ModelSelection {
|
|
6
|
+
provider: ProviderName;
|
|
7
|
+
model: string;
|
|
8
|
+
mode: ProviderMode;
|
|
9
|
+
}
|
|
10
|
+
interface ModelPickerProps {
|
|
11
|
+
/** Currently selected provider/model */
|
|
12
|
+
value: ModelSelection;
|
|
13
|
+
/** Called when selection changes */
|
|
14
|
+
onChange: (value: ModelSelection) => void;
|
|
15
|
+
/** Available providers (with availability info) */
|
|
16
|
+
availableProviders?: {
|
|
17
|
+
provider: ProviderName;
|
|
18
|
+
available: boolean;
|
|
19
|
+
mode: ProviderMode;
|
|
20
|
+
reason?: string;
|
|
21
|
+
}[];
|
|
22
|
+
/** Additional class name */
|
|
23
|
+
className?: string;
|
|
24
|
+
/** Compact mode (smaller) */
|
|
25
|
+
compact?: boolean;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Model picker component for selecting AI provider and model
|
|
29
|
+
*/
|
|
30
|
+
declare function ModelPicker({
|
|
31
|
+
value,
|
|
32
|
+
onChange,
|
|
33
|
+
availableProviders,
|
|
34
|
+
className,
|
|
35
|
+
compact
|
|
36
|
+
}: ModelPickerProps): react_jsx_runtime3.JSX.Element;
|
|
37
|
+
//#endregion
|
|
38
|
+
export { ModelPicker };
|
|
39
|
+
//# sourceMappingURL=ModelPicker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ModelPicker.d.ts","names":[],"sources":["../../../src/presentation/components/ModelPicker.tsx"],"sourcesContent":[],"mappings":";;;;UAsBiB,cAAA;YACL;;EADK,IAAA,EAGT,YAHuB;AAM/B;AAES,UAFQ,gBAAA,CAER;EAEW;EAGN,KAAA,EALL,cAKK;EAEJ;EAAY,QAAA,EAAA,CAAA,KAAA,EALF,cAKE,EAAA,GAAA,IAAA;EAqCN;EACd,kBAAA,CAAA,EAAA;IACA,QAAA,EAzCY,YAyCZ;IACA,SAAA,EAAA,OAAA;IACA,IAAA,EAzCQ,YAyCR;IACA,MAAA,CAAA,EAAA,MAAA;EACC,CAAA,EAAA;EAAgB;EAAA,SAAA,CAAA,EAAA,MAAA;;;;;;;iBANH,WAAA;;;;;;GAMb,mBAAgB,kBAAA,CAAA,GAAA,CAAA"}
|