@courseecho/ai-widget-react 1.0.24 → 1.0.25
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/ShadowWrapper.d.ts +3 -7
- package/dist/ShadowWrapper.js +6 -177
- package/package.json +1 -1
- package/dist/core/src/auto-suggestion-generator.d.ts +0 -118
- package/dist/core/src/auto-suggestion-generator.js +0 -538
- package/dist/core/src/communication-service.d.ts +0 -47
- package/dist/core/src/communication-service.js +0 -124
- package/dist/core/src/index.d.ts +0 -11
- package/dist/core/src/index.js +0 -15
- package/dist/core/src/models.d.ts +0 -137
- package/dist/core/src/models.js +0 -5
- package/dist/core/src/sdk.d.ts +0 -166
- package/dist/core/src/sdk.js +0 -387
- package/dist/core/src/state-manager.d.ts +0 -200
- package/dist/core/src/state-manager.js +0 -368
- package/dist/core/src/widget-css.d.ts +0 -14
- package/dist/core/src/widget-css.js +0 -389
- package/dist/react/src/ShadowWrapper.d.ts +0 -12
- package/dist/react/src/ShadowWrapper.js +0 -44
- package/dist/react/src/components.d.ts +0 -25
- package/dist/react/src/components.js +0 -226
- package/dist/react/src/hooks.d.ts +0 -64
- package/dist/react/src/hooks.js +0 -181
- package/dist/react/src/index.d.ts +0 -7
- package/dist/react/src/index.js +0 -5
|
@@ -1,368 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
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";
|