Skip to content

Commit e93fda6

Browse files
committed
fix(web): surface workspace image load failures
1 parent b4ae338 commit e93fda6

2 files changed

Lines changed: 40 additions & 6 deletions

File tree

‎apps/web/src/assets/assetUrls.ts‎

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,38 @@ import { usePreparedConnection } from "~/state/session";
99

1010
export { resolveAssetUrl } from "@t3tools/client-runtime/state/assets";
1111

12-
export function useAssetUrl(environmentId: EnvironmentId, resource: AssetResource): string | null {
12+
export type AssetUrlState =
13+
| { readonly _tag: "Loading" }
14+
| { readonly _tag: "Failure" }
15+
| { readonly _tag: "Success"; readonly url: string };
16+
17+
export function useAssetUrlState(
18+
environmentId: EnvironmentId,
19+
resource: AssetResource,
20+
): AssetUrlState {
1321
const preparedConnection = usePreparedConnection(environmentId);
1422
const result = useAtomValue(
1523
assetEnvironment.createUrl({
1624
environmentId,
1725
input: { resource },
1826
}),
1927
);
28+
if (result._tag === "Failure") {
29+
return { _tag: "Failure" };
30+
}
2031
if (preparedConnection._tag === "None" || result._tag !== "Success") {
32+
return { _tag: "Loading" };
33+
}
34+
const url = resolveAssetUrl(preparedConnection.value.httpBaseUrl, result.value.relativeUrl);
35+
return url === null ? { _tag: "Failure" } : { _tag: "Success", url };
36+
}
37+
38+
export function useAssetUrl(environmentId: EnvironmentId, resource: AssetResource): string | null {
39+
const result = useAssetUrlState(environmentId, resource);
40+
if (result._tag !== "Success") {
2141
return null;
2242
}
23-
return resolveAssetUrl(preparedConnection.value.httpBaseUrl, result.value.relativeUrl);
43+
return result.url;
2444
}
2545

2646
export function useAssetUrls(

‎apps/web/src/components/files/FilePreviewPanel.tsx‎

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import * as Schema from "effect/Schema";
1717
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
1818

1919
import { isBrowserPreviewFile, openFileInPreview } from "~/browser/openFileInPreview";
20-
import { useAssetUrl } from "~/assets/assetUrls";
20+
import { useAssetUrlState } from "~/assets/assetUrls";
2121
import ChatMarkdown from "~/components/ChatMarkdown";
2222
import { OpenInPicker } from "~/components/chat/OpenInPicker";
2323
import { useClientSettings } from "~/hooks/useSettings";
@@ -121,15 +121,29 @@ function WorkspaceImagePreview(props: {
121121
readonly absolutePath: string;
122122
readonly alt: string;
123123
}) {
124-
const src = useAssetUrl(props.environmentId, {
124+
const assetUrl = useAssetUrlState(props.environmentId, {
125125
_tag: "workspace-file",
126126
threadId: props.threadRef.threadId,
127127
path: props.absolutePath,
128128
});
129+
const [failedUrl, setFailedUrl] = useState<string | null>(null);
129130

130-
return src ? (
131+
if (assetUrl._tag === "Failure" || (assetUrl._tag === "Success" && failedUrl === assetUrl.url)) {
132+
return (
133+
<div className="flex min-h-0 flex-1 items-center justify-center px-6 text-center text-xs leading-relaxed text-destructive">
134+
Unable to load workspace image.
135+
</div>
136+
);
137+
}
138+
139+
return assetUrl._tag === "Success" ? (
131140
<div className="flex min-h-0 flex-1 items-center justify-center overflow-auto p-4">
132-
<img className="max-h-full max-w-full object-contain" src={src} alt={props.alt} />
141+
<img
142+
className="max-h-full max-w-full object-contain"
143+
src={assetUrl.url}
144+
alt={props.alt}
145+
onError={() => setFailedUrl(assetUrl.url)}
146+
/>
133147
</div>
134148
) : (
135149
<div className="flex min-h-0 flex-1 items-center justify-center text-muted-foreground">

0 commit comments

Comments
 (0)