@newheap/platform-ai-chat 0.15.0 → 0.17.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.",
@@ -497,6 +575,7 @@ var en = {
497
575
  "assistant-service-account-not-found": "No account that may sign in exists with this id.",
498
576
  "assistant-skill-in-code": "This skill is part of the code and cannot change here.",
499
577
  "assistant-trigger-in-code": "This trigger is part of the code and cannot change here.",
578
+ "assistant-trigger-filter-not-found": "The event has no filter with this name. Choose from the filters the event offers.",
500
579
  "assistant-skill-in-use": "A trigger still starts this skill. Change or remove the trigger first.",
501
580
  "assistant-configuration-limit": "This agent already has as many skills or triggers as allowed.",
502
581
  "assistant-run-settings-in-code": "This agent is part of the code; its runs are configured there.",
@@ -522,11 +601,19 @@ var en = {
522
601
  "assistant-mcp-authorization-cancelled": "The authorization window was closed before it finished.",
523
602
  "assistant-mcp-popup-blocked": "The browser blocked the authorization window. Allow pop-ups for this site and try again.",
524
603
  "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."
604
+ "assistant-proposal-not-placed": "The proposal was not placed at the object it concerns.",
605
+ "assistant-proposal-not-needed": "Not proposed: the work is already done or no longer applies.",
606
+ "assistant-file-upload-invalid": "The file could not be uploaded. Choose one file and try again.",
607
+ "assistant-file-type-not-accepted": "This type of file cannot be attached.",
608
+ "assistant-file-upload-refused": "The application refused this file.",
609
+ "assistant-file-upload-failed": "The file could not be stored right now. Try again in a moment.",
610
+ "assistant-file-scan-refused": "The file did not pass the scan for harmful content and was not stored."
526
611
  },
527
612
  notices: {
528
613
  "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."
614
+ "conversation-removed": "This conversation is no longer available to you.",
615
+ "message-queued": "The agent is working in this conversation. Your message waits for its next run, which adds it here.",
616
+ "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
617
  },
531
618
  preferences: {
532
619
  title: "Your preferences",
@@ -672,22 +759,31 @@ var en = {
672
759
  stopped: "Stopped",
673
760
  "configured-off": "Off in configuration",
674
761
  "configured-off-hint": "Disabled in the application's code until the next deployment; it cannot be resumed here.",
762
+ off: "Off",
763
+ "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.",
764
+ "starts-off": "It started off, as its code says.",
675
765
  changed: "Changed by {{user}} on {{date}}.",
676
766
  reason: "Note: {{reason}}",
677
767
  waiting: "{{count}} events wait for the resume.",
678
768
  action: {
679
769
  active: "Resume",
680
770
  paused: "Pause",
771
+ off: "Switch off",
772
+ "switch-on": "Switch on",
681
773
  stopped: "Stop"
682
774
  },
683
775
  explain: {
684
776
  active: "The agent works again. Every subject with waiting events gets one run.",
685
777
  paused: "No new runs start. Events wait and run after the resume; running work finishes and approved proposals execute.",
778
+ 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.",
779
+ "switch-on": "The agent takes its events, schedules and other triggers again. Nothing that happened while it was off runs.",
686
780
  stopped: "Active runs are cancelled, their open proposals withdrawn and waiting events dropped. Approved proposals wait until the agent works again."
687
781
  },
688
782
  confirm: {
689
783
  active: "Confirm resume",
690
784
  paused: "Confirm pause",
785
+ off: "Confirm switch off",
786
+ "switch-on": "Confirm switch on",
691
787
  stopped: "Confirm stop"
692
788
  },
693
789
  "note-label": "Note for other administrators (optional)",
@@ -700,6 +796,8 @@ var en = {
700
796
  explain: {
701
797
  active: "The skill {{skill}} works again. Every subject with events waiting for it gets one run.",
702
798
  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.",
799
+ 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.",
800
+ "switch-on": "The skill {{skill}} takes work again. Nothing that happened while it was off runs.",
703
801
  stopped: "The runs of the skill {{skill}} are cancelled and its waiting events dropped; its triggers are refused. The other skills keep working."
704
802
  }
705
803
  },
@@ -776,7 +874,10 @@ var en = {
776
874
  "view-policy": "Results for the whole application seen by: {{policy}}."
777
875
  },
778
876
  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."
877
+ "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.",
878
+ warnings: {
879
+ "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."
880
+ }
780
881
  },
781
882
  "agent-editor": {
782
883
  "new-title": "New agent",
@@ -791,7 +892,14 @@ var en = {
791
892
  "autonomy-hint": "Only Execute allows changes, and every change still needs approval.",
792
893
  "required-policy": "Required policy",
793
894
  "required-policy-hint": "Optional. Only users who pass this policy see the agent.",
895
+ profile: "Model profile",
896
+ "profile-default": "The application's chat profile",
897
+ "profile-hint": "The model the agent works with, from the profiles the application offers for agents created here.",
898
+ classification: "Data classification",
899
+ "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
900
  "state-hint": "Pause, stop and resume the agent from the agent list. Saving never changes its state.",
901
+ "switch-on-now": "Switch on now",
902
+ "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
903
  "tool-selectors": "Tool selectors",
796
904
  "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
905
  "add-selector": "Add pattern",
@@ -1006,7 +1114,33 @@ var en = {
1006
1114
  "client-secret-reference": "Client secret reference",
1007
1115
  "organization-account-label": "Connected account",
1008
1116
  "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."
1117
+ "organization-account-label-required": "Name the account that will be connected.",
1118
+ reader: {
1119
+ title: "Reading documents",
1120
+ 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.",
1121
+ empty: "This server reads no files.",
1122
+ item: "Reader {{number}}",
1123
+ add: "Add reader",
1124
+ "add-hint": "Each reader is one of the server's synced tools; a server has at most {{max}}.",
1125
+ limit: "This server has the most readers it can have ({{max}}).",
1126
+ remove: "Remove",
1127
+ "remove-label": "Remove reader {{number}}",
1128
+ "media-types": "Media types",
1129
+ "media-types-hint": "Comma-separated, at most 16, such as application/vnd.openxmlformats-officedocument.wordprocessingml.document.",
1130
+ "media-types-invalid": "Enter 1 to 16 media types of the form type/subtype, separated by commas.",
1131
+ tool: "Reading tool",
1132
+ "tool-choose": "Choose a tool",
1133
+ "tool-hint": "A tool synced from this server that turns a file into text. It does not have to be enabled for agents.",
1134
+ "tool-duplicate": "Another reader of this server already uses this tool.",
1135
+ "tool-sync": "Sync the server's tools first, then add a reader.",
1136
+ priority: "Priority",
1137
+ "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.",
1138
+ "priority-invalid": "Enter a whole number from {{min}} to {{max}}.",
1139
+ "max-classification": "Highest classification",
1140
+ "max-classification-choose": "Choose a classification",
1141
+ "max-classification-hint": "A file that is more sensitive is never sent to this server.",
1142
+ "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."
1143
+ }
1010
1144
  },
1011
1145
  tools: {
1012
1146
  title: "Tools of {{name}}",
@@ -1154,6 +1288,19 @@ var en = {
1154
1288
  "replay-progress": "{{replayed}} of at most {{max}} runs replayed, {{skipped}} skipped"
1155
1289
  },
1156
1290
  "run-config": {
1291
+ files: {
1292
+ label: "Accepts files",
1293
+ "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.",
1294
+ "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.",
1295
+ "limits-title": "Lower file limits",
1296
+ "agent-limits-hint": "Leave a field empty to keep the application's limit. A value can only lower it.",
1297
+ "skill-limits-hint": "Leave a field empty to keep the agent's limit. A value can only lower the application's.",
1298
+ "max-file-megabytes": "Largest file (MB)",
1299
+ "max-file-pages": "Most pages per file",
1300
+ "max-files-per-turn": "Most files per turn",
1301
+ "max-file-tokens-per-turn": "Most file tokens per turn",
1302
+ "application-limit": "The application allows at most {{value}}."
1303
+ },
1157
1304
  back: "Back to agents",
1158
1305
  title: "Run configuration of {{name}}",
1159
1306
  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.",
@@ -1173,6 +1320,7 @@ var en = {
1173
1320
  "unknown-policy": "the approval policy no longer exists.",
1174
1321
  "unknown-rule-handler": "the rules no longer exist.",
1175
1322
  "unknown-event": "the event no longer exists.",
1323
+ "unknown-filter": "a filter of the event no longer exists, so the trigger starts nothing until you remove it.",
1176
1324
  "unknown-subject-set": "the set of subjects no longer exists.",
1177
1325
  "unknown-skill": "the skill no longer exists.",
1178
1326
  "invalid-schedule": "the schedule or its time zone is not valid.",
@@ -1192,7 +1340,9 @@ var en = {
1192
1340
  taken: "This id is already in use.",
1193
1341
  "name-letters": "Use at least one letter in the name.",
1194
1342
  "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."
1343
+ "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.",
1344
+ "megabytes-up-to": "Enter a size above 0 and at most {{max}} MB.",
1345
+ "megabytes-positive": "Enter a size above 0 MB."
1196
1346
  },
1197
1347
  settings: {
1198
1348
  title: "Run settings",
@@ -1293,6 +1443,8 @@ var en = {
1293
1443
  "apply-shadow": "Evaluate in shadow next to the live version first",
1294
1444
  "apply-live": "Put live at once",
1295
1445
  "starts-in-shadow": "A new skill starts in shadow.",
1446
+ "switch-on-now": "Switch on now",
1447
+ "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
1448
  versions: "Versions",
1297
1449
  "remove-explain": "Its versions stay as history. A trigger that starts it must go first.",
1298
1450
  what: {
@@ -1304,6 +1456,22 @@ var en = {
1304
1456
  "rules-hint": "The rules decide without a model; a doubtful case goes to the model with the instructions below.",
1305
1457
  "model-hint": "The model decides with the instructions below and the chosen tools.",
1306
1458
  "profile-hint": "The model profile it uses; by default the agent's.",
1459
+ classification: {
1460
+ label: "Data classification",
1461
+ agent: "The agent's classification",
1462
+ 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.",
1463
+ "public": "Public",
1464
+ "public-hint": "Data anyone may see.",
1465
+ internal: "Internal",
1466
+ "internal-hint": "Data of the organization; the default.",
1467
+ confidential: "Confidential",
1468
+ "confidential-hint": "Data only some people may see, such as personal or financial data.",
1469
+ restricted: "Restricted",
1470
+ "restricted-hint": "The most sensitive data, for which only specially approved models may be used."
1471
+ },
1472
+ warnings: {
1473
+ "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."
1474
+ },
1307
1475
  "apply-shadow-hint": "The new version runs next to the live one; compare them, then promote or discard it.",
1308
1476
  "apply-live-hint": "For a small correction: the new version replaces the live one at once.",
1309
1477
  "tools-pattern": "All tools that match: {{count}} now.",
@@ -1385,6 +1553,13 @@ var en = {
1385
1553
  "source-schedule-hint": "At fixed times, for every subject in a set.",
1386
1554
  "source-schedule-application-hint": "At fixed times, for the whole application or for every object in a set.",
1387
1555
  "debounce-hint": "Waits this long for more events about the same subject and handles them in one run.",
1556
+ filters: {
1557
+ title: "Filters",
1558
+ hint: "Skip events this trigger should not start the agent on, such as changes the agent made itself. Every filter you select must let an event through.",
1559
+ empty: "This event offers no filters.",
1560
+ missing: "The event no longer offers this filter, so the trigger starts nothing. Clear it to save.",
1561
+ applied: "Filters: {{filters}}"
1562
+ },
1388
1563
  "subject-set-hint": "The projects or customers the schedule handles each time.",
1389
1564
  "message-hint": "What the agent reads as its task each time.",
1390
1565
  examples: "Examples",
@@ -1775,7 +1950,18 @@ var en = {
1775
1950
  previous: "Previous",
1776
1951
  next: "Next",
1777
1952
  page: "Page {{page}} of {{pages}}",
1778
- pages: "Pages"
1953
+ pages: "Pages",
1954
+ "new": "New",
1955
+ "new-label": "New since your last visit",
1956
+ "last-visit": "You last looked here on {{date}}. What came since is marked new.",
1957
+ "first-visit": "Everything here is new for you.",
1958
+ "group-loaded": "{{loaded}} of {{total}} shown",
1959
+ "select-group": "Select the {{count}} shown",
1960
+ "select-group-label": "Select the {{count}} proposals of {{name}} shown on this page",
1961
+ "group-continued": "Continued from the previous page.",
1962
+ "group-continues": "More proposals of this group are on the next page.",
1963
+ run: "One run of {{agent}}, {{date}}",
1964
+ "no-run": "Proposed in a conversation"
1779
1965
  },
1780
1966
  proposal: {
1781
1967
  approve: "Approve",
@@ -2086,7 +2272,9 @@ var nl = {
2086
2272
  elapsed: "{{seconds}} s",
2087
2273
  participant: "Deelnemer",
2088
2274
  trigger: "Gebeurtenis",
2089
- rule: "Automatische regel"
2275
+ rule: "Automatische regel",
2276
+ files: "Bijgevoegde bestanden",
2277
+ "file-meta": "{{type}}, {{size}}"
2090
2278
  },
2091
2279
  composer: {
2092
2280
  label: "Bericht",
@@ -2101,6 +2289,18 @@ var nl = {
2101
2289
  "waiting-for-approval": "Wacht op goedkeuring",
2102
2290
  "running-by": "{{name}} werkt met de assistent…",
2103
2291
  "waiting-for-approval-by": "Wacht op de beslissing van {{name}}…"
2292
+ },
2293
+ attach: "Bestanden bijvoegen",
2294
+ "attach-hint": "{{types}}, elk tot {{max}}, hoogstens {{count}} bestanden per bericht",
2295
+ attachments: "Bijgevoegde bestanden",
2296
+ "remove-attachment": "{{name}} verwijderen",
2297
+ uploading: "{{name}} wordt geüpload",
2298
+ "uploading-hint": "Het bericht kan worden verstuurd zodra de bestanden zijn geüpload",
2299
+ "attachment-errors": {
2300
+ "assistant-file-type-not-accepted": "Dit type bestand kan niet worden bijgevoegd. Voeg {{types}} bij.",
2301
+ "assistant-file-too-large": "Het bestand is groter dan {{max}}.",
2302
+ "assistant-file-count-limit": "Een bericht kan hoogstens {{count}} bestanden bevatten.",
2303
+ "assistant-file-upload-invalid": "Het bestand is leeg of de naam is niet bruikbaar."
2104
2304
  }
2105
2305
  },
2106
2306
  queue: {
@@ -2118,7 +2318,8 @@ var nl = {
2118
2318
  preparing: "Klaarzetten…",
2119
2319
  sending: "Versturen…",
2120
2320
  failed: "Niet verstuurd; hervat om opnieuw te proberen"
2121
- }
2321
+ },
2322
+ files: "Bestanden ({{count}}): {{names}}"
2122
2323
  },
2123
2324
  "tool-call": {
2124
2325
  label: "Toolaanroep: {{name}}",
@@ -2254,7 +2455,8 @@ var nl = {
2254
2455
  "assistant-proposal-already-open": "Niet opnieuw voorgesteld: hetzelfde voorstel wacht al.",
2255
2456
  "assistant-proposal-recently-rejected": "Niet opnieuw voorgesteld: het is kort geleden afgewezen.",
2256
2457
  "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."
2458
+ "assistant-proposal-not-placed": "Niet voorgesteld: het object waar het om gaat accepteert het niet.",
2459
+ "assistant-proposal-not-needed": "Niet voorgesteld: het werk is al gedaan of is niet meer van toepassing."
2258
2460
  },
2259
2461
  errors: {
2260
2462
  generic: "Er ging iets mis. Probeer het opnieuw.",
@@ -2308,6 +2510,19 @@ var nl = {
2308
2510
  "assistant-message-invalid": "Het bericht is leeg of ongeldig. Schrijf een vraag en verstuur hem opnieuw.",
2309
2511
  "assistant-message-too-long": "Het bericht is te lang. Maak het korter en verstuur het opnieuw.",
2310
2512
  "assistant-model-unavailable": "Het taalmodel is tijdelijk niet beschikbaar. Probeer het zo opnieuw.",
2513
+ "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.",
2514
+ "assistant-files-unavailable": "Deze applicatie geeft de assistent geen bestanden.",
2515
+ "assistant-files-not-accepted": "Deze agent neemt geen bestanden aan. Een beheerder kan bestanden voor hem aanzetten.",
2516
+ "assistant-file-reference-invalid": "Een bestand kon niet worden gelezen omdat de verwijzing ongeldig is.",
2517
+ "assistant-file-not-found": "Het bestand is niet gevonden. Misschien is het verwijderd.",
2518
+ "assistant-file-forbidden": "Je mag dit bestand niet lezen, dus de assistent heeft het ook niet gelezen.",
2519
+ "assistant-file-unavailable": "Het bestand kon nu niet worden geopend. Probeer het zo opnieuw.",
2520
+ "assistant-file-reader-failed": "Het leesprogramma voor dit type bestand kon het niet omzetten in tekst.",
2521
+ "assistant-file-too-large": "Het bestand is groter dan de assistent mag lezen. Deel een kleiner bestand.",
2522
+ "assistant-file-too-many-pages": "Het bestand heeft meer pagina's dan de assistent mag lezen. Deel een korter document.",
2523
+ "assistant-file-count-limit": "De assistent mag in dit antwoord geen bestanden meer lezen. Vraag naar minder bestanden tegelijk.",
2524
+ "assistant-file-token-limit": "De bestanden zijn te lang voor één antwoord. Vraag naar minder of kortere bestanden tegelijk.",
2525
+ "assistant-file-type-not-supported": "Het model van deze agent kan bestanden van dit type niet lezen.",
2311
2526
  "assistant-proposal-hash-mismatch": "De voorgestelde wijziging is intussen veranderd, er is niets gewijzigd. Laad het gesprek opnieuw en besluit opnieuw.",
2312
2527
  "assistant-title-invalid": "De titel van het gesprek is ongeldig. Gebruik een kortere titel.",
2313
2528
  "assistant-tool-call-limit-reached": "De assistent heeft het maximale aantal stappen voor één antwoord bereikt. Stel een gerichtere vraag.",
@@ -2338,6 +2553,8 @@ var nl = {
2338
2553
  "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
2554
  "assistant-skill-stopped": "Deze vaardigheid van de agent is gestopt.",
2340
2555
  "assistant-skill-paused": "Deze vaardigheid van de agent is gepauzeerd; haar gebeurtenissen wachten tot ze hervat wordt.",
2556
+ "assistant-agent-off": "Deze agent staat uit; hij neemt geen werk van andere agents aan tot hij aangezet wordt.",
2557
+ "assistant-skill-off": "Deze vaardigheid van de agent staat uit.",
2341
2558
  "assistant-skill-not-found": "De agent heeft deze vaardigheid niet.",
2342
2559
  "assistant-skill-not-available": "Die vaardigheid is nu niet beschikbaar.",
2343
2560
  "assistant-skill-switch-limit": "De run is al zo vaak van vaardigheid gewisseld als mag.",
@@ -2396,6 +2613,7 @@ var nl = {
2396
2613
  "assistant-service-account-not-found": "Er bestaat geen account met deze id dat mag inloggen.",
2397
2614
  "assistant-skill-in-code": "Deze vaardigheid staat in de code en kan hier niet veranderen.",
2398
2615
  "assistant-trigger-in-code": "Deze trigger staat in de code en kan hier niet veranderen.",
2616
+ "assistant-trigger-filter-not-found": "De gebeurtenis heeft geen filter met deze naam. Kies uit de filters die de gebeurtenis biedt.",
2399
2617
  "assistant-skill-in-use": "Een trigger start deze vaardigheid nog. Pas de trigger eerst aan of verwijder hem.",
2400
2618
  "assistant-configuration-limit": "Deze agent heeft al zoveel vaardigheden of triggers als is toegestaan.",
2401
2619
  "assistant-run-settings-in-code": "Deze agent staat in de code; daar is ingesteld hoe hij werkt.",
@@ -2421,11 +2639,19 @@ var nl = {
2421
2639
  "assistant-mcp-authorization-cancelled": "Het autorisatievenster is gesloten voordat het klaar was.",
2422
2640
  "assistant-mcp-popup-blocked": "De browser heeft het autorisatievenster geblokkeerd. Sta pop-ups toe voor deze site en probeer het opnieuw.",
2423
2641
  "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."
2642
+ "assistant-proposal-not-placed": "Het voorstel is niet bij het object geplaatst waar het om gaat.",
2643
+ "assistant-proposal-not-needed": "Niet voorgesteld: het werk is al gedaan of is niet meer van toepassing.",
2644
+ "assistant-file-upload-invalid": "Het bestand kon niet worden geüpload. Kies één bestand en probeer het opnieuw.",
2645
+ "assistant-file-type-not-accepted": "Dit type bestand kan niet worden bijgevoegd.",
2646
+ "assistant-file-upload-refused": "De applicatie heeft dit bestand geweigerd.",
2647
+ "assistant-file-upload-failed": "Het bestand kon nu niet worden opgeslagen. Probeer het zo opnieuw.",
2648
+ "assistant-file-scan-refused": "Het bestand is niet door de scan op schadelijke inhoud gekomen en is niet opgeslagen."
2425
2649
  },
2426
2650
  notices: {
2427
2651
  "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."
2652
+ "conversation-removed": "Dit gesprek is niet meer voor je beschikbaar.",
2653
+ "message-queued": "De agent werkt in dit gesprek. Je bericht wacht op zijn volgende run, die het hier toevoegt.",
2654
+ "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
2655
  },
2430
2656
  preferences: {
2431
2657
  title: "Je voorkeuren",
@@ -2571,22 +2797,31 @@ var nl = {
2571
2797
  stopped: "Gestopt",
2572
2798
  "configured-off": "Uit in configuratie",
2573
2799
  "configured-off-hint": "Uitgeschakeld in de code van de applicatie tot de volgende deployment; hier kun je de agent niet hervatten.",
2800
+ off: "Uit",
2801
+ "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.",
2802
+ "starts-off": "De agent begon uit, zoals zijn code zegt.",
2574
2803
  changed: "Gewijzigd door {{user}} op {{date}}.",
2575
2804
  reason: "Notitie: {{reason}}",
2576
2805
  waiting: "{{count}} gebeurtenissen wachten op het hervatten.",
2577
2806
  action: {
2578
2807
  active: "Hervatten",
2579
2808
  paused: "Pauzeren",
2809
+ off: "Uitzetten",
2810
+ "switch-on": "Aanzetten",
2580
2811
  stopped: "Stoppen"
2581
2812
  },
2582
2813
  explain: {
2583
2814
  active: "De agent werkt weer. Elk onderwerp met wachtende gebeurtenissen krijgt één run.",
2584
2815
  paused: "Er starten geen nieuwe runs. Gebeurtenissen wachten en lopen na het hervatten; lopend werk wordt afgemaakt en goedgekeurde voorstellen worden uitgevoerd.",
2816
+ 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.",
2817
+ "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
2818
  stopped: "Actieve runs worden geannuleerd, hun open voorstellen ingetrokken en wachtende gebeurtenissen vervallen. Goedgekeurde voorstellen wachten tot de agent weer werkt."
2586
2819
  },
2587
2820
  confirm: {
2588
2821
  active: "Hervatten bevestigen",
2589
2822
  paused: "Pauze bevestigen",
2823
+ off: "Uitzetten bevestigen",
2824
+ "switch-on": "Aanzetten bevestigen",
2590
2825
  stopped: "Stop bevestigen"
2591
2826
  },
2592
2827
  "note-label": "Notitie voor andere beheerders (optioneel)",
@@ -2599,6 +2834,8 @@ var nl = {
2599
2834
  explain: {
2600
2835
  active: "De vaardigheid {{skill}} werkt weer. Elk onderwerp met gebeurtenissen die erop wachten krijgt één run.",
2601
2836
  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.",
2837
+ 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.",
2838
+ "switch-on": "De vaardigheid {{skill}} neemt weer werk aan. Wat er gebeurde terwijl ze uit stond, loopt niet alsnog.",
2602
2839
  stopped: "De runs van de vaardigheid {{skill}} worden geannuleerd en haar wachtende gebeurtenissen vervallen; haar triggers worden geweigerd. De andere vaardigheden werken door."
2603
2840
  }
2604
2841
  },
@@ -2675,7 +2912,10 @@ var nl = {
2675
2912
  "view-policy": "Resultaten voor de hele applicatie zichtbaar voor: {{policy}}."
2676
2913
  },
2677
2914
  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."
2915
+ "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.",
2916
+ warnings: {
2917
+ "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."
2918
+ }
2679
2919
  },
2680
2920
  "agent-editor": {
2681
2921
  "new-title": "Nieuwe agent",
@@ -2690,7 +2930,14 @@ var nl = {
2690
2930
  "autonomy-hint": "Alleen Uitvoeren staat wijzigingen toe, en elke wijziging vraagt nog steeds om goedkeuring.",
2691
2931
  "required-policy": "Vereiste policy",
2692
2932
  "required-policy-hint": "Optioneel. Alleen gebruikers die aan deze policy voldoen zien de agent.",
2933
+ profile: "Modelprofiel",
2934
+ "profile-default": "Het chatprofiel van de applicatie",
2935
+ "profile-hint": "Het model waarmee de agent werkt, uit de profielen die de applicatie aanbiedt voor agents die hier gemaakt worden.",
2936
+ classification: "Dataclassificatie",
2937
+ "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
2938
  "state-hint": "Pauzeer, stop en hervat de agent vanuit de lijst met agents. Opslaan verandert de stand nooit.",
2939
+ "switch-on-now": "Nu aanzetten",
2940
+ "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
2941
  "tool-selectors": "Toolselectors",
2695
2942
  "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
2943
  "add-selector": "Patroon toevoegen",
@@ -2905,7 +3152,33 @@ var nl = {
2905
3152
  "client-secret-reference": "Verwijzing naar het clientgeheim",
2906
3153
  "organization-account-label": "Gekoppeld account",
2907
3154
  "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."
3155
+ "organization-account-label-required": "Noem het account dat gekoppeld wordt.",
3156
+ reader: {
3157
+ title: "Documenten lezen",
3158
+ 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.",
3159
+ empty: "Deze server leest geen bestanden.",
3160
+ item: "Leesprogramma {{number}}",
3161
+ add: "Leesprogramma toevoegen",
3162
+ "add-hint": "Elk leesprogramma is een van de gesynchroniseerde tools van de server; een server heeft er hoogstens {{max}}.",
3163
+ limit: "Deze server heeft het grootste aantal leesprogramma's dat kan ({{max}}).",
3164
+ remove: "Verwijderen",
3165
+ "remove-label": "Leesprogramma {{number}} verwijderen",
3166
+ "media-types": "Mediatypes",
3167
+ "media-types-hint": "Gescheiden door komma's, hoogstens 16, zoals application/vnd.openxmlformats-officedocument.wordprocessingml.document.",
3168
+ "media-types-invalid": "Vul 1 tot 16 mediatypes in van de vorm type/subtype, gescheiden door komma's.",
3169
+ tool: "Leesprogramma",
3170
+ "tool-choose": "Kies een tool",
3171
+ "tool-hint": "Een tool die van deze server is gesynchroniseerd en een bestand omzet in tekst. Hij hoeft niet voor agents aan te staan.",
3172
+ "tool-duplicate": "Een ander leesprogramma van deze server gebruikt deze tool al.",
3173
+ "tool-sync": "Synchroniseer eerst de tools van de server en voeg dan een leesprogramma toe.",
3174
+ priority: "Prioriteit",
3175
+ "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.",
3176
+ "priority-invalid": "Vul een geheel getal in van {{min}} tot {{max}}.",
3177
+ "max-classification": "Hoogste classificatie",
3178
+ "max-classification-choose": "Kies een classificatie",
3179
+ "max-classification-hint": "Een gevoeliger bestand wordt nooit naar deze server gestuurd.",
3180
+ "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."
3181
+ }
2909
3182
  },
2910
3183
  tools: {
2911
3184
  title: "Tools van {{name}}",
@@ -3053,6 +3326,19 @@ var nl = {
3053
3326
  "replay-progress": "{{replayed}} van hoogstens {{max}} runs herhaald, {{skipped}} overgeslagen"
3054
3327
  },
3055
3328
  "run-config": {
3329
+ files: {
3330
+ label: "Neemt bestanden aan",
3331
+ "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.",
3332
+ "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.",
3333
+ "limits-title": "Lagere bestandslimieten",
3334
+ "agent-limits-hint": "Laat een veld leeg om de limiet van de applicatie te houden. Een waarde kan die alleen verlagen.",
3335
+ "skill-limits-hint": "Laat een veld leeg om de limiet van de agent te houden. Een waarde kan die van de applicatie alleen verlagen.",
3336
+ "max-file-megabytes": "Grootste bestand (MB)",
3337
+ "max-file-pages": "Meeste pagina's per bestand",
3338
+ "max-files-per-turn": "Meeste bestanden per beurt",
3339
+ "max-file-tokens-per-turn": "Meeste bestandstokens per beurt",
3340
+ "application-limit": "De applicatie staat hooguit {{value}} toe."
3341
+ },
3056
3342
  back: "Terug naar agents",
3057
3343
  title: "Runconfiguratie van {{name}}",
3058
3344
  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.",
@@ -3072,6 +3358,7 @@ var nl = {
3072
3358
  "unknown-policy": "het goedkeuringsbeleid bestaat niet meer.",
3073
3359
  "unknown-rule-handler": "de regels bestaan niet meer.",
3074
3360
  "unknown-event": "de gebeurtenis bestaat niet meer.",
3361
+ "unknown-filter": "een filter van de gebeurtenis bestaat niet meer, dus de trigger start niets totdat je het verwijdert.",
3075
3362
  "unknown-subject-set": "de set onderwerpen bestaat niet meer.",
3076
3363
  "unknown-skill": "de vaardigheid bestaat niet meer.",
3077
3364
  "invalid-schedule": "het schema of de tijdzone is niet geldig.",
@@ -3091,7 +3378,9 @@ var nl = {
3091
3378
  taken: "Deze id is al in gebruik.",
3092
3379
  "name-letters": "Gebruik minstens één letter in de naam.",
3093
3380
  "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."
3381
+ "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.",
3382
+ "megabytes-up-to": "Vul een grootte in boven 0 en hooguit {{max}} MB.",
3383
+ "megabytes-positive": "Vul een grootte in boven 0 MB."
3095
3384
  },
3096
3385
  settings: {
3097
3386
  title: "Runinstellingen",
@@ -3192,6 +3481,8 @@ var nl = {
3192
3481
  "apply-shadow": "Eerst in schaduw beoordelen naast de live versie",
3193
3482
  "apply-live": "Direct live zetten",
3194
3483
  "starts-in-shadow": "Een nieuwe vaardigheid start in schaduw.",
3484
+ "switch-on-now": "Nu aanzetten",
3485
+ "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
3486
  versions: "Versies",
3196
3487
  "remove-explain": "Haar versies blijven bewaard. Een trigger die haar start moet eerst weg.",
3197
3488
  what: {
@@ -3203,6 +3494,22 @@ var nl = {
3203
3494
  "rules-hint": "De regels beslissen zonder model; een twijfelgeval gaat naar het model met de instructies hieronder.",
3204
3495
  "model-hint": "Het model beslist met de instructies hieronder en de gekozen tools.",
3205
3496
  "profile-hint": "Het modelprofiel dat hij gebruikt; standaard dat van de agent.",
3497
+ classification: {
3498
+ label: "Dataclassificatie",
3499
+ agent: "De classificatie van de agent",
3500
+ hint: "Hoe gevoelig de gegevens zijn die de vaardigheid naar haar model stuurt. Haar modelprofiel moet dit toestaan; anders worden haar modelaanroepen geweigerd.",
3501
+ "public": "Openbaar",
3502
+ "public-hint": "Gegevens die iedereen mag zien.",
3503
+ internal: "Intern",
3504
+ "internal-hint": "Gegevens van de organisatie; de standaard.",
3505
+ confidential: "Vertrouwelijk",
3506
+ "confidential-hint": "Gegevens die alleen sommige mensen mogen zien, zoals persoonlijke of financiële gegevens.",
3507
+ restricted: "Strikt vertrouwelijk",
3508
+ "restricted-hint": "De gevoeligste gegevens, waarvoor alleen speciaal goedgekeurde modellen gebruikt mogen worden."
3509
+ },
3510
+ warnings: {
3511
+ "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."
3512
+ },
3206
3513
  "apply-shadow-hint": "De nieuwe versie draait naast de live versie; vergelijk ze en promoveer of verwerp hem daarna.",
3207
3514
  "apply-live-hint": "Voor een kleine correctie: de nieuwe versie vervangt de live versie meteen.",
3208
3515
  "tools-pattern": "Alle tools die passen: nu {{count}}.",
@@ -3284,6 +3591,13 @@ var nl = {
3284
3591
  "source-schedule-hint": "Op vaste momenten, voor elk onderwerp in een verzameling.",
3285
3592
  "source-schedule-application-hint": "Op vaste momenten, voor de hele applicatie of voor elk object in een verzameling.",
3286
3593
  "debounce-hint": "Wacht zo lang op meer gebeurtenissen over hetzelfde onderwerp en behandelt ze in één run.",
3594
+ filters: {
3595
+ title: "Filters",
3596
+ hint: "Sla gebeurtenissen over waarop deze trigger de agent niet moet starten, zoals wijzigingen die de agent zelf deed. Elk filter dat je kiest moet een gebeurtenis doorlaten.",
3597
+ empty: "Deze gebeurtenis biedt geen filters.",
3598
+ missing: "De gebeurtenis biedt dit filter niet meer, dus de trigger start niets. Haal het weg om op te slaan.",
3599
+ applied: "Filters: {{filters}}"
3600
+ },
3287
3601
  "subject-set-hint": "De projecten of klanten die het schema telkens behandelt.",
3288
3602
  "message-hint": "Wat de agent telkens als opdracht leest.",
3289
3603
  examples: "Voorbeelden",
@@ -3674,7 +3988,18 @@ var nl = {
3674
3988
  previous: "Vorige",
3675
3989
  next: "Volgende",
3676
3990
  page: "Pagina {{page}} van {{pages}}",
3677
- pages: "Pagina's"
3991
+ pages: "Pagina's",
3992
+ "new": "Nieuw",
3993
+ "new-label": "Nieuw sinds je laatste bezoek",
3994
+ "last-visit": "Je keek hier voor het laatst op {{date}}. Wat sindsdien kwam, staat als nieuw gemarkeerd.",
3995
+ "first-visit": "Alles hier is nieuw voor je.",
3996
+ "group-loaded": "{{loaded}} van {{total}} getoond",
3997
+ "select-group": "De {{count}} getoonde selecteren",
3998
+ "select-group-label": "De {{count}} voorstellen van {{name}} op deze pagina selecteren",
3999
+ "group-continued": "Vervolg van de vorige pagina.",
4000
+ "group-continues": "Meer voorstellen van deze groep staan op de volgende pagina.",
4001
+ run: "Eén run van {{agent}}, {{date}}",
4002
+ "no-run": "Voorgesteld in een gesprek"
3678
4003
  },
3679
4004
  proposal: {
3680
4005
  approve: "Goedkeuren",
@@ -4080,6 +4405,11 @@ class NhAssistantSseParser {
4080
4405
  }
4081
4406
  }
4082
4407
 
4408
+ /**
4409
+ * The request header every upload carries. A browser cannot send it with a cross-site form, so the
4410
+ * upload endpoint keeps the posture of the JSON endpoints.
4411
+ */
4412
+ const NH_ASSISTANT_UPLOAD_HEADER = 'X-NH-Assistant-Upload';
4083
4413
  /** A failed JSON request of the assistant API. Carries a stable code, never response text. */
4084
4414
  class NhAssistantApiError extends Error {
4085
4415
  constructor(status, code, messageKey,
@@ -4126,6 +4456,8 @@ function nhAssistantCodeForStatus(status) {
4126
4456
  }
4127
4457
  const safeCode = /^[a-z0-9][a-z0-9.-]{0,79}$/;
4128
4458
  const safeField = /^[a-zA-Z][a-zA-Z0-9.]{0,63}$/;
4459
+ /** A field of one item of a list, such as `readers[0].toolName`. */
4460
+ const safeItemField = /^[a-zA-Z][a-zA-Z0-9]{0,31}\[\d{1,4}\](\.[a-zA-Z][a-zA-Z0-9]{0,31})?$/;
4129
4461
  /**
4130
4462
  * Whether a value the server or a callback page sent is a code the library may show: lowercase
4131
4463
  * letters, digits, dots and dashes, at most 80 characters. Every reader of untrusted codes uses this
@@ -4142,6 +4474,7 @@ class NhAssistantTransport {
4142
4474
  constructor() {
4143
4475
  this.config = inject(NH_ASSISTANT_CONFIG);
4144
4476
  this.fetchFn = inject(NH_ASSISTANT_FETCH);
4477
+ this.uploadFn = inject(NH_ASSISTANT_UPLOAD);
4145
4478
  this.translate = inject(TranslateService, { optional: true });
4146
4479
  this.injector = inject(EnvironmentInjector);
4147
4480
  }
@@ -4157,6 +4490,36 @@ class NhAssistantTransport {
4157
4490
  const init = await this.createInit(method, accept, body, signal);
4158
4491
  return this.fetchFn(this.url(path), init);
4159
4492
  }
4493
+ /**
4494
+ * Uploads `form` with progress and resolves with the parsed JSON answer. Rejects with
4495
+ * `NhAssistantApiError`, never with raw text: the code of a refused upload's error body, the
4496
+ * generic status mapping without one and `assistant-network` when it did not reach the server.
4497
+ * An aborted upload rejects with an `AbortError`.
4498
+ */
4499
+ async upload(path, form, onProgress, signal) {
4500
+ const headers = await this.headers('application/json');
4501
+ headers[NH_ASSISTANT_UPLOAD_HEADER] = '1';
4502
+ let response;
4503
+ try {
4504
+ response = await this.uploadFn({ url: this.url(path), headers, body: form, signal, onProgress });
4505
+ }
4506
+ catch (error) {
4507
+ if (signal.aborted) {
4508
+ throw error;
4509
+ }
4510
+ throw createApiError(0, nhAssistantCodeForStatus(0));
4511
+ }
4512
+ if (!response.ok) {
4513
+ const failure = await this.failure(response, {});
4514
+ throw createApiError(response.status, failure.code, failure.fieldErrors);
4515
+ }
4516
+ try {
4517
+ return JSON.parse(await response.text());
4518
+ }
4519
+ catch {
4520
+ throw createApiError(response.status, NhAssistantClientErrorCodes.invalidResponse);
4521
+ }
4522
+ }
4160
4523
  /**
4161
4524
  * Downloads a file of the assistant API. Rejects with `NhAssistantApiError`, never with raw text:
4162
4525
  * the code and field errors of a refused request's error body (whatever its content type), the
@@ -4203,7 +4566,7 @@ class NhAssistantTransport {
4203
4566
  if (errors && typeof errors === 'object' && !Array.isArray(errors)) {
4204
4567
  for (const [field, values] of Object.entries(errors)) {
4205
4568
  const safe = Array.isArray(values) ? values.filter(nhAssistantIsSafeCode) : [];
4206
- if (safeField.test(field) && safe.length > 0) {
4569
+ if ((safeField.test(field) || safeItemField.test(field)) && safe.length > 0) {
4207
4570
  fieldErrors[field] = safe;
4208
4571
  }
4209
4572
  }
@@ -4246,16 +4609,7 @@ class NhAssistantTransport {
4246
4609
  }
4247
4610
  }
4248
4611
  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
- }
4612
+ const headers = await this.headers(accept);
4259
4613
  if (body !== undefined) {
4260
4614
  headers['Content-Type'] = 'application/json';
4261
4615
  }
@@ -4267,6 +4621,20 @@ class NhAssistantTransport {
4267
4621
  cache: 'no-store'
4268
4622
  };
4269
4623
  }
4624
+ /** The host's extra headers, `Accept`, the bearer token and the current language. */
4625
+ async headers(accept) {
4626
+ const headers = await nhAssistantRequestHeaders(this.config, this.injector);
4627
+ headers['Accept'] = accept;
4628
+ const token = await runInInjectionContext(this.injector, () => this.config.getAccessToken());
4629
+ if (token) {
4630
+ headers['Authorization'] = `Bearer ${token}`;
4631
+ }
4632
+ const language = this.translate?.getCurrentLang();
4633
+ if (language) {
4634
+ headers['Accept-Language'] = language;
4635
+ }
4636
+ return headers;
4637
+ }
4270
4638
  url(path) {
4271
4639
  const base = this.config.apiBaseUrl.replace(/\/+$/, '');
4272
4640
  return `${base}/${path}`;
@@ -4300,6 +4668,8 @@ async function nhAssistantRequestHeaders(config, injector) {
4300
4668
  }
4301
4669
 
4302
4670
  const knownEventTypes = new Set(NH_ASSISTANT_SSE_EVENT_TYPES);
4671
+ /** The outcomes of a trigger receipt with which the next run takes a waiting message. */
4672
+ const queuedOutcomes = new Set(['started', 'queued', 'coalesced', 'superseded', 'waiting', 'deferred']);
4303
4673
  /**
4304
4674
  * Client for the assistant HTTP API. JSON endpoints return cold observables that abort
4305
4675
  * their request on unsubscribe. Streaming endpoints emit every contract event and complete
@@ -4330,11 +4700,33 @@ class NhAssistantApiService {
4330
4700
  return this.transport.json('DELETE', `conversations/${encodeURIComponent(conversationId)}`);
4331
4701
  }
4332
4702
  sendMessage(conversationId, request) {
4333
- return this.requestStream(`conversations/${encodeURIComponent(conversationId)}/messages`, request);
4703
+ return this.requestStream(`conversations/${encodeURIComponent(conversationId)}/messages`, request, true);
4704
+ }
4705
+ /**
4706
+ * Uploads one file to a conversation as `multipart/form-data` (`POST conversations/{id}/files`):
4707
+ * emits the progress while the file is sent and then the stored file, whose reference a message
4708
+ * carries in `files`. Fails with `NhAssistantApiError`; unsubscribing aborts the upload. The
4709
+ * media type of `file` is the one the upload declares.
4710
+ */
4711
+ uploadFile(conversationId, file, name) {
4712
+ return new Observable(subscriber => {
4713
+ const abort = new AbortController();
4714
+ const form = new FormData();
4715
+ form.append('file', file, name);
4716
+ this.transport.upload(`conversations/${encodeURIComponent(conversationId)}/files`, form, (loaded, total) => subscriber.next({ type: 'progress', loaded, total }), abort.signal).then(reference => {
4717
+ subscriber.next({ type: 'uploaded', file: reference });
4718
+ subscriber.complete();
4719
+ }, error => {
4720
+ if (!abort.signal.aborted) {
4721
+ subscriber.error(error);
4722
+ }
4723
+ });
4724
+ return () => abort.abort();
4725
+ });
4334
4726
  }
4335
4727
  decideApproval(conversationId, approvalId, request) {
4336
4728
  const path = `conversations/${encodeURIComponent(conversationId)}/approvals/${encodeURIComponent(approvalId)}/decide`;
4337
- return this.requestStream(path, request);
4729
+ return this.requestStream(path, request, false);
4338
4730
  }
4339
4731
  cancel(conversationId) {
4340
4732
  return this.transport.json('POST', `conversations/${encodeURIComponent(conversationId)}/cancel`);
@@ -4416,14 +4808,14 @@ class NhAssistantApiService {
4416
4808
  completeMcpAuthorization(request) {
4417
4809
  return this.transport.json('POST', 'mcp-connections/complete', request);
4418
4810
  }
4419
- requestStream(path, body) {
4811
+ requestStream(path, body, queuedReceipt) {
4420
4812
  return new Observable(subscriber => {
4421
4813
  const abort = new AbortController();
4422
- void this.executeStream(path, body, abort.signal, subscriber);
4814
+ void this.executeStream(path, body, queuedReceipt, abort.signal, subscriber);
4423
4815
  return () => abort.abort();
4424
4816
  });
4425
4817
  }
4426
- async executeStream(path, body, signal, subscriber) {
4818
+ async executeStream(path, body, queuedReceipt, signal, subscriber) {
4427
4819
  let response;
4428
4820
  try {
4429
4821
  response = await this.transport.fetch('POST', path, 'text/event-stream', body, signal);
@@ -4436,6 +4828,10 @@ class NhAssistantApiService {
4436
4828
  this.emitClientError(subscriber, signal, await this.transport.failureCode(response));
4437
4829
  return;
4438
4830
  }
4831
+ if (queuedReceipt && response.status === 202) {
4832
+ await this.readQueuedReceipt(response, signal, subscriber);
4833
+ return;
4834
+ }
4439
4835
  const contentType = response.headers.get('content-type') ?? '';
4440
4836
  if (!response.body || !contentType.toLowerCase().startsWith('text/event-stream')) {
4441
4837
  this.emitClientError(subscriber, signal, NhAssistantClientErrorCodes.invalidResponse);
@@ -4443,6 +4839,33 @@ class NhAssistantApiService {
4443
4839
  }
4444
4840
  await this.readStream(response.body, signal, subscriber);
4445
4841
  }
4842
+ /**
4843
+ * A message to an agent's thread whose run works waits for the next run: the server answers `202`
4844
+ * with the trigger receipt instead of a stream. A receipt the run takes the message with becomes
4845
+ * `message.queued`; one that refused it becomes an `error` with its code.
4846
+ */
4847
+ async readQueuedReceipt(response, signal, subscriber) {
4848
+ let receipt;
4849
+ try {
4850
+ receipt = JSON.parse(await response.text());
4851
+ }
4852
+ catch {
4853
+ receipt = null;
4854
+ }
4855
+ if (signal.aborted || subscriber.closed) {
4856
+ return;
4857
+ }
4858
+ if (!receipt || typeof receipt.outcome !== 'string') {
4859
+ this.emitClientError(subscriber, signal, NhAssistantClientErrorCodes.invalidResponse);
4860
+ return;
4861
+ }
4862
+ if (!queuedOutcomes.has(receipt.outcome)) {
4863
+ this.emitClientError(subscriber, signal, nhAssistantIsSafeCode(receipt.reasonCode) ? receipt.reasonCode : NhAssistantClientErrorCodes.conversationBusy);
4864
+ return;
4865
+ }
4866
+ subscriber.next({ type: 'message.queued', data: receipt });
4867
+ subscriber.complete();
4868
+ }
4446
4869
  async readStream(body, signal, subscriber) {
4447
4870
  const reader = body.getReader();
4448
4871
  const decoder = new TextDecoder();
@@ -5016,7 +5439,10 @@ class NhAssistantAgentRunApiService {
5016
5439
  retryRun(runId, mode) {
5017
5440
  return this.transport.json('POST', `runs/${encodeURIComponent(runId)}/retry`, { mode });
5018
5441
  }
5019
- /** Pending proposals the caller may decide, sorted by expiry. */
5442
+ /**
5443
+ * Pending proposals the caller may decide, sorted by expiry, or with `grouped` by parent and subject,
5444
+ * with what is new since the caller's last visit (`visitInbox`).
5445
+ */
5020
5446
  getInbox(filter = {}) {
5021
5447
  const query = new URLSearchParams();
5022
5448
  for (const [key, value] of Object.entries(filter)) {
@@ -5027,6 +5453,15 @@ class NhAssistantAgentRunApiService {
5027
5453
  const suffix = query.toString();
5028
5454
  return this.transport.json('GET', suffix ? `approvals/inbox?${suffix}` : 'approvals/inbox');
5029
5455
  }
5456
+ /**
5457
+ * Records that the caller looked at the inbox now, so it marks the proposals made after this visit
5458
+ * as new. `divisionId` records the visit of the inbox of one division the caller may work in;
5459
+ * otherwise of the whole inbox. Fails with `assistant-subject-not-found` for a division the caller
5460
+ * may not work in.
5461
+ */
5462
+ visitInbox(divisionId) {
5463
+ return this.transport.json('POST', withQuery('approvals/inbox/visit', { divisionId }));
5464
+ }
5030
5465
  /**
5031
5466
  * Decides one proposal, bound to the proposal hash the person saw; the server checks the approval
5032
5467
  * policy and the tool's approve policy again. Accepts an inbox proposal or the approval part of an
@@ -5860,6 +6295,89 @@ function sameKey(current, expected) {
5860
6295
  return bytes.length === expected.length && bytes.every((byte, index) => byte === expected[index]);
5861
6296
  }
5862
6297
 
6298
+ /** The longest file name a file reference takes. */
6299
+ const NH_ASSISTANT_MAX_FILE_NAME_LENGTH = 256;
6300
+ const unknownMediaType = 'application/octet-stream';
6301
+ /** The media types the platform names, with the extensions a file picker offers and a short label. */
6302
+ const knownTypes = {
6303
+ 'application/pdf': { extensions: ['.pdf'], label: 'PDF' },
6304
+ 'image/png': { extensions: ['.png'], label: 'PNG' },
6305
+ 'image/jpeg': { extensions: ['.jpg', '.jpeg'], label: 'JPEG' },
6306
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': { extensions: ['.docx'], label: 'Word' },
6307
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': { extensions: ['.xlsx'], label: 'Excel' }
6308
+ };
6309
+ /**
6310
+ * The media type of a chosen file, lowercase and without parameters. A browser that does not know
6311
+ * the type, as happens for Office documents, gives none; the extension of a known type then names
6312
+ * it. Otherwise `application/octet-stream`.
6313
+ */
6314
+ function nhAssistantMediaTypeOf(file) {
6315
+ const declared = file.type.split(';')[0].trim().toLowerCase();
6316
+ if (declared.length > 0 && declared !== unknownMediaType) {
6317
+ return declared;
6318
+ }
6319
+ const name = file.name.toLowerCase();
6320
+ for (const [mediaType, known] of Object.entries(knownTypes)) {
6321
+ if (known.extensions.some(extension => name.endsWith(extension))) {
6322
+ return mediaType;
6323
+ }
6324
+ }
6325
+ return unknownMediaType;
6326
+ }
6327
+ /**
6328
+ * The failure code of a file before it is uploaded, or `null` when it may be: the most files of one
6329
+ * message, an accepted media type, content and a usable name, and the largest size. `attached` counts
6330
+ * the files the message already carries.
6331
+ */
6332
+ function nhAssistantFileProblem(file, mediaType, uploads, attached) {
6333
+ if (attached >= uploads.maxFiles) {
6334
+ return 'assistant-file-count-limit';
6335
+ }
6336
+ if (!uploads.acceptedMediaTypes.includes(mediaType)) {
6337
+ return 'assistant-file-type-not-accepted';
6338
+ }
6339
+ if (file.size === 0 || file.name.trim().length === 0 || file.name.length > NH_ASSISTANT_MAX_FILE_NAME_LENGTH) {
6340
+ return 'assistant-file-upload-invalid';
6341
+ }
6342
+ if (file.size > uploads.maxFileBytes) {
6343
+ return 'assistant-file-too-large';
6344
+ }
6345
+ return null;
6346
+ }
6347
+ /** The `accept` attribute of a file input for these media types: each type and its known extensions. */
6348
+ function nhAssistantAcceptAttribute(mediaTypes) {
6349
+ return mediaTypes
6350
+ .flatMap(mediaType => [mediaType, ...(knownTypes[mediaType]?.extensions ?? [])])
6351
+ .join(',');
6352
+ }
6353
+ /** A short name of a media type, such as `PDF` or `Word`; otherwise its subtype in capitals. */
6354
+ function nhAssistantFileTypeLabel(mediaType) {
6355
+ const known = knownTypes[mediaType];
6356
+ if (known) {
6357
+ return known.label;
6358
+ }
6359
+ const subtype = mediaType.split('/')[1] ?? mediaType;
6360
+ return subtype.split(/[.+]/).pop().toUpperCase();
6361
+ }
6362
+ /** A size in bytes as people read it, such as `820 KB` or `1.4 MB`, in the given language. */
6363
+ function nhAssistantFormatFileSize(bytes, language) {
6364
+ const units = ['B', 'KB', 'MB', 'GB'];
6365
+ let value = Math.max(0, bytes);
6366
+ let unit = 0;
6367
+ while (value >= 1024 && unit < units.length - 1) {
6368
+ value /= 1024;
6369
+ unit++;
6370
+ }
6371
+ let format;
6372
+ try {
6373
+ format = new Intl.NumberFormat(language || undefined, { maximumFractionDigits: unit === 0 || value >= 10 ? 0 : 1 });
6374
+ }
6375
+ catch {
6376
+ format = new Intl.NumberFormat(undefined, { maximumFractionDigits: unit === 0 || value >= 10 ? 0 : 1 });
6377
+ }
6378
+ return `${format.format(value)} ${units[unit]}`;
6379
+ }
6380
+
5863
6381
  /**
5864
6382
  * Applies one assistant event to a conversation and returns the new conversation.
5865
6383
  * The input is never mutated, so the result can be stored in a signal directly.
@@ -6291,6 +6809,11 @@ function validId(value) {
6291
6809
 
6292
6810
  /** Notice code for a completed turn that produced no answer text and no server code. */
6293
6811
  const NH_ASSISTANT_NO_ANSWER_NOTICE_CODE = 'assistant-no-answer';
6812
+ /**
6813
+ * Notice code for a message that waits for the next run of the agent working in its thread: the
6814
+ * server answered `202` with the trigger receipt, and the run adds the message, with its files.
6815
+ */
6816
+ const NH_ASSISTANT_MESSAGE_QUEUED_NOTICE_CODE = 'assistant-message-queued';
6294
6817
  const fourEyesRequiredCode = 'assistant-four-eyes-required';
6295
6818
  /** True when a conversation has messages the caller has not read. A running turn is not unread yet. */
6296
6819
  function nhAssistantIsUnread(summary) {
@@ -6355,7 +6878,12 @@ class NhAssistantStore {
6355
6878
  this.submittingState = signal(false, ...(ngDevMode ? [{ debugName: "submittingState" }] : []));
6356
6879
  /** Agent proposals the current user rejected in this session and may give feedback on. */
6357
6880
  this.feedbackApprovalsState = signal(new Set(), ...(ngDevMode ? [{ debugName: "feedbackApprovalsState" }] : []));
6881
+ /** The attachments of the next message per conversation; `''` for a new conversation. */
6882
+ this.attachmentsState = signal(new Map(), ...(ngDevMode ? [{ debugName: "attachmentsState" }] : []));
6358
6883
  this.destroyed = false;
6884
+ this.uploadSubscriptions = new Map();
6885
+ /** The conversation an attachment of a new conversation creates, once for every file chosen meanwhile. */
6886
+ this.attachmentConversation = null;
6359
6887
  this.streams = new Map();
6360
6888
  this.cancelTimers = new Map();
6361
6889
  /** Pending reloads of agent threads after live agent-work updates, per conversation. */
@@ -6434,6 +6962,31 @@ class NhAssistantStore {
6434
6962
  && ['running', 'waiting-for-approval'].includes(conversation?.status ?? '')
6435
6963
  && (this.isOwner() || ownsTurn);
6436
6964
  }, ...(ngDevMode ? [{ debugName: "canSteer" }] : []));
6965
+ /**
6966
+ * What the person may attach to the next message: the uploads of the open conversation's agent, or
6967
+ * of the selected agent for a new conversation. `null` when nothing can be attached: the server
6968
+ * offers no uploads for the agent, the conversation is archived, or the person only reads the
6969
+ * agent's thread.
6970
+ */
6971
+ this.uploads = computed(() => {
6972
+ const conversation = this.activeConversation();
6973
+ const thread = conversation?.thread;
6974
+ if (!this.enabled() || conversation?.status === 'archived'
6975
+ || (thread && thread.role !== 'participant' && thread.role !== 'approver')) {
6976
+ return null;
6977
+ }
6978
+ const agentId = conversation?.agentId ?? this.selectedAgentIdState();
6979
+ const uploads = this.agents().find(agent => agent.id === agentId)?.uploads ?? null;
6980
+ return uploads && uploads.acceptedMediaTypes.length > 0 && uploads.maxFiles > 0 ? uploads : null;
6981
+ }, ...(ngDevMode ? [{ debugName: "uploads" }] : []));
6982
+ /** The attachments of the next message of the open conversation, in the order they were chosen. */
6983
+ this.attachments = computed(() => this.attachmentsState().get(this.activeIdState() ?? newConversationKey) ?? [], ...(ngDevMode ? [{ debugName: "attachments" }] : []));
6984
+ /** The stored files of the open conversation's next message, for `submit`. */
6985
+ this.attachedFiles = computed(() => this.attachments()
6986
+ .filter(attachment => attachment.state === 'uploaded' && attachment.file !== null)
6987
+ .map(attachment => attachment.file), ...(ngDevMode ? [{ debugName: "attachedFiles" }] : []));
6988
+ /** True while a file of the next message still uploads; the message waits for it. */
6989
+ this.uploading = computed(() => this.attachments().some(attachment => attachment.state === 'uploading'), ...(ngDevMode ? [{ debugName: "uploading" }] : []));
6437
6990
  /** The owner and participants of the open conversation; empty while it is not shared. */
6438
6991
  this.members = computed(() => this.activeConversation()?.members ?? [], ...(ngDevMode ? [{ debugName: "members" }] : []));
6439
6992
  /** True when the current user owns the open conversation (or it is new). */
@@ -6534,6 +7087,9 @@ class NhAssistantStore {
6534
7087
  this.liveSubscription.unsubscribe();
6535
7088
  this.stopAllTurns();
6536
7089
  this.clearAgentWorkTimers();
7090
+ for (const upload of this.uploadSubscriptions.values()) {
7091
+ upload.unsubscribe();
7092
+ }
6537
7093
  clearTimeout(this.refreshTimer);
6538
7094
  });
6539
7095
  effect(() => {
@@ -6722,26 +7278,28 @@ class NhAssistantStore {
6722
7278
  /**
6723
7279
  * Sends a user message. Creates a conversation with the selected agent when none is open,
6724
7280
  * shows the message optimistically and applies the streamed events. Other conversations keep
6725
- * their own turns. Resolves `false` when the message was not accepted.
7281
+ * their own turns. Resolves `false` when the message was not accepted. `files` are uploaded
7282
+ * attachments, such as `attachedFiles()`; once the message is accepted, the attachments of the
7283
+ * conversation leave the composer.
6726
7284
  */
6727
- async send(text) {
7285
+ async send(text, files = []) {
6728
7286
  // Browsers only ask for notification permission during a user action such as this one.
6729
7287
  void this.push.onUserGesture().catch(() => undefined);
6730
- return this.sendMessage(text);
7288
+ return this.sendMessage(text, undefined, files);
6731
7289
  }
6732
7290
  /**
6733
7291
  * Sends immediately when the conversation accepts a message, otherwise appends to the
6734
7292
  * conversation's FIFO queue. A shown error alone does not hold a message back while nothing is
6735
7293
  * 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.
7294
+ * for an explicit `resumeQueue` after Stop or a failure. A queued message keeps its `files`.
6737
7295
  */
6738
- async submit(text) {
7296
+ async submit(text, files = []) {
6739
7297
  const stopped = this.pausedQueuesState().has(this.activeIdState() ?? newConversationKey);
6740
7298
  if (this.canSend() && this.queuedMessages().length === 0 && !stopped && !this.submittingState()) {
6741
7299
  const revision = this.accountRevision;
6742
7300
  this.submittingState.set(true);
6743
7301
  try {
6744
- return await this.send(text);
7302
+ return await this.send(text, files);
6745
7303
  }
6746
7304
  finally {
6747
7305
  if (revision === this.accountRevision && !this.destroyed) {
@@ -6756,8 +7314,15 @@ class NhAssistantStore {
6756
7314
  }
6757
7315
  void this.push.onUserGesture().catch(() => undefined);
6758
7316
  const revision = this.accountRevision;
6759
- const item = { id: createClientMessageId(), conversationId, text: trimmed, state: 'preparing' };
7317
+ const item = {
7318
+ id: createClientMessageId(),
7319
+ conversationId,
7320
+ text: trimmed,
7321
+ state: 'preparing',
7322
+ ...(files.length > 0 ? { files: [...files] } : {})
7323
+ };
6760
7324
  this.queuedState.update(items => [...items, item]);
7325
+ this.releaseAttachments(conversationId, files);
6761
7326
  const clientContext = await this.contextForMessage();
6762
7327
  if (revision !== this.accountRevision || this.destroyed) {
6763
7328
  return false;
@@ -6820,7 +7385,7 @@ class NhAssistantStore {
6820
7385
  const revision = this.accountRevision;
6821
7386
  this.queueDispatchState.set(item.id);
6822
7387
  this.queuedState.update(items => items.map(current => current.id === item.id ? { ...current, state: 'sending' } : current));
6823
- const accepted = await this.sendMessage(item.text, item);
7388
+ const accepted = await this.sendMessage(item.text, item, item.files ?? []);
6824
7389
  if (revision !== this.accountRevision || this.destroyed) {
6825
7390
  return;
6826
7391
  }
@@ -6831,7 +7396,7 @@ class NhAssistantStore {
6831
7396
  }
6832
7397
  this.queueDispatchState.set(null);
6833
7398
  }
6834
- async sendMessage(text, queued) {
7399
+ async sendMessage(text, queued, files) {
6835
7400
  const trimmed = text.trim();
6836
7401
  const accountRevision = this.accountRevision;
6837
7402
  await this.initialize();
@@ -6871,6 +7436,10 @@ class NhAssistantStore {
6871
7436
  const conversationId = conversation.id;
6872
7437
  const clientMessageId = createClientMessageId();
6873
7438
  const previousStatus = conversation.status;
7439
+ const parts = [
7440
+ { type: 'text', text: trimmed },
7441
+ ...files.map(file => ({ type: 'file', name: file.name, mediaType: file.mediaType, size: file.size }))
7442
+ ];
6874
7443
  this.patchSession(conversationId, session => ({
6875
7444
  ...session,
6876
7445
  error: null,
@@ -6884,7 +7453,7 @@ class NhAssistantStore {
6884
7453
  id: clientMessageId,
6885
7454
  role: 'user',
6886
7455
  createdAt: new Date().toISOString(),
6887
- parts: [{ type: 'text', text: trimmed }],
7456
+ parts,
6888
7457
  authorActorId: session.conversation.currentActorId ?? null
6889
7458
  }
6890
7459
  ]
@@ -6895,12 +7464,24 @@ class NhAssistantStore {
6895
7464
  this.pendingSendResolves.set(conversationId, resolve);
6896
7465
  let started = false;
6897
7466
  let failedAfterStart = false;
7467
+ let waitsForRun = false;
6898
7468
  this.runStream(conversationId, this.api.sendMessage(conversationId, {
6899
7469
  text: trimmed,
6900
7470
  clientMessageId,
6901
- ...(clientContext === undefined ? {} : { clientContext })
7471
+ ...(clientContext === undefined ? {} : { clientContext }),
7472
+ ...(files.length > 0 ? { files: [...files] } : {})
6902
7473
  }), {
6903
7474
  next: event => {
7475
+ if (event.type === 'message.queued') {
7476
+ // An agent's run works in the thread: the message, with its files, waits for the next run,
7477
+ // which adds it to the thread. Until then the notice says so.
7478
+ waitsForRun = true;
7479
+ this.removeMessage(conversationId, clientMessageId, previousStatus);
7480
+ this.releaseAttachments(conversationId, files);
7481
+ this.patchSession(conversationId, session => ({ ...session, notice: messageQueuedNotice(files.length) }));
7482
+ resolve(true);
7483
+ return;
7484
+ }
6904
7485
  if (event.type === 'error' && !started) {
6905
7486
  this.removeMessage(conversationId, clientMessageId, previousStatus);
6906
7487
  if (!queued && this.activeIdState() === conversationId) {
@@ -6912,6 +7493,8 @@ class NhAssistantStore {
6912
7493
  }
6913
7494
  if (event.type === 'turn.started') {
6914
7495
  started = true;
7496
+ // The message carries the files now; they leave the composer.
7497
+ this.releaseAttachments(conversationId, files);
6915
7498
  resolve(true);
6916
7499
  }
6917
7500
  if (event.type === 'error') {
@@ -6921,8 +7504,9 @@ class NhAssistantStore {
6921
7504
  },
6922
7505
  complete: () => {
6923
7506
  this.pendingSendResolves.delete(conversationId);
6924
- resolve(started);
6925
- this.afterTurn(conversationId, failedAfterStart);
7507
+ resolve(started || waitsForRun);
7508
+ // A waiting message started no turn here: the thread's own state comes from the server.
7509
+ this.afterTurn(conversationId, failedAfterStart || waitsForRun);
6926
7510
  }
6927
7511
  });
6928
7512
  this.sendingState.set(null);
@@ -7210,6 +7794,159 @@ class NhAssistantStore {
7210
7794
  this.restoredDraftState.set(null);
7211
7795
  return draft;
7212
7796
  }
7797
+ /**
7798
+ * Attaches files to the next message of the open conversation and uploads them at once. Each
7799
+ * file is checked first against `uploads()`: the most files of one message, the accepted media
7800
+ * types and the largest size; a file that fails stays as a failed attachment with its code and
7801
+ * is never uploaded. A new conversation is created first, because an upload belongs to one.
7802
+ */
7803
+ async attach(files) {
7804
+ const uploads = this.uploads();
7805
+ if (!uploads || files.length === 0) {
7806
+ return;
7807
+ }
7808
+ const revision = this.accountRevision;
7809
+ const key = this.activeIdState() ?? newConversationKey;
7810
+ let attached = this.attachmentsOf(key).filter(attachment => attachment.state !== 'failed').length;
7811
+ const added = [];
7812
+ const pending = [];
7813
+ for (const file of files) {
7814
+ const mediaType = nhAssistantMediaTypeOf(file);
7815
+ const problem = nhAssistantFileProblem(file, mediaType, uploads, attached);
7816
+ const attachment = {
7817
+ id: createClientMessageId(),
7818
+ name: file.name,
7819
+ mediaType,
7820
+ size: file.size,
7821
+ state: problem ? 'failed' : 'uploading',
7822
+ progress: 0,
7823
+ file: null,
7824
+ error: problem ? clientError(problem) : null
7825
+ };
7826
+ added.push(attachment);
7827
+ if (!problem) {
7828
+ attached++;
7829
+ // The upload declares the media type the check accepted, also when the browser gave none.
7830
+ pending.push({ attachment, content: file.slice(0, file.size, mediaType) });
7831
+ }
7832
+ }
7833
+ this.patchAttachments(key, attachments => [...attachments, ...added]);
7834
+ if (pending.length === 0) {
7835
+ return;
7836
+ }
7837
+ let conversationId = this.activeIdState();
7838
+ if (conversationId === null) {
7839
+ this.attachmentConversation ??= this.createConversation().finally(() => {
7840
+ this.attachmentConversation = null;
7841
+ });
7842
+ const conversation = await this.attachmentConversation;
7843
+ if (revision !== this.accountRevision || this.destroyed) {
7844
+ return;
7845
+ }
7846
+ if (!conversation) {
7847
+ const error = this.draftErrorState() ?? clientError(NhAssistantClientErrorCodes.server);
7848
+ this.patchAttachments(key, attachments => attachments.map(attachment => pending.some(item => item.attachment.id === attachment.id)
7849
+ ? { ...attachment, state: 'failed', error }
7850
+ : attachment));
7851
+ return;
7852
+ }
7853
+ conversationId = conversation.id;
7854
+ this.moveAttachments(key, conversationId);
7855
+ }
7856
+ for (const { attachment, content } of pending) {
7857
+ this.startUpload(conversationId, attachment, content);
7858
+ }
7859
+ }
7860
+ /** Removes an attachment of the next message; an upload in progress stops. */
7861
+ removeAttachment(attachmentId) {
7862
+ this.uploadSubscriptions.get(attachmentId)?.unsubscribe();
7863
+ this.uploadSubscriptions.delete(attachmentId);
7864
+ this.attachmentsState.update(all => {
7865
+ const next = new Map();
7866
+ for (const [key, attachments] of all) {
7867
+ const kept = attachments.filter(attachment => attachment.id !== attachmentId);
7868
+ if (kept.length > 0) {
7869
+ next.set(key, kept);
7870
+ }
7871
+ }
7872
+ return next;
7873
+ });
7874
+ }
7875
+ startUpload(conversationId, attachment, content) {
7876
+ const revision = this.accountRevision;
7877
+ const update = (change) => {
7878
+ if (revision === this.accountRevision) {
7879
+ this.patchAttachments(conversationId, attachments => attachments.map(item => item.id === attachment.id ? { ...item, ...change } : item));
7880
+ }
7881
+ };
7882
+ const subscription = this.api.uploadFile(conversationId, content, attachment.name).subscribe({
7883
+ next: event => {
7884
+ if (event.type === 'progress') {
7885
+ update({ progress: event.total > 0 ? Math.min(1, event.loaded / event.total) : 0 });
7886
+ return;
7887
+ }
7888
+ update({ state: 'uploaded', progress: 1, file: event.file });
7889
+ },
7890
+ error: error => {
7891
+ this.uploadSubscriptions.delete(attachment.id);
7892
+ update({ state: 'failed', error: error instanceof NhAssistantApiError
7893
+ ? { code: error.code, messageKey: error.messageKey }
7894
+ : clientError(NhAssistantClientErrorCodes.server) });
7895
+ },
7896
+ complete: () => this.uploadSubscriptions.delete(attachment.id)
7897
+ });
7898
+ if (!subscription.closed) {
7899
+ this.uploadSubscriptions.set(attachment.id, subscription);
7900
+ }
7901
+ }
7902
+ attachmentsOf(key) {
7903
+ return this.attachmentsState().get(key) ?? [];
7904
+ }
7905
+ patchAttachments(key, update) {
7906
+ this.attachmentsState.update(all => {
7907
+ const next = new Map(all);
7908
+ const attachments = update(all.get(key) ?? []);
7909
+ if (attachments.length > 0) {
7910
+ next.set(key, attachments);
7911
+ }
7912
+ else {
7913
+ next.delete(key);
7914
+ }
7915
+ return next;
7916
+ });
7917
+ }
7918
+ /** Moves the attachments of a new conversation to the conversation that was created for them. */
7919
+ moveAttachments(from, to) {
7920
+ const moving = this.attachmentsOf(from);
7921
+ this.patchAttachments(from, () => []);
7922
+ this.patchAttachments(to, attachments => [...attachments, ...moving]);
7923
+ }
7924
+ /**
7925
+ * The attachments a message took leave the composer, and with them the failed ones; files that
7926
+ * still upload stay for the next message.
7927
+ */
7928
+ releaseAttachments(conversationId, files) {
7929
+ const sent = new Set(files.map(file => file.id));
7930
+ this.patchAttachments(conversationId, attachments => attachments.filter(attachment => attachment.state === 'uploading' || (attachment.state === 'uploaded' && !sent.has(attachment.file?.id ?? ''))));
7931
+ }
7932
+ /** Stops the uploads of a conversation, or of every conversation, and forgets their attachments. */
7933
+ dropAttachments(conversationId) {
7934
+ for (const [key, attachments] of this.attachmentsState()) {
7935
+ if (conversationId !== undefined && key !== conversationId) {
7936
+ continue;
7937
+ }
7938
+ for (const attachment of attachments) {
7939
+ this.uploadSubscriptions.get(attachment.id)?.unsubscribe();
7940
+ this.uploadSubscriptions.delete(attachment.id);
7941
+ }
7942
+ }
7943
+ if (conversationId === undefined) {
7944
+ this.attachmentsState.set(new Map());
7945
+ }
7946
+ else {
7947
+ this.patchAttachments(conversationId, () => []);
7948
+ }
7949
+ }
7213
7950
  async loadConversation(conversationId, restoring) {
7214
7951
  if (this.sessionsState().has(conversationId)) {
7215
7952
  const session = this.sessionsState().get(conversationId);
@@ -7353,6 +8090,8 @@ class NhAssistantStore {
7353
8090
  this.steeringState.set(null);
7354
8091
  this.submittingState.set(false);
7355
8092
  this.feedbackApprovalsState.set(new Set());
8093
+ this.dropAttachments();
8094
+ this.attachmentConversation = null;
7356
8095
  this.clearAgentWorkTimers();
7357
8096
  void this.live.stop();
7358
8097
  void this.push.deactivate().catch(() => undefined);
@@ -7667,6 +8406,7 @@ class NhAssistantStore {
7667
8406
  }
7668
8407
  /** Removes a deleted, left or inaccessible conversation everywhere. */
7669
8408
  forget(conversationId) {
8409
+ this.dropAttachments(conversationId);
7670
8410
  this.queuedState.update(items => items.filter(item => item.conversationId !== conversationId));
7671
8411
  this.pausedQueuesState.update(paused => new Set([...paused].filter(id => id !== conversationId)));
7672
8412
  this.streams.get(conversationId)?.unsubscribe();
@@ -7726,6 +8466,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImpo
7726
8466
  function clientError(code) {
7727
8467
  return { code, messageKey: nhAssistantErrorMessageKey(code) };
7728
8468
  }
8469
+ /**
8470
+ * The notice of a message that waits for the next run of the agent working in the thread, with its
8471
+ * files when it carries any; the run adds the message to the thread when it takes it.
8472
+ */
8473
+ function messageQueuedNotice(fileCount) {
8474
+ return {
8475
+ code: NH_ASSISTANT_MESSAGE_QUEUED_NOTICE_CODE,
8476
+ messageKey: fileCount > 0 ? 'nh-assistant.notices.message-queued-with-files' : 'nh-assistant.notices.message-queued'
8477
+ };
8478
+ }
7729
8479
  /** A completed turn with a server code, or without any answer text, explains itself with a notice. */
7730
8480
  function completionNotice(conversation, errorCode) {
7731
8481
  if (errorCode) {
@@ -8348,6 +9098,204 @@ function provideNhAssistant(config) {
8348
9098
  ]);
8349
9099
  }
8350
9100
 
9101
+ /**
9102
+ * Failure codes the assistant back-end sends in error bodies, SSE `error` events and tool
9103
+ * result codes (`NhAssistantErrorCodes`, `NhAssistantAdminErrorCodes` and the runtime codes
9104
+ * of `NewHeap.Platform.AI.Chat`). Every code needs a text under `nh-assistant.errors.<code>`
9105
+ * in each bundled language; the translation tests enforce that.
9106
+ */
9107
+ const NH_ASSISTANT_SERVER_ERROR_CODES = [
9108
+ // Chat endpoints and turns
9109
+ 'assistant-disabled',
9110
+ 'assistant-conversation-not-found',
9111
+ 'assistant-conversation-busy',
9112
+ 'assistant-agent-not-found',
9113
+ 'assistant-agent-forbidden',
9114
+ 'assistant-message-invalid',
9115
+ 'assistant-message-too-long',
9116
+ 'assistant-message-duplicate',
9117
+ 'assistant-title-invalid',
9118
+ 'assistant-approval-not-found',
9119
+ 'assistant-approval-not-pending',
9120
+ 'assistant-approval-decision-invalid',
9121
+ 'assistant-proposal-hash-mismatch',
9122
+ 'assistant-approval-expired',
9123
+ 'assistant-approval-invalid',
9124
+ 'assistant-approval-rejected',
9125
+ 'assistant-budget-exhausted',
9126
+ 'assistant-tool-call-limit-reached',
9127
+ 'assistant-tools-disabled',
9128
+ 'assistant-turn-timeout',
9129
+ 'assistant-model-unavailable',
9130
+ 'assistant-classification-not-permitted',
9131
+ 'assistant-files-unavailable',
9132
+ 'assistant-files-not-accepted',
9133
+ 'assistant-file-reference-invalid',
9134
+ 'assistant-file-not-found',
9135
+ 'assistant-file-forbidden',
9136
+ 'assistant-file-unavailable',
9137
+ 'assistant-file-reader-failed',
9138
+ 'assistant-file-too-large',
9139
+ 'assistant-file-too-many-pages',
9140
+ 'assistant-file-count-limit',
9141
+ 'assistant-file-token-limit',
9142
+ 'assistant-file-type-not-supported',
9143
+ 'assistant-file-upload-invalid',
9144
+ 'assistant-file-type-not-accepted',
9145
+ 'assistant-file-upload-refused',
9146
+ 'assistant-file-upload-failed',
9147
+ 'assistant-file-scan-refused',
9148
+ 'assistant-turn-failed',
9149
+ 'assistant-context-unavailable',
9150
+ 'assistant-actor-mismatch',
9151
+ // Sharing and notifications
9152
+ 'assistant-owner-required',
9153
+ 'assistant-approval-forbidden',
9154
+ 'assistant-turn-forbidden',
9155
+ 'assistant-share-link-invalid',
9156
+ 'assistant-participant-not-found',
9157
+ 'assistant-participant-limit-reached',
9158
+ 'assistant-directory-unavailable',
9159
+ 'assistant-push-unavailable',
9160
+ 'assistant-push-subscription-invalid',
9161
+ // Agent runs
9162
+ 'assistant-subject-not-found',
9163
+ 'assistant-subject-invalid',
9164
+ 'assistant-subject-type-unknown',
9165
+ 'assistant-subject-search-unavailable',
9166
+ 'assistant-agent-does-not-work-on',
9167
+ 'assistant-subject-access-denied',
9168
+ 'assistant-no-accountable-owner',
9169
+ 'assistant-agent-disabled',
9170
+ 'assistant-agent-stopped',
9171
+ 'assistant-agent-paused',
9172
+ 'assistant-agent-configured-off',
9173
+ 'assistant-run-token-limit',
9174
+ 'assistant-skill-stopped',
9175
+ 'assistant-skill-paused',
9176
+ 'assistant-agent-off',
9177
+ 'assistant-skill-off',
9178
+ 'assistant-skill-not-found',
9179
+ 'assistant-skill-not-available',
9180
+ 'assistant-skill-switch-limit',
9181
+ 'assistant-moved-to-shadow',
9182
+ 'assistant-shadow-variant',
9183
+ 'assistant-service-account-not-used',
9184
+ 'assistant-service-account-not-found',
9185
+ 'assistant-skill-in-code',
9186
+ 'assistant-trigger-in-code',
9187
+ 'assistant-trigger-filter-not-found',
9188
+ 'assistant-skill-in-use',
9189
+ 'assistant-configuration-limit',
9190
+ 'assistant-run-settings-in-code',
9191
+ 'assistant-run-settings-in-use',
9192
+ 'assistant-skill-candidate-not-found',
9193
+ 'assistant-skill-version-gone',
9194
+ 'assistant-account-search-unavailable',
9195
+ 'assistant-handoff-unavailable',
9196
+ 'assistant-handoff-not-accepted',
9197
+ 'assistant-handoff-subject-unavailable',
9198
+ 'assistant-handoff-shadow-to-live',
9199
+ 'assistant-handoff-chain-limit',
9200
+ 'assistant-handoff-limit-reached',
9201
+ 'assistant-replay-not-shadow',
9202
+ 'assistant-replay-active',
9203
+ 'assistant-evaluation-not-variant',
9204
+ 'assistant-agent-not-configured',
9205
+ 'assistant-run-not-found',
9206
+ 'assistant-run-not-retryable',
9207
+ 'assistant-run-filter-invalid',
9208
+ 'assistant-run-operation-cancelled',
9209
+ 'assistant-run-operation-stalled',
9210
+ 'assistant-run-operation-ended',
9211
+ 'assistant-board-not-found',
9212
+ 'assistant-board-scope-invalid',
9213
+ 'assistant-approval-policy-denied',
9214
+ 'assistant-four-eyes-required',
9215
+ 'assistant-execution-not-uncertain',
9216
+ 'assistant-proposal-stale',
9217
+ 'assistant-proposal-check-failed',
9218
+ 'assistant-proposal-already-open',
9219
+ 'assistant-proposal-recently-rejected',
9220
+ 'assistant-proposal-superseded',
9221
+ 'assistant-proposal-refused',
9222
+ 'assistant-proposal-placed',
9223
+ 'assistant-proposal-not-placed',
9224
+ 'assistant-proposal-not-needed',
9225
+ 'assistant-trigger-not-found',
9226
+ 'assistant-trigger-rate-limited',
9227
+ 'assistant-trigger-rolled-back',
9228
+ 'assistant-webhook-signature-invalid',
9229
+ 'assistant-webhook-payload-invalid',
9230
+ 'assistant-webhook-payload-too-large',
9231
+ 'assistant-memory-not-configured',
9232
+ 'assistant-memory-invalid',
9233
+ 'assistant-memory-conflict',
9234
+ 'assistant-artifact-invalid',
9235
+ 'assistant-artifact-outside-turn',
9236
+ 'assistant-artifact-not-found',
9237
+ // Feedback on proposals
9238
+ 'assistant-feedback-invalid',
9239
+ 'assistant-feedback-not-accepted',
9240
+ // Consultations of other agents
9241
+ 'assistant-consult-subject-unavailable',
9242
+ 'assistant-consult-agent-unavailable',
9243
+ 'assistant-consult-depth-exceeded',
9244
+ 'assistant-consult-budget-exhausted',
9245
+ 'assistant-consult-limit-reached',
9246
+ 'assistant-consult-timeout',
9247
+ 'assistant-consult-failed',
9248
+ 'assistant-consult-read-only',
9249
+ // Preferences and administration
9250
+ 'assistant-validation',
9251
+ 'assistant-instructions-too-long',
9252
+ 'assistant-forbidden',
9253
+ 'assistant-not-found',
9254
+ 'assistant-context-not-found',
9255
+ 'assistant-version-conflict',
9256
+ 'assistant-agent-exists',
9257
+ 'assistant-code-agent-not-deletable',
9258
+ 'assistant-agent-not-code',
9259
+ 'assistant-mcp-server-not-found',
9260
+ 'assistant-mcp-server-exists',
9261
+ 'assistant-mcp-tool-not-found',
9262
+ 'assistant-mcp-unreachable',
9263
+ 'assistant-mcp-unauthorized',
9264
+ 'assistant-mcp-host-blocked',
9265
+ 'assistant-mcp-schema-changed',
9266
+ 'assistant-mcp-tool-disabled',
9267
+ 'assistant-mcp-insufficient-scope',
9268
+ 'assistant-mcp-headless-not-allowed',
9269
+ 'assistant-mcp-credential-mismatch',
9270
+ 'assistant-mcp-consent-required',
9271
+ 'assistant-mcp-initiator-only',
9272
+ 'assistant-mcp-delegated-token-unavailable',
9273
+ 'assistant-mcp-secret-reference-invalid',
9274
+ 'assistant-mcp-certificate-reference-invalid',
9275
+ 'assistant-mcp-secret-unreadable',
9276
+ 'assistant-mcp-secret-unavailable',
9277
+ // MCP OAuth connections
9278
+ 'assistant-mcp-connection-required',
9279
+ 'assistant-mcp-reauthorization-required',
9280
+ 'assistant-mcp-scope-not-allowed',
9281
+ 'assistant-mcp-authorization-failed',
9282
+ 'assistant-mcp-authorization-expired',
9283
+ 'assistant-mcp-authorization-invalid',
9284
+ 'assistant-mcp-discovery-failed',
9285
+ 'assistant-mcp-pkce-unsupported',
9286
+ 'assistant-mcp-client-registration-required',
9287
+ 'assistant-mcp-token-request-failed',
9288
+ 'assistant-mcp-issuer-changed',
9289
+ 'assistant-mcp-authorization-not-confirmed',
9290
+ 'assistant-mcp-oauth-not-configured',
9291
+ 'assistant-mcp-too-many-authorizations',
9292
+ 'assistant-mcp-connection-not-supported'
9293
+ ];
9294
+ /** Every code the assistant UI can show: server codes and client-side transport codes. */
9295
+ const NH_ASSISTANT_ERROR_CODES = [
9296
+ ...new Set([...NH_ASSISTANT_SERVER_ERROR_CODES, ...Object.values(NhAssistantClientErrorCodes)])
9297
+ ];
9298
+
8351
9299
  /** Fragment the panel reads to join a shared conversation: `#nh-assistant-join=<id>.<token>`. */
8352
9300
  const NH_ASSISTANT_JOIN_FRAGMENT = 'nh-assistant-join';
8353
9301
  /** Fragment a clicked notification opens: `#nh-assistant-conversation=<id>`. */
@@ -8387,7 +9335,9 @@ const builtInPaths = {
8387
9335
  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
9336
  copy: 'M9 9h11v11H9zM5 15H4V4h11v1',
8389
9337
  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'
9338
+ leave: 'M10 4H5v16h5M14 8l4 4-4 4M18 12H9',
9339
+ 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',
9340
+ file: 'M6 3h8l4 4v14H6zM14 3v4h4'
8391
9341
  };
8392
9342
  /** Decorative icon. The surrounding control provides the accessible name. */
8393
9343
  class NhAssistantIconComponent {
@@ -8711,7 +9661,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImpo
8711
9661
  let nextId$a = 0;
8712
9662
  /**
8713
9663
  * 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.
9664
+ * Drafting remains available while the assistant runs or waits for approval. With `uploads`, an
9665
+ * attach button chooses files for the next message; the host uploads them and passes them back as
9666
+ * `attachments`, which show their progress, or why they cannot be attached, at the control. Sending
9667
+ * waits while a file uploads.
8715
9668
  */
8716
9669
  class NhAssistantComposerComponent {
8717
9670
  constructor() {
@@ -8732,15 +9685,30 @@ class NhAssistantComposerComponent {
8732
9685
  this.maxLength = input(null, ...(ngDevMode ? [{ debugName: "maxLength" }] : []));
8733
9686
  /** Text to put back into the input, for example a message the server did not accept. */
8734
9687
  this.restore = input(null, ...(ngDevMode ? [{ debugName: "restore" }] : []));
9688
+ /**
9689
+ * What the person may attach, for example `NhAssistantStore.uploads()`; `null` hides the attach
9690
+ * button.
9691
+ */
9692
+ this.uploads = input(null, ...(ngDevMode ? [{ debugName: "uploads" }] : []));
9693
+ /** The attachments of the next message, for example `NhAssistantStore.attachments()`. */
9694
+ this.attachments = input([], ...(ngDevMode ? [{ debugName: "attachments" }] : []));
8735
9695
  this.send = output();
8736
9696
  this.cancel = output();
8737
9697
  this.restored = output();
9698
+ /** Files the person chose to attach, for example for `NhAssistantStore.attach`. */
9699
+ this.attach = output();
9700
+ /** The id of an attachment the person removed, for example for `NhAssistantStore.removeAttachment`. */
9701
+ this.removeAttachment = output();
8738
9702
  this.host = inject(ElementRef);
9703
+ this.translate = inject(TranslateService, { optional: true });
8739
9704
  this.textarea = viewChild.required('textarea');
9705
+ this.attachButton = viewChild('attachButton', ...(ngDevMode ? [{ debugName: "attachButton" }] : []));
8740
9706
  this.ownedFocus = false;
8741
9707
  this.previousBusy = false;
8742
9708
  this.inputId = `nh-assistant-composer-${nextId$a++}`;
8743
9709
  this.hintId = `${this.inputId}-hint`;
9710
+ this.attachHintId = `${this.inputId}-attach-hint`;
9711
+ this.attachmentsId = `${this.inputId}-attachments`;
8744
9712
  this.text = signal('', ...(ngDevMode ? [{ debugName: "text" }] : []));
8745
9713
  this.length = computed(() => this.text().trim().length, ...(ngDevMode ? [{ debugName: "length" }] : []));
8746
9714
  this.tooLong = computed(() => {
@@ -8751,7 +9719,20 @@ class NhAssistantComposerComponent {
8751
9719
  const max = this.maxLength();
8752
9720
  return max !== null && this.length() >= max * 0.8;
8753
9721
  }, ...(ngDevMode ? [{ debugName: "showCounter" }] : []));
8754
- this.canSend = computed(() => !this.disabled() && !this.sendDisabled() && this.length() > 0 && !this.tooLong(), ...(ngDevMode ? [{ debugName: "canSend" }] : []));
9722
+ /** True while a file of the next message still uploads. */
9723
+ this.uploading = computed(() => this.attachments().some(attachment => attachment.state === 'uploading'), ...(ngDevMode ? [{ debugName: "uploading" }] : []));
9724
+ this.canSend = computed(() => !this.disabled() && !this.sendDisabled() && this.length() > 0 && !this.tooLong() && !this.uploading(), ...(ngDevMode ? [{ debugName: "canSend" }] : []));
9725
+ /** The `accept` attribute of the file input. */
9726
+ this.accept = computed(() => nhAssistantAcceptAttribute(this.uploads()?.acceptedMediaTypes ?? []), ...(ngDevMode ? [{ debugName: "accept" }] : []));
9727
+ /** The values the attachment texts name: the accepted types, the largest size and the most files. */
9728
+ this.uploadParams = computed(() => {
9729
+ const uploads = this.uploads();
9730
+ return {
9731
+ types: (uploads?.acceptedMediaTypes ?? []).map(nhAssistantFileTypeLabel).join(', '),
9732
+ max: uploads ? this.size(uploads.maxFileBytes) : '',
9733
+ count: uploads?.maxFiles ?? 0
9734
+ };
9735
+ }, ...(ngDevMode ? [{ debugName: "uploadParams" }] : []));
8755
9736
  effect(() => {
8756
9737
  const draft = this.restore();
8757
9738
  if (draft !== null) {
@@ -8801,6 +9782,40 @@ class NhAssistantComposerComponent {
8801
9782
  this.text.set('');
8802
9783
  this.send.emit(text);
8803
9784
  }
9785
+ /** Hands the chosen files to the host and empties the input, so the same file can be chosen again. */
9786
+ onFilesChosen(event) {
9787
+ const input = event.target;
9788
+ const files = Array.from(input.files ?? []);
9789
+ input.value = '';
9790
+ if (files.length > 0) {
9791
+ this.attach.emit(files);
9792
+ }
9793
+ }
9794
+ /** Removes an attachment and keeps the keyboard in the composer, on the attach button. */
9795
+ remove(attachment) {
9796
+ this.removeAttachment.emit(attachment.id);
9797
+ queueMicrotask(() => (this.attachButton()?.nativeElement ?? this.textarea().nativeElement).focus({ preventScroll: true }));
9798
+ }
9799
+ /** The translation keys of why an attachment failed, from the composer's own text to the generic one. */
9800
+ errorKeys(attachment) {
9801
+ const error = attachment.error;
9802
+ if (!error) {
9803
+ return [];
9804
+ }
9805
+ return [
9806
+ `nh-assistant.composer.attachment-errors.${error.code}`,
9807
+ error.messageKey,
9808
+ `nh-assistant.errors.${error.code}`,
9809
+ 'nh-assistant.errors.generic'
9810
+ ];
9811
+ }
9812
+ percent(attachment) {
9813
+ return Math.round(attachment.progress * 100);
9814
+ }
9815
+ /** A size in bytes in the current language, such as `1.4 MB`. */
9816
+ size(bytes) {
9817
+ return nhAssistantFormatFileSize(bytes, this.translate?.getCurrentLang());
9818
+ }
8804
9819
  restoreOwnedFocus() {
8805
9820
  if (!this.ownedFocus || matchMedia('(pointer: coarse)').matches) {
8806
9821
  return;
@@ -8811,12 +9826,12 @@ class NhAssistantComposerComponent {
8811
9826
  }
8812
9827
  }
8813
9828
  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 }); }
9829
+ 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
9830
  }
8816
9831
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: NhAssistantComposerComponent, decorators: [{
8817
9832
  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: [{
9833
+ 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"] }]
9834
+ }], 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
9835
  type: HostListener,
8821
9836
  args: ['focusin']
8822
9837
  }], onFocusOut: [{
@@ -9328,15 +10343,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImpo
9328
10343
  }] });
9329
10344
 
9330
10345
  /**
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.
10346
+ * Groups consecutive tool calls of a message, and consecutive files. Text and approvals break a
10347
+ * group, so an approval always stays visible. A tool group's key is its first invocation id, so it
10348
+ * keeps its identity while later calls of the same run are appended.
9334
10349
  */
9335
10350
  function groupNhAssistantParts(parts) {
9336
10351
  const groups = [];
9337
10352
  let tools = null;
10353
+ let files = null;
9338
10354
  parts.forEach((part, index) => {
9339
10355
  if (part.type === 'tool-call') {
10356
+ files = null;
9340
10357
  if (!tools) {
9341
10358
  tools = { kind: 'tools', key: `tools:${part.invocationId}`, calls: [] };
9342
10359
  groups.push(tools);
@@ -9345,6 +10362,15 @@ function groupNhAssistantParts(parts) {
9345
10362
  return;
9346
10363
  }
9347
10364
  tools = null;
10365
+ if (part.type === 'file') {
10366
+ if (!files) {
10367
+ files = { kind: 'files', key: `files:${index}`, files: [] };
10368
+ groups.push(files);
10369
+ }
10370
+ files.files.push(part);
10371
+ return;
10372
+ }
10373
+ files = null;
9348
10374
  if (part.type === 'approval') {
9349
10375
  groups.push({ kind: 'approval', key: `approval:${part.approvalId}`, part });
9350
10376
  }
@@ -9800,11 +10826,19 @@ const stickToBottomDistance = 120;
9800
10826
  /**
9801
10827
  * The messages of a conversation as a live log. Assistant text renders as sanitized
9802
10828
  * 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.
10829
+ * approvals as cards. The files a person attached show by name, type and size. Messages a
10830
+ * trigger wrote into an agent thread read as events, not as a person. While a turn runs
10831
+ * without visible progress, a working indicator shows the elapsed time. The thread follows
10832
+ * new content while the user is at the bottom.
9806
10833
  */
9807
10834
  class NhAssistantThreadComponent {
10835
+ /** The type and size of an attached file, in the current language. */
10836
+ fileMeta(file) {
10837
+ return {
10838
+ type: nhAssistantFileTypeLabel(file.mediaType),
10839
+ size: nhAssistantFormatFileSize(file.size, this.translate?.getCurrentLang())
10840
+ };
10841
+ }
9808
10842
  /** Asks the question again that the assistant message with a connection card answered. */
9809
10843
  retryAfterConnect(message) {
9810
10844
  const messages = this.messages();
@@ -9823,6 +10857,7 @@ class NhAssistantThreadComponent {
9823
10857
  }
9824
10858
  constructor() {
9825
10859
  this.injector = inject(Injector);
10860
+ this.translate = inject(TranslateService, { optional: true });
9826
10861
  this.messages = input.required(...(ngDevMode ? [{ debugName: "messages" }] : []));
9827
10862
  this.streaming = input(false, ...(ngDevMode ? [{ debugName: "streaming" }] : []));
9828
10863
  this.deciding = input(false, ...(ngDevMode ? [{ debugName: "deciding" }] : []));
@@ -9957,7 +10992,7 @@ class NhAssistantThreadComponent {
9957
10992
  }
9958
10993
  }
9959
10994
  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 }); }
10995
+ 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
10996
  }
9962
10997
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: NhAssistantThreadComponent, decorators: [{
9963
10998
  type: Component,
@@ -9965,12 +11000,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImpo
9965
11000
  NgTemplateOutlet,
9966
11001
  ScrollingModule,
9967
11002
  TranslatePipe,
11003
+ NhAssistantIconComponent,
9968
11004
  NhAssistantMarkdownPipe,
9969
11005
  NhAssistantPartGroupsPipe,
9970
11006
  NhAssistantToolCallGroupComponent,
9971
11007
  NhAssistantApprovalCardComponent,
9972
11008
  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"] }]
11009
+ ], 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
11010
  }], 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
11011
 
9976
11012
  let nextId$1 = 0;
@@ -10128,7 +11164,15 @@ class NhAssistantPanelComponent {
10128
11164
  this.showConversations.set(false);
10129
11165
  this.showPreferences.set(false);
10130
11166
  this.showShare.set(false);
10131
- void this.store.submit(text);
11167
+ void this.store.submit(text, this.store.attachedFiles());
11168
+ }
11169
+ /** Uploads the files the person attached to the next message. */
11170
+ attach(files) {
11171
+ void this.store.attach(files);
11172
+ }
11173
+ /** The names of the files a queued message carries, as the queue lists them. */
11174
+ fileNames(files) {
11175
+ return files.map(file => file.name).join(', ');
10132
11176
  }
10133
11177
  editQueuedMessage(id, text) {
10134
11178
  this.resumeAfterEditing = !this.store.queuePaused();
@@ -10188,7 +11232,7 @@ class NhAssistantPanelComponent {
10188
11232
  void this.store.cancel();
10189
11233
  }
10190
11234
  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 }); }
11235
+ 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
11236
  }
10193
11237
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: NhAssistantPanelComponent, decorators: [{
10194
11238
  type: Component,
@@ -10204,7 +11248,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImpo
10204
11248
  NhAssistantPreferencesComponent,
10205
11249
  NhAssistantShareComponent,
10206
11250
  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"] }]
11251
+ ], 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
11252
  }], ctorParameters: () => [], propDecorators: { drawer: [{ type: i0.ViewChild, args: ['drawer', { isSignal: true }] }] } });
10209
11253
 
10210
11254
  /**
@@ -10633,5 +11677,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImpo
10633
11677
  * Generated bundle index. Do not edit.
10634
11678
  */
10635
11679
 
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 };
11680
+ 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
11681
  //# sourceMappingURL=newheap-platform-ai-chat.mjs.map