71 lines
2.6 KiB
TypeScript
71 lines
2.6 KiB
TypeScript
import { normalizeImageIdentityUrl } from "./canvas-chat-media";
|
|
import type { ArchivedImageUrlMap } from "@/lib/storage/image-archive-urls";
|
|
|
|
type ImageResultNode = {
|
|
type: string;
|
|
data?: unknown;
|
|
};
|
|
|
|
function imageUrls(node: ImageResultNode): string[] {
|
|
if (node.type !== "image") return [];
|
|
const data = node.data as { src?: string; metadata?: { sourceImageKey?: string } } | undefined;
|
|
return [data?.src, data?.metadata?.sourceImageKey].filter(
|
|
(url): url is string => typeof url === "string" && Boolean(url.trim()),
|
|
);
|
|
}
|
|
|
|
export function getCanvasImageResultUrlKeys(
|
|
nodes: readonly ImageResultNode[],
|
|
aliases: ArchivedImageUrlMap = {},
|
|
) {
|
|
const keys = new Set<string>();
|
|
for (const node of nodes) {
|
|
for (const url of imageUrls(node)) {
|
|
keys.add(normalizeImageIdentityUrl(url));
|
|
if (aliases[url]) keys.add(normalizeImageIdentityUrl(aliases[url]));
|
|
}
|
|
}
|
|
keys.delete("");
|
|
return keys;
|
|
}
|
|
|
|
// A history refresh may already contain bucket URLs while this tab still shows
|
|
// provider previews. Resolve aliases only for an existing result of this message;
|
|
// a new generation never waits for storage before appearing on the canvas.
|
|
export async function resolveCanvasImageResultAliases(params: {
|
|
nodes: readonly ImageResultNode[];
|
|
messageId?: string;
|
|
imageUrls: string[];
|
|
resolveAliases: (urls: string[]) => Promise<ArchivedImageUrlMap>;
|
|
}): Promise<ArchivedImageUrlMap> {
|
|
const keys = getCanvasImageResultUrlKeys(params.nodes);
|
|
if (!params.messageId || params.imageUrls.every((url) => keys.has(normalizeImageIdentityUrl(url)))) {
|
|
return {};
|
|
}
|
|
const urls = [...new Set(params.nodes.flatMap((node) => {
|
|
const data = node.data as { metadata?: { sourceMessageId?: string } } | undefined;
|
|
return data?.metadata?.sourceMessageId === params.messageId ? imageUrls(node) : [];
|
|
}))].filter((url) => /^https?:\/\//i.test(url));
|
|
if (!urls.length) return {};
|
|
const aliases: ArchivedImageUrlMap = {};
|
|
for (let index = 0; index < urls.length; index += 100) {
|
|
Object.assign(aliases, await params.resolveAliases(urls.slice(index, index + 100)));
|
|
}
|
|
return aliases;
|
|
}
|
|
|
|
// Call synchronously immediately before changing the store, after all image
|
|
// loading/network awaits. Both stream and history writers share this check.
|
|
export function commitCanvasImageResultIfMissing<T>(params: {
|
|
getNodes: () => readonly ImageResultNode[];
|
|
url: string;
|
|
aliases: ArchivedImageUrlMap;
|
|
commit: () => T;
|
|
}): T | undefined {
|
|
const key = normalizeImageIdentityUrl(params.url);
|
|
if (!key || getCanvasImageResultUrlKeys(params.getNodes(), params.aliases).has(key)) {
|
|
return undefined;
|
|
}
|
|
return params.commit();
|
|
}
|