@mordn/chat-widget 0.1.3 → 0.1.4
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 +285 -245
- package/dist/cli/init.js +120 -93
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -1
package/dist/cli/init.js
CHANGED
|
@@ -79,111 +79,119 @@ const DEVELOPER_CONFIG = {
|
|
|
79
79
|
};
|
|
80
80
|
|
|
81
81
|
export async function POST(req: Request) {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
if (!userId) {
|
|
86
|
-
return new Response('userId is required in X-User-Id header', { status: 400 });
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const chatMessages: UIMessage[] = body.messages || [];
|
|
90
|
-
const id: string = body.id || 'temp-id';
|
|
91
|
-
|
|
92
|
-
const { model, systemPrompt, temperature } = DEVELOPER_CONFIG;
|
|
82
|
+
try {
|
|
83
|
+
const body = await req.json();
|
|
84
|
+
const userId = req.headers.get('X-User-Id');
|
|
93
85
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
.from(conversations)
|
|
98
|
-
.where(eq(conversations.id, id))
|
|
99
|
-
.limit(1);
|
|
86
|
+
if (!userId) {
|
|
87
|
+
return new Response('userId is required in X-User-Id header', { status: 400 });
|
|
88
|
+
}
|
|
100
89
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
id,
|
|
104
|
-
userId,
|
|
105
|
-
title: 'New Chat',
|
|
106
|
-
metadata: {},
|
|
107
|
-
});
|
|
108
|
-
}
|
|
90
|
+
const chatMessages: UIMessage[] = body.messages || [];
|
|
91
|
+
const id: string = body.id || 'temp-id';
|
|
109
92
|
|
|
110
|
-
|
|
111
|
-
const userMessages = chatMessages.filter(msg => msg.role === 'user');
|
|
112
|
-
if (userMessages.length > 0) {
|
|
113
|
-
const newUserMessage = userMessages[userMessages.length - 1];
|
|
114
|
-
const textPart = newUserMessage.parts?.find(p => p.type === 'text') as { text: string } | undefined;
|
|
115
|
-
const fileParts = newUserMessage.parts?.filter(p => p.type === 'file') || [];
|
|
93
|
+
const { model, systemPrompt, temperature } = DEVELOPER_CONFIG;
|
|
116
94
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
.
|
|
120
|
-
.
|
|
95
|
+
// Check if conversation exists, create if not
|
|
96
|
+
const existingConv = await db
|
|
97
|
+
.select({ id: conversations.id })
|
|
98
|
+
.from(conversations)
|
|
99
|
+
.where(eq(conversations.id, id))
|
|
121
100
|
.limit(1);
|
|
122
101
|
|
|
123
|
-
if (!
|
|
124
|
-
await db.insert(
|
|
125
|
-
id
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
files: fileParts,
|
|
130
|
-
model: model,
|
|
131
|
-
metadata: { parts: newUserMessage.parts || [] },
|
|
102
|
+
if (!existingConv.length) {
|
|
103
|
+
await db.insert(conversations).values({
|
|
104
|
+
id,
|
|
105
|
+
userId,
|
|
106
|
+
title: 'New Chat',
|
|
107
|
+
metadata: {},
|
|
132
108
|
});
|
|
133
109
|
}
|
|
134
110
|
|
|
135
|
-
//
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
111
|
+
// Save the new user message
|
|
112
|
+
const userMessages = chatMessages.filter(msg => msg.role === 'user');
|
|
113
|
+
if (userMessages.length > 0) {
|
|
114
|
+
const newUserMessage = userMessages[userMessages.length - 1];
|
|
115
|
+
const textPart = newUserMessage.parts?.find(p => p.type === 'text') as { text: string } | undefined;
|
|
116
|
+
const fileParts = newUserMessage.parts?.filter(p => p.type === 'file') || [];
|
|
117
|
+
|
|
118
|
+
const existingMsg = await db
|
|
119
|
+
.select({ id: messages.id })
|
|
120
|
+
.from(messages)
|
|
121
|
+
.where(eq(messages.id, newUserMessage.id))
|
|
141
122
|
.limit(1);
|
|
142
123
|
|
|
143
|
-
if (
|
|
144
|
-
await
|
|
124
|
+
if (!existingMsg.length) {
|
|
125
|
+
await db.insert(messages).values({
|
|
126
|
+
id: newUserMessage.id,
|
|
127
|
+
conversationId: id,
|
|
128
|
+
role: newUserMessage.role,
|
|
129
|
+
content: textPart?.text || '',
|
|
130
|
+
files: fileParts,
|
|
131
|
+
model: model,
|
|
132
|
+
metadata: { parts: newUserMessage.parts || [] },
|
|
133
|
+
});
|
|
145
134
|
}
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
135
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
136
|
+
// Update conversation title if needed
|
|
137
|
+
if (textPart?.text) {
|
|
138
|
+
const conv = await db
|
|
139
|
+
.select({ title: conversations.title })
|
|
140
|
+
.from(conversations)
|
|
141
|
+
.where(eq(conversations.id, id))
|
|
142
|
+
.limit(1);
|
|
154
143
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
if (textPart && 'text' in textPart) {
|
|
158
|
-
content.push({ type: 'text', text: textPart.text });
|
|
144
|
+
if (conv[0]?.title === 'New Chat') {
|
|
145
|
+
await updateConversationTitle(id, textPart.text.slice(0, 100));
|
|
159
146
|
}
|
|
160
|
-
for (const file of fileParts) {
|
|
161
|
-
if ('mediaType' in file && (file as any).mediaType?.startsWith('image/')) {
|
|
162
|
-
content.push({ type: 'image', image: (file as any).url });
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
return { ...msg, content };
|
|
166
147
|
}
|
|
167
148
|
}
|
|
168
|
-
return msg;
|
|
169
|
-
});
|
|
170
149
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
});
|
|
150
|
+
// Transform messages for AI (handle images)
|
|
151
|
+
const transformedMessages = chatMessages.map(msg => {
|
|
152
|
+
if (msg.role === 'user' && msg.parts) {
|
|
153
|
+
const textPart = msg.parts.find(p => p.type === 'text');
|
|
154
|
+
const fileParts = msg.parts.filter(p => p.type === 'file');
|
|
177
155
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
156
|
+
if (fileParts.length > 0) {
|
|
157
|
+
const content: any[] = [];
|
|
158
|
+
if (textPart && 'text' in textPart) {
|
|
159
|
+
content.push({ type: 'text', text: textPart.text });
|
|
160
|
+
}
|
|
161
|
+
for (const file of fileParts) {
|
|
162
|
+
if ('mediaType' in file && (file as any).mediaType?.startsWith('image/')) {
|
|
163
|
+
content.push({ type: 'image', image: (file as any).url });
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return { ...msg, content };
|
|
167
|
+
}
|
|
184
168
|
}
|
|
185
|
-
|
|
186
|
-
|
|
169
|
+
return msg;
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
const result = streamText({
|
|
173
|
+
model: model,
|
|
174
|
+
messages: convertToModelMessages(transformedMessages),
|
|
175
|
+
system: systemPrompt,
|
|
176
|
+
temperature: temperature,
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
return result.toUIMessageStreamResponse({
|
|
180
|
+
sendSources: true,
|
|
181
|
+
sendReasoning: true,
|
|
182
|
+
onFinish: ({ messages: finalMessages }) => {
|
|
183
|
+
if (finalMessages.length > 0) {
|
|
184
|
+
saveChat({ chatId: id, messages: finalMessages, model, userId });
|
|
185
|
+
}
|
|
186
|
+
},
|
|
187
|
+
});
|
|
188
|
+
} catch (error) {
|
|
189
|
+
console.error('Chat API error:', error);
|
|
190
|
+
return new Response(JSON.stringify({ error: 'Internal server error' }), {
|
|
191
|
+
status: 500,
|
|
192
|
+
headers: { 'Content-Type': 'application/json' },
|
|
193
|
+
});
|
|
194
|
+
}
|
|
187
195
|
}
|
|
188
196
|
`;
|
|
189
197
|
var HISTORY_ROUTE = `import { NextResponse } from 'next/server';
|
|
@@ -291,11 +299,19 @@ export async function GET(
|
|
|
291
299
|
var UPLOAD_ROUTE = `import { createClient } from '@supabase/supabase-js';
|
|
292
300
|
import { nanoid } from 'nanoid';
|
|
293
301
|
|
|
294
|
-
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
|
|
295
|
-
const supabaseServiceKey = process.env.SUPABASE_SERVICE_ROLE_KEY!;
|
|
296
|
-
|
|
297
302
|
export async function POST(req: Request) {
|
|
298
303
|
try {
|
|
304
|
+
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
|
|
305
|
+
const supabaseServiceKey = process.env.SUPABASE_SERVICE_ROLE_KEY;
|
|
306
|
+
|
|
307
|
+
// Check for required environment variables
|
|
308
|
+
if (!supabaseUrl || !supabaseServiceKey) {
|
|
309
|
+
console.error('Missing Supabase environment variables. Please set NEXT_PUBLIC_SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY');
|
|
310
|
+
return Response.json({
|
|
311
|
+
error: 'File upload is not configured. Please set up Supabase Storage environment variables.'
|
|
312
|
+
}, { status: 503 });
|
|
313
|
+
}
|
|
314
|
+
|
|
299
315
|
const formData = await req.formData();
|
|
300
316
|
const file = formData.get('file') as File;
|
|
301
317
|
const conversationId = formData.get('conversationId') as string;
|
|
@@ -398,9 +414,11 @@ async function init() {
|
|
|
398
414
|
filesCreated++;
|
|
399
415
|
}
|
|
400
416
|
const createUpload = await confirm("\nCreate file upload route? (requires Supabase Storage)");
|
|
417
|
+
let uploadRouteCreated = false;
|
|
401
418
|
if (createUpload) {
|
|
402
419
|
if (await writeFileWithConfirm(path.join(apiChatDir, "upload", "route.ts"), UPLOAD_ROUTE)) {
|
|
403
420
|
filesCreated++;
|
|
421
|
+
uploadRouteCreated = true;
|
|
404
422
|
}
|
|
405
423
|
}
|
|
406
424
|
console.log("\nCreating configuration files...");
|
|
@@ -416,11 +434,20 @@ async function init() {
|
|
|
416
434
|
console.log("Next steps:");
|
|
417
435
|
console.log(" 1. Copy .env.example to .env.local and fill in your credentials");
|
|
418
436
|
console.log(" 2. Run: npx drizzle-kit push");
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
437
|
+
if (uploadRouteCreated) {
|
|
438
|
+
console.log(' 3. Create a "chat-attachments" bucket in Supabase Storage');
|
|
439
|
+
console.log(" 4. Add the ChatWidget with file uploads enabled:\n");
|
|
440
|
+
console.log(" import { ChatWidget } from '@mordn/chat-widget';");
|
|
441
|
+
console.log(" import '@mordn/chat-widget/styles.css';");
|
|
442
|
+
console.log("");
|
|
443
|
+
console.log(' <ChatWidget userId="user-123" features={{ fileUpload: true }} />\n');
|
|
444
|
+
} else {
|
|
445
|
+
console.log(" 3. Add the ChatWidget to your app:\n");
|
|
446
|
+
console.log(" import { ChatWidget } from '@mordn/chat-widget';");
|
|
447
|
+
console.log(" import '@mordn/chat-widget/styles.css';");
|
|
448
|
+
console.log("");
|
|
449
|
+
console.log(' <ChatWidget userId="user-123" />\n');
|
|
450
|
+
}
|
|
424
451
|
rl.close();
|
|
425
452
|
}
|
|
426
453
|
init().catch((error) => {
|
package/dist/index.js
CHANGED
|
@@ -2159,7 +2159,7 @@ function ChatInterface({ id, initialMessages, config, onClose } = {}) {
|
|
|
2159
2159
|
)
|
|
2160
2160
|
] }),
|
|
2161
2161
|
/* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(PromptInputToolbar, { children: [
|
|
2162
|
-
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)(PromptInputTools, { children: config?.features?.fileUpload
|
|
2162
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)(PromptInputTools, { children: config?.features?.fileUpload === true && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(AttachButton, {}) }),
|
|
2163
2163
|
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)(PromptInputSubmit, { disabled: !input, status })
|
|
2164
2164
|
] })
|
|
2165
2165
|
] })
|