@lssm/module.ai-chat 0.0.0-canary-20251217080011 → 1.41.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai-chat.feature.js +1 -93
- package/dist/context/context-builder.js +2 -147
- package/dist/context/file-operations.js +1 -174
- package/dist/context/index.js +1 -5
- package/dist/context/workspace-context.js +2 -123
- package/dist/core/chat-service.js +2 -211
- package/dist/core/conversation-store.js +1 -108
- package/dist/core/index.js +1 -4
- package/dist/index.js +1 -22
- package/dist/presentation/components/ChatContainer.js +1 -62
- package/dist/presentation/components/ChatInput.js +1 -149
- package/dist/presentation/components/ChatMessage.js +1 -135
- package/dist/presentation/components/CodePreview.js +2 -126
- package/dist/presentation/components/ContextIndicator.js +1 -96
- package/dist/presentation/components/ModelPicker.js +1 -197
- package/dist/presentation/components/index.js +1 -8
- package/dist/presentation/hooks/index.js +1 -4
- package/dist/presentation/hooks/useChat.js +1 -171
- package/dist/presentation/hooks/useProviders.js +1 -42
- package/dist/presentation/index.js +1 -12
- package/dist/providers/chat-utilities.js +1 -16
- package/dist/providers/index.js +1 -7
- package/package.json +17 -18
- package/dist/ai-chat.feature.d.ts +0 -11
- package/dist/context/context-builder.d.ts +0 -56
- package/dist/context/file-operations.d.ts +0 -99
- package/dist/context/index.d.ts +0 -4
- package/dist/context/workspace-context.d.ts +0 -116
- package/dist/core/chat-service.d.ts +0 -72
- package/dist/core/conversation-store.d.ts +0 -73
- package/dist/core/index.d.ts +0 -4
- package/dist/core/message-types.d.ts +0 -149
- package/dist/index.d.ts +0 -16
- package/dist/libs/ai-providers/dist/factory.js +0 -225
- package/dist/libs/ai-providers/dist/index.js +0 -4
- package/dist/libs/ai-providers/dist/legacy.js +0 -2
- package/dist/libs/ai-providers/dist/models.js +0 -299
- package/dist/libs/ai-providers/dist/validation.js +0 -60
- package/dist/libs/design-system/dist/_virtual/rolldown_runtime.js +0 -5
- package/dist/libs/design-system/dist/components/atoms/Button.js +0 -33
- package/dist/libs/design-system/dist/components/atoms/Textarea.js +0 -35
- package/dist/libs/design-system/dist/lib/keyboard.js +0 -193
- package/dist/libs/design-system/dist/ui-kit-web/dist/ui/button.js +0 -55
- package/dist/libs/design-system/dist/ui-kit-web/dist/ui/textarea.js +0 -16
- package/dist/libs/design-system/dist/ui-kit-web/dist/ui-kit-core/dist/utils.js +0 -13
- package/dist/libs/ui-kit-web/dist/ui/avatar.js +0 -25
- package/dist/libs/ui-kit-web/dist/ui/badge.js +0 -26
- package/dist/libs/ui-kit-web/dist/ui/scroll-area.js +0 -39
- package/dist/libs/ui-kit-web/dist/ui/select.js +0 -79
- package/dist/libs/ui-kit-web/dist/ui/skeleton.js +0 -14
- package/dist/libs/ui-kit-web/dist/ui/tooltip.js +0 -39
- package/dist/libs/ui-kit-web/dist/ui/utils.js +0 -10
- package/dist/libs/ui-kit-web/dist/ui-kit-core/dist/utils.js +0 -10
- package/dist/presentation/components/ChatContainer.d.ts +0 -20
- package/dist/presentation/components/ChatInput.d.ts +0 -34
- package/dist/presentation/components/ChatMessage.d.ts +0 -23
- package/dist/presentation/components/CodePreview.d.ts +0 -39
- package/dist/presentation/components/ContextIndicator.d.ts +0 -25
- package/dist/presentation/components/ModelPicker.d.ts +0 -38
- package/dist/presentation/components/index.d.ts +0 -7
- package/dist/presentation/hooks/index.d.ts +0 -3
- package/dist/presentation/hooks/useChat.d.ts +0 -66
- package/dist/presentation/hooks/useProviders.d.ts +0 -37
- package/dist/presentation/index.d.ts +0 -10
- package/dist/providers/chat-utilities.d.ts +0 -14
- package/dist/providers/index.d.ts +0 -3
|
@@ -1,14 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { generateText, streamText } from "ai";
|
|
3
|
-
|
|
4
|
-
//#region src/core/chat-service.ts
|
|
5
|
-
/**
|
|
6
|
-
* Main chat orchestration service
|
|
7
|
-
*/
|
|
8
|
-
/**
|
|
9
|
-
* Default system prompt for ContractSpec vibe coding
|
|
10
|
-
*/
|
|
11
|
-
const DEFAULT_SYSTEM_PROMPT = `You are ContractSpec AI, an expert coding assistant specialized in ContractSpec development.
|
|
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.
|
|
12
2
|
|
|
13
3
|
Your capabilities:
|
|
14
4
|
- Help users create, modify, and understand ContractSpec specifications
|
|
@@ -21,203 +11,4 @@ Guidelines:
|
|
|
21
11
|
- Provide code examples when helpful
|
|
22
12
|
- Reference relevant ContractSpec concepts and patterns
|
|
23
13
|
- Ask clarifying questions when the user's intent is unclear
|
|
24
|
-
- When suggesting code changes, explain the rationale
|
|
25
|
-
/**
|
|
26
|
-
* Main chat service for AI-powered conversations
|
|
27
|
-
*/
|
|
28
|
-
var ChatService = class {
|
|
29
|
-
provider;
|
|
30
|
-
context;
|
|
31
|
-
store;
|
|
32
|
-
systemPrompt;
|
|
33
|
-
maxHistoryMessages;
|
|
34
|
-
onUsage;
|
|
35
|
-
constructor(config) {
|
|
36
|
-
this.provider = config.provider;
|
|
37
|
-
this.context = config.context;
|
|
38
|
-
this.store = config.store ?? new InMemoryConversationStore();
|
|
39
|
-
this.systemPrompt = config.systemPrompt ?? DEFAULT_SYSTEM_PROMPT;
|
|
40
|
-
this.maxHistoryMessages = config.maxHistoryMessages ?? 20;
|
|
41
|
-
this.onUsage = config.onUsage;
|
|
42
|
-
}
|
|
43
|
-
/**
|
|
44
|
-
* Send a message and get a complete response
|
|
45
|
-
*/
|
|
46
|
-
async send(options) {
|
|
47
|
-
let conversation;
|
|
48
|
-
if (options.conversationId) {
|
|
49
|
-
const existing = await this.store.get(options.conversationId);
|
|
50
|
-
if (!existing) throw new Error(`Conversation ${options.conversationId} not found`);
|
|
51
|
-
conversation = existing;
|
|
52
|
-
} else conversation = await this.store.create({
|
|
53
|
-
status: "active",
|
|
54
|
-
provider: this.provider.name,
|
|
55
|
-
model: this.provider.model,
|
|
56
|
-
messages: [],
|
|
57
|
-
workspacePath: this.context?.workspacePath
|
|
58
|
-
});
|
|
59
|
-
await this.store.appendMessage(conversation.id, {
|
|
60
|
-
role: "user",
|
|
61
|
-
content: options.content,
|
|
62
|
-
status: "completed",
|
|
63
|
-
attachments: options.attachments
|
|
64
|
-
});
|
|
65
|
-
const prompt = this.buildPrompt(conversation, options);
|
|
66
|
-
const model = this.provider.getModel();
|
|
67
|
-
try {
|
|
68
|
-
const result = await generateText({
|
|
69
|
-
model,
|
|
70
|
-
prompt,
|
|
71
|
-
system: this.systemPrompt
|
|
72
|
-
});
|
|
73
|
-
const assistantMessage = await this.store.appendMessage(conversation.id, {
|
|
74
|
-
role: "assistant",
|
|
75
|
-
content: result.text,
|
|
76
|
-
status: "completed"
|
|
77
|
-
});
|
|
78
|
-
const updatedConversation = await this.store.get(conversation.id);
|
|
79
|
-
if (!updatedConversation) throw new Error("Conversation lost after update");
|
|
80
|
-
return {
|
|
81
|
-
message: assistantMessage,
|
|
82
|
-
conversation: updatedConversation
|
|
83
|
-
};
|
|
84
|
-
} catch (error) {
|
|
85
|
-
await this.store.appendMessage(conversation.id, {
|
|
86
|
-
role: "assistant",
|
|
87
|
-
content: "",
|
|
88
|
-
status: "error",
|
|
89
|
-
error: {
|
|
90
|
-
code: "generation_failed",
|
|
91
|
-
message: error instanceof Error ? error.message : String(error)
|
|
92
|
-
}
|
|
93
|
-
});
|
|
94
|
-
throw error;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
/**
|
|
98
|
-
* Send a message and get a streaming response
|
|
99
|
-
*/
|
|
100
|
-
async stream(options) {
|
|
101
|
-
let conversation;
|
|
102
|
-
if (options.conversationId) {
|
|
103
|
-
const existing = await this.store.get(options.conversationId);
|
|
104
|
-
if (!existing) throw new Error(`Conversation ${options.conversationId} not found`);
|
|
105
|
-
conversation = existing;
|
|
106
|
-
} else conversation = await this.store.create({
|
|
107
|
-
status: "active",
|
|
108
|
-
provider: this.provider.name,
|
|
109
|
-
model: this.provider.model,
|
|
110
|
-
messages: [],
|
|
111
|
-
workspacePath: this.context?.workspacePath
|
|
112
|
-
});
|
|
113
|
-
await this.store.appendMessage(conversation.id, {
|
|
114
|
-
role: "user",
|
|
115
|
-
content: options.content,
|
|
116
|
-
status: "completed",
|
|
117
|
-
attachments: options.attachments
|
|
118
|
-
});
|
|
119
|
-
const assistantMessage = await this.store.appendMessage(conversation.id, {
|
|
120
|
-
role: "assistant",
|
|
121
|
-
content: "",
|
|
122
|
-
status: "streaming"
|
|
123
|
-
});
|
|
124
|
-
const prompt = this.buildPrompt(conversation, options);
|
|
125
|
-
const model = this.provider.getModel();
|
|
126
|
-
const self = this;
|
|
127
|
-
async function* streamGenerator() {
|
|
128
|
-
let fullContent = "";
|
|
129
|
-
try {
|
|
130
|
-
const result = streamText({
|
|
131
|
-
model,
|
|
132
|
-
prompt,
|
|
133
|
-
system: self.systemPrompt
|
|
134
|
-
});
|
|
135
|
-
for await (const chunk of result.textStream) {
|
|
136
|
-
fullContent += chunk;
|
|
137
|
-
yield {
|
|
138
|
-
type: "text",
|
|
139
|
-
content: chunk
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
|
-
await self.store.updateMessage(conversation.id, assistantMessage.id, {
|
|
143
|
-
content: fullContent,
|
|
144
|
-
status: "completed"
|
|
145
|
-
});
|
|
146
|
-
yield { type: "done" };
|
|
147
|
-
} catch (error) {
|
|
148
|
-
await self.store.updateMessage(conversation.id, assistantMessage.id, {
|
|
149
|
-
content: fullContent,
|
|
150
|
-
status: "error",
|
|
151
|
-
error: {
|
|
152
|
-
code: "stream_failed",
|
|
153
|
-
message: error instanceof Error ? error.message : String(error)
|
|
154
|
-
}
|
|
155
|
-
});
|
|
156
|
-
yield {
|
|
157
|
-
type: "error",
|
|
158
|
-
error: {
|
|
159
|
-
code: "stream_failed",
|
|
160
|
-
message: error instanceof Error ? error.message : String(error)
|
|
161
|
-
}
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
return {
|
|
166
|
-
conversationId: conversation.id,
|
|
167
|
-
messageId: assistantMessage.id,
|
|
168
|
-
stream: streamGenerator()
|
|
169
|
-
};
|
|
170
|
-
}
|
|
171
|
-
/**
|
|
172
|
-
* Get a conversation by ID
|
|
173
|
-
*/
|
|
174
|
-
async getConversation(conversationId) {
|
|
175
|
-
return this.store.get(conversationId);
|
|
176
|
-
}
|
|
177
|
-
/**
|
|
178
|
-
* List conversations
|
|
179
|
-
*/
|
|
180
|
-
async listConversations(options) {
|
|
181
|
-
return this.store.list({
|
|
182
|
-
status: "active",
|
|
183
|
-
...options
|
|
184
|
-
});
|
|
185
|
-
}
|
|
186
|
-
/**
|
|
187
|
-
* Delete a conversation
|
|
188
|
-
*/
|
|
189
|
-
async deleteConversation(conversationId) {
|
|
190
|
-
return this.store.delete(conversationId);
|
|
191
|
-
}
|
|
192
|
-
/**
|
|
193
|
-
* Build prompt string for LLM
|
|
194
|
-
*/
|
|
195
|
-
buildPrompt(conversation, options) {
|
|
196
|
-
let prompt = "";
|
|
197
|
-
const historyStart = Math.max(0, conversation.messages.length - this.maxHistoryMessages);
|
|
198
|
-
for (let i = historyStart; i < conversation.messages.length; i++) {
|
|
199
|
-
const msg = conversation.messages[i];
|
|
200
|
-
if (!msg) continue;
|
|
201
|
-
if (msg.role === "user" || msg.role === "assistant") prompt += `${msg.role === "user" ? "User" : "Assistant"}: ${msg.content}\n\n`;
|
|
202
|
-
}
|
|
203
|
-
let content = options.content;
|
|
204
|
-
if (options.attachments?.length) {
|
|
205
|
-
const attachmentInfo = options.attachments.map((a) => {
|
|
206
|
-
if (a.type === "file" || a.type === "code") return `\n\n### ${a.name}\n\`\`\`\n${a.content}\n\`\`\``;
|
|
207
|
-
return `\n\n[Attachment: ${a.name}]`;
|
|
208
|
-
}).join("");
|
|
209
|
-
content += attachmentInfo;
|
|
210
|
-
}
|
|
211
|
-
prompt += `User: ${content}\n\nAssistant:`;
|
|
212
|
-
return prompt;
|
|
213
|
-
}
|
|
214
|
-
};
|
|
215
|
-
/**
|
|
216
|
-
* Create a chat service with the given configuration
|
|
217
|
-
*/
|
|
218
|
-
function createChatService(config) {
|
|
219
|
-
return new ChatService(config);
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
//#endregion
|
|
223
|
-
export { ChatService, createChatService };
|
|
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};
|
|
@@ -1,108 +1 @@
|
|
|
1
|
-
|
|
2
|
-
/**
|
|
3
|
-
* Generate a unique ID
|
|
4
|
-
*/
|
|
5
|
-
function generateId(prefix) {
|
|
6
|
-
return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
|
|
7
|
-
}
|
|
8
|
-
/**
|
|
9
|
-
* In-memory conversation store for development and testing
|
|
10
|
-
*/
|
|
11
|
-
var InMemoryConversationStore = class {
|
|
12
|
-
conversations = /* @__PURE__ */ new Map();
|
|
13
|
-
async get(conversationId) {
|
|
14
|
-
return this.conversations.get(conversationId) ?? null;
|
|
15
|
-
}
|
|
16
|
-
async create(conversation) {
|
|
17
|
-
const now = /* @__PURE__ */ new Date();
|
|
18
|
-
const fullConversation = {
|
|
19
|
-
...conversation,
|
|
20
|
-
id: generateId("conv"),
|
|
21
|
-
createdAt: now,
|
|
22
|
-
updatedAt: now
|
|
23
|
-
};
|
|
24
|
-
this.conversations.set(fullConversation.id, fullConversation);
|
|
25
|
-
return fullConversation;
|
|
26
|
-
}
|
|
27
|
-
async update(conversationId, updates) {
|
|
28
|
-
const conversation = this.conversations.get(conversationId);
|
|
29
|
-
if (!conversation) return null;
|
|
30
|
-
const updated = {
|
|
31
|
-
...conversation,
|
|
32
|
-
...updates,
|
|
33
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
34
|
-
};
|
|
35
|
-
this.conversations.set(conversationId, updated);
|
|
36
|
-
return updated;
|
|
37
|
-
}
|
|
38
|
-
async appendMessage(conversationId, message) {
|
|
39
|
-
const conversation = this.conversations.get(conversationId);
|
|
40
|
-
if (!conversation) throw new Error(`Conversation ${conversationId} not found`);
|
|
41
|
-
const now = /* @__PURE__ */ new Date();
|
|
42
|
-
const fullMessage = {
|
|
43
|
-
...message,
|
|
44
|
-
id: generateId("msg"),
|
|
45
|
-
conversationId,
|
|
46
|
-
createdAt: now,
|
|
47
|
-
updatedAt: now
|
|
48
|
-
};
|
|
49
|
-
conversation.messages.push(fullMessage);
|
|
50
|
-
conversation.updatedAt = now;
|
|
51
|
-
return fullMessage;
|
|
52
|
-
}
|
|
53
|
-
async updateMessage(conversationId, messageId, updates) {
|
|
54
|
-
const conversation = this.conversations.get(conversationId);
|
|
55
|
-
if (!conversation) return null;
|
|
56
|
-
const messageIndex = conversation.messages.findIndex((m) => m.id === messageId);
|
|
57
|
-
if (messageIndex === -1) return null;
|
|
58
|
-
const message = conversation.messages[messageIndex];
|
|
59
|
-
if (!message) return null;
|
|
60
|
-
const updated = {
|
|
61
|
-
...message,
|
|
62
|
-
...updates,
|
|
63
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
64
|
-
};
|
|
65
|
-
conversation.messages[messageIndex] = updated;
|
|
66
|
-
conversation.updatedAt = /* @__PURE__ */ new Date();
|
|
67
|
-
return updated;
|
|
68
|
-
}
|
|
69
|
-
async delete(conversationId) {
|
|
70
|
-
return this.conversations.delete(conversationId);
|
|
71
|
-
}
|
|
72
|
-
async list(options) {
|
|
73
|
-
let results = Array.from(this.conversations.values());
|
|
74
|
-
if (options?.status) results = results.filter((c) => c.status === options.status);
|
|
75
|
-
results.sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime());
|
|
76
|
-
const offset = options?.offset ?? 0;
|
|
77
|
-
const limit = options?.limit ?? 100;
|
|
78
|
-
return results.slice(offset, offset + limit);
|
|
79
|
-
}
|
|
80
|
-
async search(query, limit = 20) {
|
|
81
|
-
const lowerQuery = query.toLowerCase();
|
|
82
|
-
const results = [];
|
|
83
|
-
for (const conversation of this.conversations.values()) {
|
|
84
|
-
if (conversation.title?.toLowerCase().includes(lowerQuery)) {
|
|
85
|
-
results.push(conversation);
|
|
86
|
-
continue;
|
|
87
|
-
}
|
|
88
|
-
if (conversation.messages.some((m) => m.content.toLowerCase().includes(lowerQuery))) results.push(conversation);
|
|
89
|
-
if (results.length >= limit) break;
|
|
90
|
-
}
|
|
91
|
-
return results;
|
|
92
|
-
}
|
|
93
|
-
/**
|
|
94
|
-
* Clear all conversations (for testing)
|
|
95
|
-
*/
|
|
96
|
-
clear() {
|
|
97
|
-
this.conversations.clear();
|
|
98
|
-
}
|
|
99
|
-
};
|
|
100
|
-
/**
|
|
101
|
-
* Create an in-memory conversation store
|
|
102
|
-
*/
|
|
103
|
-
function createInMemoryConversationStore() {
|
|
104
|
-
return new InMemoryConversationStore();
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
//#endregion
|
|
108
|
-
export { InMemoryConversationStore, createInMemoryConversationStore };
|
|
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};
|
package/dist/core/index.js
CHANGED
|
@@ -1,4 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { ChatService, createChatService } from "./chat-service.js";
|
|
3
|
-
|
|
4
|
-
export { ChatService, InMemoryConversationStore, createChatService, createInMemoryConversationStore };
|
|
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
CHANGED
|
@@ -1,22 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { InMemoryConversationStore, createInMemoryConversationStore } from "./core/conversation-store.js";
|
|
3
|
-
import { ChatService, createChatService } from "./core/chat-service.js";
|
|
4
|
-
import "./core/index.js";
|
|
5
|
-
import { DEFAULT_MODELS, MODELS, getDefaultModel, getModelInfo, getModelsForProvider, getRecommendedModels } from "./libs/ai-providers/dist/models.js";
|
|
6
|
-
import { createProvider, createProviderFromEnv, getAvailableProviders } from "./libs/ai-providers/dist/factory.js";
|
|
7
|
-
import { getEnvVarName, hasCredentials, isOllamaRunning, listOllamaModels, validateProvider } from "./libs/ai-providers/dist/validation.js";
|
|
8
|
-
import { isStudioAvailable, supportsLocalMode } from "./providers/chat-utilities.js";
|
|
9
|
-
import "./providers/index.js";
|
|
10
|
-
import { WorkspaceContext, createWorkspaceContext } from "./context/workspace-context.js";
|
|
11
|
-
import { ContextBuilder, createContextBuilder } from "./context/context-builder.js";
|
|
12
|
-
import { FileOperations, createNodeFileOperations } from "./context/file-operations.js";
|
|
13
|
-
import "./context/index.js";
|
|
14
|
-
import { ChatContainer } from "./presentation/components/ChatContainer.js";
|
|
15
|
-
import { ChatMessage } from "./presentation/components/ChatMessage.js";
|
|
16
|
-
import { ChatInput } from "./presentation/components/ChatInput.js";
|
|
17
|
-
import "./presentation/components/index.js";
|
|
18
|
-
import { useChat } from "./presentation/hooks/useChat.js";
|
|
19
|
-
import { useProviders } from "./presentation/hooks/useProviders.js";
|
|
20
|
-
import "./presentation/hooks/index.js";
|
|
21
|
-
|
|
22
|
-
export { AiChatFeature, ChatContainer, ChatInput, ChatMessage as ChatMessageComponent, ChatService, ContextBuilder, DEFAULT_MODELS, FileOperations, InMemoryConversationStore, MODELS, WorkspaceContext, createChatService, createContextBuilder, createInMemoryConversationStore, createNodeFileOperations, createProvider, createProviderFromEnv, createWorkspaceContext, getAvailableProviders, getDefaultModel, getEnvVarName, getModelInfo, getModelsForProvider, getRecommendedModels, hasCredentials, isOllamaRunning, isStudioAvailable, listOllamaModels, supportsLocalMode, useChat, useProviders, validateProvider };
|
|
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};
|
|
@@ -1,62 +1 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import { ScrollArea } from "../../libs/ui-kit-web/dist/ui/scroll-area.js";
|
|
4
|
-
import { cn } from "../../libs/ui-kit-web/dist/ui/utils.js";
|
|
5
|
-
import * as React from "react";
|
|
6
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
-
|
|
8
|
-
//#region src/presentation/components/ChatContainer.tsx
|
|
9
|
-
/**
|
|
10
|
-
* Container component for chat messages with scrolling
|
|
11
|
-
*/
|
|
12
|
-
function ChatContainer({ children, className, showScrollButton = true }) {
|
|
13
|
-
const scrollRef = React.useRef(null);
|
|
14
|
-
const [showScrollDown, setShowScrollDown] = React.useState(false);
|
|
15
|
-
React.useEffect(() => {
|
|
16
|
-
const container = scrollRef.current;
|
|
17
|
-
if (!container) return;
|
|
18
|
-
if (container.scrollHeight - container.scrollTop <= container.clientHeight + 100) container.scrollTop = container.scrollHeight;
|
|
19
|
-
}, [children]);
|
|
20
|
-
const handleScroll = React.useCallback((event) => {
|
|
21
|
-
const container = event.currentTarget;
|
|
22
|
-
setShowScrollDown(!(container.scrollHeight - container.scrollTop <= container.clientHeight + 100));
|
|
23
|
-
}, []);
|
|
24
|
-
const scrollToBottom = React.useCallback(() => {
|
|
25
|
-
const container = scrollRef.current;
|
|
26
|
-
if (container) container.scrollTo({
|
|
27
|
-
top: container.scrollHeight,
|
|
28
|
-
behavior: "smooth"
|
|
29
|
-
});
|
|
30
|
-
}, []);
|
|
31
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
32
|
-
className: cn("relative flex flex-1 flex-col", className),
|
|
33
|
-
children: [/* @__PURE__ */ jsx(ScrollArea, {
|
|
34
|
-
ref: scrollRef,
|
|
35
|
-
className: "flex-1",
|
|
36
|
-
onScroll: handleScroll,
|
|
37
|
-
children: /* @__PURE__ */ jsx("div", {
|
|
38
|
-
className: "flex flex-col gap-4 p-4",
|
|
39
|
-
children
|
|
40
|
-
})
|
|
41
|
-
}), showScrollButton && showScrollDown && /* @__PURE__ */ jsxs("button", {
|
|
42
|
-
onClick: scrollToBottom,
|
|
43
|
-
className: cn("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"),
|
|
44
|
-
"aria-label": "Scroll to bottom",
|
|
45
|
-
children: [/* @__PURE__ */ jsx("svg", {
|
|
46
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
47
|
-
width: "16",
|
|
48
|
-
height: "16",
|
|
49
|
-
viewBox: "0 0 24 24",
|
|
50
|
-
fill: "none",
|
|
51
|
-
stroke: "currentColor",
|
|
52
|
-
strokeWidth: "2",
|
|
53
|
-
strokeLinecap: "round",
|
|
54
|
-
strokeLinejoin: "round",
|
|
55
|
-
children: /* @__PURE__ */ jsx("path", { d: "m6 9 6 6 6-6" })
|
|
56
|
-
}), "New messages"]
|
|
57
|
-
})]
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
//#endregion
|
|
62
|
-
export { ChatContainer };
|
|
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};
|
|
@@ -1,149 +1 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import { cn } from "../../libs/ui-kit-web/dist/ui/utils.js";
|
|
4
|
-
import { Button$1 } from "../../libs/design-system/dist/components/atoms/Button.js";
|
|
5
|
-
import { Textarea$1 } from "../../libs/design-system/dist/components/atoms/Textarea.js";
|
|
6
|
-
import * as React from "react";
|
|
7
|
-
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
-
import { Code, FileText, Loader2, Paperclip, Send, X } from "lucide-react";
|
|
9
|
-
|
|
10
|
-
//#region src/presentation/components/ChatInput.tsx
|
|
11
|
-
/**
|
|
12
|
-
* Chat input component with attachment support
|
|
13
|
-
*/
|
|
14
|
-
function ChatInput({ onSend, disabled = false, isLoading = false, placeholder = "Type a message...", className, showAttachments = true, maxAttachments = 5 }) {
|
|
15
|
-
const [content, setContent] = React.useState("");
|
|
16
|
-
const [attachments, setAttachments] = React.useState([]);
|
|
17
|
-
const textareaRef = React.useRef(null);
|
|
18
|
-
const fileInputRef = React.useRef(null);
|
|
19
|
-
const canSend = content.trim().length > 0 || attachments.length > 0;
|
|
20
|
-
const handleSubmit = React.useCallback((e) => {
|
|
21
|
-
e?.preventDefault();
|
|
22
|
-
if (!canSend || disabled || isLoading) return;
|
|
23
|
-
onSend(content.trim(), attachments.length > 0 ? attachments : void 0);
|
|
24
|
-
setContent("");
|
|
25
|
-
setAttachments([]);
|
|
26
|
-
textareaRef.current?.focus();
|
|
27
|
-
}, [
|
|
28
|
-
canSend,
|
|
29
|
-
content,
|
|
30
|
-
attachments,
|
|
31
|
-
disabled,
|
|
32
|
-
isLoading,
|
|
33
|
-
onSend
|
|
34
|
-
]);
|
|
35
|
-
const handleKeyDown = React.useCallback((e) => {
|
|
36
|
-
if (e.key === "Enter" && !e.shiftKey) {
|
|
37
|
-
e.preventDefault();
|
|
38
|
-
handleSubmit();
|
|
39
|
-
}
|
|
40
|
-
}, [handleSubmit]);
|
|
41
|
-
const handleFileSelect = React.useCallback(async (e) => {
|
|
42
|
-
const files = e.target.files;
|
|
43
|
-
if (!files) return;
|
|
44
|
-
const newAttachments = [];
|
|
45
|
-
for (const file of Array.from(files)) {
|
|
46
|
-
if (attachments.length + newAttachments.length >= maxAttachments) break;
|
|
47
|
-
const content$1 = await file.text();
|
|
48
|
-
const extension = file.name.split(".").pop()?.toLowerCase() ?? "";
|
|
49
|
-
const isCode = [
|
|
50
|
-
"ts",
|
|
51
|
-
"tsx",
|
|
52
|
-
"js",
|
|
53
|
-
"jsx",
|
|
54
|
-
"py",
|
|
55
|
-
"go",
|
|
56
|
-
"rs",
|
|
57
|
-
"java"
|
|
58
|
-
].includes(extension);
|
|
59
|
-
newAttachments.push({
|
|
60
|
-
id: `att_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`,
|
|
61
|
-
type: isCode ? "code" : "file",
|
|
62
|
-
name: file.name,
|
|
63
|
-
content: content$1,
|
|
64
|
-
mimeType: file.type,
|
|
65
|
-
size: file.size
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
setAttachments((prev) => [...prev, ...newAttachments]);
|
|
69
|
-
e.target.value = "";
|
|
70
|
-
}, [attachments.length, maxAttachments]);
|
|
71
|
-
const removeAttachment = React.useCallback((id) => {
|
|
72
|
-
setAttachments((prev) => prev.filter((a) => a.id !== id));
|
|
73
|
-
}, []);
|
|
74
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
75
|
-
className: cn("flex flex-col gap-2", className),
|
|
76
|
-
children: [
|
|
77
|
-
attachments.length > 0 && /* @__PURE__ */ jsx("div", {
|
|
78
|
-
className: "flex flex-wrap gap-2",
|
|
79
|
-
children: attachments.map((attachment) => /* @__PURE__ */ jsxs("div", {
|
|
80
|
-
className: cn("flex items-center gap-1.5 rounded-md px-2 py-1", "bg-muted text-muted-foreground text-sm"),
|
|
81
|
-
children: [
|
|
82
|
-
attachment.type === "code" ? /* @__PURE__ */ jsx(Code, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx(FileText, { className: "h-3.5 w-3.5" }),
|
|
83
|
-
/* @__PURE__ */ jsx("span", {
|
|
84
|
-
className: "max-w-[150px] truncate",
|
|
85
|
-
children: attachment.name
|
|
86
|
-
}),
|
|
87
|
-
/* @__PURE__ */ jsx("button", {
|
|
88
|
-
type: "button",
|
|
89
|
-
onClick: () => removeAttachment(attachment.id),
|
|
90
|
-
className: "hover:text-foreground",
|
|
91
|
-
"aria-label": `Remove ${attachment.name}`,
|
|
92
|
-
children: /* @__PURE__ */ jsx(X, { className: "h-3.5 w-3.5" })
|
|
93
|
-
})
|
|
94
|
-
]
|
|
95
|
-
}, attachment.id))
|
|
96
|
-
}),
|
|
97
|
-
/* @__PURE__ */ jsxs("form", {
|
|
98
|
-
onSubmit: handleSubmit,
|
|
99
|
-
className: "flex items-end gap-2",
|
|
100
|
-
children: [
|
|
101
|
-
showAttachments && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("input", {
|
|
102
|
-
ref: fileInputRef,
|
|
103
|
-
type: "file",
|
|
104
|
-
multiple: true,
|
|
105
|
-
accept: ".ts,.tsx,.js,.jsx,.json,.md,.txt,.py,.go,.rs,.java,.yaml,.yml",
|
|
106
|
-
onChange: handleFileSelect,
|
|
107
|
-
className: "hidden",
|
|
108
|
-
"aria-label": "Attach files"
|
|
109
|
-
}), /* @__PURE__ */ jsx(Button$1, {
|
|
110
|
-
type: "button",
|
|
111
|
-
variant: "ghost",
|
|
112
|
-
size: "sm",
|
|
113
|
-
onPress: () => fileInputRef.current?.click(),
|
|
114
|
-
disabled: disabled || attachments.length >= maxAttachments,
|
|
115
|
-
"aria-label": "Attach files",
|
|
116
|
-
children: /* @__PURE__ */ jsx(Paperclip, { className: "h-4 w-4" })
|
|
117
|
-
})] }),
|
|
118
|
-
/* @__PURE__ */ jsx("div", {
|
|
119
|
-
className: "relative flex-1",
|
|
120
|
-
children: /* @__PURE__ */ jsx(Textarea$1, {
|
|
121
|
-
value: content,
|
|
122
|
-
onChange: (e) => setContent(e.target.value),
|
|
123
|
-
onKeyDown: handleKeyDown,
|
|
124
|
-
placeholder,
|
|
125
|
-
disabled,
|
|
126
|
-
className: cn("min-h-[44px] max-h-[200px] resize-none pr-12", "focus-visible:ring-1"),
|
|
127
|
-
rows: 1,
|
|
128
|
-
"aria-label": "Chat message"
|
|
129
|
-
})
|
|
130
|
-
}),
|
|
131
|
-
/* @__PURE__ */ jsx(Button$1, {
|
|
132
|
-
type: "submit",
|
|
133
|
-
disabled: !canSend || disabled || isLoading,
|
|
134
|
-
size: "sm",
|
|
135
|
-
"aria-label": isLoading ? "Sending..." : "Send message",
|
|
136
|
-
children: isLoading ? /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ jsx(Send, { className: "h-4 w-4" })
|
|
137
|
-
})
|
|
138
|
-
]
|
|
139
|
-
}),
|
|
140
|
-
/* @__PURE__ */ jsx("p", {
|
|
141
|
-
className: "text-muted-foreground text-xs",
|
|
142
|
-
children: "Press Enter to send, Shift+Enter for new line"
|
|
143
|
-
})
|
|
144
|
-
]
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
//#endregion
|
|
149
|
-
export { ChatInput };
|
|
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};
|