@memberjunction/ng-core-entity-forms 6.2.1 → 6.3.0-edge.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -6
- package/dist/lib/custom/AIAgentCategories/ai-agent-category-header.panel.d.ts.map +1 -1
- package/dist/lib/custom/AIAgentCategories/ai-agent-category-header.panel.js +3 -3
- package/dist/lib/custom/AIAgentCategories/ai-agent-category-header.panel.js.map +1 -1
- package/dist/lib/custom/AIAgentSessions/ai-agent-session-form.component.d.ts +12 -0
- package/dist/lib/custom/AIAgentSessions/ai-agent-session-form.component.d.ts.map +1 -1
- package/dist/lib/custom/AIAgentSessions/ai-agent-session-form.component.js +293 -230
- package/dist/lib/custom/AIAgentSessions/ai-agent-session-form.component.js.map +1 -1
- package/dist/lib/custom/AIAgents/add-action-dialog.component.js +2 -2
- package/dist/lib/custom/AIAgents/agent-prompt-advanced-settings-dialog.component.js +2 -2
- package/dist/lib/custom/AIAgents/ai-agent-form.component.d.ts +11 -0
- package/dist/lib/custom/AIAgents/ai-agent-form.component.d.ts.map +1 -1
- package/dist/lib/custom/AIAgents/ai-agent-form.component.js +862 -812
- package/dist/lib/custom/AIAgents/ai-agent-form.component.js.map +1 -1
- package/dist/lib/custom/AIAgents/create-prompt-dialog.component.js +2 -2
- package/dist/lib/custom/AIAgents/create-sub-agent-dialog.component.js +2 -2
- package/dist/lib/custom/AIAgents/new-agent-dialog.component.js +2 -2
- package/dist/lib/custom/AIAgents/prompt-selector-dialog.component.js +2 -2
- package/dist/lib/custom/AIAgents/sub-agent-advanced-settings-dialog.component.js +2 -2
- package/dist/lib/custom/AIAgents/sub-agent-selector-dialog.component.js +2 -2
- package/dist/lib/custom/AIPromptRuns/ai-prompt-run-form.component.d.ts +12 -0
- package/dist/lib/custom/AIPromptRuns/ai-prompt-run-form.component.d.ts.map +1 -1
- package/dist/lib/custom/AIPromptRuns/ai-prompt-run-form.component.js +821 -763
- package/dist/lib/custom/AIPromptRuns/ai-prompt-run-form.component.js.map +1 -1
- package/dist/lib/custom/AIPromptRuns/chat-message-viewer.component.js +2 -2
- package/dist/lib/custom/AIPrompts/ai-prompt-form.component.d.ts +10 -0
- package/dist/lib/custom/AIPrompts/ai-prompt-form.component.d.ts.map +1 -1
- package/dist/lib/custom/AIPrompts/ai-prompt-form.component.js +700 -605
- package/dist/lib/custom/AIPrompts/ai-prompt-form.component.js.map +1 -1
- package/dist/lib/custom/Actions/action-execution-log-form.component.d.ts +10 -0
- package/dist/lib/custom/Actions/action-execution-log-form.component.d.ts.map +1 -1
- package/dist/lib/custom/Actions/action-execution-log-form.component.js +223 -175
- package/dist/lib/custom/Actions/action-execution-log-form.component.js.map +1 -1
- package/dist/lib/custom/Actions/action-form.component.d.ts +10 -0
- package/dist/lib/custom/Actions/action-form.component.d.ts.map +1 -1
- package/dist/lib/custom/Actions/action-form.component.js +502 -453
- package/dist/lib/custom/Actions/action-form.component.js.map +1 -1
- package/dist/lib/custom/Companies/company-header.panel.d.ts.map +1 -1
- package/dist/lib/custom/Companies/company-header.panel.js +3 -3
- package/dist/lib/custom/Companies/company-header.panel.js.map +1 -1
- package/dist/lib/custom/Conversations/conversation-header.panel.d.ts.map +1 -1
- package/dist/lib/custom/Conversations/conversation-header.panel.js +3 -3
- package/dist/lib/custom/Conversations/conversation-header.panel.js.map +1 -1
- package/dist/lib/custom/Employees/employee-header.panel.d.ts.map +1 -1
- package/dist/lib/custom/Employees/employee-header.panel.js +3 -3
- package/dist/lib/custom/Employees/employee-header.panel.js.map +1 -1
- package/dist/lib/custom/Entities/entity-form.component.d.ts +15 -0
- package/dist/lib/custom/Entities/entity-form.component.d.ts.map +1 -1
- package/dist/lib/custom/Entities/entity-form.component.js +836 -801
- package/dist/lib/custom/Entities/entity-form.component.js.map +1 -1
- package/dist/lib/custom/EntityActions/entityaction.form.component.js +2 -2
- package/dist/lib/custom/Files/file-form.component.js +2 -2
- package/dist/lib/custom/Lists/list-form.component.d.ts +5 -0
- package/dist/lib/custom/Lists/list-form.component.d.ts.map +1 -1
- package/dist/lib/custom/Lists/list-form.component.js +355 -338
- package/dist/lib/custom/Lists/list-form.component.js.map +1 -1
- package/dist/lib/custom/MLModels/ml-model-form.component.js +3 -3
- package/dist/lib/custom/MLModels/ml-model-form.component.js.map +1 -1
- package/dist/lib/custom/MLModels/ml-model-view-models.d.ts +17 -35
- package/dist/lib/custom/MLModels/ml-model-view-models.d.ts.map +1 -1
- package/dist/lib/custom/MLModels/ml-model-view-models.js +55 -54
- package/dist/lib/custom/MLModels/ml-model-view-models.js.map +1 -1
- package/dist/lib/custom/MLModels/ps-model-detail.component.d.ts +7 -99
- package/dist/lib/custom/MLModels/ps-model-detail.component.d.ts.map +1 -1
- package/dist/lib/custom/MLModels/ps-model-detail.component.js +124 -303
- package/dist/lib/custom/MLModels/ps-model-detail.component.js.map +1 -1
- package/dist/lib/custom/Queries/query-form.component.d.ts +9 -0
- package/dist/lib/custom/Queries/query-form.component.d.ts.map +1 -1
- package/dist/lib/custom/Queries/query-form.component.js +493 -444
- package/dist/lib/custom/Queries/query-form.component.js.map +1 -1
- package/dist/lib/custom/Queries/query-run-dialog.component.js +2 -2
- package/dist/lib/custom/Templates/template-param-dialog.component.js +2 -2
- package/dist/lib/custom/Templates/templates-form.component.d.ts +9 -0
- package/dist/lib/custom/Templates/templates-form.component.d.ts.map +1 -1
- package/dist/lib/custom/Templates/templates-form.component.js +118 -74
- package/dist/lib/custom/Templates/templates-form.component.js.map +1 -1
- package/dist/lib/custom/Tests/test-form.component.d.ts +5 -0
- package/dist/lib/custom/Tests/test-form.component.d.ts.map +1 -1
- package/dist/lib/custom/Tests/test-form.component.js +471 -451
- package/dist/lib/custom/Tests/test-form.component.js.map +1 -1
- package/dist/lib/custom/Tests/test-run-form.component.d.ts +5 -0
- package/dist/lib/custom/Tests/test-run-form.component.d.ts.map +1 -1
- package/dist/lib/custom/Tests/test-run-form.component.js +312 -289
- package/dist/lib/custom/Tests/test-run-form.component.js.map +1 -1
- package/dist/lib/custom/Tests/test-suite-form.component.d.ts +5 -0
- package/dist/lib/custom/Tests/test-suite-form.component.d.ts.map +1 -1
- package/dist/lib/custom/Tests/test-suite-form.component.js +697 -677
- package/dist/lib/custom/Tests/test-suite-form.component.js.map +1 -1
- package/dist/lib/custom/Tests/test-suite-run-form.component.d.ts +5 -0
- package/dist/lib/custom/Tests/test-suite-run-form.component.d.ts.map +1 -1
- package/dist/lib/custom/Tests/test-suite-run-form.component.js +453 -433
- package/dist/lib/custom/Tests/test-suite-run-form.component.js.map +1 -1
- package/dist/lib/custom/Users/user-header.panel.d.ts.map +1 -1
- package/dist/lib/custom/Users/user-header.panel.js +3 -3
- package/dist/lib/custom/Users/user-header.panel.js.map +1 -1
- package/dist/lib/custom/ai-agent-run/agent-run-cost-split.d.ts +2 -2
- package/dist/lib/custom/ai-agent-run/agent-run-cost-split.d.ts.map +1 -1
- package/dist/lib/custom/ai-agent-run/agent-run-cost-split.js +30 -13
- package/dist/lib/custom/ai-agent-run/agent-run-cost-split.js.map +1 -1
- package/dist/lib/custom/ai-agent-run/ai-agent-run.component.d.ts +23 -0
- package/dist/lib/custom/ai-agent-run/ai-agent-run.component.d.ts.map +1 -1
- package/dist/lib/custom/ai-agent-run/ai-agent-run.component.js +276 -216
- package/dist/lib/custom/ai-agent-run/ai-agent-run.component.js.map +1 -1
- package/dist/lib/custom/custom-forms.module.d.ts +1 -1
- package/dist/lib/custom/custom-forms.module.d.ts.map +1 -1
- package/dist/lib/custom/custom-forms.module.js +7 -1
- package/dist/lib/custom/custom-forms.module.js.map +1 -1
- package/dist/lib/generated/Entities/MJAIPromptRun/mjaipromptrun.form.component.d.ts.map +1 -1
- package/dist/lib/generated/Entities/MJAIPromptRun/mjaipromptrun.form.component.js +32 -14
- package/dist/lib/generated/Entities/MJAIPromptRun/mjaipromptrun.form.component.js.map +1 -1
- package/dist/lib/generated/Entities/MJAction/mjaction.form.component.d.ts.map +1 -1
- package/dist/lib/generated/Entities/MJAction/mjaction.form.component.js +119 -95
- package/dist/lib/generated/Entities/MJAction/mjaction.form.component.js.map +1 -1
- package/dist/lib/generated/Entities/MJComment/mjcomment.form.component.d.ts +10 -0
- package/dist/lib/generated/Entities/MJComment/mjcomment.form.component.d.ts.map +1 -0
- package/dist/lib/generated/Entities/MJComment/mjcomment.form.component.js +124 -0
- package/dist/lib/generated/Entities/MJComment/mjcomment.form.component.js.map +1 -0
- package/dist/lib/generated/Entities/MJCommentType/mjcommenttype.form.component.d.ts +10 -0
- package/dist/lib/generated/Entities/MJCommentType/mjcommenttype.form.component.d.ts.map +1 -0
- package/dist/lib/generated/Entities/MJCommentType/mjcommenttype.form.component.js +99 -0
- package/dist/lib/generated/Entities/MJCommentType/mjcommenttype.form.component.js.map +1 -0
- package/dist/lib/generated/Entities/MJConversation/mjconversation.form.component.js +2 -2
- package/dist/lib/generated/Entities/MJConversation/mjconversation.form.component.js.map +1 -1
- package/dist/lib/generated/Entities/MJConversationType/mjconversationtype.form.component.d.ts +10 -0
- package/dist/lib/generated/Entities/MJConversationType/mjconversationtype.form.component.d.ts.map +1 -0
- package/dist/lib/generated/Entities/MJConversationType/mjconversationtype.form.component.js +95 -0
- package/dist/lib/generated/Entities/MJConversationType/mjconversationtype.form.component.js.map +1 -0
- package/dist/lib/generated/Entities/MJDocumentSequenceCounter/mjdocumentsequencecounter.form.component.d.ts +10 -0
- package/dist/lib/generated/Entities/MJDocumentSequenceCounter/mjdocumentsequencecounter.form.component.d.ts.map +1 -0
- package/dist/lib/generated/Entities/MJDocumentSequenceCounter/mjdocumentsequencecounter.form.component.js +60 -0
- package/dist/lib/generated/Entities/MJDocumentSequenceCounter/mjdocumentsequencecounter.form.component.js.map +1 -0
- package/dist/lib/generated/Entities/MJEntity/mjentity.form.component.d.ts.map +1 -1
- package/dist/lib/generated/Entities/MJEntity/mjentity.form.component.js +124 -106
- package/dist/lib/generated/Entities/MJEntity/mjentity.form.component.js.map +1 -1
- package/dist/lib/generated/Entities/MJEntityFieldValue/mjentityfieldvalue.form.component.d.ts.map +1 -1
- package/dist/lib/generated/Entities/MJEntityFieldValue/mjentityfieldvalue.form.component.js +12 -4
- package/dist/lib/generated/Entities/MJEntityFieldValue/mjentityfieldvalue.form.component.js.map +1 -1
- package/dist/lib/generated/Entities/MJEntityOrganicKey/mjentityorganickey.form.component.d.ts.map +1 -1
- package/dist/lib/generated/Entities/MJEntityOrganicKey/mjentityorganickey.form.component.js +19 -11
- package/dist/lib/generated/Entities/MJEntityOrganicKey/mjentityorganickey.form.component.js.map +1 -1
- package/dist/lib/generated/Entities/MJEntityUniqueKey/mjentityuniquekey.form.component.d.ts +10 -0
- package/dist/lib/generated/Entities/MJEntityUniqueKey/mjentityuniquekey.form.component.d.ts.map +1 -0
- package/dist/lib/generated/Entities/MJEntityUniqueKey/mjentityuniquekey.form.component.js +62 -0
- package/dist/lib/generated/Entities/MJEntityUniqueKey/mjentityuniquekey.form.component.js.map +1 -0
- package/dist/lib/generated/Entities/MJFeatureValue/mjfeaturevalue.form.component.d.ts.map +1 -1
- package/dist/lib/generated/Entities/MJFeatureValue/mjfeaturevalue.form.component.js +26 -12
- package/dist/lib/generated/Entities/MJFeatureValue/mjfeaturevalue.form.component.js.map +1 -1
- package/dist/lib/generated/Entities/MJFieldSuggestion/mjfieldsuggestion.form.component.d.ts +10 -0
- package/dist/lib/generated/Entities/MJFieldSuggestion/mjfieldsuggestion.form.component.d.ts.map +1 -0
- package/dist/lib/generated/Entities/MJFieldSuggestion/mjfieldsuggestion.form.component.js +140 -0
- package/dist/lib/generated/Entities/MJFieldSuggestion/mjfieldsuggestion.form.component.js.map +1 -0
- package/dist/lib/generated/Entities/MJFile/mjfile.form.component.d.ts.map +1 -1
- package/dist/lib/generated/Entities/MJFile/mjfile.form.component.js +69 -15
- package/dist/lib/generated/Entities/MJFile/mjfile.form.component.js.map +1 -1
- package/dist/lib/generated/Entities/MJFileStorageProvider/mjfilestorageprovider.form.component.d.ts.map +1 -1
- package/dist/lib/generated/Entities/MJFileStorageProvider/mjfilestorageprovider.form.component.js +31 -13
- package/dist/lib/generated/Entities/MJFileStorageProvider/mjfilestorageprovider.form.component.js.map +1 -1
- package/dist/lib/generated/Entities/MJMLEvaluationProtocol/mjmlevaluationprotocol.form.component.d.ts +10 -0
- package/dist/lib/generated/Entities/MJMLEvaluationProtocol/mjmlevaluationprotocol.form.component.d.ts.map +1 -0
- package/dist/lib/generated/Entities/MJMLEvaluationProtocol/mjmlevaluationprotocol.form.component.js +205 -0
- package/dist/lib/generated/Entities/MJMLEvaluationProtocol/mjmlevaluationprotocol.form.component.js.map +1 -0
- package/dist/lib/generated/Entities/MJMLModel/mjmlmodel.form.component.d.ts.map +1 -1
- package/dist/lib/generated/Entities/MJMLModel/mjmlmodel.form.component.js +24 -6
- package/dist/lib/generated/Entities/MJMLModel/mjmlmodel.form.component.js.map +1 -1
- package/dist/lib/generated/Entities/MJMLPredictionUnit/mjmlpredictionunit.form.component.d.ts +10 -0
- package/dist/lib/generated/Entities/MJMLPredictionUnit/mjmlpredictionunit.form.component.d.ts.map +1 -0
- package/dist/lib/generated/Entities/MJMLPredictionUnit/mjmlpredictionunit.form.component.js +173 -0
- package/dist/lib/generated/Entities/MJMLPredictionUnit/mjmlpredictionunit.form.component.js.map +1 -0
- package/dist/lib/generated/Entities/MJMLTrainingPipeline/mjmltrainingpipeline.form.component.d.ts.map +1 -1
- package/dist/lib/generated/Entities/MJMLTrainingPipeline/mjmltrainingpipeline.form.component.js +46 -24
- package/dist/lib/generated/Entities/MJMLTrainingPipeline/mjmltrainingpipeline.form.component.js.map +1 -1
- package/dist/lib/generated/Entities/MJMLTrainingRun/mjmltrainingrun.form.component.js +22 -14
- package/dist/lib/generated/Entities/MJMLTrainingRun/mjmltrainingrun.form.component.js.map +1 -1
- package/dist/lib/generated/Entities/MJMLTrainingSnapshot/mjmltrainingsnapshot.form.component.d.ts +10 -0
- package/dist/lib/generated/Entities/MJMLTrainingSnapshot/mjmltrainingsnapshot.form.component.d.ts.map +1 -0
- package/dist/lib/generated/Entities/MJMLTrainingSnapshot/mjmltrainingsnapshot.form.component.js +175 -0
- package/dist/lib/generated/Entities/MJMLTrainingSnapshot/mjmltrainingsnapshot.form.component.js.map +1 -0
- package/dist/lib/generated/Entities/MJQueryField/mjqueryfield.form.component.js +8 -6
- package/dist/lib/generated/Entities/MJQueryField/mjqueryfield.form.component.js.map +1 -1
- package/dist/lib/generated/Entities/MJRecordProcess/mjrecordprocess.form.component.d.ts.map +1 -1
- package/dist/lib/generated/Entities/MJRecordProcess/mjrecordprocess.form.component.js +44 -36
- package/dist/lib/generated/Entities/MJRecordProcess/mjrecordprocess.form.component.js.map +1 -1
- package/dist/lib/generated/Entities/MJTask/mjtask.form.component.d.ts.map +1 -1
- package/dist/lib/generated/Entities/MJTask/mjtask.form.component.js +54 -32
- package/dist/lib/generated/Entities/MJTask/mjtask.form.component.js.map +1 -1
- package/dist/lib/generated/generated-forms.module.d.ts +452 -443
- package/dist/lib/generated/generated-forms.module.d.ts.map +1 -1
- package/dist/lib/generated/generated-forms.module.js +27 -0
- package/dist/lib/generated/generated-forms.module.js.map +1 -1
- package/dist/lib/panels/feature-values/feature-values.panel.js +1 -1
- package/dist/lib/panels/feature-values/feature-values.panel.js.map +1 -1
- package/dist/lib/panels/model-predictions/model-prediction.logic.d.ts +1 -1
- package/dist/lib/panels/model-predictions/model-prediction.logic.d.ts.map +1 -1
- package/dist/lib/panels/model-predictions/model-prediction.logic.js.map +1 -1
- package/dist/lib/shared/components/template-editor.component.js +2 -2
- package/dist/lib/shared/phone-hero.d.ts +17 -0
- package/dist/lib/shared/phone-hero.d.ts.map +1 -0
- package/dist/lib/shared/phone-hero.js +30 -0
- package/dist/lib/shared/phone-hero.js.map +1 -0
- package/dist/lib/shared/phone-sections-state.d.ts +28 -0
- package/dist/lib/shared/phone-sections-state.d.ts.map +1 -0
- package/dist/lib/shared/phone-sections-state.js +38 -0
- package/dist/lib/shared/phone-sections-state.js.map +1 -0
- package/package.json +50 -50
|
@@ -225,7 +225,7 @@ let FeatureValuesPanel = class FeatureValuesPanel extends BaseFormPanel {
|
|
|
225
225
|
Fields: ['ID', 'RecordProcessID', 'EntityID', 'RecordID', 'FeatureName', 'ValueText', 'ValueNumeric',
|
|
226
226
|
'ValueDate', 'ValueBoolean', 'ValueJSON', 'Reasoning', 'Confidence', 'ProcessRunID', 'ComputedAt',
|
|
227
227
|
'PipelineVersion', 'Source', 'RecordProcess'],
|
|
228
|
-
ExtraFilter: `EntityID='${EscapeSQLString(entityID)}' AND RecordID='${EscapeSQLString(recordID)}'`,
|
|
228
|
+
ExtraFilter: `EntityID='${EscapeSQLString(entityID)}' AND RecordID='${EscapeSQLString(recordID)}' AND Role='Champion'`,
|
|
229
229
|
OrderBy: 'ComputedAt DESC',
|
|
230
230
|
MaxRows: 500,
|
|
231
231
|
ResultType: 'simple',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"feature-values.panel.js","sourceRoot":"","sources":["../../../../src/lib/panels/feature-values/feature-values.panel.ts","../../../../src/lib/panels/feature-values/feature-values.panel.html"],"names":[],"mappings":"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","sourcesContent":["import { ChangeDetectorRef, Component, OnInit, inject } from '@angular/core';\nimport { EscapeSQLString, RegisterClassEx } from '@memberjunction/global';\nimport { BaseFormPanel } from '@memberjunction/ng-base-forms';\nimport { LogError, Metadata, RunView, UserInfo } from '@memberjunction/core';\nimport { FeatureValueCard, FeatureValueRow, FeatureValuesPanelModel } from './feature-values.logic';\n\n/**\n * Entity-agnostic, read-only Feature Values panel. Shows the latest value of every\n * feature that any pipeline has materialized for the current record, with an\n * expander for reasoning and value history. Renders nothing when there are no rows.\n */\n@RegisterClassEx(BaseFormPanel, {\n key: 'feature-values:feature-values',\n skipNullKeyWarning: true,\n metadata: {\n entity: '*',\n slot: 'after-fields',\n sortKey: 41,\n },\n})\n@Component({\n standalone: false,\n selector: 'mj-feature-values-panel',\n templateUrl: './feature-values.panel.html',\n styleUrls: ['./feature-values.panel.css'],\n})\nexport class FeatureValuesPanel extends BaseFormPanel implements OnInit {\n private cdr = inject(ChangeDetectorRef);\n\n /** Latest card per pipeline+feature. */\n public Cards: FeatureValueCard[] = [];\n /** True once the load has completed (success, empty or skipped). */\n public Loaded = false;\n /** Key of the card whose detail/history is expanded. */\n public ExpandedKey: string | null = null;\n /** The expanded card's older values, newest first; computed when the card is toggled or the rows reload, not per binding. */\n public ExpandedHistory: FeatureValueRow[] = [];\n\n private rows: FeatureValueRow[] = [];\n /** Sequence number of the latest load; an older load's answer is dropped. */\n private loadSequence = 0;\n\n public async ngOnInit(): Promise<void> {\n await this.Refresh();\n }\n\n /** Reload when the parent form reloads the record, so stale rows never show. */\n public override OnRecordRefreshed(): void {\n void this.Refresh();\n }\n\n /** Reload the feature values for the current record (one RunView); a load overtaken by a later one is dropped. */\n public async Refresh(): Promise<void> {\n const sequence = ++this.loadSequence;\n const entityID = this.Record?.EntityInfo?.ID ?? null;\n // Same encoding the pipeline writes into RecordID: bare value for a single key, F1|v1||F2|v2 for composite.\n const recordID = this.Record?.PrimaryKey?.ToCompactURLSegment() ?? '';\n if (!this.Record?.IsSaved || !entityID || !recordID) {\n this.rows = [];\n this.Cards = [];\n this.syncExpandedHistory();\n this.Loaded = true;\n this.cdr.markForCheck();\n return;\n }\n this.Loaded = false;\n try {\n const result = await this.runView().RunView<FeatureValueRow>(\n {\n EntityName: 'MJ: Feature Values',\n Fields: ['ID', 'RecordProcessID', 'EntityID', 'RecordID', 'FeatureName', 'ValueText', 'ValueNumeric',\n 'ValueDate', 'ValueBoolean', 'ValueJSON', 'Reasoning', 'Confidence', 'ProcessRunID', 'ComputedAt',\n 'PipelineVersion', 'Source', 'RecordProcess'],\n ExtraFilter: `EntityID='${EscapeSQLString(entityID)}' AND RecordID='${EscapeSQLString(recordID)}'`,\n OrderBy: 'ComputedAt DESC',\n MaxRows: 500,\n ResultType: 'simple',\n },\n this.contextUser(),\n );\n if (sequence !== this.loadSequence) {\n return;\n }\n if (!result.Success) {\n LogError(`[FeatureValuesPanel] Failed to load feature values: ${result.ErrorMessage}`);\n this.rows = [];\n } else {\n this.rows = result.Results ?? [];\n }\n this.Cards = FeatureValuesPanelModel.Latest(this.rows);\n } catch (e) {\n if (sequence !== this.loadSequence) {\n return;\n }\n LogError(`[FeatureValuesPanel] ${e instanceof Error ? e.message : String(e)}`);\n this.rows = [];\n this.Cards = [];\n }\n this.syncExpandedHistory();\n this.Loaded = true;\n this.cdr.markForCheck();\n }\n\n /** Expand/collapse a card's reasoning + history. */\n public Toggle(card: FeatureValueCard): void {\n this.ExpandedKey = this.ExpandedKey === card.Key ? null : card.Key;\n this.syncExpandedHistory();\n }\n\n /** Recomputes the expanded card's history from the current rows; collapses when that card is no longer shown. */\n private syncExpandedHistory(): void {\n const card = this.Cards.find((c) => c.Key === this.ExpandedKey);\n this.ExpandedKey = card ? card.Key : null;\n this.ExpandedHistory = card ? FeatureValuesPanelModel.History(this.rows, card) : [];\n }\n\n /** Display string for a history row (\"no value\" for a valueless one). */\n public ValueOf(row: FeatureValueRow): string {\n return FeatureValuesPanelModel.HistoryValue(row);\n }\n\n private runView(): RunView {\n const provider = this.FormComponent?.ProviderToUse;\n return provider ? RunView.FromMetadataProvider(provider) : new RunView();\n }\n\n private contextUser(): UserInfo | undefined {\n return this.FormComponent?.ProviderToUse?.CurrentUser ?? new Metadata().CurrentUser;\n }\n}\n","<!--\n Entity-agnostic Feature Values panel (read-only). Renders NOTHING unless at\n least one pipeline has answered for this record. A latest answer that withheld\n the value reads \"no value\" — it supersedes any older value rather than letting\n that value stand as if it were current.\n-->\n@if (Cards.length > 0) {\n <mj-collapsible-panel\n SectionKey=\"featureValues\"\n [Order]=\"DisplayOrder\"\n SectionName=\"Feature Values\"\n Icon=\"fa-solid fa-table-list\"\n [Form]=\"FormComponent\"\n [FormContext]=\"FormContext\"\n [BadgeCount]=\"Cards.length\">\n <div class=\"fvp-toolbar\">\n <button type=\"button\" mjButton Variant=\"secondary\" Size=\"sm\" class=\"fvp-refresh\" [disabled]=\"!Loaded\" (click)=\"Refresh()\" title=\"Refresh feature values\">\n <i class=\"fa-solid fa-rotate\" [class.fa-spin]=\"!Loaded\" aria-hidden=\"true\"></i> Refresh\n </button>\n </div>\n <table class=\"fvp-table\">\n <thead>\n <tr>\n <th>Feature</th>\n <th>Value</th>\n <th>Pipeline</th>\n <th>Source</th>\n <th>Computed</th>\n <th></th>\n </tr>\n </thead>\n <tbody>\n @for (card of Cards; track card.Key) {\n <tr class=\"fvp-row\">\n <td class=\"fvp-feature\">{{ card.FeatureName }}</td>\n <td class=\"fvp-value\" [class.fvp-value--none]=\"!card.HasValue\">{{ card.DisplayValue }}</td>\n <td>{{ card.Pipeline }}@if (card.PipelineVersion !== null) { <span class=\"fvp-muted\"> v{{ card.PipelineVersion }}</span> }</td>\n <td>{{ card.Source }}@if (card.Confidence !== null) { <span class=\"fvp-muted\"> ({{ card.Confidence }})</span> }</td>\n <td class=\"fvp-muted\">{{ card.ComputedAt ? (card.ComputedAt | date: 'yyyy-MM-dd HH:mm') : '' }}</td>\n <td>\n <button type=\"button\" mjButton Variant=\"icon\" Size=\"sm\" class=\"fvp-toggle\" (click)=\"Toggle(card)\"\n [attr.aria-expanded]=\"ExpandedKey === card.Key\" AriaLabel=\"Show reasoning and history\" title=\"Show reasoning and history\">\n <i class=\"fa-solid\" [class.fa-chevron-down]=\"ExpandedKey !== card.Key\" [class.fa-chevron-up]=\"ExpandedKey === card.Key\" aria-hidden=\"true\"></i>\n </button>\n </td>\n </tr>\n @if (ExpandedKey === card.Key) {\n <tr class=\"fvp-detail\">\n <td colspan=\"6\">\n @if (card.Reasoning) {\n <div class=\"fvp-reasoning\">{{ card.Reasoning }}</div>\n }\n <div class=\"fvp-history-title\">History</div>\n @if (ExpandedHistory.length === 0) {\n <div class=\"fvp-muted fvp-history-empty\">No earlier values.</div>\n } @else {\n <ul class=\"fvp-history\">\n @for (h of ExpandedHistory; track h.ID) {\n <li class=\"fvp-history-item\">\n <span class=\"fvp-value\">{{ ValueOf(h) }}</span>\n <span class=\"fvp-muted\">{{ h.ComputedAt ? (h.ComputedAt | date: 'yyyy-MM-dd HH:mm') : '' }}</span>\n </li>\n }\n </ul>\n }\n </td>\n </tr>\n }\n }\n </tbody>\n </table>\n </mj-collapsible-panel>\n}\n"]}
|
|
1
|
+
{"version":3,"file":"feature-values.panel.js","sourceRoot":"","sources":["../../../../src/lib/panels/feature-values/feature-values.panel.ts","../../../../src/lib/panels/feature-values/feature-values.panel.html"],"names":[],"mappings":"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","sourcesContent":["import { ChangeDetectorRef, Component, OnInit, inject } from '@angular/core';\nimport { EscapeSQLString, RegisterClassEx } from '@memberjunction/global';\nimport { BaseFormPanel } from '@memberjunction/ng-base-forms';\nimport { LogError, Metadata, RunView, UserInfo } from '@memberjunction/core';\nimport { FeatureValueCard, FeatureValueRow, FeatureValuesPanelModel } from './feature-values.logic';\n\n/**\n * Entity-agnostic, read-only Feature Values panel. Shows the latest value of every\n * feature that any pipeline has materialized for the current record, with an\n * expander for reasoning and value history. Renders nothing when there are no rows.\n */\n@RegisterClassEx(BaseFormPanel, {\n key: 'feature-values:feature-values',\n skipNullKeyWarning: true,\n metadata: {\n entity: '*',\n slot: 'after-fields',\n sortKey: 41,\n },\n})\n@Component({\n standalone: false,\n selector: 'mj-feature-values-panel',\n templateUrl: './feature-values.panel.html',\n styleUrls: ['./feature-values.panel.css'],\n})\nexport class FeatureValuesPanel extends BaseFormPanel implements OnInit {\n private cdr = inject(ChangeDetectorRef);\n\n /** Latest card per pipeline+feature. */\n public Cards: FeatureValueCard[] = [];\n /** True once the load has completed (success, empty or skipped). */\n public Loaded = false;\n /** Key of the card whose detail/history is expanded. */\n public ExpandedKey: string | null = null;\n /** The expanded card's older values, newest first; computed when the card is toggled or the rows reload, not per binding. */\n public ExpandedHistory: FeatureValueRow[] = [];\n\n private rows: FeatureValueRow[] = [];\n /** Sequence number of the latest load; an older load's answer is dropped. */\n private loadSequence = 0;\n\n public async ngOnInit(): Promise<void> {\n await this.Refresh();\n }\n\n /** Reload when the parent form reloads the record, so stale rows never show. */\n public override OnRecordRefreshed(): void {\n void this.Refresh();\n }\n\n /** Reload the feature values for the current record (one RunView); a load overtaken by a later one is dropped. */\n public async Refresh(): Promise<void> {\n const sequence = ++this.loadSequence;\n const entityID = this.Record?.EntityInfo?.ID ?? null;\n // Same encoding the pipeline writes into RecordID: bare value for a single key, F1|v1||F2|v2 for composite.\n const recordID = this.Record?.PrimaryKey?.ToCompactURLSegment() ?? '';\n if (!this.Record?.IsSaved || !entityID || !recordID) {\n this.rows = [];\n this.Cards = [];\n this.syncExpandedHistory();\n this.Loaded = true;\n this.cdr.markForCheck();\n return;\n }\n this.Loaded = false;\n try {\n const result = await this.runView().RunView<FeatureValueRow>(\n {\n EntityName: 'MJ: Feature Values',\n Fields: ['ID', 'RecordProcessID', 'EntityID', 'RecordID', 'FeatureName', 'ValueText', 'ValueNumeric',\n 'ValueDate', 'ValueBoolean', 'ValueJSON', 'Reasoning', 'Confidence', 'ProcessRunID', 'ComputedAt',\n 'PipelineVersion', 'Source', 'RecordProcess'],\n ExtraFilter: `EntityID='${EscapeSQLString(entityID)}' AND RecordID='${EscapeSQLString(recordID)}' AND Role='Champion'`,\n OrderBy: 'ComputedAt DESC',\n MaxRows: 500,\n ResultType: 'simple',\n },\n this.contextUser(),\n );\n if (sequence !== this.loadSequence) {\n return;\n }\n if (!result.Success) {\n LogError(`[FeatureValuesPanel] Failed to load feature values: ${result.ErrorMessage}`);\n this.rows = [];\n } else {\n this.rows = result.Results ?? [];\n }\n this.Cards = FeatureValuesPanelModel.Latest(this.rows);\n } catch (e) {\n if (sequence !== this.loadSequence) {\n return;\n }\n LogError(`[FeatureValuesPanel] ${e instanceof Error ? e.message : String(e)}`);\n this.rows = [];\n this.Cards = [];\n }\n this.syncExpandedHistory();\n this.Loaded = true;\n this.cdr.markForCheck();\n }\n\n /** Expand/collapse a card's reasoning + history. */\n public Toggle(card: FeatureValueCard): void {\n this.ExpandedKey = this.ExpandedKey === card.Key ? null : card.Key;\n this.syncExpandedHistory();\n }\n\n /** Recomputes the expanded card's history from the current rows; collapses when that card is no longer shown. */\n private syncExpandedHistory(): void {\n const card = this.Cards.find((c) => c.Key === this.ExpandedKey);\n this.ExpandedKey = card ? card.Key : null;\n this.ExpandedHistory = card ? FeatureValuesPanelModel.History(this.rows, card) : [];\n }\n\n /** Display string for a history row (\"no value\" for a valueless one). */\n public ValueOf(row: FeatureValueRow): string {\n return FeatureValuesPanelModel.HistoryValue(row);\n }\n\n private runView(): RunView {\n const provider = this.FormComponent?.ProviderToUse;\n return provider ? RunView.FromMetadataProvider(provider) : new RunView();\n }\n\n private contextUser(): UserInfo | undefined {\n return this.FormComponent?.ProviderToUse?.CurrentUser ?? new Metadata().CurrentUser;\n }\n}\n","<!--\n Entity-agnostic Feature Values panel (read-only). Renders NOTHING unless at\n least one pipeline has answered for this record. A latest answer that withheld\n the value reads \"no value\" — it supersedes any older value rather than letting\n that value stand as if it were current.\n-->\n@if (Cards.length > 0) {\n <mj-collapsible-panel\n SectionKey=\"featureValues\"\n [Order]=\"DisplayOrder\"\n SectionName=\"Feature Values\"\n Icon=\"fa-solid fa-table-list\"\n [Form]=\"FormComponent\"\n [FormContext]=\"FormContext\"\n [BadgeCount]=\"Cards.length\">\n <div class=\"fvp-toolbar\">\n <button type=\"button\" mjButton Variant=\"secondary\" Size=\"sm\" class=\"fvp-refresh\" [disabled]=\"!Loaded\" (click)=\"Refresh()\" title=\"Refresh feature values\">\n <i class=\"fa-solid fa-rotate\" [class.fa-spin]=\"!Loaded\" aria-hidden=\"true\"></i> Refresh\n </button>\n </div>\n <table class=\"fvp-table\">\n <thead>\n <tr>\n <th>Feature</th>\n <th>Value</th>\n <th>Pipeline</th>\n <th>Source</th>\n <th>Computed</th>\n <th></th>\n </tr>\n </thead>\n <tbody>\n @for (card of Cards; track card.Key) {\n <tr class=\"fvp-row\">\n <td class=\"fvp-feature\">{{ card.FeatureName }}</td>\n <td class=\"fvp-value\" [class.fvp-value--none]=\"!card.HasValue\">{{ card.DisplayValue }}</td>\n <td>{{ card.Pipeline }}@if (card.PipelineVersion !== null) { <span class=\"fvp-muted\"> v{{ card.PipelineVersion }}</span> }</td>\n <td>{{ card.Source }}@if (card.Confidence !== null) { <span class=\"fvp-muted\"> ({{ card.Confidence }})</span> }</td>\n <td class=\"fvp-muted\">{{ card.ComputedAt ? (card.ComputedAt | date: 'yyyy-MM-dd HH:mm') : '' }}</td>\n <td>\n <button type=\"button\" mjButton Variant=\"icon\" Size=\"sm\" class=\"fvp-toggle\" (click)=\"Toggle(card)\"\n [attr.aria-expanded]=\"ExpandedKey === card.Key\" AriaLabel=\"Show reasoning and history\" title=\"Show reasoning and history\">\n <i class=\"fa-solid\" [class.fa-chevron-down]=\"ExpandedKey !== card.Key\" [class.fa-chevron-up]=\"ExpandedKey === card.Key\" aria-hidden=\"true\"></i>\n </button>\n </td>\n </tr>\n @if (ExpandedKey === card.Key) {\n <tr class=\"fvp-detail\">\n <td colspan=\"6\">\n @if (card.Reasoning) {\n <div class=\"fvp-reasoning\">{{ card.Reasoning }}</div>\n }\n <div class=\"fvp-history-title\">History</div>\n @if (ExpandedHistory.length === 0) {\n <div class=\"fvp-muted fvp-history-empty\">No earlier values.</div>\n } @else {\n <ul class=\"fvp-history\">\n @for (h of ExpandedHistory; track h.ID) {\n <li class=\"fvp-history-item\">\n <span class=\"fvp-value\">{{ ValueOf(h) }}</span>\n <span class=\"fvp-muted\">{{ h.ComputedAt ? (h.ComputedAt | date: 'yyyy-MM-dd HH:mm') : '' }}</span>\n </li>\n }\n </ul>\n }\n </td>\n </tr>\n }\n }\n </tbody>\n </table>\n </mj-collapsible-panel>\n}\n"]}
|
|
@@ -160,7 +160,7 @@ export interface RawProcessRunDetail {
|
|
|
160
160
|
/** Model metadata lookup contract for decorating history items. */
|
|
161
161
|
export interface HistoryModelMetadata {
|
|
162
162
|
Name?: string;
|
|
163
|
-
Pipeline?: string;
|
|
163
|
+
Pipeline?: string | null;
|
|
164
164
|
Version?: number;
|
|
165
165
|
ProblemType?: string;
|
|
166
166
|
TargetVariable?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model-prediction.logic.d.ts","sourceRoot":"","sources":["../../../../src/lib/panels/model-predictions/model-prediction.logic.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"model-prediction.logic.d.ts","sourceRoot":"","sources":["../../../../src/lib/panels/model-predictions/model-prediction.logic.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model-prediction.logic.js","sourceRoot":"","sources":["../../../../src/lib/panels/model-predictions/model-prediction.logic.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Pure, framework-free logic for the entity-agnostic Model Predictions panel.\n *\n * Extracted from the Angular component so the band computation, value\n * formatting, and feature-importance parsing can be unit-tested without\n * standing up Angular or a real entity record. NONE of this is entity-specific\n * or domain-specific — it operates purely on numbers, strings, and the model's\n * generic `ProblemType` / `FeatureImportance` JSON.\n */\n\nimport {\n ImportanceDisplay,\n OutcomeBand,\n OutcomeConfig,\n formatPredictionScore,\n resolveOutcomeConfig,\n resolveOutcomeStyle,\n resolveScoreBand,\n} from '@memberjunction/predictive-studio-core';\n\n/**\n * The neutral tercile a numeric prediction falls into. No moral direction is\n * implied — `low`/`mid`/`high` describe position on the value axis only.\n */\nexport type PredictionBand = 'low' | 'mid' | 'high';\n\n/** Generic problem types a trained model can have. */\nexport type ModelProblemType = 'classification' | 'regression';\n\n/** One normalized feature-importance driver for display. */\nexport interface PredictionDriver {\n /** Raw feature name as the model stored it. */\n name: string; // case-violation-ok-legacy-back-compat: the old name is also read off a value typed `any`, where a rename would compile and silently return undefined\n /** Importance magnitude (absolute value of the raw contribution). */\n importance: number; // case-violation-ok-legacy-back-compat: renaming it broke a use the checker could not see from the declaration — the compile proved it\n /** Importance as a 0–100 share of the strongest driver, for the bar width. */\n RelativePct: number;\n}\n\n/** How a prediction's primary value should be rendered. */\nexport type PredictionValueKind = 'probability' | 'numeric' | 'class';\n\n/** Maximum number of feature-importance drivers shown per card. */\nexport const MAX_DRIVERS = 5;\n\n/**\n * Coerce a dynamic field value into a finite number, or null when it isn't one.\n */\nexport function ToNumber(value: unknown): number | null {\n if (typeof value === 'number') {\n return Number.isFinite(value) ? value : null;\n }\n if (typeof value === 'string' && value.trim() !== '') {\n const n = Number(value);\n return Number.isFinite(n) ? n : null;\n }\n return null;\n}\n\n/** @deprecated Use {@link ToNumber}. */\nexport function toNumber(value: unknown): number | null {\n return ToNumber(value);\n}\n\n/**\n * Decide how to render a value given the model's problem type and the value.\n * - regression + numeric → 'numeric'\n * - classification + value in [0,1] → 'probability' (a confidence/score we can gauge)\n * - everything else (a class label, a missing value) → 'class'\n */\nexport function ValueKind(problemType: ModelProblemType, numeric: number | null): PredictionValueKind {\n const isRegression = problemType === 'regression';\n if (isRegression && numeric != null) {\n return 'numeric';\n }\n if (!isRegression && numeric != null && numeric >= 0 && numeric <= 1) {\n return 'probability';\n }\n return 'class';\n}\n\n/** @deprecated Use {@link ValueKind}. */\nexport function valueKind(problemType: ModelProblemType, numeric: number | null): PredictionValueKind {\n return ValueKind(problemType, numeric);\n}\n\n/**\n * Neutral tercile for a 0–1 value. Position on the value axis only — no\n * assumption that high is good or bad. Values are clamped to [0,1].\n */\nexport function BandFor(value: number): PredictionBand {\n const v = Math.max(0, Math.min(1, value));\n if (v < 1 / 3) return 'low';\n if (v < 2 / 3) return 'mid';\n return 'high';\n}\n\n/** @deprecated Use {@link BandFor}. */\nexport function bandFor(value: number): PredictionBand {\n return BandFor(value);\n}\n\n/** Round a 0–1 probability to an integer 0–100 gauge fill, clamped. */\nexport function GaugePct(value: number): number {\n return Math.round(Math.max(0, Math.min(1, value)) * 100);\n}\n\n/** @deprecated Use {@link GaugePct}. */\nexport function gaugePct(value: number): number {\n return GaugePct(value);\n}\n\n/**\n * Format the primary value for display:\n * - probability → \"72%\"\n * - numeric (regression) → grouped number, up to 4 decimals\n * - class label → the label string\n * - missing/empty → \"—\" (em dash)\n */\nexport function FormatValue(rawValue: unknown, numeric: number | null, kind: PredictionValueKind): string {\n if (kind === 'probability' && numeric != null) {\n return `${GaugePct(numeric)}%`;\n }\n if (kind === 'numeric' && numeric != null) {\n return numeric.toLocaleString(undefined, { maximumFractionDigits: 4 });\n }\n if (rawValue == null || rawValue === '') {\n return '—';\n }\n return String(rawValue);\n}\n\n/** @deprecated Use {@link FormatValue}. */\nexport function formatValue(rawValue: unknown, numeric: number | null, kind: PredictionValueKind): string {\n return FormatValue(rawValue, numeric, kind);\n}\n\n/**\n * Pick the human label for a prediction: prefer the model's target variable,\n * then the bound column name, then a generic fallback.\n */\nexport function ResolveLabel(targetVariable: string | null, targetColumn: string | null): string {\n const target = targetVariable?.trim();\n if (target) return target;\n const col = targetColumn?.trim();\n if (col) return col;\n return 'Prediction';\n}\n\n/** @deprecated Use {@link ResolveLabel}. */\nexport function resolveLabel(targetVariable: string | null, targetColumn: string | null): string {\n return ResolveLabel(targetVariable, targetColumn);\n}\n\n/**\n * Parse a model's `FeatureImportance` JSON (`Record<string, number>`) into the\n * top-N sorted drivers with relative bar widths. Returns [] on null / invalid /\n * empty input — the caller omits the drivers section cleanly when empty.\n */\nexport function ParseDrivers(featureImportanceJson: string | null): PredictionDriver[] {\n if (!featureImportanceJson) {\n return [];\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(featureImportanceJson);\n } catch {\n return [];\n }\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {\n return [];\n }\n const entries = Object.entries(parsed as Record<string, unknown>)\n .map(([name, raw]) => ({ name, importance: Math.abs(ToNumber(raw) ?? 0) }))\n .filter(d => d.importance > 0)\n .sort((a, b) => b.importance - a.importance)\n .slice(0, MAX_DRIVERS);\n\n const max = entries.length > 0 ? entries[0].importance : 0;\n return entries.map(d => ({\n ...d,\n RelativePct: max > 0 ? Math.round((d.importance / max) * 100) : 0,\n }));\n}\n\n/** @deprecated Use {@link ParseDrivers}. */\nexport function parseDrivers(featureImportanceJson: string | null): PredictionDriver[] {\n return ParseDrivers(featureImportanceJson);\n}\n\n/** The global drivers a prediction card may show. */\nexport class PredictionDrivers {\n /** Global drivers for a model's card: coefficient models only (split / impurity counts are not comparable across features and read as importance they are not, so they are hidden). */\n public static DriversForModel(lineageJSON: string | null | undefined, featureImportanceJson: string | null): PredictionDriver[] {\n return ImportanceDisplay.GlobalDriversShowable(lineageJSON) ? ParseDrivers(featureImportanceJson) : [];\n }\n\n /**\n * True when the model has stored importances that {@link DriversForModel} withholds because of their kind, so the card\n * shows {@link ImportanceDisplay.HiddenNote} in their place (the same note the model detail page shows).\n */\n public static DriversHidden(lineageJSON: string | null | undefined, featureImportanceJson: string | null): boolean {\n return !ImportanceDisplay.GlobalDriversShowable(lineageJSON) && ParseDrivers(featureImportanceJson).length > 0;\n }\n}\n\n/** Format a last-scored timestamp to a short date, or null when absent/invalid. */\nexport function FormatLastScored(lastScoredAt: Date | string | null): string | null {\n if (!lastScoredAt) {\n return null;\n }\n const d = lastScoredAt instanceof Date ? lastScoredAt : new Date(lastScoredAt);\n if (isNaN(d.getTime())) {\n return null;\n }\n return d.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' });\n}\n\n/** @deprecated Use {@link FormatLastScored}. */\nexport function formatLastScored(lastScoredAt: Date | string | null): string | null {\n return FormatLastScored(lastScoredAt);\n}\n\n/**\n * A resolved historical prediction row from `MJ: Process Run Details` for an entity record.\n */\nexport interface PredictionHistoryItem {\n /** The MJ: Process Run Details primary key ID */\n id: string; // case-violation-ok-legacy-back-compat: the old name is also read off a value typed `any`, where a rename would compile and silently return undefined\n /** The parent Process Run ID */\n ProcessRunId: string;\n /** Model ID if resolved from payload or model lookup */\n modelId: string | null; // case-violation-ok-legacy-back-compat: the old name is also read off a value typed `any`, where a rename would compile and silently return undefined\n /** Resolved model name (e.g. \"Member Churn Risk\" or \"LatePaymentOutcome\") */\n ModelName: string;\n /** Resolved pipeline & version string (e.g. \"Pipeline v1\") */\n Provenance: string;\n /** Target variable / label */\n target: string; // case-violation-ok-legacy-back-compat: the old name is also read off a value typed `any`, where a rename would compile and silently return undefined\n /** Problem type: classification or regression */\n problemType: ModelProblemType; // case-violation-ok-legacy-back-compat: the old name is also read off a value typed `any`, where a rename would compile and silently return undefined\n /** Primary numeric value / score */\n NumericValue: number | null;\n /** Class label if classification */\n PredictedClass: string | null;\n /** Formatted display value (e.g. \"88%\", \"Late\", \"1,240.5\") */\n DisplayValue: string;\n /** True if 0–1 probability we can gauge/badge */\n IsProbability: boolean;\n /** Neutral band: 'low' | 'mid' | 'high' */\n Band: PredictionBand | null;\n /** Resolved semantic status band from outcomeConfig */\n StatusBand: OutcomeBand | null;\n /** Human-readable status label (e.g. \"Low Risk\", \"High\") */\n StatusLabel: string | null;\n /** Semantic badge color ('green' | 'amber' | 'red' | 'blue' | 'gray') */\n BadgeColor: 'green' | 'amber' | 'red' | 'blue' | 'gray';\n /** Icon class if specified in band/style */\n BadgeIcon: string | null;\n /** Execution status: 'Succeeded' | 'Failed' | 'Pending' | 'Skipped' */\n status: string; // case-violation-ok-legacy-back-compat: the old name is also read off a value typed `any`, where a rename would compile and silently return undefined\n /** When processing completed */\n CompletedAt: Date | null;\n /** Formatted date/time for history display */\n FormattedTime: string;\n /** Drivers if available in payload */\n Drivers: PredictionDriver[];\n /** Error message if Failed */\n ErrorMessage: string | null;\n /** Full raw payload object for JSON inspection */\n RawPayload: Record<string, unknown> | null;\n}\n\n/** Summary descriptor of a model represented in prediction history. */\nexport interface ModelHistorySummary {\n modelId: string; // case-violation-ok-legacy-back-compat: the old name is also read off a value typed `any`, where a rename would compile and silently return undefined\n ModelName: string;\n count: number; // case-violation-ok-legacy-back-compat: the old name is also read off a value typed `any`, where a rename would compile and silently return undefined\n}\n\n/** Input contract for parsing a Process Run Detail record. */\nexport interface RawProcessRunDetail {\n ID: string;\n ProcessRunID: string;\n Status: string;\n CompletedAt: Date | string | null;\n ResultPayload: string | null;\n ErrorMessage: string | null;\n}\n\n/** Model metadata lookup contract for decorating history items. */\nexport interface HistoryModelMetadata {\n Name?: string;\n Pipeline?: string;\n Version?: number;\n ProblemType?: string;\n TargetVariable?: string;\n Lineage?: string | null;\n outcomeConfig?: OutcomeConfig | null; // case-violation-ok-legacy-back-compat: optional, and the old name is also read off a value the checker cannot type; renaming it stays assignable and silently yields undefined\n}\n\n/**\n * Format a history completion timestamp with date and time.\n */\nexport function FormatHistoryTimestamp(date: Date | string | null): string {\n if (!date) return '—';\n const d = date instanceof Date ? date : new Date(date);\n if (isNaN(d.getTime())) return '—';\n return d.toLocaleString(undefined, {\n month: 'short',\n day: 'numeric',\n year: 'numeric',\n hour: 'numeric',\n minute: '2-digit',\n });\n}\n\n/** @deprecated Use {@link FormatHistoryTimestamp}. */\nexport function formatHistoryTimestamp(date: Date | string | null): string {\n return FormatHistoryTimestamp(date);\n}\n\n/**\n * Parse an array of drivers from payload driver shapes (array of { feature, value } or map).\n */\nexport function ParsePayloadDrivers(rawDrivers: unknown): PredictionDriver[] {\n if (!rawDrivers) return [];\n if (Array.isArray(rawDrivers)) {\n const entries = rawDrivers\n .map((item: unknown) => {\n if (!item || typeof item !== 'object') return null;\n const rec = item as Record<string, unknown>;\n const name = typeof rec['feature'] === 'string' ? rec['feature'] : (typeof rec['name'] === 'string' ? rec['name'] : '');\n const val = ToNumber(rec['value'] ?? rec['importance']);\n if (!name || val == null) return null;\n return { name, importance: Math.abs(val) };\n })\n .filter((d): d is { name: string; importance: number } => d != null && d.importance > 0)\n .sort((a, b) => b.importance - a.importance)\n .slice(0, MAX_DRIVERS);\n\n const max = entries.length > 0 ? entries[0].importance : 0;\n return entries.map(d => ({\n ...d,\n RelativePct: max > 0 ? Math.round((d.importance / max) * 100) : 0,\n }));\n }\n if (typeof rawDrivers === 'object') {\n return ParseDrivers(JSON.stringify(rawDrivers));\n }\n return [];\n}\n\n/** @deprecated Use {@link ParsePayloadDrivers}. */\nexport function parsePayloadDrivers(rawDrivers: unknown): PredictionDriver[] {\n return ParsePayloadDrivers(rawDrivers);\n}\n\n/**\n * Parse a raw `MJ: Process Run Details` row into a decorated {@link PredictionHistoryItem}.\n */\nexport function ParseHistoryItem(\n detail: RawProcessRunDetail,\n modelLookup?: Map<string, HistoryModelMetadata>,\n): PredictionHistoryItem {\n let rawPayload: Record<string, unknown> | null = null;\n let payloadSection: Record<string, unknown> | null = null;\n\n if (detail.ResultPayload) {\n try {\n const parsed = JSON.parse(detail.ResultPayload);\n if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {\n rawPayload = parsed as Record<string, unknown>;\n const outputObj = rawPayload['output'];\n if (outputObj && typeof outputObj === 'object' && !Array.isArray(outputObj)) {\n payloadSection = outputObj as Record<string, unknown>;\n } else {\n payloadSection = rawPayload;\n }\n }\n } catch {\n rawPayload = null;\n payloadSection = null;\n }\n }\n\n const payloadModelId = typeof payloadSection?.['modelId'] === 'string' ? payloadSection['modelId'] : null;\n const modelMeta = payloadModelId && modelLookup ? modelLookup.get(payloadModelId) : undefined;\n\n const rawScore = payloadSection?.['score'] ?? payloadSection?.['value'] ?? null;\n const numericScore = ToNumber(rawScore);\n const predictedClass = typeof payloadSection?.['class'] === 'string' ? payloadSection['class'] : null;\n\n const probTypeStr = (typeof payloadSection?.['problemType'] === 'string' ? payloadSection['problemType'] : modelMeta?.ProblemType) ?? '';\n const problemType: ModelProblemType = probTypeStr.toLowerCase() === 'regression' ? 'regression' : 'classification';\n\n const kind = ValueKind(problemType, numericScore);\n const displayValue = predictedClass ?? FormatValue(rawScore, numericScore, kind);\n\n const targetVar = (typeof payloadSection?.['target'] === 'string' ? payloadSection['target'] : modelMeta?.TargetVariable) ?? '';\n const label = ResolveLabel(targetVar, null);\n\n const modelName = modelMeta?.Name ?? label;\n const provenance = modelMeta?.Pipeline && modelMeta?.Version\n ? `${modelMeta.Pipeline} v${modelMeta.Version}`\n : (modelMeta?.Name ?? 'Predictive Model');\n\n const completedAt = detail.CompletedAt ? (detail.CompletedAt instanceof Date ? detail.CompletedAt : new Date(detail.CompletedAt)) : null;\n const drivers = ParsePayloadDrivers(payloadSection?.['drivers']);\n\n const rawOutcomeConfig = (payloadSection?.['outcomeConfig'] ?? modelMeta?.outcomeConfig) as OutcomeConfig | undefined;\n const outcomeConfig = rawOutcomeConfig\n ? resolveOutcomeConfig({ outcomeConfig: rawOutcomeConfig })\n : resolveOutcomeConfig({\n Lineage: modelMeta?.Lineage ?? null,\n TargetVariable: targetVar || modelMeta?.TargetVariable || null,\n ProblemType: probTypeStr || modelMeta?.ProblemType || null,\n });\n\n let statusBand: OutcomeBand | null = null;\n let badgeColor: 'green' | 'amber' | 'red' | 'blue' | 'gray' = 'gray';\n let badgeIcon: string | null = null;\n let statusLabel: string | null = null;\n\n if (numericScore != null) {\n statusBand = resolveScoreBand(numericScore, outcomeConfig);\n if (statusBand) {\n badgeColor = statusBand.BadgeColor;\n badgeIcon = statusBand.Icon ?? null;\n statusLabel = statusBand.Label;\n }\n } else if (predictedClass) {\n const style = resolveOutcomeStyle(predictedClass, outcomeConfig);\n badgeColor = style.BadgeColor;\n badgeIcon = style.Icon ?? null;\n statusLabel = style.DisplayLabel ?? predictedClass;\n }\n\n return {\n id: detail.ID,\n ProcessRunId: detail.ProcessRunID,\n modelId: payloadModelId,\n ModelName: modelName,\n Provenance: provenance,\n target: targetVar || label,\n problemType,\n NumericValue: numericScore,\n PredictedClass: predictedClass,\n DisplayValue: displayValue,\n IsProbability: kind === 'probability',\n Band: kind === 'probability' && numericScore != null ? BandFor(numericScore) : null,\n StatusBand: statusBand,\n StatusLabel: statusLabel,\n BadgeColor: badgeColor,\n BadgeIcon: badgeIcon,\n status: detail.Status || 'Succeeded',\n CompletedAt: completedAt,\n FormattedTime: FormatHistoryTimestamp(completedAt),\n Drivers: drivers,\n ErrorMessage: detail.ErrorMessage,\n RawPayload: rawPayload,\n };\n}\n\n/** @deprecated Use {@link ParseHistoryItem}. */\nexport function parseHistoryItem(\n detail: RawProcessRunDetail,\n modelLookup?: Map<string, HistoryModelMetadata>,\n): PredictionHistoryItem {\n return ParseHistoryItem(detail, modelLookup);\n}\n\n/**\n * Filter prediction history by a selected model ID (or return all when null / 'ALL').\n */\nexport function FilterHistoryByModel(\n history: PredictionHistoryItem[],\n selectedModelId: string | null,\n): PredictionHistoryItem[] {\n if (!selectedModelId || selectedModelId === 'ALL') {\n return history;\n }\n return history.filter(item => item.modelId === selectedModelId);\n}\n\n/** @deprecated Use {@link FilterHistoryByModel}. */\nexport function filterHistoryByModel(\n history: PredictionHistoryItem[],\n selectedModelId: string | null,\n): PredictionHistoryItem[] {\n return FilterHistoryByModel(history, selectedModelId);\n}\n\n/**\n * Aggregate unique models from prediction history for multi-model tabs / filters.\n */\nexport function GetDistinctModelsFromHistory(history: PredictionHistoryItem[]): ModelHistorySummary[] {\n const counts = new Map<string, { ModelName: string; count: number }>();\n for (const item of history) {\n const id = item.modelId ?? 'UNKNOWN';\n const existing = counts.get(id);\n if (existing) {\n existing.count += 1;\n } else {\n counts.set(id, { ModelName: item.ModelName, count: 1 });\n }\n }\n return Array.from(counts.entries()).map(([modelId, data]) => ({\n modelId,\n ModelName: data.ModelName,\n count: data.count,\n }));\n}\n\n/** @deprecated Use {@link GetDistinctModelsFromHistory}. */\nexport function getDistinctModelsFromHistory(history: PredictionHistoryItem[]): ModelHistorySummary[] {\n return GetDistinctModelsFromHistory(history);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"model-prediction.logic.js","sourceRoot":"","sources":["../../../../src/lib/panels/model-predictions/model-prediction.logic.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Pure, framework-free logic for the entity-agnostic Model Predictions panel.\n *\n * Extracted from the Angular component so the band computation, value\n * formatting, and feature-importance parsing can be unit-tested without\n * standing up Angular or a real entity record. NONE of this is entity-specific\n * or domain-specific — it operates purely on numbers, strings, and the model's\n * generic `ProblemType` / `FeatureImportance` JSON.\n */\n\nimport {\n ImportanceDisplay,\n OutcomeBand,\n OutcomeConfig,\n formatPredictionScore,\n resolveOutcomeConfig,\n resolveOutcomeStyle,\n resolveScoreBand,\n} from '@memberjunction/predictive-studio-core';\n\n/**\n * The neutral tercile a numeric prediction falls into. No moral direction is\n * implied — `low`/`mid`/`high` describe position on the value axis only.\n */\nexport type PredictionBand = 'low' | 'mid' | 'high';\n\n/** Generic problem types a trained model can have. */\nexport type ModelProblemType = 'classification' | 'regression';\n\n/** One normalized feature-importance driver for display. */\nexport interface PredictionDriver {\n /** Raw feature name as the model stored it. */\n name: string; // case-violation-ok-legacy-back-compat: the old name is also read off a value typed `any`, where a rename would compile and silently return undefined\n /** Importance magnitude (absolute value of the raw contribution). */\n importance: number; // case-violation-ok-legacy-back-compat: renaming it broke a use the checker could not see from the declaration — the compile proved it\n /** Importance as a 0–100 share of the strongest driver, for the bar width. */\n RelativePct: number;\n}\n\n/** How a prediction's primary value should be rendered. */\nexport type PredictionValueKind = 'probability' | 'numeric' | 'class';\n\n/** Maximum number of feature-importance drivers shown per card. */\nexport const MAX_DRIVERS = 5;\n\n/**\n * Coerce a dynamic field value into a finite number, or null when it isn't one.\n */\nexport function ToNumber(value: unknown): number | null {\n if (typeof value === 'number') {\n return Number.isFinite(value) ? value : null;\n }\n if (typeof value === 'string' && value.trim() !== '') {\n const n = Number(value);\n return Number.isFinite(n) ? n : null;\n }\n return null;\n}\n\n/** @deprecated Use {@link ToNumber}. */\nexport function toNumber(value: unknown): number | null {\n return ToNumber(value);\n}\n\n/**\n * Decide how to render a value given the model's problem type and the value.\n * - regression + numeric → 'numeric'\n * - classification + value in [0,1] → 'probability' (a confidence/score we can gauge)\n * - everything else (a class label, a missing value) → 'class'\n */\nexport function ValueKind(problemType: ModelProblemType, numeric: number | null): PredictionValueKind {\n const isRegression = problemType === 'regression';\n if (isRegression && numeric != null) {\n return 'numeric';\n }\n if (!isRegression && numeric != null && numeric >= 0 && numeric <= 1) {\n return 'probability';\n }\n return 'class';\n}\n\n/** @deprecated Use {@link ValueKind}. */\nexport function valueKind(problemType: ModelProblemType, numeric: number | null): PredictionValueKind {\n return ValueKind(problemType, numeric);\n}\n\n/**\n * Neutral tercile for a 0–1 value. Position on the value axis only — no\n * assumption that high is good or bad. Values are clamped to [0,1].\n */\nexport function BandFor(value: number): PredictionBand {\n const v = Math.max(0, Math.min(1, value));\n if (v < 1 / 3) return 'low';\n if (v < 2 / 3) return 'mid';\n return 'high';\n}\n\n/** @deprecated Use {@link BandFor}. */\nexport function bandFor(value: number): PredictionBand {\n return BandFor(value);\n}\n\n/** Round a 0–1 probability to an integer 0–100 gauge fill, clamped. */\nexport function GaugePct(value: number): number {\n return Math.round(Math.max(0, Math.min(1, value)) * 100);\n}\n\n/** @deprecated Use {@link GaugePct}. */\nexport function gaugePct(value: number): number {\n return GaugePct(value);\n}\n\n/**\n * Format the primary value for display:\n * - probability → \"72%\"\n * - numeric (regression) → grouped number, up to 4 decimals\n * - class label → the label string\n * - missing/empty → \"—\" (em dash)\n */\nexport function FormatValue(rawValue: unknown, numeric: number | null, kind: PredictionValueKind): string {\n if (kind === 'probability' && numeric != null) {\n return `${GaugePct(numeric)}%`;\n }\n if (kind === 'numeric' && numeric != null) {\n return numeric.toLocaleString(undefined, { maximumFractionDigits: 4 });\n }\n if (rawValue == null || rawValue === '') {\n return '—';\n }\n return String(rawValue);\n}\n\n/** @deprecated Use {@link FormatValue}. */\nexport function formatValue(rawValue: unknown, numeric: number | null, kind: PredictionValueKind): string {\n return FormatValue(rawValue, numeric, kind);\n}\n\n/**\n * Pick the human label for a prediction: prefer the model's target variable,\n * then the bound column name, then a generic fallback.\n */\nexport function ResolveLabel(targetVariable: string | null, targetColumn: string | null): string {\n const target = targetVariable?.trim();\n if (target) return target;\n const col = targetColumn?.trim();\n if (col) return col;\n return 'Prediction';\n}\n\n/** @deprecated Use {@link ResolveLabel}. */\nexport function resolveLabel(targetVariable: string | null, targetColumn: string | null): string {\n return ResolveLabel(targetVariable, targetColumn);\n}\n\n/**\n * Parse a model's `FeatureImportance` JSON (`Record<string, number>`) into the\n * top-N sorted drivers with relative bar widths. Returns [] on null / invalid /\n * empty input — the caller omits the drivers section cleanly when empty.\n */\nexport function ParseDrivers(featureImportanceJson: string | null): PredictionDriver[] {\n if (!featureImportanceJson) {\n return [];\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(featureImportanceJson);\n } catch {\n return [];\n }\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {\n return [];\n }\n const entries = Object.entries(parsed as Record<string, unknown>)\n .map(([name, raw]) => ({ name, importance: Math.abs(ToNumber(raw) ?? 0) }))\n .filter(d => d.importance > 0)\n .sort((a, b) => b.importance - a.importance)\n .slice(0, MAX_DRIVERS);\n\n const max = entries.length > 0 ? entries[0].importance : 0;\n return entries.map(d => ({\n ...d,\n RelativePct: max > 0 ? Math.round((d.importance / max) * 100) : 0,\n }));\n}\n\n/** @deprecated Use {@link ParseDrivers}. */\nexport function parseDrivers(featureImportanceJson: string | null): PredictionDriver[] {\n return ParseDrivers(featureImportanceJson);\n}\n\n/** The global drivers a prediction card may show. */\nexport class PredictionDrivers {\n /** Global drivers for a model's card: coefficient models only (split / impurity counts are not comparable across features and read as importance they are not, so they are hidden). */\n public static DriversForModel(lineageJSON: string | null | undefined, featureImportanceJson: string | null): PredictionDriver[] {\n return ImportanceDisplay.GlobalDriversShowable(lineageJSON) ? ParseDrivers(featureImportanceJson) : [];\n }\n\n /**\n * True when the model has stored importances that {@link DriversForModel} withholds because of their kind, so the card\n * shows {@link ImportanceDisplay.HiddenNote} in their place (the same note the model detail page shows).\n */\n public static DriversHidden(lineageJSON: string | null | undefined, featureImportanceJson: string | null): boolean {\n return !ImportanceDisplay.GlobalDriversShowable(lineageJSON) && ParseDrivers(featureImportanceJson).length > 0;\n }\n}\n\n/** Format a last-scored timestamp to a short date, or null when absent/invalid. */\nexport function FormatLastScored(lastScoredAt: Date | string | null): string | null {\n if (!lastScoredAt) {\n return null;\n }\n const d = lastScoredAt instanceof Date ? lastScoredAt : new Date(lastScoredAt);\n if (isNaN(d.getTime())) {\n return null;\n }\n return d.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' });\n}\n\n/** @deprecated Use {@link FormatLastScored}. */\nexport function formatLastScored(lastScoredAt: Date | string | null): string | null {\n return FormatLastScored(lastScoredAt);\n}\n\n/**\n * A resolved historical prediction row from `MJ: Process Run Details` for an entity record.\n */\nexport interface PredictionHistoryItem {\n /** The MJ: Process Run Details primary key ID */\n id: string; // case-violation-ok-legacy-back-compat: the old name is also read off a value typed `any`, where a rename would compile and silently return undefined\n /** The parent Process Run ID */\n ProcessRunId: string;\n /** Model ID if resolved from payload or model lookup */\n modelId: string | null; // case-violation-ok-legacy-back-compat: the old name is also read off a value typed `any`, where a rename would compile and silently return undefined\n /** Resolved model name (e.g. \"Member Churn Risk\" or \"LatePaymentOutcome\") */\n ModelName: string;\n /** Resolved pipeline & version string (e.g. \"Pipeline v1\") */\n Provenance: string;\n /** Target variable / label */\n target: string; // case-violation-ok-legacy-back-compat: the old name is also read off a value typed `any`, where a rename would compile and silently return undefined\n /** Problem type: classification or regression */\n problemType: ModelProblemType; // case-violation-ok-legacy-back-compat: the old name is also read off a value typed `any`, where a rename would compile and silently return undefined\n /** Primary numeric value / score */\n NumericValue: number | null;\n /** Class label if classification */\n PredictedClass: string | null;\n /** Formatted display value (e.g. \"88%\", \"Late\", \"1,240.5\") */\n DisplayValue: string;\n /** True if 0–1 probability we can gauge/badge */\n IsProbability: boolean;\n /** Neutral band: 'low' | 'mid' | 'high' */\n Band: PredictionBand | null;\n /** Resolved semantic status band from outcomeConfig */\n StatusBand: OutcomeBand | null;\n /** Human-readable status label (e.g. \"Low Risk\", \"High\") */\n StatusLabel: string | null;\n /** Semantic badge color ('green' | 'amber' | 'red' | 'blue' | 'gray') */\n BadgeColor: 'green' | 'amber' | 'red' | 'blue' | 'gray';\n /** Icon class if specified in band/style */\n BadgeIcon: string | null;\n /** Execution status: 'Succeeded' | 'Failed' | 'Pending' | 'Skipped' */\n status: string; // case-violation-ok-legacy-back-compat: the old name is also read off a value typed `any`, where a rename would compile and silently return undefined\n /** When processing completed */\n CompletedAt: Date | null;\n /** Formatted date/time for history display */\n FormattedTime: string;\n /** Drivers if available in payload */\n Drivers: PredictionDriver[];\n /** Error message if Failed */\n ErrorMessage: string | null;\n /** Full raw payload object for JSON inspection */\n RawPayload: Record<string, unknown> | null;\n}\n\n/** Summary descriptor of a model represented in prediction history. */\nexport interface ModelHistorySummary {\n modelId: string; // case-violation-ok-legacy-back-compat: the old name is also read off a value typed `any`, where a rename would compile and silently return undefined\n ModelName: string;\n count: number; // case-violation-ok-legacy-back-compat: the old name is also read off a value typed `any`, where a rename would compile and silently return undefined\n}\n\n/** Input contract for parsing a Process Run Detail record. */\nexport interface RawProcessRunDetail {\n ID: string;\n ProcessRunID: string;\n Status: string;\n CompletedAt: Date | string | null;\n ResultPayload: string | null;\n ErrorMessage: string | null;\n}\n\n/** Model metadata lookup contract for decorating history items. */\nexport interface HistoryModelMetadata {\n Name?: string;\n Pipeline?: string | null;\n Version?: number;\n ProblemType?: string;\n TargetVariable?: string;\n Lineage?: string | null;\n outcomeConfig?: OutcomeConfig | null; // case-violation-ok-legacy-back-compat: optional, and the old name is also read off a value the checker cannot type; renaming it stays assignable and silently yields undefined\n}\n\n/**\n * Format a history completion timestamp with date and time.\n */\nexport function FormatHistoryTimestamp(date: Date | string | null): string {\n if (!date) return '—';\n const d = date instanceof Date ? date : new Date(date);\n if (isNaN(d.getTime())) return '—';\n return d.toLocaleString(undefined, {\n month: 'short',\n day: 'numeric',\n year: 'numeric',\n hour: 'numeric',\n minute: '2-digit',\n });\n}\n\n/** @deprecated Use {@link FormatHistoryTimestamp}. */\nexport function formatHistoryTimestamp(date: Date | string | null): string {\n return FormatHistoryTimestamp(date);\n}\n\n/**\n * Parse an array of drivers from payload driver shapes (array of { feature, value } or map).\n */\nexport function ParsePayloadDrivers(rawDrivers: unknown): PredictionDriver[] {\n if (!rawDrivers) return [];\n if (Array.isArray(rawDrivers)) {\n const entries = rawDrivers\n .map((item: unknown) => {\n if (!item || typeof item !== 'object') return null;\n const rec = item as Record<string, unknown>;\n const name = typeof rec['feature'] === 'string' ? rec['feature'] : (typeof rec['name'] === 'string' ? rec['name'] : '');\n const val = ToNumber(rec['value'] ?? rec['importance']);\n if (!name || val == null) return null;\n return { name, importance: Math.abs(val) };\n })\n .filter((d): d is { name: string; importance: number } => d != null && d.importance > 0)\n .sort((a, b) => b.importance - a.importance)\n .slice(0, MAX_DRIVERS);\n\n const max = entries.length > 0 ? entries[0].importance : 0;\n return entries.map(d => ({\n ...d,\n RelativePct: max > 0 ? Math.round((d.importance / max) * 100) : 0,\n }));\n }\n if (typeof rawDrivers === 'object') {\n return ParseDrivers(JSON.stringify(rawDrivers));\n }\n return [];\n}\n\n/** @deprecated Use {@link ParsePayloadDrivers}. */\nexport function parsePayloadDrivers(rawDrivers: unknown): PredictionDriver[] {\n return ParsePayloadDrivers(rawDrivers);\n}\n\n/**\n * Parse a raw `MJ: Process Run Details` row into a decorated {@link PredictionHistoryItem}.\n */\nexport function ParseHistoryItem(\n detail: RawProcessRunDetail,\n modelLookup?: Map<string, HistoryModelMetadata>,\n): PredictionHistoryItem {\n let rawPayload: Record<string, unknown> | null = null;\n let payloadSection: Record<string, unknown> | null = null;\n\n if (detail.ResultPayload) {\n try {\n const parsed = JSON.parse(detail.ResultPayload);\n if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {\n rawPayload = parsed as Record<string, unknown>;\n const outputObj = rawPayload['output'];\n if (outputObj && typeof outputObj === 'object' && !Array.isArray(outputObj)) {\n payloadSection = outputObj as Record<string, unknown>;\n } else {\n payloadSection = rawPayload;\n }\n }\n } catch {\n rawPayload = null;\n payloadSection = null;\n }\n }\n\n const payloadModelId = typeof payloadSection?.['modelId'] === 'string' ? payloadSection['modelId'] : null;\n const modelMeta = payloadModelId && modelLookup ? modelLookup.get(payloadModelId) : undefined;\n\n const rawScore = payloadSection?.['score'] ?? payloadSection?.['value'] ?? null;\n const numericScore = ToNumber(rawScore);\n const predictedClass = typeof payloadSection?.['class'] === 'string' ? payloadSection['class'] : null;\n\n const probTypeStr = (typeof payloadSection?.['problemType'] === 'string' ? payloadSection['problemType'] : modelMeta?.ProblemType) ?? '';\n const problemType: ModelProblemType = probTypeStr.toLowerCase() === 'regression' ? 'regression' : 'classification';\n\n const kind = ValueKind(problemType, numericScore);\n const displayValue = predictedClass ?? FormatValue(rawScore, numericScore, kind);\n\n const targetVar = (typeof payloadSection?.['target'] === 'string' ? payloadSection['target'] : modelMeta?.TargetVariable) ?? '';\n const label = ResolveLabel(targetVar, null);\n\n const modelName = modelMeta?.Name ?? label;\n const provenance = modelMeta?.Pipeline && modelMeta?.Version\n ? `${modelMeta.Pipeline} v${modelMeta.Version}`\n : (modelMeta?.Name ?? 'Predictive Model');\n\n const completedAt = detail.CompletedAt ? (detail.CompletedAt instanceof Date ? detail.CompletedAt : new Date(detail.CompletedAt)) : null;\n const drivers = ParsePayloadDrivers(payloadSection?.['drivers']);\n\n const rawOutcomeConfig = (payloadSection?.['outcomeConfig'] ?? modelMeta?.outcomeConfig) as OutcomeConfig | undefined;\n const outcomeConfig = rawOutcomeConfig\n ? resolveOutcomeConfig({ outcomeConfig: rawOutcomeConfig })\n : resolveOutcomeConfig({\n Lineage: modelMeta?.Lineage ?? null,\n TargetVariable: targetVar || modelMeta?.TargetVariable || null,\n ProblemType: probTypeStr || modelMeta?.ProblemType || null,\n });\n\n let statusBand: OutcomeBand | null = null;\n let badgeColor: 'green' | 'amber' | 'red' | 'blue' | 'gray' = 'gray';\n let badgeIcon: string | null = null;\n let statusLabel: string | null = null;\n\n if (numericScore != null) {\n statusBand = resolveScoreBand(numericScore, outcomeConfig);\n if (statusBand) {\n badgeColor = statusBand.BadgeColor;\n badgeIcon = statusBand.Icon ?? null;\n statusLabel = statusBand.Label;\n }\n } else if (predictedClass) {\n const style = resolveOutcomeStyle(predictedClass, outcomeConfig);\n badgeColor = style.BadgeColor;\n badgeIcon = style.Icon ?? null;\n statusLabel = style.DisplayLabel ?? predictedClass;\n }\n\n return {\n id: detail.ID,\n ProcessRunId: detail.ProcessRunID,\n modelId: payloadModelId,\n ModelName: modelName,\n Provenance: provenance,\n target: targetVar || label,\n problemType,\n NumericValue: numericScore,\n PredictedClass: predictedClass,\n DisplayValue: displayValue,\n IsProbability: kind === 'probability',\n Band: kind === 'probability' && numericScore != null ? BandFor(numericScore) : null,\n StatusBand: statusBand,\n StatusLabel: statusLabel,\n BadgeColor: badgeColor,\n BadgeIcon: badgeIcon,\n status: detail.Status || 'Succeeded',\n CompletedAt: completedAt,\n FormattedTime: FormatHistoryTimestamp(completedAt),\n Drivers: drivers,\n ErrorMessage: detail.ErrorMessage,\n RawPayload: rawPayload,\n };\n}\n\n/** @deprecated Use {@link ParseHistoryItem}. */\nexport function parseHistoryItem(\n detail: RawProcessRunDetail,\n modelLookup?: Map<string, HistoryModelMetadata>,\n): PredictionHistoryItem {\n return ParseHistoryItem(detail, modelLookup);\n}\n\n/**\n * Filter prediction history by a selected model ID (or return all when null / 'ALL').\n */\nexport function FilterHistoryByModel(\n history: PredictionHistoryItem[],\n selectedModelId: string | null,\n): PredictionHistoryItem[] {\n if (!selectedModelId || selectedModelId === 'ALL') {\n return history;\n }\n return history.filter(item => item.modelId === selectedModelId);\n}\n\n/** @deprecated Use {@link FilterHistoryByModel}. */\nexport function filterHistoryByModel(\n history: PredictionHistoryItem[],\n selectedModelId: string | null,\n): PredictionHistoryItem[] {\n return FilterHistoryByModel(history, selectedModelId);\n}\n\n/**\n * Aggregate unique models from prediction history for multi-model tabs / filters.\n */\nexport function GetDistinctModelsFromHistory(history: PredictionHistoryItem[]): ModelHistorySummary[] {\n const counts = new Map<string, { ModelName: string; count: number }>();\n for (const item of history) {\n const id = item.modelId ?? 'UNKNOWN';\n const existing = counts.get(id);\n if (existing) {\n existing.count += 1;\n } else {\n counts.set(id, { ModelName: item.ModelName, count: 1 });\n }\n }\n return Array.from(counts.entries()).map(([modelId, data]) => ({\n modelId,\n ModelName: data.ModelName,\n count: data.count,\n }));\n}\n\n/** @deprecated Use {@link GetDistinctModelsFromHistory}. */\nexport function getDistinctModelsFromHistory(history: PredictionHistoryItem[]): ModelHistorySummary[] {\n return GetDistinctModelsFromHistory(history);\n}\n"]}
|
|
@@ -1291,11 +1291,11 @@ export class TemplateEditorComponent extends BaseAngularComponent {
|
|
|
1291
1291
|
i0.ɵɵconditionalCreate(0, TemplateEditorComponent_Conditional_0_Template, 4, 1, "div", 1);
|
|
1292
1292
|
} if (rf & 2) {
|
|
1293
1293
|
i0.ɵɵconditional(ctx.template ? 0 : -1);
|
|
1294
|
-
} }, dependencies: [i3.CheckboxControlValueAccessor, i3.NgControlStatus, i3.NgModel, i2.MJButtonDirective, i2.MJDropdownComponent, i2.MJNumericInputComponent, i4.CodeEditorComponent, i5.LoadingComponent], styles: [".template-editor[_ngcontent-%COMP%] {\n font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;\n}\n\n.section-header[_ngcontent-%COMP%] h6[_ngcontent-%COMP%] {\n font-size: 0.9em;\n}\n\n.param-row[_ngcontent-%COMP%]:hover {\n background-color: var(--mj-bg-surface-card) !important;\n}\n\n.content-item[_ngcontent-%COMP%] {\n transition: box-shadow 0.2s ease;\n}\n\n.content-item[_ngcontent-%COMP%]:hover {\n box-shadow: 0 2px 4px var(--mj-shadow-sm);\n}\n\n.loading-state[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n}\n\n.empty-state[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 8px;\n}\n\n.badge[_ngcontent-%COMP%] {\n display: inline-block;\n font-size: 0.7em;\n line-height: 1;\n}\n\n\n.template-editor[_ngcontent-%COMP%] .mj-input[_ngcontent-%COMP%], \n.template-editor[_ngcontent-%COMP%] .mj-textarea[_ngcontent-%COMP%], \n.template-editor[_ngcontent-%COMP%] mj-dropdown[_ngcontent-%COMP%] {\n width: 100%;\n}\n\n.template-editor[_ngcontent-%COMP%] [mjButton][_ngcontent-%COMP%] {\n min-width: auto;\n}\n\n\n.content-versions-tabs[_ngcontent-%COMP%] {\n margin-bottom: 16px;\n border-bottom: 2px solid var(--mj-border-default);\n padding-bottom: 8px;\n}\n\n.content-tabs[_ngcontent-%COMP%] {\n display: flex;\n gap: 6px;\n flex-wrap: wrap;\n}\n\n.content-version-tab[_ngcontent-%COMP%] {\n padding: 8px 12px;\n border: 1px solid var(--mj-border-strong);\n border-radius: 6px;\n cursor: pointer;\n background: var(--mj-bg-surface);\n display: flex;\n align-items: center;\n gap: 6px;\n transition: all 0.2s;\n position: relative;\n}\n\n.content-version-tab.active[_ngcontent-%COMP%] {\n border-color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n}\n\n.content-version-tab[_ngcontent-%COMP%] .tab-label[_ngcontent-%COMP%] {\n font-weight: 600;\n font-size: 0.85em;\n color: var(--mj-text-secondary);\n}\n\n.content-version-tab[_ngcontent-%COMP%] .tab-meta[_ngcontent-%COMP%] {\n font-size: 0.7em;\n color: var(--mj-text-muted);\n}\n\n.content-version-tab[_ngcontent-%COMP%] .tab-meta[_ngcontent-%COMP%] .inactive-label[_ngcontent-%COMP%] {\n color: var(--mj-status-error);\n}\n\n.content-version-tab[_ngcontent-%COMP%] .delete-btn[_ngcontent-%COMP%] {\n background: none;\n border: none;\n color: var(--mj-status-error);\n padding: 4px;\n border-radius: 3px;\n transition: all 0.2s;\n cursor: pointer;\n}\n\n.content-version-tab[_ngcontent-%COMP%] .delete-btn[_ngcontent-%COMP%]:hover {\n background-color: var(--mj-status-error);\n color: var(--mj-text-inverse);\n}\n\n\n.content-version-tab.adding-new[_ngcontent-%COMP%] {\n border-color: var(--mj-status-success);\n background: color-mix(in srgb, var(--mj-status-success) 10%, var(--mj-bg-surface));\n}\n\n.content-version-tab.adding-new[_ngcontent-%COMP%] .new-label[_ngcontent-%COMP%] {\n font-weight: 600;\n color: var(--mj-status-success);\n font-size: 0.85em;\n}\n\n\n.content-version-tab.add-new[_ngcontent-%COMP%] {\n border: 1px dashed var(--mj-status-success);\n background: var(--mj-bg-surface-card);\n}\n\n.content-version-tab.add-new[_ngcontent-%COMP%]:hover {\n background: color-mix(in srgb, var(--mj-status-success) 8%, var(--mj-bg-surface));\n border-style: solid;\n}\n\n.content-version-tab.add-new[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-status-success);\n font-size: 0.75em;\n}\n\n.content-version-tab.add-new[_ngcontent-%COMP%] span[_ngcontent-%COMP%] {\n font-weight: 500;\n color: var(--mj-status-success);\n font-size: 0.85em;\n}\n\n\n.content-editor-container[_ngcontent-%COMP%] {\n flex: 1;\n display: flex;\n flex-direction: column;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n overflow: hidden;\n}\n\n\n.content-metadata-bar[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface-card);\n border-bottom: 1px solid var(--mj-border-default);\n padding: 16px 20px;\n}\n\n.metadata-grid[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: 2fr 1fr 1fr auto;\n gap: 20px;\n align-items: end;\n}\n\n.form-group[_ngcontent-%COMP%] {\n margin: 0;\n}\n\n.form-label[_ngcontent-%COMP%] {\n margin-bottom: 4px;\n font-size: 0.9em;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.form-control-plaintext[_ngcontent-%COMP%] {\n font-size: 0.9em;\n padding: 4px 0;\n}\n\n.form-check[_ngcontent-%COMP%] {\n margin-top: 8px;\n}\n\n.form-check-label[_ngcontent-%COMP%] {\n font-size: 0.9em;\n}\n\n.badge-success[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-status-success) 15%, var(--mj-bg-surface));\n color: var(--mj-status-success);\n border: 1px solid color-mix(in srgb, var(--mj-status-success) 30%, transparent);\n padding: 3px 8px;\n border-radius: 10px;\n font-size: 0.75em;\n font-weight: 600;\n}\n\n.badge-secondary[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-text-muted) 15%, var(--mj-bg-surface));\n color: var(--mj-text-muted);\n border: 1px solid color-mix(in srgb, var(--mj-text-muted) 30%, transparent);\n padding: 3px 8px;\n border-radius: 10px;\n font-size: 0.75em;\n font-weight: 600;\n}\n\n\n.content-text-editor[_ngcontent-%COMP%] {\n flex: 1;\n padding: 20px;\n display: flex;\n flex-direction: column;\n}\n\n.editor-header[_ngcontent-%COMP%] {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 12px;\n}\n\n.editor-label[_ngcontent-%COMP%] {\n margin: 0;\n font-weight: 600;\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.content-type-badge[_ngcontent-%COMP%] {\n padding: 4px 8px;\n border-radius: 4px;\n font-size: 0.75em;\n color: var(--mj-text-inverse);\n}\n\n.content-type-badge.new[_ngcontent-%COMP%] {\n background: var(--mj-status-success);\n}\n\n.content-type-badge.existing[_ngcontent-%COMP%] {\n background: var(--mj-text-muted);\n}\n\n.editor-actions[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 12px;\n}\n\n.unsaved-indicator[_ngcontent-%COMP%] {\n color: var(--mj-status-warning);\n font-size: 0.85em;\n display: flex;\n align-items: center;\n gap: 4px;\n}\n\n.unsaved-indicator[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 0.5em;\n}\n\n.run-btn[_ngcontent-%COMP%] {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n border: none;\n padding: 6px 12px;\n border-radius: 4px;\n font-size: 0.85em;\n display: flex;\n align-items: center;\n gap: 6px;\n transition: all 0.2s;\n cursor: pointer;\n}\n\n.run-btn[_ngcontent-%COMP%]:hover {\n background: var(--mj-brand-primary-hover);\n}\n\n\n.code-editor-wrapper[_ngcontent-%COMP%] {\n flex: 1;\n height: 350px;\n border: 1px solid var(--mj-border-strong);\n border-radius: 4px;\n}\n\n\n.template-help-section[_ngcontent-%COMP%] {\n margin-top: 16px;\n}\n\n.help-tabs[_ngcontent-%COMP%] {\n display: flex;\n border-bottom: 2px solid var(--mj-border-default);\n margin-bottom: 0;\n}\n\n.help-tab[_ngcontent-%COMP%] {\n padding: 8px 16px;\n border: none;\n background: none;\n cursor: pointer;\n font-size: 0.9em;\n font-weight: 600;\n color: var(--mj-text-muted);\n border-bottom: 3px solid transparent;\n transition: all 0.2s;\n}\n\n.help-tab.active[_ngcontent-%COMP%] {\n color: var(--mj-status-success);\n border-bottom-color: var(--mj-status-success);\n}\n\n\n.help-content[_ngcontent-%COMP%] {\n padding: 16px;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default);\n border-top: none;\n border-radius: 0 0 4px 4px;\n}\n\n\n.syntax-help[_ngcontent-%COMP%] {\n font-size: 0.85em;\n}\n\n.syntax-help[_ngcontent-%COMP%] .section-title[_ngcontent-%COMP%] {\n font-weight: 600;\n margin-bottom: 12px;\n color: var(--mj-status-success);\n}\n\n.syntax-grid[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 16px;\n}\n\n.syntax-item[_ngcontent-%COMP%] {\n margin-bottom: 8px;\n}\n\n.syntax-help[_ngcontent-%COMP%] code[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface);\n padding: 4px 6px;\n border-radius: 3px;\n display: inline-block;\n margin-top: 4px;\n color: var(--mj-text-primary);\n}\n\n\n.placeholders-help[_ngcontent-%COMP%] {\n font-size: 0.85em;\n}\n\n.placeholders-help[_ngcontent-%COMP%] .section-title[_ngcontent-%COMP%] {\n font-weight: 600;\n margin-bottom: 12px;\n color: var(--mj-status-success);\n}\n\n.placeholders-description[_ngcontent-%COMP%] {\n margin-bottom: 16px;\n color: var(--mj-text-secondary);\n line-height: 1.5;\n}\n\n\n.category-tabs[_ngcontent-%COMP%] {\n display: flex;\n gap: 8px;\n margin-bottom: 16px;\n flex-wrap: wrap;\n}\n\n.category-tab[_ngcontent-%COMP%] {\n padding: 6px 14px;\n border: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface);\n border-radius: 20px;\n cursor: pointer;\n font-size: 0.85em;\n font-weight: 500;\n display: flex;\n align-items: center;\n gap: 6px;\n transition: all 0.2s;\n color: var(--mj-text-muted);\n}\n\n.category-tab.active[_ngcontent-%COMP%] {\n border-color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n}\n\n.category-tab[_ngcontent-%COMP%] .tab-badge[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-text-primary) 10%, transparent);\n color: inherit;\n padding: 2px 6px;\n border-radius: 10px;\n font-size: 0.75em;\n margin-left: 4px;\n}\n\n\n.placeholder-content[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n padding: 16px;\n}\n\n.category-header[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n margin-bottom: 16px;\n}\n\n.category-header[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 1.2em;\n}\n\n.category-header[_ngcontent-%COMP%] h5[_ngcontent-%COMP%] {\n margin: 0;\n color: var(--mj-text-primary);\n font-weight: 600;\n}\n\n.placeholder-grid[_ngcontent-%COMP%] {\n display: grid;\n gap: 10px;\n}\n\n\n.placeholder-item[_ngcontent-%COMP%] {\n padding: 12px 16px;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n position: relative;\n transition: all 0.2s;\n max-width: 800px;\n}\n\n.placeholder-item[_ngcontent-%COMP%]:hover {\n border-color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 5%, var(--mj-bg-surface));\n}\n\n.placeholder-item[_ngcontent-%COMP%]:hover .copy-btn[_ngcontent-%COMP%] {\n opacity: 1 !important;\n}\n\n.placeholder-item-inner[_ngcontent-%COMP%] {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n}\n\n.placeholder-name-row[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n}\n\n.placeholder-code[_ngcontent-%COMP%] {\n color: var(--mj-status-error);\n font-weight: 600;\n font-size: 0.95em;\n}\n\n.copy-btn[_ngcontent-%COMP%] {\n opacity: 0;\n transition: opacity 0.2s;\n background: none;\n border: none;\n padding: 4px 8px;\n cursor: pointer;\n color: var(--mj-brand-primary);\n border-radius: 4px;\n display: inline-flex;\n align-items: center;\n}\n\n.copy-btn[_ngcontent-%COMP%]:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n}\n\n.copy-btn[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 0.85em;\n}\n\n.placeholder-description[_ngcontent-%COMP%] {\n color: var(--mj-text-muted);\n margin-top: 4px;\n line-height: 1.4;\n}\n\n\n.usage-example[_ngcontent-%COMP%] {\n margin-top: 20px;\n padding: 12px;\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, var(--mj-bg-surface));\n border-radius: 6px;\n border: 1px solid color-mix(in srgb, var(--mj-brand-primary) 20%, var(--mj-bg-surface));\n}\n\n.usage-example-inner[_ngcontent-%COMP%] {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n}\n\n.usage-example[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n margin-top: 2px;\n}\n\n.usage-example-text[_ngcontent-%COMP%] {\n flex: 1;\n font-size: 0.85em;\n color: var(--mj-brand-primary);\n line-height: 1.5;\n}\n\n.usage-example[_ngcontent-%COMP%] code[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface);\n padding: 2px 4px;\n border-radius: 3px;\n}\n\n\n.no-content-state[_ngcontent-%COMP%], \n.contents-load-failed[_ngcontent-%COMP%] {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n background: var(--mj-bg-surface-card);\n border: 2px dashed var(--mj-border-default);\n border-radius: 8px;\n padding: 40px;\n text-align: center;\n}\n\n.no-content-state[_ngcontent-%COMP%] i[_ngcontent-%COMP%], \n.contents-load-failed[_ngcontent-%COMP%] > i[_ngcontent-%COMP%] {\n font-size: 48px;\n margin-bottom: 16px;\n color: var(--mj-text-muted);\n opacity: 0.6;\n}\n\n.no-content-state[_ngcontent-%COMP%] h5[_ngcontent-%COMP%], \n.contents-load-failed[_ngcontent-%COMP%] h5[_ngcontent-%COMP%] {\n color: var(--mj-text-secondary);\n margin-bottom: 8px;\n}\n\n.no-content-state[_ngcontent-%COMP%] p[_ngcontent-%COMP%], \n.contents-load-failed[_ngcontent-%COMP%] p[_ngcontent-%COMP%] {\n color: var(--mj-text-muted);\n margin-bottom: 20px;\n}\n\n.contents-load-failed[_ngcontent-%COMP%] {\n border-color: var(--mj-status-error);\n}\n\n\n.contents-load-failed[_ngcontent-%COMP%] > i[_ngcontent-%COMP%] {\n color: var(--mj-status-error);\n opacity: 1;\n}"] }); }
|
|
1294
|
+
} }, dependencies: [i3.CheckboxControlValueAccessor, i3.NgControlStatus, i3.NgModel, i2.MJButtonDirective, i2.MJDropdownComponent, i2.MJNumericInputComponent, i4.CodeEditorComponent, i5.LoadingComponent], styles: [".template-editor[_ngcontent-%COMP%] {\n font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;\n}\n\n.section-header[_ngcontent-%COMP%] h6[_ngcontent-%COMP%] {\n font-size: 0.9em;\n}\n\n.param-row[_ngcontent-%COMP%]:hover {\n background-color: var(--mj-bg-surface-card) !important;\n}\n\n.content-item[_ngcontent-%COMP%] {\n transition: box-shadow 0.2s ease;\n}\n\n.content-item[_ngcontent-%COMP%]:hover {\n box-shadow: 0 2px 4px var(--mj-shadow-sm);\n}\n\n.loading-state[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n}\n\n.empty-state[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 8px;\n}\n\n.badge[_ngcontent-%COMP%] {\n display: inline-block;\n font-size: 0.7em;\n line-height: 1;\n}\n\n\n.template-editor[_ngcontent-%COMP%] .mj-input[_ngcontent-%COMP%], \n.template-editor[_ngcontent-%COMP%] .mj-textarea[_ngcontent-%COMP%], \n.template-editor[_ngcontent-%COMP%] mj-dropdown[_ngcontent-%COMP%] {\n width: 100%;\n}\n\n.template-editor[_ngcontent-%COMP%] [mjButton][_ngcontent-%COMP%] {\n min-width: auto;\n}\n\n\n.content-versions-tabs[_ngcontent-%COMP%] {\n margin-bottom: 16px;\n border-bottom: 2px solid var(--mj-border-default);\n padding-bottom: 8px;\n}\n\n.content-tabs[_ngcontent-%COMP%] {\n display: flex;\n gap: 6px;\n flex-wrap: wrap;\n}\n\n.content-version-tab[_ngcontent-%COMP%] {\n padding: 8px 12px;\n border: 1px solid var(--mj-border-strong);\n border-radius: 6px;\n cursor: pointer;\n background: var(--mj-bg-surface);\n display: flex;\n align-items: center;\n gap: 6px;\n transition: all 0.2s;\n position: relative;\n}\n\n.content-version-tab.active[_ngcontent-%COMP%] {\n border-color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n}\n\n.content-version-tab[_ngcontent-%COMP%] .tab-label[_ngcontent-%COMP%] {\n font-weight: 600;\n font-size: 0.85em;\n color: var(--mj-text-secondary);\n}\n\n.content-version-tab[_ngcontent-%COMP%] .tab-meta[_ngcontent-%COMP%] {\n font-size: 0.7em;\n color: var(--mj-text-muted);\n}\n\n.content-version-tab[_ngcontent-%COMP%] .tab-meta[_ngcontent-%COMP%] .inactive-label[_ngcontent-%COMP%] {\n color: var(--mj-status-error);\n}\n\n.content-version-tab[_ngcontent-%COMP%] .delete-btn[_ngcontent-%COMP%] {\n background: none;\n border: none;\n color: var(--mj-status-error);\n padding: 4px;\n border-radius: 3px;\n transition: all 0.2s;\n cursor: pointer;\n}\n\n.content-version-tab[_ngcontent-%COMP%] .delete-btn[_ngcontent-%COMP%]:hover {\n background-color: var(--mj-status-error);\n color: var(--mj-text-inverse);\n}\n\n\n.content-version-tab.adding-new[_ngcontent-%COMP%] {\n border-color: var(--mj-status-success);\n background: color-mix(in srgb, var(--mj-status-success) 10%, var(--mj-bg-surface));\n}\n\n.content-version-tab.adding-new[_ngcontent-%COMP%] .new-label[_ngcontent-%COMP%] {\n font-weight: 600;\n color: var(--mj-status-success);\n font-size: 0.85em;\n}\n\n\n.content-version-tab.add-new[_ngcontent-%COMP%] {\n border: 1px dashed var(--mj-status-success);\n background: var(--mj-bg-surface-card);\n}\n\n.content-version-tab.add-new[_ngcontent-%COMP%]:hover {\n background: color-mix(in srgb, var(--mj-status-success) 8%, var(--mj-bg-surface));\n border-style: solid;\n}\n\n.content-version-tab.add-new[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-status-success);\n font-size: 0.75em;\n}\n\n.content-version-tab.add-new[_ngcontent-%COMP%] span[_ngcontent-%COMP%] {\n font-weight: 500;\n color: var(--mj-status-success);\n font-size: 0.85em;\n}\n\n\n.content-editor-container[_ngcontent-%COMP%] {\n flex: 1;\n display: flex;\n flex-direction: column;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n overflow: hidden;\n}\n\n\n.content-metadata-bar[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface-card);\n border-bottom: 1px solid var(--mj-border-default);\n padding: 16px 20px;\n}\n\n.metadata-grid[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: 2fr 1fr 1fr auto;\n gap: 20px;\n align-items: end;\n}\n\n.form-group[_ngcontent-%COMP%] {\n margin: 0;\n}\n\n.form-label[_ngcontent-%COMP%] {\n margin-bottom: 4px;\n font-size: 0.9em;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.form-control-plaintext[_ngcontent-%COMP%] {\n font-size: 0.9em;\n padding: 4px 0;\n}\n\n.form-check[_ngcontent-%COMP%] {\n margin-top: 8px;\n}\n\n.form-check-label[_ngcontent-%COMP%] {\n font-size: 0.9em;\n}\n\n.badge-success[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-status-success) 15%, var(--mj-bg-surface));\n color: var(--mj-status-success);\n border: 1px solid color-mix(in srgb, var(--mj-status-success) 30%, transparent);\n padding: 3px 8px;\n border-radius: 10px;\n font-size: 0.75em;\n font-weight: 600;\n}\n\n.badge-secondary[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-text-muted) 15%, var(--mj-bg-surface));\n color: var(--mj-text-muted);\n border: 1px solid color-mix(in srgb, var(--mj-text-muted) 30%, transparent);\n padding: 3px 8px;\n border-radius: 10px;\n font-size: 0.75em;\n font-weight: 600;\n}\n\n\n.content-text-editor[_ngcontent-%COMP%] {\n flex: 1;\n padding: 20px;\n display: flex;\n flex-direction: column;\n}\n\n.editor-header[_ngcontent-%COMP%] {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 12px;\n}\n\n.editor-label[_ngcontent-%COMP%] {\n margin: 0;\n font-weight: 600;\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.content-type-badge[_ngcontent-%COMP%] {\n padding: 4px 8px;\n border-radius: 4px;\n font-size: 0.75em;\n color: var(--mj-text-inverse);\n}\n\n.content-type-badge.new[_ngcontent-%COMP%] {\n background: var(--mj-status-success);\n}\n\n.content-type-badge.existing[_ngcontent-%COMP%] {\n background: var(--mj-text-muted);\n}\n\n.editor-actions[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 12px;\n}\n\n.unsaved-indicator[_ngcontent-%COMP%] {\n color: var(--mj-status-warning);\n font-size: 0.85em;\n display: flex;\n align-items: center;\n gap: 4px;\n}\n\n.unsaved-indicator[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 0.5em;\n}\n\n.run-btn[_ngcontent-%COMP%] {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n border: none;\n padding: 6px 12px;\n border-radius: 4px;\n font-size: 0.85em;\n display: flex;\n align-items: center;\n gap: 6px;\n transition: all 0.2s;\n cursor: pointer;\n}\n\n.run-btn[_ngcontent-%COMP%]:hover {\n background: var(--mj-brand-primary-hover);\n}\n\n\n.code-editor-wrapper[_ngcontent-%COMP%] {\n flex: 1;\n height: 350px;\n border: 1px solid var(--mj-border-strong);\n border-radius: 4px;\n}\n\n\n.template-help-section[_ngcontent-%COMP%] {\n margin-top: 16px;\n}\n\n.help-tabs[_ngcontent-%COMP%] {\n display: flex;\n border-bottom: 2px solid var(--mj-border-default);\n margin-bottom: 0;\n}\n\n.help-tab[_ngcontent-%COMP%] {\n padding: 8px 16px;\n border: none;\n background: none;\n cursor: pointer;\n font-size: 0.9em;\n font-weight: 600;\n color: var(--mj-text-muted);\n border-bottom: 3px solid transparent;\n transition: all 0.2s;\n}\n\n.help-tab.active[_ngcontent-%COMP%] {\n color: var(--mj-status-success);\n border-bottom-color: var(--mj-status-success);\n}\n\n\n.help-content[_ngcontent-%COMP%] {\n padding: 16px;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default);\n border-top: none;\n border-radius: 0 0 4px 4px;\n}\n\n\n.syntax-help[_ngcontent-%COMP%] {\n font-size: 0.85em;\n}\n\n.syntax-help[_ngcontent-%COMP%] .section-title[_ngcontent-%COMP%] {\n font-weight: 600;\n margin-bottom: 12px;\n color: var(--mj-status-success);\n}\n\n.syntax-grid[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 16px;\n}\n\n\n\n\n@media (max-width: 768px) {\n .syntax-grid[_ngcontent-%COMP%] {\n grid-template-columns: minmax(0, 1fr);\n }\n\n .metadata-grid[_ngcontent-%COMP%] {\n grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);\n gap: 12px;\n }\n\n .metadata-grid[_ngcontent-%COMP%] > .form-group[_ngcontent-%COMP%]:first-child {\n grid-column: 1 / -1;\n }\n}\n\n.syntax-item[_ngcontent-%COMP%] {\n margin-bottom: 8px;\n}\n\n.syntax-help[_ngcontent-%COMP%] code[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface);\n padding: 4px 6px;\n border-radius: 3px;\n display: inline-block;\n margin-top: 4px;\n color: var(--mj-text-primary);\n}\n\n\n.placeholders-help[_ngcontent-%COMP%] {\n font-size: 0.85em;\n}\n\n.placeholders-help[_ngcontent-%COMP%] .section-title[_ngcontent-%COMP%] {\n font-weight: 600;\n margin-bottom: 12px;\n color: var(--mj-status-success);\n}\n\n.placeholders-description[_ngcontent-%COMP%] {\n margin-bottom: 16px;\n color: var(--mj-text-secondary);\n line-height: 1.5;\n}\n\n\n.category-tabs[_ngcontent-%COMP%] {\n display: flex;\n gap: 8px;\n margin-bottom: 16px;\n flex-wrap: wrap;\n}\n\n.category-tab[_ngcontent-%COMP%] {\n padding: 6px 14px;\n border: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface);\n border-radius: 20px;\n cursor: pointer;\n font-size: 0.85em;\n font-weight: 500;\n display: flex;\n align-items: center;\n gap: 6px;\n transition: all 0.2s;\n color: var(--mj-text-muted);\n}\n\n.category-tab.active[_ngcontent-%COMP%] {\n border-color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n}\n\n.category-tab[_ngcontent-%COMP%] .tab-badge[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-text-primary) 10%, transparent);\n color: inherit;\n padding: 2px 6px;\n border-radius: 10px;\n font-size: 0.75em;\n margin-left: 4px;\n}\n\n\n.placeholder-content[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n padding: 16px;\n}\n\n.category-header[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n margin-bottom: 16px;\n}\n\n.category-header[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 1.2em;\n}\n\n.category-header[_ngcontent-%COMP%] h5[_ngcontent-%COMP%] {\n margin: 0;\n color: var(--mj-text-primary);\n font-weight: 600;\n}\n\n.placeholder-grid[_ngcontent-%COMP%] {\n display: grid;\n gap: 10px;\n}\n\n\n.placeholder-item[_ngcontent-%COMP%] {\n padding: 12px 16px;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n position: relative;\n transition: all 0.2s;\n max-width: 800px;\n}\n\n.placeholder-item[_ngcontent-%COMP%]:hover {\n border-color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 5%, var(--mj-bg-surface));\n}\n\n.placeholder-item[_ngcontent-%COMP%]:hover .copy-btn[_ngcontent-%COMP%] {\n opacity: 1 !important;\n}\n\n.placeholder-item-inner[_ngcontent-%COMP%] {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n}\n\n.placeholder-name-row[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n}\n\n.placeholder-code[_ngcontent-%COMP%] {\n color: var(--mj-status-error);\n font-weight: 600;\n font-size: 0.95em;\n}\n\n.copy-btn[_ngcontent-%COMP%] {\n opacity: 0;\n transition: opacity 0.2s;\n background: none;\n border: none;\n padding: 4px 8px;\n cursor: pointer;\n color: var(--mj-brand-primary);\n border-radius: 4px;\n display: inline-flex;\n align-items: center;\n}\n\n.copy-btn[_ngcontent-%COMP%]:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n}\n\n.copy-btn[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 0.85em;\n}\n\n.placeholder-description[_ngcontent-%COMP%] {\n color: var(--mj-text-muted);\n margin-top: 4px;\n line-height: 1.4;\n}\n\n\n.usage-example[_ngcontent-%COMP%] {\n margin-top: 20px;\n padding: 12px;\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, var(--mj-bg-surface));\n border-radius: 6px;\n border: 1px solid color-mix(in srgb, var(--mj-brand-primary) 20%, var(--mj-bg-surface));\n}\n\n.usage-example-inner[_ngcontent-%COMP%] {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n}\n\n.usage-example[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n margin-top: 2px;\n}\n\n.usage-example-text[_ngcontent-%COMP%] {\n flex: 1;\n font-size: 0.85em;\n color: var(--mj-brand-primary);\n line-height: 1.5;\n}\n\n.usage-example[_ngcontent-%COMP%] code[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface);\n padding: 2px 4px;\n border-radius: 3px;\n}\n\n\n.no-content-state[_ngcontent-%COMP%], \n.contents-load-failed[_ngcontent-%COMP%] {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n background: var(--mj-bg-surface-card);\n border: 2px dashed var(--mj-border-default);\n border-radius: 8px;\n padding: 40px;\n text-align: center;\n}\n\n.no-content-state[_ngcontent-%COMP%] i[_ngcontent-%COMP%], \n.contents-load-failed[_ngcontent-%COMP%] > i[_ngcontent-%COMP%] {\n font-size: 48px;\n margin-bottom: 16px;\n color: var(--mj-text-muted);\n opacity: 0.6;\n}\n\n.no-content-state[_ngcontent-%COMP%] h5[_ngcontent-%COMP%], \n.contents-load-failed[_ngcontent-%COMP%] h5[_ngcontent-%COMP%] {\n color: var(--mj-text-secondary);\n margin-bottom: 8px;\n}\n\n.no-content-state[_ngcontent-%COMP%] p[_ngcontent-%COMP%], \n.contents-load-failed[_ngcontent-%COMP%] p[_ngcontent-%COMP%] {\n color: var(--mj-text-muted);\n margin-bottom: 20px;\n}\n\n.contents-load-failed[_ngcontent-%COMP%] {\n border-color: var(--mj-status-error);\n}\n\n\n.contents-load-failed[_ngcontent-%COMP%] > i[_ngcontent-%COMP%] {\n color: var(--mj-status-error);\n opacity: 1;\n}"] }); }
|
|
1295
1295
|
}
|
|
1296
1296
|
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(TemplateEditorComponent, [{
|
|
1297
1297
|
type: Component,
|
|
1298
|
-
args: [{ standalone: false, selector: 'mj-template-editor', template: "@if (template) {\n <div class=\"template-editor\" style=\"height: 100%; display: flex; flex-direction: column;\">\n @if (ContentsLoadState === 'loading') {\n <mj-loading text=\"Loading template contents...\" size=\"medium\"></mj-loading>\n } @else if (ContentsLoadState === 'failed') {\n <!-- The read failed: the template's contents are unknown, so neither \"no content\" nor \"create\" -->\n <div class=\"contents-load-failed\">\n <i class=\"fa-solid fa-triangle-exclamation\"></i>\n <h5>Template contents could not be loaded</h5>\n <p>Nothing is shown so nothing can be saved over contents that were not read.</p>\n <button type=\"button\" mjButton variant=\"secondary\" size=\"sm\" (click)=\"RefreshAndDiscardChanges()\">\n <i class=\"fa-solid fa-rotate-right\"></i> Retry\n </button>\n </div>\n } @else {\n <!-- Content Versions List/Tabs -->\n @if (templateContents.length > 0) {\n <div class=\"content-versions-tabs\">\n <div class=\"content-tabs\">\n @for (content of templateContents; track content.ID || $index; let i = $index) {\n <div class=\"content-version-tab\"\n [class.active]=\"selectedContentIndex === i && !isAddingNewContent\"\n (click)=\"selectTemplateContent(i, false)\">\n <div style=\"flex: 1;\">\n <div class=\"tab-label\">\n <!-- From TypeID (what the Content Type dropdown edits); the view-joined Type field is only refreshed on reload -->\n {{ content.TypeID ? GetContentTypeDisplayText(content.TypeID) : 'Version ' + (i + 1) }}\n </div>\n <div class=\"tab-meta\">\n P:{{ content.Priority }}\n @if (!content.IsActive) {\n \u2022 <span class=\"inactive-label\">Inactive</span>\n }\n </div>\n </div>\n @if (templateContents.length > 1 && config.allowEdit) {\n <button type=\"button\"\n (click)=\"deleteTemplateContent(i); $event.stopPropagation()\"\n class=\"delete-btn\"\n title=\"Delete this content version\">\n <i class=\"fa-solid fa-trash fa-xs\"></i>\n </button>\n }\n </div>\n }\n @if (isAddingNewContent) {\n <div class=\"content-version-tab adding-new\">\n <i class=\"fa-solid fa-plus text-success\"></i>\n <span class=\"new-label\">New</span>\n </div>\n }\n <!-- Add New Content (Edit Mode Only) -->\n @if (config.allowEdit && !isAddingNewContent) {\n <div class=\"content-version-tab add-new\"\n (click)=\"addNewTemplateContent()\"\n title=\"Add new template content\">\n <i class=\"fa-solid fa-plus\"></i>\n <span>New</span>\n </div>\n }\n </div>\n </div>\n }\n <!-- Content Editor -->\n @if (currentTemplateContent) {\n <div class=\"content-editor-container\">\n <!-- Content Metadata Bar -->\n <div class=\"content-metadata-bar\">\n <div class=\"metadata-grid\">\n <div class=\"form-group\">\n <label for=\"contentType\" class=\"form-label\">\n <i class=\"fa-solid fa-tag\"></i> Content Type\n </label>\n @if (config.allowEdit) {\n <mj-dropdown [(ngModel)]=\"currentTemplateContent.TypeID\"\n name=\"contentType\"\n [Data]=\"getContentTypeOptionsForContent()\"\n TextField=\"text\"\n ValueField=\"value\"\n [ValuePrimitive]=\"true\"\n (ValueChange)=\"onContentTypeChange()\">\n </mj-dropdown>\n } @else {\n <div class=\"form-control-plaintext\">\n {{ getContentTypeDisplayText(currentTemplateContent.TypeID) }}\n </div>\n }\n </div>\n <div class=\"form-group\">\n <label for=\"priority\" class=\"form-label\">\n <i class=\"fa-solid fa-sort-numeric-up\"></i> Priority\n </label>\n @if (config.allowEdit) {\n <mj-numeric-input [(ngModel)]=\"currentTemplateContent.Priority\"\n name=\"priority\"\n [Min]=\"1\"\n [Step]=\"1\"\n [Format]=\"'n0'\">\n </mj-numeric-input>\n } @else {\n <div class=\"form-control-plaintext\">\n {{ currentTemplateContent.Priority || '-' }}\n </div>\n }\n </div>\n <div class=\"form-group\">\n <label class=\"form-label\">\n <i class=\"fa-solid fa-toggle-on\"></i> Status\n </label>\n @if (config.allowEdit) {\n <div class=\"form-check\">\n <input type=\"checkbox\"\n id=\"isActive\"\n class=\"form-check-input\"\n [(ngModel)]=\"currentTemplateContent.IsActive\"\n name=\"isActive\">\n <label class=\"form-check-label\" for=\"isActive\">\n Active\n </label>\n </div>\n } @else {\n <div class=\"form-control-plaintext\">\n @if (currentTemplateContent.IsActive) {\n <span class=\"badge-success\">\n <i class=\"fa-solid fa-check-circle\"></i> Active\n </span>\n } @else {\n <span class=\"badge-secondary\">\n <i class=\"fa-solid fa-pause-circle\"></i> Inactive\n </span>\n }\n </div>\n }\n </div>\n </div>\n </div>\n <!-- Content Text Editor -->\n <div class=\"content-text-editor\">\n <div class=\"editor-header\">\n <label for=\"templateContent\" class=\"editor-label\">\n <i class=\"fa-solid fa-code\"></i> Template Content\n <span class=\"content-type-badge\"\n [class.new]=\"!currentTemplateContent.ID\"\n [class.existing]=\"!!currentTemplateContent.ID\">\n @if (!currentTemplateContent.ID) {\n New\n } @else {\n {{ getContentTypeDisplayText(currentTemplateContent.TypeID) }}\n }\n </span>\n </label>\n <div class=\"editor-actions\">\n @if (!isAddingNewContent && hasUnsavedChanges) {\n <span class=\"unsaved-indicator\">\n <i class=\"fa-solid fa-circle\"></i>\n Unsaved changes\n </span>\n }\n @if (template.IsSaved && currentTemplateContent && config.showRunButton) {\n <button type=\"button\"\n class=\"run-btn\"\n [disabled]=\"isRunningTemplate\"\n (click)=\"onRunTemplate()\"\n title=\"Test run this template\">\n @if (isRunningTemplate) {\n <i class=\"fa-solid fa-spinner fa-spin\"></i>\n Running...\n } @else {\n <i class=\"fa-solid fa-play\"></i>\n Run\n }\n </button>\n }\n </div>\n </div>\n <mj-code-editor\n #codeEditor\n (change)=\"onTemplateTextChange($event)\"\n [readonly]=\"!config.allowEdit\"\n [language]=\"getEditorLanguage()\"\n [languages]=\"supportedLanguages\"\n class=\"code-editor-wrapper\">\n </mj-code-editor>\n <!-- Template Help Section with Tabs -->\n @if (!config.compactMode) {\n <div class=\"template-help-section\">\n <!-- Tab Navigation -->\n <div class=\"help-tabs\">\n <button type=\"button\"\n class=\"help-tab\"\n [class.active]=\"activeHelpTab === 'syntax'\"\n (click)=\"activeHelpTab = 'syntax'\">\n <i class=\"fa-solid fa-code\"></i> Template Syntax\n </button>\n <button type=\"button\"\n class=\"help-tab\"\n [class.active]=\"activeHelpTab === 'placeholders'\"\n (click)=\"activeHelpTab = 'placeholders'\">\n <i class=\"fa-solid fa-at\"></i> System Placeholders\n </button>\n </div>\n <!-- Tab Content -->\n <div class=\"help-content\">\n <!-- Template Syntax Tab -->\n @if (activeHelpTab === 'syntax') {\n <div class=\"syntax-help\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-lightbulb\"></i> Template Syntax Quick Reference\n </div>\n <div class=\"syntax-grid\">\n <div>\n <div class=\"syntax-item\">\n <strong>Parameters:</strong><br>\n <code>{{'{{'}} paramName {{'}}'}}</code>\n </div>\n <div>\n <strong>Template Includes:</strong><br>\n <code>{{'{% template \"TemplateName\" %}'}}</code>\n </div>\n </div>\n <div>\n <div class=\"syntax-item\">\n <strong>Conditionals:</strong><br>\n <code>{{'{% if condition %}'}} ... {{'{% endif %}'}}</code>\n </div>\n <div>\n <strong>Loops:</strong><br>\n <code>{{'{% for item in items %}'}} ... {{'{% endfor %}'}}</code>\n </div>\n </div>\n </div>\n </div>\n }\n <!-- System Placeholders Tab -->\n @if (activeHelpTab === 'placeholders') {\n <div class=\"placeholders-help\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-at\"></i> System Placeholders Reference\n </div>\n <p class=\"placeholders-description\">\n System placeholders are automatically available in all AI prompt templates. Simply use them in your template text and they will be replaced with their current values at execution time.\n Scoped prompt parts can use the Date & Time, User Context and Environment placeholders, written alone with no filter. A part is not a template: everything else in it is sent as written.\n </p>\n <!-- Category Sub-tabs -->\n <div class=\"category-tabs\">\n @for (categoryGroup of placeholderCategories; track categoryGroup.category.name) {\n <button type=\"button\"\n class=\"category-tab\"\n [class.active]=\"activePlaceholderCategory === categoryGroup.category.name\"\n (click)=\"activePlaceholderCategory = categoryGroup.category.name\">\n <i [class]=\"'fa-solid ' + categoryGroup.category.icon\" style=\"font-size: 0.9em;\"></i>\n {{ categoryGroup.category.name }}\n <span class=\"tab-badge\">\n {{ categoryGroup.placeholders.length }}\n </span>\n </button>\n }\n </div>\n <!-- Active Category Content -->\n <div class=\"placeholder-content\">\n @for (categoryGroup of placeholderCategories; track categoryGroup.category.name) {\n @if (activePlaceholderCategory === categoryGroup.category.name) {\n <div class=\"category-header\">\n <i [class]=\"'fa-solid ' + categoryGroup.category.icon\"\n [style.color]=\"categoryGroup.category.color\"></i>\n <h5>{{ categoryGroup.category.name }}</h5>\n </div>\n <div class=\"placeholder-grid\">\n @for (placeholder of categoryGroup.placeholders; track placeholder.name) {\n <div class=\"placeholder-item\">\n <div class=\"placeholder-item-inner\">\n <div>\n <div class=\"placeholder-name-row\">\n <code class=\"placeholder-code\">{{'{{'}} {{ placeholder.name }} {{'}}'}}</code>\n <button type=\"button\"\n class=\"copy-btn\"\n (click)=\"copyPlaceholder(placeholder.name)\"\n title=\"Copy placeholder to clipboard\">\n <i class=\"fa-solid fa-copy\"></i>\n </button>\n </div>\n @if (placeholder.description) {\n <div class=\"placeholder-description\">{{ placeholder.description }}</div>\n }\n </div>\n </div>\n </div>\n }\n </div>\n }\n }\n </div>\n <!-- Usage Example -->\n <div class=\"usage-example\">\n <div class=\"usage-example-inner\">\n <i class=\"fa-solid fa-info-circle\"></i>\n <div class=\"usage-example-text\">\n <strong>Usage Example:</strong> To use a system placeholder in your template, simply include it in double curly braces.\n For example: <code>\"Today is {{'{{'}} _CURRENT_DATE {{'}}'}} and the current user is {{'{{'}} _USER_NAME {{'}}'}}\"</code>\n </div>\n </div>\n </div>\n </div>\n }\n </div>\n </div>\n }\n </div>\n </div>\n } @else {\n <!-- No Content State -->\n <div class=\"no-content-state\">\n <i class=\"fa-solid fa-file-plus\"></i>\n <h5>No Template Content</h5>\n <p>Create your first template content version to get started.</p>\n @if (config.allowEdit) {\n <button type=\"button\"\n (click)=\"addNewTemplateContent()\"\n class=\"btn btn-primary\">\n <i class=\"fa-solid fa-plus\"></i> Create First Content Version\n </button>\n }\n </div>\n }\n }\n </div>\n}\n", styles: [".template-editor {\n font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;\n}\n\n.section-header h6 {\n font-size: 0.9em;\n}\n\n.param-row:hover {\n background-color: var(--mj-bg-surface-card) !important;\n}\n\n.content-item {\n transition: box-shadow 0.2s ease;\n}\n\n.content-item:hover {\n box-shadow: 0 2px 4px var(--mj-shadow-sm);\n}\n\n.loading-state {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n}\n\n.empty-state {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 8px;\n}\n\n.badge {\n display: inline-block;\n font-size: 0.7em;\n line-height: 1;\n}\n\n/* Form control sizing */\n.template-editor .mj-input,\n.template-editor .mj-textarea,\n.template-editor mj-dropdown {\n width: 100%;\n}\n\n.template-editor [mjButton] {\n min-width: auto;\n}\n\n/* Content version tabs */\n.content-versions-tabs {\n margin-bottom: 16px;\n border-bottom: 2px solid var(--mj-border-default);\n padding-bottom: 8px;\n}\n\n.content-tabs {\n display: flex;\n gap: 6px;\n flex-wrap: wrap;\n}\n\n.content-version-tab {\n padding: 8px 12px;\n border: 1px solid var(--mj-border-strong);\n border-radius: 6px;\n cursor: pointer;\n background: var(--mj-bg-surface);\n display: flex;\n align-items: center;\n gap: 6px;\n transition: all 0.2s;\n position: relative;\n}\n\n.content-version-tab.active {\n border-color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n}\n\n.content-version-tab .tab-label {\n font-weight: 600;\n font-size: 0.85em;\n color: var(--mj-text-secondary);\n}\n\n.content-version-tab .tab-meta {\n font-size: 0.7em;\n color: var(--mj-text-muted);\n}\n\n.content-version-tab .tab-meta .inactive-label {\n color: var(--mj-status-error);\n}\n\n.content-version-tab .delete-btn {\n background: none;\n border: none;\n color: var(--mj-status-error);\n padding: 4px;\n border-radius: 3px;\n transition: all 0.2s;\n cursor: pointer;\n}\n\n.content-version-tab .delete-btn:hover {\n background-color: var(--mj-status-error);\n color: var(--mj-text-inverse);\n}\n\n/* New content tab (adding state) */\n.content-version-tab.adding-new {\n border-color: var(--mj-status-success);\n background: color-mix(in srgb, var(--mj-status-success) 10%, var(--mj-bg-surface));\n}\n\n.content-version-tab.adding-new .new-label {\n font-weight: 600;\n color: var(--mj-status-success);\n font-size: 0.85em;\n}\n\n/* Add new content button */\n.content-version-tab.add-new {\n border: 1px dashed var(--mj-status-success);\n background: var(--mj-bg-surface-card);\n}\n\n.content-version-tab.add-new:hover {\n background: color-mix(in srgb, var(--mj-status-success) 8%, var(--mj-bg-surface));\n border-style: solid;\n}\n\n.content-version-tab.add-new i {\n color: var(--mj-status-success);\n font-size: 0.75em;\n}\n\n.content-version-tab.add-new span {\n font-weight: 500;\n color: var(--mj-status-success);\n font-size: 0.85em;\n}\n\n/* Content editor container */\n.content-editor-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n overflow: hidden;\n}\n\n/* Content metadata bar */\n.content-metadata-bar {\n background: var(--mj-bg-surface-card);\n border-bottom: 1px solid var(--mj-border-default);\n padding: 16px 20px;\n}\n\n.metadata-grid {\n display: grid;\n grid-template-columns: 2fr 1fr 1fr auto;\n gap: 20px;\n align-items: end;\n}\n\n.form-group {\n margin: 0;\n}\n\n.form-label {\n margin-bottom: 4px;\n font-size: 0.9em;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.form-control-plaintext {\n font-size: 0.9em;\n padding: 4px 0;\n}\n\n.form-check {\n margin-top: 8px;\n}\n\n.form-check-label {\n font-size: 0.9em;\n}\n\n.badge-success {\n background: color-mix(in srgb, var(--mj-status-success) 15%, var(--mj-bg-surface));\n color: var(--mj-status-success);\n border: 1px solid color-mix(in srgb, var(--mj-status-success) 30%, transparent);\n padding: 3px 8px;\n border-radius: 10px;\n font-size: 0.75em;\n font-weight: 600;\n}\n\n.badge-secondary {\n background: color-mix(in srgb, var(--mj-text-muted) 15%, var(--mj-bg-surface));\n color: var(--mj-text-muted);\n border: 1px solid color-mix(in srgb, var(--mj-text-muted) 30%, transparent);\n padding: 3px 8px;\n border-radius: 10px;\n font-size: 0.75em;\n font-weight: 600;\n}\n\n/* Content text editor area */\n.content-text-editor {\n flex: 1;\n padding: 20px;\n display: flex;\n flex-direction: column;\n}\n\n.editor-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 12px;\n}\n\n.editor-label {\n margin: 0;\n font-weight: 600;\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.content-type-badge {\n padding: 4px 8px;\n border-radius: 4px;\n font-size: 0.75em;\n color: var(--mj-text-inverse);\n}\n\n.content-type-badge.new {\n background: var(--mj-status-success);\n}\n\n.content-type-badge.existing {\n background: var(--mj-text-muted);\n}\n\n.editor-actions {\n display: flex;\n align-items: center;\n gap: 12px;\n}\n\n.unsaved-indicator {\n color: var(--mj-status-warning);\n font-size: 0.85em;\n display: flex;\n align-items: center;\n gap: 4px;\n}\n\n.unsaved-indicator i {\n font-size: 0.5em;\n}\n\n.run-btn {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n border: none;\n padding: 6px 12px;\n border-radius: 4px;\n font-size: 0.85em;\n display: flex;\n align-items: center;\n gap: 6px;\n transition: all 0.2s;\n cursor: pointer;\n}\n\n.run-btn:hover {\n background: var(--mj-brand-primary-hover);\n}\n\n/* Code editor border */\n.code-editor-wrapper {\n flex: 1;\n height: 350px;\n border: 1px solid var(--mj-border-strong);\n border-radius: 4px;\n}\n\n/* Help section tabs */\n.template-help-section {\n margin-top: 16px;\n}\n\n.help-tabs {\n display: flex;\n border-bottom: 2px solid var(--mj-border-default);\n margin-bottom: 0;\n}\n\n.help-tab {\n padding: 8px 16px;\n border: none;\n background: none;\n cursor: pointer;\n font-size: 0.9em;\n font-weight: 600;\n color: var(--mj-text-muted);\n border-bottom: 3px solid transparent;\n transition: all 0.2s;\n}\n\n.help-tab.active {\n color: var(--mj-status-success);\n border-bottom-color: var(--mj-status-success);\n}\n\n/* Help content area */\n.help-content {\n padding: 16px;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default);\n border-top: none;\n border-radius: 0 0 4px 4px;\n}\n\n/* Syntax help */\n.syntax-help {\n font-size: 0.85em;\n}\n\n.syntax-help .section-title {\n font-weight: 600;\n margin-bottom: 12px;\n color: var(--mj-status-success);\n}\n\n.syntax-grid {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 16px;\n}\n\n.syntax-item {\n margin-bottom: 8px;\n}\n\n.syntax-help code {\n background: var(--mj-bg-surface);\n padding: 4px 6px;\n border-radius: 3px;\n display: inline-block;\n margin-top: 4px;\n color: var(--mj-text-primary);\n}\n\n/* Placeholders help */\n.placeholders-help {\n font-size: 0.85em;\n}\n\n.placeholders-help .section-title {\n font-weight: 600;\n margin-bottom: 12px;\n color: var(--mj-status-success);\n}\n\n.placeholders-description {\n margin-bottom: 16px;\n color: var(--mj-text-secondary);\n line-height: 1.5;\n}\n\n/* Category tabs */\n.category-tabs {\n display: flex;\n gap: 8px;\n margin-bottom: 16px;\n flex-wrap: wrap;\n}\n\n.category-tab {\n padding: 6px 14px;\n border: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface);\n border-radius: 20px;\n cursor: pointer;\n font-size: 0.85em;\n font-weight: 500;\n display: flex;\n align-items: center;\n gap: 6px;\n transition: all 0.2s;\n color: var(--mj-text-muted);\n}\n\n.category-tab.active {\n border-color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n}\n\n.category-tab .tab-badge {\n background: color-mix(in srgb, var(--mj-text-primary) 10%, transparent);\n color: inherit;\n padding: 2px 6px;\n border-radius: 10px;\n font-size: 0.75em;\n margin-left: 4px;\n}\n\n/* Placeholder content area */\n.placeholder-content {\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n padding: 16px;\n}\n\n.category-header {\n display: flex;\n align-items: center;\n gap: 8px;\n margin-bottom: 16px;\n}\n\n.category-header i {\n font-size: 1.2em;\n}\n\n.category-header h5 {\n margin: 0;\n color: var(--mj-text-primary);\n font-weight: 600;\n}\n\n.placeholder-grid {\n display: grid;\n gap: 10px;\n}\n\n/* Placeholder item */\n.placeholder-item {\n padding: 12px 16px;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n position: relative;\n transition: all 0.2s;\n max-width: 800px;\n}\n\n.placeholder-item:hover {\n border-color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 5%, var(--mj-bg-surface));\n}\n\n.placeholder-item:hover .copy-btn {\n opacity: 1 !important;\n}\n\n.placeholder-item-inner {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n}\n\n.placeholder-name-row {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n}\n\n.placeholder-code {\n color: var(--mj-status-error);\n font-weight: 600;\n font-size: 0.95em;\n}\n\n.copy-btn {\n opacity: 0;\n transition: opacity 0.2s;\n background: none;\n border: none;\n padding: 4px 8px;\n cursor: pointer;\n color: var(--mj-brand-primary);\n border-radius: 4px;\n display: inline-flex;\n align-items: center;\n}\n\n.copy-btn:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n}\n\n.copy-btn i {\n font-size: 0.85em;\n}\n\n.placeholder-description {\n color: var(--mj-text-muted);\n margin-top: 4px;\n line-height: 1.4;\n}\n\n/* Usage example box */\n.usage-example {\n margin-top: 20px;\n padding: 12px;\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, var(--mj-bg-surface));\n border-radius: 6px;\n border: 1px solid color-mix(in srgb, var(--mj-brand-primary) 20%, var(--mj-bg-surface));\n}\n\n.usage-example-inner {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n}\n\n.usage-example i {\n color: var(--mj-brand-primary);\n margin-top: 2px;\n}\n\n.usage-example-text {\n flex: 1;\n font-size: 0.85em;\n color: var(--mj-brand-primary);\n line-height: 1.5;\n}\n\n.usage-example code {\n background: var(--mj-bg-surface);\n padding: 2px 4px;\n border-radius: 3px;\n}\n\n/* No content state, and the failed-read state that must not look like it */\n.no-content-state,\n.contents-load-failed {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n background: var(--mj-bg-surface-card);\n border: 2px dashed var(--mj-border-default);\n border-radius: 8px;\n padding: 40px;\n text-align: center;\n}\n\n.no-content-state i,\n.contents-load-failed > i {\n font-size: 48px;\n margin-bottom: 16px;\n color: var(--mj-text-muted);\n opacity: 0.6;\n}\n\n.no-content-state h5,\n.contents-load-failed h5 {\n color: var(--mj-text-secondary);\n margin-bottom: 8px;\n}\n\n.no-content-state p,\n.contents-load-failed p {\n color: var(--mj-text-muted);\n margin-bottom: 20px;\n}\n\n.contents-load-failed {\n border-color: var(--mj-status-error);\n}\n\n/* Only the panel's own icon: the Retry button's icon keeps the button's size and colour */\n.contents-load-failed > i {\n color: var(--mj-status-error);\n opacity: 1;\n}\n"] }]
|
|
1298
|
+
args: [{ standalone: false, selector: 'mj-template-editor', template: "@if (template) {\n <div class=\"template-editor\" style=\"height: 100%; display: flex; flex-direction: column;\">\n @if (ContentsLoadState === 'loading') {\n <mj-loading text=\"Loading template contents...\" size=\"medium\"></mj-loading>\n } @else if (ContentsLoadState === 'failed') {\n <!-- The read failed: the template's contents are unknown, so neither \"no content\" nor \"create\" -->\n <div class=\"contents-load-failed\">\n <i class=\"fa-solid fa-triangle-exclamation\"></i>\n <h5>Template contents could not be loaded</h5>\n <p>Nothing is shown so nothing can be saved over contents that were not read.</p>\n <button type=\"button\" mjButton variant=\"secondary\" size=\"sm\" (click)=\"RefreshAndDiscardChanges()\">\n <i class=\"fa-solid fa-rotate-right\"></i> Retry\n </button>\n </div>\n } @else {\n <!-- Content Versions List/Tabs -->\n @if (templateContents.length > 0) {\n <div class=\"content-versions-tabs\">\n <div class=\"content-tabs\">\n @for (content of templateContents; track content.ID || $index; let i = $index) {\n <div class=\"content-version-tab\"\n [class.active]=\"selectedContentIndex === i && !isAddingNewContent\"\n (click)=\"selectTemplateContent(i, false)\">\n <div style=\"flex: 1;\">\n <div class=\"tab-label\">\n <!-- From TypeID (what the Content Type dropdown edits); the view-joined Type field is only refreshed on reload -->\n {{ content.TypeID ? GetContentTypeDisplayText(content.TypeID) : 'Version ' + (i + 1) }}\n </div>\n <div class=\"tab-meta\">\n P:{{ content.Priority }}\n @if (!content.IsActive) {\n \u2022 <span class=\"inactive-label\">Inactive</span>\n }\n </div>\n </div>\n @if (templateContents.length > 1 && config.allowEdit) {\n <button type=\"button\"\n (click)=\"deleteTemplateContent(i); $event.stopPropagation()\"\n class=\"delete-btn\"\n title=\"Delete this content version\">\n <i class=\"fa-solid fa-trash fa-xs\"></i>\n </button>\n }\n </div>\n }\n @if (isAddingNewContent) {\n <div class=\"content-version-tab adding-new\">\n <i class=\"fa-solid fa-plus text-success\"></i>\n <span class=\"new-label\">New</span>\n </div>\n }\n <!-- Add New Content (Edit Mode Only) -->\n @if (config.allowEdit && !isAddingNewContent) {\n <div class=\"content-version-tab add-new\"\n (click)=\"addNewTemplateContent()\"\n title=\"Add new template content\">\n <i class=\"fa-solid fa-plus\"></i>\n <span>New</span>\n </div>\n }\n </div>\n </div>\n }\n <!-- Content Editor -->\n @if (currentTemplateContent) {\n <div class=\"content-editor-container\">\n <!-- Content Metadata Bar -->\n <div class=\"content-metadata-bar\">\n <div class=\"metadata-grid\">\n <div class=\"form-group\">\n <label for=\"contentType\" class=\"form-label\">\n <i class=\"fa-solid fa-tag\"></i> Content Type\n </label>\n @if (config.allowEdit) {\n <mj-dropdown [(ngModel)]=\"currentTemplateContent.TypeID\"\n name=\"contentType\"\n [Data]=\"getContentTypeOptionsForContent()\"\n TextField=\"text\"\n ValueField=\"value\"\n [ValuePrimitive]=\"true\"\n (ValueChange)=\"onContentTypeChange()\">\n </mj-dropdown>\n } @else {\n <div class=\"form-control-plaintext\">\n {{ getContentTypeDisplayText(currentTemplateContent.TypeID) }}\n </div>\n }\n </div>\n <div class=\"form-group\">\n <label for=\"priority\" class=\"form-label\">\n <i class=\"fa-solid fa-sort-numeric-up\"></i> Priority\n </label>\n @if (config.allowEdit) {\n <mj-numeric-input [(ngModel)]=\"currentTemplateContent.Priority\"\n name=\"priority\"\n [Min]=\"1\"\n [Step]=\"1\"\n [Format]=\"'n0'\">\n </mj-numeric-input>\n } @else {\n <div class=\"form-control-plaintext\">\n {{ currentTemplateContent.Priority || '-' }}\n </div>\n }\n </div>\n <div class=\"form-group\">\n <label class=\"form-label\">\n <i class=\"fa-solid fa-toggle-on\"></i> Status\n </label>\n @if (config.allowEdit) {\n <div class=\"form-check\">\n <input type=\"checkbox\"\n id=\"isActive\"\n class=\"form-check-input\"\n [(ngModel)]=\"currentTemplateContent.IsActive\"\n name=\"isActive\">\n <label class=\"form-check-label\" for=\"isActive\">\n Active\n </label>\n </div>\n } @else {\n <div class=\"form-control-plaintext\">\n @if (currentTemplateContent.IsActive) {\n <span class=\"badge-success\">\n <i class=\"fa-solid fa-check-circle\"></i> Active\n </span>\n } @else {\n <span class=\"badge-secondary\">\n <i class=\"fa-solid fa-pause-circle\"></i> Inactive\n </span>\n }\n </div>\n }\n </div>\n </div>\n </div>\n <!-- Content Text Editor -->\n <div class=\"content-text-editor\">\n <div class=\"editor-header\">\n <label for=\"templateContent\" class=\"editor-label\">\n <i class=\"fa-solid fa-code\"></i> Template Content\n <span class=\"content-type-badge\"\n [class.new]=\"!currentTemplateContent.ID\"\n [class.existing]=\"!!currentTemplateContent.ID\">\n @if (!currentTemplateContent.ID) {\n New\n } @else {\n {{ getContentTypeDisplayText(currentTemplateContent.TypeID) }}\n }\n </span>\n </label>\n <div class=\"editor-actions\">\n @if (!isAddingNewContent && hasUnsavedChanges) {\n <span class=\"unsaved-indicator\">\n <i class=\"fa-solid fa-circle\"></i>\n Unsaved changes\n </span>\n }\n @if (template.IsSaved && currentTemplateContent && config.showRunButton) {\n <button type=\"button\"\n class=\"run-btn\"\n [disabled]=\"isRunningTemplate\"\n (click)=\"onRunTemplate()\"\n title=\"Test run this template\">\n @if (isRunningTemplate) {\n <i class=\"fa-solid fa-spinner fa-spin\"></i>\n Running...\n } @else {\n <i class=\"fa-solid fa-play\"></i>\n Run\n }\n </button>\n }\n </div>\n </div>\n <mj-code-editor\n #codeEditor\n (change)=\"onTemplateTextChange($event)\"\n [readonly]=\"!config.allowEdit\"\n [language]=\"getEditorLanguage()\"\n [languages]=\"supportedLanguages\"\n class=\"code-editor-wrapper\">\n </mj-code-editor>\n <!-- Template Help Section with Tabs -->\n @if (!config.compactMode) {\n <div class=\"template-help-section\">\n <!-- Tab Navigation -->\n <div class=\"help-tabs\">\n <button type=\"button\"\n class=\"help-tab\"\n [class.active]=\"activeHelpTab === 'syntax'\"\n (click)=\"activeHelpTab = 'syntax'\">\n <i class=\"fa-solid fa-code\"></i> Template Syntax\n </button>\n <button type=\"button\"\n class=\"help-tab\"\n [class.active]=\"activeHelpTab === 'placeholders'\"\n (click)=\"activeHelpTab = 'placeholders'\">\n <i class=\"fa-solid fa-at\"></i> System Placeholders\n </button>\n </div>\n <!-- Tab Content -->\n <div class=\"help-content\">\n <!-- Template Syntax Tab -->\n @if (activeHelpTab === 'syntax') {\n <div class=\"syntax-help\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-lightbulb\"></i> Template Syntax Quick Reference\n </div>\n <div class=\"syntax-grid\">\n <div>\n <div class=\"syntax-item\">\n <strong>Parameters:</strong><br>\n <code>{{'{{'}} paramName {{'}}'}}</code>\n </div>\n <div>\n <strong>Template Includes:</strong><br>\n <code>{{'{% template \"TemplateName\" %}'}}</code>\n </div>\n </div>\n <div>\n <div class=\"syntax-item\">\n <strong>Conditionals:</strong><br>\n <code>{{'{% if condition %}'}} ... {{'{% endif %}'}}</code>\n </div>\n <div>\n <strong>Loops:</strong><br>\n <code>{{'{% for item in items %}'}} ... {{'{% endfor %}'}}</code>\n </div>\n </div>\n </div>\n </div>\n }\n <!-- System Placeholders Tab -->\n @if (activeHelpTab === 'placeholders') {\n <div class=\"placeholders-help\">\n <div class=\"section-title\">\n <i class=\"fa-solid fa-at\"></i> System Placeholders Reference\n </div>\n <p class=\"placeholders-description\">\n System placeholders are automatically available in all AI prompt templates. Simply use them in your template text and they will be replaced with their current values at execution time.\n Scoped prompt parts can use the Date & Time, User Context and Environment placeholders, written alone with no filter. A part is not a template: everything else in it is sent as written.\n </p>\n <!-- Category Sub-tabs -->\n <div class=\"category-tabs\">\n @for (categoryGroup of placeholderCategories; track categoryGroup.category.name) {\n <button type=\"button\"\n class=\"category-tab\"\n [class.active]=\"activePlaceholderCategory === categoryGroup.category.name\"\n (click)=\"activePlaceholderCategory = categoryGroup.category.name\">\n <i [class]=\"'fa-solid ' + categoryGroup.category.icon\" style=\"font-size: 0.9em;\"></i>\n {{ categoryGroup.category.name }}\n <span class=\"tab-badge\">\n {{ categoryGroup.placeholders.length }}\n </span>\n </button>\n }\n </div>\n <!-- Active Category Content -->\n <div class=\"placeholder-content\">\n @for (categoryGroup of placeholderCategories; track categoryGroup.category.name) {\n @if (activePlaceholderCategory === categoryGroup.category.name) {\n <div class=\"category-header\">\n <i [class]=\"'fa-solid ' + categoryGroup.category.icon\"\n [style.color]=\"categoryGroup.category.color\"></i>\n <h5>{{ categoryGroup.category.name }}</h5>\n </div>\n <div class=\"placeholder-grid\">\n @for (placeholder of categoryGroup.placeholders; track placeholder.name) {\n <div class=\"placeholder-item\">\n <div class=\"placeholder-item-inner\">\n <div>\n <div class=\"placeholder-name-row\">\n <code class=\"placeholder-code\">{{'{{'}} {{ placeholder.name }} {{'}}'}}</code>\n <button type=\"button\"\n class=\"copy-btn\"\n (click)=\"copyPlaceholder(placeholder.name)\"\n title=\"Copy placeholder to clipboard\">\n <i class=\"fa-solid fa-copy\"></i>\n </button>\n </div>\n @if (placeholder.description) {\n <div class=\"placeholder-description\">{{ placeholder.description }}</div>\n }\n </div>\n </div>\n </div>\n }\n </div>\n }\n }\n </div>\n <!-- Usage Example -->\n <div class=\"usage-example\">\n <div class=\"usage-example-inner\">\n <i class=\"fa-solid fa-info-circle\"></i>\n <div class=\"usage-example-text\">\n <strong>Usage Example:</strong> To use a system placeholder in your template, simply include it in double curly braces.\n For example: <code>\"Today is {{'{{'}} _CURRENT_DATE {{'}}'}} and the current user is {{'{{'}} _USER_NAME {{'}}'}}\"</code>\n </div>\n </div>\n </div>\n </div>\n }\n </div>\n </div>\n }\n </div>\n </div>\n } @else {\n <!-- No Content State -->\n <div class=\"no-content-state\">\n <i class=\"fa-solid fa-file-plus\"></i>\n <h5>No Template Content</h5>\n <p>Create your first template content version to get started.</p>\n @if (config.allowEdit) {\n <button type=\"button\"\n (click)=\"addNewTemplateContent()\"\n class=\"btn btn-primary\">\n <i class=\"fa-solid fa-plus\"></i> Create First Content Version\n </button>\n }\n </div>\n }\n }\n </div>\n}\n", styles: [".template-editor {\n font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;\n}\n\n.section-header h6 {\n font-size: 0.9em;\n}\n\n.param-row:hover {\n background-color: var(--mj-bg-surface-card) !important;\n}\n\n.content-item {\n transition: box-shadow 0.2s ease;\n}\n\n.content-item:hover {\n box-shadow: 0 2px 4px var(--mj-shadow-sm);\n}\n\n.loading-state {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n}\n\n.empty-state {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 8px;\n}\n\n.badge {\n display: inline-block;\n font-size: 0.7em;\n line-height: 1;\n}\n\n/* Form control sizing */\n.template-editor .mj-input,\n.template-editor .mj-textarea,\n.template-editor mj-dropdown {\n width: 100%;\n}\n\n.template-editor [mjButton] {\n min-width: auto;\n}\n\n/* Content version tabs */\n.content-versions-tabs {\n margin-bottom: 16px;\n border-bottom: 2px solid var(--mj-border-default);\n padding-bottom: 8px;\n}\n\n.content-tabs {\n display: flex;\n gap: 6px;\n flex-wrap: wrap;\n}\n\n.content-version-tab {\n padding: 8px 12px;\n border: 1px solid var(--mj-border-strong);\n border-radius: 6px;\n cursor: pointer;\n background: var(--mj-bg-surface);\n display: flex;\n align-items: center;\n gap: 6px;\n transition: all 0.2s;\n position: relative;\n}\n\n.content-version-tab.active {\n border-color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n}\n\n.content-version-tab .tab-label {\n font-weight: 600;\n font-size: 0.85em;\n color: var(--mj-text-secondary);\n}\n\n.content-version-tab .tab-meta {\n font-size: 0.7em;\n color: var(--mj-text-muted);\n}\n\n.content-version-tab .tab-meta .inactive-label {\n color: var(--mj-status-error);\n}\n\n.content-version-tab .delete-btn {\n background: none;\n border: none;\n color: var(--mj-status-error);\n padding: 4px;\n border-radius: 3px;\n transition: all 0.2s;\n cursor: pointer;\n}\n\n.content-version-tab .delete-btn:hover {\n background-color: var(--mj-status-error);\n color: var(--mj-text-inverse);\n}\n\n/* New content tab (adding state) */\n.content-version-tab.adding-new {\n border-color: var(--mj-status-success);\n background: color-mix(in srgb, var(--mj-status-success) 10%, var(--mj-bg-surface));\n}\n\n.content-version-tab.adding-new .new-label {\n font-weight: 600;\n color: var(--mj-status-success);\n font-size: 0.85em;\n}\n\n/* Add new content button */\n.content-version-tab.add-new {\n border: 1px dashed var(--mj-status-success);\n background: var(--mj-bg-surface-card);\n}\n\n.content-version-tab.add-new:hover {\n background: color-mix(in srgb, var(--mj-status-success) 8%, var(--mj-bg-surface));\n border-style: solid;\n}\n\n.content-version-tab.add-new i {\n color: var(--mj-status-success);\n font-size: 0.75em;\n}\n\n.content-version-tab.add-new span {\n font-weight: 500;\n color: var(--mj-status-success);\n font-size: 0.85em;\n}\n\n/* Content editor container */\n.content-editor-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n overflow: hidden;\n}\n\n/* Content metadata bar */\n.content-metadata-bar {\n background: var(--mj-bg-surface-card);\n border-bottom: 1px solid var(--mj-border-default);\n padding: 16px 20px;\n}\n\n.metadata-grid {\n display: grid;\n grid-template-columns: 2fr 1fr 1fr auto;\n gap: 20px;\n align-items: end;\n}\n\n.form-group {\n margin: 0;\n}\n\n.form-label {\n margin-bottom: 4px;\n font-size: 0.9em;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.form-control-plaintext {\n font-size: 0.9em;\n padding: 4px 0;\n}\n\n.form-check {\n margin-top: 8px;\n}\n\n.form-check-label {\n font-size: 0.9em;\n}\n\n.badge-success {\n background: color-mix(in srgb, var(--mj-status-success) 15%, var(--mj-bg-surface));\n color: var(--mj-status-success);\n border: 1px solid color-mix(in srgb, var(--mj-status-success) 30%, transparent);\n padding: 3px 8px;\n border-radius: 10px;\n font-size: 0.75em;\n font-weight: 600;\n}\n\n.badge-secondary {\n background: color-mix(in srgb, var(--mj-text-muted) 15%, var(--mj-bg-surface));\n color: var(--mj-text-muted);\n border: 1px solid color-mix(in srgb, var(--mj-text-muted) 30%, transparent);\n padding: 3px 8px;\n border-radius: 10px;\n font-size: 0.75em;\n font-weight: 600;\n}\n\n/* Content text editor area */\n.content-text-editor {\n flex: 1;\n padding: 20px;\n display: flex;\n flex-direction: column;\n}\n\n.editor-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 12px;\n}\n\n.editor-label {\n margin: 0;\n font-weight: 600;\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.content-type-badge {\n padding: 4px 8px;\n border-radius: 4px;\n font-size: 0.75em;\n color: var(--mj-text-inverse);\n}\n\n.content-type-badge.new {\n background: var(--mj-status-success);\n}\n\n.content-type-badge.existing {\n background: var(--mj-text-muted);\n}\n\n.editor-actions {\n display: flex;\n align-items: center;\n gap: 12px;\n}\n\n.unsaved-indicator {\n color: var(--mj-status-warning);\n font-size: 0.85em;\n display: flex;\n align-items: center;\n gap: 4px;\n}\n\n.unsaved-indicator i {\n font-size: 0.5em;\n}\n\n.run-btn {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n border: none;\n padding: 6px 12px;\n border-radius: 4px;\n font-size: 0.85em;\n display: flex;\n align-items: center;\n gap: 6px;\n transition: all 0.2s;\n cursor: pointer;\n}\n\n.run-btn:hover {\n background: var(--mj-brand-primary-hover);\n}\n\n/* Code editor border */\n.code-editor-wrapper {\n flex: 1;\n height: 350px;\n border: 1px solid var(--mj-border-strong);\n border-radius: 4px;\n}\n\n/* Help section tabs */\n.template-help-section {\n margin-top: 16px;\n}\n\n.help-tabs {\n display: flex;\n border-bottom: 2px solid var(--mj-border-default);\n margin-bottom: 0;\n}\n\n.help-tab {\n padding: 8px 16px;\n border: none;\n background: none;\n cursor: pointer;\n font-size: 0.9em;\n font-weight: 600;\n color: var(--mj-text-muted);\n border-bottom: 3px solid transparent;\n transition: all 0.2s;\n}\n\n.help-tab.active {\n color: var(--mj-status-success);\n border-bottom-color: var(--mj-status-success);\n}\n\n/* Help content area */\n.help-content {\n padding: 16px;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default);\n border-top: none;\n border-radius: 0 0 4px 4px;\n}\n\n/* Syntax help */\n.syntax-help {\n font-size: 0.85em;\n}\n\n.syntax-help .section-title {\n font-weight: 600;\n margin-bottom: 12px;\n color: var(--mj-status-success);\n}\n\n.syntax-grid {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 16px;\n}\n\n/* Phone: one column, so the longer examples ({% if %} ... {% endif %}) are not clipped.\n The metadata row's four desktop columns become Content Type on its own line over\n Priority and Status side by side. */\n@media (max-width: 768px) {\n .syntax-grid {\n grid-template-columns: minmax(0, 1fr);\n }\n\n .metadata-grid {\n grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);\n gap: 12px;\n }\n\n .metadata-grid > .form-group:first-child {\n grid-column: 1 / -1;\n }\n}\n\n.syntax-item {\n margin-bottom: 8px;\n}\n\n.syntax-help code {\n background: var(--mj-bg-surface);\n padding: 4px 6px;\n border-radius: 3px;\n display: inline-block;\n margin-top: 4px;\n color: var(--mj-text-primary);\n}\n\n/* Placeholders help */\n.placeholders-help {\n font-size: 0.85em;\n}\n\n.placeholders-help .section-title {\n font-weight: 600;\n margin-bottom: 12px;\n color: var(--mj-status-success);\n}\n\n.placeholders-description {\n margin-bottom: 16px;\n color: var(--mj-text-secondary);\n line-height: 1.5;\n}\n\n/* Category tabs */\n.category-tabs {\n display: flex;\n gap: 8px;\n margin-bottom: 16px;\n flex-wrap: wrap;\n}\n\n.category-tab {\n padding: 6px 14px;\n border: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface);\n border-radius: 20px;\n cursor: pointer;\n font-size: 0.85em;\n font-weight: 500;\n display: flex;\n align-items: center;\n gap: 6px;\n transition: all 0.2s;\n color: var(--mj-text-muted);\n}\n\n.category-tab.active {\n border-color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n}\n\n.category-tab .tab-badge {\n background: color-mix(in srgb, var(--mj-text-primary) 10%, transparent);\n color: inherit;\n padding: 2px 6px;\n border-radius: 10px;\n font-size: 0.75em;\n margin-left: 4px;\n}\n\n/* Placeholder content area */\n.placeholder-content {\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n padding: 16px;\n}\n\n.category-header {\n display: flex;\n align-items: center;\n gap: 8px;\n margin-bottom: 16px;\n}\n\n.category-header i {\n font-size: 1.2em;\n}\n\n.category-header h5 {\n margin: 0;\n color: var(--mj-text-primary);\n font-weight: 600;\n}\n\n.placeholder-grid {\n display: grid;\n gap: 10px;\n}\n\n/* Placeholder item */\n.placeholder-item {\n padding: 12px 16px;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n position: relative;\n transition: all 0.2s;\n max-width: 800px;\n}\n\n.placeholder-item:hover {\n border-color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 5%, var(--mj-bg-surface));\n}\n\n.placeholder-item:hover .copy-btn {\n opacity: 1 !important;\n}\n\n.placeholder-item-inner {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n}\n\n.placeholder-name-row {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n}\n\n.placeholder-code {\n color: var(--mj-status-error);\n font-weight: 600;\n font-size: 0.95em;\n}\n\n.copy-btn {\n opacity: 0;\n transition: opacity 0.2s;\n background: none;\n border: none;\n padding: 4px 8px;\n cursor: pointer;\n color: var(--mj-brand-primary);\n border-radius: 4px;\n display: inline-flex;\n align-items: center;\n}\n\n.copy-btn:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n}\n\n.copy-btn i {\n font-size: 0.85em;\n}\n\n.placeholder-description {\n color: var(--mj-text-muted);\n margin-top: 4px;\n line-height: 1.4;\n}\n\n/* Usage example box */\n.usage-example {\n margin-top: 20px;\n padding: 12px;\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, var(--mj-bg-surface));\n border-radius: 6px;\n border: 1px solid color-mix(in srgb, var(--mj-brand-primary) 20%, var(--mj-bg-surface));\n}\n\n.usage-example-inner {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n}\n\n.usage-example i {\n color: var(--mj-brand-primary);\n margin-top: 2px;\n}\n\n.usage-example-text {\n flex: 1;\n font-size: 0.85em;\n color: var(--mj-brand-primary);\n line-height: 1.5;\n}\n\n.usage-example code {\n background: var(--mj-bg-surface);\n padding: 2px 4px;\n border-radius: 3px;\n}\n\n/* No content state, and the failed-read state that must not look like it */\n.no-content-state,\n.contents-load-failed {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n background: var(--mj-bg-surface-card);\n border: 2px dashed var(--mj-border-default);\n border-radius: 8px;\n padding: 40px;\n text-align: center;\n}\n\n.no-content-state i,\n.contents-load-failed > i {\n font-size: 48px;\n margin-bottom: 16px;\n color: var(--mj-text-muted);\n opacity: 0.6;\n}\n\n.no-content-state h5,\n.contents-load-failed h5 {\n color: var(--mj-text-secondary);\n margin-bottom: 8px;\n}\n\n.no-content-state p,\n.contents-load-failed p {\n color: var(--mj-text-muted);\n margin-bottom: 20px;\n}\n\n.contents-load-failed {\n border-color: var(--mj-status-error);\n}\n\n/* Only the panel's own icon: the Retry button's icon keeps the button's size and colour */\n.contents-load-failed > i {\n color: var(--mj-status-error);\n opacity: 1;\n}\n"] }]
|
|
1299
1299
|
}], () => [{ type: i1.MJNotificationService }, { type: i2.MJConfirmService }], { Template: [{
|
|
1300
1300
|
type: Input
|
|
1301
1301
|
}], template: [{
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helpers for the phone hero pattern (design call 6A): at phone width a form's hero keeps its
|
|
3
|
+
* icon, name, status and primary action, and everything else collapses into one summary line
|
|
4
|
+
* that expands in place. The layout lives in `phone-hero.css`; these build the line's text.
|
|
5
|
+
*/
|
|
6
|
+
/** Joins the parts that are present with a middle dot; null, undefined, false and blank are skipped. */
|
|
7
|
+
export declare function JoinHeroSummary(parts: ReadonlyArray<string | null | undefined | false>): string;
|
|
8
|
+
/** `1 param`, `0 params`, `12 params`. */
|
|
9
|
+
export declare function CountLabel(count: number, singular: string): string;
|
|
10
|
+
/** The status a run's phone chip shows; each maps to a `--mj-status-*` token pair in `phone-hero.css`. */
|
|
11
|
+
export type RunStatusKindValue = 'running' | 'success' | 'error' | 'info';
|
|
12
|
+
/**
|
|
13
|
+
* A run is running until it has completed (the completion time is the authority, not the success
|
|
14
|
+
* flag); a completed run is a success, an error, or plain info when it carries no success flag.
|
|
15
|
+
*/
|
|
16
|
+
export declare function RunStatusKind(completed: boolean, success: boolean | null | undefined): RunStatusKindValue;
|
|
17
|
+
//# sourceMappingURL=phone-hero.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"phone-hero.d.ts","sourceRoot":"","sources":["../../../src/lib/shared/phone-hero.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,wGAAwG;AACxG,wBAAgB,eAAe,CAAC,KAAK,EAAE,aAAa,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,KAAK,CAAC,GAAG,MAAM,CAK/F;AAED,0CAA0C;AAC1C,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAElE;AAED,0GAA0G;AAC1G,MAAM,MAAM,kBAAkB,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,MAAM,CAAC;AAE1E;;;GAGG;AACH,wBAAgB,aAAa,CAAC,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,GAAG,SAAS,GAAG,kBAAkB,CAKzG"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helpers for the phone hero pattern (design call 6A): at phone width a form's hero keeps its
|
|
3
|
+
* icon, name, status and primary action, and everything else collapses into one summary line
|
|
4
|
+
* that expands in place. The layout lives in `phone-hero.css`; these build the line's text.
|
|
5
|
+
*/
|
|
6
|
+
/** Joins the parts that are present with a middle dot; null, undefined, false and blank are skipped. */
|
|
7
|
+
export function JoinHeroSummary(parts) {
|
|
8
|
+
return parts
|
|
9
|
+
.filter((p) => typeof p === 'string' && p.trim() !== '')
|
|
10
|
+
.map((p) => p.trim())
|
|
11
|
+
.join(' · ');
|
|
12
|
+
}
|
|
13
|
+
/** `1 param`, `0 params`, `12 params`. */
|
|
14
|
+
export function CountLabel(count, singular) {
|
|
15
|
+
return `${count} ${count === 1 ? singular : singular + 's'}`;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* A run is running until it has completed (the completion time is the authority, not the success
|
|
19
|
+
* flag); a completed run is a success, an error, or plain info when it carries no success flag.
|
|
20
|
+
*/
|
|
21
|
+
export function RunStatusKind(completed, success) {
|
|
22
|
+
if (!completed)
|
|
23
|
+
return 'running';
|
|
24
|
+
if (success === true)
|
|
25
|
+
return 'success';
|
|
26
|
+
if (success === false)
|
|
27
|
+
return 'error';
|
|
28
|
+
return 'info';
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=phone-hero.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"phone-hero.js","sourceRoot":"","sources":["../../../src/lib/shared/phone-hero.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,wGAAwG;AACxG,MAAM,UAAU,eAAe,CAAC,KAAuD;IACnF,OAAO,KAAK;SACP,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC;SACpE,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SACpB,IAAI,CAAC,KAAK,CAAC,CAAC;AACrB,CAAC;AAED,0CAA0C;AAC1C,MAAM,UAAU,UAAU,CAAC,KAAa,EAAE,QAAgB;IACtD,OAAO,GAAG,KAAK,IAAI,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,GAAG,GAAG,EAAE,CAAC;AACjE,CAAC;AAKD;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,SAAkB,EAAE,OAAmC;IACjF,IAAI,CAAC,SAAS;QAAE,OAAO,SAAS,CAAC;IACjC,IAAI,OAAO,KAAK,IAAI;QAAE,OAAO,SAAS,CAAC;IACvC,IAAI,OAAO,KAAK,KAAK;QAAE,OAAO,OAAO,CAAC;IACtC,OAAO,MAAM,CAAC;AAClB,CAAC","sourcesContent":["/**\n * Helpers for the phone hero pattern (design call 6A): at phone width a form's hero keeps its\n * icon, name, status and primary action, and everything else collapses into one summary line\n * that expands in place. The layout lives in `phone-hero.css`; these build the line's text.\n */\n\n/** Joins the parts that are present with a middle dot; null, undefined, false and blank are skipped. */\nexport function JoinHeroSummary(parts: ReadonlyArray<string | null | undefined | false>): string {\n return parts\n .filter((p): p is string => typeof p === 'string' && p.trim() !== '')\n .map((p) => p.trim())\n .join(' · ');\n}\n\n/** `1 param`, `0 params`, `12 params`. */\nexport function CountLabel(count: number, singular: string): string {\n return `${count} ${count === 1 ? singular : singular + 's'}`;\n}\n\n/** The status a run's phone chip shows; each maps to a `--mj-status-*` token pair in `phone-hero.css`. */\nexport type RunStatusKindValue = 'running' | 'success' | 'error' | 'info';\n\n/**\n * A run is running until it has completed (the completion time is the authority, not the success\n * flag); a completed run is a success, an error, or plain info when it carries no success flag.\n */\nexport function RunStatusKind(completed: boolean, success: boolean | null | undefined): RunStatusKindValue {\n if (!completed) return 'running';\n if (success === true) return 'success';\n if (success === false) return 'error';\n return 'info';\n}\n"]}
|