@ourlu/assistant-sdk 0.2.5 → 0.2.6

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,997 @@
1
+ (function() {
2
+ function installColorUtils(ui) {
3
+ ui.clampAlpha = function clampAlpha(rawValue) {
4
+ var parsed = Number(rawValue);
5
+ if (!Number.isFinite(parsed)) return 1;
6
+ if (parsed < 0) return 0;
7
+ if (parsed > 1) return 1;
8
+ return parsed;
9
+ };
10
+
11
+ ui.normalizeHexColor = function normalizeHexColor(rawValue, fallbackColor) {
12
+ var color = String(rawValue || "").trim().toLowerCase();
13
+ if (!/^#[0-9a-f]{6}$/.test(color)) return fallbackColor;
14
+ return color;
15
+ };
16
+
17
+ ui.hexToRgba = function hexToRgba(hexColor, alphaValue) {
18
+ var normalized = ui.normalizeHexColor(hexColor, "#ffffff");
19
+ var alpha = ui.clampAlpha(alphaValue);
20
+ var r = parseInt(normalized.slice(1, 3), 16);
21
+ var g = parseInt(normalized.slice(3, 5), 16);
22
+ var b = parseInt(normalized.slice(5, 7), 16);
23
+ return "rgba(" + r + "," + g + "," + b + "," + alpha + ")";
24
+ };
25
+
26
+ ui.clampColorChannel = function clampColorChannel(channelValue) {
27
+ return Math.max(0, Math.min(255, channelValue));
28
+ };
29
+
30
+ ui.adjustHexBrightness = function adjustHexBrightness(hexColor, percentage) {
31
+ var normalizedColor = ui.normalizeHexColor(hexColor, "#000000");
32
+ var adjustment = Math.round((Number(percentage) / 100) * 255);
33
+ var red = ui.clampColorChannel(parseInt(normalizedColor.slice(1, 3), 16) + adjustment);
34
+ var green = ui.clampColorChannel(parseInt(normalizedColor.slice(3, 5), 16) + adjustment);
35
+ var blue = ui.clampColorChannel(parseInt(normalizedColor.slice(5, 7), 16) + adjustment);
36
+ return "#" + red.toString(16).padStart(2, "0") + green.toString(16).padStart(2, "0") + blue.toString(16).padStart(2, "0");
37
+ };
38
+ }
39
+
40
+ function installMascotTheme(ui) {
41
+ ui.mascotSvgSourceCache = {};
42
+
43
+ ui.escapeRegExp = function escapeRegExp(rawValue) {
44
+ return String(rawValue || "").replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
45
+ };
46
+
47
+ ui.replaceSvgColorTokens = function replaceSvgColorTokens(rawSvgMarkup, colorMap) {
48
+ var themedMarkup = String(rawSvgMarkup || "");
49
+ Object.keys(colorMap).forEach(function(token) {
50
+ var resolvedColor = String(colorMap[token] || "").trim();
51
+ if (!resolvedColor) return;
52
+ themedMarkup = themedMarkup.replace(new RegExp(ui.escapeRegExp(token), "gi"), resolvedColor);
53
+ });
54
+ return themedMarkup;
55
+ };
56
+
57
+ ui.assignMascotTokenGroup = function assignMascotTokenGroup(replacementMap, tokens, replacementColor) {
58
+ tokens.forEach(function(token) {
59
+ replacementMap[token] = replacementColor;
60
+ });
61
+ };
62
+
63
+ ui.buildMascotDerivedPalette = function buildMascotDerivedPalette(config) {
64
+ var blue500 = ui.normalizeHexColor(config.mascotSecondaryColor, "#68b1d6");
65
+ var blue700 = ui.normalizeHexColor(config.mascotSecondaryDarkColor, "#1472a8");
66
+ var gold500 = ui.normalizeHexColor(config.mascotGoldColor, "#ffd22e");
67
+ var eye500 = ui.normalizeHexColor(config.mascotEyeColor, "#040402");
68
+ var beak300 = ui.normalizeHexColor(config.mascotBeakColor, "#ab5f30");
69
+ var claw300 = ui.normalizeHexColor(config.mascotClawColor, "#ab5f30");
70
+ var neutral200 = ui.normalizeHexColor(config.mascotNeutralColor, "#e6e6e6");
71
+ var brow500 = ui.normalizeHexColor(config.mascotBrowColor, "#3e3e3e");
72
+ return {
73
+ blue400: ui.adjustHexBrightness(blue500, 8),
74
+ blue500: blue500,
75
+ blue550: ui.adjustHexBrightness(blue500, -6),
76
+ blue580: ui.adjustHexBrightness(blue500, -24),
77
+ blue600: ui.adjustHexBrightness(blue700, 12),
78
+ blue700: blue700,
79
+ blue800: ui.adjustHexBrightness(blue700, -10),
80
+ gold500: gold500,
81
+ gold450: ui.adjustHexBrightness(gold500, -6),
82
+ gold700: ui.adjustHexBrightness(gold500, -24),
83
+ gold900: ui.adjustHexBrightness(gold500, -42),
84
+ eye500: eye500,
85
+ eye900: ui.adjustHexBrightness(eye500, -26),
86
+ beak300: beak300,
87
+ beak400: ui.adjustHexBrightness(beak300, 14),
88
+ beak500: ui.adjustHexBrightness(beak300, 8),
89
+ beak700: ui.adjustHexBrightness(beak300, -24),
90
+ claw300: claw300,
91
+ claw400: ui.adjustHexBrightness(claw300, 18),
92
+ claw700: ui.adjustHexBrightness(claw300, -30),
93
+ brow500: brow500,
94
+ brow400: ui.adjustHexBrightness(brow500, 4),
95
+ neutral200: neutral200,
96
+ neutral300: ui.adjustHexBrightness(neutral200, -18),
97
+ black: ui.adjustHexBrightness(eye500, -40),
98
+ white: "#ffffff"
99
+ };
100
+ };
101
+
102
+ ui.buildMascotColorReplacementMap = function buildMascotColorReplacementMap(config) {
103
+ var palette = ui.buildMascotDerivedPalette(config || {});
104
+ var replacementMap = {};
105
+ ui.assignMascotTokenGroup(replacementMap, ["#70c1ea"], palette.blue400);
106
+ ui.assignMascotTokenGroup(replacementMap, ["#68b1d6"], palette.blue500);
107
+ ui.assignMascotTokenGroup(replacementMap, ["#44aade"], palette.blue550);
108
+ ui.assignMascotTokenGroup(replacementMap, ["#4a9ac3"], palette.blue580);
109
+ ui.assignMascotTokenGroup(replacementMap, ["#2f97cc"], palette.blue600);
110
+ ui.assignMascotTokenGroup(replacementMap, ["#1472a8"], palette.blue700);
111
+ ui.assignMascotTokenGroup(replacementMap, ["#156696"], palette.blue800);
112
+ ui.assignMascotTokenGroup(replacementMap, ["#ffd22e"], palette.gold500);
113
+ ui.assignMascotTokenGroup(replacementMap, ["#f0c31f"], palette.gold450);
114
+ ui.assignMascotTokenGroup(replacementMap, ["#c29500"], palette.gold700);
115
+ ui.assignMascotTokenGroup(replacementMap, ["#946700"], palette.gold900);
116
+ ui.assignMascotTokenGroup(replacementMap, ["#040402", "#00193b"], palette.eye900);
117
+ ui.assignMascotTokenGroup(replacementMap, ["#ab5f30"], palette.beak300);
118
+ ui.assignMascotTokenGroup(replacementMap, ["#dd8e5d", "#dd9060", "#ca7e4f"], palette.beak400);
119
+ ui.assignMascotTokenGroup(replacementMap, ["#d98d5e"], palette.beak500);
120
+ ui.assignMascotTokenGroup(replacementMap, ["#6e2200"], palette.beak700);
121
+ ui.assignMascotTokenGroup(replacementMap, ["#6f2301"], palette.claw700);
122
+ ui.assignMascotTokenGroup(replacementMap, ["#da8e5f"], palette.claw400);
123
+ ui.assignMascotTokenGroup(replacementMap, ["#3e3e3e"], palette.brow500);
124
+ ui.assignMascotTokenGroup(replacementMap, ["#434343"], palette.brow400);
125
+ ui.assignMascotTokenGroup(replacementMap, ["#e6e6e6"], palette.neutral200);
126
+ replacementMap.white = palette.white;
127
+ replacementMap.black = palette.black;
128
+ return replacementMap;
129
+ };
130
+
131
+ ui.isLikelySvgAsset = function isLikelySvgAsset(rawUrl) {
132
+ return /\.svg(?:[?#].*)?$/i.test(String(rawUrl || "").trim());
133
+ };
134
+
135
+ ui.resolveAbsoluteMascotUrl = function resolveAbsoluteMascotUrl(mascotUrl, apiBaseUrl) {
136
+ var normalized = String(mascotUrl || "").trim();
137
+ if (!normalized) return "";
138
+ if (/^https?:\/\//i.test(normalized)) return normalized;
139
+ var base = String(apiBaseUrl || "").trim().replace(/\/$/, "");
140
+ if (!base) {
141
+ throw new Error("[OurluMairie] mascot URL relative sans apiBaseUrl: " + normalized);
142
+ }
143
+ return normalized.startsWith("/") ? base + normalized : base + "/" + normalized;
144
+ };
145
+
146
+ ui.encodeSvgToDataUrl = function encodeSvgToDataUrl(svgMarkup) {
147
+ return "data:image/svg+xml;charset=utf-8," + encodeURIComponent(String(svgMarkup || ""));
148
+ };
149
+
150
+ ui.resolveMascotUrlWithTheme = function resolveMascotUrlWithTheme(mascotUrl, config) {
151
+ var normalizedMascotUrl = ui.resolveAbsoluteMascotUrl(mascotUrl, (config || {}).apiBaseUrl);
152
+ if (!normalizedMascotUrl || typeof fetch !== "function" || !ui.isLikelySvgAsset(normalizedMascotUrl)) {
153
+ return Promise.resolve(normalizedMascotUrl);
154
+ }
155
+ var replacementMap = ui.buildMascotColorReplacementMap(config || {});
156
+ var cacheKey = normalizedMascotUrl + "::" + [
157
+ ui.normalizeHexColor((config || {}).mascotSecondaryColor, "#68b1d6"),
158
+ ui.normalizeHexColor((config || {}).mascotSecondaryDarkColor, "#1472a8"),
159
+ ui.normalizeHexColor((config || {}).mascotGoldColor, "#ffd22e"),
160
+ ui.normalizeHexColor((config || {}).mascotEyeColor, "#040402"),
161
+ ui.normalizeHexColor((config || {}).mascotBeakColor, "#ab5f30"),
162
+ ui.normalizeHexColor((config || {}).mascotNeutralColor, "#e6e6e6"),
163
+ ui.normalizeHexColor((config || {}).mascotBrowColor, "#3e3e3e"),
164
+ ui.normalizeHexColor((config || {}).mascotClawColor, "#ab5f30")
165
+ ].join("::");
166
+ if (ui.mascotSvgSourceCache[cacheKey]) {
167
+ return Promise.resolve(ui.mascotSvgSourceCache[cacheKey]);
168
+ }
169
+ return fetch(normalizedMascotUrl, { method: "GET", credentials: "omit" })
170
+ .then(function(response) {
171
+ if (!response.ok) {
172
+ throw new Error("Impossible de charger la mascotte.");
173
+ }
174
+ return response.text();
175
+ })
176
+ .then(function(rawSvgMarkup) {
177
+ if (!rawSvgMarkup || rawSvgMarkup.indexOf("<svg") === -1) {
178
+ throw new Error("Mascotte SVG invalide ou réponse non-SVG pour " + normalizedMascotUrl);
179
+ }
180
+ var themedSvgMarkup = ui.replaceSvgColorTokens(rawSvgMarkup, replacementMap);
181
+ var themedDataUrl = ui.encodeSvgToDataUrl(themedSvgMarkup);
182
+ ui.mascotSvgSourceCache[cacheKey] = themedDataUrl;
183
+ return themedDataUrl;
184
+ });
185
+ };
186
+ }
187
+
188
+ function installMarkdownRender(ui) {
189
+ ui.escapeHtml = function escapeHtml(rawValue) {
190
+ return String(rawValue || "")
191
+ .replace(/&/g, "&amp;")
192
+ .replace(/</g, "&lt;")
193
+ .replace(/>/g, "&gt;")
194
+ .replace(/"/g, "&quot;")
195
+ .replace(/'/g, "&#39;");
196
+ };
197
+
198
+ ui.escapeHtmlAttribute = function escapeHtmlAttribute(rawValue) {
199
+ return ui.escapeHtml(rawValue).replace(/`/g, "&#96;");
200
+ };
201
+
202
+ ui.renderInlineMarkdown = function renderInlineMarkdown(rawText) {
203
+ var escaped = ui.escapeHtml(rawText);
204
+ escaped = escaped.replace(/\[([^\]\n]+)\]\((https?:\/\/[^\s)]+)\)/g, function(_match, label, url) {
205
+ return '<a href="' + ui.escapeHtmlAttribute(url) + '" target="_blank" rel="noopener noreferrer">' + label + "</a>";
206
+ });
207
+ escaped = escaped.replace(/`([^`\n]+)`/g, "<code>$1</code>");
208
+ escaped = escaped.replace(/\*\*([^*\n]+)\*\*/g, "<strong>$1</strong>");
209
+ escaped = escaped.replace(/\*([^*\n]+)\*/g, "<em>$1</em>");
210
+ return escaped;
211
+ };
212
+
213
+ ui.renderAssistantMarkdown = function renderAssistantMarkdown(rawText) {
214
+ var normalized = String(rawText || "").replace(/\r\n?/g, "\n").trim();
215
+ if (!normalized) {
216
+ return "";
217
+ }
218
+
219
+ var codeBlocks = [];
220
+ normalized = normalized.replace(/```([\s\S]*?)```/g, function(_match, codeContent) {
221
+ var placeholder = "@@CM_CODE_BLOCK_" + codeBlocks.length + "@@";
222
+ codeBlocks.push(
223
+ '<pre><code>' + ui.escapeHtml(String(codeContent || "").replace(/^\n+|\n+$/g, "")) + "</code></pre>"
224
+ );
225
+ return placeholder;
226
+ });
227
+
228
+ var htmlParts = [];
229
+ var listType = "";
230
+ var listItems = [];
231
+ var lines = normalized.split("\n");
232
+
233
+ function flushList() {
234
+ if (!listType || listItems.length === 0) {
235
+ listType = "";
236
+ listItems = [];
237
+ return;
238
+ }
239
+ htmlParts.push("<" + listType + ">" + listItems.join("") + "</" + listType + ">");
240
+ listType = "";
241
+ listItems = [];
242
+ }
243
+
244
+ for (var index = 0; index < lines.length; index += 1) {
245
+ var rawLine = lines[index];
246
+ var trimmedLine = rawLine.trim();
247
+ if (!trimmedLine) {
248
+ flushList();
249
+ continue;
250
+ }
251
+
252
+ var unorderedMatch = trimmedLine.match(/^[-*]\s+(.+)$/);
253
+ if (unorderedMatch) {
254
+ if (listType && listType !== "ul") {
255
+ flushList();
256
+ }
257
+ listType = "ul";
258
+ listItems.push("<li>" + ui.renderInlineMarkdown(unorderedMatch[1]) + "</li>");
259
+ continue;
260
+ }
261
+
262
+ var orderedMatch = trimmedLine.match(/^\d+\.\s+(.+)$/);
263
+ if (orderedMatch) {
264
+ if (listType && listType !== "ol") {
265
+ flushList();
266
+ }
267
+ listType = "ol";
268
+ listItems.push("<li>" + ui.renderInlineMarkdown(orderedMatch[1]) + "</li>");
269
+ continue;
270
+ }
271
+
272
+ flushList();
273
+
274
+ var headingMatch = trimmedLine.match(/^(#{1,3})\s+(.+)$/);
275
+ if (headingMatch) {
276
+ var level = Math.min(4, headingMatch[1].length + 2);
277
+ htmlParts.push("<h" + level + ">" + ui.renderInlineMarkdown(headingMatch[2]) + "</h" + level + ">");
278
+ continue;
279
+ }
280
+
281
+ htmlParts.push("<p>" + ui.renderInlineMarkdown(trimmedLine) + "</p>");
282
+ }
283
+
284
+ flushList();
285
+
286
+ var rendered = htmlParts.join("");
287
+ rendered = rendered.replace(/@@CM_CODE_BLOCK_(\d+)@@/g, function(_match, rawIndex) {
288
+ var blockIndex = Number(rawIndex);
289
+ if (!Number.isInteger(blockIndex) || blockIndex < 0 || blockIndex >= codeBlocks.length) {
290
+ return "";
291
+ }
292
+ return codeBlocks[blockIndex];
293
+ });
294
+ return rendered || "<p>" + ui.renderInlineMarkdown(normalized) + "</p>";
295
+ };
296
+ }
297
+
298
+ function installMediaUtils(ui) {
299
+ ui.trimTrailingSlash = function trimTrailingSlash(value) {
300
+ return String(value || "").replace(/\/$/, "");
301
+ };
302
+
303
+ ui.mergeTranscript = function mergeTranscript(currentText, delta) {
304
+ var base = String(currentText || "");
305
+ var incoming = String(delta || "").replace(/^\s+/, "");
306
+ if (!incoming) return base;
307
+ if (!base) return incoming;
308
+ var lastChar = base[base.length - 1];
309
+ var firstChar = incoming[0];
310
+ var noSpaceAfter = [" ", "\n", "'", "\"", "(", "[", "{", "/", "-"];
311
+ var noSpaceBefore = [".", ",", ";", ":", "!", "?", ")", "]", "}", "/", "-"];
312
+ var needsSpace = noSpaceAfter.indexOf(lastChar) === -1 && noSpaceBefore.indexOf(firstChar) === -1;
313
+ return needsSpace ? base + " " + incoming : base + incoming;
314
+ };
315
+
316
+ ui.resolveRecorderMimeType = function resolveRecorderMimeType() {
317
+ if (typeof MediaRecorder === "undefined") return "";
318
+ var preferred = ["audio/webm;codecs=opus", "audio/webm", "audio/ogg;codecs=opus", "audio/ogg", "audio/mp4"];
319
+ for (var i = 0; i < preferred.length; i += 1) {
320
+ if (MediaRecorder.isTypeSupported(preferred[i])) return preferred[i];
321
+ }
322
+ return "";
323
+ };
324
+
325
+ ui.encodeArrayBufferToBase64 = function encodeArrayBufferToBase64(arrayBuffer) {
326
+ var bytes = new Uint8Array(arrayBuffer);
327
+ var chunkSize = 0x8000;
328
+ var chunks = [];
329
+ for (var offset = 0; offset < bytes.length; offset += chunkSize) {
330
+ var part = bytes.subarray(offset, offset + chunkSize);
331
+ chunks.push(String.fromCharCode.apply(null, part));
332
+ }
333
+ return btoa(chunks.join(""));
334
+ };
335
+ }
336
+
337
+ function installEventBus(ui) {
338
+ function EventBus() {
339
+ this.events = {};
340
+ }
341
+
342
+ EventBus.prototype.on = function(eventName, handler) {
343
+ if (!this.events[eventName]) this.events[eventName] = [];
344
+ this.events[eventName].push(handler);
345
+ };
346
+
347
+ EventBus.prototype.emit = function(eventName, payload) {
348
+ var handlers = this.events[eventName] || [];
349
+ handlers.forEach(function(handler) {
350
+ try { handler(payload); } catch (_) {}
351
+ });
352
+ };
353
+
354
+ ui.EventBus = EventBus;
355
+ }
356
+
357
+ function installWidgetCssBuilder(ui) {
358
+ function WidgetCssBuilder(config) {
359
+ this.config = config;
360
+ }
361
+
362
+ WidgetCssBuilder.prototype.resolveLayout = function() {
363
+ var panelWidth = Number(this.config.panelWidth);
364
+ if (!Number.isFinite(panelWidth)) panelWidth = 420;
365
+ panelWidth = Math.max(360, Math.min(960, Math.round(panelWidth)));
366
+
367
+ var panelHeight = Number(this.config.panelHeight);
368
+ if (!Number.isFinite(panelHeight)) panelHeight = 640;
369
+ panelHeight = Math.max(480, Math.min(1400, Math.round(panelHeight)));
370
+
371
+ var panelMaxHeightVh = Number(this.config.panelMaxHeightVh);
372
+ if (!Number.isFinite(panelMaxHeightVh)) panelMaxHeightVh = 85;
373
+ panelMaxHeightVh = Math.max(60, Math.min(98, Math.round(panelMaxHeightVh)));
374
+
375
+ var borderRadius = Number(this.config.panelBorderRadius);
376
+ if (!Number.isFinite(borderRadius)) borderRadius = 16;
377
+ borderRadius = Math.max(0, Math.min(32, Math.round(borderRadius)));
378
+
379
+ return {
380
+ panelWidth: panelWidth,
381
+ panelHeight: panelHeight,
382
+ panelMaxHeightVh: panelMaxHeightVh,
383
+ borderRadius: borderRadius
384
+ };
385
+ };
386
+
387
+ WidgetCssBuilder.prototype.darkenHex = function(hex, percent) {
388
+ var h = String(hex || "#000000").replace("#", "");
389
+ if (h.length !== 6) h = "000000";
390
+ var r = Math.max(0, Math.round(parseInt(h.slice(0, 2), 16) * (1 - percent / 100)));
391
+ var g = Math.max(0, Math.round(parseInt(h.slice(2, 4), 16) * (1 - percent / 100)));
392
+ var b = Math.max(0, Math.round(parseInt(h.slice(4, 6), 16) * (1 - percent / 100)));
393
+ return "#" + ("0" + r.toString(16)).slice(-2) + ("0" + g.toString(16)).slice(-2) + ("0" + b.toString(16)).slice(-2);
394
+ };
395
+
396
+ WidgetCssBuilder.prototype.resolveBackground = function(baseColor, gradientEnabled, autoMode, manualColor2, darkenPct) {
397
+ if (!gradientEnabled) return baseColor;
398
+ var color2 = autoMode ? this.darkenHex(baseColor, darkenPct) : (manualColor2 || this.darkenHex(baseColor, darkenPct));
399
+ return "linear-gradient(135deg," + baseColor + " 0%," + color2 + " 100%)";
400
+ };
401
+
402
+ WidgetCssBuilder.prototype.build = function() {
403
+ var cfg = this.config;
404
+ var side = cfg.position === "bottom-left" ? "left" : "right";
405
+ var headerBg = this.resolveBackground(cfg.primaryColor, cfg.headerGradientEnabled !== false, cfg.headerGradientColor2Auto !== false, cfg.headerGradientColor2, 25);
406
+ var panelBg = ui.hexToRgba(cfg.panelBackgroundColor, cfg.panelBackgroundAlpha);
407
+ var layout = this.resolveLayout();
408
+ var pw = layout.panelWidth + "px";
409
+ var ph = layout.panelHeight + "px";
410
+ var mhVh = layout.panelMaxHeightVh + "vh";
411
+ var br = layout.borderRadius + "px";
412
+
413
+ var userBubbleBase = cfg.userBubbleColor || cfg.primaryColor;
414
+ var userBubbleBg = this.resolveBackground(userBubbleBase, cfg.userBubbleGradientEnabled === true, cfg.userBubbleGradientColor2Auto !== false, cfg.userBubbleGradientColor2, 25);
415
+ var assistantBubbleBase = cfg.assistantBubbleColor || "#f0f4f8";
416
+ var assistantBubbleBg = this.resolveBackground(assistantBubbleBase, cfg.assistantBubbleGradientEnabled === true, cfg.assistantBubbleGradientColor2Auto !== false, cfg.assistantBubbleGradientColor2, 15);
417
+ var assistantBubbleTextColor = cfg.assistantBubbleTextColor || "#1a1a2e";
418
+
419
+ return [
420
+ this.buildResetAndBase(),
421
+ this.buildBubble(side, headerBg, cfg.primaryColor),
422
+ this.buildPanel(side, pw, ph, mhVh, panelBg, br),
423
+ this.buildHeader(headerBg, br),
424
+ this.buildNotes(),
425
+ this.buildMessages(),
426
+ this.buildMessageBubbles(userBubbleBg, assistantBubbleBg, assistantBubbleTextColor),
427
+ this.buildTypingAndError(),
428
+ this.buildComposer(cfg.primaryColor, br),
429
+ this.buildMobileOverrides()
430
+ ].join("\n");
431
+ };
432
+
433
+ WidgetCssBuilder.prototype.buildResetAndBase = function() {
434
+ return [
435
+ ":host *{box-sizing:border-box;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,system-ui,sans-serif;-webkit-font-smoothing:antialiased}",
436
+ ":host button{outline:none;-webkit-tap-highlight-color:transparent}",
437
+ ":host button:focus{outline:none}",
438
+ ":host button:focus-visible{outline:2px solid rgba(0,102,255,.4);outline-offset:2px}"
439
+ ].join("\n");
440
+ };
441
+
442
+ WidgetCssBuilder.prototype.buildBubble = function(side, gradient, primaryColor) {
443
+ return [
444
+ "#cm-bubble{position:fixed;" + side + ":20px;bottom:20px;width:64px;height:64px;border-radius:50%;background:" + gradient + ";border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;z-index:2147483000;box-shadow:0 8px 24px rgba(0,0,0,.28),0 2px 8px rgba(0,0,0,.12);transition:transform .2s cubic-bezier(.4,0,.2,1)}",
445
+ "#cm-bubble:hover{transform:scale(1.1)}",
446
+ "#cm-bubble:active{transform:scale(0.95)}",
447
+ "#cm-bubble:focus-visible{outline:3px solid " + primaryColor + ";outline-offset:3px}",
448
+ "#cm-bubble img{width:84px;height:84px;object-fit:contain;position:absolute;top:-14px;filter:drop-shadow(0 2px 4px rgba(0,0,0,.15))}"
449
+ ].join("\n");
450
+ };
451
+
452
+ WidgetCssBuilder.prototype.buildPanel = function(side, width, height, maxHeightVh, background, borderRadius) {
453
+ return [
454
+ "#cm-panel{position:fixed;" + side + ":24px;bottom:100px;width:" + width + ";max-width:calc(100vw - 32px);height:" + height + ";max-height:" + maxHeightVh + ";background:" + background + ";border-radius:" + borderRadius + ";box-shadow:0 16px 48px rgba(0,0,0,.22),0 4px 16px rgba(0,0,0,.1);display:none;flex-direction:column;overflow:visible;z-index:2147483000;transition:box-shadow .2s,bottom .25s cubic-bezier(.4,0,.2,1)}",
455
+ "#cm-panel.open{display:flex;bottom:24px}",
456
+ "#cm-panel.maximized{width:840px !important;height:calc(100vh - 48px) !important;max-width:calc(100vw - 48px) !important;max-height:none !important}"
457
+ ].join("\n");
458
+ };
459
+
460
+ WidgetCssBuilder.prototype.buildHeader = function(gradient, borderRadius) {
461
+ return [
462
+ "#cm-header{display:flex;align-items:center;justify-content:space-between;padding:10px 12px 10px 80px;background:" + gradient + ";color:#fff;position:relative;border-radius:" + borderRadius + " " + borderRadius + " 0 0;min-height:44px;flex-shrink:0}",
463
+ "#cm-header-mascot{width:83px;height:83px;position:absolute;left:-6px;top:-16px;filter:drop-shadow(0 2px 3px rgba(0,0,0,.12));pointer-events:none}",
464
+ "#cm-title{margin:0;font-size:16px;font-weight:600;letter-spacing:-0.01em}",
465
+ "#cm-header-actions{display:flex;align-items:center;gap:4px}",
466
+ ".cm-header-btn{background:none;border:none;color:#fff;cursor:pointer;padding:6px;border-radius:8px;transition:background .15s;display:flex;align-items:center;justify-content:center;-webkit-user-select:none;user-select:none}",
467
+ ".cm-header-btn:hover{background:rgba(255,255,255,.18)}",
468
+ ".cm-header-btn:active{transform:scale(0.92)}",
469
+ ".cm-header-btn svg{width:22px;height:22px}"
470
+ ].join("\n");
471
+ };
472
+
473
+ WidgetCssBuilder.prototype.buildNotes = function() {
474
+ return [
475
+ "#cm-disclaimer,#cm-transparency{font-size:13px;display:flex;align-items:center;gap:8px;padding:8px 14px;flex-shrink:0}",
476
+ "#cm-disclaimer{background:#e8f4fd;color:#1a5276;border-bottom:1px solid #b3d9f2}",
477
+ "#cm-transparency{color:#555;border-top:1px solid #eee;background:#fff;display:none}",
478
+ ".cm-close-note{background:none;border:none;cursor:pointer;border-radius:6px;padding:4px 8px;min-width:32px;min-height:32px;width:32px;opacity:.6;transition:opacity .15s,background .15s;flex-shrink:0;display:flex;align-items:center;justify-content:center;margin-left:auto}",
479
+ ".cm-close-note:hover{opacity:1;background:rgba(0,0,0,.08)}",
480
+ ".cm-close-note svg{width:16px;height:16px}"
481
+ ].join("\n");
482
+ };
483
+
484
+ WidgetCssBuilder.prototype.buildMessages = function() {
485
+ return [
486
+ "#cm-messages{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:12px;scroll-behavior:smooth;overscroll-behavior:contain}",
487
+ "#cm-messages::-webkit-scrollbar{width:6px}",
488
+ "#cm-messages::-webkit-scrollbar-track{background:transparent}",
489
+ "#cm-messages::-webkit-scrollbar-thumb{background:rgba(0,0,0,.12);border-radius:3px}",
490
+ "#cm-messages::-webkit-scrollbar-thumb:hover{background:rgba(0,0,0,.2)}",
491
+ "#cm-welcome{color:#555;font-size:15px;line-height:1.6;text-align:center;padding:24px 16px}"
492
+ ].join("\n");
493
+ };
494
+
495
+ WidgetCssBuilder.prototype.buildMessageBubbles = function(userBubbleBg, assistantBubbleBg, assistantTextColor) {
496
+ return [
497
+ ".cm-msg{max-width:88%;padding:12px 16px;border-radius:16px;font-size:15px;line-height:1.55;word-break:break-word;animation:cm-msg-in .25s cubic-bezier(.4,0,.2,1)}",
498
+ "@keyframes cm-msg-in{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}",
499
+ ".cm-msg.assistant{background:" + assistantBubbleBg + ";color:" + assistantTextColor + ";border-bottom-left-radius:4px;align-self:flex-start}",
500
+ ".cm-msg.assistant p{margin:0 0 10px}",
501
+ ".cm-msg.assistant p:last-child{margin-bottom:0}",
502
+ ".cm-msg.assistant ul,.cm-msg.assistant ol{margin:0 0 10px 20px;padding:0}",
503
+ ".cm-msg.assistant li{margin:0 0 4px}",
504
+ ".cm-msg.assistant a{color:#0b57d0;text-decoration:underline;word-break:break-all}",
505
+ ".cm-msg.assistant code{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;background:#e2e8f0;padding:2px 6px;border-radius:4px;font-size:13px}",
506
+ ".cm-msg.assistant pre{margin:0 0 10px;padding:10px 12px;background:#1f2937;color:#f9fafb;border-radius:10px;overflow-x:auto;font-size:13px}",
507
+ ".cm-msg.assistant pre code{background:transparent;color:inherit;padding:0;font-size:inherit}",
508
+ ".cm-msg.assistant h3,.cm-msg.assistant h4{margin:0 0 8px;font-size:15px;line-height:1.35;font-weight:600}",
509
+ ".cm-msg.user{background:" + userBubbleBg + ";color:#fff;border-bottom-right-radius:4px;align-self:flex-end}"
510
+ ].join("\n");
511
+ };
512
+
513
+ WidgetCssBuilder.prototype.buildTypingAndError = function() {
514
+ return [
515
+ "#cm-typing{padding:8px 16px;font-size:13px;color:#666;display:none}",
516
+ "#cm-typing.visible{display:flex;align-items:center;gap:8px}",
517
+ "#cm-typing::before{content:'';display:inline-block;width:6px;height:6px;background:#666;border-radius:50%;animation:cm-dot-pulse 1.2s infinite}",
518
+ "@keyframes cm-dot-pulse{0%,100%{opacity:.3;transform:scale(.8)}50%{opacity:1;transform:scale(1.1)}}",
519
+ "#cm-error{display:none;background:#f8d7da;color:#58151c;font-size:13px;padding:10px 14px}",
520
+ "#cm-error.visible{display:block}"
521
+ ].join("\n");
522
+ };
523
+
524
+ WidgetCssBuilder.prototype.buildComposer = function(primaryColor, borderRadius) {
525
+ return [
526
+ "#cm-form{display:flex;flex-wrap:wrap;align-items:flex-end;gap:8px;padding:12px 16px;border-top:1px solid #eee;background:#fff;border-radius:0 0 " + borderRadius + " " + borderRadius + ";flex-shrink:0}",
527
+ "#cm-form.cm-multiline #cm-input{order:-1;flex-basis:100%;width:100%}",
528
+ "#cm-form.cm-multiline #cm-mic{margin-right:auto}",
529
+ "#cm-input{flex:1;border:1.5px solid #d1d5db;border-radius:14px;padding:12px 16px;min-height:48px;max-height:200px;resize:none;outline:none;line-height:1.5;font-size:15px;transition:border-color .2s,box-shadow .2s;overflow-y:auto}",
530
+ "#cm-input:focus{border-color:" + primaryColor + ";box-shadow:0 0 0 3px rgba(0,102,255,.15)}",
531
+ "#cm-input::placeholder{color:#9ca3af}",
532
+ "#cm-send,#cm-mic{width:48px;height:48px;border-radius:50%;display:flex;align-items:center;justify-content:center;border:none;cursor:pointer;flex-shrink:0;transition:transform .15s,opacity .15s;font-size:18px;-webkit-user-select:none;user-select:none;-webkit-user-drag:none}",
533
+ "#cm-send svg,#cm-mic svg{width:22px;height:22px}",
534
+ "#cm-send{background:" + primaryColor + ";color:#fff}",
535
+ "#cm-send:hover:not(:disabled){transform:scale(1.08)}",
536
+ "#cm-send:active:not(:disabled){transform:scale(0.94)}",
537
+ "#cm-send:disabled,#cm-mic:disabled{opacity:.4;cursor:not-allowed}",
538
+ "#cm-mic{background:#f3f4f6;color:#667085;border:none}",
539
+ "#cm-mic:hover:not(:disabled){background:#e5e7eb}",
540
+ "#cm-mic.listening{background:#ef4444;color:#fff;border-color:#ef4444;animation:cm-mic-pulse 1.5s infinite}",
541
+ "@keyframes cm-mic-pulse{0%,100%{box-shadow:0 0 0 0 rgba(239,68,68,.35)}50%{box-shadow:0 0 0 8px rgba(239,68,68,0)}}"
542
+ ].join("\n");
543
+ };
544
+
545
+ WidgetCssBuilder.prototype.buildMobileOverrides = function() {
546
+ return [
547
+ "@media (max-width:600px){",
548
+ "#cm-bubble{width:56px;height:56px;bottom:16px;right:16px !important;left:auto !important}",
549
+ "#cm-bubble img{width:72px;height:72px;top:-12px}",
550
+ "#cm-panel{position:fixed !important;left:0 !important;right:0 !important;bottom:0;top:0;width:100% !important;max-width:none !important;height:auto;max-height:none !important;border-radius:0 !important}",
551
+ "#cm-header{padding:10px 12px 10px 68px;border-radius:0 !important}",
552
+ "#cm-header-mascot{width:60px;height:60px;left:2px;top:50%;transform:translateY(-50%)}",
553
+ "#cm-title{font-size:16px}",
554
+ ".cm-header-btn-maximize{display:none !important}",
555
+ "#cm-messages{flex:1;min-height:0;padding:12px 16px;gap:12px;overflow-y:auto}",
556
+ ".cm-msg{max-width:92%;padding:12px 14px;font-size:15px;border-radius:16px}",
557
+ "#cm-form{padding:10px 12px;padding-bottom:calc(10px + env(safe-area-inset-bottom,0px));gap:8px;flex-shrink:0}",
558
+ "#cm-input{min-height:44px;font-size:16px;border-radius:14px;padding:10px 14px}",
559
+ "#cm-send,#cm-mic{width:44px;height:44px}",
560
+ "#cm-send svg,#cm-mic svg{width:22px;height:22px}",
561
+ "#cm-typing{padding:6px 16px;font-size:13px}",
562
+ "#cm-disclaimer,#cm-transparency{font-size:12px;padding:8px 12px}",
563
+ "#cm-welcome{font-size:15px;padding:24px 16px}",
564
+ "}"
565
+ ].join("\n");
566
+ };
567
+
568
+ ui.WidgetCssBuilder = WidgetCssBuilder;
569
+ }
570
+
571
+ function installWidgetUIManager(ui) {
572
+ function WidgetUIManager(config) {
573
+ this.config = config;
574
+ this.root = null;
575
+ this.bubble = null;
576
+ this.bubbleMascot = null;
577
+ this.panel = null;
578
+ this.headerMascot = null;
579
+ this.messages = null;
580
+ this.input = null;
581
+ this.sendButton = null;
582
+ this.micButton = null;
583
+ this.typing = null;
584
+ this.error = null;
585
+ this.welcome = null;
586
+ this.streamingAssistantElement = null;
587
+ this.streamingAssistantBuffer = "";
588
+ this.themeOverrideStyle = null;
589
+ }
590
+
591
+ WidgetUIManager.prototype.mount = function() {
592
+ if (document.getElementById(ui.CONTAINER_ID)) return false;
593
+ var cssBuilder = new ui.WidgetCssBuilder(this.config);
594
+ var css = cssBuilder.build();
595
+ var esc = ui.escapeHtml;
596
+ var escAttr = ui.escapeHtmlAttribute;
597
+
598
+ var host = document.createElement("div");
599
+ host.id = ui.CONTAINER_ID;
600
+ document.body.appendChild(host);
601
+ var shadow = host.attachShadow({ mode: "open" });
602
+ var root = document.createElement("div");
603
+ root.innerHTML = [
604
+ "<style>" + css + "</style>",
605
+ '<button id="cm-bubble" type="button" aria-label="Ouvrir l\'assistant">',
606
+ '<img id="cm-bubble-mascot" src="' + escAttr(this.config.mascotUrl) + '" alt="Mascotte assistant" />',
607
+ "</button>",
608
+ '<div id="cm-panel" role="dialog" aria-modal="true" aria-label="Chat ' + escAttr(this.config.assistantName) + '">',
609
+ '<div id="cm-header"><img id="cm-header-mascot" src="' + escAttr(this.config.mascotUrl) + '" alt="" /><h3 id="cm-title">' + esc(this.config.assistantName) + '</h3><div id="cm-header-actions"><button class="cm-header-btn cm-header-btn-maximize" id="cm-maximize" type="button" aria-label="Agrandir"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 3h6v6"/><path d="M9 21H3v-6"/><path d="M21 3l-7 7"/><path d="M3 21l7-7"/></svg></button><button class="cm-header-btn" id="cm-close" type="button" aria-label="Fermer l\'assistant"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg></button></div></div>',
610
+ this.config.disclaimer ? '<div id="cm-disclaimer"><span>' + esc(this.config.disclaimerText) + '</span><button class="cm-close-note" id="cm-disclaimer-close" type="button" aria-label="Fermer l\'avertissement"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg></button></div>' : "",
611
+ '<div id="cm-messages" aria-live="polite" aria-relevant="additions"><p id="cm-welcome">' + esc(this.config.welcomeMessage) + "</p></div>",
612
+ '<div id="cm-typing">L\'assistant r\u00e9fl\u00e9chit\u2026</div>',
613
+ '<div id="cm-error"></div>',
614
+ '<div id="cm-transparency"><span>' + esc(this.config.transparencyText) + '</span><button class="cm-close-note" id="cm-transparency-close" type="button" aria-label="Fermer le message de transparence"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg></button></div>',
615
+ '<form id="cm-form"><button id="cm-mic" type="button" aria-label="Dicter un message"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/><line x1="12" y1="19" x2="12" y2="23"/><line x1="8" y1="23" x2="16" y2="23"/></svg></button><textarea id="cm-input" rows="1" aria-label="Votre message" placeholder="Posez votre question\u2026"></textarea><button id="cm-send" type="submit" aria-label="Envoyer"><svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/></svg></button></form>',
616
+ "</div>"
617
+ ].join("");
618
+ shadow.appendChild(root);
619
+ this.host = host;
620
+ this.root = root;
621
+ this.bubble = root.querySelector("#cm-bubble");
622
+ this.bubbleMascot = root.querySelector("#cm-bubble-mascot");
623
+ this.panel = root.querySelector("#cm-panel");
624
+ this.headerMascot = root.querySelector("#cm-header-mascot");
625
+ this.messages = root.querySelector("#cm-messages");
626
+ this.input = root.querySelector("#cm-input");
627
+ this.sendButton = root.querySelector("#cm-send");
628
+ this.micButton = root.querySelector("#cm-mic");
629
+ this.typing = root.querySelector("#cm-typing");
630
+ this.error = root.querySelector("#cm-error");
631
+ this.welcome = root.querySelector("#cm-welcome");
632
+ this.themeOverrideStyle = document.createElement("style");
633
+ this.themeOverrideStyle.id = "cm-theme-overrides";
634
+ root.appendChild(this.themeOverrideStyle);
635
+ this.bindMascotImageErrorHandlers();
636
+ this.bindAutoGrowInput();
637
+ this.bindMobileViewportResize();
638
+ this.applyThemeOverrides();
639
+ this.applyMascotTheme();
640
+ return true;
641
+ };
642
+
643
+ WidgetUIManager.prototype.bindMobileViewportResize = function() {
644
+ var panelRef = this.panel;
645
+ if (!panelRef) return;
646
+ var mobileQuery = window.matchMedia("(max-width:600px)");
647
+ if (!window.visualViewport) return;
648
+ function onViewportResize() {
649
+ if (!mobileQuery.matches) return;
650
+ var vv = window.visualViewport;
651
+ panelRef.style.height = vv.height + "px";
652
+ panelRef.style.top = vv.offsetTop + "px";
653
+ }
654
+ window.visualViewport.addEventListener("resize", onViewportResize);
655
+ window.visualViewport.addEventListener("scroll", onViewportResize);
656
+ this._mobileQuery = mobileQuery;
657
+ };
658
+
659
+ WidgetUIManager.prototype.bindAutoGrowInput = function() {
660
+ var inputEl = this.input;
661
+ var formEl = this.root ? this.root.querySelector("#cm-form") : null;
662
+ if (!inputEl) return;
663
+ var singleLineHeight = 0;
664
+ function adjustHeight() {
665
+ inputEl.style.height = "auto";
666
+ var scrollH = inputEl.scrollHeight;
667
+ var maxH = 200;
668
+ inputEl.style.height = Math.min(scrollH, maxH) + "px";
669
+ inputEl.style.overflowY = scrollH > maxH ? "auto" : "hidden";
670
+ if (formEl) {
671
+ if (!singleLineHeight && scrollH > 0) singleLineHeight = scrollH;
672
+ var isMultiline = singleLineHeight > 0 && scrollH > singleLineHeight + 4;
673
+ formEl.classList.toggle("cm-multiline", isMultiline);
674
+ }
675
+ }
676
+ inputEl.addEventListener("input", adjustHeight);
677
+ inputEl.style.overflowY = "hidden";
678
+ };
679
+
680
+ WidgetUIManager.prototype.bindMascotImageErrorHandlers = function() {
681
+ var self = this;
682
+ [this.bubbleMascot, this.headerMascot].forEach(function(mascotNode) {
683
+ if (!mascotNode) return;
684
+ mascotNode.addEventListener("error", function() {
685
+ var failedUrl = mascotNode.getAttribute("src") || "";
686
+ var errorMessage = failedUrl ? "chargement échoué pour " + failedUrl : "chargement échoué";
687
+ console.error("[OurluMairie] mascot image error:", errorMessage);
688
+ self.showMascotLoadError(errorMessage);
689
+ });
690
+ });
691
+ };
692
+
693
+ WidgetUIManager.prototype.showMascotLoadError = function(message) {
694
+ var resolvedMessage = String(message || "Mascotte indisponible.").trim() || "Mascotte indisponible.";
695
+ this.showError("Mascotte indisponible: " + resolvedMessage);
696
+ if (this.bubbleMascot) {
697
+ this.bubbleMascot.style.display = "none";
698
+ this.bubbleMascot.alt = resolvedMessage;
699
+ }
700
+ if (this.headerMascot) {
701
+ this.headerMascot.style.display = "none";
702
+ }
703
+ };
704
+
705
+ WidgetUIManager.prototype.bind = function(callbacks) {
706
+ this.bubble.addEventListener("click", callbacks.onToggle);
707
+ this.root.querySelector("#cm-close").addEventListener("click", callbacks.onToggle);
708
+ this.root.querySelector("#cm-form").addEventListener("submit", callbacks.onSend);
709
+ this.micButton.addEventListener("click", callbacks.onMicToggle);
710
+ document.addEventListener("keydown", callbacks.onEscape);
711
+ var disclaimerClose = this.root.querySelector("#cm-disclaimer-close");
712
+ var transparencyClose = this.root.querySelector("#cm-transparency-close");
713
+ var rootRef = this.root;
714
+ if (disclaimerClose) disclaimerClose.addEventListener("click", function() { var p = rootRef.querySelector("#cm-disclaimer"); if (p) p.style.display = "none"; });
715
+ if (transparencyClose) transparencyClose.addEventListener("click", function() { var p = rootRef.querySelector("#cm-transparency"); if (p) p.style.display = "none"; });
716
+ var maximizeBtn = this.root.querySelector("#cm-maximize");
717
+ var panelRef = this.panel;
718
+ if (maximizeBtn && panelRef) maximizeBtn.addEventListener("click", function() { panelRef.classList.toggle("maximized"); });
719
+ };
720
+
721
+ WidgetUIManager.prototype.setMascotSource = function(sourceUrl) {
722
+ var normalizedSourceUrl = String(sourceUrl || "").trim();
723
+ if (!normalizedSourceUrl) return;
724
+ if (this.bubbleMascot) {
725
+ this.bubbleMascot.src = normalizedSourceUrl;
726
+ this.bubbleMascot.style.display = "block";
727
+ }
728
+ if (this.headerMascot) {
729
+ this.headerMascot.src = normalizedSourceUrl;
730
+ this.headerMascot.style.display = "block";
731
+ }
732
+ };
733
+
734
+ WidgetUIManager.prototype.applyMascotTheme = function() {
735
+ var self = this;
736
+ ui.resolveMascotUrlWithTheme(this.config.mascotUrl, this.config).then(function(themedMascotUrl) {
737
+ self.setMascotSource(themedMascotUrl);
738
+ }).catch(function(error) {
739
+ var message = (error && error.message) ? error.message : "Mascotte indisponible.";
740
+ console.error("[OurluMairie]", message, error);
741
+ self.showMascotLoadError(message);
742
+ });
743
+ };
744
+
745
+ WidgetUIManager.prototype.applyThemeOverrides = function() {
746
+ if (!this.root || !this.themeOverrideStyle) return;
747
+ var isDarkTheme = String(this.config.effectiveThemeScheme || "light").toLowerCase() === "dark";
748
+ var primaryColor = ui.normalizeHexColor(this.config.primaryColor, "#0066ff");
749
+ var panelBackgroundBaseColor = isDarkTheme ? "#111827" : this.config.panelBackgroundColor;
750
+ var panelBackgroundAlpha = isDarkTheme ? "0.96" : this.config.panelBackgroundAlpha;
751
+ var panelBackground = ui.hexToRgba(panelBackgroundBaseColor, panelBackgroundAlpha);
752
+ var borderRadius = Number(this.config.panelBorderRadius);
753
+ if (!Number.isFinite(borderRadius)) borderRadius = 16;
754
+ borderRadius = Math.max(0, Math.min(32, Math.round(borderRadius)));
755
+ var borderRadiusPx = borderRadius + "px";
756
+ var gradient = "linear-gradient(135deg," + primaryColor + " 0%,#0047b3 100%)";
757
+ var darkThemeRules = isDarkTheme ? [
758
+ "#cm-panel{color:#f3f4f6 !important}",
759
+ "#cm-messages{background:rgba(2,6,23,0.28) !important}",
760
+ ".cm-msg.assistant{background:rgba(30,41,59,0.94) !important;color:#f8fafc !important}",
761
+ ".cm-msg.assistant code{background:#334155 !important;color:#f8fafc !important}",
762
+ "#cm-form{background:#0f172a !important;border-top:1px solid #1f2937 !important}",
763
+ "#cm-input{background:#111827 !important;color:#f8fafc !important;border-color:#334155 !important}",
764
+ "#cm-input::placeholder{color:#94a3b8 !important}",
765
+ "#cm-error{background:#7f1d1d !important;color:#fee2e2 !important}",
766
+ "#cm-resize-handle{color:#94a3b8 !important}"
767
+ ] : [
768
+ "#cm-messages{background:transparent !important}",
769
+ ".cm-msg.assistant{background:#f0f4f8 !important;color:#1a1a2e !important}",
770
+ "#cm-form{background:#ffffff !important;border-top:1px solid #eee !important}",
771
+ "#cm-input{background:#ffffff !important;color:#0f172a !important;border-color:#d1d5db !important}",
772
+ "#cm-error{background:#f8d7da !important;color:#58151c !important}"
773
+ ];
774
+ this.themeOverrideStyle.textContent = [
775
+ "#cm-bubble{background:" + gradient + " !important}",
776
+ "#cm-bubble:focus-visible{outline-color:" + primaryColor + " !important}",
777
+ "#cm-panel{background:" + panelBackground + " !important;border-radius:" + borderRadiusPx + " !important}",
778
+ "#cm-header{background:" + gradient + " !important;border-radius:" + borderRadiusPx + " " + borderRadiusPx + " 0 0 !important}",
779
+ "#cm-form{border-radius:0 0 " + borderRadiusPx + " " + borderRadiusPx + " !important}",
780
+ "#cm-send{background:" + primaryColor + " !important}",
781
+ ".cm-msg.user{background:" + primaryColor + " !important}",
782
+ "#cm-input:focus{border-color:" + primaryColor + " !important;box-shadow:0 0 0 3px rgba(0,102,255,.15) !important}"
783
+ ].concat(darkThemeRules).join("\n");
784
+ };
785
+
786
+ WidgetUIManager.prototype.updateTheme = function(updatedConfig) {
787
+ this.config = Object.assign({}, this.config, updatedConfig || {});
788
+ if (this.root) {
789
+ var titleNode = this.root.querySelector("#cm-title");
790
+ if (titleNode) {
791
+ titleNode.textContent = this.config.assistantName || "Assistant mairie";
792
+ }
793
+ if (this.panel) {
794
+ this.panel.setAttribute("aria-label", "Chat " + (this.config.assistantName || "Assistant mairie"));
795
+ }
796
+ var userMessages = this.root.querySelectorAll(".cm-msg.user");
797
+ var userBubbleColor = ui.normalizeHexColor(this.config.primaryColor, "#0066ff");
798
+ userMessages.forEach(function(node) {
799
+ node.style.background = userBubbleColor;
800
+ });
801
+ if (this.welcome && this.config.welcomeMessage) {
802
+ this.welcome.textContent = this.config.welcomeMessage;
803
+ }
804
+ var disclaimerSpan = this.root.querySelector("#cm-disclaimer > span");
805
+ if (disclaimerSpan && this.config.disclaimerText) {
806
+ disclaimerSpan.textContent = this.config.disclaimerText;
807
+ }
808
+ var headerMascot = this.root.querySelector("#cm-header-mascot");
809
+ if (headerMascot && this.config.mascotUrl) {
810
+ headerMascot.setAttribute("src", this.config.mascotUrl);
811
+ headerMascot.style.display = "";
812
+ }
813
+ }
814
+ this.applyThemeOverrides();
815
+ this.applyMascotTheme();
816
+ };
817
+
818
+ WidgetUIManager.prototype.setOpen = function(opened) {
819
+ this.panel.classList.toggle("open", opened);
820
+ this.bubble.style.display = opened ? "none" : "flex";
821
+ if (opened && !("ontouchstart" in window)) this.input.focus();
822
+ this.lockBodyScroll(opened);
823
+ };
824
+
825
+ WidgetUIManager.prototype.lockBodyScroll = function(lock) {
826
+ if (!this._mobileQuery || !this._mobileQuery.matches) return;
827
+ if (lock) {
828
+ this._savedBodyOverflow = document.body.style.overflow;
829
+ this._savedBodyPosition = document.body.style.position;
830
+ this._savedBodyTop = document.body.style.top;
831
+ this._savedBodyWidth = document.body.style.width;
832
+ this._scrollY = window.scrollY;
833
+ document.body.style.overflow = "hidden";
834
+ document.body.style.position = "fixed";
835
+ document.body.style.top = "-" + this._scrollY + "px";
836
+ document.body.style.width = "100%";
837
+ } else {
838
+ document.body.style.overflow = this._savedBodyOverflow || "";
839
+ document.body.style.position = this._savedBodyPosition || "";
840
+ document.body.style.top = this._savedBodyTop || "";
841
+ document.body.style.width = this._savedBodyWidth || "";
842
+ window.scrollTo(0, this._scrollY || 0);
843
+ }
844
+ };
845
+
846
+ WidgetUIManager.prototype.pullInput = function() {
847
+ var value = this.input.value.trim();
848
+ this.input.value = "";
849
+ this.input.dispatchEvent(new Event("input", { bubbles: true }));
850
+ return value;
851
+ };
852
+
853
+ WidgetUIManager.prototype.setInput = function(value) {
854
+ this.input.value = String(value || "");
855
+ this.input.dispatchEvent(new Event("input", { bubbles: true }));
856
+ };
857
+ WidgetUIManager.prototype.inputValue = function() { return this.input.value; };
858
+ WidgetUIManager.prototype.setInputPlaceholder = function(text) {
859
+ this.input.placeholder = text || "Posez votre question\u2026";
860
+ };
861
+ WidgetUIManager.prototype.showTyping = function(visible) { this.typing.classList.toggle("visible", Boolean(visible)); };
862
+
863
+ WidgetUIManager.prototype.setComposerDisabled = function(disabled) {
864
+ this.input.disabled = disabled;
865
+ this.sendButton.disabled = disabled;
866
+ this.micButton.disabled = disabled;
867
+ };
868
+
869
+ WidgetUIManager.prototype.showError = function(message) {
870
+ this.error.textContent = message;
871
+ this.error.classList.toggle("visible", Boolean(message));
872
+ this._retryHandler = null;
873
+ };
874
+
875
+ WidgetUIManager.prototype.showRetryableError = function(message, onRetry) {
876
+ this.error.innerHTML = "";
877
+ var textNode = document.createTextNode(message + " ");
878
+ this.error.appendChild(textNode);
879
+ var retryButton = document.createElement("button");
880
+ retryButton.textContent = "Réessayer";
881
+ retryButton.style.cssText = "background:none;border:1px solid #58151c;border-radius:6px;color:#58151c;cursor:pointer;padding:4px 12px;font-size:13px;margin-left:8px;";
882
+ var self = this;
883
+ retryButton.addEventListener("click", function() {
884
+ self.showError("");
885
+ if (typeof onRetry === "function") onRetry();
886
+ });
887
+ this.error.appendChild(retryButton);
888
+ this.error.classList.add("visible");
889
+ this._retryHandler = onRetry;
890
+ };
891
+
892
+ WidgetUIManager.prototype.setAssistantDraftText = function(node, text) {
893
+ if (!node) return;
894
+ node.textContent = String(text || "");
895
+ };
896
+
897
+ WidgetUIManager.prototype.setAssistantFinalText = function(node, text) {
898
+ if (!node) return;
899
+ node.innerHTML = ui.renderAssistantMarkdown(String(text || ""));
900
+ };
901
+
902
+ WidgetUIManager.prototype.addMessage = function(role, text) {
903
+ if (this.welcome) { this.welcome.remove(); this.welcome = null; }
904
+ var node = document.createElement("div");
905
+ node.className = "cm-msg " + role;
906
+ if (role === "assistant") {
907
+ this.setAssistantDraftText(node, text);
908
+ } else {
909
+ node.textContent = text;
910
+ }
911
+ this.messages.appendChild(node);
912
+ this.messages.scrollTop = this.messages.scrollHeight;
913
+ return node;
914
+ };
915
+
916
+ WidgetUIManager.prototype.startAssistantStream = function() {
917
+ this.streamingAssistantElement = this.addMessage("assistant", "");
918
+ this.streamingAssistantBuffer = "";
919
+ };
920
+
921
+ WidgetUIManager.prototype.appendAssistantToken = function(token) {
922
+ if (!this.streamingAssistantElement) this.startAssistantStream();
923
+ var resolvedToken = String(token || "");
924
+ if (!resolvedToken) return;
925
+ this.streamingAssistantBuffer += resolvedToken;
926
+ this.streamingAssistantElement.textContent += resolvedToken;
927
+ this.messages.scrollTop = this.messages.scrollHeight;
928
+ };
929
+
930
+ WidgetUIManager.prototype.finalizeAssistantMessage = function(content) {
931
+ if (!this.streamingAssistantElement) this.startAssistantStream();
932
+ var resolvedContent = content || this.streamingAssistantBuffer || this.streamingAssistantElement.textContent || "";
933
+ this.setAssistantFinalText(this.streamingAssistantElement, resolvedContent);
934
+ this.streamingAssistantElement = null;
935
+ this.streamingAssistantBuffer = "";
936
+ };
937
+
938
+ WidgetUIManager.prototype.setMicListening = function(isListening) {
939
+ this.micButton.classList.toggle("listening", isListening);
940
+ this.micButton.innerHTML = isListening
941
+ ? '<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><rect x="6" y="6" width="12" height="12" rx="2"/></svg>'
942
+ : '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/><line x1="12" y1="19" x2="12" y2="23"/><line x1="8" y1="23" x2="16" y2="23"/></svg>';
943
+ this.micButton.setAttribute("aria-label", isListening ? "Arr\u00eater la dict\u00e9e vocale" : "Dicter un message");
944
+ this.input.placeholder = isListening ? "Transcription en cours\u2026" : "Posez votre question\u2026";
945
+ };
946
+
947
+ WidgetUIManager.prototype.getHeader = function() {
948
+ return this.root ? this.root.querySelector("#cm-header") : null;
949
+ };
950
+
951
+ WidgetUIManager.prototype.getPanel = function() {
952
+ return this.panel;
953
+ };
954
+
955
+ WidgetUIManager.prototype.getRoot = function() {
956
+ return this.root;
957
+ };
958
+
959
+ WidgetUIManager.prototype.injectSignalementButton = function(messageNode, onOpen) {
960
+ if (!messageNode) return null;
961
+ var btn = document.createElement("button");
962
+ btn.className = "cm-sig-open-btn";
963
+ btn.type = "button";
964
+ btn.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 9v4"/><path d="M12 17h.01"/><path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/></svg>Mon signalement';
965
+ btn.addEventListener("click", function() { if (typeof onOpen === "function") onOpen(); });
966
+ messageNode.appendChild(btn);
967
+ return btn;
968
+ };
969
+
970
+ ui.WidgetUIManager = WidgetUIManager;
971
+ }
972
+
973
+ "use strict";
974
+ var runtime = window.__OurluWidgetRuntimeV1 || (window.__OurluWidgetRuntimeV1 = {});
975
+ var ui = {};
976
+
977
+ ui.CONTAINER_ID = "ourlu-widget-root";
978
+
979
+ installColorUtils(ui);
980
+ installMascotTheme(ui);
981
+ installMarkdownRender(ui);
982
+ installMediaUtils(ui);
983
+ installEventBus(ui);
984
+ installWidgetCssBuilder(ui);
985
+ installWidgetUIManager(ui);
986
+
987
+ runtime.constants = { CONTAINER_ID: ui.CONTAINER_ID };
988
+ runtime.EventBus = ui.EventBus;
989
+ runtime.WidgetUIManager = ui.WidgetUIManager;
990
+ runtime.utils = {
991
+ trimTrailingSlash: ui.trimTrailingSlash,
992
+ mergeTranscript: ui.mergeTranscript,
993
+ resolveRecorderMimeType: ui.resolveRecorderMimeType,
994
+ encodeArrayBufferToBase64: ui.encodeArrayBufferToBase64,
995
+ resolveAbsoluteMascotUrl: ui.resolveAbsoluteMascotUrl
996
+ };
997
+ })();