The live screen now opens wide beside the chat while the bot works on it, a file the bot presents as its result opens next to the conversation, and a question that scrolled away gets a "Needs you" pill back to it. Focus is derived from signals clients already have (pointer events, takeover requests, a new `present` flag on attach_file, pending asks) by shared core rules, so web and mobile agree and the behaviour stays deterministic. The pane only moves on focus edges, never replaces a pane the user opened, and learns per bot whether the screen should open on its own. Pane widths persist per device. Mobile shows a pill that opens the computer route. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C7VSRHHzyt2KwoCkUkZ2Gc
39 lines
1.3 KiB
TypeScript
39 lines
1.3 KiB
TypeScript
import { useLingui } from "@lingui/react/macro";
|
|
import { useEffect, useState } from "react";
|
|
import { type ArtifactTarget, fetchArtifactBytes } from "../lib/artifact-open";
|
|
|
|
export type ArtifactBytesState =
|
|
| { status: "loading" }
|
|
| { status: "ready"; bytes: Uint8Array }
|
|
| { status: "error"; message: string };
|
|
|
|
export function useArtifactBytes(target: ArtifactTarget, artifactId: string): ArtifactBytesState {
|
|
const { t } = useLingui();
|
|
const [state, setState] = useState<ArtifactBytesState>({ status: "loading" });
|
|
const targetBotId = "botId" in target ? target.botId : undefined;
|
|
const targetGroupId = "groupId" in target ? target.groupId : undefined;
|
|
|
|
useEffect(() => {
|
|
let cancelled = false;
|
|
setState({ status: "loading" });
|
|
const artifactTarget: ArtifactTarget =
|
|
targetBotId !== undefined ? { botId: targetBotId } : { groupId: targetGroupId! };
|
|
void fetchArtifactBytes(artifactTarget, artifactId)
|
|
.then((bytes) => {
|
|
if (!cancelled) setState({ status: "ready", bytes });
|
|
})
|
|
.catch((error) => {
|
|
if (cancelled) return;
|
|
setState({
|
|
status: "error",
|
|
message: error instanceof Error ? error.message : t`Could not load this file.`,
|
|
});
|
|
});
|
|
return () => {
|
|
cancelled = true;
|
|
};
|
|
}, [artifactId, targetBotId, targetGroupId, t]);
|
|
|
|
return state;
|
|
}
|