@newheap/platform-ai-chat 0.15.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/admin/index.d.ts +106 -13
- package/fesm2022/newheap-platform-ai-chat-admin.mjs +446 -36
- package/fesm2022/newheap-platform-ai-chat-admin.mjs.map +1 -1
- package/fesm2022/newheap-platform-ai-chat-testing.mjs +522 -43
- package/fesm2022/newheap-platform-ai-chat-testing.mjs.map +1 -1
- package/fesm2022/newheap-platform-ai-chat.mjs +1054 -63
- package/fesm2022/newheap-platform-ai-chat.mjs.map +1 -1
- package/i18n/en.json +127 -8
- package/i18n/nl.json +127 -8
- package/index.d.ts +424 -38
- package/package.json +1 -1
- package/testing/index.d.ts +64 -7
|
@@ -214,6 +214,15 @@ function skillNameKeys(agentId, skillId) {
|
|
|
214
214
|
function skillDescriptionKeys(agentId, skillId) {
|
|
215
215
|
return [`nh-assistant.agents.${agentId}.skills.${skillId}.description`];
|
|
216
216
|
}
|
|
217
|
+
/**
|
|
218
|
+
* The key of a warning about an agent or a skill that is saved and in effect but may not work as
|
|
219
|
+
* configured, such as `classification-not-permitted`; a code the screen does not know shows as sent.
|
|
220
|
+
*/
|
|
221
|
+
function warningKeys(kind, code) {
|
|
222
|
+
return kind === 'agent'
|
|
223
|
+
? [`nh-assistant.admin.agents.warnings.${code}`]
|
|
224
|
+
: [`nh-assistant.admin.run-config.skills.warnings.${code}`];
|
|
225
|
+
}
|
|
217
226
|
/** The key an application may translate an authorization policy's name with. */
|
|
218
227
|
function policyKeys(policy) {
|
|
219
228
|
return policy ? [`nh-assistant.policies.${policy}`] : [];
|
|
@@ -411,6 +420,7 @@ class NhAssistantAdminAgentEditorComponent {
|
|
|
411
420
|
this.id = `nh-assistant-agent-editor-${nextId$f++}`;
|
|
412
421
|
this.max = NH_ASSISTANT_MAX_INSTRUCTIONS;
|
|
413
422
|
this.autonomyLevels = ['observe', 'explain', 'propose', 'simulate', 'execute'];
|
|
423
|
+
this.classifications = ['public', 'internal', 'confidential', 'restricted'];
|
|
414
424
|
this.agentId = signal('', ...(ngDevMode ? [{ debugName: "agentId" }] : []));
|
|
415
425
|
this.displayName = signal('', ...(ngDevMode ? [{ debugName: "displayName" }] : []));
|
|
416
426
|
this.description = signal('', ...(ngDevMode ? [{ debugName: "description" }] : []));
|
|
@@ -418,6 +428,12 @@ class NhAssistantAdminAgentEditorComponent {
|
|
|
418
428
|
this.autonomy = signal('explain', ...(ngDevMode ? [{ debugName: "autonomy" }] : []));
|
|
419
429
|
/** How the agent is used: in the chat, on its own, or both. */
|
|
420
430
|
this.use = signal('chat', ...(ngDevMode ? [{ debugName: "use" }] : []));
|
|
431
|
+
/** A new agent takes its triggers at once; without it, it starts off until someone switches it on. */
|
|
432
|
+
this.switchOnNow = signal(true, ...(ngDevMode ? [{ debugName: "switchOnNow" }] : []));
|
|
433
|
+
/** The model profile of an agent created here; empty uses the application's chat profile. */
|
|
434
|
+
this.profile = signal('', ...(ngDevMode ? [{ debugName: "profile" }] : []));
|
|
435
|
+
/** The classification of the model calls of an agent created here; `internal` when it has none. */
|
|
436
|
+
this.dataClassification = signal('internal', ...(ngDevMode ? [{ debugName: "dataClassification" }] : []));
|
|
421
437
|
this.requiredPolicy = signal('', ...(ngDevMode ? [{ debugName: "requiredPolicy" }] : []));
|
|
422
438
|
this.toolSelectors = signal([], ...(ngDevMode ? [{ debugName: "toolSelectors" }] : []));
|
|
423
439
|
this.mcpServerIds = signal([], ...(ngDevMode ? [{ debugName: "mcpServerIds" }] : []));
|
|
@@ -480,10 +496,21 @@ class NhAssistantAdminAgentEditorComponent {
|
|
|
480
496
|
}, ...(ngDevMode ? [{ debugName: "knownTypes" }] : []));
|
|
481
497
|
/** What the agent works on is edited here: an agent created here, while the run catalog is known. */
|
|
482
498
|
this.editsWorksOn = computed(() => this.choosesUse() && !this.catalogUnavailable(), ...(ngDevMode ? [{ debugName: "editsWorksOn" }] : []));
|
|
499
|
+
/** An agent created here chooses its profile from those the run catalog offers. */
|
|
500
|
+
this.choosesProfile = computed(() => this.choosesUse() && !this.catalogUnavailable(), ...(ngDevMode ? [{ debugName: "choosesProfile" }] : []));
|
|
501
|
+
/** The profiles the run catalog offers, and the agent's own when the catalog no longer lists it. */
|
|
502
|
+
this.profileChoices = computed(() => {
|
|
503
|
+
const listed = this.catalog()?.profiles ?? [];
|
|
504
|
+
const current = this.agent()?.profile;
|
|
505
|
+
return current && !listed.includes(current) ? [...listed, current] : listed;
|
|
506
|
+
}, ...(ngDevMode ? [{ debugName: "profileChoices" }] : []));
|
|
483
507
|
/** Chosen object types without a type, and not the application either: the agent would work on nothing. */
|
|
484
508
|
this.worksOnMissing = computed(() => this.editsWorksOn() && this.worksOnScope() === 'types' && this.worksOnTypes().length === 0 && !this.worksOnApplication(), ...(ngDevMode ? [{ debugName: "worksOnMissing" }] : []));
|
|
485
509
|
this.tooManyTypes = computed(() => this.editsWorksOn() && this.worksOnScope() === 'types' && this.worksOnTypes().length > maxWorksOnTypes, ...(ngDevMode ? [{ debugName: "tooManyTypes" }] : []));
|
|
486
510
|
this.policyKeys = policyKeys;
|
|
511
|
+
this.warningKeys = warningKeys;
|
|
512
|
+
/** The warnings of the agent as it was saved, until this editor saves it again. */
|
|
513
|
+
this.warnings = computed(() => this.agent()?.warnings ?? [], ...(ngDevMode ? [{ debugName: "warnings" }] : []));
|
|
487
514
|
this.valid = computed(() => !this.idInvalid() && !this.nameMissing() && !this.tooLong() && !this.worksOnMissing() && !this.tooManyTypes(), ...(ngDevMode ? [{ debugName: "valid" }] : []));
|
|
488
515
|
this.errorKeys = computed(() => _nhAssistantErrorKeys(this.error()), ...(ngDevMode ? [{ debugName: "errorKeys" }] : []));
|
|
489
516
|
this.filteredTools = computed(() => {
|
|
@@ -564,7 +591,14 @@ class NhAssistantAdminAgentEditorComponent {
|
|
|
564
591
|
autonomy: this.autonomy(),
|
|
565
592
|
...(this.choosesUse() ? { offeredInChat: this.use() !== 'background' } : {}),
|
|
566
593
|
// Omitted, what the agent works on stays as it is; a new agent then works on every object type.
|
|
567
|
-
...(this.editsWorksOn() ? this.worksOnInput() : {})
|
|
594
|
+
...(this.editsWorksOn() ? this.worksOnInput() : {}),
|
|
595
|
+
...(this.isNew() && !this.switchOnNow() ? { startsOff: true } : {}),
|
|
596
|
+
// Omitted, the profile stays as it is; a new agent then uses the application's chat profile.
|
|
597
|
+
...(this.choosesProfile() && this.profile() !== '' ? { profile: this.profile() } : {}),
|
|
598
|
+
// Sent only when it changes, so an agent without a classification keeps none.
|
|
599
|
+
...(this.choosesUse() && this.dataClassification() !== (this.editing()?.dataClassification ?? 'internal')
|
|
600
|
+
? { dataClassification: this.dataClassification() }
|
|
601
|
+
: {})
|
|
568
602
|
};
|
|
569
603
|
const startsWork = this.agent() === null && this.use() !== 'chat';
|
|
570
604
|
// An existing agent's groups are saved before its definition, a new agent's right after it is
|
|
@@ -621,6 +655,9 @@ class NhAssistantAdminAgentEditorComponent {
|
|
|
621
655
|
this.instructions.set(agent?.instructions ?? '');
|
|
622
656
|
this.autonomy.set(agent?.autonomy ?? 'explain');
|
|
623
657
|
this.use.set(agent?.offeredInChat === false ? 'background' : 'chat');
|
|
658
|
+
this.switchOnNow.set(true);
|
|
659
|
+
this.profile.set(agent?.source === 'admin' ? agent.profile ?? '' : '');
|
|
660
|
+
this.dataClassification.set(agent?.source === 'admin' ? agent.dataClassification ?? 'internal' : 'internal');
|
|
624
661
|
this.requiredPolicy.set(agent?.requiredPolicy ?? '');
|
|
625
662
|
this.toolSelectors.set([...(agent?.toolSelectors ?? [])]);
|
|
626
663
|
this.mcpServerIds.set([...(agent?.mcpServerIds ?? [])]);
|
|
@@ -645,11 +682,11 @@ class NhAssistantAdminAgentEditorComponent {
|
|
|
645
682
|
return typeof shown === 'string' && shown.length > 0 && shown !== text ? { key: text, shown } : null;
|
|
646
683
|
}
|
|
647
684
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: NhAssistantAdminAgentEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
648
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.34", type: NhAssistantAdminAgentEditorComponent, isStandalone: true, selector: "nh-assistant-admin-agent-editor", inputs: { agent: { classPropertyName: "agent", publicName: "agent", isSignal: true, isRequired: false, transformFunction: null }, tools: { classPropertyName: "tools", publicName: "tools", isSignal: true, isRequired: false, transformFunction: null }, servers: { classPropertyName: "servers", publicName: "servers", isSignal: true, isRequired: false, transformFunction: null }, catalog: { classPropertyName: "catalog", publicName: "catalog", isSignal: true, isRequired: false, transformFunction: null }, catalogUnavailable: { classPropertyName: "catalogUnavailable", publicName: "catalogUnavailable", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { saved: "saved", workRequested: "workRequested", cancelled: "cancelled" }, ngImport: i0, template: "<form class=\"section card\" [attr.aria-labelledby]=\"id + '-title'\" [attr.aria-busy]=\"saving()\" (submit)=\"$event.preventDefault(); save()\">\n <h3 [id]=\"id + '-title'\">\n @if (agent(); as current) {\n {{ 'nh-assistant.admin.agent-editor.edit-title' | translate: { name: current.displayName } }}\n } @else {\n {{ 'nh-assistant.admin.agent-editor.new-title' | translate }}\n }\n </h3>\n\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-id'\">{{ 'nh-assistant.admin.agent-editor.id' | translate }}</label>\n <input\n type=\"text\"\n class=\"mono\"\n autocomplete=\"off\"\n [id]=\"id + '-id'\"\n [value]=\"agentId()\"\n [disabled]=\"!isNew()\"\n [attr.aria-describedby]=\"id + '-id-hint'\"\n [attr.aria-invalid]=\"submitted() && idInvalid() ? true : null\"\n (input)=\"agentId.set(value($event))\" />\n <p class=\"hint\" [id]=\"id + '-id-hint'\">\n <span [class.error-text]=\"submitted() && idInvalid()\">\n {{ (submitted() && idInvalid() ? 'nh-assistant.admin.invalid-id' : 'nh-assistant.admin.agent-editor.id-hint') | translate }}\n </span>\n </p>\n </div>\n\n <div class=\"field\">\n <label [for]=\"id + '-name'\">{{ 'nh-assistant.admin.agent-editor.display-name' | translate }}</label>\n <input\n type=\"text\"\n [id]=\"id + '-name'\"\n [value]=\"displayName()\"\n [attr.aria-invalid]=\"submitted() && nameMissing() ? true : null\"\n [attr.aria-describedby]=\"id + '-name-hint'\"\n (input)=\"displayName.set(value($event))\" />\n <p class=\"hint\" [id]=\"id + '-name-hint'\">\n @if (submitted() && nameMissing()) {\n <span class=\"error-text\">{{ 'nh-assistant.admin.required' | translate }}</span>\n }\n </p>\n </div>\n </div>\n\n @if (choosesUse()) {\n <fieldset class=\"subsection\" data-agent-use>\n <legend>{{ 'nh-assistant.admin.agent-editor.use.title' | translate }}</legend>\n <div class=\"uses\">\n @for (option of uses; track option) {\n <label class=\"use\" [class.selected]=\"use() === option\">\n <input type=\"radio\" [name]=\"id + '-use'\" [value]=\"option\" [checked]=\"use() === option\" (change)=\"use.set(option)\" />\n <span>\n <strong>{{ 'nh-assistant.admin.agent-editor.use.' + option | translate }}</strong>\n <span class=\"muted\">{{ 'nh-assistant.admin.agent-editor.use.' + option + '-hint' | translate }}</span>\n </span>\n </label>\n }\n </div>\n @if (isNew() && use() !== 'chat') {\n <p class=\"hint\">{{ 'nh-assistant.admin.agent-editor.use.next' | translate }}</p>\n }\n </fieldset>\n\n @if (catalogUnavailable()) {\n <!-- Without the run catalog the kinds of objects and policies are unknown: what the agent works on stays as it is. -->\n <p class=\"message warning\" role=\"note\" data-catalog-unavailable>\n <nh-assistant-icon name=\"warning\" />{{ 'nh-assistant.admin.agent-editor.catalog-unavailable' | translate }}\n </p>\n } @else {\n <fieldset\n class=\"subsection\"\n data-works-on\n [attr.aria-describedby]=\"id + '-works-on-hint ' + id + '-works-on-error'\"\n [attr.aria-invalid]=\"(submitted() && worksOnMissing()) || fieldError('worksOn') ? true : null\">\n <legend>{{ 'nh-assistant.admin.agent-editor.works-on.title' | translate }}</legend>\n <p class=\"muted\" [id]=\"id + '-works-on-hint'\">{{ 'nh-assistant.admin.agent-editor.works-on.hint' | translate }}</p>\n <div class=\"uses\">\n <label class=\"use\" [class.selected]=\"worksOnScope() === 'all'\">\n <input type=\"radio\" [name]=\"id + '-works-on'\" value=\"all\" [checked]=\"worksOnScope() === 'all'\" (change)=\"worksOnScope.set('all')\" />\n <span>\n <strong>{{ 'nh-assistant.admin.agent-editor.works-on.all' | translate }}</strong>\n <span class=\"muted\">{{ 'nh-assistant.admin.agent-editor.works-on.all-hint' | translate }}</span>\n </span>\n </label>\n <label class=\"use\" [class.selected]=\"worksOnScope() === 'types'\">\n <input type=\"radio\" [name]=\"id + '-works-on'\" value=\"types\" [checked]=\"worksOnScope() === 'types'\" (change)=\"worksOnScope.set('types')\" />\n <span>\n <strong>{{ 'nh-assistant.admin.agent-editor.works-on.types' | translate }}</strong>\n <span class=\"muted\">{{ 'nh-assistant.admin.agent-editor.works-on.types-hint' | translate }}</span>\n </span>\n </label>\n </div>\n\n @if (worksOnScope() === 'types') {\n <div class=\"checks\" role=\"group\" [attr.aria-label]=\"'nh-assistant.admin.agent-editor.works-on.types' | translate\" data-works-on-types>\n @for (type of subjectTypes(); track type.id) {\n <label class=\"check\">\n <input type=\"checkbox\" [checked]=\"worksOnTypes().includes(type.id)\" (change)=\"toggleWorksOnType(type.id, checked($event))\" />\n <span>{{ type.pluralName ?? type.name }} <span class=\"mono muted\">{{ type.id }}</span></span>\n </label>\n } @empty {\n <p class=\"muted\">{{ 'nh-assistant.admin.agent-editor.works-on.no-types' | translate }}</p>\n }\n </div>\n @if (fieldError('worksOnSubjectTypes'); as key) {\n <p class=\"hint\"><span class=\"error-text\">{{ key | translate }}</span></p>\n } @else if (tooManyTypes()) {\n <p class=\"hint\"><span class=\"error-text\">{{ 'nh-assistant.admin.agent-editor.works-on.too-many' | translate }}</span></p>\n }\n }\n\n @if (knownTypes().length > 0) {\n <p class=\"hint\" data-known-types>{{ 'nh-assistant.admin.agent-editor.works-on.known' | translate: { types: knownTypes().join(', ') } }}</p>\n }\n\n <label class=\"check described\">\n <input type=\"checkbox\" [checked]=\"worksOnApplication()\" (change)=\"worksOnApplication.set(checked($event))\" />\n <span>\n <strong>{{ 'nh-assistant.admin.agent-editor.works-on.application' | translate }}</strong>\n <span class=\"muted\">{{ 'nh-assistant.admin.agent-editor.works-on.application-hint' | translate }}</span>\n </span>\n </label>\n\n @if (divisionsListed()) {\n <label class=\"check described\" data-per-division>\n <input\n type=\"checkbox\"\n [checked]=\"perDivision()\"\n [attr.aria-invalid]=\"fieldError('perDivision') ? true : null\"\n [attr.aria-describedby]=\"fieldError('perDivision') ? id + '-per-division-error' : null\"\n (change)=\"perDivision.set(checked($event))\" />\n <span>\n <strong>{{ 'nh-assistant.admin.agent-editor.works-on.per-division' | translate }}</strong>\n <span class=\"muted\">{{ 'nh-assistant.admin.agent-editor.works-on.per-division-hint' | translate }}</span>\n </span>\n </label>\n @if (fieldError('perDivision'); as key) {\n <p class=\"hint\" [id]=\"id + '-per-division-error'\"><span class=\"error-text\">{{ key | translate }}</span></p>\n }\n }\n\n <p class=\"hint\" [id]=\"id + '-works-on-error'\" aria-live=\"polite\">\n @if (submitted() && worksOnMissing()) {\n <span class=\"error-text\">{{ 'nh-assistant.admin.agent-editor.works-on.missing' | translate }}</span>\n } @else if (fieldError('worksOn'); as key) {\n <span class=\"error-text\">{{ key | translate }}</span>\n }\n </p>\n\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-run-policy'\">{{ 'nh-assistant.admin.agent-editor.works-on.run-policy' | translate }}</label>\n <select\n [id]=\"id + '-run-policy'\"\n [attr.aria-describedby]=\"id + '-run-policy-hint'\"\n [attr.aria-invalid]=\"fieldError('runPolicy') ? true : null\"\n (change)=\"runPolicy.set(value($event))\">\n <option value=\"\" [selected]=\"runPolicy() === ''\">{{ 'nh-assistant.admin.agent-editor.works-on.run-policy-default' | translate }}</option>\n @for (policy of agentPolicies(); track policy.name) {\n <option [value]=\"policy.name\" [selected]=\"runPolicy() === policy.name\">{{ policyKeys(policy.name) | nhAssistantTranslate: policy.description }}</option>\n }\n </select>\n <p class=\"hint\" [id]=\"id + '-run-policy-hint'\">\n @if (fieldError('runPolicy'); as key) {\n <span class=\"error-text\">{{ key | translate }}</span>\n } @else {\n {{ 'nh-assistant.admin.agent-editor.works-on.run-policy-hint' | translate }}\n }\n </p>\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-view-policy'\">{{ 'nh-assistant.admin.agent-editor.works-on.view-policy' | translate }}</label>\n <select\n [id]=\"id + '-view-policy'\"\n [attr.aria-describedby]=\"id + '-view-policy-hint'\"\n [attr.aria-invalid]=\"fieldError('viewPolicy') ? true : null\"\n (change)=\"viewPolicy.set(value($event))\">\n <option value=\"\" [selected]=\"viewPolicy() === ''\">{{ 'nh-assistant.admin.agent-editor.works-on.view-policy-default' | translate }}</option>\n @for (policy of agentPolicies(); track policy.name) {\n <option [value]=\"policy.name\" [selected]=\"viewPolicy() === policy.name\">{{ policyKeys(policy.name) | nhAssistantTranslate: policy.description }}</option>\n }\n </select>\n <p class=\"hint\" [id]=\"id + '-view-policy-hint'\">\n @if (fieldError('viewPolicy'); as key) {\n <span class=\"error-text\">{{ key | translate }}</span>\n } @else {\n {{ 'nh-assistant.admin.agent-editor.works-on.view-policy-hint' | translate }}\n }\n </p>\n </div>\n </div>\n </fieldset>\n }\n }\n\n @if (agentGroups().length > 0 && !catalogUnavailable()) {\n <fieldset\n class=\"subsection\"\n data-agent-groups\n [attr.aria-describedby]=\"id + '-groups-hint ' + id + '-groups-error'\"\n [attr.aria-invalid]=\"groupsError() ? true : null\">\n <legend>{{ 'nh-assistant.admin.agent-editor.groups.title' | translate }}</legend>\n <p class=\"muted\" [id]=\"id + '-groups-hint'\">{{ 'nh-assistant.admin.agent-editor.groups.hint' | translate }}</p>\n @if (isVariant()) {\n <p class=\"hint\" data-groups-variant>{{ 'nh-assistant.admin.agent-editor.groups.shadow-variant' | translate }}</p>\n } @else {\n <div class=\"checks\" role=\"group\" [attr.aria-label]=\"'nh-assistant.admin.agent-editor.groups.title' | translate\">\n @for (group of agentGroups(); track group.id) {\n <label class=\"check described\" [attr.data-agent-group]=\"group.id\">\n <input type=\"checkbox\" [checked]=\"groups().includes(group.id)\" (change)=\"toggleGroup(group.id, checked($event))\" />\n <span>\n <strong>{{ group.name }}</strong>\n @if (group.description; as description) {\n <span class=\"muted\">{{ description }}</span>\n }\n @if (codeGroups().includes(group.id)) {\n <span class=\"muted\" data-group-in-code>{{ 'nh-assistant.admin.agent-editor.groups.in-code' | translate }}</span>\n }\n </span>\n </label>\n }\n </div>\n }\n <p class=\"hint\" [id]=\"id + '-groups-error'\" aria-live=\"polite\">\n @if (groupsError(); as key) {\n <span class=\"error-text\">{{ key | translate }}</span>\n }\n </p>\n </fieldset>\n }\n\n <div class=\"field\">\n <label [for]=\"id + '-description'\">{{ 'nh-assistant.admin.agent-editor.description' | translate }}</label>\n <input type=\"text\" [id]=\"id + '-description'\" [value]=\"description()\" (input)=\"description.set(value($event))\" />\n </div>\n\n <div class=\"field\">\n <label [for]=\"id + '-instructions'\">{{ 'nh-assistant.admin.agent-editor.instructions' | translate }}</label>\n <textarea\n rows=\"8\"\n [id]=\"id + '-instructions'\"\n [value]=\"instructions()\"\n [attr.aria-describedby]=\"id + '-instructions-hint'\"\n [attr.aria-invalid]=\"tooLong() ? true : null\"\n (input)=\"instructions.set(value($event))\"></textarea>\n <p class=\"hint\" [id]=\"id + '-instructions-hint'\">\n <span [class.error-text]=\"tooLong()\">\n {{ (tooLong() ? 'nh-assistant.admin.too-long' : 'nh-assistant.admin.agent-editor.instructions-hint') | translate }}\n </span>\n <span class=\"counter\" [class.error-text]=\"tooLong()\">{{ 'nh-assistant.admin.characters' | translate: { count: instructions().length, max: max } }}</span>\n </p>\n </div>\n\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-autonomy'\">{{ 'nh-assistant.admin.agent-editor.autonomy' | translate }}</label>\n <select [id]=\"id + '-autonomy'\" [attr.aria-describedby]=\"id + '-autonomy-hint'\" (change)=\"autonomy.set($any(value($event)))\">\n @for (level of autonomyLevels; track level) {\n <option [value]=\"level\" [selected]=\"autonomy() === level\">{{ 'nh-assistant.admin.agents.autonomy.' + level | translate }}</option>\n }\n </select>\n <p class=\"hint\" [id]=\"id + '-autonomy-hint'\">{{ 'nh-assistant.admin.agent-editor.autonomy-hint' | translate }}</p>\n </div>\n\n <div class=\"field\">\n <label [for]=\"id + '-policy'\">{{ 'nh-assistant.admin.agent-editor.required-policy' | translate }}</label>\n <input\n type=\"text\"\n class=\"mono\"\n autocomplete=\"off\"\n [id]=\"id + '-policy'\"\n [value]=\"requiredPolicy()\"\n [attr.aria-describedby]=\"id + '-policy-hint'\"\n (input)=\"requiredPolicy.set(value($event))\" />\n <p class=\"hint\" [id]=\"id + '-policy-hint'\">{{ 'nh-assistant.admin.agent-editor.required-policy-hint' | translate }}</p>\n </div>\n </div>\n\n <p class=\"hint\">{{ 'nh-assistant.admin.agent-editor.state-hint' | translate }}</p>\n\n <fieldset class=\"subsection\">\n <legend>{{ 'nh-assistant.admin.agent-editor.tool-selectors' | translate }}</legend>\n <p class=\"muted\">{{ 'nh-assistant.admin.agent-editor.tool-selectors-hint' | translate }}</p>\n\n @if (toolSelectors().length === 0) {\n <p class=\"muted\">{{ 'nh-assistant.admin.agent-editor.no-selectors' | translate }}</p>\n } @else {\n <ul class=\"chips\">\n @for (selector of toolSelectors(); track selector) {\n <li class=\"chip\">\n <span>{{ selector }}</span>\n <button\n type=\"button\"\n [attr.aria-label]=\"'nh-assistant.admin.agent-editor.remove-selector' | translate: { selector: selector }\"\n [attr.title]=\"'nh-assistant.admin.agent-editor.remove-selector' | translate: { selector: selector }\"\n (click)=\"removeSelector(selector)\">\n <nh-assistant-icon name=\"x\" />\n </button>\n </li>\n }\n </ul>\n <p class=\"muted\" role=\"status\">{{ 'nh-assistant.admin.agent-editor.matches' | translate: { count: matchCount() } }}</p>\n }\n\n <div class=\"inline\">\n <input\n type=\"text\"\n class=\"mono\"\n autocomplete=\"off\"\n [attr.aria-label]=\"'nh-assistant.admin.agent-editor.add-selector' | translate\"\n [attr.placeholder]=\"'nh-assistant.admin.agent-editor.selector-placeholder' | translate\"\n [attr.aria-invalid]=\"selectorInvalid() ? true : null\"\n [value]=\"newSelector()\"\n (input)=\"newSelector.set(value($event))\"\n (keydown)=\"onSelectorKeydown($event)\" />\n <button class=\"secondary\" type=\"button\" [disabled]=\"newSelector().trim().length === 0 || selectorInvalid()\" (click)=\"addSelector()\">\n <nh-assistant-icon name=\"plus\" />{{ 'nh-assistant.admin.agent-editor.add-selector' | translate }}\n </button>\n </div>\n\n <div class=\"field\">\n <label class=\"label\" [for]=\"id + '-catalog-filter'\">{{ 'nh-assistant.admin.agent-editor.catalog' | translate }}</label>\n <input\n type=\"search\"\n [id]=\"id + '-catalog-filter'\"\n [attr.placeholder]=\"'nh-assistant.admin.agent-editor.catalog-filter' | translate\"\n [value]=\"catalogFilter()\"\n (input)=\"catalogFilter.set(value($event))\" />\n <ul class=\"catalog\" [attr.aria-label]=\"'nh-assistant.admin.agent-editor.catalog' | translate\">\n @for (tool of filteredTools(); track tool.id) {\n <li>\n <label>\n <input\n type=\"checkbox\"\n [checked]=\"isSelected(tool.id) || isCovered(tool.id)\"\n [disabled]=\"isCovered(tool.id)\"\n (change)=\"toggleTool(tool.id, checked($event))\" />\n <span class=\"tool-id\">{{ tool.id }}</span>\n <span class=\"badge\">{{ 'nh-assistant.admin.source.' + tool.source | translate }}</span>\n <span class=\"badge\" [class.warning]=\"tool.effect !== 'read-only'\">{{ 'nh-assistant.admin.effect.' + tool.effect | translate }}</span>\n <span class=\"muted\">{{ tool.description }}</span>\n </label>\n </li>\n } @empty {\n <li class=\"muted\">{{ 'nh-assistant.admin.agent-editor.catalog-empty' | translate }}</li>\n }\n </ul>\n </div>\n </fieldset>\n\n <fieldset class=\"subsection\">\n <legend>{{ 'nh-assistant.admin.agent-editor.mcp-servers' | translate }}</legend>\n <p class=\"muted\">{{ 'nh-assistant.admin.agent-editor.mcp-servers-hint' | translate }}</p>\n @for (server of servers(); track server.id) {\n <label class=\"check\">\n <input type=\"checkbox\" [checked]=\"mcpServerIds().includes(server.id)\" (change)=\"toggleServer(server.id, checked($event))\" />\n <span>{{ server.displayName }} <span class=\"mono muted\">{{ server.id }}</span></span>\n </label>\n } @empty {\n <p class=\"muted\">{{ 'nh-assistant.admin.agent-editor.mcp-servers-empty' | translate }}</p>\n }\n </fieldset>\n\n @if (error(); as failure) {\n <p class=\"message error\" role=\"alert\"><nh-assistant-icon name=\"warning\" />{{ errorKeys() | nhAssistantTranslate: failure.code }}</p>\n }\n\n <div class=\"actions\">\n <button class=\"secondary\" type=\"button\" (click)=\"cancelled.emit()\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n <button class=\"primary\" type=\"submit\" [disabled]=\"saving() || tooLong()\">\n {{ (saving() ? 'nh-assistant.admin.saving' : 'nh-assistant.admin.save') | translate }}\n </button>\n </div>\n</form>\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;min-width:0;color:var(--nha-text);font-family:var(--nha-font);font-size:14px}:host fieldset{min-width:0;margin:0;border:0;padding:0}:host legend,:host .label,:host .field>label{display:block;margin-bottom:6px;padding:0;color:var(--nha-text);font-size:12px;font-weight:700}:host input[type=text],:host input[type=url],:host input[type=password],:host input[type=search],:host input[type=email],:host input[type=number],:host input[type=date],:host input[type=time],:host input[type=datetime-local],:host select,:host textarea{box-sizing:border-box;width:100%;min-width:0;min-height:38px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:8px 10px;color:var(--nha-text);background:var(--nha-background);font:inherit;font-size:13px}:host input[type=text]:focus-visible,:host input[type=url]:focus-visible,:host input[type=password]:focus-visible,:host input[type=search]:focus-visible,:host input[type=email]:focus-visible,:host input[type=number]:focus-visible,:host input[type=date]:focus-visible,:host input[type=time]:focus-visible,:host input[type=datetime-local]:focus-visible,:host select:focus-visible,:host textarea:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host input[type=text]:disabled,:host input[type=url]:disabled,:host input[type=password]:disabled,:host input[type=search]:disabled,:host input[type=email]:disabled,:host input[type=number]:disabled,:host input[type=date]:disabled,:host input[type=time]:disabled,:host input[type=datetime-local]:disabled,:host select:disabled,:host textarea:disabled{cursor:not-allowed;opacity:.6}:host input[type=text][aria-invalid=true],:host input[type=url][aria-invalid=true],:host input[type=password][aria-invalid=true],:host input[type=search][aria-invalid=true],:host input[type=email][aria-invalid=true],:host input[type=number][aria-invalid=true],:host input[type=date][aria-invalid=true],:host input[type=time][aria-invalid=true],:host input[type=datetime-local][aria-invalid=true],:host select[aria-invalid=true],:host textarea[aria-invalid=true]{border-color:var(--nha-danger)}:host textarea{line-height:1.45;resize:vertical}:host input[type=number]{font-variant-numeric:tabular-nums}:host .mono{font-family:var(--nha-mono);font-size:12px}:host .field{display:flex;flex-direction:column;min-width:0}:host .hint{display:flex;justify-content:space-between;gap:8px;margin:4px 2px 0;color:var(--nha-text-muted);font-size:11px}:host .counter{flex:none;font-variant-numeric:tabular-nums}:host .error-text{color:var(--nha-danger)}:host .field-error{display:flex;align-items:flex-start;gap:6px;margin:4px 2px 0;color:var(--nha-danger);font-size:12px;font-weight:600;overflow-wrap:anywhere}:host .field-error nh-assistant-icon{flex:none;margin-top:1px}:host .form-error{box-sizing:border-box;display:flex;align-items:flex-start;gap:8px;margin:0;border:1px solid var(--nha-danger);border-radius:var(--nha-radius-control);padding:8px 12px;color:var(--nha-text);background:var(--nha-danger-soft);font-size:13px;overflow-wrap:anywhere}:host .form-error ul{margin:4px 0 0;padding-left:18px}:host .message{box-sizing:border-box;display:flex;align-items:center;gap:8px;margin:0;border:1px solid transparent;border-radius:var(--nha-radius-control);padding:8px 12px;font-size:13px;overflow-wrap:anywhere}:host .message.error{border-color:var(--nha-danger);color:var(--nha-text);background:var(--nha-danger-soft)}:host .message.warning{border-color:var(--nha-warning);color:var(--nha-text);background:var(--nha-warning-soft)}:host .message.success{border-color:var(--nha-success);color:var(--nha-text);background:var(--nha-success-soft)}:host .message.info{border-color:var(--nha-border);color:var(--nha-text);background:var(--nha-surface)}:host .message nh-assistant-icon{flex:none}:host button.link{min-height:0;border:0;padding:0;color:var(--nha-accent, currentColor);background:none;font:inherit;text-decoration:underline;cursor:pointer}:host button.link:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host button.link:disabled{cursor:not-allowed;opacity:.55}:host .primary,:host .secondary,:host .danger{display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;gap:6px;min-height:36px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px 14px;font:inherit;font-size:13px;font-weight:700;white-space:nowrap;cursor:pointer}:host .primary:focus-visible,:host .secondary:focus-visible,:host .danger:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .primary:disabled,:host .secondary:disabled,:host .danger:disabled{cursor:not-allowed;opacity:.55}:host .primary{border-color:var(--nha-accent);color:var(--nha-accent-contrast);background:var(--nha-accent)}:host .primary:hover:not(:disabled){filter:brightness(1.12)}:host .secondary{color:var(--nha-text);background:transparent}:host .secondary:hover:not(:disabled){background:var(--nha-surface-strong)}:host .danger{border-color:var(--nha-danger);color:var(--nha-danger);background:transparent}:host .danger:hover:not(:disabled){background:var(--nha-danger-soft)}:host .icon-button{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:1px solid transparent;border-radius:var(--nha-radius-control);color:var(--nha-text-muted);background:transparent;cursor:pointer}:host .icon-button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .icon-button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}:host .icon-button:disabled{cursor:not-allowed;opacity:.5}:host .actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px}:host .badge{display:inline-flex;align-items:center;gap:4px;border-radius:999px;padding:2px 8px;color:var(--nha-text-muted);background:var(--nha-surface-strong);font-size:11px;font-weight:700;white-space:nowrap}:host .badge.success{color:var(--nha-success);background:var(--nha-success-soft)}:host .badge.warning{color:var(--nha-warning);background:var(--nha-warning-soft)}:host .badge.danger{color:var(--nha-danger);background:var(--nha-danger-soft)}:host .visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}.section{display:flex;flex-direction:column;gap:16px;min-width:0}.section-header{display:flex;flex-wrap:wrap;align-items:flex-start;justify-content:space-between;gap:12px}.section-header>div{min-width:0;max-width:72ch}h3{margin:0 0 4px;font-size:17px;font-weight:750}h4{margin:0 0 8px;font-size:14px;font-weight:700}.muted{margin:0;color:var(--nha-text-muted);font-size:13px}.state{display:flex;flex-direction:column;align-items:flex-start;gap:10px;margin:0;border:1px dashed var(--nha-border);border-radius:var(--nha-radius);padding:20px;color:var(--nha-text-muted);font-size:13px}.state p{margin:0}.stack{display:flex;flex-direction:column;gap:14px;min-width:0}.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,240px),1fr));gap:14px}.meta{display:flex;flex-wrap:wrap;align-items:center;gap:8px 12px;color:var(--nha-text-muted);font-size:12px;overflow-wrap:anywhere}textarea.large{min-height:260px}.catalog-error{flex-wrap:wrap}.grow{flex:1;min-width:0}.subsection{display:flex;flex-direction:column;gap:8px;border-top:1px solid var(--nha-border);padding-top:14px}.history{display:grid;gap:6px;margin:0;padding:0;list-style:none}.history li{display:flex;flex-wrap:wrap;align-items:center;gap:6px 12px;font-size:12px}.cards{display:grid;gap:10px;margin:0;padding:0;list-style:none}.card{display:flex;flex-direction:column;gap:8px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius);padding:14px;background:var(--nha-background)}.card.inactive{opacity:.8}.card-header{display:flex;flex-wrap:wrap;align-items:center;gap:8px;min-width:0}.card-header strong{font-size:14px;overflow-wrap:anywhere}.card-actions{display:flex;flex-wrap:wrap;gap:8px}.check{display:flex;align-items:flex-start;gap:8px;font-size:13px;cursor:pointer}.check input{width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.check.described>span{display:grid;gap:2px;min-width:0;overflow-wrap:anywhere}.checks{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));gap:6px 12px;min-width:0;overflow-wrap:anywhere}.chips{display:flex;flex-wrap:wrap;gap:6px;margin:0;padding:0;list-style:none}.chip{display:inline-flex;align-items:center;gap:2px;border:1px solid var(--nha-border);border-radius:999px;padding:2px 2px 2px 10px;background:var(--nha-surface);font-family:var(--nha-mono);font-size:12px}.chip 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;width:26px;height:26px;border-radius:999px}.chip button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.chip button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}.chip button:disabled{cursor:not-allowed;opacity:.5}.inline{display:flex;flex-wrap:wrap;gap:8px}.inline>input{flex:1 1 180px}.catalog{display:grid;gap:4px;max-height:260px;margin:8px 0 0;overflow-y:auto;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px;list-style:none}.catalog li label{display:flex;flex-wrap:wrap;align-items:center;gap:6px 8px;border-radius:8px;padding:6px;cursor:pointer}.catalog li label:hover{background:var(--nha-surface)}.catalog .tool-id{font-family:var(--nha-mono);font-size:12px;overflow-wrap:anywhere}.skills{display:grid;gap:8px;margin:0;padding:0;list-style:none}.skill{display:grid;gap:6px;border-left:3px solid var(--nha-border);padding:4px 0 4px 12px}.uses{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));gap:8px}.use{display:flex;align-items:flex-start;gap:10px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:10px 12px;cursor:pointer;overflow-wrap:anywhere}.use input{flex:none;width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.use>span{display:grid;gap:2px;min-width:0}.use strong{font-size:13px}.use.selected{border-color:var(--nha-highlight);background:var(--nha-surface)}.use:focus-within{outline:2px solid var(--nha-highlight);outline-offset:2px}.discovery{display:grid;gap:8px;padding:10px 12px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control)}.discovery dl{display:grid;grid-template-columns:max-content minmax(0,1fr);gap:4px 12px;margin:0}.discovery dt{color:var(--nha-text-muted)}.discovery dd{margin:0;overflow-wrap:anywhere}\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 }); }
|
|
685
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.34", type: NhAssistantAdminAgentEditorComponent, isStandalone: true, selector: "nh-assistant-admin-agent-editor", inputs: { agent: { classPropertyName: "agent", publicName: "agent", isSignal: true, isRequired: false, transformFunction: null }, tools: { classPropertyName: "tools", publicName: "tools", isSignal: true, isRequired: false, transformFunction: null }, servers: { classPropertyName: "servers", publicName: "servers", isSignal: true, isRequired: false, transformFunction: null }, catalog: { classPropertyName: "catalog", publicName: "catalog", isSignal: true, isRequired: false, transformFunction: null }, catalogUnavailable: { classPropertyName: "catalogUnavailable", publicName: "catalogUnavailable", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { saved: "saved", workRequested: "workRequested", cancelled: "cancelled" }, ngImport: i0, template: "<form class=\"section card\" [attr.aria-labelledby]=\"id + '-title'\" [attr.aria-busy]=\"saving()\" (submit)=\"$event.preventDefault(); save()\">\n <h3 [id]=\"id + '-title'\">\n @if (agent(); as current) {\n {{ 'nh-assistant.admin.agent-editor.edit-title' | translate: { name: current.displayName } }}\n } @else {\n {{ 'nh-assistant.admin.agent-editor.new-title' | translate }}\n }\n </h3>\n\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-id'\">{{ 'nh-assistant.admin.agent-editor.id' | translate }}</label>\n <input\n type=\"text\"\n class=\"mono\"\n autocomplete=\"off\"\n [id]=\"id + '-id'\"\n [value]=\"agentId()\"\n [disabled]=\"!isNew()\"\n [attr.aria-describedby]=\"id + '-id-hint'\"\n [attr.aria-invalid]=\"submitted() && idInvalid() ? true : null\"\n (input)=\"agentId.set(value($event))\" />\n <p class=\"hint\" [id]=\"id + '-id-hint'\">\n <span [class.error-text]=\"submitted() && idInvalid()\">\n {{ (submitted() && idInvalid() ? 'nh-assistant.admin.invalid-id' : 'nh-assistant.admin.agent-editor.id-hint') | translate }}\n </span>\n </p>\n </div>\n\n <div class=\"field\">\n <label [for]=\"id + '-name'\">{{ 'nh-assistant.admin.agent-editor.display-name' | translate }}</label>\n <input\n type=\"text\"\n [id]=\"id + '-name'\"\n [value]=\"displayName()\"\n [attr.aria-invalid]=\"submitted() && nameMissing() ? true : null\"\n [attr.aria-describedby]=\"id + '-name-hint'\"\n (input)=\"displayName.set(value($event))\" />\n <p class=\"hint\" [id]=\"id + '-name-hint'\">\n @if (submitted() && nameMissing()) {\n <span class=\"error-text\">{{ 'nh-assistant.admin.required' | translate }}</span>\n }\n </p>\n </div>\n </div>\n\n @if (choosesUse()) {\n <fieldset class=\"subsection\" data-agent-use>\n <legend>{{ 'nh-assistant.admin.agent-editor.use.title' | translate }}</legend>\n <div class=\"uses\">\n @for (option of uses; track option) {\n <label class=\"use\" [class.selected]=\"use() === option\">\n <input type=\"radio\" [name]=\"id + '-use'\" [value]=\"option\" [checked]=\"use() === option\" (change)=\"use.set(option)\" />\n <span>\n <strong>{{ 'nh-assistant.admin.agent-editor.use.' + option | translate }}</strong>\n <span class=\"muted\">{{ 'nh-assistant.admin.agent-editor.use.' + option + '-hint' | translate }}</span>\n </span>\n </label>\n }\n </div>\n @if (isNew() && use() !== 'chat') {\n <p class=\"hint\">{{ 'nh-assistant.admin.agent-editor.use.next' | translate }}</p>\n }\n @if (isNew()) {\n <label class=\"check described\" data-switch-on-now>\n <input type=\"checkbox\" [checked]=\"switchOnNow()\" (change)=\"switchOnNow.set(checked($event))\" />\n <span>\n <strong>{{ 'nh-assistant.admin.agent-editor.switch-on-now' | translate }}</strong>\n <span class=\"muted\">{{ 'nh-assistant.admin.agent-editor.switch-on-now-hint' | translate }}</span>\n </span>\n </label>\n }\n </fieldset>\n\n @if (catalogUnavailable()) {\n <!-- Without the run catalog the kinds of objects and policies are unknown: what the agent works on stays as it is. -->\n <p class=\"message warning\" role=\"note\" data-catalog-unavailable>\n <nh-assistant-icon name=\"warning\" />{{ 'nh-assistant.admin.agent-editor.catalog-unavailable' | translate }}\n </p>\n } @else {\n <fieldset\n class=\"subsection\"\n data-works-on\n [attr.aria-describedby]=\"id + '-works-on-hint ' + id + '-works-on-error'\"\n [attr.aria-invalid]=\"(submitted() && worksOnMissing()) || fieldError('worksOn') ? true : null\">\n <legend>{{ 'nh-assistant.admin.agent-editor.works-on.title' | translate }}</legend>\n <p class=\"muted\" [id]=\"id + '-works-on-hint'\">{{ 'nh-assistant.admin.agent-editor.works-on.hint' | translate }}</p>\n <div class=\"uses\">\n <label class=\"use\" [class.selected]=\"worksOnScope() === 'all'\">\n <input type=\"radio\" [name]=\"id + '-works-on'\" value=\"all\" [checked]=\"worksOnScope() === 'all'\" (change)=\"worksOnScope.set('all')\" />\n <span>\n <strong>{{ 'nh-assistant.admin.agent-editor.works-on.all' | translate }}</strong>\n <span class=\"muted\">{{ 'nh-assistant.admin.agent-editor.works-on.all-hint' | translate }}</span>\n </span>\n </label>\n <label class=\"use\" [class.selected]=\"worksOnScope() === 'types'\">\n <input type=\"radio\" [name]=\"id + '-works-on'\" value=\"types\" [checked]=\"worksOnScope() === 'types'\" (change)=\"worksOnScope.set('types')\" />\n <span>\n <strong>{{ 'nh-assistant.admin.agent-editor.works-on.types' | translate }}</strong>\n <span class=\"muted\">{{ 'nh-assistant.admin.agent-editor.works-on.types-hint' | translate }}</span>\n </span>\n </label>\n </div>\n\n @if (worksOnScope() === 'types') {\n <div class=\"checks\" role=\"group\" [attr.aria-label]=\"'nh-assistant.admin.agent-editor.works-on.types' | translate\" data-works-on-types>\n @for (type of subjectTypes(); track type.id) {\n <label class=\"check\">\n <input type=\"checkbox\" [checked]=\"worksOnTypes().includes(type.id)\" (change)=\"toggleWorksOnType(type.id, checked($event))\" />\n <span>{{ type.pluralName ?? type.name }} <span class=\"mono muted\">{{ type.id }}</span></span>\n </label>\n } @empty {\n <p class=\"muted\">{{ 'nh-assistant.admin.agent-editor.works-on.no-types' | translate }}</p>\n }\n </div>\n @if (fieldError('worksOnSubjectTypes'); as key) {\n <p class=\"hint\"><span class=\"error-text\">{{ key | translate }}</span></p>\n } @else if (tooManyTypes()) {\n <p class=\"hint\"><span class=\"error-text\">{{ 'nh-assistant.admin.agent-editor.works-on.too-many' | translate }}</span></p>\n }\n }\n\n @if (knownTypes().length > 0) {\n <p class=\"hint\" data-known-types>{{ 'nh-assistant.admin.agent-editor.works-on.known' | translate: { types: knownTypes().join(', ') } }}</p>\n }\n\n <label class=\"check described\">\n <input type=\"checkbox\" [checked]=\"worksOnApplication()\" (change)=\"worksOnApplication.set(checked($event))\" />\n <span>\n <strong>{{ 'nh-assistant.admin.agent-editor.works-on.application' | translate }}</strong>\n <span class=\"muted\">{{ 'nh-assistant.admin.agent-editor.works-on.application-hint' | translate }}</span>\n </span>\n </label>\n\n @if (divisionsListed()) {\n <label class=\"check described\" data-per-division>\n <input\n type=\"checkbox\"\n [checked]=\"perDivision()\"\n [attr.aria-invalid]=\"fieldError('perDivision') ? true : null\"\n [attr.aria-describedby]=\"fieldError('perDivision') ? id + '-per-division-error' : null\"\n (change)=\"perDivision.set(checked($event))\" />\n <span>\n <strong>{{ 'nh-assistant.admin.agent-editor.works-on.per-division' | translate }}</strong>\n <span class=\"muted\">{{ 'nh-assistant.admin.agent-editor.works-on.per-division-hint' | translate }}</span>\n </span>\n </label>\n @if (fieldError('perDivision'); as key) {\n <p class=\"hint\" [id]=\"id + '-per-division-error'\"><span class=\"error-text\">{{ key | translate }}</span></p>\n }\n }\n\n <p class=\"hint\" [id]=\"id + '-works-on-error'\" aria-live=\"polite\">\n @if (submitted() && worksOnMissing()) {\n <span class=\"error-text\">{{ 'nh-assistant.admin.agent-editor.works-on.missing' | translate }}</span>\n } @else if (fieldError('worksOn'); as key) {\n <span class=\"error-text\">{{ key | translate }}</span>\n }\n </p>\n\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-run-policy'\">{{ 'nh-assistant.admin.agent-editor.works-on.run-policy' | translate }}</label>\n <select\n [id]=\"id + '-run-policy'\"\n [attr.aria-describedby]=\"id + '-run-policy-hint'\"\n [attr.aria-invalid]=\"fieldError('runPolicy') ? true : null\"\n (change)=\"runPolicy.set(value($event))\">\n <option value=\"\" [selected]=\"runPolicy() === ''\">{{ 'nh-assistant.admin.agent-editor.works-on.run-policy-default' | translate }}</option>\n @for (policy of agentPolicies(); track policy.name) {\n <option [value]=\"policy.name\" [selected]=\"runPolicy() === policy.name\">{{ policyKeys(policy.name) | nhAssistantTranslate: policy.description }}</option>\n }\n </select>\n <p class=\"hint\" [id]=\"id + '-run-policy-hint'\">\n @if (fieldError('runPolicy'); as key) {\n <span class=\"error-text\">{{ key | translate }}</span>\n } @else {\n {{ 'nh-assistant.admin.agent-editor.works-on.run-policy-hint' | translate }}\n }\n </p>\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-view-policy'\">{{ 'nh-assistant.admin.agent-editor.works-on.view-policy' | translate }}</label>\n <select\n [id]=\"id + '-view-policy'\"\n [attr.aria-describedby]=\"id + '-view-policy-hint'\"\n [attr.aria-invalid]=\"fieldError('viewPolicy') ? true : null\"\n (change)=\"viewPolicy.set(value($event))\">\n <option value=\"\" [selected]=\"viewPolicy() === ''\">{{ 'nh-assistant.admin.agent-editor.works-on.view-policy-default' | translate }}</option>\n @for (policy of agentPolicies(); track policy.name) {\n <option [value]=\"policy.name\" [selected]=\"viewPolicy() === policy.name\">{{ policyKeys(policy.name) | nhAssistantTranslate: policy.description }}</option>\n }\n </select>\n <p class=\"hint\" [id]=\"id + '-view-policy-hint'\">\n @if (fieldError('viewPolicy'); as key) {\n <span class=\"error-text\">{{ key | translate }}</span>\n } @else {\n {{ 'nh-assistant.admin.agent-editor.works-on.view-policy-hint' | translate }}\n }\n </p>\n </div>\n </div>\n </fieldset>\n }\n }\n\n @if (agentGroups().length > 0 && !catalogUnavailable()) {\n <fieldset\n class=\"subsection\"\n data-agent-groups\n [attr.aria-describedby]=\"id + '-groups-hint ' + id + '-groups-error'\"\n [attr.aria-invalid]=\"groupsError() ? true : null\">\n <legend>{{ 'nh-assistant.admin.agent-editor.groups.title' | translate }}</legend>\n <p class=\"muted\" [id]=\"id + '-groups-hint'\">{{ 'nh-assistant.admin.agent-editor.groups.hint' | translate }}</p>\n @if (isVariant()) {\n <p class=\"hint\" data-groups-variant>{{ 'nh-assistant.admin.agent-editor.groups.shadow-variant' | translate }}</p>\n } @else {\n <div class=\"checks\" role=\"group\" [attr.aria-label]=\"'nh-assistant.admin.agent-editor.groups.title' | translate\">\n @for (group of agentGroups(); track group.id) {\n <label class=\"check described\" [attr.data-agent-group]=\"group.id\">\n <input type=\"checkbox\" [checked]=\"groups().includes(group.id)\" (change)=\"toggleGroup(group.id, checked($event))\" />\n <span>\n <strong>{{ group.name }}</strong>\n @if (group.description; as description) {\n <span class=\"muted\">{{ description }}</span>\n }\n @if (codeGroups().includes(group.id)) {\n <span class=\"muted\" data-group-in-code>{{ 'nh-assistant.admin.agent-editor.groups.in-code' | translate }}</span>\n }\n </span>\n </label>\n }\n </div>\n }\n <p class=\"hint\" [id]=\"id + '-groups-error'\" aria-live=\"polite\">\n @if (groupsError(); as key) {\n <span class=\"error-text\">{{ key | translate }}</span>\n }\n </p>\n </fieldset>\n }\n\n <div class=\"field\">\n <label [for]=\"id + '-description'\">{{ 'nh-assistant.admin.agent-editor.description' | translate }}</label>\n <input type=\"text\" [id]=\"id + '-description'\" [value]=\"description()\" (input)=\"description.set(value($event))\" />\n </div>\n\n <div class=\"field\">\n <label [for]=\"id + '-instructions'\">{{ 'nh-assistant.admin.agent-editor.instructions' | translate }}</label>\n <textarea\n rows=\"8\"\n [id]=\"id + '-instructions'\"\n [value]=\"instructions()\"\n [attr.aria-describedby]=\"id + '-instructions-hint'\"\n [attr.aria-invalid]=\"tooLong() ? true : null\"\n (input)=\"instructions.set(value($event))\"></textarea>\n <p class=\"hint\" [id]=\"id + '-instructions-hint'\">\n <span [class.error-text]=\"tooLong()\">\n {{ (tooLong() ? 'nh-assistant.admin.too-long' : 'nh-assistant.admin.agent-editor.instructions-hint') | translate }}\n </span>\n <span class=\"counter\" [class.error-text]=\"tooLong()\">{{ 'nh-assistant.admin.characters' | translate: { count: instructions().length, max: max } }}</span>\n </p>\n </div>\n\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-autonomy'\">{{ 'nh-assistant.admin.agent-editor.autonomy' | translate }}</label>\n <select [id]=\"id + '-autonomy'\" [attr.aria-describedby]=\"id + '-autonomy-hint'\" (change)=\"autonomy.set($any(value($event)))\">\n @for (level of autonomyLevels; track level) {\n <option [value]=\"level\" [selected]=\"autonomy() === level\">{{ 'nh-assistant.admin.agents.autonomy.' + level | translate }}</option>\n }\n </select>\n <p class=\"hint\" [id]=\"id + '-autonomy-hint'\">{{ 'nh-assistant.admin.agent-editor.autonomy-hint' | translate }}</p>\n </div>\n\n <div class=\"field\">\n <label [for]=\"id + '-policy'\">{{ 'nh-assistant.admin.agent-editor.required-policy' | translate }}</label>\n <input\n type=\"text\"\n class=\"mono\"\n autocomplete=\"off\"\n [id]=\"id + '-policy'\"\n [value]=\"requiredPolicy()\"\n [attr.aria-describedby]=\"id + '-policy-hint'\"\n (input)=\"requiredPolicy.set(value($event))\" />\n <p class=\"hint\" [id]=\"id + '-policy-hint'\">{{ 'nh-assistant.admin.agent-editor.required-policy-hint' | translate }}</p>\n </div>\n\n @if (choosesProfile()) {\n <div class=\"field\" data-agent-profile>\n <label [for]=\"id + '-profile'\">{{ 'nh-assistant.admin.agent-editor.profile' | translate }}</label>\n <select\n [id]=\"id + '-profile'\"\n [attr.aria-describedby]=\"id + '-profile-hint' + (fieldError('profile') ? ' ' + id + '-profile-error' : '')\"\n [attr.aria-invalid]=\"fieldError('profile') ? true : null\"\n (change)=\"profile.set(value($event))\">\n @if (isNew()) {\n <option value=\"\" [selected]=\"profile() === ''\">{{ 'nh-assistant.admin.agent-editor.profile-default' | translate }}</option>\n }\n @for (name of profileChoices(); track name) {\n <option [value]=\"name\" [selected]=\"profile() === name\">{{ name }}</option>\n }\n </select>\n <p class=\"hint\" [id]=\"id + '-profile-hint'\">{{ 'nh-assistant.admin.agent-editor.profile-hint' | translate }}</p>\n @if (fieldError('profile'); as key) {\n <p class=\"hint\" [id]=\"id + '-profile-error'\"><span class=\"error-text\">{{ key | translate }}</span></p>\n }\n </div>\n }\n\n @if (choosesUse()) {\n <div class=\"field\" data-agent-classification>\n <label [for]=\"id + '-classification'\">{{ 'nh-assistant.admin.agent-editor.classification' | translate }}</label>\n <select\n [id]=\"id + '-classification'\"\n [attr.aria-describedby]=\"id + '-classification-hint' + (fieldError('dataClassification') ? ' ' + id + '-classification-error' : '')\"\n [attr.aria-invalid]=\"fieldError('dataClassification') ? true : null\"\n (change)=\"dataClassification.set($any(value($event)))\">\n @for (classification of classifications; track classification) {\n <option [value]=\"classification\" [selected]=\"dataClassification() === classification\">\n {{ 'nh-assistant.admin.run-config.skills.classification.' + classification | translate }}\n </option>\n }\n </select>\n <p class=\"hint\" [id]=\"id + '-classification-hint'\">{{ 'nh-assistant.admin.agent-editor.classification-hint' | translate }}</p>\n @for (warning of warnings(); track warning) {\n <p class=\"message warning\" [attr.data-agent-editor-warning]=\"warning\">\n <nh-assistant-icon name=\"warning\" />{{ warningKeys('agent', warning) | nhAssistantTranslate: warning }}\n </p>\n }\n @if (fieldError('dataClassification'); as key) {\n <p class=\"hint\" [id]=\"id + '-classification-error'\"><span class=\"error-text\">{{ key | translate }}</span></p>\n }\n </div>\n }\n </div>\n\n <p class=\"hint\">{{ 'nh-assistant.admin.agent-editor.state-hint' | translate }}</p>\n\n <fieldset class=\"subsection\">\n <legend>{{ 'nh-assistant.admin.agent-editor.tool-selectors' | translate }}</legend>\n <p class=\"muted\">{{ 'nh-assistant.admin.agent-editor.tool-selectors-hint' | translate }}</p>\n\n @if (toolSelectors().length === 0) {\n <p class=\"muted\">{{ 'nh-assistant.admin.agent-editor.no-selectors' | translate }}</p>\n } @else {\n <ul class=\"chips\">\n @for (selector of toolSelectors(); track selector) {\n <li class=\"chip\">\n <span>{{ selector }}</span>\n <button\n type=\"button\"\n [attr.aria-label]=\"'nh-assistant.admin.agent-editor.remove-selector' | translate: { selector: selector }\"\n [attr.title]=\"'nh-assistant.admin.agent-editor.remove-selector' | translate: { selector: selector }\"\n (click)=\"removeSelector(selector)\">\n <nh-assistant-icon name=\"x\" />\n </button>\n </li>\n }\n </ul>\n <p class=\"muted\" role=\"status\">{{ 'nh-assistant.admin.agent-editor.matches' | translate: { count: matchCount() } }}</p>\n }\n\n <div class=\"inline\">\n <input\n type=\"text\"\n class=\"mono\"\n autocomplete=\"off\"\n [attr.aria-label]=\"'nh-assistant.admin.agent-editor.add-selector' | translate\"\n [attr.placeholder]=\"'nh-assistant.admin.agent-editor.selector-placeholder' | translate\"\n [attr.aria-invalid]=\"selectorInvalid() ? true : null\"\n [value]=\"newSelector()\"\n (input)=\"newSelector.set(value($event))\"\n (keydown)=\"onSelectorKeydown($event)\" />\n <button class=\"secondary\" type=\"button\" [disabled]=\"newSelector().trim().length === 0 || selectorInvalid()\" (click)=\"addSelector()\">\n <nh-assistant-icon name=\"plus\" />{{ 'nh-assistant.admin.agent-editor.add-selector' | translate }}\n </button>\n </div>\n\n <div class=\"field\">\n <label class=\"label\" [for]=\"id + '-catalog-filter'\">{{ 'nh-assistant.admin.agent-editor.catalog' | translate }}</label>\n <input\n type=\"search\"\n [id]=\"id + '-catalog-filter'\"\n [attr.placeholder]=\"'nh-assistant.admin.agent-editor.catalog-filter' | translate\"\n [value]=\"catalogFilter()\"\n (input)=\"catalogFilter.set(value($event))\" />\n <ul class=\"catalog\" [attr.aria-label]=\"'nh-assistant.admin.agent-editor.catalog' | translate\">\n @for (tool of filteredTools(); track tool.id) {\n <li>\n <label>\n <input\n type=\"checkbox\"\n [checked]=\"isSelected(tool.id) || isCovered(tool.id)\"\n [disabled]=\"isCovered(tool.id)\"\n (change)=\"toggleTool(tool.id, checked($event))\" />\n <span class=\"tool-id\">{{ tool.id }}</span>\n <span class=\"badge\">{{ 'nh-assistant.admin.source.' + tool.source | translate }}</span>\n <span class=\"badge\" [class.warning]=\"tool.effect !== 'read-only'\">{{ 'nh-assistant.admin.effect.' + tool.effect | translate }}</span>\n <span class=\"muted\">{{ tool.description }}</span>\n </label>\n </li>\n } @empty {\n <li class=\"muted\">{{ 'nh-assistant.admin.agent-editor.catalog-empty' | translate }}</li>\n }\n </ul>\n </div>\n </fieldset>\n\n <fieldset class=\"subsection\">\n <legend>{{ 'nh-assistant.admin.agent-editor.mcp-servers' | translate }}</legend>\n <p class=\"muted\">{{ 'nh-assistant.admin.agent-editor.mcp-servers-hint' | translate }}</p>\n @for (server of servers(); track server.id) {\n <label class=\"check\">\n <input type=\"checkbox\" [checked]=\"mcpServerIds().includes(server.id)\" (change)=\"toggleServer(server.id, checked($event))\" />\n <span>{{ server.displayName }} <span class=\"mono muted\">{{ server.id }}</span></span>\n </label>\n } @empty {\n <p class=\"muted\">{{ 'nh-assistant.admin.agent-editor.mcp-servers-empty' | translate }}</p>\n }\n </fieldset>\n\n @if (error(); as failure) {\n <p class=\"message error\" role=\"alert\"><nh-assistant-icon name=\"warning\" />{{ errorKeys() | nhAssistantTranslate: failure.code }}</p>\n }\n\n <div class=\"actions\">\n <button class=\"secondary\" type=\"button\" (click)=\"cancelled.emit()\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n <button class=\"primary\" type=\"submit\" [disabled]=\"saving() || tooLong()\">\n {{ (saving() ? 'nh-assistant.admin.saving' : 'nh-assistant.admin.save') | translate }}\n </button>\n </div>\n</form>\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;min-width:0;color:var(--nha-text);font-family:var(--nha-font);font-size:14px}:host fieldset{min-width:0;margin:0;border:0;padding:0}:host legend,:host .label,:host .field>label{display:block;margin-bottom:6px;padding:0;color:var(--nha-text);font-size:12px;font-weight:700}:host input[type=text],:host input[type=url],:host input[type=password],:host input[type=search],:host input[type=email],:host input[type=number],:host input[type=date],:host input[type=time],:host input[type=datetime-local],:host select,:host textarea{box-sizing:border-box;width:100%;min-width:0;min-height:38px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:8px 10px;color:var(--nha-text);background:var(--nha-background);font:inherit;font-size:13px}:host input[type=text]:focus-visible,:host input[type=url]:focus-visible,:host input[type=password]:focus-visible,:host input[type=search]:focus-visible,:host input[type=email]:focus-visible,:host input[type=number]:focus-visible,:host input[type=date]:focus-visible,:host input[type=time]:focus-visible,:host input[type=datetime-local]:focus-visible,:host select:focus-visible,:host textarea:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host input[type=text]:disabled,:host input[type=url]:disabled,:host input[type=password]:disabled,:host input[type=search]:disabled,:host input[type=email]:disabled,:host input[type=number]:disabled,:host input[type=date]:disabled,:host input[type=time]:disabled,:host input[type=datetime-local]:disabled,:host select:disabled,:host textarea:disabled{cursor:not-allowed;opacity:.6}:host input[type=text][aria-invalid=true],:host input[type=url][aria-invalid=true],:host input[type=password][aria-invalid=true],:host input[type=search][aria-invalid=true],:host input[type=email][aria-invalid=true],:host input[type=number][aria-invalid=true],:host input[type=date][aria-invalid=true],:host input[type=time][aria-invalid=true],:host input[type=datetime-local][aria-invalid=true],:host select[aria-invalid=true],:host textarea[aria-invalid=true]{border-color:var(--nha-danger)}:host textarea{line-height:1.45;resize:vertical}:host input[type=number]{font-variant-numeric:tabular-nums}:host .mono{font-family:var(--nha-mono);font-size:12px}:host .field{display:flex;flex-direction:column;min-width:0}:host .hint{display:flex;justify-content:space-between;gap:8px;margin:4px 2px 0;color:var(--nha-text-muted);font-size:11px}:host .counter{flex:none;font-variant-numeric:tabular-nums}:host .error-text{color:var(--nha-danger)}:host .field-error{display:flex;align-items:flex-start;gap:6px;margin:4px 2px 0;color:var(--nha-danger);font-size:12px;font-weight:600;overflow-wrap:anywhere}:host .field-error nh-assistant-icon{flex:none;margin-top:1px}:host .form-error{box-sizing:border-box;display:flex;align-items:flex-start;gap:8px;margin:0;border:1px solid var(--nha-danger);border-radius:var(--nha-radius-control);padding:8px 12px;color:var(--nha-text);background:var(--nha-danger-soft);font-size:13px;overflow-wrap:anywhere}:host .form-error ul{margin:4px 0 0;padding-left:18px}:host .message{box-sizing:border-box;display:flex;align-items:center;gap:8px;margin:0;border:1px solid transparent;border-radius:var(--nha-radius-control);padding:8px 12px;font-size:13px;overflow-wrap:anywhere}:host .message.error{border-color:var(--nha-danger);color:var(--nha-text);background:var(--nha-danger-soft)}:host .message.warning{border-color:var(--nha-warning);color:var(--nha-text);background:var(--nha-warning-soft)}:host .message.success{border-color:var(--nha-success);color:var(--nha-text);background:var(--nha-success-soft)}:host .message.info{border-color:var(--nha-border);color:var(--nha-text);background:var(--nha-surface)}:host .message nh-assistant-icon{flex:none}:host button.link{min-height:0;border:0;padding:0;color:var(--nha-accent, currentColor);background:none;font:inherit;text-decoration:underline;cursor:pointer}:host button.link:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host button.link:disabled{cursor:not-allowed;opacity:.55}:host .primary,:host .secondary,:host .danger{display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;gap:6px;min-height:36px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px 14px;font:inherit;font-size:13px;font-weight:700;white-space:nowrap;cursor:pointer}:host .primary:focus-visible,:host .secondary:focus-visible,:host .danger:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .primary:disabled,:host .secondary:disabled,:host .danger:disabled{cursor:not-allowed;opacity:.55}:host .primary{border-color:var(--nha-accent);color:var(--nha-accent-contrast);background:var(--nha-accent)}:host .primary:hover:not(:disabled){filter:brightness(1.12)}:host .secondary{color:var(--nha-text);background:transparent}:host .secondary:hover:not(:disabled){background:var(--nha-surface-strong)}:host .danger{border-color:var(--nha-danger);color:var(--nha-danger);background:transparent}:host .danger:hover:not(:disabled){background:var(--nha-danger-soft)}:host .icon-button{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:1px solid transparent;border-radius:var(--nha-radius-control);color:var(--nha-text-muted);background:transparent;cursor:pointer}:host .icon-button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .icon-button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}:host .icon-button:disabled{cursor:not-allowed;opacity:.5}:host .actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px}:host .badge{display:inline-flex;align-items:center;gap:4px;border-radius:999px;padding:2px 8px;color:var(--nha-text-muted);background:var(--nha-surface-strong);font-size:11px;font-weight:700;white-space:nowrap}:host .badge.success{color:var(--nha-success);background:var(--nha-success-soft)}:host .badge.warning{color:var(--nha-warning);background:var(--nha-warning-soft)}:host .badge.danger{color:var(--nha-danger);background:var(--nha-danger-soft)}:host .visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}.section{display:flex;flex-direction:column;gap:16px;min-width:0}.section-header{display:flex;flex-wrap:wrap;align-items:flex-start;justify-content:space-between;gap:12px}.section-header>div{min-width:0;max-width:72ch}h3{margin:0 0 4px;font-size:17px;font-weight:750}h4{margin:0 0 8px;font-size:14px;font-weight:700}.muted{margin:0;color:var(--nha-text-muted);font-size:13px}.state{display:flex;flex-direction:column;align-items:flex-start;gap:10px;margin:0;border:1px dashed var(--nha-border);border-radius:var(--nha-radius);padding:20px;color:var(--nha-text-muted);font-size:13px}.state p{margin:0}.stack{display:flex;flex-direction:column;gap:14px;min-width:0}.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,240px),1fr));gap:14px}.meta{display:flex;flex-wrap:wrap;align-items:center;gap:8px 12px;color:var(--nha-text-muted);font-size:12px;overflow-wrap:anywhere}textarea.large{min-height:260px}.catalog-error{flex-wrap:wrap}.grow{flex:1;min-width:0}.subsection{display:flex;flex-direction:column;gap:8px;border-top:1px solid var(--nha-border);padding-top:14px}.history{display:grid;gap:6px;margin:0;padding:0;list-style:none}.history li{display:flex;flex-wrap:wrap;align-items:center;gap:6px 12px;font-size:12px}.cards{display:grid;gap:10px;margin:0;padding:0;list-style:none}.card{display:flex;flex-direction:column;gap:8px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius);padding:14px;background:var(--nha-background)}.card.inactive{opacity:.8}.card-header{display:flex;flex-wrap:wrap;align-items:center;gap:8px;min-width:0}.card-header strong{font-size:14px;overflow-wrap:anywhere}.card-actions{display:flex;flex-wrap:wrap;gap:8px}.check{display:flex;align-items:flex-start;gap:8px;font-size:13px;cursor:pointer}.check input{width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.check.described>span{display:grid;gap:2px;min-width:0;overflow-wrap:anywhere}.checks{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));gap:6px 12px;min-width:0;overflow-wrap:anywhere}.chips{display:flex;flex-wrap:wrap;gap:6px;margin:0;padding:0;list-style:none}.chip{display:inline-flex;align-items:center;gap:2px;border:1px solid var(--nha-border);border-radius:999px;padding:2px 2px 2px 10px;background:var(--nha-surface);font-family:var(--nha-mono);font-size:12px}.chip 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;width:26px;height:26px;border-radius:999px}.chip button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.chip button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}.chip button:disabled{cursor:not-allowed;opacity:.5}.inline{display:flex;flex-wrap:wrap;gap:8px}.inline>input{flex:1 1 180px}.catalog{display:grid;gap:4px;max-height:260px;margin:8px 0 0;overflow-y:auto;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px;list-style:none}.catalog li label{display:flex;flex-wrap:wrap;align-items:center;gap:6px 8px;border-radius:8px;padding:6px;cursor:pointer}.catalog li label:hover{background:var(--nha-surface)}.catalog .tool-id{font-family:var(--nha-mono);font-size:12px;overflow-wrap:anywhere}.skills{display:grid;gap:8px;margin:0;padding:0;list-style:none}.skill{display:grid;gap:6px;border-left:3px solid var(--nha-border);padding:4px 0 4px 12px}.uses{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));gap:8px}.use{display:flex;align-items:flex-start;gap:10px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:10px 12px;cursor:pointer;overflow-wrap:anywhere}.use input{flex:none;width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.use>span{display:grid;gap:2px;min-width:0}.use strong{font-size:13px}.use.selected{border-color:var(--nha-highlight);background:var(--nha-surface)}.use:focus-within{outline:2px solid var(--nha-highlight);outline-offset:2px}.discovery{display:grid;gap:8px;padding:10px 12px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control)}.discovery dl{display:grid;grid-template-columns:max-content minmax(0,1fr);gap:4px 12px;margin:0}.discovery dt{color:var(--nha-text-muted)}.discovery dd{margin:0;overflow-wrap:anywhere}\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 }); }
|
|
649
686
|
}
|
|
650
687
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: NhAssistantAdminAgentEditorComponent, decorators: [{
|
|
651
688
|
type: Component,
|
|
652
|
-
args: [{ selector: 'nh-assistant-admin-agent-editor', standalone: true, imports: [TranslatePipe, _NhAssistantTranslatePipe, _NhAssistantIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<form class=\"section card\" [attr.aria-labelledby]=\"id + '-title'\" [attr.aria-busy]=\"saving()\" (submit)=\"$event.preventDefault(); save()\">\n <h3 [id]=\"id + '-title'\">\n @if (agent(); as current) {\n {{ 'nh-assistant.admin.agent-editor.edit-title' | translate: { name: current.displayName } }}\n } @else {\n {{ 'nh-assistant.admin.agent-editor.new-title' | translate }}\n }\n </h3>\n\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-id'\">{{ 'nh-assistant.admin.agent-editor.id' | translate }}</label>\n <input\n type=\"text\"\n class=\"mono\"\n autocomplete=\"off\"\n [id]=\"id + '-id'\"\n [value]=\"agentId()\"\n [disabled]=\"!isNew()\"\n [attr.aria-describedby]=\"id + '-id-hint'\"\n [attr.aria-invalid]=\"submitted() && idInvalid() ? true : null\"\n (input)=\"agentId.set(value($event))\" />\n <p class=\"hint\" [id]=\"id + '-id-hint'\">\n <span [class.error-text]=\"submitted() && idInvalid()\">\n {{ (submitted() && idInvalid() ? 'nh-assistant.admin.invalid-id' : 'nh-assistant.admin.agent-editor.id-hint') | translate }}\n </span>\n </p>\n </div>\n\n <div class=\"field\">\n <label [for]=\"id + '-name'\">{{ 'nh-assistant.admin.agent-editor.display-name' | translate }}</label>\n <input\n type=\"text\"\n [id]=\"id + '-name'\"\n [value]=\"displayName()\"\n [attr.aria-invalid]=\"submitted() && nameMissing() ? true : null\"\n [attr.aria-describedby]=\"id + '-name-hint'\"\n (input)=\"displayName.set(value($event))\" />\n <p class=\"hint\" [id]=\"id + '-name-hint'\">\n @if (submitted() && nameMissing()) {\n <span class=\"error-text\">{{ 'nh-assistant.admin.required' | translate }}</span>\n }\n </p>\n </div>\n </div>\n\n @if (choosesUse()) {\n <fieldset class=\"subsection\" data-agent-use>\n <legend>{{ 'nh-assistant.admin.agent-editor.use.title' | translate }}</legend>\n <div class=\"uses\">\n @for (option of uses; track option) {\n <label class=\"use\" [class.selected]=\"use() === option\">\n <input type=\"radio\" [name]=\"id + '-use'\" [value]=\"option\" [checked]=\"use() === option\" (change)=\"use.set(option)\" />\n <span>\n <strong>{{ 'nh-assistant.admin.agent-editor.use.' + option | translate }}</strong>\n <span class=\"muted\">{{ 'nh-assistant.admin.agent-editor.use.' + option + '-hint' | translate }}</span>\n </span>\n </label>\n }\n </div>\n @if (isNew() && use() !== 'chat') {\n <p class=\"hint\">{{ 'nh-assistant.admin.agent-editor.use.next' | translate }}</p>\n }\n </fieldset>\n\n @if (catalogUnavailable()) {\n <!-- Without the run catalog the kinds of objects and policies are unknown: what the agent works on stays as it is. -->\n <p class=\"message warning\" role=\"note\" data-catalog-unavailable>\n <nh-assistant-icon name=\"warning\" />{{ 'nh-assistant.admin.agent-editor.catalog-unavailable' | translate }}\n </p>\n } @else {\n <fieldset\n class=\"subsection\"\n data-works-on\n [attr.aria-describedby]=\"id + '-works-on-hint ' + id + '-works-on-error'\"\n [attr.aria-invalid]=\"(submitted() && worksOnMissing()) || fieldError('worksOn') ? true : null\">\n <legend>{{ 'nh-assistant.admin.agent-editor.works-on.title' | translate }}</legend>\n <p class=\"muted\" [id]=\"id + '-works-on-hint'\">{{ 'nh-assistant.admin.agent-editor.works-on.hint' | translate }}</p>\n <div class=\"uses\">\n <label class=\"use\" [class.selected]=\"worksOnScope() === 'all'\">\n <input type=\"radio\" [name]=\"id + '-works-on'\" value=\"all\" [checked]=\"worksOnScope() === 'all'\" (change)=\"worksOnScope.set('all')\" />\n <span>\n <strong>{{ 'nh-assistant.admin.agent-editor.works-on.all' | translate }}</strong>\n <span class=\"muted\">{{ 'nh-assistant.admin.agent-editor.works-on.all-hint' | translate }}</span>\n </span>\n </label>\n <label class=\"use\" [class.selected]=\"worksOnScope() === 'types'\">\n <input type=\"radio\" [name]=\"id + '-works-on'\" value=\"types\" [checked]=\"worksOnScope() === 'types'\" (change)=\"worksOnScope.set('types')\" />\n <span>\n <strong>{{ 'nh-assistant.admin.agent-editor.works-on.types' | translate }}</strong>\n <span class=\"muted\">{{ 'nh-assistant.admin.agent-editor.works-on.types-hint' | translate }}</span>\n </span>\n </label>\n </div>\n\n @if (worksOnScope() === 'types') {\n <div class=\"checks\" role=\"group\" [attr.aria-label]=\"'nh-assistant.admin.agent-editor.works-on.types' | translate\" data-works-on-types>\n @for (type of subjectTypes(); track type.id) {\n <label class=\"check\">\n <input type=\"checkbox\" [checked]=\"worksOnTypes().includes(type.id)\" (change)=\"toggleWorksOnType(type.id, checked($event))\" />\n <span>{{ type.pluralName ?? type.name }} <span class=\"mono muted\">{{ type.id }}</span></span>\n </label>\n } @empty {\n <p class=\"muted\">{{ 'nh-assistant.admin.agent-editor.works-on.no-types' | translate }}</p>\n }\n </div>\n @if (fieldError('worksOnSubjectTypes'); as key) {\n <p class=\"hint\"><span class=\"error-text\">{{ key | translate }}</span></p>\n } @else if (tooManyTypes()) {\n <p class=\"hint\"><span class=\"error-text\">{{ 'nh-assistant.admin.agent-editor.works-on.too-many' | translate }}</span></p>\n }\n }\n\n @if (knownTypes().length > 0) {\n <p class=\"hint\" data-known-types>{{ 'nh-assistant.admin.agent-editor.works-on.known' | translate: { types: knownTypes().join(', ') } }}</p>\n }\n\n <label class=\"check described\">\n <input type=\"checkbox\" [checked]=\"worksOnApplication()\" (change)=\"worksOnApplication.set(checked($event))\" />\n <span>\n <strong>{{ 'nh-assistant.admin.agent-editor.works-on.application' | translate }}</strong>\n <span class=\"muted\">{{ 'nh-assistant.admin.agent-editor.works-on.application-hint' | translate }}</span>\n </span>\n </label>\n\n @if (divisionsListed()) {\n <label class=\"check described\" data-per-division>\n <input\n type=\"checkbox\"\n [checked]=\"perDivision()\"\n [attr.aria-invalid]=\"fieldError('perDivision') ? true : null\"\n [attr.aria-describedby]=\"fieldError('perDivision') ? id + '-per-division-error' : null\"\n (change)=\"perDivision.set(checked($event))\" />\n <span>\n <strong>{{ 'nh-assistant.admin.agent-editor.works-on.per-division' | translate }}</strong>\n <span class=\"muted\">{{ 'nh-assistant.admin.agent-editor.works-on.per-division-hint' | translate }}</span>\n </span>\n </label>\n @if (fieldError('perDivision'); as key) {\n <p class=\"hint\" [id]=\"id + '-per-division-error'\"><span class=\"error-text\">{{ key | translate }}</span></p>\n }\n }\n\n <p class=\"hint\" [id]=\"id + '-works-on-error'\" aria-live=\"polite\">\n @if (submitted() && worksOnMissing()) {\n <span class=\"error-text\">{{ 'nh-assistant.admin.agent-editor.works-on.missing' | translate }}</span>\n } @else if (fieldError('worksOn'); as key) {\n <span class=\"error-text\">{{ key | translate }}</span>\n }\n </p>\n\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-run-policy'\">{{ 'nh-assistant.admin.agent-editor.works-on.run-policy' | translate }}</label>\n <select\n [id]=\"id + '-run-policy'\"\n [attr.aria-describedby]=\"id + '-run-policy-hint'\"\n [attr.aria-invalid]=\"fieldError('runPolicy') ? true : null\"\n (change)=\"runPolicy.set(value($event))\">\n <option value=\"\" [selected]=\"runPolicy() === ''\">{{ 'nh-assistant.admin.agent-editor.works-on.run-policy-default' | translate }}</option>\n @for (policy of agentPolicies(); track policy.name) {\n <option [value]=\"policy.name\" [selected]=\"runPolicy() === policy.name\">{{ policyKeys(policy.name) | nhAssistantTranslate: policy.description }}</option>\n }\n </select>\n <p class=\"hint\" [id]=\"id + '-run-policy-hint'\">\n @if (fieldError('runPolicy'); as key) {\n <span class=\"error-text\">{{ key | translate }}</span>\n } @else {\n {{ 'nh-assistant.admin.agent-editor.works-on.run-policy-hint' | translate }}\n }\n </p>\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-view-policy'\">{{ 'nh-assistant.admin.agent-editor.works-on.view-policy' | translate }}</label>\n <select\n [id]=\"id + '-view-policy'\"\n [attr.aria-describedby]=\"id + '-view-policy-hint'\"\n [attr.aria-invalid]=\"fieldError('viewPolicy') ? true : null\"\n (change)=\"viewPolicy.set(value($event))\">\n <option value=\"\" [selected]=\"viewPolicy() === ''\">{{ 'nh-assistant.admin.agent-editor.works-on.view-policy-default' | translate }}</option>\n @for (policy of agentPolicies(); track policy.name) {\n <option [value]=\"policy.name\" [selected]=\"viewPolicy() === policy.name\">{{ policyKeys(policy.name) | nhAssistantTranslate: policy.description }}</option>\n }\n </select>\n <p class=\"hint\" [id]=\"id + '-view-policy-hint'\">\n @if (fieldError('viewPolicy'); as key) {\n <span class=\"error-text\">{{ key | translate }}</span>\n } @else {\n {{ 'nh-assistant.admin.agent-editor.works-on.view-policy-hint' | translate }}\n }\n </p>\n </div>\n </div>\n </fieldset>\n }\n }\n\n @if (agentGroups().length > 0 && !catalogUnavailable()) {\n <fieldset\n class=\"subsection\"\n data-agent-groups\n [attr.aria-describedby]=\"id + '-groups-hint ' + id + '-groups-error'\"\n [attr.aria-invalid]=\"groupsError() ? true : null\">\n <legend>{{ 'nh-assistant.admin.agent-editor.groups.title' | translate }}</legend>\n <p class=\"muted\" [id]=\"id + '-groups-hint'\">{{ 'nh-assistant.admin.agent-editor.groups.hint' | translate }}</p>\n @if (isVariant()) {\n <p class=\"hint\" data-groups-variant>{{ 'nh-assistant.admin.agent-editor.groups.shadow-variant' | translate }}</p>\n } @else {\n <div class=\"checks\" role=\"group\" [attr.aria-label]=\"'nh-assistant.admin.agent-editor.groups.title' | translate\">\n @for (group of agentGroups(); track group.id) {\n <label class=\"check described\" [attr.data-agent-group]=\"group.id\">\n <input type=\"checkbox\" [checked]=\"groups().includes(group.id)\" (change)=\"toggleGroup(group.id, checked($event))\" />\n <span>\n <strong>{{ group.name }}</strong>\n @if (group.description; as description) {\n <span class=\"muted\">{{ description }}</span>\n }\n @if (codeGroups().includes(group.id)) {\n <span class=\"muted\" data-group-in-code>{{ 'nh-assistant.admin.agent-editor.groups.in-code' | translate }}</span>\n }\n </span>\n </label>\n }\n </div>\n }\n <p class=\"hint\" [id]=\"id + '-groups-error'\" aria-live=\"polite\">\n @if (groupsError(); as key) {\n <span class=\"error-text\">{{ key | translate }}</span>\n }\n </p>\n </fieldset>\n }\n\n <div class=\"field\">\n <label [for]=\"id + '-description'\">{{ 'nh-assistant.admin.agent-editor.description' | translate }}</label>\n <input type=\"text\" [id]=\"id + '-description'\" [value]=\"description()\" (input)=\"description.set(value($event))\" />\n </div>\n\n <div class=\"field\">\n <label [for]=\"id + '-instructions'\">{{ 'nh-assistant.admin.agent-editor.instructions' | translate }}</label>\n <textarea\n rows=\"8\"\n [id]=\"id + '-instructions'\"\n [value]=\"instructions()\"\n [attr.aria-describedby]=\"id + '-instructions-hint'\"\n [attr.aria-invalid]=\"tooLong() ? true : null\"\n (input)=\"instructions.set(value($event))\"></textarea>\n <p class=\"hint\" [id]=\"id + '-instructions-hint'\">\n <span [class.error-text]=\"tooLong()\">\n {{ (tooLong() ? 'nh-assistant.admin.too-long' : 'nh-assistant.admin.agent-editor.instructions-hint') | translate }}\n </span>\n <span class=\"counter\" [class.error-text]=\"tooLong()\">{{ 'nh-assistant.admin.characters' | translate: { count: instructions().length, max: max } }}</span>\n </p>\n </div>\n\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-autonomy'\">{{ 'nh-assistant.admin.agent-editor.autonomy' | translate }}</label>\n <select [id]=\"id + '-autonomy'\" [attr.aria-describedby]=\"id + '-autonomy-hint'\" (change)=\"autonomy.set($any(value($event)))\">\n @for (level of autonomyLevels; track level) {\n <option [value]=\"level\" [selected]=\"autonomy() === level\">{{ 'nh-assistant.admin.agents.autonomy.' + level | translate }}</option>\n }\n </select>\n <p class=\"hint\" [id]=\"id + '-autonomy-hint'\">{{ 'nh-assistant.admin.agent-editor.autonomy-hint' | translate }}</p>\n </div>\n\n <div class=\"field\">\n <label [for]=\"id + '-policy'\">{{ 'nh-assistant.admin.agent-editor.required-policy' | translate }}</label>\n <input\n type=\"text\"\n class=\"mono\"\n autocomplete=\"off\"\n [id]=\"id + '-policy'\"\n [value]=\"requiredPolicy()\"\n [attr.aria-describedby]=\"id + '-policy-hint'\"\n (input)=\"requiredPolicy.set(value($event))\" />\n <p class=\"hint\" [id]=\"id + '-policy-hint'\">{{ 'nh-assistant.admin.agent-editor.required-policy-hint' | translate }}</p>\n </div>\n </div>\n\n <p class=\"hint\">{{ 'nh-assistant.admin.agent-editor.state-hint' | translate }}</p>\n\n <fieldset class=\"subsection\">\n <legend>{{ 'nh-assistant.admin.agent-editor.tool-selectors' | translate }}</legend>\n <p class=\"muted\">{{ 'nh-assistant.admin.agent-editor.tool-selectors-hint' | translate }}</p>\n\n @if (toolSelectors().length === 0) {\n <p class=\"muted\">{{ 'nh-assistant.admin.agent-editor.no-selectors' | translate }}</p>\n } @else {\n <ul class=\"chips\">\n @for (selector of toolSelectors(); track selector) {\n <li class=\"chip\">\n <span>{{ selector }}</span>\n <button\n type=\"button\"\n [attr.aria-label]=\"'nh-assistant.admin.agent-editor.remove-selector' | translate: { selector: selector }\"\n [attr.title]=\"'nh-assistant.admin.agent-editor.remove-selector' | translate: { selector: selector }\"\n (click)=\"removeSelector(selector)\">\n <nh-assistant-icon name=\"x\" />\n </button>\n </li>\n }\n </ul>\n <p class=\"muted\" role=\"status\">{{ 'nh-assistant.admin.agent-editor.matches' | translate: { count: matchCount() } }}</p>\n }\n\n <div class=\"inline\">\n <input\n type=\"text\"\n class=\"mono\"\n autocomplete=\"off\"\n [attr.aria-label]=\"'nh-assistant.admin.agent-editor.add-selector' | translate\"\n [attr.placeholder]=\"'nh-assistant.admin.agent-editor.selector-placeholder' | translate\"\n [attr.aria-invalid]=\"selectorInvalid() ? true : null\"\n [value]=\"newSelector()\"\n (input)=\"newSelector.set(value($event))\"\n (keydown)=\"onSelectorKeydown($event)\" />\n <button class=\"secondary\" type=\"button\" [disabled]=\"newSelector().trim().length === 0 || selectorInvalid()\" (click)=\"addSelector()\">\n <nh-assistant-icon name=\"plus\" />{{ 'nh-assistant.admin.agent-editor.add-selector' | translate }}\n </button>\n </div>\n\n <div class=\"field\">\n <label class=\"label\" [for]=\"id + '-catalog-filter'\">{{ 'nh-assistant.admin.agent-editor.catalog' | translate }}</label>\n <input\n type=\"search\"\n [id]=\"id + '-catalog-filter'\"\n [attr.placeholder]=\"'nh-assistant.admin.agent-editor.catalog-filter' | translate\"\n [value]=\"catalogFilter()\"\n (input)=\"catalogFilter.set(value($event))\" />\n <ul class=\"catalog\" [attr.aria-label]=\"'nh-assistant.admin.agent-editor.catalog' | translate\">\n @for (tool of filteredTools(); track tool.id) {\n <li>\n <label>\n <input\n type=\"checkbox\"\n [checked]=\"isSelected(tool.id) || isCovered(tool.id)\"\n [disabled]=\"isCovered(tool.id)\"\n (change)=\"toggleTool(tool.id, checked($event))\" />\n <span class=\"tool-id\">{{ tool.id }}</span>\n <span class=\"badge\">{{ 'nh-assistant.admin.source.' + tool.source | translate }}</span>\n <span class=\"badge\" [class.warning]=\"tool.effect !== 'read-only'\">{{ 'nh-assistant.admin.effect.' + tool.effect | translate }}</span>\n <span class=\"muted\">{{ tool.description }}</span>\n </label>\n </li>\n } @empty {\n <li class=\"muted\">{{ 'nh-assistant.admin.agent-editor.catalog-empty' | translate }}</li>\n }\n </ul>\n </div>\n </fieldset>\n\n <fieldset class=\"subsection\">\n <legend>{{ 'nh-assistant.admin.agent-editor.mcp-servers' | translate }}</legend>\n <p class=\"muted\">{{ 'nh-assistant.admin.agent-editor.mcp-servers-hint' | translate }}</p>\n @for (server of servers(); track server.id) {\n <label class=\"check\">\n <input type=\"checkbox\" [checked]=\"mcpServerIds().includes(server.id)\" (change)=\"toggleServer(server.id, checked($event))\" />\n <span>{{ server.displayName }} <span class=\"mono muted\">{{ server.id }}</span></span>\n </label>\n } @empty {\n <p class=\"muted\">{{ 'nh-assistant.admin.agent-editor.mcp-servers-empty' | translate }}</p>\n }\n </fieldset>\n\n @if (error(); as failure) {\n <p class=\"message error\" role=\"alert\"><nh-assistant-icon name=\"warning\" />{{ errorKeys() | nhAssistantTranslate: failure.code }}</p>\n }\n\n <div class=\"actions\">\n <button class=\"secondary\" type=\"button\" (click)=\"cancelled.emit()\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n <button class=\"primary\" type=\"submit\" [disabled]=\"saving() || tooLong()\">\n {{ (saving() ? 'nh-assistant.admin.saving' : 'nh-assistant.admin.save') | translate }}\n </button>\n </div>\n</form>\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;min-width:0;color:var(--nha-text);font-family:var(--nha-font);font-size:14px}:host fieldset{min-width:0;margin:0;border:0;padding:0}:host legend,:host .label,:host .field>label{display:block;margin-bottom:6px;padding:0;color:var(--nha-text);font-size:12px;font-weight:700}:host input[type=text],:host input[type=url],:host input[type=password],:host input[type=search],:host input[type=email],:host input[type=number],:host input[type=date],:host input[type=time],:host input[type=datetime-local],:host select,:host textarea{box-sizing:border-box;width:100%;min-width:0;min-height:38px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:8px 10px;color:var(--nha-text);background:var(--nha-background);font:inherit;font-size:13px}:host input[type=text]:focus-visible,:host input[type=url]:focus-visible,:host input[type=password]:focus-visible,:host input[type=search]:focus-visible,:host input[type=email]:focus-visible,:host input[type=number]:focus-visible,:host input[type=date]:focus-visible,:host input[type=time]:focus-visible,:host input[type=datetime-local]:focus-visible,:host select:focus-visible,:host textarea:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host input[type=text]:disabled,:host input[type=url]:disabled,:host input[type=password]:disabled,:host input[type=search]:disabled,:host input[type=email]:disabled,:host input[type=number]:disabled,:host input[type=date]:disabled,:host input[type=time]:disabled,:host input[type=datetime-local]:disabled,:host select:disabled,:host textarea:disabled{cursor:not-allowed;opacity:.6}:host input[type=text][aria-invalid=true],:host input[type=url][aria-invalid=true],:host input[type=password][aria-invalid=true],:host input[type=search][aria-invalid=true],:host input[type=email][aria-invalid=true],:host input[type=number][aria-invalid=true],:host input[type=date][aria-invalid=true],:host input[type=time][aria-invalid=true],:host input[type=datetime-local][aria-invalid=true],:host select[aria-invalid=true],:host textarea[aria-invalid=true]{border-color:var(--nha-danger)}:host textarea{line-height:1.45;resize:vertical}:host input[type=number]{font-variant-numeric:tabular-nums}:host .mono{font-family:var(--nha-mono);font-size:12px}:host .field{display:flex;flex-direction:column;min-width:0}:host .hint{display:flex;justify-content:space-between;gap:8px;margin:4px 2px 0;color:var(--nha-text-muted);font-size:11px}:host .counter{flex:none;font-variant-numeric:tabular-nums}:host .error-text{color:var(--nha-danger)}:host .field-error{display:flex;align-items:flex-start;gap:6px;margin:4px 2px 0;color:var(--nha-danger);font-size:12px;font-weight:600;overflow-wrap:anywhere}:host .field-error nh-assistant-icon{flex:none;margin-top:1px}:host .form-error{box-sizing:border-box;display:flex;align-items:flex-start;gap:8px;margin:0;border:1px solid var(--nha-danger);border-radius:var(--nha-radius-control);padding:8px 12px;color:var(--nha-text);background:var(--nha-danger-soft);font-size:13px;overflow-wrap:anywhere}:host .form-error ul{margin:4px 0 0;padding-left:18px}:host .message{box-sizing:border-box;display:flex;align-items:center;gap:8px;margin:0;border:1px solid transparent;border-radius:var(--nha-radius-control);padding:8px 12px;font-size:13px;overflow-wrap:anywhere}:host .message.error{border-color:var(--nha-danger);color:var(--nha-text);background:var(--nha-danger-soft)}:host .message.warning{border-color:var(--nha-warning);color:var(--nha-text);background:var(--nha-warning-soft)}:host .message.success{border-color:var(--nha-success);color:var(--nha-text);background:var(--nha-success-soft)}:host .message.info{border-color:var(--nha-border);color:var(--nha-text);background:var(--nha-surface)}:host .message nh-assistant-icon{flex:none}:host button.link{min-height:0;border:0;padding:0;color:var(--nha-accent, currentColor);background:none;font:inherit;text-decoration:underline;cursor:pointer}:host button.link:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host button.link:disabled{cursor:not-allowed;opacity:.55}:host .primary,:host .secondary,:host .danger{display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;gap:6px;min-height:36px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px 14px;font:inherit;font-size:13px;font-weight:700;white-space:nowrap;cursor:pointer}:host .primary:focus-visible,:host .secondary:focus-visible,:host .danger:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .primary:disabled,:host .secondary:disabled,:host .danger:disabled{cursor:not-allowed;opacity:.55}:host .primary{border-color:var(--nha-accent);color:var(--nha-accent-contrast);background:var(--nha-accent)}:host .primary:hover:not(:disabled){filter:brightness(1.12)}:host .secondary{color:var(--nha-text);background:transparent}:host .secondary:hover:not(:disabled){background:var(--nha-surface-strong)}:host .danger{border-color:var(--nha-danger);color:var(--nha-danger);background:transparent}:host .danger:hover:not(:disabled){background:var(--nha-danger-soft)}:host .icon-button{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:1px solid transparent;border-radius:var(--nha-radius-control);color:var(--nha-text-muted);background:transparent;cursor:pointer}:host .icon-button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .icon-button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}:host .icon-button:disabled{cursor:not-allowed;opacity:.5}:host .actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px}:host .badge{display:inline-flex;align-items:center;gap:4px;border-radius:999px;padding:2px 8px;color:var(--nha-text-muted);background:var(--nha-surface-strong);font-size:11px;font-weight:700;white-space:nowrap}:host .badge.success{color:var(--nha-success);background:var(--nha-success-soft)}:host .badge.warning{color:var(--nha-warning);background:var(--nha-warning-soft)}:host .badge.danger{color:var(--nha-danger);background:var(--nha-danger-soft)}:host .visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}.section{display:flex;flex-direction:column;gap:16px;min-width:0}.section-header{display:flex;flex-wrap:wrap;align-items:flex-start;justify-content:space-between;gap:12px}.section-header>div{min-width:0;max-width:72ch}h3{margin:0 0 4px;font-size:17px;font-weight:750}h4{margin:0 0 8px;font-size:14px;font-weight:700}.muted{margin:0;color:var(--nha-text-muted);font-size:13px}.state{display:flex;flex-direction:column;align-items:flex-start;gap:10px;margin:0;border:1px dashed var(--nha-border);border-radius:var(--nha-radius);padding:20px;color:var(--nha-text-muted);font-size:13px}.state p{margin:0}.stack{display:flex;flex-direction:column;gap:14px;min-width:0}.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,240px),1fr));gap:14px}.meta{display:flex;flex-wrap:wrap;align-items:center;gap:8px 12px;color:var(--nha-text-muted);font-size:12px;overflow-wrap:anywhere}textarea.large{min-height:260px}.catalog-error{flex-wrap:wrap}.grow{flex:1;min-width:0}.subsection{display:flex;flex-direction:column;gap:8px;border-top:1px solid var(--nha-border);padding-top:14px}.history{display:grid;gap:6px;margin:0;padding:0;list-style:none}.history li{display:flex;flex-wrap:wrap;align-items:center;gap:6px 12px;font-size:12px}.cards{display:grid;gap:10px;margin:0;padding:0;list-style:none}.card{display:flex;flex-direction:column;gap:8px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius);padding:14px;background:var(--nha-background)}.card.inactive{opacity:.8}.card-header{display:flex;flex-wrap:wrap;align-items:center;gap:8px;min-width:0}.card-header strong{font-size:14px;overflow-wrap:anywhere}.card-actions{display:flex;flex-wrap:wrap;gap:8px}.check{display:flex;align-items:flex-start;gap:8px;font-size:13px;cursor:pointer}.check input{width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.check.described>span{display:grid;gap:2px;min-width:0;overflow-wrap:anywhere}.checks{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));gap:6px 12px;min-width:0;overflow-wrap:anywhere}.chips{display:flex;flex-wrap:wrap;gap:6px;margin:0;padding:0;list-style:none}.chip{display:inline-flex;align-items:center;gap:2px;border:1px solid var(--nha-border);border-radius:999px;padding:2px 2px 2px 10px;background:var(--nha-surface);font-family:var(--nha-mono);font-size:12px}.chip 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;width:26px;height:26px;border-radius:999px}.chip button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.chip button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}.chip button:disabled{cursor:not-allowed;opacity:.5}.inline{display:flex;flex-wrap:wrap;gap:8px}.inline>input{flex:1 1 180px}.catalog{display:grid;gap:4px;max-height:260px;margin:8px 0 0;overflow-y:auto;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px;list-style:none}.catalog li label{display:flex;flex-wrap:wrap;align-items:center;gap:6px 8px;border-radius:8px;padding:6px;cursor:pointer}.catalog li label:hover{background:var(--nha-surface)}.catalog .tool-id{font-family:var(--nha-mono);font-size:12px;overflow-wrap:anywhere}.skills{display:grid;gap:8px;margin:0;padding:0;list-style:none}.skill{display:grid;gap:6px;border-left:3px solid var(--nha-border);padding:4px 0 4px 12px}.uses{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));gap:8px}.use{display:flex;align-items:flex-start;gap:10px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:10px 12px;cursor:pointer;overflow-wrap:anywhere}.use input{flex:none;width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.use>span{display:grid;gap:2px;min-width:0}.use strong{font-size:13px}.use.selected{border-color:var(--nha-highlight);background:var(--nha-surface)}.use:focus-within{outline:2px solid var(--nha-highlight);outline-offset:2px}.discovery{display:grid;gap:8px;padding:10px 12px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control)}.discovery dl{display:grid;grid-template-columns:max-content minmax(0,1fr);gap:4px 12px;margin:0}.discovery dt{color:var(--nha-text-muted)}.discovery dd{margin:0;overflow-wrap:anywhere}\n"] }]
|
|
689
|
+
args: [{ selector: 'nh-assistant-admin-agent-editor', standalone: true, imports: [TranslatePipe, _NhAssistantTranslatePipe, _NhAssistantIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<form class=\"section card\" [attr.aria-labelledby]=\"id + '-title'\" [attr.aria-busy]=\"saving()\" (submit)=\"$event.preventDefault(); save()\">\n <h3 [id]=\"id + '-title'\">\n @if (agent(); as current) {\n {{ 'nh-assistant.admin.agent-editor.edit-title' | translate: { name: current.displayName } }}\n } @else {\n {{ 'nh-assistant.admin.agent-editor.new-title' | translate }}\n }\n </h3>\n\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-id'\">{{ 'nh-assistant.admin.agent-editor.id' | translate }}</label>\n <input\n type=\"text\"\n class=\"mono\"\n autocomplete=\"off\"\n [id]=\"id + '-id'\"\n [value]=\"agentId()\"\n [disabled]=\"!isNew()\"\n [attr.aria-describedby]=\"id + '-id-hint'\"\n [attr.aria-invalid]=\"submitted() && idInvalid() ? true : null\"\n (input)=\"agentId.set(value($event))\" />\n <p class=\"hint\" [id]=\"id + '-id-hint'\">\n <span [class.error-text]=\"submitted() && idInvalid()\">\n {{ (submitted() && idInvalid() ? 'nh-assistant.admin.invalid-id' : 'nh-assistant.admin.agent-editor.id-hint') | translate }}\n </span>\n </p>\n </div>\n\n <div class=\"field\">\n <label [for]=\"id + '-name'\">{{ 'nh-assistant.admin.agent-editor.display-name' | translate }}</label>\n <input\n type=\"text\"\n [id]=\"id + '-name'\"\n [value]=\"displayName()\"\n [attr.aria-invalid]=\"submitted() && nameMissing() ? true : null\"\n [attr.aria-describedby]=\"id + '-name-hint'\"\n (input)=\"displayName.set(value($event))\" />\n <p class=\"hint\" [id]=\"id + '-name-hint'\">\n @if (submitted() && nameMissing()) {\n <span class=\"error-text\">{{ 'nh-assistant.admin.required' | translate }}</span>\n }\n </p>\n </div>\n </div>\n\n @if (choosesUse()) {\n <fieldset class=\"subsection\" data-agent-use>\n <legend>{{ 'nh-assistant.admin.agent-editor.use.title' | translate }}</legend>\n <div class=\"uses\">\n @for (option of uses; track option) {\n <label class=\"use\" [class.selected]=\"use() === option\">\n <input type=\"radio\" [name]=\"id + '-use'\" [value]=\"option\" [checked]=\"use() === option\" (change)=\"use.set(option)\" />\n <span>\n <strong>{{ 'nh-assistant.admin.agent-editor.use.' + option | translate }}</strong>\n <span class=\"muted\">{{ 'nh-assistant.admin.agent-editor.use.' + option + '-hint' | translate }}</span>\n </span>\n </label>\n }\n </div>\n @if (isNew() && use() !== 'chat') {\n <p class=\"hint\">{{ 'nh-assistant.admin.agent-editor.use.next' | translate }}</p>\n }\n @if (isNew()) {\n <label class=\"check described\" data-switch-on-now>\n <input type=\"checkbox\" [checked]=\"switchOnNow()\" (change)=\"switchOnNow.set(checked($event))\" />\n <span>\n <strong>{{ 'nh-assistant.admin.agent-editor.switch-on-now' | translate }}</strong>\n <span class=\"muted\">{{ 'nh-assistant.admin.agent-editor.switch-on-now-hint' | translate }}</span>\n </span>\n </label>\n }\n </fieldset>\n\n @if (catalogUnavailable()) {\n <!-- Without the run catalog the kinds of objects and policies are unknown: what the agent works on stays as it is. -->\n <p class=\"message warning\" role=\"note\" data-catalog-unavailable>\n <nh-assistant-icon name=\"warning\" />{{ 'nh-assistant.admin.agent-editor.catalog-unavailable' | translate }}\n </p>\n } @else {\n <fieldset\n class=\"subsection\"\n data-works-on\n [attr.aria-describedby]=\"id + '-works-on-hint ' + id + '-works-on-error'\"\n [attr.aria-invalid]=\"(submitted() && worksOnMissing()) || fieldError('worksOn') ? true : null\">\n <legend>{{ 'nh-assistant.admin.agent-editor.works-on.title' | translate }}</legend>\n <p class=\"muted\" [id]=\"id + '-works-on-hint'\">{{ 'nh-assistant.admin.agent-editor.works-on.hint' | translate }}</p>\n <div class=\"uses\">\n <label class=\"use\" [class.selected]=\"worksOnScope() === 'all'\">\n <input type=\"radio\" [name]=\"id + '-works-on'\" value=\"all\" [checked]=\"worksOnScope() === 'all'\" (change)=\"worksOnScope.set('all')\" />\n <span>\n <strong>{{ 'nh-assistant.admin.agent-editor.works-on.all' | translate }}</strong>\n <span class=\"muted\">{{ 'nh-assistant.admin.agent-editor.works-on.all-hint' | translate }}</span>\n </span>\n </label>\n <label class=\"use\" [class.selected]=\"worksOnScope() === 'types'\">\n <input type=\"radio\" [name]=\"id + '-works-on'\" value=\"types\" [checked]=\"worksOnScope() === 'types'\" (change)=\"worksOnScope.set('types')\" />\n <span>\n <strong>{{ 'nh-assistant.admin.agent-editor.works-on.types' | translate }}</strong>\n <span class=\"muted\">{{ 'nh-assistant.admin.agent-editor.works-on.types-hint' | translate }}</span>\n </span>\n </label>\n </div>\n\n @if (worksOnScope() === 'types') {\n <div class=\"checks\" role=\"group\" [attr.aria-label]=\"'nh-assistant.admin.agent-editor.works-on.types' | translate\" data-works-on-types>\n @for (type of subjectTypes(); track type.id) {\n <label class=\"check\">\n <input type=\"checkbox\" [checked]=\"worksOnTypes().includes(type.id)\" (change)=\"toggleWorksOnType(type.id, checked($event))\" />\n <span>{{ type.pluralName ?? type.name }} <span class=\"mono muted\">{{ type.id }}</span></span>\n </label>\n } @empty {\n <p class=\"muted\">{{ 'nh-assistant.admin.agent-editor.works-on.no-types' | translate }}</p>\n }\n </div>\n @if (fieldError('worksOnSubjectTypes'); as key) {\n <p class=\"hint\"><span class=\"error-text\">{{ key | translate }}</span></p>\n } @else if (tooManyTypes()) {\n <p class=\"hint\"><span class=\"error-text\">{{ 'nh-assistant.admin.agent-editor.works-on.too-many' | translate }}</span></p>\n }\n }\n\n @if (knownTypes().length > 0) {\n <p class=\"hint\" data-known-types>{{ 'nh-assistant.admin.agent-editor.works-on.known' | translate: { types: knownTypes().join(', ') } }}</p>\n }\n\n <label class=\"check described\">\n <input type=\"checkbox\" [checked]=\"worksOnApplication()\" (change)=\"worksOnApplication.set(checked($event))\" />\n <span>\n <strong>{{ 'nh-assistant.admin.agent-editor.works-on.application' | translate }}</strong>\n <span class=\"muted\">{{ 'nh-assistant.admin.agent-editor.works-on.application-hint' | translate }}</span>\n </span>\n </label>\n\n @if (divisionsListed()) {\n <label class=\"check described\" data-per-division>\n <input\n type=\"checkbox\"\n [checked]=\"perDivision()\"\n [attr.aria-invalid]=\"fieldError('perDivision') ? true : null\"\n [attr.aria-describedby]=\"fieldError('perDivision') ? id + '-per-division-error' : null\"\n (change)=\"perDivision.set(checked($event))\" />\n <span>\n <strong>{{ 'nh-assistant.admin.agent-editor.works-on.per-division' | translate }}</strong>\n <span class=\"muted\">{{ 'nh-assistant.admin.agent-editor.works-on.per-division-hint' | translate }}</span>\n </span>\n </label>\n @if (fieldError('perDivision'); as key) {\n <p class=\"hint\" [id]=\"id + '-per-division-error'\"><span class=\"error-text\">{{ key | translate }}</span></p>\n }\n }\n\n <p class=\"hint\" [id]=\"id + '-works-on-error'\" aria-live=\"polite\">\n @if (submitted() && worksOnMissing()) {\n <span class=\"error-text\">{{ 'nh-assistant.admin.agent-editor.works-on.missing' | translate }}</span>\n } @else if (fieldError('worksOn'); as key) {\n <span class=\"error-text\">{{ key | translate }}</span>\n }\n </p>\n\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-run-policy'\">{{ 'nh-assistant.admin.agent-editor.works-on.run-policy' | translate }}</label>\n <select\n [id]=\"id + '-run-policy'\"\n [attr.aria-describedby]=\"id + '-run-policy-hint'\"\n [attr.aria-invalid]=\"fieldError('runPolicy') ? true : null\"\n (change)=\"runPolicy.set(value($event))\">\n <option value=\"\" [selected]=\"runPolicy() === ''\">{{ 'nh-assistant.admin.agent-editor.works-on.run-policy-default' | translate }}</option>\n @for (policy of agentPolicies(); track policy.name) {\n <option [value]=\"policy.name\" [selected]=\"runPolicy() === policy.name\">{{ policyKeys(policy.name) | nhAssistantTranslate: policy.description }}</option>\n }\n </select>\n <p class=\"hint\" [id]=\"id + '-run-policy-hint'\">\n @if (fieldError('runPolicy'); as key) {\n <span class=\"error-text\">{{ key | translate }}</span>\n } @else {\n {{ 'nh-assistant.admin.agent-editor.works-on.run-policy-hint' | translate }}\n }\n </p>\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-view-policy'\">{{ 'nh-assistant.admin.agent-editor.works-on.view-policy' | translate }}</label>\n <select\n [id]=\"id + '-view-policy'\"\n [attr.aria-describedby]=\"id + '-view-policy-hint'\"\n [attr.aria-invalid]=\"fieldError('viewPolicy') ? true : null\"\n (change)=\"viewPolicy.set(value($event))\">\n <option value=\"\" [selected]=\"viewPolicy() === ''\">{{ 'nh-assistant.admin.agent-editor.works-on.view-policy-default' | translate }}</option>\n @for (policy of agentPolicies(); track policy.name) {\n <option [value]=\"policy.name\" [selected]=\"viewPolicy() === policy.name\">{{ policyKeys(policy.name) | nhAssistantTranslate: policy.description }}</option>\n }\n </select>\n <p class=\"hint\" [id]=\"id + '-view-policy-hint'\">\n @if (fieldError('viewPolicy'); as key) {\n <span class=\"error-text\">{{ key | translate }}</span>\n } @else {\n {{ 'nh-assistant.admin.agent-editor.works-on.view-policy-hint' | translate }}\n }\n </p>\n </div>\n </div>\n </fieldset>\n }\n }\n\n @if (agentGroups().length > 0 && !catalogUnavailable()) {\n <fieldset\n class=\"subsection\"\n data-agent-groups\n [attr.aria-describedby]=\"id + '-groups-hint ' + id + '-groups-error'\"\n [attr.aria-invalid]=\"groupsError() ? true : null\">\n <legend>{{ 'nh-assistant.admin.agent-editor.groups.title' | translate }}</legend>\n <p class=\"muted\" [id]=\"id + '-groups-hint'\">{{ 'nh-assistant.admin.agent-editor.groups.hint' | translate }}</p>\n @if (isVariant()) {\n <p class=\"hint\" data-groups-variant>{{ 'nh-assistant.admin.agent-editor.groups.shadow-variant' | translate }}</p>\n } @else {\n <div class=\"checks\" role=\"group\" [attr.aria-label]=\"'nh-assistant.admin.agent-editor.groups.title' | translate\">\n @for (group of agentGroups(); track group.id) {\n <label class=\"check described\" [attr.data-agent-group]=\"group.id\">\n <input type=\"checkbox\" [checked]=\"groups().includes(group.id)\" (change)=\"toggleGroup(group.id, checked($event))\" />\n <span>\n <strong>{{ group.name }}</strong>\n @if (group.description; as description) {\n <span class=\"muted\">{{ description }}</span>\n }\n @if (codeGroups().includes(group.id)) {\n <span class=\"muted\" data-group-in-code>{{ 'nh-assistant.admin.agent-editor.groups.in-code' | translate }}</span>\n }\n </span>\n </label>\n }\n </div>\n }\n <p class=\"hint\" [id]=\"id + '-groups-error'\" aria-live=\"polite\">\n @if (groupsError(); as key) {\n <span class=\"error-text\">{{ key | translate }}</span>\n }\n </p>\n </fieldset>\n }\n\n <div class=\"field\">\n <label [for]=\"id + '-description'\">{{ 'nh-assistant.admin.agent-editor.description' | translate }}</label>\n <input type=\"text\" [id]=\"id + '-description'\" [value]=\"description()\" (input)=\"description.set(value($event))\" />\n </div>\n\n <div class=\"field\">\n <label [for]=\"id + '-instructions'\">{{ 'nh-assistant.admin.agent-editor.instructions' | translate }}</label>\n <textarea\n rows=\"8\"\n [id]=\"id + '-instructions'\"\n [value]=\"instructions()\"\n [attr.aria-describedby]=\"id + '-instructions-hint'\"\n [attr.aria-invalid]=\"tooLong() ? true : null\"\n (input)=\"instructions.set(value($event))\"></textarea>\n <p class=\"hint\" [id]=\"id + '-instructions-hint'\">\n <span [class.error-text]=\"tooLong()\">\n {{ (tooLong() ? 'nh-assistant.admin.too-long' : 'nh-assistant.admin.agent-editor.instructions-hint') | translate }}\n </span>\n <span class=\"counter\" [class.error-text]=\"tooLong()\">{{ 'nh-assistant.admin.characters' | translate: { count: instructions().length, max: max } }}</span>\n </p>\n </div>\n\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-autonomy'\">{{ 'nh-assistant.admin.agent-editor.autonomy' | translate }}</label>\n <select [id]=\"id + '-autonomy'\" [attr.aria-describedby]=\"id + '-autonomy-hint'\" (change)=\"autonomy.set($any(value($event)))\">\n @for (level of autonomyLevels; track level) {\n <option [value]=\"level\" [selected]=\"autonomy() === level\">{{ 'nh-assistant.admin.agents.autonomy.' + level | translate }}</option>\n }\n </select>\n <p class=\"hint\" [id]=\"id + '-autonomy-hint'\">{{ 'nh-assistant.admin.agent-editor.autonomy-hint' | translate }}</p>\n </div>\n\n <div class=\"field\">\n <label [for]=\"id + '-policy'\">{{ 'nh-assistant.admin.agent-editor.required-policy' | translate }}</label>\n <input\n type=\"text\"\n class=\"mono\"\n autocomplete=\"off\"\n [id]=\"id + '-policy'\"\n [value]=\"requiredPolicy()\"\n [attr.aria-describedby]=\"id + '-policy-hint'\"\n (input)=\"requiredPolicy.set(value($event))\" />\n <p class=\"hint\" [id]=\"id + '-policy-hint'\">{{ 'nh-assistant.admin.agent-editor.required-policy-hint' | translate }}</p>\n </div>\n\n @if (choosesProfile()) {\n <div class=\"field\" data-agent-profile>\n <label [for]=\"id + '-profile'\">{{ 'nh-assistant.admin.agent-editor.profile' | translate }}</label>\n <select\n [id]=\"id + '-profile'\"\n [attr.aria-describedby]=\"id + '-profile-hint' + (fieldError('profile') ? ' ' + id + '-profile-error' : '')\"\n [attr.aria-invalid]=\"fieldError('profile') ? true : null\"\n (change)=\"profile.set(value($event))\">\n @if (isNew()) {\n <option value=\"\" [selected]=\"profile() === ''\">{{ 'nh-assistant.admin.agent-editor.profile-default' | translate }}</option>\n }\n @for (name of profileChoices(); track name) {\n <option [value]=\"name\" [selected]=\"profile() === name\">{{ name }}</option>\n }\n </select>\n <p class=\"hint\" [id]=\"id + '-profile-hint'\">{{ 'nh-assistant.admin.agent-editor.profile-hint' | translate }}</p>\n @if (fieldError('profile'); as key) {\n <p class=\"hint\" [id]=\"id + '-profile-error'\"><span class=\"error-text\">{{ key | translate }}</span></p>\n }\n </div>\n }\n\n @if (choosesUse()) {\n <div class=\"field\" data-agent-classification>\n <label [for]=\"id + '-classification'\">{{ 'nh-assistant.admin.agent-editor.classification' | translate }}</label>\n <select\n [id]=\"id + '-classification'\"\n [attr.aria-describedby]=\"id + '-classification-hint' + (fieldError('dataClassification') ? ' ' + id + '-classification-error' : '')\"\n [attr.aria-invalid]=\"fieldError('dataClassification') ? true : null\"\n (change)=\"dataClassification.set($any(value($event)))\">\n @for (classification of classifications; track classification) {\n <option [value]=\"classification\" [selected]=\"dataClassification() === classification\">\n {{ 'nh-assistant.admin.run-config.skills.classification.' + classification | translate }}\n </option>\n }\n </select>\n <p class=\"hint\" [id]=\"id + '-classification-hint'\">{{ 'nh-assistant.admin.agent-editor.classification-hint' | translate }}</p>\n @for (warning of warnings(); track warning) {\n <p class=\"message warning\" [attr.data-agent-editor-warning]=\"warning\">\n <nh-assistant-icon name=\"warning\" />{{ warningKeys('agent', warning) | nhAssistantTranslate: warning }}\n </p>\n }\n @if (fieldError('dataClassification'); as key) {\n <p class=\"hint\" [id]=\"id + '-classification-error'\"><span class=\"error-text\">{{ key | translate }}</span></p>\n }\n </div>\n }\n </div>\n\n <p class=\"hint\">{{ 'nh-assistant.admin.agent-editor.state-hint' | translate }}</p>\n\n <fieldset class=\"subsection\">\n <legend>{{ 'nh-assistant.admin.agent-editor.tool-selectors' | translate }}</legend>\n <p class=\"muted\">{{ 'nh-assistant.admin.agent-editor.tool-selectors-hint' | translate }}</p>\n\n @if (toolSelectors().length === 0) {\n <p class=\"muted\">{{ 'nh-assistant.admin.agent-editor.no-selectors' | translate }}</p>\n } @else {\n <ul class=\"chips\">\n @for (selector of toolSelectors(); track selector) {\n <li class=\"chip\">\n <span>{{ selector }}</span>\n <button\n type=\"button\"\n [attr.aria-label]=\"'nh-assistant.admin.agent-editor.remove-selector' | translate: { selector: selector }\"\n [attr.title]=\"'nh-assistant.admin.agent-editor.remove-selector' | translate: { selector: selector }\"\n (click)=\"removeSelector(selector)\">\n <nh-assistant-icon name=\"x\" />\n </button>\n </li>\n }\n </ul>\n <p class=\"muted\" role=\"status\">{{ 'nh-assistant.admin.agent-editor.matches' | translate: { count: matchCount() } }}</p>\n }\n\n <div class=\"inline\">\n <input\n type=\"text\"\n class=\"mono\"\n autocomplete=\"off\"\n [attr.aria-label]=\"'nh-assistant.admin.agent-editor.add-selector' | translate\"\n [attr.placeholder]=\"'nh-assistant.admin.agent-editor.selector-placeholder' | translate\"\n [attr.aria-invalid]=\"selectorInvalid() ? true : null\"\n [value]=\"newSelector()\"\n (input)=\"newSelector.set(value($event))\"\n (keydown)=\"onSelectorKeydown($event)\" />\n <button class=\"secondary\" type=\"button\" [disabled]=\"newSelector().trim().length === 0 || selectorInvalid()\" (click)=\"addSelector()\">\n <nh-assistant-icon name=\"plus\" />{{ 'nh-assistant.admin.agent-editor.add-selector' | translate }}\n </button>\n </div>\n\n <div class=\"field\">\n <label class=\"label\" [for]=\"id + '-catalog-filter'\">{{ 'nh-assistant.admin.agent-editor.catalog' | translate }}</label>\n <input\n type=\"search\"\n [id]=\"id + '-catalog-filter'\"\n [attr.placeholder]=\"'nh-assistant.admin.agent-editor.catalog-filter' | translate\"\n [value]=\"catalogFilter()\"\n (input)=\"catalogFilter.set(value($event))\" />\n <ul class=\"catalog\" [attr.aria-label]=\"'nh-assistant.admin.agent-editor.catalog' | translate\">\n @for (tool of filteredTools(); track tool.id) {\n <li>\n <label>\n <input\n type=\"checkbox\"\n [checked]=\"isSelected(tool.id) || isCovered(tool.id)\"\n [disabled]=\"isCovered(tool.id)\"\n (change)=\"toggleTool(tool.id, checked($event))\" />\n <span class=\"tool-id\">{{ tool.id }}</span>\n <span class=\"badge\">{{ 'nh-assistant.admin.source.' + tool.source | translate }}</span>\n <span class=\"badge\" [class.warning]=\"tool.effect !== 'read-only'\">{{ 'nh-assistant.admin.effect.' + tool.effect | translate }}</span>\n <span class=\"muted\">{{ tool.description }}</span>\n </label>\n </li>\n } @empty {\n <li class=\"muted\">{{ 'nh-assistant.admin.agent-editor.catalog-empty' | translate }}</li>\n }\n </ul>\n </div>\n </fieldset>\n\n <fieldset class=\"subsection\">\n <legend>{{ 'nh-assistant.admin.agent-editor.mcp-servers' | translate }}</legend>\n <p class=\"muted\">{{ 'nh-assistant.admin.agent-editor.mcp-servers-hint' | translate }}</p>\n @for (server of servers(); track server.id) {\n <label class=\"check\">\n <input type=\"checkbox\" [checked]=\"mcpServerIds().includes(server.id)\" (change)=\"toggleServer(server.id, checked($event))\" />\n <span>{{ server.displayName }} <span class=\"mono muted\">{{ server.id }}</span></span>\n </label>\n } @empty {\n <p class=\"muted\">{{ 'nh-assistant.admin.agent-editor.mcp-servers-empty' | translate }}</p>\n }\n </fieldset>\n\n @if (error(); as failure) {\n <p class=\"message error\" role=\"alert\"><nh-assistant-icon name=\"warning\" />{{ errorKeys() | nhAssistantTranslate: failure.code }}</p>\n }\n\n <div class=\"actions\">\n <button class=\"secondary\" type=\"button\" (click)=\"cancelled.emit()\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n <button class=\"primary\" type=\"submit\" [disabled]=\"saving() || tooLong()\">\n {{ (saving() ? 'nh-assistant.admin.saving' : 'nh-assistant.admin.save') | translate }}\n </button>\n </div>\n</form>\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;min-width:0;color:var(--nha-text);font-family:var(--nha-font);font-size:14px}:host fieldset{min-width:0;margin:0;border:0;padding:0}:host legend,:host .label,:host .field>label{display:block;margin-bottom:6px;padding:0;color:var(--nha-text);font-size:12px;font-weight:700}:host input[type=text],:host input[type=url],:host input[type=password],:host input[type=search],:host input[type=email],:host input[type=number],:host input[type=date],:host input[type=time],:host input[type=datetime-local],:host select,:host textarea{box-sizing:border-box;width:100%;min-width:0;min-height:38px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:8px 10px;color:var(--nha-text);background:var(--nha-background);font:inherit;font-size:13px}:host input[type=text]:focus-visible,:host input[type=url]:focus-visible,:host input[type=password]:focus-visible,:host input[type=search]:focus-visible,:host input[type=email]:focus-visible,:host input[type=number]:focus-visible,:host input[type=date]:focus-visible,:host input[type=time]:focus-visible,:host input[type=datetime-local]:focus-visible,:host select:focus-visible,:host textarea:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host input[type=text]:disabled,:host input[type=url]:disabled,:host input[type=password]:disabled,:host input[type=search]:disabled,:host input[type=email]:disabled,:host input[type=number]:disabled,:host input[type=date]:disabled,:host input[type=time]:disabled,:host input[type=datetime-local]:disabled,:host select:disabled,:host textarea:disabled{cursor:not-allowed;opacity:.6}:host input[type=text][aria-invalid=true],:host input[type=url][aria-invalid=true],:host input[type=password][aria-invalid=true],:host input[type=search][aria-invalid=true],:host input[type=email][aria-invalid=true],:host input[type=number][aria-invalid=true],:host input[type=date][aria-invalid=true],:host input[type=time][aria-invalid=true],:host input[type=datetime-local][aria-invalid=true],:host select[aria-invalid=true],:host textarea[aria-invalid=true]{border-color:var(--nha-danger)}:host textarea{line-height:1.45;resize:vertical}:host input[type=number]{font-variant-numeric:tabular-nums}:host .mono{font-family:var(--nha-mono);font-size:12px}:host .field{display:flex;flex-direction:column;min-width:0}:host .hint{display:flex;justify-content:space-between;gap:8px;margin:4px 2px 0;color:var(--nha-text-muted);font-size:11px}:host .counter{flex:none;font-variant-numeric:tabular-nums}:host .error-text{color:var(--nha-danger)}:host .field-error{display:flex;align-items:flex-start;gap:6px;margin:4px 2px 0;color:var(--nha-danger);font-size:12px;font-weight:600;overflow-wrap:anywhere}:host .field-error nh-assistant-icon{flex:none;margin-top:1px}:host .form-error{box-sizing:border-box;display:flex;align-items:flex-start;gap:8px;margin:0;border:1px solid var(--nha-danger);border-radius:var(--nha-radius-control);padding:8px 12px;color:var(--nha-text);background:var(--nha-danger-soft);font-size:13px;overflow-wrap:anywhere}:host .form-error ul{margin:4px 0 0;padding-left:18px}:host .message{box-sizing:border-box;display:flex;align-items:center;gap:8px;margin:0;border:1px solid transparent;border-radius:var(--nha-radius-control);padding:8px 12px;font-size:13px;overflow-wrap:anywhere}:host .message.error{border-color:var(--nha-danger);color:var(--nha-text);background:var(--nha-danger-soft)}:host .message.warning{border-color:var(--nha-warning);color:var(--nha-text);background:var(--nha-warning-soft)}:host .message.success{border-color:var(--nha-success);color:var(--nha-text);background:var(--nha-success-soft)}:host .message.info{border-color:var(--nha-border);color:var(--nha-text);background:var(--nha-surface)}:host .message nh-assistant-icon{flex:none}:host button.link{min-height:0;border:0;padding:0;color:var(--nha-accent, currentColor);background:none;font:inherit;text-decoration:underline;cursor:pointer}:host button.link:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host button.link:disabled{cursor:not-allowed;opacity:.55}:host .primary,:host .secondary,:host .danger{display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;gap:6px;min-height:36px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px 14px;font:inherit;font-size:13px;font-weight:700;white-space:nowrap;cursor:pointer}:host .primary:focus-visible,:host .secondary:focus-visible,:host .danger:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .primary:disabled,:host .secondary:disabled,:host .danger:disabled{cursor:not-allowed;opacity:.55}:host .primary{border-color:var(--nha-accent);color:var(--nha-accent-contrast);background:var(--nha-accent)}:host .primary:hover:not(:disabled){filter:brightness(1.12)}:host .secondary{color:var(--nha-text);background:transparent}:host .secondary:hover:not(:disabled){background:var(--nha-surface-strong)}:host .danger{border-color:var(--nha-danger);color:var(--nha-danger);background:transparent}:host .danger:hover:not(:disabled){background:var(--nha-danger-soft)}:host .icon-button{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:1px solid transparent;border-radius:var(--nha-radius-control);color:var(--nha-text-muted);background:transparent;cursor:pointer}:host .icon-button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .icon-button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}:host .icon-button:disabled{cursor:not-allowed;opacity:.5}:host .actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px}:host .badge{display:inline-flex;align-items:center;gap:4px;border-radius:999px;padding:2px 8px;color:var(--nha-text-muted);background:var(--nha-surface-strong);font-size:11px;font-weight:700;white-space:nowrap}:host .badge.success{color:var(--nha-success);background:var(--nha-success-soft)}:host .badge.warning{color:var(--nha-warning);background:var(--nha-warning-soft)}:host .badge.danger{color:var(--nha-danger);background:var(--nha-danger-soft)}:host .visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}.section{display:flex;flex-direction:column;gap:16px;min-width:0}.section-header{display:flex;flex-wrap:wrap;align-items:flex-start;justify-content:space-between;gap:12px}.section-header>div{min-width:0;max-width:72ch}h3{margin:0 0 4px;font-size:17px;font-weight:750}h4{margin:0 0 8px;font-size:14px;font-weight:700}.muted{margin:0;color:var(--nha-text-muted);font-size:13px}.state{display:flex;flex-direction:column;align-items:flex-start;gap:10px;margin:0;border:1px dashed var(--nha-border);border-radius:var(--nha-radius);padding:20px;color:var(--nha-text-muted);font-size:13px}.state p{margin:0}.stack{display:flex;flex-direction:column;gap:14px;min-width:0}.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,240px),1fr));gap:14px}.meta{display:flex;flex-wrap:wrap;align-items:center;gap:8px 12px;color:var(--nha-text-muted);font-size:12px;overflow-wrap:anywhere}textarea.large{min-height:260px}.catalog-error{flex-wrap:wrap}.grow{flex:1;min-width:0}.subsection{display:flex;flex-direction:column;gap:8px;border-top:1px solid var(--nha-border);padding-top:14px}.history{display:grid;gap:6px;margin:0;padding:0;list-style:none}.history li{display:flex;flex-wrap:wrap;align-items:center;gap:6px 12px;font-size:12px}.cards{display:grid;gap:10px;margin:0;padding:0;list-style:none}.card{display:flex;flex-direction:column;gap:8px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius);padding:14px;background:var(--nha-background)}.card.inactive{opacity:.8}.card-header{display:flex;flex-wrap:wrap;align-items:center;gap:8px;min-width:0}.card-header strong{font-size:14px;overflow-wrap:anywhere}.card-actions{display:flex;flex-wrap:wrap;gap:8px}.check{display:flex;align-items:flex-start;gap:8px;font-size:13px;cursor:pointer}.check input{width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.check.described>span{display:grid;gap:2px;min-width:0;overflow-wrap:anywhere}.checks{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));gap:6px 12px;min-width:0;overflow-wrap:anywhere}.chips{display:flex;flex-wrap:wrap;gap:6px;margin:0;padding:0;list-style:none}.chip{display:inline-flex;align-items:center;gap:2px;border:1px solid var(--nha-border);border-radius:999px;padding:2px 2px 2px 10px;background:var(--nha-surface);font-family:var(--nha-mono);font-size:12px}.chip 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;width:26px;height:26px;border-radius:999px}.chip button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.chip button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}.chip button:disabled{cursor:not-allowed;opacity:.5}.inline{display:flex;flex-wrap:wrap;gap:8px}.inline>input{flex:1 1 180px}.catalog{display:grid;gap:4px;max-height:260px;margin:8px 0 0;overflow-y:auto;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px;list-style:none}.catalog li label{display:flex;flex-wrap:wrap;align-items:center;gap:6px 8px;border-radius:8px;padding:6px;cursor:pointer}.catalog li label:hover{background:var(--nha-surface)}.catalog .tool-id{font-family:var(--nha-mono);font-size:12px;overflow-wrap:anywhere}.skills{display:grid;gap:8px;margin:0;padding:0;list-style:none}.skill{display:grid;gap:6px;border-left:3px solid var(--nha-border);padding:4px 0 4px 12px}.uses{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));gap:8px}.use{display:flex;align-items:flex-start;gap:10px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:10px 12px;cursor:pointer;overflow-wrap:anywhere}.use input{flex:none;width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.use>span{display:grid;gap:2px;min-width:0}.use strong{font-size:13px}.use.selected{border-color:var(--nha-highlight);background:var(--nha-surface)}.use:focus-within{outline:2px solid var(--nha-highlight);outline-offset:2px}.discovery{display:grid;gap:8px;padding:10px 12px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control)}.discovery dl{display:grid;grid-template-columns:max-content minmax(0,1fr);gap:4px 12px;margin:0}.discovery dt{color:var(--nha-text-muted)}.discovery dd{margin:0;overflow-wrap:anywhere}\n"] }]
|
|
653
690
|
}], ctorParameters: () => [], propDecorators: { agent: [{ type: i0.Input, args: [{ isSignal: true, alias: "agent", required: false }] }], tools: [{ type: i0.Input, args: [{ isSignal: true, alias: "tools", required: false }] }], servers: [{ type: i0.Input, args: [{ isSignal: true, alias: "servers", required: false }] }], catalog: [{ type: i0.Input, args: [{ isSignal: true, alias: "catalog", required: false }] }], catalogUnavailable: [{ type: i0.Input, args: [{ isSignal: true, alias: "catalogUnavailable", required: false }] }], saved: [{ type: i0.Output, args: ["saved"] }], workRequested: [{ type: i0.Output, args: ["workRequested"] }], cancelled: [{ type: i0.Output, args: ["cancelled"] }] } });
|
|
654
691
|
/** Whether two sorted lists of group ids are the same. */
|
|
655
692
|
function sameGroups(left, right) {
|
|
@@ -1390,6 +1427,8 @@ function withScheduleTargetError(errors, target) {
|
|
|
1390
1427
|
? { ...errors, subjectSet: { code: 'application-not-worked-on' } }
|
|
1391
1428
|
: errors;
|
|
1392
1429
|
}
|
|
1430
|
+
/** The data classifications a skill can choose, from the least to the most sensitive. */
|
|
1431
|
+
const dataClassifications = ['public', 'internal', 'confidential', 'restricted'];
|
|
1393
1432
|
function profileOptions(catalog) {
|
|
1394
1433
|
return (catalog?.profiles ?? []).map(profile => ({ value: profile, label: profile }));
|
|
1395
1434
|
}
|
|
@@ -1499,6 +1538,83 @@ function describeDuration(minutes, t) {
|
|
|
1499
1538
|
}
|
|
1500
1539
|
return t(minutes === 1 ? `${base}.minute` : `${base}.minutes`, { count: minutes });
|
|
1501
1540
|
}
|
|
1541
|
+
/** The bytes of one megabyte, as the forms show a file size. */
|
|
1542
|
+
const bytesPerMegabyte = 1024 * 1024;
|
|
1543
|
+
/** The file limit fields in the order the forms show them. */
|
|
1544
|
+
const fileLimitFields = ['maxFileBytes', 'maxFilePages', 'maxFilesPerTurn', 'maxFileTokensPerTurn'];
|
|
1545
|
+
/** A size in megabytes as a form shows it: whole megabytes plainly, others with up to two decimals. */
|
|
1546
|
+
function megabytesText(bytes) {
|
|
1547
|
+
const megabytes = bytes / bytesPerMegabyte;
|
|
1548
|
+
return Number.isInteger(megabytes) ? String(megabytes) : String(Math.round(megabytes * 100) / 100);
|
|
1549
|
+
}
|
|
1550
|
+
/**
|
|
1551
|
+
* The lower file limits of an agent or a skill as a form edits them: the largest file in megabytes,
|
|
1552
|
+
* the others as whole numbers; an empty field keeps the application's limit (for a skill, the
|
|
1553
|
+
* agent's). A size shown with decimals keeps its exact bytes until the administrator changes it.
|
|
1554
|
+
*/
|
|
1555
|
+
class FileLimitsDraft {
|
|
1556
|
+
constructor() {
|
|
1557
|
+
this.megabytes = signal('', ...(ngDevMode ? [{ debugName: "megabytes" }] : []));
|
|
1558
|
+
this.pages = signal('', ...(ngDevMode ? [{ debugName: "pages" }] : []));
|
|
1559
|
+
this.files = signal('', ...(ngDevMode ? [{ debugName: "files" }] : []));
|
|
1560
|
+
this.tokens = signal('', ...(ngDevMode ? [{ debugName: "tokens" }] : []));
|
|
1561
|
+
this.loadedBytes = null;
|
|
1562
|
+
this.loadedMegabytes = '';
|
|
1563
|
+
}
|
|
1564
|
+
load(limits) {
|
|
1565
|
+
this.loadedBytes = limits?.maxFileBytes ?? null;
|
|
1566
|
+
this.loadedMegabytes = this.loadedBytes === null ? '' : megabytesText(this.loadedBytes);
|
|
1567
|
+
this.megabytes.set(this.loadedMegabytes);
|
|
1568
|
+
this.pages.set(numberText(limits?.maxFilePages));
|
|
1569
|
+
this.files.set(numberText(limits?.maxFilesPerTurn));
|
|
1570
|
+
this.tokens.set(numberText(limits?.maxFileTokensPerTurn));
|
|
1571
|
+
}
|
|
1572
|
+
/** The text of a field, by its server name. */
|
|
1573
|
+
text(field) {
|
|
1574
|
+
switch (field) {
|
|
1575
|
+
case 'maxFileBytes':
|
|
1576
|
+
return this.megabytes;
|
|
1577
|
+
case 'maxFilePages':
|
|
1578
|
+
return this.pages;
|
|
1579
|
+
case 'maxFilesPerTurn':
|
|
1580
|
+
return this.files;
|
|
1581
|
+
default:
|
|
1582
|
+
return this.tokens;
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
/** Checks the fields against the application's limits, as the server does; only next to accepting files. */
|
|
1586
|
+
check(errors, application) {
|
|
1587
|
+
const bytes = this.bytes();
|
|
1588
|
+
if (this.megabytes().trim() !== '' && (bytes === null || bytes < 1 || (application && bytes > application.maxFileBytes))) {
|
|
1589
|
+
errors['maxFileBytes'] = application
|
|
1590
|
+
? { code: 'megabytes-up-to', params: { max: megabytesText(application.maxFileBytes) } }
|
|
1591
|
+
: { code: 'megabytes-positive' };
|
|
1592
|
+
}
|
|
1593
|
+
checkWhole(errors, 'maxFilePages', this.pages(), 1, application?.maxFilePages);
|
|
1594
|
+
checkWhole(errors, 'maxFilesPerTurn', this.files(), 1, application?.maxFilesPerTurn);
|
|
1595
|
+
checkWhole(errors, 'maxFileTokensPerTurn', this.tokens(), 1, application?.maxFileTokensPerTurn);
|
|
1596
|
+
}
|
|
1597
|
+
/** The limits a save sends: none when the agent or skill accepts no files. */
|
|
1598
|
+
toRequest(acceptsFiles) {
|
|
1599
|
+
return {
|
|
1600
|
+
maxFileBytes: acceptsFiles ? this.bytes() : null,
|
|
1601
|
+
maxFilePages: acceptsFiles ? toNumber(this.pages()) : null,
|
|
1602
|
+
maxFilesPerTurn: acceptsFiles ? toNumber(this.files()) : null,
|
|
1603
|
+
maxFileTokensPerTurn: acceptsFiles ? toNumber(this.tokens()) : null
|
|
1604
|
+
};
|
|
1605
|
+
}
|
|
1606
|
+
bytes() {
|
|
1607
|
+
const text = this.megabytes().trim();
|
|
1608
|
+
if (text === '') {
|
|
1609
|
+
return null;
|
|
1610
|
+
}
|
|
1611
|
+
if (text === this.loadedMegabytes && this.loadedBytes !== null) {
|
|
1612
|
+
return this.loadedBytes;
|
|
1613
|
+
}
|
|
1614
|
+
const megabytes = Number(text.replace(',', '.'));
|
|
1615
|
+
return Number.isFinite(megabytes) ? Math.round(megabytes * bytesPerMegabyte) : null;
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1502
1618
|
|
|
1503
1619
|
const agentFlowSteps = ['works-on', 'when', 'skills', 'account', 'approval', 'results'];
|
|
1504
1620
|
/** The most tools the flow lists before it counts the rest. */
|
|
@@ -1684,7 +1800,8 @@ const unavailableServerUses = new Set([
|
|
|
1684
1800
|
/**
|
|
1685
1801
|
* Whether the agent can be run now, by the rule the server applies to runs by hand: it is neither
|
|
1686
1802
|
* stopped nor disabled in code, an agent created here has run settings, and the run catalog names
|
|
1687
|
-
* what it runs on. A paused agent accepts the run, which waits until it is resumed
|
|
1803
|
+
* what it runs on. A paused agent accepts the run, which waits until it is resumed; an agent that is
|
|
1804
|
+
* off runs it at once, because it only ignores the work it would take on by itself.
|
|
1688
1805
|
*/
|
|
1689
1806
|
function flowRunNow(agent, configuration, catalog) {
|
|
1690
1807
|
if (agent.runState === 'configured-off') {
|
|
@@ -2501,6 +2618,82 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImpo
|
|
|
2501
2618
|
args: [{ selector: 'nh-assistant-agent-work-setup', standalone: true, imports: [TranslatePipe, _NhAssistantTranslatePipe, _NhAssistantIconComponent, _NhAssistantPickerComponent, NhAssistantAccountPickerComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<section class=\"stack card\" data-work-setup [attr.aria-labelledby]=\"id + '-title'\">\n <header class=\"stack\">\n <h5 [id]=\"id + '-title'\">{{ 'nh-assistant.admin.run-config.setup.title' | translate: { name: agentName() } }}</h5>\n <p class=\"muted\">{{ 'nh-assistant.admin.run-config.setup.explain' | translate }}</p>\n <ol class=\"steps\" [attr.aria-label]=\"'nh-assistant.admin.run-config.setup.progress' | translate\">\n @for (item of steps; track item; let index = $index) {\n <li [class.done]=\"stepState(item) === 'done'\" [attr.aria-current]=\"stepState(item) === 'current' ? 'step' : null\">\n @if (stepState(item) === 'done') {\n <nh-assistant-icon name=\"check\" />\n }\n {{ index + 1 }}. {{ 'nh-assistant.admin.run-config.setup.step.' + item | translate }}\n </li>\n }\n </ol>\n </header>\n\n @if (done()) {\n <p class=\"message success\" role=\"status\" data-setup-done>{{ 'nh-assistant.admin.run-config.setup.done' | translate }}</p>\n } @else {\n <form class=\"stack\" novalidate (submit)=\"$event.preventDefault(); step() === 'review' ? save() : next()\">\n @switch (step()) {\n @case ('when') {\n <fieldset class=\"group\" data-step=\"when\">\n <legend tabindex=\"-1\">{{ 'nh-assistant.admin.run-config.setup.when.title' | translate }}</legend>\n <div class=\"choices\" role=\"radiogroup\" [attr.aria-label]=\"'nh-assistant.admin.run-config.setup.when.title' | translate\">\n <label class=\"choice\" [class.selected]=\"source() === 'event'\">\n <input type=\"radio\" [name]=\"id + '-source'\" value=\"event\" [checked]=\"source() === 'event'\" (change)=\"setSource('event')\" />\n <span>\n <strong>{{ 'nh-assistant.admin.run-config.triggers.source-event' | translate }}</strong>\n <span class=\"muted\">{{ 'nh-assistant.admin.run-config.triggers.source-event-hint' | translate }}</span>\n </span>\n </label>\n <label class=\"choice\" [class.selected]=\"source() === 'schedule'\">\n <input type=\"radio\" [name]=\"id + '-source'\" value=\"schedule\" [checked]=\"source() === 'schedule'\" (change)=\"setSource('schedule')\" />\n <span>\n <strong>{{ 'nh-assistant.admin.run-config.triggers.source-schedule' | translate }}</strong>\n <span class=\"muted\">{{ (offersApplication() ? 'nh-assistant.admin.run-config.triggers.source-schedule-application-hint' : 'nh-assistant.admin.run-config.triggers.source-schedule-hint') | translate }}</span>\n </span>\n </label>\n </div>\n @if (source() === 'event') {\n <div class=\"field\">\n <label [for]=\"id + '-event'\">{{ 'nh-assistant.admin.run-config.triggers.event' | translate }}</label>\n <nh-assistant-picker [inputId]=\"id + '-event'\" [options]=\"events()\" [value]=\"event()\"\n [placeholder]=\"'nh-assistant.admin.run-config.choose' | translate\"\n [invalid]=\"fieldError('event') !== null\"\n [describedBy]=\"describedBy(null, 'event', id + '-event-error')\"\n (valueChange)=\"event.set($event); touch('event')\" />\n @if (errorKey('event'); as key) {\n <p class=\"field-error\" [id]=\"id + '-event-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('event')?.params }}</p>\n }\n @if (events().length === 0) {\n <p class=\"hint\">{{ 'nh-assistant.admin.run-config.setup.no-events' | translate }}</p>\n }\n </div>\n } @else {\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-cron'\">{{ 'nh-assistant.admin.run-config.triggers.cron' | translate }}</label>\n <input type=\"text\" class=\"mono\" autocomplete=\"off\" maxlength=\"128\" [id]=\"id + '-cron'\" [value]=\"cron()\"\n [attr.aria-invalid]=\"fieldError('cron') ? true : null\"\n [attr.aria-describedby]=\"describedBy(id + '-cron-hint', 'cron', id + '-cron-error')\"\n (input)=\"cron.set(value($event)); touch('cron')\" />\n <p class=\"hint\" [id]=\"id + '-cron-hint'\">{{ 'nh-assistant.admin.run-config.triggers.cron-hint' | translate }}</p>\n <ul class=\"examples\" [attr.aria-label]=\"'nh-assistant.admin.run-config.triggers.examples' | translate\">\n @for (example of examples; track example.cron) {\n <li><button type=\"button\" (click)=\"useExample(example.cron)\">{{ 'nh-assistant.admin.run-config.triggers.example.' + example.key | translate }}</button></li>\n }\n </ul>\n @if (errorKey('cron'); as key) {\n <p class=\"field-error\" [id]=\"id + '-cron-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('cron')?.params }}</p>\n }\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-zone'\">{{ 'nh-assistant.admin.run-config.triggers.time-zone' | translate }}</label>\n <nh-assistant-picker [inputId]=\"id + '-zone'\" [options]=\"timeZones\" [value]=\"timeZone()\" [noneLabel]=\"'UTC'\"\n [invalid]=\"fieldError('timeZone') !== null\"\n [describedBy]=\"describedBy(null, 'timeZone', id + '-zone-error')\"\n (valueChange)=\"timeZone.set($event); touch('timeZone')\" />\n @if (errorKey('timeZone'); as key) {\n <p class=\"field-error\" [id]=\"id + '-zone-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('timeZone')?.params }}</p>\n }\n </div>\n </div>\n @if (offersApplication()) {\n <!-- An agent that works on the whole application chooses what the schedule runs for. -->\n <div class=\"field\" data-schedule-target>\n <span class=\"label\" [id]=\"id + '-target-label'\">{{ 'nh-assistant.admin.run-config.triggers.target.title' | translate }}</span>\n <div\n class=\"choices\"\n role=\"radiogroup\"\n tabindex=\"-1\"\n [attr.aria-labelledby]=\"id + '-target-label'\"\n [attr.aria-invalid]=\"target() === 'application' && fieldError('subjectSet') ? true : null\"\n [attr.aria-describedby]=\"target() === 'application' ? describedBy(null, 'subjectSet', id + '-target-error') : null\">\n <label class=\"choice\" [class.selected]=\"target() === 'application'\">\n <input type=\"radio\" [name]=\"id + '-target'\" value=\"application\" [checked]=\"target() === 'application'\" (change)=\"setTarget('application')\" />\n <span>\n <strong>{{ 'nh-assistant.admin.run-config.triggers.target.application' | translate }}</strong>\n <span class=\"muted\" data-target-hint>{{ (perDivision() ? 'nh-assistant.admin.run-config.triggers.target.application-per-division-hint' : 'nh-assistant.admin.run-config.triggers.target.application-hint') | translate }}</span>\n </span>\n </label>\n <label class=\"choice\" [class.selected]=\"target() === 'set'\">\n <input type=\"radio\" [name]=\"id + '-target'\" value=\"set\" [checked]=\"target() === 'set'\" (change)=\"setTarget('set')\" />\n <span>\n <strong>{{ 'nh-assistant.admin.run-config.triggers.target.set' | translate }}</strong>\n <span class=\"muted\">{{ 'nh-assistant.admin.run-config.triggers.target.set-hint' | translate }}</span>\n </span>\n </label>\n </div>\n @if (target() === 'application' && errorKey('subjectSet'); as key) {\n <p class=\"field-error\" [id]=\"id + '-target-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate }}</p>\n }\n </div>\n }\n @if (target() === 'set') {\n <div class=\"field\">\n <label [for]=\"id + '-set'\">{{ 'nh-assistant.admin.run-config.triggers.subject-set' | translate }}</label>\n <nh-assistant-picker [inputId]=\"id + '-set'\" [options]=\"subjectSets()\" [value]=\"subjectSet()\"\n [placeholder]=\"'nh-assistant.admin.run-config.choose' | translate\"\n [invalid]=\"fieldError('subjectSet') !== null\"\n [describedBy]=\"describedBy(id + '-set-hint', 'subjectSet', id + '-set-error')\"\n (valueChange)=\"subjectSet.set($event); touch('subjectSet')\" />\n <p class=\"hint\" [id]=\"id + '-set-hint'\">{{ 'nh-assistant.admin.run-config.triggers.subject-set-hint' | translate }}</p>\n @if (errorKey('subjectSet'); as key) {\n <p class=\"field-error\" [id]=\"id + '-set-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('subjectSet')?.params }}</p>\n }\n </div>\n }\n <div class=\"field\">\n <label [for]=\"id + '-message'\">{{ 'nh-assistant.admin.run-config.triggers.message' | translate }}</label>\n <textarea rows=\"2\" [id]=\"id + '-message'\" [value]=\"message()\"\n [attr.aria-invalid]=\"fieldError('message') ? true : null\"\n [attr.aria-describedby]=\"describedBy(id + '-message-hint', 'message', id + '-message-error')\"\n (input)=\"message.set(value($event)); touch('message')\"></textarea>\n <p class=\"hint\" [id]=\"id + '-message-hint'\">{{ 'nh-assistant.admin.run-config.triggers.message-hint' | translate }}</p>\n @if (errorKey('message'); as key) {\n <p class=\"field-error\" [id]=\"id + '-message-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('message')?.params }}</p>\n }\n </div>\n }\n </fieldset>\n }\n @case ('what') {\n <fieldset class=\"group\" data-step=\"what\">\n <legend tabindex=\"-1\">{{ 'nh-assistant.admin.run-config.setup.what.title' | translate }}</legend>\n <div class=\"field\">\n <label [for]=\"id + '-name'\">{{ 'nh-assistant.admin.run-config.setup.what.name' | translate }}</label>\n <input type=\"text\" autocomplete=\"off\" maxlength=\"64\" [id]=\"id + '-name'\" [value]=\"name()\"\n [attr.aria-invalid]=\"fieldError('name') ? true : null\"\n [attr.aria-describedby]=\"describedBy(id + '-name-hint', 'name', id + '-name-error')\"\n (input)=\"name.set(value($event)); touch('name')\" />\n <p class=\"hint\" [id]=\"id + '-name-hint'\">\n {{ (skillId() ? 'nh-assistant.admin.run-config.setup.what.name-id' : 'nh-assistant.admin.run-config.setup.what.name-hint') | translate: { id: skillId() } }}\n </p>\n @if (errorKey('name'); as key) {\n <p class=\"field-error\" [id]=\"id + '-name-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('name')?.params }}</p>\n }\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-description'\">{{ 'nh-assistant.admin.run-config.skills.description-label' | translate }}</label>\n <input type=\"text\" [id]=\"id + '-description'\" [value]=\"description()\"\n [attr.aria-invalid]=\"fieldError('description') ? true : null\"\n [attr.aria-describedby]=\"describedBy(id + '-description-hint', 'description', id + '-description-error')\"\n (input)=\"description.set(value($event)); touch('description')\" />\n <p class=\"hint\" [id]=\"id + '-description-hint'\">\n <span>{{ 'nh-assistant.admin.run-config.skills.description-hint' | translate }}</span>\n <span class=\"counter\">{{ 'nh-assistant.admin.characters' | translate: { count: description().length, max: maxDescription } }}</span>\n </p>\n @if (errorKey('description'); as key) {\n <p class=\"field-error\" [id]=\"id + '-description-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('description')?.params }}</p>\n }\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-rules'\">{{ 'nh-assistant.admin.run-config.way-of-working' | translate }}</label>\n <nh-assistant-picker [inputId]=\"id + '-rules'\" [options]=\"rules()\" [value]=\"ruleHandler()\"\n [noneLabel]=\"'nh-assistant.admin.run-config.model' | translate\"\n [invalid]=\"fieldError('ruleHandler') !== null\"\n [describedBy]=\"describedBy(id + '-rules-hint', 'ruleHandler', id + '-rules-error')\"\n (valueChange)=\"ruleHandler.set($event); touch('ruleHandler'); touch('instructions')\" />\n <p class=\"hint\" [id]=\"id + '-rules-hint'\">\n {{ (ruleHandler() ? 'nh-assistant.admin.run-config.skills.rules-hint' : 'nh-assistant.admin.run-config.skills.model-hint') | translate }}\n </p>\n @if (errorKey('ruleHandler'); as key) {\n <p class=\"field-error\" [id]=\"id + '-rules-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('ruleHandler')?.params }}</p>\n }\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-instructions'\">{{ 'nh-assistant.admin.run-config.skills.instructions' | translate }}</label>\n <textarea rows=\"4\" [id]=\"id + '-instructions'\" [value]=\"instructions()\"\n [attr.aria-invalid]=\"fieldError('instructions') ? true : null\"\n [attr.aria-describedby]=\"describedBy(id + '-instructions-hint', 'instructions', id + '-instructions-error')\"\n (input)=\"instructions.set(value($event)); touch('instructions')\"></textarea>\n <p class=\"hint\" [id]=\"id + '-instructions-hint'\">{{ 'nh-assistant.admin.run-config.skills.instructions-hint' | translate }} {{ 'nh-assistant.admin.run-config.skills.background-hint' | translate }}</p>\n @if (errorKey('instructions'); as key) {\n <p class=\"field-error\" [id]=\"id + '-instructions-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('instructions')?.params }}</p>\n }\n </div>\n <fieldset class=\"field\">\n <legend>{{ 'nh-assistant.admin.run-config.skills.tools' | translate }}</legend>\n <p class=\"hint\">{{ 'nh-assistant.admin.run-config.skills.tools-hint' | translate }}</p>\n @for (choice of toolChoices(); track choice.selector) {\n <label class=\"check\">\n <input type=\"checkbox\" [checked]=\"tools().includes(choice.selector)\" (change)=\"toggleTool(choice.selector, checked($event))\" />\n <span class=\"tool-choice\">\n <span class=\"mono\">{{ choice.selector }}</span>\n @if (choice.description) {\n <span class=\"muted block\">{{ choice.description }}</span>\n } @else if (choice.matches !== null) {\n <span class=\"muted block\">{{ 'nh-assistant.admin.run-config.skills.tools-pattern' | translate: { count: choice.matches } }}</span>\n }\n </span>\n </label>\n } @empty {\n <p class=\"muted\">{{ 'nh-assistant.admin.agent-editor.no-selectors' | translate }}</p>\n }\n @if (errorKey('tools'); as key) {\n <p class=\"field-error\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('tools')?.params }}</p>\n }\n </fieldset>\n </fieldset>\n }\n @case ('who') {\n <fieldset class=\"group\" data-step=\"who\">\n <legend tabindex=\"-1\">{{ 'nh-assistant.admin.run-config.settings.who.title' | translate }}</legend>\n <p class=\"muted\">{{ 'nh-assistant.admin.run-config.settings.who.explain' | translate }}</p>\n <div class=\"choices\" role=\"radiogroup\" [attr.aria-label]=\"'nh-assistant.admin.run-config.settings.who.title' | translate\">\n @for (option of accountabilities; track option) {\n <label class=\"choice\" [class.selected]=\"accountability() === option\">\n <input type=\"radio\" [name]=\"id + '-accountability'\" [value]=\"option\" [checked]=\"accountability() === option\" (change)=\"setAccountability(option)\" />\n <span>\n <strong>{{ 'nh-assistant.admin.run-config.settings.accountability.' + option | translate }}</strong>\n <span class=\"muted\">{{ 'nh-assistant.admin.run-config.settings.accountability-explain.' + option | translate }}</span>\n </span>\n </label>\n }\n </div>\n @if (accountability() === 'service-account') {\n <div class=\"field\">\n <label [for]=\"id + '-account'\">{{ 'nh-assistant.admin.run-config.settings.service-account' | translate }}</label>\n <nh-assistant-account-picker [inputId]=\"id + '-account'\" [value]=\"serviceAccount()\" [searchable]=\"accountSearch()\"\n [invalid]=\"fieldError('serviceAccountActorId') !== null\"\n [describedBy]=\"describedBy(id + '-account-hint', 'serviceAccountActorId', id + '-account-error')\"\n (valueChange)=\"serviceAccount.set($event); touch('serviceAccountActorId')\" />\n <p class=\"hint\" [id]=\"id + '-account-hint'\">\n {{ (accountSearch() ? 'nh-assistant.admin.run-config.account.search-hint' : 'nh-assistant.admin.run-config.account.id-hint') | translate }}\n </p>\n @if (errorKey('serviceAccountActorId'); as key) {\n <p class=\"field-error\" [id]=\"id + '-account-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('serviceAccountActorId')?.params }}</p>\n }\n </div>\n }\n @if (errorKey('accountability'); as key) {\n <p class=\"field-error\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('accountability')?.params }}</p>\n }\n </fieldset>\n }\n @case ('limits') {\n <fieldset class=\"group\" data-step=\"limits\">\n <legend tabindex=\"-1\">{{ 'nh-assistant.admin.run-config.setup.limits.title' | translate }}</legend>\n <div class=\"field\">\n <label [for]=\"id + '-policy'\">{{ 'nh-assistant.admin.run-config.approval-policy' | translate }}</label>\n <nh-assistant-picker [inputId]=\"id + '-policy'\" [options]=\"policies()\" [value]=\"approvalPolicy()\"\n [noneLabel]=\"'nh-assistant.admin.run-config.settings.approval.default' | translate\"\n [invalid]=\"fieldError('approvalPolicy') !== null\"\n [describedBy]=\"describedBy(id + '-policy-hint', 'approvalPolicy', id + '-policy-error')\"\n (valueChange)=\"approvalPolicy.set($event); touch('approvalPolicy')\" />\n <p class=\"hint\" [id]=\"id + '-policy-hint'\">{{ 'nh-assistant.admin.run-config.settings.approval.hint' | translate }}</p>\n @if (errorKey('approvalPolicy'); as key) {\n <p class=\"field-error\" [id]=\"id + '-policy-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('approvalPolicy')?.params }}</p>\n }\n </div>\n <p class=\"muted\" [id]=\"id + '-limits-hint'\">{{ 'nh-assistant.admin.run-config.budget-hint' | translate }}</p>\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-run-tokens'\">{{ 'nh-assistant.admin.run-config.max-tokens' | translate }}</label>\n <input type=\"number\" min=\"1\" inputmode=\"numeric\" [id]=\"id + '-run-tokens'\" [value]=\"maxTokensPerRun()\"\n [attr.aria-invalid]=\"fieldError('maxTokensPerRun') ? true : null\"\n [attr.aria-describedby]=\"describedBy(id + '-limits-hint', 'maxTokensPerRun', id + '-run-tokens-error')\"\n (input)=\"maxTokensPerRun.set(value($event)); touch('maxTokensPerRun')\" />\n @if (errorKey('maxTokensPerRun'); as key) {\n <p class=\"field-error\" [id]=\"id + '-run-tokens-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('maxTokensPerRun')?.params }}</p>\n }\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-daily-calls'\">{{ 'nh-assistant.admin.run-config.daily-tool-calls' | translate }}</label>\n <input type=\"number\" min=\"1\" inputmode=\"numeric\" [id]=\"id + '-daily-calls'\" [value]=\"dailyToolCalls()\"\n [attr.aria-invalid]=\"fieldError('dailyToolCalls') ? true : null\"\n [attr.aria-describedby]=\"describedBy(id + '-limits-hint', 'dailyToolCalls', id + '-daily-calls-error')\"\n (input)=\"dailyToolCalls.set(value($event)); touch('dailyToolCalls'); touch('dailyTokens')\" />\n @if (errorKey('dailyToolCalls'); as key) {\n <p class=\"field-error\" [id]=\"id + '-daily-calls-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('dailyToolCalls')?.params }}</p>\n }\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-daily-tokens'\">{{ 'nh-assistant.admin.run-config.daily-tokens' | translate }}</label>\n <input type=\"number\" min=\"1\" inputmode=\"numeric\" [id]=\"id + '-daily-tokens'\" [value]=\"dailyTokens()\"\n [attr.aria-invalid]=\"fieldError('dailyTokens') ? true : null\"\n [attr.aria-describedby]=\"describedBy(id + '-limits-hint', 'dailyTokens', id + '-daily-tokens-error')\"\n (input)=\"dailyTokens.set(value($event)); touch('dailyTokens'); touch('dailyToolCalls')\" />\n @if (errorKey('dailyTokens'); as key) {\n <p class=\"field-error\" [id]=\"id + '-daily-tokens-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('dailyTokens')?.params }}</p>\n }\n </div>\n </div>\n </fieldset>\n }\n @case ('review') {\n <fieldset class=\"group\" data-step=\"review\">\n <legend tabindex=\"-1\">{{ 'nh-assistant.admin.run-config.setup.review.title' | translate }}</legend>\n <dl class=\"review\">\n <dt>{{ 'nh-assistant.admin.run-config.setup.step.when' | translate }}</dt>\n <dd>\n @if (source() === 'event') {\n {{ 'nh-assistant.admin.run-config.setup.review.event' | translate: { event: event() } }}\n @if (eventDescription(); as description) { <span class=\"muted block\">{{ description }}</span> }\n } @else if (target() === 'application') {\n <!-- Without a subject set the schedule runs for the whole application, per division for a per-division agent. -->\n <span data-review-target>{{ (perDivision() ? 'nh-assistant.admin.run-config.setup.review.schedule-divisions' : 'nh-assistant.admin.run-config.setup.review.schedule-application') | translate: { cron: cron(), zone: timeZone() || 'UTC' } }}</span>\n } @else {\n {{ 'nh-assistant.admin.run-config.setup.review.schedule' | translate: { cron: cron(), zone: timeZone() || 'UTC', set: subjectSet() } }}\n @if (subjectSetDescription(); as description) { <span class=\"muted block\">{{ description }}</span> }\n }\n </dd>\n <dt>{{ 'nh-assistant.admin.run-config.setup.step.what' | translate }}</dt>\n <dd>\n <strong>{{ name() }}</strong> <span class=\"mono\">({{ skillId() }})</span>\n <span class=\"block\">{{ description() }}</span>\n <span class=\"muted block\">\n {{ ruleHandler() ? ruleHandler() + (ruleDescription() ? ' \u2014 ' + ruleDescription() : '') : ('nh-assistant.admin.run-config.model' | translate) }}\n </span>\n </dd>\n <dt>{{ 'nh-assistant.admin.run-config.setup.step.who' | translate }}</dt>\n <dd>{{ 'nh-assistant.admin.run-config.settings.accountability.' + accountability() | translate }}</dd>\n <dt>{{ 'nh-assistant.admin.run-config.setup.step.limits' | translate }}</dt>\n <dd>\n {{ approvalPolicy() || ('nh-assistant.admin.run-config.settings.approval.default' | translate) }}\n @if (policyDescription(); as description) { <span class=\"muted block\">{{ description }}</span> }\n </dd>\n </dl>\n <p class=\"hint\">{{ 'nh-assistant.admin.run-config.setup.review.shadow' | translate }}</p>\n </fieldset>\n }\n }\n\n @if (hasErrors()) {\n <div class=\"form-error\" role=\"alert\" data-form-error>\n <nh-assistant-icon name=\"warning\" />\n <div>\n @if (serverError(); as failure) {\n @if (failure.code === 'assistant-validation') {\n {{ 'nh-assistant.admin.run-config.check-fields' | translate }}\n } @else {\n {{ errorKeys() | nhAssistantTranslate: failure.code }}\n }\n } @else {\n {{ 'nh-assistant.admin.run-config.check-fields' | translate }}\n }\n </div>\n </div>\n }\n <div class=\"actions\">\n <button class=\"secondary\" type=\"button\" (click)=\"cancelled.emit()\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n @if (stepIndex() > 0) {\n <button class=\"secondary\" type=\"button\" [disabled]=\"busy()\" (click)=\"back()\">{{ 'nh-assistant.admin.run-config.setup.back' | translate }}</button>\n }\n @if (step() === 'review') {\n <button class=\"secondary\" type=\"button\" data-setup-live [disabled]=\"busy()\" (click)=\"save(true)\">{{ 'nh-assistant.admin.run-config.setup.save-live' | translate }}</button>\n <button class=\"primary\" type=\"submit\" [disabled]=\"busy()\">{{ (busy() ? 'nh-assistant.admin.saving' : 'nh-assistant.admin.run-config.setup.save-shadow') | translate }}</button>\n } @else {\n <button class=\"primary\" type=\"submit\">{{ 'nh-assistant.admin.run-config.setup.next' | translate }}</button>\n }\n </div>\n </form>\n }\n</section>\n", styles: [":host{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){:host{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}:host{display:block;min-width:0;color:var(--nha-text);font-family:var(--nha-font);font-size:14px}:host fieldset{min-width:0;margin:0;border:0;padding:0}:host legend,:host .label,:host .field>label{display:block;margin-bottom:6px;padding:0;color:var(--nha-text);font-size:12px;font-weight:700}:host input[type=text],:host input[type=url],:host input[type=password],:host input[type=search],:host input[type=email],:host input[type=number],:host input[type=date],:host input[type=time],:host input[type=datetime-local],:host select,:host textarea{box-sizing:border-box;width:100%;min-width:0;min-height:38px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:8px 10px;color:var(--nha-text);background:var(--nha-background);font:inherit;font-size:13px}:host input[type=text]:focus-visible,:host input[type=url]:focus-visible,:host input[type=password]:focus-visible,:host input[type=search]:focus-visible,:host input[type=email]:focus-visible,:host input[type=number]:focus-visible,:host input[type=date]:focus-visible,:host input[type=time]:focus-visible,:host input[type=datetime-local]:focus-visible,:host select:focus-visible,:host textarea:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host input[type=text]:disabled,:host input[type=url]:disabled,:host input[type=password]:disabled,:host input[type=search]:disabled,:host input[type=email]:disabled,:host input[type=number]:disabled,:host input[type=date]:disabled,:host input[type=time]:disabled,:host input[type=datetime-local]:disabled,:host select:disabled,:host textarea:disabled{cursor:not-allowed;opacity:.6}:host input[type=text][aria-invalid=true],:host input[type=url][aria-invalid=true],:host input[type=password][aria-invalid=true],:host input[type=search][aria-invalid=true],:host input[type=email][aria-invalid=true],:host input[type=number][aria-invalid=true],:host input[type=date][aria-invalid=true],:host input[type=time][aria-invalid=true],:host input[type=datetime-local][aria-invalid=true],:host select[aria-invalid=true],:host textarea[aria-invalid=true]{border-color:var(--nha-danger)}:host textarea{line-height:1.45;resize:vertical}:host input[type=number]{font-variant-numeric:tabular-nums}:host .mono{font-family:var(--nha-mono);font-size:12px}:host .field{display:flex;flex-direction:column;min-width:0}:host .hint{display:flex;justify-content:space-between;gap:8px;margin:4px 2px 0;color:var(--nha-text-muted);font-size:11px}:host .counter{flex:none;font-variant-numeric:tabular-nums}:host .error-text{color:var(--nha-danger)}:host .field-error{display:flex;align-items:flex-start;gap:6px;margin:4px 2px 0;color:var(--nha-danger);font-size:12px;font-weight:600;overflow-wrap:anywhere}:host .field-error nh-assistant-icon{flex:none;margin-top:1px}:host .form-error{box-sizing:border-box;display:flex;align-items:flex-start;gap:8px;margin:0;border:1px solid var(--nha-danger);border-radius:var(--nha-radius-control);padding:8px 12px;color:var(--nha-text);background:var(--nha-danger-soft);font-size:13px;overflow-wrap:anywhere}:host .form-error ul{margin:4px 0 0;padding-left:18px}:host .message{box-sizing:border-box;display:flex;align-items:center;gap:8px;margin:0;border:1px solid transparent;border-radius:var(--nha-radius-control);padding:8px 12px;font-size:13px;overflow-wrap:anywhere}:host .message.error{border-color:var(--nha-danger);color:var(--nha-text);background:var(--nha-danger-soft)}:host .message.warning{border-color:var(--nha-warning);color:var(--nha-text);background:var(--nha-warning-soft)}:host .message.success{border-color:var(--nha-success);color:var(--nha-text);background:var(--nha-success-soft)}:host .message.info{border-color:var(--nha-border);color:var(--nha-text);background:var(--nha-surface)}:host .message nh-assistant-icon{flex:none}:host button.link{min-height:0;border:0;padding:0;color:var(--nha-accent, currentColor);background:none;font:inherit;text-decoration:underline;cursor:pointer}:host button.link:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host button.link:disabled{cursor:not-allowed;opacity:.55}:host .primary,:host .secondary,:host .danger{display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;gap:6px;min-height:36px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px 14px;font:inherit;font-size:13px;font-weight:700;white-space:nowrap;cursor:pointer}:host .primary:focus-visible,:host .secondary:focus-visible,:host .danger:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .primary:disabled,:host .secondary:disabled,:host .danger:disabled{cursor:not-allowed;opacity:.55}:host .primary{border-color:var(--nha-accent);color:var(--nha-accent-contrast);background:var(--nha-accent)}:host .primary:hover:not(:disabled){filter:brightness(1.12)}:host .secondary{color:var(--nha-text);background:transparent}:host .secondary:hover:not(:disabled){background:var(--nha-surface-strong)}:host .danger{border-color:var(--nha-danger);color:var(--nha-danger);background:transparent}:host .danger:hover:not(:disabled){background:var(--nha-danger-soft)}:host .icon-button{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:1px solid transparent;border-radius:var(--nha-radius-control);color:var(--nha-text-muted);background:transparent;cursor:pointer}:host .icon-button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .icon-button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}:host .icon-button:disabled{cursor:not-allowed;opacity:.5}:host .actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px}:host .badge{display:inline-flex;align-items:center;gap:4px;border-radius:999px;padding:2px 8px;color:var(--nha-text-muted);background:var(--nha-surface-strong);font-size:11px;font-weight:700;white-space:nowrap}:host .badge.success{color:var(--nha-success);background:var(--nha-success-soft)}:host .badge.warning{color:var(--nha-warning);background:var(--nha-warning-soft)}:host .badge.danger{color:var(--nha-danger);background:var(--nha-danger-soft)}:host .visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}.section{display:flex;flex-direction:column;gap:16px;min-width:0}.section-header{display:flex;flex-wrap:wrap;align-items:flex-start;justify-content:space-between;gap:12px}.section-header>div{min-width:0;max-width:72ch}h3{margin:0 0 4px;font-size:17px;font-weight:750}h4{margin:0 0 8px;font-size:14px;font-weight:700}.muted{margin:0;color:var(--nha-text-muted);font-size:13px}.state{display:flex;flex-direction:column;align-items:flex-start;gap:10px;margin:0;border:1px dashed var(--nha-border);border-radius:var(--nha-radius);padding:20px;color:var(--nha-text-muted);font-size:13px}.state p{margin:0}.stack{display:flex;flex-direction:column;gap:14px;min-width:0}.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,240px),1fr));gap:14px}.meta{display:flex;flex-wrap:wrap;align-items:center;gap:8px 12px;color:var(--nha-text-muted);font-size:12px;overflow-wrap:anywhere}textarea.large{min-height:260px}.catalog-error{flex-wrap:wrap}.grow{flex:1;min-width:0}.subsection{display:flex;flex-direction:column;gap:8px;border-top:1px solid var(--nha-border);padding-top:14px}.history{display:grid;gap:6px;margin:0;padding:0;list-style:none}.history li{display:flex;flex-wrap:wrap;align-items:center;gap:6px 12px;font-size:12px}.cards{display:grid;gap:10px;margin:0;padding:0;list-style:none}.card{display:flex;flex-direction:column;gap:8px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius);padding:14px;background:var(--nha-background)}.card.inactive{opacity:.8}.card-header{display:flex;flex-wrap:wrap;align-items:center;gap:8px;min-width:0}.card-header strong{font-size:14px;overflow-wrap:anywhere}.card-actions{display:flex;flex-wrap:wrap;gap:8px}.check{display:flex;align-items:flex-start;gap:8px;font-size:13px;cursor:pointer}.check input{width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.check.described>span{display:grid;gap:2px;min-width:0;overflow-wrap:anywhere}.checks{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));gap:6px 12px;min-width:0;overflow-wrap:anywhere}.chips{display:flex;flex-wrap:wrap;gap:6px;margin:0;padding:0;list-style:none}.chip{display:inline-flex;align-items:center;gap:2px;border:1px solid var(--nha-border);border-radius:999px;padding:2px 2px 2px 10px;background:var(--nha-surface);font-family:var(--nha-mono);font-size:12px}.chip 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;width:26px;height:26px;border-radius:999px}.chip button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.chip button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}.chip button:disabled{cursor:not-allowed;opacity:.5}.inline{display:flex;flex-wrap:wrap;gap:8px}.inline>input{flex:1 1 180px}.catalog{display:grid;gap:4px;max-height:260px;margin:8px 0 0;overflow-y:auto;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px;list-style:none}.catalog li label{display:flex;flex-wrap:wrap;align-items:center;gap:6px 8px;border-radius:8px;padding:6px;cursor:pointer}.catalog li label:hover{background:var(--nha-surface)}.catalog .tool-id{font-family:var(--nha-mono);font-size:12px;overflow-wrap:anywhere}.skills{display:grid;gap:8px;margin:0;padding:0;list-style:none}.skill{display:grid;gap:6px;border-left:3px solid var(--nha-border);padding:4px 0 4px 12px}.uses{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));gap:8px}.use{display:flex;align-items:flex-start;gap:10px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:10px 12px;cursor:pointer;overflow-wrap:anywhere}.use input{flex:none;width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.use>span{display:grid;gap:2px;min-width:0}.use strong{font-size:13px}.use.selected{border-color:var(--nha-highlight);background:var(--nha-surface)}.use:focus-within{outline:2px solid var(--nha-highlight);outline-offset:2px}.discovery{display:grid;gap:8px;padding:10px 12px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control)}.discovery dl{display:grid;grid-template-columns:max-content minmax(0,1fr);gap:4px 12px;margin:0}.discovery dt{color:var(--nha-text-muted)}.discovery dd{margin:0;overflow-wrap:anywhere}\n", "h5{margin:0;font-size:15px;font-weight:750}.group{display:flex;flex-direction:column;gap:10px;min-width:0;border-top:1px solid var(--nha-border);padding-top:14px}.group>legend{float:left;width:100%;margin-bottom:2px;font-size:14px;font-weight:750}.choices{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,220px),1fr));gap:8px}.choice{display:flex;align-items:flex-start;gap:10px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:10px 12px;cursor:pointer;overflow-wrap:anywhere}.choice input{flex:none;width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.choice>span{display:grid;gap:2px;min-width:0}.choice strong{font-size:13px}.choice.selected{border-color:var(--nha-highlight);background:var(--nha-surface)}.choice:focus-within{outline:2px solid var(--nha-highlight);outline-offset:2px}.block{display:block;margin-top:2px}.advanced{border-top:1px solid var(--nha-border);padding-top:12px}.advanced>summary{width:fit-content;border-radius:6px;font-size:14px;font-weight:750;cursor:pointer}.advanced>summary:focus-visible{outline:2px solid var(--nha-highlight);outline-offset:2px}.advanced>.stack{margin-top:12px}.examples{display:flex;flex-wrap:wrap;gap:6px;margin:6px 0 0;padding:0;list-style:none}.examples button{border:1px solid var(--nha-border);border-radius:999px;padding:3px 10px;color:var(--nha-text);background:transparent;font:inherit;font-size:12px;cursor:pointer}.examples button:hover{background:var(--nha-surface-strong)}.examples button:focus-visible{outline:2px solid var(--nha-highlight);outline-offset:2px}.steps{display:flex;flex-wrap:wrap;gap:6px;margin:0;padding:0;list-style:none}.steps li{display:inline-flex;align-items:center;gap:6px;border-radius:999px;padding:4px 10px;color:var(--nha-text-muted);background:var(--nha-surface-strong);font-size:12px;font-weight:700}.steps li[aria-current=step]{color:var(--nha-accent-contrast);background:var(--nha-accent)}.steps li.done{color:var(--nha-success);background:var(--nha-success-soft)}.review{display:grid;grid-template-columns:minmax(0,max-content) minmax(0,1fr);gap:6px 16px;margin:0;font-size:13px}.review dt{color:var(--nha-text-muted)}.review dd{margin:0;overflow-wrap:anywhere}.tool-choice{display:flex;flex-direction:column;gap:2px;min-width:0;overflow-wrap:anywhere}@media(max-width:520px){.review{grid-template-columns:minmax(0,1fr)}.review dd{margin-bottom:6px}}.table-scroll{max-width:100%;overflow-x:auto}\n"] }]
|
|
2502
2619
|
}], propDecorators: { agentId: [{ type: i0.Input, args: [{ isSignal: true, alias: "agentId", required: true }] }], agentName: [{ type: i0.Input, args: [{ isSignal: true, alias: "agentName", required: false }] }], catalog: [{ type: i0.Input, args: [{ isSignal: true, alias: "catalog", required: false }] }], worksOn: [{ type: i0.Input, args: [{ isSignal: true, alias: "worksOn", required: false }] }], toolSelectors: [{ type: i0.Input, args: [{ isSignal: true, alias: "toolSelectors", required: false }] }], toolCatalog: [{ type: i0.Input, args: [{ isSignal: true, alias: "toolCatalog", required: false }] }], takenSkillIds: [{ type: i0.Input, args: [{ isSignal: true, alias: "takenSkillIds", required: false }] }], takenTriggerIds: [{ type: i0.Input, args: [{ isSignal: true, alias: "takenTriggerIds", required: false }] }], completed: [{ type: i0.Output, args: ["completed"] }], cancelled: [{ type: i0.Output, args: ["cancelled"] }] } });
|
|
2503
2620
|
|
|
2621
|
+
const controls = {
|
|
2622
|
+
maxFileBytes: { field: 'maxFileBytes', key: 'file-size', label: 'max-file-megabytes', step: 'any', inputMode: 'decimal' },
|
|
2623
|
+
maxFilePages: { field: 'maxFilePages', key: 'file-pages', label: 'max-file-pages', step: '1', inputMode: 'numeric' },
|
|
2624
|
+
maxFilesPerTurn: { field: 'maxFilesPerTurn', key: 'files-per-turn', label: 'max-files-per-turn', step: '1', inputMode: 'numeric' },
|
|
2625
|
+
maxFileTokensPerTurn: { field: 'maxFileTokensPerTurn', key: 'file-tokens', label: 'max-file-tokens-per-turn', step: '1', inputMode: 'numeric' }
|
|
2626
|
+
};
|
|
2627
|
+
/**
|
|
2628
|
+
* The lower file limits of an agent or a skill that accepts files, next to its "Accepts files"
|
|
2629
|
+
* switch: the largest file in megabytes, the most pages of one file, the most files of one turn and
|
|
2630
|
+
* the most file tokens of one turn. Each field names the application's limit, which it can only
|
|
2631
|
+
* lower; an empty field keeps it. Errors appear at their fields with the form's other errors.
|
|
2632
|
+
*/
|
|
2633
|
+
class NhAssistantFileLimitsFieldsComponent {
|
|
2634
|
+
constructor() {
|
|
2635
|
+
/** The form's limits. */
|
|
2636
|
+
this.draft = input.required(...(ngDevMode ? [{ debugName: "draft" }] : []));
|
|
2637
|
+
/** The application's limits from the run catalog; `null` on older servers. */
|
|
2638
|
+
this.application = input(null, ...(ngDevMode ? [{ debugName: "application" }] : []));
|
|
2639
|
+
/** The form's field errors, by server field name. */
|
|
2640
|
+
this.errors = input({}, ...(ngDevMode ? [{ debugName: "errors" }] : []));
|
|
2641
|
+
/** The prefix of the controls' ids. */
|
|
2642
|
+
this.idPrefix = input.required(...(ngDevMode ? [{ debugName: "idPrefix" }] : []));
|
|
2643
|
+
/** The hint under the title: what an empty field keeps. */
|
|
2644
|
+
this.hintKey = input.required(...(ngDevMode ? [{ debugName: "hintKey" }] : []));
|
|
2645
|
+
/** A field the administrator changed, so the form clears its error. */
|
|
2646
|
+
this.touched = output();
|
|
2647
|
+
this.controls = fileLimitFields.map(field => controls[field]);
|
|
2648
|
+
}
|
|
2649
|
+
idOf(control, suffix = '') {
|
|
2650
|
+
return `${this.idPrefix()}-${control.key}${suffix}`;
|
|
2651
|
+
}
|
|
2652
|
+
/** The application's limit as the field shows it: megabytes for the file size. */
|
|
2653
|
+
applicationLimit(control) {
|
|
2654
|
+
const application = this.application();
|
|
2655
|
+
if (!application) {
|
|
2656
|
+
return null;
|
|
2657
|
+
}
|
|
2658
|
+
const value = application[control.field];
|
|
2659
|
+
return control.field === 'maxFileBytes' ? `${megabytesText(value)} MB` : String(value);
|
|
2660
|
+
}
|
|
2661
|
+
/** The upper bound a browser shows for the field. */
|
|
2662
|
+
max(control) {
|
|
2663
|
+
const application = this.application();
|
|
2664
|
+
if (!application) {
|
|
2665
|
+
return null;
|
|
2666
|
+
}
|
|
2667
|
+
return control.field === 'maxFileBytes' ? application.maxFileBytes / (1024 * 1024) : application[control.field];
|
|
2668
|
+
}
|
|
2669
|
+
errorKey(control) {
|
|
2670
|
+
return fieldErrorKey(this.errors()[control.field]);
|
|
2671
|
+
}
|
|
2672
|
+
errorParams(control) {
|
|
2673
|
+
return this.errors()[control.field]?.params;
|
|
2674
|
+
}
|
|
2675
|
+
describedBy(control) {
|
|
2676
|
+
const ids = [`${this.idPrefix()}-file-limits-hint`];
|
|
2677
|
+
if (this.application()) {
|
|
2678
|
+
ids.push(this.idOf(control, '-limit'));
|
|
2679
|
+
}
|
|
2680
|
+
if (this.errors()[control.field]) {
|
|
2681
|
+
ids.push(this.idOf(control, '-error'));
|
|
2682
|
+
}
|
|
2683
|
+
return ids.join(' ');
|
|
2684
|
+
}
|
|
2685
|
+
change(control, event) {
|
|
2686
|
+
this.draft().text(control.field).set(event.target.value);
|
|
2687
|
+
this.touched.emit(control.field);
|
|
2688
|
+
}
|
|
2689
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: NhAssistantFileLimitsFieldsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
2690
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.34", type: NhAssistantFileLimitsFieldsComponent, isStandalone: true, selector: "nh-assistant-file-limits-fields", inputs: { draft: { classPropertyName: "draft", publicName: "draft", isSignal: true, isRequired: true, transformFunction: null }, application: { classPropertyName: "application", publicName: "application", isSignal: true, isRequired: false, transformFunction: null }, errors: { classPropertyName: "errors", publicName: "errors", isSignal: true, isRequired: false, transformFunction: null }, idPrefix: { classPropertyName: "idPrefix", publicName: "idPrefix", isSignal: true, isRequired: true, transformFunction: null }, hintKey: { classPropertyName: "hintKey", publicName: "hintKey", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { touched: "touched" }, ngImport: i0, template: "<fieldset class=\"field file-limits\" data-file-limits>\n <legend>{{ 'nh-assistant.admin.run-config.files.limits-title' | translate }}</legend>\n <p class=\"hint\" [id]=\"idPrefix() + '-file-limits-hint'\">{{ hintKey() | translate }}</p>\n <div class=\"grid\">\n @for (control of controls; track control.field) {\n <div class=\"field\" [attr.data-file-limit]=\"control.field\">\n <label [for]=\"idOf(control)\">{{ 'nh-assistant.admin.run-config.files.' + control.label | translate }}</label>\n <input type=\"number\" min=\"0\" [attr.step]=\"control.step\" [attr.max]=\"max(control)\" [attr.inputmode]=\"control.inputMode\"\n [id]=\"idOf(control)\" [value]=\"draft().text(control.field)()\"\n [attr.aria-invalid]=\"errorKey(control) ? true : null\"\n [attr.aria-describedby]=\"describedBy(control)\"\n (input)=\"change(control, $event)\" />\n @if (applicationLimit(control); as limit) {\n <p class=\"hint\" [id]=\"idOf(control, '-limit')\">{{ 'nh-assistant.admin.run-config.files.application-limit' | translate: { value: limit } }}</p>\n }\n @if (errorKey(control); as key) {\n <p class=\"field-error\" [id]=\"idOf(control, '-error')\"><nh-assistant-icon name=\"warning\" />{{ key | translate: errorParams(control) }}</p>\n }\n </div>\n }\n </div>\n</fieldset>\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;min-width:0;color:var(--nha-text);font-family:var(--nha-font);font-size:14px}:host fieldset{min-width:0;margin:0;border:0;padding:0}:host legend,:host .label,:host .field>label{display:block;margin-bottom:6px;padding:0;color:var(--nha-text);font-size:12px;font-weight:700}:host input[type=text],:host input[type=url],:host input[type=password],:host input[type=search],:host input[type=email],:host input[type=number],:host input[type=date],:host input[type=time],:host input[type=datetime-local],:host select,:host textarea{box-sizing:border-box;width:100%;min-width:0;min-height:38px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:8px 10px;color:var(--nha-text);background:var(--nha-background);font:inherit;font-size:13px}:host input[type=text]:focus-visible,:host input[type=url]:focus-visible,:host input[type=password]:focus-visible,:host input[type=search]:focus-visible,:host input[type=email]:focus-visible,:host input[type=number]:focus-visible,:host input[type=date]:focus-visible,:host input[type=time]:focus-visible,:host input[type=datetime-local]:focus-visible,:host select:focus-visible,:host textarea:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host input[type=text]:disabled,:host input[type=url]:disabled,:host input[type=password]:disabled,:host input[type=search]:disabled,:host input[type=email]:disabled,:host input[type=number]:disabled,:host input[type=date]:disabled,:host input[type=time]:disabled,:host input[type=datetime-local]:disabled,:host select:disabled,:host textarea:disabled{cursor:not-allowed;opacity:.6}:host input[type=text][aria-invalid=true],:host input[type=url][aria-invalid=true],:host input[type=password][aria-invalid=true],:host input[type=search][aria-invalid=true],:host input[type=email][aria-invalid=true],:host input[type=number][aria-invalid=true],:host input[type=date][aria-invalid=true],:host input[type=time][aria-invalid=true],:host input[type=datetime-local][aria-invalid=true],:host select[aria-invalid=true],:host textarea[aria-invalid=true]{border-color:var(--nha-danger)}:host textarea{line-height:1.45;resize:vertical}:host input[type=number]{font-variant-numeric:tabular-nums}:host .mono{font-family:var(--nha-mono);font-size:12px}:host .field{display:flex;flex-direction:column;min-width:0}:host .hint{display:flex;justify-content:space-between;gap:8px;margin:4px 2px 0;color:var(--nha-text-muted);font-size:11px}:host .counter{flex:none;font-variant-numeric:tabular-nums}:host .error-text{color:var(--nha-danger)}:host .field-error{display:flex;align-items:flex-start;gap:6px;margin:4px 2px 0;color:var(--nha-danger);font-size:12px;font-weight:600;overflow-wrap:anywhere}:host .field-error nh-assistant-icon{flex:none;margin-top:1px}:host .form-error{box-sizing:border-box;display:flex;align-items:flex-start;gap:8px;margin:0;border:1px solid var(--nha-danger);border-radius:var(--nha-radius-control);padding:8px 12px;color:var(--nha-text);background:var(--nha-danger-soft);font-size:13px;overflow-wrap:anywhere}:host .form-error ul{margin:4px 0 0;padding-left:18px}:host .message{box-sizing:border-box;display:flex;align-items:center;gap:8px;margin:0;border:1px solid transparent;border-radius:var(--nha-radius-control);padding:8px 12px;font-size:13px;overflow-wrap:anywhere}:host .message.error{border-color:var(--nha-danger);color:var(--nha-text);background:var(--nha-danger-soft)}:host .message.warning{border-color:var(--nha-warning);color:var(--nha-text);background:var(--nha-warning-soft)}:host .message.success{border-color:var(--nha-success);color:var(--nha-text);background:var(--nha-success-soft)}:host .message.info{border-color:var(--nha-border);color:var(--nha-text);background:var(--nha-surface)}:host .message nh-assistant-icon{flex:none}:host button.link{min-height:0;border:0;padding:0;color:var(--nha-accent, currentColor);background:none;font:inherit;text-decoration:underline;cursor:pointer}:host button.link:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host button.link:disabled{cursor:not-allowed;opacity:.55}:host .primary,:host .secondary,:host .danger{display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;gap:6px;min-height:36px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px 14px;font:inherit;font-size:13px;font-weight:700;white-space:nowrap;cursor:pointer}:host .primary:focus-visible,:host .secondary:focus-visible,:host .danger:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .primary:disabled,:host .secondary:disabled,:host .danger:disabled{cursor:not-allowed;opacity:.55}:host .primary{border-color:var(--nha-accent);color:var(--nha-accent-contrast);background:var(--nha-accent)}:host .primary:hover:not(:disabled){filter:brightness(1.12)}:host .secondary{color:var(--nha-text);background:transparent}:host .secondary:hover:not(:disabled){background:var(--nha-surface-strong)}:host .danger{border-color:var(--nha-danger);color:var(--nha-danger);background:transparent}:host .danger:hover:not(:disabled){background:var(--nha-danger-soft)}:host .icon-button{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:1px solid transparent;border-radius:var(--nha-radius-control);color:var(--nha-text-muted);background:transparent;cursor:pointer}:host .icon-button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .icon-button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}:host .icon-button:disabled{cursor:not-allowed;opacity:.5}:host .actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px}:host .badge{display:inline-flex;align-items:center;gap:4px;border-radius:999px;padding:2px 8px;color:var(--nha-text-muted);background:var(--nha-surface-strong);font-size:11px;font-weight:700;white-space:nowrap}:host .badge.success{color:var(--nha-success);background:var(--nha-success-soft)}:host .badge.warning{color:var(--nha-warning);background:var(--nha-warning-soft)}:host .badge.danger{color:var(--nha-danger);background:var(--nha-danger-soft)}:host .visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}.section{display:flex;flex-direction:column;gap:16px;min-width:0}.section-header{display:flex;flex-wrap:wrap;align-items:flex-start;justify-content:space-between;gap:12px}.section-header>div{min-width:0;max-width:72ch}h3{margin:0 0 4px;font-size:17px;font-weight:750}h4{margin:0 0 8px;font-size:14px;font-weight:700}.muted{margin:0;color:var(--nha-text-muted);font-size:13px}.state{display:flex;flex-direction:column;align-items:flex-start;gap:10px;margin:0;border:1px dashed var(--nha-border);border-radius:var(--nha-radius);padding:20px;color:var(--nha-text-muted);font-size:13px}.state p{margin:0}.stack{display:flex;flex-direction:column;gap:14px;min-width:0}.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,240px),1fr));gap:14px}.meta{display:flex;flex-wrap:wrap;align-items:center;gap:8px 12px;color:var(--nha-text-muted);font-size:12px;overflow-wrap:anywhere}textarea.large{min-height:260px}.catalog-error{flex-wrap:wrap}.grow{flex:1;min-width:0}.subsection{display:flex;flex-direction:column;gap:8px;border-top:1px solid var(--nha-border);padding-top:14px}.history{display:grid;gap:6px;margin:0;padding:0;list-style:none}.history li{display:flex;flex-wrap:wrap;align-items:center;gap:6px 12px;font-size:12px}.cards{display:grid;gap:10px;margin:0;padding:0;list-style:none}.card{display:flex;flex-direction:column;gap:8px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius);padding:14px;background:var(--nha-background)}.card.inactive{opacity:.8}.card-header{display:flex;flex-wrap:wrap;align-items:center;gap:8px;min-width:0}.card-header strong{font-size:14px;overflow-wrap:anywhere}.card-actions{display:flex;flex-wrap:wrap;gap:8px}.check{display:flex;align-items:flex-start;gap:8px;font-size:13px;cursor:pointer}.check input{width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.check.described>span{display:grid;gap:2px;min-width:0;overflow-wrap:anywhere}.checks{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));gap:6px 12px;min-width:0;overflow-wrap:anywhere}.chips{display:flex;flex-wrap:wrap;gap:6px;margin:0;padding:0;list-style:none}.chip{display:inline-flex;align-items:center;gap:2px;border:1px solid var(--nha-border);border-radius:999px;padding:2px 2px 2px 10px;background:var(--nha-surface);font-family:var(--nha-mono);font-size:12px}.chip 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;width:26px;height:26px;border-radius:999px}.chip button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.chip button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}.chip button:disabled{cursor:not-allowed;opacity:.5}.inline{display:flex;flex-wrap:wrap;gap:8px}.inline>input{flex:1 1 180px}.catalog{display:grid;gap:4px;max-height:260px;margin:8px 0 0;overflow-y:auto;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px;list-style:none}.catalog li label{display:flex;flex-wrap:wrap;align-items:center;gap:6px 8px;border-radius:8px;padding:6px;cursor:pointer}.catalog li label:hover{background:var(--nha-surface)}.catalog .tool-id{font-family:var(--nha-mono);font-size:12px;overflow-wrap:anywhere}.skills{display:grid;gap:8px;margin:0;padding:0;list-style:none}.skill{display:grid;gap:6px;border-left:3px solid var(--nha-border);padding:4px 0 4px 12px}.uses{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));gap:8px}.use{display:flex;align-items:flex-start;gap:10px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:10px 12px;cursor:pointer;overflow-wrap:anywhere}.use input{flex:none;width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.use>span{display:grid;gap:2px;min-width:0}.use strong{font-size:13px}.use.selected{border-color:var(--nha-highlight);background:var(--nha-surface)}.use:focus-within{outline:2px solid var(--nha-highlight);outline-offset:2px}.discovery{display:grid;gap:8px;padding:10px 12px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control)}.discovery dl{display:grid;grid-template-columns:max-content minmax(0,1fr);gap:4px 12px;margin:0}.discovery dt{color:var(--nha-text-muted)}.discovery dd{margin:0;overflow-wrap:anywhere}\n", "h5{margin:0;font-size:15px;font-weight:750}.group{display:flex;flex-direction:column;gap:10px;min-width:0;border-top:1px solid var(--nha-border);padding-top:14px}.group>legend{float:left;width:100%;margin-bottom:2px;font-size:14px;font-weight:750}.choices{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,220px),1fr));gap:8px}.choice{display:flex;align-items:flex-start;gap:10px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:10px 12px;cursor:pointer;overflow-wrap:anywhere}.choice input{flex:none;width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.choice>span{display:grid;gap:2px;min-width:0}.choice strong{font-size:13px}.choice.selected{border-color:var(--nha-highlight);background:var(--nha-surface)}.choice:focus-within{outline:2px solid var(--nha-highlight);outline-offset:2px}.block{display:block;margin-top:2px}.advanced{border-top:1px solid var(--nha-border);padding-top:12px}.advanced>summary{width:fit-content;border-radius:6px;font-size:14px;font-weight:750;cursor:pointer}.advanced>summary:focus-visible{outline:2px solid var(--nha-highlight);outline-offset:2px}.advanced>.stack{margin-top:12px}.examples{display:flex;flex-wrap:wrap;gap:6px;margin:6px 0 0;padding:0;list-style:none}.examples button{border:1px solid var(--nha-border);border-radius:999px;padding:3px 10px;color:var(--nha-text);background:transparent;font:inherit;font-size:12px;cursor:pointer}.examples button:hover{background:var(--nha-surface-strong)}.examples button:focus-visible{outline:2px solid var(--nha-highlight);outline-offset:2px}.steps{display:flex;flex-wrap:wrap;gap:6px;margin:0;padding:0;list-style:none}.steps li{display:inline-flex;align-items:center;gap:6px;border-radius:999px;padding:4px 10px;color:var(--nha-text-muted);background:var(--nha-surface-strong);font-size:12px;font-weight:700}.steps li[aria-current=step]{color:var(--nha-accent-contrast);background:var(--nha-accent)}.steps li.done{color:var(--nha-success);background:var(--nha-success-soft)}.review{display:grid;grid-template-columns:minmax(0,max-content) minmax(0,1fr);gap:6px 16px;margin:0;font-size:13px}.review dt{color:var(--nha-text-muted)}.review dd{margin:0;overflow-wrap:anywhere}.tool-choice{display:flex;flex-direction:column;gap:2px;min-width:0;overflow-wrap:anywhere}@media(max-width:520px){.review{grid-template-columns:minmax(0,1fr)}.review dd{margin-bottom:6px}}.table-scroll{max-width:100%;overflow-x:auto}\n"], dependencies: [{ kind: "component", type: _NhAssistantIconComponent, selector: "nh-assistant-icon", inputs: ["name"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
2691
|
+
}
|
|
2692
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: NhAssistantFileLimitsFieldsComponent, decorators: [{
|
|
2693
|
+
type: Component,
|
|
2694
|
+
args: [{ selector: 'nh-assistant-file-limits-fields', standalone: true, imports: [TranslatePipe, _NhAssistantIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<fieldset class=\"field file-limits\" data-file-limits>\n <legend>{{ 'nh-assistant.admin.run-config.files.limits-title' | translate }}</legend>\n <p class=\"hint\" [id]=\"idPrefix() + '-file-limits-hint'\">{{ hintKey() | translate }}</p>\n <div class=\"grid\">\n @for (control of controls; track control.field) {\n <div class=\"field\" [attr.data-file-limit]=\"control.field\">\n <label [for]=\"idOf(control)\">{{ 'nh-assistant.admin.run-config.files.' + control.label | translate }}</label>\n <input type=\"number\" min=\"0\" [attr.step]=\"control.step\" [attr.max]=\"max(control)\" [attr.inputmode]=\"control.inputMode\"\n [id]=\"idOf(control)\" [value]=\"draft().text(control.field)()\"\n [attr.aria-invalid]=\"errorKey(control) ? true : null\"\n [attr.aria-describedby]=\"describedBy(control)\"\n (input)=\"change(control, $event)\" />\n @if (applicationLimit(control); as limit) {\n <p class=\"hint\" [id]=\"idOf(control, '-limit')\">{{ 'nh-assistant.admin.run-config.files.application-limit' | translate: { value: limit } }}</p>\n }\n @if (errorKey(control); as key) {\n <p class=\"field-error\" [id]=\"idOf(control, '-error')\"><nh-assistant-icon name=\"warning\" />{{ key | translate: errorParams(control) }}</p>\n }\n </div>\n }\n </div>\n</fieldset>\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;min-width:0;color:var(--nha-text);font-family:var(--nha-font);font-size:14px}:host fieldset{min-width:0;margin:0;border:0;padding:0}:host legend,:host .label,:host .field>label{display:block;margin-bottom:6px;padding:0;color:var(--nha-text);font-size:12px;font-weight:700}:host input[type=text],:host input[type=url],:host input[type=password],:host input[type=search],:host input[type=email],:host input[type=number],:host input[type=date],:host input[type=time],:host input[type=datetime-local],:host select,:host textarea{box-sizing:border-box;width:100%;min-width:0;min-height:38px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:8px 10px;color:var(--nha-text);background:var(--nha-background);font:inherit;font-size:13px}:host input[type=text]:focus-visible,:host input[type=url]:focus-visible,:host input[type=password]:focus-visible,:host input[type=search]:focus-visible,:host input[type=email]:focus-visible,:host input[type=number]:focus-visible,:host input[type=date]:focus-visible,:host input[type=time]:focus-visible,:host input[type=datetime-local]:focus-visible,:host select:focus-visible,:host textarea:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host input[type=text]:disabled,:host input[type=url]:disabled,:host input[type=password]:disabled,:host input[type=search]:disabled,:host input[type=email]:disabled,:host input[type=number]:disabled,:host input[type=date]:disabled,:host input[type=time]:disabled,:host input[type=datetime-local]:disabled,:host select:disabled,:host textarea:disabled{cursor:not-allowed;opacity:.6}:host input[type=text][aria-invalid=true],:host input[type=url][aria-invalid=true],:host input[type=password][aria-invalid=true],:host input[type=search][aria-invalid=true],:host input[type=email][aria-invalid=true],:host input[type=number][aria-invalid=true],:host input[type=date][aria-invalid=true],:host input[type=time][aria-invalid=true],:host input[type=datetime-local][aria-invalid=true],:host select[aria-invalid=true],:host textarea[aria-invalid=true]{border-color:var(--nha-danger)}:host textarea{line-height:1.45;resize:vertical}:host input[type=number]{font-variant-numeric:tabular-nums}:host .mono{font-family:var(--nha-mono);font-size:12px}:host .field{display:flex;flex-direction:column;min-width:0}:host .hint{display:flex;justify-content:space-between;gap:8px;margin:4px 2px 0;color:var(--nha-text-muted);font-size:11px}:host .counter{flex:none;font-variant-numeric:tabular-nums}:host .error-text{color:var(--nha-danger)}:host .field-error{display:flex;align-items:flex-start;gap:6px;margin:4px 2px 0;color:var(--nha-danger);font-size:12px;font-weight:600;overflow-wrap:anywhere}:host .field-error nh-assistant-icon{flex:none;margin-top:1px}:host .form-error{box-sizing:border-box;display:flex;align-items:flex-start;gap:8px;margin:0;border:1px solid var(--nha-danger);border-radius:var(--nha-radius-control);padding:8px 12px;color:var(--nha-text);background:var(--nha-danger-soft);font-size:13px;overflow-wrap:anywhere}:host .form-error ul{margin:4px 0 0;padding-left:18px}:host .message{box-sizing:border-box;display:flex;align-items:center;gap:8px;margin:0;border:1px solid transparent;border-radius:var(--nha-radius-control);padding:8px 12px;font-size:13px;overflow-wrap:anywhere}:host .message.error{border-color:var(--nha-danger);color:var(--nha-text);background:var(--nha-danger-soft)}:host .message.warning{border-color:var(--nha-warning);color:var(--nha-text);background:var(--nha-warning-soft)}:host .message.success{border-color:var(--nha-success);color:var(--nha-text);background:var(--nha-success-soft)}:host .message.info{border-color:var(--nha-border);color:var(--nha-text);background:var(--nha-surface)}:host .message nh-assistant-icon{flex:none}:host button.link{min-height:0;border:0;padding:0;color:var(--nha-accent, currentColor);background:none;font:inherit;text-decoration:underline;cursor:pointer}:host button.link:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host button.link:disabled{cursor:not-allowed;opacity:.55}:host .primary,:host .secondary,:host .danger{display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;gap:6px;min-height:36px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px 14px;font:inherit;font-size:13px;font-weight:700;white-space:nowrap;cursor:pointer}:host .primary:focus-visible,:host .secondary:focus-visible,:host .danger:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .primary:disabled,:host .secondary:disabled,:host .danger:disabled{cursor:not-allowed;opacity:.55}:host .primary{border-color:var(--nha-accent);color:var(--nha-accent-contrast);background:var(--nha-accent)}:host .primary:hover:not(:disabled){filter:brightness(1.12)}:host .secondary{color:var(--nha-text);background:transparent}:host .secondary:hover:not(:disabled){background:var(--nha-surface-strong)}:host .danger{border-color:var(--nha-danger);color:var(--nha-danger);background:transparent}:host .danger:hover:not(:disabled){background:var(--nha-danger-soft)}:host .icon-button{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:1px solid transparent;border-radius:var(--nha-radius-control);color:var(--nha-text-muted);background:transparent;cursor:pointer}:host .icon-button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .icon-button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}:host .icon-button:disabled{cursor:not-allowed;opacity:.5}:host .actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px}:host .badge{display:inline-flex;align-items:center;gap:4px;border-radius:999px;padding:2px 8px;color:var(--nha-text-muted);background:var(--nha-surface-strong);font-size:11px;font-weight:700;white-space:nowrap}:host .badge.success{color:var(--nha-success);background:var(--nha-success-soft)}:host .badge.warning{color:var(--nha-warning);background:var(--nha-warning-soft)}:host .badge.danger{color:var(--nha-danger);background:var(--nha-danger-soft)}:host .visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}.section{display:flex;flex-direction:column;gap:16px;min-width:0}.section-header{display:flex;flex-wrap:wrap;align-items:flex-start;justify-content:space-between;gap:12px}.section-header>div{min-width:0;max-width:72ch}h3{margin:0 0 4px;font-size:17px;font-weight:750}h4{margin:0 0 8px;font-size:14px;font-weight:700}.muted{margin:0;color:var(--nha-text-muted);font-size:13px}.state{display:flex;flex-direction:column;align-items:flex-start;gap:10px;margin:0;border:1px dashed var(--nha-border);border-radius:var(--nha-radius);padding:20px;color:var(--nha-text-muted);font-size:13px}.state p{margin:0}.stack{display:flex;flex-direction:column;gap:14px;min-width:0}.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,240px),1fr));gap:14px}.meta{display:flex;flex-wrap:wrap;align-items:center;gap:8px 12px;color:var(--nha-text-muted);font-size:12px;overflow-wrap:anywhere}textarea.large{min-height:260px}.catalog-error{flex-wrap:wrap}.grow{flex:1;min-width:0}.subsection{display:flex;flex-direction:column;gap:8px;border-top:1px solid var(--nha-border);padding-top:14px}.history{display:grid;gap:6px;margin:0;padding:0;list-style:none}.history li{display:flex;flex-wrap:wrap;align-items:center;gap:6px 12px;font-size:12px}.cards{display:grid;gap:10px;margin:0;padding:0;list-style:none}.card{display:flex;flex-direction:column;gap:8px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius);padding:14px;background:var(--nha-background)}.card.inactive{opacity:.8}.card-header{display:flex;flex-wrap:wrap;align-items:center;gap:8px;min-width:0}.card-header strong{font-size:14px;overflow-wrap:anywhere}.card-actions{display:flex;flex-wrap:wrap;gap:8px}.check{display:flex;align-items:flex-start;gap:8px;font-size:13px;cursor:pointer}.check input{width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.check.described>span{display:grid;gap:2px;min-width:0;overflow-wrap:anywhere}.checks{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));gap:6px 12px;min-width:0;overflow-wrap:anywhere}.chips{display:flex;flex-wrap:wrap;gap:6px;margin:0;padding:0;list-style:none}.chip{display:inline-flex;align-items:center;gap:2px;border:1px solid var(--nha-border);border-radius:999px;padding:2px 2px 2px 10px;background:var(--nha-surface);font-family:var(--nha-mono);font-size:12px}.chip 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;width:26px;height:26px;border-radius:999px}.chip button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.chip button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}.chip button:disabled{cursor:not-allowed;opacity:.5}.inline{display:flex;flex-wrap:wrap;gap:8px}.inline>input{flex:1 1 180px}.catalog{display:grid;gap:4px;max-height:260px;margin:8px 0 0;overflow-y:auto;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px;list-style:none}.catalog li label{display:flex;flex-wrap:wrap;align-items:center;gap:6px 8px;border-radius:8px;padding:6px;cursor:pointer}.catalog li label:hover{background:var(--nha-surface)}.catalog .tool-id{font-family:var(--nha-mono);font-size:12px;overflow-wrap:anywhere}.skills{display:grid;gap:8px;margin:0;padding:0;list-style:none}.skill{display:grid;gap:6px;border-left:3px solid var(--nha-border);padding:4px 0 4px 12px}.uses{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));gap:8px}.use{display:flex;align-items:flex-start;gap:10px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:10px 12px;cursor:pointer;overflow-wrap:anywhere}.use input{flex:none;width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.use>span{display:grid;gap:2px;min-width:0}.use strong{font-size:13px}.use.selected{border-color:var(--nha-highlight);background:var(--nha-surface)}.use:focus-within{outline:2px solid var(--nha-highlight);outline-offset:2px}.discovery{display:grid;gap:8px;padding:10px 12px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control)}.discovery dl{display:grid;grid-template-columns:max-content minmax(0,1fr);gap:4px 12px;margin:0}.discovery dt{color:var(--nha-text-muted)}.discovery dd{margin:0;overflow-wrap:anywhere}\n", "h5{margin:0;font-size:15px;font-weight:750}.group{display:flex;flex-direction:column;gap:10px;min-width:0;border-top:1px solid var(--nha-border);padding-top:14px}.group>legend{float:left;width:100%;margin-bottom:2px;font-size:14px;font-weight:750}.choices{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,220px),1fr));gap:8px}.choice{display:flex;align-items:flex-start;gap:10px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:10px 12px;cursor:pointer;overflow-wrap:anywhere}.choice input{flex:none;width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.choice>span{display:grid;gap:2px;min-width:0}.choice strong{font-size:13px}.choice.selected{border-color:var(--nha-highlight);background:var(--nha-surface)}.choice:focus-within{outline:2px solid var(--nha-highlight);outline-offset:2px}.block{display:block;margin-top:2px}.advanced{border-top:1px solid var(--nha-border);padding-top:12px}.advanced>summary{width:fit-content;border-radius:6px;font-size:14px;font-weight:750;cursor:pointer}.advanced>summary:focus-visible{outline:2px solid var(--nha-highlight);outline-offset:2px}.advanced>.stack{margin-top:12px}.examples{display:flex;flex-wrap:wrap;gap:6px;margin:6px 0 0;padding:0;list-style:none}.examples button{border:1px solid var(--nha-border);border-radius:999px;padding:3px 10px;color:var(--nha-text);background:transparent;font:inherit;font-size:12px;cursor:pointer}.examples button:hover{background:var(--nha-surface-strong)}.examples button:focus-visible{outline:2px solid var(--nha-highlight);outline-offset:2px}.steps{display:flex;flex-wrap:wrap;gap:6px;margin:0;padding:0;list-style:none}.steps li{display:inline-flex;align-items:center;gap:6px;border-radius:999px;padding:4px 10px;color:var(--nha-text-muted);background:var(--nha-surface-strong);font-size:12px;font-weight:700}.steps li[aria-current=step]{color:var(--nha-accent-contrast);background:var(--nha-accent)}.steps li.done{color:var(--nha-success);background:var(--nha-success-soft)}.review{display:grid;grid-template-columns:minmax(0,max-content) minmax(0,1fr);gap:6px 16px;margin:0;font-size:13px}.review dt{color:var(--nha-text-muted)}.review dd{margin:0;overflow-wrap:anywhere}.tool-choice{display:flex;flex-direction:column;gap:2px;min-width:0;overflow-wrap:anywhere}@media(max-width:520px){.review{grid-template-columns:minmax(0,1fr)}.review dd{margin-bottom:6px}}.table-scroll{max-width:100%;overflow-x:auto}\n"] }]
|
|
2695
|
+
}], propDecorators: { draft: [{ type: i0.Input, args: [{ isSignal: true, alias: "draft", required: true }] }], application: [{ type: i0.Input, args: [{ isSignal: true, alias: "application", required: false }] }], errors: [{ type: i0.Input, args: [{ isSignal: true, alias: "errors", required: false }] }], idPrefix: [{ type: i0.Input, args: [{ isSignal: true, alias: "idPrefix", required: true }] }], hintKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "hintKey", required: true }] }], touched: [{ type: i0.Output, args: ["touched"] }] } });
|
|
2696
|
+
|
|
2504
2697
|
let nextId$8 = 0;
|
|
2505
2698
|
const defaults = { retries: '3', concurrent: '4', perSubject: '10' };
|
|
2506
2699
|
/**
|
|
@@ -2525,7 +2718,7 @@ class NhAssistantRunSettingsFormComponent extends RunConfigForm {
|
|
|
2525
2718
|
this.fields = [
|
|
2526
2719
|
'accountability', 'serviceAccountActorId', 'ruleHandler', 'approvalPolicy', 'triageProfile', 'retryCount',
|
|
2527
2720
|
'maxConcurrentRuns', 'maxRunsPerSubjectPerDay', 'maxTokensPerRun', 'dailyToolCalls', 'dailyTokens',
|
|
2528
|
-
'elevatedReads', 'allowGatewayReads', 'reason'
|
|
2721
|
+
'elevatedReads', 'allowGatewayReads', 'reason', 'maxFileBytes', 'maxFilePages', 'maxFilesPerTurn', 'maxFileTokensPerTurn'
|
|
2529
2722
|
];
|
|
2530
2723
|
this.accountability = signal('service-account', ...(ngDevMode ? [{ debugName: "accountability" }] : []));
|
|
2531
2724
|
this.serviceAccount = signal('', ...(ngDevMode ? [{ debugName: "serviceAccount" }] : []));
|
|
@@ -2540,6 +2733,10 @@ class NhAssistantRunSettingsFormComponent extends RunConfigForm {
|
|
|
2540
2733
|
this.dailyTokens = signal('', ...(ngDevMode ? [{ debugName: "dailyTokens" }] : []));
|
|
2541
2734
|
this.allowGatewayReads = signal(false, ...(ngDevMode ? [{ debugName: "allowGatewayReads" }] : []));
|
|
2542
2735
|
this.delegateFromInitiator = signal(false, ...(ngDevMode ? [{ debugName: "delegateFromInitiator" }] : []));
|
|
2736
|
+
/** Whether people's messages and the agent's tools may give it files; off for a new agent. */
|
|
2737
|
+
this.acceptsFiles = signal(false, ...(ngDevMode ? [{ debugName: "acceptsFiles" }] : []));
|
|
2738
|
+
/** The agent's lower file limits, next to accepting files; empty keeps the application's. */
|
|
2739
|
+
this.fileLimits = new FileLimitsDraft();
|
|
2543
2740
|
this.elevatedReads = signal('restricted', ...(ngDevMode ? [{ debugName: "elevatedReads" }] : []));
|
|
2544
2741
|
this.reason = signal('', ...(ngDevMode ? [{ debugName: "reason" }] : []));
|
|
2545
2742
|
this.advancedOpen = signal(false, ...(ngDevMode ? [{ debugName: "advancedOpen" }] : []));
|
|
@@ -2577,6 +2774,8 @@ class NhAssistantRunSettingsFormComponent extends RunConfigForm {
|
|
|
2577
2774
|
this.dailyTokens.set(numberText(settings?.dailyTokens));
|
|
2578
2775
|
this.allowGatewayReads.set(settings?.allowGatewayReads ?? false);
|
|
2579
2776
|
this.delegateFromInitiator.set(settings?.delegateFromInitiator ?? false);
|
|
2777
|
+
this.acceptsFiles.set(settings?.acceptsFiles ?? false);
|
|
2778
|
+
this.fileLimits.load(settings);
|
|
2580
2779
|
this.elevatedReads.set(settings?.elevatedReads ?? 'restricted');
|
|
2581
2780
|
this.advancedOpen.set(this.advancedInUse());
|
|
2582
2781
|
}
|
|
@@ -2595,6 +2794,9 @@ class NhAssistantRunSettingsFormComponent extends RunConfigForm {
|
|
|
2595
2794
|
checkWhole(errors, 'maxRunsPerSubjectPerDay', this.maxRunsPerSubjectPerDay(), ...runConfigLimits.perSubject, true);
|
|
2596
2795
|
checkWhole(errors, 'maxTokensPerRun', this.maxTokensPerRun(), 1);
|
|
2597
2796
|
checkBudget(errors, this.dailyToolCalls(), this.dailyTokens());
|
|
2797
|
+
if (this.acceptsFiles()) {
|
|
2798
|
+
this.fileLimits.check(errors, this.catalog()?.fileLimits);
|
|
2799
|
+
}
|
|
2598
2800
|
const request = {
|
|
2599
2801
|
accountability: this.accountability(),
|
|
2600
2802
|
serviceAccountActorId: this.accountability() === 'service-account' ? blank$1(this.serviceAccount()) : null,
|
|
@@ -2611,7 +2813,9 @@ class NhAssistantRunSettingsFormComponent extends RunConfigForm {
|
|
|
2611
2813
|
triageProfile: blank$1(this.triageProfile()),
|
|
2612
2814
|
ruleHandler: blank$1(this.ruleHandler()),
|
|
2613
2815
|
expectedVersion: this.settings()?.version ?? null,
|
|
2614
|
-
reason: blank$1(this.reason())
|
|
2816
|
+
reason: blank$1(this.reason()),
|
|
2817
|
+
acceptsFiles: this.acceptsFiles(),
|
|
2818
|
+
...this.fileLimits.toRequest(this.acceptsFiles())
|
|
2615
2819
|
};
|
|
2616
2820
|
this.submit(errors, () => this.admin.saveRunSettings(this.agentId(), request), settings => this.saved.emit(settings));
|
|
2617
2821
|
}
|
|
@@ -2619,11 +2823,18 @@ class NhAssistantRunSettingsFormComponent extends RunConfigForm {
|
|
|
2619
2823
|
return withBudgetField(errors, this.dailyToolCalls());
|
|
2620
2824
|
}
|
|
2621
2825
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: NhAssistantRunSettingsFormComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
2622
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.34", type: NhAssistantRunSettingsFormComponent, isStandalone: true, selector: "nh-assistant-run-settings-form", inputs: { agentId: { classPropertyName: "agentId", publicName: "agentId", isSignal: true, isRequired: true, transformFunction: null }, catalog: { classPropertyName: "catalog", publicName: "catalog", isSignal: true, isRequired: false, transformFunction: null }, settings: { classPropertyName: "settings", publicName: "settings", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { saved: "saved", cancelled: "cancelled" }, usesInheritance: true, ngImport: i0, template: "<form class=\"stack card\" data-form=\"settings\" novalidate (submit)=\"$event.preventDefault(); save()\">\n <h5>{{ (settings() ? 'nh-assistant.admin.run-config.settings.edit-title' : 'nh-assistant.admin.run-config.settings.add-title') | translate }}</h5>\n\n <fieldset class=\"group\" [attr.aria-describedby]=\"id + '-who-explain'\">\n <legend>{{ 'nh-assistant.admin.run-config.settings.who.title' | translate }}</legend>\n <p class=\"muted\" [id]=\"id + '-who-explain'\">{{ 'nh-assistant.admin.run-config.settings.who.explain' | translate }}</p>\n <div class=\"choices\" role=\"radiogroup\" [attr.aria-label]=\"'nh-assistant.admin.run-config.settings.who.title' | translate\">\n @for (option of accountabilities; track option) {\n <label class=\"choice\" [class.selected]=\"accountability() === option\">\n <input type=\"radio\" [name]=\"id + '-accountability'\" [value]=\"option\" [checked]=\"accountability() === option\" (change)=\"setAccountability(option)\" />\n <span>\n <strong>{{ 'nh-assistant.admin.run-config.settings.accountability.' + option | translate }}</strong>\n <span class=\"muted\">{{ 'nh-assistant.admin.run-config.settings.accountability-explain.' + option | translate }}</span>\n </span>\n </label>\n }\n </div>\n @if (errorKey('accountability'); as key) {\n <p class=\"field-error\" [id]=\"id + '-accountability-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('accountability')?.params }}</p>\n }\n @if (accountability() === 'service-account') {\n <div class=\"field\">\n <label [for]=\"id + '-service-account'\">{{ 'nh-assistant.admin.run-config.settings.service-account' | translate }}</label>\n <nh-assistant-account-picker\n [inputId]=\"id + '-service-account'\"\n [value]=\"serviceAccount()\"\n [searchable]=\"accountSearch()\"\n [invalid]=\"fieldError('serviceAccountActorId') !== null\"\n [describedBy]=\"describedBy(id + '-service-account-hint', 'serviceAccountActorId', id + '-service-account-error')\"\n (valueChange)=\"serviceAccount.set($event); touch('serviceAccountActorId')\" />\n <p class=\"hint\" [id]=\"id + '-service-account-hint'\">\n {{ (accountSearch() ? 'nh-assistant.admin.run-config.account.search-hint' : 'nh-assistant.admin.run-config.account.id-hint') | translate }}\n </p>\n @if (errorKey('serviceAccountActorId'); as key) {\n <p class=\"field-error\" [id]=\"id + '-service-account-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('serviceAccountActorId')?.params }}</p>\n }\n </div>\n }\n </fieldset>\n\n <fieldset class=\"group\">\n <legend>{{ 'nh-assistant.admin.run-config.settings.how.title' | translate }}</legend>\n <div class=\"field\">\n <label [for]=\"id + '-rule-handler'\">{{ 'nh-assistant.admin.run-config.way-of-working' | translate }}</label>\n <nh-assistant-picker\n [inputId]=\"id + '-rule-handler'\"\n [options]=\"rules()\"\n [value]=\"ruleHandler()\"\n [noneLabel]=\"'nh-assistant.admin.run-config.model' | translate\"\n [invalid]=\"fieldError('ruleHandler') !== null\"\n [describedBy]=\"describedBy(id + '-rule-handler-hint', 'ruleHandler', id + '-rule-handler-error')\"\n (valueChange)=\"ruleHandler.set($event); touch('ruleHandler')\" />\n <p class=\"hint\" [id]=\"id + '-rule-handler-hint'\">{{ 'nh-assistant.admin.run-config.settings.how.hint' | translate }}</p>\n @if (errorKey('ruleHandler'); as key) {\n <p class=\"field-error\" [id]=\"id + '-rule-handler-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('ruleHandler')?.params }}</p>\n }\n </div>\n </fieldset>\n\n <fieldset class=\"group\">\n <legend>{{ 'nh-assistant.admin.run-config.settings.approval.title' | translate }}</legend>\n <div class=\"field\">\n <label [for]=\"id + '-policy'\">{{ 'nh-assistant.admin.run-config.approval-policy' | translate }}</label>\n <nh-assistant-picker\n [inputId]=\"id + '-policy'\"\n [options]=\"policies()\"\n [value]=\"approvalPolicy()\"\n [noneLabel]=\"'nh-assistant.admin.run-config.settings.approval.default' | translate\"\n [invalid]=\"fieldError('approvalPolicy') !== null\"\n [describedBy]=\"describedBy(id + '-policy-hint', 'approvalPolicy', id + '-policy-error')\"\n (valueChange)=\"approvalPolicy.set($event); touch('approvalPolicy')\" />\n <p class=\"hint\" [id]=\"id + '-policy-hint'\">{{ 'nh-assistant.admin.run-config.settings.approval.hint' | translate }}</p>\n @if (errorKey('approvalPolicy'); as key) {\n <p class=\"field-error\" [id]=\"id + '-policy-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('approvalPolicy')?.params }}</p>\n }\n </div>\n </fieldset>\n\n <fieldset class=\"group\">\n <legend>{{ 'nh-assistant.admin.run-config.limits.title' | translate }}</legend>\n <p class=\"muted\" [id]=\"id + '-limits-hint'\">{{ 'nh-assistant.admin.run-config.budget-hint' | translate }}</p>\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-run-tokens'\">{{ 'nh-assistant.admin.run-config.max-tokens' | translate }}</label>\n <input type=\"number\" min=\"1\" inputmode=\"numeric\" [id]=\"id + '-run-tokens'\" [value]=\"maxTokensPerRun()\"\n [attr.aria-invalid]=\"fieldError('maxTokensPerRun') ? true : null\"\n [attr.aria-describedby]=\"describedBy(id + '-limits-hint', 'maxTokensPerRun', id + '-run-tokens-error')\"\n (input)=\"maxTokensPerRun.set(value($event)); touch('maxTokensPerRun')\" />\n @if (errorKey('maxTokensPerRun'); as key) {\n <p class=\"field-error\" [id]=\"id + '-run-tokens-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('maxTokensPerRun')?.params }}</p>\n }\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-daily-calls'\">{{ 'nh-assistant.admin.run-config.daily-tool-calls' | translate }}</label>\n <input type=\"number\" min=\"1\" inputmode=\"numeric\" [id]=\"id + '-daily-calls'\" [value]=\"dailyToolCalls()\"\n [attr.aria-invalid]=\"fieldError('dailyToolCalls') ? true : null\"\n [attr.aria-describedby]=\"describedBy(id + '-limits-hint', 'dailyToolCalls', id + '-daily-calls-error')\"\n (input)=\"dailyToolCalls.set(value($event)); touch('dailyToolCalls'); touch('dailyTokens')\" />\n @if (errorKey('dailyToolCalls'); as key) {\n <p class=\"field-error\" [id]=\"id + '-daily-calls-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('dailyToolCalls')?.params }}</p>\n }\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-daily-tokens'\">{{ 'nh-assistant.admin.run-config.daily-tokens' | translate }}</label>\n <input type=\"number\" min=\"1\" inputmode=\"numeric\" [id]=\"id + '-daily-tokens'\" [value]=\"dailyTokens()\"\n [attr.aria-invalid]=\"fieldError('dailyTokens') ? true : null\"\n [attr.aria-describedby]=\"describedBy(id + '-limits-hint', 'dailyTokens', id + '-daily-tokens-error')\"\n (input)=\"dailyTokens.set(value($event)); touch('dailyTokens'); touch('dailyToolCalls')\" />\n @if (errorKey('dailyTokens'); as key) {\n <p class=\"field-error\" [id]=\"id + '-daily-tokens-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('dailyTokens')?.params }}</p>\n }\n </div>\n </div>\n </fieldset>\n\n <details class=\"advanced\" data-advanced [open]=\"advancedOpen() || advancedHasErrors()\" (toggle)=\"advancedOpen.set($any($event.target).open)\">\n <summary>{{ 'nh-assistant.admin.run-config.advanced.title' | translate }}</summary>\n <div class=\"stack\">\n <p class=\"muted\">{{ 'nh-assistant.admin.run-config.advanced.explain' | translate }}</p>\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-retries'\">{{ 'nh-assistant.admin.run-config.settings.retries' | translate }}</label>\n <input type=\"number\" min=\"0\" max=\"10\" inputmode=\"numeric\" [id]=\"id + '-retries'\" [value]=\"retryCount()\"\n [attr.aria-invalid]=\"fieldError('retryCount') ? true : null\"\n [attr.aria-describedby]=\"describedBy(id + '-retries-hint', 'retryCount', id + '-retries-error')\"\n (input)=\"retryCount.set(value($event)); touch('retryCount')\" />\n <p class=\"hint\" [id]=\"id + '-retries-hint'\">{{ 'nh-assistant.admin.run-config.settings.retries-hint' | translate }}</p>\n @if (errorKey('retryCount'); as key) {\n <p class=\"field-error\" [id]=\"id + '-retries-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('retryCount')?.params }}</p>\n }\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-concurrent'\">{{ 'nh-assistant.admin.run-config.settings.max-concurrent' | translate }}</label>\n <input type=\"number\" min=\"1\" max=\"100\" inputmode=\"numeric\" [id]=\"id + '-concurrent'\" [value]=\"maxConcurrentRuns()\"\n [attr.aria-invalid]=\"fieldError('maxConcurrentRuns') ? true : null\"\n [attr.aria-describedby]=\"describedBy(id + '-concurrent-hint', 'maxConcurrentRuns', id + '-concurrent-error')\"\n (input)=\"maxConcurrentRuns.set(value($event)); touch('maxConcurrentRuns')\" />\n <p class=\"hint\" [id]=\"id + '-concurrent-hint'\">{{ 'nh-assistant.admin.run-config.settings.max-concurrent-hint' | translate }}</p>\n @if (errorKey('maxConcurrentRuns'); as key) {\n <p class=\"field-error\" [id]=\"id + '-concurrent-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('maxConcurrentRuns')?.params }}</p>\n }\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-per-subject'\">{{ 'nh-assistant.admin.run-config.settings.max-per-subject' | translate }}</label>\n <input type=\"number\" min=\"1\" max=\"1000\" inputmode=\"numeric\" [id]=\"id + '-per-subject'\" [value]=\"maxRunsPerSubjectPerDay()\"\n [attr.aria-invalid]=\"fieldError('maxRunsPerSubjectPerDay') ? true : null\"\n [attr.aria-describedby]=\"describedBy(id + '-per-subject-hint', 'maxRunsPerSubjectPerDay', id + '-per-subject-error')\"\n (input)=\"maxRunsPerSubjectPerDay.set(value($event)); touch('maxRunsPerSubjectPerDay')\" />\n <p class=\"hint\" [id]=\"id + '-per-subject-hint'\">{{ 'nh-assistant.admin.run-config.settings.max-per-subject-hint' | translate }}</p>\n @if (errorKey('maxRunsPerSubjectPerDay'); as key) {\n <p class=\"field-error\" [id]=\"id + '-per-subject-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('maxRunsPerSubjectPerDay')?.params }}</p>\n }\n </div>\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-triage'\">{{ 'nh-assistant.admin.run-config.settings.triage' | translate }}</label>\n <nh-assistant-picker\n [inputId]=\"id + '-triage'\"\n [options]=\"profiles()\"\n [value]=\"triageProfile()\"\n [noneLabel]=\"'nh-assistant.admin.run-config.settings.no-triage' | translate\"\n [invalid]=\"fieldError('triageProfile') !== null\"\n [describedBy]=\"describedBy(id + '-triage-hint', 'triageProfile', id + '-triage-error')\"\n (valueChange)=\"triageProfile.set($event); touch('triageProfile')\" />\n <p class=\"hint\" [id]=\"id + '-triage-hint'\">{{ 'nh-assistant.admin.run-config.settings.triage-hint' | translate }}</p>\n @if (errorKey('triageProfile'); as key) {\n <p class=\"field-error\" [id]=\"id + '-triage-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('triageProfile')?.params }}</p>\n }\n </div>\n <fieldset class=\"field\">\n <legend>{{ 'nh-assistant.admin.run-config.settings.elevated-reads' | translate }}</legend>\n <p class=\"hint\">{{ 'nh-assistant.admin.run-config.settings.elevated-reads-hint' | translate }}</p>\n @for (mode of elevatedReadModes; track mode) {\n <label class=\"check\">\n <input type=\"radio\" [name]=\"id + '-elevated'\" [value]=\"mode\" [checked]=\"elevatedReads() === mode\" (change)=\"elevatedReads.set(mode); touch('elevatedReads')\" />\n <span>{{ 'nh-assistant.admin.run-config.settings.elevated.' + mode | translate }}</span>\n </label>\n }\n @if (errorKey('elevatedReads'); as key) {\n <p class=\"field-error\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('elevatedReads')?.params }}</p>\n }\n </fieldset>\n <label class=\"check\">\n <input type=\"checkbox\" [checked]=\"allowGatewayReads()\" [attr.aria-invalid]=\"fieldError('allowGatewayReads') ? true : null\" (change)=\"allowGatewayReads.set(checked($event)); touch('allowGatewayReads')\" />\n <span>\n {{ 'nh-assistant.admin.run-config.settings.gateway-reads' | translate }}\n <span class=\"muted block\">{{ 'nh-assistant.admin.run-config.settings.gateway-reads-hint' | translate }}</span>\n </span>\n </label>\n @if (errorKey('allowGatewayReads'); as key) {\n <p class=\"field-error\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('allowGatewayReads')?.params }}</p>\n }\n <label class=\"check\">\n <input type=\"checkbox\" [checked]=\"delegateFromInitiator()\" (change)=\"delegateFromInitiator.set(checked($event))\" />\n <span>\n {{ 'nh-assistant.admin.run-config.settings.delegate' | translate }}\n <span class=\"muted block\">{{ 'nh-assistant.admin.run-config.settings.delegate-hint' | translate }}</span>\n </span>\n </label>\n </div>\n </details>\n\n <div class=\"field\">\n <label [for]=\"id + '-reason'\">{{ 'nh-assistant.admin.agents.state.note-label' | translate }}</label>\n <input type=\"text\" autocomplete=\"off\" [id]=\"id + '-reason'\" [maxLength]=\"maxReason\" [value]=\"reason()\" [attr.aria-describedby]=\"id + '-reason-hint'\" (input)=\"reason.set(value($event))\" />\n <p class=\"hint\" [id]=\"id + '-reason-hint'\">{{ 'nh-assistant.admin.run-config.note-hint' | translate }}</p>\n </div>\n\n @if (hasErrors()) {\n <div class=\"form-error\" role=\"alert\" data-form-error>\n <nh-assistant-icon name=\"warning\" />\n <div>\n @if (serverError(); as failure) {\n @if (failure.code === 'assistant-validation') {\n {{ 'nh-assistant.admin.run-config.check-fields' | translate }}\n } @else {\n {{ errorKeys() | nhAssistantTranslate: failure.code }}\n }\n } @else {\n {{ 'nh-assistant.admin.run-config.check-fields' | translate }}\n }\n @if (otherFieldErrors().length > 0) {\n <ul>\n @for (field of otherFieldErrors(); track field) {\n <li><span class=\"mono\">{{ field }}</span></li>\n }\n </ul>\n }\n </div>\n </div>\n }\n <div class=\"actions\">\n <button class=\"secondary\" type=\"button\" (click)=\"cancelled.emit()\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n <button class=\"primary\" type=\"submit\" [disabled]=\"busy()\">{{ (busy() ? 'nh-assistant.admin.saving' : 'nh-assistant.admin.save') | translate }}</button>\n </div>\n</form>\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;min-width:0;color:var(--nha-text);font-family:var(--nha-font);font-size:14px}:host fieldset{min-width:0;margin:0;border:0;padding:0}:host legend,:host .label,:host .field>label{display:block;margin-bottom:6px;padding:0;color:var(--nha-text);font-size:12px;font-weight:700}:host input[type=text],:host input[type=url],:host input[type=password],:host input[type=search],:host input[type=email],:host input[type=number],:host input[type=date],:host input[type=time],:host input[type=datetime-local],:host select,:host textarea{box-sizing:border-box;width:100%;min-width:0;min-height:38px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:8px 10px;color:var(--nha-text);background:var(--nha-background);font:inherit;font-size:13px}:host input[type=text]:focus-visible,:host input[type=url]:focus-visible,:host input[type=password]:focus-visible,:host input[type=search]:focus-visible,:host input[type=email]:focus-visible,:host input[type=number]:focus-visible,:host input[type=date]:focus-visible,:host input[type=time]:focus-visible,:host input[type=datetime-local]:focus-visible,:host select:focus-visible,:host textarea:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host input[type=text]:disabled,:host input[type=url]:disabled,:host input[type=password]:disabled,:host input[type=search]:disabled,:host input[type=email]:disabled,:host input[type=number]:disabled,:host input[type=date]:disabled,:host input[type=time]:disabled,:host input[type=datetime-local]:disabled,:host select:disabled,:host textarea:disabled{cursor:not-allowed;opacity:.6}:host input[type=text][aria-invalid=true],:host input[type=url][aria-invalid=true],:host input[type=password][aria-invalid=true],:host input[type=search][aria-invalid=true],:host input[type=email][aria-invalid=true],:host input[type=number][aria-invalid=true],:host input[type=date][aria-invalid=true],:host input[type=time][aria-invalid=true],:host input[type=datetime-local][aria-invalid=true],:host select[aria-invalid=true],:host textarea[aria-invalid=true]{border-color:var(--nha-danger)}:host textarea{line-height:1.45;resize:vertical}:host input[type=number]{font-variant-numeric:tabular-nums}:host .mono{font-family:var(--nha-mono);font-size:12px}:host .field{display:flex;flex-direction:column;min-width:0}:host .hint{display:flex;justify-content:space-between;gap:8px;margin:4px 2px 0;color:var(--nha-text-muted);font-size:11px}:host .counter{flex:none;font-variant-numeric:tabular-nums}:host .error-text{color:var(--nha-danger)}:host .field-error{display:flex;align-items:flex-start;gap:6px;margin:4px 2px 0;color:var(--nha-danger);font-size:12px;font-weight:600;overflow-wrap:anywhere}:host .field-error nh-assistant-icon{flex:none;margin-top:1px}:host .form-error{box-sizing:border-box;display:flex;align-items:flex-start;gap:8px;margin:0;border:1px solid var(--nha-danger);border-radius:var(--nha-radius-control);padding:8px 12px;color:var(--nha-text);background:var(--nha-danger-soft);font-size:13px;overflow-wrap:anywhere}:host .form-error ul{margin:4px 0 0;padding-left:18px}:host .message{box-sizing:border-box;display:flex;align-items:center;gap:8px;margin:0;border:1px solid transparent;border-radius:var(--nha-radius-control);padding:8px 12px;font-size:13px;overflow-wrap:anywhere}:host .message.error{border-color:var(--nha-danger);color:var(--nha-text);background:var(--nha-danger-soft)}:host .message.warning{border-color:var(--nha-warning);color:var(--nha-text);background:var(--nha-warning-soft)}:host .message.success{border-color:var(--nha-success);color:var(--nha-text);background:var(--nha-success-soft)}:host .message.info{border-color:var(--nha-border);color:var(--nha-text);background:var(--nha-surface)}:host .message nh-assistant-icon{flex:none}:host button.link{min-height:0;border:0;padding:0;color:var(--nha-accent, currentColor);background:none;font:inherit;text-decoration:underline;cursor:pointer}:host button.link:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host button.link:disabled{cursor:not-allowed;opacity:.55}:host .primary,:host .secondary,:host .danger{display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;gap:6px;min-height:36px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px 14px;font:inherit;font-size:13px;font-weight:700;white-space:nowrap;cursor:pointer}:host .primary:focus-visible,:host .secondary:focus-visible,:host .danger:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .primary:disabled,:host .secondary:disabled,:host .danger:disabled{cursor:not-allowed;opacity:.55}:host .primary{border-color:var(--nha-accent);color:var(--nha-accent-contrast);background:var(--nha-accent)}:host .primary:hover:not(:disabled){filter:brightness(1.12)}:host .secondary{color:var(--nha-text);background:transparent}:host .secondary:hover:not(:disabled){background:var(--nha-surface-strong)}:host .danger{border-color:var(--nha-danger);color:var(--nha-danger);background:transparent}:host .danger:hover:not(:disabled){background:var(--nha-danger-soft)}:host .icon-button{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:1px solid transparent;border-radius:var(--nha-radius-control);color:var(--nha-text-muted);background:transparent;cursor:pointer}:host .icon-button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .icon-button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}:host .icon-button:disabled{cursor:not-allowed;opacity:.5}:host .actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px}:host .badge{display:inline-flex;align-items:center;gap:4px;border-radius:999px;padding:2px 8px;color:var(--nha-text-muted);background:var(--nha-surface-strong);font-size:11px;font-weight:700;white-space:nowrap}:host .badge.success{color:var(--nha-success);background:var(--nha-success-soft)}:host .badge.warning{color:var(--nha-warning);background:var(--nha-warning-soft)}:host .badge.danger{color:var(--nha-danger);background:var(--nha-danger-soft)}:host .visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}.section{display:flex;flex-direction:column;gap:16px;min-width:0}.section-header{display:flex;flex-wrap:wrap;align-items:flex-start;justify-content:space-between;gap:12px}.section-header>div{min-width:0;max-width:72ch}h3{margin:0 0 4px;font-size:17px;font-weight:750}h4{margin:0 0 8px;font-size:14px;font-weight:700}.muted{margin:0;color:var(--nha-text-muted);font-size:13px}.state{display:flex;flex-direction:column;align-items:flex-start;gap:10px;margin:0;border:1px dashed var(--nha-border);border-radius:var(--nha-radius);padding:20px;color:var(--nha-text-muted);font-size:13px}.state p{margin:0}.stack{display:flex;flex-direction:column;gap:14px;min-width:0}.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,240px),1fr));gap:14px}.meta{display:flex;flex-wrap:wrap;align-items:center;gap:8px 12px;color:var(--nha-text-muted);font-size:12px;overflow-wrap:anywhere}textarea.large{min-height:260px}.catalog-error{flex-wrap:wrap}.grow{flex:1;min-width:0}.subsection{display:flex;flex-direction:column;gap:8px;border-top:1px solid var(--nha-border);padding-top:14px}.history{display:grid;gap:6px;margin:0;padding:0;list-style:none}.history li{display:flex;flex-wrap:wrap;align-items:center;gap:6px 12px;font-size:12px}.cards{display:grid;gap:10px;margin:0;padding:0;list-style:none}.card{display:flex;flex-direction:column;gap:8px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius);padding:14px;background:var(--nha-background)}.card.inactive{opacity:.8}.card-header{display:flex;flex-wrap:wrap;align-items:center;gap:8px;min-width:0}.card-header strong{font-size:14px;overflow-wrap:anywhere}.card-actions{display:flex;flex-wrap:wrap;gap:8px}.check{display:flex;align-items:flex-start;gap:8px;font-size:13px;cursor:pointer}.check input{width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.check.described>span{display:grid;gap:2px;min-width:0;overflow-wrap:anywhere}.checks{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));gap:6px 12px;min-width:0;overflow-wrap:anywhere}.chips{display:flex;flex-wrap:wrap;gap:6px;margin:0;padding:0;list-style:none}.chip{display:inline-flex;align-items:center;gap:2px;border:1px solid var(--nha-border);border-radius:999px;padding:2px 2px 2px 10px;background:var(--nha-surface);font-family:var(--nha-mono);font-size:12px}.chip 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;width:26px;height:26px;border-radius:999px}.chip button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.chip button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}.chip button:disabled{cursor:not-allowed;opacity:.5}.inline{display:flex;flex-wrap:wrap;gap:8px}.inline>input{flex:1 1 180px}.catalog{display:grid;gap:4px;max-height:260px;margin:8px 0 0;overflow-y:auto;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px;list-style:none}.catalog li label{display:flex;flex-wrap:wrap;align-items:center;gap:6px 8px;border-radius:8px;padding:6px;cursor:pointer}.catalog li label:hover{background:var(--nha-surface)}.catalog .tool-id{font-family:var(--nha-mono);font-size:12px;overflow-wrap:anywhere}.skills{display:grid;gap:8px;margin:0;padding:0;list-style:none}.skill{display:grid;gap:6px;border-left:3px solid var(--nha-border);padding:4px 0 4px 12px}.uses{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));gap:8px}.use{display:flex;align-items:flex-start;gap:10px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:10px 12px;cursor:pointer;overflow-wrap:anywhere}.use input{flex:none;width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.use>span{display:grid;gap:2px;min-width:0}.use strong{font-size:13px}.use.selected{border-color:var(--nha-highlight);background:var(--nha-surface)}.use:focus-within{outline:2px solid var(--nha-highlight);outline-offset:2px}.discovery{display:grid;gap:8px;padding:10px 12px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control)}.discovery dl{display:grid;grid-template-columns:max-content minmax(0,1fr);gap:4px 12px;margin:0}.discovery dt{color:var(--nha-text-muted)}.discovery dd{margin:0;overflow-wrap:anywhere}\n", "h5{margin:0;font-size:15px;font-weight:750}.group{display:flex;flex-direction:column;gap:10px;min-width:0;border-top:1px solid var(--nha-border);padding-top:14px}.group>legend{float:left;width:100%;margin-bottom:2px;font-size:14px;font-weight:750}.choices{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,220px),1fr));gap:8px}.choice{display:flex;align-items:flex-start;gap:10px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:10px 12px;cursor:pointer;overflow-wrap:anywhere}.choice input{flex:none;width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.choice>span{display:grid;gap:2px;min-width:0}.choice strong{font-size:13px}.choice.selected{border-color:var(--nha-highlight);background:var(--nha-surface)}.choice:focus-within{outline:2px solid var(--nha-highlight);outline-offset:2px}.block{display:block;margin-top:2px}.advanced{border-top:1px solid var(--nha-border);padding-top:12px}.advanced>summary{width:fit-content;border-radius:6px;font-size:14px;font-weight:750;cursor:pointer}.advanced>summary:focus-visible{outline:2px solid var(--nha-highlight);outline-offset:2px}.advanced>.stack{margin-top:12px}.examples{display:flex;flex-wrap:wrap;gap:6px;margin:6px 0 0;padding:0;list-style:none}.examples button{border:1px solid var(--nha-border);border-radius:999px;padding:3px 10px;color:var(--nha-text);background:transparent;font:inherit;font-size:12px;cursor:pointer}.examples button:hover{background:var(--nha-surface-strong)}.examples button:focus-visible{outline:2px solid var(--nha-highlight);outline-offset:2px}.steps{display:flex;flex-wrap:wrap;gap:6px;margin:0;padding:0;list-style:none}.steps li{display:inline-flex;align-items:center;gap:6px;border-radius:999px;padding:4px 10px;color:var(--nha-text-muted);background:var(--nha-surface-strong);font-size:12px;font-weight:700}.steps li[aria-current=step]{color:var(--nha-accent-contrast);background:var(--nha-accent)}.steps li.done{color:var(--nha-success);background:var(--nha-success-soft)}.review{display:grid;grid-template-columns:minmax(0,max-content) minmax(0,1fr);gap:6px 16px;margin:0;font-size:13px}.review dt{color:var(--nha-text-muted)}.review dd{margin:0;overflow-wrap:anywhere}.tool-choice{display:flex;flex-direction:column;gap:2px;min-width:0;overflow-wrap:anywhere}@media(max-width:520px){.review{grid-template-columns:minmax(0,1fr)}.review dd{margin-bottom:6px}}.table-scroll{max-width:100%;overflow-x:auto}\n"], dependencies: [{ kind: "component", type: _NhAssistantIconComponent, selector: "nh-assistant-icon", inputs: ["name"] }, { kind: "component", type: _NhAssistantPickerComponent, selector: "nh-assistant-picker", inputs: ["inputId", "options", "value", "selectedLabel", "noneLabel", "placeholder", "search", "minQueryLength", "invalid", "describedBy", "disabled"], outputs: ["valueChange", "optionChange"] }, { kind: "component", type: NhAssistantAccountPickerComponent, selector: "nh-assistant-account-picker", inputs: ["inputId", "value", "searchable", "invalid", "describedBy"], outputs: ["valueChange"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }, { kind: "pipe", type: _NhAssistantTranslatePipe, name: "nhAssistantTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
2826
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.34", type: NhAssistantRunSettingsFormComponent, isStandalone: true, selector: "nh-assistant-run-settings-form", inputs: { agentId: { classPropertyName: "agentId", publicName: "agentId", isSignal: true, isRequired: true, transformFunction: null }, catalog: { classPropertyName: "catalog", publicName: "catalog", isSignal: true, isRequired: false, transformFunction: null }, settings: { classPropertyName: "settings", publicName: "settings", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { saved: "saved", cancelled: "cancelled" }, usesInheritance: true, ngImport: i0, template: "<form class=\"stack card\" data-form=\"settings\" novalidate (submit)=\"$event.preventDefault(); save()\">\n <h5>{{ (settings() ? 'nh-assistant.admin.run-config.settings.edit-title' : 'nh-assistant.admin.run-config.settings.add-title') | translate }}</h5>\n\n <fieldset class=\"group\" [attr.aria-describedby]=\"id + '-who-explain'\">\n <legend>{{ 'nh-assistant.admin.run-config.settings.who.title' | translate }}</legend>\n <p class=\"muted\" [id]=\"id + '-who-explain'\">{{ 'nh-assistant.admin.run-config.settings.who.explain' | translate }}</p>\n <div class=\"choices\" role=\"radiogroup\" [attr.aria-label]=\"'nh-assistant.admin.run-config.settings.who.title' | translate\">\n @for (option of accountabilities; track option) {\n <label class=\"choice\" [class.selected]=\"accountability() === option\">\n <input type=\"radio\" [name]=\"id + '-accountability'\" [value]=\"option\" [checked]=\"accountability() === option\" (change)=\"setAccountability(option)\" />\n <span>\n <strong>{{ 'nh-assistant.admin.run-config.settings.accountability.' + option | translate }}</strong>\n <span class=\"muted\">{{ 'nh-assistant.admin.run-config.settings.accountability-explain.' + option | translate }}</span>\n </span>\n </label>\n }\n </div>\n @if (errorKey('accountability'); as key) {\n <p class=\"field-error\" [id]=\"id + '-accountability-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('accountability')?.params }}</p>\n }\n @if (accountability() === 'service-account') {\n <div class=\"field\">\n <label [for]=\"id + '-service-account'\">{{ 'nh-assistant.admin.run-config.settings.service-account' | translate }}</label>\n <nh-assistant-account-picker\n [inputId]=\"id + '-service-account'\"\n [value]=\"serviceAccount()\"\n [searchable]=\"accountSearch()\"\n [invalid]=\"fieldError('serviceAccountActorId') !== null\"\n [describedBy]=\"describedBy(id + '-service-account-hint', 'serviceAccountActorId', id + '-service-account-error')\"\n (valueChange)=\"serviceAccount.set($event); touch('serviceAccountActorId')\" />\n <p class=\"hint\" [id]=\"id + '-service-account-hint'\">\n {{ (accountSearch() ? 'nh-assistant.admin.run-config.account.search-hint' : 'nh-assistant.admin.run-config.account.id-hint') | translate }}\n </p>\n @if (errorKey('serviceAccountActorId'); as key) {\n <p class=\"field-error\" [id]=\"id + '-service-account-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('serviceAccountActorId')?.params }}</p>\n }\n </div>\n }\n </fieldset>\n\n <fieldset class=\"group\">\n <legend>{{ 'nh-assistant.admin.run-config.settings.how.title' | translate }}</legend>\n <div class=\"field\">\n <label [for]=\"id + '-rule-handler'\">{{ 'nh-assistant.admin.run-config.way-of-working' | translate }}</label>\n <nh-assistant-picker\n [inputId]=\"id + '-rule-handler'\"\n [options]=\"rules()\"\n [value]=\"ruleHandler()\"\n [noneLabel]=\"'nh-assistant.admin.run-config.model' | translate\"\n [invalid]=\"fieldError('ruleHandler') !== null\"\n [describedBy]=\"describedBy(id + '-rule-handler-hint', 'ruleHandler', id + '-rule-handler-error')\"\n (valueChange)=\"ruleHandler.set($event); touch('ruleHandler')\" />\n <p class=\"hint\" [id]=\"id + '-rule-handler-hint'\">{{ 'nh-assistant.admin.run-config.settings.how.hint' | translate }}</p>\n @if (errorKey('ruleHandler'); as key) {\n <p class=\"field-error\" [id]=\"id + '-rule-handler-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('ruleHandler')?.params }}</p>\n }\n </div>\n <label class=\"check\" data-accepts-files>\n <input type=\"checkbox\" [checked]=\"acceptsFiles()\" (change)=\"acceptsFiles.set(checked($event))\" />\n <span>\n {{ 'nh-assistant.admin.run-config.files.label' | translate }}\n <span class=\"muted block\">{{ 'nh-assistant.admin.run-config.files.agent-hint' | translate }}</span>\n </span>\n </label>\n @if (acceptsFiles()) {\n <nh-assistant-file-limits-fields\n [draft]=\"fileLimits\"\n [application]=\"catalog()?.fileLimits\"\n [errors]=\"fieldErrors()\"\n [idPrefix]=\"id\"\n hintKey=\"nh-assistant.admin.run-config.files.agent-limits-hint\"\n (touched)=\"touch($event)\" />\n }\n </fieldset>\n\n <fieldset class=\"group\">\n <legend>{{ 'nh-assistant.admin.run-config.settings.approval.title' | translate }}</legend>\n <div class=\"field\">\n <label [for]=\"id + '-policy'\">{{ 'nh-assistant.admin.run-config.approval-policy' | translate }}</label>\n <nh-assistant-picker\n [inputId]=\"id + '-policy'\"\n [options]=\"policies()\"\n [value]=\"approvalPolicy()\"\n [noneLabel]=\"'nh-assistant.admin.run-config.settings.approval.default' | translate\"\n [invalid]=\"fieldError('approvalPolicy') !== null\"\n [describedBy]=\"describedBy(id + '-policy-hint', 'approvalPolicy', id + '-policy-error')\"\n (valueChange)=\"approvalPolicy.set($event); touch('approvalPolicy')\" />\n <p class=\"hint\" [id]=\"id + '-policy-hint'\">{{ 'nh-assistant.admin.run-config.settings.approval.hint' | translate }}</p>\n @if (errorKey('approvalPolicy'); as key) {\n <p class=\"field-error\" [id]=\"id + '-policy-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('approvalPolicy')?.params }}</p>\n }\n </div>\n </fieldset>\n\n <fieldset class=\"group\">\n <legend>{{ 'nh-assistant.admin.run-config.limits.title' | translate }}</legend>\n <p class=\"muted\" [id]=\"id + '-limits-hint'\">{{ 'nh-assistant.admin.run-config.budget-hint' | translate }}</p>\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-run-tokens'\">{{ 'nh-assistant.admin.run-config.max-tokens' | translate }}</label>\n <input type=\"number\" min=\"1\" inputmode=\"numeric\" [id]=\"id + '-run-tokens'\" [value]=\"maxTokensPerRun()\"\n [attr.aria-invalid]=\"fieldError('maxTokensPerRun') ? true : null\"\n [attr.aria-describedby]=\"describedBy(id + '-limits-hint', 'maxTokensPerRun', id + '-run-tokens-error')\"\n (input)=\"maxTokensPerRun.set(value($event)); touch('maxTokensPerRun')\" />\n @if (errorKey('maxTokensPerRun'); as key) {\n <p class=\"field-error\" [id]=\"id + '-run-tokens-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('maxTokensPerRun')?.params }}</p>\n }\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-daily-calls'\">{{ 'nh-assistant.admin.run-config.daily-tool-calls' | translate }}</label>\n <input type=\"number\" min=\"1\" inputmode=\"numeric\" [id]=\"id + '-daily-calls'\" [value]=\"dailyToolCalls()\"\n [attr.aria-invalid]=\"fieldError('dailyToolCalls') ? true : null\"\n [attr.aria-describedby]=\"describedBy(id + '-limits-hint', 'dailyToolCalls', id + '-daily-calls-error')\"\n (input)=\"dailyToolCalls.set(value($event)); touch('dailyToolCalls'); touch('dailyTokens')\" />\n @if (errorKey('dailyToolCalls'); as key) {\n <p class=\"field-error\" [id]=\"id + '-daily-calls-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('dailyToolCalls')?.params }}</p>\n }\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-daily-tokens'\">{{ 'nh-assistant.admin.run-config.daily-tokens' | translate }}</label>\n <input type=\"number\" min=\"1\" inputmode=\"numeric\" [id]=\"id + '-daily-tokens'\" [value]=\"dailyTokens()\"\n [attr.aria-invalid]=\"fieldError('dailyTokens') ? true : null\"\n [attr.aria-describedby]=\"describedBy(id + '-limits-hint', 'dailyTokens', id + '-daily-tokens-error')\"\n (input)=\"dailyTokens.set(value($event)); touch('dailyTokens'); touch('dailyToolCalls')\" />\n @if (errorKey('dailyTokens'); as key) {\n <p class=\"field-error\" [id]=\"id + '-daily-tokens-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('dailyTokens')?.params }}</p>\n }\n </div>\n </div>\n </fieldset>\n\n <details class=\"advanced\" data-advanced [open]=\"advancedOpen() || advancedHasErrors()\" (toggle)=\"advancedOpen.set($any($event.target).open)\">\n <summary>{{ 'nh-assistant.admin.run-config.advanced.title' | translate }}</summary>\n <div class=\"stack\">\n <p class=\"muted\">{{ 'nh-assistant.admin.run-config.advanced.explain' | translate }}</p>\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-retries'\">{{ 'nh-assistant.admin.run-config.settings.retries' | translate }}</label>\n <input type=\"number\" min=\"0\" max=\"10\" inputmode=\"numeric\" [id]=\"id + '-retries'\" [value]=\"retryCount()\"\n [attr.aria-invalid]=\"fieldError('retryCount') ? true : null\"\n [attr.aria-describedby]=\"describedBy(id + '-retries-hint', 'retryCount', id + '-retries-error')\"\n (input)=\"retryCount.set(value($event)); touch('retryCount')\" />\n <p class=\"hint\" [id]=\"id + '-retries-hint'\">{{ 'nh-assistant.admin.run-config.settings.retries-hint' | translate }}</p>\n @if (errorKey('retryCount'); as key) {\n <p class=\"field-error\" [id]=\"id + '-retries-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('retryCount')?.params }}</p>\n }\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-concurrent'\">{{ 'nh-assistant.admin.run-config.settings.max-concurrent' | translate }}</label>\n <input type=\"number\" min=\"1\" max=\"100\" inputmode=\"numeric\" [id]=\"id + '-concurrent'\" [value]=\"maxConcurrentRuns()\"\n [attr.aria-invalid]=\"fieldError('maxConcurrentRuns') ? true : null\"\n [attr.aria-describedby]=\"describedBy(id + '-concurrent-hint', 'maxConcurrentRuns', id + '-concurrent-error')\"\n (input)=\"maxConcurrentRuns.set(value($event)); touch('maxConcurrentRuns')\" />\n <p class=\"hint\" [id]=\"id + '-concurrent-hint'\">{{ 'nh-assistant.admin.run-config.settings.max-concurrent-hint' | translate }}</p>\n @if (errorKey('maxConcurrentRuns'); as key) {\n <p class=\"field-error\" [id]=\"id + '-concurrent-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('maxConcurrentRuns')?.params }}</p>\n }\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-per-subject'\">{{ 'nh-assistant.admin.run-config.settings.max-per-subject' | translate }}</label>\n <input type=\"number\" min=\"1\" max=\"1000\" inputmode=\"numeric\" [id]=\"id + '-per-subject'\" [value]=\"maxRunsPerSubjectPerDay()\"\n [attr.aria-invalid]=\"fieldError('maxRunsPerSubjectPerDay') ? true : null\"\n [attr.aria-describedby]=\"describedBy(id + '-per-subject-hint', 'maxRunsPerSubjectPerDay', id + '-per-subject-error')\"\n (input)=\"maxRunsPerSubjectPerDay.set(value($event)); touch('maxRunsPerSubjectPerDay')\" />\n <p class=\"hint\" [id]=\"id + '-per-subject-hint'\">{{ 'nh-assistant.admin.run-config.settings.max-per-subject-hint' | translate }}</p>\n @if (errorKey('maxRunsPerSubjectPerDay'); as key) {\n <p class=\"field-error\" [id]=\"id + '-per-subject-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('maxRunsPerSubjectPerDay')?.params }}</p>\n }\n </div>\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-triage'\">{{ 'nh-assistant.admin.run-config.settings.triage' | translate }}</label>\n <nh-assistant-picker\n [inputId]=\"id + '-triage'\"\n [options]=\"profiles()\"\n [value]=\"triageProfile()\"\n [noneLabel]=\"'nh-assistant.admin.run-config.settings.no-triage' | translate\"\n [invalid]=\"fieldError('triageProfile') !== null\"\n [describedBy]=\"describedBy(id + '-triage-hint', 'triageProfile', id + '-triage-error')\"\n (valueChange)=\"triageProfile.set($event); touch('triageProfile')\" />\n <p class=\"hint\" [id]=\"id + '-triage-hint'\">{{ 'nh-assistant.admin.run-config.settings.triage-hint' | translate }}</p>\n @if (errorKey('triageProfile'); as key) {\n <p class=\"field-error\" [id]=\"id + '-triage-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('triageProfile')?.params }}</p>\n }\n </div>\n <fieldset class=\"field\">\n <legend>{{ 'nh-assistant.admin.run-config.settings.elevated-reads' | translate }}</legend>\n <p class=\"hint\">{{ 'nh-assistant.admin.run-config.settings.elevated-reads-hint' | translate }}</p>\n @for (mode of elevatedReadModes; track mode) {\n <label class=\"check\">\n <input type=\"radio\" [name]=\"id + '-elevated'\" [value]=\"mode\" [checked]=\"elevatedReads() === mode\" (change)=\"elevatedReads.set(mode); touch('elevatedReads')\" />\n <span>{{ 'nh-assistant.admin.run-config.settings.elevated.' + mode | translate }}</span>\n </label>\n }\n @if (errorKey('elevatedReads'); as key) {\n <p class=\"field-error\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('elevatedReads')?.params }}</p>\n }\n </fieldset>\n <label class=\"check\">\n <input type=\"checkbox\" [checked]=\"allowGatewayReads()\" [attr.aria-invalid]=\"fieldError('allowGatewayReads') ? true : null\" (change)=\"allowGatewayReads.set(checked($event)); touch('allowGatewayReads')\" />\n <span>\n {{ 'nh-assistant.admin.run-config.settings.gateway-reads' | translate }}\n <span class=\"muted block\">{{ 'nh-assistant.admin.run-config.settings.gateway-reads-hint' | translate }}</span>\n </span>\n </label>\n @if (errorKey('allowGatewayReads'); as key) {\n <p class=\"field-error\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('allowGatewayReads')?.params }}</p>\n }\n <label class=\"check\">\n <input type=\"checkbox\" [checked]=\"delegateFromInitiator()\" (change)=\"delegateFromInitiator.set(checked($event))\" />\n <span>\n {{ 'nh-assistant.admin.run-config.settings.delegate' | translate }}\n <span class=\"muted block\">{{ 'nh-assistant.admin.run-config.settings.delegate-hint' | translate }}</span>\n </span>\n </label>\n </div>\n </details>\n\n <div class=\"field\">\n <label [for]=\"id + '-reason'\">{{ 'nh-assistant.admin.agents.state.note-label' | translate }}</label>\n <input type=\"text\" autocomplete=\"off\" [id]=\"id + '-reason'\" [maxLength]=\"maxReason\" [value]=\"reason()\" [attr.aria-describedby]=\"id + '-reason-hint'\" (input)=\"reason.set(value($event))\" />\n <p class=\"hint\" [id]=\"id + '-reason-hint'\">{{ 'nh-assistant.admin.run-config.note-hint' | translate }}</p>\n </div>\n\n @if (hasErrors()) {\n <div class=\"form-error\" role=\"alert\" data-form-error>\n <nh-assistant-icon name=\"warning\" />\n <div>\n @if (serverError(); as failure) {\n @if (failure.code === 'assistant-validation') {\n {{ 'nh-assistant.admin.run-config.check-fields' | translate }}\n } @else {\n {{ errorKeys() | nhAssistantTranslate: failure.code }}\n }\n } @else {\n {{ 'nh-assistant.admin.run-config.check-fields' | translate }}\n }\n @if (otherFieldErrors().length > 0) {\n <ul>\n @for (field of otherFieldErrors(); track field) {\n <li><span class=\"mono\">{{ field }}</span></li>\n }\n </ul>\n }\n </div>\n </div>\n }\n <div class=\"actions\">\n <button class=\"secondary\" type=\"button\" (click)=\"cancelled.emit()\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n <button class=\"primary\" type=\"submit\" [disabled]=\"busy()\">{{ (busy() ? 'nh-assistant.admin.saving' : 'nh-assistant.admin.save') | translate }}</button>\n </div>\n</form>\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;min-width:0;color:var(--nha-text);font-family:var(--nha-font);font-size:14px}:host fieldset{min-width:0;margin:0;border:0;padding:0}:host legend,:host .label,:host .field>label{display:block;margin-bottom:6px;padding:0;color:var(--nha-text);font-size:12px;font-weight:700}:host input[type=text],:host input[type=url],:host input[type=password],:host input[type=search],:host input[type=email],:host input[type=number],:host input[type=date],:host input[type=time],:host input[type=datetime-local],:host select,:host textarea{box-sizing:border-box;width:100%;min-width:0;min-height:38px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:8px 10px;color:var(--nha-text);background:var(--nha-background);font:inherit;font-size:13px}:host input[type=text]:focus-visible,:host input[type=url]:focus-visible,:host input[type=password]:focus-visible,:host input[type=search]:focus-visible,:host input[type=email]:focus-visible,:host input[type=number]:focus-visible,:host input[type=date]:focus-visible,:host input[type=time]:focus-visible,:host input[type=datetime-local]:focus-visible,:host select:focus-visible,:host textarea:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host input[type=text]:disabled,:host input[type=url]:disabled,:host input[type=password]:disabled,:host input[type=search]:disabled,:host input[type=email]:disabled,:host input[type=number]:disabled,:host input[type=date]:disabled,:host input[type=time]:disabled,:host input[type=datetime-local]:disabled,:host select:disabled,:host textarea:disabled{cursor:not-allowed;opacity:.6}:host input[type=text][aria-invalid=true],:host input[type=url][aria-invalid=true],:host input[type=password][aria-invalid=true],:host input[type=search][aria-invalid=true],:host input[type=email][aria-invalid=true],:host input[type=number][aria-invalid=true],:host input[type=date][aria-invalid=true],:host input[type=time][aria-invalid=true],:host input[type=datetime-local][aria-invalid=true],:host select[aria-invalid=true],:host textarea[aria-invalid=true]{border-color:var(--nha-danger)}:host textarea{line-height:1.45;resize:vertical}:host input[type=number]{font-variant-numeric:tabular-nums}:host .mono{font-family:var(--nha-mono);font-size:12px}:host .field{display:flex;flex-direction:column;min-width:0}:host .hint{display:flex;justify-content:space-between;gap:8px;margin:4px 2px 0;color:var(--nha-text-muted);font-size:11px}:host .counter{flex:none;font-variant-numeric:tabular-nums}:host .error-text{color:var(--nha-danger)}:host .field-error{display:flex;align-items:flex-start;gap:6px;margin:4px 2px 0;color:var(--nha-danger);font-size:12px;font-weight:600;overflow-wrap:anywhere}:host .field-error nh-assistant-icon{flex:none;margin-top:1px}:host .form-error{box-sizing:border-box;display:flex;align-items:flex-start;gap:8px;margin:0;border:1px solid var(--nha-danger);border-radius:var(--nha-radius-control);padding:8px 12px;color:var(--nha-text);background:var(--nha-danger-soft);font-size:13px;overflow-wrap:anywhere}:host .form-error ul{margin:4px 0 0;padding-left:18px}:host .message{box-sizing:border-box;display:flex;align-items:center;gap:8px;margin:0;border:1px solid transparent;border-radius:var(--nha-radius-control);padding:8px 12px;font-size:13px;overflow-wrap:anywhere}:host .message.error{border-color:var(--nha-danger);color:var(--nha-text);background:var(--nha-danger-soft)}:host .message.warning{border-color:var(--nha-warning);color:var(--nha-text);background:var(--nha-warning-soft)}:host .message.success{border-color:var(--nha-success);color:var(--nha-text);background:var(--nha-success-soft)}:host .message.info{border-color:var(--nha-border);color:var(--nha-text);background:var(--nha-surface)}:host .message nh-assistant-icon{flex:none}:host button.link{min-height:0;border:0;padding:0;color:var(--nha-accent, currentColor);background:none;font:inherit;text-decoration:underline;cursor:pointer}:host button.link:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host button.link:disabled{cursor:not-allowed;opacity:.55}:host .primary,:host .secondary,:host .danger{display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;gap:6px;min-height:36px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px 14px;font:inherit;font-size:13px;font-weight:700;white-space:nowrap;cursor:pointer}:host .primary:focus-visible,:host .secondary:focus-visible,:host .danger:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .primary:disabled,:host .secondary:disabled,:host .danger:disabled{cursor:not-allowed;opacity:.55}:host .primary{border-color:var(--nha-accent);color:var(--nha-accent-contrast);background:var(--nha-accent)}:host .primary:hover:not(:disabled){filter:brightness(1.12)}:host .secondary{color:var(--nha-text);background:transparent}:host .secondary:hover:not(:disabled){background:var(--nha-surface-strong)}:host .danger{border-color:var(--nha-danger);color:var(--nha-danger);background:transparent}:host .danger:hover:not(:disabled){background:var(--nha-danger-soft)}:host .icon-button{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:1px solid transparent;border-radius:var(--nha-radius-control);color:var(--nha-text-muted);background:transparent;cursor:pointer}:host .icon-button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .icon-button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}:host .icon-button:disabled{cursor:not-allowed;opacity:.5}:host .actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px}:host .badge{display:inline-flex;align-items:center;gap:4px;border-radius:999px;padding:2px 8px;color:var(--nha-text-muted);background:var(--nha-surface-strong);font-size:11px;font-weight:700;white-space:nowrap}:host .badge.success{color:var(--nha-success);background:var(--nha-success-soft)}:host .badge.warning{color:var(--nha-warning);background:var(--nha-warning-soft)}:host .badge.danger{color:var(--nha-danger);background:var(--nha-danger-soft)}:host .visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}.section{display:flex;flex-direction:column;gap:16px;min-width:0}.section-header{display:flex;flex-wrap:wrap;align-items:flex-start;justify-content:space-between;gap:12px}.section-header>div{min-width:0;max-width:72ch}h3{margin:0 0 4px;font-size:17px;font-weight:750}h4{margin:0 0 8px;font-size:14px;font-weight:700}.muted{margin:0;color:var(--nha-text-muted);font-size:13px}.state{display:flex;flex-direction:column;align-items:flex-start;gap:10px;margin:0;border:1px dashed var(--nha-border);border-radius:var(--nha-radius);padding:20px;color:var(--nha-text-muted);font-size:13px}.state p{margin:0}.stack{display:flex;flex-direction:column;gap:14px;min-width:0}.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,240px),1fr));gap:14px}.meta{display:flex;flex-wrap:wrap;align-items:center;gap:8px 12px;color:var(--nha-text-muted);font-size:12px;overflow-wrap:anywhere}textarea.large{min-height:260px}.catalog-error{flex-wrap:wrap}.grow{flex:1;min-width:0}.subsection{display:flex;flex-direction:column;gap:8px;border-top:1px solid var(--nha-border);padding-top:14px}.history{display:grid;gap:6px;margin:0;padding:0;list-style:none}.history li{display:flex;flex-wrap:wrap;align-items:center;gap:6px 12px;font-size:12px}.cards{display:grid;gap:10px;margin:0;padding:0;list-style:none}.card{display:flex;flex-direction:column;gap:8px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius);padding:14px;background:var(--nha-background)}.card.inactive{opacity:.8}.card-header{display:flex;flex-wrap:wrap;align-items:center;gap:8px;min-width:0}.card-header strong{font-size:14px;overflow-wrap:anywhere}.card-actions{display:flex;flex-wrap:wrap;gap:8px}.check{display:flex;align-items:flex-start;gap:8px;font-size:13px;cursor:pointer}.check input{width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.check.described>span{display:grid;gap:2px;min-width:0;overflow-wrap:anywhere}.checks{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));gap:6px 12px;min-width:0;overflow-wrap:anywhere}.chips{display:flex;flex-wrap:wrap;gap:6px;margin:0;padding:0;list-style:none}.chip{display:inline-flex;align-items:center;gap:2px;border:1px solid var(--nha-border);border-radius:999px;padding:2px 2px 2px 10px;background:var(--nha-surface);font-family:var(--nha-mono);font-size:12px}.chip 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;width:26px;height:26px;border-radius:999px}.chip button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.chip button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}.chip button:disabled{cursor:not-allowed;opacity:.5}.inline{display:flex;flex-wrap:wrap;gap:8px}.inline>input{flex:1 1 180px}.catalog{display:grid;gap:4px;max-height:260px;margin:8px 0 0;overflow-y:auto;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px;list-style:none}.catalog li label{display:flex;flex-wrap:wrap;align-items:center;gap:6px 8px;border-radius:8px;padding:6px;cursor:pointer}.catalog li label:hover{background:var(--nha-surface)}.catalog .tool-id{font-family:var(--nha-mono);font-size:12px;overflow-wrap:anywhere}.skills{display:grid;gap:8px;margin:0;padding:0;list-style:none}.skill{display:grid;gap:6px;border-left:3px solid var(--nha-border);padding:4px 0 4px 12px}.uses{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));gap:8px}.use{display:flex;align-items:flex-start;gap:10px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:10px 12px;cursor:pointer;overflow-wrap:anywhere}.use input{flex:none;width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.use>span{display:grid;gap:2px;min-width:0}.use strong{font-size:13px}.use.selected{border-color:var(--nha-highlight);background:var(--nha-surface)}.use:focus-within{outline:2px solid var(--nha-highlight);outline-offset:2px}.discovery{display:grid;gap:8px;padding:10px 12px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control)}.discovery dl{display:grid;grid-template-columns:max-content minmax(0,1fr);gap:4px 12px;margin:0}.discovery dt{color:var(--nha-text-muted)}.discovery dd{margin:0;overflow-wrap:anywhere}\n", "h5{margin:0;font-size:15px;font-weight:750}.group{display:flex;flex-direction:column;gap:10px;min-width:0;border-top:1px solid var(--nha-border);padding-top:14px}.group>legend{float:left;width:100%;margin-bottom:2px;font-size:14px;font-weight:750}.choices{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,220px),1fr));gap:8px}.choice{display:flex;align-items:flex-start;gap:10px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:10px 12px;cursor:pointer;overflow-wrap:anywhere}.choice input{flex:none;width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.choice>span{display:grid;gap:2px;min-width:0}.choice strong{font-size:13px}.choice.selected{border-color:var(--nha-highlight);background:var(--nha-surface)}.choice:focus-within{outline:2px solid var(--nha-highlight);outline-offset:2px}.block{display:block;margin-top:2px}.advanced{border-top:1px solid var(--nha-border);padding-top:12px}.advanced>summary{width:fit-content;border-radius:6px;font-size:14px;font-weight:750;cursor:pointer}.advanced>summary:focus-visible{outline:2px solid var(--nha-highlight);outline-offset:2px}.advanced>.stack{margin-top:12px}.examples{display:flex;flex-wrap:wrap;gap:6px;margin:6px 0 0;padding:0;list-style:none}.examples button{border:1px solid var(--nha-border);border-radius:999px;padding:3px 10px;color:var(--nha-text);background:transparent;font:inherit;font-size:12px;cursor:pointer}.examples button:hover{background:var(--nha-surface-strong)}.examples button:focus-visible{outline:2px solid var(--nha-highlight);outline-offset:2px}.steps{display:flex;flex-wrap:wrap;gap:6px;margin:0;padding:0;list-style:none}.steps li{display:inline-flex;align-items:center;gap:6px;border-radius:999px;padding:4px 10px;color:var(--nha-text-muted);background:var(--nha-surface-strong);font-size:12px;font-weight:700}.steps li[aria-current=step]{color:var(--nha-accent-contrast);background:var(--nha-accent)}.steps li.done{color:var(--nha-success);background:var(--nha-success-soft)}.review{display:grid;grid-template-columns:minmax(0,max-content) minmax(0,1fr);gap:6px 16px;margin:0;font-size:13px}.review dt{color:var(--nha-text-muted)}.review dd{margin:0;overflow-wrap:anywhere}.tool-choice{display:flex;flex-direction:column;gap:2px;min-width:0;overflow-wrap:anywhere}@media(max-width:520px){.review{grid-template-columns:minmax(0,1fr)}.review dd{margin-bottom:6px}}.table-scroll{max-width:100%;overflow-x:auto}\n"], dependencies: [{ kind: "component", type: _NhAssistantIconComponent, selector: "nh-assistant-icon", inputs: ["name"] }, { kind: "component", type: _NhAssistantPickerComponent, selector: "nh-assistant-picker", inputs: ["inputId", "options", "value", "selectedLabel", "noneLabel", "placeholder", "search", "minQueryLength", "invalid", "describedBy", "disabled"], outputs: ["valueChange", "optionChange"] }, { kind: "component", type: NhAssistantAccountPickerComponent, selector: "nh-assistant-account-picker", inputs: ["inputId", "value", "searchable", "invalid", "describedBy"], outputs: ["valueChange"] }, { kind: "component", type: NhAssistantFileLimitsFieldsComponent, selector: "nh-assistant-file-limits-fields", inputs: ["draft", "application", "errors", "idPrefix", "hintKey"], outputs: ["touched"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }, { kind: "pipe", type: _NhAssistantTranslatePipe, name: "nhAssistantTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
2623
2827
|
}
|
|
2624
2828
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: NhAssistantRunSettingsFormComponent, decorators: [{
|
|
2625
2829
|
type: Component,
|
|
2626
|
-
args: [{ selector: 'nh-assistant-run-settings-form', standalone: true, imports: [TranslatePipe, _NhAssistantTranslatePipe, _NhAssistantIconComponent, _NhAssistantPickerComponent, NhAssistantAccountPickerComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<form class=\"stack card\" data-form=\"settings\" novalidate (submit)=\"$event.preventDefault(); save()\">\n <h5>{{ (settings() ? 'nh-assistant.admin.run-config.settings.edit-title' : 'nh-assistant.admin.run-config.settings.add-title') | translate }}</h5>\n\n <fieldset class=\"group\" [attr.aria-describedby]=\"id + '-who-explain'\">\n <legend>{{ 'nh-assistant.admin.run-config.settings.who.title' | translate }}</legend>\n <p class=\"muted\" [id]=\"id + '-who-explain'\">{{ 'nh-assistant.admin.run-config.settings.who.explain' | translate }}</p>\n <div class=\"choices\" role=\"radiogroup\" [attr.aria-label]=\"'nh-assistant.admin.run-config.settings.who.title' | translate\">\n @for (option of accountabilities; track option) {\n <label class=\"choice\" [class.selected]=\"accountability() === option\">\n <input type=\"radio\" [name]=\"id + '-accountability'\" [value]=\"option\" [checked]=\"accountability() === option\" (change)=\"setAccountability(option)\" />\n <span>\n <strong>{{ 'nh-assistant.admin.run-config.settings.accountability.' + option | translate }}</strong>\n <span class=\"muted\">{{ 'nh-assistant.admin.run-config.settings.accountability-explain.' + option | translate }}</span>\n </span>\n </label>\n }\n </div>\n @if (errorKey('accountability'); as key) {\n <p class=\"field-error\" [id]=\"id + '-accountability-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('accountability')?.params }}</p>\n }\n @if (accountability() === 'service-account') {\n <div class=\"field\">\n <label [for]=\"id + '-service-account'\">{{ 'nh-assistant.admin.run-config.settings.service-account' | translate }}</label>\n <nh-assistant-account-picker\n [inputId]=\"id + '-service-account'\"\n [value]=\"serviceAccount()\"\n [searchable]=\"accountSearch()\"\n [invalid]=\"fieldError('serviceAccountActorId') !== null\"\n [describedBy]=\"describedBy(id + '-service-account-hint', 'serviceAccountActorId', id + '-service-account-error')\"\n (valueChange)=\"serviceAccount.set($event); touch('serviceAccountActorId')\" />\n <p class=\"hint\" [id]=\"id + '-service-account-hint'\">\n {{ (accountSearch() ? 'nh-assistant.admin.run-config.account.search-hint' : 'nh-assistant.admin.run-config.account.id-hint') | translate }}\n </p>\n @if (errorKey('serviceAccountActorId'); as key) {\n <p class=\"field-error\" [id]=\"id + '-service-account-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('serviceAccountActorId')?.params }}</p>\n }\n </div>\n }\n </fieldset>\n\n <fieldset class=\"group\">\n <legend>{{ 'nh-assistant.admin.run-config.settings.how.title' | translate }}</legend>\n <div class=\"field\">\n <label [for]=\"id + '-rule-handler'\">{{ 'nh-assistant.admin.run-config.way-of-working' | translate }}</label>\n <nh-assistant-picker\n [inputId]=\"id + '-rule-handler'\"\n [options]=\"rules()\"\n [value]=\"ruleHandler()\"\n [noneLabel]=\"'nh-assistant.admin.run-config.model' | translate\"\n [invalid]=\"fieldError('ruleHandler') !== null\"\n [describedBy]=\"describedBy(id + '-rule-handler-hint', 'ruleHandler', id + '-rule-handler-error')\"\n (valueChange)=\"ruleHandler.set($event); touch('ruleHandler')\" />\n <p class=\"hint\" [id]=\"id + '-rule-handler-hint'\">{{ 'nh-assistant.admin.run-config.settings.how.hint' | translate }}</p>\n @if (errorKey('ruleHandler'); as key) {\n <p class=\"field-error\" [id]=\"id + '-rule-handler-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('ruleHandler')?.params }}</p>\n }\n </div>\n </fieldset>\n\n <fieldset class=\"group\">\n <legend>{{ 'nh-assistant.admin.run-config.settings.approval.title' | translate }}</legend>\n <div class=\"field\">\n <label [for]=\"id + '-policy'\">{{ 'nh-assistant.admin.run-config.approval-policy' | translate }}</label>\n <nh-assistant-picker\n [inputId]=\"id + '-policy'\"\n [options]=\"policies()\"\n [value]=\"approvalPolicy()\"\n [noneLabel]=\"'nh-assistant.admin.run-config.settings.approval.default' | translate\"\n [invalid]=\"fieldError('approvalPolicy') !== null\"\n [describedBy]=\"describedBy(id + '-policy-hint', 'approvalPolicy', id + '-policy-error')\"\n (valueChange)=\"approvalPolicy.set($event); touch('approvalPolicy')\" />\n <p class=\"hint\" [id]=\"id + '-policy-hint'\">{{ 'nh-assistant.admin.run-config.settings.approval.hint' | translate }}</p>\n @if (errorKey('approvalPolicy'); as key) {\n <p class=\"field-error\" [id]=\"id + '-policy-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('approvalPolicy')?.params }}</p>\n }\n </div>\n </fieldset>\n\n <fieldset class=\"group\">\n <legend>{{ 'nh-assistant.admin.run-config.limits.title' | translate }}</legend>\n <p class=\"muted\" [id]=\"id + '-limits-hint'\">{{ 'nh-assistant.admin.run-config.budget-hint' | translate }}</p>\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-run-tokens'\">{{ 'nh-assistant.admin.run-config.max-tokens' | translate }}</label>\n <input type=\"number\" min=\"1\" inputmode=\"numeric\" [id]=\"id + '-run-tokens'\" [value]=\"maxTokensPerRun()\"\n [attr.aria-invalid]=\"fieldError('maxTokensPerRun') ? true : null\"\n [attr.aria-describedby]=\"describedBy(id + '-limits-hint', 'maxTokensPerRun', id + '-run-tokens-error')\"\n (input)=\"maxTokensPerRun.set(value($event)); touch('maxTokensPerRun')\" />\n @if (errorKey('maxTokensPerRun'); as key) {\n <p class=\"field-error\" [id]=\"id + '-run-tokens-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('maxTokensPerRun')?.params }}</p>\n }\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-daily-calls'\">{{ 'nh-assistant.admin.run-config.daily-tool-calls' | translate }}</label>\n <input type=\"number\" min=\"1\" inputmode=\"numeric\" [id]=\"id + '-daily-calls'\" [value]=\"dailyToolCalls()\"\n [attr.aria-invalid]=\"fieldError('dailyToolCalls') ? true : null\"\n [attr.aria-describedby]=\"describedBy(id + '-limits-hint', 'dailyToolCalls', id + '-daily-calls-error')\"\n (input)=\"dailyToolCalls.set(value($event)); touch('dailyToolCalls'); touch('dailyTokens')\" />\n @if (errorKey('dailyToolCalls'); as key) {\n <p class=\"field-error\" [id]=\"id + '-daily-calls-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('dailyToolCalls')?.params }}</p>\n }\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-daily-tokens'\">{{ 'nh-assistant.admin.run-config.daily-tokens' | translate }}</label>\n <input type=\"number\" min=\"1\" inputmode=\"numeric\" [id]=\"id + '-daily-tokens'\" [value]=\"dailyTokens()\"\n [attr.aria-invalid]=\"fieldError('dailyTokens') ? true : null\"\n [attr.aria-describedby]=\"describedBy(id + '-limits-hint', 'dailyTokens', id + '-daily-tokens-error')\"\n (input)=\"dailyTokens.set(value($event)); touch('dailyTokens'); touch('dailyToolCalls')\" />\n @if (errorKey('dailyTokens'); as key) {\n <p class=\"field-error\" [id]=\"id + '-daily-tokens-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('dailyTokens')?.params }}</p>\n }\n </div>\n </div>\n </fieldset>\n\n <details class=\"advanced\" data-advanced [open]=\"advancedOpen() || advancedHasErrors()\" (toggle)=\"advancedOpen.set($any($event.target).open)\">\n <summary>{{ 'nh-assistant.admin.run-config.advanced.title' | translate }}</summary>\n <div class=\"stack\">\n <p class=\"muted\">{{ 'nh-assistant.admin.run-config.advanced.explain' | translate }}</p>\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-retries'\">{{ 'nh-assistant.admin.run-config.settings.retries' | translate }}</label>\n <input type=\"number\" min=\"0\" max=\"10\" inputmode=\"numeric\" [id]=\"id + '-retries'\" [value]=\"retryCount()\"\n [attr.aria-invalid]=\"fieldError('retryCount') ? true : null\"\n [attr.aria-describedby]=\"describedBy(id + '-retries-hint', 'retryCount', id + '-retries-error')\"\n (input)=\"retryCount.set(value($event)); touch('retryCount')\" />\n <p class=\"hint\" [id]=\"id + '-retries-hint'\">{{ 'nh-assistant.admin.run-config.settings.retries-hint' | translate }}</p>\n @if (errorKey('retryCount'); as key) {\n <p class=\"field-error\" [id]=\"id + '-retries-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('retryCount')?.params }}</p>\n }\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-concurrent'\">{{ 'nh-assistant.admin.run-config.settings.max-concurrent' | translate }}</label>\n <input type=\"number\" min=\"1\" max=\"100\" inputmode=\"numeric\" [id]=\"id + '-concurrent'\" [value]=\"maxConcurrentRuns()\"\n [attr.aria-invalid]=\"fieldError('maxConcurrentRuns') ? true : null\"\n [attr.aria-describedby]=\"describedBy(id + '-concurrent-hint', 'maxConcurrentRuns', id + '-concurrent-error')\"\n (input)=\"maxConcurrentRuns.set(value($event)); touch('maxConcurrentRuns')\" />\n <p class=\"hint\" [id]=\"id + '-concurrent-hint'\">{{ 'nh-assistant.admin.run-config.settings.max-concurrent-hint' | translate }}</p>\n @if (errorKey('maxConcurrentRuns'); as key) {\n <p class=\"field-error\" [id]=\"id + '-concurrent-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('maxConcurrentRuns')?.params }}</p>\n }\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-per-subject'\">{{ 'nh-assistant.admin.run-config.settings.max-per-subject' | translate }}</label>\n <input type=\"number\" min=\"1\" max=\"1000\" inputmode=\"numeric\" [id]=\"id + '-per-subject'\" [value]=\"maxRunsPerSubjectPerDay()\"\n [attr.aria-invalid]=\"fieldError('maxRunsPerSubjectPerDay') ? true : null\"\n [attr.aria-describedby]=\"describedBy(id + '-per-subject-hint', 'maxRunsPerSubjectPerDay', id + '-per-subject-error')\"\n (input)=\"maxRunsPerSubjectPerDay.set(value($event)); touch('maxRunsPerSubjectPerDay')\" />\n <p class=\"hint\" [id]=\"id + '-per-subject-hint'\">{{ 'nh-assistant.admin.run-config.settings.max-per-subject-hint' | translate }}</p>\n @if (errorKey('maxRunsPerSubjectPerDay'); as key) {\n <p class=\"field-error\" [id]=\"id + '-per-subject-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('maxRunsPerSubjectPerDay')?.params }}</p>\n }\n </div>\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-triage'\">{{ 'nh-assistant.admin.run-config.settings.triage' | translate }}</label>\n <nh-assistant-picker\n [inputId]=\"id + '-triage'\"\n [options]=\"profiles()\"\n [value]=\"triageProfile()\"\n [noneLabel]=\"'nh-assistant.admin.run-config.settings.no-triage' | translate\"\n [invalid]=\"fieldError('triageProfile') !== null\"\n [describedBy]=\"describedBy(id + '-triage-hint', 'triageProfile', id + '-triage-error')\"\n (valueChange)=\"triageProfile.set($event); touch('triageProfile')\" />\n <p class=\"hint\" [id]=\"id + '-triage-hint'\">{{ 'nh-assistant.admin.run-config.settings.triage-hint' | translate }}</p>\n @if (errorKey('triageProfile'); as key) {\n <p class=\"field-error\" [id]=\"id + '-triage-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('triageProfile')?.params }}</p>\n }\n </div>\n <fieldset class=\"field\">\n <legend>{{ 'nh-assistant.admin.run-config.settings.elevated-reads' | translate }}</legend>\n <p class=\"hint\">{{ 'nh-assistant.admin.run-config.settings.elevated-reads-hint' | translate }}</p>\n @for (mode of elevatedReadModes; track mode) {\n <label class=\"check\">\n <input type=\"radio\" [name]=\"id + '-elevated'\" [value]=\"mode\" [checked]=\"elevatedReads() === mode\" (change)=\"elevatedReads.set(mode); touch('elevatedReads')\" />\n <span>{{ 'nh-assistant.admin.run-config.settings.elevated.' + mode | translate }}</span>\n </label>\n }\n @if (errorKey('elevatedReads'); as key) {\n <p class=\"field-error\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('elevatedReads')?.params }}</p>\n }\n </fieldset>\n <label class=\"check\">\n <input type=\"checkbox\" [checked]=\"allowGatewayReads()\" [attr.aria-invalid]=\"fieldError('allowGatewayReads') ? true : null\" (change)=\"allowGatewayReads.set(checked($event)); touch('allowGatewayReads')\" />\n <span>\n {{ 'nh-assistant.admin.run-config.settings.gateway-reads' | translate }}\n <span class=\"muted block\">{{ 'nh-assistant.admin.run-config.settings.gateway-reads-hint' | translate }}</span>\n </span>\n </label>\n @if (errorKey('allowGatewayReads'); as key) {\n <p class=\"field-error\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('allowGatewayReads')?.params }}</p>\n }\n <label class=\"check\">\n <input type=\"checkbox\" [checked]=\"delegateFromInitiator()\" (change)=\"delegateFromInitiator.set(checked($event))\" />\n <span>\n {{ 'nh-assistant.admin.run-config.settings.delegate' | translate }}\n <span class=\"muted block\">{{ 'nh-assistant.admin.run-config.settings.delegate-hint' | translate }}</span>\n </span>\n </label>\n </div>\n </details>\n\n <div class=\"field\">\n <label [for]=\"id + '-reason'\">{{ 'nh-assistant.admin.agents.state.note-label' | translate }}</label>\n <input type=\"text\" autocomplete=\"off\" [id]=\"id + '-reason'\" [maxLength]=\"maxReason\" [value]=\"reason()\" [attr.aria-describedby]=\"id + '-reason-hint'\" (input)=\"reason.set(value($event))\" />\n <p class=\"hint\" [id]=\"id + '-reason-hint'\">{{ 'nh-assistant.admin.run-config.note-hint' | translate }}</p>\n </div>\n\n @if (hasErrors()) {\n <div class=\"form-error\" role=\"alert\" data-form-error>\n <nh-assistant-icon name=\"warning\" />\n <div>\n @if (serverError(); as failure) {\n @if (failure.code === 'assistant-validation') {\n {{ 'nh-assistant.admin.run-config.check-fields' | translate }}\n } @else {\n {{ errorKeys() | nhAssistantTranslate: failure.code }}\n }\n } @else {\n {{ 'nh-assistant.admin.run-config.check-fields' | translate }}\n }\n @if (otherFieldErrors().length > 0) {\n <ul>\n @for (field of otherFieldErrors(); track field) {\n <li><span class=\"mono\">{{ field }}</span></li>\n }\n </ul>\n }\n </div>\n </div>\n }\n <div class=\"actions\">\n <button class=\"secondary\" type=\"button\" (click)=\"cancelled.emit()\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n <button class=\"primary\" type=\"submit\" [disabled]=\"busy()\">{{ (busy() ? 'nh-assistant.admin.saving' : 'nh-assistant.admin.save') | translate }}</button>\n </div>\n</form>\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;min-width:0;color:var(--nha-text);font-family:var(--nha-font);font-size:14px}:host fieldset{min-width:0;margin:0;border:0;padding:0}:host legend,:host .label,:host .field>label{display:block;margin-bottom:6px;padding:0;color:var(--nha-text);font-size:12px;font-weight:700}:host input[type=text],:host input[type=url],:host input[type=password],:host input[type=search],:host input[type=email],:host input[type=number],:host input[type=date],:host input[type=time],:host input[type=datetime-local],:host select,:host textarea{box-sizing:border-box;width:100%;min-width:0;min-height:38px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:8px 10px;color:var(--nha-text);background:var(--nha-background);font:inherit;font-size:13px}:host input[type=text]:focus-visible,:host input[type=url]:focus-visible,:host input[type=password]:focus-visible,:host input[type=search]:focus-visible,:host input[type=email]:focus-visible,:host input[type=number]:focus-visible,:host input[type=date]:focus-visible,:host input[type=time]:focus-visible,:host input[type=datetime-local]:focus-visible,:host select:focus-visible,:host textarea:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host input[type=text]:disabled,:host input[type=url]:disabled,:host input[type=password]:disabled,:host input[type=search]:disabled,:host input[type=email]:disabled,:host input[type=number]:disabled,:host input[type=date]:disabled,:host input[type=time]:disabled,:host input[type=datetime-local]:disabled,:host select:disabled,:host textarea:disabled{cursor:not-allowed;opacity:.6}:host input[type=text][aria-invalid=true],:host input[type=url][aria-invalid=true],:host input[type=password][aria-invalid=true],:host input[type=search][aria-invalid=true],:host input[type=email][aria-invalid=true],:host input[type=number][aria-invalid=true],:host input[type=date][aria-invalid=true],:host input[type=time][aria-invalid=true],:host input[type=datetime-local][aria-invalid=true],:host select[aria-invalid=true],:host textarea[aria-invalid=true]{border-color:var(--nha-danger)}:host textarea{line-height:1.45;resize:vertical}:host input[type=number]{font-variant-numeric:tabular-nums}:host .mono{font-family:var(--nha-mono);font-size:12px}:host .field{display:flex;flex-direction:column;min-width:0}:host .hint{display:flex;justify-content:space-between;gap:8px;margin:4px 2px 0;color:var(--nha-text-muted);font-size:11px}:host .counter{flex:none;font-variant-numeric:tabular-nums}:host .error-text{color:var(--nha-danger)}:host .field-error{display:flex;align-items:flex-start;gap:6px;margin:4px 2px 0;color:var(--nha-danger);font-size:12px;font-weight:600;overflow-wrap:anywhere}:host .field-error nh-assistant-icon{flex:none;margin-top:1px}:host .form-error{box-sizing:border-box;display:flex;align-items:flex-start;gap:8px;margin:0;border:1px solid var(--nha-danger);border-radius:var(--nha-radius-control);padding:8px 12px;color:var(--nha-text);background:var(--nha-danger-soft);font-size:13px;overflow-wrap:anywhere}:host .form-error ul{margin:4px 0 0;padding-left:18px}:host .message{box-sizing:border-box;display:flex;align-items:center;gap:8px;margin:0;border:1px solid transparent;border-radius:var(--nha-radius-control);padding:8px 12px;font-size:13px;overflow-wrap:anywhere}:host .message.error{border-color:var(--nha-danger);color:var(--nha-text);background:var(--nha-danger-soft)}:host .message.warning{border-color:var(--nha-warning);color:var(--nha-text);background:var(--nha-warning-soft)}:host .message.success{border-color:var(--nha-success);color:var(--nha-text);background:var(--nha-success-soft)}:host .message.info{border-color:var(--nha-border);color:var(--nha-text);background:var(--nha-surface)}:host .message nh-assistant-icon{flex:none}:host button.link{min-height:0;border:0;padding:0;color:var(--nha-accent, currentColor);background:none;font:inherit;text-decoration:underline;cursor:pointer}:host button.link:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host button.link:disabled{cursor:not-allowed;opacity:.55}:host .primary,:host .secondary,:host .danger{display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;gap:6px;min-height:36px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px 14px;font:inherit;font-size:13px;font-weight:700;white-space:nowrap;cursor:pointer}:host .primary:focus-visible,:host .secondary:focus-visible,:host .danger:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .primary:disabled,:host .secondary:disabled,:host .danger:disabled{cursor:not-allowed;opacity:.55}:host .primary{border-color:var(--nha-accent);color:var(--nha-accent-contrast);background:var(--nha-accent)}:host .primary:hover:not(:disabled){filter:brightness(1.12)}:host .secondary{color:var(--nha-text);background:transparent}:host .secondary:hover:not(:disabled){background:var(--nha-surface-strong)}:host .danger{border-color:var(--nha-danger);color:var(--nha-danger);background:transparent}:host .danger:hover:not(:disabled){background:var(--nha-danger-soft)}:host .icon-button{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:1px solid transparent;border-radius:var(--nha-radius-control);color:var(--nha-text-muted);background:transparent;cursor:pointer}:host .icon-button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .icon-button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}:host .icon-button:disabled{cursor:not-allowed;opacity:.5}:host .actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px}:host .badge{display:inline-flex;align-items:center;gap:4px;border-radius:999px;padding:2px 8px;color:var(--nha-text-muted);background:var(--nha-surface-strong);font-size:11px;font-weight:700;white-space:nowrap}:host .badge.success{color:var(--nha-success);background:var(--nha-success-soft)}:host .badge.warning{color:var(--nha-warning);background:var(--nha-warning-soft)}:host .badge.danger{color:var(--nha-danger);background:var(--nha-danger-soft)}:host .visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}.section{display:flex;flex-direction:column;gap:16px;min-width:0}.section-header{display:flex;flex-wrap:wrap;align-items:flex-start;justify-content:space-between;gap:12px}.section-header>div{min-width:0;max-width:72ch}h3{margin:0 0 4px;font-size:17px;font-weight:750}h4{margin:0 0 8px;font-size:14px;font-weight:700}.muted{margin:0;color:var(--nha-text-muted);font-size:13px}.state{display:flex;flex-direction:column;align-items:flex-start;gap:10px;margin:0;border:1px dashed var(--nha-border);border-radius:var(--nha-radius);padding:20px;color:var(--nha-text-muted);font-size:13px}.state p{margin:0}.stack{display:flex;flex-direction:column;gap:14px;min-width:0}.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,240px),1fr));gap:14px}.meta{display:flex;flex-wrap:wrap;align-items:center;gap:8px 12px;color:var(--nha-text-muted);font-size:12px;overflow-wrap:anywhere}textarea.large{min-height:260px}.catalog-error{flex-wrap:wrap}.grow{flex:1;min-width:0}.subsection{display:flex;flex-direction:column;gap:8px;border-top:1px solid var(--nha-border);padding-top:14px}.history{display:grid;gap:6px;margin:0;padding:0;list-style:none}.history li{display:flex;flex-wrap:wrap;align-items:center;gap:6px 12px;font-size:12px}.cards{display:grid;gap:10px;margin:0;padding:0;list-style:none}.card{display:flex;flex-direction:column;gap:8px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius);padding:14px;background:var(--nha-background)}.card.inactive{opacity:.8}.card-header{display:flex;flex-wrap:wrap;align-items:center;gap:8px;min-width:0}.card-header strong{font-size:14px;overflow-wrap:anywhere}.card-actions{display:flex;flex-wrap:wrap;gap:8px}.check{display:flex;align-items:flex-start;gap:8px;font-size:13px;cursor:pointer}.check input{width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.check.described>span{display:grid;gap:2px;min-width:0;overflow-wrap:anywhere}.checks{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));gap:6px 12px;min-width:0;overflow-wrap:anywhere}.chips{display:flex;flex-wrap:wrap;gap:6px;margin:0;padding:0;list-style:none}.chip{display:inline-flex;align-items:center;gap:2px;border:1px solid var(--nha-border);border-radius:999px;padding:2px 2px 2px 10px;background:var(--nha-surface);font-family:var(--nha-mono);font-size:12px}.chip 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;width:26px;height:26px;border-radius:999px}.chip button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.chip button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}.chip button:disabled{cursor:not-allowed;opacity:.5}.inline{display:flex;flex-wrap:wrap;gap:8px}.inline>input{flex:1 1 180px}.catalog{display:grid;gap:4px;max-height:260px;margin:8px 0 0;overflow-y:auto;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px;list-style:none}.catalog li label{display:flex;flex-wrap:wrap;align-items:center;gap:6px 8px;border-radius:8px;padding:6px;cursor:pointer}.catalog li label:hover{background:var(--nha-surface)}.catalog .tool-id{font-family:var(--nha-mono);font-size:12px;overflow-wrap:anywhere}.skills{display:grid;gap:8px;margin:0;padding:0;list-style:none}.skill{display:grid;gap:6px;border-left:3px solid var(--nha-border);padding:4px 0 4px 12px}.uses{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));gap:8px}.use{display:flex;align-items:flex-start;gap:10px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:10px 12px;cursor:pointer;overflow-wrap:anywhere}.use input{flex:none;width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.use>span{display:grid;gap:2px;min-width:0}.use strong{font-size:13px}.use.selected{border-color:var(--nha-highlight);background:var(--nha-surface)}.use:focus-within{outline:2px solid var(--nha-highlight);outline-offset:2px}.discovery{display:grid;gap:8px;padding:10px 12px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control)}.discovery dl{display:grid;grid-template-columns:max-content minmax(0,1fr);gap:4px 12px;margin:0}.discovery dt{color:var(--nha-text-muted)}.discovery dd{margin:0;overflow-wrap:anywhere}\n", "h5{margin:0;font-size:15px;font-weight:750}.group{display:flex;flex-direction:column;gap:10px;min-width:0;border-top:1px solid var(--nha-border);padding-top:14px}.group>legend{float:left;width:100%;margin-bottom:2px;font-size:14px;font-weight:750}.choices{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,220px),1fr));gap:8px}.choice{display:flex;align-items:flex-start;gap:10px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:10px 12px;cursor:pointer;overflow-wrap:anywhere}.choice input{flex:none;width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.choice>span{display:grid;gap:2px;min-width:0}.choice strong{font-size:13px}.choice.selected{border-color:var(--nha-highlight);background:var(--nha-surface)}.choice:focus-within{outline:2px solid var(--nha-highlight);outline-offset:2px}.block{display:block;margin-top:2px}.advanced{border-top:1px solid var(--nha-border);padding-top:12px}.advanced>summary{width:fit-content;border-radius:6px;font-size:14px;font-weight:750;cursor:pointer}.advanced>summary:focus-visible{outline:2px solid var(--nha-highlight);outline-offset:2px}.advanced>.stack{margin-top:12px}.examples{display:flex;flex-wrap:wrap;gap:6px;margin:6px 0 0;padding:0;list-style:none}.examples button{border:1px solid var(--nha-border);border-radius:999px;padding:3px 10px;color:var(--nha-text);background:transparent;font:inherit;font-size:12px;cursor:pointer}.examples button:hover{background:var(--nha-surface-strong)}.examples button:focus-visible{outline:2px solid var(--nha-highlight);outline-offset:2px}.steps{display:flex;flex-wrap:wrap;gap:6px;margin:0;padding:0;list-style:none}.steps li{display:inline-flex;align-items:center;gap:6px;border-radius:999px;padding:4px 10px;color:var(--nha-text-muted);background:var(--nha-surface-strong);font-size:12px;font-weight:700}.steps li[aria-current=step]{color:var(--nha-accent-contrast);background:var(--nha-accent)}.steps li.done{color:var(--nha-success);background:var(--nha-success-soft)}.review{display:grid;grid-template-columns:minmax(0,max-content) minmax(0,1fr);gap:6px 16px;margin:0;font-size:13px}.review dt{color:var(--nha-text-muted)}.review dd{margin:0;overflow-wrap:anywhere}.tool-choice{display:flex;flex-direction:column;gap:2px;min-width:0;overflow-wrap:anywhere}@media(max-width:520px){.review{grid-template-columns:minmax(0,1fr)}.review dd{margin-bottom:6px}}.table-scroll{max-width:100%;overflow-x:auto}\n"] }]
|
|
2830
|
+
args: [{ selector: 'nh-assistant-run-settings-form', standalone: true, imports: [
|
|
2831
|
+
TranslatePipe,
|
|
2832
|
+
_NhAssistantTranslatePipe,
|
|
2833
|
+
_NhAssistantIconComponent,
|
|
2834
|
+
_NhAssistantPickerComponent,
|
|
2835
|
+
NhAssistantAccountPickerComponent,
|
|
2836
|
+
NhAssistantFileLimitsFieldsComponent
|
|
2837
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, template: "<form class=\"stack card\" data-form=\"settings\" novalidate (submit)=\"$event.preventDefault(); save()\">\n <h5>{{ (settings() ? 'nh-assistant.admin.run-config.settings.edit-title' : 'nh-assistant.admin.run-config.settings.add-title') | translate }}</h5>\n\n <fieldset class=\"group\" [attr.aria-describedby]=\"id + '-who-explain'\">\n <legend>{{ 'nh-assistant.admin.run-config.settings.who.title' | translate }}</legend>\n <p class=\"muted\" [id]=\"id + '-who-explain'\">{{ 'nh-assistant.admin.run-config.settings.who.explain' | translate }}</p>\n <div class=\"choices\" role=\"radiogroup\" [attr.aria-label]=\"'nh-assistant.admin.run-config.settings.who.title' | translate\">\n @for (option of accountabilities; track option) {\n <label class=\"choice\" [class.selected]=\"accountability() === option\">\n <input type=\"radio\" [name]=\"id + '-accountability'\" [value]=\"option\" [checked]=\"accountability() === option\" (change)=\"setAccountability(option)\" />\n <span>\n <strong>{{ 'nh-assistant.admin.run-config.settings.accountability.' + option | translate }}</strong>\n <span class=\"muted\">{{ 'nh-assistant.admin.run-config.settings.accountability-explain.' + option | translate }}</span>\n </span>\n </label>\n }\n </div>\n @if (errorKey('accountability'); as key) {\n <p class=\"field-error\" [id]=\"id + '-accountability-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('accountability')?.params }}</p>\n }\n @if (accountability() === 'service-account') {\n <div class=\"field\">\n <label [for]=\"id + '-service-account'\">{{ 'nh-assistant.admin.run-config.settings.service-account' | translate }}</label>\n <nh-assistant-account-picker\n [inputId]=\"id + '-service-account'\"\n [value]=\"serviceAccount()\"\n [searchable]=\"accountSearch()\"\n [invalid]=\"fieldError('serviceAccountActorId') !== null\"\n [describedBy]=\"describedBy(id + '-service-account-hint', 'serviceAccountActorId', id + '-service-account-error')\"\n (valueChange)=\"serviceAccount.set($event); touch('serviceAccountActorId')\" />\n <p class=\"hint\" [id]=\"id + '-service-account-hint'\">\n {{ (accountSearch() ? 'nh-assistant.admin.run-config.account.search-hint' : 'nh-assistant.admin.run-config.account.id-hint') | translate }}\n </p>\n @if (errorKey('serviceAccountActorId'); as key) {\n <p class=\"field-error\" [id]=\"id + '-service-account-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('serviceAccountActorId')?.params }}</p>\n }\n </div>\n }\n </fieldset>\n\n <fieldset class=\"group\">\n <legend>{{ 'nh-assistant.admin.run-config.settings.how.title' | translate }}</legend>\n <div class=\"field\">\n <label [for]=\"id + '-rule-handler'\">{{ 'nh-assistant.admin.run-config.way-of-working' | translate }}</label>\n <nh-assistant-picker\n [inputId]=\"id + '-rule-handler'\"\n [options]=\"rules()\"\n [value]=\"ruleHandler()\"\n [noneLabel]=\"'nh-assistant.admin.run-config.model' | translate\"\n [invalid]=\"fieldError('ruleHandler') !== null\"\n [describedBy]=\"describedBy(id + '-rule-handler-hint', 'ruleHandler', id + '-rule-handler-error')\"\n (valueChange)=\"ruleHandler.set($event); touch('ruleHandler')\" />\n <p class=\"hint\" [id]=\"id + '-rule-handler-hint'\">{{ 'nh-assistant.admin.run-config.settings.how.hint' | translate }}</p>\n @if (errorKey('ruleHandler'); as key) {\n <p class=\"field-error\" [id]=\"id + '-rule-handler-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('ruleHandler')?.params }}</p>\n }\n </div>\n <label class=\"check\" data-accepts-files>\n <input type=\"checkbox\" [checked]=\"acceptsFiles()\" (change)=\"acceptsFiles.set(checked($event))\" />\n <span>\n {{ 'nh-assistant.admin.run-config.files.label' | translate }}\n <span class=\"muted block\">{{ 'nh-assistant.admin.run-config.files.agent-hint' | translate }}</span>\n </span>\n </label>\n @if (acceptsFiles()) {\n <nh-assistant-file-limits-fields\n [draft]=\"fileLimits\"\n [application]=\"catalog()?.fileLimits\"\n [errors]=\"fieldErrors()\"\n [idPrefix]=\"id\"\n hintKey=\"nh-assistant.admin.run-config.files.agent-limits-hint\"\n (touched)=\"touch($event)\" />\n }\n </fieldset>\n\n <fieldset class=\"group\">\n <legend>{{ 'nh-assistant.admin.run-config.settings.approval.title' | translate }}</legend>\n <div class=\"field\">\n <label [for]=\"id + '-policy'\">{{ 'nh-assistant.admin.run-config.approval-policy' | translate }}</label>\n <nh-assistant-picker\n [inputId]=\"id + '-policy'\"\n [options]=\"policies()\"\n [value]=\"approvalPolicy()\"\n [noneLabel]=\"'nh-assistant.admin.run-config.settings.approval.default' | translate\"\n [invalid]=\"fieldError('approvalPolicy') !== null\"\n [describedBy]=\"describedBy(id + '-policy-hint', 'approvalPolicy', id + '-policy-error')\"\n (valueChange)=\"approvalPolicy.set($event); touch('approvalPolicy')\" />\n <p class=\"hint\" [id]=\"id + '-policy-hint'\">{{ 'nh-assistant.admin.run-config.settings.approval.hint' | translate }}</p>\n @if (errorKey('approvalPolicy'); as key) {\n <p class=\"field-error\" [id]=\"id + '-policy-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('approvalPolicy')?.params }}</p>\n }\n </div>\n </fieldset>\n\n <fieldset class=\"group\">\n <legend>{{ 'nh-assistant.admin.run-config.limits.title' | translate }}</legend>\n <p class=\"muted\" [id]=\"id + '-limits-hint'\">{{ 'nh-assistant.admin.run-config.budget-hint' | translate }}</p>\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-run-tokens'\">{{ 'nh-assistant.admin.run-config.max-tokens' | translate }}</label>\n <input type=\"number\" min=\"1\" inputmode=\"numeric\" [id]=\"id + '-run-tokens'\" [value]=\"maxTokensPerRun()\"\n [attr.aria-invalid]=\"fieldError('maxTokensPerRun') ? true : null\"\n [attr.aria-describedby]=\"describedBy(id + '-limits-hint', 'maxTokensPerRun', id + '-run-tokens-error')\"\n (input)=\"maxTokensPerRun.set(value($event)); touch('maxTokensPerRun')\" />\n @if (errorKey('maxTokensPerRun'); as key) {\n <p class=\"field-error\" [id]=\"id + '-run-tokens-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('maxTokensPerRun')?.params }}</p>\n }\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-daily-calls'\">{{ 'nh-assistant.admin.run-config.daily-tool-calls' | translate }}</label>\n <input type=\"number\" min=\"1\" inputmode=\"numeric\" [id]=\"id + '-daily-calls'\" [value]=\"dailyToolCalls()\"\n [attr.aria-invalid]=\"fieldError('dailyToolCalls') ? true : null\"\n [attr.aria-describedby]=\"describedBy(id + '-limits-hint', 'dailyToolCalls', id + '-daily-calls-error')\"\n (input)=\"dailyToolCalls.set(value($event)); touch('dailyToolCalls'); touch('dailyTokens')\" />\n @if (errorKey('dailyToolCalls'); as key) {\n <p class=\"field-error\" [id]=\"id + '-daily-calls-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('dailyToolCalls')?.params }}</p>\n }\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-daily-tokens'\">{{ 'nh-assistant.admin.run-config.daily-tokens' | translate }}</label>\n <input type=\"number\" min=\"1\" inputmode=\"numeric\" [id]=\"id + '-daily-tokens'\" [value]=\"dailyTokens()\"\n [attr.aria-invalid]=\"fieldError('dailyTokens') ? true : null\"\n [attr.aria-describedby]=\"describedBy(id + '-limits-hint', 'dailyTokens', id + '-daily-tokens-error')\"\n (input)=\"dailyTokens.set(value($event)); touch('dailyTokens'); touch('dailyToolCalls')\" />\n @if (errorKey('dailyTokens'); as key) {\n <p class=\"field-error\" [id]=\"id + '-daily-tokens-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('dailyTokens')?.params }}</p>\n }\n </div>\n </div>\n </fieldset>\n\n <details class=\"advanced\" data-advanced [open]=\"advancedOpen() || advancedHasErrors()\" (toggle)=\"advancedOpen.set($any($event.target).open)\">\n <summary>{{ 'nh-assistant.admin.run-config.advanced.title' | translate }}</summary>\n <div class=\"stack\">\n <p class=\"muted\">{{ 'nh-assistant.admin.run-config.advanced.explain' | translate }}</p>\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-retries'\">{{ 'nh-assistant.admin.run-config.settings.retries' | translate }}</label>\n <input type=\"number\" min=\"0\" max=\"10\" inputmode=\"numeric\" [id]=\"id + '-retries'\" [value]=\"retryCount()\"\n [attr.aria-invalid]=\"fieldError('retryCount') ? true : null\"\n [attr.aria-describedby]=\"describedBy(id + '-retries-hint', 'retryCount', id + '-retries-error')\"\n (input)=\"retryCount.set(value($event)); touch('retryCount')\" />\n <p class=\"hint\" [id]=\"id + '-retries-hint'\">{{ 'nh-assistant.admin.run-config.settings.retries-hint' | translate }}</p>\n @if (errorKey('retryCount'); as key) {\n <p class=\"field-error\" [id]=\"id + '-retries-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('retryCount')?.params }}</p>\n }\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-concurrent'\">{{ 'nh-assistant.admin.run-config.settings.max-concurrent' | translate }}</label>\n <input type=\"number\" min=\"1\" max=\"100\" inputmode=\"numeric\" [id]=\"id + '-concurrent'\" [value]=\"maxConcurrentRuns()\"\n [attr.aria-invalid]=\"fieldError('maxConcurrentRuns') ? true : null\"\n [attr.aria-describedby]=\"describedBy(id + '-concurrent-hint', 'maxConcurrentRuns', id + '-concurrent-error')\"\n (input)=\"maxConcurrentRuns.set(value($event)); touch('maxConcurrentRuns')\" />\n <p class=\"hint\" [id]=\"id + '-concurrent-hint'\">{{ 'nh-assistant.admin.run-config.settings.max-concurrent-hint' | translate }}</p>\n @if (errorKey('maxConcurrentRuns'); as key) {\n <p class=\"field-error\" [id]=\"id + '-concurrent-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('maxConcurrentRuns')?.params }}</p>\n }\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-per-subject'\">{{ 'nh-assistant.admin.run-config.settings.max-per-subject' | translate }}</label>\n <input type=\"number\" min=\"1\" max=\"1000\" inputmode=\"numeric\" [id]=\"id + '-per-subject'\" [value]=\"maxRunsPerSubjectPerDay()\"\n [attr.aria-invalid]=\"fieldError('maxRunsPerSubjectPerDay') ? true : null\"\n [attr.aria-describedby]=\"describedBy(id + '-per-subject-hint', 'maxRunsPerSubjectPerDay', id + '-per-subject-error')\"\n (input)=\"maxRunsPerSubjectPerDay.set(value($event)); touch('maxRunsPerSubjectPerDay')\" />\n <p class=\"hint\" [id]=\"id + '-per-subject-hint'\">{{ 'nh-assistant.admin.run-config.settings.max-per-subject-hint' | translate }}</p>\n @if (errorKey('maxRunsPerSubjectPerDay'); as key) {\n <p class=\"field-error\" [id]=\"id + '-per-subject-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('maxRunsPerSubjectPerDay')?.params }}</p>\n }\n </div>\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-triage'\">{{ 'nh-assistant.admin.run-config.settings.triage' | translate }}</label>\n <nh-assistant-picker\n [inputId]=\"id + '-triage'\"\n [options]=\"profiles()\"\n [value]=\"triageProfile()\"\n [noneLabel]=\"'nh-assistant.admin.run-config.settings.no-triage' | translate\"\n [invalid]=\"fieldError('triageProfile') !== null\"\n [describedBy]=\"describedBy(id + '-triage-hint', 'triageProfile', id + '-triage-error')\"\n (valueChange)=\"triageProfile.set($event); touch('triageProfile')\" />\n <p class=\"hint\" [id]=\"id + '-triage-hint'\">{{ 'nh-assistant.admin.run-config.settings.triage-hint' | translate }}</p>\n @if (errorKey('triageProfile'); as key) {\n <p class=\"field-error\" [id]=\"id + '-triage-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('triageProfile')?.params }}</p>\n }\n </div>\n <fieldset class=\"field\">\n <legend>{{ 'nh-assistant.admin.run-config.settings.elevated-reads' | translate }}</legend>\n <p class=\"hint\">{{ 'nh-assistant.admin.run-config.settings.elevated-reads-hint' | translate }}</p>\n @for (mode of elevatedReadModes; track mode) {\n <label class=\"check\">\n <input type=\"radio\" [name]=\"id + '-elevated'\" [value]=\"mode\" [checked]=\"elevatedReads() === mode\" (change)=\"elevatedReads.set(mode); touch('elevatedReads')\" />\n <span>{{ 'nh-assistant.admin.run-config.settings.elevated.' + mode | translate }}</span>\n </label>\n }\n @if (errorKey('elevatedReads'); as key) {\n <p class=\"field-error\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('elevatedReads')?.params }}</p>\n }\n </fieldset>\n <label class=\"check\">\n <input type=\"checkbox\" [checked]=\"allowGatewayReads()\" [attr.aria-invalid]=\"fieldError('allowGatewayReads') ? true : null\" (change)=\"allowGatewayReads.set(checked($event)); touch('allowGatewayReads')\" />\n <span>\n {{ 'nh-assistant.admin.run-config.settings.gateway-reads' | translate }}\n <span class=\"muted block\">{{ 'nh-assistant.admin.run-config.settings.gateway-reads-hint' | translate }}</span>\n </span>\n </label>\n @if (errorKey('allowGatewayReads'); as key) {\n <p class=\"field-error\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('allowGatewayReads')?.params }}</p>\n }\n <label class=\"check\">\n <input type=\"checkbox\" [checked]=\"delegateFromInitiator()\" (change)=\"delegateFromInitiator.set(checked($event))\" />\n <span>\n {{ 'nh-assistant.admin.run-config.settings.delegate' | translate }}\n <span class=\"muted block\">{{ 'nh-assistant.admin.run-config.settings.delegate-hint' | translate }}</span>\n </span>\n </label>\n </div>\n </details>\n\n <div class=\"field\">\n <label [for]=\"id + '-reason'\">{{ 'nh-assistant.admin.agents.state.note-label' | translate }}</label>\n <input type=\"text\" autocomplete=\"off\" [id]=\"id + '-reason'\" [maxLength]=\"maxReason\" [value]=\"reason()\" [attr.aria-describedby]=\"id + '-reason-hint'\" (input)=\"reason.set(value($event))\" />\n <p class=\"hint\" [id]=\"id + '-reason-hint'\">{{ 'nh-assistant.admin.run-config.note-hint' | translate }}</p>\n </div>\n\n @if (hasErrors()) {\n <div class=\"form-error\" role=\"alert\" data-form-error>\n <nh-assistant-icon name=\"warning\" />\n <div>\n @if (serverError(); as failure) {\n @if (failure.code === 'assistant-validation') {\n {{ 'nh-assistant.admin.run-config.check-fields' | translate }}\n } @else {\n {{ errorKeys() | nhAssistantTranslate: failure.code }}\n }\n } @else {\n {{ 'nh-assistant.admin.run-config.check-fields' | translate }}\n }\n @if (otherFieldErrors().length > 0) {\n <ul>\n @for (field of otherFieldErrors(); track field) {\n <li><span class=\"mono\">{{ field }}</span></li>\n }\n </ul>\n }\n </div>\n </div>\n }\n <div class=\"actions\">\n <button class=\"secondary\" type=\"button\" (click)=\"cancelled.emit()\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n <button class=\"primary\" type=\"submit\" [disabled]=\"busy()\">{{ (busy() ? 'nh-assistant.admin.saving' : 'nh-assistant.admin.save') | translate }}</button>\n </div>\n</form>\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;min-width:0;color:var(--nha-text);font-family:var(--nha-font);font-size:14px}:host fieldset{min-width:0;margin:0;border:0;padding:0}:host legend,:host .label,:host .field>label{display:block;margin-bottom:6px;padding:0;color:var(--nha-text);font-size:12px;font-weight:700}:host input[type=text],:host input[type=url],:host input[type=password],:host input[type=search],:host input[type=email],:host input[type=number],:host input[type=date],:host input[type=time],:host input[type=datetime-local],:host select,:host textarea{box-sizing:border-box;width:100%;min-width:0;min-height:38px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:8px 10px;color:var(--nha-text);background:var(--nha-background);font:inherit;font-size:13px}:host input[type=text]:focus-visible,:host input[type=url]:focus-visible,:host input[type=password]:focus-visible,:host input[type=search]:focus-visible,:host input[type=email]:focus-visible,:host input[type=number]:focus-visible,:host input[type=date]:focus-visible,:host input[type=time]:focus-visible,:host input[type=datetime-local]:focus-visible,:host select:focus-visible,:host textarea:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host input[type=text]:disabled,:host input[type=url]:disabled,:host input[type=password]:disabled,:host input[type=search]:disabled,:host input[type=email]:disabled,:host input[type=number]:disabled,:host input[type=date]:disabled,:host input[type=time]:disabled,:host input[type=datetime-local]:disabled,:host select:disabled,:host textarea:disabled{cursor:not-allowed;opacity:.6}:host input[type=text][aria-invalid=true],:host input[type=url][aria-invalid=true],:host input[type=password][aria-invalid=true],:host input[type=search][aria-invalid=true],:host input[type=email][aria-invalid=true],:host input[type=number][aria-invalid=true],:host input[type=date][aria-invalid=true],:host input[type=time][aria-invalid=true],:host input[type=datetime-local][aria-invalid=true],:host select[aria-invalid=true],:host textarea[aria-invalid=true]{border-color:var(--nha-danger)}:host textarea{line-height:1.45;resize:vertical}:host input[type=number]{font-variant-numeric:tabular-nums}:host .mono{font-family:var(--nha-mono);font-size:12px}:host .field{display:flex;flex-direction:column;min-width:0}:host .hint{display:flex;justify-content:space-between;gap:8px;margin:4px 2px 0;color:var(--nha-text-muted);font-size:11px}:host .counter{flex:none;font-variant-numeric:tabular-nums}:host .error-text{color:var(--nha-danger)}:host .field-error{display:flex;align-items:flex-start;gap:6px;margin:4px 2px 0;color:var(--nha-danger);font-size:12px;font-weight:600;overflow-wrap:anywhere}:host .field-error nh-assistant-icon{flex:none;margin-top:1px}:host .form-error{box-sizing:border-box;display:flex;align-items:flex-start;gap:8px;margin:0;border:1px solid var(--nha-danger);border-radius:var(--nha-radius-control);padding:8px 12px;color:var(--nha-text);background:var(--nha-danger-soft);font-size:13px;overflow-wrap:anywhere}:host .form-error ul{margin:4px 0 0;padding-left:18px}:host .message{box-sizing:border-box;display:flex;align-items:center;gap:8px;margin:0;border:1px solid transparent;border-radius:var(--nha-radius-control);padding:8px 12px;font-size:13px;overflow-wrap:anywhere}:host .message.error{border-color:var(--nha-danger);color:var(--nha-text);background:var(--nha-danger-soft)}:host .message.warning{border-color:var(--nha-warning);color:var(--nha-text);background:var(--nha-warning-soft)}:host .message.success{border-color:var(--nha-success);color:var(--nha-text);background:var(--nha-success-soft)}:host .message.info{border-color:var(--nha-border);color:var(--nha-text);background:var(--nha-surface)}:host .message nh-assistant-icon{flex:none}:host button.link{min-height:0;border:0;padding:0;color:var(--nha-accent, currentColor);background:none;font:inherit;text-decoration:underline;cursor:pointer}:host button.link:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host button.link:disabled{cursor:not-allowed;opacity:.55}:host .primary,:host .secondary,:host .danger{display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;gap:6px;min-height:36px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px 14px;font:inherit;font-size:13px;font-weight:700;white-space:nowrap;cursor:pointer}:host .primary:focus-visible,:host .secondary:focus-visible,:host .danger:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .primary:disabled,:host .secondary:disabled,:host .danger:disabled{cursor:not-allowed;opacity:.55}:host .primary{border-color:var(--nha-accent);color:var(--nha-accent-contrast);background:var(--nha-accent)}:host .primary:hover:not(:disabled){filter:brightness(1.12)}:host .secondary{color:var(--nha-text);background:transparent}:host .secondary:hover:not(:disabled){background:var(--nha-surface-strong)}:host .danger{border-color:var(--nha-danger);color:var(--nha-danger);background:transparent}:host .danger:hover:not(:disabled){background:var(--nha-danger-soft)}:host .icon-button{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:1px solid transparent;border-radius:var(--nha-radius-control);color:var(--nha-text-muted);background:transparent;cursor:pointer}:host .icon-button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .icon-button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}:host .icon-button:disabled{cursor:not-allowed;opacity:.5}:host .actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px}:host .badge{display:inline-flex;align-items:center;gap:4px;border-radius:999px;padding:2px 8px;color:var(--nha-text-muted);background:var(--nha-surface-strong);font-size:11px;font-weight:700;white-space:nowrap}:host .badge.success{color:var(--nha-success);background:var(--nha-success-soft)}:host .badge.warning{color:var(--nha-warning);background:var(--nha-warning-soft)}:host .badge.danger{color:var(--nha-danger);background:var(--nha-danger-soft)}:host .visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}.section{display:flex;flex-direction:column;gap:16px;min-width:0}.section-header{display:flex;flex-wrap:wrap;align-items:flex-start;justify-content:space-between;gap:12px}.section-header>div{min-width:0;max-width:72ch}h3{margin:0 0 4px;font-size:17px;font-weight:750}h4{margin:0 0 8px;font-size:14px;font-weight:700}.muted{margin:0;color:var(--nha-text-muted);font-size:13px}.state{display:flex;flex-direction:column;align-items:flex-start;gap:10px;margin:0;border:1px dashed var(--nha-border);border-radius:var(--nha-radius);padding:20px;color:var(--nha-text-muted);font-size:13px}.state p{margin:0}.stack{display:flex;flex-direction:column;gap:14px;min-width:0}.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,240px),1fr));gap:14px}.meta{display:flex;flex-wrap:wrap;align-items:center;gap:8px 12px;color:var(--nha-text-muted);font-size:12px;overflow-wrap:anywhere}textarea.large{min-height:260px}.catalog-error{flex-wrap:wrap}.grow{flex:1;min-width:0}.subsection{display:flex;flex-direction:column;gap:8px;border-top:1px solid var(--nha-border);padding-top:14px}.history{display:grid;gap:6px;margin:0;padding:0;list-style:none}.history li{display:flex;flex-wrap:wrap;align-items:center;gap:6px 12px;font-size:12px}.cards{display:grid;gap:10px;margin:0;padding:0;list-style:none}.card{display:flex;flex-direction:column;gap:8px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius);padding:14px;background:var(--nha-background)}.card.inactive{opacity:.8}.card-header{display:flex;flex-wrap:wrap;align-items:center;gap:8px;min-width:0}.card-header strong{font-size:14px;overflow-wrap:anywhere}.card-actions{display:flex;flex-wrap:wrap;gap:8px}.check{display:flex;align-items:flex-start;gap:8px;font-size:13px;cursor:pointer}.check input{width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.check.described>span{display:grid;gap:2px;min-width:0;overflow-wrap:anywhere}.checks{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));gap:6px 12px;min-width:0;overflow-wrap:anywhere}.chips{display:flex;flex-wrap:wrap;gap:6px;margin:0;padding:0;list-style:none}.chip{display:inline-flex;align-items:center;gap:2px;border:1px solid var(--nha-border);border-radius:999px;padding:2px 2px 2px 10px;background:var(--nha-surface);font-family:var(--nha-mono);font-size:12px}.chip 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;width:26px;height:26px;border-radius:999px}.chip button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.chip button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}.chip button:disabled{cursor:not-allowed;opacity:.5}.inline{display:flex;flex-wrap:wrap;gap:8px}.inline>input{flex:1 1 180px}.catalog{display:grid;gap:4px;max-height:260px;margin:8px 0 0;overflow-y:auto;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px;list-style:none}.catalog li label{display:flex;flex-wrap:wrap;align-items:center;gap:6px 8px;border-radius:8px;padding:6px;cursor:pointer}.catalog li label:hover{background:var(--nha-surface)}.catalog .tool-id{font-family:var(--nha-mono);font-size:12px;overflow-wrap:anywhere}.skills{display:grid;gap:8px;margin:0;padding:0;list-style:none}.skill{display:grid;gap:6px;border-left:3px solid var(--nha-border);padding:4px 0 4px 12px}.uses{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));gap:8px}.use{display:flex;align-items:flex-start;gap:10px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:10px 12px;cursor:pointer;overflow-wrap:anywhere}.use input{flex:none;width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.use>span{display:grid;gap:2px;min-width:0}.use strong{font-size:13px}.use.selected{border-color:var(--nha-highlight);background:var(--nha-surface)}.use:focus-within{outline:2px solid var(--nha-highlight);outline-offset:2px}.discovery{display:grid;gap:8px;padding:10px 12px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control)}.discovery dl{display:grid;grid-template-columns:max-content minmax(0,1fr);gap:4px 12px;margin:0}.discovery dt{color:var(--nha-text-muted)}.discovery dd{margin:0;overflow-wrap:anywhere}\n", "h5{margin:0;font-size:15px;font-weight:750}.group{display:flex;flex-direction:column;gap:10px;min-width:0;border-top:1px solid var(--nha-border);padding-top:14px}.group>legend{float:left;width:100%;margin-bottom:2px;font-size:14px;font-weight:750}.choices{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,220px),1fr));gap:8px}.choice{display:flex;align-items:flex-start;gap:10px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:10px 12px;cursor:pointer;overflow-wrap:anywhere}.choice input{flex:none;width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.choice>span{display:grid;gap:2px;min-width:0}.choice strong{font-size:13px}.choice.selected{border-color:var(--nha-highlight);background:var(--nha-surface)}.choice:focus-within{outline:2px solid var(--nha-highlight);outline-offset:2px}.block{display:block;margin-top:2px}.advanced{border-top:1px solid var(--nha-border);padding-top:12px}.advanced>summary{width:fit-content;border-radius:6px;font-size:14px;font-weight:750;cursor:pointer}.advanced>summary:focus-visible{outline:2px solid var(--nha-highlight);outline-offset:2px}.advanced>.stack{margin-top:12px}.examples{display:flex;flex-wrap:wrap;gap:6px;margin:6px 0 0;padding:0;list-style:none}.examples button{border:1px solid var(--nha-border);border-radius:999px;padding:3px 10px;color:var(--nha-text);background:transparent;font:inherit;font-size:12px;cursor:pointer}.examples button:hover{background:var(--nha-surface-strong)}.examples button:focus-visible{outline:2px solid var(--nha-highlight);outline-offset:2px}.steps{display:flex;flex-wrap:wrap;gap:6px;margin:0;padding:0;list-style:none}.steps li{display:inline-flex;align-items:center;gap:6px;border-radius:999px;padding:4px 10px;color:var(--nha-text-muted);background:var(--nha-surface-strong);font-size:12px;font-weight:700}.steps li[aria-current=step]{color:var(--nha-accent-contrast);background:var(--nha-accent)}.steps li.done{color:var(--nha-success);background:var(--nha-success-soft)}.review{display:grid;grid-template-columns:minmax(0,max-content) minmax(0,1fr);gap:6px 16px;margin:0;font-size:13px}.review dt{color:var(--nha-text-muted)}.review dd{margin:0;overflow-wrap:anywhere}.tool-choice{display:flex;flex-direction:column;gap:2px;min-width:0;overflow-wrap:anywhere}@media(max-width:520px){.review{grid-template-columns:minmax(0,1fr)}.review dd{margin-bottom:6px}}.table-scroll{max-width:100%;overflow-x:auto}\n"] }]
|
|
2627
2838
|
}], propDecorators: { agentId: [{ type: i0.Input, args: [{ isSignal: true, alias: "agentId", required: true }] }], catalog: [{ type: i0.Input, args: [{ isSignal: true, alias: "catalog", required: false }] }], settings: [{ type: i0.Input, args: [{ isSignal: true, alias: "settings", required: false }] }], saved: [{ type: i0.Output, args: ["saved"] }], cancelled: [{ type: i0.Output, args: ["cancelled"] }] } });
|
|
2628
2839
|
|
|
2629
2840
|
let nextId$7 = 0;
|
|
@@ -2654,24 +2865,44 @@ class NhAssistantSkillFormComponent extends RunConfigForm {
|
|
|
2654
2865
|
this.maxDescription = runConfigLimits.description;
|
|
2655
2866
|
this.maxInstructions = runConfigLimits.instructions;
|
|
2656
2867
|
this.fields = [
|
|
2657
|
-
'skillId', 'description', 'instructions', 'tools', 'ruleHandler', 'profile', 'approvalPolicy',
|
|
2658
|
-
'maxTokensPerRun', 'dailyToolCalls', 'dailyTokens', 'apply', 'reason'
|
|
2868
|
+
'skillId', 'description', 'instructions', 'tools', 'ruleHandler', 'profile', 'dataClassification', 'approvalPolicy',
|
|
2869
|
+
'maxTokensPerRun', 'dailyToolCalls', 'dailyTokens', 'apply', 'startsOff', 'reason',
|
|
2870
|
+
'maxFileBytes', 'maxFilePages', 'maxFilesPerTurn', 'maxFileTokensPerTurn'
|
|
2659
2871
|
];
|
|
2660
2872
|
this.skillId = signal('', ...(ngDevMode ? [{ debugName: "skillId" }] : []));
|
|
2661
2873
|
this.description = signal('', ...(ngDevMode ? [{ debugName: "description" }] : []));
|
|
2662
2874
|
this.instructions = signal('', ...(ngDevMode ? [{ debugName: "instructions" }] : []));
|
|
2663
2875
|
this.tools = signal([], ...(ngDevMode ? [{ debugName: "tools" }] : []));
|
|
2664
2876
|
this.profile = signal('', ...(ngDevMode ? [{ debugName: "profile" }] : []));
|
|
2877
|
+
/** The skill's data classification; empty uses the agent's. */
|
|
2878
|
+
this.dataClassification = signal('', ...(ngDevMode ? [{ debugName: "dataClassification" }] : []));
|
|
2879
|
+
/** Whether a run in the skill reads files when the agent accepts them too; off for a new skill. */
|
|
2880
|
+
this.acceptsFiles = signal(false, ...(ngDevMode ? [{ debugName: "acceptsFiles" }] : []));
|
|
2881
|
+
/** The skill's lower file limits, next to accepting files; empty keeps the agent's. */
|
|
2882
|
+
this.fileLimits = new FileLimitsDraft();
|
|
2665
2883
|
this.ruleHandler = signal('', ...(ngDevMode ? [{ debugName: "ruleHandler" }] : []));
|
|
2666
2884
|
this.approvalPolicy = signal('', ...(ngDevMode ? [{ debugName: "approvalPolicy" }] : []));
|
|
2667
2885
|
this.maxTokens = signal('', ...(ngDevMode ? [{ debugName: "maxTokens" }] : []));
|
|
2668
2886
|
this.dailyToolCalls = signal('', ...(ngDevMode ? [{ debugName: "dailyToolCalls" }] : []));
|
|
2669
2887
|
this.dailyTokens = signal('', ...(ngDevMode ? [{ debugName: "dailyTokens" }] : []));
|
|
2670
2888
|
this.apply = signal('shadow', ...(ngDevMode ? [{ debugName: "apply" }] : []));
|
|
2889
|
+
/** A new skill takes work at once; without it, it starts off until someone switches it on. */
|
|
2890
|
+
this.switchOnNow = signal(true, ...(ngDevMode ? [{ debugName: "switchOnNow" }] : []));
|
|
2671
2891
|
this.reason = signal('', ...(ngDevMode ? [{ debugName: "reason" }] : []));
|
|
2672
2892
|
this.editing = computed(() => this.skill() !== null, ...(ngDevMode ? [{ debugName: "editing" }] : []));
|
|
2893
|
+
/** The warnings of the skill as it was saved, until this form saves it again. */
|
|
2894
|
+
this.warnings = computed(() => this.skill()?.warnings ?? [], ...(ngDevMode ? [{ debugName: "warnings" }] : []));
|
|
2895
|
+
this.warningKeys = warningKeys;
|
|
2673
2896
|
this.rules = computed(() => ruleOptions(this.catalog()), ...(ngDevMode ? [{ debugName: "rules" }] : []));
|
|
2674
2897
|
this.profiles = computed(() => profileOptions(this.catalog()), ...(ngDevMode ? [{ debugName: "profiles" }] : []));
|
|
2898
|
+
this.classifications = computed(() => {
|
|
2899
|
+
this.language();
|
|
2900
|
+
return dataClassifications.map(value => ({
|
|
2901
|
+
value,
|
|
2902
|
+
label: this.t(`nh-assistant.admin.run-config.skills.classification.${value}`),
|
|
2903
|
+
description: this.t(`nh-assistant.admin.run-config.skills.classification.${value}-hint`)
|
|
2904
|
+
}));
|
|
2905
|
+
}, ...(ngDevMode ? [{ debugName: "classifications" }] : []));
|
|
2675
2906
|
this.policies = computed(() => {
|
|
2676
2907
|
this.language();
|
|
2677
2908
|
return policyOptions(this.catalog(), summary => describePolicy(summary, (key, params) => this.t(key, params)));
|
|
@@ -2684,6 +2915,9 @@ class NhAssistantSkillFormComponent extends RunConfigForm {
|
|
|
2684
2915
|
this.instructions.set(skill?.instructions ?? '');
|
|
2685
2916
|
this.tools.set([...(skill?.tools ?? [])]);
|
|
2686
2917
|
this.profile.set(skill?.profile ?? '');
|
|
2918
|
+
this.dataClassification.set(skill?.dataClassification ?? '');
|
|
2919
|
+
this.acceptsFiles.set(skill?.acceptsFiles ?? false);
|
|
2920
|
+
this.fileLimits.load(skill);
|
|
2687
2921
|
this.ruleHandler.set(skill?.ruleHandler ?? '');
|
|
2688
2922
|
this.approvalPolicy.set(skill?.approvalPolicy ?? '');
|
|
2689
2923
|
this.maxTokens.set(numberText(skill?.maxTokensPerRun));
|
|
@@ -2719,11 +2953,17 @@ class NhAssistantSkillFormComponent extends RunConfigForm {
|
|
|
2719
2953
|
}
|
|
2720
2954
|
checkWhole(errors, 'maxTokensPerRun', this.maxTokens(), 1);
|
|
2721
2955
|
checkBudget(errors, this.dailyToolCalls(), this.dailyTokens());
|
|
2956
|
+
if (this.acceptsFiles()) {
|
|
2957
|
+
this.fileLimits.check(errors, this.catalog()?.fileLimits);
|
|
2958
|
+
}
|
|
2722
2959
|
const request = {
|
|
2723
2960
|
description: this.description().trim(),
|
|
2724
2961
|
instructions: blank$1(this.instructions()),
|
|
2725
2962
|
tools: this.tools(),
|
|
2726
2963
|
profile: blank$1(this.profile()),
|
|
2964
|
+
dataClassification: blank$1(this.dataClassification()),
|
|
2965
|
+
acceptsFiles: this.acceptsFiles(),
|
|
2966
|
+
...this.fileLimits.toRequest(this.acceptsFiles()),
|
|
2727
2967
|
ruleHandler: blank$1(this.ruleHandler()),
|
|
2728
2968
|
approvalPolicy: blank$1(this.approvalPolicy()),
|
|
2729
2969
|
maxTokensPerRun: toNumber(this.maxTokens()),
|
|
@@ -2731,7 +2971,8 @@ class NhAssistantSkillFormComponent extends RunConfigForm {
|
|
|
2731
2971
|
dailyTokens: toNumber(this.dailyTokens()),
|
|
2732
2972
|
expectedVersion: editing?.version ?? null,
|
|
2733
2973
|
reason: blank$1(this.reason()),
|
|
2734
|
-
...(editing ? { apply: this.apply() } : {})
|
|
2974
|
+
...(editing ? { apply: this.apply() } : {}),
|
|
2975
|
+
...(!editing && !this.switchOnNow() ? { startsOff: true } : {})
|
|
2735
2976
|
};
|
|
2736
2977
|
this.submit(errors, () => this.admin.saveDataSkill(this.agentId(), skillId, request), skill => this.saved.emit({
|
|
2737
2978
|
skill,
|
|
@@ -2743,11 +2984,11 @@ class NhAssistantSkillFormComponent extends RunConfigForm {
|
|
|
2743
2984
|
return withBudgetField(errors, this.dailyToolCalls());
|
|
2744
2985
|
}
|
|
2745
2986
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: NhAssistantSkillFormComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
2746
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.34", type: NhAssistantSkillFormComponent, isStandalone: true, selector: "nh-assistant-skill-form", inputs: { agentId: { classPropertyName: "agentId", publicName: "agentId", isSignal: true, isRequired: true, transformFunction: null }, catalog: { classPropertyName: "catalog", publicName: "catalog", isSignal: true, isRequired: false, transformFunction: null }, toolSelectors: { classPropertyName: "toolSelectors", publicName: "toolSelectors", isSignal: true, isRequired: false, transformFunction: null }, toolCatalog: { classPropertyName: "toolCatalog", publicName: "toolCatalog", isSignal: true, isRequired: false, transformFunction: null }, skill: { classPropertyName: "skill", publicName: "skill", isSignal: true, isRequired: false, transformFunction: null }, takenIds: { classPropertyName: "takenIds", publicName: "takenIds", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { saved: "saved", cancelled: "cancelled" }, usesInheritance: true, ngImport: i0, template: "<form class=\"stack card\" data-form=\"skill\" novalidate (submit)=\"$event.preventDefault(); save()\">\n <h5>\n @if (skill(); as current) {\n {{ 'nh-assistant.admin.run-config.skills.edit-title' | translate: { skill: current.skillId, version: current.version } }}\n } @else {\n {{ 'nh-assistant.admin.run-config.skills.new-title' | translate }}\n }\n </h5>\n\n <fieldset class=\"group\">\n <legend>{{ 'nh-assistant.admin.run-config.skills.what.title' | translate }}</legend>\n @if (!editing()) {\n <div class=\"field\">\n <label [for]=\"id + '-skill-id'\">{{ 'nh-assistant.admin.run-config.skills.id' | translate }}</label>\n <input type=\"text\" class=\"mono\" autocomplete=\"off\" maxlength=\"64\" [id]=\"id + '-skill-id'\" [value]=\"skillId()\"\n [attr.aria-invalid]=\"fieldError('skillId') ? true : null\"\n [attr.aria-describedby]=\"describedBy(id + '-skill-id-hint', 'skillId', id + '-skill-id-error')\"\n (input)=\"skillId.set(value($event)); touch('skillId')\" />\n <p class=\"hint\" [id]=\"id + '-skill-id-hint'\">{{ 'nh-assistant.admin.run-config.skills.id-hint' | translate }}</p>\n @if (errorKey('skillId'); as key) {\n <p class=\"field-error\" [id]=\"id + '-skill-id-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('skillId')?.params }}</p>\n }\n </div>\n }\n <div class=\"field\">\n <label [for]=\"id + '-skill-description'\">{{ 'nh-assistant.admin.run-config.skills.description-label' | translate }}</label>\n <input type=\"text\" [id]=\"id + '-skill-description'\" [value]=\"description()\"\n [attr.aria-invalid]=\"fieldError('description') ? true : null\"\n [attr.aria-describedby]=\"describedBy(id + '-skill-description-hint', 'description', id + '-skill-description-error')\"\n (input)=\"description.set(value($event)); touch('description')\" />\n <p class=\"hint\" [id]=\"id + '-skill-description-hint'\">\n <span>{{ 'nh-assistant.admin.run-config.skills.description-hint' | translate }}</span>\n <span class=\"counter\" [class.error-text]=\"description().length > maxDescription\">{{ 'nh-assistant.admin.characters' | translate: { count: description().length, max: maxDescription } }}</span>\n </p>\n @if (errorKey('description'); as key) {\n <p class=\"field-error\" [id]=\"id + '-skill-description-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('description')?.params }}</p>\n }\n </div>\n </fieldset>\n\n <fieldset class=\"group\">\n <legend>{{ 'nh-assistant.admin.run-config.skills.how.title' | translate }}</legend>\n <div class=\"field\">\n <label [for]=\"id + '-skill-rules'\">{{ 'nh-assistant.admin.run-config.way-of-working' | translate }}</label>\n <nh-assistant-picker\n [inputId]=\"id + '-skill-rules'\"\n [options]=\"rules()\"\n [value]=\"ruleHandler()\"\n [noneLabel]=\"'nh-assistant.admin.run-config.model' | translate\"\n [invalid]=\"fieldError('ruleHandler') !== null\"\n [describedBy]=\"describedBy(id + '-skill-rules-hint', 'ruleHandler', id + '-skill-rules-error')\"\n (valueChange)=\"ruleHandler.set($event); touch('ruleHandler')\" />\n <p class=\"hint\" [id]=\"id + '-skill-rules-hint'\">\n {{ (ruleHandler() ? 'nh-assistant.admin.run-config.skills.rules-hint' : 'nh-assistant.admin.run-config.skills.model-hint') | translate }}\n </p>\n @if (errorKey('ruleHandler'); as key) {\n <p class=\"field-error\" [id]=\"id + '-skill-rules-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('ruleHandler')?.params }}</p>\n }\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-skill-instructions'\">{{ 'nh-assistant.admin.run-config.skills.instructions' | translate }}</label>\n <textarea rows=\"5\" [id]=\"id + '-skill-instructions'\" [value]=\"instructions()\"\n [attr.aria-invalid]=\"fieldError('instructions') ? true : null\"\n [attr.aria-describedby]=\"describedBy(id + '-skill-instructions-hint', 'instructions', id + '-skill-instructions-error')\"\n (input)=\"instructions.set(value($event)); touch('instructions')\"></textarea>\n <p class=\"hint\" [id]=\"id + '-skill-instructions-hint'\">\n <span>{{ 'nh-assistant.admin.run-config.skills.instructions-hint' | translate }} {{ 'nh-assistant.admin.run-config.skills.background-hint' | translate }}</span>\n <span class=\"counter\" [class.error-text]=\"instructions().length > maxInstructions\">{{ 'nh-assistant.admin.characters' | translate: { count: instructions().length, max: maxInstructions } }}</span>\n </p>\n @if (errorKey('instructions'); as key) {\n <p class=\"field-error\" [id]=\"id + '-skill-instructions-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('instructions')?.params }}</p>\n }\n </div>\n <fieldset class=\"field\" [attr.aria-describedby]=\"id + '-tools-hint'\">\n <legend>{{ 'nh-assistant.admin.run-config.skills.tools' | translate }}</legend>\n <p class=\"hint\" [id]=\"id + '-tools-hint'\">{{ 'nh-assistant.admin.run-config.skills.tools-hint' | translate }}</p>\n @for (choice of toolChoices(); track choice.selector) {\n <label class=\"check\">\n <input type=\"checkbox\" [checked]=\"tools().includes(choice.selector)\" [attr.aria-invalid]=\"fieldError('tools') ? true : null\" (change)=\"toggleTool(choice.selector, checked($event))\" />\n <span class=\"tool-choice\">\n <span class=\"mono\">{{ choice.selector }}</span>\n @if (choice.description) {\n <span class=\"muted block\">{{ choice.description }}</span>\n } @else if (choice.matches !== null) {\n <span class=\"muted block\">{{ 'nh-assistant.admin.run-config.skills.tools-pattern' | translate: { count: choice.matches } }}</span>\n }\n </span>\n </label>\n } @empty {\n <p class=\"muted\">{{ 'nh-assistant.admin.agent-editor.no-selectors' | translate }}</p>\n }\n @if (errorKey('tools'); as key) {\n <p class=\"field-error\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('tools')?.params }}</p>\n }\n </fieldset>\n <div class=\"field\">\n <label [for]=\"id + '-skill-profile'\">{{ 'nh-assistant.admin.run-config.skills.profile' | translate }}</label>\n <nh-assistant-picker\n [inputId]=\"id + '-skill-profile'\"\n [options]=\"profiles()\"\n [value]=\"profile()\"\n [noneLabel]=\"'nh-assistant.admin.run-config.skills.agent-profile' | translate\"\n [invalid]=\"fieldError('profile') !== null\"\n [describedBy]=\"describedBy(id + '-skill-profile-hint', 'profile', id + '-skill-profile-error')\"\n (valueChange)=\"profile.set($event); touch('profile')\" />\n <p class=\"hint\" [id]=\"id + '-skill-profile-hint'\">{{ 'nh-assistant.admin.run-config.skills.profile-hint' | translate }}</p>\n @if (errorKey('profile'); as key) {\n <p class=\"field-error\" [id]=\"id + '-skill-profile-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('profile')?.params }}</p>\n }\n </div>\n </fieldset>\n\n <fieldset class=\"group\">\n <legend>{{ 'nh-assistant.admin.run-config.settings.approval.title' | translate }}</legend>\n <div class=\"field\">\n <label [for]=\"id + '-skill-policy'\">{{ 'nh-assistant.admin.run-config.approval-policy' | translate }}</label>\n <nh-assistant-picker\n [inputId]=\"id + '-skill-policy'\"\n [options]=\"policies()\"\n [value]=\"approvalPolicy()\"\n [noneLabel]=\"'nh-assistant.admin.run-config.skills.agent-policy' | translate\"\n [invalid]=\"fieldError('approvalPolicy') !== null\"\n [describedBy]=\"describedBy(null, 'approvalPolicy', id + '-skill-policy-error')\"\n (valueChange)=\"approvalPolicy.set($event); touch('approvalPolicy')\" />\n @if (errorKey('approvalPolicy'); as key) {\n <p class=\"field-error\" [id]=\"id + '-skill-policy-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('approvalPolicy')?.params }}</p>\n }\n </div>\n </fieldset>\n\n <fieldset class=\"group\">\n <legend>{{ 'nh-assistant.admin.run-config.limits.title' | translate }}</legend>\n <p class=\"muted\" [id]=\"id + '-limits-hint'\">{{ 'nh-assistant.admin.run-config.budget-hint' | translate }}</p>\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-skill-tokens'\">{{ 'nh-assistant.admin.run-config.max-tokens' | translate }}</label>\n <input type=\"number\" min=\"1\" inputmode=\"numeric\" [id]=\"id + '-skill-tokens'\" [value]=\"maxTokens()\"\n [attr.aria-invalid]=\"fieldError('maxTokensPerRun') ? true : null\"\n [attr.aria-describedby]=\"describedBy(id + '-limits-hint', 'maxTokensPerRun', id + '-skill-tokens-error')\"\n (input)=\"maxTokens.set(value($event)); touch('maxTokensPerRun')\" />\n @if (errorKey('maxTokensPerRun'); as key) {\n <p class=\"field-error\" [id]=\"id + '-skill-tokens-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('maxTokensPerRun')?.params }}</p>\n }\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-skill-calls'\">{{ 'nh-assistant.admin.run-config.daily-tool-calls' | translate }}</label>\n <input type=\"number\" min=\"1\" inputmode=\"numeric\" [id]=\"id + '-skill-calls'\" [value]=\"dailyToolCalls()\"\n [attr.aria-invalid]=\"fieldError('dailyToolCalls') ? true : null\"\n [attr.aria-describedby]=\"describedBy(id + '-limits-hint', 'dailyToolCalls', id + '-skill-calls-error')\"\n (input)=\"dailyToolCalls.set(value($event)); touch('dailyToolCalls'); touch('dailyTokens')\" />\n @if (errorKey('dailyToolCalls'); as key) {\n <p class=\"field-error\" [id]=\"id + '-skill-calls-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('dailyToolCalls')?.params }}</p>\n }\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-skill-daily-tokens'\">{{ 'nh-assistant.admin.run-config.daily-tokens' | translate }}</label>\n <input type=\"number\" min=\"1\" inputmode=\"numeric\" [id]=\"id + '-skill-daily-tokens'\" [value]=\"dailyTokens()\"\n [attr.aria-invalid]=\"fieldError('dailyTokens') ? true : null\"\n [attr.aria-describedby]=\"describedBy(id + '-limits-hint', 'dailyTokens', id + '-skill-daily-tokens-error')\"\n (input)=\"dailyTokens.set(value($event)); touch('dailyTokens'); touch('dailyToolCalls')\" />\n @if (errorKey('dailyTokens'); as key) {\n <p class=\"field-error\" [id]=\"id + '-skill-daily-tokens-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('dailyTokens')?.params }}</p>\n }\n </div>\n </div>\n </fieldset>\n\n @if (editing()) {\n <fieldset class=\"group\" data-apply>\n <legend>{{ 'nh-assistant.admin.run-config.skills.apply' | translate }}</legend>\n <div class=\"choices\">\n <label class=\"choice\" [class.selected]=\"apply() === 'shadow'\">\n <input type=\"radio\" [name]=\"id + '-apply'\" value=\"shadow\" [checked]=\"apply() === 'shadow'\" (change)=\"apply.set('shadow')\" />\n <span>\n <strong>{{ 'nh-assistant.admin.run-config.skills.apply-shadow' | translate }}</strong>\n <span class=\"muted\">{{ 'nh-assistant.admin.run-config.skills.apply-shadow-hint' | translate }}</span>\n </span>\n </label>\n <label class=\"choice\" [class.selected]=\"apply() === 'live'\">\n <input type=\"radio\" [name]=\"id + '-apply'\" value=\"live\" [checked]=\"apply() === 'live'\" (change)=\"apply.set('live')\" />\n <span>\n <strong>{{ 'nh-assistant.admin.run-config.skills.apply-live' | translate }}</strong>\n <span class=\"muted\">{{ 'nh-assistant.admin.run-config.skills.apply-live-hint' | translate }}</span>\n </span>\n </label>\n </div>\n </fieldset>\n } @else {\n <p class=\"hint\">{{ 'nh-assistant.admin.run-config.skills.starts-in-shadow' | translate }}</p>\n }\n\n <div class=\"field\">\n <label [for]=\"id + '-skill-reason'\">{{ 'nh-assistant.admin.agents.state.note-label' | translate }}</label>\n <input type=\"text\" autocomplete=\"off\" [id]=\"id + '-skill-reason'\" [maxLength]=\"maxReason\" [value]=\"reason()\" [attr.aria-describedby]=\"id + '-skill-reason-hint'\" (input)=\"reason.set(value($event))\" />\n <p class=\"hint\" [id]=\"id + '-skill-reason-hint'\">{{ 'nh-assistant.admin.run-config.note-hint' | translate }}</p>\n </div>\n\n @if (hasErrors()) {\n <div class=\"form-error\" role=\"alert\" data-form-error>\n <nh-assistant-icon name=\"warning\" />\n <div>\n @if (serverError(); as failure) {\n @if (failure.code === 'assistant-validation') {\n {{ 'nh-assistant.admin.run-config.check-fields' | translate }}\n } @else {\n {{ errorKeys() | nhAssistantTranslate: failure.code }}\n }\n } @else {\n {{ 'nh-assistant.admin.run-config.check-fields' | translate }}\n }\n @if (otherFieldErrors().length > 0) {\n <ul>\n @for (field of otherFieldErrors(); track field) {\n <li><span class=\"mono\">{{ field }}</span></li>\n }\n </ul>\n }\n </div>\n </div>\n }\n <div class=\"actions\">\n <button class=\"secondary\" type=\"button\" (click)=\"cancelled.emit()\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n <button class=\"primary\" type=\"submit\" [disabled]=\"busy()\">{{ (busy() ? 'nh-assistant.admin.saving' : 'nh-assistant.admin.save') | translate }}</button>\n </div>\n</form>\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;min-width:0;color:var(--nha-text);font-family:var(--nha-font);font-size:14px}:host fieldset{min-width:0;margin:0;border:0;padding:0}:host legend,:host .label,:host .field>label{display:block;margin-bottom:6px;padding:0;color:var(--nha-text);font-size:12px;font-weight:700}:host input[type=text],:host input[type=url],:host input[type=password],:host input[type=search],:host input[type=email],:host input[type=number],:host input[type=date],:host input[type=time],:host input[type=datetime-local],:host select,:host textarea{box-sizing:border-box;width:100%;min-width:0;min-height:38px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:8px 10px;color:var(--nha-text);background:var(--nha-background);font:inherit;font-size:13px}:host input[type=text]:focus-visible,:host input[type=url]:focus-visible,:host input[type=password]:focus-visible,:host input[type=search]:focus-visible,:host input[type=email]:focus-visible,:host input[type=number]:focus-visible,:host input[type=date]:focus-visible,:host input[type=time]:focus-visible,:host input[type=datetime-local]:focus-visible,:host select:focus-visible,:host textarea:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host input[type=text]:disabled,:host input[type=url]:disabled,:host input[type=password]:disabled,:host input[type=search]:disabled,:host input[type=email]:disabled,:host input[type=number]:disabled,:host input[type=date]:disabled,:host input[type=time]:disabled,:host input[type=datetime-local]:disabled,:host select:disabled,:host textarea:disabled{cursor:not-allowed;opacity:.6}:host input[type=text][aria-invalid=true],:host input[type=url][aria-invalid=true],:host input[type=password][aria-invalid=true],:host input[type=search][aria-invalid=true],:host input[type=email][aria-invalid=true],:host input[type=number][aria-invalid=true],:host input[type=date][aria-invalid=true],:host input[type=time][aria-invalid=true],:host input[type=datetime-local][aria-invalid=true],:host select[aria-invalid=true],:host textarea[aria-invalid=true]{border-color:var(--nha-danger)}:host textarea{line-height:1.45;resize:vertical}:host input[type=number]{font-variant-numeric:tabular-nums}:host .mono{font-family:var(--nha-mono);font-size:12px}:host .field{display:flex;flex-direction:column;min-width:0}:host .hint{display:flex;justify-content:space-between;gap:8px;margin:4px 2px 0;color:var(--nha-text-muted);font-size:11px}:host .counter{flex:none;font-variant-numeric:tabular-nums}:host .error-text{color:var(--nha-danger)}:host .field-error{display:flex;align-items:flex-start;gap:6px;margin:4px 2px 0;color:var(--nha-danger);font-size:12px;font-weight:600;overflow-wrap:anywhere}:host .field-error nh-assistant-icon{flex:none;margin-top:1px}:host .form-error{box-sizing:border-box;display:flex;align-items:flex-start;gap:8px;margin:0;border:1px solid var(--nha-danger);border-radius:var(--nha-radius-control);padding:8px 12px;color:var(--nha-text);background:var(--nha-danger-soft);font-size:13px;overflow-wrap:anywhere}:host .form-error ul{margin:4px 0 0;padding-left:18px}:host .message{box-sizing:border-box;display:flex;align-items:center;gap:8px;margin:0;border:1px solid transparent;border-radius:var(--nha-radius-control);padding:8px 12px;font-size:13px;overflow-wrap:anywhere}:host .message.error{border-color:var(--nha-danger);color:var(--nha-text);background:var(--nha-danger-soft)}:host .message.warning{border-color:var(--nha-warning);color:var(--nha-text);background:var(--nha-warning-soft)}:host .message.success{border-color:var(--nha-success);color:var(--nha-text);background:var(--nha-success-soft)}:host .message.info{border-color:var(--nha-border);color:var(--nha-text);background:var(--nha-surface)}:host .message nh-assistant-icon{flex:none}:host button.link{min-height:0;border:0;padding:0;color:var(--nha-accent, currentColor);background:none;font:inherit;text-decoration:underline;cursor:pointer}:host button.link:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host button.link:disabled{cursor:not-allowed;opacity:.55}:host .primary,:host .secondary,:host .danger{display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;gap:6px;min-height:36px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px 14px;font:inherit;font-size:13px;font-weight:700;white-space:nowrap;cursor:pointer}:host .primary:focus-visible,:host .secondary:focus-visible,:host .danger:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .primary:disabled,:host .secondary:disabled,:host .danger:disabled{cursor:not-allowed;opacity:.55}:host .primary{border-color:var(--nha-accent);color:var(--nha-accent-contrast);background:var(--nha-accent)}:host .primary:hover:not(:disabled){filter:brightness(1.12)}:host .secondary{color:var(--nha-text);background:transparent}:host .secondary:hover:not(:disabled){background:var(--nha-surface-strong)}:host .danger{border-color:var(--nha-danger);color:var(--nha-danger);background:transparent}:host .danger:hover:not(:disabled){background:var(--nha-danger-soft)}:host .icon-button{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:1px solid transparent;border-radius:var(--nha-radius-control);color:var(--nha-text-muted);background:transparent;cursor:pointer}:host .icon-button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .icon-button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}:host .icon-button:disabled{cursor:not-allowed;opacity:.5}:host .actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px}:host .badge{display:inline-flex;align-items:center;gap:4px;border-radius:999px;padding:2px 8px;color:var(--nha-text-muted);background:var(--nha-surface-strong);font-size:11px;font-weight:700;white-space:nowrap}:host .badge.success{color:var(--nha-success);background:var(--nha-success-soft)}:host .badge.warning{color:var(--nha-warning);background:var(--nha-warning-soft)}:host .badge.danger{color:var(--nha-danger);background:var(--nha-danger-soft)}:host .visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}.section{display:flex;flex-direction:column;gap:16px;min-width:0}.section-header{display:flex;flex-wrap:wrap;align-items:flex-start;justify-content:space-between;gap:12px}.section-header>div{min-width:0;max-width:72ch}h3{margin:0 0 4px;font-size:17px;font-weight:750}h4{margin:0 0 8px;font-size:14px;font-weight:700}.muted{margin:0;color:var(--nha-text-muted);font-size:13px}.state{display:flex;flex-direction:column;align-items:flex-start;gap:10px;margin:0;border:1px dashed var(--nha-border);border-radius:var(--nha-radius);padding:20px;color:var(--nha-text-muted);font-size:13px}.state p{margin:0}.stack{display:flex;flex-direction:column;gap:14px;min-width:0}.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,240px),1fr));gap:14px}.meta{display:flex;flex-wrap:wrap;align-items:center;gap:8px 12px;color:var(--nha-text-muted);font-size:12px;overflow-wrap:anywhere}textarea.large{min-height:260px}.catalog-error{flex-wrap:wrap}.grow{flex:1;min-width:0}.subsection{display:flex;flex-direction:column;gap:8px;border-top:1px solid var(--nha-border);padding-top:14px}.history{display:grid;gap:6px;margin:0;padding:0;list-style:none}.history li{display:flex;flex-wrap:wrap;align-items:center;gap:6px 12px;font-size:12px}.cards{display:grid;gap:10px;margin:0;padding:0;list-style:none}.card{display:flex;flex-direction:column;gap:8px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius);padding:14px;background:var(--nha-background)}.card.inactive{opacity:.8}.card-header{display:flex;flex-wrap:wrap;align-items:center;gap:8px;min-width:0}.card-header strong{font-size:14px;overflow-wrap:anywhere}.card-actions{display:flex;flex-wrap:wrap;gap:8px}.check{display:flex;align-items:flex-start;gap:8px;font-size:13px;cursor:pointer}.check input{width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.check.described>span{display:grid;gap:2px;min-width:0;overflow-wrap:anywhere}.checks{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));gap:6px 12px;min-width:0;overflow-wrap:anywhere}.chips{display:flex;flex-wrap:wrap;gap:6px;margin:0;padding:0;list-style:none}.chip{display:inline-flex;align-items:center;gap:2px;border:1px solid var(--nha-border);border-radius:999px;padding:2px 2px 2px 10px;background:var(--nha-surface);font-family:var(--nha-mono);font-size:12px}.chip 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;width:26px;height:26px;border-radius:999px}.chip button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.chip button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}.chip button:disabled{cursor:not-allowed;opacity:.5}.inline{display:flex;flex-wrap:wrap;gap:8px}.inline>input{flex:1 1 180px}.catalog{display:grid;gap:4px;max-height:260px;margin:8px 0 0;overflow-y:auto;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px;list-style:none}.catalog li label{display:flex;flex-wrap:wrap;align-items:center;gap:6px 8px;border-radius:8px;padding:6px;cursor:pointer}.catalog li label:hover{background:var(--nha-surface)}.catalog .tool-id{font-family:var(--nha-mono);font-size:12px;overflow-wrap:anywhere}.skills{display:grid;gap:8px;margin:0;padding:0;list-style:none}.skill{display:grid;gap:6px;border-left:3px solid var(--nha-border);padding:4px 0 4px 12px}.uses{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));gap:8px}.use{display:flex;align-items:flex-start;gap:10px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:10px 12px;cursor:pointer;overflow-wrap:anywhere}.use input{flex:none;width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.use>span{display:grid;gap:2px;min-width:0}.use strong{font-size:13px}.use.selected{border-color:var(--nha-highlight);background:var(--nha-surface)}.use:focus-within{outline:2px solid var(--nha-highlight);outline-offset:2px}.discovery{display:grid;gap:8px;padding:10px 12px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control)}.discovery dl{display:grid;grid-template-columns:max-content minmax(0,1fr);gap:4px 12px;margin:0}.discovery dt{color:var(--nha-text-muted)}.discovery dd{margin:0;overflow-wrap:anywhere}\n", "h5{margin:0;font-size:15px;font-weight:750}.group{display:flex;flex-direction:column;gap:10px;min-width:0;border-top:1px solid var(--nha-border);padding-top:14px}.group>legend{float:left;width:100%;margin-bottom:2px;font-size:14px;font-weight:750}.choices{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,220px),1fr));gap:8px}.choice{display:flex;align-items:flex-start;gap:10px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:10px 12px;cursor:pointer;overflow-wrap:anywhere}.choice input{flex:none;width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.choice>span{display:grid;gap:2px;min-width:0}.choice strong{font-size:13px}.choice.selected{border-color:var(--nha-highlight);background:var(--nha-surface)}.choice:focus-within{outline:2px solid var(--nha-highlight);outline-offset:2px}.block{display:block;margin-top:2px}.advanced{border-top:1px solid var(--nha-border);padding-top:12px}.advanced>summary{width:fit-content;border-radius:6px;font-size:14px;font-weight:750;cursor:pointer}.advanced>summary:focus-visible{outline:2px solid var(--nha-highlight);outline-offset:2px}.advanced>.stack{margin-top:12px}.examples{display:flex;flex-wrap:wrap;gap:6px;margin:6px 0 0;padding:0;list-style:none}.examples button{border:1px solid var(--nha-border);border-radius:999px;padding:3px 10px;color:var(--nha-text);background:transparent;font:inherit;font-size:12px;cursor:pointer}.examples button:hover{background:var(--nha-surface-strong)}.examples button:focus-visible{outline:2px solid var(--nha-highlight);outline-offset:2px}.steps{display:flex;flex-wrap:wrap;gap:6px;margin:0;padding:0;list-style:none}.steps li{display:inline-flex;align-items:center;gap:6px;border-radius:999px;padding:4px 10px;color:var(--nha-text-muted);background:var(--nha-surface-strong);font-size:12px;font-weight:700}.steps li[aria-current=step]{color:var(--nha-accent-contrast);background:var(--nha-accent)}.steps li.done{color:var(--nha-success);background:var(--nha-success-soft)}.review{display:grid;grid-template-columns:minmax(0,max-content) minmax(0,1fr);gap:6px 16px;margin:0;font-size:13px}.review dt{color:var(--nha-text-muted)}.review dd{margin:0;overflow-wrap:anywhere}.tool-choice{display:flex;flex-direction:column;gap:2px;min-width:0;overflow-wrap:anywhere}@media(max-width:520px){.review{grid-template-columns:minmax(0,1fr)}.review dd{margin-bottom:6px}}.table-scroll{max-width:100%;overflow-x:auto}\n"], dependencies: [{ kind: "component", type: _NhAssistantIconComponent, selector: "nh-assistant-icon", inputs: ["name"] }, { kind: "component", type: _NhAssistantPickerComponent, selector: "nh-assistant-picker", inputs: ["inputId", "options", "value", "selectedLabel", "noneLabel", "placeholder", "search", "minQueryLength", "invalid", "describedBy", "disabled"], outputs: ["valueChange", "optionChange"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }, { kind: "pipe", type: _NhAssistantTranslatePipe, name: "nhAssistantTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
2987
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.34", type: NhAssistantSkillFormComponent, isStandalone: true, selector: "nh-assistant-skill-form", inputs: { agentId: { classPropertyName: "agentId", publicName: "agentId", isSignal: true, isRequired: true, transformFunction: null }, catalog: { classPropertyName: "catalog", publicName: "catalog", isSignal: true, isRequired: false, transformFunction: null }, toolSelectors: { classPropertyName: "toolSelectors", publicName: "toolSelectors", isSignal: true, isRequired: false, transformFunction: null }, toolCatalog: { classPropertyName: "toolCatalog", publicName: "toolCatalog", isSignal: true, isRequired: false, transformFunction: null }, skill: { classPropertyName: "skill", publicName: "skill", isSignal: true, isRequired: false, transformFunction: null }, takenIds: { classPropertyName: "takenIds", publicName: "takenIds", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { saved: "saved", cancelled: "cancelled" }, usesInheritance: true, ngImport: i0, template: "<form class=\"stack card\" data-form=\"skill\" novalidate (submit)=\"$event.preventDefault(); save()\">\n <h5>\n @if (skill(); as current) {\n {{ 'nh-assistant.admin.run-config.skills.edit-title' | translate: { skill: current.skillId, version: current.version } }}\n } @else {\n {{ 'nh-assistant.admin.run-config.skills.new-title' | translate }}\n }\n </h5>\n\n <fieldset class=\"group\">\n <legend>{{ 'nh-assistant.admin.run-config.skills.what.title' | translate }}</legend>\n @if (!editing()) {\n <div class=\"field\">\n <label [for]=\"id + '-skill-id'\">{{ 'nh-assistant.admin.run-config.skills.id' | translate }}</label>\n <input type=\"text\" class=\"mono\" autocomplete=\"off\" maxlength=\"64\" [id]=\"id + '-skill-id'\" [value]=\"skillId()\"\n [attr.aria-invalid]=\"fieldError('skillId') ? true : null\"\n [attr.aria-describedby]=\"describedBy(id + '-skill-id-hint', 'skillId', id + '-skill-id-error')\"\n (input)=\"skillId.set(value($event)); touch('skillId')\" />\n <p class=\"hint\" [id]=\"id + '-skill-id-hint'\">{{ 'nh-assistant.admin.run-config.skills.id-hint' | translate }}</p>\n @if (errorKey('skillId'); as key) {\n <p class=\"field-error\" [id]=\"id + '-skill-id-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('skillId')?.params }}</p>\n }\n </div>\n }\n <div class=\"field\">\n <label [for]=\"id + '-skill-description'\">{{ 'nh-assistant.admin.run-config.skills.description-label' | translate }}</label>\n <input type=\"text\" [id]=\"id + '-skill-description'\" [value]=\"description()\"\n [attr.aria-invalid]=\"fieldError('description') ? true : null\"\n [attr.aria-describedby]=\"describedBy(id + '-skill-description-hint', 'description', id + '-skill-description-error')\"\n (input)=\"description.set(value($event)); touch('description')\" />\n <p class=\"hint\" [id]=\"id + '-skill-description-hint'\">\n <span>{{ 'nh-assistant.admin.run-config.skills.description-hint' | translate }}</span>\n <span class=\"counter\" [class.error-text]=\"description().length > maxDescription\">{{ 'nh-assistant.admin.characters' | translate: { count: description().length, max: maxDescription } }}</span>\n </p>\n @if (errorKey('description'); as key) {\n <p class=\"field-error\" [id]=\"id + '-skill-description-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('description')?.params }}</p>\n }\n </div>\n </fieldset>\n\n <fieldset class=\"group\">\n <legend>{{ 'nh-assistant.admin.run-config.skills.how.title' | translate }}</legend>\n <div class=\"field\">\n <label [for]=\"id + '-skill-rules'\">{{ 'nh-assistant.admin.run-config.way-of-working' | translate }}</label>\n <nh-assistant-picker\n [inputId]=\"id + '-skill-rules'\"\n [options]=\"rules()\"\n [value]=\"ruleHandler()\"\n [noneLabel]=\"'nh-assistant.admin.run-config.model' | translate\"\n [invalid]=\"fieldError('ruleHandler') !== null\"\n [describedBy]=\"describedBy(id + '-skill-rules-hint', 'ruleHandler', id + '-skill-rules-error')\"\n (valueChange)=\"ruleHandler.set($event); touch('ruleHandler')\" />\n <p class=\"hint\" [id]=\"id + '-skill-rules-hint'\">\n {{ (ruleHandler() ? 'nh-assistant.admin.run-config.skills.rules-hint' : 'nh-assistant.admin.run-config.skills.model-hint') | translate }}\n </p>\n @if (errorKey('ruleHandler'); as key) {\n <p class=\"field-error\" [id]=\"id + '-skill-rules-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('ruleHandler')?.params }}</p>\n }\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-skill-instructions'\">{{ 'nh-assistant.admin.run-config.skills.instructions' | translate }}</label>\n <textarea rows=\"5\" [id]=\"id + '-skill-instructions'\" [value]=\"instructions()\"\n [attr.aria-invalid]=\"fieldError('instructions') ? true : null\"\n [attr.aria-describedby]=\"describedBy(id + '-skill-instructions-hint', 'instructions', id + '-skill-instructions-error')\"\n (input)=\"instructions.set(value($event)); touch('instructions')\"></textarea>\n <p class=\"hint\" [id]=\"id + '-skill-instructions-hint'\">\n <span>{{ 'nh-assistant.admin.run-config.skills.instructions-hint' | translate }} {{ 'nh-assistant.admin.run-config.skills.background-hint' | translate }}</span>\n <span class=\"counter\" [class.error-text]=\"instructions().length > maxInstructions\">{{ 'nh-assistant.admin.characters' | translate: { count: instructions().length, max: maxInstructions } }}</span>\n </p>\n @if (errorKey('instructions'); as key) {\n <p class=\"field-error\" [id]=\"id + '-skill-instructions-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('instructions')?.params }}</p>\n }\n </div>\n <fieldset class=\"field\" [attr.aria-describedby]=\"id + '-tools-hint'\">\n <legend>{{ 'nh-assistant.admin.run-config.skills.tools' | translate }}</legend>\n <p class=\"hint\" [id]=\"id + '-tools-hint'\">{{ 'nh-assistant.admin.run-config.skills.tools-hint' | translate }}</p>\n @for (choice of toolChoices(); track choice.selector) {\n <label class=\"check\">\n <input type=\"checkbox\" [checked]=\"tools().includes(choice.selector)\" [attr.aria-invalid]=\"fieldError('tools') ? true : null\" (change)=\"toggleTool(choice.selector, checked($event))\" />\n <span class=\"tool-choice\">\n <span class=\"mono\">{{ choice.selector }}</span>\n @if (choice.description) {\n <span class=\"muted block\">{{ choice.description }}</span>\n } @else if (choice.matches !== null) {\n <span class=\"muted block\">{{ 'nh-assistant.admin.run-config.skills.tools-pattern' | translate: { count: choice.matches } }}</span>\n }\n </span>\n </label>\n } @empty {\n <p class=\"muted\">{{ 'nh-assistant.admin.agent-editor.no-selectors' | translate }}</p>\n }\n @if (errorKey('tools'); as key) {\n <p class=\"field-error\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('tools')?.params }}</p>\n }\n </fieldset>\n <div class=\"field\">\n <label [for]=\"id + '-skill-profile'\">{{ 'nh-assistant.admin.run-config.skills.profile' | translate }}</label>\n <nh-assistant-picker\n [inputId]=\"id + '-skill-profile'\"\n [options]=\"profiles()\"\n [value]=\"profile()\"\n [noneLabel]=\"'nh-assistant.admin.run-config.skills.agent-profile' | translate\"\n [invalid]=\"fieldError('profile') !== null\"\n [describedBy]=\"describedBy(id + '-skill-profile-hint', 'profile', id + '-skill-profile-error')\"\n (valueChange)=\"profile.set($event); touch('profile')\" />\n <p class=\"hint\" [id]=\"id + '-skill-profile-hint'\">{{ 'nh-assistant.admin.run-config.skills.profile-hint' | translate }}</p>\n @if (errorKey('profile'); as key) {\n <p class=\"field-error\" [id]=\"id + '-skill-profile-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('profile')?.params }}</p>\n }\n </div>\n <div class=\"field\" data-classification>\n <label [for]=\"id + '-skill-classification'\">{{ 'nh-assistant.admin.run-config.skills.classification.label' | translate }}</label>\n <nh-assistant-picker\n [inputId]=\"id + '-skill-classification'\"\n [options]=\"classifications()\"\n [value]=\"dataClassification()\"\n [noneLabel]=\"'nh-assistant.admin.run-config.skills.classification.agent' | translate\"\n [invalid]=\"fieldError('dataClassification') !== null\"\n [describedBy]=\"describedBy(id + '-skill-classification-hint', 'dataClassification', id + '-skill-classification-error')\"\n (valueChange)=\"dataClassification.set($event); touch('dataClassification')\" />\n <p class=\"hint\" [id]=\"id + '-skill-classification-hint'\">{{ 'nh-assistant.admin.run-config.skills.classification.hint' | translate }}</p>\n @for (warning of warnings(); track warning) {\n <p class=\"message warning\" [attr.data-skill-form-warning]=\"warning\">\n <nh-assistant-icon name=\"warning\" />{{ warningKeys('skill', warning) | nhAssistantTranslate: warning }}\n </p>\n }\n @if (errorKey('dataClassification'); as key) {\n <p class=\"field-error\" [id]=\"id + '-skill-classification-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('dataClassification')?.params }}</p>\n }\n </div>\n <label class=\"check\" data-accepts-files>\n <input type=\"checkbox\" [checked]=\"acceptsFiles()\" (change)=\"acceptsFiles.set(checked($event))\" />\n <span>\n {{ 'nh-assistant.admin.run-config.files.label' | translate }}\n <span class=\"muted block\">{{ 'nh-assistant.admin.run-config.files.skill-hint' | translate }}</span>\n </span>\n </label>\n @if (acceptsFiles()) {\n <nh-assistant-file-limits-fields\n [draft]=\"fileLimits\"\n [application]=\"catalog()?.fileLimits\"\n [errors]=\"fieldErrors()\"\n [idPrefix]=\"id\"\n hintKey=\"nh-assistant.admin.run-config.files.skill-limits-hint\"\n (touched)=\"touch($event)\" />\n }\n </fieldset>\n\n <fieldset class=\"group\">\n <legend>{{ 'nh-assistant.admin.run-config.settings.approval.title' | translate }}</legend>\n <div class=\"field\">\n <label [for]=\"id + '-skill-policy'\">{{ 'nh-assistant.admin.run-config.approval-policy' | translate }}</label>\n <nh-assistant-picker\n [inputId]=\"id + '-skill-policy'\"\n [options]=\"policies()\"\n [value]=\"approvalPolicy()\"\n [noneLabel]=\"'nh-assistant.admin.run-config.skills.agent-policy' | translate\"\n [invalid]=\"fieldError('approvalPolicy') !== null\"\n [describedBy]=\"describedBy(null, 'approvalPolicy', id + '-skill-policy-error')\"\n (valueChange)=\"approvalPolicy.set($event); touch('approvalPolicy')\" />\n @if (errorKey('approvalPolicy'); as key) {\n <p class=\"field-error\" [id]=\"id + '-skill-policy-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('approvalPolicy')?.params }}</p>\n }\n </div>\n </fieldset>\n\n <fieldset class=\"group\">\n <legend>{{ 'nh-assistant.admin.run-config.limits.title' | translate }}</legend>\n <p class=\"muted\" [id]=\"id + '-limits-hint'\">{{ 'nh-assistant.admin.run-config.budget-hint' | translate }}</p>\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-skill-tokens'\">{{ 'nh-assistant.admin.run-config.max-tokens' | translate }}</label>\n <input type=\"number\" min=\"1\" inputmode=\"numeric\" [id]=\"id + '-skill-tokens'\" [value]=\"maxTokens()\"\n [attr.aria-invalid]=\"fieldError('maxTokensPerRun') ? true : null\"\n [attr.aria-describedby]=\"describedBy(id + '-limits-hint', 'maxTokensPerRun', id + '-skill-tokens-error')\"\n (input)=\"maxTokens.set(value($event)); touch('maxTokensPerRun')\" />\n @if (errorKey('maxTokensPerRun'); as key) {\n <p class=\"field-error\" [id]=\"id + '-skill-tokens-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('maxTokensPerRun')?.params }}</p>\n }\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-skill-calls'\">{{ 'nh-assistant.admin.run-config.daily-tool-calls' | translate }}</label>\n <input type=\"number\" min=\"1\" inputmode=\"numeric\" [id]=\"id + '-skill-calls'\" [value]=\"dailyToolCalls()\"\n [attr.aria-invalid]=\"fieldError('dailyToolCalls') ? true : null\"\n [attr.aria-describedby]=\"describedBy(id + '-limits-hint', 'dailyToolCalls', id + '-skill-calls-error')\"\n (input)=\"dailyToolCalls.set(value($event)); touch('dailyToolCalls'); touch('dailyTokens')\" />\n @if (errorKey('dailyToolCalls'); as key) {\n <p class=\"field-error\" [id]=\"id + '-skill-calls-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('dailyToolCalls')?.params }}</p>\n }\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-skill-daily-tokens'\">{{ 'nh-assistant.admin.run-config.daily-tokens' | translate }}</label>\n <input type=\"number\" min=\"1\" inputmode=\"numeric\" [id]=\"id + '-skill-daily-tokens'\" [value]=\"dailyTokens()\"\n [attr.aria-invalid]=\"fieldError('dailyTokens') ? true : null\"\n [attr.aria-describedby]=\"describedBy(id + '-limits-hint', 'dailyTokens', id + '-skill-daily-tokens-error')\"\n (input)=\"dailyTokens.set(value($event)); touch('dailyTokens'); touch('dailyToolCalls')\" />\n @if (errorKey('dailyTokens'); as key) {\n <p class=\"field-error\" [id]=\"id + '-skill-daily-tokens-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('dailyTokens')?.params }}</p>\n }\n </div>\n </div>\n </fieldset>\n\n @if (editing()) {\n <fieldset class=\"group\" data-apply>\n <legend>{{ 'nh-assistant.admin.run-config.skills.apply' | translate }}</legend>\n <div class=\"choices\">\n <label class=\"choice\" [class.selected]=\"apply() === 'shadow'\">\n <input type=\"radio\" [name]=\"id + '-apply'\" value=\"shadow\" [checked]=\"apply() === 'shadow'\" (change)=\"apply.set('shadow')\" />\n <span>\n <strong>{{ 'nh-assistant.admin.run-config.skills.apply-shadow' | translate }}</strong>\n <span class=\"muted\">{{ 'nh-assistant.admin.run-config.skills.apply-shadow-hint' | translate }}</span>\n </span>\n </label>\n <label class=\"choice\" [class.selected]=\"apply() === 'live'\">\n <input type=\"radio\" [name]=\"id + '-apply'\" value=\"live\" [checked]=\"apply() === 'live'\" (change)=\"apply.set('live')\" />\n <span>\n <strong>{{ 'nh-assistant.admin.run-config.skills.apply-live' | translate }}</strong>\n <span class=\"muted\">{{ 'nh-assistant.admin.run-config.skills.apply-live-hint' | translate }}</span>\n </span>\n </label>\n </div>\n </fieldset>\n } @else {\n <p class=\"hint\">{{ 'nh-assistant.admin.run-config.skills.starts-in-shadow' | translate }}</p>\n <label class=\"check\" data-switch-on-now>\n <input type=\"checkbox\" [checked]=\"switchOnNow()\" (change)=\"switchOnNow.set(checked($event))\" />\n <span>\n {{ 'nh-assistant.admin.run-config.skills.switch-on-now' | translate }}\n <span class=\"muted block\">{{ 'nh-assistant.admin.run-config.skills.switch-on-now-hint' | translate }}</span>\n </span>\n </label>\n }\n\n <div class=\"field\">\n <label [for]=\"id + '-skill-reason'\">{{ 'nh-assistant.admin.agents.state.note-label' | translate }}</label>\n <input type=\"text\" autocomplete=\"off\" [id]=\"id + '-skill-reason'\" [maxLength]=\"maxReason\" [value]=\"reason()\" [attr.aria-describedby]=\"id + '-skill-reason-hint'\" (input)=\"reason.set(value($event))\" />\n <p class=\"hint\" [id]=\"id + '-skill-reason-hint'\">{{ 'nh-assistant.admin.run-config.note-hint' | translate }}</p>\n </div>\n\n @if (hasErrors()) {\n <div class=\"form-error\" role=\"alert\" data-form-error>\n <nh-assistant-icon name=\"warning\" />\n <div>\n @if (serverError(); as failure) {\n @if (failure.code === 'assistant-validation') {\n {{ 'nh-assistant.admin.run-config.check-fields' | translate }}\n } @else {\n {{ errorKeys() | nhAssistantTranslate: failure.code }}\n }\n } @else {\n {{ 'nh-assistant.admin.run-config.check-fields' | translate }}\n }\n @if (otherFieldErrors().length > 0) {\n <ul>\n @for (field of otherFieldErrors(); track field) {\n <li><span class=\"mono\">{{ field }}</span></li>\n }\n </ul>\n }\n </div>\n </div>\n }\n <div class=\"actions\">\n <button class=\"secondary\" type=\"button\" (click)=\"cancelled.emit()\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n <button class=\"primary\" type=\"submit\" [disabled]=\"busy()\">{{ (busy() ? 'nh-assistant.admin.saving' : 'nh-assistant.admin.save') | translate }}</button>\n </div>\n</form>\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;min-width:0;color:var(--nha-text);font-family:var(--nha-font);font-size:14px}:host fieldset{min-width:0;margin:0;border:0;padding:0}:host legend,:host .label,:host .field>label{display:block;margin-bottom:6px;padding:0;color:var(--nha-text);font-size:12px;font-weight:700}:host input[type=text],:host input[type=url],:host input[type=password],:host input[type=search],:host input[type=email],:host input[type=number],:host input[type=date],:host input[type=time],:host input[type=datetime-local],:host select,:host textarea{box-sizing:border-box;width:100%;min-width:0;min-height:38px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:8px 10px;color:var(--nha-text);background:var(--nha-background);font:inherit;font-size:13px}:host input[type=text]:focus-visible,:host input[type=url]:focus-visible,:host input[type=password]:focus-visible,:host input[type=search]:focus-visible,:host input[type=email]:focus-visible,:host input[type=number]:focus-visible,:host input[type=date]:focus-visible,:host input[type=time]:focus-visible,:host input[type=datetime-local]:focus-visible,:host select:focus-visible,:host textarea:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host input[type=text]:disabled,:host input[type=url]:disabled,:host input[type=password]:disabled,:host input[type=search]:disabled,:host input[type=email]:disabled,:host input[type=number]:disabled,:host input[type=date]:disabled,:host input[type=time]:disabled,:host input[type=datetime-local]:disabled,:host select:disabled,:host textarea:disabled{cursor:not-allowed;opacity:.6}:host input[type=text][aria-invalid=true],:host input[type=url][aria-invalid=true],:host input[type=password][aria-invalid=true],:host input[type=search][aria-invalid=true],:host input[type=email][aria-invalid=true],:host input[type=number][aria-invalid=true],:host input[type=date][aria-invalid=true],:host input[type=time][aria-invalid=true],:host input[type=datetime-local][aria-invalid=true],:host select[aria-invalid=true],:host textarea[aria-invalid=true]{border-color:var(--nha-danger)}:host textarea{line-height:1.45;resize:vertical}:host input[type=number]{font-variant-numeric:tabular-nums}:host .mono{font-family:var(--nha-mono);font-size:12px}:host .field{display:flex;flex-direction:column;min-width:0}:host .hint{display:flex;justify-content:space-between;gap:8px;margin:4px 2px 0;color:var(--nha-text-muted);font-size:11px}:host .counter{flex:none;font-variant-numeric:tabular-nums}:host .error-text{color:var(--nha-danger)}:host .field-error{display:flex;align-items:flex-start;gap:6px;margin:4px 2px 0;color:var(--nha-danger);font-size:12px;font-weight:600;overflow-wrap:anywhere}:host .field-error nh-assistant-icon{flex:none;margin-top:1px}:host .form-error{box-sizing:border-box;display:flex;align-items:flex-start;gap:8px;margin:0;border:1px solid var(--nha-danger);border-radius:var(--nha-radius-control);padding:8px 12px;color:var(--nha-text);background:var(--nha-danger-soft);font-size:13px;overflow-wrap:anywhere}:host .form-error ul{margin:4px 0 0;padding-left:18px}:host .message{box-sizing:border-box;display:flex;align-items:center;gap:8px;margin:0;border:1px solid transparent;border-radius:var(--nha-radius-control);padding:8px 12px;font-size:13px;overflow-wrap:anywhere}:host .message.error{border-color:var(--nha-danger);color:var(--nha-text);background:var(--nha-danger-soft)}:host .message.warning{border-color:var(--nha-warning);color:var(--nha-text);background:var(--nha-warning-soft)}:host .message.success{border-color:var(--nha-success);color:var(--nha-text);background:var(--nha-success-soft)}:host .message.info{border-color:var(--nha-border);color:var(--nha-text);background:var(--nha-surface)}:host .message nh-assistant-icon{flex:none}:host button.link{min-height:0;border:0;padding:0;color:var(--nha-accent, currentColor);background:none;font:inherit;text-decoration:underline;cursor:pointer}:host button.link:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host button.link:disabled{cursor:not-allowed;opacity:.55}:host .primary,:host .secondary,:host .danger{display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;gap:6px;min-height:36px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px 14px;font:inherit;font-size:13px;font-weight:700;white-space:nowrap;cursor:pointer}:host .primary:focus-visible,:host .secondary:focus-visible,:host .danger:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .primary:disabled,:host .secondary:disabled,:host .danger:disabled{cursor:not-allowed;opacity:.55}:host .primary{border-color:var(--nha-accent);color:var(--nha-accent-contrast);background:var(--nha-accent)}:host .primary:hover:not(:disabled){filter:brightness(1.12)}:host .secondary{color:var(--nha-text);background:transparent}:host .secondary:hover:not(:disabled){background:var(--nha-surface-strong)}:host .danger{border-color:var(--nha-danger);color:var(--nha-danger);background:transparent}:host .danger:hover:not(:disabled){background:var(--nha-danger-soft)}:host .icon-button{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:1px solid transparent;border-radius:var(--nha-radius-control);color:var(--nha-text-muted);background:transparent;cursor:pointer}:host .icon-button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .icon-button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}:host .icon-button:disabled{cursor:not-allowed;opacity:.5}:host .actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px}:host .badge{display:inline-flex;align-items:center;gap:4px;border-radius:999px;padding:2px 8px;color:var(--nha-text-muted);background:var(--nha-surface-strong);font-size:11px;font-weight:700;white-space:nowrap}:host .badge.success{color:var(--nha-success);background:var(--nha-success-soft)}:host .badge.warning{color:var(--nha-warning);background:var(--nha-warning-soft)}:host .badge.danger{color:var(--nha-danger);background:var(--nha-danger-soft)}:host .visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}.section{display:flex;flex-direction:column;gap:16px;min-width:0}.section-header{display:flex;flex-wrap:wrap;align-items:flex-start;justify-content:space-between;gap:12px}.section-header>div{min-width:0;max-width:72ch}h3{margin:0 0 4px;font-size:17px;font-weight:750}h4{margin:0 0 8px;font-size:14px;font-weight:700}.muted{margin:0;color:var(--nha-text-muted);font-size:13px}.state{display:flex;flex-direction:column;align-items:flex-start;gap:10px;margin:0;border:1px dashed var(--nha-border);border-radius:var(--nha-radius);padding:20px;color:var(--nha-text-muted);font-size:13px}.state p{margin:0}.stack{display:flex;flex-direction:column;gap:14px;min-width:0}.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,240px),1fr));gap:14px}.meta{display:flex;flex-wrap:wrap;align-items:center;gap:8px 12px;color:var(--nha-text-muted);font-size:12px;overflow-wrap:anywhere}textarea.large{min-height:260px}.catalog-error{flex-wrap:wrap}.grow{flex:1;min-width:0}.subsection{display:flex;flex-direction:column;gap:8px;border-top:1px solid var(--nha-border);padding-top:14px}.history{display:grid;gap:6px;margin:0;padding:0;list-style:none}.history li{display:flex;flex-wrap:wrap;align-items:center;gap:6px 12px;font-size:12px}.cards{display:grid;gap:10px;margin:0;padding:0;list-style:none}.card{display:flex;flex-direction:column;gap:8px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius);padding:14px;background:var(--nha-background)}.card.inactive{opacity:.8}.card-header{display:flex;flex-wrap:wrap;align-items:center;gap:8px;min-width:0}.card-header strong{font-size:14px;overflow-wrap:anywhere}.card-actions{display:flex;flex-wrap:wrap;gap:8px}.check{display:flex;align-items:flex-start;gap:8px;font-size:13px;cursor:pointer}.check input{width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.check.described>span{display:grid;gap:2px;min-width:0;overflow-wrap:anywhere}.checks{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));gap:6px 12px;min-width:0;overflow-wrap:anywhere}.chips{display:flex;flex-wrap:wrap;gap:6px;margin:0;padding:0;list-style:none}.chip{display:inline-flex;align-items:center;gap:2px;border:1px solid var(--nha-border);border-radius:999px;padding:2px 2px 2px 10px;background:var(--nha-surface);font-family:var(--nha-mono);font-size:12px}.chip 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;width:26px;height:26px;border-radius:999px}.chip button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.chip button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}.chip button:disabled{cursor:not-allowed;opacity:.5}.inline{display:flex;flex-wrap:wrap;gap:8px}.inline>input{flex:1 1 180px}.catalog{display:grid;gap:4px;max-height:260px;margin:8px 0 0;overflow-y:auto;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px;list-style:none}.catalog li label{display:flex;flex-wrap:wrap;align-items:center;gap:6px 8px;border-radius:8px;padding:6px;cursor:pointer}.catalog li label:hover{background:var(--nha-surface)}.catalog .tool-id{font-family:var(--nha-mono);font-size:12px;overflow-wrap:anywhere}.skills{display:grid;gap:8px;margin:0;padding:0;list-style:none}.skill{display:grid;gap:6px;border-left:3px solid var(--nha-border);padding:4px 0 4px 12px}.uses{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));gap:8px}.use{display:flex;align-items:flex-start;gap:10px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:10px 12px;cursor:pointer;overflow-wrap:anywhere}.use input{flex:none;width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.use>span{display:grid;gap:2px;min-width:0}.use strong{font-size:13px}.use.selected{border-color:var(--nha-highlight);background:var(--nha-surface)}.use:focus-within{outline:2px solid var(--nha-highlight);outline-offset:2px}.discovery{display:grid;gap:8px;padding:10px 12px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control)}.discovery dl{display:grid;grid-template-columns:max-content minmax(0,1fr);gap:4px 12px;margin:0}.discovery dt{color:var(--nha-text-muted)}.discovery dd{margin:0;overflow-wrap:anywhere}\n", "h5{margin:0;font-size:15px;font-weight:750}.group{display:flex;flex-direction:column;gap:10px;min-width:0;border-top:1px solid var(--nha-border);padding-top:14px}.group>legend{float:left;width:100%;margin-bottom:2px;font-size:14px;font-weight:750}.choices{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,220px),1fr));gap:8px}.choice{display:flex;align-items:flex-start;gap:10px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:10px 12px;cursor:pointer;overflow-wrap:anywhere}.choice input{flex:none;width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.choice>span{display:grid;gap:2px;min-width:0}.choice strong{font-size:13px}.choice.selected{border-color:var(--nha-highlight);background:var(--nha-surface)}.choice:focus-within{outline:2px solid var(--nha-highlight);outline-offset:2px}.block{display:block;margin-top:2px}.advanced{border-top:1px solid var(--nha-border);padding-top:12px}.advanced>summary{width:fit-content;border-radius:6px;font-size:14px;font-weight:750;cursor:pointer}.advanced>summary:focus-visible{outline:2px solid var(--nha-highlight);outline-offset:2px}.advanced>.stack{margin-top:12px}.examples{display:flex;flex-wrap:wrap;gap:6px;margin:6px 0 0;padding:0;list-style:none}.examples button{border:1px solid var(--nha-border);border-radius:999px;padding:3px 10px;color:var(--nha-text);background:transparent;font:inherit;font-size:12px;cursor:pointer}.examples button:hover{background:var(--nha-surface-strong)}.examples button:focus-visible{outline:2px solid var(--nha-highlight);outline-offset:2px}.steps{display:flex;flex-wrap:wrap;gap:6px;margin:0;padding:0;list-style:none}.steps li{display:inline-flex;align-items:center;gap:6px;border-radius:999px;padding:4px 10px;color:var(--nha-text-muted);background:var(--nha-surface-strong);font-size:12px;font-weight:700}.steps li[aria-current=step]{color:var(--nha-accent-contrast);background:var(--nha-accent)}.steps li.done{color:var(--nha-success);background:var(--nha-success-soft)}.review{display:grid;grid-template-columns:minmax(0,max-content) minmax(0,1fr);gap:6px 16px;margin:0;font-size:13px}.review dt{color:var(--nha-text-muted)}.review dd{margin:0;overflow-wrap:anywhere}.tool-choice{display:flex;flex-direction:column;gap:2px;min-width:0;overflow-wrap:anywhere}@media(max-width:520px){.review{grid-template-columns:minmax(0,1fr)}.review dd{margin-bottom:6px}}.table-scroll{max-width:100%;overflow-x:auto}\n"], dependencies: [{ kind: "component", type: _NhAssistantIconComponent, selector: "nh-assistant-icon", inputs: ["name"] }, { kind: "component", type: _NhAssistantPickerComponent, selector: "nh-assistant-picker", inputs: ["inputId", "options", "value", "selectedLabel", "noneLabel", "placeholder", "search", "minQueryLength", "invalid", "describedBy", "disabled"], outputs: ["valueChange", "optionChange"] }, { kind: "component", type: NhAssistantFileLimitsFieldsComponent, selector: "nh-assistant-file-limits-fields", inputs: ["draft", "application", "errors", "idPrefix", "hintKey"], outputs: ["touched"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }, { kind: "pipe", type: _NhAssistantTranslatePipe, name: "nhAssistantTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
2747
2988
|
}
|
|
2748
2989
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: NhAssistantSkillFormComponent, decorators: [{
|
|
2749
2990
|
type: Component,
|
|
2750
|
-
args: [{ selector: 'nh-assistant-skill-form', standalone: true, imports: [TranslatePipe, _NhAssistantTranslatePipe, _NhAssistantIconComponent, _NhAssistantPickerComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<form class=\"stack card\" data-form=\"skill\" novalidate (submit)=\"$event.preventDefault(); save()\">\n <h5>\n @if (skill(); as current) {\n {{ 'nh-assistant.admin.run-config.skills.edit-title' | translate: { skill: current.skillId, version: current.version } }}\n } @else {\n {{ 'nh-assistant.admin.run-config.skills.new-title' | translate }}\n }\n </h5>\n\n <fieldset class=\"group\">\n <legend>{{ 'nh-assistant.admin.run-config.skills.what.title' | translate }}</legend>\n @if (!editing()) {\n <div class=\"field\">\n <label [for]=\"id + '-skill-id'\">{{ 'nh-assistant.admin.run-config.skills.id' | translate }}</label>\n <input type=\"text\" class=\"mono\" autocomplete=\"off\" maxlength=\"64\" [id]=\"id + '-skill-id'\" [value]=\"skillId()\"\n [attr.aria-invalid]=\"fieldError('skillId') ? true : null\"\n [attr.aria-describedby]=\"describedBy(id + '-skill-id-hint', 'skillId', id + '-skill-id-error')\"\n (input)=\"skillId.set(value($event)); touch('skillId')\" />\n <p class=\"hint\" [id]=\"id + '-skill-id-hint'\">{{ 'nh-assistant.admin.run-config.skills.id-hint' | translate }}</p>\n @if (errorKey('skillId'); as key) {\n <p class=\"field-error\" [id]=\"id + '-skill-id-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('skillId')?.params }}</p>\n }\n </div>\n }\n <div class=\"field\">\n <label [for]=\"id + '-skill-description'\">{{ 'nh-assistant.admin.run-config.skills.description-label' | translate }}</label>\n <input type=\"text\" [id]=\"id + '-skill-description'\" [value]=\"description()\"\n [attr.aria-invalid]=\"fieldError('description') ? true : null\"\n [attr.aria-describedby]=\"describedBy(id + '-skill-description-hint', 'description', id + '-skill-description-error')\"\n (input)=\"description.set(value($event)); touch('description')\" />\n <p class=\"hint\" [id]=\"id + '-skill-description-hint'\">\n <span>{{ 'nh-assistant.admin.run-config.skills.description-hint' | translate }}</span>\n <span class=\"counter\" [class.error-text]=\"description().length > maxDescription\">{{ 'nh-assistant.admin.characters' | translate: { count: description().length, max: maxDescription } }}</span>\n </p>\n @if (errorKey('description'); as key) {\n <p class=\"field-error\" [id]=\"id + '-skill-description-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('description')?.params }}</p>\n }\n </div>\n </fieldset>\n\n <fieldset class=\"group\">\n <legend>{{ 'nh-assistant.admin.run-config.skills.how.title' | translate }}</legend>\n <div class=\"field\">\n <label [for]=\"id + '-skill-rules'\">{{ 'nh-assistant.admin.run-config.way-of-working' | translate }}</label>\n <nh-assistant-picker\n [inputId]=\"id + '-skill-rules'\"\n [options]=\"rules()\"\n [value]=\"ruleHandler()\"\n [noneLabel]=\"'nh-assistant.admin.run-config.model' | translate\"\n [invalid]=\"fieldError('ruleHandler') !== null\"\n [describedBy]=\"describedBy(id + '-skill-rules-hint', 'ruleHandler', id + '-skill-rules-error')\"\n (valueChange)=\"ruleHandler.set($event); touch('ruleHandler')\" />\n <p class=\"hint\" [id]=\"id + '-skill-rules-hint'\">\n {{ (ruleHandler() ? 'nh-assistant.admin.run-config.skills.rules-hint' : 'nh-assistant.admin.run-config.skills.model-hint') | translate }}\n </p>\n @if (errorKey('ruleHandler'); as key) {\n <p class=\"field-error\" [id]=\"id + '-skill-rules-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('ruleHandler')?.params }}</p>\n }\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-skill-instructions'\">{{ 'nh-assistant.admin.run-config.skills.instructions' | translate }}</label>\n <textarea rows=\"5\" [id]=\"id + '-skill-instructions'\" [value]=\"instructions()\"\n [attr.aria-invalid]=\"fieldError('instructions') ? true : null\"\n [attr.aria-describedby]=\"describedBy(id + '-skill-instructions-hint', 'instructions', id + '-skill-instructions-error')\"\n (input)=\"instructions.set(value($event)); touch('instructions')\"></textarea>\n <p class=\"hint\" [id]=\"id + '-skill-instructions-hint'\">\n <span>{{ 'nh-assistant.admin.run-config.skills.instructions-hint' | translate }} {{ 'nh-assistant.admin.run-config.skills.background-hint' | translate }}</span>\n <span class=\"counter\" [class.error-text]=\"instructions().length > maxInstructions\">{{ 'nh-assistant.admin.characters' | translate: { count: instructions().length, max: maxInstructions } }}</span>\n </p>\n @if (errorKey('instructions'); as key) {\n <p class=\"field-error\" [id]=\"id + '-skill-instructions-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('instructions')?.params }}</p>\n }\n </div>\n <fieldset class=\"field\" [attr.aria-describedby]=\"id + '-tools-hint'\">\n <legend>{{ 'nh-assistant.admin.run-config.skills.tools' | translate }}</legend>\n <p class=\"hint\" [id]=\"id + '-tools-hint'\">{{ 'nh-assistant.admin.run-config.skills.tools-hint' | translate }}</p>\n @for (choice of toolChoices(); track choice.selector) {\n <label class=\"check\">\n <input type=\"checkbox\" [checked]=\"tools().includes(choice.selector)\" [attr.aria-invalid]=\"fieldError('tools') ? true : null\" (change)=\"toggleTool(choice.selector, checked($event))\" />\n <span class=\"tool-choice\">\n <span class=\"mono\">{{ choice.selector }}</span>\n @if (choice.description) {\n <span class=\"muted block\">{{ choice.description }}</span>\n } @else if (choice.matches !== null) {\n <span class=\"muted block\">{{ 'nh-assistant.admin.run-config.skills.tools-pattern' | translate: { count: choice.matches } }}</span>\n }\n </span>\n </label>\n } @empty {\n <p class=\"muted\">{{ 'nh-assistant.admin.agent-editor.no-selectors' | translate }}</p>\n }\n @if (errorKey('tools'); as key) {\n <p class=\"field-error\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('tools')?.params }}</p>\n }\n </fieldset>\n <div class=\"field\">\n <label [for]=\"id + '-skill-profile'\">{{ 'nh-assistant.admin.run-config.skills.profile' | translate }}</label>\n <nh-assistant-picker\n [inputId]=\"id + '-skill-profile'\"\n [options]=\"profiles()\"\n [value]=\"profile()\"\n [noneLabel]=\"'nh-assistant.admin.run-config.skills.agent-profile' | translate\"\n [invalid]=\"fieldError('profile') !== null\"\n [describedBy]=\"describedBy(id + '-skill-profile-hint', 'profile', id + '-skill-profile-error')\"\n (valueChange)=\"profile.set($event); touch('profile')\" />\n <p class=\"hint\" [id]=\"id + '-skill-profile-hint'\">{{ 'nh-assistant.admin.run-config.skills.profile-hint' | translate }}</p>\n @if (errorKey('profile'); as key) {\n <p class=\"field-error\" [id]=\"id + '-skill-profile-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('profile')?.params }}</p>\n }\n </div>\n </fieldset>\n\n <fieldset class=\"group\">\n <legend>{{ 'nh-assistant.admin.run-config.settings.approval.title' | translate }}</legend>\n <div class=\"field\">\n <label [for]=\"id + '-skill-policy'\">{{ 'nh-assistant.admin.run-config.approval-policy' | translate }}</label>\n <nh-assistant-picker\n [inputId]=\"id + '-skill-policy'\"\n [options]=\"policies()\"\n [value]=\"approvalPolicy()\"\n [noneLabel]=\"'nh-assistant.admin.run-config.skills.agent-policy' | translate\"\n [invalid]=\"fieldError('approvalPolicy') !== null\"\n [describedBy]=\"describedBy(null, 'approvalPolicy', id + '-skill-policy-error')\"\n (valueChange)=\"approvalPolicy.set($event); touch('approvalPolicy')\" />\n @if (errorKey('approvalPolicy'); as key) {\n <p class=\"field-error\" [id]=\"id + '-skill-policy-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('approvalPolicy')?.params }}</p>\n }\n </div>\n </fieldset>\n\n <fieldset class=\"group\">\n <legend>{{ 'nh-assistant.admin.run-config.limits.title' | translate }}</legend>\n <p class=\"muted\" [id]=\"id + '-limits-hint'\">{{ 'nh-assistant.admin.run-config.budget-hint' | translate }}</p>\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-skill-tokens'\">{{ 'nh-assistant.admin.run-config.max-tokens' | translate }}</label>\n <input type=\"number\" min=\"1\" inputmode=\"numeric\" [id]=\"id + '-skill-tokens'\" [value]=\"maxTokens()\"\n [attr.aria-invalid]=\"fieldError('maxTokensPerRun') ? true : null\"\n [attr.aria-describedby]=\"describedBy(id + '-limits-hint', 'maxTokensPerRun', id + '-skill-tokens-error')\"\n (input)=\"maxTokens.set(value($event)); touch('maxTokensPerRun')\" />\n @if (errorKey('maxTokensPerRun'); as key) {\n <p class=\"field-error\" [id]=\"id + '-skill-tokens-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('maxTokensPerRun')?.params }}</p>\n }\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-skill-calls'\">{{ 'nh-assistant.admin.run-config.daily-tool-calls' | translate }}</label>\n <input type=\"number\" min=\"1\" inputmode=\"numeric\" [id]=\"id + '-skill-calls'\" [value]=\"dailyToolCalls()\"\n [attr.aria-invalid]=\"fieldError('dailyToolCalls') ? true : null\"\n [attr.aria-describedby]=\"describedBy(id + '-limits-hint', 'dailyToolCalls', id + '-skill-calls-error')\"\n (input)=\"dailyToolCalls.set(value($event)); touch('dailyToolCalls'); touch('dailyTokens')\" />\n @if (errorKey('dailyToolCalls'); as key) {\n <p class=\"field-error\" [id]=\"id + '-skill-calls-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('dailyToolCalls')?.params }}</p>\n }\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-skill-daily-tokens'\">{{ 'nh-assistant.admin.run-config.daily-tokens' | translate }}</label>\n <input type=\"number\" min=\"1\" inputmode=\"numeric\" [id]=\"id + '-skill-daily-tokens'\" [value]=\"dailyTokens()\"\n [attr.aria-invalid]=\"fieldError('dailyTokens') ? true : null\"\n [attr.aria-describedby]=\"describedBy(id + '-limits-hint', 'dailyTokens', id + '-skill-daily-tokens-error')\"\n (input)=\"dailyTokens.set(value($event)); touch('dailyTokens'); touch('dailyToolCalls')\" />\n @if (errorKey('dailyTokens'); as key) {\n <p class=\"field-error\" [id]=\"id + '-skill-daily-tokens-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('dailyTokens')?.params }}</p>\n }\n </div>\n </div>\n </fieldset>\n\n @if (editing()) {\n <fieldset class=\"group\" data-apply>\n <legend>{{ 'nh-assistant.admin.run-config.skills.apply' | translate }}</legend>\n <div class=\"choices\">\n <label class=\"choice\" [class.selected]=\"apply() === 'shadow'\">\n <input type=\"radio\" [name]=\"id + '-apply'\" value=\"shadow\" [checked]=\"apply() === 'shadow'\" (change)=\"apply.set('shadow')\" />\n <span>\n <strong>{{ 'nh-assistant.admin.run-config.skills.apply-shadow' | translate }}</strong>\n <span class=\"muted\">{{ 'nh-assistant.admin.run-config.skills.apply-shadow-hint' | translate }}</span>\n </span>\n </label>\n <label class=\"choice\" [class.selected]=\"apply() === 'live'\">\n <input type=\"radio\" [name]=\"id + '-apply'\" value=\"live\" [checked]=\"apply() === 'live'\" (change)=\"apply.set('live')\" />\n <span>\n <strong>{{ 'nh-assistant.admin.run-config.skills.apply-live' | translate }}</strong>\n <span class=\"muted\">{{ 'nh-assistant.admin.run-config.skills.apply-live-hint' | translate }}</span>\n </span>\n </label>\n </div>\n </fieldset>\n } @else {\n <p class=\"hint\">{{ 'nh-assistant.admin.run-config.skills.starts-in-shadow' | translate }}</p>\n }\n\n <div class=\"field\">\n <label [for]=\"id + '-skill-reason'\">{{ 'nh-assistant.admin.agents.state.note-label' | translate }}</label>\n <input type=\"text\" autocomplete=\"off\" [id]=\"id + '-skill-reason'\" [maxLength]=\"maxReason\" [value]=\"reason()\" [attr.aria-describedby]=\"id + '-skill-reason-hint'\" (input)=\"reason.set(value($event))\" />\n <p class=\"hint\" [id]=\"id + '-skill-reason-hint'\">{{ 'nh-assistant.admin.run-config.note-hint' | translate }}</p>\n </div>\n\n @if (hasErrors()) {\n <div class=\"form-error\" role=\"alert\" data-form-error>\n <nh-assistant-icon name=\"warning\" />\n <div>\n @if (serverError(); as failure) {\n @if (failure.code === 'assistant-validation') {\n {{ 'nh-assistant.admin.run-config.check-fields' | translate }}\n } @else {\n {{ errorKeys() | nhAssistantTranslate: failure.code }}\n }\n } @else {\n {{ 'nh-assistant.admin.run-config.check-fields' | translate }}\n }\n @if (otherFieldErrors().length > 0) {\n <ul>\n @for (field of otherFieldErrors(); track field) {\n <li><span class=\"mono\">{{ field }}</span></li>\n }\n </ul>\n }\n </div>\n </div>\n }\n <div class=\"actions\">\n <button class=\"secondary\" type=\"button\" (click)=\"cancelled.emit()\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n <button class=\"primary\" type=\"submit\" [disabled]=\"busy()\">{{ (busy() ? 'nh-assistant.admin.saving' : 'nh-assistant.admin.save') | translate }}</button>\n </div>\n</form>\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;min-width:0;color:var(--nha-text);font-family:var(--nha-font);font-size:14px}:host fieldset{min-width:0;margin:0;border:0;padding:0}:host legend,:host .label,:host .field>label{display:block;margin-bottom:6px;padding:0;color:var(--nha-text);font-size:12px;font-weight:700}:host input[type=text],:host input[type=url],:host input[type=password],:host input[type=search],:host input[type=email],:host input[type=number],:host input[type=date],:host input[type=time],:host input[type=datetime-local],:host select,:host textarea{box-sizing:border-box;width:100%;min-width:0;min-height:38px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:8px 10px;color:var(--nha-text);background:var(--nha-background);font:inherit;font-size:13px}:host input[type=text]:focus-visible,:host input[type=url]:focus-visible,:host input[type=password]:focus-visible,:host input[type=search]:focus-visible,:host input[type=email]:focus-visible,:host input[type=number]:focus-visible,:host input[type=date]:focus-visible,:host input[type=time]:focus-visible,:host input[type=datetime-local]:focus-visible,:host select:focus-visible,:host textarea:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host input[type=text]:disabled,:host input[type=url]:disabled,:host input[type=password]:disabled,:host input[type=search]:disabled,:host input[type=email]:disabled,:host input[type=number]:disabled,:host input[type=date]:disabled,:host input[type=time]:disabled,:host input[type=datetime-local]:disabled,:host select:disabled,:host textarea:disabled{cursor:not-allowed;opacity:.6}:host input[type=text][aria-invalid=true],:host input[type=url][aria-invalid=true],:host input[type=password][aria-invalid=true],:host input[type=search][aria-invalid=true],:host input[type=email][aria-invalid=true],:host input[type=number][aria-invalid=true],:host input[type=date][aria-invalid=true],:host input[type=time][aria-invalid=true],:host input[type=datetime-local][aria-invalid=true],:host select[aria-invalid=true],:host textarea[aria-invalid=true]{border-color:var(--nha-danger)}:host textarea{line-height:1.45;resize:vertical}:host input[type=number]{font-variant-numeric:tabular-nums}:host .mono{font-family:var(--nha-mono);font-size:12px}:host .field{display:flex;flex-direction:column;min-width:0}:host .hint{display:flex;justify-content:space-between;gap:8px;margin:4px 2px 0;color:var(--nha-text-muted);font-size:11px}:host .counter{flex:none;font-variant-numeric:tabular-nums}:host .error-text{color:var(--nha-danger)}:host .field-error{display:flex;align-items:flex-start;gap:6px;margin:4px 2px 0;color:var(--nha-danger);font-size:12px;font-weight:600;overflow-wrap:anywhere}:host .field-error nh-assistant-icon{flex:none;margin-top:1px}:host .form-error{box-sizing:border-box;display:flex;align-items:flex-start;gap:8px;margin:0;border:1px solid var(--nha-danger);border-radius:var(--nha-radius-control);padding:8px 12px;color:var(--nha-text);background:var(--nha-danger-soft);font-size:13px;overflow-wrap:anywhere}:host .form-error ul{margin:4px 0 0;padding-left:18px}:host .message{box-sizing:border-box;display:flex;align-items:center;gap:8px;margin:0;border:1px solid transparent;border-radius:var(--nha-radius-control);padding:8px 12px;font-size:13px;overflow-wrap:anywhere}:host .message.error{border-color:var(--nha-danger);color:var(--nha-text);background:var(--nha-danger-soft)}:host .message.warning{border-color:var(--nha-warning);color:var(--nha-text);background:var(--nha-warning-soft)}:host .message.success{border-color:var(--nha-success);color:var(--nha-text);background:var(--nha-success-soft)}:host .message.info{border-color:var(--nha-border);color:var(--nha-text);background:var(--nha-surface)}:host .message nh-assistant-icon{flex:none}:host button.link{min-height:0;border:0;padding:0;color:var(--nha-accent, currentColor);background:none;font:inherit;text-decoration:underline;cursor:pointer}:host button.link:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host button.link:disabled{cursor:not-allowed;opacity:.55}:host .primary,:host .secondary,:host .danger{display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;gap:6px;min-height:36px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px 14px;font:inherit;font-size:13px;font-weight:700;white-space:nowrap;cursor:pointer}:host .primary:focus-visible,:host .secondary:focus-visible,:host .danger:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .primary:disabled,:host .secondary:disabled,:host .danger:disabled{cursor:not-allowed;opacity:.55}:host .primary{border-color:var(--nha-accent);color:var(--nha-accent-contrast);background:var(--nha-accent)}:host .primary:hover:not(:disabled){filter:brightness(1.12)}:host .secondary{color:var(--nha-text);background:transparent}:host .secondary:hover:not(:disabled){background:var(--nha-surface-strong)}:host .danger{border-color:var(--nha-danger);color:var(--nha-danger);background:transparent}:host .danger:hover:not(:disabled){background:var(--nha-danger-soft)}:host .icon-button{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:1px solid transparent;border-radius:var(--nha-radius-control);color:var(--nha-text-muted);background:transparent;cursor:pointer}:host .icon-button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .icon-button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}:host .icon-button:disabled{cursor:not-allowed;opacity:.5}:host .actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px}:host .badge{display:inline-flex;align-items:center;gap:4px;border-radius:999px;padding:2px 8px;color:var(--nha-text-muted);background:var(--nha-surface-strong);font-size:11px;font-weight:700;white-space:nowrap}:host .badge.success{color:var(--nha-success);background:var(--nha-success-soft)}:host .badge.warning{color:var(--nha-warning);background:var(--nha-warning-soft)}:host .badge.danger{color:var(--nha-danger);background:var(--nha-danger-soft)}:host .visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}.section{display:flex;flex-direction:column;gap:16px;min-width:0}.section-header{display:flex;flex-wrap:wrap;align-items:flex-start;justify-content:space-between;gap:12px}.section-header>div{min-width:0;max-width:72ch}h3{margin:0 0 4px;font-size:17px;font-weight:750}h4{margin:0 0 8px;font-size:14px;font-weight:700}.muted{margin:0;color:var(--nha-text-muted);font-size:13px}.state{display:flex;flex-direction:column;align-items:flex-start;gap:10px;margin:0;border:1px dashed var(--nha-border);border-radius:var(--nha-radius);padding:20px;color:var(--nha-text-muted);font-size:13px}.state p{margin:0}.stack{display:flex;flex-direction:column;gap:14px;min-width:0}.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,240px),1fr));gap:14px}.meta{display:flex;flex-wrap:wrap;align-items:center;gap:8px 12px;color:var(--nha-text-muted);font-size:12px;overflow-wrap:anywhere}textarea.large{min-height:260px}.catalog-error{flex-wrap:wrap}.grow{flex:1;min-width:0}.subsection{display:flex;flex-direction:column;gap:8px;border-top:1px solid var(--nha-border);padding-top:14px}.history{display:grid;gap:6px;margin:0;padding:0;list-style:none}.history li{display:flex;flex-wrap:wrap;align-items:center;gap:6px 12px;font-size:12px}.cards{display:grid;gap:10px;margin:0;padding:0;list-style:none}.card{display:flex;flex-direction:column;gap:8px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius);padding:14px;background:var(--nha-background)}.card.inactive{opacity:.8}.card-header{display:flex;flex-wrap:wrap;align-items:center;gap:8px;min-width:0}.card-header strong{font-size:14px;overflow-wrap:anywhere}.card-actions{display:flex;flex-wrap:wrap;gap:8px}.check{display:flex;align-items:flex-start;gap:8px;font-size:13px;cursor:pointer}.check input{width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.check.described>span{display:grid;gap:2px;min-width:0;overflow-wrap:anywhere}.checks{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));gap:6px 12px;min-width:0;overflow-wrap:anywhere}.chips{display:flex;flex-wrap:wrap;gap:6px;margin:0;padding:0;list-style:none}.chip{display:inline-flex;align-items:center;gap:2px;border:1px solid var(--nha-border);border-radius:999px;padding:2px 2px 2px 10px;background:var(--nha-surface);font-family:var(--nha-mono);font-size:12px}.chip 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;width:26px;height:26px;border-radius:999px}.chip button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.chip button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}.chip button:disabled{cursor:not-allowed;opacity:.5}.inline{display:flex;flex-wrap:wrap;gap:8px}.inline>input{flex:1 1 180px}.catalog{display:grid;gap:4px;max-height:260px;margin:8px 0 0;overflow-y:auto;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px;list-style:none}.catalog li label{display:flex;flex-wrap:wrap;align-items:center;gap:6px 8px;border-radius:8px;padding:6px;cursor:pointer}.catalog li label:hover{background:var(--nha-surface)}.catalog .tool-id{font-family:var(--nha-mono);font-size:12px;overflow-wrap:anywhere}.skills{display:grid;gap:8px;margin:0;padding:0;list-style:none}.skill{display:grid;gap:6px;border-left:3px solid var(--nha-border);padding:4px 0 4px 12px}.uses{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));gap:8px}.use{display:flex;align-items:flex-start;gap:10px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:10px 12px;cursor:pointer;overflow-wrap:anywhere}.use input{flex:none;width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.use>span{display:grid;gap:2px;min-width:0}.use strong{font-size:13px}.use.selected{border-color:var(--nha-highlight);background:var(--nha-surface)}.use:focus-within{outline:2px solid var(--nha-highlight);outline-offset:2px}.discovery{display:grid;gap:8px;padding:10px 12px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control)}.discovery dl{display:grid;grid-template-columns:max-content minmax(0,1fr);gap:4px 12px;margin:0}.discovery dt{color:var(--nha-text-muted)}.discovery dd{margin:0;overflow-wrap:anywhere}\n", "h5{margin:0;font-size:15px;font-weight:750}.group{display:flex;flex-direction:column;gap:10px;min-width:0;border-top:1px solid var(--nha-border);padding-top:14px}.group>legend{float:left;width:100%;margin-bottom:2px;font-size:14px;font-weight:750}.choices{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,220px),1fr));gap:8px}.choice{display:flex;align-items:flex-start;gap:10px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:10px 12px;cursor:pointer;overflow-wrap:anywhere}.choice input{flex:none;width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.choice>span{display:grid;gap:2px;min-width:0}.choice strong{font-size:13px}.choice.selected{border-color:var(--nha-highlight);background:var(--nha-surface)}.choice:focus-within{outline:2px solid var(--nha-highlight);outline-offset:2px}.block{display:block;margin-top:2px}.advanced{border-top:1px solid var(--nha-border);padding-top:12px}.advanced>summary{width:fit-content;border-radius:6px;font-size:14px;font-weight:750;cursor:pointer}.advanced>summary:focus-visible{outline:2px solid var(--nha-highlight);outline-offset:2px}.advanced>.stack{margin-top:12px}.examples{display:flex;flex-wrap:wrap;gap:6px;margin:6px 0 0;padding:0;list-style:none}.examples button{border:1px solid var(--nha-border);border-radius:999px;padding:3px 10px;color:var(--nha-text);background:transparent;font:inherit;font-size:12px;cursor:pointer}.examples button:hover{background:var(--nha-surface-strong)}.examples button:focus-visible{outline:2px solid var(--nha-highlight);outline-offset:2px}.steps{display:flex;flex-wrap:wrap;gap:6px;margin:0;padding:0;list-style:none}.steps li{display:inline-flex;align-items:center;gap:6px;border-radius:999px;padding:4px 10px;color:var(--nha-text-muted);background:var(--nha-surface-strong);font-size:12px;font-weight:700}.steps li[aria-current=step]{color:var(--nha-accent-contrast);background:var(--nha-accent)}.steps li.done{color:var(--nha-success);background:var(--nha-success-soft)}.review{display:grid;grid-template-columns:minmax(0,max-content) minmax(0,1fr);gap:6px 16px;margin:0;font-size:13px}.review dt{color:var(--nha-text-muted)}.review dd{margin:0;overflow-wrap:anywhere}.tool-choice{display:flex;flex-direction:column;gap:2px;min-width:0;overflow-wrap:anywhere}@media(max-width:520px){.review{grid-template-columns:minmax(0,1fr)}.review dd{margin-bottom:6px}}.table-scroll{max-width:100%;overflow-x:auto}\n"] }]
|
|
2991
|
+
args: [{ selector: 'nh-assistant-skill-form', standalone: true, imports: [TranslatePipe, _NhAssistantTranslatePipe, _NhAssistantIconComponent, _NhAssistantPickerComponent, NhAssistantFileLimitsFieldsComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<form class=\"stack card\" data-form=\"skill\" novalidate (submit)=\"$event.preventDefault(); save()\">\n <h5>\n @if (skill(); as current) {\n {{ 'nh-assistant.admin.run-config.skills.edit-title' | translate: { skill: current.skillId, version: current.version } }}\n } @else {\n {{ 'nh-assistant.admin.run-config.skills.new-title' | translate }}\n }\n </h5>\n\n <fieldset class=\"group\">\n <legend>{{ 'nh-assistant.admin.run-config.skills.what.title' | translate }}</legend>\n @if (!editing()) {\n <div class=\"field\">\n <label [for]=\"id + '-skill-id'\">{{ 'nh-assistant.admin.run-config.skills.id' | translate }}</label>\n <input type=\"text\" class=\"mono\" autocomplete=\"off\" maxlength=\"64\" [id]=\"id + '-skill-id'\" [value]=\"skillId()\"\n [attr.aria-invalid]=\"fieldError('skillId') ? true : null\"\n [attr.aria-describedby]=\"describedBy(id + '-skill-id-hint', 'skillId', id + '-skill-id-error')\"\n (input)=\"skillId.set(value($event)); touch('skillId')\" />\n <p class=\"hint\" [id]=\"id + '-skill-id-hint'\">{{ 'nh-assistant.admin.run-config.skills.id-hint' | translate }}</p>\n @if (errorKey('skillId'); as key) {\n <p class=\"field-error\" [id]=\"id + '-skill-id-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('skillId')?.params }}</p>\n }\n </div>\n }\n <div class=\"field\">\n <label [for]=\"id + '-skill-description'\">{{ 'nh-assistant.admin.run-config.skills.description-label' | translate }}</label>\n <input type=\"text\" [id]=\"id + '-skill-description'\" [value]=\"description()\"\n [attr.aria-invalid]=\"fieldError('description') ? true : null\"\n [attr.aria-describedby]=\"describedBy(id + '-skill-description-hint', 'description', id + '-skill-description-error')\"\n (input)=\"description.set(value($event)); touch('description')\" />\n <p class=\"hint\" [id]=\"id + '-skill-description-hint'\">\n <span>{{ 'nh-assistant.admin.run-config.skills.description-hint' | translate }}</span>\n <span class=\"counter\" [class.error-text]=\"description().length > maxDescription\">{{ 'nh-assistant.admin.characters' | translate: { count: description().length, max: maxDescription } }}</span>\n </p>\n @if (errorKey('description'); as key) {\n <p class=\"field-error\" [id]=\"id + '-skill-description-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('description')?.params }}</p>\n }\n </div>\n </fieldset>\n\n <fieldset class=\"group\">\n <legend>{{ 'nh-assistant.admin.run-config.skills.how.title' | translate }}</legend>\n <div class=\"field\">\n <label [for]=\"id + '-skill-rules'\">{{ 'nh-assistant.admin.run-config.way-of-working' | translate }}</label>\n <nh-assistant-picker\n [inputId]=\"id + '-skill-rules'\"\n [options]=\"rules()\"\n [value]=\"ruleHandler()\"\n [noneLabel]=\"'nh-assistant.admin.run-config.model' | translate\"\n [invalid]=\"fieldError('ruleHandler') !== null\"\n [describedBy]=\"describedBy(id + '-skill-rules-hint', 'ruleHandler', id + '-skill-rules-error')\"\n (valueChange)=\"ruleHandler.set($event); touch('ruleHandler')\" />\n <p class=\"hint\" [id]=\"id + '-skill-rules-hint'\">\n {{ (ruleHandler() ? 'nh-assistant.admin.run-config.skills.rules-hint' : 'nh-assistant.admin.run-config.skills.model-hint') | translate }}\n </p>\n @if (errorKey('ruleHandler'); as key) {\n <p class=\"field-error\" [id]=\"id + '-skill-rules-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('ruleHandler')?.params }}</p>\n }\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-skill-instructions'\">{{ 'nh-assistant.admin.run-config.skills.instructions' | translate }}</label>\n <textarea rows=\"5\" [id]=\"id + '-skill-instructions'\" [value]=\"instructions()\"\n [attr.aria-invalid]=\"fieldError('instructions') ? true : null\"\n [attr.aria-describedby]=\"describedBy(id + '-skill-instructions-hint', 'instructions', id + '-skill-instructions-error')\"\n (input)=\"instructions.set(value($event)); touch('instructions')\"></textarea>\n <p class=\"hint\" [id]=\"id + '-skill-instructions-hint'\">\n <span>{{ 'nh-assistant.admin.run-config.skills.instructions-hint' | translate }} {{ 'nh-assistant.admin.run-config.skills.background-hint' | translate }}</span>\n <span class=\"counter\" [class.error-text]=\"instructions().length > maxInstructions\">{{ 'nh-assistant.admin.characters' | translate: { count: instructions().length, max: maxInstructions } }}</span>\n </p>\n @if (errorKey('instructions'); as key) {\n <p class=\"field-error\" [id]=\"id + '-skill-instructions-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('instructions')?.params }}</p>\n }\n </div>\n <fieldset class=\"field\" [attr.aria-describedby]=\"id + '-tools-hint'\">\n <legend>{{ 'nh-assistant.admin.run-config.skills.tools' | translate }}</legend>\n <p class=\"hint\" [id]=\"id + '-tools-hint'\">{{ 'nh-assistant.admin.run-config.skills.tools-hint' | translate }}</p>\n @for (choice of toolChoices(); track choice.selector) {\n <label class=\"check\">\n <input type=\"checkbox\" [checked]=\"tools().includes(choice.selector)\" [attr.aria-invalid]=\"fieldError('tools') ? true : null\" (change)=\"toggleTool(choice.selector, checked($event))\" />\n <span class=\"tool-choice\">\n <span class=\"mono\">{{ choice.selector }}</span>\n @if (choice.description) {\n <span class=\"muted block\">{{ choice.description }}</span>\n } @else if (choice.matches !== null) {\n <span class=\"muted block\">{{ 'nh-assistant.admin.run-config.skills.tools-pattern' | translate: { count: choice.matches } }}</span>\n }\n </span>\n </label>\n } @empty {\n <p class=\"muted\">{{ 'nh-assistant.admin.agent-editor.no-selectors' | translate }}</p>\n }\n @if (errorKey('tools'); as key) {\n <p class=\"field-error\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('tools')?.params }}</p>\n }\n </fieldset>\n <div class=\"field\">\n <label [for]=\"id + '-skill-profile'\">{{ 'nh-assistant.admin.run-config.skills.profile' | translate }}</label>\n <nh-assistant-picker\n [inputId]=\"id + '-skill-profile'\"\n [options]=\"profiles()\"\n [value]=\"profile()\"\n [noneLabel]=\"'nh-assistant.admin.run-config.skills.agent-profile' | translate\"\n [invalid]=\"fieldError('profile') !== null\"\n [describedBy]=\"describedBy(id + '-skill-profile-hint', 'profile', id + '-skill-profile-error')\"\n (valueChange)=\"profile.set($event); touch('profile')\" />\n <p class=\"hint\" [id]=\"id + '-skill-profile-hint'\">{{ 'nh-assistant.admin.run-config.skills.profile-hint' | translate }}</p>\n @if (errorKey('profile'); as key) {\n <p class=\"field-error\" [id]=\"id + '-skill-profile-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('profile')?.params }}</p>\n }\n </div>\n <div class=\"field\" data-classification>\n <label [for]=\"id + '-skill-classification'\">{{ 'nh-assistant.admin.run-config.skills.classification.label' | translate }}</label>\n <nh-assistant-picker\n [inputId]=\"id + '-skill-classification'\"\n [options]=\"classifications()\"\n [value]=\"dataClassification()\"\n [noneLabel]=\"'nh-assistant.admin.run-config.skills.classification.agent' | translate\"\n [invalid]=\"fieldError('dataClassification') !== null\"\n [describedBy]=\"describedBy(id + '-skill-classification-hint', 'dataClassification', id + '-skill-classification-error')\"\n (valueChange)=\"dataClassification.set($event); touch('dataClassification')\" />\n <p class=\"hint\" [id]=\"id + '-skill-classification-hint'\">{{ 'nh-assistant.admin.run-config.skills.classification.hint' | translate }}</p>\n @for (warning of warnings(); track warning) {\n <p class=\"message warning\" [attr.data-skill-form-warning]=\"warning\">\n <nh-assistant-icon name=\"warning\" />{{ warningKeys('skill', warning) | nhAssistantTranslate: warning }}\n </p>\n }\n @if (errorKey('dataClassification'); as key) {\n <p class=\"field-error\" [id]=\"id + '-skill-classification-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('dataClassification')?.params }}</p>\n }\n </div>\n <label class=\"check\" data-accepts-files>\n <input type=\"checkbox\" [checked]=\"acceptsFiles()\" (change)=\"acceptsFiles.set(checked($event))\" />\n <span>\n {{ 'nh-assistant.admin.run-config.files.label' | translate }}\n <span class=\"muted block\">{{ 'nh-assistant.admin.run-config.files.skill-hint' | translate }}</span>\n </span>\n </label>\n @if (acceptsFiles()) {\n <nh-assistant-file-limits-fields\n [draft]=\"fileLimits\"\n [application]=\"catalog()?.fileLimits\"\n [errors]=\"fieldErrors()\"\n [idPrefix]=\"id\"\n hintKey=\"nh-assistant.admin.run-config.files.skill-limits-hint\"\n (touched)=\"touch($event)\" />\n }\n </fieldset>\n\n <fieldset class=\"group\">\n <legend>{{ 'nh-assistant.admin.run-config.settings.approval.title' | translate }}</legend>\n <div class=\"field\">\n <label [for]=\"id + '-skill-policy'\">{{ 'nh-assistant.admin.run-config.approval-policy' | translate }}</label>\n <nh-assistant-picker\n [inputId]=\"id + '-skill-policy'\"\n [options]=\"policies()\"\n [value]=\"approvalPolicy()\"\n [noneLabel]=\"'nh-assistant.admin.run-config.skills.agent-policy' | translate\"\n [invalid]=\"fieldError('approvalPolicy') !== null\"\n [describedBy]=\"describedBy(null, 'approvalPolicy', id + '-skill-policy-error')\"\n (valueChange)=\"approvalPolicy.set($event); touch('approvalPolicy')\" />\n @if (errorKey('approvalPolicy'); as key) {\n <p class=\"field-error\" [id]=\"id + '-skill-policy-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('approvalPolicy')?.params }}</p>\n }\n </div>\n </fieldset>\n\n <fieldset class=\"group\">\n <legend>{{ 'nh-assistant.admin.run-config.limits.title' | translate }}</legend>\n <p class=\"muted\" [id]=\"id + '-limits-hint'\">{{ 'nh-assistant.admin.run-config.budget-hint' | translate }}</p>\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-skill-tokens'\">{{ 'nh-assistant.admin.run-config.max-tokens' | translate }}</label>\n <input type=\"number\" min=\"1\" inputmode=\"numeric\" [id]=\"id + '-skill-tokens'\" [value]=\"maxTokens()\"\n [attr.aria-invalid]=\"fieldError('maxTokensPerRun') ? true : null\"\n [attr.aria-describedby]=\"describedBy(id + '-limits-hint', 'maxTokensPerRun', id + '-skill-tokens-error')\"\n (input)=\"maxTokens.set(value($event)); touch('maxTokensPerRun')\" />\n @if (errorKey('maxTokensPerRun'); as key) {\n <p class=\"field-error\" [id]=\"id + '-skill-tokens-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('maxTokensPerRun')?.params }}</p>\n }\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-skill-calls'\">{{ 'nh-assistant.admin.run-config.daily-tool-calls' | translate }}</label>\n <input type=\"number\" min=\"1\" inputmode=\"numeric\" [id]=\"id + '-skill-calls'\" [value]=\"dailyToolCalls()\"\n [attr.aria-invalid]=\"fieldError('dailyToolCalls') ? true : null\"\n [attr.aria-describedby]=\"describedBy(id + '-limits-hint', 'dailyToolCalls', id + '-skill-calls-error')\"\n (input)=\"dailyToolCalls.set(value($event)); touch('dailyToolCalls'); touch('dailyTokens')\" />\n @if (errorKey('dailyToolCalls'); as key) {\n <p class=\"field-error\" [id]=\"id + '-skill-calls-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('dailyToolCalls')?.params }}</p>\n }\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-skill-daily-tokens'\">{{ 'nh-assistant.admin.run-config.daily-tokens' | translate }}</label>\n <input type=\"number\" min=\"1\" inputmode=\"numeric\" [id]=\"id + '-skill-daily-tokens'\" [value]=\"dailyTokens()\"\n [attr.aria-invalid]=\"fieldError('dailyTokens') ? true : null\"\n [attr.aria-describedby]=\"describedBy(id + '-limits-hint', 'dailyTokens', id + '-skill-daily-tokens-error')\"\n (input)=\"dailyTokens.set(value($event)); touch('dailyTokens'); touch('dailyToolCalls')\" />\n @if (errorKey('dailyTokens'); as key) {\n <p class=\"field-error\" [id]=\"id + '-skill-daily-tokens-error'\"><nh-assistant-icon name=\"warning\" />{{ key | translate: fieldError('dailyTokens')?.params }}</p>\n }\n </div>\n </div>\n </fieldset>\n\n @if (editing()) {\n <fieldset class=\"group\" data-apply>\n <legend>{{ 'nh-assistant.admin.run-config.skills.apply' | translate }}</legend>\n <div class=\"choices\">\n <label class=\"choice\" [class.selected]=\"apply() === 'shadow'\">\n <input type=\"radio\" [name]=\"id + '-apply'\" value=\"shadow\" [checked]=\"apply() === 'shadow'\" (change)=\"apply.set('shadow')\" />\n <span>\n <strong>{{ 'nh-assistant.admin.run-config.skills.apply-shadow' | translate }}</strong>\n <span class=\"muted\">{{ 'nh-assistant.admin.run-config.skills.apply-shadow-hint' | translate }}</span>\n </span>\n </label>\n <label class=\"choice\" [class.selected]=\"apply() === 'live'\">\n <input type=\"radio\" [name]=\"id + '-apply'\" value=\"live\" [checked]=\"apply() === 'live'\" (change)=\"apply.set('live')\" />\n <span>\n <strong>{{ 'nh-assistant.admin.run-config.skills.apply-live' | translate }}</strong>\n <span class=\"muted\">{{ 'nh-assistant.admin.run-config.skills.apply-live-hint' | translate }}</span>\n </span>\n </label>\n </div>\n </fieldset>\n } @else {\n <p class=\"hint\">{{ 'nh-assistant.admin.run-config.skills.starts-in-shadow' | translate }}</p>\n <label class=\"check\" data-switch-on-now>\n <input type=\"checkbox\" [checked]=\"switchOnNow()\" (change)=\"switchOnNow.set(checked($event))\" />\n <span>\n {{ 'nh-assistant.admin.run-config.skills.switch-on-now' | translate }}\n <span class=\"muted block\">{{ 'nh-assistant.admin.run-config.skills.switch-on-now-hint' | translate }}</span>\n </span>\n </label>\n }\n\n <div class=\"field\">\n <label [for]=\"id + '-skill-reason'\">{{ 'nh-assistant.admin.agents.state.note-label' | translate }}</label>\n <input type=\"text\" autocomplete=\"off\" [id]=\"id + '-skill-reason'\" [maxLength]=\"maxReason\" [value]=\"reason()\" [attr.aria-describedby]=\"id + '-skill-reason-hint'\" (input)=\"reason.set(value($event))\" />\n <p class=\"hint\" [id]=\"id + '-skill-reason-hint'\">{{ 'nh-assistant.admin.run-config.note-hint' | translate }}</p>\n </div>\n\n @if (hasErrors()) {\n <div class=\"form-error\" role=\"alert\" data-form-error>\n <nh-assistant-icon name=\"warning\" />\n <div>\n @if (serverError(); as failure) {\n @if (failure.code === 'assistant-validation') {\n {{ 'nh-assistant.admin.run-config.check-fields' | translate }}\n } @else {\n {{ errorKeys() | nhAssistantTranslate: failure.code }}\n }\n } @else {\n {{ 'nh-assistant.admin.run-config.check-fields' | translate }}\n }\n @if (otherFieldErrors().length > 0) {\n <ul>\n @for (field of otherFieldErrors(); track field) {\n <li><span class=\"mono\">{{ field }}</span></li>\n }\n </ul>\n }\n </div>\n </div>\n }\n <div class=\"actions\">\n <button class=\"secondary\" type=\"button\" (click)=\"cancelled.emit()\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n <button class=\"primary\" type=\"submit\" [disabled]=\"busy()\">{{ (busy() ? 'nh-assistant.admin.saving' : 'nh-assistant.admin.save') | translate }}</button>\n </div>\n</form>\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;min-width:0;color:var(--nha-text);font-family:var(--nha-font);font-size:14px}:host fieldset{min-width:0;margin:0;border:0;padding:0}:host legend,:host .label,:host .field>label{display:block;margin-bottom:6px;padding:0;color:var(--nha-text);font-size:12px;font-weight:700}:host input[type=text],:host input[type=url],:host input[type=password],:host input[type=search],:host input[type=email],:host input[type=number],:host input[type=date],:host input[type=time],:host input[type=datetime-local],:host select,:host textarea{box-sizing:border-box;width:100%;min-width:0;min-height:38px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:8px 10px;color:var(--nha-text);background:var(--nha-background);font:inherit;font-size:13px}:host input[type=text]:focus-visible,:host input[type=url]:focus-visible,:host input[type=password]:focus-visible,:host input[type=search]:focus-visible,:host input[type=email]:focus-visible,:host input[type=number]:focus-visible,:host input[type=date]:focus-visible,:host input[type=time]:focus-visible,:host input[type=datetime-local]:focus-visible,:host select:focus-visible,:host textarea:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host input[type=text]:disabled,:host input[type=url]:disabled,:host input[type=password]:disabled,:host input[type=search]:disabled,:host input[type=email]:disabled,:host input[type=number]:disabled,:host input[type=date]:disabled,:host input[type=time]:disabled,:host input[type=datetime-local]:disabled,:host select:disabled,:host textarea:disabled{cursor:not-allowed;opacity:.6}:host input[type=text][aria-invalid=true],:host input[type=url][aria-invalid=true],:host input[type=password][aria-invalid=true],:host input[type=search][aria-invalid=true],:host input[type=email][aria-invalid=true],:host input[type=number][aria-invalid=true],:host input[type=date][aria-invalid=true],:host input[type=time][aria-invalid=true],:host input[type=datetime-local][aria-invalid=true],:host select[aria-invalid=true],:host textarea[aria-invalid=true]{border-color:var(--nha-danger)}:host textarea{line-height:1.45;resize:vertical}:host input[type=number]{font-variant-numeric:tabular-nums}:host .mono{font-family:var(--nha-mono);font-size:12px}:host .field{display:flex;flex-direction:column;min-width:0}:host .hint{display:flex;justify-content:space-between;gap:8px;margin:4px 2px 0;color:var(--nha-text-muted);font-size:11px}:host .counter{flex:none;font-variant-numeric:tabular-nums}:host .error-text{color:var(--nha-danger)}:host .field-error{display:flex;align-items:flex-start;gap:6px;margin:4px 2px 0;color:var(--nha-danger);font-size:12px;font-weight:600;overflow-wrap:anywhere}:host .field-error nh-assistant-icon{flex:none;margin-top:1px}:host .form-error{box-sizing:border-box;display:flex;align-items:flex-start;gap:8px;margin:0;border:1px solid var(--nha-danger);border-radius:var(--nha-radius-control);padding:8px 12px;color:var(--nha-text);background:var(--nha-danger-soft);font-size:13px;overflow-wrap:anywhere}:host .form-error ul{margin:4px 0 0;padding-left:18px}:host .message{box-sizing:border-box;display:flex;align-items:center;gap:8px;margin:0;border:1px solid transparent;border-radius:var(--nha-radius-control);padding:8px 12px;font-size:13px;overflow-wrap:anywhere}:host .message.error{border-color:var(--nha-danger);color:var(--nha-text);background:var(--nha-danger-soft)}:host .message.warning{border-color:var(--nha-warning);color:var(--nha-text);background:var(--nha-warning-soft)}:host .message.success{border-color:var(--nha-success);color:var(--nha-text);background:var(--nha-success-soft)}:host .message.info{border-color:var(--nha-border);color:var(--nha-text);background:var(--nha-surface)}:host .message nh-assistant-icon{flex:none}:host button.link{min-height:0;border:0;padding:0;color:var(--nha-accent, currentColor);background:none;font:inherit;text-decoration:underline;cursor:pointer}:host button.link:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host button.link:disabled{cursor:not-allowed;opacity:.55}:host .primary,:host .secondary,:host .danger{display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;gap:6px;min-height:36px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px 14px;font:inherit;font-size:13px;font-weight:700;white-space:nowrap;cursor:pointer}:host .primary:focus-visible,:host .secondary:focus-visible,:host .danger:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .primary:disabled,:host .secondary:disabled,:host .danger:disabled{cursor:not-allowed;opacity:.55}:host .primary{border-color:var(--nha-accent);color:var(--nha-accent-contrast);background:var(--nha-accent)}:host .primary:hover:not(:disabled){filter:brightness(1.12)}:host .secondary{color:var(--nha-text);background:transparent}:host .secondary:hover:not(:disabled){background:var(--nha-surface-strong)}:host .danger{border-color:var(--nha-danger);color:var(--nha-danger);background:transparent}:host .danger:hover:not(:disabled){background:var(--nha-danger-soft)}:host .icon-button{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:1px solid transparent;border-radius:var(--nha-radius-control);color:var(--nha-text-muted);background:transparent;cursor:pointer}:host .icon-button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .icon-button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}:host .icon-button:disabled{cursor:not-allowed;opacity:.5}:host .actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px}:host .badge{display:inline-flex;align-items:center;gap:4px;border-radius:999px;padding:2px 8px;color:var(--nha-text-muted);background:var(--nha-surface-strong);font-size:11px;font-weight:700;white-space:nowrap}:host .badge.success{color:var(--nha-success);background:var(--nha-success-soft)}:host .badge.warning{color:var(--nha-warning);background:var(--nha-warning-soft)}:host .badge.danger{color:var(--nha-danger);background:var(--nha-danger-soft)}:host .visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}.section{display:flex;flex-direction:column;gap:16px;min-width:0}.section-header{display:flex;flex-wrap:wrap;align-items:flex-start;justify-content:space-between;gap:12px}.section-header>div{min-width:0;max-width:72ch}h3{margin:0 0 4px;font-size:17px;font-weight:750}h4{margin:0 0 8px;font-size:14px;font-weight:700}.muted{margin:0;color:var(--nha-text-muted);font-size:13px}.state{display:flex;flex-direction:column;align-items:flex-start;gap:10px;margin:0;border:1px dashed var(--nha-border);border-radius:var(--nha-radius);padding:20px;color:var(--nha-text-muted);font-size:13px}.state p{margin:0}.stack{display:flex;flex-direction:column;gap:14px;min-width:0}.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,240px),1fr));gap:14px}.meta{display:flex;flex-wrap:wrap;align-items:center;gap:8px 12px;color:var(--nha-text-muted);font-size:12px;overflow-wrap:anywhere}textarea.large{min-height:260px}.catalog-error{flex-wrap:wrap}.grow{flex:1;min-width:0}.subsection{display:flex;flex-direction:column;gap:8px;border-top:1px solid var(--nha-border);padding-top:14px}.history{display:grid;gap:6px;margin:0;padding:0;list-style:none}.history li{display:flex;flex-wrap:wrap;align-items:center;gap:6px 12px;font-size:12px}.cards{display:grid;gap:10px;margin:0;padding:0;list-style:none}.card{display:flex;flex-direction:column;gap:8px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius);padding:14px;background:var(--nha-background)}.card.inactive{opacity:.8}.card-header{display:flex;flex-wrap:wrap;align-items:center;gap:8px;min-width:0}.card-header strong{font-size:14px;overflow-wrap:anywhere}.card-actions{display:flex;flex-wrap:wrap;gap:8px}.check{display:flex;align-items:flex-start;gap:8px;font-size:13px;cursor:pointer}.check input{width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.check.described>span{display:grid;gap:2px;min-width:0;overflow-wrap:anywhere}.checks{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));gap:6px 12px;min-width:0;overflow-wrap:anywhere}.chips{display:flex;flex-wrap:wrap;gap:6px;margin:0;padding:0;list-style:none}.chip{display:inline-flex;align-items:center;gap:2px;border:1px solid var(--nha-border);border-radius:999px;padding:2px 2px 2px 10px;background:var(--nha-surface);font-family:var(--nha-mono);font-size:12px}.chip 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;width:26px;height:26px;border-radius:999px}.chip button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.chip button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}.chip button:disabled{cursor:not-allowed;opacity:.5}.inline{display:flex;flex-wrap:wrap;gap:8px}.inline>input{flex:1 1 180px}.catalog{display:grid;gap:4px;max-height:260px;margin:8px 0 0;overflow-y:auto;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px;list-style:none}.catalog li label{display:flex;flex-wrap:wrap;align-items:center;gap:6px 8px;border-radius:8px;padding:6px;cursor:pointer}.catalog li label:hover{background:var(--nha-surface)}.catalog .tool-id{font-family:var(--nha-mono);font-size:12px;overflow-wrap:anywhere}.skills{display:grid;gap:8px;margin:0;padding:0;list-style:none}.skill{display:grid;gap:6px;border-left:3px solid var(--nha-border);padding:4px 0 4px 12px}.uses{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));gap:8px}.use{display:flex;align-items:flex-start;gap:10px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:10px 12px;cursor:pointer;overflow-wrap:anywhere}.use input{flex:none;width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.use>span{display:grid;gap:2px;min-width:0}.use strong{font-size:13px}.use.selected{border-color:var(--nha-highlight);background:var(--nha-surface)}.use:focus-within{outline:2px solid var(--nha-highlight);outline-offset:2px}.discovery{display:grid;gap:8px;padding:10px 12px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control)}.discovery dl{display:grid;grid-template-columns:max-content minmax(0,1fr);gap:4px 12px;margin:0}.discovery dt{color:var(--nha-text-muted)}.discovery dd{margin:0;overflow-wrap:anywhere}\n", "h5{margin:0;font-size:15px;font-weight:750}.group{display:flex;flex-direction:column;gap:10px;min-width:0;border-top:1px solid var(--nha-border);padding-top:14px}.group>legend{float:left;width:100%;margin-bottom:2px;font-size:14px;font-weight:750}.choices{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,220px),1fr));gap:8px}.choice{display:flex;align-items:flex-start;gap:10px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:10px 12px;cursor:pointer;overflow-wrap:anywhere}.choice input{flex:none;width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.choice>span{display:grid;gap:2px;min-width:0}.choice strong{font-size:13px}.choice.selected{border-color:var(--nha-highlight);background:var(--nha-surface)}.choice:focus-within{outline:2px solid var(--nha-highlight);outline-offset:2px}.block{display:block;margin-top:2px}.advanced{border-top:1px solid var(--nha-border);padding-top:12px}.advanced>summary{width:fit-content;border-radius:6px;font-size:14px;font-weight:750;cursor:pointer}.advanced>summary:focus-visible{outline:2px solid var(--nha-highlight);outline-offset:2px}.advanced>.stack{margin-top:12px}.examples{display:flex;flex-wrap:wrap;gap:6px;margin:6px 0 0;padding:0;list-style:none}.examples button{border:1px solid var(--nha-border);border-radius:999px;padding:3px 10px;color:var(--nha-text);background:transparent;font:inherit;font-size:12px;cursor:pointer}.examples button:hover{background:var(--nha-surface-strong)}.examples button:focus-visible{outline:2px solid var(--nha-highlight);outline-offset:2px}.steps{display:flex;flex-wrap:wrap;gap:6px;margin:0;padding:0;list-style:none}.steps li{display:inline-flex;align-items:center;gap:6px;border-radius:999px;padding:4px 10px;color:var(--nha-text-muted);background:var(--nha-surface-strong);font-size:12px;font-weight:700}.steps li[aria-current=step]{color:var(--nha-accent-contrast);background:var(--nha-accent)}.steps li.done{color:var(--nha-success);background:var(--nha-success-soft)}.review{display:grid;grid-template-columns:minmax(0,max-content) minmax(0,1fr);gap:6px 16px;margin:0;font-size:13px}.review dt{color:var(--nha-text-muted)}.review dd{margin:0;overflow-wrap:anywhere}.tool-choice{display:flex;flex-direction:column;gap:2px;min-width:0;overflow-wrap:anywhere}@media(max-width:520px){.review{grid-template-columns:minmax(0,1fr)}.review dd{margin-bottom:6px}}.table-scroll{max-width:100%;overflow-x:auto}\n"] }]
|
|
2751
2992
|
}], propDecorators: { agentId: [{ type: i0.Input, args: [{ isSignal: true, alias: "agentId", required: true }] }], catalog: [{ type: i0.Input, args: [{ isSignal: true, alias: "catalog", required: false }] }], toolSelectors: [{ type: i0.Input, args: [{ isSignal: true, alias: "toolSelectors", required: false }] }], toolCatalog: [{ type: i0.Input, args: [{ isSignal: true, alias: "toolCatalog", required: false }] }], skill: [{ type: i0.Input, args: [{ isSignal: true, alias: "skill", required: false }] }], takenIds: [{ type: i0.Input, args: [{ isSignal: true, alias: "takenIds", required: false }] }], saved: [{ type: i0.Output, args: ["saved"] }], cancelled: [{ type: i0.Output, args: ["cancelled"] }] } });
|
|
2752
2993
|
|
|
2753
2994
|
let nextId$6 = 0;
|
|
@@ -2958,6 +3199,7 @@ class NhAssistantAgentRunConfigurationComponent {
|
|
|
2958
3199
|
this.id = `nh-assistant-run-configuration-${nextId$5++}`;
|
|
2959
3200
|
this.maxReason = runConfigLimits.reason;
|
|
2960
3201
|
this.skillNameKeys = skillNameKeys;
|
|
3202
|
+
this.warningKeys = warningKeys;
|
|
2961
3203
|
this.skillDescriptionKeys = skillDescriptionKeys;
|
|
2962
3204
|
this.readableId = readableId;
|
|
2963
3205
|
this.loading = signal(true, ...(ngDevMode ? [{ debugName: "loading" }] : []));
|
|
@@ -2968,6 +3210,8 @@ class NhAssistantAgentRunConfigurationComponent {
|
|
|
2968
3210
|
this.error = signal(null, ...(ngDevMode ? [{ debugName: "error" }] : []));
|
|
2969
3211
|
this.errorKeys = computed(() => _nhAssistantErrorKeys(this.error()), ...(ngDevMode ? [{ debugName: "errorKeys" }] : []));
|
|
2970
3212
|
this.saved = signal(null, ...(ngDevMode ? [{ debugName: "saved" }] : []));
|
|
3213
|
+
/** The warnings of the skill that was just saved, shown with the saved message. */
|
|
3214
|
+
this.savedWarnings = signal([], ...(ngDevMode ? [{ debugName: "savedWarnings" }] : []));
|
|
2971
3215
|
this.form = signal(null, ...(ngDevMode ? [{ debugName: "form" }] : []));
|
|
2972
3216
|
this.confirming = signal(null, ...(ngDevMode ? [{ debugName: "confirming" }] : []));
|
|
2973
3217
|
this.reason = signal('', ...(ngDevMode ? [{ debugName: "reason" }] : []));
|
|
@@ -3156,7 +3400,7 @@ class NhAssistantAgentRunConfigurationComponent {
|
|
|
3156
3400
|
onSkillSaved(result) {
|
|
3157
3401
|
this.finish(result.candidate
|
|
3158
3402
|
? 'nh-assistant.admin.run-config.skills.saved-candidate'
|
|
3159
|
-
: result.added ? 'nh-assistant.admin.run-config.skills.added' : 'nh-assistant.admin.run-config.skills.saved');
|
|
3403
|
+
: result.added ? 'nh-assistant.admin.run-config.skills.added' : 'nh-assistant.admin.run-config.skills.saved', result.skill.warnings ?? []);
|
|
3160
3404
|
}
|
|
3161
3405
|
onTriggerSaved(added) {
|
|
3162
3406
|
this.finish(added ? 'nh-assistant.admin.run-config.triggers.added' : 'nh-assistant.admin.run-config.triggers.saved');
|
|
@@ -3264,10 +3508,11 @@ class NhAssistantAgentRunConfigurationComponent {
|
|
|
3264
3508
|
openForm();
|
|
3265
3509
|
this.returnFocus = opener instanceof HTMLElement ? opener : null;
|
|
3266
3510
|
}
|
|
3267
|
-
finish(savedKey) {
|
|
3511
|
+
finish(savedKey, warnings = []) {
|
|
3268
3512
|
this.returnFocus = null;
|
|
3269
3513
|
this.form.set(null);
|
|
3270
3514
|
this.saved.set(savedKey);
|
|
3515
|
+
this.savedWarnings.set(warnings);
|
|
3271
3516
|
this.changed.emit();
|
|
3272
3517
|
this.load();
|
|
3273
3518
|
afterNextRender(() => this.host.nativeElement.querySelector('[data-saved]')?.focus(), { injector: this.injector });
|
|
@@ -3310,7 +3555,7 @@ class NhAssistantAgentRunConfigurationComponent {
|
|
|
3310
3555
|
});
|
|
3311
3556
|
}
|
|
3312
3557
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: NhAssistantAgentRunConfigurationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
3313
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.34", type: NhAssistantAgentRunConfigurationComponent, isStandalone: true, selector: "nh-assistant-agent-run-configuration", inputs: { agent: { classPropertyName: "agent", publicName: "agent", isSignal: true, isRequired: true, transformFunction: null }, startSetup: { classPropertyName: "startSetup", publicName: "startSetup", isSignal: true, isRequired: false, transformFunction: null }, tools: { classPropertyName: "tools", publicName: "tools", isSignal: true, isRequired: false, transformFunction: null }, servers: { classPropertyName: "servers", publicName: "servers", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { changed: "changed", editAgent: "editAgent", changeAccount: "changeAccount", openRuns: "openRuns", openMcpServers: "openMcpServers" }, ngImport: i0, template: "<section class=\"section\" [attr.aria-labelledby]=\"id + '-title'\" [attr.aria-busy]=\"loading() || busy()\">\n <header class=\"section-header\">\n <div>\n <h3 [id]=\"id + '-title'\">{{ 'nh-assistant.admin.run-config.title' | translate: { name: agentName() } }}</h3>\n <p class=\"muted\">{{ 'nh-assistant.admin.run-config.description' | translate }}</p>\n </div>\n </header>\n\n @if (saved(); as key) {\n <p class=\"message success\" role=\"status\" tabindex=\"-1\" data-saved>{{ key | translate }}</p>\n }\n\n @if (loading() && !configuration()) {\n <p class=\"state\" role=\"status\">{{ 'nh-assistant.admin.loading' | translate }}</p>\n } @else if (loadFailed()) {\n <div class=\"state\" role=\"alert\">\n <p>{{ 'nh-assistant.admin.load-failed' | translate }}</p>\n <button class=\"secondary\" type=\"button\" (click)=\"load()\">{{ 'nh-assistant.admin.retry' | translate }}</button>\n </div>\n } @else if (configuration(); as config) {\n <nh-assistant-account-names-error [names]=\"accountNames\" />\n @if (problems().length > 0) {\n <!-- Each problem shows at the step of the flow it concerns, with the form that fixes it. -->\n <p class=\"message warning\" role=\"status\" data-problems>\n <nh-assistant-icon name=\"warning\" />{{ 'nh-assistant.admin.run-config.flow.problems' | translate }}\n </p>\n }\n\n <!-- The agent at a glance; the forms below change what each step shows. -->\n <nh-assistant-agent-flow\n [agent]=\"agent()\"\n [configuration]=\"config\"\n [catalog]=\"catalog()\"\n [tools]=\"tools()\"\n [servers]=\"servers()\"\n [accountName]=\"accountName()\"\n [busy]=\"busy()\"\n (entry)=\"onFlowEntry($event)\" />\n\n @if (config.source === 'admin') {\n <section class=\"subsection\" [attr.aria-labelledby]=\"id + '-settings-title'\" data-run-settings>\n <header class=\"section-header\">\n <h4 [id]=\"id + '-settings-title'\">{{ 'nh-assistant.admin.run-config.settings.title' | translate }}</h4>\n </header>\n @if (form() === 'setup') {\n <nh-assistant-agent-work-setup\n [agentId]=\"config.agentId\"\n [agentName]=\"agentName()\"\n [catalog]=\"catalog()\"\n [worksOn]=\"agent()\"\n [toolSelectors]=\"config.toolSelectors\"\n [toolCatalog]=\"tools()\"\n [takenSkillIds]=\"skillIds()\"\n [takenTriggerIds]=\"triggerIds()\"\n (completed)=\"onSetupCompleted($event.live)\"\n (cancelled)=\"cancel()\" />\n } @else if (form() === 'settings') {\n <nh-assistant-run-settings-form\n [agentId]=\"config.agentId\"\n [catalog]=\"catalog()\"\n [settings]=\"config.runSettings\"\n (saved)=\"onSettingsSaved()\"\n (cancelled)=\"cancel()\" />\n } @else if (config.runSettings; as settings) {\n <dl class=\"review\" data-settings-summary>\n <dt>{{ 'nh-assistant.admin.run-config.settings.who.short' | translate }}</dt>\n <dd>\n {{ 'nh-assistant.admin.run-config.settings.accountability.' + settings.accountability | translate }}\n @if (settings.serviceAccountActorId; as accountId) {\n <span class=\"block\" data-account-name>\n <nh-assistant-account-label [actorId]=\"accountId\" [name]=\"accountName()\" />\n </span>\n }\n </dd>\n <dt>{{ 'nh-assistant.admin.run-config.way-of-working' | translate }}</dt>\n <dd>{{ settings.ruleHandler ?? ('nh-assistant.admin.run-config.model' | translate) }}</dd>\n <dt>{{ 'nh-assistant.admin.run-config.approval-policy' | translate }}</dt>\n <dd>\n {{ settings.approvalPolicy ?? ('nh-assistant.admin.run-config.settings.approval.default' | translate) }}\n @if (settingsPolicy(); as sentence) {\n <span class=\"muted block\">{{ sentence }}</span>\n }\n </dd>\n <dt>{{ 'nh-assistant.admin.agents.version-label' | translate }}</dt>\n <dd>\n {{ 'nh-assistant.admin.agents.version' | translate: { version: settings.version } }}\n @if (!settings.inEffect) {\n <span class=\"badge danger\">{{ 'nh-assistant.admin.run-config.out-of-effect' | translate }}</span>\n }\n </dd>\n </dl>\n <p class=\"muted\">{{ 'nh-assistant.admin.run-config.settings.rights' | translate }}</p>\n <div class=\"card-actions\">\n <button class=\"secondary\" type=\"button\" data-open-form=\"settings\" [disabled]=\"busy()\" (click)=\"editSettings()\">\n <nh-assistant-icon name=\"edit\" />{{ 'nh-assistant.admin.run-config.settings.edit' | translate }}\n </button>\n @if (isConfirming('remove-settings', config.agentId)) {\n <span class=\"muted\">{{ 'nh-assistant.admin.run-config.settings.remove-explain' | translate }}</span>\n <button class=\"danger\" type=\"button\" [disabled]=\"busy()\" (click)=\"removeSettings()\">{{ 'nh-assistant.admin.confirm-delete' | translate }}</button>\n <button class=\"secondary\" type=\"button\" (click)=\"confirming.set(null)\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n } @else {\n <button class=\"danger\" type=\"button\" [disabled]=\"busy()\" (click)=\"confirm('remove-settings', config.agentId)\">\n <nh-assistant-icon name=\"trash\" />{{ 'nh-assistant.admin.run-config.settings.remove' | translate }}\n </button>\n }\n </div>\n @if (failed('settings')) {\n <p class=\"form-error\" role=\"alert\"><nh-assistant-icon name=\"warning\" />{{ errorKeys() | nhAssistantTranslate: error()!.code }}</p>\n }\n } @else {\n <div class=\"state\" data-setup-offer>\n <p>{{ 'nh-assistant.admin.run-config.settings.empty' | translate }}</p>\n <div class=\"card-actions\">\n <button class=\"primary\" type=\"button\" data-open-form=\"setup\" [disabled]=\"busy()\" (click)=\"openSetup()\">\n <nh-assistant-icon name=\"plus\" />{{ 'nh-assistant.admin.run-config.setup.start' | translate }}\n </button>\n <button class=\"secondary\" type=\"button\" data-open-form=\"settings\" [disabled]=\"busy()\" (click)=\"editSettings()\">\n {{ 'nh-assistant.admin.run-config.settings.add' | translate }}\n </button>\n </div>\n </div>\n }\n </section>\n }\n\n <section class=\"subsection\" [attr.aria-labelledby]=\"id + '-skills-title'\" data-skills>\n <header class=\"section-header\">\n <div>\n <h4 [id]=\"id + '-skills-title'\">{{ 'nh-assistant.admin.agents.skills.title' | translate }}</h4>\n <p class=\"muted\">{{ 'nh-assistant.admin.run-config.skills.description' | translate }}</p>\n </div>\n <button class=\"secondary\" type=\"button\" data-open-form=\"skill\" [disabled]=\"busy() || !canAddRunData() || form() === 'skill'\" (click)=\"addSkill()\">\n <nh-assistant-icon name=\"plus\" />{{ 'nh-assistant.admin.run-config.skills.add' | translate }}\n </button>\n </header>\n @if (!canAddRunData()) {\n <p class=\"muted\">{{ 'nh-assistant.admin.run-config.needs-settings' | translate }}</p>\n }\n\n @if (form() === 'skill') {\n <nh-assistant-skill-form\n [agentId]=\"config.agentId\"\n [catalog]=\"catalog()\"\n [toolSelectors]=\"config.toolSelectors\"\n [toolCatalog]=\"tools()\"\n [skill]=\"editingSkill()\"\n [takenIds]=\"skillIds()\"\n (saved)=\"onSkillSaved($event)\"\n (cancelled)=\"cancel()\" />\n }\n\n @if (config.codeSkills.length === 0 && config.dataSkills.length === 0) {\n <p class=\"state\">{{ 'nh-assistant.admin.run-config.skills.empty' | translate }}</p>\n } @else {\n <ul class=\"skills\">\n @for (skill of config.codeSkills; track skill.skillId) {\n <li class=\"skill\" [attr.data-code-skill]=\"skill.skillId\">\n <div class=\"card-header\">\n <strong>{{ (skillNameKeys(config.agentId, skill.skillId) | nhAssistantTranslate: readableId(skill.skillId)) }}</strong>\n <span class=\"mono muted\">{{ skill.skillId }}</span>\n <span class=\"badge\">{{ 'nh-assistant.admin.agents.source-code' | translate }}</span>\n </div>\n <p class=\"muted\">{{ skillDescriptionKeys(config.agentId, skill.skillId) | nhAssistantTranslate: skill.description }}</p>\n </li>\n }\n @for (skill of config.dataSkills; track skill.skillId) {\n <li class=\"skill\" [attr.data-data-skill]=\"skill.skillId\">\n <div class=\"card-header\">\n <strong>{{ (skillNameKeys(config.agentId, skill.skillId) | nhAssistantTranslate: readableId(skill.skillId)) }}</strong>\n <span class=\"mono muted\">{{ skill.skillId }}</span>\n <span class=\"badge\">{{ 'nh-assistant.admin.agents.version' | translate: { version: skill.version } }}</span>\n @if (!skill.inEffect) {\n <span class=\"badge danger\">{{ 'nh-assistant.admin.run-config.out-of-effect' | translate }}</span>\n }\n @if (skill.candidate) {\n <span class=\"badge warning\" data-candidate-badge>{{ 'nh-assistant.admin.run-config.candidate.badge' | translate: { version: skill.candidate.version } }}</span>\n }\n </div>\n <p class=\"muted\">{{ skill.description }}</p>\n <div class=\"meta\">\n <span>{{ skill.ruleHandler ? ('nh-assistant.admin.run-config.rule-handler-value' | translate: { name: skill.ruleHandler }) : ('nh-assistant.admin.run-config.model' | translate) }}</span>\n @if (skill.profile) {\n <span>{{ 'nh-assistant.admin.run-config.profile-value' | translate: { name: skill.profile } }}</span>\n }\n <span>{{ (skill.tools.length === 1 ? 'nh-assistant.admin.agents.tool-count-one' : 'nh-assistant.admin.agents.tool-count') | translate: { count: skill.tools.length } }}</span>\n <span [attr.title]=\"accountTitle(skill.changedBy)\">{{ 'nh-assistant.admin.run-config.changed' | translate: { user: nameOf(skill.changedBy), date: (skill.changedAt | date: 'medium') } }}</span>\n </div>\n @if (skill.candidate; as candidate) {\n <div class=\"subsection\" data-candidate>\n <p class=\"muted\">{{ 'nh-assistant.admin.run-config.candidate.explain' | translate: { version: candidate.version, description: candidate.description } }}</p>\n <div class=\"card-actions\">\n <button class=\"secondary\" type=\"button\" [attr.aria-expanded]=\"comparison()?.skillId === skill.skillId\" (click)=\"toggleComparison(skill)\">\n <nh-assistant-icon name=\"list\" />{{ 'nh-assistant.admin.run-config.candidate.compare' | translate }}\n </button>\n @if (isConfirming('promote', skill.skillId)) {\n <button class=\"primary\" type=\"button\" [disabled]=\"busy()\" (click)=\"promote(skill)\">{{ 'nh-assistant.admin.run-config.candidate.confirm-promote' | translate }}</button>\n <button class=\"secondary\" type=\"button\" (click)=\"confirming.set(null)\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n } @else if (isConfirming('discard', skill.skillId)) {\n <button class=\"danger\" type=\"button\" [disabled]=\"busy()\" (click)=\"discard(skill)\">{{ 'nh-assistant.admin.run-config.candidate.confirm-discard' | translate }}</button>\n <button class=\"secondary\" type=\"button\" (click)=\"confirming.set(null)\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n } @else {\n <button class=\"secondary\" type=\"button\" [disabled]=\"busy()\" (click)=\"confirm('promote', skill.skillId)\">{{ 'nh-assistant.admin.run-config.candidate.promote' | translate }}</button>\n <button class=\"danger\" type=\"button\" [disabled]=\"busy()\" (click)=\"confirm('discard', skill.skillId)\">{{ 'nh-assistant.admin.run-config.candidate.discard' | translate }}</button>\n }\n </div>\n @if (comparison(); as shown) {\n @if (shown.skillId === skill.skillId) {\n @if (shown.failed) {\n <p class=\"message error\" role=\"alert\">{{ 'nh-assistant.admin.load-failed' | translate }}</p>\n } @else if (shown.result; as result) {\n <div class=\"table-scroll\">\n <table class=\"summary-table\" data-comparison>\n <caption class=\"visually-hidden\">{{ 'nh-assistant.admin.run-config.candidate.comparison' | translate: { skill: (skillNameKeys(config.agentId, skill.skillId) | nhAssistantTranslate: readableId(skill.skillId)) } }}</caption>\n <thead>\n <tr>\n <th scope=\"col\">{{ 'nh-assistant.admin.evaluation.columns.measure' | translate }}</th>\n <th scope=\"col\">{{ 'nh-assistant.admin.run-config.candidate.live-column' | translate: { version: result.live.version } }}</th>\n <th scope=\"col\">{{ 'nh-assistant.admin.run-config.candidate.candidate-column' | translate: { version: result.candidate.version } }}</th>\n </tr>\n </thead>\n <tbody>\n <tr><th scope=\"row\">{{ 'nh-assistant.admin.run-config.candidate.runs' | translate }}</th><td>{{ result.live.runs | number }}</td><td>{{ result.candidate.runs | number }}</td></tr>\n <tr><th scope=\"row\">{{ 'nh-assistant.admin.run-config.candidate.completed' | translate }}</th><td>{{ result.live.completed | number }}</td><td>{{ result.candidate.completed | number }}</td></tr>\n <tr><th scope=\"row\">{{ 'nh-assistant.admin.run-config.candidate.failed' | translate }}</th><td>{{ result.live.failed | number }}</td><td>{{ result.candidate.failed | number }}</td></tr>\n <tr><th scope=\"row\">{{ 'nh-assistant.admin.run-config.candidate.proposals' | translate }}</th><td>{{ result.live.proposals | number }}</td><td>{{ result.candidate.proposals | number }}</td></tr>\n <tr><th scope=\"row\">{{ 'nh-assistant.admin.run-config.candidate.tokens' | translate }}</th><td>{{ result.live.tokens | number }}</td><td>{{ result.candidate.tokens | number }}</td></tr>\n </tbody>\n </table>\n </div>\n <p class=\"muted\">{{ 'nh-assistant.admin.run-config.candidate.paired' | translate: { count: result.pairedEvents, date: (result.from | date: 'medium') } }}</p>\n } @else {\n <p class=\"state\" role=\"status\">{{ 'nh-assistant.admin.loading' | translate }}</p>\n }\n }\n }\n </div>\n }\n <div class=\"card-actions\">\n <button class=\"secondary\" type=\"button\" [disabled]=\"busy()\" (click)=\"editSkill(skill)\">\n <nh-assistant-icon name=\"edit\" />{{ 'nh-assistant.admin.edit' | translate }}\n </button>\n <button class=\"secondary\" type=\"button\" [attr.aria-expanded]=\"versions()?.skillId === skill.skillId\" (click)=\"toggleVersions(skill)\">\n <nh-assistant-icon name=\"list\" />{{ 'nh-assistant.admin.run-config.skills.versions' | translate }}\n </button>\n @if (isConfirming('remove-skill', skill.skillId)) {\n <span class=\"muted\">{{ 'nh-assistant.admin.run-config.skills.remove-explain' | translate }}</span>\n <button class=\"danger\" type=\"button\" [disabled]=\"busy()\" (click)=\"removeSkill(skill)\">{{ 'nh-assistant.admin.confirm-delete' | translate }}</button>\n <button class=\"secondary\" type=\"button\" (click)=\"confirming.set(null)\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n } @else {\n <button class=\"danger\" type=\"button\" [disabled]=\"busy()\" (click)=\"confirm('remove-skill', skill.skillId)\">\n <nh-assistant-icon name=\"trash\" />{{ 'nh-assistant.admin.delete' | translate }}\n </button>\n }\n </div>\n @if (failed('skill:' + skill.skillId)) {\n <p class=\"form-error\" role=\"alert\"><nh-assistant-icon name=\"warning\" />{{ errorKeys() | nhAssistantTranslate: error()!.code }}</p>\n }\n @if (versions(); as shown) {\n @if (shown.skillId === skill.skillId) {\n @if (shown.failed) {\n <p class=\"message error\" role=\"alert\">{{ 'nh-assistant.admin.load-failed' | translate }}</p>\n } @else if (shown.items; as items) {\n <ul class=\"history\" data-versions>\n @for (version of items; track version.version) {\n <li>\n <span class=\"badge\">{{ 'nh-assistant.admin.agents.version' | translate: { version: version.version } }}</span>\n <span class=\"badge\" [class.success]=\"version.status === 'live'\" [class.warning]=\"version.status === 'candidate'\">{{ 'nh-assistant.admin.run-config.status.' + version.status | translate }}</span>\n <span>{{ version.description }}</span>\n <span class=\"muted\" [attr.title]=\"accountTitle(version.createdBy)\">{{ 'nh-assistant.admin.run-config.changed' | translate: { user: nameOf(version.createdBy), date: (version.createdAt | date: 'medium') } }}</span>\n @if (version.reason) {\n <span class=\"muted\">{{ 'nh-assistant.admin.agents.state.reason' | translate: { reason: version.reason } }}</span>\n }\n </li>\n }\n </ul>\n } @else {\n <p class=\"state\" role=\"status\">{{ 'nh-assistant.admin.loading' | translate }}</p>\n }\n }\n }\n </li>\n }\n </ul>\n }\n </section>\n\n <section class=\"subsection\" [attr.aria-labelledby]=\"id + '-triggers-title'\" data-triggers>\n <header class=\"section-header\">\n <div>\n <h4 [id]=\"id + '-triggers-title'\">{{ 'nh-assistant.admin.run-config.triggers.title' | translate }}</h4>\n <p class=\"muted\">{{ 'nh-assistant.admin.run-config.triggers.description' | translate }}</p>\n </div>\n <button class=\"secondary\" type=\"button\" data-open-form=\"trigger\" [disabled]=\"busy() || !canAddRunData() || form() === 'trigger'\" (click)=\"addTrigger()\">\n <nh-assistant-icon name=\"plus\" />{{ 'nh-assistant.admin.run-config.triggers.add' | translate }}\n </button>\n </header>\n\n @if (form() === 'trigger') {\n <nh-assistant-trigger-form\n [agentId]=\"config.agentId\"\n [catalog]=\"catalog()\"\n [worksOn]=\"agent()\"\n [skills]=\"skillOptions()\"\n [trigger]=\"editingTrigger()\"\n [takenIds]=\"triggerIds()\"\n (saved)=\"onTriggerSaved($event.added)\"\n (cancelled)=\"cancel()\" />\n }\n\n @if (config.codeTriggers.length === 0 && config.dataTriggers.length === 0) {\n <p class=\"state\">{{ 'nh-assistant.admin.run-config.triggers.empty' | translate }}</p>\n } @else {\n <ul class=\"skills\">\n @for (trigger of config.codeTriggers; track trigger.triggerId) {\n <li class=\"skill\" [attr.data-code-trigger]=\"trigger.triggerId\">\n <div class=\"card-header\">\n <span class=\"mono\">{{ trigger.triggerId }}</span>\n <span class=\"badge\">{{ 'nh-assistant.admin.agents.source-code' | translate }}</span>\n <span class=\"badge\">{{ 'nh-assistant.admin.run-config.triggers.sources.' + trigger.source | translate }}</span>\n </div>\n @if (trigger.skillId) {\n <p class=\"muted\">{{ 'nh-assistant.admin.run-config.triggers.skill-value' | translate: { skill: skillName(trigger.skillId) } }}</p>\n }\n </li>\n }\n @for (trigger of config.dataTriggers; track trigger.triggerId) {\n <li class=\"skill\" [attr.data-data-trigger]=\"trigger.triggerId\">\n <div class=\"card-header\">\n <span class=\"mono\">{{ trigger.triggerId }}</span>\n <span class=\"badge\">{{ 'nh-assistant.admin.run-config.triggers.sources.' + trigger.source | translate }}</span>\n @if (trigger.shadow) {\n <span class=\"badge warning\" data-shadow-badge>{{ 'nh-assistant.admin.agents.shadow.badge' | translate }}</span>\n } @else {\n <span class=\"badge success\">{{ 'nh-assistant.admin.run-config.triggers.live-badge' | translate }}</span>\n }\n @if (!trigger.inEffect) {\n <span class=\"badge danger\">{{ 'nh-assistant.admin.run-config.out-of-effect' | translate }}</span>\n }\n </div>\n <div class=\"meta\">\n @if (trigger.source === 'event') {\n <span class=\"mono\">{{ trigger.event }}</span>\n } @else {\n <span class=\"mono\">{{ trigger.cron }} {{ trigger.timeZone ?? 'UTC' }}</span>\n @if (trigger.subjectSet) {\n <span class=\"mono\">{{ trigger.subjectSet }}</span>\n } @else {\n <!-- A schedule without a subject set runs for the whole application, per division for a per-division agent. -->\n <span data-trigger-target>{{ (agent().perDivision ? 'nh-assistant.admin.run-config.triggers.target.every-division' : 'nh-assistant.admin.run-config.triggers.target.application') | translate }}</span>\n }\n }\n <span>{{ (trigger.skillId ? 'nh-assistant.admin.run-config.triggers.skill-value' : 'nh-assistant.admin.run-config.triggers.agent-routes') | translate: { skill: skillName(trigger.skillId) } }}</span>\n <span>{{ 'nh-assistant.admin.run-config.triggers.conflicts.' + trigger.conflict | translate }}</span>\n <span>{{ 'nh-assistant.admin.agents.version' | translate: { version: trigger.version } }}</span>\n </div>\n <div class=\"card-actions\">\n <button class=\"secondary\" type=\"button\" [disabled]=\"busy()\" (click)=\"editTrigger(trigger)\">\n <nh-assistant-icon name=\"edit\" />{{ 'nh-assistant.admin.edit' | translate }}\n </button>\n <button class=\"secondary\" type=\"button\" [attr.data-trigger-shadow]=\"trigger.shadow ? 'live' : 'shadow'\" [disabled]=\"busy()\" (click)=\"setTriggerShadow(trigger, !trigger.shadow)\">\n {{ (trigger.shadow ? 'nh-assistant.admin.agents.shadow.action.live' : 'nh-assistant.admin.agents.shadow.action.shadow') | translate }}\n </button>\n @if (isConfirming('remove-trigger', trigger.triggerId)) {\n <button class=\"danger\" type=\"button\" [disabled]=\"busy()\" (click)=\"removeTrigger(trigger)\">{{ 'nh-assistant.admin.confirm-delete' | translate }}</button>\n <button class=\"secondary\" type=\"button\" (click)=\"confirming.set(null)\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n } @else {\n <button class=\"danger\" type=\"button\" [disabled]=\"busy()\" (click)=\"confirm('remove-trigger', trigger.triggerId)\">\n <nh-assistant-icon name=\"trash\" />{{ 'nh-assistant.admin.delete' | translate }}\n </button>\n }\n </div>\n @if (failed('trigger:' + trigger.triggerId)) {\n <p class=\"form-error\" role=\"alert\"><nh-assistant-icon name=\"warning\" />{{ errorKeys() | nhAssistantTranslate: error()!.code }}</p>\n }\n </li>\n }\n </ul>\n }\n </section>\n }\n</section>\n", styles: [":host{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){:host{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}:host{display:block;min-width:0;color:var(--nha-text);font-family:var(--nha-font);font-size:14px}:host fieldset{min-width:0;margin:0;border:0;padding:0}:host legend,:host .label,:host .field>label{display:block;margin-bottom:6px;padding:0;color:var(--nha-text);font-size:12px;font-weight:700}:host input[type=text],:host input[type=url],:host input[type=password],:host input[type=search],:host input[type=email],:host input[type=number],:host input[type=date],:host input[type=time],:host input[type=datetime-local],:host select,:host textarea{box-sizing:border-box;width:100%;min-width:0;min-height:38px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:8px 10px;color:var(--nha-text);background:var(--nha-background);font:inherit;font-size:13px}:host input[type=text]:focus-visible,:host input[type=url]:focus-visible,:host input[type=password]:focus-visible,:host input[type=search]:focus-visible,:host input[type=email]:focus-visible,:host input[type=number]:focus-visible,:host input[type=date]:focus-visible,:host input[type=time]:focus-visible,:host input[type=datetime-local]:focus-visible,:host select:focus-visible,:host textarea:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host input[type=text]:disabled,:host input[type=url]:disabled,:host input[type=password]:disabled,:host input[type=search]:disabled,:host input[type=email]:disabled,:host input[type=number]:disabled,:host input[type=date]:disabled,:host input[type=time]:disabled,:host input[type=datetime-local]:disabled,:host select:disabled,:host textarea:disabled{cursor:not-allowed;opacity:.6}:host input[type=text][aria-invalid=true],:host input[type=url][aria-invalid=true],:host input[type=password][aria-invalid=true],:host input[type=search][aria-invalid=true],:host input[type=email][aria-invalid=true],:host input[type=number][aria-invalid=true],:host input[type=date][aria-invalid=true],:host input[type=time][aria-invalid=true],:host input[type=datetime-local][aria-invalid=true],:host select[aria-invalid=true],:host textarea[aria-invalid=true]{border-color:var(--nha-danger)}:host textarea{line-height:1.45;resize:vertical}:host input[type=number]{font-variant-numeric:tabular-nums}:host .mono{font-family:var(--nha-mono);font-size:12px}:host .field{display:flex;flex-direction:column;min-width:0}:host .hint{display:flex;justify-content:space-between;gap:8px;margin:4px 2px 0;color:var(--nha-text-muted);font-size:11px}:host .counter{flex:none;font-variant-numeric:tabular-nums}:host .error-text{color:var(--nha-danger)}:host .field-error{display:flex;align-items:flex-start;gap:6px;margin:4px 2px 0;color:var(--nha-danger);font-size:12px;font-weight:600;overflow-wrap:anywhere}:host .field-error nh-assistant-icon{flex:none;margin-top:1px}:host .form-error{box-sizing:border-box;display:flex;align-items:flex-start;gap:8px;margin:0;border:1px solid var(--nha-danger);border-radius:var(--nha-radius-control);padding:8px 12px;color:var(--nha-text);background:var(--nha-danger-soft);font-size:13px;overflow-wrap:anywhere}:host .form-error ul{margin:4px 0 0;padding-left:18px}:host .message{box-sizing:border-box;display:flex;align-items:center;gap:8px;margin:0;border:1px solid transparent;border-radius:var(--nha-radius-control);padding:8px 12px;font-size:13px;overflow-wrap:anywhere}:host .message.error{border-color:var(--nha-danger);color:var(--nha-text);background:var(--nha-danger-soft)}:host .message.warning{border-color:var(--nha-warning);color:var(--nha-text);background:var(--nha-warning-soft)}:host .message.success{border-color:var(--nha-success);color:var(--nha-text);background:var(--nha-success-soft)}:host .message.info{border-color:var(--nha-border);color:var(--nha-text);background:var(--nha-surface)}:host .message nh-assistant-icon{flex:none}:host button.link{min-height:0;border:0;padding:0;color:var(--nha-accent, currentColor);background:none;font:inherit;text-decoration:underline;cursor:pointer}:host button.link:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host button.link:disabled{cursor:not-allowed;opacity:.55}:host .primary,:host .secondary,:host .danger{display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;gap:6px;min-height:36px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px 14px;font:inherit;font-size:13px;font-weight:700;white-space:nowrap;cursor:pointer}:host .primary:focus-visible,:host .secondary:focus-visible,:host .danger:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .primary:disabled,:host .secondary:disabled,:host .danger:disabled{cursor:not-allowed;opacity:.55}:host .primary{border-color:var(--nha-accent);color:var(--nha-accent-contrast);background:var(--nha-accent)}:host .primary:hover:not(:disabled){filter:brightness(1.12)}:host .secondary{color:var(--nha-text);background:transparent}:host .secondary:hover:not(:disabled){background:var(--nha-surface-strong)}:host .danger{border-color:var(--nha-danger);color:var(--nha-danger);background:transparent}:host .danger:hover:not(:disabled){background:var(--nha-danger-soft)}:host .icon-button{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:1px solid transparent;border-radius:var(--nha-radius-control);color:var(--nha-text-muted);background:transparent;cursor:pointer}:host .icon-button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .icon-button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}:host .icon-button:disabled{cursor:not-allowed;opacity:.5}:host .actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px}:host .badge{display:inline-flex;align-items:center;gap:4px;border-radius:999px;padding:2px 8px;color:var(--nha-text-muted);background:var(--nha-surface-strong);font-size:11px;font-weight:700;white-space:nowrap}:host .badge.success{color:var(--nha-success);background:var(--nha-success-soft)}:host .badge.warning{color:var(--nha-warning);background:var(--nha-warning-soft)}:host .badge.danger{color:var(--nha-danger);background:var(--nha-danger-soft)}:host .visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}.section{display:flex;flex-direction:column;gap:16px;min-width:0}.section-header{display:flex;flex-wrap:wrap;align-items:flex-start;justify-content:space-between;gap:12px}.section-header>div{min-width:0;max-width:72ch}h3{margin:0 0 4px;font-size:17px;font-weight:750}h4{margin:0 0 8px;font-size:14px;font-weight:700}.muted{margin:0;color:var(--nha-text-muted);font-size:13px}.state{display:flex;flex-direction:column;align-items:flex-start;gap:10px;margin:0;border:1px dashed var(--nha-border);border-radius:var(--nha-radius);padding:20px;color:var(--nha-text-muted);font-size:13px}.state p{margin:0}.stack{display:flex;flex-direction:column;gap:14px;min-width:0}.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,240px),1fr));gap:14px}.meta{display:flex;flex-wrap:wrap;align-items:center;gap:8px 12px;color:var(--nha-text-muted);font-size:12px;overflow-wrap:anywhere}textarea.large{min-height:260px}.catalog-error{flex-wrap:wrap}.grow{flex:1;min-width:0}.subsection{display:flex;flex-direction:column;gap:8px;border-top:1px solid var(--nha-border);padding-top:14px}.history{display:grid;gap:6px;margin:0;padding:0;list-style:none}.history li{display:flex;flex-wrap:wrap;align-items:center;gap:6px 12px;font-size:12px}.cards{display:grid;gap:10px;margin:0;padding:0;list-style:none}.card{display:flex;flex-direction:column;gap:8px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius);padding:14px;background:var(--nha-background)}.card.inactive{opacity:.8}.card-header{display:flex;flex-wrap:wrap;align-items:center;gap:8px;min-width:0}.card-header strong{font-size:14px;overflow-wrap:anywhere}.card-actions{display:flex;flex-wrap:wrap;gap:8px}.check{display:flex;align-items:flex-start;gap:8px;font-size:13px;cursor:pointer}.check input{width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.check.described>span{display:grid;gap:2px;min-width:0;overflow-wrap:anywhere}.checks{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));gap:6px 12px;min-width:0;overflow-wrap:anywhere}.chips{display:flex;flex-wrap:wrap;gap:6px;margin:0;padding:0;list-style:none}.chip{display:inline-flex;align-items:center;gap:2px;border:1px solid var(--nha-border);border-radius:999px;padding:2px 2px 2px 10px;background:var(--nha-surface);font-family:var(--nha-mono);font-size:12px}.chip 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;width:26px;height:26px;border-radius:999px}.chip button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.chip button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}.chip button:disabled{cursor:not-allowed;opacity:.5}.inline{display:flex;flex-wrap:wrap;gap:8px}.inline>input{flex:1 1 180px}.catalog{display:grid;gap:4px;max-height:260px;margin:8px 0 0;overflow-y:auto;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px;list-style:none}.catalog li label{display:flex;flex-wrap:wrap;align-items:center;gap:6px 8px;border-radius:8px;padding:6px;cursor:pointer}.catalog li label:hover{background:var(--nha-surface)}.catalog .tool-id{font-family:var(--nha-mono);font-size:12px;overflow-wrap:anywhere}.skills{display:grid;gap:8px;margin:0;padding:0;list-style:none}.skill{display:grid;gap:6px;border-left:3px solid var(--nha-border);padding:4px 0 4px 12px}.uses{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));gap:8px}.use{display:flex;align-items:flex-start;gap:10px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:10px 12px;cursor:pointer;overflow-wrap:anywhere}.use input{flex:none;width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.use>span{display:grid;gap:2px;min-width:0}.use strong{font-size:13px}.use.selected{border-color:var(--nha-highlight);background:var(--nha-surface)}.use:focus-within{outline:2px solid var(--nha-highlight);outline-offset:2px}.discovery{display:grid;gap:8px;padding:10px 12px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control)}.discovery dl{display:grid;grid-template-columns:max-content minmax(0,1fr);gap:4px 12px;margin:0}.discovery dt{color:var(--nha-text-muted)}.discovery dd{margin:0;overflow-wrap:anywhere}\n", ".header-actions{display:flex;flex-wrap:wrap;gap:8px}.export-hint{margin-top:4px;font-size:12px}.filters{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));align-items:start;gap:12px}.subsection h4,.list-header h4{margin:0}.list-header{display:flex;flex-wrap:wrap;align-items:baseline;justify-content:space-between;gap:4px 12px}.table-container{container-type:inline-size;min-width:0}.summary-table{width:100%;border-collapse:collapse;font-size:13px}.summary-table th,.summary-table td{border-bottom:1px solid var(--nha-border);padding:8px 10px;text-align:left;vertical-align:top}.summary-table thead th{color:var(--nha-text-muted);font-size:11px;font-weight:700;letter-spacing:.04em;text-transform:uppercase}.summary-table tbody th{font-weight:600}.summary-table .number{text-align:right;font-variant-numeric:tabular-nums}.summary-table .mono{overflow-wrap:anywhere}@container (max-width: 820px){.summary-table thead{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}.summary-table,.summary-table tbody,.summary-table tr{display:block}.summary-table tr{margin-bottom:8px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px 12px;background:var(--nha-background)}.summary-table tbody th,.summary-table td{display:grid;grid-template-columns:minmax(110px,40%) minmax(0,1fr);gap:8px;border:0;padding:4px 0;text-align:left}.summary-table tbody th:before,.summary-table td:before{content:attr(data-label);color:var(--nha-text-muted);font-size:12px;font-weight:700}}.badges{display:flex;flex-wrap:wrap;gap:4px;margin:0;padding:0;list-style:none}.badge .count{font-variant-numeric:tabular-nums;opacity:.85}.feedback-text{margin:0;border-left:3px solid var(--nha-border);padding:2px 0 2px 10px;font-size:13px;white-space:pre-wrap;overflow-wrap:anywhere}.proposal{margin:0;color:var(--nha-text-muted);font-size:12px;overflow-wrap:anywhere}.proposal-label{margin-right:4px;color:var(--nha-text);font-weight:700}.pager{display:flex;flex-wrap:wrap;align-items:center;gap:8px}\n", "h5{margin:0;font-size:15px;font-weight:750}.group{display:flex;flex-direction:column;gap:10px;min-width:0;border-top:1px solid var(--nha-border);padding-top:14px}.group>legend{float:left;width:100%;margin-bottom:2px;font-size:14px;font-weight:750}.choices{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,220px),1fr));gap:8px}.choice{display:flex;align-items:flex-start;gap:10px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:10px 12px;cursor:pointer;overflow-wrap:anywhere}.choice input{flex:none;width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.choice>span{display:grid;gap:2px;min-width:0}.choice strong{font-size:13px}.choice.selected{border-color:var(--nha-highlight);background:var(--nha-surface)}.choice:focus-within{outline:2px solid var(--nha-highlight);outline-offset:2px}.block{display:block;margin-top:2px}.advanced{border-top:1px solid var(--nha-border);padding-top:12px}.advanced>summary{width:fit-content;border-radius:6px;font-size:14px;font-weight:750;cursor:pointer}.advanced>summary:focus-visible{outline:2px solid var(--nha-highlight);outline-offset:2px}.advanced>.stack{margin-top:12px}.examples{display:flex;flex-wrap:wrap;gap:6px;margin:6px 0 0;padding:0;list-style:none}.examples button{border:1px solid var(--nha-border);border-radius:999px;padding:3px 10px;color:var(--nha-text);background:transparent;font:inherit;font-size:12px;cursor:pointer}.examples button:hover{background:var(--nha-surface-strong)}.examples button:focus-visible{outline:2px solid var(--nha-highlight);outline-offset:2px}.steps{display:flex;flex-wrap:wrap;gap:6px;margin:0;padding:0;list-style:none}.steps li{display:inline-flex;align-items:center;gap:6px;border-radius:999px;padding:4px 10px;color:var(--nha-text-muted);background:var(--nha-surface-strong);font-size:12px;font-weight:700}.steps li[aria-current=step]{color:var(--nha-accent-contrast);background:var(--nha-accent)}.steps li.done{color:var(--nha-success);background:var(--nha-success-soft)}.review{display:grid;grid-template-columns:minmax(0,max-content) minmax(0,1fr);gap:6px 16px;margin:0;font-size:13px}.review dt{color:var(--nha-text-muted)}.review dd{margin:0;overflow-wrap:anywhere}.tool-choice{display:flex;flex-direction:column;gap:2px;min-width:0;overflow-wrap:anywhere}@media(max-width:520px){.review{grid-template-columns:minmax(0,1fr)}.review dd{margin-bottom:6px}}.table-scroll{max-width:100%;overflow-x:auto}\n"], dependencies: [{ kind: "component", type: _NhAssistantIconComponent, selector: "nh-assistant-icon", inputs: ["name"] }, { kind: "component", type: NhAssistantAgentFlowComponent, selector: "nh-assistant-agent-flow", inputs: ["agent", "configuration", "catalog", "tools", "servers", "accountName", "busy"], outputs: ["entry"] }, { kind: "component", type: NhAssistantAgentWorkSetupComponent, selector: "nh-assistant-agent-work-setup", inputs: ["agentId", "agentName", "catalog", "worksOn", "toolSelectors", "toolCatalog", "takenSkillIds", "takenTriggerIds"], outputs: ["completed", "cancelled"] }, { kind: "component", type: NhAssistantRunSettingsFormComponent, selector: "nh-assistant-run-settings-form", inputs: ["agentId", "catalog", "settings"], outputs: ["saved", "cancelled"] }, { kind: "component", type: NhAssistantSkillFormComponent, selector: "nh-assistant-skill-form", inputs: ["agentId", "catalog", "toolSelectors", "toolCatalog", "skill", "takenIds"], outputs: ["saved", "cancelled"] }, { kind: "component", type: NhAssistantTriggerFormComponent, selector: "nh-assistant-trigger-form", inputs: ["agentId", "catalog", "worksOn", "skills", "trigger", "takenIds"], outputs: ["saved", "cancelled"] }, { kind: "component", type: NhAssistantAccountNamesErrorComponent, selector: "nh-assistant-account-names-error", inputs: ["names"] }, { kind: "component", type: NhAssistantAccountLabelComponent, selector: "nh-assistant-account-label", inputs: ["actorId", "name"] }, { kind: "pipe", type: DatePipe, name: "date" }, { kind: "pipe", type: DecimalPipe, name: "number" }, { kind: "pipe", type: TranslatePipe, name: "translate" }, { kind: "pipe", type: _NhAssistantTranslatePipe, name: "nhAssistantTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
3558
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.34", type: NhAssistantAgentRunConfigurationComponent, isStandalone: true, selector: "nh-assistant-agent-run-configuration", inputs: { agent: { classPropertyName: "agent", publicName: "agent", isSignal: true, isRequired: true, transformFunction: null }, startSetup: { classPropertyName: "startSetup", publicName: "startSetup", isSignal: true, isRequired: false, transformFunction: null }, tools: { classPropertyName: "tools", publicName: "tools", isSignal: true, isRequired: false, transformFunction: null }, servers: { classPropertyName: "servers", publicName: "servers", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { changed: "changed", editAgent: "editAgent", changeAccount: "changeAccount", openRuns: "openRuns", openMcpServers: "openMcpServers" }, ngImport: i0, template: "<section class=\"section\" [attr.aria-labelledby]=\"id + '-title'\" [attr.aria-busy]=\"loading() || busy()\">\n <header class=\"section-header\">\n <div>\n <h3 [id]=\"id + '-title'\">{{ 'nh-assistant.admin.run-config.title' | translate: { name: agentName() } }}</h3>\n <p class=\"muted\">{{ 'nh-assistant.admin.run-config.description' | translate }}</p>\n </div>\n </header>\n\n @if (saved(); as key) {\n <p class=\"message success\" role=\"status\" tabindex=\"-1\" data-saved>{{ key | translate }}</p>\n <!-- The save succeeded; a warning says what may still keep the skill from working. -->\n @for (warning of savedWarnings(); track warning) {\n <p class=\"message warning\" role=\"status\" [attr.data-saved-warning]=\"warning\">\n <nh-assistant-icon name=\"warning\" />{{ warningKeys('skill', warning) | nhAssistantTranslate: warning }}\n </p>\n }\n }\n\n @if (loading() && !configuration()) {\n <p class=\"state\" role=\"status\">{{ 'nh-assistant.admin.loading' | translate }}</p>\n } @else if (loadFailed()) {\n <div class=\"state\" role=\"alert\">\n <p>{{ 'nh-assistant.admin.load-failed' | translate }}</p>\n <button class=\"secondary\" type=\"button\" (click)=\"load()\">{{ 'nh-assistant.admin.retry' | translate }}</button>\n </div>\n } @else if (configuration(); as config) {\n <nh-assistant-account-names-error [names]=\"accountNames\" />\n @if (problems().length > 0) {\n <!-- Each problem shows at the step of the flow it concerns, with the form that fixes it. -->\n <p class=\"message warning\" role=\"status\" data-problems>\n <nh-assistant-icon name=\"warning\" />{{ 'nh-assistant.admin.run-config.flow.problems' | translate }}\n </p>\n }\n\n <!-- The agent at a glance; the forms below change what each step shows. -->\n <nh-assistant-agent-flow\n [agent]=\"agent()\"\n [configuration]=\"config\"\n [catalog]=\"catalog()\"\n [tools]=\"tools()\"\n [servers]=\"servers()\"\n [accountName]=\"accountName()\"\n [busy]=\"busy()\"\n (entry)=\"onFlowEntry($event)\" />\n\n @if (config.source === 'admin') {\n <section class=\"subsection\" [attr.aria-labelledby]=\"id + '-settings-title'\" data-run-settings>\n <header class=\"section-header\">\n <h4 [id]=\"id + '-settings-title'\">{{ 'nh-assistant.admin.run-config.settings.title' | translate }}</h4>\n </header>\n @if (form() === 'setup') {\n <nh-assistant-agent-work-setup\n [agentId]=\"config.agentId\"\n [agentName]=\"agentName()\"\n [catalog]=\"catalog()\"\n [worksOn]=\"agent()\"\n [toolSelectors]=\"config.toolSelectors\"\n [toolCatalog]=\"tools()\"\n [takenSkillIds]=\"skillIds()\"\n [takenTriggerIds]=\"triggerIds()\"\n (completed)=\"onSetupCompleted($event.live)\"\n (cancelled)=\"cancel()\" />\n } @else if (form() === 'settings') {\n <nh-assistant-run-settings-form\n [agentId]=\"config.agentId\"\n [catalog]=\"catalog()\"\n [settings]=\"config.runSettings\"\n (saved)=\"onSettingsSaved()\"\n (cancelled)=\"cancel()\" />\n } @else if (config.runSettings; as settings) {\n <dl class=\"review\" data-settings-summary>\n <dt>{{ 'nh-assistant.admin.run-config.settings.who.short' | translate }}</dt>\n <dd>\n {{ 'nh-assistant.admin.run-config.settings.accountability.' + settings.accountability | translate }}\n @if (settings.serviceAccountActorId; as accountId) {\n <span class=\"block\" data-account-name>\n <nh-assistant-account-label [actorId]=\"accountId\" [name]=\"accountName()\" />\n </span>\n }\n </dd>\n <dt>{{ 'nh-assistant.admin.run-config.way-of-working' | translate }}</dt>\n <dd>{{ settings.ruleHandler ?? ('nh-assistant.admin.run-config.model' | translate) }}</dd>\n <dt>{{ 'nh-assistant.admin.run-config.approval-policy' | translate }}</dt>\n <dd>\n {{ settings.approvalPolicy ?? ('nh-assistant.admin.run-config.settings.approval.default' | translate) }}\n @if (settingsPolicy(); as sentence) {\n <span class=\"muted block\">{{ sentence }}</span>\n }\n </dd>\n <dt>{{ 'nh-assistant.admin.agents.version-label' | translate }}</dt>\n <dd>\n {{ 'nh-assistant.admin.agents.version' | translate: { version: settings.version } }}\n @if (!settings.inEffect) {\n <span class=\"badge danger\">{{ 'nh-assistant.admin.run-config.out-of-effect' | translate }}</span>\n }\n </dd>\n </dl>\n <p class=\"muted\">{{ 'nh-assistant.admin.run-config.settings.rights' | translate }}</p>\n <div class=\"card-actions\">\n <button class=\"secondary\" type=\"button\" data-open-form=\"settings\" [disabled]=\"busy()\" (click)=\"editSettings()\">\n <nh-assistant-icon name=\"edit\" />{{ 'nh-assistant.admin.run-config.settings.edit' | translate }}\n </button>\n @if (isConfirming('remove-settings', config.agentId)) {\n <span class=\"muted\">{{ 'nh-assistant.admin.run-config.settings.remove-explain' | translate }}</span>\n <button class=\"danger\" type=\"button\" [disabled]=\"busy()\" (click)=\"removeSettings()\">{{ 'nh-assistant.admin.confirm-delete' | translate }}</button>\n <button class=\"secondary\" type=\"button\" (click)=\"confirming.set(null)\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n } @else {\n <button class=\"danger\" type=\"button\" [disabled]=\"busy()\" (click)=\"confirm('remove-settings', config.agentId)\">\n <nh-assistant-icon name=\"trash\" />{{ 'nh-assistant.admin.run-config.settings.remove' | translate }}\n </button>\n }\n </div>\n @if (failed('settings')) {\n <p class=\"form-error\" role=\"alert\"><nh-assistant-icon name=\"warning\" />{{ errorKeys() | nhAssistantTranslate: error()!.code }}</p>\n }\n } @else {\n <div class=\"state\" data-setup-offer>\n <p>{{ 'nh-assistant.admin.run-config.settings.empty' | translate }}</p>\n <div class=\"card-actions\">\n <button class=\"primary\" type=\"button\" data-open-form=\"setup\" [disabled]=\"busy()\" (click)=\"openSetup()\">\n <nh-assistant-icon name=\"plus\" />{{ 'nh-assistant.admin.run-config.setup.start' | translate }}\n </button>\n <button class=\"secondary\" type=\"button\" data-open-form=\"settings\" [disabled]=\"busy()\" (click)=\"editSettings()\">\n {{ 'nh-assistant.admin.run-config.settings.add' | translate }}\n </button>\n </div>\n </div>\n }\n </section>\n }\n\n <section class=\"subsection\" [attr.aria-labelledby]=\"id + '-skills-title'\" data-skills>\n <header class=\"section-header\">\n <div>\n <h4 [id]=\"id + '-skills-title'\">{{ 'nh-assistant.admin.agents.skills.title' | translate }}</h4>\n <p class=\"muted\">{{ 'nh-assistant.admin.run-config.skills.description' | translate }}</p>\n </div>\n <button class=\"secondary\" type=\"button\" data-open-form=\"skill\" [disabled]=\"busy() || !canAddRunData() || form() === 'skill'\" (click)=\"addSkill()\">\n <nh-assistant-icon name=\"plus\" />{{ 'nh-assistant.admin.run-config.skills.add' | translate }}\n </button>\n </header>\n @if (!canAddRunData()) {\n <p class=\"muted\">{{ 'nh-assistant.admin.run-config.needs-settings' | translate }}</p>\n }\n\n @if (form() === 'skill') {\n <nh-assistant-skill-form\n [agentId]=\"config.agentId\"\n [catalog]=\"catalog()\"\n [toolSelectors]=\"config.toolSelectors\"\n [toolCatalog]=\"tools()\"\n [skill]=\"editingSkill()\"\n [takenIds]=\"skillIds()\"\n (saved)=\"onSkillSaved($event)\"\n (cancelled)=\"cancel()\" />\n }\n\n @if (config.codeSkills.length === 0 && config.dataSkills.length === 0) {\n <p class=\"state\">{{ 'nh-assistant.admin.run-config.skills.empty' | translate }}</p>\n } @else {\n <ul class=\"skills\">\n @for (skill of config.codeSkills; track skill.skillId) {\n <li class=\"skill\" [attr.data-code-skill]=\"skill.skillId\">\n <div class=\"card-header\">\n <strong>{{ (skillNameKeys(config.agentId, skill.skillId) | nhAssistantTranslate: readableId(skill.skillId)) }}</strong>\n <span class=\"mono muted\">{{ skill.skillId }}</span>\n <span class=\"badge\">{{ 'nh-assistant.admin.agents.source-code' | translate }}</span>\n </div>\n <p class=\"muted\">{{ skillDescriptionKeys(config.agentId, skill.skillId) | nhAssistantTranslate: skill.description }}</p>\n </li>\n }\n @for (skill of config.dataSkills; track skill.skillId) {\n <li class=\"skill\" [attr.data-data-skill]=\"skill.skillId\">\n <div class=\"card-header\">\n <strong>{{ (skillNameKeys(config.agentId, skill.skillId) | nhAssistantTranslate: readableId(skill.skillId)) }}</strong>\n <span class=\"mono muted\">{{ skill.skillId }}</span>\n <span class=\"badge\">{{ 'nh-assistant.admin.agents.version' | translate: { version: skill.version } }}</span>\n @if (!skill.inEffect) {\n <span class=\"badge danger\">{{ 'nh-assistant.admin.run-config.out-of-effect' | translate }}</span>\n }\n @if (skill.candidate) {\n <span class=\"badge warning\" data-candidate-badge>{{ 'nh-assistant.admin.run-config.candidate.badge' | translate: { version: skill.candidate.version } }}</span>\n }\n </div>\n <p class=\"muted\">{{ skill.description }}</p>\n @for (warning of skill.warnings ?? []; track warning) {\n <p class=\"message warning\" [attr.data-skill-warning]=\"warning\">\n <nh-assistant-icon name=\"warning\" />{{ warningKeys('skill', warning) | nhAssistantTranslate: warning }}\n </p>\n }\n <div class=\"meta\">\n <span>{{ skill.ruleHandler ? ('nh-assistant.admin.run-config.rule-handler-value' | translate: { name: skill.ruleHandler }) : ('nh-assistant.admin.run-config.model' | translate) }}</span>\n @if (skill.profile) {\n <span>{{ 'nh-assistant.admin.run-config.profile-value' | translate: { name: skill.profile } }}</span>\n }\n <span>{{ (skill.tools.length === 1 ? 'nh-assistant.admin.agents.tool-count-one' : 'nh-assistant.admin.agents.tool-count') | translate: { count: skill.tools.length } }}</span>\n <span [attr.title]=\"accountTitle(skill.changedBy)\">{{ 'nh-assistant.admin.run-config.changed' | translate: { user: nameOf(skill.changedBy), date: (skill.changedAt | date: 'medium') } }}</span>\n </div>\n @if (skill.candidate; as candidate) {\n <div class=\"subsection\" data-candidate>\n <p class=\"muted\">{{ 'nh-assistant.admin.run-config.candidate.explain' | translate: { version: candidate.version, description: candidate.description } }}</p>\n <div class=\"card-actions\">\n <button class=\"secondary\" type=\"button\" [attr.aria-expanded]=\"comparison()?.skillId === skill.skillId\" (click)=\"toggleComparison(skill)\">\n <nh-assistant-icon name=\"list\" />{{ 'nh-assistant.admin.run-config.candidate.compare' | translate }}\n </button>\n @if (isConfirming('promote', skill.skillId)) {\n <button class=\"primary\" type=\"button\" [disabled]=\"busy()\" (click)=\"promote(skill)\">{{ 'nh-assistant.admin.run-config.candidate.confirm-promote' | translate }}</button>\n <button class=\"secondary\" type=\"button\" (click)=\"confirming.set(null)\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n } @else if (isConfirming('discard', skill.skillId)) {\n <button class=\"danger\" type=\"button\" [disabled]=\"busy()\" (click)=\"discard(skill)\">{{ 'nh-assistant.admin.run-config.candidate.confirm-discard' | translate }}</button>\n <button class=\"secondary\" type=\"button\" (click)=\"confirming.set(null)\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n } @else {\n <button class=\"secondary\" type=\"button\" [disabled]=\"busy()\" (click)=\"confirm('promote', skill.skillId)\">{{ 'nh-assistant.admin.run-config.candidate.promote' | translate }}</button>\n <button class=\"danger\" type=\"button\" [disabled]=\"busy()\" (click)=\"confirm('discard', skill.skillId)\">{{ 'nh-assistant.admin.run-config.candidate.discard' | translate }}</button>\n }\n </div>\n @if (comparison(); as shown) {\n @if (shown.skillId === skill.skillId) {\n @if (shown.failed) {\n <p class=\"message error\" role=\"alert\">{{ 'nh-assistant.admin.load-failed' | translate }}</p>\n } @else if (shown.result; as result) {\n <div class=\"table-scroll\">\n <table class=\"summary-table\" data-comparison>\n <caption class=\"visually-hidden\">{{ 'nh-assistant.admin.run-config.candidate.comparison' | translate: { skill: (skillNameKeys(config.agentId, skill.skillId) | nhAssistantTranslate: readableId(skill.skillId)) } }}</caption>\n <thead>\n <tr>\n <th scope=\"col\">{{ 'nh-assistant.admin.evaluation.columns.measure' | translate }}</th>\n <th scope=\"col\">{{ 'nh-assistant.admin.run-config.candidate.live-column' | translate: { version: result.live.version } }}</th>\n <th scope=\"col\">{{ 'nh-assistant.admin.run-config.candidate.candidate-column' | translate: { version: result.candidate.version } }}</th>\n </tr>\n </thead>\n <tbody>\n <tr><th scope=\"row\">{{ 'nh-assistant.admin.run-config.candidate.runs' | translate }}</th><td>{{ result.live.runs | number }}</td><td>{{ result.candidate.runs | number }}</td></tr>\n <tr><th scope=\"row\">{{ 'nh-assistant.admin.run-config.candidate.completed' | translate }}</th><td>{{ result.live.completed | number }}</td><td>{{ result.candidate.completed | number }}</td></tr>\n <tr><th scope=\"row\">{{ 'nh-assistant.admin.run-config.candidate.failed' | translate }}</th><td>{{ result.live.failed | number }}</td><td>{{ result.candidate.failed | number }}</td></tr>\n <tr><th scope=\"row\">{{ 'nh-assistant.admin.run-config.candidate.proposals' | translate }}</th><td>{{ result.live.proposals | number }}</td><td>{{ result.candidate.proposals | number }}</td></tr>\n <tr><th scope=\"row\">{{ 'nh-assistant.admin.run-config.candidate.tokens' | translate }}</th><td>{{ result.live.tokens | number }}</td><td>{{ result.candidate.tokens | number }}</td></tr>\n </tbody>\n </table>\n </div>\n <p class=\"muted\">{{ 'nh-assistant.admin.run-config.candidate.paired' | translate: { count: result.pairedEvents, date: (result.from | date: 'medium') } }}</p>\n } @else {\n <p class=\"state\" role=\"status\">{{ 'nh-assistant.admin.loading' | translate }}</p>\n }\n }\n }\n </div>\n }\n <div class=\"card-actions\">\n <button class=\"secondary\" type=\"button\" [disabled]=\"busy()\" (click)=\"editSkill(skill)\">\n <nh-assistant-icon name=\"edit\" />{{ 'nh-assistant.admin.edit' | translate }}\n </button>\n <button class=\"secondary\" type=\"button\" [attr.aria-expanded]=\"versions()?.skillId === skill.skillId\" (click)=\"toggleVersions(skill)\">\n <nh-assistant-icon name=\"list\" />{{ 'nh-assistant.admin.run-config.skills.versions' | translate }}\n </button>\n @if (isConfirming('remove-skill', skill.skillId)) {\n <span class=\"muted\">{{ 'nh-assistant.admin.run-config.skills.remove-explain' | translate }}</span>\n <button class=\"danger\" type=\"button\" [disabled]=\"busy()\" (click)=\"removeSkill(skill)\">{{ 'nh-assistant.admin.confirm-delete' | translate }}</button>\n <button class=\"secondary\" type=\"button\" (click)=\"confirming.set(null)\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n } @else {\n <button class=\"danger\" type=\"button\" [disabled]=\"busy()\" (click)=\"confirm('remove-skill', skill.skillId)\">\n <nh-assistant-icon name=\"trash\" />{{ 'nh-assistant.admin.delete' | translate }}\n </button>\n }\n </div>\n @if (failed('skill:' + skill.skillId)) {\n <p class=\"form-error\" role=\"alert\"><nh-assistant-icon name=\"warning\" />{{ errorKeys() | nhAssistantTranslate: error()!.code }}</p>\n }\n @if (versions(); as shown) {\n @if (shown.skillId === skill.skillId) {\n @if (shown.failed) {\n <p class=\"message error\" role=\"alert\">{{ 'nh-assistant.admin.load-failed' | translate }}</p>\n } @else if (shown.items; as items) {\n <ul class=\"history\" data-versions>\n @for (version of items; track version.version) {\n <li>\n <span class=\"badge\">{{ 'nh-assistant.admin.agents.version' | translate: { version: version.version } }}</span>\n <span class=\"badge\" [class.success]=\"version.status === 'live'\" [class.warning]=\"version.status === 'candidate'\">{{ 'nh-assistant.admin.run-config.status.' + version.status | translate }}</span>\n <span>{{ version.description }}</span>\n <span class=\"muted\" [attr.title]=\"accountTitle(version.createdBy)\">{{ 'nh-assistant.admin.run-config.changed' | translate: { user: nameOf(version.createdBy), date: (version.createdAt | date: 'medium') } }}</span>\n @if (version.reason) {\n <span class=\"muted\">{{ 'nh-assistant.admin.agents.state.reason' | translate: { reason: version.reason } }}</span>\n }\n </li>\n }\n </ul>\n } @else {\n <p class=\"state\" role=\"status\">{{ 'nh-assistant.admin.loading' | translate }}</p>\n }\n }\n }\n </li>\n }\n </ul>\n }\n </section>\n\n <section class=\"subsection\" [attr.aria-labelledby]=\"id + '-triggers-title'\" data-triggers>\n <header class=\"section-header\">\n <div>\n <h4 [id]=\"id + '-triggers-title'\">{{ 'nh-assistant.admin.run-config.triggers.title' | translate }}</h4>\n <p class=\"muted\">{{ 'nh-assistant.admin.run-config.triggers.description' | translate }}</p>\n </div>\n <button class=\"secondary\" type=\"button\" data-open-form=\"trigger\" [disabled]=\"busy() || !canAddRunData() || form() === 'trigger'\" (click)=\"addTrigger()\">\n <nh-assistant-icon name=\"plus\" />{{ 'nh-assistant.admin.run-config.triggers.add' | translate }}\n </button>\n </header>\n\n @if (form() === 'trigger') {\n <nh-assistant-trigger-form\n [agentId]=\"config.agentId\"\n [catalog]=\"catalog()\"\n [worksOn]=\"agent()\"\n [skills]=\"skillOptions()\"\n [trigger]=\"editingTrigger()\"\n [takenIds]=\"triggerIds()\"\n (saved)=\"onTriggerSaved($event.added)\"\n (cancelled)=\"cancel()\" />\n }\n\n @if (config.codeTriggers.length === 0 && config.dataTriggers.length === 0) {\n <p class=\"state\">{{ 'nh-assistant.admin.run-config.triggers.empty' | translate }}</p>\n } @else {\n <ul class=\"skills\">\n @for (trigger of config.codeTriggers; track trigger.triggerId) {\n <li class=\"skill\" [attr.data-code-trigger]=\"trigger.triggerId\">\n <div class=\"card-header\">\n <span class=\"mono\">{{ trigger.triggerId }}</span>\n <span class=\"badge\">{{ 'nh-assistant.admin.agents.source-code' | translate }}</span>\n <span class=\"badge\">{{ 'nh-assistant.admin.run-config.triggers.sources.' + trigger.source | translate }}</span>\n </div>\n @if (trigger.skillId) {\n <p class=\"muted\">{{ 'nh-assistant.admin.run-config.triggers.skill-value' | translate: { skill: skillName(trigger.skillId) } }}</p>\n }\n </li>\n }\n @for (trigger of config.dataTriggers; track trigger.triggerId) {\n <li class=\"skill\" [attr.data-data-trigger]=\"trigger.triggerId\">\n <div class=\"card-header\">\n <span class=\"mono\">{{ trigger.triggerId }}</span>\n <span class=\"badge\">{{ 'nh-assistant.admin.run-config.triggers.sources.' + trigger.source | translate }}</span>\n @if (trigger.shadow) {\n <span class=\"badge warning\" data-shadow-badge>{{ 'nh-assistant.admin.agents.shadow.badge' | translate }}</span>\n } @else {\n <span class=\"badge success\">{{ 'nh-assistant.admin.run-config.triggers.live-badge' | translate }}</span>\n }\n @if (!trigger.inEffect) {\n <span class=\"badge danger\">{{ 'nh-assistant.admin.run-config.out-of-effect' | translate }}</span>\n }\n </div>\n <div class=\"meta\">\n @if (trigger.source === 'event') {\n <span class=\"mono\">{{ trigger.event }}</span>\n } @else {\n <span class=\"mono\">{{ trigger.cron }} {{ trigger.timeZone ?? 'UTC' }}</span>\n @if (trigger.subjectSet) {\n <span class=\"mono\">{{ trigger.subjectSet }}</span>\n } @else {\n <!-- A schedule without a subject set runs for the whole application, per division for a per-division agent. -->\n <span data-trigger-target>{{ (agent().perDivision ? 'nh-assistant.admin.run-config.triggers.target.every-division' : 'nh-assistant.admin.run-config.triggers.target.application') | translate }}</span>\n }\n }\n <span>{{ (trigger.skillId ? 'nh-assistant.admin.run-config.triggers.skill-value' : 'nh-assistant.admin.run-config.triggers.agent-routes') | translate: { skill: skillName(trigger.skillId) } }}</span>\n <span>{{ 'nh-assistant.admin.run-config.triggers.conflicts.' + trigger.conflict | translate }}</span>\n <span>{{ 'nh-assistant.admin.agents.version' | translate: { version: trigger.version } }}</span>\n </div>\n <div class=\"card-actions\">\n <button class=\"secondary\" type=\"button\" [disabled]=\"busy()\" (click)=\"editTrigger(trigger)\">\n <nh-assistant-icon name=\"edit\" />{{ 'nh-assistant.admin.edit' | translate }}\n </button>\n <button class=\"secondary\" type=\"button\" [attr.data-trigger-shadow]=\"trigger.shadow ? 'live' : 'shadow'\" [disabled]=\"busy()\" (click)=\"setTriggerShadow(trigger, !trigger.shadow)\">\n {{ (trigger.shadow ? 'nh-assistant.admin.agents.shadow.action.live' : 'nh-assistant.admin.agents.shadow.action.shadow') | translate }}\n </button>\n @if (isConfirming('remove-trigger', trigger.triggerId)) {\n <button class=\"danger\" type=\"button\" [disabled]=\"busy()\" (click)=\"removeTrigger(trigger)\">{{ 'nh-assistant.admin.confirm-delete' | translate }}</button>\n <button class=\"secondary\" type=\"button\" (click)=\"confirming.set(null)\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n } @else {\n <button class=\"danger\" type=\"button\" [disabled]=\"busy()\" (click)=\"confirm('remove-trigger', trigger.triggerId)\">\n <nh-assistant-icon name=\"trash\" />{{ 'nh-assistant.admin.delete' | translate }}\n </button>\n }\n </div>\n @if (failed('trigger:' + trigger.triggerId)) {\n <p class=\"form-error\" role=\"alert\"><nh-assistant-icon name=\"warning\" />{{ errorKeys() | nhAssistantTranslate: error()!.code }}</p>\n }\n </li>\n }\n </ul>\n }\n </section>\n }\n</section>\n", styles: [":host{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){:host{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}:host{display:block;min-width:0;color:var(--nha-text);font-family:var(--nha-font);font-size:14px}:host fieldset{min-width:0;margin:0;border:0;padding:0}:host legend,:host .label,:host .field>label{display:block;margin-bottom:6px;padding:0;color:var(--nha-text);font-size:12px;font-weight:700}:host input[type=text],:host input[type=url],:host input[type=password],:host input[type=search],:host input[type=email],:host input[type=number],:host input[type=date],:host input[type=time],:host input[type=datetime-local],:host select,:host textarea{box-sizing:border-box;width:100%;min-width:0;min-height:38px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:8px 10px;color:var(--nha-text);background:var(--nha-background);font:inherit;font-size:13px}:host input[type=text]:focus-visible,:host input[type=url]:focus-visible,:host input[type=password]:focus-visible,:host input[type=search]:focus-visible,:host input[type=email]:focus-visible,:host input[type=number]:focus-visible,:host input[type=date]:focus-visible,:host input[type=time]:focus-visible,:host input[type=datetime-local]:focus-visible,:host select:focus-visible,:host textarea:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host input[type=text]:disabled,:host input[type=url]:disabled,:host input[type=password]:disabled,:host input[type=search]:disabled,:host input[type=email]:disabled,:host input[type=number]:disabled,:host input[type=date]:disabled,:host input[type=time]:disabled,:host input[type=datetime-local]:disabled,:host select:disabled,:host textarea:disabled{cursor:not-allowed;opacity:.6}:host input[type=text][aria-invalid=true],:host input[type=url][aria-invalid=true],:host input[type=password][aria-invalid=true],:host input[type=search][aria-invalid=true],:host input[type=email][aria-invalid=true],:host input[type=number][aria-invalid=true],:host input[type=date][aria-invalid=true],:host input[type=time][aria-invalid=true],:host input[type=datetime-local][aria-invalid=true],:host select[aria-invalid=true],:host textarea[aria-invalid=true]{border-color:var(--nha-danger)}:host textarea{line-height:1.45;resize:vertical}:host input[type=number]{font-variant-numeric:tabular-nums}:host .mono{font-family:var(--nha-mono);font-size:12px}:host .field{display:flex;flex-direction:column;min-width:0}:host .hint{display:flex;justify-content:space-between;gap:8px;margin:4px 2px 0;color:var(--nha-text-muted);font-size:11px}:host .counter{flex:none;font-variant-numeric:tabular-nums}:host .error-text{color:var(--nha-danger)}:host .field-error{display:flex;align-items:flex-start;gap:6px;margin:4px 2px 0;color:var(--nha-danger);font-size:12px;font-weight:600;overflow-wrap:anywhere}:host .field-error nh-assistant-icon{flex:none;margin-top:1px}:host .form-error{box-sizing:border-box;display:flex;align-items:flex-start;gap:8px;margin:0;border:1px solid var(--nha-danger);border-radius:var(--nha-radius-control);padding:8px 12px;color:var(--nha-text);background:var(--nha-danger-soft);font-size:13px;overflow-wrap:anywhere}:host .form-error ul{margin:4px 0 0;padding-left:18px}:host .message{box-sizing:border-box;display:flex;align-items:center;gap:8px;margin:0;border:1px solid transparent;border-radius:var(--nha-radius-control);padding:8px 12px;font-size:13px;overflow-wrap:anywhere}:host .message.error{border-color:var(--nha-danger);color:var(--nha-text);background:var(--nha-danger-soft)}:host .message.warning{border-color:var(--nha-warning);color:var(--nha-text);background:var(--nha-warning-soft)}:host .message.success{border-color:var(--nha-success);color:var(--nha-text);background:var(--nha-success-soft)}:host .message.info{border-color:var(--nha-border);color:var(--nha-text);background:var(--nha-surface)}:host .message nh-assistant-icon{flex:none}:host button.link{min-height:0;border:0;padding:0;color:var(--nha-accent, currentColor);background:none;font:inherit;text-decoration:underline;cursor:pointer}:host button.link:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host button.link:disabled{cursor:not-allowed;opacity:.55}:host .primary,:host .secondary,:host .danger{display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;gap:6px;min-height:36px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px 14px;font:inherit;font-size:13px;font-weight:700;white-space:nowrap;cursor:pointer}:host .primary:focus-visible,:host .secondary:focus-visible,:host .danger:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .primary:disabled,:host .secondary:disabled,:host .danger:disabled{cursor:not-allowed;opacity:.55}:host .primary{border-color:var(--nha-accent);color:var(--nha-accent-contrast);background:var(--nha-accent)}:host .primary:hover:not(:disabled){filter:brightness(1.12)}:host .secondary{color:var(--nha-text);background:transparent}:host .secondary:hover:not(:disabled){background:var(--nha-surface-strong)}:host .danger{border-color:var(--nha-danger);color:var(--nha-danger);background:transparent}:host .danger:hover:not(:disabled){background:var(--nha-danger-soft)}:host .icon-button{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:1px solid transparent;border-radius:var(--nha-radius-control);color:var(--nha-text-muted);background:transparent;cursor:pointer}:host .icon-button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .icon-button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}:host .icon-button:disabled{cursor:not-allowed;opacity:.5}:host .actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px}:host .badge{display:inline-flex;align-items:center;gap:4px;border-radius:999px;padding:2px 8px;color:var(--nha-text-muted);background:var(--nha-surface-strong);font-size:11px;font-weight:700;white-space:nowrap}:host .badge.success{color:var(--nha-success);background:var(--nha-success-soft)}:host .badge.warning{color:var(--nha-warning);background:var(--nha-warning-soft)}:host .badge.danger{color:var(--nha-danger);background:var(--nha-danger-soft)}:host .visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}.section{display:flex;flex-direction:column;gap:16px;min-width:0}.section-header{display:flex;flex-wrap:wrap;align-items:flex-start;justify-content:space-between;gap:12px}.section-header>div{min-width:0;max-width:72ch}h3{margin:0 0 4px;font-size:17px;font-weight:750}h4{margin:0 0 8px;font-size:14px;font-weight:700}.muted{margin:0;color:var(--nha-text-muted);font-size:13px}.state{display:flex;flex-direction:column;align-items:flex-start;gap:10px;margin:0;border:1px dashed var(--nha-border);border-radius:var(--nha-radius);padding:20px;color:var(--nha-text-muted);font-size:13px}.state p{margin:0}.stack{display:flex;flex-direction:column;gap:14px;min-width:0}.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,240px),1fr));gap:14px}.meta{display:flex;flex-wrap:wrap;align-items:center;gap:8px 12px;color:var(--nha-text-muted);font-size:12px;overflow-wrap:anywhere}textarea.large{min-height:260px}.catalog-error{flex-wrap:wrap}.grow{flex:1;min-width:0}.subsection{display:flex;flex-direction:column;gap:8px;border-top:1px solid var(--nha-border);padding-top:14px}.history{display:grid;gap:6px;margin:0;padding:0;list-style:none}.history li{display:flex;flex-wrap:wrap;align-items:center;gap:6px 12px;font-size:12px}.cards{display:grid;gap:10px;margin:0;padding:0;list-style:none}.card{display:flex;flex-direction:column;gap:8px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius);padding:14px;background:var(--nha-background)}.card.inactive{opacity:.8}.card-header{display:flex;flex-wrap:wrap;align-items:center;gap:8px;min-width:0}.card-header strong{font-size:14px;overflow-wrap:anywhere}.card-actions{display:flex;flex-wrap:wrap;gap:8px}.check{display:flex;align-items:flex-start;gap:8px;font-size:13px;cursor:pointer}.check input{width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.check.described>span{display:grid;gap:2px;min-width:0;overflow-wrap:anywhere}.checks{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));gap:6px 12px;min-width:0;overflow-wrap:anywhere}.chips{display:flex;flex-wrap:wrap;gap:6px;margin:0;padding:0;list-style:none}.chip{display:inline-flex;align-items:center;gap:2px;border:1px solid var(--nha-border);border-radius:999px;padding:2px 2px 2px 10px;background:var(--nha-surface);font-family:var(--nha-mono);font-size:12px}.chip 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;width:26px;height:26px;border-radius:999px}.chip button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.chip button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}.chip button:disabled{cursor:not-allowed;opacity:.5}.inline{display:flex;flex-wrap:wrap;gap:8px}.inline>input{flex:1 1 180px}.catalog{display:grid;gap:4px;max-height:260px;margin:8px 0 0;overflow-y:auto;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px;list-style:none}.catalog li label{display:flex;flex-wrap:wrap;align-items:center;gap:6px 8px;border-radius:8px;padding:6px;cursor:pointer}.catalog li label:hover{background:var(--nha-surface)}.catalog .tool-id{font-family:var(--nha-mono);font-size:12px;overflow-wrap:anywhere}.skills{display:grid;gap:8px;margin:0;padding:0;list-style:none}.skill{display:grid;gap:6px;border-left:3px solid var(--nha-border);padding:4px 0 4px 12px}.uses{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));gap:8px}.use{display:flex;align-items:flex-start;gap:10px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:10px 12px;cursor:pointer;overflow-wrap:anywhere}.use input{flex:none;width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.use>span{display:grid;gap:2px;min-width:0}.use strong{font-size:13px}.use.selected{border-color:var(--nha-highlight);background:var(--nha-surface)}.use:focus-within{outline:2px solid var(--nha-highlight);outline-offset:2px}.discovery{display:grid;gap:8px;padding:10px 12px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control)}.discovery dl{display:grid;grid-template-columns:max-content minmax(0,1fr);gap:4px 12px;margin:0}.discovery dt{color:var(--nha-text-muted)}.discovery dd{margin:0;overflow-wrap:anywhere}\n", ".header-actions{display:flex;flex-wrap:wrap;gap:8px}.export-hint{margin-top:4px;font-size:12px}.filters{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));align-items:start;gap:12px}.subsection h4,.list-header h4{margin:0}.list-header{display:flex;flex-wrap:wrap;align-items:baseline;justify-content:space-between;gap:4px 12px}.table-container{container-type:inline-size;min-width:0}.summary-table{width:100%;border-collapse:collapse;font-size:13px}.summary-table th,.summary-table td{border-bottom:1px solid var(--nha-border);padding:8px 10px;text-align:left;vertical-align:top}.summary-table thead th{color:var(--nha-text-muted);font-size:11px;font-weight:700;letter-spacing:.04em;text-transform:uppercase}.summary-table tbody th{font-weight:600}.summary-table .number{text-align:right;font-variant-numeric:tabular-nums}.summary-table .mono{overflow-wrap:anywhere}@container (max-width: 820px){.summary-table thead{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}.summary-table,.summary-table tbody,.summary-table tr{display:block}.summary-table tr{margin-bottom:8px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px 12px;background:var(--nha-background)}.summary-table tbody th,.summary-table td{display:grid;grid-template-columns:minmax(110px,40%) minmax(0,1fr);gap:8px;border:0;padding:4px 0;text-align:left}.summary-table tbody th:before,.summary-table td:before{content:attr(data-label);color:var(--nha-text-muted);font-size:12px;font-weight:700}}.badges{display:flex;flex-wrap:wrap;gap:4px;margin:0;padding:0;list-style:none}.badge .count{font-variant-numeric:tabular-nums;opacity:.85}.feedback-text{margin:0;border-left:3px solid var(--nha-border);padding:2px 0 2px 10px;font-size:13px;white-space:pre-wrap;overflow-wrap:anywhere}.proposal{margin:0;color:var(--nha-text-muted);font-size:12px;overflow-wrap:anywhere}.proposal-label{margin-right:4px;color:var(--nha-text);font-weight:700}.pager{display:flex;flex-wrap:wrap;align-items:center;gap:8px}\n", "h5{margin:0;font-size:15px;font-weight:750}.group{display:flex;flex-direction:column;gap:10px;min-width:0;border-top:1px solid var(--nha-border);padding-top:14px}.group>legend{float:left;width:100%;margin-bottom:2px;font-size:14px;font-weight:750}.choices{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,220px),1fr));gap:8px}.choice{display:flex;align-items:flex-start;gap:10px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:10px 12px;cursor:pointer;overflow-wrap:anywhere}.choice input{flex:none;width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.choice>span{display:grid;gap:2px;min-width:0}.choice strong{font-size:13px}.choice.selected{border-color:var(--nha-highlight);background:var(--nha-surface)}.choice:focus-within{outline:2px solid var(--nha-highlight);outline-offset:2px}.block{display:block;margin-top:2px}.advanced{border-top:1px solid var(--nha-border);padding-top:12px}.advanced>summary{width:fit-content;border-radius:6px;font-size:14px;font-weight:750;cursor:pointer}.advanced>summary:focus-visible{outline:2px solid var(--nha-highlight);outline-offset:2px}.advanced>.stack{margin-top:12px}.examples{display:flex;flex-wrap:wrap;gap:6px;margin:6px 0 0;padding:0;list-style:none}.examples button{border:1px solid var(--nha-border);border-radius:999px;padding:3px 10px;color:var(--nha-text);background:transparent;font:inherit;font-size:12px;cursor:pointer}.examples button:hover{background:var(--nha-surface-strong)}.examples button:focus-visible{outline:2px solid var(--nha-highlight);outline-offset:2px}.steps{display:flex;flex-wrap:wrap;gap:6px;margin:0;padding:0;list-style:none}.steps li{display:inline-flex;align-items:center;gap:6px;border-radius:999px;padding:4px 10px;color:var(--nha-text-muted);background:var(--nha-surface-strong);font-size:12px;font-weight:700}.steps li[aria-current=step]{color:var(--nha-accent-contrast);background:var(--nha-accent)}.steps li.done{color:var(--nha-success);background:var(--nha-success-soft)}.review{display:grid;grid-template-columns:minmax(0,max-content) minmax(0,1fr);gap:6px 16px;margin:0;font-size:13px}.review dt{color:var(--nha-text-muted)}.review dd{margin:0;overflow-wrap:anywhere}.tool-choice{display:flex;flex-direction:column;gap:2px;min-width:0;overflow-wrap:anywhere}@media(max-width:520px){.review{grid-template-columns:minmax(0,1fr)}.review dd{margin-bottom:6px}}.table-scroll{max-width:100%;overflow-x:auto}\n"], dependencies: [{ kind: "component", type: _NhAssistantIconComponent, selector: "nh-assistant-icon", inputs: ["name"] }, { kind: "component", type: NhAssistantAgentFlowComponent, selector: "nh-assistant-agent-flow", inputs: ["agent", "configuration", "catalog", "tools", "servers", "accountName", "busy"], outputs: ["entry"] }, { kind: "component", type: NhAssistantAgentWorkSetupComponent, selector: "nh-assistant-agent-work-setup", inputs: ["agentId", "agentName", "catalog", "worksOn", "toolSelectors", "toolCatalog", "takenSkillIds", "takenTriggerIds"], outputs: ["completed", "cancelled"] }, { kind: "component", type: NhAssistantRunSettingsFormComponent, selector: "nh-assistant-run-settings-form", inputs: ["agentId", "catalog", "settings"], outputs: ["saved", "cancelled"] }, { kind: "component", type: NhAssistantSkillFormComponent, selector: "nh-assistant-skill-form", inputs: ["agentId", "catalog", "toolSelectors", "toolCatalog", "skill", "takenIds"], outputs: ["saved", "cancelled"] }, { kind: "component", type: NhAssistantTriggerFormComponent, selector: "nh-assistant-trigger-form", inputs: ["agentId", "catalog", "worksOn", "skills", "trigger", "takenIds"], outputs: ["saved", "cancelled"] }, { kind: "component", type: NhAssistantAccountNamesErrorComponent, selector: "nh-assistant-account-names-error", inputs: ["names"] }, { kind: "component", type: NhAssistantAccountLabelComponent, selector: "nh-assistant-account-label", inputs: ["actorId", "name"] }, { kind: "pipe", type: DatePipe, name: "date" }, { kind: "pipe", type: DecimalPipe, name: "number" }, { kind: "pipe", type: TranslatePipe, name: "translate" }, { kind: "pipe", type: _NhAssistantTranslatePipe, name: "nhAssistantTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
3314
3559
|
}
|
|
3315
3560
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: NhAssistantAgentRunConfigurationComponent, decorators: [{
|
|
3316
3561
|
type: Component,
|
|
@@ -3327,7 +3572,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImpo
|
|
|
3327
3572
|
NhAssistantTriggerFormComponent,
|
|
3328
3573
|
NhAssistantAccountNamesErrorComponent,
|
|
3329
3574
|
NhAssistantAccountLabelComponent
|
|
3330
|
-
], changeDetection: ChangeDetectionStrategy.OnPush, template: "<section class=\"section\" [attr.aria-labelledby]=\"id + '-title'\" [attr.aria-busy]=\"loading() || busy()\">\n <header class=\"section-header\">\n <div>\n <h3 [id]=\"id + '-title'\">{{ 'nh-assistant.admin.run-config.title' | translate: { name: agentName() } }}</h3>\n <p class=\"muted\">{{ 'nh-assistant.admin.run-config.description' | translate }}</p>\n </div>\n </header>\n\n @if (saved(); as key) {\n <p class=\"message success\" role=\"status\" tabindex=\"-1\" data-saved>{{ key | translate }}</p>\n }\n\n @if (loading() && !configuration()) {\n <p class=\"state\" role=\"status\">{{ 'nh-assistant.admin.loading' | translate }}</p>\n } @else if (loadFailed()) {\n <div class=\"state\" role=\"alert\">\n <p>{{ 'nh-assistant.admin.load-failed' | translate }}</p>\n <button class=\"secondary\" type=\"button\" (click)=\"load()\">{{ 'nh-assistant.admin.retry' | translate }}</button>\n </div>\n } @else if (configuration(); as config) {\n <nh-assistant-account-names-error [names]=\"accountNames\" />\n @if (problems().length > 0) {\n <!-- Each problem shows at the step of the flow it concerns, with the form that fixes it. -->\n <p class=\"message warning\" role=\"status\" data-problems>\n <nh-assistant-icon name=\"warning\" />{{ 'nh-assistant.admin.run-config.flow.problems' | translate }}\n </p>\n }\n\n <!-- The agent at a glance; the forms below change what each step shows. -->\n <nh-assistant-agent-flow\n [agent]=\"agent()\"\n [configuration]=\"config\"\n [catalog]=\"catalog()\"\n [tools]=\"tools()\"\n [servers]=\"servers()\"\n [accountName]=\"accountName()\"\n [busy]=\"busy()\"\n (entry)=\"onFlowEntry($event)\" />\n\n @if (config.source === 'admin') {\n <section class=\"subsection\" [attr.aria-labelledby]=\"id + '-settings-title'\" data-run-settings>\n <header class=\"section-header\">\n <h4 [id]=\"id + '-settings-title'\">{{ 'nh-assistant.admin.run-config.settings.title' | translate }}</h4>\n </header>\n @if (form() === 'setup') {\n <nh-assistant-agent-work-setup\n [agentId]=\"config.agentId\"\n [agentName]=\"agentName()\"\n [catalog]=\"catalog()\"\n [worksOn]=\"agent()\"\n [toolSelectors]=\"config.toolSelectors\"\n [toolCatalog]=\"tools()\"\n [takenSkillIds]=\"skillIds()\"\n [takenTriggerIds]=\"triggerIds()\"\n (completed)=\"onSetupCompleted($event.live)\"\n (cancelled)=\"cancel()\" />\n } @else if (form() === 'settings') {\n <nh-assistant-run-settings-form\n [agentId]=\"config.agentId\"\n [catalog]=\"catalog()\"\n [settings]=\"config.runSettings\"\n (saved)=\"onSettingsSaved()\"\n (cancelled)=\"cancel()\" />\n } @else if (config.runSettings; as settings) {\n <dl class=\"review\" data-settings-summary>\n <dt>{{ 'nh-assistant.admin.run-config.settings.who.short' | translate }}</dt>\n <dd>\n {{ 'nh-assistant.admin.run-config.settings.accountability.' + settings.accountability | translate }}\n @if (settings.serviceAccountActorId; as accountId) {\n <span class=\"block\" data-account-name>\n <nh-assistant-account-label [actorId]=\"accountId\" [name]=\"accountName()\" />\n </span>\n }\n </dd>\n <dt>{{ 'nh-assistant.admin.run-config.way-of-working' | translate }}</dt>\n <dd>{{ settings.ruleHandler ?? ('nh-assistant.admin.run-config.model' | translate) }}</dd>\n <dt>{{ 'nh-assistant.admin.run-config.approval-policy' | translate }}</dt>\n <dd>\n {{ settings.approvalPolicy ?? ('nh-assistant.admin.run-config.settings.approval.default' | translate) }}\n @if (settingsPolicy(); as sentence) {\n <span class=\"muted block\">{{ sentence }}</span>\n }\n </dd>\n <dt>{{ 'nh-assistant.admin.agents.version-label' | translate }}</dt>\n <dd>\n {{ 'nh-assistant.admin.agents.version' | translate: { version: settings.version } }}\n @if (!settings.inEffect) {\n <span class=\"badge danger\">{{ 'nh-assistant.admin.run-config.out-of-effect' | translate }}</span>\n }\n </dd>\n </dl>\n <p class=\"muted\">{{ 'nh-assistant.admin.run-config.settings.rights' | translate }}</p>\n <div class=\"card-actions\">\n <button class=\"secondary\" type=\"button\" data-open-form=\"settings\" [disabled]=\"busy()\" (click)=\"editSettings()\">\n <nh-assistant-icon name=\"edit\" />{{ 'nh-assistant.admin.run-config.settings.edit' | translate }}\n </button>\n @if (isConfirming('remove-settings', config.agentId)) {\n <span class=\"muted\">{{ 'nh-assistant.admin.run-config.settings.remove-explain' | translate }}</span>\n <button class=\"danger\" type=\"button\" [disabled]=\"busy()\" (click)=\"removeSettings()\">{{ 'nh-assistant.admin.confirm-delete' | translate }}</button>\n <button class=\"secondary\" type=\"button\" (click)=\"confirming.set(null)\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n } @else {\n <button class=\"danger\" type=\"button\" [disabled]=\"busy()\" (click)=\"confirm('remove-settings', config.agentId)\">\n <nh-assistant-icon name=\"trash\" />{{ 'nh-assistant.admin.run-config.settings.remove' | translate }}\n </button>\n }\n </div>\n @if (failed('settings')) {\n <p class=\"form-error\" role=\"alert\"><nh-assistant-icon name=\"warning\" />{{ errorKeys() | nhAssistantTranslate: error()!.code }}</p>\n }\n } @else {\n <div class=\"state\" data-setup-offer>\n <p>{{ 'nh-assistant.admin.run-config.settings.empty' | translate }}</p>\n <div class=\"card-actions\">\n <button class=\"primary\" type=\"button\" data-open-form=\"setup\" [disabled]=\"busy()\" (click)=\"openSetup()\">\n <nh-assistant-icon name=\"plus\" />{{ 'nh-assistant.admin.run-config.setup.start' | translate }}\n </button>\n <button class=\"secondary\" type=\"button\" data-open-form=\"settings\" [disabled]=\"busy()\" (click)=\"editSettings()\">\n {{ 'nh-assistant.admin.run-config.settings.add' | translate }}\n </button>\n </div>\n </div>\n }\n </section>\n }\n\n <section class=\"subsection\" [attr.aria-labelledby]=\"id + '-skills-title'\" data-skills>\n <header class=\"section-header\">\n <div>\n <h4 [id]=\"id + '-skills-title'\">{{ 'nh-assistant.admin.agents.skills.title' | translate }}</h4>\n <p class=\"muted\">{{ 'nh-assistant.admin.run-config.skills.description' | translate }}</p>\n </div>\n <button class=\"secondary\" type=\"button\" data-open-form=\"skill\" [disabled]=\"busy() || !canAddRunData() || form() === 'skill'\" (click)=\"addSkill()\">\n <nh-assistant-icon name=\"plus\" />{{ 'nh-assistant.admin.run-config.skills.add' | translate }}\n </button>\n </header>\n @if (!canAddRunData()) {\n <p class=\"muted\">{{ 'nh-assistant.admin.run-config.needs-settings' | translate }}</p>\n }\n\n @if (form() === 'skill') {\n <nh-assistant-skill-form\n [agentId]=\"config.agentId\"\n [catalog]=\"catalog()\"\n [toolSelectors]=\"config.toolSelectors\"\n [toolCatalog]=\"tools()\"\n [skill]=\"editingSkill()\"\n [takenIds]=\"skillIds()\"\n (saved)=\"onSkillSaved($event)\"\n (cancelled)=\"cancel()\" />\n }\n\n @if (config.codeSkills.length === 0 && config.dataSkills.length === 0) {\n <p class=\"state\">{{ 'nh-assistant.admin.run-config.skills.empty' | translate }}</p>\n } @else {\n <ul class=\"skills\">\n @for (skill of config.codeSkills; track skill.skillId) {\n <li class=\"skill\" [attr.data-code-skill]=\"skill.skillId\">\n <div class=\"card-header\">\n <strong>{{ (skillNameKeys(config.agentId, skill.skillId) | nhAssistantTranslate: readableId(skill.skillId)) }}</strong>\n <span class=\"mono muted\">{{ skill.skillId }}</span>\n <span class=\"badge\">{{ 'nh-assistant.admin.agents.source-code' | translate }}</span>\n </div>\n <p class=\"muted\">{{ skillDescriptionKeys(config.agentId, skill.skillId) | nhAssistantTranslate: skill.description }}</p>\n </li>\n }\n @for (skill of config.dataSkills; track skill.skillId) {\n <li class=\"skill\" [attr.data-data-skill]=\"skill.skillId\">\n <div class=\"card-header\">\n <strong>{{ (skillNameKeys(config.agentId, skill.skillId) | nhAssistantTranslate: readableId(skill.skillId)) }}</strong>\n <span class=\"mono muted\">{{ skill.skillId }}</span>\n <span class=\"badge\">{{ 'nh-assistant.admin.agents.version' | translate: { version: skill.version } }}</span>\n @if (!skill.inEffect) {\n <span class=\"badge danger\">{{ 'nh-assistant.admin.run-config.out-of-effect' | translate }}</span>\n }\n @if (skill.candidate) {\n <span class=\"badge warning\" data-candidate-badge>{{ 'nh-assistant.admin.run-config.candidate.badge' | translate: { version: skill.candidate.version } }}</span>\n }\n </div>\n <p class=\"muted\">{{ skill.description }}</p>\n <div class=\"meta\">\n <span>{{ skill.ruleHandler ? ('nh-assistant.admin.run-config.rule-handler-value' | translate: { name: skill.ruleHandler }) : ('nh-assistant.admin.run-config.model' | translate) }}</span>\n @if (skill.profile) {\n <span>{{ 'nh-assistant.admin.run-config.profile-value' | translate: { name: skill.profile } }}</span>\n }\n <span>{{ (skill.tools.length === 1 ? 'nh-assistant.admin.agents.tool-count-one' : 'nh-assistant.admin.agents.tool-count') | translate: { count: skill.tools.length } }}</span>\n <span [attr.title]=\"accountTitle(skill.changedBy)\">{{ 'nh-assistant.admin.run-config.changed' | translate: { user: nameOf(skill.changedBy), date: (skill.changedAt | date: 'medium') } }}</span>\n </div>\n @if (skill.candidate; as candidate) {\n <div class=\"subsection\" data-candidate>\n <p class=\"muted\">{{ 'nh-assistant.admin.run-config.candidate.explain' | translate: { version: candidate.version, description: candidate.description } }}</p>\n <div class=\"card-actions\">\n <button class=\"secondary\" type=\"button\" [attr.aria-expanded]=\"comparison()?.skillId === skill.skillId\" (click)=\"toggleComparison(skill)\">\n <nh-assistant-icon name=\"list\" />{{ 'nh-assistant.admin.run-config.candidate.compare' | translate }}\n </button>\n @if (isConfirming('promote', skill.skillId)) {\n <button class=\"primary\" type=\"button\" [disabled]=\"busy()\" (click)=\"promote(skill)\">{{ 'nh-assistant.admin.run-config.candidate.confirm-promote' | translate }}</button>\n <button class=\"secondary\" type=\"button\" (click)=\"confirming.set(null)\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n } @else if (isConfirming('discard', skill.skillId)) {\n <button class=\"danger\" type=\"button\" [disabled]=\"busy()\" (click)=\"discard(skill)\">{{ 'nh-assistant.admin.run-config.candidate.confirm-discard' | translate }}</button>\n <button class=\"secondary\" type=\"button\" (click)=\"confirming.set(null)\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n } @else {\n <button class=\"secondary\" type=\"button\" [disabled]=\"busy()\" (click)=\"confirm('promote', skill.skillId)\">{{ 'nh-assistant.admin.run-config.candidate.promote' | translate }}</button>\n <button class=\"danger\" type=\"button\" [disabled]=\"busy()\" (click)=\"confirm('discard', skill.skillId)\">{{ 'nh-assistant.admin.run-config.candidate.discard' | translate }}</button>\n }\n </div>\n @if (comparison(); as shown) {\n @if (shown.skillId === skill.skillId) {\n @if (shown.failed) {\n <p class=\"message error\" role=\"alert\">{{ 'nh-assistant.admin.load-failed' | translate }}</p>\n } @else if (shown.result; as result) {\n <div class=\"table-scroll\">\n <table class=\"summary-table\" data-comparison>\n <caption class=\"visually-hidden\">{{ 'nh-assistant.admin.run-config.candidate.comparison' | translate: { skill: (skillNameKeys(config.agentId, skill.skillId) | nhAssistantTranslate: readableId(skill.skillId)) } }}</caption>\n <thead>\n <tr>\n <th scope=\"col\">{{ 'nh-assistant.admin.evaluation.columns.measure' | translate }}</th>\n <th scope=\"col\">{{ 'nh-assistant.admin.run-config.candidate.live-column' | translate: { version: result.live.version } }}</th>\n <th scope=\"col\">{{ 'nh-assistant.admin.run-config.candidate.candidate-column' | translate: { version: result.candidate.version } }}</th>\n </tr>\n </thead>\n <tbody>\n <tr><th scope=\"row\">{{ 'nh-assistant.admin.run-config.candidate.runs' | translate }}</th><td>{{ result.live.runs | number }}</td><td>{{ result.candidate.runs | number }}</td></tr>\n <tr><th scope=\"row\">{{ 'nh-assistant.admin.run-config.candidate.completed' | translate }}</th><td>{{ result.live.completed | number }}</td><td>{{ result.candidate.completed | number }}</td></tr>\n <tr><th scope=\"row\">{{ 'nh-assistant.admin.run-config.candidate.failed' | translate }}</th><td>{{ result.live.failed | number }}</td><td>{{ result.candidate.failed | number }}</td></tr>\n <tr><th scope=\"row\">{{ 'nh-assistant.admin.run-config.candidate.proposals' | translate }}</th><td>{{ result.live.proposals | number }}</td><td>{{ result.candidate.proposals | number }}</td></tr>\n <tr><th scope=\"row\">{{ 'nh-assistant.admin.run-config.candidate.tokens' | translate }}</th><td>{{ result.live.tokens | number }}</td><td>{{ result.candidate.tokens | number }}</td></tr>\n </tbody>\n </table>\n </div>\n <p class=\"muted\">{{ 'nh-assistant.admin.run-config.candidate.paired' | translate: { count: result.pairedEvents, date: (result.from | date: 'medium') } }}</p>\n } @else {\n <p class=\"state\" role=\"status\">{{ 'nh-assistant.admin.loading' | translate }}</p>\n }\n }\n }\n </div>\n }\n <div class=\"card-actions\">\n <button class=\"secondary\" type=\"button\" [disabled]=\"busy()\" (click)=\"editSkill(skill)\">\n <nh-assistant-icon name=\"edit\" />{{ 'nh-assistant.admin.edit' | translate }}\n </button>\n <button class=\"secondary\" type=\"button\" [attr.aria-expanded]=\"versions()?.skillId === skill.skillId\" (click)=\"toggleVersions(skill)\">\n <nh-assistant-icon name=\"list\" />{{ 'nh-assistant.admin.run-config.skills.versions' | translate }}\n </button>\n @if (isConfirming('remove-skill', skill.skillId)) {\n <span class=\"muted\">{{ 'nh-assistant.admin.run-config.skills.remove-explain' | translate }}</span>\n <button class=\"danger\" type=\"button\" [disabled]=\"busy()\" (click)=\"removeSkill(skill)\">{{ 'nh-assistant.admin.confirm-delete' | translate }}</button>\n <button class=\"secondary\" type=\"button\" (click)=\"confirming.set(null)\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n } @else {\n <button class=\"danger\" type=\"button\" [disabled]=\"busy()\" (click)=\"confirm('remove-skill', skill.skillId)\">\n <nh-assistant-icon name=\"trash\" />{{ 'nh-assistant.admin.delete' | translate }}\n </button>\n }\n </div>\n @if (failed('skill:' + skill.skillId)) {\n <p class=\"form-error\" role=\"alert\"><nh-assistant-icon name=\"warning\" />{{ errorKeys() | nhAssistantTranslate: error()!.code }}</p>\n }\n @if (versions(); as shown) {\n @if (shown.skillId === skill.skillId) {\n @if (shown.failed) {\n <p class=\"message error\" role=\"alert\">{{ 'nh-assistant.admin.load-failed' | translate }}</p>\n } @else if (shown.items; as items) {\n <ul class=\"history\" data-versions>\n @for (version of items; track version.version) {\n <li>\n <span class=\"badge\">{{ 'nh-assistant.admin.agents.version' | translate: { version: version.version } }}</span>\n <span class=\"badge\" [class.success]=\"version.status === 'live'\" [class.warning]=\"version.status === 'candidate'\">{{ 'nh-assistant.admin.run-config.status.' + version.status | translate }}</span>\n <span>{{ version.description }}</span>\n <span class=\"muted\" [attr.title]=\"accountTitle(version.createdBy)\">{{ 'nh-assistant.admin.run-config.changed' | translate: { user: nameOf(version.createdBy), date: (version.createdAt | date: 'medium') } }}</span>\n @if (version.reason) {\n <span class=\"muted\">{{ 'nh-assistant.admin.agents.state.reason' | translate: { reason: version.reason } }}</span>\n }\n </li>\n }\n </ul>\n } @else {\n <p class=\"state\" role=\"status\">{{ 'nh-assistant.admin.loading' | translate }}</p>\n }\n }\n }\n </li>\n }\n </ul>\n }\n </section>\n\n <section class=\"subsection\" [attr.aria-labelledby]=\"id + '-triggers-title'\" data-triggers>\n <header class=\"section-header\">\n <div>\n <h4 [id]=\"id + '-triggers-title'\">{{ 'nh-assistant.admin.run-config.triggers.title' | translate }}</h4>\n <p class=\"muted\">{{ 'nh-assistant.admin.run-config.triggers.description' | translate }}</p>\n </div>\n <button class=\"secondary\" type=\"button\" data-open-form=\"trigger\" [disabled]=\"busy() || !canAddRunData() || form() === 'trigger'\" (click)=\"addTrigger()\">\n <nh-assistant-icon name=\"plus\" />{{ 'nh-assistant.admin.run-config.triggers.add' | translate }}\n </button>\n </header>\n\n @if (form() === 'trigger') {\n <nh-assistant-trigger-form\n [agentId]=\"config.agentId\"\n [catalog]=\"catalog()\"\n [worksOn]=\"agent()\"\n [skills]=\"skillOptions()\"\n [trigger]=\"editingTrigger()\"\n [takenIds]=\"triggerIds()\"\n (saved)=\"onTriggerSaved($event.added)\"\n (cancelled)=\"cancel()\" />\n }\n\n @if (config.codeTriggers.length === 0 && config.dataTriggers.length === 0) {\n <p class=\"state\">{{ 'nh-assistant.admin.run-config.triggers.empty' | translate }}</p>\n } @else {\n <ul class=\"skills\">\n @for (trigger of config.codeTriggers; track trigger.triggerId) {\n <li class=\"skill\" [attr.data-code-trigger]=\"trigger.triggerId\">\n <div class=\"card-header\">\n <span class=\"mono\">{{ trigger.triggerId }}</span>\n <span class=\"badge\">{{ 'nh-assistant.admin.agents.source-code' | translate }}</span>\n <span class=\"badge\">{{ 'nh-assistant.admin.run-config.triggers.sources.' + trigger.source | translate }}</span>\n </div>\n @if (trigger.skillId) {\n <p class=\"muted\">{{ 'nh-assistant.admin.run-config.triggers.skill-value' | translate: { skill: skillName(trigger.skillId) } }}</p>\n }\n </li>\n }\n @for (trigger of config.dataTriggers; track trigger.triggerId) {\n <li class=\"skill\" [attr.data-data-trigger]=\"trigger.triggerId\">\n <div class=\"card-header\">\n <span class=\"mono\">{{ trigger.triggerId }}</span>\n <span class=\"badge\">{{ 'nh-assistant.admin.run-config.triggers.sources.' + trigger.source | translate }}</span>\n @if (trigger.shadow) {\n <span class=\"badge warning\" data-shadow-badge>{{ 'nh-assistant.admin.agents.shadow.badge' | translate }}</span>\n } @else {\n <span class=\"badge success\">{{ 'nh-assistant.admin.run-config.triggers.live-badge' | translate }}</span>\n }\n @if (!trigger.inEffect) {\n <span class=\"badge danger\">{{ 'nh-assistant.admin.run-config.out-of-effect' | translate }}</span>\n }\n </div>\n <div class=\"meta\">\n @if (trigger.source === 'event') {\n <span class=\"mono\">{{ trigger.event }}</span>\n } @else {\n <span class=\"mono\">{{ trigger.cron }} {{ trigger.timeZone ?? 'UTC' }}</span>\n @if (trigger.subjectSet) {\n <span class=\"mono\">{{ trigger.subjectSet }}</span>\n } @else {\n <!-- A schedule without a subject set runs for the whole application, per division for a per-division agent. -->\n <span data-trigger-target>{{ (agent().perDivision ? 'nh-assistant.admin.run-config.triggers.target.every-division' : 'nh-assistant.admin.run-config.triggers.target.application') | translate }}</span>\n }\n }\n <span>{{ (trigger.skillId ? 'nh-assistant.admin.run-config.triggers.skill-value' : 'nh-assistant.admin.run-config.triggers.agent-routes') | translate: { skill: skillName(trigger.skillId) } }}</span>\n <span>{{ 'nh-assistant.admin.run-config.triggers.conflicts.' + trigger.conflict | translate }}</span>\n <span>{{ 'nh-assistant.admin.agents.version' | translate: { version: trigger.version } }}</span>\n </div>\n <div class=\"card-actions\">\n <button class=\"secondary\" type=\"button\" [disabled]=\"busy()\" (click)=\"editTrigger(trigger)\">\n <nh-assistant-icon name=\"edit\" />{{ 'nh-assistant.admin.edit' | translate }}\n </button>\n <button class=\"secondary\" type=\"button\" [attr.data-trigger-shadow]=\"trigger.shadow ? 'live' : 'shadow'\" [disabled]=\"busy()\" (click)=\"setTriggerShadow(trigger, !trigger.shadow)\">\n {{ (trigger.shadow ? 'nh-assistant.admin.agents.shadow.action.live' : 'nh-assistant.admin.agents.shadow.action.shadow') | translate }}\n </button>\n @if (isConfirming('remove-trigger', trigger.triggerId)) {\n <button class=\"danger\" type=\"button\" [disabled]=\"busy()\" (click)=\"removeTrigger(trigger)\">{{ 'nh-assistant.admin.confirm-delete' | translate }}</button>\n <button class=\"secondary\" type=\"button\" (click)=\"confirming.set(null)\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n } @else {\n <button class=\"danger\" type=\"button\" [disabled]=\"busy()\" (click)=\"confirm('remove-trigger', trigger.triggerId)\">\n <nh-assistant-icon name=\"trash\" />{{ 'nh-assistant.admin.delete' | translate }}\n </button>\n }\n </div>\n @if (failed('trigger:' + trigger.triggerId)) {\n <p class=\"form-error\" role=\"alert\"><nh-assistant-icon name=\"warning\" />{{ errorKeys() | nhAssistantTranslate: error()!.code }}</p>\n }\n </li>\n }\n </ul>\n }\n </section>\n }\n</section>\n", styles: [":host{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){:host{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}:host{display:block;min-width:0;color:var(--nha-text);font-family:var(--nha-font);font-size:14px}:host fieldset{min-width:0;margin:0;border:0;padding:0}:host legend,:host .label,:host .field>label{display:block;margin-bottom:6px;padding:0;color:var(--nha-text);font-size:12px;font-weight:700}:host input[type=text],:host input[type=url],:host input[type=password],:host input[type=search],:host input[type=email],:host input[type=number],:host input[type=date],:host input[type=time],:host input[type=datetime-local],:host select,:host textarea{box-sizing:border-box;width:100%;min-width:0;min-height:38px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:8px 10px;color:var(--nha-text);background:var(--nha-background);font:inherit;font-size:13px}:host input[type=text]:focus-visible,:host input[type=url]:focus-visible,:host input[type=password]:focus-visible,:host input[type=search]:focus-visible,:host input[type=email]:focus-visible,:host input[type=number]:focus-visible,:host input[type=date]:focus-visible,:host input[type=time]:focus-visible,:host input[type=datetime-local]:focus-visible,:host select:focus-visible,:host textarea:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host input[type=text]:disabled,:host input[type=url]:disabled,:host input[type=password]:disabled,:host input[type=search]:disabled,:host input[type=email]:disabled,:host input[type=number]:disabled,:host input[type=date]:disabled,:host input[type=time]:disabled,:host input[type=datetime-local]:disabled,:host select:disabled,:host textarea:disabled{cursor:not-allowed;opacity:.6}:host input[type=text][aria-invalid=true],:host input[type=url][aria-invalid=true],:host input[type=password][aria-invalid=true],:host input[type=search][aria-invalid=true],:host input[type=email][aria-invalid=true],:host input[type=number][aria-invalid=true],:host input[type=date][aria-invalid=true],:host input[type=time][aria-invalid=true],:host input[type=datetime-local][aria-invalid=true],:host select[aria-invalid=true],:host textarea[aria-invalid=true]{border-color:var(--nha-danger)}:host textarea{line-height:1.45;resize:vertical}:host input[type=number]{font-variant-numeric:tabular-nums}:host .mono{font-family:var(--nha-mono);font-size:12px}:host .field{display:flex;flex-direction:column;min-width:0}:host .hint{display:flex;justify-content:space-between;gap:8px;margin:4px 2px 0;color:var(--nha-text-muted);font-size:11px}:host .counter{flex:none;font-variant-numeric:tabular-nums}:host .error-text{color:var(--nha-danger)}:host .field-error{display:flex;align-items:flex-start;gap:6px;margin:4px 2px 0;color:var(--nha-danger);font-size:12px;font-weight:600;overflow-wrap:anywhere}:host .field-error nh-assistant-icon{flex:none;margin-top:1px}:host .form-error{box-sizing:border-box;display:flex;align-items:flex-start;gap:8px;margin:0;border:1px solid var(--nha-danger);border-radius:var(--nha-radius-control);padding:8px 12px;color:var(--nha-text);background:var(--nha-danger-soft);font-size:13px;overflow-wrap:anywhere}:host .form-error ul{margin:4px 0 0;padding-left:18px}:host .message{box-sizing:border-box;display:flex;align-items:center;gap:8px;margin:0;border:1px solid transparent;border-radius:var(--nha-radius-control);padding:8px 12px;font-size:13px;overflow-wrap:anywhere}:host .message.error{border-color:var(--nha-danger);color:var(--nha-text);background:var(--nha-danger-soft)}:host .message.warning{border-color:var(--nha-warning);color:var(--nha-text);background:var(--nha-warning-soft)}:host .message.success{border-color:var(--nha-success);color:var(--nha-text);background:var(--nha-success-soft)}:host .message.info{border-color:var(--nha-border);color:var(--nha-text);background:var(--nha-surface)}:host .message nh-assistant-icon{flex:none}:host button.link{min-height:0;border:0;padding:0;color:var(--nha-accent, currentColor);background:none;font:inherit;text-decoration:underline;cursor:pointer}:host button.link:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host button.link:disabled{cursor:not-allowed;opacity:.55}:host .primary,:host .secondary,:host .danger{display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;gap:6px;min-height:36px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px 14px;font:inherit;font-size:13px;font-weight:700;white-space:nowrap;cursor:pointer}:host .primary:focus-visible,:host .secondary:focus-visible,:host .danger:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .primary:disabled,:host .secondary:disabled,:host .danger:disabled{cursor:not-allowed;opacity:.55}:host .primary{border-color:var(--nha-accent);color:var(--nha-accent-contrast);background:var(--nha-accent)}:host .primary:hover:not(:disabled){filter:brightness(1.12)}:host .secondary{color:var(--nha-text);background:transparent}:host .secondary:hover:not(:disabled){background:var(--nha-surface-strong)}:host .danger{border-color:var(--nha-danger);color:var(--nha-danger);background:transparent}:host .danger:hover:not(:disabled){background:var(--nha-danger-soft)}:host .icon-button{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:1px solid transparent;border-radius:var(--nha-radius-control);color:var(--nha-text-muted);background:transparent;cursor:pointer}:host .icon-button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .icon-button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}:host .icon-button:disabled{cursor:not-allowed;opacity:.5}:host .actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px}:host .badge{display:inline-flex;align-items:center;gap:4px;border-radius:999px;padding:2px 8px;color:var(--nha-text-muted);background:var(--nha-surface-strong);font-size:11px;font-weight:700;white-space:nowrap}:host .badge.success{color:var(--nha-success);background:var(--nha-success-soft)}:host .badge.warning{color:var(--nha-warning);background:var(--nha-warning-soft)}:host .badge.danger{color:var(--nha-danger);background:var(--nha-danger-soft)}:host .visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}.section{display:flex;flex-direction:column;gap:16px;min-width:0}.section-header{display:flex;flex-wrap:wrap;align-items:flex-start;justify-content:space-between;gap:12px}.section-header>div{min-width:0;max-width:72ch}h3{margin:0 0 4px;font-size:17px;font-weight:750}h4{margin:0 0 8px;font-size:14px;font-weight:700}.muted{margin:0;color:var(--nha-text-muted);font-size:13px}.state{display:flex;flex-direction:column;align-items:flex-start;gap:10px;margin:0;border:1px dashed var(--nha-border);border-radius:var(--nha-radius);padding:20px;color:var(--nha-text-muted);font-size:13px}.state p{margin:0}.stack{display:flex;flex-direction:column;gap:14px;min-width:0}.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,240px),1fr));gap:14px}.meta{display:flex;flex-wrap:wrap;align-items:center;gap:8px 12px;color:var(--nha-text-muted);font-size:12px;overflow-wrap:anywhere}textarea.large{min-height:260px}.catalog-error{flex-wrap:wrap}.grow{flex:1;min-width:0}.subsection{display:flex;flex-direction:column;gap:8px;border-top:1px solid var(--nha-border);padding-top:14px}.history{display:grid;gap:6px;margin:0;padding:0;list-style:none}.history li{display:flex;flex-wrap:wrap;align-items:center;gap:6px 12px;font-size:12px}.cards{display:grid;gap:10px;margin:0;padding:0;list-style:none}.card{display:flex;flex-direction:column;gap:8px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius);padding:14px;background:var(--nha-background)}.card.inactive{opacity:.8}.card-header{display:flex;flex-wrap:wrap;align-items:center;gap:8px;min-width:0}.card-header strong{font-size:14px;overflow-wrap:anywhere}.card-actions{display:flex;flex-wrap:wrap;gap:8px}.check{display:flex;align-items:flex-start;gap:8px;font-size:13px;cursor:pointer}.check input{width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.check.described>span{display:grid;gap:2px;min-width:0;overflow-wrap:anywhere}.checks{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));gap:6px 12px;min-width:0;overflow-wrap:anywhere}.chips{display:flex;flex-wrap:wrap;gap:6px;margin:0;padding:0;list-style:none}.chip{display:inline-flex;align-items:center;gap:2px;border:1px solid var(--nha-border);border-radius:999px;padding:2px 2px 2px 10px;background:var(--nha-surface);font-family:var(--nha-mono);font-size:12px}.chip 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;width:26px;height:26px;border-radius:999px}.chip button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.chip button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}.chip button:disabled{cursor:not-allowed;opacity:.5}.inline{display:flex;flex-wrap:wrap;gap:8px}.inline>input{flex:1 1 180px}.catalog{display:grid;gap:4px;max-height:260px;margin:8px 0 0;overflow-y:auto;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px;list-style:none}.catalog li label{display:flex;flex-wrap:wrap;align-items:center;gap:6px 8px;border-radius:8px;padding:6px;cursor:pointer}.catalog li label:hover{background:var(--nha-surface)}.catalog .tool-id{font-family:var(--nha-mono);font-size:12px;overflow-wrap:anywhere}.skills{display:grid;gap:8px;margin:0;padding:0;list-style:none}.skill{display:grid;gap:6px;border-left:3px solid var(--nha-border);padding:4px 0 4px 12px}.uses{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));gap:8px}.use{display:flex;align-items:flex-start;gap:10px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:10px 12px;cursor:pointer;overflow-wrap:anywhere}.use input{flex:none;width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.use>span{display:grid;gap:2px;min-width:0}.use strong{font-size:13px}.use.selected{border-color:var(--nha-highlight);background:var(--nha-surface)}.use:focus-within{outline:2px solid var(--nha-highlight);outline-offset:2px}.discovery{display:grid;gap:8px;padding:10px 12px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control)}.discovery dl{display:grid;grid-template-columns:max-content minmax(0,1fr);gap:4px 12px;margin:0}.discovery dt{color:var(--nha-text-muted)}.discovery dd{margin:0;overflow-wrap:anywhere}\n", ".header-actions{display:flex;flex-wrap:wrap;gap:8px}.export-hint{margin-top:4px;font-size:12px}.filters{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));align-items:start;gap:12px}.subsection h4,.list-header h4{margin:0}.list-header{display:flex;flex-wrap:wrap;align-items:baseline;justify-content:space-between;gap:4px 12px}.table-container{container-type:inline-size;min-width:0}.summary-table{width:100%;border-collapse:collapse;font-size:13px}.summary-table th,.summary-table td{border-bottom:1px solid var(--nha-border);padding:8px 10px;text-align:left;vertical-align:top}.summary-table thead th{color:var(--nha-text-muted);font-size:11px;font-weight:700;letter-spacing:.04em;text-transform:uppercase}.summary-table tbody th{font-weight:600}.summary-table .number{text-align:right;font-variant-numeric:tabular-nums}.summary-table .mono{overflow-wrap:anywhere}@container (max-width: 820px){.summary-table thead{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}.summary-table,.summary-table tbody,.summary-table tr{display:block}.summary-table tr{margin-bottom:8px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px 12px;background:var(--nha-background)}.summary-table tbody th,.summary-table td{display:grid;grid-template-columns:minmax(110px,40%) minmax(0,1fr);gap:8px;border:0;padding:4px 0;text-align:left}.summary-table tbody th:before,.summary-table td:before{content:attr(data-label);color:var(--nha-text-muted);font-size:12px;font-weight:700}}.badges{display:flex;flex-wrap:wrap;gap:4px;margin:0;padding:0;list-style:none}.badge .count{font-variant-numeric:tabular-nums;opacity:.85}.feedback-text{margin:0;border-left:3px solid var(--nha-border);padding:2px 0 2px 10px;font-size:13px;white-space:pre-wrap;overflow-wrap:anywhere}.proposal{margin:0;color:var(--nha-text-muted);font-size:12px;overflow-wrap:anywhere}.proposal-label{margin-right:4px;color:var(--nha-text);font-weight:700}.pager{display:flex;flex-wrap:wrap;align-items:center;gap:8px}\n", "h5{margin:0;font-size:15px;font-weight:750}.group{display:flex;flex-direction:column;gap:10px;min-width:0;border-top:1px solid var(--nha-border);padding-top:14px}.group>legend{float:left;width:100%;margin-bottom:2px;font-size:14px;font-weight:750}.choices{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,220px),1fr));gap:8px}.choice{display:flex;align-items:flex-start;gap:10px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:10px 12px;cursor:pointer;overflow-wrap:anywhere}.choice input{flex:none;width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.choice>span{display:grid;gap:2px;min-width:0}.choice strong{font-size:13px}.choice.selected{border-color:var(--nha-highlight);background:var(--nha-surface)}.choice:focus-within{outline:2px solid var(--nha-highlight);outline-offset:2px}.block{display:block;margin-top:2px}.advanced{border-top:1px solid var(--nha-border);padding-top:12px}.advanced>summary{width:fit-content;border-radius:6px;font-size:14px;font-weight:750;cursor:pointer}.advanced>summary:focus-visible{outline:2px solid var(--nha-highlight);outline-offset:2px}.advanced>.stack{margin-top:12px}.examples{display:flex;flex-wrap:wrap;gap:6px;margin:6px 0 0;padding:0;list-style:none}.examples button{border:1px solid var(--nha-border);border-radius:999px;padding:3px 10px;color:var(--nha-text);background:transparent;font:inherit;font-size:12px;cursor:pointer}.examples button:hover{background:var(--nha-surface-strong)}.examples button:focus-visible{outline:2px solid var(--nha-highlight);outline-offset:2px}.steps{display:flex;flex-wrap:wrap;gap:6px;margin:0;padding:0;list-style:none}.steps li{display:inline-flex;align-items:center;gap:6px;border-radius:999px;padding:4px 10px;color:var(--nha-text-muted);background:var(--nha-surface-strong);font-size:12px;font-weight:700}.steps li[aria-current=step]{color:var(--nha-accent-contrast);background:var(--nha-accent)}.steps li.done{color:var(--nha-success);background:var(--nha-success-soft)}.review{display:grid;grid-template-columns:minmax(0,max-content) minmax(0,1fr);gap:6px 16px;margin:0;font-size:13px}.review dt{color:var(--nha-text-muted)}.review dd{margin:0;overflow-wrap:anywhere}.tool-choice{display:flex;flex-direction:column;gap:2px;min-width:0;overflow-wrap:anywhere}@media(max-width:520px){.review{grid-template-columns:minmax(0,1fr)}.review dd{margin-bottom:6px}}.table-scroll{max-width:100%;overflow-x:auto}\n"] }]
|
|
3575
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, template: "<section class=\"section\" [attr.aria-labelledby]=\"id + '-title'\" [attr.aria-busy]=\"loading() || busy()\">\n <header class=\"section-header\">\n <div>\n <h3 [id]=\"id + '-title'\">{{ 'nh-assistant.admin.run-config.title' | translate: { name: agentName() } }}</h3>\n <p class=\"muted\">{{ 'nh-assistant.admin.run-config.description' | translate }}</p>\n </div>\n </header>\n\n @if (saved(); as key) {\n <p class=\"message success\" role=\"status\" tabindex=\"-1\" data-saved>{{ key | translate }}</p>\n <!-- The save succeeded; a warning says what may still keep the skill from working. -->\n @for (warning of savedWarnings(); track warning) {\n <p class=\"message warning\" role=\"status\" [attr.data-saved-warning]=\"warning\">\n <nh-assistant-icon name=\"warning\" />{{ warningKeys('skill', warning) | nhAssistantTranslate: warning }}\n </p>\n }\n }\n\n @if (loading() && !configuration()) {\n <p class=\"state\" role=\"status\">{{ 'nh-assistant.admin.loading' | translate }}</p>\n } @else if (loadFailed()) {\n <div class=\"state\" role=\"alert\">\n <p>{{ 'nh-assistant.admin.load-failed' | translate }}</p>\n <button class=\"secondary\" type=\"button\" (click)=\"load()\">{{ 'nh-assistant.admin.retry' | translate }}</button>\n </div>\n } @else if (configuration(); as config) {\n <nh-assistant-account-names-error [names]=\"accountNames\" />\n @if (problems().length > 0) {\n <!-- Each problem shows at the step of the flow it concerns, with the form that fixes it. -->\n <p class=\"message warning\" role=\"status\" data-problems>\n <nh-assistant-icon name=\"warning\" />{{ 'nh-assistant.admin.run-config.flow.problems' | translate }}\n </p>\n }\n\n <!-- The agent at a glance; the forms below change what each step shows. -->\n <nh-assistant-agent-flow\n [agent]=\"agent()\"\n [configuration]=\"config\"\n [catalog]=\"catalog()\"\n [tools]=\"tools()\"\n [servers]=\"servers()\"\n [accountName]=\"accountName()\"\n [busy]=\"busy()\"\n (entry)=\"onFlowEntry($event)\" />\n\n @if (config.source === 'admin') {\n <section class=\"subsection\" [attr.aria-labelledby]=\"id + '-settings-title'\" data-run-settings>\n <header class=\"section-header\">\n <h4 [id]=\"id + '-settings-title'\">{{ 'nh-assistant.admin.run-config.settings.title' | translate }}</h4>\n </header>\n @if (form() === 'setup') {\n <nh-assistant-agent-work-setup\n [agentId]=\"config.agentId\"\n [agentName]=\"agentName()\"\n [catalog]=\"catalog()\"\n [worksOn]=\"agent()\"\n [toolSelectors]=\"config.toolSelectors\"\n [toolCatalog]=\"tools()\"\n [takenSkillIds]=\"skillIds()\"\n [takenTriggerIds]=\"triggerIds()\"\n (completed)=\"onSetupCompleted($event.live)\"\n (cancelled)=\"cancel()\" />\n } @else if (form() === 'settings') {\n <nh-assistant-run-settings-form\n [agentId]=\"config.agentId\"\n [catalog]=\"catalog()\"\n [settings]=\"config.runSettings\"\n (saved)=\"onSettingsSaved()\"\n (cancelled)=\"cancel()\" />\n } @else if (config.runSettings; as settings) {\n <dl class=\"review\" data-settings-summary>\n <dt>{{ 'nh-assistant.admin.run-config.settings.who.short' | translate }}</dt>\n <dd>\n {{ 'nh-assistant.admin.run-config.settings.accountability.' + settings.accountability | translate }}\n @if (settings.serviceAccountActorId; as accountId) {\n <span class=\"block\" data-account-name>\n <nh-assistant-account-label [actorId]=\"accountId\" [name]=\"accountName()\" />\n </span>\n }\n </dd>\n <dt>{{ 'nh-assistant.admin.run-config.way-of-working' | translate }}</dt>\n <dd>{{ settings.ruleHandler ?? ('nh-assistant.admin.run-config.model' | translate) }}</dd>\n <dt>{{ 'nh-assistant.admin.run-config.approval-policy' | translate }}</dt>\n <dd>\n {{ settings.approvalPolicy ?? ('nh-assistant.admin.run-config.settings.approval.default' | translate) }}\n @if (settingsPolicy(); as sentence) {\n <span class=\"muted block\">{{ sentence }}</span>\n }\n </dd>\n <dt>{{ 'nh-assistant.admin.agents.version-label' | translate }}</dt>\n <dd>\n {{ 'nh-assistant.admin.agents.version' | translate: { version: settings.version } }}\n @if (!settings.inEffect) {\n <span class=\"badge danger\">{{ 'nh-assistant.admin.run-config.out-of-effect' | translate }}</span>\n }\n </dd>\n </dl>\n <p class=\"muted\">{{ 'nh-assistant.admin.run-config.settings.rights' | translate }}</p>\n <div class=\"card-actions\">\n <button class=\"secondary\" type=\"button\" data-open-form=\"settings\" [disabled]=\"busy()\" (click)=\"editSettings()\">\n <nh-assistant-icon name=\"edit\" />{{ 'nh-assistant.admin.run-config.settings.edit' | translate }}\n </button>\n @if (isConfirming('remove-settings', config.agentId)) {\n <span class=\"muted\">{{ 'nh-assistant.admin.run-config.settings.remove-explain' | translate }}</span>\n <button class=\"danger\" type=\"button\" [disabled]=\"busy()\" (click)=\"removeSettings()\">{{ 'nh-assistant.admin.confirm-delete' | translate }}</button>\n <button class=\"secondary\" type=\"button\" (click)=\"confirming.set(null)\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n } @else {\n <button class=\"danger\" type=\"button\" [disabled]=\"busy()\" (click)=\"confirm('remove-settings', config.agentId)\">\n <nh-assistant-icon name=\"trash\" />{{ 'nh-assistant.admin.run-config.settings.remove' | translate }}\n </button>\n }\n </div>\n @if (failed('settings')) {\n <p class=\"form-error\" role=\"alert\"><nh-assistant-icon name=\"warning\" />{{ errorKeys() | nhAssistantTranslate: error()!.code }}</p>\n }\n } @else {\n <div class=\"state\" data-setup-offer>\n <p>{{ 'nh-assistant.admin.run-config.settings.empty' | translate }}</p>\n <div class=\"card-actions\">\n <button class=\"primary\" type=\"button\" data-open-form=\"setup\" [disabled]=\"busy()\" (click)=\"openSetup()\">\n <nh-assistant-icon name=\"plus\" />{{ 'nh-assistant.admin.run-config.setup.start' | translate }}\n </button>\n <button class=\"secondary\" type=\"button\" data-open-form=\"settings\" [disabled]=\"busy()\" (click)=\"editSettings()\">\n {{ 'nh-assistant.admin.run-config.settings.add' | translate }}\n </button>\n </div>\n </div>\n }\n </section>\n }\n\n <section class=\"subsection\" [attr.aria-labelledby]=\"id + '-skills-title'\" data-skills>\n <header class=\"section-header\">\n <div>\n <h4 [id]=\"id + '-skills-title'\">{{ 'nh-assistant.admin.agents.skills.title' | translate }}</h4>\n <p class=\"muted\">{{ 'nh-assistant.admin.run-config.skills.description' | translate }}</p>\n </div>\n <button class=\"secondary\" type=\"button\" data-open-form=\"skill\" [disabled]=\"busy() || !canAddRunData() || form() === 'skill'\" (click)=\"addSkill()\">\n <nh-assistant-icon name=\"plus\" />{{ 'nh-assistant.admin.run-config.skills.add' | translate }}\n </button>\n </header>\n @if (!canAddRunData()) {\n <p class=\"muted\">{{ 'nh-assistant.admin.run-config.needs-settings' | translate }}</p>\n }\n\n @if (form() === 'skill') {\n <nh-assistant-skill-form\n [agentId]=\"config.agentId\"\n [catalog]=\"catalog()\"\n [toolSelectors]=\"config.toolSelectors\"\n [toolCatalog]=\"tools()\"\n [skill]=\"editingSkill()\"\n [takenIds]=\"skillIds()\"\n (saved)=\"onSkillSaved($event)\"\n (cancelled)=\"cancel()\" />\n }\n\n @if (config.codeSkills.length === 0 && config.dataSkills.length === 0) {\n <p class=\"state\">{{ 'nh-assistant.admin.run-config.skills.empty' | translate }}</p>\n } @else {\n <ul class=\"skills\">\n @for (skill of config.codeSkills; track skill.skillId) {\n <li class=\"skill\" [attr.data-code-skill]=\"skill.skillId\">\n <div class=\"card-header\">\n <strong>{{ (skillNameKeys(config.agentId, skill.skillId) | nhAssistantTranslate: readableId(skill.skillId)) }}</strong>\n <span class=\"mono muted\">{{ skill.skillId }}</span>\n <span class=\"badge\">{{ 'nh-assistant.admin.agents.source-code' | translate }}</span>\n </div>\n <p class=\"muted\">{{ skillDescriptionKeys(config.agentId, skill.skillId) | nhAssistantTranslate: skill.description }}</p>\n </li>\n }\n @for (skill of config.dataSkills; track skill.skillId) {\n <li class=\"skill\" [attr.data-data-skill]=\"skill.skillId\">\n <div class=\"card-header\">\n <strong>{{ (skillNameKeys(config.agentId, skill.skillId) | nhAssistantTranslate: readableId(skill.skillId)) }}</strong>\n <span class=\"mono muted\">{{ skill.skillId }}</span>\n <span class=\"badge\">{{ 'nh-assistant.admin.agents.version' | translate: { version: skill.version } }}</span>\n @if (!skill.inEffect) {\n <span class=\"badge danger\">{{ 'nh-assistant.admin.run-config.out-of-effect' | translate }}</span>\n }\n @if (skill.candidate) {\n <span class=\"badge warning\" data-candidate-badge>{{ 'nh-assistant.admin.run-config.candidate.badge' | translate: { version: skill.candidate.version } }}</span>\n }\n </div>\n <p class=\"muted\">{{ skill.description }}</p>\n @for (warning of skill.warnings ?? []; track warning) {\n <p class=\"message warning\" [attr.data-skill-warning]=\"warning\">\n <nh-assistant-icon name=\"warning\" />{{ warningKeys('skill', warning) | nhAssistantTranslate: warning }}\n </p>\n }\n <div class=\"meta\">\n <span>{{ skill.ruleHandler ? ('nh-assistant.admin.run-config.rule-handler-value' | translate: { name: skill.ruleHandler }) : ('nh-assistant.admin.run-config.model' | translate) }}</span>\n @if (skill.profile) {\n <span>{{ 'nh-assistant.admin.run-config.profile-value' | translate: { name: skill.profile } }}</span>\n }\n <span>{{ (skill.tools.length === 1 ? 'nh-assistant.admin.agents.tool-count-one' : 'nh-assistant.admin.agents.tool-count') | translate: { count: skill.tools.length } }}</span>\n <span [attr.title]=\"accountTitle(skill.changedBy)\">{{ 'nh-assistant.admin.run-config.changed' | translate: { user: nameOf(skill.changedBy), date: (skill.changedAt | date: 'medium') } }}</span>\n </div>\n @if (skill.candidate; as candidate) {\n <div class=\"subsection\" data-candidate>\n <p class=\"muted\">{{ 'nh-assistant.admin.run-config.candidate.explain' | translate: { version: candidate.version, description: candidate.description } }}</p>\n <div class=\"card-actions\">\n <button class=\"secondary\" type=\"button\" [attr.aria-expanded]=\"comparison()?.skillId === skill.skillId\" (click)=\"toggleComparison(skill)\">\n <nh-assistant-icon name=\"list\" />{{ 'nh-assistant.admin.run-config.candidate.compare' | translate }}\n </button>\n @if (isConfirming('promote', skill.skillId)) {\n <button class=\"primary\" type=\"button\" [disabled]=\"busy()\" (click)=\"promote(skill)\">{{ 'nh-assistant.admin.run-config.candidate.confirm-promote' | translate }}</button>\n <button class=\"secondary\" type=\"button\" (click)=\"confirming.set(null)\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n } @else if (isConfirming('discard', skill.skillId)) {\n <button class=\"danger\" type=\"button\" [disabled]=\"busy()\" (click)=\"discard(skill)\">{{ 'nh-assistant.admin.run-config.candidate.confirm-discard' | translate }}</button>\n <button class=\"secondary\" type=\"button\" (click)=\"confirming.set(null)\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n } @else {\n <button class=\"secondary\" type=\"button\" [disabled]=\"busy()\" (click)=\"confirm('promote', skill.skillId)\">{{ 'nh-assistant.admin.run-config.candidate.promote' | translate }}</button>\n <button class=\"danger\" type=\"button\" [disabled]=\"busy()\" (click)=\"confirm('discard', skill.skillId)\">{{ 'nh-assistant.admin.run-config.candidate.discard' | translate }}</button>\n }\n </div>\n @if (comparison(); as shown) {\n @if (shown.skillId === skill.skillId) {\n @if (shown.failed) {\n <p class=\"message error\" role=\"alert\">{{ 'nh-assistant.admin.load-failed' | translate }}</p>\n } @else if (shown.result; as result) {\n <div class=\"table-scroll\">\n <table class=\"summary-table\" data-comparison>\n <caption class=\"visually-hidden\">{{ 'nh-assistant.admin.run-config.candidate.comparison' | translate: { skill: (skillNameKeys(config.agentId, skill.skillId) | nhAssistantTranslate: readableId(skill.skillId)) } }}</caption>\n <thead>\n <tr>\n <th scope=\"col\">{{ 'nh-assistant.admin.evaluation.columns.measure' | translate }}</th>\n <th scope=\"col\">{{ 'nh-assistant.admin.run-config.candidate.live-column' | translate: { version: result.live.version } }}</th>\n <th scope=\"col\">{{ 'nh-assistant.admin.run-config.candidate.candidate-column' | translate: { version: result.candidate.version } }}</th>\n </tr>\n </thead>\n <tbody>\n <tr><th scope=\"row\">{{ 'nh-assistant.admin.run-config.candidate.runs' | translate }}</th><td>{{ result.live.runs | number }}</td><td>{{ result.candidate.runs | number }}</td></tr>\n <tr><th scope=\"row\">{{ 'nh-assistant.admin.run-config.candidate.completed' | translate }}</th><td>{{ result.live.completed | number }}</td><td>{{ result.candidate.completed | number }}</td></tr>\n <tr><th scope=\"row\">{{ 'nh-assistant.admin.run-config.candidate.failed' | translate }}</th><td>{{ result.live.failed | number }}</td><td>{{ result.candidate.failed | number }}</td></tr>\n <tr><th scope=\"row\">{{ 'nh-assistant.admin.run-config.candidate.proposals' | translate }}</th><td>{{ result.live.proposals | number }}</td><td>{{ result.candidate.proposals | number }}</td></tr>\n <tr><th scope=\"row\">{{ 'nh-assistant.admin.run-config.candidate.tokens' | translate }}</th><td>{{ result.live.tokens | number }}</td><td>{{ result.candidate.tokens | number }}</td></tr>\n </tbody>\n </table>\n </div>\n <p class=\"muted\">{{ 'nh-assistant.admin.run-config.candidate.paired' | translate: { count: result.pairedEvents, date: (result.from | date: 'medium') } }}</p>\n } @else {\n <p class=\"state\" role=\"status\">{{ 'nh-assistant.admin.loading' | translate }}</p>\n }\n }\n }\n </div>\n }\n <div class=\"card-actions\">\n <button class=\"secondary\" type=\"button\" [disabled]=\"busy()\" (click)=\"editSkill(skill)\">\n <nh-assistant-icon name=\"edit\" />{{ 'nh-assistant.admin.edit' | translate }}\n </button>\n <button class=\"secondary\" type=\"button\" [attr.aria-expanded]=\"versions()?.skillId === skill.skillId\" (click)=\"toggleVersions(skill)\">\n <nh-assistant-icon name=\"list\" />{{ 'nh-assistant.admin.run-config.skills.versions' | translate }}\n </button>\n @if (isConfirming('remove-skill', skill.skillId)) {\n <span class=\"muted\">{{ 'nh-assistant.admin.run-config.skills.remove-explain' | translate }}</span>\n <button class=\"danger\" type=\"button\" [disabled]=\"busy()\" (click)=\"removeSkill(skill)\">{{ 'nh-assistant.admin.confirm-delete' | translate }}</button>\n <button class=\"secondary\" type=\"button\" (click)=\"confirming.set(null)\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n } @else {\n <button class=\"danger\" type=\"button\" [disabled]=\"busy()\" (click)=\"confirm('remove-skill', skill.skillId)\">\n <nh-assistant-icon name=\"trash\" />{{ 'nh-assistant.admin.delete' | translate }}\n </button>\n }\n </div>\n @if (failed('skill:' + skill.skillId)) {\n <p class=\"form-error\" role=\"alert\"><nh-assistant-icon name=\"warning\" />{{ errorKeys() | nhAssistantTranslate: error()!.code }}</p>\n }\n @if (versions(); as shown) {\n @if (shown.skillId === skill.skillId) {\n @if (shown.failed) {\n <p class=\"message error\" role=\"alert\">{{ 'nh-assistant.admin.load-failed' | translate }}</p>\n } @else if (shown.items; as items) {\n <ul class=\"history\" data-versions>\n @for (version of items; track version.version) {\n <li>\n <span class=\"badge\">{{ 'nh-assistant.admin.agents.version' | translate: { version: version.version } }}</span>\n <span class=\"badge\" [class.success]=\"version.status === 'live'\" [class.warning]=\"version.status === 'candidate'\">{{ 'nh-assistant.admin.run-config.status.' + version.status | translate }}</span>\n <span>{{ version.description }}</span>\n <span class=\"muted\" [attr.title]=\"accountTitle(version.createdBy)\">{{ 'nh-assistant.admin.run-config.changed' | translate: { user: nameOf(version.createdBy), date: (version.createdAt | date: 'medium') } }}</span>\n @if (version.reason) {\n <span class=\"muted\">{{ 'nh-assistant.admin.agents.state.reason' | translate: { reason: version.reason } }}</span>\n }\n </li>\n }\n </ul>\n } @else {\n <p class=\"state\" role=\"status\">{{ 'nh-assistant.admin.loading' | translate }}</p>\n }\n }\n }\n </li>\n }\n </ul>\n }\n </section>\n\n <section class=\"subsection\" [attr.aria-labelledby]=\"id + '-triggers-title'\" data-triggers>\n <header class=\"section-header\">\n <div>\n <h4 [id]=\"id + '-triggers-title'\">{{ 'nh-assistant.admin.run-config.triggers.title' | translate }}</h4>\n <p class=\"muted\">{{ 'nh-assistant.admin.run-config.triggers.description' | translate }}</p>\n </div>\n <button class=\"secondary\" type=\"button\" data-open-form=\"trigger\" [disabled]=\"busy() || !canAddRunData() || form() === 'trigger'\" (click)=\"addTrigger()\">\n <nh-assistant-icon name=\"plus\" />{{ 'nh-assistant.admin.run-config.triggers.add' | translate }}\n </button>\n </header>\n\n @if (form() === 'trigger') {\n <nh-assistant-trigger-form\n [agentId]=\"config.agentId\"\n [catalog]=\"catalog()\"\n [worksOn]=\"agent()\"\n [skills]=\"skillOptions()\"\n [trigger]=\"editingTrigger()\"\n [takenIds]=\"triggerIds()\"\n (saved)=\"onTriggerSaved($event.added)\"\n (cancelled)=\"cancel()\" />\n }\n\n @if (config.codeTriggers.length === 0 && config.dataTriggers.length === 0) {\n <p class=\"state\">{{ 'nh-assistant.admin.run-config.triggers.empty' | translate }}</p>\n } @else {\n <ul class=\"skills\">\n @for (trigger of config.codeTriggers; track trigger.triggerId) {\n <li class=\"skill\" [attr.data-code-trigger]=\"trigger.triggerId\">\n <div class=\"card-header\">\n <span class=\"mono\">{{ trigger.triggerId }}</span>\n <span class=\"badge\">{{ 'nh-assistant.admin.agents.source-code' | translate }}</span>\n <span class=\"badge\">{{ 'nh-assistant.admin.run-config.triggers.sources.' + trigger.source | translate }}</span>\n </div>\n @if (trigger.skillId) {\n <p class=\"muted\">{{ 'nh-assistant.admin.run-config.triggers.skill-value' | translate: { skill: skillName(trigger.skillId) } }}</p>\n }\n </li>\n }\n @for (trigger of config.dataTriggers; track trigger.triggerId) {\n <li class=\"skill\" [attr.data-data-trigger]=\"trigger.triggerId\">\n <div class=\"card-header\">\n <span class=\"mono\">{{ trigger.triggerId }}</span>\n <span class=\"badge\">{{ 'nh-assistant.admin.run-config.triggers.sources.' + trigger.source | translate }}</span>\n @if (trigger.shadow) {\n <span class=\"badge warning\" data-shadow-badge>{{ 'nh-assistant.admin.agents.shadow.badge' | translate }}</span>\n } @else {\n <span class=\"badge success\">{{ 'nh-assistant.admin.run-config.triggers.live-badge' | translate }}</span>\n }\n @if (!trigger.inEffect) {\n <span class=\"badge danger\">{{ 'nh-assistant.admin.run-config.out-of-effect' | translate }}</span>\n }\n </div>\n <div class=\"meta\">\n @if (trigger.source === 'event') {\n <span class=\"mono\">{{ trigger.event }}</span>\n } @else {\n <span class=\"mono\">{{ trigger.cron }} {{ trigger.timeZone ?? 'UTC' }}</span>\n @if (trigger.subjectSet) {\n <span class=\"mono\">{{ trigger.subjectSet }}</span>\n } @else {\n <!-- A schedule without a subject set runs for the whole application, per division for a per-division agent. -->\n <span data-trigger-target>{{ (agent().perDivision ? 'nh-assistant.admin.run-config.triggers.target.every-division' : 'nh-assistant.admin.run-config.triggers.target.application') | translate }}</span>\n }\n }\n <span>{{ (trigger.skillId ? 'nh-assistant.admin.run-config.triggers.skill-value' : 'nh-assistant.admin.run-config.triggers.agent-routes') | translate: { skill: skillName(trigger.skillId) } }}</span>\n <span>{{ 'nh-assistant.admin.run-config.triggers.conflicts.' + trigger.conflict | translate }}</span>\n <span>{{ 'nh-assistant.admin.agents.version' | translate: { version: trigger.version } }}</span>\n </div>\n <div class=\"card-actions\">\n <button class=\"secondary\" type=\"button\" [disabled]=\"busy()\" (click)=\"editTrigger(trigger)\">\n <nh-assistant-icon name=\"edit\" />{{ 'nh-assistant.admin.edit' | translate }}\n </button>\n <button class=\"secondary\" type=\"button\" [attr.data-trigger-shadow]=\"trigger.shadow ? 'live' : 'shadow'\" [disabled]=\"busy()\" (click)=\"setTriggerShadow(trigger, !trigger.shadow)\">\n {{ (trigger.shadow ? 'nh-assistant.admin.agents.shadow.action.live' : 'nh-assistant.admin.agents.shadow.action.shadow') | translate }}\n </button>\n @if (isConfirming('remove-trigger', trigger.triggerId)) {\n <button class=\"danger\" type=\"button\" [disabled]=\"busy()\" (click)=\"removeTrigger(trigger)\">{{ 'nh-assistant.admin.confirm-delete' | translate }}</button>\n <button class=\"secondary\" type=\"button\" (click)=\"confirming.set(null)\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n } @else {\n <button class=\"danger\" type=\"button\" [disabled]=\"busy()\" (click)=\"confirm('remove-trigger', trigger.triggerId)\">\n <nh-assistant-icon name=\"trash\" />{{ 'nh-assistant.admin.delete' | translate }}\n </button>\n }\n </div>\n @if (failed('trigger:' + trigger.triggerId)) {\n <p class=\"form-error\" role=\"alert\"><nh-assistant-icon name=\"warning\" />{{ errorKeys() | nhAssistantTranslate: error()!.code }}</p>\n }\n </li>\n }\n </ul>\n }\n </section>\n }\n</section>\n", styles: [":host{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){:host{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}:host{display:block;min-width:0;color:var(--nha-text);font-family:var(--nha-font);font-size:14px}:host fieldset{min-width:0;margin:0;border:0;padding:0}:host legend,:host .label,:host .field>label{display:block;margin-bottom:6px;padding:0;color:var(--nha-text);font-size:12px;font-weight:700}:host input[type=text],:host input[type=url],:host input[type=password],:host input[type=search],:host input[type=email],:host input[type=number],:host input[type=date],:host input[type=time],:host input[type=datetime-local],:host select,:host textarea{box-sizing:border-box;width:100%;min-width:0;min-height:38px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:8px 10px;color:var(--nha-text);background:var(--nha-background);font:inherit;font-size:13px}:host input[type=text]:focus-visible,:host input[type=url]:focus-visible,:host input[type=password]:focus-visible,:host input[type=search]:focus-visible,:host input[type=email]:focus-visible,:host input[type=number]:focus-visible,:host input[type=date]:focus-visible,:host input[type=time]:focus-visible,:host input[type=datetime-local]:focus-visible,:host select:focus-visible,:host textarea:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host input[type=text]:disabled,:host input[type=url]:disabled,:host input[type=password]:disabled,:host input[type=search]:disabled,:host input[type=email]:disabled,:host input[type=number]:disabled,:host input[type=date]:disabled,:host input[type=time]:disabled,:host input[type=datetime-local]:disabled,:host select:disabled,:host textarea:disabled{cursor:not-allowed;opacity:.6}:host input[type=text][aria-invalid=true],:host input[type=url][aria-invalid=true],:host input[type=password][aria-invalid=true],:host input[type=search][aria-invalid=true],:host input[type=email][aria-invalid=true],:host input[type=number][aria-invalid=true],:host input[type=date][aria-invalid=true],:host input[type=time][aria-invalid=true],:host input[type=datetime-local][aria-invalid=true],:host select[aria-invalid=true],:host textarea[aria-invalid=true]{border-color:var(--nha-danger)}:host textarea{line-height:1.45;resize:vertical}:host input[type=number]{font-variant-numeric:tabular-nums}:host .mono{font-family:var(--nha-mono);font-size:12px}:host .field{display:flex;flex-direction:column;min-width:0}:host .hint{display:flex;justify-content:space-between;gap:8px;margin:4px 2px 0;color:var(--nha-text-muted);font-size:11px}:host .counter{flex:none;font-variant-numeric:tabular-nums}:host .error-text{color:var(--nha-danger)}:host .field-error{display:flex;align-items:flex-start;gap:6px;margin:4px 2px 0;color:var(--nha-danger);font-size:12px;font-weight:600;overflow-wrap:anywhere}:host .field-error nh-assistant-icon{flex:none;margin-top:1px}:host .form-error{box-sizing:border-box;display:flex;align-items:flex-start;gap:8px;margin:0;border:1px solid var(--nha-danger);border-radius:var(--nha-radius-control);padding:8px 12px;color:var(--nha-text);background:var(--nha-danger-soft);font-size:13px;overflow-wrap:anywhere}:host .form-error ul{margin:4px 0 0;padding-left:18px}:host .message{box-sizing:border-box;display:flex;align-items:center;gap:8px;margin:0;border:1px solid transparent;border-radius:var(--nha-radius-control);padding:8px 12px;font-size:13px;overflow-wrap:anywhere}:host .message.error{border-color:var(--nha-danger);color:var(--nha-text);background:var(--nha-danger-soft)}:host .message.warning{border-color:var(--nha-warning);color:var(--nha-text);background:var(--nha-warning-soft)}:host .message.success{border-color:var(--nha-success);color:var(--nha-text);background:var(--nha-success-soft)}:host .message.info{border-color:var(--nha-border);color:var(--nha-text);background:var(--nha-surface)}:host .message nh-assistant-icon{flex:none}:host button.link{min-height:0;border:0;padding:0;color:var(--nha-accent, currentColor);background:none;font:inherit;text-decoration:underline;cursor:pointer}:host button.link:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host button.link:disabled{cursor:not-allowed;opacity:.55}:host .primary,:host .secondary,:host .danger{display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;gap:6px;min-height:36px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px 14px;font:inherit;font-size:13px;font-weight:700;white-space:nowrap;cursor:pointer}:host .primary:focus-visible,:host .secondary:focus-visible,:host .danger:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .primary:disabled,:host .secondary:disabled,:host .danger:disabled{cursor:not-allowed;opacity:.55}:host .primary{border-color:var(--nha-accent);color:var(--nha-accent-contrast);background:var(--nha-accent)}:host .primary:hover:not(:disabled){filter:brightness(1.12)}:host .secondary{color:var(--nha-text);background:transparent}:host .secondary:hover:not(:disabled){background:var(--nha-surface-strong)}:host .danger{border-color:var(--nha-danger);color:var(--nha-danger);background:transparent}:host .danger:hover:not(:disabled){background:var(--nha-danger-soft)}:host .icon-button{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:1px solid transparent;border-radius:var(--nha-radius-control);color:var(--nha-text-muted);background:transparent;cursor:pointer}:host .icon-button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .icon-button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}:host .icon-button:disabled{cursor:not-allowed;opacity:.5}:host .actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px}:host .badge{display:inline-flex;align-items:center;gap:4px;border-radius:999px;padding:2px 8px;color:var(--nha-text-muted);background:var(--nha-surface-strong);font-size:11px;font-weight:700;white-space:nowrap}:host .badge.success{color:var(--nha-success);background:var(--nha-success-soft)}:host .badge.warning{color:var(--nha-warning);background:var(--nha-warning-soft)}:host .badge.danger{color:var(--nha-danger);background:var(--nha-danger-soft)}:host .visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}.section{display:flex;flex-direction:column;gap:16px;min-width:0}.section-header{display:flex;flex-wrap:wrap;align-items:flex-start;justify-content:space-between;gap:12px}.section-header>div{min-width:0;max-width:72ch}h3{margin:0 0 4px;font-size:17px;font-weight:750}h4{margin:0 0 8px;font-size:14px;font-weight:700}.muted{margin:0;color:var(--nha-text-muted);font-size:13px}.state{display:flex;flex-direction:column;align-items:flex-start;gap:10px;margin:0;border:1px dashed var(--nha-border);border-radius:var(--nha-radius);padding:20px;color:var(--nha-text-muted);font-size:13px}.state p{margin:0}.stack{display:flex;flex-direction:column;gap:14px;min-width:0}.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,240px),1fr));gap:14px}.meta{display:flex;flex-wrap:wrap;align-items:center;gap:8px 12px;color:var(--nha-text-muted);font-size:12px;overflow-wrap:anywhere}textarea.large{min-height:260px}.catalog-error{flex-wrap:wrap}.grow{flex:1;min-width:0}.subsection{display:flex;flex-direction:column;gap:8px;border-top:1px solid var(--nha-border);padding-top:14px}.history{display:grid;gap:6px;margin:0;padding:0;list-style:none}.history li{display:flex;flex-wrap:wrap;align-items:center;gap:6px 12px;font-size:12px}.cards{display:grid;gap:10px;margin:0;padding:0;list-style:none}.card{display:flex;flex-direction:column;gap:8px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius);padding:14px;background:var(--nha-background)}.card.inactive{opacity:.8}.card-header{display:flex;flex-wrap:wrap;align-items:center;gap:8px;min-width:0}.card-header strong{font-size:14px;overflow-wrap:anywhere}.card-actions{display:flex;flex-wrap:wrap;gap:8px}.check{display:flex;align-items:flex-start;gap:8px;font-size:13px;cursor:pointer}.check input{width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.check.described>span{display:grid;gap:2px;min-width:0;overflow-wrap:anywhere}.checks{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));gap:6px 12px;min-width:0;overflow-wrap:anywhere}.chips{display:flex;flex-wrap:wrap;gap:6px;margin:0;padding:0;list-style:none}.chip{display:inline-flex;align-items:center;gap:2px;border:1px solid var(--nha-border);border-radius:999px;padding:2px 2px 2px 10px;background:var(--nha-surface);font-family:var(--nha-mono);font-size:12px}.chip 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;width:26px;height:26px;border-radius:999px}.chip button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.chip button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}.chip button:disabled{cursor:not-allowed;opacity:.5}.inline{display:flex;flex-wrap:wrap;gap:8px}.inline>input{flex:1 1 180px}.catalog{display:grid;gap:4px;max-height:260px;margin:8px 0 0;overflow-y:auto;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px;list-style:none}.catalog li label{display:flex;flex-wrap:wrap;align-items:center;gap:6px 8px;border-radius:8px;padding:6px;cursor:pointer}.catalog li label:hover{background:var(--nha-surface)}.catalog .tool-id{font-family:var(--nha-mono);font-size:12px;overflow-wrap:anywhere}.skills{display:grid;gap:8px;margin:0;padding:0;list-style:none}.skill{display:grid;gap:6px;border-left:3px solid var(--nha-border);padding:4px 0 4px 12px}.uses{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));gap:8px}.use{display:flex;align-items:flex-start;gap:10px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:10px 12px;cursor:pointer;overflow-wrap:anywhere}.use input{flex:none;width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.use>span{display:grid;gap:2px;min-width:0}.use strong{font-size:13px}.use.selected{border-color:var(--nha-highlight);background:var(--nha-surface)}.use:focus-within{outline:2px solid var(--nha-highlight);outline-offset:2px}.discovery{display:grid;gap:8px;padding:10px 12px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control)}.discovery dl{display:grid;grid-template-columns:max-content minmax(0,1fr);gap:4px 12px;margin:0}.discovery dt{color:var(--nha-text-muted)}.discovery dd{margin:0;overflow-wrap:anywhere}\n", ".header-actions{display:flex;flex-wrap:wrap;gap:8px}.export-hint{margin-top:4px;font-size:12px}.filters{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));align-items:start;gap:12px}.subsection h4,.list-header h4{margin:0}.list-header{display:flex;flex-wrap:wrap;align-items:baseline;justify-content:space-between;gap:4px 12px}.table-container{container-type:inline-size;min-width:0}.summary-table{width:100%;border-collapse:collapse;font-size:13px}.summary-table th,.summary-table td{border-bottom:1px solid var(--nha-border);padding:8px 10px;text-align:left;vertical-align:top}.summary-table thead th{color:var(--nha-text-muted);font-size:11px;font-weight:700;letter-spacing:.04em;text-transform:uppercase}.summary-table tbody th{font-weight:600}.summary-table .number{text-align:right;font-variant-numeric:tabular-nums}.summary-table .mono{overflow-wrap:anywhere}@container (max-width: 820px){.summary-table thead{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}.summary-table,.summary-table tbody,.summary-table tr{display:block}.summary-table tr{margin-bottom:8px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px 12px;background:var(--nha-background)}.summary-table tbody th,.summary-table td{display:grid;grid-template-columns:minmax(110px,40%) minmax(0,1fr);gap:8px;border:0;padding:4px 0;text-align:left}.summary-table tbody th:before,.summary-table td:before{content:attr(data-label);color:var(--nha-text-muted);font-size:12px;font-weight:700}}.badges{display:flex;flex-wrap:wrap;gap:4px;margin:0;padding:0;list-style:none}.badge .count{font-variant-numeric:tabular-nums;opacity:.85}.feedback-text{margin:0;border-left:3px solid var(--nha-border);padding:2px 0 2px 10px;font-size:13px;white-space:pre-wrap;overflow-wrap:anywhere}.proposal{margin:0;color:var(--nha-text-muted);font-size:12px;overflow-wrap:anywhere}.proposal-label{margin-right:4px;color:var(--nha-text);font-weight:700}.pager{display:flex;flex-wrap:wrap;align-items:center;gap:8px}\n", "h5{margin:0;font-size:15px;font-weight:750}.group{display:flex;flex-direction:column;gap:10px;min-width:0;border-top:1px solid var(--nha-border);padding-top:14px}.group>legend{float:left;width:100%;margin-bottom:2px;font-size:14px;font-weight:750}.choices{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,220px),1fr));gap:8px}.choice{display:flex;align-items:flex-start;gap:10px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:10px 12px;cursor:pointer;overflow-wrap:anywhere}.choice input{flex:none;width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.choice>span{display:grid;gap:2px;min-width:0}.choice strong{font-size:13px}.choice.selected{border-color:var(--nha-highlight);background:var(--nha-surface)}.choice:focus-within{outline:2px solid var(--nha-highlight);outline-offset:2px}.block{display:block;margin-top:2px}.advanced{border-top:1px solid var(--nha-border);padding-top:12px}.advanced>summary{width:fit-content;border-radius:6px;font-size:14px;font-weight:750;cursor:pointer}.advanced>summary:focus-visible{outline:2px solid var(--nha-highlight);outline-offset:2px}.advanced>.stack{margin-top:12px}.examples{display:flex;flex-wrap:wrap;gap:6px;margin:6px 0 0;padding:0;list-style:none}.examples button{border:1px solid var(--nha-border);border-radius:999px;padding:3px 10px;color:var(--nha-text);background:transparent;font:inherit;font-size:12px;cursor:pointer}.examples button:hover{background:var(--nha-surface-strong)}.examples button:focus-visible{outline:2px solid var(--nha-highlight);outline-offset:2px}.steps{display:flex;flex-wrap:wrap;gap:6px;margin:0;padding:0;list-style:none}.steps li{display:inline-flex;align-items:center;gap:6px;border-radius:999px;padding:4px 10px;color:var(--nha-text-muted);background:var(--nha-surface-strong);font-size:12px;font-weight:700}.steps li[aria-current=step]{color:var(--nha-accent-contrast);background:var(--nha-accent)}.steps li.done{color:var(--nha-success);background:var(--nha-success-soft)}.review{display:grid;grid-template-columns:minmax(0,max-content) minmax(0,1fr);gap:6px 16px;margin:0;font-size:13px}.review dt{color:var(--nha-text-muted)}.review dd{margin:0;overflow-wrap:anywhere}.tool-choice{display:flex;flex-direction:column;gap:2px;min-width:0;overflow-wrap:anywhere}@media(max-width:520px){.review{grid-template-columns:minmax(0,1fr)}.review dd{margin-bottom:6px}}.table-scroll{max-width:100%;overflow-x:auto}\n"] }]
|
|
3331
3576
|
}], ctorParameters: () => [], propDecorators: { agent: [{ type: i0.Input, args: [{ isSignal: true, alias: "agent", required: true }] }], startSetup: [{ type: i0.Input, args: [{ isSignal: true, alias: "startSetup", required: false }] }], tools: [{ type: i0.Input, args: [{ isSignal: true, alias: "tools", required: false }] }], servers: [{ type: i0.Input, args: [{ isSignal: true, alias: "servers", required: false }] }], changed: [{ type: i0.Output, args: ["changed"] }], editAgent: [{ type: i0.Output, args: ["editAgent"] }], changeAccount: [{ type: i0.Output, args: ["changeAccount"] }], openRuns: [{ type: i0.Output, args: ["openRuns"] }], openMcpServers: [{ type: i0.Output, args: ["openMcpServers"] }] } });
|
|
3332
3577
|
function blank(value) {
|
|
3333
3578
|
const trimmed = value.trim();
|
|
@@ -3341,7 +3586,8 @@ const maxStateReason = 500;
|
|
|
3341
3586
|
const accountRefusals = new Set(['assistant-service-account-not-found', 'assistant-service-account-not-used']);
|
|
3342
3587
|
/**
|
|
3343
3588
|
* Agent administration: code agents (override, reset) and agents created here (edit, delete),
|
|
3344
|
-
* and for every agent its run state with pause,
|
|
3589
|
+
* and for every agent and skill its run state with pause, switch off, stop and resume (switch on
|
|
3590
|
+
* for one that is off), its shadow mode and that of each
|
|
3345
3591
|
* skill, and the technical account of an agent that answers to one, each with an optional reason.
|
|
3346
3592
|
* Editing opens `nh-assistant-admin-agent-editor`; the feedback action opens
|
|
3347
3593
|
* `nh-assistant-agent-feedback-report` for the agent, the evaluation action of an agent with
|
|
@@ -3444,6 +3690,7 @@ class NhAssistantAdminAgentsComponent {
|
|
|
3444
3690
|
this.errorKeys = computed(() => _nhAssistantErrorKeys(this.error()), ...(ngDevMode ? [{ debugName: "errorKeys" }] : []));
|
|
3445
3691
|
this.codeTextKeys = codeTextKeys;
|
|
3446
3692
|
this.skillNameKeys = skillNameKeys;
|
|
3693
|
+
this.warningKeys = warningKeys;
|
|
3447
3694
|
this.skillDescriptionKeys = skillDescriptionKeys;
|
|
3448
3695
|
this.policyKeys = policyKeys;
|
|
3449
3696
|
this.readableId = readableId;
|
|
@@ -3527,13 +3774,13 @@ class NhAssistantAdminAgentsComponent {
|
|
|
3527
3774
|
}
|
|
3528
3775
|
/**
|
|
3529
3776
|
* Whether the agent can be run now: it runs on its own, so it has a run configuration, it is
|
|
3530
|
-
* neither stopped nor disabled in code, and the run catalog names what it can run on.
|
|
3531
|
-
* decides the rest when the run starts.
|
|
3777
|
+
* neither stopped nor disabled in code, and the run catalog names what it can run on. An agent
|
|
3778
|
+
* that is off still runs for a person. The server decides the rest when the run starts.
|
|
3532
3779
|
*/
|
|
3533
3780
|
canRunNow(agent) {
|
|
3534
3781
|
return this.catalog() !== null
|
|
3535
3782
|
&& this.hasRunConfiguration(agent)
|
|
3536
|
-
&& (agent.runState === 'active' || agent.runState === 'paused');
|
|
3783
|
+
&& (agent.runState === 'active' || agent.runState === 'paused' || agent.runState === 'off');
|
|
3537
3784
|
}
|
|
3538
3785
|
/** Opens the run-now dialog of the agent. */
|
|
3539
3786
|
runNow(agent) {
|
|
@@ -3699,11 +3946,13 @@ class NhAssistantAdminAgentsComponent {
|
|
|
3699
3946
|
stateActions(agent) {
|
|
3700
3947
|
switch (agent.runState) {
|
|
3701
3948
|
case 'active':
|
|
3702
|
-
return ['paused', 'stopped'];
|
|
3949
|
+
return ['paused', 'off', 'stopped'];
|
|
3703
3950
|
case 'paused':
|
|
3951
|
+
return ['active', 'off', 'stopped'];
|
|
3952
|
+
case 'off':
|
|
3704
3953
|
return ['active', 'stopped'];
|
|
3705
3954
|
case 'stopped':
|
|
3706
|
-
return ['active'];
|
|
3955
|
+
return ['active', 'off'];
|
|
3707
3956
|
default:
|
|
3708
3957
|
return [];
|
|
3709
3958
|
}
|
|
@@ -3712,17 +3961,30 @@ class NhAssistantAdminAgentsComponent {
|
|
|
3712
3961
|
skillActions(skill) {
|
|
3713
3962
|
switch (skill.runState) {
|
|
3714
3963
|
case 'active':
|
|
3715
|
-
return ['paused', 'stopped'];
|
|
3964
|
+
return ['paused', 'off', 'stopped'];
|
|
3716
3965
|
case 'paused':
|
|
3966
|
+
return ['active', 'off', 'stopped'];
|
|
3967
|
+
case 'off':
|
|
3717
3968
|
return ['active', 'stopped'];
|
|
3718
3969
|
default:
|
|
3719
3970
|
return ['active'];
|
|
3720
3971
|
}
|
|
3721
3972
|
}
|
|
3973
|
+
/**
|
|
3974
|
+
* The key of a state change's label, explanation or confirmation: making an agent or skill that is
|
|
3975
|
+
* off active again switches it on, which replays nothing, unlike a resume.
|
|
3976
|
+
*/
|
|
3977
|
+
stateKey(part, from, action) {
|
|
3978
|
+
return `nh-assistant.admin.agents.state.${part}.${from === 'off' && action === 'active' ? 'switch-on' : action}`;
|
|
3979
|
+
}
|
|
3980
|
+
/** The explanation of a state change of one skill. */
|
|
3981
|
+
skillStateExplainKey(change) {
|
|
3982
|
+
return `nh-assistant.admin.agents.skills.explain.${change.from === 'off' && change.state === 'active' ? 'switch-on' : change.state}`;
|
|
3983
|
+
}
|
|
3722
3984
|
/** Opens the reason form of a state change of the agent or, with a skill, of that skill. */
|
|
3723
3985
|
askState(agent, state, skill) {
|
|
3724
3986
|
this.closeForms();
|
|
3725
|
-
this.changingState.set({ agentId: agent.id, skillId: skill?.skillId, state });
|
|
3987
|
+
this.changingState.set({ agentId: agent.id, skillId: skill?.skillId, state, from: skill?.runState ?? agent.runState });
|
|
3726
3988
|
afterNextRender(() => this.reasonInput()?.nativeElement.focus(), { injector: this.injector });
|
|
3727
3989
|
}
|
|
3728
3990
|
/**
|
|
@@ -3957,7 +4219,7 @@ class NhAssistantAdminAgentsComponent {
|
|
|
3957
4219
|
void this.store.reloadStatus();
|
|
3958
4220
|
}
|
|
3959
4221
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: NhAssistantAdminAgentsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
3960
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.34", type: NhAssistantAdminAgentsComponent, isStandalone: true, selector: "nh-assistant-admin-agents", outputs: { openMcpServers: "openMcpServers" }, viewQueries: [{ propertyName: "backButton", first: true, predicate: ["back"], descendants: true, isSignal: true }, { propertyName: "reasonInput", first: true, predicate: ["reasonInput"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (editing() !== undefined) {\n <nh-assistant-admin-agent-editor\n [agent]=\"editing() ?? null\"\n [tools]=\"tools()\"\n [servers]=\"servers()\"\n [catalog]=\"catalog()\"\n [catalogUnavailable]=\"catalogError() !== null\"\n (saved)=\"onEditorSaved($event)\"\n (workRequested)=\"onWorkRequested($event)\"\n (cancelled)=\"onEditorCancelled()\" />\n} @else if (reporting(); as agent) {\n <div class=\"stack\">\n <div>\n <button #back class=\"secondary\" type=\"button\" (click)=\"closeReport()\">\n {{ 'nh-assistant.admin.feedback.back' | translate }}\n </button>\n </div>\n <p class=\"muted\">{{ 'nh-assistant.admin.feedback.agent' | translate: { name: (codeTextKeys(agent.source, agent.displayName) | nhAssistantTranslate: agent.displayName), id: agent.id } }}</p>\n <nh-assistant-agent-feedback-report [agentId]=\"agent.id\" />\n </div>\n} @else if (configuring(); as agent) {\n <div class=\"stack\">\n <div>\n <button #back class=\"secondary\" type=\"button\" (click)=\"closeRunConfiguration()\">\n {{ 'nh-assistant.admin.run-config.back' | translate }}\n </button>\n </div>\n <nh-assistant-agent-run-configuration\n [agent]=\"agent\"\n [startSetup]=\"setupOnOpen()\"\n [tools]=\"tools()\"\n [servers]=\"servers()\"\n (editAgent)=\"editFromRunConfiguration(agent)\"\n (openRuns)=\"runsFromRunConfiguration(agent)\"\n (changeAccount)=\"accountFromRunConfiguration(agent)\"\n (openMcpServers)=\"openMcpServers.emit()\" />\n </div>\n} @else if (viewingRuns(); as agent) {\n <div class=\"stack\">\n <div>\n <button #back class=\"secondary\" type=\"button\" (click)=\"closeRuns()\">\n {{ (returnTo() === agent.id ? 'nh-assistant.admin.runs.back-to-run-config' : 'nh-assistant.admin.runs.back') | translate }}\n </button>\n </div>\n <p class=\"muted\">{{ 'nh-assistant.admin.feedback.agent' | translate: { name: (codeTextKeys(agent.source, agent.displayName) | nhAssistantTranslate: agent.displayName), id: agent.id } }}</p>\n <nh-assistant-agent-run-history\n [agentId]=\"agent.id\"\n [subjectTypes]=\"catalog()?.subjectTypes ?? []\"\n (openConversation)=\"openConversation($event)\" />\n </div>\n} @else if (evaluating(); as agent) {\n <div class=\"stack\">\n <div>\n <button #back class=\"secondary\" type=\"button\" (click)=\"closeEvaluation()\">\n {{ 'nh-assistant.admin.evaluation.back' | translate }}\n </button>\n </div>\n <p class=\"muted\">{{ 'nh-assistant.admin.feedback.agent' | translate: { name: (codeTextKeys(agent.source, agent.displayName) | nhAssistantTranslate: agent.displayName), id: agent.id } }}</p>\n <nh-assistant-agent-evaluation [agentId]=\"agent.id\" [variants]=\"variantsOf(agent)\" />\n </div>\n} @else {\n <section class=\"section\" [attr.aria-labelledby]=\"id + '-title'\" [attr.aria-busy]=\"loading()\">\n <header class=\"section-header\">\n <div>\n <h3 [id]=\"id + '-title'\">{{ 'nh-assistant.admin.agents.title' | translate }}</h3>\n <p class=\"muted\">{{ 'nh-assistant.admin.agents.description' | translate }}</p>\n </div>\n <button class=\"primary\" type=\"button\" [disabled]=\"loading() || loadFailed()\" (click)=\"create()\">\n <nh-assistant-icon name=\"plus\" />{{ 'nh-assistant.admin.agents.new' | translate }}\n </button>\n </header>\n\n @if (error(); as failure) {\n @if (errorAgentId() === null) {\n <p class=\"message error\" role=\"alert\"><nh-assistant-icon name=\"warning\" />{{ errorKeys() | nhAssistantTranslate: failure.code }}</p>\n }\n }\n @if (!loading() && !loadFailed()) {\n @if (catalogError(); as failure) {\n <!-- Without the run catalog nothing that depends on it is offered: run now, what agents work on, groups. -->\n <div class=\"message warning catalog-error\" role=\"alert\" data-catalog-error>\n <nh-assistant-icon name=\"warning\" />\n <span class=\"grow\">\n {{ 'nh-assistant.admin.agents.catalog-failed' | translate }}\n {{ catalogErrorKeys() | nhAssistantTranslate: failure.code }}\n </span>\n <button class=\"secondary\" type=\"button\" data-catalog-retry [disabled]=\"catalogLoading()\" (click)=\"retryCatalog()\">\n <nh-assistant-icon name=\"refresh\" />{{ 'nh-assistant.admin.retry' | translate }}\n </button>\n </div>\n }\n <nh-assistant-account-names-error [names]=\"accountNameLookup\" />\n }\n\n @if (loading()) {\n <p class=\"state\" role=\"status\">{{ 'nh-assistant.admin.loading' | translate }}</p>\n } @else if (loadFailed()) {\n <div class=\"state\" role=\"alert\">\n <p>{{ 'nh-assistant.admin.load-failed' | translate }}</p>\n <button class=\"secondary\" type=\"button\" (click)=\"load()\">{{ 'nh-assistant.admin.retry' | translate }}</button>\n </div>\n } @else if (agents().length === 0) {\n <p class=\"state\">{{ 'nh-assistant.admin.agents.empty' | translate }}</p>\n } @else {\n <ul class=\"cards\">\n @for (agent of agents(); track agent.id) {\n <li class=\"card\" [class.inactive]=\"agent.runState !== 'active'\" [attr.aria-busy]=\"busyAgentId() === agent.id\">\n <div class=\"card-header\">\n <!-- An agent in code names and describes itself with translation keys. -->\n <strong>{{ (codeTextKeys(agent.source, agent.displayName) | nhAssistantTranslate: agent.displayName) }}</strong>\n <span class=\"mono muted\">{{ agent.id }}</span>\n <span class=\"badge\">{{ (agent.source === 'code' ? 'nh-assistant.admin.agents.source-code' : 'nh-assistant.admin.agents.source-admin') | translate }}</span>\n @if (agent.isOverridden) {\n <span class=\"badge warning\">{{ 'nh-assistant.admin.agents.overridden' | translate }}</span>\n }\n <span\n class=\"badge\"\n [class.success]=\"agent.runState === 'active'\"\n [class.warning]=\"agent.runState === 'paused'\"\n [class.danger]=\"agent.runState === 'stopped'\">{{ 'nh-assistant.admin.agents.state.' + agent.runState | translate }}</span>\n @if (agent.shadow?.shadow) {\n <span class=\"badge warning\" data-shadow-badge>{{ 'nh-assistant.admin.agents.shadow.badge' | translate }}</span>\n }\n @if (agent.offeredInChat === false) {\n <span class=\"badge\" data-hidden-from-chat>{{ 'nh-assistant.admin.agents.chat.hidden' | translate }}</span>\n }\n </div>\n @if (agent.description) {\n <p class=\"muted\">{{ codeTextKeys(agent.source, agent.description) | nhAssistantTranslate: agent.description }}</p>\n }\n @if (agent.runState === 'configured-off') {\n <p class=\"muted\">{{ 'nh-assistant.admin.agents.state.configured-off-hint' | translate }}</p>\n } @else if (agent.runState !== 'active') {\n <p class=\"muted\">\n @if (agent.stateChangedAt) {\n <span [attr.title]=\"accountTitle(agent.stateChangedBy)\">{{ 'nh-assistant.admin.agents.state.changed' | translate: { user: accountName(agent.stateChangedBy), date: (agent.stateChangedAt | date: 'medium') } }}</span>\n }\n @if (agent.stateReason) {\n {{ 'nh-assistant.admin.agents.state.reason' | translate: { reason: agent.stateReason } }}\n }\n </p>\n @if (agent.runState === 'paused') {\n <p class=\"muted\">{{ 'nh-assistant.admin.agents.state.waiting' | translate: { count: agent.waitingInputs } }}</p>\n }\n }\n @if (agent.shadow; as shadow) {\n @if (shadow.shadow) {\n <p class=\"muted\">\n {{ (shadow.locked ? 'nh-assistant.admin.agents.shadow.locked' : 'nh-assistant.admin.agents.shadow.hint') | translate }}\n @if (shadow.changedAt && shadow.override !== null) {\n <span [attr.title]=\"accountTitle(shadow.changedBy)\">{{ 'nh-assistant.admin.agents.shadow.changed' | translate: { user: accountName(shadow.changedBy), date: (shadow.changedAt | date: 'medium') } }}</span>\n }\n @if (shadow.reason && shadow.override !== null) {\n {{ 'nh-assistant.admin.agents.state.reason' | translate: { reason: shadow.reason } }}\n }\n </p>\n }\n }\n @if (agent.serviceAccount; as account) {\n <p class=\"muted\" data-service-account>\n <span [attr.title]=\"accountTitle(account.actorId)\">{{ 'nh-assistant.admin.agents.service-account.label' | translate: { account: accountName(account.actorId) } }}</span>\n @if (account.overridden) {\n <span [attr.title]=\"accountTitle(account.configuredActorId)\">{{ 'nh-assistant.admin.agents.service-account.configured' | translate: { account: accountName(account.configuredActorId) } }}</span>\n @if (account.reason) {\n {{ 'nh-assistant.admin.agents.state.reason' | translate: { reason: account.reason } }}\n }\n }\n </p>\n }\n @if (worksOn(agent); as works) {\n <!-- What the agent works on; an agent in code shows its code setting. -->\n <p class=\"muted\" data-works-on>\n <span>{{ 'nh-assistant.admin.agents.works-on.label' | translate }}</span>\n <strong>\n @switch (works.only) {\n @case ('all') { {{ 'nh-assistant.admin.agents.works-on.all' | translate }} }\n @case ('types') { {{ works.types.join(', ') }} }\n @default { {{ 'nh-assistant.admin.agents.works-on.application' | translate }} }\n }\n </strong>\n @if (works.application && works.only !== 'application') {\n <span>{{ 'nh-assistant.admin.agents.works-on.and-application' | translate }}</span>\n }\n @if (works.perDivision) {\n <span>{{ 'nh-assistant.admin.agents.works-on.per-division' | translate }}</span>\n }\n @if (works.undeclared) {\n <span>{{ 'nh-assistant.admin.agents.works-on.undeclared' | translate }}</span>\n }\n </p>\n @if (works.all && works.known.length > 0) {\n <p class=\"muted\" data-known-types>{{ 'nh-assistant.admin.agents.works-on.known' | translate: { types: works.known.join(', ') } }}</p>\n }\n @if (works.runPolicy || works.viewPolicy) {\n <!-- Each policy is an item of its own, wrapping on narrow screens. -->\n <div class=\"meta\" data-works-on-policies>\n @if (works.runPolicy; as policy) {\n <span>{{ 'nh-assistant.admin.agents.works-on.run-policy' | translate: { policy: (policyKeys(policy) | nhAssistantTranslate: policy) } }}</span>\n }\n @if (works.viewPolicy; as policy) {\n <span>{{ 'nh-assistant.admin.agents.works-on.view-policy' | translate: { policy: (policyKeys(policy) | nhAssistantTranslate: policy) } }}</span>\n }\n </div>\n }\n }\n @if (groupNames(agent); as names) {\n <p class=\"muted\" data-agent-groups>{{ 'nh-assistant.admin.agents.groups' | translate: { groups: names } }}</p>\n }\n <div class=\"meta\">\n <span>{{ 'nh-assistant.admin.agents.autonomy.' + agent.autonomy | translate }}</span>\n <span>{{ (agent.toolSelectors.length === 1 ? 'nh-assistant.admin.agents.tool-count-one' : 'nh-assistant.admin.agents.tool-count') | translate: { count: agent.toolSelectors.length } }}</span>\n <span>{{ (agent.mcpServerIds.length === 1 ? 'nh-assistant.admin.agents.server-count-one' : 'nh-assistant.admin.agents.server-count') | translate: { count: agent.mcpServerIds.length } }}</span>\n <span>{{ 'nh-assistant.admin.agents.version' | translate: { version: agent.version } }}</span>\n @if (agent.requiredPolicy; as policy) {\n <!-- The application may name its policies; otherwise the policy shows as it is called in code. -->\n <span data-required-policy>\n {{ 'nh-assistant.admin.agents.required-policy' | translate }}\n @if (policyKeys(policy) | nhAssistantTranslate; as text) {\n {{ text }}\n } @else {\n <code class=\"mono\">{{ policy }}</code>\n }\n </span>\n }\n </div>\n <div class=\"card-actions\">\n <button class=\"secondary\" type=\"button\" [disabled]=\"busyAgentId() !== null\" (click)=\"edit(agent)\">\n <nh-assistant-icon name=\"edit\" />{{ 'nh-assistant.admin.edit' | translate }}\n </button>\n @for (action of stateActions(agent); track action) {\n <button\n type=\"button\"\n [class.secondary]=\"action !== 'stopped'\"\n [class.danger]=\"action === 'stopped'\"\n [attr.aria-expanded]=\"isChangingState(agent.id, action)\"\n [disabled]=\"busyAgentId() !== null\"\n (click)=\"askState(agent, action)\">\n {{ 'nh-assistant.admin.agents.state.action.' + action | translate }}\n </button>\n }\n @if (agent.runState !== 'configured-off') {\n @for (mode of shadowActions(agent.shadow); track mode) {\n <button\n class=\"secondary\"\n type=\"button\"\n [attr.data-shadow-action]=\"mode\"\n [attr.aria-expanded]=\"isChangingShadow(agent.id, mode)\"\n [disabled]=\"busyAgentId() !== null\"\n (click)=\"askShadow(agent, mode)\">\n {{ shadowActionKey(agent, mode) | translate }}\n </button>\n }\n }\n @if (agent.serviceAccount; as account) {\n <button\n class=\"secondary\"\n type=\"button\"\n data-account-action=\"set\"\n [attr.aria-expanded]=\"isChangingAccount(agent.id, 'set')\"\n [disabled]=\"busyAgentId() !== null\"\n (click)=\"askAccount(agent, 'set')\">\n {{ 'nh-assistant.admin.agents.service-account.action.set' | translate }}\n </button>\n @if (account.overridden) {\n <button\n class=\"secondary\"\n type=\"button\"\n data-account-action=\"code\"\n [attr.aria-expanded]=\"isChangingAccount(agent.id, 'code')\"\n [disabled]=\"busyAgentId() !== null\"\n (click)=\"askAccount(agent, 'code')\">\n {{ 'nh-assistant.admin.agents.service-account.action.code' | translate }}\n </button>\n }\n }\n @if (hasRunConfiguration(agent)) {\n <button class=\"secondary\" type=\"button\" [attr.data-run-config-agent]=\"agent.id\" (click)=\"openRunConfiguration(agent)\">\n <nh-assistant-icon name=\"list\" />{{ 'nh-assistant.admin.agents.run-config' | translate }}\n </button>\n }\n @if (hasRunConfiguration(agent)) {\n <button class=\"secondary\" type=\"button\" [attr.data-runs-agent]=\"agent.id\" (click)=\"openRuns(agent)\">\n <nh-assistant-icon name=\"list\" />{{ 'nh-assistant.admin.agents.runs' | translate }}\n </button>\n }\n @if (canRunNow(agent)) {\n <button\n class=\"secondary\"\n type=\"button\"\n aria-haspopup=\"dialog\"\n [attr.data-run-now-agent]=\"agent.id\"\n [disabled]=\"busyAgentId() !== null\"\n (click)=\"runNow(agent)\">\n <nh-assistant-icon name=\"send\" />{{ 'nh-assistant.admin.agents.run-now' | translate }}\n </button>\n }\n <button class=\"secondary\" type=\"button\" [attr.data-feedback-agent]=\"agent.id\" (click)=\"openReport(agent)\">\n <nh-assistant-icon name=\"list\" />{{ 'nh-assistant.admin.agents.feedback' | translate }}\n </button>\n @if (variantsOf(agent).length > 0) {\n <button class=\"secondary\" type=\"button\" [attr.data-evaluation-agent]=\"agent.id\" (click)=\"openEvaluation(agent)\">\n <nh-assistant-icon name=\"list\" />{{ 'nh-assistant.admin.agents.evaluation' | translate }}\n </button>\n }\n @if (agent.source === 'code' && agent.isOverridden) {\n @if (isConfirming(agent.id, 'reset')) {\n <span class=\"muted\">{{ 'nh-assistant.admin.agents.reset-keeps-state' | translate }}</span>\n <button class=\"danger\" type=\"button\" (click)=\"reset(agent)\">{{ 'nh-assistant.admin.agents.confirm-reset' | translate }}</button>\n <button class=\"secondary\" type=\"button\" (click)=\"confirming.set(null)\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n } @else {\n <button class=\"secondary\" type=\"button\" [disabled]=\"busyAgentId() !== null\" (click)=\"confirm(agent.id, 'reset')\">\n <nh-assistant-icon name=\"refresh\" />{{ 'nh-assistant.admin.agents.reset' | translate }}\n </button>\n }\n }\n @if (agent.source === 'admin') {\n @if (isConfirming(agent.id, 'delete')) {\n <button class=\"danger\" type=\"button\" (click)=\"remove(agent)\">{{ 'nh-assistant.admin.confirm-delete' | translate }}</button>\n <button class=\"secondary\" type=\"button\" (click)=\"confirming.set(null)\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n } @else {\n <button class=\"danger\" type=\"button\" [disabled]=\"busyAgentId() !== null\" (click)=\"confirm(agent.id, 'delete')\">\n <nh-assistant-icon name=\"trash\" />{{ 'nh-assistant.admin.delete' | translate }}\n </button>\n }\n }\n </div>\n @if (agent.skills?.length) {\n <ul class=\"skills\" [attr.aria-label]=\"'nh-assistant.admin.agents.skills.title' | translate\">\n @for (skill of agent.skills; track skill.skillId) {\n <li class=\"skill\" [attr.data-skill]=\"skill.skillId\">\n <div class=\"card-header\">\n <strong>{{ (skillNameKeys(agent.id, skill.skillId) | nhAssistantTranslate: readableId(skill.skillId)) }}</strong>\n <span class=\"mono muted\">{{ skill.skillId }}</span>\n <span\n class=\"badge\"\n [class.success]=\"skill.runState === 'active'\"\n [class.warning]=\"skill.runState === 'paused'\"\n [class.danger]=\"skill.runState === 'stopped'\">{{ 'nh-assistant.admin.agents.state.' + skill.runState | translate }}</span>\n @if (skill.shadow?.shadow) {\n <span class=\"badge warning\" data-shadow-badge>{{ 'nh-assistant.admin.agents.shadow.badge' | translate }}</span>\n }\n </div>\n <p class=\"muted\">{{ skillDescriptionKeys(agent.id, skill.skillId) | nhAssistantTranslate: skill.description }}</p>\n <div class=\"meta\">\n <span>{{ 'nh-assistant.admin.agents.skills.usage' | translate: { calls: skill.toolCallsToday, tokens: skill.tokensToday } }}</span>\n @if (skill.dailyToolCalls !== null) {\n <span>{{ 'nh-assistant.admin.agents.skills.budget' | translate: { calls: skill.dailyToolCalls, tokens: skill.dailyTokens } }}</span>\n }\n @if (skill.runState === 'paused') {\n <span>{{ 'nh-assistant.admin.agents.state.waiting' | translate: { count: skill.waitingInputs } }}</span>\n }\n @if (skill.reason) {\n <span>{{ 'nh-assistant.admin.agents.state.reason' | translate: { reason: skill.reason } }}</span>\n }\n </div>\n @if (agent.runState !== 'configured-off') {\n <div class=\"card-actions\">\n @for (action of skillActions(skill); track action) {\n <button\n type=\"button\"\n [class.secondary]=\"action !== 'stopped'\"\n [class.danger]=\"action === 'stopped'\"\n [attr.aria-label]=\"('nh-assistant.admin.agents.state.action.' + action | translate) + ' ' + (skillNameKeys(agent.id, skill.skillId) | nhAssistantTranslate: readableId(skill.skillId))\"\n [attr.aria-expanded]=\"isChangingState(agent.id, action, skill.skillId)\"\n [disabled]=\"busyAgentId() !== null\"\n (click)=\"askState(agent, action, skill)\">\n {{ 'nh-assistant.admin.agents.state.action.' + action | translate }}\n </button>\n }\n @for (mode of shadowActions(skill.shadow); track mode) {\n <button\n class=\"secondary\"\n type=\"button\"\n [attr.data-shadow-action]=\"mode\"\n [attr.aria-label]=\"(shadowActionKey(agent, mode) | translate) + ' ' + (skillNameKeys(agent.id, skill.skillId) | nhAssistantTranslate: readableId(skill.skillId))\"\n [attr.aria-expanded]=\"isChangingShadow(agent.id, mode, skill.skillId)\"\n [disabled]=\"busyAgentId() !== null\"\n (click)=\"askShadow(agent, mode, skill)\">\n {{ shadowActionKey(agent, mode) | translate }}\n </button>\n }\n </div>\n }\n </li>\n }\n </ul>\n }\n @if (changingState(); as change) {\n @if (change.agentId === agent.id) {\n <form class=\"stack\" (submit)=\"$event.preventDefault(); applyState(agent)\">\n @if (change.skillId) {\n <p class=\"muted\">{{ 'nh-assistant.admin.agents.skills.explain.' + change.state | translate: { skill: (skillNameKeys(agent.id, change.skillId) | nhAssistantTranslate: readableId(change.skillId)) } }}</p>\n } @else {\n <p class=\"muted\">{{ 'nh-assistant.admin.agents.state.explain.' + change.state | translate }}</p>\n }\n <div class=\"field\">\n <label [for]=\"id + '-' + agent.id + '-reason'\">{{ 'nh-assistant.admin.agents.state.note-label' | translate }}</label>\n <input\n #reasonInput\n type=\"text\"\n autocomplete=\"off\"\n [id]=\"id + '-' + agent.id + '-reason'\"\n [attr.aria-describedby]=\"id + '-' + agent.id + '-reason-hint'\"\n [maxLength]=\"maxReason\"\n [value]=\"stateReason()\"\n (input)=\"stateReason.set(value($event))\" />\n <p class=\"hint\" [id]=\"id + '-' + agent.id + '-reason-hint'\">{{ 'nh-assistant.admin.agents.state.note-hint' | translate }}</p>\n </div>\n <div class=\"card-actions\">\n <button type=\"submit\" [class.primary]=\"change.state !== 'stopped'\" [class.danger]=\"change.state === 'stopped'\" [disabled]=\"busyAgentId() !== null\">\n {{ 'nh-assistant.admin.agents.state.confirm.' + change.state | translate }}\n </button>\n <button class=\"secondary\" type=\"button\" (click)=\"changingState.set(null)\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n </div>\n </form>\n }\n }\n @if (changingShadow(); as change) {\n @if (change.agentId === agent.id) {\n <form class=\"stack\" data-shadow-form (submit)=\"$event.preventDefault(); applyShadow(agent)\">\n @if (change.skillId) {\n <p class=\"muted\">{{ shadowExplainKey(agent, change.mode, true) | translate: { skill: (skillNameKeys(agent.id, change.skillId) | nhAssistantTranslate: readableId(change.skillId)), mode: (ownShadowModeKey(agent, change.skillId) | translate) } }}</p>\n } @else {\n <p class=\"muted\">{{ shadowExplainKey(agent, change.mode, false) | translate: { mode: (ownShadowModeKey(agent) | translate) } }}</p>\n }\n <div class=\"field\">\n <label [for]=\"id + '-' + agent.id + '-shadow-reason'\">{{ 'nh-assistant.admin.agents.state.note-label' | translate }}</label>\n <input\n #reasonInput\n type=\"text\"\n autocomplete=\"off\"\n [id]=\"id + '-' + agent.id + '-shadow-reason'\"\n [attr.aria-describedby]=\"id + '-' + agent.id + '-shadow-reason-hint'\"\n [maxLength]=\"maxReason\"\n [value]=\"stateReason()\"\n (input)=\"stateReason.set(value($event))\" />\n <p class=\"hint\" [id]=\"id + '-' + agent.id + '-shadow-reason-hint'\">{{ 'nh-assistant.admin.agents.state.note-hint' | translate }}</p>\n </div>\n <div class=\"card-actions\">\n <button type=\"submit\" class=\"primary\" [disabled]=\"busyAgentId() !== null\">\n {{ 'nh-assistant.admin.agents.shadow.confirm.' + change.mode | translate }}\n </button>\n <button class=\"secondary\" type=\"button\" (click)=\"changingShadow.set(null)\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n </div>\n </form>\n }\n }\n @if (changingAccount(); as change) {\n @if (change.agentId === agent.id) {\n <form class=\"stack\" data-account-form (submit)=\"$event.preventDefault(); applyAccount(agent)\">\n <p class=\"muted\" [attr.title]=\"accountTitle(agent.serviceAccount?.configuredActorId)\">{{ 'nh-assistant.admin.agents.service-account.explain.' + change.mode | translate: { account: accountName(agent.serviceAccount?.configuredActorId) } }}</p>\n @if (change.mode === 'set') {\n <div class=\"field\">\n <label [for]=\"id + '-' + agent.id + '-account'\">{{ (accountSearch() ? 'nh-assistant.admin.agents.service-account.input-label-search' : 'nh-assistant.admin.agents.service-account.input-label') | translate }}</label>\n <nh-assistant-account-picker\n [inputId]=\"id + '-' + agent.id + '-account'\"\n [value]=\"accountId()\"\n [searchable]=\"accountSearch()\"\n [invalid]=\"accountError() !== null\"\n [describedBy]=\"accountError() ? id + '-' + agent.id + '-account-error' : null\"\n (valueChange)=\"onAccountChange($event)\" />\n @if (accountError(); as failure) {\n <p class=\"field-error\" data-account-error [id]=\"id + '-' + agent.id + '-account-error'\">\n <nh-assistant-icon name=\"warning\" />{{ accountErrorKeys() | nhAssistantTranslate: failure.code }}\n </p>\n }\n </div>\n }\n <div class=\"field\">\n <label [for]=\"id + '-' + agent.id + '-account-reason'\">{{ 'nh-assistant.admin.agents.state.note-label' | translate }}</label>\n <input\n type=\"text\"\n autocomplete=\"off\"\n [id]=\"id + '-' + agent.id + '-account-reason'\"\n [attr.aria-describedby]=\"id + '-' + agent.id + '-account-reason-hint'\"\n [maxLength]=\"maxReason\"\n [value]=\"stateReason()\"\n (input)=\"stateReason.set(value($event))\" />\n <p class=\"hint\" [id]=\"id + '-' + agent.id + '-account-reason-hint'\">{{ 'nh-assistant.admin.agents.state.note-hint' | translate }}</p>\n </div>\n <div class=\"card-actions\">\n <button type=\"submit\" class=\"primary\" [disabled]=\"busyAgentId() !== null\">\n {{ 'nh-assistant.admin.agents.service-account.confirm.' + change.mode | translate }}\n </button>\n <button class=\"secondary\" type=\"button\" (click)=\"changingAccount.set(null)\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n </div>\n </form>\n }\n }\n @if (error(); as failure) {\n @if (errorAgentId() === agent.id) {\n <p class=\"form-error\" role=\"alert\" data-agent-error><nh-assistant-icon name=\"warning\" />{{ errorKeys() | nhAssistantTranslate: failure.code }}</p>\n }\n }\n </li>\n }\n </ul>\n }\n @if (runNowAgent(); as runAgent) {\n <nh-assistant-run-now-dialog\n [agent]=\"runAgent\"\n [subjectTypes]=\"catalog()?.subjectTypes ?? []\"\n [divisions]=\"catalog()?.divisions === true\"\n (openConversation)=\"openConversation($event)\"\n (closed)=\"runningNow.set(null)\" />\n }\n </section>\n}\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;min-width:0;color:var(--nha-text);font-family:var(--nha-font);font-size:14px}:host fieldset{min-width:0;margin:0;border:0;padding:0}:host legend,:host .label,:host .field>label{display:block;margin-bottom:6px;padding:0;color:var(--nha-text);font-size:12px;font-weight:700}:host input[type=text],:host input[type=url],:host input[type=password],:host input[type=search],:host input[type=email],:host input[type=number],:host input[type=date],:host input[type=time],:host input[type=datetime-local],:host select,:host textarea{box-sizing:border-box;width:100%;min-width:0;min-height:38px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:8px 10px;color:var(--nha-text);background:var(--nha-background);font:inherit;font-size:13px}:host input[type=text]:focus-visible,:host input[type=url]:focus-visible,:host input[type=password]:focus-visible,:host input[type=search]:focus-visible,:host input[type=email]:focus-visible,:host input[type=number]:focus-visible,:host input[type=date]:focus-visible,:host input[type=time]:focus-visible,:host input[type=datetime-local]:focus-visible,:host select:focus-visible,:host textarea:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host input[type=text]:disabled,:host input[type=url]:disabled,:host input[type=password]:disabled,:host input[type=search]:disabled,:host input[type=email]:disabled,:host input[type=number]:disabled,:host input[type=date]:disabled,:host input[type=time]:disabled,:host input[type=datetime-local]:disabled,:host select:disabled,:host textarea:disabled{cursor:not-allowed;opacity:.6}:host input[type=text][aria-invalid=true],:host input[type=url][aria-invalid=true],:host input[type=password][aria-invalid=true],:host input[type=search][aria-invalid=true],:host input[type=email][aria-invalid=true],:host input[type=number][aria-invalid=true],:host input[type=date][aria-invalid=true],:host input[type=time][aria-invalid=true],:host input[type=datetime-local][aria-invalid=true],:host select[aria-invalid=true],:host textarea[aria-invalid=true]{border-color:var(--nha-danger)}:host textarea{line-height:1.45;resize:vertical}:host input[type=number]{font-variant-numeric:tabular-nums}:host .mono{font-family:var(--nha-mono);font-size:12px}:host .field{display:flex;flex-direction:column;min-width:0}:host .hint{display:flex;justify-content:space-between;gap:8px;margin:4px 2px 0;color:var(--nha-text-muted);font-size:11px}:host .counter{flex:none;font-variant-numeric:tabular-nums}:host .error-text{color:var(--nha-danger)}:host .field-error{display:flex;align-items:flex-start;gap:6px;margin:4px 2px 0;color:var(--nha-danger);font-size:12px;font-weight:600;overflow-wrap:anywhere}:host .field-error nh-assistant-icon{flex:none;margin-top:1px}:host .form-error{box-sizing:border-box;display:flex;align-items:flex-start;gap:8px;margin:0;border:1px solid var(--nha-danger);border-radius:var(--nha-radius-control);padding:8px 12px;color:var(--nha-text);background:var(--nha-danger-soft);font-size:13px;overflow-wrap:anywhere}:host .form-error ul{margin:4px 0 0;padding-left:18px}:host .message{box-sizing:border-box;display:flex;align-items:center;gap:8px;margin:0;border:1px solid transparent;border-radius:var(--nha-radius-control);padding:8px 12px;font-size:13px;overflow-wrap:anywhere}:host .message.error{border-color:var(--nha-danger);color:var(--nha-text);background:var(--nha-danger-soft)}:host .message.warning{border-color:var(--nha-warning);color:var(--nha-text);background:var(--nha-warning-soft)}:host .message.success{border-color:var(--nha-success);color:var(--nha-text);background:var(--nha-success-soft)}:host .message.info{border-color:var(--nha-border);color:var(--nha-text);background:var(--nha-surface)}:host .message nh-assistant-icon{flex:none}:host button.link{min-height:0;border:0;padding:0;color:var(--nha-accent, currentColor);background:none;font:inherit;text-decoration:underline;cursor:pointer}:host button.link:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host button.link:disabled{cursor:not-allowed;opacity:.55}:host .primary,:host .secondary,:host .danger{display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;gap:6px;min-height:36px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px 14px;font:inherit;font-size:13px;font-weight:700;white-space:nowrap;cursor:pointer}:host .primary:focus-visible,:host .secondary:focus-visible,:host .danger:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .primary:disabled,:host .secondary:disabled,:host .danger:disabled{cursor:not-allowed;opacity:.55}:host .primary{border-color:var(--nha-accent);color:var(--nha-accent-contrast);background:var(--nha-accent)}:host .primary:hover:not(:disabled){filter:brightness(1.12)}:host .secondary{color:var(--nha-text);background:transparent}:host .secondary:hover:not(:disabled){background:var(--nha-surface-strong)}:host .danger{border-color:var(--nha-danger);color:var(--nha-danger);background:transparent}:host .danger:hover:not(:disabled){background:var(--nha-danger-soft)}:host .icon-button{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:1px solid transparent;border-radius:var(--nha-radius-control);color:var(--nha-text-muted);background:transparent;cursor:pointer}:host .icon-button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .icon-button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}:host .icon-button:disabled{cursor:not-allowed;opacity:.5}:host .actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px}:host .badge{display:inline-flex;align-items:center;gap:4px;border-radius:999px;padding:2px 8px;color:var(--nha-text-muted);background:var(--nha-surface-strong);font-size:11px;font-weight:700;white-space:nowrap}:host .badge.success{color:var(--nha-success);background:var(--nha-success-soft)}:host .badge.warning{color:var(--nha-warning);background:var(--nha-warning-soft)}:host .badge.danger{color:var(--nha-danger);background:var(--nha-danger-soft)}:host .visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}.section{display:flex;flex-direction:column;gap:16px;min-width:0}.section-header{display:flex;flex-wrap:wrap;align-items:flex-start;justify-content:space-between;gap:12px}.section-header>div{min-width:0;max-width:72ch}h3{margin:0 0 4px;font-size:17px;font-weight:750}h4{margin:0 0 8px;font-size:14px;font-weight:700}.muted{margin:0;color:var(--nha-text-muted);font-size:13px}.state{display:flex;flex-direction:column;align-items:flex-start;gap:10px;margin:0;border:1px dashed var(--nha-border);border-radius:var(--nha-radius);padding:20px;color:var(--nha-text-muted);font-size:13px}.state p{margin:0}.stack{display:flex;flex-direction:column;gap:14px;min-width:0}.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,240px),1fr));gap:14px}.meta{display:flex;flex-wrap:wrap;align-items:center;gap:8px 12px;color:var(--nha-text-muted);font-size:12px;overflow-wrap:anywhere}textarea.large{min-height:260px}.catalog-error{flex-wrap:wrap}.grow{flex:1;min-width:0}.subsection{display:flex;flex-direction:column;gap:8px;border-top:1px solid var(--nha-border);padding-top:14px}.history{display:grid;gap:6px;margin:0;padding:0;list-style:none}.history li{display:flex;flex-wrap:wrap;align-items:center;gap:6px 12px;font-size:12px}.cards{display:grid;gap:10px;margin:0;padding:0;list-style:none}.card{display:flex;flex-direction:column;gap:8px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius);padding:14px;background:var(--nha-background)}.card.inactive{opacity:.8}.card-header{display:flex;flex-wrap:wrap;align-items:center;gap:8px;min-width:0}.card-header strong{font-size:14px;overflow-wrap:anywhere}.card-actions{display:flex;flex-wrap:wrap;gap:8px}.check{display:flex;align-items:flex-start;gap:8px;font-size:13px;cursor:pointer}.check input{width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.check.described>span{display:grid;gap:2px;min-width:0;overflow-wrap:anywhere}.checks{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));gap:6px 12px;min-width:0;overflow-wrap:anywhere}.chips{display:flex;flex-wrap:wrap;gap:6px;margin:0;padding:0;list-style:none}.chip{display:inline-flex;align-items:center;gap:2px;border:1px solid var(--nha-border);border-radius:999px;padding:2px 2px 2px 10px;background:var(--nha-surface);font-family:var(--nha-mono);font-size:12px}.chip 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;width:26px;height:26px;border-radius:999px}.chip button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.chip button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}.chip button:disabled{cursor:not-allowed;opacity:.5}.inline{display:flex;flex-wrap:wrap;gap:8px}.inline>input{flex:1 1 180px}.catalog{display:grid;gap:4px;max-height:260px;margin:8px 0 0;overflow-y:auto;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px;list-style:none}.catalog li label{display:flex;flex-wrap:wrap;align-items:center;gap:6px 8px;border-radius:8px;padding:6px;cursor:pointer}.catalog li label:hover{background:var(--nha-surface)}.catalog .tool-id{font-family:var(--nha-mono);font-size:12px;overflow-wrap:anywhere}.skills{display:grid;gap:8px;margin:0;padding:0;list-style:none}.skill{display:grid;gap:6px;border-left:3px solid var(--nha-border);padding:4px 0 4px 12px}.uses{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));gap:8px}.use{display:flex;align-items:flex-start;gap:10px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:10px 12px;cursor:pointer;overflow-wrap:anywhere}.use input{flex:none;width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.use>span{display:grid;gap:2px;min-width:0}.use strong{font-size:13px}.use.selected{border-color:var(--nha-highlight);background:var(--nha-surface)}.use:focus-within{outline:2px solid var(--nha-highlight);outline-offset:2px}.discovery{display:grid;gap:8px;padding:10px 12px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control)}.discovery dl{display:grid;grid-template-columns:max-content minmax(0,1fr);gap:4px 12px;margin:0}.discovery dt{color:var(--nha-text-muted)}.discovery dd{margin:0;overflow-wrap:anywhere}\n"], dependencies: [{ kind: "component", type: _NhAssistantIconComponent, selector: "nh-assistant-icon", inputs: ["name"] }, { kind: "component", type: NhAssistantAdminAgentEditorComponent, selector: "nh-assistant-admin-agent-editor", inputs: ["agent", "tools", "servers", "catalog", "catalogUnavailable"], outputs: ["saved", "workRequested", "cancelled"] }, { kind: "component", type: NhAssistantAgentEvaluationComponent, selector: "nh-assistant-agent-evaluation", inputs: ["agentId", "variants"] }, { kind: "component", type: NhAssistantAgentFeedbackReportComponent, selector: "nh-assistant-agent-feedback-report", inputs: ["agentId", "itemsPerPage"] }, { kind: "component", type: NhAssistantAgentRunHistoryComponent, selector: "nh-assistant-agent-run-history", inputs: ["agentId", "subjectTypes"], outputs: ["openConversation"] }, { kind: "component", type: NhAssistantAgentRunConfigurationComponent, selector: "nh-assistant-agent-run-configuration", inputs: ["agent", "startSetup", "tools", "servers"], outputs: ["changed", "editAgent", "changeAccount", "openRuns", "openMcpServers"] }, { kind: "component", type: NhAssistantAccountPickerComponent, selector: "nh-assistant-account-picker", inputs: ["inputId", "value", "searchable", "invalid", "describedBy"], outputs: ["valueChange"] }, { kind: "component", type: _NhAssistantRunNowDialogComponent, selector: "nh-assistant-run-now-dialog", inputs: ["agent", "subjectTypes", "divisions", "subject"], outputs: ["started", "openConversation", "closed"] }, { kind: "component", type: NhAssistantAccountNamesErrorComponent, selector: "nh-assistant-account-names-error", inputs: ["names"] }, { kind: "pipe", type: DatePipe, name: "date" }, { kind: "pipe", type: TranslatePipe, name: "translate" }, { kind: "pipe", type: _NhAssistantTranslatePipe, name: "nhAssistantTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
4222
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.34", type: NhAssistantAdminAgentsComponent, isStandalone: true, selector: "nh-assistant-admin-agents", outputs: { openMcpServers: "openMcpServers" }, viewQueries: [{ propertyName: "backButton", first: true, predicate: ["back"], descendants: true, isSignal: true }, { propertyName: "reasonInput", first: true, predicate: ["reasonInput"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (editing() !== undefined) {\n <nh-assistant-admin-agent-editor\n [agent]=\"editing() ?? null\"\n [tools]=\"tools()\"\n [servers]=\"servers()\"\n [catalog]=\"catalog()\"\n [catalogUnavailable]=\"catalogError() !== null\"\n (saved)=\"onEditorSaved($event)\"\n (workRequested)=\"onWorkRequested($event)\"\n (cancelled)=\"onEditorCancelled()\" />\n} @else if (reporting(); as agent) {\n <div class=\"stack\">\n <div>\n <button #back class=\"secondary\" type=\"button\" (click)=\"closeReport()\">\n {{ 'nh-assistant.admin.feedback.back' | translate }}\n </button>\n </div>\n <p class=\"muted\">{{ 'nh-assistant.admin.feedback.agent' | translate: { name: (codeTextKeys(agent.source, agent.displayName) | nhAssistantTranslate: agent.displayName), id: agent.id } }}</p>\n <nh-assistant-agent-feedback-report [agentId]=\"agent.id\" />\n </div>\n} @else if (configuring(); as agent) {\n <div class=\"stack\">\n <div>\n <button #back class=\"secondary\" type=\"button\" (click)=\"closeRunConfiguration()\">\n {{ 'nh-assistant.admin.run-config.back' | translate }}\n </button>\n </div>\n <nh-assistant-agent-run-configuration\n [agent]=\"agent\"\n [startSetup]=\"setupOnOpen()\"\n [tools]=\"tools()\"\n [servers]=\"servers()\"\n (editAgent)=\"editFromRunConfiguration(agent)\"\n (openRuns)=\"runsFromRunConfiguration(agent)\"\n (changeAccount)=\"accountFromRunConfiguration(agent)\"\n (openMcpServers)=\"openMcpServers.emit()\" />\n </div>\n} @else if (viewingRuns(); as agent) {\n <div class=\"stack\">\n <div>\n <button #back class=\"secondary\" type=\"button\" (click)=\"closeRuns()\">\n {{ (returnTo() === agent.id ? 'nh-assistant.admin.runs.back-to-run-config' : 'nh-assistant.admin.runs.back') | translate }}\n </button>\n </div>\n <p class=\"muted\">{{ 'nh-assistant.admin.feedback.agent' | translate: { name: (codeTextKeys(agent.source, agent.displayName) | nhAssistantTranslate: agent.displayName), id: agent.id } }}</p>\n <nh-assistant-agent-run-history\n [agentId]=\"agent.id\"\n [subjectTypes]=\"catalog()?.subjectTypes ?? []\"\n (openConversation)=\"openConversation($event)\" />\n </div>\n} @else if (evaluating(); as agent) {\n <div class=\"stack\">\n <div>\n <button #back class=\"secondary\" type=\"button\" (click)=\"closeEvaluation()\">\n {{ 'nh-assistant.admin.evaluation.back' | translate }}\n </button>\n </div>\n <p class=\"muted\">{{ 'nh-assistant.admin.feedback.agent' | translate: { name: (codeTextKeys(agent.source, agent.displayName) | nhAssistantTranslate: agent.displayName), id: agent.id } }}</p>\n <nh-assistant-agent-evaluation [agentId]=\"agent.id\" [variants]=\"variantsOf(agent)\" />\n </div>\n} @else {\n <section class=\"section\" [attr.aria-labelledby]=\"id + '-title'\" [attr.aria-busy]=\"loading()\">\n <header class=\"section-header\">\n <div>\n <h3 [id]=\"id + '-title'\">{{ 'nh-assistant.admin.agents.title' | translate }}</h3>\n <p class=\"muted\">{{ 'nh-assistant.admin.agents.description' | translate }}</p>\n </div>\n <button class=\"primary\" type=\"button\" [disabled]=\"loading() || loadFailed()\" (click)=\"create()\">\n <nh-assistant-icon name=\"plus\" />{{ 'nh-assistant.admin.agents.new' | translate }}\n </button>\n </header>\n\n @if (error(); as failure) {\n @if (errorAgentId() === null) {\n <p class=\"message error\" role=\"alert\"><nh-assistant-icon name=\"warning\" />{{ errorKeys() | nhAssistantTranslate: failure.code }}</p>\n }\n }\n @if (!loading() && !loadFailed()) {\n @if (catalogError(); as failure) {\n <!-- Without the run catalog nothing that depends on it is offered: run now, what agents work on, groups. -->\n <div class=\"message warning catalog-error\" role=\"alert\" data-catalog-error>\n <nh-assistant-icon name=\"warning\" />\n <span class=\"grow\">\n {{ 'nh-assistant.admin.agents.catalog-failed' | translate }}\n {{ catalogErrorKeys() | nhAssistantTranslate: failure.code }}\n </span>\n <button class=\"secondary\" type=\"button\" data-catalog-retry [disabled]=\"catalogLoading()\" (click)=\"retryCatalog()\">\n <nh-assistant-icon name=\"refresh\" />{{ 'nh-assistant.admin.retry' | translate }}\n </button>\n </div>\n }\n <nh-assistant-account-names-error [names]=\"accountNameLookup\" />\n }\n\n @if (loading()) {\n <p class=\"state\" role=\"status\">{{ 'nh-assistant.admin.loading' | translate }}</p>\n } @else if (loadFailed()) {\n <div class=\"state\" role=\"alert\">\n <p>{{ 'nh-assistant.admin.load-failed' | translate }}</p>\n <button class=\"secondary\" type=\"button\" (click)=\"load()\">{{ 'nh-assistant.admin.retry' | translate }}</button>\n </div>\n } @else if (agents().length === 0) {\n <p class=\"state\">{{ 'nh-assistant.admin.agents.empty' | translate }}</p>\n } @else {\n <ul class=\"cards\">\n @for (agent of agents(); track agent.id) {\n <li class=\"card\" [class.inactive]=\"agent.runState !== 'active'\" [attr.aria-busy]=\"busyAgentId() === agent.id\">\n <div class=\"card-header\">\n <!-- An agent in code names and describes itself with translation keys. -->\n <strong>{{ (codeTextKeys(agent.source, agent.displayName) | nhAssistantTranslate: agent.displayName) }}</strong>\n <span class=\"mono muted\">{{ agent.id }}</span>\n <span class=\"badge\">{{ (agent.source === 'code' ? 'nh-assistant.admin.agents.source-code' : 'nh-assistant.admin.agents.source-admin') | translate }}</span>\n @if (agent.isOverridden) {\n <span class=\"badge warning\">{{ 'nh-assistant.admin.agents.overridden' | translate }}</span>\n }\n <span\n class=\"badge\"\n [class.success]=\"agent.runState === 'active'\"\n [class.warning]=\"agent.runState === 'paused'\"\n [class.danger]=\"agent.runState === 'stopped'\">{{ 'nh-assistant.admin.agents.state.' + agent.runState | translate }}</span>\n @if (agent.shadow?.shadow) {\n <span class=\"badge warning\" data-shadow-badge>{{ 'nh-assistant.admin.agents.shadow.badge' | translate }}</span>\n }\n @if (agent.offeredInChat === false) {\n <span class=\"badge\" data-hidden-from-chat>{{ 'nh-assistant.admin.agents.chat.hidden' | translate }}</span>\n }\n </div>\n @if (agent.description) {\n <p class=\"muted\">{{ codeTextKeys(agent.source, agent.description) | nhAssistantTranslate: agent.description }}</p>\n }\n <!-- A warning never refused the save: the agent is in effect but may not work as configured. -->\n @for (warning of agent.warnings ?? []; track warning) {\n <p class=\"message warning\" [attr.data-agent-warning]=\"warning\">\n <nh-assistant-icon name=\"warning\" />{{ warningKeys('agent', warning) | nhAssistantTranslate: warning }}\n </p>\n }\n @if (agent.runState === 'configured-off') {\n <p class=\"muted\">{{ 'nh-assistant.admin.agents.state.configured-off-hint' | translate }}</p>\n } @else if (agent.runState !== 'active') {\n <p class=\"muted\">\n @if (agent.stateChangedAt) {\n <span [attr.title]=\"accountTitle(agent.stateChangedBy)\">{{ 'nh-assistant.admin.agents.state.changed' | translate: { user: accountName(agent.stateChangedBy), date: (agent.stateChangedAt | date: 'medium') } }}</span>\n }\n @if (agent.stateReason) {\n {{ 'nh-assistant.admin.agents.state.reason' | translate: { reason: agent.stateReason } }}\n }\n </p>\n @if (agent.runState === 'paused') {\n <p class=\"muted\">{{ 'nh-assistant.admin.agents.state.waiting' | translate: { count: agent.waitingInputs } }}</p>\n }\n @if (agent.runState === 'off') {\n <p class=\"muted\" data-off-hint>\n {{ 'nh-assistant.admin.agents.state.off-hint' | translate }}\n @if (!agent.stateChangedAt) {\n {{ 'nh-assistant.admin.agents.state.starts-off' | translate }}\n }\n </p>\n }\n }\n @if (agent.shadow; as shadow) {\n @if (shadow.shadow) {\n <p class=\"muted\">\n {{ (shadow.locked ? 'nh-assistant.admin.agents.shadow.locked' : 'nh-assistant.admin.agents.shadow.hint') | translate }}\n @if (shadow.changedAt && shadow.override !== null) {\n <span [attr.title]=\"accountTitle(shadow.changedBy)\">{{ 'nh-assistant.admin.agents.shadow.changed' | translate: { user: accountName(shadow.changedBy), date: (shadow.changedAt | date: 'medium') } }}</span>\n }\n @if (shadow.reason && shadow.override !== null) {\n {{ 'nh-assistant.admin.agents.state.reason' | translate: { reason: shadow.reason } }}\n }\n </p>\n }\n }\n @if (agent.serviceAccount; as account) {\n <p class=\"muted\" data-service-account>\n <span [attr.title]=\"accountTitle(account.actorId)\">{{ 'nh-assistant.admin.agents.service-account.label' | translate: { account: accountName(account.actorId) } }}</span>\n @if (account.overridden) {\n <span [attr.title]=\"accountTitle(account.configuredActorId)\">{{ 'nh-assistant.admin.agents.service-account.configured' | translate: { account: accountName(account.configuredActorId) } }}</span>\n @if (account.reason) {\n {{ 'nh-assistant.admin.agents.state.reason' | translate: { reason: account.reason } }}\n }\n }\n </p>\n }\n @if (worksOn(agent); as works) {\n <!-- What the agent works on; an agent in code shows its code setting. -->\n <p class=\"muted\" data-works-on>\n <span>{{ 'nh-assistant.admin.agents.works-on.label' | translate }}</span>\n <strong>\n @switch (works.only) {\n @case ('all') { {{ 'nh-assistant.admin.agents.works-on.all' | translate }} }\n @case ('types') { {{ works.types.join(', ') }} }\n @default { {{ 'nh-assistant.admin.agents.works-on.application' | translate }} }\n }\n </strong>\n @if (works.application && works.only !== 'application') {\n <span>{{ 'nh-assistant.admin.agents.works-on.and-application' | translate }}</span>\n }\n @if (works.perDivision) {\n <span>{{ 'nh-assistant.admin.agents.works-on.per-division' | translate }}</span>\n }\n @if (works.undeclared) {\n <span>{{ 'nh-assistant.admin.agents.works-on.undeclared' | translate }}</span>\n }\n </p>\n @if (works.all && works.known.length > 0) {\n <p class=\"muted\" data-known-types>{{ 'nh-assistant.admin.agents.works-on.known' | translate: { types: works.known.join(', ') } }}</p>\n }\n @if (works.runPolicy || works.viewPolicy) {\n <!-- Each policy is an item of its own, wrapping on narrow screens. -->\n <div class=\"meta\" data-works-on-policies>\n @if (works.runPolicy; as policy) {\n <span>{{ 'nh-assistant.admin.agents.works-on.run-policy' | translate: { policy: (policyKeys(policy) | nhAssistantTranslate: policy) } }}</span>\n }\n @if (works.viewPolicy; as policy) {\n <span>{{ 'nh-assistant.admin.agents.works-on.view-policy' | translate: { policy: (policyKeys(policy) | nhAssistantTranslate: policy) } }}</span>\n }\n </div>\n }\n }\n @if (groupNames(agent); as names) {\n <p class=\"muted\" data-agent-groups>{{ 'nh-assistant.admin.agents.groups' | translate: { groups: names } }}</p>\n }\n <div class=\"meta\">\n <span>{{ 'nh-assistant.admin.agents.autonomy.' + agent.autonomy | translate }}</span>\n <span>{{ (agent.toolSelectors.length === 1 ? 'nh-assistant.admin.agents.tool-count-one' : 'nh-assistant.admin.agents.tool-count') | translate: { count: agent.toolSelectors.length } }}</span>\n <span>{{ (agent.mcpServerIds.length === 1 ? 'nh-assistant.admin.agents.server-count-one' : 'nh-assistant.admin.agents.server-count') | translate: { count: agent.mcpServerIds.length } }}</span>\n <span>{{ 'nh-assistant.admin.agents.version' | translate: { version: agent.version } }}</span>\n @if (agent.requiredPolicy; as policy) {\n <!-- The application may name its policies; otherwise the policy shows as it is called in code. -->\n <span data-required-policy>\n {{ 'nh-assistant.admin.agents.required-policy' | translate }}\n @if (policyKeys(policy) | nhAssistantTranslate; as text) {\n {{ text }}\n } @else {\n <code class=\"mono\">{{ policy }}</code>\n }\n </span>\n }\n </div>\n <div class=\"card-actions\">\n <button class=\"secondary\" type=\"button\" [disabled]=\"busyAgentId() !== null\" (click)=\"edit(agent)\">\n <nh-assistant-icon name=\"edit\" />{{ 'nh-assistant.admin.edit' | translate }}\n </button>\n @for (action of stateActions(agent); track action) {\n <button\n type=\"button\"\n [class.secondary]=\"action !== 'stopped'\"\n [class.danger]=\"action === 'stopped'\"\n [attr.data-state-action]=\"action\"\n [attr.aria-expanded]=\"isChangingState(agent.id, action)\"\n [disabled]=\"busyAgentId() !== null\"\n (click)=\"askState(agent, action)\">\n {{ stateKey('action', agent.runState, action) | translate }}\n </button>\n }\n @if (agent.runState !== 'configured-off') {\n @for (mode of shadowActions(agent.shadow); track mode) {\n <button\n class=\"secondary\"\n type=\"button\"\n [attr.data-shadow-action]=\"mode\"\n [attr.aria-expanded]=\"isChangingShadow(agent.id, mode)\"\n [disabled]=\"busyAgentId() !== null\"\n (click)=\"askShadow(agent, mode)\">\n {{ shadowActionKey(agent, mode) | translate }}\n </button>\n }\n }\n @if (agent.serviceAccount; as account) {\n <button\n class=\"secondary\"\n type=\"button\"\n data-account-action=\"set\"\n [attr.aria-expanded]=\"isChangingAccount(agent.id, 'set')\"\n [disabled]=\"busyAgentId() !== null\"\n (click)=\"askAccount(agent, 'set')\">\n {{ 'nh-assistant.admin.agents.service-account.action.set' | translate }}\n </button>\n @if (account.overridden) {\n <button\n class=\"secondary\"\n type=\"button\"\n data-account-action=\"code\"\n [attr.aria-expanded]=\"isChangingAccount(agent.id, 'code')\"\n [disabled]=\"busyAgentId() !== null\"\n (click)=\"askAccount(agent, 'code')\">\n {{ 'nh-assistant.admin.agents.service-account.action.code' | translate }}\n </button>\n }\n }\n @if (hasRunConfiguration(agent)) {\n <button class=\"secondary\" type=\"button\" [attr.data-run-config-agent]=\"agent.id\" (click)=\"openRunConfiguration(agent)\">\n <nh-assistant-icon name=\"list\" />{{ 'nh-assistant.admin.agents.run-config' | translate }}\n </button>\n }\n @if (hasRunConfiguration(agent)) {\n <button class=\"secondary\" type=\"button\" [attr.data-runs-agent]=\"agent.id\" (click)=\"openRuns(agent)\">\n <nh-assistant-icon name=\"list\" />{{ 'nh-assistant.admin.agents.runs' | translate }}\n </button>\n }\n @if (canRunNow(agent)) {\n <button\n class=\"secondary\"\n type=\"button\"\n aria-haspopup=\"dialog\"\n [attr.data-run-now-agent]=\"agent.id\"\n [disabled]=\"busyAgentId() !== null\"\n (click)=\"runNow(agent)\">\n <nh-assistant-icon name=\"send\" />{{ 'nh-assistant.admin.agents.run-now' | translate }}\n </button>\n }\n <button class=\"secondary\" type=\"button\" [attr.data-feedback-agent]=\"agent.id\" (click)=\"openReport(agent)\">\n <nh-assistant-icon name=\"list\" />{{ 'nh-assistant.admin.agents.feedback' | translate }}\n </button>\n @if (variantsOf(agent).length > 0) {\n <button class=\"secondary\" type=\"button\" [attr.data-evaluation-agent]=\"agent.id\" (click)=\"openEvaluation(agent)\">\n <nh-assistant-icon name=\"list\" />{{ 'nh-assistant.admin.agents.evaluation' | translate }}\n </button>\n }\n @if (agent.source === 'code' && agent.isOverridden) {\n @if (isConfirming(agent.id, 'reset')) {\n <span class=\"muted\">{{ 'nh-assistant.admin.agents.reset-keeps-state' | translate }}</span>\n <button class=\"danger\" type=\"button\" (click)=\"reset(agent)\">{{ 'nh-assistant.admin.agents.confirm-reset' | translate }}</button>\n <button class=\"secondary\" type=\"button\" (click)=\"confirming.set(null)\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n } @else {\n <button class=\"secondary\" type=\"button\" [disabled]=\"busyAgentId() !== null\" (click)=\"confirm(agent.id, 'reset')\">\n <nh-assistant-icon name=\"refresh\" />{{ 'nh-assistant.admin.agents.reset' | translate }}\n </button>\n }\n }\n @if (agent.source === 'admin') {\n @if (isConfirming(agent.id, 'delete')) {\n <button class=\"danger\" type=\"button\" (click)=\"remove(agent)\">{{ 'nh-assistant.admin.confirm-delete' | translate }}</button>\n <button class=\"secondary\" type=\"button\" (click)=\"confirming.set(null)\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n } @else {\n <button class=\"danger\" type=\"button\" [disabled]=\"busyAgentId() !== null\" (click)=\"confirm(agent.id, 'delete')\">\n <nh-assistant-icon name=\"trash\" />{{ 'nh-assistant.admin.delete' | translate }}\n </button>\n }\n }\n </div>\n @if (agent.skills?.length) {\n <ul class=\"skills\" [attr.aria-label]=\"'nh-assistant.admin.agents.skills.title' | translate\">\n @for (skill of agent.skills; track skill.skillId) {\n <li class=\"skill\" [attr.data-skill]=\"skill.skillId\">\n <div class=\"card-header\">\n <strong>{{ (skillNameKeys(agent.id, skill.skillId) | nhAssistantTranslate: readableId(skill.skillId)) }}</strong>\n <span class=\"mono muted\">{{ skill.skillId }}</span>\n <span\n class=\"badge\"\n [class.success]=\"skill.runState === 'active'\"\n [class.warning]=\"skill.runState === 'paused'\"\n [class.danger]=\"skill.runState === 'stopped'\">{{ 'nh-assistant.admin.agents.state.' + skill.runState | translate }}</span>\n @if (skill.shadow?.shadow) {\n <span class=\"badge warning\" data-shadow-badge>{{ 'nh-assistant.admin.agents.shadow.badge' | translate }}</span>\n }\n </div>\n <p class=\"muted\">{{ skillDescriptionKeys(agent.id, skill.skillId) | nhAssistantTranslate: skill.description }}</p>\n <div class=\"meta\">\n <span>{{ 'nh-assistant.admin.agents.skills.usage' | translate: { calls: skill.toolCallsToday, tokens: skill.tokensToday } }}</span>\n @if (skill.dailyToolCalls !== null) {\n <span>{{ 'nh-assistant.admin.agents.skills.budget' | translate: { calls: skill.dailyToolCalls, tokens: skill.dailyTokens } }}</span>\n }\n @if (skill.runState === 'paused') {\n <span>{{ 'nh-assistant.admin.agents.state.waiting' | translate: { count: skill.waitingInputs } }}</span>\n }\n @if (skill.reason) {\n <span>{{ 'nh-assistant.admin.agents.state.reason' | translate: { reason: skill.reason } }}</span>\n }\n </div>\n @if (agent.runState !== 'configured-off') {\n <div class=\"card-actions\">\n @for (action of skillActions(skill); track action) {\n <button\n type=\"button\"\n [class.secondary]=\"action !== 'stopped'\"\n [class.danger]=\"action === 'stopped'\"\n [attr.data-state-action]=\"action\"\n [attr.aria-label]=\"(stateKey('action', skill.runState, action) | translate) + ' ' + (skillNameKeys(agent.id, skill.skillId) | nhAssistantTranslate: readableId(skill.skillId))\"\n [attr.aria-expanded]=\"isChangingState(agent.id, action, skill.skillId)\"\n [disabled]=\"busyAgentId() !== null\"\n (click)=\"askState(agent, action, skill)\">\n {{ stateKey('action', skill.runState, action) | translate }}\n </button>\n }\n @for (mode of shadowActions(skill.shadow); track mode) {\n <button\n class=\"secondary\"\n type=\"button\"\n [attr.data-shadow-action]=\"mode\"\n [attr.aria-label]=\"(shadowActionKey(agent, mode) | translate) + ' ' + (skillNameKeys(agent.id, skill.skillId) | nhAssistantTranslate: readableId(skill.skillId))\"\n [attr.aria-expanded]=\"isChangingShadow(agent.id, mode, skill.skillId)\"\n [disabled]=\"busyAgentId() !== null\"\n (click)=\"askShadow(agent, mode, skill)\">\n {{ shadowActionKey(agent, mode) | translate }}\n </button>\n }\n </div>\n }\n </li>\n }\n </ul>\n }\n @if (changingState(); as change) {\n @if (change.agentId === agent.id) {\n <form class=\"stack\" (submit)=\"$event.preventDefault(); applyState(agent)\">\n @if (change.skillId) {\n <p class=\"muted\">{{ skillStateExplainKey(change) | translate: { skill: (skillNameKeys(agent.id, change.skillId) | nhAssistantTranslate: readableId(change.skillId)) } }}</p>\n } @else {\n <p class=\"muted\">{{ stateKey('explain', change.from, change.state) | translate }}</p>\n }\n <div class=\"field\">\n <label [for]=\"id + '-' + agent.id + '-reason'\">{{ 'nh-assistant.admin.agents.state.note-label' | translate }}</label>\n <input\n #reasonInput\n type=\"text\"\n autocomplete=\"off\"\n [id]=\"id + '-' + agent.id + '-reason'\"\n [attr.aria-describedby]=\"id + '-' + agent.id + '-reason-hint'\"\n [maxLength]=\"maxReason\"\n [value]=\"stateReason()\"\n (input)=\"stateReason.set(value($event))\" />\n <p class=\"hint\" [id]=\"id + '-' + agent.id + '-reason-hint'\">{{ 'nh-assistant.admin.agents.state.note-hint' | translate }}</p>\n </div>\n <div class=\"card-actions\">\n <button type=\"submit\" [class.primary]=\"change.state !== 'stopped'\" [class.danger]=\"change.state === 'stopped'\" [disabled]=\"busyAgentId() !== null\">\n {{ stateKey('confirm', change.from, change.state) | translate }}\n </button>\n <button class=\"secondary\" type=\"button\" (click)=\"changingState.set(null)\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n </div>\n </form>\n }\n }\n @if (changingShadow(); as change) {\n @if (change.agentId === agent.id) {\n <form class=\"stack\" data-shadow-form (submit)=\"$event.preventDefault(); applyShadow(agent)\">\n @if (change.skillId) {\n <p class=\"muted\">{{ shadowExplainKey(agent, change.mode, true) | translate: { skill: (skillNameKeys(agent.id, change.skillId) | nhAssistantTranslate: readableId(change.skillId)), mode: (ownShadowModeKey(agent, change.skillId) | translate) } }}</p>\n } @else {\n <p class=\"muted\">{{ shadowExplainKey(agent, change.mode, false) | translate: { mode: (ownShadowModeKey(agent) | translate) } }}</p>\n }\n <div class=\"field\">\n <label [for]=\"id + '-' + agent.id + '-shadow-reason'\">{{ 'nh-assistant.admin.agents.state.note-label' | translate }}</label>\n <input\n #reasonInput\n type=\"text\"\n autocomplete=\"off\"\n [id]=\"id + '-' + agent.id + '-shadow-reason'\"\n [attr.aria-describedby]=\"id + '-' + agent.id + '-shadow-reason-hint'\"\n [maxLength]=\"maxReason\"\n [value]=\"stateReason()\"\n (input)=\"stateReason.set(value($event))\" />\n <p class=\"hint\" [id]=\"id + '-' + agent.id + '-shadow-reason-hint'\">{{ 'nh-assistant.admin.agents.state.note-hint' | translate }}</p>\n </div>\n <div class=\"card-actions\">\n <button type=\"submit\" class=\"primary\" [disabled]=\"busyAgentId() !== null\">\n {{ 'nh-assistant.admin.agents.shadow.confirm.' + change.mode | translate }}\n </button>\n <button class=\"secondary\" type=\"button\" (click)=\"changingShadow.set(null)\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n </div>\n </form>\n }\n }\n @if (changingAccount(); as change) {\n @if (change.agentId === agent.id) {\n <form class=\"stack\" data-account-form (submit)=\"$event.preventDefault(); applyAccount(agent)\">\n <p class=\"muted\" [attr.title]=\"accountTitle(agent.serviceAccount?.configuredActorId)\">{{ 'nh-assistant.admin.agents.service-account.explain.' + change.mode | translate: { account: accountName(agent.serviceAccount?.configuredActorId) } }}</p>\n @if (change.mode === 'set') {\n <div class=\"field\">\n <label [for]=\"id + '-' + agent.id + '-account'\">{{ (accountSearch() ? 'nh-assistant.admin.agents.service-account.input-label-search' : 'nh-assistant.admin.agents.service-account.input-label') | translate }}</label>\n <nh-assistant-account-picker\n [inputId]=\"id + '-' + agent.id + '-account'\"\n [value]=\"accountId()\"\n [searchable]=\"accountSearch()\"\n [invalid]=\"accountError() !== null\"\n [describedBy]=\"accountError() ? id + '-' + agent.id + '-account-error' : null\"\n (valueChange)=\"onAccountChange($event)\" />\n @if (accountError(); as failure) {\n <p class=\"field-error\" data-account-error [id]=\"id + '-' + agent.id + '-account-error'\">\n <nh-assistant-icon name=\"warning\" />{{ accountErrorKeys() | nhAssistantTranslate: failure.code }}\n </p>\n }\n </div>\n }\n <div class=\"field\">\n <label [for]=\"id + '-' + agent.id + '-account-reason'\">{{ 'nh-assistant.admin.agents.state.note-label' | translate }}</label>\n <input\n type=\"text\"\n autocomplete=\"off\"\n [id]=\"id + '-' + agent.id + '-account-reason'\"\n [attr.aria-describedby]=\"id + '-' + agent.id + '-account-reason-hint'\"\n [maxLength]=\"maxReason\"\n [value]=\"stateReason()\"\n (input)=\"stateReason.set(value($event))\" />\n <p class=\"hint\" [id]=\"id + '-' + agent.id + '-account-reason-hint'\">{{ 'nh-assistant.admin.agents.state.note-hint' | translate }}</p>\n </div>\n <div class=\"card-actions\">\n <button type=\"submit\" class=\"primary\" [disabled]=\"busyAgentId() !== null\">\n {{ 'nh-assistant.admin.agents.service-account.confirm.' + change.mode | translate }}\n </button>\n <button class=\"secondary\" type=\"button\" (click)=\"changingAccount.set(null)\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n </div>\n </form>\n }\n }\n @if (error(); as failure) {\n @if (errorAgentId() === agent.id) {\n <p class=\"form-error\" role=\"alert\" data-agent-error><nh-assistant-icon name=\"warning\" />{{ errorKeys() | nhAssistantTranslate: failure.code }}</p>\n }\n }\n </li>\n }\n </ul>\n }\n @if (runNowAgent(); as runAgent) {\n <nh-assistant-run-now-dialog\n [agent]=\"runAgent\"\n [subjectTypes]=\"catalog()?.subjectTypes ?? []\"\n [divisions]=\"catalog()?.divisions === true\"\n (openConversation)=\"openConversation($event)\"\n (closed)=\"runningNow.set(null)\" />\n }\n </section>\n}\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;min-width:0;color:var(--nha-text);font-family:var(--nha-font);font-size:14px}:host fieldset{min-width:0;margin:0;border:0;padding:0}:host legend,:host .label,:host .field>label{display:block;margin-bottom:6px;padding:0;color:var(--nha-text);font-size:12px;font-weight:700}:host input[type=text],:host input[type=url],:host input[type=password],:host input[type=search],:host input[type=email],:host input[type=number],:host input[type=date],:host input[type=time],:host input[type=datetime-local],:host select,:host textarea{box-sizing:border-box;width:100%;min-width:0;min-height:38px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:8px 10px;color:var(--nha-text);background:var(--nha-background);font:inherit;font-size:13px}:host input[type=text]:focus-visible,:host input[type=url]:focus-visible,:host input[type=password]:focus-visible,:host input[type=search]:focus-visible,:host input[type=email]:focus-visible,:host input[type=number]:focus-visible,:host input[type=date]:focus-visible,:host input[type=time]:focus-visible,:host input[type=datetime-local]:focus-visible,:host select:focus-visible,:host textarea:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host input[type=text]:disabled,:host input[type=url]:disabled,:host input[type=password]:disabled,:host input[type=search]:disabled,:host input[type=email]:disabled,:host input[type=number]:disabled,:host input[type=date]:disabled,:host input[type=time]:disabled,:host input[type=datetime-local]:disabled,:host select:disabled,:host textarea:disabled{cursor:not-allowed;opacity:.6}:host input[type=text][aria-invalid=true],:host input[type=url][aria-invalid=true],:host input[type=password][aria-invalid=true],:host input[type=search][aria-invalid=true],:host input[type=email][aria-invalid=true],:host input[type=number][aria-invalid=true],:host input[type=date][aria-invalid=true],:host input[type=time][aria-invalid=true],:host input[type=datetime-local][aria-invalid=true],:host select[aria-invalid=true],:host textarea[aria-invalid=true]{border-color:var(--nha-danger)}:host textarea{line-height:1.45;resize:vertical}:host input[type=number]{font-variant-numeric:tabular-nums}:host .mono{font-family:var(--nha-mono);font-size:12px}:host .field{display:flex;flex-direction:column;min-width:0}:host .hint{display:flex;justify-content:space-between;gap:8px;margin:4px 2px 0;color:var(--nha-text-muted);font-size:11px}:host .counter{flex:none;font-variant-numeric:tabular-nums}:host .error-text{color:var(--nha-danger)}:host .field-error{display:flex;align-items:flex-start;gap:6px;margin:4px 2px 0;color:var(--nha-danger);font-size:12px;font-weight:600;overflow-wrap:anywhere}:host .field-error nh-assistant-icon{flex:none;margin-top:1px}:host .form-error{box-sizing:border-box;display:flex;align-items:flex-start;gap:8px;margin:0;border:1px solid var(--nha-danger);border-radius:var(--nha-radius-control);padding:8px 12px;color:var(--nha-text);background:var(--nha-danger-soft);font-size:13px;overflow-wrap:anywhere}:host .form-error ul{margin:4px 0 0;padding-left:18px}:host .message{box-sizing:border-box;display:flex;align-items:center;gap:8px;margin:0;border:1px solid transparent;border-radius:var(--nha-radius-control);padding:8px 12px;font-size:13px;overflow-wrap:anywhere}:host .message.error{border-color:var(--nha-danger);color:var(--nha-text);background:var(--nha-danger-soft)}:host .message.warning{border-color:var(--nha-warning);color:var(--nha-text);background:var(--nha-warning-soft)}:host .message.success{border-color:var(--nha-success);color:var(--nha-text);background:var(--nha-success-soft)}:host .message.info{border-color:var(--nha-border);color:var(--nha-text);background:var(--nha-surface)}:host .message nh-assistant-icon{flex:none}:host button.link{min-height:0;border:0;padding:0;color:var(--nha-accent, currentColor);background:none;font:inherit;text-decoration:underline;cursor:pointer}:host button.link:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host button.link:disabled{cursor:not-allowed;opacity:.55}:host .primary,:host .secondary,:host .danger{display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;gap:6px;min-height:36px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px 14px;font:inherit;font-size:13px;font-weight:700;white-space:nowrap;cursor:pointer}:host .primary:focus-visible,:host .secondary:focus-visible,:host .danger:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .primary:disabled,:host .secondary:disabled,:host .danger:disabled{cursor:not-allowed;opacity:.55}:host .primary{border-color:var(--nha-accent);color:var(--nha-accent-contrast);background:var(--nha-accent)}:host .primary:hover:not(:disabled){filter:brightness(1.12)}:host .secondary{color:var(--nha-text);background:transparent}:host .secondary:hover:not(:disabled){background:var(--nha-surface-strong)}:host .danger{border-color:var(--nha-danger);color:var(--nha-danger);background:transparent}:host .danger:hover:not(:disabled){background:var(--nha-danger-soft)}:host .icon-button{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:1px solid transparent;border-radius:var(--nha-radius-control);color:var(--nha-text-muted);background:transparent;cursor:pointer}:host .icon-button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .icon-button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}:host .icon-button:disabled{cursor:not-allowed;opacity:.5}:host .actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px}:host .badge{display:inline-flex;align-items:center;gap:4px;border-radius:999px;padding:2px 8px;color:var(--nha-text-muted);background:var(--nha-surface-strong);font-size:11px;font-weight:700;white-space:nowrap}:host .badge.success{color:var(--nha-success);background:var(--nha-success-soft)}:host .badge.warning{color:var(--nha-warning);background:var(--nha-warning-soft)}:host .badge.danger{color:var(--nha-danger);background:var(--nha-danger-soft)}:host .visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}.section{display:flex;flex-direction:column;gap:16px;min-width:0}.section-header{display:flex;flex-wrap:wrap;align-items:flex-start;justify-content:space-between;gap:12px}.section-header>div{min-width:0;max-width:72ch}h3{margin:0 0 4px;font-size:17px;font-weight:750}h4{margin:0 0 8px;font-size:14px;font-weight:700}.muted{margin:0;color:var(--nha-text-muted);font-size:13px}.state{display:flex;flex-direction:column;align-items:flex-start;gap:10px;margin:0;border:1px dashed var(--nha-border);border-radius:var(--nha-radius);padding:20px;color:var(--nha-text-muted);font-size:13px}.state p{margin:0}.stack{display:flex;flex-direction:column;gap:14px;min-width:0}.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,240px),1fr));gap:14px}.meta{display:flex;flex-wrap:wrap;align-items:center;gap:8px 12px;color:var(--nha-text-muted);font-size:12px;overflow-wrap:anywhere}textarea.large{min-height:260px}.catalog-error{flex-wrap:wrap}.grow{flex:1;min-width:0}.subsection{display:flex;flex-direction:column;gap:8px;border-top:1px solid var(--nha-border);padding-top:14px}.history{display:grid;gap:6px;margin:0;padding:0;list-style:none}.history li{display:flex;flex-wrap:wrap;align-items:center;gap:6px 12px;font-size:12px}.cards{display:grid;gap:10px;margin:0;padding:0;list-style:none}.card{display:flex;flex-direction:column;gap:8px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius);padding:14px;background:var(--nha-background)}.card.inactive{opacity:.8}.card-header{display:flex;flex-wrap:wrap;align-items:center;gap:8px;min-width:0}.card-header strong{font-size:14px;overflow-wrap:anywhere}.card-actions{display:flex;flex-wrap:wrap;gap:8px}.check{display:flex;align-items:flex-start;gap:8px;font-size:13px;cursor:pointer}.check input{width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.check.described>span{display:grid;gap:2px;min-width:0;overflow-wrap:anywhere}.checks{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));gap:6px 12px;min-width:0;overflow-wrap:anywhere}.chips{display:flex;flex-wrap:wrap;gap:6px;margin:0;padding:0;list-style:none}.chip{display:inline-flex;align-items:center;gap:2px;border:1px solid var(--nha-border);border-radius:999px;padding:2px 2px 2px 10px;background:var(--nha-surface);font-family:var(--nha-mono);font-size:12px}.chip 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;width:26px;height:26px;border-radius:999px}.chip button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.chip button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}.chip button:disabled{cursor:not-allowed;opacity:.5}.inline{display:flex;flex-wrap:wrap;gap:8px}.inline>input{flex:1 1 180px}.catalog{display:grid;gap:4px;max-height:260px;margin:8px 0 0;overflow-y:auto;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px;list-style:none}.catalog li label{display:flex;flex-wrap:wrap;align-items:center;gap:6px 8px;border-radius:8px;padding:6px;cursor:pointer}.catalog li label:hover{background:var(--nha-surface)}.catalog .tool-id{font-family:var(--nha-mono);font-size:12px;overflow-wrap:anywhere}.skills{display:grid;gap:8px;margin:0;padding:0;list-style:none}.skill{display:grid;gap:6px;border-left:3px solid var(--nha-border);padding:4px 0 4px 12px}.uses{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));gap:8px}.use{display:flex;align-items:flex-start;gap:10px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:10px 12px;cursor:pointer;overflow-wrap:anywhere}.use input{flex:none;width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.use>span{display:grid;gap:2px;min-width:0}.use strong{font-size:13px}.use.selected{border-color:var(--nha-highlight);background:var(--nha-surface)}.use:focus-within{outline:2px solid var(--nha-highlight);outline-offset:2px}.discovery{display:grid;gap:8px;padding:10px 12px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control)}.discovery dl{display:grid;grid-template-columns:max-content minmax(0,1fr);gap:4px 12px;margin:0}.discovery dt{color:var(--nha-text-muted)}.discovery dd{margin:0;overflow-wrap:anywhere}\n"], dependencies: [{ kind: "component", type: _NhAssistantIconComponent, selector: "nh-assistant-icon", inputs: ["name"] }, { kind: "component", type: NhAssistantAdminAgentEditorComponent, selector: "nh-assistant-admin-agent-editor", inputs: ["agent", "tools", "servers", "catalog", "catalogUnavailable"], outputs: ["saved", "workRequested", "cancelled"] }, { kind: "component", type: NhAssistantAgentEvaluationComponent, selector: "nh-assistant-agent-evaluation", inputs: ["agentId", "variants"] }, { kind: "component", type: NhAssistantAgentFeedbackReportComponent, selector: "nh-assistant-agent-feedback-report", inputs: ["agentId", "itemsPerPage"] }, { kind: "component", type: NhAssistantAgentRunHistoryComponent, selector: "nh-assistant-agent-run-history", inputs: ["agentId", "subjectTypes"], outputs: ["openConversation"] }, { kind: "component", type: NhAssistantAgentRunConfigurationComponent, selector: "nh-assistant-agent-run-configuration", inputs: ["agent", "startSetup", "tools", "servers"], outputs: ["changed", "editAgent", "changeAccount", "openRuns", "openMcpServers"] }, { kind: "component", type: NhAssistantAccountPickerComponent, selector: "nh-assistant-account-picker", inputs: ["inputId", "value", "searchable", "invalid", "describedBy"], outputs: ["valueChange"] }, { kind: "component", type: _NhAssistantRunNowDialogComponent, selector: "nh-assistant-run-now-dialog", inputs: ["agent", "subjectTypes", "divisions", "subject"], outputs: ["started", "openConversation", "closed"] }, { kind: "component", type: NhAssistantAccountNamesErrorComponent, selector: "nh-assistant-account-names-error", inputs: ["names"] }, { kind: "pipe", type: DatePipe, name: "date" }, { kind: "pipe", type: TranslatePipe, name: "translate" }, { kind: "pipe", type: _NhAssistantTranslatePipe, name: "nhAssistantTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
3961
4223
|
}
|
|
3962
4224
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: NhAssistantAdminAgentsComponent, decorators: [{
|
|
3963
4225
|
type: Component,
|
|
@@ -3974,7 +4236,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImpo
|
|
|
3974
4236
|
NhAssistantAccountPickerComponent,
|
|
3975
4237
|
_NhAssistantRunNowDialogComponent,
|
|
3976
4238
|
NhAssistantAccountNamesErrorComponent
|
|
3977
|
-
], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (editing() !== undefined) {\n <nh-assistant-admin-agent-editor\n [agent]=\"editing() ?? null\"\n [tools]=\"tools()\"\n [servers]=\"servers()\"\n [catalog]=\"catalog()\"\n [catalogUnavailable]=\"catalogError() !== null\"\n (saved)=\"onEditorSaved($event)\"\n (workRequested)=\"onWorkRequested($event)\"\n (cancelled)=\"onEditorCancelled()\" />\n} @else if (reporting(); as agent) {\n <div class=\"stack\">\n <div>\n <button #back class=\"secondary\" type=\"button\" (click)=\"closeReport()\">\n {{ 'nh-assistant.admin.feedback.back' | translate }}\n </button>\n </div>\n <p class=\"muted\">{{ 'nh-assistant.admin.feedback.agent' | translate: { name: (codeTextKeys(agent.source, agent.displayName) | nhAssistantTranslate: agent.displayName), id: agent.id } }}</p>\n <nh-assistant-agent-feedback-report [agentId]=\"agent.id\" />\n </div>\n} @else if (configuring(); as agent) {\n <div class=\"stack\">\n <div>\n <button #back class=\"secondary\" type=\"button\" (click)=\"closeRunConfiguration()\">\n {{ 'nh-assistant.admin.run-config.back' | translate }}\n </button>\n </div>\n <nh-assistant-agent-run-configuration\n [agent]=\"agent\"\n [startSetup]=\"setupOnOpen()\"\n [tools]=\"tools()\"\n [servers]=\"servers()\"\n (editAgent)=\"editFromRunConfiguration(agent)\"\n (openRuns)=\"runsFromRunConfiguration(agent)\"\n (changeAccount)=\"accountFromRunConfiguration(agent)\"\n (openMcpServers)=\"openMcpServers.emit()\" />\n </div>\n} @else if (viewingRuns(); as agent) {\n <div class=\"stack\">\n <div>\n <button #back class=\"secondary\" type=\"button\" (click)=\"closeRuns()\">\n {{ (returnTo() === agent.id ? 'nh-assistant.admin.runs.back-to-run-config' : 'nh-assistant.admin.runs.back') | translate }}\n </button>\n </div>\n <p class=\"muted\">{{ 'nh-assistant.admin.feedback.agent' | translate: { name: (codeTextKeys(agent.source, agent.displayName) | nhAssistantTranslate: agent.displayName), id: agent.id } }}</p>\n <nh-assistant-agent-run-history\n [agentId]=\"agent.id\"\n [subjectTypes]=\"catalog()?.subjectTypes ?? []\"\n (openConversation)=\"openConversation($event)\" />\n </div>\n} @else if (evaluating(); as agent) {\n <div class=\"stack\">\n <div>\n <button #back class=\"secondary\" type=\"button\" (click)=\"closeEvaluation()\">\n {{ 'nh-assistant.admin.evaluation.back' | translate }}\n </button>\n </div>\n <p class=\"muted\">{{ 'nh-assistant.admin.feedback.agent' | translate: { name: (codeTextKeys(agent.source, agent.displayName) | nhAssistantTranslate: agent.displayName), id: agent.id } }}</p>\n <nh-assistant-agent-evaluation [agentId]=\"agent.id\" [variants]=\"variantsOf(agent)\" />\n </div>\n} @else {\n <section class=\"section\" [attr.aria-labelledby]=\"id + '-title'\" [attr.aria-busy]=\"loading()\">\n <header class=\"section-header\">\n <div>\n <h3 [id]=\"id + '-title'\">{{ 'nh-assistant.admin.agents.title' | translate }}</h3>\n <p class=\"muted\">{{ 'nh-assistant.admin.agents.description' | translate }}</p>\n </div>\n <button class=\"primary\" type=\"button\" [disabled]=\"loading() || loadFailed()\" (click)=\"create()\">\n <nh-assistant-icon name=\"plus\" />{{ 'nh-assistant.admin.agents.new' | translate }}\n </button>\n </header>\n\n @if (error(); as failure) {\n @if (errorAgentId() === null) {\n <p class=\"message error\" role=\"alert\"><nh-assistant-icon name=\"warning\" />{{ errorKeys() | nhAssistantTranslate: failure.code }}</p>\n }\n }\n @if (!loading() && !loadFailed()) {\n @if (catalogError(); as failure) {\n <!-- Without the run catalog nothing that depends on it is offered: run now, what agents work on, groups. -->\n <div class=\"message warning catalog-error\" role=\"alert\" data-catalog-error>\n <nh-assistant-icon name=\"warning\" />\n <span class=\"grow\">\n {{ 'nh-assistant.admin.agents.catalog-failed' | translate }}\n {{ catalogErrorKeys() | nhAssistantTranslate: failure.code }}\n </span>\n <button class=\"secondary\" type=\"button\" data-catalog-retry [disabled]=\"catalogLoading()\" (click)=\"retryCatalog()\">\n <nh-assistant-icon name=\"refresh\" />{{ 'nh-assistant.admin.retry' | translate }}\n </button>\n </div>\n }\n <nh-assistant-account-names-error [names]=\"accountNameLookup\" />\n }\n\n @if (loading()) {\n <p class=\"state\" role=\"status\">{{ 'nh-assistant.admin.loading' | translate }}</p>\n } @else if (loadFailed()) {\n <div class=\"state\" role=\"alert\">\n <p>{{ 'nh-assistant.admin.load-failed' | translate }}</p>\n <button class=\"secondary\" type=\"button\" (click)=\"load()\">{{ 'nh-assistant.admin.retry' | translate }}</button>\n </div>\n } @else if (agents().length === 0) {\n <p class=\"state\">{{ 'nh-assistant.admin.agents.empty' | translate }}</p>\n } @else {\n <ul class=\"cards\">\n @for (agent of agents(); track agent.id) {\n <li class=\"card\" [class.inactive]=\"agent.runState !== 'active'\" [attr.aria-busy]=\"busyAgentId() === agent.id\">\n <div class=\"card-header\">\n <!-- An agent in code names and describes itself with translation keys. -->\n <strong>{{ (codeTextKeys(agent.source, agent.displayName) | nhAssistantTranslate: agent.displayName) }}</strong>\n <span class=\"mono muted\">{{ agent.id }}</span>\n <span class=\"badge\">{{ (agent.source === 'code' ? 'nh-assistant.admin.agents.source-code' : 'nh-assistant.admin.agents.source-admin') | translate }}</span>\n @if (agent.isOverridden) {\n <span class=\"badge warning\">{{ 'nh-assistant.admin.agents.overridden' | translate }}</span>\n }\n <span\n class=\"badge\"\n [class.success]=\"agent.runState === 'active'\"\n [class.warning]=\"agent.runState === 'paused'\"\n [class.danger]=\"agent.runState === 'stopped'\">{{ 'nh-assistant.admin.agents.state.' + agent.runState | translate }}</span>\n @if (agent.shadow?.shadow) {\n <span class=\"badge warning\" data-shadow-badge>{{ 'nh-assistant.admin.agents.shadow.badge' | translate }}</span>\n }\n @if (agent.offeredInChat === false) {\n <span class=\"badge\" data-hidden-from-chat>{{ 'nh-assistant.admin.agents.chat.hidden' | translate }}</span>\n }\n </div>\n @if (agent.description) {\n <p class=\"muted\">{{ codeTextKeys(agent.source, agent.description) | nhAssistantTranslate: agent.description }}</p>\n }\n @if (agent.runState === 'configured-off') {\n <p class=\"muted\">{{ 'nh-assistant.admin.agents.state.configured-off-hint' | translate }}</p>\n } @else if (agent.runState !== 'active') {\n <p class=\"muted\">\n @if (agent.stateChangedAt) {\n <span [attr.title]=\"accountTitle(agent.stateChangedBy)\">{{ 'nh-assistant.admin.agents.state.changed' | translate: { user: accountName(agent.stateChangedBy), date: (agent.stateChangedAt | date: 'medium') } }}</span>\n }\n @if (agent.stateReason) {\n {{ 'nh-assistant.admin.agents.state.reason' | translate: { reason: agent.stateReason } }}\n }\n </p>\n @if (agent.runState === 'paused') {\n <p class=\"muted\">{{ 'nh-assistant.admin.agents.state.waiting' | translate: { count: agent.waitingInputs } }}</p>\n }\n }\n @if (agent.shadow; as shadow) {\n @if (shadow.shadow) {\n <p class=\"muted\">\n {{ (shadow.locked ? 'nh-assistant.admin.agents.shadow.locked' : 'nh-assistant.admin.agents.shadow.hint') | translate }}\n @if (shadow.changedAt && shadow.override !== null) {\n <span [attr.title]=\"accountTitle(shadow.changedBy)\">{{ 'nh-assistant.admin.agents.shadow.changed' | translate: { user: accountName(shadow.changedBy), date: (shadow.changedAt | date: 'medium') } }}</span>\n }\n @if (shadow.reason && shadow.override !== null) {\n {{ 'nh-assistant.admin.agents.state.reason' | translate: { reason: shadow.reason } }}\n }\n </p>\n }\n }\n @if (agent.serviceAccount; as account) {\n <p class=\"muted\" data-service-account>\n <span [attr.title]=\"accountTitle(account.actorId)\">{{ 'nh-assistant.admin.agents.service-account.label' | translate: { account: accountName(account.actorId) } }}</span>\n @if (account.overridden) {\n <span [attr.title]=\"accountTitle(account.configuredActorId)\">{{ 'nh-assistant.admin.agents.service-account.configured' | translate: { account: accountName(account.configuredActorId) } }}</span>\n @if (account.reason) {\n {{ 'nh-assistant.admin.agents.state.reason' | translate: { reason: account.reason } }}\n }\n }\n </p>\n }\n @if (worksOn(agent); as works) {\n <!-- What the agent works on; an agent in code shows its code setting. -->\n <p class=\"muted\" data-works-on>\n <span>{{ 'nh-assistant.admin.agents.works-on.label' | translate }}</span>\n <strong>\n @switch (works.only) {\n @case ('all') { {{ 'nh-assistant.admin.agents.works-on.all' | translate }} }\n @case ('types') { {{ works.types.join(', ') }} }\n @default { {{ 'nh-assistant.admin.agents.works-on.application' | translate }} }\n }\n </strong>\n @if (works.application && works.only !== 'application') {\n <span>{{ 'nh-assistant.admin.agents.works-on.and-application' | translate }}</span>\n }\n @if (works.perDivision) {\n <span>{{ 'nh-assistant.admin.agents.works-on.per-division' | translate }}</span>\n }\n @if (works.undeclared) {\n <span>{{ 'nh-assistant.admin.agents.works-on.undeclared' | translate }}</span>\n }\n </p>\n @if (works.all && works.known.length > 0) {\n <p class=\"muted\" data-known-types>{{ 'nh-assistant.admin.agents.works-on.known' | translate: { types: works.known.join(', ') } }}</p>\n }\n @if (works.runPolicy || works.viewPolicy) {\n <!-- Each policy is an item of its own, wrapping on narrow screens. -->\n <div class=\"meta\" data-works-on-policies>\n @if (works.runPolicy; as policy) {\n <span>{{ 'nh-assistant.admin.agents.works-on.run-policy' | translate: { policy: (policyKeys(policy) | nhAssistantTranslate: policy) } }}</span>\n }\n @if (works.viewPolicy; as policy) {\n <span>{{ 'nh-assistant.admin.agents.works-on.view-policy' | translate: { policy: (policyKeys(policy) | nhAssistantTranslate: policy) } }}</span>\n }\n </div>\n }\n }\n @if (groupNames(agent); as names) {\n <p class=\"muted\" data-agent-groups>{{ 'nh-assistant.admin.agents.groups' | translate: { groups: names } }}</p>\n }\n <div class=\"meta\">\n <span>{{ 'nh-assistant.admin.agents.autonomy.' + agent.autonomy | translate }}</span>\n <span>{{ (agent.toolSelectors.length === 1 ? 'nh-assistant.admin.agents.tool-count-one' : 'nh-assistant.admin.agents.tool-count') | translate: { count: agent.toolSelectors.length } }}</span>\n <span>{{ (agent.mcpServerIds.length === 1 ? 'nh-assistant.admin.agents.server-count-one' : 'nh-assistant.admin.agents.server-count') | translate: { count: agent.mcpServerIds.length } }}</span>\n <span>{{ 'nh-assistant.admin.agents.version' | translate: { version: agent.version } }}</span>\n @if (agent.requiredPolicy; as policy) {\n <!-- The application may name its policies; otherwise the policy shows as it is called in code. -->\n <span data-required-policy>\n {{ 'nh-assistant.admin.agents.required-policy' | translate }}\n @if (policyKeys(policy) | nhAssistantTranslate; as text) {\n {{ text }}\n } @else {\n <code class=\"mono\">{{ policy }}</code>\n }\n </span>\n }\n </div>\n <div class=\"card-actions\">\n <button class=\"secondary\" type=\"button\" [disabled]=\"busyAgentId() !== null\" (click)=\"edit(agent)\">\n <nh-assistant-icon name=\"edit\" />{{ 'nh-assistant.admin.edit' | translate }}\n </button>\n @for (action of stateActions(agent); track action) {\n <button\n type=\"button\"\n [class.secondary]=\"action !== 'stopped'\"\n [class.danger]=\"action === 'stopped'\"\n [attr.aria-expanded]=\"isChangingState(agent.id, action)\"\n [disabled]=\"busyAgentId() !== null\"\n (click)=\"askState(agent, action)\">\n {{ 'nh-assistant.admin.agents.state.action.' + action | translate }}\n </button>\n }\n @if (agent.runState !== 'configured-off') {\n @for (mode of shadowActions(agent.shadow); track mode) {\n <button\n class=\"secondary\"\n type=\"button\"\n [attr.data-shadow-action]=\"mode\"\n [attr.aria-expanded]=\"isChangingShadow(agent.id, mode)\"\n [disabled]=\"busyAgentId() !== null\"\n (click)=\"askShadow(agent, mode)\">\n {{ shadowActionKey(agent, mode) | translate }}\n </button>\n }\n }\n @if (agent.serviceAccount; as account) {\n <button\n class=\"secondary\"\n type=\"button\"\n data-account-action=\"set\"\n [attr.aria-expanded]=\"isChangingAccount(agent.id, 'set')\"\n [disabled]=\"busyAgentId() !== null\"\n (click)=\"askAccount(agent, 'set')\">\n {{ 'nh-assistant.admin.agents.service-account.action.set' | translate }}\n </button>\n @if (account.overridden) {\n <button\n class=\"secondary\"\n type=\"button\"\n data-account-action=\"code\"\n [attr.aria-expanded]=\"isChangingAccount(agent.id, 'code')\"\n [disabled]=\"busyAgentId() !== null\"\n (click)=\"askAccount(agent, 'code')\">\n {{ 'nh-assistant.admin.agents.service-account.action.code' | translate }}\n </button>\n }\n }\n @if (hasRunConfiguration(agent)) {\n <button class=\"secondary\" type=\"button\" [attr.data-run-config-agent]=\"agent.id\" (click)=\"openRunConfiguration(agent)\">\n <nh-assistant-icon name=\"list\" />{{ 'nh-assistant.admin.agents.run-config' | translate }}\n </button>\n }\n @if (hasRunConfiguration(agent)) {\n <button class=\"secondary\" type=\"button\" [attr.data-runs-agent]=\"agent.id\" (click)=\"openRuns(agent)\">\n <nh-assistant-icon name=\"list\" />{{ 'nh-assistant.admin.agents.runs' | translate }}\n </button>\n }\n @if (canRunNow(agent)) {\n <button\n class=\"secondary\"\n type=\"button\"\n aria-haspopup=\"dialog\"\n [attr.data-run-now-agent]=\"agent.id\"\n [disabled]=\"busyAgentId() !== null\"\n (click)=\"runNow(agent)\">\n <nh-assistant-icon name=\"send\" />{{ 'nh-assistant.admin.agents.run-now' | translate }}\n </button>\n }\n <button class=\"secondary\" type=\"button\" [attr.data-feedback-agent]=\"agent.id\" (click)=\"openReport(agent)\">\n <nh-assistant-icon name=\"list\" />{{ 'nh-assistant.admin.agents.feedback' | translate }}\n </button>\n @if (variantsOf(agent).length > 0) {\n <button class=\"secondary\" type=\"button\" [attr.data-evaluation-agent]=\"agent.id\" (click)=\"openEvaluation(agent)\">\n <nh-assistant-icon name=\"list\" />{{ 'nh-assistant.admin.agents.evaluation' | translate }}\n </button>\n }\n @if (agent.source === 'code' && agent.isOverridden) {\n @if (isConfirming(agent.id, 'reset')) {\n <span class=\"muted\">{{ 'nh-assistant.admin.agents.reset-keeps-state' | translate }}</span>\n <button class=\"danger\" type=\"button\" (click)=\"reset(agent)\">{{ 'nh-assistant.admin.agents.confirm-reset' | translate }}</button>\n <button class=\"secondary\" type=\"button\" (click)=\"confirming.set(null)\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n } @else {\n <button class=\"secondary\" type=\"button\" [disabled]=\"busyAgentId() !== null\" (click)=\"confirm(agent.id, 'reset')\">\n <nh-assistant-icon name=\"refresh\" />{{ 'nh-assistant.admin.agents.reset' | translate }}\n </button>\n }\n }\n @if (agent.source === 'admin') {\n @if (isConfirming(agent.id, 'delete')) {\n <button class=\"danger\" type=\"button\" (click)=\"remove(agent)\">{{ 'nh-assistant.admin.confirm-delete' | translate }}</button>\n <button class=\"secondary\" type=\"button\" (click)=\"confirming.set(null)\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n } @else {\n <button class=\"danger\" type=\"button\" [disabled]=\"busyAgentId() !== null\" (click)=\"confirm(agent.id, 'delete')\">\n <nh-assistant-icon name=\"trash\" />{{ 'nh-assistant.admin.delete' | translate }}\n </button>\n }\n }\n </div>\n @if (agent.skills?.length) {\n <ul class=\"skills\" [attr.aria-label]=\"'nh-assistant.admin.agents.skills.title' | translate\">\n @for (skill of agent.skills; track skill.skillId) {\n <li class=\"skill\" [attr.data-skill]=\"skill.skillId\">\n <div class=\"card-header\">\n <strong>{{ (skillNameKeys(agent.id, skill.skillId) | nhAssistantTranslate: readableId(skill.skillId)) }}</strong>\n <span class=\"mono muted\">{{ skill.skillId }}</span>\n <span\n class=\"badge\"\n [class.success]=\"skill.runState === 'active'\"\n [class.warning]=\"skill.runState === 'paused'\"\n [class.danger]=\"skill.runState === 'stopped'\">{{ 'nh-assistant.admin.agents.state.' + skill.runState | translate }}</span>\n @if (skill.shadow?.shadow) {\n <span class=\"badge warning\" data-shadow-badge>{{ 'nh-assistant.admin.agents.shadow.badge' | translate }}</span>\n }\n </div>\n <p class=\"muted\">{{ skillDescriptionKeys(agent.id, skill.skillId) | nhAssistantTranslate: skill.description }}</p>\n <div class=\"meta\">\n <span>{{ 'nh-assistant.admin.agents.skills.usage' | translate: { calls: skill.toolCallsToday, tokens: skill.tokensToday } }}</span>\n @if (skill.dailyToolCalls !== null) {\n <span>{{ 'nh-assistant.admin.agents.skills.budget' | translate: { calls: skill.dailyToolCalls, tokens: skill.dailyTokens } }}</span>\n }\n @if (skill.runState === 'paused') {\n <span>{{ 'nh-assistant.admin.agents.state.waiting' | translate: { count: skill.waitingInputs } }}</span>\n }\n @if (skill.reason) {\n <span>{{ 'nh-assistant.admin.agents.state.reason' | translate: { reason: skill.reason } }}</span>\n }\n </div>\n @if (agent.runState !== 'configured-off') {\n <div class=\"card-actions\">\n @for (action of skillActions(skill); track action) {\n <button\n type=\"button\"\n [class.secondary]=\"action !== 'stopped'\"\n [class.danger]=\"action === 'stopped'\"\n [attr.aria-label]=\"('nh-assistant.admin.agents.state.action.' + action | translate) + ' ' + (skillNameKeys(agent.id, skill.skillId) | nhAssistantTranslate: readableId(skill.skillId))\"\n [attr.aria-expanded]=\"isChangingState(agent.id, action, skill.skillId)\"\n [disabled]=\"busyAgentId() !== null\"\n (click)=\"askState(agent, action, skill)\">\n {{ 'nh-assistant.admin.agents.state.action.' + action | translate }}\n </button>\n }\n @for (mode of shadowActions(skill.shadow); track mode) {\n <button\n class=\"secondary\"\n type=\"button\"\n [attr.data-shadow-action]=\"mode\"\n [attr.aria-label]=\"(shadowActionKey(agent, mode) | translate) + ' ' + (skillNameKeys(agent.id, skill.skillId) | nhAssistantTranslate: readableId(skill.skillId))\"\n [attr.aria-expanded]=\"isChangingShadow(agent.id, mode, skill.skillId)\"\n [disabled]=\"busyAgentId() !== null\"\n (click)=\"askShadow(agent, mode, skill)\">\n {{ shadowActionKey(agent, mode) | translate }}\n </button>\n }\n </div>\n }\n </li>\n }\n </ul>\n }\n @if (changingState(); as change) {\n @if (change.agentId === agent.id) {\n <form class=\"stack\" (submit)=\"$event.preventDefault(); applyState(agent)\">\n @if (change.skillId) {\n <p class=\"muted\">{{ 'nh-assistant.admin.agents.skills.explain.' + change.state | translate: { skill: (skillNameKeys(agent.id, change.skillId) | nhAssistantTranslate: readableId(change.skillId)) } }}</p>\n } @else {\n <p class=\"muted\">{{ 'nh-assistant.admin.agents.state.explain.' + change.state | translate }}</p>\n }\n <div class=\"field\">\n <label [for]=\"id + '-' + agent.id + '-reason'\">{{ 'nh-assistant.admin.agents.state.note-label' | translate }}</label>\n <input\n #reasonInput\n type=\"text\"\n autocomplete=\"off\"\n [id]=\"id + '-' + agent.id + '-reason'\"\n [attr.aria-describedby]=\"id + '-' + agent.id + '-reason-hint'\"\n [maxLength]=\"maxReason\"\n [value]=\"stateReason()\"\n (input)=\"stateReason.set(value($event))\" />\n <p class=\"hint\" [id]=\"id + '-' + agent.id + '-reason-hint'\">{{ 'nh-assistant.admin.agents.state.note-hint' | translate }}</p>\n </div>\n <div class=\"card-actions\">\n <button type=\"submit\" [class.primary]=\"change.state !== 'stopped'\" [class.danger]=\"change.state === 'stopped'\" [disabled]=\"busyAgentId() !== null\">\n {{ 'nh-assistant.admin.agents.state.confirm.' + change.state | translate }}\n </button>\n <button class=\"secondary\" type=\"button\" (click)=\"changingState.set(null)\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n </div>\n </form>\n }\n }\n @if (changingShadow(); as change) {\n @if (change.agentId === agent.id) {\n <form class=\"stack\" data-shadow-form (submit)=\"$event.preventDefault(); applyShadow(agent)\">\n @if (change.skillId) {\n <p class=\"muted\">{{ shadowExplainKey(agent, change.mode, true) | translate: { skill: (skillNameKeys(agent.id, change.skillId) | nhAssistantTranslate: readableId(change.skillId)), mode: (ownShadowModeKey(agent, change.skillId) | translate) } }}</p>\n } @else {\n <p class=\"muted\">{{ shadowExplainKey(agent, change.mode, false) | translate: { mode: (ownShadowModeKey(agent) | translate) } }}</p>\n }\n <div class=\"field\">\n <label [for]=\"id + '-' + agent.id + '-shadow-reason'\">{{ 'nh-assistant.admin.agents.state.note-label' | translate }}</label>\n <input\n #reasonInput\n type=\"text\"\n autocomplete=\"off\"\n [id]=\"id + '-' + agent.id + '-shadow-reason'\"\n [attr.aria-describedby]=\"id + '-' + agent.id + '-shadow-reason-hint'\"\n [maxLength]=\"maxReason\"\n [value]=\"stateReason()\"\n (input)=\"stateReason.set(value($event))\" />\n <p class=\"hint\" [id]=\"id + '-' + agent.id + '-shadow-reason-hint'\">{{ 'nh-assistant.admin.agents.state.note-hint' | translate }}</p>\n </div>\n <div class=\"card-actions\">\n <button type=\"submit\" class=\"primary\" [disabled]=\"busyAgentId() !== null\">\n {{ 'nh-assistant.admin.agents.shadow.confirm.' + change.mode | translate }}\n </button>\n <button class=\"secondary\" type=\"button\" (click)=\"changingShadow.set(null)\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n </div>\n </form>\n }\n }\n @if (changingAccount(); as change) {\n @if (change.agentId === agent.id) {\n <form class=\"stack\" data-account-form (submit)=\"$event.preventDefault(); applyAccount(agent)\">\n <p class=\"muted\" [attr.title]=\"accountTitle(agent.serviceAccount?.configuredActorId)\">{{ 'nh-assistant.admin.agents.service-account.explain.' + change.mode | translate: { account: accountName(agent.serviceAccount?.configuredActorId) } }}</p>\n @if (change.mode === 'set') {\n <div class=\"field\">\n <label [for]=\"id + '-' + agent.id + '-account'\">{{ (accountSearch() ? 'nh-assistant.admin.agents.service-account.input-label-search' : 'nh-assistant.admin.agents.service-account.input-label') | translate }}</label>\n <nh-assistant-account-picker\n [inputId]=\"id + '-' + agent.id + '-account'\"\n [value]=\"accountId()\"\n [searchable]=\"accountSearch()\"\n [invalid]=\"accountError() !== null\"\n [describedBy]=\"accountError() ? id + '-' + agent.id + '-account-error' : null\"\n (valueChange)=\"onAccountChange($event)\" />\n @if (accountError(); as failure) {\n <p class=\"field-error\" data-account-error [id]=\"id + '-' + agent.id + '-account-error'\">\n <nh-assistant-icon name=\"warning\" />{{ accountErrorKeys() | nhAssistantTranslate: failure.code }}\n </p>\n }\n </div>\n }\n <div class=\"field\">\n <label [for]=\"id + '-' + agent.id + '-account-reason'\">{{ 'nh-assistant.admin.agents.state.note-label' | translate }}</label>\n <input\n type=\"text\"\n autocomplete=\"off\"\n [id]=\"id + '-' + agent.id + '-account-reason'\"\n [attr.aria-describedby]=\"id + '-' + agent.id + '-account-reason-hint'\"\n [maxLength]=\"maxReason\"\n [value]=\"stateReason()\"\n (input)=\"stateReason.set(value($event))\" />\n <p class=\"hint\" [id]=\"id + '-' + agent.id + '-account-reason-hint'\">{{ 'nh-assistant.admin.agents.state.note-hint' | translate }}</p>\n </div>\n <div class=\"card-actions\">\n <button type=\"submit\" class=\"primary\" [disabled]=\"busyAgentId() !== null\">\n {{ 'nh-assistant.admin.agents.service-account.confirm.' + change.mode | translate }}\n </button>\n <button class=\"secondary\" type=\"button\" (click)=\"changingAccount.set(null)\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n </div>\n </form>\n }\n }\n @if (error(); as failure) {\n @if (errorAgentId() === agent.id) {\n <p class=\"form-error\" role=\"alert\" data-agent-error><nh-assistant-icon name=\"warning\" />{{ errorKeys() | nhAssistantTranslate: failure.code }}</p>\n }\n }\n </li>\n }\n </ul>\n }\n @if (runNowAgent(); as runAgent) {\n <nh-assistant-run-now-dialog\n [agent]=\"runAgent\"\n [subjectTypes]=\"catalog()?.subjectTypes ?? []\"\n [divisions]=\"catalog()?.divisions === true\"\n (openConversation)=\"openConversation($event)\"\n (closed)=\"runningNow.set(null)\" />\n }\n </section>\n}\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;min-width:0;color:var(--nha-text);font-family:var(--nha-font);font-size:14px}:host fieldset{min-width:0;margin:0;border:0;padding:0}:host legend,:host .label,:host .field>label{display:block;margin-bottom:6px;padding:0;color:var(--nha-text);font-size:12px;font-weight:700}:host input[type=text],:host input[type=url],:host input[type=password],:host input[type=search],:host input[type=email],:host input[type=number],:host input[type=date],:host input[type=time],:host input[type=datetime-local],:host select,:host textarea{box-sizing:border-box;width:100%;min-width:0;min-height:38px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:8px 10px;color:var(--nha-text);background:var(--nha-background);font:inherit;font-size:13px}:host input[type=text]:focus-visible,:host input[type=url]:focus-visible,:host input[type=password]:focus-visible,:host input[type=search]:focus-visible,:host input[type=email]:focus-visible,:host input[type=number]:focus-visible,:host input[type=date]:focus-visible,:host input[type=time]:focus-visible,:host input[type=datetime-local]:focus-visible,:host select:focus-visible,:host textarea:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host input[type=text]:disabled,:host input[type=url]:disabled,:host input[type=password]:disabled,:host input[type=search]:disabled,:host input[type=email]:disabled,:host input[type=number]:disabled,:host input[type=date]:disabled,:host input[type=time]:disabled,:host input[type=datetime-local]:disabled,:host select:disabled,:host textarea:disabled{cursor:not-allowed;opacity:.6}:host input[type=text][aria-invalid=true],:host input[type=url][aria-invalid=true],:host input[type=password][aria-invalid=true],:host input[type=search][aria-invalid=true],:host input[type=email][aria-invalid=true],:host input[type=number][aria-invalid=true],:host input[type=date][aria-invalid=true],:host input[type=time][aria-invalid=true],:host input[type=datetime-local][aria-invalid=true],:host select[aria-invalid=true],:host textarea[aria-invalid=true]{border-color:var(--nha-danger)}:host textarea{line-height:1.45;resize:vertical}:host input[type=number]{font-variant-numeric:tabular-nums}:host .mono{font-family:var(--nha-mono);font-size:12px}:host .field{display:flex;flex-direction:column;min-width:0}:host .hint{display:flex;justify-content:space-between;gap:8px;margin:4px 2px 0;color:var(--nha-text-muted);font-size:11px}:host .counter{flex:none;font-variant-numeric:tabular-nums}:host .error-text{color:var(--nha-danger)}:host .field-error{display:flex;align-items:flex-start;gap:6px;margin:4px 2px 0;color:var(--nha-danger);font-size:12px;font-weight:600;overflow-wrap:anywhere}:host .field-error nh-assistant-icon{flex:none;margin-top:1px}:host .form-error{box-sizing:border-box;display:flex;align-items:flex-start;gap:8px;margin:0;border:1px solid var(--nha-danger);border-radius:var(--nha-radius-control);padding:8px 12px;color:var(--nha-text);background:var(--nha-danger-soft);font-size:13px;overflow-wrap:anywhere}:host .form-error ul{margin:4px 0 0;padding-left:18px}:host .message{box-sizing:border-box;display:flex;align-items:center;gap:8px;margin:0;border:1px solid transparent;border-radius:var(--nha-radius-control);padding:8px 12px;font-size:13px;overflow-wrap:anywhere}:host .message.error{border-color:var(--nha-danger);color:var(--nha-text);background:var(--nha-danger-soft)}:host .message.warning{border-color:var(--nha-warning);color:var(--nha-text);background:var(--nha-warning-soft)}:host .message.success{border-color:var(--nha-success);color:var(--nha-text);background:var(--nha-success-soft)}:host .message.info{border-color:var(--nha-border);color:var(--nha-text);background:var(--nha-surface)}:host .message nh-assistant-icon{flex:none}:host button.link{min-height:0;border:0;padding:0;color:var(--nha-accent, currentColor);background:none;font:inherit;text-decoration:underline;cursor:pointer}:host button.link:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host button.link:disabled{cursor:not-allowed;opacity:.55}:host .primary,:host .secondary,:host .danger{display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;gap:6px;min-height:36px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px 14px;font:inherit;font-size:13px;font-weight:700;white-space:nowrap;cursor:pointer}:host .primary:focus-visible,:host .secondary:focus-visible,:host .danger:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .primary:disabled,:host .secondary:disabled,:host .danger:disabled{cursor:not-allowed;opacity:.55}:host .primary{border-color:var(--nha-accent);color:var(--nha-accent-contrast);background:var(--nha-accent)}:host .primary:hover:not(:disabled){filter:brightness(1.12)}:host .secondary{color:var(--nha-text);background:transparent}:host .secondary:hover:not(:disabled){background:var(--nha-surface-strong)}:host .danger{border-color:var(--nha-danger);color:var(--nha-danger);background:transparent}:host .danger:hover:not(:disabled){background:var(--nha-danger-soft)}:host .icon-button{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:1px solid transparent;border-radius:var(--nha-radius-control);color:var(--nha-text-muted);background:transparent;cursor:pointer}:host .icon-button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .icon-button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}:host .icon-button:disabled{cursor:not-allowed;opacity:.5}:host .actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px}:host .badge{display:inline-flex;align-items:center;gap:4px;border-radius:999px;padding:2px 8px;color:var(--nha-text-muted);background:var(--nha-surface-strong);font-size:11px;font-weight:700;white-space:nowrap}:host .badge.success{color:var(--nha-success);background:var(--nha-success-soft)}:host .badge.warning{color:var(--nha-warning);background:var(--nha-warning-soft)}:host .badge.danger{color:var(--nha-danger);background:var(--nha-danger-soft)}:host .visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}.section{display:flex;flex-direction:column;gap:16px;min-width:0}.section-header{display:flex;flex-wrap:wrap;align-items:flex-start;justify-content:space-between;gap:12px}.section-header>div{min-width:0;max-width:72ch}h3{margin:0 0 4px;font-size:17px;font-weight:750}h4{margin:0 0 8px;font-size:14px;font-weight:700}.muted{margin:0;color:var(--nha-text-muted);font-size:13px}.state{display:flex;flex-direction:column;align-items:flex-start;gap:10px;margin:0;border:1px dashed var(--nha-border);border-radius:var(--nha-radius);padding:20px;color:var(--nha-text-muted);font-size:13px}.state p{margin:0}.stack{display:flex;flex-direction:column;gap:14px;min-width:0}.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,240px),1fr));gap:14px}.meta{display:flex;flex-wrap:wrap;align-items:center;gap:8px 12px;color:var(--nha-text-muted);font-size:12px;overflow-wrap:anywhere}textarea.large{min-height:260px}.catalog-error{flex-wrap:wrap}.grow{flex:1;min-width:0}.subsection{display:flex;flex-direction:column;gap:8px;border-top:1px solid var(--nha-border);padding-top:14px}.history{display:grid;gap:6px;margin:0;padding:0;list-style:none}.history li{display:flex;flex-wrap:wrap;align-items:center;gap:6px 12px;font-size:12px}.cards{display:grid;gap:10px;margin:0;padding:0;list-style:none}.card{display:flex;flex-direction:column;gap:8px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius);padding:14px;background:var(--nha-background)}.card.inactive{opacity:.8}.card-header{display:flex;flex-wrap:wrap;align-items:center;gap:8px;min-width:0}.card-header strong{font-size:14px;overflow-wrap:anywhere}.card-actions{display:flex;flex-wrap:wrap;gap:8px}.check{display:flex;align-items:flex-start;gap:8px;font-size:13px;cursor:pointer}.check input{width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.check.described>span{display:grid;gap:2px;min-width:0;overflow-wrap:anywhere}.checks{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));gap:6px 12px;min-width:0;overflow-wrap:anywhere}.chips{display:flex;flex-wrap:wrap;gap:6px;margin:0;padding:0;list-style:none}.chip{display:inline-flex;align-items:center;gap:2px;border:1px solid var(--nha-border);border-radius:999px;padding:2px 2px 2px 10px;background:var(--nha-surface);font-family:var(--nha-mono);font-size:12px}.chip 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;width:26px;height:26px;border-radius:999px}.chip button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.chip button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}.chip button:disabled{cursor:not-allowed;opacity:.5}.inline{display:flex;flex-wrap:wrap;gap:8px}.inline>input{flex:1 1 180px}.catalog{display:grid;gap:4px;max-height:260px;margin:8px 0 0;overflow-y:auto;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px;list-style:none}.catalog li label{display:flex;flex-wrap:wrap;align-items:center;gap:6px 8px;border-radius:8px;padding:6px;cursor:pointer}.catalog li label:hover{background:var(--nha-surface)}.catalog .tool-id{font-family:var(--nha-mono);font-size:12px;overflow-wrap:anywhere}.skills{display:grid;gap:8px;margin:0;padding:0;list-style:none}.skill{display:grid;gap:6px;border-left:3px solid var(--nha-border);padding:4px 0 4px 12px}.uses{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));gap:8px}.use{display:flex;align-items:flex-start;gap:10px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:10px 12px;cursor:pointer;overflow-wrap:anywhere}.use input{flex:none;width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.use>span{display:grid;gap:2px;min-width:0}.use strong{font-size:13px}.use.selected{border-color:var(--nha-highlight);background:var(--nha-surface)}.use:focus-within{outline:2px solid var(--nha-highlight);outline-offset:2px}.discovery{display:grid;gap:8px;padding:10px 12px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control)}.discovery dl{display:grid;grid-template-columns:max-content minmax(0,1fr);gap:4px 12px;margin:0}.discovery dt{color:var(--nha-text-muted)}.discovery dd{margin:0;overflow-wrap:anywhere}\n"] }]
|
|
4239
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (editing() !== undefined) {\n <nh-assistant-admin-agent-editor\n [agent]=\"editing() ?? null\"\n [tools]=\"tools()\"\n [servers]=\"servers()\"\n [catalog]=\"catalog()\"\n [catalogUnavailable]=\"catalogError() !== null\"\n (saved)=\"onEditorSaved($event)\"\n (workRequested)=\"onWorkRequested($event)\"\n (cancelled)=\"onEditorCancelled()\" />\n} @else if (reporting(); as agent) {\n <div class=\"stack\">\n <div>\n <button #back class=\"secondary\" type=\"button\" (click)=\"closeReport()\">\n {{ 'nh-assistant.admin.feedback.back' | translate }}\n </button>\n </div>\n <p class=\"muted\">{{ 'nh-assistant.admin.feedback.agent' | translate: { name: (codeTextKeys(agent.source, agent.displayName) | nhAssistantTranslate: agent.displayName), id: agent.id } }}</p>\n <nh-assistant-agent-feedback-report [agentId]=\"agent.id\" />\n </div>\n} @else if (configuring(); as agent) {\n <div class=\"stack\">\n <div>\n <button #back class=\"secondary\" type=\"button\" (click)=\"closeRunConfiguration()\">\n {{ 'nh-assistant.admin.run-config.back' | translate }}\n </button>\n </div>\n <nh-assistant-agent-run-configuration\n [agent]=\"agent\"\n [startSetup]=\"setupOnOpen()\"\n [tools]=\"tools()\"\n [servers]=\"servers()\"\n (editAgent)=\"editFromRunConfiguration(agent)\"\n (openRuns)=\"runsFromRunConfiguration(agent)\"\n (changeAccount)=\"accountFromRunConfiguration(agent)\"\n (openMcpServers)=\"openMcpServers.emit()\" />\n </div>\n} @else if (viewingRuns(); as agent) {\n <div class=\"stack\">\n <div>\n <button #back class=\"secondary\" type=\"button\" (click)=\"closeRuns()\">\n {{ (returnTo() === agent.id ? 'nh-assistant.admin.runs.back-to-run-config' : 'nh-assistant.admin.runs.back') | translate }}\n </button>\n </div>\n <p class=\"muted\">{{ 'nh-assistant.admin.feedback.agent' | translate: { name: (codeTextKeys(agent.source, agent.displayName) | nhAssistantTranslate: agent.displayName), id: agent.id } }}</p>\n <nh-assistant-agent-run-history\n [agentId]=\"agent.id\"\n [subjectTypes]=\"catalog()?.subjectTypes ?? []\"\n (openConversation)=\"openConversation($event)\" />\n </div>\n} @else if (evaluating(); as agent) {\n <div class=\"stack\">\n <div>\n <button #back class=\"secondary\" type=\"button\" (click)=\"closeEvaluation()\">\n {{ 'nh-assistant.admin.evaluation.back' | translate }}\n </button>\n </div>\n <p class=\"muted\">{{ 'nh-assistant.admin.feedback.agent' | translate: { name: (codeTextKeys(agent.source, agent.displayName) | nhAssistantTranslate: agent.displayName), id: agent.id } }}</p>\n <nh-assistant-agent-evaluation [agentId]=\"agent.id\" [variants]=\"variantsOf(agent)\" />\n </div>\n} @else {\n <section class=\"section\" [attr.aria-labelledby]=\"id + '-title'\" [attr.aria-busy]=\"loading()\">\n <header class=\"section-header\">\n <div>\n <h3 [id]=\"id + '-title'\">{{ 'nh-assistant.admin.agents.title' | translate }}</h3>\n <p class=\"muted\">{{ 'nh-assistant.admin.agents.description' | translate }}</p>\n </div>\n <button class=\"primary\" type=\"button\" [disabled]=\"loading() || loadFailed()\" (click)=\"create()\">\n <nh-assistant-icon name=\"plus\" />{{ 'nh-assistant.admin.agents.new' | translate }}\n </button>\n </header>\n\n @if (error(); as failure) {\n @if (errorAgentId() === null) {\n <p class=\"message error\" role=\"alert\"><nh-assistant-icon name=\"warning\" />{{ errorKeys() | nhAssistantTranslate: failure.code }}</p>\n }\n }\n @if (!loading() && !loadFailed()) {\n @if (catalogError(); as failure) {\n <!-- Without the run catalog nothing that depends on it is offered: run now, what agents work on, groups. -->\n <div class=\"message warning catalog-error\" role=\"alert\" data-catalog-error>\n <nh-assistant-icon name=\"warning\" />\n <span class=\"grow\">\n {{ 'nh-assistant.admin.agents.catalog-failed' | translate }}\n {{ catalogErrorKeys() | nhAssistantTranslate: failure.code }}\n </span>\n <button class=\"secondary\" type=\"button\" data-catalog-retry [disabled]=\"catalogLoading()\" (click)=\"retryCatalog()\">\n <nh-assistant-icon name=\"refresh\" />{{ 'nh-assistant.admin.retry' | translate }}\n </button>\n </div>\n }\n <nh-assistant-account-names-error [names]=\"accountNameLookup\" />\n }\n\n @if (loading()) {\n <p class=\"state\" role=\"status\">{{ 'nh-assistant.admin.loading' | translate }}</p>\n } @else if (loadFailed()) {\n <div class=\"state\" role=\"alert\">\n <p>{{ 'nh-assistant.admin.load-failed' | translate }}</p>\n <button class=\"secondary\" type=\"button\" (click)=\"load()\">{{ 'nh-assistant.admin.retry' | translate }}</button>\n </div>\n } @else if (agents().length === 0) {\n <p class=\"state\">{{ 'nh-assistant.admin.agents.empty' | translate }}</p>\n } @else {\n <ul class=\"cards\">\n @for (agent of agents(); track agent.id) {\n <li class=\"card\" [class.inactive]=\"agent.runState !== 'active'\" [attr.aria-busy]=\"busyAgentId() === agent.id\">\n <div class=\"card-header\">\n <!-- An agent in code names and describes itself with translation keys. -->\n <strong>{{ (codeTextKeys(agent.source, agent.displayName) | nhAssistantTranslate: agent.displayName) }}</strong>\n <span class=\"mono muted\">{{ agent.id }}</span>\n <span class=\"badge\">{{ (agent.source === 'code' ? 'nh-assistant.admin.agents.source-code' : 'nh-assistant.admin.agents.source-admin') | translate }}</span>\n @if (agent.isOverridden) {\n <span class=\"badge warning\">{{ 'nh-assistant.admin.agents.overridden' | translate }}</span>\n }\n <span\n class=\"badge\"\n [class.success]=\"agent.runState === 'active'\"\n [class.warning]=\"agent.runState === 'paused'\"\n [class.danger]=\"agent.runState === 'stopped'\">{{ 'nh-assistant.admin.agents.state.' + agent.runState | translate }}</span>\n @if (agent.shadow?.shadow) {\n <span class=\"badge warning\" data-shadow-badge>{{ 'nh-assistant.admin.agents.shadow.badge' | translate }}</span>\n }\n @if (agent.offeredInChat === false) {\n <span class=\"badge\" data-hidden-from-chat>{{ 'nh-assistant.admin.agents.chat.hidden' | translate }}</span>\n }\n </div>\n @if (agent.description) {\n <p class=\"muted\">{{ codeTextKeys(agent.source, agent.description) | nhAssistantTranslate: agent.description }}</p>\n }\n <!-- A warning never refused the save: the agent is in effect but may not work as configured. -->\n @for (warning of agent.warnings ?? []; track warning) {\n <p class=\"message warning\" [attr.data-agent-warning]=\"warning\">\n <nh-assistant-icon name=\"warning\" />{{ warningKeys('agent', warning) | nhAssistantTranslate: warning }}\n </p>\n }\n @if (agent.runState === 'configured-off') {\n <p class=\"muted\">{{ 'nh-assistant.admin.agents.state.configured-off-hint' | translate }}</p>\n } @else if (agent.runState !== 'active') {\n <p class=\"muted\">\n @if (agent.stateChangedAt) {\n <span [attr.title]=\"accountTitle(agent.stateChangedBy)\">{{ 'nh-assistant.admin.agents.state.changed' | translate: { user: accountName(agent.stateChangedBy), date: (agent.stateChangedAt | date: 'medium') } }}</span>\n }\n @if (agent.stateReason) {\n {{ 'nh-assistant.admin.agents.state.reason' | translate: { reason: agent.stateReason } }}\n }\n </p>\n @if (agent.runState === 'paused') {\n <p class=\"muted\">{{ 'nh-assistant.admin.agents.state.waiting' | translate: { count: agent.waitingInputs } }}</p>\n }\n @if (agent.runState === 'off') {\n <p class=\"muted\" data-off-hint>\n {{ 'nh-assistant.admin.agents.state.off-hint' | translate }}\n @if (!agent.stateChangedAt) {\n {{ 'nh-assistant.admin.agents.state.starts-off' | translate }}\n }\n </p>\n }\n }\n @if (agent.shadow; as shadow) {\n @if (shadow.shadow) {\n <p class=\"muted\">\n {{ (shadow.locked ? 'nh-assistant.admin.agents.shadow.locked' : 'nh-assistant.admin.agents.shadow.hint') | translate }}\n @if (shadow.changedAt && shadow.override !== null) {\n <span [attr.title]=\"accountTitle(shadow.changedBy)\">{{ 'nh-assistant.admin.agents.shadow.changed' | translate: { user: accountName(shadow.changedBy), date: (shadow.changedAt | date: 'medium') } }}</span>\n }\n @if (shadow.reason && shadow.override !== null) {\n {{ 'nh-assistant.admin.agents.state.reason' | translate: { reason: shadow.reason } }}\n }\n </p>\n }\n }\n @if (agent.serviceAccount; as account) {\n <p class=\"muted\" data-service-account>\n <span [attr.title]=\"accountTitle(account.actorId)\">{{ 'nh-assistant.admin.agents.service-account.label' | translate: { account: accountName(account.actorId) } }}</span>\n @if (account.overridden) {\n <span [attr.title]=\"accountTitle(account.configuredActorId)\">{{ 'nh-assistant.admin.agents.service-account.configured' | translate: { account: accountName(account.configuredActorId) } }}</span>\n @if (account.reason) {\n {{ 'nh-assistant.admin.agents.state.reason' | translate: { reason: account.reason } }}\n }\n }\n </p>\n }\n @if (worksOn(agent); as works) {\n <!-- What the agent works on; an agent in code shows its code setting. -->\n <p class=\"muted\" data-works-on>\n <span>{{ 'nh-assistant.admin.agents.works-on.label' | translate }}</span>\n <strong>\n @switch (works.only) {\n @case ('all') { {{ 'nh-assistant.admin.agents.works-on.all' | translate }} }\n @case ('types') { {{ works.types.join(', ') }} }\n @default { {{ 'nh-assistant.admin.agents.works-on.application' | translate }} }\n }\n </strong>\n @if (works.application && works.only !== 'application') {\n <span>{{ 'nh-assistant.admin.agents.works-on.and-application' | translate }}</span>\n }\n @if (works.perDivision) {\n <span>{{ 'nh-assistant.admin.agents.works-on.per-division' | translate }}</span>\n }\n @if (works.undeclared) {\n <span>{{ 'nh-assistant.admin.agents.works-on.undeclared' | translate }}</span>\n }\n </p>\n @if (works.all && works.known.length > 0) {\n <p class=\"muted\" data-known-types>{{ 'nh-assistant.admin.agents.works-on.known' | translate: { types: works.known.join(', ') } }}</p>\n }\n @if (works.runPolicy || works.viewPolicy) {\n <!-- Each policy is an item of its own, wrapping on narrow screens. -->\n <div class=\"meta\" data-works-on-policies>\n @if (works.runPolicy; as policy) {\n <span>{{ 'nh-assistant.admin.agents.works-on.run-policy' | translate: { policy: (policyKeys(policy) | nhAssistantTranslate: policy) } }}</span>\n }\n @if (works.viewPolicy; as policy) {\n <span>{{ 'nh-assistant.admin.agents.works-on.view-policy' | translate: { policy: (policyKeys(policy) | nhAssistantTranslate: policy) } }}</span>\n }\n </div>\n }\n }\n @if (groupNames(agent); as names) {\n <p class=\"muted\" data-agent-groups>{{ 'nh-assistant.admin.agents.groups' | translate: { groups: names } }}</p>\n }\n <div class=\"meta\">\n <span>{{ 'nh-assistant.admin.agents.autonomy.' + agent.autonomy | translate }}</span>\n <span>{{ (agent.toolSelectors.length === 1 ? 'nh-assistant.admin.agents.tool-count-one' : 'nh-assistant.admin.agents.tool-count') | translate: { count: agent.toolSelectors.length } }}</span>\n <span>{{ (agent.mcpServerIds.length === 1 ? 'nh-assistant.admin.agents.server-count-one' : 'nh-assistant.admin.agents.server-count') | translate: { count: agent.mcpServerIds.length } }}</span>\n <span>{{ 'nh-assistant.admin.agents.version' | translate: { version: agent.version } }}</span>\n @if (agent.requiredPolicy; as policy) {\n <!-- The application may name its policies; otherwise the policy shows as it is called in code. -->\n <span data-required-policy>\n {{ 'nh-assistant.admin.agents.required-policy' | translate }}\n @if (policyKeys(policy) | nhAssistantTranslate; as text) {\n {{ text }}\n } @else {\n <code class=\"mono\">{{ policy }}</code>\n }\n </span>\n }\n </div>\n <div class=\"card-actions\">\n <button class=\"secondary\" type=\"button\" [disabled]=\"busyAgentId() !== null\" (click)=\"edit(agent)\">\n <nh-assistant-icon name=\"edit\" />{{ 'nh-assistant.admin.edit' | translate }}\n </button>\n @for (action of stateActions(agent); track action) {\n <button\n type=\"button\"\n [class.secondary]=\"action !== 'stopped'\"\n [class.danger]=\"action === 'stopped'\"\n [attr.data-state-action]=\"action\"\n [attr.aria-expanded]=\"isChangingState(agent.id, action)\"\n [disabled]=\"busyAgentId() !== null\"\n (click)=\"askState(agent, action)\">\n {{ stateKey('action', agent.runState, action) | translate }}\n </button>\n }\n @if (agent.runState !== 'configured-off') {\n @for (mode of shadowActions(agent.shadow); track mode) {\n <button\n class=\"secondary\"\n type=\"button\"\n [attr.data-shadow-action]=\"mode\"\n [attr.aria-expanded]=\"isChangingShadow(agent.id, mode)\"\n [disabled]=\"busyAgentId() !== null\"\n (click)=\"askShadow(agent, mode)\">\n {{ shadowActionKey(agent, mode) | translate }}\n </button>\n }\n }\n @if (agent.serviceAccount; as account) {\n <button\n class=\"secondary\"\n type=\"button\"\n data-account-action=\"set\"\n [attr.aria-expanded]=\"isChangingAccount(agent.id, 'set')\"\n [disabled]=\"busyAgentId() !== null\"\n (click)=\"askAccount(agent, 'set')\">\n {{ 'nh-assistant.admin.agents.service-account.action.set' | translate }}\n </button>\n @if (account.overridden) {\n <button\n class=\"secondary\"\n type=\"button\"\n data-account-action=\"code\"\n [attr.aria-expanded]=\"isChangingAccount(agent.id, 'code')\"\n [disabled]=\"busyAgentId() !== null\"\n (click)=\"askAccount(agent, 'code')\">\n {{ 'nh-assistant.admin.agents.service-account.action.code' | translate }}\n </button>\n }\n }\n @if (hasRunConfiguration(agent)) {\n <button class=\"secondary\" type=\"button\" [attr.data-run-config-agent]=\"agent.id\" (click)=\"openRunConfiguration(agent)\">\n <nh-assistant-icon name=\"list\" />{{ 'nh-assistant.admin.agents.run-config' | translate }}\n </button>\n }\n @if (hasRunConfiguration(agent)) {\n <button class=\"secondary\" type=\"button\" [attr.data-runs-agent]=\"agent.id\" (click)=\"openRuns(agent)\">\n <nh-assistant-icon name=\"list\" />{{ 'nh-assistant.admin.agents.runs' | translate }}\n </button>\n }\n @if (canRunNow(agent)) {\n <button\n class=\"secondary\"\n type=\"button\"\n aria-haspopup=\"dialog\"\n [attr.data-run-now-agent]=\"agent.id\"\n [disabled]=\"busyAgentId() !== null\"\n (click)=\"runNow(agent)\">\n <nh-assistant-icon name=\"send\" />{{ 'nh-assistant.admin.agents.run-now' | translate }}\n </button>\n }\n <button class=\"secondary\" type=\"button\" [attr.data-feedback-agent]=\"agent.id\" (click)=\"openReport(agent)\">\n <nh-assistant-icon name=\"list\" />{{ 'nh-assistant.admin.agents.feedback' | translate }}\n </button>\n @if (variantsOf(agent).length > 0) {\n <button class=\"secondary\" type=\"button\" [attr.data-evaluation-agent]=\"agent.id\" (click)=\"openEvaluation(agent)\">\n <nh-assistant-icon name=\"list\" />{{ 'nh-assistant.admin.agents.evaluation' | translate }}\n </button>\n }\n @if (agent.source === 'code' && agent.isOverridden) {\n @if (isConfirming(agent.id, 'reset')) {\n <span class=\"muted\">{{ 'nh-assistant.admin.agents.reset-keeps-state' | translate }}</span>\n <button class=\"danger\" type=\"button\" (click)=\"reset(agent)\">{{ 'nh-assistant.admin.agents.confirm-reset' | translate }}</button>\n <button class=\"secondary\" type=\"button\" (click)=\"confirming.set(null)\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n } @else {\n <button class=\"secondary\" type=\"button\" [disabled]=\"busyAgentId() !== null\" (click)=\"confirm(agent.id, 'reset')\">\n <nh-assistant-icon name=\"refresh\" />{{ 'nh-assistant.admin.agents.reset' | translate }}\n </button>\n }\n }\n @if (agent.source === 'admin') {\n @if (isConfirming(agent.id, 'delete')) {\n <button class=\"danger\" type=\"button\" (click)=\"remove(agent)\">{{ 'nh-assistant.admin.confirm-delete' | translate }}</button>\n <button class=\"secondary\" type=\"button\" (click)=\"confirming.set(null)\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n } @else {\n <button class=\"danger\" type=\"button\" [disabled]=\"busyAgentId() !== null\" (click)=\"confirm(agent.id, 'delete')\">\n <nh-assistant-icon name=\"trash\" />{{ 'nh-assistant.admin.delete' | translate }}\n </button>\n }\n }\n </div>\n @if (agent.skills?.length) {\n <ul class=\"skills\" [attr.aria-label]=\"'nh-assistant.admin.agents.skills.title' | translate\">\n @for (skill of agent.skills; track skill.skillId) {\n <li class=\"skill\" [attr.data-skill]=\"skill.skillId\">\n <div class=\"card-header\">\n <strong>{{ (skillNameKeys(agent.id, skill.skillId) | nhAssistantTranslate: readableId(skill.skillId)) }}</strong>\n <span class=\"mono muted\">{{ skill.skillId }}</span>\n <span\n class=\"badge\"\n [class.success]=\"skill.runState === 'active'\"\n [class.warning]=\"skill.runState === 'paused'\"\n [class.danger]=\"skill.runState === 'stopped'\">{{ 'nh-assistant.admin.agents.state.' + skill.runState | translate }}</span>\n @if (skill.shadow?.shadow) {\n <span class=\"badge warning\" data-shadow-badge>{{ 'nh-assistant.admin.agents.shadow.badge' | translate }}</span>\n }\n </div>\n <p class=\"muted\">{{ skillDescriptionKeys(agent.id, skill.skillId) | nhAssistantTranslate: skill.description }}</p>\n <div class=\"meta\">\n <span>{{ 'nh-assistant.admin.agents.skills.usage' | translate: { calls: skill.toolCallsToday, tokens: skill.tokensToday } }}</span>\n @if (skill.dailyToolCalls !== null) {\n <span>{{ 'nh-assistant.admin.agents.skills.budget' | translate: { calls: skill.dailyToolCalls, tokens: skill.dailyTokens } }}</span>\n }\n @if (skill.runState === 'paused') {\n <span>{{ 'nh-assistant.admin.agents.state.waiting' | translate: { count: skill.waitingInputs } }}</span>\n }\n @if (skill.reason) {\n <span>{{ 'nh-assistant.admin.agents.state.reason' | translate: { reason: skill.reason } }}</span>\n }\n </div>\n @if (agent.runState !== 'configured-off') {\n <div class=\"card-actions\">\n @for (action of skillActions(skill); track action) {\n <button\n type=\"button\"\n [class.secondary]=\"action !== 'stopped'\"\n [class.danger]=\"action === 'stopped'\"\n [attr.data-state-action]=\"action\"\n [attr.aria-label]=\"(stateKey('action', skill.runState, action) | translate) + ' ' + (skillNameKeys(agent.id, skill.skillId) | nhAssistantTranslate: readableId(skill.skillId))\"\n [attr.aria-expanded]=\"isChangingState(agent.id, action, skill.skillId)\"\n [disabled]=\"busyAgentId() !== null\"\n (click)=\"askState(agent, action, skill)\">\n {{ stateKey('action', skill.runState, action) | translate }}\n </button>\n }\n @for (mode of shadowActions(skill.shadow); track mode) {\n <button\n class=\"secondary\"\n type=\"button\"\n [attr.data-shadow-action]=\"mode\"\n [attr.aria-label]=\"(shadowActionKey(agent, mode) | translate) + ' ' + (skillNameKeys(agent.id, skill.skillId) | nhAssistantTranslate: readableId(skill.skillId))\"\n [attr.aria-expanded]=\"isChangingShadow(agent.id, mode, skill.skillId)\"\n [disabled]=\"busyAgentId() !== null\"\n (click)=\"askShadow(agent, mode, skill)\">\n {{ shadowActionKey(agent, mode) | translate }}\n </button>\n }\n </div>\n }\n </li>\n }\n </ul>\n }\n @if (changingState(); as change) {\n @if (change.agentId === agent.id) {\n <form class=\"stack\" (submit)=\"$event.preventDefault(); applyState(agent)\">\n @if (change.skillId) {\n <p class=\"muted\">{{ skillStateExplainKey(change) | translate: { skill: (skillNameKeys(agent.id, change.skillId) | nhAssistantTranslate: readableId(change.skillId)) } }}</p>\n } @else {\n <p class=\"muted\">{{ stateKey('explain', change.from, change.state) | translate }}</p>\n }\n <div class=\"field\">\n <label [for]=\"id + '-' + agent.id + '-reason'\">{{ 'nh-assistant.admin.agents.state.note-label' | translate }}</label>\n <input\n #reasonInput\n type=\"text\"\n autocomplete=\"off\"\n [id]=\"id + '-' + agent.id + '-reason'\"\n [attr.aria-describedby]=\"id + '-' + agent.id + '-reason-hint'\"\n [maxLength]=\"maxReason\"\n [value]=\"stateReason()\"\n (input)=\"stateReason.set(value($event))\" />\n <p class=\"hint\" [id]=\"id + '-' + agent.id + '-reason-hint'\">{{ 'nh-assistant.admin.agents.state.note-hint' | translate }}</p>\n </div>\n <div class=\"card-actions\">\n <button type=\"submit\" [class.primary]=\"change.state !== 'stopped'\" [class.danger]=\"change.state === 'stopped'\" [disabled]=\"busyAgentId() !== null\">\n {{ stateKey('confirm', change.from, change.state) | translate }}\n </button>\n <button class=\"secondary\" type=\"button\" (click)=\"changingState.set(null)\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n </div>\n </form>\n }\n }\n @if (changingShadow(); as change) {\n @if (change.agentId === agent.id) {\n <form class=\"stack\" data-shadow-form (submit)=\"$event.preventDefault(); applyShadow(agent)\">\n @if (change.skillId) {\n <p class=\"muted\">{{ shadowExplainKey(agent, change.mode, true) | translate: { skill: (skillNameKeys(agent.id, change.skillId) | nhAssistantTranslate: readableId(change.skillId)), mode: (ownShadowModeKey(agent, change.skillId) | translate) } }}</p>\n } @else {\n <p class=\"muted\">{{ shadowExplainKey(agent, change.mode, false) | translate: { mode: (ownShadowModeKey(agent) | translate) } }}</p>\n }\n <div class=\"field\">\n <label [for]=\"id + '-' + agent.id + '-shadow-reason'\">{{ 'nh-assistant.admin.agents.state.note-label' | translate }}</label>\n <input\n #reasonInput\n type=\"text\"\n autocomplete=\"off\"\n [id]=\"id + '-' + agent.id + '-shadow-reason'\"\n [attr.aria-describedby]=\"id + '-' + agent.id + '-shadow-reason-hint'\"\n [maxLength]=\"maxReason\"\n [value]=\"stateReason()\"\n (input)=\"stateReason.set(value($event))\" />\n <p class=\"hint\" [id]=\"id + '-' + agent.id + '-shadow-reason-hint'\">{{ 'nh-assistant.admin.agents.state.note-hint' | translate }}</p>\n </div>\n <div class=\"card-actions\">\n <button type=\"submit\" class=\"primary\" [disabled]=\"busyAgentId() !== null\">\n {{ 'nh-assistant.admin.agents.shadow.confirm.' + change.mode | translate }}\n </button>\n <button class=\"secondary\" type=\"button\" (click)=\"changingShadow.set(null)\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n </div>\n </form>\n }\n }\n @if (changingAccount(); as change) {\n @if (change.agentId === agent.id) {\n <form class=\"stack\" data-account-form (submit)=\"$event.preventDefault(); applyAccount(agent)\">\n <p class=\"muted\" [attr.title]=\"accountTitle(agent.serviceAccount?.configuredActorId)\">{{ 'nh-assistant.admin.agents.service-account.explain.' + change.mode | translate: { account: accountName(agent.serviceAccount?.configuredActorId) } }}</p>\n @if (change.mode === 'set') {\n <div class=\"field\">\n <label [for]=\"id + '-' + agent.id + '-account'\">{{ (accountSearch() ? 'nh-assistant.admin.agents.service-account.input-label-search' : 'nh-assistant.admin.agents.service-account.input-label') | translate }}</label>\n <nh-assistant-account-picker\n [inputId]=\"id + '-' + agent.id + '-account'\"\n [value]=\"accountId()\"\n [searchable]=\"accountSearch()\"\n [invalid]=\"accountError() !== null\"\n [describedBy]=\"accountError() ? id + '-' + agent.id + '-account-error' : null\"\n (valueChange)=\"onAccountChange($event)\" />\n @if (accountError(); as failure) {\n <p class=\"field-error\" data-account-error [id]=\"id + '-' + agent.id + '-account-error'\">\n <nh-assistant-icon name=\"warning\" />{{ accountErrorKeys() | nhAssistantTranslate: failure.code }}\n </p>\n }\n </div>\n }\n <div class=\"field\">\n <label [for]=\"id + '-' + agent.id + '-account-reason'\">{{ 'nh-assistant.admin.agents.state.note-label' | translate }}</label>\n <input\n type=\"text\"\n autocomplete=\"off\"\n [id]=\"id + '-' + agent.id + '-account-reason'\"\n [attr.aria-describedby]=\"id + '-' + agent.id + '-account-reason-hint'\"\n [maxLength]=\"maxReason\"\n [value]=\"stateReason()\"\n (input)=\"stateReason.set(value($event))\" />\n <p class=\"hint\" [id]=\"id + '-' + agent.id + '-account-reason-hint'\">{{ 'nh-assistant.admin.agents.state.note-hint' | translate }}</p>\n </div>\n <div class=\"card-actions\">\n <button type=\"submit\" class=\"primary\" [disabled]=\"busyAgentId() !== null\">\n {{ 'nh-assistant.admin.agents.service-account.confirm.' + change.mode | translate }}\n </button>\n <button class=\"secondary\" type=\"button\" (click)=\"changingAccount.set(null)\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n </div>\n </form>\n }\n }\n @if (error(); as failure) {\n @if (errorAgentId() === agent.id) {\n <p class=\"form-error\" role=\"alert\" data-agent-error><nh-assistant-icon name=\"warning\" />{{ errorKeys() | nhAssistantTranslate: failure.code }}</p>\n }\n }\n </li>\n }\n </ul>\n }\n @if (runNowAgent(); as runAgent) {\n <nh-assistant-run-now-dialog\n [agent]=\"runAgent\"\n [subjectTypes]=\"catalog()?.subjectTypes ?? []\"\n [divisions]=\"catalog()?.divisions === true\"\n (openConversation)=\"openConversation($event)\"\n (closed)=\"runningNow.set(null)\" />\n }\n </section>\n}\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;min-width:0;color:var(--nha-text);font-family:var(--nha-font);font-size:14px}:host fieldset{min-width:0;margin:0;border:0;padding:0}:host legend,:host .label,:host .field>label{display:block;margin-bottom:6px;padding:0;color:var(--nha-text);font-size:12px;font-weight:700}:host input[type=text],:host input[type=url],:host input[type=password],:host input[type=search],:host input[type=email],:host input[type=number],:host input[type=date],:host input[type=time],:host input[type=datetime-local],:host select,:host textarea{box-sizing:border-box;width:100%;min-width:0;min-height:38px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:8px 10px;color:var(--nha-text);background:var(--nha-background);font:inherit;font-size:13px}:host input[type=text]:focus-visible,:host input[type=url]:focus-visible,:host input[type=password]:focus-visible,:host input[type=search]:focus-visible,:host input[type=email]:focus-visible,:host input[type=number]:focus-visible,:host input[type=date]:focus-visible,:host input[type=time]:focus-visible,:host input[type=datetime-local]:focus-visible,:host select:focus-visible,:host textarea:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host input[type=text]:disabled,:host input[type=url]:disabled,:host input[type=password]:disabled,:host input[type=search]:disabled,:host input[type=email]:disabled,:host input[type=number]:disabled,:host input[type=date]:disabled,:host input[type=time]:disabled,:host input[type=datetime-local]:disabled,:host select:disabled,:host textarea:disabled{cursor:not-allowed;opacity:.6}:host input[type=text][aria-invalid=true],:host input[type=url][aria-invalid=true],:host input[type=password][aria-invalid=true],:host input[type=search][aria-invalid=true],:host input[type=email][aria-invalid=true],:host input[type=number][aria-invalid=true],:host input[type=date][aria-invalid=true],:host input[type=time][aria-invalid=true],:host input[type=datetime-local][aria-invalid=true],:host select[aria-invalid=true],:host textarea[aria-invalid=true]{border-color:var(--nha-danger)}:host textarea{line-height:1.45;resize:vertical}:host input[type=number]{font-variant-numeric:tabular-nums}:host .mono{font-family:var(--nha-mono);font-size:12px}:host .field{display:flex;flex-direction:column;min-width:0}:host .hint{display:flex;justify-content:space-between;gap:8px;margin:4px 2px 0;color:var(--nha-text-muted);font-size:11px}:host .counter{flex:none;font-variant-numeric:tabular-nums}:host .error-text{color:var(--nha-danger)}:host .field-error{display:flex;align-items:flex-start;gap:6px;margin:4px 2px 0;color:var(--nha-danger);font-size:12px;font-weight:600;overflow-wrap:anywhere}:host .field-error nh-assistant-icon{flex:none;margin-top:1px}:host .form-error{box-sizing:border-box;display:flex;align-items:flex-start;gap:8px;margin:0;border:1px solid var(--nha-danger);border-radius:var(--nha-radius-control);padding:8px 12px;color:var(--nha-text);background:var(--nha-danger-soft);font-size:13px;overflow-wrap:anywhere}:host .form-error ul{margin:4px 0 0;padding-left:18px}:host .message{box-sizing:border-box;display:flex;align-items:center;gap:8px;margin:0;border:1px solid transparent;border-radius:var(--nha-radius-control);padding:8px 12px;font-size:13px;overflow-wrap:anywhere}:host .message.error{border-color:var(--nha-danger);color:var(--nha-text);background:var(--nha-danger-soft)}:host .message.warning{border-color:var(--nha-warning);color:var(--nha-text);background:var(--nha-warning-soft)}:host .message.success{border-color:var(--nha-success);color:var(--nha-text);background:var(--nha-success-soft)}:host .message.info{border-color:var(--nha-border);color:var(--nha-text);background:var(--nha-surface)}:host .message nh-assistant-icon{flex:none}:host button.link{min-height:0;border:0;padding:0;color:var(--nha-accent, currentColor);background:none;font:inherit;text-decoration:underline;cursor:pointer}:host button.link:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host button.link:disabled{cursor:not-allowed;opacity:.55}:host .primary,:host .secondary,:host .danger{display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;gap:6px;min-height:36px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px 14px;font:inherit;font-size:13px;font-weight:700;white-space:nowrap;cursor:pointer}:host .primary:focus-visible,:host .secondary:focus-visible,:host .danger:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .primary:disabled,:host .secondary:disabled,:host .danger:disabled{cursor:not-allowed;opacity:.55}:host .primary{border-color:var(--nha-accent);color:var(--nha-accent-contrast);background:var(--nha-accent)}:host .primary:hover:not(:disabled){filter:brightness(1.12)}:host .secondary{color:var(--nha-text);background:transparent}:host .secondary:hover:not(:disabled){background:var(--nha-surface-strong)}:host .danger{border-color:var(--nha-danger);color:var(--nha-danger);background:transparent}:host .danger:hover:not(:disabled){background:var(--nha-danger-soft)}:host .icon-button{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:1px solid transparent;border-radius:var(--nha-radius-control);color:var(--nha-text-muted);background:transparent;cursor:pointer}:host .icon-button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .icon-button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}:host .icon-button:disabled{cursor:not-allowed;opacity:.5}:host .actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px}:host .badge{display:inline-flex;align-items:center;gap:4px;border-radius:999px;padding:2px 8px;color:var(--nha-text-muted);background:var(--nha-surface-strong);font-size:11px;font-weight:700;white-space:nowrap}:host .badge.success{color:var(--nha-success);background:var(--nha-success-soft)}:host .badge.warning{color:var(--nha-warning);background:var(--nha-warning-soft)}:host .badge.danger{color:var(--nha-danger);background:var(--nha-danger-soft)}:host .visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}.section{display:flex;flex-direction:column;gap:16px;min-width:0}.section-header{display:flex;flex-wrap:wrap;align-items:flex-start;justify-content:space-between;gap:12px}.section-header>div{min-width:0;max-width:72ch}h3{margin:0 0 4px;font-size:17px;font-weight:750}h4{margin:0 0 8px;font-size:14px;font-weight:700}.muted{margin:0;color:var(--nha-text-muted);font-size:13px}.state{display:flex;flex-direction:column;align-items:flex-start;gap:10px;margin:0;border:1px dashed var(--nha-border);border-radius:var(--nha-radius);padding:20px;color:var(--nha-text-muted);font-size:13px}.state p{margin:0}.stack{display:flex;flex-direction:column;gap:14px;min-width:0}.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,240px),1fr));gap:14px}.meta{display:flex;flex-wrap:wrap;align-items:center;gap:8px 12px;color:var(--nha-text-muted);font-size:12px;overflow-wrap:anywhere}textarea.large{min-height:260px}.catalog-error{flex-wrap:wrap}.grow{flex:1;min-width:0}.subsection{display:flex;flex-direction:column;gap:8px;border-top:1px solid var(--nha-border);padding-top:14px}.history{display:grid;gap:6px;margin:0;padding:0;list-style:none}.history li{display:flex;flex-wrap:wrap;align-items:center;gap:6px 12px;font-size:12px}.cards{display:grid;gap:10px;margin:0;padding:0;list-style:none}.card{display:flex;flex-direction:column;gap:8px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius);padding:14px;background:var(--nha-background)}.card.inactive{opacity:.8}.card-header{display:flex;flex-wrap:wrap;align-items:center;gap:8px;min-width:0}.card-header strong{font-size:14px;overflow-wrap:anywhere}.card-actions{display:flex;flex-wrap:wrap;gap:8px}.check{display:flex;align-items:flex-start;gap:8px;font-size:13px;cursor:pointer}.check input{width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.check.described>span{display:grid;gap:2px;min-width:0;overflow-wrap:anywhere}.checks{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));gap:6px 12px;min-width:0;overflow-wrap:anywhere}.chips{display:flex;flex-wrap:wrap;gap:6px;margin:0;padding:0;list-style:none}.chip{display:inline-flex;align-items:center;gap:2px;border:1px solid var(--nha-border);border-radius:999px;padding:2px 2px 2px 10px;background:var(--nha-surface);font-family:var(--nha-mono);font-size:12px}.chip 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;width:26px;height:26px;border-radius:999px}.chip button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.chip button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}.chip button:disabled{cursor:not-allowed;opacity:.5}.inline{display:flex;flex-wrap:wrap;gap:8px}.inline>input{flex:1 1 180px}.catalog{display:grid;gap:4px;max-height:260px;margin:8px 0 0;overflow-y:auto;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px;list-style:none}.catalog li label{display:flex;flex-wrap:wrap;align-items:center;gap:6px 8px;border-radius:8px;padding:6px;cursor:pointer}.catalog li label:hover{background:var(--nha-surface)}.catalog .tool-id{font-family:var(--nha-mono);font-size:12px;overflow-wrap:anywhere}.skills{display:grid;gap:8px;margin:0;padding:0;list-style:none}.skill{display:grid;gap:6px;border-left:3px solid var(--nha-border);padding:4px 0 4px 12px}.uses{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));gap:8px}.use{display:flex;align-items:flex-start;gap:10px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:10px 12px;cursor:pointer;overflow-wrap:anywhere}.use input{flex:none;width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.use>span{display:grid;gap:2px;min-width:0}.use strong{font-size:13px}.use.selected{border-color:var(--nha-highlight);background:var(--nha-surface)}.use:focus-within{outline:2px solid var(--nha-highlight);outline-offset:2px}.discovery{display:grid;gap:8px;padding:10px 12px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control)}.discovery dl{display:grid;grid-template-columns:max-content minmax(0,1fr);gap:4px 12px;margin:0}.discovery dt{color:var(--nha-text-muted)}.discovery dd{margin:0;overflow-wrap:anywhere}\n"] }]
|
|
3978
4240
|
}], ctorParameters: () => [], propDecorators: { backButton: [{ type: i0.ViewChild, args: ['back', { isSignal: true }] }], reasonInput: [{ type: i0.ViewChild, args: ['reasonInput', { isSignal: true }] }], openMcpServers: [{ type: i0.Output, args: ["openMcpServers"] }] } });
|
|
3979
4241
|
function withState(agent, state) {
|
|
3980
4242
|
return {
|
|
@@ -4036,8 +4298,11 @@ const serverFields = [
|
|
|
4036
4298
|
'oauth.organizationAccountLabel',
|
|
4037
4299
|
'oauth.confirmAuthorization',
|
|
4038
4300
|
'requiredPolicy',
|
|
4039
|
-
'headlessUse'
|
|
4301
|
+
'headlessUse',
|
|
4302
|
+
'readers'
|
|
4040
4303
|
];
|
|
4304
|
+
/** The fields of one reader the server reports errors for, as `readers[<index>].<field>`. */
|
|
4305
|
+
const readerFieldPattern = /^readers\[\d+\]\.(mediaTypes|toolName|maxClassification|priority)$/;
|
|
4041
4306
|
/** A header name the server accepts: 1 to 128 letters, digits and dashes. */
|
|
4042
4307
|
const headerNamePattern = /^[A-Za-z0-9-]{1,128}$/;
|
|
4043
4308
|
/** `<prefix>:<name>` as the server accepts it; the name never climbs a path. */
|
|
@@ -4045,13 +4310,25 @@ const secretReferencePattern = /^[a-z][a-z0-9-]{0,31}:[A-Za-z0-9][A-Za-z0-9._/-]
|
|
|
4045
4310
|
const certificateNamePattern = /^[A-Za-z0-9][A-Za-z0-9._-]{0,99}$/;
|
|
4046
4311
|
/** Space-separated scope tokens: printable ASCII without quotes or backslashes (RFC 6749). */
|
|
4047
4312
|
const scopesPattern = /^[\x21\x23-\x5B\x5D-\x7E]+( [\x21\x23-\x5B\x5D-\x7E]+)*$/;
|
|
4313
|
+
/** A media type without parameters, as the server compares them (`type/subtype`). */
|
|
4314
|
+
const mediaTypePattern = /^[a-z0-9][a-z0-9!#$&^_.+-]*\/[a-z0-9][a-z0-9!#$&^_.+-]*$/;
|
|
4315
|
+
/** The most media types one reader reads. */
|
|
4316
|
+
const maxReaderMediaTypes = 16;
|
|
4317
|
+
/** The most readers one server has. */
|
|
4318
|
+
const maxReaders = 16;
|
|
4319
|
+
/** The priorities a reader can have; a higher one is asked first. */
|
|
4320
|
+
const readerPriority = { min: -1000, max: 1000 };
|
|
4048
4321
|
let nextId$3 = 0;
|
|
4322
|
+
let nextReaderKey = 0;
|
|
4049
4323
|
/**
|
|
4050
4324
|
* Creates or edits one MCP server: the authentication with a write-only secret or a reference to
|
|
4051
4325
|
* one, OAuth with discovery the administrator confirms, an optional client certificate, the
|
|
4052
|
-
* transport
|
|
4053
|
-
*
|
|
4054
|
-
*
|
|
4326
|
+
* transport, the use in background runs and, for a server that exists, its readers: per reader the
|
|
4327
|
+
* synced tool that turns files into text, the media types it reads, the highest classification that
|
|
4328
|
+
* may be sent to it and its priority, with readers to add and remove. Every field stops at the length
|
|
4329
|
+
* the server accepts; a refused save shows each of the server's field errors at its field, those of a
|
|
4330
|
+
* reader at that reader (`readers[0].toolName`), for example a secret reference the application cannot
|
|
4331
|
+
* resolve (`not-found`), and moves the focus to the first of them.
|
|
4055
4332
|
*/
|
|
4056
4333
|
class NhAssistantAdminMcpServerEditorComponent {
|
|
4057
4334
|
constructor() {
|
|
@@ -4078,6 +4355,7 @@ class NhAssistantAdminMcpServerEditorComponent {
|
|
|
4078
4355
|
this.transports = ['streamable-http', 'auto'];
|
|
4079
4356
|
this.secretSources = ['stored', 'reference'];
|
|
4080
4357
|
this.connectionScopes = ['organization', 'user'];
|
|
4358
|
+
this.classifications = ['public', 'internal', 'confidential', 'restricted'];
|
|
4081
4359
|
this.serverId = signal('', ...(ngDevMode ? [{ debugName: "serverId" }] : []));
|
|
4082
4360
|
this.displayName = signal('', ...(ngDevMode ? [{ debugName: "displayName" }] : []));
|
|
4083
4361
|
this.url = signal('', ...(ngDevMode ? [{ debugName: "url" }] : []));
|
|
@@ -4108,6 +4386,12 @@ class NhAssistantAdminMcpServerEditorComponent {
|
|
|
4108
4386
|
this.discovering = signal(false, ...(ngDevMode ? [{ debugName: "discovering" }] : []));
|
|
4109
4387
|
this.discoveryError = signal(null, ...(ngDevMode ? [{ debugName: "discoveryError" }] : []));
|
|
4110
4388
|
this.confirmAuthorization = signal(false, ...(ngDevMode ? [{ debugName: "confirmAuthorization" }] : []));
|
|
4389
|
+
/** The readers of the server as the editor shows them, in their order. */
|
|
4390
|
+
this.readers = signal([], ...(ngDevMode ? [{ debugName: "readers" }] : []));
|
|
4391
|
+
this.maxReaders = maxReaders;
|
|
4392
|
+
this.readerPriority = readerPriority;
|
|
4393
|
+
/** The server's synced tools, which can read files; empty until they are loaded. */
|
|
4394
|
+
this.readerTools = signal([], ...(ngDevMode ? [{ debugName: "readerTools" }] : []));
|
|
4111
4395
|
this.submitted = signal(false, ...(ngDevMode ? [{ debugName: "submitted" }] : []));
|
|
4112
4396
|
this.saving = signal(false, ...(ngDevMode ? [{ debugName: "saving" }] : []));
|
|
4113
4397
|
this.error = signal(null, ...(ngDevMode ? [{ debugName: "error" }] : []));
|
|
@@ -4183,8 +4467,39 @@ class NhAssistantAdminMcpServerEditorComponent {
|
|
|
4183
4467
|
const header = this.headerName().trim();
|
|
4184
4468
|
return this.authMode() === 'api-key' && header.length > 0 && !headerNamePattern.test(header);
|
|
4185
4469
|
}, ...(ngDevMode ? [{ debugName: "headerNameInvalid" }] : []));
|
|
4470
|
+
/** The tool names to choose from, keeping the stored tools the list no longer holds selectable. */
|
|
4471
|
+
this.readerToolNames = computed(() => {
|
|
4472
|
+
const names = this.readerTools().map(tool => tool.remoteName);
|
|
4473
|
+
const stored = (this.server()?.readers ?? []).map(reader => reader.toolName).filter(name => !names.includes(name));
|
|
4474
|
+
return [...names, ...stored];
|
|
4475
|
+
}, ...(ngDevMode ? [{ debugName: "readerToolNames" }] : []));
|
|
4476
|
+
/** What is wrong with each reader, in the order of `readers`. */
|
|
4477
|
+
this.readerProblems = computed(() => {
|
|
4478
|
+
const seen = new Set();
|
|
4479
|
+
return this.readers().map(reader => {
|
|
4480
|
+
const types = splitMediaTypes(reader.mediaTypes);
|
|
4481
|
+
const duplicate = reader.toolName.length > 0 && seen.has(reader.toolName);
|
|
4482
|
+
seen.add(reader.toolName);
|
|
4483
|
+
return {
|
|
4484
|
+
types: types.length === 0 || types.some(type => !isMediaType(type)) ||
|
|
4485
|
+
new Set(types.map(type => type.split(';')[0].trim().toLowerCase())).size > maxReaderMediaTypes,
|
|
4486
|
+
tool: reader.toolName.length === 0,
|
|
4487
|
+
duplicate,
|
|
4488
|
+
classification: reader.maxClassification === '',
|
|
4489
|
+
priority: parsePriority(reader.priority) === null
|
|
4490
|
+
};
|
|
4491
|
+
});
|
|
4492
|
+
}, ...(ngDevMode ? [{ debugName: "readerProblems" }] : []));
|
|
4493
|
+
/** Why a reader can or cannot be added: the server's tools are not synced, or it has the most. */
|
|
4494
|
+
this.readerAddHint = computed(() => this.readerToolNames().length === 0
|
|
4495
|
+
? 'nh-assistant.admin.server-editor.reader.tool-sync'
|
|
4496
|
+
: this.readers().length >= maxReaders
|
|
4497
|
+
? 'nh-assistant.admin.server-editor.reader.limit'
|
|
4498
|
+
: 'nh-assistant.admin.server-editor.reader.add-hint', ...(ngDevMode ? [{ debugName: "readerAddHint" }] : []));
|
|
4499
|
+
this.readerInvalid = computed(() => this.readerProblems().some(problems => problems.types || problems.tool || problems.duplicate || problems.classification || problems.priority), ...(ngDevMode ? [{ debugName: "readerInvalid" }] : []));
|
|
4186
4500
|
this.valid = computed(() => !this.idInvalid() && !this.nameMissing() && !this.urlInvalid() && !this.headerNameInvalid() &&
|
|
4187
|
-
!this.userNameInvalid() && !this.secretReferenceInvalid() && !this.certificateInvalid() && !this.oauthInvalid()
|
|
4501
|
+
!this.userNameInvalid() && !this.secretReferenceInvalid() && !this.certificateInvalid() && !this.oauthInvalid() &&
|
|
4502
|
+
!this.readerInvalid(), ...(ngDevMode ? [{ debugName: "valid" }] : []));
|
|
4188
4503
|
this.errorKeys = computed(() => _nhAssistantErrorKeys(this.error()), ...(ngDevMode ? [{ debugName: "errorKeys" }] : []));
|
|
4189
4504
|
/** The server's first error per field of the refused save, until the administrator changes the field. */
|
|
4190
4505
|
this.serverErrors = computed(() => {
|
|
@@ -4199,13 +4514,14 @@ class NhAssistantAdminMcpServerEditorComponent {
|
|
|
4199
4514
|
}, ...(ngDevMode ? [{ debugName: "serverErrors" }] : []));
|
|
4200
4515
|
/** Field errors of fields the editor does not show, listed with the form's error. */
|
|
4201
4516
|
this.otherServerErrors = computed(() => Object.entries(this.serverErrors())
|
|
4202
|
-
.filter(([field]) => !serverFields.includes(field))
|
|
4517
|
+
.filter(([field]) => !serverFields.includes(field) && !this.showsReaderField(field))
|
|
4203
4518
|
.map(([field, value]) => ({ field, keys: this.valueKeys(value) })), ...(ngDevMode ? [{ debugName: "otherServerErrors" }] : []));
|
|
4204
4519
|
this.discoveryErrorKeys = computed(() => _nhAssistantErrorKeys(this.discoveryError()), ...(ngDevMode ? [{ debugName: "discoveryErrorKeys" }] : []));
|
|
4205
4520
|
inject(DestroyRef).onDestroy(() => {
|
|
4206
4521
|
this.subscription?.unsubscribe();
|
|
4207
4522
|
this.discoverySubscription?.unsubscribe();
|
|
4208
4523
|
this.modesSubscription?.unsubscribe();
|
|
4524
|
+
this.toolsSubscription?.unsubscribe();
|
|
4209
4525
|
});
|
|
4210
4526
|
// The application's own modes; without them the editor offers the built-in modes.
|
|
4211
4527
|
this.modesSubscription = this.admin.getMcpAuthModes().subscribe({
|
|
@@ -4231,7 +4547,36 @@ class NhAssistantAdminMcpServerEditorComponent {
|
|
|
4231
4547
|
}
|
|
4232
4548
|
/** The id of a field's error, for `aria-describedby`. */
|
|
4233
4549
|
errorId(field) {
|
|
4234
|
-
return `${this.id}-${field.replace(
|
|
4550
|
+
return `${this.id}-${field.replace(/[^A-Za-z0-9-]+/g, '-')}-error`;
|
|
4551
|
+
}
|
|
4552
|
+
/** The name of a reader's field in the server's errors, such as `readers[0].toolName`. */
|
|
4553
|
+
readerField(index, field) {
|
|
4554
|
+
return `readers[${index}].${field}`;
|
|
4555
|
+
}
|
|
4556
|
+
/** Adds an empty reader at the end, at priority 0, and moves the focus to its tool. */
|
|
4557
|
+
addReader() {
|
|
4558
|
+
if (this.readers().length >= maxReaders) {
|
|
4559
|
+
return;
|
|
4560
|
+
}
|
|
4561
|
+
this.forgetReaderErrors();
|
|
4562
|
+
const key = nextReaderKey++;
|
|
4563
|
+
this.readers.set([...this.readers(), { key, toolName: '', mediaTypes: '', maxClassification: '', priority: '0' }]);
|
|
4564
|
+
this.focusById(`${this.id}-reader-tool-${key}`);
|
|
4565
|
+
}
|
|
4566
|
+
/**
|
|
4567
|
+
* Removes a reader and moves the focus to the button that adds one; the server's errors of the
|
|
4568
|
+
* readers no longer match their positions.
|
|
4569
|
+
*/
|
|
4570
|
+
removeReader(index) {
|
|
4571
|
+
this.forgetReaderErrors();
|
|
4572
|
+
this.readers.set(this.readers().filter((_, position) => position !== index));
|
|
4573
|
+
this.focusById(`${this.id}-reader-add`);
|
|
4574
|
+
}
|
|
4575
|
+
/** Changes one field of a reader as the administrator typed or chose it. */
|
|
4576
|
+
updateReader(index, field, value) {
|
|
4577
|
+
// The classification comes from the select's options, the empty choice included.
|
|
4578
|
+
this.readers.set(this.readers().map((reader, position) => position === index ? { ...reader, [field]: value } : reader));
|
|
4579
|
+
this.touch(this.readerField(index, field));
|
|
4235
4580
|
}
|
|
4236
4581
|
/** The ids a control describes itself with: its hint and, while the server refused it, its error. */
|
|
4237
4582
|
describedBy(hintId, field) {
|
|
@@ -4358,6 +4703,15 @@ class NhAssistantAdminMcpServerEditorComponent {
|
|
|
4358
4703
|
input.connectionScope = authMode === 'oauth' ? this.connectionScope() : 'organization';
|
|
4359
4704
|
input.oauth = this.oauthInput();
|
|
4360
4705
|
}
|
|
4706
|
+
if (!this.isNew()) {
|
|
4707
|
+
// Every save sends the readers as shown; none removes them all.
|
|
4708
|
+
input.readers = this.readers().map(reader => ({
|
|
4709
|
+
mediaTypes: splitMediaTypes(reader.mediaTypes),
|
|
4710
|
+
toolName: reader.toolName,
|
|
4711
|
+
maxClassification: reader.maxClassification || null,
|
|
4712
|
+
priority: parsePriority(reader.priority) ?? 0
|
|
4713
|
+
}));
|
|
4714
|
+
}
|
|
4361
4715
|
const existing = this.server();
|
|
4362
4716
|
const request = existing ? this.admin.updateMcpServer(existing.id, input) : this.admin.createMcpServer(input);
|
|
4363
4717
|
this.saving.set(true);
|
|
@@ -4381,9 +4735,24 @@ class NhAssistantAdminMcpServerEditorComponent {
|
|
|
4381
4735
|
focusFirstInvalid() {
|
|
4382
4736
|
afterNextRender(() => focusFirstInvalid(this.host.nativeElement.querySelector('form')), { injector: this.injector });
|
|
4383
4737
|
}
|
|
4738
|
+
focusById(id) {
|
|
4739
|
+
afterNextRender(() => this.host.nativeElement.querySelector(`[id="${id}"]`)?.focus(), { injector: this.injector });
|
|
4740
|
+
}
|
|
4384
4741
|
valueKeys(value) {
|
|
4385
4742
|
return [`nh-assistant.admin.run-config.field-errors.${value}`, 'nh-assistant.errors.generic'];
|
|
4386
4743
|
}
|
|
4744
|
+
/** Whether the editor shows a field error at a reader's own field. */
|
|
4745
|
+
showsReaderField(field) {
|
|
4746
|
+
const match = /^readers\[(\d+)\]\./.exec(field);
|
|
4747
|
+
return match !== null && readerFieldPattern.test(field) && Number(match[1]) < this.readers().length;
|
|
4748
|
+
}
|
|
4749
|
+
/** The server's errors of the readers no longer apply once a reader is added or removed. */
|
|
4750
|
+
forgetReaderErrors() {
|
|
4751
|
+
const readerFields = Object.keys(this.serverErrors()).filter(field => field.startsWith('readers'));
|
|
4752
|
+
if (readerFields.length > 0) {
|
|
4753
|
+
this.touched.set(new Set([...this.touched(), ...readerFields]));
|
|
4754
|
+
}
|
|
4755
|
+
}
|
|
4387
4756
|
reset(server) {
|
|
4388
4757
|
this.serverId.set(server?.id ?? '');
|
|
4389
4758
|
this.displayName.set(server?.displayName ?? '');
|
|
@@ -4413,10 +4782,30 @@ class NhAssistantAdminMcpServerEditorComponent {
|
|
|
4413
4782
|
this.discovery.set(null);
|
|
4414
4783
|
this.discoveryError.set(null);
|
|
4415
4784
|
this.confirmAuthorization.set(false);
|
|
4785
|
+
this.readers.set((server?.readers ?? []).map(reader => ({
|
|
4786
|
+
key: nextReaderKey++,
|
|
4787
|
+
toolName: reader.toolName,
|
|
4788
|
+
mediaTypes: reader.mediaTypes.join(', '),
|
|
4789
|
+
maxClassification: reader.maxClassification,
|
|
4790
|
+
priority: String(reader.priority)
|
|
4791
|
+
})));
|
|
4792
|
+
this.loadReaderTools(server);
|
|
4416
4793
|
this.submitted.set(false);
|
|
4417
4794
|
this.error.set(null);
|
|
4418
4795
|
this.touched.set(new Set());
|
|
4419
4796
|
}
|
|
4797
|
+
/** The synced tools of an existing server, to choose the reading tool from. */
|
|
4798
|
+
loadReaderTools(server) {
|
|
4799
|
+
this.toolsSubscription?.unsubscribe();
|
|
4800
|
+
this.readerTools.set([]);
|
|
4801
|
+
if (server === null) {
|
|
4802
|
+
return;
|
|
4803
|
+
}
|
|
4804
|
+
this.toolsSubscription = this.admin.getMcpTools(server.id).subscribe({
|
|
4805
|
+
next: tools => this.readerTools.set(tools),
|
|
4806
|
+
error: () => this.readerTools.set([])
|
|
4807
|
+
});
|
|
4808
|
+
}
|
|
4420
4809
|
/**
|
|
4421
4810
|
* The OAuth settings as the editor shows them: every value is sent, so an emptied field clears the
|
|
4422
4811
|
* stored value. The client secret is write-only: sent only when replaced.
|
|
@@ -4444,15 +4833,36 @@ class NhAssistantAdminMcpServerEditorComponent {
|
|
|
4444
4833
|
return input;
|
|
4445
4834
|
}
|
|
4446
4835
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: NhAssistantAdminMcpServerEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
4447
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.34", type: NhAssistantAdminMcpServerEditorComponent, isStandalone: true, selector: "nh-assistant-admin-mcp-server-editor", inputs: { server: { classPropertyName: "server", publicName: "server", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { saved: "saved", cancelled: "cancelled" }, ngImport: i0, template: "<form class=\"section card\" [attr.aria-labelledby]=\"id + '-title'\" [attr.aria-busy]=\"saving()\" autocomplete=\"off\" (submit)=\"$event.preventDefault(); save()\">\n <h3 [id]=\"id + '-title'\">\n @if (server(); as current) {\n {{ 'nh-assistant.admin.server-editor.edit-title' | translate: { name: current.displayName } }}\n } @else {\n {{ 'nh-assistant.admin.server-editor.new-title' | translate }}\n }\n </h3>\n\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-id'\">{{ 'nh-assistant.admin.server-editor.id' | translate }}</label>\n <input\n type=\"text\"\n class=\"mono\"\n [id]=\"id + '-id'\"\n [maxLength]=\"limits.id\"\n [value]=\"serverId()\"\n [disabled]=\"!isNew()\"\n [attr.aria-describedby]=\"describedBy(id + '-id-hint', 'id')\"\n [attr.aria-invalid]=\"(submitted() && idInvalid()) || serverInvalid('id') ? true : null\"\n (input)=\"serverId.set(value($event)); touch('id')\" />\n <p class=\"hint\" [id]=\"id + '-id-hint'\">\n <span [class.error-text]=\"submitted() && idInvalid()\">\n {{ (submitted() && idInvalid() ? 'nh-assistant.admin.invalid-id' : 'nh-assistant.admin.server-editor.id-hint') | translate }}\n </span>\n </p>\n @if (serverInvalid('id')) {\n <p class=\"field-error\" [id]=\"errorId('id')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('id') | nhAssistantTranslate }}</p>\n }\n </div>\n\n <div class=\"field\">\n <label [for]=\"id + '-name'\">{{ 'nh-assistant.admin.server-editor.display-name' | translate }}</label>\n <input\n type=\"text\"\n [id]=\"id + '-name'\"\n [maxLength]=\"limits.displayName\"\n [value]=\"displayName()\"\n [attr.aria-invalid]=\"(submitted() && nameMissing()) || serverInvalid('displayName') ? true : null\"\n [attr.aria-describedby]=\"describedBy(id + '-name-hint', 'displayName')\"\n (input)=\"displayName.set(value($event)); touch('displayName')\" />\n <p class=\"hint\" [id]=\"id + '-name-hint'\">\n @if (submitted() && nameMissing()) {\n <span class=\"error-text\">{{ 'nh-assistant.admin.required' | translate }}</span>\n }\n </p>\n @if (serverInvalid('displayName')) {\n <p class=\"field-error\" [id]=\"errorId('displayName')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('displayName') | nhAssistantTranslate }}</p>\n }\n </div>\n </div>\n\n <div class=\"field\">\n <label [for]=\"id + '-url'\">{{ 'nh-assistant.admin.server-editor.url' | translate }}</label>\n <input\n type=\"url\"\n class=\"mono\"\n inputmode=\"url\"\n [id]=\"id + '-url'\"\n [maxLength]=\"limits.url\"\n [value]=\"url()\"\n [attr.aria-describedby]=\"describedBy(id + '-url-hint', 'url')\"\n [attr.aria-invalid]=\"(submitted() && urlInvalid()) || serverInvalid('url') ? true : null\"\n (input)=\"onUrlInput($event); touch('url')\" />\n <p class=\"hint\" [id]=\"id + '-url-hint'\">\n <span [class.error-text]=\"submitted() && urlInvalid()\">\n {{ (submitted() && urlInvalid() ? 'nh-assistant.admin.server-editor.url-invalid' : 'nh-assistant.admin.server-editor.url-hint') | translate }}\n </span>\n </p>\n @if (serverInvalid('url')) {\n <p class=\"field-error\" [id]=\"errorId('url')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('url') | nhAssistantTranslate }}</p>\n }\n </div>\n\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-auth'\">{{ 'nh-assistant.admin.server-editor.auth-mode' | translate }}</label>\n <select\n [id]=\"id + '-auth'\"\n [attr.aria-describedby]=\"describedBy(null, 'authMode')\"\n [attr.aria-invalid]=\"serverInvalid('authMode') ? true : null\"\n (change)=\"authMode.set($any(value($event))); touch('authMode')\">\n @for (mode of authModes(); track mode) {\n <option [value]=\"mode\" [selected]=\"authMode() === mode\">{{ ['nh-assistant.admin.mcp.auth.' + mode] | nhAssistantTranslate: mode }}</option>\n }\n </select>\n @if (customOwnership(); as ownership) {\n <p class=\"hint\" data-custom-auth-mode>{{ 'nh-assistant.admin.server-editor.custom-auth.' + ownership | translate }}</p>\n }\n @if (serverInvalid('authMode')) {\n <p class=\"field-error\" [id]=\"errorId('authMode')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('authMode') | nhAssistantTranslate }}</p>\n }\n </div>\n\n @if (authMode() === 'api-key') {\n <div class=\"field\">\n <label [for]=\"id + '-header'\">{{ 'nh-assistant.admin.server-editor.header-name' | translate }}</label>\n <input\n type=\"text\"\n class=\"mono\"\n placeholder=\"X-Api-Key\"\n [id]=\"id + '-header'\"\n [maxLength]=\"limits.headerName\"\n [value]=\"headerName()\"\n [attr.aria-describedby]=\"describedBy(id + '-header-hint', 'headerName')\"\n [attr.aria-invalid]=\"(submitted() && headerNameInvalid()) || serverInvalid('headerName') ? true : null\"\n (input)=\"headerName.set(value($event)); touch('headerName')\" />\n <p class=\"hint\" [id]=\"id + '-header-hint'\">\n <span [class.error-text]=\"submitted() && headerNameInvalid()\">\n {{ (submitted() && headerNameInvalid() ? 'nh-assistant.admin.server-editor.header-name-invalid' : 'nh-assistant.admin.server-editor.header-name-hint') | translate }}\n </span>\n </p>\n @if (serverInvalid('headerName')) {\n <p class=\"field-error\" [id]=\"errorId('headerName')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('headerName') | nhAssistantTranslate }}</p>\n }\n </div>\n }\n\n @if (authMode() === 'basic') {\n <div class=\"field\">\n <label [for]=\"id + '-user'\">{{ 'nh-assistant.admin.server-editor.user-name' | translate }}</label>\n <input\n type=\"text\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n [id]=\"id + '-user'\"\n [maxLength]=\"limits.userName\"\n [value]=\"userName()\"\n [attr.aria-describedby]=\"describedBy(id + '-user-hint', 'userName')\"\n [attr.aria-invalid]=\"(submitted() && userNameInvalid()) || serverInvalid('userName') ? true : null\"\n (input)=\"userName.set(value($event)); touch('userName')\" />\n <p class=\"hint\" [id]=\"id + '-user-hint'\">\n <span [class.error-text]=\"submitted() && userNameInvalid()\">\n {{ (submitted() && userNameInvalid() ? 'nh-assistant.admin.server-editor.user-name-invalid' : 'nh-assistant.admin.server-editor.user-name-hint') | translate }}\n </span>\n </p>\n @if (serverInvalid('userName')) {\n <p class=\"field-error\" [id]=\"errorId('userName')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('userName') | nhAssistantTranslate }}</p>\n }\n </div>\n }\n </div>\n\n @if (authMode() === 'forward-user-token') {\n <p class=\"message warning\" role=\"note\"><nh-assistant-icon name=\"warning\" />{{ 'nh-assistant.admin.server-editor.forward-warning' | translate }}</p>\n }\n\n @if (isOAuth()) {\n <fieldset class=\"field\">\n <legend>OAuth</legend>\n\n @if (authMode() === 'oauth') {\n <div\n class=\"inline\"\n role=\"radiogroup\"\n [attr.aria-label]=\"'nh-assistant.admin.server-editor.connection-scope' | translate\"\n [attr.aria-describedby]=\"describedBy(id + '-scope-hint', 'connectionScope')\">\n @for (scope of connectionScopes; track scope) {\n <label class=\"check\">\n <input\n type=\"radio\"\n [name]=\"id + '-connection-scope'\"\n [value]=\"scope\"\n [checked]=\"connectionScope() === scope\"\n [attr.aria-invalid]=\"serverInvalid('connectionScope') ? true : null\"\n (change)=\"connectionScope.set(scope); touch('connectionScope')\" />\n <span>{{ 'nh-assistant.admin.mcp.connection-scope.' + scope | translate }}</span>\n </label>\n }\n </div>\n <p class=\"hint\" [id]=\"id + '-scope-hint'\">{{ 'nh-assistant.admin.server-editor.connection-scope-hint' | translate }}</p>\n @if (serverInvalid('connectionScope')) {\n <p class=\"field-error\" [id]=\"errorId('connectionScope')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('connectionScope') | nhAssistantTranslate }}</p>\n }\n }\n\n @if (isOrganizationOAuth()) {\n <p class=\"message warning\" role=\"note\"><nh-assistant-icon name=\"warning\" />{{ 'nh-assistant.admin.server-editor.oauth-warning' | translate }}</p>\n <label [for]=\"id + '-account-label'\">{{ 'nh-assistant.admin.server-editor.organization-account-label' | translate }}</label>\n <input\n type=\"text\"\n [id]=\"id + '-account-label'\"\n [maxLength]=\"limits.organizationAccountLabel\"\n [value]=\"organizationAccountLabel()\"\n [attr.aria-describedby]=\"describedBy(id + '-account-label-hint', 'oauth.organizationAccountLabel')\"\n [attr.aria-invalid]=\"(submitted() && labelMissing()) || serverInvalid('oauth.organizationAccountLabel') ? true : null\"\n (input)=\"organizationAccountLabel.set(value($event)); touch('oauth.organizationAccountLabel')\" />\n <p class=\"hint\" [id]=\"id + '-account-label-hint'\">\n <span [class.error-text]=\"submitted() && labelMissing()\">\n {{ (submitted() && labelMissing() ? 'nh-assistant.admin.server-editor.organization-account-label-required' : 'nh-assistant.admin.server-editor.organization-account-label-hint') | translate }}\n </span>\n </p>\n @if (serverInvalid('oauth.organizationAccountLabel')) {\n <p class=\"field-error\" [id]=\"errorId('oauth.organizationAccountLabel')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('oauth.organizationAccountLabel') | nhAssistantTranslate }}</p>\n }\n }\n\n <div class=\"meta\">\n @if (server()?.authorization; as confirmed) {\n @if (!needsConfirmation()) {\n <span class=\"badge success\">{{ 'nh-assistant.admin.server-editor.confirmed-authorization' | translate: { issuer: confirmed.issuer } }}</span>\n }\n }\n <button class=\"secondary\" type=\"button\" [disabled]=\"discovering() || urlInvalid()\" (click)=\"discover()\">\n <nh-assistant-icon name=\"link\" />{{ (discovering() ? 'nh-assistant.admin.server-editor.discovering' : 'nh-assistant.admin.server-editor.discover') | translate }}\n </button>\n </div>\n @if (discoveryError(); as failure) {\n <p class=\"message error\" role=\"alert\"><nh-assistant-icon name=\"warning\" />{{ discoveryErrorKeys() | nhAssistantTranslate: failure.code }}</p>\n }\n @if (discovery(); as found) {\n <div class=\"discovery\" role=\"group\" [attr.aria-label]=\"'nh-assistant.admin.server-editor.discovery-title' | translate\">\n <dl>\n <dt>{{ 'nh-assistant.admin.server-editor.discovery-issuer' | translate }}</dt>\n <dd class=\"mono\">{{ found.issuer }}</dd>\n <dt>{{ 'nh-assistant.admin.server-editor.discovery-resource' | translate }}</dt>\n <dd class=\"mono\">{{ found.resource }}</dd>\n @if (found.scopesSupported.length > 0) {\n <dt>{{ 'nh-assistant.admin.server-editor.discovery-scopes' | translate }}</dt>\n <dd class=\"mono\">{{ found.scopesSupported.join(' ') }}</dd>\n }\n @if (authMode() === 'oauth' && registration(); as kind) {\n <dt>{{ 'nh-assistant.admin.server-editor.discovery-registration' | translate }}</dt>\n <dd>{{ 'nh-assistant.admin.server-editor.registration-' + kind | translate }}</dd>\n }\n </dl>\n @if (pkceMissing()) {\n <p class=\"message error\" role=\"alert\"><nh-assistant-icon name=\"warning\" />{{ 'nh-assistant.admin.server-editor.pkce-missing' | translate }}</p>\n } @else {\n <label class=\"check\">\n <input\n type=\"checkbox\"\n [checked]=\"confirmAuthorization()\"\n [attr.aria-describedby]=\"describedBy(id + '-confirm-hint', 'oauth.confirmAuthorization')\"\n [attr.aria-invalid]=\"serverInvalid('oauth.confirmAuthorization') ? true : null\"\n (change)=\"confirmAuthorization.set(checked($event)); touch('oauth.confirmAuthorization')\" />\n <span>{{ 'nh-assistant.admin.server-editor.confirm-authorization' | translate }}</span>\n </label>\n <p class=\"hint\" [id]=\"id + '-confirm-hint'\">{{ 'nh-assistant.admin.server-editor.confirm-authorization-hint' | translate }}</p>\n }\n </div>\n }\n @if (submitted() && confirmMissing()) {\n <p class=\"hint\"><span class=\"error-text\">{{ 'nh-assistant.admin.server-editor.confirm-required' | translate }}</span></p>\n } @else if (serverInvalid('oauth.confirmAuthorization')) {\n <p class=\"field-error\" [id]=\"errorId('oauth.confirmAuthorization')\"><nh-assistant-icon name=\"warning\" />{{ 'nh-assistant.admin.server-editor.confirm-required' | translate }}</p>\n }\n\n <label [for]=\"id + '-scopes'\">{{ 'nh-assistant.admin.server-editor.scopes' | translate }}</label>\n <input\n type=\"text\"\n class=\"mono\"\n spellcheck=\"false\"\n [id]=\"id + '-scopes'\"\n [maxLength]=\"limits.scopes\"\n [value]=\"oauthScopes()\"\n [attr.aria-describedby]=\"describedBy(id + '-scopes-hint', 'oauth.scopes')\"\n [attr.aria-invalid]=\"(submitted() && scopesInvalid()) || serverInvalid('oauth.scopes') ? true : null\"\n (input)=\"oauthScopes.set(value($event)); touch('oauth.scopes')\" />\n <p class=\"hint\" [id]=\"id + '-scopes-hint'\">\n <span [class.error-text]=\"submitted() && scopesInvalid()\">\n {{ (submitted() && scopesInvalid() ? 'nh-assistant.admin.server-editor.scopes-invalid' : 'nh-assistant.admin.server-editor.scopes-hint') | translate }}\n </span>\n </p>\n @if (serverInvalid('oauth.scopes')) {\n <p class=\"field-error\" [id]=\"errorId('oauth.scopes')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('oauth.scopes') | nhAssistantTranslate }}</p>\n }\n\n <label [for]=\"id + '-client-id'\">{{ 'nh-assistant.admin.server-editor.client-id' | translate }}</label>\n <input\n type=\"text\"\n class=\"mono\"\n spellcheck=\"false\"\n [id]=\"id + '-client-id'\"\n [maxLength]=\"limits.clientId\"\n [value]=\"oauthClientId()\"\n [attr.aria-describedby]=\"describedBy(id + '-client-id-hint', 'oauth.clientId')\"\n [attr.aria-invalid]=\"(submitted() && clientIdMissing()) || serverInvalid('oauth.clientId') ? true : null\"\n (input)=\"oauthClientId.set(value($event)); touch('oauth.clientId')\" />\n <p class=\"hint\" [id]=\"id + '-client-id-hint'\">\n <span [class.error-text]=\"submitted() && clientIdMissing()\">\n {{ (submitted() && clientIdMissing() ? 'nh-assistant.admin.server-editor.client-id-required' : 'nh-assistant.admin.server-editor.client-id-hint') | translate }}\n </span>\n </p>\n @if (serverInvalid('oauth.clientId')) {\n <p class=\"field-error\" [id]=\"errorId('oauth.clientId')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('oauth.clientId') | nhAssistantTranslate }}</p>\n }\n\n @if (oauthClientId().trim().length > 0) {\n <div class=\"inline\" role=\"radiogroup\" [attr.aria-label]=\"'nh-assistant.admin.server-editor.client-secret' | translate\">\n @for (source of secretSources; track source) {\n <label class=\"check\">\n <input\n type=\"radio\"\n [name]=\"id + '-oauth-secret-source'\"\n [value]=\"source\"\n [checked]=\"oauthSecretSource() === source\"\n (change)=\"setOAuthSecretSource(source)\" />\n <span>{{ 'nh-assistant.admin.server-editor.secret-source-' + source | translate }}</span>\n </label>\n }\n </div>\n @if (usesOAuthReference()) {\n <label [for]=\"id + '-oauth-reference'\">{{ 'nh-assistant.admin.server-editor.client-secret-reference' | translate }}</label>\n <input\n type=\"text\"\n class=\"mono\"\n spellcheck=\"false\"\n placeholder=\"config:planning-client-secret\"\n [id]=\"id + '-oauth-reference'\"\n [maxLength]=\"limits.secretReference\"\n [value]=\"oauthSecretReference()\"\n [attr.aria-describedby]=\"describedBy(null, 'oauth.clientSecretReference')\"\n [attr.aria-invalid]=\"(submitted() && oauthReferenceInvalid()) || serverInvalid('oauth.clientSecretReference') ? true : null\"\n (input)=\"oauthSecretReference.set(value($event)); touch('oauth.clientSecretReference')\" />\n @if (submitted() && oauthReferenceInvalid()) {\n <p class=\"hint\"><span class=\"error-text\">{{ 'nh-assistant.admin.server-editor.secret-reference-invalid' | translate }}</span></p>\n }\n @if (serverInvalid('oauth.clientSecretReference')) {\n <p class=\"field-error\" [id]=\"errorId('oauth.clientSecretReference')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('oauth.clientSecretReference') | nhAssistantTranslate }}</p>\n }\n } @else {\n @if (hasOAuthSecret() && oauthSecretAction() === 'keep') {\n <div class=\"meta secret-state\">\n <span class=\"badge success\">{{ 'nh-assistant.admin.server-editor.secret-set' | translate }}</span>\n <button class=\"secondary\" type=\"button\" (click)=\"oauthSecretAction.set('replace')\">{{ 'nh-assistant.admin.server-editor.secret-replace' | translate }}</button>\n </div>\n }\n @if (showOAuthSecretInput()) {\n <label [for]=\"id + '-oauth-secret'\">{{ 'nh-assistant.admin.server-editor.client-secret' | translate }}</label>\n <input\n type=\"password\"\n class=\"mono\"\n autocomplete=\"new-password\"\n spellcheck=\"false\"\n [id]=\"id + '-oauth-secret'\"\n [maxLength]=\"limits.clientSecret\"\n [attr.aria-describedby]=\"describedBy(id + '-oauth-secret-hint', 'oauth.clientSecret')\"\n [attr.aria-invalid]=\"serverInvalid('oauth.clientSecret') ? true : null\"\n [value]=\"oauthSecret()\"\n (input)=\"onOAuthSecretInput($event); touch('oauth.clientSecret')\" />\n }\n <p class=\"hint\" [id]=\"id + '-oauth-secret-hint'\">{{ 'nh-assistant.admin.server-editor.client-secret-hint' | translate }}</p>\n }\n }\n @if (serverInvalid('oauth.clientSecret')) {\n <p class=\"field-error\" [id]=\"errorId('oauth.clientSecret')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('oauth.clientSecret') | nhAssistantTranslate }}</p>\n }\n </fieldset>\n }\n\n @if (usesSecret()) {\n <fieldset class=\"field\">\n <legend>{{ 'nh-assistant.admin.server-editor.secret' | translate }}</legend>\n <div class=\"inline\" role=\"radiogroup\" [attr.aria-label]=\"'nh-assistant.admin.server-editor.secret-source' | translate\">\n @for (source of secretSources; track source) {\n <label class=\"check\">\n <input\n type=\"radio\"\n [name]=\"id + '-secret-source'\"\n [value]=\"source\"\n [checked]=\"secretSource() === source\"\n (change)=\"setSecretSource(source)\" />\n <span>{{ 'nh-assistant.admin.server-editor.secret-source-' + source | translate }}</span>\n </label>\n }\n </div>\n @if (usesReference()) {\n <label [for]=\"id + '-secret-reference'\">{{ 'nh-assistant.admin.server-editor.secret-reference' | translate }}</label>\n <input\n type=\"text\"\n class=\"mono\"\n spellcheck=\"false\"\n placeholder=\"config:planning-token\"\n [id]=\"id + '-secret-reference'\"\n [maxLength]=\"limits.secretReference\"\n [value]=\"secretReference()\"\n [attr.aria-describedby]=\"describedBy(id + '-secret-reference-hint', 'secretReference')\"\n [attr.aria-invalid]=\"(submitted() && secretReferenceInvalid()) || serverInvalid('secretReference') ? true : null\"\n (input)=\"secretReference.set(value($event)); touch('secretReference')\" />\n <p class=\"hint\" [id]=\"id + '-secret-reference-hint'\">\n <span [class.error-text]=\"submitted() && secretReferenceInvalid()\">\n {{ (submitted() && secretReferenceInvalid() ? 'nh-assistant.admin.server-editor.secret-reference-invalid' : 'nh-assistant.admin.server-editor.secret-reference-hint') | translate }}\n </span>\n </p>\n @if (serverInvalid('secretReference')) {\n <p class=\"field-error\" [id]=\"errorId('secretReference')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('secretReference') | nhAssistantTranslate }}</p>\n }\n } @else if (hasSecret()) {\n <div class=\"meta secret-state\">\n @switch (secretAction()) {\n @case ('clear') {\n <span class=\"badge warning\">{{ 'nh-assistant.admin.server-editor.secret-will-clear' | translate }}</span>\n <button class=\"secondary\" type=\"button\" (click)=\"setSecretAction('keep')\">{{ 'nh-assistant.admin.server-editor.secret-undo' | translate }}</button>\n }\n @case ('replace') {\n <span class=\"badge\">{{ 'nh-assistant.admin.server-editor.secret-set' | translate }}</span>\n <button class=\"secondary\" type=\"button\" (click)=\"setSecretAction('keep')\">{{ 'nh-assistant.admin.server-editor.secret-undo' | translate }}</button>\n }\n @default {\n <span class=\"badge success\">{{ 'nh-assistant.admin.server-editor.secret-set' | translate }}</span>\n <!-- A mode with a secret always needs one: it is replaced, or removed by choosing a reference or another mode. -->\n <button class=\"secondary\" type=\"button\" (click)=\"setSecretAction('replace')\">{{ 'nh-assistant.admin.server-editor.secret-replace' | translate }}</button>\n }\n }\n </div>\n } @else {\n <p class=\"muted\">{{ 'nh-assistant.admin.server-editor.secret-none' | translate }}</p>\n }\n @if (showSecretInput()) {\n <label class=\"visually-hidden\" [for]=\"id + '-secret'\">{{ 'nh-assistant.admin.server-editor.secret-new' | translate }}</label>\n <input\n type=\"password\"\n class=\"mono\"\n autocomplete=\"new-password\"\n spellcheck=\"false\"\n [id]=\"id + '-secret'\"\n [maxLength]=\"limits.secret\"\n [attr.placeholder]=\"'nh-assistant.admin.server-editor.secret-new' | translate\"\n [attr.aria-describedby]=\"describedBy(id + '-secret-hint', 'secret')\"\n [attr.aria-invalid]=\"serverInvalid('secret') ? true : null\"\n [value]=\"secret()\"\n (input)=\"onSecretInput($event); touch('secret')\" />\n }\n @if (!usesReference()) {\n <p class=\"hint\" [id]=\"id + '-secret-hint'\">{{ 'nh-assistant.admin.server-editor.secret-hint' | translate }}</p>\n }\n @if (serverInvalid('secret')) {\n <p class=\"field-error\" [id]=\"errorId('secret')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('secret') | nhAssistantTranslate }}</p>\n }\n </fieldset>\n }\n\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-certificate'\">{{ 'nh-assistant.admin.server-editor.client-certificate' | translate }}</label>\n <input\n type=\"text\"\n class=\"mono\"\n spellcheck=\"false\"\n [id]=\"id + '-certificate'\"\n [maxLength]=\"limits.clientCertificateReference\"\n [value]=\"clientCertificateReference()\"\n [attr.aria-describedby]=\"describedBy(id + '-certificate-hint', 'clientCertificateReference')\"\n [attr.aria-invalid]=\"(submitted() && certificateInvalid()) || serverInvalid('clientCertificateReference') ? true : null\"\n (input)=\"clientCertificateReference.set(value($event)); touch('clientCertificateReference')\" />\n <p class=\"hint\" [id]=\"id + '-certificate-hint'\">\n <span [class.error-text]=\"submitted() && certificateInvalid()\">\n {{ (submitted() && certificateInvalid() ? 'nh-assistant.admin.server-editor.client-certificate-invalid' : 'nh-assistant.admin.server-editor.client-certificate-hint') | translate }}\n </span>\n </p>\n @if (serverInvalid('clientCertificateReference')) {\n <p class=\"field-error\" [id]=\"errorId('clientCertificateReference')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('clientCertificateReference') | nhAssistantTranslate }}</p>\n }\n </div>\n\n <div class=\"field\">\n <label [for]=\"id + '-transport'\">{{ 'nh-assistant.admin.server-editor.transport' | translate }}</label>\n <select\n [id]=\"id + '-transport'\"\n [attr.aria-describedby]=\"describedBy(id + '-transport-hint', 'transport')\"\n [attr.aria-invalid]=\"serverInvalid('transport') ? true : null\"\n (change)=\"transport.set($any(value($event))); touch('transport')\">\n @for (option of transports; track option) {\n <option [value]=\"option\" [selected]=\"transport() === option\">{{ 'nh-assistant.admin.mcp.transport.' + option | translate }}</option>\n }\n </select>\n <p class=\"hint\" [id]=\"id + '-transport-hint'\">{{ 'nh-assistant.admin.server-editor.transport-hint' | translate }}</p>\n @if (serverInvalid('transport')) {\n <p class=\"field-error\" [id]=\"errorId('transport')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('transport') | nhAssistantTranslate }}</p>\n }\n </div>\n </div>\n\n <div class=\"field\">\n <label [for]=\"id + '-policy'\">{{ 'nh-assistant.admin.server-editor.required-policy' | translate }}</label>\n <input\n type=\"text\"\n class=\"mono\"\n [id]=\"id + '-policy'\"\n [maxLength]=\"limits.requiredPolicy\"\n [value]=\"requiredPolicy()\"\n [attr.aria-describedby]=\"describedBy(id + '-policy-hint', 'requiredPolicy')\"\n [attr.aria-invalid]=\"serverInvalid('requiredPolicy') ? true : null\"\n (input)=\"requiredPolicy.set(value($event)); touch('requiredPolicy')\" />\n <p class=\"hint\" [id]=\"id + '-policy-hint'\">{{ 'nh-assistant.admin.server-editor.required-policy-hint' | translate }}</p>\n @if (serverInvalid('requiredPolicy')) {\n <p class=\"field-error\" [id]=\"errorId('requiredPolicy')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('requiredPolicy') | nhAssistantTranslate }}</p>\n }\n </div>\n\n <div class=\"field\">\n <label [for]=\"id + '-headless'\">{{ 'nh-assistant.admin.server-editor.headless-use' | translate }}</label>\n <select\n [id]=\"id + '-headless'\"\n [attr.aria-describedby]=\"describedBy(id + '-headless-hint', 'headlessUse')\"\n [attr.aria-invalid]=\"serverInvalid('headlessUse') ? true : null\"\n (change)=\"headlessUse.set($any(value($event))); touch('headlessUse')\">\n @for (use of headlessUses(); track use) {\n <option [value]=\"use\" [selected]=\"effectiveHeadlessUse() === use\">{{ 'nh-assistant.admin.mcp.headless.' + use | translate }}</option>\n }\n </select>\n <p class=\"hint\" [id]=\"id + '-headless-hint'\">\n {{ (personalCredential() ? 'nh-assistant.admin.server-editor.headless-use-personal' : 'nh-assistant.admin.server-editor.headless-use-hint') | translate }}\n </p>\n @if (serverInvalid('headlessUse')) {\n <p class=\"field-error\" [id]=\"errorId('headlessUse')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('headlessUse') | nhAssistantTranslate }}</p>\n }\n </div>\n\n <label class=\"check\">\n <input type=\"checkbox\" [checked]=\"isEnabled()\" (change)=\"isEnabled.set(checked($event))\" />\n <span>{{ 'nh-assistant.admin.server-editor.enabled' | translate }}</span>\n </label>\n\n @if (error(); as failure) {\n <div class=\"form-error\" role=\"alert\" data-server-error>\n <nh-assistant-icon name=\"warning\" />\n <div>\n <span>{{ errorKeys() | nhAssistantTranslate: failure.code }}</span>\n @if (otherServerErrors().length > 0) {\n <ul>\n @for (other of otherServerErrors(); track other.field) {\n <li><code class=\"mono\">{{ other.field }}</code>: {{ other.keys | nhAssistantTranslate }}</li>\n }\n </ul>\n }\n </div>\n </div>\n }\n\n <div class=\"actions\">\n <button class=\"secondary\" type=\"button\" (click)=\"cancelled.emit()\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n <button class=\"primary\" type=\"submit\" [disabled]=\"saving()\">\n {{ (saving() ? 'nh-assistant.admin.saving' : 'nh-assistant.admin.save') | translate }}\n </button>\n </div>\n</form>\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;min-width:0;color:var(--nha-text);font-family:var(--nha-font);font-size:14px}:host fieldset{min-width:0;margin:0;border:0;padding:0}:host legend,:host .label,:host .field>label{display:block;margin-bottom:6px;padding:0;color:var(--nha-text);font-size:12px;font-weight:700}:host input[type=text],:host input[type=url],:host input[type=password],:host input[type=search],:host input[type=email],:host input[type=number],:host input[type=date],:host input[type=time],:host input[type=datetime-local],:host select,:host textarea{box-sizing:border-box;width:100%;min-width:0;min-height:38px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:8px 10px;color:var(--nha-text);background:var(--nha-background);font:inherit;font-size:13px}:host input[type=text]:focus-visible,:host input[type=url]:focus-visible,:host input[type=password]:focus-visible,:host input[type=search]:focus-visible,:host input[type=email]:focus-visible,:host input[type=number]:focus-visible,:host input[type=date]:focus-visible,:host input[type=time]:focus-visible,:host input[type=datetime-local]:focus-visible,:host select:focus-visible,:host textarea:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host input[type=text]:disabled,:host input[type=url]:disabled,:host input[type=password]:disabled,:host input[type=search]:disabled,:host input[type=email]:disabled,:host input[type=number]:disabled,:host input[type=date]:disabled,:host input[type=time]:disabled,:host input[type=datetime-local]:disabled,:host select:disabled,:host textarea:disabled{cursor:not-allowed;opacity:.6}:host input[type=text][aria-invalid=true],:host input[type=url][aria-invalid=true],:host input[type=password][aria-invalid=true],:host input[type=search][aria-invalid=true],:host input[type=email][aria-invalid=true],:host input[type=number][aria-invalid=true],:host input[type=date][aria-invalid=true],:host input[type=time][aria-invalid=true],:host input[type=datetime-local][aria-invalid=true],:host select[aria-invalid=true],:host textarea[aria-invalid=true]{border-color:var(--nha-danger)}:host textarea{line-height:1.45;resize:vertical}:host input[type=number]{font-variant-numeric:tabular-nums}:host .mono{font-family:var(--nha-mono);font-size:12px}:host .field{display:flex;flex-direction:column;min-width:0}:host .hint{display:flex;justify-content:space-between;gap:8px;margin:4px 2px 0;color:var(--nha-text-muted);font-size:11px}:host .counter{flex:none;font-variant-numeric:tabular-nums}:host .error-text{color:var(--nha-danger)}:host .field-error{display:flex;align-items:flex-start;gap:6px;margin:4px 2px 0;color:var(--nha-danger);font-size:12px;font-weight:600;overflow-wrap:anywhere}:host .field-error nh-assistant-icon{flex:none;margin-top:1px}:host .form-error{box-sizing:border-box;display:flex;align-items:flex-start;gap:8px;margin:0;border:1px solid var(--nha-danger);border-radius:var(--nha-radius-control);padding:8px 12px;color:var(--nha-text);background:var(--nha-danger-soft);font-size:13px;overflow-wrap:anywhere}:host .form-error ul{margin:4px 0 0;padding-left:18px}:host .message{box-sizing:border-box;display:flex;align-items:center;gap:8px;margin:0;border:1px solid transparent;border-radius:var(--nha-radius-control);padding:8px 12px;font-size:13px;overflow-wrap:anywhere}:host .message.error{border-color:var(--nha-danger);color:var(--nha-text);background:var(--nha-danger-soft)}:host .message.warning{border-color:var(--nha-warning);color:var(--nha-text);background:var(--nha-warning-soft)}:host .message.success{border-color:var(--nha-success);color:var(--nha-text);background:var(--nha-success-soft)}:host .message.info{border-color:var(--nha-border);color:var(--nha-text);background:var(--nha-surface)}:host .message nh-assistant-icon{flex:none}:host button.link{min-height:0;border:0;padding:0;color:var(--nha-accent, currentColor);background:none;font:inherit;text-decoration:underline;cursor:pointer}:host button.link:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host button.link:disabled{cursor:not-allowed;opacity:.55}:host .primary,:host .secondary,:host .danger{display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;gap:6px;min-height:36px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px 14px;font:inherit;font-size:13px;font-weight:700;white-space:nowrap;cursor:pointer}:host .primary:focus-visible,:host .secondary:focus-visible,:host .danger:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .primary:disabled,:host .secondary:disabled,:host .danger:disabled{cursor:not-allowed;opacity:.55}:host .primary{border-color:var(--nha-accent);color:var(--nha-accent-contrast);background:var(--nha-accent)}:host .primary:hover:not(:disabled){filter:brightness(1.12)}:host .secondary{color:var(--nha-text);background:transparent}:host .secondary:hover:not(:disabled){background:var(--nha-surface-strong)}:host .danger{border-color:var(--nha-danger);color:var(--nha-danger);background:transparent}:host .danger:hover:not(:disabled){background:var(--nha-danger-soft)}:host .icon-button{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:1px solid transparent;border-radius:var(--nha-radius-control);color:var(--nha-text-muted);background:transparent;cursor:pointer}:host .icon-button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .icon-button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}:host .icon-button:disabled{cursor:not-allowed;opacity:.5}:host .actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px}:host .badge{display:inline-flex;align-items:center;gap:4px;border-radius:999px;padding:2px 8px;color:var(--nha-text-muted);background:var(--nha-surface-strong);font-size:11px;font-weight:700;white-space:nowrap}:host .badge.success{color:var(--nha-success);background:var(--nha-success-soft)}:host .badge.warning{color:var(--nha-warning);background:var(--nha-warning-soft)}:host .badge.danger{color:var(--nha-danger);background:var(--nha-danger-soft)}:host .visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}.section{display:flex;flex-direction:column;gap:16px;min-width:0}.section-header{display:flex;flex-wrap:wrap;align-items:flex-start;justify-content:space-between;gap:12px}.section-header>div{min-width:0;max-width:72ch}h3{margin:0 0 4px;font-size:17px;font-weight:750}h4{margin:0 0 8px;font-size:14px;font-weight:700}.muted{margin:0;color:var(--nha-text-muted);font-size:13px}.state{display:flex;flex-direction:column;align-items:flex-start;gap:10px;margin:0;border:1px dashed var(--nha-border);border-radius:var(--nha-radius);padding:20px;color:var(--nha-text-muted);font-size:13px}.state p{margin:0}.stack{display:flex;flex-direction:column;gap:14px;min-width:0}.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,240px),1fr));gap:14px}.meta{display:flex;flex-wrap:wrap;align-items:center;gap:8px 12px;color:var(--nha-text-muted);font-size:12px;overflow-wrap:anywhere}textarea.large{min-height:260px}.catalog-error{flex-wrap:wrap}.grow{flex:1;min-width:0}.subsection{display:flex;flex-direction:column;gap:8px;border-top:1px solid var(--nha-border);padding-top:14px}.history{display:grid;gap:6px;margin:0;padding:0;list-style:none}.history li{display:flex;flex-wrap:wrap;align-items:center;gap:6px 12px;font-size:12px}.cards{display:grid;gap:10px;margin:0;padding:0;list-style:none}.card{display:flex;flex-direction:column;gap:8px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius);padding:14px;background:var(--nha-background)}.card.inactive{opacity:.8}.card-header{display:flex;flex-wrap:wrap;align-items:center;gap:8px;min-width:0}.card-header strong{font-size:14px;overflow-wrap:anywhere}.card-actions{display:flex;flex-wrap:wrap;gap:8px}.check{display:flex;align-items:flex-start;gap:8px;font-size:13px;cursor:pointer}.check input{width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.check.described>span{display:grid;gap:2px;min-width:0;overflow-wrap:anywhere}.checks{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));gap:6px 12px;min-width:0;overflow-wrap:anywhere}.chips{display:flex;flex-wrap:wrap;gap:6px;margin:0;padding:0;list-style:none}.chip{display:inline-flex;align-items:center;gap:2px;border:1px solid var(--nha-border);border-radius:999px;padding:2px 2px 2px 10px;background:var(--nha-surface);font-family:var(--nha-mono);font-size:12px}.chip 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;width:26px;height:26px;border-radius:999px}.chip button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.chip button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}.chip button:disabled{cursor:not-allowed;opacity:.5}.inline{display:flex;flex-wrap:wrap;gap:8px}.inline>input{flex:1 1 180px}.catalog{display:grid;gap:4px;max-height:260px;margin:8px 0 0;overflow-y:auto;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px;list-style:none}.catalog li label{display:flex;flex-wrap:wrap;align-items:center;gap:6px 8px;border-radius:8px;padding:6px;cursor:pointer}.catalog li label:hover{background:var(--nha-surface)}.catalog .tool-id{font-family:var(--nha-mono);font-size:12px;overflow-wrap:anywhere}.skills{display:grid;gap:8px;margin:0;padding:0;list-style:none}.skill{display:grid;gap:6px;border-left:3px solid var(--nha-border);padding:4px 0 4px 12px}.uses{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));gap:8px}.use{display:flex;align-items:flex-start;gap:10px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:10px 12px;cursor:pointer;overflow-wrap:anywhere}.use input{flex:none;width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.use>span{display:grid;gap:2px;min-width:0}.use strong{font-size:13px}.use.selected{border-color:var(--nha-highlight);background:var(--nha-surface)}.use:focus-within{outline:2px solid var(--nha-highlight);outline-offset:2px}.discovery{display:grid;gap:8px;padding:10px 12px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control)}.discovery dl{display:grid;grid-template-columns:max-content minmax(0,1fr);gap:4px 12px;margin:0}.discovery dt{color:var(--nha-text-muted)}.discovery dd{margin:0;overflow-wrap:anywhere}\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 }); }
|
|
4836
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.34", type: NhAssistantAdminMcpServerEditorComponent, isStandalone: true, selector: "nh-assistant-admin-mcp-server-editor", inputs: { server: { classPropertyName: "server", publicName: "server", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { saved: "saved", cancelled: "cancelled" }, ngImport: i0, template: "<form class=\"section card\" [attr.aria-labelledby]=\"id + '-title'\" [attr.aria-busy]=\"saving()\" autocomplete=\"off\" (submit)=\"$event.preventDefault(); save()\">\n <h3 [id]=\"id + '-title'\">\n @if (server(); as current) {\n {{ 'nh-assistant.admin.server-editor.edit-title' | translate: { name: current.displayName } }}\n } @else {\n {{ 'nh-assistant.admin.server-editor.new-title' | translate }}\n }\n </h3>\n\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-id'\">{{ 'nh-assistant.admin.server-editor.id' | translate }}</label>\n <input\n type=\"text\"\n class=\"mono\"\n [id]=\"id + '-id'\"\n [maxLength]=\"limits.id\"\n [value]=\"serverId()\"\n [disabled]=\"!isNew()\"\n [attr.aria-describedby]=\"describedBy(id + '-id-hint', 'id')\"\n [attr.aria-invalid]=\"(submitted() && idInvalid()) || serverInvalid('id') ? true : null\"\n (input)=\"serverId.set(value($event)); touch('id')\" />\n <p class=\"hint\" [id]=\"id + '-id-hint'\">\n <span [class.error-text]=\"submitted() && idInvalid()\">\n {{ (submitted() && idInvalid() ? 'nh-assistant.admin.invalid-id' : 'nh-assistant.admin.server-editor.id-hint') | translate }}\n </span>\n </p>\n @if (serverInvalid('id')) {\n <p class=\"field-error\" [id]=\"errorId('id')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('id') | nhAssistantTranslate }}</p>\n }\n </div>\n\n <div class=\"field\">\n <label [for]=\"id + '-name'\">{{ 'nh-assistant.admin.server-editor.display-name' | translate }}</label>\n <input\n type=\"text\"\n [id]=\"id + '-name'\"\n [maxLength]=\"limits.displayName\"\n [value]=\"displayName()\"\n [attr.aria-invalid]=\"(submitted() && nameMissing()) || serverInvalid('displayName') ? true : null\"\n [attr.aria-describedby]=\"describedBy(id + '-name-hint', 'displayName')\"\n (input)=\"displayName.set(value($event)); touch('displayName')\" />\n <p class=\"hint\" [id]=\"id + '-name-hint'\">\n @if (submitted() && nameMissing()) {\n <span class=\"error-text\">{{ 'nh-assistant.admin.required' | translate }}</span>\n }\n </p>\n @if (serverInvalid('displayName')) {\n <p class=\"field-error\" [id]=\"errorId('displayName')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('displayName') | nhAssistantTranslate }}</p>\n }\n </div>\n </div>\n\n <div class=\"field\">\n <label [for]=\"id + '-url'\">{{ 'nh-assistant.admin.server-editor.url' | translate }}</label>\n <input\n type=\"url\"\n class=\"mono\"\n inputmode=\"url\"\n [id]=\"id + '-url'\"\n [maxLength]=\"limits.url\"\n [value]=\"url()\"\n [attr.aria-describedby]=\"describedBy(id + '-url-hint', 'url')\"\n [attr.aria-invalid]=\"(submitted() && urlInvalid()) || serverInvalid('url') ? true : null\"\n (input)=\"onUrlInput($event); touch('url')\" />\n <p class=\"hint\" [id]=\"id + '-url-hint'\">\n <span [class.error-text]=\"submitted() && urlInvalid()\">\n {{ (submitted() && urlInvalid() ? 'nh-assistant.admin.server-editor.url-invalid' : 'nh-assistant.admin.server-editor.url-hint') | translate }}\n </span>\n </p>\n @if (serverInvalid('url')) {\n <p class=\"field-error\" [id]=\"errorId('url')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('url') | nhAssistantTranslate }}</p>\n }\n </div>\n\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-auth'\">{{ 'nh-assistant.admin.server-editor.auth-mode' | translate }}</label>\n <select\n [id]=\"id + '-auth'\"\n [attr.aria-describedby]=\"describedBy(null, 'authMode')\"\n [attr.aria-invalid]=\"serverInvalid('authMode') ? true : null\"\n (change)=\"authMode.set($any(value($event))); touch('authMode')\">\n @for (mode of authModes(); track mode) {\n <option [value]=\"mode\" [selected]=\"authMode() === mode\">{{ ['nh-assistant.admin.mcp.auth.' + mode] | nhAssistantTranslate: mode }}</option>\n }\n </select>\n @if (customOwnership(); as ownership) {\n <p class=\"hint\" data-custom-auth-mode>{{ 'nh-assistant.admin.server-editor.custom-auth.' + ownership | translate }}</p>\n }\n @if (serverInvalid('authMode')) {\n <p class=\"field-error\" [id]=\"errorId('authMode')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('authMode') | nhAssistantTranslate }}</p>\n }\n </div>\n\n @if (authMode() === 'api-key') {\n <div class=\"field\">\n <label [for]=\"id + '-header'\">{{ 'nh-assistant.admin.server-editor.header-name' | translate }}</label>\n <input\n type=\"text\"\n class=\"mono\"\n placeholder=\"X-Api-Key\"\n [id]=\"id + '-header'\"\n [maxLength]=\"limits.headerName\"\n [value]=\"headerName()\"\n [attr.aria-describedby]=\"describedBy(id + '-header-hint', 'headerName')\"\n [attr.aria-invalid]=\"(submitted() && headerNameInvalid()) || serverInvalid('headerName') ? true : null\"\n (input)=\"headerName.set(value($event)); touch('headerName')\" />\n <p class=\"hint\" [id]=\"id + '-header-hint'\">\n <span [class.error-text]=\"submitted() && headerNameInvalid()\">\n {{ (submitted() && headerNameInvalid() ? 'nh-assistant.admin.server-editor.header-name-invalid' : 'nh-assistant.admin.server-editor.header-name-hint') | translate }}\n </span>\n </p>\n @if (serverInvalid('headerName')) {\n <p class=\"field-error\" [id]=\"errorId('headerName')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('headerName') | nhAssistantTranslate }}</p>\n }\n </div>\n }\n\n @if (authMode() === 'basic') {\n <div class=\"field\">\n <label [for]=\"id + '-user'\">{{ 'nh-assistant.admin.server-editor.user-name' | translate }}</label>\n <input\n type=\"text\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n [id]=\"id + '-user'\"\n [maxLength]=\"limits.userName\"\n [value]=\"userName()\"\n [attr.aria-describedby]=\"describedBy(id + '-user-hint', 'userName')\"\n [attr.aria-invalid]=\"(submitted() && userNameInvalid()) || serverInvalid('userName') ? true : null\"\n (input)=\"userName.set(value($event)); touch('userName')\" />\n <p class=\"hint\" [id]=\"id + '-user-hint'\">\n <span [class.error-text]=\"submitted() && userNameInvalid()\">\n {{ (submitted() && userNameInvalid() ? 'nh-assistant.admin.server-editor.user-name-invalid' : 'nh-assistant.admin.server-editor.user-name-hint') | translate }}\n </span>\n </p>\n @if (serverInvalid('userName')) {\n <p class=\"field-error\" [id]=\"errorId('userName')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('userName') | nhAssistantTranslate }}</p>\n }\n </div>\n }\n </div>\n\n @if (authMode() === 'forward-user-token') {\n <p class=\"message warning\" role=\"note\"><nh-assistant-icon name=\"warning\" />{{ 'nh-assistant.admin.server-editor.forward-warning' | translate }}</p>\n }\n\n @if (isOAuth()) {\n <fieldset class=\"field\">\n <legend>OAuth</legend>\n\n @if (authMode() === 'oauth') {\n <div\n class=\"inline\"\n role=\"radiogroup\"\n [attr.aria-label]=\"'nh-assistant.admin.server-editor.connection-scope' | translate\"\n [attr.aria-describedby]=\"describedBy(id + '-scope-hint', 'connectionScope')\">\n @for (scope of connectionScopes; track scope) {\n <label class=\"check\">\n <input\n type=\"radio\"\n [name]=\"id + '-connection-scope'\"\n [value]=\"scope\"\n [checked]=\"connectionScope() === scope\"\n [attr.aria-invalid]=\"serverInvalid('connectionScope') ? true : null\"\n (change)=\"connectionScope.set(scope); touch('connectionScope')\" />\n <span>{{ 'nh-assistant.admin.mcp.connection-scope.' + scope | translate }}</span>\n </label>\n }\n </div>\n <p class=\"hint\" [id]=\"id + '-scope-hint'\">{{ 'nh-assistant.admin.server-editor.connection-scope-hint' | translate }}</p>\n @if (serverInvalid('connectionScope')) {\n <p class=\"field-error\" [id]=\"errorId('connectionScope')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('connectionScope') | nhAssistantTranslate }}</p>\n }\n }\n\n @if (isOrganizationOAuth()) {\n <p class=\"message warning\" role=\"note\"><nh-assistant-icon name=\"warning\" />{{ 'nh-assistant.admin.server-editor.oauth-warning' | translate }}</p>\n <label [for]=\"id + '-account-label'\">{{ 'nh-assistant.admin.server-editor.organization-account-label' | translate }}</label>\n <input\n type=\"text\"\n [id]=\"id + '-account-label'\"\n [maxLength]=\"limits.organizationAccountLabel\"\n [value]=\"organizationAccountLabel()\"\n [attr.aria-describedby]=\"describedBy(id + '-account-label-hint', 'oauth.organizationAccountLabel')\"\n [attr.aria-invalid]=\"(submitted() && labelMissing()) || serverInvalid('oauth.organizationAccountLabel') ? true : null\"\n (input)=\"organizationAccountLabel.set(value($event)); touch('oauth.organizationAccountLabel')\" />\n <p class=\"hint\" [id]=\"id + '-account-label-hint'\">\n <span [class.error-text]=\"submitted() && labelMissing()\">\n {{ (submitted() && labelMissing() ? 'nh-assistant.admin.server-editor.organization-account-label-required' : 'nh-assistant.admin.server-editor.organization-account-label-hint') | translate }}\n </span>\n </p>\n @if (serverInvalid('oauth.organizationAccountLabel')) {\n <p class=\"field-error\" [id]=\"errorId('oauth.organizationAccountLabel')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('oauth.organizationAccountLabel') | nhAssistantTranslate }}</p>\n }\n }\n\n <div class=\"meta\">\n @if (server()?.authorization; as confirmed) {\n @if (!needsConfirmation()) {\n <span class=\"badge success\">{{ 'nh-assistant.admin.server-editor.confirmed-authorization' | translate: { issuer: confirmed.issuer } }}</span>\n }\n }\n <button class=\"secondary\" type=\"button\" [disabled]=\"discovering() || urlInvalid()\" (click)=\"discover()\">\n <nh-assistant-icon name=\"link\" />{{ (discovering() ? 'nh-assistant.admin.server-editor.discovering' : 'nh-assistant.admin.server-editor.discover') | translate }}\n </button>\n </div>\n @if (discoveryError(); as failure) {\n <p class=\"message error\" role=\"alert\"><nh-assistant-icon name=\"warning\" />{{ discoveryErrorKeys() | nhAssistantTranslate: failure.code }}</p>\n }\n @if (discovery(); as found) {\n <div class=\"discovery\" role=\"group\" [attr.aria-label]=\"'nh-assistant.admin.server-editor.discovery-title' | translate\">\n <dl>\n <dt>{{ 'nh-assistant.admin.server-editor.discovery-issuer' | translate }}</dt>\n <dd class=\"mono\">{{ found.issuer }}</dd>\n <dt>{{ 'nh-assistant.admin.server-editor.discovery-resource' | translate }}</dt>\n <dd class=\"mono\">{{ found.resource }}</dd>\n @if (found.scopesSupported.length > 0) {\n <dt>{{ 'nh-assistant.admin.server-editor.discovery-scopes' | translate }}</dt>\n <dd class=\"mono\">{{ found.scopesSupported.join(' ') }}</dd>\n }\n @if (authMode() === 'oauth' && registration(); as kind) {\n <dt>{{ 'nh-assistant.admin.server-editor.discovery-registration' | translate }}</dt>\n <dd>{{ 'nh-assistant.admin.server-editor.registration-' + kind | translate }}</dd>\n }\n </dl>\n @if (pkceMissing()) {\n <p class=\"message error\" role=\"alert\"><nh-assistant-icon name=\"warning\" />{{ 'nh-assistant.admin.server-editor.pkce-missing' | translate }}</p>\n } @else {\n <label class=\"check\">\n <input\n type=\"checkbox\"\n [checked]=\"confirmAuthorization()\"\n [attr.aria-describedby]=\"describedBy(id + '-confirm-hint', 'oauth.confirmAuthorization')\"\n [attr.aria-invalid]=\"serverInvalid('oauth.confirmAuthorization') ? true : null\"\n (change)=\"confirmAuthorization.set(checked($event)); touch('oauth.confirmAuthorization')\" />\n <span>{{ 'nh-assistant.admin.server-editor.confirm-authorization' | translate }}</span>\n </label>\n <p class=\"hint\" [id]=\"id + '-confirm-hint'\">{{ 'nh-assistant.admin.server-editor.confirm-authorization-hint' | translate }}</p>\n }\n </div>\n }\n @if (submitted() && confirmMissing()) {\n <p class=\"hint\"><span class=\"error-text\">{{ 'nh-assistant.admin.server-editor.confirm-required' | translate }}</span></p>\n } @else if (serverInvalid('oauth.confirmAuthorization')) {\n <p class=\"field-error\" [id]=\"errorId('oauth.confirmAuthorization')\"><nh-assistant-icon name=\"warning\" />{{ 'nh-assistant.admin.server-editor.confirm-required' | translate }}</p>\n }\n\n <label [for]=\"id + '-scopes'\">{{ 'nh-assistant.admin.server-editor.scopes' | translate }}</label>\n <input\n type=\"text\"\n class=\"mono\"\n spellcheck=\"false\"\n [id]=\"id + '-scopes'\"\n [maxLength]=\"limits.scopes\"\n [value]=\"oauthScopes()\"\n [attr.aria-describedby]=\"describedBy(id + '-scopes-hint', 'oauth.scopes')\"\n [attr.aria-invalid]=\"(submitted() && scopesInvalid()) || serverInvalid('oauth.scopes') ? true : null\"\n (input)=\"oauthScopes.set(value($event)); touch('oauth.scopes')\" />\n <p class=\"hint\" [id]=\"id + '-scopes-hint'\">\n <span [class.error-text]=\"submitted() && scopesInvalid()\">\n {{ (submitted() && scopesInvalid() ? 'nh-assistant.admin.server-editor.scopes-invalid' : 'nh-assistant.admin.server-editor.scopes-hint') | translate }}\n </span>\n </p>\n @if (serverInvalid('oauth.scopes')) {\n <p class=\"field-error\" [id]=\"errorId('oauth.scopes')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('oauth.scopes') | nhAssistantTranslate }}</p>\n }\n\n <label [for]=\"id + '-client-id'\">{{ 'nh-assistant.admin.server-editor.client-id' | translate }}</label>\n <input\n type=\"text\"\n class=\"mono\"\n spellcheck=\"false\"\n [id]=\"id + '-client-id'\"\n [maxLength]=\"limits.clientId\"\n [value]=\"oauthClientId()\"\n [attr.aria-describedby]=\"describedBy(id + '-client-id-hint', 'oauth.clientId')\"\n [attr.aria-invalid]=\"(submitted() && clientIdMissing()) || serverInvalid('oauth.clientId') ? true : null\"\n (input)=\"oauthClientId.set(value($event)); touch('oauth.clientId')\" />\n <p class=\"hint\" [id]=\"id + '-client-id-hint'\">\n <span [class.error-text]=\"submitted() && clientIdMissing()\">\n {{ (submitted() && clientIdMissing() ? 'nh-assistant.admin.server-editor.client-id-required' : 'nh-assistant.admin.server-editor.client-id-hint') | translate }}\n </span>\n </p>\n @if (serverInvalid('oauth.clientId')) {\n <p class=\"field-error\" [id]=\"errorId('oauth.clientId')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('oauth.clientId') | nhAssistantTranslate }}</p>\n }\n\n @if (oauthClientId().trim().length > 0) {\n <div class=\"inline\" role=\"radiogroup\" [attr.aria-label]=\"'nh-assistant.admin.server-editor.client-secret' | translate\">\n @for (source of secretSources; track source) {\n <label class=\"check\">\n <input\n type=\"radio\"\n [name]=\"id + '-oauth-secret-source'\"\n [value]=\"source\"\n [checked]=\"oauthSecretSource() === source\"\n (change)=\"setOAuthSecretSource(source)\" />\n <span>{{ 'nh-assistant.admin.server-editor.secret-source-' + source | translate }}</span>\n </label>\n }\n </div>\n @if (usesOAuthReference()) {\n <label [for]=\"id + '-oauth-reference'\">{{ 'nh-assistant.admin.server-editor.client-secret-reference' | translate }}</label>\n <input\n type=\"text\"\n class=\"mono\"\n spellcheck=\"false\"\n placeholder=\"config:planning-client-secret\"\n [id]=\"id + '-oauth-reference'\"\n [maxLength]=\"limits.secretReference\"\n [value]=\"oauthSecretReference()\"\n [attr.aria-describedby]=\"describedBy(null, 'oauth.clientSecretReference')\"\n [attr.aria-invalid]=\"(submitted() && oauthReferenceInvalid()) || serverInvalid('oauth.clientSecretReference') ? true : null\"\n (input)=\"oauthSecretReference.set(value($event)); touch('oauth.clientSecretReference')\" />\n @if (submitted() && oauthReferenceInvalid()) {\n <p class=\"hint\"><span class=\"error-text\">{{ 'nh-assistant.admin.server-editor.secret-reference-invalid' | translate }}</span></p>\n }\n @if (serverInvalid('oauth.clientSecretReference')) {\n <p class=\"field-error\" [id]=\"errorId('oauth.clientSecretReference')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('oauth.clientSecretReference') | nhAssistantTranslate }}</p>\n }\n } @else {\n @if (hasOAuthSecret() && oauthSecretAction() === 'keep') {\n <div class=\"meta secret-state\">\n <span class=\"badge success\">{{ 'nh-assistant.admin.server-editor.secret-set' | translate }}</span>\n <button class=\"secondary\" type=\"button\" (click)=\"oauthSecretAction.set('replace')\">{{ 'nh-assistant.admin.server-editor.secret-replace' | translate }}</button>\n </div>\n }\n @if (showOAuthSecretInput()) {\n <label [for]=\"id + '-oauth-secret'\">{{ 'nh-assistant.admin.server-editor.client-secret' | translate }}</label>\n <input\n type=\"password\"\n class=\"mono\"\n autocomplete=\"new-password\"\n spellcheck=\"false\"\n [id]=\"id + '-oauth-secret'\"\n [maxLength]=\"limits.clientSecret\"\n [attr.aria-describedby]=\"describedBy(id + '-oauth-secret-hint', 'oauth.clientSecret')\"\n [attr.aria-invalid]=\"serverInvalid('oauth.clientSecret') ? true : null\"\n [value]=\"oauthSecret()\"\n (input)=\"onOAuthSecretInput($event); touch('oauth.clientSecret')\" />\n }\n <p class=\"hint\" [id]=\"id + '-oauth-secret-hint'\">{{ 'nh-assistant.admin.server-editor.client-secret-hint' | translate }}</p>\n }\n }\n @if (serverInvalid('oauth.clientSecret')) {\n <p class=\"field-error\" [id]=\"errorId('oauth.clientSecret')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('oauth.clientSecret') | nhAssistantTranslate }}</p>\n }\n </fieldset>\n }\n\n @if (usesSecret()) {\n <fieldset class=\"field\">\n <legend>{{ 'nh-assistant.admin.server-editor.secret' | translate }}</legend>\n <div class=\"inline\" role=\"radiogroup\" [attr.aria-label]=\"'nh-assistant.admin.server-editor.secret-source' | translate\">\n @for (source of secretSources; track source) {\n <label class=\"check\">\n <input\n type=\"radio\"\n [name]=\"id + '-secret-source'\"\n [value]=\"source\"\n [checked]=\"secretSource() === source\"\n (change)=\"setSecretSource(source)\" />\n <span>{{ 'nh-assistant.admin.server-editor.secret-source-' + source | translate }}</span>\n </label>\n }\n </div>\n @if (usesReference()) {\n <label [for]=\"id + '-secret-reference'\">{{ 'nh-assistant.admin.server-editor.secret-reference' | translate }}</label>\n <input\n type=\"text\"\n class=\"mono\"\n spellcheck=\"false\"\n placeholder=\"config:planning-token\"\n [id]=\"id + '-secret-reference'\"\n [maxLength]=\"limits.secretReference\"\n [value]=\"secretReference()\"\n [attr.aria-describedby]=\"describedBy(id + '-secret-reference-hint', 'secretReference')\"\n [attr.aria-invalid]=\"(submitted() && secretReferenceInvalid()) || serverInvalid('secretReference') ? true : null\"\n (input)=\"secretReference.set(value($event)); touch('secretReference')\" />\n <p class=\"hint\" [id]=\"id + '-secret-reference-hint'\">\n <span [class.error-text]=\"submitted() && secretReferenceInvalid()\">\n {{ (submitted() && secretReferenceInvalid() ? 'nh-assistant.admin.server-editor.secret-reference-invalid' : 'nh-assistant.admin.server-editor.secret-reference-hint') | translate }}\n </span>\n </p>\n @if (serverInvalid('secretReference')) {\n <p class=\"field-error\" [id]=\"errorId('secretReference')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('secretReference') | nhAssistantTranslate }}</p>\n }\n } @else if (hasSecret()) {\n <div class=\"meta secret-state\">\n @switch (secretAction()) {\n @case ('clear') {\n <span class=\"badge warning\">{{ 'nh-assistant.admin.server-editor.secret-will-clear' | translate }}</span>\n <button class=\"secondary\" type=\"button\" (click)=\"setSecretAction('keep')\">{{ 'nh-assistant.admin.server-editor.secret-undo' | translate }}</button>\n }\n @case ('replace') {\n <span class=\"badge\">{{ 'nh-assistant.admin.server-editor.secret-set' | translate }}</span>\n <button class=\"secondary\" type=\"button\" (click)=\"setSecretAction('keep')\">{{ 'nh-assistant.admin.server-editor.secret-undo' | translate }}</button>\n }\n @default {\n <span class=\"badge success\">{{ 'nh-assistant.admin.server-editor.secret-set' | translate }}</span>\n <!-- A mode with a secret always needs one: it is replaced, or removed by choosing a reference or another mode. -->\n <button class=\"secondary\" type=\"button\" (click)=\"setSecretAction('replace')\">{{ 'nh-assistant.admin.server-editor.secret-replace' | translate }}</button>\n }\n }\n </div>\n } @else {\n <p class=\"muted\">{{ 'nh-assistant.admin.server-editor.secret-none' | translate }}</p>\n }\n @if (showSecretInput()) {\n <label class=\"visually-hidden\" [for]=\"id + '-secret'\">{{ 'nh-assistant.admin.server-editor.secret-new' | translate }}</label>\n <input\n type=\"password\"\n class=\"mono\"\n autocomplete=\"new-password\"\n spellcheck=\"false\"\n [id]=\"id + '-secret'\"\n [maxLength]=\"limits.secret\"\n [attr.placeholder]=\"'nh-assistant.admin.server-editor.secret-new' | translate\"\n [attr.aria-describedby]=\"describedBy(id + '-secret-hint', 'secret')\"\n [attr.aria-invalid]=\"serverInvalid('secret') ? true : null\"\n [value]=\"secret()\"\n (input)=\"onSecretInput($event); touch('secret')\" />\n }\n @if (!usesReference()) {\n <p class=\"hint\" [id]=\"id + '-secret-hint'\">{{ 'nh-assistant.admin.server-editor.secret-hint' | translate }}</p>\n }\n @if (serverInvalid('secret')) {\n <p class=\"field-error\" [id]=\"errorId('secret')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('secret') | nhAssistantTranslate }}</p>\n }\n </fieldset>\n }\n\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-certificate'\">{{ 'nh-assistant.admin.server-editor.client-certificate' | translate }}</label>\n <input\n type=\"text\"\n class=\"mono\"\n spellcheck=\"false\"\n [id]=\"id + '-certificate'\"\n [maxLength]=\"limits.clientCertificateReference\"\n [value]=\"clientCertificateReference()\"\n [attr.aria-describedby]=\"describedBy(id + '-certificate-hint', 'clientCertificateReference')\"\n [attr.aria-invalid]=\"(submitted() && certificateInvalid()) || serverInvalid('clientCertificateReference') ? true : null\"\n (input)=\"clientCertificateReference.set(value($event)); touch('clientCertificateReference')\" />\n <p class=\"hint\" [id]=\"id + '-certificate-hint'\">\n <span [class.error-text]=\"submitted() && certificateInvalid()\">\n {{ (submitted() && certificateInvalid() ? 'nh-assistant.admin.server-editor.client-certificate-invalid' : 'nh-assistant.admin.server-editor.client-certificate-hint') | translate }}\n </span>\n </p>\n @if (serverInvalid('clientCertificateReference')) {\n <p class=\"field-error\" [id]=\"errorId('clientCertificateReference')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('clientCertificateReference') | nhAssistantTranslate }}</p>\n }\n </div>\n\n <div class=\"field\">\n <label [for]=\"id + '-transport'\">{{ 'nh-assistant.admin.server-editor.transport' | translate }}</label>\n <select\n [id]=\"id + '-transport'\"\n [attr.aria-describedby]=\"describedBy(id + '-transport-hint', 'transport')\"\n [attr.aria-invalid]=\"serverInvalid('transport') ? true : null\"\n (change)=\"transport.set($any(value($event))); touch('transport')\">\n @for (option of transports; track option) {\n <option [value]=\"option\" [selected]=\"transport() === option\">{{ 'nh-assistant.admin.mcp.transport.' + option | translate }}</option>\n }\n </select>\n <p class=\"hint\" [id]=\"id + '-transport-hint'\">{{ 'nh-assistant.admin.server-editor.transport-hint' | translate }}</p>\n @if (serverInvalid('transport')) {\n <p class=\"field-error\" [id]=\"errorId('transport')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('transport') | nhAssistantTranslate }}</p>\n }\n </div>\n </div>\n\n <div class=\"field\">\n <label [for]=\"id + '-policy'\">{{ 'nh-assistant.admin.server-editor.required-policy' | translate }}</label>\n <input\n type=\"text\"\n class=\"mono\"\n [id]=\"id + '-policy'\"\n [maxLength]=\"limits.requiredPolicy\"\n [value]=\"requiredPolicy()\"\n [attr.aria-describedby]=\"describedBy(id + '-policy-hint', 'requiredPolicy')\"\n [attr.aria-invalid]=\"serverInvalid('requiredPolicy') ? true : null\"\n (input)=\"requiredPolicy.set(value($event)); touch('requiredPolicy')\" />\n <p class=\"hint\" [id]=\"id + '-policy-hint'\">{{ 'nh-assistant.admin.server-editor.required-policy-hint' | translate }}</p>\n @if (serverInvalid('requiredPolicy')) {\n <p class=\"field-error\" [id]=\"errorId('requiredPolicy')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('requiredPolicy') | nhAssistantTranslate }}</p>\n }\n </div>\n\n <div class=\"field\">\n <label [for]=\"id + '-headless'\">{{ 'nh-assistant.admin.server-editor.headless-use' | translate }}</label>\n <select\n [id]=\"id + '-headless'\"\n [attr.aria-describedby]=\"describedBy(id + '-headless-hint', 'headlessUse')\"\n [attr.aria-invalid]=\"serverInvalid('headlessUse') ? true : null\"\n (change)=\"headlessUse.set($any(value($event))); touch('headlessUse')\">\n @for (use of headlessUses(); track use) {\n <option [value]=\"use\" [selected]=\"effectiveHeadlessUse() === use\">{{ 'nh-assistant.admin.mcp.headless.' + use | translate }}</option>\n }\n </select>\n <p class=\"hint\" [id]=\"id + '-headless-hint'\">\n {{ (personalCredential() ? 'nh-assistant.admin.server-editor.headless-use-personal' : 'nh-assistant.admin.server-editor.headless-use-hint') | translate }}\n </p>\n @if (serverInvalid('headlessUse')) {\n <p class=\"field-error\" [id]=\"errorId('headlessUse')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('headlessUse') | nhAssistantTranslate }}</p>\n }\n </div>\n\n @if (!isNew()) {\n <fieldset class=\"field readers\">\n <legend>{{ 'nh-assistant.admin.server-editor.reader.title' | translate }}</legend>\n <p class=\"hint\">{{ 'nh-assistant.admin.server-editor.reader.hint' | translate }}</p>\n @if (serverInvalid('readers')) {\n <p class=\"field-error\" [id]=\"errorId('readers')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('readers') | nhAssistantTranslate }}</p>\n }\n\n @for (reader of readers(); track reader.key; let index = $index) {\n <fieldset class=\"subsection reader\" [attr.data-reader]=\"index\">\n <legend class=\"reader-title\">{{ 'nh-assistant.admin.server-editor.reader.item' | translate: { number: index + 1 } }}</legend>\n\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-reader-tool-' + reader.key\">{{ 'nh-assistant.admin.server-editor.reader.tool' | translate }}</label>\n <select\n [id]=\"id + '-reader-tool-' + reader.key\"\n [attr.aria-describedby]=\"describedBy(id + '-reader-tool-hint-' + reader.key, readerField(index, 'toolName'))\"\n [attr.aria-invalid]=\"(submitted() && (readerProblems()[index].tool || readerProblems()[index].duplicate)) || serverInvalid(readerField(index, 'toolName')) ? true : null\"\n (change)=\"updateReader(index, 'toolName', value($event))\">\n <option value=\"\" [selected]=\"reader.toolName === ''\">{{ 'nh-assistant.admin.server-editor.reader.tool-choose' | translate }}</option>\n @for (name of readerToolNames(); track name) {\n <option [value]=\"name\" [selected]=\"reader.toolName === name\">{{ name }}</option>\n }\n </select>\n <p class=\"hint\" [id]=\"id + '-reader-tool-hint-' + reader.key\">\n @if (submitted() && readerProblems()[index].tool) {\n <span class=\"error-text\">{{ 'nh-assistant.admin.required' | translate }}</span>\n } @else if (submitted() && readerProblems()[index].duplicate) {\n <span class=\"error-text\">{{ 'nh-assistant.admin.server-editor.reader.tool-duplicate' | translate }}</span>\n } @else {\n {{ 'nh-assistant.admin.server-editor.reader.tool-hint' | translate }}\n }\n </p>\n @if (serverInvalid(readerField(index, 'toolName'))) {\n <p class=\"field-error\" [id]=\"errorId(readerField(index, 'toolName'))\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys(readerField(index, 'toolName')) | nhAssistantTranslate }}</p>\n }\n </div>\n\n <div class=\"field\">\n <label [for]=\"id + '-reader-classification-' + reader.key\">{{ 'nh-assistant.admin.server-editor.reader.max-classification' | translate }}</label>\n <select\n [id]=\"id + '-reader-classification-' + reader.key\"\n [attr.aria-describedby]=\"describedBy(id + '-reader-classification-hint-' + reader.key, readerField(index, 'maxClassification'))\"\n [attr.aria-invalid]=\"(submitted() && readerProblems()[index].classification) || serverInvalid(readerField(index, 'maxClassification')) ? true : null\"\n (change)=\"updateReader(index, 'maxClassification', value($event))\">\n <option value=\"\" [selected]=\"reader.maxClassification === ''\">{{ 'nh-assistant.admin.server-editor.reader.max-classification-choose' | translate }}</option>\n @for (classification of classifications; track classification) {\n <option [value]=\"classification\" [selected]=\"reader.maxClassification === classification\">\n {{ 'nh-assistant.admin.run-config.skills.classification.' + classification | translate }}\n </option>\n }\n </select>\n <p class=\"hint\" [id]=\"id + '-reader-classification-hint-' + reader.key\">\n @if (submitted() && readerProblems()[index].classification) {\n <span class=\"error-text\">{{ 'nh-assistant.admin.required' | translate }}</span>\n } @else {\n {{ 'nh-assistant.admin.server-editor.reader.max-classification-hint' | translate }}\n }\n </p>\n @if (serverInvalid(readerField(index, 'maxClassification'))) {\n <p class=\"field-error\" [id]=\"errorId(readerField(index, 'maxClassification'))\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys(readerField(index, 'maxClassification')) | nhAssistantTranslate }}</p>\n }\n </div>\n\n <div class=\"field\">\n <label [for]=\"id + '-reader-types-' + reader.key\">{{ 'nh-assistant.admin.server-editor.reader.media-types' | translate }}</label>\n <input\n type=\"text\"\n class=\"mono\"\n spellcheck=\"false\"\n [id]=\"id + '-reader-types-' + reader.key\"\n [value]=\"reader.mediaTypes\"\n [attr.aria-describedby]=\"describedBy(id + '-reader-types-hint-' + reader.key, readerField(index, 'mediaTypes'))\"\n [attr.aria-invalid]=\"(submitted() && readerProblems()[index].types) || serverInvalid(readerField(index, 'mediaTypes')) ? true : null\"\n (input)=\"updateReader(index, 'mediaTypes', value($event))\" />\n <p class=\"hint\" [id]=\"id + '-reader-types-hint-' + reader.key\">\n <span [class.error-text]=\"submitted() && readerProblems()[index].types\">\n {{ (submitted() && readerProblems()[index].types ? 'nh-assistant.admin.server-editor.reader.media-types-invalid' : 'nh-assistant.admin.server-editor.reader.media-types-hint') | translate }}\n </span>\n </p>\n @if (serverInvalid(readerField(index, 'mediaTypes'))) {\n <p class=\"field-error\" [id]=\"errorId(readerField(index, 'mediaTypes'))\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys(readerField(index, 'mediaTypes')) | nhAssistantTranslate }}</p>\n }\n </div>\n\n <div class=\"field\">\n <label [for]=\"id + '-reader-priority-' + reader.key\">{{ 'nh-assistant.admin.server-editor.reader.priority' | translate }}</label>\n <input\n type=\"number\"\n inputmode=\"numeric\"\n step=\"1\"\n [min]=\"readerPriority.min\"\n [max]=\"readerPriority.max\"\n [id]=\"id + '-reader-priority-' + reader.key\"\n [value]=\"reader.priority\"\n [attr.aria-describedby]=\"describedBy(id + '-reader-priority-hint-' + reader.key, readerField(index, 'priority'))\"\n [attr.aria-invalid]=\"(submitted() && readerProblems()[index].priority) || serverInvalid(readerField(index, 'priority')) ? true : null\"\n (input)=\"updateReader(index, 'priority', value($event))\" />\n <p class=\"hint\" [id]=\"id + '-reader-priority-hint-' + reader.key\">\n <span [class.error-text]=\"submitted() && readerProblems()[index].priority\">\n {{ (submitted() && readerProblems()[index].priority ? 'nh-assistant.admin.server-editor.reader.priority-invalid' : 'nh-assistant.admin.server-editor.reader.priority-hint') | translate: readerPriority }}\n </span>\n </p>\n @if (serverInvalid(readerField(index, 'priority'))) {\n <p class=\"field-error\" [id]=\"errorId(readerField(index, 'priority'))\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys(readerField(index, 'priority')) | nhAssistantTranslate }}</p>\n }\n </div>\n </div>\n\n <div class=\"inline\">\n <button\n type=\"button\"\n class=\"secondary\"\n [attr.aria-label]=\"'nh-assistant.admin.server-editor.reader.remove-label' | translate: { number: index + 1 }\"\n (click)=\"removeReader(index)\">\n <nh-assistant-icon name=\"trash\" />{{ 'nh-assistant.admin.server-editor.reader.remove' | translate }}\n </button>\n </div>\n </fieldset>\n } @empty {\n <p class=\"muted\">{{ 'nh-assistant.admin.server-editor.reader.empty' | translate }}</p>\n }\n\n <div class=\"inline\">\n <button\n type=\"button\"\n class=\"secondary\"\n [id]=\"id + '-reader-add'\"\n [disabled]=\"readers().length >= maxReaders || readerToolNames().length === 0\"\n [attr.aria-describedby]=\"id + '-reader-add-hint'\"\n (click)=\"addReader()\">\n <nh-assistant-icon name=\"plus\" />{{ 'nh-assistant.admin.server-editor.reader.add' | translate }}\n </button>\n </div>\n <p class=\"hint\" [id]=\"id + '-reader-add-hint'\">{{ readerAddHint() | translate: { max: maxReaders } }}</p>\n @if (readers().length > 0) {\n <p class=\"message warning\" role=\"note\"><nh-assistant-icon name=\"warning\" />{{ 'nh-assistant.admin.server-editor.reader.leaves-application' | translate }}</p>\n }\n </fieldset>\n }\n\n <label class=\"check\">\n <input type=\"checkbox\" [checked]=\"isEnabled()\" (change)=\"isEnabled.set(checked($event))\" />\n <span>{{ 'nh-assistant.admin.server-editor.enabled' | translate }}</span>\n </label>\n\n @if (error(); as failure) {\n <div class=\"form-error\" role=\"alert\" data-server-error>\n <nh-assistant-icon name=\"warning\" />\n <div>\n <span>{{ errorKeys() | nhAssistantTranslate: failure.code }}</span>\n @if (otherServerErrors().length > 0) {\n <ul>\n @for (other of otherServerErrors(); track other.field) {\n <li><code class=\"mono\">{{ other.field }}</code>: {{ other.keys | nhAssistantTranslate }}</li>\n }\n </ul>\n }\n </div>\n </div>\n }\n\n <div class=\"actions\">\n <button class=\"secondary\" type=\"button\" (click)=\"cancelled.emit()\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n <button class=\"primary\" type=\"submit\" [disabled]=\"saving()\">\n {{ (saving() ? 'nh-assistant.admin.saving' : 'nh-assistant.admin.save') | translate }}\n </button>\n </div>\n</form>\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;min-width:0;color:var(--nha-text);font-family:var(--nha-font);font-size:14px}:host fieldset{min-width:0;margin:0;border:0;padding:0}:host legend,:host .label,:host .field>label{display:block;margin-bottom:6px;padding:0;color:var(--nha-text);font-size:12px;font-weight:700}:host input[type=text],:host input[type=url],:host input[type=password],:host input[type=search],:host input[type=email],:host input[type=number],:host input[type=date],:host input[type=time],:host input[type=datetime-local],:host select,:host textarea{box-sizing:border-box;width:100%;min-width:0;min-height:38px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:8px 10px;color:var(--nha-text);background:var(--nha-background);font:inherit;font-size:13px}:host input[type=text]:focus-visible,:host input[type=url]:focus-visible,:host input[type=password]:focus-visible,:host input[type=search]:focus-visible,:host input[type=email]:focus-visible,:host input[type=number]:focus-visible,:host input[type=date]:focus-visible,:host input[type=time]:focus-visible,:host input[type=datetime-local]:focus-visible,:host select:focus-visible,:host textarea:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host input[type=text]:disabled,:host input[type=url]:disabled,:host input[type=password]:disabled,:host input[type=search]:disabled,:host input[type=email]:disabled,:host input[type=number]:disabled,:host input[type=date]:disabled,:host input[type=time]:disabled,:host input[type=datetime-local]:disabled,:host select:disabled,:host textarea:disabled{cursor:not-allowed;opacity:.6}:host input[type=text][aria-invalid=true],:host input[type=url][aria-invalid=true],:host input[type=password][aria-invalid=true],:host input[type=search][aria-invalid=true],:host input[type=email][aria-invalid=true],:host input[type=number][aria-invalid=true],:host input[type=date][aria-invalid=true],:host input[type=time][aria-invalid=true],:host input[type=datetime-local][aria-invalid=true],:host select[aria-invalid=true],:host textarea[aria-invalid=true]{border-color:var(--nha-danger)}:host textarea{line-height:1.45;resize:vertical}:host input[type=number]{font-variant-numeric:tabular-nums}:host .mono{font-family:var(--nha-mono);font-size:12px}:host .field{display:flex;flex-direction:column;min-width:0}:host .hint{display:flex;justify-content:space-between;gap:8px;margin:4px 2px 0;color:var(--nha-text-muted);font-size:11px}:host .counter{flex:none;font-variant-numeric:tabular-nums}:host .error-text{color:var(--nha-danger)}:host .field-error{display:flex;align-items:flex-start;gap:6px;margin:4px 2px 0;color:var(--nha-danger);font-size:12px;font-weight:600;overflow-wrap:anywhere}:host .field-error nh-assistant-icon{flex:none;margin-top:1px}:host .form-error{box-sizing:border-box;display:flex;align-items:flex-start;gap:8px;margin:0;border:1px solid var(--nha-danger);border-radius:var(--nha-radius-control);padding:8px 12px;color:var(--nha-text);background:var(--nha-danger-soft);font-size:13px;overflow-wrap:anywhere}:host .form-error ul{margin:4px 0 0;padding-left:18px}:host .message{box-sizing:border-box;display:flex;align-items:center;gap:8px;margin:0;border:1px solid transparent;border-radius:var(--nha-radius-control);padding:8px 12px;font-size:13px;overflow-wrap:anywhere}:host .message.error{border-color:var(--nha-danger);color:var(--nha-text);background:var(--nha-danger-soft)}:host .message.warning{border-color:var(--nha-warning);color:var(--nha-text);background:var(--nha-warning-soft)}:host .message.success{border-color:var(--nha-success);color:var(--nha-text);background:var(--nha-success-soft)}:host .message.info{border-color:var(--nha-border);color:var(--nha-text);background:var(--nha-surface)}:host .message nh-assistant-icon{flex:none}:host button.link{min-height:0;border:0;padding:0;color:var(--nha-accent, currentColor);background:none;font:inherit;text-decoration:underline;cursor:pointer}:host button.link:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host button.link:disabled{cursor:not-allowed;opacity:.55}:host .primary,:host .secondary,:host .danger{display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;gap:6px;min-height:36px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px 14px;font:inherit;font-size:13px;font-weight:700;white-space:nowrap;cursor:pointer}:host .primary:focus-visible,:host .secondary:focus-visible,:host .danger:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .primary:disabled,:host .secondary:disabled,:host .danger:disabled{cursor:not-allowed;opacity:.55}:host .primary{border-color:var(--nha-accent);color:var(--nha-accent-contrast);background:var(--nha-accent)}:host .primary:hover:not(:disabled){filter:brightness(1.12)}:host .secondary{color:var(--nha-text);background:transparent}:host .secondary:hover:not(:disabled){background:var(--nha-surface-strong)}:host .danger{border-color:var(--nha-danger);color:var(--nha-danger);background:transparent}:host .danger:hover:not(:disabled){background:var(--nha-danger-soft)}:host .icon-button{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:1px solid transparent;border-radius:var(--nha-radius-control);color:var(--nha-text-muted);background:transparent;cursor:pointer}:host .icon-button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .icon-button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}:host .icon-button:disabled{cursor:not-allowed;opacity:.5}:host .actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px}:host .badge{display:inline-flex;align-items:center;gap:4px;border-radius:999px;padding:2px 8px;color:var(--nha-text-muted);background:var(--nha-surface-strong);font-size:11px;font-weight:700;white-space:nowrap}:host .badge.success{color:var(--nha-success);background:var(--nha-success-soft)}:host .badge.warning{color:var(--nha-warning);background:var(--nha-warning-soft)}:host .badge.danger{color:var(--nha-danger);background:var(--nha-danger-soft)}:host .visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}.section{display:flex;flex-direction:column;gap:16px;min-width:0}.section-header{display:flex;flex-wrap:wrap;align-items:flex-start;justify-content:space-between;gap:12px}.section-header>div{min-width:0;max-width:72ch}h3{margin:0 0 4px;font-size:17px;font-weight:750}h4{margin:0 0 8px;font-size:14px;font-weight:700}.muted{margin:0;color:var(--nha-text-muted);font-size:13px}.state{display:flex;flex-direction:column;align-items:flex-start;gap:10px;margin:0;border:1px dashed var(--nha-border);border-radius:var(--nha-radius);padding:20px;color:var(--nha-text-muted);font-size:13px}.state p{margin:0}.stack{display:flex;flex-direction:column;gap:14px;min-width:0}.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,240px),1fr));gap:14px}.meta{display:flex;flex-wrap:wrap;align-items:center;gap:8px 12px;color:var(--nha-text-muted);font-size:12px;overflow-wrap:anywhere}textarea.large{min-height:260px}.catalog-error{flex-wrap:wrap}.grow{flex:1;min-width:0}.subsection{display:flex;flex-direction:column;gap:8px;border-top:1px solid var(--nha-border);padding-top:14px}.history{display:grid;gap:6px;margin:0;padding:0;list-style:none}.history li{display:flex;flex-wrap:wrap;align-items:center;gap:6px 12px;font-size:12px}.cards{display:grid;gap:10px;margin:0;padding:0;list-style:none}.card{display:flex;flex-direction:column;gap:8px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius);padding:14px;background:var(--nha-background)}.card.inactive{opacity:.8}.card-header{display:flex;flex-wrap:wrap;align-items:center;gap:8px;min-width:0}.card-header strong{font-size:14px;overflow-wrap:anywhere}.card-actions{display:flex;flex-wrap:wrap;gap:8px}.check{display:flex;align-items:flex-start;gap:8px;font-size:13px;cursor:pointer}.check input{width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.check.described>span{display:grid;gap:2px;min-width:0;overflow-wrap:anywhere}.checks{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));gap:6px 12px;min-width:0;overflow-wrap:anywhere}.chips{display:flex;flex-wrap:wrap;gap:6px;margin:0;padding:0;list-style:none}.chip{display:inline-flex;align-items:center;gap:2px;border:1px solid var(--nha-border);border-radius:999px;padding:2px 2px 2px 10px;background:var(--nha-surface);font-family:var(--nha-mono);font-size:12px}.chip 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;width:26px;height:26px;border-radius:999px}.chip button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.chip button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}.chip button:disabled{cursor:not-allowed;opacity:.5}.inline{display:flex;flex-wrap:wrap;gap:8px}.inline>input{flex:1 1 180px}.catalog{display:grid;gap:4px;max-height:260px;margin:8px 0 0;overflow-y:auto;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px;list-style:none}.catalog li label{display:flex;flex-wrap:wrap;align-items:center;gap:6px 8px;border-radius:8px;padding:6px;cursor:pointer}.catalog li label:hover{background:var(--nha-surface)}.catalog .tool-id{font-family:var(--nha-mono);font-size:12px;overflow-wrap:anywhere}.skills{display:grid;gap:8px;margin:0;padding:0;list-style:none}.skill{display:grid;gap:6px;border-left:3px solid var(--nha-border);padding:4px 0 4px 12px}.uses{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));gap:8px}.use{display:flex;align-items:flex-start;gap:10px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:10px 12px;cursor:pointer;overflow-wrap:anywhere}.use input{flex:none;width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.use>span{display:grid;gap:2px;min-width:0}.use strong{font-size:13px}.use.selected{border-color:var(--nha-highlight);background:var(--nha-surface)}.use:focus-within{outline:2px solid var(--nha-highlight);outline-offset:2px}.discovery{display:grid;gap:8px;padding:10px 12px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control)}.discovery dl{display:grid;grid-template-columns:max-content minmax(0,1fr);gap:4px 12px;margin:0}.discovery dt{color:var(--nha-text-muted)}.discovery dd{margin:0;overflow-wrap:anywhere}\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 }); }
|
|
4448
4837
|
}
|
|
4449
4838
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.34", ngImport: i0, type: NhAssistantAdminMcpServerEditorComponent, decorators: [{
|
|
4450
4839
|
type: Component,
|
|
4451
|
-
args: [{ selector: 'nh-assistant-admin-mcp-server-editor', standalone: true, imports: [TranslatePipe, _NhAssistantTranslatePipe, _NhAssistantIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<form class=\"section card\" [attr.aria-labelledby]=\"id + '-title'\" [attr.aria-busy]=\"saving()\" autocomplete=\"off\" (submit)=\"$event.preventDefault(); save()\">\n <h3 [id]=\"id + '-title'\">\n @if (server(); as current) {\n {{ 'nh-assistant.admin.server-editor.edit-title' | translate: { name: current.displayName } }}\n } @else {\n {{ 'nh-assistant.admin.server-editor.new-title' | translate }}\n }\n </h3>\n\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-id'\">{{ 'nh-assistant.admin.server-editor.id' | translate }}</label>\n <input\n type=\"text\"\n class=\"mono\"\n [id]=\"id + '-id'\"\n [maxLength]=\"limits.id\"\n [value]=\"serverId()\"\n [disabled]=\"!isNew()\"\n [attr.aria-describedby]=\"describedBy(id + '-id-hint', 'id')\"\n [attr.aria-invalid]=\"(submitted() && idInvalid()) || serverInvalid('id') ? true : null\"\n (input)=\"serverId.set(value($event)); touch('id')\" />\n <p class=\"hint\" [id]=\"id + '-id-hint'\">\n <span [class.error-text]=\"submitted() && idInvalid()\">\n {{ (submitted() && idInvalid() ? 'nh-assistant.admin.invalid-id' : 'nh-assistant.admin.server-editor.id-hint') | translate }}\n </span>\n </p>\n @if (serverInvalid('id')) {\n <p class=\"field-error\" [id]=\"errorId('id')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('id') | nhAssistantTranslate }}</p>\n }\n </div>\n\n <div class=\"field\">\n <label [for]=\"id + '-name'\">{{ 'nh-assistant.admin.server-editor.display-name' | translate }}</label>\n <input\n type=\"text\"\n [id]=\"id + '-name'\"\n [maxLength]=\"limits.displayName\"\n [value]=\"displayName()\"\n [attr.aria-invalid]=\"(submitted() && nameMissing()) || serverInvalid('displayName') ? true : null\"\n [attr.aria-describedby]=\"describedBy(id + '-name-hint', 'displayName')\"\n (input)=\"displayName.set(value($event)); touch('displayName')\" />\n <p class=\"hint\" [id]=\"id + '-name-hint'\">\n @if (submitted() && nameMissing()) {\n <span class=\"error-text\">{{ 'nh-assistant.admin.required' | translate }}</span>\n }\n </p>\n @if (serverInvalid('displayName')) {\n <p class=\"field-error\" [id]=\"errorId('displayName')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('displayName') | nhAssistantTranslate }}</p>\n }\n </div>\n </div>\n\n <div class=\"field\">\n <label [for]=\"id + '-url'\">{{ 'nh-assistant.admin.server-editor.url' | translate }}</label>\n <input\n type=\"url\"\n class=\"mono\"\n inputmode=\"url\"\n [id]=\"id + '-url'\"\n [maxLength]=\"limits.url\"\n [value]=\"url()\"\n [attr.aria-describedby]=\"describedBy(id + '-url-hint', 'url')\"\n [attr.aria-invalid]=\"(submitted() && urlInvalid()) || serverInvalid('url') ? true : null\"\n (input)=\"onUrlInput($event); touch('url')\" />\n <p class=\"hint\" [id]=\"id + '-url-hint'\">\n <span [class.error-text]=\"submitted() && urlInvalid()\">\n {{ (submitted() && urlInvalid() ? 'nh-assistant.admin.server-editor.url-invalid' : 'nh-assistant.admin.server-editor.url-hint') | translate }}\n </span>\n </p>\n @if (serverInvalid('url')) {\n <p class=\"field-error\" [id]=\"errorId('url')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('url') | nhAssistantTranslate }}</p>\n }\n </div>\n\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-auth'\">{{ 'nh-assistant.admin.server-editor.auth-mode' | translate }}</label>\n <select\n [id]=\"id + '-auth'\"\n [attr.aria-describedby]=\"describedBy(null, 'authMode')\"\n [attr.aria-invalid]=\"serverInvalid('authMode') ? true : null\"\n (change)=\"authMode.set($any(value($event))); touch('authMode')\">\n @for (mode of authModes(); track mode) {\n <option [value]=\"mode\" [selected]=\"authMode() === mode\">{{ ['nh-assistant.admin.mcp.auth.' + mode] | nhAssistantTranslate: mode }}</option>\n }\n </select>\n @if (customOwnership(); as ownership) {\n <p class=\"hint\" data-custom-auth-mode>{{ 'nh-assistant.admin.server-editor.custom-auth.' + ownership | translate }}</p>\n }\n @if (serverInvalid('authMode')) {\n <p class=\"field-error\" [id]=\"errorId('authMode')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('authMode') | nhAssistantTranslate }}</p>\n }\n </div>\n\n @if (authMode() === 'api-key') {\n <div class=\"field\">\n <label [for]=\"id + '-header'\">{{ 'nh-assistant.admin.server-editor.header-name' | translate }}</label>\n <input\n type=\"text\"\n class=\"mono\"\n placeholder=\"X-Api-Key\"\n [id]=\"id + '-header'\"\n [maxLength]=\"limits.headerName\"\n [value]=\"headerName()\"\n [attr.aria-describedby]=\"describedBy(id + '-header-hint', 'headerName')\"\n [attr.aria-invalid]=\"(submitted() && headerNameInvalid()) || serverInvalid('headerName') ? true : null\"\n (input)=\"headerName.set(value($event)); touch('headerName')\" />\n <p class=\"hint\" [id]=\"id + '-header-hint'\">\n <span [class.error-text]=\"submitted() && headerNameInvalid()\">\n {{ (submitted() && headerNameInvalid() ? 'nh-assistant.admin.server-editor.header-name-invalid' : 'nh-assistant.admin.server-editor.header-name-hint') | translate }}\n </span>\n </p>\n @if (serverInvalid('headerName')) {\n <p class=\"field-error\" [id]=\"errorId('headerName')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('headerName') | nhAssistantTranslate }}</p>\n }\n </div>\n }\n\n @if (authMode() === 'basic') {\n <div class=\"field\">\n <label [for]=\"id + '-user'\">{{ 'nh-assistant.admin.server-editor.user-name' | translate }}</label>\n <input\n type=\"text\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n [id]=\"id + '-user'\"\n [maxLength]=\"limits.userName\"\n [value]=\"userName()\"\n [attr.aria-describedby]=\"describedBy(id + '-user-hint', 'userName')\"\n [attr.aria-invalid]=\"(submitted() && userNameInvalid()) || serverInvalid('userName') ? true : null\"\n (input)=\"userName.set(value($event)); touch('userName')\" />\n <p class=\"hint\" [id]=\"id + '-user-hint'\">\n <span [class.error-text]=\"submitted() && userNameInvalid()\">\n {{ (submitted() && userNameInvalid() ? 'nh-assistant.admin.server-editor.user-name-invalid' : 'nh-assistant.admin.server-editor.user-name-hint') | translate }}\n </span>\n </p>\n @if (serverInvalid('userName')) {\n <p class=\"field-error\" [id]=\"errorId('userName')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('userName') | nhAssistantTranslate }}</p>\n }\n </div>\n }\n </div>\n\n @if (authMode() === 'forward-user-token') {\n <p class=\"message warning\" role=\"note\"><nh-assistant-icon name=\"warning\" />{{ 'nh-assistant.admin.server-editor.forward-warning' | translate }}</p>\n }\n\n @if (isOAuth()) {\n <fieldset class=\"field\">\n <legend>OAuth</legend>\n\n @if (authMode() === 'oauth') {\n <div\n class=\"inline\"\n role=\"radiogroup\"\n [attr.aria-label]=\"'nh-assistant.admin.server-editor.connection-scope' | translate\"\n [attr.aria-describedby]=\"describedBy(id + '-scope-hint', 'connectionScope')\">\n @for (scope of connectionScopes; track scope) {\n <label class=\"check\">\n <input\n type=\"radio\"\n [name]=\"id + '-connection-scope'\"\n [value]=\"scope\"\n [checked]=\"connectionScope() === scope\"\n [attr.aria-invalid]=\"serverInvalid('connectionScope') ? true : null\"\n (change)=\"connectionScope.set(scope); touch('connectionScope')\" />\n <span>{{ 'nh-assistant.admin.mcp.connection-scope.' + scope | translate }}</span>\n </label>\n }\n </div>\n <p class=\"hint\" [id]=\"id + '-scope-hint'\">{{ 'nh-assistant.admin.server-editor.connection-scope-hint' | translate }}</p>\n @if (serverInvalid('connectionScope')) {\n <p class=\"field-error\" [id]=\"errorId('connectionScope')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('connectionScope') | nhAssistantTranslate }}</p>\n }\n }\n\n @if (isOrganizationOAuth()) {\n <p class=\"message warning\" role=\"note\"><nh-assistant-icon name=\"warning\" />{{ 'nh-assistant.admin.server-editor.oauth-warning' | translate }}</p>\n <label [for]=\"id + '-account-label'\">{{ 'nh-assistant.admin.server-editor.organization-account-label' | translate }}</label>\n <input\n type=\"text\"\n [id]=\"id + '-account-label'\"\n [maxLength]=\"limits.organizationAccountLabel\"\n [value]=\"organizationAccountLabel()\"\n [attr.aria-describedby]=\"describedBy(id + '-account-label-hint', 'oauth.organizationAccountLabel')\"\n [attr.aria-invalid]=\"(submitted() && labelMissing()) || serverInvalid('oauth.organizationAccountLabel') ? true : null\"\n (input)=\"organizationAccountLabel.set(value($event)); touch('oauth.organizationAccountLabel')\" />\n <p class=\"hint\" [id]=\"id + '-account-label-hint'\">\n <span [class.error-text]=\"submitted() && labelMissing()\">\n {{ (submitted() && labelMissing() ? 'nh-assistant.admin.server-editor.organization-account-label-required' : 'nh-assistant.admin.server-editor.organization-account-label-hint') | translate }}\n </span>\n </p>\n @if (serverInvalid('oauth.organizationAccountLabel')) {\n <p class=\"field-error\" [id]=\"errorId('oauth.organizationAccountLabel')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('oauth.organizationAccountLabel') | nhAssistantTranslate }}</p>\n }\n }\n\n <div class=\"meta\">\n @if (server()?.authorization; as confirmed) {\n @if (!needsConfirmation()) {\n <span class=\"badge success\">{{ 'nh-assistant.admin.server-editor.confirmed-authorization' | translate: { issuer: confirmed.issuer } }}</span>\n }\n }\n <button class=\"secondary\" type=\"button\" [disabled]=\"discovering() || urlInvalid()\" (click)=\"discover()\">\n <nh-assistant-icon name=\"link\" />{{ (discovering() ? 'nh-assistant.admin.server-editor.discovering' : 'nh-assistant.admin.server-editor.discover') | translate }}\n </button>\n </div>\n @if (discoveryError(); as failure) {\n <p class=\"message error\" role=\"alert\"><nh-assistant-icon name=\"warning\" />{{ discoveryErrorKeys() | nhAssistantTranslate: failure.code }}</p>\n }\n @if (discovery(); as found) {\n <div class=\"discovery\" role=\"group\" [attr.aria-label]=\"'nh-assistant.admin.server-editor.discovery-title' | translate\">\n <dl>\n <dt>{{ 'nh-assistant.admin.server-editor.discovery-issuer' | translate }}</dt>\n <dd class=\"mono\">{{ found.issuer }}</dd>\n <dt>{{ 'nh-assistant.admin.server-editor.discovery-resource' | translate }}</dt>\n <dd class=\"mono\">{{ found.resource }}</dd>\n @if (found.scopesSupported.length > 0) {\n <dt>{{ 'nh-assistant.admin.server-editor.discovery-scopes' | translate }}</dt>\n <dd class=\"mono\">{{ found.scopesSupported.join(' ') }}</dd>\n }\n @if (authMode() === 'oauth' && registration(); as kind) {\n <dt>{{ 'nh-assistant.admin.server-editor.discovery-registration' | translate }}</dt>\n <dd>{{ 'nh-assistant.admin.server-editor.registration-' + kind | translate }}</dd>\n }\n </dl>\n @if (pkceMissing()) {\n <p class=\"message error\" role=\"alert\"><nh-assistant-icon name=\"warning\" />{{ 'nh-assistant.admin.server-editor.pkce-missing' | translate }}</p>\n } @else {\n <label class=\"check\">\n <input\n type=\"checkbox\"\n [checked]=\"confirmAuthorization()\"\n [attr.aria-describedby]=\"describedBy(id + '-confirm-hint', 'oauth.confirmAuthorization')\"\n [attr.aria-invalid]=\"serverInvalid('oauth.confirmAuthorization') ? true : null\"\n (change)=\"confirmAuthorization.set(checked($event)); touch('oauth.confirmAuthorization')\" />\n <span>{{ 'nh-assistant.admin.server-editor.confirm-authorization' | translate }}</span>\n </label>\n <p class=\"hint\" [id]=\"id + '-confirm-hint'\">{{ 'nh-assistant.admin.server-editor.confirm-authorization-hint' | translate }}</p>\n }\n </div>\n }\n @if (submitted() && confirmMissing()) {\n <p class=\"hint\"><span class=\"error-text\">{{ 'nh-assistant.admin.server-editor.confirm-required' | translate }}</span></p>\n } @else if (serverInvalid('oauth.confirmAuthorization')) {\n <p class=\"field-error\" [id]=\"errorId('oauth.confirmAuthorization')\"><nh-assistant-icon name=\"warning\" />{{ 'nh-assistant.admin.server-editor.confirm-required' | translate }}</p>\n }\n\n <label [for]=\"id + '-scopes'\">{{ 'nh-assistant.admin.server-editor.scopes' | translate }}</label>\n <input\n type=\"text\"\n class=\"mono\"\n spellcheck=\"false\"\n [id]=\"id + '-scopes'\"\n [maxLength]=\"limits.scopes\"\n [value]=\"oauthScopes()\"\n [attr.aria-describedby]=\"describedBy(id + '-scopes-hint', 'oauth.scopes')\"\n [attr.aria-invalid]=\"(submitted() && scopesInvalid()) || serverInvalid('oauth.scopes') ? true : null\"\n (input)=\"oauthScopes.set(value($event)); touch('oauth.scopes')\" />\n <p class=\"hint\" [id]=\"id + '-scopes-hint'\">\n <span [class.error-text]=\"submitted() && scopesInvalid()\">\n {{ (submitted() && scopesInvalid() ? 'nh-assistant.admin.server-editor.scopes-invalid' : 'nh-assistant.admin.server-editor.scopes-hint') | translate }}\n </span>\n </p>\n @if (serverInvalid('oauth.scopes')) {\n <p class=\"field-error\" [id]=\"errorId('oauth.scopes')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('oauth.scopes') | nhAssistantTranslate }}</p>\n }\n\n <label [for]=\"id + '-client-id'\">{{ 'nh-assistant.admin.server-editor.client-id' | translate }}</label>\n <input\n type=\"text\"\n class=\"mono\"\n spellcheck=\"false\"\n [id]=\"id + '-client-id'\"\n [maxLength]=\"limits.clientId\"\n [value]=\"oauthClientId()\"\n [attr.aria-describedby]=\"describedBy(id + '-client-id-hint', 'oauth.clientId')\"\n [attr.aria-invalid]=\"(submitted() && clientIdMissing()) || serverInvalid('oauth.clientId') ? true : null\"\n (input)=\"oauthClientId.set(value($event)); touch('oauth.clientId')\" />\n <p class=\"hint\" [id]=\"id + '-client-id-hint'\">\n <span [class.error-text]=\"submitted() && clientIdMissing()\">\n {{ (submitted() && clientIdMissing() ? 'nh-assistant.admin.server-editor.client-id-required' : 'nh-assistant.admin.server-editor.client-id-hint') | translate }}\n </span>\n </p>\n @if (serverInvalid('oauth.clientId')) {\n <p class=\"field-error\" [id]=\"errorId('oauth.clientId')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('oauth.clientId') | nhAssistantTranslate }}</p>\n }\n\n @if (oauthClientId().trim().length > 0) {\n <div class=\"inline\" role=\"radiogroup\" [attr.aria-label]=\"'nh-assistant.admin.server-editor.client-secret' | translate\">\n @for (source of secretSources; track source) {\n <label class=\"check\">\n <input\n type=\"radio\"\n [name]=\"id + '-oauth-secret-source'\"\n [value]=\"source\"\n [checked]=\"oauthSecretSource() === source\"\n (change)=\"setOAuthSecretSource(source)\" />\n <span>{{ 'nh-assistant.admin.server-editor.secret-source-' + source | translate }}</span>\n </label>\n }\n </div>\n @if (usesOAuthReference()) {\n <label [for]=\"id + '-oauth-reference'\">{{ 'nh-assistant.admin.server-editor.client-secret-reference' | translate }}</label>\n <input\n type=\"text\"\n class=\"mono\"\n spellcheck=\"false\"\n placeholder=\"config:planning-client-secret\"\n [id]=\"id + '-oauth-reference'\"\n [maxLength]=\"limits.secretReference\"\n [value]=\"oauthSecretReference()\"\n [attr.aria-describedby]=\"describedBy(null, 'oauth.clientSecretReference')\"\n [attr.aria-invalid]=\"(submitted() && oauthReferenceInvalid()) || serverInvalid('oauth.clientSecretReference') ? true : null\"\n (input)=\"oauthSecretReference.set(value($event)); touch('oauth.clientSecretReference')\" />\n @if (submitted() && oauthReferenceInvalid()) {\n <p class=\"hint\"><span class=\"error-text\">{{ 'nh-assistant.admin.server-editor.secret-reference-invalid' | translate }}</span></p>\n }\n @if (serverInvalid('oauth.clientSecretReference')) {\n <p class=\"field-error\" [id]=\"errorId('oauth.clientSecretReference')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('oauth.clientSecretReference') | nhAssistantTranslate }}</p>\n }\n } @else {\n @if (hasOAuthSecret() && oauthSecretAction() === 'keep') {\n <div class=\"meta secret-state\">\n <span class=\"badge success\">{{ 'nh-assistant.admin.server-editor.secret-set' | translate }}</span>\n <button class=\"secondary\" type=\"button\" (click)=\"oauthSecretAction.set('replace')\">{{ 'nh-assistant.admin.server-editor.secret-replace' | translate }}</button>\n </div>\n }\n @if (showOAuthSecretInput()) {\n <label [for]=\"id + '-oauth-secret'\">{{ 'nh-assistant.admin.server-editor.client-secret' | translate }}</label>\n <input\n type=\"password\"\n class=\"mono\"\n autocomplete=\"new-password\"\n spellcheck=\"false\"\n [id]=\"id + '-oauth-secret'\"\n [maxLength]=\"limits.clientSecret\"\n [attr.aria-describedby]=\"describedBy(id + '-oauth-secret-hint', 'oauth.clientSecret')\"\n [attr.aria-invalid]=\"serverInvalid('oauth.clientSecret') ? true : null\"\n [value]=\"oauthSecret()\"\n (input)=\"onOAuthSecretInput($event); touch('oauth.clientSecret')\" />\n }\n <p class=\"hint\" [id]=\"id + '-oauth-secret-hint'\">{{ 'nh-assistant.admin.server-editor.client-secret-hint' | translate }}</p>\n }\n }\n @if (serverInvalid('oauth.clientSecret')) {\n <p class=\"field-error\" [id]=\"errorId('oauth.clientSecret')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('oauth.clientSecret') | nhAssistantTranslate }}</p>\n }\n </fieldset>\n }\n\n @if (usesSecret()) {\n <fieldset class=\"field\">\n <legend>{{ 'nh-assistant.admin.server-editor.secret' | translate }}</legend>\n <div class=\"inline\" role=\"radiogroup\" [attr.aria-label]=\"'nh-assistant.admin.server-editor.secret-source' | translate\">\n @for (source of secretSources; track source) {\n <label class=\"check\">\n <input\n type=\"radio\"\n [name]=\"id + '-secret-source'\"\n [value]=\"source\"\n [checked]=\"secretSource() === source\"\n (change)=\"setSecretSource(source)\" />\n <span>{{ 'nh-assistant.admin.server-editor.secret-source-' + source | translate }}</span>\n </label>\n }\n </div>\n @if (usesReference()) {\n <label [for]=\"id + '-secret-reference'\">{{ 'nh-assistant.admin.server-editor.secret-reference' | translate }}</label>\n <input\n type=\"text\"\n class=\"mono\"\n spellcheck=\"false\"\n placeholder=\"config:planning-token\"\n [id]=\"id + '-secret-reference'\"\n [maxLength]=\"limits.secretReference\"\n [value]=\"secretReference()\"\n [attr.aria-describedby]=\"describedBy(id + '-secret-reference-hint', 'secretReference')\"\n [attr.aria-invalid]=\"(submitted() && secretReferenceInvalid()) || serverInvalid('secretReference') ? true : null\"\n (input)=\"secretReference.set(value($event)); touch('secretReference')\" />\n <p class=\"hint\" [id]=\"id + '-secret-reference-hint'\">\n <span [class.error-text]=\"submitted() && secretReferenceInvalid()\">\n {{ (submitted() && secretReferenceInvalid() ? 'nh-assistant.admin.server-editor.secret-reference-invalid' : 'nh-assistant.admin.server-editor.secret-reference-hint') | translate }}\n </span>\n </p>\n @if (serverInvalid('secretReference')) {\n <p class=\"field-error\" [id]=\"errorId('secretReference')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('secretReference') | nhAssistantTranslate }}</p>\n }\n } @else if (hasSecret()) {\n <div class=\"meta secret-state\">\n @switch (secretAction()) {\n @case ('clear') {\n <span class=\"badge warning\">{{ 'nh-assistant.admin.server-editor.secret-will-clear' | translate }}</span>\n <button class=\"secondary\" type=\"button\" (click)=\"setSecretAction('keep')\">{{ 'nh-assistant.admin.server-editor.secret-undo' | translate }}</button>\n }\n @case ('replace') {\n <span class=\"badge\">{{ 'nh-assistant.admin.server-editor.secret-set' | translate }}</span>\n <button class=\"secondary\" type=\"button\" (click)=\"setSecretAction('keep')\">{{ 'nh-assistant.admin.server-editor.secret-undo' | translate }}</button>\n }\n @default {\n <span class=\"badge success\">{{ 'nh-assistant.admin.server-editor.secret-set' | translate }}</span>\n <!-- A mode with a secret always needs one: it is replaced, or removed by choosing a reference or another mode. -->\n <button class=\"secondary\" type=\"button\" (click)=\"setSecretAction('replace')\">{{ 'nh-assistant.admin.server-editor.secret-replace' | translate }}</button>\n }\n }\n </div>\n } @else {\n <p class=\"muted\">{{ 'nh-assistant.admin.server-editor.secret-none' | translate }}</p>\n }\n @if (showSecretInput()) {\n <label class=\"visually-hidden\" [for]=\"id + '-secret'\">{{ 'nh-assistant.admin.server-editor.secret-new' | translate }}</label>\n <input\n type=\"password\"\n class=\"mono\"\n autocomplete=\"new-password\"\n spellcheck=\"false\"\n [id]=\"id + '-secret'\"\n [maxLength]=\"limits.secret\"\n [attr.placeholder]=\"'nh-assistant.admin.server-editor.secret-new' | translate\"\n [attr.aria-describedby]=\"describedBy(id + '-secret-hint', 'secret')\"\n [attr.aria-invalid]=\"serverInvalid('secret') ? true : null\"\n [value]=\"secret()\"\n (input)=\"onSecretInput($event); touch('secret')\" />\n }\n @if (!usesReference()) {\n <p class=\"hint\" [id]=\"id + '-secret-hint'\">{{ 'nh-assistant.admin.server-editor.secret-hint' | translate }}</p>\n }\n @if (serverInvalid('secret')) {\n <p class=\"field-error\" [id]=\"errorId('secret')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('secret') | nhAssistantTranslate }}</p>\n }\n </fieldset>\n }\n\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-certificate'\">{{ 'nh-assistant.admin.server-editor.client-certificate' | translate }}</label>\n <input\n type=\"text\"\n class=\"mono\"\n spellcheck=\"false\"\n [id]=\"id + '-certificate'\"\n [maxLength]=\"limits.clientCertificateReference\"\n [value]=\"clientCertificateReference()\"\n [attr.aria-describedby]=\"describedBy(id + '-certificate-hint', 'clientCertificateReference')\"\n [attr.aria-invalid]=\"(submitted() && certificateInvalid()) || serverInvalid('clientCertificateReference') ? true : null\"\n (input)=\"clientCertificateReference.set(value($event)); touch('clientCertificateReference')\" />\n <p class=\"hint\" [id]=\"id + '-certificate-hint'\">\n <span [class.error-text]=\"submitted() && certificateInvalid()\">\n {{ (submitted() && certificateInvalid() ? 'nh-assistant.admin.server-editor.client-certificate-invalid' : 'nh-assistant.admin.server-editor.client-certificate-hint') | translate }}\n </span>\n </p>\n @if (serverInvalid('clientCertificateReference')) {\n <p class=\"field-error\" [id]=\"errorId('clientCertificateReference')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('clientCertificateReference') | nhAssistantTranslate }}</p>\n }\n </div>\n\n <div class=\"field\">\n <label [for]=\"id + '-transport'\">{{ 'nh-assistant.admin.server-editor.transport' | translate }}</label>\n <select\n [id]=\"id + '-transport'\"\n [attr.aria-describedby]=\"describedBy(id + '-transport-hint', 'transport')\"\n [attr.aria-invalid]=\"serverInvalid('transport') ? true : null\"\n (change)=\"transport.set($any(value($event))); touch('transport')\">\n @for (option of transports; track option) {\n <option [value]=\"option\" [selected]=\"transport() === option\">{{ 'nh-assistant.admin.mcp.transport.' + option | translate }}</option>\n }\n </select>\n <p class=\"hint\" [id]=\"id + '-transport-hint'\">{{ 'nh-assistant.admin.server-editor.transport-hint' | translate }}</p>\n @if (serverInvalid('transport')) {\n <p class=\"field-error\" [id]=\"errorId('transport')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('transport') | nhAssistantTranslate }}</p>\n }\n </div>\n </div>\n\n <div class=\"field\">\n <label [for]=\"id + '-policy'\">{{ 'nh-assistant.admin.server-editor.required-policy' | translate }}</label>\n <input\n type=\"text\"\n class=\"mono\"\n [id]=\"id + '-policy'\"\n [maxLength]=\"limits.requiredPolicy\"\n [value]=\"requiredPolicy()\"\n [attr.aria-describedby]=\"describedBy(id + '-policy-hint', 'requiredPolicy')\"\n [attr.aria-invalid]=\"serverInvalid('requiredPolicy') ? true : null\"\n (input)=\"requiredPolicy.set(value($event)); touch('requiredPolicy')\" />\n <p class=\"hint\" [id]=\"id + '-policy-hint'\">{{ 'nh-assistant.admin.server-editor.required-policy-hint' | translate }}</p>\n @if (serverInvalid('requiredPolicy')) {\n <p class=\"field-error\" [id]=\"errorId('requiredPolicy')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('requiredPolicy') | nhAssistantTranslate }}</p>\n }\n </div>\n\n <div class=\"field\">\n <label [for]=\"id + '-headless'\">{{ 'nh-assistant.admin.server-editor.headless-use' | translate }}</label>\n <select\n [id]=\"id + '-headless'\"\n [attr.aria-describedby]=\"describedBy(id + '-headless-hint', 'headlessUse')\"\n [attr.aria-invalid]=\"serverInvalid('headlessUse') ? true : null\"\n (change)=\"headlessUse.set($any(value($event))); touch('headlessUse')\">\n @for (use of headlessUses(); track use) {\n <option [value]=\"use\" [selected]=\"effectiveHeadlessUse() === use\">{{ 'nh-assistant.admin.mcp.headless.' + use | translate }}</option>\n }\n </select>\n <p class=\"hint\" [id]=\"id + '-headless-hint'\">\n {{ (personalCredential() ? 'nh-assistant.admin.server-editor.headless-use-personal' : 'nh-assistant.admin.server-editor.headless-use-hint') | translate }}\n </p>\n @if (serverInvalid('headlessUse')) {\n <p class=\"field-error\" [id]=\"errorId('headlessUse')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('headlessUse') | nhAssistantTranslate }}</p>\n }\n </div>\n\n <label class=\"check\">\n <input type=\"checkbox\" [checked]=\"isEnabled()\" (change)=\"isEnabled.set(checked($event))\" />\n <span>{{ 'nh-assistant.admin.server-editor.enabled' | translate }}</span>\n </label>\n\n @if (error(); as failure) {\n <div class=\"form-error\" role=\"alert\" data-server-error>\n <nh-assistant-icon name=\"warning\" />\n <div>\n <span>{{ errorKeys() | nhAssistantTranslate: failure.code }}</span>\n @if (otherServerErrors().length > 0) {\n <ul>\n @for (other of otherServerErrors(); track other.field) {\n <li><code class=\"mono\">{{ other.field }}</code>: {{ other.keys | nhAssistantTranslate }}</li>\n }\n </ul>\n }\n </div>\n </div>\n }\n\n <div class=\"actions\">\n <button class=\"secondary\" type=\"button\" (click)=\"cancelled.emit()\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n <button class=\"primary\" type=\"submit\" [disabled]=\"saving()\">\n {{ (saving() ? 'nh-assistant.admin.saving' : 'nh-assistant.admin.save') | translate }}\n </button>\n </div>\n</form>\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;min-width:0;color:var(--nha-text);font-family:var(--nha-font);font-size:14px}:host fieldset{min-width:0;margin:0;border:0;padding:0}:host legend,:host .label,:host .field>label{display:block;margin-bottom:6px;padding:0;color:var(--nha-text);font-size:12px;font-weight:700}:host input[type=text],:host input[type=url],:host input[type=password],:host input[type=search],:host input[type=email],:host input[type=number],:host input[type=date],:host input[type=time],:host input[type=datetime-local],:host select,:host textarea{box-sizing:border-box;width:100%;min-width:0;min-height:38px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:8px 10px;color:var(--nha-text);background:var(--nha-background);font:inherit;font-size:13px}:host input[type=text]:focus-visible,:host input[type=url]:focus-visible,:host input[type=password]:focus-visible,:host input[type=search]:focus-visible,:host input[type=email]:focus-visible,:host input[type=number]:focus-visible,:host input[type=date]:focus-visible,:host input[type=time]:focus-visible,:host input[type=datetime-local]:focus-visible,:host select:focus-visible,:host textarea:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host input[type=text]:disabled,:host input[type=url]:disabled,:host input[type=password]:disabled,:host input[type=search]:disabled,:host input[type=email]:disabled,:host input[type=number]:disabled,:host input[type=date]:disabled,:host input[type=time]:disabled,:host input[type=datetime-local]:disabled,:host select:disabled,:host textarea:disabled{cursor:not-allowed;opacity:.6}:host input[type=text][aria-invalid=true],:host input[type=url][aria-invalid=true],:host input[type=password][aria-invalid=true],:host input[type=search][aria-invalid=true],:host input[type=email][aria-invalid=true],:host input[type=number][aria-invalid=true],:host input[type=date][aria-invalid=true],:host input[type=time][aria-invalid=true],:host input[type=datetime-local][aria-invalid=true],:host select[aria-invalid=true],:host textarea[aria-invalid=true]{border-color:var(--nha-danger)}:host textarea{line-height:1.45;resize:vertical}:host input[type=number]{font-variant-numeric:tabular-nums}:host .mono{font-family:var(--nha-mono);font-size:12px}:host .field{display:flex;flex-direction:column;min-width:0}:host .hint{display:flex;justify-content:space-between;gap:8px;margin:4px 2px 0;color:var(--nha-text-muted);font-size:11px}:host .counter{flex:none;font-variant-numeric:tabular-nums}:host .error-text{color:var(--nha-danger)}:host .field-error{display:flex;align-items:flex-start;gap:6px;margin:4px 2px 0;color:var(--nha-danger);font-size:12px;font-weight:600;overflow-wrap:anywhere}:host .field-error nh-assistant-icon{flex:none;margin-top:1px}:host .form-error{box-sizing:border-box;display:flex;align-items:flex-start;gap:8px;margin:0;border:1px solid var(--nha-danger);border-radius:var(--nha-radius-control);padding:8px 12px;color:var(--nha-text);background:var(--nha-danger-soft);font-size:13px;overflow-wrap:anywhere}:host .form-error ul{margin:4px 0 0;padding-left:18px}:host .message{box-sizing:border-box;display:flex;align-items:center;gap:8px;margin:0;border:1px solid transparent;border-radius:var(--nha-radius-control);padding:8px 12px;font-size:13px;overflow-wrap:anywhere}:host .message.error{border-color:var(--nha-danger);color:var(--nha-text);background:var(--nha-danger-soft)}:host .message.warning{border-color:var(--nha-warning);color:var(--nha-text);background:var(--nha-warning-soft)}:host .message.success{border-color:var(--nha-success);color:var(--nha-text);background:var(--nha-success-soft)}:host .message.info{border-color:var(--nha-border);color:var(--nha-text);background:var(--nha-surface)}:host .message nh-assistant-icon{flex:none}:host button.link{min-height:0;border:0;padding:0;color:var(--nha-accent, currentColor);background:none;font:inherit;text-decoration:underline;cursor:pointer}:host button.link:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host button.link:disabled{cursor:not-allowed;opacity:.55}:host .primary,:host .secondary,:host .danger{display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;gap:6px;min-height:36px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px 14px;font:inherit;font-size:13px;font-weight:700;white-space:nowrap;cursor:pointer}:host .primary:focus-visible,:host .secondary:focus-visible,:host .danger:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .primary:disabled,:host .secondary:disabled,:host .danger:disabled{cursor:not-allowed;opacity:.55}:host .primary{border-color:var(--nha-accent);color:var(--nha-accent-contrast);background:var(--nha-accent)}:host .primary:hover:not(:disabled){filter:brightness(1.12)}:host .secondary{color:var(--nha-text);background:transparent}:host .secondary:hover:not(:disabled){background:var(--nha-surface-strong)}:host .danger{border-color:var(--nha-danger);color:var(--nha-danger);background:transparent}:host .danger:hover:not(:disabled){background:var(--nha-danger-soft)}:host .icon-button{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:1px solid transparent;border-radius:var(--nha-radius-control);color:var(--nha-text-muted);background:transparent;cursor:pointer}:host .icon-button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .icon-button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}:host .icon-button:disabled{cursor:not-allowed;opacity:.5}:host .actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px}:host .badge{display:inline-flex;align-items:center;gap:4px;border-radius:999px;padding:2px 8px;color:var(--nha-text-muted);background:var(--nha-surface-strong);font-size:11px;font-weight:700;white-space:nowrap}:host .badge.success{color:var(--nha-success);background:var(--nha-success-soft)}:host .badge.warning{color:var(--nha-warning);background:var(--nha-warning-soft)}:host .badge.danger{color:var(--nha-danger);background:var(--nha-danger-soft)}:host .visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}.section{display:flex;flex-direction:column;gap:16px;min-width:0}.section-header{display:flex;flex-wrap:wrap;align-items:flex-start;justify-content:space-between;gap:12px}.section-header>div{min-width:0;max-width:72ch}h3{margin:0 0 4px;font-size:17px;font-weight:750}h4{margin:0 0 8px;font-size:14px;font-weight:700}.muted{margin:0;color:var(--nha-text-muted);font-size:13px}.state{display:flex;flex-direction:column;align-items:flex-start;gap:10px;margin:0;border:1px dashed var(--nha-border);border-radius:var(--nha-radius);padding:20px;color:var(--nha-text-muted);font-size:13px}.state p{margin:0}.stack{display:flex;flex-direction:column;gap:14px;min-width:0}.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,240px),1fr));gap:14px}.meta{display:flex;flex-wrap:wrap;align-items:center;gap:8px 12px;color:var(--nha-text-muted);font-size:12px;overflow-wrap:anywhere}textarea.large{min-height:260px}.catalog-error{flex-wrap:wrap}.grow{flex:1;min-width:0}.subsection{display:flex;flex-direction:column;gap:8px;border-top:1px solid var(--nha-border);padding-top:14px}.history{display:grid;gap:6px;margin:0;padding:0;list-style:none}.history li{display:flex;flex-wrap:wrap;align-items:center;gap:6px 12px;font-size:12px}.cards{display:grid;gap:10px;margin:0;padding:0;list-style:none}.card{display:flex;flex-direction:column;gap:8px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius);padding:14px;background:var(--nha-background)}.card.inactive{opacity:.8}.card-header{display:flex;flex-wrap:wrap;align-items:center;gap:8px;min-width:0}.card-header strong{font-size:14px;overflow-wrap:anywhere}.card-actions{display:flex;flex-wrap:wrap;gap:8px}.check{display:flex;align-items:flex-start;gap:8px;font-size:13px;cursor:pointer}.check input{width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.check.described>span{display:grid;gap:2px;min-width:0;overflow-wrap:anywhere}.checks{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));gap:6px 12px;min-width:0;overflow-wrap:anywhere}.chips{display:flex;flex-wrap:wrap;gap:6px;margin:0;padding:0;list-style:none}.chip{display:inline-flex;align-items:center;gap:2px;border:1px solid var(--nha-border);border-radius:999px;padding:2px 2px 2px 10px;background:var(--nha-surface);font-family:var(--nha-mono);font-size:12px}.chip 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;width:26px;height:26px;border-radius:999px}.chip button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.chip button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}.chip button:disabled{cursor:not-allowed;opacity:.5}.inline{display:flex;flex-wrap:wrap;gap:8px}.inline>input{flex:1 1 180px}.catalog{display:grid;gap:4px;max-height:260px;margin:8px 0 0;overflow-y:auto;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px;list-style:none}.catalog li label{display:flex;flex-wrap:wrap;align-items:center;gap:6px 8px;border-radius:8px;padding:6px;cursor:pointer}.catalog li label:hover{background:var(--nha-surface)}.catalog .tool-id{font-family:var(--nha-mono);font-size:12px;overflow-wrap:anywhere}.skills{display:grid;gap:8px;margin:0;padding:0;list-style:none}.skill{display:grid;gap:6px;border-left:3px solid var(--nha-border);padding:4px 0 4px 12px}.uses{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));gap:8px}.use{display:flex;align-items:flex-start;gap:10px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:10px 12px;cursor:pointer;overflow-wrap:anywhere}.use input{flex:none;width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.use>span{display:grid;gap:2px;min-width:0}.use strong{font-size:13px}.use.selected{border-color:var(--nha-highlight);background:var(--nha-surface)}.use:focus-within{outline:2px solid var(--nha-highlight);outline-offset:2px}.discovery{display:grid;gap:8px;padding:10px 12px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control)}.discovery dl{display:grid;grid-template-columns:max-content minmax(0,1fr);gap:4px 12px;margin:0}.discovery dt{color:var(--nha-text-muted)}.discovery dd{margin:0;overflow-wrap:anywhere}\n"] }]
|
|
4840
|
+
args: [{ selector: 'nh-assistant-admin-mcp-server-editor', standalone: true, imports: [TranslatePipe, _NhAssistantTranslatePipe, _NhAssistantIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<form class=\"section card\" [attr.aria-labelledby]=\"id + '-title'\" [attr.aria-busy]=\"saving()\" autocomplete=\"off\" (submit)=\"$event.preventDefault(); save()\">\n <h3 [id]=\"id + '-title'\">\n @if (server(); as current) {\n {{ 'nh-assistant.admin.server-editor.edit-title' | translate: { name: current.displayName } }}\n } @else {\n {{ 'nh-assistant.admin.server-editor.new-title' | translate }}\n }\n </h3>\n\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-id'\">{{ 'nh-assistant.admin.server-editor.id' | translate }}</label>\n <input\n type=\"text\"\n class=\"mono\"\n [id]=\"id + '-id'\"\n [maxLength]=\"limits.id\"\n [value]=\"serverId()\"\n [disabled]=\"!isNew()\"\n [attr.aria-describedby]=\"describedBy(id + '-id-hint', 'id')\"\n [attr.aria-invalid]=\"(submitted() && idInvalid()) || serverInvalid('id') ? true : null\"\n (input)=\"serverId.set(value($event)); touch('id')\" />\n <p class=\"hint\" [id]=\"id + '-id-hint'\">\n <span [class.error-text]=\"submitted() && idInvalid()\">\n {{ (submitted() && idInvalid() ? 'nh-assistant.admin.invalid-id' : 'nh-assistant.admin.server-editor.id-hint') | translate }}\n </span>\n </p>\n @if (serverInvalid('id')) {\n <p class=\"field-error\" [id]=\"errorId('id')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('id') | nhAssistantTranslate }}</p>\n }\n </div>\n\n <div class=\"field\">\n <label [for]=\"id + '-name'\">{{ 'nh-assistant.admin.server-editor.display-name' | translate }}</label>\n <input\n type=\"text\"\n [id]=\"id + '-name'\"\n [maxLength]=\"limits.displayName\"\n [value]=\"displayName()\"\n [attr.aria-invalid]=\"(submitted() && nameMissing()) || serverInvalid('displayName') ? true : null\"\n [attr.aria-describedby]=\"describedBy(id + '-name-hint', 'displayName')\"\n (input)=\"displayName.set(value($event)); touch('displayName')\" />\n <p class=\"hint\" [id]=\"id + '-name-hint'\">\n @if (submitted() && nameMissing()) {\n <span class=\"error-text\">{{ 'nh-assistant.admin.required' | translate }}</span>\n }\n </p>\n @if (serverInvalid('displayName')) {\n <p class=\"field-error\" [id]=\"errorId('displayName')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('displayName') | nhAssistantTranslate }}</p>\n }\n </div>\n </div>\n\n <div class=\"field\">\n <label [for]=\"id + '-url'\">{{ 'nh-assistant.admin.server-editor.url' | translate }}</label>\n <input\n type=\"url\"\n class=\"mono\"\n inputmode=\"url\"\n [id]=\"id + '-url'\"\n [maxLength]=\"limits.url\"\n [value]=\"url()\"\n [attr.aria-describedby]=\"describedBy(id + '-url-hint', 'url')\"\n [attr.aria-invalid]=\"(submitted() && urlInvalid()) || serverInvalid('url') ? true : null\"\n (input)=\"onUrlInput($event); touch('url')\" />\n <p class=\"hint\" [id]=\"id + '-url-hint'\">\n <span [class.error-text]=\"submitted() && urlInvalid()\">\n {{ (submitted() && urlInvalid() ? 'nh-assistant.admin.server-editor.url-invalid' : 'nh-assistant.admin.server-editor.url-hint') | translate }}\n </span>\n </p>\n @if (serverInvalid('url')) {\n <p class=\"field-error\" [id]=\"errorId('url')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('url') | nhAssistantTranslate }}</p>\n }\n </div>\n\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-auth'\">{{ 'nh-assistant.admin.server-editor.auth-mode' | translate }}</label>\n <select\n [id]=\"id + '-auth'\"\n [attr.aria-describedby]=\"describedBy(null, 'authMode')\"\n [attr.aria-invalid]=\"serverInvalid('authMode') ? true : null\"\n (change)=\"authMode.set($any(value($event))); touch('authMode')\">\n @for (mode of authModes(); track mode) {\n <option [value]=\"mode\" [selected]=\"authMode() === mode\">{{ ['nh-assistant.admin.mcp.auth.' + mode] | nhAssistantTranslate: mode }}</option>\n }\n </select>\n @if (customOwnership(); as ownership) {\n <p class=\"hint\" data-custom-auth-mode>{{ 'nh-assistant.admin.server-editor.custom-auth.' + ownership | translate }}</p>\n }\n @if (serverInvalid('authMode')) {\n <p class=\"field-error\" [id]=\"errorId('authMode')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('authMode') | nhAssistantTranslate }}</p>\n }\n </div>\n\n @if (authMode() === 'api-key') {\n <div class=\"field\">\n <label [for]=\"id + '-header'\">{{ 'nh-assistant.admin.server-editor.header-name' | translate }}</label>\n <input\n type=\"text\"\n class=\"mono\"\n placeholder=\"X-Api-Key\"\n [id]=\"id + '-header'\"\n [maxLength]=\"limits.headerName\"\n [value]=\"headerName()\"\n [attr.aria-describedby]=\"describedBy(id + '-header-hint', 'headerName')\"\n [attr.aria-invalid]=\"(submitted() && headerNameInvalid()) || serverInvalid('headerName') ? true : null\"\n (input)=\"headerName.set(value($event)); touch('headerName')\" />\n <p class=\"hint\" [id]=\"id + '-header-hint'\">\n <span [class.error-text]=\"submitted() && headerNameInvalid()\">\n {{ (submitted() && headerNameInvalid() ? 'nh-assistant.admin.server-editor.header-name-invalid' : 'nh-assistant.admin.server-editor.header-name-hint') | translate }}\n </span>\n </p>\n @if (serverInvalid('headerName')) {\n <p class=\"field-error\" [id]=\"errorId('headerName')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('headerName') | nhAssistantTranslate }}</p>\n }\n </div>\n }\n\n @if (authMode() === 'basic') {\n <div class=\"field\">\n <label [for]=\"id + '-user'\">{{ 'nh-assistant.admin.server-editor.user-name' | translate }}</label>\n <input\n type=\"text\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n [id]=\"id + '-user'\"\n [maxLength]=\"limits.userName\"\n [value]=\"userName()\"\n [attr.aria-describedby]=\"describedBy(id + '-user-hint', 'userName')\"\n [attr.aria-invalid]=\"(submitted() && userNameInvalid()) || serverInvalid('userName') ? true : null\"\n (input)=\"userName.set(value($event)); touch('userName')\" />\n <p class=\"hint\" [id]=\"id + '-user-hint'\">\n <span [class.error-text]=\"submitted() && userNameInvalid()\">\n {{ (submitted() && userNameInvalid() ? 'nh-assistant.admin.server-editor.user-name-invalid' : 'nh-assistant.admin.server-editor.user-name-hint') | translate }}\n </span>\n </p>\n @if (serverInvalid('userName')) {\n <p class=\"field-error\" [id]=\"errorId('userName')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('userName') | nhAssistantTranslate }}</p>\n }\n </div>\n }\n </div>\n\n @if (authMode() === 'forward-user-token') {\n <p class=\"message warning\" role=\"note\"><nh-assistant-icon name=\"warning\" />{{ 'nh-assistant.admin.server-editor.forward-warning' | translate }}</p>\n }\n\n @if (isOAuth()) {\n <fieldset class=\"field\">\n <legend>OAuth</legend>\n\n @if (authMode() === 'oauth') {\n <div\n class=\"inline\"\n role=\"radiogroup\"\n [attr.aria-label]=\"'nh-assistant.admin.server-editor.connection-scope' | translate\"\n [attr.aria-describedby]=\"describedBy(id + '-scope-hint', 'connectionScope')\">\n @for (scope of connectionScopes; track scope) {\n <label class=\"check\">\n <input\n type=\"radio\"\n [name]=\"id + '-connection-scope'\"\n [value]=\"scope\"\n [checked]=\"connectionScope() === scope\"\n [attr.aria-invalid]=\"serverInvalid('connectionScope') ? true : null\"\n (change)=\"connectionScope.set(scope); touch('connectionScope')\" />\n <span>{{ 'nh-assistant.admin.mcp.connection-scope.' + scope | translate }}</span>\n </label>\n }\n </div>\n <p class=\"hint\" [id]=\"id + '-scope-hint'\">{{ 'nh-assistant.admin.server-editor.connection-scope-hint' | translate }}</p>\n @if (serverInvalid('connectionScope')) {\n <p class=\"field-error\" [id]=\"errorId('connectionScope')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('connectionScope') | nhAssistantTranslate }}</p>\n }\n }\n\n @if (isOrganizationOAuth()) {\n <p class=\"message warning\" role=\"note\"><nh-assistant-icon name=\"warning\" />{{ 'nh-assistant.admin.server-editor.oauth-warning' | translate }}</p>\n <label [for]=\"id + '-account-label'\">{{ 'nh-assistant.admin.server-editor.organization-account-label' | translate }}</label>\n <input\n type=\"text\"\n [id]=\"id + '-account-label'\"\n [maxLength]=\"limits.organizationAccountLabel\"\n [value]=\"organizationAccountLabel()\"\n [attr.aria-describedby]=\"describedBy(id + '-account-label-hint', 'oauth.organizationAccountLabel')\"\n [attr.aria-invalid]=\"(submitted() && labelMissing()) || serverInvalid('oauth.organizationAccountLabel') ? true : null\"\n (input)=\"organizationAccountLabel.set(value($event)); touch('oauth.organizationAccountLabel')\" />\n <p class=\"hint\" [id]=\"id + '-account-label-hint'\">\n <span [class.error-text]=\"submitted() && labelMissing()\">\n {{ (submitted() && labelMissing() ? 'nh-assistant.admin.server-editor.organization-account-label-required' : 'nh-assistant.admin.server-editor.organization-account-label-hint') | translate }}\n </span>\n </p>\n @if (serverInvalid('oauth.organizationAccountLabel')) {\n <p class=\"field-error\" [id]=\"errorId('oauth.organizationAccountLabel')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('oauth.organizationAccountLabel') | nhAssistantTranslate }}</p>\n }\n }\n\n <div class=\"meta\">\n @if (server()?.authorization; as confirmed) {\n @if (!needsConfirmation()) {\n <span class=\"badge success\">{{ 'nh-assistant.admin.server-editor.confirmed-authorization' | translate: { issuer: confirmed.issuer } }}</span>\n }\n }\n <button class=\"secondary\" type=\"button\" [disabled]=\"discovering() || urlInvalid()\" (click)=\"discover()\">\n <nh-assistant-icon name=\"link\" />{{ (discovering() ? 'nh-assistant.admin.server-editor.discovering' : 'nh-assistant.admin.server-editor.discover') | translate }}\n </button>\n </div>\n @if (discoveryError(); as failure) {\n <p class=\"message error\" role=\"alert\"><nh-assistant-icon name=\"warning\" />{{ discoveryErrorKeys() | nhAssistantTranslate: failure.code }}</p>\n }\n @if (discovery(); as found) {\n <div class=\"discovery\" role=\"group\" [attr.aria-label]=\"'nh-assistant.admin.server-editor.discovery-title' | translate\">\n <dl>\n <dt>{{ 'nh-assistant.admin.server-editor.discovery-issuer' | translate }}</dt>\n <dd class=\"mono\">{{ found.issuer }}</dd>\n <dt>{{ 'nh-assistant.admin.server-editor.discovery-resource' | translate }}</dt>\n <dd class=\"mono\">{{ found.resource }}</dd>\n @if (found.scopesSupported.length > 0) {\n <dt>{{ 'nh-assistant.admin.server-editor.discovery-scopes' | translate }}</dt>\n <dd class=\"mono\">{{ found.scopesSupported.join(' ') }}</dd>\n }\n @if (authMode() === 'oauth' && registration(); as kind) {\n <dt>{{ 'nh-assistant.admin.server-editor.discovery-registration' | translate }}</dt>\n <dd>{{ 'nh-assistant.admin.server-editor.registration-' + kind | translate }}</dd>\n }\n </dl>\n @if (pkceMissing()) {\n <p class=\"message error\" role=\"alert\"><nh-assistant-icon name=\"warning\" />{{ 'nh-assistant.admin.server-editor.pkce-missing' | translate }}</p>\n } @else {\n <label class=\"check\">\n <input\n type=\"checkbox\"\n [checked]=\"confirmAuthorization()\"\n [attr.aria-describedby]=\"describedBy(id + '-confirm-hint', 'oauth.confirmAuthorization')\"\n [attr.aria-invalid]=\"serverInvalid('oauth.confirmAuthorization') ? true : null\"\n (change)=\"confirmAuthorization.set(checked($event)); touch('oauth.confirmAuthorization')\" />\n <span>{{ 'nh-assistant.admin.server-editor.confirm-authorization' | translate }}</span>\n </label>\n <p class=\"hint\" [id]=\"id + '-confirm-hint'\">{{ 'nh-assistant.admin.server-editor.confirm-authorization-hint' | translate }}</p>\n }\n </div>\n }\n @if (submitted() && confirmMissing()) {\n <p class=\"hint\"><span class=\"error-text\">{{ 'nh-assistant.admin.server-editor.confirm-required' | translate }}</span></p>\n } @else if (serverInvalid('oauth.confirmAuthorization')) {\n <p class=\"field-error\" [id]=\"errorId('oauth.confirmAuthorization')\"><nh-assistant-icon name=\"warning\" />{{ 'nh-assistant.admin.server-editor.confirm-required' | translate }}</p>\n }\n\n <label [for]=\"id + '-scopes'\">{{ 'nh-assistant.admin.server-editor.scopes' | translate }}</label>\n <input\n type=\"text\"\n class=\"mono\"\n spellcheck=\"false\"\n [id]=\"id + '-scopes'\"\n [maxLength]=\"limits.scopes\"\n [value]=\"oauthScopes()\"\n [attr.aria-describedby]=\"describedBy(id + '-scopes-hint', 'oauth.scopes')\"\n [attr.aria-invalid]=\"(submitted() && scopesInvalid()) || serverInvalid('oauth.scopes') ? true : null\"\n (input)=\"oauthScopes.set(value($event)); touch('oauth.scopes')\" />\n <p class=\"hint\" [id]=\"id + '-scopes-hint'\">\n <span [class.error-text]=\"submitted() && scopesInvalid()\">\n {{ (submitted() && scopesInvalid() ? 'nh-assistant.admin.server-editor.scopes-invalid' : 'nh-assistant.admin.server-editor.scopes-hint') | translate }}\n </span>\n </p>\n @if (serverInvalid('oauth.scopes')) {\n <p class=\"field-error\" [id]=\"errorId('oauth.scopes')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('oauth.scopes') | nhAssistantTranslate }}</p>\n }\n\n <label [for]=\"id + '-client-id'\">{{ 'nh-assistant.admin.server-editor.client-id' | translate }}</label>\n <input\n type=\"text\"\n class=\"mono\"\n spellcheck=\"false\"\n [id]=\"id + '-client-id'\"\n [maxLength]=\"limits.clientId\"\n [value]=\"oauthClientId()\"\n [attr.aria-describedby]=\"describedBy(id + '-client-id-hint', 'oauth.clientId')\"\n [attr.aria-invalid]=\"(submitted() && clientIdMissing()) || serverInvalid('oauth.clientId') ? true : null\"\n (input)=\"oauthClientId.set(value($event)); touch('oauth.clientId')\" />\n <p class=\"hint\" [id]=\"id + '-client-id-hint'\">\n <span [class.error-text]=\"submitted() && clientIdMissing()\">\n {{ (submitted() && clientIdMissing() ? 'nh-assistant.admin.server-editor.client-id-required' : 'nh-assistant.admin.server-editor.client-id-hint') | translate }}\n </span>\n </p>\n @if (serverInvalid('oauth.clientId')) {\n <p class=\"field-error\" [id]=\"errorId('oauth.clientId')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('oauth.clientId') | nhAssistantTranslate }}</p>\n }\n\n @if (oauthClientId().trim().length > 0) {\n <div class=\"inline\" role=\"radiogroup\" [attr.aria-label]=\"'nh-assistant.admin.server-editor.client-secret' | translate\">\n @for (source of secretSources; track source) {\n <label class=\"check\">\n <input\n type=\"radio\"\n [name]=\"id + '-oauth-secret-source'\"\n [value]=\"source\"\n [checked]=\"oauthSecretSource() === source\"\n (change)=\"setOAuthSecretSource(source)\" />\n <span>{{ 'nh-assistant.admin.server-editor.secret-source-' + source | translate }}</span>\n </label>\n }\n </div>\n @if (usesOAuthReference()) {\n <label [for]=\"id + '-oauth-reference'\">{{ 'nh-assistant.admin.server-editor.client-secret-reference' | translate }}</label>\n <input\n type=\"text\"\n class=\"mono\"\n spellcheck=\"false\"\n placeholder=\"config:planning-client-secret\"\n [id]=\"id + '-oauth-reference'\"\n [maxLength]=\"limits.secretReference\"\n [value]=\"oauthSecretReference()\"\n [attr.aria-describedby]=\"describedBy(null, 'oauth.clientSecretReference')\"\n [attr.aria-invalid]=\"(submitted() && oauthReferenceInvalid()) || serverInvalid('oauth.clientSecretReference') ? true : null\"\n (input)=\"oauthSecretReference.set(value($event)); touch('oauth.clientSecretReference')\" />\n @if (submitted() && oauthReferenceInvalid()) {\n <p class=\"hint\"><span class=\"error-text\">{{ 'nh-assistant.admin.server-editor.secret-reference-invalid' | translate }}</span></p>\n }\n @if (serverInvalid('oauth.clientSecretReference')) {\n <p class=\"field-error\" [id]=\"errorId('oauth.clientSecretReference')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('oauth.clientSecretReference') | nhAssistantTranslate }}</p>\n }\n } @else {\n @if (hasOAuthSecret() && oauthSecretAction() === 'keep') {\n <div class=\"meta secret-state\">\n <span class=\"badge success\">{{ 'nh-assistant.admin.server-editor.secret-set' | translate }}</span>\n <button class=\"secondary\" type=\"button\" (click)=\"oauthSecretAction.set('replace')\">{{ 'nh-assistant.admin.server-editor.secret-replace' | translate }}</button>\n </div>\n }\n @if (showOAuthSecretInput()) {\n <label [for]=\"id + '-oauth-secret'\">{{ 'nh-assistant.admin.server-editor.client-secret' | translate }}</label>\n <input\n type=\"password\"\n class=\"mono\"\n autocomplete=\"new-password\"\n spellcheck=\"false\"\n [id]=\"id + '-oauth-secret'\"\n [maxLength]=\"limits.clientSecret\"\n [attr.aria-describedby]=\"describedBy(id + '-oauth-secret-hint', 'oauth.clientSecret')\"\n [attr.aria-invalid]=\"serverInvalid('oauth.clientSecret') ? true : null\"\n [value]=\"oauthSecret()\"\n (input)=\"onOAuthSecretInput($event); touch('oauth.clientSecret')\" />\n }\n <p class=\"hint\" [id]=\"id + '-oauth-secret-hint'\">{{ 'nh-assistant.admin.server-editor.client-secret-hint' | translate }}</p>\n }\n }\n @if (serverInvalid('oauth.clientSecret')) {\n <p class=\"field-error\" [id]=\"errorId('oauth.clientSecret')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('oauth.clientSecret') | nhAssistantTranslate }}</p>\n }\n </fieldset>\n }\n\n @if (usesSecret()) {\n <fieldset class=\"field\">\n <legend>{{ 'nh-assistant.admin.server-editor.secret' | translate }}</legend>\n <div class=\"inline\" role=\"radiogroup\" [attr.aria-label]=\"'nh-assistant.admin.server-editor.secret-source' | translate\">\n @for (source of secretSources; track source) {\n <label class=\"check\">\n <input\n type=\"radio\"\n [name]=\"id + '-secret-source'\"\n [value]=\"source\"\n [checked]=\"secretSource() === source\"\n (change)=\"setSecretSource(source)\" />\n <span>{{ 'nh-assistant.admin.server-editor.secret-source-' + source | translate }}</span>\n </label>\n }\n </div>\n @if (usesReference()) {\n <label [for]=\"id + '-secret-reference'\">{{ 'nh-assistant.admin.server-editor.secret-reference' | translate }}</label>\n <input\n type=\"text\"\n class=\"mono\"\n spellcheck=\"false\"\n placeholder=\"config:planning-token\"\n [id]=\"id + '-secret-reference'\"\n [maxLength]=\"limits.secretReference\"\n [value]=\"secretReference()\"\n [attr.aria-describedby]=\"describedBy(id + '-secret-reference-hint', 'secretReference')\"\n [attr.aria-invalid]=\"(submitted() && secretReferenceInvalid()) || serverInvalid('secretReference') ? true : null\"\n (input)=\"secretReference.set(value($event)); touch('secretReference')\" />\n <p class=\"hint\" [id]=\"id + '-secret-reference-hint'\">\n <span [class.error-text]=\"submitted() && secretReferenceInvalid()\">\n {{ (submitted() && secretReferenceInvalid() ? 'nh-assistant.admin.server-editor.secret-reference-invalid' : 'nh-assistant.admin.server-editor.secret-reference-hint') | translate }}\n </span>\n </p>\n @if (serverInvalid('secretReference')) {\n <p class=\"field-error\" [id]=\"errorId('secretReference')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('secretReference') | nhAssistantTranslate }}</p>\n }\n } @else if (hasSecret()) {\n <div class=\"meta secret-state\">\n @switch (secretAction()) {\n @case ('clear') {\n <span class=\"badge warning\">{{ 'nh-assistant.admin.server-editor.secret-will-clear' | translate }}</span>\n <button class=\"secondary\" type=\"button\" (click)=\"setSecretAction('keep')\">{{ 'nh-assistant.admin.server-editor.secret-undo' | translate }}</button>\n }\n @case ('replace') {\n <span class=\"badge\">{{ 'nh-assistant.admin.server-editor.secret-set' | translate }}</span>\n <button class=\"secondary\" type=\"button\" (click)=\"setSecretAction('keep')\">{{ 'nh-assistant.admin.server-editor.secret-undo' | translate }}</button>\n }\n @default {\n <span class=\"badge success\">{{ 'nh-assistant.admin.server-editor.secret-set' | translate }}</span>\n <!-- A mode with a secret always needs one: it is replaced, or removed by choosing a reference or another mode. -->\n <button class=\"secondary\" type=\"button\" (click)=\"setSecretAction('replace')\">{{ 'nh-assistant.admin.server-editor.secret-replace' | translate }}</button>\n }\n }\n </div>\n } @else {\n <p class=\"muted\">{{ 'nh-assistant.admin.server-editor.secret-none' | translate }}</p>\n }\n @if (showSecretInput()) {\n <label class=\"visually-hidden\" [for]=\"id + '-secret'\">{{ 'nh-assistant.admin.server-editor.secret-new' | translate }}</label>\n <input\n type=\"password\"\n class=\"mono\"\n autocomplete=\"new-password\"\n spellcheck=\"false\"\n [id]=\"id + '-secret'\"\n [maxLength]=\"limits.secret\"\n [attr.placeholder]=\"'nh-assistant.admin.server-editor.secret-new' | translate\"\n [attr.aria-describedby]=\"describedBy(id + '-secret-hint', 'secret')\"\n [attr.aria-invalid]=\"serverInvalid('secret') ? true : null\"\n [value]=\"secret()\"\n (input)=\"onSecretInput($event); touch('secret')\" />\n }\n @if (!usesReference()) {\n <p class=\"hint\" [id]=\"id + '-secret-hint'\">{{ 'nh-assistant.admin.server-editor.secret-hint' | translate }}</p>\n }\n @if (serverInvalid('secret')) {\n <p class=\"field-error\" [id]=\"errorId('secret')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('secret') | nhAssistantTranslate }}</p>\n }\n </fieldset>\n }\n\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-certificate'\">{{ 'nh-assistant.admin.server-editor.client-certificate' | translate }}</label>\n <input\n type=\"text\"\n class=\"mono\"\n spellcheck=\"false\"\n [id]=\"id + '-certificate'\"\n [maxLength]=\"limits.clientCertificateReference\"\n [value]=\"clientCertificateReference()\"\n [attr.aria-describedby]=\"describedBy(id + '-certificate-hint', 'clientCertificateReference')\"\n [attr.aria-invalid]=\"(submitted() && certificateInvalid()) || serverInvalid('clientCertificateReference') ? true : null\"\n (input)=\"clientCertificateReference.set(value($event)); touch('clientCertificateReference')\" />\n <p class=\"hint\" [id]=\"id + '-certificate-hint'\">\n <span [class.error-text]=\"submitted() && certificateInvalid()\">\n {{ (submitted() && certificateInvalid() ? 'nh-assistant.admin.server-editor.client-certificate-invalid' : 'nh-assistant.admin.server-editor.client-certificate-hint') | translate }}\n </span>\n </p>\n @if (serverInvalid('clientCertificateReference')) {\n <p class=\"field-error\" [id]=\"errorId('clientCertificateReference')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('clientCertificateReference') | nhAssistantTranslate }}</p>\n }\n </div>\n\n <div class=\"field\">\n <label [for]=\"id + '-transport'\">{{ 'nh-assistant.admin.server-editor.transport' | translate }}</label>\n <select\n [id]=\"id + '-transport'\"\n [attr.aria-describedby]=\"describedBy(id + '-transport-hint', 'transport')\"\n [attr.aria-invalid]=\"serverInvalid('transport') ? true : null\"\n (change)=\"transport.set($any(value($event))); touch('transport')\">\n @for (option of transports; track option) {\n <option [value]=\"option\" [selected]=\"transport() === option\">{{ 'nh-assistant.admin.mcp.transport.' + option | translate }}</option>\n }\n </select>\n <p class=\"hint\" [id]=\"id + '-transport-hint'\">{{ 'nh-assistant.admin.server-editor.transport-hint' | translate }}</p>\n @if (serverInvalid('transport')) {\n <p class=\"field-error\" [id]=\"errorId('transport')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('transport') | nhAssistantTranslate }}</p>\n }\n </div>\n </div>\n\n <div class=\"field\">\n <label [for]=\"id + '-policy'\">{{ 'nh-assistant.admin.server-editor.required-policy' | translate }}</label>\n <input\n type=\"text\"\n class=\"mono\"\n [id]=\"id + '-policy'\"\n [maxLength]=\"limits.requiredPolicy\"\n [value]=\"requiredPolicy()\"\n [attr.aria-describedby]=\"describedBy(id + '-policy-hint', 'requiredPolicy')\"\n [attr.aria-invalid]=\"serverInvalid('requiredPolicy') ? true : null\"\n (input)=\"requiredPolicy.set(value($event)); touch('requiredPolicy')\" />\n <p class=\"hint\" [id]=\"id + '-policy-hint'\">{{ 'nh-assistant.admin.server-editor.required-policy-hint' | translate }}</p>\n @if (serverInvalid('requiredPolicy')) {\n <p class=\"field-error\" [id]=\"errorId('requiredPolicy')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('requiredPolicy') | nhAssistantTranslate }}</p>\n }\n </div>\n\n <div class=\"field\">\n <label [for]=\"id + '-headless'\">{{ 'nh-assistant.admin.server-editor.headless-use' | translate }}</label>\n <select\n [id]=\"id + '-headless'\"\n [attr.aria-describedby]=\"describedBy(id + '-headless-hint', 'headlessUse')\"\n [attr.aria-invalid]=\"serverInvalid('headlessUse') ? true : null\"\n (change)=\"headlessUse.set($any(value($event))); touch('headlessUse')\">\n @for (use of headlessUses(); track use) {\n <option [value]=\"use\" [selected]=\"effectiveHeadlessUse() === use\">{{ 'nh-assistant.admin.mcp.headless.' + use | translate }}</option>\n }\n </select>\n <p class=\"hint\" [id]=\"id + '-headless-hint'\">\n {{ (personalCredential() ? 'nh-assistant.admin.server-editor.headless-use-personal' : 'nh-assistant.admin.server-editor.headless-use-hint') | translate }}\n </p>\n @if (serverInvalid('headlessUse')) {\n <p class=\"field-error\" [id]=\"errorId('headlessUse')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('headlessUse') | nhAssistantTranslate }}</p>\n }\n </div>\n\n @if (!isNew()) {\n <fieldset class=\"field readers\">\n <legend>{{ 'nh-assistant.admin.server-editor.reader.title' | translate }}</legend>\n <p class=\"hint\">{{ 'nh-assistant.admin.server-editor.reader.hint' | translate }}</p>\n @if (serverInvalid('readers')) {\n <p class=\"field-error\" [id]=\"errorId('readers')\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys('readers') | nhAssistantTranslate }}</p>\n }\n\n @for (reader of readers(); track reader.key; let index = $index) {\n <fieldset class=\"subsection reader\" [attr.data-reader]=\"index\">\n <legend class=\"reader-title\">{{ 'nh-assistant.admin.server-editor.reader.item' | translate: { number: index + 1 } }}</legend>\n\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-reader-tool-' + reader.key\">{{ 'nh-assistant.admin.server-editor.reader.tool' | translate }}</label>\n <select\n [id]=\"id + '-reader-tool-' + reader.key\"\n [attr.aria-describedby]=\"describedBy(id + '-reader-tool-hint-' + reader.key, readerField(index, 'toolName'))\"\n [attr.aria-invalid]=\"(submitted() && (readerProblems()[index].tool || readerProblems()[index].duplicate)) || serverInvalid(readerField(index, 'toolName')) ? true : null\"\n (change)=\"updateReader(index, 'toolName', value($event))\">\n <option value=\"\" [selected]=\"reader.toolName === ''\">{{ 'nh-assistant.admin.server-editor.reader.tool-choose' | translate }}</option>\n @for (name of readerToolNames(); track name) {\n <option [value]=\"name\" [selected]=\"reader.toolName === name\">{{ name }}</option>\n }\n </select>\n <p class=\"hint\" [id]=\"id + '-reader-tool-hint-' + reader.key\">\n @if (submitted() && readerProblems()[index].tool) {\n <span class=\"error-text\">{{ 'nh-assistant.admin.required' | translate }}</span>\n } @else if (submitted() && readerProblems()[index].duplicate) {\n <span class=\"error-text\">{{ 'nh-assistant.admin.server-editor.reader.tool-duplicate' | translate }}</span>\n } @else {\n {{ 'nh-assistant.admin.server-editor.reader.tool-hint' | translate }}\n }\n </p>\n @if (serverInvalid(readerField(index, 'toolName'))) {\n <p class=\"field-error\" [id]=\"errorId(readerField(index, 'toolName'))\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys(readerField(index, 'toolName')) | nhAssistantTranslate }}</p>\n }\n </div>\n\n <div class=\"field\">\n <label [for]=\"id + '-reader-classification-' + reader.key\">{{ 'nh-assistant.admin.server-editor.reader.max-classification' | translate }}</label>\n <select\n [id]=\"id + '-reader-classification-' + reader.key\"\n [attr.aria-describedby]=\"describedBy(id + '-reader-classification-hint-' + reader.key, readerField(index, 'maxClassification'))\"\n [attr.aria-invalid]=\"(submitted() && readerProblems()[index].classification) || serverInvalid(readerField(index, 'maxClassification')) ? true : null\"\n (change)=\"updateReader(index, 'maxClassification', value($event))\">\n <option value=\"\" [selected]=\"reader.maxClassification === ''\">{{ 'nh-assistant.admin.server-editor.reader.max-classification-choose' | translate }}</option>\n @for (classification of classifications; track classification) {\n <option [value]=\"classification\" [selected]=\"reader.maxClassification === classification\">\n {{ 'nh-assistant.admin.run-config.skills.classification.' + classification | translate }}\n </option>\n }\n </select>\n <p class=\"hint\" [id]=\"id + '-reader-classification-hint-' + reader.key\">\n @if (submitted() && readerProblems()[index].classification) {\n <span class=\"error-text\">{{ 'nh-assistant.admin.required' | translate }}</span>\n } @else {\n {{ 'nh-assistant.admin.server-editor.reader.max-classification-hint' | translate }}\n }\n </p>\n @if (serverInvalid(readerField(index, 'maxClassification'))) {\n <p class=\"field-error\" [id]=\"errorId(readerField(index, 'maxClassification'))\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys(readerField(index, 'maxClassification')) | nhAssistantTranslate }}</p>\n }\n </div>\n\n <div class=\"field\">\n <label [for]=\"id + '-reader-types-' + reader.key\">{{ 'nh-assistant.admin.server-editor.reader.media-types' | translate }}</label>\n <input\n type=\"text\"\n class=\"mono\"\n spellcheck=\"false\"\n [id]=\"id + '-reader-types-' + reader.key\"\n [value]=\"reader.mediaTypes\"\n [attr.aria-describedby]=\"describedBy(id + '-reader-types-hint-' + reader.key, readerField(index, 'mediaTypes'))\"\n [attr.aria-invalid]=\"(submitted() && readerProblems()[index].types) || serverInvalid(readerField(index, 'mediaTypes')) ? true : null\"\n (input)=\"updateReader(index, 'mediaTypes', value($event))\" />\n <p class=\"hint\" [id]=\"id + '-reader-types-hint-' + reader.key\">\n <span [class.error-text]=\"submitted() && readerProblems()[index].types\">\n {{ (submitted() && readerProblems()[index].types ? 'nh-assistant.admin.server-editor.reader.media-types-invalid' : 'nh-assistant.admin.server-editor.reader.media-types-hint') | translate }}\n </span>\n </p>\n @if (serverInvalid(readerField(index, 'mediaTypes'))) {\n <p class=\"field-error\" [id]=\"errorId(readerField(index, 'mediaTypes'))\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys(readerField(index, 'mediaTypes')) | nhAssistantTranslate }}</p>\n }\n </div>\n\n <div class=\"field\">\n <label [for]=\"id + '-reader-priority-' + reader.key\">{{ 'nh-assistant.admin.server-editor.reader.priority' | translate }}</label>\n <input\n type=\"number\"\n inputmode=\"numeric\"\n step=\"1\"\n [min]=\"readerPriority.min\"\n [max]=\"readerPriority.max\"\n [id]=\"id + '-reader-priority-' + reader.key\"\n [value]=\"reader.priority\"\n [attr.aria-describedby]=\"describedBy(id + '-reader-priority-hint-' + reader.key, readerField(index, 'priority'))\"\n [attr.aria-invalid]=\"(submitted() && readerProblems()[index].priority) || serverInvalid(readerField(index, 'priority')) ? true : null\"\n (input)=\"updateReader(index, 'priority', value($event))\" />\n <p class=\"hint\" [id]=\"id + '-reader-priority-hint-' + reader.key\">\n <span [class.error-text]=\"submitted() && readerProblems()[index].priority\">\n {{ (submitted() && readerProblems()[index].priority ? 'nh-assistant.admin.server-editor.reader.priority-invalid' : 'nh-assistant.admin.server-editor.reader.priority-hint') | translate: readerPriority }}\n </span>\n </p>\n @if (serverInvalid(readerField(index, 'priority'))) {\n <p class=\"field-error\" [id]=\"errorId(readerField(index, 'priority'))\"><nh-assistant-icon name=\"warning\" />{{ serverErrorKeys(readerField(index, 'priority')) | nhAssistantTranslate }}</p>\n }\n </div>\n </div>\n\n <div class=\"inline\">\n <button\n type=\"button\"\n class=\"secondary\"\n [attr.aria-label]=\"'nh-assistant.admin.server-editor.reader.remove-label' | translate: { number: index + 1 }\"\n (click)=\"removeReader(index)\">\n <nh-assistant-icon name=\"trash\" />{{ 'nh-assistant.admin.server-editor.reader.remove' | translate }}\n </button>\n </div>\n </fieldset>\n } @empty {\n <p class=\"muted\">{{ 'nh-assistant.admin.server-editor.reader.empty' | translate }}</p>\n }\n\n <div class=\"inline\">\n <button\n type=\"button\"\n class=\"secondary\"\n [id]=\"id + '-reader-add'\"\n [disabled]=\"readers().length >= maxReaders || readerToolNames().length === 0\"\n [attr.aria-describedby]=\"id + '-reader-add-hint'\"\n (click)=\"addReader()\">\n <nh-assistant-icon name=\"plus\" />{{ 'nh-assistant.admin.server-editor.reader.add' | translate }}\n </button>\n </div>\n <p class=\"hint\" [id]=\"id + '-reader-add-hint'\">{{ readerAddHint() | translate: { max: maxReaders } }}</p>\n @if (readers().length > 0) {\n <p class=\"message warning\" role=\"note\"><nh-assistant-icon name=\"warning\" />{{ 'nh-assistant.admin.server-editor.reader.leaves-application' | translate }}</p>\n }\n </fieldset>\n }\n\n <label class=\"check\">\n <input type=\"checkbox\" [checked]=\"isEnabled()\" (change)=\"isEnabled.set(checked($event))\" />\n <span>{{ 'nh-assistant.admin.server-editor.enabled' | translate }}</span>\n </label>\n\n @if (error(); as failure) {\n <div class=\"form-error\" role=\"alert\" data-server-error>\n <nh-assistant-icon name=\"warning\" />\n <div>\n <span>{{ errorKeys() | nhAssistantTranslate: failure.code }}</span>\n @if (otherServerErrors().length > 0) {\n <ul>\n @for (other of otherServerErrors(); track other.field) {\n <li><code class=\"mono\">{{ other.field }}</code>: {{ other.keys | nhAssistantTranslate }}</li>\n }\n </ul>\n }\n </div>\n </div>\n }\n\n <div class=\"actions\">\n <button class=\"secondary\" type=\"button\" (click)=\"cancelled.emit()\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n <button class=\"primary\" type=\"submit\" [disabled]=\"saving()\">\n {{ (saving() ? 'nh-assistant.admin.saving' : 'nh-assistant.admin.save') | translate }}\n </button>\n </div>\n</form>\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;min-width:0;color:var(--nha-text);font-family:var(--nha-font);font-size:14px}:host fieldset{min-width:0;margin:0;border:0;padding:0}:host legend,:host .label,:host .field>label{display:block;margin-bottom:6px;padding:0;color:var(--nha-text);font-size:12px;font-weight:700}:host input[type=text],:host input[type=url],:host input[type=password],:host input[type=search],:host input[type=email],:host input[type=number],:host input[type=date],:host input[type=time],:host input[type=datetime-local],:host select,:host textarea{box-sizing:border-box;width:100%;min-width:0;min-height:38px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:8px 10px;color:var(--nha-text);background:var(--nha-background);font:inherit;font-size:13px}:host input[type=text]:focus-visible,:host input[type=url]:focus-visible,:host input[type=password]:focus-visible,:host input[type=search]:focus-visible,:host input[type=email]:focus-visible,:host input[type=number]:focus-visible,:host input[type=date]:focus-visible,:host input[type=time]:focus-visible,:host input[type=datetime-local]:focus-visible,:host select:focus-visible,:host textarea:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host input[type=text]:disabled,:host input[type=url]:disabled,:host input[type=password]:disabled,:host input[type=search]:disabled,:host input[type=email]:disabled,:host input[type=number]:disabled,:host input[type=date]:disabled,:host input[type=time]:disabled,:host input[type=datetime-local]:disabled,:host select:disabled,:host textarea:disabled{cursor:not-allowed;opacity:.6}:host input[type=text][aria-invalid=true],:host input[type=url][aria-invalid=true],:host input[type=password][aria-invalid=true],:host input[type=search][aria-invalid=true],:host input[type=email][aria-invalid=true],:host input[type=number][aria-invalid=true],:host input[type=date][aria-invalid=true],:host input[type=time][aria-invalid=true],:host input[type=datetime-local][aria-invalid=true],:host select[aria-invalid=true],:host textarea[aria-invalid=true]{border-color:var(--nha-danger)}:host textarea{line-height:1.45;resize:vertical}:host input[type=number]{font-variant-numeric:tabular-nums}:host .mono{font-family:var(--nha-mono);font-size:12px}:host .field{display:flex;flex-direction:column;min-width:0}:host .hint{display:flex;justify-content:space-between;gap:8px;margin:4px 2px 0;color:var(--nha-text-muted);font-size:11px}:host .counter{flex:none;font-variant-numeric:tabular-nums}:host .error-text{color:var(--nha-danger)}:host .field-error{display:flex;align-items:flex-start;gap:6px;margin:4px 2px 0;color:var(--nha-danger);font-size:12px;font-weight:600;overflow-wrap:anywhere}:host .field-error nh-assistant-icon{flex:none;margin-top:1px}:host .form-error{box-sizing:border-box;display:flex;align-items:flex-start;gap:8px;margin:0;border:1px solid var(--nha-danger);border-radius:var(--nha-radius-control);padding:8px 12px;color:var(--nha-text);background:var(--nha-danger-soft);font-size:13px;overflow-wrap:anywhere}:host .form-error ul{margin:4px 0 0;padding-left:18px}:host .message{box-sizing:border-box;display:flex;align-items:center;gap:8px;margin:0;border:1px solid transparent;border-radius:var(--nha-radius-control);padding:8px 12px;font-size:13px;overflow-wrap:anywhere}:host .message.error{border-color:var(--nha-danger);color:var(--nha-text);background:var(--nha-danger-soft)}:host .message.warning{border-color:var(--nha-warning);color:var(--nha-text);background:var(--nha-warning-soft)}:host .message.success{border-color:var(--nha-success);color:var(--nha-text);background:var(--nha-success-soft)}:host .message.info{border-color:var(--nha-border);color:var(--nha-text);background:var(--nha-surface)}:host .message nh-assistant-icon{flex:none}:host button.link{min-height:0;border:0;padding:0;color:var(--nha-accent, currentColor);background:none;font:inherit;text-decoration:underline;cursor:pointer}:host button.link:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host button.link:disabled{cursor:not-allowed;opacity:.55}:host .primary,:host .secondary,:host .danger{display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;gap:6px;min-height:36px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px 14px;font:inherit;font-size:13px;font-weight:700;white-space:nowrap;cursor:pointer}:host .primary:focus-visible,:host .secondary:focus-visible,:host .danger:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .primary:disabled,:host .secondary:disabled,:host .danger:disabled{cursor:not-allowed;opacity:.55}:host .primary{border-color:var(--nha-accent);color:var(--nha-accent-contrast);background:var(--nha-accent)}:host .primary:hover:not(:disabled){filter:brightness(1.12)}:host .secondary{color:var(--nha-text);background:transparent}:host .secondary:hover:not(:disabled){background:var(--nha-surface-strong)}:host .danger{border-color:var(--nha-danger);color:var(--nha-danger);background:transparent}:host .danger:hover:not(:disabled){background:var(--nha-danger-soft)}:host .icon-button{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:1px solid transparent;border-radius:var(--nha-radius-control);color:var(--nha-text-muted);background:transparent;cursor:pointer}:host .icon-button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .icon-button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}:host .icon-button:disabled{cursor:not-allowed;opacity:.5}:host .actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px}:host .badge{display:inline-flex;align-items:center;gap:4px;border-radius:999px;padding:2px 8px;color:var(--nha-text-muted);background:var(--nha-surface-strong);font-size:11px;font-weight:700;white-space:nowrap}:host .badge.success{color:var(--nha-success);background:var(--nha-success-soft)}:host .badge.warning{color:var(--nha-warning);background:var(--nha-warning-soft)}:host .badge.danger{color:var(--nha-danger);background:var(--nha-danger-soft)}:host .visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}.section{display:flex;flex-direction:column;gap:16px;min-width:0}.section-header{display:flex;flex-wrap:wrap;align-items:flex-start;justify-content:space-between;gap:12px}.section-header>div{min-width:0;max-width:72ch}h3{margin:0 0 4px;font-size:17px;font-weight:750}h4{margin:0 0 8px;font-size:14px;font-weight:700}.muted{margin:0;color:var(--nha-text-muted);font-size:13px}.state{display:flex;flex-direction:column;align-items:flex-start;gap:10px;margin:0;border:1px dashed var(--nha-border);border-radius:var(--nha-radius);padding:20px;color:var(--nha-text-muted);font-size:13px}.state p{margin:0}.stack{display:flex;flex-direction:column;gap:14px;min-width:0}.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,240px),1fr));gap:14px}.meta{display:flex;flex-wrap:wrap;align-items:center;gap:8px 12px;color:var(--nha-text-muted);font-size:12px;overflow-wrap:anywhere}textarea.large{min-height:260px}.catalog-error{flex-wrap:wrap}.grow{flex:1;min-width:0}.subsection{display:flex;flex-direction:column;gap:8px;border-top:1px solid var(--nha-border);padding-top:14px}.history{display:grid;gap:6px;margin:0;padding:0;list-style:none}.history li{display:flex;flex-wrap:wrap;align-items:center;gap:6px 12px;font-size:12px}.cards{display:grid;gap:10px;margin:0;padding:0;list-style:none}.card{display:flex;flex-direction:column;gap:8px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius);padding:14px;background:var(--nha-background)}.card.inactive{opacity:.8}.card-header{display:flex;flex-wrap:wrap;align-items:center;gap:8px;min-width:0}.card-header strong{font-size:14px;overflow-wrap:anywhere}.card-actions{display:flex;flex-wrap:wrap;gap:8px}.check{display:flex;align-items:flex-start;gap:8px;font-size:13px;cursor:pointer}.check input{width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.check.described>span{display:grid;gap:2px;min-width:0;overflow-wrap:anywhere}.checks{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));gap:6px 12px;min-width:0;overflow-wrap:anywhere}.chips{display:flex;flex-wrap:wrap;gap:6px;margin:0;padding:0;list-style:none}.chip{display:inline-flex;align-items:center;gap:2px;border:1px solid var(--nha-border);border-radius:999px;padding:2px 2px 2px 10px;background:var(--nha-surface);font-family:var(--nha-mono);font-size:12px}.chip 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;width:26px;height:26px;border-radius:999px}.chip button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.chip button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}.chip button:disabled{cursor:not-allowed;opacity:.5}.inline{display:flex;flex-wrap:wrap;gap:8px}.inline>input{flex:1 1 180px}.catalog{display:grid;gap:4px;max-height:260px;margin:8px 0 0;overflow-y:auto;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px;list-style:none}.catalog li label{display:flex;flex-wrap:wrap;align-items:center;gap:6px 8px;border-radius:8px;padding:6px;cursor:pointer}.catalog li label:hover{background:var(--nha-surface)}.catalog .tool-id{font-family:var(--nha-mono);font-size:12px;overflow-wrap:anywhere}.skills{display:grid;gap:8px;margin:0;padding:0;list-style:none}.skill{display:grid;gap:6px;border-left:3px solid var(--nha-border);padding:4px 0 4px 12px}.uses{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));gap:8px}.use{display:flex;align-items:flex-start;gap:10px;min-width:0;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:10px 12px;cursor:pointer;overflow-wrap:anywhere}.use input{flex:none;width:16px;height:16px;margin:2px 0 0;accent-color:var(--nha-highlight)}.use>span{display:grid;gap:2px;min-width:0}.use strong{font-size:13px}.use.selected{border-color:var(--nha-highlight);background:var(--nha-surface)}.use:focus-within{outline:2px solid var(--nha-highlight);outline-offset:2px}.discovery{display:grid;gap:8px;padding:10px 12px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control)}.discovery dl{display:grid;grid-template-columns:max-content minmax(0,1fr);gap:4px 12px;margin:0}.discovery dt{color:var(--nha-text-muted)}.discovery dd{margin:0;overflow-wrap:anywhere}\n"] }]
|
|
4452
4841
|
}], ctorParameters: () => [], propDecorators: { server: [{ type: i0.Input, args: [{ isSignal: true, alias: "server", required: false }] }], saved: [{ type: i0.Output, args: ["saved"] }], cancelled: [{ type: i0.Output, args: ["cancelled"] }] } });
|
|
4453
4842
|
function isSecretReference(value) {
|
|
4454
4843
|
return secretReferencePattern.test(value) && !value.includes('..');
|
|
4455
4844
|
}
|
|
4845
|
+
/** The media types an administrator typed, separated by commas, spaces or new lines. */
|
|
4846
|
+
function splitMediaTypes(value) {
|
|
4847
|
+
return value.split(/[\s,]+/).map(item => item.trim()).filter(item => item.length > 0);
|
|
4848
|
+
}
|
|
4849
|
+
/**
|
|
4850
|
+
* The priority a reader's field holds: a whole number from -1000 to 1000, empty for 0, or `null` when
|
|
4851
|
+
* the server would refuse it.
|
|
4852
|
+
*/
|
|
4853
|
+
function parsePriority(value) {
|
|
4854
|
+
const trimmed = value.trim();
|
|
4855
|
+
if (trimmed.length === 0) {
|
|
4856
|
+
return 0;
|
|
4857
|
+
}
|
|
4858
|
+
const priority = Number(trimmed);
|
|
4859
|
+
return Number.isInteger(priority) && priority >= readerPriority.min && priority <= readerPriority.max ? priority : null;
|
|
4860
|
+
}
|
|
4861
|
+
/** Whether a value is a media type the server accepts: `type/subtype`, optionally with parameters. */
|
|
4862
|
+
function isMediaType(value) {
|
|
4863
|
+
const essence = value.split(';')[0].trim().toLowerCase();
|
|
4864
|
+
return essence.length <= 128 && mediaTypePattern.test(essence);
|
|
4865
|
+
}
|
|
4456
4866
|
function isAllowedUrl(value) {
|
|
4457
4867
|
try {
|
|
4458
4868
|
const url = new URL(value);
|