@courseecho/ai-widget-react 1.0.23 → 1.0.24

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.
@@ -0,0 +1,368 @@
1
+ /**
2
+ * AI Widget SDK - Core Chat State Manager
3
+ * Manage chat messages, loading state, errors - framework-agnostic
4
+ */
5
+ /**
6
+ * Core Chat State - manages messages and loading states
7
+ */
8
+ export class ChatStateManager {
9
+ constructor(maxChatHistory = 100) {
10
+ this.messages = [];
11
+ this.maxChatHistory = 100;
12
+ this.isLoading = false;
13
+ this.error = null;
14
+ this.messageListeners = [];
15
+ this.loadingListeners = [];
16
+ this.errorListeners = [];
17
+ this.maxChatHistory = maxChatHistory;
18
+ }
19
+ /**
20
+ * Add a new message
21
+ */
22
+ addMessage(message) {
23
+ this.messages.push(message);
24
+ // Trim old messages if exceeding max history
25
+ if (this.messages.length > this.maxChatHistory) {
26
+ this.messages = this.messages.slice(-this.maxChatHistory);
27
+ }
28
+ this.notifyMessageListeners();
29
+ }
30
+ /**
31
+ * Get all messages
32
+ */
33
+ getMessages() {
34
+ return [...this.messages];
35
+ }
36
+ /**
37
+ * Clear all messages
38
+ */
39
+ clearMessages() {
40
+ this.messages = [];
41
+ this.error = null;
42
+ this.notifyMessageListeners();
43
+ this.notifyErrorListeners();
44
+ }
45
+ /**
46
+ * Set loading state
47
+ */
48
+ setLoading(isLoading) {
49
+ this.isLoading = isLoading;
50
+ this.notifyLoadingListeners();
51
+ }
52
+ /**
53
+ * Get loading state
54
+ */
55
+ isLoadingState() {
56
+ return this.isLoading;
57
+ }
58
+ /**
59
+ * Set error
60
+ */
61
+ setError(error) {
62
+ this.error = error;
63
+ this.notifyErrorListeners();
64
+ }
65
+ /**
66
+ * Get current error
67
+ */
68
+ getError() {
69
+ return this.error;
70
+ }
71
+ /**
72
+ * Get last message
73
+ */
74
+ getLastMessage() {
75
+ return this.messages[this.messages.length - 1];
76
+ }
77
+ /**
78
+ * Get message count
79
+ */
80
+ getMessageCount() {
81
+ return this.messages.length;
82
+ }
83
+ /**
84
+ * Subscribe to message changes
85
+ */
86
+ onMessagesChange(listener) {
87
+ this.messageListeners.push(listener);
88
+ return () => {
89
+ this.messageListeners = this.messageListeners.filter((l) => l !== listener);
90
+ };
91
+ }
92
+ /**
93
+ * Subscribe to loading changes
94
+ */
95
+ onLoadingChange(listener) {
96
+ this.loadingListeners.push(listener);
97
+ return () => {
98
+ this.loadingListeners = this.loadingListeners.filter((l) => l !== listener);
99
+ };
100
+ }
101
+ /**
102
+ * Subscribe to error changes
103
+ */
104
+ onErrorChange(listener) {
105
+ this.errorListeners.push(listener);
106
+ return () => {
107
+ this.errorListeners = this.errorListeners.filter((l) => l !== listener);
108
+ };
109
+ }
110
+ notifyMessageListeners() {
111
+ this.messageListeners.forEach((listener) => listener(this.getMessages()));
112
+ }
113
+ notifyLoadingListeners() {
114
+ this.loadingListeners.forEach((listener) => listener(this.isLoading));
115
+ }
116
+ notifyErrorListeners() {
117
+ this.errorListeners.forEach((listener) => listener(this.error));
118
+ }
119
+ }
120
+ /**
121
+ * Core Context State - manages page context
122
+ */
123
+ export class ContextStateManager {
124
+ constructor(initialContext) {
125
+ this.contextHistory = [];
126
+ this.contextListeners = [];
127
+ this.context = { ...initialContext };
128
+ this.contextHistory.push({ ...initialContext });
129
+ }
130
+ /**
131
+ * Update entire context
132
+ */
133
+ setContext(context) {
134
+ this.context = { ...context };
135
+ this.contextHistory.push({ ...context });
136
+ this.notifyContextListeners();
137
+ }
138
+ /**
139
+ * Update single context property
140
+ */
141
+ updateProperty(key, value) {
142
+ this.context = {
143
+ ...this.context,
144
+ [key]: value,
145
+ };
146
+ this.contextHistory.push({ ...this.context });
147
+ this.notifyContextListeners();
148
+ }
149
+ /**
150
+ * Get current context
151
+ */
152
+ getContext() {
153
+ return { ...this.context };
154
+ }
155
+ /**
156
+ * Get context history
157
+ */
158
+ getHistory() {
159
+ return this.contextHistory.map((c) => ({ ...c }));
160
+ }
161
+ /**
162
+ * Get single context property
163
+ */
164
+ getProperty(key) {
165
+ return this.context[key];
166
+ }
167
+ /**
168
+ * Subscribe to context changes
169
+ */
170
+ onContextChange(listener) {
171
+ this.contextListeners.push(listener);
172
+ return () => {
173
+ this.contextListeners = this.contextListeners.filter((l) => l !== listener);
174
+ };
175
+ }
176
+ /**
177
+ * Serialize context
178
+ */
179
+ serialize() {
180
+ return JSON.stringify(this.context);
181
+ }
182
+ notifyContextListeners() {
183
+ this.contextListeners.forEach((listener) => listener(this.getContext()));
184
+ }
185
+ }
186
+ /**
187
+ * Chart Manager - manage charts caching and export
188
+ */
189
+ export class ChartManager {
190
+ constructor() {
191
+ this.charts = new Map();
192
+ }
193
+ /**
194
+ * Cache a chart
195
+ */
196
+ cacheChart(id, chart) {
197
+ this.charts.set(id, { ...chart });
198
+ }
199
+ /**
200
+ * Get cached chart
201
+ */
202
+ getChart(id) {
203
+ const chart = this.charts.get(id);
204
+ return chart ? { ...chart } : undefined;
205
+ }
206
+ /**
207
+ * Get all cached charts
208
+ */
209
+ getAllCharts() {
210
+ return Array.from(this.charts.values()).map((c) => ({ ...c }));
211
+ }
212
+ /**
213
+ * Clear cache
214
+ */
215
+ clearCache() {
216
+ this.charts.clear();
217
+ }
218
+ /**
219
+ * Export charts to CSV
220
+ */
221
+ exportToCSV(filename = 'charts.csv') {
222
+ const charts = this.getAllCharts();
223
+ if (charts.length === 0) {
224
+ return '';
225
+ }
226
+ const headers = ['Chart Title', 'Chart Type', 'Labels', 'Data'];
227
+ const rows = [headers.join(',')];
228
+ charts.forEach((chart) => {
229
+ chart.datasets.forEach((dataset) => {
230
+ const row = [
231
+ `"${chart.title}"`,
232
+ chart.type,
233
+ `"${chart.labels.join(';')}"`,
234
+ `"${dataset.data.join(';')}"`,
235
+ ];
236
+ rows.push(row.join(','));
237
+ });
238
+ });
239
+ return rows.join('\n');
240
+ }
241
+ /**
242
+ * Export charts to JSON
243
+ */
244
+ exportToJSON() {
245
+ return JSON.stringify(this.getAllCharts(), null, 2);
246
+ }
247
+ }
248
+ /**
249
+ * Suggestion Manager - manage suggestions for chat queries
250
+ */
251
+ export class SuggestionManager {
252
+ constructor(config) {
253
+ this.suggestions = [];
254
+ this.suggestionListeners = [];
255
+ this.config = config || {
256
+ enabled: true,
257
+ maxVisibleSuggestions: 5,
258
+ showOnFocus: true,
259
+ showOnClick: true,
260
+ };
261
+ // Initialize with default suggestions if provided
262
+ if (this.config.suggestions) {
263
+ this.suggestions = [...this.config.suggestions];
264
+ }
265
+ }
266
+ /**
267
+ * Get all suggestions
268
+ */
269
+ getSuggestions() {
270
+ return [...this.suggestions];
271
+ }
272
+ /**
273
+ * Filter suggestions based on query
274
+ */
275
+ filterSuggestions(query) {
276
+ if (!query.trim()) {
277
+ return this.suggestions.slice(0, this.config.maxVisibleSuggestions);
278
+ }
279
+ const lowerQuery = query.toLowerCase();
280
+ return this.suggestions
281
+ .filter((s) => s.text.toLowerCase().includes(lowerQuery) ||
282
+ s.category?.toLowerCase().includes(lowerQuery) ||
283
+ s.description?.toLowerCase().includes(lowerQuery))
284
+ .slice(0, this.config.maxVisibleSuggestions);
285
+ }
286
+ /**
287
+ * Get suggestions by category
288
+ */
289
+ getSuggestionsByCategory(category) {
290
+ return this.suggestions.filter((s) => s.category === category);
291
+ }
292
+ /**
293
+ * Add a suggestion
294
+ */
295
+ addSuggestion(suggestion) {
296
+ this.suggestions.push(suggestion);
297
+ this.notifySuggestionListeners();
298
+ }
299
+ /**
300
+ * Add multiple suggestions
301
+ */
302
+ addSuggestions(suggestions) {
303
+ this.suggestions.push(...suggestions);
304
+ this.notifySuggestionListeners();
305
+ }
306
+ /**
307
+ * Remove a suggestion by id
308
+ */
309
+ removeSuggestion(id) {
310
+ this.suggestions = this.suggestions.filter((s) => s.id !== id);
311
+ this.notifySuggestionListeners();
312
+ }
313
+ /**
314
+ * Clear all suggestions
315
+ */
316
+ clearSuggestions() {
317
+ this.suggestions = [];
318
+ this.notifySuggestionListeners();
319
+ }
320
+ /**
321
+ * Update suggestion
322
+ */
323
+ updateSuggestion(id, updates) {
324
+ const suggestion = this.suggestions.find((s) => s.id === id);
325
+ if (suggestion) {
326
+ Object.assign(suggestion, updates);
327
+ this.notifySuggestionListeners();
328
+ }
329
+ }
330
+ /**
331
+ * Check if suggestions are enabled
332
+ */
333
+ isEnabled() {
334
+ return this.config.enabled;
335
+ }
336
+ /**
337
+ * Toggle suggestions on/off
338
+ */
339
+ toggleEnabled() {
340
+ if (this.config) {
341
+ this.config.enabled = !this.config.enabled;
342
+ }
343
+ }
344
+ /**
345
+ * Subscribe to suggestion changes
346
+ */
347
+ onSuggestionsChange(listener) {
348
+ this.suggestionListeners.push(listener);
349
+ return () => {
350
+ this.suggestionListeners = this.suggestionListeners.filter((l) => l !== listener);
351
+ };
352
+ }
353
+ /**
354
+ * Get config
355
+ */
356
+ getConfig() {
357
+ return { ...this.config };
358
+ }
359
+ /**
360
+ * Update config
361
+ */
362
+ updateConfig(config) {
363
+ this.config = { ...this.config, ...config };
364
+ }
365
+ notifySuggestionListeners() {
366
+ this.suggestionListeners.forEach((listener) => listener(this.getSuggestions()));
367
+ }
368
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Canonical widget CSS — single source of truth for all framework packages.
3
+ *
4
+ * Isolation strategy:
5
+ * - React : injected into a Shadow DOM via ShadowWrapper (createPortal).
6
+ * :host { all: initial } resets inherited properties at the boundary.
7
+ * - jQuery : injected into attachShadow() root. Same :host reset applies.
8
+ * - Angular: ViewEncapsulation.ShadowDom — Angular handles the shadow root.
9
+ *
10
+ * NOTE: `@import` for Google Fonts does NOT work inside shadow-DOM <style> tags.
11
+ * Each package is responsible for loading the Inter font into document <head>
12
+ * separately (React: ensureFont(), jQuery: injectFontLink()).
13
+ */
14
+ export declare const WIDGET_CSS = "\n/* \u2500\u2500 Shadow-DOM host isolation \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n:host {\n all: initial !important;\n display: block !important;\n}\n\n/* Scoped box-model reset \u2014 only touches widget elements, not all of shadow DOM */\n.aiwg-root *, .aiwg-root *::before, .aiwg-root *::after {\n box-sizing: border-box;\n margin: 0;\n padding: 0;\n}\n\n/* \u2500\u2500 Container \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.aiwg-root {\n --aiwg-primary: #6366f1;\n --aiwg-primary-dark: #8b5cf6;\n --aiwg-fg-color: #ffffff;\n --aiwg-bg-color: #ffffff;\n --aiwg-chat-bg: #f1f5f9;\n\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n font-size: 14px;\n line-height: 1.5;\n color: #1a1a2e;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n border-radius: 16px;\n background: var(--aiwg-bg-color);\n position: fixed !important;\n bottom: 24px;\n right: 24px;\n width: 380px !important;\n max-width: calc(100vw - 48px) !important;\n z-index: 99999;\n transition: height 0.25s ease;\n box-shadow: 0 24px 64px rgba(0,0,0,0.18);\n}\n\n/* \u2500\u2500 Header \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.aiwg-header {\n background: linear-gradient(135deg, var(--aiwg-primary) 0%, var(--aiwg-primary-dark) 100%);\n padding: 16px 20px;\n display: flex;\n align-items: center;\n gap: 12px;\n flex-shrink: 0;\n position: relative;\n overflow: hidden;\n}\n.aiwg-header::before {\n content: '';\n position: absolute;\n width: 140px; height: 140px;\n background: rgba(255,255,255,0.08);\n border-radius: 50%;\n top: -50px; right: -30px;\n}\n.aiwg-avatar {\n width: 40px; height: 40px;\n background: rgba(255,255,255,0.25);\n border-radius: 50%;\n display: flex; align-items: center; justify-content: center;\n font-size: 18px;\n flex-shrink: 0;\n border: 2px solid rgba(255,255,255,0.4);\n}\n.aiwg-header-info { flex: 1; }\n.aiwg-title { color: #fff; font-weight: 600; font-size: 15px; }\n.aiwg-subtitle {\n color: rgba(255,255,255,0.75);\n font-size: 12px;\n display: flex; align-items: center; gap: 5px;\n}\n.aiwg-online-dot {\n width: 7px; height: 7px;\n background: #4ade80;\n border-radius: 50%;\n display: inline-block;\n animation: aiwg-pulse 2s ease-in-out infinite;\n}\n@keyframes aiwg-pulse {\n 0%, 100% { opacity: 1; transform: scale(1); }\n 50% { opacity: 0.6; transform: scale(0.85); }\n}\n.aiwg-minimize-btn {\n width: 36px; height: 36px;\n background: rgba(255,255,255,0.18);\n border: 1.5px solid rgba(255,255,255,0.3);\n border-radius: 50%;\n color: #fff; cursor: pointer;\n display: flex; align-items: center; justify-content: center;\n font-size: 18px; line-height: 1;\n transition: background 0.15s, transform 0.15s;\n flex-shrink: 0;\n position: relative; z-index: 2;\n}\n.aiwg-minimize-btn * { cursor: pointer !important; pointer-events: none; }\n.aiwg-minimize-btn:hover { background: rgba(255,255,255,0.32); transform: scale(1.08); }\n\n/* \u2500\u2500 Messages area \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.aiwg-messages {\n flex: 1;\n overflow-y: auto;\n padding: 20px 16px 8px;\n scroll-behavior: smooth;\n display: flex;\n flex-direction: column;\n gap: 12px;\n}\n.aiwg-messages::-webkit-scrollbar { width: 4px; }\n.aiwg-messages::-webkit-scrollbar-thumb { background: #e2e8f0; border-radius: 99px; }\n\n/* \u2500\u2500 Welcome card \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.aiwg-welcome {\n text-align: center;\n padding: 24px 16px;\n display: flex; flex-direction: column; align-items: center; gap: 8px;\n}\n.aiwg-welcome-icon { font-size: 36px; margin-bottom: 4px; }\n.aiwg-welcome h4 { font-size: 15px; font-weight: 600; color: #1a1a2e; }\n.aiwg-welcome p { font-size: 13px; color: #64748b; }\n\n/* \u2500\u2500 Message bubbles \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.aiwg-msg {\n display: flex;\n gap: 8px;\n align-items: flex-end;\n animation: aiwg-fade-up 0.2s ease both;\n}\n@keyframes aiwg-fade-up {\n from { opacity: 0; transform: translateY(8px); }\n to { opacity: 1; transform: translateY(0); }\n}\n.aiwg-msg--user { flex-direction: row-reverse; }\n.aiwg-msg-avatar {\n width: 28px; height: 28px; border-radius: 50%;\n display: flex; align-items: center; justify-content: center;\n font-size: 13px; flex-shrink: 0;\n margin-bottom: 2px;\n}\n.aiwg-msg--bot .aiwg-msg-avatar { background: linear-gradient(135deg, var(--aiwg-primary), var(--aiwg-primary-dark)); color: var(--aiwg-fg-color); }\n.aiwg-msg--user .aiwg-msg-avatar { background: #e2e8f0; color: #64748b; }\n.aiwg-msg-body { max-width: 78%; }\n.aiwg-msg-bubble {\n padding: 10px 14px;\n border-radius: 18px;\n font-size: 14px;\n line-height: 1.55;\n word-break: break-word;\n}\n.aiwg-msg--bot .aiwg-msg-bubble { background: var(--aiwg-chat-bg); color: #1a1a2e; border-bottom-left-radius: 4px; }\n.aiwg-msg--user .aiwg-msg-bubble { background: linear-gradient(135deg, var(--aiwg-primary), var(--aiwg-primary-dark)); color: var(--aiwg-fg-color); border-bottom-right-radius: 4px; }\n.aiwg-msg-time { font-size: 10px; color: #94a3b8; margin-top: 4px; text-align: right; padding: 0 4px; }\n.aiwg-msg--bot .aiwg-msg-time { text-align: left; }\n\n/* \u2500\u2500 Typing indicator \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.aiwg-typing { padding: 10px 14px; }\n.aiwg-typing-dots { display: flex; gap: 4px; align-items: center; }\n.aiwg-typing-dots span {\n width: 7px; height: 7px;\n background: #94a3b8; border-radius: 50%;\n animation: aiwg-bounce 1.2s ease-in-out infinite;\n}\n.aiwg-typing-dots span:nth-child(2) { animation-delay: 0.2s; }\n.aiwg-typing-dots span:nth-child(3) { animation-delay: 0.4s; }\n@keyframes aiwg-bounce {\n 0%, 80%, 100% { transform: translateY(0); }\n 40% { transform: translateY(-6px); }\n}\n\n/* \u2500\u2500 Quick-reply chips \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.aiwg-chip-row {\n padding: 6px 16px 2px;\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n flex-shrink: 0;\n}\n.aiwg-chip {\n background: #f1f5f9;\n border: 1px solid #e2e8f0;\n border-radius: 99px;\n padding: 4px 12px;\n font-size: 12px;\n color: #4f46e5;\n cursor: pointer;\n transition: all 0.15s;\n white-space: nowrap;\n font-weight: 500;\n}\n.aiwg-chip:hover { background: #ede9fe; border-color: #a5b4fc; }\n.aiwg-chip-shimmer {\n background: linear-gradient(90deg, #e2e8f0 25%, #f1f5f9 50%, #e2e8f0 75%);\n background-size: 200% 100%;\n border: 1px solid transparent;\n border-radius: 99px;\n padding: 4px 34px;\n animation: aiwg-shimmer 1.4s ease-in-out infinite;\n cursor: default;\n pointer-events: none;\n}\n.aiwg-chip-ai-badge {\n display: inline-block;\n font-size: 9px; font-weight: 700;\n background: linear-gradient(135deg, var(--aiwg-primary), var(--aiwg-primary-dark));\n color: #fff;\n padding: 1px 5px;\n border-radius: 99px;\n margin-left: 5px;\n vertical-align: middle;\n letter-spacing: 0.04em;\n}\n@keyframes aiwg-shimmer {\n 0% { background-position: 200% 0; }\n 100% { background-position: -200% 0; }\n}\n\n/* \u2500\u2500 Input area \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.aiwg-input-area {\n padding: 12px 16px 16px;\n flex-shrink: 0;\n background: #fff;\n border-top: 1px solid #f1f5f9;\n position: relative;\n}\n.aiwg-input-row { display: flex; gap: 8px; align-items: flex-end; }\n.aiwg-input {\n flex: 1;\n border: 1.5px solid #e2e8f0;\n border-radius: 12px;\n padding: 10px 14px;\n font-size: 14px;\n font-family: inherit;\n outline: none;\n resize: none;\n background: #f8fafc;\n color: #1a1a2e;\n transition: border-color 0.15s, box-shadow 0.15s, background 0.15s;\n line-height: 1.4;\n max-height: 120px;\n overflow-y: auto;\n scrollbar-width: none;\n -ms-overflow-style: none;\n}\n.aiwg-input::-webkit-scrollbar { display: none; }\n.aiwg-input::placeholder { color: #94a3b8; }\n.aiwg-input:focus {\n border-color: var(--aiwg-primary-dark);\n background: #fff;\n box-shadow: 0 0 0 3px rgba(139,92,246,0.1);\n}\n.aiwg-send-btn {\n width: 42px; height: 42px;\n background: linear-gradient(135deg, var(--aiwg-primary), var(--aiwg-primary-dark));\n border: none; border-radius: 12px;\n color: var(--aiwg-fg-color); cursor: pointer;\n display: flex; align-items: center; justify-content: center;\n flex-shrink: 0;\n transition: transform 0.15s, box-shadow 0.15s, opacity 0.15s;\n box-shadow: 0 4px 12px rgba(99,102,241,0.35);\n}\n.aiwg-send-btn svg { width: 18px; height: 18px; }\n.aiwg-send-btn:hover { transform: translateY(-1px); box-shadow: 0 6px 16px rgba(99,102,241,0.4); }\n.aiwg-send-btn:active { transform: translateY(0); }\n.aiwg-send-btn:disabled { opacity: 0.5; cursor: not-allowed; transform: none; }\n\n/* \u2500\u2500 Autocomplete suggestions dropdown \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.aiwg-suggestions {\n position: absolute;\n bottom: calc(100% + 4px);\n left: 16px; right: 16px;\n background: #fff;\n border: 1px solid #e2e8f0;\n border-radius: 12px;\n box-shadow: 0 8px 24px rgba(0,0,0,0.12);\n overflow: hidden;\n z-index: 100;\n animation: aiwg-fade-up 0.15s ease both;\n}\n.aiwg-suggestions-header {\n padding: 8px 14px 4px;\n font-size: 11px; font-weight: 600;\n color: #94a3b8;\n text-transform: uppercase; letter-spacing: 0.06em;\n border-bottom: 1px solid #f1f5f9;\n}\n.aiwg-suggestion-item {\n display: flex; align-items: center; gap: 10px;\n padding: 10px 14px;\n cursor: pointer;\n transition: background 0.1s;\n border-bottom: 1px solid #f8fafc;\n}\n.aiwg-suggestion-item:last-child { border-bottom: none; }\n.aiwg-suggestion-item:hover,\n.aiwg-suggestion-item.aiwg-active { background: #f5f3ff; }\n.aiwg-suggestion-icon { font-size: 16px; flex-shrink: 0; }\n.aiwg-suggestion-main { flex: 1; min-width: 0; }\n.aiwg-suggestion-text { font-size: 13px; font-weight: 500; color: #1a1a2e; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n.aiwg-suggestion-text mark { background: none; color: #6366f1; font-weight: 600; }\n.aiwg-suggestion-desc { font-size: 11px; color: #94a3b8; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n.aiwg-suggestion-badge { font-size: 10px; font-weight: 600; padding: 2px 8px; border-radius: 99px; background: #ede9fe; color: #6366f1; flex-shrink: 0; }\n.aiwg-kbd-hint {\n padding: 6px 14px;\n font-size: 11px; color: #cbd5e1; background: #f8fafc;\n display: flex; justify-content: flex-end; gap: 10px;\n}\n.aiwg-kbd-hint kbd { font-family: inherit; background: #e2e8f0; border-radius: 4px; padding: 1px 5px; font-size: 10px; color: #64748b; }\n\n/* \u2500\u2500 Error toast \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.aiwg-error {\n margin: 0 16px 8px;\n padding: 8px 12px;\n background: #fef2f2;\n border: 1px solid #fecaca;\n border-radius: 8px;\n font-size: 12px;\n color: #dc2626;\n}\n\n/* \u2500\u2500 Minimized state \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.aiwg-root.aiwg-minimized .aiwg-messages,\n.aiwg-root.aiwg-minimized .aiwg-chip-row,\n.aiwg-root.aiwg-minimized .aiwg-error,\n.aiwg-root.aiwg-minimized .aiwg-input-area,\n.aiwg-root.aiwg-minimized .aiwg-powered { display: none !important; }\n.aiwg-root.aiwg-minimized .aiwg-minimize-btn { display: none !important; }\n.aiwg-root.aiwg-minimized { cursor: pointer; box-shadow: 0 8px 32px rgba(0,0,0,0.28); }\n.aiwg-root.aiwg-minimized .aiwg-header { cursor: pointer !important; }\n\n/* \u2500\u2500 Powered-by footer \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.aiwg-powered {\n text-align: center;\n padding: 6px 16px 10px;\n font-size: 11px; color: #94a3b8;\n flex-shrink: 0;\n border-top: 1px solid #f1f5f9;\n background: #fff;\n}\n.aiwg-powered a { color: #6366f1; text-decoration: none; font-weight: 500; }\n.aiwg-powered a:hover { text-decoration: underline; }\n\n/* \u2500\u2500 Markdown rendering \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.aiwg-msg-bubble strong { font-weight: 600; }\n.aiwg-msg-bubble em { font-style: italic; }\n.aiwg-msg-bubble code { background: rgba(0,0,0,0.08); padding: 1px 5px; border-radius: 4px; font-family: 'Fira Code','Consolas',monospace; font-size: 0.9em; }\n.aiwg-msg--user .aiwg-msg-bubble code { background: rgba(255,255,255,0.2); }\n.aiwg-msg-bubble ol, .aiwg-msg-bubble ul { padding-left: 18px; margin: 6px 0; display: flex; flex-direction: column; gap: 3px; }\n.aiwg-msg-bubble ol { list-style: decimal; }\n.aiwg-msg-bubble ul { list-style: disc; }\n.aiwg-msg-bubble li { line-height: 1.5; }\n.aiwg-msg-bubble h3, .aiwg-msg-bubble h4 { font-weight: 600; margin: 8px 0 4px; }\n.aiwg-msg-bubble p { margin: 2px 0; }\n\n/* \u2500\u2500 Dark theme \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.aiwg-root.aiwg-dark { background: #0f172a; color: #e2e8f0; }\n.aiwg-dark .aiwg-msg--bot .aiwg-msg-bubble { background: #1e293b; color: #e2e8f0; }\n.aiwg-dark .aiwg-input-area { background: #0f172a; border-top-color: #1e293b; }\n.aiwg-dark .aiwg-input { background: #1e293b; border-color: #334155; color: #e2e8f0; }\n.aiwg-dark .aiwg-input:focus { border-color: #8b5cf6; background: #1e293b; box-shadow: 0 0 0 3px rgba(139,92,246,0.2); }\n.aiwg-dark .aiwg-suggestions { background: #1e293b; border-color: #334155; }\n.aiwg-dark .aiwg-suggestions-header { color: #64748b; border-bottom-color: #334155; }\n.aiwg-dark .aiwg-suggestion-item { border-bottom-color: #0f172a; }\n.aiwg-dark .aiwg-suggestion-item:hover,\n.aiwg-dark .aiwg-suggestion-item.aiwg-active { background: #2d1b69; }\n.aiwg-dark .aiwg-suggestion-text { color: #e2e8f0; }\n.aiwg-dark .aiwg-chip { background: #1e293b; border-color: #334155; }\n.aiwg-dark .aiwg-chip:hover { background: #2d1b69; }\n.aiwg-dark .aiwg-chip-shimmer { background: linear-gradient(90deg, #1e293b 25%, #334155 50%, #1e293b 75%); background-size: 200% 100%; }\n.aiwg-dark .aiwg-kbd-hint { background: #1e293b; }\n.aiwg-dark .aiwg-welcome h4 { color: #e2e8f0; }\n.aiwg-dark .aiwg-powered { background: #0f172a; border-top-color: #1e293b; }\n";