@newheap/platform-ai-chat 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/admin/index.d.ts +8 -2
- package/agent-runs/index.d.ts +46 -4
- package/fesm2022/newheap-platform-ai-chat-admin.mjs +23 -3
- package/fesm2022/newheap-platform-ai-chat-admin.mjs.map +1 -1
- package/fesm2022/newheap-platform-ai-chat-agent-runs.mjs +148 -24
- package/fesm2022/newheap-platform-ai-chat-agent-runs.mjs.map +1 -1
- package/fesm2022/newheap-platform-ai-chat.mjs +109 -18
- package/fesm2022/newheap-platform-ai-chat.mjs.map +1 -1
- package/i18n/en.json +32 -6
- package/i18n/nl.json +32 -6
- package/index.d.ts +122 -8
- package/package.json +1 -1
package/admin/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as _angular_core from '@angular/core';
|
|
2
|
-
import { NhAssistantStore, NhAssistantError, ApplicationContext, ApplicationContextVersion, AdminAgent, ToolCatalogEntry, McpServer, AssistantAutonomy, AgentProposalFeedbackCategory, AgentFeedbackSummaryRow, AgentFeedbackItem, AgentFeedbackCategoryCount, AgentProposalFeedbackGroup, McpServerTestResult, McpAuthMode, McpToolEffect, McpTool } from '@newheap/platform-ai-chat';
|
|
2
|
+
import { NhAssistantStore, NhAssistantError, ApplicationContext, ApplicationContextVersion, AdminAgent, ToolCatalogEntry, McpServer, AssistantAutonomy, AgentProposalFeedbackCategory, AgentFeedbackSummaryRow, AgentFeedbackItem, AgentFeedbackCategoryCount, StaleKind, AgentProposalFeedbackGroup, McpServerTestResult, McpAuthMode, McpToolEffect, McpTool } from '@newheap/platform-ai-chat';
|
|
3
3
|
|
|
4
4
|
type NhAssistantAdminTab = 'context' | 'agents' | 'mcp-servers';
|
|
5
5
|
/**
|
|
@@ -170,9 +170,15 @@ declare class NhAssistantAdminAgentEditorComponent {
|
|
|
170
170
|
|
|
171
171
|
/** Periods the report offers, in days before now. */
|
|
172
172
|
type NhAssistantFeedbackPeriod = 7 | 30 | 90;
|
|
173
|
-
/** A summary row with its most frequent feedback categories. */
|
|
173
|
+
/** A summary row with its most frequent feedback categories and why open proposals closed. */
|
|
174
174
|
interface NhAssistantFeedbackSummaryView extends AgentFeedbackSummaryRow {
|
|
175
175
|
topCategories: AgentFeedbackCategoryCount[];
|
|
176
|
+
/** Open proposals closed before a decision, per kind, most frequent first. */
|
|
177
|
+
invalidatedKinds: {
|
|
178
|
+
kind: StaleKind;
|
|
179
|
+
count: number;
|
|
180
|
+
reasons: string;
|
|
181
|
+
}[];
|
|
176
182
|
}
|
|
177
183
|
/**
|
|
178
184
|
* What people said when they rejected an agent's proposals, for the agent's administrators: per
|
package/agent-runs/index.d.ts
CHANGED
|
@@ -8,7 +8,9 @@ import { AgentProposal, AgentThreadLookup, AgentTriggerReceipt, NhAssistantError
|
|
|
8
8
|
* server checks each decision against the proposal's approval policy again. After a rejection the
|
|
9
9
|
* inbox asks, optionally, what was wrong, also when the rejected proposals have left the list.
|
|
10
10
|
* With live updates the inbox reloads by itself when agent work changes, for example when a
|
|
11
|
-
* colleague decided a proposal or a run proposed a new one.
|
|
11
|
+
* colleague decided a proposal or a run proposed a new one. A proposal that closes because it no
|
|
12
|
+
* longer fits, for example because someone did the work by hand, stays as a closed card with the
|
|
13
|
+
* reason until it is dismissed.
|
|
12
14
|
*/
|
|
13
15
|
declare class NhAssistantAgentInboxComponent {
|
|
14
16
|
private readonly api;
|
|
@@ -114,6 +116,11 @@ interface NhAssistantFeedbackRequest {
|
|
|
114
116
|
* optionally and above the list, what was wrong; closed proposals keep a low-key action to give or
|
|
115
117
|
* change that feedback later. With `decisionRights` each proposal has its own right, for example
|
|
116
118
|
* from the agent work on a subject, and a pending proposal the viewer may not decide says who does.
|
|
119
|
+
* A proposal that no longer fits, for example because a person already did the work, shows why.
|
|
120
|
+
* With `holdClosedCards`, a list of open proposals keeps such a proposal, or one a newer proposal
|
|
121
|
+
* replaced, as a closed card when it closes while shown, or when an approval is refused because of
|
|
122
|
+
* it, until the person dismisses it. It reads those proposals by id with one lookup, never through
|
|
123
|
+
* their threads.
|
|
117
124
|
*/
|
|
118
125
|
declare class NhAssistantAgentProposalsComponent {
|
|
119
126
|
private readonly api;
|
|
@@ -129,6 +136,12 @@ declare class NhAssistantAgentProposalsComponent {
|
|
|
129
136
|
*/
|
|
130
137
|
readonly decisionRights: _angular_core.InputSignal<ReadonlyMap<string, AgentProposalDecisionRight> | null>;
|
|
131
138
|
readonly selectable: _angular_core.InputSignal<boolean>;
|
|
139
|
+
/**
|
|
140
|
+
* Keeps proposals that close as stale or are replaced while shown as closed cards with their
|
|
141
|
+
* explanation until they are dismissed. For lists of open proposals, which drop a proposal once
|
|
142
|
+
* it closes.
|
|
143
|
+
*/
|
|
144
|
+
readonly holdClosedCards: _angular_core.InputSignal<boolean>;
|
|
132
145
|
/** Emits after a proposal or its feedback changed, so the host reloads its list. */
|
|
133
146
|
readonly changed: _angular_core.OutputEmitterRef<void>;
|
|
134
147
|
readonly busy: _angular_core.WritableSignal<boolean>;
|
|
@@ -140,7 +153,30 @@ declare class NhAssistantAgentProposalsComponent {
|
|
|
140
153
|
readonly allSelected: _angular_core.Signal<boolean>;
|
|
141
154
|
/** The feedback question. It does not depend on `proposals`, which drop rejected proposals in the inbox. */
|
|
142
155
|
readonly feedback: _angular_core.WritableSignal<NhAssistantFeedbackRequest | null>;
|
|
156
|
+
/** Proposals that closed as stale while shown, kept until dismissed; newest first. */
|
|
157
|
+
readonly held: _angular_core.WritableSignal<AgentProposal[]>;
|
|
158
|
+
/** The held closed cards first, then the proposals the host gave. */
|
|
159
|
+
readonly shown: _angular_core.Signal<AgentProposal[]>;
|
|
160
|
+
private readonly lookups;
|
|
143
161
|
constructor();
|
|
162
|
+
/**
|
|
163
|
+
* The translation key of a proposal's status: the kind of a stale proposal, "replaced" for a
|
|
164
|
+
* superseded one, otherwise its status.
|
|
165
|
+
*/
|
|
166
|
+
statusKey(proposal: AgentProposal): string;
|
|
167
|
+
/** True when the newer proposal that replaced this one is in the list. */
|
|
168
|
+
hasSuccessor(proposal: AgentProposal): boolean;
|
|
169
|
+
/** Moves focus to the newer proposal that replaced this one. */
|
|
170
|
+
showSuccessor(proposal: AgentProposal): void;
|
|
171
|
+
/** True for a closed card the list keeps until it is dismissed. */
|
|
172
|
+
isHeld(proposal: AgentProposal): boolean;
|
|
173
|
+
/** Removes a closed card. */
|
|
174
|
+
dismiss(proposal: AgentProposal): void;
|
|
175
|
+
/**
|
|
176
|
+
* Called by a host of open proposals after a live update replaced `previous` with `next`: keeps
|
|
177
|
+
* the shown pending proposals that left the list because they closed as stale or were replaced.
|
|
178
|
+
*/
|
|
179
|
+
holdClosed(previous: readonly AgentProposal[], next: readonly AgentProposal[]): void;
|
|
144
180
|
/** Whether the caller may decide this proposal: its own right when one was given, otherwise `canDecide`. */
|
|
145
181
|
mayDecide(proposal: AgentProposal): boolean;
|
|
146
182
|
/**
|
|
@@ -164,9 +200,14 @@ declare class NhAssistantAgentProposalsComponent {
|
|
|
164
200
|
/** Closes the question; from a card, focus returns to the card's feedback action. */
|
|
165
201
|
onFeedbackDismissed(): void;
|
|
166
202
|
private ask;
|
|
203
|
+
/**
|
|
204
|
+
* Reads `proposals` again by id and keeps those that closed as stale or were replaced as closed
|
|
205
|
+
* cards, with their kind and explanation or their successor.
|
|
206
|
+
*/
|
|
207
|
+
private holdStale;
|
|
167
208
|
private run;
|
|
168
209
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<NhAssistantAgentProposalsComponent, never>;
|
|
169
|
-
static ɵcmp: _angular_core.ɵɵComponentDeclaration<NhAssistantAgentProposalsComponent, "nh-assistant-agent-proposals", never, { "proposals": { "alias": "proposals"; "required": true; "isSignal": true; }; "canDecide": { "alias": "canDecide"; "required": false; "isSignal": true; }; "decisionRights": { "alias": "decisionRights"; "required": false; "isSignal": true; }; "selectable": { "alias": "selectable"; "required": false; "isSignal": true; }; }, { "changed": "changed"; }, never, never, true, never>;
|
|
210
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<NhAssistantAgentProposalsComponent, "nh-assistant-agent-proposals", never, { "proposals": { "alias": "proposals"; "required": true; "isSignal": true; }; "canDecide": { "alias": "canDecide"; "required": false; "isSignal": true; }; "decisionRights": { "alias": "decisionRights"; "required": false; "isSignal": true; }; "selectable": { "alias": "selectable"; "required": false; "isSignal": true; }; "holdClosedCards": { "alias": "holdClosedCards"; "required": false; "isSignal": true; }; }, { "changed": "changed"; }, never, never, true, never>;
|
|
170
211
|
}
|
|
171
212
|
|
|
172
213
|
/**
|
|
@@ -282,8 +323,9 @@ interface NhAssistantStartedAgent {
|
|
|
282
323
|
* decides when the viewer may not and the optional question after a rejection, and per agent the
|
|
283
324
|
* conversation about the subject and, for participants, a start action. `agentId` names the agent
|
|
284
325
|
* whose conversation the host offers also before it worked on the subject. With live updates the
|
|
285
|
-
* view reloads by itself when agent work on its subject changes
|
|
286
|
-
*
|
|
326
|
+
* view reloads by itself when agent work on its subject changes; a proposal that closes because it
|
|
327
|
+
* no longer fits stays as a closed card with the reason until it is dismissed. Opening a
|
|
328
|
+
* conversation uses `NhAssistantPanelService.openSubject` and emits `conversationOpened` afterwards.
|
|
287
329
|
*/
|
|
288
330
|
declare class NhAssistantSubjectSuggestionsComponent {
|
|
289
331
|
private readonly api;
|
|
@@ -295,7 +295,8 @@ class NhAssistantAgentFeedbackReportComponent {
|
|
|
295
295
|
.filter(row => !this.toolId() || row.toolId === this.toolId())
|
|
296
296
|
.map(row => ({
|
|
297
297
|
...row,
|
|
298
|
-
topCategories: [...row.categories].sort((left, right) => right.count - left.count).slice(0, topCategoryCount)
|
|
298
|
+
topCategories: [...row.categories].sort((left, right) => right.count - left.count).slice(0, topCategoryCount),
|
|
299
|
+
invalidatedKinds: invalidatedKinds(row)
|
|
299
300
|
})), ...(ngDevMode ? [{ debugName: "visibleRows" }] : []));
|
|
300
301
|
this.filtered = computed(() => this.toolId() !== '' || this.category() !== '', ...(ngDevMode ? [{ debugName: "filtered" }] : []));
|
|
301
302
|
/** The period the last load asked for, so the list, summary and export agree. */
|
|
@@ -421,16 +422,35 @@ class NhAssistantAgentFeedbackReportComponent {
|
|
|
421
422
|
}
|
|
422
423
|
}
|
|
423
424
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantAgentFeedbackReportComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
424
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.33", type: NhAssistantAgentFeedbackReportComponent, isStandalone: true, selector: "nh-assistant-agent-feedback-report", inputs: { agentId: { classPropertyName: "agentId", publicName: "agentId", isSignal: true, isRequired: true, transformFunction: null }, itemsPerPage: { classPropertyName: "itemsPerPage", publicName: "itemsPerPage", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<section class=\"section report\" [attr.aria-labelledby]=\"id + '-title'\">\n <header class=\"section-header\">\n <div>\n <h3 [id]=\"id + '-title'\">{{ 'nh-assistant.admin.feedback.title' | translate }}</h3>\n <p class=\"muted\">{{ 'nh-assistant.admin.feedback.description' | translate }}</p>\n <p class=\"muted export-hint\" [id]=\"id + '-export-hint'\">{{ 'nh-assistant.admin.feedback.export-hint' | translate }}</p>\n </div>\n <div class=\"header-actions\">\n <button class=\"secondary\" type=\"button\" [disabled]=\"summaryLoading() || listLoading()\" (click)=\"load()\">\n <nh-assistant-icon name=\"refresh\" />{{ 'nh-assistant.admin.feedback.refresh' | translate }}\n </button>\n <button\n class=\"secondary\"\n type=\"button\"\n [disabled]=\"!canExport()\"\n [attr.aria-describedby]=\"id + '-export-hint'\"\n (click)=\"export()\">\n <nh-assistant-icon name=\"page\" />{{ (exporting() ? 'nh-assistant.admin.feedback.exporting' : 'nh-assistant.admin.feedback.export') | translate }}\n </button>\n </div>\n </header>\n\n <div class=\"filters\">\n <div class=\"field\">\n <label [for]=\"id + '-period'\">{{ 'nh-assistant.admin.feedback.period' | translate }}</label>\n <select [id]=\"id + '-period'\" (change)=\"onPeriod($event)\">\n @for (days of periods; track days) {\n <option [value]=\"days\" [selected]=\"period() === days\">{{ 'nh-assistant.admin.feedback.periods.last-' + days + '-days' | translate }}</option>\n }\n </select>\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-tool'\">{{ 'nh-assistant.admin.feedback.tool' | translate }}</label>\n <select [id]=\"id + '-tool'\" [disabled]=\"summaryLoading() && tools().length === 0\" (change)=\"onTool($event)\">\n <option value=\"\" [selected]=\"toolId() === ''\">{{ 'nh-assistant.admin.feedback.all-tools' | translate }}</option>\n @for (tool of tools(); track tool) {\n <option [value]=\"tool\" [selected]=\"toolId() === tool\">{{ tool }}</option>\n }\n </select>\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-category'\">{{ 'nh-assistant.admin.feedback.category' | translate }}</label>\n <select [id]=\"id + '-category'\" [attr.aria-describedby]=\"id + '-category-hint'\" (change)=\"onCategory($event)\">\n <option value=\"\" [selected]=\"category() === ''\">{{ 'nh-assistant.admin.feedback.all-categories' | translate }}</option>\n @for (option of categories; track option) {\n <option [value]=\"option\" [selected]=\"category() === option\">{{ 'nh-assistant.proposal-feedback.categories.' + option | translate }}</option>\n }\n </select>\n <p class=\"hint\" [id]=\"id + '-category-hint'\"><span>{{ 'nh-assistant.admin.feedback.category-hint' | translate }}</span></p>\n </div>\n </div>\n\n @if (exportError(); as failure) {\n <p class=\"message error\" role=\"alert\"><nh-assistant-icon name=\"warning\" />{{ exportErrorKeys() | nhAssistantTranslate: failure.code }}</p>\n }\n\n <section class=\"subsection\" [attr.aria-labelledby]=\"id + '-summary-title'\" [attr.aria-busy]=\"summaryLoading()\">\n <h4 [id]=\"id + '-summary-title'\">{{ 'nh-assistant.admin.feedback.summary-title' | translate }}</h4>\n @if (summaryLoading() && rows().length === 0) {\n <p class=\"state\" role=\"status\">{{ 'nh-assistant.admin.loading' | translate }}</p>\n } @else if (summaryError(); as failure) {\n <div class=\"state\" role=\"alert\">\n <p>{{ summaryErrorKeys() | nhAssistantTranslate: failure.code }}</p>\n <button class=\"secondary\" type=\"button\" (click)=\"loadSummary()\">{{ 'nh-assistant.admin.retry' | translate }}</button>\n </div>\n } @else if (visibleRows().length === 0) {\n <p class=\"state\">{{ 'nh-assistant.admin.feedback.summary-empty' | translate }}</p>\n } @else {\n <div class=\"table-container\">\n <table class=\"summary-table\" [attr.aria-labelledby]=\"id + '-summary-title'\">\n <thead>\n <tr>\n <th scope=\"col\">{{ 'nh-assistant.admin.feedback.columns.tool' | translate }}</th>\n <th scope=\"col\">{{ 'nh-assistant.admin.feedback.columns.version' | translate }}</th>\n <th scope=\"col\" class=\"number\">{{ 'nh-assistant.admin.feedback.columns.approved' | translate }}</th>\n <th scope=\"col\" class=\"number\">{{ 'nh-assistant.admin.feedback.columns.rejected' | translate }}</th>\n <th scope=\"col\" class=\"number\">{{ 'nh-assistant.admin.feedback.columns.expired' | translate }}</th>\n <th scope=\"col\" class=\"number\">{{ 'nh-assistant.admin.feedback.columns.rejection-rate' | translate }}</th>\n <th scope=\"col\" class=\"number\">{{ 'nh-assistant.admin.feedback.columns.with-feedback' | translate }}</th>\n <th scope=\"col\" class=\"number\">{{ 'nh-assistant.admin.feedback.columns.people' | translate }}</th>\n <th scope=\"col\">{{ 'nh-assistant.admin.feedback.columns.categories' | translate }}</th>\n </tr>\n </thead>\n <tbody>\n @for (row of visibleRows(); track row.toolId + '|' + row.agentVersion) {\n <tr>\n <th scope=\"row\" class=\"tool\" [attr.data-label]=\"'nh-assistant.admin.feedback.columns.tool' | translate\">\n <span class=\"mono\">{{ row.toolId }}</span>\n </th>\n <td [attr.data-label]=\"'nh-assistant.admin.feedback.columns.version' | translate\">\n @if (row.agentVersion === null) {\n {{ 'nh-assistant.admin.feedback.consultations' | translate }}\n } @else {\n {{ 'nh-assistant.admin.feedback.version' | translate: { version: row.agentVersion } }}\n }\n </td>\n <td class=\"number\" [attr.data-label]=\"'nh-assistant.admin.feedback.columns.approved' | translate\">{{ row.approved }}</td>\n <td class=\"number\" [attr.data-label]=\"'nh-assistant.admin.feedback.columns.rejected' | translate\">{{ row.rejected }}</td>\n <td class=\"number\" [attr.data-label]=\"'nh-assistant.admin.feedback.columns.expired' | translate\">{{ row.expired }}</td>\n <td class=\"number\" [attr.data-label]=\"'nh-assistant.admin.feedback.columns.rejection-rate' | translate\">\n @if (row.rejectionRate === null) {\n <span class=\"muted\">{{ 'nh-assistant.admin.feedback.no-decisions' | translate }}</span>\n } @else {\n {{ row.rejectionRate | percent: '1.0-0' }}\n }\n </td>\n <td class=\"number\" [attr.data-label]=\"'nh-assistant.admin.feedback.columns.with-feedback' | translate\">{{ row.rejectedWithFeedback }}</td>\n <td class=\"number\" [attr.data-label]=\"'nh-assistant.admin.feedback.columns.people' | translate\">{{ row.feedbackAuthors }}</td>\n <td [attr.data-label]=\"'nh-assistant.admin.feedback.columns.categories' | translate\">\n @if (row.topCategories.length === 0) {\n <span class=\"muted\">{{ 'nh-assistant.admin.feedback.no-categories' | translate }}</span>\n } @else {\n <ul class=\"badges\">\n @for (item of row.topCategories; track item.category) {\n <li>\n <span [class]=\"'badge ' + groupClass(item.group)\" [attr.title]=\"'nh-assistant.proposal-feedback.groups.' + item.group | translate\">\n {{ 'nh-assistant.proposal-feedback.categories.' + item.category | nhAssistantTranslate: item.category }}\n <span class=\"count\">{{ item.count }}</span>\n </span>\n </li>\n }\n </ul>\n }\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n </section>\n\n <section class=\"subsection\" [attr.aria-labelledby]=\"id + '-list-title'\" [attr.aria-busy]=\"listLoading()\">\n <div class=\"list-header\">\n <h4 [id]=\"id + '-list-title'\">{{ 'nh-assistant.admin.feedback.list-title' | translate }}</h4>\n @if (!listLoading() && listError() === null) {\n <span class=\"muted\">{{ 'nh-assistant.admin.feedback.list-count' | translate: { count: total() } }}</span>\n }\n </div>\n @if (listLoading() && items().length === 0) {\n <p class=\"state\" role=\"status\">{{ 'nh-assistant.admin.loading' | translate }}</p>\n } @else if (listError(); as failure) {\n <div class=\"state\" role=\"alert\">\n <p>{{ listErrorKeys() | nhAssistantTranslate: failure.code }}</p>\n <button class=\"secondary\" type=\"button\" (click)=\"loadList()\">{{ 'nh-assistant.admin.retry' | translate }}</button>\n </div>\n } @else if (items().length === 0) {\n <p class=\"state\">{{ (filtered() ? 'nh-assistant.admin.feedback.list-empty-filtered' : 'nh-assistant.admin.feedback.list-empty') | translate }}</p>\n } @else {\n <ul class=\"cards\">\n @for (item of items(); track item.id) {\n <li class=\"card\">\n <div class=\"card-header\">\n <strong>{{ item.subjectDisplayName ?? item.subjectId }}</strong>\n <span class=\"muted\">{{ item.presentation?.toolDisplayName ?? item.toolId }}</span>\n @if (item.agentVersion !== null) {\n <span class=\"badge\">{{ 'nh-assistant.admin.feedback.version' | translate: { version: item.agentVersion } }}</span>\n }\n <span [class]=\"'badge ' + groupClass(item.group)\" [attr.title]=\"'nh-assistant.proposal-feedback.groups.' + item.group | translate\">\n {{ 'nh-assistant.proposal-feedback.categories.' + item.category | nhAssistantTranslate: item.category }}\n </span>\n @if (item.bulk) {\n <span class=\"badge\" [attr.title]=\"'nh-assistant.admin.feedback.bulk-hint' | translate\">{{ 'nh-assistant.admin.feedback.bulk' | translate }}</span>\n }\n </div>\n @if (item.text) {\n <blockquote class=\"feedback-text\">{{ item.text }}</blockquote>\n } @else {\n <p class=\"muted\">{{ 'nh-assistant.admin.feedback.no-text' | translate }}</p>\n }\n <p class=\"proposal\">\n <span class=\"proposal-label\">{{ 'nh-assistant.admin.feedback.proposal' | translate }}</span>\n {{ item.presentation?.summary ?? item.summary }}\n </p>\n <div class=\"meta\">\n <span>{{ 'nh-assistant.agent-runs.proposal.status.' + item.approvalStatus | nhAssistantTranslate: item.approvalStatus }}</span>\n <span>{{ 'nh-assistant.admin.feedback.author' | translate: { actor: item.authorDisplayName || item.authorActorId } }}</span>\n <span>{{ item.updatedAt | date: 'short' }}</span>\n </div>\n </li>\n }\n </ul>\n @if (pages() > 1) {\n <nav class=\"pager\" [attr.aria-label]=\"'nh-assistant.admin.feedback.pages' | translate\">\n <button class=\"secondary\" type=\"button\" [disabled]=\"page() <= 1 || listLoading()\" (click)=\"goTo(page() - 1)\">\n {{ 'nh-assistant.admin.feedback.previous' | translate }}\n </button>\n <span class=\"muted\">{{ 'nh-assistant.admin.feedback.page' | translate: { page: page(), pages: pages() } }}</span>\n <button class=\"secondary\" type=\"button\" [disabled]=\"page() >= pages() || listLoading()\" (click)=\"goTo(page() + 1)\">\n {{ 'nh-assistant.admin.feedback.next' | translate }}\n </button>\n </nav>\n }\n }\n </section>\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", ".header-actions{display:flex;flex-wrap:wrap;gap:8px}.export-hint{margin-top:4px;font-size:12px}.filters{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));align-items:start;gap:12px}.subsection h4,.list-header h4{margin:0}.list-header{display:flex;flex-wrap:wrap;align-items:baseline;justify-content:space-between;gap:4px 12px}.table-container{container-type:inline-size;min-width:0}.summary-table{width:100%;border-collapse:collapse;font-size:13px}.summary-table th,.summary-table td{border-bottom:1px solid var(--nha-border);padding:8px 10px;text-align:left;vertical-align:top}.summary-table thead th{color:var(--nha-text-muted);font-size:11px;font-weight:700;letter-spacing:.04em;text-transform:uppercase}.summary-table tbody th{font-weight:600}.summary-table .number{text-align:right;font-variant-numeric:tabular-nums}.summary-table .mono{overflow-wrap:anywhere}@container (max-width: 820px){.summary-table thead{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}.summary-table,.summary-table tbody,.summary-table tr{display:block}.summary-table tr{margin-bottom:8px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px 12px;background:var(--nha-background)}.summary-table tbody th,.summary-table td{display:grid;grid-template-columns:minmax(110px,40%) minmax(0,1fr);gap:8px;border:0;padding:4px 0;text-align:left}.summary-table tbody th:before,.summary-table td:before{content:attr(data-label);color:var(--nha-text-muted);font-size:12px;font-weight:700}}.badges{display:flex;flex-wrap:wrap;gap:4px;margin:0;padding:0;list-style:none}.badge .count{font-variant-numeric:tabular-nums;opacity:.85}.feedback-text{margin:0;border-left:3px solid var(--nha-border);padding:2px 0 2px 10px;font-size:13px;white-space:pre-wrap;overflow-wrap:anywhere}.proposal{margin:0;color:var(--nha-text-muted);font-size:12px;overflow-wrap:anywhere}.proposal-label{margin-right:4px;color:var(--nha-text);font-weight:700}.pager{display:flex;flex-wrap:wrap;align-items:center;gap:8px}\n"], dependencies: [{ kind: "component", type: _NhAssistantIconComponent, selector: "nh-assistant-icon", inputs: ["name"] }, { kind: "pipe", type: DatePipe, name: "date" }, { kind: "pipe", type: PercentPipe, name: "percent" }, { kind: "pipe", type: TranslatePipe, name: "translate" }, { kind: "pipe", type: _NhAssistantTranslatePipe, name: "nhAssistantTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
425
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.33", type: NhAssistantAgentFeedbackReportComponent, isStandalone: true, selector: "nh-assistant-agent-feedback-report", inputs: { agentId: { classPropertyName: "agentId", publicName: "agentId", isSignal: true, isRequired: true, transformFunction: null }, itemsPerPage: { classPropertyName: "itemsPerPage", publicName: "itemsPerPage", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<section class=\"section report\" [attr.aria-labelledby]=\"id + '-title'\">\n <header class=\"section-header\">\n <div>\n <h3 [id]=\"id + '-title'\">{{ 'nh-assistant.admin.feedback.title' | translate }}</h3>\n <p class=\"muted\">{{ 'nh-assistant.admin.feedback.description' | translate }}</p>\n <p class=\"muted export-hint\" [id]=\"id + '-export-hint'\">{{ 'nh-assistant.admin.feedback.export-hint' | translate }}</p>\n </div>\n <div class=\"header-actions\">\n <button class=\"secondary\" type=\"button\" [disabled]=\"summaryLoading() || listLoading()\" (click)=\"load()\">\n <nh-assistant-icon name=\"refresh\" />{{ 'nh-assistant.admin.feedback.refresh' | translate }}\n </button>\n <button\n class=\"secondary\"\n type=\"button\"\n [disabled]=\"!canExport()\"\n [attr.aria-describedby]=\"id + '-export-hint'\"\n (click)=\"export()\">\n <nh-assistant-icon name=\"page\" />{{ (exporting() ? 'nh-assistant.admin.feedback.exporting' : 'nh-assistant.admin.feedback.export') | translate }}\n </button>\n </div>\n </header>\n\n <div class=\"filters\">\n <div class=\"field\">\n <label [for]=\"id + '-period'\">{{ 'nh-assistant.admin.feedback.period' | translate }}</label>\n <select [id]=\"id + '-period'\" (change)=\"onPeriod($event)\">\n @for (days of periods; track days) {\n <option [value]=\"days\" [selected]=\"period() === days\">{{ 'nh-assistant.admin.feedback.periods.last-' + days + '-days' | translate }}</option>\n }\n </select>\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-tool'\">{{ 'nh-assistant.admin.feedback.tool' | translate }}</label>\n <select [id]=\"id + '-tool'\" [disabled]=\"summaryLoading() && tools().length === 0\" (change)=\"onTool($event)\">\n <option value=\"\" [selected]=\"toolId() === ''\">{{ 'nh-assistant.admin.feedback.all-tools' | translate }}</option>\n @for (tool of tools(); track tool) {\n <option [value]=\"tool\" [selected]=\"toolId() === tool\">{{ tool }}</option>\n }\n </select>\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-category'\">{{ 'nh-assistant.admin.feedback.category' | translate }}</label>\n <select [id]=\"id + '-category'\" [attr.aria-describedby]=\"id + '-category-hint'\" (change)=\"onCategory($event)\">\n <option value=\"\" [selected]=\"category() === ''\">{{ 'nh-assistant.admin.feedback.all-categories' | translate }}</option>\n @for (option of categories; track option) {\n <option [value]=\"option\" [selected]=\"category() === option\">{{ 'nh-assistant.proposal-feedback.categories.' + option | translate }}</option>\n }\n </select>\n <p class=\"hint\" [id]=\"id + '-category-hint'\"><span>{{ 'nh-assistant.admin.feedback.category-hint' | translate }}</span></p>\n </div>\n </div>\n\n @if (exportError(); as failure) {\n <p class=\"message error\" role=\"alert\"><nh-assistant-icon name=\"warning\" />{{ exportErrorKeys() | nhAssistantTranslate: failure.code }}</p>\n }\n\n <section class=\"subsection\" [attr.aria-labelledby]=\"id + '-summary-title'\" [attr.aria-busy]=\"summaryLoading()\">\n <h4 [id]=\"id + '-summary-title'\">{{ 'nh-assistant.admin.feedback.summary-title' | translate }}</h4>\n @if (summaryLoading() && rows().length === 0) {\n <p class=\"state\" role=\"status\">{{ 'nh-assistant.admin.loading' | translate }}</p>\n } @else if (summaryError(); as failure) {\n <div class=\"state\" role=\"alert\">\n <p>{{ summaryErrorKeys() | nhAssistantTranslate: failure.code }}</p>\n <button class=\"secondary\" type=\"button\" (click)=\"loadSummary()\">{{ 'nh-assistant.admin.retry' | translate }}</button>\n </div>\n } @else if (visibleRows().length === 0) {\n <p class=\"state\">{{ 'nh-assistant.admin.feedback.summary-empty' | translate }}</p>\n } @else {\n <div class=\"table-container\">\n <table class=\"summary-table\" [attr.aria-labelledby]=\"id + '-summary-title'\">\n <thead>\n <tr>\n <th scope=\"col\">{{ 'nh-assistant.admin.feedback.columns.tool' | translate }}</th>\n <th scope=\"col\">{{ 'nh-assistant.admin.feedback.columns.version' | translate }}</th>\n <th scope=\"col\" class=\"number\">{{ 'nh-assistant.admin.feedback.columns.approved' | translate }}</th>\n <th scope=\"col\" class=\"number\">{{ 'nh-assistant.admin.feedback.columns.rejected' | translate }}</th>\n <th scope=\"col\" class=\"number\">{{ 'nh-assistant.admin.feedback.columns.expired' | translate }}</th>\n <th scope=\"col\" class=\"number\" [attr.title]=\"'nh-assistant.admin.feedback.invalidated-hint' | translate\">\n {{ 'nh-assistant.admin.feedback.columns.invalidated' | translate }}\n </th>\n <th scope=\"col\" class=\"number\">{{ 'nh-assistant.admin.feedback.columns.rejection-rate' | translate }}</th>\n <th scope=\"col\" class=\"number\">{{ 'nh-assistant.admin.feedback.columns.with-feedback' | translate }}</th>\n <th scope=\"col\" class=\"number\">{{ 'nh-assistant.admin.feedback.columns.people' | translate }}</th>\n <th scope=\"col\">{{ 'nh-assistant.admin.feedback.columns.categories' | translate }}</th>\n </tr>\n </thead>\n <tbody>\n @for (row of visibleRows(); track row.toolId + '|' + row.agentVersion) {\n <tr>\n <th scope=\"row\" class=\"tool\" [attr.data-label]=\"'nh-assistant.admin.feedback.columns.tool' | translate\">\n <span class=\"mono\">{{ row.toolId }}</span>\n </th>\n <td [attr.data-label]=\"'nh-assistant.admin.feedback.columns.version' | translate\">\n @if (row.agentVersion === null) {\n {{ 'nh-assistant.admin.feedback.consultations' | translate }}\n } @else {\n {{ 'nh-assistant.admin.feedback.version' | translate: { version: row.agentVersion } }}\n }\n </td>\n <td class=\"number\" [attr.data-label]=\"'nh-assistant.admin.feedback.columns.approved' | translate\">{{ row.approved }}</td>\n <td class=\"number\" [attr.data-label]=\"'nh-assistant.admin.feedback.columns.rejected' | translate\">{{ row.rejected }}</td>\n <td class=\"number\" [attr.data-label]=\"'nh-assistant.admin.feedback.columns.expired' | translate\">{{ row.expired }}</td>\n <td class=\"number\" [attr.data-label]=\"'nh-assistant.admin.feedback.columns.invalidated' | translate\">\n {{ row.invalidated ?? 0 }}\n @if (row.invalidatedKinds.length > 0) {\n <ul class=\"badges\">\n @for (item of row.invalidatedKinds; track item.kind) {\n <li>\n <span class=\"badge\" [attr.title]=\"item.reasons\">\n {{ 'nh-assistant.approval.stale-kind.' + item.kind | translate }}\n <span class=\"count\">{{ item.count }}</span>\n </span>\n </li>\n }\n </ul>\n }\n </td>\n <td class=\"number\" [attr.data-label]=\"'nh-assistant.admin.feedback.columns.rejection-rate' | translate\">\n @if (row.rejectionRate === null) {\n <span class=\"muted\">{{ 'nh-assistant.admin.feedback.no-decisions' | translate }}</span>\n } @else {\n {{ row.rejectionRate | percent: '1.0-0' }}\n }\n </td>\n <td class=\"number\" [attr.data-label]=\"'nh-assistant.admin.feedback.columns.with-feedback' | translate\">{{ row.rejectedWithFeedback }}</td>\n <td class=\"number\" [attr.data-label]=\"'nh-assistant.admin.feedback.columns.people' | translate\">{{ row.feedbackAuthors }}</td>\n <td [attr.data-label]=\"'nh-assistant.admin.feedback.columns.categories' | translate\">\n @if (row.topCategories.length === 0) {\n <span class=\"muted\">{{ 'nh-assistant.admin.feedback.no-categories' | translate }}</span>\n } @else {\n <ul class=\"badges\">\n @for (item of row.topCategories; track item.category) {\n <li>\n <span [class]=\"'badge ' + groupClass(item.group)\" [attr.title]=\"'nh-assistant.proposal-feedback.groups.' + item.group | translate\">\n {{ 'nh-assistant.proposal-feedback.categories.' + item.category | nhAssistantTranslate: item.category }}\n <span class=\"count\">{{ item.count }}</span>\n </span>\n </li>\n }\n </ul>\n }\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n </section>\n\n <section class=\"subsection\" [attr.aria-labelledby]=\"id + '-list-title'\" [attr.aria-busy]=\"listLoading()\">\n <div class=\"list-header\">\n <h4 [id]=\"id + '-list-title'\">{{ 'nh-assistant.admin.feedback.list-title' | translate }}</h4>\n @if (!listLoading() && listError() === null) {\n <span class=\"muted\">{{ 'nh-assistant.admin.feedback.list-count' | translate: { count: total() } }}</span>\n }\n </div>\n @if (listLoading() && items().length === 0) {\n <p class=\"state\" role=\"status\">{{ 'nh-assistant.admin.loading' | translate }}</p>\n } @else if (listError(); as failure) {\n <div class=\"state\" role=\"alert\">\n <p>{{ listErrorKeys() | nhAssistantTranslate: failure.code }}</p>\n <button class=\"secondary\" type=\"button\" (click)=\"loadList()\">{{ 'nh-assistant.admin.retry' | translate }}</button>\n </div>\n } @else if (items().length === 0) {\n <p class=\"state\">{{ (filtered() ? 'nh-assistant.admin.feedback.list-empty-filtered' : 'nh-assistant.admin.feedback.list-empty') | translate }}</p>\n } @else {\n <ul class=\"cards\">\n @for (item of items(); track item.id) {\n <li class=\"card\">\n <div class=\"card-header\">\n <strong>{{ item.subjectDisplayName ?? item.subjectId }}</strong>\n <span class=\"muted\">{{ item.presentation?.toolDisplayName ?? item.toolId }}</span>\n @if (item.agentVersion !== null) {\n <span class=\"badge\">{{ 'nh-assistant.admin.feedback.version' | translate: { version: item.agentVersion } }}</span>\n }\n <span [class]=\"'badge ' + groupClass(item.group)\" [attr.title]=\"'nh-assistant.proposal-feedback.groups.' + item.group | translate\">\n {{ 'nh-assistant.proposal-feedback.categories.' + item.category | nhAssistantTranslate: item.category }}\n </span>\n @if (item.bulk) {\n <span class=\"badge\" [attr.title]=\"'nh-assistant.admin.feedback.bulk-hint' | translate\">{{ 'nh-assistant.admin.feedback.bulk' | translate }}</span>\n }\n </div>\n @if (item.text) {\n <blockquote class=\"feedback-text\">{{ item.text }}</blockquote>\n } @else {\n <p class=\"muted\">{{ 'nh-assistant.admin.feedback.no-text' | translate }}</p>\n }\n <p class=\"proposal\">\n <span class=\"proposal-label\">{{ 'nh-assistant.admin.feedback.proposal' | translate }}</span>\n {{ item.presentation?.summary ?? item.summary }}\n </p>\n <div class=\"meta\">\n <span>{{ 'nh-assistant.agent-runs.proposal.status.' + item.approvalStatus | nhAssistantTranslate: item.approvalStatus }}</span>\n <span>{{ 'nh-assistant.admin.feedback.author' | translate: { actor: item.authorDisplayName || item.authorActorId } }}</span>\n <span>{{ item.updatedAt | date: 'short' }}</span>\n </div>\n </li>\n }\n </ul>\n @if (pages() > 1) {\n <nav class=\"pager\" [attr.aria-label]=\"'nh-assistant.admin.feedback.pages' | translate\">\n <button class=\"secondary\" type=\"button\" [disabled]=\"page() <= 1 || listLoading()\" (click)=\"goTo(page() - 1)\">\n {{ 'nh-assistant.admin.feedback.previous' | translate }}\n </button>\n <span class=\"muted\">{{ 'nh-assistant.admin.feedback.page' | translate: { page: page(), pages: pages() } }}</span>\n <button class=\"secondary\" type=\"button\" [disabled]=\"page() >= pages() || listLoading()\" (click)=\"goTo(page() + 1)\">\n {{ 'nh-assistant.admin.feedback.next' | translate }}\n </button>\n </nav>\n }\n }\n </section>\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", ".header-actions{display:flex;flex-wrap:wrap;gap:8px}.export-hint{margin-top:4px;font-size:12px}.filters{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));align-items:start;gap:12px}.subsection h4,.list-header h4{margin:0}.list-header{display:flex;flex-wrap:wrap;align-items:baseline;justify-content:space-between;gap:4px 12px}.table-container{container-type:inline-size;min-width:0}.summary-table{width:100%;border-collapse:collapse;font-size:13px}.summary-table th,.summary-table td{border-bottom:1px solid var(--nha-border);padding:8px 10px;text-align:left;vertical-align:top}.summary-table thead th{color:var(--nha-text-muted);font-size:11px;font-weight:700;letter-spacing:.04em;text-transform:uppercase}.summary-table tbody th{font-weight:600}.summary-table .number{text-align:right;font-variant-numeric:tabular-nums}.summary-table .mono{overflow-wrap:anywhere}@container (max-width: 820px){.summary-table thead{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}.summary-table,.summary-table tbody,.summary-table tr{display:block}.summary-table tr{margin-bottom:8px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px 12px;background:var(--nha-background)}.summary-table tbody th,.summary-table td{display:grid;grid-template-columns:minmax(110px,40%) minmax(0,1fr);gap:8px;border:0;padding:4px 0;text-align:left}.summary-table tbody th:before,.summary-table td:before{content:attr(data-label);color:var(--nha-text-muted);font-size:12px;font-weight:700}}.badges{display:flex;flex-wrap:wrap;gap:4px;margin:0;padding:0;list-style:none}.badge .count{font-variant-numeric:tabular-nums;opacity:.85}.feedback-text{margin:0;border-left:3px solid var(--nha-border);padding:2px 0 2px 10px;font-size:13px;white-space:pre-wrap;overflow-wrap:anywhere}.proposal{margin:0;color:var(--nha-text-muted);font-size:12px;overflow-wrap:anywhere}.proposal-label{margin-right:4px;color:var(--nha-text);font-weight:700}.pager{display:flex;flex-wrap:wrap;align-items:center;gap:8px}\n"], dependencies: [{ kind: "component", type: _NhAssistantIconComponent, selector: "nh-assistant-icon", inputs: ["name"] }, { kind: "pipe", type: DatePipe, name: "date" }, { kind: "pipe", type: PercentPipe, name: "percent" }, { kind: "pipe", type: TranslatePipe, name: "translate" }, { kind: "pipe", type: _NhAssistantTranslatePipe, name: "nhAssistantTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
425
426
|
}
|
|
426
427
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.33", ngImport: i0, type: NhAssistantAgentFeedbackReportComponent, decorators: [{
|
|
427
428
|
type: Component,
|
|
428
|
-
args: [{ selector: 'nh-assistant-agent-feedback-report', standalone: true, imports: [DatePipe, PercentPipe, TranslatePipe, _NhAssistantTranslatePipe, _NhAssistantIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<section class=\"section report\" [attr.aria-labelledby]=\"id + '-title'\">\n <header class=\"section-header\">\n <div>\n <h3 [id]=\"id + '-title'\">{{ 'nh-assistant.admin.feedback.title' | translate }}</h3>\n <p class=\"muted\">{{ 'nh-assistant.admin.feedback.description' | translate }}</p>\n <p class=\"muted export-hint\" [id]=\"id + '-export-hint'\">{{ 'nh-assistant.admin.feedback.export-hint' | translate }}</p>\n </div>\n <div class=\"header-actions\">\n <button class=\"secondary\" type=\"button\" [disabled]=\"summaryLoading() || listLoading()\" (click)=\"load()\">\n <nh-assistant-icon name=\"refresh\" />{{ 'nh-assistant.admin.feedback.refresh' | translate }}\n </button>\n <button\n class=\"secondary\"\n type=\"button\"\n [disabled]=\"!canExport()\"\n [attr.aria-describedby]=\"id + '-export-hint'\"\n (click)=\"export()\">\n <nh-assistant-icon name=\"page\" />{{ (exporting() ? 'nh-assistant.admin.feedback.exporting' : 'nh-assistant.admin.feedback.export') | translate }}\n </button>\n </div>\n </header>\n\n <div class=\"filters\">\n <div class=\"field\">\n <label [for]=\"id + '-period'\">{{ 'nh-assistant.admin.feedback.period' | translate }}</label>\n <select [id]=\"id + '-period'\" (change)=\"onPeriod($event)\">\n @for (days of periods; track days) {\n <option [value]=\"days\" [selected]=\"period() === days\">{{ 'nh-assistant.admin.feedback.periods.last-' + days + '-days' | translate }}</option>\n }\n </select>\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-tool'\">{{ 'nh-assistant.admin.feedback.tool' | translate }}</label>\n <select [id]=\"id + '-tool'\" [disabled]=\"summaryLoading() && tools().length === 0\" (change)=\"onTool($event)\">\n <option value=\"\" [selected]=\"toolId() === ''\">{{ 'nh-assistant.admin.feedback.all-tools' | translate }}</option>\n @for (tool of tools(); track tool) {\n <option [value]=\"tool\" [selected]=\"toolId() === tool\">{{ tool }}</option>\n }\n </select>\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-category'\">{{ 'nh-assistant.admin.feedback.category' | translate }}</label>\n <select [id]=\"id + '-category'\" [attr.aria-describedby]=\"id + '-category-hint'\" (change)=\"onCategory($event)\">\n <option value=\"\" [selected]=\"category() === ''\">{{ 'nh-assistant.admin.feedback.all-categories' | translate }}</option>\n @for (option of categories; track option) {\n <option [value]=\"option\" [selected]=\"category() === option\">{{ 'nh-assistant.proposal-feedback.categories.' + option | translate }}</option>\n }\n </select>\n <p class=\"hint\" [id]=\"id + '-category-hint'\"><span>{{ 'nh-assistant.admin.feedback.category-hint' | translate }}</span></p>\n </div>\n </div>\n\n @if (exportError(); as failure) {\n <p class=\"message error\" role=\"alert\"><nh-assistant-icon name=\"warning\" />{{ exportErrorKeys() | nhAssistantTranslate: failure.code }}</p>\n }\n\n <section class=\"subsection\" [attr.aria-labelledby]=\"id + '-summary-title'\" [attr.aria-busy]=\"summaryLoading()\">\n <h4 [id]=\"id + '-summary-title'\">{{ 'nh-assistant.admin.feedback.summary-title' | translate }}</h4>\n @if (summaryLoading() && rows().length === 0) {\n <p class=\"state\" role=\"status\">{{ 'nh-assistant.admin.loading' | translate }}</p>\n } @else if (summaryError(); as failure) {\n <div class=\"state\" role=\"alert\">\n <p>{{ summaryErrorKeys() | nhAssistantTranslate: failure.code }}</p>\n <button class=\"secondary\" type=\"button\" (click)=\"loadSummary()\">{{ 'nh-assistant.admin.retry' | translate }}</button>\n </div>\n } @else if (visibleRows().length === 0) {\n <p class=\"state\">{{ 'nh-assistant.admin.feedback.summary-empty' | translate }}</p>\n } @else {\n <div class=\"table-container\">\n <table class=\"summary-table\" [attr.aria-labelledby]=\"id + '-summary-title'\">\n <thead>\n <tr>\n <th scope=\"col\">{{ 'nh-assistant.admin.feedback.columns.tool' | translate }}</th>\n <th scope=\"col\">{{ 'nh-assistant.admin.feedback.columns.version' | translate }}</th>\n <th scope=\"col\" class=\"number\">{{ 'nh-assistant.admin.feedback.columns.approved' | translate }}</th>\n <th scope=\"col\" class=\"number\">{{ 'nh-assistant.admin.feedback.columns.rejected' | translate }}</th>\n <th scope=\"col\" class=\"number\">{{ 'nh-assistant.admin.feedback.columns.expired' | translate }}</th>\n <th scope=\"col\" class=\"number\">{{ 'nh-assistant.admin.feedback.columns.rejection-rate' | translate }}</th>\n <th scope=\"col\" class=\"number\">{{ 'nh-assistant.admin.feedback.columns.with-feedback' | translate }}</th>\n <th scope=\"col\" class=\"number\">{{ 'nh-assistant.admin.feedback.columns.people' | translate }}</th>\n <th scope=\"col\">{{ 'nh-assistant.admin.feedback.columns.categories' | translate }}</th>\n </tr>\n </thead>\n <tbody>\n @for (row of visibleRows(); track row.toolId + '|' + row.agentVersion) {\n <tr>\n <th scope=\"row\" class=\"tool\" [attr.data-label]=\"'nh-assistant.admin.feedback.columns.tool' | translate\">\n <span class=\"mono\">{{ row.toolId }}</span>\n </th>\n <td [attr.data-label]=\"'nh-assistant.admin.feedback.columns.version' | translate\">\n @if (row.agentVersion === null) {\n {{ 'nh-assistant.admin.feedback.consultations' | translate }}\n } @else {\n {{ 'nh-assistant.admin.feedback.version' | translate: { version: row.agentVersion } }}\n }\n </td>\n <td class=\"number\" [attr.data-label]=\"'nh-assistant.admin.feedback.columns.approved' | translate\">{{ row.approved }}</td>\n <td class=\"number\" [attr.data-label]=\"'nh-assistant.admin.feedback.columns.rejected' | translate\">{{ row.rejected }}</td>\n <td class=\"number\" [attr.data-label]=\"'nh-assistant.admin.feedback.columns.expired' | translate\">{{ row.expired }}</td>\n <td class=\"number\" [attr.data-label]=\"'nh-assistant.admin.feedback.columns.rejection-rate' | translate\">\n @if (row.rejectionRate === null) {\n <span class=\"muted\">{{ 'nh-assistant.admin.feedback.no-decisions' | translate }}</span>\n } @else {\n {{ row.rejectionRate | percent: '1.0-0' }}\n }\n </td>\n <td class=\"number\" [attr.data-label]=\"'nh-assistant.admin.feedback.columns.with-feedback' | translate\">{{ row.rejectedWithFeedback }}</td>\n <td class=\"number\" [attr.data-label]=\"'nh-assistant.admin.feedback.columns.people' | translate\">{{ row.feedbackAuthors }}</td>\n <td [attr.data-label]=\"'nh-assistant.admin.feedback.columns.categories' | translate\">\n @if (row.topCategories.length === 0) {\n <span class=\"muted\">{{ 'nh-assistant.admin.feedback.no-categories' | translate }}</span>\n } @else {\n <ul class=\"badges\">\n @for (item of row.topCategories; track item.category) {\n <li>\n <span [class]=\"'badge ' + groupClass(item.group)\" [attr.title]=\"'nh-assistant.proposal-feedback.groups.' + item.group | translate\">\n {{ 'nh-assistant.proposal-feedback.categories.' + item.category | nhAssistantTranslate: item.category }}\n <span class=\"count\">{{ item.count }}</span>\n </span>\n </li>\n }\n </ul>\n }\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n </section>\n\n <section class=\"subsection\" [attr.aria-labelledby]=\"id + '-list-title'\" [attr.aria-busy]=\"listLoading()\">\n <div class=\"list-header\">\n <h4 [id]=\"id + '-list-title'\">{{ 'nh-assistant.admin.feedback.list-title' | translate }}</h4>\n @if (!listLoading() && listError() === null) {\n <span class=\"muted\">{{ 'nh-assistant.admin.feedback.list-count' | translate: { count: total() } }}</span>\n }\n </div>\n @if (listLoading() && items().length === 0) {\n <p class=\"state\" role=\"status\">{{ 'nh-assistant.admin.loading' | translate }}</p>\n } @else if (listError(); as failure) {\n <div class=\"state\" role=\"alert\">\n <p>{{ listErrorKeys() | nhAssistantTranslate: failure.code }}</p>\n <button class=\"secondary\" type=\"button\" (click)=\"loadList()\">{{ 'nh-assistant.admin.retry' | translate }}</button>\n </div>\n } @else if (items().length === 0) {\n <p class=\"state\">{{ (filtered() ? 'nh-assistant.admin.feedback.list-empty-filtered' : 'nh-assistant.admin.feedback.list-empty') | translate }}</p>\n } @else {\n <ul class=\"cards\">\n @for (item of items(); track item.id) {\n <li class=\"card\">\n <div class=\"card-header\">\n <strong>{{ item.subjectDisplayName ?? item.subjectId }}</strong>\n <span class=\"muted\">{{ item.presentation?.toolDisplayName ?? item.toolId }}</span>\n @if (item.agentVersion !== null) {\n <span class=\"badge\">{{ 'nh-assistant.admin.feedback.version' | translate: { version: item.agentVersion } }}</span>\n }\n <span [class]=\"'badge ' + groupClass(item.group)\" [attr.title]=\"'nh-assistant.proposal-feedback.groups.' + item.group | translate\">\n {{ 'nh-assistant.proposal-feedback.categories.' + item.category | nhAssistantTranslate: item.category }}\n </span>\n @if (item.bulk) {\n <span class=\"badge\" [attr.title]=\"'nh-assistant.admin.feedback.bulk-hint' | translate\">{{ 'nh-assistant.admin.feedback.bulk' | translate }}</span>\n }\n </div>\n @if (item.text) {\n <blockquote class=\"feedback-text\">{{ item.text }}</blockquote>\n } @else {\n <p class=\"muted\">{{ 'nh-assistant.admin.feedback.no-text' | translate }}</p>\n }\n <p class=\"proposal\">\n <span class=\"proposal-label\">{{ 'nh-assistant.admin.feedback.proposal' | translate }}</span>\n {{ item.presentation?.summary ?? item.summary }}\n </p>\n <div class=\"meta\">\n <span>{{ 'nh-assistant.agent-runs.proposal.status.' + item.approvalStatus | nhAssistantTranslate: item.approvalStatus }}</span>\n <span>{{ 'nh-assistant.admin.feedback.author' | translate: { actor: item.authorDisplayName || item.authorActorId } }}</span>\n <span>{{ item.updatedAt | date: 'short' }}</span>\n </div>\n </li>\n }\n </ul>\n @if (pages() > 1) {\n <nav class=\"pager\" [attr.aria-label]=\"'nh-assistant.admin.feedback.pages' | translate\">\n <button class=\"secondary\" type=\"button\" [disabled]=\"page() <= 1 || listLoading()\" (click)=\"goTo(page() - 1)\">\n {{ 'nh-assistant.admin.feedback.previous' | translate }}\n </button>\n <span class=\"muted\">{{ 'nh-assistant.admin.feedback.page' | translate: { page: page(), pages: pages() } }}</span>\n <button class=\"secondary\" type=\"button\" [disabled]=\"page() >= pages() || listLoading()\" (click)=\"goTo(page() + 1)\">\n {{ 'nh-assistant.admin.feedback.next' | translate }}\n </button>\n </nav>\n }\n }\n </section>\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", ".header-actions{display:flex;flex-wrap:wrap;gap:8px}.export-hint{margin-top:4px;font-size:12px}.filters{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));align-items:start;gap:12px}.subsection h4,.list-header h4{margin:0}.list-header{display:flex;flex-wrap:wrap;align-items:baseline;justify-content:space-between;gap:4px 12px}.table-container{container-type:inline-size;min-width:0}.summary-table{width:100%;border-collapse:collapse;font-size:13px}.summary-table th,.summary-table td{border-bottom:1px solid var(--nha-border);padding:8px 10px;text-align:left;vertical-align:top}.summary-table thead th{color:var(--nha-text-muted);font-size:11px;font-weight:700;letter-spacing:.04em;text-transform:uppercase}.summary-table tbody th{font-weight:600}.summary-table .number{text-align:right;font-variant-numeric:tabular-nums}.summary-table .mono{overflow-wrap:anywhere}@container (max-width: 820px){.summary-table thead{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}.summary-table,.summary-table tbody,.summary-table tr{display:block}.summary-table tr{margin-bottom:8px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px 12px;background:var(--nha-background)}.summary-table tbody th,.summary-table td{display:grid;grid-template-columns:minmax(110px,40%) minmax(0,1fr);gap:8px;border:0;padding:4px 0;text-align:left}.summary-table tbody th:before,.summary-table td:before{content:attr(data-label);color:var(--nha-text-muted);font-size:12px;font-weight:700}}.badges{display:flex;flex-wrap:wrap;gap:4px;margin:0;padding:0;list-style:none}.badge .count{font-variant-numeric:tabular-nums;opacity:.85}.feedback-text{margin:0;border-left:3px solid var(--nha-border);padding:2px 0 2px 10px;font-size:13px;white-space:pre-wrap;overflow-wrap:anywhere}.proposal{margin:0;color:var(--nha-text-muted);font-size:12px;overflow-wrap:anywhere}.proposal-label{margin-right:4px;color:var(--nha-text);font-weight:700}.pager{display:flex;flex-wrap:wrap;align-items:center;gap:8px}\n"] }]
|
|
429
|
+
args: [{ selector: 'nh-assistant-agent-feedback-report', standalone: true, imports: [DatePipe, PercentPipe, TranslatePipe, _NhAssistantTranslatePipe, _NhAssistantIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<section class=\"section report\" [attr.aria-labelledby]=\"id + '-title'\">\n <header class=\"section-header\">\n <div>\n <h3 [id]=\"id + '-title'\">{{ 'nh-assistant.admin.feedback.title' | translate }}</h3>\n <p class=\"muted\">{{ 'nh-assistant.admin.feedback.description' | translate }}</p>\n <p class=\"muted export-hint\" [id]=\"id + '-export-hint'\">{{ 'nh-assistant.admin.feedback.export-hint' | translate }}</p>\n </div>\n <div class=\"header-actions\">\n <button class=\"secondary\" type=\"button\" [disabled]=\"summaryLoading() || listLoading()\" (click)=\"load()\">\n <nh-assistant-icon name=\"refresh\" />{{ 'nh-assistant.admin.feedback.refresh' | translate }}\n </button>\n <button\n class=\"secondary\"\n type=\"button\"\n [disabled]=\"!canExport()\"\n [attr.aria-describedby]=\"id + '-export-hint'\"\n (click)=\"export()\">\n <nh-assistant-icon name=\"page\" />{{ (exporting() ? 'nh-assistant.admin.feedback.exporting' : 'nh-assistant.admin.feedback.export') | translate }}\n </button>\n </div>\n </header>\n\n <div class=\"filters\">\n <div class=\"field\">\n <label [for]=\"id + '-period'\">{{ 'nh-assistant.admin.feedback.period' | translate }}</label>\n <select [id]=\"id + '-period'\" (change)=\"onPeriod($event)\">\n @for (days of periods; track days) {\n <option [value]=\"days\" [selected]=\"period() === days\">{{ 'nh-assistant.admin.feedback.periods.last-' + days + '-days' | translate }}</option>\n }\n </select>\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-tool'\">{{ 'nh-assistant.admin.feedback.tool' | translate }}</label>\n <select [id]=\"id + '-tool'\" [disabled]=\"summaryLoading() && tools().length === 0\" (change)=\"onTool($event)\">\n <option value=\"\" [selected]=\"toolId() === ''\">{{ 'nh-assistant.admin.feedback.all-tools' | translate }}</option>\n @for (tool of tools(); track tool) {\n <option [value]=\"tool\" [selected]=\"toolId() === tool\">{{ tool }}</option>\n }\n </select>\n </div>\n <div class=\"field\">\n <label [for]=\"id + '-category'\">{{ 'nh-assistant.admin.feedback.category' | translate }}</label>\n <select [id]=\"id + '-category'\" [attr.aria-describedby]=\"id + '-category-hint'\" (change)=\"onCategory($event)\">\n <option value=\"\" [selected]=\"category() === ''\">{{ 'nh-assistant.admin.feedback.all-categories' | translate }}</option>\n @for (option of categories; track option) {\n <option [value]=\"option\" [selected]=\"category() === option\">{{ 'nh-assistant.proposal-feedback.categories.' + option | translate }}</option>\n }\n </select>\n <p class=\"hint\" [id]=\"id + '-category-hint'\"><span>{{ 'nh-assistant.admin.feedback.category-hint' | translate }}</span></p>\n </div>\n </div>\n\n @if (exportError(); as failure) {\n <p class=\"message error\" role=\"alert\"><nh-assistant-icon name=\"warning\" />{{ exportErrorKeys() | nhAssistantTranslate: failure.code }}</p>\n }\n\n <section class=\"subsection\" [attr.aria-labelledby]=\"id + '-summary-title'\" [attr.aria-busy]=\"summaryLoading()\">\n <h4 [id]=\"id + '-summary-title'\">{{ 'nh-assistant.admin.feedback.summary-title' | translate }}</h4>\n @if (summaryLoading() && rows().length === 0) {\n <p class=\"state\" role=\"status\">{{ 'nh-assistant.admin.loading' | translate }}</p>\n } @else if (summaryError(); as failure) {\n <div class=\"state\" role=\"alert\">\n <p>{{ summaryErrorKeys() | nhAssistantTranslate: failure.code }}</p>\n <button class=\"secondary\" type=\"button\" (click)=\"loadSummary()\">{{ 'nh-assistant.admin.retry' | translate }}</button>\n </div>\n } @else if (visibleRows().length === 0) {\n <p class=\"state\">{{ 'nh-assistant.admin.feedback.summary-empty' | translate }}</p>\n } @else {\n <div class=\"table-container\">\n <table class=\"summary-table\" [attr.aria-labelledby]=\"id + '-summary-title'\">\n <thead>\n <tr>\n <th scope=\"col\">{{ 'nh-assistant.admin.feedback.columns.tool' | translate }}</th>\n <th scope=\"col\">{{ 'nh-assistant.admin.feedback.columns.version' | translate }}</th>\n <th scope=\"col\" class=\"number\">{{ 'nh-assistant.admin.feedback.columns.approved' | translate }}</th>\n <th scope=\"col\" class=\"number\">{{ 'nh-assistant.admin.feedback.columns.rejected' | translate }}</th>\n <th scope=\"col\" class=\"number\">{{ 'nh-assistant.admin.feedback.columns.expired' | translate }}</th>\n <th scope=\"col\" class=\"number\" [attr.title]=\"'nh-assistant.admin.feedback.invalidated-hint' | translate\">\n {{ 'nh-assistant.admin.feedback.columns.invalidated' | translate }}\n </th>\n <th scope=\"col\" class=\"number\">{{ 'nh-assistant.admin.feedback.columns.rejection-rate' | translate }}</th>\n <th scope=\"col\" class=\"number\">{{ 'nh-assistant.admin.feedback.columns.with-feedback' | translate }}</th>\n <th scope=\"col\" class=\"number\">{{ 'nh-assistant.admin.feedback.columns.people' | translate }}</th>\n <th scope=\"col\">{{ 'nh-assistant.admin.feedback.columns.categories' | translate }}</th>\n </tr>\n </thead>\n <tbody>\n @for (row of visibleRows(); track row.toolId + '|' + row.agentVersion) {\n <tr>\n <th scope=\"row\" class=\"tool\" [attr.data-label]=\"'nh-assistant.admin.feedback.columns.tool' | translate\">\n <span class=\"mono\">{{ row.toolId }}</span>\n </th>\n <td [attr.data-label]=\"'nh-assistant.admin.feedback.columns.version' | translate\">\n @if (row.agentVersion === null) {\n {{ 'nh-assistant.admin.feedback.consultations' | translate }}\n } @else {\n {{ 'nh-assistant.admin.feedback.version' | translate: { version: row.agentVersion } }}\n }\n </td>\n <td class=\"number\" [attr.data-label]=\"'nh-assistant.admin.feedback.columns.approved' | translate\">{{ row.approved }}</td>\n <td class=\"number\" [attr.data-label]=\"'nh-assistant.admin.feedback.columns.rejected' | translate\">{{ row.rejected }}</td>\n <td class=\"number\" [attr.data-label]=\"'nh-assistant.admin.feedback.columns.expired' | translate\">{{ row.expired }}</td>\n <td class=\"number\" [attr.data-label]=\"'nh-assistant.admin.feedback.columns.invalidated' | translate\">\n {{ row.invalidated ?? 0 }}\n @if (row.invalidatedKinds.length > 0) {\n <ul class=\"badges\">\n @for (item of row.invalidatedKinds; track item.kind) {\n <li>\n <span class=\"badge\" [attr.title]=\"item.reasons\">\n {{ 'nh-assistant.approval.stale-kind.' + item.kind | translate }}\n <span class=\"count\">{{ item.count }}</span>\n </span>\n </li>\n }\n </ul>\n }\n </td>\n <td class=\"number\" [attr.data-label]=\"'nh-assistant.admin.feedback.columns.rejection-rate' | translate\">\n @if (row.rejectionRate === null) {\n <span class=\"muted\">{{ 'nh-assistant.admin.feedback.no-decisions' | translate }}</span>\n } @else {\n {{ row.rejectionRate | percent: '1.0-0' }}\n }\n </td>\n <td class=\"number\" [attr.data-label]=\"'nh-assistant.admin.feedback.columns.with-feedback' | translate\">{{ row.rejectedWithFeedback }}</td>\n <td class=\"number\" [attr.data-label]=\"'nh-assistant.admin.feedback.columns.people' | translate\">{{ row.feedbackAuthors }}</td>\n <td [attr.data-label]=\"'nh-assistant.admin.feedback.columns.categories' | translate\">\n @if (row.topCategories.length === 0) {\n <span class=\"muted\">{{ 'nh-assistant.admin.feedback.no-categories' | translate }}</span>\n } @else {\n <ul class=\"badges\">\n @for (item of row.topCategories; track item.category) {\n <li>\n <span [class]=\"'badge ' + groupClass(item.group)\" [attr.title]=\"'nh-assistant.proposal-feedback.groups.' + item.group | translate\">\n {{ 'nh-assistant.proposal-feedback.categories.' + item.category | nhAssistantTranslate: item.category }}\n <span class=\"count\">{{ item.count }}</span>\n </span>\n </li>\n }\n </ul>\n }\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n </section>\n\n <section class=\"subsection\" [attr.aria-labelledby]=\"id + '-list-title'\" [attr.aria-busy]=\"listLoading()\">\n <div class=\"list-header\">\n <h4 [id]=\"id + '-list-title'\">{{ 'nh-assistant.admin.feedback.list-title' | translate }}</h4>\n @if (!listLoading() && listError() === null) {\n <span class=\"muted\">{{ 'nh-assistant.admin.feedback.list-count' | translate: { count: total() } }}</span>\n }\n </div>\n @if (listLoading() && items().length === 0) {\n <p class=\"state\" role=\"status\">{{ 'nh-assistant.admin.loading' | translate }}</p>\n } @else if (listError(); as failure) {\n <div class=\"state\" role=\"alert\">\n <p>{{ listErrorKeys() | nhAssistantTranslate: failure.code }}</p>\n <button class=\"secondary\" type=\"button\" (click)=\"loadList()\">{{ 'nh-assistant.admin.retry' | translate }}</button>\n </div>\n } @else if (items().length === 0) {\n <p class=\"state\">{{ (filtered() ? 'nh-assistant.admin.feedback.list-empty-filtered' : 'nh-assistant.admin.feedback.list-empty') | translate }}</p>\n } @else {\n <ul class=\"cards\">\n @for (item of items(); track item.id) {\n <li class=\"card\">\n <div class=\"card-header\">\n <strong>{{ item.subjectDisplayName ?? item.subjectId }}</strong>\n <span class=\"muted\">{{ item.presentation?.toolDisplayName ?? item.toolId }}</span>\n @if (item.agentVersion !== null) {\n <span class=\"badge\">{{ 'nh-assistant.admin.feedback.version' | translate: { version: item.agentVersion } }}</span>\n }\n <span [class]=\"'badge ' + groupClass(item.group)\" [attr.title]=\"'nh-assistant.proposal-feedback.groups.' + item.group | translate\">\n {{ 'nh-assistant.proposal-feedback.categories.' + item.category | nhAssistantTranslate: item.category }}\n </span>\n @if (item.bulk) {\n <span class=\"badge\" [attr.title]=\"'nh-assistant.admin.feedback.bulk-hint' | translate\">{{ 'nh-assistant.admin.feedback.bulk' | translate }}</span>\n }\n </div>\n @if (item.text) {\n <blockquote class=\"feedback-text\">{{ item.text }}</blockquote>\n } @else {\n <p class=\"muted\">{{ 'nh-assistant.admin.feedback.no-text' | translate }}</p>\n }\n <p class=\"proposal\">\n <span class=\"proposal-label\">{{ 'nh-assistant.admin.feedback.proposal' | translate }}</span>\n {{ item.presentation?.summary ?? item.summary }}\n </p>\n <div class=\"meta\">\n <span>{{ 'nh-assistant.agent-runs.proposal.status.' + item.approvalStatus | nhAssistantTranslate: item.approvalStatus }}</span>\n <span>{{ 'nh-assistant.admin.feedback.author' | translate: { actor: item.authorDisplayName || item.authorActorId } }}</span>\n <span>{{ item.updatedAt | date: 'short' }}</span>\n </div>\n </li>\n }\n </ul>\n @if (pages() > 1) {\n <nav class=\"pager\" [attr.aria-label]=\"'nh-assistant.admin.feedback.pages' | translate\">\n <button class=\"secondary\" type=\"button\" [disabled]=\"page() <= 1 || listLoading()\" (click)=\"goTo(page() - 1)\">\n {{ 'nh-assistant.admin.feedback.previous' | translate }}\n </button>\n <span class=\"muted\">{{ 'nh-assistant.admin.feedback.page' | translate: { page: page(), pages: pages() } }}</span>\n <button class=\"secondary\" type=\"button\" [disabled]=\"page() >= pages() || listLoading()\" (click)=\"goTo(page() + 1)\">\n {{ 'nh-assistant.admin.feedback.next' | translate }}\n </button>\n </nav>\n }\n }\n </section>\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", ".header-actions{display:flex;flex-wrap:wrap;gap:8px}.export-hint{margin-top:4px;font-size:12px}.filters{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));align-items:start;gap:12px}.subsection h4,.list-header h4{margin:0}.list-header{display:flex;flex-wrap:wrap;align-items:baseline;justify-content:space-between;gap:4px 12px}.table-container{container-type:inline-size;min-width:0}.summary-table{width:100%;border-collapse:collapse;font-size:13px}.summary-table th,.summary-table td{border-bottom:1px solid var(--nha-border);padding:8px 10px;text-align:left;vertical-align:top}.summary-table thead th{color:var(--nha-text-muted);font-size:11px;font-weight:700;letter-spacing:.04em;text-transform:uppercase}.summary-table tbody th{font-weight:600}.summary-table .number{text-align:right;font-variant-numeric:tabular-nums}.summary-table .mono{overflow-wrap:anywhere}@container (max-width: 820px){.summary-table thead{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}.summary-table,.summary-table tbody,.summary-table tr{display:block}.summary-table tr{margin-bottom:8px;border:1px solid var(--nha-border);border-radius:var(--nha-radius-control);padding:6px 12px;background:var(--nha-background)}.summary-table tbody th,.summary-table td{display:grid;grid-template-columns:minmax(110px,40%) minmax(0,1fr);gap:8px;border:0;padding:4px 0;text-align:left}.summary-table tbody th:before,.summary-table td:before{content:attr(data-label);color:var(--nha-text-muted);font-size:12px;font-weight:700}}.badges{display:flex;flex-wrap:wrap;gap:4px;margin:0;padding:0;list-style:none}.badge .count{font-variant-numeric:tabular-nums;opacity:.85}.feedback-text{margin:0;border-left:3px solid var(--nha-border);padding:2px 0 2px 10px;font-size:13px;white-space:pre-wrap;overflow-wrap:anywhere}.proposal{margin:0;color:var(--nha-text-muted);font-size:12px;overflow-wrap:anywhere}.proposal-label{margin-right:4px;color:var(--nha-text);font-weight:700}.pager{display:flex;flex-wrap:wrap;align-items:center;gap:8px}\n"] }]
|
|
429
430
|
}], ctorParameters: () => [], propDecorators: { agentId: [{ type: i0.Input, args: [{ isSignal: true, alias: "agentId", required: true }] }], itemsPerPage: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemsPerPage", required: false }] }] } });
|
|
430
431
|
function periodRange(days) {
|
|
431
432
|
const to = Date.now();
|
|
432
433
|
return { from: new Date(to - days * dayMs).toISOString(), to: new Date(to).toISOString() };
|
|
433
434
|
}
|
|
435
|
+
/**
|
|
436
|
+
* The open proposals of a row that closed before a decision, per kind, with their reason codes for
|
|
437
|
+
* the tooltip. Proposals that closed after their approval are part of the approved count.
|
|
438
|
+
*/
|
|
439
|
+
function invalidatedKinds(row) {
|
|
440
|
+
const kinds = new Map();
|
|
441
|
+
for (const item of row.invalidations ?? []) {
|
|
442
|
+
if (item.afterApproval) {
|
|
443
|
+
continue;
|
|
444
|
+
}
|
|
445
|
+
const entry = kinds.get(item.kind) ?? { count: 0, reasons: [] };
|
|
446
|
+
entry.count += item.count;
|
|
447
|
+
entry.reasons.push(`${item.reasonCode} (${item.count})`);
|
|
448
|
+
kinds.set(item.kind, entry);
|
|
449
|
+
}
|
|
450
|
+
return [...kinds.entries()]
|
|
451
|
+
.map(([kind, entry]) => ({ kind, count: entry.count, reasons: entry.reasons.join(', ') }))
|
|
452
|
+
.sort((left, right) => right.count - left.count);
|
|
453
|
+
}
|
|
434
454
|
|
|
435
455
|
let nextId$4 = 0;
|
|
436
456
|
/**
|