@newheap/platform-ai-chat 0.5.0 → 0.6.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/fesm2022/newheap-platform-ai-chat-admin.mjs +21 -21
- package/fesm2022/newheap-platform-ai-chat-testing.mjs +323 -18
- package/fesm2022/newheap-platform-ai-chat-testing.mjs.map +1 -1
- package/fesm2022/newheap-platform-ai-chat.mjs +1799 -343
- package/fesm2022/newheap-platform-ai-chat.mjs.map +1 -1
- package/i18n/en.json +81 -9
- package/i18n/nl.json +81 -9
- package/index.d.ts +512 -36
- package/package.json +5 -1
- package/push/nh-assistant-push-worker.js +70 -0
- package/testing/index.d.ts +102 -11
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { InjectionToken, inject, DestroyRef, Injectable, EnvironmentInjector, runInInjectionContext, signal, computed,
|
|
2
|
+
import { InjectionToken, inject, DestroyRef, Injectable, EnvironmentInjector, runInInjectionContext, PLATFORM_ID, signal, computed, effect, untracked, Injector, afterNextRender, makeEnvironmentProviders, provideEnvironmentInitializer, input, ChangeDetectionStrategy, Component, ChangeDetectorRef, Pipe, output, ElementRef, viewChild, HostListener, ViewContainerRef } from '@angular/core';
|
|
3
3
|
import { TranslateService, TranslatePipe } from '@ngx-translate/core';
|
|
4
|
-
import { merge, Observable,
|
|
4
|
+
import { merge, Observable, Subject, firstValueFrom, Subscription, isObservable } from 'rxjs';
|
|
5
|
+
import { isPlatformBrowser, DOCUMENT, DatePipe, NgTemplateOutlet } from '@angular/common';
|
|
5
6
|
import { BreakpointObserver } from '@angular/cdk/layout';
|
|
6
7
|
import { Overlay } from '@angular/cdk/overlay';
|
|
7
|
-
import { DOCUMENT, DatePipe, NgTemplateOutlet } from '@angular/common';
|
|
8
8
|
import { TemplatePortal } from '@angular/cdk/portal';
|
|
9
9
|
import { Router, NavigationEnd, RouterLink } from '@angular/router';
|
|
10
10
|
import * as i1 from '@angular/cdk/scrolling';
|
|
@@ -43,7 +43,9 @@ const NhAssistantClientErrorCodes = {
|
|
|
43
43
|
versionConflict: 'assistant-version-conflict',
|
|
44
44
|
invalidResponse: 'assistant-invalid-response',
|
|
45
45
|
streamInterrupted: 'assistant-stream-interrupted',
|
|
46
|
-
server: 'assistant-server'
|
|
46
|
+
server: 'assistant-server',
|
|
47
|
+
/** The open conversation was deleted by its owner, or the user left or was removed. */
|
|
48
|
+
conversationRemoved: 'assistant-conversation-removed'
|
|
47
49
|
};
|
|
48
50
|
|
|
49
51
|
/*
|
|
@@ -77,7 +79,9 @@ var en = {
|
|
|
77
79
|
launcher: {
|
|
78
80
|
open: "Open assistant",
|
|
79
81
|
close: "Close assistant",
|
|
80
|
-
"pending-approval": "An action is waiting for your approval"
|
|
82
|
+
"pending-approval": "An action is waiting for your approval",
|
|
83
|
+
unread: "{{count}} unread conversations",
|
|
84
|
+
running: "{{count}} conversations working"
|
|
81
85
|
},
|
|
82
86
|
panel: {
|
|
83
87
|
title: "Assistant",
|
|
@@ -95,7 +99,9 @@ var en = {
|
|
|
95
99
|
"dismiss-error": "Dismiss message",
|
|
96
100
|
usage: "{{inputTokens}} input tokens, {{outputTokens}} output tokens, {{toolCalls}} tool calls",
|
|
97
101
|
"open-preferences": "Your assistant preferences",
|
|
98
|
-
"open-admin": "Assistant administration"
|
|
102
|
+
"open-admin": "Assistant administration",
|
|
103
|
+
share: "Share conversation",
|
|
104
|
+
members: "People in this conversation"
|
|
99
105
|
},
|
|
100
106
|
"agent-picker": {
|
|
101
107
|
label: "Agent",
|
|
@@ -115,7 +121,11 @@ var en = {
|
|
|
115
121
|
"waiting-for-approval": "Waiting for approval",
|
|
116
122
|
failed: "Failed",
|
|
117
123
|
archived: "Archived"
|
|
118
|
-
}
|
|
124
|
+
},
|
|
125
|
+
leave: "Leave conversation",
|
|
126
|
+
unread: "Unread",
|
|
127
|
+
shared: "Shared with {{count}} people",
|
|
128
|
+
"shared-with-you": "Shared with you"
|
|
119
129
|
},
|
|
120
130
|
thread: {
|
|
121
131
|
label: "Conversation",
|
|
@@ -124,7 +134,8 @@ var en = {
|
|
|
124
134
|
tool: "Tool",
|
|
125
135
|
working: "The assistant is working",
|
|
126
136
|
loading: "Loading conversation",
|
|
127
|
-
elapsed: "{{seconds}}s"
|
|
137
|
+
elapsed: "{{seconds}}s",
|
|
138
|
+
participant: "Participant"
|
|
128
139
|
},
|
|
129
140
|
composer: {
|
|
130
141
|
label: "Message",
|
|
@@ -136,7 +147,9 @@ var en = {
|
|
|
136
147
|
"too-long": "The message is too long.",
|
|
137
148
|
status: {
|
|
138
149
|
running: "You can keep typing while the assistant works. Sending is temporarily unavailable.",
|
|
139
|
-
"waiting-for-approval": "You can keep typing while an action waits for your approval. Sending is temporarily unavailable."
|
|
150
|
+
"waiting-for-approval": "You can keep typing while an action waits for your approval. Sending is temporarily unavailable.",
|
|
151
|
+
"running-by": "{{name}} is working with the assistant…",
|
|
152
|
+
"waiting-for-approval-by": "Waiting for {{name}} to decide…"
|
|
140
153
|
}
|
|
141
154
|
},
|
|
142
155
|
"tool-call": {
|
|
@@ -184,7 +197,9 @@ var en = {
|
|
|
184
197
|
approved: "Approved",
|
|
185
198
|
rejected: "Rejected",
|
|
186
199
|
expired: "Expired"
|
|
187
|
-
}
|
|
200
|
+
},
|
|
201
|
+
"waiting-for": "Waiting for {{name}} to decide. The action uses their permissions.",
|
|
202
|
+
"waiting-for-participant": "Waiting for the participant who started this task to decide."
|
|
188
203
|
},
|
|
189
204
|
"result-codes": {
|
|
190
205
|
"api-bridge-validation": "The request was not valid.",
|
|
@@ -242,10 +257,21 @@ var en = {
|
|
|
242
257
|
"assistant-tool-call-limit-reached": "The assistant reached the maximum number of steps for one answer. Ask a narrower question.",
|
|
243
258
|
"assistant-tools-disabled": "No further actions were run in this answer because an action was rejected.",
|
|
244
259
|
"assistant-turn-failed": "The assistant could not finish the answer. Try again.",
|
|
245
|
-
"assistant-turn-timeout": "The answer took too long and was stopped. Try again or ask a narrower question."
|
|
260
|
+
"assistant-turn-timeout": "The answer took too long and was stopped. Try again or ask a narrower question.",
|
|
261
|
+
"assistant-owner-required": "Only the owner of this conversation can do this.",
|
|
262
|
+
"assistant-approval-forbidden": "Only the person who started this task can decide about it.",
|
|
263
|
+
"assistant-turn-forbidden": "Only the owner or the person who started this task can stop it.",
|
|
264
|
+
"assistant-share-link-invalid": "This invitation link is no longer valid. Ask the owner for a new one.",
|
|
265
|
+
"assistant-participant-not-found": "This person is not part of the conversation.",
|
|
266
|
+
"assistant-participant-limit-reached": "This conversation has the maximum number of participants.",
|
|
267
|
+
"assistant-directory-unavailable": "Inviting colleagues directly is not available.",
|
|
268
|
+
"assistant-push-unavailable": "Desktop notifications are not available.",
|
|
269
|
+
"assistant-push-subscription-invalid": "This browser could not be registered for notifications.",
|
|
270
|
+
"assistant-conversation-removed": "This conversation is no longer available to you."
|
|
246
271
|
},
|
|
247
272
|
notices: {
|
|
248
|
-
"no-answer": "The assistant finished without an answer. Try again or rephrase your question."
|
|
273
|
+
"no-answer": "The assistant finished without an answer. Try again or rephrase your question.",
|
|
274
|
+
"conversation-removed": "This conversation is no longer available to you."
|
|
249
275
|
},
|
|
250
276
|
preferences: {
|
|
251
277
|
title: "Your preferences",
|
|
@@ -282,7 +308,16 @@ var en = {
|
|
|
282
308
|
back: "Back to the conversation",
|
|
283
309
|
loading: "Loading your preferences",
|
|
284
310
|
"load-failed": "Your preferences could not be loaded.",
|
|
285
|
-
retry: "Try again"
|
|
311
|
+
retry: "Try again",
|
|
312
|
+
"notifications-label": "Notifications",
|
|
313
|
+
notifications: "Desktop notifications",
|
|
314
|
+
"notifications-state": {
|
|
315
|
+
on: "You get a notification when a longer task finishes or needs your approval while you are away.",
|
|
316
|
+
prompt: "Your browser asks for permission the next time you send a message.",
|
|
317
|
+
blocked: "Your browser blocks notifications for this site. Allow them in the browser settings.",
|
|
318
|
+
off: "Notifications are off for all your devices.",
|
|
319
|
+
unavailable: "Notifications are not available."
|
|
320
|
+
}
|
|
286
321
|
},
|
|
287
322
|
admin: {
|
|
288
323
|
title: "Assistant administration",
|
|
@@ -467,6 +502,45 @@ var en = {
|
|
|
467
502
|
excluded: "Not sent with this message: {{page}}",
|
|
468
503
|
exclude: "Do not send {{page}} with this message",
|
|
469
504
|
include: "Send {{page}} with this message again"
|
|
505
|
+
},
|
|
506
|
+
activity: {
|
|
507
|
+
label: "Other conversations",
|
|
508
|
+
running: "Working",
|
|
509
|
+
waiting: "Waiting for approval",
|
|
510
|
+
unread: "Unread"
|
|
511
|
+
},
|
|
512
|
+
share: {
|
|
513
|
+
title: "Share conversation",
|
|
514
|
+
"members-title": "People in this conversation",
|
|
515
|
+
back: "Back to the conversation",
|
|
516
|
+
description: "People you share with read the whole conversation, including tool results, and can continue it with their own permissions.",
|
|
517
|
+
"link-title": "Invitation link",
|
|
518
|
+
"link-label": "Invitation link",
|
|
519
|
+
"link-hint": "Anyone in your organization who may use this assistant can join with the link. A new link replaces the old one; people who joined stay.",
|
|
520
|
+
create: "Create invitation link",
|
|
521
|
+
copy: "Copy link",
|
|
522
|
+
copied: "Link copied.",
|
|
523
|
+
renew: "New link",
|
|
524
|
+
revoke: "Stop link",
|
|
525
|
+
"invite-title": "Invite a colleague",
|
|
526
|
+
"search-label": "Search colleagues",
|
|
527
|
+
"search-placeholder": "Search by name",
|
|
528
|
+
searching: "Searching…",
|
|
529
|
+
"no-results": "No colleagues found.",
|
|
530
|
+
invite: "Invite",
|
|
531
|
+
invited: "{{name}} was invited.",
|
|
532
|
+
members: "People",
|
|
533
|
+
count: "{{count}} of {{max}} invited",
|
|
534
|
+
"not-shared": "Only you take part in this conversation.",
|
|
535
|
+
unnamed: "Unnamed colleague",
|
|
536
|
+
you: "(you)",
|
|
537
|
+
role: {
|
|
538
|
+
owner: "Owner",
|
|
539
|
+
participant: "Participant"
|
|
540
|
+
},
|
|
541
|
+
remove: "Remove {{name}}",
|
|
542
|
+
leave: "Leave conversation",
|
|
543
|
+
"no-conversation": "Start or open a conversation to share it."
|
|
470
544
|
}
|
|
471
545
|
}
|
|
472
546
|
};
|
|
@@ -476,7 +550,9 @@ var nl = {
|
|
|
476
550
|
launcher: {
|
|
477
551
|
open: "Assistent openen",
|
|
478
552
|
close: "Assistent sluiten",
|
|
479
|
-
"pending-approval": "Er wacht een actie op je goedkeuring"
|
|
553
|
+
"pending-approval": "Er wacht een actie op je goedkeuring",
|
|
554
|
+
unread: "{{count}} ongelezen gesprekken",
|
|
555
|
+
running: "{{count}} gesprekken bezig"
|
|
480
556
|
},
|
|
481
557
|
panel: {
|
|
482
558
|
title: "Assistent",
|
|
@@ -494,7 +570,9 @@ var nl = {
|
|
|
494
570
|
"dismiss-error": "Melding sluiten",
|
|
495
571
|
usage: "{{inputTokens}} invoertokens, {{outputTokens}} uitvoertokens, {{toolCalls}} toolaanroepen",
|
|
496
572
|
"open-preferences": "Je assistentvoorkeuren",
|
|
497
|
-
"open-admin": "Beheer van de assistent"
|
|
573
|
+
"open-admin": "Beheer van de assistent",
|
|
574
|
+
share: "Gesprek delen",
|
|
575
|
+
members: "Mensen in dit gesprek"
|
|
498
576
|
},
|
|
499
577
|
"agent-picker": {
|
|
500
578
|
label: "Agent",
|
|
@@ -514,7 +592,11 @@ var nl = {
|
|
|
514
592
|
"waiting-for-approval": "Wacht op goedkeuring",
|
|
515
593
|
failed: "Mislukt",
|
|
516
594
|
archived: "Gearchiveerd"
|
|
517
|
-
}
|
|
595
|
+
},
|
|
596
|
+
leave: "Gesprek verlaten",
|
|
597
|
+
unread: "Ongelezen",
|
|
598
|
+
shared: "Gedeeld met {{count}} mensen",
|
|
599
|
+
"shared-with-you": "Met jou gedeeld"
|
|
518
600
|
},
|
|
519
601
|
thread: {
|
|
520
602
|
label: "Gesprek",
|
|
@@ -523,7 +605,8 @@ var nl = {
|
|
|
523
605
|
tool: "Tool",
|
|
524
606
|
working: "De assistent is bezig",
|
|
525
607
|
loading: "Gesprek laden",
|
|
526
|
-
elapsed: "{{seconds}} s"
|
|
608
|
+
elapsed: "{{seconds}} s",
|
|
609
|
+
participant: "Deelnemer"
|
|
527
610
|
},
|
|
528
611
|
composer: {
|
|
529
612
|
label: "Bericht",
|
|
@@ -535,7 +618,9 @@ var nl = {
|
|
|
535
618
|
"too-long": "Het bericht is te lang.",
|
|
536
619
|
status: {
|
|
537
620
|
running: "Je kunt verder typen terwijl de assistent bezig is. Versturen is tijdelijk niet mogelijk.",
|
|
538
|
-
"waiting-for-approval": "Je kunt verder typen terwijl een actie op je goedkeuring wacht. Versturen is tijdelijk niet mogelijk."
|
|
621
|
+
"waiting-for-approval": "Je kunt verder typen terwijl een actie op je goedkeuring wacht. Versturen is tijdelijk niet mogelijk.",
|
|
622
|
+
"running-by": "{{name}} werkt met de assistent…",
|
|
623
|
+
"waiting-for-approval-by": "Wacht op de beslissing van {{name}}…"
|
|
539
624
|
}
|
|
540
625
|
},
|
|
541
626
|
"tool-call": {
|
|
@@ -583,7 +668,9 @@ var nl = {
|
|
|
583
668
|
approved: "Goedgekeurd",
|
|
584
669
|
rejected: "Afgewezen",
|
|
585
670
|
expired: "Verlopen"
|
|
586
|
-
}
|
|
671
|
+
},
|
|
672
|
+
"waiting-for": "Wacht op de beslissing van {{name}}. De actie gebruikt diens rechten.",
|
|
673
|
+
"waiting-for-participant": "Wacht op de beslissing van de deelnemer die deze taak startte."
|
|
587
674
|
},
|
|
588
675
|
"result-codes": {
|
|
589
676
|
"api-bridge-validation": "Het verzoek was niet geldig.",
|
|
@@ -641,10 +728,21 @@ var nl = {
|
|
|
641
728
|
"assistant-tool-call-limit-reached": "De assistent heeft het maximale aantal stappen voor één antwoord bereikt. Stel een gerichtere vraag.",
|
|
642
729
|
"assistant-tools-disabled": "In dit antwoord zijn geen verdere acties uitgevoerd omdat een actie is afgewezen.",
|
|
643
730
|
"assistant-turn-failed": "De assistent kon het antwoord niet afmaken. Probeer het opnieuw.",
|
|
644
|
-
"assistant-turn-timeout": "Het antwoord duurde te lang en is gestopt. Probeer het opnieuw of stel een gerichtere vraag."
|
|
731
|
+
"assistant-turn-timeout": "Het antwoord duurde te lang en is gestopt. Probeer het opnieuw of stel een gerichtere vraag.",
|
|
732
|
+
"assistant-owner-required": "Alleen de eigenaar van dit gesprek kan dit doen.",
|
|
733
|
+
"assistant-approval-forbidden": "Alleen degene die deze taak startte, kan erover beslissen.",
|
|
734
|
+
"assistant-turn-forbidden": "Alleen de eigenaar of degene die deze taak startte, kan haar stoppen.",
|
|
735
|
+
"assistant-share-link-invalid": "Deze uitnodigingslink is niet meer geldig. Vraag de eigenaar om een nieuwe.",
|
|
736
|
+
"assistant-participant-not-found": "Deze persoon doet niet mee aan het gesprek.",
|
|
737
|
+
"assistant-participant-limit-reached": "Dit gesprek heeft het maximale aantal deelnemers.",
|
|
738
|
+
"assistant-directory-unavailable": "Collega’s direct uitnodigen is niet beschikbaar.",
|
|
739
|
+
"assistant-push-unavailable": "Bureaubladmeldingen zijn niet beschikbaar.",
|
|
740
|
+
"assistant-push-subscription-invalid": "Deze browser kon niet worden aangemeld voor meldingen.",
|
|
741
|
+
"assistant-conversation-removed": "Dit gesprek is niet meer voor je beschikbaar."
|
|
645
742
|
},
|
|
646
743
|
notices: {
|
|
647
|
-
"no-answer": "De assistent is klaar zonder antwoord. Probeer het opnieuw of stel je vraag anders."
|
|
744
|
+
"no-answer": "De assistent is klaar zonder antwoord. Probeer het opnieuw of stel je vraag anders.",
|
|
745
|
+
"conversation-removed": "Dit gesprek is niet meer voor je beschikbaar."
|
|
648
746
|
},
|
|
649
747
|
preferences: {
|
|
650
748
|
title: "Je voorkeuren",
|
|
@@ -681,7 +779,16 @@ var nl = {
|
|
|
681
779
|
back: "Terug naar het gesprek",
|
|
682
780
|
loading: "Je voorkeuren laden",
|
|
683
781
|
"load-failed": "Je voorkeuren konden niet worden geladen.",
|
|
684
|
-
retry: "Opnieuw proberen"
|
|
782
|
+
retry: "Opnieuw proberen",
|
|
783
|
+
"notifications-label": "Meldingen",
|
|
784
|
+
notifications: "Bureaubladmeldingen",
|
|
785
|
+
"notifications-state": {
|
|
786
|
+
on: "Je krijgt een melding als een langere taak klaar is of je goedkeuring nodig heeft terwijl je weg bent.",
|
|
787
|
+
prompt: "Je browser vraagt om toestemming wanneer je het volgende bericht verstuurt.",
|
|
788
|
+
blocked: "Je browser blokkeert meldingen voor deze site. Sta ze toe in de browserinstellingen.",
|
|
789
|
+
off: "Meldingen staan uit op al je apparaten.",
|
|
790
|
+
unavailable: "Meldingen zijn niet beschikbaar."
|
|
791
|
+
}
|
|
685
792
|
},
|
|
686
793
|
admin: {
|
|
687
794
|
title: "Beheer van de assistent",
|
|
@@ -866,6 +973,45 @@ var nl = {
|
|
|
866
973
|
excluded: "Niet meegestuurd bij dit bericht: {{page}}",
|
|
867
974
|
exclude: "{{page}} niet meesturen met dit bericht",
|
|
868
975
|
include: "{{page}} toch meesturen met dit bericht"
|
|
976
|
+
},
|
|
977
|
+
activity: {
|
|
978
|
+
label: "Andere gesprekken",
|
|
979
|
+
running: "Bezig",
|
|
980
|
+
waiting: "Wacht op goedkeuring",
|
|
981
|
+
unread: "Ongelezen"
|
|
982
|
+
},
|
|
983
|
+
share: {
|
|
984
|
+
title: "Gesprek delen",
|
|
985
|
+
"members-title": "Mensen in dit gesprek",
|
|
986
|
+
back: "Terug naar het gesprek",
|
|
987
|
+
description: "Mensen met wie je deelt lezen het hele gesprek, ook de resultaten van tools, en kunnen het voortzetten met hun eigen rechten.",
|
|
988
|
+
"link-title": "Uitnodigingslink",
|
|
989
|
+
"link-label": "Uitnodigingslink",
|
|
990
|
+
"link-hint": "Iedereen in je organisatie die deze assistent mag gebruiken, kan met de link meedoen. Een nieuwe link vervangt de oude; wie al meedoet, blijft.",
|
|
991
|
+
create: "Uitnodigingslink maken",
|
|
992
|
+
copy: "Link kopiëren",
|
|
993
|
+
copied: "Link gekopieerd.",
|
|
994
|
+
renew: "Nieuwe link",
|
|
995
|
+
revoke: "Link stoppen",
|
|
996
|
+
"invite-title": "Collega uitnodigen",
|
|
997
|
+
"search-label": "Collega’s zoeken",
|
|
998
|
+
"search-placeholder": "Zoek op naam",
|
|
999
|
+
searching: "Zoeken…",
|
|
1000
|
+
"no-results": "Geen collega’s gevonden.",
|
|
1001
|
+
invite: "Uitnodigen",
|
|
1002
|
+
invited: "{{name}} is uitgenodigd.",
|
|
1003
|
+
members: "Mensen",
|
|
1004
|
+
count: "{{count}} van {{max}} uitgenodigd",
|
|
1005
|
+
"not-shared": "Alleen jij doet mee aan dit gesprek.",
|
|
1006
|
+
unnamed: "Collega zonder naam",
|
|
1007
|
+
you: "(jij)",
|
|
1008
|
+
role: {
|
|
1009
|
+
owner: "Eigenaar",
|
|
1010
|
+
participant: "Deelnemer"
|
|
1011
|
+
},
|
|
1012
|
+
remove: "{{name}} verwijderen",
|
|
1013
|
+
leave: "Gesprek verlaten",
|
|
1014
|
+
"no-conversation": "Start of open een gesprek om het te delen."
|
|
869
1015
|
}
|
|
870
1016
|
}
|
|
871
1017
|
};
|
|
@@ -931,10 +1077,10 @@ class NhAssistantTranslationMerger {
|
|
|
931
1077
|
const merged = mergeTrees(bundleRoot, isTree(existing) ? existing : {});
|
|
932
1078
|
this.translate?.setTranslation(language, { [rootKey]: merged }, true);
|
|
933
1079
|
}
|
|
934
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
935
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.
|
|
1080
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantTranslationMerger, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1081
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantTranslationMerger, providedIn: 'root' }); }
|
|
936
1082
|
}
|
|
937
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
1083
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantTranslationMerger, decorators: [{
|
|
938
1084
|
type: Injectable,
|
|
939
1085
|
args: [{ providedIn: 'root' }]
|
|
940
1086
|
}] });
|
|
@@ -1069,10 +1215,10 @@ class NhAssistantTransport {
|
|
|
1069
1215
|
const base = this.config.apiBaseUrl.replace(/\/+$/, '');
|
|
1070
1216
|
return `${base}/${path}`;
|
|
1071
1217
|
}
|
|
1072
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
1073
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.
|
|
1218
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantTransport, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1219
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantTransport }); }
|
|
1074
1220
|
}
|
|
1075
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
1221
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantTransport, decorators: [{
|
|
1076
1222
|
type: Injectable
|
|
1077
1223
|
}] });
|
|
1078
1224
|
function createApiError(status, code) {
|
|
@@ -1154,10 +1300,10 @@ class NhAssistantAdminApiService {
|
|
|
1154
1300
|
request(method, path, body) {
|
|
1155
1301
|
return this.transport.json(method, path, body, adminStatusCodes);
|
|
1156
1302
|
}
|
|
1157
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
1158
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.
|
|
1303
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantAdminApiService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1304
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantAdminApiService }); }
|
|
1159
1305
|
}
|
|
1160
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
1306
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantAdminApiService, decorators: [{
|
|
1161
1307
|
type: Injectable
|
|
1162
1308
|
}] });
|
|
1163
1309
|
|
|
@@ -1289,6 +1435,52 @@ class NhAssistantApiService {
|
|
|
1289
1435
|
cancel(conversationId) {
|
|
1290
1436
|
return this.transport.json('POST', `conversations/${encodeURIComponent(conversationId)}/cancel`);
|
|
1291
1437
|
}
|
|
1438
|
+
/** Raises the caller's read position to `sequence`, or to the latest message when omitted. */
|
|
1439
|
+
markRead(conversationId, sequence) {
|
|
1440
|
+
return this.transport.json('POST', `conversations/${encodeURIComponent(conversationId)}/read`, sequence === undefined ? {} : { sequence });
|
|
1441
|
+
}
|
|
1442
|
+
/** Owner only: creates a new invitation link; an earlier link stops working. */
|
|
1443
|
+
createShareLink(conversationId) {
|
|
1444
|
+
return this.transport.json('POST', `conversations/${encodeURIComponent(conversationId)}/share-link`);
|
|
1445
|
+
}
|
|
1446
|
+
/** Owner only: the invitation link stops working; people who joined stay. */
|
|
1447
|
+
revokeShareLink(conversationId) {
|
|
1448
|
+
return this.transport.json('DELETE', `conversations/${encodeURIComponent(conversationId)}/share-link`);
|
|
1449
|
+
}
|
|
1450
|
+
/** Joins a shared conversation with the token of its invitation link. */
|
|
1451
|
+
joinConversation(conversationId, token) {
|
|
1452
|
+
return this.transport.json('POST', `conversations/${encodeURIComponent(conversationId)}/join`, { token });
|
|
1453
|
+
}
|
|
1454
|
+
/** Owner only: searches the application's directory for people to invite. */
|
|
1455
|
+
searchParticipantCandidates(conversationId, query) {
|
|
1456
|
+
const path = `conversations/${encodeURIComponent(conversationId)}/participant-candidates?query=${encodeURIComponent(query)}`;
|
|
1457
|
+
return this.transport.json('GET', path);
|
|
1458
|
+
}
|
|
1459
|
+
/** Owner only: invites a person the directory returned. */
|
|
1460
|
+
inviteParticipant(conversationId, actorId) {
|
|
1461
|
+
return this.transport.json('POST', `conversations/${encodeURIComponent(conversationId)}/participants`, { actorId });
|
|
1462
|
+
}
|
|
1463
|
+
/** The owner removes a participant, or a participant passes their own id to leave. */
|
|
1464
|
+
removeParticipant(conversationId, actorId) {
|
|
1465
|
+
const path = `conversations/${encodeURIComponent(conversationId)}/participants/${encodeURIComponent(actorId)}`;
|
|
1466
|
+
return this.transport.json('DELETE', path);
|
|
1467
|
+
}
|
|
1468
|
+
/** The caller's notification choice and the server's Web Push key. */
|
|
1469
|
+
getNotificationSettings() {
|
|
1470
|
+
return this.transport.json('GET', 'notifications');
|
|
1471
|
+
}
|
|
1472
|
+
/** Turns push notifications on or off for every browser of the caller. */
|
|
1473
|
+
updateNotificationSettings(pushEnabled) {
|
|
1474
|
+
return this.transport.json('PUT', 'notifications', { pushEnabled });
|
|
1475
|
+
}
|
|
1476
|
+
/** Registers this browser's push subscription for the caller. */
|
|
1477
|
+
subscribePush(subscription) {
|
|
1478
|
+
return this.transport.json('PUT', 'notifications/push-subscription', subscription);
|
|
1479
|
+
}
|
|
1480
|
+
/** Removes this browser's push subscription. */
|
|
1481
|
+
unsubscribePush(endpoint) {
|
|
1482
|
+
return this.transport.json('DELETE', 'notifications/push-subscription', { endpoint });
|
|
1483
|
+
}
|
|
1292
1484
|
/** The caller's own assistant preferences. */
|
|
1293
1485
|
getPreferences() {
|
|
1294
1486
|
return this.transport.json('GET', 'preferences');
|
|
@@ -1373,13 +1565,386 @@ class NhAssistantApiService {
|
|
|
1373
1565
|
subscriber.next({ type: 'error', data: { code, messageKey: nhAssistantErrorMessageKey(code) } });
|
|
1374
1566
|
subscriber.complete();
|
|
1375
1567
|
}
|
|
1376
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
1377
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.
|
|
1568
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantApiService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1569
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantApiService }); }
|
|
1378
1570
|
}
|
|
1379
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
1571
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantApiService, decorators: [{
|
|
1380
1572
|
type: Injectable
|
|
1381
1573
|
}] });
|
|
1382
1574
|
|
|
1575
|
+
const reconnectDelaysMs = [0, 2_000, 5_000, 10_000, 30_000];
|
|
1576
|
+
const restartDelayMs = 30_000;
|
|
1577
|
+
/**
|
|
1578
|
+
* Live updates of the assistant hub: conversation changes, read positions, removals and the
|
|
1579
|
+
* turn events of other sessions. Connects only while the store asks for it, reconnects on its
|
|
1580
|
+
* own and emits `resync` after every (re)connect, because updates sent while disconnected are lost.
|
|
1581
|
+
*/
|
|
1582
|
+
class NhAssistantLiveService {
|
|
1583
|
+
constructor() {
|
|
1584
|
+
this.config = inject(NH_ASSISTANT_CONFIG);
|
|
1585
|
+
this.injector = inject(EnvironmentInjector);
|
|
1586
|
+
this.browser = isPlatformBrowser(inject(PLATFORM_ID));
|
|
1587
|
+
this.changedSubject = new Subject();
|
|
1588
|
+
this.readSubject = new Subject();
|
|
1589
|
+
this.removedSubject = new Subject();
|
|
1590
|
+
this.eventSubject = new Subject();
|
|
1591
|
+
this.resyncSubject = new Subject();
|
|
1592
|
+
this.stateSignal = signal('off', ...(ngDevMode ? [{ debugName: "stateSignal" }] : []));
|
|
1593
|
+
this.hubPath = null;
|
|
1594
|
+
this.hubUrl = null;
|
|
1595
|
+
this.revision = 0;
|
|
1596
|
+
this.changed$ = this.changedSubject.asObservable();
|
|
1597
|
+
this.read$ = this.readSubject.asObservable();
|
|
1598
|
+
this.removed$ = this.removedSubject.asObservable();
|
|
1599
|
+
this.events$ = this.eventSubject.asObservable();
|
|
1600
|
+
/** Reload snapshots: emitted after every (re)connect. */
|
|
1601
|
+
this.resync$ = this.resyncSubject.asObservable();
|
|
1602
|
+
this.state = this.stateSignal.asReadonly();
|
|
1603
|
+
inject(DestroyRef).onDestroy(() => {
|
|
1604
|
+
void this.stop();
|
|
1605
|
+
this.changedSubject.complete();
|
|
1606
|
+
this.readSubject.complete();
|
|
1607
|
+
this.removedSubject.complete();
|
|
1608
|
+
this.eventSubject.complete();
|
|
1609
|
+
this.resyncSubject.complete();
|
|
1610
|
+
});
|
|
1611
|
+
}
|
|
1612
|
+
/**
|
|
1613
|
+
* Connects to the hub at `hubPath`, or disconnects when it is `null`. Calling it again with the
|
|
1614
|
+
* same path keeps the connection; another path or `restart` reconnects, for example after the
|
|
1615
|
+
* signed-in user changed.
|
|
1616
|
+
*/
|
|
1617
|
+
async connect(hubPath, restart = false) {
|
|
1618
|
+
const url = this.browser && this.config.liveUpdates !== false && hubPath ? this.resolveUrl(hubPath) : null;
|
|
1619
|
+
if (!restart && url === this.hubUrl && this.connection) {
|
|
1620
|
+
return;
|
|
1621
|
+
}
|
|
1622
|
+
await this.stop();
|
|
1623
|
+
this.hubPath = url ? hubPath : null;
|
|
1624
|
+
this.hubUrl = url;
|
|
1625
|
+
if (!url) {
|
|
1626
|
+
return;
|
|
1627
|
+
}
|
|
1628
|
+
const revision = ++this.revision;
|
|
1629
|
+
this.stateSignal.set('connecting');
|
|
1630
|
+
try {
|
|
1631
|
+
const connection = await this.build(url);
|
|
1632
|
+
if (revision !== this.revision) {
|
|
1633
|
+
await connection.stop().catch(() => undefined);
|
|
1634
|
+
return;
|
|
1635
|
+
}
|
|
1636
|
+
this.connection = connection;
|
|
1637
|
+
await connection.start();
|
|
1638
|
+
if (revision === this.revision) {
|
|
1639
|
+
this.stateSignal.set('connected');
|
|
1640
|
+
this.resyncSubject.next();
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
catch {
|
|
1644
|
+
// Live updates are optional; snapshots keep the assistant correct. Try again later.
|
|
1645
|
+
if (revision === this.revision) {
|
|
1646
|
+
this.stateSignal.set('disconnected');
|
|
1647
|
+
this.scheduleRestart(revision);
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
async stop() {
|
|
1652
|
+
this.revision++;
|
|
1653
|
+
this.hubUrl = null;
|
|
1654
|
+
clearTimeout(this.restartTimer);
|
|
1655
|
+
const connection = this.connection;
|
|
1656
|
+
this.connection = undefined;
|
|
1657
|
+
this.stateSignal.set('off');
|
|
1658
|
+
if (connection) {
|
|
1659
|
+
await connection.stop().catch(() => undefined);
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
async build(url) {
|
|
1663
|
+
const signalR = await import('@microsoft/signalr');
|
|
1664
|
+
const connection = new signalR.HubConnectionBuilder()
|
|
1665
|
+
.withUrl(url, {
|
|
1666
|
+
accessTokenFactory: async () => (await this.token()) ?? ''
|
|
1667
|
+
})
|
|
1668
|
+
.withAutomaticReconnect(reconnectDelaysMs)
|
|
1669
|
+
.configureLogging(signalR.LogLevel.None)
|
|
1670
|
+
.build();
|
|
1671
|
+
connection.on('ConversationChanged', (message) => this.changedSubject.next(message));
|
|
1672
|
+
connection.on('ConversationRead', (message) => this.readSubject.next(message));
|
|
1673
|
+
connection.on('ConversationRemoved', (message) => this.removedSubject.next(message));
|
|
1674
|
+
connection.on('ConversationEvent', (message) => this.eventSubject.next(message));
|
|
1675
|
+
connection.onreconnecting(() => this.stateSignal.set('reconnecting'));
|
|
1676
|
+
connection.onreconnected(() => {
|
|
1677
|
+
this.stateSignal.set('connected');
|
|
1678
|
+
this.resyncSubject.next();
|
|
1679
|
+
});
|
|
1680
|
+
connection.onclose(() => {
|
|
1681
|
+
if (this.connection === connection) {
|
|
1682
|
+
this.connection = undefined;
|
|
1683
|
+
this.stateSignal.set('disconnected');
|
|
1684
|
+
this.scheduleRestart(this.revision);
|
|
1685
|
+
}
|
|
1686
|
+
});
|
|
1687
|
+
return connection;
|
|
1688
|
+
}
|
|
1689
|
+
/** Starts over after the automatic reconnect gave up or the first start failed. */
|
|
1690
|
+
scheduleRestart(revision) {
|
|
1691
|
+
clearTimeout(this.restartTimer);
|
|
1692
|
+
this.restartTimer = setTimeout(() => {
|
|
1693
|
+
if (revision === this.revision && this.hubPath) {
|
|
1694
|
+
void this.connect(this.hubPath, true);
|
|
1695
|
+
}
|
|
1696
|
+
}, restartDelayMs);
|
|
1697
|
+
}
|
|
1698
|
+
async token() {
|
|
1699
|
+
try {
|
|
1700
|
+
return await runInInjectionContext(this.injector, () => this.config.getAccessToken());
|
|
1701
|
+
}
|
|
1702
|
+
catch {
|
|
1703
|
+
return null;
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
/** `hubBaseUrl` + `hubPath`, or the hub path on the origin of `apiBaseUrl`. */
|
|
1707
|
+
resolveUrl(hubPath) {
|
|
1708
|
+
const page = globalThis.location?.href ?? 'http://localhost';
|
|
1709
|
+
if (this.config.hubBaseUrl) {
|
|
1710
|
+
const base = new URL(this.config.hubBaseUrl, page).toString().replace(/\/+$/, '');
|
|
1711
|
+
return `${base}/${hubPath.replace(/^\/+/, '')}`;
|
|
1712
|
+
}
|
|
1713
|
+
return new URL(hubPath, new URL(this.config.apiBaseUrl, page).origin).toString();
|
|
1714
|
+
}
|
|
1715
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantLiveService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1716
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantLiveService }); }
|
|
1717
|
+
}
|
|
1718
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantLiveService, decorators: [{
|
|
1719
|
+
type: Injectable
|
|
1720
|
+
}], ctorParameters: () => [] });
|
|
1721
|
+
|
|
1722
|
+
const openConversationMessage = 'nh-assistant.open-conversation';
|
|
1723
|
+
/**
|
|
1724
|
+
* Keeps this browser's Web Push subscription in line with the user's choice. Notifications are on
|
|
1725
|
+
* by default; because browsers only ask for permission after a user action, the assistant asks
|
|
1726
|
+
* when the user sends a message or turns notifications on. The bundled service worker shows a
|
|
1727
|
+
* notification only when no window of the application is focused.
|
|
1728
|
+
*/
|
|
1729
|
+
class NhAssistantPushService {
|
|
1730
|
+
constructor() {
|
|
1731
|
+
this.api = inject(NhAssistantApiService);
|
|
1732
|
+
this.config = inject(NH_ASSISTANT_CONFIG);
|
|
1733
|
+
this.translate = inject(TranslateService, { optional: true });
|
|
1734
|
+
this.browser = isPlatformBrowser(inject(PLATFORM_ID));
|
|
1735
|
+
this.settingsState = signal(null, ...(ngDevMode ? [{ debugName: "settingsState" }] : []));
|
|
1736
|
+
this.permissionState = signal('default', ...(ngDevMode ? [{ debugName: "permissionState" }] : []));
|
|
1737
|
+
this.subscribedState = signal(false, ...(ngDevMode ? [{ debugName: "subscribedState" }] : []));
|
|
1738
|
+
this.busyState = signal(false, ...(ngDevMode ? [{ debugName: "busyState" }] : []));
|
|
1739
|
+
this.openSubject = new Subject();
|
|
1740
|
+
this.revision = 0;
|
|
1741
|
+
this.onWorkerMessage = (event) => {
|
|
1742
|
+
const data = event.data;
|
|
1743
|
+
if (data?.type === openConversationMessage && typeof data.conversationId === 'string') {
|
|
1744
|
+
this.openSubject.next(data.conversationId);
|
|
1745
|
+
}
|
|
1746
|
+
};
|
|
1747
|
+
/** The caller's server-side choice, or `null` before it was loaded. */
|
|
1748
|
+
this.settings = this.settingsState.asReadonly();
|
|
1749
|
+
this.busy = this.busyState.asReadonly();
|
|
1750
|
+
/** Conversations a clicked notification asks to open. */
|
|
1751
|
+
this.openConversation$ = this.openSubject.asObservable();
|
|
1752
|
+
this.state = computed(() => {
|
|
1753
|
+
const settings = this.settingsState();
|
|
1754
|
+
if (!settings?.pushAvailable || !this.isSupported()) {
|
|
1755
|
+
return 'unavailable';
|
|
1756
|
+
}
|
|
1757
|
+
if (!settings.pushEnabled) {
|
|
1758
|
+
return 'off';
|
|
1759
|
+
}
|
|
1760
|
+
const permission = this.permissionState();
|
|
1761
|
+
if (permission === 'denied' || permission === 'unsupported') {
|
|
1762
|
+
return 'blocked';
|
|
1763
|
+
}
|
|
1764
|
+
return permission === 'granted' && this.subscribedState() ? 'on' : 'prompt';
|
|
1765
|
+
}, ...(ngDevMode ? [{ debugName: "state" }] : []));
|
|
1766
|
+
inject(DestroyRef).onDestroy(() => {
|
|
1767
|
+
if (this.browser && this.isSupported()) {
|
|
1768
|
+
navigator.serviceWorker.removeEventListener('message', this.onWorkerMessage);
|
|
1769
|
+
}
|
|
1770
|
+
this.openSubject.complete();
|
|
1771
|
+
});
|
|
1772
|
+
if (this.browser && this.isSupported()) {
|
|
1773
|
+
navigator.serviceWorker.addEventListener('message', this.onWorkerMessage);
|
|
1774
|
+
navigator.serviceWorker.startMessages();
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
/**
|
|
1778
|
+
* Loads the user's choice and, when permission was granted before, renews this browser's
|
|
1779
|
+
* subscription, which also moves it to the current user and to a rotated server key.
|
|
1780
|
+
*/
|
|
1781
|
+
async activate(serverSupportsPush) {
|
|
1782
|
+
const revision = ++this.revision;
|
|
1783
|
+
if (!serverSupportsPush || !this.isSupported()) {
|
|
1784
|
+
this.settingsState.set(null);
|
|
1785
|
+
return;
|
|
1786
|
+
}
|
|
1787
|
+
try {
|
|
1788
|
+
const settings = await firstValueFrom(this.api.getNotificationSettings());
|
|
1789
|
+
if (revision !== this.revision) {
|
|
1790
|
+
return;
|
|
1791
|
+
}
|
|
1792
|
+
this.settingsState.set(settings);
|
|
1793
|
+
this.permissionState.set(Notification.permission);
|
|
1794
|
+
if (settings.pushEnabled && Notification.permission === 'granted') {
|
|
1795
|
+
await this.subscribe(settings);
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
catch {
|
|
1799
|
+
// Notifications are optional; the assistant works without them.
|
|
1800
|
+
if (revision === this.revision) {
|
|
1801
|
+
this.settingsState.set(null);
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1805
|
+
/**
|
|
1806
|
+
* Forgets the user. The browser subscription is cancelled, so a previous user on a shared device
|
|
1807
|
+
* stops receiving notifications even when the server cannot be told.
|
|
1808
|
+
*/
|
|
1809
|
+
async deactivate() {
|
|
1810
|
+
this.revision++;
|
|
1811
|
+
this.settingsState.set(null);
|
|
1812
|
+
this.subscribedState.set(false);
|
|
1813
|
+
if (!this.isSupported()) {
|
|
1814
|
+
return;
|
|
1815
|
+
}
|
|
1816
|
+
try {
|
|
1817
|
+
const registration = await navigator.serviceWorker.getRegistration(this.scope());
|
|
1818
|
+
const subscription = await registration?.pushManager.getSubscription();
|
|
1819
|
+
await subscription?.unsubscribe();
|
|
1820
|
+
}
|
|
1821
|
+
catch {
|
|
1822
|
+
// Nothing to cancel.
|
|
1823
|
+
}
|
|
1824
|
+
}
|
|
1825
|
+
/** Asks for permission once, after a user action such as sending a message. */
|
|
1826
|
+
async onUserGesture() {
|
|
1827
|
+
const settings = this.settingsState();
|
|
1828
|
+
if (!settings?.pushEnabled || !settings.pushAvailable || !this.isSupported() || Notification.permission !== 'default') {
|
|
1829
|
+
return;
|
|
1830
|
+
}
|
|
1831
|
+
const permission = await Notification.requestPermission();
|
|
1832
|
+
this.permissionState.set(permission);
|
|
1833
|
+
if (permission === 'granted') {
|
|
1834
|
+
await this.subscribe(settings);
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
/** Turns notifications on or off for every browser of the user. Call it from a user action. */
|
|
1838
|
+
async setEnabled(enabled) {
|
|
1839
|
+
this.busyState.set(true);
|
|
1840
|
+
try {
|
|
1841
|
+
const settings = await firstValueFrom(this.api.updateNotificationSettings(enabled));
|
|
1842
|
+
this.settingsState.set(settings);
|
|
1843
|
+
if (!this.isSupported()) {
|
|
1844
|
+
return;
|
|
1845
|
+
}
|
|
1846
|
+
if (enabled) {
|
|
1847
|
+
if (Notification.permission === 'default') {
|
|
1848
|
+
this.permissionState.set(await Notification.requestPermission());
|
|
1849
|
+
}
|
|
1850
|
+
if (Notification.permission === 'granted') {
|
|
1851
|
+
await this.subscribe(settings);
|
|
1852
|
+
}
|
|
1853
|
+
return;
|
|
1854
|
+
}
|
|
1855
|
+
await this.unsubscribe();
|
|
1856
|
+
}
|
|
1857
|
+
finally {
|
|
1858
|
+
this.busyState.set(false);
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1861
|
+
async subscribe(settings) {
|
|
1862
|
+
if (!settings.publicKey) {
|
|
1863
|
+
return;
|
|
1864
|
+
}
|
|
1865
|
+
const revision = this.revision;
|
|
1866
|
+
const registration = await this.register();
|
|
1867
|
+
const key = decodeBase64Url(settings.publicKey);
|
|
1868
|
+
let subscription = await registration.pushManager.getSubscription();
|
|
1869
|
+
if (subscription && !sameKey(subscription.options.applicationServerKey, key)) {
|
|
1870
|
+
// The server rotated its key; a subscription for the old key would be refused.
|
|
1871
|
+
await subscription.unsubscribe();
|
|
1872
|
+
subscription = null;
|
|
1873
|
+
}
|
|
1874
|
+
subscription ??= await registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: key });
|
|
1875
|
+
const json = subscription.toJSON();
|
|
1876
|
+
if (!json.endpoint || !json.keys?.['p256dh'] || !json.keys['auth'] || revision !== this.revision) {
|
|
1877
|
+
return;
|
|
1878
|
+
}
|
|
1879
|
+
await firstValueFrom(this.api.subscribePush({
|
|
1880
|
+
endpoint: json.endpoint,
|
|
1881
|
+
keys: { p256dh: json.keys['p256dh'], auth: json.keys['auth'] },
|
|
1882
|
+
language: this.translate?.getCurrentLang() ?? navigator.language
|
|
1883
|
+
}), { defaultValue: undefined });
|
|
1884
|
+
if (revision === this.revision) {
|
|
1885
|
+
this.subscribedState.set(true);
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
async unsubscribe() {
|
|
1889
|
+
this.subscribedState.set(false);
|
|
1890
|
+
const registration = await navigator.serviceWorker.getRegistration(this.scope());
|
|
1891
|
+
const subscription = await registration?.pushManager.getSubscription();
|
|
1892
|
+
if (!subscription) {
|
|
1893
|
+
return;
|
|
1894
|
+
}
|
|
1895
|
+
await firstValueFrom(this.api.unsubscribePush(subscription.endpoint), { defaultValue: undefined }).catch(() => undefined);
|
|
1896
|
+
await subscription.unsubscribe();
|
|
1897
|
+
}
|
|
1898
|
+
register() {
|
|
1899
|
+
const push = this.config.push;
|
|
1900
|
+
this.registration ??= (async () => {
|
|
1901
|
+
const url = new URL(push.serviceWorkerUrl, location.href);
|
|
1902
|
+
url.searchParams.set('app', new URL(push.openUrl ?? '/', location.href).href);
|
|
1903
|
+
if (push.icon) {
|
|
1904
|
+
url.searchParams.set('icon', new URL(push.icon, location.href).href);
|
|
1905
|
+
}
|
|
1906
|
+
return navigator.serviceWorker.register(url.href, { scope: this.scope() });
|
|
1907
|
+
})();
|
|
1908
|
+
return this.registration;
|
|
1909
|
+
}
|
|
1910
|
+
scope() {
|
|
1911
|
+
const push = this.config.push;
|
|
1912
|
+
if (!push) {
|
|
1913
|
+
return '/';
|
|
1914
|
+
}
|
|
1915
|
+
return push.scope ?? new URL('./', new URL(push.serviceWorkerUrl, location.href)).pathname;
|
|
1916
|
+
}
|
|
1917
|
+
isSupported() {
|
|
1918
|
+
return this.browser
|
|
1919
|
+
&& !!this.config.push?.serviceWorkerUrl
|
|
1920
|
+
&& globalThis.isSecureContext === true
|
|
1921
|
+
&& 'serviceWorker' in navigator
|
|
1922
|
+
&& 'PushManager' in globalThis
|
|
1923
|
+
&& 'Notification' in globalThis;
|
|
1924
|
+
}
|
|
1925
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantPushService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1926
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantPushService }); }
|
|
1927
|
+
}
|
|
1928
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantPushService, decorators: [{
|
|
1929
|
+
type: Injectable
|
|
1930
|
+
}], ctorParameters: () => [] });
|
|
1931
|
+
function decodeBase64Url(value) {
|
|
1932
|
+
const base64 = value.replace(/-/g, '+').replace(/_/g, '/').padEnd(Math.ceil(value.length / 4) * 4, '=');
|
|
1933
|
+
const binary = atob(base64);
|
|
1934
|
+
const bytes = new Uint8Array(new ArrayBuffer(binary.length));
|
|
1935
|
+
for (let index = 0; index < binary.length; index++) {
|
|
1936
|
+
bytes[index] = binary.charCodeAt(index);
|
|
1937
|
+
}
|
|
1938
|
+
return bytes;
|
|
1939
|
+
}
|
|
1940
|
+
function sameKey(current, expected) {
|
|
1941
|
+
if (!current) {
|
|
1942
|
+
return false;
|
|
1943
|
+
}
|
|
1944
|
+
const bytes = new Uint8Array(current);
|
|
1945
|
+
return bytes.length === expected.length && bytes.every((byte, index) => byte === expected[index]);
|
|
1946
|
+
}
|
|
1947
|
+
|
|
1383
1948
|
/**
|
|
1384
1949
|
* Applies one assistant event to a conversation and returns the new conversation.
|
|
1385
1950
|
* The input is never mutated, so the result can be stored in a signal directly.
|
|
@@ -1491,6 +2056,36 @@ function applyNhAssistantApprovalDecision(conversation, approvalId, decision) {
|
|
|
1491
2056
|
}
|
|
1492
2057
|
return updated;
|
|
1493
2058
|
}
|
|
2059
|
+
/**
|
|
2060
|
+
* Applies a live update of another session: `message.created` adds a participant's message,
|
|
2061
|
+
* the turn events behave exactly like the starting request's stream. Unknown types are ignored.
|
|
2062
|
+
*/
|
|
2063
|
+
function applyNhAssistantLiveEvent(conversation, event) {
|
|
2064
|
+
if (event.type === 'message.created') {
|
|
2065
|
+
const message = event.data;
|
|
2066
|
+
if (!message?.id || conversation.messages.some(existing => existing.id === message.id)) {
|
|
2067
|
+
return conversation;
|
|
2068
|
+
}
|
|
2069
|
+
return { ...conversation, messages: [...conversation.messages, message] };
|
|
2070
|
+
}
|
|
2071
|
+
if (!NH_ASSISTANT_SSE_EVENT_TYPES.includes(event.type)) {
|
|
2072
|
+
return conversation;
|
|
2073
|
+
}
|
|
2074
|
+
return applyNhAssistantEvent(conversation, { type: event.type, data: event.data });
|
|
2075
|
+
}
|
|
2076
|
+
/** Applies the shared state of a conversation that a live update announced. */
|
|
2077
|
+
function applyNhAssistantConversationChange(conversation, change) {
|
|
2078
|
+
return {
|
|
2079
|
+
...conversation,
|
|
2080
|
+
status: change.status,
|
|
2081
|
+
title: change.title,
|
|
2082
|
+
updatedAt: change.updatedAt,
|
|
2083
|
+
activeActorId: change.activeActorId,
|
|
2084
|
+
lastMessageSequence: change.lastMessageSequence,
|
|
2085
|
+
participantCount: change.participantCount,
|
|
2086
|
+
pendingApproval: change.status === 'waiting-for-approval' ? conversation.pendingApproval : null
|
|
2087
|
+
};
|
|
2088
|
+
}
|
|
1494
2089
|
/**
|
|
1495
2090
|
* True when an assistant message after the latest user message contains non-blank text,
|
|
1496
2091
|
* so the latest turn produced a visible answer. Tool calls and approvals alone do not count.
|
|
@@ -1752,10 +2347,10 @@ class NhAssistantUiState {
|
|
|
1752
2347
|
return { ...emptyState };
|
|
1753
2348
|
}
|
|
1754
2349
|
}
|
|
1755
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
1756
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.
|
|
2350
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantUiState, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
2351
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantUiState }); }
|
|
1757
2352
|
}
|
|
1758
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
2353
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantUiState, decorators: [{
|
|
1759
2354
|
type: Injectable
|
|
1760
2355
|
}] });
|
|
1761
2356
|
function validId(value) {
|
|
@@ -1764,11 +2359,23 @@ function validId(value) {
|
|
|
1764
2359
|
|
|
1765
2360
|
/** Notice code for a completed turn that produced no answer text and no server code. */
|
|
1766
2361
|
const NH_ASSISTANT_NO_ANSWER_NOTICE_CODE = 'assistant-no-answer';
|
|
2362
|
+
/** True when a conversation has messages the caller has not read. A running turn is not unread yet. */
|
|
2363
|
+
function nhAssistantIsUnread(summary) {
|
|
2364
|
+
if (summary.status === 'running' || summary.lastReadSequence === undefined) {
|
|
2365
|
+
return false;
|
|
2366
|
+
}
|
|
2367
|
+
return (summary.lastMessageSequence ?? 0) > summary.lastReadSequence;
|
|
2368
|
+
}
|
|
1767
2369
|
const conversationPageSize = 50;
|
|
1768
2370
|
const cancelGracePeriodMs = 5_000;
|
|
2371
|
+
const maxSessions = 20;
|
|
2372
|
+
const refreshDelayMs = 300;
|
|
2373
|
+
const newConversationKey = '';
|
|
1769
2374
|
/**
|
|
1770
|
-
* Signal-based state of one assistant scope: availability, agents, the conversation list
|
|
1771
|
-
*
|
|
2375
|
+
* Signal-based state of one assistant scope: availability, agents, the conversation list and
|
|
2376
|
+
* every conversation this tab has opened. Several conversations can run turns at the same time;
|
|
2377
|
+
* the public signals describe the conversation that is open in the panel. With live updates the
|
|
2378
|
+
* store follows turns of other people and of the user's other tabs.
|
|
1772
2379
|
*/
|
|
1773
2380
|
class NhAssistantStore {
|
|
1774
2381
|
constructor() {
|
|
@@ -1777,6 +2384,10 @@ class NhAssistantStore {
|
|
|
1777
2384
|
this.injector = inject(EnvironmentInjector);
|
|
1778
2385
|
this.accessPolicy = inject(NH_ASSISTANT_ACCESS_POLICY);
|
|
1779
2386
|
this.uiState = inject(NhAssistantUiState);
|
|
2387
|
+
this.live = inject(NhAssistantLiveService);
|
|
2388
|
+
this.push = inject(NhAssistantPushService);
|
|
2389
|
+
this.document = inject(DOCUMENT);
|
|
2390
|
+
this.browser = isPlatformBrowser(inject(PLATFORM_ID));
|
|
1780
2391
|
this.accessGrantedState = signal(null, ...(ngDevMode ? [{ debugName: "accessGrantedState" }] : []));
|
|
1781
2392
|
this.statusState = signal(null, ...(ngDevMode ? [{ debugName: "statusState" }] : []));
|
|
1782
2393
|
this.statusLoadingState = signal(false, ...(ngDevMode ? [{ debugName: "statusLoadingState" }] : []));
|
|
@@ -1784,19 +2395,30 @@ class NhAssistantStore {
|
|
|
1784
2395
|
this.conversationsState = signal([], ...(ngDevMode ? [{ debugName: "conversationsState" }] : []));
|
|
1785
2396
|
this.conversationsTotalState = signal(0, ...(ngDevMode ? [{ debugName: "conversationsTotalState" }] : []));
|
|
1786
2397
|
this.conversationsLoadingState = signal(false, ...(ngDevMode ? [{ debugName: "conversationsLoadingState" }] : []));
|
|
1787
|
-
this.
|
|
2398
|
+
this.sessionsState = signal(new Map(), ...(ngDevMode ? [{ debugName: "sessionsState" }] : []));
|
|
2399
|
+
this.activeIdState = signal(null, ...(ngDevMode ? [{ debugName: "activeIdState" }] : []));
|
|
1788
2400
|
this.conversationLoadingState = signal(false, ...(ngDevMode ? [{ debugName: "conversationLoadingState" }] : []));
|
|
1789
|
-
|
|
1790
|
-
this.
|
|
1791
|
-
this.
|
|
1792
|
-
this.noticeState = signal(null, ...(ngDevMode ? [{ debugName: "noticeState" }] : []));
|
|
1793
|
-
this.lastUsageState = signal(null, ...(ngDevMode ? [{ debugName: "lastUsageState" }] : []));
|
|
2401
|
+
/** The conversation a message is being prepared for; `''` while a new conversation is created. */
|
|
2402
|
+
this.sendingState = signal(null, ...(ngDevMode ? [{ debugName: "sendingState" }] : []));
|
|
2403
|
+
this.draftErrorState = signal(null, ...(ngDevMode ? [{ debugName: "draftErrorState" }] : []));
|
|
1794
2404
|
this.restoredDraftState = signal(null, ...(ngDevMode ? [{ debugName: "restoredDraftState" }] : []));
|
|
1795
2405
|
this.pageContextState = signal(null, ...(ngDevMode ? [{ debugName: "pageContextState" }] : []));
|
|
1796
2406
|
this.pageContextExcludedState = signal(false, ...(ngDevMode ? [{ debugName: "pageContextExcludedState" }] : []));
|
|
1797
2407
|
this.restorePanelOpenState = signal(false, ...(ngDevMode ? [{ debugName: "restorePanelOpenState" }] : []));
|
|
2408
|
+
this.viewingState = signal(false, ...(ngDevMode ? [{ debugName: "viewingState" }] : []));
|
|
2409
|
+
this.pageVisibleState = signal(true, ...(ngDevMode ? [{ debugName: "pageVisibleState" }] : []));
|
|
2410
|
+
this.streams = new Map();
|
|
2411
|
+
this.cancelTimers = new Map();
|
|
2412
|
+
this.pendingSendResolves = new Map();
|
|
2413
|
+
this.markingRead = new Set();
|
|
2414
|
+
this.liveSubscription = new Subscription();
|
|
1798
2415
|
this.statusRevision = 0;
|
|
1799
2416
|
this.accountRevision = 0;
|
|
2417
|
+
this.collaborationKey = null;
|
|
2418
|
+
this.activeSession = computed(() => {
|
|
2419
|
+
const id = this.activeIdState();
|
|
2420
|
+
return id === null ? null : this.sessionsState().get(id) ?? null;
|
|
2421
|
+
}, ...(ngDevMode ? [{ debugName: "activeSession" }] : []));
|
|
1800
2422
|
/** True when the access policy allows the user and the server reports the assistant as enabled. */
|
|
1801
2423
|
this.enabled = computed(() => this.accessGrantedState() === true && this.statusState()?.enabled === true, ...(ngDevMode ? [{ debugName: "enabled" }] : []));
|
|
1802
2424
|
this.accessGranted = this.accessGrantedState.asReadonly();
|
|
@@ -1805,20 +2427,28 @@ class NhAssistantStore {
|
|
|
1805
2427
|
this.statusLoading = this.statusLoadingState.asReadonly();
|
|
1806
2428
|
this.limits = computed(() => this.statusState()?.limits ?? null, ...(ngDevMode ? [{ debugName: "limits" }] : []));
|
|
1807
2429
|
this.agents = computed(() => this.statusState()?.agents ?? [], ...(ngDevMode ? [{ debugName: "agents" }] : []));
|
|
2430
|
+
/** Sharing, live update and notification features the server offers. */
|
|
2431
|
+
this.collaboration = computed(() => this.statusState()?.collaboration ?? null, ...(ngDevMode ? [{ debugName: "collaboration" }] : []));
|
|
1808
2432
|
this.selectedAgentId = this.selectedAgentIdState.asReadonly();
|
|
1809
2433
|
this.selectedAgent = computed(() => this.agents().find(agent => agent.id === this.selectedAgentIdState()) ?? null, ...(ngDevMode ? [{ debugName: "selectedAgent" }] : []));
|
|
1810
2434
|
this.conversations = this.conversationsState.asReadonly();
|
|
1811
2435
|
this.conversationsTotal = this.conversationsTotalState.asReadonly();
|
|
1812
2436
|
this.conversationsLoading = this.conversationsLoadingState.asReadonly();
|
|
1813
|
-
|
|
2437
|
+
/** The conversation open in the panel, or `null` for a new one. */
|
|
2438
|
+
this.activeConversation = computed(() => this.activeSession()?.conversation ?? null, ...(ngDevMode ? [{ debugName: "activeConversation" }] : []));
|
|
1814
2439
|
this.conversationLoading = this.conversationLoadingState.asReadonly();
|
|
1815
|
-
this
|
|
1816
|
-
this.
|
|
1817
|
-
|
|
1818
|
-
|
|
2440
|
+
/** True while this tab sends a message or streams a turn of the open conversation. */
|
|
2441
|
+
this.streaming = computed(() => {
|
|
2442
|
+
const sending = this.sendingState();
|
|
2443
|
+
const activeId = this.activeIdState();
|
|
2444
|
+
return (sending !== null && sending === (activeId ?? newConversationKey)) || this.activeSession()?.streaming === true;
|
|
2445
|
+
}, ...(ngDevMode ? [{ debugName: "streaming" }] : []));
|
|
2446
|
+
this.pendingApproval = computed(() => this.activeConversation()?.pendingApproval ?? null, ...(ngDevMode ? [{ debugName: "pendingApproval" }] : []));
|
|
2447
|
+
this.deciding = computed(() => this.activeSession()?.deciding === true, ...(ngDevMode ? [{ debugName: "deciding" }] : []));
|
|
2448
|
+
this.error = computed(() => this.activeIdState() === null ? this.draftErrorState() : this.activeSession()?.error ?? null, ...(ngDevMode ? [{ debugName: "error" }] : []));
|
|
1819
2449
|
/** Non-blocking remark about the latest completed turn; shown only while no error is shown. */
|
|
1820
|
-
this.notice = this.
|
|
1821
|
-
this.lastUsage = this.
|
|
2450
|
+
this.notice = computed(() => this.activeSession()?.notice ?? null, ...(ngDevMode ? [{ debugName: "notice" }] : []));
|
|
2451
|
+
this.lastUsage = computed(() => this.activeSession()?.lastUsage ?? null, ...(ngDevMode ? [{ debugName: "lastUsage" }] : []));
|
|
1822
2452
|
/** Text of a message the server did not accept, so the composer can offer it again. */
|
|
1823
2453
|
this.restoredDraft = this.restoredDraftState.asReadonly();
|
|
1824
2454
|
/** Page context that the next message would send, for the transparency chip. */
|
|
@@ -1829,14 +2459,94 @@ class NhAssistantStore {
|
|
|
1829
2459
|
this.restorePanelOpen = this.restorePanelOpenState.asReadonly();
|
|
1830
2460
|
/** True while the user can send: enabled, an agent is chosen and no turn runs or waits. */
|
|
1831
2461
|
this.canSend = computed(() => {
|
|
1832
|
-
const conversation = this.
|
|
2462
|
+
const conversation = this.activeConversation();
|
|
1833
2463
|
const blocked = conversation !== null && conversation.status !== 'idle';
|
|
1834
|
-
return this.enabled() && this.selectedAgentIdState() !== null && !this.
|
|
2464
|
+
return this.enabled() && this.selectedAgentIdState() !== null && !this.streaming() && !this.conversationLoadingState() && !blocked;
|
|
1835
2465
|
}, ...(ngDevMode ? [{ debugName: "canSend" }] : []));
|
|
1836
|
-
|
|
2466
|
+
/** The owner and participants of the open conversation; empty while it is not shared. */
|
|
2467
|
+
this.members = computed(() => this.activeConversation()?.members ?? [], ...(ngDevMode ? [{ debugName: "members" }] : []));
|
|
2468
|
+
/** True when the current user owns the open conversation (or it is new). */
|
|
2469
|
+
this.isOwner = computed(() => (this.activeConversation()?.role ?? 'owner') === 'owner', ...(ngDevMode ? [{ debugName: "isOwner" }] : []));
|
|
2470
|
+
/** True when the current user may decide the pending approval: it belongs to their own turn. */
|
|
2471
|
+
this.canDecide = computed(() => {
|
|
2472
|
+
const conversation = this.activeConversation();
|
|
2473
|
+
if (!conversation?.pendingApproval) {
|
|
2474
|
+
return false;
|
|
2475
|
+
}
|
|
2476
|
+
return !conversation.activeActorId || !conversation.currentActorId || conversation.activeActorId === conversation.currentActorId;
|
|
2477
|
+
}, ...(ngDevMode ? [{ debugName: "canDecide" }] : []));
|
|
2478
|
+
/** The member whose turn runs or waits in the open conversation, when it is someone else. */
|
|
2479
|
+
this.activeMember = computed(() => {
|
|
2480
|
+
const conversation = this.activeConversation();
|
|
2481
|
+
if (!conversation?.activeActorId || conversation.activeActorId === conversation.currentActorId) {
|
|
2482
|
+
return null;
|
|
2483
|
+
}
|
|
2484
|
+
return conversation.members?.find(member => member.actorId === conversation.activeActorId) ?? null;
|
|
2485
|
+
}, ...(ngDevMode ? [{ debugName: "activeMember" }] : []));
|
|
2486
|
+
/** Conversations that need attention: running, waiting for approval or unread. Most recent first. */
|
|
2487
|
+
this.activity = computed(() => {
|
|
2488
|
+
const sessions = this.sessionsState();
|
|
2489
|
+
const activeId = this.activeIdState();
|
|
2490
|
+
return this.conversationsState()
|
|
2491
|
+
.map(summary => {
|
|
2492
|
+
const session = sessions.get(summary.id);
|
|
2493
|
+
const status = session?.streaming ? session.conversation.status : summary.status;
|
|
2494
|
+
return {
|
|
2495
|
+
id: summary.id,
|
|
2496
|
+
title: summary.title,
|
|
2497
|
+
status,
|
|
2498
|
+
unread: nhAssistantIsUnread({ ...summary, status }),
|
|
2499
|
+
shared: (summary.participantCount ?? 0) > 0,
|
|
2500
|
+
active: summary.id === activeId,
|
|
2501
|
+
updatedAt: summary.updatedAt
|
|
2502
|
+
};
|
|
2503
|
+
})
|
|
2504
|
+
.filter(item => item.status === 'running' || item.status === 'waiting-for-approval' || item.unread);
|
|
2505
|
+
}, ...(ngDevMode ? [{ debugName: "activity" }] : []));
|
|
2506
|
+
this.runningCount = computed(() => this.activity().filter(item => item.status === 'running').length, ...(ngDevMode ? [{ debugName: "runningCount" }] : []));
|
|
2507
|
+
this.waitingCount = computed(() => this.activity().filter(item => item.status === 'waiting-for-approval').length, ...(ngDevMode ? [{ debugName: "waitingCount" }] : []));
|
|
2508
|
+
this.unreadCount = computed(() => this.activity().filter(item => item.unread).length, ...(ngDevMode ? [{ debugName: "unreadCount" }] : []));
|
|
2509
|
+
/** Live update connection state. */
|
|
2510
|
+
this.liveState = this.live.state;
|
|
2511
|
+
const destroyRef = inject(DestroyRef);
|
|
2512
|
+
destroyRef.onDestroy(() => {
|
|
1837
2513
|
this.accessSubscription?.unsubscribe();
|
|
1838
|
-
this.
|
|
1839
|
-
|
|
2514
|
+
this.liveSubscription.unsubscribe();
|
|
2515
|
+
this.stopAllTurns();
|
|
2516
|
+
clearTimeout(this.refreshTimer);
|
|
2517
|
+
});
|
|
2518
|
+
this.liveSubscription.add(this.live.changed$.subscribe(change => this.onLiveChanged(change)));
|
|
2519
|
+
this.liveSubscription.add(this.live.read$.subscribe(read => this.onLiveRead(read)));
|
|
2520
|
+
this.liveSubscription.add(this.live.removed$.subscribe(removed => this.onLiveRemoved(removed)));
|
|
2521
|
+
this.liveSubscription.add(this.live.events$.subscribe(event => this.onLiveEvent(event)));
|
|
2522
|
+
this.liveSubscription.add(this.live.resync$.subscribe(() => this.resync()));
|
|
2523
|
+
if (this.browser) {
|
|
2524
|
+
const update = () => this.pageVisibleState.set(this.document.visibilityState === 'visible' && this.document.hasFocus());
|
|
2525
|
+
const visible = () => {
|
|
2526
|
+
update();
|
|
2527
|
+
if (this.document.visibilityState === 'visible' && this.enabled()) {
|
|
2528
|
+
// Without live updates, or on another instance, coming back reloads the list.
|
|
2529
|
+
void this.refreshConversations();
|
|
2530
|
+
}
|
|
2531
|
+
};
|
|
2532
|
+
this.document.addEventListener('visibilitychange', visible);
|
|
2533
|
+
globalThis.addEventListener?.('focus', update);
|
|
2534
|
+
globalThis.addEventListener?.('blur', update);
|
|
2535
|
+
destroyRef.onDestroy(() => {
|
|
2536
|
+
this.document.removeEventListener('visibilitychange', visible);
|
|
2537
|
+
globalThis.removeEventListener?.('focus', update);
|
|
2538
|
+
globalThis.removeEventListener?.('blur', update);
|
|
2539
|
+
});
|
|
2540
|
+
update();
|
|
2541
|
+
}
|
|
2542
|
+
// Marks the open conversation as read while the user looks at it.
|
|
2543
|
+
effect(() => {
|
|
2544
|
+
this.activeIdState();
|
|
2545
|
+
this.viewingState();
|
|
2546
|
+
this.pageVisibleState();
|
|
2547
|
+
this.conversationsState();
|
|
2548
|
+
this.sessionsState();
|
|
2549
|
+
untracked(() => this.markActiveReadWhenSeen());
|
|
1840
2550
|
});
|
|
1841
2551
|
}
|
|
1842
2552
|
/** Evaluates the access policy and loads the server status once. Safe to call repeatedly. */
|
|
@@ -1848,21 +2558,9 @@ class NhAssistantStore {
|
|
|
1848
2558
|
async reloadStatus() {
|
|
1849
2559
|
const revision = ++this.statusRevision;
|
|
1850
2560
|
if (this.accessGrantedState() !== true) {
|
|
1851
|
-
this.
|
|
1852
|
-
this.stopLocalTurn();
|
|
2561
|
+
this.resetAccount();
|
|
1853
2562
|
this.statusState.set(null);
|
|
1854
|
-
this.activeConversationState.set(null);
|
|
1855
2563
|
this.selectedAgentIdState.set(null);
|
|
1856
|
-
this.conversationsState.set([]);
|
|
1857
|
-
this.conversationsTotalState.set(0);
|
|
1858
|
-
this.conversationLoadingState.set(false);
|
|
1859
|
-
this.conversationsLoadingState.set(false);
|
|
1860
|
-
this.pageContextState.set(null);
|
|
1861
|
-
this.pageContextExcludedState.set(false);
|
|
1862
|
-
this.restoredDraftState.set(null);
|
|
1863
|
-
this.errorState.set(null);
|
|
1864
|
-
this.noticeState.set(null);
|
|
1865
|
-
this.lastUsageState.set(null);
|
|
1866
2564
|
this.restorePanelOpenState.set(false);
|
|
1867
2565
|
this.uiState.deactivate();
|
|
1868
2566
|
return;
|
|
@@ -1874,20 +2572,8 @@ class NhAssistantStore {
|
|
|
1874
2572
|
return;
|
|
1875
2573
|
}
|
|
1876
2574
|
if (restored.changed) {
|
|
1877
|
-
this.
|
|
1878
|
-
this.stopLocalTurn();
|
|
2575
|
+
this.resetAccount();
|
|
1879
2576
|
this.statusState.set(null);
|
|
1880
|
-
this.activeConversationState.set(null);
|
|
1881
|
-
this.conversationsState.set([]);
|
|
1882
|
-
this.conversationsTotalState.set(0);
|
|
1883
|
-
this.conversationLoadingState.set(false);
|
|
1884
|
-
this.conversationsLoadingState.set(false);
|
|
1885
|
-
this.pageContextState.set(null);
|
|
1886
|
-
this.pageContextExcludedState.set(false);
|
|
1887
|
-
this.restoredDraftState.set(null);
|
|
1888
|
-
this.errorState.set(null);
|
|
1889
|
-
this.noticeState.set(null);
|
|
1890
|
-
this.lastUsageState.set(null);
|
|
1891
2577
|
this.selectedAgentIdState.set(restored.state.agentId);
|
|
1892
2578
|
this.restorePanelOpenState.set(restored.state.panelOpen);
|
|
1893
2579
|
}
|
|
@@ -1898,7 +2584,8 @@ class NhAssistantStore {
|
|
|
1898
2584
|
this.statusState.set(status);
|
|
1899
2585
|
if (status.enabled) {
|
|
1900
2586
|
this.selectInitialAgent(status.agents);
|
|
1901
|
-
|
|
2587
|
+
this.startCollaboration(status);
|
|
2588
|
+
if (this.activeIdState() === null && restored.state.conversationId) {
|
|
1902
2589
|
await this.loadConversation(restored.state.conversationId, true);
|
|
1903
2590
|
}
|
|
1904
2591
|
}
|
|
@@ -1924,13 +2611,13 @@ class NhAssistantStore {
|
|
|
1924
2611
|
try {
|
|
1925
2612
|
const page = await firstValueFrom(this.api.listConversations(1, conversationPageSize));
|
|
1926
2613
|
if (revision === this.accountRevision) {
|
|
1927
|
-
this.conversationsState.set(page.items);
|
|
2614
|
+
this.conversationsState.set(page.items.map(item => this.withLocalState(item)));
|
|
1928
2615
|
this.conversationsTotalState.set(page.total);
|
|
1929
2616
|
}
|
|
1930
2617
|
}
|
|
1931
2618
|
catch (error) {
|
|
1932
2619
|
if (revision === this.accountRevision) {
|
|
1933
|
-
this.setError(error);
|
|
2620
|
+
this.setError(this.activeIdState(), error);
|
|
1934
2621
|
}
|
|
1935
2622
|
}
|
|
1936
2623
|
finally {
|
|
@@ -1939,72 +2626,37 @@ class NhAssistantStore {
|
|
|
1939
2626
|
}
|
|
1940
2627
|
}
|
|
1941
2628
|
}
|
|
1942
|
-
/** Chooses the agent for the next new conversation. Switching away from the
|
|
2629
|
+
/** Chooses the agent for the next new conversation. Switching away from the open conversation's agent starts a new one. */
|
|
1943
2630
|
selectAgent(agentId) {
|
|
1944
2631
|
if (!this.agents().some(agent => agent.id === agentId)) {
|
|
1945
2632
|
return;
|
|
1946
2633
|
}
|
|
1947
2634
|
this.selectedAgentIdState.set(agentId);
|
|
1948
2635
|
this.uiState.update({ agentId });
|
|
1949
|
-
const active = this.
|
|
1950
|
-
if (active && active.agentId !== agentId
|
|
2636
|
+
const active = this.activeConversation();
|
|
2637
|
+
if (active && active.agentId !== agentId) {
|
|
1951
2638
|
this.startNewConversation();
|
|
1952
2639
|
}
|
|
1953
2640
|
}
|
|
1954
|
-
/**
|
|
2641
|
+
/**
|
|
2642
|
+
* Leaves the open conversation; the next message creates a new one. Turns of other
|
|
2643
|
+
* conversations keep running in the background.
|
|
2644
|
+
*/
|
|
1955
2645
|
startNewConversation() {
|
|
1956
|
-
|
|
1957
|
-
return;
|
|
1958
|
-
}
|
|
1959
|
-
this.activeConversationState.set(null);
|
|
2646
|
+
this.activeIdState.set(null);
|
|
1960
2647
|
this.uiState.update({ conversationId: null });
|
|
1961
|
-
this.
|
|
1962
|
-
this.noticeState.set(null);
|
|
1963
|
-
this.lastUsageState.set(null);
|
|
2648
|
+
this.draftErrorState.set(null);
|
|
1964
2649
|
}
|
|
2650
|
+
/** Opens a conversation; one that this tab already follows opens immediately. */
|
|
1965
2651
|
async openConversation(conversationId) {
|
|
1966
2652
|
await this.loadConversation(conversationId, false);
|
|
1967
2653
|
}
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
}
|
|
1972
|
-
const revision = this.accountRevision;
|
|
1973
|
-
this.conversationLoadingState.set(true);
|
|
1974
|
-
this.errorState.set(null);
|
|
1975
|
-
this.noticeState.set(null);
|
|
1976
|
-
try {
|
|
1977
|
-
const conversation = await firstValueFrom(this.api.getConversation(conversationId));
|
|
1978
|
-
if (revision !== this.accountRevision) {
|
|
1979
|
-
return;
|
|
1980
|
-
}
|
|
1981
|
-
if (!this.agents().some(agent => agent.id === conversation.agentId)) {
|
|
1982
|
-
this.uiState.update({ conversationId: null });
|
|
1983
|
-
return;
|
|
1984
|
-
}
|
|
1985
|
-
this.activeConversationState.set(conversation);
|
|
1986
|
-
this.selectedAgentIdState.set(conversation.agentId);
|
|
1987
|
-
this.uiState.update({ conversationId, agentId: conversation.agentId });
|
|
1988
|
-
}
|
|
1989
|
-
catch (error) {
|
|
1990
|
-
if (revision !== this.accountRevision) {
|
|
1991
|
-
return;
|
|
1992
|
-
}
|
|
1993
|
-
if (error instanceof NhAssistantApiError && (error.status === 403 || error.status === 404)) {
|
|
1994
|
-
this.uiState.update({ conversationId: null });
|
|
1995
|
-
}
|
|
1996
|
-
if (!restoring) {
|
|
1997
|
-
this.setError(error);
|
|
1998
|
-
}
|
|
1999
|
-
}
|
|
2000
|
-
finally {
|
|
2001
|
-
if (revision === this.accountRevision) {
|
|
2002
|
-
this.conversationLoadingState.set(false);
|
|
2003
|
-
}
|
|
2004
|
-
}
|
|
2654
|
+
/** Tells the store whether the user can see the open thread, so it can mark it as read. */
|
|
2655
|
+
setViewing(viewing) {
|
|
2656
|
+
this.viewingState.set(viewing);
|
|
2005
2657
|
}
|
|
2006
2658
|
async deleteConversation(conversationId) {
|
|
2007
|
-
if (this.
|
|
2659
|
+
if (this.sessionsState().get(conversationId)?.streaming) {
|
|
2008
2660
|
return;
|
|
2009
2661
|
}
|
|
2010
2662
|
const revision = this.accountRevision;
|
|
@@ -2013,26 +2665,22 @@ class NhAssistantStore {
|
|
|
2013
2665
|
if (revision !== this.accountRevision) {
|
|
2014
2666
|
return;
|
|
2015
2667
|
}
|
|
2016
|
-
this.
|
|
2017
|
-
this.conversationsTotalState.update(total => Math.max(0, total - 1));
|
|
2018
|
-
if (this.activeConversationState()?.id === conversationId) {
|
|
2019
|
-
this.activeConversationState.set(null);
|
|
2020
|
-
this.noticeState.set(null);
|
|
2021
|
-
this.uiState.update({ conversationId: null });
|
|
2022
|
-
}
|
|
2668
|
+
this.forget(conversationId);
|
|
2023
2669
|
}
|
|
2024
2670
|
catch (error) {
|
|
2025
2671
|
if (revision === this.accountRevision) {
|
|
2026
|
-
this.setError(error);
|
|
2672
|
+
this.setError(this.activeIdState(), error);
|
|
2027
2673
|
}
|
|
2028
2674
|
}
|
|
2029
2675
|
}
|
|
2030
2676
|
/**
|
|
2031
|
-
* Sends a user message. Creates a conversation with the selected agent when none is
|
|
2032
|
-
*
|
|
2033
|
-
* Resolves `false` when the message was not accepted.
|
|
2677
|
+
* Sends a user message. Creates a conversation with the selected agent when none is open,
|
|
2678
|
+
* shows the message optimistically and applies the streamed events. Other conversations keep
|
|
2679
|
+
* their own turns. Resolves `false` when the message was not accepted.
|
|
2034
2680
|
*/
|
|
2035
2681
|
async send(text) {
|
|
2682
|
+
// Browsers only ask for notification permission during a user action such as this one.
|
|
2683
|
+
void this.push.onUserGesture().catch(() => undefined);
|
|
2036
2684
|
const trimmed = text.trim();
|
|
2037
2685
|
await this.initialize();
|
|
2038
2686
|
await this.reloadStatus();
|
|
@@ -2040,51 +2688,65 @@ class NhAssistantStore {
|
|
|
2040
2688
|
if (trimmed.length === 0 || trimmed.length > maxChars || !this.canSend()) {
|
|
2041
2689
|
return false;
|
|
2042
2690
|
}
|
|
2043
|
-
this.errorState.set(null);
|
|
2044
|
-
this.noticeState.set(null);
|
|
2045
|
-
this.restoredDraftState.set(null);
|
|
2046
|
-
this.streamingState.set(true);
|
|
2047
2691
|
const accountRevision = this.accountRevision;
|
|
2048
|
-
let conversation = this.
|
|
2692
|
+
let conversation = this.activeConversation();
|
|
2693
|
+
this.sendingState.set(conversation?.id ?? newConversationKey);
|
|
2694
|
+
this.restoredDraftState.set(null);
|
|
2695
|
+
this.draftErrorState.set(null);
|
|
2049
2696
|
if (!conversation) {
|
|
2050
2697
|
conversation = await this.createConversation();
|
|
2051
2698
|
if (!conversation) {
|
|
2052
2699
|
if (accountRevision === this.accountRevision) {
|
|
2053
|
-
this.
|
|
2700
|
+
this.sendingState.set(null);
|
|
2054
2701
|
this.restoredDraftState.set(text);
|
|
2055
2702
|
}
|
|
2056
2703
|
return false;
|
|
2057
2704
|
}
|
|
2705
|
+
this.sendingState.set(conversation.id);
|
|
2058
2706
|
}
|
|
2059
2707
|
const clientContext = await this.contextForMessage();
|
|
2060
2708
|
if (accountRevision !== this.accountRevision) {
|
|
2061
2709
|
return false;
|
|
2062
2710
|
}
|
|
2711
|
+
const conversationId = conversation.id;
|
|
2063
2712
|
const clientMessageId = createClientMessageId();
|
|
2064
2713
|
const previousStatus = conversation.status;
|
|
2065
|
-
this.
|
|
2066
|
-
...
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2714
|
+
this.patchSession(conversationId, session => ({
|
|
2715
|
+
...session,
|
|
2716
|
+
error: null,
|
|
2717
|
+
notice: null,
|
|
2718
|
+
conversation: {
|
|
2719
|
+
...session.conversation,
|
|
2720
|
+
status: 'running',
|
|
2721
|
+
messages: [
|
|
2722
|
+
...session.conversation.messages,
|
|
2723
|
+
{
|
|
2724
|
+
id: clientMessageId,
|
|
2725
|
+
role: 'user',
|
|
2726
|
+
createdAt: new Date().toISOString(),
|
|
2727
|
+
parts: [{ type: 'text', text: trimmed }],
|
|
2728
|
+
authorActorId: session.conversation.currentActorId ?? null
|
|
2729
|
+
}
|
|
2730
|
+
]
|
|
2731
|
+
}
|
|
2732
|
+
}));
|
|
2733
|
+
this.patchSummary(conversationId, { status: 'running', updatedAt: new Date().toISOString() });
|
|
2074
2734
|
return new Promise(resolve => {
|
|
2075
|
-
this.
|
|
2735
|
+
this.pendingSendResolves.set(conversationId, resolve);
|
|
2076
2736
|
let started = false;
|
|
2077
2737
|
let failedAfterStart = false;
|
|
2078
|
-
this.runStream(this.api.sendMessage(conversationId, {
|
|
2738
|
+
this.runStream(conversationId, this.api.sendMessage(conversationId, {
|
|
2079
2739
|
text: trimmed,
|
|
2080
2740
|
clientMessageId,
|
|
2081
2741
|
...(clientContext === undefined ? {} : { clientContext })
|
|
2082
2742
|
}), {
|
|
2083
2743
|
next: event => {
|
|
2084
2744
|
if (event.type === 'error' && !started) {
|
|
2085
|
-
this.removeMessage(clientMessageId, previousStatus);
|
|
2086
|
-
this.
|
|
2087
|
-
|
|
2745
|
+
this.removeMessage(conversationId, clientMessageId, previousStatus);
|
|
2746
|
+
if (this.activeIdState() === conversationId) {
|
|
2747
|
+
this.restoredDraftState.set(text);
|
|
2748
|
+
}
|
|
2749
|
+
this.patchSession(conversationId, session => ({ ...session, error: event.data }));
|
|
2088
2750
|
resolve(false);
|
|
2089
2751
|
return;
|
|
2090
2752
|
}
|
|
@@ -2095,90 +2757,218 @@ class NhAssistantStore {
|
|
|
2095
2757
|
if (event.type === 'error') {
|
|
2096
2758
|
failedAfterStart = true;
|
|
2097
2759
|
}
|
|
2098
|
-
this.applyEvent(event, clientMessageId);
|
|
2760
|
+
this.applyEvent(conversationId, event, clientMessageId);
|
|
2099
2761
|
},
|
|
2100
2762
|
complete: () => {
|
|
2101
|
-
this.
|
|
2763
|
+
this.pendingSendResolves.delete(conversationId);
|
|
2102
2764
|
resolve(started);
|
|
2103
2765
|
this.afterTurn(conversationId, failedAfterStart);
|
|
2104
2766
|
}
|
|
2105
2767
|
});
|
|
2768
|
+
this.sendingState.set(null);
|
|
2106
2769
|
});
|
|
2107
2770
|
}
|
|
2108
2771
|
/** Approves or rejects the pending approval. Ignores repeated calls while a decision is in flight. */
|
|
2109
2772
|
decide(decision, reason) {
|
|
2110
|
-
const
|
|
2773
|
+
const session = this.activeSession();
|
|
2774
|
+
const conversation = session?.conversation;
|
|
2111
2775
|
const approval = conversation?.pendingApproval ?? null;
|
|
2112
|
-
if (!conversation || !approval ||
|
|
2776
|
+
if (!session || !conversation || !approval || session.deciding || session.streaming || !this.canDecide()) {
|
|
2113
2777
|
return;
|
|
2114
2778
|
}
|
|
2115
|
-
|
|
2116
|
-
this.
|
|
2117
|
-
this.errorState.set(null);
|
|
2118
|
-
this.noticeState.set(null);
|
|
2779
|
+
const conversationId = conversation.id;
|
|
2780
|
+
this.patchSession(conversationId, current => ({ ...current, deciding: true, error: null, notice: null }));
|
|
2119
2781
|
let accepted = false;
|
|
2120
2782
|
let failed = false;
|
|
2121
2783
|
const request = { decision, expectedProposalHash: approval.proposalHash, ...(reason ? { reason } : {}) };
|
|
2122
|
-
this.runStream(this.api.decideApproval(
|
|
2784
|
+
this.runStream(conversationId, this.api.decideApproval(conversationId, approval.approvalId, request), {
|
|
2123
2785
|
next: event => {
|
|
2124
2786
|
if (!accepted && event.type !== 'error') {
|
|
2125
2787
|
accepted = true;
|
|
2126
|
-
this.
|
|
2788
|
+
this.patchSession(conversationId, current => ({
|
|
2789
|
+
...current,
|
|
2790
|
+
conversation: applyNhAssistantApprovalDecision(current.conversation, approval.approvalId, decision)
|
|
2791
|
+
}));
|
|
2127
2792
|
}
|
|
2128
2793
|
if (event.type === 'error') {
|
|
2129
2794
|
failed = true;
|
|
2130
2795
|
if (!accepted) {
|
|
2131
|
-
this.
|
|
2796
|
+
this.patchSession(conversationId, current => ({ ...current, error: event.data }));
|
|
2132
2797
|
return;
|
|
2133
2798
|
}
|
|
2134
2799
|
}
|
|
2135
|
-
this.applyEvent(event);
|
|
2800
|
+
this.applyEvent(conversationId, event);
|
|
2136
2801
|
},
|
|
2137
2802
|
complete: () => {
|
|
2138
|
-
this.
|
|
2139
|
-
this.afterTurn(
|
|
2803
|
+
this.patchSession(conversationId, current => ({ ...current, deciding: false }));
|
|
2804
|
+
this.afterTurn(conversationId, failed);
|
|
2140
2805
|
}
|
|
2141
2806
|
});
|
|
2142
2807
|
}
|
|
2143
|
-
/** Asks the server to stop the running turn or to abandon a waiting approval. */
|
|
2808
|
+
/** Asks the server to stop the running turn or to abandon a waiting approval of the open conversation. */
|
|
2144
2809
|
async cancel() {
|
|
2145
|
-
const
|
|
2146
|
-
|
|
2810
|
+
const session = this.activeSession();
|
|
2811
|
+
const conversation = session?.conversation;
|
|
2812
|
+
if (!session || !conversation || (conversation.status === 'idle' && !session.streaming)) {
|
|
2147
2813
|
return;
|
|
2148
2814
|
}
|
|
2815
|
+
const conversationId = conversation.id;
|
|
2149
2816
|
const revision = this.accountRevision;
|
|
2150
2817
|
try {
|
|
2151
|
-
await firstValueFrom(this.api.cancel(
|
|
2818
|
+
await firstValueFrom(this.api.cancel(conversationId), { defaultValue: undefined });
|
|
2152
2819
|
}
|
|
2153
2820
|
catch (error) {
|
|
2154
2821
|
if (revision === this.accountRevision) {
|
|
2155
|
-
this.setError(error);
|
|
2822
|
+
this.setError(conversationId, error);
|
|
2156
2823
|
}
|
|
2157
2824
|
return;
|
|
2158
2825
|
}
|
|
2159
2826
|
if (revision !== this.accountRevision) {
|
|
2160
2827
|
return;
|
|
2161
2828
|
}
|
|
2162
|
-
if (!this.
|
|
2163
|
-
await this.
|
|
2829
|
+
if (!this.sessionsState().get(conversationId)?.streaming) {
|
|
2830
|
+
await this.reloadSession(conversationId);
|
|
2164
2831
|
return;
|
|
2165
2832
|
}
|
|
2166
2833
|
// The server ends the stream with turn.completed(cancelled); stop waiting if it does not.
|
|
2167
|
-
clearTimeout(this.
|
|
2168
|
-
this.
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
this.
|
|
2172
|
-
this.
|
|
2173
|
-
|
|
2834
|
+
clearTimeout(this.cancelTimers.get(conversationId));
|
|
2835
|
+
this.cancelTimers.set(conversationId, setTimeout(() => {
|
|
2836
|
+
this.cancelTimers.delete(conversationId);
|
|
2837
|
+
if (this.sessionsState().get(conversationId)?.streaming) {
|
|
2838
|
+
this.streams.get(conversationId)?.unsubscribe();
|
|
2839
|
+
this.streams.delete(conversationId);
|
|
2840
|
+
this.patchSession(conversationId, current => ({ ...current, streaming: false, deciding: false }));
|
|
2841
|
+
void this.reloadSession(conversationId);
|
|
2842
|
+
}
|
|
2843
|
+
}, cancelGracePeriodMs));
|
|
2844
|
+
}
|
|
2845
|
+
/** Joins a shared conversation with an invitation link and opens it. Resolves `false` on failure. */
|
|
2846
|
+
async joinConversation(conversationId, token) {
|
|
2847
|
+
await this.initialize();
|
|
2848
|
+
const revision = this.accountRevision;
|
|
2849
|
+
try {
|
|
2850
|
+
const conversation = await firstValueFrom(this.api.joinConversation(conversationId, token));
|
|
2851
|
+
if (revision !== this.accountRevision) {
|
|
2852
|
+
return false;
|
|
2853
|
+
}
|
|
2854
|
+
this.upsertSession(conversation);
|
|
2855
|
+
this.setActive(conversation.id, conversation.agentId);
|
|
2856
|
+
void this.refreshConversations();
|
|
2857
|
+
return true;
|
|
2858
|
+
}
|
|
2859
|
+
catch (error) {
|
|
2860
|
+
if (revision === this.accountRevision) {
|
|
2861
|
+
this.startNewConversation();
|
|
2862
|
+
this.setError(null, error);
|
|
2863
|
+
}
|
|
2864
|
+
return false;
|
|
2865
|
+
}
|
|
2866
|
+
}
|
|
2867
|
+
/** Owner only: creates a new invitation link for the open conversation and returns its token. */
|
|
2868
|
+
async createShareLink() {
|
|
2869
|
+
const conversationId = this.activeIdState();
|
|
2870
|
+
if (!conversationId) {
|
|
2871
|
+
return null;
|
|
2872
|
+
}
|
|
2873
|
+
try {
|
|
2874
|
+
const link = await firstValueFrom(this.api.createShareLink(conversationId));
|
|
2875
|
+
this.patchSession(conversationId, session => ({
|
|
2876
|
+
...session,
|
|
2877
|
+
conversation: { ...session.conversation, shareToken: link.token }
|
|
2878
|
+
}));
|
|
2879
|
+
return link.token;
|
|
2880
|
+
}
|
|
2881
|
+
catch (error) {
|
|
2882
|
+
this.setError(conversationId, error);
|
|
2883
|
+
return null;
|
|
2884
|
+
}
|
|
2885
|
+
}
|
|
2886
|
+
/** Owner only: the invitation link of the open conversation stops working. */
|
|
2887
|
+
async revokeShareLink() {
|
|
2888
|
+
const conversationId = this.activeIdState();
|
|
2889
|
+
if (!conversationId) {
|
|
2890
|
+
return false;
|
|
2891
|
+
}
|
|
2892
|
+
try {
|
|
2893
|
+
await firstValueFrom(this.api.revokeShareLink(conversationId), { defaultValue: undefined });
|
|
2894
|
+
this.patchSession(conversationId, session => ({
|
|
2895
|
+
...session,
|
|
2896
|
+
conversation: { ...session.conversation, shareToken: null }
|
|
2897
|
+
}));
|
|
2898
|
+
return true;
|
|
2899
|
+
}
|
|
2900
|
+
catch (error) {
|
|
2901
|
+
this.setError(conversationId, error);
|
|
2902
|
+
return false;
|
|
2903
|
+
}
|
|
2904
|
+
}
|
|
2905
|
+
/** Owner only: searches the application's directory for people to invite to the open conversation. */
|
|
2906
|
+
async searchParticipantCandidates(query) {
|
|
2907
|
+
const conversationId = this.activeIdState();
|
|
2908
|
+
if (!conversationId || query.trim().length < 2) {
|
|
2909
|
+
return [];
|
|
2910
|
+
}
|
|
2911
|
+
try {
|
|
2912
|
+
return await firstValueFrom(this.api.searchParticipantCandidates(conversationId, query.trim()));
|
|
2913
|
+
}
|
|
2914
|
+
catch (error) {
|
|
2915
|
+
this.setError(conversationId, error);
|
|
2916
|
+
return [];
|
|
2917
|
+
}
|
|
2918
|
+
}
|
|
2919
|
+
/** Owner only: invites a person from the directory to the open conversation. */
|
|
2920
|
+
async inviteParticipant(actorId) {
|
|
2921
|
+
const conversationId = this.activeIdState();
|
|
2922
|
+
if (!conversationId) {
|
|
2923
|
+
return false;
|
|
2924
|
+
}
|
|
2925
|
+
try {
|
|
2926
|
+
await firstValueFrom(this.api.inviteParticipant(conversationId, actorId), { defaultValue: undefined });
|
|
2927
|
+
await this.reloadSession(conversationId);
|
|
2928
|
+
return true;
|
|
2929
|
+
}
|
|
2930
|
+
catch (error) {
|
|
2931
|
+
this.setError(conversationId, error);
|
|
2932
|
+
return false;
|
|
2933
|
+
}
|
|
2934
|
+
}
|
|
2935
|
+
/**
|
|
2936
|
+
* The owner removes a participant of the open conversation; with the current user's own id a
|
|
2937
|
+
* participant leaves it.
|
|
2938
|
+
*/
|
|
2939
|
+
async removeParticipant(actorId) {
|
|
2940
|
+
const conversation = this.activeConversation();
|
|
2941
|
+
if (!conversation) {
|
|
2942
|
+
return false;
|
|
2943
|
+
}
|
|
2944
|
+
try {
|
|
2945
|
+
await firstValueFrom(this.api.removeParticipant(conversation.id, actorId), { defaultValue: undefined });
|
|
2946
|
+
if (actorId === conversation.currentActorId) {
|
|
2947
|
+
this.forget(conversation.id);
|
|
2948
|
+
}
|
|
2949
|
+
else {
|
|
2950
|
+
await this.reloadSession(conversation.id);
|
|
2174
2951
|
}
|
|
2175
|
-
|
|
2952
|
+
return true;
|
|
2953
|
+
}
|
|
2954
|
+
catch (error) {
|
|
2955
|
+
this.setError(conversation.id, error);
|
|
2956
|
+
return false;
|
|
2957
|
+
}
|
|
2176
2958
|
}
|
|
2177
2959
|
clearError() {
|
|
2178
|
-
this.
|
|
2960
|
+
const activeId = this.activeIdState();
|
|
2961
|
+
if (activeId === null) {
|
|
2962
|
+
this.draftErrorState.set(null);
|
|
2963
|
+
return;
|
|
2964
|
+
}
|
|
2965
|
+
this.patchSession(activeId, session => ({ ...session, error: null }));
|
|
2179
2966
|
}
|
|
2180
2967
|
clearNotice() {
|
|
2181
|
-
this.
|
|
2968
|
+
const activeId = this.activeIdState();
|
|
2969
|
+
if (activeId !== null) {
|
|
2970
|
+
this.patchSession(activeId, session => ({ ...session, notice: null }));
|
|
2971
|
+
}
|
|
2182
2972
|
}
|
|
2183
2973
|
/** Persists only drawer visibility, never the page context or a message draft. */
|
|
2184
2974
|
setPanelOpen(open) {
|
|
@@ -2203,6 +2993,54 @@ class NhAssistantStore {
|
|
|
2203
2993
|
this.restoredDraftState.set(null);
|
|
2204
2994
|
return draft;
|
|
2205
2995
|
}
|
|
2996
|
+
async loadConversation(conversationId, restoring) {
|
|
2997
|
+
if (this.sessionsState().has(conversationId)) {
|
|
2998
|
+
const session = this.sessionsState().get(conversationId);
|
|
2999
|
+
this.setActive(conversationId, session.conversation.agentId);
|
|
3000
|
+
return;
|
|
3001
|
+
}
|
|
3002
|
+
if (this.activeIdState() === conversationId) {
|
|
3003
|
+
return;
|
|
3004
|
+
}
|
|
3005
|
+
const revision = this.accountRevision;
|
|
3006
|
+
this.conversationLoadingState.set(true);
|
|
3007
|
+
this.draftErrorState.set(null);
|
|
3008
|
+
try {
|
|
3009
|
+
const conversation = await firstValueFrom(this.api.getConversation(conversationId));
|
|
3010
|
+
if (revision !== this.accountRevision) {
|
|
3011
|
+
return;
|
|
3012
|
+
}
|
|
3013
|
+
if (!this.agents().some(agent => agent.id === conversation.agentId)) {
|
|
3014
|
+
this.uiState.update({ conversationId: null });
|
|
3015
|
+
return;
|
|
3016
|
+
}
|
|
3017
|
+
this.upsertSession(conversation);
|
|
3018
|
+
this.setActive(conversationId, conversation.agentId);
|
|
3019
|
+
}
|
|
3020
|
+
catch (error) {
|
|
3021
|
+
if (revision !== this.accountRevision) {
|
|
3022
|
+
return;
|
|
3023
|
+
}
|
|
3024
|
+
if (error instanceof NhAssistantApiError && (error.status === 403 || error.status === 404)) {
|
|
3025
|
+
this.uiState.update({ conversationId: null });
|
|
3026
|
+
}
|
|
3027
|
+
if (!restoring) {
|
|
3028
|
+
this.setError(null, error);
|
|
3029
|
+
}
|
|
3030
|
+
}
|
|
3031
|
+
finally {
|
|
3032
|
+
if (revision === this.accountRevision) {
|
|
3033
|
+
this.conversationLoadingState.set(false);
|
|
3034
|
+
}
|
|
3035
|
+
}
|
|
3036
|
+
}
|
|
3037
|
+
setActive(conversationId, agentId) {
|
|
3038
|
+
this.activeIdState.set(conversationId);
|
|
3039
|
+
if (agentId) {
|
|
3040
|
+
this.selectedAgentIdState.set(agentId);
|
|
3041
|
+
}
|
|
3042
|
+
this.uiState.update(agentId ? { conversationId, agentId } : { conversationId });
|
|
3043
|
+
}
|
|
2206
3044
|
/**
|
|
2207
3045
|
* Page context for one message: `undefined` omits the field (no getter, getter failed or
|
|
2208
3046
|
* invalid shape), `null` sends an explicit "none" (no context or left out by the user).
|
|
@@ -2260,6 +3098,37 @@ class NhAssistantStore {
|
|
|
2260
3098
|
});
|
|
2261
3099
|
});
|
|
2262
3100
|
}
|
|
3101
|
+
/** Connects live updates and notifications once per account and server configuration. */
|
|
3102
|
+
startCollaboration(status) {
|
|
3103
|
+
const collaboration = status.collaboration ?? null;
|
|
3104
|
+
const key = `${this.accountRevision}|${collaboration?.hubPath ?? ''}|${collaboration?.push === true}`;
|
|
3105
|
+
if (key === this.collaborationKey) {
|
|
3106
|
+
return;
|
|
3107
|
+
}
|
|
3108
|
+
const accountChanged = this.collaborationKey?.split('|')[0] !== String(this.accountRevision);
|
|
3109
|
+
this.collaborationKey = key;
|
|
3110
|
+
void this.live.connect(collaboration?.hubPath ?? null, accountChanged);
|
|
3111
|
+
void this.push.activate(collaboration?.push === true);
|
|
3112
|
+
}
|
|
3113
|
+
/** Forgets everything about the previous account: turns, conversations, live updates and push. */
|
|
3114
|
+
resetAccount() {
|
|
3115
|
+
this.accountRevision++;
|
|
3116
|
+
this.collaborationKey = null;
|
|
3117
|
+
this.stopAllTurns();
|
|
3118
|
+
this.sessionsState.set(new Map());
|
|
3119
|
+
this.activeIdState.set(null);
|
|
3120
|
+
this.conversationsState.set([]);
|
|
3121
|
+
this.conversationsTotalState.set(0);
|
|
3122
|
+
this.conversationLoadingState.set(false);
|
|
3123
|
+
this.conversationsLoadingState.set(false);
|
|
3124
|
+
this.pageContextState.set(null);
|
|
3125
|
+
this.pageContextExcludedState.set(false);
|
|
3126
|
+
this.restoredDraftState.set(null);
|
|
3127
|
+
this.draftErrorState.set(null);
|
|
3128
|
+
this.sendingState.set(null);
|
|
3129
|
+
void this.live.stop();
|
|
3130
|
+
void this.push.deactivate().catch(() => undefined);
|
|
3131
|
+
}
|
|
2263
3132
|
async createConversation() {
|
|
2264
3133
|
const agentId = this.selectedAgentIdState();
|
|
2265
3134
|
if (!agentId) {
|
|
@@ -2271,108 +3140,305 @@ class NhAssistantStore {
|
|
|
2271
3140
|
if (revision !== this.accountRevision) {
|
|
2272
3141
|
return null;
|
|
2273
3142
|
}
|
|
2274
|
-
this.
|
|
2275
|
-
this.
|
|
3143
|
+
this.upsertSession(conversation);
|
|
3144
|
+
this.setActive(conversation.id);
|
|
2276
3145
|
this.conversationsState.update(items => [toSummary(conversation), ...items.filter(item => item.id !== conversation.id)]);
|
|
2277
3146
|
this.conversationsTotalState.update(total => total + 1);
|
|
2278
3147
|
return conversation;
|
|
2279
3148
|
}
|
|
2280
3149
|
catch (error) {
|
|
2281
3150
|
if (revision === this.accountRevision) {
|
|
2282
|
-
this.setError(error);
|
|
3151
|
+
this.setError(null, error);
|
|
2283
3152
|
}
|
|
2284
3153
|
return null;
|
|
2285
3154
|
}
|
|
2286
3155
|
}
|
|
2287
|
-
runStream(stream, handlers) {
|
|
2288
|
-
this.
|
|
2289
|
-
this.
|
|
3156
|
+
runStream(conversationId, stream, handlers) {
|
|
3157
|
+
this.streams.get(conversationId)?.unsubscribe();
|
|
3158
|
+
this.patchSession(conversationId, session => ({ ...session, streaming: true }));
|
|
2290
3159
|
const finish = () => {
|
|
2291
|
-
clearTimeout(this.
|
|
2292
|
-
this.
|
|
3160
|
+
clearTimeout(this.cancelTimers.get(conversationId));
|
|
3161
|
+
this.cancelTimers.delete(conversationId);
|
|
3162
|
+
this.streams.delete(conversationId);
|
|
3163
|
+
this.patchSession(conversationId, session => ({ ...session, streaming: false }));
|
|
2293
3164
|
handlers.complete();
|
|
2294
3165
|
};
|
|
2295
|
-
|
|
3166
|
+
const subscription = stream.subscribe({
|
|
2296
3167
|
next: event => handlers.next(event),
|
|
2297
3168
|
error: () => {
|
|
2298
|
-
this.
|
|
3169
|
+
this.patchSession(conversationId, session => ({ ...session, error: clientError(NhAssistantClientErrorCodes.network) }));
|
|
2299
3170
|
finish();
|
|
2300
3171
|
},
|
|
2301
3172
|
complete: finish
|
|
2302
3173
|
});
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
this.streamSubscription?.unsubscribe();
|
|
2306
|
-
this.pendingSendResolve?.(false);
|
|
2307
|
-
this.pendingSendResolve = undefined;
|
|
2308
|
-
clearTimeout(this.cancelTimer);
|
|
2309
|
-
this.streamingState.set(false);
|
|
2310
|
-
this.decidingState.set(false);
|
|
2311
|
-
}
|
|
2312
|
-
applyEvent(event, clientMessageId) {
|
|
2313
|
-
this.updateActive(conversation => applyNhAssistantEvent(conversation, event, clientMessageId));
|
|
2314
|
-
if (event.type === 'error') {
|
|
2315
|
-
this.errorState.set(event.data);
|
|
2316
|
-
}
|
|
2317
|
-
if (event.type === 'turn.completed') {
|
|
2318
|
-
this.lastUsageState.set(event.data.usage);
|
|
2319
|
-
if (event.data.status === 'failed') {
|
|
2320
|
-
const code = event.data.errorCode ?? NhAssistantClientErrorCodes.server;
|
|
2321
|
-
this.errorState.set(clientError(code));
|
|
2322
|
-
}
|
|
2323
|
-
else if (event.data.status === 'completed') {
|
|
2324
|
-
this.noticeState.set(this.completionNotice(event.data.errorCode));
|
|
2325
|
-
}
|
|
3174
|
+
if (!subscription.closed) {
|
|
3175
|
+
this.streams.set(conversationId, subscription);
|
|
2326
3176
|
}
|
|
2327
3177
|
}
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
return clientError(errorCode);
|
|
3178
|
+
stopAllTurns() {
|
|
3179
|
+
for (const subscription of this.streams.values()) {
|
|
3180
|
+
subscription.unsubscribe();
|
|
2332
3181
|
}
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
3182
|
+
this.streams.clear();
|
|
3183
|
+
for (const resolve of this.pendingSendResolves.values()) {
|
|
3184
|
+
resolve(false);
|
|
2336
3185
|
}
|
|
2337
|
-
|
|
3186
|
+
this.pendingSendResolves.clear();
|
|
3187
|
+
for (const timer of this.cancelTimers.values()) {
|
|
3188
|
+
clearTimeout(timer);
|
|
3189
|
+
}
|
|
3190
|
+
this.cancelTimers.clear();
|
|
3191
|
+
}
|
|
3192
|
+
applyEvent(conversationId, event, clientMessageId) {
|
|
3193
|
+
this.patchSession(conversationId, session => {
|
|
3194
|
+
const conversation = applyNhAssistantEvent(session.conversation, event, clientMessageId);
|
|
3195
|
+
let { error, notice, lastUsage } = session;
|
|
3196
|
+
if (event.type === 'error') {
|
|
3197
|
+
error = event.data;
|
|
3198
|
+
}
|
|
3199
|
+
if (event.type === 'turn.completed') {
|
|
3200
|
+
lastUsage = event.data.usage;
|
|
3201
|
+
if (event.data.status === 'failed') {
|
|
3202
|
+
error = clientError(event.data.errorCode ?? NhAssistantClientErrorCodes.server);
|
|
3203
|
+
}
|
|
3204
|
+
else if (event.data.status === 'completed') {
|
|
3205
|
+
notice = completionNotice(conversation, event.data.errorCode);
|
|
3206
|
+
}
|
|
3207
|
+
}
|
|
3208
|
+
return { ...session, conversation, error, notice, lastUsage };
|
|
3209
|
+
});
|
|
2338
3210
|
}
|
|
2339
3211
|
afterTurn(conversationId, reload) {
|
|
2340
|
-
const
|
|
2341
|
-
if (
|
|
2342
|
-
|
|
3212
|
+
const session = this.sessionsState().get(conversationId);
|
|
3213
|
+
if (session) {
|
|
3214
|
+
const status = session.conversation.status;
|
|
3215
|
+
this.patchSummary(conversationId, summary => ({
|
|
3216
|
+
...summary,
|
|
3217
|
+
status,
|
|
3218
|
+
updatedAt: new Date().toISOString(),
|
|
3219
|
+
activeActorId: status === 'waiting-for-approval' ? summary.activeActorId ?? session.conversation.currentActorId ?? null : null
|
|
3220
|
+
}));
|
|
2343
3221
|
}
|
|
2344
3222
|
if (reload) {
|
|
2345
|
-
void this.
|
|
2346
|
-
}
|
|
2347
|
-
if (!active?.title) {
|
|
2348
|
-
void this.refreshConversations();
|
|
3223
|
+
void this.reloadSession(conversationId);
|
|
2349
3224
|
}
|
|
3225
|
+
// The list carries the exact title, latest message and read position of the finished turn.
|
|
3226
|
+
void this.refreshConversations();
|
|
2350
3227
|
}
|
|
2351
|
-
async
|
|
3228
|
+
async reloadSession(conversationId) {
|
|
2352
3229
|
const revision = this.accountRevision;
|
|
2353
3230
|
try {
|
|
2354
3231
|
const conversation = await firstValueFrom(this.api.getConversation(conversationId));
|
|
2355
|
-
|
|
2356
|
-
|
|
3232
|
+
const session = this.sessionsState().get(conversationId);
|
|
3233
|
+
if (revision === this.accountRevision && session && !session.streaming) {
|
|
3234
|
+
this.upsertSession(conversation);
|
|
3235
|
+
}
|
|
3236
|
+
}
|
|
3237
|
+
catch (error) {
|
|
3238
|
+
if (error instanceof NhAssistantApiError && (error.status === 403 || error.status === 404)) {
|
|
3239
|
+
this.forget(conversationId);
|
|
2357
3240
|
}
|
|
3241
|
+
// Otherwise keep the local state; the error of the turn is already shown.
|
|
3242
|
+
}
|
|
3243
|
+
}
|
|
3244
|
+
markActiveReadWhenSeen() {
|
|
3245
|
+
const conversationId = this.activeIdState();
|
|
3246
|
+
if (!conversationId || !this.viewingState() || !this.pageVisibleState() || this.markingRead.has(conversationId)) {
|
|
3247
|
+
return;
|
|
3248
|
+
}
|
|
3249
|
+
const session = this.sessionsState().get(conversationId);
|
|
3250
|
+
const summary = this.conversationsState().find(item => item.id === conversationId);
|
|
3251
|
+
if (!session || session.streaming || !summary || !nhAssistantIsUnread(summary)) {
|
|
3252
|
+
return;
|
|
3253
|
+
}
|
|
3254
|
+
this.markingRead.add(conversationId);
|
|
3255
|
+
const revision = this.accountRevision;
|
|
3256
|
+
let request;
|
|
3257
|
+
try {
|
|
3258
|
+
request = this.api.markRead(conversationId);
|
|
2358
3259
|
}
|
|
2359
3260
|
catch {
|
|
2360
|
-
|
|
3261
|
+
this.markingRead.delete(conversationId);
|
|
3262
|
+
return;
|
|
2361
3263
|
}
|
|
3264
|
+
firstValueFrom(request, { defaultValue: undefined })
|
|
3265
|
+
.then(() => {
|
|
3266
|
+
if (revision === this.accountRevision) {
|
|
3267
|
+
this.patchSummary(conversationId, item => ({ ...item, lastReadSequence: item.lastMessageSequence ?? item.lastReadSequence }));
|
|
3268
|
+
}
|
|
3269
|
+
})
|
|
3270
|
+
.catch(() => undefined)
|
|
3271
|
+
.finally(() => this.markingRead.delete(conversationId));
|
|
2362
3272
|
}
|
|
2363
|
-
|
|
2364
|
-
const
|
|
2365
|
-
if (
|
|
2366
|
-
this.
|
|
3273
|
+
onLiveChanged(change) {
|
|
3274
|
+
const known = this.conversationsState().some(item => item.id === change.conversationId);
|
|
3275
|
+
if (!known) {
|
|
3276
|
+
this.scheduleRefresh();
|
|
3277
|
+
}
|
|
3278
|
+
else {
|
|
3279
|
+
this.patchSummary(change.conversationId, summary => ({
|
|
3280
|
+
...summary,
|
|
3281
|
+
status: change.status,
|
|
3282
|
+
title: change.title,
|
|
3283
|
+
updatedAt: change.updatedAt,
|
|
3284
|
+
activeActorId: change.activeActorId,
|
|
3285
|
+
lastMessageSequence: Math.max(summary.lastMessageSequence ?? 0, change.lastMessageSequence),
|
|
3286
|
+
participantCount: change.participantCount
|
|
3287
|
+
}));
|
|
3288
|
+
}
|
|
3289
|
+
const session = this.sessionsState().get(change.conversationId);
|
|
3290
|
+
if (!session || session.streaming) {
|
|
3291
|
+
return;
|
|
3292
|
+
}
|
|
3293
|
+
const wasRunning = session.conversation.status === 'running';
|
|
3294
|
+
this.patchSession(change.conversationId, current => ({
|
|
3295
|
+
...current,
|
|
3296
|
+
conversation: applyNhAssistantConversationChange(current.conversation, change)
|
|
3297
|
+
}));
|
|
3298
|
+
if (wasRunning && change.status !== 'running') {
|
|
3299
|
+
// Another session ended its turn; the snapshot holds the exact answer and approvals.
|
|
3300
|
+
void this.reloadSession(change.conversationId);
|
|
3301
|
+
}
|
|
3302
|
+
else if (session.conversation.participantCount !== change.participantCount) {
|
|
3303
|
+
void this.reloadSession(change.conversationId);
|
|
2367
3304
|
}
|
|
2368
3305
|
}
|
|
2369
|
-
|
|
2370
|
-
this.
|
|
2371
|
-
...
|
|
2372
|
-
|
|
2373
|
-
messages: conversation.messages.filter(message => message.id !== messageId)
|
|
3306
|
+
onLiveRead(read) {
|
|
3307
|
+
this.patchSummary(read.conversationId, summary => ({
|
|
3308
|
+
...summary,
|
|
3309
|
+
lastReadSequence: Math.max(summary.lastReadSequence ?? 0, read.lastReadSequence)
|
|
2374
3310
|
}));
|
|
2375
3311
|
}
|
|
3312
|
+
onLiveRemoved(removed) {
|
|
3313
|
+
const wasActive = this.activeIdState() === removed.conversationId;
|
|
3314
|
+
this.forget(removed.conversationId);
|
|
3315
|
+
if (wasActive) {
|
|
3316
|
+
this.draftErrorState.set(clientError(NhAssistantClientErrorCodes.conversationRemoved));
|
|
3317
|
+
}
|
|
3318
|
+
}
|
|
3319
|
+
onLiveEvent(event) {
|
|
3320
|
+
const session = this.sessionsState().get(event.conversationId);
|
|
3321
|
+
if (event.type === 'turn.started') {
|
|
3322
|
+
this.patchSummary(event.conversationId, summary => ({ ...summary, status: 'running', activeActorId: event.actorId }));
|
|
3323
|
+
}
|
|
3324
|
+
if (!session || session.streaming) {
|
|
3325
|
+
// This tab streams the turn itself, or does not show the conversation.
|
|
3326
|
+
return;
|
|
3327
|
+
}
|
|
3328
|
+
this.patchSession(event.conversationId, current => ({
|
|
3329
|
+
...current,
|
|
3330
|
+
conversation: applyNhAssistantLiveEvent(current.conversation, event)
|
|
3331
|
+
}));
|
|
3332
|
+
}
|
|
3333
|
+
/** After a reconnect: updates sent while disconnected are lost, so reload what is shown. */
|
|
3334
|
+
resync() {
|
|
3335
|
+
if (!this.enabled()) {
|
|
3336
|
+
return;
|
|
3337
|
+
}
|
|
3338
|
+
void this.refreshConversations();
|
|
3339
|
+
for (const [conversationId, session] of this.sessionsState()) {
|
|
3340
|
+
if (!session.streaming) {
|
|
3341
|
+
void this.reloadSession(conversationId);
|
|
3342
|
+
}
|
|
3343
|
+
}
|
|
3344
|
+
}
|
|
3345
|
+
scheduleRefresh() {
|
|
3346
|
+
clearTimeout(this.refreshTimer);
|
|
3347
|
+
this.refreshTimer = setTimeout(() => void this.refreshConversations(), refreshDelayMs);
|
|
3348
|
+
}
|
|
3349
|
+
/** Stores a snapshot, keeping at most `maxSessions` idle conversations in memory. */
|
|
3350
|
+
upsertSession(conversation) {
|
|
3351
|
+
this.sessionsState.update(sessions => {
|
|
3352
|
+
const next = new Map(sessions);
|
|
3353
|
+
const existing = next.get(conversation.id);
|
|
3354
|
+
next.set(conversation.id, existing
|
|
3355
|
+
? { ...existing, conversation }
|
|
3356
|
+
: { conversation, streaming: false, deciding: false, error: null, notice: null, lastUsage: null });
|
|
3357
|
+
if (next.size > maxSessions) {
|
|
3358
|
+
const activeId = this.activeIdState();
|
|
3359
|
+
for (const [id, session] of next) {
|
|
3360
|
+
if (next.size <= maxSessions) {
|
|
3361
|
+
break;
|
|
3362
|
+
}
|
|
3363
|
+
if (id !== activeId && id !== conversation.id && !session.streaming) {
|
|
3364
|
+
next.delete(id);
|
|
3365
|
+
}
|
|
3366
|
+
}
|
|
3367
|
+
}
|
|
3368
|
+
return next;
|
|
3369
|
+
});
|
|
3370
|
+
this.patchSummary(conversation.id, summary => ({
|
|
3371
|
+
...summary,
|
|
3372
|
+
title: conversation.title,
|
|
3373
|
+
status: conversation.status,
|
|
3374
|
+
updatedAt: conversation.updatedAt,
|
|
3375
|
+
...(conversation.lastMessageSequence === undefined ? {} : { lastMessageSequence: conversation.lastMessageSequence }),
|
|
3376
|
+
...(conversation.lastReadSequence === undefined ? {} : { lastReadSequence: conversation.lastReadSequence }),
|
|
3377
|
+
...(conversation.activeActorId === undefined ? {} : { activeActorId: conversation.activeActorId }),
|
|
3378
|
+
...(conversation.members === undefined ? {} : { participantCount: Math.max(0, conversation.members.length - 1) })
|
|
3379
|
+
}));
|
|
3380
|
+
}
|
|
3381
|
+
patchSession(conversationId, update) {
|
|
3382
|
+
const session = this.sessionsState().get(conversationId);
|
|
3383
|
+
if (!session) {
|
|
3384
|
+
return;
|
|
3385
|
+
}
|
|
3386
|
+
this.sessionsState.update(sessions => new Map(sessions).set(conversationId, update(session)));
|
|
3387
|
+
}
|
|
3388
|
+
patchSummary(conversationId, update) {
|
|
3389
|
+
this.conversationsState.update(items => items.map(item => {
|
|
3390
|
+
if (item.id !== conversationId) {
|
|
3391
|
+
return item;
|
|
3392
|
+
}
|
|
3393
|
+
return typeof update === 'function' ? update(item) : { ...item, ...update };
|
|
3394
|
+
}));
|
|
3395
|
+
}
|
|
3396
|
+
/**
|
|
3397
|
+
* Keeps local knowledge when the list is reloaded: the status of turns this tab streams, and a
|
|
3398
|
+
* read position the server already confirmed, which a list response sent earlier may not show.
|
|
3399
|
+
*/
|
|
3400
|
+
withLocalState(summary) {
|
|
3401
|
+
const session = this.sessionsState().get(summary.id);
|
|
3402
|
+
const known = this.conversationsState().find(item => item.id === summary.id)?.lastReadSequence;
|
|
3403
|
+
const lastReadSequence = known !== undefined && summary.lastReadSequence !== undefined
|
|
3404
|
+
? Math.max(known, summary.lastReadSequence)
|
|
3405
|
+
: summary.lastReadSequence;
|
|
3406
|
+
return {
|
|
3407
|
+
...summary,
|
|
3408
|
+
lastReadSequence,
|
|
3409
|
+
status: session?.streaming ? session.conversation.status : summary.status
|
|
3410
|
+
};
|
|
3411
|
+
}
|
|
3412
|
+
/** Removes a deleted, left or inaccessible conversation everywhere. */
|
|
3413
|
+
forget(conversationId) {
|
|
3414
|
+
this.streams.get(conversationId)?.unsubscribe();
|
|
3415
|
+
this.streams.delete(conversationId);
|
|
3416
|
+
this.sessionsState.update(sessions => {
|
|
3417
|
+
const next = new Map(sessions);
|
|
3418
|
+
next.delete(conversationId);
|
|
3419
|
+
return next;
|
|
3420
|
+
});
|
|
3421
|
+
const before = this.conversationsState().length;
|
|
3422
|
+
this.conversationsState.update(items => items.filter(item => item.id !== conversationId));
|
|
3423
|
+
if (this.conversationsState().length < before) {
|
|
3424
|
+
this.conversationsTotalState.update(total => Math.max(0, total - 1));
|
|
3425
|
+
}
|
|
3426
|
+
if (this.activeIdState() === conversationId) {
|
|
3427
|
+
this.activeIdState.set(null);
|
|
3428
|
+
this.uiState.update({ conversationId: null });
|
|
3429
|
+
}
|
|
3430
|
+
}
|
|
3431
|
+
removeMessage(conversationId, messageId, status) {
|
|
3432
|
+
this.patchSession(conversationId, session => ({
|
|
3433
|
+
...session,
|
|
3434
|
+
conversation: {
|
|
3435
|
+
...session.conversation,
|
|
3436
|
+
status,
|
|
3437
|
+
messages: session.conversation.messages.filter(message => message.id !== messageId)
|
|
3438
|
+
}
|
|
3439
|
+
}));
|
|
3440
|
+
this.patchSummary(conversationId, { status });
|
|
3441
|
+
}
|
|
2376
3442
|
selectInitialAgent(agents) {
|
|
2377
3443
|
const current = this.selectedAgentIdState();
|
|
2378
3444
|
if (current && agents.some(agent => agent.id === current)) {
|
|
@@ -2382,22 +3448,36 @@ class NhAssistantStore {
|
|
|
2382
3448
|
this.selectedAgentIdState.set(preferred?.id ?? null);
|
|
2383
3449
|
this.uiState.update({ agentId: preferred?.id ?? null });
|
|
2384
3450
|
}
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
3451
|
+
/** Shows an error on a conversation, or on the new-conversation view for `null`. */
|
|
3452
|
+
setError(conversationId, error) {
|
|
3453
|
+
const value = error instanceof NhAssistantApiError
|
|
3454
|
+
? { code: error.code, messageKey: error.messageKey }
|
|
3455
|
+
: clientError(NhAssistantClientErrorCodes.server);
|
|
3456
|
+
if (conversationId === null || !this.sessionsState().has(conversationId)) {
|
|
3457
|
+
this.draftErrorState.set(value);
|
|
2388
3458
|
return;
|
|
2389
3459
|
}
|
|
2390
|
-
this.
|
|
3460
|
+
this.patchSession(conversationId, session => ({ ...session, error: value }));
|
|
2391
3461
|
}
|
|
2392
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
2393
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.
|
|
3462
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantStore, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
3463
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantStore }); }
|
|
2394
3464
|
}
|
|
2395
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
3465
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantStore, decorators: [{
|
|
2396
3466
|
type: Injectable
|
|
2397
3467
|
}], ctorParameters: () => [] });
|
|
2398
3468
|
function clientError(code) {
|
|
2399
3469
|
return { code, messageKey: nhAssistantErrorMessageKey(code) };
|
|
2400
3470
|
}
|
|
3471
|
+
/** A completed turn with a server code, or without any answer text, explains itself with a notice. */
|
|
3472
|
+
function completionNotice(conversation, errorCode) {
|
|
3473
|
+
if (errorCode) {
|
|
3474
|
+
return clientError(errorCode);
|
|
3475
|
+
}
|
|
3476
|
+
if (!nhAssistantLatestTurnHasText(conversation)) {
|
|
3477
|
+
return { code: NH_ASSISTANT_NO_ANSWER_NOTICE_CODE, messageKey: 'nh-assistant.notices.no-answer' };
|
|
3478
|
+
}
|
|
3479
|
+
return null;
|
|
3480
|
+
}
|
|
2401
3481
|
function toSummary(conversation) {
|
|
2402
3482
|
return {
|
|
2403
3483
|
id: conversation.id,
|
|
@@ -2405,7 +3485,12 @@ function toSummary(conversation) {
|
|
|
2405
3485
|
title: conversation.title,
|
|
2406
3486
|
status: conversation.status,
|
|
2407
3487
|
createdAt: conversation.createdAt,
|
|
2408
|
-
updatedAt: conversation.updatedAt
|
|
3488
|
+
updatedAt: conversation.updatedAt,
|
|
3489
|
+
role: conversation.role,
|
|
3490
|
+
participantCount: conversation.members ? Math.max(0, conversation.members.length - 1) : conversation.participantCount,
|
|
3491
|
+
lastMessageSequence: conversation.lastMessageSequence,
|
|
3492
|
+
lastReadSequence: conversation.lastReadSequence,
|
|
3493
|
+
activeActorId: conversation.activeActorId
|
|
2409
3494
|
};
|
|
2410
3495
|
}
|
|
2411
3496
|
function createClientMessageId() {
|
|
@@ -2569,10 +3654,10 @@ class NhAssistantPanelService {
|
|
|
2569
3654
|
this.overlayRef?.dispose();
|
|
2570
3655
|
this.overlayRef = undefined;
|
|
2571
3656
|
}
|
|
2572
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
2573
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.
|
|
3657
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantPanelService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
3658
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantPanelService }); }
|
|
2574
3659
|
}
|
|
2575
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
3660
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantPanelService, decorators: [{
|
|
2576
3661
|
type: Injectable
|
|
2577
3662
|
}], ctorParameters: () => [] });
|
|
2578
3663
|
|
|
@@ -2588,6 +3673,8 @@ function provideNhAssistant(config) {
|
|
|
2588
3673
|
NhAssistantTransport,
|
|
2589
3674
|
NhAssistantApiService,
|
|
2590
3675
|
NhAssistantAdminApiService,
|
|
3676
|
+
NhAssistantLiveService,
|
|
3677
|
+
NhAssistantPushService,
|
|
2591
3678
|
NhAssistantStore,
|
|
2592
3679
|
NhAssistantUiState,
|
|
2593
3680
|
NhAssistantPanelService,
|
|
@@ -2597,6 +3684,17 @@ function provideNhAssistant(config) {
|
|
|
2597
3684
|
]);
|
|
2598
3685
|
}
|
|
2599
3686
|
|
|
3687
|
+
/** Fragment the panel reads to join a shared conversation: `#nh-assistant-join=<id>.<token>`. */
|
|
3688
|
+
const NH_ASSISTANT_JOIN_FRAGMENT = 'nh-assistant-join';
|
|
3689
|
+
/** Fragment a clicked notification opens: `#nh-assistant-conversation=<id>`. */
|
|
3690
|
+
const NH_ASSISTANT_CONVERSATION_FRAGMENT = 'nh-assistant-conversation';
|
|
3691
|
+
/** Builds the default invitation link: the current page with the join fragment. */
|
|
3692
|
+
function nhAssistantShareLink(conversationId, token) {
|
|
3693
|
+
const location = globalThis.location;
|
|
3694
|
+
const page = location ? `${location.origin}${location.pathname}${location.search}` : '';
|
|
3695
|
+
return `${page}#${NH_ASSISTANT_JOIN_FRAGMENT}=${encodeURIComponent(conversationId)}.${encodeURIComponent(token)}`;
|
|
3696
|
+
}
|
|
3697
|
+
|
|
2600
3698
|
/**
|
|
2601
3699
|
* Optional icon classes of the host's icon library, for example `{ close: 'ph ph-x' }`.
|
|
2602
3700
|
* Icons without a class use the library's small built-in outline glyphs.
|
|
@@ -2621,7 +3719,11 @@ const builtInPaths = {
|
|
|
2621
3719
|
edit: 'M4 20h4L19 9l-4-4L4 16zM13.5 6.5l4 4',
|
|
2622
3720
|
refresh: 'M20 11a8 8 0 0 0-14.3-4.9L4 8M4 4v4h4M4 13a8 8 0 0 0 14.3 4.9L20 16M20 20v-4h-4',
|
|
2623
3721
|
link: 'M10 14a4 4 0 0 0 5.7 0l3-3a4 4 0 0 0-5.7-5.7l-1 1M14 10a4 4 0 0 0-5.7 0l-3 3a4 4 0 0 0 5.7 5.7l1-1',
|
|
2624
|
-
page: 'M6 3h8l4 4v14H6zM14 3v4h4M9 12h6M9 16h6'
|
|
3722
|
+
page: 'M6 3h8l4 4v14H6zM14 3v4h4M9 12h6M9 16h6',
|
|
3723
|
+
users: 'M9 11a3.5 3.5 0 1 0 0-7 3.5 3.5 0 1 0 0 7zM3 20c0-3.3 2.7-6 6-6s6 2.7 6 6M16 4.5a3.5 3.5 0 0 1 0 6.5M18 14.5c1.8.9 3 2.8 3 5',
|
|
3724
|
+
copy: 'M9 9h11v11H9zM5 15H4V4h11v1',
|
|
3725
|
+
bell: 'M6 16V11a6 6 0 0 1 12 0v5l2 2H4zM10 20a2 2 0 0 0 4 0',
|
|
3726
|
+
leave: 'M10 4H5v16h5M14 8l4 4-4 4M18 12H9'
|
|
2625
3727
|
};
|
|
2626
3728
|
/** Decorative icon. The surrounding control provides the accessible name. */
|
|
2627
3729
|
class NhAssistantIconComponent {
|
|
@@ -2631,8 +3733,8 @@ class NhAssistantIconComponent {
|
|
|
2631
3733
|
this.iconClass = computed(() => this.icons?.[this.name()] ?? null, ...(ngDevMode ? [{ debugName: "iconClass" }] : []));
|
|
2632
3734
|
this.path = computed(() => builtInPaths[this.name()], ...(ngDevMode ? [{ debugName: "path" }] : []));
|
|
2633
3735
|
}
|
|
2634
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
2635
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.
|
|
3736
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantIconComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
3737
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.33", type: NhAssistantIconComponent, isStandalone: true, selector: "nh-assistant-icon", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: `
|
|
2636
3738
|
@if (iconClass(); as iconClass) {
|
|
2637
3739
|
<i [class]="iconClass" aria-hidden="true"></i>
|
|
2638
3740
|
} @else {
|
|
@@ -2642,7 +3744,7 @@ class NhAssistantIconComponent {
|
|
|
2642
3744
|
}
|
|
2643
3745
|
`, isInline: true, styles: [":host{display:inline-flex;align-items:center;justify-content:center;line-height:1;font-size:1.15em}svg{display:block}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
2644
3746
|
}
|
|
2645
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
3747
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantIconComponent, decorators: [{
|
|
2646
3748
|
type: Component,
|
|
2647
3749
|
args: [{ selector: 'nh-assistant-icon', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
2648
3750
|
@if (iconClass(); as iconClass) {
|
|
@@ -2657,8 +3759,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImpo
|
|
|
2657
3759
|
|
|
2658
3760
|
/**
|
|
2659
3761
|
* Header button that opens the assistant panel. It renders nothing while the assistant is
|
|
2660
|
-
* disabled on the server or the access policy denies the user.
|
|
2661
|
-
* that waits
|
|
3762
|
+
* disabled on the server or the access policy denies the user. One small badge shows what needs
|
|
3763
|
+
* attention, in this order: an approval that waits, the number of unread conversations, or a
|
|
3764
|
+
* turn that runs. Projected content replaces the default icon.
|
|
2662
3765
|
*/
|
|
2663
3766
|
class NhAssistantLauncherComponent {
|
|
2664
3767
|
constructor() {
|
|
@@ -2666,6 +3769,16 @@ class NhAssistantLauncherComponent {
|
|
|
2666
3769
|
this.panel = inject(NhAssistantPanelService);
|
|
2667
3770
|
this.waitingForApproval = computed(() => this.store.pendingApproval() !== null ||
|
|
2668
3771
|
this.store.conversations().some(conversation => conversation.status === 'waiting-for-approval'), ...(ngDevMode ? [{ debugName: "waitingForApproval" }] : []));
|
|
3772
|
+
this.badge = computed(() => {
|
|
3773
|
+
if (this.waitingForApproval()) {
|
|
3774
|
+
return 'waiting';
|
|
3775
|
+
}
|
|
3776
|
+
if (this.store.unreadCount() > 0) {
|
|
3777
|
+
return 'unread';
|
|
3778
|
+
}
|
|
3779
|
+
return this.store.runningCount() > 0 || this.store.streaming() ? 'running' : null;
|
|
3780
|
+
}, ...(ngDevMode ? [{ debugName: "badge" }] : []));
|
|
3781
|
+
this.unreadLabel = computed(() => this.store.unreadCount() > 9 ? '9+' : String(this.store.unreadCount()), ...(ngDevMode ? [{ debugName: "unreadLabel" }] : []));
|
|
2669
3782
|
this.label = computed(() => this.panel.isOpen() ? 'nh-assistant.launcher.close' : 'nh-assistant.launcher.open', ...(ngDevMode ? [{ debugName: "label" }] : []));
|
|
2670
3783
|
// Browser only: the status request and access policy need the signed-in user.
|
|
2671
3784
|
afterNextRender(() => {
|
|
@@ -2677,8 +3790,8 @@ class NhAssistantLauncherComponent {
|
|
|
2677
3790
|
}
|
|
2678
3791
|
});
|
|
2679
3792
|
}
|
|
2680
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
2681
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.
|
|
3793
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantLauncherComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
3794
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.33", type: NhAssistantLauncherComponent, isStandalone: true, selector: "nh-assistant-launcher", ngImport: i0, template: `
|
|
2682
3795
|
@if (store.enabled()) {
|
|
2683
3796
|
<button
|
|
2684
3797
|
class="launcher"
|
|
@@ -2689,15 +3802,31 @@ class NhAssistantLauncherComponent {
|
|
|
2689
3802
|
[attr.title]="label() | translate"
|
|
2690
3803
|
(click)="panel.toggle()">
|
|
2691
3804
|
<ng-content><nh-assistant-icon name="assistant" /></ng-content>
|
|
3805
|
+
@switch (badge()) {
|
|
3806
|
+
@case ('waiting') {
|
|
3807
|
+
<span class="badge" data-state="waiting" aria-hidden="true"></span>
|
|
3808
|
+
}
|
|
3809
|
+
@case ('unread') {
|
|
3810
|
+
<span class="badge count" data-state="unread" aria-hidden="true">{{ unreadLabel() }}</span>
|
|
3811
|
+
}
|
|
3812
|
+
@case ('running') {
|
|
3813
|
+
<span class="badge" data-state="running" aria-hidden="true"></span>
|
|
3814
|
+
}
|
|
3815
|
+
}
|
|
2692
3816
|
@if (waitingForApproval()) {
|
|
2693
|
-
<span class="badge" aria-hidden="true"></span>
|
|
2694
3817
|
<span class="visually-hidden">{{ 'nh-assistant.launcher.pending-approval' | translate }}</span>
|
|
2695
3818
|
}
|
|
3819
|
+
@if (store.unreadCount() > 0) {
|
|
3820
|
+
<span class="visually-hidden">{{ 'nh-assistant.launcher.unread' | translate: { count: store.unreadCount() } }}</span>
|
|
3821
|
+
}
|
|
3822
|
+
@if (store.runningCount() > 0) {
|
|
3823
|
+
<span class="visually-hidden">{{ 'nh-assistant.launcher.running' | translate: { count: store.runningCount() } }}</span>
|
|
3824
|
+
}
|
|
2696
3825
|
</button>
|
|
2697
3826
|
}
|
|
2698
|
-
`, isInline: true, styles: [":host{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){:host{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}:host{display:inline-flex}.launcher{position:relative;display:inline-flex;align-items:center;justify-content:center;width:var(--nh-assistant-launcher-size, 40px);height:var(--nh-assistant-launcher-size, 40px);border:1px solid var(--nh-assistant-launcher-border, var(--nha-border));border-radius:var(--nha-radius-control);padding:0;color:var(--nh-assistant-launcher-color, var(--nha-text));background:var(--nh-assistant-launcher-background, transparent);font-size:18px;cursor:pointer}.launcher:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.launcher:hover,.launcher.open{border-color:var(--nha-highlight);color:var(--nha-highlight)}.badge{position:absolute;top:6px;right:6px;width:9px;height:9px;border:2px solid var(--nh-assistant-launcher-badge-ring, var(--nha-background));border-radius:50%;background:var(--nha-highlight)}.visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}\n"], dependencies: [{ kind: "component", type: NhAssistantIconComponent, selector: "nh-assistant-icon", inputs: ["name"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
3827
|
+
`, isInline: true, styles: [":host{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){:host{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}:host{display:inline-flex}.launcher{position:relative;display:inline-flex;align-items:center;justify-content:center;width:var(--nh-assistant-launcher-size, 40px);height:var(--nh-assistant-launcher-size, 40px);border:1px solid var(--nh-assistant-launcher-border, var(--nha-border));border-radius:var(--nha-radius-control);padding:0;color:var(--nh-assistant-launcher-color, var(--nha-text));background:var(--nh-assistant-launcher-background, transparent);font-size:18px;cursor:pointer}.launcher:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.launcher:hover,.launcher.open{border-color:var(--nha-highlight);color:var(--nha-highlight)}.badge{position:absolute;top:6px;right:6px;width:9px;height:9px;border:2px solid var(--nh-assistant-launcher-badge-ring, var(--nha-background));border-radius:50%;background:var(--nha-highlight)}.badge[data-state=waiting]{background:var(--nha-warning)}.badge.count{top:2px;right:2px;display:inline-flex;align-items:center;justify-content:center;width:auto;min-width:16px;height:16px;border-radius:999px;padding:0 3px;color:var(--nha-accent-contrast);font-size:10px;font-weight:700;line-height:1}.badge[data-state=running]{border-color:color-mix(in srgb,var(--nha-highlight) 30%,transparent);border-top-color:var(--nha-highlight);background:var(--nh-assistant-launcher-badge-ring, var(--nha-background));animation:nh-assistant-spin .9s linear infinite}@media(prefers-reduced-motion:reduce){.badge[data-state=running]{border-color:var(--nha-highlight);animation:none}}@keyframes nh-assistant-spin{to{transform:rotate(360deg)}}.visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}\n"], dependencies: [{ kind: "component", type: NhAssistantIconComponent, selector: "nh-assistant-icon", inputs: ["name"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
2699
3828
|
}
|
|
2700
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
3829
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantLauncherComponent, decorators: [{
|
|
2701
3830
|
type: Component,
|
|
2702
3831
|
args: [{ selector: 'nh-assistant-launcher', standalone: true, imports: [TranslatePipe, NhAssistantIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
2703
3832
|
@if (store.enabled()) {
|
|
@@ -2710,13 +3839,29 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImpo
|
|
|
2710
3839
|
[attr.title]="label() | translate"
|
|
2711
3840
|
(click)="panel.toggle()">
|
|
2712
3841
|
<ng-content><nh-assistant-icon name="assistant" /></ng-content>
|
|
3842
|
+
@switch (badge()) {
|
|
3843
|
+
@case ('waiting') {
|
|
3844
|
+
<span class="badge" data-state="waiting" aria-hidden="true"></span>
|
|
3845
|
+
}
|
|
3846
|
+
@case ('unread') {
|
|
3847
|
+
<span class="badge count" data-state="unread" aria-hidden="true">{{ unreadLabel() }}</span>
|
|
3848
|
+
}
|
|
3849
|
+
@case ('running') {
|
|
3850
|
+
<span class="badge" data-state="running" aria-hidden="true"></span>
|
|
3851
|
+
}
|
|
3852
|
+
}
|
|
2713
3853
|
@if (waitingForApproval()) {
|
|
2714
|
-
<span class="badge" aria-hidden="true"></span>
|
|
2715
3854
|
<span class="visually-hidden">{{ 'nh-assistant.launcher.pending-approval' | translate }}</span>
|
|
2716
3855
|
}
|
|
3856
|
+
@if (store.unreadCount() > 0) {
|
|
3857
|
+
<span class="visually-hidden">{{ 'nh-assistant.launcher.unread' | translate: { count: store.unreadCount() } }}</span>
|
|
3858
|
+
}
|
|
3859
|
+
@if (store.runningCount() > 0) {
|
|
3860
|
+
<span class="visually-hidden">{{ 'nh-assistant.launcher.running' | translate: { count: store.runningCount() } }}</span>
|
|
3861
|
+
}
|
|
2717
3862
|
</button>
|
|
2718
3863
|
}
|
|
2719
|
-
`, styles: [":host{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){:host{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}:host{display:inline-flex}.launcher{position:relative;display:inline-flex;align-items:center;justify-content:center;width:var(--nh-assistant-launcher-size, 40px);height:var(--nh-assistant-launcher-size, 40px);border:1px solid var(--nh-assistant-launcher-border, var(--nha-border));border-radius:var(--nha-radius-control);padding:0;color:var(--nh-assistant-launcher-color, var(--nha-text));background:var(--nh-assistant-launcher-background, transparent);font-size:18px;cursor:pointer}.launcher:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.launcher:hover,.launcher.open{border-color:var(--nha-highlight);color:var(--nha-highlight)}.badge{position:absolute;top:6px;right:6px;width:9px;height:9px;border:2px solid var(--nh-assistant-launcher-badge-ring, var(--nha-background));border-radius:50%;background:var(--nha-highlight)}.visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}\n"] }]
|
|
3864
|
+
`, styles: [":host{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){:host{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}:host{display:inline-flex}.launcher{position:relative;display:inline-flex;align-items:center;justify-content:center;width:var(--nh-assistant-launcher-size, 40px);height:var(--nh-assistant-launcher-size, 40px);border:1px solid var(--nh-assistant-launcher-border, var(--nha-border));border-radius:var(--nha-radius-control);padding:0;color:var(--nh-assistant-launcher-color, var(--nha-text));background:var(--nh-assistant-launcher-background, transparent);font-size:18px;cursor:pointer}.launcher:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.launcher:hover,.launcher.open{border-color:var(--nha-highlight);color:var(--nha-highlight)}.badge{position:absolute;top:6px;right:6px;width:9px;height:9px;border:2px solid var(--nh-assistant-launcher-badge-ring, var(--nha-background));border-radius:50%;background:var(--nha-highlight)}.badge[data-state=waiting]{background:var(--nha-warning)}.badge.count{top:2px;right:2px;display:inline-flex;align-items:center;justify-content:center;width:auto;min-width:16px;height:16px;border-radius:999px;padding:0 3px;color:var(--nha-accent-contrast);font-size:10px;font-weight:700;line-height:1}.badge[data-state=running]{border-color:color-mix(in srgb,var(--nha-highlight) 30%,transparent);border-top-color:var(--nha-highlight);background:var(--nh-assistant-launcher-badge-ring, var(--nha-background));animation:nh-assistant-spin .9s linear infinite}@media(prefers-reduced-motion:reduce){.badge[data-state=running]{border-color:var(--nha-highlight);animation:none}}@keyframes nh-assistant-spin{to{transform:rotate(360deg)}}.visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}\n"] }]
|
|
2720
3865
|
}], ctorParameters: () => [] });
|
|
2721
3866
|
|
|
2722
3867
|
/**
|
|
@@ -2760,15 +3905,81 @@ class NhAssistantTranslatePipe {
|
|
|
2760
3905
|
}
|
|
2761
3906
|
return fallback;
|
|
2762
3907
|
}
|
|
2763
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
2764
|
-
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "20.3.
|
|
3908
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantTranslatePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
|
|
3909
|
+
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantTranslatePipe, isStandalone: true, name: "nhAssistantTranslate", pure: false }); }
|
|
2765
3910
|
}
|
|
2766
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
3911
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantTranslatePipe, decorators: [{
|
|
2767
3912
|
type: Pipe,
|
|
2768
3913
|
args: [{ name: 'nhAssistantTranslate', standalone: true, pure: false }]
|
|
2769
3914
|
}], ctorParameters: () => [] });
|
|
2770
3915
|
|
|
2771
|
-
|
|
3916
|
+
/**
|
|
3917
|
+
* One slim row of the other conversations that need attention: a running turn, an approval that
|
|
3918
|
+
* waits, or an unread answer. It renders nothing when there are none, so it costs no space in the
|
|
3919
|
+
* usual case. Selecting a chip opens that conversation; its turn keeps running meanwhile.
|
|
3920
|
+
*/
|
|
3921
|
+
class NhAssistantActivityComponent {
|
|
3922
|
+
constructor() {
|
|
3923
|
+
this.items = input.required(...(ngDevMode ? [{ debugName: "items" }] : []));
|
|
3924
|
+
this.conversationSelect = output();
|
|
3925
|
+
/** The open conversation already shows its own state. */
|
|
3926
|
+
this.others = computed(() => this.items().filter(item => !item.active), ...(ngDevMode ? [{ debugName: "others" }] : []));
|
|
3927
|
+
}
|
|
3928
|
+
state(item) {
|
|
3929
|
+
if (item.status === 'running') {
|
|
3930
|
+
return 'running';
|
|
3931
|
+
}
|
|
3932
|
+
return item.status === 'waiting-for-approval' ? 'waiting' : 'unread';
|
|
3933
|
+
}
|
|
3934
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantActivityComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
3935
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.33", type: NhAssistantActivityComponent, isStandalone: true, selector: "nh-assistant-activity", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { conversationSelect: "conversationSelect" }, ngImport: i0, template: `
|
|
3936
|
+
@if (others().length > 0) {
|
|
3937
|
+
<nav class="strip" [attr.aria-label]="'nh-assistant.activity.label' | translate">
|
|
3938
|
+
@for (item of others(); track item.id) {
|
|
3939
|
+
<button
|
|
3940
|
+
class="chip"
|
|
3941
|
+
type="button"
|
|
3942
|
+
[attr.data-state]="state(item)"
|
|
3943
|
+
[attr.title]="(item.title || ('nh-assistant.conversation-list.untitled' | translate)) + ' — ' + ('nh-assistant.activity.' + state(item) | translate)"
|
|
3944
|
+
(click)="conversationSelect.emit(item.id)">
|
|
3945
|
+
<span class="indicator" aria-hidden="true"></span>
|
|
3946
|
+
<span class="title">{{ item.title || ('nh-assistant.conversation-list.untitled' | translate) }}</span>
|
|
3947
|
+
@if (item.shared) {
|
|
3948
|
+
<nh-assistant-icon class="shared" name="users" />
|
|
3949
|
+
}
|
|
3950
|
+
<span class="visually-hidden">{{ 'nh-assistant.activity.' + state(item) | translate }}</span>
|
|
3951
|
+
</button>
|
|
3952
|
+
}
|
|
3953
|
+
</nav>
|
|
3954
|
+
}
|
|
3955
|
+
`, isInline: true, styles: [":host{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){:host{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}:host{display:block;min-width:0}.strip{display:flex;gap:6px;overflow-x:auto;padding:0 12px 8px 16px;scrollbar-width:none}.strip::-webkit-scrollbar{display:none}.chip{display:inline-flex;flex:none;align-items:center;gap:6px;max-width:180px;height:26px;border:1px solid var(--nha-border);border-radius:999px;padding:0 10px 0 8px;color:var(--nha-text);background:var(--nha-surface);font:inherit;font-size:12px;cursor:pointer}.chip:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.chip:hover{border-color:var(--nha-highlight)}.title{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.shared{flex:none;color:var(--nha-text-muted);font-size:12px}.indicator{flex:none;width:8px;height:8px;border-radius:50%;background:var(--nha-accent)}.chip[data-state=unread] .indicator{background:var(--nha-highlight)}.chip[data-state=waiting] .indicator{background:var(--nha-warning)}.chip[data-state=running] .indicator{width:10px;height:10px;border:2px solid color-mix(in srgb,var(--nha-highlight) 30%,transparent);border-top-color:var(--nha-highlight);background:transparent;animation:nh-assistant-spin .9s linear infinite}@media(prefers-reduced-motion:reduce){.chip[data-state=running] .indicator{border-color:var(--nha-highlight);animation:none}}@keyframes nh-assistant-spin{to{transform:rotate(360deg)}}.visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}\n"], dependencies: [{ kind: "component", type: NhAssistantIconComponent, selector: "nh-assistant-icon", inputs: ["name"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
3956
|
+
}
|
|
3957
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantActivityComponent, decorators: [{
|
|
3958
|
+
type: Component,
|
|
3959
|
+
args: [{ selector: 'nh-assistant-activity', standalone: true, imports: [TranslatePipe, NhAssistantIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
3960
|
+
@if (others().length > 0) {
|
|
3961
|
+
<nav class="strip" [attr.aria-label]="'nh-assistant.activity.label' | translate">
|
|
3962
|
+
@for (item of others(); track item.id) {
|
|
3963
|
+
<button
|
|
3964
|
+
class="chip"
|
|
3965
|
+
type="button"
|
|
3966
|
+
[attr.data-state]="state(item)"
|
|
3967
|
+
[attr.title]="(item.title || ('nh-assistant.conversation-list.untitled' | translate)) + ' — ' + ('nh-assistant.activity.' + state(item) | translate)"
|
|
3968
|
+
(click)="conversationSelect.emit(item.id)">
|
|
3969
|
+
<span class="indicator" aria-hidden="true"></span>
|
|
3970
|
+
<span class="title">{{ item.title || ('nh-assistant.conversation-list.untitled' | translate) }}</span>
|
|
3971
|
+
@if (item.shared) {
|
|
3972
|
+
<nh-assistant-icon class="shared" name="users" />
|
|
3973
|
+
}
|
|
3974
|
+
<span class="visually-hidden">{{ 'nh-assistant.activity.' + state(item) | translate }}</span>
|
|
3975
|
+
</button>
|
|
3976
|
+
}
|
|
3977
|
+
</nav>
|
|
3978
|
+
}
|
|
3979
|
+
`, styles: [":host{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){:host{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}:host{display:block;min-width:0}.strip{display:flex;gap:6px;overflow-x:auto;padding:0 12px 8px 16px;scrollbar-width:none}.strip::-webkit-scrollbar{display:none}.chip{display:inline-flex;flex:none;align-items:center;gap:6px;max-width:180px;height:26px;border:1px solid var(--nha-border);border-radius:999px;padding:0 10px 0 8px;color:var(--nha-text);background:var(--nha-surface);font:inherit;font-size:12px;cursor:pointer}.chip:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.chip:hover{border-color:var(--nha-highlight)}.title{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.shared{flex:none;color:var(--nha-text-muted);font-size:12px}.indicator{flex:none;width:8px;height:8px;border-radius:50%;background:var(--nha-accent)}.chip[data-state=unread] .indicator{background:var(--nha-highlight)}.chip[data-state=waiting] .indicator{background:var(--nha-warning)}.chip[data-state=running] .indicator{width:10px;height:10px;border:2px solid color-mix(in srgb,var(--nha-highlight) 30%,transparent);border-top-color:var(--nha-highlight);background:transparent;animation:nh-assistant-spin .9s linear infinite}@media(prefers-reduced-motion:reduce){.chip[data-state=running] .indicator{border-color:var(--nha-highlight);animation:none}}@keyframes nh-assistant-spin{to{transform:rotate(360deg)}}.visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}\n"] }]
|
|
3980
|
+
}], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: true }] }], conversationSelect: [{ type: i0.Output, args: ["conversationSelect"] }] } });
|
|
3981
|
+
|
|
3982
|
+
let nextId$6 = 0;
|
|
2772
3983
|
/**
|
|
2773
3984
|
* Chooses the agent for new conversations. Agent names come from the host's translation
|
|
2774
3985
|
* keys; agents created by an administrator carry literal text, which is shown as is.
|
|
@@ -2779,14 +3990,14 @@ class NhAssistantAgentPickerComponent {
|
|
|
2779
3990
|
this.selectedAgentId = input(null, ...(ngDevMode ? [{ debugName: "selectedAgentId" }] : []));
|
|
2780
3991
|
this.disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
|
|
2781
3992
|
this.agentChange = output();
|
|
2782
|
-
this.selectId = `nh-assistant-agent-picker-${nextId$
|
|
3993
|
+
this.selectId = `nh-assistant-agent-picker-${nextId$6++}`;
|
|
2783
3994
|
this.selected = computed(() => this.agents().find(agent => agent.id === this.selectedAgentId()) ?? null, ...(ngDevMode ? [{ debugName: "selected" }] : []));
|
|
2784
3995
|
}
|
|
2785
3996
|
onChange(event) {
|
|
2786
3997
|
this.agentChange.emit(event.target.value);
|
|
2787
3998
|
}
|
|
2788
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
2789
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.
|
|
3999
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantAgentPickerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
4000
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.33", type: NhAssistantAgentPickerComponent, isStandalone: true, selector: "nh-assistant-agent-picker", inputs: { agents: { classPropertyName: "agents", publicName: "agents", isSignal: true, isRequired: true, transformFunction: null }, selectedAgentId: { classPropertyName: "selectedAgentId", publicName: "selectedAgentId", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { agentChange: "agentChange" }, ngImport: i0, template: `
|
|
2790
4001
|
<label class="visually-hidden" [for]="selectId">{{ 'nh-assistant.agent-picker.label' | translate }}</label>
|
|
2791
4002
|
<select
|
|
2792
4003
|
[id]="selectId"
|
|
@@ -2806,7 +4017,7 @@ class NhAssistantAgentPickerComponent {
|
|
|
2806
4017
|
}
|
|
2807
4018
|
`, isInline: true, styles: [":host{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){:host{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}:host{display:flex;flex-direction:column;gap:2px;min-width:0}select{align-self:flex-start;max-width:100%;min-height:32px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:4px 28px 4px 10px;color:var(--nha-text);background:var(--nha-background);font:inherit;font-size:13px;font-weight:650}select:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}select:disabled{opacity:1;cursor:default}.capability{padding-left:2px;color:var(--nha-text-muted);font-size:11px}.capability[data-can-mutate=true]{color:var(--nha-warning)}.visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}\n"], dependencies: [{ kind: "pipe", type: TranslatePipe, name: "translate" }, { kind: "pipe", type: NhAssistantTranslatePipe, name: "nhAssistantTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
2808
4019
|
}
|
|
2809
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
4020
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantAgentPickerComponent, decorators: [{
|
|
2810
4021
|
type: Component,
|
|
2811
4022
|
args: [{ selector: 'nh-assistant-agent-picker', standalone: true, imports: [TranslatePipe, NhAssistantTranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
2812
4023
|
<label class="visually-hidden" [for]="selectId">{{ 'nh-assistant.agent-picker.label' | translate }}</label>
|
|
@@ -2829,7 +4040,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImpo
|
|
|
2829
4040
|
`, styles: [":host{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){:host{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}:host{display:flex;flex-direction:column;gap:2px;min-width:0}select{align-self:flex-start;max-width:100%;min-height:32px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:4px 28px 4px 10px;color:var(--nha-text);background:var(--nha-background);font:inherit;font-size:13px;font-weight:650}select:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}select:disabled{opacity:1;cursor:default}.capability{padding-left:2px;color:var(--nha-text-muted);font-size:11px}.capability[data-can-mutate=true]{color:var(--nha-warning)}.visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}\n"] }]
|
|
2830
4041
|
}], propDecorators: { agents: [{ type: i0.Input, args: [{ isSignal: true, alias: "agents", required: true }] }], selectedAgentId: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedAgentId", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], agentChange: [{ type: i0.Output, args: ["agentChange"] }] } });
|
|
2831
4042
|
|
|
2832
|
-
let nextId$
|
|
4043
|
+
let nextId$5 = 0;
|
|
2833
4044
|
/**
|
|
2834
4045
|
* Message input. Enter sends when sending is available; Shift+Enter inserts a new line.
|
|
2835
4046
|
* Drafting remains available while the assistant runs or waits for approval.
|
|
@@ -2844,6 +4055,10 @@ class NhAssistantComposerComponent {
|
|
|
2844
4055
|
this.busy = input(false, ...(ngDevMode ? [{ debugName: "busy" }] : []));
|
|
2845
4056
|
/** Announces why sending is temporarily unavailable. */
|
|
2846
4057
|
this.status = input(null, ...(ngDevMode ? [{ debugName: "status" }] : []));
|
|
4058
|
+
/** Name of the participant whose turn runs or waits, when it is someone else. */
|
|
4059
|
+
this.statusActor = input(null, ...(ngDevMode ? [{ debugName: "statusActor" }] : []));
|
|
4060
|
+
/** False when the user may not stop the running turn, such as another participant's turn. */
|
|
4061
|
+
this.canStop = input(true, ...(ngDevMode ? [{ debugName: "canStop" }] : []));
|
|
2847
4062
|
this.maxLength = input(null, ...(ngDevMode ? [{ debugName: "maxLength" }] : []));
|
|
2848
4063
|
/** Text to put back into the input, for example a message the server did not accept. */
|
|
2849
4064
|
this.restore = input(null, ...(ngDevMode ? [{ debugName: "restore" }] : []));
|
|
@@ -2854,7 +4069,7 @@ class NhAssistantComposerComponent {
|
|
|
2854
4069
|
this.textarea = viewChild.required('textarea');
|
|
2855
4070
|
this.ownedFocus = false;
|
|
2856
4071
|
this.previousBusy = false;
|
|
2857
|
-
this.inputId = `nh-assistant-composer-${nextId$
|
|
4072
|
+
this.inputId = `nh-assistant-composer-${nextId$5++}`;
|
|
2858
4073
|
this.hintId = `${this.inputId}-hint`;
|
|
2859
4074
|
this.text = signal('', ...(ngDevMode ? [{ debugName: "text" }] : []));
|
|
2860
4075
|
this.length = computed(() => this.text().trim().length, ...(ngDevMode ? [{ debugName: "length" }] : []));
|
|
@@ -2925,13 +4140,13 @@ class NhAssistantComposerComponent {
|
|
|
2925
4140
|
this.textarea().nativeElement.focus({ preventScroll: true });
|
|
2926
4141
|
}
|
|
2927
4142
|
}
|
|
2928
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
2929
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.
|
|
4143
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantComposerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
4144
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.33", type: NhAssistantComposerComponent, isStandalone: true, selector: "nh-assistant-composer", inputs: { disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, sendDisabled: { classPropertyName: "sendDisabled", publicName: "sendDisabled", isSignal: true, isRequired: false, transformFunction: null }, busy: { classPropertyName: "busy", publicName: "busy", isSignal: true, isRequired: false, transformFunction: null }, status: { classPropertyName: "status", publicName: "status", isSignal: true, isRequired: false, transformFunction: null }, statusActor: { classPropertyName: "statusActor", publicName: "statusActor", isSignal: true, isRequired: false, transformFunction: null }, canStop: { classPropertyName: "canStop", publicName: "canStop", isSignal: true, isRequired: false, transformFunction: null }, maxLength: { classPropertyName: "maxLength", publicName: "maxLength", isSignal: true, isRequired: false, transformFunction: null }, restore: { classPropertyName: "restore", publicName: "restore", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { send: "send", cancel: "cancel", restored: "restored" }, host: { listeners: { "focusin": "onFocusIn()", "focusout": "onFocusOut($event)" } }, viewQueries: [{ propertyName: "textarea", first: true, predicate: ["textarea"], descendants: true, isSignal: true }], ngImport: i0, template: "<form class=\"composer\" (submit)=\"onSubmit($event)\">\n <label class=\"visually-hidden\" [for]=\"inputId\">{{ 'nh-assistant.composer.label' | translate }}</label>\n <textarea\n #textarea\n data-nh-assistant-autofocus\n rows=\"2\"\n [id]=\"inputId\"\n [value]=\"text()\"\n [disabled]=\"disabled()\"\n [attr.placeholder]=\"'nh-assistant.composer.placeholder' | translate\"\n [attr.aria-describedby]=\"hintId\"\n [attr.aria-invalid]=\"tooLong() ? true : null\"\n (input)=\"onInput($event)\"\n (keydown)=\"onKeydown($event)\"></textarea>\n\n <div class=\"actions\">\n @if (busy() && canStop()) {\n <button\n class=\"stop\"\n type=\"button\"\n [attr.aria-label]=\"'nh-assistant.composer.stop' | translate\"\n [attr.title]=\"'nh-assistant.composer.stop' | translate\"\n (click)=\"cancel.emit()\">\n <nh-assistant-icon name=\"stop\" />\n </button>\n } @else {\n <button\n class=\"send\"\n type=\"submit\"\n [disabled]=\"!canSend()\"\n [attr.aria-label]=\"'nh-assistant.composer.send' | translate\"\n [attr.title]=\"'nh-assistant.composer.send' | translate\">\n <nh-assistant-icon name=\"send\" />\n </button>\n }\n </div>\n</form>\n\n@if (status(); as status) {\n <p class=\"composer-status\" role=\"status\" aria-live=\"polite\">\n @if (statusActor(); as name) {\n {{ 'nh-assistant.composer.status.' + status + '-by' | translate: { name } }}\n } @else {\n {{ 'nh-assistant.composer.status.' + status | translate }}\n }\n </p>\n}\n\n<p class=\"hint\" [id]=\"hintId\">\n @if (tooLong()) {\n <span class=\"error\">{{ 'nh-assistant.composer.too-long' | translate }}</span>\n } @else {\n <span>{{ 'nh-assistant.composer.hint' | translate }}</span>\n }\n @if (showCounter()) {\n <span class=\"counter\" [class.error]=\"tooLong()\">\n {{ 'nh-assistant.composer.characters' | translate: { count: length(), max: maxLength() } }}\n </span>\n }\n</p>\n", styles: [":host{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){:host{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}:host{display:block}.composer{display:flex;align-items:flex-end;gap:8px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px 6px 6px 12px;background:var(--nha-background)}.composer:focus-within{border-color:var(--nha-focus);box-shadow:0 0 0 2px color-mix(in srgb,var(--nha-focus) 30%,transparent)}textarea{flex:1;min-width:0;min-height:44px;max-height:180px;border:0;padding:6px 0;color:var(--nha-text);background:transparent;font:inherit;font-size:14px;line-height:1.45;resize:vertical;outline:none}textarea::placeholder{color:var(--nha-text-muted);opacity:1}textarea:disabled{cursor:not-allowed}.actions{display:flex}.send,.stop{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:1px solid transparent;border-radius:var(--nha-radius-control);color:var(--nha-text-muted);background:transparent;cursor:pointer;width:40px;height:40px}.send:focus-visible,.stop:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.send:hover:not(:disabled),.stop:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}.send:disabled,.stop:disabled{cursor:not-allowed;opacity:.5}.send:not(:disabled){color:var(--nha-accent-contrast);background:var(--nha-accent)}.send:not(:disabled):hover{color:var(--nha-accent-contrast);background:var(--nha-accent);filter:brightness(1.12)}.stop{color:var(--nha-danger);border-color:var(--nha-danger)}.hint,.composer-status{display:flex;justify-content:space-between;gap:8px;margin:6px 2px 0;color:var(--nha-text-muted);font-size:11px}.composer-status{margin-bottom:0}.counter{flex:none;font-variant-numeric:tabular-nums}.error{color:var(--nha-danger)}.visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}\n"], dependencies: [{ kind: "component", type: NhAssistantIconComponent, selector: "nh-assistant-icon", inputs: ["name"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
2930
4145
|
}
|
|
2931
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
4146
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantComposerComponent, decorators: [{
|
|
2932
4147
|
type: Component,
|
|
2933
|
-
args: [{ selector: 'nh-assistant-composer', standalone: true, imports: [TranslatePipe, NhAssistantIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<form class=\"composer\" (submit)=\"onSubmit($event)\">\n <label class=\"visually-hidden\" [for]=\"inputId\">{{ 'nh-assistant.composer.label' | translate }}</label>\n <textarea\n #textarea\n data-nh-assistant-autofocus\n rows=\"2\"\n [id]=\"inputId\"\n [value]=\"text()\"\n [disabled]=\"disabled()\"\n [attr.placeholder]=\"'nh-assistant.composer.placeholder' | translate\"\n [attr.aria-describedby]=\"hintId\"\n [attr.aria-invalid]=\"tooLong() ? true : null\"\n (input)=\"onInput($event)\"\n (keydown)=\"onKeydown($event)\"></textarea>\n\n <div class=\"actions\">\n @if (busy()) {\n <button\n class=\"stop\"\n type=\"button\"\n [attr.aria-label]=\"'nh-assistant.composer.stop' | translate\"\n [attr.title]=\"'nh-assistant.composer.stop' | translate\"\n (click)=\"cancel.emit()\">\n <nh-assistant-icon name=\"stop\" />\n </button>\n } @else {\n <button\n class=\"send\"\n type=\"submit\"\n [disabled]=\"!canSend()\"\n [attr.aria-label]=\"'nh-assistant.composer.send' | translate\"\n [attr.title]=\"'nh-assistant.composer.send' | translate\">\n <nh-assistant-icon name=\"send\" />\n </button>\n }\n </div>\n</form>\n\n@if (status(); as status) {\n <p class=\"composer-status\" role=\"status\" aria-live=\"polite\">\n {{ 'nh-assistant.composer.status.' + status | translate }}\n </p>\n}\n\n<p class=\"hint\" [id]=\"hintId\">\n @if (tooLong()) {\n <span class=\"error\">{{ 'nh-assistant.composer.too-long' | translate }}</span>\n } @else {\n <span>{{ 'nh-assistant.composer.hint' | translate }}</span>\n }\n @if (showCounter()) {\n <span class=\"counter\" [class.error]=\"tooLong()\">\n {{ 'nh-assistant.composer.characters' | translate: { count: length(), max: maxLength() } }}\n </span>\n }\n</p>\n", styles: [":host{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){:host{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}:host{display:block}.composer{display:flex;align-items:flex-end;gap:8px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px 6px 6px 12px;background:var(--nha-background)}.composer:focus-within{border-color:var(--nha-focus);box-shadow:0 0 0 2px color-mix(in srgb,var(--nha-focus) 30%,transparent)}textarea{flex:1;min-width:0;min-height:44px;max-height:180px;border:0;padding:6px 0;color:var(--nha-text);background:transparent;font:inherit;font-size:14px;line-height:1.45;resize:vertical;outline:none}textarea::placeholder{color:var(--nha-text-muted);opacity:1}textarea:disabled{cursor:not-allowed}.actions{display:flex}.send,.stop{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:1px solid transparent;border-radius:var(--nha-radius-control);color:var(--nha-text-muted);background:transparent;cursor:pointer;width:40px;height:40px}.send:focus-visible,.stop:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.send:hover:not(:disabled),.stop:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}.send:disabled,.stop:disabled{cursor:not-allowed;opacity:.5}.send:not(:disabled){color:var(--nha-accent-contrast);background:var(--nha-accent)}.send:not(:disabled):hover{color:var(--nha-accent-contrast);background:var(--nha-accent);filter:brightness(1.12)}.stop{color:var(--nha-danger);border-color:var(--nha-danger)}.hint,.composer-status{display:flex;justify-content:space-between;gap:8px;margin:6px 2px 0;color:var(--nha-text-muted);font-size:11px}.composer-status{margin-bottom:0}.counter{flex:none;font-variant-numeric:tabular-nums}.error{color:var(--nha-danger)}.visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}\n"] }]
|
|
2934
|
-
}], ctorParameters: () => [], propDecorators: { disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], sendDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "sendDisabled", required: false }] }], busy: [{ type: i0.Input, args: [{ isSignal: true, alias: "busy", required: false }] }], status: [{ type: i0.Input, args: [{ isSignal: true, alias: "status", required: false }] }], maxLength: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxLength", required: false }] }], restore: [{ type: i0.Input, args: [{ isSignal: true, alias: "restore", required: false }] }], send: [{ type: i0.Output, args: ["send"] }], cancel: [{ type: i0.Output, args: ["cancel"] }], restored: [{ type: i0.Output, args: ["restored"] }], textarea: [{ type: i0.ViewChild, args: ['textarea', { isSignal: true }] }], onFocusIn: [{
|
|
4148
|
+
args: [{ selector: 'nh-assistant-composer', standalone: true, imports: [TranslatePipe, NhAssistantIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<form class=\"composer\" (submit)=\"onSubmit($event)\">\n <label class=\"visually-hidden\" [for]=\"inputId\">{{ 'nh-assistant.composer.label' | translate }}</label>\n <textarea\n #textarea\n data-nh-assistant-autofocus\n rows=\"2\"\n [id]=\"inputId\"\n [value]=\"text()\"\n [disabled]=\"disabled()\"\n [attr.placeholder]=\"'nh-assistant.composer.placeholder' | translate\"\n [attr.aria-describedby]=\"hintId\"\n [attr.aria-invalid]=\"tooLong() ? true : null\"\n (input)=\"onInput($event)\"\n (keydown)=\"onKeydown($event)\"></textarea>\n\n <div class=\"actions\">\n @if (busy() && canStop()) {\n <button\n class=\"stop\"\n type=\"button\"\n [attr.aria-label]=\"'nh-assistant.composer.stop' | translate\"\n [attr.title]=\"'nh-assistant.composer.stop' | translate\"\n (click)=\"cancel.emit()\">\n <nh-assistant-icon name=\"stop\" />\n </button>\n } @else {\n <button\n class=\"send\"\n type=\"submit\"\n [disabled]=\"!canSend()\"\n [attr.aria-label]=\"'nh-assistant.composer.send' | translate\"\n [attr.title]=\"'nh-assistant.composer.send' | translate\">\n <nh-assistant-icon name=\"send\" />\n </button>\n }\n </div>\n</form>\n\n@if (status(); as status) {\n <p class=\"composer-status\" role=\"status\" aria-live=\"polite\">\n @if (statusActor(); as name) {\n {{ 'nh-assistant.composer.status.' + status + '-by' | translate: { name } }}\n } @else {\n {{ 'nh-assistant.composer.status.' + status | translate }}\n }\n </p>\n}\n\n<p class=\"hint\" [id]=\"hintId\">\n @if (tooLong()) {\n <span class=\"error\">{{ 'nh-assistant.composer.too-long' | translate }}</span>\n } @else {\n <span>{{ 'nh-assistant.composer.hint' | translate }}</span>\n }\n @if (showCounter()) {\n <span class=\"counter\" [class.error]=\"tooLong()\">\n {{ 'nh-assistant.composer.characters' | translate: { count: length(), max: maxLength() } }}\n </span>\n }\n</p>\n", styles: [":host{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){:host{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}:host{display:block}.composer{display:flex;align-items:flex-end;gap:8px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px 6px 6px 12px;background:var(--nha-background)}.composer:focus-within{border-color:var(--nha-focus);box-shadow:0 0 0 2px color-mix(in srgb,var(--nha-focus) 30%,transparent)}textarea{flex:1;min-width:0;min-height:44px;max-height:180px;border:0;padding:6px 0;color:var(--nha-text);background:transparent;font:inherit;font-size:14px;line-height:1.45;resize:vertical;outline:none}textarea::placeholder{color:var(--nha-text-muted);opacity:1}textarea:disabled{cursor:not-allowed}.actions{display:flex}.send,.stop{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:1px solid transparent;border-radius:var(--nha-radius-control);color:var(--nha-text-muted);background:transparent;cursor:pointer;width:40px;height:40px}.send:focus-visible,.stop:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.send:hover:not(:disabled),.stop:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}.send:disabled,.stop:disabled{cursor:not-allowed;opacity:.5}.send:not(:disabled){color:var(--nha-accent-contrast);background:var(--nha-accent)}.send:not(:disabled):hover{color:var(--nha-accent-contrast);background:var(--nha-accent);filter:brightness(1.12)}.stop{color:var(--nha-danger);border-color:var(--nha-danger)}.hint,.composer-status{display:flex;justify-content:space-between;gap:8px;margin:6px 2px 0;color:var(--nha-text-muted);font-size:11px}.composer-status{margin-bottom:0}.counter{flex:none;font-variant-numeric:tabular-nums}.error{color:var(--nha-danger)}.visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}\n"] }]
|
|
4149
|
+
}], ctorParameters: () => [], propDecorators: { disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], sendDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "sendDisabled", required: false }] }], busy: [{ type: i0.Input, args: [{ isSignal: true, alias: "busy", required: false }] }], status: [{ type: i0.Input, args: [{ isSignal: true, alias: "status", required: false }] }], statusActor: [{ type: i0.Input, args: [{ isSignal: true, alias: "statusActor", required: false }] }], canStop: [{ type: i0.Input, args: [{ isSignal: true, alias: "canStop", required: false }] }], maxLength: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxLength", required: false }] }], restore: [{ type: i0.Input, args: [{ isSignal: true, alias: "restore", required: false }] }], send: [{ type: i0.Output, args: ["send"] }], cancel: [{ type: i0.Output, args: ["cancel"] }], restored: [{ type: i0.Output, args: ["restored"] }], textarea: [{ type: i0.ViewChild, args: ['textarea', { isSignal: true }] }], onFocusIn: [{
|
|
2935
4150
|
type: HostListener,
|
|
2936
4151
|
args: ['focusin']
|
|
2937
4152
|
}], onFocusOut: [{
|
|
@@ -2939,7 +4154,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImpo
|
|
|
2939
4154
|
args: ['focusout', ['$event']]
|
|
2940
4155
|
}] } });
|
|
2941
4156
|
|
|
2942
|
-
/**
|
|
4157
|
+
/**
|
|
4158
|
+
* The user's own and shared conversations with loading and empty states; selecting one opens it.
|
|
4159
|
+
* Each row marks a running turn, a waiting approval and unread messages. A participant leaves a
|
|
4160
|
+
* shared conversation instead of deleting it.
|
|
4161
|
+
*/
|
|
2943
4162
|
class NhAssistantConversationListComponent {
|
|
2944
4163
|
constructor() {
|
|
2945
4164
|
this.conversations = input.required(...(ngDevMode ? [{ debugName: "conversations" }] : []));
|
|
@@ -2948,33 +4167,48 @@ class NhAssistantConversationListComponent {
|
|
|
2948
4167
|
this.loading = input(false, ...(ngDevMode ? [{ debugName: "loading" }] : []));
|
|
2949
4168
|
this.disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
|
|
2950
4169
|
this.conversationSelect = output();
|
|
4170
|
+
/** The owner deletes the conversation; a participant leaves it. */
|
|
2951
4171
|
this.conversationDelete = output();
|
|
2952
4172
|
this.conversationCreate = output();
|
|
2953
4173
|
}
|
|
2954
|
-
|
|
2955
|
-
|
|
4174
|
+
unread(conversation) {
|
|
4175
|
+
return nhAssistantIsUnread(conversation);
|
|
4176
|
+
}
|
|
4177
|
+
indicator(conversation) {
|
|
4178
|
+
if (conversation.status === 'running') {
|
|
4179
|
+
return 'running';
|
|
4180
|
+
}
|
|
4181
|
+
if (conversation.status === 'waiting-for-approval') {
|
|
4182
|
+
return 'waiting';
|
|
4183
|
+
}
|
|
4184
|
+
return this.unread(conversation) ? 'unread' : null;
|
|
4185
|
+
}
|
|
4186
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantConversationListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
4187
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.33", type: NhAssistantConversationListComponent, isStandalone: true, selector: "nh-assistant-conversation-list", inputs: { conversations: { classPropertyName: "conversations", publicName: "conversations", isSignal: true, isRequired: true, transformFunction: null }, total: { classPropertyName: "total", publicName: "total", isSignal: true, isRequired: false, transformFunction: null }, activeConversationId: { classPropertyName: "activeConversationId", publicName: "activeConversationId", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { conversationSelect: "conversationSelect", conversationDelete: "conversationDelete", conversationCreate: "conversationCreate" }, ngImport: i0, template: "<nav class=\"list\" [attr.aria-label]=\"'nh-assistant.conversation-list.title' | translate\" [attr.aria-busy]=\"loading()\">\n <header class=\"header\">\n <h3>{{ 'nh-assistant.conversation-list.title' | translate }}</h3>\n @if (conversations().length > 0 && total() > conversations().length) {\n <span class=\"count\">{{ 'nh-assistant.conversation-list.count' | translate: { count: conversations().length, total: total() } }}</span>\n }\n <button\n class=\"icon-button\"\n type=\"button\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"'nh-assistant.panel.new-conversation' | translate\"\n [attr.title]=\"'nh-assistant.panel.new-conversation' | translate\"\n (click)=\"conversationCreate.emit()\">\n <nh-assistant-icon name=\"plus\" />\n </button>\n </header>\n\n @if (loading() && conversations().length === 0) {\n <p class=\"state\" role=\"status\">{{ 'nh-assistant.conversation-list.loading' | translate }}</p>\n } @else if (conversations().length === 0) {\n <p class=\"state\">{{ 'nh-assistant.conversation-list.empty' | translate }}</p>\n } @else {\n <ul>\n @for (conversation of conversations(); track conversation.id) {\n <li [class.active]=\"conversation.id === activeConversationId()\" [class.unread]=\"unread(conversation)\">\n <button\n class=\"item\"\n type=\"button\"\n [disabled]=\"disabled()\"\n [attr.aria-current]=\"conversation.id === activeConversationId() ? 'true' : null\"\n (click)=\"conversationSelect.emit(conversation.id)\">\n <span class=\"title-row\">\n <span class=\"indicator\" [attr.data-state]=\"indicator(conversation)\" aria-hidden=\"true\"></span>\n <span class=\"title\">{{ conversation.title || ('nh-assistant.conversation-list.untitled' | translate) }}</span>\n @if ((conversation.participantCount ?? 0) > 0) {\n <nh-assistant-icon\n class=\"shared\"\n name=\"users\"\n [attr.title]=\"'nh-assistant.conversation-list.shared' | translate: { count: conversation.participantCount }\" />\n }\n </span>\n <span class=\"meta\">\n <span class=\"status\" [attr.data-status]=\"conversation.status\">{{ 'nh-assistant.conversation-list.status.' + conversation.status | translate }}</span>\n @if (unread(conversation)) {\n <span class=\"unread-label\">{{ 'nh-assistant.conversation-list.unread' | translate }}</span>\n }\n @if (conversation.role === 'participant') {\n <span>{{ 'nh-assistant.conversation-list.shared-with-you' | translate }}</span>\n }\n <time [attr.datetime]=\"conversation.updatedAt\">{{ conversation.updatedAt | date: 'short' }}</time>\n </span>\n </button>\n <button\n class=\"icon-button delete\"\n type=\"button\"\n [disabled]=\"disabled() || conversation.status === 'running'\"\n [attr.aria-label]=\"((conversation.role === 'participant' ? 'nh-assistant.conversation-list.leave' : 'nh-assistant.conversation-list.delete') | translate) + ': ' + (conversation.title || ('nh-assistant.conversation-list.untitled' | translate))\"\n [attr.title]=\"(conversation.role === 'participant' ? 'nh-assistant.conversation-list.leave' : 'nh-assistant.conversation-list.delete') | translate\"\n (click)=\"conversationDelete.emit(conversation.id)\">\n <nh-assistant-icon [name]=\"conversation.role === 'participant' ? 'leave' : 'trash'\" />\n </button>\n </li>\n }\n </ul>\n }\n</nav>\n", styles: [":host{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){:host{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}:host{display:flex;flex-direction:column;min-height:0;color:var(--nha-text)}.list{display:flex;flex:1;flex-direction:column;min-height:0}.header{display:flex;align-items:center;gap:8px;padding:12px 12px 8px 16px}h3{flex:1;margin:0;font-size:13px;font-weight:700}.count{color:var(--nha-text-muted);font-size:11px}.icon-button{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:1px solid transparent;border-radius:var(--nha-radius-control);color:var(--nha-text-muted);background:transparent;cursor:pointer}.icon-button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.icon-button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}.icon-button:disabled{cursor:not-allowed;opacity:.5}.state{margin:0;padding:12px 16px;color:var(--nha-text-muted);font-size:13px}ul{flex:1;min-height:0;margin:0;overflow-y:auto;padding:0 8px 12px;list-style:none}li{display:flex;align-items:center;gap:4px;border-radius:var(--nha-radius-control)}li.active{background:var(--nha-surface-strong)}.item{display:flex;flex:1;flex-direction:column;gap:2px;min-width:0;border:0;border-radius:var(--nha-radius-control);padding:8px;color:inherit;background:transparent;font:inherit;text-align:left;cursor:pointer}.item:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.item:hover:not(:disabled){background:var(--nha-surface-strong)}.item:disabled{cursor:not-allowed;opacity:.6}.title-row{display:flex;align-items:center;gap:6px;min-width:0}.title{overflow:hidden;font-size:13px;font-weight:650;text-overflow:ellipsis;white-space:nowrap}li.unread .title{font-weight:800}.shared{flex:none;color:var(--nha-text-muted);font-size:12px}.indicator{flex:none;width:8px;height:8px;border-radius:50%}.indicator:not([data-state]){display:none}.indicator[data-state=unread]{background:var(--nha-highlight)}.indicator[data-state=waiting]{background:var(--nha-warning)}.indicator[data-state=running]{width:10px;height:10px;border:2px solid color-mix(in srgb,var(--nha-highlight) 30%,transparent);border-top-color:var(--nha-highlight);animation:nh-assistant-spin .9s linear infinite}@media(prefers-reduced-motion:reduce){.indicator[data-state=running]{border-color:var(--nha-highlight);animation:none}}@keyframes nh-assistant-spin{to{transform:rotate(360deg)}}.unread-label{color:var(--nha-highlight);font-weight:700}.meta{display:flex;gap:8px;color:var(--nha-text-muted);font-size:11px}.status[data-status=waiting-for-approval]{color:var(--nha-warning);font-weight:700}.status[data-status=failed]{color:var(--nha-danger)}.delete{flex:none}\n"], dependencies: [{ kind: "component", type: NhAssistantIconComponent, selector: "nh-assistant-icon", inputs: ["name"] }, { kind: "pipe", type: DatePipe, name: "date" }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
2956
4188
|
}
|
|
2957
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
4189
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantConversationListComponent, decorators: [{
|
|
2958
4190
|
type: Component,
|
|
2959
|
-
args: [{ selector: 'nh-assistant-conversation-list', standalone: true, imports: [DatePipe, TranslatePipe, NhAssistantIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<nav class=\"list\" [attr.aria-label]=\"'nh-assistant.conversation-list.title' | translate\" [attr.aria-busy]=\"loading()\">\n <header class=\"header\">\n <h3>{{ 'nh-assistant.conversation-list.title' | translate }}</h3>\n @if (conversations().length > 0 && total() > conversations().length) {\n <span class=\"count\">{{ 'nh-assistant.conversation-list.count' | translate: { count: conversations().length, total: total() } }}</span>\n }\n <button\n class=\"icon-button\"\n type=\"button\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"'nh-assistant.panel.new-conversation' | translate\"\n [attr.title]=\"'nh-assistant.panel.new-conversation' | translate\"\n (click)=\"conversationCreate.emit()\">\n <nh-assistant-icon name=\"plus\" />\n </button>\n </header>\n\n @if (loading() && conversations().length === 0) {\n <p class=\"state\" role=\"status\">{{ 'nh-assistant.conversation-list.loading' | translate }}</p>\n } @else if (conversations().length === 0) {\n <p class=\"state\">{{ 'nh-assistant.conversation-list.empty' | translate }}</p>\n } @else {\n <ul>\n @for (conversation of conversations(); track conversation.id) {\n <li [class.active]=\"conversation.id === activeConversationId()\">\n <button\n class=\"item\"\n type=\"button\"\n [disabled]=\"disabled()\"\n [attr.aria-current]=\"conversation.id === activeConversationId() ? 'true' : null\"\n (click)=\"conversationSelect.emit(conversation.id)\">\n <span class=\"title\">{{ conversation.title || ('nh-assistant.conversation-list.untitled' | translate) }}</span>\n <span class=\"meta\">\n <span class=\"status\" [attr.data-status]=\"conversation.status\">{{ 'nh-assistant.conversation-list.status.' + conversation.status | translate }}</span>\n <time [attr.datetime]=\"conversation.updatedAt\">{{ conversation.updatedAt | date: 'short' }}</time>\n </span>\n </button>\n <button\n class=\"icon-button delete\"\n type=\"button\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"('nh-assistant.conversation-list.delete' | translate) + ': ' + (conversation.title || ('nh-assistant.conversation-list.untitled' | translate))\"\n [attr.title]=\"'nh-assistant.conversation-list.delete' | translate\"\n (click)=\"conversationDelete.emit(conversation.id)\">\n <nh-assistant-icon name=\"trash\" />\n </button>\n </li>\n }\n </ul>\n }\n</nav>\n", styles: [":host{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){:host{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}:host{display:flex;flex-direction:column;min-height:0;color:var(--nha-text)}.list{display:flex;flex:1;flex-direction:column;min-height:0}.header{display:flex;align-items:center;gap:8px;padding:12px 12px 8px 16px}h3{flex:1;margin:0;font-size:13px;font-weight:700}.count{color:var(--nha-text-muted);font-size:11px}.icon-button{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:1px solid transparent;border-radius:var(--nha-radius-control);color:var(--nha-text-muted);background:transparent;cursor:pointer}.icon-button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.icon-button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}.icon-button:disabled{cursor:not-allowed;opacity:.5}.state{margin:0;padding:12px 16px;color:var(--nha-text-muted);font-size:13px}ul{flex:1;min-height:0;margin:0;overflow-y:auto;padding:0 8px 12px;list-style:none}li{display:flex;align-items:center;gap:4px;border-radius:var(--nha-radius-control)}li.active{background:var(--nha-surface-strong)}.item{display:flex;flex:1;flex-direction:column;gap:2px;min-width:0;border:0;border-radius:var(--nha-radius-control);padding:8px;color:inherit;background:transparent;font:inherit;text-align:left;cursor:pointer}.item:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.item:hover:not(:disabled){background:var(--nha-surface-strong)}.item:disabled{cursor:not-allowed;opacity:.6}.title{overflow:hidden;font-size:13px;font-weight:650;text-overflow:ellipsis;white-space:nowrap}.meta{display:flex;gap:8px;color:var(--nha-text-muted);font-size:11px}.status[data-status=waiting-for-approval]{color:var(--nha-warning);font-weight:700}.status[data-status=failed]{color:var(--nha-danger)}.delete{flex:none}\n"] }]
|
|
4191
|
+
args: [{ selector: 'nh-assistant-conversation-list', standalone: true, imports: [DatePipe, TranslatePipe, NhAssistantIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<nav class=\"list\" [attr.aria-label]=\"'nh-assistant.conversation-list.title' | translate\" [attr.aria-busy]=\"loading()\">\n <header class=\"header\">\n <h3>{{ 'nh-assistant.conversation-list.title' | translate }}</h3>\n @if (conversations().length > 0 && total() > conversations().length) {\n <span class=\"count\">{{ 'nh-assistant.conversation-list.count' | translate: { count: conversations().length, total: total() } }}</span>\n }\n <button\n class=\"icon-button\"\n type=\"button\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"'nh-assistant.panel.new-conversation' | translate\"\n [attr.title]=\"'nh-assistant.panel.new-conversation' | translate\"\n (click)=\"conversationCreate.emit()\">\n <nh-assistant-icon name=\"plus\" />\n </button>\n </header>\n\n @if (loading() && conversations().length === 0) {\n <p class=\"state\" role=\"status\">{{ 'nh-assistant.conversation-list.loading' | translate }}</p>\n } @else if (conversations().length === 0) {\n <p class=\"state\">{{ 'nh-assistant.conversation-list.empty' | translate }}</p>\n } @else {\n <ul>\n @for (conversation of conversations(); track conversation.id) {\n <li [class.active]=\"conversation.id === activeConversationId()\" [class.unread]=\"unread(conversation)\">\n <button\n class=\"item\"\n type=\"button\"\n [disabled]=\"disabled()\"\n [attr.aria-current]=\"conversation.id === activeConversationId() ? 'true' : null\"\n (click)=\"conversationSelect.emit(conversation.id)\">\n <span class=\"title-row\">\n <span class=\"indicator\" [attr.data-state]=\"indicator(conversation)\" aria-hidden=\"true\"></span>\n <span class=\"title\">{{ conversation.title || ('nh-assistant.conversation-list.untitled' | translate) }}</span>\n @if ((conversation.participantCount ?? 0) > 0) {\n <nh-assistant-icon\n class=\"shared\"\n name=\"users\"\n [attr.title]=\"'nh-assistant.conversation-list.shared' | translate: { count: conversation.participantCount }\" />\n }\n </span>\n <span class=\"meta\">\n <span class=\"status\" [attr.data-status]=\"conversation.status\">{{ 'nh-assistant.conversation-list.status.' + conversation.status | translate }}</span>\n @if (unread(conversation)) {\n <span class=\"unread-label\">{{ 'nh-assistant.conversation-list.unread' | translate }}</span>\n }\n @if (conversation.role === 'participant') {\n <span>{{ 'nh-assistant.conversation-list.shared-with-you' | translate }}</span>\n }\n <time [attr.datetime]=\"conversation.updatedAt\">{{ conversation.updatedAt | date: 'short' }}</time>\n </span>\n </button>\n <button\n class=\"icon-button delete\"\n type=\"button\"\n [disabled]=\"disabled() || conversation.status === 'running'\"\n [attr.aria-label]=\"((conversation.role === 'participant' ? 'nh-assistant.conversation-list.leave' : 'nh-assistant.conversation-list.delete') | translate) + ': ' + (conversation.title || ('nh-assistant.conversation-list.untitled' | translate))\"\n [attr.title]=\"(conversation.role === 'participant' ? 'nh-assistant.conversation-list.leave' : 'nh-assistant.conversation-list.delete') | translate\"\n (click)=\"conversationDelete.emit(conversation.id)\">\n <nh-assistant-icon [name]=\"conversation.role === 'participant' ? 'leave' : 'trash'\" />\n </button>\n </li>\n }\n </ul>\n }\n</nav>\n", styles: [":host{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){:host{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}:host{display:flex;flex-direction:column;min-height:0;color:var(--nha-text)}.list{display:flex;flex:1;flex-direction:column;min-height:0}.header{display:flex;align-items:center;gap:8px;padding:12px 12px 8px 16px}h3{flex:1;margin:0;font-size:13px;font-weight:700}.count{color:var(--nha-text-muted);font-size:11px}.icon-button{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:1px solid transparent;border-radius:var(--nha-radius-control);color:var(--nha-text-muted);background:transparent;cursor:pointer}.icon-button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.icon-button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}.icon-button:disabled{cursor:not-allowed;opacity:.5}.state{margin:0;padding:12px 16px;color:var(--nha-text-muted);font-size:13px}ul{flex:1;min-height:0;margin:0;overflow-y:auto;padding:0 8px 12px;list-style:none}li{display:flex;align-items:center;gap:4px;border-radius:var(--nha-radius-control)}li.active{background:var(--nha-surface-strong)}.item{display:flex;flex:1;flex-direction:column;gap:2px;min-width:0;border:0;border-radius:var(--nha-radius-control);padding:8px;color:inherit;background:transparent;font:inherit;text-align:left;cursor:pointer}.item:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.item:hover:not(:disabled){background:var(--nha-surface-strong)}.item:disabled{cursor:not-allowed;opacity:.6}.title-row{display:flex;align-items:center;gap:6px;min-width:0}.title{overflow:hidden;font-size:13px;font-weight:650;text-overflow:ellipsis;white-space:nowrap}li.unread .title{font-weight:800}.shared{flex:none;color:var(--nha-text-muted);font-size:12px}.indicator{flex:none;width:8px;height:8px;border-radius:50%}.indicator:not([data-state]){display:none}.indicator[data-state=unread]{background:var(--nha-highlight)}.indicator[data-state=waiting]{background:var(--nha-warning)}.indicator[data-state=running]{width:10px;height:10px;border:2px solid color-mix(in srgb,var(--nha-highlight) 30%,transparent);border-top-color:var(--nha-highlight);animation:nh-assistant-spin .9s linear infinite}@media(prefers-reduced-motion:reduce){.indicator[data-state=running]{border-color:var(--nha-highlight);animation:none}}@keyframes nh-assistant-spin{to{transform:rotate(360deg)}}.unread-label{color:var(--nha-highlight);font-weight:700}.meta{display:flex;gap:8px;color:var(--nha-text-muted);font-size:11px}.status[data-status=waiting-for-approval]{color:var(--nha-warning);font-weight:700}.status[data-status=failed]{color:var(--nha-danger)}.delete{flex:none}\n"] }]
|
|
2960
4192
|
}], propDecorators: { conversations: [{ type: i0.Input, args: [{ isSignal: true, alias: "conversations", required: true }] }], total: [{ type: i0.Input, args: [{ isSignal: true, alias: "total", required: false }] }], activeConversationId: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeConversationId", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], conversationSelect: [{ type: i0.Output, args: ["conversationSelect"] }], conversationDelete: [{ type: i0.Output, args: ["conversationDelete"] }], conversationCreate: [{ type: i0.Output, args: ["conversationCreate"] }] } });
|
|
2961
4193
|
|
|
2962
4194
|
/** Maximum length of the user's own instructions. */
|
|
2963
4195
|
const NH_ASSISTANT_MAX_CUSTOM_INSTRUCTIONS = 1_000;
|
|
2964
|
-
let nextId$
|
|
4196
|
+
let nextId$4 = 0;
|
|
2965
4197
|
/**
|
|
2966
|
-
* The user's own assistant preferences: style, form of address,
|
|
2967
|
-
* optional instructions.
|
|
2968
|
-
* application context and
|
|
4198
|
+
* The user's own assistant preferences: desktop notifications, style, form of address,
|
|
4199
|
+
* answer length and optional instructions. Notifications apply immediately; the other
|
|
4200
|
+
* preferences steer style only and are saved together, below the application context and
|
|
4201
|
+
* agent instructions.
|
|
2969
4202
|
*/
|
|
2970
4203
|
class NhAssistantPreferencesComponent {
|
|
2971
4204
|
constructor() {
|
|
2972
4205
|
this.api = inject(NhAssistantApiService);
|
|
4206
|
+
this.push = inject(NhAssistantPushService);
|
|
2973
4207
|
/** Emitted when the user leaves the preferences. */
|
|
2974
4208
|
this.closed = output();
|
|
2975
4209
|
/** Emitted with the stored preferences after a successful save. */
|
|
2976
4210
|
this.saved = output();
|
|
2977
|
-
this.id = `nh-assistant-preferences-${nextId$
|
|
4211
|
+
this.id = `nh-assistant-preferences-${nextId$4++}`;
|
|
2978
4212
|
this.styles = ['default', 'direct', 'personal', 'detailed'];
|
|
2979
4213
|
this.addressForms = ['informal', 'formal'];
|
|
2980
4214
|
this.responseLengths = ['short', 'normal', 'long'];
|
|
@@ -3019,6 +4253,19 @@ class NhAssistantPreferencesComponent {
|
|
|
3019
4253
|
}
|
|
3020
4254
|
});
|
|
3021
4255
|
}
|
|
4256
|
+
/** Turns desktop notifications on or off; turning them on asks the browser for permission. */
|
|
4257
|
+
async toggleNotifications(event) {
|
|
4258
|
+
const enabled = event.target.checked;
|
|
4259
|
+
this.error.set(null);
|
|
4260
|
+
try {
|
|
4261
|
+
await this.push.setEnabled(enabled);
|
|
4262
|
+
}
|
|
4263
|
+
catch (error) {
|
|
4264
|
+
this.error.set(error instanceof NhAssistantApiError
|
|
4265
|
+
? { code: error.code, messageKey: error.messageKey }
|
|
4266
|
+
: { code: 'assistant-server', messageKey: nhAssistantErrorMessageKey('assistant-server') });
|
|
4267
|
+
}
|
|
4268
|
+
}
|
|
3022
4269
|
onInstructionsInput(event) {
|
|
3023
4270
|
this.customInstructions.set(event.target.value);
|
|
3024
4271
|
this.savedMessage.set(false);
|
|
@@ -3064,14 +4311,137 @@ class NhAssistantPreferencesComponent {
|
|
|
3064
4311
|
this.responseLength.set(preferences.responseLength);
|
|
3065
4312
|
this.customInstructions.set(preferences.customInstructions ?? '');
|
|
3066
4313
|
}
|
|
3067
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
3068
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.28", type: NhAssistantPreferencesComponent, isStandalone: true, selector: "nh-assistant-preferences", outputs: { closed: "closed", saved: "saved" }, ngImport: i0, template: "<section class=\"preferences\" [attr.aria-labelledby]=\"id + '-title'\" [attr.aria-busy]=\"loading() || saving()\">\n <header class=\"header\">\n <h3 [id]=\"id + '-title'\">{{ 'nh-assistant.preferences.title' | translate }}</h3>\n <button\n class=\"icon-button\"\n type=\"button\"\n [attr.aria-label]=\"'nh-assistant.preferences.back' | translate\"\n [attr.title]=\"'nh-assistant.preferences.back' | translate\"\n (click)=\"closed.emit()\">\n <nh-assistant-icon name=\"close\" />\n </button>\n </header>\n\n @if (loading()) {\n <p class=\"state\" role=\"status\">{{ 'nh-assistant.preferences.loading' | translate }}</p>\n } @else if (loadFailed()) {\n <div class=\"state\" role=\"alert\">\n <p>{{ 'nh-assistant.preferences.load-failed' | translate }}</p>\n <button class=\"secondary\" type=\"button\" (click)=\"load()\">{{ 'nh-assistant.preferences.retry' | translate }}</button>\n </div>\n } @else {\n <form class=\"form\" (submit)=\"$event.preventDefault(); save()\">\n <p class=\"description\">{{ 'nh-assistant.preferences.description' | translate }}</p>\n\n <fieldset>\n <legend>{{ 'nh-assistant.preferences.style-label' | translate }}</legend>\n <div class=\"options styles\">\n @for (option of styles; track option) {\n <label class=\"option\" [class.selected]=\"style() === option\">\n <input type=\"radio\" [name]=\"id + '-style'\" [value]=\"option\" [checked]=\"style() === option\" (change)=\"choose(style, option)\" />\n <span>\n <strong>{{ 'nh-assistant.preferences.style.' + option | translate }}</strong>\n <small>{{ 'nh-assistant.preferences.style.' + option + '-description' | translate }}</small>\n </span>\n </label>\n }\n </div>\n </fieldset>\n\n <fieldset>\n <legend>{{ 'nh-assistant.preferences.address-form-label' | translate }}</legend>\n <div class=\"options segmented\">\n @for (option of addressForms; track option) {\n <label class=\"option\" [class.selected]=\"addressForm() === option\">\n <input type=\"radio\" [name]=\"id + '-address'\" [value]=\"option\" [checked]=\"addressForm() === option\" (change)=\"choose(addressForm, option)\" />\n <span><strong>{{ 'nh-assistant.preferences.address-form.' + option | translate }}</strong></span>\n </label>\n }\n </div>\n </fieldset>\n\n <fieldset>\n <legend>{{ 'nh-assistant.preferences.response-length-label' | translate }}</legend>\n <div class=\"options segmented\">\n @for (option of responseLengths; track option) {\n <label class=\"option\" [class.selected]=\"responseLength() === option\">\n <input type=\"radio\" [name]=\"id + '-length'\" [value]=\"option\" [checked]=\"responseLength() === option\" (change)=\"choose(responseLength, option)\" />\n <span><strong>{{ 'nh-assistant.preferences.response-length.' + option | translate }}</strong></span>\n </label>\n }\n </div>\n </fieldset>\n\n <div class=\"field\">\n <label [for]=\"id + '-instructions'\">{{ 'nh-assistant.preferences.custom-instructions-label' | translate }}</label>\n <textarea\n rows=\"4\"\n [id]=\"id + '-instructions'\"\n [value]=\"customInstructions()\"\n [attr.aria-describedby]=\"id + '-instructions-hint'\"\n [attr.aria-invalid]=\"tooLong() ? true : null\"\n (input)=\"onInstructionsInput($event)\"></textarea>\n <p class=\"hint\" [id]=\"id + '-instructions-hint'\">\n @if (tooLong()) {\n <span class=\"error-text\">{{ 'nh-assistant.preferences.too-long' | translate }}</span>\n } @else {\n <span>{{ 'nh-assistant.preferences.custom-instructions-hint' | translate }}</span>\n }\n <span class=\"counter\" [class.error-text]=\"tooLong()\">\n {{ 'nh-assistant.preferences.characters' | translate: { count: instructionsLength(), max: maxInstructions } }}\n </span>\n </p>\n </div>\n\n @if (error(); as error) {\n <p class=\"message error\" role=\"alert\">\n <nh-assistant-icon name=\"warning\" />\n {{ errorKeys() | nhAssistantTranslate: error.code }}\n </p>\n }\n @if (savedMessage()) {\n <p class=\"message success\" role=\"status\">\n <nh-assistant-icon name=\"check\" />\n {{ 'nh-assistant.preferences.saved' | translate }}\n </p>\n }\n\n <div class=\"actions\">\n <button class=\"secondary\" type=\"button\" (click)=\"closed.emit()\">{{ 'nh-assistant.preferences.back' | translate }}</button>\n <button class=\"primary\" type=\"submit\" [disabled]=\"!canSave()\">\n {{ (saving() ? 'nh-assistant.preferences.saving' : 'nh-assistant.preferences.save') | translate }}\n </button>\n </div>\n </form>\n }\n</section>\n", styles: [":host{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){:host{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}:host{display:flex;flex-direction:column;min-height:0;color:var(--nha-text)}:host fieldset{min-width:0;margin:0;border:0;padding:0}:host legend,:host .label,:host .field>label{display:block;margin-bottom:6px;padding:0;color:var(--nha-text);font-size:12px;font-weight:700}:host input[type=text],:host input[type=url],:host input[type=password],:host input[type=search],:host select,:host textarea{width:100%;min-width:0;min-height:38px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:8px 10px;color:var(--nha-text);background:var(--nha-background);font:inherit;font-size:13px}:host input[type=text]:focus-visible,:host input[type=url]:focus-visible,:host input[type=password]:focus-visible,:host input[type=search]:focus-visible,:host select:focus-visible,:host textarea:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host input[type=text]:disabled,:host input[type=url]:disabled,:host input[type=password]:disabled,:host input[type=search]:disabled,:host select:disabled,:host textarea:disabled{cursor:not-allowed;opacity:.6}:host input[type=text][aria-invalid=true],:host input[type=url][aria-invalid=true],:host input[type=password][aria-invalid=true],:host input[type=search][aria-invalid=true],:host select[aria-invalid=true],:host textarea[aria-invalid=true]{border-color:var(--nha-danger)}:host textarea{line-height:1.45;resize:vertical}:host .mono{font-family:var(--nha-mono);font-size:12px}:host .field{display:flex;flex-direction:column;min-width:0}:host .hint{display:flex;justify-content:space-between;gap:8px;margin:4px 2px 0;color:var(--nha-text-muted);font-size:11px}:host .counter{flex:none;font-variant-numeric:tabular-nums}:host .error-text{color:var(--nha-danger)}:host .message{display:flex;align-items:center;gap:8px;margin:0;border:1px solid transparent;border-radius:var(--nha-radius-control);padding:8px 12px;font-size:13px}:host .message.error{border-color:var(--nha-danger);color:var(--nha-text);background:var(--nha-danger-soft)}:host .message.warning{border-color:var(--nha-warning);color:var(--nha-text);background:var(--nha-warning-soft)}:host .message.success{border-color:var(--nha-success);color:var(--nha-text);background:var(--nha-success-soft)}:host .message nh-assistant-icon{flex:none}:host .primary,:host .secondary,:host .danger{display:inline-flex;align-items:center;justify-content:center;gap:6px;min-height:36px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px 14px;font:inherit;font-size:13px;font-weight:700;white-space:nowrap;cursor:pointer}:host .primary:focus-visible,:host .secondary:focus-visible,:host .danger:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .primary:disabled,:host .secondary:disabled,:host .danger:disabled{cursor:not-allowed;opacity:.55}:host .primary{border-color:var(--nha-accent);color:var(--nha-accent-contrast);background:var(--nha-accent)}:host .primary:hover:not(:disabled){filter:brightness(1.12)}:host .secondary{color:var(--nha-text);background:transparent}:host .secondary:hover:not(:disabled){background:var(--nha-surface-strong)}:host .danger{border-color:var(--nha-danger);color:var(--nha-danger);background:transparent}:host .danger:hover:not(:disabled){background:var(--nha-danger-soft)}:host .icon-button{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:1px solid transparent;border-radius:var(--nha-radius-control);color:var(--nha-text-muted);background:transparent;cursor:pointer}:host .icon-button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .icon-button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}:host .icon-button:disabled{cursor:not-allowed;opacity:.5}:host .actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px}:host .badge{display:inline-flex;align-items:center;gap:4px;border-radius:999px;padding:2px 8px;color:var(--nha-text-muted);background:var(--nha-surface-strong);font-size:11px;font-weight:700;white-space:nowrap}:host .badge.success{color:var(--nha-success);background:var(--nha-success-soft)}:host .badge.warning{color:var(--nha-warning);background:var(--nha-warning-soft)}:host .badge.danger{color:var(--nha-danger);background:var(--nha-danger-soft)}:host .visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}.preferences{display:flex;flex:1;flex-direction:column;min-height:0}.header{display:flex;align-items:center;gap:8px;padding:12px 12px 8px 16px}h3{flex:1;margin:0;font-size:15px;font-weight:700}.state{display:flex;flex-direction:column;align-items:flex-start;gap:10px;margin:0;padding:12px 16px;color:var(--nha-text-muted);font-size:13px}.state p{margin:0}.form{display:flex;flex:1;flex-direction:column;gap:18px;min-height:0;overflow-y:auto;padding:0 16px 16px}.description{margin:0;color:var(--nha-text-muted);font-size:13px}.options{display:grid;gap:8px}.styles{grid-template-columns:repeat(auto-fit,minmax(150px,1fr))}.segmented{grid-template-columns:repeat(auto-fit,minmax(90px,1fr))}.option{position:relative;display:flex;align-items:flex-start;gap:8px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:10px;background:var(--nha-background);cursor:pointer}.option:hover{background:var(--nha-surface)}.option.selected{border-color:var(--nha-highlight);background:var(--nha-surface);box-shadow:inset 0 0 0 1px var(--nha-highlight)}.option:focus-within{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.option input{flex:none;margin:2px 0 0;accent-color:var(--nha-highlight)}.option span{display:flex;flex-direction:column;gap:2px;min-width:0}.option strong{font-size:13px}.option small{color:var(--nha-text-muted);font-size:12px}\n"], dependencies: [{ kind: "component", type: NhAssistantIconComponent, selector: "nh-assistant-icon", inputs: ["name"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }, { kind: "pipe", type: NhAssistantTranslatePipe, name: "nhAssistantTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
4314
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantPreferencesComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
4315
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.33", type: NhAssistantPreferencesComponent, isStandalone: true, selector: "nh-assistant-preferences", outputs: { closed: "closed", saved: "saved" }, ngImport: i0, template: "<section class=\"preferences\" [attr.aria-labelledby]=\"id + '-title'\" [attr.aria-busy]=\"loading() || saving()\">\n <header class=\"header\">\n <h3 [id]=\"id + '-title'\">{{ 'nh-assistant.preferences.title' | translate }}</h3>\n <button\n class=\"icon-button\"\n type=\"button\"\n [attr.aria-label]=\"'nh-assistant.preferences.back' | translate\"\n [attr.title]=\"'nh-assistant.preferences.back' | translate\"\n (click)=\"closed.emit()\">\n <nh-assistant-icon name=\"close\" />\n </button>\n </header>\n\n @if (loading()) {\n <p class=\"state\" role=\"status\">{{ 'nh-assistant.preferences.loading' | translate }}</p>\n } @else if (loadFailed()) {\n <div class=\"state\" role=\"alert\">\n <p>{{ 'nh-assistant.preferences.load-failed' | translate }}</p>\n <button class=\"secondary\" type=\"button\" (click)=\"load()\">{{ 'nh-assistant.preferences.retry' | translate }}</button>\n </div>\n } @else {\n <form class=\"form\" (submit)=\"$event.preventDefault(); save()\">\n @if (push.state() !== 'unavailable') {\n <fieldset class=\"notifications\">\n <legend>{{ 'nh-assistant.preferences.notifications-label' | translate }}</legend>\n <label class=\"switch\">\n <input\n type=\"checkbox\"\n [checked]=\"push.settings()?.pushEnabled === true\"\n [disabled]=\"push.busy()\"\n [attr.aria-describedby]=\"id + '-notifications-hint'\"\n (change)=\"toggleNotifications($event)\" />\n <span>{{ 'nh-assistant.preferences.notifications' | translate }}</span>\n </label>\n <p class=\"hint\" [id]=\"id + '-notifications-hint'\">\n <span>{{ 'nh-assistant.preferences.notifications-state.' + push.state() | translate }}</span>\n </p>\n </fieldset>\n }\n\n <p class=\"description\">{{ 'nh-assistant.preferences.description' | translate }}</p>\n\n <fieldset>\n <legend>{{ 'nh-assistant.preferences.style-label' | translate }}</legend>\n <div class=\"options styles\">\n @for (option of styles; track option) {\n <label class=\"option\" [class.selected]=\"style() === option\">\n <input type=\"radio\" [name]=\"id + '-style'\" [value]=\"option\" [checked]=\"style() === option\" (change)=\"choose(style, option)\" />\n <span>\n <strong>{{ 'nh-assistant.preferences.style.' + option | translate }}</strong>\n <small>{{ 'nh-assistant.preferences.style.' + option + '-description' | translate }}</small>\n </span>\n </label>\n }\n </div>\n </fieldset>\n\n <fieldset>\n <legend>{{ 'nh-assistant.preferences.address-form-label' | translate }}</legend>\n <div class=\"options segmented\">\n @for (option of addressForms; track option) {\n <label class=\"option\" [class.selected]=\"addressForm() === option\">\n <input type=\"radio\" [name]=\"id + '-address'\" [value]=\"option\" [checked]=\"addressForm() === option\" (change)=\"choose(addressForm, option)\" />\n <span><strong>{{ 'nh-assistant.preferences.address-form.' + option | translate }}</strong></span>\n </label>\n }\n </div>\n </fieldset>\n\n <fieldset>\n <legend>{{ 'nh-assistant.preferences.response-length-label' | translate }}</legend>\n <div class=\"options segmented\">\n @for (option of responseLengths; track option) {\n <label class=\"option\" [class.selected]=\"responseLength() === option\">\n <input type=\"radio\" [name]=\"id + '-length'\" [value]=\"option\" [checked]=\"responseLength() === option\" (change)=\"choose(responseLength, option)\" />\n <span><strong>{{ 'nh-assistant.preferences.response-length.' + option | translate }}</strong></span>\n </label>\n }\n </div>\n </fieldset>\n\n <div class=\"field\">\n <label [for]=\"id + '-instructions'\">{{ 'nh-assistant.preferences.custom-instructions-label' | translate }}</label>\n <textarea\n rows=\"4\"\n [id]=\"id + '-instructions'\"\n [value]=\"customInstructions()\"\n [attr.aria-describedby]=\"id + '-instructions-hint'\"\n [attr.aria-invalid]=\"tooLong() ? true : null\"\n (input)=\"onInstructionsInput($event)\"></textarea>\n <p class=\"hint\" [id]=\"id + '-instructions-hint'\">\n @if (tooLong()) {\n <span class=\"error-text\">{{ 'nh-assistant.preferences.too-long' | translate }}</span>\n } @else {\n <span>{{ 'nh-assistant.preferences.custom-instructions-hint' | translate }}</span>\n }\n <span class=\"counter\" [class.error-text]=\"tooLong()\">\n {{ 'nh-assistant.preferences.characters' | translate: { count: instructionsLength(), max: maxInstructions } }}\n </span>\n </p>\n </div>\n\n @if (error(); as error) {\n <p class=\"message error\" role=\"alert\">\n <nh-assistant-icon name=\"warning\" />\n {{ errorKeys() | nhAssistantTranslate: error.code }}\n </p>\n }\n @if (savedMessage()) {\n <p class=\"message success\" role=\"status\">\n <nh-assistant-icon name=\"check\" />\n {{ 'nh-assistant.preferences.saved' | translate }}\n </p>\n }\n\n <div class=\"actions\">\n <button class=\"secondary\" type=\"button\" (click)=\"closed.emit()\">{{ 'nh-assistant.preferences.back' | translate }}</button>\n <button class=\"primary\" type=\"submit\" [disabled]=\"!canSave()\">\n {{ (saving() ? 'nh-assistant.preferences.saving' : 'nh-assistant.preferences.save') | translate }}\n </button>\n </div>\n </form>\n }\n</section>\n", styles: [":host{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){:host{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}:host{display:flex;flex-direction:column;min-height:0;color:var(--nha-text)}:host fieldset{min-width:0;margin:0;border:0;padding:0}:host legend,:host .label,:host .field>label{display:block;margin-bottom:6px;padding:0;color:var(--nha-text);font-size:12px;font-weight:700}:host input[type=text],:host input[type=url],:host input[type=password],:host input[type=search],:host select,:host textarea{width:100%;min-width:0;min-height:38px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:8px 10px;color:var(--nha-text);background:var(--nha-background);font:inherit;font-size:13px}:host input[type=text]:focus-visible,:host input[type=url]:focus-visible,:host input[type=password]:focus-visible,:host input[type=search]:focus-visible,:host select:focus-visible,:host textarea:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host input[type=text]:disabled,:host input[type=url]:disabled,:host input[type=password]:disabled,:host input[type=search]:disabled,:host select:disabled,:host textarea:disabled{cursor:not-allowed;opacity:.6}:host input[type=text][aria-invalid=true],:host input[type=url][aria-invalid=true],:host input[type=password][aria-invalid=true],:host input[type=search][aria-invalid=true],:host select[aria-invalid=true],:host textarea[aria-invalid=true]{border-color:var(--nha-danger)}:host textarea{line-height:1.45;resize:vertical}:host .mono{font-family:var(--nha-mono);font-size:12px}:host .field{display:flex;flex-direction:column;min-width:0}:host .hint{display:flex;justify-content:space-between;gap:8px;margin:4px 2px 0;color:var(--nha-text-muted);font-size:11px}:host .counter{flex:none;font-variant-numeric:tabular-nums}:host .error-text{color:var(--nha-danger)}:host .message{display:flex;align-items:center;gap:8px;margin:0;border:1px solid transparent;border-radius:var(--nha-radius-control);padding:8px 12px;font-size:13px}:host .message.error{border-color:var(--nha-danger);color:var(--nha-text);background:var(--nha-danger-soft)}:host .message.warning{border-color:var(--nha-warning);color:var(--nha-text);background:var(--nha-warning-soft)}:host .message.success{border-color:var(--nha-success);color:var(--nha-text);background:var(--nha-success-soft)}:host .message nh-assistant-icon{flex:none}:host .primary,:host .secondary,:host .danger{display:inline-flex;align-items:center;justify-content:center;gap:6px;min-height:36px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px 14px;font:inherit;font-size:13px;font-weight:700;white-space:nowrap;cursor:pointer}:host .primary:focus-visible,:host .secondary:focus-visible,:host .danger:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .primary:disabled,:host .secondary:disabled,:host .danger:disabled{cursor:not-allowed;opacity:.55}:host .primary{border-color:var(--nha-accent);color:var(--nha-accent-contrast);background:var(--nha-accent)}:host .primary:hover:not(:disabled){filter:brightness(1.12)}:host .secondary{color:var(--nha-text);background:transparent}:host .secondary:hover:not(:disabled){background:var(--nha-surface-strong)}:host .danger{border-color:var(--nha-danger);color:var(--nha-danger);background:transparent}:host .danger:hover:not(:disabled){background:var(--nha-danger-soft)}:host .icon-button{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:1px solid transparent;border-radius:var(--nha-radius-control);color:var(--nha-text-muted);background:transparent;cursor:pointer}:host .icon-button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .icon-button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}:host .icon-button:disabled{cursor:not-allowed;opacity:.5}:host .actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px}:host .badge{display:inline-flex;align-items:center;gap:4px;border-radius:999px;padding:2px 8px;color:var(--nha-text-muted);background:var(--nha-surface-strong);font-size:11px;font-weight:700;white-space:nowrap}:host .badge.success{color:var(--nha-success);background:var(--nha-success-soft)}:host .badge.warning{color:var(--nha-warning);background:var(--nha-warning-soft)}:host .badge.danger{color:var(--nha-danger);background:var(--nha-danger-soft)}:host .visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}.preferences{display:flex;flex:1;flex-direction:column;min-height:0}.header{display:flex;align-items:center;gap:8px;padding:12px 12px 8px 16px}h3{flex:1;margin:0;font-size:15px;font-weight:700}.state{display:flex;flex-direction:column;align-items:flex-start;gap:10px;margin:0;padding:12px 16px;color:var(--nha-text-muted);font-size:13px}.state p{margin:0}.form{display:flex;flex:1;flex-direction:column;gap:18px;min-height:0;overflow-y:auto;padding:0 16px 16px}.description{margin:0;color:var(--nha-text-muted);font-size:13px}.options{display:grid;gap:8px}.styles{grid-template-columns:repeat(auto-fit,minmax(150px,1fr))}.segmented{grid-template-columns:repeat(auto-fit,minmax(90px,1fr))}.option{position:relative;display:flex;align-items:flex-start;gap:8px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:10px;background:var(--nha-background);cursor:pointer}.option:hover{background:var(--nha-surface)}.option.selected{border-color:var(--nha-highlight);background:var(--nha-surface);box-shadow:inset 0 0 0 1px var(--nha-highlight)}.option:focus-within{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.option input{flex:none;margin:2px 0 0;accent-color:var(--nha-highlight)}.option span{display:flex;flex-direction:column;gap:2px;min-width:0}.option strong{font-size:13px}.option small{color:var(--nha-text-muted);font-size:12px}.switch{display:flex;align-items:center;gap:8px;font-size:13px;font-weight:650;cursor:pointer}.switch input{width:16px;height:16px;margin:0;accent-color:var(--nha-highlight)}\n"], dependencies: [{ kind: "component", type: NhAssistantIconComponent, selector: "nh-assistant-icon", inputs: ["name"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }, { kind: "pipe", type: NhAssistantTranslatePipe, name: "nhAssistantTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
3069
4316
|
}
|
|
3070
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
4317
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantPreferencesComponent, decorators: [{
|
|
3071
4318
|
type: Component,
|
|
3072
|
-
args: [{ selector: 'nh-assistant-preferences', standalone: true, imports: [TranslatePipe, NhAssistantTranslatePipe, NhAssistantIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<section class=\"preferences\" [attr.aria-labelledby]=\"id + '-title'\" [attr.aria-busy]=\"loading() || saving()\">\n <header class=\"header\">\n <h3 [id]=\"id + '-title'\">{{ 'nh-assistant.preferences.title' | translate }}</h3>\n <button\n class=\"icon-button\"\n type=\"button\"\n [attr.aria-label]=\"'nh-assistant.preferences.back' | translate\"\n [attr.title]=\"'nh-assistant.preferences.back' | translate\"\n (click)=\"closed.emit()\">\n <nh-assistant-icon name=\"close\" />\n </button>\n </header>\n\n @if (loading()) {\n <p class=\"state\" role=\"status\">{{ 'nh-assistant.preferences.loading' | translate }}</p>\n } @else if (loadFailed()) {\n <div class=\"state\" role=\"alert\">\n <p>{{ 'nh-assistant.preferences.load-failed' | translate }}</p>\n <button class=\"secondary\" type=\"button\" (click)=\"load()\">{{ 'nh-assistant.preferences.retry' | translate }}</button>\n </div>\n } @else {\n <form class=\"form\" (submit)=\"$event.preventDefault(); save()\">\n <p class=\"description\">{{ 'nh-assistant.preferences.description' | translate }}</p>\n\n <fieldset>\n <legend>{{ 'nh-assistant.preferences.style-label' | translate }}</legend>\n <div class=\"options styles\">\n @for (option of styles; track option) {\n <label class=\"option\" [class.selected]=\"style() === option\">\n <input type=\"radio\" [name]=\"id + '-style'\" [value]=\"option\" [checked]=\"style() === option\" (change)=\"choose(style, option)\" />\n <span>\n <strong>{{ 'nh-assistant.preferences.style.' + option | translate }}</strong>\n <small>{{ 'nh-assistant.preferences.style.' + option + '-description' | translate }}</small>\n </span>\n </label>\n }\n </div>\n </fieldset>\n\n <fieldset>\n <legend>{{ 'nh-assistant.preferences.address-form-label' | translate }}</legend>\n <div class=\"options segmented\">\n @for (option of addressForms; track option) {\n <label class=\"option\" [class.selected]=\"addressForm() === option\">\n <input type=\"radio\" [name]=\"id + '-address'\" [value]=\"option\" [checked]=\"addressForm() === option\" (change)=\"choose(addressForm, option)\" />\n <span><strong>{{ 'nh-assistant.preferences.address-form.' + option | translate }}</strong></span>\n </label>\n }\n </div>\n </fieldset>\n\n <fieldset>\n <legend>{{ 'nh-assistant.preferences.response-length-label' | translate }}</legend>\n <div class=\"options segmented\">\n @for (option of responseLengths; track option) {\n <label class=\"option\" [class.selected]=\"responseLength() === option\">\n <input type=\"radio\" [name]=\"id + '-length'\" [value]=\"option\" [checked]=\"responseLength() === option\" (change)=\"choose(responseLength, option)\" />\n <span><strong>{{ 'nh-assistant.preferences.response-length.' + option | translate }}</strong></span>\n </label>\n }\n </div>\n </fieldset>\n\n <div class=\"field\">\n <label [for]=\"id + '-instructions'\">{{ 'nh-assistant.preferences.custom-instructions-label' | translate }}</label>\n <textarea\n rows=\"4\"\n [id]=\"id + '-instructions'\"\n [value]=\"customInstructions()\"\n [attr.aria-describedby]=\"id + '-instructions-hint'\"\n [attr.aria-invalid]=\"tooLong() ? true : null\"\n (input)=\"onInstructionsInput($event)\"></textarea>\n <p class=\"hint\" [id]=\"id + '-instructions-hint'\">\n @if (tooLong()) {\n <span class=\"error-text\">{{ 'nh-assistant.preferences.too-long' | translate }}</span>\n } @else {\n <span>{{ 'nh-assistant.preferences.custom-instructions-hint' | translate }}</span>\n }\n <span class=\"counter\" [class.error-text]=\"tooLong()\">\n {{ 'nh-assistant.preferences.characters' | translate: { count: instructionsLength(), max: maxInstructions } }}\n </span>\n </p>\n </div>\n\n @if (error(); as error) {\n <p class=\"message error\" role=\"alert\">\n <nh-assistant-icon name=\"warning\" />\n {{ errorKeys() | nhAssistantTranslate: error.code }}\n </p>\n }\n @if (savedMessage()) {\n <p class=\"message success\" role=\"status\">\n <nh-assistant-icon name=\"check\" />\n {{ 'nh-assistant.preferences.saved' | translate }}\n </p>\n }\n\n <div class=\"actions\">\n <button class=\"secondary\" type=\"button\" (click)=\"closed.emit()\">{{ 'nh-assistant.preferences.back' | translate }}</button>\n <button class=\"primary\" type=\"submit\" [disabled]=\"!canSave()\">\n {{ (saving() ? 'nh-assistant.preferences.saving' : 'nh-assistant.preferences.save') | translate }}\n </button>\n </div>\n </form>\n }\n</section>\n", styles: [":host{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){:host{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}:host{display:flex;flex-direction:column;min-height:0;color:var(--nha-text)}:host fieldset{min-width:0;margin:0;border:0;padding:0}:host legend,:host .label,:host .field>label{display:block;margin-bottom:6px;padding:0;color:var(--nha-text);font-size:12px;font-weight:700}:host input[type=text],:host input[type=url],:host input[type=password],:host input[type=search],:host select,:host textarea{width:100%;min-width:0;min-height:38px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:8px 10px;color:var(--nha-text);background:var(--nha-background);font:inherit;font-size:13px}:host input[type=text]:focus-visible,:host input[type=url]:focus-visible,:host input[type=password]:focus-visible,:host input[type=search]:focus-visible,:host select:focus-visible,:host textarea:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host input[type=text]:disabled,:host input[type=url]:disabled,:host input[type=password]:disabled,:host input[type=search]:disabled,:host select:disabled,:host textarea:disabled{cursor:not-allowed;opacity:.6}:host input[type=text][aria-invalid=true],:host input[type=url][aria-invalid=true],:host input[type=password][aria-invalid=true],:host input[type=search][aria-invalid=true],:host select[aria-invalid=true],:host textarea[aria-invalid=true]{border-color:var(--nha-danger)}:host textarea{line-height:1.45;resize:vertical}:host .mono{font-family:var(--nha-mono);font-size:12px}:host .field{display:flex;flex-direction:column;min-width:0}:host .hint{display:flex;justify-content:space-between;gap:8px;margin:4px 2px 0;color:var(--nha-text-muted);font-size:11px}:host .counter{flex:none;font-variant-numeric:tabular-nums}:host .error-text{color:var(--nha-danger)}:host .message{display:flex;align-items:center;gap:8px;margin:0;border:1px solid transparent;border-radius:var(--nha-radius-control);padding:8px 12px;font-size:13px}:host .message.error{border-color:var(--nha-danger);color:var(--nha-text);background:var(--nha-danger-soft)}:host .message.warning{border-color:var(--nha-warning);color:var(--nha-text);background:var(--nha-warning-soft)}:host .message.success{border-color:var(--nha-success);color:var(--nha-text);background:var(--nha-success-soft)}:host .message nh-assistant-icon{flex:none}:host .primary,:host .secondary,:host .danger{display:inline-flex;align-items:center;justify-content:center;gap:6px;min-height:36px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px 14px;font:inherit;font-size:13px;font-weight:700;white-space:nowrap;cursor:pointer}:host .primary:focus-visible,:host .secondary:focus-visible,:host .danger:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .primary:disabled,:host .secondary:disabled,:host .danger:disabled{cursor:not-allowed;opacity:.55}:host .primary{border-color:var(--nha-accent);color:var(--nha-accent-contrast);background:var(--nha-accent)}:host .primary:hover:not(:disabled){filter:brightness(1.12)}:host .secondary{color:var(--nha-text);background:transparent}:host .secondary:hover:not(:disabled){background:var(--nha-surface-strong)}:host .danger{border-color:var(--nha-danger);color:var(--nha-danger);background:transparent}:host .danger:hover:not(:disabled){background:var(--nha-danger-soft)}:host .icon-button{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:1px solid transparent;border-radius:var(--nha-radius-control);color:var(--nha-text-muted);background:transparent;cursor:pointer}:host .icon-button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .icon-button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}:host .icon-button:disabled{cursor:not-allowed;opacity:.5}:host .actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px}:host .badge{display:inline-flex;align-items:center;gap:4px;border-radius:999px;padding:2px 8px;color:var(--nha-text-muted);background:var(--nha-surface-strong);font-size:11px;font-weight:700;white-space:nowrap}:host .badge.success{color:var(--nha-success);background:var(--nha-success-soft)}:host .badge.warning{color:var(--nha-warning);background:var(--nha-warning-soft)}:host .badge.danger{color:var(--nha-danger);background:var(--nha-danger-soft)}:host .visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}.preferences{display:flex;flex:1;flex-direction:column;min-height:0}.header{display:flex;align-items:center;gap:8px;padding:12px 12px 8px 16px}h3{flex:1;margin:0;font-size:15px;font-weight:700}.state{display:flex;flex-direction:column;align-items:flex-start;gap:10px;margin:0;padding:12px 16px;color:var(--nha-text-muted);font-size:13px}.state p{margin:0}.form{display:flex;flex:1;flex-direction:column;gap:18px;min-height:0;overflow-y:auto;padding:0 16px 16px}.description{margin:0;color:var(--nha-text-muted);font-size:13px}.options{display:grid;gap:8px}.styles{grid-template-columns:repeat(auto-fit,minmax(150px,1fr))}.segmented{grid-template-columns:repeat(auto-fit,minmax(90px,1fr))}.option{position:relative;display:flex;align-items:flex-start;gap:8px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:10px;background:var(--nha-background);cursor:pointer}.option:hover{background:var(--nha-surface)}.option.selected{border-color:var(--nha-highlight);background:var(--nha-surface);box-shadow:inset 0 0 0 1px var(--nha-highlight)}.option:focus-within{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.option input{flex:none;margin:2px 0 0;accent-color:var(--nha-highlight)}.option span{display:flex;flex-direction:column;gap:2px;min-width:0}.option strong{font-size:13px}.option small{color:var(--nha-text-muted);font-size:12px}\n"] }]
|
|
4319
|
+
args: [{ selector: 'nh-assistant-preferences', standalone: true, imports: [TranslatePipe, NhAssistantTranslatePipe, NhAssistantIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<section class=\"preferences\" [attr.aria-labelledby]=\"id + '-title'\" [attr.aria-busy]=\"loading() || saving()\">\n <header class=\"header\">\n <h3 [id]=\"id + '-title'\">{{ 'nh-assistant.preferences.title' | translate }}</h3>\n <button\n class=\"icon-button\"\n type=\"button\"\n [attr.aria-label]=\"'nh-assistant.preferences.back' | translate\"\n [attr.title]=\"'nh-assistant.preferences.back' | translate\"\n (click)=\"closed.emit()\">\n <nh-assistant-icon name=\"close\" />\n </button>\n </header>\n\n @if (loading()) {\n <p class=\"state\" role=\"status\">{{ 'nh-assistant.preferences.loading' | translate }}</p>\n } @else if (loadFailed()) {\n <div class=\"state\" role=\"alert\">\n <p>{{ 'nh-assistant.preferences.load-failed' | translate }}</p>\n <button class=\"secondary\" type=\"button\" (click)=\"load()\">{{ 'nh-assistant.preferences.retry' | translate }}</button>\n </div>\n } @else {\n <form class=\"form\" (submit)=\"$event.preventDefault(); save()\">\n @if (push.state() !== 'unavailable') {\n <fieldset class=\"notifications\">\n <legend>{{ 'nh-assistant.preferences.notifications-label' | translate }}</legend>\n <label class=\"switch\">\n <input\n type=\"checkbox\"\n [checked]=\"push.settings()?.pushEnabled === true\"\n [disabled]=\"push.busy()\"\n [attr.aria-describedby]=\"id + '-notifications-hint'\"\n (change)=\"toggleNotifications($event)\" />\n <span>{{ 'nh-assistant.preferences.notifications' | translate }}</span>\n </label>\n <p class=\"hint\" [id]=\"id + '-notifications-hint'\">\n <span>{{ 'nh-assistant.preferences.notifications-state.' + push.state() | translate }}</span>\n </p>\n </fieldset>\n }\n\n <p class=\"description\">{{ 'nh-assistant.preferences.description' | translate }}</p>\n\n <fieldset>\n <legend>{{ 'nh-assistant.preferences.style-label' | translate }}</legend>\n <div class=\"options styles\">\n @for (option of styles; track option) {\n <label class=\"option\" [class.selected]=\"style() === option\">\n <input type=\"radio\" [name]=\"id + '-style'\" [value]=\"option\" [checked]=\"style() === option\" (change)=\"choose(style, option)\" />\n <span>\n <strong>{{ 'nh-assistant.preferences.style.' + option | translate }}</strong>\n <small>{{ 'nh-assistant.preferences.style.' + option + '-description' | translate }}</small>\n </span>\n </label>\n }\n </div>\n </fieldset>\n\n <fieldset>\n <legend>{{ 'nh-assistant.preferences.address-form-label' | translate }}</legend>\n <div class=\"options segmented\">\n @for (option of addressForms; track option) {\n <label class=\"option\" [class.selected]=\"addressForm() === option\">\n <input type=\"radio\" [name]=\"id + '-address'\" [value]=\"option\" [checked]=\"addressForm() === option\" (change)=\"choose(addressForm, option)\" />\n <span><strong>{{ 'nh-assistant.preferences.address-form.' + option | translate }}</strong></span>\n </label>\n }\n </div>\n </fieldset>\n\n <fieldset>\n <legend>{{ 'nh-assistant.preferences.response-length-label' | translate }}</legend>\n <div class=\"options segmented\">\n @for (option of responseLengths; track option) {\n <label class=\"option\" [class.selected]=\"responseLength() === option\">\n <input type=\"radio\" [name]=\"id + '-length'\" [value]=\"option\" [checked]=\"responseLength() === option\" (change)=\"choose(responseLength, option)\" />\n <span><strong>{{ 'nh-assistant.preferences.response-length.' + option | translate }}</strong></span>\n </label>\n }\n </div>\n </fieldset>\n\n <div class=\"field\">\n <label [for]=\"id + '-instructions'\">{{ 'nh-assistant.preferences.custom-instructions-label' | translate }}</label>\n <textarea\n rows=\"4\"\n [id]=\"id + '-instructions'\"\n [value]=\"customInstructions()\"\n [attr.aria-describedby]=\"id + '-instructions-hint'\"\n [attr.aria-invalid]=\"tooLong() ? true : null\"\n (input)=\"onInstructionsInput($event)\"></textarea>\n <p class=\"hint\" [id]=\"id + '-instructions-hint'\">\n @if (tooLong()) {\n <span class=\"error-text\">{{ 'nh-assistant.preferences.too-long' | translate }}</span>\n } @else {\n <span>{{ 'nh-assistant.preferences.custom-instructions-hint' | translate }}</span>\n }\n <span class=\"counter\" [class.error-text]=\"tooLong()\">\n {{ 'nh-assistant.preferences.characters' | translate: { count: instructionsLength(), max: maxInstructions } }}\n </span>\n </p>\n </div>\n\n @if (error(); as error) {\n <p class=\"message error\" role=\"alert\">\n <nh-assistant-icon name=\"warning\" />\n {{ errorKeys() | nhAssistantTranslate: error.code }}\n </p>\n }\n @if (savedMessage()) {\n <p class=\"message success\" role=\"status\">\n <nh-assistant-icon name=\"check\" />\n {{ 'nh-assistant.preferences.saved' | translate }}\n </p>\n }\n\n <div class=\"actions\">\n <button class=\"secondary\" type=\"button\" (click)=\"closed.emit()\">{{ 'nh-assistant.preferences.back' | translate }}</button>\n <button class=\"primary\" type=\"submit\" [disabled]=\"!canSave()\">\n {{ (saving() ? 'nh-assistant.preferences.saving' : 'nh-assistant.preferences.save') | translate }}\n </button>\n </div>\n </form>\n }\n</section>\n", styles: [":host{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){:host{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}:host{display:flex;flex-direction:column;min-height:0;color:var(--nha-text)}:host fieldset{min-width:0;margin:0;border:0;padding:0}:host legend,:host .label,:host .field>label{display:block;margin-bottom:6px;padding:0;color:var(--nha-text);font-size:12px;font-weight:700}:host input[type=text],:host input[type=url],:host input[type=password],:host input[type=search],:host select,:host textarea{width:100%;min-width:0;min-height:38px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:8px 10px;color:var(--nha-text);background:var(--nha-background);font:inherit;font-size:13px}:host input[type=text]:focus-visible,:host input[type=url]:focus-visible,:host input[type=password]:focus-visible,:host input[type=search]:focus-visible,:host select:focus-visible,:host textarea:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host input[type=text]:disabled,:host input[type=url]:disabled,:host input[type=password]:disabled,:host input[type=search]:disabled,:host select:disabled,:host textarea:disabled{cursor:not-allowed;opacity:.6}:host input[type=text][aria-invalid=true],:host input[type=url][aria-invalid=true],:host input[type=password][aria-invalid=true],:host input[type=search][aria-invalid=true],:host select[aria-invalid=true],:host textarea[aria-invalid=true]{border-color:var(--nha-danger)}:host textarea{line-height:1.45;resize:vertical}:host .mono{font-family:var(--nha-mono);font-size:12px}:host .field{display:flex;flex-direction:column;min-width:0}:host .hint{display:flex;justify-content:space-between;gap:8px;margin:4px 2px 0;color:var(--nha-text-muted);font-size:11px}:host .counter{flex:none;font-variant-numeric:tabular-nums}:host .error-text{color:var(--nha-danger)}:host .message{display:flex;align-items:center;gap:8px;margin:0;border:1px solid transparent;border-radius:var(--nha-radius-control);padding:8px 12px;font-size:13px}:host .message.error{border-color:var(--nha-danger);color:var(--nha-text);background:var(--nha-danger-soft)}:host .message.warning{border-color:var(--nha-warning);color:var(--nha-text);background:var(--nha-warning-soft)}:host .message.success{border-color:var(--nha-success);color:var(--nha-text);background:var(--nha-success-soft)}:host .message nh-assistant-icon{flex:none}:host .primary,:host .secondary,:host .danger{display:inline-flex;align-items:center;justify-content:center;gap:6px;min-height:36px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px 14px;font:inherit;font-size:13px;font-weight:700;white-space:nowrap;cursor:pointer}:host .primary:focus-visible,:host .secondary:focus-visible,:host .danger:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .primary:disabled,:host .secondary:disabled,:host .danger:disabled{cursor:not-allowed;opacity:.55}:host .primary{border-color:var(--nha-accent);color:var(--nha-accent-contrast);background:var(--nha-accent)}:host .primary:hover:not(:disabled){filter:brightness(1.12)}:host .secondary{color:var(--nha-text);background:transparent}:host .secondary:hover:not(:disabled){background:var(--nha-surface-strong)}:host .danger{border-color:var(--nha-danger);color:var(--nha-danger);background:transparent}:host .danger:hover:not(:disabled){background:var(--nha-danger-soft)}:host .icon-button{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:1px solid transparent;border-radius:var(--nha-radius-control);color:var(--nha-text-muted);background:transparent;cursor:pointer}:host .icon-button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .icon-button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}:host .icon-button:disabled{cursor:not-allowed;opacity:.5}:host .actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px}:host .badge{display:inline-flex;align-items:center;gap:4px;border-radius:999px;padding:2px 8px;color:var(--nha-text-muted);background:var(--nha-surface-strong);font-size:11px;font-weight:700;white-space:nowrap}:host .badge.success{color:var(--nha-success);background:var(--nha-success-soft)}:host .badge.warning{color:var(--nha-warning);background:var(--nha-warning-soft)}:host .badge.danger{color:var(--nha-danger);background:var(--nha-danger-soft)}:host .visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}.preferences{display:flex;flex:1;flex-direction:column;min-height:0}.header{display:flex;align-items:center;gap:8px;padding:12px 12px 8px 16px}h3{flex:1;margin:0;font-size:15px;font-weight:700}.state{display:flex;flex-direction:column;align-items:flex-start;gap:10px;margin:0;padding:12px 16px;color:var(--nha-text-muted);font-size:13px}.state p{margin:0}.form{display:flex;flex:1;flex-direction:column;gap:18px;min-height:0;overflow-y:auto;padding:0 16px 16px}.description{margin:0;color:var(--nha-text-muted);font-size:13px}.options{display:grid;gap:8px}.styles{grid-template-columns:repeat(auto-fit,minmax(150px,1fr))}.segmented{grid-template-columns:repeat(auto-fit,minmax(90px,1fr))}.option{position:relative;display:flex;align-items:flex-start;gap:8px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:10px;background:var(--nha-background);cursor:pointer}.option:hover{background:var(--nha-surface)}.option.selected{border-color:var(--nha-highlight);background:var(--nha-surface);box-shadow:inset 0 0 0 1px var(--nha-highlight)}.option:focus-within{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.option input{flex:none;margin:2px 0 0;accent-color:var(--nha-highlight)}.option span{display:flex;flex-direction:column;gap:2px;min-width:0}.option strong{font-size:13px}.option small{color:var(--nha-text-muted);font-size:12px}.switch{display:flex;align-items:center;gap:8px;font-size:13px;font-weight:650;cursor:pointer}.switch input{width:16px;height:16px;margin:0;accent-color:var(--nha-highlight)}\n"] }]
|
|
3073
4320
|
}], ctorParameters: () => [], propDecorators: { closed: [{ type: i0.Output, args: ["closed"] }], saved: [{ type: i0.Output, args: ["saved"] }] } });
|
|
3074
4321
|
|
|
4322
|
+
let nextId$3 = 0;
|
|
4323
|
+
const searchDelayMs = 250;
|
|
4324
|
+
/**
|
|
4325
|
+
* Sharing of the open conversation. The owner creates, copies, renews or stops an invitation
|
|
4326
|
+
* link, invites colleagues through the application's directory when it offers one, and removes
|
|
4327
|
+
* participants. A participant sees who takes part and can leave.
|
|
4328
|
+
*/
|
|
4329
|
+
class NhAssistantShareComponent {
|
|
4330
|
+
constructor() {
|
|
4331
|
+
this.store = inject(NhAssistantStore);
|
|
4332
|
+
this.config = inject(NH_ASSISTANT_CONFIG);
|
|
4333
|
+
this.searchRevision = 0;
|
|
4334
|
+
/** Emitted when the user leaves the view. */
|
|
4335
|
+
this.closed = output();
|
|
4336
|
+
this.id = `nh-assistant-share-${nextId$3++}`;
|
|
4337
|
+
this.conversation = this.store.activeConversation;
|
|
4338
|
+
this.isOwner = this.store.isOwner;
|
|
4339
|
+
this.busy = signal(false, ...(ngDevMode ? [{ debugName: "busy" }] : []));
|
|
4340
|
+
this.copied = signal(false, ...(ngDevMode ? [{ debugName: "copied" }] : []));
|
|
4341
|
+
this.query = signal('', ...(ngDevMode ? [{ debugName: "query" }] : []));
|
|
4342
|
+
this.searching = signal(false, ...(ngDevMode ? [{ debugName: "searching" }] : []));
|
|
4343
|
+
this.candidates = signal([], ...(ngDevMode ? [{ debugName: "candidates" }] : []));
|
|
4344
|
+
this.invited = signal(null, ...(ngDevMode ? [{ debugName: "invited" }] : []));
|
|
4345
|
+
this.directory = computed(() => this.store.collaboration()?.directory === true && this.isOwner(), ...(ngDevMode ? [{ debugName: "directory" }] : []));
|
|
4346
|
+
this.maxParticipants = computed(() => this.store.collaboration()?.maxParticipants ?? null, ...(ngDevMode ? [{ debugName: "maxParticipants" }] : []));
|
|
4347
|
+
this.participants = computed(() => this.store.members().filter(member => member.role === 'participant'), ...(ngDevMode ? [{ debugName: "participants" }] : []));
|
|
4348
|
+
this.link = computed(() => {
|
|
4349
|
+
const conversation = this.conversation();
|
|
4350
|
+
if (!conversation?.shareToken) {
|
|
4351
|
+
return null;
|
|
4352
|
+
}
|
|
4353
|
+
return (this.config.buildShareLink ?? nhAssistantShareLink)(conversation.id, conversation.shareToken);
|
|
4354
|
+
}, ...(ngDevMode ? [{ debugName: "link" }] : []));
|
|
4355
|
+
this.full = computed(() => {
|
|
4356
|
+
const max = this.maxParticipants();
|
|
4357
|
+
return max !== null && this.participants().length >= max;
|
|
4358
|
+
}, ...(ngDevMode ? [{ debugName: "full" }] : []));
|
|
4359
|
+
inject(DestroyRef).onDestroy(() => clearTimeout(this.searchTimer));
|
|
4360
|
+
}
|
|
4361
|
+
async createLink() {
|
|
4362
|
+
await this.run(() => this.store.createShareLink());
|
|
4363
|
+
this.copied.set(false);
|
|
4364
|
+
}
|
|
4365
|
+
async revokeLink() {
|
|
4366
|
+
await this.run(() => this.store.revokeShareLink());
|
|
4367
|
+
this.copied.set(false);
|
|
4368
|
+
}
|
|
4369
|
+
async copy(input) {
|
|
4370
|
+
const link = this.link();
|
|
4371
|
+
if (!link) {
|
|
4372
|
+
return;
|
|
4373
|
+
}
|
|
4374
|
+
try {
|
|
4375
|
+
await navigator.clipboard.writeText(link);
|
|
4376
|
+
}
|
|
4377
|
+
catch {
|
|
4378
|
+
// Without clipboard access the selected text can be copied by hand.
|
|
4379
|
+
input.select();
|
|
4380
|
+
}
|
|
4381
|
+
this.copied.set(true);
|
|
4382
|
+
}
|
|
4383
|
+
onSearch(event) {
|
|
4384
|
+
const value = event.target.value;
|
|
4385
|
+
this.query.set(value);
|
|
4386
|
+
this.invited.set(null);
|
|
4387
|
+
clearTimeout(this.searchTimer);
|
|
4388
|
+
const revision = ++this.searchRevision;
|
|
4389
|
+
if (value.trim().length < 2) {
|
|
4390
|
+
this.candidates.set([]);
|
|
4391
|
+
this.searching.set(false);
|
|
4392
|
+
return;
|
|
4393
|
+
}
|
|
4394
|
+
this.searching.set(true);
|
|
4395
|
+
this.searchTimer = setTimeout(async () => {
|
|
4396
|
+
const results = await this.store.searchParticipantCandidates(value);
|
|
4397
|
+
if (revision === this.searchRevision) {
|
|
4398
|
+
this.candidates.set(results);
|
|
4399
|
+
this.searching.set(false);
|
|
4400
|
+
}
|
|
4401
|
+
}, searchDelayMs);
|
|
4402
|
+
}
|
|
4403
|
+
async invite(entry) {
|
|
4404
|
+
const invited = await this.run(() => this.store.inviteParticipant(entry.actorId));
|
|
4405
|
+
if (invited) {
|
|
4406
|
+
this.invited.set(entry.displayName);
|
|
4407
|
+
this.candidates.update(items => items.filter(item => item.actorId !== entry.actorId));
|
|
4408
|
+
}
|
|
4409
|
+
}
|
|
4410
|
+
async remove(member) {
|
|
4411
|
+
await this.run(() => this.store.removeParticipant(member.actorId));
|
|
4412
|
+
}
|
|
4413
|
+
async leave() {
|
|
4414
|
+
const actorId = this.conversation()?.currentActorId;
|
|
4415
|
+
if (!actorId) {
|
|
4416
|
+
return;
|
|
4417
|
+
}
|
|
4418
|
+
if (await this.run(() => this.store.removeParticipant(actorId))) {
|
|
4419
|
+
this.closed.emit();
|
|
4420
|
+
}
|
|
4421
|
+
}
|
|
4422
|
+
isCurrentUser(member) {
|
|
4423
|
+
return member.actorId === this.conversation()?.currentActorId;
|
|
4424
|
+
}
|
|
4425
|
+
async run(action) {
|
|
4426
|
+
if (this.busy()) {
|
|
4427
|
+
return undefined;
|
|
4428
|
+
}
|
|
4429
|
+
this.busy.set(true);
|
|
4430
|
+
try {
|
|
4431
|
+
return await action();
|
|
4432
|
+
}
|
|
4433
|
+
finally {
|
|
4434
|
+
this.busy.set(false);
|
|
4435
|
+
}
|
|
4436
|
+
}
|
|
4437
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantShareComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
4438
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.33", type: NhAssistantShareComponent, isStandalone: true, selector: "nh-assistant-share", outputs: { closed: "closed" }, ngImport: i0, template: "<section class=\"share\" [attr.aria-labelledby]=\"id + '-title'\" [attr.aria-busy]=\"busy()\">\n <header class=\"header\">\n <h3 [id]=\"id + '-title'\">\n {{ (isOwner() ? 'nh-assistant.share.title' : 'nh-assistant.share.members-title') | translate }}\n </h3>\n <button\n class=\"icon-button\"\n type=\"button\"\n [attr.aria-label]=\"'nh-assistant.share.back' | translate\"\n [attr.title]=\"'nh-assistant.share.back' | translate\"\n (click)=\"closed.emit()\">\n <nh-assistant-icon name=\"close\" />\n </button>\n </header>\n\n @if (conversation(); as conversation) {\n <div class=\"content\">\n @if (isOwner()) {\n <p class=\"description\">{{ 'nh-assistant.share.description' | translate }}</p>\n\n <section class=\"block\" [attr.aria-labelledby]=\"id + '-link'\">\n <h4 [id]=\"id + '-link'\">{{ 'nh-assistant.share.link-title' | translate }}</h4>\n @if (link(); as link) {\n <div class=\"link-row\">\n <label class=\"visually-hidden\" [for]=\"id + '-link-input'\">{{ 'nh-assistant.share.link-label' | translate }}</label>\n <input #linkInput type=\"text\" readonly [id]=\"id + '-link-input'\" [value]=\"link\" (focus)=\"linkInput.select()\" />\n <button\n class=\"icon-button\"\n type=\"button\"\n [attr.aria-label]=\"'nh-assistant.share.copy' | translate\"\n [attr.title]=\"'nh-assistant.share.copy' | translate\"\n (click)=\"copy(linkInput)\">\n <nh-assistant-icon name=\"copy\" />\n </button>\n </div>\n @if (copied()) {\n <p class=\"message success\" role=\"status\">\n <nh-assistant-icon name=\"check\" />\n {{ 'nh-assistant.share.copied' | translate }}\n </p>\n }\n <div class=\"actions\">\n <button class=\"secondary\" type=\"button\" [disabled]=\"busy()\" (click)=\"revokeLink()\">\n {{ 'nh-assistant.share.revoke' | translate }}\n </button>\n <button class=\"secondary\" type=\"button\" [disabled]=\"busy()\" (click)=\"createLink()\">\n {{ 'nh-assistant.share.renew' | translate }}\n </button>\n </div>\n <p class=\"hint\">{{ 'nh-assistant.share.link-hint' | translate }}</p>\n } @else {\n <button class=\"primary\" type=\"button\" [disabled]=\"busy() || full()\" (click)=\"createLink()\">\n <nh-assistant-icon name=\"link\" />\n {{ 'nh-assistant.share.create' | translate }}\n </button>\n }\n </section>\n\n @if (directory()) {\n <section class=\"block\" [attr.aria-labelledby]=\"id + '-invite'\">\n <h4 [id]=\"id + '-invite'\">{{ 'nh-assistant.share.invite-title' | translate }}</h4>\n <label class=\"visually-hidden\" [for]=\"id + '-search'\">{{ 'nh-assistant.share.search-label' | translate }}</label>\n <input\n type=\"search\"\n autocomplete=\"off\"\n [id]=\"id + '-search'\"\n [value]=\"query()\"\n [disabled]=\"full()\"\n [attr.placeholder]=\"'nh-assistant.share.search-placeholder' | translate\"\n (input)=\"onSearch($event)\" />\n @if (searching()) {\n <p class=\"hint\" role=\"status\">{{ 'nh-assistant.share.searching' | translate }}</p>\n } @else if (query().trim().length >= 2 && candidates().length === 0 && !invited()) {\n <p class=\"hint\">{{ 'nh-assistant.share.no-results' | translate }}</p>\n }\n @if (candidates().length > 0) {\n <ul class=\"people\">\n @for (entry of candidates(); track entry.actorId) {\n <li>\n <span class=\"person\">\n <strong>{{ entry.displayName }}</strong>\n @if (entry.detail) {\n <small>{{ entry.detail }}</small>\n }\n </span>\n <button class=\"secondary small\" type=\"button\" [disabled]=\"busy() || full()\" (click)=\"invite(entry)\">\n {{ 'nh-assistant.share.invite' | translate }}\n </button>\n </li>\n }\n </ul>\n }\n @if (invited(); as name) {\n <p class=\"message success\" role=\"status\">\n <nh-assistant-icon name=\"check\" />\n {{ 'nh-assistant.share.invited' | translate: { name } }}\n </p>\n }\n </section>\n }\n }\n\n <section class=\"block\" [attr.aria-labelledby]=\"id + '-members'\">\n <h4 [id]=\"id + '-members'\">\n {{ 'nh-assistant.share.members' | translate }}\n @if (maxParticipants() !== null && isOwner()) {\n <span class=\"count\">{{ 'nh-assistant.share.count' | translate: { count: participants().length, max: maxParticipants() } }}</span>\n }\n </h4>\n @if (store.members().length === 0) {\n <p class=\"hint\">{{ 'nh-assistant.share.not-shared' | translate }}</p>\n } @else {\n <ul class=\"people\">\n @for (member of store.members(); track member.actorId) {\n <li>\n <span class=\"person\">\n <strong>\n {{ member.displayName || ('nh-assistant.share.unnamed' | translate) }}\n @if (isCurrentUser(member)) {\n <small class=\"you\">{{ 'nh-assistant.share.you' | translate }}</small>\n }\n </strong>\n <small>{{ 'nh-assistant.share.role.' + member.role | translate }}</small>\n </span>\n @if (isOwner() && member.role === 'participant') {\n <button\n class=\"icon-button\"\n type=\"button\"\n [disabled]=\"busy()\"\n [attr.aria-label]=\"'nh-assistant.share.remove' | translate: { name: member.displayName || ('nh-assistant.share.unnamed' | translate) }\"\n [attr.title]=\"'nh-assistant.share.remove' | translate: { name: member.displayName || ('nh-assistant.share.unnamed' | translate) }\"\n (click)=\"remove(member)\">\n <nh-assistant-icon name=\"trash\" />\n </button>\n }\n </li>\n }\n </ul>\n }\n </section>\n\n @if (!isOwner()) {\n <div class=\"actions\">\n <button class=\"secondary danger\" type=\"button\" [disabled]=\"busy() || conversation.status === 'running'\" (click)=\"leave()\">\n <nh-assistant-icon name=\"leave\" />\n {{ 'nh-assistant.share.leave' | translate }}\n </button>\n </div>\n }\n </div>\n } @else {\n <p class=\"state\">{{ 'nh-assistant.share.no-conversation' | translate }}</p>\n }\n</section>\n", styles: [":host{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){:host{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}:host{display:flex;flex-direction:column;min-height:0;color:var(--nha-text)}:host fieldset{min-width:0;margin:0;border:0;padding:0}:host legend,:host .label,:host .field>label{display:block;margin-bottom:6px;padding:0;color:var(--nha-text);font-size:12px;font-weight:700}:host input[type=text],:host input[type=url],:host input[type=password],:host input[type=search],:host select,:host textarea{width:100%;min-width:0;min-height:38px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:8px 10px;color:var(--nha-text);background:var(--nha-background);font:inherit;font-size:13px}:host input[type=text]:focus-visible,:host input[type=url]:focus-visible,:host input[type=password]:focus-visible,:host input[type=search]:focus-visible,:host select:focus-visible,:host textarea:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host input[type=text]:disabled,:host input[type=url]:disabled,:host input[type=password]:disabled,:host input[type=search]:disabled,:host select:disabled,:host textarea:disabled{cursor:not-allowed;opacity:.6}:host input[type=text][aria-invalid=true],:host input[type=url][aria-invalid=true],:host input[type=password][aria-invalid=true],:host input[type=search][aria-invalid=true],:host select[aria-invalid=true],:host textarea[aria-invalid=true]{border-color:var(--nha-danger)}:host textarea{line-height:1.45;resize:vertical}:host .mono{font-family:var(--nha-mono);font-size:12px}:host .field{display:flex;flex-direction:column;min-width:0}:host .hint{display:flex;justify-content:space-between;gap:8px;margin:4px 2px 0;color:var(--nha-text-muted);font-size:11px}:host .counter{flex:none;font-variant-numeric:tabular-nums}:host .error-text{color:var(--nha-danger)}:host .message{display:flex;align-items:center;gap:8px;margin:0;border:1px solid transparent;border-radius:var(--nha-radius-control);padding:8px 12px;font-size:13px}:host .message.error{border-color:var(--nha-danger);color:var(--nha-text);background:var(--nha-danger-soft)}:host .message.warning{border-color:var(--nha-warning);color:var(--nha-text);background:var(--nha-warning-soft)}:host .message.success{border-color:var(--nha-success);color:var(--nha-text);background:var(--nha-success-soft)}:host .message nh-assistant-icon{flex:none}:host .primary,:host .secondary,:host .danger{display:inline-flex;align-items:center;justify-content:center;gap:6px;min-height:36px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px 14px;font:inherit;font-size:13px;font-weight:700;white-space:nowrap;cursor:pointer}:host .primary:focus-visible,:host .secondary:focus-visible,:host .danger:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .primary:disabled,:host .secondary:disabled,:host .danger:disabled{cursor:not-allowed;opacity:.55}:host .primary{border-color:var(--nha-accent);color:var(--nha-accent-contrast);background:var(--nha-accent)}:host .primary:hover:not(:disabled){filter:brightness(1.12)}:host .secondary{color:var(--nha-text);background:transparent}:host .secondary:hover:not(:disabled){background:var(--nha-surface-strong)}:host .danger{border-color:var(--nha-danger);color:var(--nha-danger);background:transparent}:host .danger:hover:not(:disabled){background:var(--nha-danger-soft)}:host .icon-button{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:1px solid transparent;border-radius:var(--nha-radius-control);color:var(--nha-text-muted);background:transparent;cursor:pointer}:host .icon-button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .icon-button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}:host .icon-button:disabled{cursor:not-allowed;opacity:.5}:host .actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px}:host .badge{display:inline-flex;align-items:center;gap:4px;border-radius:999px;padding:2px 8px;color:var(--nha-text-muted);background:var(--nha-surface-strong);font-size:11px;font-weight:700;white-space:nowrap}:host .badge.success{color:var(--nha-success);background:var(--nha-success-soft)}:host .badge.warning{color:var(--nha-warning);background:var(--nha-warning-soft)}:host .badge.danger{color:var(--nha-danger);background:var(--nha-danger-soft)}:host .visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}.share{display:flex;flex:1;flex-direction:column;min-height:0}.header{display:flex;align-items:center;gap:8px;padding:12px 12px 8px 16px}h3{flex:1;margin:0;font-size:15px;font-weight:700}.content{display:flex;flex:1;flex-direction:column;gap:18px;min-height:0;overflow-y:auto;padding:0 16px 16px}.description,.state{margin:0;color:var(--nha-text-muted);font-size:13px}.state{padding:12px 16px}.block{display:flex;flex-direction:column;gap:8px}h4{display:flex;align-items:baseline;gap:8px;margin:0;font-size:12px;font-weight:700}.count{color:var(--nha-text-muted);font-size:11px;font-weight:400}.link-row{display:flex;align-items:center;gap:4px}.link-row input{font-family:var(--nha-mono);font-size:12px}.actions{justify-content:flex-start}.people{display:flex;flex-direction:column;gap:4px;margin:0;padding:0;list-style:none}.people li{display:flex;align-items:center;gap:8px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px 6px 6px 10px}.person{display:flex;flex:1;flex-direction:column;min-width:0}.person strong{overflow:hidden;font-size:13px;text-overflow:ellipsis;white-space:nowrap}.person small{color:var(--nha-text-muted);font-size:11px}.you{margin-left:4px;font-weight:400}.small{min-height:30px;padding:4px 10px;font-size:12px}\n"], dependencies: [{ kind: "component", type: NhAssistantIconComponent, selector: "nh-assistant-icon", inputs: ["name"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
4439
|
+
}
|
|
4440
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantShareComponent, decorators: [{
|
|
4441
|
+
type: Component,
|
|
4442
|
+
args: [{ selector: 'nh-assistant-share', standalone: true, imports: [TranslatePipe, NhAssistantIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<section class=\"share\" [attr.aria-labelledby]=\"id + '-title'\" [attr.aria-busy]=\"busy()\">\n <header class=\"header\">\n <h3 [id]=\"id + '-title'\">\n {{ (isOwner() ? 'nh-assistant.share.title' : 'nh-assistant.share.members-title') | translate }}\n </h3>\n <button\n class=\"icon-button\"\n type=\"button\"\n [attr.aria-label]=\"'nh-assistant.share.back' | translate\"\n [attr.title]=\"'nh-assistant.share.back' | translate\"\n (click)=\"closed.emit()\">\n <nh-assistant-icon name=\"close\" />\n </button>\n </header>\n\n @if (conversation(); as conversation) {\n <div class=\"content\">\n @if (isOwner()) {\n <p class=\"description\">{{ 'nh-assistant.share.description' | translate }}</p>\n\n <section class=\"block\" [attr.aria-labelledby]=\"id + '-link'\">\n <h4 [id]=\"id + '-link'\">{{ 'nh-assistant.share.link-title' | translate }}</h4>\n @if (link(); as link) {\n <div class=\"link-row\">\n <label class=\"visually-hidden\" [for]=\"id + '-link-input'\">{{ 'nh-assistant.share.link-label' | translate }}</label>\n <input #linkInput type=\"text\" readonly [id]=\"id + '-link-input'\" [value]=\"link\" (focus)=\"linkInput.select()\" />\n <button\n class=\"icon-button\"\n type=\"button\"\n [attr.aria-label]=\"'nh-assistant.share.copy' | translate\"\n [attr.title]=\"'nh-assistant.share.copy' | translate\"\n (click)=\"copy(linkInput)\">\n <nh-assistant-icon name=\"copy\" />\n </button>\n </div>\n @if (copied()) {\n <p class=\"message success\" role=\"status\">\n <nh-assistant-icon name=\"check\" />\n {{ 'nh-assistant.share.copied' | translate }}\n </p>\n }\n <div class=\"actions\">\n <button class=\"secondary\" type=\"button\" [disabled]=\"busy()\" (click)=\"revokeLink()\">\n {{ 'nh-assistant.share.revoke' | translate }}\n </button>\n <button class=\"secondary\" type=\"button\" [disabled]=\"busy()\" (click)=\"createLink()\">\n {{ 'nh-assistant.share.renew' | translate }}\n </button>\n </div>\n <p class=\"hint\">{{ 'nh-assistant.share.link-hint' | translate }}</p>\n } @else {\n <button class=\"primary\" type=\"button\" [disabled]=\"busy() || full()\" (click)=\"createLink()\">\n <nh-assistant-icon name=\"link\" />\n {{ 'nh-assistant.share.create' | translate }}\n </button>\n }\n </section>\n\n @if (directory()) {\n <section class=\"block\" [attr.aria-labelledby]=\"id + '-invite'\">\n <h4 [id]=\"id + '-invite'\">{{ 'nh-assistant.share.invite-title' | translate }}</h4>\n <label class=\"visually-hidden\" [for]=\"id + '-search'\">{{ 'nh-assistant.share.search-label' | translate }}</label>\n <input\n type=\"search\"\n autocomplete=\"off\"\n [id]=\"id + '-search'\"\n [value]=\"query()\"\n [disabled]=\"full()\"\n [attr.placeholder]=\"'nh-assistant.share.search-placeholder' | translate\"\n (input)=\"onSearch($event)\" />\n @if (searching()) {\n <p class=\"hint\" role=\"status\">{{ 'nh-assistant.share.searching' | translate }}</p>\n } @else if (query().trim().length >= 2 && candidates().length === 0 && !invited()) {\n <p class=\"hint\">{{ 'nh-assistant.share.no-results' | translate }}</p>\n }\n @if (candidates().length > 0) {\n <ul class=\"people\">\n @for (entry of candidates(); track entry.actorId) {\n <li>\n <span class=\"person\">\n <strong>{{ entry.displayName }}</strong>\n @if (entry.detail) {\n <small>{{ entry.detail }}</small>\n }\n </span>\n <button class=\"secondary small\" type=\"button\" [disabled]=\"busy() || full()\" (click)=\"invite(entry)\">\n {{ 'nh-assistant.share.invite' | translate }}\n </button>\n </li>\n }\n </ul>\n }\n @if (invited(); as name) {\n <p class=\"message success\" role=\"status\">\n <nh-assistant-icon name=\"check\" />\n {{ 'nh-assistant.share.invited' | translate: { name } }}\n </p>\n }\n </section>\n }\n }\n\n <section class=\"block\" [attr.aria-labelledby]=\"id + '-members'\">\n <h4 [id]=\"id + '-members'\">\n {{ 'nh-assistant.share.members' | translate }}\n @if (maxParticipants() !== null && isOwner()) {\n <span class=\"count\">{{ 'nh-assistant.share.count' | translate: { count: participants().length, max: maxParticipants() } }}</span>\n }\n </h4>\n @if (store.members().length === 0) {\n <p class=\"hint\">{{ 'nh-assistant.share.not-shared' | translate }}</p>\n } @else {\n <ul class=\"people\">\n @for (member of store.members(); track member.actorId) {\n <li>\n <span class=\"person\">\n <strong>\n {{ member.displayName || ('nh-assistant.share.unnamed' | translate) }}\n @if (isCurrentUser(member)) {\n <small class=\"you\">{{ 'nh-assistant.share.you' | translate }}</small>\n }\n </strong>\n <small>{{ 'nh-assistant.share.role.' + member.role | translate }}</small>\n </span>\n @if (isOwner() && member.role === 'participant') {\n <button\n class=\"icon-button\"\n type=\"button\"\n [disabled]=\"busy()\"\n [attr.aria-label]=\"'nh-assistant.share.remove' | translate: { name: member.displayName || ('nh-assistant.share.unnamed' | translate) }\"\n [attr.title]=\"'nh-assistant.share.remove' | translate: { name: member.displayName || ('nh-assistant.share.unnamed' | translate) }\"\n (click)=\"remove(member)\">\n <nh-assistant-icon name=\"trash\" />\n </button>\n }\n </li>\n }\n </ul>\n }\n </section>\n\n @if (!isOwner()) {\n <div class=\"actions\">\n <button class=\"secondary danger\" type=\"button\" [disabled]=\"busy() || conversation.status === 'running'\" (click)=\"leave()\">\n <nh-assistant-icon name=\"leave\" />\n {{ 'nh-assistant.share.leave' | translate }}\n </button>\n </div>\n }\n </div>\n } @else {\n <p class=\"state\">{{ 'nh-assistant.share.no-conversation' | translate }}</p>\n }\n</section>\n", styles: [":host{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){:host{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}:host{display:flex;flex-direction:column;min-height:0;color:var(--nha-text)}:host fieldset{min-width:0;margin:0;border:0;padding:0}:host legend,:host .label,:host .field>label{display:block;margin-bottom:6px;padding:0;color:var(--nha-text);font-size:12px;font-weight:700}:host input[type=text],:host input[type=url],:host input[type=password],:host input[type=search],:host select,:host textarea{width:100%;min-width:0;min-height:38px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:8px 10px;color:var(--nha-text);background:var(--nha-background);font:inherit;font-size:13px}:host input[type=text]:focus-visible,:host input[type=url]:focus-visible,:host input[type=password]:focus-visible,:host input[type=search]:focus-visible,:host select:focus-visible,:host textarea:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host input[type=text]:disabled,:host input[type=url]:disabled,:host input[type=password]:disabled,:host input[type=search]:disabled,:host select:disabled,:host textarea:disabled{cursor:not-allowed;opacity:.6}:host input[type=text][aria-invalid=true],:host input[type=url][aria-invalid=true],:host input[type=password][aria-invalid=true],:host input[type=search][aria-invalid=true],:host select[aria-invalid=true],:host textarea[aria-invalid=true]{border-color:var(--nha-danger)}:host textarea{line-height:1.45;resize:vertical}:host .mono{font-family:var(--nha-mono);font-size:12px}:host .field{display:flex;flex-direction:column;min-width:0}:host .hint{display:flex;justify-content:space-between;gap:8px;margin:4px 2px 0;color:var(--nha-text-muted);font-size:11px}:host .counter{flex:none;font-variant-numeric:tabular-nums}:host .error-text{color:var(--nha-danger)}:host .message{display:flex;align-items:center;gap:8px;margin:0;border:1px solid transparent;border-radius:var(--nha-radius-control);padding:8px 12px;font-size:13px}:host .message.error{border-color:var(--nha-danger);color:var(--nha-text);background:var(--nha-danger-soft)}:host .message.warning{border-color:var(--nha-warning);color:var(--nha-text);background:var(--nha-warning-soft)}:host .message.success{border-color:var(--nha-success);color:var(--nha-text);background:var(--nha-success-soft)}:host .message nh-assistant-icon{flex:none}:host .primary,:host .secondary,:host .danger{display:inline-flex;align-items:center;justify-content:center;gap:6px;min-height:36px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px 14px;font:inherit;font-size:13px;font-weight:700;white-space:nowrap;cursor:pointer}:host .primary:focus-visible,:host .secondary:focus-visible,:host .danger:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .primary:disabled,:host .secondary:disabled,:host .danger:disabled{cursor:not-allowed;opacity:.55}:host .primary{border-color:var(--nha-accent);color:var(--nha-accent-contrast);background:var(--nha-accent)}:host .primary:hover:not(:disabled){filter:brightness(1.12)}:host .secondary{color:var(--nha-text);background:transparent}:host .secondary:hover:not(:disabled){background:var(--nha-surface-strong)}:host .danger{border-color:var(--nha-danger);color:var(--nha-danger);background:transparent}:host .danger:hover:not(:disabled){background:var(--nha-danger-soft)}:host .icon-button{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:1px solid transparent;border-radius:var(--nha-radius-control);color:var(--nha-text-muted);background:transparent;cursor:pointer}:host .icon-button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .icon-button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}:host .icon-button:disabled{cursor:not-allowed;opacity:.5}:host .actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px}:host .badge{display:inline-flex;align-items:center;gap:4px;border-radius:999px;padding:2px 8px;color:var(--nha-text-muted);background:var(--nha-surface-strong);font-size:11px;font-weight:700;white-space:nowrap}:host .badge.success{color:var(--nha-success);background:var(--nha-success-soft)}:host .badge.warning{color:var(--nha-warning);background:var(--nha-warning-soft)}:host .badge.danger{color:var(--nha-danger);background:var(--nha-danger-soft)}:host .visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}.share{display:flex;flex:1;flex-direction:column;min-height:0}.header{display:flex;align-items:center;gap:8px;padding:12px 12px 8px 16px}h3{flex:1;margin:0;font-size:15px;font-weight:700}.content{display:flex;flex:1;flex-direction:column;gap:18px;min-height:0;overflow-y:auto;padding:0 16px 16px}.description,.state{margin:0;color:var(--nha-text-muted);font-size:13px}.state{padding:12px 16px}.block{display:flex;flex-direction:column;gap:8px}h4{display:flex;align-items:baseline;gap:8px;margin:0;font-size:12px;font-weight:700}.count{color:var(--nha-text-muted);font-size:11px;font-weight:400}.link-row{display:flex;align-items:center;gap:4px}.link-row input{font-family:var(--nha-mono);font-size:12px}.actions{justify-content:flex-start}.people{display:flex;flex-direction:column;gap:4px;margin:0;padding:0;list-style:none}.people li{display:flex;align-items:center;gap:8px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px 6px 6px 10px}.person{display:flex;flex:1;flex-direction:column;min-width:0}.person strong{overflow:hidden;font-size:13px;text-overflow:ellipsis;white-space:nowrap}.person small{color:var(--nha-text-muted);font-size:11px}.you{margin-left:4px;font-weight:400}.small{min-height:30px;padding:4px 10px;font-size:12px}\n"] }]
|
|
4443
|
+
}], ctorParameters: () => [], propDecorators: { closed: [{ type: i0.Output, args: ["closed"] }] } });
|
|
4444
|
+
|
|
3075
4445
|
const allowedTags = [
|
|
3076
4446
|
'p', 'br', 'strong', 'em', 'del', 'code', 'pre', 'blockquote', 'ul', 'ol', 'li', 'a',
|
|
3077
4447
|
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'table', 'thead', 'tbody', 'tr', 'th', 'td'
|
|
@@ -3144,10 +4514,10 @@ class NhAssistantMarkdownRenderer {
|
|
|
3144
4514
|
this.purifier = purifier;
|
|
3145
4515
|
return purifier;
|
|
3146
4516
|
}
|
|
3147
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
3148
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.
|
|
4517
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantMarkdownRenderer, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
4518
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantMarkdownRenderer, providedIn: 'root' }); }
|
|
3149
4519
|
}
|
|
3150
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
4520
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantMarkdownRenderer, decorators: [{
|
|
3151
4521
|
type: Injectable,
|
|
3152
4522
|
args: [{ providedIn: 'root' }]
|
|
3153
4523
|
}] });
|
|
@@ -3160,10 +4530,10 @@ class NhAssistantMarkdownPipe {
|
|
|
3160
4530
|
transform(text) {
|
|
3161
4531
|
return this.renderer.render(text);
|
|
3162
4532
|
}
|
|
3163
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
3164
|
-
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "20.3.
|
|
4533
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantMarkdownPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
|
|
4534
|
+
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantMarkdownPipe, isStandalone: true, name: "nhAssistantMarkdown" }); }
|
|
3165
4535
|
}
|
|
3166
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
4536
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantMarkdownPipe, decorators: [{
|
|
3167
4537
|
type: Pipe,
|
|
3168
4538
|
args: [{ name: 'nhAssistantMarkdown', standalone: true }]
|
|
3169
4539
|
}] });
|
|
@@ -3200,10 +4570,10 @@ class NhAssistantPartGroupsPipe {
|
|
|
3200
4570
|
transform(parts) {
|
|
3201
4571
|
return groupNhAssistantParts(parts);
|
|
3202
4572
|
}
|
|
3203
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
3204
|
-
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "20.3.
|
|
4573
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantPartGroupsPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
|
|
4574
|
+
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantPartGroupsPipe, isStandalone: true, name: "nhAssistantPartGroups" }); }
|
|
3205
4575
|
}
|
|
3206
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
4576
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantPartGroupsPipe, decorators: [{
|
|
3207
4577
|
type: Pipe,
|
|
3208
4578
|
args: [{ name: 'nhAssistantPartGroups', standalone: true }]
|
|
3209
4579
|
}] });
|
|
@@ -3229,6 +4599,13 @@ class NhAssistantApprovalCardComponent {
|
|
|
3229
4599
|
this.approval = input.required(...(ngDevMode ? [{ debugName: "approval" }] : []));
|
|
3230
4600
|
/** True while the host submits a decision; disables both buttons. */
|
|
3231
4601
|
this.deciding = input(false, ...(ngDevMode ? [{ debugName: "deciding" }] : []));
|
|
4602
|
+
/**
|
|
4603
|
+
* False when the approval belongs to another participant's turn: only the person whose
|
|
4604
|
+
* permissions the action would use may decide it.
|
|
4605
|
+
*/
|
|
4606
|
+
this.canDecide = input(true, ...(ngDevMode ? [{ debugName: "canDecide" }] : []));
|
|
4607
|
+
/** Name of the participant who decides, shown when the current user cannot. */
|
|
4608
|
+
this.decisionOwner = input(null, ...(ngDevMode ? [{ debugName: "decisionOwner" }] : []));
|
|
3232
4609
|
this.decide = output();
|
|
3233
4610
|
this.now = signal(Date.now(), ...(ngDevMode ? [{ debugName: "now" }] : []));
|
|
3234
4611
|
this.submitted = signal(false, ...(ngDevMode ? [{ debugName: "submitted" }] : []));
|
|
@@ -3237,7 +4614,7 @@ class NhAssistantApprovalCardComponent {
|
|
|
3237
4614
|
(this.approval().status === 'pending' && this.remainingMs() <= 0), ...(ngDevMode ? [{ debugName: "expired" }] : []));
|
|
3238
4615
|
this.effectiveStatus = computed(() => this.expired() ? 'expired' : this.approval().status, ...(ngDevMode ? [{ debugName: "effectiveStatus" }] : []));
|
|
3239
4616
|
this.actionable = computed(() => this.effectiveStatus() === 'pending', ...(ngDevMode ? [{ debugName: "actionable" }] : []));
|
|
3240
|
-
this.disabled = computed(() => !this.actionable() || this.deciding() || this.submitted(), ...(ngDevMode ? [{ debugName: "disabled" }] : []));
|
|
4617
|
+
this.disabled = computed(() => !this.actionable() || !this.canDecide() || this.deciding() || this.submitted(), ...(ngDevMode ? [{ debugName: "disabled" }] : []));
|
|
3241
4618
|
this.countdown = computed(() => formatNhAssistantCountdown(this.remainingMs()), ...(ngDevMode ? [{ debugName: "countdown" }] : []));
|
|
3242
4619
|
this.presentedSummary = computed(() => this.approval().presentation?.summary ?? null, ...(ngDevMode ? [{ debugName: "presentedSummary" }] : []));
|
|
3243
4620
|
// Tick only while the approval is pending and not yet expired.
|
|
@@ -3268,15 +4645,15 @@ class NhAssistantApprovalCardComponent {
|
|
|
3268
4645
|
this.submitted.set(true);
|
|
3269
4646
|
this.decide.emit(decision);
|
|
3270
4647
|
}
|
|
3271
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
3272
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.
|
|
4648
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantApprovalCardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
4649
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.33", type: NhAssistantApprovalCardComponent, isStandalone: true, selector: "nh-assistant-approval-card", inputs: { approval: { classPropertyName: "approval", publicName: "approval", isSignal: true, isRequired: true, transformFunction: null }, deciding: { classPropertyName: "deciding", publicName: "deciding", isSignal: true, isRequired: false, transformFunction: null }, canDecide: { classPropertyName: "canDecide", publicName: "canDecide", isSignal: true, isRequired: false, transformFunction: null }, decisionOwner: { classPropertyName: "decisionOwner", publicName: "decisionOwner", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { decide: "decide" }, host: { properties: { "attr.data-status": "effectiveStatus()" } }, ngImport: i0, template: "<section class=\"card\" [attr.aria-label]=\"'nh-assistant.approval.title' | translate\">\n <header class=\"header\">\n <nh-assistant-icon [name]=\"actionable() ? 'warning' : effectiveStatus() === 'approved' ? 'check' : 'x'\" />\n <h3>{{ 'nh-assistant.approval.title' | translate }}</h3>\n <span class=\"status\">{{ 'nh-assistant.approval.status.' + effectiveStatus() | translate }}</span>\n </header>\n\n @if (approval().presentation; as presentation) {\n <p class=\"tool-name\">{{ presentation.toolDisplayName }}</p>\n <p class=\"summary\">{{ presentation.summary }}</p>\n @if (presentation.fields.length > 0) {\n <dl class=\"presentation-fields\">\n @for (field of presentation.fields; track $index) {\n <div>\n <dt>{{ field.label }}</dt>\n <dd>{{ field.value }}</dd>\n </div>\n }\n </dl>\n }\n @if (presentation.notice) {\n <p class=\"notice\">{{ presentation.notice }}</p>\n }\n } @else {\n <p class=\"summary\">{{ 'nh-assistant.approval.fallback-summary' | translate }}</p>\n }\n\n <details class=\"technical-details\">\n <summary>{{ 'nh-assistant.approval.technical-details' | translate }}</summary>\n <p class=\"label\">{{ 'nh-assistant.approval.tool-id' | translate }}</p>\n <code>{{ approval().toolId }}</code>\n @if (approval().targets.length > 0) {\n <p class=\"label\">{{ 'nh-assistant.approval.targets' | translate }}</p>\n <ul class=\"targets\">\n @for (target of approval().targets; track $index) {\n <li>{{ target }}</li>\n }\n </ul>\n }\n @if (approval().argumentsPreview) {\n <p class=\"label\">{{ 'nh-assistant.approval.arguments' | translate }}</p>\n <pre>{{ approval().argumentsPreview }}</pre>\n }\n </details>\n\n @if (actionable() && !canDecide()) {\n <p class=\"waiting-for-other\" role=\"status\">\n <nh-assistant-icon name=\"users\" />\n {{ decisionOwner()\n ? ('nh-assistant.approval.waiting-for' | translate: { name: decisionOwner() })\n : ('nh-assistant.approval.waiting-for-participant' | translate) }}\n </p>\n } @else if (actionable()) {\n <p class=\"expiry\" aria-live=\"off\">\n <nh-assistant-icon name=\"clock\" />\n {{ 'nh-assistant.approval.expires-in' | translate: { time: countdown() } }}\n </p>\n <div class=\"actions\">\n <button\n class=\"reject\"\n type=\"button\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"'nh-assistant.approval.reject-label' | translate: { summary: presentedSummary() ?? ('nh-assistant.approval.fallback-summary' | translate) }\"\n (click)=\"choose('reject')\">\n {{ 'nh-assistant.approval.reject' | translate }}\n </button>\n <button\n class=\"approve\"\n type=\"button\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"'nh-assistant.approval.approve-label' | translate: { summary: presentedSummary() ?? ('nh-assistant.approval.fallback-summary' | translate) }\"\n (click)=\"choose('approve')\">\n {{ 'nh-assistant.approval.approve' | translate }}\n </button>\n </div>\n @if (deciding()) {\n <p class=\"deciding\" role=\"status\">{{ 'nh-assistant.approval.deciding' | translate }}</p>\n }\n } @else if (expired()) {\n <p class=\"expiry\">{{ 'nh-assistant.approval.expired' | translate }}</p>\n }\n</section>\n", styles: [":host{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){:host{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}:host{display:block;--nha-status-color: var(--nha-warning);--nha-status-background: var(--nha-warning-soft)}:host([data-status=approved]){--nha-status-color: var(--nha-success);--nha-status-background: var(--nha-success-soft)}:host([data-status=rejected]),:host([data-status=expired]){--nha-status-color: var(--nha-text-muted);--nha-status-background: var(--nha-surface-strong)}.card{border:1px solid var(--nha-status-color);border-radius:var(--nha-radius-control);padding:12px;color:var(--nha-text);background:var(--nha-background)}.header{display:flex;align-items:center;gap:8px;color:var(--nha-status-color)}h3{flex:1;margin:0;color:var(--nha-text);font-size:14px;font-weight:700}.status{border-radius:999px;padding:2px 8px;color:var(--nha-status-color);background:var(--nha-status-background);font-size:11px;font-weight:700}.summary{margin:8px 0 0;font-size:14px;overflow-wrap:anywhere}.tool-name{margin:10px 0 0;font-size:12px;font-weight:700}.presentation-fields{display:grid;gap:6px;margin:10px 0 0}.presentation-fields div{display:grid;grid-template-columns:minmax(90px,.4fr) minmax(0,1fr);gap:8px}.presentation-fields dt{color:var(--nha-text-muted);font-size:12px;font-weight:700}.presentation-fields dd{margin:0;overflow-wrap:anywhere;font-size:13px}.notice{margin:10px 0 0;color:var(--nha-text-muted);font-size:12px}.technical-details{margin-top:10px;color:var(--nha-text-muted);font-size:12px}.technical-details summary{cursor:pointer;font-weight:700}.technical-details code{overflow-wrap:anywhere}.label{margin:10px 0 4px;color:var(--nha-text-muted);font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.04em}.targets{margin:0;padding-left:18px;font-size:13px;overflow-wrap:anywhere}pre{max-height:160px;margin:0;overflow:auto;border-radius:8px;padding:8px;background:var(--nha-surface);font-family:var(--nha-mono);font-size:12px;white-space:pre-wrap;word-break:break-word}.expiry,.deciding,.waiting-for-other{display:flex;align-items:center;gap:6px;margin:10px 0 0;color:var(--nha-text-muted);font-size:12px;font-variant-numeric:tabular-nums}.actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px;margin-top:12px}button{min-height:36px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px 14px;font:inherit;font-size:13px;font-weight:700;cursor:pointer}button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}button:disabled{cursor:not-allowed;opacity:.55}.reject{color:var(--nha-text);background:transparent}.reject:hover:not(:disabled){background:var(--nha-surface-strong)}.approve{border-color:var(--nha-accent);color:var(--nha-accent-contrast);background:var(--nha-accent)}.approve:hover:not(:disabled){filter:brightness(1.12)}\n"], dependencies: [{ kind: "component", type: NhAssistantIconComponent, selector: "nh-assistant-icon", inputs: ["name"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
3273
4650
|
}
|
|
3274
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
4651
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantApprovalCardComponent, decorators: [{
|
|
3275
4652
|
type: Component,
|
|
3276
4653
|
args: [{ selector: 'nh-assistant-approval-card', standalone: true, imports: [TranslatePipe, NhAssistantIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, host: {
|
|
3277
4654
|
'[attr.data-status]': 'effectiveStatus()'
|
|
3278
|
-
}, template: "<section class=\"card\" [attr.aria-label]=\"'nh-assistant.approval.title' | translate\">\n <header class=\"header\">\n <nh-assistant-icon [name]=\"actionable() ? 'warning' : effectiveStatus() === 'approved' ? 'check' : 'x'\" />\n <h3>{{ 'nh-assistant.approval.title' | translate }}</h3>\n <span class=\"status\">{{ 'nh-assistant.approval.status.' + effectiveStatus() | translate }}</span>\n </header>\n\n @if (approval().presentation; as presentation) {\n <p class=\"tool-name\">{{ presentation.toolDisplayName }}</p>\n <p class=\"summary\">{{ presentation.summary }}</p>\n @if (presentation.fields.length > 0) {\n <dl class=\"presentation-fields\">\n @for (field of presentation.fields; track $index) {\n <div>\n <dt>{{ field.label }}</dt>\n <dd>{{ field.value }}</dd>\n </div>\n }\n </dl>\n }\n @if (presentation.notice) {\n <p class=\"notice\">{{ presentation.notice }}</p>\n }\n } @else {\n <p class=\"summary\">{{ 'nh-assistant.approval.fallback-summary' | translate }}</p>\n }\n\n <details class=\"technical-details\">\n <summary>{{ 'nh-assistant.approval.technical-details' | translate }}</summary>\n <p class=\"label\">{{ 'nh-assistant.approval.tool-id' | translate }}</p>\n <code>{{ approval().toolId }}</code>\n @if (approval().targets.length > 0) {\n <p class=\"label\">{{ 'nh-assistant.approval.targets' | translate }}</p>\n <ul class=\"targets\">\n @for (target of approval().targets; track $index) {\n <li>{{ target }}</li>\n }\n </ul>\n }\n @if (approval().argumentsPreview) {\n <p class=\"label\">{{ 'nh-assistant.approval.arguments' | translate }}</p>\n <pre>{{ approval().argumentsPreview }}</pre>\n }\n </details>\n\n @if (actionable()) {\n <p class=\"expiry\" aria-live=\"off\">\n <nh-assistant-icon name=\"clock\" />\n {{ 'nh-assistant.approval.expires-in' | translate: { time: countdown() } }}\n </p>\n <div class=\"actions\">\n <button\n class=\"reject\"\n type=\"button\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"'nh-assistant.approval.reject-label' | translate: { summary: presentedSummary() ?? ('nh-assistant.approval.fallback-summary' | translate) }\"\n (click)=\"choose('reject')\">\n {{ 'nh-assistant.approval.reject' | translate }}\n </button>\n <button\n class=\"approve\"\n type=\"button\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"'nh-assistant.approval.approve-label' | translate: { summary: presentedSummary() ?? ('nh-assistant.approval.fallback-summary' | translate) }\"\n (click)=\"choose('approve')\">\n {{ 'nh-assistant.approval.approve' | translate }}\n </button>\n </div>\n @if (deciding()) {\n <p class=\"deciding\" role=\"status\">{{ 'nh-assistant.approval.deciding' | translate }}</p>\n }\n } @else if (expired()) {\n <p class=\"expiry\">{{ 'nh-assistant.approval.expired' | translate }}</p>\n }\n</section>\n", styles: [":host{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){:host{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}:host{display:block;--nha-status-color: var(--nha-warning);--nha-status-background: var(--nha-warning-soft)}:host([data-status=approved]){--nha-status-color: var(--nha-success);--nha-status-background: var(--nha-success-soft)}:host([data-status=rejected]),:host([data-status=expired]){--nha-status-color: var(--nha-text-muted);--nha-status-background: var(--nha-surface-strong)}.card{border:1px solid var(--nha-status-color);border-radius:var(--nha-radius-control);padding:12px;color:var(--nha-text);background:var(--nha-background)}.header{display:flex;align-items:center;gap:8px;color:var(--nha-status-color)}h3{flex:1;margin:0;color:var(--nha-text);font-size:14px;font-weight:700}.status{border-radius:999px;padding:2px 8px;color:var(--nha-status-color);background:var(--nha-status-background);font-size:11px;font-weight:700}.summary{margin:8px 0 0;font-size:14px;overflow-wrap:anywhere}.tool-name{margin:10px 0 0;font-size:12px;font-weight:700}.presentation-fields{display:grid;gap:6px;margin:10px 0 0}.presentation-fields div{display:grid;grid-template-columns:minmax(90px,.4fr) minmax(0,1fr);gap:8px}.presentation-fields dt{color:var(--nha-text-muted);font-size:12px;font-weight:700}.presentation-fields dd{margin:0;overflow-wrap:anywhere;font-size:13px}.notice{margin:10px 0 0;color:var(--nha-text-muted);font-size:12px}.technical-details{margin-top:10px;color:var(--nha-text-muted);font-size:12px}.technical-details summary{cursor:pointer;font-weight:700}.technical-details code{overflow-wrap:anywhere}.label{margin:10px 0 4px;color:var(--nha-text-muted);font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.04em}.targets{margin:0;padding-left:18px;font-size:13px;overflow-wrap:anywhere}pre{max-height:160px;margin:0;overflow:auto;border-radius:8px;padding:8px;background:var(--nha-surface);font-family:var(--nha-mono);font-size:12px;white-space:pre-wrap;word-break:break-word}.expiry,.deciding{display:flex;align-items:center;gap:6px;margin:10px 0 0;color:var(--nha-text-muted);font-size:12px;font-variant-numeric:tabular-nums}.actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px;margin-top:12px}button{min-height:36px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px 14px;font:inherit;font-size:13px;font-weight:700;cursor:pointer}button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}button:disabled{cursor:not-allowed;opacity:.55}.reject{color:var(--nha-text);background:transparent}.reject:hover:not(:disabled){background:var(--nha-surface-strong)}.approve{border-color:var(--nha-accent);color:var(--nha-accent-contrast);background:var(--nha-accent)}.approve:hover:not(:disabled){filter:brightness(1.12)}\n"] }]
|
|
3279
|
-
}], ctorParameters: () => [], propDecorators: { approval: [{ type: i0.Input, args: [{ isSignal: true, alias: "approval", required: true }] }], deciding: [{ type: i0.Input, args: [{ isSignal: true, alias: "deciding", required: false }] }], decide: [{ type: i0.Output, args: ["decide"] }] } });
|
|
4655
|
+
}, template: "<section class=\"card\" [attr.aria-label]=\"'nh-assistant.approval.title' | translate\">\n <header class=\"header\">\n <nh-assistant-icon [name]=\"actionable() ? 'warning' : effectiveStatus() === 'approved' ? 'check' : 'x'\" />\n <h3>{{ 'nh-assistant.approval.title' | translate }}</h3>\n <span class=\"status\">{{ 'nh-assistant.approval.status.' + effectiveStatus() | translate }}</span>\n </header>\n\n @if (approval().presentation; as presentation) {\n <p class=\"tool-name\">{{ presentation.toolDisplayName }}</p>\n <p class=\"summary\">{{ presentation.summary }}</p>\n @if (presentation.fields.length > 0) {\n <dl class=\"presentation-fields\">\n @for (field of presentation.fields; track $index) {\n <div>\n <dt>{{ field.label }}</dt>\n <dd>{{ field.value }}</dd>\n </div>\n }\n </dl>\n }\n @if (presentation.notice) {\n <p class=\"notice\">{{ presentation.notice }}</p>\n }\n } @else {\n <p class=\"summary\">{{ 'nh-assistant.approval.fallback-summary' | translate }}</p>\n }\n\n <details class=\"technical-details\">\n <summary>{{ 'nh-assistant.approval.technical-details' | translate }}</summary>\n <p class=\"label\">{{ 'nh-assistant.approval.tool-id' | translate }}</p>\n <code>{{ approval().toolId }}</code>\n @if (approval().targets.length > 0) {\n <p class=\"label\">{{ 'nh-assistant.approval.targets' | translate }}</p>\n <ul class=\"targets\">\n @for (target of approval().targets; track $index) {\n <li>{{ target }}</li>\n }\n </ul>\n }\n @if (approval().argumentsPreview) {\n <p class=\"label\">{{ 'nh-assistant.approval.arguments' | translate }}</p>\n <pre>{{ approval().argumentsPreview }}</pre>\n }\n </details>\n\n @if (actionable() && !canDecide()) {\n <p class=\"waiting-for-other\" role=\"status\">\n <nh-assistant-icon name=\"users\" />\n {{ decisionOwner()\n ? ('nh-assistant.approval.waiting-for' | translate: { name: decisionOwner() })\n : ('nh-assistant.approval.waiting-for-participant' | translate) }}\n </p>\n } @else if (actionable()) {\n <p class=\"expiry\" aria-live=\"off\">\n <nh-assistant-icon name=\"clock\" />\n {{ 'nh-assistant.approval.expires-in' | translate: { time: countdown() } }}\n </p>\n <div class=\"actions\">\n <button\n class=\"reject\"\n type=\"button\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"'nh-assistant.approval.reject-label' | translate: { summary: presentedSummary() ?? ('nh-assistant.approval.fallback-summary' | translate) }\"\n (click)=\"choose('reject')\">\n {{ 'nh-assistant.approval.reject' | translate }}\n </button>\n <button\n class=\"approve\"\n type=\"button\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"'nh-assistant.approval.approve-label' | translate: { summary: presentedSummary() ?? ('nh-assistant.approval.fallback-summary' | translate) }\"\n (click)=\"choose('approve')\">\n {{ 'nh-assistant.approval.approve' | translate }}\n </button>\n </div>\n @if (deciding()) {\n <p class=\"deciding\" role=\"status\">{{ 'nh-assistant.approval.deciding' | translate }}</p>\n }\n } @else if (expired()) {\n <p class=\"expiry\">{{ 'nh-assistant.approval.expired' | translate }}</p>\n }\n</section>\n", styles: [":host{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){:host{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}:host{display:block;--nha-status-color: var(--nha-warning);--nha-status-background: var(--nha-warning-soft)}:host([data-status=approved]){--nha-status-color: var(--nha-success);--nha-status-background: var(--nha-success-soft)}:host([data-status=rejected]),:host([data-status=expired]){--nha-status-color: var(--nha-text-muted);--nha-status-background: var(--nha-surface-strong)}.card{border:1px solid var(--nha-status-color);border-radius:var(--nha-radius-control);padding:12px;color:var(--nha-text);background:var(--nha-background)}.header{display:flex;align-items:center;gap:8px;color:var(--nha-status-color)}h3{flex:1;margin:0;color:var(--nha-text);font-size:14px;font-weight:700}.status{border-radius:999px;padding:2px 8px;color:var(--nha-status-color);background:var(--nha-status-background);font-size:11px;font-weight:700}.summary{margin:8px 0 0;font-size:14px;overflow-wrap:anywhere}.tool-name{margin:10px 0 0;font-size:12px;font-weight:700}.presentation-fields{display:grid;gap:6px;margin:10px 0 0}.presentation-fields div{display:grid;grid-template-columns:minmax(90px,.4fr) minmax(0,1fr);gap:8px}.presentation-fields dt{color:var(--nha-text-muted);font-size:12px;font-weight:700}.presentation-fields dd{margin:0;overflow-wrap:anywhere;font-size:13px}.notice{margin:10px 0 0;color:var(--nha-text-muted);font-size:12px}.technical-details{margin-top:10px;color:var(--nha-text-muted);font-size:12px}.technical-details summary{cursor:pointer;font-weight:700}.technical-details code{overflow-wrap:anywhere}.label{margin:10px 0 4px;color:var(--nha-text-muted);font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.04em}.targets{margin:0;padding-left:18px;font-size:13px;overflow-wrap:anywhere}pre{max-height:160px;margin:0;overflow:auto;border-radius:8px;padding:8px;background:var(--nha-surface);font-family:var(--nha-mono);font-size:12px;white-space:pre-wrap;word-break:break-word}.expiry,.deciding,.waiting-for-other{display:flex;align-items:center;gap:6px;margin:10px 0 0;color:var(--nha-text-muted);font-size:12px;font-variant-numeric:tabular-nums}.actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px;margin-top:12px}button{min-height:36px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px 14px;font:inherit;font-size:13px;font-weight:700;cursor:pointer}button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}button:disabled{cursor:not-allowed;opacity:.55}.reject{color:var(--nha-text);background:transparent}.reject:hover:not(:disabled){background:var(--nha-surface-strong)}.approve{border-color:var(--nha-accent);color:var(--nha-accent-contrast);background:var(--nha-accent)}.approve:hover:not(:disabled){filter:brightness(1.12)}\n"] }]
|
|
4656
|
+
}], ctorParameters: () => [], propDecorators: { approval: [{ type: i0.Input, args: [{ isSignal: true, alias: "approval", required: true }] }], deciding: [{ type: i0.Input, args: [{ isSignal: true, alias: "deciding", required: false }] }], canDecide: [{ type: i0.Input, args: [{ isSignal: true, alias: "canDecide", required: false }] }], decisionOwner: [{ type: i0.Input, args: [{ isSignal: true, alias: "decisionOwner", required: false }] }], decide: [{ type: i0.Output, args: ["decide"] }] } });
|
|
3280
4657
|
|
|
3281
4658
|
let nextId$2 = 0;
|
|
3282
4659
|
const statusIcons = {
|
|
@@ -3298,10 +4675,10 @@ class NhAssistantToolCallCardComponent {
|
|
|
3298
4675
|
toggle() {
|
|
3299
4676
|
this.expanded.update(expanded => !expanded);
|
|
3300
4677
|
}
|
|
3301
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
3302
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.
|
|
4678
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantToolCallCardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
4679
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.33", type: NhAssistantToolCallCardComponent, isStandalone: true, selector: "nh-assistant-tool-call-card", inputs: { part: { classPropertyName: "part", publicName: "part", isSignal: true, isRequired: true, transformFunction: null } }, host: { properties: { "attr.data-status": "part().status" } }, ngImport: i0, template: "<article class=\"card\" [attr.aria-label]=\"'nh-assistant.tool-call.label' | translate: { name: part().displayName }\">\n <header class=\"header\">\n <span class=\"status-icon\" [class.spinning]=\"part().status === 'running'\">\n <nh-assistant-icon [name]=\"statusIcon()\" />\n </span>\n <span class=\"title\">{{ part().displayName }}</span>\n <span class=\"status\">{{ 'nh-assistant.tool-call.status.' + part().status | translate }}</span>\n @if (hasDetails()) {\n <button\n class=\"toggle\"\n type=\"button\"\n [attr.aria-expanded]=\"expanded()\"\n [attr.aria-controls]=\"detailsId\"\n [attr.aria-label]=\"(expanded() ? 'nh-assistant.tool-call.hide-details' : 'nh-assistant.tool-call.show-details') | translate\"\n [attr.title]=\"(expanded() ? 'nh-assistant.tool-call.hide-details' : 'nh-assistant.tool-call.show-details') | translate\"\n (click)=\"toggle()\">\n <nh-assistant-icon name=\"chevron-down\" [class.rotated]=\"expanded()\" />\n </button>\n }\n </header>\n\n @if (part().resultCode; as resultCode) {\n <p class=\"result-code\">\n {{ ['nh-assistant.result-codes.' + resultCode, 'nh-assistant.errors.' + resultCode] | nhAssistantTranslate: resultCode }}\n </p>\n }\n\n @if (hasDetails()) {\n <div class=\"details\" [id]=\"detailsId\" [hidden]=\"!expanded()\">\n <p class=\"label\">{{ 'nh-assistant.tool-call.tool-id' | translate }}</p>\n <code>{{ part().toolId }}</code>\n @if (part().argumentsPreview; as argumentsPreview) {\n <p class=\"label\">{{ 'nh-assistant.tool-call.arguments' | translate }}</p>\n <pre>{{ argumentsPreview }}</pre>\n }\n @if (part().resultPreview; as resultPreview) {\n <p class=\"label\">{{ 'nh-assistant.tool-call.result' | translate }}</p>\n <pre>{{ resultPreview }}</pre>\n }\n </div>\n }\n</article>\n", styles: [":host{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){:host{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}:host{display:block;font:inherit;--nha-status-color: var(--nha-text-muted);--nha-status-background: var(--nha-surface-strong)}:host([data-status=succeeded]){--nha-status-color: var(--nha-success);--nha-status-background: var(--nha-success-soft)}:host([data-status=failed]){--nha-status-color: var(--nha-danger);--nha-status-background: var(--nha-danger-soft)}:host([data-status=awaiting-approval]){--nha-status-color: var(--nha-warning);--nha-status-background: var(--nha-warning-soft)}:host([data-status=rejected]){--nha-status-color: var(--nha-text-muted)}.card{border:1px solid var(--nha-border);border-left:3px solid var(--nha-status-color);border-radius:var(--nha-radius-control);padding:8px 10px;color:var(--nha-text);background:var(--nha-background)}.header{display:flex;align-items:center;gap:8px;min-width:0}.status-icon{display:inline-flex;color:var(--nha-status-color)}.title{flex:1;min-width:0;overflow:hidden;font-size:13px;font-weight:650;text-overflow:ellipsis;white-space:nowrap}.status{flex:none;border-radius:999px;padding:2px 8px;color:var(--nha-status-color);background:var(--nha-status-background);font-size:11px;font-weight:700}.toggle{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:1px solid transparent;border-radius:var(--nha-radius-control);color:var(--nha-text-muted);background:transparent;cursor:pointer;width:28px;height:28px}.toggle:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.toggle:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}.toggle:disabled{cursor:not-allowed;opacity:.5}.rotated{transform:rotate(180deg)}.result-code{margin:6px 0 0;color:var(--nha-text-muted);font-size:12px}.details{margin-top:8px}.label{margin:8px 0 4px;color:var(--nha-text-muted);font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.04em}pre{max-height:180px;margin:0;overflow:auto;border-radius:8px;padding:8px;background:var(--nha-surface);font-family:var(--nha-mono);font-size:12px;white-space:pre-wrap;word-break:break-word}@media(prefers-reduced-motion:no-preference){.spinning{animation:nha-pulse 1.2s ease-in-out infinite}nh-assistant-icon{transition:transform .16s ease}}@keyframes nha-pulse{50%{opacity:.35}}\n"], dependencies: [{ kind: "component", type: NhAssistantIconComponent, selector: "nh-assistant-icon", inputs: ["name"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }, { kind: "pipe", type: NhAssistantTranslatePipe, name: "nhAssistantTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
3303
4680
|
}
|
|
3304
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
4681
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantToolCallCardComponent, decorators: [{
|
|
3305
4682
|
type: Component,
|
|
3306
4683
|
args: [{ selector: 'nh-assistant-tool-call-card', standalone: true, imports: [TranslatePipe, NhAssistantTranslatePipe, NhAssistantIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, host: {
|
|
3307
4684
|
'[attr.data-status]': 'part().status'
|
|
@@ -3347,10 +4724,10 @@ class NhAssistantToolCallGroupComponent {
|
|
|
3347
4724
|
toggle() {
|
|
3348
4725
|
this.expanded.update(expanded => !expanded);
|
|
3349
4726
|
}
|
|
3350
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
3351
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.
|
|
4727
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantToolCallGroupComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
4728
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.33", type: NhAssistantToolCallGroupComponent, isStandalone: true, selector: "nh-assistant-tool-call-group", inputs: { calls: { classPropertyName: "calls", publicName: "calls", isSignal: true, isRequired: true, transformFunction: null } }, host: { properties: { "attr.data-status": "state()" } }, ngImport: i0, template: "<section class=\"group\">\n <button\n class=\"summary\"\n type=\"button\"\n [attr.aria-expanded]=\"expanded()\"\n [attr.aria-controls]=\"callsId\"\n [attr.title]=\"(expanded() ? 'nh-assistant.tool-group.hide-calls' : 'nh-assistant.tool-group.show-calls') | translate\"\n (click)=\"toggle()\">\n <span class=\"status-icon\" [class.spinning]=\"state() === 'running'\">\n <nh-assistant-icon [name]=\"stateIcon()\" />\n </span>\n <span class=\"title\">\n @switch (state()) {\n @case ('running') {\n {{ 'nh-assistant.tool-group.running' | translate: { name: current()?.displayName } }}\n }\n @case ('awaiting-approval') {\n {{ 'nh-assistant.tool-group.awaiting-approval' | translate: { name: current()?.displayName } }}\n }\n @default {\n {{ (total() === 1 ? 'nh-assistant.tool-group.used-one' : 'nh-assistant.tool-group.used-many') | translate: { count: total() } }}\n }\n }\n </span>\n @if (state() === 'running' || state() === 'awaiting-approval') {\n <span class=\"count\">{{ 'nh-assistant.tool-group.progress' | translate: { finished: finished(), total: total() } }}</span>\n } @else if (failed() > 0) {\n <span class=\"count failed\">{{ 'nh-assistant.tool-group.failed' | translate: { count: failed() } }}</span>\n }\n <nh-assistant-icon class=\"chevron\" name=\"chevron-down\" [class.rotated]=\"expanded()\" />\n </button>\n\n <div class=\"calls\" [id]=\"callsId\" [hidden]=\"!expanded()\">\n @if (expanded()) {\n @for (call of calls(); track call.invocationId) {\n <nh-assistant-tool-call-card [part]=\"call\" />\n }\n }\n </div>\n</section>\n", styles: [":host{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){:host{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}:host{display:block;font:inherit;--nha-status-color: var(--nha-text-muted)}:host([data-status=succeeded]){--nha-status-color: var(--nha-success)}:host([data-status=failed]){--nha-status-color: var(--nha-danger)}:host([data-status=awaiting-approval]){--nha-status-color: var(--nha-warning)}.group{border:1px solid var(--nha-border);border-left:3px solid var(--nha-status-color);border-radius:var(--nha-radius-control);color:var(--nha-text);background:var(--nha-background)}.summary{display:flex;align-items:center;gap:8px;width:100%;min-width:0;min-height:40px;border:0;border-radius:var(--nha-radius-control);padding:8px 10px;color:inherit;background:transparent;font:inherit;text-align:left;cursor:pointer}.summary:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.summary:hover{background:var(--nha-surface)}.status-icon{display:inline-flex;flex:none;color:var(--nha-status-color)}.title{flex:1;min-width:0;overflow:hidden;font-size:13px;font-weight:650;text-overflow:ellipsis;white-space:nowrap}.count{flex:none;color:var(--nha-text-muted);font-size:12px;font-variant-numeric:tabular-nums}.count.failed{border-radius:999px;padding:2px 8px;color:var(--nha-danger);background:var(--nha-danger-soft);font-size:11px;font-weight:700}.chevron{flex:none;color:var(--nha-text-muted)}.rotated{transform:rotate(180deg)}.calls{display:flex;flex-direction:column;gap:6px;border-top:1px solid var(--nha-border);padding:8px}@media(prefers-reduced-motion:no-preference){.spinning{animation:nha-pulse 1.2s ease-in-out infinite}.chevron{transition:transform .16s ease}}@keyframes nha-pulse{50%{opacity:.35}}\n"], dependencies: [{ kind: "component", type: NhAssistantIconComponent, selector: "nh-assistant-icon", inputs: ["name"] }, { kind: "component", type: NhAssistantToolCallCardComponent, selector: "nh-assistant-tool-call-card", inputs: ["part"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
3352
4729
|
}
|
|
3353
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
4730
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantToolCallGroupComponent, decorators: [{
|
|
3354
4731
|
type: Component,
|
|
3355
4732
|
args: [{ selector: 'nh-assistant-tool-call-group', standalone: true, imports: [TranslatePipe, NhAssistantIconComponent, NhAssistantToolCallCardComponent], changeDetection: ChangeDetectionStrategy.OnPush, host: {
|
|
3356
4733
|
'[attr.data-status]': 'state()'
|
|
@@ -3385,7 +4762,16 @@ class NhAssistantThreadComponent {
|
|
|
3385
4762
|
this.streaming = input(false, ...(ngDevMode ? [{ debugName: "streaming" }] : []));
|
|
3386
4763
|
this.deciding = input(false, ...(ngDevMode ? [{ debugName: "deciding" }] : []));
|
|
3387
4764
|
this.markdown = input(true, ...(ngDevMode ? [{ debugName: "markdown" }] : []));
|
|
4765
|
+
/** The owner and participants of a shared conversation; empty while it is not shared. */
|
|
4766
|
+
this.members = input([], ...(ngDevMode ? [{ debugName: "members" }] : []));
|
|
4767
|
+
/** The current user, to tell their own messages from those of others. */
|
|
4768
|
+
this.currentActorId = input(null, ...(ngDevMode ? [{ debugName: "currentActorId" }] : []));
|
|
4769
|
+
/** False when the pending approval belongs to another participant's turn. */
|
|
4770
|
+
this.canDecide = input(true, ...(ngDevMode ? [{ debugName: "canDecide" }] : []));
|
|
4771
|
+
/** Name of the participant who decides the pending approval, when it is someone else. */
|
|
4772
|
+
this.decisionOwner = input(null, ...(ngDevMode ? [{ debugName: "decisionOwner" }] : []));
|
|
3388
4773
|
this.approvalDecision = output();
|
|
4774
|
+
this.names = computed(() => new Map(this.members().map(member => [member.actorId, member.displayName])), ...(ngDevMode ? [{ debugName: "names" }] : []));
|
|
3389
4775
|
this.virtualThreshold = NH_ASSISTANT_VIRTUAL_SCROLL_THRESHOLD;
|
|
3390
4776
|
this.virtual = computed(() => this.messages().length > NH_ASSISTANT_VIRTUAL_SCROLL_THRESHOLD, ...(ngDevMode ? [{ debugName: "virtual" }] : []));
|
|
3391
4777
|
/**
|
|
@@ -3443,6 +4829,17 @@ class NhAssistantThreadComponent {
|
|
|
3443
4829
|
trackMessage(_index, message) {
|
|
3444
4830
|
return message.id;
|
|
3445
4831
|
}
|
|
4832
|
+
/** True for a user message written by another person in a shared conversation. */
|
|
4833
|
+
isOthers(message) {
|
|
4834
|
+
return message.role === 'user'
|
|
4835
|
+
&& !!message.authorActorId
|
|
4836
|
+
&& !!this.currentActorId()
|
|
4837
|
+
&& message.authorActorId !== this.currentActorId();
|
|
4838
|
+
}
|
|
4839
|
+
/** Display name of another person's message, or `null` when the application provides none. */
|
|
4840
|
+
authorName(message) {
|
|
4841
|
+
return message.authorActorId ? this.names().get(message.authorActorId) ?? null : null;
|
|
4842
|
+
}
|
|
3446
4843
|
scrollElement() {
|
|
3447
4844
|
return this.viewport()?.elementRef.nativeElement ?? this.scroller()?.nativeElement ?? null;
|
|
3448
4845
|
}
|
|
@@ -3458,10 +4855,10 @@ class NhAssistantThreadComponent {
|
|
|
3458
4855
|
element.scrollTop = element.scrollHeight;
|
|
3459
4856
|
}
|
|
3460
4857
|
}
|
|
3461
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
3462
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.28", type: NhAssistantThreadComponent, isStandalone: true, selector: "nh-assistant-thread", inputs: { messages: { classPropertyName: "messages", publicName: "messages", isSignal: true, isRequired: true, transformFunction: null }, streaming: { classPropertyName: "streaming", publicName: "streaming", isSignal: true, isRequired: false, transformFunction: null }, deciding: { classPropertyName: "deciding", publicName: "deciding", isSignal: true, isRequired: false, transformFunction: null }, markdown: { classPropertyName: "markdown", publicName: "markdown", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { approvalDecision: "approvalDecision" }, viewQueries: [{ propertyName: "scroller", first: true, predicate: ["scroller"], descendants: true, isSignal: true }, { propertyName: "viewport", first: true, predicate: CdkVirtualScrollViewport, descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"log\" role=\"log\" aria-live=\"polite\" [attr.aria-label]=\"'nh-assistant.thread.label' | translate\" [attr.aria-busy]=\"streaming()\">\n @if (virtual()) {\n <cdk-virtual-scroll-viewport class=\"scroller\" itemSize=\"120\" minBufferPx=\"1200\" maxBufferPx=\"2400\">\n <div class=\"row\" *cdkVirtualFor=\"let message of messages(); trackBy: trackMessage\">\n <ng-container [ngTemplateOutlet]=\"messageTemplate\" [ngTemplateOutletContext]=\"{ $implicit: message }\" />\n </div>\n @if (working()) {\n <ng-container [ngTemplateOutlet]=\"workingTemplate\" />\n }\n </cdk-virtual-scroll-viewport>\n } @else {\n <div class=\"scroller\" #scroller tabindex=\"0\">\n @for (message of messages(); track message.id) {\n <div class=\"row\">\n <ng-container [ngTemplateOutlet]=\"messageTemplate\" [ngTemplateOutletContext]=\"{ $implicit: message }\" />\n </div>\n }\n @if (working()) {\n <ng-container [ngTemplateOutlet]=\"workingTemplate\" />\n }\n </div>\n }\n</div>\n\n<ng-template #messageTemplate let-message>\n <article class=\"message\" [attr.data-role]=\"message.role\">\n <p class=\"author\">{{ 'nh-assistant.thread.' + (message.role === 'user' ? 'you' : message.role) | translate }}</p>\n @for (group of message.parts | nhAssistantPartGroups; track group.key) {\n @switch (group.kind) {\n @case ('text') {\n @if (message.role === 'assistant' && markdown()) {\n <div class=\"bubble markdown\" [innerHTML]=\"group.part.text | nhAssistantMarkdown\"></div>\n } @else {\n <div class=\"bubble plain\">{{ group.part.text }}</div>\n }\n }\n @case ('tools') {\n <nh-assistant-tool-call-group class=\"part\" [calls]=\"group.calls\" />\n }\n @case ('approval') {\n <nh-assistant-approval-card class=\"part\" [approval]=\"group.part\" [deciding]=\"deciding()\" (decide)=\"approvalDecision.emit($event)\" />\n }\n }\n }\n </article>\n</ng-template>\n\n<ng-template #workingTemplate>\n <p class=\"working\" role=\"status\">\n <span class=\"dots\" aria-hidden=\"true\"><span></span><span></span><span></span></span>\n {{ 'nh-assistant.thread.working' | translate }}\n @if (elapsedSeconds() > 0) {\n <span class=\"elapsed\">{{ 'nh-assistant.thread.elapsed' | translate: { seconds: elapsedSeconds() } }}</span>\n }\n </p>\n</ng-template>\n", styles: [":host{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){:host{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}:host{display:flex;flex-direction:column;min-height:0;color:var(--nha-text)}.log{display:flex;flex:1;flex-direction:column;min-height:0}.scroller{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain;padding:16px;outline:none}.scroller:focus-visible{box-shadow:inset 0 0 0 2px var(--nha-focus)}cdk-virtual-scroll-viewport.scroller{height:100%}.row+.row{margin-top:14px}.message{display:flex;flex-direction:column;align-items:flex-start;gap:6px;min-width:0}.message[data-role=user]{align-items:flex-end}.author{margin:0;color:var(--nha-text-muted);font-size:11px;font-weight:700;letter-spacing:.04em;text-transform:uppercase}.bubble{max-width:100%;border-radius:var(--nha-radius-control);padding:8px 12px;font-size:14px;line-height:1.5;overflow-wrap:anywhere}.plain{white-space:pre-wrap}.message[data-role=user] .bubble{max-width:88%;color:var(--nha-user-text);background:var(--nha-user-bubble)}.message[data-role=assistant] .bubble,.message[data-role=tool] .bubble{border:1px solid var(--nha-border);background:var(--nha-surface)}.part{width:100%}:host ::ng-deep .markdown :first-child{margin-top:0}:host ::ng-deep .markdown :last-child{margin-bottom:0}:host ::ng-deep .markdown p,:host ::ng-deep .markdown ul,:host ::ng-deep .markdown ol,:host ::ng-deep .markdown pre,:host ::ng-deep .markdown blockquote,:host ::ng-deep .markdown table{margin:0 0 8px}:host ::ng-deep .markdown ul,:host ::ng-deep .markdown ol{padding-left:20px}:host ::ng-deep .markdown h1,:host ::ng-deep .markdown h2,:host ::ng-deep .markdown h3,:host ::ng-deep .markdown h4,:host ::ng-deep .markdown h5,:host ::ng-deep .markdown h6{margin:10px 0 6px;font-size:15px;line-height:1.3}:host ::ng-deep .markdown a{color:var(--nha-highlight);text-decoration:underline}:host ::ng-deep .markdown a:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host ::ng-deep .markdown code{border-radius:6px;padding:1px 4px;background:var(--nha-surface-strong);font-family:var(--nha-mono);font-size:12.5px}:host ::ng-deep .markdown pre{overflow-x:auto;border-radius:8px;padding:8px;background:var(--nha-surface-strong)}:host ::ng-deep .markdown pre code{padding:0;background:none}:host ::ng-deep .markdown blockquote{border-left:3px solid var(--nha-border);padding-left:10px;color:var(--nha-text-muted)}:host ::ng-deep .markdown table{display:block;max-width:100%;overflow-x:auto;border-collapse:collapse;font-size:13px}:host ::ng-deep .markdown th,:host ::ng-deep .markdown td{border:1px solid var(--nha-border);padding:4px 8px;text-align:left}.working{display:flex;align-items:center;gap:8px;margin:14px 0 0;color:var(--nha-text-muted);font-size:13px}.elapsed{font-variant-numeric:tabular-nums}.dots{display:inline-flex;gap:3px}.dots span{width:6px;height:6px;border-radius:50%;background:currentColor;opacity:.6}@media(prefers-reduced-motion:no-preference){.dots span{animation:nha-bounce 1s ease-in-out infinite}.dots span:nth-child(2){animation-delay:.15s}.dots span:nth-child(3){animation-delay:.3s}}@keyframes nha-bounce{0%,80%,to{transform:translateY(0);opacity:.35}40%{transform:translateY(-3px);opacity:1}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: ScrollingModule }, { kind: "directive", type: i1.CdkFixedSizeVirtualScroll, selector: "cdk-virtual-scroll-viewport[itemSize]", inputs: ["itemSize", "minBufferPx", "maxBufferPx"] }, { kind: "directive", type: i1.CdkVirtualForOf, selector: "[cdkVirtualFor][cdkVirtualForOf]", inputs: ["cdkVirtualForOf", "cdkVirtualForTrackBy", "cdkVirtualForTemplate", "cdkVirtualForTemplateCacheSize"] }, { kind: "component", type: i1.CdkVirtualScrollViewport, selector: "cdk-virtual-scroll-viewport", inputs: ["orientation", "appendOnly"], outputs: ["scrolledIndexChange"] }, { kind: "component", type: NhAssistantToolCallGroupComponent, selector: "nh-assistant-tool-call-group", inputs: ["calls"] }, { kind: "component", type: NhAssistantApprovalCardComponent, selector: "nh-assistant-approval-card", inputs: ["approval", "deciding"], outputs: ["decide"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }, { kind: "pipe", type: NhAssistantMarkdownPipe, name: "nhAssistantMarkdown" }, { kind: "pipe", type: NhAssistantPartGroupsPipe, name: "nhAssistantPartGroups" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
4858
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantThreadComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
4859
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.33", type: NhAssistantThreadComponent, isStandalone: true, selector: "nh-assistant-thread", inputs: { messages: { classPropertyName: "messages", publicName: "messages", isSignal: true, isRequired: true, transformFunction: null }, streaming: { classPropertyName: "streaming", publicName: "streaming", isSignal: true, isRequired: false, transformFunction: null }, deciding: { classPropertyName: "deciding", publicName: "deciding", isSignal: true, isRequired: false, transformFunction: null }, markdown: { classPropertyName: "markdown", publicName: "markdown", isSignal: true, isRequired: false, transformFunction: null }, members: { classPropertyName: "members", publicName: "members", isSignal: true, isRequired: false, transformFunction: null }, currentActorId: { classPropertyName: "currentActorId", publicName: "currentActorId", isSignal: true, isRequired: false, transformFunction: null }, canDecide: { classPropertyName: "canDecide", publicName: "canDecide", isSignal: true, isRequired: false, transformFunction: null }, decisionOwner: { classPropertyName: "decisionOwner", publicName: "decisionOwner", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { approvalDecision: "approvalDecision" }, viewQueries: [{ propertyName: "scroller", first: true, predicate: ["scroller"], descendants: true, isSignal: true }, { propertyName: "viewport", first: true, predicate: CdkVirtualScrollViewport, descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"log\" role=\"log\" aria-live=\"polite\" [attr.aria-label]=\"'nh-assistant.thread.label' | translate\" [attr.aria-busy]=\"streaming()\">\n @if (virtual()) {\n <cdk-virtual-scroll-viewport class=\"scroller\" itemSize=\"120\" minBufferPx=\"1200\" maxBufferPx=\"2400\">\n <div class=\"row\" *cdkVirtualFor=\"let message of messages(); trackBy: trackMessage\">\n <ng-container [ngTemplateOutlet]=\"messageTemplate\" [ngTemplateOutletContext]=\"{ $implicit: message }\" />\n </div>\n @if (working()) {\n <ng-container [ngTemplateOutlet]=\"workingTemplate\" />\n }\n </cdk-virtual-scroll-viewport>\n } @else {\n <div class=\"scroller\" #scroller tabindex=\"0\">\n @for (message of messages(); track message.id) {\n <div class=\"row\">\n <ng-container [ngTemplateOutlet]=\"messageTemplate\" [ngTemplateOutletContext]=\"{ $implicit: message }\" />\n </div>\n }\n @if (working()) {\n <ng-container [ngTemplateOutlet]=\"workingTemplate\" />\n }\n </div>\n }\n</div>\n\n<ng-template #messageTemplate let-message>\n <article class=\"message\" [attr.data-role]=\"message.role\" [attr.data-others]=\"isOthers(message) ? 'true' : null\">\n @if (isOthers(message)) {\n <p class=\"author\">{{ authorName(message) ?? ('nh-assistant.thread.participant' | translate) }}</p>\n } @else {\n <p class=\"author\">{{ 'nh-assistant.thread.' + (message.role === 'user' ? 'you' : message.role) | translate }}</p>\n }\n @for (group of message.parts | nhAssistantPartGroups; track group.key) {\n @switch (group.kind) {\n @case ('text') {\n @if (message.role === 'assistant' && markdown()) {\n <div class=\"bubble markdown\" [innerHTML]=\"group.part.text | nhAssistantMarkdown\"></div>\n } @else {\n <div class=\"bubble plain\">{{ group.part.text }}</div>\n }\n }\n @case ('tools') {\n <nh-assistant-tool-call-group class=\"part\" [calls]=\"group.calls\" />\n }\n @case ('approval') {\n <nh-assistant-approval-card\n class=\"part\"\n [approval]=\"group.part\"\n [deciding]=\"deciding()\"\n [canDecide]=\"canDecide()\"\n [decisionOwner]=\"decisionOwner()\"\n (decide)=\"approvalDecision.emit($event)\" />\n }\n }\n }\n </article>\n</ng-template>\n\n<ng-template #workingTemplate>\n <p class=\"working\" role=\"status\">\n <span class=\"dots\" aria-hidden=\"true\"><span></span><span></span><span></span></span>\n {{ 'nh-assistant.thread.working' | translate }}\n @if (elapsedSeconds() > 0) {\n <span class=\"elapsed\">{{ 'nh-assistant.thread.elapsed' | translate: { seconds: elapsedSeconds() } }}</span>\n }\n </p>\n</ng-template>\n", styles: [":host{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){:host{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}:host{display:flex;flex-direction:column;min-height:0;color:var(--nha-text)}.log{display:flex;flex:1;flex-direction:column;min-height:0}.scroller{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain;padding:16px;outline:none}.scroller:focus-visible{box-shadow:inset 0 0 0 2px var(--nha-focus)}cdk-virtual-scroll-viewport.scroller{height:100%}.row+.row{margin-top:14px}.message{display:flex;flex-direction:column;align-items:flex-start;gap:6px;min-width:0}.message[data-role=user]{align-items:flex-end}.author{margin:0;color:var(--nha-text-muted);font-size:11px;font-weight:700;letter-spacing:.04em;text-transform:uppercase}.bubble{max-width:100%;border-radius:var(--nha-radius-control);padding:8px 12px;font-size:14px;line-height:1.5;overflow-wrap:anywhere}.plain{white-space:pre-wrap}.message[data-role=user] .bubble{max-width:88%;color:var(--nha-user-text);background:var(--nha-user-bubble)}.message[data-others=true]{align-items:flex-start}.message[data-others=true] .bubble{color:var(--nha-text);background:var(--nha-surface-strong)}.message[data-role=assistant] .bubble,.message[data-role=tool] .bubble{border:1px solid var(--nha-border);background:var(--nha-surface)}.part{width:100%}:host ::ng-deep .markdown :first-child{margin-top:0}:host ::ng-deep .markdown :last-child{margin-bottom:0}:host ::ng-deep .markdown p,:host ::ng-deep .markdown ul,:host ::ng-deep .markdown ol,:host ::ng-deep .markdown pre,:host ::ng-deep .markdown blockquote,:host ::ng-deep .markdown table{margin:0 0 8px}:host ::ng-deep .markdown ul,:host ::ng-deep .markdown ol{padding-left:20px}:host ::ng-deep .markdown h1,:host ::ng-deep .markdown h2,:host ::ng-deep .markdown h3,:host ::ng-deep .markdown h4,:host ::ng-deep .markdown h5,:host ::ng-deep .markdown h6{margin:10px 0 6px;font-size:15px;line-height:1.3}:host ::ng-deep .markdown a{color:var(--nha-highlight);text-decoration:underline}:host ::ng-deep .markdown a:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host ::ng-deep .markdown code{border-radius:6px;padding:1px 4px;background:var(--nha-surface-strong);font-family:var(--nha-mono);font-size:12.5px}:host ::ng-deep .markdown pre{overflow-x:auto;border-radius:8px;padding:8px;background:var(--nha-surface-strong)}:host ::ng-deep .markdown pre code{padding:0;background:none}:host ::ng-deep .markdown blockquote{border-left:3px solid var(--nha-border);padding-left:10px;color:var(--nha-text-muted)}:host ::ng-deep .markdown table{display:block;max-width:100%;overflow-x:auto;border-collapse:collapse;font-size:13px}:host ::ng-deep .markdown th,:host ::ng-deep .markdown td{border:1px solid var(--nha-border);padding:4px 8px;text-align:left}.working{display:flex;align-items:center;gap:8px;margin:14px 0 0;color:var(--nha-text-muted);font-size:13px}.elapsed{font-variant-numeric:tabular-nums}.dots{display:inline-flex;gap:3px}.dots span{width:6px;height:6px;border-radius:50%;background:currentColor;opacity:.6}@media(prefers-reduced-motion:no-preference){.dots span{animation:nha-bounce 1s ease-in-out infinite}.dots span:nth-child(2){animation-delay:.15s}.dots span:nth-child(3){animation-delay:.3s}}@keyframes nha-bounce{0%,80%,to{transform:translateY(0);opacity:.35}40%{transform:translateY(-3px);opacity:1}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: ScrollingModule }, { kind: "directive", type: i1.CdkFixedSizeVirtualScroll, selector: "cdk-virtual-scroll-viewport[itemSize]", inputs: ["itemSize", "minBufferPx", "maxBufferPx"] }, { kind: "directive", type: i1.CdkVirtualForOf, selector: "[cdkVirtualFor][cdkVirtualForOf]", inputs: ["cdkVirtualForOf", "cdkVirtualForTrackBy", "cdkVirtualForTemplate", "cdkVirtualForTemplateCacheSize"] }, { kind: "component", type: i1.CdkVirtualScrollViewport, selector: "cdk-virtual-scroll-viewport", inputs: ["orientation", "appendOnly"], outputs: ["scrolledIndexChange"] }, { kind: "component", type: NhAssistantToolCallGroupComponent, selector: "nh-assistant-tool-call-group", inputs: ["calls"] }, { kind: "component", type: NhAssistantApprovalCardComponent, selector: "nh-assistant-approval-card", inputs: ["approval", "deciding", "canDecide", "decisionOwner"], outputs: ["decide"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }, { kind: "pipe", type: NhAssistantMarkdownPipe, name: "nhAssistantMarkdown" }, { kind: "pipe", type: NhAssistantPartGroupsPipe, name: "nhAssistantPartGroups" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
3463
4860
|
}
|
|
3464
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
4861
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantThreadComponent, decorators: [{
|
|
3465
4862
|
type: Component,
|
|
3466
4863
|
args: [{ selector: 'nh-assistant-thread', standalone: true, imports: [
|
|
3467
4864
|
NgTemplateOutlet,
|
|
@@ -3471,18 +4868,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImpo
|
|
|
3471
4868
|
NhAssistantPartGroupsPipe,
|
|
3472
4869
|
NhAssistantToolCallGroupComponent,
|
|
3473
4870
|
NhAssistantApprovalCardComponent
|
|
3474
|
-
], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"log\" role=\"log\" aria-live=\"polite\" [attr.aria-label]=\"'nh-assistant.thread.label' | translate\" [attr.aria-busy]=\"streaming()\">\n @if (virtual()) {\n <cdk-virtual-scroll-viewport class=\"scroller\" itemSize=\"120\" minBufferPx=\"1200\" maxBufferPx=\"2400\">\n <div class=\"row\" *cdkVirtualFor=\"let message of messages(); trackBy: trackMessage\">\n <ng-container [ngTemplateOutlet]=\"messageTemplate\" [ngTemplateOutletContext]=\"{ $implicit: message }\" />\n </div>\n @if (working()) {\n <ng-container [ngTemplateOutlet]=\"workingTemplate\" />\n }\n </cdk-virtual-scroll-viewport>\n } @else {\n <div class=\"scroller\" #scroller tabindex=\"0\">\n @for (message of messages(); track message.id) {\n <div class=\"row\">\n <ng-container [ngTemplateOutlet]=\"messageTemplate\" [ngTemplateOutletContext]=\"{ $implicit: message }\" />\n </div>\n }\n @if (working()) {\n <ng-container [ngTemplateOutlet]=\"workingTemplate\" />\n }\n </div>\n }\n</div>\n\n<ng-template #messageTemplate let-message>\n <article class=\"message\" [attr.data-role]=\"message.role\">\n <p class=\"author\">{{ 'nh-assistant.thread.' + (message.role === 'user' ? 'you' : message.role) | translate }}</p>\n @for (group of message.parts | nhAssistantPartGroups; track group.key) {\n @switch (group.kind) {\n @case ('text') {\n @if (message.role === 'assistant' && markdown()) {\n <div class=\"bubble markdown\" [innerHTML]=\"group.part.text | nhAssistantMarkdown\"></div>\n } @else {\n <div class=\"bubble plain\">{{ group.part.text }}</div>\n }\n }\n @case ('tools') {\n <nh-assistant-tool-call-group class=\"part\" [calls]=\"group.calls\" />\n }\n @case ('approval') {\n <nh-assistant-approval-card
|
|
3475
|
-
}], ctorParameters: () => [], propDecorators: { messages: [{ type: i0.Input, args: [{ isSignal: true, alias: "messages", required: true }] }], streaming: [{ type: i0.Input, args: [{ isSignal: true, alias: "streaming", required: false }] }], deciding: [{ type: i0.Input, args: [{ isSignal: true, alias: "deciding", required: false }] }], markdown: [{ type: i0.Input, args: [{ isSignal: true, alias: "markdown", required: false }] }], approvalDecision: [{ type: i0.Output, args: ["approvalDecision"] }], scroller: [{ type: i0.ViewChild, args: ['scroller', { isSignal: true }] }], viewport: [{ type: i0.ViewChild, args: [i0.forwardRef(() => CdkVirtualScrollViewport), { isSignal: true }] }] } });
|
|
4871
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"log\" role=\"log\" aria-live=\"polite\" [attr.aria-label]=\"'nh-assistant.thread.label' | translate\" [attr.aria-busy]=\"streaming()\">\n @if (virtual()) {\n <cdk-virtual-scroll-viewport class=\"scroller\" itemSize=\"120\" minBufferPx=\"1200\" maxBufferPx=\"2400\">\n <div class=\"row\" *cdkVirtualFor=\"let message of messages(); trackBy: trackMessage\">\n <ng-container [ngTemplateOutlet]=\"messageTemplate\" [ngTemplateOutletContext]=\"{ $implicit: message }\" />\n </div>\n @if (working()) {\n <ng-container [ngTemplateOutlet]=\"workingTemplate\" />\n }\n </cdk-virtual-scroll-viewport>\n } @else {\n <div class=\"scroller\" #scroller tabindex=\"0\">\n @for (message of messages(); track message.id) {\n <div class=\"row\">\n <ng-container [ngTemplateOutlet]=\"messageTemplate\" [ngTemplateOutletContext]=\"{ $implicit: message }\" />\n </div>\n }\n @if (working()) {\n <ng-container [ngTemplateOutlet]=\"workingTemplate\" />\n }\n </div>\n }\n</div>\n\n<ng-template #messageTemplate let-message>\n <article class=\"message\" [attr.data-role]=\"message.role\" [attr.data-others]=\"isOthers(message) ? 'true' : null\">\n @if (isOthers(message)) {\n <p class=\"author\">{{ authorName(message) ?? ('nh-assistant.thread.participant' | translate) }}</p>\n } @else {\n <p class=\"author\">{{ 'nh-assistant.thread.' + (message.role === 'user' ? 'you' : message.role) | translate }}</p>\n }\n @for (group of message.parts | nhAssistantPartGroups; track group.key) {\n @switch (group.kind) {\n @case ('text') {\n @if (message.role === 'assistant' && markdown()) {\n <div class=\"bubble markdown\" [innerHTML]=\"group.part.text | nhAssistantMarkdown\"></div>\n } @else {\n <div class=\"bubble plain\">{{ group.part.text }}</div>\n }\n }\n @case ('tools') {\n <nh-assistant-tool-call-group class=\"part\" [calls]=\"group.calls\" />\n }\n @case ('approval') {\n <nh-assistant-approval-card\n class=\"part\"\n [approval]=\"group.part\"\n [deciding]=\"deciding()\"\n [canDecide]=\"canDecide()\"\n [decisionOwner]=\"decisionOwner()\"\n (decide)=\"approvalDecision.emit($event)\" />\n }\n }\n }\n </article>\n</ng-template>\n\n<ng-template #workingTemplate>\n <p class=\"working\" role=\"status\">\n <span class=\"dots\" aria-hidden=\"true\"><span></span><span></span><span></span></span>\n {{ 'nh-assistant.thread.working' | translate }}\n @if (elapsedSeconds() > 0) {\n <span class=\"elapsed\">{{ 'nh-assistant.thread.elapsed' | translate: { seconds: elapsedSeconds() } }}</span>\n }\n </p>\n</ng-template>\n", styles: [":host{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){:host{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}:host{display:flex;flex-direction:column;min-height:0;color:var(--nha-text)}.log{display:flex;flex:1;flex-direction:column;min-height:0}.scroller{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain;padding:16px;outline:none}.scroller:focus-visible{box-shadow:inset 0 0 0 2px var(--nha-focus)}cdk-virtual-scroll-viewport.scroller{height:100%}.row+.row{margin-top:14px}.message{display:flex;flex-direction:column;align-items:flex-start;gap:6px;min-width:0}.message[data-role=user]{align-items:flex-end}.author{margin:0;color:var(--nha-text-muted);font-size:11px;font-weight:700;letter-spacing:.04em;text-transform:uppercase}.bubble{max-width:100%;border-radius:var(--nha-radius-control);padding:8px 12px;font-size:14px;line-height:1.5;overflow-wrap:anywhere}.plain{white-space:pre-wrap}.message[data-role=user] .bubble{max-width:88%;color:var(--nha-user-text);background:var(--nha-user-bubble)}.message[data-others=true]{align-items:flex-start}.message[data-others=true] .bubble{color:var(--nha-text);background:var(--nha-surface-strong)}.message[data-role=assistant] .bubble,.message[data-role=tool] .bubble{border:1px solid var(--nha-border);background:var(--nha-surface)}.part{width:100%}:host ::ng-deep .markdown :first-child{margin-top:0}:host ::ng-deep .markdown :last-child{margin-bottom:0}:host ::ng-deep .markdown p,:host ::ng-deep .markdown ul,:host ::ng-deep .markdown ol,:host ::ng-deep .markdown pre,:host ::ng-deep .markdown blockquote,:host ::ng-deep .markdown table{margin:0 0 8px}:host ::ng-deep .markdown ul,:host ::ng-deep .markdown ol{padding-left:20px}:host ::ng-deep .markdown h1,:host ::ng-deep .markdown h2,:host ::ng-deep .markdown h3,:host ::ng-deep .markdown h4,:host ::ng-deep .markdown h5,:host ::ng-deep .markdown h6{margin:10px 0 6px;font-size:15px;line-height:1.3}:host ::ng-deep .markdown a{color:var(--nha-highlight);text-decoration:underline}:host ::ng-deep .markdown a:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host ::ng-deep .markdown code{border-radius:6px;padding:1px 4px;background:var(--nha-surface-strong);font-family:var(--nha-mono);font-size:12.5px}:host ::ng-deep .markdown pre{overflow-x:auto;border-radius:8px;padding:8px;background:var(--nha-surface-strong)}:host ::ng-deep .markdown pre code{padding:0;background:none}:host ::ng-deep .markdown blockquote{border-left:3px solid var(--nha-border);padding-left:10px;color:var(--nha-text-muted)}:host ::ng-deep .markdown table{display:block;max-width:100%;overflow-x:auto;border-collapse:collapse;font-size:13px}:host ::ng-deep .markdown th,:host ::ng-deep .markdown td{border:1px solid var(--nha-border);padding:4px 8px;text-align:left}.working{display:flex;align-items:center;gap:8px;margin:14px 0 0;color:var(--nha-text-muted);font-size:13px}.elapsed{font-variant-numeric:tabular-nums}.dots{display:inline-flex;gap:3px}.dots span{width:6px;height:6px;border-radius:50%;background:currentColor;opacity:.6}@media(prefers-reduced-motion:no-preference){.dots span{animation:nha-bounce 1s ease-in-out infinite}.dots span:nth-child(2){animation-delay:.15s}.dots span:nth-child(3){animation-delay:.3s}}@keyframes nha-bounce{0%,80%,to{transform:translateY(0);opacity:.35}40%{transform:translateY(-3px);opacity:1}}\n"] }]
|
|
4872
|
+
}], ctorParameters: () => [], propDecorators: { messages: [{ type: i0.Input, args: [{ isSignal: true, alias: "messages", required: true }] }], streaming: [{ type: i0.Input, args: [{ isSignal: true, alias: "streaming", required: false }] }], deciding: [{ type: i0.Input, args: [{ isSignal: true, alias: "deciding", required: false }] }], markdown: [{ type: i0.Input, args: [{ isSignal: true, alias: "markdown", required: false }] }], members: [{ type: i0.Input, args: [{ isSignal: true, alias: "members", required: false }] }], currentActorId: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentActorId", required: false }] }], canDecide: [{ type: i0.Input, args: [{ isSignal: true, alias: "canDecide", required: false }] }], decisionOwner: [{ type: i0.Input, args: [{ isSignal: true, alias: "decisionOwner", required: false }] }], approvalDecision: [{ type: i0.Output, args: ["approvalDecision"] }], scroller: [{ type: i0.ViewChild, args: ['scroller', { isSignal: true }] }], viewport: [{ type: i0.ViewChild, args: [i0.forwardRef(() => CdkVirtualScrollViewport), { isSignal: true }] }] } });
|
|
3476
4873
|
|
|
3477
4874
|
let nextId = 0;
|
|
3478
4875
|
/**
|
|
3479
4876
|
* The assistant drawer. Place it once in the application layout; it renders nothing in
|
|
3480
|
-
* place and appears as an overlay when `NhAssistantPanelService.open()` is called.
|
|
4877
|
+
* place and appears as an overlay when `NhAssistantPanelService.open()` is called. It opens
|
|
4878
|
+
* by itself for an invitation link (`#nh-assistant-join=...`) and for a clicked notification.
|
|
3481
4879
|
*/
|
|
3482
4880
|
class NhAssistantPanelComponent {
|
|
3483
4881
|
constructor() {
|
|
3484
4882
|
this.store = inject(NhAssistantStore);
|
|
3485
4883
|
this.panel = inject(NhAssistantPanelService);
|
|
4884
|
+
this.push = inject(NhAssistantPushService);
|
|
3486
4885
|
this.config = inject(NH_ASSISTANT_CONFIG);
|
|
3487
4886
|
this.viewContainerRef = inject(ViewContainerRef);
|
|
3488
4887
|
this.router = inject(Router, { optional: true });
|
|
@@ -3491,6 +4890,7 @@ class NhAssistantPanelComponent {
|
|
|
3491
4890
|
this.markdown = this.config.markdown?.enabled ?? true;
|
|
3492
4891
|
this.showConversations = signal(false, ...(ngDevMode ? [{ debugName: "showConversations" }] : []));
|
|
3493
4892
|
this.showPreferences = signal(false, ...(ngDevMode ? [{ debugName: "showPreferences" }] : []));
|
|
4893
|
+
this.showShare = signal(false, ...(ngDevMode ? [{ debugName: "showShare" }] : []));
|
|
3494
4894
|
this.adminRoute = this.config.adminRoute ?? null;
|
|
3495
4895
|
this.conversation = this.store.activeConversation;
|
|
3496
4896
|
this.conversationId = computed(() => this.conversation()?.id ?? null, ...(ngDevMode ? [{ debugName: "conversationId" }] : []));
|
|
@@ -3507,6 +4907,15 @@ class NhAssistantPanelComponent {
|
|
|
3507
4907
|
}
|
|
3508
4908
|
return this.conversation()?.status === 'waiting-for-approval' ? 'waiting-for-approval' : null;
|
|
3509
4909
|
}, ...(ngDevMode ? [{ debugName: "composerStatus" }] : []));
|
|
4910
|
+
/** Name of another participant whose turn runs or waits, or `null`. */
|
|
4911
|
+
this.activeMemberName = computed(() => {
|
|
4912
|
+
const member = this.store.activeMember();
|
|
4913
|
+
return member ? member.displayName ?? '' : null;
|
|
4914
|
+
}, ...(ngDevMode ? [{ debugName: "activeMemberName" }] : []));
|
|
4915
|
+
/** The owner and the person who started the turn may stop it. */
|
|
4916
|
+
this.canStop = computed(() => this.store.isOwner() || this.store.activeMember() === null, ...(ngDevMode ? [{ debugName: "canStop" }] : []));
|
|
4917
|
+
this.shared = computed(() => this.store.members().length > 0, ...(ngDevMode ? [{ debugName: "shared" }] : []));
|
|
4918
|
+
this.canShare = computed(() => this.conversation() !== null && !!this.store.collaboration(), ...(ngDevMode ? [{ debugName: "canShare" }] : []));
|
|
3510
4919
|
this.errorKeys = computed(() => {
|
|
3511
4920
|
const error = this.store.error();
|
|
3512
4921
|
return error ? [error.messageKey, `nh-assistant.errors.${error.code}`, 'nh-assistant.errors.generic'] : [];
|
|
@@ -3521,9 +4930,18 @@ class NhAssistantPanelComponent {
|
|
|
3521
4930
|
untracked(() => {
|
|
3522
4931
|
this.showConversations.set(false);
|
|
3523
4932
|
this.showPreferences.set(false);
|
|
4933
|
+
this.showShare.set(false);
|
|
3524
4934
|
});
|
|
3525
4935
|
}
|
|
3526
4936
|
});
|
|
4937
|
+
// The open thread counts as seen only while the drawer shows it.
|
|
4938
|
+
effect(() => {
|
|
4939
|
+
const viewing = this.panel.isOpen() && !this.showConversations() && !this.showPreferences() && !this.showShare();
|
|
4940
|
+
untracked(() => this.store.setViewing(viewing));
|
|
4941
|
+
});
|
|
4942
|
+
// A clicked notification opens its conversation in this window.
|
|
4943
|
+
const notifications = this.push.openConversation$.subscribe(conversationId => this.panel.open(conversationId));
|
|
4944
|
+
inject(DestroyRef).onDestroy(() => notifications.unsubscribe());
|
|
3527
4945
|
effect(() => {
|
|
3528
4946
|
if (this.panel.isOpen()) {
|
|
3529
4947
|
untracked(() => {
|
|
@@ -3543,6 +4961,7 @@ class NhAssistantPanelComponent {
|
|
|
3543
4961
|
ngAfterViewInit() {
|
|
3544
4962
|
this.portal = new TemplatePortal(this.drawer(), this.viewContainerRef);
|
|
3545
4963
|
this.panel.registerPanel(this.portal);
|
|
4964
|
+
this.openFromLocation();
|
|
3546
4965
|
}
|
|
3547
4966
|
ngOnDestroy() {
|
|
3548
4967
|
if (this.portal) {
|
|
@@ -3557,12 +4976,19 @@ class NhAssistantPanelComponent {
|
|
|
3557
4976
|
}
|
|
3558
4977
|
toggleConversations() {
|
|
3559
4978
|
this.showPreferences.set(false);
|
|
4979
|
+
this.showShare.set(false);
|
|
3560
4980
|
this.showConversations.update(show => !show);
|
|
3561
4981
|
}
|
|
3562
4982
|
togglePreferences() {
|
|
3563
4983
|
this.showConversations.set(false);
|
|
4984
|
+
this.showShare.set(false);
|
|
3564
4985
|
this.showPreferences.update(show => !show);
|
|
3565
4986
|
}
|
|
4987
|
+
toggleShare() {
|
|
4988
|
+
this.showConversations.set(false);
|
|
4989
|
+
this.showPreferences.set(false);
|
|
4990
|
+
this.showShare.update(show => !show);
|
|
4991
|
+
}
|
|
3566
4992
|
newConversation() {
|
|
3567
4993
|
this.store.startNewConversation();
|
|
3568
4994
|
this.showConversations.set(false);
|
|
@@ -3577,30 +5003,60 @@ class NhAssistantPanelComponent {
|
|
|
3577
5003
|
send(text) {
|
|
3578
5004
|
this.showConversations.set(false);
|
|
3579
5005
|
this.showPreferences.set(false);
|
|
5006
|
+
this.showShare.set(false);
|
|
3580
5007
|
void this.store.send(text);
|
|
3581
5008
|
}
|
|
5009
|
+
/**
|
|
5010
|
+
* Opens the panel for an invitation link or a clicked notification in the URL fragment and
|
|
5011
|
+
* removes the fragment, so a reload or a shared screenshot does not reuse the token.
|
|
5012
|
+
*/
|
|
5013
|
+
openFromLocation() {
|
|
5014
|
+
const location = globalThis.location;
|
|
5015
|
+
const hash = location?.hash?.replace(/^#/, '') ?? '';
|
|
5016
|
+
const separatorIndex = hash.indexOf('=');
|
|
5017
|
+
const name = separatorIndex < 0 ? hash : hash.slice(0, separatorIndex);
|
|
5018
|
+
const value = separatorIndex < 0 ? '' : hash.slice(separatorIndex + 1);
|
|
5019
|
+
if (!value || (name !== NH_ASSISTANT_JOIN_FRAGMENT && name !== NH_ASSISTANT_CONVERSATION_FRAGMENT)) {
|
|
5020
|
+
return;
|
|
5021
|
+
}
|
|
5022
|
+
globalThis.history?.replaceState(globalThis.history.state, '', location.pathname + location.search);
|
|
5023
|
+
if (name === NH_ASSISTANT_CONVERSATION_FRAGMENT) {
|
|
5024
|
+
this.panel.open(decodeURIComponent(value));
|
|
5025
|
+
return;
|
|
5026
|
+
}
|
|
5027
|
+
const tokenIndex = value.indexOf('.');
|
|
5028
|
+
if (tokenIndex <= 0) {
|
|
5029
|
+
return;
|
|
5030
|
+
}
|
|
5031
|
+
const conversationId = decodeURIComponent(value.slice(0, tokenIndex));
|
|
5032
|
+
const token = decodeURIComponent(value.slice(tokenIndex + 1));
|
|
5033
|
+
this.panel.open();
|
|
5034
|
+
void this.store.initialize().then(() => this.store.joinConversation(conversationId, token));
|
|
5035
|
+
}
|
|
3582
5036
|
decide(decision) {
|
|
3583
5037
|
this.store.decide(decision);
|
|
3584
5038
|
}
|
|
3585
5039
|
cancel() {
|
|
3586
5040
|
void this.store.cancel();
|
|
3587
5041
|
}
|
|
3588
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
3589
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.28", type: NhAssistantPanelComponent, isStandalone: true, selector: "nh-assistant-panel", viewQueries: [{ propertyName: "drawer", first: true, predicate: ["drawer"], descendants: true, isSignal: true }], ngImport: i0, template: "<ng-template #drawer>\n <section class=\"drawer\" role=\"dialog\" aria-modal=\"false\" [attr.aria-labelledby]=\"titleId\">\n <header class=\"header\">\n <div class=\"heading\">\n <h2 [id]=\"titleId\">{{ 'nh-assistant.panel.title' | translate }}</h2>\n @if (store.enabled()) {\n <nh-assistant-agent-picker\n [agents]=\"store.agents()\"\n [selectedAgentId]=\"store.selectedAgentId()\"\n [disabled]=\"store.streaming()\"\n (agentChange)=\"store.selectAgent($event)\" />\n }\n </div>\n <div class=\"header-actions\">\n @if (store.enabled()) {\n <button\n class=\"icon-button\"\n type=\"button\"\n [class.active]=\"showConversations()\"\n [attr.aria-expanded]=\"showConversations()\"\n [attr.aria-label]=\"(showConversations() ? 'nh-assistant.panel.hide-conversations' : 'nh-assistant.panel.show-conversations') | translate\"\n [attr.title]=\"(showConversations() ? 'nh-assistant.panel.hide-conversations' : 'nh-assistant.panel.show-conversations') | translate\"\n (click)=\"toggleConversations()\">\n <nh-assistant-icon name=\"list\" />\n </button>\n <button\n class=\"icon-button\"\n type=\"button\"\n [class.active]=\"showPreferences()\"\n [attr.aria-expanded]=\"showPreferences()\"\n [attr.aria-label]=\"'nh-assistant.panel.open-preferences' | translate\"\n [attr.title]=\"'nh-assistant.panel.open-preferences' | translate\"\n (click)=\"togglePreferences()\">\n <nh-assistant-icon name=\"settings\" />\n </button>\n @if (adminRoute && store.canAdminister()) {\n <a\n class=\"icon-button\"\n [routerLink]=\"adminRoute\"\n [attr.aria-label]=\"'nh-assistant.panel.open-admin' | translate\"\n [attr.title]=\"'nh-assistant.panel.open-admin' | translate\"\n (click)=\"panel.close()\">\n <nh-assistant-icon name=\"admin\" />\n </a>\n }\n <button\n class=\"icon-button\"\n type=\"button\"\n [disabled]=\"store.streaming()\"\n [attr.aria-label]=\"'nh-assistant.panel.new-conversation' | translate\"\n [attr.title]=\"'nh-assistant.panel.new-conversation' | translate\"\n (click)=\"newConversation()\">\n <nh-assistant-icon name=\"plus\" />\n </button>\n }\n <button\n class=\"icon-button\"\n type=\"button\"\n [attr.aria-label]=\"'nh-assistant.panel.close' | translate\"\n [attr.title]=\"'nh-assistant.panel.close' | translate\"\n (click)=\"panel.close()\">\n <nh-assistant-icon name=\"close\" />\n </button>\n </div>\n </header>\n\n <div class=\"body\">\n @if (store.statusLoading() && !store.enabled()) {\n <div class=\"state\" role=\"status\">\n <p class=\"state-title\">{{ 'nh-assistant.panel.loading' | translate }}</p>\n </div>\n } @else if (!store.enabled()) {\n <div class=\"state\">\n <nh-assistant-icon class=\"state-icon\" name=\"warning\" />\n <p class=\"state-title\">{{ 'nh-assistant.panel.unavailable-title' | translate }}</p>\n <p>{{ 'nh-assistant.panel.unavailable-description' | translate }}</p>\n </div>\n } @else {\n @if (showPreferences()) {\n <nh-assistant-preferences class=\"overlay-view\" (closed)=\"showPreferences.set(false)\" />\n }\n\n @if (showConversations()) {\n <nh-assistant-conversation-list\n class=\"overlay-view\"\n [conversations]=\"store.conversations()\"\n [total]=\"store.conversationsTotal()\"\n [activeConversationId]=\"conversation()?.id ?? null\"\n [loading]=\"store.conversationsLoading()\"\n [disabled]=\"store.streaming()\"\n (conversationSelect)=\"openConversation($event)\"\n (conversationDelete)=\"deleteConversation($event)\"\n (conversationCreate)=\"newConversation()\" />\n }\n\n @if (store.conversationLoading()) {\n <div class=\"state\" role=\"status\">\n <p class=\"state-title\">{{ 'nh-assistant.thread.loading' | translate }}</p>\n </div>\n } @else if (messages().length === 0 && !store.streaming()) {\n <div class=\"state\">\n <nh-assistant-icon class=\"state-icon\" name=\"assistant\" />\n <p class=\"state-title\">{{ 'nh-assistant.panel.empty-title' | translate }}</p>\n <p>{{ 'nh-assistant.panel.empty-description' | translate }}</p>\n </div>\n } @else {\n <nh-assistant-thread\n class=\"thread\"\n [messages]=\"messages()\"\n [streaming]=\"store.streaming()\"\n [deciding]=\"store.deciding()\"\n [markdown]=\"markdown\"\n (approvalDecision)=\"decide($event)\" />\n }\n }\n </div>\n\n @if (store.error(); as error) {\n <div class=\"error\" role=\"alert\">\n <nh-assistant-icon name=\"warning\" />\n <p>{{ errorKeys() | nhAssistantTranslate: error.code }}</p>\n <button\n class=\"icon-button\"\n type=\"button\"\n [attr.aria-label]=\"'nh-assistant.panel.dismiss-error' | translate\"\n [attr.title]=\"'nh-assistant.panel.dismiss-error' | translate\"\n (click)=\"store.clearError()\">\n <nh-assistant-icon name=\"close\" />\n </button>\n </div>\n } @else if (store.notice(); as notice) {\n <div class=\"turn-notice\" role=\"status\">\n <nh-assistant-icon name=\"warning\" />\n <p>{{ noticeKeys() | nhAssistantTranslate: notice.code }}</p>\n <button\n class=\"icon-button\"\n type=\"button\"\n [attr.aria-label]=\"'nh-assistant.panel.dismiss-error' | translate\"\n [attr.title]=\"'nh-assistant.panel.dismiss-error' | translate\"\n (click)=\"store.clearNotice()\">\n <nh-assistant-icon name=\"close\" />\n </button>\n </div>\n }\n\n @if (store.enabled() && !showPreferences()) {\n <footer class=\"footer\" (focusin)=\"refreshPageContext()\">\n @if (closed()) {\n <p class=\"notice\">{{ 'nh-assistant.panel.conversation-closed' | translate }}</p>\n }\n @if (store.pageContext(); as context) {\n <div class=\"context-chip\" [class.excluded]=\"store.pageContextExcluded()\" [attr.title]=\"context.route\">\n <nh-assistant-icon name=\"page\" />\n <span class=\"context-text\">\n {{ (store.pageContextExcluded() ? 'nh-assistant.page-context.excluded' : 'nh-assistant.page-context.included') | translate: { page: describeContext(context) } }}\n </span>\n <button\n class=\"icon-button small\"\n type=\"button\"\n [attr.aria-pressed]=\"store.pageContextExcluded()\"\n [attr.aria-label]=\"(store.pageContextExcluded() ? 'nh-assistant.page-context.include' : 'nh-assistant.page-context.exclude') | translate: { page: describeContext(context) }\"\n [attr.title]=\"(store.pageContextExcluded() ? 'nh-assistant.page-context.include' : 'nh-assistant.page-context.exclude') | translate: { page: describeContext(context) }\"\n (click)=\"store.setPageContextExcluded(!store.pageContextExcluded())\">\n <nh-assistant-icon [name]=\"store.pageContextExcluded() ? 'plus' : 'x'\" />\n </button>\n </div>\n }\n <nh-assistant-composer\n [disabled]=\"closed()\"\n [sendDisabled]=\"composerDisabled()\"\n [busy]=\"busy()\"\n [status]=\"composerStatus()\"\n [maxLength]=\"store.limits()?.maxMessageChars ?? null\"\n [restore]=\"store.restoredDraft()\"\n (restored)=\"store.consumeRestoredDraft()\"\n (send)=\"send($event)\"\n (cancel)=\"cancel()\" />\n </footer>\n }\n </section>\n</ng-template>\n", styles: [":host{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){:host{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}.drawer{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){.drawer{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}:host{display:contents}.drawer{position:relative;display:flex;flex-direction:column;box-sizing:border-box;width:100%;height:100%;border-left:1px solid var(--nha-border);color:var(--nha-text);background:var(--nha-background);box-shadow:var(--nha-shadow);font-family:var(--nha-font);font-size:14px}.drawer *,.drawer *:before,.drawer *:after{box-sizing:border-box}.header{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:6px 12px;border-bottom:1px solid var(--nha-border);padding:12px 12px 12px 16px}.heading{display:contents}nh-assistant-agent-picker{grid-column:1/-1;grid-row:2}h2{grid-row:1;min-width:0;margin:0;overflow:hidden;font-size:16px;font-weight:750;letter-spacing:-.01em;text-overflow:ellipsis;white-space:nowrap}.header-actions{display:flex;flex:none;grid-column:2;grid-row:1;gap:4px}.icon-button{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:1px solid transparent;border-radius:var(--nha-radius-control);color:var(--nha-text-muted);background:transparent;cursor:pointer;text-decoration:none}.icon-button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.icon-button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}.icon-button:disabled{cursor:not-allowed;opacity:.5}.icon-button.active{color:var(--nha-highlight);background:var(--nha-surface-strong)}.body{position:relative;display:flex;flex:1;flex-direction:column;min-height:0;background:var(--nha-background)}.thread{flex:1;min-height:0}.overlay-view{position:absolute;z-index:1;inset:0;border-bottom:1px solid var(--nha-border);background:var(--nha-background)}.state{display:flex;flex:1;flex-direction:column;align-items:center;justify-content:center;gap:4px;padding:24px;color:var(--nha-text-muted);text-align:center}.state p{max-width:32ch;margin:0;font-size:13px}.state-icon{margin-bottom:8px;color:var(--nha-highlight);font-size:28px}.state .state-title{color:var(--nha-text);font-size:15px;font-weight:700}.error{display:flex;align-items:center;gap:8px;margin:0 12px 8px;border:1px solid var(--nha-danger);border-radius:var(--nha-radius-control);padding:4px 4px 4px 12px;color:var(--nha-danger);background:var(--nha-danger-soft)}.error p{flex:1;margin:0;color:var(--nha-text);font-size:13px}.turn-notice{display:flex;align-items:center;gap:8px;margin:0 12px 8px;border:1px solid var(--nha-warning);border-radius:var(--nha-radius-control);padding:4px 4px 4px 12px;color:var(--nha-warning);background:var(--nha-warning-soft)}.turn-notice p{flex:1;margin:0;color:var(--nha-text);font-size:13px}.footer{border-top:1px solid var(--nha-border);padding:12px 16px 14px}.context-chip{display:flex;align-items:center;gap:6px;max-width:100%;margin:0 0 8px;border:1px solid var(--nha-border);border-radius:999px;padding:2px 2px 2px 10px;width:fit-content;color:var(--nha-text-muted);background:var(--nha-surface);font-size:12px}.context-chip.excluded .context-text{text-decoration:line-through}.context-text{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.icon-button.small{flex:none;width:26px;height:26px;border-radius:999px}.notice{margin:0 0 8px;color:var(--nha-text-muted);font-size:12px}\n"], dependencies: [{ kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "component", type: NhAssistantIconComponent, selector: "nh-assistant-icon", inputs: ["name"] }, { kind: "component", type: NhAssistantAgentPickerComponent, selector: "nh-assistant-agent-picker", inputs: ["agents", "selectedAgentId", "disabled"], outputs: ["agentChange"] }, { kind: "component", type: NhAssistantComposerComponent, selector: "nh-assistant-composer", inputs: ["disabled", "sendDisabled", "busy", "status", "maxLength", "restore"], outputs: ["send", "cancel", "restored"] }, { kind: "component", type: NhAssistantConversationListComponent, selector: "nh-assistant-conversation-list", inputs: ["conversations", "total", "activeConversationId", "loading", "disabled"], outputs: ["conversationSelect", "conversationDelete", "conversationCreate"] }, { kind: "component", type: NhAssistantPreferencesComponent, selector: "nh-assistant-preferences", outputs: ["closed", "saved"] }, { kind: "component", type: NhAssistantThreadComponent, selector: "nh-assistant-thread", inputs: ["messages", "streaming", "deciding", "markdown"], outputs: ["approvalDecision"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }, { kind: "pipe", type: NhAssistantTranslatePipe, name: "nhAssistantTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
5042
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
5043
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.33", type: NhAssistantPanelComponent, isStandalone: true, selector: "nh-assistant-panel", viewQueries: [{ propertyName: "drawer", first: true, predicate: ["drawer"], descendants: true, isSignal: true }], ngImport: i0, template: "<ng-template #drawer>\n <section class=\"drawer\" role=\"dialog\" aria-modal=\"false\" [attr.aria-labelledby]=\"titleId\">\n <header class=\"header\">\n <div class=\"heading\">\n <h2 [id]=\"titleId\">{{ 'nh-assistant.panel.title' | translate }}</h2>\n @if (store.enabled()) {\n <nh-assistant-agent-picker\n [agents]=\"store.agents()\"\n [selectedAgentId]=\"store.selectedAgentId()\"\n (agentChange)=\"store.selectAgent($event)\" />\n }\n </div>\n <div class=\"header-actions\">\n @if (store.enabled()) {\n @if (canShare()) {\n <button\n class=\"icon-button\"\n type=\"button\"\n [class.active]=\"showShare() || shared()\"\n [attr.aria-expanded]=\"showShare()\"\n [attr.aria-label]=\"(store.isOwner() ? 'nh-assistant.panel.share' : 'nh-assistant.panel.members') | translate\"\n [attr.title]=\"(store.isOwner() ? 'nh-assistant.panel.share' : 'nh-assistant.panel.members') | translate\"\n (click)=\"toggleShare()\">\n <nh-assistant-icon name=\"users\" />\n </button>\n }\n <button\n class=\"icon-button\"\n type=\"button\"\n [class.active]=\"showConversations()\"\n [attr.aria-expanded]=\"showConversations()\"\n [attr.aria-label]=\"(showConversations() ? 'nh-assistant.panel.hide-conversations' : 'nh-assistant.panel.show-conversations') | translate\"\n [attr.title]=\"(showConversations() ? 'nh-assistant.panel.hide-conversations' : 'nh-assistant.panel.show-conversations') | translate\"\n (click)=\"toggleConversations()\">\n <nh-assistant-icon name=\"list\" />\n </button>\n <button\n class=\"icon-button\"\n type=\"button\"\n [class.active]=\"showPreferences()\"\n [attr.aria-expanded]=\"showPreferences()\"\n [attr.aria-label]=\"'nh-assistant.panel.open-preferences' | translate\"\n [attr.title]=\"'nh-assistant.panel.open-preferences' | translate\"\n (click)=\"togglePreferences()\">\n <nh-assistant-icon name=\"settings\" />\n </button>\n @if (adminRoute && store.canAdminister()) {\n <a\n class=\"icon-button\"\n [routerLink]=\"adminRoute\"\n [attr.aria-label]=\"'nh-assistant.panel.open-admin' | translate\"\n [attr.title]=\"'nh-assistant.panel.open-admin' | translate\"\n (click)=\"panel.close()\">\n <nh-assistant-icon name=\"admin\" />\n </a>\n }\n <button\n class=\"icon-button\"\n type=\"button\"\n [attr.aria-label]=\"'nh-assistant.panel.new-conversation' | translate\"\n [attr.title]=\"'nh-assistant.panel.new-conversation' | translate\"\n (click)=\"newConversation()\">\n <nh-assistant-icon name=\"plus\" />\n </button>\n }\n <button\n class=\"icon-button\"\n type=\"button\"\n [attr.aria-label]=\"'nh-assistant.panel.close' | translate\"\n [attr.title]=\"'nh-assistant.panel.close' | translate\"\n (click)=\"panel.close()\">\n <nh-assistant-icon name=\"close\" />\n </button>\n </div>\n </header>\n\n @if (store.enabled()) {\n <nh-assistant-activity [items]=\"store.activity()\" (conversationSelect)=\"openConversation($event)\" />\n }\n\n <div class=\"body\">\n @if (store.statusLoading() && !store.enabled()) {\n <div class=\"state\" role=\"status\">\n <p class=\"state-title\">{{ 'nh-assistant.panel.loading' | translate }}</p>\n </div>\n } @else if (!store.enabled()) {\n <div class=\"state\">\n <nh-assistant-icon class=\"state-icon\" name=\"warning\" />\n <p class=\"state-title\">{{ 'nh-assistant.panel.unavailable-title' | translate }}</p>\n <p>{{ 'nh-assistant.panel.unavailable-description' | translate }}</p>\n </div>\n } @else {\n @if (showPreferences()) {\n <nh-assistant-preferences class=\"overlay-view\" (closed)=\"showPreferences.set(false)\" />\n }\n\n @if (showShare()) {\n <nh-assistant-share class=\"overlay-view\" (closed)=\"showShare.set(false)\" />\n }\n\n @if (showConversations()) {\n <nh-assistant-conversation-list\n class=\"overlay-view\"\n [conversations]=\"store.conversations()\"\n [total]=\"store.conversationsTotal()\"\n [activeConversationId]=\"conversation()?.id ?? null\"\n [loading]=\"store.conversationsLoading()\"\n (conversationSelect)=\"openConversation($event)\"\n (conversationDelete)=\"deleteConversation($event)\"\n (conversationCreate)=\"newConversation()\" />\n }\n\n @if (store.conversationLoading()) {\n <div class=\"state\" role=\"status\">\n <p class=\"state-title\">{{ 'nh-assistant.thread.loading' | translate }}</p>\n </div>\n } @else if (messages().length === 0 && !store.streaming()) {\n <div class=\"state\">\n <nh-assistant-icon class=\"state-icon\" name=\"assistant\" />\n <p class=\"state-title\">{{ 'nh-assistant.panel.empty-title' | translate }}</p>\n <p>{{ 'nh-assistant.panel.empty-description' | translate }}</p>\n </div>\n } @else {\n <nh-assistant-thread\n class=\"thread\"\n [messages]=\"messages()\"\n [streaming]=\"busy()\"\n [deciding]=\"store.deciding()\"\n [markdown]=\"markdown\"\n [members]=\"store.members()\"\n [currentActorId]=\"conversation()?.currentActorId ?? null\"\n [canDecide]=\"store.canDecide()\"\n [decisionOwner]=\"activeMemberName()\"\n (approvalDecision)=\"decide($event)\" />\n }\n }\n </div>\n\n @if (store.error(); as error) {\n <div class=\"error\" role=\"alert\">\n <nh-assistant-icon name=\"warning\" />\n <p>{{ errorKeys() | nhAssistantTranslate: error.code }}</p>\n <button\n class=\"icon-button\"\n type=\"button\"\n [attr.aria-label]=\"'nh-assistant.panel.dismiss-error' | translate\"\n [attr.title]=\"'nh-assistant.panel.dismiss-error' | translate\"\n (click)=\"store.clearError()\">\n <nh-assistant-icon name=\"close\" />\n </button>\n </div>\n } @else if (store.notice(); as notice) {\n <div class=\"turn-notice\" role=\"status\">\n <nh-assistant-icon name=\"warning\" />\n <p>{{ noticeKeys() | nhAssistantTranslate: notice.code }}</p>\n <button\n class=\"icon-button\"\n type=\"button\"\n [attr.aria-label]=\"'nh-assistant.panel.dismiss-error' | translate\"\n [attr.title]=\"'nh-assistant.panel.dismiss-error' | translate\"\n (click)=\"store.clearNotice()\">\n <nh-assistant-icon name=\"close\" />\n </button>\n </div>\n }\n\n @if (store.enabled() && !showPreferences() && !showShare()) {\n <footer class=\"footer\" (focusin)=\"refreshPageContext()\">\n @if (closed()) {\n <p class=\"notice\">{{ 'nh-assistant.panel.conversation-closed' | translate }}</p>\n }\n @if (store.pageContext(); as context) {\n <div class=\"context-chip\" [class.excluded]=\"store.pageContextExcluded()\" [attr.title]=\"context.route\">\n <nh-assistant-icon name=\"page\" />\n <span class=\"context-text\">\n {{ (store.pageContextExcluded() ? 'nh-assistant.page-context.excluded' : 'nh-assistant.page-context.included') | translate: { page: describeContext(context) } }}\n </span>\n <button\n class=\"icon-button small\"\n type=\"button\"\n [attr.aria-pressed]=\"store.pageContextExcluded()\"\n [attr.aria-label]=\"(store.pageContextExcluded() ? 'nh-assistant.page-context.include' : 'nh-assistant.page-context.exclude') | translate: { page: describeContext(context) }\"\n [attr.title]=\"(store.pageContextExcluded() ? 'nh-assistant.page-context.include' : 'nh-assistant.page-context.exclude') | translate: { page: describeContext(context) }\"\n (click)=\"store.setPageContextExcluded(!store.pageContextExcluded())\">\n <nh-assistant-icon [name]=\"store.pageContextExcluded() ? 'plus' : 'x'\" />\n </button>\n </div>\n }\n <nh-assistant-composer\n [disabled]=\"closed()\"\n [sendDisabled]=\"composerDisabled()\"\n [busy]=\"busy()\"\n [status]=\"composerStatus()\"\n [statusActor]=\"activeMemberName()\"\n [canStop]=\"canStop()\"\n [maxLength]=\"store.limits()?.maxMessageChars ?? null\"\n [restore]=\"store.restoredDraft()\"\n (restored)=\"store.consumeRestoredDraft()\"\n (send)=\"send($event)\"\n (cancel)=\"cancel()\" />\n </footer>\n }\n </section>\n</ng-template>\n", styles: [":host{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){:host{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}.drawer{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){.drawer{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}:host{display:contents}.drawer{position:relative;display:flex;flex-direction:column;box-sizing:border-box;width:100%;height:100%;border-left:1px solid var(--nha-border);color:var(--nha-text);background:var(--nha-background);box-shadow:var(--nha-shadow);font-family:var(--nha-font);font-size:14px}.drawer *,.drawer *:before,.drawer *:after{box-sizing:border-box}.header{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:6px 12px;border-bottom:1px solid var(--nha-border);padding:12px 12px 12px 16px}.heading{display:contents}nh-assistant-agent-picker{grid-column:1/-1;grid-row:2}h2{grid-row:1;min-width:0;margin:0;overflow:hidden;font-size:16px;font-weight:750;letter-spacing:-.01em;text-overflow:ellipsis;white-space:nowrap}.header-actions{display:flex;flex:none;grid-column:2;grid-row:1;gap:4px}.icon-button{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:1px solid transparent;border-radius:var(--nha-radius-control);color:var(--nha-text-muted);background:transparent;cursor:pointer;text-decoration:none}.icon-button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.icon-button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}.icon-button:disabled{cursor:not-allowed;opacity:.5}.icon-button.active{color:var(--nha-highlight);background:var(--nha-surface-strong)}.body{position:relative;display:flex;flex:1;flex-direction:column;min-height:0;background:var(--nha-background)}.thread{flex:1;min-height:0}.overlay-view{position:absolute;z-index:1;inset:0;border-bottom:1px solid var(--nha-border);background:var(--nha-background)}.state{display:flex;flex:1;flex-direction:column;align-items:center;justify-content:center;gap:4px;padding:24px;color:var(--nha-text-muted);text-align:center}.state p{max-width:32ch;margin:0;font-size:13px}.state-icon{margin-bottom:8px;color:var(--nha-highlight);font-size:28px}.state .state-title{color:var(--nha-text);font-size:15px;font-weight:700}.error{display:flex;align-items:center;gap:8px;margin:0 12px 8px;border:1px solid var(--nha-danger);border-radius:var(--nha-radius-control);padding:4px 4px 4px 12px;color:var(--nha-danger);background:var(--nha-danger-soft)}.error p{flex:1;margin:0;color:var(--nha-text);font-size:13px}.turn-notice{display:flex;align-items:center;gap:8px;margin:0 12px 8px;border:1px solid var(--nha-warning);border-radius:var(--nha-radius-control);padding:4px 4px 4px 12px;color:var(--nha-warning);background:var(--nha-warning-soft)}.turn-notice p{flex:1;margin:0;color:var(--nha-text);font-size:13px}.footer{border-top:1px solid var(--nha-border);padding:12px 16px 14px}.context-chip{display:flex;align-items:center;gap:6px;max-width:100%;margin:0 0 8px;border:1px solid var(--nha-border);border-radius:999px;padding:2px 2px 2px 10px;width:fit-content;color:var(--nha-text-muted);background:var(--nha-surface);font-size:12px}.context-chip.excluded .context-text{text-decoration:line-through}.context-text{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.icon-button.small{flex:none;width:26px;height:26px;border-radius:999px}.notice{margin:0 0 8px;color:var(--nha-text-muted);font-size:12px}\n"], dependencies: [{ kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "component", type: NhAssistantIconComponent, selector: "nh-assistant-icon", inputs: ["name"] }, { kind: "component", type: NhAssistantActivityComponent, selector: "nh-assistant-activity", inputs: ["items"], outputs: ["conversationSelect"] }, { kind: "component", type: NhAssistantAgentPickerComponent, selector: "nh-assistant-agent-picker", inputs: ["agents", "selectedAgentId", "disabled"], outputs: ["agentChange"] }, { kind: "component", type: NhAssistantComposerComponent, selector: "nh-assistant-composer", inputs: ["disabled", "sendDisabled", "busy", "status", "statusActor", "canStop", "maxLength", "restore"], outputs: ["send", "cancel", "restored"] }, { kind: "component", type: NhAssistantConversationListComponent, selector: "nh-assistant-conversation-list", inputs: ["conversations", "total", "activeConversationId", "loading", "disabled"], outputs: ["conversationSelect", "conversationDelete", "conversationCreate"] }, { kind: "component", type: NhAssistantPreferencesComponent, selector: "nh-assistant-preferences", outputs: ["closed", "saved"] }, { kind: "component", type: NhAssistantShareComponent, selector: "nh-assistant-share", outputs: ["closed"] }, { kind: "component", type: NhAssistantThreadComponent, selector: "nh-assistant-thread", inputs: ["messages", "streaming", "deciding", "markdown", "members", "currentActorId", "canDecide", "decisionOwner"], outputs: ["approvalDecision"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }, { kind: "pipe", type: NhAssistantTranslatePipe, name: "nhAssistantTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
3590
5044
|
}
|
|
3591
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
5045
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantPanelComponent, decorators: [{
|
|
3592
5046
|
type: Component,
|
|
3593
5047
|
args: [{ selector: 'nh-assistant-panel', standalone: true, imports: [
|
|
3594
5048
|
RouterLink,
|
|
3595
5049
|
TranslatePipe,
|
|
3596
5050
|
NhAssistantTranslatePipe,
|
|
3597
5051
|
NhAssistantIconComponent,
|
|
5052
|
+
NhAssistantActivityComponent,
|
|
3598
5053
|
NhAssistantAgentPickerComponent,
|
|
3599
5054
|
NhAssistantComposerComponent,
|
|
3600
5055
|
NhAssistantConversationListComponent,
|
|
3601
5056
|
NhAssistantPreferencesComponent,
|
|
5057
|
+
NhAssistantShareComponent,
|
|
3602
5058
|
NhAssistantThreadComponent
|
|
3603
|
-
], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-template #drawer>\n <section class=\"drawer\" role=\"dialog\" aria-modal=\"false\" [attr.aria-labelledby]=\"titleId\">\n <header class=\"header\">\n <div class=\"heading\">\n <h2 [id]=\"titleId\">{{ 'nh-assistant.panel.title' | translate }}</h2>\n @if (store.enabled()) {\n <nh-assistant-agent-picker\n [agents]=\"store.agents()\"\n [selectedAgentId]=\"store.selectedAgentId()\"\n [disabled]=\"store.streaming()\"\n (agentChange)=\"store.selectAgent($event)\" />\n }\n </div>\n <div class=\"header-actions\">\n @if (store.enabled()) {\n <button\n class=\"icon-button\"\n type=\"button\"\n [class.active]=\"showConversations()\"\n [attr.aria-expanded]=\"showConversations()\"\n [attr.aria-label]=\"(showConversations() ? 'nh-assistant.panel.hide-conversations' : 'nh-assistant.panel.show-conversations') | translate\"\n [attr.title]=\"(showConversations() ? 'nh-assistant.panel.hide-conversations' : 'nh-assistant.panel.show-conversations') | translate\"\n (click)=\"toggleConversations()\">\n <nh-assistant-icon name=\"list\" />\n </button>\n <button\n class=\"icon-button\"\n type=\"button\"\n [class.active]=\"showPreferences()\"\n [attr.aria-expanded]=\"showPreferences()\"\n [attr.aria-label]=\"'nh-assistant.panel.open-preferences' | translate\"\n [attr.title]=\"'nh-assistant.panel.open-preferences' | translate\"\n (click)=\"togglePreferences()\">\n <nh-assistant-icon name=\"settings\" />\n </button>\n @if (adminRoute && store.canAdminister()) {\n <a\n class=\"icon-button\"\n [routerLink]=\"adminRoute\"\n [attr.aria-label]=\"'nh-assistant.panel.open-admin' | translate\"\n [attr.title]=\"'nh-assistant.panel.open-admin' | translate\"\n (click)=\"panel.close()\">\n <nh-assistant-icon name=\"admin\" />\n </a>\n }\n <button\n class=\"icon-button\"\n type=\"button\"\n [disabled]=\"store.streaming()\"\n [attr.aria-label]=\"'nh-assistant.panel.new-conversation' | translate\"\n [attr.title]=\"'nh-assistant.panel.new-conversation' | translate\"\n (click)=\"newConversation()\">\n <nh-assistant-icon name=\"plus\" />\n </button>\n }\n <button\n class=\"icon-button\"\n type=\"button\"\n [attr.aria-label]=\"'nh-assistant.panel.close' | translate\"\n [attr.title]=\"'nh-assistant.panel.close' | translate\"\n (click)=\"panel.close()\">\n <nh-assistant-icon name=\"close\" />\n </button>\n </div>\n </header>\n\n <div class=\"body\">\n @if (store.statusLoading() && !store.enabled()) {\n <div class=\"state\" role=\"status\">\n <p class=\"state-title\">{{ 'nh-assistant.panel.loading' | translate }}</p>\n </div>\n } @else if (!store.enabled()) {\n <div class=\"state\">\n <nh-assistant-icon class=\"state-icon\" name=\"warning\" />\n <p class=\"state-title\">{{ 'nh-assistant.panel.unavailable-title' | translate }}</p>\n <p>{{ 'nh-assistant.panel.unavailable-description' | translate }}</p>\n </div>\n } @else {\n @if (showPreferences()) {\n <nh-assistant-preferences class=\"overlay-view\" (closed)=\"showPreferences.set(false)\" />\n }\n\n @if (showConversations()) {\n <nh-assistant-conversation-list\n class=\"overlay-view\"\n [conversations]=\"store.conversations()\"\n [total]=\"store.conversationsTotal()\"\n [activeConversationId]=\"conversation()?.id ?? null\"\n [loading]=\"store.conversationsLoading()\"\n [disabled]=\"store.streaming()\"\n (conversationSelect)=\"openConversation($event)\"\n (conversationDelete)=\"deleteConversation($event)\"\n (conversationCreate)=\"newConversation()\" />\n }\n\n @if (store.conversationLoading()) {\n <div class=\"state\" role=\"status\">\n <p class=\"state-title\">{{ 'nh-assistant.thread.loading' | translate }}</p>\n </div>\n } @else if (messages().length === 0 && !store.streaming()) {\n <div class=\"state\">\n <nh-assistant-icon class=\"state-icon\" name=\"assistant\" />\n <p class=\"state-title\">{{ 'nh-assistant.panel.empty-title' | translate }}</p>\n <p>{{ 'nh-assistant.panel.empty-description' | translate }}</p>\n </div>\n } @else {\n <nh-assistant-thread\n class=\"thread\"\n [messages]=\"messages()\"\n [streaming]=\"store.streaming()\"\n [deciding]=\"store.deciding()\"\n [markdown]=\"markdown\"\n (approvalDecision)=\"decide($event)\" />\n }\n }\n </div>\n\n @if (store.error(); as error) {\n <div class=\"error\" role=\"alert\">\n <nh-assistant-icon name=\"warning\" />\n <p>{{ errorKeys() | nhAssistantTranslate: error.code }}</p>\n <button\n class=\"icon-button\"\n type=\"button\"\n [attr.aria-label]=\"'nh-assistant.panel.dismiss-error' | translate\"\n [attr.title]=\"'nh-assistant.panel.dismiss-error' | translate\"\n (click)=\"store.clearError()\">\n <nh-assistant-icon name=\"close\" />\n </button>\n </div>\n } @else if (store.notice(); as notice) {\n <div class=\"turn-notice\" role=\"status\">\n <nh-assistant-icon name=\"warning\" />\n <p>{{ noticeKeys() | nhAssistantTranslate: notice.code }}</p>\n <button\n class=\"icon-button\"\n type=\"button\"\n [attr.aria-label]=\"'nh-assistant.panel.dismiss-error' | translate\"\n [attr.title]=\"'nh-assistant.panel.dismiss-error' | translate\"\n (click)=\"store.clearNotice()\">\n <nh-assistant-icon name=\"close\" />\n </button>\n </div>\n }\n\n @if (store.enabled() && !showPreferences()) {\n <footer class=\"footer\" (focusin)=\"refreshPageContext()\">\n @if (closed()) {\n <p class=\"notice\">{{ 'nh-assistant.panel.conversation-closed' | translate }}</p>\n }\n @if (store.pageContext(); as context) {\n <div class=\"context-chip\" [class.excluded]=\"store.pageContextExcluded()\" [attr.title]=\"context.route\">\n <nh-assistant-icon name=\"page\" />\n <span class=\"context-text\">\n {{ (store.pageContextExcluded() ? 'nh-assistant.page-context.excluded' : 'nh-assistant.page-context.included') | translate: { page: describeContext(context) } }}\n </span>\n <button\n class=\"icon-button small\"\n type=\"button\"\n [attr.aria-pressed]=\"store.pageContextExcluded()\"\n [attr.aria-label]=\"(store.pageContextExcluded() ? 'nh-assistant.page-context.include' : 'nh-assistant.page-context.exclude') | translate: { page: describeContext(context) }\"\n [attr.title]=\"(store.pageContextExcluded() ? 'nh-assistant.page-context.include' : 'nh-assistant.page-context.exclude') | translate: { page: describeContext(context) }\"\n (click)=\"store.setPageContextExcluded(!store.pageContextExcluded())\">\n <nh-assistant-icon [name]=\"store.pageContextExcluded() ? 'plus' : 'x'\" />\n </button>\n </div>\n }\n <nh-assistant-composer\n [disabled]=\"closed()\"\n [sendDisabled]=\"composerDisabled()\"\n [busy]=\"busy()\"\n [status]=\"composerStatus()\"\n [maxLength]=\"store.limits()?.maxMessageChars ?? null\"\n [restore]=\"store.restoredDraft()\"\n (restored)=\"store.consumeRestoredDraft()\"\n (send)=\"send($event)\"\n (cancel)=\"cancel()\" />\n </footer>\n }\n </section>\n</ng-template>\n", styles: [":host{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){:host{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}.drawer{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){.drawer{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}:host{display:contents}.drawer{position:relative;display:flex;flex-direction:column;box-sizing:border-box;width:100%;height:100%;border-left:1px solid var(--nha-border);color:var(--nha-text);background:var(--nha-background);box-shadow:var(--nha-shadow);font-family:var(--nha-font);font-size:14px}.drawer *,.drawer *:before,.drawer *:after{box-sizing:border-box}.header{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:6px 12px;border-bottom:1px solid var(--nha-border);padding:12px 12px 12px 16px}.heading{display:contents}nh-assistant-agent-picker{grid-column:1/-1;grid-row:2}h2{grid-row:1;min-width:0;margin:0;overflow:hidden;font-size:16px;font-weight:750;letter-spacing:-.01em;text-overflow:ellipsis;white-space:nowrap}.header-actions{display:flex;flex:none;grid-column:2;grid-row:1;gap:4px}.icon-button{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:1px solid transparent;border-radius:var(--nha-radius-control);color:var(--nha-text-muted);background:transparent;cursor:pointer;text-decoration:none}.icon-button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.icon-button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}.icon-button:disabled{cursor:not-allowed;opacity:.5}.icon-button.active{color:var(--nha-highlight);background:var(--nha-surface-strong)}.body{position:relative;display:flex;flex:1;flex-direction:column;min-height:0;background:var(--nha-background)}.thread{flex:1;min-height:0}.overlay-view{position:absolute;z-index:1;inset:0;border-bottom:1px solid var(--nha-border);background:var(--nha-background)}.state{display:flex;flex:1;flex-direction:column;align-items:center;justify-content:center;gap:4px;padding:24px;color:var(--nha-text-muted);text-align:center}.state p{max-width:32ch;margin:0;font-size:13px}.state-icon{margin-bottom:8px;color:var(--nha-highlight);font-size:28px}.state .state-title{color:var(--nha-text);font-size:15px;font-weight:700}.error{display:flex;align-items:center;gap:8px;margin:0 12px 8px;border:1px solid var(--nha-danger);border-radius:var(--nha-radius-control);padding:4px 4px 4px 12px;color:var(--nha-danger);background:var(--nha-danger-soft)}.error p{flex:1;margin:0;color:var(--nha-text);font-size:13px}.turn-notice{display:flex;align-items:center;gap:8px;margin:0 12px 8px;border:1px solid var(--nha-warning);border-radius:var(--nha-radius-control);padding:4px 4px 4px 12px;color:var(--nha-warning);background:var(--nha-warning-soft)}.turn-notice p{flex:1;margin:0;color:var(--nha-text);font-size:13px}.footer{border-top:1px solid var(--nha-border);padding:12px 16px 14px}.context-chip{display:flex;align-items:center;gap:6px;max-width:100%;margin:0 0 8px;border:1px solid var(--nha-border);border-radius:999px;padding:2px 2px 2px 10px;width:fit-content;color:var(--nha-text-muted);background:var(--nha-surface);font-size:12px}.context-chip.excluded .context-text{text-decoration:line-through}.context-text{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.icon-button.small{flex:none;width:26px;height:26px;border-radius:999px}.notice{margin:0 0 8px;color:var(--nha-text-muted);font-size:12px}\n"] }]
|
|
5059
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-template #drawer>\n <section class=\"drawer\" role=\"dialog\" aria-modal=\"false\" [attr.aria-labelledby]=\"titleId\">\n <header class=\"header\">\n <div class=\"heading\">\n <h2 [id]=\"titleId\">{{ 'nh-assistant.panel.title' | translate }}</h2>\n @if (store.enabled()) {\n <nh-assistant-agent-picker\n [agents]=\"store.agents()\"\n [selectedAgentId]=\"store.selectedAgentId()\"\n (agentChange)=\"store.selectAgent($event)\" />\n }\n </div>\n <div class=\"header-actions\">\n @if (store.enabled()) {\n @if (canShare()) {\n <button\n class=\"icon-button\"\n type=\"button\"\n [class.active]=\"showShare() || shared()\"\n [attr.aria-expanded]=\"showShare()\"\n [attr.aria-label]=\"(store.isOwner() ? 'nh-assistant.panel.share' : 'nh-assistant.panel.members') | translate\"\n [attr.title]=\"(store.isOwner() ? 'nh-assistant.panel.share' : 'nh-assistant.panel.members') | translate\"\n (click)=\"toggleShare()\">\n <nh-assistant-icon name=\"users\" />\n </button>\n }\n <button\n class=\"icon-button\"\n type=\"button\"\n [class.active]=\"showConversations()\"\n [attr.aria-expanded]=\"showConversations()\"\n [attr.aria-label]=\"(showConversations() ? 'nh-assistant.panel.hide-conversations' : 'nh-assistant.panel.show-conversations') | translate\"\n [attr.title]=\"(showConversations() ? 'nh-assistant.panel.hide-conversations' : 'nh-assistant.panel.show-conversations') | translate\"\n (click)=\"toggleConversations()\">\n <nh-assistant-icon name=\"list\" />\n </button>\n <button\n class=\"icon-button\"\n type=\"button\"\n [class.active]=\"showPreferences()\"\n [attr.aria-expanded]=\"showPreferences()\"\n [attr.aria-label]=\"'nh-assistant.panel.open-preferences' | translate\"\n [attr.title]=\"'nh-assistant.panel.open-preferences' | translate\"\n (click)=\"togglePreferences()\">\n <nh-assistant-icon name=\"settings\" />\n </button>\n @if (adminRoute && store.canAdminister()) {\n <a\n class=\"icon-button\"\n [routerLink]=\"adminRoute\"\n [attr.aria-label]=\"'nh-assistant.panel.open-admin' | translate\"\n [attr.title]=\"'nh-assistant.panel.open-admin' | translate\"\n (click)=\"panel.close()\">\n <nh-assistant-icon name=\"admin\" />\n </a>\n }\n <button\n class=\"icon-button\"\n type=\"button\"\n [attr.aria-label]=\"'nh-assistant.panel.new-conversation' | translate\"\n [attr.title]=\"'nh-assistant.panel.new-conversation' | translate\"\n (click)=\"newConversation()\">\n <nh-assistant-icon name=\"plus\" />\n </button>\n }\n <button\n class=\"icon-button\"\n type=\"button\"\n [attr.aria-label]=\"'nh-assistant.panel.close' | translate\"\n [attr.title]=\"'nh-assistant.panel.close' | translate\"\n (click)=\"panel.close()\">\n <nh-assistant-icon name=\"close\" />\n </button>\n </div>\n </header>\n\n @if (store.enabled()) {\n <nh-assistant-activity [items]=\"store.activity()\" (conversationSelect)=\"openConversation($event)\" />\n }\n\n <div class=\"body\">\n @if (store.statusLoading() && !store.enabled()) {\n <div class=\"state\" role=\"status\">\n <p class=\"state-title\">{{ 'nh-assistant.panel.loading' | translate }}</p>\n </div>\n } @else if (!store.enabled()) {\n <div class=\"state\">\n <nh-assistant-icon class=\"state-icon\" name=\"warning\" />\n <p class=\"state-title\">{{ 'nh-assistant.panel.unavailable-title' | translate }}</p>\n <p>{{ 'nh-assistant.panel.unavailable-description' | translate }}</p>\n </div>\n } @else {\n @if (showPreferences()) {\n <nh-assistant-preferences class=\"overlay-view\" (closed)=\"showPreferences.set(false)\" />\n }\n\n @if (showShare()) {\n <nh-assistant-share class=\"overlay-view\" (closed)=\"showShare.set(false)\" />\n }\n\n @if (showConversations()) {\n <nh-assistant-conversation-list\n class=\"overlay-view\"\n [conversations]=\"store.conversations()\"\n [total]=\"store.conversationsTotal()\"\n [activeConversationId]=\"conversation()?.id ?? null\"\n [loading]=\"store.conversationsLoading()\"\n (conversationSelect)=\"openConversation($event)\"\n (conversationDelete)=\"deleteConversation($event)\"\n (conversationCreate)=\"newConversation()\" />\n }\n\n @if (store.conversationLoading()) {\n <div class=\"state\" role=\"status\">\n <p class=\"state-title\">{{ 'nh-assistant.thread.loading' | translate }}</p>\n </div>\n } @else if (messages().length === 0 && !store.streaming()) {\n <div class=\"state\">\n <nh-assistant-icon class=\"state-icon\" name=\"assistant\" />\n <p class=\"state-title\">{{ 'nh-assistant.panel.empty-title' | translate }}</p>\n <p>{{ 'nh-assistant.panel.empty-description' | translate }}</p>\n </div>\n } @else {\n <nh-assistant-thread\n class=\"thread\"\n [messages]=\"messages()\"\n [streaming]=\"busy()\"\n [deciding]=\"store.deciding()\"\n [markdown]=\"markdown\"\n [members]=\"store.members()\"\n [currentActorId]=\"conversation()?.currentActorId ?? null\"\n [canDecide]=\"store.canDecide()\"\n [decisionOwner]=\"activeMemberName()\"\n (approvalDecision)=\"decide($event)\" />\n }\n }\n </div>\n\n @if (store.error(); as error) {\n <div class=\"error\" role=\"alert\">\n <nh-assistant-icon name=\"warning\" />\n <p>{{ errorKeys() | nhAssistantTranslate: error.code }}</p>\n <button\n class=\"icon-button\"\n type=\"button\"\n [attr.aria-label]=\"'nh-assistant.panel.dismiss-error' | translate\"\n [attr.title]=\"'nh-assistant.panel.dismiss-error' | translate\"\n (click)=\"store.clearError()\">\n <nh-assistant-icon name=\"close\" />\n </button>\n </div>\n } @else if (store.notice(); as notice) {\n <div class=\"turn-notice\" role=\"status\">\n <nh-assistant-icon name=\"warning\" />\n <p>{{ noticeKeys() | nhAssistantTranslate: notice.code }}</p>\n <button\n class=\"icon-button\"\n type=\"button\"\n [attr.aria-label]=\"'nh-assistant.panel.dismiss-error' | translate\"\n [attr.title]=\"'nh-assistant.panel.dismiss-error' | translate\"\n (click)=\"store.clearNotice()\">\n <nh-assistant-icon name=\"close\" />\n </button>\n </div>\n }\n\n @if (store.enabled() && !showPreferences() && !showShare()) {\n <footer class=\"footer\" (focusin)=\"refreshPageContext()\">\n @if (closed()) {\n <p class=\"notice\">{{ 'nh-assistant.panel.conversation-closed' | translate }}</p>\n }\n @if (store.pageContext(); as context) {\n <div class=\"context-chip\" [class.excluded]=\"store.pageContextExcluded()\" [attr.title]=\"context.route\">\n <nh-assistant-icon name=\"page\" />\n <span class=\"context-text\">\n {{ (store.pageContextExcluded() ? 'nh-assistant.page-context.excluded' : 'nh-assistant.page-context.included') | translate: { page: describeContext(context) } }}\n </span>\n <button\n class=\"icon-button small\"\n type=\"button\"\n [attr.aria-pressed]=\"store.pageContextExcluded()\"\n [attr.aria-label]=\"(store.pageContextExcluded() ? 'nh-assistant.page-context.include' : 'nh-assistant.page-context.exclude') | translate: { page: describeContext(context) }\"\n [attr.title]=\"(store.pageContextExcluded() ? 'nh-assistant.page-context.include' : 'nh-assistant.page-context.exclude') | translate: { page: describeContext(context) }\"\n (click)=\"store.setPageContextExcluded(!store.pageContextExcluded())\">\n <nh-assistant-icon [name]=\"store.pageContextExcluded() ? 'plus' : 'x'\" />\n </button>\n </div>\n }\n <nh-assistant-composer\n [disabled]=\"closed()\"\n [sendDisabled]=\"composerDisabled()\"\n [busy]=\"busy()\"\n [status]=\"composerStatus()\"\n [statusActor]=\"activeMemberName()\"\n [canStop]=\"canStop()\"\n [maxLength]=\"store.limits()?.maxMessageChars ?? null\"\n [restore]=\"store.restoredDraft()\"\n (restored)=\"store.consumeRestoredDraft()\"\n (send)=\"send($event)\"\n (cancel)=\"cancel()\" />\n </footer>\n }\n </section>\n</ng-template>\n", styles: [":host{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){:host{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}.drawer{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){.drawer{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}:host{display:contents}.drawer{position:relative;display:flex;flex-direction:column;box-sizing:border-box;width:100%;height:100%;border-left:1px solid var(--nha-border);color:var(--nha-text);background:var(--nha-background);box-shadow:var(--nha-shadow);font-family:var(--nha-font);font-size:14px}.drawer *,.drawer *:before,.drawer *:after{box-sizing:border-box}.header{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:6px 12px;border-bottom:1px solid var(--nha-border);padding:12px 12px 12px 16px}.heading{display:contents}nh-assistant-agent-picker{grid-column:1/-1;grid-row:2}h2{grid-row:1;min-width:0;margin:0;overflow:hidden;font-size:16px;font-weight:750;letter-spacing:-.01em;text-overflow:ellipsis;white-space:nowrap}.header-actions{display:flex;flex:none;grid-column:2;grid-row:1;gap:4px}.icon-button{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:1px solid transparent;border-radius:var(--nha-radius-control);color:var(--nha-text-muted);background:transparent;cursor:pointer;text-decoration:none}.icon-button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.icon-button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}.icon-button:disabled{cursor:not-allowed;opacity:.5}.icon-button.active{color:var(--nha-highlight);background:var(--nha-surface-strong)}.body{position:relative;display:flex;flex:1;flex-direction:column;min-height:0;background:var(--nha-background)}.thread{flex:1;min-height:0}.overlay-view{position:absolute;z-index:1;inset:0;border-bottom:1px solid var(--nha-border);background:var(--nha-background)}.state{display:flex;flex:1;flex-direction:column;align-items:center;justify-content:center;gap:4px;padding:24px;color:var(--nha-text-muted);text-align:center}.state p{max-width:32ch;margin:0;font-size:13px}.state-icon{margin-bottom:8px;color:var(--nha-highlight);font-size:28px}.state .state-title{color:var(--nha-text);font-size:15px;font-weight:700}.error{display:flex;align-items:center;gap:8px;margin:0 12px 8px;border:1px solid var(--nha-danger);border-radius:var(--nha-radius-control);padding:4px 4px 4px 12px;color:var(--nha-danger);background:var(--nha-danger-soft)}.error p{flex:1;margin:0;color:var(--nha-text);font-size:13px}.turn-notice{display:flex;align-items:center;gap:8px;margin:0 12px 8px;border:1px solid var(--nha-warning);border-radius:var(--nha-radius-control);padding:4px 4px 4px 12px;color:var(--nha-warning);background:var(--nha-warning-soft)}.turn-notice p{flex:1;margin:0;color:var(--nha-text);font-size:13px}.footer{border-top:1px solid var(--nha-border);padding:12px 16px 14px}.context-chip{display:flex;align-items:center;gap:6px;max-width:100%;margin:0 0 8px;border:1px solid var(--nha-border);border-radius:999px;padding:2px 2px 2px 10px;width:fit-content;color:var(--nha-text-muted);background:var(--nha-surface);font-size:12px}.context-chip.excluded .context-text{text-decoration:line-through}.context-text{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.icon-button.small{flex:none;width:26px;height:26px;border-radius:999px}.notice{margin:0 0 8px;color:var(--nha-text-muted);font-size:12px}\n"] }]
|
|
3604
5060
|
}], ctorParameters: () => [], propDecorators: { drawer: [{ type: i0.ViewChild, args: ['drawer', { isSignal: true }] }] } });
|
|
3605
5061
|
|
|
3606
5062
|
/** Converts any failure into a user-facing code and translation key, never raw text. */
|
|
@@ -3625,5 +5081,5 @@ const NH_ASSISTANT_DASH_CASE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
|
3625
5081
|
* Generated bundle index. Do not edit.
|
|
3626
5082
|
*/
|
|
3627
5083
|
|
|
3628
|
-
export { NH_ASSISTANT_ACCESS_POLICY, NH_ASSISTANT_CONFIG, NH_ASSISTANT_FETCH, NH_ASSISTANT_ICONS, NH_ASSISTANT_MAX_CUSTOM_INSTRUCTIONS, NH_ASSISTANT_NO_ANSWER_NOTICE_CODE, NH_ASSISTANT_PAGE_CONTEXT_LIMITS, NH_ASSISTANT_PANEL_MOBILE_QUERY, NH_ASSISTANT_PANEL_WIDTH, NH_ASSISTANT_SSE_EVENT_TYPES, NH_ASSISTANT_TRANSLATIONS, NH_ASSISTANT_VIRTUAL_SCROLL_THRESHOLD, NhAssistantAdminApiService, NhAssistantAgentPickerComponent, NhAssistantAllowAllAccessPolicy, NhAssistantApiError, NhAssistantApiService, NhAssistantApprovalCardComponent, NhAssistantClientErrorCodes, NhAssistantComposerComponent, NhAssistantConversationListComponent, NhAssistantLauncherComponent, NhAssistantPanelComponent, NhAssistantPanelService, NhAssistantPreferencesComponent, NhAssistantSseParser, NhAssistantStore, NhAssistantThreadComponent, NhAssistantToolCallCardComponent, NhAssistantToolCallGroupComponent, NhAssistantTranslationMerger, applyNhAssistantApprovalDecision, applyNhAssistantEvent, describeNhAssistantClientContext, formatNhAssistantCountdown, nhAssistantCodeForStatus, nhAssistantErrorMessageKey, nhAssistantLatestTurnHasText, normalizeNhAssistantClientContext, provideNhAssistant, NH_ASSISTANT_DASH_CASE as ɵNH_ASSISTANT_DASH_CASE, NhAssistantIconComponent as ɵNhAssistantIconComponent, NhAssistantTranslatePipe as ɵNhAssistantTranslatePipe, nhAssistantErrorKeys as ɵnhAssistantErrorKeys, toNhAssistantError as ɵtoNhAssistantError };
|
|
5084
|
+
export { NH_ASSISTANT_ACCESS_POLICY, NH_ASSISTANT_CONFIG, NH_ASSISTANT_CONVERSATION_FRAGMENT, NH_ASSISTANT_FETCH, NH_ASSISTANT_ICONS, NH_ASSISTANT_JOIN_FRAGMENT, NH_ASSISTANT_MAX_CUSTOM_INSTRUCTIONS, NH_ASSISTANT_NO_ANSWER_NOTICE_CODE, NH_ASSISTANT_PAGE_CONTEXT_LIMITS, NH_ASSISTANT_PANEL_MOBILE_QUERY, NH_ASSISTANT_PANEL_WIDTH, NH_ASSISTANT_SSE_EVENT_TYPES, NH_ASSISTANT_TRANSLATIONS, NH_ASSISTANT_VIRTUAL_SCROLL_THRESHOLD, NhAssistantActivityComponent, NhAssistantAdminApiService, NhAssistantAgentPickerComponent, NhAssistantAllowAllAccessPolicy, NhAssistantApiError, NhAssistantApiService, NhAssistantApprovalCardComponent, NhAssistantClientErrorCodes, NhAssistantComposerComponent, NhAssistantConversationListComponent, NhAssistantLauncherComponent, NhAssistantLiveService, NhAssistantPanelComponent, NhAssistantPanelService, NhAssistantPreferencesComponent, NhAssistantPushService, NhAssistantShareComponent, NhAssistantSseParser, NhAssistantStore, NhAssistantThreadComponent, NhAssistantToolCallCardComponent, NhAssistantToolCallGroupComponent, NhAssistantTranslationMerger, applyNhAssistantApprovalDecision, applyNhAssistantConversationChange, applyNhAssistantEvent, applyNhAssistantLiveEvent, describeNhAssistantClientContext, formatNhAssistantCountdown, nhAssistantCodeForStatus, nhAssistantErrorMessageKey, nhAssistantIsUnread, nhAssistantLatestTurnHasText, nhAssistantShareLink, normalizeNhAssistantClientContext, provideNhAssistant, NH_ASSISTANT_DASH_CASE as ɵNH_ASSISTANT_DASH_CASE, NhAssistantIconComponent as ɵNhAssistantIconComponent, NhAssistantTranslatePipe as ɵNhAssistantTranslatePipe, nhAssistantErrorKeys as ɵnhAssistantErrorKeys, toNhAssistantError as ɵtoNhAssistantError };
|
|
3629
5085
|
//# sourceMappingURL=newheap-platform-ai-chat.mjs.map
|