feat: split authoring diagnosis and repair beats
This commit is contained in:
@@ -4,14 +4,18 @@ import {
|
||||
projectPreparedAuthoringThread,
|
||||
type AuthoringPhaseId,
|
||||
} from "./authoring-recording.js";
|
||||
import type { Scene9SubmittedOverrides } from "./scene9-message-state.js";
|
||||
import {
|
||||
recordingPhaseForStep,
|
||||
type PreparedLifecycleStepId,
|
||||
} from "./authoring-projection.js";
|
||||
import type { PreparedLifecycleSubmittedOverrides } from "./prepared-lifecycle-message-state.js";
|
||||
|
||||
type AuthoringConversationProps = {
|
||||
readonly throughPhase: AuthoringPhaseId;
|
||||
readonly activePhase: AuthoringPhaseId;
|
||||
readonly throughPhase: PreparedLifecycleStepId | AuthoringPhaseId;
|
||||
readonly activePhase: PreparedLifecycleStepId | AuthoringPhaseId;
|
||||
readonly surface: "stage" | "dock";
|
||||
readonly requestOverride?: string | undefined;
|
||||
readonly requestOverrides?: Scene9SubmittedOverrides | undefined;
|
||||
readonly requestOverrides?: PreparedLifecycleSubmittedOverrides | undefined;
|
||||
readonly scrollMode?: "active" | "start" | undefined;
|
||||
};
|
||||
|
||||
@@ -27,8 +31,12 @@ export const AuthoringConversation = ({
|
||||
<AssistantOperatorThread
|
||||
mode={surface === "stage" ? "full" : "dock"}
|
||||
surface={surface}
|
||||
messages={projectPreparedAuthoringThread(throughPhase, requestOverride, requestOverrides)}
|
||||
activeToolGroupId={authoringToolGroupId(activePhase)}
|
||||
messages={projectPreparedAuthoringThread(
|
||||
recordingPhaseForStep(throughPhase),
|
||||
requestOverride,
|
||||
requestOverrides,
|
||||
)}
|
||||
activeToolGroupId={authoringToolGroupId(recordingPhaseForStep(activePhase))}
|
||||
scrollMode={scrollMode}
|
||||
ariaLabel="prepared authoring conversation"
|
||||
/>
|
||||
|
||||
@@ -4,16 +4,16 @@ import type { ComponentProps } from "react";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { PresentationAssistantPane } from "./PresentationAssistantPane.js";
|
||||
import {
|
||||
SCENE9_PHASE_PLACEHOLDERS,
|
||||
SCENE9_PHASE_PROMPTS,
|
||||
initialScene9MessageState,
|
||||
projectScene9Message,
|
||||
} from "./scene9-message-state.js";
|
||||
PREPARED_LIFECYCLE_PHASE_PLACEHOLDERS,
|
||||
PREPARED_LIFECYCLE_PHASE_PROMPTS,
|
||||
initialPreparedLifecycleMessageState,
|
||||
projectPreparedLifecycleMessage,
|
||||
} from "./prepared-lifecycle-message-state.js";
|
||||
|
||||
afterEach(cleanup);
|
||||
|
||||
const renderPane = (
|
||||
phase: Parameters<typeof projectScene9Message>[1],
|
||||
phase: Parameters<typeof projectPreparedLifecycleMessage>[1],
|
||||
options: Partial<ComponentProps<typeof PresentationAssistantPane>> = {},
|
||||
) => {
|
||||
const onDraftChange = options.onDraftChange ?? (() => undefined);
|
||||
@@ -22,7 +22,7 @@ const renderPane = (
|
||||
return render(
|
||||
<PresentationAssistantPane
|
||||
phase={phase}
|
||||
message={projectScene9Message(initialScene9MessageState, phase)}
|
||||
message={projectPreparedLifecycleMessage(initialPreparedLifecycleMessageState, phase)}
|
||||
submittedOverrides={{}}
|
||||
runRequested={null}
|
||||
onDraftChange={onDraftChange}
|
||||
@@ -69,7 +69,7 @@ describe("PresentationAssistantPane", () => {
|
||||
|
||||
const input = screen.getByRole("textbox", { name: /message to authoring assistant/i });
|
||||
expect(input).toHaveValue("");
|
||||
expect(input).toHaveAttribute("placeholder", SCENE9_PHASE_PLACEHOLDERS[phase]);
|
||||
expect(input).toHaveAttribute("placeholder", PREPARED_LIFECYCLE_PHASE_PLACEHOLDERS[phase]);
|
||||
expect(screen.getByRole("button", { name: /send message/i })).toBeDisabled();
|
||||
}
|
||||
});
|
||||
@@ -80,7 +80,7 @@ describe("PresentationAssistantPane", () => {
|
||||
renderPane(phase);
|
||||
|
||||
expect(screen.getByRole("textbox", { name: /message to authoring assistant/i }))
|
||||
.toHaveValue(SCENE9_PHASE_PROMPTS[phase]);
|
||||
.toHaveValue(PREPARED_LIFECYCLE_PHASE_PROMPTS[phase]);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -4,34 +4,37 @@ import { Textarea } from "../../components/ui/textarea.js";
|
||||
import { AuthoringConversation } from "./AuthoringConversation.js";
|
||||
import type { AuthoringPhaseId } from "./authoring-recording.js";
|
||||
import type {
|
||||
Scene9MessageProjection,
|
||||
Scene9SubmittedOverrides,
|
||||
} from "./scene9-message-state.js";
|
||||
PreparedLifecycleMessageProjection,
|
||||
PreparedLifecycleSubmittedOverrides,
|
||||
} from "./prepared-lifecycle-message-state.js";
|
||||
import type { PreparedLifecycleStepId } from "./authoring-projection.js";
|
||||
import { PREPARED_COMPOSER_HELP, usePreparedComposerSubmit } from "./usePreparedComposerSubmit.js";
|
||||
|
||||
export type PresentationAssistantPaneProps = {
|
||||
readonly phase: AuthoringPhaseId;
|
||||
readonly phase: PreparedLifecycleStepId | AuthoringPhaseId;
|
||||
readonly visualRole?: "support";
|
||||
readonly message: Scene9MessageProjection;
|
||||
readonly submittedOverrides: Scene9SubmittedOverrides;
|
||||
readonly message: PreparedLifecycleMessageProjection;
|
||||
readonly submittedOverrides: PreparedLifecycleSubmittedOverrides;
|
||||
readonly runRequested: string | null;
|
||||
readonly onDraftChange: (draft: string) => void;
|
||||
readonly onSubmit: (message: string) => void;
|
||||
};
|
||||
|
||||
const phaseLabels: Readonly<Record<AuthoringPhaseId, string>> = {
|
||||
const phaseLabels: Readonly<Record<PreparedLifecycleStepId | AuthoringPhaseId, string>> = {
|
||||
discover: "Discover",
|
||||
draft: "Draft",
|
||||
diagnose: "Diagnose",
|
||||
repair: "Repair",
|
||||
validate: "Validate",
|
||||
artifact: "Artifact",
|
||||
deployment: "Deployment",
|
||||
};
|
||||
|
||||
/**
|
||||
* Stable Scene 9 boundary for the prepared assistant surface.
|
||||
* Stable prepared lifecycle boundary for the prepared assistant surface.
|
||||
*
|
||||
* The pane owns only the transient composer buffer. Submitted text is handed
|
||||
* back to the Scene 9 controller so the replay can project it later.
|
||||
* back to the lifecycle controller so the replay can project it later.
|
||||
*/
|
||||
export const PresentationAssistantPane = ({
|
||||
phase,
|
||||
@@ -83,22 +86,22 @@ export const PresentationAssistantPane = ({
|
||||
/>
|
||||
</div>
|
||||
<form className="presentation-assistant-pane__composer" onSubmit={submit}>
|
||||
<label htmlFor="scene9-authoring-message">Message to authoring assistant</label>
|
||||
<label htmlFor="prepared-lifecycle-authoring-message">Message to authoring assistant</label>
|
||||
<Textarea
|
||||
id="scene9-authoring-message"
|
||||
id="prepared-lifecycle-authoring-message"
|
||||
value={draft}
|
||||
placeholder={message.placeholder}
|
||||
disabled={runRequested !== null}
|
||||
onChange={(event) => updateDraft(event.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
aria-describedby="scene9-authoring-message-help"
|
||||
aria-describedby="prepared-lifecycle-authoring-message-help"
|
||||
/>
|
||||
<div className="presentation-assistant-pane__composer-actions">
|
||||
<Button type="submit" disabled={!canSubmit}>
|
||||
Send message
|
||||
</Button>
|
||||
</div>
|
||||
<p id="scene9-authoring-message-help" className="presentation-assistant-pane__composer-help">
|
||||
<p id="prepared-lifecycle-authoring-message-help" className="presentation-assistant-pane__composer-help">
|
||||
{PREPARED_COMPOSER_HELP}
|
||||
</p>
|
||||
{runRequested !== null ? (
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { projectPreparedAuthoring } from "./authoring-recording.js";
|
||||
import { projectPreparedAuthoringPhase } from "./authoring-projection.js";
|
||||
import {
|
||||
projectPreparedAuthoringPhase,
|
||||
projectPreparedLifecycleStep,
|
||||
recordingPhaseForStep,
|
||||
} from "./authoring-projection.js";
|
||||
|
||||
describe("projectPreparedAuthoringPhase", () => {
|
||||
it("projects the discover phase with sources, capabilities, schema", () => {
|
||||
@@ -64,4 +68,37 @@ describe("projectPreparedAuthoringPhase", () => {
|
||||
expect(["success", "diagnostic"]).toContain(cmd.result);
|
||||
}
|
||||
});
|
||||
|
||||
it("splits one recorded validate phase into diagnosis and repair presentation steps", () => {
|
||||
const diagnose = projectPreparedLifecycleStep("diagnose");
|
||||
const repair = projectPreparedLifecycleStep("repair");
|
||||
|
||||
expect(diagnose.recordingPhase).toBe("validate");
|
||||
expect(repair.recordingPhase).toBe("validate");
|
||||
expect(diagnose.focus).toBe("diagnose");
|
||||
expect(repair.focus).toBe("repair");
|
||||
expect(diagnose.primaryCommand.title).toBe("workflow.draft_workspaces.validate");
|
||||
expect(repair.primaryCommand.title).toBe("workflow.draft_workspaces.set_step_output_map");
|
||||
});
|
||||
|
||||
it("projects six presentation steps from five recorded phases", () => {
|
||||
const steps = [
|
||||
"discover",
|
||||
"draft",
|
||||
"diagnose",
|
||||
"repair",
|
||||
"artifact",
|
||||
"deployment",
|
||||
] as const;
|
||||
|
||||
expect(steps.map(recordingPhaseForStep)).toEqual([
|
||||
"discover",
|
||||
"draft",
|
||||
"validate",
|
||||
"validate",
|
||||
"artifact",
|
||||
"deployment",
|
||||
]);
|
||||
expect(steps.map((step) => projectPreparedLifecycleStep(step).step)).toEqual(steps);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
import { projectPreparedAuthoring, type AuthoringPhaseId, type PreparedAuthoringCommand } from "./authoring-recording.js";
|
||||
|
||||
export type PreparedLifecycleStepId =
|
||||
| "discover"
|
||||
| "draft"
|
||||
| "diagnose"
|
||||
| "repair"
|
||||
| "artifact"
|
||||
| "deployment";
|
||||
|
||||
export type AuthoringPhaseProjection = {
|
||||
readonly phase: AuthoringPhaseId;
|
||||
readonly beatId: string;
|
||||
@@ -10,6 +18,13 @@ export type AuthoringPhaseProjection = {
|
||||
readonly visual: AuthoringPhaseVisualModel;
|
||||
};
|
||||
|
||||
export type PreparedLifecycleStepProjection = AuthoringPhaseProjection & {
|
||||
readonly step: PreparedLifecycleStepId;
|
||||
readonly recordingPhase: AuthoringPhaseId;
|
||||
readonly focus: "full" | "diagnose" | "repair";
|
||||
readonly primaryCommand: PreparedAuthoringCommand;
|
||||
};
|
||||
|
||||
export type AuthoringPhaseVisualModel =
|
||||
| {
|
||||
readonly kind: "inventory";
|
||||
@@ -111,3 +126,34 @@ export const projectPreparedAuthoringPhase = (
|
||||
visual: visualForPhase(found.phase),
|
||||
};
|
||||
};
|
||||
|
||||
export const recordingPhaseForStep = (
|
||||
step: PreparedLifecycleStepId | AuthoringPhaseId,
|
||||
): AuthoringPhaseId => {
|
||||
if (step === "diagnose" || step === "repair") return "validate";
|
||||
return step;
|
||||
};
|
||||
|
||||
export const projectPreparedLifecycleStep = (
|
||||
step: PreparedLifecycleStepId,
|
||||
): PreparedLifecycleStepProjection => {
|
||||
const recordingPhase = recordingPhaseForStep(step);
|
||||
const phase = projectPreparedAuthoringPhase(recordingPhase);
|
||||
// Diagnose and repair are presentation choreography over one factual
|
||||
// recording phase; they select distinct evidence without duplicating it.
|
||||
const commandIndex = step === "repair" ? 1 : 0;
|
||||
const primaryCommand = phase.commands[commandIndex];
|
||||
if (primaryCommand === undefined) {
|
||||
throw new Error(
|
||||
`recorded phase "${recordingPhase}" has no command at index ${commandIndex} for presentation step "${step}"`,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
...phase,
|
||||
step,
|
||||
recordingPhase,
|
||||
focus: step === "diagnose" || step === "repair" ? step : "full",
|
||||
primaryCommand,
|
||||
};
|
||||
};
|
||||
|
||||
+32
-32
@@ -1,15 +1,15 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
SCENE9_PHASE_PROMPTS,
|
||||
initialScene9MessageState,
|
||||
projectScene9Message,
|
||||
projectScene9SubmittedOverrides,
|
||||
scene9MessageReducer,
|
||||
} from "./scene9-message-state.js";
|
||||
PREPARED_LIFECYCLE_PHASE_PROMPTS,
|
||||
initialPreparedLifecycleMessageState,
|
||||
projectPreparedLifecycleMessage,
|
||||
projectPreparedLifecycleSubmittedOverrides,
|
||||
preparedLifecycleMessageReducer,
|
||||
} from "./prepared-lifecycle-message-state.js";
|
||||
|
||||
describe("scene 9 staged message state", () => {
|
||||
describe("prepared lifecycle staged message state", () => {
|
||||
it("defines the exact prompt for every phase", () => {
|
||||
expect(SCENE9_PHASE_PROMPTS).toEqual({
|
||||
expect(PREPARED_LIFECYCLE_PHASE_PROMPTS).toEqual({
|
||||
discover: "",
|
||||
draft: "Is the draft valid? Can you check and fix any issues?",
|
||||
validate: "",
|
||||
@@ -20,12 +20,12 @@ describe("scene 9 staged message state", () => {
|
||||
});
|
||||
|
||||
it("projects empty phases with a useful placeholder", () => {
|
||||
expect(projectScene9Message(initialScene9MessageState, "discover")).toMatchObject({
|
||||
expect(projectPreparedLifecycleMessage(initialPreparedLifecycleMessageState, "discover")).toMatchObject({
|
||||
draft: "",
|
||||
prefill: "",
|
||||
placeholder: expect.any(String),
|
||||
});
|
||||
expect(projectScene9Message(initialScene9MessageState, "validate")).toMatchObject({
|
||||
expect(projectPreparedLifecycleMessage(initialPreparedLifecycleMessageState, "diagnose")).toMatchObject({
|
||||
draft: "",
|
||||
prefill: "",
|
||||
placeholder: expect.any(String),
|
||||
@@ -33,23 +33,23 @@ describe("scene 9 staged message state", () => {
|
||||
});
|
||||
|
||||
it("projects the exact phase prefill without changing the draft", () => {
|
||||
expect(projectScene9Message(initialScene9MessageState, "draft")).toMatchObject({
|
||||
expect(projectPreparedLifecycleMessage(initialPreparedLifecycleMessageState, "draft")).toMatchObject({
|
||||
draft: "",
|
||||
prefill: SCENE9_PHASE_PROMPTS.draft,
|
||||
prefill: PREPARED_LIFECYCLE_PHASE_PROMPTS.draft,
|
||||
});
|
||||
expect(projectScene9Message(initialScene9MessageState, "artifact")).toMatchObject({
|
||||
expect(projectPreparedLifecycleMessage(initialPreparedLifecycleMessageState, "artifact")).toMatchObject({
|
||||
draft: "",
|
||||
prefill: SCENE9_PHASE_PROMPTS.artifact,
|
||||
prefill: PREPARED_LIFECYCLE_PHASE_PROMPTS.artifact,
|
||||
});
|
||||
});
|
||||
|
||||
it("preserves edited draft text exactly when submitting draft", () => {
|
||||
const edited = scene9MessageReducer(initialScene9MessageState, {
|
||||
const edited = preparedLifecycleMessageReducer(initialPreparedLifecycleMessageState, {
|
||||
type: "draft_edited",
|
||||
draft: " Check only the report binding. ",
|
||||
});
|
||||
|
||||
expect(scene9MessageReducer(edited, { type: "draft_submitted" })).toEqual({
|
||||
expect(preparedLifecycleMessageReducer(edited, { type: "draft_submitted" })).toEqual({
|
||||
draft: edited.draft,
|
||||
submittedOverrides: { validate: edited.draft },
|
||||
runRequested: null,
|
||||
@@ -57,12 +57,12 @@ describe("scene 9 staged message state", () => {
|
||||
});
|
||||
|
||||
it("stores artifact submissions under the deployment destination", () => {
|
||||
const state = scene9MessageReducer(initialScene9MessageState, {
|
||||
const state = preparedLifecycleMessageReducer(initialPreparedLifecycleMessageState, {
|
||||
type: "draft_edited",
|
||||
draft: "Save this edited deployment request",
|
||||
});
|
||||
|
||||
expect(scene9MessageReducer(state, { type: "artifact_submitted" })).toEqual({
|
||||
expect(preparedLifecycleMessageReducer(state, { type: "artifact_submitted" })).toEqual({
|
||||
draft: state.draft,
|
||||
submittedOverrides: { deployment: state.draft },
|
||||
runRequested: null,
|
||||
@@ -70,12 +70,12 @@ describe("scene 9 staged message state", () => {
|
||||
});
|
||||
|
||||
it("stores discover submissions under the discover destination", () => {
|
||||
const state = scene9MessageReducer(initialScene9MessageState, {
|
||||
const state = preparedLifecycleMessageReducer(initialPreparedLifecycleMessageState, {
|
||||
type: "draft_edited",
|
||||
draft: "Inspect the report source first.",
|
||||
});
|
||||
|
||||
expect(scene9MessageReducer(state, { type: "discover_submitted" })).toEqual({
|
||||
expect(preparedLifecycleMessageReducer(state, { type: "discover_submitted" })).toEqual({
|
||||
draft: state.draft,
|
||||
submittedOverrides: { discover: state.draft },
|
||||
runRequested: null,
|
||||
@@ -83,43 +83,43 @@ describe("scene 9 staged message state", () => {
|
||||
});
|
||||
|
||||
it("ignores blank submits and keeps duplicate submits idempotent", () => {
|
||||
const blank = { ...initialScene9MessageState, draft: " \n\t" };
|
||||
expect(scene9MessageReducer(blank, { type: "draft_submitted" })).toBe(blank);
|
||||
expect(scene9MessageReducer(blank, { type: "artifact_submitted" })).toBe(blank);
|
||||
expect(scene9MessageReducer(blank, { type: "run_requested" })).toBe(blank);
|
||||
const blank = { ...initialPreparedLifecycleMessageState, draft: " \n\t" };
|
||||
expect(preparedLifecycleMessageReducer(blank, { type: "draft_submitted" })).toBe(blank);
|
||||
expect(preparedLifecycleMessageReducer(blank, { type: "artifact_submitted" })).toBe(blank);
|
||||
expect(preparedLifecycleMessageReducer(blank, { type: "run_requested" })).toBe(blank);
|
||||
|
||||
const submitted = scene9MessageReducer(
|
||||
scene9MessageReducer(initialScene9MessageState, {
|
||||
const submitted = preparedLifecycleMessageReducer(
|
||||
preparedLifecycleMessageReducer(initialPreparedLifecycleMessageState, {
|
||||
type: "draft_edited",
|
||||
draft: "A draft override",
|
||||
}),
|
||||
{ type: "draft_submitted" },
|
||||
);
|
||||
expect(scene9MessageReducer(submitted, { type: "draft_submitted" })).toBe(submitted);
|
||||
expect(preparedLifecycleMessageReducer(submitted, { type: "draft_submitted" })).toBe(submitted);
|
||||
});
|
||||
|
||||
it("records the final run request without claiming execution", () => {
|
||||
const state = scene9MessageReducer(initialScene9MessageState, {
|
||||
const state = preparedLifecycleMessageReducer(initialPreparedLifecycleMessageState, {
|
||||
type: "draft_edited",
|
||||
draft: "Run this deployment",
|
||||
});
|
||||
const requested = scene9MessageReducer(state, { type: "run_requested" });
|
||||
const requested = preparedLifecycleMessageReducer(state, { type: "run_requested" });
|
||||
|
||||
expect(requested.runRequested).toBe("Run this deployment");
|
||||
expect(requested.submittedOverrides).toEqual({});
|
||||
expect(scene9MessageReducer(requested, { type: "run_requested" })).toBe(requested);
|
||||
expect(preparedLifecycleMessageReducer(requested, { type: "run_requested" })).toBe(requested);
|
||||
});
|
||||
|
||||
it("projects only destination-phase overrides for the prepared thread", () => {
|
||||
const state = {
|
||||
...initialScene9MessageState,
|
||||
...initialPreparedLifecycleMessageState,
|
||||
submittedOverrides: {
|
||||
validate: "Edited validation request",
|
||||
deployment: "Edited deployment request",
|
||||
},
|
||||
};
|
||||
|
||||
expect(projectScene9SubmittedOverrides(state)).toEqual({
|
||||
expect(projectPreparedLifecycleSubmittedOverrides(state)).toEqual({
|
||||
validate: "Edited validation request",
|
||||
deployment: "Edited deployment request",
|
||||
});
|
||||
+36
-29
@@ -1,9 +1,13 @@
|
||||
import type { AuthoringPhaseId } from "./authoring-recording.js";
|
||||
import {
|
||||
recordingPhaseForStep,
|
||||
type PreparedLifecycleStepId,
|
||||
} from "./authoring-projection.js";
|
||||
|
||||
export type Scene9MessagePhase = AuthoringPhaseId;
|
||||
export type Scene9DestinationPhase = "discover" | "validate" | "deployment";
|
||||
export type PreparedLifecycleMessagePhase = PreparedLifecycleStepId | AuthoringPhaseId;
|
||||
export type PreparedLifecycleDestinationPhase = "discover" | "validate" | "deployment";
|
||||
|
||||
export const SCENE9_PHASE_PROMPTS: Readonly<Record<Scene9MessagePhase, string>> = {
|
||||
export const PREPARED_LIFECYCLE_PHASE_PROMPTS: Readonly<Record<AuthoringPhaseId, string>> = {
|
||||
discover: "",
|
||||
draft: "Is the draft valid? Can you check and fix any issues?",
|
||||
validate: "",
|
||||
@@ -12,7 +16,7 @@ export const SCENE9_PHASE_PROMPTS: Readonly<Record<Scene9MessagePhase, string>>
|
||||
"The deployment lda_report_case_study.default is saved and valid. Shall we run it now?",
|
||||
};
|
||||
|
||||
export const SCENE9_PHASE_PLACEHOLDERS: Readonly<Record<Scene9MessagePhase, string>> = {
|
||||
export const PREPARED_LIFECYCLE_PHASE_PLACEHOLDERS: Readonly<Record<AuthoringPhaseId, string>> = {
|
||||
discover: "Ask about the workflow authoring process.",
|
||||
draft: "Review the prepared draft.",
|
||||
validate: "Ask about the draft validation results.",
|
||||
@@ -20,23 +24,23 @@ export const SCENE9_PHASE_PLACEHOLDERS: Readonly<Record<Scene9MessagePhase, stri
|
||||
deployment: "Ask whether the saved deployment should run.",
|
||||
};
|
||||
|
||||
export type Scene9SubmittedOverrides = Readonly<
|
||||
Partial<Record<Scene9DestinationPhase, string>>
|
||||
export type PreparedLifecycleSubmittedOverrides = Readonly<
|
||||
Partial<Record<PreparedLifecycleDestinationPhase, string>>
|
||||
>;
|
||||
|
||||
export type Scene9MessageState = {
|
||||
export type PreparedLifecycleMessageState = {
|
||||
readonly draft: string;
|
||||
readonly submittedOverrides: Scene9SubmittedOverrides;
|
||||
readonly submittedOverrides: PreparedLifecycleSubmittedOverrides;
|
||||
readonly runRequested: string | null;
|
||||
};
|
||||
|
||||
export const initialScene9MessageState: Scene9MessageState = {
|
||||
export const initialPreparedLifecycleMessageState: PreparedLifecycleMessageState = {
|
||||
draft: "",
|
||||
submittedOverrides: {},
|
||||
runRequested: null,
|
||||
};
|
||||
|
||||
export type Scene9MessageAction =
|
||||
export type PreparedLifecycleMessageAction =
|
||||
| { readonly type: "draft_edited"; readonly draft: string }
|
||||
| { readonly type: "discover_submitted" }
|
||||
| { readonly type: "draft_submitted" }
|
||||
@@ -46,9 +50,9 @@ export type Scene9MessageAction =
|
||||
const hasText = (text: string): boolean => text.trim().length > 0;
|
||||
|
||||
const submitOverride = (
|
||||
state: Scene9MessageState,
|
||||
destination: Scene9DestinationPhase,
|
||||
): Scene9MessageState => {
|
||||
state: PreparedLifecycleMessageState,
|
||||
destination: PreparedLifecycleDestinationPhase,
|
||||
): PreparedLifecycleMessageState => {
|
||||
if (!hasText(state.draft) || state.submittedOverrides[destination] !== undefined) return state;
|
||||
|
||||
return {
|
||||
@@ -60,10 +64,10 @@ const submitOverride = (
|
||||
};
|
||||
};
|
||||
|
||||
export const scene9MessageReducer = (
|
||||
state: Scene9MessageState,
|
||||
action: Scene9MessageAction,
|
||||
): Scene9MessageState => {
|
||||
export const preparedLifecycleMessageReducer = (
|
||||
state: PreparedLifecycleMessageState,
|
||||
action: PreparedLifecycleMessageAction,
|
||||
): PreparedLifecycleMessageState => {
|
||||
switch (action.type) {
|
||||
case "draft_edited":
|
||||
return state.runRequested === null ? { ...state, draft: action.draft } : state;
|
||||
@@ -80,22 +84,25 @@ export const scene9MessageReducer = (
|
||||
}
|
||||
};
|
||||
|
||||
export type Scene9MessageProjection = {
|
||||
export type PreparedLifecycleMessageProjection = {
|
||||
readonly draft: string;
|
||||
readonly prefill: string;
|
||||
readonly placeholder: string;
|
||||
};
|
||||
|
||||
export const projectScene9Message = (
|
||||
state: Scene9MessageState,
|
||||
phase: Scene9MessagePhase,
|
||||
): Scene9MessageProjection => ({
|
||||
draft: state.draft,
|
||||
prefill: SCENE9_PHASE_PROMPTS[phase],
|
||||
placeholder: SCENE9_PHASE_PLACEHOLDERS[phase],
|
||||
});
|
||||
export const projectPreparedLifecycleMessage = (
|
||||
state: PreparedLifecycleMessageState,
|
||||
step: PreparedLifecycleMessagePhase,
|
||||
): PreparedLifecycleMessageProjection => {
|
||||
const phase = recordingPhaseForStep(step);
|
||||
return {
|
||||
draft: state.draft,
|
||||
prefill: PREPARED_LIFECYCLE_PHASE_PROMPTS[phase],
|
||||
placeholder: PREPARED_LIFECYCLE_PHASE_PLACEHOLDERS[phase],
|
||||
};
|
||||
};
|
||||
|
||||
/** Returns the transcript-facing request overrides by destination phase. */
|
||||
export const projectScene9SubmittedOverrides = (
|
||||
state: Scene9MessageState,
|
||||
): Scene9SubmittedOverrides => state.submittedOverrides;
|
||||
export const projectPreparedLifecycleSubmittedOverrides = (
|
||||
state: PreparedLifecycleMessageState,
|
||||
): PreparedLifecycleSubmittedOverrides => state.submittedOverrides;
|
||||
Reference in New Issue
Block a user