@memberjunction/ng-conversations 6.1.0-edge.0 → 6.1.0-edge.2
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/dist/lib/components/composer/ai-composer.component.d.ts +11 -2
- package/dist/lib/components/composer/ai-composer.component.d.ts.map +1 -1
- package/dist/lib/components/composer/ai-composer.component.js +48 -32
- package/dist/lib/components/composer/ai-composer.component.js.map +1 -1
- package/dist/lib/components/message/message-input.component.d.ts +17 -7
- package/dist/lib/components/message/message-input.component.d.ts.map +1 -1
- package/dist/lib/components/message/message-input.component.js +56 -179
- package/dist/lib/components/message/message-input.component.js.map +1 -1
- package/dist/lib/components/message/workflow-plan-card.component.d.ts +117 -0
- package/dist/lib/components/message/workflow-plan-card.component.d.ts.map +1 -0
- package/dist/lib/components/message/workflow-plan-card.component.js +230 -0
- package/dist/lib/components/message/workflow-plan-card.component.js.map +1 -0
- package/dist/lib/components/task/tasks-full-view.component.d.ts +8 -0
- package/dist/lib/components/task/tasks-full-view.component.d.ts.map +1 -1
- package/dist/lib/components/task/tasks-full-view.component.js +14 -62
- package/dist/lib/components/task/tasks-full-view.component.js.map +1 -1
- package/dist/lib/conversations.module.d.ts +74 -72
- package/dist/lib/conversations.module.d.ts.map +1 -1
- package/dist/lib/conversations.module.js +8 -0
- package/dist/lib/conversations.module.js.map +1 -1
- package/dist/lib/services/realtime-pairing.d.ts +8 -3
- package/dist/lib/services/realtime-pairing.d.ts.map +1 -1
- package/dist/lib/services/realtime-pairing.js +9 -5
- package/dist/lib/services/realtime-pairing.js.map +1 -1
- package/dist/public-api.d.ts +1 -0
- package/dist/public-api.d.ts.map +1 -1
- package/dist/public-api.js +1 -0
- package/dist/public-api.js.map +1 -1
- package/package.json +35 -34
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview The plan card in a conversation: what an agent decomposed, and what became of it.
|
|
3
|
+
*
|
|
4
|
+
* **The moment this exists for.** An agent breaks a request into steps, the work runs, and it was
|
|
5
|
+
* good. Today that is where it ends — the decomposition was ephemeral, so the next person who wants
|
|
6
|
+
* the same thing asks an agent to invent it again. This card is where a one-off agent plan becomes
|
|
7
|
+
* reusable organizational automation (D17).
|
|
8
|
+
*
|
|
9
|
+
* Renders through the *same* `TaskGraphSpec` component the editor uses, in `ReadOnly` mode. That is
|
|
10
|
+
* deliberate: a second, simpler renderer for chat would be a second thing that can disagree with the
|
|
11
|
+
* canvas about what a graph means, and the whole program has been about removing exactly that.
|
|
12
|
+
*
|
|
13
|
+
* Save is **intent only**. This is a `widgets`-layer component in a conversation; it does not
|
|
14
|
+
* persist agents. The host converts and writes through `AgentSpecSync`, which keeps the one place
|
|
15
|
+
* that writes an agent the one place that writes an agent.
|
|
16
|
+
*
|
|
17
|
+
* @module @memberjunction/ng-conversations
|
|
18
|
+
*/
|
|
19
|
+
import { EventEmitter } from '@angular/core';
|
|
20
|
+
import type { TaskGraphSpec } from '@memberjunction/ai-core-plus';
|
|
21
|
+
import type { TaskGraphRuntimeStatus } from '@memberjunction/ng-task-graph-editor';
|
|
22
|
+
import * as i0 from "@angular/core";
|
|
23
|
+
/** Fired when the user asks to promote this plan into a reusable workflow. */
|
|
24
|
+
export declare class SaveAsWorkflowRequestedEventArgs {
|
|
25
|
+
readonly Spec: TaskGraphSpec;
|
|
26
|
+
/** The parent task the graph ran as, when it was dispatched. Absent for a folded graph. */
|
|
27
|
+
readonly ParentTaskID: string | null;
|
|
28
|
+
/**
|
|
29
|
+
* What to call it, as typed in the card.
|
|
30
|
+
*
|
|
31
|
+
* Asked here rather than in a dialog (ratified answer ④): one field and a button keeps the
|
|
32
|
+
* capture at two seconds. A saved workflow gets NO trigger from this path — it runs on
|
|
33
|
+
* demand until someone gives it a schedule — because the moment of saving is about capture,
|
|
34
|
+
* not scheduling.
|
|
35
|
+
*/
|
|
36
|
+
readonly Name: string;
|
|
37
|
+
/**
|
|
38
|
+
* True when the user asked to continue in the editor rather than just save.
|
|
39
|
+
*
|
|
40
|
+
* A secondary route on purpose: making the editor mandatory turns a two-second capture into
|
|
41
|
+
* a task, which is the friction that stops good one-off plans becoming reusable.
|
|
42
|
+
*/
|
|
43
|
+
readonly OpenInEditor: boolean;
|
|
44
|
+
constructor(Spec: TaskGraphSpec,
|
|
45
|
+
/** The parent task the graph ran as, when it was dispatched. Absent for a folded graph. */
|
|
46
|
+
ParentTaskID: string | null,
|
|
47
|
+
/**
|
|
48
|
+
* What to call it, as typed in the card.
|
|
49
|
+
*
|
|
50
|
+
* Asked here rather than in a dialog (ratified answer ④): one field and a button keeps the
|
|
51
|
+
* capture at two seconds. A saved workflow gets NO trigger from this path — it runs on
|
|
52
|
+
* demand until someone gives it a schedule — because the moment of saving is about capture,
|
|
53
|
+
* not scheduling.
|
|
54
|
+
*/
|
|
55
|
+
Name: string,
|
|
56
|
+
/**
|
|
57
|
+
* True when the user asked to continue in the editor rather than just save.
|
|
58
|
+
*
|
|
59
|
+
* A secondary route on purpose: making the editor mandatory turns a two-second capture into
|
|
60
|
+
* a task, which is the friction that stops good one-off plans becoming reusable.
|
|
61
|
+
*/
|
|
62
|
+
OpenInEditor?: boolean);
|
|
63
|
+
}
|
|
64
|
+
export declare class WorkflowPlanCardComponent {
|
|
65
|
+
/** The plan. Null renders nothing — a card with no graph has nothing to say. */
|
|
66
|
+
Spec: TaskGraphSpec | null;
|
|
67
|
+
/** Live per-step state. Absent means the card shows the plan without a progress line. */
|
|
68
|
+
RuntimeStatus: TaskGraphRuntimeStatus | null;
|
|
69
|
+
/** Handle for status/cancel, when the graph was dispatched rather than folded. */
|
|
70
|
+
ParentTaskID: string | null;
|
|
71
|
+
/**
|
|
72
|
+
* Whether to offer Save as Workflow.
|
|
73
|
+
*
|
|
74
|
+
* Off while work is still running: offering to save a plan whose shape may yet change — a
|
|
75
|
+
* retry, a failure routing down a recovery branch — invites saving something that never
|
|
76
|
+
* actually happened.
|
|
77
|
+
*/
|
|
78
|
+
AllowSaveAsWorkflow: boolean;
|
|
79
|
+
/** Starts collapsed. A plan card sits inline in a conversation and must not dominate it. */
|
|
80
|
+
Expanded: boolean;
|
|
81
|
+
SaveAsWorkflowRequested: EventEmitter<SaveAsWorkflowRequestedEventArgs>;
|
|
82
|
+
ExpandedChange: EventEmitter<boolean>;
|
|
83
|
+
/**
|
|
84
|
+
* The name to save under, seeded from the plan and editable in place.
|
|
85
|
+
*
|
|
86
|
+
* Held rather than read off the DOM so the empty-name guard is testable without a browser.
|
|
87
|
+
*/
|
|
88
|
+
SaveName: string;
|
|
89
|
+
/** True once the user has typed, so the seeded name stops being overwritten by the plan's. */
|
|
90
|
+
private nameTouched;
|
|
91
|
+
get StepCount(): number;
|
|
92
|
+
get TempIds(): string[];
|
|
93
|
+
/** One line of progress, or the plan's own reasoning when nothing is running yet. */
|
|
94
|
+
get SummaryLine(): string;
|
|
95
|
+
/** True once every step has reached a state nothing will move it out of. */
|
|
96
|
+
get IsFinished(): boolean;
|
|
97
|
+
/**
|
|
98
|
+
* Save is offered only once the work has settled — or when there is no runtime at all, which is
|
|
99
|
+
* the design-time case (a plan being previewed before it runs).
|
|
100
|
+
*/
|
|
101
|
+
get CanSave(): boolean;
|
|
102
|
+
ToggleExpanded(): void;
|
|
103
|
+
/**
|
|
104
|
+
* The name the field shows: what the user typed, else the plan's own name.
|
|
105
|
+
*
|
|
106
|
+
* Seeded rather than left blank because the plan already has a perfectly good name, and making
|
|
107
|
+
* someone invent one is the difference between saving and not bothering.
|
|
108
|
+
*/
|
|
109
|
+
get EffectiveName(): string;
|
|
110
|
+
OnNameChanged(value: string): void;
|
|
111
|
+
/** A workflow with no name is not saveable — there would be nothing to find it by later. */
|
|
112
|
+
get CanCommit(): boolean;
|
|
113
|
+
RequestSave(openInEditor?: boolean): void;
|
|
114
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<WorkflowPlanCardComponent, never>;
|
|
115
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<WorkflowPlanCardComponent, "mj-workflow-plan-card", never, { "Spec": { "alias": "Spec"; "required": false; }; "RuntimeStatus": { "alias": "RuntimeStatus"; "required": false; }; "ParentTaskID": { "alias": "ParentTaskID"; "required": false; }; "AllowSaveAsWorkflow": { "alias": "AllowSaveAsWorkflow"; "required": false; }; "Expanded": { "alias": "Expanded"; "required": false; }; }, { "SaveAsWorkflowRequested": "SaveAsWorkflowRequested"; "ExpandedChange": "ExpandedChange"; }, never, never, false, never>;
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=workflow-plan-card.component.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workflow-plan-card.component.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/message/workflow-plan-card.component.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAAa,YAAY,EAAiB,MAAM,eAAe,CAAC;AACvE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAClE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,sCAAsC,CAAC;;AAGnF,8EAA8E;AAC9E,qBAAa,gCAAgC;aAErB,IAAI,EAAE,aAAa;IACnC,2FAA2F;aAC3E,YAAY,EAAE,MAAM,GAAG,IAAI;IAC3C;;;;;;;OAOG;aACa,IAAI,EAAE,MAAM;IAC5B;;;;;OAKG;aACa,YAAY,EAAE,OAAO;gBAlBrB,IAAI,EAAE,aAAa;IACnC,2FAA2F;IAC3E,YAAY,EAAE,MAAM,GAAG,IAAI;IAC3C;;;;;;;OAOG;IACa,IAAI,EAAE,MAAM;IAC5B;;;;;OAKG;IACa,YAAY,GAAE,OAAe;CAEpD;AAED,qBAMa,yBAAyB;IAClC,gFAAgF;IAChE,IAAI,EAAE,aAAa,GAAG,IAAI,CAAQ;IAElD,yFAAyF;IACzE,aAAa,EAAE,sBAAsB,GAAG,IAAI,CAAQ;IAEpE,kFAAkF;IAClE,YAAY,EAAE,MAAM,GAAG,IAAI,CAAQ;IAEnD;;;;;;OAMG;IACa,mBAAmB,EAAE,OAAO,CAAQ;IAEpD,4FAA4F;IAC5E,QAAQ,EAAE,OAAO,CAAS;IAEzB,uBAAuB,iDAAwD;IAC/E,cAAc,wBAA+B;IAE9D;;;;OAIG;IACI,QAAQ,SAAM;IAErB,8FAA8F;IAC9F,OAAO,CAAC,WAAW,CAAS;IAE5B,IAAW,SAAS,IAAI,MAAM,CAE7B;IAED,IAAW,OAAO,IAAI,MAAM,EAAE,CAE7B;IAED,qFAAqF;IACrF,IAAW,WAAW,IAAI,MAAM,CAG/B;IAED,4EAA4E;IAC5E,IAAW,UAAU,IAAI,OAAO,CAE/B;IAED;;;OAGG;IACH,IAAW,OAAO,IAAI,OAAO,CAE5B;IAEM,cAAc,IAAI,IAAI;IAK7B;;;;;OAKG;IACH,IAAW,aAAa,IAAI,MAAM,CAEjC;IAEM,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAKzC,4FAA4F;IAC5F,IAAW,SAAS,IAAI,OAAO,CAE9B;IAEM,WAAW,CAAC,YAAY,GAAE,OAAe,GAAG,IAAI;yCAvF9C,yBAAyB;2CAAzB,yBAAyB;CAmGrC"}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview The plan card in a conversation: what an agent decomposed, and what became of it.
|
|
3
|
+
*
|
|
4
|
+
* **The moment this exists for.** An agent breaks a request into steps, the work runs, and it was
|
|
5
|
+
* good. Today that is where it ends — the decomposition was ephemeral, so the next person who wants
|
|
6
|
+
* the same thing asks an agent to invent it again. This card is where a one-off agent plan becomes
|
|
7
|
+
* reusable organizational automation (D17).
|
|
8
|
+
*
|
|
9
|
+
* Renders through the *same* `TaskGraphSpec` component the editor uses, in `ReadOnly` mode. That is
|
|
10
|
+
* deliberate: a second, simpler renderer for chat would be a second thing that can disagree with the
|
|
11
|
+
* canvas about what a graph means, and the whole program has been about removing exactly that.
|
|
12
|
+
*
|
|
13
|
+
* Save is **intent only**. This is a `widgets`-layer component in a conversation; it does not
|
|
14
|
+
* persist agents. The host converts and writes through `AgentSpecSync`, which keeps the one place
|
|
15
|
+
* that writes an agent the one place that writes an agent.
|
|
16
|
+
*
|
|
17
|
+
* @module @memberjunction/ng-conversations
|
|
18
|
+
*/
|
|
19
|
+
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
|
20
|
+
import { IsRuntimeSettled, SummarizeRuntime } from '@memberjunction/ng-task-graph-editor';
|
|
21
|
+
import * as i0 from "@angular/core";
|
|
22
|
+
import * as i1 from "@angular/forms";
|
|
23
|
+
import * as i2 from "@memberjunction/ng-task-graph-editor";
|
|
24
|
+
import * as i3 from "@memberjunction/ng-ui-components";
|
|
25
|
+
function WorkflowPlanCardComponent_Conditional_0_Conditional_7_Template(rf, ctx) { if (rf & 1) {
|
|
26
|
+
i0.ɵɵelement(0, "i", 6);
|
|
27
|
+
} }
|
|
28
|
+
function WorkflowPlanCardComponent_Conditional_0_Conditional_8_Conditional_2_Template(rf, ctx) { if (rf & 1) {
|
|
29
|
+
const _r3 = i0.ɵɵgetCurrentView();
|
|
30
|
+
i0.ɵɵelementStart(0, "div", 9)(1, "input", 10);
|
|
31
|
+
i0.ɵɵlistener("ngModelChange", function WorkflowPlanCardComponent_Conditional_0_Conditional_8_Conditional_2_Template_input_ngModelChange_1_listener($event) { i0.ɵɵrestoreView(_r3); const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.OnNameChanged($event)); });
|
|
32
|
+
i0.ɵɵelementEnd();
|
|
33
|
+
i0.ɵɵelementStart(2, "button", 11);
|
|
34
|
+
i0.ɵɵlistener("click", function WorkflowPlanCardComponent_Conditional_0_Conditional_8_Conditional_2_Template_button_click_2_listener() { i0.ɵɵrestoreView(_r3); const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.RequestSave(false)); });
|
|
35
|
+
i0.ɵɵelement(3, "i", 12);
|
|
36
|
+
i0.ɵɵtext(4, " Save as Workflow ");
|
|
37
|
+
i0.ɵɵelementEnd();
|
|
38
|
+
i0.ɵɵelementStart(5, "button", 13);
|
|
39
|
+
i0.ɵɵlistener("click", function WorkflowPlanCardComponent_Conditional_0_Conditional_8_Conditional_2_Template_button_click_5_listener() { i0.ɵɵrestoreView(_r3); const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.RequestSave(true)); });
|
|
40
|
+
i0.ɵɵtext(6, " Open in editor ");
|
|
41
|
+
i0.ɵɵelementEnd()();
|
|
42
|
+
i0.ɵɵelementStart(7, "p", 14);
|
|
43
|
+
i0.ɵɵtext(8, "Saved workflows run on demand until you give them a schedule.");
|
|
44
|
+
i0.ɵɵelementEnd();
|
|
45
|
+
} if (rf & 2) {
|
|
46
|
+
const ctx_r1 = i0.ɵɵnextContext(3);
|
|
47
|
+
i0.ɵɵadvance();
|
|
48
|
+
i0.ɵɵproperty("ngModel", ctx_r1.EffectiveName);
|
|
49
|
+
i0.ɵɵadvance();
|
|
50
|
+
i0.ɵɵproperty("disabled", !ctx_r1.CanCommit);
|
|
51
|
+
i0.ɵɵadvance(3);
|
|
52
|
+
i0.ɵɵproperty("disabled", !ctx_r1.CanCommit);
|
|
53
|
+
} }
|
|
54
|
+
function WorkflowPlanCardComponent_Conditional_0_Conditional_8_Template(rf, ctx) { if (rf & 1) {
|
|
55
|
+
i0.ɵɵelementStart(0, "div", 7);
|
|
56
|
+
i0.ɵɵelement(1, "mj-task-graph-editor", 8);
|
|
57
|
+
i0.ɵɵconditionalCreate(2, WorkflowPlanCardComponent_Conditional_0_Conditional_8_Conditional_2_Template, 9, 3);
|
|
58
|
+
i0.ɵɵelementEnd();
|
|
59
|
+
} if (rf & 2) {
|
|
60
|
+
const ctx_r1 = i0.ɵɵnextContext(2);
|
|
61
|
+
i0.ɵɵadvance();
|
|
62
|
+
i0.ɵɵproperty("Spec", ctx_r1.Spec)("RuntimeStatus", ctx_r1.RuntimeStatus)("ReadOnly", true)("ShowPalette", false)("ShowToolbar", false)("ShowMinimap", false);
|
|
63
|
+
i0.ɵɵadvance();
|
|
64
|
+
i0.ɵɵconditional(ctx_r1.CanSave ? 2 : -1);
|
|
65
|
+
} }
|
|
66
|
+
function WorkflowPlanCardComponent_Conditional_0_Template(rf, ctx) { if (rf & 1) {
|
|
67
|
+
const _r1 = i0.ɵɵgetCurrentView();
|
|
68
|
+
i0.ɵɵelementStart(0, "div", 1)(1, "button", 2);
|
|
69
|
+
i0.ɵɵlistener("click", function WorkflowPlanCardComponent_Conditional_0_Template_button_click_1_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.ToggleExpanded()); });
|
|
70
|
+
i0.ɵɵelement(2, "i", 3);
|
|
71
|
+
i0.ɵɵelementStart(3, "span", 4);
|
|
72
|
+
i0.ɵɵtext(4);
|
|
73
|
+
i0.ɵɵelementEnd();
|
|
74
|
+
i0.ɵɵelementStart(5, "span", 5);
|
|
75
|
+
i0.ɵɵtext(6);
|
|
76
|
+
i0.ɵɵelementEnd();
|
|
77
|
+
i0.ɵɵconditionalCreate(7, WorkflowPlanCardComponent_Conditional_0_Conditional_7_Template, 1, 0, "i", 6);
|
|
78
|
+
i0.ɵɵelementEnd();
|
|
79
|
+
i0.ɵɵconditionalCreate(8, WorkflowPlanCardComponent_Conditional_0_Conditional_8_Template, 3, 7, "div", 7);
|
|
80
|
+
i0.ɵɵelementEnd();
|
|
81
|
+
} if (rf & 2) {
|
|
82
|
+
const ctx_r1 = i0.ɵɵnextContext();
|
|
83
|
+
i0.ɵɵclassProp("mj-wpc--expanded", ctx_r1.Expanded);
|
|
84
|
+
i0.ɵɵadvance();
|
|
85
|
+
i0.ɵɵattribute("aria-expanded", ctx_r1.Expanded);
|
|
86
|
+
i0.ɵɵadvance();
|
|
87
|
+
i0.ɵɵclassProp("fa-chevron-right", !ctx_r1.Expanded)("fa-chevron-down", ctx_r1.Expanded);
|
|
88
|
+
i0.ɵɵadvance(2);
|
|
89
|
+
i0.ɵɵtextInterpolate(ctx_r1.Spec.workflowName);
|
|
90
|
+
i0.ɵɵadvance(2);
|
|
91
|
+
i0.ɵɵtextInterpolate(ctx_r1.SummaryLine);
|
|
92
|
+
i0.ɵɵadvance();
|
|
93
|
+
i0.ɵɵconditional(ctx_r1.IsFinished ? 7 : -1);
|
|
94
|
+
i0.ɵɵadvance();
|
|
95
|
+
i0.ɵɵconditional(ctx_r1.Expanded ? 8 : -1);
|
|
96
|
+
} }
|
|
97
|
+
/** Fired when the user asks to promote this plan into a reusable workflow. */
|
|
98
|
+
export class SaveAsWorkflowRequestedEventArgs {
|
|
99
|
+
Spec;
|
|
100
|
+
ParentTaskID;
|
|
101
|
+
Name;
|
|
102
|
+
OpenInEditor;
|
|
103
|
+
constructor(Spec,
|
|
104
|
+
/** The parent task the graph ran as, when it was dispatched. Absent for a folded graph. */
|
|
105
|
+
ParentTaskID,
|
|
106
|
+
/**
|
|
107
|
+
* What to call it, as typed in the card.
|
|
108
|
+
*
|
|
109
|
+
* Asked here rather than in a dialog (ratified answer ④): one field and a button keeps the
|
|
110
|
+
* capture at two seconds. A saved workflow gets NO trigger from this path — it runs on
|
|
111
|
+
* demand until someone gives it a schedule — because the moment of saving is about capture,
|
|
112
|
+
* not scheduling.
|
|
113
|
+
*/
|
|
114
|
+
Name,
|
|
115
|
+
/**
|
|
116
|
+
* True when the user asked to continue in the editor rather than just save.
|
|
117
|
+
*
|
|
118
|
+
* A secondary route on purpose: making the editor mandatory turns a two-second capture into
|
|
119
|
+
* a task, which is the friction that stops good one-off plans becoming reusable.
|
|
120
|
+
*/
|
|
121
|
+
OpenInEditor = false) {
|
|
122
|
+
this.Spec = Spec;
|
|
123
|
+
this.ParentTaskID = ParentTaskID;
|
|
124
|
+
this.Name = Name;
|
|
125
|
+
this.OpenInEditor = OpenInEditor;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
export class WorkflowPlanCardComponent {
|
|
129
|
+
/** The plan. Null renders nothing — a card with no graph has nothing to say. */
|
|
130
|
+
Spec = null;
|
|
131
|
+
/** Live per-step state. Absent means the card shows the plan without a progress line. */
|
|
132
|
+
RuntimeStatus = null;
|
|
133
|
+
/** Handle for status/cancel, when the graph was dispatched rather than folded. */
|
|
134
|
+
ParentTaskID = null;
|
|
135
|
+
/**
|
|
136
|
+
* Whether to offer Save as Workflow.
|
|
137
|
+
*
|
|
138
|
+
* Off while work is still running: offering to save a plan whose shape may yet change — a
|
|
139
|
+
* retry, a failure routing down a recovery branch — invites saving something that never
|
|
140
|
+
* actually happened.
|
|
141
|
+
*/
|
|
142
|
+
AllowSaveAsWorkflow = true;
|
|
143
|
+
/** Starts collapsed. A plan card sits inline in a conversation and must not dominate it. */
|
|
144
|
+
Expanded = false;
|
|
145
|
+
SaveAsWorkflowRequested = new EventEmitter();
|
|
146
|
+
ExpandedChange = new EventEmitter();
|
|
147
|
+
/**
|
|
148
|
+
* The name to save under, seeded from the plan and editable in place.
|
|
149
|
+
*
|
|
150
|
+
* Held rather than read off the DOM so the empty-name guard is testable without a browser.
|
|
151
|
+
*/
|
|
152
|
+
SaveName = '';
|
|
153
|
+
/** True once the user has typed, so the seeded name stops being overwritten by the plan's. */
|
|
154
|
+
nameTouched = false;
|
|
155
|
+
get StepCount() {
|
|
156
|
+
return this.Spec?.tasks?.length ?? 0;
|
|
157
|
+
}
|
|
158
|
+
get TempIds() {
|
|
159
|
+
return (this.Spec?.tasks ?? []).map((t) => t.tempId);
|
|
160
|
+
}
|
|
161
|
+
/** One line of progress, or the plan's own reasoning when nothing is running yet. */
|
|
162
|
+
get SummaryLine() {
|
|
163
|
+
if (this.RuntimeStatus)
|
|
164
|
+
return SummarizeRuntime(this.RuntimeStatus, this.TempIds);
|
|
165
|
+
return this.Spec?.reasoning || `${this.StepCount} step${this.StepCount === 1 ? '' : 's'}`;
|
|
166
|
+
}
|
|
167
|
+
/** True once every step has reached a state nothing will move it out of. */
|
|
168
|
+
get IsFinished() {
|
|
169
|
+
return !!this.RuntimeStatus && IsRuntimeSettled(this.RuntimeStatus, this.TempIds);
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Save is offered only once the work has settled — or when there is no runtime at all, which is
|
|
173
|
+
* the design-time case (a plan being previewed before it runs).
|
|
174
|
+
*/
|
|
175
|
+
get CanSave() {
|
|
176
|
+
return this.AllowSaveAsWorkflow && !!this.Spec && (!this.RuntimeStatus || this.IsFinished);
|
|
177
|
+
}
|
|
178
|
+
ToggleExpanded() {
|
|
179
|
+
this.Expanded = !this.Expanded;
|
|
180
|
+
this.ExpandedChange.emit(this.Expanded);
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* The name the field shows: what the user typed, else the plan's own name.
|
|
184
|
+
*
|
|
185
|
+
* Seeded rather than left blank because the plan already has a perfectly good name, and making
|
|
186
|
+
* someone invent one is the difference between saving and not bothering.
|
|
187
|
+
*/
|
|
188
|
+
get EffectiveName() {
|
|
189
|
+
return this.nameTouched ? this.SaveName : this.SaveName || (this.Spec?.workflowName ?? '');
|
|
190
|
+
}
|
|
191
|
+
OnNameChanged(value) {
|
|
192
|
+
this.nameTouched = true;
|
|
193
|
+
this.SaveName = value;
|
|
194
|
+
}
|
|
195
|
+
/** A workflow with no name is not saveable — there would be nothing to find it by later. */
|
|
196
|
+
get CanCommit() {
|
|
197
|
+
return this.CanSave && this.EffectiveName.trim().length > 0;
|
|
198
|
+
}
|
|
199
|
+
RequestSave(openInEditor = false) {
|
|
200
|
+
if (this.Spec && this.CanCommit) {
|
|
201
|
+
this.SaveAsWorkflowRequested.emit(new SaveAsWorkflowRequestedEventArgs(this.Spec, this.ParentTaskID, this.EffectiveName.trim(), openInEditor));
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
static ɵfac = function WorkflowPlanCardComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || WorkflowPlanCardComponent)(); };
|
|
205
|
+
static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: WorkflowPlanCardComponent, selectors: [["mj-workflow-plan-card"]], inputs: { Spec: "Spec", RuntimeStatus: "RuntimeStatus", ParentTaskID: "ParentTaskID", AllowSaveAsWorkflow: "AllowSaveAsWorkflow", Expanded: "Expanded" }, outputs: { SaveAsWorkflowRequested: "SaveAsWorkflowRequested", ExpandedChange: "ExpandedChange" }, standalone: false, decls: 1, vars: 1, consts: [[1, "mj-wpc", 3, "mj-wpc--expanded"], [1, "mj-wpc"], ["type", "button", 1, "mj-wpc__header", 3, "click"], [1, "fa-solid"], [1, "mj-wpc__title"], [1, "mj-wpc__summary"], ["aria-label", "Finished", 1, "fa-solid", "fa-circle-check", "mj-wpc__done"], [1, "mj-wpc__body"], [1, "mj-wpc__canvas", 3, "Spec", "RuntimeStatus", "ReadOnly", "ShowPalette", "ShowToolbar", "ShowMinimap"], [1, "mj-wpc__actions"], ["type", "text", "aria-label", "Workflow name", 1, "mj-wpc__name", 3, "ngModelChange", "ngModel"], ["mjButton", "", "variant", "primary", "size", "sm", 3, "click", "disabled"], [1, "fa-solid", "fa-floppy-disk"], ["mjButton", "", "variant", "secondary", "size", "sm", 3, "click", "disabled"], [1, "mj-wpc__note"]], template: function WorkflowPlanCardComponent_Template(rf, ctx) { if (rf & 1) {
|
|
206
|
+
i0.ɵɵconditionalCreate(0, WorkflowPlanCardComponent_Conditional_0_Template, 9, 11, "div", 0);
|
|
207
|
+
} if (rf & 2) {
|
|
208
|
+
i0.ɵɵconditional(ctx.Spec ? 0 : -1);
|
|
209
|
+
} }, dependencies: [i1.DefaultValueAccessor, i1.NgControlStatus, i1.NgModel, i2.TaskGraphEditorComponent, i3.MJButtonDirective], styles: ["\n\n.mj-wpc[_ngcontent-%COMP%] {\n border: 1px solid var(--mj-border-subtle);\n border-radius: var(--mj-radius-md, 6px);\n background: var(--mj-bg-surface);\n overflow: hidden;\n}\n\n.mj-wpc__header[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: var(--mj-space-sm, 0.5rem);\n width: 100%;\n padding: var(--mj-space-sm, 0.5rem) var(--mj-space-md, 1rem);\n background: transparent;\n border: none;\n cursor: pointer;\n text-align: left;\n color: var(--mj-text-primary);\n}\n\n.mj-wpc__title[_ngcontent-%COMP%] {\n font-weight: 600;\n}\n\n.mj-wpc__summary[_ngcontent-%COMP%] {\n flex: 1 1 auto;\n font-size: var(--mj-font-size-sm, 0.8125rem);\n color: var(--mj-text-secondary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.mj-wpc__done[_ngcontent-%COMP%] {\n color: var(--mj-color-success);\n}\n\n.mj-wpc__body[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n border-top: 1px solid var(--mj-border-subtle);\n}\n\n\n\n.mj-wpc__canvas[_ngcontent-%COMP%] {\n height: 320px;\n min-height: 0;\n}\n\n.mj-wpc__actions[_ngcontent-%COMP%] {\n display: flex;\n justify-content: flex-start;\n padding: var(--mj-space-sm, 0.5rem) var(--mj-space-md, 1rem);\n border-top: 1px solid var(--mj-border-subtle);\n}\n\n\n\n.mj-wpc__name[_ngcontent-%COMP%] {\n flex: 1;\n min-width: 0;\n border: 1px solid var(--mj-border-strong);\n border-radius: var(--mj-radius-md);\n padding: var(--mj-space-2) var(--mj-space-3);\n font: inherit;\n background: var(--mj-bg-surface);\n color: var(--mj-text-primary);\n}\n\n.mj-wpc__name[_ngcontent-%COMP%]:focus-visible {\n outline: 2px solid var(--mj-color-primary);\n outline-offset: -1px;\n}\n\n\n\n.mj-wpc__note[_ngcontent-%COMP%] {\n margin-top: var(--mj-space-2);\n font-size: var(--mj-font-size-xs);\n color: var(--mj-text-tertiary);\n}"] });
|
|
210
|
+
}
|
|
211
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(WorkflowPlanCardComponent, [{
|
|
212
|
+
type: Component,
|
|
213
|
+
args: [{ standalone: false, selector: 'mj-workflow-plan-card', template: "@if (Spec) {\n <div class=\"mj-wpc\" [class.mj-wpc--expanded]=\"Expanded\">\n <button type=\"button\" class=\"mj-wpc__header\" (click)=\"ToggleExpanded()\"\n [attr.aria-expanded]=\"Expanded\">\n <i class=\"fa-solid\" [class.fa-chevron-right]=\"!Expanded\" [class.fa-chevron-down]=\"Expanded\"></i>\n <span class=\"mj-wpc__title\">{{ Spec.workflowName }}</span>\n <span class=\"mj-wpc__summary\">{{ SummaryLine }}</span>\n @if (IsFinished) {\n <i class=\"fa-solid fa-circle-check mj-wpc__done\" aria-label=\"Finished\"></i>\n }\n </button>\n\n @if (Expanded) {\n <div class=\"mj-wpc__body\">\n <!-- The SAME component the editor uses, read-only. A simpler chat-only renderer would be a\n second thing that can disagree with the canvas about what a graph means. -->\n <mj-task-graph-editor\n class=\"mj-wpc__canvas\"\n [Spec]=\"Spec\"\n [RuntimeStatus]=\"RuntimeStatus\"\n [ReadOnly]=\"true\"\n [ShowPalette]=\"false\"\n [ShowToolbar]=\"false\"\n [ShowMinimap]=\"false\">\n </mj-task-graph-editor>\n\n @if (CanSave) {\n <!-- Name it inline, then offer the editor (ratified answer \u2463). No trigger is requested\n here: saving is capture, not scheduling. -->\n <div class=\"mj-wpc__actions\">\n <input\n class=\"mj-wpc__name\"\n type=\"text\"\n aria-label=\"Workflow name\"\n [ngModel]=\"EffectiveName\"\n (ngModelChange)=\"OnNameChanged($event)\" />\n <button mjButton variant=\"primary\" size=\"sm\" [disabled]=\"!CanCommit\" (click)=\"RequestSave(false)\">\n <i class=\"fa-solid fa-floppy-disk\"></i> Save as Workflow\n </button>\n <button mjButton variant=\"secondary\" size=\"sm\" [disabled]=\"!CanCommit\" (click)=\"RequestSave(true)\">\n Open in editor\n </button>\n </div>\n <p class=\"mj-wpc__note\">Saved workflows run on demand until you give them a schedule.</p>\n }\n </div>\n }\n </div>\n}\n", styles: ["/* Design tokens only \u2014 no hardcoded colors. */\n.mj-wpc {\n border: 1px solid var(--mj-border-subtle);\n border-radius: var(--mj-radius-md, 6px);\n background: var(--mj-bg-surface);\n overflow: hidden;\n}\n\n.mj-wpc__header {\n display: flex;\n align-items: center;\n gap: var(--mj-space-sm, 0.5rem);\n width: 100%;\n padding: var(--mj-space-sm, 0.5rem) var(--mj-space-md, 1rem);\n background: transparent;\n border: none;\n cursor: pointer;\n text-align: left;\n color: var(--mj-text-primary);\n}\n\n.mj-wpc__title {\n font-weight: 600;\n}\n\n.mj-wpc__summary {\n flex: 1 1 auto;\n font-size: var(--mj-font-size-sm, 0.8125rem);\n color: var(--mj-text-secondary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.mj-wpc__done {\n color: var(--mj-color-success);\n}\n\n.mj-wpc__body {\n display: flex;\n flex-direction: column;\n border-top: 1px solid var(--mj-border-subtle);\n}\n\n/* A plan card sits inline in a conversation \u2014 a bounded height keeps it from dominating the thread. */\n.mj-wpc__canvas {\n height: 320px;\n min-height: 0;\n}\n\n.mj-wpc__actions {\n display: flex;\n justify-content: flex-start;\n padding: var(--mj-space-sm, 0.5rem) var(--mj-space-md, 1rem);\n border-top: 1px solid var(--mj-border-subtle);\n}\n\n/* Name it inline (\u2463): one field and a button, so capture stays a two-second act. */\n.mj-wpc__name {\n flex: 1;\n min-width: 0;\n border: 1px solid var(--mj-border-strong);\n border-radius: var(--mj-radius-md);\n padding: var(--mj-space-2) var(--mj-space-3);\n font: inherit;\n background: var(--mj-bg-surface);\n color: var(--mj-text-primary);\n}\n\n.mj-wpc__name:focus-visible {\n outline: 2px solid var(--mj-color-primary);\n outline-offset: -1px;\n}\n\n/* States the default plainly, so nobody goes looking for a schedule field that is not there. */\n.mj-wpc__note {\n margin-top: var(--mj-space-2);\n font-size: var(--mj-font-size-xs);\n color: var(--mj-text-tertiary);\n}\n"] }]
|
|
214
|
+
}], null, { Spec: [{
|
|
215
|
+
type: Input
|
|
216
|
+
}], RuntimeStatus: [{
|
|
217
|
+
type: Input
|
|
218
|
+
}], ParentTaskID: [{
|
|
219
|
+
type: Input
|
|
220
|
+
}], AllowSaveAsWorkflow: [{
|
|
221
|
+
type: Input
|
|
222
|
+
}], Expanded: [{
|
|
223
|
+
type: Input
|
|
224
|
+
}], SaveAsWorkflowRequested: [{
|
|
225
|
+
type: Output
|
|
226
|
+
}], ExpandedChange: [{
|
|
227
|
+
type: Output
|
|
228
|
+
}] }); })();
|
|
229
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(WorkflowPlanCardComponent, { className: "WorkflowPlanCardComponent", filePath: "src/lib/components/message/workflow-plan-card.component.ts", lineNumber: 55 }); })();
|
|
230
|
+
//# sourceMappingURL=workflow-plan-card.component.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workflow-plan-card.component.js","sourceRoot":"","sources":["../../../../src/lib/components/message/workflow-plan-card.component.ts","../../../../src/lib/components/message/workflow-plan-card.component.html"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAGvE,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,sCAAsC,CAAC;;;;;;ICblF,uBAA2E;;;;IAsBvE,AADF,8BAA6B,gBAMiB;IAA1C,+OAAiB,4BAAqB,KAAC;IALzC,iBAK4C;IAC5C,kCAAkG;IAA7B,0NAAS,mBAAY,KAAK,CAAC,KAAC;IAC/F,wBAAuC;IAAC,kCAC1C;IAAA,iBAAS;IACT,kCAAmG;IAA5B,0NAAS,mBAAY,IAAI,CAAC,KAAC;IAChG,gCACF;IACF,AADE,iBAAS,EACL;IACN,6BAAwB;IAAA,6EAA6D;IAAA,iBAAI;;;IATrF,cAAyB;IAAzB,8CAAyB;IAEkB,cAAuB;IAAvB,4CAAuB;IAGrB,eAAuB;IAAvB,4CAAuB;;;IA1B5E,8BAA0B;IAGxB,0CAQuB;IAEvB,6GAAe;IAmBjB,iBAAM;;;IA3BF,cAAa;IAKb,AADA,AADA,AADA,AADA,AADA,kCAAa,uCACkB,kBACd,sBACI,sBACA,sBACA;IAGvB,cAkBC;IAlBD,yCAkBC;;;;IA1CL,AADF,8BAAwD,gBAEd;IADK,6LAAS,uBAAgB,KAAC;IAErE,uBAAgG;IAChG,+BAA4B;IAAA,YAAuB;IAAA,iBAAO;IAC1D,+BAA8B;IAAA,YAAiB;IAAA,iBAAO;IACtD,uGAAkB;IAGpB,iBAAS;IAET,yGAAgB;IAmClB,iBAAM;;;IA9Cc,mDAAmC;IAE7C,cAA+B;;IACjB,cAAoC;IAAC,AAArC,oDAAoC,oCAAmC;IAC/D,eAAuB;IAAvB,8CAAuB;IACrB,eAAiB;IAAjB,wCAAiB;IAC/C,cAEC;IAFD,4CAEC;IAGH,cAkCC;IAlCD,0CAkCC;;ADvBL,8EAA8E;AAC9E,MAAM,OAAO,gCAAgC;IAErB;IAEA;IASA;IAOA;IAnBpB,YACoB,IAAmB;IACnC,2FAA2F;IAC3E,YAA2B;IAC3C;;;;;;;OAOG;IACa,IAAY;IAC5B;;;;;OAKG;IACa,eAAwB,KAAK;QAlB7B,SAAI,GAAJ,IAAI,CAAe;QAEnB,iBAAY,GAAZ,YAAY,CAAe;QAS3B,SAAI,GAAJ,IAAI,CAAQ;QAOZ,iBAAY,GAAZ,YAAY,CAAiB;IAC9C,CAAC;CACP;AAQD,MAAM,OAAO,yBAAyB;IAClC,gFAAgF;IAChE,IAAI,GAAyB,IAAI,CAAC;IAElD,yFAAyF;IACzE,aAAa,GAAkC,IAAI,CAAC;IAEpE,kFAAkF;IAClE,YAAY,GAAkB,IAAI,CAAC;IAEnD;;;;;;OAMG;IACa,mBAAmB,GAAY,IAAI,CAAC;IAEpD,4FAA4F;IAC5E,QAAQ,GAAY,KAAK,CAAC;IAEzB,uBAAuB,GAAG,IAAI,YAAY,EAAoC,CAAC;IAC/E,cAAc,GAAG,IAAI,YAAY,EAAW,CAAC;IAE9D;;;;OAIG;IACI,QAAQ,GAAG,EAAE,CAAC;IAErB,8FAA8F;IACtF,WAAW,GAAG,KAAK,CAAC;IAE5B,IAAW,SAAS;QAChB,OAAO,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,MAAM,IAAI,CAAC,CAAC;IACzC,CAAC;IAED,IAAW,OAAO;QACd,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;IACzD,CAAC;IAED,qFAAqF;IACrF,IAAW,WAAW;QAClB,IAAI,IAAI,CAAC,aAAa;YAAE,OAAO,gBAAgB,CAAC,IAAI,CAAC,aAAa,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;QAClF,OAAO,IAAI,CAAC,IAAI,EAAE,SAAS,IAAI,GAAG,IAAI,CAAC,SAAS,QAAQ,IAAI,CAAC,SAAS,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC;IAC9F,CAAC;IAED,4EAA4E;IAC5E,IAAW,UAAU;QACjB,OAAO,CAAC,CAAC,IAAI,CAAC,aAAa,IAAI,gBAAgB,CAAC,IAAI,CAAC,aAAa,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;IACtF,CAAC;IAED;;;OAGG;IACH,IAAW,OAAO;QACd,OAAO,IAAI,CAAC,mBAAmB,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,CAAC,aAAa,IAAI,IAAI,CAAC,UAAU,CAAC,CAAC;IAC/F,CAAC;IAEM,cAAc;QACjB,IAAI,CAAC,QAAQ,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC;QAC/B,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC5C,CAAC;IAED;;;;;OAKG;IACH,IAAW,aAAa;QACpB,OAAO,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,YAAY,IAAI,EAAE,CAAC,CAAC;IAC/F,CAAC;IAEM,aAAa,CAAC,KAAa;QAC9B,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;QACxB,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC;IAC1B,CAAC;IAED,4FAA4F;IAC5F,IAAW,SAAS;QAChB,OAAO,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC;IAChE,CAAC;IAEM,WAAW,CAAC,eAAwB,KAAK;QAC5C,IAAI,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YAC9B,IAAI,CAAC,uBAAuB,CAAC,IAAI,CAC7B,IAAI,gCAAgC,CAChC,IAAI,CAAC,IAAI,EACT,IAAI,CAAC,YAAY,EACjB,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,EACzB,YAAY,CACf,CACJ,CAAC;QACN,CAAC;IACL,CAAC;mHAlGQ,yBAAyB;6DAAzB,yBAAyB;YCtDtC,4FAAY;;YAAZ,mCAgDC;;;iFDMY,yBAAyB;cANrC,SAAS;6BACM,KAAK,YACP,uBAAuB;;kBAMhC,KAAK;;kBAGL,KAAK;;kBAGL,KAAK;;kBASL,KAAK;;kBAGL,KAAK;;kBAEL,MAAM;;kBACN,MAAM;;kFAvBE,yBAAyB","sourcesContent":["/**\n * @fileoverview The plan card in a conversation: what an agent decomposed, and what became of it.\n *\n * **The moment this exists for.** An agent breaks a request into steps, the work runs, and it was\n * good. Today that is where it ends — the decomposition was ephemeral, so the next person who wants\n * the same thing asks an agent to invent it again. This card is where a one-off agent plan becomes\n * reusable organizational automation (D17).\n *\n * Renders through the *same* `TaskGraphSpec` component the editor uses, in `ReadOnly` mode. That is\n * deliberate: a second, simpler renderer for chat would be a second thing that can disagree with the\n * canvas about what a graph means, and the whole program has been about removing exactly that.\n *\n * Save is **intent only**. This is a `widgets`-layer component in a conversation; it does not\n * persist agents. The host converts and writes through `AgentSpecSync`, which keeps the one place\n * that writes an agent the one place that writes an agent.\n *\n * @module @memberjunction/ng-conversations\n */\nimport { Component, EventEmitter, Input, Output } from '@angular/core';\nimport type { TaskGraphSpec } from '@memberjunction/ai-core-plus';\nimport type { TaskGraphRuntimeStatus } from '@memberjunction/ng-task-graph-editor';\nimport { IsRuntimeSettled, SummarizeRuntime } from '@memberjunction/ng-task-graph-editor';\n\n/** Fired when the user asks to promote this plan into a reusable workflow. */\nexport class SaveAsWorkflowRequestedEventArgs {\n constructor(\n public readonly Spec: TaskGraphSpec,\n /** The parent task the graph ran as, when it was dispatched. Absent for a folded graph. */\n public readonly ParentTaskID: string | null,\n /**\n * What to call it, as typed in the card.\n *\n * Asked here rather than in a dialog (ratified answer ④): one field and a button keeps the\n * capture at two seconds. A saved workflow gets NO trigger from this path — it runs on\n * demand until someone gives it a schedule — because the moment of saving is about capture,\n * not scheduling.\n */\n public readonly Name: string,\n /**\n * True when the user asked to continue in the editor rather than just save.\n *\n * A secondary route on purpose: making the editor mandatory turns a two-second capture into\n * a task, which is the friction that stops good one-off plans becoming reusable.\n */\n public readonly OpenInEditor: boolean = false,\n ) {}\n}\n\n@Component({\n standalone: false,\n selector: 'mj-workflow-plan-card',\n templateUrl: './workflow-plan-card.component.html',\n styleUrls: ['./workflow-plan-card.component.css'],\n})\nexport class WorkflowPlanCardComponent {\n /** The plan. Null renders nothing — a card with no graph has nothing to say. */\n @Input() public Spec: TaskGraphSpec | null = null;\n\n /** Live per-step state. Absent means the card shows the plan without a progress line. */\n @Input() public RuntimeStatus: TaskGraphRuntimeStatus | null = null;\n\n /** Handle for status/cancel, when the graph was dispatched rather than folded. */\n @Input() public ParentTaskID: string | null = null;\n\n /**\n * Whether to offer Save as Workflow.\n *\n * Off while work is still running: offering to save a plan whose shape may yet change — a\n * retry, a failure routing down a recovery branch — invites saving something that never\n * actually happened.\n */\n @Input() public AllowSaveAsWorkflow: boolean = true;\n\n /** Starts collapsed. A plan card sits inline in a conversation and must not dominate it. */\n @Input() public Expanded: boolean = false;\n\n @Output() public SaveAsWorkflowRequested = new EventEmitter<SaveAsWorkflowRequestedEventArgs>();\n @Output() public ExpandedChange = new EventEmitter<boolean>();\n\n /**\n * The name to save under, seeded from the plan and editable in place.\n *\n * Held rather than read off the DOM so the empty-name guard is testable without a browser.\n */\n public SaveName = '';\n\n /** True once the user has typed, so the seeded name stops being overwritten by the plan's. */\n private nameTouched = false;\n\n public get StepCount(): number {\n return this.Spec?.tasks?.length ?? 0;\n }\n\n public get TempIds(): string[] {\n return (this.Spec?.tasks ?? []).map((t) => t.tempId);\n }\n\n /** One line of progress, or the plan's own reasoning when nothing is running yet. */\n public get SummaryLine(): string {\n if (this.RuntimeStatus) return SummarizeRuntime(this.RuntimeStatus, this.TempIds);\n return this.Spec?.reasoning || `${this.StepCount} step${this.StepCount === 1 ? '' : 's'}`;\n }\n\n /** True once every step has reached a state nothing will move it out of. */\n public get IsFinished(): boolean {\n return !!this.RuntimeStatus && IsRuntimeSettled(this.RuntimeStatus, this.TempIds);\n }\n\n /**\n * Save is offered only once the work has settled — or when there is no runtime at all, which is\n * the design-time case (a plan being previewed before it runs).\n */\n public get CanSave(): boolean {\n return this.AllowSaveAsWorkflow && !!this.Spec && (!this.RuntimeStatus || this.IsFinished);\n }\n\n public ToggleExpanded(): void {\n this.Expanded = !this.Expanded;\n this.ExpandedChange.emit(this.Expanded);\n }\n\n /**\n * The name the field shows: what the user typed, else the plan's own name.\n *\n * Seeded rather than left blank because the plan already has a perfectly good name, and making\n * someone invent one is the difference between saving and not bothering.\n */\n public get EffectiveName(): string {\n return this.nameTouched ? this.SaveName : this.SaveName || (this.Spec?.workflowName ?? '');\n }\n\n public OnNameChanged(value: string): void {\n this.nameTouched = true;\n this.SaveName = value;\n }\n\n /** A workflow with no name is not saveable — there would be nothing to find it by later. */\n public get CanCommit(): boolean {\n return this.CanSave && this.EffectiveName.trim().length > 0;\n }\n\n public RequestSave(openInEditor: boolean = false): void {\n if (this.Spec && this.CanCommit) {\n this.SaveAsWorkflowRequested.emit(\n new SaveAsWorkflowRequestedEventArgs(\n this.Spec,\n this.ParentTaskID,\n this.EffectiveName.trim(),\n openInEditor,\n ),\n );\n }\n }\n}\n","@if (Spec) {\n <div class=\"mj-wpc\" [class.mj-wpc--expanded]=\"Expanded\">\n <button type=\"button\" class=\"mj-wpc__header\" (click)=\"ToggleExpanded()\"\n [attr.aria-expanded]=\"Expanded\">\n <i class=\"fa-solid\" [class.fa-chevron-right]=\"!Expanded\" [class.fa-chevron-down]=\"Expanded\"></i>\n <span class=\"mj-wpc__title\">{{ Spec.workflowName }}</span>\n <span class=\"mj-wpc__summary\">{{ SummaryLine }}</span>\n @if (IsFinished) {\n <i class=\"fa-solid fa-circle-check mj-wpc__done\" aria-label=\"Finished\"></i>\n }\n </button>\n\n @if (Expanded) {\n <div class=\"mj-wpc__body\">\n <!-- The SAME component the editor uses, read-only. A simpler chat-only renderer would be a\n second thing that can disagree with the canvas about what a graph means. -->\n <mj-task-graph-editor\n class=\"mj-wpc__canvas\"\n [Spec]=\"Spec\"\n [RuntimeStatus]=\"RuntimeStatus\"\n [ReadOnly]=\"true\"\n [ShowPalette]=\"false\"\n [ShowToolbar]=\"false\"\n [ShowMinimap]=\"false\">\n </mj-task-graph-editor>\n\n @if (CanSave) {\n <!-- Name it inline, then offer the editor (ratified answer ④). No trigger is requested\n here: saving is capture, not scheduling. -->\n <div class=\"mj-wpc__actions\">\n <input\n class=\"mj-wpc__name\"\n type=\"text\"\n aria-label=\"Workflow name\"\n [ngModel]=\"EffectiveName\"\n (ngModelChange)=\"OnNameChanged($event)\" />\n <button mjButton variant=\"primary\" size=\"sm\" [disabled]=\"!CanCommit\" (click)=\"RequestSave(false)\">\n <i class=\"fa-solid fa-floppy-disk\"></i> Save as Workflow\n </button>\n <button mjButton variant=\"secondary\" size=\"sm\" [disabled]=\"!CanCommit\" (click)=\"RequestSave(true)\">\n Open in editor\n </button>\n </div>\n <p class=\"mj-wpc__note\">Saved workflows run on demand until you give them a schedule.</p>\n }\n </div>\n }\n </div>\n}\n"]}
|
|
@@ -34,6 +34,14 @@ export declare class TasksFullViewComponent extends BaseAngularComponent impleme
|
|
|
34
34
|
onTaskClick(task: MJTaskEntity): Promise<void>;
|
|
35
35
|
private loadTaskHierarchy;
|
|
36
36
|
private loadTaskDependencies;
|
|
37
|
+
/**
|
|
38
|
+
* Builds the TaskID -> AgentRunID map from each task's own AgentRunID column.
|
|
39
|
+
*
|
|
40
|
+
* Previously this joined tasks to runs through the shared ConversationDetailID, which meant
|
|
41
|
+
* every sibling task in a graph resolved to the SAME agent run — the run link in the Gantt
|
|
42
|
+
* and detail panel was wrong for all but one task. Each task now records the specific run
|
|
43
|
+
* that executed it (Task.AgentRunID), so the mapping is direct and needs no query at all.
|
|
44
|
+
*/
|
|
37
45
|
private loadAgentRuns;
|
|
38
46
|
backToTaskList(): void;
|
|
39
47
|
onSubTaskClick(subTask: MJTaskEntity): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tasks-full-view.component.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/task/tasks-full-view.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4B,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACzG,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AAErE,OAAO,EAAE,QAAQ,EAAW,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,YAAY,EAAE,sBAAsB,EAAsB,MAAM,+BAA+B,CAAC;;AAKzG;;;;GAIG;AACH,qBA2Ha,sBAAuB,SAAQ,oBAAqB,YAAW,MAAM,EAAE,SAAS;IAClF,aAAa,EAAG,MAAM,CAAC;IACvB,WAAW,EAAG,QAAQ,CAAC;IACvB,UAAU,EAAE,MAAM,CAAS;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IACrB,gBAAgB;oBAAkC,MAAM;kBAAY,MAAM;OAAM;IAChF,YAAY,8BAAqC;IAEpD,QAAQ,EAAE,YAAY,EAAE,CAAM;IAC9B,aAAa,EAAE,YAAY,EAAE,CAAM;IACnC,QAAQ,EAAE,YAAY,EAAE,CAAM;IAC9B,kBAAkB,EAAE,YAAY,EAAE,CAAM;IACxC,gBAAgB,EAAE,sBAAsB,EAAE,CAAM;IAChD,WAAW,sBAA6B;IACxC,YAAY,EAAE,YAAY,GAAG,IAAI,CAAQ;IACzC,mBAAmB,EAAE,OAAO,CAAS;IACrC,SAAS,EAAE,OAAO,CAAS;IAClC,OAAO,CAAC,kBAAkB,CAAkB;IAE5C,QAAQ;IAIR,WAAW,CAAC,OAAO,EAAE,aAAa;IAerB,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;IA4D1B,WAAW,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;YAY7C,iBAAiB;YAgDjB,oBAAoB;
|
|
1
|
+
{"version":3,"file":"tasks-full-view.component.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/task/tasks-full-view.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4B,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACzG,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AAErE,OAAO,EAAE,QAAQ,EAAW,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,YAAY,EAAE,sBAAsB,EAAsB,MAAM,+BAA+B,CAAC;;AAKzG;;;;GAIG;AACH,qBA2Ha,sBAAuB,SAAQ,oBAAqB,YAAW,MAAM,EAAE,SAAS;IAClF,aAAa,EAAG,MAAM,CAAC;IACvB,WAAW,EAAG,QAAQ,CAAC;IACvB,UAAU,EAAE,MAAM,CAAS;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IACrB,gBAAgB;oBAAkC,MAAM;kBAAY,MAAM;OAAM;IAChF,YAAY,8BAAqC;IAEpD,QAAQ,EAAE,YAAY,EAAE,CAAM;IAC9B,aAAa,EAAE,YAAY,EAAE,CAAM;IACnC,QAAQ,EAAE,YAAY,EAAE,CAAM;IAC9B,kBAAkB,EAAE,YAAY,EAAE,CAAM;IACxC,gBAAgB,EAAE,sBAAsB,EAAE,CAAM;IAChD,WAAW,sBAA6B;IACxC,YAAY,EAAE,YAAY,GAAG,IAAI,CAAQ;IACzC,mBAAmB,EAAE,OAAO,CAAS;IACrC,SAAS,EAAE,OAAO,CAAS;IAClC,OAAO,CAAC,kBAAkB,CAAkB;IAE5C,QAAQ;IAIR,WAAW,CAAC,OAAO,EAAE,aAAa;IAerB,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;IA4D1B,WAAW,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;YAY7C,iBAAiB;YAgDjB,oBAAoB;IAkClC;;;;;;;OAOG;IACH,OAAO,CAAC,aAAa;IASd,cAAc,IAAI,IAAI;IAYtB,cAAc,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI;IAK3C,kBAAkB,CAAC,KAAK,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IAKzE,cAAc,IAAI,MAAM;IAMxB,wBAAwB,IAAI,MAAM;yCA7O9B,sBAAsB;2CAAtB,sBAAsB;CAqPlC"}
|
|
@@ -164,7 +164,7 @@ export class TasksFullViewComponent extends BaseAngularComponent {
|
|
|
164
164
|
// Load task dependencies for this hierarchy
|
|
165
165
|
await this.loadTaskDependencies(rootId);
|
|
166
166
|
// Load agent runs for this hierarchy
|
|
167
|
-
|
|
167
|
+
this.loadAgentRuns(allHierarchy);
|
|
168
168
|
}
|
|
169
169
|
else {
|
|
170
170
|
console.error('❌ Failed to load task hierarchy:', hierarchyResult.ErrorMessage);
|
|
@@ -208,69 +208,21 @@ export class TasksFullViewComponent extends BaseAngularComponent {
|
|
|
208
208
|
this.taskDependencies = [];
|
|
209
209
|
}
|
|
210
210
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
const schema = '__mj';
|
|
225
|
-
// Build filter to find agent runs for these conversation details
|
|
226
|
-
// Use a subquery to avoid passing large ID lists
|
|
227
|
-
const taskIds = tasks.map(t => `'${t.ID}'`).join(',');
|
|
228
|
-
const agentRunsResult = await rv.RunView({
|
|
229
|
-
EntityName: 'MJ: AI Agent Runs',
|
|
230
|
-
ExtraFilter: `
|
|
231
|
-
ConversationDetailID IN (
|
|
232
|
-
SELECT DISTINCT ConversationDetailID
|
|
233
|
-
FROM [${schema}].[vwTasks]
|
|
234
|
-
WHERE ID IN (${taskIds})
|
|
235
|
-
AND ConversationDetailID IS NOT NULL
|
|
236
|
-
)
|
|
237
|
-
`,
|
|
238
|
-
ResultType: 'entity_object'
|
|
239
|
-
}, this.currentUser);
|
|
240
|
-
if (agentRunsResult.Success) {
|
|
241
|
-
const agentRuns = agentRunsResult.Results || [];
|
|
242
|
-
console.log(`🤖 Loaded ${agentRuns.length} agent runs`, agentRuns);
|
|
243
|
-
// Build map: ConversationDetailID -> AgentRunID
|
|
244
|
-
const convoToRunMap = new Map();
|
|
245
|
-
agentRuns.forEach(run => {
|
|
246
|
-
if (run.ConversationDetailID) {
|
|
247
|
-
convoToRunMap.set(run.ConversationDetailID, run.ID);
|
|
248
|
-
console.log(`📝 Mapping ConvoDetailID ${run.ConversationDetailID} -> RunID ${run.ID}`);
|
|
249
|
-
}
|
|
250
|
-
});
|
|
251
|
-
// Map TaskID -> AgentRunID using ConversationDetailID as the link
|
|
252
|
-
tasks.forEach(task => {
|
|
253
|
-
console.log(`🔍 Task ${task.Name} - ConvoDetailID: ${task.ConversationDetailID}, AgentID: ${task.AgentID}`);
|
|
254
|
-
if (task.ConversationDetailID) {
|
|
255
|
-
const agentRunId = convoToRunMap.get(task.ConversationDetailID);
|
|
256
|
-
if (agentRunId) {
|
|
257
|
-
this.agentRunMap.set(task.ID, agentRunId);
|
|
258
|
-
console.log(`✅ Mapped Task ${task.ID} -> AgentRun ${agentRunId}`);
|
|
259
|
-
}
|
|
260
|
-
else {
|
|
261
|
-
console.log(`⚠️ No agent run found for ConvoDetailID ${task.ConversationDetailID}`);
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
});
|
|
265
|
-
console.log(`🔗 Mapped ${this.agentRunMap.size} tasks to agent runs`, Array.from(this.agentRunMap.entries()));
|
|
266
|
-
}
|
|
267
|
-
else {
|
|
268
|
-
console.error('❌ Failed to load agent runs:', agentRunsResult.ErrorMessage);
|
|
211
|
+
/**
|
|
212
|
+
* Builds the TaskID -> AgentRunID map from each task's own AgentRunID column.
|
|
213
|
+
*
|
|
214
|
+
* Previously this joined tasks to runs through the shared ConversationDetailID, which meant
|
|
215
|
+
* every sibling task in a graph resolved to the SAME agent run — the run link in the Gantt
|
|
216
|
+
* and detail panel was wrong for all but one task. Each task now records the specific run
|
|
217
|
+
* that executed it (Task.AgentRunID), so the mapping is direct and needs no query at all.
|
|
218
|
+
*/
|
|
219
|
+
loadAgentRuns(tasks) {
|
|
220
|
+
this.agentRunMap.clear();
|
|
221
|
+
for (const task of tasks) {
|
|
222
|
+
if (task.AgentRunID) {
|
|
223
|
+
this.agentRunMap.set(task.ID, task.AgentRunID);
|
|
269
224
|
}
|
|
270
225
|
}
|
|
271
|
-
catch (error) {
|
|
272
|
-
console.error('Failed to load agent runs:', error);
|
|
273
|
-
}
|
|
274
226
|
}
|
|
275
227
|
backToTaskList() {
|
|
276
228
|
this.selectedTask = null;
|