From 748505a7d354cce0fa2a6a17f506e39e708187d5 Mon Sep 17 00:00:00 2001 From: genz27 Date: Sat, 14 Mar 2026 19:33:07 +0800 Subject: [PATCH] fix: improve gemini image rendering and log previews --- src/api/routes.py | 26 +++++++++++++++++++++----- static/manage.html | 44 ++++++++++++++++++++++++++++++++++---------- 2 files changed, 55 insertions(+), 15 deletions(-) diff --git a/src/api/routes.py b/src/api/routes.py index 71865cf..569ab5e 100644 --- a/src/api/routes.py +++ b/src/api/routes.py @@ -93,8 +93,8 @@ def _guess_mime_type(uri: str, fallback: str) -> str: async def retrieve_image_data(url: str) -> Optional[bytes]: """Read image bytes from local /tmp cache or remote URL.""" + file_cache = getattr(generation_handler, "file_cache", None) try: - file_cache = getattr(generation_handler, "file_cache", None) if "/tmp/" in url and file_cache: path = urlparse(url).path filename = path.split("/tmp/")[-1] @@ -107,15 +107,30 @@ async def retrieve_image_data(url: str) -> Optional[bytes]: except Exception as exc: debug_logger.log_warning(f"[CONTEXT] 本地缓存读取失败: {str(exc)}") + proxy_url = None + try: + if file_cache and hasattr(file_cache, "_resolve_download_proxy"): + proxy_url = await file_cache._resolve_download_proxy("image") + except Exception as exc: + debug_logger.log_warning(f"[CONTEXT] 图片下载代理解析失败: {str(exc)}") + try: async with AsyncSession() as session: response = await session.get( url, - timeout=30, - impersonate="chrome110", + timeout=60, + proxies={"http": proxy_url, "https": proxy_url} if proxy_url else None, + headers={ + "Accept": "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", + "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8", + "Accept-Encoding": "gzip, deflate, br", + "Connection": "keep-alive", + "Referer": "https://labs.google/", + }, + impersonate="chrome120", verify=False, ) - if response.status_code == 200: + if response.status_code == 200 and response.content: return response.content debug_logger.log_warning( f"[CONTEXT] 图片下载失败,状态码: {response.status_code}" @@ -418,7 +433,8 @@ async def _build_image_parts_from_uri(uri: str) -> List[Dict[str, Any]]: "mimeType": _guess_mime_type(uri, "image/png"), "fileUri": uri, } - } + }, + {"text": uri}, ] diff --git a/static/manage.html b/static/manage.html index 5df9bd7..4b622ea 100644 --- a/static/manage.html +++ b/static/manage.html @@ -833,7 +833,7 @@ getLogOperationLabel=l=>{const operation=String(l&&l.operation||'').trim();if(operation==='generate_image')return'图片';if(operation==='generate_video')return'视频';return''}, formatLogOutcome=l=>{const statusCode=Number(l&&l.status_code);if(statusCode===200){const operationLabel=getLogOperationLabel(l);return operationLabel?`${operationLabel}结果已返回`:'已返回结果'}if(statusCode===102)return'处理中';const errorSummary=extractLogErrorSummary(l);if(errorSummary)return truncateLogText(errorSummary,96);return statusCode>=400?'请求失败':'-'}, formatLogOutcomeClass=l=>{if((l.status_code||0)>=400)return'text-red-600';if(l.status_code===200)return'text-green-700';if(l.status_code===102)return'text-amber-700';return'text-muted-foreground'}, - startLogsAutoRefresh=()=>{if(window.logsAutoRefreshTimer)return;window.logsAutoRefreshTimer=setInterval(()=>{const panel=$('panelLogs');if(panel&&!panel.classList.contains('hidden'))loadLogs({silent:true})},5000)}, + startLogsAutoRefresh=()=>{stopLogsAutoRefresh()}, stopLogsAutoRefresh=()=>{if(window.logsAutoRefreshTimer){clearInterval(window.logsAutoRefreshTimer);window.logsAutoRefreshTimer=null}}, loadLogs=async(opts={})=>{const silent=!!opts.silent;try{const r=await apiRequest('/api/logs?limit=100');if(!r)return;const logs=await r.json();window.allLogs=logs;window.logDetailCache=window.logDetailCache||Object.create(null);window.logListMeta=Object.create(null);logs.forEach(l=>window.logListMeta[l.id]={updated_at:l.updated_at,created_at:l.created_at,status_code:l.status_code,progress:l.progress,status_text:l.status_text,error_summary:l.error_summary||''});Object.keys(window.logDetailCache).forEach(key=>{const meta=window.logListMeta[key];const cached=window.logDetailCache[key];if(!meta||!cached||cached.updated_at!==meta.updated_at)delete window.logDetailCache[key]});const tb=$('logsTableBody');if(!logs.length){tb.innerHTML='\u6682\u65e0\u65e5\u5fd7';return}tb.innerHTML=logs.map(l=>{const statusText=formatLogStatus(l),progressText=formatLogProgress(l),outcomeText=formatLogOutcome(l),outcomePreview=truncateLogText(outcomeText,96),statusCodeClass=l.status_code===102?'bg-amber-50 text-amber-700':(l.status_code===200?'bg-green-50 text-green-700':'bg-red-50 text-red-700');return`${escapeLogHtml(l.operation||'-')}${escapeLogHtml(l.token_email||'\u672a\u77e5')}${escapeLogHtml(statusText)}${escapeLogHtml(progressText)}${escapeLogHtml(l.status_code??'-')}
${escapeLogHtml(outcomePreview)}
${Number(l.duration||0).toFixed(2)}${l.created_at?new Date(l.created_at).toLocaleString('zh-CN'):'-'}`}).join('');if(window.activeLogDetailId&&window.logListMeta[window.activeLogDetailId])await showLogDetail(window.activeLogDetailId,{silent:true})}catch(e){console.error('load logs failed:',e);if(!silent)showToast('\u52a0\u8f7d\u65e5\u5fd7\u5931\u8d25: '+e.message,'error')}}, refreshLogs=async()=>{await loadLogs()}, @@ -976,12 +976,28 @@ return key; } + function normalizeLogMediaUrl(url){ + if(!url) return ''; + const text=String(url).trim(); + if(!text||/^data:/i.test(text)) return text; + try{ + const parsed=new URL(text,window.location.origin); + if(parsed.pathname.startsWith('/tmp/')){ + return `${window.location.origin}${parsed.pathname}${parsed.search}${parsed.hash}`; + } + return parsed.toString(); + }catch(_){ + return text; + } + } + function renderLogLink(label,url){ if(!url) return ''; - if(/^data:/i.test(String(url))){ - return `

${label}: data URL(长度 ${String(url).length})

`; + const normalizedUrl=normalizeLogMediaUrl(url); + if(/^data:/i.test(String(normalizedUrl))){ + return `

${label}: data URL(长度 ${String(normalizedUrl).length})

`; } - const safeUrl=escapeLogHtml(url); + const safeUrl=escapeLogHtml(normalizedUrl); return `

${label}: ${safeUrl}

`; } @@ -1001,10 +1017,18 @@ function renderMediaPreview(label,url,withUrl=true){ if(!url) return ''; - const mediaType=isVideoUrl(url)?'video':(isImageUrl(url)?'image':''); - const mediaKey=mediaType?cacheLogMediaUrl(url):''; + const previewUrl=normalizeLogMediaUrl(url); + const mediaType=isVideoUrl(previewUrl)?'video':(isImageUrl(previewUrl)?'image':''); + const mediaKey=mediaType?cacheLogMediaUrl(previewUrl):''; const previewTrigger=mediaType?`
`:''; - return `

${escapeLogHtml(label)}

${withUrl?renderLogLink('URL',url):''}${previewTrigger}
`; + return `

${escapeLogHtml(label)}

${withUrl?renderLogLink('URL',previewUrl):''}${previewTrigger}
`; + } + + function handleLogMediaPreviewError(node){ + if(!node) return; + const mediaUrl=node.dataset.mediaUrl||''; + const mediaLabel=node.dataset.mediaLabel||'媒体'; + node.outerHTML=`

${escapeLogHtml(mediaLabel)}预览加载失败,请直接打开链接查看。

${renderLogLink('URL',mediaUrl)}
`; } function loadLogMediaPreview(button){ @@ -1016,9 +1040,9 @@ const mediaType=button.dataset.mediaType||''; if(!container||!url||!mediaType) return; if(mediaType==='video'){ - container.innerHTML=``; + container.innerHTML=``; }else{ - container.innerHTML=`${escapeLogHtml(label)}`; + container.innerHTML=`${escapeLogHtml(label)}`; } button.remove(); } @@ -1056,7 +1080,7 @@ const upPreviewUrl=up.local_url||up.url||null; assetsHtml+=`

放大分辨率: ${escapeLogHtml(upResolution)}

`; if(upPreviewUrl){ - assetsHtml+=renderMediaPreview(`${upResolution}结果`,upPreviewUrl,false); + assetsHtml+=renderMediaPreview(`${upResolution}结果`,upPreviewUrl,!/^data:/i.test(String(upPreviewUrl||''))); } if(up.base64){ const preview=String(up.base64).length>600?`${String(up.base64).slice(0,600)}...`:String(up.base64);