feat: add scene 9 staged message surface
This commit is contained in:
@@ -4,12 +4,14 @@ import {
|
|||||||
projectPreparedAuthoringThread,
|
projectPreparedAuthoringThread,
|
||||||
type AuthoringPhaseId,
|
type AuthoringPhaseId,
|
||||||
} from "./authoring-recording.js";
|
} from "./authoring-recording.js";
|
||||||
|
import type { Scene9SubmittedOverrides } from "./scene9-message-state.js";
|
||||||
|
|
||||||
type AuthoringConversationProps = {
|
type AuthoringConversationProps = {
|
||||||
readonly throughPhase: AuthoringPhaseId;
|
readonly throughPhase: AuthoringPhaseId;
|
||||||
readonly activePhase: AuthoringPhaseId;
|
readonly activePhase: AuthoringPhaseId;
|
||||||
readonly surface: "stage" | "dock";
|
readonly surface: "stage" | "dock";
|
||||||
readonly requestOverride?: string | undefined;
|
readonly requestOverride?: string | undefined;
|
||||||
|
readonly requestOverrides?: Scene9SubmittedOverrides | undefined;
|
||||||
readonly scrollMode?: "active" | "start" | undefined;
|
readonly scrollMode?: "active" | "start" | undefined;
|
||||||
readonly runAction?: { readonly label: string; readonly disabled: boolean; readonly run: () => void } | undefined;
|
readonly runAction?: { readonly label: string; readonly disabled: boolean; readonly run: () => void } | undefined;
|
||||||
};
|
};
|
||||||
@@ -20,13 +22,14 @@ export const AuthoringConversation = ({
|
|||||||
activePhase,
|
activePhase,
|
||||||
surface,
|
surface,
|
||||||
requestOverride,
|
requestOverride,
|
||||||
|
requestOverrides,
|
||||||
scrollMode,
|
scrollMode,
|
||||||
runAction,
|
runAction,
|
||||||
}: AuthoringConversationProps) => (
|
}: AuthoringConversationProps) => (
|
||||||
<AssistantOperatorThread
|
<AssistantOperatorThread
|
||||||
mode={surface === "stage" ? "full" : "dock"}
|
mode={surface === "stage" ? "full" : "dock"}
|
||||||
surface={surface}
|
surface={surface}
|
||||||
messages={projectPreparedAuthoringThread(throughPhase, requestOverride)}
|
messages={projectPreparedAuthoringThread(throughPhase, requestOverride, requestOverrides)}
|
||||||
activeToolGroupId={authoringToolGroupId(activePhase)}
|
activeToolGroupId={authoringToolGroupId(activePhase)}
|
||||||
scrollMode={scrollMode}
|
scrollMode={scrollMode}
|
||||||
ariaLabel="prepared authoring conversation"
|
ariaLabel="prepared authoring conversation"
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
|
import { useReducer } from "react";
|
||||||
import { projectPreparedAuthoringPhase } from "./authoring-projection.js";
|
import { projectPreparedAuthoringPhase } from "./authoring-projection.js";
|
||||||
import { AuthoringPhaseVisual } from "./AuthoringPhaseVisual.js";
|
import { AuthoringPhaseVisual } from "./AuthoringPhaseVisual.js";
|
||||||
import { PresentationAssistantPane } from "./PresentationAssistantPane.js";
|
import { PresentationAssistantPane } from "./PresentationAssistantPane.js";
|
||||||
import type { AuthoringPhaseId } from "./authoring-recording.js";
|
import type { AuthoringPhaseId } from "./authoring-recording.js";
|
||||||
|
import {
|
||||||
|
initialScene9MessageState,
|
||||||
|
projectScene9Message,
|
||||||
|
projectScene9SubmittedOverrides,
|
||||||
|
scene9MessageReducer,
|
||||||
|
} from "./scene9-message-state.js";
|
||||||
import type { SceneBeatDefinition, SceneDefinition } from "../storyboard.js";
|
import type { SceneBeatDefinition, SceneDefinition } from "../storyboard.js";
|
||||||
import { StageCaption } from "../StageCaption.js";
|
import { StageCaption } from "../StageCaption.js";
|
||||||
|
|
||||||
@@ -25,6 +32,10 @@ const phases: readonly { readonly id: AuthoringPhaseId; readonly label: string }
|
|||||||
* projection sourced from the prepared authoring recording.
|
* projection sourced from the prepared authoring recording.
|
||||||
*/
|
*/
|
||||||
export const PreparedAuthoringLifecycleScene = ({ scene, beat }: PreparedAuthoringLifecycleSceneProps) => {
|
export const PreparedAuthoringLifecycleScene = ({ scene, beat }: PreparedAuthoringLifecycleSceneProps) => {
|
||||||
|
const [messageState, dispatch] = useReducer(
|
||||||
|
scene9MessageReducer,
|
||||||
|
initialScene9MessageState,
|
||||||
|
);
|
||||||
// Storyboard beats normally match these IDs. Discovery is a safe projection
|
// Storyboard beats normally match these IDs. Discovery is a safe projection
|
||||||
// if a future beat reaches this scene before its authoring mapping is added.
|
// if a future beat reaches this scene before its authoring mapping is added.
|
||||||
const beatId = phases.find((phase) => phase.id === beat.id)?.id ?? "discover";
|
const beatId = phases.find((phase) => phase.id === beat.id)?.id ?? "discover";
|
||||||
@@ -43,7 +54,18 @@ export const PreparedAuthoringLifecycleScene = ({ scene, beat }: PreparedAuthori
|
|||||||
data-support-surface="prepared-chat"
|
data-support-surface="prepared-chat"
|
||||||
data-presentation-surface="editorial"
|
data-presentation-surface="editorial"
|
||||||
>
|
>
|
||||||
<PresentationAssistantPane phase={beatId} />
|
<PresentationAssistantPane
|
||||||
|
phase={beatId}
|
||||||
|
message={projectScene9Message(messageState, beatId)}
|
||||||
|
submittedOverrides={projectScene9SubmittedOverrides(messageState)}
|
||||||
|
runRequested={messageState.runRequested}
|
||||||
|
onDraftChange={(draft) => dispatch({ type: "draft_edited", draft })}
|
||||||
|
onSubmit={() => {
|
||||||
|
if (beatId === "draft") dispatch({ type: "draft_submitted" });
|
||||||
|
if (beatId === "artifact") dispatch({ type: "artifact_submitted" });
|
||||||
|
if (beatId === "deployment") dispatch({ type: "run_requested" });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<div className="prepared-lifecycle-scene__presentation">
|
<div className="prepared-lifecycle-scene__presentation">
|
||||||
<ol className="prepared-lifecycle-scene__rail" aria-label="authoring phase rail">
|
<ol className="prepared-lifecycle-scene__rail" aria-label="authoring phase rail">
|
||||||
{phases.map((phase) => (
|
{phases.map((phase) => (
|
||||||
|
|||||||
@@ -1,12 +1,40 @@
|
|||||||
import { cleanup, render, screen } from "@testing-library/react";
|
import { cleanup, render, screen } from "@testing-library/react";
|
||||||
import { afterEach, describe, expect, it } from "vitest";
|
import userEvent from "@testing-library/user-event";
|
||||||
|
import type { ComponentProps } from "react";
|
||||||
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||||
import { PresentationAssistantPane } from "./PresentationAssistantPane.js";
|
import { PresentationAssistantPane } from "./PresentationAssistantPane.js";
|
||||||
|
import {
|
||||||
|
SCENE9_PHASE_PLACEHOLDERS,
|
||||||
|
SCENE9_PHASE_PROMPTS,
|
||||||
|
initialScene9MessageState,
|
||||||
|
projectScene9Message,
|
||||||
|
} from "./scene9-message-state.js";
|
||||||
|
|
||||||
afterEach(cleanup);
|
afterEach(cleanup);
|
||||||
|
|
||||||
|
const renderPane = (
|
||||||
|
phase: Parameters<typeof projectScene9Message>[1],
|
||||||
|
options: Partial<ComponentProps<typeof PresentationAssistantPane>> = {},
|
||||||
|
) => {
|
||||||
|
const onDraftChange = options.onDraftChange ?? (() => undefined);
|
||||||
|
const onSubmit = options.onSubmit ?? (() => undefined);
|
||||||
|
|
||||||
|
return render(
|
||||||
|
<PresentationAssistantPane
|
||||||
|
phase={phase}
|
||||||
|
message={projectScene9Message(initialScene9MessageState, phase)}
|
||||||
|
submittedOverrides={{}}
|
||||||
|
runRequested={null}
|
||||||
|
onDraftChange={onDraftChange}
|
||||||
|
onSubmit={onSubmit}
|
||||||
|
{...options}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
describe("PresentationAssistantPane", () => {
|
describe("PresentationAssistantPane", () => {
|
||||||
it("renders a persistent prepared replay surface for the current phase", () => {
|
it("renders a persistent prepared replay surface for the current phase", () => {
|
||||||
render(<PresentationAssistantPane phase="validate" />);
|
renderPane("validate");
|
||||||
|
|
||||||
expect(screen.getByRole("complementary", { name: /prepared authoring assistant/i })).toBeInTheDocument();
|
expect(screen.getByRole("complementary", { name: /prepared authoring assistant/i })).toBeInTheDocument();
|
||||||
expect(screen.getByRole("heading", { name: /authoring assistant/i })).toBeInTheDocument();
|
expect(screen.getByRole("heading", { name: /authoring assistant/i })).toBeInTheDocument();
|
||||||
@@ -15,7 +43,7 @@ describe("PresentationAssistantPane", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("keeps the active tool group synchronized with the phase", () => {
|
it("keeps the active tool group synchronized with the phase", () => {
|
||||||
render(<PresentationAssistantPane phase="artifact" />);
|
renderPane("artifact");
|
||||||
|
|
||||||
expect(screen.getByRole("button", { name: /artifact.*3 tool calls/i }))
|
expect(screen.getByRole("button", { name: /artifact.*3 tool calls/i }))
|
||||||
.toHaveAttribute("aria-expanded", "true");
|
.toHaveAttribute("aria-expanded", "true");
|
||||||
@@ -23,10 +51,87 @@ describe("PresentationAssistantPane", () => {
|
|||||||
.toHaveAttribute("aria-expanded", "false");
|
.toHaveAttribute("aria-expanded", "false");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not expose a live or run action", () => {
|
it("renders an empty, accessible message surface for empty phases", () => {
|
||||||
render(<PresentationAssistantPane phase="deployment" />);
|
for (const phase of ["discover", "validate"] as const) {
|
||||||
|
cleanup();
|
||||||
|
renderPane(phase);
|
||||||
|
|
||||||
expect(screen.queryByRole("button", { name: /run|send|execute/i })).not.toBeInTheDocument();
|
const input = screen.getByRole("textbox", { name: /message to authoring assistant/i });
|
||||||
|
expect(input).toHaveValue("");
|
||||||
|
expect(input).toHaveAttribute("placeholder", SCENE9_PHASE_PLACEHOLDERS[phase]);
|
||||||
|
expect(screen.getByRole("button", { name: /send message/i })).toBeDisabled();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("prefills every staged request with its exact prompt", () => {
|
||||||
|
for (const phase of ["draft", "artifact", "deployment"] as const) {
|
||||||
|
cleanup();
|
||||||
|
renderPane(phase);
|
||||||
|
|
||||||
|
expect(screen.getByRole("textbox", { name: /message to authoring assistant/i }))
|
||||||
|
.toHaveValue(SCENE9_PHASE_PROMPTS[phase]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("preserves edits until submit and submits the edited value", async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
const onSubmit = vi.fn();
|
||||||
|
renderPane("draft", { onSubmit });
|
||||||
|
|
||||||
|
const input = screen.getByRole("textbox", { name: /message to authoring assistant/i });
|
||||||
|
await user.clear(input);
|
||||||
|
await user.type(input, "Check only the report binding.");
|
||||||
|
expect(input).toHaveValue("Check only the report binding.");
|
||||||
|
|
||||||
|
await user.click(screen.getByRole("button", { name: /send message/i }));
|
||||||
|
expect(onSubmit).toHaveBeenCalledWith("Check only the report binding.");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses Shift+Enter for newlines and Enter to submit", async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
const onSubmit = vi.fn();
|
||||||
|
renderPane("artifact", { onSubmit });
|
||||||
|
const input = screen.getByRole("textbox", { name: /message to authoring assistant/i });
|
||||||
|
|
||||||
|
await user.clear(input);
|
||||||
|
await user.type(input, "first");
|
||||||
|
await user.keyboard("{Shift>}{Enter}{/Shift}");
|
||||||
|
await user.type(input, "second");
|
||||||
|
expect(input).toHaveValue("first\nsecond");
|
||||||
|
expect(onSubmit).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
await user.keyboard("{Enter}");
|
||||||
|
expect(onSubmit).toHaveBeenCalledWith("first\nsecond");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("ignores blank submissions and disables the terminal request after submission", async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
const onSubmit = vi.fn();
|
||||||
|
renderPane("deployment", { onSubmit });
|
||||||
|
const input = screen.getByRole("textbox", { name: /message to authoring assistant/i });
|
||||||
|
|
||||||
|
await user.clear(input);
|
||||||
|
expect(screen.getByRole("button", { name: /send message/i })).toBeDisabled();
|
||||||
|
await user.click(screen.getByRole("button", { name: /send message/i }));
|
||||||
|
expect(onSubmit).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
cleanup();
|
||||||
|
renderPane("deployment", { runRequested: "Run this deployment" });
|
||||||
|
expect(screen.getByRole("button", { name: /send message/i })).toBeDisabled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders submitted overrides while keeping the replay tools canonical", () => {
|
||||||
|
renderPane("validate", { submittedOverrides: { validate: "Edited validation request" } });
|
||||||
|
|
||||||
|
expect(screen.getByText("Edited validation request")).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole("button", { name: /workflow\.draft_workspaces\.validate/i }))
|
||||||
|
.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not expose a live or run action", () => {
|
||||||
|
renderPane("deployment");
|
||||||
|
|
||||||
|
expect(screen.queryByRole("button", { name: /run|execute/i })).not.toBeInTheDocument();
|
||||||
expect(screen.queryByText(/workflow\.runs\.start/i)).not.toBeInTheDocument();
|
expect(screen.queryByText(/workflow\.runs\.start/i)).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,8 +1,20 @@
|
|||||||
|
import { useEffect, useState, type FormEvent, type KeyboardEvent } from "react";
|
||||||
|
import { Button } from "../../components/ui/button.js";
|
||||||
|
import { Textarea } from "../../components/ui/textarea.js";
|
||||||
import { AuthoringConversation } from "./AuthoringConversation.js";
|
import { AuthoringConversation } from "./AuthoringConversation.js";
|
||||||
import type { AuthoringPhaseId } from "./authoring-recording.js";
|
import type { AuthoringPhaseId } from "./authoring-recording.js";
|
||||||
|
import type {
|
||||||
|
Scene9MessageProjection,
|
||||||
|
Scene9SubmittedOverrides,
|
||||||
|
} from "./scene9-message-state.js";
|
||||||
|
|
||||||
type PresentationAssistantPaneProps = {
|
export type PresentationAssistantPaneProps = {
|
||||||
readonly phase: AuthoringPhaseId;
|
readonly phase: AuthoringPhaseId;
|
||||||
|
readonly message: Scene9MessageProjection;
|
||||||
|
readonly submittedOverrides: Scene9SubmittedOverrides;
|
||||||
|
readonly runRequested: string | null;
|
||||||
|
readonly onDraftChange: (draft: string) => void;
|
||||||
|
readonly onSubmit: (message: string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const phaseLabels: Readonly<Record<AuthoringPhaseId, string>> = {
|
const phaseLabels: Readonly<Record<AuthoringPhaseId, string>> = {
|
||||||
@@ -16,30 +28,87 @@ const phaseLabels: Readonly<Record<AuthoringPhaseId, string>> = {
|
|||||||
/**
|
/**
|
||||||
* Stable Scene 9 boundary for the prepared assistant surface.
|
* Stable Scene 9 boundary for the prepared assistant surface.
|
||||||
*
|
*
|
||||||
* The pane deliberately passes no action handlers: its conversation is a
|
* The pane owns only the transient composer buffer. Submitted text is handed
|
||||||
* replay projection, not a live assistant runtime or authoring client.
|
* back to the Scene 9 controller so the replay can project it later.
|
||||||
*/
|
*/
|
||||||
export const PresentationAssistantPane = ({ phase }: PresentationAssistantPaneProps) => (
|
export const PresentationAssistantPane = ({
|
||||||
<aside
|
phase,
|
||||||
className="presentation-assistant-pane"
|
message,
|
||||||
aria-label="prepared authoring assistant"
|
submittedOverrides,
|
||||||
data-phase={phase}
|
runRequested,
|
||||||
data-surface="prepared-replay"
|
onDraftChange,
|
||||||
>
|
onSubmit,
|
||||||
<header className="presentation-assistant-pane__header">
|
}: PresentationAssistantPaneProps) => {
|
||||||
<p className="presentation-assistant-pane__eyebrow">Prepared workflow</p>
|
const [draft, setDraft] = useState(message.prefill);
|
||||||
<h2>Authoring assistant</h2>
|
|
||||||
<p>Current phase: {phaseLabels[phase]}</p>
|
useEffect(() => {
|
||||||
<p className="presentation-assistant-pane__disclosure">
|
setDraft(message.prefill);
|
||||||
Prepared replay only. No live actions or RPC calls.
|
}, [phase, message.prefill]);
|
||||||
</p>
|
|
||||||
</header>
|
const canSubmit = draft.trim().length > 0 && runRequested === null;
|
||||||
<div className="presentation-assistant-pane__conversation">
|
const updateDraft = (nextDraft: string) => {
|
||||||
<AuthoringConversation
|
setDraft(nextDraft);
|
||||||
throughPhase={phase}
|
onDraftChange(nextDraft);
|
||||||
activePhase={phase}
|
};
|
||||||
surface="stage"
|
const submit = (event?: FormEvent<HTMLFormElement>) => {
|
||||||
/>
|
event?.preventDefault();
|
||||||
</div>
|
if (canSubmit) onSubmit(draft);
|
||||||
</aside>
|
};
|
||||||
);
|
const handleKeyDown = (event: KeyboardEvent<HTMLTextAreaElement>) => {
|
||||||
|
if (event.key === "Enter" && !event.shiftKey) {
|
||||||
|
event.preventDefault();
|
||||||
|
submit();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<aside
|
||||||
|
className="presentation-assistant-pane"
|
||||||
|
aria-label="prepared authoring assistant"
|
||||||
|
data-phase={phase}
|
||||||
|
data-surface="prepared-replay"
|
||||||
|
>
|
||||||
|
<header className="presentation-assistant-pane__header">
|
||||||
|
<p className="presentation-assistant-pane__eyebrow">Prepared workflow</p>
|
||||||
|
<h2>Authoring assistant</h2>
|
||||||
|
<p>Current phase: {phaseLabels[phase]}</p>
|
||||||
|
<p className="presentation-assistant-pane__disclosure">
|
||||||
|
Prepared replay only. No live actions or RPC calls.
|
||||||
|
</p>
|
||||||
|
</header>
|
||||||
|
<div className="presentation-assistant-pane__conversation">
|
||||||
|
<AuthoringConversation
|
||||||
|
throughPhase={phase}
|
||||||
|
activePhase={phase}
|
||||||
|
surface="stage"
|
||||||
|
requestOverrides={submittedOverrides}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<form className="presentation-assistant-pane__composer" onSubmit={submit}>
|
||||||
|
<label htmlFor="scene9-authoring-message">Message to authoring assistant</label>
|
||||||
|
<Textarea
|
||||||
|
id="scene9-authoring-message"
|
||||||
|
value={draft}
|
||||||
|
placeholder={message.placeholder}
|
||||||
|
disabled={runRequested !== null}
|
||||||
|
onChange={(event) => updateDraft(event.target.value)}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
aria-describedby="scene9-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">
|
||||||
|
Shift+Enter adds a new line. This is a deterministic prepared replay, not a live model request.
|
||||||
|
</p>
|
||||||
|
{runRequested !== null ? (
|
||||||
|
<p role="status" className="presentation-assistant-pane__run-status">
|
||||||
|
Run request prepared for the next execution slice.
|
||||||
|
</p>
|
||||||
|
) : null}
|
||||||
|
</form>
|
||||||
|
</aside>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@@ -178,4 +178,31 @@ describe("projectPreparedAuthoringThread", () => {
|
|||||||
]);
|
]);
|
||||||
expect(overriddenTools).toEqual(canonicalTools);
|
expect(overriddenTools).toEqual(canonicalTools);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("projects staged overrides onto their phase user turns", () => {
|
||||||
|
const messages = projectPreparedAuthoringThread("validate", undefined, {
|
||||||
|
validate: "Edited validation request",
|
||||||
|
});
|
||||||
|
const validateUser = messages.find(
|
||||||
|
(message) => message.id === "authoring-validate-message-0",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(validateUser?.parts).toEqual([{ type: "text", text: "Edited validation request" }]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("projects both staged overrides without changing tool messages", () => {
|
||||||
|
const canonical = projectPreparedAuthoringThread("deployment");
|
||||||
|
const overridden = projectPreparedAuthoringThread("deployment", undefined, {
|
||||||
|
validate: "Edited validation request",
|
||||||
|
deployment: "Edited deployment request",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(overridden.find((message) => message.id === "authoring-validate-message-0")?.parts)
|
||||||
|
.toEqual([{ type: "text", text: "Edited validation request" }]);
|
||||||
|
expect(overridden.find((message) => message.id === "authoring-deployment-message-0")?.parts)
|
||||||
|
.toEqual([{ type: "text", text: "Edited deployment request" }]);
|
||||||
|
expect(overridden.filter((message) => message.id.endsWith("-tools"))).toEqual(
|
||||||
|
canonical.filter((message) => message.id.endsWith("-tools")),
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -281,19 +281,28 @@ export const authoringToolGroupId = (phase: AuthoringPhaseId): string =>
|
|||||||
export const projectPreparedAuthoringThread = (
|
export const projectPreparedAuthoringThread = (
|
||||||
throughPhase: AuthoringPhaseId = "deployment",
|
throughPhase: AuthoringPhaseId = "deployment",
|
||||||
requestOverride?: string,
|
requestOverride?: string,
|
||||||
|
requestOverrides?: Readonly<Partial<Record<"validate" | "deployment", string>>>,
|
||||||
): readonly AgentMessage[] => {
|
): readonly AgentMessage[] => {
|
||||||
const finalPhaseIndex = recording.findIndex(({ phase }) => phase === throughPhase);
|
const finalPhaseIndex = recording.findIndex(({ phase }) => phase === throughPhase);
|
||||||
if (finalPhaseIndex < 0) throw new Error(`unknown phase: ${throughPhase}`);
|
if (finalPhaseIndex < 0) throw new Error(`unknown phase: ${throughPhase}`);
|
||||||
|
|
||||||
let requestReplaced = false;
|
let requestReplaced = false;
|
||||||
return recording.slice(0, finalPhaseIndex + 1).flatMap((phase) => {
|
return recording.slice(0, finalPhaseIndex + 1).flatMap((phase) => {
|
||||||
|
const phaseOverride =
|
||||||
|
phase.phase === "validate"
|
||||||
|
? requestOverrides?.validate
|
||||||
|
: phase.phase === "deployment"
|
||||||
|
? requestOverrides?.deployment
|
||||||
|
: undefined;
|
||||||
const conversation = phase.conversation.map((turn, index) => {
|
const conversation = phase.conversation.map((turn, index) => {
|
||||||
const shouldReplaceRequest = requestOverride !== undefined && !requestReplaced && turn.role === "user";
|
const shouldReplaceRequest =
|
||||||
if (shouldReplaceRequest) requestReplaced = true;
|
turn.role === "user" &&
|
||||||
|
(phaseOverride !== undefined || (requestOverride !== undefined && !requestReplaced));
|
||||||
|
if (shouldReplaceRequest && phaseOverride === undefined) requestReplaced = true;
|
||||||
return agentTextMessage(
|
return agentTextMessage(
|
||||||
`authoring-${phase.phase}-message-${index}`,
|
`authoring-${phase.phase}-message-${index}`,
|
||||||
turn.role,
|
turn.role,
|
||||||
shouldReplaceRequest ? requestOverride : turn.text,
|
shouldReplaceRequest ? (phaseOverride ?? requestOverride ?? turn.text) : turn.text,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
const groupId = authoringToolGroupId(phase.phase);
|
const groupId = authoringToolGroupId(phase.phase);
|
||||||
|
|||||||
@@ -3308,6 +3308,49 @@
|
|||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.presentation-assistant-pane__composer {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
display: grid;
|
||||||
|
gap: 0.45rem;
|
||||||
|
padding: 0.7rem 0.9rem 0.85rem;
|
||||||
|
border-top: 1px solid var(--stage-line);
|
||||||
|
}
|
||||||
|
|
||||||
|
.presentation-assistant-pane__composer label {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 0.72rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.presentation-assistant-pane__composer textarea {
|
||||||
|
min-height: 4.5rem;
|
||||||
|
resize: vertical;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.presentation-assistant-pane__composer-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.presentation-assistant-pane__composer-actions button {
|
||||||
|
height: 2rem;
|
||||||
|
padding-inline: 0.7rem;
|
||||||
|
font-size: 0.72rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.presentation-assistant-pane__composer-help,
|
||||||
|
.presentation-assistant-pane__run-status {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 0.65rem;
|
||||||
|
line-height: 1.35;
|
||||||
|
}
|
||||||
|
|
||||||
|
.presentation-assistant-pane__run-status {
|
||||||
|
color: var(--accent-cyan);
|
||||||
|
}
|
||||||
|
|
||||||
.prepared-lifecycle-scene[data-presentation-surface="editorial"] {
|
.prepared-lifecycle-scene[data-presentation-surface="editorial"] {
|
||||||
--authoring-paper: var(--color-editorial-paper, oklch(0.975 0.012 82));
|
--authoring-paper: var(--color-editorial-paper, oklch(0.975 0.012 82));
|
||||||
--authoring-ink: var(--color-editorial-ink, oklch(0.19 0.015 65));
|
--authoring-ink: var(--color-editorial-ink, oklch(0.19 0.015 65));
|
||||||
|
|||||||
Reference in New Issue
Block a user