Spaces:
Running
Running
| import { isImagePath, previewTextSnippet } from "./previewHelpers"; | |
| import { htmlPreviewUrl, isHtmlPath } from "./filePreview"; | |
| import { | |
| base64ToDataUrl, | |
| isUsableImageBase64, | |
| mergeImageFields, | |
| resolveImageUrl, | |
| } from "./imagePreview"; | |
| const BROWSER_TOOLS = [ | |
| "web_fetch", "browser_visit", "browser_open", | |
| "browser_click", "browser_snapshot", "browser_scroll", "browser_press", "browser_type", "browser_fill", | |
| ]; | |
| function hasValidScreenshot(data) { | |
| const b64 = data?.screenshot_base64; | |
| if (!b64 || typeof b64 !== "string") return false; | |
| if (b64.includes("truncated") || b64.includes("[screenshot:")) return false; | |
| return b64.length > 500; | |
| } | |
| /** Build a displayable image src from SSE / tool result fields. */ | |
| export function buildImagePreviewSrc(input) { | |
| const fields = mergeImageFields(input); | |
| const dataUrl = base64ToDataUrl(fields.image_base64, fields.mime); | |
| if (dataUrl) return dataUrl; | |
| if (input?.src && typeof input.src === "string") { | |
| const fromDirect = resolveImageUrl(input.src); | |
| if (fromDirect) return fromDirect; | |
| } | |
| return resolveImageUrl(fields.image_url); | |
| } | |
| /** Pair display src + alternate (URL ↔ base64) for resilient previews. */ | |
| export function buildImagePreviewPair(input) { | |
| if (!input || typeof input !== "object") return { src: null, fallbackSrc: null }; | |
| const fields = mergeImageFields(input); | |
| const dataUrl = base64ToDataUrl(fields.image_base64, fields.mime); | |
| const fromUrl = resolveImageUrl(fields.image_url); | |
| const main = dataUrl || fromUrl || buildImagePreviewSrc(input); | |
| if (!main) return { src: null, fallbackSrc: null }; | |
| let fallbackSrc = null; | |
| if (main === dataUrl && fromUrl) fallbackSrc = fromUrl; | |
| else if (main === fromUrl && dataUrl) fallbackSrc = dataUrl; | |
| if (fallbackSrc === main) fallbackSrc = null; | |
| return { src: main, fallbackSrc }; | |
| } | |
| /** True when event carries enough data to attempt image display. */ | |
| export function hasImagePayload(input) { | |
| if (!input || typeof input !== "object") return false; | |
| const fields = mergeImageFields(input); | |
| return Boolean( | |
| isUsableImageBase64(fields.image_base64) | |
| || resolveImageUrl(fields.image_url) | |
| || input.has_image, | |
| ); | |
| } | |
| /** Détecte si un événement tool peut afficher un aperçu visuel. */ | |
| export function hasToolPreview(event) { | |
| return Boolean(resolveToolPreview(event)); | |
| } | |
| /** Contenu fichier complet pour copie (priorité SSE > args > result). */ | |
| export function resolveFullCopyContent(event) { | |
| const preview = event?.inlinePreview; | |
| const result = event?.result; | |
| const args = event?.args || {}; | |
| const tool = event?.tool; | |
| if (preview?.preview) return preview.preview; | |
| if (tool === "write_file" || tool === "edit_file") { | |
| if (args.content) return args.content; | |
| } | |
| if (result?.content) return result.content; | |
| return ""; | |
| } | |
| function enrichWithFullContent(data, event) { | |
| if (!data || (data.kind !== "text" && data.kind !== "html")) return data; | |
| const copyContent = resolveFullCopyContent(event); | |
| return copyContent ? { ...data, fullContent: copyContent } : data; | |
| } | |
| /** Résout les données d'aperçu pour fichier, HTML, site, capture. */ | |
| export function resolveToolPreview(event) { | |
| const preview = event?.inlinePreview; | |
| const result = event?.result; | |
| const tool = event?.tool; | |
| const args = event?.args || {}; | |
| if (preview?.type === "browser") { | |
| const p = preview; | |
| if (p.action === "search" && (p.results || []).length) { | |
| return { | |
| kind: "search", | |
| title: p.query || "Recherche", | |
| results: (p.results || []).slice(0, 4), | |
| }; | |
| } | |
| if (p.url || hasValidScreenshot(p)) { | |
| return { | |
| kind: "interactive", | |
| url: p.url || result?.url || "", | |
| title: p.title || p.url || "Navigateur", | |
| previewText: p.preview, | |
| screenshot_base64: hasValidScreenshot(p) ? p.screenshot_base64 : null, | |
| elements: p.elements || [], | |
| session_id: p.session_id || "default", | |
| }; | |
| } | |
| } | |
| if (preview?.type === "file") { | |
| return enrichWithFullContent( | |
| resolveFilePreview(preview.path, preview.preview, preview.is_image), | |
| event, | |
| ); | |
| } | |
| if (preview?.type === "image" || (tool === "generate_image" && result?.ok !== false)) { | |
| const fields = mergeImageFields(preview, result); | |
| if (hasImagePayload(fields) || hasImagePayload(preview) || hasImagePayload(result)) { | |
| return { | |
| kind: "image", | |
| title: fields.title || args.prompt || "Image générée", | |
| imageFields: fields, | |
| }; | |
| } | |
| } | |
| if (!result || result.ok === false) { | |
| const url = args.url; | |
| if (url && BROWSER_TOOLS.includes(tool)) { | |
| return { | |
| kind: "interactive", | |
| url, | |
| title: url, | |
| elements: [], | |
| session_id: "default", | |
| }; | |
| } | |
| if (tool === "generate_image" && result?.error) { | |
| return { kind: "text", text: result.error, title: "Génération d'image" }; | |
| } | |
| return null; | |
| } | |
| if (tool === "web_search" && (result.results || []).length) { | |
| return { | |
| kind: "search", | |
| title: result.query || args.query || "Recherche", | |
| results: (result.results || []).slice(0, 4), | |
| }; | |
| } | |
| if (tool === "write_file" || tool === "read_file" || tool === "edit_file") { | |
| const path = result.path || args.path || preview?.path || ""; | |
| const content = | |
| tool === "write_file" || tool === "edit_file" | |
| ? (args.content || result.content || preview?.preview || "") | |
| : (result.content || preview?.preview || ""); | |
| if (path && content) { | |
| return enrichWithFullContent(resolveFilePreview(path, content), event); | |
| } | |
| } | |
| if (BROWSER_TOOLS.includes(tool)) { | |
| const url = result.url || args.url; | |
| if (url || hasValidScreenshot(result)) { | |
| return { | |
| kind: "interactive", | |
| url: url || "", | |
| title: result.title || url || "Capture", | |
| previewText: result.preview || result.text, | |
| screenshot_base64: hasValidScreenshot(result) ? result.screenshot_base64 : null, | |
| elements: result.elements || [], | |
| session_id: result.session_id || "default", | |
| }; | |
| } | |
| } | |
| if (hasValidScreenshot(result)) { | |
| return { | |
| kind: "interactive", | |
| url: result.url || "", | |
| title: result.title || result.url || "Capture", | |
| screenshot_base64: result.screenshot_base64, | |
| elements: result.elements || [], | |
| session_id: result.session_id || "default", | |
| }; | |
| } | |
| return null; | |
| } | |
| function resolveFilePreview(path, content, isImage) { | |
| if (!path || !content) return null; | |
| if (isImage && content.startsWith("data:")) { | |
| return { kind: "image", src: content, title: path.split(/[/\\]/).pop(), path }; | |
| } | |
| if (isImagePath(path) && content.startsWith("data:")) { | |
| return { kind: "image", src: content, title: path.split(/[/\\]/).pop(), path }; | |
| } | |
| if (isImage && !content.startsWith("data:") && content.length > 100) { | |
| return { | |
| kind: "image", | |
| src: `data:image/jpeg;base64,${content}`, | |
| title: path.split(/[/\\]/).pop(), | |
| path, | |
| }; | |
| } | |
| if (isHtmlPath(path)) { | |
| return { | |
| kind: "html", | |
| htmlUrl: htmlPreviewUrl(content), | |
| title: path.split(/[/\\]/).pop(), | |
| path, | |
| fullContent: content, | |
| }; | |
| } | |
| if (content.length > 20) { | |
| return { | |
| kind: "text", | |
| text: previewTextSnippet(content, 1200), | |
| title: path.split(/[/\\]/).pop(), | |
| path, | |
| fullContent: content, | |
| }; | |
| } | |
| return null; | |
| } | |