ai-chatbot/lib/storage/image-archive-urls.ts

57 lines
2.1 KiB
TypeScript

// Shared by the server and canvas; no storage or network work happens here.
export type ArchivedImageUrlMap = Record<string, string>;
export function isGeneratedImageSource(value: string) {
return (
/^https?:\/\//i.test(value) || /^data:image\/[a-z0-9.+-]+;base64,[a-z0-9+/=]+$/i.test(value)
);
}
export function collectImageArchiveSources(value: unknown): string[] {
const urls = new Set<string>();
const visit = (item: unknown) => {
if (typeof item === "string") {
if (isGeneratedImageSource(item)) urls.add(item);
else
for (const match of item.matchAll(/!\[[^\]]*\]\(([^)]+)\)/g)) {
if (isGeneratedImageSource(match[1])) urls.add(match[1]);
}
} else if (Array.isArray(item)) item.forEach(visit);
else if (item && typeof item === "object" && !(item instanceof Date))
Object.values(item).forEach(visit);
};
visit(value);
return [...urls];
}
// Structural sharing preserves unaffected nodes and Date values; no undo step is added.
export function replaceArchivedImageUrls<T>(value: T, replacements: ArchivedImageUrlMap): T {
if (typeof value === "string") {
if (replacements[value]) return replacements[value] as T;
// URLs inside generated-image Markdown carry both preview and original URLs.
return value.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, (_match, alt: string, url: string) => {
const nextAlt = alt
.split("|||")
.map((part) => replacements[part] || part)
.join("|||");
return `![${nextAlt}](${replacements[url] || url})`;
}) as T;
}
if (!value || typeof value !== "object" || value instanceof Date) return value;
let changed = false;
if (Array.isArray(value)) {
const next = value.map((item) => {
const replaced = replaceArchivedImageUrls(item, replacements);
if (replaced !== item) changed = true;
return replaced;
});
return (changed ? next : value) as T;
}
const next: Record<string, unknown> = {};
for (const [key, item] of Object.entries(value)) {
next[key] = replaceArchivedImageUrls(item, replacements);
if (next[key] !== item) changed = true;
}
return (changed ? next : value) as T;
}