@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.
Files changed (91) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +169 -0
  3. package/dist/ai-chat.feature.d.ts +12 -0
  4. package/dist/ai-chat.feature.d.ts.map +1 -0
  5. package/dist/ai-chat.feature.js +102 -0
  6. package/dist/ai-chat.feature.js.map +1 -0
  7. package/dist/ai-chat.operations.d.ts +243 -0
  8. package/dist/ai-chat.operations.d.ts.map +1 -0
  9. package/dist/ai-chat.operations.js +172 -0
  10. package/dist/ai-chat.operations.js.map +1 -0
  11. package/dist/context/context-builder.d.ts +57 -0
  12. package/dist/context/context-builder.d.ts.map +1 -0
  13. package/dist/context/context-builder.js +148 -0
  14. package/dist/context/context-builder.js.map +1 -0
  15. package/dist/context/file-operations.d.ts +100 -0
  16. package/dist/context/file-operations.d.ts.map +1 -0
  17. package/dist/context/file-operations.js +175 -0
  18. package/dist/context/file-operations.js.map +1 -0
  19. package/dist/context/index.d.ts +4 -0
  20. package/dist/context/index.js +5 -0
  21. package/dist/context/workspace-context.d.ts +117 -0
  22. package/dist/context/workspace-context.d.ts.map +1 -0
  23. package/dist/context/workspace-context.js +124 -0
  24. package/dist/context/workspace-context.js.map +1 -0
  25. package/dist/core/chat-service.d.ts +73 -0
  26. package/dist/core/chat-service.d.ts.map +1 -0
  27. package/dist/core/chat-service.js +227 -0
  28. package/dist/core/chat-service.js.map +1 -0
  29. package/dist/core/conversation-store.d.ts +74 -0
  30. package/dist/core/conversation-store.d.ts.map +1 -0
  31. package/dist/core/conversation-store.js +109 -0
  32. package/dist/core/conversation-store.js.map +1 -0
  33. package/dist/core/index.d.ts +4 -0
  34. package/dist/core/index.js +4 -0
  35. package/dist/core/message-types.d.ts +150 -0
  36. package/dist/core/message-types.d.ts.map +1 -0
  37. package/dist/events.d.ts +115 -0
  38. package/dist/events.d.ts.map +1 -0
  39. package/dist/events.js +98 -0
  40. package/dist/events.js.map +1 -0
  41. package/dist/index.d.ts +21 -0
  42. package/dist/index.js +23 -0
  43. package/dist/presentation/components/ChatContainer.d.ts +21 -0
  44. package/dist/presentation/components/ChatContainer.d.ts.map +1 -0
  45. package/dist/presentation/components/ChatContainer.js +63 -0
  46. package/dist/presentation/components/ChatContainer.js.map +1 -0
  47. package/dist/presentation/components/ChatInput.d.ts +35 -0
  48. package/dist/presentation/components/ChatInput.d.ts.map +1 -0
  49. package/dist/presentation/components/ChatInput.js +149 -0
  50. package/dist/presentation/components/ChatInput.js.map +1 -0
  51. package/dist/presentation/components/ChatMessage.d.ts +24 -0
  52. package/dist/presentation/components/ChatMessage.d.ts.map +1 -0
  53. package/dist/presentation/components/ChatMessage.js +136 -0
  54. package/dist/presentation/components/ChatMessage.js.map +1 -0
  55. package/dist/presentation/components/CodePreview.d.ts +40 -0
  56. package/dist/presentation/components/CodePreview.d.ts.map +1 -0
  57. package/dist/presentation/components/CodePreview.js +127 -0
  58. package/dist/presentation/components/CodePreview.js.map +1 -0
  59. package/dist/presentation/components/ContextIndicator.d.ts +26 -0
  60. package/dist/presentation/components/ContextIndicator.d.ts.map +1 -0
  61. package/dist/presentation/components/ContextIndicator.js +97 -0
  62. package/dist/presentation/components/ContextIndicator.js.map +1 -0
  63. package/dist/presentation/components/ModelPicker.d.ts +39 -0
  64. package/dist/presentation/components/ModelPicker.d.ts.map +1 -0
  65. package/dist/presentation/components/ModelPicker.js +202 -0
  66. package/dist/presentation/components/ModelPicker.js.map +1 -0
  67. package/dist/presentation/components/index.d.ts +7 -0
  68. package/dist/presentation/components/index.js +8 -0
  69. package/dist/presentation/hooks/index.d.ts +3 -0
  70. package/dist/presentation/hooks/index.js +4 -0
  71. package/dist/presentation/hooks/useChat.d.ts +67 -0
  72. package/dist/presentation/hooks/useChat.d.ts.map +1 -0
  73. package/dist/presentation/hooks/useChat.js +172 -0
  74. package/dist/presentation/hooks/useChat.js.map +1 -0
  75. package/dist/presentation/hooks/useProviders.d.ts +38 -0
  76. package/dist/presentation/hooks/useProviders.d.ts.map +1 -0
  77. package/dist/presentation/hooks/useProviders.js +41 -0
  78. package/dist/presentation/hooks/useProviders.js.map +1 -0
  79. package/dist/presentation/index.d.ts +11 -0
  80. package/dist/presentation/index.js +12 -0
  81. package/dist/providers/chat-utilities.d.ts +15 -0
  82. package/dist/providers/chat-utilities.d.ts.map +1 -0
  83. package/dist/providers/chat-utilities.js +17 -0
  84. package/dist/providers/chat-utilities.js.map +1 -0
  85. package/dist/providers/index.d.ts +3 -0
  86. package/dist/providers/index.js +4 -0
  87. package/dist/schema.d.ts +222 -0
  88. package/dist/schema.d.ts.map +1 -0
  89. package/dist/schema.js +100 -0
  90. package/dist/schema.js.map +1 -0
  91. package/package.json +87 -0
@@ -0,0 +1,202 @@
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
@@ -0,0 +1 @@
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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"}
@@ -0,0 +1,7 @@
1
+ import { ChatContainer } from "./ChatContainer.js";
2
+ import { ChatMessage } from "./ChatMessage.js";
3
+ import { ChatInput } from "./ChatInput.js";
4
+ import { ModelPicker } from "./ModelPicker.js";
5
+ import { ContextIndicator } from "./ContextIndicator.js";
6
+ import { CodePreview } from "./CodePreview.js";
7
+ export { ChatContainer, ChatInput, ChatMessage, CodePreview, ContextIndicator, ModelPicker };
@@ -0,0 +1,8 @@
1
+ import { ChatContainer } from "./ChatContainer.js";
2
+ import { CodePreview } from "./CodePreview.js";
3
+ import { ChatMessage } from "./ChatMessage.js";
4
+ import { ChatInput } from "./ChatInput.js";
5
+ import { ModelPicker } from "./ModelPicker.js";
6
+ import { ContextIndicator } from "./ContextIndicator.js";
7
+
8
+ export { ChatContainer, ChatInput, ChatMessage, CodePreview, ContextIndicator, ModelPicker };
@@ -0,0 +1,3 @@
1
+ import { UseChatOptions, UseChatReturn, useChat } from "./useChat.js";
2
+ import { UseProvidersReturn, useProviders } from "./useProviders.js";
3
+ export { type UseChatOptions, type UseChatReturn, type UseProvidersReturn, useChat, useProviders };
@@ -0,0 +1,4 @@
1
+ import { useChat } from "./useChat.js";
2
+ import { useProviders } from "./useProviders.js";
3
+
4
+ export { useChat, useProviders };
@@ -0,0 +1,67 @@
1
+ import { ChatAttachment, ChatConversation, ChatMessage } from "../../core/message-types.js";
2
+ import { ProviderMode, ProviderName } from "@contractspec/lib.ai-providers";
3
+
4
+ //#region src/presentation/hooks/useChat.d.ts
5
+
6
+ /**
7
+ * Options for useChat hook
8
+ */
9
+ interface UseChatOptions {
10
+ /** Provider to use */
11
+ provider?: ProviderName;
12
+ /** Provider mode */
13
+ mode?: ProviderMode;
14
+ /** Model to use */
15
+ model?: string;
16
+ /** API key for BYOK mode */
17
+ apiKey?: string;
18
+ /** API proxy URL for managed mode */
19
+ proxyUrl?: string;
20
+ /** Initial conversation ID to resume */
21
+ conversationId?: string;
22
+ /** System prompt override */
23
+ systemPrompt?: string;
24
+ /** Enable streaming */
25
+ streaming?: boolean;
26
+ /** Called when a message is sent */
27
+ onSend?: (message: ChatMessage) => void;
28
+ /** Called when a response is received */
29
+ onResponse?: (message: ChatMessage) => void;
30
+ /** Called on error */
31
+ onError?: (error: Error) => void;
32
+ /** Called when usage is recorded */
33
+ onUsage?: (usage: {
34
+ inputTokens: number;
35
+ outputTokens: number;
36
+ }) => void;
37
+ }
38
+ /**
39
+ * Return type for useChat hook
40
+ */
41
+ interface UseChatReturn {
42
+ /** Current messages */
43
+ messages: ChatMessage[];
44
+ /** Current conversation */
45
+ conversation: ChatConversation | null;
46
+ /** Whether currently loading/streaming */
47
+ isLoading: boolean;
48
+ /** Current error */
49
+ error: Error | null;
50
+ /** Send a message */
51
+ sendMessage: (content: string, attachments?: ChatAttachment[]) => Promise<void>;
52
+ /** Clear conversation and start fresh */
53
+ clearConversation: () => void;
54
+ /** Set conversation ID to resume */
55
+ setConversationId: (id: string | null) => void;
56
+ /** Regenerate last response */
57
+ regenerate: () => Promise<void>;
58
+ /** Stop current generation */
59
+ stop: () => void;
60
+ }
61
+ /**
62
+ * Hook for managing AI chat state
63
+ */
64
+ declare function useChat(options?: UseChatOptions): UseChatReturn;
65
+ //#endregion
66
+ export { UseChatOptions, UseChatReturn, useChat };
67
+ //# sourceMappingURL=useChat.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useChat.d.ts","names":[],"sources":["../../../src/presentation/hooks/useChat.tsx"],"sourcesContent":[],"mappings":";;;;;;;AAkBA;AAEa,UAFI,cAAA,CAEJ;EAEJ;EAcY,QAAA,CAAA,EAhBR,YAgBQ;EAEI;EAEL,IAAA,CAAA,EAlBX,YAkBW;EAAK;EAQR,KAAA,CAAA,EAAA,MAAA;EAEL;EAEI,MAAA,CAAA,EAAA,MAAA;EAIP;EAIS,QAAA,CAAA,EAAA,MAAA;EACX;EAMa,cAAA,CAAA,EAAA,MAAA;EAAO;EAQX,YAAO,CAAA,EAAA,MAAA;;;;qBAvCF;;yBAEI;;oBAEL;;;;;;;;;;UAQH,aAAA;;YAEL;;gBAEI;;;;SAIP;;+CAIS,qBACX;;;;;;oBAMa;;;;;;;iBAQJ,OAAA,WAAiB,iBAAsB"}
@@ -0,0 +1,172 @@
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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useChat.js","names":["error"],"sources":["../../../src/presentation/hooks/useChat.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport type {\n ChatAttachment,\n ChatConversation,\n ChatMessage,\n} from '../../core/message-types';\nimport { ChatService } from '../../core/chat-service';\nimport {\n createProvider,\n type ProviderMode,\n type ProviderName,\n} from '@contractspec/lib.ai-providers';\n\n/**\n * Options for useChat hook\n */\nexport interface UseChatOptions {\n /** Provider to use */\n provider?: ProviderName;\n /** Provider mode */\n mode?: ProviderMode;\n /** Model to use */\n model?: string;\n /** API key for BYOK mode */\n apiKey?: string;\n /** API proxy URL for managed mode */\n proxyUrl?: string;\n /** Initial conversation ID to resume */\n conversationId?: string;\n /** System prompt override */\n systemPrompt?: string;\n /** Enable streaming */\n streaming?: boolean;\n /** Called when a message is sent */\n onSend?: (message: ChatMessage) => void;\n /** Called when a response is received */\n onResponse?: (message: ChatMessage) => void;\n /** Called on error */\n onError?: (error: Error) => void;\n /** Called when usage is recorded */\n onUsage?: (usage: { inputTokens: number; outputTokens: number }) => void;\n}\n\n/**\n * Return type for useChat hook\n */\nexport interface UseChatReturn {\n /** Current messages */\n messages: ChatMessage[];\n /** Current conversation */\n conversation: ChatConversation | null;\n /** Whether currently loading/streaming */\n isLoading: boolean;\n /** Current error */\n error: Error | null;\n /** Send a message */\n sendMessage: (\n content: string,\n attachments?: ChatAttachment[]\n ) => Promise<void>;\n /** Clear conversation and start fresh */\n clearConversation: () => void;\n /** Set conversation ID to resume */\n setConversationId: (id: string | null) => void;\n /** Regenerate last response */\n regenerate: () => Promise<void>;\n /** Stop current generation */\n stop: () => void;\n}\n\n/**\n * Hook for managing AI chat state\n */\nexport function useChat(options: UseChatOptions = {}): UseChatReturn {\n const {\n provider = 'openai',\n mode = 'byok',\n model,\n apiKey,\n proxyUrl,\n conversationId: initialConversationId,\n systemPrompt,\n streaming = true,\n onSend,\n onResponse,\n onError,\n onUsage,\n } = options;\n\n const [messages, setMessages] = React.useState<ChatMessage[]>([]);\n const [conversation, setConversation] =\n React.useState<ChatConversation | null>(null);\n const [isLoading, setIsLoading] = React.useState(false);\n const [error, setError] = React.useState<Error | null>(null);\n const [conversationId, setConversationId] = React.useState<string | null>(\n initialConversationId ?? null\n );\n\n const abortControllerRef = React.useRef<AbortController | null>(null);\n const chatServiceRef = React.useRef<ChatService | null>(null);\n\n // Initialize chat service\n React.useEffect(() => {\n const chatProvider = createProvider({\n provider,\n model,\n apiKey,\n proxyUrl,\n });\n\n chatServiceRef.current = new ChatService({\n provider: chatProvider,\n systemPrompt,\n onUsage,\n });\n }, [provider, mode, model, apiKey, proxyUrl, systemPrompt, onUsage]);\n\n // Load existing conversation\n React.useEffect(() => {\n if (!conversationId || !chatServiceRef.current) return;\n\n const loadConversation = async () => {\n if (!chatServiceRef.current) return;\n\n const conv = await chatServiceRef.current.getConversation(conversationId);\n if (conv) {\n setConversation(conv);\n setMessages(conv.messages);\n }\n };\n\n loadConversation().catch(console.error);\n }, [conversationId]);\n\n const sendMessage = React.useCallback(\n async (content: string, attachments?: ChatAttachment[]) => {\n if (!chatServiceRef.current) {\n throw new Error('Chat service not initialized');\n }\n\n setIsLoading(true);\n setError(null);\n\n // Create abort controller\n abortControllerRef.current = new AbortController();\n\n try {\n // Add user message immediately\n const userMessage: ChatMessage = {\n id: `msg_${Date.now()}`,\n conversationId: conversationId ?? '',\n role: 'user',\n content,\n status: 'completed',\n createdAt: new Date(),\n updatedAt: new Date(),\n attachments,\n };\n setMessages((prev) => [...prev, userMessage]);\n onSend?.(userMessage);\n\n if (streaming) {\n // Streaming mode\n const result = await chatServiceRef.current.stream({\n conversationId: conversationId ?? undefined,\n content,\n attachments,\n });\n\n // Update conversation ID if new\n if (!conversationId) {\n setConversationId(result.conversationId);\n }\n\n // Add placeholder for assistant message\n const assistantMessage: ChatMessage = {\n id: result.messageId,\n conversationId: result.conversationId,\n role: 'assistant',\n content: '',\n status: 'streaming',\n createdAt: new Date(),\n updatedAt: new Date(),\n };\n setMessages((prev) => [...prev, assistantMessage]);\n\n // Process stream\n let fullContent = '';\n for await (const chunk of result.stream) {\n if (chunk.type === 'text' && chunk.content) {\n fullContent += chunk.content;\n setMessages((prev) =>\n prev.map((m) =>\n m.id === result.messageId ? { ...m, content: fullContent } : m\n )\n );\n } else if (chunk.type === 'done') {\n setMessages((prev) =>\n prev.map((m) =>\n m.id === result.messageId\n ? {\n ...m,\n status: 'completed',\n usage: chunk.usage,\n updatedAt: new Date(),\n }\n : m\n )\n );\n onResponse?.(\n messages.find((m) => m.id === result.messageId) ??\n assistantMessage\n );\n } else if (chunk.type === 'error') {\n setMessages((prev) =>\n prev.map((m) =>\n m.id === result.messageId\n ? {\n ...m,\n status: 'error',\n error: chunk.error,\n updatedAt: new Date(),\n }\n : m\n )\n );\n if (chunk.error) {\n const err = new Error(chunk.error.message);\n setError(err);\n onError?.(err);\n }\n }\n }\n } else {\n // Non-streaming mode\n const result = await chatServiceRef.current.send({\n conversationId: conversationId ?? undefined,\n content,\n attachments,\n });\n\n setConversation(result.conversation);\n setMessages(result.conversation.messages);\n\n if (!conversationId) {\n setConversationId(result.conversation.id);\n }\n\n onResponse?.(result.message);\n }\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n setError(error);\n onError?.(error);\n } finally {\n setIsLoading(false);\n abortControllerRef.current = null;\n }\n },\n [conversationId, streaming, onSend, onResponse, onError, messages]\n );\n\n const clearConversation = React.useCallback(() => {\n setMessages([]);\n setConversation(null);\n setConversationId(null);\n setError(null);\n }, []);\n\n const regenerate = React.useCallback(async () => {\n // Find the last user message\n const lastUserMessageIndex = messages.findLastIndex(\n (m) => m.role === 'user'\n );\n if (lastUserMessageIndex === -1) return;\n\n const lastUserMessage = messages[lastUserMessageIndex];\n if (!lastUserMessage) return;\n\n // Remove the last assistant message\n setMessages((prev) => prev.slice(0, lastUserMessageIndex + 1));\n\n // Resend\n await sendMessage(lastUserMessage.content, lastUserMessage.attachments);\n }, [messages, sendMessage]);\n\n const stop = React.useCallback(() => {\n abortControllerRef.current?.abort();\n setIsLoading(false);\n }, []);\n\n return {\n messages,\n conversation,\n isLoading,\n error,\n sendMessage,\n clearConversation,\n setConversationId,\n regenerate,\n stop,\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ import { ModelInfo, ProviderMode, ProviderName } from "@contractspec/lib.ai-providers";
2
+
3
+ //#region src/presentation/hooks/useProviders.d.ts
4
+
5
+ /**
6
+ * Provider availability info
7
+ */
8
+ interface ProviderInfo {
9
+ provider: ProviderName;
10
+ available: boolean;
11
+ mode: ProviderMode;
12
+ reason?: string;
13
+ models: ModelInfo[];
14
+ }
15
+ /**
16
+ * Return type for useProviders hook
17
+ */
18
+ interface UseProvidersReturn {
19
+ /** All providers with availability info */
20
+ providers: ProviderInfo[];
21
+ /** Available providers only */
22
+ availableProviders: ProviderInfo[];
23
+ /** Check if a provider is available */
24
+ isAvailable: (provider: ProviderName) => boolean;
25
+ /** Get models for a provider */
26
+ getModels: (provider: ProviderName) => ModelInfo[];
27
+ /** Loading state */
28
+ isLoading: boolean;
29
+ /** Refresh provider availability */
30
+ refresh: () => Promise<void>;
31
+ }
32
+ /**
33
+ * Hook for managing AI provider information
34
+ */
35
+ declare function useProviders(): UseProvidersReturn;
36
+ //#endregion
37
+ export { UseProvidersReturn, useProviders };
38
+ //# sourceMappingURL=useProviders.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useProviders.d.ts","names":[],"sources":["../../../src/presentation/hooks/useProviders.tsx"],"sourcesContent":[],"mappings":";;;;;;AAcA;AACY,UADK,YAAA,CACL;EAEJ,QAAA,EAFI,YAEJ;EAEE,SAAA,EAAA,OAAA;EAAS,IAAA,EAFX,YAEW;EAMF,MAAA,CAAA,EAAA,MAAA;EAEJ,MAAA,EARH,SAQG,EAAA;;;;;AAUI,UAZA,kBAAA,CAYA;EAAO;EAMR,SAAA,EAhBH,YAgBe,EAAA;;sBAdN;;0BAEI;;wBAEF,iBAAiB;;;;iBAIxB;;;;;iBAMD,YAAA,CAAA,GAAgB"}
@@ -0,0 +1,41 @@
1
+ 'use client';
2
+
3
+ import * as React from "react";
4
+ import { getAvailableProviders, getModelsForProvider } from "@contractspec/lib.ai-providers";
5
+
6
+ //#region src/presentation/hooks/useProviders.tsx
7
+ /**
8
+ * Hook for managing AI provider information
9
+ */
10
+ function useProviders() {
11
+ const [providers, setProviders] = React.useState([]);
12
+ const [isLoading, setIsLoading] = React.useState(true);
13
+ const loadProviders = React.useCallback(async () => {
14
+ setIsLoading(true);
15
+ try {
16
+ setProviders(getAvailableProviders().map((p) => ({
17
+ ...p,
18
+ models: getModelsForProvider(p.provider)
19
+ })));
20
+ } catch (error) {
21
+ console.error("Failed to load providers:", error);
22
+ } finally {
23
+ setIsLoading(false);
24
+ }
25
+ }, []);
26
+ React.useEffect(() => {
27
+ loadProviders();
28
+ }, [loadProviders]);
29
+ return {
30
+ providers,
31
+ availableProviders: React.useMemo(() => providers.filter((p) => p.available), [providers]),
32
+ isAvailable: React.useCallback((provider) => providers.some((p) => p.provider === provider && p.available), [providers]),
33
+ getModels: React.useCallback((provider) => providers.find((p) => p.provider === provider)?.models ?? [], [providers]),
34
+ isLoading,
35
+ refresh: loadProviders
36
+ };
37
+ }
38
+
39
+ //#endregion
40
+ export { useProviders };
41
+ //# sourceMappingURL=useProviders.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useProviders.js","names":[],"sources":["../../../src/presentation/hooks/useProviders.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport {\n type ProviderName,\n type ProviderMode,\n type ModelInfo,\n getAvailableProviders,\n getModelsForProvider,\n} from '@contractspec/lib.ai-providers';\n\n/**\n * Provider availability info\n */\nexport interface ProviderInfo {\n provider: ProviderName;\n available: boolean;\n mode: ProviderMode;\n reason?: string;\n models: ModelInfo[];\n}\n\n/**\n * Return type for useProviders hook\n */\nexport interface UseProvidersReturn {\n /** All providers with availability info */\n providers: ProviderInfo[];\n /** Available providers only */\n availableProviders: ProviderInfo[];\n /** Check if a provider is available */\n isAvailable: (provider: ProviderName) => boolean;\n /** Get models for a provider */\n getModels: (provider: ProviderName) => ModelInfo[];\n /** Loading state */\n isLoading: boolean;\n /** Refresh provider availability */\n refresh: () => Promise<void>;\n}\n\n/**\n * Hook for managing AI provider information\n */\nexport function useProviders(): UseProvidersReturn {\n const [providers, setProviders] = React.useState<ProviderInfo[]>([]);\n const [isLoading, setIsLoading] = React.useState(true);\n\n const loadProviders = React.useCallback(async () => {\n setIsLoading(true);\n try {\n const available = getAvailableProviders();\n const providersWithModels: ProviderInfo[] = available.map((p) => ({\n ...p,\n models: getModelsForProvider(p.provider),\n }));\n setProviders(providersWithModels);\n } catch (error) {\n console.error('Failed to load providers:', error);\n } finally {\n setIsLoading(false);\n }\n }, []);\n\n React.useEffect(() => {\n loadProviders();\n }, [loadProviders]);\n\n const availableProviders = React.useMemo(\n () => providers.filter((p) => p.available),\n [providers]\n );\n\n const isAvailable = React.useCallback(\n (provider: ProviderName) =>\n providers.some((p) => p.provider === provider && p.available),\n [providers]\n );\n\n const getModelsCallback = React.useCallback(\n (provider: ProviderName) =>\n providers.find((p) => p.provider === provider)?.models ?? [],\n [providers]\n );\n\n return {\n providers,\n availableProviders,\n isAvailable,\n getModels: getModelsCallback,\n isLoading,\n refresh: loadProviders,\n };\n}\n"],"mappings":";;;;;;;;;AA2CA,SAAgB,eAAmC;CACjD,MAAM,CAAC,WAAW,gBAAgB,MAAM,SAAyB,EAAE,CAAC;CACpE,MAAM,CAAC,WAAW,gBAAgB,MAAM,SAAS,KAAK;CAEtD,MAAM,gBAAgB,MAAM,YAAY,YAAY;AAClD,eAAa,KAAK;AAClB,MAAI;AAMF,gBALkB,uBAAuB,CACa,KAAK,OAAO;IAChE,GAAG;IACH,QAAQ,qBAAqB,EAAE,SAAS;IACzC,EAAE,CAC8B;WAC1B,OAAO;AACd,WAAQ,MAAM,6BAA6B,MAAM;YACzC;AACR,gBAAa,MAAM;;IAEpB,EAAE,CAAC;AAEN,OAAM,gBAAgB;AACpB,iBAAe;IACd,CAAC,cAAc,CAAC;AAmBnB,QAAO;EACL;EACA,oBAnByB,MAAM,cACzB,UAAU,QAAQ,MAAM,EAAE,UAAU,EAC1C,CAAC,UAAU,CACZ;EAiBC,aAfkB,MAAM,aACvB,aACC,UAAU,MAAM,MAAM,EAAE,aAAa,YAAY,EAAE,UAAU,EAC/D,CAAC,UAAU,CACZ;EAYC,WAVwB,MAAM,aAC7B,aACC,UAAU,MAAM,MAAM,EAAE,aAAa,SAAS,EAAE,UAAU,EAAE,EAC9D,CAAC,UAAU,CACZ;EAOC;EACA,SAAS;EACV"}
@@ -0,0 +1,11 @@
1
+ import { ChatContainer } from "./components/ChatContainer.js";
2
+ import { ChatMessage } from "./components/ChatMessage.js";
3
+ import { ChatInput } from "./components/ChatInput.js";
4
+ import { ModelPicker } from "./components/ModelPicker.js";
5
+ import { ContextIndicator } from "./components/ContextIndicator.js";
6
+ import { CodePreview } from "./components/CodePreview.js";
7
+ import "./components/index.js";
8
+ import { UseChatOptions, UseChatReturn, useChat } from "./hooks/useChat.js";
9
+ import { UseProvidersReturn, useProviders } from "./hooks/useProviders.js";
10
+ import "./hooks/index.js";
11
+ export { ChatContainer, ChatInput, ChatMessage, CodePreview, ContextIndicator, ModelPicker, UseChatOptions, UseChatReturn, UseProvidersReturn, useChat, useProviders };
@@ -0,0 +1,12 @@
1
+ import { ChatContainer } from "./components/ChatContainer.js";
2
+ import { CodePreview } from "./components/CodePreview.js";
3
+ import { ChatMessage } from "./components/ChatMessage.js";
4
+ import { ChatInput } from "./components/ChatInput.js";
5
+ import { ModelPicker } from "./components/ModelPicker.js";
6
+ import { ContextIndicator } from "./components/ContextIndicator.js";
7
+ import "./components/index.js";
8
+ import { useChat } from "./hooks/useChat.js";
9
+ import { useProviders } from "./hooks/useProviders.js";
10
+ import "./hooks/index.js";
11
+
12
+ export { ChatContainer, ChatInput, ChatMessage, CodePreview, ContextIndicator, ModelPicker, useChat, useProviders };