@newheap/platform-ai-chat 0.15.0 → 0.16.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.
@@ -106,6 +106,53 @@ const NH_ASSISTANT_FETCH = new InjectionToken('NH_ASSISTANT_FETCH', {
106
106
  providedIn: 'root',
107
107
  factory: () => (input, init) => globalThis.fetch(input, init)
108
108
  });
109
+ /**
110
+ * Sends the uploads of the assistant client. The default uses `XMLHttpRequest`, because `fetch` does
111
+ * not report upload progress. The mock API from `@newheap/platform-ai-chat/testing` replaces it.
112
+ */
113
+ const NH_ASSISTANT_UPLOAD = new InjectionToken('NH_ASSISTANT_UPLOAD', {
114
+ providedIn: 'root',
115
+ factory: () => nhAssistantXhrUpload
116
+ });
117
+ /** The default upload: `XMLHttpRequest` with upload progress, answered as a `Response`. */
118
+ function nhAssistantXhrUpload(request) {
119
+ return new Promise((resolve, reject) => {
120
+ if (request.signal.aborted) {
121
+ reject(new DOMException('The upload was aborted.', 'AbortError'));
122
+ return;
123
+ }
124
+ const xhr = new XMLHttpRequest();
125
+ const abort = () => xhr.abort();
126
+ request.signal.addEventListener('abort', abort, { once: true });
127
+ const finish = () => request.signal.removeEventListener('abort', abort);
128
+ xhr.open('POST', request.url);
129
+ for (const [name, value] of Object.entries(request.headers)) {
130
+ xhr.setRequestHeader(name, value);
131
+ }
132
+ xhr.upload.onprogress = event => {
133
+ if (event.lengthComputable) {
134
+ request.onProgress(event.loaded, event.total);
135
+ }
136
+ };
137
+ xhr.onload = () => {
138
+ finish();
139
+ const contentType = xhr.getResponseHeader('content-type');
140
+ resolve(new Response(xhr.status === 204 ? null : xhr.responseText, {
141
+ status: xhr.status,
142
+ headers: contentType ? { 'content-type': contentType } : {}
143
+ }));
144
+ };
145
+ xhr.onerror = () => {
146
+ finish();
147
+ reject(new TypeError('The upload did not reach the server.'));
148
+ };
149
+ xhr.onabort = () => {
150
+ finish();
151
+ reject(new DOMException('The upload was aborted.', 'AbortError'));
152
+ };
153
+ xhr.send(request.body);
154
+ });
155
+ }
109
156
  /** Default policy: every user who can reach the host may use the assistant. */
110
157
  class NhAssistantAllowAllAccessPolicy {
111
158
  canUse() {
@@ -187,7 +234,9 @@ var en = {
187
234
  elapsed: "{{seconds}}s",
188
235
  participant: "Participant",
189
236
  trigger: "Event",
190
- rule: "Automatic rule"
237
+ rule: "Automatic rule",
238
+ files: "Attached files",
239
+ "file-meta": "{{type}}, {{size}}"
191
240
  },
192
241
  composer: {
193
242
  label: "Message",
@@ -202,6 +251,18 @@ var en = {
202
251
  "waiting-for-approval": "Waiting for approval",
203
252
  "running-by": "{{name}} is working with the assistant…",
204
253
  "waiting-for-approval-by": "Waiting for {{name}} to decide…"
254
+ },
255
+ attach: "Attach files",
256
+ "attach-hint": "{{types}}, up to {{max}} each, at most {{count}} files per message",
257
+ attachments: "Attached files",
258
+ "remove-attachment": "Remove {{name}}",
259
+ uploading: "Uploading {{name}}",
260
+ "uploading-hint": "The message can be sent once the files are uploaded",
261
+ "attachment-errors": {
262
+ "assistant-file-type-not-accepted": "This type of file cannot be attached. Attach {{types}}.",
263
+ "assistant-file-too-large": "The file is larger than {{max}}.",
264
+ "assistant-file-count-limit": "A message can carry at most {{count}} files.",
265
+ "assistant-file-upload-invalid": "The file is empty or its name cannot be used."
205
266
  }
206
267
  },
207
268
  queue: {
@@ -219,7 +280,8 @@ var en = {
219
280
  preparing: "Preparing…",
220
281
  sending: "Sending…",
221
282
  failed: "Not sent; resume to retry"
222
- }
283
+ },
284
+ files: "Files ({{count}}): {{names}}"
223
285
  },
224
286
  "tool-call": {
225
287
  label: "Tool call: {{name}}",
@@ -355,7 +417,8 @@ var en = {
355
417
  "assistant-proposal-already-open": "Not proposed again: the same proposal already waits.",
356
418
  "assistant-proposal-recently-rejected": "Not proposed again: people rejected it recently.",
357
419
  "assistant-proposal-placed": "Proposed at its object; waits for that object's approvers",
358
- "assistant-proposal-not-placed": "Not proposed: the object it concerns does not accept it."
420
+ "assistant-proposal-not-placed": "Not proposed: the object it concerns does not accept it.",
421
+ "assistant-proposal-not-needed": "Not proposed: the work is already done or no longer applies."
359
422
  },
360
423
  errors: {
361
424
  generic: "Something went wrong. Please try again.",
@@ -409,6 +472,19 @@ var en = {
409
472
  "assistant-message-invalid": "The message is empty or not valid. Write a question and send it again.",
410
473
  "assistant-message-too-long": "The message is too long. Shorten it and send it again.",
411
474
  "assistant-model-unavailable": "The language model is temporarily unavailable. Try again in a moment.",
475
+ "assistant-classification-not-permitted": "The model this agent uses may not receive data of the agent's classification. Ask an administrator to choose a model profile that permits it.",
476
+ "assistant-files-unavailable": "This application gives the assistant no files.",
477
+ "assistant-files-not-accepted": "This agent does not accept files. An administrator can switch files on for it.",
478
+ "assistant-file-reference-invalid": "A file could not be read because its reference is not valid.",
479
+ "assistant-file-not-found": "The file was not found. It may have been removed.",
480
+ "assistant-file-forbidden": "You may not read this file, so the assistant did not read it either.",
481
+ "assistant-file-unavailable": "The file could not be opened right now. Try again in a moment.",
482
+ "assistant-file-reader-failed": "The reading tool for this type of file could not turn it into text.",
483
+ "assistant-file-too-large": "The file is larger than the assistant may read. Share a smaller file.",
484
+ "assistant-file-too-many-pages": "The file has more pages than the assistant may read. Share a shorter document.",
485
+ "assistant-file-count-limit": "The assistant may read no more files in this answer. Ask about fewer files at a time.",
486
+ "assistant-file-token-limit": "The files are too long for one answer. Ask about fewer or shorter files at a time.",
487
+ "assistant-file-type-not-supported": "The model of this agent cannot read files of this type.",
412
488
  "assistant-proposal-hash-mismatch": "The proposed change was modified in the meantime, so nothing was changed. Reload the conversation and decide again.",
413
489
  "assistant-title-invalid": "The conversation title is not valid. Use a shorter title.",
414
490
  "assistant-tool-call-limit-reached": "The assistant reached the maximum number of steps for one answer. Ask a narrower question.",
@@ -439,6 +515,8 @@ var en = {
439
515
  "assistant-run-token-limit": "This run stopped because it reached its token budget. What it did so far stays; an approver can resume it.",
440
516
  "assistant-skill-stopped": "This skill of the agent has been stopped.",
441
517
  "assistant-skill-paused": "This skill of the agent is paused; its events wait until it resumes.",
518
+ "assistant-agent-off": "This agent is switched off; it takes no work from other agents until it is switched on.",
519
+ "assistant-skill-off": "This skill of the agent is switched off.",
442
520
  "assistant-skill-not-found": "The agent has no such skill.",
443
521
  "assistant-skill-not-available": "That skill is not available now.",
444
522
  "assistant-skill-switch-limit": "The run already switched skills as often as allowed.",
@@ -522,11 +600,19 @@ var en = {
522
600
  "assistant-mcp-authorization-cancelled": "The authorization window was closed before it finished.",
523
601
  "assistant-mcp-popup-blocked": "The browser blocked the authorization window. Allow pop-ups for this site and try again.",
524
602
  "assistant-proposal-placed": "The proposal waits at the object it concerns, for that object's approvers.",
525
- "assistant-proposal-not-placed": "The proposal was not placed at the object it concerns."
603
+ "assistant-proposal-not-placed": "The proposal was not placed at the object it concerns.",
604
+ "assistant-proposal-not-needed": "Not proposed: the work is already done or no longer applies.",
605
+ "assistant-file-upload-invalid": "The file could not be uploaded. Choose one file and try again.",
606
+ "assistant-file-type-not-accepted": "This type of file cannot be attached.",
607
+ "assistant-file-upload-refused": "The application refused this file.",
608
+ "assistant-file-upload-failed": "The file could not be stored right now. Try again in a moment.",
609
+ "assistant-file-scan-refused": "The file did not pass the scan for harmful content and was not stored."
526
610
  },
527
611
  notices: {
528
612
  "no-answer": "The assistant finished without an answer. Try again or rephrase your question.",
529
- "conversation-removed": "This conversation is no longer available to you."
613
+ "conversation-removed": "This conversation is no longer available to you.",
614
+ "message-queued": "The agent is working in this conversation. Your message waits for its next run, which adds it here.",
615
+ "message-queued-with-files": "The agent is working in this conversation. Your message and its files wait for its next run, which adds them here."
530
616
  },
531
617
  preferences: {
532
618
  title: "Your preferences",
@@ -672,22 +758,31 @@ var en = {
672
758
  stopped: "Stopped",
673
759
  "configured-off": "Off in configuration",
674
760
  "configured-off-hint": "Disabled in the application's code until the next deployment; it cannot be resumed here.",
761
+ off: "Off",
762
+ "off-hint": "Events, schedules and other triggers are ignored until it is switched on, and nothing waits for it. People can still chat with it and start it by hand.",
763
+ "starts-off": "It started off, as its code says.",
675
764
  changed: "Changed by {{user}} on {{date}}.",
676
765
  reason: "Note: {{reason}}",
677
766
  waiting: "{{count}} events wait for the resume.",
678
767
  action: {
679
768
  active: "Resume",
680
769
  paused: "Pause",
770
+ off: "Switch off",
771
+ "switch-on": "Switch on",
681
772
  stopped: "Stop"
682
773
  },
683
774
  explain: {
684
775
  active: "The agent works again. Every subject with waiting events gets one run.",
685
776
  paused: "No new runs start. Events wait and run after the resume; running work finishes and approved proposals execute.",
777
+ off: "Events, schedules, webhooks, hand-offs and follow-ups are ignored and nothing waits; switching on later starts nothing for them. Running work finishes, open proposals can still be decided and people can still chat with the agent and start it by hand.",
778
+ "switch-on": "The agent takes its events, schedules and other triggers again. Nothing that happened while it was off runs.",
686
779
  stopped: "Active runs are cancelled, their open proposals withdrawn and waiting events dropped. Approved proposals wait until the agent works again."
687
780
  },
688
781
  confirm: {
689
782
  active: "Confirm resume",
690
783
  paused: "Confirm pause",
784
+ off: "Confirm switch off",
785
+ "switch-on": "Confirm switch on",
691
786
  stopped: "Confirm stop"
692
787
  },
693
788
  "note-label": "Note for other administrators (optional)",
@@ -700,6 +795,8 @@ var en = {
700
795
  explain: {
701
796
  active: "The skill {{skill}} works again. Every subject with events waiting for it gets one run.",
702
797
  paused: "The skill {{skill}} starts no new runs: its events wait, triage no longer chooses it and running work finishes. The other skills keep working.",
798
+ off: "Triggers of the skill {{skill}} are ignored and rules and triage no longer route work to it; nothing waits for it. Running work finishes. The other skills keep working.",
799
+ "switch-on": "The skill {{skill}} takes work again. Nothing that happened while it was off runs.",
703
800
  stopped: "The runs of the skill {{skill}} are cancelled and its waiting events dropped; its triggers are refused. The other skills keep working."
704
801
  }
705
802
  },
@@ -776,7 +873,10 @@ var en = {
776
873
  "view-policy": "Results for the whole application seen by: {{policy}}."
777
874
  },
778
875
  groups: "Groups: {{groups}}",
779
- "catalog-failed": "The building blocks of agent runs could not be read. Until they are, agents cannot be run now here and what an agent works on and its groups stay as they are."
876
+ "catalog-failed": "The building blocks of agent runs could not be read. Until they are, agents cannot be run now here and what an agent works on and its groups stay as they are.",
877
+ warnings: {
878
+ "classification-not-permitted": "Neither the agent's model profile nor the profile's fallbacks permit its data classification. The agent is saved, but its model calls are refused until a profile permits the classification."
879
+ }
780
880
  },
781
881
  "agent-editor": {
782
882
  "new-title": "New agent",
@@ -791,7 +891,14 @@ var en = {
791
891
  "autonomy-hint": "Only Execute allows changes, and every change still needs approval.",
792
892
  "required-policy": "Required policy",
793
893
  "required-policy-hint": "Optional. Only users who pass this policy see the agent.",
894
+ profile: "Model profile",
895
+ "profile-default": "The application's chat profile",
896
+ "profile-hint": "The model the agent works with, from the profiles the application offers for agents created here.",
897
+ classification: "Data classification",
898
+ "classification-hint": "How sensitive the data is that the agent's model calls carry, in chats with it and in its runs. Its model profile or one of the profile's fallbacks must permit it; a skill may set its own.",
794
899
  "state-hint": "Pause, stop and resume the agent from the agent list. Saving never changes its state.",
900
+ "switch-on-now": "Switch on now",
901
+ "switch-on-now-hint": "Otherwise the agent starts off: its events, schedules and other triggers are ignored until you switch it on in the agent list. People can still chat with it and start it by hand.",
795
902
  "tool-selectors": "Tool selectors",
796
903
  "tool-selectors-hint": "Choose tools from the catalog or add a pattern such as sample-api.project.*. MCP tools come from the assigned servers.",
797
904
  "add-selector": "Add pattern",
@@ -1006,7 +1113,33 @@ var en = {
1006
1113
  "client-secret-reference": "Client secret reference",
1007
1114
  "organization-account-label": "Connected account",
1008
1115
  "organization-account-label-hint": "Which account the organization connects, for example \"Planning service account\". Everyone using the server sees it.",
1009
- "organization-account-label-required": "Name the account that will be connected."
1116
+ "organization-account-label-required": "Name the account that will be connected.",
1117
+ reader: {
1118
+ title: "Reading documents",
1119
+ hint: "Models that cannot read a file type themselves can get the file as text from a reader of this server. The assistant calls the reader's tool with the file name, media type and base64 content of the file, after the application's own readers.",
1120
+ empty: "This server reads no files.",
1121
+ item: "Reader {{number}}",
1122
+ add: "Add reader",
1123
+ "add-hint": "Each reader is one of the server's synced tools; a server has at most {{max}}.",
1124
+ limit: "This server has the most readers it can have ({{max}}).",
1125
+ remove: "Remove",
1126
+ "remove-label": "Remove reader {{number}}",
1127
+ "media-types": "Media types",
1128
+ "media-types-hint": "Comma-separated, at most 16, such as application/vnd.openxmlformats-officedocument.wordprocessingml.document.",
1129
+ "media-types-invalid": "Enter 1 to 16 media types of the form type/subtype, separated by commas.",
1130
+ tool: "Reading tool",
1131
+ "tool-choose": "Choose a tool",
1132
+ "tool-hint": "A tool synced from this server that turns a file into text. It does not have to be enabled for agents.",
1133
+ "tool-duplicate": "Another reader of this server already uses this tool.",
1134
+ "tool-sync": "Sync the server's tools first, then add a reader.",
1135
+ priority: "Priority",
1136
+ "priority-hint": "Readers with a higher priority are asked first, after the application's own readers; with the same priority, by server and tool name.",
1137
+ "priority-invalid": "Enter a whole number from {{min}} to {{max}}.",
1138
+ "max-classification": "Highest classification",
1139
+ "max-classification-choose": "Choose a classification",
1140
+ "max-classification-hint": "A file that is more sensitive is never sent to this server.",
1141
+ "leaves-application": "A file sent to this server leaves the application. Choose a classification the server may receive; for personal connections the person's consent to background use still applies."
1142
+ }
1010
1143
  },
1011
1144
  tools: {
1012
1145
  title: "Tools of {{name}}",
@@ -1154,6 +1287,19 @@ var en = {
1154
1287
  "replay-progress": "{{replayed}} of at most {{max}} runs replayed, {{skipped}} skipped"
1155
1288
  },
1156
1289
  "run-config": {
1290
+ files: {
1291
+ label: "Accepts files",
1292
+ "agent-hint": "People's messages and the agent's tools may give it documents, such as PDFs, within the application's limits. A new agent accepts none until you switch this on.",
1293
+ "skill-hint": "Runs in this skill may read documents when the agent accepts them too. A new skill accepts none until you switch this on.",
1294
+ "limits-title": "Lower file limits",
1295
+ "agent-limits-hint": "Leave a field empty to keep the application's limit. A value can only lower it.",
1296
+ "skill-limits-hint": "Leave a field empty to keep the agent's limit. A value can only lower the application's.",
1297
+ "max-file-megabytes": "Largest file (MB)",
1298
+ "max-file-pages": "Most pages per file",
1299
+ "max-files-per-turn": "Most files per turn",
1300
+ "max-file-tokens-per-turn": "Most file tokens per turn",
1301
+ "application-limit": "The application allows at most {{value}}."
1302
+ },
1157
1303
  back: "Back to agents",
1158
1304
  title: "Run configuration of {{name}}",
1159
1305
  description: "Skills and triggers that the administration adds without a deploy, made of the building blocks the application registers. New skills and triggers start in shadow, where nobody is notified and nothing executes.",
@@ -1192,7 +1338,9 @@ var en = {
1192
1338
  taken: "This id is already in use.",
1193
1339
  "name-letters": "Use at least one letter in the name.",
1194
1340
  "instructions-for-model": "Without fixed rules the model works from these instructions; describe what it should do.",
1195
- "application-not-worked-on": "The agent does not work on the whole application. Choose a set of objects, or let the agent work on the whole application first."
1341
+ "application-not-worked-on": "The agent does not work on the whole application. Choose a set of objects, or let the agent work on the whole application first.",
1342
+ "megabytes-up-to": "Enter a size above 0 and at most {{max}} MB.",
1343
+ "megabytes-positive": "Enter a size above 0 MB."
1196
1344
  },
1197
1345
  settings: {
1198
1346
  title: "Run settings",
@@ -1293,6 +1441,8 @@ var en = {
1293
1441
  "apply-shadow": "Evaluate in shadow next to the live version first",
1294
1442
  "apply-live": "Put live at once",
1295
1443
  "starts-in-shadow": "A new skill starts in shadow.",
1444
+ "switch-on-now": "Switch on now",
1445
+ "switch-on-now-hint": "Otherwise the skill starts off: triggers that name it are ignored and rules and triage do not route to it until you switch it on in the agent list.",
1296
1446
  versions: "Versions",
1297
1447
  "remove-explain": "Its versions stay as history. A trigger that starts it must go first.",
1298
1448
  what: {
@@ -1304,6 +1454,22 @@ var en = {
1304
1454
  "rules-hint": "The rules decide without a model; a doubtful case goes to the model with the instructions below.",
1305
1455
  "model-hint": "The model decides with the instructions below and the chosen tools.",
1306
1456
  "profile-hint": "The model profile it uses; by default the agent's.",
1457
+ classification: {
1458
+ label: "Data classification",
1459
+ agent: "The agent's classification",
1460
+ hint: "How sensitive the data is that the skill sends to its model. Its model profile must permit it; otherwise its model calls are refused.",
1461
+ "public": "Public",
1462
+ "public-hint": "Data anyone may see.",
1463
+ internal: "Internal",
1464
+ "internal-hint": "Data of the organization; the default.",
1465
+ confidential: "Confidential",
1466
+ "confidential-hint": "Data only some people may see, such as personal or financial data.",
1467
+ restricted: "Restricted",
1468
+ "restricted-hint": "The most sensitive data, for which only specially approved models may be used."
1469
+ },
1470
+ warnings: {
1471
+ "classification-not-permitted": "Neither the skill's model profile nor the profile's fallbacks permit its data classification. The skill is saved, but its model calls are refused until a profile permits the classification."
1472
+ },
1307
1473
  "apply-shadow-hint": "The new version runs next to the live one; compare them, then promote or discard it.",
1308
1474
  "apply-live-hint": "For a small correction: the new version replaces the live one at once.",
1309
1475
  "tools-pattern": "All tools that match: {{count}} now.",
@@ -2086,7 +2252,9 @@ var nl = {
2086
2252
  elapsed: "{{seconds}} s",
2087
2253
  participant: "Deelnemer",
2088
2254
  trigger: "Gebeurtenis",
2089
- rule: "Automatische regel"
2255
+ rule: "Automatische regel",
2256
+ files: "Bijgevoegde bestanden",
2257
+ "file-meta": "{{type}}, {{size}}"
2090
2258
  },
2091
2259
  composer: {
2092
2260
  label: "Bericht",
@@ -2101,6 +2269,18 @@ var nl = {
2101
2269
  "waiting-for-approval": "Wacht op goedkeuring",
2102
2270
  "running-by": "{{name}} werkt met de assistent…",
2103
2271
  "waiting-for-approval-by": "Wacht op de beslissing van {{name}}…"
2272
+ },
2273
+ attach: "Bestanden bijvoegen",
2274
+ "attach-hint": "{{types}}, elk tot {{max}}, hoogstens {{count}} bestanden per bericht",
2275
+ attachments: "Bijgevoegde bestanden",
2276
+ "remove-attachment": "{{name}} verwijderen",
2277
+ uploading: "{{name}} wordt geüpload",
2278
+ "uploading-hint": "Het bericht kan worden verstuurd zodra de bestanden zijn geüpload",
2279
+ "attachment-errors": {
2280
+ "assistant-file-type-not-accepted": "Dit type bestand kan niet worden bijgevoegd. Voeg {{types}} bij.",
2281
+ "assistant-file-too-large": "Het bestand is groter dan {{max}}.",
2282
+ "assistant-file-count-limit": "Een bericht kan hoogstens {{count}} bestanden bevatten.",
2283
+ "assistant-file-upload-invalid": "Het bestand is leeg of de naam is niet bruikbaar."
2104
2284
  }
2105
2285
  },
2106
2286
  queue: {
@@ -2118,7 +2298,8 @@ var nl = {
2118
2298
  preparing: "Klaarzetten…",
2119
2299
  sending: "Versturen…",
2120
2300
  failed: "Niet verstuurd; hervat om opnieuw te proberen"
2121
- }
2301
+ },
2302
+ files: "Bestanden ({{count}}): {{names}}"
2122
2303
  },
2123
2304
  "tool-call": {
2124
2305
  label: "Toolaanroep: {{name}}",
@@ -2254,7 +2435,8 @@ var nl = {
2254
2435
  "assistant-proposal-already-open": "Niet opnieuw voorgesteld: hetzelfde voorstel wacht al.",
2255
2436
  "assistant-proposal-recently-rejected": "Niet opnieuw voorgesteld: het is kort geleden afgewezen.",
2256
2437
  "assistant-proposal-placed": "Voorgesteld bij het object; wacht op de goedkeurders daarvan",
2257
- "assistant-proposal-not-placed": "Niet voorgesteld: het object waar het om gaat accepteert het niet."
2438
+ "assistant-proposal-not-placed": "Niet voorgesteld: het object waar het om gaat accepteert het niet.",
2439
+ "assistant-proposal-not-needed": "Niet voorgesteld: het werk is al gedaan of is niet meer van toepassing."
2258
2440
  },
2259
2441
  errors: {
2260
2442
  generic: "Er ging iets mis. Probeer het opnieuw.",
@@ -2308,6 +2490,19 @@ var nl = {
2308
2490
  "assistant-message-invalid": "Het bericht is leeg of ongeldig. Schrijf een vraag en verstuur hem opnieuw.",
2309
2491
  "assistant-message-too-long": "Het bericht is te lang. Maak het korter en verstuur het opnieuw.",
2310
2492
  "assistant-model-unavailable": "Het taalmodel is tijdelijk niet beschikbaar. Probeer het zo opnieuw.",
2493
+ "assistant-classification-not-permitted": "Het model dat deze agent gebruikt mag geen gegevens met de classificatie van de agent ontvangen. Vraag een beheerder een modelprofiel te kiezen dat dit toestaat.",
2494
+ "assistant-files-unavailable": "Deze applicatie geeft de assistent geen bestanden.",
2495
+ "assistant-files-not-accepted": "Deze agent neemt geen bestanden aan. Een beheerder kan bestanden voor hem aanzetten.",
2496
+ "assistant-file-reference-invalid": "Een bestand kon niet worden gelezen omdat de verwijzing ongeldig is.",
2497
+ "assistant-file-not-found": "Het bestand is niet gevonden. Misschien is het verwijderd.",
2498
+ "assistant-file-forbidden": "Je mag dit bestand niet lezen, dus de assistent heeft het ook niet gelezen.",
2499
+ "assistant-file-unavailable": "Het bestand kon nu niet worden geopend. Probeer het zo opnieuw.",
2500
+ "assistant-file-reader-failed": "Het leesprogramma voor dit type bestand kon het niet omzetten in tekst.",
2501
+ "assistant-file-too-large": "Het bestand is groter dan de assistent mag lezen. Deel een kleiner bestand.",
2502
+ "assistant-file-too-many-pages": "Het bestand heeft meer pagina's dan de assistent mag lezen. Deel een korter document.",
2503
+ "assistant-file-count-limit": "De assistent mag in dit antwoord geen bestanden meer lezen. Vraag naar minder bestanden tegelijk.",
2504
+ "assistant-file-token-limit": "De bestanden zijn te lang voor één antwoord. Vraag naar minder of kortere bestanden tegelijk.",
2505
+ "assistant-file-type-not-supported": "Het model van deze agent kan bestanden van dit type niet lezen.",
2311
2506
  "assistant-proposal-hash-mismatch": "De voorgestelde wijziging is intussen veranderd, er is niets gewijzigd. Laad het gesprek opnieuw en besluit opnieuw.",
2312
2507
  "assistant-title-invalid": "De titel van het gesprek is ongeldig. Gebruik een kortere titel.",
2313
2508
  "assistant-tool-call-limit-reached": "De assistent heeft het maximale aantal stappen voor één antwoord bereikt. Stel een gerichtere vraag.",
@@ -2338,6 +2533,8 @@ var nl = {
2338
2533
  "assistant-run-token-limit": "Deze run is gestopt omdat hij zijn tokenbudget heeft bereikt. Wat hij al deed blijft staan; een goedkeurder kan hem hervatten.",
2339
2534
  "assistant-skill-stopped": "Deze vaardigheid van de agent is gestopt.",
2340
2535
  "assistant-skill-paused": "Deze vaardigheid van de agent is gepauzeerd; haar gebeurtenissen wachten tot ze hervat wordt.",
2536
+ "assistant-agent-off": "Deze agent staat uit; hij neemt geen werk van andere agents aan tot hij aangezet wordt.",
2537
+ "assistant-skill-off": "Deze vaardigheid van de agent staat uit.",
2341
2538
  "assistant-skill-not-found": "De agent heeft deze vaardigheid niet.",
2342
2539
  "assistant-skill-not-available": "Die vaardigheid is nu niet beschikbaar.",
2343
2540
  "assistant-skill-switch-limit": "De run is al zo vaak van vaardigheid gewisseld als mag.",
@@ -2421,11 +2618,19 @@ var nl = {
2421
2618
  "assistant-mcp-authorization-cancelled": "Het autorisatievenster is gesloten voordat het klaar was.",
2422
2619
  "assistant-mcp-popup-blocked": "De browser heeft het autorisatievenster geblokkeerd. Sta pop-ups toe voor deze site en probeer het opnieuw.",
2423
2620
  "assistant-proposal-placed": "Het voorstel wacht bij het object waar het om gaat, op de goedkeurders daarvan.",
2424
- "assistant-proposal-not-placed": "Het voorstel is niet bij het object geplaatst waar het om gaat."
2621
+ "assistant-proposal-not-placed": "Het voorstel is niet bij het object geplaatst waar het om gaat.",
2622
+ "assistant-proposal-not-needed": "Niet voorgesteld: het werk is al gedaan of is niet meer van toepassing.",
2623
+ "assistant-file-upload-invalid": "Het bestand kon niet worden geüpload. Kies één bestand en probeer het opnieuw.",
2624
+ "assistant-file-type-not-accepted": "Dit type bestand kan niet worden bijgevoegd.",
2625
+ "assistant-file-upload-refused": "De applicatie heeft dit bestand geweigerd.",
2626
+ "assistant-file-upload-failed": "Het bestand kon nu niet worden opgeslagen. Probeer het zo opnieuw.",
2627
+ "assistant-file-scan-refused": "Het bestand is niet door de scan op schadelijke inhoud gekomen en is niet opgeslagen."
2425
2628
  },
2426
2629
  notices: {
2427
2630
  "no-answer": "De assistent is klaar zonder antwoord. Probeer het opnieuw of stel je vraag anders.",
2428
- "conversation-removed": "Dit gesprek is niet meer voor je beschikbaar."
2631
+ "conversation-removed": "Dit gesprek is niet meer voor je beschikbaar.",
2632
+ "message-queued": "De agent werkt in dit gesprek. Je bericht wacht op zijn volgende run, die het hier toevoegt.",
2633
+ "message-queued-with-files": "De agent werkt in dit gesprek. Je bericht en de bestanden wachten op zijn volgende run, die ze hier toevoegt."
2429
2634
  },
2430
2635
  preferences: {
2431
2636
  title: "Je voorkeuren",
@@ -2571,22 +2776,31 @@ var nl = {
2571
2776
  stopped: "Gestopt",
2572
2777
  "configured-off": "Uit in configuratie",
2573
2778
  "configured-off-hint": "Uitgeschakeld in de code van de applicatie tot de volgende deployment; hier kun je de agent niet hervatten.",
2779
+ off: "Uit",
2780
+ "off-hint": "Gebeurtenissen, schema's en andere triggers worden genegeerd tot de agent aangezet wordt, en er wacht niets op hem. Mensen kunnen nog met de agent chatten en hem handmatig starten.",
2781
+ "starts-off": "De agent begon uit, zoals zijn code zegt.",
2574
2782
  changed: "Gewijzigd door {{user}} op {{date}}.",
2575
2783
  reason: "Notitie: {{reason}}",
2576
2784
  waiting: "{{count}} gebeurtenissen wachten op het hervatten.",
2577
2785
  action: {
2578
2786
  active: "Hervatten",
2579
2787
  paused: "Pauzeren",
2788
+ off: "Uitzetten",
2789
+ "switch-on": "Aanzetten",
2580
2790
  stopped: "Stoppen"
2581
2791
  },
2582
2792
  explain: {
2583
2793
  active: "De agent werkt weer. Elk onderwerp met wachtende gebeurtenissen krijgt één run.",
2584
2794
  paused: "Er starten geen nieuwe runs. Gebeurtenissen wachten en lopen na het hervatten; lopend werk wordt afgemaakt en goedgekeurde voorstellen worden uitgevoerd.",
2795
+ off: "Gebeurtenissen, schema's, webhooks, overdrachten en vervolgruns worden genegeerd en er wacht niets; later aanzetten start er niets voor. Lopend werk wordt afgemaakt, open voorstellen kunnen nog beoordeeld worden en mensen kunnen nog met de agent chatten en hem handmatig starten.",
2796
+ "switch-on": "De agent neemt zijn gebeurtenissen, schema's en andere triggers weer aan. Wat er gebeurde terwijl hij uit stond, loopt niet alsnog.",
2585
2797
  stopped: "Actieve runs worden geannuleerd, hun open voorstellen ingetrokken en wachtende gebeurtenissen vervallen. Goedgekeurde voorstellen wachten tot de agent weer werkt."
2586
2798
  },
2587
2799
  confirm: {
2588
2800
  active: "Hervatten bevestigen",
2589
2801
  paused: "Pauze bevestigen",
2802
+ off: "Uitzetten bevestigen",
2803
+ "switch-on": "Aanzetten bevestigen",
2590
2804
  stopped: "Stop bevestigen"
2591
2805
  },
2592
2806
  "note-label": "Notitie voor andere beheerders (optioneel)",
@@ -2599,6 +2813,8 @@ var nl = {
2599
2813
  explain: {
2600
2814
  active: "De vaardigheid {{skill}} werkt weer. Elk onderwerp met gebeurtenissen die erop wachten krijgt één run.",
2601
2815
  paused: "De vaardigheid {{skill}} start geen nieuwe runs: haar gebeurtenissen wachten, triage kiest haar niet meer en lopend werk wordt afgemaakt. De andere vaardigheden werken door.",
2816
+ off: "Triggers van de vaardigheid {{skill}} worden genegeerd en regels en triage sturen er geen werk meer heen; er wacht niets op haar. Lopend werk wordt afgemaakt. De andere vaardigheden werken door.",
2817
+ "switch-on": "De vaardigheid {{skill}} neemt weer werk aan. Wat er gebeurde terwijl ze uit stond, loopt niet alsnog.",
2602
2818
  stopped: "De runs van de vaardigheid {{skill}} worden geannuleerd en haar wachtende gebeurtenissen vervallen; haar triggers worden geweigerd. De andere vaardigheden werken door."
2603
2819
  }
2604
2820
  },
@@ -2675,7 +2891,10 @@ var nl = {
2675
2891
  "view-policy": "Resultaten voor de hele applicatie zichtbaar voor: {{policy}}."
2676
2892
  },
2677
2893
  groups: "Groepen: {{groups}}",
2678
- "catalog-failed": "De bouwstenen van agentruns konden niet worden gelezen. Tot dat lukt, kunnen agents hier niet nu draaien en blijven waar een agent aan werkt en zijn groepen zoals ze zijn."
2894
+ "catalog-failed": "De bouwstenen van agentruns konden niet worden gelezen. Tot dat lukt, kunnen agents hier niet nu draaien en blijven waar een agent aan werkt en zijn groepen zoals ze zijn.",
2895
+ warnings: {
2896
+ "classification-not-permitted": "Het modelprofiel van de agent en de terugvalprofielen daarvan staan zijn dataclassificatie niet toe. De agent is opgeslagen, maar zijn modelaanroepen worden geweigerd tot een profiel de classificatie toestaat."
2897
+ }
2679
2898
  },
2680
2899
  "agent-editor": {
2681
2900
  "new-title": "Nieuwe agent",
@@ -2690,7 +2909,14 @@ var nl = {
2690
2909
  "autonomy-hint": "Alleen Uitvoeren staat wijzigingen toe, en elke wijziging vraagt nog steeds om goedkeuring.",
2691
2910
  "required-policy": "Vereiste policy",
2692
2911
  "required-policy-hint": "Optioneel. Alleen gebruikers die aan deze policy voldoen zien de agent.",
2912
+ profile: "Modelprofiel",
2913
+ "profile-default": "Het chatprofiel van de applicatie",
2914
+ "profile-hint": "Het model waarmee de agent werkt, uit de profielen die de applicatie aanbiedt voor agents die hier gemaakt worden.",
2915
+ classification: "Dataclassificatie",
2916
+ "classification-hint": "Hoe gevoelig de gegevens zijn die de modelaanroepen van de agent bevatten, in chats met hem en in zijn runs. Zijn modelprofiel of een van de terugvalprofielen daarvan moet dit toestaan; een vaardigheid kan een eigen classificatie hebben.",
2693
2917
  "state-hint": "Pauzeer, stop en hervat de agent vanuit de lijst met agents. Opslaan verandert de stand nooit.",
2918
+ "switch-on-now": "Nu aanzetten",
2919
+ "switch-on-now-hint": "Anders begint de agent uit: zijn gebeurtenissen, schema's en andere triggers worden genegeerd tot je hem aanzet in de lijst met agents. Mensen kunnen nog met de agent chatten en hem handmatig starten.",
2694
2920
  "tool-selectors": "Toolselectors",
2695
2921
  "tool-selectors-hint": "Kies tools uit de catalogus of voeg een patroon toe, zoals sample-api.project.*. MCP-tools komen van de toegewezen servers.",
2696
2922
  "add-selector": "Patroon toevoegen",
@@ -2905,7 +3131,33 @@ var nl = {
2905
3131
  "client-secret-reference": "Verwijzing naar het clientgeheim",
2906
3132
  "organization-account-label": "Gekoppeld account",
2907
3133
  "organization-account-label-hint": "Welk account de organisatie koppelt, bijvoorbeeld \"Planning-serviceaccount\". Iedereen die de server gebruikt ziet het.",
2908
- "organization-account-label-required": "Noem het account dat gekoppeld wordt."
3134
+ "organization-account-label-required": "Noem het account dat gekoppeld wordt.",
3135
+ reader: {
3136
+ title: "Documenten lezen",
3137
+ hint: "Modellen die een bestandstype niet zelf kunnen lezen, kunnen het bestand als tekst van een leesprogramma van deze server krijgen. De assistent roept de tool van het leesprogramma aan met de bestandsnaam, het mediatype en de base64-inhoud van het bestand, na de eigen leesprogramma's van de applicatie.",
3138
+ empty: "Deze server leest geen bestanden.",
3139
+ item: "Leesprogramma {{number}}",
3140
+ add: "Leesprogramma toevoegen",
3141
+ "add-hint": "Elk leesprogramma is een van de gesynchroniseerde tools van de server; een server heeft er hoogstens {{max}}.",
3142
+ limit: "Deze server heeft het grootste aantal leesprogramma's dat kan ({{max}}).",
3143
+ remove: "Verwijderen",
3144
+ "remove-label": "Leesprogramma {{number}} verwijderen",
3145
+ "media-types": "Mediatypes",
3146
+ "media-types-hint": "Gescheiden door komma's, hoogstens 16, zoals application/vnd.openxmlformats-officedocument.wordprocessingml.document.",
3147
+ "media-types-invalid": "Vul 1 tot 16 mediatypes in van de vorm type/subtype, gescheiden door komma's.",
3148
+ tool: "Leesprogramma",
3149
+ "tool-choose": "Kies een tool",
3150
+ "tool-hint": "Een tool die van deze server is gesynchroniseerd en een bestand omzet in tekst. Hij hoeft niet voor agents aan te staan.",
3151
+ "tool-duplicate": "Een ander leesprogramma van deze server gebruikt deze tool al.",
3152
+ "tool-sync": "Synchroniseer eerst de tools van de server en voeg dan een leesprogramma toe.",
3153
+ priority: "Prioriteit",
3154
+ "priority-hint": "Leesprogramma's met een hogere prioriteit worden eerst gevraagd, na de eigen leesprogramma's van de applicatie; bij dezelfde prioriteit op server en toolnaam.",
3155
+ "priority-invalid": "Vul een geheel getal in van {{min}} tot {{max}}.",
3156
+ "max-classification": "Hoogste classificatie",
3157
+ "max-classification-choose": "Kies een classificatie",
3158
+ "max-classification-hint": "Een gevoeliger bestand wordt nooit naar deze server gestuurd.",
3159
+ "leaves-application": "Een bestand dat naar deze server gaat, verlaat de applicatie. Kies een classificatie die de server mag ontvangen; voor persoonlijke verbindingen blijft de toestemming van de persoon voor gebruik op de achtergrond gelden."
3160
+ }
2909
3161
  },
2910
3162
  tools: {
2911
3163
  title: "Tools van {{name}}",
@@ -3053,6 +3305,19 @@ var nl = {
3053
3305
  "replay-progress": "{{replayed}} van hoogstens {{max}} runs herhaald, {{skipped}} overgeslagen"
3054
3306
  },
3055
3307
  "run-config": {
3308
+ files: {
3309
+ label: "Neemt bestanden aan",
3310
+ "agent-hint": "Berichten van mensen en de tools van de agent mogen hem documenten geven, zoals pdf's, binnen de limieten van de applicatie. Een nieuwe agent neemt er geen aan totdat je dit aanzet.",
3311
+ "skill-hint": "Runs in deze vaardigheid mogen documenten lezen als de agent ze ook aanneemt. Een nieuwe vaardigheid neemt er geen aan totdat je dit aanzet.",
3312
+ "limits-title": "Lagere bestandslimieten",
3313
+ "agent-limits-hint": "Laat een veld leeg om de limiet van de applicatie te houden. Een waarde kan die alleen verlagen.",
3314
+ "skill-limits-hint": "Laat een veld leeg om de limiet van de agent te houden. Een waarde kan die van de applicatie alleen verlagen.",
3315
+ "max-file-megabytes": "Grootste bestand (MB)",
3316
+ "max-file-pages": "Meeste pagina's per bestand",
3317
+ "max-files-per-turn": "Meeste bestanden per beurt",
3318
+ "max-file-tokens-per-turn": "Meeste bestandstokens per beurt",
3319
+ "application-limit": "De applicatie staat hooguit {{value}} toe."
3320
+ },
3056
3321
  back: "Terug naar agents",
3057
3322
  title: "Runconfiguratie van {{name}}",
3058
3323
  description: "Vaardigheden en triggers die het beheer zonder deploy toevoegt, gemaakt van de bouwstenen die de applicatie registreert. Nieuwe vaardigheden en triggers starten in schaduw, waar niemand een melding krijgt en niets wordt uitgevoerd.",
@@ -3091,7 +3356,9 @@ var nl = {
3091
3356
  taken: "Deze id is al in gebruik.",
3092
3357
  "name-letters": "Gebruik minstens één letter in de naam.",
3093
3358
  "instructions-for-model": "Zonder vaste regels werkt het model met deze instructies; beschrijf wat het moet doen.",
3094
- "application-not-worked-on": "De agent werkt niet aan de hele applicatie. Kies een verzameling objecten, of laat de agent eerst aan de hele applicatie werken."
3359
+ "application-not-worked-on": "De agent werkt niet aan de hele applicatie. Kies een verzameling objecten, of laat de agent eerst aan de hele applicatie werken.",
3360
+ "megabytes-up-to": "Vul een grootte in boven 0 en hooguit {{max}} MB.",
3361
+ "megabytes-positive": "Vul een grootte in boven 0 MB."
3095
3362
  },
3096
3363
  settings: {
3097
3364
  title: "Runinstellingen",
@@ -3192,6 +3459,8 @@ var nl = {
3192
3459
  "apply-shadow": "Eerst in schaduw beoordelen naast de live versie",
3193
3460
  "apply-live": "Direct live zetten",
3194
3461
  "starts-in-shadow": "Een nieuwe vaardigheid start in schaduw.",
3462
+ "switch-on-now": "Nu aanzetten",
3463
+ "switch-on-now-hint": "Anders begint de vaardigheid uit: triggers die haar noemen worden genegeerd en regels en triage sturen er niets heen tot je haar aanzet in de lijst met agents.",
3195
3464
  versions: "Versies",
3196
3465
  "remove-explain": "Haar versies blijven bewaard. Een trigger die haar start moet eerst weg.",
3197
3466
  what: {
@@ -3203,6 +3472,22 @@ var nl = {
3203
3472
  "rules-hint": "De regels beslissen zonder model; een twijfelgeval gaat naar het model met de instructies hieronder.",
3204
3473
  "model-hint": "Het model beslist met de instructies hieronder en de gekozen tools.",
3205
3474
  "profile-hint": "Het modelprofiel dat hij gebruikt; standaard dat van de agent.",
3475
+ classification: {
3476
+ label: "Dataclassificatie",
3477
+ agent: "De classificatie van de agent",
3478
+ hint: "Hoe gevoelig de gegevens zijn die de vaardigheid naar haar model stuurt. Haar modelprofiel moet dit toestaan; anders worden haar modelaanroepen geweigerd.",
3479
+ "public": "Openbaar",
3480
+ "public-hint": "Gegevens die iedereen mag zien.",
3481
+ internal: "Intern",
3482
+ "internal-hint": "Gegevens van de organisatie; de standaard.",
3483
+ confidential: "Vertrouwelijk",
3484
+ "confidential-hint": "Gegevens die alleen sommige mensen mogen zien, zoals persoonlijke of financiële gegevens.",
3485
+ restricted: "Strikt vertrouwelijk",
3486
+ "restricted-hint": "De gevoeligste gegevens, waarvoor alleen speciaal goedgekeurde modellen gebruikt mogen worden."
3487
+ },
3488
+ warnings: {
3489
+ "classification-not-permitted": "Het modelprofiel van de vaardigheid en de terugvalprofielen daarvan staan haar dataclassificatie niet toe. De vaardigheid is opgeslagen, maar haar modelaanroepen worden geweigerd tot een profiel de classificatie toestaat."
3490
+ },
3206
3491
  "apply-shadow-hint": "De nieuwe versie draait naast de live versie; vergelijk ze en promoveer of verwerp hem daarna.",
3207
3492
  "apply-live-hint": "Voor een kleine correctie: de nieuwe versie vervangt de live versie meteen.",
3208
3493
  "tools-pattern": "Alle tools die passen: nu {{count}}.",
@@ -4080,6 +4365,11 @@ class NhAssistantSseParser {
4080
4365
  }
4081
4366
  }
4082
4367
 
4368
+ /**
4369
+ * The request header every upload carries. A browser cannot send it with a cross-site form, so the
4370
+ * upload endpoint keeps the posture of the JSON endpoints.
4371
+ */
4372
+ const NH_ASSISTANT_UPLOAD_HEADER = 'X-NH-Assistant-Upload';
4083
4373
  /** A failed JSON request of the assistant API. Carries a stable code, never response text. */
4084
4374
  class NhAssistantApiError extends Error {
4085
4375
  constructor(status, code, messageKey,
@@ -4126,6 +4416,8 @@ function nhAssistantCodeForStatus(status) {
4126
4416
  }
4127
4417
  const safeCode = /^[a-z0-9][a-z0-9.-]{0,79}$/;
4128
4418
  const safeField = /^[a-zA-Z][a-zA-Z0-9.]{0,63}$/;
4419
+ /** A field of one item of a list, such as `readers[0].toolName`. */
4420
+ const safeItemField = /^[a-zA-Z][a-zA-Z0-9]{0,31}\[\d{1,4}\](\.[a-zA-Z][a-zA-Z0-9]{0,31})?$/;
4129
4421
  /**
4130
4422
  * Whether a value the server or a callback page sent is a code the library may show: lowercase
4131
4423
  * letters, digits, dots and dashes, at most 80 characters. Every reader of untrusted codes uses this
@@ -4142,6 +4434,7 @@ class NhAssistantTransport {
4142
4434
  constructor() {
4143
4435
  this.config = inject(NH_ASSISTANT_CONFIG);
4144
4436
  this.fetchFn = inject(NH_ASSISTANT_FETCH);
4437
+ this.uploadFn = inject(NH_ASSISTANT_UPLOAD);
4145
4438
  this.translate = inject(TranslateService, { optional: true });
4146
4439
  this.injector = inject(EnvironmentInjector);
4147
4440
  }
@@ -4157,6 +4450,36 @@ class NhAssistantTransport {
4157
4450
  const init = await this.createInit(method, accept, body, signal);
4158
4451
  return this.fetchFn(this.url(path), init);
4159
4452
  }
4453
+ /**
4454
+ * Uploads `form` with progress and resolves with the parsed JSON answer. Rejects with
4455
+ * `NhAssistantApiError`, never with raw text: the code of a refused upload's error body, the
4456
+ * generic status mapping without one and `assistant-network` when it did not reach the server.
4457
+ * An aborted upload rejects with an `AbortError`.
4458
+ */
4459
+ async upload(path, form, onProgress, signal) {
4460
+ const headers = await this.headers('application/json');
4461
+ headers[NH_ASSISTANT_UPLOAD_HEADER] = '1';
4462
+ let response;
4463
+ try {
4464
+ response = await this.uploadFn({ url: this.url(path), headers, body: form, signal, onProgress });
4465
+ }
4466
+ catch (error) {
4467
+ if (signal.aborted) {
4468
+ throw error;
4469
+ }
4470
+ throw createApiError(0, nhAssistantCodeForStatus(0));
4471
+ }
4472
+ if (!response.ok) {
4473
+ const failure = await this.failure(response, {});
4474
+ throw createApiError(response.status, failure.code, failure.fieldErrors);
4475
+ }
4476
+ try {
4477
+ return JSON.parse(await response.text());
4478
+ }
4479
+ catch {
4480
+ throw createApiError(response.status, NhAssistantClientErrorCodes.invalidResponse);
4481
+ }
4482
+ }
4160
4483
  /**
4161
4484
  * Downloads a file of the assistant API. Rejects with `NhAssistantApiError`, never with raw text:
4162
4485
  * the code and field errors of a refused request's error body (whatever its content type), the
@@ -4203,7 +4526,7 @@ class NhAssistantTransport {
4203
4526
  if (errors && typeof errors === 'object' && !Array.isArray(errors)) {
4204
4527
  for (const [field, values] of Object.entries(errors)) {
4205
4528
  const safe = Array.isArray(values) ? values.filter(nhAssistantIsSafeCode) : [];
4206
- if (safeField.test(field) && safe.length > 0) {
4529
+ if ((safeField.test(field) || safeItemField.test(field)) && safe.length > 0) {
4207
4530
  fieldErrors[field] = safe;
4208
4531
  }
4209
4532
  }
@@ -4246,16 +4569,7 @@ class NhAssistantTransport {
4246
4569
  }
4247
4570
  }
4248
4571
  async createInit(method, accept, body, signal) {
4249
- const headers = await nhAssistantRequestHeaders(this.config, this.injector);
4250
- headers['Accept'] = accept;
4251
- const token = await runInInjectionContext(this.injector, () => this.config.getAccessToken());
4252
- if (token) {
4253
- headers['Authorization'] = `Bearer ${token}`;
4254
- }
4255
- const language = this.translate?.getCurrentLang();
4256
- if (language) {
4257
- headers['Accept-Language'] = language;
4258
- }
4572
+ const headers = await this.headers(accept);
4259
4573
  if (body !== undefined) {
4260
4574
  headers['Content-Type'] = 'application/json';
4261
4575
  }
@@ -4267,6 +4581,20 @@ class NhAssistantTransport {
4267
4581
  cache: 'no-store'
4268
4582
  };
4269
4583
  }
4584
+ /** The host's extra headers, `Accept`, the bearer token and the current language. */
4585
+ async headers(accept) {
4586
+ const headers = await nhAssistantRequestHeaders(this.config, this.injector);
4587
+ headers['Accept'] = accept;
4588
+ const token = await runInInjectionContext(this.injector, () => this.config.getAccessToken());
4589
+ if (token) {
4590
+ headers['Authorization'] = `Bearer ${token}`;
4591
+ }
4592
+ const language = this.translate?.getCurrentLang();
4593
+ if (language) {
4594
+ headers['Accept-Language'] = language;
4595
+ }
4596
+ return headers;
4597
+ }
4270
4598
  url(path) {
4271
4599
  const base = this.config.apiBaseUrl.replace(/\/+$/, '');
4272
4600
  return `${base}/${path}`;
@@ -4300,6 +4628,8 @@ async function nhAssistantRequestHeaders(config, injector) {
4300
4628
  }
4301
4629
 
4302
4630
  const knownEventTypes = new Set(NH_ASSISTANT_SSE_EVENT_TYPES);
4631
+ /** The outcomes of a trigger receipt with which the next run takes a waiting message. */
4632
+ const queuedOutcomes = new Set(['started', 'queued', 'coalesced', 'superseded', 'waiting', 'deferred']);
4303
4633
  /**
4304
4634
  * Client for the assistant HTTP API. JSON endpoints return cold observables that abort
4305
4635
  * their request on unsubscribe. Streaming endpoints emit every contract event and complete
@@ -4330,11 +4660,33 @@ class NhAssistantApiService {
4330
4660
  return this.transport.json('DELETE', `conversations/${encodeURIComponent(conversationId)}`);
4331
4661
  }
4332
4662
  sendMessage(conversationId, request) {
4333
- return this.requestStream(`conversations/${encodeURIComponent(conversationId)}/messages`, request);
4663
+ return this.requestStream(`conversations/${encodeURIComponent(conversationId)}/messages`, request, true);
4664
+ }
4665
+ /**
4666
+ * Uploads one file to a conversation as `multipart/form-data` (`POST conversations/{id}/files`):
4667
+ * emits the progress while the file is sent and then the stored file, whose reference a message
4668
+ * carries in `files`. Fails with `NhAssistantApiError`; unsubscribing aborts the upload. The
4669
+ * media type of `file` is the one the upload declares.
4670
+ */
4671
+ uploadFile(conversationId, file, name) {
4672
+ return new Observable(subscriber => {
4673
+ const abort = new AbortController();
4674
+ const form = new FormData();
4675
+ form.append('file', file, name);
4676
+ this.transport.upload(`conversations/${encodeURIComponent(conversationId)}/files`, form, (loaded, total) => subscriber.next({ type: 'progress', loaded, total }), abort.signal).then(reference => {
4677
+ subscriber.next({ type: 'uploaded', file: reference });
4678
+ subscriber.complete();
4679
+ }, error => {
4680
+ if (!abort.signal.aborted) {
4681
+ subscriber.error(error);
4682
+ }
4683
+ });
4684
+ return () => abort.abort();
4685
+ });
4334
4686
  }
4335
4687
  decideApproval(conversationId, approvalId, request) {
4336
4688
  const path = `conversations/${encodeURIComponent(conversationId)}/approvals/${encodeURIComponent(approvalId)}/decide`;
4337
- return this.requestStream(path, request);
4689
+ return this.requestStream(path, request, false);
4338
4690
  }
4339
4691
  cancel(conversationId) {
4340
4692
  return this.transport.json('POST', `conversations/${encodeURIComponent(conversationId)}/cancel`);
@@ -4416,14 +4768,14 @@ class NhAssistantApiService {
4416
4768
  completeMcpAuthorization(request) {
4417
4769
  return this.transport.json('POST', 'mcp-connections/complete', request);
4418
4770
  }
4419
- requestStream(path, body) {
4771
+ requestStream(path, body, queuedReceipt) {
4420
4772
  return new Observable(subscriber => {
4421
4773
  const abort = new AbortController();
4422
- void this.executeStream(path, body, abort.signal, subscriber);
4774
+ void this.executeStream(path, body, queuedReceipt, abort.signal, subscriber);
4423
4775
  return () => abort.abort();
4424
4776
  });
4425
4777
  }
4426
- async executeStream(path, body, signal, subscriber) {
4778
+ async executeStream(path, body, queuedReceipt, signal, subscriber) {
4427
4779
  let response;
4428
4780
  try {
4429
4781
  response = await this.transport.fetch('POST', path, 'text/event-stream', body, signal);
@@ -4436,6 +4788,10 @@ class NhAssistantApiService {
4436
4788
  this.emitClientError(subscriber, signal, await this.transport.failureCode(response));
4437
4789
  return;
4438
4790
  }
4791
+ if (queuedReceipt && response.status === 202) {
4792
+ await this.readQueuedReceipt(response, signal, subscriber);
4793
+ return;
4794
+ }
4439
4795
  const contentType = response.headers.get('content-type') ?? '';
4440
4796
  if (!response.body || !contentType.toLowerCase().startsWith('text/event-stream')) {
4441
4797
  this.emitClientError(subscriber, signal, NhAssistantClientErrorCodes.invalidResponse);
@@ -4443,6 +4799,33 @@ class NhAssistantApiService {
4443
4799
  }
4444
4800
  await this.readStream(response.body, signal, subscriber);
4445
4801
  }
4802
+ /**
4803
+ * A message to an agent's thread whose run works waits for the next run: the server answers `202`
4804
+ * with the trigger receipt instead of a stream. A receipt the run takes the message with becomes
4805
+ * `message.queued`; one that refused it becomes an `error` with its code.
4806
+ */
4807
+ async readQueuedReceipt(response, signal, subscriber) {
4808
+ let receipt;
4809
+ try {
4810
+ receipt = JSON.parse(await response.text());
4811
+ }
4812
+ catch {
4813
+ receipt = null;
4814
+ }
4815
+ if (signal.aborted || subscriber.closed) {
4816
+ return;
4817
+ }
4818
+ if (!receipt || typeof receipt.outcome !== 'string') {
4819
+ this.emitClientError(subscriber, signal, NhAssistantClientErrorCodes.invalidResponse);
4820
+ return;
4821
+ }
4822
+ if (!queuedOutcomes.has(receipt.outcome)) {
4823
+ this.emitClientError(subscriber, signal, nhAssistantIsSafeCode(receipt.reasonCode) ? receipt.reasonCode : NhAssistantClientErrorCodes.conversationBusy);
4824
+ return;
4825
+ }
4826
+ subscriber.next({ type: 'message.queued', data: receipt });
4827
+ subscriber.complete();
4828
+ }
4446
4829
  async readStream(body, signal, subscriber) {
4447
4830
  const reader = body.getReader();
4448
4831
  const decoder = new TextDecoder();
@@ -5860,6 +6243,89 @@ function sameKey(current, expected) {
5860
6243
  return bytes.length === expected.length && bytes.every((byte, index) => byte === expected[index]);
5861
6244
  }
5862
6245
 
6246
+ /** The longest file name a file reference takes. */
6247
+ const NH_ASSISTANT_MAX_FILE_NAME_LENGTH = 256;
6248
+ const unknownMediaType = 'application/octet-stream';
6249
+ /** The media types the platform names, with the extensions a file picker offers and a short label. */
6250
+ const knownTypes = {
6251
+ 'application/pdf': { extensions: ['.pdf'], label: 'PDF' },
6252
+ 'image/png': { extensions: ['.png'], label: 'PNG' },
6253
+ 'image/jpeg': { extensions: ['.jpg', '.jpeg'], label: 'JPEG' },
6254
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': { extensions: ['.docx'], label: 'Word' },
6255
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': { extensions: ['.xlsx'], label: 'Excel' }
6256
+ };
6257
+ /**
6258
+ * The media type of a chosen file, lowercase and without parameters. A browser that does not know
6259
+ * the type, as happens for Office documents, gives none; the extension of a known type then names
6260
+ * it. Otherwise `application/octet-stream`.
6261
+ */
6262
+ function nhAssistantMediaTypeOf(file) {
6263
+ const declared = file.type.split(';')[0].trim().toLowerCase();
6264
+ if (declared.length > 0 && declared !== unknownMediaType) {
6265
+ return declared;
6266
+ }
6267
+ const name = file.name.toLowerCase();
6268
+ for (const [mediaType, known] of Object.entries(knownTypes)) {
6269
+ if (known.extensions.some(extension => name.endsWith(extension))) {
6270
+ return mediaType;
6271
+ }
6272
+ }
6273
+ return unknownMediaType;
6274
+ }
6275
+ /**
6276
+ * The failure code of a file before it is uploaded, or `null` when it may be: the most files of one
6277
+ * message, an accepted media type, content and a usable name, and the largest size. `attached` counts
6278
+ * the files the message already carries.
6279
+ */
6280
+ function nhAssistantFileProblem(file, mediaType, uploads, attached) {
6281
+ if (attached >= uploads.maxFiles) {
6282
+ return 'assistant-file-count-limit';
6283
+ }
6284
+ if (!uploads.acceptedMediaTypes.includes(mediaType)) {
6285
+ return 'assistant-file-type-not-accepted';
6286
+ }
6287
+ if (file.size === 0 || file.name.trim().length === 0 || file.name.length > NH_ASSISTANT_MAX_FILE_NAME_LENGTH) {
6288
+ return 'assistant-file-upload-invalid';
6289
+ }
6290
+ if (file.size > uploads.maxFileBytes) {
6291
+ return 'assistant-file-too-large';
6292
+ }
6293
+ return null;
6294
+ }
6295
+ /** The `accept` attribute of a file input for these media types: each type and its known extensions. */
6296
+ function nhAssistantAcceptAttribute(mediaTypes) {
6297
+ return mediaTypes
6298
+ .flatMap(mediaType => [mediaType, ...(knownTypes[mediaType]?.extensions ?? [])])
6299
+ .join(',');
6300
+ }
6301
+ /** A short name of a media type, such as `PDF` or `Word`; otherwise its subtype in capitals. */
6302
+ function nhAssistantFileTypeLabel(mediaType) {
6303
+ const known = knownTypes[mediaType];
6304
+ if (known) {
6305
+ return known.label;
6306
+ }
6307
+ const subtype = mediaType.split('/')[1] ?? mediaType;
6308
+ return subtype.split(/[.+]/).pop().toUpperCase();
6309
+ }
6310
+ /** A size in bytes as people read it, such as `820 KB` or `1.4 MB`, in the given language. */
6311
+ function nhAssistantFormatFileSize(bytes, language) {
6312
+ const units = ['B', 'KB', 'MB', 'GB'];
6313
+ let value = Math.max(0, bytes);
6314
+ let unit = 0;
6315
+ while (value >= 1024 && unit < units.length - 1) {
6316
+ value /= 1024;
6317
+ unit++;
6318
+ }
6319
+ let format;
6320
+ try {
6321
+ format = new Intl.NumberFormat(language || undefined, { maximumFractionDigits: unit === 0 || value >= 10 ? 0 : 1 });
6322
+ }
6323
+ catch {
6324
+ format = new Intl.NumberFormat(undefined, { maximumFractionDigits: unit === 0 || value >= 10 ? 0 : 1 });
6325
+ }
6326
+ return `${format.format(value)} ${units[unit]}`;
6327
+ }
6328
+
5863
6329
  /**
5864
6330
  * Applies one assistant event to a conversation and returns the new conversation.
5865
6331
  * The input is never mutated, so the result can be stored in a signal directly.
@@ -6291,6 +6757,11 @@ function validId(value) {
6291
6757
 
6292
6758
  /** Notice code for a completed turn that produced no answer text and no server code. */
6293
6759
  const NH_ASSISTANT_NO_ANSWER_NOTICE_CODE = 'assistant-no-answer';
6760
+ /**
6761
+ * Notice code for a message that waits for the next run of the agent working in its thread: the
6762
+ * server answered `202` with the trigger receipt, and the run adds the message, with its files.
6763
+ */
6764
+ const NH_ASSISTANT_MESSAGE_QUEUED_NOTICE_CODE = 'assistant-message-queued';
6294
6765
  const fourEyesRequiredCode = 'assistant-four-eyes-required';
6295
6766
  /** True when a conversation has messages the caller has not read. A running turn is not unread yet. */
6296
6767
  function nhAssistantIsUnread(summary) {
@@ -6355,7 +6826,12 @@ class NhAssistantStore {
6355
6826
  this.submittingState = signal(false, ...(ngDevMode ? [{ debugName: "submittingState" }] : []));
6356
6827
  /** Agent proposals the current user rejected in this session and may give feedback on. */
6357
6828
  this.feedbackApprovalsState = signal(new Set(), ...(ngDevMode ? [{ debugName: "feedbackApprovalsState" }] : []));
6829
+ /** The attachments of the next message per conversation; `''` for a new conversation. */
6830
+ this.attachmentsState = signal(new Map(), ...(ngDevMode ? [{ debugName: "attachmentsState" }] : []));
6358
6831
  this.destroyed = false;
6832
+ this.uploadSubscriptions = new Map();
6833
+ /** The conversation an attachment of a new conversation creates, once for every file chosen meanwhile. */
6834
+ this.attachmentConversation = null;
6359
6835
  this.streams = new Map();
6360
6836
  this.cancelTimers = new Map();
6361
6837
  /** Pending reloads of agent threads after live agent-work updates, per conversation. */
@@ -6434,6 +6910,31 @@ class NhAssistantStore {
6434
6910
  && ['running', 'waiting-for-approval'].includes(conversation?.status ?? '')
6435
6911
  && (this.isOwner() || ownsTurn);
6436
6912
  }, ...(ngDevMode ? [{ debugName: "canSteer" }] : []));
6913
+ /**
6914
+ * What the person may attach to the next message: the uploads of the open conversation's agent, or
6915
+ * of the selected agent for a new conversation. `null` when nothing can be attached: the server
6916
+ * offers no uploads for the agent, the conversation is archived, or the person only reads the
6917
+ * agent's thread.
6918
+ */
6919
+ this.uploads = computed(() => {
6920
+ const conversation = this.activeConversation();
6921
+ const thread = conversation?.thread;
6922
+ if (!this.enabled() || conversation?.status === 'archived'
6923
+ || (thread && thread.role !== 'participant' && thread.role !== 'approver')) {
6924
+ return null;
6925
+ }
6926
+ const agentId = conversation?.agentId ?? this.selectedAgentIdState();
6927
+ const uploads = this.agents().find(agent => agent.id === agentId)?.uploads ?? null;
6928
+ return uploads && uploads.acceptedMediaTypes.length > 0 && uploads.maxFiles > 0 ? uploads : null;
6929
+ }, ...(ngDevMode ? [{ debugName: "uploads" }] : []));
6930
+ /** The attachments of the next message of the open conversation, in the order they were chosen. */
6931
+ this.attachments = computed(() => this.attachmentsState().get(this.activeIdState() ?? newConversationKey) ?? [], ...(ngDevMode ? [{ debugName: "attachments" }] : []));
6932
+ /** The stored files of the open conversation's next message, for `submit`. */
6933
+ this.attachedFiles = computed(() => this.attachments()
6934
+ .filter(attachment => attachment.state === 'uploaded' && attachment.file !== null)
6935
+ .map(attachment => attachment.file), ...(ngDevMode ? [{ debugName: "attachedFiles" }] : []));
6936
+ /** True while a file of the next message still uploads; the message waits for it. */
6937
+ this.uploading = computed(() => this.attachments().some(attachment => attachment.state === 'uploading'), ...(ngDevMode ? [{ debugName: "uploading" }] : []));
6437
6938
  /** The owner and participants of the open conversation; empty while it is not shared. */
6438
6939
  this.members = computed(() => this.activeConversation()?.members ?? [], ...(ngDevMode ? [{ debugName: "members" }] : []));
6439
6940
  /** True when the current user owns the open conversation (or it is new). */
@@ -6534,6 +7035,9 @@ class NhAssistantStore {
6534
7035
  this.liveSubscription.unsubscribe();
6535
7036
  this.stopAllTurns();
6536
7037
  this.clearAgentWorkTimers();
7038
+ for (const upload of this.uploadSubscriptions.values()) {
7039
+ upload.unsubscribe();
7040
+ }
6537
7041
  clearTimeout(this.refreshTimer);
6538
7042
  });
6539
7043
  effect(() => {
@@ -6722,26 +7226,28 @@ class NhAssistantStore {
6722
7226
  /**
6723
7227
  * Sends a user message. Creates a conversation with the selected agent when none is open,
6724
7228
  * shows the message optimistically and applies the streamed events. Other conversations keep
6725
- * their own turns. Resolves `false` when the message was not accepted.
7229
+ * their own turns. Resolves `false` when the message was not accepted. `files` are uploaded
7230
+ * attachments, such as `attachedFiles()`; once the message is accepted, the attachments of the
7231
+ * conversation leave the composer.
6726
7232
  */
6727
- async send(text) {
7233
+ async send(text, files = []) {
6728
7234
  // Browsers only ask for notification permission during a user action such as this one.
6729
7235
  void this.push.onUserGesture().catch(() => undefined);
6730
- return this.sendMessage(text);
7236
+ return this.sendMessage(text, undefined, files);
6731
7237
  }
6732
7238
  /**
6733
7239
  * Sends immediately when the conversation accepts a message, otherwise appends to the
6734
7240
  * conversation's FIFO queue. A shown error alone does not hold a message back while nothing is
6735
7241
  * queued: the person asks again, for example after a failed turn. Queued messages still wait
6736
- * for an explicit `resumeQueue` after Stop or a failure.
7242
+ * for an explicit `resumeQueue` after Stop or a failure. A queued message keeps its `files`.
6737
7243
  */
6738
- async submit(text) {
7244
+ async submit(text, files = []) {
6739
7245
  const stopped = this.pausedQueuesState().has(this.activeIdState() ?? newConversationKey);
6740
7246
  if (this.canSend() && this.queuedMessages().length === 0 && !stopped && !this.submittingState()) {
6741
7247
  const revision = this.accountRevision;
6742
7248
  this.submittingState.set(true);
6743
7249
  try {
6744
- return await this.send(text);
7250
+ return await this.send(text, files);
6745
7251
  }
6746
7252
  finally {
6747
7253
  if (revision === this.accountRevision && !this.destroyed) {
@@ -6756,8 +7262,15 @@ class NhAssistantStore {
6756
7262
  }
6757
7263
  void this.push.onUserGesture().catch(() => undefined);
6758
7264
  const revision = this.accountRevision;
6759
- const item = { id: createClientMessageId(), conversationId, text: trimmed, state: 'preparing' };
7265
+ const item = {
7266
+ id: createClientMessageId(),
7267
+ conversationId,
7268
+ text: trimmed,
7269
+ state: 'preparing',
7270
+ ...(files.length > 0 ? { files: [...files] } : {})
7271
+ };
6760
7272
  this.queuedState.update(items => [...items, item]);
7273
+ this.releaseAttachments(conversationId, files);
6761
7274
  const clientContext = await this.contextForMessage();
6762
7275
  if (revision !== this.accountRevision || this.destroyed) {
6763
7276
  return false;
@@ -6820,7 +7333,7 @@ class NhAssistantStore {
6820
7333
  const revision = this.accountRevision;
6821
7334
  this.queueDispatchState.set(item.id);
6822
7335
  this.queuedState.update(items => items.map(current => current.id === item.id ? { ...current, state: 'sending' } : current));
6823
- const accepted = await this.sendMessage(item.text, item);
7336
+ const accepted = await this.sendMessage(item.text, item, item.files ?? []);
6824
7337
  if (revision !== this.accountRevision || this.destroyed) {
6825
7338
  return;
6826
7339
  }
@@ -6831,7 +7344,7 @@ class NhAssistantStore {
6831
7344
  }
6832
7345
  this.queueDispatchState.set(null);
6833
7346
  }
6834
- async sendMessage(text, queued) {
7347
+ async sendMessage(text, queued, files) {
6835
7348
  const trimmed = text.trim();
6836
7349
  const accountRevision = this.accountRevision;
6837
7350
  await this.initialize();
@@ -6871,6 +7384,10 @@ class NhAssistantStore {
6871
7384
  const conversationId = conversation.id;
6872
7385
  const clientMessageId = createClientMessageId();
6873
7386
  const previousStatus = conversation.status;
7387
+ const parts = [
7388
+ { type: 'text', text: trimmed },
7389
+ ...files.map(file => ({ type: 'file', name: file.name, mediaType: file.mediaType, size: file.size }))
7390
+ ];
6874
7391
  this.patchSession(conversationId, session => ({
6875
7392
  ...session,
6876
7393
  error: null,
@@ -6884,7 +7401,7 @@ class NhAssistantStore {
6884
7401
  id: clientMessageId,
6885
7402
  role: 'user',
6886
7403
  createdAt: new Date().toISOString(),
6887
- parts: [{ type: 'text', text: trimmed }],
7404
+ parts,
6888
7405
  authorActorId: session.conversation.currentActorId ?? null
6889
7406
  }
6890
7407
  ]
@@ -6895,12 +7412,24 @@ class NhAssistantStore {
6895
7412
  this.pendingSendResolves.set(conversationId, resolve);
6896
7413
  let started = false;
6897
7414
  let failedAfterStart = false;
7415
+ let waitsForRun = false;
6898
7416
  this.runStream(conversationId, this.api.sendMessage(conversationId, {
6899
7417
  text: trimmed,
6900
7418
  clientMessageId,
6901
- ...(clientContext === undefined ? {} : { clientContext })
7419
+ ...(clientContext === undefined ? {} : { clientContext }),
7420
+ ...(files.length > 0 ? { files: [...files] } : {})
6902
7421
  }), {
6903
7422
  next: event => {
7423
+ if (event.type === 'message.queued') {
7424
+ // An agent's run works in the thread: the message, with its files, waits for the next run,
7425
+ // which adds it to the thread. Until then the notice says so.
7426
+ waitsForRun = true;
7427
+ this.removeMessage(conversationId, clientMessageId, previousStatus);
7428
+ this.releaseAttachments(conversationId, files);
7429
+ this.patchSession(conversationId, session => ({ ...session, notice: messageQueuedNotice(files.length) }));
7430
+ resolve(true);
7431
+ return;
7432
+ }
6904
7433
  if (event.type === 'error' && !started) {
6905
7434
  this.removeMessage(conversationId, clientMessageId, previousStatus);
6906
7435
  if (!queued && this.activeIdState() === conversationId) {
@@ -6912,6 +7441,8 @@ class NhAssistantStore {
6912
7441
  }
6913
7442
  if (event.type === 'turn.started') {
6914
7443
  started = true;
7444
+ // The message carries the files now; they leave the composer.
7445
+ this.releaseAttachments(conversationId, files);
6915
7446
  resolve(true);
6916
7447
  }
6917
7448
  if (event.type === 'error') {
@@ -6921,8 +7452,9 @@ class NhAssistantStore {
6921
7452
  },
6922
7453
  complete: () => {
6923
7454
  this.pendingSendResolves.delete(conversationId);
6924
- resolve(started);
6925
- this.afterTurn(conversationId, failedAfterStart);
7455
+ resolve(started || waitsForRun);
7456
+ // A waiting message started no turn here: the thread's own state comes from the server.
7457
+ this.afterTurn(conversationId, failedAfterStart || waitsForRun);
6926
7458
  }
6927
7459
  });
6928
7460
  this.sendingState.set(null);
@@ -7210,6 +7742,159 @@ class NhAssistantStore {
7210
7742
  this.restoredDraftState.set(null);
7211
7743
  return draft;
7212
7744
  }
7745
+ /**
7746
+ * Attaches files to the next message of the open conversation and uploads them at once. Each
7747
+ * file is checked first against `uploads()`: the most files of one message, the accepted media
7748
+ * types and the largest size; a file that fails stays as a failed attachment with its code and
7749
+ * is never uploaded. A new conversation is created first, because an upload belongs to one.
7750
+ */
7751
+ async attach(files) {
7752
+ const uploads = this.uploads();
7753
+ if (!uploads || files.length === 0) {
7754
+ return;
7755
+ }
7756
+ const revision = this.accountRevision;
7757
+ const key = this.activeIdState() ?? newConversationKey;
7758
+ let attached = this.attachmentsOf(key).filter(attachment => attachment.state !== 'failed').length;
7759
+ const added = [];
7760
+ const pending = [];
7761
+ for (const file of files) {
7762
+ const mediaType = nhAssistantMediaTypeOf(file);
7763
+ const problem = nhAssistantFileProblem(file, mediaType, uploads, attached);
7764
+ const attachment = {
7765
+ id: createClientMessageId(),
7766
+ name: file.name,
7767
+ mediaType,
7768
+ size: file.size,
7769
+ state: problem ? 'failed' : 'uploading',
7770
+ progress: 0,
7771
+ file: null,
7772
+ error: problem ? clientError(problem) : null
7773
+ };
7774
+ added.push(attachment);
7775
+ if (!problem) {
7776
+ attached++;
7777
+ // The upload declares the media type the check accepted, also when the browser gave none.
7778
+ pending.push({ attachment, content: file.slice(0, file.size, mediaType) });
7779
+ }
7780
+ }
7781
+ this.patchAttachments(key, attachments => [...attachments, ...added]);
7782
+ if (pending.length === 0) {
7783
+ return;
7784
+ }
7785
+ let conversationId = this.activeIdState();
7786
+ if (conversationId === null) {
7787
+ this.attachmentConversation ??= this.createConversation().finally(() => {
7788
+ this.attachmentConversation = null;
7789
+ });
7790
+ const conversation = await this.attachmentConversation;
7791
+ if (revision !== this.accountRevision || this.destroyed) {
7792
+ return;
7793
+ }
7794
+ if (!conversation) {
7795
+ const error = this.draftErrorState() ?? clientError(NhAssistantClientErrorCodes.server);
7796
+ this.patchAttachments(key, attachments => attachments.map(attachment => pending.some(item => item.attachment.id === attachment.id)
7797
+ ? { ...attachment, state: 'failed', error }
7798
+ : attachment));
7799
+ return;
7800
+ }
7801
+ conversationId = conversation.id;
7802
+ this.moveAttachments(key, conversationId);
7803
+ }
7804
+ for (const { attachment, content } of pending) {
7805
+ this.startUpload(conversationId, attachment, content);
7806
+ }
7807
+ }
7808
+ /** Removes an attachment of the next message; an upload in progress stops. */
7809
+ removeAttachment(attachmentId) {
7810
+ this.uploadSubscriptions.get(attachmentId)?.unsubscribe();
7811
+ this.uploadSubscriptions.delete(attachmentId);
7812
+ this.attachmentsState.update(all => {
7813
+ const next = new Map();
7814
+ for (const [key, attachments] of all) {
7815
+ const kept = attachments.filter(attachment => attachment.id !== attachmentId);
7816
+ if (kept.length > 0) {
7817
+ next.set(key, kept);
7818
+ }
7819
+ }
7820
+ return next;
7821
+ });
7822
+ }
7823
+ startUpload(conversationId, attachment, content) {
7824
+ const revision = this.accountRevision;
7825
+ const update = (change) => {
7826
+ if (revision === this.accountRevision) {
7827
+ this.patchAttachments(conversationId, attachments => attachments.map(item => item.id === attachment.id ? { ...item, ...change } : item));
7828
+ }
7829
+ };
7830
+ const subscription = this.api.uploadFile(conversationId, content, attachment.name).subscribe({
7831
+ next: event => {
7832
+ if (event.type === 'progress') {
7833
+ update({ progress: event.total > 0 ? Math.min(1, event.loaded / event.total) : 0 });
7834
+ return;
7835
+ }
7836
+ update({ state: 'uploaded', progress: 1, file: event.file });
7837
+ },
7838
+ error: error => {
7839
+ this.uploadSubscriptions.delete(attachment.id);
7840
+ update({ state: 'failed', error: error instanceof NhAssistantApiError
7841
+ ? { code: error.code, messageKey: error.messageKey }
7842
+ : clientError(NhAssistantClientErrorCodes.server) });
7843
+ },
7844
+ complete: () => this.uploadSubscriptions.delete(attachment.id)
7845
+ });
7846
+ if (!subscription.closed) {
7847
+ this.uploadSubscriptions.set(attachment.id, subscription);
7848
+ }
7849
+ }
7850
+ attachmentsOf(key) {
7851
+ return this.attachmentsState().get(key) ?? [];
7852
+ }
7853
+ patchAttachments(key, update) {
7854
+ this.attachmentsState.update(all => {
7855
+ const next = new Map(all);
7856
+ const attachments = update(all.get(key) ?? []);
7857
+ if (attachments.length > 0) {
7858
+ next.set(key, attachments);
7859
+ }
7860
+ else {
7861
+ next.delete(key);
7862
+ }
7863
+ return next;
7864
+ });
7865
+ }
7866
+ /** Moves the attachments of a new conversation to the conversation that was created for them. */
7867
+ moveAttachments(from, to) {
7868
+ const moving = this.attachmentsOf(from);
7869
+ this.patchAttachments(from, () => []);
7870
+ this.patchAttachments(to, attachments => [...attachments, ...moving]);
7871
+ }
7872
+ /**
7873
+ * The attachments a message took leave the composer, and with them the failed ones; files that
7874
+ * still upload stay for the next message.
7875
+ */
7876
+ releaseAttachments(conversationId, files) {
7877
+ const sent = new Set(files.map(file => file.id));
7878
+ this.patchAttachments(conversationId, attachments => attachments.filter(attachment => attachment.state === 'uploading' || (attachment.state === 'uploaded' && !sent.has(attachment.file?.id ?? ''))));
7879
+ }
7880
+ /** Stops the uploads of a conversation, or of every conversation, and forgets their attachments. */
7881
+ dropAttachments(conversationId) {
7882
+ for (const [key, attachments] of this.attachmentsState()) {
7883
+ if (conversationId !== undefined && key !== conversationId) {
7884
+ continue;
7885
+ }
7886
+ for (const attachment of attachments) {
7887
+ this.uploadSubscriptions.get(attachment.id)?.unsubscribe();
7888
+ this.uploadSubscriptions.delete(attachment.id);
7889
+ }
7890
+ }
7891
+ if (conversationId === undefined) {
7892
+ this.attachmentsState.set(new Map());
7893
+ }
7894
+ else {
7895
+ this.patchAttachments(conversationId, () => []);
7896
+ }
7897
+ }
7213
7898
  async loadConversation(conversationId, restoring) {
7214
7899
  if (this.sessionsState().has(conversationId)) {
7215
7900
  const session = this.sessionsState().get(conversationId);
@@ -7353,6 +8038,8 @@ class NhAssistantStore {
7353
8038
  this.steeringState.set(null);
7354
8039
  this.submittingState.set(false);
7355
8040
  this.feedbackApprovalsState.set(new Set());
8041
+ this.dropAttachments();
8042
+ this.attachmentConversation = null;
7356
8043
  this.clearAgentWorkTimers();
7357
8044
  void this.live.stop();
7358
8045
  void this.push.deactivate().catch(() => undefined);
@@ -7667,6 +8354,7 @@ class NhAssistantStore {
7667
8354
  }
7668
8355
  /** Removes a deleted, left or inaccessible conversation everywhere. */
7669
8356
  forget(conversationId) {
8357
+ this.dropAttachments(conversationId);
7670
8358
  this.queuedState.update(items => items.filter(item => item.conversationId !== conversationId));
7671
8359
  this.pausedQueuesState.update(paused => new Set([...paused].filter(id => id !== conversationId)));
7672
8360
  this.streams.get(conversationId)?.unsubscribe();
@@ -7726,6 +8414,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImpo
7726
8414
  function clientError(code) {
7727
8415
  return { code, messageKey: nhAssistantErrorMessageKey(code) };
7728
8416
  }
8417
+ /**
8418
+ * The notice of a message that waits for the next run of the agent working in the thread, with its
8419
+ * files when it carries any; the run adds the message to the thread when it takes it.
8420
+ */
8421
+ function messageQueuedNotice(fileCount) {
8422
+ return {
8423
+ code: NH_ASSISTANT_MESSAGE_QUEUED_NOTICE_CODE,
8424
+ messageKey: fileCount > 0 ? 'nh-assistant.notices.message-queued-with-files' : 'nh-assistant.notices.message-queued'
8425
+ };
8426
+ }
7729
8427
  /** A completed turn with a server code, or without any answer text, explains itself with a notice. */
7730
8428
  function completionNotice(conversation, errorCode) {
7731
8429
  if (errorCode) {
@@ -8348,6 +9046,203 @@ function provideNhAssistant(config) {
8348
9046
  ]);
8349
9047
  }
8350
9048
 
9049
+ /**
9050
+ * Failure codes the assistant back-end sends in error bodies, SSE `error` events and tool
9051
+ * result codes (`NhAssistantErrorCodes`, `NhAssistantAdminErrorCodes` and the runtime codes
9052
+ * of `NewHeap.Platform.AI.Chat`). Every code needs a text under `nh-assistant.errors.<code>`
9053
+ * in each bundled language; the translation tests enforce that.
9054
+ */
9055
+ const NH_ASSISTANT_SERVER_ERROR_CODES = [
9056
+ // Chat endpoints and turns
9057
+ 'assistant-disabled',
9058
+ 'assistant-conversation-not-found',
9059
+ 'assistant-conversation-busy',
9060
+ 'assistant-agent-not-found',
9061
+ 'assistant-agent-forbidden',
9062
+ 'assistant-message-invalid',
9063
+ 'assistant-message-too-long',
9064
+ 'assistant-message-duplicate',
9065
+ 'assistant-title-invalid',
9066
+ 'assistant-approval-not-found',
9067
+ 'assistant-approval-not-pending',
9068
+ 'assistant-approval-decision-invalid',
9069
+ 'assistant-proposal-hash-mismatch',
9070
+ 'assistant-approval-expired',
9071
+ 'assistant-approval-invalid',
9072
+ 'assistant-approval-rejected',
9073
+ 'assistant-budget-exhausted',
9074
+ 'assistant-tool-call-limit-reached',
9075
+ 'assistant-tools-disabled',
9076
+ 'assistant-turn-timeout',
9077
+ 'assistant-model-unavailable',
9078
+ 'assistant-classification-not-permitted',
9079
+ 'assistant-files-unavailable',
9080
+ 'assistant-files-not-accepted',
9081
+ 'assistant-file-reference-invalid',
9082
+ 'assistant-file-not-found',
9083
+ 'assistant-file-forbidden',
9084
+ 'assistant-file-unavailable',
9085
+ 'assistant-file-reader-failed',
9086
+ 'assistant-file-too-large',
9087
+ 'assistant-file-too-many-pages',
9088
+ 'assistant-file-count-limit',
9089
+ 'assistant-file-token-limit',
9090
+ 'assistant-file-type-not-supported',
9091
+ 'assistant-file-upload-invalid',
9092
+ 'assistant-file-type-not-accepted',
9093
+ 'assistant-file-upload-refused',
9094
+ 'assistant-file-upload-failed',
9095
+ 'assistant-file-scan-refused',
9096
+ 'assistant-turn-failed',
9097
+ 'assistant-context-unavailable',
9098
+ 'assistant-actor-mismatch',
9099
+ // Sharing and notifications
9100
+ 'assistant-owner-required',
9101
+ 'assistant-approval-forbidden',
9102
+ 'assistant-turn-forbidden',
9103
+ 'assistant-share-link-invalid',
9104
+ 'assistant-participant-not-found',
9105
+ 'assistant-participant-limit-reached',
9106
+ 'assistant-directory-unavailable',
9107
+ 'assistant-push-unavailable',
9108
+ 'assistant-push-subscription-invalid',
9109
+ // Agent runs
9110
+ 'assistant-subject-not-found',
9111
+ 'assistant-subject-invalid',
9112
+ 'assistant-subject-type-unknown',
9113
+ 'assistant-subject-search-unavailable',
9114
+ 'assistant-agent-does-not-work-on',
9115
+ 'assistant-subject-access-denied',
9116
+ 'assistant-no-accountable-owner',
9117
+ 'assistant-agent-disabled',
9118
+ 'assistant-agent-stopped',
9119
+ 'assistant-agent-paused',
9120
+ 'assistant-agent-configured-off',
9121
+ 'assistant-run-token-limit',
9122
+ 'assistant-skill-stopped',
9123
+ 'assistant-skill-paused',
9124
+ 'assistant-agent-off',
9125
+ 'assistant-skill-off',
9126
+ 'assistant-skill-not-found',
9127
+ 'assistant-skill-not-available',
9128
+ 'assistant-skill-switch-limit',
9129
+ 'assistant-moved-to-shadow',
9130
+ 'assistant-shadow-variant',
9131
+ 'assistant-service-account-not-used',
9132
+ 'assistant-service-account-not-found',
9133
+ 'assistant-skill-in-code',
9134
+ 'assistant-trigger-in-code',
9135
+ 'assistant-skill-in-use',
9136
+ 'assistant-configuration-limit',
9137
+ 'assistant-run-settings-in-code',
9138
+ 'assistant-run-settings-in-use',
9139
+ 'assistant-skill-candidate-not-found',
9140
+ 'assistant-skill-version-gone',
9141
+ 'assistant-account-search-unavailable',
9142
+ 'assistant-handoff-unavailable',
9143
+ 'assistant-handoff-not-accepted',
9144
+ 'assistant-handoff-subject-unavailable',
9145
+ 'assistant-handoff-shadow-to-live',
9146
+ 'assistant-handoff-chain-limit',
9147
+ 'assistant-handoff-limit-reached',
9148
+ 'assistant-replay-not-shadow',
9149
+ 'assistant-replay-active',
9150
+ 'assistant-evaluation-not-variant',
9151
+ 'assistant-agent-not-configured',
9152
+ 'assistant-run-not-found',
9153
+ 'assistant-run-not-retryable',
9154
+ 'assistant-run-filter-invalid',
9155
+ 'assistant-run-operation-cancelled',
9156
+ 'assistant-run-operation-stalled',
9157
+ 'assistant-run-operation-ended',
9158
+ 'assistant-board-not-found',
9159
+ 'assistant-board-scope-invalid',
9160
+ 'assistant-approval-policy-denied',
9161
+ 'assistant-four-eyes-required',
9162
+ 'assistant-execution-not-uncertain',
9163
+ 'assistant-proposal-stale',
9164
+ 'assistant-proposal-check-failed',
9165
+ 'assistant-proposal-already-open',
9166
+ 'assistant-proposal-recently-rejected',
9167
+ 'assistant-proposal-superseded',
9168
+ 'assistant-proposal-refused',
9169
+ 'assistant-proposal-placed',
9170
+ 'assistant-proposal-not-placed',
9171
+ 'assistant-proposal-not-needed',
9172
+ 'assistant-trigger-not-found',
9173
+ 'assistant-trigger-rate-limited',
9174
+ 'assistant-trigger-rolled-back',
9175
+ 'assistant-webhook-signature-invalid',
9176
+ 'assistant-webhook-payload-invalid',
9177
+ 'assistant-webhook-payload-too-large',
9178
+ 'assistant-memory-not-configured',
9179
+ 'assistant-memory-invalid',
9180
+ 'assistant-memory-conflict',
9181
+ 'assistant-artifact-invalid',
9182
+ 'assistant-artifact-outside-turn',
9183
+ 'assistant-artifact-not-found',
9184
+ // Feedback on proposals
9185
+ 'assistant-feedback-invalid',
9186
+ 'assistant-feedback-not-accepted',
9187
+ // Consultations of other agents
9188
+ 'assistant-consult-subject-unavailable',
9189
+ 'assistant-consult-agent-unavailable',
9190
+ 'assistant-consult-depth-exceeded',
9191
+ 'assistant-consult-budget-exhausted',
9192
+ 'assistant-consult-limit-reached',
9193
+ 'assistant-consult-timeout',
9194
+ 'assistant-consult-failed',
9195
+ 'assistant-consult-read-only',
9196
+ // Preferences and administration
9197
+ 'assistant-validation',
9198
+ 'assistant-instructions-too-long',
9199
+ 'assistant-forbidden',
9200
+ 'assistant-not-found',
9201
+ 'assistant-context-not-found',
9202
+ 'assistant-version-conflict',
9203
+ 'assistant-agent-exists',
9204
+ 'assistant-code-agent-not-deletable',
9205
+ 'assistant-agent-not-code',
9206
+ 'assistant-mcp-server-not-found',
9207
+ 'assistant-mcp-server-exists',
9208
+ 'assistant-mcp-tool-not-found',
9209
+ 'assistant-mcp-unreachable',
9210
+ 'assistant-mcp-unauthorized',
9211
+ 'assistant-mcp-host-blocked',
9212
+ 'assistant-mcp-schema-changed',
9213
+ 'assistant-mcp-tool-disabled',
9214
+ 'assistant-mcp-insufficient-scope',
9215
+ 'assistant-mcp-headless-not-allowed',
9216
+ 'assistant-mcp-credential-mismatch',
9217
+ 'assistant-mcp-consent-required',
9218
+ 'assistant-mcp-initiator-only',
9219
+ 'assistant-mcp-delegated-token-unavailable',
9220
+ 'assistant-mcp-secret-reference-invalid',
9221
+ 'assistant-mcp-certificate-reference-invalid',
9222
+ 'assistant-mcp-secret-unreadable',
9223
+ 'assistant-mcp-secret-unavailable',
9224
+ // MCP OAuth connections
9225
+ 'assistant-mcp-connection-required',
9226
+ 'assistant-mcp-reauthorization-required',
9227
+ 'assistant-mcp-scope-not-allowed',
9228
+ 'assistant-mcp-authorization-failed',
9229
+ 'assistant-mcp-authorization-expired',
9230
+ 'assistant-mcp-authorization-invalid',
9231
+ 'assistant-mcp-discovery-failed',
9232
+ 'assistant-mcp-pkce-unsupported',
9233
+ 'assistant-mcp-client-registration-required',
9234
+ 'assistant-mcp-token-request-failed',
9235
+ 'assistant-mcp-issuer-changed',
9236
+ 'assistant-mcp-authorization-not-confirmed',
9237
+ 'assistant-mcp-oauth-not-configured',
9238
+ 'assistant-mcp-too-many-authorizations',
9239
+ 'assistant-mcp-connection-not-supported'
9240
+ ];
9241
+ /** Every code the assistant UI can show: server codes and client-side transport codes. */
9242
+ const NH_ASSISTANT_ERROR_CODES = [
9243
+ ...new Set([...NH_ASSISTANT_SERVER_ERROR_CODES, ...Object.values(NhAssistantClientErrorCodes)])
9244
+ ];
9245
+
8351
9246
  /** Fragment the panel reads to join a shared conversation: `#nh-assistant-join=<id>.<token>`. */
8352
9247
  const NH_ASSISTANT_JOIN_FRAGMENT = 'nh-assistant-join';
8353
9248
  /** Fragment a clicked notification opens: `#nh-assistant-conversation=<id>`. */
@@ -8387,7 +9282,9 @@ const builtInPaths = {
8387
9282
  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',
8388
9283
  copy: 'M9 9h11v11H9zM5 15H4V4h11v1',
8389
9284
  bell: 'M6 16V11a6 6 0 0 1 12 0v5l2 2H4zM10 20a2 2 0 0 0 4 0',
8390
- leave: 'M10 4H5v16h5M14 8l4 4-4 4M18 12H9'
9285
+ leave: 'M10 4H5v16h5M14 8l4 4-4 4M18 12H9',
9286
+ attach: 'M19 11.5l-7.6 7.6a4.6 4.6 0 0 1-6.5-6.5l8.3-8.3a3 3 0 0 1 4.3 4.3l-8.2 8.2a1.5 1.5 0 0 1-2.1-2.1l7.4-7.4',
9287
+ file: 'M6 3h8l4 4v14H6zM14 3v4h4'
8391
9288
  };
8392
9289
  /** Decorative icon. The surrounding control provides the accessible name. */
8393
9290
  class NhAssistantIconComponent {
@@ -8711,7 +9608,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImpo
8711
9608
  let nextId$a = 0;
8712
9609
  /**
8713
9610
  * Message input. Enter sends when sending is available; Shift+Enter inserts a new line.
8714
- * Drafting remains available while the assistant runs or waits for approval.
9611
+ * Drafting remains available while the assistant runs or waits for approval. With `uploads`, an
9612
+ * attach button chooses files for the next message; the host uploads them and passes them back as
9613
+ * `attachments`, which show their progress, or why they cannot be attached, at the control. Sending
9614
+ * waits while a file uploads.
8715
9615
  */
8716
9616
  class NhAssistantComposerComponent {
8717
9617
  constructor() {
@@ -8732,15 +9632,30 @@ class NhAssistantComposerComponent {
8732
9632
  this.maxLength = input(null, ...(ngDevMode ? [{ debugName: "maxLength" }] : []));
8733
9633
  /** Text to put back into the input, for example a message the server did not accept. */
8734
9634
  this.restore = input(null, ...(ngDevMode ? [{ debugName: "restore" }] : []));
9635
+ /**
9636
+ * What the person may attach, for example `NhAssistantStore.uploads()`; `null` hides the attach
9637
+ * button.
9638
+ */
9639
+ this.uploads = input(null, ...(ngDevMode ? [{ debugName: "uploads" }] : []));
9640
+ /** The attachments of the next message, for example `NhAssistantStore.attachments()`. */
9641
+ this.attachments = input([], ...(ngDevMode ? [{ debugName: "attachments" }] : []));
8735
9642
  this.send = output();
8736
9643
  this.cancel = output();
8737
9644
  this.restored = output();
9645
+ /** Files the person chose to attach, for example for `NhAssistantStore.attach`. */
9646
+ this.attach = output();
9647
+ /** The id of an attachment the person removed, for example for `NhAssistantStore.removeAttachment`. */
9648
+ this.removeAttachment = output();
8738
9649
  this.host = inject(ElementRef);
9650
+ this.translate = inject(TranslateService, { optional: true });
8739
9651
  this.textarea = viewChild.required('textarea');
9652
+ this.attachButton = viewChild('attachButton', ...(ngDevMode ? [{ debugName: "attachButton" }] : []));
8740
9653
  this.ownedFocus = false;
8741
9654
  this.previousBusy = false;
8742
9655
  this.inputId = `nh-assistant-composer-${nextId$a++}`;
8743
9656
  this.hintId = `${this.inputId}-hint`;
9657
+ this.attachHintId = `${this.inputId}-attach-hint`;
9658
+ this.attachmentsId = `${this.inputId}-attachments`;
8744
9659
  this.text = signal('', ...(ngDevMode ? [{ debugName: "text" }] : []));
8745
9660
  this.length = computed(() => this.text().trim().length, ...(ngDevMode ? [{ debugName: "length" }] : []));
8746
9661
  this.tooLong = computed(() => {
@@ -8751,7 +9666,20 @@ class NhAssistantComposerComponent {
8751
9666
  const max = this.maxLength();
8752
9667
  return max !== null && this.length() >= max * 0.8;
8753
9668
  }, ...(ngDevMode ? [{ debugName: "showCounter" }] : []));
8754
- this.canSend = computed(() => !this.disabled() && !this.sendDisabled() && this.length() > 0 && !this.tooLong(), ...(ngDevMode ? [{ debugName: "canSend" }] : []));
9669
+ /** True while a file of the next message still uploads. */
9670
+ this.uploading = computed(() => this.attachments().some(attachment => attachment.state === 'uploading'), ...(ngDevMode ? [{ debugName: "uploading" }] : []));
9671
+ this.canSend = computed(() => !this.disabled() && !this.sendDisabled() && this.length() > 0 && !this.tooLong() && !this.uploading(), ...(ngDevMode ? [{ debugName: "canSend" }] : []));
9672
+ /** The `accept` attribute of the file input. */
9673
+ this.accept = computed(() => nhAssistantAcceptAttribute(this.uploads()?.acceptedMediaTypes ?? []), ...(ngDevMode ? [{ debugName: "accept" }] : []));
9674
+ /** The values the attachment texts name: the accepted types, the largest size and the most files. */
9675
+ this.uploadParams = computed(() => {
9676
+ const uploads = this.uploads();
9677
+ return {
9678
+ types: (uploads?.acceptedMediaTypes ?? []).map(nhAssistantFileTypeLabel).join(', '),
9679
+ max: uploads ? this.size(uploads.maxFileBytes) : '',
9680
+ count: uploads?.maxFiles ?? 0
9681
+ };
9682
+ }, ...(ngDevMode ? [{ debugName: "uploadParams" }] : []));
8755
9683
  effect(() => {
8756
9684
  const draft = this.restore();
8757
9685
  if (draft !== null) {
@@ -8801,6 +9729,40 @@ class NhAssistantComposerComponent {
8801
9729
  this.text.set('');
8802
9730
  this.send.emit(text);
8803
9731
  }
9732
+ /** Hands the chosen files to the host and empties the input, so the same file can be chosen again. */
9733
+ onFilesChosen(event) {
9734
+ const input = event.target;
9735
+ const files = Array.from(input.files ?? []);
9736
+ input.value = '';
9737
+ if (files.length > 0) {
9738
+ this.attach.emit(files);
9739
+ }
9740
+ }
9741
+ /** Removes an attachment and keeps the keyboard in the composer, on the attach button. */
9742
+ remove(attachment) {
9743
+ this.removeAttachment.emit(attachment.id);
9744
+ queueMicrotask(() => (this.attachButton()?.nativeElement ?? this.textarea().nativeElement).focus({ preventScroll: true }));
9745
+ }
9746
+ /** The translation keys of why an attachment failed, from the composer's own text to the generic one. */
9747
+ errorKeys(attachment) {
9748
+ const error = attachment.error;
9749
+ if (!error) {
9750
+ return [];
9751
+ }
9752
+ return [
9753
+ `nh-assistant.composer.attachment-errors.${error.code}`,
9754
+ error.messageKey,
9755
+ `nh-assistant.errors.${error.code}`,
9756
+ 'nh-assistant.errors.generic'
9757
+ ];
9758
+ }
9759
+ percent(attachment) {
9760
+ return Math.round(attachment.progress * 100);
9761
+ }
9762
+ /** A size in bytes in the current language, such as `1.4 MB`. */
9763
+ size(bytes) {
9764
+ return nhAssistantFormatFileSize(bytes, this.translate?.getCurrentLang());
9765
+ }
8804
9766
  restoreOwnedFocus() {
8805
9767
  if (!this.ownedFocus || matchMedia('(pointer: coarse)').matches) {
8806
9768
  return;
@@ -8811,12 +9773,12 @@ class NhAssistantComposerComponent {
8811
9773
  }
8812
9774
  }
8813
9775
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: NhAssistantComposerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
8814
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.34", 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 }, queueWhileBusy: { classPropertyName: "queueWhileBusy", publicName: "queueWhileBusy", 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=\"prompt-bar\">\n <div class=\"prompt-controls\">\n <ng-content select=\"[nhAssistantComposerControls]\" />\n </div>\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 }\n @if (!busy() || !canStop() || queueWhileBusy()) {\n <button\n class=\"send\"\n type=\"submit\"\n [disabled]=\"!canSend()\"\n [attr.aria-label]=\"(busy() && queueWhileBusy() ? 'nh-assistant.queue.add' : 'nh-assistant.composer.send') | translate\"\n [attr.title]=\"(busy() && queueWhileBusy() ? 'nh-assistant.queue.add' : 'nh-assistant.composer.send') | translate\">\n <nh-assistant-icon name=\"send\" />\n </button>\n }\n </div>\n </div>\n</form>\n\n@if (status() === 'waiting-for-approval') {\n <p class=\"composer-status\" role=\"status\" aria-live=\"polite\">\n @if (statusActor(); as name) {\n {{ 'nh-assistant.composer.status.waiting-for-approval-by' | translate: { name } }}\n } @else {\n {{ 'nh-assistant.composer.status.waiting-for-approval' | 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>{{ (busy() && queueWhileBusy() ? 'nh-assistant.queue.hint' : '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;flex-direction:column;gap:8px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:8px 8px 8px 14px;background:var(--nha-surface);box-shadow:0 2px 6px color-mix(in srgb,var(--nha-text) 4%,transparent)}.composer:focus-within{border-color:var(--nha-focus);box-shadow:0 0 0 2px color-mix(in srgb,var(--nha-focus) 16%,transparent)}textarea{width:100%;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}.prompt-bar{display:flex;align-items:center;gap:8px;min-width:0}.prompt-controls{flex:1;min-width:0}.actions{display:flex;flex-shrink:0;gap:6px}.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 }); }
9776
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.34", 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 }, queueWhileBusy: { classPropertyName: "queueWhileBusy", publicName: "queueWhileBusy", 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 }, uploads: { classPropertyName: "uploads", publicName: "uploads", isSignal: true, isRequired: false, transformFunction: null }, attachments: { classPropertyName: "attachments", publicName: "attachments", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { send: "send", cancel: "cancel", restored: "restored", attach: "attach", removeAttachment: "removeAttachment" }, host: { listeners: { "focusin": "onFocusIn()", "focusout": "onFocusOut($event)" } }, viewQueries: [{ propertyName: "textarea", first: true, predicate: ["textarea"], descendants: true, isSignal: true }, { propertyName: "attachButton", first: true, predicate: ["attachButton"], 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 @if (attachments().length > 0) {\n <ul class=\"attachments\" [id]=\"attachmentsId\" [attr.aria-label]=\"'nh-assistant.composer.attachments' | translate\">\n @for (attachment of attachments(); track attachment.id) {\n <li class=\"attachment\" [attr.data-state]=\"attachment.state\">\n <nh-assistant-icon class=\"attachment-icon\" [name]=\"attachment.state === 'failed' ? 'warning' : 'file'\" />\n <div class=\"attachment-body\">\n <span class=\"attachment-name\" [attr.title]=\"attachment.name\">{{ attachment.name }}</span>\n @switch (attachment.state) {\n @case ('uploading') {\n <progress\n class=\"attachment-progress\"\n max=\"100\"\n [value]=\"percent(attachment)\"\n [attr.aria-label]=\"'nh-assistant.composer.uploading' | translate: { name: attachment.name }\"></progress>\n }\n @case ('failed') {\n <span class=\"attachment-error\" role=\"alert\">\n {{ errorKeys(attachment) | nhAssistantTranslate: (attachment.error?.code ?? '') : uploadParams() }}\n </span>\n }\n @default {\n <span class=\"attachment-meta\">{{ size(attachment.size) }}</span>\n }\n }\n </div>\n <button\n class=\"attachment-remove\"\n type=\"button\"\n [attr.aria-label]=\"'nh-assistant.composer.remove-attachment' | translate: { name: attachment.name }\"\n [attr.title]=\"'nh-assistant.composer.remove-attachment' | translate: { name: attachment.name }\"\n (click)=\"remove(attachment)\">\n <nh-assistant-icon name=\"close\" />\n </button>\n </li>\n }\n </ul>\n }\n\n <div class=\"prompt-bar\">\n @if (uploads()) {\n <button\n #attachButton\n class=\"attach\"\n type=\"button\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"'nh-assistant.composer.attach' | translate\"\n [attr.title]=\"'nh-assistant.composer.attach-hint' | translate: uploadParams()\"\n [attr.aria-describedby]=\"attachHintId\"\n (click)=\"fileInput.click()\">\n <nh-assistant-icon name=\"attach\" />\n </button>\n <span class=\"visually-hidden\" [id]=\"attachHintId\">{{ 'nh-assistant.composer.attach-hint' | translate: uploadParams() }}</span>\n <input\n #fileInput\n class=\"visually-hidden\"\n type=\"file\"\n multiple\n tabindex=\"-1\"\n aria-hidden=\"true\"\n [accept]=\"accept()\"\n (change)=\"onFilesChosen($event)\" />\n }\n <div class=\"prompt-controls\">\n <ng-content select=\"[nhAssistantComposerControls]\" />\n </div>\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 }\n @if (!busy() || !canStop() || queueWhileBusy()) {\n <button\n class=\"send\"\n type=\"submit\"\n [disabled]=\"!canSend()\"\n [attr.aria-label]=\"(busy() && queueWhileBusy() ? 'nh-assistant.queue.add' : 'nh-assistant.composer.send') | translate\"\n [attr.title]=\"(busy() && queueWhileBusy() ? 'nh-assistant.queue.add' : 'nh-assistant.composer.send') | translate\">\n <nh-assistant-icon name=\"send\" />\n </button>\n }\n </div>\n </div>\n</form>\n\n@if (status() === 'waiting-for-approval') {\n <p class=\"composer-status\" role=\"status\" aria-live=\"polite\">\n @if (statusActor(); as name) {\n {{ 'nh-assistant.composer.status.waiting-for-approval-by' | translate: { name } }}\n } @else {\n {{ 'nh-assistant.composer.status.waiting-for-approval' | 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 if (uploading()) {\n <span>{{ 'nh-assistant.composer.uploading-hint' | translate }}</span>\n } @else {\n <span>{{ (busy() && queueWhileBusy() ? 'nh-assistant.queue.hint' : '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;flex-direction:column;gap:8px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:8px 8px 8px 14px;background:var(--nha-surface);box-shadow:0 2px 6px color-mix(in srgb,var(--nha-text) 4%,transparent)}.composer:focus-within{border-color:var(--nha-focus);box-shadow:0 0 0 2px color-mix(in srgb,var(--nha-focus) 16%,transparent)}textarea{width:100%;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}.prompt-bar{display:flex;align-items:center;gap:8px;min-width:0}.prompt-controls{flex:1;min-width:0}.actions{display:flex;flex-shrink:0;gap:6px}.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)}.attach{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;flex-shrink:0;width:40px;height:40px}.attach:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.attach:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}.attach:disabled{cursor:not-allowed;opacity:.5}.attachments{display:flex;flex-wrap:wrap;gap:6px;min-width:0;margin:0;padding:0;list-style:none}.attachment{display:flex;align-items:center;gap:8px;min-width:0;max-width:100%;flex:0 1 240px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:4px 4px 4px 10px;background:var(--nha-background)}.attachment[data-state=failed]{flex-basis:100%;border-color:var(--nha-danger);background:var(--nha-danger-soft)}.attachment-icon{flex-shrink:0;color:var(--nha-text-muted)}[data-state=failed] .attachment-icon{color:var(--nha-danger)}.attachment-body{display:flex;flex:1;flex-direction:column;gap:2px;min-width:0}.attachment-name{overflow:hidden;font-size:12px;font-weight:600;text-overflow:ellipsis;white-space:nowrap}.attachment-meta,.attachment-error{font-size:11px;color:var(--nha-text-muted);overflow-wrap:anywhere}.attachment-error{color:var(--nha-danger)}.attachment-progress{width:100%;height:4px;accent-color:var(--nha-accent)}.attachment-remove{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;flex-shrink:0;width:32px;height:32px}.attachment-remove:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.attachment-remove:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}.attachment-remove:disabled{cursor:not-allowed;opacity:.5}.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" }, { kind: "pipe", type: NhAssistantTranslatePipe, name: "nhAssistantTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
8815
9777
  }
8816
9778
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: NhAssistantComposerComponent, decorators: [{
8817
9779
  type: Component,
8818
- 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=\"prompt-bar\">\n <div class=\"prompt-controls\">\n <ng-content select=\"[nhAssistantComposerControls]\" />\n </div>\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 }\n @if (!busy() || !canStop() || queueWhileBusy()) {\n <button\n class=\"send\"\n type=\"submit\"\n [disabled]=\"!canSend()\"\n [attr.aria-label]=\"(busy() && queueWhileBusy() ? 'nh-assistant.queue.add' : 'nh-assistant.composer.send') | translate\"\n [attr.title]=\"(busy() && queueWhileBusy() ? 'nh-assistant.queue.add' : 'nh-assistant.composer.send') | translate\">\n <nh-assistant-icon name=\"send\" />\n </button>\n }\n </div>\n </div>\n</form>\n\n@if (status() === 'waiting-for-approval') {\n <p class=\"composer-status\" role=\"status\" aria-live=\"polite\">\n @if (statusActor(); as name) {\n {{ 'nh-assistant.composer.status.waiting-for-approval-by' | translate: { name } }}\n } @else {\n {{ 'nh-assistant.composer.status.waiting-for-approval' | 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>{{ (busy() && queueWhileBusy() ? 'nh-assistant.queue.hint' : '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;flex-direction:column;gap:8px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:8px 8px 8px 14px;background:var(--nha-surface);box-shadow:0 2px 6px color-mix(in srgb,var(--nha-text) 4%,transparent)}.composer:focus-within{border-color:var(--nha-focus);box-shadow:0 0 0 2px color-mix(in srgb,var(--nha-focus) 16%,transparent)}textarea{width:100%;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}.prompt-bar{display:flex;align-items:center;gap:8px;min-width:0}.prompt-controls{flex:1;min-width:0}.actions{display:flex;flex-shrink:0;gap:6px}.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"] }]
8819
- }], 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 }] }], queueWhileBusy: [{ type: i0.Input, args: [{ isSignal: true, alias: "queueWhileBusy", 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: [{
9780
+ args: [{ selector: 'nh-assistant-composer', standalone: true, imports: [TranslatePipe, NhAssistantIconComponent, NhAssistantTranslatePipe], 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 @if (attachments().length > 0) {\n <ul class=\"attachments\" [id]=\"attachmentsId\" [attr.aria-label]=\"'nh-assistant.composer.attachments' | translate\">\n @for (attachment of attachments(); track attachment.id) {\n <li class=\"attachment\" [attr.data-state]=\"attachment.state\">\n <nh-assistant-icon class=\"attachment-icon\" [name]=\"attachment.state === 'failed' ? 'warning' : 'file'\" />\n <div class=\"attachment-body\">\n <span class=\"attachment-name\" [attr.title]=\"attachment.name\">{{ attachment.name }}</span>\n @switch (attachment.state) {\n @case ('uploading') {\n <progress\n class=\"attachment-progress\"\n max=\"100\"\n [value]=\"percent(attachment)\"\n [attr.aria-label]=\"'nh-assistant.composer.uploading' | translate: { name: attachment.name }\"></progress>\n }\n @case ('failed') {\n <span class=\"attachment-error\" role=\"alert\">\n {{ errorKeys(attachment) | nhAssistantTranslate: (attachment.error?.code ?? '') : uploadParams() }}\n </span>\n }\n @default {\n <span class=\"attachment-meta\">{{ size(attachment.size) }}</span>\n }\n }\n </div>\n <button\n class=\"attachment-remove\"\n type=\"button\"\n [attr.aria-label]=\"'nh-assistant.composer.remove-attachment' | translate: { name: attachment.name }\"\n [attr.title]=\"'nh-assistant.composer.remove-attachment' | translate: { name: attachment.name }\"\n (click)=\"remove(attachment)\">\n <nh-assistant-icon name=\"close\" />\n </button>\n </li>\n }\n </ul>\n }\n\n <div class=\"prompt-bar\">\n @if (uploads()) {\n <button\n #attachButton\n class=\"attach\"\n type=\"button\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"'nh-assistant.composer.attach' | translate\"\n [attr.title]=\"'nh-assistant.composer.attach-hint' | translate: uploadParams()\"\n [attr.aria-describedby]=\"attachHintId\"\n (click)=\"fileInput.click()\">\n <nh-assistant-icon name=\"attach\" />\n </button>\n <span class=\"visually-hidden\" [id]=\"attachHintId\">{{ 'nh-assistant.composer.attach-hint' | translate: uploadParams() }}</span>\n <input\n #fileInput\n class=\"visually-hidden\"\n type=\"file\"\n multiple\n tabindex=\"-1\"\n aria-hidden=\"true\"\n [accept]=\"accept()\"\n (change)=\"onFilesChosen($event)\" />\n }\n <div class=\"prompt-controls\">\n <ng-content select=\"[nhAssistantComposerControls]\" />\n </div>\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 }\n @if (!busy() || !canStop() || queueWhileBusy()) {\n <button\n class=\"send\"\n type=\"submit\"\n [disabled]=\"!canSend()\"\n [attr.aria-label]=\"(busy() && queueWhileBusy() ? 'nh-assistant.queue.add' : 'nh-assistant.composer.send') | translate\"\n [attr.title]=\"(busy() && queueWhileBusy() ? 'nh-assistant.queue.add' : 'nh-assistant.composer.send') | translate\">\n <nh-assistant-icon name=\"send\" />\n </button>\n }\n </div>\n </div>\n</form>\n\n@if (status() === 'waiting-for-approval') {\n <p class=\"composer-status\" role=\"status\" aria-live=\"polite\">\n @if (statusActor(); as name) {\n {{ 'nh-assistant.composer.status.waiting-for-approval-by' | translate: { name } }}\n } @else {\n {{ 'nh-assistant.composer.status.waiting-for-approval' | 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 if (uploading()) {\n <span>{{ 'nh-assistant.composer.uploading-hint' | translate }}</span>\n } @else {\n <span>{{ (busy() && queueWhileBusy() ? 'nh-assistant.queue.hint' : '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;flex-direction:column;gap:8px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:8px 8px 8px 14px;background:var(--nha-surface);box-shadow:0 2px 6px color-mix(in srgb,var(--nha-text) 4%,transparent)}.composer:focus-within{border-color:var(--nha-focus);box-shadow:0 0 0 2px color-mix(in srgb,var(--nha-focus) 16%,transparent)}textarea{width:100%;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}.prompt-bar{display:flex;align-items:center;gap:8px;min-width:0}.prompt-controls{flex:1;min-width:0}.actions{display:flex;flex-shrink:0;gap:6px}.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)}.attach{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;flex-shrink:0;width:40px;height:40px}.attach:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.attach:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}.attach:disabled{cursor:not-allowed;opacity:.5}.attachments{display:flex;flex-wrap:wrap;gap:6px;min-width:0;margin:0;padding:0;list-style:none}.attachment{display:flex;align-items:center;gap:8px;min-width:0;max-width:100%;flex:0 1 240px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:4px 4px 4px 10px;background:var(--nha-background)}.attachment[data-state=failed]{flex-basis:100%;border-color:var(--nha-danger);background:var(--nha-danger-soft)}.attachment-icon{flex-shrink:0;color:var(--nha-text-muted)}[data-state=failed] .attachment-icon{color:var(--nha-danger)}.attachment-body{display:flex;flex:1;flex-direction:column;gap:2px;min-width:0}.attachment-name{overflow:hidden;font-size:12px;font-weight:600;text-overflow:ellipsis;white-space:nowrap}.attachment-meta,.attachment-error{font-size:11px;color:var(--nha-text-muted);overflow-wrap:anywhere}.attachment-error{color:var(--nha-danger)}.attachment-progress{width:100%;height:4px;accent-color:var(--nha-accent)}.attachment-remove{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;flex-shrink:0;width:32px;height:32px}.attachment-remove:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.attachment-remove:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}.attachment-remove:disabled{cursor:not-allowed;opacity:.5}.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"] }]
9781
+ }], 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 }] }], queueWhileBusy: [{ type: i0.Input, args: [{ isSignal: true, alias: "queueWhileBusy", 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 }] }], uploads: [{ type: i0.Input, args: [{ isSignal: true, alias: "uploads", required: false }] }], attachments: [{ type: i0.Input, args: [{ isSignal: true, alias: "attachments", required: false }] }], send: [{ type: i0.Output, args: ["send"] }], cancel: [{ type: i0.Output, args: ["cancel"] }], restored: [{ type: i0.Output, args: ["restored"] }], attach: [{ type: i0.Output, args: ["attach"] }], removeAttachment: [{ type: i0.Output, args: ["removeAttachment"] }], textarea: [{ type: i0.ViewChild, args: ['textarea', { isSignal: true }] }], attachButton: [{ type: i0.ViewChild, args: ['attachButton', { isSignal: true }] }], onFocusIn: [{
8820
9782
  type: HostListener,
8821
9783
  args: ['focusin']
8822
9784
  }], onFocusOut: [{
@@ -9328,15 +10290,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImpo
9328
10290
  }] });
9329
10291
 
9330
10292
  /**
9331
- * Groups consecutive tool calls of a message. Text and approvals break a group, so an
9332
- * approval always stays visible. A group's key is its first invocation id, so it keeps
9333
- * its identity while later calls of the same run are appended.
10293
+ * Groups consecutive tool calls of a message, and consecutive files. Text and approvals break a
10294
+ * group, so an approval always stays visible. A tool group's key is its first invocation id, so it
10295
+ * keeps its identity while later calls of the same run are appended.
9334
10296
  */
9335
10297
  function groupNhAssistantParts(parts) {
9336
10298
  const groups = [];
9337
10299
  let tools = null;
10300
+ let files = null;
9338
10301
  parts.forEach((part, index) => {
9339
10302
  if (part.type === 'tool-call') {
10303
+ files = null;
9340
10304
  if (!tools) {
9341
10305
  tools = { kind: 'tools', key: `tools:${part.invocationId}`, calls: [] };
9342
10306
  groups.push(tools);
@@ -9345,6 +10309,15 @@ function groupNhAssistantParts(parts) {
9345
10309
  return;
9346
10310
  }
9347
10311
  tools = null;
10312
+ if (part.type === 'file') {
10313
+ if (!files) {
10314
+ files = { kind: 'files', key: `files:${index}`, files: [] };
10315
+ groups.push(files);
10316
+ }
10317
+ files.files.push(part);
10318
+ return;
10319
+ }
10320
+ files = null;
9348
10321
  if (part.type === 'approval') {
9349
10322
  groups.push({ kind: 'approval', key: `approval:${part.approvalId}`, part });
9350
10323
  }
@@ -9800,11 +10773,19 @@ const stickToBottomDistance = 120;
9800
10773
  /**
9801
10774
  * The messages of a conversation as a live log. Assistant text renders as sanitized
9802
10775
  * Markdown, consecutive tool calls as one collapsible group that counts along, and
9803
- * approvals as cards. Messages a trigger wrote into an agent thread read as events, not as
9804
- * a person. While a turn runs without visible progress, a working indicator shows the
9805
- * elapsed time. The thread follows new content while the user is at the bottom.
10776
+ * approvals as cards. The files a person attached show by name, type and size. Messages a
10777
+ * trigger wrote into an agent thread read as events, not as a person. While a turn runs
10778
+ * without visible progress, a working indicator shows the elapsed time. The thread follows
10779
+ * new content while the user is at the bottom.
9806
10780
  */
9807
10781
  class NhAssistantThreadComponent {
10782
+ /** The type and size of an attached file, in the current language. */
10783
+ fileMeta(file) {
10784
+ return {
10785
+ type: nhAssistantFileTypeLabel(file.mediaType),
10786
+ size: nhAssistantFormatFileSize(file.size, this.translate?.getCurrentLang())
10787
+ };
10788
+ }
9808
10789
  /** Asks the question again that the assistant message with a connection card answered. */
9809
10790
  retryAfterConnect(message) {
9810
10791
  const messages = this.messages();
@@ -9823,6 +10804,7 @@ class NhAssistantThreadComponent {
9823
10804
  }
9824
10805
  constructor() {
9825
10806
  this.injector = inject(Injector);
10807
+ this.translate = inject(TranslateService, { optional: true });
9826
10808
  this.messages = input.required(...(ngDevMode ? [{ debugName: "messages" }] : []));
9827
10809
  this.streaming = input(false, ...(ngDevMode ? [{ debugName: "streaming" }] : []));
9828
10810
  this.deciding = input(false, ...(ngDevMode ? [{ debugName: "deciding" }] : []));
@@ -9957,7 +10939,7 @@ class NhAssistantThreadComponent {
9957
10939
  }
9958
10940
  }
9959
10941
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: NhAssistantThreadComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
9960
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.34", 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 }, approvalRights: { classPropertyName: "approvalRights", publicName: "approvalRights", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { approvalDecision: "approvalDecision", approvalChoice: "approvalChoice", retryMessage: "retryMessage" }, 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\n class=\"message\"\n [attr.data-role]=\"message.role\"\n [attr.data-others]=\"isOthers(message) ? 'true' : null\"\n [attr.data-author]=\"isTrigger(message) ? 'trigger' : isRule(message) ? 'rule' : null\">\n @if (isTrigger(message)) {\n <p class=\"author\">{{ 'nh-assistant.thread.trigger' | translate }}</p>\n } @else if (isRule(message)) {\n <p class=\"author\">{{ 'nh-assistant.thread.rule' | translate }}</p>\n } @else 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 ('connection') {\n <nh-assistant-connection-required\n class=\"part\"\n [part]=\"group.part\"\n [canRetry]=\"!streaming()\"\n (retry)=\"retryAfterConnect(message)\" />\n }\n @case ('approval') {\n <nh-assistant-approval-card\n class=\"part\"\n [approval]=\"group.part\"\n [deciding]=\"deciding()\"\n [canDecide]=\"rightFor(group.part).canDecide\"\n [decisionOwner]=\"rightFor(group.part).decisionOwner\"\n [waitingFor]=\"rightFor(group.part).waitingFor\"\n [canGiveFeedback]=\"rightFor(group.part).canGiveFeedback === true\"\n (decide)=\"choose(group.part, $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:20px}.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:600}.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-author=trigger]{align-items:stretch}.message[data-author=trigger] .bubble{max-width:none;color:var(--nha-text-muted);background:transparent;border:1px dashed var(--nha-border)}.message[data-role=assistant] .bubble,.message[data-role=tool] .bubble{padding-inline:0;background:transparent}.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 hr{margin:12px 0;border:0;border-top:1px solid var(--nha-border)}: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", "waitingFor", "canGiveFeedback"], outputs: ["decide"] }, { kind: "component", type: NhAssistantConnectionRequiredComponent, selector: "nh-assistant-connection-required", inputs: ["part", "canRetry"], outputs: ["retry"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }, { kind: "pipe", type: NhAssistantMarkdownPipe, name: "nhAssistantMarkdown" }, { kind: "pipe", type: NhAssistantPartGroupsPipe, name: "nhAssistantPartGroups" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
10942
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.34", 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 }, approvalRights: { classPropertyName: "approvalRights", publicName: "approvalRights", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { approvalDecision: "approvalDecision", approvalChoice: "approvalChoice", retryMessage: "retryMessage" }, 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\n class=\"message\"\n [attr.data-role]=\"message.role\"\n [attr.data-others]=\"isOthers(message) ? 'true' : null\"\n [attr.data-author]=\"isTrigger(message) ? 'trigger' : isRule(message) ? 'rule' : null\">\n @if (isTrigger(message)) {\n <p class=\"author\">{{ 'nh-assistant.thread.trigger' | translate }}</p>\n } @else if (isRule(message)) {\n <p class=\"author\">{{ 'nh-assistant.thread.rule' | translate }}</p>\n } @else 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 ('files') {\n <ul class=\"files\" [attr.aria-label]=\"'nh-assistant.thread.files' | translate\">\n @for (file of group.files; track $index) {\n <li class=\"file\">\n <nh-assistant-icon class=\"file-icon\" name=\"file\" />\n <span class=\"file-name\" [attr.title]=\"file.name\">{{ file.name }}</span>\n <span class=\"file-meta\">{{ 'nh-assistant.thread.file-meta' | translate: fileMeta(file) }}</span>\n </li>\n }\n </ul>\n }\n @case ('connection') {\n <nh-assistant-connection-required\n class=\"part\"\n [part]=\"group.part\"\n [canRetry]=\"!streaming()\"\n (retry)=\"retryAfterConnect(message)\" />\n }\n @case ('approval') {\n <nh-assistant-approval-card\n class=\"part\"\n [approval]=\"group.part\"\n [deciding]=\"deciding()\"\n [canDecide]=\"rightFor(group.part).canDecide\"\n [decisionOwner]=\"rightFor(group.part).decisionOwner\"\n [waitingFor]=\"rightFor(group.part).waitingFor\"\n [canGiveFeedback]=\"rightFor(group.part).canGiveFeedback === true\"\n (decide)=\"choose(group.part, $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:20px}.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:600}.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-author=trigger]{align-items:stretch}.message[data-author=trigger] .bubble{max-width:none;color:var(--nha-text-muted);background:transparent;border:1px dashed var(--nha-border)}.message[data-role=assistant] .bubble,.message[data-role=tool] .bubble{padding-inline:0;background:transparent}.part{width:100%}.files{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:6px;max-width:88%;min-width:0;margin:0;padding:0;list-style:none}.message:not([data-role=user]) .files,.message[data-others=true] .files{justify-content:flex-start}.file{display:flex;align-items:center;gap:6px;min-width:0;max-width:100%;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:4px 10px;color:var(--nha-text);background:var(--nha-surface);font-size:12px}.file-icon{flex-shrink:0;color:var(--nha-text-muted)}.file-name{min-width:0;overflow:hidden;font-weight:600;text-overflow:ellipsis;white-space:nowrap}.file-meta{flex-shrink:0;color:var(--nha-text-muted);white-space:nowrap}: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 hr{margin:12px 0;border:0;border-top:1px solid var(--nha-border)}: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: NhAssistantIconComponent, selector: "nh-assistant-icon", inputs: ["name"] }, { 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", "waitingFor", "canGiveFeedback"], outputs: ["decide"] }, { kind: "component", type: NhAssistantConnectionRequiredComponent, selector: "nh-assistant-connection-required", inputs: ["part", "canRetry"], outputs: ["retry"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }, { kind: "pipe", type: NhAssistantMarkdownPipe, name: "nhAssistantMarkdown" }, { kind: "pipe", type: NhAssistantPartGroupsPipe, name: "nhAssistantPartGroups" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
9961
10943
  }
9962
10944
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: NhAssistantThreadComponent, decorators: [{
9963
10945
  type: Component,
@@ -9965,12 +10947,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImpo
9965
10947
  NgTemplateOutlet,
9966
10948
  ScrollingModule,
9967
10949
  TranslatePipe,
10950
+ NhAssistantIconComponent,
9968
10951
  NhAssistantMarkdownPipe,
9969
10952
  NhAssistantPartGroupsPipe,
9970
10953
  NhAssistantToolCallGroupComponent,
9971
10954
  NhAssistantApprovalCardComponent,
9972
10955
  NhAssistantConnectionRequiredComponent
9973
- ], 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\n class=\"message\"\n [attr.data-role]=\"message.role\"\n [attr.data-others]=\"isOthers(message) ? 'true' : null\"\n [attr.data-author]=\"isTrigger(message) ? 'trigger' : isRule(message) ? 'rule' : null\">\n @if (isTrigger(message)) {\n <p class=\"author\">{{ 'nh-assistant.thread.trigger' | translate }}</p>\n } @else if (isRule(message)) {\n <p class=\"author\">{{ 'nh-assistant.thread.rule' | translate }}</p>\n } @else 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 ('connection') {\n <nh-assistant-connection-required\n class=\"part\"\n [part]=\"group.part\"\n [canRetry]=\"!streaming()\"\n (retry)=\"retryAfterConnect(message)\" />\n }\n @case ('approval') {\n <nh-assistant-approval-card\n class=\"part\"\n [approval]=\"group.part\"\n [deciding]=\"deciding()\"\n [canDecide]=\"rightFor(group.part).canDecide\"\n [decisionOwner]=\"rightFor(group.part).decisionOwner\"\n [waitingFor]=\"rightFor(group.part).waitingFor\"\n [canGiveFeedback]=\"rightFor(group.part).canGiveFeedback === true\"\n (decide)=\"choose(group.part, $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:20px}.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:600}.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-author=trigger]{align-items:stretch}.message[data-author=trigger] .bubble{max-width:none;color:var(--nha-text-muted);background:transparent;border:1px dashed var(--nha-border)}.message[data-role=assistant] .bubble,.message[data-role=tool] .bubble{padding-inline:0;background:transparent}.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 hr{margin:12px 0;border:0;border-top:1px solid var(--nha-border)}: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"] }]
10956
+ ], 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\n class=\"message\"\n [attr.data-role]=\"message.role\"\n [attr.data-others]=\"isOthers(message) ? 'true' : null\"\n [attr.data-author]=\"isTrigger(message) ? 'trigger' : isRule(message) ? 'rule' : null\">\n @if (isTrigger(message)) {\n <p class=\"author\">{{ 'nh-assistant.thread.trigger' | translate }}</p>\n } @else if (isRule(message)) {\n <p class=\"author\">{{ 'nh-assistant.thread.rule' | translate }}</p>\n } @else 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 ('files') {\n <ul class=\"files\" [attr.aria-label]=\"'nh-assistant.thread.files' | translate\">\n @for (file of group.files; track $index) {\n <li class=\"file\">\n <nh-assistant-icon class=\"file-icon\" name=\"file\" />\n <span class=\"file-name\" [attr.title]=\"file.name\">{{ file.name }}</span>\n <span class=\"file-meta\">{{ 'nh-assistant.thread.file-meta' | translate: fileMeta(file) }}</span>\n </li>\n }\n </ul>\n }\n @case ('connection') {\n <nh-assistant-connection-required\n class=\"part\"\n [part]=\"group.part\"\n [canRetry]=\"!streaming()\"\n (retry)=\"retryAfterConnect(message)\" />\n }\n @case ('approval') {\n <nh-assistant-approval-card\n class=\"part\"\n [approval]=\"group.part\"\n [deciding]=\"deciding()\"\n [canDecide]=\"rightFor(group.part).canDecide\"\n [decisionOwner]=\"rightFor(group.part).decisionOwner\"\n [waitingFor]=\"rightFor(group.part).waitingFor\"\n [canGiveFeedback]=\"rightFor(group.part).canGiveFeedback === true\"\n (decide)=\"choose(group.part, $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:20px}.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:600}.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-author=trigger]{align-items:stretch}.message[data-author=trigger] .bubble{max-width:none;color:var(--nha-text-muted);background:transparent;border:1px dashed var(--nha-border)}.message[data-role=assistant] .bubble,.message[data-role=tool] .bubble{padding-inline:0;background:transparent}.part{width:100%}.files{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:6px;max-width:88%;min-width:0;margin:0;padding:0;list-style:none}.message:not([data-role=user]) .files,.message[data-others=true] .files{justify-content:flex-start}.file{display:flex;align-items:center;gap:6px;min-width:0;max-width:100%;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:4px 10px;color:var(--nha-text);background:var(--nha-surface);font-size:12px}.file-icon{flex-shrink:0;color:var(--nha-text-muted)}.file-name{min-width:0;overflow:hidden;font-weight:600;text-overflow:ellipsis;white-space:nowrap}.file-meta{flex-shrink:0;color:var(--nha-text-muted);white-space:nowrap}: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 hr{margin:12px 0;border:0;border-top:1px solid var(--nha-border)}: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"] }]
9974
10957
  }], 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 }] }], approvalRights: [{ type: i0.Input, args: [{ isSignal: true, alias: "approvalRights", required: false }] }], approvalDecision: [{ type: i0.Output, args: ["approvalDecision"] }], approvalChoice: [{ type: i0.Output, args: ["approvalChoice"] }], retryMessage: [{ type: i0.Output, args: ["retryMessage"] }], scroller: [{ type: i0.ViewChild, args: ['scroller', { isSignal: true }] }], viewport: [{ type: i0.ViewChild, args: [i0.forwardRef(() => CdkVirtualScrollViewport), { isSignal: true }] }] } });
9975
10958
 
9976
10959
  let nextId$1 = 0;
@@ -10128,7 +11111,15 @@ class NhAssistantPanelComponent {
10128
11111
  this.showConversations.set(false);
10129
11112
  this.showPreferences.set(false);
10130
11113
  this.showShare.set(false);
10131
- void this.store.submit(text);
11114
+ void this.store.submit(text, this.store.attachedFiles());
11115
+ }
11116
+ /** Uploads the files the person attached to the next message. */
11117
+ attach(files) {
11118
+ void this.store.attach(files);
11119
+ }
11120
+ /** The names of the files a queued message carries, as the queue lists them. */
11121
+ fileNames(files) {
11122
+ return files.map(file => file.name).join(', ');
10132
11123
  }
10133
11124
  editQueuedMessage(id, text) {
10134
11125
  this.resumeAfterEditing = !this.store.queuePaused();
@@ -10188,7 +11179,7 @@ class NhAssistantPanelComponent {
10188
11179
  void this.store.cancel();
10189
11180
  }
10190
11181
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: NhAssistantPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
10191
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.34", 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 </div>\n <div class=\"header-actions\">\n @if (store.enabled()) {\n @if (push.state() !== 'unavailable') {\n <button\n class=\"icon-button notification-button\"\n type=\"button\"\n [class.active]=\"push.state() === 'on'\"\n [class.off]=\"!push.settings()?.pushEnabled\"\n [class.blocked]=\"push.state() === 'blocked'\"\n [disabled]=\"push.busy()\"\n [attr.aria-pressed]=\"push.settings()?.pushEnabled === true\"\n [attr.aria-label]=\"notificationsLabel() | translate\"\n [attr.title]=\"('nh-assistant.preferences.notifications-state.' + push.state() | translate) + ' \u00B7 ' + (notificationsLabel() | translate)\"\n (click)=\"toggleNotifications()\">\n <nh-assistant-icon name=\"bell\" />\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\" [section]=\"shareSection()\" (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 [attr.inert]=\"showPreferences() || showShare() || showConversations() ? '' : null\"\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 [approvalRights]=\"store.approvalRights()\"\n (approvalChoice)=\"decideApproval($event)\"\n (retryMessage)=\"store.send($event)\" />\n }\n }\n </div>\n\n @if (notificationError() ?? 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(); notificationError.set(null)\">\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 } @else if (lastTurnFailed()) {\n <p class=\"notice\">{{ 'nh-assistant.panel.last-turn-failed' | translate }}</p>\n }\n @if (store.collaboration() || store.pageContext()) {\n <div class=\"conversation-actions\">\n @if (store.collaboration()) {\n @if (!conversation() || store.isOwner()) {\n <button class=\"icon-button\" type=\"button\" [disabled]=\"!canShare()\"\n [attr.aria-label]=\"'nh-assistant.panel.share' | translate\"\n [attr.title]=\"'nh-assistant.panel.share' | translate\"\n [attr.aria-expanded]=\"showShare() && shareSection() === 'link'\"\n (click)=\"toggleShare('link')\">\n <nh-assistant-icon name=\"link\" />\n </button>\n }\n <button class=\"icon-button\" type=\"button\" [disabled]=\"!canShare()\"\n [class.active]=\"shared()\"\n [attr.aria-label]=\"(store.isOwner() || !conversation() ? 'nh-assistant.panel.add-members' : 'nh-assistant.panel.members') | translate\"\n [attr.title]=\"(store.isOwner() || !conversation() ? 'nh-assistant.panel.add-members' : 'nh-assistant.panel.members') | translate\"\n [attr.aria-expanded]=\"showShare() && shareSection() === 'members'\"\n (click)=\"toggleShare('members')\">\n <nh-assistant-icon name=\"users\" />\n </button>\n }\n @if (store.pageContext(); as context) {\n <button\n class=\"icon-button page-context-button\"\n [class.active]=\"!store.pageContextExcluded()\"\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=\"page\" />\n </button>\n }\n </div>\n }\n @if (store.queuedMessages().length > 0) {\n <section class=\"message-queue\" [attr.aria-label]=\"'nh-assistant.queue.title' | translate: { count: store.queuedMessages().length }\">\n <div class=\"queue-heading\">\n <span aria-live=\"polite\">{{ 'nh-assistant.queue.title' | translate: { count: store.queuedMessages().length } }}</span>\n @if (store.queuePaused()) {\n <button type=\"button\" class=\"queue-resume\" [disabled]=\"editingQueuedId() !== null\" (click)=\"store.resumeQueue()\">\n {{ 'nh-assistant.queue.resume' | translate }}\n </button>\n }\n </div>\n <ol class=\"queue-items\">\n @for (item of store.queuedMessages(); track item.id) {\n <li class=\"queue-item\" [class.failed]=\"item.state === 'failed'\">\n @if (editingQueuedId() === item.id && item.state !== 'sending') {\n <div class=\"queue-edit\">\n <textarea rows=\"2\" [value]=\"queuedDraft()\" (input)=\"changeQueuedDraft($event)\"\n [attr.aria-label]=\"'nh-assistant.queue.edit' | translate\"></textarea>\n <div class=\"queue-buttons\">\n <button type=\"button\" class=\"icon-button\" [disabled]=\"!validQueuedDraft()\"\n [attr.aria-label]=\"'nh-assistant.queue.save' | translate\" [attr.title]=\"'nh-assistant.queue.save' | translate\"\n (click)=\"saveQueuedMessage(item.id)\"><nh-assistant-icon name=\"check\" /></button>\n <button type=\"button\" class=\"icon-button\" [attr.aria-label]=\"'nh-assistant.queue.cancel-edit' | translate\"\n [attr.title]=\"'nh-assistant.queue.cancel-edit' | translate\"\n (click)=\"cancelQueueEdit()\"><nh-assistant-icon name=\"close\" /></button>\n </div>\n </div>\n } @else {\n <div class=\"queue-message\">\n <p [title]=\"item.text\">{{ item.text }}</p>\n @if (item.state !== 'queued') {\n <span class=\"queue-state\">{{ 'nh-assistant.queue.state.' + item.state | translate }}</span>\n }\n </div>\n <div class=\"queue-buttons\">\n @if (store.canSteer() && item.state === 'queued') {\n <button type=\"button\" class=\"queue-steer\" [disabled]=\"editingQueuedId() !== null\"\n [attr.title]=\"'nh-assistant.queue.steer-hint' | translate\"\n (click)=\"store.steerQueuedMessage(item.id)\">{{ 'nh-assistant.queue.steer' | translate }}</button>\n }\n <button type=\"button\" class=\"icon-button\" [disabled]=\"item.state === 'sending'\"\n [attr.aria-label]=\"'nh-assistant.queue.edit' | translate\" [attr.title]=\"'nh-assistant.queue.edit' | translate\"\n (click)=\"editQueuedMessage(item.id, item.text)\"><nh-assistant-icon name=\"edit\" /></button>\n <button type=\"button\" class=\"icon-button\" [disabled]=\"item.state === 'sending'\"\n [attr.aria-label]=\"'nh-assistant.queue.remove' | translate\" [attr.title]=\"'nh-assistant.queue.remove' | translate\"\n (click)=\"store.removeQueuedMessage(item.id)\"><nh-assistant-icon name=\"close\" /></button>\n </div>\n }\n </li>\n }\n </ol>\n </section>\n }\n <nh-assistant-composer\n [disabled]=\"closed()\"\n [sendDisabled]=\"composerDisabled()\"\n [busy]=\"busy()\"\n [queueWhileBusy]=\"true\"\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 @if (store.agents().length > 1) {\n <nh-assistant-agent-picker\n nhAssistantComposerControls\n [compact]=\"true\"\n [agents]=\"store.agents()\"\n [selectedAgentId]=\"store.selectedAgentId()\"\n (agentChange)=\"store.selectAgent($event)\" />\n }\n </nh-assistant-composer>\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}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:2px;min-width:0}.header-actions .icon-button{width:32px;height:32px}.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{padding:12px 16px 14px}.conversation-actions{display:flex;align-items:center;gap:4px;margin:0 -4px 6px}.page-context-button{margin-left:auto}.message-queue{margin-bottom:10px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);background:var(--nha-background);overflow:hidden;font-size:12px}.queue-heading{display:flex;justify-content:space-between;align-items:center;gap:8px;padding:8px 10px;color:var(--nha-text-muted)}.queue-items{max-height:180px;overflow-y:auto;margin:0;padding:0;list-style:none}.queue-item{display:flex;align-items:center;gap:8px;border-top:1px solid var(--nha-border);padding:6px 8px 6px 10px}.queue-item.failed{border-left:2px solid var(--nha-danger)}.queue-message{flex:1;min-width:0}.queue-message p{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;overflow-wrap:anywhere;margin:0}.queue-state{color:var(--nha-text-muted);font-size:11px}.queue-buttons{display:flex;flex-shrink:0;align-items:center;gap:2px}.queue-buttons .icon-button{width:28px;height:30px}.queue-resume,.queue-steer{border:0;border-radius:6px;padding:6px;color:var(--nha-highlight);background:transparent;font:inherit;cursor:pointer}.queue-resume:hover,.queue-steer:hover{background:var(--nha-surface)}.queue-resume:focus-visible,.queue-steer:focus-visible{outline:2px solid var(--nha-focus)}.queue-resume:disabled,.queue-steer:disabled{cursor:not-allowed;opacity:.5}.queue-edit{width:100%}.queue-edit textarea{box-sizing:border-box;width:100%;border:1px solid var(--nha-border);border-radius:6px;padding:8px;color:var(--nha-text);background:var(--nha-surface);font:inherit;resize:vertical}.queue-edit .queue-buttons{justify-content:flex-end}.notification-button{position:relative}.notification-button.off:after{content:\"\";position:absolute;width:18px;height:2px;border-radius:1px;background:currentColor;transform:rotate(-45deg)}.notification-button.blocked{color:var(--nha-warning)}@media(max-width:359px){.header{gap:6px;padding-inline:12px}.header-actions .icon-button{width:28px;height:32px}}.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", "compact"], outputs: ["agentChange"] }, { kind: "component", type: NhAssistantComposerComponent, selector: "nh-assistant-composer", inputs: ["disabled", "sendDisabled", "busy", "queueWhileBusy", "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", inputs: ["section"], outputs: ["closed"] }, { kind: "component", type: NhAssistantThreadComponent, selector: "nh-assistant-thread", inputs: ["messages", "streaming", "deciding", "markdown", "members", "currentActorId", "canDecide", "decisionOwner", "approvalRights"], outputs: ["approvalDecision", "approvalChoice", "retryMessage"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }, { kind: "pipe", type: NhAssistantTranslatePipe, name: "nhAssistantTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
11182
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.34", 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 </div>\n <div class=\"header-actions\">\n @if (store.enabled()) {\n @if (push.state() !== 'unavailable') {\n <button\n class=\"icon-button notification-button\"\n type=\"button\"\n [class.active]=\"push.state() === 'on'\"\n [class.off]=\"!push.settings()?.pushEnabled\"\n [class.blocked]=\"push.state() === 'blocked'\"\n [disabled]=\"push.busy()\"\n [attr.aria-pressed]=\"push.settings()?.pushEnabled === true\"\n [attr.aria-label]=\"notificationsLabel() | translate\"\n [attr.title]=\"('nh-assistant.preferences.notifications-state.' + push.state() | translate) + ' \u00B7 ' + (notificationsLabel() | translate)\"\n (click)=\"toggleNotifications()\">\n <nh-assistant-icon name=\"bell\" />\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\" [section]=\"shareSection()\" (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 [attr.inert]=\"showPreferences() || showShare() || showConversations() ? '' : null\"\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 [approvalRights]=\"store.approvalRights()\"\n (approvalChoice)=\"decideApproval($event)\"\n (retryMessage)=\"store.send($event)\" />\n }\n }\n </div>\n\n @if (notificationError() ?? 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(); notificationError.set(null)\">\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 } @else if (lastTurnFailed()) {\n <p class=\"notice\">{{ 'nh-assistant.panel.last-turn-failed' | translate }}</p>\n }\n @if (store.collaboration() || store.pageContext()) {\n <div class=\"conversation-actions\">\n @if (store.collaboration()) {\n @if (!conversation() || store.isOwner()) {\n <button class=\"icon-button\" type=\"button\" [disabled]=\"!canShare()\"\n [attr.aria-label]=\"'nh-assistant.panel.share' | translate\"\n [attr.title]=\"'nh-assistant.panel.share' | translate\"\n [attr.aria-expanded]=\"showShare() && shareSection() === 'link'\"\n (click)=\"toggleShare('link')\">\n <nh-assistant-icon name=\"link\" />\n </button>\n }\n <button class=\"icon-button\" type=\"button\" [disabled]=\"!canShare()\"\n [class.active]=\"shared()\"\n [attr.aria-label]=\"(store.isOwner() || !conversation() ? 'nh-assistant.panel.add-members' : 'nh-assistant.panel.members') | translate\"\n [attr.title]=\"(store.isOwner() || !conversation() ? 'nh-assistant.panel.add-members' : 'nh-assistant.panel.members') | translate\"\n [attr.aria-expanded]=\"showShare() && shareSection() === 'members'\"\n (click)=\"toggleShare('members')\">\n <nh-assistant-icon name=\"users\" />\n </button>\n }\n @if (store.pageContext(); as context) {\n <button\n class=\"icon-button page-context-button\"\n [class.active]=\"!store.pageContextExcluded()\"\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=\"page\" />\n </button>\n }\n </div>\n }\n @if (store.queuedMessages().length > 0) {\n <section class=\"message-queue\" [attr.aria-label]=\"'nh-assistant.queue.title' | translate: { count: store.queuedMessages().length }\">\n <div class=\"queue-heading\">\n <span aria-live=\"polite\">{{ 'nh-assistant.queue.title' | translate: { count: store.queuedMessages().length } }}</span>\n @if (store.queuePaused()) {\n <button type=\"button\" class=\"queue-resume\" [disabled]=\"editingQueuedId() !== null\" (click)=\"store.resumeQueue()\">\n {{ 'nh-assistant.queue.resume' | translate }}\n </button>\n }\n </div>\n <ol class=\"queue-items\">\n @for (item of store.queuedMessages(); track item.id) {\n <li class=\"queue-item\" [class.failed]=\"item.state === 'failed'\">\n @if (editingQueuedId() === item.id && item.state !== 'sending') {\n <div class=\"queue-edit\">\n <textarea rows=\"2\" [value]=\"queuedDraft()\" (input)=\"changeQueuedDraft($event)\"\n [attr.aria-label]=\"'nh-assistant.queue.edit' | translate\"></textarea>\n <div class=\"queue-buttons\">\n <button type=\"button\" class=\"icon-button\" [disabled]=\"!validQueuedDraft()\"\n [attr.aria-label]=\"'nh-assistant.queue.save' | translate\" [attr.title]=\"'nh-assistant.queue.save' | translate\"\n (click)=\"saveQueuedMessage(item.id)\"><nh-assistant-icon name=\"check\" /></button>\n <button type=\"button\" class=\"icon-button\" [attr.aria-label]=\"'nh-assistant.queue.cancel-edit' | translate\"\n [attr.title]=\"'nh-assistant.queue.cancel-edit' | translate\"\n (click)=\"cancelQueueEdit()\"><nh-assistant-icon name=\"close\" /></button>\n </div>\n </div>\n } @else {\n <div class=\"queue-message\">\n <p [title]=\"item.text\">{{ item.text }}</p>\n @if (item.files?.length) {\n <span class=\"queue-state queue-files\" data-queue-files>{{ 'nh-assistant.queue.files' | translate: { count: item.files!.length, names: fileNames(item.files!) } }}</span>\n }\n @if (item.state !== 'queued') {\n <span class=\"queue-state\">{{ 'nh-assistant.queue.state.' + item.state | translate }}</span>\n }\n </div>\n <div class=\"queue-buttons\">\n @if (store.canSteer() && item.state === 'queued') {\n <button type=\"button\" class=\"queue-steer\" [disabled]=\"editingQueuedId() !== null\"\n [attr.title]=\"'nh-assistant.queue.steer-hint' | translate\"\n (click)=\"store.steerQueuedMessage(item.id)\">{{ 'nh-assistant.queue.steer' | translate }}</button>\n }\n <button type=\"button\" class=\"icon-button\" [disabled]=\"item.state === 'sending'\"\n [attr.aria-label]=\"'nh-assistant.queue.edit' | translate\" [attr.title]=\"'nh-assistant.queue.edit' | translate\"\n (click)=\"editQueuedMessage(item.id, item.text)\"><nh-assistant-icon name=\"edit\" /></button>\n <button type=\"button\" class=\"icon-button\" [disabled]=\"item.state === 'sending'\"\n [attr.aria-label]=\"'nh-assistant.queue.remove' | translate\" [attr.title]=\"'nh-assistant.queue.remove' | translate\"\n (click)=\"store.removeQueuedMessage(item.id)\"><nh-assistant-icon name=\"close\" /></button>\n </div>\n }\n </li>\n }\n </ol>\n </section>\n }\n <nh-assistant-composer\n [disabled]=\"closed()\"\n [sendDisabled]=\"composerDisabled()\"\n [busy]=\"busy()\"\n [queueWhileBusy]=\"true\"\n [status]=\"composerStatus()\"\n [statusActor]=\"activeMemberName()\"\n [canStop]=\"canStop()\"\n [maxLength]=\"store.limits()?.maxMessageChars ?? null\"\n [restore]=\"store.restoredDraft()\"\n [uploads]=\"store.uploads()\"\n [attachments]=\"store.attachments()\"\n (restored)=\"store.consumeRestoredDraft()\"\n (send)=\"send($event)\"\n (attach)=\"attach($event)\"\n (removeAttachment)=\"store.removeAttachment($event)\"\n (cancel)=\"cancel()\">\n @if (store.agents().length > 1) {\n <nh-assistant-agent-picker\n nhAssistantComposerControls\n [compact]=\"true\"\n [agents]=\"store.agents()\"\n [selectedAgentId]=\"store.selectedAgentId()\"\n (agentChange)=\"store.selectAgent($event)\" />\n }\n </nh-assistant-composer>\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}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:2px;min-width:0}.header-actions .icon-button{width:32px;height:32px}.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{padding:12px 16px 14px}.conversation-actions{display:flex;align-items:center;gap:4px;margin:0 -4px 6px}.page-context-button{margin-left:auto}.message-queue{margin-bottom:10px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);background:var(--nha-background);overflow:hidden;font-size:12px}.queue-heading{display:flex;justify-content:space-between;align-items:center;gap:8px;padding:8px 10px;color:var(--nha-text-muted)}.queue-items{max-height:180px;overflow-y:auto;margin:0;padding:0;list-style:none}.queue-item{display:flex;align-items:center;gap:8px;border-top:1px solid var(--nha-border);padding:6px 8px 6px 10px}.queue-item.failed{border-left:2px solid var(--nha-danger)}.queue-message{flex:1;min-width:0}.queue-message p{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;overflow-wrap:anywhere;margin:0}.queue-state{color:var(--nha-text-muted);font-size:11px}.queue-files{display:block;overflow-wrap:anywhere}.queue-buttons{display:flex;flex-shrink:0;align-items:center;gap:2px}.queue-buttons .icon-button{width:28px;height:30px}.queue-resume,.queue-steer{border:0;border-radius:6px;padding:6px;color:var(--nha-highlight);background:transparent;font:inherit;cursor:pointer}.queue-resume:hover,.queue-steer:hover{background:var(--nha-surface)}.queue-resume:focus-visible,.queue-steer:focus-visible{outline:2px solid var(--nha-focus)}.queue-resume:disabled,.queue-steer:disabled{cursor:not-allowed;opacity:.5}.queue-edit{width:100%}.queue-edit textarea{box-sizing:border-box;width:100%;border:1px solid var(--nha-border);border-radius:6px;padding:8px;color:var(--nha-text);background:var(--nha-surface);font:inherit;resize:vertical}.queue-edit .queue-buttons{justify-content:flex-end}.notification-button{position:relative}.notification-button.off:after{content:\"\";position:absolute;width:18px;height:2px;border-radius:1px;background:currentColor;transform:rotate(-45deg)}.notification-button.blocked{color:var(--nha-warning)}@media(max-width:359px){.header{gap:6px;padding-inline:12px}.header-actions .icon-button{width:28px;height:32px}}.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", "compact"], outputs: ["agentChange"] }, { kind: "component", type: NhAssistantComposerComponent, selector: "nh-assistant-composer", inputs: ["disabled", "sendDisabled", "busy", "queueWhileBusy", "status", "statusActor", "canStop", "maxLength", "restore", "uploads", "attachments"], outputs: ["send", "cancel", "restored", "attach", "removeAttachment"] }, { 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", inputs: ["section"], outputs: ["closed"] }, { kind: "component", type: NhAssistantThreadComponent, selector: "nh-assistant-thread", inputs: ["messages", "streaming", "deciding", "markdown", "members", "currentActorId", "canDecide", "decisionOwner", "approvalRights"], outputs: ["approvalDecision", "approvalChoice", "retryMessage"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }, { kind: "pipe", type: NhAssistantTranslatePipe, name: "nhAssistantTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
10192
11183
  }
10193
11184
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: NhAssistantPanelComponent, decorators: [{
10194
11185
  type: Component,
@@ -10204,7 +11195,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImpo
10204
11195
  NhAssistantPreferencesComponent,
10205
11196
  NhAssistantShareComponent,
10206
11197
  NhAssistantThreadComponent
10207
- ], 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 </div>\n <div class=\"header-actions\">\n @if (store.enabled()) {\n @if (push.state() !== 'unavailable') {\n <button\n class=\"icon-button notification-button\"\n type=\"button\"\n [class.active]=\"push.state() === 'on'\"\n [class.off]=\"!push.settings()?.pushEnabled\"\n [class.blocked]=\"push.state() === 'blocked'\"\n [disabled]=\"push.busy()\"\n [attr.aria-pressed]=\"push.settings()?.pushEnabled === true\"\n [attr.aria-label]=\"notificationsLabel() | translate\"\n [attr.title]=\"('nh-assistant.preferences.notifications-state.' + push.state() | translate) + ' \u00B7 ' + (notificationsLabel() | translate)\"\n (click)=\"toggleNotifications()\">\n <nh-assistant-icon name=\"bell\" />\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\" [section]=\"shareSection()\" (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 [attr.inert]=\"showPreferences() || showShare() || showConversations() ? '' : null\"\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 [approvalRights]=\"store.approvalRights()\"\n (approvalChoice)=\"decideApproval($event)\"\n (retryMessage)=\"store.send($event)\" />\n }\n }\n </div>\n\n @if (notificationError() ?? 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(); notificationError.set(null)\">\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 } @else if (lastTurnFailed()) {\n <p class=\"notice\">{{ 'nh-assistant.panel.last-turn-failed' | translate }}</p>\n }\n @if (store.collaboration() || store.pageContext()) {\n <div class=\"conversation-actions\">\n @if (store.collaboration()) {\n @if (!conversation() || store.isOwner()) {\n <button class=\"icon-button\" type=\"button\" [disabled]=\"!canShare()\"\n [attr.aria-label]=\"'nh-assistant.panel.share' | translate\"\n [attr.title]=\"'nh-assistant.panel.share' | translate\"\n [attr.aria-expanded]=\"showShare() && shareSection() === 'link'\"\n (click)=\"toggleShare('link')\">\n <nh-assistant-icon name=\"link\" />\n </button>\n }\n <button class=\"icon-button\" type=\"button\" [disabled]=\"!canShare()\"\n [class.active]=\"shared()\"\n [attr.aria-label]=\"(store.isOwner() || !conversation() ? 'nh-assistant.panel.add-members' : 'nh-assistant.panel.members') | translate\"\n [attr.title]=\"(store.isOwner() || !conversation() ? 'nh-assistant.panel.add-members' : 'nh-assistant.panel.members') | translate\"\n [attr.aria-expanded]=\"showShare() && shareSection() === 'members'\"\n (click)=\"toggleShare('members')\">\n <nh-assistant-icon name=\"users\" />\n </button>\n }\n @if (store.pageContext(); as context) {\n <button\n class=\"icon-button page-context-button\"\n [class.active]=\"!store.pageContextExcluded()\"\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=\"page\" />\n </button>\n }\n </div>\n }\n @if (store.queuedMessages().length > 0) {\n <section class=\"message-queue\" [attr.aria-label]=\"'nh-assistant.queue.title' | translate: { count: store.queuedMessages().length }\">\n <div class=\"queue-heading\">\n <span aria-live=\"polite\">{{ 'nh-assistant.queue.title' | translate: { count: store.queuedMessages().length } }}</span>\n @if (store.queuePaused()) {\n <button type=\"button\" class=\"queue-resume\" [disabled]=\"editingQueuedId() !== null\" (click)=\"store.resumeQueue()\">\n {{ 'nh-assistant.queue.resume' | translate }}\n </button>\n }\n </div>\n <ol class=\"queue-items\">\n @for (item of store.queuedMessages(); track item.id) {\n <li class=\"queue-item\" [class.failed]=\"item.state === 'failed'\">\n @if (editingQueuedId() === item.id && item.state !== 'sending') {\n <div class=\"queue-edit\">\n <textarea rows=\"2\" [value]=\"queuedDraft()\" (input)=\"changeQueuedDraft($event)\"\n [attr.aria-label]=\"'nh-assistant.queue.edit' | translate\"></textarea>\n <div class=\"queue-buttons\">\n <button type=\"button\" class=\"icon-button\" [disabled]=\"!validQueuedDraft()\"\n [attr.aria-label]=\"'nh-assistant.queue.save' | translate\" [attr.title]=\"'nh-assistant.queue.save' | translate\"\n (click)=\"saveQueuedMessage(item.id)\"><nh-assistant-icon name=\"check\" /></button>\n <button type=\"button\" class=\"icon-button\" [attr.aria-label]=\"'nh-assistant.queue.cancel-edit' | translate\"\n [attr.title]=\"'nh-assistant.queue.cancel-edit' | translate\"\n (click)=\"cancelQueueEdit()\"><nh-assistant-icon name=\"close\" /></button>\n </div>\n </div>\n } @else {\n <div class=\"queue-message\">\n <p [title]=\"item.text\">{{ item.text }}</p>\n @if (item.state !== 'queued') {\n <span class=\"queue-state\">{{ 'nh-assistant.queue.state.' + item.state | translate }}</span>\n }\n </div>\n <div class=\"queue-buttons\">\n @if (store.canSteer() && item.state === 'queued') {\n <button type=\"button\" class=\"queue-steer\" [disabled]=\"editingQueuedId() !== null\"\n [attr.title]=\"'nh-assistant.queue.steer-hint' | translate\"\n (click)=\"store.steerQueuedMessage(item.id)\">{{ 'nh-assistant.queue.steer' | translate }}</button>\n }\n <button type=\"button\" class=\"icon-button\" [disabled]=\"item.state === 'sending'\"\n [attr.aria-label]=\"'nh-assistant.queue.edit' | translate\" [attr.title]=\"'nh-assistant.queue.edit' | translate\"\n (click)=\"editQueuedMessage(item.id, item.text)\"><nh-assistant-icon name=\"edit\" /></button>\n <button type=\"button\" class=\"icon-button\" [disabled]=\"item.state === 'sending'\"\n [attr.aria-label]=\"'nh-assistant.queue.remove' | translate\" [attr.title]=\"'nh-assistant.queue.remove' | translate\"\n (click)=\"store.removeQueuedMessage(item.id)\"><nh-assistant-icon name=\"close\" /></button>\n </div>\n }\n </li>\n }\n </ol>\n </section>\n }\n <nh-assistant-composer\n [disabled]=\"closed()\"\n [sendDisabled]=\"composerDisabled()\"\n [busy]=\"busy()\"\n [queueWhileBusy]=\"true\"\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 @if (store.agents().length > 1) {\n <nh-assistant-agent-picker\n nhAssistantComposerControls\n [compact]=\"true\"\n [agents]=\"store.agents()\"\n [selectedAgentId]=\"store.selectedAgentId()\"\n (agentChange)=\"store.selectAgent($event)\" />\n }\n </nh-assistant-composer>\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}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:2px;min-width:0}.header-actions .icon-button{width:32px;height:32px}.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{padding:12px 16px 14px}.conversation-actions{display:flex;align-items:center;gap:4px;margin:0 -4px 6px}.page-context-button{margin-left:auto}.message-queue{margin-bottom:10px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);background:var(--nha-background);overflow:hidden;font-size:12px}.queue-heading{display:flex;justify-content:space-between;align-items:center;gap:8px;padding:8px 10px;color:var(--nha-text-muted)}.queue-items{max-height:180px;overflow-y:auto;margin:0;padding:0;list-style:none}.queue-item{display:flex;align-items:center;gap:8px;border-top:1px solid var(--nha-border);padding:6px 8px 6px 10px}.queue-item.failed{border-left:2px solid var(--nha-danger)}.queue-message{flex:1;min-width:0}.queue-message p{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;overflow-wrap:anywhere;margin:0}.queue-state{color:var(--nha-text-muted);font-size:11px}.queue-buttons{display:flex;flex-shrink:0;align-items:center;gap:2px}.queue-buttons .icon-button{width:28px;height:30px}.queue-resume,.queue-steer{border:0;border-radius:6px;padding:6px;color:var(--nha-highlight);background:transparent;font:inherit;cursor:pointer}.queue-resume:hover,.queue-steer:hover{background:var(--nha-surface)}.queue-resume:focus-visible,.queue-steer:focus-visible{outline:2px solid var(--nha-focus)}.queue-resume:disabled,.queue-steer:disabled{cursor:not-allowed;opacity:.5}.queue-edit{width:100%}.queue-edit textarea{box-sizing:border-box;width:100%;border:1px solid var(--nha-border);border-radius:6px;padding:8px;color:var(--nha-text);background:var(--nha-surface);font:inherit;resize:vertical}.queue-edit .queue-buttons{justify-content:flex-end}.notification-button{position:relative}.notification-button.off:after{content:\"\";position:absolute;width:18px;height:2px;border-radius:1px;background:currentColor;transform:rotate(-45deg)}.notification-button.blocked{color:var(--nha-warning)}@media(max-width:359px){.header{gap:6px;padding-inline:12px}.header-actions .icon-button{width:28px;height:32px}}.notice{margin:0 0 8px;color:var(--nha-text-muted);font-size:12px}\n"] }]
11198
+ ], 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 </div>\n <div class=\"header-actions\">\n @if (store.enabled()) {\n @if (push.state() !== 'unavailable') {\n <button\n class=\"icon-button notification-button\"\n type=\"button\"\n [class.active]=\"push.state() === 'on'\"\n [class.off]=\"!push.settings()?.pushEnabled\"\n [class.blocked]=\"push.state() === 'blocked'\"\n [disabled]=\"push.busy()\"\n [attr.aria-pressed]=\"push.settings()?.pushEnabled === true\"\n [attr.aria-label]=\"notificationsLabel() | translate\"\n [attr.title]=\"('nh-assistant.preferences.notifications-state.' + push.state() | translate) + ' \u00B7 ' + (notificationsLabel() | translate)\"\n (click)=\"toggleNotifications()\">\n <nh-assistant-icon name=\"bell\" />\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\" [section]=\"shareSection()\" (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 [attr.inert]=\"showPreferences() || showShare() || showConversations() ? '' : null\"\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 [approvalRights]=\"store.approvalRights()\"\n (approvalChoice)=\"decideApproval($event)\"\n (retryMessage)=\"store.send($event)\" />\n }\n }\n </div>\n\n @if (notificationError() ?? 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(); notificationError.set(null)\">\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 } @else if (lastTurnFailed()) {\n <p class=\"notice\">{{ 'nh-assistant.panel.last-turn-failed' | translate }}</p>\n }\n @if (store.collaboration() || store.pageContext()) {\n <div class=\"conversation-actions\">\n @if (store.collaboration()) {\n @if (!conversation() || store.isOwner()) {\n <button class=\"icon-button\" type=\"button\" [disabled]=\"!canShare()\"\n [attr.aria-label]=\"'nh-assistant.panel.share' | translate\"\n [attr.title]=\"'nh-assistant.panel.share' | translate\"\n [attr.aria-expanded]=\"showShare() && shareSection() === 'link'\"\n (click)=\"toggleShare('link')\">\n <nh-assistant-icon name=\"link\" />\n </button>\n }\n <button class=\"icon-button\" type=\"button\" [disabled]=\"!canShare()\"\n [class.active]=\"shared()\"\n [attr.aria-label]=\"(store.isOwner() || !conversation() ? 'nh-assistant.panel.add-members' : 'nh-assistant.panel.members') | translate\"\n [attr.title]=\"(store.isOwner() || !conversation() ? 'nh-assistant.panel.add-members' : 'nh-assistant.panel.members') | translate\"\n [attr.aria-expanded]=\"showShare() && shareSection() === 'members'\"\n (click)=\"toggleShare('members')\">\n <nh-assistant-icon name=\"users\" />\n </button>\n }\n @if (store.pageContext(); as context) {\n <button\n class=\"icon-button page-context-button\"\n [class.active]=\"!store.pageContextExcluded()\"\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=\"page\" />\n </button>\n }\n </div>\n }\n @if (store.queuedMessages().length > 0) {\n <section class=\"message-queue\" [attr.aria-label]=\"'nh-assistant.queue.title' | translate: { count: store.queuedMessages().length }\">\n <div class=\"queue-heading\">\n <span aria-live=\"polite\">{{ 'nh-assistant.queue.title' | translate: { count: store.queuedMessages().length } }}</span>\n @if (store.queuePaused()) {\n <button type=\"button\" class=\"queue-resume\" [disabled]=\"editingQueuedId() !== null\" (click)=\"store.resumeQueue()\">\n {{ 'nh-assistant.queue.resume' | translate }}\n </button>\n }\n </div>\n <ol class=\"queue-items\">\n @for (item of store.queuedMessages(); track item.id) {\n <li class=\"queue-item\" [class.failed]=\"item.state === 'failed'\">\n @if (editingQueuedId() === item.id && item.state !== 'sending') {\n <div class=\"queue-edit\">\n <textarea rows=\"2\" [value]=\"queuedDraft()\" (input)=\"changeQueuedDraft($event)\"\n [attr.aria-label]=\"'nh-assistant.queue.edit' | translate\"></textarea>\n <div class=\"queue-buttons\">\n <button type=\"button\" class=\"icon-button\" [disabled]=\"!validQueuedDraft()\"\n [attr.aria-label]=\"'nh-assistant.queue.save' | translate\" [attr.title]=\"'nh-assistant.queue.save' | translate\"\n (click)=\"saveQueuedMessage(item.id)\"><nh-assistant-icon name=\"check\" /></button>\n <button type=\"button\" class=\"icon-button\" [attr.aria-label]=\"'nh-assistant.queue.cancel-edit' | translate\"\n [attr.title]=\"'nh-assistant.queue.cancel-edit' | translate\"\n (click)=\"cancelQueueEdit()\"><nh-assistant-icon name=\"close\" /></button>\n </div>\n </div>\n } @else {\n <div class=\"queue-message\">\n <p [title]=\"item.text\">{{ item.text }}</p>\n @if (item.files?.length) {\n <span class=\"queue-state queue-files\" data-queue-files>{{ 'nh-assistant.queue.files' | translate: { count: item.files!.length, names: fileNames(item.files!) } }}</span>\n }\n @if (item.state !== 'queued') {\n <span class=\"queue-state\">{{ 'nh-assistant.queue.state.' + item.state | translate }}</span>\n }\n </div>\n <div class=\"queue-buttons\">\n @if (store.canSteer() && item.state === 'queued') {\n <button type=\"button\" class=\"queue-steer\" [disabled]=\"editingQueuedId() !== null\"\n [attr.title]=\"'nh-assistant.queue.steer-hint' | translate\"\n (click)=\"store.steerQueuedMessage(item.id)\">{{ 'nh-assistant.queue.steer' | translate }}</button>\n }\n <button type=\"button\" class=\"icon-button\" [disabled]=\"item.state === 'sending'\"\n [attr.aria-label]=\"'nh-assistant.queue.edit' | translate\" [attr.title]=\"'nh-assistant.queue.edit' | translate\"\n (click)=\"editQueuedMessage(item.id, item.text)\"><nh-assistant-icon name=\"edit\" /></button>\n <button type=\"button\" class=\"icon-button\" [disabled]=\"item.state === 'sending'\"\n [attr.aria-label]=\"'nh-assistant.queue.remove' | translate\" [attr.title]=\"'nh-assistant.queue.remove' | translate\"\n (click)=\"store.removeQueuedMessage(item.id)\"><nh-assistant-icon name=\"close\" /></button>\n </div>\n }\n </li>\n }\n </ol>\n </section>\n }\n <nh-assistant-composer\n [disabled]=\"closed()\"\n [sendDisabled]=\"composerDisabled()\"\n [busy]=\"busy()\"\n [queueWhileBusy]=\"true\"\n [status]=\"composerStatus()\"\n [statusActor]=\"activeMemberName()\"\n [canStop]=\"canStop()\"\n [maxLength]=\"store.limits()?.maxMessageChars ?? null\"\n [restore]=\"store.restoredDraft()\"\n [uploads]=\"store.uploads()\"\n [attachments]=\"store.attachments()\"\n (restored)=\"store.consumeRestoredDraft()\"\n (send)=\"send($event)\"\n (attach)=\"attach($event)\"\n (removeAttachment)=\"store.removeAttachment($event)\"\n (cancel)=\"cancel()\">\n @if (store.agents().length > 1) {\n <nh-assistant-agent-picker\n nhAssistantComposerControls\n [compact]=\"true\"\n [agents]=\"store.agents()\"\n [selectedAgentId]=\"store.selectedAgentId()\"\n (agentChange)=\"store.selectAgent($event)\" />\n }\n </nh-assistant-composer>\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}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:2px;min-width:0}.header-actions .icon-button{width:32px;height:32px}.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{padding:12px 16px 14px}.conversation-actions{display:flex;align-items:center;gap:4px;margin:0 -4px 6px}.page-context-button{margin-left:auto}.message-queue{margin-bottom:10px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);background:var(--nha-background);overflow:hidden;font-size:12px}.queue-heading{display:flex;justify-content:space-between;align-items:center;gap:8px;padding:8px 10px;color:var(--nha-text-muted)}.queue-items{max-height:180px;overflow-y:auto;margin:0;padding:0;list-style:none}.queue-item{display:flex;align-items:center;gap:8px;border-top:1px solid var(--nha-border);padding:6px 8px 6px 10px}.queue-item.failed{border-left:2px solid var(--nha-danger)}.queue-message{flex:1;min-width:0}.queue-message p{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;overflow-wrap:anywhere;margin:0}.queue-state{color:var(--nha-text-muted);font-size:11px}.queue-files{display:block;overflow-wrap:anywhere}.queue-buttons{display:flex;flex-shrink:0;align-items:center;gap:2px}.queue-buttons .icon-button{width:28px;height:30px}.queue-resume,.queue-steer{border:0;border-radius:6px;padding:6px;color:var(--nha-highlight);background:transparent;font:inherit;cursor:pointer}.queue-resume:hover,.queue-steer:hover{background:var(--nha-surface)}.queue-resume:focus-visible,.queue-steer:focus-visible{outline:2px solid var(--nha-focus)}.queue-resume:disabled,.queue-steer:disabled{cursor:not-allowed;opacity:.5}.queue-edit{width:100%}.queue-edit textarea{box-sizing:border-box;width:100%;border:1px solid var(--nha-border);border-radius:6px;padding:8px;color:var(--nha-text);background:var(--nha-surface);font:inherit;resize:vertical}.queue-edit .queue-buttons{justify-content:flex-end}.notification-button{position:relative}.notification-button.off:after{content:\"\";position:absolute;width:18px;height:2px;border-radius:1px;background:currentColor;transform:rotate(-45deg)}.notification-button.blocked{color:var(--nha-warning)}@media(max-width:359px){.header{gap:6px;padding-inline:12px}.header-actions .icon-button{width:28px;height:32px}}.notice{margin:0 0 8px;color:var(--nha-text-muted);font-size:12px}\n"] }]
10208
11199
  }], ctorParameters: () => [], propDecorators: { drawer: [{ type: i0.ViewChild, args: ['drawer', { isSignal: true }] }] } });
10209
11200
 
10210
11201
  /**
@@ -10633,5 +11624,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImpo
10633
11624
  * Generated bundle index. Do not edit.
10634
11625
  */
10635
11626
 
10636
- export { NH_ASSISTANT_ACCESS_POLICY, NH_ASSISTANT_CONFIG, NH_ASSISTANT_CONVERSATION_FRAGMENT, NH_ASSISTANT_FEEDBACK_CATEGORIES, NH_ASSISTANT_FEEDBACK_GROUPS, NH_ASSISTANT_FETCH, NH_ASSISTANT_ICONS, NH_ASSISTANT_JOIN_FRAGMENT, NH_ASSISTANT_MAX_BULK_FEEDBACK, NH_ASSISTANT_MAX_CUSTOM_INSTRUCTIONS, NH_ASSISTANT_MAX_FEEDBACK_TEXT, NH_ASSISTANT_MAX_PROPOSAL_LOOKUP, NH_ASSISTANT_MAX_SUBJECT_SUMMARY, NH_ASSISTANT_MCP_AUTHORIZATION_MESSAGE, NH_ASSISTANT_MCP_AUTHORIZATION_WINDOW, NH_ASSISTANT_NO_ANSWER_NOTICE_CODE, NH_ASSISTANT_PAGE_CONTEXT_LIMITS, NH_ASSISTANT_PANEL_MOBILE_QUERY, NH_ASSISTANT_PANEL_WIDTH, NH_ASSISTANT_RESULT_PREVIEW_LENGTH, NH_ASSISTANT_RUN_HISTORY_MAX_PAGE, NH_ASSISTANT_RUN_RESULT_MAX_LENGTH, NH_ASSISTANT_SSE_EVENT_TYPES, NH_ASSISTANT_SUBJECT_SEARCH, NH_ASSISTANT_SYSTEM_GLOBAL_ID, NH_ASSISTANT_SYSTEM_SUBJECT_TYPE, NH_ASSISTANT_TRANSLATIONS, NH_ASSISTANT_VIRTUAL_SCROLL_THRESHOLD, NhAssistantActivityComponent, NhAssistantAdminApiService, NhAssistantAgentPickerComponent, NhAssistantAgentRunApiService, NhAssistantAllowAllAccessPolicy, NhAssistantApiError, NhAssistantApiService, NhAssistantApprovalCardComponent, NhAssistantClientErrorCodes, NhAssistantComposerComponent, NhAssistantConnectionRequiredComponent, NhAssistantConversationListComponent, NhAssistantLauncherComponent, NhAssistantLiveService, NhAssistantMcpConnectService, NhAssistantMcpConnectionsComponent, NhAssistantMcpPopupAuthorizationWindow, NhAssistantPanelComponent, NhAssistantPanelService, NhAssistantPreferencesComponent, NhAssistantProposalFeedbackComponent, NhAssistantPushService, NhAssistantShareComponent, NhAssistantSseParser, NhAssistantStore, NhAssistantSubjectBadgeComponent, NhAssistantSubjectSummaryService, NhAssistantThreadComponent, NhAssistantToolCallCardComponent, NhAssistantToolCallGroupComponent, NhAssistantTranslationMerger, applyNhAssistantApprovalDecision, applyNhAssistantConversationChange, applyNhAssistantEvent, applyNhAssistantLiveEvent, describeNhAssistantClientContext, formatNhAssistantCountdown, nhAssistantCodeForStatus, nhAssistantConnectMcp, nhAssistantErrorMessageKey, nhAssistantIsUnread, nhAssistantLatestTurnHasText, nhAssistantMcpAuthorizationFromFragment, nhAssistantShareLink, normalizeNhAssistantClientContext, provideNhAssistant, NH_ASSISTANT_DASH_CASE as ɵNH_ASSISTANT_DASH_CASE, NhAssistantIconComponent as ɵNhAssistantIconComponent, NhAssistantMarkdownPipe as ɵNhAssistantMarkdownPipe, NhAssistantPickerComponent as ɵNhAssistantPickerComponent, NhAssistantSubjectLinkComponent as ɵNhAssistantSubjectLinkComponent, NhAssistantTranslatePipe as ɵNhAssistantTranslatePipe, nhAssistantCodeKeys as ɵnhAssistantCodeKeys, nhAssistantErrorKeys as ɵnhAssistantErrorKeys, nhAssistantErrorOf as ɵnhAssistantErrorOf, nhAssistantReloadOnAgentWork as ɵnhAssistantReloadOnAgentWork, nhAssistantWatchAgentWork as ɵnhAssistantWatchAgentWork, toNhAssistantError as ɵtoNhAssistantError };
11627
+ export { NH_ASSISTANT_ACCESS_POLICY, NH_ASSISTANT_CONFIG, NH_ASSISTANT_CONVERSATION_FRAGMENT, NH_ASSISTANT_ERROR_CODES, NH_ASSISTANT_FEEDBACK_CATEGORIES, NH_ASSISTANT_FEEDBACK_GROUPS, NH_ASSISTANT_FETCH, NH_ASSISTANT_ICONS, NH_ASSISTANT_JOIN_FRAGMENT, NH_ASSISTANT_MAX_BULK_FEEDBACK, NH_ASSISTANT_MAX_CUSTOM_INSTRUCTIONS, NH_ASSISTANT_MAX_FEEDBACK_TEXT, NH_ASSISTANT_MAX_PROPOSAL_LOOKUP, NH_ASSISTANT_MAX_SUBJECT_SUMMARY, NH_ASSISTANT_MCP_AUTHORIZATION_MESSAGE, NH_ASSISTANT_MCP_AUTHORIZATION_WINDOW, NH_ASSISTANT_MESSAGE_QUEUED_NOTICE_CODE, NH_ASSISTANT_NO_ANSWER_NOTICE_CODE, NH_ASSISTANT_PAGE_CONTEXT_LIMITS, NH_ASSISTANT_PANEL_MOBILE_QUERY, NH_ASSISTANT_PANEL_WIDTH, NH_ASSISTANT_RESULT_PREVIEW_LENGTH, NH_ASSISTANT_RUN_HISTORY_MAX_PAGE, NH_ASSISTANT_RUN_RESULT_MAX_LENGTH, NH_ASSISTANT_SERVER_ERROR_CODES, NH_ASSISTANT_SSE_EVENT_TYPES, NH_ASSISTANT_SUBJECT_SEARCH, NH_ASSISTANT_SYSTEM_GLOBAL_ID, NH_ASSISTANT_SYSTEM_SUBJECT_TYPE, NH_ASSISTANT_TRANSLATIONS, NH_ASSISTANT_UPLOAD, NH_ASSISTANT_UPLOAD_HEADER, NH_ASSISTANT_VIRTUAL_SCROLL_THRESHOLD, NhAssistantActivityComponent, NhAssistantAdminApiService, NhAssistantAgentPickerComponent, NhAssistantAgentRunApiService, NhAssistantAllowAllAccessPolicy, NhAssistantApiError, NhAssistantApiService, NhAssistantApprovalCardComponent, NhAssistantClientErrorCodes, NhAssistantComposerComponent, NhAssistantConnectionRequiredComponent, NhAssistantConversationListComponent, NhAssistantLauncherComponent, NhAssistantLiveService, NhAssistantMcpConnectService, NhAssistantMcpConnectionsComponent, NhAssistantMcpPopupAuthorizationWindow, NhAssistantPanelComponent, NhAssistantPanelService, NhAssistantPreferencesComponent, NhAssistantProposalFeedbackComponent, NhAssistantPushService, NhAssistantShareComponent, NhAssistantSseParser, NhAssistantStore, NhAssistantSubjectBadgeComponent, NhAssistantSubjectSummaryService, NhAssistantThreadComponent, NhAssistantToolCallCardComponent, NhAssistantToolCallGroupComponent, NhAssistantTranslationMerger, applyNhAssistantApprovalDecision, applyNhAssistantConversationChange, applyNhAssistantEvent, applyNhAssistantLiveEvent, describeNhAssistantClientContext, formatNhAssistantCountdown, nhAssistantCodeForStatus, nhAssistantConnectMcp, nhAssistantErrorMessageKey, nhAssistantIsUnread, nhAssistantLatestTurnHasText, nhAssistantMcpAuthorizationFromFragment, nhAssistantShareLink, nhAssistantXhrUpload, normalizeNhAssistantClientContext, provideNhAssistant, toNhAssistantError, NH_ASSISTANT_DASH_CASE as ɵNH_ASSISTANT_DASH_CASE, NhAssistantIconComponent as ɵNhAssistantIconComponent, NhAssistantMarkdownPipe as ɵNhAssistantMarkdownPipe, NhAssistantPickerComponent as ɵNhAssistantPickerComponent, NhAssistantSubjectLinkComponent as ɵNhAssistantSubjectLinkComponent, NhAssistantTranslatePipe as ɵNhAssistantTranslatePipe, nhAssistantCodeKeys as ɵnhAssistantCodeKeys, nhAssistantErrorKeys as ɵnhAssistantErrorKeys, nhAssistantErrorOf as ɵnhAssistantErrorOf, nhAssistantReloadOnAgentWork as ɵnhAssistantReloadOnAgentWork, nhAssistantWatchAgentWork as ɵnhAssistantWatchAgentWork, toNhAssistantError as ɵtoNhAssistantError };
10637
11628
  //# sourceMappingURL=newheap-platform-ai-chat.mjs.map