@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/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
- const body = await req.json();
83
- const userId = req.headers.get('X-User-Id');
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
- // Check if conversation exists, create if not
95
- const existingConv = await db
96
- .select({ id: conversations.id })
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
- if (!existingConv.length) {
102
- await db.insert(conversations).values({
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
- // Save the new user message
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
- const existingMsg = await db
118
- .select({ id: messages.id })
119
- .from(messages)
120
- .where(eq(messages.id, newUserMessage.id))
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 (!existingMsg.length) {
124
- await db.insert(messages).values({
125
- id: newUserMessage.id,
126
- conversationId: id,
127
- role: newUserMessage.role,
128
- content: textPart?.text || '',
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
- // Update conversation title if needed
136
- if (textPart?.text) {
137
- const conv = await db
138
- .select({ title: conversations.title })
139
- .from(conversations)
140
- .where(eq(conversations.id, id))
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 (conv[0]?.title === 'New Chat') {
144
- await updateConversationTitle(id, textPart.text.slice(0, 100));
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
- // Transform messages for AI (handle images)
150
- const transformedMessages = chatMessages.map(msg => {
151
- if (msg.role === 'user' && msg.parts) {
152
- const textPart = msg.parts.find(p => p.type === 'text');
153
- const fileParts = msg.parts.filter(p => p.type === 'file');
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
- if (fileParts.length > 0) {
156
- const content: any[] = [];
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
- const result = streamText({
172
- model: model,
173
- messages: convertToModelMessages(transformedMessages),
174
- system: systemPrompt,
175
- temperature: temperature,
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
- return result.toUIMessageStreamResponse({
179
- sendSources: true,
180
- sendReasoning: true,
181
- onFinish: ({ messages: finalMessages }) => {
182
- if (finalMessages.length > 0) {
183
- saveChat({ chatId: id, messages: finalMessages, model, userId });
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
- console.log(" 3. Add the ChatWidget to your app:\n");
420
- console.log(" import { ChatWidget } from '@mordn/chat-widget';");
421
- console.log(" import '@mordn/chat-widget/styles.css';");
422
- console.log("");
423
- console.log(' <ChatWidget userId="user-123" />\n');
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 !== false && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(AttachButton, {}) }),
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
  ] })