@lssm/module.ai-chat 0.0.0-canary-20251217023603
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/README.md +166 -0
- package/dist/ai-chat.feature.js +1 -0
- package/dist/context/context-builder.js +2 -0
- package/dist/context/file-operations.js +1 -0
- package/dist/context/index.js +1 -0
- package/dist/context/workspace-context.js +2 -0
- package/dist/core/chat-service.js +14 -0
- package/dist/core/conversation-store.js +1 -0
- package/dist/core/index.js +1 -0
- package/dist/index.js +1 -0
- package/dist/presentation/components/ChatContainer.js +1 -0
- package/dist/presentation/components/ChatInput.js +1 -0
- package/dist/presentation/components/ChatMessage.js +1 -0
- package/dist/presentation/components/CodePreview.js +2 -0
- package/dist/presentation/components/ContextIndicator.js +1 -0
- package/dist/presentation/components/ModelPicker.js +1 -0
- package/dist/presentation/components/index.js +1 -0
- package/dist/presentation/hooks/index.js +1 -0
- package/dist/presentation/hooks/useChat.js +1 -0
- package/dist/presentation/hooks/useProviders.js +1 -0
- package/dist/presentation/index.js +1 -0
- package/dist/providers/chat-utilities.js +1 -0
- package/dist/providers/index.js +1 -0
- package/package.json +71 -0
package/README.md
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# @lssm/module.ai-chat
|
|
2
|
+
|
|
3
|
+
**ContractSpec Vibe Coding Chat** — AI-powered conversational coding assistant for ContractSpec.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
This module provides a reusable AI chat system that can be integrated into CLI, VSCode extension, and ContractSpec Studio. It supports multiple LLM providers with full workspace context for vibe coding.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **Multi-Provider Support**: OpenAI, Anthropic, Mistral, Google Gemini, and local Ollama
|
|
12
|
+
- **Three Provider Modes**: Local (Ollama), BYOK (Bring Your Own Key), Managed (API proxy)
|
|
13
|
+
- **Full Workspace Context**: Access specs, files, and codebase for context-aware assistance
|
|
14
|
+
- **Streaming Responses**: Real-time token streaming for responsive UX
|
|
15
|
+
- **Usage Tracking**: Integrated metering and cost tracking
|
|
16
|
+
- **UI Components**: React components for chat interfaces
|
|
17
|
+
|
|
18
|
+
## Related Packages
|
|
19
|
+
|
|
20
|
+
- `@lssm/lib.ai-providers` — Shared provider abstraction (types, factory, validation)
|
|
21
|
+
- `@lssm/lib.ai-agent` — Agent orchestration and tool execution
|
|
22
|
+
|
|
23
|
+
## Providers
|
|
24
|
+
|
|
25
|
+
| Provider | Local | BYOK | Managed |
|
|
26
|
+
|----------|-------|------|---------|
|
|
27
|
+
| Ollama | ✅ | - | - |
|
|
28
|
+
| OpenAI | - | ✅ | ✅ |
|
|
29
|
+
| Anthropic | - | ✅ | ✅ |
|
|
30
|
+
| Mistral | - | ✅ | ✅ |
|
|
31
|
+
| Google Gemini | - | ✅ | ✅ |
|
|
32
|
+
|
|
33
|
+
## Usage
|
|
34
|
+
|
|
35
|
+
### Basic Chat
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
import { createProvider } from '@lssm/lib.ai-providers';
|
|
39
|
+
import { ChatService } from '@lssm/module.ai-chat';
|
|
40
|
+
|
|
41
|
+
const provider = createProvider({
|
|
42
|
+
provider: 'openai',
|
|
43
|
+
apiKey: process.env.OPENAI_API_KEY,
|
|
44
|
+
model: 'gpt-4o',
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const chatService = new ChatService({ provider });
|
|
48
|
+
|
|
49
|
+
const response = await chatService.send({
|
|
50
|
+
content: 'Help me create a new API endpoint',
|
|
51
|
+
});
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### With Workspace Context
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
import { createProvider } from '@lssm/lib.ai-providers';
|
|
58
|
+
import { ChatService, WorkspaceContext } from '@lssm/module.ai-chat';
|
|
59
|
+
|
|
60
|
+
const context = await WorkspaceContext.fromPath('/path/to/project');
|
|
61
|
+
const provider = createProvider({ provider: 'anthropic', proxyUrl: '/api/chat' });
|
|
62
|
+
|
|
63
|
+
const chatService = new ChatService({ provider, context });
|
|
64
|
+
|
|
65
|
+
// The chat now has access to specs, files, and can suggest code changes
|
|
66
|
+
const response = await chatService.send({
|
|
67
|
+
content: 'Add validation to the user.create command',
|
|
68
|
+
});
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### React Components
|
|
72
|
+
|
|
73
|
+
```tsx
|
|
74
|
+
import { ChatContainer, ChatMessage, ChatInput } from '@lssm/module.ai-chat/presentation/components';
|
|
75
|
+
import { useChat } from '@lssm/module.ai-chat/presentation/hooks';
|
|
76
|
+
|
|
77
|
+
function VibeCodingChat() {
|
|
78
|
+
const { messages, sendMessage, isLoading } = useChat({
|
|
79
|
+
provider: 'openai',
|
|
80
|
+
mode: 'managed',
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<ChatContainer>
|
|
85
|
+
{messages.map((msg) => (
|
|
86
|
+
<ChatMessage key={msg.id} message={msg} />
|
|
87
|
+
))}
|
|
88
|
+
<ChatInput onSend={sendMessage} disabled={isLoading} />
|
|
89
|
+
</ChatContainer>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Architecture
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
98
|
+
│ Consumer Surfaces │
|
|
99
|
+
│ ┌─────────┐ ┌──────────────┐ ┌─────────────────┐ │
|
|
100
|
+
│ │ CLI │ │ VSCode │ │ Studio │ │
|
|
101
|
+
│ └────┬────┘ └──────┬───────┘ └────────┬────────┘ │
|
|
102
|
+
└───────┼────────────────┼─────────────────────┼─────────────┘
|
|
103
|
+
│ │ │
|
|
104
|
+
▼ ▼ ▼
|
|
105
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
106
|
+
│ @lssm/module.ai-chat │
|
|
107
|
+
│ ┌────────────┐ ┌──────────────┐ ┌───────────────────┐ │
|
|
108
|
+
│ │ ChatService│ │ Providers │ │ Workspace Context │ │
|
|
109
|
+
│ │ │ │ (re-exports) │ │ │ │
|
|
110
|
+
│ └────────────┘ └──────┬───────┘ └───────────────────┘ │
|
|
111
|
+
│ │ │
|
|
112
|
+
│ ┌──────────────────────┼───────────────────────────────┐ │
|
|
113
|
+
│ │ UI Components (React) │ │
|
|
114
|
+
│ └──────────────────────────────────────────────────────┘ │
|
|
115
|
+
└─────────────────────────┼───────────────────────────────────┘
|
|
116
|
+
│
|
|
117
|
+
┌─────────────────┴─────────────────┐
|
|
118
|
+
▼ ▼
|
|
119
|
+
┌───────────────────┐ ┌──────────────────────────┐
|
|
120
|
+
│ @lssm/lib. │ │ @lssm/lib.ai-agent │
|
|
121
|
+
│ ai-providers │ │ │
|
|
122
|
+
│ ┌───────────────┐ │ │ Agent orchestration, │
|
|
123
|
+
│ │ Provider │ │ │ tool execution, │
|
|
124
|
+
│ │ abstraction │ │ │ memory framework │
|
|
125
|
+
│ └───────────────┘ │ └──────────────────────────┘
|
|
126
|
+
│ ┌───────────────┐ │
|
|
127
|
+
│ │ Model info │ │
|
|
128
|
+
│ │ & validation │ │
|
|
129
|
+
│ └───────────────┘ │
|
|
130
|
+
└───────────────────┘
|
|
131
|
+
│
|
|
132
|
+
▼
|
|
133
|
+
┌───────────────────┐ ┌──────────────────────────┐
|
|
134
|
+
│ Local Providers │ │ API Proxy │
|
|
135
|
+
│ ┌──────────────┐ │ │ ┌──────────────────────┐│
|
|
136
|
+
│ │ Ollama │ │ │ │ Metering + Costing ││
|
|
137
|
+
│ └──────────────┘ │ │ └──────────────────────┘│
|
|
138
|
+
└───────────────────┘ │ ┌──────────────────────┐│
|
|
139
|
+
│ │ Cloud Providers ││
|
|
140
|
+
│ │ OpenAI/Anthropic/etc ││
|
|
141
|
+
│ └──────────────────────┘│
|
|
142
|
+
└──────────────────────────┘
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Metrics
|
|
146
|
+
|
|
147
|
+
The module tracks the following metrics via `@lssm/lib.metering`:
|
|
148
|
+
|
|
149
|
+
| Metric | Description |
|
|
150
|
+
|--------|-------------|
|
|
151
|
+
| `ai_chat.messages` | Total chat messages sent |
|
|
152
|
+
| `ai_chat.tokens_input` | Input tokens consumed |
|
|
153
|
+
| `ai_chat.tokens_output` | Output tokens generated |
|
|
154
|
+
| `ai_chat.latency_ms` | Response latency |
|
|
155
|
+
| `ai_chat.errors` | Failed completions |
|
|
156
|
+
|
|
157
|
+
## Feature Flags
|
|
158
|
+
|
|
159
|
+
- `ai_chat.enabled` - Master toggle for the chat feature
|
|
160
|
+
- `ai_chat.managed_keys` - Enable managed key mode (API proxy)
|
|
161
|
+
- `ai_chat.workspace_context` - Enable workspace read/write access
|
|
162
|
+
- `ai_chat.code_execution` - Enable code execution (future)
|
|
163
|
+
|
|
164
|
+
## License
|
|
165
|
+
|
|
166
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e={meta:{key:`ai-chat`,title:`AI Vibe Coding Chat`,description:`AI-powered conversational coding assistant with full workspace context`,domain:`platform`,owners:[`@platform.ai`],tags:[`ai`,`chat`,`llm`,`vibe-coding`,`assistant`],stability:`experimental`},operations:[{name:`ai-chat.send`,version:1},{name:`ai-chat.stream`,version:1},{name:`ai-chat.conversations.list`,version:1},{name:`ai-chat.conversations.get`,version:1},{name:`ai-chat.conversations.delete`,version:1},{name:`ai-chat.providers.list`,version:1},{name:`ai-chat.context.scan`,version:1}],events:[{name:`ai-chat.message.sent`,version:1},{name:`ai-chat.message.received`,version:1},{name:`ai-chat.conversation.created`,version:1},{name:`ai-chat.conversation.deleted`,version:1},{name:`ai-chat.error`,version:1}],presentations:[],opToPresentation:[],presentationsTargets:[],capabilities:{provides:[{key:`ai-chat`,version:1}],requires:[{key:`identity`,version:1},{key:`metering`,version:1}]}};export{e as AiChatFeature};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
function e(e){return Math.ceil(e.length/4)}function t(e,t){if(!t)return .5;let n=t.toLowerCase(),r=0;return e.name.toLowerCase().includes(n)&&(r+=.4),e.description?.toLowerCase().includes(n)&&(r+=.3),e.tags?.some(e=>e.toLowerCase().includes(n))&&(r+=.2),Math.min(r,1)}function n(e,t){if(!t)return .5;let n=t.toLowerCase(),r=0;return e.path.toLowerCase().includes(n)&&(r+=.5),e.name.toLowerCase().includes(n)&&(r+=.3),e.isSpec&&(r+=.2),Math.min(r,1)}var r=class{context;constructor(e){this.context=e}build(r={}){let i=r.maxTokens??4e3,a=[],o=0;if(r.includeSpecs?.length)for(let t of r.includeSpecs){let n=this.context.getSpecs().find(e=>e.name===t);if(n){let t={type:`spec`,path:n.path,summary:`${n.type}: ${n.name}${n.description?` - ${n.description}`:``}`,relevance:1};a.push(t),o+=e(t.summary??``)}}if(r.includeFiles?.length)for(let t of r.includeFiles){let n=this.context.getFiles().find(e=>e.path===t);if(n){let t={type:`file`,path:n.path,summary:`File: ${n.relativePath}`,relevance:1};a.push(t),o+=e(t.summary??``)}}if(r.query){let n=this.context.getSpecs().map(e=>({spec:e,score:t(e,r.query)})).filter(({score:e})=>e>.2).sort((e,t)=>t.score-e.score);for(let{spec:t,score:r}of n){if(o>=i)break;if(a.some(e=>e.path===t.path))continue;let n={type:`spec`,path:t.path,summary:`${t.type}: ${t.name}${t.description?` - ${t.description}`:``}`,relevance:r};a.push(n),o+=e(n.summary??``)}}if(r.query){let t=this.context.getFiles().map(e=>({file:e,score:n(e,r.query)})).filter(({score:e})=>e>.2).sort((e,t)=>t.score-e.score);for(let{file:n,score:r}of t){if(o>=i)break;if(a.some(e=>e.path===n.path))continue;let t={type:`file`,path:n.path,summary:`File: ${n.relativePath}`,relevance:r};a.push(t),o+=e(t.summary??``)}}let s=this.buildSummary(a);return{entries:a,summary:s,totalTokensEstimate:o+e(s)}}buildSummary(e){if(e.length===0)return this.context.getContextSummary();let t=[],n=this.context.getSummary();t.push(`Workspace: ${n.name}`),t.push(``);let r=e.filter(e=>e.type===`spec`);if(r.length>0){t.push(`### Relevant Specs`);for(let e of r)t.push(`- ${e.summary}`);t.push(``)}let i=e.filter(e=>e.type===`file`);if(i.length>0){t.push(`### Relevant Files`);for(let e of i)t.push(`- ${e.summary}`)}return t.join(`
|
|
2
|
+
`)}};function i(e){return new r(e)}export{r as ContextBuilder,i as createContextBuilder};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var e=class{constructor(e,t,n=!1){this.fs=e,this.workspacePath=t,this.allowWrites=n}async read(e){let t=this.resolvePath(e);try{return{success:!0,path:e,content:await this.fs.readFile(t)}}catch(t){return{success:!1,path:e,error:t instanceof Error?t.message:String(t)}}}async write(e,t){if(!this.allowWrites)return{success:!1,path:e,error:`File writes are not enabled`};let n=this.resolvePath(e);try{return await this.fs.writeFile(n,t),{success:!0,path:e}}catch(t){return{success:!1,path:e,error:t instanceof Error?t.message:String(t)}}}async execute(e){let t=[];for(let n of e){let e;switch(n.type){case`read`:{let t=await this.read(n.path);e={operation:n,success:t.success,content:t.content,error:t.error};break}case`write`:case`create`:if(!n.content)e={operation:n,success:!1,error:`Content is required for write operations`};else{let t=await this.write(n.path,n.content);e={operation:n,success:t.success,error:t.error}}break;case`delete`:if(!this.allowWrites)e={operation:n,success:!1,error:`File writes are not enabled`};else try{await this.fs.deleteFile(this.resolvePath(n.path)),e={operation:n,success:!0}}catch(t){e={operation:n,success:!1,error:t instanceof Error?t.message:String(t)}}break;default:e={operation:n,success:!1,error:`Unknown operation type: ${n.type}`}}t.push(e)}return t}resolvePath(e){let t=e.replace(/\.\./g,``).replace(/^\//,``);return`${this.workspacePath}/${t}`}};function t(t,n=!1){return new e({async readFile(e){let{readFile:t}=await import(`node:fs/promises`);return t(e,`utf-8`)},async writeFile(e,t){let{writeFile:n,mkdir:r}=await import(`node:fs/promises`),{dirname:i}=await import(`node:path`);await r(i(e),{recursive:!0}),await n(e,t,`utf-8`)},async exists(e){let{access:t}=await import(`node:fs/promises`);try{return await t(e),!0}catch{return!1}},async deleteFile(e){let{unlink:t}=await import(`node:fs/promises`);await t(e)},async listFiles(e,t){let{readdir:n}=await import(`node:fs/promises`),{join:r}=await import(`node:path`),i=[],a=await n(e,{withFileTypes:!0});for(let n of a){let a=r(e,n.name);if(n.isDirectory()&&t?.recursive){let e=await this.listFiles(a,t);i.push(...e)}else n.isFile()&&(!t?.pattern||n.name.match(new RegExp(t.pattern)))&&i.push(a)}return i}},t,n)}export{e as FileOperations,t as createNodeFileOperations};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{WorkspaceContext as e,createWorkspaceContext as t}from"./workspace-context.js";import{ContextBuilder as n,createContextBuilder as r}from"./context-builder.js";import{FileOperations as i,createNodeFileOperations as a}from"./file-operations.js";export{n as ContextBuilder,i as FileOperations,e as WorkspaceContext,r as createContextBuilder,a as createNodeFileOperations,t as createWorkspaceContext};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var e=class{workspacePath;allowWrites;specs=[];files=[];initialized=!1;constructor(e){this.workspacePath=e.workspacePath,this.allowWrites=e.allowWrites??!1}async initialize(){this.initialized||=!0}getSpecs(){return this.specs}getFiles(){return this.files}addSpecs(e){this.specs.push(...e)}addFiles(e){this.files.push(...e)}getSummary(){let e=this.specs.filter(e=>e.type===`command`).length,t=this.specs.filter(e=>e.type===`query`).length,n=this.specs.filter(e=>e.type===`event`).length,r=this.specs.filter(e=>e.type===`presentation`).length,i=this.files.filter(e=>e.extension===`.ts`).length,a=this.files.filter(e=>e.isSpec).length;return{name:this.workspacePath.split(`/`).pop()??`workspace`,path:this.workspacePath,specs:{total:this.specs.length,commands:e,queries:t,events:n,presentations:r},files:{total:this.files.length,typescript:i,specFiles:a}}}getContextSummary(){let e=this.getSummary(),t=[`Workspace: ${e.name}`,`Path: ${e.path}`,``,`### Specs`,`- Commands: ${e.specs.commands}`,`- Queries: ${e.specs.queries}`,`- Events: ${e.specs.events}`,`- Presentations: ${e.specs.presentations}`];if(this.specs.length>0){t.push(``,`### Available Specs`);for(let e of this.specs.slice(0,20))t.push(`- ${e.name} (${e.type})`);this.specs.length>20&&t.push(`- ... and ${this.specs.length-20} more`)}return t.join(`
|
|
2
|
+
`)}findSpecs(e){let t=e.toLowerCase();return this.specs.filter(e=>e.name.toLowerCase().includes(t)||e.description?.toLowerCase().includes(t)||e.tags?.some(e=>e.toLowerCase().includes(t)))}findFiles(e){let t=e.toLowerCase();return this.files.filter(e=>e.path.toLowerCase().includes(t)||e.name.toLowerCase().includes(t))}};async function t(t,n){let r=new e({workspacePath:t,...n});return await r.initialize(),r}export{e as WorkspaceContext,t as createWorkspaceContext};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import{InMemoryConversationStore as e}from"./conversation-store.js";import{generateText as t,streamText as n}from"ai";var r=class{provider;context;store;systemPrompt;maxHistoryMessages;onUsage;constructor(t){this.provider=t.provider,this.context=t.context,this.store=t.store??new e,this.systemPrompt=t.systemPrompt??`You are ContractSpec AI, an expert coding assistant specialized in ContractSpec development.
|
|
2
|
+
|
|
3
|
+
Your capabilities:
|
|
4
|
+
- Help users create, modify, and understand ContractSpec specifications
|
|
5
|
+
- Generate code that follows ContractSpec patterns and best practices
|
|
6
|
+
- Explain concepts from the ContractSpec documentation
|
|
7
|
+
- Suggest improvements and identify issues in specs and implementations
|
|
8
|
+
|
|
9
|
+
Guidelines:
|
|
10
|
+
- Be concise but thorough
|
|
11
|
+
- Provide code examples when helpful
|
|
12
|
+
- Reference relevant ContractSpec concepts and patterns
|
|
13
|
+
- Ask clarifying questions when the user's intent is unclear
|
|
14
|
+
- When suggesting code changes, explain the rationale`,this.maxHistoryMessages=t.maxHistoryMessages??20,this.onUsage=t.onUsage}async send(e){let n;if(e.conversationId){let t=await this.store.get(e.conversationId);if(!t)throw Error(`Conversation ${e.conversationId} not found`);n=t}else n=await this.store.create({status:`active`,provider:this.provider.name,model:this.provider.model,messages:[],workspacePath:this.context?.workspacePath});await this.store.appendMessage(n.id,{role:`user`,content:e.content,status:`completed`,attachments:e.attachments});let r=this.buildPrompt(n,e),i=this.provider.getModel();try{let e=await t({model:i,prompt:r,system:this.systemPrompt}),a=await this.store.appendMessage(n.id,{role:`assistant`,content:e.text,status:`completed`}),o=await this.store.get(n.id);if(!o)throw Error(`Conversation lost after update`);return{message:a,conversation:o}}catch(e){throw await this.store.appendMessage(n.id,{role:`assistant`,content:``,status:`error`,error:{code:`generation_failed`,message:e instanceof Error?e.message:String(e)}}),e}}async stream(e){let t;if(e.conversationId){let n=await this.store.get(e.conversationId);if(!n)throw Error(`Conversation ${e.conversationId} not found`);t=n}else t=await this.store.create({status:`active`,provider:this.provider.name,model:this.provider.model,messages:[],workspacePath:this.context?.workspacePath});await this.store.appendMessage(t.id,{role:`user`,content:e.content,status:`completed`,attachments:e.attachments});let r=await this.store.appendMessage(t.id,{role:`assistant`,content:``,status:`streaming`}),i=this.buildPrompt(t,e),a=this.provider.getModel(),o=this;async function*s(){let e=``;try{let s=n({model:a,prompt:i,system:o.systemPrompt});for await(let t of s.textStream)e+=t,yield{type:`text`,content:t};await o.store.updateMessage(t.id,r.id,{content:e,status:`completed`}),yield{type:`done`}}catch(n){await o.store.updateMessage(t.id,r.id,{content:e,status:`error`,error:{code:`stream_failed`,message:n instanceof Error?n.message:String(n)}}),yield{type:`error`,error:{code:`stream_failed`,message:n instanceof Error?n.message:String(n)}}}}return{conversationId:t.id,messageId:r.id,stream:s()}}async getConversation(e){return this.store.get(e)}async listConversations(e){return this.store.list({status:`active`,...e})}async deleteConversation(e){return this.store.delete(e)}buildPrompt(e,t){let n=``,r=Math.max(0,e.messages.length-this.maxHistoryMessages);for(let t=r;t<e.messages.length;t++){let r=e.messages[t];r&&(r.role===`user`||r.role===`assistant`)&&(n+=`${r.role===`user`?`User`:`Assistant`}: ${r.content}\n\n`)}let i=t.content;if(t.attachments?.length){let e=t.attachments.map(e=>e.type===`file`||e.type===`code`?`\n\n### ${e.name}\n\`\`\`\n${e.content}\n\`\`\``:`\n\n[Attachment: ${e.name}]`).join(``);i+=e}return n+=`User: ${i}\n\nAssistant:`,n}};function i(e){return new r(e)}export{r as ChatService,i as createChatService};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function e(e){return`${e}_${Date.now()}_${Math.random().toString(36).slice(2,11)}`}var t=class{conversations=new Map;async get(e){return this.conversations.get(e)??null}async create(t){let n=new Date,r={...t,id:e(`conv`),createdAt:n,updatedAt:n};return this.conversations.set(r.id,r),r}async update(e,t){let n=this.conversations.get(e);if(!n)return null;let r={...n,...t,updatedAt:new Date};return this.conversations.set(e,r),r}async appendMessage(t,n){let r=this.conversations.get(t);if(!r)throw Error(`Conversation ${t} not found`);let i=new Date,a={...n,id:e(`msg`),conversationId:t,createdAt:i,updatedAt:i};return r.messages.push(a),r.updatedAt=i,a}async updateMessage(e,t,n){let r=this.conversations.get(e);if(!r)return null;let i=r.messages.findIndex(e=>e.id===t);if(i===-1)return null;let a=r.messages[i];if(!a)return null;let o={...a,...n,updatedAt:new Date};return r.messages[i]=o,r.updatedAt=new Date,o}async delete(e){return this.conversations.delete(e)}async list(e){let t=Array.from(this.conversations.values());e?.status&&(t=t.filter(t=>t.status===e.status)),t.sort((e,t)=>t.updatedAt.getTime()-e.updatedAt.getTime());let n=e?.offset??0,r=e?.limit??100;return t.slice(n,n+r)}async search(e,t=20){let n=e.toLowerCase(),r=[];for(let e of this.conversations.values()){if(e.title?.toLowerCase().includes(n)){r.push(e);continue}if(e.messages.some(e=>e.content.toLowerCase().includes(n))&&r.push(e),r.length>=t)break}return r}clear(){this.conversations.clear()}};function n(){return new t}export{t as InMemoryConversationStore,n as createInMemoryConversationStore};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{InMemoryConversationStore as e,createInMemoryConversationStore as t}from"./conversation-store.js";import{ChatService as n,createChatService as r}from"./chat-service.js";export{n as ChatService,e as InMemoryConversationStore,r as createChatService,t as createInMemoryConversationStore};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{AiChatFeature as e}from"./ai-chat.feature.js";import{InMemoryConversationStore as t,createInMemoryConversationStore as n}from"./core/conversation-store.js";import{ChatService as r,createChatService as i}from"./core/chat-service.js";import"./core/index.js";import{isStudioAvailable as a,supportsLocalMode as o}from"./providers/chat-utilities.js";import{DEFAULT_MODELS as s,MODELS as c,createProvider as l,createProviderFromEnv as u,getAvailableProviders as d,getDefaultModel as f,getEnvVarName as p,getModelInfo as m,getModelsForProvider as h,getRecommendedModels as g,hasCredentials as _,isOllamaRunning as v,listOllamaModels as y,validateProvider as b}from"./providers/index.js";import{WorkspaceContext as x,createWorkspaceContext as S}from"./context/workspace-context.js";import{ContextBuilder as C,createContextBuilder as w}from"./context/context-builder.js";import{FileOperations as T,createNodeFileOperations as E}from"./context/file-operations.js";import"./context/index.js";import{ChatContainer as D}from"./presentation/components/ChatContainer.js";import{ChatMessage as O}from"./presentation/components/ChatMessage.js";import{ChatInput as k}from"./presentation/components/ChatInput.js";import"./presentation/components/index.js";import{useChat as A}from"./presentation/hooks/useChat.js";import{useProviders as j}from"./presentation/hooks/useProviders.js";import"./presentation/hooks/index.js";export{e as AiChatFeature,D as ChatContainer,k as ChatInput,O as ChatMessageComponent,r as ChatService,C as ContextBuilder,s as DEFAULT_MODELS,T as FileOperations,t as InMemoryConversationStore,c as MODELS,x as WorkspaceContext,i as createChatService,w as createContextBuilder,n as createInMemoryConversationStore,E as createNodeFileOperations,l as createProvider,u as createProviderFromEnv,S as createWorkspaceContext,d as getAvailableProviders,f as getDefaultModel,p as getEnvVarName,m as getModelInfo,h as getModelsForProvider,g as getRecommendedModels,_ as hasCredentials,v as isOllamaRunning,a as isStudioAvailable,y as listOllamaModels,o as supportsLocalMode,A as useChat,j as useProviders,b as validateProvider};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use client";import*as e from"react";import{ScrollArea as t}from"@lssm/lib.ui-kit-web/ui/scroll-area";import{cn as n}from"@lssm/lib.ui-kit-web/ui/utils";import{jsx as r,jsxs as i}from"react/jsx-runtime";function a({children:a,className:o,showScrollButton:s=!0}){let c=e.useRef(null),[l,u]=e.useState(!1);e.useEffect(()=>{let e=c.current;e&&e.scrollHeight-e.scrollTop<=e.clientHeight+100&&(e.scrollTop=e.scrollHeight)},[a]);let d=e.useCallback(e=>{let t=e.currentTarget;u(!(t.scrollHeight-t.scrollTop<=t.clientHeight+100))},[]),f=e.useCallback(()=>{let e=c.current;e&&e.scrollTo({top:e.scrollHeight,behavior:`smooth`})},[]);return i(`div`,{className:n(`relative flex flex-1 flex-col`,o),children:[r(t,{ref:c,className:`flex-1`,onScroll:d,children:r(`div`,{className:`flex flex-col gap-4 p-4`,children:a})}),s&&l&&i(`button`,{onClick:f,className:n(`absolute bottom-4 left-1/2 -translate-x-1/2`,`bg-primary text-primary-foreground`,`rounded-full px-3 py-1.5 text-sm font-medium shadow-lg`,`hover:bg-primary/90 transition-colors`,`flex items-center gap-1.5`),"aria-label":`Scroll to bottom`,children:[r(`svg`,{xmlns:`http://www.w3.org/2000/svg`,width:`16`,height:`16`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2`,strokeLinecap:`round`,strokeLinejoin:`round`,children:r(`path`,{d:`m6 9 6 6 6-6`})}),`New messages`]})]})}export{a as ChatContainer};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use client";import*as e from"react";import{cn as t}from"@lssm/lib.ui-kit-web/ui/utils";import{Fragment as n,jsx as r,jsxs as i}from"react/jsx-runtime";import{Code as a,FileText as o,Loader2 as s,Paperclip as c,Send as l,X as u}from"lucide-react";import{Button as d,Textarea as f}from"@lssm/lib.design-system";function p({onSend:p,disabled:m=!1,isLoading:h=!1,placeholder:g=`Type a message...`,className:_,showAttachments:v=!0,maxAttachments:y=5}){let[b,x]=e.useState(``),[S,C]=e.useState([]),w=e.useRef(null),T=e.useRef(null),E=b.trim().length>0||S.length>0,D=e.useCallback(e=>{e?.preventDefault(),!(!E||m||h)&&(p(b.trim(),S.length>0?S:void 0),x(``),C([]),w.current?.focus())},[E,b,S,m,h,p]),O=e.useCallback(e=>{e.key===`Enter`&&!e.shiftKey&&(e.preventDefault(),D())},[D]),k=e.useCallback(async e=>{let t=e.target.files;if(!t)return;let n=[];for(let e of Array.from(t)){if(S.length+n.length>=y)break;let t=await e.text(),r=e.name.split(`.`).pop()?.toLowerCase()??``,i=[`ts`,`tsx`,`js`,`jsx`,`py`,`go`,`rs`,`java`].includes(r);n.push({id:`att_${Date.now()}_${Math.random().toString(36).slice(2,9)}`,type:i?`code`:`file`,name:e.name,content:t,mimeType:e.type,size:e.size})}C(e=>[...e,...n]),e.target.value=``},[S.length,y]),A=e.useCallback(e=>{C(t=>t.filter(t=>t.id!==e))},[]);return i(`div`,{className:t(`flex flex-col gap-2`,_),children:[S.length>0&&r(`div`,{className:`flex flex-wrap gap-2`,children:S.map(e=>i(`div`,{className:t(`flex items-center gap-1.5 rounded-md px-2 py-1`,`bg-muted text-muted-foreground text-sm`),children:[e.type===`code`?r(a,{className:`h-3.5 w-3.5`}):r(o,{className:`h-3.5 w-3.5`}),r(`span`,{className:`max-w-[150px] truncate`,children:e.name}),r(`button`,{type:`button`,onClick:()=>A(e.id),className:`hover:text-foreground`,"aria-label":`Remove ${e.name}`,children:r(u,{className:`h-3.5 w-3.5`})})]},e.id))}),i(`form`,{onSubmit:D,className:`flex items-end gap-2`,children:[v&&i(n,{children:[r(`input`,{ref:T,type:`file`,multiple:!0,accept:`.ts,.tsx,.js,.jsx,.json,.md,.txt,.py,.go,.rs,.java,.yaml,.yml`,onChange:k,className:`hidden`,"aria-label":`Attach files`}),r(d,{type:`button`,variant:`ghost`,size:`sm`,onPress:()=>T.current?.click(),disabled:m||S.length>=y,"aria-label":`Attach files`,children:r(c,{className:`h-4 w-4`})})]}),r(`div`,{className:`relative flex-1`,children:r(f,{value:b,onChange:e=>x(e.target.value),onKeyDown:O,placeholder:g,disabled:m,className:t(`min-h-[44px] max-h-[200px] resize-none pr-12`,`focus-visible:ring-1`),rows:1,"aria-label":`Chat message`})}),r(d,{type:`submit`,disabled:!E||m||h,size:`sm`,"aria-label":h?`Sending...`:`Send message`,children:h?r(s,{className:`h-4 w-4 animate-spin`}):r(l,{className:`h-4 w-4`})})]}),r(`p`,{className:`text-muted-foreground text-xs`,children:`Press Enter to send, Shift+Enter for new line`})]})}export{p as ChatInput};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use client";import{CodePreview as e}from"./CodePreview.js";import*as t from"react";import{cn as n}from"@lssm/lib.ui-kit-web/ui/utils";import{Fragment as r,jsx as i,jsxs as a}from"react/jsx-runtime";import{Avatar as o,AvatarFallback as s}from"@lssm/lib.ui-kit-web/ui/avatar";import{Skeleton as c}from"@lssm/lib.ui-kit-web/ui/skeleton";import{AlertCircle as l,Bot as u,Check as d,Copy as f,User as p}from"lucide-react";import{Button as m}from"@lssm/lib.design-system";function h(e){let t=/```(\w+)?\n([\s\S]*?)```/g,n=[],r;for(;(r=t.exec(e))!==null;)n.push({language:r[1]??`text`,code:r[2]??``,raw:r[0]});return n}function g({content:t}){let n=h(t);if(n.length===0)return i(`p`,{className:`whitespace-pre-wrap`,children:t});let a=t,o=[],s=0;for(let t of n){let[n,r]=a.split(t.raw);n&&o.push(i(`p`,{className:`whitespace-pre-wrap`,children:n.trim()},s++)),o.push(i(e,{code:t.code,language:t.language,className:`my-2`},s++)),a=r??``}return a.trim()&&o.push(i(`p`,{className:`whitespace-pre-wrap`,children:a.trim()},s++)),i(r,{children:o})}function _({message:e,className:r,showCopy:h=!0,showAvatar:_=!0}){let[v,y]=t.useState(!1),b=e.role===`user`,x=e.status===`error`,S=e.status===`streaming`,C=t.useCallback(async()=>{await navigator.clipboard.writeText(e.content),y(!0),setTimeout(()=>y(!1),2e3)},[e.content]);return a(`div`,{className:n(`group flex gap-3`,b&&`flex-row-reverse`,r),children:[_&&i(o,{className:`h-8 w-8 shrink-0`,children:i(s,{className:n(b?`bg-primary text-primary-foreground`:`bg-muted`),children:i(b?p:u,{className:`h-4 w-4`})})}),a(`div`,{className:n(`flex max-w-[80%] flex-col gap-1`,b&&`items-end`),children:[i(`div`,{className:n(`rounded-2xl px-4 py-2`,b?`bg-primary text-primary-foreground`:`bg-muted text-foreground`,x&&`border-destructive bg-destructive/10 border`),children:x&&e.error?a(`div`,{className:`flex items-start gap-2`,children:[i(l,{className:`text-destructive mt-0.5 h-4 w-4 shrink-0`}),a(`div`,{children:[i(`p`,{className:`text-destructive font-medium`,children:e.error.code}),i(`p`,{className:`text-muted-foreground text-sm`,children:e.error.message})]})]}):S&&!e.content?a(`div`,{className:`flex flex-col gap-2`,children:[i(c,{className:`h-4 w-48`}),i(c,{className:`h-4 w-32`})]}):i(g,{content:e.content})}),a(`div`,{className:n(`flex items-center gap-2 text-xs`,`text-muted-foreground opacity-0 transition-opacity`,`group-hover:opacity-100`),children:[i(`span`,{children:new Date(e.createdAt).toLocaleTimeString([],{hour:`2-digit`,minute:`2-digit`})}),e.usage&&a(`span`,{children:[e.usage.inputTokens+e.usage.outputTokens,` tokens`]}),h&&!b&&e.content&&i(m,{variant:`ghost`,size:`sm`,className:`h-6 w-6 p-0`,onPress:C,"aria-label":v?`Copied`:`Copy message`,children:i(v?d:f,{className:`h-3 w-3`})})]}),e.reasoning&&a(`details`,{className:`text-muted-foreground mt-2 text-sm`,children:[i(`summary`,{className:`cursor-pointer hover:underline`,children:`View reasoning`}),i(`div`,{className:`bg-muted mt-1 rounded-md p-2`,children:i(`p`,{className:`whitespace-pre-wrap`,children:e.reasoning})})]})]})]})}export{_ as ChatMessage};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use client";import*as e from"react";import{cn as t}from"@lssm/lib.ui-kit-web/ui/utils";import{jsx as n,jsxs as r}from"react/jsx-runtime";import{Check as i,Copy as a,Download as o,Play as s}from"lucide-react";import{Button as c}from"@lssm/lib.design-system";const l={ts:`TypeScript`,tsx:`TypeScript (React)`,typescript:`TypeScript`,js:`JavaScript`,jsx:`JavaScript (React)`,javascript:`JavaScript`,json:`JSON`,md:`Markdown`,yaml:`YAML`,yml:`YAML`,bash:`Bash`,sh:`Shell`,sql:`SQL`,py:`Python`,python:`Python`,go:`Go`,rust:`Rust`,rs:`Rust`};function u({code:u,language:d=`text`,filename:f,className:p,showCopy:m=!0,showExecute:h=!1,onExecute:g,showDownload:_=!1,maxHeight:v=400}){let[y,b]=e.useState(!1),x=l[d.toLowerCase()]??d,S=u.split(`
|
|
2
|
+
`),C=e.useCallback(async()=>{await navigator.clipboard.writeText(u),b(!0),setTimeout(()=>b(!1),2e3)},[u]),w=e.useCallback(()=>{let e=new Blob([u],{type:`text/plain`}),t=URL.createObjectURL(e),n=document.createElement(`a`);n.href=t,n.download=f??`code.${d}`,document.body.appendChild(n),n.click(),document.body.removeChild(n),URL.revokeObjectURL(t)},[u,f,d]);return r(`div`,{className:t(`overflow-hidden rounded-lg border`,`bg-muted/50`,p),children:[r(`div`,{className:t(`flex items-center justify-between px-3 py-1.5`,`border-b bg-muted/80`),children:[r(`div`,{className:`flex items-center gap-2 text-sm`,children:[f&&n(`span`,{className:`text-foreground font-mono`,children:f}),n(`span`,{className:`text-muted-foreground`,children:x})]}),r(`div`,{className:`flex items-center gap-1`,children:[h&&g&&n(c,{variant:`ghost`,size:`sm`,onPress:()=>g(u),className:`h-7 w-7 p-0`,"aria-label":`Execute code`,children:n(s,{className:`h-3.5 w-3.5`})}),_&&n(c,{variant:`ghost`,size:`sm`,onPress:w,className:`h-7 w-7 p-0`,"aria-label":`Download code`,children:n(o,{className:`h-3.5 w-3.5`})}),m&&n(c,{variant:`ghost`,size:`sm`,onPress:C,className:`h-7 w-7 p-0`,"aria-label":y?`Copied`:`Copy code`,children:y?n(i,{className:`h-3.5 w-3.5 text-green-500`}):n(a,{className:`h-3.5 w-3.5`})})]})]}),n(`div`,{className:`overflow-auto`,style:{maxHeight:v},children:n(`pre`,{className:`p-3`,children:n(`code`,{className:`text-sm`,children:S.map((e,t)=>r(`div`,{className:`flex`,children:[n(`span`,{className:`text-muted-foreground mr-4 select-none text-right w-8`,children:t+1}),n(`span`,{className:`flex-1`,children:e||` `})]},t))})})})]})}export{u as CodePreview};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use client";import"react";import{cn as e}from"@lssm/lib.ui-kit-web/ui/utils";import{Fragment as t,jsx as n,jsxs as r}from"react/jsx-runtime";import{FileCode as i,FolderOpen as a,Info as o,Zap as s}from"lucide-react";import{Badge as c}from"@lssm/lib.ui-kit-web/ui/badge";import{Tooltip as l,TooltipContent as u,TooltipProvider as d,TooltipTrigger as f}from"@lssm/lib.ui-kit-web/ui/tooltip";function p({summary:p,active:m=!1,className:h,showDetails:g=!0}){if(!p&&!m)return r(`div`,{className:e(`flex items-center gap-1.5 text-sm`,`text-muted-foreground`,h),children:[n(o,{className:`h-4 w-4`}),n(`span`,{children:`No workspace context`})]});let _=r(`div`,{className:e(`flex items-center gap-2`,m?`text-foreground`:`text-muted-foreground`,h),children:[r(c,{variant:m?`default`:`secondary`,className:`flex items-center gap-1`,children:[n(s,{className:`h-3 w-3`}),`Context`]}),p&&g&&r(t,{children:[r(`div`,{className:`flex items-center gap-1 text-xs`,children:[n(a,{className:`h-3.5 w-3.5`}),n(`span`,{children:p.name})]}),r(`div`,{className:`flex items-center gap-1 text-xs`,children:[n(i,{className:`h-3.5 w-3.5`}),r(`span`,{children:[p.specs.total,` specs`]})]})]})]});return p?n(d,{children:r(l,{children:[n(f,{asChild:!0,children:_}),n(u,{side:`bottom`,className:`max-w-[300px]`,children:r(`div`,{className:`flex flex-col gap-2 text-sm`,children:[n(`div`,{className:`font-medium`,children:p.name}),n(`div`,{className:`text-muted-foreground text-xs`,children:p.path}),n(`div`,{className:`border-t pt-2`,children:r(`div`,{className:`grid grid-cols-2 gap-1 text-xs`,children:[n(`span`,{children:`Commands:`}),n(`span`,{className:`text-right`,children:p.specs.commands}),n(`span`,{children:`Queries:`}),n(`span`,{className:`text-right`,children:p.specs.queries}),n(`span`,{children:`Events:`}),n(`span`,{className:`text-right`,children:p.specs.events}),n(`span`,{children:`Presentations:`}),n(`span`,{className:`text-right`,children:p.specs.presentations})]})}),r(`div`,{className:`border-t pt-2 text-xs`,children:[r(`span`,{children:[p.files.total,` files`]}),n(`span`,{className:`mx-1`,children:`•`}),r(`span`,{children:[p.files.specFiles,` spec files`]})]})]})})]})}):_}export{p as ContextIndicator};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use client";import{getModelsForProvider as e}from"@lssm/lib.ai-providers";import*as t from"react";import{cn as n}from"@lssm/lib.ui-kit-web/ui/utils";import{jsx as r,jsxs as i}from"react/jsx-runtime";import{Bot as a,Cloud as o,Cpu as s,Sparkles as c}from"lucide-react";import{Button as l}from"@lssm/lib.design-system";import{Select as u,SelectContent as d,SelectItem as f,SelectTrigger as p,SelectValue as m}from"@lssm/lib.ui-kit-web/ui/select";import{Badge as h}from"@lssm/lib.ui-kit-web/ui/badge";const g={ollama:r(s,{className:`h-4 w-4`}),openai:r(a,{className:`h-4 w-4`}),anthropic:r(c,{className:`h-4 w-4`}),mistral:r(o,{className:`h-4 w-4`}),gemini:r(c,{className:`h-4 w-4`})},_={ollama:`Ollama (Local)`,openai:`OpenAI`,anthropic:`Anthropic`,mistral:`Mistral`,gemini:`Google Gemini`},v={local:{label:`Local`,variant:`secondary`},byok:{label:`BYOK`,variant:`outline`},managed:{label:`Managed`,variant:`default`}};function y({value:a,onChange:o,availableProviders:s,className:c,compact:y=!1}){let b=s??[{provider:`ollama`,available:!0,mode:`local`},{provider:`openai`,available:!0,mode:`byok`},{provider:`anthropic`,available:!0,mode:`byok`},{provider:`mistral`,available:!0,mode:`byok`},{provider:`gemini`,available:!0,mode:`byok`}],x=e(a.provider),S=x.find(e=>e.id===a.model),C=t.useCallback(t=>{let n=t,r=b.find(e=>e.provider===n);o({provider:n,model:e(n)[0]?.id??``,mode:r?.mode??`byok`})},[o,b]),w=t.useCallback(e=>{o({...a,model:e})},[o,a]);return y?i(`div`,{className:n(`flex items-center gap-2`,c),children:[i(u,{value:a.provider,onValueChange:C,children:[r(p,{className:`w-[140px]`,children:r(m,{})}),r(d,{children:b.map(e=>r(f,{value:e.provider,disabled:!e.available,children:i(`div`,{className:`flex items-center gap-2`,children:[g[e.provider],r(`span`,{children:_[e.provider]})]})},e.provider))})]}),i(u,{value:a.model,onValueChange:w,children:[r(p,{className:`w-[160px]`,children:r(m,{})}),r(d,{children:x.map(e=>r(f,{value:e.id,children:e.name},e.id))})]})]}):i(`div`,{className:n(`flex flex-col gap-3`,c),children:[i(`div`,{className:`flex flex-col gap-1.5`,children:[r(`label`,{className:`text-sm font-medium`,children:`Provider`}),r(`div`,{className:`flex flex-wrap gap-2`,children:b.map(e=>i(l,{variant:a.provider===e.provider?`default`:`outline`,size:`sm`,onPress:()=>e.available&&C(e.provider),disabled:!e.available,className:n(!e.available&&`opacity-50`),children:[g[e.provider],r(`span`,{children:_[e.provider]}),r(h,{variant:v[e.mode].variant,className:`ml-1`,children:v[e.mode].label})]},e.provider))})]}),i(`div`,{className:`flex flex-col gap-1.5`,children:[r(`label`,{className:`text-sm font-medium`,children:`Model`}),i(u,{value:a.model,onValueChange:w,children:[r(p,{children:r(m,{placeholder:`Select a model`})}),r(d,{children:x.map(e=>r(f,{value:e.id,children:i(`div`,{className:`flex items-center gap-2`,children:[r(`span`,{children:e.name}),i(`span`,{className:`text-muted-foreground text-xs`,children:[Math.round(e.contextWindow/1e3),`K`]}),e.capabilities.vision&&r(h,{variant:`outline`,className:`text-xs`,children:`Vision`}),e.capabilities.reasoning&&r(h,{variant:`outline`,className:`text-xs`,children:`Reasoning`})]})},e.id))})]})]}),S&&i(`div`,{className:`text-muted-foreground flex flex-wrap gap-2 text-xs`,children:[i(`span`,{children:[`Context: `,Math.round(S.contextWindow/1e3),`K tokens`]}),S.capabilities.vision&&r(`span`,{children:`• Vision`}),S.capabilities.tools&&r(`span`,{children:`• Tools`}),S.capabilities.reasoning&&r(`span`,{children:`• Reasoning`})]})]})}export{y as ModelPicker};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{ChatContainer as e}from"./ChatContainer.js";import{CodePreview as t}from"./CodePreview.js";import{ChatMessage as n}from"./ChatMessage.js";import{ChatInput as r}from"./ChatInput.js";import{ModelPicker as i}from"./ModelPicker.js";import{ContextIndicator as a}from"./ContextIndicator.js";export{e as ChatContainer,r as ChatInput,n as ChatMessage,t as CodePreview,a as ContextIndicator,i as ModelPicker};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{useChat as e}from"./useChat.js";import{useProviders as t}from"./useProviders.js";export{e as useChat,t as useProviders};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use client";import{ChatService as e}from"../../core/chat-service.js";import{createProvider as t}from"@lssm/lib.ai-providers";import*as n from"react";function r(r={}){let{provider:i=`openai`,mode:a=`byok`,model:o,apiKey:s,proxyUrl:c,conversationId:l,systemPrompt:u,streaming:d=!0,onSend:f,onResponse:p,onError:m,onUsage:h}=r,[g,_]=n.useState([]),[v,y]=n.useState(null),[b,x]=n.useState(!1),[S,C]=n.useState(null),[w,T]=n.useState(l??null),E=n.useRef(null),D=n.useRef(null);n.useEffect(()=>{D.current=new e({provider:t({provider:i,model:o,apiKey:s,proxyUrl:c}),systemPrompt:u,onUsage:h})},[i,a,o,s,c,u,h]),n.useEffect(()=>{!w||!D.current||(async()=>{let e=await D.current.getConversation(w);e&&(y(e),_(e.messages))})().catch(console.error)},[w]);let O=n.useCallback(async(e,t)=>{if(!D.current)throw Error(`Chat service not initialized`);x(!0),C(null),E.current=new AbortController;try{let n={id:`msg_${Date.now()}`,conversationId:w??``,role:`user`,content:e,status:`completed`,createdAt:new Date,updatedAt:new Date,attachments:t};if(_(e=>[...e,n]),f?.(n),d){let n=await D.current.stream({conversationId:w??void 0,content:e,attachments:t});w||T(n.conversationId);let r={id:n.messageId,conversationId:n.conversationId,role:`assistant`,content:``,status:`streaming`,createdAt:new Date,updatedAt:new Date};_(e=>[...e,r]);let i=``;for await(let e of n.stream)if(e.type===`text`&&e.content)i+=e.content,_(e=>e.map(e=>e.id===n.messageId?{...e,content:i}:e));else if(e.type===`done`)_(t=>t.map(t=>t.id===n.messageId?{...t,status:`completed`,usage:e.usage,updatedAt:new Date}:t)),p?.(g.find(e=>e.id===n.messageId)??r);else if(e.type===`error`&&(_(t=>t.map(t=>t.id===n.messageId?{...t,status:`error`,error:e.error,updatedAt:new Date}:t)),e.error)){let t=Error(e.error.message);C(t),m?.(t)}}else{let n=await D.current.send({conversationId:w??void 0,content:e,attachments:t});y(n.conversation),_(n.conversation.messages),w||T(n.conversation.id),p?.(n.message)}}catch(e){let t=e instanceof Error?e:Error(String(e));C(t),m?.(t)}finally{x(!1),E.current=null}},[w,d,f,p,m,g]);return{messages:g,conversation:v,isLoading:b,error:S,sendMessage:O,clearConversation:n.useCallback(()=>{_([]),y(null),T(null),C(null)},[]),setConversationId:T,regenerate:n.useCallback(async()=>{let e=g.findLastIndex(e=>e.role===`user`);if(e===-1)return;let t=g[e];t&&(_(t=>t.slice(0,e+1)),await O(t.content,t.attachments))},[g,O]),stop:n.useCallback(()=>{E.current?.abort(),x(!1)},[])}}export{r as useChat};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use client";import{getAvailableProviders as e,getModelsForProvider as t}from"@lssm/lib.ai-providers";import*as n from"react";function r(){let[r,i]=n.useState([]),[a,o]=n.useState(!0),s=n.useCallback(async()=>{o(!0);try{i(e().map(e=>({...e,models:t(e.provider)})))}catch(e){console.error(`Failed to load providers:`,e)}finally{o(!1)}},[]);return n.useEffect(()=>{s()},[s]),{providers:r,availableProviders:n.useMemo(()=>r.filter(e=>e.available),[r]),isAvailable:n.useCallback(e=>r.some(t=>t.provider===e&&t.available),[r]),getModels:n.useCallback(e=>r.find(t=>t.provider===e)?.models??[],[r]),isLoading:a,refresh:s}}export{r as useProviders};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{ChatContainer as e}from"./components/ChatContainer.js";import{CodePreview as t}from"./components/CodePreview.js";import{ChatMessage as n}from"./components/ChatMessage.js";import{ChatInput as r}from"./components/ChatInput.js";import{ModelPicker as i}from"./components/ModelPicker.js";import{ContextIndicator as a}from"./components/ContextIndicator.js";import"./components/index.js";import{useChat as o}from"./hooks/useChat.js";import{useProviders as s}from"./hooks/useProviders.js";import"./hooks/index.js";export{e as ChatContainer,r as ChatInput,n as ChatMessage,t as CodePreview,a as ContextIndicator,i as ModelPicker,o as useChat,s as useProviders};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function e(e){return e===`ollama`}function t(e){return e!==`ollama`}export{t as isStudioAvailable,e as supportsLocalMode};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{isStudioAvailable as e,supportsLocalMode as t}from"./chat-utilities.js";import{DEFAULT_MODELS as n,MODELS as r,createProvider as i,createProviderFromEnv as a,getAvailableProviders as o,getDefaultModel as s,getEnvVarName as c,getModelInfo as l,getModelsForProvider as u,getRecommendedModels as d,hasCredentials as f,isOllamaRunning as p,listOllamaModels as m,validateProvider as h}from"@lssm/lib.ai-providers";export{n as DEFAULT_MODELS,r as MODELS,i as createProvider,a as createProviderFromEnv,o as getAvailableProviders,s as getDefaultModel,c as getEnvVarName,l as getModelInfo,u as getModelsForProvider,d as getRecommendedModels,f as hasCredentials,p as isOllamaRunning,e as isStudioAvailable,m as listOllamaModels,t as supportsLocalMode,h as validateProvider};
|
package/package.json
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lssm/module.ai-chat",
|
|
3
|
+
"version": "0.0.0-canary-20251217023603",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"README.md"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"publish:pkg": "bun publish --tolerate-republish --ignore-scripts --verbose",
|
|
14
|
+
"build": "bun build:bundle && bun build:types",
|
|
15
|
+
"build:bundle": "tsdown",
|
|
16
|
+
"build:types": "tsc --noEmit",
|
|
17
|
+
"dev": "bun build:bundle --watch",
|
|
18
|
+
"clean": "rimraf dist .turbo",
|
|
19
|
+
"lint": "bun lint:fix",
|
|
20
|
+
"lint:fix": "eslint src --fix",
|
|
21
|
+
"lint:check": "eslint src",
|
|
22
|
+
"test": "bun test"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@lssm/lib.ai-agent": "workspace:*",
|
|
26
|
+
"@lssm/lib.ai-providers": "workspace:*",
|
|
27
|
+
"@lssm/lib.contracts": "workspace:*",
|
|
28
|
+
"@lssm/lib.metering": "workspace:*",
|
|
29
|
+
"@lssm/lib.cost-tracking": "workspace:*",
|
|
30
|
+
"@lssm/lib.design-system": "workspace:*",
|
|
31
|
+
"@lssm/lib.ui-kit-web": "workspace:*",
|
|
32
|
+
"@ai-sdk/react": "beta",
|
|
33
|
+
"ai": "beta",
|
|
34
|
+
"lucide-react": "^0.535.0",
|
|
35
|
+
"react": "19.2.3",
|
|
36
|
+
"zod": "^4.1.13"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@lssm/tool.tsdown": "workspace:*",
|
|
40
|
+
"@lssm/tool.typescript": "workspace:*",
|
|
41
|
+
"@types/react": "^19.0.14",
|
|
42
|
+
"tsdown": "^0.17.4",
|
|
43
|
+
"typescript": "^5.9.3"
|
|
44
|
+
},
|
|
45
|
+
"peerDependencies": {
|
|
46
|
+
"react": ">=18.0.0"
|
|
47
|
+
},
|
|
48
|
+
"exports": {
|
|
49
|
+
".": "./src/index.ts",
|
|
50
|
+
"./context": "./src/context/index.ts",
|
|
51
|
+
"./core": "./src/core/index.ts",
|
|
52
|
+
"./presentation": "./src/presentation/index.ts",
|
|
53
|
+
"./presentation/components": "./src/presentation/components/index.ts",
|
|
54
|
+
"./presentation/hooks": "./src/presentation/hooks/index.ts",
|
|
55
|
+
"./providers": "./src/providers/index.ts",
|
|
56
|
+
"./*": "./*"
|
|
57
|
+
},
|
|
58
|
+
"publishConfig": {
|
|
59
|
+
"access": "public",
|
|
60
|
+
"exports": {
|
|
61
|
+
".": "./dist/index.js",
|
|
62
|
+
"./context": "./dist/context/index.js",
|
|
63
|
+
"./core": "./dist/core/index.js",
|
|
64
|
+
"./presentation": "./dist/presentation/index.js",
|
|
65
|
+
"./presentation/components": "./dist/presentation/components/index.js",
|
|
66
|
+
"./presentation/hooks": "./dist/presentation/hooks/index.js",
|
|
67
|
+
"./providers": "./dist/providers/index.js",
|
|
68
|
+
"./*": "./*"
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|