mirror of
https://github.com/TheSmallHanCat/flow2api.git
synced 2026-06-05 14:21:45 +08:00
fix: simplify image fallback and log summaries
This commit is contained in:
@@ -1119,19 +1119,24 @@ async def get_logs(
|
||||
|
||||
result = []
|
||||
for log in logs:
|
||||
raw_status_code = log.get("status_code")
|
||||
try:
|
||||
status_code = int(raw_status_code) if raw_status_code is not None else None
|
||||
except (TypeError, ValueError):
|
||||
status_code = None
|
||||
result.append({
|
||||
"id": log.get("id"),
|
||||
"token_id": log.get("token_id"),
|
||||
"token_email": log.get("token_email"),
|
||||
"token_username": log.get("token_username"),
|
||||
"operation": log.get("operation"),
|
||||
"status_code": log.get("status_code"),
|
||||
"status_code": status_code if status_code is not None else raw_status_code,
|
||||
"duration": log.get("duration"),
|
||||
"status_text": log.get("status_text") or "",
|
||||
"progress": log.get("progress") or 0,
|
||||
"created_at": log.get("created_at"),
|
||||
"updated_at": log.get("updated_at"),
|
||||
"error_summary": _extract_error_summary(log.get("response_body_excerpt")),
|
||||
"error_summary": _extract_error_summary(log.get("response_body_excerpt")) if status_code is not None and status_code >= 400 else "",
|
||||
})
|
||||
return result
|
||||
|
||||
|
||||
@@ -128,6 +128,21 @@ class FileCache:
|
||||
|
||||
return f"{url_hash}{ext}"
|
||||
|
||||
def _normalize_cache_error(self, error: Exception) -> str:
|
||||
"""整理缓存错误,避免将底层命令异常直接暴露给用户。"""
|
||||
if isinstance(error, FileNotFoundError):
|
||||
missing_name = Path(getattr(error, "filename", "") or "curl").name or "curl"
|
||||
return f"本机未安装 {missing_name}"
|
||||
|
||||
message = str(error or "").strip()
|
||||
if not message:
|
||||
return "未知错误"
|
||||
|
||||
if message.startswith("Failed to cache file:"):
|
||||
message = message.split(":", 1)[1].strip() or "未知错误"
|
||||
|
||||
return message
|
||||
|
||||
async def download_and_cache(self, url: str, media_type: str) -> str:
|
||||
"""
|
||||
Download file from URL and cache it locally
|
||||
@@ -281,13 +296,22 @@ class FileCache:
|
||||
error_msg = result.stderr.decode('utf-8', errors='ignore') if result.stderr else "Unknown error"
|
||||
raise Exception(f"curl command failed: {error_msg}")
|
||||
|
||||
except Exception as e:
|
||||
except FileNotFoundError as e:
|
||||
normalized_error = self._normalize_cache_error(e)
|
||||
debug_logger.log_error(
|
||||
error_message=f"Failed to download file: {str(e)}",
|
||||
status_code=0,
|
||||
response_text=str(e)
|
||||
)
|
||||
raise Exception(f"Failed to cache file: {str(e)}")
|
||||
raise Exception(normalized_error) from e
|
||||
except Exception as e:
|
||||
normalized_error = self._normalize_cache_error(e)
|
||||
debug_logger.log_error(
|
||||
error_message=f"Failed to download file: {str(e)}",
|
||||
status_code=0,
|
||||
response_text=str(e)
|
||||
)
|
||||
raise Exception(normalized_error) from e
|
||||
|
||||
async def cache_base64_image(self, base64_data: str, resolution: str = "") -> str:
|
||||
"""
|
||||
|
||||
@@ -1252,7 +1252,8 @@ class GenerationHandler:
|
||||
except Exception as e:
|
||||
debug_logger.log_error(f"Failed to cache {resolution_name} image: {str(e)}")
|
||||
if stream:
|
||||
yield self._create_stream_chunk(f"⚠️ 缓存失败: {str(e)},返回 base64...\n")
|
||||
cache_error = self._normalize_error_message(e, max_length=120)
|
||||
yield self._create_stream_chunk(f"⚠️ 缓存失败: {cache_error},返回 base64...\n")
|
||||
|
||||
# 缓存未启用或缓存失败,返回 base64 格式
|
||||
base64_url = f"data:image/jpeg;base64,{encoded_image}"
|
||||
@@ -1297,29 +1298,12 @@ class GenerationHandler:
|
||||
if image_trace is not None:
|
||||
image_trace["upsample_ms"] = int((time.time() - upsample_started_at) * 1000)
|
||||
|
||||
# 缓存图片 (如果启用)
|
||||
# 1K 原图统一直接返回官方直链,避免额外下载缓存依赖 curl/wget。
|
||||
local_url = image_url
|
||||
cache_started_at = time.time()
|
||||
if config.cache_enabled:
|
||||
await self._update_request_log_progress(request_log_state, token_id=token.id, status_text="caching_image", progress=92)
|
||||
try:
|
||||
if stream:
|
||||
yield self._create_stream_chunk("缓存图片中...\n")
|
||||
cached_filename = await self.file_cache.download_and_cache(image_url, "image")
|
||||
local_url = f"{self._get_base_url()}/tmp/{cached_filename}"
|
||||
if stream:
|
||||
yield self._create_stream_chunk("✅ 图片缓存成功,准备返回缓存地址...\n")
|
||||
except Exception as e:
|
||||
debug_logger.log_error(f"Failed to cache image: {str(e)}")
|
||||
# 缓存失败不影响结果返回,使用原始URL
|
||||
local_url = image_url
|
||||
if stream:
|
||||
yield self._create_stream_chunk(f"⚠️ 缓存失败: {str(e)}\n正在返回源链接...\n")
|
||||
else:
|
||||
if stream:
|
||||
yield self._create_stream_chunk("缓存已关闭,正在返回源链接...\n")
|
||||
if stream:
|
||||
yield self._create_stream_chunk("正在返回官方图片链接...\n")
|
||||
if image_trace is not None:
|
||||
image_trace["cache_image_ms"] = int((time.time() - cache_started_at) * 1000)
|
||||
image_trace["cache_image_ms"] = 0
|
||||
|
||||
# 返回结果
|
||||
# 存储URL用于日志记录
|
||||
@@ -1728,7 +1712,8 @@ class GenerationHandler:
|
||||
# 缓存失败不影响结果返回,使用原始URL
|
||||
local_url = video_url
|
||||
if stream:
|
||||
yield self._create_stream_chunk(f"⚠️ 缓存失败: {str(e)}\n正在返回源链接...\n")
|
||||
cache_error = self._normalize_error_message(e, max_length=120)
|
||||
yield self._create_stream_chunk(f"⚠️ 缓存失败: {cache_error}\n正在返回源链接...\n")
|
||||
else:
|
||||
if stream:
|
||||
yield self._create_stream_chunk("缓存已关闭,正在返回源链接...\n")
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
@keyframes slide-up{from{transform:translateY(100%);opacity:0}to{transform:translateY(0);opacity:1}}
|
||||
.animate-slide-up{animation:slide-up .3s ease-out}
|
||||
.tab-btn{transition:all .2s ease}
|
||||
.line-clamp-2{display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;overflow:hidden}
|
||||
</style>
|
||||
<script>
|
||||
tailwind.config={theme:{extend:{colors:{border:"hsl(0 0% 89%)",input:"hsl(0 0% 89%)",ring:"hsl(0 0% 3.9%)",background:"hsl(0 0% 100%)",foreground:"hsl(0 0% 3.9%)",primary:{DEFAULT:"hsl(0 0% 9%)",foreground:"hsl(0 0% 98%)"},secondary:{DEFAULT:"hsl(0 0% 96.1%)",foreground:"hsl(0 0% 9%)"},muted:{DEFAULT:"hsl(0 0% 96.1%)",foreground:"hsl(0 0% 45.1%)"},destructive:{DEFAULT:"hsl(0 84.2% 60.2%)",foreground:"hsl(0 0% 98%)"}}}}}
|
||||
@@ -514,7 +515,7 @@
|
||||
<th class="h-10 px-3 text-left align-middle font-medium text-muted-foreground">状态</th>
|
||||
<th class="h-10 px-3 text-left align-middle font-medium text-muted-foreground">进度</th>
|
||||
<th class="h-10 px-3 text-left align-middle font-medium text-muted-foreground">状态码</th>
|
||||
<th class="h-10 px-3 text-left align-middle font-medium text-muted-foreground">结果 / 错误</th>
|
||||
<th class="h-10 w-[17rem] px-3 text-left align-middle font-medium text-muted-foreground">结果摘要</th>
|
||||
<th class="h-10 px-3 text-left align-middle font-medium text-muted-foreground">耗时(秒)</th>
|
||||
<th class="h-10 px-3 text-left align-middle font-medium text-muted-foreground">时间</th>
|
||||
<th class="h-10 px-3 text-left align-middle font-medium text-muted-foreground">详情</th>
|
||||
@@ -826,14 +827,15 @@
|
||||
generateRandomToken=()=>{const chars='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';let token='';for(let i=0;i<32;i++){token+=chars.charAt(Math.floor(Math.random()*chars.length))}$('cfgPluginConnectionToken').value=token;showToast('随机Token已生成','success')},
|
||||
toggleATAutoRefresh=async()=>{try{const enabled=$('atAutoRefreshToggle').checked;const r=await apiRequest('/api/token-refresh/enabled',{method:'POST',body:JSON.stringify({enabled:enabled})});if(!r){$('atAutoRefreshToggle').checked=!enabled;return}const d=await r.json();if(d.success){showToast(enabled?'AT自动刷新已启用':'AT自动刷新已禁用','success')}else{showToast('操作失败: '+(d.detail||'未知错误'),'error');$('atAutoRefreshToggle').checked=!enabled}}catch(e){showToast('操作失败: '+e.message,'error');$('atAutoRefreshToggle').checked=!enabled}},
|
||||
loadATAutoRefreshConfig=async()=>{try{const r=await apiRequest('/api/token-refresh/config');if(!r)return;const d=await r.json();if(d.success&&d.config){$('atAutoRefreshToggle').checked=d.config.at_auto_refresh_enabled||false}else{console.error('AT自动刷新配置数据格式错误:',d)}}catch(e){console.error('加载AT自动刷新配置失败:',e)}},
|
||||
formatLogStatus=l=>{const statusText=(l.status_text||'').trim();if(statusText){const map={started:'\u5df2\u542f\u52a8',token_selected:'\u5df2\u9009\u4e2d\u8d26\u53f7',token_ready:'\u51c6\u5907\u751f\u6210\u73af\u5883',project_ready:'\u9879\u76ee\u5df2\u5c31\u7eea',uploading_images:'\u4e0a\u4f20\u53c2\u8003\u56fe\u4e2d',submitting_image:'\u83b7\u53d6\u6253\u7801\u5e76\u63d0\u4ea4\u56fe\u7247\u4efb\u52a1',image_generated:'\u56fe\u7247\u751f\u6210\u5b8c\u6210',preparing_video:'\u51c6\u5907\u89c6\u9891\u4efb\u52a1',submitting_video:'\u83b7\u53d6\u6253\u7801\u5e76\u63d0\u4ea4\u89c6\u9891\u4efb\u52a1',video_submitted:'\u89c6\u9891\u4efb\u52a1\u5df2\u63d0\u4ea4',video_polling:'\u89c6\u9891\u751f\u6210\u4e2d',caching_image:'\u7f13\u5b58\u56fe\u7247\u4e2d',caching_video:'\u7f13\u5b58\u89c6\u9891\u4e2d',completed:'\u5df2\u5b8c\u6210',failed:'\u5931\u8d25',processing:'\u5904\u7406\u4e2d',upsampling_2k:'\u6b63\u5728\u653e\u5927\u52302K',upsampling_4k:'\u6b63\u5728\u653e\u5927\u52304K',upsampling_1080p:'\u6b63\u5728\u653e\u5927\u52301080P'};return map[statusText]||statusText}if(l.status_code===102)return'\u5904\u7406\u4e2d';if(l.status_code===200)return'\u5df2\u5b8c\u6210';if(l.status_code&&l.status_code>=400)return'\u5931\u8d25';return'-'},
|
||||
formatLogStatus=l=>{const statusText=(l.status_text||'').trim();if(statusText){const map={started:'\u5df2\u542f\u52a8',token_selected:'\u5df2\u9009\u4e2d\u8d26\u53f7',token_ready:'\u51c6\u5907\u751f\u6210\u73af\u5883',project_ready:'\u9879\u76ee\u5df2\u5c31\u7eea',uploading_images:'\u4e0a\u4f20\u53c2\u8003\u56fe\u4e2d',submitting_image:'\u56fe\u7247\u63d0\u4ea4\u4e2d',image_generated:'\u56fe\u7247\u751f\u6210\u5b8c\u6210',preparing_video:'\u51c6\u5907\u89c6\u9891\u4efb\u52a1',submitting_video:'\u89c6\u9891\u63d0\u4ea4\u4e2d',video_submitted:'\u89c6\u9891\u4efb\u52a1\u5df2\u63d0\u4ea4',video_polling:'\u89c6\u9891\u751f\u6210\u4e2d',caching_image:'\u7f13\u5b58\u56fe\u7247\u4e2d',caching_video:'\u7f13\u5b58\u89c6\u9891\u4e2d',completed:'\u5df2\u5b8c\u6210',failed:'\u5931\u8d25',processing:'\u5904\u7406\u4e2d',upsampling_2k:'\u6b63\u5728\u653e\u5927\u52302K',upsampling_4k:'\u6b63\u5728\u653e\u5927\u52304K',upsampling_1080p:'\u6b63\u5728\u653e\u5927\u52301080P'};return map[statusText]||statusText}if(l.status_code===102)return'\u5904\u7406\u4e2d';if(l.status_code===200)return'\u5df2\u5b8c\u6210';if(l.status_code&&l.status_code>=400)return'\u5931\u8d25';return'-'},
|
||||
formatLogStatusClass=l=>{const statusText=formatLogStatus(l);if(statusText==='\u5904\u7406\u4e2d')return'bg-amber-50 text-amber-700';if(statusText==='\u5df2\u5b8c\u6210')return'bg-green-50 text-green-700';if(statusText==='\u5931\u8d25')return'bg-red-50 text-red-700';return'bg-gray-100 text-gray-700'},
|
||||
formatLogProgress=l=>{if(l.progress===null||l.progress===undefined||l.progress==='')return'-';const progress=Number(l.progress);return Number.isFinite(progress)?`${Math.max(0,Math.min(100,progress))}%`:'-'},
|
||||
formatLogOutcome=l=>{const errorSummary=extractLogErrorSummary(l);if(errorSummary)return errorSummary;if(l.status_code===200)return'已返回结果';if(l.status_code===102)return'处理中';return'-'},
|
||||
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)},
|
||||
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='<tr><td colspan="9" class="py-8 px-3 text-center text-sm text-muted-foreground">\u6682\u65e0\u65e5\u5fd7</td></tr>';return}tb.innerHTML=logs.map(l=>{const statusText=formatLogStatus(l),progressText=formatLogProgress(l),outcomeText=formatLogOutcome(l),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`<tr><td class="py-2.5 px-3">${escapeLogHtml(l.operation||'-')}</td><td class="py-2.5 px-3"><span class="text-xs ${l.token_email?'text-blue-600':'text-muted-foreground'}">${escapeLogHtml(l.token_email||'\u672a\u77e5')}</span></td><td class="py-2.5 px-3"><span class="inline-flex items-center rounded px-2 py-0.5 text-xs ${formatLogStatusClass(l)}">${escapeLogHtml(statusText)}</span></td><td class="py-2.5 px-3 text-xs">${escapeLogHtml(progressText)}</td><td class="py-2.5 px-3"><span class="inline-flex items-center rounded px-2 py-0.5 text-xs ${statusCodeClass}">${escapeLogHtml(l.status_code??'-')}</span></td><td class="py-2.5 px-3"><div class="max-w-[320px] text-xs break-all ${formatLogOutcomeClass(l)}">${escapeLogHtml(outcomeText)}</div></td><td class="py-2.5 px-3">${Number(l.duration||0).toFixed(2)}</td><td class="py-2.5 px-3 text-xs text-muted-foreground">${l.created_at?new Date(l.created_at).toLocaleString('zh-CN'):'-'}</td><td class="py-2.5 px-3"><button onclick="showLogDetail(${l.id})" class="inline-flex items-center justify-center rounded-md hover:bg-blue-50 hover:text-blue-700 h-7 px-2 text-xs">详情</button></td></tr>`}).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')}},
|
||||
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='<tr><td colspan="9" class="py-8 px-3 text-center text-sm text-muted-foreground">\u6682\u65e0\u65e5\u5fd7</td></tr>';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`<tr><td class="py-2.5 px-3">${escapeLogHtml(l.operation||'-')}</td><td class="py-2.5 px-3"><span class="text-xs ${l.token_email?'text-blue-600':'text-muted-foreground'}">${escapeLogHtml(l.token_email||'\u672a\u77e5')}</span></td><td class="py-2.5 px-3"><span class="inline-flex items-center rounded px-2 py-0.5 text-xs ${formatLogStatusClass(l)}">${escapeLogHtml(statusText)}</span></td><td class="py-2.5 px-3 text-xs">${escapeLogHtml(progressText)}</td><td class="py-2.5 px-3"><span class="inline-flex items-center rounded px-2 py-0.5 text-xs ${statusCodeClass}">${escapeLogHtml(l.status_code??'-')}</span></td><td class="py-2.5 px-3 align-top"><div class="w-[17rem] max-w-[17rem] text-xs leading-5 whitespace-pre-wrap break-words line-clamp-2 ${formatLogOutcomeClass(l)}" title="${escapeLogHtml(outcomeText)}">${escapeLogHtml(outcomePreview)}</div></td><td class="py-2.5 px-3">${Number(l.duration||0).toFixed(2)}</td><td class="py-2.5 px-3 text-xs text-muted-foreground">${l.created_at?new Date(l.created_at).toLocaleString('zh-CN'):'-'}</td><td class="py-2.5 px-3"><button onclick="showLogDetail(${l.id})" class="inline-flex items-center justify-center rounded-md hover:bg-blue-50 hover:text-blue-700 h-7 px-2 text-xs">详情</button></td></tr>`}).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()},
|
||||
clearAllLogs=async()=>{if(!confirm('确定要清空所有日志吗?此操作不可恢复!'))return;try{const r=await apiRequest('/api/logs',{method:'DELETE'});if(!r)return;const d=await r.json();if(d.success){window.logDetailCache=Object.create(null);window.activeLogDetailId=null;resetLogMediaCache();showToast('日志已清空','success');await loadLogs()}else{showToast('清空失败: '+(d.message||'未知错误'),'error')}}catch(e){showToast('清空失败: '+e.message,'error')}},
|
||||
showLogDetail=async(logId,opts={})=>{const silent=!!opts.silent;const modal=$('logDetailModal');const content=$('logDetailContent');window.logDetailCache=window.logDetailCache||Object.create(null);window.logListMeta=window.logListMeta||Object.create(null);window.logDetailRequestSeq=(window.logDetailRequestSeq||0)+1;const requestSeq=window.logDetailRequestSeq;window.activeLogDetailId=logId;modal.classList.remove('hidden');content.innerHTML='<div class="rounded-md border border-border p-4 bg-muted/30 text-sm text-muted-foreground">\u65e5\u5fd7\u8be6\u60c5\u52a0\u8f7d\u4e2d...</div>';try{const meta=window.logListMeta[logId];let log=window.logDetailCache[logId];if(!log||!meta||log.updated_at!==meta.updated_at){const r=await apiRequest(`/api/logs/${logId}`);if(!r)return;if(window.logDetailRequestSeq!==requestSeq||window.activeLogDetailId!==logId||modal.classList.contains('hidden'))return;if(r.status===404){content.innerHTML='<div class="rounded-md border border-red-200 p-4 bg-red-50 text-sm text-red-700">\u65e5\u5fd7\u4e0d\u5b58\u5728\u6216\u5df2\u88ab\u5220\u9664</div>';return}log=await r.json();window.logDetailCache[logId]=log}if(window.logDetailRequestSeq!==requestSeq||window.activeLogDetailId!==logId||modal.classList.contains('hidden'))return;renderLogDetail(log)}catch(e){if(window.logDetailRequestSeq!==requestSeq||window.activeLogDetailId!==logId||modal.classList.contains('hidden'))return;console.error('\u52a0\u8f7d\u65e5\u5fd7\u8be6\u60c5\u5931\u8d25:',e);content.innerHTML=`<div class="rounded-md border border-red-200 p-4 bg-red-50 text-sm text-red-700">\u52a0\u8f7d\u65e5\u5fd7\u8be6\u60c5\u5931\u8d25: ${escapeLogHtml(e.message||'\u672a\u77e5\u9519\u8bef')}</div>`;if(!silent)showToast('\u52a0\u8f7d\u65e5\u5fd7\u8be6\u60c5\u5931\u8d25: '+e.message,'error')}},
|
||||
|
||||
Reference in New Issue
Block a user