@newheap/platform-ai-chat 0.1.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/LICENSE +201 -0
- package/NOTICE +4 -0
- package/README.md +294 -0
- package/admin/index.d.ts +294 -0
- package/fesm2022/newheap-platform-ai-chat-admin.mjs +785 -0
- package/fesm2022/newheap-platform-ai-chat-admin.mjs.map +1 -0
- package/fesm2022/newheap-platform-ai-chat-testing.mjs +911 -0
- package/fesm2022/newheap-platform-ai-chat-testing.mjs.map +1 -0
- package/fesm2022/newheap-platform-ai-chat.mjs +3103 -0
- package/fesm2022/newheap-platform-ai-chat.mjs.map +1 -0
- package/i18n/en.json +376 -0
- package/i18n/nl.json +376 -0
- package/index.d.ts +937 -0
- package/package.json +54 -0
- package/testing/index.d.ts +189 -0
|
@@ -0,0 +1,785 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { inject, signal, computed, DestroyRef, ChangeDetectionStrategy, Component, input, output, effect, untracked, ElementRef, afterNextRender } from '@angular/core';
|
|
3
|
+
import { TranslatePipe } from '@ngx-translate/core';
|
|
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, NhAssistantStore, nhAssistantErrorMessageKey } from '@newheap/platform-ai-chat';
|
|
5
|
+
import { forkJoin, Subscription } from 'rxjs';
|
|
6
|
+
import { DatePipe } from '@angular/common';
|
|
7
|
+
|
|
8
|
+
/** Maximum length of the application context and of agent instructions. */
|
|
9
|
+
const NH_ASSISTANT_MAX_INSTRUCTIONS = 20_000;
|
|
10
|
+
let nextId$6 = 0;
|
|
11
|
+
/**
|
|
12
|
+
* Editor of the application context: the current version with its hash, optimistic
|
|
13
|
+
* concurrency through `expectedVersion`, and the version history.
|
|
14
|
+
*/
|
|
15
|
+
class NhAssistantAdminContextComponent {
|
|
16
|
+
constructor() {
|
|
17
|
+
this.admin = inject(NhAssistantAdminApiService);
|
|
18
|
+
this.id = `nh-assistant-admin-context-${nextId$6++}`;
|
|
19
|
+
this.max = NH_ASSISTANT_MAX_INSTRUCTIONS;
|
|
20
|
+
this.loading = signal(true, ...(ngDevMode ? [{ debugName: "loading" }] : []));
|
|
21
|
+
this.loadFailed = signal(false, ...(ngDevMode ? [{ debugName: "loadFailed" }] : []));
|
|
22
|
+
this.saving = signal(false, ...(ngDevMode ? [{ debugName: "saving" }] : []));
|
|
23
|
+
this.saved = signal(false, ...(ngDevMode ? [{ debugName: "saved" }] : []));
|
|
24
|
+
this.conflict = signal(false, ...(ngDevMode ? [{ debugName: "conflict" }] : []));
|
|
25
|
+
this.error = signal(null, ...(ngDevMode ? [{ debugName: "error" }] : []));
|
|
26
|
+
this.context = signal(null, ...(ngDevMode ? [{ debugName: "context" }] : []));
|
|
27
|
+
this.versions = signal([], ...(ngDevMode ? [{ debugName: "versions" }] : []));
|
|
28
|
+
this.text = signal('', ...(ngDevMode ? [{ debugName: "text" }] : []));
|
|
29
|
+
this.tooLong = computed(() => this.text().length > this.max, ...(ngDevMode ? [{ debugName: "tooLong" }] : []));
|
|
30
|
+
this.dirty = computed(() => this.text() !== (this.context()?.text ?? ''), ...(ngDevMode ? [{ debugName: "dirty" }] : []));
|
|
31
|
+
this.canSave = computed(() => !this.saving() && !this.tooLong() && this.dirty() && this.context() !== null, ...(ngDevMode ? [{ debugName: "canSave" }] : []));
|
|
32
|
+
this.errorKeys = computed(() => _nhAssistantErrorKeys(this.error()), ...(ngDevMode ? [{ debugName: "errorKeys" }] : []));
|
|
33
|
+
this.history = computed(() => this.versions().filter(version => version.version !== this.context()?.version), ...(ngDevMode ? [{ debugName: "history" }] : []));
|
|
34
|
+
inject(DestroyRef).onDestroy(() => this.subscription?.unsubscribe());
|
|
35
|
+
this.load(false);
|
|
36
|
+
}
|
|
37
|
+
/** Loads the latest version; with `keepDraft` the user's unsaved text stays in the editor. */
|
|
38
|
+
load(keepDraft) {
|
|
39
|
+
this.loading.set(!keepDraft);
|
|
40
|
+
this.loadFailed.set(false);
|
|
41
|
+
this.subscription?.unsubscribe();
|
|
42
|
+
this.subscription = forkJoin([this.admin.getContext(), this.admin.getContextVersions()]).subscribe({
|
|
43
|
+
next: ([context, versions]) => {
|
|
44
|
+
const draft = this.text();
|
|
45
|
+
this.context.set(context);
|
|
46
|
+
this.versions.set(versions);
|
|
47
|
+
this.text.set(keepDraft ? draft : context.text);
|
|
48
|
+
this.conflict.set(false);
|
|
49
|
+
this.error.set(null);
|
|
50
|
+
this.loading.set(false);
|
|
51
|
+
},
|
|
52
|
+
error: () => {
|
|
53
|
+
this.loading.set(false);
|
|
54
|
+
this.loadFailed.set(true);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
onInput(event) {
|
|
59
|
+
this.text.set(event.target.value);
|
|
60
|
+
this.saved.set(false);
|
|
61
|
+
}
|
|
62
|
+
save() {
|
|
63
|
+
const context = this.context();
|
|
64
|
+
if (!context || !this.canSave()) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
this.saving.set(true);
|
|
68
|
+
this.saved.set(false);
|
|
69
|
+
this.error.set(null);
|
|
70
|
+
this.subscription?.unsubscribe();
|
|
71
|
+
this.subscription = this.admin.updateContext({ text: this.text(), expectedVersion: context.version }).subscribe({
|
|
72
|
+
next: updated => {
|
|
73
|
+
this.context.set(updated);
|
|
74
|
+
this.versions.update(versions => [
|
|
75
|
+
{ version: updated.version, hash: updated.hash, updatedAt: updated.updatedAt, updatedBy: updated.updatedBy },
|
|
76
|
+
...versions
|
|
77
|
+
]);
|
|
78
|
+
this.text.set(updated.text);
|
|
79
|
+
this.saving.set(false);
|
|
80
|
+
this.saved.set(true);
|
|
81
|
+
},
|
|
82
|
+
error: (error) => {
|
|
83
|
+
const failure = _toNhAssistantError(error);
|
|
84
|
+
this.saving.set(false);
|
|
85
|
+
this.conflict.set(failure.code === NhAssistantClientErrorCodes.versionConflict);
|
|
86
|
+
this.error.set(this.conflict() ? null : failure);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: NhAssistantAdminContextComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
91
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.28", type: NhAssistantAdminContextComponent, isStandalone: true, selector: "nh-assistant-admin-context", ngImport: i0, template: "<section class=\"section\" [attr.aria-labelledby]=\"id + '-title'\" [attr.aria-busy]=\"loading() || saving()\">\n <header class=\"section-header\">\n <div>\n <h3 [id]=\"id + '-title'\">{{ 'nh-assistant.admin.context.title' | translate }}</h3>\n <p class=\"muted\">{{ 'nh-assistant.admin.context.description' | translate }}</p>\n </div>\n </header>\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(false)\">{{ 'nh-assistant.admin.retry' | translate }}</button>\n </div>\n } @else if (context(); as current) {\n <form class=\"stack\" (submit)=\"$event.preventDefault(); save()\">\n <div class=\"meta\">\n <span class=\"badge\">{{ 'nh-assistant.admin.context.version' | translate: { version: current.version } }}</span>\n <span>\n @if (current.updatedBy) {\n {{ 'nh-assistant.admin.context.updated' | translate: { date: (current.updatedAt | date: 'medium'), user: current.updatedBy } }}\n } @else {\n {{ 'nh-assistant.admin.context.updated-unknown' | translate: { date: (current.updatedAt | date: 'medium') } }}\n }\n </span>\n <span class=\"mono\" [attr.title]=\"current.hash\">{{ 'nh-assistant.admin.context.hash' | translate }} {{ current.hash.slice(0, 18) }}</span>\n </div>\n\n <div class=\"field\">\n <label [for]=\"id + '-text'\">{{ 'nh-assistant.admin.context.label' | translate }}</label>\n <textarea\n class=\"large\"\n rows=\"14\"\n [id]=\"id + '-text'\"\n [value]=\"text()\"\n [attr.aria-describedby]=\"id + '-hint'\"\n [attr.aria-invalid]=\"tooLong() ? true : null\"\n (input)=\"onInput($event)\"></textarea>\n <p class=\"hint\" [id]=\"id + '-hint'\">\n <span [class.error-text]=\"tooLong()\">{{ tooLong() ? ('nh-assistant.admin.too-long' | translate) : '' }}</span>\n <span class=\"counter\" [class.error-text]=\"tooLong()\">{{ 'nh-assistant.admin.characters' | translate: { count: text().length, max: max } }}</span>\n </p>\n </div>\n\n @if (conflict()) {\n <div class=\"message warning\" role=\"alert\">\n <nh-assistant-icon name=\"warning\" />\n <span class=\"grow\">{{ 'nh-assistant.admin.context.conflict' | translate }}</span>\n <button class=\"secondary\" type=\"button\" (click)=\"load(true)\">\n <nh-assistant-icon name=\"refresh\" />{{ 'nh-assistant.admin.context.reload' | translate }}\n </button>\n </div>\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 @if (saved()) {\n <p class=\"message success\" role=\"status\"><nh-assistant-icon name=\"check\" />{{ 'nh-assistant.admin.saved' | translate }}</p>\n }\n\n <div class=\"actions\">\n <button class=\"primary\" type=\"submit\" [disabled]=\"!canSave()\">\n {{ (saving() ? 'nh-assistant.admin.saving' : 'nh-assistant.admin.save') | translate }}\n </button>\n </div>\n </form>\n\n <section class=\"subsection\" [attr.aria-labelledby]=\"id + '-history'\">\n <h4 [id]=\"id + '-history'\">{{ 'nh-assistant.admin.context.history' | translate }}</h4>\n @if (history().length === 0) {\n <p class=\"muted\">{{ 'nh-assistant.admin.context.history-empty' | translate }}</p>\n } @else {\n <ol class=\"history\">\n @for (version of history(); track version.version) {\n <li>\n <span class=\"badge\">{{ 'nh-assistant.admin.context.version' | translate: { version: version.version } }}</span>\n <span>{{ version.updatedAt | date: 'medium' }}</span>\n @if (version.updatedBy) {\n <span>{{ version.updatedBy }}</span>\n }\n <span class=\"mono muted\" [attr.title]=\"version.hash\">{{ version.hash.slice(0, 18) }}</span>\n </li>\n }\n </ol>\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}\n"], dependencies: [{ kind: "component", type: _NhAssistantIconComponent, selector: "nh-assistant-icon", inputs: ["name"] }, { kind: "pipe", type: DatePipe, name: "date" }, { kind: "pipe", type: TranslatePipe, name: "translate" }, { kind: "pipe", type: _NhAssistantTranslatePipe, name: "nhAssistantTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
92
|
+
}
|
|
93
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: NhAssistantAdminContextComponent, decorators: [{
|
|
94
|
+
type: Component,
|
|
95
|
+
args: [{ selector: 'nh-assistant-admin-context', standalone: true, imports: [DatePipe, TranslatePipe, _NhAssistantTranslatePipe, _NhAssistantIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<section class=\"section\" [attr.aria-labelledby]=\"id + '-title'\" [attr.aria-busy]=\"loading() || saving()\">\n <header class=\"section-header\">\n <div>\n <h3 [id]=\"id + '-title'\">{{ 'nh-assistant.admin.context.title' | translate }}</h3>\n <p class=\"muted\">{{ 'nh-assistant.admin.context.description' | translate }}</p>\n </div>\n </header>\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(false)\">{{ 'nh-assistant.admin.retry' | translate }}</button>\n </div>\n } @else if (context(); as current) {\n <form class=\"stack\" (submit)=\"$event.preventDefault(); save()\">\n <div class=\"meta\">\n <span class=\"badge\">{{ 'nh-assistant.admin.context.version' | translate: { version: current.version } }}</span>\n <span>\n @if (current.updatedBy) {\n {{ 'nh-assistant.admin.context.updated' | translate: { date: (current.updatedAt | date: 'medium'), user: current.updatedBy } }}\n } @else {\n {{ 'nh-assistant.admin.context.updated-unknown' | translate: { date: (current.updatedAt | date: 'medium') } }}\n }\n </span>\n <span class=\"mono\" [attr.title]=\"current.hash\">{{ 'nh-assistant.admin.context.hash' | translate }} {{ current.hash.slice(0, 18) }}</span>\n </div>\n\n <div class=\"field\">\n <label [for]=\"id + '-text'\">{{ 'nh-assistant.admin.context.label' | translate }}</label>\n <textarea\n class=\"large\"\n rows=\"14\"\n [id]=\"id + '-text'\"\n [value]=\"text()\"\n [attr.aria-describedby]=\"id + '-hint'\"\n [attr.aria-invalid]=\"tooLong() ? true : null\"\n (input)=\"onInput($event)\"></textarea>\n <p class=\"hint\" [id]=\"id + '-hint'\">\n <span [class.error-text]=\"tooLong()\">{{ tooLong() ? ('nh-assistant.admin.too-long' | translate) : '' }}</span>\n <span class=\"counter\" [class.error-text]=\"tooLong()\">{{ 'nh-assistant.admin.characters' | translate: { count: text().length, max: max } }}</span>\n </p>\n </div>\n\n @if (conflict()) {\n <div class=\"message warning\" role=\"alert\">\n <nh-assistant-icon name=\"warning\" />\n <span class=\"grow\">{{ 'nh-assistant.admin.context.conflict' | translate }}</span>\n <button class=\"secondary\" type=\"button\" (click)=\"load(true)\">\n <nh-assistant-icon name=\"refresh\" />{{ 'nh-assistant.admin.context.reload' | translate }}\n </button>\n </div>\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 @if (saved()) {\n <p class=\"message success\" role=\"status\"><nh-assistant-icon name=\"check\" />{{ 'nh-assistant.admin.saved' | translate }}</p>\n }\n\n <div class=\"actions\">\n <button class=\"primary\" type=\"submit\" [disabled]=\"!canSave()\">\n {{ (saving() ? 'nh-assistant.admin.saving' : 'nh-assistant.admin.save') | translate }}\n </button>\n </div>\n </form>\n\n <section class=\"subsection\" [attr.aria-labelledby]=\"id + '-history'\">\n <h4 [id]=\"id + '-history'\">{{ 'nh-assistant.admin.context.history' | translate }}</h4>\n @if (history().length === 0) {\n <p class=\"muted\">{{ 'nh-assistant.admin.context.history-empty' | translate }}</p>\n } @else {\n <ol class=\"history\">\n @for (version of history(); track version.version) {\n <li>\n <span class=\"badge\">{{ 'nh-assistant.admin.context.version' | translate: { version: version.version } }}</span>\n <span>{{ version.updatedAt | date: 'medium' }}</span>\n @if (version.updatedBy) {\n <span>{{ version.updatedBy }}</span>\n }\n <span class=\"mono muted\" [attr.title]=\"version.hash\">{{ version.hash.slice(0, 18) }}</span>\n </li>\n }\n </ol>\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}\n"] }]
|
|
96
|
+
}], ctorParameters: () => [] });
|
|
97
|
+
|
|
98
|
+
/** True when a tool id matches a selector glob (`*` matches any characters). */
|
|
99
|
+
function nhAssistantSelectorMatches(toolId, selector) {
|
|
100
|
+
const pattern = selector.split('*').map(part => part.replace(/[.+?^${}()|[\]\\]/g, '\\$&')).join('.*');
|
|
101
|
+
return new RegExp(`^${pattern}$`).test(toolId);
|
|
102
|
+
}
|
|
103
|
+
const selectorPattern = /^[a-z0-9*][a-z0-9.*_-]*$/i;
|
|
104
|
+
let nextId$5 = 0;
|
|
105
|
+
/**
|
|
106
|
+
* Creates or edits one agent: texts, autonomy, policy, tool selectors chosen from the tool
|
|
107
|
+
* catalog or typed as patterns, and assigned MCP servers.
|
|
108
|
+
*/
|
|
109
|
+
class NhAssistantAdminAgentEditorComponent {
|
|
110
|
+
constructor() {
|
|
111
|
+
this.admin = inject(NhAssistantAdminApiService);
|
|
112
|
+
/** The agent to edit, or null for a new agent. */
|
|
113
|
+
this.agent = input(null, ...(ngDevMode ? [{ debugName: "agent" }] : []));
|
|
114
|
+
this.tools = input([], ...(ngDevMode ? [{ debugName: "tools" }] : []));
|
|
115
|
+
this.servers = input([], ...(ngDevMode ? [{ debugName: "servers" }] : []));
|
|
116
|
+
this.saved = output();
|
|
117
|
+
this.cancelled = output();
|
|
118
|
+
this.id = `nh-assistant-agent-editor-${nextId$5++}`;
|
|
119
|
+
this.max = NH_ASSISTANT_MAX_INSTRUCTIONS;
|
|
120
|
+
this.autonomyLevels = ['observe', 'explain', 'propose', 'simulate', 'execute'];
|
|
121
|
+
this.agentId = signal('', ...(ngDevMode ? [{ debugName: "agentId" }] : []));
|
|
122
|
+
this.displayName = signal('', ...(ngDevMode ? [{ debugName: "displayName" }] : []));
|
|
123
|
+
this.description = signal('', ...(ngDevMode ? [{ debugName: "description" }] : []));
|
|
124
|
+
this.instructions = signal('', ...(ngDevMode ? [{ debugName: "instructions" }] : []));
|
|
125
|
+
this.autonomy = signal('explain', ...(ngDevMode ? [{ debugName: "autonomy" }] : []));
|
|
126
|
+
this.requiredPolicy = signal('', ...(ngDevMode ? [{ debugName: "requiredPolicy" }] : []));
|
|
127
|
+
this.isEnabled = signal(true, ...(ngDevMode ? [{ debugName: "isEnabled" }] : []));
|
|
128
|
+
this.toolSelectors = signal([], ...(ngDevMode ? [{ debugName: "toolSelectors" }] : []));
|
|
129
|
+
this.mcpServerIds = signal([], ...(ngDevMode ? [{ debugName: "mcpServerIds" }] : []));
|
|
130
|
+
this.newSelector = signal('', ...(ngDevMode ? [{ debugName: "newSelector" }] : []));
|
|
131
|
+
this.catalogFilter = signal('', ...(ngDevMode ? [{ debugName: "catalogFilter" }] : []));
|
|
132
|
+
this.submitted = signal(false, ...(ngDevMode ? [{ debugName: "submitted" }] : []));
|
|
133
|
+
this.saving = signal(false, ...(ngDevMode ? [{ debugName: "saving" }] : []));
|
|
134
|
+
this.error = signal(null, ...(ngDevMode ? [{ debugName: "error" }] : []));
|
|
135
|
+
this.isNew = computed(() => this.agent() === null, ...(ngDevMode ? [{ debugName: "isNew" }] : []));
|
|
136
|
+
this.idInvalid = computed(() => this.isNew() && !_NH_ASSISTANT_DASH_CASE.test(this.agentId()), ...(ngDevMode ? [{ debugName: "idInvalid" }] : []));
|
|
137
|
+
this.nameMissing = computed(() => this.displayName().trim().length === 0, ...(ngDevMode ? [{ debugName: "nameMissing" }] : []));
|
|
138
|
+
this.tooLong = computed(() => this.instructions().length > this.max, ...(ngDevMode ? [{ debugName: "tooLong" }] : []));
|
|
139
|
+
this.selectorInvalid = computed(() => this.newSelector().trim().length > 0 && !selectorPattern.test(this.newSelector().trim()), ...(ngDevMode ? [{ debugName: "selectorInvalid" }] : []));
|
|
140
|
+
this.valid = computed(() => !this.idInvalid() && !this.nameMissing() && !this.tooLong(), ...(ngDevMode ? [{ debugName: "valid" }] : []));
|
|
141
|
+
this.errorKeys = computed(() => _nhAssistantErrorKeys(this.error()), ...(ngDevMode ? [{ debugName: "errorKeys" }] : []));
|
|
142
|
+
this.filteredTools = computed(() => {
|
|
143
|
+
const filter = this.catalogFilter().trim().toLowerCase();
|
|
144
|
+
return this.tools().filter(tool => !filter ||
|
|
145
|
+
tool.id.toLowerCase().includes(filter) || tool.description.toLowerCase().includes(filter));
|
|
146
|
+
}, ...(ngDevMode ? [{ debugName: "filteredTools" }] : []));
|
|
147
|
+
this.matchCount = computed(() => this.tools()
|
|
148
|
+
.filter(tool => this.toolSelectors().some(selector => nhAssistantSelectorMatches(tool.id, selector))).length, ...(ngDevMode ? [{ debugName: "matchCount" }] : []));
|
|
149
|
+
inject(DestroyRef).onDestroy(() => this.subscription?.unsubscribe());
|
|
150
|
+
effect(() => {
|
|
151
|
+
const agent = this.agent();
|
|
152
|
+
untracked(() => this.reset(agent));
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
isSelected(toolId) {
|
|
156
|
+
return this.toolSelectors().includes(toolId);
|
|
157
|
+
}
|
|
158
|
+
isCovered(toolId) {
|
|
159
|
+
return this.toolSelectors().some(selector => selector !== toolId && nhAssistantSelectorMatches(toolId, selector));
|
|
160
|
+
}
|
|
161
|
+
toggleTool(toolId, checked) {
|
|
162
|
+
this.toolSelectors.update(selectors => checked
|
|
163
|
+
? [...selectors.filter(selector => selector !== toolId), toolId]
|
|
164
|
+
: selectors.filter(selector => selector !== toolId));
|
|
165
|
+
}
|
|
166
|
+
addSelector() {
|
|
167
|
+
const selector = this.newSelector().trim();
|
|
168
|
+
if (selector.length === 0 || this.selectorInvalid()) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
this.toolSelectors.update(selectors => selectors.includes(selector) ? selectors : [...selectors, selector]);
|
|
172
|
+
this.newSelector.set('');
|
|
173
|
+
}
|
|
174
|
+
onSelectorKeydown(event) {
|
|
175
|
+
if (event.key === 'Enter') {
|
|
176
|
+
event.preventDefault();
|
|
177
|
+
this.addSelector();
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
removeSelector(selector) {
|
|
181
|
+
this.toolSelectors.update(selectors => selectors.filter(item => item !== selector));
|
|
182
|
+
}
|
|
183
|
+
toggleServer(serverId, checked) {
|
|
184
|
+
this.mcpServerIds.update(ids => checked ? [...ids.filter(id => id !== serverId), serverId] : ids.filter(id => id !== serverId));
|
|
185
|
+
}
|
|
186
|
+
value(event) {
|
|
187
|
+
return event.target.value;
|
|
188
|
+
}
|
|
189
|
+
checked(event) {
|
|
190
|
+
return event.target.checked;
|
|
191
|
+
}
|
|
192
|
+
save() {
|
|
193
|
+
this.submitted.set(true);
|
|
194
|
+
if (!this.valid() || this.saving()) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
const policy = this.requiredPolicy().trim();
|
|
198
|
+
const input = {
|
|
199
|
+
id: this.isNew() ? this.agentId() : this.agent().id,
|
|
200
|
+
displayName: this.displayName().trim(),
|
|
201
|
+
description: this.description().trim(),
|
|
202
|
+
instructions: this.instructions(),
|
|
203
|
+
toolSelectors: this.toolSelectors(),
|
|
204
|
+
mcpServerIds: this.mcpServerIds(),
|
|
205
|
+
requiredPolicy: policy.length > 0 ? policy : null,
|
|
206
|
+
autonomy: this.autonomy(),
|
|
207
|
+
isEnabled: this.isEnabled()
|
|
208
|
+
};
|
|
209
|
+
const existing = this.agent();
|
|
210
|
+
const request = existing
|
|
211
|
+
? this.admin.updateAgent(existing.id, { ...input, expectedVersion: existing.version })
|
|
212
|
+
: this.admin.createAgent(input);
|
|
213
|
+
this.saving.set(true);
|
|
214
|
+
this.error.set(null);
|
|
215
|
+
this.subscription?.unsubscribe();
|
|
216
|
+
this.subscription = request.subscribe({
|
|
217
|
+
next: agent => {
|
|
218
|
+
this.saving.set(false);
|
|
219
|
+
this.saved.emit(agent);
|
|
220
|
+
},
|
|
221
|
+
error: (error) => {
|
|
222
|
+
this.saving.set(false);
|
|
223
|
+
this.error.set(_toNhAssistantError(error));
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
reset(agent) {
|
|
228
|
+
this.agentId.set(agent?.id ?? '');
|
|
229
|
+
this.displayName.set(agent?.displayName ?? '');
|
|
230
|
+
this.description.set(agent?.description ?? '');
|
|
231
|
+
this.instructions.set(agent?.instructions ?? '');
|
|
232
|
+
this.autonomy.set(agent?.autonomy ?? 'explain');
|
|
233
|
+
this.requiredPolicy.set(agent?.requiredPolicy ?? '');
|
|
234
|
+
this.isEnabled.set(agent?.isEnabled ?? true);
|
|
235
|
+
this.toolSelectors.set([...(agent?.toolSelectors ?? [])]);
|
|
236
|
+
this.mcpServerIds.set([...(agent?.mcpServerIds ?? [])]);
|
|
237
|
+
this.submitted.set(false);
|
|
238
|
+
this.error.set(null);
|
|
239
|
+
}
|
|
240
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: NhAssistantAdminAgentEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
241
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.28", type: NhAssistantAdminAgentEditorComponent, isStandalone: true, selector: "nh-assistant-admin-agent-editor", inputs: { agent: { classPropertyName: "agent", publicName: "agent", isSignal: true, isRequired: false, transformFunction: null }, tools: { classPropertyName: "tools", publicName: "tools", isSignal: true, isRequired: false, transformFunction: null }, servers: { classPropertyName: "servers", publicName: "servers", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { saved: "saved", cancelled: "cancelled" }, ngImport: i0, template: "<form class=\"section card\" [attr.aria-labelledby]=\"id + '-title'\" [attr.aria-busy]=\"saving()\" (submit)=\"$event.preventDefault(); save()\">\n <h3 [id]=\"id + '-title'\">\n @if (agent(); as current) {\n {{ 'nh-assistant.admin.agent-editor.edit-title' | translate: { name: current.displayName } }}\n } @else {\n {{ 'nh-assistant.admin.agent-editor.new-title' | translate }}\n }\n </h3>\n\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-id'\">{{ 'nh-assistant.admin.agent-editor.id' | translate }}</label>\n <input\n type=\"text\"\n class=\"mono\"\n autocomplete=\"off\"\n [id]=\"id + '-id'\"\n [value]=\"agentId()\"\n [disabled]=\"!isNew()\"\n [attr.aria-describedby]=\"id + '-id-hint'\"\n [attr.aria-invalid]=\"submitted() && idInvalid() ? true : null\"\n (input)=\"agentId.set(value($event))\" />\n <p class=\"hint\" [id]=\"id + '-id-hint'\">\n <span [class.error-text]=\"submitted() && idInvalid()\">\n {{ (submitted() && idInvalid() ? 'nh-assistant.admin.invalid-id' : 'nh-assistant.admin.agent-editor.id-hint') | translate }}\n </span>\n </p>\n </div>\n\n <div class=\"field\">\n <label [for]=\"id + '-name'\">{{ 'nh-assistant.admin.agent-editor.display-name' | translate }}</label>\n <input\n type=\"text\"\n [id]=\"id + '-name'\"\n [value]=\"displayName()\"\n [attr.aria-invalid]=\"submitted() && nameMissing() ? true : null\"\n [attr.aria-describedby]=\"id + '-name-hint'\"\n (input)=\"displayName.set(value($event))\" />\n <p class=\"hint\" [id]=\"id + '-name-hint'\">\n @if (submitted() && nameMissing()) {\n <span class=\"error-text\">{{ 'nh-assistant.admin.required' | translate }}</span>\n }\n </p>\n </div>\n </div>\n\n <div class=\"field\">\n <label [for]=\"id + '-description'\">{{ 'nh-assistant.admin.agent-editor.description' | translate }}</label>\n <input type=\"text\" [id]=\"id + '-description'\" [value]=\"description()\" (input)=\"description.set(value($event))\" />\n </div>\n\n <div class=\"field\">\n <label [for]=\"id + '-instructions'\">{{ 'nh-assistant.admin.agent-editor.instructions' | translate }}</label>\n <textarea\n rows=\"8\"\n [id]=\"id + '-instructions'\"\n [value]=\"instructions()\"\n [attr.aria-describedby]=\"id + '-instructions-hint'\"\n [attr.aria-invalid]=\"tooLong() ? true : null\"\n (input)=\"instructions.set(value($event))\"></textarea>\n <p class=\"hint\" [id]=\"id + '-instructions-hint'\">\n <span [class.error-text]=\"tooLong()\">\n {{ (tooLong() ? 'nh-assistant.admin.too-long' : 'nh-assistant.admin.agent-editor.instructions-hint') | translate }}\n </span>\n <span class=\"counter\" [class.error-text]=\"tooLong()\">{{ 'nh-assistant.admin.characters' | translate: { count: instructions().length, max: max } }}</span>\n </p>\n </div>\n\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-autonomy'\">{{ 'nh-assistant.admin.agent-editor.autonomy' | translate }}</label>\n <select [id]=\"id + '-autonomy'\" [attr.aria-describedby]=\"id + '-autonomy-hint'\" (change)=\"autonomy.set($any(value($event)))\">\n @for (level of autonomyLevels; track level) {\n <option [value]=\"level\" [selected]=\"autonomy() === level\">{{ 'nh-assistant.admin.agents.autonomy.' + level | translate }}</option>\n }\n </select>\n <p class=\"hint\" [id]=\"id + '-autonomy-hint'\">{{ 'nh-assistant.admin.agent-editor.autonomy-hint' | translate }}</p>\n </div>\n\n <div class=\"field\">\n <label [for]=\"id + '-policy'\">{{ 'nh-assistant.admin.agent-editor.required-policy' | translate }}</label>\n <input\n type=\"text\"\n class=\"mono\"\n autocomplete=\"off\"\n [id]=\"id + '-policy'\"\n [value]=\"requiredPolicy()\"\n [attr.aria-describedby]=\"id + '-policy-hint'\"\n (input)=\"requiredPolicy.set(value($event))\" />\n <p class=\"hint\" [id]=\"id + '-policy-hint'\">{{ 'nh-assistant.admin.agent-editor.required-policy-hint' | translate }}</p>\n </div>\n </div>\n\n <label class=\"check\">\n <input type=\"checkbox\" [checked]=\"isEnabled()\" (change)=\"isEnabled.set(checked($event))\" />\n <span>{{ 'nh-assistant.admin.agent-editor.enabled' | translate }}</span>\n </label>\n\n <fieldset class=\"subsection\">\n <legend>{{ 'nh-assistant.admin.agent-editor.tool-selectors' | translate }}</legend>\n <p class=\"muted\">{{ 'nh-assistant.admin.agent-editor.tool-selectors-hint' | translate }}</p>\n\n @if (toolSelectors().length === 0) {\n <p class=\"muted\">{{ 'nh-assistant.admin.agent-editor.no-selectors' | translate }}</p>\n } @else {\n <ul class=\"chips\">\n @for (selector of toolSelectors(); track selector) {\n <li class=\"chip\">\n <span>{{ selector }}</span>\n <button\n type=\"button\"\n [attr.aria-label]=\"'nh-assistant.admin.agent-editor.remove-selector' | translate: { selector: selector }\"\n [attr.title]=\"'nh-assistant.admin.agent-editor.remove-selector' | translate: { selector: selector }\"\n (click)=\"removeSelector(selector)\">\n <nh-assistant-icon name=\"x\" />\n </button>\n </li>\n }\n </ul>\n <p class=\"muted\" role=\"status\">{{ 'nh-assistant.admin.agent-editor.matches' | translate: { count: matchCount() } }}</p>\n }\n\n <div class=\"inline\">\n <input\n type=\"text\"\n class=\"mono\"\n autocomplete=\"off\"\n [attr.aria-label]=\"'nh-assistant.admin.agent-editor.add-selector' | translate\"\n [attr.placeholder]=\"'nh-assistant.admin.agent-editor.selector-placeholder' | translate\"\n [attr.aria-invalid]=\"selectorInvalid() ? true : null\"\n [value]=\"newSelector()\"\n (input)=\"newSelector.set(value($event))\"\n (keydown)=\"onSelectorKeydown($event)\" />\n <button class=\"secondary\" type=\"button\" [disabled]=\"newSelector().trim().length === 0 || selectorInvalid()\" (click)=\"addSelector()\">\n <nh-assistant-icon name=\"plus\" />{{ 'nh-assistant.admin.agent-editor.add-selector' | translate }}\n </button>\n </div>\n\n <div class=\"field\">\n <label class=\"label\" [for]=\"id + '-catalog-filter'\">{{ 'nh-assistant.admin.agent-editor.catalog' | translate }}</label>\n <input\n type=\"search\"\n [id]=\"id + '-catalog-filter'\"\n [attr.placeholder]=\"'nh-assistant.admin.agent-editor.catalog-filter' | translate\"\n [value]=\"catalogFilter()\"\n (input)=\"catalogFilter.set(value($event))\" />\n <ul class=\"catalog\" [attr.aria-label]=\"'nh-assistant.admin.agent-editor.catalog' | translate\">\n @for (tool of filteredTools(); track tool.id) {\n <li>\n <label>\n <input\n type=\"checkbox\"\n [checked]=\"isSelected(tool.id) || isCovered(tool.id)\"\n [disabled]=\"isCovered(tool.id)\"\n (change)=\"toggleTool(tool.id, checked($event))\" />\n <span class=\"tool-id\">{{ tool.id }}</span>\n <span class=\"badge\">{{ 'nh-assistant.admin.source.' + tool.source | translate }}</span>\n <span class=\"badge\" [class.warning]=\"tool.effect !== 'read-only'\">{{ 'nh-assistant.admin.effect.' + tool.effect | translate }}</span>\n <span class=\"muted\">{{ tool.description }}</span>\n </label>\n </li>\n } @empty {\n <li class=\"muted\">{{ 'nh-assistant.admin.agent-editor.catalog-empty' | translate }}</li>\n }\n </ul>\n </div>\n </fieldset>\n\n <fieldset class=\"subsection\">\n <legend>{{ 'nh-assistant.admin.agent-editor.mcp-servers' | translate }}</legend>\n <p class=\"muted\">{{ 'nh-assistant.admin.agent-editor.mcp-servers-hint' | translate }}</p>\n @for (server of servers(); track server.id) {\n <label class=\"check\">\n <input type=\"checkbox\" [checked]=\"mcpServerIds().includes(server.id)\" (change)=\"toggleServer(server.id, checked($event))\" />\n <span>{{ server.displayName }} <span class=\"mono muted\">{{ server.id }}</span></span>\n </label>\n } @empty {\n <p class=\"muted\">{{ 'nh-assistant.admin.agent-editor.mcp-servers-empty' | translate }}</p>\n }\n </fieldset>\n\n @if (error(); as failure) {\n <p class=\"message error\" role=\"alert\"><nh-assistant-icon name=\"warning\" />{{ errorKeys() | nhAssistantTranslate: failure.code }}</p>\n }\n\n <div class=\"actions\">\n <button class=\"secondary\" type=\"button\" (click)=\"cancelled.emit()\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n <button class=\"primary\" type=\"submit\" [disabled]=\"saving() || tooLong()\">\n {{ (saving() ? 'nh-assistant.admin.saving' : 'nh-assistant.admin.save') | translate }}\n </button>\n </div>\n</form>\n", styles: [":host{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){:host{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}:host{display:block;min-width:0;color:var(--nha-text);font-family:var(--nha-font);font-size:14px}:host fieldset{min-width:0;margin:0;border:0;padding:0}:host legend,:host .label,:host .field>label{display:block;margin-bottom:6px;padding:0;color:var(--nha-text);font-size:12px;font-weight:700}:host input[type=text],:host input[type=url],:host input[type=password],:host input[type=search],:host 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}\n"], dependencies: [{ kind: "component", type: _NhAssistantIconComponent, selector: "nh-assistant-icon", inputs: ["name"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }, { kind: "pipe", type: _NhAssistantTranslatePipe, name: "nhAssistantTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
242
|
+
}
|
|
243
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: NhAssistantAdminAgentEditorComponent, decorators: [{
|
|
244
|
+
type: Component,
|
|
245
|
+
args: [{ selector: 'nh-assistant-admin-agent-editor', standalone: true, imports: [TranslatePipe, _NhAssistantTranslatePipe, _NhAssistantIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<form class=\"section card\" [attr.aria-labelledby]=\"id + '-title'\" [attr.aria-busy]=\"saving()\" (submit)=\"$event.preventDefault(); save()\">\n <h3 [id]=\"id + '-title'\">\n @if (agent(); as current) {\n {{ 'nh-assistant.admin.agent-editor.edit-title' | translate: { name: current.displayName } }}\n } @else {\n {{ 'nh-assistant.admin.agent-editor.new-title' | translate }}\n }\n </h3>\n\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-id'\">{{ 'nh-assistant.admin.agent-editor.id' | translate }}</label>\n <input\n type=\"text\"\n class=\"mono\"\n autocomplete=\"off\"\n [id]=\"id + '-id'\"\n [value]=\"agentId()\"\n [disabled]=\"!isNew()\"\n [attr.aria-describedby]=\"id + '-id-hint'\"\n [attr.aria-invalid]=\"submitted() && idInvalid() ? true : null\"\n (input)=\"agentId.set(value($event))\" />\n <p class=\"hint\" [id]=\"id + '-id-hint'\">\n <span [class.error-text]=\"submitted() && idInvalid()\">\n {{ (submitted() && idInvalid() ? 'nh-assistant.admin.invalid-id' : 'nh-assistant.admin.agent-editor.id-hint') | translate }}\n </span>\n </p>\n </div>\n\n <div class=\"field\">\n <label [for]=\"id + '-name'\">{{ 'nh-assistant.admin.agent-editor.display-name' | translate }}</label>\n <input\n type=\"text\"\n [id]=\"id + '-name'\"\n [value]=\"displayName()\"\n [attr.aria-invalid]=\"submitted() && nameMissing() ? true : null\"\n [attr.aria-describedby]=\"id + '-name-hint'\"\n (input)=\"displayName.set(value($event))\" />\n <p class=\"hint\" [id]=\"id + '-name-hint'\">\n @if (submitted() && nameMissing()) {\n <span class=\"error-text\">{{ 'nh-assistant.admin.required' | translate }}</span>\n }\n </p>\n </div>\n </div>\n\n <div class=\"field\">\n <label [for]=\"id + '-description'\">{{ 'nh-assistant.admin.agent-editor.description' | translate }}</label>\n <input type=\"text\" [id]=\"id + '-description'\" [value]=\"description()\" (input)=\"description.set(value($event))\" />\n </div>\n\n <div class=\"field\">\n <label [for]=\"id + '-instructions'\">{{ 'nh-assistant.admin.agent-editor.instructions' | translate }}</label>\n <textarea\n rows=\"8\"\n [id]=\"id + '-instructions'\"\n [value]=\"instructions()\"\n [attr.aria-describedby]=\"id + '-instructions-hint'\"\n [attr.aria-invalid]=\"tooLong() ? true : null\"\n (input)=\"instructions.set(value($event))\"></textarea>\n <p class=\"hint\" [id]=\"id + '-instructions-hint'\">\n <span [class.error-text]=\"tooLong()\">\n {{ (tooLong() ? 'nh-assistant.admin.too-long' : 'nh-assistant.admin.agent-editor.instructions-hint') | translate }}\n </span>\n <span class=\"counter\" [class.error-text]=\"tooLong()\">{{ 'nh-assistant.admin.characters' | translate: { count: instructions().length, max: max } }}</span>\n </p>\n </div>\n\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-autonomy'\">{{ 'nh-assistant.admin.agent-editor.autonomy' | translate }}</label>\n <select [id]=\"id + '-autonomy'\" [attr.aria-describedby]=\"id + '-autonomy-hint'\" (change)=\"autonomy.set($any(value($event)))\">\n @for (level of autonomyLevels; track level) {\n <option [value]=\"level\" [selected]=\"autonomy() === level\">{{ 'nh-assistant.admin.agents.autonomy.' + level | translate }}</option>\n }\n </select>\n <p class=\"hint\" [id]=\"id + '-autonomy-hint'\">{{ 'nh-assistant.admin.agent-editor.autonomy-hint' | translate }}</p>\n </div>\n\n <div class=\"field\">\n <label [for]=\"id + '-policy'\">{{ 'nh-assistant.admin.agent-editor.required-policy' | translate }}</label>\n <input\n type=\"text\"\n class=\"mono\"\n autocomplete=\"off\"\n [id]=\"id + '-policy'\"\n [value]=\"requiredPolicy()\"\n [attr.aria-describedby]=\"id + '-policy-hint'\"\n (input)=\"requiredPolicy.set(value($event))\" />\n <p class=\"hint\" [id]=\"id + '-policy-hint'\">{{ 'nh-assistant.admin.agent-editor.required-policy-hint' | translate }}</p>\n </div>\n </div>\n\n <label class=\"check\">\n <input type=\"checkbox\" [checked]=\"isEnabled()\" (change)=\"isEnabled.set(checked($event))\" />\n <span>{{ 'nh-assistant.admin.agent-editor.enabled' | translate }}</span>\n </label>\n\n <fieldset class=\"subsection\">\n <legend>{{ 'nh-assistant.admin.agent-editor.tool-selectors' | translate }}</legend>\n <p class=\"muted\">{{ 'nh-assistant.admin.agent-editor.tool-selectors-hint' | translate }}</p>\n\n @if (toolSelectors().length === 0) {\n <p class=\"muted\">{{ 'nh-assistant.admin.agent-editor.no-selectors' | translate }}</p>\n } @else {\n <ul class=\"chips\">\n @for (selector of toolSelectors(); track selector) {\n <li class=\"chip\">\n <span>{{ selector }}</span>\n <button\n type=\"button\"\n [attr.aria-label]=\"'nh-assistant.admin.agent-editor.remove-selector' | translate: { selector: selector }\"\n [attr.title]=\"'nh-assistant.admin.agent-editor.remove-selector' | translate: { selector: selector }\"\n (click)=\"removeSelector(selector)\">\n <nh-assistant-icon name=\"x\" />\n </button>\n </li>\n }\n </ul>\n <p class=\"muted\" role=\"status\">{{ 'nh-assistant.admin.agent-editor.matches' | translate: { count: matchCount() } }}</p>\n }\n\n <div class=\"inline\">\n <input\n type=\"text\"\n class=\"mono\"\n autocomplete=\"off\"\n [attr.aria-label]=\"'nh-assistant.admin.agent-editor.add-selector' | translate\"\n [attr.placeholder]=\"'nh-assistant.admin.agent-editor.selector-placeholder' | translate\"\n [attr.aria-invalid]=\"selectorInvalid() ? true : null\"\n [value]=\"newSelector()\"\n (input)=\"newSelector.set(value($event))\"\n (keydown)=\"onSelectorKeydown($event)\" />\n <button class=\"secondary\" type=\"button\" [disabled]=\"newSelector().trim().length === 0 || selectorInvalid()\" (click)=\"addSelector()\">\n <nh-assistant-icon name=\"plus\" />{{ 'nh-assistant.admin.agent-editor.add-selector' | translate }}\n </button>\n </div>\n\n <div class=\"field\">\n <label class=\"label\" [for]=\"id + '-catalog-filter'\">{{ 'nh-assistant.admin.agent-editor.catalog' | translate }}</label>\n <input\n type=\"search\"\n [id]=\"id + '-catalog-filter'\"\n [attr.placeholder]=\"'nh-assistant.admin.agent-editor.catalog-filter' | translate\"\n [value]=\"catalogFilter()\"\n (input)=\"catalogFilter.set(value($event))\" />\n <ul class=\"catalog\" [attr.aria-label]=\"'nh-assistant.admin.agent-editor.catalog' | translate\">\n @for (tool of filteredTools(); track tool.id) {\n <li>\n <label>\n <input\n type=\"checkbox\"\n [checked]=\"isSelected(tool.id) || isCovered(tool.id)\"\n [disabled]=\"isCovered(tool.id)\"\n (change)=\"toggleTool(tool.id, checked($event))\" />\n <span class=\"tool-id\">{{ tool.id }}</span>\n <span class=\"badge\">{{ 'nh-assistant.admin.source.' + tool.source | translate }}</span>\n <span class=\"badge\" [class.warning]=\"tool.effect !== 'read-only'\">{{ 'nh-assistant.admin.effect.' + tool.effect | translate }}</span>\n <span class=\"muted\">{{ tool.description }}</span>\n </label>\n </li>\n } @empty {\n <li class=\"muted\">{{ 'nh-assistant.admin.agent-editor.catalog-empty' | translate }}</li>\n }\n </ul>\n </div>\n </fieldset>\n\n <fieldset class=\"subsection\">\n <legend>{{ 'nh-assistant.admin.agent-editor.mcp-servers' | translate }}</legend>\n <p class=\"muted\">{{ 'nh-assistant.admin.agent-editor.mcp-servers-hint' | translate }}</p>\n @for (server of servers(); track server.id) {\n <label class=\"check\">\n <input type=\"checkbox\" [checked]=\"mcpServerIds().includes(server.id)\" (change)=\"toggleServer(server.id, checked($event))\" />\n <span>{{ server.displayName }} <span class=\"mono muted\">{{ server.id }}</span></span>\n </label>\n } @empty {\n <p class=\"muted\">{{ 'nh-assistant.admin.agent-editor.mcp-servers-empty' | translate }}</p>\n }\n </fieldset>\n\n @if (error(); as failure) {\n <p class=\"message error\" role=\"alert\"><nh-assistant-icon name=\"warning\" />{{ errorKeys() | nhAssistantTranslate: failure.code }}</p>\n }\n\n <div class=\"actions\">\n <button class=\"secondary\" type=\"button\" (click)=\"cancelled.emit()\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n <button class=\"primary\" type=\"submit\" [disabled]=\"saving() || tooLong()\">\n {{ (saving() ? 'nh-assistant.admin.saving' : 'nh-assistant.admin.save') | translate }}\n </button>\n </div>\n</form>\n", styles: [":host{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){:host{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}:host{display:block;min-width:0;color:var(--nha-text);font-family:var(--nha-font);font-size:14px}:host fieldset{min-width:0;margin:0;border:0;padding:0}:host legend,:host .label,:host .field>label{display:block;margin-bottom:6px;padding:0;color:var(--nha-text);font-size:12px;font-weight:700}:host input[type=text],:host input[type=url],:host input[type=password],:host input[type=search],:host 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}\n"] }]
|
|
246
|
+
}], ctorParameters: () => [], propDecorators: { agent: [{ type: i0.Input, args: [{ isSignal: true, alias: "agent", required: false }] }], tools: [{ type: i0.Input, args: [{ isSignal: true, alias: "tools", required: false }] }], servers: [{ type: i0.Input, args: [{ isSignal: true, alias: "servers", required: false }] }], saved: [{ type: i0.Output, args: ["saved"] }], cancelled: [{ type: i0.Output, args: ["cancelled"] }] } });
|
|
247
|
+
|
|
248
|
+
let nextId$4 = 0;
|
|
249
|
+
/**
|
|
250
|
+
* Agent administration: code agents (override, disable, reset) and agents created here
|
|
251
|
+
* (edit, disable, delete). Editing opens `nh-assistant-admin-agent-editor`.
|
|
252
|
+
*/
|
|
253
|
+
class NhAssistantAdminAgentsComponent {
|
|
254
|
+
constructor() {
|
|
255
|
+
this.admin = inject(NhAssistantAdminApiService);
|
|
256
|
+
this.store = inject(NhAssistantStore);
|
|
257
|
+
this.id = `nh-assistant-admin-agents-${nextId$4++}`;
|
|
258
|
+
this.loading = signal(true, ...(ngDevMode ? [{ debugName: "loading" }] : []));
|
|
259
|
+
this.loadFailed = signal(false, ...(ngDevMode ? [{ debugName: "loadFailed" }] : []));
|
|
260
|
+
this.agents = signal([], ...(ngDevMode ? [{ debugName: "agents" }] : []));
|
|
261
|
+
this.tools = signal([], ...(ngDevMode ? [{ debugName: "tools" }] : []));
|
|
262
|
+
this.servers = signal([], ...(ngDevMode ? [{ debugName: "servers" }] : []));
|
|
263
|
+
/** `undefined`: list; `null`: new agent; an agent: editing that agent. */
|
|
264
|
+
this.editing = signal(undefined, ...(ngDevMode ? [{ debugName: "editing" }] : []));
|
|
265
|
+
this.confirming = signal(null, ...(ngDevMode ? [{ debugName: "confirming" }] : []));
|
|
266
|
+
this.busyAgentId = signal(null, ...(ngDevMode ? [{ debugName: "busyAgentId" }] : []));
|
|
267
|
+
this.error = signal(null, ...(ngDevMode ? [{ debugName: "error" }] : []));
|
|
268
|
+
this.errorKeys = computed(() => _nhAssistantErrorKeys(this.error()), ...(ngDevMode ? [{ debugName: "errorKeys" }] : []));
|
|
269
|
+
inject(DestroyRef).onDestroy(() => {
|
|
270
|
+
this.subscription?.unsubscribe();
|
|
271
|
+
this.actionSubscription?.unsubscribe();
|
|
272
|
+
});
|
|
273
|
+
this.load();
|
|
274
|
+
}
|
|
275
|
+
load() {
|
|
276
|
+
this.loading.set(true);
|
|
277
|
+
this.loadFailed.set(false);
|
|
278
|
+
this.subscription?.unsubscribe();
|
|
279
|
+
this.subscription = forkJoin([this.admin.getAgents(), this.admin.getTools(), this.admin.getMcpServers()]).subscribe({
|
|
280
|
+
next: ([agents, tools, servers]) => {
|
|
281
|
+
this.agents.set(agents);
|
|
282
|
+
this.tools.set(tools);
|
|
283
|
+
this.servers.set(servers);
|
|
284
|
+
this.loading.set(false);
|
|
285
|
+
},
|
|
286
|
+
error: () => {
|
|
287
|
+
this.loading.set(false);
|
|
288
|
+
this.loadFailed.set(true);
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
create() {
|
|
293
|
+
this.error.set(null);
|
|
294
|
+
this.editing.set(null);
|
|
295
|
+
}
|
|
296
|
+
edit(agent) {
|
|
297
|
+
this.error.set(null);
|
|
298
|
+
this.editing.set(agent);
|
|
299
|
+
}
|
|
300
|
+
onSaved(agent) {
|
|
301
|
+
this.agents.update(agents => agents.some(item => item.id === agent.id)
|
|
302
|
+
? agents.map(item => item.id === agent.id ? agent : item)
|
|
303
|
+
: [...agents, agent]);
|
|
304
|
+
this.editing.set(undefined);
|
|
305
|
+
this.refreshChat();
|
|
306
|
+
}
|
|
307
|
+
setEnabled(agent, isEnabled) {
|
|
308
|
+
const { version, source, isOverridden, instructionsHash, updatedAt, ...input } = agent;
|
|
309
|
+
this.run(agent.id, this.admin.updateAgent(agent.id, { ...input, isEnabled, expectedVersion: version }), updated => this.replace(updated));
|
|
310
|
+
}
|
|
311
|
+
confirm(agentId, action) {
|
|
312
|
+
this.confirming.set({ agentId, action });
|
|
313
|
+
}
|
|
314
|
+
isConfirming(agentId, action) {
|
|
315
|
+
const confirming = this.confirming();
|
|
316
|
+
return confirming?.agentId === agentId && confirming.action === action;
|
|
317
|
+
}
|
|
318
|
+
reset(agent) {
|
|
319
|
+
this.run(agent.id, this.admin.resetAgent(agent.id), updated => this.replace(updated));
|
|
320
|
+
}
|
|
321
|
+
remove(agent) {
|
|
322
|
+
this.run(agent.id, this.admin.deleteAgent(agent.id), () => {
|
|
323
|
+
this.agents.update(agents => agents.filter(item => item.id !== agent.id));
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
run(agentId, request, done) {
|
|
327
|
+
this.busyAgentId.set(agentId);
|
|
328
|
+
this.confirming.set(null);
|
|
329
|
+
this.error.set(null);
|
|
330
|
+
this.actionSubscription?.unsubscribe();
|
|
331
|
+
this.actionSubscription = request.subscribe({
|
|
332
|
+
next: result => done(result),
|
|
333
|
+
complete: () => {
|
|
334
|
+
this.busyAgentId.set(null);
|
|
335
|
+
this.refreshChat();
|
|
336
|
+
},
|
|
337
|
+
error: (error) => {
|
|
338
|
+
this.busyAgentId.set(null);
|
|
339
|
+
this.error.set(_toNhAssistantError(error));
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
replace(agent) {
|
|
344
|
+
this.agents.update(agents => agents.map(item => item.id === agent.id ? agent : item));
|
|
345
|
+
}
|
|
346
|
+
/** Agent changes affect the agent picker of the panel. */
|
|
347
|
+
refreshChat() {
|
|
348
|
+
void this.store.reloadStatus();
|
|
349
|
+
}
|
|
350
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: NhAssistantAdminAgentsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
351
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.28", type: NhAssistantAdminAgentsComponent, isStandalone: true, selector: "nh-assistant-admin-agents", 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 {\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.isEnabled\" [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 class=\"badge\" [class.success]=\"agent.isEnabled\">{{ (agent.isEnabled ? 'nh-assistant.admin.enabled' : 'nh-assistant.admin.disabled') | translate }}</span>\n </div>\n @if (agent.description) {\n <p class=\"muted\">{{ agent.description }}</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 <button class=\"secondary\" type=\"button\" [disabled]=\"busyAgentId() !== null\" (click)=\"setEnabled(agent, !agent.isEnabled)\">\n {{ (agent.isEnabled ? 'nh-assistant.admin.disable' : 'nh-assistant.admin.enable') | translate }}\n </button>\n @if (agent.source === 'code' && agent.isOverridden) {\n @if (isConfirming(agent.id, 'reset')) {\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 </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}\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: "pipe", type: TranslatePipe, name: "translate" }, { kind: "pipe", type: _NhAssistantTranslatePipe, name: "nhAssistantTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
352
|
+
}
|
|
353
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: NhAssistantAdminAgentsComponent, decorators: [{
|
|
354
|
+
type: Component,
|
|
355
|
+
args: [{ selector: 'nh-assistant-admin-agents', standalone: true, imports: [TranslatePipe, _NhAssistantTranslatePipe, _NhAssistantIconComponent, NhAssistantAdminAgentEditorComponent], 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 {\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.isEnabled\" [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 class=\"badge\" [class.success]=\"agent.isEnabled\">{{ (agent.isEnabled ? 'nh-assistant.admin.enabled' : 'nh-assistant.admin.disabled') | translate }}</span>\n </div>\n @if (agent.description) {\n <p class=\"muted\">{{ agent.description }}</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 <button class=\"secondary\" type=\"button\" [disabled]=\"busyAgentId() !== null\" (click)=\"setEnabled(agent, !agent.isEnabled)\">\n {{ (agent.isEnabled ? 'nh-assistant.admin.disable' : 'nh-assistant.admin.enable') | translate }}\n </button>\n @if (agent.source === 'code' && agent.isOverridden) {\n @if (isConfirming(agent.id, 'reset')) {\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 </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}\n"] }]
|
|
356
|
+
}], ctorParameters: () => [] });
|
|
357
|
+
|
|
358
|
+
let nextId$3 = 0;
|
|
359
|
+
/** Creates or edits one MCP server with a write-only secret. */
|
|
360
|
+
class NhAssistantAdminMcpServerEditorComponent {
|
|
361
|
+
constructor() {
|
|
362
|
+
this.admin = inject(NhAssistantAdminApiService);
|
|
363
|
+
/** The server to edit, or null for a new server. */
|
|
364
|
+
this.server = input(null, ...(ngDevMode ? [{ debugName: "server" }] : []));
|
|
365
|
+
this.saved = output();
|
|
366
|
+
this.cancelled = output();
|
|
367
|
+
this.id = `nh-assistant-server-editor-${nextId$3++}`;
|
|
368
|
+
this.authModes = ['none', 'bearer', 'api-key', 'forward-user-token'];
|
|
369
|
+
this.serverId = signal('', ...(ngDevMode ? [{ debugName: "serverId" }] : []));
|
|
370
|
+
this.displayName = signal('', ...(ngDevMode ? [{ debugName: "displayName" }] : []));
|
|
371
|
+
this.url = signal('', ...(ngDevMode ? [{ debugName: "url" }] : []));
|
|
372
|
+
this.authMode = signal('none', ...(ngDevMode ? [{ debugName: "authMode" }] : []));
|
|
373
|
+
this.headerName = signal('', ...(ngDevMode ? [{ debugName: "headerName" }] : []));
|
|
374
|
+
this.requiredPolicy = signal('', ...(ngDevMode ? [{ debugName: "requiredPolicy" }] : []));
|
|
375
|
+
this.isEnabled = signal(true, ...(ngDevMode ? [{ debugName: "isEnabled" }] : []));
|
|
376
|
+
this.secretAction = signal('keep', ...(ngDevMode ? [{ debugName: "secretAction" }] : []));
|
|
377
|
+
/** The new secret while typing. Never filled from the server. */
|
|
378
|
+
this.secret = signal('', ...(ngDevMode ? [{ debugName: "secret" }] : []));
|
|
379
|
+
this.submitted = signal(false, ...(ngDevMode ? [{ debugName: "submitted" }] : []));
|
|
380
|
+
this.saving = signal(false, ...(ngDevMode ? [{ debugName: "saving" }] : []));
|
|
381
|
+
this.error = signal(null, ...(ngDevMode ? [{ debugName: "error" }] : []));
|
|
382
|
+
this.isNew = computed(() => this.server() === null, ...(ngDevMode ? [{ debugName: "isNew" }] : []));
|
|
383
|
+
this.hasSecret = computed(() => this.server()?.hasSecret === true, ...(ngDevMode ? [{ debugName: "hasSecret" }] : []));
|
|
384
|
+
this.usesSecret = computed(() => this.authMode() === 'bearer' || this.authMode() === 'api-key', ...(ngDevMode ? [{ debugName: "usesSecret" }] : []));
|
|
385
|
+
this.showSecretInput = computed(() => this.usesSecret() && (!this.hasSecret() || this.secretAction() === 'replace'), ...(ngDevMode ? [{ debugName: "showSecretInput" }] : []));
|
|
386
|
+
this.idInvalid = computed(() => this.isNew() && (this.serverId().length > 40 || !_NH_ASSISTANT_DASH_CASE.test(this.serverId())), ...(ngDevMode ? [{ debugName: "idInvalid" }] : []));
|
|
387
|
+
this.nameMissing = computed(() => this.displayName().trim().length === 0, ...(ngDevMode ? [{ debugName: "nameMissing" }] : []));
|
|
388
|
+
this.urlInvalid = computed(() => !isAllowedUrl(this.url().trim()), ...(ngDevMode ? [{ debugName: "urlInvalid" }] : []));
|
|
389
|
+
this.valid = computed(() => !this.idInvalid() && !this.nameMissing() && !this.urlInvalid(), ...(ngDevMode ? [{ debugName: "valid" }] : []));
|
|
390
|
+
this.errorKeys = computed(() => _nhAssistantErrorKeys(this.error()), ...(ngDevMode ? [{ debugName: "errorKeys" }] : []));
|
|
391
|
+
inject(DestroyRef).onDestroy(() => this.subscription?.unsubscribe());
|
|
392
|
+
effect(() => {
|
|
393
|
+
const server = this.server();
|
|
394
|
+
untracked(() => this.reset(server));
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
value(event) {
|
|
398
|
+
return event.target.value;
|
|
399
|
+
}
|
|
400
|
+
checked(event) {
|
|
401
|
+
return event.target.checked;
|
|
402
|
+
}
|
|
403
|
+
setSecretAction(action) {
|
|
404
|
+
this.secretAction.set(action);
|
|
405
|
+
this.secret.set('');
|
|
406
|
+
}
|
|
407
|
+
onSecretInput(event) {
|
|
408
|
+
this.secret.set(this.value(event));
|
|
409
|
+
if (!this.hasSecret()) {
|
|
410
|
+
this.secretAction.set(this.secret().length > 0 ? 'replace' : 'keep');
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
save() {
|
|
414
|
+
this.submitted.set(true);
|
|
415
|
+
if (!this.valid() || this.saving()) {
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
const authMode = this.authMode();
|
|
419
|
+
const policy = this.requiredPolicy().trim();
|
|
420
|
+
const header = this.headerName().trim();
|
|
421
|
+
const input = {
|
|
422
|
+
id: this.isNew() ? this.serverId() : this.server().id,
|
|
423
|
+
displayName: this.displayName().trim(),
|
|
424
|
+
url: this.url().trim(),
|
|
425
|
+
authMode,
|
|
426
|
+
headerName: authMode === 'api-key' && header.length > 0 ? header : null,
|
|
427
|
+
requiredPolicy: policy.length > 0 ? policy : null,
|
|
428
|
+
isEnabled: this.isEnabled()
|
|
429
|
+
};
|
|
430
|
+
// Write-only secret: omitted keeps the stored value, "" clears it.
|
|
431
|
+
const action = this.secretAction();
|
|
432
|
+
if (!this.usesSecret() && this.hasSecret()) {
|
|
433
|
+
input.secret = '';
|
|
434
|
+
}
|
|
435
|
+
else if (action === 'clear') {
|
|
436
|
+
input.secret = '';
|
|
437
|
+
}
|
|
438
|
+
else if (action === 'replace' && this.secret().length > 0) {
|
|
439
|
+
input.secret = this.secret();
|
|
440
|
+
}
|
|
441
|
+
const existing = this.server();
|
|
442
|
+
const request = existing ? this.admin.updateMcpServer(existing.id, input) : this.admin.createMcpServer(input);
|
|
443
|
+
this.saving.set(true);
|
|
444
|
+
this.error.set(null);
|
|
445
|
+
this.subscription?.unsubscribe();
|
|
446
|
+
this.subscription = request.subscribe({
|
|
447
|
+
next: server => {
|
|
448
|
+
this.saving.set(false);
|
|
449
|
+
this.secret.set('');
|
|
450
|
+
this.saved.emit(server);
|
|
451
|
+
},
|
|
452
|
+
error: (error) => {
|
|
453
|
+
this.saving.set(false);
|
|
454
|
+
this.error.set(_toNhAssistantError(error));
|
|
455
|
+
}
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
reset(server) {
|
|
459
|
+
this.serverId.set(server?.id ?? '');
|
|
460
|
+
this.displayName.set(server?.displayName ?? '');
|
|
461
|
+
this.url.set(server?.url ?? 'https://');
|
|
462
|
+
this.authMode.set(server?.authMode ?? 'none');
|
|
463
|
+
this.headerName.set(server?.headerName ?? '');
|
|
464
|
+
this.requiredPolicy.set(server?.requiredPolicy ?? '');
|
|
465
|
+
this.isEnabled.set(server?.isEnabled ?? true);
|
|
466
|
+
this.secretAction.set('keep');
|
|
467
|
+
this.secret.set('');
|
|
468
|
+
this.submitted.set(false);
|
|
469
|
+
this.error.set(null);
|
|
470
|
+
}
|
|
471
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: NhAssistantAdminMcpServerEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
472
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.28", type: NhAssistantAdminMcpServerEditorComponent, isStandalone: true, selector: "nh-assistant-admin-mcp-server-editor", inputs: { server: { classPropertyName: "server", publicName: "server", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { saved: "saved", cancelled: "cancelled" }, ngImport: i0, template: "<form class=\"section card\" [attr.aria-labelledby]=\"id + '-title'\" [attr.aria-busy]=\"saving()\" autocomplete=\"off\" (submit)=\"$event.preventDefault(); save()\">\n <h3 [id]=\"id + '-title'\">\n @if (server(); as current) {\n {{ 'nh-assistant.admin.server-editor.edit-title' | translate: { name: current.displayName } }}\n } @else {\n {{ 'nh-assistant.admin.server-editor.new-title' | translate }}\n }\n </h3>\n\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-id'\">{{ 'nh-assistant.admin.server-editor.id' | translate }}</label>\n <input\n type=\"text\"\n class=\"mono\"\n [id]=\"id + '-id'\"\n [value]=\"serverId()\"\n [disabled]=\"!isNew()\"\n [attr.aria-describedby]=\"id + '-id-hint'\"\n [attr.aria-invalid]=\"submitted() && idInvalid() ? true : null\"\n (input)=\"serverId.set(value($event))\" />\n <p class=\"hint\" [id]=\"id + '-id-hint'\">\n <span [class.error-text]=\"submitted() && idInvalid()\">\n {{ (submitted() && idInvalid() ? 'nh-assistant.admin.invalid-id' : 'nh-assistant.admin.server-editor.id-hint') | translate }}\n </span>\n </p>\n </div>\n\n <div class=\"field\">\n <label [for]=\"id + '-name'\">{{ 'nh-assistant.admin.server-editor.display-name' | translate }}</label>\n <input\n type=\"text\"\n [id]=\"id + '-name'\"\n [value]=\"displayName()\"\n [attr.aria-invalid]=\"submitted() && nameMissing() ? true : null\"\n [attr.aria-describedby]=\"id + '-name-hint'\"\n (input)=\"displayName.set(value($event))\" />\n <p class=\"hint\" [id]=\"id + '-name-hint'\">\n @if (submitted() && nameMissing()) {\n <span class=\"error-text\">{{ 'nh-assistant.admin.required' | translate }}</span>\n }\n </p>\n </div>\n </div>\n\n <div class=\"field\">\n <label [for]=\"id + '-url'\">{{ 'nh-assistant.admin.server-editor.url' | translate }}</label>\n <input\n type=\"url\"\n class=\"mono\"\n inputmode=\"url\"\n [id]=\"id + '-url'\"\n [value]=\"url()\"\n [attr.aria-describedby]=\"id + '-url-hint'\"\n [attr.aria-invalid]=\"submitted() && urlInvalid() ? true : null\"\n (input)=\"url.set(value($event))\" />\n <p class=\"hint\" [id]=\"id + '-url-hint'\">\n <span [class.error-text]=\"submitted() && urlInvalid()\">\n {{ (submitted() && urlInvalid() ? 'nh-assistant.admin.server-editor.url-invalid' : 'nh-assistant.admin.server-editor.url-hint') | translate }}\n </span>\n </p>\n </div>\n\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-auth'\">{{ 'nh-assistant.admin.server-editor.auth-mode' | translate }}</label>\n <select [id]=\"id + '-auth'\" (change)=\"authMode.set($any(value($event)))\">\n @for (mode of authModes; track mode) {\n <option [value]=\"mode\" [selected]=\"authMode() === mode\">{{ 'nh-assistant.admin.mcp.auth.' + mode | translate }}</option>\n }\n </select>\n </div>\n\n @if (authMode() === 'api-key') {\n <div class=\"field\">\n <label [for]=\"id + '-header'\">{{ 'nh-assistant.admin.server-editor.header-name' | translate }}</label>\n <input\n type=\"text\"\n class=\"mono\"\n placeholder=\"X-Api-Key\"\n [id]=\"id + '-header'\"\n [value]=\"headerName()\"\n [attr.aria-describedby]=\"id + '-header-hint'\"\n (input)=\"headerName.set(value($event))\" />\n <p class=\"hint\" [id]=\"id + '-header-hint'\">{{ 'nh-assistant.admin.server-editor.header-name-hint' | translate }}</p>\n </div>\n }\n </div>\n\n @if (authMode() === 'forward-user-token') {\n <p class=\"message warning\" role=\"note\"><nh-assistant-icon name=\"warning\" />{{ 'nh-assistant.admin.server-editor.forward-warning' | translate }}</p>\n }\n\n @if (usesSecret()) {\n <fieldset class=\"field\">\n <legend>{{ 'nh-assistant.admin.server-editor.secret' | translate }}</legend>\n @if (hasSecret()) {\n <div class=\"meta secret-state\">\n @switch (secretAction()) {\n @case ('clear') {\n <span class=\"badge warning\">{{ 'nh-assistant.admin.server-editor.secret-will-clear' | translate }}</span>\n <button class=\"secondary\" type=\"button\" (click)=\"setSecretAction('keep')\">{{ 'nh-assistant.admin.server-editor.secret-undo' | translate }}</button>\n }\n @case ('replace') {\n <span class=\"badge\">{{ 'nh-assistant.admin.server-editor.secret-set' | translate }}</span>\n <button class=\"secondary\" type=\"button\" (click)=\"setSecretAction('keep')\">{{ 'nh-assistant.admin.server-editor.secret-undo' | translate }}</button>\n }\n @default {\n <span class=\"badge success\">{{ 'nh-assistant.admin.server-editor.secret-set' | translate }}</span>\n <button class=\"secondary\" type=\"button\" (click)=\"setSecretAction('replace')\">{{ 'nh-assistant.admin.server-editor.secret-replace' | translate }}</button>\n <button class=\"danger\" type=\"button\" (click)=\"setSecretAction('clear')\">{{ 'nh-assistant.admin.server-editor.secret-clear' | translate }}</button>\n }\n }\n </div>\n } @else {\n <p class=\"muted\">{{ 'nh-assistant.admin.server-editor.secret-none' | translate }}</p>\n }\n @if (showSecretInput()) {\n <label class=\"visually-hidden\" [for]=\"id + '-secret'\">{{ 'nh-assistant.admin.server-editor.secret-new' | translate }}</label>\n <input\n type=\"password\"\n class=\"mono\"\n autocomplete=\"new-password\"\n spellcheck=\"false\"\n [id]=\"id + '-secret'\"\n [attr.placeholder]=\"'nh-assistant.admin.server-editor.secret-new' | translate\"\n [attr.aria-describedby]=\"id + '-secret-hint'\"\n [value]=\"secret()\"\n (input)=\"onSecretInput($event)\" />\n }\n <p class=\"hint\" [id]=\"id + '-secret-hint'\">{{ 'nh-assistant.admin.server-editor.secret-hint' | translate }}</p>\n </fieldset>\n }\n\n <div class=\"field\">\n <label [for]=\"id + '-policy'\">{{ 'nh-assistant.admin.server-editor.required-policy' | translate }}</label>\n <input\n type=\"text\"\n class=\"mono\"\n [id]=\"id + '-policy'\"\n [value]=\"requiredPolicy()\"\n [attr.aria-describedby]=\"id + '-policy-hint'\"\n (input)=\"requiredPolicy.set(value($event))\" />\n <p class=\"hint\" [id]=\"id + '-policy-hint'\">{{ 'nh-assistant.admin.server-editor.required-policy-hint' | translate }}</p>\n </div>\n\n <label class=\"check\">\n <input type=\"checkbox\" [checked]=\"isEnabled()\" (change)=\"isEnabled.set(checked($event))\" />\n <span>{{ 'nh-assistant.admin.server-editor.enabled' | translate }}</span>\n </label>\n\n @if (error(); as failure) {\n <p class=\"message error\" role=\"alert\"><nh-assistant-icon name=\"warning\" />{{ errorKeys() | nhAssistantTranslate: failure.code }}</p>\n }\n\n <div class=\"actions\">\n <button class=\"secondary\" type=\"button\" (click)=\"cancelled.emit()\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n <button class=\"primary\" type=\"submit\" [disabled]=\"saving()\">\n {{ (saving() ? 'nh-assistant.admin.saving' : 'nh-assistant.admin.save') | translate }}\n </button>\n </div>\n</form>\n", styles: [":host{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){:host{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}:host{display:block;min-width:0;color:var(--nha-text);font-family:var(--nha-font);font-size:14px}:host fieldset{min-width:0;margin:0;border:0;padding:0}:host legend,:host .label,:host .field>label{display:block;margin-bottom:6px;padding:0;color:var(--nha-text);font-size:12px;font-weight:700}:host input[type=text],:host input[type=url],:host input[type=password],:host input[type=search],:host 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}\n"], dependencies: [{ kind: "component", type: _NhAssistantIconComponent, selector: "nh-assistant-icon", inputs: ["name"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }, { kind: "pipe", type: _NhAssistantTranslatePipe, name: "nhAssistantTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
473
|
+
}
|
|
474
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: NhAssistantAdminMcpServerEditorComponent, decorators: [{
|
|
475
|
+
type: Component,
|
|
476
|
+
args: [{ selector: 'nh-assistant-admin-mcp-server-editor', standalone: true, imports: [TranslatePipe, _NhAssistantTranslatePipe, _NhAssistantIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<form class=\"section card\" [attr.aria-labelledby]=\"id + '-title'\" [attr.aria-busy]=\"saving()\" autocomplete=\"off\" (submit)=\"$event.preventDefault(); save()\">\n <h3 [id]=\"id + '-title'\">\n @if (server(); as current) {\n {{ 'nh-assistant.admin.server-editor.edit-title' | translate: { name: current.displayName } }}\n } @else {\n {{ 'nh-assistant.admin.server-editor.new-title' | translate }}\n }\n </h3>\n\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-id'\">{{ 'nh-assistant.admin.server-editor.id' | translate }}</label>\n <input\n type=\"text\"\n class=\"mono\"\n [id]=\"id + '-id'\"\n [value]=\"serverId()\"\n [disabled]=\"!isNew()\"\n [attr.aria-describedby]=\"id + '-id-hint'\"\n [attr.aria-invalid]=\"submitted() && idInvalid() ? true : null\"\n (input)=\"serverId.set(value($event))\" />\n <p class=\"hint\" [id]=\"id + '-id-hint'\">\n <span [class.error-text]=\"submitted() && idInvalid()\">\n {{ (submitted() && idInvalid() ? 'nh-assistant.admin.invalid-id' : 'nh-assistant.admin.server-editor.id-hint') | translate }}\n </span>\n </p>\n </div>\n\n <div class=\"field\">\n <label [for]=\"id + '-name'\">{{ 'nh-assistant.admin.server-editor.display-name' | translate }}</label>\n <input\n type=\"text\"\n [id]=\"id + '-name'\"\n [value]=\"displayName()\"\n [attr.aria-invalid]=\"submitted() && nameMissing() ? true : null\"\n [attr.aria-describedby]=\"id + '-name-hint'\"\n (input)=\"displayName.set(value($event))\" />\n <p class=\"hint\" [id]=\"id + '-name-hint'\">\n @if (submitted() && nameMissing()) {\n <span class=\"error-text\">{{ 'nh-assistant.admin.required' | translate }}</span>\n }\n </p>\n </div>\n </div>\n\n <div class=\"field\">\n <label [for]=\"id + '-url'\">{{ 'nh-assistant.admin.server-editor.url' | translate }}</label>\n <input\n type=\"url\"\n class=\"mono\"\n inputmode=\"url\"\n [id]=\"id + '-url'\"\n [value]=\"url()\"\n [attr.aria-describedby]=\"id + '-url-hint'\"\n [attr.aria-invalid]=\"submitted() && urlInvalid() ? true : null\"\n (input)=\"url.set(value($event))\" />\n <p class=\"hint\" [id]=\"id + '-url-hint'\">\n <span [class.error-text]=\"submitted() && urlInvalid()\">\n {{ (submitted() && urlInvalid() ? 'nh-assistant.admin.server-editor.url-invalid' : 'nh-assistant.admin.server-editor.url-hint') | translate }}\n </span>\n </p>\n </div>\n\n <div class=\"grid\">\n <div class=\"field\">\n <label [for]=\"id + '-auth'\">{{ 'nh-assistant.admin.server-editor.auth-mode' | translate }}</label>\n <select [id]=\"id + '-auth'\" (change)=\"authMode.set($any(value($event)))\">\n @for (mode of authModes; track mode) {\n <option [value]=\"mode\" [selected]=\"authMode() === mode\">{{ 'nh-assistant.admin.mcp.auth.' + mode | translate }}</option>\n }\n </select>\n </div>\n\n @if (authMode() === 'api-key') {\n <div class=\"field\">\n <label [for]=\"id + '-header'\">{{ 'nh-assistant.admin.server-editor.header-name' | translate }}</label>\n <input\n type=\"text\"\n class=\"mono\"\n placeholder=\"X-Api-Key\"\n [id]=\"id + '-header'\"\n [value]=\"headerName()\"\n [attr.aria-describedby]=\"id + '-header-hint'\"\n (input)=\"headerName.set(value($event))\" />\n <p class=\"hint\" [id]=\"id + '-header-hint'\">{{ 'nh-assistant.admin.server-editor.header-name-hint' | translate }}</p>\n </div>\n }\n </div>\n\n @if (authMode() === 'forward-user-token') {\n <p class=\"message warning\" role=\"note\"><nh-assistant-icon name=\"warning\" />{{ 'nh-assistant.admin.server-editor.forward-warning' | translate }}</p>\n }\n\n @if (usesSecret()) {\n <fieldset class=\"field\">\n <legend>{{ 'nh-assistant.admin.server-editor.secret' | translate }}</legend>\n @if (hasSecret()) {\n <div class=\"meta secret-state\">\n @switch (secretAction()) {\n @case ('clear') {\n <span class=\"badge warning\">{{ 'nh-assistant.admin.server-editor.secret-will-clear' | translate }}</span>\n <button class=\"secondary\" type=\"button\" (click)=\"setSecretAction('keep')\">{{ 'nh-assistant.admin.server-editor.secret-undo' | translate }}</button>\n }\n @case ('replace') {\n <span class=\"badge\">{{ 'nh-assistant.admin.server-editor.secret-set' | translate }}</span>\n <button class=\"secondary\" type=\"button\" (click)=\"setSecretAction('keep')\">{{ 'nh-assistant.admin.server-editor.secret-undo' | translate }}</button>\n }\n @default {\n <span class=\"badge success\">{{ 'nh-assistant.admin.server-editor.secret-set' | translate }}</span>\n <button class=\"secondary\" type=\"button\" (click)=\"setSecretAction('replace')\">{{ 'nh-assistant.admin.server-editor.secret-replace' | translate }}</button>\n <button class=\"danger\" type=\"button\" (click)=\"setSecretAction('clear')\">{{ 'nh-assistant.admin.server-editor.secret-clear' | translate }}</button>\n }\n }\n </div>\n } @else {\n <p class=\"muted\">{{ 'nh-assistant.admin.server-editor.secret-none' | translate }}</p>\n }\n @if (showSecretInput()) {\n <label class=\"visually-hidden\" [for]=\"id + '-secret'\">{{ 'nh-assistant.admin.server-editor.secret-new' | translate }}</label>\n <input\n type=\"password\"\n class=\"mono\"\n autocomplete=\"new-password\"\n spellcheck=\"false\"\n [id]=\"id + '-secret'\"\n [attr.placeholder]=\"'nh-assistant.admin.server-editor.secret-new' | translate\"\n [attr.aria-describedby]=\"id + '-secret-hint'\"\n [value]=\"secret()\"\n (input)=\"onSecretInput($event)\" />\n }\n <p class=\"hint\" [id]=\"id + '-secret-hint'\">{{ 'nh-assistant.admin.server-editor.secret-hint' | translate }}</p>\n </fieldset>\n }\n\n <div class=\"field\">\n <label [for]=\"id + '-policy'\">{{ 'nh-assistant.admin.server-editor.required-policy' | translate }}</label>\n <input\n type=\"text\"\n class=\"mono\"\n [id]=\"id + '-policy'\"\n [value]=\"requiredPolicy()\"\n [attr.aria-describedby]=\"id + '-policy-hint'\"\n (input)=\"requiredPolicy.set(value($event))\" />\n <p class=\"hint\" [id]=\"id + '-policy-hint'\">{{ 'nh-assistant.admin.server-editor.required-policy-hint' | translate }}</p>\n </div>\n\n <label class=\"check\">\n <input type=\"checkbox\" [checked]=\"isEnabled()\" (change)=\"isEnabled.set(checked($event))\" />\n <span>{{ 'nh-assistant.admin.server-editor.enabled' | translate }}</span>\n </label>\n\n @if (error(); as failure) {\n <p class=\"message error\" role=\"alert\"><nh-assistant-icon name=\"warning\" />{{ errorKeys() | nhAssistantTranslate: failure.code }}</p>\n }\n\n <div class=\"actions\">\n <button class=\"secondary\" type=\"button\" (click)=\"cancelled.emit()\">{{ 'nh-assistant.admin.cancel' | translate }}</button>\n <button class=\"primary\" type=\"submit\" [disabled]=\"saving()\">\n {{ (saving() ? 'nh-assistant.admin.saving' : 'nh-assistant.admin.save') | translate }}\n </button>\n </div>\n</form>\n", styles: [":host{--nha-font: var(--nh-assistant-font, inherit);--nha-background: var(--nh-assistant-background, #ffffff);--nha-surface: var(--nh-assistant-surface, #f5f7fb);--nha-surface-strong: var(--nh-assistant-surface-strong, #eceff6);--nha-text: var(--nh-assistant-text, #080c1d);--nha-text-muted: var(--nh-assistant-text-muted, #5b6482);--nha-border: var(--nh-assistant-border, #dce0ec);--nha-accent: var(--nh-assistant-accent, #022962);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #c84f00);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #247d4e);--nha-success-soft: var(--nh-assistant-success-soft, #e6f5ec);--nha-warning: var(--nh-assistant-warning, #8a5a00);--nha-warning-soft: var(--nh-assistant-warning-soft, #fff3d9);--nha-danger: var(--nh-assistant-danger, #b92e52);--nha-danger-soft: var(--nh-assistant-danger-soft, #ffe9ef);--nha-focus: var(--nh-assistant-focus, #ff6700);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(2 41 98 / .16));--nha-radius: var(--nh-assistant-radius, 16px);--nha-radius-control: var(--nh-assistant-radius-control, 12px);--nha-mono: var(--nh-assistant-font-mono, ui-monospace, \"Cascadia Code\", Consolas, monospace)}@media(prefers-color-scheme:dark){:host{--nha-background: var(--nh-assistant-background, #0b0e22);--nha-surface: var(--nh-assistant-surface, #11152d);--nha-surface-strong: var(--nh-assistant-surface-strong, #181d3b);--nha-text: var(--nh-assistant-text, #ffffff);--nha-text-muted: var(--nh-assistant-text-muted, #b8bed8);--nha-border: var(--nh-assistant-border, #2b3155);--nha-accent: var(--nh-assistant-accent, #0f4087);--nha-accent-contrast: var(--nh-assistant-accent-contrast, #ffffff);--nha-highlight: var(--nh-assistant-highlight, #ff6700);--nha-user-bubble: var(--nh-assistant-user-bubble, #022962);--nha-user-text: var(--nh-assistant-user-text, #ffffff);--nha-success: var(--nh-assistant-success, #67c58f);--nha-success-soft: var(--nh-assistant-success-soft, #102d24);--nha-warning: var(--nh-assistant-warning, #ffbf5b);--nha-warning-soft: var(--nh-assistant-warning-soft, #342818);--nha-danger: var(--nh-assistant-danger, #ff6d8a);--nha-danger-soft: var(--nh-assistant-danger-soft, #351b2a);--nha-shadow: var(--nh-assistant-shadow, 0 16px 36px rgb(0 0 0 / .32))}}:host{display:block;min-width:0;color:var(--nha-text);font-family:var(--nha-font);font-size:14px}:host fieldset{min-width:0;margin:0;border:0;padding:0}:host legend,:host .label,:host .field>label{display:block;margin-bottom:6px;padding:0;color:var(--nha-text);font-size:12px;font-weight:700}:host input[type=text],:host input[type=url],:host input[type=password],:host input[type=search],:host 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}\n"] }]
|
|
477
|
+
}], ctorParameters: () => [], propDecorators: { server: [{ type: i0.Input, args: [{ isSignal: true, alias: "server", required: false }] }], saved: [{ type: i0.Output, args: ["saved"] }], cancelled: [{ type: i0.Output, args: ["cancelled"] }] } });
|
|
478
|
+
function isAllowedUrl(value) {
|
|
479
|
+
try {
|
|
480
|
+
const url = new URL(value);
|
|
481
|
+
return url.protocol === 'https:' || (url.protocol === 'http:' && url.hostname === 'localhost');
|
|
482
|
+
}
|
|
483
|
+
catch {
|
|
484
|
+
return false;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
let nextId$2 = 0;
|
|
489
|
+
/**
|
|
490
|
+
* The tools of one MCP server. Every tool starts disabled as a change that needs approval;
|
|
491
|
+
* the administrator enables it and chooses the effect. Remote annotations appear as hints
|
|
492
|
+
* only, and a changed remote input schema is shown as a warning.
|
|
493
|
+
*/
|
|
494
|
+
class NhAssistantAdminMcpToolsComponent {
|
|
495
|
+
constructor() {
|
|
496
|
+
this.admin = inject(NhAssistantAdminApiService);
|
|
497
|
+
this.server = input.required(...(ngDevMode ? [{ debugName: "server" }] : []));
|
|
498
|
+
this.closed = output();
|
|
499
|
+
/** Emitted after a sync so the host can refresh the server's sync status. */
|
|
500
|
+
this.synced = output();
|
|
501
|
+
this.id = `nh-assistant-mcp-tools-${nextId$2++}`;
|
|
502
|
+
this.effects = ['mutation', 'read-only'];
|
|
503
|
+
this.loading = signal(true, ...(ngDevMode ? [{ debugName: "loading" }] : []));
|
|
504
|
+
this.loadFailed = signal(false, ...(ngDevMode ? [{ debugName: "loadFailed" }] : []));
|
|
505
|
+
this.syncing = signal(false, ...(ngDevMode ? [{ debugName: "syncing" }] : []));
|
|
506
|
+
this.savingTool = signal(null, ...(ngDevMode ? [{ debugName: "savingTool" }] : []));
|
|
507
|
+
this.savedTool = signal(null, ...(ngDevMode ? [{ debugName: "savedTool" }] : []));
|
|
508
|
+
this.tools = signal([], ...(ngDevMode ? [{ debugName: "tools" }] : []));
|
|
509
|
+
this.drafts = signal({}, ...(ngDevMode ? [{ debugName: "drafts" }] : []));
|
|
510
|
+
this.error = signal(null, ...(ngDevMode ? [{ debugName: "error" }] : []));
|
|
511
|
+
this.errorKeys = computed(() => _nhAssistantErrorKeys(this.error()), ...(ngDevMode ? [{ debugName: "errorKeys" }] : []));
|
|
512
|
+
inject(DestroyRef).onDestroy(() => {
|
|
513
|
+
this.subscription?.unsubscribe();
|
|
514
|
+
this.rowSubscription?.unsubscribe();
|
|
515
|
+
});
|
|
516
|
+
effect(() => {
|
|
517
|
+
const server = this.server();
|
|
518
|
+
untracked(() => this.load(server.id));
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
load(serverId = this.server().id) {
|
|
522
|
+
this.loading.set(true);
|
|
523
|
+
this.loadFailed.set(false);
|
|
524
|
+
this.subscription?.unsubscribe();
|
|
525
|
+
this.subscription = this.admin.getMcpTools(serverId).subscribe({
|
|
526
|
+
next: tools => {
|
|
527
|
+
this.setTools(tools);
|
|
528
|
+
this.loading.set(false);
|
|
529
|
+
},
|
|
530
|
+
error: () => {
|
|
531
|
+
this.loading.set(false);
|
|
532
|
+
this.loadFailed.set(true);
|
|
533
|
+
}
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
sync() {
|
|
537
|
+
this.syncing.set(true);
|
|
538
|
+
this.error.set(null);
|
|
539
|
+
this.subscription?.unsubscribe();
|
|
540
|
+
this.subscription = this.admin.syncMcpServer(this.server().id).subscribe({
|
|
541
|
+
next: tools => {
|
|
542
|
+
this.setTools(tools);
|
|
543
|
+
this.syncing.set(false);
|
|
544
|
+
this.synced.emit();
|
|
545
|
+
},
|
|
546
|
+
error: (error) => {
|
|
547
|
+
this.syncing.set(false);
|
|
548
|
+
this.error.set(_toNhAssistantError(error));
|
|
549
|
+
this.synced.emit();
|
|
550
|
+
}
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
draft(tool) {
|
|
554
|
+
return this.drafts()[tool.remoteName] ?? toDraft(tool);
|
|
555
|
+
}
|
|
556
|
+
isDirty(tool) {
|
|
557
|
+
const draft = this.draft(tool);
|
|
558
|
+
return draft.isEnabled !== tool.isEnabled || draft.effect !== tool.effect ||
|
|
559
|
+
draft.descriptionOverride !== (tool.descriptionOverride ?? '');
|
|
560
|
+
}
|
|
561
|
+
change(tool, patch) {
|
|
562
|
+
this.savedTool.set(null);
|
|
563
|
+
this.drafts.update(drafts => ({ ...drafts, [tool.remoteName]: { ...this.draft(tool), ...patch } }));
|
|
564
|
+
}
|
|
565
|
+
value(event) {
|
|
566
|
+
return event.target.value;
|
|
567
|
+
}
|
|
568
|
+
checked(event) {
|
|
569
|
+
return event.target.checked;
|
|
570
|
+
}
|
|
571
|
+
save(tool) {
|
|
572
|
+
const draft = this.draft(tool);
|
|
573
|
+
const override = draft.descriptionOverride.trim();
|
|
574
|
+
this.savingTool.set(tool.remoteName);
|
|
575
|
+
this.savedTool.set(null);
|
|
576
|
+
this.error.set(null);
|
|
577
|
+
this.rowSubscription?.unsubscribe();
|
|
578
|
+
this.rowSubscription = this.admin.updateMcpTool(this.server().id, tool.remoteName, {
|
|
579
|
+
isEnabled: draft.isEnabled,
|
|
580
|
+
effect: draft.effect,
|
|
581
|
+
descriptionOverride: override.length > 0 ? override : null
|
|
582
|
+
}).subscribe({
|
|
583
|
+
next: updated => {
|
|
584
|
+
this.tools.update(tools => tools.map(item => item.remoteName === updated.remoteName ? updated : item));
|
|
585
|
+
this.drafts.update(drafts => {
|
|
586
|
+
const { [updated.remoteName]: removed, ...rest } = drafts;
|
|
587
|
+
return rest;
|
|
588
|
+
});
|
|
589
|
+
this.savingTool.set(null);
|
|
590
|
+
this.savedTool.set(updated.remoteName);
|
|
591
|
+
},
|
|
592
|
+
error: (error) => {
|
|
593
|
+
this.savingTool.set(null);
|
|
594
|
+
this.error.set(_toNhAssistantError(error));
|
|
595
|
+
}
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
setTools(tools) {
|
|
599
|
+
this.tools.set(tools);
|
|
600
|
+
this.drafts.set({});
|
|
601
|
+
this.savedTool.set(null);
|
|
602
|
+
}
|
|
603
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: NhAssistantAdminMcpToolsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
604
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.28", type: NhAssistantAdminMcpToolsComponent, isStandalone: true, selector: "nh-assistant-admin-mcp-tools", inputs: { server: { classPropertyName: "server", publicName: "server", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { closed: "closed", synced: "synced" }, ngImport: i0, template: "<section class=\"section\" [attr.aria-labelledby]=\"id + '-title'\" [attr.aria-busy]=\"loading() || syncing()\">\n <header class=\"section-header\">\n <div>\n <h3 [id]=\"id + '-title'\">{{ 'nh-assistant.admin.tools.title' | translate: { name: server().displayName } }}</h3>\n <p class=\"muted\">{{ 'nh-assistant.admin.tools.description' | translate }}</p>\n </div>\n <div class=\"actions\">\n <button class=\"secondary\" type=\"button\" (click)=\"closed.emit()\">{{ 'nh-assistant.admin.tools.back' | translate }}</button>\n <button class=\"primary\" type=\"button\" [disabled]=\"syncing()\" (click)=\"sync()\">\n <nh-assistant-icon name=\"refresh\" />{{ (syncing() ? 'nh-assistant.admin.mcp.syncing' : 'nh-assistant.admin.mcp.sync') | translate }}\n </button>\n </div>\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 (tools().length === 0) {\n <p class=\"state\">{{ 'nh-assistant.admin.tools.empty' | translate }}</p>\n } @else {\n <ul class=\"cards\">\n @for (tool of tools(); track tool.remoteName) {\n <li class=\"card\" [class.inactive]=\"!tool.isEnabled\" [attr.data-status]=\"tool.status\" [attr.aria-busy]=\"savingTool() === tool.remoteName\">\n <div class=\"card-header\">\n <strong class=\"mono\">{{ tool.remoteName }}</strong>\n <span class=\"badge\"\n [class.warning]=\"tool.status === 'schema-changed'\"\n [class.danger]=\"tool.status === 'missing'\">{{ 'nh-assistant.admin.tools.status.' + tool.status | translate }}</span>\n <span class=\"badge\" [class.success]=\"tool.isEnabled\">{{ (tool.isEnabled ? 'nh-assistant.admin.enabled' : 'nh-assistant.admin.disabled') | translate }}</span>\n @if (tool.readOnlyHint !== null) {\n <span class=\"badge hint\" [attr.title]=\"'nh-assistant.admin.tools.description' | translate\">\n {{ (tool.readOnlyHint ? 'nh-assistant.admin.tools.hint-read-only' : 'nh-assistant.admin.tools.hint-mutation') | translate }}\n </span>\n }\n </div>\n <span class=\"mono muted\">{{ tool.localId }}</span>\n\n @if (tool.status === 'schema-changed') {\n <p class=\"message warning\" role=\"alert\"><nh-assistant-icon name=\"warning\" />{{ 'nh-assistant.admin.tools.schema-changed-warning' | translate }}</p>\n }\n @if (tool.status === 'missing') {\n <p class=\"message error\"><nh-assistant-icon name=\"warning\" />{{ 'nh-assistant.admin.tools.missing-warning' | translate }}</p>\n }\n\n <p class=\"muted\"><span class=\"label\">{{ 'nh-assistant.admin.tools.remote-description' | translate }}</span>{{ tool.description }}</p>\n\n <div class=\"grid\">\n <label class=\"check\">\n <input\n type=\"checkbox\"\n [checked]=\"draft(tool).isEnabled\"\n [disabled]=\"tool.status === 'missing'\"\n (change)=\"change(tool, { isEnabled: checked($event) })\" />\n <span>{{ 'nh-assistant.admin.tools.enabled' | translate }}</span>\n </label>\n <div class=\"field\">\n <label [for]=\"id + '-effect-' + $index\">{{ 'nh-assistant.admin.tools.effect' | translate }}</label>\n <select [id]=\"id + '-effect-' + $index\" (change)=\"change(tool, { effect: $any(value($event)) })\">\n @for (effect of effects; track effect) {\n <option [value]=\"effect\" [selected]=\"draft(tool).effect === effect\">{{ 'nh-assistant.admin.effect.' + effect | translate }}</option>\n }\n </select>\n </div>\n </div>\n\n <div class=\"field\">\n <label [for]=\"id + '-override-' + $index\">{{ 'nh-assistant.admin.tools.description-override' | translate }}</label>\n <input\n type=\"text\"\n maxlength=\"1000\"\n [id]=\"id + '-override-' + $index\"\n [value]=\"draft(tool).descriptionOverride\"\n [attr.aria-describedby]=\"id + '-override-hint-' + $index\"\n (input)=\"change(tool, { descriptionOverride: value($event) })\" />\n <p class=\"hint\" [id]=\"id + '-override-hint-' + $index\">{{ 'nh-assistant.admin.tools.description-override-hint' | translate }}</p>\n </div>\n\n <div class=\"card-actions\">\n <button class=\"primary\" type=\"button\" [disabled]=\"!isDirty(tool) || savingTool() !== null\" (click)=\"save(tool)\">\n {{ (savingTool() === tool.remoteName ? 'nh-assistant.admin.saving' : 'nh-assistant.admin.tools.save') | translate }}\n </button>\n @if (savedTool() === tool.remoteName) {\n <span class=\"badge success\" role=\"status\"><nh-assistant-icon name=\"check\" />{{ 'nh-assistant.admin.saved' | translate }}</span>\n }\n </div>\n </li>\n }\n </ul>\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}\n"], dependencies: [{ kind: "component", type: _NhAssistantIconComponent, selector: "nh-assistant-icon", inputs: ["name"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }, { kind: "pipe", type: _NhAssistantTranslatePipe, name: "nhAssistantTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
605
|
+
}
|
|
606
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: NhAssistantAdminMcpToolsComponent, decorators: [{
|
|
607
|
+
type: Component,
|
|
608
|
+
args: [{ selector: 'nh-assistant-admin-mcp-tools', standalone: true, imports: [TranslatePipe, _NhAssistantTranslatePipe, _NhAssistantIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<section class=\"section\" [attr.aria-labelledby]=\"id + '-title'\" [attr.aria-busy]=\"loading() || syncing()\">\n <header class=\"section-header\">\n <div>\n <h3 [id]=\"id + '-title'\">{{ 'nh-assistant.admin.tools.title' | translate: { name: server().displayName } }}</h3>\n <p class=\"muted\">{{ 'nh-assistant.admin.tools.description' | translate }}</p>\n </div>\n <div class=\"actions\">\n <button class=\"secondary\" type=\"button\" (click)=\"closed.emit()\">{{ 'nh-assistant.admin.tools.back' | translate }}</button>\n <button class=\"primary\" type=\"button\" [disabled]=\"syncing()\" (click)=\"sync()\">\n <nh-assistant-icon name=\"refresh\" />{{ (syncing() ? 'nh-assistant.admin.mcp.syncing' : 'nh-assistant.admin.mcp.sync') | translate }}\n </button>\n </div>\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 (tools().length === 0) {\n <p class=\"state\">{{ 'nh-assistant.admin.tools.empty' | translate }}</p>\n } @else {\n <ul class=\"cards\">\n @for (tool of tools(); track tool.remoteName) {\n <li class=\"card\" [class.inactive]=\"!tool.isEnabled\" [attr.data-status]=\"tool.status\" [attr.aria-busy]=\"savingTool() === tool.remoteName\">\n <div class=\"card-header\">\n <strong class=\"mono\">{{ tool.remoteName }}</strong>\n <span class=\"badge\"\n [class.warning]=\"tool.status === 'schema-changed'\"\n [class.danger]=\"tool.status === 'missing'\">{{ 'nh-assistant.admin.tools.status.' + tool.status | translate }}</span>\n <span class=\"badge\" [class.success]=\"tool.isEnabled\">{{ (tool.isEnabled ? 'nh-assistant.admin.enabled' : 'nh-assistant.admin.disabled') | translate }}</span>\n @if (tool.readOnlyHint !== null) {\n <span class=\"badge hint\" [attr.title]=\"'nh-assistant.admin.tools.description' | translate\">\n {{ (tool.readOnlyHint ? 'nh-assistant.admin.tools.hint-read-only' : 'nh-assistant.admin.tools.hint-mutation') | translate }}\n </span>\n }\n </div>\n <span class=\"mono muted\">{{ tool.localId }}</span>\n\n @if (tool.status === 'schema-changed') {\n <p class=\"message warning\" role=\"alert\"><nh-assistant-icon name=\"warning\" />{{ 'nh-assistant.admin.tools.schema-changed-warning' | translate }}</p>\n }\n @if (tool.status === 'missing') {\n <p class=\"message error\"><nh-assistant-icon name=\"warning\" />{{ 'nh-assistant.admin.tools.missing-warning' | translate }}</p>\n }\n\n <p class=\"muted\"><span class=\"label\">{{ 'nh-assistant.admin.tools.remote-description' | translate }}</span>{{ tool.description }}</p>\n\n <div class=\"grid\">\n <label class=\"check\">\n <input\n type=\"checkbox\"\n [checked]=\"draft(tool).isEnabled\"\n [disabled]=\"tool.status === 'missing'\"\n (change)=\"change(tool, { isEnabled: checked($event) })\" />\n <span>{{ 'nh-assistant.admin.tools.enabled' | translate }}</span>\n </label>\n <div class=\"field\">\n <label [for]=\"id + '-effect-' + $index\">{{ 'nh-assistant.admin.tools.effect' | translate }}</label>\n <select [id]=\"id + '-effect-' + $index\" (change)=\"change(tool, { effect: $any(value($event)) })\">\n @for (effect of effects; track effect) {\n <option [value]=\"effect\" [selected]=\"draft(tool).effect === effect\">{{ 'nh-assistant.admin.effect.' + effect | translate }}</option>\n }\n </select>\n </div>\n </div>\n\n <div class=\"field\">\n <label [for]=\"id + '-override-' + $index\">{{ 'nh-assistant.admin.tools.description-override' | translate }}</label>\n <input\n type=\"text\"\n maxlength=\"1000\"\n [id]=\"id + '-override-' + $index\"\n [value]=\"draft(tool).descriptionOverride\"\n [attr.aria-describedby]=\"id + '-override-hint-' + $index\"\n (input)=\"change(tool, { descriptionOverride: value($event) })\" />\n <p class=\"hint\" [id]=\"id + '-override-hint-' + $index\">{{ 'nh-assistant.admin.tools.description-override-hint' | translate }}</p>\n </div>\n\n <div class=\"card-actions\">\n <button class=\"primary\" type=\"button\" [disabled]=\"!isDirty(tool) || savingTool() !== null\" (click)=\"save(tool)\">\n {{ (savingTool() === tool.remoteName ? 'nh-assistant.admin.saving' : 'nh-assistant.admin.tools.save') | translate }}\n </button>\n @if (savedTool() === tool.remoteName) {\n <span class=\"badge success\" role=\"status\"><nh-assistant-icon name=\"check\" />{{ 'nh-assistant.admin.saved' | translate }}</span>\n }\n </div>\n </li>\n }\n </ul>\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}\n"] }]
|
|
609
|
+
}], ctorParameters: () => [], propDecorators: { server: [{ type: i0.Input, args: [{ isSignal: true, alias: "server", required: true }] }], closed: [{ type: i0.Output, args: ["closed"] }], synced: [{ type: i0.Output, args: ["synced"] }] } });
|
|
610
|
+
function toDraft(tool) {
|
|
611
|
+
return { isEnabled: tool.isEnabled, effect: tool.effect, descriptionOverride: tool.descriptionOverride ?? '' };
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
let nextId$1 = 0;
|
|
615
|
+
/**
|
|
616
|
+
* MCP server administration: list, connection test, tool sync, the server editor and the
|
|
617
|
+
* tool table of one server.
|
|
618
|
+
*/
|
|
619
|
+
class NhAssistantAdminMcpServersComponent {
|
|
620
|
+
constructor() {
|
|
621
|
+
this.admin = inject(NhAssistantAdminApiService);
|
|
622
|
+
this.actions = new Subscription();
|
|
623
|
+
this.id = `nh-assistant-admin-mcp-${nextId$1++}`;
|
|
624
|
+
this.loading = signal(true, ...(ngDevMode ? [{ debugName: "loading" }] : []));
|
|
625
|
+
this.loadFailed = signal(false, ...(ngDevMode ? [{ debugName: "loadFailed" }] : []));
|
|
626
|
+
this.servers = signal([], ...(ngDevMode ? [{ debugName: "servers" }] : []));
|
|
627
|
+
/** `undefined`: list; `null`: new server; a server: editing that server. */
|
|
628
|
+
this.editing = signal(undefined, ...(ngDevMode ? [{ debugName: "editing" }] : []));
|
|
629
|
+
this.toolsOf = signal(null, ...(ngDevMode ? [{ debugName: "toolsOf" }] : []));
|
|
630
|
+
this.tests = signal({}, ...(ngDevMode ? [{ debugName: "tests" }] : []));
|
|
631
|
+
this.confirmingDelete = signal(null, ...(ngDevMode ? [{ debugName: "confirmingDelete" }] : []));
|
|
632
|
+
this.error = signal(null, ...(ngDevMode ? [{ debugName: "error" }] : []));
|
|
633
|
+
this.errorKeys = computed(() => _nhAssistantErrorKeys(this.error()), ...(ngDevMode ? [{ debugName: "errorKeys" }] : []));
|
|
634
|
+
inject(DestroyRef).onDestroy(() => {
|
|
635
|
+
this.subscription?.unsubscribe();
|
|
636
|
+
this.actions.unsubscribe();
|
|
637
|
+
});
|
|
638
|
+
this.load();
|
|
639
|
+
}
|
|
640
|
+
load() {
|
|
641
|
+
this.loading.set(true);
|
|
642
|
+
this.loadFailed.set(false);
|
|
643
|
+
this.subscription?.unsubscribe();
|
|
644
|
+
this.subscription = this.admin.getMcpServers().subscribe({
|
|
645
|
+
next: servers => {
|
|
646
|
+
this.servers.set(servers);
|
|
647
|
+
this.loading.set(false);
|
|
648
|
+
},
|
|
649
|
+
error: () => {
|
|
650
|
+
this.loading.set(false);
|
|
651
|
+
this.loadFailed.set(true);
|
|
652
|
+
}
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
/** Reloads the list silently, for example after a sync changed the sync status. */
|
|
656
|
+
refresh() {
|
|
657
|
+
this.actions.add(this.admin.getMcpServers().subscribe({ next: servers => this.servers.set(servers), error: () => undefined }));
|
|
658
|
+
}
|
|
659
|
+
create() {
|
|
660
|
+
this.error.set(null);
|
|
661
|
+
this.editing.set(null);
|
|
662
|
+
}
|
|
663
|
+
edit(server) {
|
|
664
|
+
this.error.set(null);
|
|
665
|
+
this.editing.set(server);
|
|
666
|
+
}
|
|
667
|
+
onSaved(server) {
|
|
668
|
+
this.servers.update(servers => servers.some(item => item.id === server.id)
|
|
669
|
+
? servers.map(item => item.id === server.id ? server : item)
|
|
670
|
+
: [...servers, server]);
|
|
671
|
+
this.editing.set(undefined);
|
|
672
|
+
}
|
|
673
|
+
test(server) {
|
|
674
|
+
this.setTest(server.id, { running: true, result: null });
|
|
675
|
+
this.actions.add(this.admin.testMcpServer(server.id).subscribe({
|
|
676
|
+
next: result => this.setTest(server.id, { running: false, result }),
|
|
677
|
+
error: (error) => {
|
|
678
|
+
const failure = _toNhAssistantError(error);
|
|
679
|
+
this.setTest(server.id, { running: false, result: { ok: false, code: failure.code, toolCount: null } });
|
|
680
|
+
}
|
|
681
|
+
}));
|
|
682
|
+
}
|
|
683
|
+
testState(serverId) {
|
|
684
|
+
return this.tests()[serverId] ?? null;
|
|
685
|
+
}
|
|
686
|
+
testErrorKeys(code) {
|
|
687
|
+
return code ? [nhAssistantErrorMessageKey(code), 'nh-assistant.errors.generic'] : ['nh-assistant.errors.generic'];
|
|
688
|
+
}
|
|
689
|
+
remove(server) {
|
|
690
|
+
this.confirmingDelete.set(null);
|
|
691
|
+
this.error.set(null);
|
|
692
|
+
this.actions.add(this.admin.deleteMcpServer(server.id).subscribe({
|
|
693
|
+
complete: () => this.servers.update(servers => servers.filter(item => item.id !== server.id)),
|
|
694
|
+
error: (error) => this.error.set(_toNhAssistantError(error))
|
|
695
|
+
}));
|
|
696
|
+
}
|
|
697
|
+
setTest(serverId, state) {
|
|
698
|
+
this.tests.update(tests => ({ ...tests, [serverId]: state }));
|
|
699
|
+
}
|
|
700
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: NhAssistantAdminMcpServersComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
701
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.28", type: NhAssistantAdminMcpServersComponent, isStandalone: true, selector: "nh-assistant-admin-mcp-servers", ngImport: i0, template: "@if (editing() !== undefined) {\n <nh-assistant-admin-mcp-server-editor [server]=\"editing() ?? null\" (saved)=\"onSaved($event)\" (cancelled)=\"editing.set(undefined)\" />\n} @else if (toolsOf(); as server) {\n <nh-assistant-admin-mcp-tools [server]=\"server\" (closed)=\"toolsOf.set(null); refresh()\" (synced)=\"refresh()\" />\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.mcp.title' | translate }}</h3>\n <p class=\"muted\">{{ 'nh-assistant.admin.mcp.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.mcp.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 (servers().length === 0) {\n <p class=\"state\">{{ 'nh-assistant.admin.mcp.empty' | translate }}</p>\n } @else {\n <ul class=\"cards\">\n @for (server of servers(); track server.id) {\n <li class=\"card\" [class.inactive]=\"!server.isEnabled\">\n <div class=\"card-header\">\n <strong>{{ server.displayName }}</strong>\n <span class=\"mono muted\">{{ server.id }}</span>\n <span class=\"badge\" [class.success]=\"server.isEnabled\">{{ (server.isEnabled ? 'nh-assistant.admin.enabled' : 'nh-assistant.admin.disabled') | translate }}</span>\n @if (server.lastSyncStatus) {\n <span class=\"badge\" [class.success]=\"server.lastSyncStatus === 'ok'\" [class.danger]=\"server.lastSyncStatus === 'failed'\">\n {{ (server.lastSyncStatus === 'ok' ? 'nh-assistant.admin.mcp.sync-ok' : 'nh-assistant.admin.mcp.sync-failed') | translate }}\n </span>\n }\n </div>\n <span class=\"mono muted url\">{{ server.url }}</span>\n <div class=\"meta\">\n <span>{{ 'nh-assistant.admin.mcp.auth.' + server.authMode | translate }}</span>\n @if (server.authMode === 'bearer' || server.authMode === 'api-key') {\n <span class=\"badge\" [class.success]=\"server.hasSecret\" [class.warning]=\"!server.hasSecret\">\n {{ (server.hasSecret ? 'nh-assistant.admin.mcp.secret-set' : 'nh-assistant.admin.mcp.no-secret') | translate }}\n </span>\n }\n <span>\n @if (server.lastSyncAt) {\n {{ 'nh-assistant.admin.mcp.last-sync' | translate: { date: (server.lastSyncAt | date: 'medium') } }}\n } @else {\n {{ 'nh-assistant.admin.mcp.never-synced' | translate }}\n }\n </span>\n <span>\n @if (server.assignedAgentIds.length > 0) {\n {{ 'nh-assistant.admin.mcp.assigned' | translate: { agents: server.assignedAgentIds.join(', ') } }}\n } @else {\n {{ 'nh-assistant.admin.mcp.unassigned' | translate }}\n }\n </span>\n </div>\n\n @if (testState(server.id); as state) {\n @if (state.running) {\n <p class=\"muted\" role=\"status\">{{ 'nh-assistant.admin.mcp.testing' | translate }}</p>\n } @else if (state.result?.ok) {\n <p class=\"message success\" role=\"status\"><nh-assistant-icon name=\"check\" />{{ 'nh-assistant.admin.mcp.test-ok' | translate: { count: state.result?.toolCount ?? 0 } }}</p>\n } @else {\n <p class=\"message error\" role=\"alert\"><nh-assistant-icon name=\"warning\" />{{ testErrorKeys(state.result?.code ?? null) | nhAssistantTranslate: (state.result?.code ?? '') }}</p>\n }\n }\n\n <div class=\"card-actions\">\n <button class=\"secondary\" type=\"button\" (click)=\"edit(server)\"><nh-assistant-icon name=\"edit\" />{{ 'nh-assistant.admin.edit' | translate }}</button>\n <button class=\"secondary\" type=\"button\" [disabled]=\"testState(server.id)?.running\" (click)=\"test(server)\">\n <nh-assistant-icon name=\"link\" />{{ 'nh-assistant.admin.mcp.test' | translate }}\n </button>\n <button class=\"secondary\" type=\"button\" (click)=\"toolsOf.set(server)\"><nh-assistant-icon name=\"tool\" />{{ 'nh-assistant.admin.mcp.tools' | translate }}</button>\n @if (confirmingDelete() === server.id) {\n <button class=\"danger\" type=\"button\" (click)=\"remove(server)\">{{ 'nh-assistant.admin.confirm-delete' | translate }}</button>\n <button class=\"secondary\" type=\"button\" (click)=\"confirmingDelete.set(null)\">{{ 'nh-assistant.admin.keep' | translate }}</button>\n } @else {\n <button class=\"danger\" type=\"button\" (click)=\"confirmingDelete.set(server.id)\"><nh-assistant-icon name=\"trash\" />{{ 'nh-assistant.admin.delete' | translate }}</button>\n }\n </div>\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}\n"], dependencies: [{ kind: "component", type: _NhAssistantIconComponent, selector: "nh-assistant-icon", inputs: ["name"] }, { kind: "component", type: NhAssistantAdminMcpServerEditorComponent, selector: "nh-assistant-admin-mcp-server-editor", inputs: ["server"], outputs: ["saved", "cancelled"] }, { kind: "component", type: NhAssistantAdminMcpToolsComponent, selector: "nh-assistant-admin-mcp-tools", inputs: ["server"], outputs: ["closed", "synced"] }, { kind: "pipe", type: DatePipe, name: "date" }, { kind: "pipe", type: TranslatePipe, name: "translate" }, { kind: "pipe", type: _NhAssistantTranslatePipe, name: "nhAssistantTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
702
|
+
}
|
|
703
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: NhAssistantAdminMcpServersComponent, decorators: [{
|
|
704
|
+
type: Component,
|
|
705
|
+
args: [{ selector: 'nh-assistant-admin-mcp-servers', standalone: true, imports: [
|
|
706
|
+
DatePipe,
|
|
707
|
+
TranslatePipe,
|
|
708
|
+
_NhAssistantTranslatePipe,
|
|
709
|
+
_NhAssistantIconComponent,
|
|
710
|
+
NhAssistantAdminMcpServerEditorComponent,
|
|
711
|
+
NhAssistantAdminMcpToolsComponent
|
|
712
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (editing() !== undefined) {\n <nh-assistant-admin-mcp-server-editor [server]=\"editing() ?? null\" (saved)=\"onSaved($event)\" (cancelled)=\"editing.set(undefined)\" />\n} @else if (toolsOf(); as server) {\n <nh-assistant-admin-mcp-tools [server]=\"server\" (closed)=\"toolsOf.set(null); refresh()\" (synced)=\"refresh()\" />\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.mcp.title' | translate }}</h3>\n <p class=\"muted\">{{ 'nh-assistant.admin.mcp.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.mcp.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 (servers().length === 0) {\n <p class=\"state\">{{ 'nh-assistant.admin.mcp.empty' | translate }}</p>\n } @else {\n <ul class=\"cards\">\n @for (server of servers(); track server.id) {\n <li class=\"card\" [class.inactive]=\"!server.isEnabled\">\n <div class=\"card-header\">\n <strong>{{ server.displayName }}</strong>\n <span class=\"mono muted\">{{ server.id }}</span>\n <span class=\"badge\" [class.success]=\"server.isEnabled\">{{ (server.isEnabled ? 'nh-assistant.admin.enabled' : 'nh-assistant.admin.disabled') | translate }}</span>\n @if (server.lastSyncStatus) {\n <span class=\"badge\" [class.success]=\"server.lastSyncStatus === 'ok'\" [class.danger]=\"server.lastSyncStatus === 'failed'\">\n {{ (server.lastSyncStatus === 'ok' ? 'nh-assistant.admin.mcp.sync-ok' : 'nh-assistant.admin.mcp.sync-failed') | translate }}\n </span>\n }\n </div>\n <span class=\"mono muted url\">{{ server.url }}</span>\n <div class=\"meta\">\n <span>{{ 'nh-assistant.admin.mcp.auth.' + server.authMode | translate }}</span>\n @if (server.authMode === 'bearer' || server.authMode === 'api-key') {\n <span class=\"badge\" [class.success]=\"server.hasSecret\" [class.warning]=\"!server.hasSecret\">\n {{ (server.hasSecret ? 'nh-assistant.admin.mcp.secret-set' : 'nh-assistant.admin.mcp.no-secret') | translate }}\n </span>\n }\n <span>\n @if (server.lastSyncAt) {\n {{ 'nh-assistant.admin.mcp.last-sync' | translate: { date: (server.lastSyncAt | date: 'medium') } }}\n } @else {\n {{ 'nh-assistant.admin.mcp.never-synced' | translate }}\n }\n </span>\n <span>\n @if (server.assignedAgentIds.length > 0) {\n {{ 'nh-assistant.admin.mcp.assigned' | translate: { agents: server.assignedAgentIds.join(', ') } }}\n } @else {\n {{ 'nh-assistant.admin.mcp.unassigned' | translate }}\n }\n </span>\n </div>\n\n @if (testState(server.id); as state) {\n @if (state.running) {\n <p class=\"muted\" role=\"status\">{{ 'nh-assistant.admin.mcp.testing' | translate }}</p>\n } @else if (state.result?.ok) {\n <p class=\"message success\" role=\"status\"><nh-assistant-icon name=\"check\" />{{ 'nh-assistant.admin.mcp.test-ok' | translate: { count: state.result?.toolCount ?? 0 } }}</p>\n } @else {\n <p class=\"message error\" role=\"alert\"><nh-assistant-icon name=\"warning\" />{{ testErrorKeys(state.result?.code ?? null) | nhAssistantTranslate: (state.result?.code ?? '') }}</p>\n }\n }\n\n <div class=\"card-actions\">\n <button class=\"secondary\" type=\"button\" (click)=\"edit(server)\"><nh-assistant-icon name=\"edit\" />{{ 'nh-assistant.admin.edit' | translate }}</button>\n <button class=\"secondary\" type=\"button\" [disabled]=\"testState(server.id)?.running\" (click)=\"test(server)\">\n <nh-assistant-icon name=\"link\" />{{ 'nh-assistant.admin.mcp.test' | translate }}\n </button>\n <button class=\"secondary\" type=\"button\" (click)=\"toolsOf.set(server)\"><nh-assistant-icon name=\"tool\" />{{ 'nh-assistant.admin.mcp.tools' | translate }}</button>\n @if (confirmingDelete() === server.id) {\n <button class=\"danger\" type=\"button\" (click)=\"remove(server)\">{{ 'nh-assistant.admin.confirm-delete' | translate }}</button>\n <button class=\"secondary\" type=\"button\" (click)=\"confirmingDelete.set(null)\">{{ 'nh-assistant.admin.keep' | translate }}</button>\n } @else {\n <button class=\"danger\" type=\"button\" (click)=\"confirmingDelete.set(server.id)\"><nh-assistant-icon name=\"trash\" />{{ 'nh-assistant.admin.delete' | translate }}</button>\n }\n </div>\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}\n"] }]
|
|
713
|
+
}], ctorParameters: () => [] });
|
|
714
|
+
|
|
715
|
+
let nextId = 0;
|
|
716
|
+
/**
|
|
717
|
+
* The assistant administration page with the tabs Context, Agents and MCP servers. The host
|
|
718
|
+
* routes to it and guards the route with its admin permission; the component additionally
|
|
719
|
+
* shows a no-access state unless the server reports `canAdminister`.
|
|
720
|
+
*/
|
|
721
|
+
class NhAssistantAdminComponent {
|
|
722
|
+
constructor() {
|
|
723
|
+
this.host = inject(ElementRef);
|
|
724
|
+
this.store = inject(NhAssistantStore);
|
|
725
|
+
/** Tab shown first. */
|
|
726
|
+
this.initialTab = input('context', ...(ngDevMode ? [{ debugName: "initialTab" }] : []));
|
|
727
|
+
this.id = `nh-assistant-admin-${nextId++}`;
|
|
728
|
+
this.tabs = ['context', 'agents', 'mcp-servers'];
|
|
729
|
+
this.activeTab = signal(null, ...(ngDevMode ? [{ debugName: "activeTab" }] : []));
|
|
730
|
+
this.ready = signal(false, ...(ngDevMode ? [{ debugName: "ready" }] : []));
|
|
731
|
+
afterNextRender(() => {
|
|
732
|
+
void this.store.initialize().then(() => this.ready.set(true));
|
|
733
|
+
});
|
|
734
|
+
}
|
|
735
|
+
selected() {
|
|
736
|
+
return this.activeTab() ?? this.initialTab();
|
|
737
|
+
}
|
|
738
|
+
select(tab) {
|
|
739
|
+
this.activeTab.set(tab);
|
|
740
|
+
}
|
|
741
|
+
/** Arrow keys, Home and End move between tabs as the ARIA tabs pattern describes. */
|
|
742
|
+
onTabKeydown(event) {
|
|
743
|
+
const index = this.tabs.indexOf(this.selected());
|
|
744
|
+
const last = this.tabs.length - 1;
|
|
745
|
+
const targets = {
|
|
746
|
+
ArrowRight: index === last ? 0 : index + 1,
|
|
747
|
+
ArrowLeft: index === 0 ? last : index - 1,
|
|
748
|
+
Home: 0,
|
|
749
|
+
End: last
|
|
750
|
+
};
|
|
751
|
+
const next = targets[event.key];
|
|
752
|
+
if (next === undefined) {
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
755
|
+
event.preventDefault();
|
|
756
|
+
this.select(this.tabs[next]);
|
|
757
|
+
this.host.nativeElement.querySelector(`#${this.id}-tab-${this.tabs[next]}`)?.focus();
|
|
758
|
+
}
|
|
759
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: NhAssistantAdminComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
760
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.28", type: NhAssistantAdminComponent, isStandalone: true, selector: "nh-assistant-admin", inputs: { initialTab: { classPropertyName: "initialTab", publicName: "initialTab", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<section class=\"admin\" [attr.aria-labelledby]=\"id + '-title'\">\n <header class=\"page-header\">\n <nh-assistant-icon class=\"page-icon\" name=\"admin\" />\n <div>\n <h2 [id]=\"id + '-title'\">{{ 'nh-assistant.admin.title' | translate }}</h2>\n <p>{{ 'nh-assistant.admin.description' | translate }}</p>\n </div>\n </header>\n\n @if (!ready() || (store.statusLoading() && !store.enabled())) {\n <p class=\"state\" role=\"status\">{{ 'nh-assistant.admin.loading' | translate }}</p>\n } @else if (!store.enabled()) {\n <div class=\"state\">\n <nh-assistant-icon class=\"state-icon\" name=\"warning\" />\n <p class=\"state-title\">{{ 'nh-assistant.panel.unavailable-title' | translate }}</p>\n <p>{{ 'nh-assistant.panel.unavailable-description' | translate }}</p>\n </div>\n } @else if (!store.canAdminister()) {\n <div class=\"state\">\n <nh-assistant-icon class=\"state-icon\" name=\"warning\" />\n <p class=\"state-title\">{{ 'nh-assistant.admin.forbidden-title' | translate }}</p>\n <p>{{ 'nh-assistant.admin.forbidden-description' | translate }}</p>\n </div>\n } @else {\n <div class=\"tabs\" role=\"tablist\" [attr.aria-label]=\"'nh-assistant.admin.tabs-label' | translate\" (keydown)=\"onTabKeydown($event)\">\n @for (tab of tabs; track tab) {\n <button\n type=\"button\"\n role=\"tab\"\n class=\"tab\"\n [id]=\"id + '-tab-' + tab\"\n [attr.aria-selected]=\"selected() === tab\"\n [attr.aria-controls]=\"id + '-panel'\"\n [attr.tabindex]=\"selected() === tab ? 0 : -1\"\n (click)=\"select(tab)\">\n {{ 'nh-assistant.admin.tabs.' + tab | translate }}\n </button>\n }\n </div>\n\n <div class=\"panel\" role=\"tabpanel\" tabindex=\"0\" [id]=\"id + '-panel'\" [attr.aria-labelledby]=\"id + '-tab-' + selected()\">\n @switch (selected()) {\n @case ('context') {\n <nh-assistant-admin-context />\n }\n @case ('agents') {\n <nh-assistant-admin-agents />\n }\n @case ('mcp-servers') {\n <nh-assistant-admin-mcp-servers />\n }\n }\n </div>\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)}.admin{display:flex;flex-direction:column;gap:18px;min-width:0}.page-header{display:flex;align-items:flex-start;gap:12px}.page-header h2{margin:0 0 4px;font-size:clamp(20px,3vw,26px);line-height:1.2;letter-spacing:-.02em}.page-header p{max-width:72ch;margin:0;color:var(--nha-text-muted);font-size:14px}.page-icon{flex:none;margin-top:4px;color:var(--nha-highlight);font-size:24px}.state{display:flex;flex-direction:column;align-items:flex-start;gap:4px;margin:0;border:1px dashed var(--nha-border);border-radius:var(--nha-radius);padding:24px;color:var(--nha-text-muted);font-size:14px}.state p{margin:0}.state-icon{color:var(--nha-warning);font-size:24px}.state .state-title{color:var(--nha-text);font-weight:700}.tabs{display:flex;gap:4px;max-width:100%;overflow-x:auto;overflow-y:hidden;border-bottom:1px solid var(--nha-border)}.tab{flex:none;min-height:40px;margin-bottom:-1px;border:0;border-bottom:2px solid transparent;padding:8px 14px;color:var(--nha-text-muted);background:transparent;font:inherit;font-size:14px;font-weight:700;cursor:pointer}.tab:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.tab:hover{color:var(--nha-text)}.tab[aria-selected=true]{border-bottom-color:var(--nha-highlight);color:var(--nha-text)}.panel{min-width:0;outline:none}.panel:focus-visible{box-shadow:0 0 0 2px var(--nha-focus);border-radius:var(--nha-radius-control)}\n"], dependencies: [{ kind: "component", type: _NhAssistantIconComponent, selector: "nh-assistant-icon", inputs: ["name"] }, { kind: "component", type: NhAssistantAdminContextComponent, selector: "nh-assistant-admin-context" }, { kind: "component", type: NhAssistantAdminAgentsComponent, selector: "nh-assistant-admin-agents" }, { kind: "component", type: NhAssistantAdminMcpServersComponent, selector: "nh-assistant-admin-mcp-servers" }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
761
|
+
}
|
|
762
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: NhAssistantAdminComponent, decorators: [{
|
|
763
|
+
type: Component,
|
|
764
|
+
args: [{ selector: 'nh-assistant-admin', standalone: true, imports: [
|
|
765
|
+
TranslatePipe,
|
|
766
|
+
_NhAssistantIconComponent,
|
|
767
|
+
NhAssistantAdminContextComponent,
|
|
768
|
+
NhAssistantAdminAgentsComponent,
|
|
769
|
+
NhAssistantAdminMcpServersComponent
|
|
770
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, template: "<section class=\"admin\" [attr.aria-labelledby]=\"id + '-title'\">\n <header class=\"page-header\">\n <nh-assistant-icon class=\"page-icon\" name=\"admin\" />\n <div>\n <h2 [id]=\"id + '-title'\">{{ 'nh-assistant.admin.title' | translate }}</h2>\n <p>{{ 'nh-assistant.admin.description' | translate }}</p>\n </div>\n </header>\n\n @if (!ready() || (store.statusLoading() && !store.enabled())) {\n <p class=\"state\" role=\"status\">{{ 'nh-assistant.admin.loading' | translate }}</p>\n } @else if (!store.enabled()) {\n <div class=\"state\">\n <nh-assistant-icon class=\"state-icon\" name=\"warning\" />\n <p class=\"state-title\">{{ 'nh-assistant.panel.unavailable-title' | translate }}</p>\n <p>{{ 'nh-assistant.panel.unavailable-description' | translate }}</p>\n </div>\n } @else if (!store.canAdminister()) {\n <div class=\"state\">\n <nh-assistant-icon class=\"state-icon\" name=\"warning\" />\n <p class=\"state-title\">{{ 'nh-assistant.admin.forbidden-title' | translate }}</p>\n <p>{{ 'nh-assistant.admin.forbidden-description' | translate }}</p>\n </div>\n } @else {\n <div class=\"tabs\" role=\"tablist\" [attr.aria-label]=\"'nh-assistant.admin.tabs-label' | translate\" (keydown)=\"onTabKeydown($event)\">\n @for (tab of tabs; track tab) {\n <button\n type=\"button\"\n role=\"tab\"\n class=\"tab\"\n [id]=\"id + '-tab-' + tab\"\n [attr.aria-selected]=\"selected() === tab\"\n [attr.aria-controls]=\"id + '-panel'\"\n [attr.tabindex]=\"selected() === tab ? 0 : -1\"\n (click)=\"select(tab)\">\n {{ 'nh-assistant.admin.tabs.' + tab | translate }}\n </button>\n }\n </div>\n\n <div class=\"panel\" role=\"tabpanel\" tabindex=\"0\" [id]=\"id + '-panel'\" [attr.aria-labelledby]=\"id + '-tab-' + selected()\">\n @switch (selected()) {\n @case ('context') {\n <nh-assistant-admin-context />\n }\n @case ('agents') {\n <nh-assistant-admin-agents />\n }\n @case ('mcp-servers') {\n <nh-assistant-admin-mcp-servers />\n }\n }\n </div>\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)}.admin{display:flex;flex-direction:column;gap:18px;min-width:0}.page-header{display:flex;align-items:flex-start;gap:12px}.page-header h2{margin:0 0 4px;font-size:clamp(20px,3vw,26px);line-height:1.2;letter-spacing:-.02em}.page-header p{max-width:72ch;margin:0;color:var(--nha-text-muted);font-size:14px}.page-icon{flex:none;margin-top:4px;color:var(--nha-highlight);font-size:24px}.state{display:flex;flex-direction:column;align-items:flex-start;gap:4px;margin:0;border:1px dashed var(--nha-border);border-radius:var(--nha-radius);padding:24px;color:var(--nha-text-muted);font-size:14px}.state p{margin:0}.state-icon{color:var(--nha-warning);font-size:24px}.state .state-title{color:var(--nha-text);font-weight:700}.tabs{display:flex;gap:4px;max-width:100%;overflow-x:auto;overflow-y:hidden;border-bottom:1px solid var(--nha-border)}.tab{flex:none;min-height:40px;margin-bottom:-1px;border:0;border-bottom:2px solid transparent;padding:8px 14px;color:var(--nha-text-muted);background:transparent;font:inherit;font-size:14px;font-weight:700;cursor:pointer}.tab:focus-visible{outline:3px solid color-mix(in srgb,var(--nha-focus) 80%,transparent);outline-offset:2px}.tab:hover{color:var(--nha-text)}.tab[aria-selected=true]{border-bottom-color:var(--nha-highlight);color:var(--nha-text)}.panel{min-width:0;outline:none}.panel:focus-visible{box-shadow:0 0 0 2px var(--nha-focus);border-radius:var(--nha-radius-control)}\n"] }]
|
|
771
|
+
}], ctorParameters: () => [], propDecorators: { initialTab: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialTab", required: false }] }] } });
|
|
772
|
+
|
|
773
|
+
/*
|
|
774
|
+
* Public API surface of @newheap/platform-ai-chat/admin.
|
|
775
|
+
*
|
|
776
|
+
* The administration page is a separate entry point so hosts load it only on their lazy
|
|
777
|
+
* admin route and the chat panel stays small.
|
|
778
|
+
*/
|
|
779
|
+
|
|
780
|
+
/**
|
|
781
|
+
* Generated bundle index. Do not edit.
|
|
782
|
+
*/
|
|
783
|
+
|
|
784
|
+
export { NH_ASSISTANT_MAX_INSTRUCTIONS, NhAssistantAdminAgentEditorComponent, NhAssistantAdminAgentsComponent, NhAssistantAdminComponent, NhAssistantAdminContextComponent, NhAssistantAdminMcpServerEditorComponent, NhAssistantAdminMcpServersComponent, NhAssistantAdminMcpToolsComponent, nhAssistantSelectorMatches };
|
|
785
|
+
//# sourceMappingURL=newheap-platform-ai-chat-admin.mjs.map
|