@memberjunction/ng-dashboards 5.46.0 → 5.47.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.
Files changed (50) hide show
  1. package/dist/PredictiveStudio/at-risk.view-models.d.ts +33 -0
  2. package/dist/PredictiveStudio/at-risk.view-models.d.ts.map +1 -1
  3. package/dist/PredictiveStudio/at-risk.view-models.js +69 -2
  4. package/dist/PredictiveStudio/at-risk.view-models.js.map +1 -1
  5. package/dist/PredictiveStudio/components/ps-catalog.component.js +2 -2
  6. package/dist/PredictiveStudio/components/ps-catalog.component.js.map +1 -1
  7. package/dist/PredictiveStudio/components/ps-compare.component.js +2 -2
  8. package/dist/PredictiveStudio/components/ps-compare.component.js.map +1 -1
  9. package/dist/PredictiveStudio/components/ps-confirm-modal.component.js +2 -2
  10. package/dist/PredictiveStudio/components/ps-confirm-modal.component.js.map +1 -1
  11. package/dist/PredictiveStudio/components/ps-experiments.component.js +2 -2
  12. package/dist/PredictiveStudio/components/ps-experiments.component.js.map +1 -1
  13. package/dist/PredictiveStudio/components/ps-home.component.js +2 -2
  14. package/dist/PredictiveStudio/components/ps-home.component.js.map +1 -1
  15. package/dist/PredictiveStudio/components/ps-operate-dialog.component.js +2 -2
  16. package/dist/PredictiveStudio/components/ps-operate-dialog.component.js.map +1 -1
  17. package/dist/PredictiveStudio/components/ps-pipelines.component.d.ts.map +1 -1
  18. package/dist/PredictiveStudio/components/ps-pipelines.component.js +306 -297
  19. package/dist/PredictiveStudio/components/ps-pipelines.component.js.map +1 -1
  20. package/dist/PredictiveStudio/components/ps-production.component.js +2 -2
  21. package/dist/PredictiveStudio/components/ps-production.component.js.map +1 -1
  22. package/dist/PredictiveStudio/components/ps-registry.component.d.ts +1 -1
  23. package/dist/PredictiveStudio/components/ps-registry.component.d.ts.map +1 -1
  24. package/dist/PredictiveStudio/components/ps-registry.component.js +15 -9
  25. package/dist/PredictiveStudio/components/ps-registry.component.js.map +1 -1
  26. package/dist/PredictiveStudio/predictive-studio-agent-context.d.ts +115 -0
  27. package/dist/PredictiveStudio/predictive-studio-agent-context.d.ts.map +1 -0
  28. package/dist/PredictiveStudio/predictive-studio-agent-context.js +119 -0
  29. package/dist/PredictiveStudio/predictive-studio-agent-context.js.map +1 -0
  30. package/dist/PredictiveStudio/predictive-studio-copilot.view-models.d.ts +36 -0
  31. package/dist/PredictiveStudio/predictive-studio-copilot.view-models.d.ts.map +1 -0
  32. package/dist/PredictiveStudio/predictive-studio-copilot.view-models.js +52 -0
  33. package/dist/PredictiveStudio/predictive-studio-copilot.view-models.js.map +1 -0
  34. package/dist/PredictiveStudio/resources/ps-models-resource.component.d.ts +9 -1
  35. package/dist/PredictiveStudio/resources/ps-models-resource.component.d.ts.map +1 -1
  36. package/dist/PredictiveStudio/resources/ps-models-resource.component.js +133 -56
  37. package/dist/PredictiveStudio/resources/ps-models-resource.component.js.map +1 -1
  38. package/dist/PredictiveStudio/resources/ps-predictions-resource.component.d.ts +50 -1
  39. package/dist/PredictiveStudio/resources/ps-predictions-resource.component.d.ts.map +1 -1
  40. package/dist/PredictiveStudio/resources/ps-predictions-resource.component.js +591 -165
  41. package/dist/PredictiveStudio/resources/ps-predictions-resource.component.js.map +1 -1
  42. package/dist/PredictiveStudio/resources/ps-resource-base.d.ts +17 -0
  43. package/dist/PredictiveStudio/resources/ps-resource-base.d.ts.map +1 -1
  44. package/dist/PredictiveStudio/resources/ps-resource-base.js +27 -1
  45. package/dist/PredictiveStudio/resources/ps-resource-base.js.map +1 -1
  46. package/dist/PredictiveStudio/resources/ps-studio-resource.component.d.ts +11 -1
  47. package/dist/PredictiveStudio/resources/ps-studio-resource.component.d.ts.map +1 -1
  48. package/dist/PredictiveStudio/resources/ps-studio-resource.component.js +199 -99
  49. package/dist/PredictiveStudio/resources/ps-studio-resource.component.js.map +1 -1
  50. package/package.json +59 -59
@@ -0,0 +1,119 @@
1
+ /**
2
+ * @fileoverview Pure, framework-agnostic helpers for Predictive Studio's AI-agent integration across its
3
+ * three doors — **Predictions** (business front door), **Studio** (analyst workbench), and **Models**
4
+ * (registry + production). Each door component supplies a plain snapshot of its current state and these
5
+ * helpers shape it into the key-value context object that flows to the async chat agent AND the realtime
6
+ * co-agent via `NavigationService.SetAgentContext`, plus resolve an agent-supplied id/name/partial to a
7
+ * concrete record for the doors' client tools.
8
+ *
9
+ * Kept Angular-free so it's unit-testable in isolation (see `src/__tests__/predictive-studio-agent-context.test.ts`).
10
+ * The shared validation/cap helpers (`validateStringParam`, `boundNameList`) come from
11
+ * `src/shared/agent-tool-validation.ts`; the PS-specific resolver + builders live LOCAL here (same
12
+ * convention as every other MJ surface — e.g. Lists' `lists-agent-context.ts`).
13
+ *
14
+ * 🔒 SAFETY BOUNDARY: every function here is read-only and side-effect-free — it only shapes context and
15
+ * matches strings. Predictive Studio surfaces are **read / navigate-only to the agent**: the doors expose
16
+ * ONLY section-switch / open-prediction / review / export / open-copilot tools. Model **training,
17
+ * promotion/publish, deletion, scoring-run, "save scores to records", and "send to a list" (which creates
18
+ * `MJ: Lists` + `MJ: List Details` rows) are DELIBERATELY NOT exposed** — those write/irreversible actions
19
+ * stay behind the user's own clicks and the Model Development Agent's approve-gated builder. The
20
+ * read-only boundary is restated on each door component; this module never mutates anything.
21
+ */
22
+ import { boundNameList } from '../shared/agent-tool-validation';
23
+ /** Upper bound on names published in any PS context list field; a companion `*Count` is surfaced when truncated. */
24
+ export const PS_AGENT_CONTEXT_NAME_LIST_CAP = 25;
25
+ /** Cap a name list to {@link PS_AGENT_CONTEXT_NAME_LIST_CAP}. Pure; never mutates the input. */
26
+ export function capPSNames(names) {
27
+ return boundNameList(names, PS_AGENT_CONTEXT_NAME_LIST_CAP);
28
+ }
29
+ /**
30
+ * Resolve an agent-supplied reference the way a user names things: exact ID (case-insensitive, to tolerate
31
+ * SQL-Server-upper vs PG-lower UUID casing) → exact name (trimmed, case-insensitive) → case-insensitive
32
+ * *contains* match on the name, but ONLY when it's unambiguous — a needle contained in several candidate
33
+ * names returns null (silently opening the first would be silently opening the wrong one; the paired
34
+ * {@link buildPSNotFoundError} then lists the contenders so the agent can disambiguate). Pure +
35
+ * deterministic over the candidate list.
36
+ */
37
+ export function resolvePSRecord(input, candidates) {
38
+ const needle = (input ?? '').trim().toLowerCase();
39
+ if (!needle)
40
+ return null;
41
+ const byId = candidates.find((c) => c.ID.toLowerCase() === needle);
42
+ if (byId)
43
+ return byId;
44
+ const byName = candidates.find((c) => c.Name.trim().toLowerCase() === needle);
45
+ if (byName)
46
+ return byName;
47
+ const contains = candidates.filter((c) => c.Name.toLowerCase().includes(needle));
48
+ return contains.length === 1 ? contains[0] : null;
49
+ }
50
+ /**
51
+ * Tolerant "not found" message so the agent can self-correct: when the input partially matches SEVERAL
52
+ * candidates it becomes a "did you mean…" listing exactly those; otherwise it samples available names. Pure.
53
+ */
54
+ export function buildPSNotFoundError(input, candidates, noun) {
55
+ const needle = (input ?? '').trim().toLowerCase();
56
+ const contains = needle ? candidates.filter((c) => c.Name.toLowerCase().includes(needle)) : [];
57
+ if (contains.length > 1) {
58
+ return `"${input}" matches more than one ${noun} — did you mean: ${contains.slice(0, 6).map((c) => c.Name).join(', ')}? Use the exact name or ID.`;
59
+ }
60
+ const sample = candidates.slice(0, 6).map((c) => c.Name).join(', ');
61
+ return `No ${noun} matching "${input}" is available. Available ${noun}s include: ${sample || '(none)'}.`;
62
+ }
63
+ /** Build the Predictions door context: catalog counts + bounded names, and (in workspace) the selection + at-risk breakdown. */
64
+ export function buildPredictionsAgentContext(input) {
65
+ const context = {
66
+ View: input.View,
67
+ PredictionCount: input.PredictionCount,
68
+ ReadyPredictionCount: input.ReadyPredictionCount,
69
+ ChatOpen: input.ChatOpen,
70
+ VisiblePredictionNames: capPSNames(input.VisiblePredictionNames),
71
+ };
72
+ if (input.VisiblePredictionNames.length > PS_AGENT_CONTEXT_NAME_LIST_CAP) {
73
+ context['VisiblePredictionNameCount'] = input.VisiblePredictionNames.length;
74
+ }
75
+ if (input.View === 'workspace' && input.Selected) {
76
+ context['SelectedPredictionName'] = input.Selected.Name;
77
+ context['SelectedPredictionTrust'] = input.Selected.TrustGrade;
78
+ context['SelectedPredictionCanAct'] = input.Selected.CanOpen;
79
+ // Only surface the at-risk breakdown once loaded — never fabricate zeros before the fetch resolves.
80
+ if (input.AtRiskLoaded) {
81
+ context['AtRiskCount'] = input.AtRiskCount ?? 0;
82
+ context['HighRiskCount'] = input.HighRiskCount ?? 0;
83
+ context['MediumRiskCount'] = input.MediumRiskCount ?? 0;
84
+ context['LowRiskCount'] = input.LowRiskCount ?? 0;
85
+ }
86
+ if (input.Drivers && input.Drivers.length > 0) {
87
+ context['Drivers'] = capPSNames(input.Drivers);
88
+ }
89
+ }
90
+ return context;
91
+ }
92
+ /** Build the Studio door context: active section + section labels + the workbench's headline counts. */
93
+ export function buildStudioAgentContext(input) {
94
+ return {
95
+ ActiveSection: input.ActiveSection,
96
+ ActiveSectionLabel: input.ActiveSectionLabel,
97
+ SectionLabels: capPSNames(input.SectionLabels),
98
+ PublishedModelCount: input.PublishedModelCount,
99
+ RunningSessionCount: input.RunningSessionCount,
100
+ PipelineCount: input.PipelineCount,
101
+ AlgorithmCount: input.AlgorithmCount,
102
+ ExperimentCount: input.ExperimentCount,
103
+ TrainingRunCount: input.TrainingRunCount,
104
+ ChatOpen: input.ChatOpen,
105
+ };
106
+ }
107
+ /** Build the Models door context: active section + section labels + the lifecycle counts. */
108
+ export function buildModelsAgentContext(input) {
109
+ return {
110
+ ActiveSection: input.ActiveSection,
111
+ ActiveSectionLabel: input.ActiveSectionLabel,
112
+ SectionLabels: capPSNames(input.SectionLabels),
113
+ TotalModelCount: input.TotalModelCount,
114
+ PublishedModelCount: input.PublishedModelCount,
115
+ DraftModelCount: input.DraftModelCount,
116
+ ProductionModelCount: input.ProductionModelCount,
117
+ };
118
+ }
119
+ //# sourceMappingURL=predictive-studio-agent-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"predictive-studio-agent-context.js","sourceRoot":"","sources":["../../src/PredictiveStudio/predictive-studio-agent-context.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @fileoverview Pure, framework-agnostic helpers for Predictive Studio's AI-agent integration across its\n * three doors — **Predictions** (business front door), **Studio** (analyst workbench), and **Models**\n * (registry + production). Each door component supplies a plain snapshot of its current state and these\n * helpers shape it into the key-value context object that flows to the async chat agent AND the realtime\n * co-agent via `NavigationService.SetAgentContext`, plus resolve an agent-supplied id/name/partial to a\n * concrete record for the doors' client tools.\n *\n * Kept Angular-free so it's unit-testable in isolation (see `src/__tests__/predictive-studio-agent-context.test.ts`).\n * The shared validation/cap helpers (`validateStringParam`, `boundNameList`) come from\n * `src/shared/agent-tool-validation.ts`; the PS-specific resolver + builders live LOCAL here (same\n * convention as every other MJ surface — e.g. Lists' `lists-agent-context.ts`).\n *\n * 🔒 SAFETY BOUNDARY: every function here is read-only and side-effect-free — it only shapes context and\n * matches strings. Predictive Studio surfaces are **read / navigate-only to the agent**: the doors expose\n * ONLY section-switch / open-prediction / review / export / open-copilot tools. Model **training,\n * promotion/publish, deletion, scoring-run, \"save scores to records\", and \"send to a list\" (which creates\n * `MJ: Lists` + `MJ: List Details` rows) are DELIBERATELY NOT exposed** — those write/irreversible actions\n * stay behind the user's own clicks and the Model Development Agent's approve-gated builder. The\n * read-only boundary is restated on each door component; this module never mutates anything.\n */\n\nimport { boundNameList } from '../shared/agent-tool-validation';\n\n/** Upper bound on names published in any PS context list field; a companion `*Count` is surfaced when truncated. */\nexport const PS_AGENT_CONTEXT_NAME_LIST_CAP = 25;\n\n/** Cap a name list to {@link PS_AGENT_CONTEXT_NAME_LIST_CAP}. Pure; never mutates the input. */\nexport function capPSNames(names: readonly string[]): string[] {\n return boundNameList(names, PS_AGENT_CONTEXT_NAME_LIST_CAP);\n}\n\n/** Minimal id+name descriptor the doors hand the resolver so it can match an agent reference. */\nexport interface PSNamedRecord {\n ID: string;\n Name: string;\n}\n\n/**\n * Resolve an agent-supplied reference the way a user names things: exact ID (case-insensitive, to tolerate\n * SQL-Server-upper vs PG-lower UUID casing) → exact name (trimmed, case-insensitive) → case-insensitive\n * *contains* match on the name, but ONLY when it's unambiguous — a needle contained in several candidate\n * names returns null (silently opening the first would be silently opening the wrong one; the paired\n * {@link buildPSNotFoundError} then lists the contenders so the agent can disambiguate). Pure +\n * deterministic over the candidate list.\n */\nexport function resolvePSRecord<T extends PSNamedRecord>(input: string, candidates: readonly T[]): T | null {\n const needle = (input ?? '').trim().toLowerCase();\n if (!needle) return null;\n const byId = candidates.find((c) => c.ID.toLowerCase() === needle);\n if (byId) return byId;\n const byName = candidates.find((c) => c.Name.trim().toLowerCase() === needle);\n if (byName) return byName;\n const contains = candidates.filter((c) => c.Name.toLowerCase().includes(needle));\n return contains.length === 1 ? contains[0] : null;\n}\n\n/**\n * Tolerant \"not found\" message so the agent can self-correct: when the input partially matches SEVERAL\n * candidates it becomes a \"did you mean…\" listing exactly those; otherwise it samples available names. Pure.\n */\nexport function buildPSNotFoundError(input: string, candidates: readonly PSNamedRecord[], noun: string): string {\n const needle = (input ?? '').trim().toLowerCase();\n const contains = needle ? candidates.filter((c) => c.Name.toLowerCase().includes(needle)) : [];\n if (contains.length > 1) {\n return `\"${input}\" matches more than one ${noun} — did you mean: ${contains.slice(0, 6).map((c) => c.Name).join(', ')}? Use the exact name or ID.`;\n }\n const sample = candidates.slice(0, 6).map((c) => c.Name).join(', ');\n return `No ${noun} matching \"${input}\" is available. Available ${noun}s include: ${sample || '(none)'}.`;\n}\n\n// ============================================================================\n// PREDICTIONS door context (catalog ↔ trust-gated workspace)\n// ============================================================================\n\n/** The open prediction's salient slice, published when the workspace is showing a selection. */\nexport interface SelectedPredictionSummary {\n Name: string;\n /** Trust grade (Poor/Fair/Good/Excellent). */\n TrustGrade: string;\n /** Whether the trust gate lets the user act on this prediction. */\n CanOpen: boolean;\n}\n\n/** Component-supplied snapshot for the Predictions door. */\nexport interface PredictionsAgentContextInput {\n /** catalog (the home grid) ↔ workspace (a selected prediction). */\n View: 'catalog' | 'workspace';\n /** Total published predictions in the catalog. */\n PredictionCount: number;\n /** How many clear the trust gate (openable). */\n ReadyPredictionCount: number;\n /** Names of the catalog predictions, in display order (bounded). */\n VisiblePredictionNames: string[];\n /** Whether the \"+ New prediction\" co-pilot is open. */\n ChatOpen: boolean;\n /** The open prediction (workspace view only), else null. */\n Selected?: SelectedPredictionSummary | null;\n /** Whether the at-risk list has finished loading for the selection. */\n AtRiskLoaded?: boolean;\n /** Ranked at-risk row count (when loaded). */\n AtRiskCount?: number;\n /** Band breakdown of the at-risk rows (when loaded). */\n HighRiskCount?: number;\n MediumRiskCount?: number;\n LowRiskCount?: number;\n /** Plain-language \"what's driving this\" drivers for the selection (bounded). */\n Drivers?: string[];\n}\n\n/** Build the Predictions door context: catalog counts + bounded names, and (in workspace) the selection + at-risk breakdown. */\nexport function buildPredictionsAgentContext(input: PredictionsAgentContextInput): Record<string, unknown> {\n const context: Record<string, unknown> = {\n View: input.View,\n PredictionCount: input.PredictionCount,\n ReadyPredictionCount: input.ReadyPredictionCount,\n ChatOpen: input.ChatOpen,\n VisiblePredictionNames: capPSNames(input.VisiblePredictionNames),\n };\n if (input.VisiblePredictionNames.length > PS_AGENT_CONTEXT_NAME_LIST_CAP) {\n context['VisiblePredictionNameCount'] = input.VisiblePredictionNames.length;\n }\n if (input.View === 'workspace' && input.Selected) {\n context['SelectedPredictionName'] = input.Selected.Name;\n context['SelectedPredictionTrust'] = input.Selected.TrustGrade;\n context['SelectedPredictionCanAct'] = input.Selected.CanOpen;\n // Only surface the at-risk breakdown once loaded — never fabricate zeros before the fetch resolves.\n if (input.AtRiskLoaded) {\n context['AtRiskCount'] = input.AtRiskCount ?? 0;\n context['HighRiskCount'] = input.HighRiskCount ?? 0;\n context['MediumRiskCount'] = input.MediumRiskCount ?? 0;\n context['LowRiskCount'] = input.LowRiskCount ?? 0;\n }\n if (input.Drivers && input.Drivers.length > 0) {\n context['Drivers'] = capPSNames(input.Drivers);\n }\n }\n return context;\n}\n\n// ============================================================================\n// STUDIO door context (build/run workbench)\n// ============================================================================\n\n/** Component-supplied snapshot for the Studio door. */\nexport interface StudioAgentContextInput {\n /** Active section key (home/pipelines/catalog/experiments/compare). */\n ActiveSection: string;\n /** Active section display label. */\n ActiveSectionLabel: string;\n /** Labels of the door's sections, in nav order. */\n SectionLabels: string[];\n PublishedModelCount: number;\n RunningSessionCount: number;\n PipelineCount: number;\n AlgorithmCount: number;\n ExperimentCount: number;\n TrainingRunCount: number;\n /** Whether the docked Model Dev Agent co-pilot is open. */\n ChatOpen: boolean;\n}\n\n/** Build the Studio door context: active section + section labels + the workbench's headline counts. */\nexport function buildStudioAgentContext(input: StudioAgentContextInput): Record<string, unknown> {\n return {\n ActiveSection: input.ActiveSection,\n ActiveSectionLabel: input.ActiveSectionLabel,\n SectionLabels: capPSNames(input.SectionLabels),\n PublishedModelCount: input.PublishedModelCount,\n RunningSessionCount: input.RunningSessionCount,\n PipelineCount: input.PipelineCount,\n AlgorithmCount: input.AlgorithmCount,\n ExperimentCount: input.ExperimentCount,\n TrainingRunCount: input.TrainingRunCount,\n ChatOpen: input.ChatOpen,\n };\n}\n\n// ============================================================================\n// MODELS door context (registry + production)\n// ============================================================================\n\n/** Component-supplied snapshot for the Models door. */\nexport interface ModelsAgentContextInput {\n /** Active section key (registry/production). */\n ActiveSection: string;\n /** Active section display label. */\n ActiveSectionLabel: string;\n /** Labels of the door's sections, in nav order. */\n SectionLabels: string[];\n TotalModelCount: number;\n PublishedModelCount: number;\n DraftModelCount: number;\n /** Models with at least one scoring Record Process (live in production). */\n ProductionModelCount: number;\n}\n\n/** Build the Models door context: active section + section labels + the lifecycle counts. */\nexport function buildModelsAgentContext(input: ModelsAgentContextInput): Record<string, unknown> {\n return {\n ActiveSection: input.ActiveSection,\n ActiveSectionLabel: input.ActiveSectionLabel,\n SectionLabels: capPSNames(input.SectionLabels),\n TotalModelCount: input.TotalModelCount,\n PublishedModelCount: input.PublishedModelCount,\n DraftModelCount: input.DraftModelCount,\n ProductionModelCount: input.ProductionModelCount,\n };\n}\n"]}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * @fileoverview Pure (Angular-free) content + helpers for the Predictive Studio co-pilot experience —
3
+ * the first-run capability intro that introduces what PS can do (shown on the catalog), and the
4
+ * "Improve this" seed for a blocked prediction. The co-pilot itself opens to a clean chat (Sonar-style);
5
+ * guidance lives on the catalog cards, not in the chat.
6
+ *
7
+ * All copy here is deliberately **domain-neutral** (PS is entity-agnostic). Kept framework-free so it's
8
+ * unit-tested without the Angular runtime (see `src/__tests__/predictive-studio-copilot.test.ts`).
9
+ */
10
+ /** One capability shown on the empty catalog so a first-time user learns what PS is for. */
11
+ export interface PSCapabilityCard {
12
+ /** Font Awesome icon class. */
13
+ icon: string;
14
+ /** Short capability title. */
15
+ title: string;
16
+ /** One-line plain-language description. */
17
+ blurb: string;
18
+ }
19
+ /** The four things PS lets you do — shown as cards on the empty catalog to introduce the product. */
20
+ export declare const PS_CAPABILITY_CARDS: readonly PSCapabilityCard[];
21
+ /** Context for {@link buildImprovePrompt} — a blocked prediction the user wants to improve. */
22
+ export interface ImprovePromptInput {
23
+ /** The prediction's display name. */
24
+ name: string;
25
+ /** Its trust grade (Poor/Fair/…), when known. */
26
+ trustGrade?: string | null;
27
+ /** The plain-language reason it's blocked (trust gate / leakage), when known. */
28
+ reason?: string | null;
29
+ }
30
+ /**
31
+ * Compose the co-pilot seed for the "Improve this" action on a blocked prediction — turning the trust
32
+ * dead-end into a next step. Names the prediction + why it's held so the agent can propose concrete
33
+ * improvements (more/better data, different target framing, algorithm, more history). Pure + deterministic.
34
+ */
35
+ export declare function buildImprovePrompt(input: ImprovePromptInput): string;
36
+ //# sourceMappingURL=predictive-studio-copilot.view-models.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"predictive-studio-copilot.view-models.d.ts","sourceRoot":"","sources":["../../src/PredictiveStudio/predictive-studio-copilot.view-models.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH,4FAA4F;AAC5F,MAAM,WAAW,gBAAgB;IAC/B,+BAA+B;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,8BAA8B;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,2CAA2C;IAC3C,KAAK,EAAE,MAAM,CAAC;CACf;AAED,qGAAqG;AACrG,eAAO,MAAM,mBAAmB,EAAE,SAAS,gBAAgB,EAqB1D,CAAC;AAMF,+FAA+F;AAC/F,MAAM,WAAW,kBAAkB;IACjC,qCAAqC;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,iDAAiD;IACjD,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,iFAAiF;IACjF,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AASD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,kBAAkB,GAAG,MAAM,CAUpE"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * @fileoverview Pure (Angular-free) content + helpers for the Predictive Studio co-pilot experience —
3
+ * the first-run capability intro that introduces what PS can do (shown on the catalog), and the
4
+ * "Improve this" seed for a blocked prediction. The co-pilot itself opens to a clean chat (Sonar-style);
5
+ * guidance lives on the catalog cards, not in the chat.
6
+ *
7
+ * All copy here is deliberately **domain-neutral** (PS is entity-agnostic). Kept framework-free so it's
8
+ * unit-tested without the Angular runtime (see `src/__tests__/predictive-studio-copilot.test.ts`).
9
+ */
10
+ /** The four things PS lets you do — shown as cards on the empty catalog to introduce the product. */
11
+ export const PS_CAPABILITY_CARDS = [
12
+ {
13
+ icon: 'fa-solid fa-wand-magic-sparkles',
14
+ title: 'Build a prediction',
15
+ blurb: 'Describe what you want to know in plain words; the agent assembles the data and trains a model.',
16
+ },
17
+ {
18
+ icon: 'fa-solid fa-list-check',
19
+ title: "See who's at risk",
20
+ blurb: 'Get a ranked list of the records most likely to hit the outcome you care about.',
21
+ },
22
+ {
23
+ icon: 'fa-solid fa-lightbulb',
24
+ title: 'Understand why',
25
+ blurb: 'See the plain-language drivers behind the prediction — and behind each individual record.',
26
+ },
27
+ {
28
+ icon: 'fa-solid fa-paper-plane',
29
+ title: 'Take action',
30
+ blurb: 'Send the at-risk list to a List, export it, or save the scores back onto your records.',
31
+ },
32
+ ];
33
+ const trim = (v) => (typeof v === 'string' ? v.trim() : '');
34
+ /** Ensure a fragment ends with sentence punctuation so the composed prompt reads cleanly. */
35
+ function ensureSentence(text) {
36
+ return /[.!?]$/.test(text) ? text : `${text}.`;
37
+ }
38
+ /**
39
+ * Compose the co-pilot seed for the "Improve this" action on a blocked prediction — turning the trust
40
+ * dead-end into a next step. Names the prediction + why it's held so the agent can propose concrete
41
+ * improvements (more/better data, different target framing, algorithm, more history). Pure + deterministic.
42
+ */
43
+ export function buildImprovePrompt(input) {
44
+ const name = trim(input.name) || 'this prediction';
45
+ const grade = trim(input.trustGrade);
46
+ const reason = trim(input.reason);
47
+ const why = reason ? ` It's being held back because: ${ensureSentence(reason)}` : '';
48
+ const gradePart = grade ? ` (current trust: ${grade})` : '';
49
+ return (`The "${name}" prediction${gradePart} isn't trustworthy enough to use yet.${why} ` +
50
+ 'Help me improve it — what would make it reliable? Consider more or cleaner training data, more history, a clearer outcome definition, or a different algorithm, then rebuild it if that helps.');
51
+ }
52
+ //# sourceMappingURL=predictive-studio-copilot.view-models.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"predictive-studio-copilot.view-models.js","sourceRoot":"","sources":["../../src/PredictiveStudio/predictive-studio-copilot.view-models.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAgBH,qGAAqG;AACrG,MAAM,CAAC,MAAM,mBAAmB,GAAgC;IAC9D;QACE,IAAI,EAAE,iCAAiC;QACvC,KAAK,EAAE,oBAAoB;QAC3B,KAAK,EAAE,iGAAiG;KACzG;IACD;QACE,IAAI,EAAE,wBAAwB;QAC9B,KAAK,EAAE,mBAAmB;QAC1B,KAAK,EAAE,iFAAiF;KACzF;IACD;QACE,IAAI,EAAE,uBAAuB;QAC7B,KAAK,EAAE,gBAAgB;QACvB,KAAK,EAAE,2FAA2F;KACnG;IACD;QACE,IAAI,EAAE,yBAAyB;QAC/B,KAAK,EAAE,aAAa;QACpB,KAAK,EAAE,wFAAwF;KAChG;CACF,CAAC;AAgBF,MAAM,IAAI,GAAG,CAAC,CAA4B,EAAU,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;AAE/F,6FAA6F;AAC7F,SAAS,cAAc,CAAC,IAAY;IAClC,OAAO,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC;AACjD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,KAAyB;IAC1D,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,iBAAiB,CAAC;IACnD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;IACrC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAClC,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,CAAC,kCAAkC,cAAc,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IACrF,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,CAAC,oBAAoB,KAAK,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IAC5D,OAAO,CACL,QAAQ,IAAI,eAAe,SAAS,wCAAwC,GAAG,GAAG;QAClF,gMAAgM,CACjM,CAAC;AACJ,CAAC","sourcesContent":["/**\n * @fileoverview Pure (Angular-free) content + helpers for the Predictive Studio co-pilot experience —\n * the first-run capability intro that introduces what PS can do (shown on the catalog), and the\n * \"Improve this\" seed for a blocked prediction. The co-pilot itself opens to a clean chat (Sonar-style);\n * guidance lives on the catalog cards, not in the chat.\n *\n * All copy here is deliberately **domain-neutral** (PS is entity-agnostic). Kept framework-free so it's\n * unit-tested without the Angular runtime (see `src/__tests__/predictive-studio-copilot.test.ts`).\n */\n\n// ============================================================================\n// First-run capability intro (introduces the features of PS)\n// ============================================================================\n\n/** One capability shown on the empty catalog so a first-time user learns what PS is for. */\nexport interface PSCapabilityCard {\n /** Font Awesome icon class. */\n icon: string;\n /** Short capability title. */\n title: string;\n /** One-line plain-language description. */\n blurb: string;\n}\n\n/** The four things PS lets you do — shown as cards on the empty catalog to introduce the product. */\nexport const PS_CAPABILITY_CARDS: readonly PSCapabilityCard[] = [\n {\n icon: 'fa-solid fa-wand-magic-sparkles',\n title: 'Build a prediction',\n blurb: 'Describe what you want to know in plain words; the agent assembles the data and trains a model.',\n },\n {\n icon: 'fa-solid fa-list-check',\n title: \"See who's at risk\",\n blurb: 'Get a ranked list of the records most likely to hit the outcome you care about.',\n },\n {\n icon: 'fa-solid fa-lightbulb',\n title: 'Understand why',\n blurb: 'See the plain-language drivers behind the prediction — and behind each individual record.',\n },\n {\n icon: 'fa-solid fa-paper-plane',\n title: 'Take action',\n blurb: 'Send the at-risk list to a List, export it, or save the scores back onto your records.',\n },\n];\n\n// ============================================================================\n// \"Improve this\" seed for a blocked (not-trustworthy-enough) prediction\n// ============================================================================\n\n/** Context for {@link buildImprovePrompt} — a blocked prediction the user wants to improve. */\nexport interface ImprovePromptInput {\n /** The prediction's display name. */\n name: string;\n /** Its trust grade (Poor/Fair/…), when known. */\n trustGrade?: string | null;\n /** The plain-language reason it's blocked (trust gate / leakage), when known. */\n reason?: string | null;\n}\n\nconst trim = (v: string | null | undefined): string => (typeof v === 'string' ? v.trim() : '');\n\n/** Ensure a fragment ends with sentence punctuation so the composed prompt reads cleanly. */\nfunction ensureSentence(text: string): string {\n return /[.!?]$/.test(text) ? text : `${text}.`;\n}\n\n/**\n * Compose the co-pilot seed for the \"Improve this\" action on a blocked prediction — turning the trust\n * dead-end into a next step. Names the prediction + why it's held so the agent can propose concrete\n * improvements (more/better data, different target framing, algorithm, more history). Pure + deterministic.\n */\nexport function buildImprovePrompt(input: ImprovePromptInput): string {\n const name = trim(input.name) || 'this prediction';\n const grade = trim(input.trustGrade);\n const reason = trim(input.reason);\n const why = reason ? ` It's being held back because: ${ensureSentence(reason)}` : '';\n const gradePart = grade ? ` (current trust: ${grade})` : '';\n return (\n `The \"${name}\" prediction${gradePart} isn't trustworthy enough to use yet.${why} ` +\n 'Help me improve it — what would make it reliable? Consider more or cleaner training data, more history, a clearer outcome definition, or a different algorithm, then rebuild it if that helps.'\n );\n}\n"]}
@@ -19,11 +19,19 @@ export declare class PSModelsResourceComponent extends PSResourceBase {
19
19
  readonly sections: readonly PSSection[];
20
20
  ngOnInit(): void;
21
21
  protected OnQueryParamsChanged(params: Record<string, string>, _source: 'popstate' | 'deeplink'): void;
22
+ /** Deep agent context for the Models door: active section + trained-model lifecycle counts. */
22
23
  protected extraAgentContext(): Record<string, unknown>;
24
+ /**
25
+ * 🔒 Read/navigate-only agent tool for the Models door: switch the active section (Model Registry ↔
26
+ * Models in Production). NO promote/retire/delete/score tool is exposed — model lifecycle changes stay
27
+ * behind the user's own clicks.
28
+ */
29
+ protected registerAgentTools(): void;
23
30
  get groups(): string[];
24
31
  itemsForGroup(group: string): PSSection[];
25
32
  get activeLabel(): string;
26
- get activeIcon(): string;
33
+ /** Section-specific subtitle for the interior header. */
34
+ get activeSubtitle(): string;
27
35
  selectSection(key: PSPanelKey): void;
28
36
  static ɵfac: i0.ɵɵFactoryDeclaration<PSModelsResourceComponent, never>;
29
37
  static ɵcmp: i0.ɵɵComponentDeclaration<PSModelsResourceComponent, "mj-ps-models-resource", never, {}, {}, never, never, false, never>;
@@ -1 +1 @@
1
- {"version":3,"file":"ps-models-resource.component.d.ts","sourceRoot":"","sources":["../../../src/PredictiveStudio/resources/ps-models-resource.component.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AACxD,OAAO,EAAmB,SAAS,EAAyE,MAAM,0BAA0B,CAAC;;AAE7I;;;;;;;GAOG;AACH,qBAmDa,yBAA0B,SAAQ,cAAc;IAC3D,SAAS,CAAC,QAAQ,CAAC,UAAU,YAAY;IACzC,SAAS,CAAC,QAAQ,CAAC,YAAY,YAAY;IAC3C,SAAS,CAAC,QAAQ,CAAC,WAAW,uBAAuB;IAErD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA6B;IAE/C,aAAa,EAAE,UAAU,CAAc;IAC9C,SAAgB,QAAQ,EAAE,SAAS,SAAS,EAAE,CAAmB;IAExD,QAAQ,IAAI,IAAI;cAMN,oBAAoB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,UAAU,GAAG,UAAU,GAAG,IAAI;cAQ5F,iBAAiB,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAI/D,IAAW,MAAM,IAAI,MAAM,EAAE,CAAyC;IAC/D,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE;IAChD,IAAW,WAAW,IAAI,MAAM,CAA4D;IAC5F,IAAW,UAAU,IAAI,MAAM,CAA2D;IAEnF,aAAa,CAAC,GAAG,EAAE,UAAU,GAAG,IAAI;yCAjChC,yBAAyB;2CAAzB,yBAAyB;CAwCrC;AAED,wGAAwG;AACxG,wBAAgB,oBAAoB,IAAI,IAAI,CAE3C"}
1
+ {"version":3,"file":"ps-models-resource.component.d.ts","sourceRoot":"","sources":["../../../src/PredictiveStudio/resources/ps-models-resource.component.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AACxD,OAAO,EAAmB,SAAS,EAA4D,MAAM,0BAA0B,CAAC;;AAIhI;;;;;;;GAOG;AACH,qBAoEa,yBAA0B,SAAQ,cAAc;IAC3D,SAAS,CAAC,QAAQ,CAAC,UAAU,YAAY;IACzC,SAAS,CAAC,QAAQ,CAAC,YAAY,YAAY;IAC3C,SAAS,CAAC,QAAQ,CAAC,WAAW,uBAAuB;IAErD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA6B;IAE/C,aAAa,EAAE,UAAU,CAAc;IAC9C,SAAgB,QAAQ,EAAE,SAAS,SAAS,EAAE,CAAmB;IAExD,QAAQ,IAAI,IAAI;cAMN,oBAAoB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,UAAU,GAAG,UAAU,GAAG,IAAI;IAQ/G,+FAA+F;cAC5E,iBAAiB,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAa/D;;;;OAIG;cACgB,kBAAkB,IAAI,IAAI;IAmB7C,IAAW,MAAM,IAAI,MAAM,EAAE,CAAyC;IAC/D,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE;IAChD,IAAW,WAAW,IAAI,MAAM,CAA4D;IAE5F,yDAAyD;IACzD,IAAW,cAAc,IAAI,MAAM,CAMlC;IAEM,aAAa,CAAC,GAAG,EAAE,UAAU,GAAG,IAAI;yCA3EhC,yBAAyB;2CAAzB,yBAAyB;CAkFrC;AAED,wGAAwG;AACxG,wBAAgB,oBAAoB,IAAI,IAAI,CAE3C"}
@@ -8,7 +8,9 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, inject } from '@
8
8
  import { RegisterClass } from '@memberjunction/global';
9
9
  import { BaseResourceComponent } from '@memberjunction/ng-shared';
10
10
  import { PSResourceBase } from './ps-resource-base';
11
- import { MODELS_SECTIONS, sectionGroups, sectionsInGroup, sectionLabel, sectionIcon, hasSection } from '../predictive-studio.nav';
11
+ import { MODELS_SECTIONS, sectionGroups, sectionsInGroup, sectionLabel, hasSection } from '../predictive-studio.nav';
12
+ import { buildModelsAgentContext, resolvePSRecord, buildPSNotFoundError } from '../predictive-studio-agent-context';
13
+ import { validateStringParam } from '../../shared/agent-tool-validation';
12
14
  import * as i0 from "@angular/core";
13
15
  import * as i1 from "@memberjunction/ng-ui-components";
14
16
  import * as i2 from "@memberjunction/ng-shared-generic";
@@ -16,73 +18,96 @@ import * as i3 from "../components/ps-registry.component";
16
18
  import * as i4 from "../components/ps-production.component";
17
19
  const _forTrack0 = ($index, $item) => $item.key;
18
20
  function PSModelsResourceComponent_Conditional_2_Template(rf, ctx) { if (rf & 1) {
19
- i0.ɵɵelement(0, "mj-loading", 1);
21
+ i0.ɵɵelement(0, "mj-loading", 2);
20
22
  } }
21
- function PSModelsResourceComponent_Conditional_3_For_3_Conditional_0_Template(rf, ctx) { if (rf & 1) {
22
- i0.ɵɵelementStart(0, "div", 7);
23
+ function PSModelsResourceComponent_Conditional_3_Template(rf, ctx) { if (rf & 1) {
24
+ const _r1 = i0.ɵɵgetCurrentView();
25
+ i0.ɵɵelementStart(0, "div", 3);
26
+ i0.ɵɵelement(1, "i", 5);
27
+ i0.ɵɵelementStart(2, "div", 6)(3, "strong");
28
+ i0.ɵɵtext(4);
29
+ i0.ɵɵelementEnd();
30
+ i0.ɵɵelementStart(5, "span", 7);
31
+ i0.ɵɵtext(6);
32
+ i0.ɵɵelementEnd()();
33
+ i0.ɵɵelementStart(7, "button", 8);
34
+ i0.ɵɵlistener("click", function PSModelsResourceComponent_Conditional_3_Template_button_click_7_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.retryLoad()); });
35
+ i0.ɵɵelement(8, "i", 9);
36
+ i0.ɵɵtext(9, " Try again");
37
+ i0.ɵɵelementEnd()();
38
+ } if (rf & 2) {
39
+ const ctx_r1 = i0.ɵɵnextContext();
40
+ i0.ɵɵadvance(4);
41
+ i0.ɵɵtextInterpolate1("Couldn't load ", ctx_r1.sectionTitle);
42
+ i0.ɵɵadvance(2);
43
+ i0.ɵɵtextInterpolate(ctx_r1.loadError);
44
+ } }
45
+ function PSModelsResourceComponent_Conditional_4_For_3_Conditional_0_Template(rf, ctx) { if (rf & 1) {
46
+ i0.ɵɵelementStart(0, "div", 14);
23
47
  i0.ɵɵtext(1);
24
48
  i0.ɵɵelementEnd();
25
49
  } if (rf & 2) {
26
- const group_r1 = i0.ɵɵnextContext().$implicit;
50
+ const group_r3 = i0.ɵɵnextContext().$implicit;
27
51
  i0.ɵɵadvance();
28
- i0.ɵɵtextInterpolate(group_r1);
52
+ i0.ɵɵtextInterpolate(group_r3);
29
53
  } }
30
- function PSModelsResourceComponent_Conditional_3_For_3_For_2_Template(rf, ctx) { if (rf & 1) {
31
- const _r2 = i0.ɵɵgetCurrentView();
32
- i0.ɵɵelementStart(0, "button", 9);
33
- i0.ɵɵlistener("click", function PSModelsResourceComponent_Conditional_3_For_3_For_2_Template_button_click_0_listener() { const item_r3 = i0.ɵɵrestoreView(_r2).$implicit; const ctx_r3 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r3.selectSection(item_r3.key)); });
54
+ function PSModelsResourceComponent_Conditional_4_For_3_For_2_Template(rf, ctx) { if (rf & 1) {
55
+ const _r4 = i0.ɵɵgetCurrentView();
56
+ i0.ɵɵelementStart(0, "button", 16);
57
+ i0.ɵɵlistener("click", function PSModelsResourceComponent_Conditional_4_For_3_For_2_Template_button_click_0_listener() { const item_r5 = i0.ɵɵrestoreView(_r4).$implicit; const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.selectSection(item_r5.key)); });
34
58
  i0.ɵɵelement(1, "i");
35
59
  i0.ɵɵelementStart(2, "span");
36
60
  i0.ɵɵtext(3);
37
61
  i0.ɵɵelementEnd()();
38
62
  } if (rf & 2) {
39
- const item_r3 = ctx.$implicit;
40
- const ctx_r3 = i0.ɵɵnextContext(3);
41
- i0.ɵɵclassProp("active", ctx_r3.activeSection === item_r3.key);
42
- i0.ɵɵattribute("data-testid", "ps-nav-" + item_r3.key);
63
+ const item_r5 = ctx.$implicit;
64
+ const ctx_r1 = i0.ɵɵnextContext(3);
65
+ i0.ɵɵclassProp("active", ctx_r1.activeSection === item_r5.key);
66
+ i0.ɵɵattribute("data-testid", "ps-nav-" + item_r5.key);
43
67
  i0.ɵɵadvance();
44
- i0.ɵɵclassMap(item_r3.icon);
68
+ i0.ɵɵclassMap(item_r5.icon);
45
69
  i0.ɵɵadvance(2);
46
- i0.ɵɵtextInterpolate(item_r3.label);
70
+ i0.ɵɵtextInterpolate(item_r5.label);
47
71
  } }
48
- function PSModelsResourceComponent_Conditional_3_For_3_Template(rf, ctx) { if (rf & 1) {
49
- i0.ɵɵconditionalCreate(0, PSModelsResourceComponent_Conditional_3_For_3_Conditional_0_Template, 2, 1, "div", 7);
50
- i0.ɵɵrepeaterCreate(1, PSModelsResourceComponent_Conditional_3_For_3_For_2_Template, 4, 6, "button", 8, _forTrack0);
72
+ function PSModelsResourceComponent_Conditional_4_For_3_Template(rf, ctx) { if (rf & 1) {
73
+ i0.ɵɵconditionalCreate(0, PSModelsResourceComponent_Conditional_4_For_3_Conditional_0_Template, 2, 1, "div", 14);
74
+ i0.ɵɵrepeaterCreate(1, PSModelsResourceComponent_Conditional_4_For_3_For_2_Template, 4, 6, "button", 15, _forTrack0);
51
75
  } if (rf & 2) {
52
- const group_r1 = ctx.$implicit;
53
- const ctx_r3 = i0.ɵɵnextContext(2);
54
- i0.ɵɵconditional(group_r1 ? 0 : -1);
76
+ const group_r3 = ctx.$implicit;
77
+ const ctx_r1 = i0.ɵɵnextContext(2);
78
+ i0.ɵɵconditional(group_r3 ? 0 : -1);
55
79
  i0.ɵɵadvance();
56
- i0.ɵɵrepeater(ctx_r3.itemsForGroup(group_r1));
80
+ i0.ɵɵrepeater(ctx_r1.itemsForGroup(group_r3));
57
81
  } }
58
- function PSModelsResourceComponent_Conditional_3_Case_5_Template(rf, ctx) { if (rf & 1) {
59
- i0.ɵɵelement(0, "ps-registry", 5);
82
+ function PSModelsResourceComponent_Conditional_4_Case_5_Template(rf, ctx) { if (rf & 1) {
83
+ i0.ɵɵelement(0, "ps-registry", 12);
60
84
  } if (rf & 2) {
61
- const ctx_r3 = i0.ɵɵnextContext(2);
62
- i0.ɵɵproperty("engine", ctx_r3.engine)("provider", ctx_r3.ProviderToUse)("currentUser", ctx_r3.ProviderToUse.CurrentUser);
85
+ const ctx_r1 = i0.ɵɵnextContext(2);
86
+ i0.ɵɵproperty("engine", ctx_r1.engine)("provider", ctx_r1.ProviderToUse)("currentUser", ctx_r1.ProviderToUse.CurrentUser);
63
87
  } }
64
- function PSModelsResourceComponent_Conditional_3_Case_6_Template(rf, ctx) { if (rf & 1) {
65
- i0.ɵɵelement(0, "ps-production", 6);
88
+ function PSModelsResourceComponent_Conditional_4_Case_6_Template(rf, ctx) { if (rf & 1) {
89
+ i0.ɵɵelement(0, "ps-production", 13);
66
90
  } if (rf & 2) {
67
- const ctx_r3 = i0.ɵɵnextContext(2);
68
- i0.ɵɵproperty("engine", ctx_r3.engine);
91
+ const ctx_r1 = i0.ɵɵnextContext(2);
92
+ i0.ɵɵproperty("engine", ctx_r1.engine);
69
93
  } }
70
- function PSModelsResourceComponent_Conditional_3_Template(rf, ctx) { if (rf & 1) {
71
- i0.ɵɵelementStart(0, "div", 2)(1, "aside", 3);
72
- i0.ɵɵrepeaterCreate(2, PSModelsResourceComponent_Conditional_3_For_3_Template, 3, 1, null, null, i0.ɵɵrepeaterTrackByIdentity);
94
+ function PSModelsResourceComponent_Conditional_4_Template(rf, ctx) { if (rf & 1) {
95
+ i0.ɵɵelementStart(0, "div", 4)(1, "aside", 10);
96
+ i0.ɵɵrepeaterCreate(2, PSModelsResourceComponent_Conditional_4_For_3_Template, 3, 1, null, null, i0.ɵɵrepeaterTrackByIdentity);
73
97
  i0.ɵɵelementEnd();
74
- i0.ɵɵelementStart(4, "section", 4);
75
- i0.ɵɵconditionalCreate(5, PSModelsResourceComponent_Conditional_3_Case_5_Template, 1, 3, "ps-registry", 5)(6, PSModelsResourceComponent_Conditional_3_Case_6_Template, 1, 1, "ps-production", 6);
98
+ i0.ɵɵelementStart(4, "section", 11);
99
+ i0.ɵɵconditionalCreate(5, PSModelsResourceComponent_Conditional_4_Case_5_Template, 1, 3, "ps-registry", 12)(6, PSModelsResourceComponent_Conditional_4_Case_6_Template, 1, 1, "ps-production", 13);
76
100
  i0.ɵɵelementEnd()();
77
101
  } if (rf & 2) {
78
- let tmp_3_0;
79
- const ctx_r3 = i0.ɵɵnextContext();
102
+ let tmp_4_0;
103
+ const ctx_r1 = i0.ɵɵnextContext();
80
104
  i0.ɵɵadvance(2);
81
- i0.ɵɵrepeater(ctx_r3.groups);
105
+ i0.ɵɵrepeater(ctx_r1.groups);
82
106
  i0.ɵɵadvance(2);
83
- i0.ɵɵattribute("data-testid", "ps-panel-" + ctx_r3.activeSection);
107
+ i0.ɵɵclassProp("fill", ctx_r1.activeSection === "registry");
108
+ i0.ɵɵattribute("data-testid", "ps-panel-" + ctx_r1.activeSection);
84
109
  i0.ɵɵadvance();
85
- i0.ɵɵconditional((tmp_3_0 = ctx_r3.activeSection) === "registry" ? 5 : tmp_3_0 === "production" ? 6 : -1);
110
+ i0.ɵɵconditional((tmp_4_0 = ctx_r1.activeSection) === "registry" ? 5 : tmp_4_0 === "production" ? 6 : -1);
86
111
  } }
87
112
  /**
88
113
  * **Models** — the trained-model lifecycle door (one of Predictive Studio's three consolidated nav
@@ -112,13 +137,55 @@ let PSModelsResourceComponent = class PSModelsResourceComponent extends PSResour
112
137
  this.cdrLocal.detectChanges();
113
138
  }
114
139
  }
140
+ /** Deep agent context for the Models door: active section + trained-model lifecycle counts. */
115
141
  extraAgentContext() {
116
- return { ActiveSection: sectionLabel(this.sections, this.activeSection) };
142
+ const models = this.engine.Models;
143
+ return buildModelsAgentContext({
144
+ ActiveSection: this.activeSection,
145
+ ActiveSectionLabel: this.activeLabel,
146
+ SectionLabels: this.sections.map((s) => s.label),
147
+ TotalModelCount: models.length,
148
+ PublishedModelCount: this.engine.PublishedModels.length,
149
+ DraftModelCount: models.filter((m) => m.Status === 'Draft').length,
150
+ ProductionModelCount: models.filter((m) => this.engine.RecordProcessIDsForModel(m.ID).length > 0).length,
151
+ });
152
+ }
153
+ /**
154
+ * 🔒 Read/navigate-only agent tool for the Models door: switch the active section (Model Registry ↔
155
+ * Models in Production). NO promote/retire/delete/score tool is exposed — model lifecycle changes stay
156
+ * behind the user's own clicks.
157
+ */
158
+ registerAgentTools() {
159
+ this.navigationService.SetAgentClientTools(this, [
160
+ {
161
+ Name: 'SwitchModelsSection',
162
+ Description: 'Switch the Models door to a section. Pass the section key or label (see SectionLabels): Model Registry or Models in Production.',
163
+ ParameterSchema: { type: 'object', properties: { section: { type: 'string', description: 'The section key or label to switch to' } } },
164
+ Handler: async (params) => {
165
+ const check = validateStringParam(params['section'], 'section');
166
+ if (!check.ok)
167
+ return check.result;
168
+ const candidates = this.sections.map((s) => ({ ID: s.key, Name: s.label }));
169
+ const match = resolvePSRecord(check.value, candidates);
170
+ if (!match)
171
+ return { Success: false, ErrorMessage: buildPSNotFoundError(check.value, candidates, 'section') };
172
+ this.selectSection(match.ID);
173
+ return { Success: true, Data: { activeSection: match.Name } };
174
+ },
175
+ },
176
+ ]);
117
177
  }
118
178
  get groups() { return sectionGroups(this.sections); }
119
179
  itemsForGroup(group) { return sectionsInGroup(this.sections, group); }
120
180
  get activeLabel() { return sectionLabel(this.sections, this.activeSection); }
121
- get activeIcon() { return sectionIcon(this.sections, this.activeSection); }
181
+ /** Section-specific subtitle for the interior header. */
182
+ get activeSubtitle() {
183
+ const map = {
184
+ registry: 'Versioned trained models, their metrics, and lineage.',
185
+ production: "What's scoring live, and its recent runs.",
186
+ };
187
+ return map[this.activeSection] ?? '';
188
+ }
122
189
  selectSection(key) {
123
190
  if (this.activeSection === key)
124
191
  return;
@@ -128,16 +195,18 @@ let PSModelsResourceComponent = class PSModelsResourceComponent extends PSResour
128
195
  this.cdrLocal.detectChanges();
129
196
  }
130
197
  static ɵfac = /*@__PURE__*/ (() => { let ɵPSModelsResourceComponent_BaseFactory; return function PSModelsResourceComponent_Factory(__ngFactoryType__) { return (ɵPSModelsResourceComponent_BaseFactory || (ɵPSModelsResourceComponent_BaseFactory = i0.ɵɵgetInheritedFactory(PSModelsResourceComponent)))(__ngFactoryType__ || PSModelsResourceComponent); }; })();
131
- static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: PSModelsResourceComponent, selectors: [["mj-ps-models-resource"]], standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 4, vars: 2, consts: [["Subtitle", "Versioned trained models and what's live in production", 3, "Title"], ["text", "Loading Models\u2026", "size", "medium"], ["data-testid", "ps-models-shell", 1, "ps-models-host"], [1, "ps-leftnav"], [1, "ps-content"], [3, "engine", "provider", "currentUser"], [3, "engine"], [1, "ps-nav-group"], [1, "ps-nav-item", 3, "active"], [1, "ps-nav-item", 3, "click"]], template: function PSModelsResourceComponent_Template(rf, ctx) { if (rf & 1) {
198
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: PSModelsResourceComponent, selectors: [["mj-ps-models-resource"]], standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 5, vars: 5, consts: [[3, "Title", "Subtitle"], [3, "Flex", "Padding"], ["text", "Loading Models\u2026", "size", "medium"], ["data-testid", "ps-load-error", "role", "alert", 1, "ps-load-error"], ["data-testid", "ps-models-shell", 1, "ps-models-host"], [1, "fa-solid", "fa-triangle-exclamation"], [1, "ps-load-error-text"], [1, "ps-load-error-detail"], ["mjButton", "", "variant", "secondary", "size", "sm", 3, "click"], [1, "fa-solid", "fa-rotate-right"], [1, "ps-leftnav"], [1, "ps-content"], [3, "engine", "provider", "currentUser"], [3, "engine"], [1, "ps-nav-group"], [1, "ps-nav-item", 3, "active"], [1, "ps-nav-item", 3, "click"]], template: function PSModelsResourceComponent_Template(rf, ctx) { if (rf & 1) {
132
199
  i0.ɵɵelement(0, "mj-page-header-interior", 0);
133
- i0.ɵɵelementStart(1, "mj-page-body-interior");
134
- i0.ɵɵconditionalCreate(2, PSModelsResourceComponent_Conditional_2_Template, 1, 0, "mj-loading", 1)(3, PSModelsResourceComponent_Conditional_3_Template, 7, 2, "div", 2);
200
+ i0.ɵɵelementStart(1, "mj-page-body-interior", 1);
201
+ i0.ɵɵconditionalCreate(2, PSModelsResourceComponent_Conditional_2_Template, 1, 0, "mj-loading", 2)(3, PSModelsResourceComponent_Conditional_3_Template, 10, 2, "div", 3)(4, PSModelsResourceComponent_Conditional_4_Template, 7, 4, "div", 4);
135
202
  i0.ɵɵelementEnd();
136
203
  } if (rf & 2) {
137
- i0.ɵɵproperty("Title", ctx.activeLabel);
138
- i0.ɵɵadvance(2);
139
- i0.ɵɵconditional(ctx.isLoading ? 2 : 3);
140
- } }, dependencies: [i1.MJPageHeaderInteriorComponent, i1.MJPageBodyInteriorComponent, i2.LoadingComponent, i3.PSRegistryComponent, i4.PSProductionComponent], styles: ["[_nghost-%COMP%] { display: flex; flex-direction: column; width: 100%; height: 100%; min-height: 0; }\n .ps-models-host[_ngcontent-%COMP%] { display: flex; flex: 1; min-height: 0; overflow: hidden; }\n .ps-leftnav[_ngcontent-%COMP%] { width: 210px; flex: none; border-right: 1px solid var(--mj-border-default); background: var(--mj-bg-surface-card); overflow-y: auto; padding: 10px 8px; display: flex; flex-direction: column; gap: 2px; }\n .ps-nav-group[_ngcontent-%COMP%] { font-size: var(--mj-text-xs); font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: var(--mj-text-muted); padding: 12px 10px 4px; }\n .ps-nav-item[_ngcontent-%COMP%] { display: flex; align-items: center; gap: 10px; width: 100%; text-align: left; padding: 8px 10px; border: none; background: transparent; border-radius: var(--mj-radius-md); cursor: pointer; color: var(--mj-text-secondary); font-size: var(--mj-text-sm); font-weight: 500; transition: background .12s, color .12s; }\n .ps-nav-item[_ngcontent-%COMP%] i[_ngcontent-%COMP%] { width: 18px; text-align: center; color: var(--mj-text-muted); }\n .ps-nav-item[_ngcontent-%COMP%]:hover { background: var(--mj-bg-surface-hover); color: var(--mj-text-primary); }\n .ps-nav-item.active[_ngcontent-%COMP%] { background: color-mix(in srgb, var(--mj-brand-primary) 12%, transparent); color: var(--mj-brand-primary); font-weight: 600; }\n .ps-nav-item.active[_ngcontent-%COMP%] i[_ngcontent-%COMP%] { color: var(--mj-brand-primary); }\n .ps-content[_ngcontent-%COMP%] { flex: 1; min-width: 0; overflow-y: auto; padding: 8px 14px 24px; }"], changeDetection: 0 });
204
+ i0.ɵɵproperty("Title", ctx.activeLabel)("Subtitle", ctx.activeSubtitle);
205
+ i0.ɵɵadvance();
206
+ i0.ɵɵproperty("Flex", true)("Padding", false);
207
+ i0.ɵɵadvance();
208
+ i0.ɵɵconditional(ctx.isLoading ? 2 : ctx.loadError ? 3 : 4);
209
+ } }, dependencies: [i1.MJButtonDirective, i1.MJPageHeaderInteriorComponent, i1.MJPageBodyInteriorComponent, i2.LoadingComponent, i3.PSRegistryComponent, i4.PSProductionComponent], styles: ["[_nghost-%COMP%] { display: flex; flex-direction: column; width: 100%; height: 100%; min-height: 0; }\n .ps-models-host[_ngcontent-%COMP%] { display: flex; flex: 1; min-height: 0; overflow: hidden; }\n .ps-leftnav[_ngcontent-%COMP%] { width: 210px; flex: none; border-right: 1px solid var(--mj-border-default); background: var(--mj-bg-surface-card); overflow-y: auto; padding: 10px 8px; display: flex; flex-direction: column; gap: 2px; }\n .ps-nav-group[_ngcontent-%COMP%] { font-size: var(--mj-text-xs); font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: var(--mj-text-muted); padding: 12px 10px 4px; }\n .ps-nav-item[_ngcontent-%COMP%] { display: flex; align-items: center; gap: 10px; width: 100%; text-align: left; padding: 8px 10px; border: none; background: transparent; border-radius: var(--mj-radius-md); cursor: pointer; color: var(--mj-text-secondary); font-size: var(--mj-text-sm); font-weight: 500; transition: background .12s, color .12s; }\n .ps-nav-item[_ngcontent-%COMP%] i[_ngcontent-%COMP%] { width: 18px; text-align: center; color: var(--mj-text-muted); }\n .ps-nav-item[_ngcontent-%COMP%]:hover { background: var(--mj-bg-surface-hover); color: var(--mj-text-primary); }\n .ps-nav-item.active[_ngcontent-%COMP%] { background: color-mix(in srgb, var(--mj-brand-primary) 12%, transparent); color: var(--mj-brand-primary); font-weight: 600; }\n .ps-nav-item.active[_ngcontent-%COMP%] i[_ngcontent-%COMP%] { color: var(--mj-brand-primary); }\n .ps-content[_ngcontent-%COMP%] { flex: 1; min-width: 0; overflow-y: auto; padding: 8px 14px 24px; }\n \n\n\n .ps-content.fill[_ngcontent-%COMP%] { overflow: hidden; display: flex; flex-direction: column; padding-bottom: 14px; }\n .ps-content.fill[_ngcontent-%COMP%] > *[_ngcontent-%COMP%] { flex: 1; min-height: 0; display: flex; flex-direction: column; }\n .ps-load-error[_ngcontent-%COMP%] { display: flex; align-items: center; gap: 14px; max-width: 620px; margin: 32px auto; padding: 18px 20px; border: 1px solid var(--mj-status-error-border); background: var(--mj-status-error-bg); border-radius: var(--mj-radius-lg); }\n .ps-load-error[_ngcontent-%COMP%] > i[_ngcontent-%COMP%] { font-size: 24px; color: var(--mj-status-error); }\n .ps-load-error-text[_ngcontent-%COMP%] { display: flex; flex-direction: column; gap: 2px; flex: 1; min-width: 0; }\n .ps-load-error-text[_ngcontent-%COMP%] strong[_ngcontent-%COMP%] { color: var(--mj-text-primary); }\n .ps-load-error-detail[_ngcontent-%COMP%] { color: var(--mj-text-secondary); font-size: var(--mj-text-sm); word-break: break-word; }"], changeDetection: 0 });
141
210
  };
142
211
  PSModelsResourceComponent = __decorate([
143
212
  RegisterClass(BaseResourceComponent, 'PredictiveStudioModelsResource')
@@ -146,12 +215,20 @@ export { PSModelsResourceComponent };
146
215
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(PSModelsResourceComponent, [{
147
216
  type: Component,
148
217
  args: [{ standalone: false, selector: 'mj-ps-models-resource', template: `
149
- <mj-page-header-interior [Title]="activeLabel"
150
- Subtitle="Versioned trained models and what's live in production">
218
+ <mj-page-header-interior [Title]="activeLabel" [Subtitle]="activeSubtitle">
151
219
  </mj-page-header-interior>
152
- <mj-page-body-interior>
220
+ <mj-page-body-interior [Flex]="true" [Padding]="false">
153
221
  @if (isLoading) {
154
222
  <mj-loading text="Loading Models…" size="medium"></mj-loading>
223
+ } @else if (loadError) {
224
+ <div class="ps-load-error" data-testid="ps-load-error" role="alert">
225
+ <i class="fa-solid fa-triangle-exclamation"></i>
226
+ <div class="ps-load-error-text">
227
+ <strong>Couldn't load {{ sectionTitle }}</strong>
228
+ <span class="ps-load-error-detail">{{ loadError }}</span>
229
+ </div>
230
+ <button mjButton variant="secondary" size="sm" (click)="retryLoad()"><i class="fa-solid fa-rotate-right"></i> Try again</button>
231
+ </div>
155
232
  } @else {
156
233
  <div class="ps-models-host" data-testid="ps-models-shell">
157
234
  <aside class="ps-leftnav">
@@ -166,7 +243,7 @@ export { PSModelsResourceComponent };
166
243
  }
167
244
  </aside>
168
245
 
169
- <section class="ps-content" [attr.data-testid]="'ps-panel-' + activeSection">
246
+ <section class="ps-content" [class.fill]="activeSection === 'registry'" [attr.data-testid]="'ps-panel-' + activeSection">
170
247
  @switch (activeSection) {
171
248
  @case ('registry') { <ps-registry [engine]="engine" [provider]="ProviderToUse" [currentUser]="ProviderToUse.CurrentUser"></ps-registry> }
172
249
  @case ('production') { <ps-production [engine]="engine"></ps-production> }
@@ -175,9 +252,9 @@ export { PSModelsResourceComponent };
175
252
  </div>
176
253
  }
177
254
  </mj-page-body-interior>
178
- `, changeDetection: ChangeDetectionStrategy.OnPush, styles: ["\n :host { display: flex; flex-direction: column; width: 100%; height: 100%; min-height: 0; }\n .ps-models-host { display: flex; flex: 1; min-height: 0; overflow: hidden; }\n .ps-leftnav { width: 210px; flex: none; border-right: 1px solid var(--mj-border-default); background: var(--mj-bg-surface-card); overflow-y: auto; padding: 10px 8px; display: flex; flex-direction: column; gap: 2px; }\n .ps-nav-group { font-size: var(--mj-text-xs); font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: var(--mj-text-muted); padding: 12px 10px 4px; }\n .ps-nav-item { display: flex; align-items: center; gap: 10px; width: 100%; text-align: left; padding: 8px 10px; border: none; background: transparent; border-radius: var(--mj-radius-md); cursor: pointer; color: var(--mj-text-secondary); font-size: var(--mj-text-sm); font-weight: 500; transition: background .12s, color .12s; }\n .ps-nav-item i { width: 18px; text-align: center; color: var(--mj-text-muted); }\n .ps-nav-item:hover { background: var(--mj-bg-surface-hover); color: var(--mj-text-primary); }\n .ps-nav-item.active { background: color-mix(in srgb, var(--mj-brand-primary) 12%, transparent); color: var(--mj-brand-primary); font-weight: 600; }\n .ps-nav-item.active i { color: var(--mj-brand-primary); }\n .ps-content { flex: 1; min-width: 0; overflow-y: auto; padding: 8px 14px 24px; }\n "] }]
255
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: ["\n :host { display: flex; flex-direction: column; width: 100%; height: 100%; min-height: 0; }\n .ps-models-host { display: flex; flex: 1; min-height: 0; overflow: hidden; }\n .ps-leftnav { width: 210px; flex: none; border-right: 1px solid var(--mj-border-default); background: var(--mj-bg-surface-card); overflow-y: auto; padding: 10px 8px; display: flex; flex-direction: column; gap: 2px; }\n .ps-nav-group { font-size: var(--mj-text-xs); font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: var(--mj-text-muted); padding: 12px 10px 4px; }\n .ps-nav-item { display: flex; align-items: center; gap: 10px; width: 100%; text-align: left; padding: 8px 10px; border: none; background: transparent; border-radius: var(--mj-radius-md); cursor: pointer; color: var(--mj-text-secondary); font-size: var(--mj-text-sm); font-weight: 500; transition: background .12s, color .12s; }\n .ps-nav-item i { width: 18px; text-align: center; color: var(--mj-text-muted); }\n .ps-nav-item:hover { background: var(--mj-bg-surface-hover); color: var(--mj-text-primary); }\n .ps-nav-item.active { background: color-mix(in srgb, var(--mj-brand-primary) 12%, transparent); color: var(--mj-brand-primary); font-weight: 600; }\n .ps-nav-item.active i { color: var(--mj-brand-primary); }\n .ps-content { flex: 1; min-width: 0; overflow-y: auto; padding: 8px 14px 24px; }\n /* Fill mode (registry): the section stops page-scrolling so the panel's inner\n columns (model list / detail) can each own their scrollbar. */\n .ps-content.fill { overflow: hidden; display: flex; flex-direction: column; padding-bottom: 14px; }\n .ps-content.fill > * { flex: 1; min-height: 0; display: flex; flex-direction: column; }\n .ps-load-error { display: flex; align-items: center; gap: 14px; max-width: 620px; margin: 32px auto; padding: 18px 20px; border: 1px solid var(--mj-status-error-border); background: var(--mj-status-error-bg); border-radius: var(--mj-radius-lg); }\n .ps-load-error > i { font-size: 24px; color: var(--mj-status-error); }\n .ps-load-error-text { display: flex; flex-direction: column; gap: 2px; flex: 1; min-width: 0; }\n .ps-load-error-text strong { color: var(--mj-text-primary); }\n .ps-load-error-detail { color: var(--mj-text-secondary); font-size: var(--mj-text-sm); word-break: break-word; }\n "] }]
179
256
  }], null, null); })();
180
- (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(PSModelsResourceComponent, { className: "PSModelsResourceComponent", filePath: "src/PredictiveStudio/resources/ps-models-resource.component.ts", lineNumber: 67 }); })();
257
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(PSModelsResourceComponent, { className: "PSModelsResourceComponent", filePath: "src/PredictiveStudio/resources/ps-models-resource.component.ts", lineNumber: 86 }); })();
181
258
  /** Tree-shaking prevention — called from the subpath module so the @RegisterClass survives bundling. */
182
259
  export function LoadPSModelsResource() {
183
260
  // intentionally empty