@newheap/platform-ai-chat 0.4.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 +1977 -364
- package/fesm2022/newheap-platform-ai-chat.mjs.map +1 -1
- package/i18n/en.json +92 -9
- package/i18n/nl.json +92 -9
- package/index.d.ts +547 -38
- 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",
|
|
@@ -123,7 +133,9 @@ var en = {
|
|
|
123
133
|
assistant: "Assistant",
|
|
124
134
|
tool: "Tool",
|
|
125
135
|
working: "The assistant is working",
|
|
126
|
-
loading: "Loading conversation"
|
|
136
|
+
loading: "Loading conversation",
|
|
137
|
+
elapsed: "{{seconds}}s",
|
|
138
|
+
participant: "Participant"
|
|
127
139
|
},
|
|
128
140
|
composer: {
|
|
129
141
|
label: "Message",
|
|
@@ -135,7 +147,9 @@ var en = {
|
|
|
135
147
|
"too-long": "The message is too long.",
|
|
136
148
|
status: {
|
|
137
149
|
running: "You can keep typing while the assistant works. Sending is temporarily unavailable.",
|
|
138
|
-
"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…"
|
|
139
153
|
}
|
|
140
154
|
},
|
|
141
155
|
"tool-call": {
|
|
@@ -154,6 +168,16 @@ var en = {
|
|
|
154
168
|
rejected: "Rejected"
|
|
155
169
|
}
|
|
156
170
|
},
|
|
171
|
+
"tool-group": {
|
|
172
|
+
running: "Calling {{name}}…",
|
|
173
|
+
"awaiting-approval": "Waiting for approval: {{name}}",
|
|
174
|
+
"used-one": "Used 1 tool",
|
|
175
|
+
"used-many": "Used {{count}} tools",
|
|
176
|
+
progress: "{{finished}} of {{total}} done",
|
|
177
|
+
failed: "{{count}} failed",
|
|
178
|
+
"show-calls": "Show tool calls",
|
|
179
|
+
"hide-calls": "Hide tool calls"
|
|
180
|
+
},
|
|
157
181
|
approval: {
|
|
158
182
|
title: "Approval required",
|
|
159
183
|
"fallback-summary": "Review this action before it runs.",
|
|
@@ -173,7 +197,9 @@ var en = {
|
|
|
173
197
|
approved: "Approved",
|
|
174
198
|
rejected: "Rejected",
|
|
175
199
|
expired: "Expired"
|
|
176
|
-
}
|
|
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."
|
|
177
203
|
},
|
|
178
204
|
"result-codes": {
|
|
179
205
|
"api-bridge-validation": "The request was not valid.",
|
|
@@ -231,10 +257,21 @@ var en = {
|
|
|
231
257
|
"assistant-tool-call-limit-reached": "The assistant reached the maximum number of steps for one answer. Ask a narrower question.",
|
|
232
258
|
"assistant-tools-disabled": "No further actions were run in this answer because an action was rejected.",
|
|
233
259
|
"assistant-turn-failed": "The assistant could not finish the answer. Try again.",
|
|
234
|
-
"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."
|
|
235
271
|
},
|
|
236
272
|
notices: {
|
|
237
|
-
"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."
|
|
238
275
|
},
|
|
239
276
|
preferences: {
|
|
240
277
|
title: "Your preferences",
|
|
@@ -271,7 +308,16 @@ var en = {
|
|
|
271
308
|
back: "Back to the conversation",
|
|
272
309
|
loading: "Loading your preferences",
|
|
273
310
|
"load-failed": "Your preferences could not be loaded.",
|
|
274
|
-
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
|
+
}
|
|
275
321
|
},
|
|
276
322
|
admin: {
|
|
277
323
|
title: "Assistant administration",
|
|
@@ -456,6 +502,45 @@ var en = {
|
|
|
456
502
|
excluded: "Not sent with this message: {{page}}",
|
|
457
503
|
exclude: "Do not send {{page}} with this message",
|
|
458
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."
|
|
459
544
|
}
|
|
460
545
|
}
|
|
461
546
|
};
|
|
@@ -465,7 +550,9 @@ var nl = {
|
|
|
465
550
|
launcher: {
|
|
466
551
|
open: "Assistent openen",
|
|
467
552
|
close: "Assistent sluiten",
|
|
468
|
-
"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"
|
|
469
556
|
},
|
|
470
557
|
panel: {
|
|
471
558
|
title: "Assistent",
|
|
@@ -483,7 +570,9 @@ var nl = {
|
|
|
483
570
|
"dismiss-error": "Melding sluiten",
|
|
484
571
|
usage: "{{inputTokens}} invoertokens, {{outputTokens}} uitvoertokens, {{toolCalls}} toolaanroepen",
|
|
485
572
|
"open-preferences": "Je assistentvoorkeuren",
|
|
486
|
-
"open-admin": "Beheer van de assistent"
|
|
573
|
+
"open-admin": "Beheer van de assistent",
|
|
574
|
+
share: "Gesprek delen",
|
|
575
|
+
members: "Mensen in dit gesprek"
|
|
487
576
|
},
|
|
488
577
|
"agent-picker": {
|
|
489
578
|
label: "Agent",
|
|
@@ -503,7 +592,11 @@ var nl = {
|
|
|
503
592
|
"waiting-for-approval": "Wacht op goedkeuring",
|
|
504
593
|
failed: "Mislukt",
|
|
505
594
|
archived: "Gearchiveerd"
|
|
506
|
-
}
|
|
595
|
+
},
|
|
596
|
+
leave: "Gesprek verlaten",
|
|
597
|
+
unread: "Ongelezen",
|
|
598
|
+
shared: "Gedeeld met {{count}} mensen",
|
|
599
|
+
"shared-with-you": "Met jou gedeeld"
|
|
507
600
|
},
|
|
508
601
|
thread: {
|
|
509
602
|
label: "Gesprek",
|
|
@@ -511,7 +604,9 @@ var nl = {
|
|
|
511
604
|
assistant: "Assistent",
|
|
512
605
|
tool: "Tool",
|
|
513
606
|
working: "De assistent is bezig",
|
|
514
|
-
loading: "Gesprek laden"
|
|
607
|
+
loading: "Gesprek laden",
|
|
608
|
+
elapsed: "{{seconds}} s",
|
|
609
|
+
participant: "Deelnemer"
|
|
515
610
|
},
|
|
516
611
|
composer: {
|
|
517
612
|
label: "Bericht",
|
|
@@ -523,7 +618,9 @@ var nl = {
|
|
|
523
618
|
"too-long": "Het bericht is te lang.",
|
|
524
619
|
status: {
|
|
525
620
|
running: "Je kunt verder typen terwijl de assistent bezig is. Versturen is tijdelijk niet mogelijk.",
|
|
526
|
-
"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}}…"
|
|
527
624
|
}
|
|
528
625
|
},
|
|
529
626
|
"tool-call": {
|
|
@@ -542,6 +639,16 @@ var nl = {
|
|
|
542
639
|
rejected: "Afgewezen"
|
|
543
640
|
}
|
|
544
641
|
},
|
|
642
|
+
"tool-group": {
|
|
643
|
+
running: "Roept {{name}} aan…",
|
|
644
|
+
"awaiting-approval": "Wacht op goedkeuring: {{name}}",
|
|
645
|
+
"used-one": "1 tool gebruikt",
|
|
646
|
+
"used-many": "{{count}} tools gebruikt",
|
|
647
|
+
progress: "{{finished}} van {{total}} klaar",
|
|
648
|
+
failed: "{{count}} mislukt",
|
|
649
|
+
"show-calls": "Tool-aanroepen tonen",
|
|
650
|
+
"hide-calls": "Tool-aanroepen verbergen"
|
|
651
|
+
},
|
|
545
652
|
approval: {
|
|
546
653
|
title: "Goedkeuring nodig",
|
|
547
654
|
"fallback-summary": "Controleer deze actie voordat ze wordt uitgevoerd.",
|
|
@@ -561,7 +668,9 @@ var nl = {
|
|
|
561
668
|
approved: "Goedgekeurd",
|
|
562
669
|
rejected: "Afgewezen",
|
|
563
670
|
expired: "Verlopen"
|
|
564
|
-
}
|
|
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."
|
|
565
674
|
},
|
|
566
675
|
"result-codes": {
|
|
567
676
|
"api-bridge-validation": "Het verzoek was niet geldig.",
|
|
@@ -619,10 +728,21 @@ var nl = {
|
|
|
619
728
|
"assistant-tool-call-limit-reached": "De assistent heeft het maximale aantal stappen voor één antwoord bereikt. Stel een gerichtere vraag.",
|
|
620
729
|
"assistant-tools-disabled": "In dit antwoord zijn geen verdere acties uitgevoerd omdat een actie is afgewezen.",
|
|
621
730
|
"assistant-turn-failed": "De assistent kon het antwoord niet afmaken. Probeer het opnieuw.",
|
|
622
|
-
"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."
|
|
623
742
|
},
|
|
624
743
|
notices: {
|
|
625
|
-
"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."
|
|
626
746
|
},
|
|
627
747
|
preferences: {
|
|
628
748
|
title: "Je voorkeuren",
|
|
@@ -659,7 +779,16 @@ var nl = {
|
|
|
659
779
|
back: "Terug naar het gesprek",
|
|
660
780
|
loading: "Je voorkeuren laden",
|
|
661
781
|
"load-failed": "Je voorkeuren konden niet worden geladen.",
|
|
662
|
-
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
|
+
}
|
|
663
792
|
},
|
|
664
793
|
admin: {
|
|
665
794
|
title: "Beheer van de assistent",
|
|
@@ -844,6 +973,45 @@ var nl = {
|
|
|
844
973
|
excluded: "Niet meegestuurd bij dit bericht: {{page}}",
|
|
845
974
|
exclude: "{{page}} niet meesturen met dit bericht",
|
|
846
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."
|
|
847
1015
|
}
|
|
848
1016
|
}
|
|
849
1017
|
};
|
|
@@ -909,10 +1077,10 @@ class NhAssistantTranslationMerger {
|
|
|
909
1077
|
const merged = mergeTrees(bundleRoot, isTree(existing) ? existing : {});
|
|
910
1078
|
this.translate?.setTranslation(language, { [rootKey]: merged }, true);
|
|
911
1079
|
}
|
|
912
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
913
|
-
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' }); }
|
|
914
1082
|
}
|
|
915
|
-
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: [{
|
|
916
1084
|
type: Injectable,
|
|
917
1085
|
args: [{ providedIn: 'root' }]
|
|
918
1086
|
}] });
|
|
@@ -1047,10 +1215,10 @@ class NhAssistantTransport {
|
|
|
1047
1215
|
const base = this.config.apiBaseUrl.replace(/\/+$/, '');
|
|
1048
1216
|
return `${base}/${path}`;
|
|
1049
1217
|
}
|
|
1050
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
1051
|
-
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 }); }
|
|
1052
1220
|
}
|
|
1053
|
-
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: [{
|
|
1054
1222
|
type: Injectable
|
|
1055
1223
|
}] });
|
|
1056
1224
|
function createApiError(status, code) {
|
|
@@ -1132,10 +1300,10 @@ class NhAssistantAdminApiService {
|
|
|
1132
1300
|
request(method, path, body) {
|
|
1133
1301
|
return this.transport.json(method, path, body, adminStatusCodes);
|
|
1134
1302
|
}
|
|
1135
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
1136
|
-
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 }); }
|
|
1137
1305
|
}
|
|
1138
|
-
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: [{
|
|
1139
1307
|
type: Injectable
|
|
1140
1308
|
}] });
|
|
1141
1309
|
|
|
@@ -1267,6 +1435,52 @@ class NhAssistantApiService {
|
|
|
1267
1435
|
cancel(conversationId) {
|
|
1268
1436
|
return this.transport.json('POST', `conversations/${encodeURIComponent(conversationId)}/cancel`);
|
|
1269
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
|
+
}
|
|
1270
1484
|
/** The caller's own assistant preferences. */
|
|
1271
1485
|
getPreferences() {
|
|
1272
1486
|
return this.transport.json('GET', 'preferences');
|
|
@@ -1351,13 +1565,386 @@ class NhAssistantApiService {
|
|
|
1351
1565
|
subscriber.next({ type: 'error', data: { code, messageKey: nhAssistantErrorMessageKey(code) } });
|
|
1352
1566
|
subscriber.complete();
|
|
1353
1567
|
}
|
|
1354
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
1355
|
-
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 }); }
|
|
1356
1570
|
}
|
|
1357
|
-
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: [{
|
|
1358
1572
|
type: Injectable
|
|
1359
1573
|
}] });
|
|
1360
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
|
+
|
|
1361
1948
|
/**
|
|
1362
1949
|
* Applies one assistant event to a conversation and returns the new conversation.
|
|
1363
1950
|
* The input is never mutated, so the result can be stored in a signal directly.
|
|
@@ -1469,6 +2056,36 @@ function applyNhAssistantApprovalDecision(conversation, approvalId, decision) {
|
|
|
1469
2056
|
}
|
|
1470
2057
|
return updated;
|
|
1471
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
|
+
}
|
|
1472
2089
|
/**
|
|
1473
2090
|
* True when an assistant message after the latest user message contains non-blank text,
|
|
1474
2091
|
* so the latest turn produced a visible answer. Tool calls and approvals alone do not count.
|
|
@@ -1730,10 +2347,10 @@ class NhAssistantUiState {
|
|
|
1730
2347
|
return { ...emptyState };
|
|
1731
2348
|
}
|
|
1732
2349
|
}
|
|
1733
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
1734
|
-
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 }); }
|
|
1735
2352
|
}
|
|
1736
|
-
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: [{
|
|
1737
2354
|
type: Injectable
|
|
1738
2355
|
}] });
|
|
1739
2356
|
function validId(value) {
|
|
@@ -1742,11 +2359,23 @@ function validId(value) {
|
|
|
1742
2359
|
|
|
1743
2360
|
/** Notice code for a completed turn that produced no answer text and no server code. */
|
|
1744
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
|
+
}
|
|
1745
2369
|
const conversationPageSize = 50;
|
|
1746
2370
|
const cancelGracePeriodMs = 5_000;
|
|
2371
|
+
const maxSessions = 20;
|
|
2372
|
+
const refreshDelayMs = 300;
|
|
2373
|
+
const newConversationKey = '';
|
|
1747
2374
|
/**
|
|
1748
|
-
* Signal-based state of one assistant scope: availability, agents, the conversation list
|
|
1749
|
-
*
|
|
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.
|
|
1750
2379
|
*/
|
|
1751
2380
|
class NhAssistantStore {
|
|
1752
2381
|
constructor() {
|
|
@@ -1755,6 +2384,10 @@ class NhAssistantStore {
|
|
|
1755
2384
|
this.injector = inject(EnvironmentInjector);
|
|
1756
2385
|
this.accessPolicy = inject(NH_ASSISTANT_ACCESS_POLICY);
|
|
1757
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));
|
|
1758
2391
|
this.accessGrantedState = signal(null, ...(ngDevMode ? [{ debugName: "accessGrantedState" }] : []));
|
|
1759
2392
|
this.statusState = signal(null, ...(ngDevMode ? [{ debugName: "statusState" }] : []));
|
|
1760
2393
|
this.statusLoadingState = signal(false, ...(ngDevMode ? [{ debugName: "statusLoadingState" }] : []));
|
|
@@ -1762,19 +2395,30 @@ class NhAssistantStore {
|
|
|
1762
2395
|
this.conversationsState = signal([], ...(ngDevMode ? [{ debugName: "conversationsState" }] : []));
|
|
1763
2396
|
this.conversationsTotalState = signal(0, ...(ngDevMode ? [{ debugName: "conversationsTotalState" }] : []));
|
|
1764
2397
|
this.conversationsLoadingState = signal(false, ...(ngDevMode ? [{ debugName: "conversationsLoadingState" }] : []));
|
|
1765
|
-
this.
|
|
2398
|
+
this.sessionsState = signal(new Map(), ...(ngDevMode ? [{ debugName: "sessionsState" }] : []));
|
|
2399
|
+
this.activeIdState = signal(null, ...(ngDevMode ? [{ debugName: "activeIdState" }] : []));
|
|
1766
2400
|
this.conversationLoadingState = signal(false, ...(ngDevMode ? [{ debugName: "conversationLoadingState" }] : []));
|
|
1767
|
-
|
|
1768
|
-
this.
|
|
1769
|
-
this.
|
|
1770
|
-
this.noticeState = signal(null, ...(ngDevMode ? [{ debugName: "noticeState" }] : []));
|
|
1771
|
-
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" }] : []));
|
|
1772
2404
|
this.restoredDraftState = signal(null, ...(ngDevMode ? [{ debugName: "restoredDraftState" }] : []));
|
|
1773
2405
|
this.pageContextState = signal(null, ...(ngDevMode ? [{ debugName: "pageContextState" }] : []));
|
|
1774
2406
|
this.pageContextExcludedState = signal(false, ...(ngDevMode ? [{ debugName: "pageContextExcludedState" }] : []));
|
|
1775
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();
|
|
1776
2415
|
this.statusRevision = 0;
|
|
1777
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" }] : []));
|
|
1778
2422
|
/** True when the access policy allows the user and the server reports the assistant as enabled. */
|
|
1779
2423
|
this.enabled = computed(() => this.accessGrantedState() === true && this.statusState()?.enabled === true, ...(ngDevMode ? [{ debugName: "enabled" }] : []));
|
|
1780
2424
|
this.accessGranted = this.accessGrantedState.asReadonly();
|
|
@@ -1783,20 +2427,28 @@ class NhAssistantStore {
|
|
|
1783
2427
|
this.statusLoading = this.statusLoadingState.asReadonly();
|
|
1784
2428
|
this.limits = computed(() => this.statusState()?.limits ?? null, ...(ngDevMode ? [{ debugName: "limits" }] : []));
|
|
1785
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" }] : []));
|
|
1786
2432
|
this.selectedAgentId = this.selectedAgentIdState.asReadonly();
|
|
1787
2433
|
this.selectedAgent = computed(() => this.agents().find(agent => agent.id === this.selectedAgentIdState()) ?? null, ...(ngDevMode ? [{ debugName: "selectedAgent" }] : []));
|
|
1788
2434
|
this.conversations = this.conversationsState.asReadonly();
|
|
1789
2435
|
this.conversationsTotal = this.conversationsTotalState.asReadonly();
|
|
1790
2436
|
this.conversationsLoading = this.conversationsLoadingState.asReadonly();
|
|
1791
|
-
|
|
2437
|
+
/** The conversation open in the panel, or `null` for a new one. */
|
|
2438
|
+
this.activeConversation = computed(() => this.activeSession()?.conversation ?? null, ...(ngDevMode ? [{ debugName: "activeConversation" }] : []));
|
|
1792
2439
|
this.conversationLoading = this.conversationLoadingState.asReadonly();
|
|
1793
|
-
this
|
|
1794
|
-
this.
|
|
1795
|
-
|
|
1796
|
-
|
|
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" }] : []));
|
|
1797
2449
|
/** Non-blocking remark about the latest completed turn; shown only while no error is shown. */
|
|
1798
|
-
this.notice = this.
|
|
1799
|
-
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" }] : []));
|
|
1800
2452
|
/** Text of a message the server did not accept, so the composer can offer it again. */
|
|
1801
2453
|
this.restoredDraft = this.restoredDraftState.asReadonly();
|
|
1802
2454
|
/** Page context that the next message would send, for the transparency chip. */
|
|
@@ -1807,14 +2459,94 @@ class NhAssistantStore {
|
|
|
1807
2459
|
this.restorePanelOpen = this.restorePanelOpenState.asReadonly();
|
|
1808
2460
|
/** True while the user can send: enabled, an agent is chosen and no turn runs or waits. */
|
|
1809
2461
|
this.canSend = computed(() => {
|
|
1810
|
-
const conversation = this.
|
|
2462
|
+
const conversation = this.activeConversation();
|
|
1811
2463
|
const blocked = conversation !== null && conversation.status !== 'idle';
|
|
1812
|
-
return this.enabled() && this.selectedAgentIdState() !== null && !this.
|
|
2464
|
+
return this.enabled() && this.selectedAgentIdState() !== null && !this.streaming() && !this.conversationLoadingState() && !blocked;
|
|
1813
2465
|
}, ...(ngDevMode ? [{ debugName: "canSend" }] : []));
|
|
1814
|
-
|
|
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(() => {
|
|
1815
2513
|
this.accessSubscription?.unsubscribe();
|
|
1816
|
-
this.
|
|
1817
|
-
|
|
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());
|
|
1818
2550
|
});
|
|
1819
2551
|
}
|
|
1820
2552
|
/** Evaluates the access policy and loads the server status once. Safe to call repeatedly. */
|
|
@@ -1826,21 +2558,9 @@ class NhAssistantStore {
|
|
|
1826
2558
|
async reloadStatus() {
|
|
1827
2559
|
const revision = ++this.statusRevision;
|
|
1828
2560
|
if (this.accessGrantedState() !== true) {
|
|
1829
|
-
this.
|
|
1830
|
-
this.stopLocalTurn();
|
|
2561
|
+
this.resetAccount();
|
|
1831
2562
|
this.statusState.set(null);
|
|
1832
|
-
this.activeConversationState.set(null);
|
|
1833
2563
|
this.selectedAgentIdState.set(null);
|
|
1834
|
-
this.conversationsState.set([]);
|
|
1835
|
-
this.conversationsTotalState.set(0);
|
|
1836
|
-
this.conversationLoadingState.set(false);
|
|
1837
|
-
this.conversationsLoadingState.set(false);
|
|
1838
|
-
this.pageContextState.set(null);
|
|
1839
|
-
this.pageContextExcludedState.set(false);
|
|
1840
|
-
this.restoredDraftState.set(null);
|
|
1841
|
-
this.errorState.set(null);
|
|
1842
|
-
this.noticeState.set(null);
|
|
1843
|
-
this.lastUsageState.set(null);
|
|
1844
2564
|
this.restorePanelOpenState.set(false);
|
|
1845
2565
|
this.uiState.deactivate();
|
|
1846
2566
|
return;
|
|
@@ -1852,20 +2572,8 @@ class NhAssistantStore {
|
|
|
1852
2572
|
return;
|
|
1853
2573
|
}
|
|
1854
2574
|
if (restored.changed) {
|
|
1855
|
-
this.
|
|
1856
|
-
this.stopLocalTurn();
|
|
2575
|
+
this.resetAccount();
|
|
1857
2576
|
this.statusState.set(null);
|
|
1858
|
-
this.activeConversationState.set(null);
|
|
1859
|
-
this.conversationsState.set([]);
|
|
1860
|
-
this.conversationsTotalState.set(0);
|
|
1861
|
-
this.conversationLoadingState.set(false);
|
|
1862
|
-
this.conversationsLoadingState.set(false);
|
|
1863
|
-
this.pageContextState.set(null);
|
|
1864
|
-
this.pageContextExcludedState.set(false);
|
|
1865
|
-
this.restoredDraftState.set(null);
|
|
1866
|
-
this.errorState.set(null);
|
|
1867
|
-
this.noticeState.set(null);
|
|
1868
|
-
this.lastUsageState.set(null);
|
|
1869
2577
|
this.selectedAgentIdState.set(restored.state.agentId);
|
|
1870
2578
|
this.restorePanelOpenState.set(restored.state.panelOpen);
|
|
1871
2579
|
}
|
|
@@ -1876,7 +2584,8 @@ class NhAssistantStore {
|
|
|
1876
2584
|
this.statusState.set(status);
|
|
1877
2585
|
if (status.enabled) {
|
|
1878
2586
|
this.selectInitialAgent(status.agents);
|
|
1879
|
-
|
|
2587
|
+
this.startCollaboration(status);
|
|
2588
|
+
if (this.activeIdState() === null && restored.state.conversationId) {
|
|
1880
2589
|
await this.loadConversation(restored.state.conversationId, true);
|
|
1881
2590
|
}
|
|
1882
2591
|
}
|
|
@@ -1902,13 +2611,13 @@ class NhAssistantStore {
|
|
|
1902
2611
|
try {
|
|
1903
2612
|
const page = await firstValueFrom(this.api.listConversations(1, conversationPageSize));
|
|
1904
2613
|
if (revision === this.accountRevision) {
|
|
1905
|
-
this.conversationsState.set(page.items);
|
|
2614
|
+
this.conversationsState.set(page.items.map(item => this.withLocalState(item)));
|
|
1906
2615
|
this.conversationsTotalState.set(page.total);
|
|
1907
2616
|
}
|
|
1908
2617
|
}
|
|
1909
2618
|
catch (error) {
|
|
1910
2619
|
if (revision === this.accountRevision) {
|
|
1911
|
-
this.setError(error);
|
|
2620
|
+
this.setError(this.activeIdState(), error);
|
|
1912
2621
|
}
|
|
1913
2622
|
}
|
|
1914
2623
|
finally {
|
|
@@ -1917,72 +2626,37 @@ class NhAssistantStore {
|
|
|
1917
2626
|
}
|
|
1918
2627
|
}
|
|
1919
2628
|
}
|
|
1920
|
-
/** 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. */
|
|
1921
2630
|
selectAgent(agentId) {
|
|
1922
2631
|
if (!this.agents().some(agent => agent.id === agentId)) {
|
|
1923
2632
|
return;
|
|
1924
2633
|
}
|
|
1925
2634
|
this.selectedAgentIdState.set(agentId);
|
|
1926
2635
|
this.uiState.update({ agentId });
|
|
1927
|
-
const active = this.
|
|
1928
|
-
if (active && active.agentId !== agentId
|
|
2636
|
+
const active = this.activeConversation();
|
|
2637
|
+
if (active && active.agentId !== agentId) {
|
|
1929
2638
|
this.startNewConversation();
|
|
1930
2639
|
}
|
|
1931
2640
|
}
|
|
1932
|
-
/**
|
|
2641
|
+
/**
|
|
2642
|
+
* Leaves the open conversation; the next message creates a new one. Turns of other
|
|
2643
|
+
* conversations keep running in the background.
|
|
2644
|
+
*/
|
|
1933
2645
|
startNewConversation() {
|
|
1934
|
-
|
|
1935
|
-
return;
|
|
1936
|
-
}
|
|
1937
|
-
this.activeConversationState.set(null);
|
|
2646
|
+
this.activeIdState.set(null);
|
|
1938
2647
|
this.uiState.update({ conversationId: null });
|
|
1939
|
-
this.
|
|
1940
|
-
this.noticeState.set(null);
|
|
1941
|
-
this.lastUsageState.set(null);
|
|
2648
|
+
this.draftErrorState.set(null);
|
|
1942
2649
|
}
|
|
2650
|
+
/** Opens a conversation; one that this tab already follows opens immediately. */
|
|
1943
2651
|
async openConversation(conversationId) {
|
|
1944
2652
|
await this.loadConversation(conversationId, false);
|
|
1945
2653
|
}
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
}
|
|
1950
|
-
const revision = this.accountRevision;
|
|
1951
|
-
this.conversationLoadingState.set(true);
|
|
1952
|
-
this.errorState.set(null);
|
|
1953
|
-
this.noticeState.set(null);
|
|
1954
|
-
try {
|
|
1955
|
-
const conversation = await firstValueFrom(this.api.getConversation(conversationId));
|
|
1956
|
-
if (revision !== this.accountRevision) {
|
|
1957
|
-
return;
|
|
1958
|
-
}
|
|
1959
|
-
if (!this.agents().some(agent => agent.id === conversation.agentId)) {
|
|
1960
|
-
this.uiState.update({ conversationId: null });
|
|
1961
|
-
return;
|
|
1962
|
-
}
|
|
1963
|
-
this.activeConversationState.set(conversation);
|
|
1964
|
-
this.selectedAgentIdState.set(conversation.agentId);
|
|
1965
|
-
this.uiState.update({ conversationId, agentId: conversation.agentId });
|
|
1966
|
-
}
|
|
1967
|
-
catch (error) {
|
|
1968
|
-
if (revision !== this.accountRevision) {
|
|
1969
|
-
return;
|
|
1970
|
-
}
|
|
1971
|
-
if (error instanceof NhAssistantApiError && (error.status === 403 || error.status === 404)) {
|
|
1972
|
-
this.uiState.update({ conversationId: null });
|
|
1973
|
-
}
|
|
1974
|
-
if (!restoring) {
|
|
1975
|
-
this.setError(error);
|
|
1976
|
-
}
|
|
1977
|
-
}
|
|
1978
|
-
finally {
|
|
1979
|
-
if (revision === this.accountRevision) {
|
|
1980
|
-
this.conversationLoadingState.set(false);
|
|
1981
|
-
}
|
|
1982
|
-
}
|
|
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);
|
|
1983
2657
|
}
|
|
1984
2658
|
async deleteConversation(conversationId) {
|
|
1985
|
-
if (this.
|
|
2659
|
+
if (this.sessionsState().get(conversationId)?.streaming) {
|
|
1986
2660
|
return;
|
|
1987
2661
|
}
|
|
1988
2662
|
const revision = this.accountRevision;
|
|
@@ -1991,26 +2665,22 @@ class NhAssistantStore {
|
|
|
1991
2665
|
if (revision !== this.accountRevision) {
|
|
1992
2666
|
return;
|
|
1993
2667
|
}
|
|
1994
|
-
this.
|
|
1995
|
-
this.conversationsTotalState.update(total => Math.max(0, total - 1));
|
|
1996
|
-
if (this.activeConversationState()?.id === conversationId) {
|
|
1997
|
-
this.activeConversationState.set(null);
|
|
1998
|
-
this.noticeState.set(null);
|
|
1999
|
-
this.uiState.update({ conversationId: null });
|
|
2000
|
-
}
|
|
2668
|
+
this.forget(conversationId);
|
|
2001
2669
|
}
|
|
2002
2670
|
catch (error) {
|
|
2003
2671
|
if (revision === this.accountRevision) {
|
|
2004
|
-
this.setError(error);
|
|
2672
|
+
this.setError(this.activeIdState(), error);
|
|
2005
2673
|
}
|
|
2006
2674
|
}
|
|
2007
2675
|
}
|
|
2008
2676
|
/**
|
|
2009
|
-
* Sends a user message. Creates a conversation with the selected agent when none is
|
|
2010
|
-
*
|
|
2011
|
-
* 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.
|
|
2012
2680
|
*/
|
|
2013
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);
|
|
2014
2684
|
const trimmed = text.trim();
|
|
2015
2685
|
await this.initialize();
|
|
2016
2686
|
await this.reloadStatus();
|
|
@@ -2018,51 +2688,65 @@ class NhAssistantStore {
|
|
|
2018
2688
|
if (trimmed.length === 0 || trimmed.length > maxChars || !this.canSend()) {
|
|
2019
2689
|
return false;
|
|
2020
2690
|
}
|
|
2021
|
-
this.errorState.set(null);
|
|
2022
|
-
this.noticeState.set(null);
|
|
2023
|
-
this.restoredDraftState.set(null);
|
|
2024
|
-
this.streamingState.set(true);
|
|
2025
2691
|
const accountRevision = this.accountRevision;
|
|
2026
|
-
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);
|
|
2027
2696
|
if (!conversation) {
|
|
2028
2697
|
conversation = await this.createConversation();
|
|
2029
2698
|
if (!conversation) {
|
|
2030
2699
|
if (accountRevision === this.accountRevision) {
|
|
2031
|
-
this.
|
|
2700
|
+
this.sendingState.set(null);
|
|
2032
2701
|
this.restoredDraftState.set(text);
|
|
2033
2702
|
}
|
|
2034
2703
|
return false;
|
|
2035
2704
|
}
|
|
2705
|
+
this.sendingState.set(conversation.id);
|
|
2036
2706
|
}
|
|
2037
2707
|
const clientContext = await this.contextForMessage();
|
|
2038
2708
|
if (accountRevision !== this.accountRevision) {
|
|
2039
2709
|
return false;
|
|
2040
2710
|
}
|
|
2711
|
+
const conversationId = conversation.id;
|
|
2041
2712
|
const clientMessageId = createClientMessageId();
|
|
2042
2713
|
const previousStatus = conversation.status;
|
|
2043
|
-
this.
|
|
2044
|
-
...
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
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() });
|
|
2052
2734
|
return new Promise(resolve => {
|
|
2053
|
-
this.
|
|
2735
|
+
this.pendingSendResolves.set(conversationId, resolve);
|
|
2054
2736
|
let started = false;
|
|
2055
2737
|
let failedAfterStart = false;
|
|
2056
|
-
this.runStream(this.api.sendMessage(conversationId, {
|
|
2738
|
+
this.runStream(conversationId, this.api.sendMessage(conversationId, {
|
|
2057
2739
|
text: trimmed,
|
|
2058
2740
|
clientMessageId,
|
|
2059
2741
|
...(clientContext === undefined ? {} : { clientContext })
|
|
2060
2742
|
}), {
|
|
2061
2743
|
next: event => {
|
|
2062
2744
|
if (event.type === 'error' && !started) {
|
|
2063
|
-
this.removeMessage(clientMessageId, previousStatus);
|
|
2064
|
-
this.
|
|
2065
|
-
|
|
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 }));
|
|
2066
2750
|
resolve(false);
|
|
2067
2751
|
return;
|
|
2068
2752
|
}
|
|
@@ -2073,114 +2757,290 @@ class NhAssistantStore {
|
|
|
2073
2757
|
if (event.type === 'error') {
|
|
2074
2758
|
failedAfterStart = true;
|
|
2075
2759
|
}
|
|
2076
|
-
this.applyEvent(event, clientMessageId);
|
|
2760
|
+
this.applyEvent(conversationId, event, clientMessageId);
|
|
2077
2761
|
},
|
|
2078
2762
|
complete: () => {
|
|
2079
|
-
this.
|
|
2763
|
+
this.pendingSendResolves.delete(conversationId);
|
|
2080
2764
|
resolve(started);
|
|
2081
2765
|
this.afterTurn(conversationId, failedAfterStart);
|
|
2082
2766
|
}
|
|
2083
2767
|
});
|
|
2768
|
+
this.sendingState.set(null);
|
|
2084
2769
|
});
|
|
2085
2770
|
}
|
|
2086
2771
|
/** Approves or rejects the pending approval. Ignores repeated calls while a decision is in flight. */
|
|
2087
2772
|
decide(decision, reason) {
|
|
2088
|
-
const
|
|
2773
|
+
const session = this.activeSession();
|
|
2774
|
+
const conversation = session?.conversation;
|
|
2089
2775
|
const approval = conversation?.pendingApproval ?? null;
|
|
2090
|
-
if (!conversation || !approval ||
|
|
2776
|
+
if (!session || !conversation || !approval || session.deciding || session.streaming || !this.canDecide()) {
|
|
2091
2777
|
return;
|
|
2092
2778
|
}
|
|
2093
|
-
|
|
2094
|
-
this.
|
|
2095
|
-
this.errorState.set(null);
|
|
2096
|
-
this.noticeState.set(null);
|
|
2779
|
+
const conversationId = conversation.id;
|
|
2780
|
+
this.patchSession(conversationId, current => ({ ...current, deciding: true, error: null, notice: null }));
|
|
2097
2781
|
let accepted = false;
|
|
2098
2782
|
let failed = false;
|
|
2099
2783
|
const request = { decision, expectedProposalHash: approval.proposalHash, ...(reason ? { reason } : {}) };
|
|
2100
|
-
this.runStream(this.api.decideApproval(
|
|
2784
|
+
this.runStream(conversationId, this.api.decideApproval(conversationId, approval.approvalId, request), {
|
|
2101
2785
|
next: event => {
|
|
2102
2786
|
if (!accepted && event.type !== 'error') {
|
|
2103
2787
|
accepted = true;
|
|
2104
|
-
this.
|
|
2788
|
+
this.patchSession(conversationId, current => ({
|
|
2789
|
+
...current,
|
|
2790
|
+
conversation: applyNhAssistantApprovalDecision(current.conversation, approval.approvalId, decision)
|
|
2791
|
+
}));
|
|
2105
2792
|
}
|
|
2106
2793
|
if (event.type === 'error') {
|
|
2107
2794
|
failed = true;
|
|
2108
2795
|
if (!accepted) {
|
|
2109
|
-
this.
|
|
2796
|
+
this.patchSession(conversationId, current => ({ ...current, error: event.data }));
|
|
2110
2797
|
return;
|
|
2111
2798
|
}
|
|
2112
2799
|
}
|
|
2113
|
-
this.applyEvent(event);
|
|
2800
|
+
this.applyEvent(conversationId, event);
|
|
2114
2801
|
},
|
|
2115
2802
|
complete: () => {
|
|
2116
|
-
this.
|
|
2117
|
-
this.afterTurn(
|
|
2803
|
+
this.patchSession(conversationId, current => ({ ...current, deciding: false }));
|
|
2804
|
+
this.afterTurn(conversationId, failed);
|
|
2118
2805
|
}
|
|
2119
2806
|
});
|
|
2120
2807
|
}
|
|
2121
|
-
/** 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. */
|
|
2122
2809
|
async cancel() {
|
|
2123
|
-
const
|
|
2124
|
-
|
|
2810
|
+
const session = this.activeSession();
|
|
2811
|
+
const conversation = session?.conversation;
|
|
2812
|
+
if (!session || !conversation || (conversation.status === 'idle' && !session.streaming)) {
|
|
2125
2813
|
return;
|
|
2126
2814
|
}
|
|
2815
|
+
const conversationId = conversation.id;
|
|
2127
2816
|
const revision = this.accountRevision;
|
|
2128
2817
|
try {
|
|
2129
|
-
await firstValueFrom(this.api.cancel(
|
|
2818
|
+
await firstValueFrom(this.api.cancel(conversationId), { defaultValue: undefined });
|
|
2130
2819
|
}
|
|
2131
2820
|
catch (error) {
|
|
2132
2821
|
if (revision === this.accountRevision) {
|
|
2133
|
-
this.setError(error);
|
|
2822
|
+
this.setError(conversationId, error);
|
|
2134
2823
|
}
|
|
2135
2824
|
return;
|
|
2136
2825
|
}
|
|
2137
2826
|
if (revision !== this.accountRevision) {
|
|
2138
2827
|
return;
|
|
2139
2828
|
}
|
|
2140
|
-
if (!this.
|
|
2141
|
-
await this.
|
|
2829
|
+
if (!this.sessionsState().get(conversationId)?.streaming) {
|
|
2830
|
+
await this.reloadSession(conversationId);
|
|
2142
2831
|
return;
|
|
2143
2832
|
}
|
|
2144
2833
|
// The server ends the stream with turn.completed(cancelled); stop waiting if it does not.
|
|
2145
|
-
clearTimeout(this.
|
|
2146
|
-
this.
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
this.
|
|
2150
|
-
this.
|
|
2151
|
-
|
|
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);
|
|
2152
2842
|
}
|
|
2153
|
-
}, cancelGracePeriodMs);
|
|
2154
|
-
}
|
|
2155
|
-
clearError() {
|
|
2156
|
-
this.errorState.set(null);
|
|
2157
|
-
}
|
|
2158
|
-
clearNotice() {
|
|
2159
|
-
this.noticeState.set(null);
|
|
2160
|
-
}
|
|
2161
|
-
/** Persists only drawer visibility, never the page context or a message draft. */
|
|
2162
|
-
setPanelOpen(open) {
|
|
2163
|
-
this.restorePanelOpenState.set(open);
|
|
2164
|
-
this.uiState.update({ panelOpen: open });
|
|
2843
|
+
}, cancelGracePeriodMs));
|
|
2165
2844
|
}
|
|
2166
|
-
/**
|
|
2167
|
-
async
|
|
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();
|
|
2168
2848
|
const revision = this.accountRevision;
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
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;
|
|
2172
2865
|
}
|
|
2173
2866
|
}
|
|
2174
|
-
/**
|
|
2175
|
-
|
|
2176
|
-
this.
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
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);
|
|
2951
|
+
}
|
|
2952
|
+
return true;
|
|
2953
|
+
}
|
|
2954
|
+
catch (error) {
|
|
2955
|
+
this.setError(conversation.id, error);
|
|
2956
|
+
return false;
|
|
2957
|
+
}
|
|
2958
|
+
}
|
|
2959
|
+
clearError() {
|
|
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 }));
|
|
2966
|
+
}
|
|
2967
|
+
clearNotice() {
|
|
2968
|
+
const activeId = this.activeIdState();
|
|
2969
|
+
if (activeId !== null) {
|
|
2970
|
+
this.patchSession(activeId, session => ({ ...session, notice: null }));
|
|
2971
|
+
}
|
|
2972
|
+
}
|
|
2973
|
+
/** Persists only drawer visibility, never the page context or a message draft. */
|
|
2974
|
+
setPanelOpen(open) {
|
|
2975
|
+
this.restorePanelOpenState.set(open);
|
|
2976
|
+
this.uiState.update({ panelOpen: open });
|
|
2977
|
+
}
|
|
2978
|
+
/** Reads the host's page context again so the chip shows what the next message sends. */
|
|
2979
|
+
async refreshPageContext() {
|
|
2980
|
+
const revision = this.accountRevision;
|
|
2981
|
+
const context = await this.readPageContext();
|
|
2982
|
+
if (revision === this.accountRevision) {
|
|
2983
|
+
this.pageContextState.set(context ?? null);
|
|
2984
|
+
}
|
|
2985
|
+
}
|
|
2986
|
+
/** Leaves the page context out of the next message only, or includes it again. */
|
|
2987
|
+
setPageContextExcluded(excluded) {
|
|
2988
|
+
this.pageContextExcludedState.set(excluded);
|
|
2989
|
+
}
|
|
2990
|
+
/** Returns and clears the draft of a message that was not accepted. */
|
|
2991
|
+
consumeRestoredDraft() {
|
|
2992
|
+
const draft = this.restoredDraftState();
|
|
2181
2993
|
this.restoredDraftState.set(null);
|
|
2182
2994
|
return draft;
|
|
2183
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
|
+
}
|
|
2184
3044
|
/**
|
|
2185
3045
|
* Page context for one message: `undefined` omits the field (no getter, getter failed or
|
|
2186
3046
|
* invalid shape), `null` sends an explicit "none" (no context or left out by the user).
|
|
@@ -2238,6 +3098,37 @@ class NhAssistantStore {
|
|
|
2238
3098
|
});
|
|
2239
3099
|
});
|
|
2240
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
|
+
}
|
|
2241
3132
|
async createConversation() {
|
|
2242
3133
|
const agentId = this.selectedAgentIdState();
|
|
2243
3134
|
if (!agentId) {
|
|
@@ -2249,108 +3140,305 @@ class NhAssistantStore {
|
|
|
2249
3140
|
if (revision !== this.accountRevision) {
|
|
2250
3141
|
return null;
|
|
2251
3142
|
}
|
|
2252
|
-
this.
|
|
2253
|
-
this.
|
|
3143
|
+
this.upsertSession(conversation);
|
|
3144
|
+
this.setActive(conversation.id);
|
|
2254
3145
|
this.conversationsState.update(items => [toSummary(conversation), ...items.filter(item => item.id !== conversation.id)]);
|
|
2255
3146
|
this.conversationsTotalState.update(total => total + 1);
|
|
2256
3147
|
return conversation;
|
|
2257
3148
|
}
|
|
2258
3149
|
catch (error) {
|
|
2259
3150
|
if (revision === this.accountRevision) {
|
|
2260
|
-
this.setError(error);
|
|
3151
|
+
this.setError(null, error);
|
|
2261
3152
|
}
|
|
2262
3153
|
return null;
|
|
2263
3154
|
}
|
|
2264
3155
|
}
|
|
2265
|
-
runStream(stream, handlers) {
|
|
2266
|
-
this.
|
|
2267
|
-
this.
|
|
3156
|
+
runStream(conversationId, stream, handlers) {
|
|
3157
|
+
this.streams.get(conversationId)?.unsubscribe();
|
|
3158
|
+
this.patchSession(conversationId, session => ({ ...session, streaming: true }));
|
|
2268
3159
|
const finish = () => {
|
|
2269
|
-
clearTimeout(this.
|
|
2270
|
-
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 }));
|
|
2271
3164
|
handlers.complete();
|
|
2272
3165
|
};
|
|
2273
|
-
|
|
3166
|
+
const subscription = stream.subscribe({
|
|
2274
3167
|
next: event => handlers.next(event),
|
|
2275
3168
|
error: () => {
|
|
2276
|
-
this.
|
|
3169
|
+
this.patchSession(conversationId, session => ({ ...session, error: clientError(NhAssistantClientErrorCodes.network) }));
|
|
2277
3170
|
finish();
|
|
2278
3171
|
},
|
|
2279
3172
|
complete: finish
|
|
2280
3173
|
});
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
this.streamSubscription?.unsubscribe();
|
|
2284
|
-
this.pendingSendResolve?.(false);
|
|
2285
|
-
this.pendingSendResolve = undefined;
|
|
2286
|
-
clearTimeout(this.cancelTimer);
|
|
2287
|
-
this.streamingState.set(false);
|
|
2288
|
-
this.decidingState.set(false);
|
|
2289
|
-
}
|
|
2290
|
-
applyEvent(event, clientMessageId) {
|
|
2291
|
-
this.updateActive(conversation => applyNhAssistantEvent(conversation, event, clientMessageId));
|
|
2292
|
-
if (event.type === 'error') {
|
|
2293
|
-
this.errorState.set(event.data);
|
|
2294
|
-
}
|
|
2295
|
-
if (event.type === 'turn.completed') {
|
|
2296
|
-
this.lastUsageState.set(event.data.usage);
|
|
2297
|
-
if (event.data.status === 'failed') {
|
|
2298
|
-
const code = event.data.errorCode ?? NhAssistantClientErrorCodes.server;
|
|
2299
|
-
this.errorState.set(clientError(code));
|
|
2300
|
-
}
|
|
2301
|
-
else if (event.data.status === 'completed') {
|
|
2302
|
-
this.noticeState.set(this.completionNotice(event.data.errorCode));
|
|
2303
|
-
}
|
|
3174
|
+
if (!subscription.closed) {
|
|
3175
|
+
this.streams.set(conversationId, subscription);
|
|
2304
3176
|
}
|
|
2305
3177
|
}
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
return clientError(errorCode);
|
|
3178
|
+
stopAllTurns() {
|
|
3179
|
+
for (const subscription of this.streams.values()) {
|
|
3180
|
+
subscription.unsubscribe();
|
|
2310
3181
|
}
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
3182
|
+
this.streams.clear();
|
|
3183
|
+
for (const resolve of this.pendingSendResolves.values()) {
|
|
3184
|
+
resolve(false);
|
|
2314
3185
|
}
|
|
2315
|
-
|
|
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
|
+
});
|
|
2316
3210
|
}
|
|
2317
3211
|
afterTurn(conversationId, reload) {
|
|
2318
|
-
const
|
|
2319
|
-
if (
|
|
2320
|
-
|
|
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
|
+
}));
|
|
2321
3221
|
}
|
|
2322
3222
|
if (reload) {
|
|
2323
|
-
void this.
|
|
2324
|
-
}
|
|
2325
|
-
if (!active?.title) {
|
|
2326
|
-
void this.refreshConversations();
|
|
3223
|
+
void this.reloadSession(conversationId);
|
|
2327
3224
|
}
|
|
3225
|
+
// The list carries the exact title, latest message and read position of the finished turn.
|
|
3226
|
+
void this.refreshConversations();
|
|
2328
3227
|
}
|
|
2329
|
-
async
|
|
3228
|
+
async reloadSession(conversationId) {
|
|
2330
3229
|
const revision = this.accountRevision;
|
|
2331
3230
|
try {
|
|
2332
3231
|
const conversation = await firstValueFrom(this.api.getConversation(conversationId));
|
|
2333
|
-
|
|
2334
|
-
|
|
3232
|
+
const session = this.sessionsState().get(conversationId);
|
|
3233
|
+
if (revision === this.accountRevision && session && !session.streaming) {
|
|
3234
|
+
this.upsertSession(conversation);
|
|
2335
3235
|
}
|
|
2336
3236
|
}
|
|
3237
|
+
catch (error) {
|
|
3238
|
+
if (error instanceof NhAssistantApiError && (error.status === 403 || error.status === 404)) {
|
|
3239
|
+
this.forget(conversationId);
|
|
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);
|
|
3259
|
+
}
|
|
2337
3260
|
catch {
|
|
2338
|
-
|
|
3261
|
+
this.markingRead.delete(conversationId);
|
|
3262
|
+
return;
|
|
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));
|
|
3272
|
+
}
|
|
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);
|
|
2339
3304
|
}
|
|
2340
3305
|
}
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
3306
|
+
onLiveRead(read) {
|
|
3307
|
+
this.patchSummary(read.conversationId, summary => ({
|
|
3308
|
+
...summary,
|
|
3309
|
+
lastReadSequence: Math.max(summary.lastReadSequence ?? 0, read.lastReadSequence)
|
|
3310
|
+
}));
|
|
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));
|
|
2345
3317
|
}
|
|
2346
3318
|
}
|
|
2347
|
-
|
|
2348
|
-
this.
|
|
2349
|
-
|
|
2350
|
-
status,
|
|
2351
|
-
|
|
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)
|
|
2352
3331
|
}));
|
|
2353
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
|
+
}
|
|
2354
3442
|
selectInitialAgent(agents) {
|
|
2355
3443
|
const current = this.selectedAgentIdState();
|
|
2356
3444
|
if (current && agents.some(agent => agent.id === current)) {
|
|
@@ -2360,22 +3448,36 @@ class NhAssistantStore {
|
|
|
2360
3448
|
this.selectedAgentIdState.set(preferred?.id ?? null);
|
|
2361
3449
|
this.uiState.update({ agentId: preferred?.id ?? null });
|
|
2362
3450
|
}
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
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);
|
|
2366
3458
|
return;
|
|
2367
3459
|
}
|
|
2368
|
-
this.
|
|
3460
|
+
this.patchSession(conversationId, session => ({ ...session, error: value }));
|
|
2369
3461
|
}
|
|
2370
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
2371
|
-
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 }); }
|
|
2372
3464
|
}
|
|
2373
|
-
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: [{
|
|
2374
3466
|
type: Injectable
|
|
2375
3467
|
}], ctorParameters: () => [] });
|
|
2376
3468
|
function clientError(code) {
|
|
2377
3469
|
return { code, messageKey: nhAssistantErrorMessageKey(code) };
|
|
2378
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
|
+
}
|
|
2379
3481
|
function toSummary(conversation) {
|
|
2380
3482
|
return {
|
|
2381
3483
|
id: conversation.id,
|
|
@@ -2383,7 +3485,12 @@ function toSummary(conversation) {
|
|
|
2383
3485
|
title: conversation.title,
|
|
2384
3486
|
status: conversation.status,
|
|
2385
3487
|
createdAt: conversation.createdAt,
|
|
2386
|
-
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
|
|
2387
3494
|
};
|
|
2388
3495
|
}
|
|
2389
3496
|
function createClientMessageId() {
|
|
@@ -2547,10 +3654,10 @@ class NhAssistantPanelService {
|
|
|
2547
3654
|
this.overlayRef?.dispose();
|
|
2548
3655
|
this.overlayRef = undefined;
|
|
2549
3656
|
}
|
|
2550
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
2551
|
-
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 }); }
|
|
2552
3659
|
}
|
|
2553
|
-
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: [{
|
|
2554
3661
|
type: Injectable
|
|
2555
3662
|
}], ctorParameters: () => [] });
|
|
2556
3663
|
|
|
@@ -2566,6 +3673,8 @@ function provideNhAssistant(config) {
|
|
|
2566
3673
|
NhAssistantTransport,
|
|
2567
3674
|
NhAssistantApiService,
|
|
2568
3675
|
NhAssistantAdminApiService,
|
|
3676
|
+
NhAssistantLiveService,
|
|
3677
|
+
NhAssistantPushService,
|
|
2569
3678
|
NhAssistantStore,
|
|
2570
3679
|
NhAssistantUiState,
|
|
2571
3680
|
NhAssistantPanelService,
|
|
@@ -2575,6 +3684,17 @@ function provideNhAssistant(config) {
|
|
|
2575
3684
|
]);
|
|
2576
3685
|
}
|
|
2577
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
|
+
|
|
2578
3698
|
/**
|
|
2579
3699
|
* Optional icon classes of the host's icon library, for example `{ close: 'ph ph-x' }`.
|
|
2580
3700
|
* Icons without a class use the library's small built-in outline glyphs.
|
|
@@ -2599,7 +3719,11 @@ const builtInPaths = {
|
|
|
2599
3719
|
edit: 'M4 20h4L19 9l-4-4L4 16zM13.5 6.5l4 4',
|
|
2600
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',
|
|
2601
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',
|
|
2602
|
-
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'
|
|
2603
3727
|
};
|
|
2604
3728
|
/** Decorative icon. The surrounding control provides the accessible name. */
|
|
2605
3729
|
class NhAssistantIconComponent {
|
|
@@ -2609,8 +3733,8 @@ class NhAssistantIconComponent {
|
|
|
2609
3733
|
this.iconClass = computed(() => this.icons?.[this.name()] ?? null, ...(ngDevMode ? [{ debugName: "iconClass" }] : []));
|
|
2610
3734
|
this.path = computed(() => builtInPaths[this.name()], ...(ngDevMode ? [{ debugName: "path" }] : []));
|
|
2611
3735
|
}
|
|
2612
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
2613
|
-
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: `
|
|
2614
3738
|
@if (iconClass(); as iconClass) {
|
|
2615
3739
|
<i [class]="iconClass" aria-hidden="true"></i>
|
|
2616
3740
|
} @else {
|
|
@@ -2620,7 +3744,7 @@ class NhAssistantIconComponent {
|
|
|
2620
3744
|
}
|
|
2621
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 }); }
|
|
2622
3746
|
}
|
|
2623
|
-
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: [{
|
|
2624
3748
|
type: Component,
|
|
2625
3749
|
args: [{ selector: 'nh-assistant-icon', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
2626
3750
|
@if (iconClass(); as iconClass) {
|
|
@@ -2635,8 +3759,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImpo
|
|
|
2635
3759
|
|
|
2636
3760
|
/**
|
|
2637
3761
|
* Header button that opens the assistant panel. It renders nothing while the assistant is
|
|
2638
|
-
* disabled on the server or the access policy denies the user.
|
|
2639
|
-
* 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.
|
|
2640
3765
|
*/
|
|
2641
3766
|
class NhAssistantLauncherComponent {
|
|
2642
3767
|
constructor() {
|
|
@@ -2644,6 +3769,16 @@ class NhAssistantLauncherComponent {
|
|
|
2644
3769
|
this.panel = inject(NhAssistantPanelService);
|
|
2645
3770
|
this.waitingForApproval = computed(() => this.store.pendingApproval() !== null ||
|
|
2646
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" }] : []));
|
|
2647
3782
|
this.label = computed(() => this.panel.isOpen() ? 'nh-assistant.launcher.close' : 'nh-assistant.launcher.open', ...(ngDevMode ? [{ debugName: "label" }] : []));
|
|
2648
3783
|
// Browser only: the status request and access policy need the signed-in user.
|
|
2649
3784
|
afterNextRender(() => {
|
|
@@ -2655,8 +3790,8 @@ class NhAssistantLauncherComponent {
|
|
|
2655
3790
|
}
|
|
2656
3791
|
});
|
|
2657
3792
|
}
|
|
2658
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
2659
|
-
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: `
|
|
2660
3795
|
@if (store.enabled()) {
|
|
2661
3796
|
<button
|
|
2662
3797
|
class="launcher"
|
|
@@ -2667,15 +3802,31 @@ class NhAssistantLauncherComponent {
|
|
|
2667
3802
|
[attr.title]="label() | translate"
|
|
2668
3803
|
(click)="panel.toggle()">
|
|
2669
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
|
+
}
|
|
2670
3816
|
@if (waitingForApproval()) {
|
|
2671
|
-
<span class="badge" aria-hidden="true"></span>
|
|
2672
3817
|
<span class="visually-hidden">{{ 'nh-assistant.launcher.pending-approval' | translate }}</span>
|
|
2673
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
|
+
}
|
|
2674
3825
|
</button>
|
|
2675
3826
|
}
|
|
2676
|
-
`, 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 }); }
|
|
2677
3828
|
}
|
|
2678
|
-
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: [{
|
|
2679
3830
|
type: Component,
|
|
2680
3831
|
args: [{ selector: 'nh-assistant-launcher', standalone: true, imports: [TranslatePipe, NhAssistantIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
2681
3832
|
@if (store.enabled()) {
|
|
@@ -2688,13 +3839,29 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImpo
|
|
|
2688
3839
|
[attr.title]="label() | translate"
|
|
2689
3840
|
(click)="panel.toggle()">
|
|
2690
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
|
+
}
|
|
2691
3853
|
@if (waitingForApproval()) {
|
|
2692
|
-
<span class="badge" aria-hidden="true"></span>
|
|
2693
3854
|
<span class="visually-hidden">{{ 'nh-assistant.launcher.pending-approval' | translate }}</span>
|
|
2694
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
|
+
}
|
|
2695
3862
|
</button>
|
|
2696
3863
|
}
|
|
2697
|
-
`, 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"] }]
|
|
2698
3865
|
}], ctorParameters: () => [] });
|
|
2699
3866
|
|
|
2700
3867
|
/**
|
|
@@ -2738,15 +3905,81 @@ class NhAssistantTranslatePipe {
|
|
|
2738
3905
|
}
|
|
2739
3906
|
return fallback;
|
|
2740
3907
|
}
|
|
2741
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
2742
|
-
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 }); }
|
|
2743
3910
|
}
|
|
2744
|
-
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: [{
|
|
2745
3912
|
type: Pipe,
|
|
2746
3913
|
args: [{ name: 'nhAssistantTranslate', standalone: true, pure: false }]
|
|
2747
3914
|
}], ctorParameters: () => [] });
|
|
2748
3915
|
|
|
2749
|
-
|
|
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;
|
|
2750
3983
|
/**
|
|
2751
3984
|
* Chooses the agent for new conversations. Agent names come from the host's translation
|
|
2752
3985
|
* keys; agents created by an administrator carry literal text, which is shown as is.
|
|
@@ -2757,14 +3990,14 @@ class NhAssistantAgentPickerComponent {
|
|
|
2757
3990
|
this.selectedAgentId = input(null, ...(ngDevMode ? [{ debugName: "selectedAgentId" }] : []));
|
|
2758
3991
|
this.disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
|
|
2759
3992
|
this.agentChange = output();
|
|
2760
|
-
this.selectId = `nh-assistant-agent-picker-${nextId$
|
|
3993
|
+
this.selectId = `nh-assistant-agent-picker-${nextId$6++}`;
|
|
2761
3994
|
this.selected = computed(() => this.agents().find(agent => agent.id === this.selectedAgentId()) ?? null, ...(ngDevMode ? [{ debugName: "selected" }] : []));
|
|
2762
3995
|
}
|
|
2763
3996
|
onChange(event) {
|
|
2764
3997
|
this.agentChange.emit(event.target.value);
|
|
2765
3998
|
}
|
|
2766
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
2767
|
-
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: `
|
|
2768
4001
|
<label class="visually-hidden" [for]="selectId">{{ 'nh-assistant.agent-picker.label' | translate }}</label>
|
|
2769
4002
|
<select
|
|
2770
4003
|
[id]="selectId"
|
|
@@ -2784,7 +4017,7 @@ class NhAssistantAgentPickerComponent {
|
|
|
2784
4017
|
}
|
|
2785
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 }); }
|
|
2786
4019
|
}
|
|
2787
|
-
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: [{
|
|
2788
4021
|
type: Component,
|
|
2789
4022
|
args: [{ selector: 'nh-assistant-agent-picker', standalone: true, imports: [TranslatePipe, NhAssistantTranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
2790
4023
|
<label class="visually-hidden" [for]="selectId">{{ 'nh-assistant.agent-picker.label' | translate }}</label>
|
|
@@ -2807,7 +4040,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImpo
|
|
|
2807
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"] }]
|
|
2808
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"] }] } });
|
|
2809
4042
|
|
|
2810
|
-
let nextId$
|
|
4043
|
+
let nextId$5 = 0;
|
|
2811
4044
|
/**
|
|
2812
4045
|
* Message input. Enter sends when sending is available; Shift+Enter inserts a new line.
|
|
2813
4046
|
* Drafting remains available while the assistant runs or waits for approval.
|
|
@@ -2822,6 +4055,10 @@ class NhAssistantComposerComponent {
|
|
|
2822
4055
|
this.busy = input(false, ...(ngDevMode ? [{ debugName: "busy" }] : []));
|
|
2823
4056
|
/** Announces why sending is temporarily unavailable. */
|
|
2824
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" }] : []));
|
|
2825
4062
|
this.maxLength = input(null, ...(ngDevMode ? [{ debugName: "maxLength" }] : []));
|
|
2826
4063
|
/** Text to put back into the input, for example a message the server did not accept. */
|
|
2827
4064
|
this.restore = input(null, ...(ngDevMode ? [{ debugName: "restore" }] : []));
|
|
@@ -2832,7 +4069,7 @@ class NhAssistantComposerComponent {
|
|
|
2832
4069
|
this.textarea = viewChild.required('textarea');
|
|
2833
4070
|
this.ownedFocus = false;
|
|
2834
4071
|
this.previousBusy = false;
|
|
2835
|
-
this.inputId = `nh-assistant-composer-${nextId$
|
|
4072
|
+
this.inputId = `nh-assistant-composer-${nextId$5++}`;
|
|
2836
4073
|
this.hintId = `${this.inputId}-hint`;
|
|
2837
4074
|
this.text = signal('', ...(ngDevMode ? [{ debugName: "text" }] : []));
|
|
2838
4075
|
this.length = computed(() => this.text().trim().length, ...(ngDevMode ? [{ debugName: "length" }] : []));
|
|
@@ -2903,13 +4140,13 @@ class NhAssistantComposerComponent {
|
|
|
2903
4140
|
this.textarea().nativeElement.focus({ preventScroll: true });
|
|
2904
4141
|
}
|
|
2905
4142
|
}
|
|
2906
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
2907
|
-
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 }); }
|
|
2908
4145
|
}
|
|
2909
|
-
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: [{
|
|
2910
4147
|
type: Component,
|
|
2911
|
-
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"] }]
|
|
2912
|
-
}], 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: [{
|
|
2913
4150
|
type: HostListener,
|
|
2914
4151
|
args: ['focusin']
|
|
2915
4152
|
}], onFocusOut: [{
|
|
@@ -2917,7 +4154,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImpo
|
|
|
2917
4154
|
args: ['focusout', ['$event']]
|
|
2918
4155
|
}] } });
|
|
2919
4156
|
|
|
2920
|
-
/**
|
|
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
|
+
*/
|
|
2921
4162
|
class NhAssistantConversationListComponent {
|
|
2922
4163
|
constructor() {
|
|
2923
4164
|
this.conversations = input.required(...(ngDevMode ? [{ debugName: "conversations" }] : []));
|
|
@@ -2926,33 +4167,48 @@ class NhAssistantConversationListComponent {
|
|
|
2926
4167
|
this.loading = input(false, ...(ngDevMode ? [{ debugName: "loading" }] : []));
|
|
2927
4168
|
this.disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
|
|
2928
4169
|
this.conversationSelect = output();
|
|
4170
|
+
/** The owner deletes the conversation; a participant leaves it. */
|
|
2929
4171
|
this.conversationDelete = output();
|
|
2930
4172
|
this.conversationCreate = output();
|
|
2931
4173
|
}
|
|
2932
|
-
|
|
2933
|
-
|
|
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 }); }
|
|
2934
4188
|
}
|
|
2935
|
-
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: [{
|
|
2936
4190
|
type: Component,
|
|
2937
|
-
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"] }]
|
|
2938
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"] }] } });
|
|
2939
4193
|
|
|
2940
4194
|
/** Maximum length of the user's own instructions. */
|
|
2941
4195
|
const NH_ASSISTANT_MAX_CUSTOM_INSTRUCTIONS = 1_000;
|
|
2942
|
-
let nextId$
|
|
4196
|
+
let nextId$4 = 0;
|
|
2943
4197
|
/**
|
|
2944
|
-
* The user's own assistant preferences: style, form of address,
|
|
2945
|
-
* optional instructions.
|
|
2946
|
-
* 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.
|
|
2947
4202
|
*/
|
|
2948
4203
|
class NhAssistantPreferencesComponent {
|
|
2949
4204
|
constructor() {
|
|
2950
4205
|
this.api = inject(NhAssistantApiService);
|
|
4206
|
+
this.push = inject(NhAssistantPushService);
|
|
2951
4207
|
/** Emitted when the user leaves the preferences. */
|
|
2952
4208
|
this.closed = output();
|
|
2953
4209
|
/** Emitted with the stored preferences after a successful save. */
|
|
2954
4210
|
this.saved = output();
|
|
2955
|
-
this.id = `nh-assistant-preferences-${nextId$
|
|
4211
|
+
this.id = `nh-assistant-preferences-${nextId$4++}`;
|
|
2956
4212
|
this.styles = ['default', 'direct', 'personal', 'detailed'];
|
|
2957
4213
|
this.addressForms = ['informal', 'formal'];
|
|
2958
4214
|
this.responseLengths = ['short', 'normal', 'long'];
|
|
@@ -2997,6 +4253,19 @@ class NhAssistantPreferencesComponent {
|
|
|
2997
4253
|
}
|
|
2998
4254
|
});
|
|
2999
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
|
+
}
|
|
3000
4269
|
onInstructionsInput(event) {
|
|
3001
4270
|
this.customInstructions.set(event.target.value);
|
|
3002
4271
|
this.savedMessage.set(false);
|
|
@@ -3042,14 +4311,137 @@ class NhAssistantPreferencesComponent {
|
|
|
3042
4311
|
this.responseLength.set(preferences.responseLength);
|
|
3043
4312
|
this.customInstructions.set(preferences.customInstructions ?? '');
|
|
3044
4313
|
}
|
|
3045
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
3046
|
-
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 }); }
|
|
3047
4316
|
}
|
|
3048
|
-
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: [{
|
|
3049
4318
|
type: Component,
|
|
3050
|
-
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"] }]
|
|
3051
4320
|
}], ctorParameters: () => [], propDecorators: { closed: [{ type: i0.Output, args: ["closed"] }], saved: [{ type: i0.Output, args: ["saved"] }] } });
|
|
3052
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
|
+
|
|
3053
4445
|
const allowedTags = [
|
|
3054
4446
|
'p', 'br', 'strong', 'em', 'del', 'code', 'pre', 'blockquote', 'ul', 'ol', 'li', 'a',
|
|
3055
4447
|
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'table', 'thead', 'tbody', 'tr', 'th', 'td'
|
|
@@ -3122,10 +4514,10 @@ class NhAssistantMarkdownRenderer {
|
|
|
3122
4514
|
this.purifier = purifier;
|
|
3123
4515
|
return purifier;
|
|
3124
4516
|
}
|
|
3125
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
3126
|
-
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' }); }
|
|
3127
4519
|
}
|
|
3128
|
-
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: [{
|
|
3129
4521
|
type: Injectable,
|
|
3130
4522
|
args: [{ providedIn: 'root' }]
|
|
3131
4523
|
}] });
|
|
@@ -3138,14 +4530,54 @@ class NhAssistantMarkdownPipe {
|
|
|
3138
4530
|
transform(text) {
|
|
3139
4531
|
return this.renderer.render(text);
|
|
3140
4532
|
}
|
|
3141
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
3142
|
-
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" }); }
|
|
3143
4535
|
}
|
|
3144
|
-
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: [{
|
|
3145
4537
|
type: Pipe,
|
|
3146
4538
|
args: [{ name: 'nhAssistantMarkdown', standalone: true }]
|
|
3147
4539
|
}] });
|
|
3148
4540
|
|
|
4541
|
+
/**
|
|
4542
|
+
* Groups consecutive tool calls of a message. Text and approvals break a group, so an
|
|
4543
|
+
* approval always stays visible. A group's key is its first invocation id, so it keeps
|
|
4544
|
+
* its identity while later calls of the same run are appended.
|
|
4545
|
+
*/
|
|
4546
|
+
function groupNhAssistantParts(parts) {
|
|
4547
|
+
const groups = [];
|
|
4548
|
+
let tools = null;
|
|
4549
|
+
parts.forEach((part, index) => {
|
|
4550
|
+
if (part.type === 'tool-call') {
|
|
4551
|
+
if (!tools) {
|
|
4552
|
+
tools = { kind: 'tools', key: `tools:${part.invocationId}`, calls: [] };
|
|
4553
|
+
groups.push(tools);
|
|
4554
|
+
}
|
|
4555
|
+
tools.calls.push(part);
|
|
4556
|
+
return;
|
|
4557
|
+
}
|
|
4558
|
+
tools = null;
|
|
4559
|
+
if (part.type === 'approval') {
|
|
4560
|
+
groups.push({ kind: 'approval', key: `approval:${part.approvalId}`, part });
|
|
4561
|
+
}
|
|
4562
|
+
else {
|
|
4563
|
+
groups.push({ kind: 'text', key: `text:${index}`, part });
|
|
4564
|
+
}
|
|
4565
|
+
});
|
|
4566
|
+
return groups;
|
|
4567
|
+
}
|
|
4568
|
+
/** Pure, so the groups are only rebuilt when the reducer replaces a message's parts. */
|
|
4569
|
+
class NhAssistantPartGroupsPipe {
|
|
4570
|
+
transform(parts) {
|
|
4571
|
+
return groupNhAssistantParts(parts);
|
|
4572
|
+
}
|
|
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" }); }
|
|
4575
|
+
}
|
|
4576
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantPartGroupsPipe, decorators: [{
|
|
4577
|
+
type: Pipe,
|
|
4578
|
+
args: [{ name: 'nhAssistantPartGroups', standalone: true }]
|
|
4579
|
+
}] });
|
|
4580
|
+
|
|
3149
4581
|
/** Formats remaining milliseconds as `m:ss`, or `h:mm:ss` above one hour. */
|
|
3150
4582
|
function formatNhAssistantCountdown(milliseconds) {
|
|
3151
4583
|
const totalSeconds = Math.max(0, Math.ceil(milliseconds / 1000));
|
|
@@ -3167,6 +4599,13 @@ class NhAssistantApprovalCardComponent {
|
|
|
3167
4599
|
this.approval = input.required(...(ngDevMode ? [{ debugName: "approval" }] : []));
|
|
3168
4600
|
/** True while the host submits a decision; disables both buttons. */
|
|
3169
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" }] : []));
|
|
3170
4609
|
this.decide = output();
|
|
3171
4610
|
this.now = signal(Date.now(), ...(ngDevMode ? [{ debugName: "now" }] : []));
|
|
3172
4611
|
this.submitted = signal(false, ...(ngDevMode ? [{ debugName: "submitted" }] : []));
|
|
@@ -3175,7 +4614,7 @@ class NhAssistantApprovalCardComponent {
|
|
|
3175
4614
|
(this.approval().status === 'pending' && this.remainingMs() <= 0), ...(ngDevMode ? [{ debugName: "expired" }] : []));
|
|
3176
4615
|
this.effectiveStatus = computed(() => this.expired() ? 'expired' : this.approval().status, ...(ngDevMode ? [{ debugName: "effectiveStatus" }] : []));
|
|
3177
4616
|
this.actionable = computed(() => this.effectiveStatus() === 'pending', ...(ngDevMode ? [{ debugName: "actionable" }] : []));
|
|
3178
|
-
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" }] : []));
|
|
3179
4618
|
this.countdown = computed(() => formatNhAssistantCountdown(this.remainingMs()), ...(ngDevMode ? [{ debugName: "countdown" }] : []));
|
|
3180
4619
|
this.presentedSummary = computed(() => this.approval().presentation?.summary ?? null, ...(ngDevMode ? [{ debugName: "presentedSummary" }] : []));
|
|
3181
4620
|
// Tick only while the approval is pending and not yet expired.
|
|
@@ -3206,17 +4645,17 @@ class NhAssistantApprovalCardComponent {
|
|
|
3206
4645
|
this.submitted.set(true);
|
|
3207
4646
|
this.decide.emit(decision);
|
|
3208
4647
|
}
|
|
3209
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
3210
|
-
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 }); }
|
|
3211
4650
|
}
|
|
3212
|
-
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: [{
|
|
3213
4652
|
type: Component,
|
|
3214
4653
|
args: [{ selector: 'nh-assistant-approval-card', standalone: true, imports: [TranslatePipe, NhAssistantIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, host: {
|
|
3215
4654
|
'[attr.data-status]': 'effectiveStatus()'
|
|
3216
|
-
}, 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"] }]
|
|
3217
|
-
}], 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"] }] } });
|
|
3218
4657
|
|
|
3219
|
-
let nextId$
|
|
4658
|
+
let nextId$2 = 0;
|
|
3220
4659
|
const statusIcons = {
|
|
3221
4660
|
running: 'tool',
|
|
3222
4661
|
succeeded: 'check',
|
|
@@ -3228,7 +4667,7 @@ const statusIcons = {
|
|
|
3228
4667
|
class NhAssistantToolCallCardComponent {
|
|
3229
4668
|
constructor() {
|
|
3230
4669
|
this.part = input.required(...(ngDevMode ? [{ debugName: "part" }] : []));
|
|
3231
|
-
this.detailsId = `nh-assistant-tool-call-${nextId$
|
|
4670
|
+
this.detailsId = `nh-assistant-tool-call-${nextId$2++}`;
|
|
3232
4671
|
this.expanded = signal(false, ...(ngDevMode ? [{ debugName: "expanded" }] : []));
|
|
3233
4672
|
this.statusIcon = computed(() => statusIcons[this.part().status], ...(ngDevMode ? [{ debugName: "statusIcon" }] : []));
|
|
3234
4673
|
this.hasDetails = computed(() => !!this.part().toolId, ...(ngDevMode ? [{ debugName: "hasDetails" }] : []));
|
|
@@ -3236,24 +4675,85 @@ class NhAssistantToolCallCardComponent {
|
|
|
3236
4675
|
toggle() {
|
|
3237
4676
|
this.expanded.update(expanded => !expanded);
|
|
3238
4677
|
}
|
|
3239
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
3240
|
-
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 }); }
|
|
3241
4680
|
}
|
|
3242
|
-
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: [{
|
|
3243
4682
|
type: Component,
|
|
3244
4683
|
args: [{ selector: 'nh-assistant-tool-call-card', standalone: true, imports: [TranslatePipe, NhAssistantTranslatePipe, NhAssistantIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, host: {
|
|
3245
4684
|
'[attr.data-status]': 'part().status'
|
|
3246
4685
|
}, 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"] }]
|
|
3247
4686
|
}], propDecorators: { part: [{ type: i0.Input, args: [{ isSignal: true, alias: "part", required: true }] }] } });
|
|
3248
4687
|
|
|
4688
|
+
let nextId$1 = 0;
|
|
4689
|
+
const stateIcons = {
|
|
4690
|
+
'awaiting-approval': 'clock',
|
|
4691
|
+
running: 'tool',
|
|
4692
|
+
failed: 'warning',
|
|
4693
|
+
succeeded: 'check'
|
|
4694
|
+
};
|
|
4695
|
+
/**
|
|
4696
|
+
* Summarizes consecutive tool calls in one collapsed line that counts along live: the
|
|
4697
|
+
* tool that runs now while the assistant works, and how many tools were used and failed
|
|
4698
|
+
* afterwards. Expanding it shows every call as a card.
|
|
4699
|
+
*/
|
|
4700
|
+
class NhAssistantToolCallGroupComponent {
|
|
4701
|
+
constructor() {
|
|
4702
|
+
this.calls = input.required(...(ngDevMode ? [{ debugName: "calls" }] : []));
|
|
4703
|
+
this.callsId = `nh-assistant-tool-call-group-${nextId$1++}`;
|
|
4704
|
+
this.expanded = signal(false, ...(ngDevMode ? [{ debugName: "expanded" }] : []));
|
|
4705
|
+
/** The call that currently needs attention or runs; the latest one when several do. */
|
|
4706
|
+
this.current = computed(() => {
|
|
4707
|
+
const calls = this.calls();
|
|
4708
|
+
return findLast(calls, call => call.status === 'awaiting-approval')
|
|
4709
|
+
?? findLast(calls, call => call.status === 'running')
|
|
4710
|
+
?? null;
|
|
4711
|
+
}, ...(ngDevMode ? [{ debugName: "current" }] : []));
|
|
4712
|
+
this.total = computed(() => this.calls().length, ...(ngDevMode ? [{ debugName: "total" }] : []));
|
|
4713
|
+
this.finished = computed(() => this.calls().filter(call => !isActive(call)).length, ...(ngDevMode ? [{ debugName: "finished" }] : []));
|
|
4714
|
+
this.failed = computed(() => this.calls().filter(call => call.status === 'failed').length, ...(ngDevMode ? [{ debugName: "failed" }] : []));
|
|
4715
|
+
this.state = computed(() => {
|
|
4716
|
+
const current = this.current();
|
|
4717
|
+
if (current) {
|
|
4718
|
+
return current.status === 'awaiting-approval' ? 'awaiting-approval' : 'running';
|
|
4719
|
+
}
|
|
4720
|
+
return this.failed() > 0 ? 'failed' : 'succeeded';
|
|
4721
|
+
}, ...(ngDevMode ? [{ debugName: "state" }] : []));
|
|
4722
|
+
this.stateIcon = computed(() => stateIcons[this.state()], ...(ngDevMode ? [{ debugName: "stateIcon" }] : []));
|
|
4723
|
+
}
|
|
4724
|
+
toggle() {
|
|
4725
|
+
this.expanded.update(expanded => !expanded);
|
|
4726
|
+
}
|
|
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 }); }
|
|
4729
|
+
}
|
|
4730
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantToolCallGroupComponent, decorators: [{
|
|
4731
|
+
type: Component,
|
|
4732
|
+
args: [{ selector: 'nh-assistant-tool-call-group', standalone: true, imports: [TranslatePipe, NhAssistantIconComponent, NhAssistantToolCallCardComponent], changeDetection: ChangeDetectionStrategy.OnPush, host: {
|
|
4733
|
+
'[attr.data-status]': 'state()'
|
|
4734
|
+
}, 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"] }]
|
|
4735
|
+
}], propDecorators: { calls: [{ type: i0.Input, args: [{ isSignal: true, alias: "calls", required: true }] }] } });
|
|
4736
|
+
function isActive(call) {
|
|
4737
|
+
return call.status === 'running' || call.status === 'awaiting-approval';
|
|
4738
|
+
}
|
|
4739
|
+
function findLast(calls, predicate) {
|
|
4740
|
+
for (let index = calls.length - 1; index >= 0; index--) {
|
|
4741
|
+
if (predicate(calls[index])) {
|
|
4742
|
+
return calls[index];
|
|
4743
|
+
}
|
|
4744
|
+
}
|
|
4745
|
+
return undefined;
|
|
4746
|
+
}
|
|
4747
|
+
|
|
3249
4748
|
/** Above this number of messages the thread renders through CDK virtual scrolling. */
|
|
3250
4749
|
const NH_ASSISTANT_VIRTUAL_SCROLL_THRESHOLD = 200;
|
|
3251
4750
|
/** Distance from the bottom, in pixels, within which new content keeps the thread pinned to the end. */
|
|
3252
4751
|
const stickToBottomDistance = 120;
|
|
3253
4752
|
/**
|
|
3254
4753
|
* The messages of a conversation as a live log. Assistant text renders as sanitized
|
|
3255
|
-
* Markdown, tool calls
|
|
3256
|
-
*
|
|
4754
|
+
* Markdown, consecutive tool calls as one collapsible group that counts along, and
|
|
4755
|
+
* approvals as cards. While a turn runs without visible progress, a working indicator
|
|
4756
|
+
* shows the elapsed time. The thread follows new content while the user is at the bottom.
|
|
3257
4757
|
*/
|
|
3258
4758
|
class NhAssistantThreadComponent {
|
|
3259
4759
|
constructor() {
|
|
@@ -3262,18 +4762,60 @@ class NhAssistantThreadComponent {
|
|
|
3262
4762
|
this.streaming = input(false, ...(ngDevMode ? [{ debugName: "streaming" }] : []));
|
|
3263
4763
|
this.deciding = input(false, ...(ngDevMode ? [{ debugName: "deciding" }] : []));
|
|
3264
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" }] : []));
|
|
3265
4773
|
this.approvalDecision = output();
|
|
4774
|
+
this.names = computed(() => new Map(this.members().map(member => [member.actorId, member.displayName])), ...(ngDevMode ? [{ debugName: "names" }] : []));
|
|
3266
4775
|
this.virtualThreshold = NH_ASSISTANT_VIRTUAL_SCROLL_THRESHOLD;
|
|
3267
4776
|
this.virtual = computed(() => this.messages().length > NH_ASSISTANT_VIRTUAL_SCROLL_THRESHOLD, ...(ngDevMode ? [{ debugName: "virtual" }] : []));
|
|
4777
|
+
/**
|
|
4778
|
+
* True while the turn runs and nothing else shows progress: before the first part and
|
|
4779
|
+
* between tool calls while the model decides what to do next. Streaming text and a
|
|
4780
|
+
* running tool group show their own progress; an approval waits for the user.
|
|
4781
|
+
*/
|
|
3268
4782
|
this.working = computed(() => {
|
|
3269
4783
|
if (!this.streaming()) {
|
|
3270
4784
|
return false;
|
|
3271
4785
|
}
|
|
3272
4786
|
const last = this.messages()[this.messages().length - 1];
|
|
3273
|
-
|
|
4787
|
+
if (!last || last.role === 'user' || last.parts.length === 0) {
|
|
4788
|
+
return true;
|
|
4789
|
+
}
|
|
4790
|
+
const lastPart = last.parts[last.parts.length - 1];
|
|
4791
|
+
return lastPart.type === 'tool-call'
|
|
4792
|
+
&& lastPart.status !== 'running'
|
|
4793
|
+
&& lastPart.status !== 'awaiting-approval';
|
|
3274
4794
|
}, ...(ngDevMode ? [{ debugName: "working" }] : []));
|
|
4795
|
+
/** Whole seconds since the running turn started. */
|
|
4796
|
+
this.elapsedSeconds = computed(() => {
|
|
4797
|
+
const startedAt = this.turnStartedAt();
|
|
4798
|
+
return startedAt === null ? 0 : Math.max(0, Math.floor((this.now() - startedAt) / 1000));
|
|
4799
|
+
}, ...(ngDevMode ? [{ debugName: "elapsedSeconds" }] : []));
|
|
4800
|
+
this.turnStartedAt = signal(null, ...(ngDevMode ? [{ debugName: "turnStartedAt" }] : []));
|
|
4801
|
+
this.now = signal(Date.now(), ...(ngDevMode ? [{ debugName: "now" }] : []));
|
|
3275
4802
|
this.scroller = viewChild('scroller', ...(ngDevMode ? [{ debugName: "scroller" }] : []));
|
|
3276
4803
|
this.viewport = viewChild(CdkVirtualScrollViewport, ...(ngDevMode ? [{ debugName: "viewport" }] : []));
|
|
4804
|
+
effect(onCleanup => {
|
|
4805
|
+
if (!this.streaming()) {
|
|
4806
|
+
this.turnStartedAt.set(null);
|
|
4807
|
+
return;
|
|
4808
|
+
}
|
|
4809
|
+
untracked(() => {
|
|
4810
|
+
const now = Date.now();
|
|
4811
|
+
this.now.set(now);
|
|
4812
|
+
if (this.turnStartedAt() === null) {
|
|
4813
|
+
this.turnStartedAt.set(now);
|
|
4814
|
+
}
|
|
4815
|
+
});
|
|
4816
|
+
const timer = setInterval(() => this.now.set(Date.now()), 1000);
|
|
4817
|
+
onCleanup(() => clearInterval(timer));
|
|
4818
|
+
});
|
|
3277
4819
|
effect(() => {
|
|
3278
4820
|
this.messages();
|
|
3279
4821
|
this.working();
|
|
@@ -3287,6 +4829,17 @@ class NhAssistantThreadComponent {
|
|
|
3287
4829
|
trackMessage(_index, message) {
|
|
3288
4830
|
return message.id;
|
|
3289
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
|
+
}
|
|
3290
4843
|
scrollElement() {
|
|
3291
4844
|
return this.viewport()?.elementRef.nativeElement ?? this.scroller()?.nativeElement ?? null;
|
|
3292
4845
|
}
|
|
@@ -3302,30 +4855,33 @@ class NhAssistantThreadComponent {
|
|
|
3302
4855
|
element.scrollTop = element.scrollHeight;
|
|
3303
4856
|
}
|
|
3304
4857
|
}
|
|
3305
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
3306
|
-
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 (part of message.parts; track $index) {\n @switch (part.type) {\n @case ('text') {\n @if (message.role === 'assistant' && markdown()) {\n <div class=\"bubble markdown\" [innerHTML]=\"part.text | nhAssistantMarkdown\"></div>\n } @else {\n <div class=\"bubble plain\">{{ part.text }}</div>\n }\n }\n @case ('tool-call') {\n <nh-assistant-tool-call-card class=\"part\" [part]=\"part\" />\n }\n @case ('approval') {\n <nh-assistant-approval-card class=\"part\" [approval]=\"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 </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}.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: NhAssistantToolCallCardComponent, selector: "nh-assistant-tool-call-card", inputs: ["part"] }, { 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" }], 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 }); }
|
|
3307
4860
|
}
|
|
3308
|
-
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: [{
|
|
3309
4862
|
type: Component,
|
|
3310
4863
|
args: [{ selector: 'nh-assistant-thread', standalone: true, imports: [
|
|
3311
4864
|
NgTemplateOutlet,
|
|
3312
4865
|
ScrollingModule,
|
|
3313
4866
|
TranslatePipe,
|
|
3314
4867
|
NhAssistantMarkdownPipe,
|
|
3315
|
-
|
|
4868
|
+
NhAssistantPartGroupsPipe,
|
|
4869
|
+
NhAssistantToolCallGroupComponent,
|
|
3316
4870
|
NhAssistantApprovalCardComponent
|
|
3317
|
-
], 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 (
|
|
3318
|
-
}], 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 }] }] } });
|
|
3319
4873
|
|
|
3320
4874
|
let nextId = 0;
|
|
3321
4875
|
/**
|
|
3322
4876
|
* The assistant drawer. Place it once in the application layout; it renders nothing in
|
|
3323
|
-
* 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.
|
|
3324
4879
|
*/
|
|
3325
4880
|
class NhAssistantPanelComponent {
|
|
3326
4881
|
constructor() {
|
|
3327
4882
|
this.store = inject(NhAssistantStore);
|
|
3328
4883
|
this.panel = inject(NhAssistantPanelService);
|
|
4884
|
+
this.push = inject(NhAssistantPushService);
|
|
3329
4885
|
this.config = inject(NH_ASSISTANT_CONFIG);
|
|
3330
4886
|
this.viewContainerRef = inject(ViewContainerRef);
|
|
3331
4887
|
this.router = inject(Router, { optional: true });
|
|
@@ -3334,6 +4890,7 @@ class NhAssistantPanelComponent {
|
|
|
3334
4890
|
this.markdown = this.config.markdown?.enabled ?? true;
|
|
3335
4891
|
this.showConversations = signal(false, ...(ngDevMode ? [{ debugName: "showConversations" }] : []));
|
|
3336
4892
|
this.showPreferences = signal(false, ...(ngDevMode ? [{ debugName: "showPreferences" }] : []));
|
|
4893
|
+
this.showShare = signal(false, ...(ngDevMode ? [{ debugName: "showShare" }] : []));
|
|
3337
4894
|
this.adminRoute = this.config.adminRoute ?? null;
|
|
3338
4895
|
this.conversation = this.store.activeConversation;
|
|
3339
4896
|
this.conversationId = computed(() => this.conversation()?.id ?? null, ...(ngDevMode ? [{ debugName: "conversationId" }] : []));
|
|
@@ -3350,6 +4907,15 @@ class NhAssistantPanelComponent {
|
|
|
3350
4907
|
}
|
|
3351
4908
|
return this.conversation()?.status === 'waiting-for-approval' ? 'waiting-for-approval' : null;
|
|
3352
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" }] : []));
|
|
3353
4919
|
this.errorKeys = computed(() => {
|
|
3354
4920
|
const error = this.store.error();
|
|
3355
4921
|
return error ? [error.messageKey, `nh-assistant.errors.${error.code}`, 'nh-assistant.errors.generic'] : [];
|
|
@@ -3364,9 +4930,18 @@ class NhAssistantPanelComponent {
|
|
|
3364
4930
|
untracked(() => {
|
|
3365
4931
|
this.showConversations.set(false);
|
|
3366
4932
|
this.showPreferences.set(false);
|
|
4933
|
+
this.showShare.set(false);
|
|
3367
4934
|
});
|
|
3368
4935
|
}
|
|
3369
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());
|
|
3370
4945
|
effect(() => {
|
|
3371
4946
|
if (this.panel.isOpen()) {
|
|
3372
4947
|
untracked(() => {
|
|
@@ -3386,6 +4961,7 @@ class NhAssistantPanelComponent {
|
|
|
3386
4961
|
ngAfterViewInit() {
|
|
3387
4962
|
this.portal = new TemplatePortal(this.drawer(), this.viewContainerRef);
|
|
3388
4963
|
this.panel.registerPanel(this.portal);
|
|
4964
|
+
this.openFromLocation();
|
|
3389
4965
|
}
|
|
3390
4966
|
ngOnDestroy() {
|
|
3391
4967
|
if (this.portal) {
|
|
@@ -3400,12 +4976,19 @@ class NhAssistantPanelComponent {
|
|
|
3400
4976
|
}
|
|
3401
4977
|
toggleConversations() {
|
|
3402
4978
|
this.showPreferences.set(false);
|
|
4979
|
+
this.showShare.set(false);
|
|
3403
4980
|
this.showConversations.update(show => !show);
|
|
3404
4981
|
}
|
|
3405
4982
|
togglePreferences() {
|
|
3406
4983
|
this.showConversations.set(false);
|
|
4984
|
+
this.showShare.set(false);
|
|
3407
4985
|
this.showPreferences.update(show => !show);
|
|
3408
4986
|
}
|
|
4987
|
+
toggleShare() {
|
|
4988
|
+
this.showConversations.set(false);
|
|
4989
|
+
this.showPreferences.set(false);
|
|
4990
|
+
this.showShare.update(show => !show);
|
|
4991
|
+
}
|
|
3409
4992
|
newConversation() {
|
|
3410
4993
|
this.store.startNewConversation();
|
|
3411
4994
|
this.showConversations.set(false);
|
|
@@ -3420,30 +5003,60 @@ class NhAssistantPanelComponent {
|
|
|
3420
5003
|
send(text) {
|
|
3421
5004
|
this.showConversations.set(false);
|
|
3422
5005
|
this.showPreferences.set(false);
|
|
5006
|
+
this.showShare.set(false);
|
|
3423
5007
|
void this.store.send(text);
|
|
3424
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
|
+
}
|
|
3425
5036
|
decide(decision) {
|
|
3426
5037
|
this.store.decide(decision);
|
|
3427
5038
|
}
|
|
3428
5039
|
cancel() {
|
|
3429
5040
|
void this.store.cancel();
|
|
3430
5041
|
}
|
|
3431
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
3432
|
-
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 }); }
|
|
3433
5044
|
}
|
|
3434
|
-
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: [{
|
|
3435
5046
|
type: Component,
|
|
3436
5047
|
args: [{ selector: 'nh-assistant-panel', standalone: true, imports: [
|
|
3437
5048
|
RouterLink,
|
|
3438
5049
|
TranslatePipe,
|
|
3439
5050
|
NhAssistantTranslatePipe,
|
|
3440
5051
|
NhAssistantIconComponent,
|
|
5052
|
+
NhAssistantActivityComponent,
|
|
3441
5053
|
NhAssistantAgentPickerComponent,
|
|
3442
5054
|
NhAssistantComposerComponent,
|
|
3443
5055
|
NhAssistantConversationListComponent,
|
|
3444
5056
|
NhAssistantPreferencesComponent,
|
|
5057
|
+
NhAssistantShareComponent,
|
|
3445
5058
|
NhAssistantThreadComponent
|
|
3446
|
-
], 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"] }]
|
|
3447
5060
|
}], ctorParameters: () => [], propDecorators: { drawer: [{ type: i0.ViewChild, args: ['drawer', { isSignal: true }] }] } });
|
|
3448
5061
|
|
|
3449
5062
|
/** Converts any failure into a user-facing code and translation key, never raw text. */
|
|
@@ -3468,5 +5081,5 @@ const NH_ASSISTANT_DASH_CASE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
|
3468
5081
|
* Generated bundle index. Do not edit.
|
|
3469
5082
|
*/
|
|
3470
5083
|
|
|
3471
|
-
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, 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 };
|
|
3472
5085
|
//# sourceMappingURL=newheap-platform-ai-chat.mjs.map
|