@usesidekick/react 0.1.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.
Files changed (48) hide show
  1. package/README.md +246 -0
  2. package/dist/index.d.mts +358 -0
  3. package/dist/index.d.ts +358 -0
  4. package/dist/index.js +2470 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/index.mjs +2403 -0
  7. package/dist/index.mjs.map +1 -0
  8. package/dist/jsx-dev-runtime.d.mts +21 -0
  9. package/dist/jsx-dev-runtime.d.ts +21 -0
  10. package/dist/jsx-dev-runtime.js +160 -0
  11. package/dist/jsx-dev-runtime.js.map +1 -0
  12. package/dist/jsx-dev-runtime.mjs +122 -0
  13. package/dist/jsx-dev-runtime.mjs.map +1 -0
  14. package/dist/jsx-runtime.d.mts +26 -0
  15. package/dist/jsx-runtime.d.ts +26 -0
  16. package/dist/jsx-runtime.js +150 -0
  17. package/dist/jsx-runtime.js.map +1 -0
  18. package/dist/jsx-runtime.mjs +109 -0
  19. package/dist/jsx-runtime.mjs.map +1 -0
  20. package/dist/server/index.d.mts +235 -0
  21. package/dist/server/index.d.ts +235 -0
  22. package/dist/server/index.js +642 -0
  23. package/dist/server/index.js.map +1 -0
  24. package/dist/server/index.mjs +597 -0
  25. package/dist/server/index.mjs.map +1 -0
  26. package/package.json +64 -0
  27. package/src/components/SidekickPanel.tsx +868 -0
  28. package/src/components/index.ts +1 -0
  29. package/src/context.tsx +157 -0
  30. package/src/flags.ts +47 -0
  31. package/src/index.ts +71 -0
  32. package/src/jsx-dev-runtime.ts +138 -0
  33. package/src/jsx-runtime.ts +159 -0
  34. package/src/loader.ts +35 -0
  35. package/src/primitives/behavior.ts +70 -0
  36. package/src/primitives/data.ts +91 -0
  37. package/src/primitives/index.ts +3 -0
  38. package/src/primitives/ui.ts +268 -0
  39. package/src/provider.tsx +1264 -0
  40. package/src/runtime-loader.ts +106 -0
  41. package/src/server/drizzle-adapter.ts +53 -0
  42. package/src/server/drizzle-schema.ts +16 -0
  43. package/src/server/generate.ts +578 -0
  44. package/src/server/handler.ts +343 -0
  45. package/src/server/index.ts +20 -0
  46. package/src/server/storage.ts +1 -0
  47. package/src/server/types.ts +49 -0
  48. package/src/types.ts +295 -0
@@ -0,0 +1,868 @@
1
+ 'use client';
2
+
3
+ import { useState, useCallback, useEffect, FormEvent } from 'react';
4
+ import { useSidekickSafe } from '../context';
5
+ import { setFlag } from '../flags';
6
+
7
+ interface Message {
8
+ id: string;
9
+ role: 'user' | 'assistant' | 'system';
10
+ content: string;
11
+ timestamp: Date;
12
+ status?: 'pending' | 'generating' | 'success' | 'error';
13
+ overrideId?: string;
14
+ }
15
+
16
+ interface SidekickPanelProps {
17
+ apiEndpoint?: string;
18
+ deleteEndpoint?: string;
19
+ toggleEndpoint?: string;
20
+ onGenerate?: (request: string) => Promise<{ success: boolean; overrideId?: string; error?: string }>;
21
+ onDelete?: (overrideId: string) => Promise<{ success: boolean; error?: string }>;
22
+ onToggle?: (overrideId: string, enabled: boolean) => Promise<{ success: boolean; error?: string }>;
23
+ }
24
+
25
+ export function SidekickPanel({ apiEndpoint, deleteEndpoint, toggleEndpoint, onGenerate, onDelete, onToggle }: SidekickPanelProps) {
26
+ // Initialize state from sessionStorage to persist across reloads
27
+ const [isOpen, setIsOpen] = useState(() => {
28
+ if (typeof window === 'undefined') return false;
29
+ return sessionStorage.getItem('sidekick_panel_open') === 'true';
30
+ });
31
+ const [activeTab, setActiveTab] = useState<'chat' | 'overrides'>(() => {
32
+ if (typeof window === 'undefined') return 'chat';
33
+ return (sessionStorage.getItem('sidekick_panel_tab') as 'chat' | 'overrides') || 'chat';
34
+ });
35
+ const [messages, setMessages] = useState<Message[]>([]);
36
+ const [input, setInput] = useState('');
37
+ const [isGenerating, setIsGenerating] = useState(false);
38
+ const [deletingId, setDeletingId] = useState<string | null>(null);
39
+ const [togglingId, setTogglingId] = useState<string | null>(null);
40
+ const [editingOverrideId, setEditingOverrideId] = useState<string | null>(null);
41
+
42
+ const context = useSidekickSafe();
43
+ const overrides = context?.overrides ?? [];
44
+
45
+ // Compute endpoints from apiEndpoint if not provided
46
+ const effectiveDeleteEndpoint = deleteEndpoint || (apiEndpoint ? apiEndpoint.replace('/generate', '/delete') : undefined);
47
+ const effectiveToggleEndpoint = toggleEndpoint || (apiEndpoint ? apiEndpoint.replace('/generate', '/toggle') : undefined);
48
+
49
+ // Persist panel state changes to sessionStorage
50
+ useEffect(() => {
51
+ if (typeof window === 'undefined') return;
52
+ sessionStorage.setItem('sidekick_panel_open', isOpen ? 'true' : 'false');
53
+ }, [isOpen]);
54
+
55
+ useEffect(() => {
56
+ if (typeof window === 'undefined') return;
57
+ sessionStorage.setItem('sidekick_panel_tab', activeTab);
58
+ }, [activeTab]);
59
+
60
+ // Compute sessionStorage key based on editing mode
61
+ const messagesStorageKey = editingOverrideId
62
+ ? `sidekick_edit_messages_${editingOverrideId}`
63
+ : 'sidekick_messages';
64
+
65
+ // Load message history from sessionStorage (re-runs when editing mode changes)
66
+ useEffect(() => {
67
+ if (typeof window === 'undefined') return;
68
+ const stored = sessionStorage.getItem(messagesStorageKey);
69
+ if (stored) {
70
+ try {
71
+ const parsed = JSON.parse(stored);
72
+ setMessages(parsed.map((m: Message) => ({ ...m, timestamp: new Date(m.timestamp) })));
73
+ } catch {
74
+ setMessages([]);
75
+ }
76
+ } else {
77
+ setMessages([]);
78
+ }
79
+ }, [messagesStorageKey]);
80
+
81
+ // Save messages to sessionStorage
82
+ useEffect(() => {
83
+ if (typeof window === 'undefined' || messages.length === 0) return;
84
+ sessionStorage.setItem(messagesStorageKey, JSON.stringify(messages));
85
+ }, [messages, messagesStorageKey]);
86
+
87
+ const handleSubmit = useCallback(async (e: FormEvent) => {
88
+ e.preventDefault();
89
+ if (!input.trim() || isGenerating) return;
90
+
91
+ const userMessage: Message = {
92
+ id: generateId(),
93
+ role: 'user',
94
+ content: input.trim(),
95
+ timestamp: new Date(),
96
+ };
97
+
98
+ const assistantMessage: Message = {
99
+ id: generateId(),
100
+ role: 'assistant',
101
+ content: 'Generating your customization...',
102
+ timestamp: new Date(),
103
+ status: 'generating',
104
+ };
105
+
106
+ setMessages((prev) => [...prev, userMessage, assistantMessage]);
107
+ setInput('');
108
+ setIsGenerating(true);
109
+
110
+ try {
111
+ let result: { success: boolean; overrideId?: string; name?: string; description?: string; error?: string };
112
+
113
+ const requestBody: { request: string; overrideId?: string } = { request: userMessage.content };
114
+ if (editingOverrideId) {
115
+ requestBody.overrideId = editingOverrideId;
116
+ }
117
+
118
+ if (onGenerate) {
119
+ result = await onGenerate(userMessage.content);
120
+ } else if (apiEndpoint) {
121
+ const response = await fetch(apiEndpoint, {
122
+ method: 'POST',
123
+ headers: { 'Content-Type': 'application/json' },
124
+ body: JSON.stringify(requestBody),
125
+ });
126
+ result = await response.json();
127
+ } else {
128
+ result = {
129
+ success: false,
130
+ error: 'No API endpoint or onGenerate handler configured',
131
+ };
132
+ }
133
+
134
+ const verb = editingOverrideId ? 'Updated' : 'Created';
135
+ const successMessage = result.success
136
+ ? `${verb} "${result.name || result.overrideId}"!\n\n${result.description || ''}\n\nReload the page to apply changes.`
137
+ : `Failed to generate: ${result.error}`;
138
+
139
+ setMessages((prev) =>
140
+ prev.map((m) =>
141
+ m.id === assistantMessage.id
142
+ ? {
143
+ ...m,
144
+ content: successMessage,
145
+ status: result.success ? 'success' : 'error',
146
+ overrideId: result.overrideId,
147
+ }
148
+ : m
149
+ )
150
+ );
151
+ } catch (error) {
152
+ setMessages((prev) =>
153
+ prev.map((m) =>
154
+ m.id === assistantMessage.id
155
+ ? {
156
+ ...m,
157
+ content: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
158
+ status: 'error',
159
+ }
160
+ : m
161
+ )
162
+ );
163
+ } finally {
164
+ setIsGenerating(false);
165
+ }
166
+ }, [input, isGenerating, onGenerate, apiEndpoint, editingOverrideId]);
167
+
168
+ const handleToggleOverride = useCallback(async (overrideId: string, enabled: boolean) => {
169
+ setTogglingId(overrideId);
170
+
171
+ try {
172
+ if (onToggle) {
173
+ await onToggle(overrideId, enabled);
174
+ } else if (effectiveToggleEndpoint) {
175
+ const response = await fetch(effectiveToggleEndpoint, {
176
+ method: 'POST',
177
+ headers: { 'Content-Type': 'application/json' },
178
+ body: JSON.stringify({ overrideId, enabled }),
179
+ });
180
+ const result = await response.json();
181
+ if (!result.success) {
182
+ throw new Error(result.error || 'Toggle failed');
183
+ }
184
+ } else {
185
+ // Fallback to localStorage
186
+ setFlag(overrideId, enabled);
187
+ }
188
+
189
+ // Reload the page to apply the change
190
+ window.location.reload();
191
+ } catch (error) {
192
+ alert(`Failed to toggle: ${error instanceof Error ? error.message : 'Unknown error'}`);
193
+ setTogglingId(null);
194
+ }
195
+ }, [onToggle, effectiveToggleEndpoint]);
196
+
197
+ const handleDeleteOverride = useCallback(async (overrideId: string) => {
198
+ if (!confirm(`Are you sure you want to delete this override? This cannot be undone.`)) {
199
+ return;
200
+ }
201
+
202
+ setDeletingId(overrideId);
203
+
204
+ try {
205
+ let result: { success: boolean; error?: string };
206
+
207
+ if (onDelete) {
208
+ result = await onDelete(overrideId);
209
+ } else if (effectiveDeleteEndpoint) {
210
+ const response = await fetch(effectiveDeleteEndpoint, {
211
+ method: 'POST',
212
+ headers: { 'Content-Type': 'application/json' },
213
+ body: JSON.stringify({ overrideId }),
214
+ });
215
+ result = await response.json();
216
+ } else {
217
+ result = { success: false, error: 'No delete endpoint configured' };
218
+ }
219
+
220
+ if (result.success) {
221
+ // Clear the flag and reload
222
+ setFlag(overrideId, false);
223
+ window.location.reload();
224
+ } else {
225
+ alert(`Failed to delete: ${result.error}`);
226
+ }
227
+ } catch (error) {
228
+ alert(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`);
229
+ } finally {
230
+ setDeletingId(null);
231
+ }
232
+ }, [onDelete, effectiveDeleteEndpoint]);
233
+
234
+ const handleRegenerateOverride = useCallback((overrideId: string) => {
235
+ setEditingOverrideId(overrideId);
236
+ setActiveTab('chat');
237
+ setInput('');
238
+ }, []);
239
+
240
+ const handleEditOverride = useCallback((overrideId: string) => {
241
+ setEditingOverrideId(overrideId);
242
+ setActiveTab('chat');
243
+ setInput('');
244
+ }, []);
245
+
246
+ const handleExitEditMode = useCallback(() => {
247
+ setEditingOverrideId(null);
248
+ setInput('');
249
+ }, []);
250
+
251
+ const handleClearHistory = useCallback(() => {
252
+ setMessages([]);
253
+ sessionStorage.removeItem(messagesStorageKey);
254
+ }, [messagesStorageKey]);
255
+
256
+ if (!isOpen) {
257
+ return (
258
+ <button
259
+ onClick={() => setIsOpen(true)}
260
+ style={styles.trigger}
261
+ aria-label="Open Sidekick Panel"
262
+ >
263
+ <SidekickIcon />
264
+ </button>
265
+ );
266
+ }
267
+
268
+ return (
269
+ <div style={styles.overlay} onWheel={(e) => e.stopPropagation()} onTouchMove={(e) => e.stopPropagation()}>
270
+ <div style={styles.panel}>
271
+ {/* Header */}
272
+ <div style={styles.header}>
273
+ <h2 style={styles.title}>Sidekick</h2>
274
+ <button onClick={() => setIsOpen(false)} style={styles.closeButton}>
275
+ <CloseIcon />
276
+ </button>
277
+ </div>
278
+
279
+ {/* Tabs */}
280
+ <div style={styles.tabs}>
281
+ <button
282
+ onClick={() => setActiveTab('chat')}
283
+ style={{
284
+ ...styles.tab,
285
+ ...(activeTab === 'chat' ? styles.tabActive : {}),
286
+ }}
287
+ >
288
+ Customize
289
+ </button>
290
+ <button
291
+ onClick={() => setActiveTab('overrides')}
292
+ style={{
293
+ ...styles.tab,
294
+ ...(activeTab === 'overrides' ? styles.tabActive : {}),
295
+ }}
296
+ >
297
+ Overrides ({overrides.length})
298
+ </button>
299
+ </div>
300
+
301
+ {/* Content */}
302
+ <div style={styles.content}>
303
+ {activeTab === 'chat' ? (
304
+ <ChatTab
305
+ messages={messages}
306
+ input={input}
307
+ isGenerating={isGenerating}
308
+ onInputChange={setInput}
309
+ onSubmit={handleSubmit}
310
+ onClearHistory={handleClearHistory}
311
+ editingOverride={editingOverrideId ? overrides.find(o => o.id === editingOverrideId) : undefined}
312
+ onExitEditMode={handleExitEditMode}
313
+ />
314
+ ) : (
315
+ <OverridesTab
316
+ overrides={overrides}
317
+ onToggle={handleToggleOverride}
318
+ onDelete={handleDeleteOverride}
319
+ onRegenerate={handleRegenerateOverride}
320
+ onEdit={handleEditOverride}
321
+ deletingId={deletingId}
322
+ togglingId={togglingId}
323
+ />
324
+ )}
325
+ </div>
326
+ </div>
327
+ </div>
328
+ );
329
+ }
330
+
331
+ interface ChatTabProps {
332
+ messages: Message[];
333
+ input: string;
334
+ isGenerating: boolean;
335
+ onInputChange: (value: string) => void;
336
+ onSubmit: (e: FormEvent) => void;
337
+ onClearHistory: () => void;
338
+ editingOverride?: { id: string; name: string; description: string; enabled: boolean };
339
+ onExitEditMode: () => void;
340
+ }
341
+
342
+ function ChatTab({
343
+ messages,
344
+ input,
345
+ isGenerating,
346
+ onInputChange,
347
+ onSubmit,
348
+ onClearHistory,
349
+ editingOverride,
350
+ onExitEditMode,
351
+ }: ChatTabProps) {
352
+ return (
353
+ <div style={styles.chatContainer}>
354
+ {/* Edit mode header */}
355
+ {editingOverride && (
356
+ <div style={styles.editHeader}>
357
+ <span style={styles.editHeaderText}>Editing: {editingOverride.name}</span>
358
+ <button onClick={onExitEditMode} style={styles.editHeaderClose}>✕</button>
359
+ </div>
360
+ )}
361
+ {/* Messages */}
362
+ <div style={styles.messages}>
363
+ {messages.length === 0 ? (
364
+ editingOverride ? (
365
+ <div style={styles.emptyState}>
366
+ <p style={styles.emptyTitle}>Edit "{editingOverride.name}"</p>
367
+ <p style={styles.emptyDescription}>
368
+ Describe what you want to change about this override, and I'll modify the existing code.
369
+ </p>
370
+ <div style={styles.examples}>
371
+ <p style={styles.examplesTitle}>Try saying:</p>
372
+ <ul style={styles.examplesList}>
373
+ <li>"Change the color to red"</li>
374
+ <li>"Make it only apply to high priority items"</li>
375
+ <li>"Add a hover effect"</li>
376
+ </ul>
377
+ </div>
378
+ </div>
379
+ ) : (
380
+ <div style={styles.emptyState}>
381
+ <p style={styles.emptyTitle}>Customize your app</p>
382
+ <p style={styles.emptyDescription}>
383
+ Describe what you want to change in natural language, and I'll generate the code.
384
+ </p>
385
+ <div style={styles.examples}>
386
+ <p style={styles.examplesTitle}>Try saying:</p>
387
+ <ul style={styles.examplesList}>
388
+ <li>"Add a column showing days until due date"</li>
389
+ <li>"Add a banner at the top of the sidebar"</li>
390
+ <li>"Highlight overdue tasks in red"</li>
391
+ </ul>
392
+ </div>
393
+ </div>
394
+ )
395
+ ) : (
396
+ <>
397
+ {messages.map((message) => (
398
+ <div
399
+ key={message.id}
400
+ style={{
401
+ ...styles.message,
402
+ ...(message.role === 'user' ? styles.userMessage : styles.assistantMessage),
403
+ }}
404
+ >
405
+ <div style={styles.messageContent}>
406
+ {message.content}
407
+ {message.status === 'generating' && (
408
+ <span style={styles.spinner}>⟳</span>
409
+ )}
410
+ </div>
411
+ {message.status === 'success' && (
412
+ <div style={styles.successContainer}>
413
+ <div style={styles.successBadge}>✓ {editingOverride ? 'Updated' : 'Created'}</div>
414
+ <button
415
+ onClick={() => window.location.reload()}
416
+ style={styles.reloadButton}
417
+ >
418
+ Reload to Apply
419
+ </button>
420
+ </div>
421
+ )}
422
+ {message.status === 'error' && (
423
+ <div style={styles.errorBadge}>✕ Failed</div>
424
+ )}
425
+ </div>
426
+ ))}
427
+ <button onClick={onClearHistory} style={styles.clearButton}>
428
+ Clear history
429
+ </button>
430
+ </>
431
+ )}
432
+ </div>
433
+
434
+ {/* Input */}
435
+ <form onSubmit={onSubmit} style={styles.inputForm}>
436
+ <input
437
+ type="text"
438
+ value={input}
439
+ onChange={(e) => onInputChange(e.target.value)}
440
+ placeholder="Describe your customization..."
441
+ disabled={isGenerating}
442
+ style={styles.input}
443
+ />
444
+ <button
445
+ type="submit"
446
+ disabled={!input.trim() || isGenerating}
447
+ style={{
448
+ ...styles.submitButton,
449
+ ...((!input.trim() || isGenerating) ? styles.submitButtonDisabled : {}),
450
+ }}
451
+ >
452
+ {isGenerating ? '...' : '→'}
453
+ </button>
454
+ </form>
455
+ </div>
456
+ );
457
+ }
458
+
459
+ interface OverridesTabProps {
460
+ overrides: Array<{ id: string; name: string; description: string; enabled: boolean }>;
461
+ onToggle: (id: string, enabled: boolean) => void;
462
+ onDelete: (id: string) => void;
463
+ onRegenerate: (id: string) => void;
464
+ onEdit: (id: string) => void;
465
+ deletingId: string | null;
466
+ togglingId: string | null;
467
+ }
468
+
469
+ function OverridesTab({ overrides, onToggle, onDelete, onRegenerate, onEdit, deletingId, togglingId }: OverridesTabProps) {
470
+ const handleToggle = (override: { id: string; enabled: boolean }) => {
471
+ onToggle(override.id, !override.enabled);
472
+ };
473
+
474
+ if (overrides.length === 0) {
475
+ return (
476
+ <div style={styles.emptyState}>
477
+ <p style={styles.emptyTitle}>No overrides yet</p>
478
+ <p style={styles.emptyDescription}>
479
+ Use the Customize tab to create your first customization.
480
+ </p>
481
+ </div>
482
+ );
483
+ }
484
+
485
+ return (
486
+ <div style={styles.overridesList}>
487
+ {overrides.map((override) => (
488
+ <div key={override.id} style={styles.overrideItem}>
489
+ <div style={styles.overrideInfo}>
490
+ <h3 style={styles.overrideName}>{override.name}</h3>
491
+ <p style={styles.overrideDescription}>{override.description}</p>
492
+ <div style={styles.overrideActions}>
493
+ <button
494
+ onClick={() => onEdit(override.id)}
495
+ style={styles.actionButton}
496
+ >
497
+ Edit
498
+ </button>
499
+ <button
500
+ onClick={() => onRegenerate(override.id)}
501
+ style={styles.actionButton}
502
+ >
503
+ Regenerate
504
+ </button>
505
+ <button
506
+ onClick={() => onDelete(override.id)}
507
+ disabled={deletingId === override.id}
508
+ style={{
509
+ ...styles.actionButton,
510
+ ...styles.deleteButton,
511
+ ...(deletingId === override.id ? styles.actionButtonDisabled : {}),
512
+ }}
513
+ >
514
+ {deletingId === override.id ? 'Deleting...' : 'Delete'}
515
+ </button>
516
+ </div>
517
+ </div>
518
+ <button
519
+ onClick={() => handleToggle(override)}
520
+ disabled={togglingId === override.id}
521
+ style={{
522
+ ...styles.toggle,
523
+ ...(override.enabled ? styles.toggleOn : styles.toggleOff),
524
+ ...(togglingId === override.id ? styles.toggleDisabled : {}),
525
+ }}
526
+ >
527
+ {togglingId === override.id ? '...' : override.enabled ? 'ON' : 'OFF'}
528
+ </button>
529
+ </div>
530
+ ))}
531
+ </div>
532
+ );
533
+ }
534
+
535
+ // Icons
536
+ function SidekickIcon() {
537
+ return (
538
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
539
+ <path d="M12 2L2 7l10 5 10-5-10-5z" />
540
+ <path d="M2 17l10 5 10-5" />
541
+ <path d="M2 12l10 5 10-5" />
542
+ </svg>
543
+ );
544
+ }
545
+
546
+ function CloseIcon() {
547
+ return (
548
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
549
+ <path d="M18 6L6 18M6 6l12 12" />
550
+ </svg>
551
+ );
552
+ }
553
+
554
+ function generateId(): string {
555
+ return Math.random().toString(36).substring(2, 11);
556
+ }
557
+
558
+ // Styles
559
+ const styles: Record<string, React.CSSProperties> = {
560
+ trigger: {
561
+ position: 'fixed',
562
+ bottom: '24px',
563
+ right: '24px',
564
+ width: '56px',
565
+ height: '56px',
566
+ borderRadius: '50%',
567
+ backgroundColor: '#3B82F6',
568
+ color: 'white',
569
+ border: 'none',
570
+ cursor: 'pointer',
571
+ display: 'flex',
572
+ alignItems: 'center',
573
+ justifyContent: 'center',
574
+ boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)',
575
+ zIndex: 9999,
576
+ },
577
+ overlay: {
578
+ position: 'fixed',
579
+ top: 0,
580
+ right: 0,
581
+ bottom: 0,
582
+ width: '400px',
583
+ zIndex: 9999,
584
+ boxShadow: '-4px 0 24px rgba(0, 0, 0, 0.15)',
585
+ overscrollBehavior: 'contain',
586
+ },
587
+ panel: {
588
+ height: '100%',
589
+ backgroundColor: 'white',
590
+ display: 'flex',
591
+ flexDirection: 'column',
592
+ },
593
+ header: {
594
+ display: 'flex',
595
+ alignItems: 'center',
596
+ justifyContent: 'space-between',
597
+ padding: '16px 20px',
598
+ borderBottom: '1px solid #E5E7EB',
599
+ },
600
+ title: {
601
+ margin: 0,
602
+ fontSize: '18px',
603
+ fontWeight: 600,
604
+ color: '#111827',
605
+ },
606
+ closeButton: {
607
+ background: 'none',
608
+ border: 'none',
609
+ cursor: 'pointer',
610
+ padding: '4px',
611
+ color: '#6B7280',
612
+ },
613
+ tabs: {
614
+ display: 'flex',
615
+ borderBottom: '1px solid #E5E7EB',
616
+ },
617
+ tab: {
618
+ flex: 1,
619
+ padding: '12px 16px',
620
+ background: 'none',
621
+ border: 'none',
622
+ cursor: 'pointer',
623
+ fontSize: '14px',
624
+ fontWeight: 500,
625
+ color: '#6B7280',
626
+ borderBottom: '2px solid transparent',
627
+ },
628
+ tabActive: {
629
+ color: '#3B82F6',
630
+ borderBottomColor: '#3B82F6',
631
+ },
632
+ content: {
633
+ flex: 1,
634
+ overflow: 'hidden',
635
+ display: 'flex',
636
+ flexDirection: 'column',
637
+ },
638
+ chatContainer: {
639
+ flex: 1,
640
+ display: 'flex',
641
+ flexDirection: 'column',
642
+ overflow: 'hidden',
643
+ },
644
+ messages: {
645
+ flex: 1,
646
+ overflowY: 'auto',
647
+ padding: '16px',
648
+ overscrollBehavior: 'contain',
649
+ },
650
+ emptyState: {
651
+ textAlign: 'center',
652
+ padding: '40px 20px',
653
+ },
654
+ emptyTitle: {
655
+ margin: '0 0 8px 0',
656
+ fontSize: '16px',
657
+ fontWeight: 600,
658
+ color: '#111827',
659
+ },
660
+ emptyDescription: {
661
+ margin: '0 0 24px 0',
662
+ fontSize: '14px',
663
+ color: '#6B7280',
664
+ },
665
+ examples: {
666
+ textAlign: 'left',
667
+ backgroundColor: '#F9FAFB',
668
+ borderRadius: '8px',
669
+ padding: '16px',
670
+ },
671
+ examplesTitle: {
672
+ margin: '0 0 8px 0',
673
+ fontSize: '13px',
674
+ fontWeight: 500,
675
+ color: '#374151',
676
+ },
677
+ examplesList: {
678
+ margin: 0,
679
+ padding: '0 0 0 20px',
680
+ fontSize: '13px',
681
+ color: '#6B7280',
682
+ lineHeight: 1.8,
683
+ },
684
+ message: {
685
+ marginBottom: '12px',
686
+ padding: '12px 16px',
687
+ borderRadius: '12px',
688
+ fontSize: '14px',
689
+ lineHeight: 1.5,
690
+ },
691
+ userMessage: {
692
+ backgroundColor: '#3B82F6',
693
+ color: 'white',
694
+ marginLeft: '40px',
695
+ },
696
+ assistantMessage: {
697
+ backgroundColor: '#F3F4F6',
698
+ color: '#111827',
699
+ marginRight: '40px',
700
+ },
701
+ messageContent: {
702
+ whiteSpace: 'pre-wrap',
703
+ },
704
+ spinner: {
705
+ display: 'inline-block',
706
+ animation: 'spin 1s linear infinite',
707
+ },
708
+ successContainer: {
709
+ marginTop: '12px',
710
+ display: 'flex',
711
+ flexDirection: 'column',
712
+ gap: '8px',
713
+ },
714
+ successBadge: {
715
+ fontSize: '12px',
716
+ color: '#059669',
717
+ fontWeight: 500,
718
+ },
719
+ reloadButton: {
720
+ padding: '8px 16px',
721
+ backgroundColor: '#059669',
722
+ color: 'white',
723
+ border: 'none',
724
+ borderRadius: '6px',
725
+ fontSize: '13px',
726
+ fontWeight: 500,
727
+ cursor: 'pointer',
728
+ },
729
+ errorBadge: {
730
+ marginTop: '8px',
731
+ fontSize: '12px',
732
+ color: '#DC2626',
733
+ fontWeight: 500,
734
+ },
735
+ clearButton: {
736
+ background: 'none',
737
+ border: 'none',
738
+ color: '#6B7280',
739
+ fontSize: '12px',
740
+ cursor: 'pointer',
741
+ padding: '8px',
742
+ textAlign: 'center',
743
+ width: '100%',
744
+ },
745
+ inputForm: {
746
+ display: 'flex',
747
+ gap: '8px',
748
+ padding: '16px',
749
+ borderTop: '1px solid #E5E7EB',
750
+ },
751
+ input: {
752
+ flex: 1,
753
+ padding: '12px 16px',
754
+ border: '1px solid #E5E7EB',
755
+ borderRadius: '8px',
756
+ fontSize: '14px',
757
+ outline: 'none',
758
+ },
759
+ submitButton: {
760
+ padding: '12px 20px',
761
+ backgroundColor: '#3B82F6',
762
+ color: 'white',
763
+ border: 'none',
764
+ borderRadius: '8px',
765
+ fontSize: '16px',
766
+ cursor: 'pointer',
767
+ fontWeight: 600,
768
+ },
769
+ submitButtonDisabled: {
770
+ backgroundColor: '#93C5FD',
771
+ cursor: 'not-allowed',
772
+ },
773
+ overridesList: {
774
+ padding: '16px',
775
+ flex: 1,
776
+ overflowY: 'auto',
777
+ overscrollBehavior: 'contain',
778
+ },
779
+ overrideItem: {
780
+ display: 'flex',
781
+ alignItems: 'center',
782
+ justifyContent: 'space-between',
783
+ padding: '16px',
784
+ backgroundColor: '#F9FAFB',
785
+ borderRadius: '8px',
786
+ marginBottom: '12px',
787
+ },
788
+ overrideInfo: {
789
+ flex: 1,
790
+ marginRight: '16px',
791
+ },
792
+ overrideName: {
793
+ margin: '0 0 4px 0',
794
+ fontSize: '14px',
795
+ fontWeight: 600,
796
+ color: '#111827',
797
+ },
798
+ overrideDescription: {
799
+ margin: 0,
800
+ fontSize: '13px',
801
+ color: '#6B7280',
802
+ },
803
+ toggle: {
804
+ padding: '6px 12px',
805
+ borderRadius: '4px',
806
+ border: 'none',
807
+ fontSize: '12px',
808
+ fontWeight: 600,
809
+ cursor: 'pointer',
810
+ },
811
+ toggleOn: {
812
+ backgroundColor: '#059669',
813
+ color: 'white',
814
+ },
815
+ toggleOff: {
816
+ backgroundColor: '#E5E7EB',
817
+ color: '#6B7280',
818
+ },
819
+ toggleDisabled: {
820
+ opacity: 0.5,
821
+ cursor: 'not-allowed',
822
+ },
823
+ overrideActions: {
824
+ display: 'flex',
825
+ gap: '8px',
826
+ marginTop: '8px',
827
+ },
828
+ actionButton: {
829
+ padding: '4px 8px',
830
+ fontSize: '11px',
831
+ fontWeight: 500,
832
+ border: '1px solid #E5E7EB',
833
+ borderRadius: '4px',
834
+ backgroundColor: 'white',
835
+ color: '#6B7280',
836
+ cursor: 'pointer',
837
+ },
838
+ actionButtonDisabled: {
839
+ opacity: 0.5,
840
+ cursor: 'not-allowed',
841
+ },
842
+ deleteButton: {
843
+ color: '#DC2626',
844
+ borderColor: '#FCA5A5',
845
+ },
846
+ editHeader: {
847
+ display: 'flex',
848
+ alignItems: 'center',
849
+ justifyContent: 'space-between',
850
+ padding: '8px 16px',
851
+ backgroundColor: '#EFF6FF',
852
+ borderBottom: '1px solid #BFDBFE',
853
+ },
854
+ editHeaderText: {
855
+ fontSize: '13px',
856
+ fontWeight: 600,
857
+ color: '#1D4ED8',
858
+ },
859
+ editHeaderClose: {
860
+ background: 'none',
861
+ border: 'none',
862
+ cursor: 'pointer',
863
+ fontSize: '14px',
864
+ color: '#1D4ED8',
865
+ padding: '2px 6px',
866
+ borderRadius: '4px',
867
+ },
868
+ };