fix: keep scene 8 transcript visible
This commit is contained in:
@@ -10,6 +10,7 @@ type AuthoringConversationProps = {
|
|||||||
readonly activePhase: AuthoringPhaseId;
|
readonly activePhase: AuthoringPhaseId;
|
||||||
readonly surface: "stage" | "dock";
|
readonly surface: "stage" | "dock";
|
||||||
readonly requestOverride?: string | undefined;
|
readonly requestOverride?: string | 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;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -19,6 +20,7 @@ export const AuthoringConversation = ({
|
|||||||
activePhase,
|
activePhase,
|
||||||
surface,
|
surface,
|
||||||
requestOverride,
|
requestOverride,
|
||||||
|
scrollMode,
|
||||||
runAction,
|
runAction,
|
||||||
}: AuthoringConversationProps) => (
|
}: AuthoringConversationProps) => (
|
||||||
<AssistantOperatorThread
|
<AssistantOperatorThread
|
||||||
@@ -26,6 +28,7 @@ export const AuthoringConversation = ({
|
|||||||
surface={surface}
|
surface={surface}
|
||||||
messages={projectPreparedAuthoringThread(throughPhase, requestOverride)}
|
messages={projectPreparedAuthoringThread(throughPhase, requestOverride)}
|
||||||
activeToolGroupId={authoringToolGroupId(activePhase)}
|
activeToolGroupId={authoringToolGroupId(activePhase)}
|
||||||
|
scrollMode={scrollMode}
|
||||||
ariaLabel="prepared authoring conversation"
|
ariaLabel="prepared authoring conversation"
|
||||||
runAction={runAction}
|
runAction={runAction}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -45,6 +45,10 @@ describe("Scene8ChatEntry", () => {
|
|||||||
await user.click(screen.getByRole("button", { name: "Send" }));
|
await user.click(screen.getByRole("button", { name: "Send" }));
|
||||||
expect(screen.getByText(/let me inspect the available sources/i)).toBeInTheDocument();
|
expect(screen.getByText(/let me inspect the available sources/i)).toBeInTheDocument();
|
||||||
expect(screen.getByRole("button", { name: /discover.*4 tool calls/i })).toBeInTheDocument();
|
expect(screen.getByRole("button", { name: /discover.*4 tool calls/i })).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole("region", { name: "authoring chat entry" })).toHaveAttribute(
|
||||||
|
"data-entry-phase",
|
||||||
|
"submitted",
|
||||||
|
);
|
||||||
expect(screen.queryByRole("button", { name: /run prepared workflow/i })).not.toBeInTheDocument();
|
expect(screen.queryByRole("button", { name: /run prepared workflow/i })).not.toBeInTheDocument();
|
||||||
expect(screen.getByRole("button", { name: "Send" })).toBeDisabled();
|
expect(screen.getByRole("button", { name: "Send" })).toBeDisabled();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -24,7 +24,11 @@ export const Scene8ChatEntry = ({ state, dispatch }: Scene8ChatEntryProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="agent-handoff-scene__entry" aria-label="authoring chat entry">
|
<section
|
||||||
|
className="agent-handoff-scene__entry"
|
||||||
|
aria-label="authoring chat entry"
|
||||||
|
data-entry-phase={state.phase}
|
||||||
|
>
|
||||||
<div className="agent-handoff-scene__intro">
|
<div className="agent-handoff-scene__intro">
|
||||||
<span>Scene 8 · agent handoff</span>
|
<span>Scene 8 · agent handoff</span>
|
||||||
<h1>What should the workflow author prepare?</h1>
|
<h1>What should the workflow author prepare?</h1>
|
||||||
@@ -62,6 +66,7 @@ export const Scene8ChatEntry = ({ state, dispatch }: Scene8ChatEntryProps) => {
|
|||||||
throughPhase="discover"
|
throughPhase="discover"
|
||||||
activePhase="discover"
|
activePhase="discover"
|
||||||
surface="stage"
|
surface="stage"
|
||||||
|
scrollMode="start"
|
||||||
requestOverride={state.request}
|
requestOverride={state.request}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -179,6 +179,53 @@ describe("AssistantOperatorThread", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("can keep a first-stage group anchored at the start of the transcript", async () => {
|
||||||
|
const setScrollTop = vi.fn();
|
||||||
|
const originalDescriptor = Object.getOwnPropertyDescriptor(HTMLDivElement.prototype, "scrollTop");
|
||||||
|
const messages: ReadonlyArray<AgentMessage> = [
|
||||||
|
{
|
||||||
|
id: "assistant-start-scroll-text",
|
||||||
|
role: "assistant",
|
||||||
|
parts: [
|
||||||
|
{ type: "text", text: "The first authoring turn is visible." },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "authoring-discover-tools",
|
||||||
|
role: "assistant",
|
||||||
|
parts: [
|
||||||
|
{ type: "tool-call", call: { id: "authoring-discover-command-0", name: "workflow.sources.list", input: { phase: "discover" } } },
|
||||||
|
{ type: "tool-result", result: { callId: "authoring-discover-command-0", name: "workflow.sources.list", status: "success", output: {} } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
try {
|
||||||
|
Object.defineProperty(HTMLDivElement.prototype, "scrollTop", {
|
||||||
|
configurable: true,
|
||||||
|
get: () => 0,
|
||||||
|
set: setScrollTop,
|
||||||
|
});
|
||||||
|
render(
|
||||||
|
<AssistantOperatorThread
|
||||||
|
mode="full"
|
||||||
|
surface="stage"
|
||||||
|
messages={messages}
|
||||||
|
activeToolGroupId="authoring-discover"
|
||||||
|
scrollMode="start"
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
await waitFor(() => expect(setScrollTop).toHaveBeenCalledWith(0));
|
||||||
|
} finally {
|
||||||
|
if (originalDescriptor) {
|
||||||
|
Object.defineProperty(HTMLDivElement.prototype, "scrollTop", originalDescriptor);
|
||||||
|
} else {
|
||||||
|
delete (HTMLDivElement.prototype as { scrollTop?: number }).scrollTop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it("pairs a lone tool call with its result", () => {
|
it("pairs a lone tool call with its result", () => {
|
||||||
const messages: ReadonlyArray<AgentMessage> = [
|
const messages: ReadonlyArray<AgentMessage> = [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ type AssistantOperatorThreadProps = {
|
|||||||
readonly mode: "hidden" | "full" | "rail" | "dock";
|
readonly mode: "hidden" | "full" | "rail" | "dock";
|
||||||
readonly messages: ReadonlyArray<AgentMessage>;
|
readonly messages: ReadonlyArray<AgentMessage>;
|
||||||
readonly runAction?: { readonly label: string; readonly disabled: boolean; readonly run: () => void } | undefined;
|
readonly runAction?: { readonly label: string; readonly disabled: boolean; readonly run: () => void } | undefined;
|
||||||
|
readonly scrollMode?: "active" | "start" | undefined;
|
||||||
readonly submitApproval?: (() => void) | undefined;
|
readonly submitApproval?: (() => void) | undefined;
|
||||||
readonly requestRevision?: (() => void) | undefined;
|
readonly requestRevision?: (() => void) | undefined;
|
||||||
readonly ariaLabel?: string | undefined;
|
readonly ariaLabel?: string | undefined;
|
||||||
@@ -250,6 +251,7 @@ export const AssistantOperatorThread = ({
|
|||||||
mode,
|
mode,
|
||||||
messages,
|
messages,
|
||||||
runAction,
|
runAction,
|
||||||
|
scrollMode = "active",
|
||||||
submitApproval,
|
submitApproval,
|
||||||
requestRevision,
|
requestRevision,
|
||||||
ariaLabel = "operator conversation",
|
ariaLabel = "operator conversation",
|
||||||
@@ -282,13 +284,17 @@ export const AssistantOperatorThread = ({
|
|||||||
if (!viewport || !activeGroup) return;
|
if (!viewport || !activeGroup) return;
|
||||||
const bottomAlignedTop = activeGroup.offsetTop + activeGroup.offsetHeight - viewport.clientHeight;
|
const bottomAlignedTop = activeGroup.offsetTop + activeGroup.offsetHeight - viewport.clientHeight;
|
||||||
const dockCenteredTop = activeGroup.offsetTop - (viewport.clientHeight - activeGroup.offsetHeight) / 2;
|
const dockCenteredTop = activeGroup.offsetTop - (viewport.clientHeight - activeGroup.offsetHeight) / 2;
|
||||||
const requestedTop = surface === "dock" ? dockCenteredTop : bottomAlignedTop;
|
const requestedTop = scrollMode === "start"
|
||||||
|
? 0
|
||||||
|
: surface === "dock"
|
||||||
|
? dockCenteredTop
|
||||||
|
: bottomAlignedTop;
|
||||||
const top = Math.min(
|
const top = Math.min(
|
||||||
Math.max(0, requestedTop),
|
Math.max(0, requestedTop),
|
||||||
Math.max(0, viewport.scrollHeight - viewport.clientHeight),
|
Math.max(0, viewport.scrollHeight - viewport.clientHeight),
|
||||||
);
|
);
|
||||||
viewport.scrollTop = top;
|
viewport.scrollTop = top;
|
||||||
}, [activeToolGroupId, projected, surface]);
|
}, [activeToolGroupId, projected, scrollMode, surface]);
|
||||||
|
|
||||||
const setToolGroupOpen = useCallback((groupId: string, open: boolean) => {
|
const setToolGroupOpen = useCallback((groupId: string, open: boolean) => {
|
||||||
setToolGroupOverrides((current) => {
|
setToolGroupOverrides((current) => {
|
||||||
|
|||||||
@@ -3042,6 +3042,30 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.agent-handoff-scene__entry[data-entry-phase="submitted"] {
|
||||||
|
grid-template-rows: auto minmax(0, 1fr) auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-handoff-scene__entry[data-entry-phase="submitted"] .agent-handoff-scene__entry-thread {
|
||||||
|
grid-row: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-handoff-scene__entry[data-entry-phase="submitted"] .agent-handoff-scene__composer {
|
||||||
|
grid-row: 3;
|
||||||
|
gap: 0.4rem;
|
||||||
|
padding: 0.65rem 1rem;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-handoff-scene__entry[data-entry-phase="submitted"] .agent-handoff-scene__composer textarea {
|
||||||
|
min-height: 3.25rem;
|
||||||
|
max-height: 4.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-handoff-scene__entry[data-entry-phase="submitted"] .agent-handoff-scene__composer-help {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.agent-handoff-scene__intro {
|
.agent-handoff-scene__intro {
|
||||||
width: min(100%, 52rem);
|
width: min(100%, 52rem);
|
||||||
margin-inline: auto;
|
margin-inline: auto;
|
||||||
|
|||||||
Reference in New Issue
Block a user