feat(app): store assistant image previews as assets
This commit is contained in:
@@ -55,4 +55,15 @@ describe("assistant image metadata", () => {
|
||||
),
|
||||
).toBeGreaterThan(220);
|
||||
});
|
||||
|
||||
it("estimates image-only data-image markdown without caching the full payload as text", () => {
|
||||
const source = `data:image/png;base64,${"a".repeat(512)}`;
|
||||
setAssistantImageMetadata({ source }, { width: 1200, height: 800 });
|
||||
|
||||
const imageOnlyHeight = estimateAssistantMessageHeightFromCache(``);
|
||||
const mixedHeight = estimateAssistantMessageHeightFromCache(`Text\n\n`);
|
||||
|
||||
expect(imageOnlyHeight).toBeGreaterThan(220);
|
||||
expect(mixedHeight).toBeGreaterThan(imageOnlyHeight ?? 0);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { MAX_CONTENT_WIDTH } from "@/constants/layout";
|
||||
import { resolveAssistantImageSource } from "@/utils/assistant-image-source";
|
||||
import { createImageSourceCacheKey } from "@/attachments/utils";
|
||||
|
||||
export interface AssistantImageMetadata {
|
||||
width: number;
|
||||
@@ -48,8 +49,25 @@ function normalizeAssistantImageSourceToken(value: string): string | null {
|
||||
return source || null;
|
||||
}
|
||||
|
||||
function parseAssistantImageMarkdown(markdown: string): {
|
||||
sources: string[];
|
||||
hasNonImageText: boolean;
|
||||
} {
|
||||
const sources: string[] = [];
|
||||
for (const match of markdown.matchAll(MARKDOWN_IMAGE_PATTERN)) {
|
||||
const normalized = normalizeAssistantImageSourceToken(match[1] ?? "");
|
||||
if (normalized) {
|
||||
sources.push(normalized);
|
||||
}
|
||||
}
|
||||
return {
|
||||
sources,
|
||||
hasNonImageText: markdown.replace(MARKDOWN_IMAGE_PATTERN, "").trim().length > 0,
|
||||
};
|
||||
}
|
||||
|
||||
function createSourceAliasKey(source: string): string {
|
||||
return `source:${source}`;
|
||||
return `source:${createImageSourceCacheKey(source)}`;
|
||||
}
|
||||
|
||||
function createResolutionKey(input: {
|
||||
@@ -65,7 +83,7 @@ function createResolutionKey(input: {
|
||||
return null;
|
||||
}
|
||||
if (resolution.kind === "direct") {
|
||||
return `direct:${resolution.uri}`;
|
||||
return `direct:${createImageSourceCacheKey(resolution.uri)}`;
|
||||
}
|
||||
return `file:${input.serverId ?? "unknown-server"}:${resolution.cwd}:${resolution.path}`;
|
||||
}
|
||||
@@ -140,7 +158,8 @@ export function setAssistantImageMetadata(
|
||||
}
|
||||
|
||||
export function extractAssistantImageSources(markdown: string): string[] {
|
||||
const cachedParse = assistantImageParseCache.get(markdown);
|
||||
const shouldCacheParse = !/data:image\//i.test(markdown);
|
||||
const cachedParse = shouldCacheParse ? assistantImageParseCache.get(markdown) : undefined;
|
||||
if (cachedParse) {
|
||||
touchCacheEntry(
|
||||
assistantImageParseCache,
|
||||
@@ -151,32 +170,15 @@ export function extractAssistantImageSources(markdown: string): string[] {
|
||||
return cachedParse.sources;
|
||||
}
|
||||
|
||||
const sources: string[] = [];
|
||||
for (const match of markdown.matchAll(MARKDOWN_IMAGE_PATTERN)) {
|
||||
const normalized = normalizeAssistantImageSourceToken(match[1] ?? "");
|
||||
if (normalized) {
|
||||
sources.push(normalized);
|
||||
}
|
||||
const parsed = parseAssistantImageMarkdown(markdown);
|
||||
if (shouldCacheParse) {
|
||||
touchCacheEntry(assistantImageParseCache, markdown, parsed, ASSISTANT_IMAGE_PARSE_CACHE_LIMIT);
|
||||
}
|
||||
const hasNonImageText = markdown.replace(MARKDOWN_IMAGE_PATTERN, "").trim().length > 0;
|
||||
touchCacheEntry(
|
||||
assistantImageParseCache,
|
||||
markdown,
|
||||
{ sources, hasNonImageText },
|
||||
ASSISTANT_IMAGE_PARSE_CACHE_LIMIT,
|
||||
);
|
||||
return sources;
|
||||
return parsed.sources;
|
||||
}
|
||||
|
||||
export function estimateAssistantMessageHeightFromCache(markdown: string): number | null {
|
||||
const cachedParse = assistantImageParseCache.get(markdown);
|
||||
const parsed =
|
||||
cachedParse ??
|
||||
(() => {
|
||||
const sources = extractAssistantImageSources(markdown);
|
||||
const nextParsed = assistantImageParseCache.get(markdown);
|
||||
return nextParsed ?? { sources, hasNonImageText: true };
|
||||
})();
|
||||
const parsed = assistantImageParseCache.get(markdown) ?? parseAssistantImageMarkdown(markdown);
|
||||
if (parsed.sources.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -28,8 +28,10 @@ function createTestStore(): AttachmentStore {
|
||||
byteSize = input.source.blob.size;
|
||||
} else if (input.source.kind === "data_url") {
|
||||
byteSize = input.source.dataUrl.length;
|
||||
} else {
|
||||
} else if (input.source.kind === "file_uri") {
|
||||
byteSize = input.source.uri.length;
|
||||
} else {
|
||||
byteSize = input.source.base64.length;
|
||||
}
|
||||
return {
|
||||
id,
|
||||
|
||||
Reference in New Issue
Block a user