fix(*): 请求明细与画布主题 - 区分非计费下载日志并更新默认网格配色

This commit is contained in:
ddCat
2026-09-01 23:54:41 +08:00
parent 1bbccaedc4
commit 58272e683f
14 changed files with 81 additions and 47 deletions

View File

@@ -70,6 +70,8 @@ function LogDetail({ log, querying, onQueryProviderTask }: { log: ApiCallLog; qu
],
["渠道 / 模型", `${log.channelName || "未记录渠道"} / ${log.model || "未识别模型"}`],
["能力", capabilityText(log.capability)],
["请求阶段", requestKindText(log.requestKind)],
["计费属性", log.billable ? "计费调用" : "不计费"],
["总耗时", formatDuration(log.durationMs)],
["视频轮询", log.capability === "video" ? `${log.pollCount || 0} 次` : "--"],
["Token", log.usageAvailable ? `${log.inputTokens} 输入 / ${log.outputTokens} 输出 / ${log.cachedTokens} 缓存` : "未返回"],
@@ -110,12 +112,18 @@ function LogDetail({ log, querying, onQueryProviderTask }: { log: ApiCallLog; qu
}
function billingText(log: ApiCallLog) {
if (!log.billable) return "不计费";
if (!log.billingAvailable) return "未扣积分";
const status = log.billingStatus || "reserved";
const statusLabel = ({ settled: "已结算", refunded: "已退回", uncertain: "待核对", running: "运行中", reserved: "已预授权" } as const)[status];
return `${formatCredits(log.billingAmountMicrocredits)} 积分 · ${statusLabel}`;
}
function requestKindText(value: ApiCallLog["requestKind"]) {
const labels: Partial<Record<ApiCallLog["requestKind"], string>> = { create: "模型生成", poll: "状态查询", download: "结果下载", repair: "结果修复" };
return labels[value] || "上游请求";
}
function PayloadPanel({ value, empty }: { value?: string; empty: string }) {
if (!value) return <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description={empty} />;
return (

View File

@@ -82,7 +82,7 @@ export default function LogsPage() {
},
{ title: "能力", dataIndex: "capability", width: 88, render: capabilityText },
{ title: "结果", width: 118, render: (_, log) => <MediaResult log={log} onPreview={(url, kind) => setMediaPreview({ url, kind, title: `${capabilityText(log.capability)}结果` })} /> },
{ title: "调用状态", width: 160, render: (_, log) => <CallStatus log={log} /> },
{ title: "请求阶段 / 状态", width: 160, render: (_, log) => <CallStatus log={log} /> },
{
title: "错误信息",
width: 260,
@@ -125,7 +125,7 @@ export default function LogsPage() {
return (
<AdminPageFrame
title="请求明细"
description="上游调用与积分计费"
description="模型生成、状态查询与结果下载;仅计费调用扣除积分"
actions={
<AdminExportButton
exportFile={() => exportAdminApiLogs({ keyword: debouncedKeyword || undefined, status: status === "all" ? undefined : status })}
@@ -258,6 +258,7 @@ function formatDuration(value: number) {
}
function BillingSummary({ log }: { log: ApiCallLog }) {
if (!log.billable) return <span className="text-foreground/45">不计费</span>;
if (!log.billingAvailable) return <span className="text-foreground/35">未扣积分</span>;
const status = log.billingStatus || "reserved";
const statusLabel = ({ settled: "已结算", refunded: "已退回", uncertain: "待核对", running: "运行中", reserved: "已预授权" } as const)[status];
@@ -308,8 +309,14 @@ function CallStatus({ log }: { log: ApiCallLog }) {
const failed = log.status === "failed" || ["failed", "cancelled", "expired"].includes(providerStatus || "");
return (
<div>
<div className="mb-1 text-xs font-medium text-foreground/70">{requestKindText(log.requestKind)}</div>
<AdminStatusBadge label={failed ? "失败" : processing ? "处理中" : "成功"} tone={failed ? "error" : processing ? "warning" : "success"} />
{log.capability === "video" ? <div className="mt-1 text-xs tabular-nums text-foreground/45">已轮询 {log.pollCount || 0} 次</div> : null}
</div>
);
}
function requestKindText(value: ApiCallLog["requestKind"]) {
const labels: Partial<Record<ApiCallLog["requestKind"], string>> = { create: "模型生成", poll: "状态查询", download: "结果下载", repair: "结果修复" };
return labels[value] || "上游请求";
}

View File

@@ -12,7 +12,7 @@ import { uploadImage } from "@/services/image-storage";
import { imageMetadata } from "@/lib/canvas/canvas-generation-task-sync";
import copyToClipboard from "copy-to-clipboard";
import { nanoid } from "nanoid";
import { canvasAppearanceBaseTheme, canvasAppearanceForTheme, normalizeCanvasAppearance, resolveCanvasAppearance, writeCanvasAppearanceDefault, type CanvasAppearance } from "@/lib/canvas/canvas-appearance";
import { canvasAppearanceBaseTheme, canvasAppearanceForTheme, DEFAULT_CANVAS_BACKGROUND_MODE, normalizeCanvasAppearance, resolveCanvasAppearance, writeCanvasAppearanceDefault, type CanvasAppearance } from "@/lib/canvas/canvas-appearance";
import { canvasThemes, type CanvasBackgroundMode } from "@/lib/canvas-theme";
import { persistCanvasMediaPerformanceMode, readCanvasMediaPerformanceMode } from "@/lib/canvas/canvas-performance-mode";
import { summarizeCanvasContext } from "@/lib/canvas/canvas-context-summary";
@@ -256,7 +256,7 @@ function InfiniteCanvasPage() {
const [contextMenu, setContextMenu] = useState<ContextMenuState | null>(null);
const [isMiniMapOpen, setIsMiniMapOpen] = useState(false);
const [canvasAppearance, setCanvasAppearance] = useState<CanvasAppearance>(() => canvasAppearanceForTheme(colorTheme));
const [backgroundMode, setBackgroundMode] = useState<CanvasBackgroundMode>("lines");
const [backgroundMode, setBackgroundMode] = useState<CanvasBackgroundMode>(DEFAULT_CANVAS_BACKGROUND_MODE);
const [showImageInfo, setShowImageInfo] = useState(false);
const [canvasTool, setCanvasTool] = useState<CanvasToolMode>("move");
const [mediaPerformanceMode, setMediaPerformanceMode] = useState<CanvasMediaPerformanceMode>(readCanvasMediaPerformanceMode);

View File

@@ -13,7 +13,7 @@ import { InfiniteCanvas } from "@/components/canvas/infinite-canvas";
import { FullScreenLoader } from "@/components/ui/aceternity/full-screen-loader";
import { WorkspaceState } from "@/components/layout/workspace-state";
import { NODE_DEFAULT_SIZE } from "@/constant/canvas";
import { canvasAppearanceBaseTheme, canvasAppearanceForTheme, normalizeCanvasAppearance, resolveCanvasAppearance, type CanvasAppearance } from "@/lib/canvas/canvas-appearance";
import { canvasAppearanceBaseTheme, canvasAppearanceForTheme, DEFAULT_CANVAS_BACKGROUND_MODE, normalizeCanvasAppearance, resolveCanvasAppearance, type CanvasAppearance } from "@/lib/canvas/canvas-appearance";
import { canvasThemes } from "@/lib/canvas-theme";
import { FOLDER_COLLAPSED_HEIGHT, FOLDER_COLLAPSED_WIDTH, isCanvasFolderNode, isFrameNode, isNodeHiddenByCollapsedFrame, resolveFrameConnection } from "@/lib/canvas/canvas-frame";
import { ensureMediaNodeMinimumSize } from "@/lib/canvas/canvas-node-size";
@@ -93,7 +93,7 @@ export default function SharedCanvasPage() {
setAppearance(nextAppearance);
appliedShareTheme = canvasAppearanceBaseTheme(nextAppearance, themeBeforeShare);
useThemeStore.getState().setTheme(appliedShareTheme);
setBackgroundMode(project.backgroundMode || "lines");
setBackgroundMode(project.backgroundMode || DEFAULT_CANVAS_BACKGROUND_MODE);
const initial = project.viewport || { x: 0, y: 0, k: 1 };
viewportRef.current = initial;
setViewport(initial);

View File

@@ -2,7 +2,7 @@ import { startTransition, useCallback, useEffect, useRef, useState, type Dispatc
import { App } from "antd";
import { useNavigate } from "react-router";
import { canvasAppearanceBaseTheme, canvasAppearanceForTheme, normalizeCanvasAppearance, type CanvasAppearance } from "@/lib/canvas/canvas-appearance";
import { canvasAppearanceBaseTheme, canvasAppearanceForTheme, DEFAULT_CANVAS_BACKGROUND_MODE, normalizeCanvasAppearance, type CanvasAppearance } from "@/lib/canvas/canvas-appearance";
import type { CanvasBackgroundMode } from "@/lib/canvas-theme";
import { removeCanvasDrawing } from "@/lib/canvas/canvas-drawing-storage";
import { normalizeCanvasNodeTimestamps } from "@/lib/canvas/canvas-node-timestamps";
@@ -103,7 +103,7 @@ export function useCanvasProjectLifecycle({
chatSessions: restoredSessions,
activeChatId: project.activeChatId || null,
canvasAppearance: restoredAppearance,
backgroundMode: project.backgroundMode || "dots",
backgroundMode: project.backgroundMode || DEFAULT_CANVAS_BACKGROUND_MODE,
showImageInfo: project.showImageInfo || false,
};
nodesRef.current = snapshot.nodes;