@mordn/chat-widget 0.1.3 → 0.2.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/README.md +285 -245
- package/dist/cli/init.js +120 -93
- package/dist/index.d.mts +19 -4
- package/dist/index.d.ts +19 -4
- package/dist/index.js +94 -45
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +94 -45
- package/dist/index.mjs.map +1 -1
- package/dist/styles.css +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.d.mts
CHANGED
|
@@ -70,21 +70,36 @@ interface StarterPrompt {
|
|
|
70
70
|
}
|
|
71
71
|
interface ThemeConfig {
|
|
72
72
|
/**
|
|
73
|
-
* Theme mode
|
|
73
|
+
* Theme mode — drives the default token values (light vs dark).
|
|
74
74
|
*/
|
|
75
75
|
mode?: 'light' | 'dark';
|
|
76
76
|
/**
|
|
77
|
-
* Primary color (hex)
|
|
77
|
+
* Primary accent color (any CSS color: hex, rgb, hsl, named).
|
|
78
|
+
* Maps to `--chat-primary` on the widget container at runtime.
|
|
78
79
|
*/
|
|
79
80
|
primaryColor?: string;
|
|
80
81
|
/**
|
|
81
|
-
* Background color (
|
|
82
|
+
* Background color (any CSS color).
|
|
83
|
+
* Maps to `--chat-background` on the widget container.
|
|
82
84
|
*/
|
|
83
85
|
backgroundColor?: string;
|
|
84
86
|
/**
|
|
85
|
-
*
|
|
87
|
+
* Body text color (any CSS color).
|
|
88
|
+
* Maps to `--chat-text` on the widget container.
|
|
86
89
|
*/
|
|
87
90
|
textColor?: string;
|
|
91
|
+
/**
|
|
92
|
+
* Fine-grained CSS variable overrides. Keys must start with `--chat-`.
|
|
93
|
+
* Use this when the three high-level props above aren't enough — e.g. to
|
|
94
|
+
* theme the header glass, hover state, or surface tones independently.
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* tokens: {
|
|
98
|
+
* '--chat-surface': '0 0% 96%', // HSL-triplet tokens
|
|
99
|
+
* '--chat-header-bg': 'rgba(0,0,0,0.7)', // direct color tokens
|
|
100
|
+
* }
|
|
101
|
+
*/
|
|
102
|
+
tokens?: Record<`--chat-${string}`, string>;
|
|
88
103
|
}
|
|
89
104
|
interface FeatureConfig {
|
|
90
105
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -70,21 +70,36 @@ interface StarterPrompt {
|
|
|
70
70
|
}
|
|
71
71
|
interface ThemeConfig {
|
|
72
72
|
/**
|
|
73
|
-
* Theme mode
|
|
73
|
+
* Theme mode — drives the default token values (light vs dark).
|
|
74
74
|
*/
|
|
75
75
|
mode?: 'light' | 'dark';
|
|
76
76
|
/**
|
|
77
|
-
* Primary color (hex)
|
|
77
|
+
* Primary accent color (any CSS color: hex, rgb, hsl, named).
|
|
78
|
+
* Maps to `--chat-primary` on the widget container at runtime.
|
|
78
79
|
*/
|
|
79
80
|
primaryColor?: string;
|
|
80
81
|
/**
|
|
81
|
-
* Background color (
|
|
82
|
+
* Background color (any CSS color).
|
|
83
|
+
* Maps to `--chat-background` on the widget container.
|
|
82
84
|
*/
|
|
83
85
|
backgroundColor?: string;
|
|
84
86
|
/**
|
|
85
|
-
*
|
|
87
|
+
* Body text color (any CSS color).
|
|
88
|
+
* Maps to `--chat-text` on the widget container.
|
|
86
89
|
*/
|
|
87
90
|
textColor?: string;
|
|
91
|
+
/**
|
|
92
|
+
* Fine-grained CSS variable overrides. Keys must start with `--chat-`.
|
|
93
|
+
* Use this when the three high-level props above aren't enough — e.g. to
|
|
94
|
+
* theme the header glass, hover state, or surface tones independently.
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* tokens: {
|
|
98
|
+
* '--chat-surface': '0 0% 96%', // HSL-triplet tokens
|
|
99
|
+
* '--chat-header-bg': 'rgba(0,0,0,0.7)', // direct color tokens
|
|
100
|
+
* }
|
|
101
|
+
*/
|
|
102
|
+
tokens?: Record<`--chat-${string}`, string>;
|
|
88
103
|
}
|
|
89
104
|
interface FeatureConfig {
|
|
90
105
|
/**
|
package/dist/index.js
CHANGED
|
@@ -227,7 +227,7 @@ function Textarea({ className, ...props }) {
|
|
|
227
227
|
{
|
|
228
228
|
"data-slot": "textarea",
|
|
229
229
|
className: cn(
|
|
230
|
-
"border-input placeholder:text-muted-foreground focus-visible:border-ring aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-
|
|
230
|
+
"border-input placeholder:text-muted-foreground focus-visible:border-ring aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-[14px] transition-colors outline-none disabled:cursor-not-allowed disabled:opacity-50",
|
|
231
231
|
className
|
|
232
232
|
),
|
|
233
233
|
...props
|
|
@@ -1920,20 +1920,20 @@ function ChatInterface({ id, initialMessages, config, onClose } = {}) {
|
|
|
1920
1920
|
),
|
|
1921
1921
|
children: [
|
|
1922
1922
|
/* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "flex items-center gap-2 px-3 py-2 border-b backdrop-blur-sm relative z-20", style: {
|
|
1923
|
-
borderColor:
|
|
1924
|
-
backgroundColor:
|
|
1923
|
+
borderColor: "var(--chat-border-soft)",
|
|
1924
|
+
backgroundColor: "var(--chat-header-bg)"
|
|
1925
1925
|
}, children: [
|
|
1926
1926
|
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "flex items-center gap-1 flex-1 min-w-0 overflow-x-auto scrollbar-hide py-0.5 scroll-smooth", children: tabs.map((tab, index) => /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
|
|
1927
1927
|
"div",
|
|
1928
1928
|
{
|
|
1929
1929
|
className: "relative flex items-center gap-1.5 px-3 py-1.5 rounded-lg cursor-pointer transition-all duration-150 group flex-shrink-0 min-w-0",
|
|
1930
1930
|
style: {
|
|
1931
|
-
backgroundColor: tab.isActive ?
|
|
1932
|
-
color: tab.isActive ?
|
|
1931
|
+
backgroundColor: tab.isActive ? "hsl(var(--chat-surface))" : "transparent",
|
|
1932
|
+
color: tab.isActive ? "hsl(var(--chat-text))" : "hsl(var(--chat-text-muted))"
|
|
1933
1933
|
},
|
|
1934
1934
|
onMouseEnter: (e) => {
|
|
1935
1935
|
if (!tab.isActive) {
|
|
1936
|
-
e.currentTarget.style.backgroundColor =
|
|
1936
|
+
e.currentTarget.style.backgroundColor = "var(--chat-hover-bg)";
|
|
1937
1937
|
}
|
|
1938
1938
|
},
|
|
1939
1939
|
onMouseLeave: (e) => {
|
|
@@ -1960,7 +1960,7 @@ function ChatInterface({ id, initialMessages, config, onClose } = {}) {
|
|
|
1960
1960
|
},
|
|
1961
1961
|
onMouseEnter: (e) => {
|
|
1962
1962
|
e.currentTarget.style.opacity = "1";
|
|
1963
|
-
e.currentTarget.style.backgroundColor =
|
|
1963
|
+
e.currentTarget.style.backgroundColor = "hsl(var(--chat-surface-hover))";
|
|
1964
1964
|
},
|
|
1965
1965
|
onMouseLeave: (e) => {
|
|
1966
1966
|
e.currentTarget.style.opacity = tab.isActive ? "0.6" : "0";
|
|
@@ -1980,14 +1980,14 @@ function ChatInterface({ id, initialMessages, config, onClose } = {}) {
|
|
|
1980
1980
|
onClick: createNewTab,
|
|
1981
1981
|
className: "flex items-center justify-center w-7 h-7 rounded-lg transition-all duration-150",
|
|
1982
1982
|
style: {
|
|
1983
|
-
color:
|
|
1983
|
+
color: "hsl(var(--chat-text-muted))"
|
|
1984
1984
|
},
|
|
1985
1985
|
onMouseEnter: (e) => {
|
|
1986
|
-
e.currentTarget.style.color =
|
|
1987
|
-
e.currentTarget.style.backgroundColor =
|
|
1986
|
+
e.currentTarget.style.color = "hsl(var(--chat-text))";
|
|
1987
|
+
e.currentTarget.style.backgroundColor = "hsl(var(--chat-surface))";
|
|
1988
1988
|
},
|
|
1989
1989
|
onMouseLeave: (e) => {
|
|
1990
|
-
e.currentTarget.style.color =
|
|
1990
|
+
e.currentTarget.style.color = "hsl(var(--chat-text-muted))";
|
|
1991
1991
|
e.currentTarget.style.backgroundColor = "transparent";
|
|
1992
1992
|
},
|
|
1993
1993
|
title: "New Chat",
|
|
@@ -2001,18 +2001,18 @@ function ChatInterface({ id, initialMessages, config, onClose } = {}) {
|
|
|
2001
2001
|
onClick: () => setShowHistory(!showHistory),
|
|
2002
2002
|
className: "flex items-center justify-center w-7 h-7 rounded-lg transition-all duration-150",
|
|
2003
2003
|
style: {
|
|
2004
|
-
color: showHistory ?
|
|
2005
|
-
backgroundColor: showHistory ?
|
|
2004
|
+
color: showHistory ? "hsl(var(--chat-text))" : "hsl(var(--chat-text-muted))",
|
|
2005
|
+
backgroundColor: showHistory ? "hsl(var(--chat-surface))" : "transparent"
|
|
2006
2006
|
},
|
|
2007
2007
|
onMouseEnter: (e) => {
|
|
2008
2008
|
if (!showHistory) {
|
|
2009
|
-
e.currentTarget.style.color =
|
|
2010
|
-
e.currentTarget.style.backgroundColor =
|
|
2009
|
+
e.currentTarget.style.color = "hsl(var(--chat-text))";
|
|
2010
|
+
e.currentTarget.style.backgroundColor = "hsl(var(--chat-surface))";
|
|
2011
2011
|
}
|
|
2012
2012
|
},
|
|
2013
2013
|
onMouseLeave: (e) => {
|
|
2014
2014
|
if (!showHistory) {
|
|
2015
|
-
e.currentTarget.style.color =
|
|
2015
|
+
e.currentTarget.style.color = "hsl(var(--chat-text-muted))";
|
|
2016
2016
|
e.currentTarget.style.backgroundColor = "transparent";
|
|
2017
2017
|
}
|
|
2018
2018
|
},
|
|
@@ -2021,15 +2021,15 @@ function ChatInterface({ id, initialMessages, config, onClose } = {}) {
|
|
|
2021
2021
|
}
|
|
2022
2022
|
),
|
|
2023
2023
|
showHistory && /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "absolute right-0 top-full mt-1.5 w-72 rounded-xl shadow-[0_4px_24px_rgba(0,0,0,0.08)] dark:shadow-[0_4px_24px_rgba(0,0,0,0.3)] z-50 animate-in fade-in slide-in-from-top-1 duration-150 overflow-hidden", style: {
|
|
2024
|
-
backgroundColor:
|
|
2025
|
-
border: `1px solid ${
|
|
2024
|
+
backgroundColor: "hsl(var(--chat-background))",
|
|
2025
|
+
border: `1px solid ${"var(--chat-border-medium)"}`
|
|
2026
2026
|
}, children: [
|
|
2027
2027
|
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "p-2.5 border-b", style: {
|
|
2028
|
-
borderColor:
|
|
2029
|
-
backgroundColor:
|
|
2028
|
+
borderColor: "var(--chat-border-soft)",
|
|
2029
|
+
backgroundColor: "var(--chat-overlay)"
|
|
2030
2030
|
}, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "relative", children: [
|
|
2031
2031
|
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react10.SearchIcon, { className: "absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5", style: {
|
|
2032
|
-
color:
|
|
2032
|
+
color: "hsl(var(--chat-text-muted))"
|
|
2033
2033
|
}, strokeWidth: 2 }),
|
|
2034
2034
|
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
|
|
2035
2035
|
"input",
|
|
@@ -2040,29 +2040,29 @@ function ChatInterface({ id, initialMessages, config, onClose } = {}) {
|
|
|
2040
2040
|
onChange: (e) => setSearchQuery(e.target.value),
|
|
2041
2041
|
className: "w-full h-7 pl-8 pr-2.5 text-[13px] rounded-lg focus:outline-none transition-all",
|
|
2042
2042
|
style: {
|
|
2043
|
-
backgroundColor:
|
|
2044
|
-
border: `1px solid ${
|
|
2045
|
-
color:
|
|
2043
|
+
backgroundColor: "hsl(var(--chat-surface-deep))",
|
|
2044
|
+
border: `1px solid ${"var(--chat-border-medium)"}`,
|
|
2045
|
+
color: "hsl(var(--chat-text))"
|
|
2046
2046
|
}
|
|
2047
2047
|
}
|
|
2048
2048
|
)
|
|
2049
2049
|
] }) }),
|
|
2050
|
-
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "max-h-[300px] overflow-y-auto ai-assistant-scrollbar", children: loadingHistory ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "flex items-center justify-center py-12", children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "text-[13px]", style: { color:
|
|
2050
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "max-h-[300px] overflow-y-auto ai-assistant-scrollbar", children: loadingHistory ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "flex items-center justify-center py-12", children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "text-[13px]", style: { color: "hsl(var(--chat-text-muted))" }, children: "Loading..." }) }) : conversations.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "flex flex-col items-center justify-center py-12 px-4 text-center", children: [
|
|
2051
2051
|
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "w-12 h-12 rounded-full flex items-center justify-center mb-3", style: {
|
|
2052
|
-
backgroundColor:
|
|
2053
|
-
}, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react10.MessageSquareIcon, { className: "h-5 w-5", style: { color:
|
|
2054
|
-
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)("p", { className: "text-[13px] font-medium mb-0.5", style: { color:
|
|
2055
|
-
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)("p", { className: "text-[12px]", style: { color:
|
|
2052
|
+
backgroundColor: "hsl(var(--chat-surface))"
|
|
2053
|
+
}, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react10.MessageSquareIcon, { className: "h-5 w-5", style: { color: "hsl(var(--chat-text-subtle))" }, strokeWidth: 2 }) }),
|
|
2054
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)("p", { className: "text-[13px] font-medium mb-0.5", style: { color: "hsl(var(--chat-text))" }, children: "No Conversations" }),
|
|
2055
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)("p", { className: "text-[12px]", style: { color: "hsl(var(--chat-text-muted))" }, children: "Start a new chat to begin" })
|
|
2056
2056
|
] }) : groupedConversations.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "flex flex-col items-center justify-center py-12 px-4 text-center", children: [
|
|
2057
2057
|
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "w-12 h-12 rounded-full flex items-center justify-center mb-3", style: {
|
|
2058
|
-
backgroundColor:
|
|
2059
|
-
}, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react10.SearchIcon, { className: "h-5 w-5", style: { color:
|
|
2060
|
-
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)("p", { className: "text-[13px] font-medium mb-0.5", style: { color:
|
|
2061
|
-
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)("p", { className: "text-[12px]", style: { color:
|
|
2058
|
+
backgroundColor: "hsl(var(--chat-surface))"
|
|
2059
|
+
}, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react10.SearchIcon, { className: "h-5 w-5", style: { color: "hsl(var(--chat-text-subtle))" }, strokeWidth: 2 }) }),
|
|
2060
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)("p", { className: "text-[13px] font-medium mb-0.5", style: { color: "hsl(var(--chat-text))" }, children: "No Results" }),
|
|
2061
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)("p", { className: "text-[12px]", style: { color: "hsl(var(--chat-text-muted))" }, children: "Try a different search" })
|
|
2062
2062
|
] }) : /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "py-0.5", children: groupedConversations.map(([groupName, groupConversations]) => /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "mb-0.5", children: [
|
|
2063
2063
|
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "px-2.5 py-1 sticky top-0 backdrop-blur-sm z-10", style: {
|
|
2064
|
-
backgroundColor:
|
|
2065
|
-
}, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("h3", { className: "text-[10px] font-semibold uppercase tracking-wide", style: { color:
|
|
2064
|
+
backgroundColor: "var(--chat-header-bg-strong)"
|
|
2065
|
+
}, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("h3", { className: "text-[10px] font-semibold uppercase tracking-wide", style: { color: "hsl(var(--chat-text-muted))" }, children: groupName }) }),
|
|
2066
2066
|
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "px-1 space-y-0.5", children: groupConversations.map((conversation) => {
|
|
2067
2067
|
const isActiveConversation = activeTabId === conversation.id;
|
|
2068
2068
|
return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
|
|
@@ -2070,11 +2070,11 @@ function ChatInterface({ id, initialMessages, config, onClose } = {}) {
|
|
|
2070
2070
|
{
|
|
2071
2071
|
className: "w-full px-2 py-1 rounded-md transition-all duration-150 text-left group relative",
|
|
2072
2072
|
style: {
|
|
2073
|
-
backgroundColor: isActiveConversation ?
|
|
2073
|
+
backgroundColor: isActiveConversation ? "hsl(var(--chat-surface))" : "transparent"
|
|
2074
2074
|
},
|
|
2075
2075
|
onMouseEnter: (e) => {
|
|
2076
2076
|
if (!isActiveConversation) {
|
|
2077
|
-
e.currentTarget.style.backgroundColor =
|
|
2077
|
+
e.currentTarget.style.backgroundColor = "var(--chat-hover-bg)";
|
|
2078
2078
|
}
|
|
2079
2079
|
},
|
|
2080
2080
|
onMouseLeave: (e) => {
|
|
@@ -2086,14 +2086,14 @@ function ChatInterface({ id, initialMessages, config, onClose } = {}) {
|
|
|
2086
2086
|
children: /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "flex items-center gap-1.5", children: [
|
|
2087
2087
|
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "flex-1 min-w-0", children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("p", { className: "text-[12px] line-clamp-1 transition-colors leading-tight", style: {
|
|
2088
2088
|
fontWeight: isActiveConversation ? 500 : 400,
|
|
2089
|
-
color: isActiveConversation ?
|
|
2089
|
+
color: isActiveConversation ? "hsl(var(--chat-text))" : "hsl(var(--chat-text-strong))"
|
|
2090
2090
|
}, children: conversation.title }) }),
|
|
2091
2091
|
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
|
|
2092
2092
|
import_lucide_react10.ChevronRightIcon,
|
|
2093
2093
|
{
|
|
2094
2094
|
className: "h-3 w-3 transition-all duration-150 flex-shrink-0",
|
|
2095
2095
|
style: {
|
|
2096
|
-
color:
|
|
2096
|
+
color: "hsl(var(--chat-text-subtle))",
|
|
2097
2097
|
opacity: isActiveConversation ? 1 : 0
|
|
2098
2098
|
},
|
|
2099
2099
|
strokeWidth: 2
|
|
@@ -2113,14 +2113,14 @@ function ChatInterface({ id, initialMessages, config, onClose } = {}) {
|
|
|
2113
2113
|
onClick: onClose,
|
|
2114
2114
|
className: "flex items-center justify-center w-7 h-7 rounded-lg transition-all duration-150",
|
|
2115
2115
|
style: {
|
|
2116
|
-
color:
|
|
2116
|
+
color: "hsl(var(--chat-text-muted))"
|
|
2117
2117
|
},
|
|
2118
2118
|
onMouseEnter: (e) => {
|
|
2119
|
-
e.currentTarget.style.color =
|
|
2120
|
-
e.currentTarget.style.backgroundColor =
|
|
2119
|
+
e.currentTarget.style.color = "hsl(var(--chat-text))";
|
|
2120
|
+
e.currentTarget.style.backgroundColor = "hsl(var(--chat-surface))";
|
|
2121
2121
|
},
|
|
2122
2122
|
onMouseLeave: (e) => {
|
|
2123
|
-
e.currentTarget.style.color =
|
|
2123
|
+
e.currentTarget.style.color = "hsl(var(--chat-text-muted))";
|
|
2124
2124
|
e.currentTarget.style.backgroundColor = "transparent";
|
|
2125
2125
|
},
|
|
2126
2126
|
title: "Close Chat",
|
|
@@ -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
|
] })
|
|
@@ -2171,6 +2171,43 @@ function ChatInterface({ id, initialMessages, config, onClose } = {}) {
|
|
|
2171
2171
|
|
|
2172
2172
|
// src/ChatWidget.tsx
|
|
2173
2173
|
var import_lucide_react11 = require("lucide-react");
|
|
2174
|
+
|
|
2175
|
+
// src/utils/color.ts
|
|
2176
|
+
function toHslTripletIfHex(value) {
|
|
2177
|
+
const trimmed = value.trim();
|
|
2178
|
+
const hexMatch = /^#?([0-9a-fA-F]{6}|[0-9a-fA-F]{3})$/.exec(trimmed);
|
|
2179
|
+
if (!hexMatch) return value;
|
|
2180
|
+
let hex = hexMatch[1];
|
|
2181
|
+
if (hex.length === 3) {
|
|
2182
|
+
hex = hex.split("").map((c) => c + c).join("");
|
|
2183
|
+
}
|
|
2184
|
+
const r = parseInt(hex.substring(0, 2), 16) / 255;
|
|
2185
|
+
const g = parseInt(hex.substring(2, 4), 16) / 255;
|
|
2186
|
+
const b = parseInt(hex.substring(4, 6), 16) / 255;
|
|
2187
|
+
const max = Math.max(r, g, b);
|
|
2188
|
+
const min = Math.min(r, g, b);
|
|
2189
|
+
const l = (max + min) / 2;
|
|
2190
|
+
let h = 0;
|
|
2191
|
+
let s = 0;
|
|
2192
|
+
if (max !== min) {
|
|
2193
|
+
const d = max - min;
|
|
2194
|
+
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
2195
|
+
switch (max) {
|
|
2196
|
+
case r:
|
|
2197
|
+
h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
|
|
2198
|
+
break;
|
|
2199
|
+
case g:
|
|
2200
|
+
h = ((b - r) / d + 2) / 6;
|
|
2201
|
+
break;
|
|
2202
|
+
case b:
|
|
2203
|
+
h = ((r - g) / d + 4) / 6;
|
|
2204
|
+
break;
|
|
2205
|
+
}
|
|
2206
|
+
}
|
|
2207
|
+
return `${Math.round(h * 360)} ${Math.round(s * 100)}% ${Math.round(l * 100)}%`;
|
|
2208
|
+
}
|
|
2209
|
+
|
|
2210
|
+
// src/ChatWidget.tsx
|
|
2174
2211
|
var import_jsx_runtime21 = require("react/jsx-runtime");
|
|
2175
2212
|
function ChatWidget({
|
|
2176
2213
|
userId,
|
|
@@ -2196,8 +2233,20 @@ function ChatWidget({
|
|
|
2196
2233
|
if (display?.width) {
|
|
2197
2234
|
styles["--chat-widget-width"] = display.width;
|
|
2198
2235
|
}
|
|
2236
|
+
if (theme?.primaryColor) {
|
|
2237
|
+
styles["--chat-primary"] = toHslTripletIfHex(theme.primaryColor);
|
|
2238
|
+
}
|
|
2239
|
+
if (theme?.backgroundColor) {
|
|
2240
|
+
styles["--chat-background"] = toHslTripletIfHex(theme.backgroundColor);
|
|
2241
|
+
}
|
|
2242
|
+
if (theme?.textColor) {
|
|
2243
|
+
styles["--chat-text"] = toHslTripletIfHex(theme.textColor);
|
|
2244
|
+
}
|
|
2245
|
+
if (theme?.tokens) {
|
|
2246
|
+
Object.assign(styles, theme.tokens);
|
|
2247
|
+
}
|
|
2199
2248
|
return styles;
|
|
2200
|
-
}, [display?.width]);
|
|
2249
|
+
}, [display?.width, theme?.primaryColor, theme?.backgroundColor, theme?.textColor, theme?.tokens]);
|
|
2201
2250
|
const handleMouseDown = (0, import_react12.useCallback)((e) => {
|
|
2202
2251
|
if (!resizable) return;
|
|
2203
2252
|
e.preventDefault();
|