@123toto/ai-app-assistant-client 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +62 -0
- package/dist/angular.d.ts +185 -0
- package/dist/angular.d.ts.map +1 -0
- package/dist/angular.js +715 -0
- package/dist/angular.js.map +1 -0
- package/dist/capture.d.ts +24 -0
- package/dist/capture.d.ts.map +1 -0
- package/dist/capture.js +123 -0
- package/dist/capture.js.map +1 -0
- package/dist/client.d.ts +46 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +124 -0
- package/dist/client.js.map +1 -0
- package/dist/controller.d.ts +108 -0
- package/dist/controller.d.ts.map +1 -0
- package/dist/controller.js +341 -0
- package/dist/controller.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/picker.d.ts +14 -0
- package/dist/picker.d.ts.map +1 -0
- package/dist/picker.js +140 -0
- package/dist/picker.js.map +1 -0
- package/dist/settings-web-component.d.ts +57 -0
- package/dist/settings-web-component.d.ts.map +1 -0
- package/dist/settings-web-component.js +352 -0
- package/dist/settings-web-component.js.map +1 -0
- package/dist/settings.d.ts +56 -0
- package/dist/settings.d.ts.map +1 -0
- package/dist/settings.js +164 -0
- package/dist/settings.js.map +1 -0
- package/dist/web-component.d.ts +49 -0
- package/dist/web-component.d.ts.map +1 -0
- package/dist/web-component.js +305 -0
- package/dist/web-component.js.map +1 -0
- package/package.json +89 -0
package/dist/angular.js
ADDED
|
@@ -0,0 +1,715 @@
|
|
|
1
|
+
import { HttpClient, HttpErrorResponse, HttpEventType, HttpHeaders, HttpResponse } from "@angular/common/http";
|
|
2
|
+
import { ChangeDetectionStrategy, Component, ElementRef, InjectionToken, computed, effect, inject, makeEnvironmentProviders, signal, viewChild } from "@angular/core";
|
|
3
|
+
import { firstValueFrom, fromEvent, takeUntil } from "rxjs";
|
|
4
|
+
import { createAiDocsClient } from "./client.js";
|
|
5
|
+
import { AiDocsAssistantController, normalizeAiDocsError as normalizeError } from "./controller.js";
|
|
6
|
+
import { createAiDocsSettingsClient } from "./settings.js";
|
|
7
|
+
import { defineAiDocsSettingsElement } from "./settings-web-component.js";
|
|
8
|
+
import * as i0 from "@angular/core";
|
|
9
|
+
const AI_DOCS_CLIENT = new InjectionToken("AI_DOCS_CLIENT");
|
|
10
|
+
const AI_DOCS_CONFIG = new InjectionToken("AI_DOCS_CONFIG");
|
|
11
|
+
const AI_DOCS_SETTINGS_CLIENT = new InjectionToken("AI_DOCS_SETTINGS_CLIENT");
|
|
12
|
+
/** Registers the generic client while preserving Angular HTTP interceptors. */
|
|
13
|
+
export function provideAiDocs(config) {
|
|
14
|
+
return makeEnvironmentProviders([
|
|
15
|
+
{ provide: AI_DOCS_CONFIG, useValue: config },
|
|
16
|
+
{
|
|
17
|
+
provide: AI_DOCS_CLIENT,
|
|
18
|
+
useFactory: () => {
|
|
19
|
+
const http = inject(HttpClient);
|
|
20
|
+
return createAiDocsClient({
|
|
21
|
+
endpoint: config.endpoint,
|
|
22
|
+
...(config.streamEndpoint ? { streamEndpoint: config.streamEndpoint } : {}),
|
|
23
|
+
transport: async (request, options) => {
|
|
24
|
+
if (options.signal?.aborted)
|
|
25
|
+
throw abortReason(options.signal);
|
|
26
|
+
const response$ = http.post(options.endpoint, request, {
|
|
27
|
+
headers: toHttpHeaders(await config.headers?.())
|
|
28
|
+
});
|
|
29
|
+
return firstValueFrom(options.signal
|
|
30
|
+
? response$.pipe(takeUntil(fromEvent(options.signal, "abort")))
|
|
31
|
+
: response$);
|
|
32
|
+
},
|
|
33
|
+
streamTransport: createAngularStreamTransport(http, config)
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
provide: AI_DOCS_SETTINGS_CLIENT,
|
|
39
|
+
useFactory: () => {
|
|
40
|
+
if (!config.managedEndpoint)
|
|
41
|
+
return undefined;
|
|
42
|
+
return createAiDocsSettingsClient({
|
|
43
|
+
endpoint: config.managedEndpoint,
|
|
44
|
+
fetch: createAngularFetch(inject(HttpClient))
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
provide: AiDocsService,
|
|
50
|
+
useFactory: () => new AiDocsService(inject(AI_DOCS_CONFIG), inject(AI_DOCS_CLIENT), inject(AI_DOCS_SETTINGS_CLIENT))
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
provide: AiDocsSettingsService,
|
|
54
|
+
useFactory: () => new AiDocsSettingsService(inject(AI_DOCS_CONFIG), inject(AI_DOCS_SETTINGS_CLIENT), inject(AiDocsService))
|
|
55
|
+
}
|
|
56
|
+
]);
|
|
57
|
+
}
|
|
58
|
+
/** Angular state, conversation and cancellable DOM-selection facade. */
|
|
59
|
+
export class AiDocsService {
|
|
60
|
+
settingsClient;
|
|
61
|
+
#state = signal({ status: "idle" }, ...(ngDevMode ? [{ debugName: "#state" }] : []));
|
|
62
|
+
#messages = signal([], ...(ngDevMode ? [{ debugName: "#messages" }] : []));
|
|
63
|
+
#selecting = signal(false, ...(ngDevMode ? [{ debugName: "#selecting" }] : []));
|
|
64
|
+
#selectedElementLabel = signal(undefined, ...(ngDevMode ? [{ debugName: "#selectedElementLabel" }] : []));
|
|
65
|
+
#loading = signal(false, ...(ngDevMode ? [{ debugName: "#loading" }] : []));
|
|
66
|
+
#maxConversationTurns = signal(3, ...(ngDevMode ? [{ debugName: "#maxConversationTurns" }] : []));
|
|
67
|
+
#conversationTurns = signal(0, ...(ngDevMode ? [{ debugName: "#conversationTurns" }] : []));
|
|
68
|
+
#conversationLimitReached = signal(false, ...(ngDevMode ? [{ debugName: "#conversationLimitReached" }] : []));
|
|
69
|
+
#available = signal(false, ...(ngDevMode ? [{ debugName: "#available" }] : []));
|
|
70
|
+
state = this.#state.asReadonly();
|
|
71
|
+
messages = this.#messages.asReadonly();
|
|
72
|
+
selecting = this.#selecting.asReadonly();
|
|
73
|
+
loading = this.#loading.asReadonly();
|
|
74
|
+
selectedElementLabel = this.#selectedElementLabel.asReadonly();
|
|
75
|
+
maxConversationTurns = this.#maxConversationTurns.asReadonly();
|
|
76
|
+
conversationTurns = this.#conversationTurns.asReadonly();
|
|
77
|
+
conversationLimitReached = this.#conversationLimitReached.asReadonly();
|
|
78
|
+
available = this.#available.asReadonly();
|
|
79
|
+
config;
|
|
80
|
+
#controller;
|
|
81
|
+
constructor(config, client, settingsClient) {
|
|
82
|
+
this.settingsClient = settingsClient;
|
|
83
|
+
this.config = config;
|
|
84
|
+
this.#available.set(!config.managedEndpoint);
|
|
85
|
+
this.#controller = new AiDocsAssistantController(config, client);
|
|
86
|
+
this.#controller.subscribe((snapshot) => {
|
|
87
|
+
this.#state.set(snapshot.state);
|
|
88
|
+
this.#messages.set([...snapshot.messages]);
|
|
89
|
+
this.#selecting.set(snapshot.selecting);
|
|
90
|
+
this.#selectedElementLabel.set(snapshot.selectedElementLabel);
|
|
91
|
+
this.#loading.set(snapshot.loading);
|
|
92
|
+
this.#maxConversationTurns.set(snapshot.maxConversationTurns);
|
|
93
|
+
this.#conversationTurns.set(snapshot.conversationTurns);
|
|
94
|
+
this.#conversationLimitReached.set(snapshot.conversationLimitReached);
|
|
95
|
+
});
|
|
96
|
+
if (config.managedEndpoint)
|
|
97
|
+
void this.refreshAccess();
|
|
98
|
+
}
|
|
99
|
+
/** Refreshes launcher visibility and the server-defined conversation limit. */
|
|
100
|
+
async refreshAccess() {
|
|
101
|
+
if (!this.config.managedEndpoint) {
|
|
102
|
+
this.#available.set(true);
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
try {
|
|
106
|
+
if (!this.settingsClient)
|
|
107
|
+
throw new Error("Managed AI Docs client is unavailable");
|
|
108
|
+
const access = await this.settingsClient.getAccess();
|
|
109
|
+
this.#available.set(access.available);
|
|
110
|
+
this.setMaxConversationTurns(access.maxConversationTurns);
|
|
111
|
+
if (!access.available) {
|
|
112
|
+
this.#controller.stop();
|
|
113
|
+
this.#controller.cancelElementSelection();
|
|
114
|
+
}
|
|
115
|
+
return access;
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
this.#available.set(false);
|
|
119
|
+
return undefined;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/** Captures the page and asks without application-specific DOM annotations. */
|
|
123
|
+
async ask(options = {}) {
|
|
124
|
+
return this.#controller.ask(options);
|
|
125
|
+
}
|
|
126
|
+
/** Starts selection only. Asking remains a separate user action. */
|
|
127
|
+
async selectElement() {
|
|
128
|
+
return this.#controller.selectElement();
|
|
129
|
+
}
|
|
130
|
+
/** Backward-compatible shortcut; prefer selectElement followed by ask. */
|
|
131
|
+
async selectAndAsk(question, options) {
|
|
132
|
+
return this.#controller.selectAndAsk(question, options);
|
|
133
|
+
}
|
|
134
|
+
cancelElementSelection() { this.#controller.cancelElementSelection(); }
|
|
135
|
+
clearSelectedElement() { this.#controller.clearSelectedElement(); }
|
|
136
|
+
/** Aborts the active provider call without erasing the conversation. */
|
|
137
|
+
stop() { this.#controller.stop(); }
|
|
138
|
+
/** Replays the last prompt after automatic retries have been exhausted. */
|
|
139
|
+
retry() { return this.#controller.retry(); }
|
|
140
|
+
/** Explicitly clears conversation and selection; minimizing does not. */
|
|
141
|
+
newConversation() { this.#controller.newConversation(); }
|
|
142
|
+
/** Resets automatically when the application's page URL changes. */
|
|
143
|
+
syncPage(key) { return this.#controller.syncPage(key); }
|
|
144
|
+
reset() { this.#controller.reset(); }
|
|
145
|
+
/** Applies a host-provided runtime limit without recreating the assistant. */
|
|
146
|
+
setMaxConversationTurns(value) {
|
|
147
|
+
this.#controller.setMaxConversationTurns(value);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/** Opens the framework-neutral settings UI with Angular's authenticated HttpClient. */
|
|
151
|
+
export class AiDocsSettingsService {
|
|
152
|
+
config;
|
|
153
|
+
client;
|
|
154
|
+
assistant;
|
|
155
|
+
#element;
|
|
156
|
+
constructor(config, client, assistant) {
|
|
157
|
+
this.config = config;
|
|
158
|
+
this.client = client;
|
|
159
|
+
this.assistant = assistant;
|
|
160
|
+
}
|
|
161
|
+
/** Lazily creates and opens the generic settings element. */
|
|
162
|
+
open() {
|
|
163
|
+
if (!this.config.managedEndpoint || !this.client) {
|
|
164
|
+
throw new Error("Set managedEndpoint before opening AI Docs settings.");
|
|
165
|
+
}
|
|
166
|
+
const element = this.element();
|
|
167
|
+
element.show();
|
|
168
|
+
}
|
|
169
|
+
close() { this.#element?.close(); }
|
|
170
|
+
/** Creates one shared element and refreshes launcher access after changes. */
|
|
171
|
+
element() {
|
|
172
|
+
if (this.#element?.isConnected)
|
|
173
|
+
return this.#element;
|
|
174
|
+
defineAiDocsSettingsElement();
|
|
175
|
+
const element = document.createElement("ai-docs-settings");
|
|
176
|
+
const settingsTheme = compactTheme({
|
|
177
|
+
accent: this.config.theme?.accent,
|
|
178
|
+
surface: this.config.theme?.surface,
|
|
179
|
+
surfaceMuted: this.config.theme?.surfaceMuted,
|
|
180
|
+
text: this.config.theme?.text,
|
|
181
|
+
textMuted: this.config.theme?.textMuted,
|
|
182
|
+
border: this.config.theme?.border,
|
|
183
|
+
danger: this.config.theme?.danger,
|
|
184
|
+
...this.config.settings?.theme
|
|
185
|
+
});
|
|
186
|
+
element.configure({
|
|
187
|
+
endpoint: this.config.managedEndpoint,
|
|
188
|
+
client: this.client,
|
|
189
|
+
title: this.config.settings?.title ?? "AI assistant settings",
|
|
190
|
+
...(this.config.settings?.confirmRevoke
|
|
191
|
+
? { confirmRevoke: this.config.settings.confirmRevoke }
|
|
192
|
+
: {}),
|
|
193
|
+
theme: settingsTheme
|
|
194
|
+
});
|
|
195
|
+
const configurationChanged = () => {
|
|
196
|
+
element.close();
|
|
197
|
+
void this.assistant.refreshAccess();
|
|
198
|
+
};
|
|
199
|
+
element.addEventListener("ai-docs-settings-saved", configurationChanged);
|
|
200
|
+
element.addEventListener("ai-docs-key-revoked", configurationChanged);
|
|
201
|
+
(this.config.settings?.container?.() ?? document.body).append(element);
|
|
202
|
+
this.#element = element;
|
|
203
|
+
return element;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
/** Complete standalone UI. Add `<ai-docs-assistant />` near the app root. */
|
|
207
|
+
export class AiDocsAssistantComponent {
|
|
208
|
+
service = inject(AiDocsService);
|
|
209
|
+
open = signal(false, ...(ngDevMode ? [{ debugName: "open" }] : []));
|
|
210
|
+
docked = signal(true, ...(ngDevMode ? [{ debugName: "docked" }] : []));
|
|
211
|
+
left = signal(null, ...(ngDevMode ? [{ debugName: "left" }] : []));
|
|
212
|
+
top = signal(16, ...(ngDevMode ? [{ debugName: "top" }] : []));
|
|
213
|
+
question = signal("", ...(ngDevMode ? [{ debugName: "question" }] : []));
|
|
214
|
+
assistantName = computed(() => this.service.config.assistantName ?? "AI guide", ...(ngDevMode ? [{ debugName: "assistantName" }] : []));
|
|
215
|
+
launcherLabel = computed(() => this.service.config.launcherLabel ?? "Ask AI", ...(ngDevMode ? [{ debugName: "launcherLabel" }] : []));
|
|
216
|
+
subtitle = computed(() => this.service.config.subtitle ?? "Help about this page", ...(ngDevMode ? [{ debugName: "subtitle" }] : []));
|
|
217
|
+
mascot = computed(() => this.service.config.mascot ?? "✦", ...(ngDevMode ? [{ debugName: "mascot" }] : []));
|
|
218
|
+
theme = computed(() => ({
|
|
219
|
+
...this.service.config.theme,
|
|
220
|
+
...(this.service.config.theme?.accent || !this.service.config.accentColor
|
|
221
|
+
? {}
|
|
222
|
+
: { accent: this.service.config.accentColor })
|
|
223
|
+
}), ...(ngDevMode ? [{ debugName: "theme" }] : []));
|
|
224
|
+
labels = computed(() => ({
|
|
225
|
+
...(document.documentElement.lang.toLowerCase().startsWith("fr") ? FRENCH_LABELS : ENGLISH_LABELS),
|
|
226
|
+
...this.service.config.labels
|
|
227
|
+
}), ...(ngDevMode ? [{ debugName: "labels" }] : []));
|
|
228
|
+
conversationContent = viewChild("conversationContent", ...(ngDevMode ? [{ debugName: "conversationContent" }] : []));
|
|
229
|
+
routeTimer;
|
|
230
|
+
dragCleanup;
|
|
231
|
+
scrollFrame;
|
|
232
|
+
scrollKey = "";
|
|
233
|
+
/** Keeps the latest question and the start of its answer in view. */
|
|
234
|
+
keepLatestTurnVisible = effect(() => {
|
|
235
|
+
const open = this.open();
|
|
236
|
+
const messages = this.service.messages();
|
|
237
|
+
const status = this.service.state().status;
|
|
238
|
+
const content = this.conversationContent();
|
|
239
|
+
if (!open || !content) {
|
|
240
|
+
this.scrollKey = "";
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
const lastMessage = messages.at(-1);
|
|
244
|
+
const anchorMessage = lastMessage?.role === "selection"
|
|
245
|
+
? lastMessage
|
|
246
|
+
: [...messages].reverse().find((message) => message.role === "user");
|
|
247
|
+
if (!anchorMessage)
|
|
248
|
+
return;
|
|
249
|
+
const nextKey = `${anchorMessage.id}:${lastMessage?.id ?? ""}:${status}`;
|
|
250
|
+
// Partial streaming updates keep the same key: the viewport remains on the
|
|
251
|
+
// question and opening lines instead of chasing the end of a long answer.
|
|
252
|
+
if (nextKey === this.scrollKey)
|
|
253
|
+
return;
|
|
254
|
+
this.scrollKey = nextKey;
|
|
255
|
+
if (this.scrollFrame !== undefined)
|
|
256
|
+
cancelAnimationFrame(this.scrollFrame);
|
|
257
|
+
this.scrollFrame = requestAnimationFrame(() => {
|
|
258
|
+
const element = content.nativeElement;
|
|
259
|
+
const anchor = element.querySelector(`[data-ai-message-id="${anchorMessage.id}"]`);
|
|
260
|
+
this.scrollFrame = undefined;
|
|
261
|
+
if (!anchor)
|
|
262
|
+
return;
|
|
263
|
+
const top = element.scrollTop
|
|
264
|
+
+ anchor.getBoundingClientRect().top
|
|
265
|
+
- element.getBoundingClientRect().top
|
|
266
|
+
- 6;
|
|
267
|
+
element.scrollTo({
|
|
268
|
+
top: Math.max(0, top),
|
|
269
|
+
behavior: "smooth"
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
}, ...(ngDevMode ? [{ debugName: "keepLatestTurnVisible" }] : []));
|
|
273
|
+
ngOnInit() {
|
|
274
|
+
this.routeTimer = setInterval(() => {
|
|
275
|
+
if (this.service.syncPage())
|
|
276
|
+
this.question.set("");
|
|
277
|
+
}, 500);
|
|
278
|
+
}
|
|
279
|
+
ngOnDestroy() {
|
|
280
|
+
if (this.routeTimer)
|
|
281
|
+
clearInterval(this.routeTimer);
|
|
282
|
+
if (this.scrollFrame !== undefined)
|
|
283
|
+
cancelAnimationFrame(this.scrollFrame);
|
|
284
|
+
this.dragCleanup?.();
|
|
285
|
+
this.service.stop();
|
|
286
|
+
this.service.cancelElementSelection();
|
|
287
|
+
}
|
|
288
|
+
show() {
|
|
289
|
+
if (this.service.syncPage())
|
|
290
|
+
this.question.set("");
|
|
291
|
+
this.open.set(true);
|
|
292
|
+
}
|
|
293
|
+
newConversation() { this.service.newConversation(); this.question.set(""); }
|
|
294
|
+
toggleDock() {
|
|
295
|
+
if (this.docked()) {
|
|
296
|
+
this.left.set(Math.max(12, window.innerWidth - Math.min(390, window.innerWidth - 24) - 16));
|
|
297
|
+
this.top.set(16);
|
|
298
|
+
this.docked.set(false);
|
|
299
|
+
}
|
|
300
|
+
else {
|
|
301
|
+
this.left.set(null);
|
|
302
|
+
this.top.set(16);
|
|
303
|
+
this.docked.set(true);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
confidencePercent(response) { return Math.round(response.confidence.score * 100); }
|
|
307
|
+
confidenceClass(response) {
|
|
308
|
+
return `ai-confidence-${response.confidence.level}`;
|
|
309
|
+
}
|
|
310
|
+
confidenceNotice(response) {
|
|
311
|
+
if (response.confidence.level === "medium")
|
|
312
|
+
return this.labels().mediumConfidence;
|
|
313
|
+
if (response.confidence.level === "low")
|
|
314
|
+
return this.labels().lowConfidence;
|
|
315
|
+
if (response.confidence.level === "insufficient")
|
|
316
|
+
return this.labels().insufficientConfidence;
|
|
317
|
+
return undefined;
|
|
318
|
+
}
|
|
319
|
+
progressLabel(phase) {
|
|
320
|
+
return phase === "preparing" ? this.labels().reading
|
|
321
|
+
: phase === "thinking" ? this.labels().thinking
|
|
322
|
+
: phase === "writing" ? this.labels().writing
|
|
323
|
+
: this.labels().finalizing;
|
|
324
|
+
}
|
|
325
|
+
visibleLimitations(response) {
|
|
326
|
+
return response.limitations.slice(0, 2);
|
|
327
|
+
}
|
|
328
|
+
updateQuestion(event) { this.question.set(event.target.value); }
|
|
329
|
+
composerKeydown(event) { if (event.key === "Enter" && !event.shiftKey) {
|
|
330
|
+
event.preventDefault();
|
|
331
|
+
void this.submit();
|
|
332
|
+
} }
|
|
333
|
+
async submit(event) {
|
|
334
|
+
event?.preventDefault();
|
|
335
|
+
if (this.service.conversationLimitReached())
|
|
336
|
+
return;
|
|
337
|
+
const question = this.question().trim();
|
|
338
|
+
this.question.set("");
|
|
339
|
+
try {
|
|
340
|
+
await this.service.ask(question ? { question } : {});
|
|
341
|
+
}
|
|
342
|
+
catch { /* visible state */ }
|
|
343
|
+
}
|
|
344
|
+
async beginSelection() {
|
|
345
|
+
this.open.set(false);
|
|
346
|
+
try {
|
|
347
|
+
await this.service.selectElement();
|
|
348
|
+
this.open.set(true);
|
|
349
|
+
}
|
|
350
|
+
catch (error) {
|
|
351
|
+
if (normalizeError(error).name !== "AbortError")
|
|
352
|
+
this.open.set(true);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
cancelSelection() { this.service.cancelElementSelection(); this.open.set(true); }
|
|
356
|
+
clearSelection() { this.service.clearSelectedElement(); }
|
|
357
|
+
async retry() { try {
|
|
358
|
+
await this.service.retry();
|
|
359
|
+
}
|
|
360
|
+
catch { /* visible state */ } }
|
|
361
|
+
/** Relocates each sparkle between cycles; movement is hidden while transparent. */
|
|
362
|
+
randomizeStar(event) {
|
|
363
|
+
const star = event.currentTarget;
|
|
364
|
+
const between = (minimum, maximum) => Math.round(minimum + Math.random() * (maximum - minimum));
|
|
365
|
+
for (let position = 0; position < 3; position += 1) {
|
|
366
|
+
star.style.setProperty(`--ai-x${position}`, `${between(-16, 16)}px`);
|
|
367
|
+
star.style.setProperty(`--ai-y${position}`, `${between(-15, 15)}px`);
|
|
368
|
+
let deltaX = between(-3, 3);
|
|
369
|
+
const deltaY = between(-3, 3);
|
|
370
|
+
if (deltaX === 0 && deltaY === 0)
|
|
371
|
+
deltaX = 2;
|
|
372
|
+
star.style.setProperty(`--ai-dx${position}`, `${deltaX}px`);
|
|
373
|
+
star.style.setProperty(`--ai-dy${position}`, `${deltaY}px`);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
startDrag(event) {
|
|
377
|
+
if (event.button !== 0 || window.innerWidth < 576)
|
|
378
|
+
return;
|
|
379
|
+
const panel = event.currentTarget.closest(".ai-panel");
|
|
380
|
+
if (!panel)
|
|
381
|
+
return;
|
|
382
|
+
event.preventDefault();
|
|
383
|
+
const rect = panel.getBoundingClientRect();
|
|
384
|
+
this.docked.set(false);
|
|
385
|
+
this.left.set(rect.left);
|
|
386
|
+
this.top.set(rect.top);
|
|
387
|
+
const origin = { x: event.clientX, y: event.clientY, left: rect.left, top: rect.top };
|
|
388
|
+
const move = (next) => {
|
|
389
|
+
this.left.set(Math.max(0, Math.min(window.innerWidth - panel.offsetWidth, origin.left + next.clientX - origin.x)));
|
|
390
|
+
this.top.set(Math.max(0, Math.min(window.innerHeight - 56, origin.top + next.clientY - origin.y)));
|
|
391
|
+
};
|
|
392
|
+
const stop = () => this.dragCleanup?.();
|
|
393
|
+
document.addEventListener("pointermove", move);
|
|
394
|
+
document.addEventListener("pointerup", stop, { once: true });
|
|
395
|
+
this.dragCleanup = () => {
|
|
396
|
+
document.removeEventListener("pointermove", move);
|
|
397
|
+
document.removeEventListener("pointerup", stop);
|
|
398
|
+
this.dragCleanup = undefined;
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.29", ngImport: i0, type: AiDocsAssistantComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
402
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.29", type: AiDocsAssistantComponent, isStandalone: true, selector: "ai-docs-assistant", host: { attributes: { "data-ai-docs-ui": "" }, properties: { "style.display": "service.available() ? null : 'none'", "style.--ai-accent": "theme().accent", "style.--ai-accent-contrast": "theme().accentContrast", "style.--ai-header": "theme().header", "style.--ai-header-text": "theme().headerText", "style.--ai-launcher": "theme().launcher", "style.--ai-launcher-text": "theme().launcherText", "style.--ai-surface": "theme().surface", "style.--ai-surface-muted": "theme().surfaceMuted", "style.--ai-text": "theme().text", "style.--ai-text-muted": "theme().textMuted", "style.--ai-border": "theme().border", "style.--ai-selection": "theme().selection", "style.--ai-selection-text": "theme().selectionText", "style.--ai-danger": "theme().danger" } }, viewQueries: [{ propertyName: "conversationContent", first: true, predicate: ["conversationContent"], descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
403
|
+
@if (!open()) {
|
|
404
|
+
<button class="ai-launcher" type="button" (click)="show()" [attr.aria-label]="launcherLabel()">
|
|
405
|
+
<span class="ai-launcher-orbit" aria-hidden="true"><span class="ai-avatar">{{ mascot() }}</span><i class="ai-star ai-star-one" (animationiteration)="randomizeStar($event)">✦</i><i class="ai-star ai-star-two" (animationiteration)="randomizeStar($event)">✦</i><i class="ai-star ai-star-three" (animationiteration)="randomizeStar($event)">✦</i></span><span class="ai-launcher-label">{{ launcherLabel() }}</span>
|
|
406
|
+
</button>
|
|
407
|
+
}
|
|
408
|
+
@if (service.selecting()) {
|
|
409
|
+
<div class="ai-selection-hud" role="status"><span>{{ labels().selectionInstruction }}</span><kbd>Esc</kbd><button type="button" (click)="cancelSelection()">{{ labels().cancel }}</button></div>
|
|
410
|
+
}
|
|
411
|
+
@if (open()) {
|
|
412
|
+
<aside class="ai-panel" [class.ai-docked]="docked()" [style.left.px]="left()" [style.top.px]="top()" aria-label="Assistant de documentation" aria-live="polite">
|
|
413
|
+
<header class="ai-header" (pointerdown)="startDrag($event)">
|
|
414
|
+
<div class="ai-identity"><span class="ai-avatar" aria-hidden="true">{{ mascot() }}</span><div><strong>{{ assistantName() }}</strong><small>{{ subtitle() }}</small></div></div>
|
|
415
|
+
<div class="ai-window-actions">
|
|
416
|
+
<button type="button" [attr.aria-label]="labels().newConversation" [title]="labels().newConversation" (pointerdown)="$event.stopPropagation()" (click)="newConversation()">↻</button>
|
|
417
|
+
<button type="button" [title]="docked() ? labels().detach : labels().dock" (pointerdown)="$event.stopPropagation()" (click)="toggleDock()">{{ docked() ? '↗' : '↘' }}</button>
|
|
418
|
+
<button type="button" [title]="labels().minimize" (pointerdown)="$event.stopPropagation()" (click)="open.set(false)">—</button>
|
|
419
|
+
</div>
|
|
420
|
+
</header>
|
|
421
|
+
<main #conversationContent class="ai-content">
|
|
422
|
+
@if (service.messages().length === 0 && service.state().status === 'idle') {
|
|
423
|
+
<div class="ai-welcome"><span aria-hidden="true">{{ mascot() }}</span><div><strong>{{ labels().welcomeTitle }}</strong><p>{{ labels().welcomeBody }}</p></div></div>
|
|
424
|
+
}
|
|
425
|
+
@for (message of service.messages(); track message.id) {
|
|
426
|
+
@if (message.role === 'selection') {
|
|
427
|
+
<article class="ai-selection-event" [attr.data-ai-message-id]="message.id"><span class="ai-selection-icon">✦</span><div><small>{{ labels().elementSelected }}</small><strong>{{ message.label }}</strong></div><i>✦</i><i>✦</i></article>
|
|
428
|
+
} @else if (message.role === 'user') {
|
|
429
|
+
<article class="ai-message ai-user" [attr.data-ai-message-id]="message.id"><p>{{ message.text }}</p></article>
|
|
430
|
+
} @else {
|
|
431
|
+
<article class="ai-message ai-assistant" [attr.data-ai-message-id]="message.id">
|
|
432
|
+
@if (message.response.answer.title) { <h3>{{ message.response.answer.title }}</h3> }
|
|
433
|
+
<p>{{ message.response.answer.summary }}</p>
|
|
434
|
+
@for (section of message.response.answer.sections; track section.heading) { <section><h4>{{ section.heading }}</h4><p>{{ section.content }}</p></section> }
|
|
435
|
+
@if (message.response.answer.steps?.length) { <ol>@for (step of message.response.answer.steps; track step.label) { <li><strong>{{ step.label }}</strong> {{ step.description }}</li> }</ol> }
|
|
436
|
+
@for (warning of message.response.answer.warnings ?? []; track warning) { <p class="ai-warning">⚠ {{ warning }}</p> }
|
|
437
|
+
<footer><span class="ai-confidence"><i class="ai-confidence-dot" [class]="confidenceClass(message.response)" aria-hidden="true"></i>{{ labels().reliability }}: {{ confidencePercent(message.response) }} %</span>@if (confidenceNotice(message.response); as notice) { <small class="ai-confidence-note">{{ notice }}</small> }@for (limitation of visibleLimitations(message.response); track limitation) { <small>{{ limitation }}</small> }</footer>
|
|
438
|
+
</article>
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
@if (service.state(); as state) {
|
|
442
|
+
@if (state.status === 'loading') {
|
|
443
|
+
<article class="ai-message ai-assistant ai-progress-message">
|
|
444
|
+
@if (state.partialText) { <p class="ai-stream-text">{{ state.partialText }}</p> }
|
|
445
|
+
<div class="ai-progress" role="status"><span class="ai-dots" aria-hidden="true"><i></i><i></i><i></i></span><span>{{ progressLabel(state.phase) }}</span></div>
|
|
446
|
+
@if (state.retry) { <small>{{ labels().retrying }} {{ state.retry.attempt }}/{{ state.retry.maxRetries }}…</small> }
|
|
447
|
+
</article>
|
|
448
|
+
} @else if (state.status === 'error') {
|
|
449
|
+
<div class="ai-error"><strong>{{ labels().errorTitle }}</strong><span>{{ state.error.message }}</span><button type="button" (click)="retry()">{{ labels().retry }}</button></div>
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
</main>
|
|
453
|
+
<form class="ai-composer" (submit)="submit($event)">
|
|
454
|
+
@if (service.conversationLimitReached()) { <div class="ai-conversation-limit"><span>{{ labels().conversationLimitReached }}</span><button type="button" (click)="newConversation()">{{ labels().newConversation }}</button></div> }
|
|
455
|
+
@if (service.selectedElementLabel(); as label) { <div class="ai-selection-chip"><span>◎ {{ label }}</span><button type="button" aria-label="Retirer la sélection" (click)="clearSelection()">×</button></div> }
|
|
456
|
+
<textarea rows="2" maxlength="4000" [value]="question()" [disabled]="service.loading() || service.conversationLimitReached()" [placeholder]="labels().placeholder" (input)="updateQuestion($event)" (keydown)="composerKeydown($event)"></textarea>
|
|
457
|
+
<div class="ai-composer-actions"><button class="ai-select" type="button" [disabled]="service.loading() || service.conversationLimitReached()" (click)="beginSelection()">◎ {{ labels().select }}</button>@if (service.loading()) { <button class="ai-send ai-stop-control" type="button" [attr.aria-label]="labels().stop" [title]="labels().stop" (click)="service.stop()">■</button> } @else { <button class="ai-send" type="submit" [disabled]="service.conversationLimitReached()" [attr.aria-label]="labels().send">➤</button> }</div>
|
|
458
|
+
</form>
|
|
459
|
+
</aside>
|
|
460
|
+
}
|
|
461
|
+
`, isInline: true, styles: ["\n :host{font:14px/1.45 system-ui,sans-serif;color:#202624}button,textarea{font:inherit}.ai-launcher{position:fixed;right:20px;bottom:20px;z-index:9000;display:flex;align-items:center;width:48px;height:48px;padding:0;color:#fff;font-weight:650;background:linear-gradient(135deg,var(--ai-accent),#0f5947);border:0;border-radius:50%;box-shadow:0 8px 24px #0003;cursor:pointer;transition:width .22s ease,border-radius .22s ease,box-shadow .22s ease;animation:ai-beacon 9s ease-in-out infinite}.ai-launcher:hover,.ai-launcher:focus-visible{width:142px;border-radius:999px;box-shadow:0 10px 30px #0004,0 0 0 4px color-mix(in srgb,var(--ai-accent) 16%,transparent);animation:none}.ai-launcher-orbit{position:relative;display:grid;flex:0 0 48px;height:48px;overflow:hidden;place-items:center}.ai-launcher-orbit:before,.ai-launcher-orbit:after{position:absolute;z-index:2;content:'\u2726';color:#eafff9;line-height:1;text-shadow:0 0 7px #fff;opacity:0;pointer-events:none;animation:ai-sparkle 3.8s ease-in-out infinite}.ai-launcher-orbit:before{font-size:12px}.ai-launcher-orbit:after{font-size:7px;animation-delay:1.15s}.ai-launcher .ai-avatar{background:transparent}.ai-launcher-label{max-width:0;overflow:hidden;opacity:0;white-space:nowrap;transform:translateX(-5px);transition:max-width .22s ease,opacity .15s ease,transform .22s ease}.ai-launcher:hover .ai-launcher-label,.ai-launcher:focus-visible .ai-launcher-label{max-width:86px;opacity:1;transform:none}.ai-avatar{display:grid;width:32px;height:32px;place-items:center;background:#ffffff26;border-radius:50%}.ai-panel{position:fixed;z-index:9100;display:flex;flex-direction:column;width:min(390px,calc(100vw - 24px));height:min(600px,calc(100vh - 32px));min-width:310px;min-height:380px;overflow:hidden;background:#fff;border:1px solid #dce3e0;border-radius:16px;box-shadow:0 18px 55px #1c332b38;resize:both}.ai-panel.ai-docked{top:auto!important;right:16px;bottom:16px;left:auto!important;height:min(600px,calc(100vh - 32px));resize:both}.ai-header{display:flex;flex:0 0 auto;align-items:center;justify-content:space-between;gap:10px;padding:10px 12px;color:#fff;background:linear-gradient(135deg,var(--ai-accent),#0f5947);cursor:move;touch-action:none}.ai-identity{display:flex;align-items:center;gap:9px;min-width:0}.ai-identity div{display:flex;flex-direction:column;min-width:0}.ai-identity small{overflow:hidden;opacity:.78;text-overflow:ellipsis;white-space:nowrap}.ai-window-actions{display:flex;gap:1px}.ai-window-actions button{width:28px;height:28px;color:#fff;background:transparent;border:0;border-radius:8px;cursor:pointer}.ai-window-actions button:hover{background:#ffffff26}.ai-content{flex:1 1 auto;min-height:0;padding:14px;overflow:auto;background:#f6f8f7}.ai-welcome{display:flex;gap:10px;padding:13px;background:#e9f4ef;border:1px solid #d6e9e0;border-radius:12px}.ai-welcome p{margin:4px 0 0;color:#57635e}.ai-message{width:fit-content;max-width:92%;margin:0 0 11px;padding:10px 12px;border-radius:13px;box-shadow:0 2px 9px #20382f12}.ai-message p{margin:0 0 8px;white-space:pre-wrap}.ai-message p:last-child{margin-bottom:0}.ai-user{margin-left:auto;color:#fff;background:var(--ai-accent);border-bottom-right-radius:4px}.ai-user small{display:block;margin-bottom:5px;opacity:.78}.ai-assistant{background:#fff;border:1px solid #e0e6e3;border-bottom-left-radius:4px}.ai-assistant h3,.ai-assistant h4{margin:0 0 6px;font-size:14px}.ai-assistant section{margin-top:10px}.ai-assistant footer{display:flex;flex-direction:column;gap:3px;margin:10px -12px -10px;padding:8px 12px;color:#69736f;font-size:11px;font-style:italic;border-top:1px solid #edf0ef}.ai-warning{color:#8b5c00}.ai-stream-text{white-space:pre-wrap}.ai-streaming:after{content:'\u258B';color:var(--ai-accent);animation:blink 1s steps(1) infinite}.ai-dots{display:inline-flex;gap:3px}.ai-dots i{width:5px;height:5px;background:var(--ai-accent);border-radius:50%;animation:pulse 1s infinite alternate}.ai-dots i:nth-child(2){animation-delay:.2s}.ai-dots i:nth-child(3){animation-delay:.4s}.ai-link{display:block;padding:6px 0 0;color:var(--ai-accent);background:none;border:0;cursor:pointer}.ai-error{display:flex;flex-direction:column;gap:7px;padding:12px;color:#7e2525;background:#fff0f0;border:1px solid #f2cece;border-radius:12px}.ai-error button{align-self:flex-start;padding:6px 10px;color:#fff;background:#a43737;border:0;border-radius:8px;cursor:pointer}.ai-composer{flex:0 0 auto;padding:9px;background:#fff;border-top:1px solid #e0e6e3}.ai-composer textarea{box-sizing:border-box;width:100%;min-height:48px;padding:9px 10px;resize:none;color:inherit;background:#f8faf9;border:1px solid #d7dfdc;border-radius:10px;outline:0}.ai-composer textarea:focus{border-color:var(--ai-accent);box-shadow:0 0 0 3px color-mix(in srgb,var(--ai-accent) 15%,transparent)}.ai-composer-actions{display:flex;justify-content:space-between;margin-top:7px}.ai-composer-actions button{border-radius:9px;cursor:pointer}.ai-select{padding:6px 9px;color:var(--ai-accent);background:#fff;border:1px solid #ccd8d3}.ai-send{width:34px;height:32px;color:#fff;background:var(--ai-accent);border:0}button:disabled,textarea:disabled{opacity:.55;cursor:default}.ai-selection-chip{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:7px;padding:5px 8px;color:#175844;background:#e7f4ee;border-radius:9px}.ai-selection-chip span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ai-selection-chip button{color:inherit;background:none;border:0;cursor:pointer}.ai-selection-hud{position:fixed;top:16px;left:50%;z-index:2147483646;display:flex;align-items:center;gap:10px;padding:9px 12px;color:#fff;background:#18231f;border-radius:12px;box-shadow:0 10px 35px #0005;transform:translateX(-50%)}.ai-selection-hud kbd{padding:2px 5px;background:#ffffff20;border-radius:4px}.ai-selection-hud button{color:#fff;background:transparent;border:1px solid #ffffff55;border-radius:7px;cursor:pointer}@media(max-width:575px){.ai-panel,.ai-panel.ai-docked{inset:auto 8px 8px!important;width:calc(100vw - 16px);height:min(600px,calc(100vh - 16px));min-width:0;min-height:360px;border-radius:14px;resize:none}.ai-selection-hud{width:calc(100vw - 24px);justify-content:center}}@media(prefers-reduced-motion:reduce){.ai-launcher,.ai-launcher-orbit:before,.ai-launcher-orbit:after{animation:none}}@keyframes ai-beacon{0%,91%,100%{transform:translateY(0)}94%{transform:translateY(-4px)}97%{transform:translateY(0)}98.5%{transform:translateY(-2px)}}@keyframes ai-sparkle{0%,58%,100%{opacity:0;transform:translate(-15px,14px) scale(.45)}68%{opacity:1}82%{opacity:.85;transform:translate(14px,-13px) scale(1.05)}88%{opacity:0;transform:translate(18px,-17px) scale(.5)}}@keyframes blink{50%{opacity:0}}@keyframes pulse{to{opacity:.25;transform:translateY(-2px)}}\n ", "\n .ai-launcher{animation-duration:14s}.ai-launcher-orbit:before,.ai-launcher-orbit:after{display:none}.ai-star{position:absolute;z-index:2;color:#effffb;font-style:normal;line-height:1;text-shadow:0 0 7px #fff;opacity:0;pointer-events:none}.ai-star-one{font-size:11px;animation:ai-snow-one 6.8s ease-in-out infinite}.ai-star-two{font-size:7px;animation:ai-snow-two 8.1s ease-in-out 1.7s infinite}.ai-star-three{font-size:9px;animation:ai-snow-three 7.4s ease-in-out 3.4s infinite}.ai-identity small{max-width:220px;overflow:visible;line-height:1.18;text-overflow:clip;white-space:normal}.ai-selection-event{position:relative;display:flex;align-items:center;gap:10px;max-width:92%;margin:0 0 11px;padding:10px 12px;overflow:hidden;color:#155a45;background:linear-gradient(135deg,#effbf6,#dff5eb);border:1px solid #9dd8c2;border-radius:13px;box-shadow:0 7px 22px #14745b25;animation:ai-selection-arrive .55s cubic-bezier(.2,1.35,.4,1)}.ai-selection-event div{display:flex;flex-direction:column;min-width:0}.ai-selection-event small{color:#527268}.ai-selection-event strong{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ai-selection-event>i{position:absolute;color:#fff;font-style:normal;text-shadow:0 0 7px var(--ai-accent);animation:ai-selection-glint 1.35s ease-out both}.ai-selection-event>i:nth-last-child(2){right:28px;bottom:4px}.ai-selection-event>i:last-child{top:4px;right:8px;font-size:8px;animation-delay:.18s}.ai-selection-icon{display:grid;flex:0 0 28px;height:28px;place-items:center;color:#fff;background:var(--ai-accent);border-radius:50%;box-shadow:0 0 0 5px #14745b16}.ai-progress-message{min-width:190px}.ai-progress{display:flex;align-items:center;gap:8px;color:#50625b}.ai-progress-star{display:grid;width:24px;height:24px;place-items:center;color:#fff;background:var(--ai-accent);border-radius:50%;animation:ai-thinking 1.8s ease-in-out infinite}.ai-stop-control{background:#34443e}.ai-confidence-note{padding:8px 9px;color:#725400;background:#fff8dc;border-left:3px solid #d4a800;border-radius:7px}.ai-streaming:after{content:none}@media(prefers-reduced-motion:reduce){.ai-star,.ai-selection-event,.ai-selection-event>i,.ai-progress-star{animation:none}}@keyframes ai-snow-one{0%,56%,100%{opacity:0;transform:translate(-17px,18px) rotate(-12deg) scale(.55)}65%{opacity:1}84%{opacity:.9;transform:translate(13px,-14px) rotate(14deg) scale(1.05)}91%{opacity:0;transform:translate(18px,-20px) rotate(23deg) scale(.55)}}@keyframes ai-snow-two{0%,48%,100%{opacity:0;transform:translate(-8px,20px) rotate(8deg) scale(.4)}59%{opacity:.9}78%{opacity:.75;transform:translate(17px,-9px) rotate(-18deg) scale(1)}86%{opacity:0;transform:translate(21px,-15px) scale(.5)}}@keyframes ai-snow-three{0%,63%,100%{opacity:0;transform:translate(-20px,9px) rotate(-20deg) scale(.45)}72%{opacity:1}87%{opacity:.8;transform:translate(9px,-18px) rotate(20deg) scale(1.08)}94%{opacity:0;transform:translate(15px,-22px) scale(.5)}}@keyframes ai-selection-arrive{from{opacity:0;transform:translateY(22px) scale(.78);filter:blur(4px)}to{opacity:1;transform:none;filter:none}}@keyframes ai-selection-glint{from{opacity:0;transform:translate(-22px,14px) scale(.4)}45%{opacity:1}to{opacity:0;transform:translate(8px,-10px) scale(1.3)}}@keyframes ai-thinking{0%,100%{box-shadow:0 0 0 0 #14745b45;transform:rotate(0)}50%{box-shadow:0 0 0 6px #14745b0f;transform:rotate(22deg)}}\n ", "\n :host{--ai-accent:#14745b;--ai-accent-contrast:#fff;--ai-header:linear-gradient(135deg,var(--ai-accent),color-mix(in srgb,var(--ai-accent) 70%,#000));--ai-header-text:var(--ai-accent-contrast);--ai-launcher:var(--ai-header);--ai-launcher-text:var(--ai-header-text);--ai-surface:#fff;--ai-surface-muted:#f6f8f7;--ai-text:#202624;--ai-text-muted:#69736f;--ai-border:#dce3e0;--ai-selection:#eef0ff;--ai-selection-text:#303b82;--ai-danger:#a43737;color:var(--ai-text)}\n .ai-launcher{color:var(--ai-launcher-text);background:var(--ai-launcher);box-shadow:0 8px 24px #0003,0 0 0 1px color-mix(in srgb,var(--ai-launcher) 55%,transparent),0 0 18px color-mix(in srgb,var(--ai-launcher) 48%,transparent)}\n .ai-launcher:hover,.ai-launcher:focus-visible{box-shadow:0 10px 30px #0004,0 0 0 3px color-mix(in srgb,var(--ai-launcher) 24%,transparent),0 0 24px color-mix(in srgb,var(--ai-launcher) 58%,transparent)}\n .ai-panel{color:var(--ai-text);background:var(--ai-surface);border-color:var(--ai-border)}\n .ai-header{color:var(--ai-header-text);background:var(--ai-header)}\n .ai-window-actions button{color:var(--ai-header-text)}\n .ai-content{background:var(--ai-surface-muted)}\n .ai-welcome{background:color-mix(in srgb,var(--ai-accent) 9%,var(--ai-surface));border-color:color-mix(in srgb,var(--ai-accent) 20%,var(--ai-border))}\n .ai-welcome p,.ai-progress{color:var(--ai-text-muted)}\n .ai-progress{gap:9px}.ai-progress .ai-dots{align-items:center;height:16px;gap:4px}.ai-progress .ai-dots i{width:5px;height:5px;background:var(--ai-accent);animation:ai-wave 1.05s ease-in-out infinite}.ai-progress .ai-dots i:nth-child(2){animation-delay:.14s}.ai-progress .ai-dots i:nth-child(3){animation-delay:.28s}\n .ai-user{color:var(--ai-accent-contrast)}\n .ai-assistant{color:var(--ai-text);background:var(--ai-surface);border-color:var(--ai-border)}\n .ai-assistant>h3{margin:0 0 10px;padding:0 0 8px;font-size:15px;font-weight:650;line-height:1.3;border-bottom:1px solid var(--ai-border)}\n .ai-assistant section{margin-top:14px;padding-top:11px;border-top:1px solid color-mix(in srgb,var(--ai-border) 72%,transparent)}\n .ai-assistant section h4{margin:0 0 6px;font-size:13px;font-weight:650;line-height:1.3}\n .ai-assistant p,.ai-assistant li{font-weight:350;line-height:1.48}\n .ai-assistant ol{margin:12px 0 0;padding-left:20px}.ai-assistant li+li{margin-top:7px}\n .ai-assistant footer{color:var(--ai-text-muted);border-color:var(--ai-border)}\n .ai-composer{color:var(--ai-text);background:var(--ai-surface);border-color:var(--ai-border)}\n .ai-composer textarea{color:var(--ai-text);background:var(--ai-surface-muted);border-color:var(--ai-border)}\n .ai-select{color:var(--ai-accent);background:var(--ai-surface);border-color:var(--ai-border)}\n .ai-send,.ai-progress-star{color:var(--ai-header-text);background:var(--ai-header)}\n .ai-selection-chip,.ai-selection-event{color:var(--ai-selection-text);background:var(--ai-selection);border-color:color-mix(in srgb,var(--ai-selection-text) 25%,var(--ai-border))}\n .ai-selection-event{box-shadow:0 7px 22px color-mix(in srgb,var(--ai-selection-text) 15%,transparent)}\n .ai-selection-event small{color:color-mix(in srgb,var(--ai-selection-text) 72%,var(--ai-text-muted))}\n .ai-selection-icon{color:var(--ai-accent-contrast);background:var(--ai-selection-text);box-shadow:0 0 0 5px color-mix(in srgb,var(--ai-selection-text) 10%,transparent)}\n .ai-error{color:var(--ai-danger);background:color-mix(in srgb,var(--ai-danger) 9%,var(--ai-surface));border-color:color-mix(in srgb,var(--ai-danger) 28%,var(--ai-border))}\n .ai-error button{background:var(--ai-danger)}\n .ai-confidence{display:inline-flex;align-items:center;gap:5px;color:var(--ai-text);font-style:normal}\n .ai-confidence-dot{display:inline-block;width:8px;height:8px;background:#8b9490;border-radius:50%;box-shadow:0 0 0 2px color-mix(in srgb,currentColor 10%,transparent)}\n .ai-confidence-dot.ai-confidence-high{background:#238636}.ai-confidence-dot.ai-confidence-medium{background:#d29922}.ai-confidence-dot.ai-confidence-low{background:#da3633}.ai-confidence-dot.ai-confidence-insufficient{background:#8b9490}\n .ai-confidence-note{padding:0;color:var(--ai-text-muted);background:none;border:0;border-radius:0;font-size:11px;font-style:italic}\n .ai-conversation-limit{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:7px;padding:7px 8px;color:var(--ai-text-muted);background:var(--ai-surface-muted);border:1px solid var(--ai-border);border-radius:9px;font-size:12px}.ai-conversation-limit button{flex:0 0 auto;padding:4px 7px;color:var(--ai-accent);background:var(--ai-surface);border:1px solid var(--ai-border);border-radius:7px;cursor:pointer}\n .ai-star{--ai-dx0:2px;--ai-dy0:-1px;--ai-dx1:-2px;--ai-dy1:1px;--ai-dx2:1px;--ai-dy2:2px;color:var(--ai-launcher-text);text-shadow:0 0 6px var(--ai-launcher-text)}.ai-star-one{--ai-x0:-13px;--ai-y0:-10px;--ai-x1:11px;--ai-y1:-12px;--ai-x2:3px;--ai-y2:10px;font-size:10px;animation:ai-star-twinkle 10.9s ease-in-out infinite}.ai-star-two{--ai-x0:14px;--ai-y0:8px;--ai-x1:-11px;--ai-y1:-12px;--ai-x2:14px;--ai-y2:-3px;font-size:7px;animation:ai-star-twinkle 12.7s ease-in-out -3.1s infinite}.ai-star-three{--ai-x0:-14px;--ai-y0:-5px;--ai-x1:7px;--ai-y1:8px;--ai-x2:-4px;--ai-y2:-14px;font-size:8px;animation:ai-star-twinkle 11.8s ease-in-out -6.4s infinite}\n @media(prefers-color-scheme:dark){:host{--ai-surface:#1d2321;--ai-surface-muted:#151a18;--ai-text:#edf2ef;--ai-text-muted:#aab5b0;--ai-border:#39443f;--ai-selection:#252949;--ai-selection-text:#c9d0ff;--ai-danger:#ff7b72}}\n @keyframes ai-star-twinkle{0%,3%{opacity:0;transform:translate(var(--ai-x0),var(--ai-y0)) scale(.15)}6%{opacity:.38;transform:translate(var(--ai-x0),var(--ai-y0)) scale(.42)}10%{opacity:1;transform:translate(var(--ai-x0),var(--ai-y0)) scale(1.18)}14%{opacity:.45;transform:translate(calc(var(--ai-x0) + var(--ai-dx0)),calc(var(--ai-y0) + var(--ai-dy0))) scale(.58)}17%{opacity:0;transform:translate(calc(var(--ai-x0) + var(--ai-dx0)),calc(var(--ai-y0) + var(--ai-dy0))) scale(.15)}18%,41%{opacity:0;transform:translate(var(--ai-x1),var(--ai-y1)) scale(.15)}44%{opacity:.34;transform:translate(var(--ai-x1),var(--ai-y1)) scale(.4)}48%{opacity:.92;transform:translate(var(--ai-x1),var(--ai-y1)) scale(1.12)}52%{opacity:.4;transform:translate(calc(var(--ai-x1) + var(--ai-dx1)),calc(var(--ai-y1) + var(--ai-dy1))) scale(.55)}55%{opacity:0;transform:translate(calc(var(--ai-x1) + var(--ai-dx1)),calc(var(--ai-y1) + var(--ai-dy1))) scale(.15)}56%,78%{opacity:0;transform:translate(var(--ai-x2),var(--ai-y2)) scale(.15)}81%{opacity:.36;transform:translate(var(--ai-x2),var(--ai-y2)) scale(.42)}85%{opacity:.96;transform:translate(var(--ai-x2),var(--ai-y2)) scale(1.16)}89%{opacity:.42;transform:translate(calc(var(--ai-x2) + var(--ai-dx2)),calc(var(--ai-y2) + var(--ai-dy2))) scale(.57)}93%,100%{opacity:0;transform:translate(calc(var(--ai-x2) + var(--ai-dx2)),calc(var(--ai-y2) + var(--ai-dy2))) scale(.15)}}\n @keyframes ai-wave{0%,60%,100%{opacity:.4;transform:translateY(0)}30%{opacity:1;transform:translateY(-4px)}}\n @media(prefers-reduced-motion:reduce){.ai-star,.ai-progress .ai-dots i{animation:none}}\n "], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
462
|
+
}
|
|
463
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.29", ngImport: i0, type: AiDocsAssistantComponent, decorators: [{
|
|
464
|
+
type: Component,
|
|
465
|
+
args: [{ selector: "ai-docs-assistant", standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, host: {
|
|
466
|
+
"data-ai-docs-ui": "",
|
|
467
|
+
"[style.display]": "service.available() ? null : 'none'",
|
|
468
|
+
"[style.--ai-accent]": "theme().accent",
|
|
469
|
+
"[style.--ai-accent-contrast]": "theme().accentContrast",
|
|
470
|
+
"[style.--ai-header]": "theme().header",
|
|
471
|
+
"[style.--ai-header-text]": "theme().headerText",
|
|
472
|
+
"[style.--ai-launcher]": "theme().launcher",
|
|
473
|
+
"[style.--ai-launcher-text]": "theme().launcherText",
|
|
474
|
+
"[style.--ai-surface]": "theme().surface",
|
|
475
|
+
"[style.--ai-surface-muted]": "theme().surfaceMuted",
|
|
476
|
+
"[style.--ai-text]": "theme().text",
|
|
477
|
+
"[style.--ai-text-muted]": "theme().textMuted",
|
|
478
|
+
"[style.--ai-border]": "theme().border",
|
|
479
|
+
"[style.--ai-selection]": "theme().selection",
|
|
480
|
+
"[style.--ai-selection-text]": "theme().selectionText",
|
|
481
|
+
"[style.--ai-danger]": "theme().danger"
|
|
482
|
+
}, template: `
|
|
483
|
+
@if (!open()) {
|
|
484
|
+
<button class="ai-launcher" type="button" (click)="show()" [attr.aria-label]="launcherLabel()">
|
|
485
|
+
<span class="ai-launcher-orbit" aria-hidden="true"><span class="ai-avatar">{{ mascot() }}</span><i class="ai-star ai-star-one" (animationiteration)="randomizeStar($event)">✦</i><i class="ai-star ai-star-two" (animationiteration)="randomizeStar($event)">✦</i><i class="ai-star ai-star-three" (animationiteration)="randomizeStar($event)">✦</i></span><span class="ai-launcher-label">{{ launcherLabel() }}</span>
|
|
486
|
+
</button>
|
|
487
|
+
}
|
|
488
|
+
@if (service.selecting()) {
|
|
489
|
+
<div class="ai-selection-hud" role="status"><span>{{ labels().selectionInstruction }}</span><kbd>Esc</kbd><button type="button" (click)="cancelSelection()">{{ labels().cancel }}</button></div>
|
|
490
|
+
}
|
|
491
|
+
@if (open()) {
|
|
492
|
+
<aside class="ai-panel" [class.ai-docked]="docked()" [style.left.px]="left()" [style.top.px]="top()" aria-label="Assistant de documentation" aria-live="polite">
|
|
493
|
+
<header class="ai-header" (pointerdown)="startDrag($event)">
|
|
494
|
+
<div class="ai-identity"><span class="ai-avatar" aria-hidden="true">{{ mascot() }}</span><div><strong>{{ assistantName() }}</strong><small>{{ subtitle() }}</small></div></div>
|
|
495
|
+
<div class="ai-window-actions">
|
|
496
|
+
<button type="button" [attr.aria-label]="labels().newConversation" [title]="labels().newConversation" (pointerdown)="$event.stopPropagation()" (click)="newConversation()">↻</button>
|
|
497
|
+
<button type="button" [title]="docked() ? labels().detach : labels().dock" (pointerdown)="$event.stopPropagation()" (click)="toggleDock()">{{ docked() ? '↗' : '↘' }}</button>
|
|
498
|
+
<button type="button" [title]="labels().minimize" (pointerdown)="$event.stopPropagation()" (click)="open.set(false)">—</button>
|
|
499
|
+
</div>
|
|
500
|
+
</header>
|
|
501
|
+
<main #conversationContent class="ai-content">
|
|
502
|
+
@if (service.messages().length === 0 && service.state().status === 'idle') {
|
|
503
|
+
<div class="ai-welcome"><span aria-hidden="true">{{ mascot() }}</span><div><strong>{{ labels().welcomeTitle }}</strong><p>{{ labels().welcomeBody }}</p></div></div>
|
|
504
|
+
}
|
|
505
|
+
@for (message of service.messages(); track message.id) {
|
|
506
|
+
@if (message.role === 'selection') {
|
|
507
|
+
<article class="ai-selection-event" [attr.data-ai-message-id]="message.id"><span class="ai-selection-icon">✦</span><div><small>{{ labels().elementSelected }}</small><strong>{{ message.label }}</strong></div><i>✦</i><i>✦</i></article>
|
|
508
|
+
} @else if (message.role === 'user') {
|
|
509
|
+
<article class="ai-message ai-user" [attr.data-ai-message-id]="message.id"><p>{{ message.text }}</p></article>
|
|
510
|
+
} @else {
|
|
511
|
+
<article class="ai-message ai-assistant" [attr.data-ai-message-id]="message.id">
|
|
512
|
+
@if (message.response.answer.title) { <h3>{{ message.response.answer.title }}</h3> }
|
|
513
|
+
<p>{{ message.response.answer.summary }}</p>
|
|
514
|
+
@for (section of message.response.answer.sections; track section.heading) { <section><h4>{{ section.heading }}</h4><p>{{ section.content }}</p></section> }
|
|
515
|
+
@if (message.response.answer.steps?.length) { <ol>@for (step of message.response.answer.steps; track step.label) { <li><strong>{{ step.label }}</strong> {{ step.description }}</li> }</ol> }
|
|
516
|
+
@for (warning of message.response.answer.warnings ?? []; track warning) { <p class="ai-warning">⚠ {{ warning }}</p> }
|
|
517
|
+
<footer><span class="ai-confidence"><i class="ai-confidence-dot" [class]="confidenceClass(message.response)" aria-hidden="true"></i>{{ labels().reliability }}: {{ confidencePercent(message.response) }} %</span>@if (confidenceNotice(message.response); as notice) { <small class="ai-confidence-note">{{ notice }}</small> }@for (limitation of visibleLimitations(message.response); track limitation) { <small>{{ limitation }}</small> }</footer>
|
|
518
|
+
</article>
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
@if (service.state(); as state) {
|
|
522
|
+
@if (state.status === 'loading') {
|
|
523
|
+
<article class="ai-message ai-assistant ai-progress-message">
|
|
524
|
+
@if (state.partialText) { <p class="ai-stream-text">{{ state.partialText }}</p> }
|
|
525
|
+
<div class="ai-progress" role="status"><span class="ai-dots" aria-hidden="true"><i></i><i></i><i></i></span><span>{{ progressLabel(state.phase) }}</span></div>
|
|
526
|
+
@if (state.retry) { <small>{{ labels().retrying }} {{ state.retry.attempt }}/{{ state.retry.maxRetries }}…</small> }
|
|
527
|
+
</article>
|
|
528
|
+
} @else if (state.status === 'error') {
|
|
529
|
+
<div class="ai-error"><strong>{{ labels().errorTitle }}</strong><span>{{ state.error.message }}</span><button type="button" (click)="retry()">{{ labels().retry }}</button></div>
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
</main>
|
|
533
|
+
<form class="ai-composer" (submit)="submit($event)">
|
|
534
|
+
@if (service.conversationLimitReached()) { <div class="ai-conversation-limit"><span>{{ labels().conversationLimitReached }}</span><button type="button" (click)="newConversation()">{{ labels().newConversation }}</button></div> }
|
|
535
|
+
@if (service.selectedElementLabel(); as label) { <div class="ai-selection-chip"><span>◎ {{ label }}</span><button type="button" aria-label="Retirer la sélection" (click)="clearSelection()">×</button></div> }
|
|
536
|
+
<textarea rows="2" maxlength="4000" [value]="question()" [disabled]="service.loading() || service.conversationLimitReached()" [placeholder]="labels().placeholder" (input)="updateQuestion($event)" (keydown)="composerKeydown($event)"></textarea>
|
|
537
|
+
<div class="ai-composer-actions"><button class="ai-select" type="button" [disabled]="service.loading() || service.conversationLimitReached()" (click)="beginSelection()">◎ {{ labels().select }}</button>@if (service.loading()) { <button class="ai-send ai-stop-control" type="button" [attr.aria-label]="labels().stop" [title]="labels().stop" (click)="service.stop()">■</button> } @else { <button class="ai-send" type="submit" [disabled]="service.conversationLimitReached()" [attr.aria-label]="labels().send">➤</button> }</div>
|
|
538
|
+
</form>
|
|
539
|
+
</aside>
|
|
540
|
+
}
|
|
541
|
+
`, styles: ["\n :host{font:14px/1.45 system-ui,sans-serif;color:#202624}button,textarea{font:inherit}.ai-launcher{position:fixed;right:20px;bottom:20px;z-index:9000;display:flex;align-items:center;width:48px;height:48px;padding:0;color:#fff;font-weight:650;background:linear-gradient(135deg,var(--ai-accent),#0f5947);border:0;border-radius:50%;box-shadow:0 8px 24px #0003;cursor:pointer;transition:width .22s ease,border-radius .22s ease,box-shadow .22s ease;animation:ai-beacon 9s ease-in-out infinite}.ai-launcher:hover,.ai-launcher:focus-visible{width:142px;border-radius:999px;box-shadow:0 10px 30px #0004,0 0 0 4px color-mix(in srgb,var(--ai-accent) 16%,transparent);animation:none}.ai-launcher-orbit{position:relative;display:grid;flex:0 0 48px;height:48px;overflow:hidden;place-items:center}.ai-launcher-orbit:before,.ai-launcher-orbit:after{position:absolute;z-index:2;content:'\u2726';color:#eafff9;line-height:1;text-shadow:0 0 7px #fff;opacity:0;pointer-events:none;animation:ai-sparkle 3.8s ease-in-out infinite}.ai-launcher-orbit:before{font-size:12px}.ai-launcher-orbit:after{font-size:7px;animation-delay:1.15s}.ai-launcher .ai-avatar{background:transparent}.ai-launcher-label{max-width:0;overflow:hidden;opacity:0;white-space:nowrap;transform:translateX(-5px);transition:max-width .22s ease,opacity .15s ease,transform .22s ease}.ai-launcher:hover .ai-launcher-label,.ai-launcher:focus-visible .ai-launcher-label{max-width:86px;opacity:1;transform:none}.ai-avatar{display:grid;width:32px;height:32px;place-items:center;background:#ffffff26;border-radius:50%}.ai-panel{position:fixed;z-index:9100;display:flex;flex-direction:column;width:min(390px,calc(100vw - 24px));height:min(600px,calc(100vh - 32px));min-width:310px;min-height:380px;overflow:hidden;background:#fff;border:1px solid #dce3e0;border-radius:16px;box-shadow:0 18px 55px #1c332b38;resize:both}.ai-panel.ai-docked{top:auto!important;right:16px;bottom:16px;left:auto!important;height:min(600px,calc(100vh - 32px));resize:both}.ai-header{display:flex;flex:0 0 auto;align-items:center;justify-content:space-between;gap:10px;padding:10px 12px;color:#fff;background:linear-gradient(135deg,var(--ai-accent),#0f5947);cursor:move;touch-action:none}.ai-identity{display:flex;align-items:center;gap:9px;min-width:0}.ai-identity div{display:flex;flex-direction:column;min-width:0}.ai-identity small{overflow:hidden;opacity:.78;text-overflow:ellipsis;white-space:nowrap}.ai-window-actions{display:flex;gap:1px}.ai-window-actions button{width:28px;height:28px;color:#fff;background:transparent;border:0;border-radius:8px;cursor:pointer}.ai-window-actions button:hover{background:#ffffff26}.ai-content{flex:1 1 auto;min-height:0;padding:14px;overflow:auto;background:#f6f8f7}.ai-welcome{display:flex;gap:10px;padding:13px;background:#e9f4ef;border:1px solid #d6e9e0;border-radius:12px}.ai-welcome p{margin:4px 0 0;color:#57635e}.ai-message{width:fit-content;max-width:92%;margin:0 0 11px;padding:10px 12px;border-radius:13px;box-shadow:0 2px 9px #20382f12}.ai-message p{margin:0 0 8px;white-space:pre-wrap}.ai-message p:last-child{margin-bottom:0}.ai-user{margin-left:auto;color:#fff;background:var(--ai-accent);border-bottom-right-radius:4px}.ai-user small{display:block;margin-bottom:5px;opacity:.78}.ai-assistant{background:#fff;border:1px solid #e0e6e3;border-bottom-left-radius:4px}.ai-assistant h3,.ai-assistant h4{margin:0 0 6px;font-size:14px}.ai-assistant section{margin-top:10px}.ai-assistant footer{display:flex;flex-direction:column;gap:3px;margin:10px -12px -10px;padding:8px 12px;color:#69736f;font-size:11px;font-style:italic;border-top:1px solid #edf0ef}.ai-warning{color:#8b5c00}.ai-stream-text{white-space:pre-wrap}.ai-streaming:after{content:'\u258B';color:var(--ai-accent);animation:blink 1s steps(1) infinite}.ai-dots{display:inline-flex;gap:3px}.ai-dots i{width:5px;height:5px;background:var(--ai-accent);border-radius:50%;animation:pulse 1s infinite alternate}.ai-dots i:nth-child(2){animation-delay:.2s}.ai-dots i:nth-child(3){animation-delay:.4s}.ai-link{display:block;padding:6px 0 0;color:var(--ai-accent);background:none;border:0;cursor:pointer}.ai-error{display:flex;flex-direction:column;gap:7px;padding:12px;color:#7e2525;background:#fff0f0;border:1px solid #f2cece;border-radius:12px}.ai-error button{align-self:flex-start;padding:6px 10px;color:#fff;background:#a43737;border:0;border-radius:8px;cursor:pointer}.ai-composer{flex:0 0 auto;padding:9px;background:#fff;border-top:1px solid #e0e6e3}.ai-composer textarea{box-sizing:border-box;width:100%;min-height:48px;padding:9px 10px;resize:none;color:inherit;background:#f8faf9;border:1px solid #d7dfdc;border-radius:10px;outline:0}.ai-composer textarea:focus{border-color:var(--ai-accent);box-shadow:0 0 0 3px color-mix(in srgb,var(--ai-accent) 15%,transparent)}.ai-composer-actions{display:flex;justify-content:space-between;margin-top:7px}.ai-composer-actions button{border-radius:9px;cursor:pointer}.ai-select{padding:6px 9px;color:var(--ai-accent);background:#fff;border:1px solid #ccd8d3}.ai-send{width:34px;height:32px;color:#fff;background:var(--ai-accent);border:0}button:disabled,textarea:disabled{opacity:.55;cursor:default}.ai-selection-chip{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:7px;padding:5px 8px;color:#175844;background:#e7f4ee;border-radius:9px}.ai-selection-chip span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ai-selection-chip button{color:inherit;background:none;border:0;cursor:pointer}.ai-selection-hud{position:fixed;top:16px;left:50%;z-index:2147483646;display:flex;align-items:center;gap:10px;padding:9px 12px;color:#fff;background:#18231f;border-radius:12px;box-shadow:0 10px 35px #0005;transform:translateX(-50%)}.ai-selection-hud kbd{padding:2px 5px;background:#ffffff20;border-radius:4px}.ai-selection-hud button{color:#fff;background:transparent;border:1px solid #ffffff55;border-radius:7px;cursor:pointer}@media(max-width:575px){.ai-panel,.ai-panel.ai-docked{inset:auto 8px 8px!important;width:calc(100vw - 16px);height:min(600px,calc(100vh - 16px));min-width:0;min-height:360px;border-radius:14px;resize:none}.ai-selection-hud{width:calc(100vw - 24px);justify-content:center}}@media(prefers-reduced-motion:reduce){.ai-launcher,.ai-launcher-orbit:before,.ai-launcher-orbit:after{animation:none}}@keyframes ai-beacon{0%,91%,100%{transform:translateY(0)}94%{transform:translateY(-4px)}97%{transform:translateY(0)}98.5%{transform:translateY(-2px)}}@keyframes ai-sparkle{0%,58%,100%{opacity:0;transform:translate(-15px,14px) scale(.45)}68%{opacity:1}82%{opacity:.85;transform:translate(14px,-13px) scale(1.05)}88%{opacity:0;transform:translate(18px,-17px) scale(.5)}}@keyframes blink{50%{opacity:0}}@keyframes pulse{to{opacity:.25;transform:translateY(-2px)}}\n ", "\n .ai-launcher{animation-duration:14s}.ai-launcher-orbit:before,.ai-launcher-orbit:after{display:none}.ai-star{position:absolute;z-index:2;color:#effffb;font-style:normal;line-height:1;text-shadow:0 0 7px #fff;opacity:0;pointer-events:none}.ai-star-one{font-size:11px;animation:ai-snow-one 6.8s ease-in-out infinite}.ai-star-two{font-size:7px;animation:ai-snow-two 8.1s ease-in-out 1.7s infinite}.ai-star-three{font-size:9px;animation:ai-snow-three 7.4s ease-in-out 3.4s infinite}.ai-identity small{max-width:220px;overflow:visible;line-height:1.18;text-overflow:clip;white-space:normal}.ai-selection-event{position:relative;display:flex;align-items:center;gap:10px;max-width:92%;margin:0 0 11px;padding:10px 12px;overflow:hidden;color:#155a45;background:linear-gradient(135deg,#effbf6,#dff5eb);border:1px solid #9dd8c2;border-radius:13px;box-shadow:0 7px 22px #14745b25;animation:ai-selection-arrive .55s cubic-bezier(.2,1.35,.4,1)}.ai-selection-event div{display:flex;flex-direction:column;min-width:0}.ai-selection-event small{color:#527268}.ai-selection-event strong{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ai-selection-event>i{position:absolute;color:#fff;font-style:normal;text-shadow:0 0 7px var(--ai-accent);animation:ai-selection-glint 1.35s ease-out both}.ai-selection-event>i:nth-last-child(2){right:28px;bottom:4px}.ai-selection-event>i:last-child{top:4px;right:8px;font-size:8px;animation-delay:.18s}.ai-selection-icon{display:grid;flex:0 0 28px;height:28px;place-items:center;color:#fff;background:var(--ai-accent);border-radius:50%;box-shadow:0 0 0 5px #14745b16}.ai-progress-message{min-width:190px}.ai-progress{display:flex;align-items:center;gap:8px;color:#50625b}.ai-progress-star{display:grid;width:24px;height:24px;place-items:center;color:#fff;background:var(--ai-accent);border-radius:50%;animation:ai-thinking 1.8s ease-in-out infinite}.ai-stop-control{background:#34443e}.ai-confidence-note{padding:8px 9px;color:#725400;background:#fff8dc;border-left:3px solid #d4a800;border-radius:7px}.ai-streaming:after{content:none}@media(prefers-reduced-motion:reduce){.ai-star,.ai-selection-event,.ai-selection-event>i,.ai-progress-star{animation:none}}@keyframes ai-snow-one{0%,56%,100%{opacity:0;transform:translate(-17px,18px) rotate(-12deg) scale(.55)}65%{opacity:1}84%{opacity:.9;transform:translate(13px,-14px) rotate(14deg) scale(1.05)}91%{opacity:0;transform:translate(18px,-20px) rotate(23deg) scale(.55)}}@keyframes ai-snow-two{0%,48%,100%{opacity:0;transform:translate(-8px,20px) rotate(8deg) scale(.4)}59%{opacity:.9}78%{opacity:.75;transform:translate(17px,-9px) rotate(-18deg) scale(1)}86%{opacity:0;transform:translate(21px,-15px) scale(.5)}}@keyframes ai-snow-three{0%,63%,100%{opacity:0;transform:translate(-20px,9px) rotate(-20deg) scale(.45)}72%{opacity:1}87%{opacity:.8;transform:translate(9px,-18px) rotate(20deg) scale(1.08)}94%{opacity:0;transform:translate(15px,-22px) scale(.5)}}@keyframes ai-selection-arrive{from{opacity:0;transform:translateY(22px) scale(.78);filter:blur(4px)}to{opacity:1;transform:none;filter:none}}@keyframes ai-selection-glint{from{opacity:0;transform:translate(-22px,14px) scale(.4)}45%{opacity:1}to{opacity:0;transform:translate(8px,-10px) scale(1.3)}}@keyframes ai-thinking{0%,100%{box-shadow:0 0 0 0 #14745b45;transform:rotate(0)}50%{box-shadow:0 0 0 6px #14745b0f;transform:rotate(22deg)}}\n ", "\n :host{--ai-accent:#14745b;--ai-accent-contrast:#fff;--ai-header:linear-gradient(135deg,var(--ai-accent),color-mix(in srgb,var(--ai-accent) 70%,#000));--ai-header-text:var(--ai-accent-contrast);--ai-launcher:var(--ai-header);--ai-launcher-text:var(--ai-header-text);--ai-surface:#fff;--ai-surface-muted:#f6f8f7;--ai-text:#202624;--ai-text-muted:#69736f;--ai-border:#dce3e0;--ai-selection:#eef0ff;--ai-selection-text:#303b82;--ai-danger:#a43737;color:var(--ai-text)}\n .ai-launcher{color:var(--ai-launcher-text);background:var(--ai-launcher);box-shadow:0 8px 24px #0003,0 0 0 1px color-mix(in srgb,var(--ai-launcher) 55%,transparent),0 0 18px color-mix(in srgb,var(--ai-launcher) 48%,transparent)}\n .ai-launcher:hover,.ai-launcher:focus-visible{box-shadow:0 10px 30px #0004,0 0 0 3px color-mix(in srgb,var(--ai-launcher) 24%,transparent),0 0 24px color-mix(in srgb,var(--ai-launcher) 58%,transparent)}\n .ai-panel{color:var(--ai-text);background:var(--ai-surface);border-color:var(--ai-border)}\n .ai-header{color:var(--ai-header-text);background:var(--ai-header)}\n .ai-window-actions button{color:var(--ai-header-text)}\n .ai-content{background:var(--ai-surface-muted)}\n .ai-welcome{background:color-mix(in srgb,var(--ai-accent) 9%,var(--ai-surface));border-color:color-mix(in srgb,var(--ai-accent) 20%,var(--ai-border))}\n .ai-welcome p,.ai-progress{color:var(--ai-text-muted)}\n .ai-progress{gap:9px}.ai-progress .ai-dots{align-items:center;height:16px;gap:4px}.ai-progress .ai-dots i{width:5px;height:5px;background:var(--ai-accent);animation:ai-wave 1.05s ease-in-out infinite}.ai-progress .ai-dots i:nth-child(2){animation-delay:.14s}.ai-progress .ai-dots i:nth-child(3){animation-delay:.28s}\n .ai-user{color:var(--ai-accent-contrast)}\n .ai-assistant{color:var(--ai-text);background:var(--ai-surface);border-color:var(--ai-border)}\n .ai-assistant>h3{margin:0 0 10px;padding:0 0 8px;font-size:15px;font-weight:650;line-height:1.3;border-bottom:1px solid var(--ai-border)}\n .ai-assistant section{margin-top:14px;padding-top:11px;border-top:1px solid color-mix(in srgb,var(--ai-border) 72%,transparent)}\n .ai-assistant section h4{margin:0 0 6px;font-size:13px;font-weight:650;line-height:1.3}\n .ai-assistant p,.ai-assistant li{font-weight:350;line-height:1.48}\n .ai-assistant ol{margin:12px 0 0;padding-left:20px}.ai-assistant li+li{margin-top:7px}\n .ai-assistant footer{color:var(--ai-text-muted);border-color:var(--ai-border)}\n .ai-composer{color:var(--ai-text);background:var(--ai-surface);border-color:var(--ai-border)}\n .ai-composer textarea{color:var(--ai-text);background:var(--ai-surface-muted);border-color:var(--ai-border)}\n .ai-select{color:var(--ai-accent);background:var(--ai-surface);border-color:var(--ai-border)}\n .ai-send,.ai-progress-star{color:var(--ai-header-text);background:var(--ai-header)}\n .ai-selection-chip,.ai-selection-event{color:var(--ai-selection-text);background:var(--ai-selection);border-color:color-mix(in srgb,var(--ai-selection-text) 25%,var(--ai-border))}\n .ai-selection-event{box-shadow:0 7px 22px color-mix(in srgb,var(--ai-selection-text) 15%,transparent)}\n .ai-selection-event small{color:color-mix(in srgb,var(--ai-selection-text) 72%,var(--ai-text-muted))}\n .ai-selection-icon{color:var(--ai-accent-contrast);background:var(--ai-selection-text);box-shadow:0 0 0 5px color-mix(in srgb,var(--ai-selection-text) 10%,transparent)}\n .ai-error{color:var(--ai-danger);background:color-mix(in srgb,var(--ai-danger) 9%,var(--ai-surface));border-color:color-mix(in srgb,var(--ai-danger) 28%,var(--ai-border))}\n .ai-error button{background:var(--ai-danger)}\n .ai-confidence{display:inline-flex;align-items:center;gap:5px;color:var(--ai-text);font-style:normal}\n .ai-confidence-dot{display:inline-block;width:8px;height:8px;background:#8b9490;border-radius:50%;box-shadow:0 0 0 2px color-mix(in srgb,currentColor 10%,transparent)}\n .ai-confidence-dot.ai-confidence-high{background:#238636}.ai-confidence-dot.ai-confidence-medium{background:#d29922}.ai-confidence-dot.ai-confidence-low{background:#da3633}.ai-confidence-dot.ai-confidence-insufficient{background:#8b9490}\n .ai-confidence-note{padding:0;color:var(--ai-text-muted);background:none;border:0;border-radius:0;font-size:11px;font-style:italic}\n .ai-conversation-limit{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:7px;padding:7px 8px;color:var(--ai-text-muted);background:var(--ai-surface-muted);border:1px solid var(--ai-border);border-radius:9px;font-size:12px}.ai-conversation-limit button{flex:0 0 auto;padding:4px 7px;color:var(--ai-accent);background:var(--ai-surface);border:1px solid var(--ai-border);border-radius:7px;cursor:pointer}\n .ai-star{--ai-dx0:2px;--ai-dy0:-1px;--ai-dx1:-2px;--ai-dy1:1px;--ai-dx2:1px;--ai-dy2:2px;color:var(--ai-launcher-text);text-shadow:0 0 6px var(--ai-launcher-text)}.ai-star-one{--ai-x0:-13px;--ai-y0:-10px;--ai-x1:11px;--ai-y1:-12px;--ai-x2:3px;--ai-y2:10px;font-size:10px;animation:ai-star-twinkle 10.9s ease-in-out infinite}.ai-star-two{--ai-x0:14px;--ai-y0:8px;--ai-x1:-11px;--ai-y1:-12px;--ai-x2:14px;--ai-y2:-3px;font-size:7px;animation:ai-star-twinkle 12.7s ease-in-out -3.1s infinite}.ai-star-three{--ai-x0:-14px;--ai-y0:-5px;--ai-x1:7px;--ai-y1:8px;--ai-x2:-4px;--ai-y2:-14px;font-size:8px;animation:ai-star-twinkle 11.8s ease-in-out -6.4s infinite}\n @media(prefers-color-scheme:dark){:host{--ai-surface:#1d2321;--ai-surface-muted:#151a18;--ai-text:#edf2ef;--ai-text-muted:#aab5b0;--ai-border:#39443f;--ai-selection:#252949;--ai-selection-text:#c9d0ff;--ai-danger:#ff7b72}}\n @keyframes ai-star-twinkle{0%,3%{opacity:0;transform:translate(var(--ai-x0),var(--ai-y0)) scale(.15)}6%{opacity:.38;transform:translate(var(--ai-x0),var(--ai-y0)) scale(.42)}10%{opacity:1;transform:translate(var(--ai-x0),var(--ai-y0)) scale(1.18)}14%{opacity:.45;transform:translate(calc(var(--ai-x0) + var(--ai-dx0)),calc(var(--ai-y0) + var(--ai-dy0))) scale(.58)}17%{opacity:0;transform:translate(calc(var(--ai-x0) + var(--ai-dx0)),calc(var(--ai-y0) + var(--ai-dy0))) scale(.15)}18%,41%{opacity:0;transform:translate(var(--ai-x1),var(--ai-y1)) scale(.15)}44%{opacity:.34;transform:translate(var(--ai-x1),var(--ai-y1)) scale(.4)}48%{opacity:.92;transform:translate(var(--ai-x1),var(--ai-y1)) scale(1.12)}52%{opacity:.4;transform:translate(calc(var(--ai-x1) + var(--ai-dx1)),calc(var(--ai-y1) + var(--ai-dy1))) scale(.55)}55%{opacity:0;transform:translate(calc(var(--ai-x1) + var(--ai-dx1)),calc(var(--ai-y1) + var(--ai-dy1))) scale(.15)}56%,78%{opacity:0;transform:translate(var(--ai-x2),var(--ai-y2)) scale(.15)}81%{opacity:.36;transform:translate(var(--ai-x2),var(--ai-y2)) scale(.42)}85%{opacity:.96;transform:translate(var(--ai-x2),var(--ai-y2)) scale(1.16)}89%{opacity:.42;transform:translate(calc(var(--ai-x2) + var(--ai-dx2)),calc(var(--ai-y2) + var(--ai-dy2))) scale(.57)}93%,100%{opacity:0;transform:translate(calc(var(--ai-x2) + var(--ai-dx2)),calc(var(--ai-y2) + var(--ai-dy2))) scale(.15)}}\n @keyframes ai-wave{0%,60%,100%{opacity:.4;transform:translateY(0)}30%{opacity:1;transform:translateY(-4px)}}\n @media(prefers-reduced-motion:reduce){.ai-star,.ai-progress .ai-dots i{animation:none}}\n "] }]
|
|
542
|
+
}], propDecorators: { conversationContent: [{ type: i0.ViewChild, args: ["conversationContent", { isSignal: true }] }] } });
|
|
543
|
+
function createAngularStreamTransport(http, config) {
|
|
544
|
+
return async function* (request, options) {
|
|
545
|
+
if (options.signal?.aborted)
|
|
546
|
+
throw abortReason(options.signal);
|
|
547
|
+
const events$ = http.post(options.endpoint, request, {
|
|
548
|
+
headers: toHttpHeaders(await config.headers?.()), observe: "events", reportProgress: true, responseType: "text"
|
|
549
|
+
});
|
|
550
|
+
let parsedLength = 0;
|
|
551
|
+
let buffer = "";
|
|
552
|
+
for await (const event of observableEvents(events$, options.signal)) {
|
|
553
|
+
const text = event instanceof HttpResponse
|
|
554
|
+
? String(event.body ?? "")
|
|
555
|
+
: isDownloadProgress(event)
|
|
556
|
+
? event.partialText ?? ""
|
|
557
|
+
: "";
|
|
558
|
+
if (!text || text.length < parsedLength)
|
|
559
|
+
continue;
|
|
560
|
+
buffer += text.slice(parsedLength);
|
|
561
|
+
parsedLength = text.length;
|
|
562
|
+
const lines = buffer.split("\n");
|
|
563
|
+
buffer = lines.pop() ?? "";
|
|
564
|
+
for (const line of lines)
|
|
565
|
+
if (line.trim())
|
|
566
|
+
yield JSON.parse(line);
|
|
567
|
+
}
|
|
568
|
+
if (buffer.trim())
|
|
569
|
+
yield JSON.parse(buffer);
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
async function* observableEvents(source, signal) {
|
|
573
|
+
const queue = [];
|
|
574
|
+
let wake;
|
|
575
|
+
let done = false;
|
|
576
|
+
let failure;
|
|
577
|
+
const subscription = source.subscribe({
|
|
578
|
+
next: (value) => { queue.push(value); wake?.(); },
|
|
579
|
+
error: (error) => { failure = error; done = true; wake?.(); },
|
|
580
|
+
complete: () => { done = true; wake?.(); }
|
|
581
|
+
});
|
|
582
|
+
const abort = () => { failure = abortReason(signal); done = true; subscription.unsubscribe(); wake?.(); };
|
|
583
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
584
|
+
try {
|
|
585
|
+
while (!done || queue.length) {
|
|
586
|
+
if (queue.length) {
|
|
587
|
+
yield queue.shift();
|
|
588
|
+
continue;
|
|
589
|
+
}
|
|
590
|
+
await new Promise((resolve) => { wake = resolve; });
|
|
591
|
+
wake = undefined;
|
|
592
|
+
}
|
|
593
|
+
if (failure)
|
|
594
|
+
throw failure;
|
|
595
|
+
}
|
|
596
|
+
finally {
|
|
597
|
+
signal?.removeEventListener("abort", abort);
|
|
598
|
+
subscription.unsubscribe();
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
function isDownloadProgress(event) {
|
|
602
|
+
return Boolean(event && typeof event === "object" && event.type === HttpEventType.DownloadProgress);
|
|
603
|
+
}
|
|
604
|
+
function toHttpHeaders(input) {
|
|
605
|
+
let result = new HttpHeaders();
|
|
606
|
+
if (!input)
|
|
607
|
+
return result;
|
|
608
|
+
new Headers(input).forEach((value, name) => { result = result.set(name, value); });
|
|
609
|
+
return result;
|
|
610
|
+
}
|
|
611
|
+
/** Converts Angular HttpClient responses back to Fetch responses for generic clients. */
|
|
612
|
+
function createAngularFetch(http) {
|
|
613
|
+
return async (input, init) => {
|
|
614
|
+
const source = input instanceof Request ? input : undefined;
|
|
615
|
+
const url = source?.url ?? String(input);
|
|
616
|
+
const method = (init?.method ?? source?.method ?? "GET").toUpperCase();
|
|
617
|
+
const headers = toHttpHeaders(init?.headers ?? source?.headers);
|
|
618
|
+
let body = init?.body;
|
|
619
|
+
if (body === undefined && source && method !== "GET" && method !== "HEAD") {
|
|
620
|
+
body = await source.clone().text();
|
|
621
|
+
}
|
|
622
|
+
try {
|
|
623
|
+
const result = await firstValueFrom(http.request(method, url, {
|
|
624
|
+
headers,
|
|
625
|
+
observe: "response",
|
|
626
|
+
responseType: "text",
|
|
627
|
+
...(body !== undefined ? { body } : {})
|
|
628
|
+
}));
|
|
629
|
+
return new Response(result.body ?? "", {
|
|
630
|
+
status: result.status,
|
|
631
|
+
statusText: result.statusText,
|
|
632
|
+
headers: toFetchHeaders(result.headers)
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
catch (error) {
|
|
636
|
+
if (!(error instanceof HttpErrorResponse))
|
|
637
|
+
throw error;
|
|
638
|
+
const responseBody = typeof error.error === "string"
|
|
639
|
+
? error.error
|
|
640
|
+
: JSON.stringify(error.error ?? { message: error.message });
|
|
641
|
+
return new Response(responseBody, {
|
|
642
|
+
status: error.status || 500,
|
|
643
|
+
statusText: error.statusText,
|
|
644
|
+
headers: toFetchHeaders(error.headers)
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
function toFetchHeaders(input) {
|
|
650
|
+
const headers = new Headers();
|
|
651
|
+
for (const name of input.keys()) {
|
|
652
|
+
for (const value of input.getAll(name) ?? [])
|
|
653
|
+
headers.append(name, value);
|
|
654
|
+
}
|
|
655
|
+
return headers;
|
|
656
|
+
}
|
|
657
|
+
function compactTheme(input) {
|
|
658
|
+
return Object.fromEntries(Object.entries(input).filter((entry) => Boolean(entry[1])));
|
|
659
|
+
}
|
|
660
|
+
function abortReason(signal) { return signal.reason ?? new DOMException("Aborted", "AbortError"); }
|
|
661
|
+
const ENGLISH_LABELS = {
|
|
662
|
+
selectionInstruction: "Select an element on the page",
|
|
663
|
+
cancel: "Cancel",
|
|
664
|
+
newConversation: "New conversation",
|
|
665
|
+
detach: "Detach",
|
|
666
|
+
dock: "Dock",
|
|
667
|
+
minimize: "Minimize",
|
|
668
|
+
welcomeTitle: "How can I help?",
|
|
669
|
+
welcomeBody: "Ask about this page or select a specific element.",
|
|
670
|
+
reliability: "Confidence",
|
|
671
|
+
retrying: "Retry",
|
|
672
|
+
reading: "Reading the page…",
|
|
673
|
+
thinking: "Thinking…",
|
|
674
|
+
writing: "Writing the answer…",
|
|
675
|
+
finalizing: "Checking the answer…",
|
|
676
|
+
stop: "Stop generating",
|
|
677
|
+
errorTitle: "The answer could not be generated.",
|
|
678
|
+
retry: "Retry",
|
|
679
|
+
placeholder: "Ask your question…",
|
|
680
|
+
select: "Select element",
|
|
681
|
+
elementSelected: "Element added to the conversation",
|
|
682
|
+
mediumConfidence: "Based on the available information; verify before acting.",
|
|
683
|
+
lowConfidence: "Likely answer based on limited information.",
|
|
684
|
+
insufficientConfidence: "I do not have enough reliable information to answer.",
|
|
685
|
+
conversationLimitReached: "Question limit reached. Start a new conversation to continue.",
|
|
686
|
+
send: "Send"
|
|
687
|
+
};
|
|
688
|
+
const FRENCH_LABELS = {
|
|
689
|
+
selectionInstruction: "Choisissez un élément dans la page",
|
|
690
|
+
cancel: "Annuler",
|
|
691
|
+
newConversation: "Nouvelle conversation",
|
|
692
|
+
detach: "Détacher",
|
|
693
|
+
dock: "Ancrer",
|
|
694
|
+
minimize: "Réduire",
|
|
695
|
+
welcomeTitle: "Comment puis-je vous aider ?",
|
|
696
|
+
welcomeBody: "Interrogez cette page ou sélectionnez un élément précis.",
|
|
697
|
+
reliability: "Fiabilité",
|
|
698
|
+
retrying: "Nouvelle tentative",
|
|
699
|
+
reading: "Lecture de la page…",
|
|
700
|
+
thinking: "Réflexion…",
|
|
701
|
+
writing: "Rédaction de la réponse…",
|
|
702
|
+
finalizing: "Vérification de la réponse…",
|
|
703
|
+
stop: "Arrêter la génération",
|
|
704
|
+
errorTitle: "La réponse n’a pas pu être générée.",
|
|
705
|
+
retry: "Réessayer",
|
|
706
|
+
placeholder: "Posez votre question…",
|
|
707
|
+
select: "Sélectionner un élément",
|
|
708
|
+
elementSelected: "Élément ajouté à la conversation",
|
|
709
|
+
mediumConfidence: "D’après les éléments disponibles ; à vérifier avant d’agir.",
|
|
710
|
+
lowConfidence: "Je suppose ceci à partir d’informations limitées.",
|
|
711
|
+
insufficientConfidence: "Je n’ai pas assez d’informations fiables pour répondre.",
|
|
712
|
+
conversationLimitReached: "Limite de questions atteinte. Démarrez une nouvelle conversation pour continuer.",
|
|
713
|
+
send: "Envoyer"
|
|
714
|
+
};
|
|
715
|
+
//# sourceMappingURL=angular.js.map
|