evOo-local/apps/web/src/hooks/useArtifactBytes.ts
Claude 065b1322bb
feat: let the pane beside a conversation follow the bot's work
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
2026-10-03 18:30:39 +00:00

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;
}