@newheap/platform-ai-chat 0.12.0 → 0.14.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/README.md +5 -0
- package/admin/index.d.ts +194 -5
- package/fesm2022/newheap-platform-ai-chat-admin.mjs +551 -20
- package/fesm2022/newheap-platform-ai-chat-admin.mjs.map +1 -1
- package/fesm2022/newheap-platform-ai-chat-agent-runs.mjs +2 -2
- package/fesm2022/newheap-platform-ai-chat-agent-runs.mjs.map +1 -1
- package/fesm2022/newheap-platform-ai-chat-testing.mjs +703 -6
- package/fesm2022/newheap-platform-ai-chat-testing.mjs.map +1 -1
- package/fesm2022/newheap-platform-ai-chat.mjs +631 -30
- package/fesm2022/newheap-platform-ai-chat.mjs.map +1 -1
- package/i18n/en.json +238 -4
- package/i18n/nl.json +238 -4
- package/index.d.ts +411 -6
- package/package.json +1 -1
- package/testing/index.d.ts +19 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { inject, signal, computed, DestroyRef, ChangeDetectionStrategy, Component, input, output, effect, untracked, Injector, ElementRef,
|
|
2
|
+
import { inject, signal, computed, DestroyRef, ChangeDetectionStrategy, Component, input, output, effect, untracked, Injector, ElementRef, afterNextRender, viewChild } from '@angular/core';
|
|
3
3
|
import { TranslatePipe } from '@ngx-translate/core';
|
|
4
4
|
import { NhAssistantAdminApiService, ɵnhAssistantErrorKeys as _nhAssistantErrorKeys, ɵtoNhAssistantError as _toNhAssistantError, NhAssistantClientErrorCodes, ɵNhAssistantIconComponent as _NhAssistantIconComponent, ɵNhAssistantTranslatePipe as _NhAssistantTranslatePipe, ɵNH_ASSISTANT_DASH_CASE as _NH_ASSISTANT_DASH_CASE, NhAssistantPanelService, NH_ASSISTANT_FEEDBACK_CATEGORIES, NhAssistantStore, nhAssistantErrorMessageKey } from '@newheap/platform-ai-chat';
|
|
5
5
|
import { DatePipe, DOCUMENT, DecimalPipe, PercentPipe } from '@angular/common';
|
|
@@ -7,7 +7,7 @@ import { forkJoin, Subscription } from 'rxjs';
|
|
|
7
7
|
|
|
8
8
|
/** Maximum length of the application context and of agent instructions. */
|
|
9
9
|
const NH_ASSISTANT_MAX_INSTRUCTIONS = 20_000;
|
|
10
|
-
let nextId$
|
|
10
|
+
let nextId$9 = 0;
|
|
11
11
|
/**
|
|
12
12
|
* Editor of the application context: the current version with its hash, optimistic
|
|
13
13
|
* concurrency through `expectedVersion`, and the version history.
|
|
@@ -15,7 +15,7 @@ let nextId$8 = 0;
|
|
|
15
15
|
class NhAssistantAdminContextComponent {
|
|
16
16
|
constructor() {
|
|
17
17
|
this.admin = inject(NhAssistantAdminApiService);
|
|
18
|
-
this.id = `nh-assistant-admin-context-${nextId$
|
|
18
|
+
this.id = `nh-assistant-admin-context-${nextId$9++}`;
|
|
19
19
|
this.max = NH_ASSISTANT_MAX_INSTRUCTIONS;
|
|
20
20
|
this.loading = signal(true, ...(ngDevMode ? [{ debugName: "loading" }] : []));
|
|
21
21
|
this.loadFailed = signal(false, ...(ngDevMode ? [{ debugName: "loadFailed" }] : []));
|
|
@@ -101,7 +101,7 @@ function nhAssistantSelectorMatches(toolId, selector) {
|
|
|
101
101
|
return new RegExp(`^${pattern}$`).test(toolId);
|
|
102
102
|
}
|
|
103
103
|
const selectorPattern = /^[a-z0-9*][a-z0-9.*_-]*$/i;
|
|
104
|
-
let nextId$
|
|
104
|
+
let nextId$8 = 0;
|
|
105
105
|
/**
|
|
106
106
|
* Creates or edits one agent: texts, autonomy, policy, tool selectors chosen from the tool
|
|
107
107
|
* catalog or typed as patterns, and assigned MCP servers.
|
|
@@ -115,7 +115,7 @@ class NhAssistantAdminAgentEditorComponent {
|
|
|
115
115
|
this.servers = input([], ...(ngDevMode ? [{ debugName: "servers" }] : []));
|
|
116
116
|
this.saved = output();
|
|
117
117
|
this.cancelled = output();
|
|
118
|
-
this.id = `nh-assistant-agent-editor-${nextId$
|
|
118
|
+
this.id = `nh-assistant-agent-editor-${nextId$8++}`;
|
|
119
119
|
this.max = NH_ASSISTANT_MAX_INSTRUCTIONS;
|
|
120
120
|
this.autonomyLevels = ['observe', 'explain', 'propose', 'simulate', 'execute'];
|
|
121
121
|
this.agentId = signal('', ...(ngDevMode ? [{ debugName: "agentId" }] : []));
|
|
@@ -245,7 +245,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImpo
|
|
|
245
245
|
const dayMs$1 = 24 * 60 * 60 * 1000;
|
|
246
246
|
const defaultReplayRuns = 100;
|
|
247
247
|
const maxReplayRuns = 500;
|
|
248
|
-
let nextId$
|
|
248
|
+
let nextId$7 = 0;
|
|
249
249
|
/**
|
|
250
250
|
* Compares a shadow variant of an agent with the live agent, for the agent's administrators: the
|
|
251
251
|
* runs that handled the same events, per tool the proposals both made, only one made and what
|
|
@@ -263,7 +263,7 @@ class NhAssistantAgentEvaluationComponent {
|
|
|
263
263
|
this.agentId = input.required(...(ngDevMode ? [{ debugName: "agentId" }] : []));
|
|
264
264
|
/** Its shadow variants; the first is compared until another is chosen. */
|
|
265
265
|
this.variants = input.required(...(ngDevMode ? [{ debugName: "variants" }] : []));
|
|
266
|
-
this.id = `nh-assistant-agent-evaluation-${nextId$
|
|
266
|
+
this.id = `nh-assistant-agent-evaluation-${nextId$7++}`;
|
|
267
267
|
this.periods = [7, 30, 90];
|
|
268
268
|
this.maxReplayRuns = maxReplayRuns;
|
|
269
269
|
this.canOpenThreads = this.panel !== null;
|
|
@@ -425,7 +425,7 @@ function periodRange$1(days) {
|
|
|
425
425
|
|
|
426
426
|
const dayMs = 24 * 60 * 60 * 1000;
|
|
427
427
|
const topCategoryCount = 3;
|
|
428
|
-
let nextId$
|
|
428
|
+
let nextId$6 = 0;
|
|
429
429
|
/**
|
|
430
430
|
* What people said when they rejected an agent's proposals, for the agent's administrators: per
|
|
431
431
|
* tool and agent version the decisions, the rejection rate, the share of rejections with feedback,
|
|
@@ -440,7 +440,7 @@ class NhAssistantAgentFeedbackReportComponent {
|
|
|
440
440
|
this.agentId = input.required(...(ngDevMode ? [{ debugName: "agentId" }] : []));
|
|
441
441
|
/** Feedback per page; the server allows at most 100. */
|
|
442
442
|
this.itemsPerPage = input(20, ...(ngDevMode ? [{ debugName: "itemsPerPage" }] : []));
|
|
443
|
-
this.id = `nh-assistant-agent-feedback-${nextId$
|
|
443
|
+
this.id = `nh-assistant-agent-feedback-${nextId$6++}`;
|
|
444
444
|
this.periods = [7, 30, 90];
|
|
445
445
|
this.categories = NH_ASSISTANT_FEEDBACK_CATEGORIES;
|
|
446
446
|
this.period = signal(30, ...(ngDevMode ? [{ debugName: "period" }] : []));
|
|
@@ -630,15 +630,427 @@ function invalidatedKinds(row) {
|
|
|
630
630
|
.sort((left, right) => right.count - left.count);
|
|
631
631
|
}
|
|
632
632
|
|
|
633
|
+
let nextId$5 = 0;
|
|
634
|
+
/** The longest reason the server stores with a change. */
|
|
635
|
+
const maxReason = 500;
|
|
636
|
+
const maxDescription = 300;
|
|
637
|
+
const maxInstructions = 20_000;
|
|
638
|
+
const maxMessage = 2_000;
|
|
639
|
+
const dashCase = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
640
|
+
/**
|
|
641
|
+
* The run configuration of one agent as data: the run settings of an agent created in the
|
|
642
|
+
* administration, its skills and triggers in code (read only) and as data, and the data that no
|
|
643
|
+
* longer fits the code. Administrators add and change data skills, live at once or as a candidate in
|
|
644
|
+
* shadow that they compare with the live version and promote or discard; they add data triggers on
|
|
645
|
+
* the application's events or on a schedule and put them live. New skills and triggers start in
|
|
646
|
+
* shadow; `changed` tells the agents page to reload the agent's skill states.
|
|
647
|
+
*/
|
|
648
|
+
class NhAssistantAgentRunConfigurationComponent {
|
|
649
|
+
constructor() {
|
|
650
|
+
this.admin = inject(NhAssistantAdminApiService);
|
|
651
|
+
this.injector = inject(Injector);
|
|
652
|
+
this.host = inject(ElementRef);
|
|
653
|
+
this.agent = input.required(...(ngDevMode ? [{ debugName: "agent" }] : []));
|
|
654
|
+
/** A change of the run configuration changed the agent's skills or state. */
|
|
655
|
+
this.changed = output();
|
|
656
|
+
this.id = `nh-assistant-run-configuration-${nextId$5++}`;
|
|
657
|
+
this.maxReason = maxReason;
|
|
658
|
+
this.maxDescription = maxDescription;
|
|
659
|
+
this.maxInstructions = maxInstructions;
|
|
660
|
+
this.maxMessage = maxMessage;
|
|
661
|
+
this.accountabilities = ['agent', 'subject-responsible', 'service-account'];
|
|
662
|
+
this.elevatedReadModes = ['restricted', 'deny'];
|
|
663
|
+
this.conflicts = ['coalesce', 'queue', 'supersede', 'ignore'];
|
|
664
|
+
this.loading = signal(true, ...(ngDevMode ? [{ debugName: "loading" }] : []));
|
|
665
|
+
this.loadFailed = signal(false, ...(ngDevMode ? [{ debugName: "loadFailed" }] : []));
|
|
666
|
+
this.busy = signal(false, ...(ngDevMode ? [{ debugName: "busy" }] : []));
|
|
667
|
+
this.catalog = signal(null, ...(ngDevMode ? [{ debugName: "catalog" }] : []));
|
|
668
|
+
this.configuration = signal(null, ...(ngDevMode ? [{ debugName: "configuration" }] : []));
|
|
669
|
+
this.error = signal(null, ...(ngDevMode ? [{ debugName: "error" }] : []));
|
|
670
|
+
this.errorKeys = computed(() => _nhAssistantErrorKeys(this.error()), ...(ngDevMode ? [{ debugName: "errorKeys" }] : []));
|
|
671
|
+
this.saved = signal(null, ...(ngDevMode ? [{ debugName: "saved" }] : []));
|
|
672
|
+
this.form = signal(null, ...(ngDevMode ? [{ debugName: "form" }] : []));
|
|
673
|
+
this.confirming = signal(null, ...(ngDevMode ? [{ debugName: "confirming" }] : []));
|
|
674
|
+
this.reason = signal('', ...(ngDevMode ? [{ debugName: "reason" }] : []));
|
|
675
|
+
/** The skill being changed; `null` with the skill form open adds a new skill. */
|
|
676
|
+
this.editingSkill = signal(null, ...(ngDevMode ? [{ debugName: "editingSkill" }] : []));
|
|
677
|
+
/** The trigger being changed; `null` with the trigger form open adds a new trigger. */
|
|
678
|
+
this.editingTrigger = signal(null, ...(ngDevMode ? [{ debugName: "editingTrigger" }] : []));
|
|
679
|
+
this.comparison = signal(null, ...(ngDevMode ? [{ debugName: "comparison" }] : []));
|
|
680
|
+
this.versions = signal(null, ...(ngDevMode ? [{ debugName: "versions" }] : []));
|
|
681
|
+
// Run settings form.
|
|
682
|
+
this.accountability = signal('service-account', ...(ngDevMode ? [{ debugName: "accountability" }] : []));
|
|
683
|
+
this.serviceAccount = signal('', ...(ngDevMode ? [{ debugName: "serviceAccount" }] : []));
|
|
684
|
+
this.approvalPolicy = signal('', ...(ngDevMode ? [{ debugName: "approvalPolicy" }] : []));
|
|
685
|
+
this.ruleHandler = signal('', ...(ngDevMode ? [{ debugName: "ruleHandler" }] : []));
|
|
686
|
+
this.triageProfile = signal('', ...(ngDevMode ? [{ debugName: "triageProfile" }] : []));
|
|
687
|
+
this.retryCount = signal('3', ...(ngDevMode ? [{ debugName: "retryCount" }] : []));
|
|
688
|
+
this.maxConcurrentRuns = signal('4', ...(ngDevMode ? [{ debugName: "maxConcurrentRuns" }] : []));
|
|
689
|
+
this.maxRunsPerSubjectPerDay = signal('10', ...(ngDevMode ? [{ debugName: "maxRunsPerSubjectPerDay" }] : []));
|
|
690
|
+
this.maxTokensPerRun = signal('', ...(ngDevMode ? [{ debugName: "maxTokensPerRun" }] : []));
|
|
691
|
+
this.dailyToolCalls = signal('', ...(ngDevMode ? [{ debugName: "dailyToolCalls" }] : []));
|
|
692
|
+
this.dailyTokens = signal('', ...(ngDevMode ? [{ debugName: "dailyTokens" }] : []));
|
|
693
|
+
this.allowGatewayReads = signal(false, ...(ngDevMode ? [{ debugName: "allowGatewayReads" }] : []));
|
|
694
|
+
this.delegateFromInitiator = signal(false, ...(ngDevMode ? [{ debugName: "delegateFromInitiator" }] : []));
|
|
695
|
+
this.elevatedReads = signal('restricted', ...(ngDevMode ? [{ debugName: "elevatedReads" }] : []));
|
|
696
|
+
// Skill form.
|
|
697
|
+
this.skillId = signal('', ...(ngDevMode ? [{ debugName: "skillId" }] : []));
|
|
698
|
+
this.skillDescription = signal('', ...(ngDevMode ? [{ debugName: "skillDescription" }] : []));
|
|
699
|
+
this.skillInstructions = signal('', ...(ngDevMode ? [{ debugName: "skillInstructions" }] : []));
|
|
700
|
+
this.skillTools = signal([], ...(ngDevMode ? [{ debugName: "skillTools" }] : []));
|
|
701
|
+
this.skillProfile = signal('', ...(ngDevMode ? [{ debugName: "skillProfile" }] : []));
|
|
702
|
+
this.skillRuleHandler = signal('', ...(ngDevMode ? [{ debugName: "skillRuleHandler" }] : []));
|
|
703
|
+
this.skillApprovalPolicy = signal('', ...(ngDevMode ? [{ debugName: "skillApprovalPolicy" }] : []));
|
|
704
|
+
this.skillMaxTokens = signal('', ...(ngDevMode ? [{ debugName: "skillMaxTokens" }] : []));
|
|
705
|
+
this.skillDailyToolCalls = signal('', ...(ngDevMode ? [{ debugName: "skillDailyToolCalls" }] : []));
|
|
706
|
+
this.skillDailyTokens = signal('', ...(ngDevMode ? [{ debugName: "skillDailyTokens" }] : []));
|
|
707
|
+
this.skillApply = signal('live', ...(ngDevMode ? [{ debugName: "skillApply" }] : []));
|
|
708
|
+
// Trigger form.
|
|
709
|
+
this.triggerId = signal('', ...(ngDevMode ? [{ debugName: "triggerId" }] : []));
|
|
710
|
+
this.triggerSource = signal('event', ...(ngDevMode ? [{ debugName: "triggerSource" }] : []));
|
|
711
|
+
this.triggerEvent = signal('', ...(ngDevMode ? [{ debugName: "triggerEvent" }] : []));
|
|
712
|
+
this.triggerCron = signal('', ...(ngDevMode ? [{ debugName: "triggerCron" }] : []));
|
|
713
|
+
this.triggerTimeZone = signal('', ...(ngDevMode ? [{ debugName: "triggerTimeZone" }] : []));
|
|
714
|
+
this.triggerSubjectSet = signal('', ...(ngDevMode ? [{ debugName: "triggerSubjectSet" }] : []));
|
|
715
|
+
this.triggerMessage = signal('', ...(ngDevMode ? [{ debugName: "triggerMessage" }] : []));
|
|
716
|
+
this.triggerSkill = signal('', ...(ngDevMode ? [{ debugName: "triggerSkill" }] : []));
|
|
717
|
+
this.triggerConflict = signal('coalesce', ...(ngDevMode ? [{ debugName: "triggerConflict" }] : []));
|
|
718
|
+
this.triggerDebounce = signal('120', ...(ngDevMode ? [{ debugName: "triggerDebounce" }] : []));
|
|
719
|
+
this.isAdminAgent = computed(() => this.configuration()?.source === 'admin', ...(ngDevMode ? [{ debugName: "isAdminAgent" }] : []));
|
|
720
|
+
/** An agent created in the administration gets skills and triggers once it has run settings. */
|
|
721
|
+
this.canAddRunData = computed(() => {
|
|
722
|
+
const configuration = this.configuration();
|
|
723
|
+
return configuration !== null && (configuration.source === 'code' || configuration.runSettings !== null);
|
|
724
|
+
}, ...(ngDevMode ? [{ debugName: "canAddRunData" }] : []));
|
|
725
|
+
this.skillOptions = computed(() => {
|
|
726
|
+
const configuration = this.configuration();
|
|
727
|
+
return configuration
|
|
728
|
+
? [...configuration.codeSkills.map(skill => skill.skillId), ...configuration.dataSkills.map(skill => skill.skillId)]
|
|
729
|
+
: [];
|
|
730
|
+
}, ...(ngDevMode ? [{ debugName: "skillOptions" }] : []));
|
|
731
|
+
this.problems = computed(() => this.configuration()?.problems ?? [], ...(ngDevMode ? [{ debugName: "problems" }] : []));
|
|
732
|
+
this.skillIdInvalid = computed(() => this.editingSkill() === null && !dashCase.test(this.skillId().trim()), ...(ngDevMode ? [{ debugName: "skillIdInvalid" }] : []));
|
|
733
|
+
this.triggerIdInvalid = computed(() => this.editingTrigger() === null && !dashCase.test(this.triggerId().trim()), ...(ngDevMode ? [{ debugName: "triggerIdInvalid" }] : []));
|
|
734
|
+
this.descriptionInvalid = computed(() => {
|
|
735
|
+
const description = this.skillDescription().trim();
|
|
736
|
+
return description.length === 0 || description.length > maxDescription;
|
|
737
|
+
}, ...(ngDevMode ? [{ debugName: "descriptionInvalid" }] : []));
|
|
738
|
+
inject(DestroyRef).onDestroy(() => {
|
|
739
|
+
this.loadSubscription?.unsubscribe();
|
|
740
|
+
this.actionSubscription?.unsubscribe();
|
|
741
|
+
this.detailSubscription?.unsubscribe();
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
ngOnInit() {
|
|
745
|
+
this.load();
|
|
746
|
+
}
|
|
747
|
+
load() {
|
|
748
|
+
this.loading.set(true);
|
|
749
|
+
this.loadFailed.set(false);
|
|
750
|
+
this.loadSubscription?.unsubscribe();
|
|
751
|
+
this.loadSubscription = forkJoin([this.admin.getRunCatalog(), this.admin.getAgentConfiguration(this.agent().id)]).subscribe({
|
|
752
|
+
next: ([catalog, configuration]) => {
|
|
753
|
+
this.catalog.set(catalog);
|
|
754
|
+
this.configuration.set(configuration);
|
|
755
|
+
this.loading.set(false);
|
|
756
|
+
},
|
|
757
|
+
error: () => {
|
|
758
|
+
this.loading.set(false);
|
|
759
|
+
this.loadFailed.set(true);
|
|
760
|
+
}
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
/** The first error of a field from the last failed save, as a translation key. */
|
|
764
|
+
fieldError(field) {
|
|
765
|
+
const code = this.error()?.fieldErrors?.[field]?.[0];
|
|
766
|
+
return code ? `nh-assistant.admin.run-config.field-errors.${code}` : null;
|
|
767
|
+
}
|
|
768
|
+
editSettings() {
|
|
769
|
+
const settings = this.configuration()?.runSettings;
|
|
770
|
+
this.accountability.set(settings?.accountability ?? 'service-account');
|
|
771
|
+
this.serviceAccount.set(settings?.serviceAccountActorId ?? '');
|
|
772
|
+
this.approvalPolicy.set(settings?.approvalPolicy ?? '');
|
|
773
|
+
this.ruleHandler.set(settings?.ruleHandler ?? '');
|
|
774
|
+
this.triageProfile.set(settings?.triageProfile ?? '');
|
|
775
|
+
this.retryCount.set(String(settings?.retryCount ?? 3));
|
|
776
|
+
this.maxConcurrentRuns.set(String(settings?.maxConcurrentRuns ?? 4));
|
|
777
|
+
this.maxRunsPerSubjectPerDay.set(String(settings?.maxRunsPerSubjectPerDay ?? 10));
|
|
778
|
+
this.maxTokensPerRun.set(numberText(settings?.maxTokensPerRun));
|
|
779
|
+
this.dailyToolCalls.set(numberText(settings?.dailyToolCalls));
|
|
780
|
+
this.dailyTokens.set(numberText(settings?.dailyTokens));
|
|
781
|
+
this.allowGatewayReads.set(settings?.allowGatewayReads ?? false);
|
|
782
|
+
this.delegateFromInitiator.set(settings?.delegateFromInitiator ?? false);
|
|
783
|
+
this.elevatedReads.set(settings?.elevatedReads ?? 'restricted');
|
|
784
|
+
this.open('settings');
|
|
785
|
+
}
|
|
786
|
+
saveSettings() {
|
|
787
|
+
const request = {
|
|
788
|
+
accountability: this.accountability(),
|
|
789
|
+
serviceAccountActorId: this.accountability() === 'service-account' ? blank(this.serviceAccount()) : null,
|
|
790
|
+
delegateFromInitiator: this.delegateFromInitiator(),
|
|
791
|
+
allowGatewayReads: this.allowGatewayReads(),
|
|
792
|
+
elevatedReads: this.elevatedReads(),
|
|
793
|
+
approvalPolicy: blank(this.approvalPolicy()),
|
|
794
|
+
retryCount: toNumber(this.retryCount()),
|
|
795
|
+
maxConcurrentRuns: toNumber(this.maxConcurrentRuns()),
|
|
796
|
+
maxRunsPerSubjectPerDay: toNumber(this.maxRunsPerSubjectPerDay()),
|
|
797
|
+
maxTokensPerRun: toNumber(this.maxTokensPerRun()),
|
|
798
|
+
dailyToolCalls: toNumber(this.dailyToolCalls()),
|
|
799
|
+
dailyTokens: toNumber(this.dailyTokens()),
|
|
800
|
+
triageProfile: blank(this.triageProfile()),
|
|
801
|
+
ruleHandler: blank(this.ruleHandler()),
|
|
802
|
+
expectedVersion: this.configuration()?.runSettings?.version ?? null,
|
|
803
|
+
reason: blank(this.reason())
|
|
804
|
+
};
|
|
805
|
+
this.run(this.admin.saveRunSettings(this.agent().id, request), 'nh-assistant.admin.run-config.settings.saved');
|
|
806
|
+
}
|
|
807
|
+
removeSettings() {
|
|
808
|
+
const version = this.configuration()?.runSettings?.version ?? null;
|
|
809
|
+
this.run(this.admin.removeRunSettings(this.agent().id, { expectedVersion: version }), 'nh-assistant.admin.run-config.settings.removed');
|
|
810
|
+
}
|
|
811
|
+
addSkill() {
|
|
812
|
+
this.editingSkill.set(null);
|
|
813
|
+
this.fillSkill(null);
|
|
814
|
+
this.open('skill');
|
|
815
|
+
}
|
|
816
|
+
editSkill(skill) {
|
|
817
|
+
this.editingSkill.set(skill);
|
|
818
|
+
this.fillSkill(skill);
|
|
819
|
+
this.open('skill');
|
|
820
|
+
}
|
|
821
|
+
toggleTool(selector, selected) {
|
|
822
|
+
this.skillTools.update(tools => selected
|
|
823
|
+
? [...tools.filter(tool => tool !== selector), selector]
|
|
824
|
+
: tools.filter(tool => tool !== selector));
|
|
825
|
+
}
|
|
826
|
+
saveSkill() {
|
|
827
|
+
const editing = this.editingSkill();
|
|
828
|
+
const skillId = editing?.skillId ?? this.skillId().trim();
|
|
829
|
+
if (this.skillIdInvalid() || this.descriptionInvalid() || this.skillInstructions().length > maxInstructions) {
|
|
830
|
+
return;
|
|
831
|
+
}
|
|
832
|
+
const request = {
|
|
833
|
+
description: this.skillDescription().trim(),
|
|
834
|
+
instructions: blank(this.skillInstructions()),
|
|
835
|
+
tools: this.skillTools(),
|
|
836
|
+
profile: blank(this.skillProfile()),
|
|
837
|
+
ruleHandler: blank(this.skillRuleHandler()),
|
|
838
|
+
approvalPolicy: blank(this.skillApprovalPolicy()),
|
|
839
|
+
maxTokensPerRun: toNumber(this.skillMaxTokens()),
|
|
840
|
+
dailyToolCalls: toNumber(this.skillDailyToolCalls()),
|
|
841
|
+
dailyTokens: toNumber(this.skillDailyTokens()),
|
|
842
|
+
expectedVersion: editing?.version ?? null,
|
|
843
|
+
reason: blank(this.reason()),
|
|
844
|
+
...(editing ? { apply: this.skillApply() } : {})
|
|
845
|
+
};
|
|
846
|
+
const saved = editing && this.skillApply() === 'shadow'
|
|
847
|
+
? 'nh-assistant.admin.run-config.skills.saved-candidate'
|
|
848
|
+
: editing ? 'nh-assistant.admin.run-config.skills.saved' : 'nh-assistant.admin.run-config.skills.added';
|
|
849
|
+
this.run(this.admin.saveDataSkill(this.agent().id, skillId, request), saved);
|
|
850
|
+
}
|
|
851
|
+
removeSkill(skill) {
|
|
852
|
+
this.run(this.admin.removeDataSkill(this.agent().id, skill.skillId, { expectedVersion: skill.version }), 'nh-assistant.admin.run-config.skills.removed');
|
|
853
|
+
}
|
|
854
|
+
promote(skill) {
|
|
855
|
+
const candidate = skill.candidate;
|
|
856
|
+
if (candidate) {
|
|
857
|
+
this.run(this.admin.promoteSkillCandidate(this.agent().id, skill.skillId, { version: candidate.version, reason: blank(this.reason()) }), 'nh-assistant.admin.run-config.candidate.promoted');
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
discard(skill) {
|
|
861
|
+
const candidate = skill.candidate;
|
|
862
|
+
if (candidate) {
|
|
863
|
+
this.run(this.admin.discardSkillCandidate(this.agent().id, skill.skillId, { version: candidate.version, reason: blank(this.reason()) }), 'nh-assistant.admin.run-config.candidate.discarded');
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
/** Shows or hides the comparison of a skill's candidate with its live version. */
|
|
867
|
+
toggleComparison(skill) {
|
|
868
|
+
if (this.comparison()?.skillId === skill.skillId) {
|
|
869
|
+
this.comparison.set(null);
|
|
870
|
+
return;
|
|
871
|
+
}
|
|
872
|
+
this.versions.set(null);
|
|
873
|
+
this.comparison.set({ skillId: skill.skillId, result: null, failed: false });
|
|
874
|
+
this.detailSubscription?.unsubscribe();
|
|
875
|
+
this.detailSubscription = this.admin.compareSkillCandidate(this.agent().id, skill.skillId).subscribe({
|
|
876
|
+
next: result => this.comparison.set({ skillId: skill.skillId, result, failed: false }),
|
|
877
|
+
error: () => this.comparison.set({ skillId: skill.skillId, result: null, failed: true })
|
|
878
|
+
});
|
|
879
|
+
}
|
|
880
|
+
/** Shows or hides the version history of a skill. */
|
|
881
|
+
toggleVersions(skill) {
|
|
882
|
+
if (this.versions()?.skillId === skill.skillId) {
|
|
883
|
+
this.versions.set(null);
|
|
884
|
+
return;
|
|
885
|
+
}
|
|
886
|
+
this.comparison.set(null);
|
|
887
|
+
this.versions.set({ skillId: skill.skillId, items: null, failed: false });
|
|
888
|
+
this.detailSubscription?.unsubscribe();
|
|
889
|
+
this.detailSubscription = this.admin.getDataSkillVersions(this.agent().id, skill.skillId).subscribe({
|
|
890
|
+
next: items => this.versions.set({ skillId: skill.skillId, items, failed: false }),
|
|
891
|
+
error: () => this.versions.set({ skillId: skill.skillId, items: null, failed: true })
|
|
892
|
+
});
|
|
893
|
+
}
|
|
894
|
+
addTrigger() {
|
|
895
|
+
this.editingTrigger.set(null);
|
|
896
|
+
this.fillTrigger(null);
|
|
897
|
+
this.open('trigger');
|
|
898
|
+
}
|
|
899
|
+
editTrigger(trigger) {
|
|
900
|
+
this.editingTrigger.set(trigger);
|
|
901
|
+
this.fillTrigger(trigger);
|
|
902
|
+
this.open('trigger');
|
|
903
|
+
}
|
|
904
|
+
/** A schedule skips a tick while a run is busy by default; an event coalesces. */
|
|
905
|
+
setTriggerSource(source) {
|
|
906
|
+
this.triggerSource.set(source);
|
|
907
|
+
if (this.editingTrigger() === null) {
|
|
908
|
+
this.triggerConflict.set(source === 'schedule' ? 'ignore' : 'coalesce');
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
saveTrigger() {
|
|
912
|
+
const editing = this.editingTrigger();
|
|
913
|
+
if (this.triggerIdInvalid()) {
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
916
|
+
const schedule = this.triggerSource() === 'schedule';
|
|
917
|
+
const request = {
|
|
918
|
+
source: this.triggerSource(),
|
|
919
|
+
skill: blank(this.triggerSkill()),
|
|
920
|
+
event: schedule ? null : blank(this.triggerEvent()),
|
|
921
|
+
cron: schedule ? blank(this.triggerCron()) : null,
|
|
922
|
+
timeZone: schedule ? blank(this.triggerTimeZone()) : null,
|
|
923
|
+
subjectSet: schedule ? blank(this.triggerSubjectSet()) : null,
|
|
924
|
+
message: schedule ? blank(this.triggerMessage()) : null,
|
|
925
|
+
conflict: this.triggerConflict(),
|
|
926
|
+
debounceSeconds: schedule ? null : toNumber(this.triggerDebounce()),
|
|
927
|
+
expectedVersion: editing?.version ?? null,
|
|
928
|
+
reason: blank(this.reason())
|
|
929
|
+
};
|
|
930
|
+
this.run(this.admin.saveDataTrigger(this.agent().id, editing?.triggerId ?? this.triggerId().trim(), request), editing ? 'nh-assistant.admin.run-config.triggers.saved' : 'nh-assistant.admin.run-config.triggers.added');
|
|
931
|
+
}
|
|
932
|
+
setTriggerShadow(trigger, shadow) {
|
|
933
|
+
this.run(this.admin.setDataTriggerShadow(this.agent().id, trigger.triggerId, { mode: shadow ? 'shadow' : 'live', reason: null }), shadow ? 'nh-assistant.admin.run-config.triggers.in-shadow' : 'nh-assistant.admin.run-config.triggers.live');
|
|
934
|
+
}
|
|
935
|
+
removeTrigger(trigger) {
|
|
936
|
+
this.run(this.admin.removeDataTrigger(this.agent().id, trigger.triggerId, { expectedVersion: trigger.version }), 'nh-assistant.admin.run-config.triggers.removed');
|
|
937
|
+
}
|
|
938
|
+
confirm(action, id) {
|
|
939
|
+
this.form.set(null);
|
|
940
|
+
this.error.set(null);
|
|
941
|
+
this.saved.set(null);
|
|
942
|
+
this.reason.set('');
|
|
943
|
+
this.confirming.set({ action, id });
|
|
944
|
+
}
|
|
945
|
+
isConfirming(action, id) {
|
|
946
|
+
const confirming = this.confirming();
|
|
947
|
+
return confirming?.action === action && confirming.id === id;
|
|
948
|
+
}
|
|
949
|
+
cancel() {
|
|
950
|
+
this.form.set(null);
|
|
951
|
+
this.confirming.set(null);
|
|
952
|
+
this.error.set(null);
|
|
953
|
+
}
|
|
954
|
+
value(event) {
|
|
955
|
+
return event.target.value;
|
|
956
|
+
}
|
|
957
|
+
checked(event) {
|
|
958
|
+
return event.target.checked;
|
|
959
|
+
}
|
|
960
|
+
fillSkill(skill) {
|
|
961
|
+
this.skillId.set(skill?.skillId ?? '');
|
|
962
|
+
this.skillDescription.set(skill?.description ?? '');
|
|
963
|
+
this.skillInstructions.set(skill?.instructions ?? '');
|
|
964
|
+
this.skillTools.set([...(skill?.tools ?? [])]);
|
|
965
|
+
this.skillProfile.set(skill?.profile ?? '');
|
|
966
|
+
this.skillRuleHandler.set(skill?.ruleHandler ?? '');
|
|
967
|
+
this.skillApprovalPolicy.set(skill?.approvalPolicy ?? '');
|
|
968
|
+
this.skillMaxTokens.set(numberText(skill?.maxTokensPerRun));
|
|
969
|
+
this.skillDailyToolCalls.set(numberText(skill?.dailyToolCalls));
|
|
970
|
+
this.skillDailyTokens.set(numberText(skill?.dailyTokens));
|
|
971
|
+
// A real change is evaluated in shadow first; a small correction goes live.
|
|
972
|
+
this.skillApply.set('shadow');
|
|
973
|
+
}
|
|
974
|
+
fillTrigger(trigger) {
|
|
975
|
+
this.triggerId.set(trigger?.triggerId ?? '');
|
|
976
|
+
this.triggerSource.set(trigger?.source ?? 'event');
|
|
977
|
+
this.triggerEvent.set(trigger?.event ?? '');
|
|
978
|
+
this.triggerCron.set(trigger?.cron ?? '');
|
|
979
|
+
this.triggerTimeZone.set(trigger?.timeZone ?? '');
|
|
980
|
+
this.triggerSubjectSet.set(trigger?.subjectSet ?? '');
|
|
981
|
+
this.triggerMessage.set(trigger?.message ?? '');
|
|
982
|
+
this.triggerSkill.set(trigger?.skillId ?? '');
|
|
983
|
+
this.triggerConflict.set(trigger?.conflict ?? 'coalesce');
|
|
984
|
+
this.triggerDebounce.set(String(trigger?.debounceSeconds ?? 120));
|
|
985
|
+
}
|
|
986
|
+
/** Opens one form at a time and moves the focus into it. */
|
|
987
|
+
open(form) {
|
|
988
|
+
this.confirming.set(null);
|
|
989
|
+
this.error.set(null);
|
|
990
|
+
this.saved.set(null);
|
|
991
|
+
this.reason.set('');
|
|
992
|
+
this.form.set(form);
|
|
993
|
+
afterNextRender(() => {
|
|
994
|
+
this.host.nativeElement.querySelector(`[data-form="${form}"] input:not([disabled]), [data-form="${form}"] select, [data-form="${form}"] textarea`)?.focus();
|
|
995
|
+
}, { injector: this.injector });
|
|
996
|
+
}
|
|
997
|
+
/** Runs a change and reloads the configuration, which every change may affect. */
|
|
998
|
+
run(request, savedKey) {
|
|
999
|
+
this.busy.set(true);
|
|
1000
|
+
this.error.set(null);
|
|
1001
|
+
this.saved.set(null);
|
|
1002
|
+
this.actionSubscription?.unsubscribe();
|
|
1003
|
+
this.actionSubscription = request.subscribe({
|
|
1004
|
+
error: (error) => {
|
|
1005
|
+
this.busy.set(false);
|
|
1006
|
+
this.error.set(_toNhAssistantError(error));
|
|
1007
|
+
},
|
|
1008
|
+
complete: () => {
|
|
1009
|
+
this.busy.set(false);
|
|
1010
|
+
this.form.set(null);
|
|
1011
|
+
this.confirming.set(null);
|
|
1012
|
+
this.comparison.set(null);
|
|
1013
|
+
this.versions.set(null);
|
|
1014
|
+
this.saved.set(savedKey);
|
|
1015
|
+
this.changed.emit();
|
|
1016
|
+
this.load();
|
|
1017
|
+
}
|
|
1018
|
+
});
|
|
1019
|
+
}
|
|
1020
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantAgentRunConfigurationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1021
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.33", type: NhAssistantAgentRunConfigurationComponent, isStandalone: true, selector: "nh-assistant-agent-run-configuration", inputs: { agent: { classPropertyName: "agent", publicName: "agent", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { changed: "changed" }, 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: agent().displayName } }}</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\">{{ key | translate }}</p>\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 @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 @if (problems().length > 0) {\n <div class=\"message warning\" role=\"status\" data-problems>\n <nh-assistant-icon name=\"warning\" />\n <div class=\"stack\">\n <strong>{{ 'nh-assistant.admin.run-config.problems.title' | translate }}</strong>\n <ul>\n @for (problem of problems(); track problem.kind + problem.id) {\n <li>\n {{ 'nh-assistant.admin.run-config.problems.kind.' + problem.kind | translate: { id: problem.id } }}\n {{ 'nh-assistant.admin.run-config.problems.code.' + problem.code | translate }}\n </li>\n }\n </ul>\n </div>\n </div>\n }\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 (config.runSettings; as settings) {\n <div class=\"meta\">\n <span>{{ 'nh-assistant.admin.run-config.settings.accountability.' + settings.accountability | translate }}</span>\n @if (settings.serviceAccountActorId) {\n <span class=\"mono\">{{ settings.serviceAccountActorId }}</span>\n }\n @if (settings.ruleHandler) {\n <span>{{ 'nh-assistant.admin.run-config.rule-handler-value' | translate: { name: settings.ruleHandler } }}</span>\n }\n @if (settings.approvalPolicy) {\n <span>{{ 'nh-assistant.admin.run-config.policy-value' | translate: { name: settings.approvalPolicy } }}</span>\n }\n <span>{{ 'nh-assistant.admin.agents.version' | translate: { version: settings.version } }}</span>\n @if (!settings.inEffect) {\n <span class=\"badge danger\">{{ 'nh-assistant.admin.run-config.out-of-effect' | translate }}</span>\n }\n </div>\n <p class=\"muted\">{{ 'nh-assistant.admin.run-config.settings.rights' | translate }}</p>\n } @else {\n <p class=\"muted\">{{ 'nh-assistant.admin.run-config.settings.empty' | translate }}</p>\n }\n @if (form() !== 'settings') {\n <div class=\"card-actions\">\n <button class=\"secondary\" type=\"button\" [disabled]=\"busy()\" (click)=\"editSettings()\">\n <nh-assistant-icon [name]=\"config.runSettings ? 'edit' : 'plus'\" />\n {{ (config.runSettings ? 'nh-assistant.admin.run-config.settings.edit' : 'nh-assistant.admin.run-config.settings.add') | translate }}\n </button>\n @if (config.runSettings) {\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.keep' | 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 }\n </div>\n } @else {\n <form class=\"stack\" data-form=\"settings\" (submit)=\"$event.preventDefault(); saveSettings()\">\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-accountability'\">{{ 'nh-assistant.admin.run-config.settings.accountability-label' | translate }}</label>\n <select [id]=\"id + '-accountability'\" [attr.aria-describedby]=\"id + '-accountability-hint'\" (change)=\"accountability.set($any(value($event)))\">\n @for (option of accountabilities; track option) {\n <option [value]=\"option\" [selected]=\"accountability() === option\">{{ 'nh-assistant.admin.run-config.settings.accountability.' + option | translate }}</option>\n }\n </select>\n <p class=\"hint\" [id]=\"id + '-accountability-hint'\">{{ 'nh-assistant.admin.run-config.settings.accountability-hint' | translate }}</p>\n </div>\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 <input\n type=\"text\"\n class=\"mono\"\n autocomplete=\"off\"\n maxlength=\"256\"\n [id]=\"id + '-service-account'\"\n [value]=\"serviceAccount()\"\n [attr.aria-invalid]=\"fieldError('serviceAccountActorId') ? true : null\"\n [attr.aria-describedby]=\"id + '-service-account-hint'\"\n (input)=\"serviceAccount.set(value($event))\" />\n <p class=\"hint\" [id]=\"id + '-service-account-hint'\">\n @if (fieldError('serviceAccountActorId'); as key) {\n <span class=\"error-text\">{{ key | translate }}</span>\n }\n </p>\n </div>\n }\n </div>\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-rule-handler'\">{{ 'nh-assistant.admin.run-config.rule-handler' | translate }}</label>\n <select [id]=\"id + '-rule-handler'\" [attr.aria-invalid]=\"fieldError('ruleHandler') ? true : null\" (change)=\"ruleHandler.set(value($event))\">\n <option value=\"\" [selected]=\"ruleHandler() === ''\">{{ 'nh-assistant.admin.run-config.model' | translate }}</option>\n @for (handler of catalog()?.ruleHandlers ?? []; track handler.name) {\n <option [value]=\"handler.name\" [selected]=\"ruleHandler() === handler.name\">{{ handler.name }} \u00B7 {{ handler.description }}</option>\n }\n </select>\n @if (fieldError('ruleHandler'); as key) {\n <p class=\"hint\"><span class=\"error-text\">{{ key | translate }}</span></p>\n }\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-policy'\">{{ 'nh-assistant.admin.run-config.approval-policy' | translate }}</label>\n <select [id]=\"id + '-policy'\" [attr.aria-invalid]=\"fieldError('approvalPolicy') ? true : null\" (change)=\"approvalPolicy.set(value($event))\">\n <option value=\"\" [selected]=\"approvalPolicy() === ''\">{{ 'nh-assistant.admin.run-config.none' | translate }}</option>\n @for (policy of catalog()?.approvalPolicies ?? []; track policy) {\n <option [value]=\"policy\" [selected]=\"approvalPolicy() === policy\">{{ policy }}</option>\n }\n </select>\n @if (fieldError('approvalPolicy'); as key) {\n <p class=\"hint\"><span class=\"error-text\">{{ key | translate }}</span></p>\n }\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-triage'\">{{ 'nh-assistant.admin.run-config.settings.triage' | translate }}</label>\n <select [id]=\"id + '-triage'\" [attr.aria-describedby]=\"id + '-triage-hint'\" (change)=\"triageProfile.set(value($event))\">\n <option value=\"\" [selected]=\"triageProfile() === ''\">{{ 'nh-assistant.admin.run-config.none' | translate }}</option>\n @for (profile of catalog()?.profiles ?? []; track profile) {\n <option [value]=\"profile\" [selected]=\"triageProfile() === profile\">{{ profile }}</option>\n }\n </select>\n <p class=\"hint\" [id]=\"id + '-triage-hint'\">{{ 'nh-assistant.admin.run-config.settings.triage-hint' | translate }}</p>\n </div>\n </div>\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\" [id]=\"id + '-retries'\" [value]=\"retryCount()\" [attr.aria-invalid]=\"fieldError('retryCount') ? true : null\" (input)=\"retryCount.set(value($event))\" />\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\" [id]=\"id + '-concurrent'\" [value]=\"maxConcurrentRuns()\" [attr.aria-invalid]=\"fieldError('maxConcurrentRuns') ? true : null\" (input)=\"maxConcurrentRuns.set(value($event))\" />\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\" [id]=\"id + '-per-subject'\" [value]=\"maxRunsPerSubjectPerDay()\" [attr.aria-invalid]=\"fieldError('maxRunsPerSubjectPerDay') ? true : null\" (input)=\"maxRunsPerSubjectPerDay.set(value($event))\" />\n </div>\n </div>\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\" [id]=\"id + '-run-tokens'\" [value]=\"maxTokensPerRun()\" (input)=\"maxTokensPerRun.set(value($event))\" />\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\" [id]=\"id + '-daily-calls'\" [value]=\"dailyToolCalls()\" [attr.aria-invalid]=\"fieldError('dailyBudget') ? true : null\" (input)=\"dailyToolCalls.set(value($event))\" />\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\" [id]=\"id + '-daily-tokens'\" [value]=\"dailyTokens()\" [attr.aria-invalid]=\"fieldError('dailyBudget') ? true : null\" (input)=\"dailyTokens.set(value($event))\" />\n </div>\n </div>\n <p class=\"hint\">{{ 'nh-assistant.admin.run-config.budget-hint' | translate }}</p>\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-elevated'\">{{ 'nh-assistant.admin.run-config.settings.elevated-reads' | translate }}</label>\n <select [id]=\"id + '-elevated'\" (change)=\"elevatedReads.set($any(value($event)))\">\n @for (mode of elevatedReadModes; track mode) {\n <option [value]=\"mode\" [selected]=\"elevatedReads() === mode\">{{ 'nh-assistant.admin.run-config.settings.elevated.' + mode | translate }}</option>\n }\n </select>\n </div>\n </div>\n <label class=\"check\">\n <input type=\"checkbox\" [checked]=\"allowGatewayReads()\" (change)=\"allowGatewayReads.set(checked($event))\" />\n <span>\n {{ 'nh-assistant.admin.run-config.settings.gateway-reads' | translate }}\n @if (fieldError('allowGatewayReads'); as key) {\n <span class=\"error-text\">{{ key | translate }}</span>\n }\n </span>\n </label>\n <label class=\"check\">\n <input type=\"checkbox\" [checked]=\"delegateFromInitiator()\" (change)=\"delegateFromInitiator.set(checked($event))\" />\n <span>{{ 'nh-assistant.admin.run-config.settings.delegate' | translate }}</span>\n </label>\n <div class=\"field\">\n <label [for]=\"id + '-settings-reason'\">{{ 'nh-assistant.admin.agents.state.reason-label' | translate }}</label>\n <input type=\"text\" autocomplete=\"off\" [id]=\"id + '-settings-reason'\" [maxLength]=\"maxReason\" [value]=\"reason()\" (input)=\"reason.set(value($event))\" />\n </div>\n <div class=\"actions\">\n <button class=\"secondary\" type=\"button\" (click)=\"cancel()\">{{ '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 }\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\" [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 <form class=\"stack card\" data-form=\"skill\" (submit)=\"$event.preventDefault(); saveSkill()\">\n <h5>\n @if (editingSkill(); as editing) {\n {{ 'nh-assistant.admin.run-config.skills.edit-title' | translate: { skill: editing.skillId, version: editing.version } }}\n } @else {\n {{ 'nh-assistant.admin.run-config.skills.new-title' | translate }}\n }\n </h5>\n @if (!editingSkill()) {\n <div class=\"field\">\n <label [for]=\"id + '-skill-id'\">{{ 'nh-assistant.admin.run-config.skills.id' | translate }}</label>\n <input\n type=\"text\"\n class=\"mono\"\n autocomplete=\"off\"\n maxlength=\"64\"\n [id]=\"id + '-skill-id'\"\n [value]=\"skillId()\"\n [attr.aria-invalid]=\"skillId().length > 0 && skillIdInvalid() ? true : null\"\n [attr.aria-describedby]=\"id + '-skill-id-hint'\"\n (input)=\"skillId.set(value($event))\" />\n <p class=\"hint\" [id]=\"id + '-skill-id-hint'\">\n <span [class.error-text]=\"skillId().length > 0 && skillIdInvalid()\">{{ 'nh-assistant.admin.run-config.skills.id-hint' | translate }}</span>\n </p>\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\n type=\"text\"\n [id]=\"id + '-skill-description'\"\n [maxLength]=\"maxDescription\"\n [value]=\"skillDescription()\"\n [attr.aria-invalid]=\"fieldError('description') ? true : null\"\n [attr.aria-describedby]=\"id + '-skill-description-hint'\"\n (input)=\"skillDescription.set(value($event))\" />\n <p class=\"hint\" [id]=\"id + '-skill-description-hint'\">\n @if (fieldError('description'); as key) {\n <span class=\"error-text\">{{ key | translate }}</span>\n } @else {\n <span>{{ 'nh-assistant.admin.run-config.skills.description-hint' | translate }}</span>\n }\n <span class=\"counter\">{{ 'nh-assistant.admin.characters' | translate: { count: skillDescription().length, max: maxDescription } }}</span>\n </p>\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-skill-instructions'\">{{ 'nh-assistant.admin.run-config.skills.instructions' | translate }}</label>\n <textarea\n rows=\"6\"\n [id]=\"id + '-skill-instructions'\"\n [value]=\"skillInstructions()\"\n [attr.aria-invalid]=\"skillInstructions().length > maxInstructions ? true : null\"\n [attr.aria-describedby]=\"id + '-skill-instructions-hint'\"\n (input)=\"skillInstructions.set(value($event))\"></textarea>\n <p class=\"hint\" [id]=\"id + '-skill-instructions-hint'\">\n <span>{{ 'nh-assistant.admin.run-config.skills.instructions-hint' | translate }}</span>\n <span class=\"counter\" [class.error-text]=\"skillInstructions().length > maxInstructions\">{{ 'nh-assistant.admin.characters' | translate: { count: skillInstructions().length, max: maxInstructions } }}</span>\n </p>\n </div>\n <fieldset class=\"subsection\">\n <legend>{{ 'nh-assistant.admin.run-config.skills.tools' | translate }}</legend>\n <p class=\"muted\">{{ 'nh-assistant.admin.run-config.skills.tools-hint' | translate }}</p>\n @for (selector of config.toolSelectors; track selector) {\n <label class=\"check\">\n <input type=\"checkbox\" [checked]=\"skillTools().includes(selector)\" (change)=\"toggleTool(selector, checked($event))\" />\n <span class=\"mono\">{{ selector }}</span>\n </label>\n } @empty {\n <p class=\"muted\">{{ 'nh-assistant.admin.agent-editor.no-selectors' | translate }}</p>\n }\n @if (fieldError('tools'); as key) {\n <p class=\"hint\"><span class=\"error-text\">{{ key | translate }}</span></p>\n }\n </fieldset>\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-skill-rules'\">{{ 'nh-assistant.admin.run-config.rule-handler' | translate }}</label>\n <select [id]=\"id + '-skill-rules'\" [attr.aria-invalid]=\"fieldError('ruleHandler') ? true : null\" (change)=\"skillRuleHandler.set(value($event))\">\n <option value=\"\" [selected]=\"skillRuleHandler() === ''\">{{ 'nh-assistant.admin.run-config.model' | translate }}</option>\n @for (handler of catalog()?.ruleHandlers ?? []; track handler.name) {\n <option [value]=\"handler.name\" [selected]=\"skillRuleHandler() === handler.name\">{{ handler.name }} \u00B7 {{ handler.description }}</option>\n }\n </select>\n @if (fieldError('ruleHandler'); as key) {\n <p class=\"hint\"><span class=\"error-text\">{{ key | translate }}</span></p>\n }\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-skill-profile'\">{{ 'nh-assistant.admin.run-config.skills.profile' | translate }}</label>\n <select [id]=\"id + '-skill-profile'\" [attr.aria-invalid]=\"fieldError('profile') ? true : null\" (change)=\"skillProfile.set(value($event))\">\n <option value=\"\" [selected]=\"skillProfile() === ''\">{{ 'nh-assistant.admin.run-config.skills.agent-profile' | translate }}</option>\n @for (profile of catalog()?.profiles ?? []; track profile) {\n <option [value]=\"profile\" [selected]=\"skillProfile() === profile\">{{ profile }}</option>\n }\n </select>\n @if (fieldError('profile'); as key) {\n <p class=\"hint\"><span class=\"error-text\">{{ key | translate }}</span></p>\n }\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-skill-policy'\">{{ 'nh-assistant.admin.run-config.approval-policy' | translate }}</label>\n <select [id]=\"id + '-skill-policy'\" [attr.aria-invalid]=\"fieldError('approvalPolicy') ? true : null\" (change)=\"skillApprovalPolicy.set(value($event))\">\n <option value=\"\" [selected]=\"skillApprovalPolicy() === ''\">{{ 'nh-assistant.admin.run-config.skills.agent-policy' | translate }}</option>\n @for (policy of catalog()?.approvalPolicies ?? []; track policy) {\n <option [value]=\"policy\" [selected]=\"skillApprovalPolicy() === policy\">{{ policy }}</option>\n }\n </select>\n </div>\n </div>\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\" [id]=\"id + '-skill-tokens'\" [value]=\"skillMaxTokens()\" [attr.aria-invalid]=\"fieldError('maxTokensPerRun') ? true : null\" (input)=\"skillMaxTokens.set(value($event))\" />\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\" [id]=\"id + '-skill-calls'\" [value]=\"skillDailyToolCalls()\" [attr.aria-invalid]=\"fieldError('dailyBudget') ? true : null\" (input)=\"skillDailyToolCalls.set(value($event))\" />\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\" [id]=\"id + '-skill-daily-tokens'\" [value]=\"skillDailyTokens()\" [attr.aria-invalid]=\"fieldError('dailyBudget') ? true : null\" (input)=\"skillDailyTokens.set(value($event))\" />\n </div>\n </div>\n <p class=\"hint\">{{ 'nh-assistant.admin.run-config.budget-hint' | translate }}</p>\n @if (editingSkill()) {\n <fieldset class=\"subsection\" data-apply>\n <legend>{{ 'nh-assistant.admin.run-config.skills.apply' | translate }}</legend>\n <label class=\"check\">\n <input type=\"radio\" [name]=\"id + '-apply'\" value=\"shadow\" [checked]=\"skillApply() === 'shadow'\" (change)=\"skillApply.set('shadow')\" />\n <span>{{ 'nh-assistant.admin.run-config.skills.apply-shadow' | translate }}</span>\n </label>\n <label class=\"check\">\n <input type=\"radio\" [name]=\"id + '-apply'\" value=\"live\" [checked]=\"skillApply() === 'live'\" (change)=\"skillApply.set('live')\" />\n <span>{{ 'nh-assistant.admin.run-config.skills.apply-live' | translate }}</span>\n </label>\n </fieldset>\n } @else {\n <p class=\"hint\">{{ 'nh-assistant.admin.run-config.skills.starts-in-shadow' | translate }}</p>\n }\n <div class=\"field\">\n <label [for]=\"id + '-skill-reason'\">{{ 'nh-assistant.admin.agents.state.reason-label' | translate }}</label>\n <input type=\"text\" autocomplete=\"off\" [id]=\"id + '-skill-reason'\" [maxLength]=\"maxReason\" [value]=\"reason()\" (input)=\"reason.set(value($event))\" />\n </div>\n <div class=\"actions\">\n <button class=\"secondary\" type=\"button\" (click)=\"cancel()\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n <button class=\"primary\" type=\"submit\" [disabled]=\"busy() || skillIdInvalid() || descriptionInvalid() || skillInstructions().length > maxInstructions\">\n {{ (busy() ? 'nh-assistant.admin.saving' : 'nh-assistant.admin.save') | translate }}\n </button>\n </div>\n </form>\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 <span class=\"mono\">{{ skill.skillId }}</span>\n <span class=\"badge\">{{ 'nh-assistant.admin.agents.source-code' | translate }}</span>\n </div>\n <p class=\"muted\">{{ 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 <span class=\"mono\">{{ 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 @if (skill.ruleHandler) {\n <span>{{ 'nh-assistant.admin.run-config.rule-handler-value' | translate: { name: skill.ruleHandler } }}</span>\n }\n @if (skill.profile) {\n <span>{{ 'nh-assistant.admin.run-config.profile-value' | translate: { name: skill.profile } }}</span>\n }\n <span>{{ 'nh-assistant.admin.agents.tool-count' | translate: { count: skill.tools.length } }}</span>\n <span>{{ 'nh-assistant.admin.run-config.changed' | translate: { user: 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.keep' | 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.keep' | 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 <table class=\"summary-table\" data-comparison>\n <caption class=\"visually-hidden\">{{ 'nh-assistant.admin.run-config.candidate.comparison' | translate: { skill: 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 <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.keep' | 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 (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\">{{ 'nh-assistant.admin.run-config.changed' | translate: { user: 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\" [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 <form class=\"stack card\" data-form=\"trigger\" (submit)=\"$event.preventDefault(); saveTrigger()\">\n <h5>\n @if (editingTrigger(); as editing) {\n {{ 'nh-assistant.admin.run-config.triggers.edit-title' | translate: { trigger: editing.triggerId } }}\n } @else {\n {{ 'nh-assistant.admin.run-config.triggers.new-title' | translate }}\n }\n </h5>\n @if (!editingTrigger()) {\n <div class=\"field\">\n <label [for]=\"id + '-trigger-id'\">{{ 'nh-assistant.admin.run-config.triggers.id' | translate }}</label>\n <input\n type=\"text\"\n class=\"mono\"\n autocomplete=\"off\"\n maxlength=\"64\"\n [id]=\"id + '-trigger-id'\"\n [value]=\"triggerId()\"\n [attr.aria-invalid]=\"(triggerId().length > 0 && triggerIdInvalid()) || fieldError('triggerId') ? true : null\"\n [attr.aria-describedby]=\"id + '-trigger-id-hint'\"\n (input)=\"triggerId.set(value($event))\" />\n <p class=\"hint\" [id]=\"id + '-trigger-id-hint'\">\n @if (fieldError('triggerId'); as key) {\n <span class=\"error-text\">{{ key | translate }}</span>\n } @else {\n <span [class.error-text]=\"triggerId().length > 0 && triggerIdInvalid()\">{{ 'nh-assistant.admin.run-config.skills.id-hint' | translate }}</span>\n }\n </p>\n </div>\n }\n <fieldset class=\"subsection\">\n <legend>{{ 'nh-assistant.admin.run-config.triggers.source' | translate }}</legend>\n <label class=\"check\">\n <input type=\"radio\" [name]=\"id + '-source'\" value=\"event\" [checked]=\"triggerSource() === 'event'\" (change)=\"setTriggerSource('event')\" />\n <span>{{ 'nh-assistant.admin.run-config.triggers.source-event' | translate }}</span>\n </label>\n <label class=\"check\">\n <input type=\"radio\" [name]=\"id + '-source'\" value=\"schedule\" [checked]=\"triggerSource() === 'schedule'\" (change)=\"setTriggerSource('schedule')\" />\n <span>{{ 'nh-assistant.admin.run-config.triggers.source-schedule' | translate }}</span>\n </label>\n </fieldset>\n @if (triggerSource() === 'event') {\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-trigger-event'\">{{ 'nh-assistant.admin.run-config.triggers.event' | translate }}</label>\n <select [id]=\"id + '-trigger-event'\" [attr.aria-invalid]=\"fieldError('event') ? true : null\" (change)=\"triggerEvent.set(value($event))\">\n <option value=\"\" [selected]=\"triggerEvent() === ''\" disabled>{{ 'nh-assistant.admin.run-config.choose' | translate }}</option>\n @for (event of catalog()?.triggerEvents ?? []; track event.name) {\n <option [value]=\"event.name\" [selected]=\"triggerEvent() === event.name\">{{ event.name }} \u00B7 {{ event.description }}</option>\n }\n </select>\n @if (fieldError('event'); as key) {\n <p class=\"hint\"><span class=\"error-text\">{{ key | translate }}</span></p>\n }\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-trigger-debounce'\">{{ 'nh-assistant.admin.run-config.triggers.debounce' | translate }}</label>\n <input type=\"number\" min=\"0\" max=\"3600\" [id]=\"id + '-trigger-debounce'\" [value]=\"triggerDebounce()\" [attr.aria-invalid]=\"fieldError('debounceSeconds') ? true : null\" (input)=\"triggerDebounce.set(value($event))\" />\n </div>\n </div>\n } @else {\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-trigger-cron'\">{{ 'nh-assistant.admin.run-config.triggers.cron' | translate }}</label>\n <input\n type=\"text\"\n class=\"mono\"\n autocomplete=\"off\"\n maxlength=\"128\"\n [id]=\"id + '-trigger-cron'\"\n [value]=\"triggerCron()\"\n [attr.aria-invalid]=\"fieldError('cron') ? true : null\"\n [attr.aria-describedby]=\"id + '-trigger-cron-hint'\"\n (input)=\"triggerCron.set(value($event))\" />\n <p class=\"hint\" [id]=\"id + '-trigger-cron-hint'\">\n @if (fieldError('cron'); as key) {\n <span class=\"error-text\">{{ key | translate }}</span>\n } @else {\n <span>{{ 'nh-assistant.admin.run-config.triggers.cron-hint' | translate }}</span>\n }\n </p>\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-trigger-zone'\">{{ 'nh-assistant.admin.run-config.triggers.time-zone' | translate }}</label>\n <input type=\"text\" class=\"mono\" autocomplete=\"off\" maxlength=\"64\" [id]=\"id + '-trigger-zone'\" [value]=\"triggerTimeZone()\" [attr.placeholder]=\"'UTC'\" (input)=\"triggerTimeZone.set(value($event))\" />\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-trigger-set'\">{{ 'nh-assistant.admin.run-config.triggers.subject-set' | translate }}</label>\n <select [id]=\"id + '-trigger-set'\" [attr.aria-invalid]=\"fieldError('subjectSet') ? true : null\" (change)=\"triggerSubjectSet.set(value($event))\">\n <option value=\"\" [selected]=\"triggerSubjectSet() === ''\" disabled>{{ 'nh-assistant.admin.run-config.choose' | translate }}</option>\n @for (set of catalog()?.subjectSets ?? []; track set.name) {\n <option [value]=\"set.name\" [selected]=\"triggerSubjectSet() === set.name\">{{ set.name }} \u00B7 {{ set.description }}</option>\n }\n </select>\n @if (fieldError('subjectSet'); as key) {\n <p class=\"hint\"><span class=\"error-text\">{{ key | translate }}</span></p>\n }\n </div>\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-trigger-message'\">{{ 'nh-assistant.admin.run-config.triggers.message' | translate }}</label>\n <textarea rows=\"2\" [id]=\"id + '-trigger-message'\" [maxLength]=\"maxMessage\" [value]=\"triggerMessage()\" [attr.aria-invalid]=\"fieldError('message') ? true : null\" (input)=\"triggerMessage.set(value($event))\"></textarea>\n </div>\n }\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-trigger-skill'\">{{ 'nh-assistant.admin.run-config.triggers.skill' | translate }}</label>\n <select [id]=\"id + '-trigger-skill'\" [attr.aria-invalid]=\"fieldError('skill') ? true : null\" (change)=\"triggerSkill.set(value($event))\">\n <option value=\"\" [selected]=\"triggerSkill() === ''\">{{ 'nh-assistant.admin.run-config.triggers.agent-routes' | translate }}</option>\n @for (skill of skillOptions(); track skill) {\n <option [value]=\"skill\" [selected]=\"triggerSkill() === skill\">{{ skill }}</option>\n }\n </select>\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-trigger-conflict'\">{{ 'nh-assistant.admin.run-config.triggers.conflict' | translate }}</label>\n <select [id]=\"id + '-trigger-conflict'\" (change)=\"triggerConflict.set($any(value($event)))\">\n @for (conflict of conflicts; track conflict) {\n <option [value]=\"conflict\" [selected]=\"triggerConflict() === conflict\">{{ 'nh-assistant.admin.run-config.triggers.conflicts.' + conflict | translate }}</option>\n }\n </select>\n </div>\n </div>\n @if (!editingTrigger()) {\n <p class=\"hint\">{{ 'nh-assistant.admin.run-config.triggers.starts-in-shadow' | translate }}</p>\n }\n <div class=\"field\">\n <label [for]=\"id + '-trigger-reason'\">{{ 'nh-assistant.admin.agents.state.reason-label' | translate }}</label>\n <input type=\"text\" autocomplete=\"off\" [id]=\"id + '-trigger-reason'\" [maxLength]=\"maxReason\" [value]=\"reason()\" (input)=\"reason.set(value($event))\" />\n </div>\n <div class=\"actions\">\n <button class=\"secondary\" type=\"button\" (click)=\"cancel()\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n <button class=\"primary\" type=\"submit\" [disabled]=\"busy() || triggerIdInvalid()\">{{ (busy() ? 'nh-assistant.admin.saving' : 'nh-assistant.admin.save') | translate }}</button>\n </div>\n </form>\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: 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 }\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 <span class=\"mono\">{{ trigger.subjectSet }}</span>\n }\n <span>{{ (trigger.skillId ? 'nh-assistant.admin.run-config.triggers.skill-value' : 'nh-assistant.admin.run-config.triggers.agent-routes') | translate: { skill: 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.keep' | 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 </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 select,:host textarea{width:100%;min-width:0;min-height:38px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:8px 10px;color:var(--nha-text);background:var(--nha-background);font:inherit;font-size:13px}:host input[type=text]:focus-visible,:host input[type=url]:focus-visible,:host input[type=password]:focus-visible,:host input[type=search]:focus-visible,:host select:focus-visible,:host textarea:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host input[type=text]:disabled,:host input[type=url]:disabled,:host input[type=password]:disabled,:host input[type=search]:disabled,:host select:disabled,:host textarea:disabled{cursor:not-allowed;opacity:.6}:host input[type=text][aria-invalid=true],:host input[type=url][aria-invalid=true],:host input[type=password][aria-invalid=true],:host input[type=search][aria-invalid=true],:host select[aria-invalid=true],:host textarea[aria-invalid=true]{border-color:var(--nha-danger)}:host textarea{line-height:1.45;resize:vertical}:host .mono{font-family:var(--nha-mono);font-size:12px}:host .field{display:flex;flex-direction:column;min-width:0}:host .hint{display:flex;justify-content:space-between;gap:8px;margin:4px 2px 0;color:var(--nha-text-muted);font-size:11px}:host .counter{flex:none;font-variant-numeric:tabular-nums}:host .error-text{color:var(--nha-danger)}:host .message{display:flex;align-items:center;gap:8px;margin:0;border:1px solid transparent;border-radius:var(--nha-radius-control);padding:8px 12px;font-size:13px}:host .message.error{border-color:var(--nha-danger);color:var(--nha-text);background:var(--nha-danger-soft)}:host .message.warning{border-color:var(--nha-warning);color:var(--nha-text);background:var(--nha-warning-soft)}:host .message.success{border-color:var(--nha-success);color:var(--nha-text);background:var(--nha-success-soft)}:host .message nh-assistant-icon{flex:none}:host .primary,:host .secondary,:host .danger{display:inline-flex;align-items:center;justify-content:center;gap:6px;min-height:36px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px 14px;font:inherit;font-size:13px;font-weight:700;white-space:nowrap;cursor:pointer}:host .primary:focus-visible,:host .secondary:focus-visible,:host .danger:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .primary:disabled,:host .secondary:disabled,:host .danger:disabled{cursor:not-allowed;opacity:.55}:host .primary{border-color:var(--nha-accent);color:var(--nha-accent-contrast);background:var(--nha-accent)}:host .primary:hover:not(:disabled){filter:brightness(1.12)}:host .secondary{color:var(--nha-text);background:transparent}:host .secondary:hover:not(:disabled){background:var(--nha-surface-strong)}:host .danger{border-color:var(--nha-danger);color:var(--nha-danger);background:transparent}:host .danger:hover:not(:disabled){background:var(--nha-danger-soft)}:host .icon-button{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:1px solid transparent;border-radius:var(--nha-radius-control);color:var(--nha-text-muted);background:transparent;cursor:pointer}:host .icon-button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .icon-button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}:host .icon-button:disabled{cursor:not-allowed;opacity:.5}:host .actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px}:host .badge{display:inline-flex;align-items:center;gap:4px;border-radius:999px;padding:2px 8px;color:var(--nha-text-muted);background:var(--nha-surface-strong);font-size:11px;font-weight:700;white-space:nowrap}:host .badge.success{color:var(--nha-success);background:var(--nha-success-soft)}:host .badge.warning{color:var(--nha-warning);background:var(--nha-warning-soft)}:host .badge.danger{color:var(--nha-danger);background:var(--nha-danger-soft)}:host .visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}.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}.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)}.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}\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"], dependencies: [{ kind: "component", type: _NhAssistantIconComponent, selector: "nh-assistant-icon", inputs: ["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 }); }
|
|
1022
|
+
}
|
|
1023
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantAgentRunConfigurationComponent, decorators: [{
|
|
1024
|
+
type: Component,
|
|
1025
|
+
args: [{ selector: 'nh-assistant-agent-run-configuration', standalone: true, imports: [DatePipe, DecimalPipe, TranslatePipe, _NhAssistantTranslatePipe, _NhAssistantIconComponent], 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: agent().displayName } }}</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\">{{ key | translate }}</p>\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 @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 @if (problems().length > 0) {\n <div class=\"message warning\" role=\"status\" data-problems>\n <nh-assistant-icon name=\"warning\" />\n <div class=\"stack\">\n <strong>{{ 'nh-assistant.admin.run-config.problems.title' | translate }}</strong>\n <ul>\n @for (problem of problems(); track problem.kind + problem.id) {\n <li>\n {{ 'nh-assistant.admin.run-config.problems.kind.' + problem.kind | translate: { id: problem.id } }}\n {{ 'nh-assistant.admin.run-config.problems.code.' + problem.code | translate }}\n </li>\n }\n </ul>\n </div>\n </div>\n }\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 (config.runSettings; as settings) {\n <div class=\"meta\">\n <span>{{ 'nh-assistant.admin.run-config.settings.accountability.' + settings.accountability | translate }}</span>\n @if (settings.serviceAccountActorId) {\n <span class=\"mono\">{{ settings.serviceAccountActorId }}</span>\n }\n @if (settings.ruleHandler) {\n <span>{{ 'nh-assistant.admin.run-config.rule-handler-value' | translate: { name: settings.ruleHandler } }}</span>\n }\n @if (settings.approvalPolicy) {\n <span>{{ 'nh-assistant.admin.run-config.policy-value' | translate: { name: settings.approvalPolicy } }}</span>\n }\n <span>{{ 'nh-assistant.admin.agents.version' | translate: { version: settings.version } }}</span>\n @if (!settings.inEffect) {\n <span class=\"badge danger\">{{ 'nh-assistant.admin.run-config.out-of-effect' | translate }}</span>\n }\n </div>\n <p class=\"muted\">{{ 'nh-assistant.admin.run-config.settings.rights' | translate }}</p>\n } @else {\n <p class=\"muted\">{{ 'nh-assistant.admin.run-config.settings.empty' | translate }}</p>\n }\n @if (form() !== 'settings') {\n <div class=\"card-actions\">\n <button class=\"secondary\" type=\"button\" [disabled]=\"busy()\" (click)=\"editSettings()\">\n <nh-assistant-icon [name]=\"config.runSettings ? 'edit' : 'plus'\" />\n {{ (config.runSettings ? 'nh-assistant.admin.run-config.settings.edit' : 'nh-assistant.admin.run-config.settings.add') | translate }}\n </button>\n @if (config.runSettings) {\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.keep' | 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 }\n </div>\n } @else {\n <form class=\"stack\" data-form=\"settings\" (submit)=\"$event.preventDefault(); saveSettings()\">\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-accountability'\">{{ 'nh-assistant.admin.run-config.settings.accountability-label' | translate }}</label>\n <select [id]=\"id + '-accountability'\" [attr.aria-describedby]=\"id + '-accountability-hint'\" (change)=\"accountability.set($any(value($event)))\">\n @for (option of accountabilities; track option) {\n <option [value]=\"option\" [selected]=\"accountability() === option\">{{ 'nh-assistant.admin.run-config.settings.accountability.' + option | translate }}</option>\n }\n </select>\n <p class=\"hint\" [id]=\"id + '-accountability-hint'\">{{ 'nh-assistant.admin.run-config.settings.accountability-hint' | translate }}</p>\n </div>\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 <input\n type=\"text\"\n class=\"mono\"\n autocomplete=\"off\"\n maxlength=\"256\"\n [id]=\"id + '-service-account'\"\n [value]=\"serviceAccount()\"\n [attr.aria-invalid]=\"fieldError('serviceAccountActorId') ? true : null\"\n [attr.aria-describedby]=\"id + '-service-account-hint'\"\n (input)=\"serviceAccount.set(value($event))\" />\n <p class=\"hint\" [id]=\"id + '-service-account-hint'\">\n @if (fieldError('serviceAccountActorId'); as key) {\n <span class=\"error-text\">{{ key | translate }}</span>\n }\n </p>\n </div>\n }\n </div>\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-rule-handler'\">{{ 'nh-assistant.admin.run-config.rule-handler' | translate }}</label>\n <select [id]=\"id + '-rule-handler'\" [attr.aria-invalid]=\"fieldError('ruleHandler') ? true : null\" (change)=\"ruleHandler.set(value($event))\">\n <option value=\"\" [selected]=\"ruleHandler() === ''\">{{ 'nh-assistant.admin.run-config.model' | translate }}</option>\n @for (handler of catalog()?.ruleHandlers ?? []; track handler.name) {\n <option [value]=\"handler.name\" [selected]=\"ruleHandler() === handler.name\">{{ handler.name }} \u00B7 {{ handler.description }}</option>\n }\n </select>\n @if (fieldError('ruleHandler'); as key) {\n <p class=\"hint\"><span class=\"error-text\">{{ key | translate }}</span></p>\n }\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-policy'\">{{ 'nh-assistant.admin.run-config.approval-policy' | translate }}</label>\n <select [id]=\"id + '-policy'\" [attr.aria-invalid]=\"fieldError('approvalPolicy') ? true : null\" (change)=\"approvalPolicy.set(value($event))\">\n <option value=\"\" [selected]=\"approvalPolicy() === ''\">{{ 'nh-assistant.admin.run-config.none' | translate }}</option>\n @for (policy of catalog()?.approvalPolicies ?? []; track policy) {\n <option [value]=\"policy\" [selected]=\"approvalPolicy() === policy\">{{ policy }}</option>\n }\n </select>\n @if (fieldError('approvalPolicy'); as key) {\n <p class=\"hint\"><span class=\"error-text\">{{ key | translate }}</span></p>\n }\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-triage'\">{{ 'nh-assistant.admin.run-config.settings.triage' | translate }}</label>\n <select [id]=\"id + '-triage'\" [attr.aria-describedby]=\"id + '-triage-hint'\" (change)=\"triageProfile.set(value($event))\">\n <option value=\"\" [selected]=\"triageProfile() === ''\">{{ 'nh-assistant.admin.run-config.none' | translate }}</option>\n @for (profile of catalog()?.profiles ?? []; track profile) {\n <option [value]=\"profile\" [selected]=\"triageProfile() === profile\">{{ profile }}</option>\n }\n </select>\n <p class=\"hint\" [id]=\"id + '-triage-hint'\">{{ 'nh-assistant.admin.run-config.settings.triage-hint' | translate }}</p>\n </div>\n </div>\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\" [id]=\"id + '-retries'\" [value]=\"retryCount()\" [attr.aria-invalid]=\"fieldError('retryCount') ? true : null\" (input)=\"retryCount.set(value($event))\" />\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\" [id]=\"id + '-concurrent'\" [value]=\"maxConcurrentRuns()\" [attr.aria-invalid]=\"fieldError('maxConcurrentRuns') ? true : null\" (input)=\"maxConcurrentRuns.set(value($event))\" />\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\" [id]=\"id + '-per-subject'\" [value]=\"maxRunsPerSubjectPerDay()\" [attr.aria-invalid]=\"fieldError('maxRunsPerSubjectPerDay') ? true : null\" (input)=\"maxRunsPerSubjectPerDay.set(value($event))\" />\n </div>\n </div>\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\" [id]=\"id + '-run-tokens'\" [value]=\"maxTokensPerRun()\" (input)=\"maxTokensPerRun.set(value($event))\" />\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\" [id]=\"id + '-daily-calls'\" [value]=\"dailyToolCalls()\" [attr.aria-invalid]=\"fieldError('dailyBudget') ? true : null\" (input)=\"dailyToolCalls.set(value($event))\" />\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\" [id]=\"id + '-daily-tokens'\" [value]=\"dailyTokens()\" [attr.aria-invalid]=\"fieldError('dailyBudget') ? true : null\" (input)=\"dailyTokens.set(value($event))\" />\n </div>\n </div>\n <p class=\"hint\">{{ 'nh-assistant.admin.run-config.budget-hint' | translate }}</p>\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-elevated'\">{{ 'nh-assistant.admin.run-config.settings.elevated-reads' | translate }}</label>\n <select [id]=\"id + '-elevated'\" (change)=\"elevatedReads.set($any(value($event)))\">\n @for (mode of elevatedReadModes; track mode) {\n <option [value]=\"mode\" [selected]=\"elevatedReads() === mode\">{{ 'nh-assistant.admin.run-config.settings.elevated.' + mode | translate }}</option>\n }\n </select>\n </div>\n </div>\n <label class=\"check\">\n <input type=\"checkbox\" [checked]=\"allowGatewayReads()\" (change)=\"allowGatewayReads.set(checked($event))\" />\n <span>\n {{ 'nh-assistant.admin.run-config.settings.gateway-reads' | translate }}\n @if (fieldError('allowGatewayReads'); as key) {\n <span class=\"error-text\">{{ key | translate }}</span>\n }\n </span>\n </label>\n <label class=\"check\">\n <input type=\"checkbox\" [checked]=\"delegateFromInitiator()\" (change)=\"delegateFromInitiator.set(checked($event))\" />\n <span>{{ 'nh-assistant.admin.run-config.settings.delegate' | translate }}</span>\n </label>\n <div class=\"field\">\n <label [for]=\"id + '-settings-reason'\">{{ 'nh-assistant.admin.agents.state.reason-label' | translate }}</label>\n <input type=\"text\" autocomplete=\"off\" [id]=\"id + '-settings-reason'\" [maxLength]=\"maxReason\" [value]=\"reason()\" (input)=\"reason.set(value($event))\" />\n </div>\n <div class=\"actions\">\n <button class=\"secondary\" type=\"button\" (click)=\"cancel()\">{{ '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 }\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\" [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 <form class=\"stack card\" data-form=\"skill\" (submit)=\"$event.preventDefault(); saveSkill()\">\n <h5>\n @if (editingSkill(); as editing) {\n {{ 'nh-assistant.admin.run-config.skills.edit-title' | translate: { skill: editing.skillId, version: editing.version } }}\n } @else {\n {{ 'nh-assistant.admin.run-config.skills.new-title' | translate }}\n }\n </h5>\n @if (!editingSkill()) {\n <div class=\"field\">\n <label [for]=\"id + '-skill-id'\">{{ 'nh-assistant.admin.run-config.skills.id' | translate }}</label>\n <input\n type=\"text\"\n class=\"mono\"\n autocomplete=\"off\"\n maxlength=\"64\"\n [id]=\"id + '-skill-id'\"\n [value]=\"skillId()\"\n [attr.aria-invalid]=\"skillId().length > 0 && skillIdInvalid() ? true : null\"\n [attr.aria-describedby]=\"id + '-skill-id-hint'\"\n (input)=\"skillId.set(value($event))\" />\n <p class=\"hint\" [id]=\"id + '-skill-id-hint'\">\n <span [class.error-text]=\"skillId().length > 0 && skillIdInvalid()\">{{ 'nh-assistant.admin.run-config.skills.id-hint' | translate }}</span>\n </p>\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\n type=\"text\"\n [id]=\"id + '-skill-description'\"\n [maxLength]=\"maxDescription\"\n [value]=\"skillDescription()\"\n [attr.aria-invalid]=\"fieldError('description') ? true : null\"\n [attr.aria-describedby]=\"id + '-skill-description-hint'\"\n (input)=\"skillDescription.set(value($event))\" />\n <p class=\"hint\" [id]=\"id + '-skill-description-hint'\">\n @if (fieldError('description'); as key) {\n <span class=\"error-text\">{{ key | translate }}</span>\n } @else {\n <span>{{ 'nh-assistant.admin.run-config.skills.description-hint' | translate }}</span>\n }\n <span class=\"counter\">{{ 'nh-assistant.admin.characters' | translate: { count: skillDescription().length, max: maxDescription } }}</span>\n </p>\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-skill-instructions'\">{{ 'nh-assistant.admin.run-config.skills.instructions' | translate }}</label>\n <textarea\n rows=\"6\"\n [id]=\"id + '-skill-instructions'\"\n [value]=\"skillInstructions()\"\n [attr.aria-invalid]=\"skillInstructions().length > maxInstructions ? true : null\"\n [attr.aria-describedby]=\"id + '-skill-instructions-hint'\"\n (input)=\"skillInstructions.set(value($event))\"></textarea>\n <p class=\"hint\" [id]=\"id + '-skill-instructions-hint'\">\n <span>{{ 'nh-assistant.admin.run-config.skills.instructions-hint' | translate }}</span>\n <span class=\"counter\" [class.error-text]=\"skillInstructions().length > maxInstructions\">{{ 'nh-assistant.admin.characters' | translate: { count: skillInstructions().length, max: maxInstructions } }}</span>\n </p>\n </div>\n <fieldset class=\"subsection\">\n <legend>{{ 'nh-assistant.admin.run-config.skills.tools' | translate }}</legend>\n <p class=\"muted\">{{ 'nh-assistant.admin.run-config.skills.tools-hint' | translate }}</p>\n @for (selector of config.toolSelectors; track selector) {\n <label class=\"check\">\n <input type=\"checkbox\" [checked]=\"skillTools().includes(selector)\" (change)=\"toggleTool(selector, checked($event))\" />\n <span class=\"mono\">{{ selector }}</span>\n </label>\n } @empty {\n <p class=\"muted\">{{ 'nh-assistant.admin.agent-editor.no-selectors' | translate }}</p>\n }\n @if (fieldError('tools'); as key) {\n <p class=\"hint\"><span class=\"error-text\">{{ key | translate }}</span></p>\n }\n </fieldset>\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-skill-rules'\">{{ 'nh-assistant.admin.run-config.rule-handler' | translate }}</label>\n <select [id]=\"id + '-skill-rules'\" [attr.aria-invalid]=\"fieldError('ruleHandler') ? true : null\" (change)=\"skillRuleHandler.set(value($event))\">\n <option value=\"\" [selected]=\"skillRuleHandler() === ''\">{{ 'nh-assistant.admin.run-config.model' | translate }}</option>\n @for (handler of catalog()?.ruleHandlers ?? []; track handler.name) {\n <option [value]=\"handler.name\" [selected]=\"skillRuleHandler() === handler.name\">{{ handler.name }} \u00B7 {{ handler.description }}</option>\n }\n </select>\n @if (fieldError('ruleHandler'); as key) {\n <p class=\"hint\"><span class=\"error-text\">{{ key | translate }}</span></p>\n }\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-skill-profile'\">{{ 'nh-assistant.admin.run-config.skills.profile' | translate }}</label>\n <select [id]=\"id + '-skill-profile'\" [attr.aria-invalid]=\"fieldError('profile') ? true : null\" (change)=\"skillProfile.set(value($event))\">\n <option value=\"\" [selected]=\"skillProfile() === ''\">{{ 'nh-assistant.admin.run-config.skills.agent-profile' | translate }}</option>\n @for (profile of catalog()?.profiles ?? []; track profile) {\n <option [value]=\"profile\" [selected]=\"skillProfile() === profile\">{{ profile }}</option>\n }\n </select>\n @if (fieldError('profile'); as key) {\n <p class=\"hint\"><span class=\"error-text\">{{ key | translate }}</span></p>\n }\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-skill-policy'\">{{ 'nh-assistant.admin.run-config.approval-policy' | translate }}</label>\n <select [id]=\"id + '-skill-policy'\" [attr.aria-invalid]=\"fieldError('approvalPolicy') ? true : null\" (change)=\"skillApprovalPolicy.set(value($event))\">\n <option value=\"\" [selected]=\"skillApprovalPolicy() === ''\">{{ 'nh-assistant.admin.run-config.skills.agent-policy' | translate }}</option>\n @for (policy of catalog()?.approvalPolicies ?? []; track policy) {\n <option [value]=\"policy\" [selected]=\"skillApprovalPolicy() === policy\">{{ policy }}</option>\n }\n </select>\n </div>\n </div>\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\" [id]=\"id + '-skill-tokens'\" [value]=\"skillMaxTokens()\" [attr.aria-invalid]=\"fieldError('maxTokensPerRun') ? true : null\" (input)=\"skillMaxTokens.set(value($event))\" />\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\" [id]=\"id + '-skill-calls'\" [value]=\"skillDailyToolCalls()\" [attr.aria-invalid]=\"fieldError('dailyBudget') ? true : null\" (input)=\"skillDailyToolCalls.set(value($event))\" />\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\" [id]=\"id + '-skill-daily-tokens'\" [value]=\"skillDailyTokens()\" [attr.aria-invalid]=\"fieldError('dailyBudget') ? true : null\" (input)=\"skillDailyTokens.set(value($event))\" />\n </div>\n </div>\n <p class=\"hint\">{{ 'nh-assistant.admin.run-config.budget-hint' | translate }}</p>\n @if (editingSkill()) {\n <fieldset class=\"subsection\" data-apply>\n <legend>{{ 'nh-assistant.admin.run-config.skills.apply' | translate }}</legend>\n <label class=\"check\">\n <input type=\"radio\" [name]=\"id + '-apply'\" value=\"shadow\" [checked]=\"skillApply() === 'shadow'\" (change)=\"skillApply.set('shadow')\" />\n <span>{{ 'nh-assistant.admin.run-config.skills.apply-shadow' | translate }}</span>\n </label>\n <label class=\"check\">\n <input type=\"radio\" [name]=\"id + '-apply'\" value=\"live\" [checked]=\"skillApply() === 'live'\" (change)=\"skillApply.set('live')\" />\n <span>{{ 'nh-assistant.admin.run-config.skills.apply-live' | translate }}</span>\n </label>\n </fieldset>\n } @else {\n <p class=\"hint\">{{ 'nh-assistant.admin.run-config.skills.starts-in-shadow' | translate }}</p>\n }\n <div class=\"field\">\n <label [for]=\"id + '-skill-reason'\">{{ 'nh-assistant.admin.agents.state.reason-label' | translate }}</label>\n <input type=\"text\" autocomplete=\"off\" [id]=\"id + '-skill-reason'\" [maxLength]=\"maxReason\" [value]=\"reason()\" (input)=\"reason.set(value($event))\" />\n </div>\n <div class=\"actions\">\n <button class=\"secondary\" type=\"button\" (click)=\"cancel()\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n <button class=\"primary\" type=\"submit\" [disabled]=\"busy() || skillIdInvalid() || descriptionInvalid() || skillInstructions().length > maxInstructions\">\n {{ (busy() ? 'nh-assistant.admin.saving' : 'nh-assistant.admin.save') | translate }}\n </button>\n </div>\n </form>\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 <span class=\"mono\">{{ skill.skillId }}</span>\n <span class=\"badge\">{{ 'nh-assistant.admin.agents.source-code' | translate }}</span>\n </div>\n <p class=\"muted\">{{ 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 <span class=\"mono\">{{ 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 @if (skill.ruleHandler) {\n <span>{{ 'nh-assistant.admin.run-config.rule-handler-value' | translate: { name: skill.ruleHandler } }}</span>\n }\n @if (skill.profile) {\n <span>{{ 'nh-assistant.admin.run-config.profile-value' | translate: { name: skill.profile } }}</span>\n }\n <span>{{ 'nh-assistant.admin.agents.tool-count' | translate: { count: skill.tools.length } }}</span>\n <span>{{ 'nh-assistant.admin.run-config.changed' | translate: { user: 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.keep' | 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.keep' | 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 <table class=\"summary-table\" data-comparison>\n <caption class=\"visually-hidden\">{{ 'nh-assistant.admin.run-config.candidate.comparison' | translate: { skill: 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 <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.keep' | 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 (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\">{{ 'nh-assistant.admin.run-config.changed' | translate: { user: 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\" [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 <form class=\"stack card\" data-form=\"trigger\" (submit)=\"$event.preventDefault(); saveTrigger()\">\n <h5>\n @if (editingTrigger(); as editing) {\n {{ 'nh-assistant.admin.run-config.triggers.edit-title' | translate: { trigger: editing.triggerId } }}\n } @else {\n {{ 'nh-assistant.admin.run-config.triggers.new-title' | translate }}\n }\n </h5>\n @if (!editingTrigger()) {\n <div class=\"field\">\n <label [for]=\"id + '-trigger-id'\">{{ 'nh-assistant.admin.run-config.triggers.id' | translate }}</label>\n <input\n type=\"text\"\n class=\"mono\"\n autocomplete=\"off\"\n maxlength=\"64\"\n [id]=\"id + '-trigger-id'\"\n [value]=\"triggerId()\"\n [attr.aria-invalid]=\"(triggerId().length > 0 && triggerIdInvalid()) || fieldError('triggerId') ? true : null\"\n [attr.aria-describedby]=\"id + '-trigger-id-hint'\"\n (input)=\"triggerId.set(value($event))\" />\n <p class=\"hint\" [id]=\"id + '-trigger-id-hint'\">\n @if (fieldError('triggerId'); as key) {\n <span class=\"error-text\">{{ key | translate }}</span>\n } @else {\n <span [class.error-text]=\"triggerId().length > 0 && triggerIdInvalid()\">{{ 'nh-assistant.admin.run-config.skills.id-hint' | translate }}</span>\n }\n </p>\n </div>\n }\n <fieldset class=\"subsection\">\n <legend>{{ 'nh-assistant.admin.run-config.triggers.source' | translate }}</legend>\n <label class=\"check\">\n <input type=\"radio\" [name]=\"id + '-source'\" value=\"event\" [checked]=\"triggerSource() === 'event'\" (change)=\"setTriggerSource('event')\" />\n <span>{{ 'nh-assistant.admin.run-config.triggers.source-event' | translate }}</span>\n </label>\n <label class=\"check\">\n <input type=\"radio\" [name]=\"id + '-source'\" value=\"schedule\" [checked]=\"triggerSource() === 'schedule'\" (change)=\"setTriggerSource('schedule')\" />\n <span>{{ 'nh-assistant.admin.run-config.triggers.source-schedule' | translate }}</span>\n </label>\n </fieldset>\n @if (triggerSource() === 'event') {\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-trigger-event'\">{{ 'nh-assistant.admin.run-config.triggers.event' | translate }}</label>\n <select [id]=\"id + '-trigger-event'\" [attr.aria-invalid]=\"fieldError('event') ? true : null\" (change)=\"triggerEvent.set(value($event))\">\n <option value=\"\" [selected]=\"triggerEvent() === ''\" disabled>{{ 'nh-assistant.admin.run-config.choose' | translate }}</option>\n @for (event of catalog()?.triggerEvents ?? []; track event.name) {\n <option [value]=\"event.name\" [selected]=\"triggerEvent() === event.name\">{{ event.name }} \u00B7 {{ event.description }}</option>\n }\n </select>\n @if (fieldError('event'); as key) {\n <p class=\"hint\"><span class=\"error-text\">{{ key | translate }}</span></p>\n }\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-trigger-debounce'\">{{ 'nh-assistant.admin.run-config.triggers.debounce' | translate }}</label>\n <input type=\"number\" min=\"0\" max=\"3600\" [id]=\"id + '-trigger-debounce'\" [value]=\"triggerDebounce()\" [attr.aria-invalid]=\"fieldError('debounceSeconds') ? true : null\" (input)=\"triggerDebounce.set(value($event))\" />\n </div>\n </div>\n } @else {\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-trigger-cron'\">{{ 'nh-assistant.admin.run-config.triggers.cron' | translate }}</label>\n <input\n type=\"text\"\n class=\"mono\"\n autocomplete=\"off\"\n maxlength=\"128\"\n [id]=\"id + '-trigger-cron'\"\n [value]=\"triggerCron()\"\n [attr.aria-invalid]=\"fieldError('cron') ? true : null\"\n [attr.aria-describedby]=\"id + '-trigger-cron-hint'\"\n (input)=\"triggerCron.set(value($event))\" />\n <p class=\"hint\" [id]=\"id + '-trigger-cron-hint'\">\n @if (fieldError('cron'); as key) {\n <span class=\"error-text\">{{ key | translate }}</span>\n } @else {\n <span>{{ 'nh-assistant.admin.run-config.triggers.cron-hint' | translate }}</span>\n }\n </p>\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-trigger-zone'\">{{ 'nh-assistant.admin.run-config.triggers.time-zone' | translate }}</label>\n <input type=\"text\" class=\"mono\" autocomplete=\"off\" maxlength=\"64\" [id]=\"id + '-trigger-zone'\" [value]=\"triggerTimeZone()\" [attr.placeholder]=\"'UTC'\" (input)=\"triggerTimeZone.set(value($event))\" />\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-trigger-set'\">{{ 'nh-assistant.admin.run-config.triggers.subject-set' | translate }}</label>\n <select [id]=\"id + '-trigger-set'\" [attr.aria-invalid]=\"fieldError('subjectSet') ? true : null\" (change)=\"triggerSubjectSet.set(value($event))\">\n <option value=\"\" [selected]=\"triggerSubjectSet() === ''\" disabled>{{ 'nh-assistant.admin.run-config.choose' | translate }}</option>\n @for (set of catalog()?.subjectSets ?? []; track set.name) {\n <option [value]=\"set.name\" [selected]=\"triggerSubjectSet() === set.name\">{{ set.name }} \u00B7 {{ set.description }}</option>\n }\n </select>\n @if (fieldError('subjectSet'); as key) {\n <p class=\"hint\"><span class=\"error-text\">{{ key | translate }}</span></p>\n }\n </div>\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-trigger-message'\">{{ 'nh-assistant.admin.run-config.triggers.message' | translate }}</label>\n <textarea rows=\"2\" [id]=\"id + '-trigger-message'\" [maxLength]=\"maxMessage\" [value]=\"triggerMessage()\" [attr.aria-invalid]=\"fieldError('message') ? true : null\" (input)=\"triggerMessage.set(value($event))\"></textarea>\n </div>\n }\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-trigger-skill'\">{{ 'nh-assistant.admin.run-config.triggers.skill' | translate }}</label>\n <select [id]=\"id + '-trigger-skill'\" [attr.aria-invalid]=\"fieldError('skill') ? true : null\" (change)=\"triggerSkill.set(value($event))\">\n <option value=\"\" [selected]=\"triggerSkill() === ''\">{{ 'nh-assistant.admin.run-config.triggers.agent-routes' | translate }}</option>\n @for (skill of skillOptions(); track skill) {\n <option [value]=\"skill\" [selected]=\"triggerSkill() === skill\">{{ skill }}</option>\n }\n </select>\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-trigger-conflict'\">{{ 'nh-assistant.admin.run-config.triggers.conflict' | translate }}</label>\n <select [id]=\"id + '-trigger-conflict'\" (change)=\"triggerConflict.set($any(value($event)))\">\n @for (conflict of conflicts; track conflict) {\n <option [value]=\"conflict\" [selected]=\"triggerConflict() === conflict\">{{ 'nh-assistant.admin.run-config.triggers.conflicts.' + conflict | translate }}</option>\n }\n </select>\n </div>\n </div>\n @if (!editingTrigger()) {\n <p class=\"hint\">{{ 'nh-assistant.admin.run-config.triggers.starts-in-shadow' | translate }}</p>\n }\n <div class=\"field\">\n <label [for]=\"id + '-trigger-reason'\">{{ 'nh-assistant.admin.agents.state.reason-label' | translate }}</label>\n <input type=\"text\" autocomplete=\"off\" [id]=\"id + '-trigger-reason'\" [maxLength]=\"maxReason\" [value]=\"reason()\" (input)=\"reason.set(value($event))\" />\n </div>\n <div class=\"actions\">\n <button class=\"secondary\" type=\"button\" (click)=\"cancel()\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n <button class=\"primary\" type=\"submit\" [disabled]=\"busy() || triggerIdInvalid()\">{{ (busy() ? 'nh-assistant.admin.saving' : 'nh-assistant.admin.save') | translate }}</button>\n </div>\n </form>\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: 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 }\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 <span class=\"mono\">{{ trigger.subjectSet }}</span>\n }\n <span>{{ (trigger.skillId ? 'nh-assistant.admin.run-config.triggers.skill-value' : 'nh-assistant.admin.run-config.triggers.agent-routes') | translate: { skill: 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.keep' | 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 </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 select,:host textarea{width:100%;min-width:0;min-height:38px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:8px 10px;color:var(--nha-text);background:var(--nha-background);font:inherit;font-size:13px}:host input[type=text]:focus-visible,:host input[type=url]:focus-visible,:host input[type=password]:focus-visible,:host input[type=search]:focus-visible,:host select:focus-visible,:host textarea:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host input[type=text]:disabled,:host input[type=url]:disabled,:host input[type=password]:disabled,:host input[type=search]:disabled,:host select:disabled,:host textarea:disabled{cursor:not-allowed;opacity:.6}:host input[type=text][aria-invalid=true],:host input[type=url][aria-invalid=true],:host input[type=password][aria-invalid=true],:host input[type=search][aria-invalid=true],:host select[aria-invalid=true],:host textarea[aria-invalid=true]{border-color:var(--nha-danger)}:host textarea{line-height:1.45;resize:vertical}:host .mono{font-family:var(--nha-mono);font-size:12px}:host .field{display:flex;flex-direction:column;min-width:0}:host .hint{display:flex;justify-content:space-between;gap:8px;margin:4px 2px 0;color:var(--nha-text-muted);font-size:11px}:host .counter{flex:none;font-variant-numeric:tabular-nums}:host .error-text{color:var(--nha-danger)}:host .message{display:flex;align-items:center;gap:8px;margin:0;border:1px solid transparent;border-radius:var(--nha-radius-control);padding:8px 12px;font-size:13px}:host .message.error{border-color:var(--nha-danger);color:var(--nha-text);background:var(--nha-danger-soft)}:host .message.warning{border-color:var(--nha-warning);color:var(--nha-text);background:var(--nha-warning-soft)}:host .message.success{border-color:var(--nha-success);color:var(--nha-text);background:var(--nha-success-soft)}:host .message nh-assistant-icon{flex:none}:host .primary,:host .secondary,:host .danger{display:inline-flex;align-items:center;justify-content:center;gap:6px;min-height:36px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px 14px;font:inherit;font-size:13px;font-weight:700;white-space:nowrap;cursor:pointer}:host .primary:focus-visible,:host .secondary:focus-visible,:host .danger:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .primary:disabled,:host .secondary:disabled,:host .danger:disabled{cursor:not-allowed;opacity:.55}:host .primary{border-color:var(--nha-accent);color:var(--nha-accent-contrast);background:var(--nha-accent)}:host .primary:hover:not(:disabled){filter:brightness(1.12)}:host .secondary{color:var(--nha-text);background:transparent}:host .secondary:hover:not(:disabled){background:var(--nha-surface-strong)}:host .danger{border-color:var(--nha-danger);color:var(--nha-danger);background:transparent}:host .danger:hover:not(:disabled){background:var(--nha-danger-soft)}:host .icon-button{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:1px solid transparent;border-radius:var(--nha-radius-control);color:var(--nha-text-muted);background:transparent;cursor:pointer}:host .icon-button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .icon-button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}:host .icon-button:disabled{cursor:not-allowed;opacity:.5}:host .actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px}:host .badge{display:inline-flex;align-items:center;gap:4px;border-radius:999px;padding:2px 8px;color:var(--nha-text-muted);background:var(--nha-surface-strong);font-size:11px;font-weight:700;white-space:nowrap}:host .badge.success{color:var(--nha-success);background:var(--nha-success-soft)}:host .badge.warning{color:var(--nha-warning);background:var(--nha-warning-soft)}:host .badge.danger{color:var(--nha-danger);background:var(--nha-danger-soft)}:host .visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}.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}.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)}.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}\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"] }]
|
|
1026
|
+
}], ctorParameters: () => [], propDecorators: { agent: [{ type: i0.Input, args: [{ isSignal: true, alias: "agent", required: true }] }], changed: [{ type: i0.Output, args: ["changed"] }] } });
|
|
1027
|
+
function blank(value) {
|
|
1028
|
+
const trimmed = value.trim();
|
|
1029
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
1030
|
+
}
|
|
1031
|
+
function toNumber(value) {
|
|
1032
|
+
const trimmed = value.trim();
|
|
1033
|
+
if (trimmed.length === 0) {
|
|
1034
|
+
return null;
|
|
1035
|
+
}
|
|
1036
|
+
const parsed = Number(trimmed);
|
|
1037
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
1038
|
+
}
|
|
1039
|
+
function numberText(value) {
|
|
1040
|
+
return value === null || value === undefined ? '' : String(value);
|
|
1041
|
+
}
|
|
1042
|
+
|
|
633
1043
|
let nextId$4 = 0;
|
|
634
1044
|
/** The longest reason the server stores with a state change. */
|
|
635
1045
|
const maxStateReason = 500;
|
|
636
1046
|
/**
|
|
637
1047
|
* Agent administration: code agents (override, reset) and agents created here (edit, delete),
|
|
638
|
-
* and for every agent its run state with pause, stop and resume,
|
|
1048
|
+
* and for every agent its run state with pause, stop and resume, its shadow mode and that of each
|
|
1049
|
+
* skill, and the technical account of an agent that answers to one, each with an optional reason.
|
|
639
1050
|
* Editing opens `nh-assistant-admin-agent-editor`; the feedback action opens
|
|
640
|
-
* `nh-assistant-agent-feedback-report` for the agent,
|
|
641
|
-
* shadow variants opens `nh-assistant-agent-evaluation
|
|
1051
|
+
* `nh-assistant-agent-feedback-report` for the agent, the evaluation action of an agent with
|
|
1052
|
+
* shadow variants opens `nh-assistant-agent-evaluation`, and the run configuration action opens
|
|
1053
|
+
* `nh-assistant-agent-run-configuration` for the skills, triggers and run settings defined as data.
|
|
642
1054
|
*/
|
|
643
1055
|
class NhAssistantAdminAgentsComponent {
|
|
644
1056
|
constructor() {
|
|
@@ -660,10 +1072,17 @@ class NhAssistantAdminAgentsComponent {
|
|
|
660
1072
|
this.reporting = signal(null, ...(ngDevMode ? [{ debugName: "reporting" }] : []));
|
|
661
1073
|
/** The agent whose comparison with its shadow variants is shown instead of the list. */
|
|
662
1074
|
this.evaluating = signal(null, ...(ngDevMode ? [{ debugName: "evaluating" }] : []));
|
|
1075
|
+
/** The agent whose run configuration is shown instead of the list. */
|
|
1076
|
+
this.configuring = signal(null, ...(ngDevMode ? [{ debugName: "configuring" }] : []));
|
|
663
1077
|
this.confirming = signal(null, ...(ngDevMode ? [{ debugName: "confirming" }] : []));
|
|
664
1078
|
this.busyAgentId = signal(null, ...(ngDevMode ? [{ debugName: "busyAgentId" }] : []));
|
|
665
1079
|
/** The state change whose reason is being entered, of the agent or one of its skills. */
|
|
666
1080
|
this.changingState = signal(null, ...(ngDevMode ? [{ debugName: "changingState" }] : []));
|
|
1081
|
+
/** The shadow mode change whose reason is being entered, of the agent or one of its skills. */
|
|
1082
|
+
this.changingShadow = signal(null, ...(ngDevMode ? [{ debugName: "changingShadow" }] : []));
|
|
1083
|
+
/** The change of the technical account being entered: a new account, or the one in code. */
|
|
1084
|
+
this.changingAccount = signal(null, ...(ngDevMode ? [{ debugName: "changingAccount" }] : []));
|
|
1085
|
+
this.accountId = signal('', ...(ngDevMode ? [{ debugName: "accountId" }] : []));
|
|
667
1086
|
this.stateReason = signal('', ...(ngDevMode ? [{ debugName: "stateReason" }] : []));
|
|
668
1087
|
this.maxReason = maxStateReason;
|
|
669
1088
|
this.error = signal(null, ...(ngDevMode ? [{ debugName: "error" }] : []));
|
|
@@ -719,6 +1138,33 @@ class NhAssistantAdminAgentsComponent {
|
|
|
719
1138
|
this.evaluating.set(agent);
|
|
720
1139
|
afterNextRender(() => this.backButton()?.nativeElement.focus(), { injector: this.injector });
|
|
721
1140
|
}
|
|
1141
|
+
/**
|
|
1142
|
+
* Whether the agent can run on its own and so has a run configuration: an agent in code with runs,
|
|
1143
|
+
* or any agent created here, which gets run settings as data.
|
|
1144
|
+
*/
|
|
1145
|
+
hasRunConfiguration(agent) {
|
|
1146
|
+
return agent.source === 'admin' || (agent.shadow !== null && agent.shadow !== undefined);
|
|
1147
|
+
}
|
|
1148
|
+
/** Shows the agent's skills, triggers and run settings defined as data. */
|
|
1149
|
+
openRunConfiguration(agent) {
|
|
1150
|
+
this.error.set(null);
|
|
1151
|
+
this.confirming.set(null);
|
|
1152
|
+
this.configuring.set(agent);
|
|
1153
|
+
afterNextRender(() => this.backButton()?.nativeElement.focus(), { injector: this.injector });
|
|
1154
|
+
}
|
|
1155
|
+
/** Returns to the list, refreshed, and to the run configuration action of the agent. */
|
|
1156
|
+
closeRunConfiguration() {
|
|
1157
|
+
const agentId = this.configuring()?.id;
|
|
1158
|
+
this.configuring.set(null);
|
|
1159
|
+
this.refreshAgents();
|
|
1160
|
+
if (agentId !== undefined) {
|
|
1161
|
+
afterNextRender(() => {
|
|
1162
|
+
const action = Array.from(this.host.nativeElement.querySelectorAll('[data-run-config-agent]'))
|
|
1163
|
+
.find(item => item.dataset['runConfigAgent'] === agentId);
|
|
1164
|
+
action?.focus();
|
|
1165
|
+
}, { injector: this.injector });
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
722
1168
|
/** Returns to the list and to the evaluation action of the agent whose comparison was open. */
|
|
723
1169
|
closeEvaluation() {
|
|
724
1170
|
const agentId = this.evaluating()?.id;
|
|
@@ -777,12 +1223,68 @@ class NhAssistantAdminAgentsComponent {
|
|
|
777
1223
|
}
|
|
778
1224
|
/** Opens the reason form of a state change of the agent or, with a skill, of that skill. */
|
|
779
1225
|
askState(agent, state, skill) {
|
|
780
|
-
this.
|
|
781
|
-
this.error.set(null);
|
|
782
|
-
this.stateReason.set('');
|
|
1226
|
+
this.closeForms();
|
|
783
1227
|
this.changingState.set({ agentId: agent.id, skillId: skill?.skillId, state });
|
|
784
1228
|
afterNextRender(() => this.reasonInput()?.nativeElement.focus(), { injector: this.injector });
|
|
785
1229
|
}
|
|
1230
|
+
/**
|
|
1231
|
+
* The shadow modes an agent or skill can change to: the opposite of its own mode, and the mode
|
|
1232
|
+
* in code once an administrator overrode it. A shadow variant, and an agent without runs that
|
|
1233
|
+
* only chats, offer none.
|
|
1234
|
+
*/
|
|
1235
|
+
shadowActions(shadow) {
|
|
1236
|
+
if (!shadow || shadow.locked) {
|
|
1237
|
+
return [];
|
|
1238
|
+
}
|
|
1239
|
+
const own = shadow.override ?? shadow.configured;
|
|
1240
|
+
const actions = [own ? 'live' : 'shadow'];
|
|
1241
|
+
return shadow.override === null ? actions : [...actions, 'code'];
|
|
1242
|
+
}
|
|
1243
|
+
/** Opens the reason form of a shadow mode change of the agent or, with a skill, of that skill. */
|
|
1244
|
+
askShadow(agent, mode, skill) {
|
|
1245
|
+
this.closeForms();
|
|
1246
|
+
this.changingShadow.set({ agentId: agent.id, skillId: skill?.skillId, mode });
|
|
1247
|
+
afterNextRender(() => this.reasonInput()?.nativeElement.focus(), { injector: this.injector });
|
|
1248
|
+
}
|
|
1249
|
+
isChangingShadow(agentId, mode, skillId) {
|
|
1250
|
+
const change = this.changingShadow();
|
|
1251
|
+
return change?.agentId === agentId && change.mode === mode && change.skillId === skillId;
|
|
1252
|
+
}
|
|
1253
|
+
applyShadow(agent) {
|
|
1254
|
+
const change = this.changingShadow();
|
|
1255
|
+
if (change?.agentId !== agent.id) {
|
|
1256
|
+
return;
|
|
1257
|
+
}
|
|
1258
|
+
const reason = this.stateReason().trim();
|
|
1259
|
+
const request = { mode: change.mode, reason: reason.length > 0 ? reason : null };
|
|
1260
|
+
if (change.skillId !== undefined) {
|
|
1261
|
+
this.run(agent.id, this.admin.setSkillShadow(agent.id, change.skillId, request), skill => this.replace(withSkill(agent, skill)));
|
|
1262
|
+
return;
|
|
1263
|
+
}
|
|
1264
|
+
this.run(agent.id, this.admin.setAgentShadow(agent.id, request), state => this.replace(withState(agent, state)));
|
|
1265
|
+
}
|
|
1266
|
+
/** Opens the form that changes the technical account, or returns to the one in code. */
|
|
1267
|
+
askAccount(agent, mode) {
|
|
1268
|
+
this.closeForms();
|
|
1269
|
+
this.changingAccount.set({ agentId: agent.id, mode });
|
|
1270
|
+
afterNextRender(() => this.reasonInput()?.nativeElement.focus(), { injector: this.injector });
|
|
1271
|
+
}
|
|
1272
|
+
isChangingAccount(agentId, mode) {
|
|
1273
|
+
const change = this.changingAccount();
|
|
1274
|
+
return change?.agentId === agentId && change.mode === mode;
|
|
1275
|
+
}
|
|
1276
|
+
applyAccount(agent) {
|
|
1277
|
+
const change = this.changingAccount();
|
|
1278
|
+
if (change?.agentId !== agent.id) {
|
|
1279
|
+
return;
|
|
1280
|
+
}
|
|
1281
|
+
const actorId = change.mode === 'set' ? this.accountId().trim() : null;
|
|
1282
|
+
if (actorId === '') {
|
|
1283
|
+
return;
|
|
1284
|
+
}
|
|
1285
|
+
const reason = this.stateReason().trim();
|
|
1286
|
+
this.run(agent.id, this.admin.setServiceAccount(agent.id, { actorId, reason: reason.length > 0 ? reason : null }), state => this.replace(withState(agent, state)));
|
|
1287
|
+
}
|
|
786
1288
|
isChangingState(agentId, state, skillId) {
|
|
787
1289
|
const change = this.changingState();
|
|
788
1290
|
return change?.agentId === agentId && change.state === state && change.skillId === skillId;
|
|
@@ -805,6 +1307,8 @@ class NhAssistantAdminAgentsComponent {
|
|
|
805
1307
|
}
|
|
806
1308
|
confirm(agentId, action) {
|
|
807
1309
|
this.changingState.set(null);
|
|
1310
|
+
this.changingShadow.set(null);
|
|
1311
|
+
this.changingAccount.set(null);
|
|
808
1312
|
this.confirming.set({ agentId, action });
|
|
809
1313
|
}
|
|
810
1314
|
isConfirming(agentId, action) {
|
|
@@ -823,6 +1327,8 @@ class NhAssistantAdminAgentsComponent {
|
|
|
823
1327
|
this.busyAgentId.set(agentId);
|
|
824
1328
|
this.confirming.set(null);
|
|
825
1329
|
this.changingState.set(null);
|
|
1330
|
+
this.changingShadow.set(null);
|
|
1331
|
+
this.changingAccount.set(null);
|
|
826
1332
|
this.error.set(null);
|
|
827
1333
|
this.actionSubscription?.unsubscribe();
|
|
828
1334
|
this.actionSubscription = request.subscribe({
|
|
@@ -837,15 +1343,37 @@ class NhAssistantAdminAgentsComponent {
|
|
|
837
1343
|
}
|
|
838
1344
|
});
|
|
839
1345
|
}
|
|
1346
|
+
/** Closes the open confirmation and change forms before another opens; one is open at a time. */
|
|
1347
|
+
closeForms() {
|
|
1348
|
+
this.confirming.set(null);
|
|
1349
|
+
this.error.set(null);
|
|
1350
|
+
this.stateReason.set('');
|
|
1351
|
+
this.accountId.set('');
|
|
1352
|
+
this.changingState.set(null);
|
|
1353
|
+
this.changingShadow.set(null);
|
|
1354
|
+
this.changingAccount.set(null);
|
|
1355
|
+
}
|
|
840
1356
|
replace(agent) {
|
|
841
1357
|
this.agents.update(agents => agents.map(item => item.id === agent.id ? agent : item));
|
|
842
1358
|
}
|
|
1359
|
+
/**
|
|
1360
|
+
* Reloads the agents behind the visible list, for example their skills after the run
|
|
1361
|
+
* configuration changed; the list stays as it is when that fails.
|
|
1362
|
+
*/
|
|
1363
|
+
refreshAgents() {
|
|
1364
|
+
this.subscription?.unsubscribe();
|
|
1365
|
+
this.subscription = this.admin.getAgents().subscribe({
|
|
1366
|
+
next: agents => this.agents.set(agents),
|
|
1367
|
+
error: () => undefined
|
|
1368
|
+
});
|
|
1369
|
+
this.refreshChat();
|
|
1370
|
+
}
|
|
843
1371
|
/** Agent changes affect the agent picker of the panel. */
|
|
844
1372
|
refreshChat() {
|
|
845
1373
|
void this.store.reloadStatus();
|
|
846
1374
|
}
|
|
847
1375
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantAdminAgentsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
848
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.33", type: NhAssistantAdminAgentsComponent, isStandalone: true, selector: "nh-assistant-admin-agents", 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 (saved)=\"onSaved($event)\"\n (cancelled)=\"editing.set(undefined)\" />\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: agent.displayName, id: agent.id } }}</p>\n <nh-assistant-agent-feedback-report [agentId]=\"agent.id\" />\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: 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 <p class=\"message error\" role=\"alert\"><nh-assistant-icon name=\"warning\" />{{ errorKeys() | nhAssistantTranslate: failure.code }}</p>\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 <strong>{{ 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 </div>\n @if (agent.description) {\n <p class=\"muted\">{{ 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 {{ 'nh-assistant.admin.agents.state.changed' | translate: { user: agent.stateChangedBy, date: (agent.stateChangedAt | date: 'medium') } }}\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 <div class=\"meta\">\n <span>{{ 'nh-assistant.admin.agents.autonomy.' + agent.autonomy | translate }}</span>\n <span>{{ 'nh-assistant.admin.agents.tool-count' | translate: { count: agent.toolSelectors.length } }}</span>\n <span>{{ '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) {\n <span class=\"mono\">{{ agent.requiredPolicy }}</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 <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.keep' | 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.keep' | 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 <span class=\"mono\">{{ 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 </div>\n <p class=\"muted\">{{ 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) + ' ' + 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 </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: 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.reason-label' | translate }}</label>\n <input\n #reasonInput\n type=\"text\"\n autocomplete=\"off\"\n [id]=\"id + '-' + agent.id + '-reason'\"\n [maxLength]=\"maxReason\"\n [value]=\"stateReason()\"\n (input)=\"stateReason.set(value($event))\" />\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.keep' | translate }}</button>\n </div>\n </form>\n }\n }\n </li>\n }\n </ul>\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 select,:host textarea{width:100%;min-width:0;min-height:38px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:8px 10px;color:var(--nha-text);background:var(--nha-background);font:inherit;font-size:13px}:host input[type=text]:focus-visible,:host input[type=url]:focus-visible,:host input[type=password]:focus-visible,:host input[type=search]:focus-visible,:host select:focus-visible,:host textarea:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host input[type=text]:disabled,:host input[type=url]:disabled,:host input[type=password]:disabled,:host input[type=search]:disabled,:host select:disabled,:host textarea:disabled{cursor:not-allowed;opacity:.6}:host input[type=text][aria-invalid=true],:host input[type=url][aria-invalid=true],:host input[type=password][aria-invalid=true],:host input[type=search][aria-invalid=true],:host select[aria-invalid=true],:host textarea[aria-invalid=true]{border-color:var(--nha-danger)}:host textarea{line-height:1.45;resize:vertical}:host .mono{font-family:var(--nha-mono);font-size:12px}:host .field{display:flex;flex-direction:column;min-width:0}:host .hint{display:flex;justify-content:space-between;gap:8px;margin:4px 2px 0;color:var(--nha-text-muted);font-size:11px}:host .counter{flex:none;font-variant-numeric:tabular-nums}:host .error-text{color:var(--nha-danger)}:host .message{display:flex;align-items:center;gap:8px;margin:0;border:1px solid transparent;border-radius:var(--nha-radius-control);padding:8px 12px;font-size:13px}:host .message.error{border-color:var(--nha-danger);color:var(--nha-text);background:var(--nha-danger-soft)}:host .message.warning{border-color:var(--nha-warning);color:var(--nha-text);background:var(--nha-warning-soft)}:host .message.success{border-color:var(--nha-success);color:var(--nha-text);background:var(--nha-success-soft)}:host .message nh-assistant-icon{flex:none}:host .primary,:host .secondary,:host .danger{display:inline-flex;align-items:center;justify-content:center;gap:6px;min-height:36px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px 14px;font:inherit;font-size:13px;font-weight:700;white-space:nowrap;cursor:pointer}:host .primary:focus-visible,:host .secondary:focus-visible,:host .danger:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .primary:disabled,:host .secondary:disabled,:host .danger:disabled{cursor:not-allowed;opacity:.55}:host .primary{border-color:var(--nha-accent);color:var(--nha-accent-contrast);background:var(--nha-accent)}:host .primary:hover:not(:disabled){filter:brightness(1.12)}:host .secondary{color:var(--nha-text);background:transparent}:host .secondary:hover:not(:disabled){background:var(--nha-surface-strong)}:host .danger{border-color:var(--nha-danger);color:var(--nha-danger);background:transparent}:host .danger:hover:not(:disabled){background:var(--nha-danger-soft)}:host .icon-button{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:1px solid transparent;border-radius:var(--nha-radius-control);color:var(--nha-text-muted);background:transparent;cursor:pointer}:host .icon-button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .icon-button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}:host .icon-button:disabled{cursor:not-allowed;opacity:.5}:host .actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px}:host .badge{display:inline-flex;align-items:center;gap:4px;border-radius:999px;padding:2px 8px;color:var(--nha-text-muted);background:var(--nha-surface-strong);font-size:11px;font-weight:700;white-space:nowrap}:host .badge.success{color:var(--nha-success);background:var(--nha-success-soft)}:host .badge.warning{color:var(--nha-warning);background:var(--nha-warning-soft)}:host .badge.danger{color:var(--nha-danger);background:var(--nha-danger-soft)}:host .visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}.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}.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)}.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}\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"], outputs: ["saved", "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: "pipe", type: DatePipe, name: "date" }, { kind: "pipe", type: TranslatePipe, name: "translate" }, { kind: "pipe", type: _NhAssistantTranslatePipe, name: "nhAssistantTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
1376
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.33", type: NhAssistantAdminAgentsComponent, isStandalone: true, selector: "nh-assistant-admin-agents", 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 (saved)=\"onSaved($event)\"\n (cancelled)=\"editing.set(undefined)\" />\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: 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 [agent]=\"agent\" />\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: 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 <p class=\"message error\" role=\"alert\"><nh-assistant-icon name=\"warning\" />{{ errorKeys() | nhAssistantTranslate: failure.code }}</p>\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 <strong>{{ 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 </div>\n @if (agent.description) {\n <p class=\"muted\">{{ 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 {{ 'nh-assistant.admin.agents.state.changed' | translate: { user: agent.stateChangedBy, date: (agent.stateChangedAt | date: 'medium') } }}\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 {{ 'nh-assistant.admin.agents.shadow.changed' | translate: { user: shadow.changedBy, date: (shadow.changedAt | date: 'medium') } }}\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 class=\"mono\">{{ 'nh-assistant.admin.agents.service-account.label' | translate: { account: account.actorId } }}</span>\n @if (account.overridden) {\n {{ 'nh-assistant.admin.agents.service-account.configured' | translate: { account: account.configuredActorId } }}\n @if (account.reason) {\n {{ 'nh-assistant.admin.agents.state.reason' | translate: { reason: account.reason } }}\n }\n }\n </p>\n }\n <div class=\"meta\">\n <span>{{ 'nh-assistant.admin.agents.autonomy.' + agent.autonomy | translate }}</span>\n <span>{{ 'nh-assistant.admin.agents.tool-count' | translate: { count: agent.toolSelectors.length } }}</span>\n <span>{{ '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) {\n <span class=\"mono\">{{ agent.requiredPolicy }}</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 {{ 'nh-assistant.admin.agents.shadow.action.' + 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 <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.keep' | 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.keep' | 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 <span class=\"mono\">{{ 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\">{{ 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) + ' ' + 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]=\"('nh-assistant.admin.agents.shadow.action.' + mode | translate) + ' ' + skill.skillId\"\n [attr.aria-expanded]=\"isChangingShadow(agent.id, mode, skill.skillId)\"\n [disabled]=\"busyAgentId() !== null\"\n (click)=\"askShadow(agent, mode, skill)\">\n {{ 'nh-assistant.admin.agents.shadow.action.' + 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: 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.reason-label' | translate }}</label>\n <input\n #reasonInput\n type=\"text\"\n autocomplete=\"off\"\n [id]=\"id + '-' + agent.id + '-reason'\"\n [maxLength]=\"maxReason\"\n [value]=\"stateReason()\"\n (input)=\"stateReason.set(value($event))\" />\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.keep' | 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\">{{ 'nh-assistant.admin.agents.shadow.skill-explain.' + change.mode | translate: { skill: change.skillId } }}</p>\n } @else {\n <p class=\"muted\">{{ 'nh-assistant.admin.agents.shadow.explain.' + change.mode | translate }}</p>\n }\n <div class=\"field\">\n <label [for]=\"id + '-' + agent.id + '-shadow-reason'\">{{ 'nh-assistant.admin.agents.state.reason-label' | translate }}</label>\n <input\n #reasonInput\n type=\"text\"\n autocomplete=\"off\"\n [id]=\"id + '-' + agent.id + '-shadow-reason'\"\n [maxLength]=\"maxReason\"\n [value]=\"stateReason()\"\n (input)=\"stateReason.set(value($event))\" />\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.keep' | 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\">{{ 'nh-assistant.admin.agents.service-account.explain.' + change.mode | translate: { account: agent.serviceAccount?.configuredActorId } }}</p>\n @if (change.mode === 'set') {\n <div class=\"field\">\n <label [for]=\"id + '-' + agent.id + '-account'\">{{ 'nh-assistant.admin.agents.service-account.input-label' | translate }}</label>\n <input\n #reasonInput\n type=\"text\"\n autocomplete=\"off\"\n required\n [id]=\"id + '-' + agent.id + '-account'\"\n maxLength=\"256\"\n [value]=\"accountId()\"\n (input)=\"accountId.set(value($event))\" />\n </div>\n }\n <div class=\"field\">\n <label [for]=\"id + '-' + agent.id + '-account-reason'\">{{ 'nh-assistant.admin.agents.state.reason-label' | translate }}</label>\n <input\n type=\"text\"\n autocomplete=\"off\"\n [id]=\"id + '-' + agent.id + '-account-reason'\"\n [maxLength]=\"maxReason\"\n [value]=\"stateReason()\"\n (input)=\"stateReason.set(value($event))\" />\n </div>\n <div class=\"card-actions\">\n <button type=\"submit\" class=\"primary\" [disabled]=\"busyAgentId() !== null || (change.mode === 'set' && accountId().trim() === '')\">\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.keep' | translate }}</button>\n </div>\n </form>\n }\n }\n </li>\n }\n </ul>\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 select,:host textarea{width:100%;min-width:0;min-height:38px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:8px 10px;color:var(--nha-text);background:var(--nha-background);font:inherit;font-size:13px}:host input[type=text]:focus-visible,:host input[type=url]:focus-visible,:host input[type=password]:focus-visible,:host input[type=search]:focus-visible,:host select:focus-visible,:host textarea:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host input[type=text]:disabled,:host input[type=url]:disabled,:host input[type=password]:disabled,:host input[type=search]:disabled,:host select:disabled,:host textarea:disabled{cursor:not-allowed;opacity:.6}:host input[type=text][aria-invalid=true],:host input[type=url][aria-invalid=true],:host input[type=password][aria-invalid=true],:host input[type=search][aria-invalid=true],:host select[aria-invalid=true],:host textarea[aria-invalid=true]{border-color:var(--nha-danger)}:host textarea{line-height:1.45;resize:vertical}:host .mono{font-family:var(--nha-mono);font-size:12px}:host .field{display:flex;flex-direction:column;min-width:0}:host .hint{display:flex;justify-content:space-between;gap:8px;margin:4px 2px 0;color:var(--nha-text-muted);font-size:11px}:host .counter{flex:none;font-variant-numeric:tabular-nums}:host .error-text{color:var(--nha-danger)}:host .message{display:flex;align-items:center;gap:8px;margin:0;border:1px solid transparent;border-radius:var(--nha-radius-control);padding:8px 12px;font-size:13px}:host .message.error{border-color:var(--nha-danger);color:var(--nha-text);background:var(--nha-danger-soft)}:host .message.warning{border-color:var(--nha-warning);color:var(--nha-text);background:var(--nha-warning-soft)}:host .message.success{border-color:var(--nha-success);color:var(--nha-text);background:var(--nha-success-soft)}:host .message nh-assistant-icon{flex:none}:host .primary,:host .secondary,:host .danger{display:inline-flex;align-items:center;justify-content:center;gap:6px;min-height:36px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px 14px;font:inherit;font-size:13px;font-weight:700;white-space:nowrap;cursor:pointer}:host .primary:focus-visible,:host .secondary:focus-visible,:host .danger:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .primary:disabled,:host .secondary:disabled,:host .danger:disabled{cursor:not-allowed;opacity:.55}:host .primary{border-color:var(--nha-accent);color:var(--nha-accent-contrast);background:var(--nha-accent)}:host .primary:hover:not(:disabled){filter:brightness(1.12)}:host .secondary{color:var(--nha-text);background:transparent}:host .secondary:hover:not(:disabled){background:var(--nha-surface-strong)}:host .danger{border-color:var(--nha-danger);color:var(--nha-danger);background:transparent}:host .danger:hover:not(:disabled){background:var(--nha-danger-soft)}:host .icon-button{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:1px solid transparent;border-radius:var(--nha-radius-control);color:var(--nha-text-muted);background:transparent;cursor:pointer}:host .icon-button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .icon-button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}:host .icon-button:disabled{cursor:not-allowed;opacity:.5}:host .actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px}:host .badge{display:inline-flex;align-items:center;gap:4px;border-radius:999px;padding:2px 8px;color:var(--nha-text-muted);background:var(--nha-surface-strong);font-size:11px;font-weight:700;white-space:nowrap}:host .badge.success{color:var(--nha-success);background:var(--nha-success-soft)}:host .badge.warning{color:var(--nha-warning);background:var(--nha-warning-soft)}:host .badge.danger{color:var(--nha-danger);background:var(--nha-danger-soft)}:host .visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}.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}.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)}.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}\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"], outputs: ["saved", "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: NhAssistantAgentRunConfigurationComponent, selector: "nh-assistant-agent-run-configuration", inputs: ["agent"], outputs: ["changed"] }, { kind: "pipe", type: DatePipe, name: "date" }, { kind: "pipe", type: TranslatePipe, name: "translate" }, { kind: "pipe", type: _NhAssistantTranslatePipe, name: "nhAssistantTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
849
1377
|
}
|
|
850
1378
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantAdminAgentsComponent, decorators: [{
|
|
851
1379
|
type: Component,
|
|
@@ -856,8 +1384,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImpo
|
|
|
856
1384
|
_NhAssistantIconComponent,
|
|
857
1385
|
NhAssistantAdminAgentEditorComponent,
|
|
858
1386
|
NhAssistantAgentEvaluationComponent,
|
|
859
|
-
NhAssistantAgentFeedbackReportComponent
|
|
860
|
-
], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (editing() !== undefined) {\n <nh-assistant-admin-agent-editor\n [agent]=\"editing() ?? null\"\n [tools]=\"tools()\"\n [servers]=\"servers()\"\n (saved)=\"onSaved($event)\"\n (cancelled)=\"editing.set(undefined)\" />\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: agent.displayName, id: agent.id } }}</p>\n <nh-assistant-agent-feedback-report [agentId]=\"agent.id\" />\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: 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 <p class=\"message error\" role=\"alert\"><nh-assistant-icon name=\"warning\" />{{ errorKeys() | nhAssistantTranslate: failure.code }}</p>\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 <strong>{{ 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 </div>\n @if (agent.description) {\n <p class=\"muted\">{{ 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 {{ 'nh-assistant.admin.agents.state.changed' | translate: { user: agent.stateChangedBy, date: (agent.stateChangedAt | date: 'medium') } }}\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 <div class=\"meta\">\n <span>{{ 'nh-assistant.admin.agents.autonomy.' + agent.autonomy | translate }}</span>\n <span>{{ 'nh-assistant.admin.agents.tool-count' | translate: { count: agent.toolSelectors.length } }}</span>\n <span>{{ '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) {\n <span class=\"mono\">{{ agent.requiredPolicy }}</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 <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.keep' | 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.keep' | 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 <span class=\"mono\">{{ 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 </div>\n <p class=\"muted\">{{ 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) + ' ' + 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 </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: 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.reason-label' | translate }}</label>\n <input\n #reasonInput\n type=\"text\"\n autocomplete=\"off\"\n [id]=\"id + '-' + agent.id + '-reason'\"\n [maxLength]=\"maxReason\"\n [value]=\"stateReason()\"\n (input)=\"stateReason.set(value($event))\" />\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.keep' | translate }}</button>\n </div>\n </form>\n }\n }\n </li>\n }\n </ul>\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 select,:host textarea{width:100%;min-width:0;min-height:38px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:8px 10px;color:var(--nha-text);background:var(--nha-background);font:inherit;font-size:13px}:host input[type=text]:focus-visible,:host input[type=url]:focus-visible,:host input[type=password]:focus-visible,:host input[type=search]:focus-visible,:host select:focus-visible,:host textarea:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host input[type=text]:disabled,:host input[type=url]:disabled,:host input[type=password]:disabled,:host input[type=search]:disabled,:host select:disabled,:host textarea:disabled{cursor:not-allowed;opacity:.6}:host input[type=text][aria-invalid=true],:host input[type=url][aria-invalid=true],:host input[type=password][aria-invalid=true],:host input[type=search][aria-invalid=true],:host select[aria-invalid=true],:host textarea[aria-invalid=true]{border-color:var(--nha-danger)}:host textarea{line-height:1.45;resize:vertical}:host .mono{font-family:var(--nha-mono);font-size:12px}:host .field{display:flex;flex-direction:column;min-width:0}:host .hint{display:flex;justify-content:space-between;gap:8px;margin:4px 2px 0;color:var(--nha-text-muted);font-size:11px}:host .counter{flex:none;font-variant-numeric:tabular-nums}:host .error-text{color:var(--nha-danger)}:host .message{display:flex;align-items:center;gap:8px;margin:0;border:1px solid transparent;border-radius:var(--nha-radius-control);padding:8px 12px;font-size:13px}:host .message.error{border-color:var(--nha-danger);color:var(--nha-text);background:var(--nha-danger-soft)}:host .message.warning{border-color:var(--nha-warning);color:var(--nha-text);background:var(--nha-warning-soft)}:host .message.success{border-color:var(--nha-success);color:var(--nha-text);background:var(--nha-success-soft)}:host .message nh-assistant-icon{flex:none}:host .primary,:host .secondary,:host .danger{display:inline-flex;align-items:center;justify-content:center;gap:6px;min-height:36px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px 14px;font:inherit;font-size:13px;font-weight:700;white-space:nowrap;cursor:pointer}:host .primary:focus-visible,:host .secondary:focus-visible,:host .danger:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .primary:disabled,:host .secondary:disabled,:host .danger:disabled{cursor:not-allowed;opacity:.55}:host .primary{border-color:var(--nha-accent);color:var(--nha-accent-contrast);background:var(--nha-accent)}:host .primary:hover:not(:disabled){filter:brightness(1.12)}:host .secondary{color:var(--nha-text);background:transparent}:host .secondary:hover:not(:disabled){background:var(--nha-surface-strong)}:host .danger{border-color:var(--nha-danger);color:var(--nha-danger);background:transparent}:host .danger:hover:not(:disabled){background:var(--nha-danger-soft)}:host .icon-button{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:1px solid transparent;border-radius:var(--nha-radius-control);color:var(--nha-text-muted);background:transparent;cursor:pointer}:host .icon-button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .icon-button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}:host .icon-button:disabled{cursor:not-allowed;opacity:.5}:host .actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px}:host .badge{display:inline-flex;align-items:center;gap:4px;border-radius:999px;padding:2px 8px;color:var(--nha-text-muted);background:var(--nha-surface-strong);font-size:11px;font-weight:700;white-space:nowrap}:host .badge.success{color:var(--nha-success);background:var(--nha-success-soft)}:host .badge.warning{color:var(--nha-warning);background:var(--nha-warning-soft)}:host .badge.danger{color:var(--nha-danger);background:var(--nha-danger-soft)}:host .visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}.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}.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)}.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}\n"] }]
|
|
1387
|
+
NhAssistantAgentFeedbackReportComponent,
|
|
1388
|
+
NhAssistantAgentRunConfigurationComponent
|
|
1389
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (editing() !== undefined) {\n <nh-assistant-admin-agent-editor\n [agent]=\"editing() ?? null\"\n [tools]=\"tools()\"\n [servers]=\"servers()\"\n (saved)=\"onSaved($event)\"\n (cancelled)=\"editing.set(undefined)\" />\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: 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 [agent]=\"agent\" />\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: 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 <p class=\"message error\" role=\"alert\"><nh-assistant-icon name=\"warning\" />{{ errorKeys() | nhAssistantTranslate: failure.code }}</p>\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 <strong>{{ 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 </div>\n @if (agent.description) {\n <p class=\"muted\">{{ 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 {{ 'nh-assistant.admin.agents.state.changed' | translate: { user: agent.stateChangedBy, date: (agent.stateChangedAt | date: 'medium') } }}\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 {{ 'nh-assistant.admin.agents.shadow.changed' | translate: { user: shadow.changedBy, date: (shadow.changedAt | date: 'medium') } }}\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 class=\"mono\">{{ 'nh-assistant.admin.agents.service-account.label' | translate: { account: account.actorId } }}</span>\n @if (account.overridden) {\n {{ 'nh-assistant.admin.agents.service-account.configured' | translate: { account: account.configuredActorId } }}\n @if (account.reason) {\n {{ 'nh-assistant.admin.agents.state.reason' | translate: { reason: account.reason } }}\n }\n }\n </p>\n }\n <div class=\"meta\">\n <span>{{ 'nh-assistant.admin.agents.autonomy.' + agent.autonomy | translate }}</span>\n <span>{{ 'nh-assistant.admin.agents.tool-count' | translate: { count: agent.toolSelectors.length } }}</span>\n <span>{{ '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) {\n <span class=\"mono\">{{ agent.requiredPolicy }}</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 {{ 'nh-assistant.admin.agents.shadow.action.' + 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 <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.keep' | 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.keep' | 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 <span class=\"mono\">{{ 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\">{{ 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) + ' ' + 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]=\"('nh-assistant.admin.agents.shadow.action.' + mode | translate) + ' ' + skill.skillId\"\n [attr.aria-expanded]=\"isChangingShadow(agent.id, mode, skill.skillId)\"\n [disabled]=\"busyAgentId() !== null\"\n (click)=\"askShadow(agent, mode, skill)\">\n {{ 'nh-assistant.admin.agents.shadow.action.' + 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: 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.reason-label' | translate }}</label>\n <input\n #reasonInput\n type=\"text\"\n autocomplete=\"off\"\n [id]=\"id + '-' + agent.id + '-reason'\"\n [maxLength]=\"maxReason\"\n [value]=\"stateReason()\"\n (input)=\"stateReason.set(value($event))\" />\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.keep' | 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\">{{ 'nh-assistant.admin.agents.shadow.skill-explain.' + change.mode | translate: { skill: change.skillId } }}</p>\n } @else {\n <p class=\"muted\">{{ 'nh-assistant.admin.agents.shadow.explain.' + change.mode | translate }}</p>\n }\n <div class=\"field\">\n <label [for]=\"id + '-' + agent.id + '-shadow-reason'\">{{ 'nh-assistant.admin.agents.state.reason-label' | translate }}</label>\n <input\n #reasonInput\n type=\"text\"\n autocomplete=\"off\"\n [id]=\"id + '-' + agent.id + '-shadow-reason'\"\n [maxLength]=\"maxReason\"\n [value]=\"stateReason()\"\n (input)=\"stateReason.set(value($event))\" />\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.keep' | 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\">{{ 'nh-assistant.admin.agents.service-account.explain.' + change.mode | translate: { account: agent.serviceAccount?.configuredActorId } }}</p>\n @if (change.mode === 'set') {\n <div class=\"field\">\n <label [for]=\"id + '-' + agent.id + '-account'\">{{ 'nh-assistant.admin.agents.service-account.input-label' | translate }}</label>\n <input\n #reasonInput\n type=\"text\"\n autocomplete=\"off\"\n required\n [id]=\"id + '-' + agent.id + '-account'\"\n maxLength=\"256\"\n [value]=\"accountId()\"\n (input)=\"accountId.set(value($event))\" />\n </div>\n }\n <div class=\"field\">\n <label [for]=\"id + '-' + agent.id + '-account-reason'\">{{ 'nh-assistant.admin.agents.state.reason-label' | translate }}</label>\n <input\n type=\"text\"\n autocomplete=\"off\"\n [id]=\"id + '-' + agent.id + '-account-reason'\"\n [maxLength]=\"maxReason\"\n [value]=\"stateReason()\"\n (input)=\"stateReason.set(value($event))\" />\n </div>\n <div class=\"card-actions\">\n <button type=\"submit\" class=\"primary\" [disabled]=\"busyAgentId() !== null || (change.mode === 'set' && accountId().trim() === '')\">\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.keep' | translate }}</button>\n </div>\n </form>\n }\n }\n </li>\n }\n </ul>\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 select,:host textarea{width:100%;min-width:0;min-height:38px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:8px 10px;color:var(--nha-text);background:var(--nha-background);font:inherit;font-size:13px}:host input[type=text]:focus-visible,:host input[type=url]:focus-visible,:host input[type=password]:focus-visible,:host input[type=search]:focus-visible,:host select:focus-visible,:host textarea:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host input[type=text]:disabled,:host input[type=url]:disabled,:host input[type=password]:disabled,:host input[type=search]:disabled,:host select:disabled,:host textarea:disabled{cursor:not-allowed;opacity:.6}:host input[type=text][aria-invalid=true],:host input[type=url][aria-invalid=true],:host input[type=password][aria-invalid=true],:host input[type=search][aria-invalid=true],:host select[aria-invalid=true],:host textarea[aria-invalid=true]{border-color:var(--nha-danger)}:host textarea{line-height:1.45;resize:vertical}:host .mono{font-family:var(--nha-mono);font-size:12px}:host .field{display:flex;flex-direction:column;min-width:0}:host .hint{display:flex;justify-content:space-between;gap:8px;margin:4px 2px 0;color:var(--nha-text-muted);font-size:11px}:host .counter{flex:none;font-variant-numeric:tabular-nums}:host .error-text{color:var(--nha-danger)}:host .message{display:flex;align-items:center;gap:8px;margin:0;border:1px solid transparent;border-radius:var(--nha-radius-control);padding:8px 12px;font-size:13px}:host .message.error{border-color:var(--nha-danger);color:var(--nha-text);background:var(--nha-danger-soft)}:host .message.warning{border-color:var(--nha-warning);color:var(--nha-text);background:var(--nha-warning-soft)}:host .message.success{border-color:var(--nha-success);color:var(--nha-text);background:var(--nha-success-soft)}:host .message nh-assistant-icon{flex:none}:host .primary,:host .secondary,:host .danger{display:inline-flex;align-items:center;justify-content:center;gap:6px;min-height:36px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px 14px;font:inherit;font-size:13px;font-weight:700;white-space:nowrap;cursor:pointer}:host .primary:focus-visible,:host .secondary:focus-visible,:host .danger:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .primary:disabled,:host .secondary:disabled,:host .danger:disabled{cursor:not-allowed;opacity:.55}:host .primary{border-color:var(--nha-accent);color:var(--nha-accent-contrast);background:var(--nha-accent)}:host .primary:hover:not(:disabled){filter:brightness(1.12)}:host .secondary{color:var(--nha-text);background:transparent}:host .secondary:hover:not(:disabled){background:var(--nha-surface-strong)}:host .danger{border-color:var(--nha-danger);color:var(--nha-danger);background:transparent}:host .danger:hover:not(:disabled){background:var(--nha-danger-soft)}:host .icon-button{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:1px solid transparent;border-radius:var(--nha-radius-control);color:var(--nha-text-muted);background:transparent;cursor:pointer}:host .icon-button:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}:host .icon-button:hover:not(:disabled){color:var(--nha-text);background:var(--nha-surface-strong)}:host .icon-button:disabled{cursor:not-allowed;opacity:.5}:host .actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px}:host .badge{display:inline-flex;align-items:center;gap:4px;border-radius:999px;padding:2px 8px;color:var(--nha-text-muted);background:var(--nha-surface-strong);font-size:11px;font-weight:700;white-space:nowrap}:host .badge.success{color:var(--nha-success);background:var(--nha-success-soft)}:host .badge.warning{color:var(--nha-warning);background:var(--nha-warning-soft)}:host .badge.danger{color:var(--nha-danger);background:var(--nha-danger-soft)}:host .visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}.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}.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)}.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}\n"] }]
|
|
861
1390
|
}], ctorParameters: () => [], propDecorators: { backButton: [{ type: i0.ViewChild, args: ['back', { isSignal: true }] }], reasonInput: [{ type: i0.ViewChild, args: ['reasonInput', { isSignal: true }] }] } });
|
|
862
1391
|
function withState(agent, state) {
|
|
863
1392
|
return {
|
|
@@ -868,7 +1397,9 @@ function withState(agent, state) {
|
|
|
868
1397
|
stateChangedBy: state.changedBy,
|
|
869
1398
|
stateReason: state.reason,
|
|
870
1399
|
waitingInputs: state.waitingInputs,
|
|
871
|
-
skills: state.skills ?? agent.skills
|
|
1400
|
+
skills: state.skills ?? agent.skills,
|
|
1401
|
+
shadow: state.shadow ?? agent.shadow,
|
|
1402
|
+
serviceAccount: state.serviceAccount === undefined ? agent.serviceAccount : state.serviceAccount
|
|
872
1403
|
};
|
|
873
1404
|
}
|
|
874
1405
|
function withSkill(agent, skill) {
|
|
@@ -1304,5 +1835,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImpo
|
|
|
1304
1835
|
* Generated bundle index. Do not edit.
|
|
1305
1836
|
*/
|
|
1306
1837
|
|
|
1307
|
-
export { NH_ASSISTANT_MAX_INSTRUCTIONS, NhAssistantAdminAgentEditorComponent, NhAssistantAdminAgentsComponent, NhAssistantAdminComponent, NhAssistantAdminContextComponent, NhAssistantAdminMcpServerEditorComponent, NhAssistantAdminMcpServersComponent, NhAssistantAdminMcpToolsComponent, NhAssistantAgentEvaluationComponent, NhAssistantAgentFeedbackReportComponent, nhAssistantSelectorMatches };
|
|
1838
|
+
export { NH_ASSISTANT_MAX_INSTRUCTIONS, NhAssistantAdminAgentEditorComponent, NhAssistantAdminAgentsComponent, NhAssistantAdminComponent, NhAssistantAdminContextComponent, NhAssistantAdminMcpServerEditorComponent, NhAssistantAdminMcpServersComponent, NhAssistantAdminMcpToolsComponent, NhAssistantAgentEvaluationComponent, NhAssistantAgentFeedbackReportComponent, NhAssistantAgentRunConfigurationComponent, nhAssistantSelectorMatches };
|
|
1308
1839
|
//# sourceMappingURL=newheap-platform-ai-chat-admin.mjs.map
|