From 7c6129555bf75049ad4943b1c785b3e61b833e14 Mon Sep 17 00:00:00 2001 From: Stringadmin Date: Thu, 4 Jun 2026 01:12:51 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=A4=9A=E4=B8=AA?= =?UTF-8?q?=E8=BF=90=E8=A1=8C=E6=97=B6=E5=B4=A9=E6=BA=83=E5=92=8C=E5=8A=9F?= =?UTF-8?q?=E8=83=BDbug=EF=BC=8C=E4=BC=98=E5=8C=96=E7=94=BB=E5=B8=83?= =?UTF-8?q?=E8=BF=9E=E6=8E=A5=E7=BA=BF=E5=92=8C=E5=89=A7=E6=9C=AC=E8=AF=84?= =?UTF-8?q?=E5=88=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复 EcommercePage generateEcommerceImage 调用不存在变量导致运行时崩溃 - 修复 DigitalHumanPage/ImageWorkbenchPage 变量名错误导致页面不可用 - 修复 ecommerceVideoService token 读取用错 key 导致请求 401 - 修复画布连接线在弹窗出现后仍跟随鼠标的问题 - 剧本评分 .docx 文件改为服务端 mammoth 解析(新增 /api/files/extract-text) - ErrorBoundary 加 key 支持切换页面时自动重置 - Vite proxy 改为指向公网域名 omniai.net.cn - 新增视频生成历史记录面板和删除确认弹窗 Co-Authored-By: Claude Opus 4.7 --- src/App.tsx | 2 +- src/features/canvas/CanvasPage.tsx | 27 +- .../digital-human/DigitalHumanPage.tsx | 4 +- src/features/ecommerce/EcommercePage.tsx | 39 ++- .../ecommerce/EcommerceVideoWorkspace.tsx | 45 ++- .../ecommerce/ecommerceVideoService.ts | 70 +++++ .../ecommerce/panels/EcommerceClonePanel.tsx | 8 + .../panels/EcommerceVideoHistoryPanel.tsx | 185 ++++++++++++ .../image-workbench/ImageWorkbenchPage.tsx | 7 +- .../script-tokens/ScriptTokensPage.tsx | 82 ++---- src/styles/pages/ecommerce-video.css | 266 ++++++++++++++++++ vite.config.ts | 7 +- 12 files changed, 637 insertions(+), 105 deletions(-) create mode 100644 src/features/ecommerce/panels/EcommerceVideoHistoryPanel.tsx diff --git a/src/App.tsx b/src/App.tsx index 8def355..d42a8f4 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1224,7 +1224,7 @@ function App() { onMarkNotificationRead={handleMarkNotificationRead} onMarkAllNotificationsRead={handleMarkAllNotificationsRead} > - +
diff --git a/src/features/canvas/CanvasPage.tsx b/src/features/canvas/CanvasPage.tsx index 68c758c..28b3089 100644 --- a/src/features/canvas/CanvasPage.tsx +++ b/src/features/canvas/CanvasPage.tsx @@ -2646,7 +2646,23 @@ function CanvasPage({ } : null; })() - : null; + : connectionDropMenu + ? (() => { + const source = getNodePortPoint(connectionDropMenu.sourcePort); + const target = getCanvasWorldPointFromClient(connectionDropMenu.originLeft, connectionDropMenu.originTop); + return source + ? { + id: "pending-link-preview", + sourceX: source.x, + sourceY: source.y, + targetX: target.x, + targetY: target.y, + sourceSide: connectionDropMenu.sourcePort.side, + targetSide: null, + } + : null; + })() + : null; const openCanvasAddNodeMenu = useCallback((clientX: number, clientY: number) => { const menuPosition = positionFloatingMenu(clientX, clientY, 260, 390, 0); @@ -2816,6 +2832,8 @@ function CanvasPage({ originTop: event.clientY, sourcePort: connectorDrag.port, }); + setPendingLinkPort(null); + setPendingLinkPreviewPoint(null); } } else { clearPendingConnector(); @@ -2840,7 +2858,7 @@ function CanvasPage({ }, [selectedNode]); const handleCanvasMouseMove = (event: MouseEvent) => { - if (!pendingLinkPort) return; + if (!pendingLinkPort || connectionDropMenu) return; setPendingLinkPreviewPoint(getCanvasWorldPointFromClient(event.clientX, event.clientY)); }; @@ -5534,11 +5552,6 @@ function CanvasPage({ role="menu" onClick={(event) => event.stopPropagation()} onContextMenu={(event) => event.preventDefault()} - onMouseMove={(event) => { - if (pendingLinkPort) { - setPendingLinkPreviewPoint(getCanvasWorldPointFromClient(event.clientX, event.clientY)); - } - }} >
新建节点并连接
) : null} + + setVideoHistoryVisible(false)} + /> ); } diff --git a/src/features/ecommerce/EcommerceVideoWorkspace.tsx b/src/features/ecommerce/EcommerceVideoWorkspace.tsx index 9e7cafe..e476d1a 100644 --- a/src/features/ecommerce/EcommerceVideoWorkspace.tsx +++ b/src/features/ecommerce/EcommerceVideoWorkspace.tsx @@ -4,13 +4,13 @@ import { CopyOutlined, DownloadOutlined, FolderAddOutlined, + HistoryOutlined, LoadingOutlined, - PlayCircleOutlined, ReloadOutlined, SendOutlined, StopOutlined, } from "@ant-design/icons"; -import { runVideoPlan, renderSceneImage, renderScene, buildSceneTasks } from "./ecommerceVideoService"; +import { runVideoPlan, renderSceneImage, renderScene, buildSceneTasks, saveVideoHistory } from "./ecommerceVideoService"; import { PLAN_STEP_LABELS, PLAN_STEPS_DISPLAY, @@ -40,6 +40,8 @@ interface EcommerceVideoWorkspaceProps { durationSeconds: number; resolution: string; onRequestLogin?: () => void; + onOpenHistory?: () => void; + triggerPlan?: number; } const ALL_STEPS: PlanStep[] = [ @@ -101,6 +103,8 @@ export default function EcommerceVideoWorkspace({ durationSeconds, resolution, onRequestLogin, + onOpenHistory, + triggerPlan, }: EcommerceVideoWorkspaceProps) { const [stage, setStage] = useState("idle"); const [planResult, setPlanResult] = useState(null); @@ -160,6 +164,32 @@ export default function EcommerceVideoWorkspace({ } }, [stage, scenes, planResult]); + // ── External trigger: start plan from parent ──────────────── + const triggerPlanPrevRef = useRef(triggerPlan); + useEffect(() => { + if (triggerPlan != null && triggerPlan !== triggerPlanPrevRef.current) { + triggerPlanPrevRef.current = triggerPlan; + void handlePlan(); + } + }, [triggerPlan]); + + // ── Auto-save: persist completed results to server ────────── + const historySavedRef = useRef(false); + useEffect(() => { + if (stage !== "completed") { historySavedRef.current = false; return; } + if (historySavedRef.current) return; + if (!planResult || !scenes.length) return; + historySavedRef.current = true; + const title = planResult.storyboard?.video_title || planResult.summary?.product_name || "电商广告视频"; + saveVideoHistory({ + title, + config: { platform, aspectRatio, durationSeconds, resolution }, + plan: planResult as unknown as Record, + scenes: scenes.map((s) => ({ sceneId: s.sceneId, prompt: s.prompt, imageUrl: s.imageUrl, videoUrl: s.resultUrl })), + sourceImageUrls, + }).catch(() => {}); + }, [stage, planResult, scenes, sourceImageUrls, platform, aspectRatio, durationSeconds, resolution]); + // ── Keep-alive: resume polling for running tasks ────────── useEffect(() => { if (keepalivePollingStartedRef.current) return; @@ -568,6 +598,11 @@ export default function EcommerceVideoWorkspace({
+ {onOpenHistory ? ( + + ) : null} {error ? {error} : null} {stage === "idle" && planProgress && (planProgress.summary || planProgress.creatives || planProgress.storyboard) ? ( ) : null} - {stage !== "planning" && stage !== "imaging" && stage !== "rendering" ? ( - - ) : null} {stage === "planned" || stage === "imaged" ? ( + ) : null} + {cloneOutput === "video-outfit" ? (
diff --git a/src/features/ecommerce/panels/EcommerceVideoHistoryPanel.tsx b/src/features/ecommerce/panels/EcommerceVideoHistoryPanel.tsx new file mode 100644 index 0000000..461ad3f --- /dev/null +++ b/src/features/ecommerce/panels/EcommerceVideoHistoryPanel.tsx @@ -0,0 +1,185 @@ +import { useCallback, useEffect, useState } from "react"; +import { + CloseOutlined, + DeleteOutlined, + ExclamationCircleOutlined, + HistoryOutlined, + LoadingOutlined, + PlayCircleOutlined, +} from "@ant-design/icons"; +import { + fetchVideoHistory, + deleteVideoHistory, + type VideoHistoryItem, +} from "../ecommerceVideoService"; + +interface EcommerceVideoHistoryPanelProps { + visible: boolean; + onClose: () => void; +} + +export default function EcommerceVideoHistoryPanel({ + visible, + onClose, +}: EcommerceVideoHistoryPanelProps) { + const [items, setItems] = useState([]); + const [total, setTotal] = useState(0); + const [loading, setLoading] = useState(false); + const [offset, setOffset] = useState(0); + const [previewMedia, setPreviewMedia] = useState<{ + url: string; + type: "image" | "video"; + } | null>(null); + const [confirmDeleteId, setConfirmDeleteId] = useState(null); + const limit = 10; + + const load = useCallback(async (off: number) => { + setLoading(true); + try { + const res = await fetchVideoHistory(limit, off); + setItems(res.items); + setTotal(res.total); + setOffset(off); + } catch { /* silent */ } + setLoading(false); + }, []); + + useEffect(() => { + if (visible) load(0); + }, [visible, load]); + + const handleDelete = async (id: number) => { + try { + await deleteVideoHistory(id); + setItems((prev) => prev.filter((i) => i.id !== id)); + setTotal((t) => t - 1); + } catch { /* silent */ } + setConfirmDeleteId(null); + }; + + if (!visible) return null; + + const totalPages = Math.ceil(total / limit); + const currentPage = Math.floor(offset / limit) + 1; + + return ( + <> +
+
+ + 生成记录 + +
+ +
+ {loading && !items.length ? ( +
+ + 加载中... +
+ ) : !items.length ? ( +
+ + 暂无生成记录 +
+ ) : ( + items.map((item) => ( +
+
+ + {item.title || "未命名"} + + + {new Date(item.createdAt).toLocaleDateString("zh-CN")} + + +
+
+ {item.scenes.map((scene, idx) => ( +
+ {scene.imageUrl && ( + {`分镜${idx + setPreviewMedia({ url: scene.imageUrl!, type: "image" }) + } + /> + )} + {scene.videoUrl && ( +
+ setPreviewMedia({ url: scene.videoUrl!, type: "video" }) + } + > + +
+ )} +
+ ))} +
+
+ )) + )} +
+ + {totalPages > 1 && ( +
+ + {currentPage}/{totalPages} + +
+ )} +
+ + {confirmDeleteId !== null && ( +
setConfirmDeleteId(null)}> +
e.stopPropagation()}> + +

+ 确定要删除这条记录吗?相关的图片和视频文件也将被永久删除,此操作不可恢复。 +

+
+ + +
+
+
+ )} + + {previewMedia && ( +
setPreviewMedia(null)} + > + + {previewMedia.type === "image" ? ( + preview + ) : ( +
+ )} + + ); +} diff --git a/src/features/image-workbench/ImageWorkbenchPage.tsx b/src/features/image-workbench/ImageWorkbenchPage.tsx index 77122cf..eed76d3 100644 --- a/src/features/image-workbench/ImageWorkbenchPage.tsx +++ b/src/features/image-workbench/ImageWorkbenchPage.tsx @@ -148,22 +148,21 @@ function ImageWorkbenchPage({ initialTool = "workbench", onOpenMore, onSelectVie keepaliveRestoredRef.current = true; const saved = loadToolTaskState("imagewb"); if (!saved || saved.resultUrl) return; - setIsGenerating(true); + setGenerating(true); abortRef.current = false; taskIdRef.current = saved.taskId; void waitForTask(saved.taskId, { onProgress: (e) => { - setTaskProgress(Math.max(0, Math.min(100, Math.trunc(e.progress || 0)))); setStatus(`${e.status} / ${e.progress}%`); if (e.status === "completed" && e.resultUrl) { setResultImages([e.resultUrl]); clearToolTaskState("imagewb"); - setIsGenerating(false); + setGenerating(false); setStatus("恢复任务完成"); } if (e.status === "failed") { clearToolTaskState("imagewb"); - setIsGenerating(false); + setGenerating(false); setStatus("恢复任务失败"); } }, diff --git a/src/features/script-tokens/ScriptTokensPage.tsx b/src/features/script-tokens/ScriptTokensPage.tsx index b9123a5..10e4e6b 100644 --- a/src/features/script-tokens/ScriptTokensPage.tsx +++ b/src/features/script-tokens/ScriptTokensPage.tsx @@ -10,6 +10,7 @@ import { } from "@ant-design/icons"; import { useEffect, useRef, useState, type ChangeEvent, type KeyboardEvent } from "react"; import { evaluateScript } from "../../api/scriptEvalClient"; +import { buildApiUrl, getStoredToken } from "../../api/serverConnection"; import { useSessionStore } from "../../stores"; interface ScoreDimension { @@ -175,61 +176,6 @@ function normalizeUploadedText(raw: string, ext: string): string { return raw; } -async function extractDocxText(bytes: Uint8Array): Promise { - const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength); - const entries: Array<{ name: string; offset: number; size: number; compressed: boolean }> = []; - let pos = 0; - while (pos < bytes.length - 30) { - if (view.getUint32(pos, true) !== 0x04034b50) break; - const compressed = view.getUint16(pos + 10, true) !== 0; - const compressedSize = view.getUint32(pos + 18, true); - const fileNameLen = view.getUint16(pos + 26, true); - const extraLen = view.getUint16(pos + 28, true); - const name = new TextDecoder().decode(bytes.slice(pos + 30, pos + 30 + fileNameLen)); - const dataStart = pos + 30 + fileNameLen + extraLen; - entries.push({ name, offset: dataStart, size: compressedSize, compressed }); - pos = dataStart + compressedSize; - } - const docEntry = entries.find((e) => e.name === "word/document.xml"); - if (!docEntry) return ""; - const xmlBytes = bytes.slice(docEntry.offset, docEntry.offset + docEntry.size); - let xmlText: string; - if (docEntry.compressed) { - try { - const ds = new DecompressionStream("deflate-raw"); - const writer = ds.writable.getWriter(); - writer.write(xmlBytes); - writer.close(); - const reader = ds.readable.getReader(); - const chunks: Uint8Array[] = []; - while (true) { - const { done, value } = await reader.read(); - if (done) break; - chunks.push(value); - } - const totalLen = chunks.reduce((s, c) => s + c.length, 0); - const combined = new Uint8Array(totalLen); - let offset = 0; - for (const c of chunks) { combined.set(c, offset); offset += c.length; } - xmlText = new TextDecoder().decode(combined); - } catch { - xmlText = new TextDecoder().decode(xmlBytes); - } - } else { - xmlText = new TextDecoder().decode(xmlBytes); - } - const textMatches = xmlText.match(/]*>([\s\S]*?)<\/w:t>/g); - if (!textMatches) return ""; - const paraMatches = xmlText.match(/][\s\S]*?<\/w:p>/g); - if (paraMatches) { - return paraMatches.map((p) => { - const tMatches = p.match(/]*>([\s\S]*?)<\/w:t>/g); - if (!tMatches) return ""; - return tMatches.map((m) => m.replace(/<[^>]+>/g, "").replace(/</g, "<").replace(/>/g, ">").replace(/&/g, "&").replace(/"/g, "\"")).join(""); - }).filter(Boolean).join("\n").trim(); - } - return ""; -} function formatFileSize(size: number): string { if (size < 1024) return `${size} B`; @@ -321,22 +267,26 @@ function ScriptTokensPage() { const ext = getFileExtension(file.name); const readable = isReadableTextFile(file, ext); setUploadedFile({ name: file.name, size: file.size }); - if (ext === ".docx") { + if (ext === ".docx" || ext === ".doc") { try { - const bytes = new Uint8Array(await file.arrayBuffer()); - const text = await extractDocxText(bytes); - if (text) { - setScript(text); + const formData = new FormData(); + formData.append("file", file); + const token = getStoredToken(); + const resp = await fetch(buildApiUrl("files/extract-text"), { + method: "POST", + headers: token ? { Authorization: `Bearer ${token}` } : {}, + body: formData, + }); + if (resp.ok) { + const { text } = await resp.json(); + setScript(text || ""); } else { - setScript(`[已上传文件:${file.name}]\n\n无法从 DOCX 文件中提取文本,请尝试另存为 TXT 格式后重新上传。`); + const err = await resp.json().catch(() => ({ error: "解析失败" })); + setScript(`[已上传文件:${file.name}]\n\n${err.error || "文件解析失败,请尝试另存为 TXT 格式后重新上传。"}`); } } catch { - setScript(`[已上传文件:${file.name}]\n\n解析 DOCX 文件失败,请尝试另存为 TXT 格式后重新上传。`); + setScript(`[已上传文件:${file.name}]\n\n文件解析请求失败,请检查网络连接后重试。`); } - } else if (ext === ".doc") { - const text = await decodeTextFile(file); - const cleaned = text.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f]/g, "").replace(/\s{3,}/g, "\n\n").trim(); - setScript(cleaned || `[已上传文件:${file.name}]\n\n无法从 .doc 文件中提取文本,请另存为 .docx 或 .txt 格式。`); } else if (readable) { const text = normalizeUploadedText(await decodeTextFile(file), ext); setScript(text); diff --git a/src/styles/pages/ecommerce-video.css b/src/styles/pages/ecommerce-video.css index 7355f58..715671c 100644 --- a/src/styles/pages/ecommerce-video.css +++ b/src/styles/pages/ecommerce-video.css @@ -1145,3 +1145,269 @@ from { opacity: 0; } to { opacity: 1; } } + +/* ── History panel ──────────────────────────────── */ + +.ecom-video-history-panel { + position: fixed; + top: 0; + right: 0; + z-index: 9000; + display: flex; + flex-direction: column; + width: 420px; + max-width: 90vw; + height: 100vh; + background: #1a1d24; + box-shadow: -4px 0 24px rgba(0, 0, 0, 0.5); + animation: ecom-history-slide-in 0.25s ease-out; +} + +@keyframes ecom-history-slide-in { + from { transform: translateX(100%); } + to { transform: translateX(0); } +} + +.ecom-video-history-panel__header { + display: flex; + align-items: center; + gap: 8px; + padding: 16px 20px; + border-bottom: 1px solid rgba(255, 255, 255, 0.08); + color: #fff; + font-size: 15px; + font-weight: 500; +} + +.ecom-video-history-panel__close { + margin-left: auto; + display: grid; + width: 28px; + height: 28px; + place-items: center; + border: none; + border-radius: 6px; + background: transparent; + color: rgba(255, 255, 255, 0.6); + font-size: 14px; + cursor: pointer; +} + +.ecom-video-history-panel__close:hover { + background: rgba(255, 255, 255, 0.1); + color: #fff; +} + +.ecom-video-history-panel__body { + flex: 1; + overflow-y: auto; + padding: 16px; + display: flex; + flex-direction: column; + gap: 12px; +} + +.ecom-video-history-panel__empty { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 12px; + padding: 60px 20px; + color: rgba(255, 255, 255, 0.4); + font-size: 14px; +} + +.ecom-video-history-card { + border: 1px solid rgba(255, 255, 255, 0.08); + border-radius: 10px; + background: rgba(255, 255, 255, 0.03); + padding: 14px; +} + +.ecom-video-history-card__header { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 10px; +} + +.ecom-video-history-card__title { + color: #fff; + font-size: 13px; + font-weight: 500; + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.ecom-video-history-card__date { + color: rgba(255, 255, 255, 0.4); + font-size: 11px; + white-space: nowrap; +} + +.ecom-video-history-card__delete { + display: grid; + width: 24px; + height: 24px; + place-items: center; + border: none; + border-radius: 4px; + background: transparent; + color: rgba(255, 255, 255, 0.35); + font-size: 12px; + cursor: pointer; +} + +.ecom-video-history-card__delete:hover { + background: rgba(255, 80, 80, 0.15); + color: #ff5050; +} + +.ecom-video-history-card__scenes { + display: flex; + gap: 8px; + overflow-x: auto; + padding-bottom: 4px; +} + +.ecom-video-history-card__scene { + position: relative; + flex-shrink: 0; + width: 80px; + height: 60px; + border-radius: 6px; + overflow: hidden; + background: rgba(255, 255, 255, 0.05); +} + +.ecom-video-history-card__scene img { + width: 100%; + height: 100%; + object-fit: cover; + cursor: pointer; + transition: opacity 0.15s; +} + +.ecom-video-history-card__scene img:hover { + opacity: 0.8; +} + +.ecom-video-history-card__video-thumb { + position: absolute; + inset: 0; + display: grid; + place-items: center; + background: rgba(0, 0, 0, 0.4); + color: #fff; + font-size: 20px; + cursor: pointer; + transition: background 0.15s; +} + +.ecom-video-history-card__video-thumb:hover { + background: rgba(0, 0, 0, 0.2); +} + +.ecom-video-history-panel__pager { + display: flex; + align-items: center; + justify-content: center; + gap: 12px; + padding: 12px 20px; + border-top: 1px solid rgba(255, 255, 255, 0.08); + color: rgba(255, 255, 255, 0.6); + font-size: 12px; +} + +.ecom-video-history-panel__pager button { + padding: 4px 10px; + border: 1px solid rgba(255, 255, 255, 0.15); + border-radius: 4px; + background: transparent; + color: rgba(255, 255, 255, 0.7); + font-size: 12px; + cursor: pointer; +} + +.ecom-video-history-panel__pager button:hover:not(:disabled) { + background: rgba(255, 255, 255, 0.1); + color: #fff; +} + +.ecom-video-history-panel__pager button:disabled { + opacity: 0.3; + cursor: not-allowed; +} + +/* ── Delete confirmation dialog ─────────────────── */ + +.ecom-video-confirm-dialog-backdrop { + position: fixed; + inset: 0; + z-index: 9999; + display: grid; + place-items: center; + background: rgba(0, 0, 0, 0.6); + backdrop-filter: blur(4px); +} + +.ecom-video-confirm-dialog { + display: flex; + flex-direction: column; + align-items: center; + gap: 16px; + padding: 28px 32px; + border-radius: 12px; + background: #1e2128; + border: 1px solid rgba(255, 255, 255, 0.1); + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5); + max-width: 340px; + text-align: center; +} + +.ecom-video-confirm-dialog__icon { + font-size: 36px; + color: #faad14; +} + +.ecom-video-confirm-dialog__text { + margin: 0; + font-size: 14px; + line-height: 1.6; + color: rgba(255, 255, 255, 0.85); +} + +.ecom-video-confirm-dialog__actions { + display: flex; + gap: 12px; + margin-top: 4px; +} + +.ecom-video-confirm-dialog__actions button { + padding: 6px 20px; + border: 1px solid rgba(255, 255, 255, 0.15); + border-radius: 6px; + background: transparent; + color: rgba(255, 255, 255, 0.8); + font-size: 13px; + cursor: pointer; + transition: background 0.15s, border-color 0.15s; +} + +.ecom-video-confirm-dialog__actions button:hover { + background: rgba(255, 255, 255, 0.08); +} + +.ecom-video-confirm-dialog__actions button.is-danger { + background: #ff4d4f; + border-color: #ff4d4f; + color: #fff; +} + +.ecom-video-confirm-dialog__actions button.is-danger:hover { + background: #ff7875; + border-color: #ff7875; +} diff --git a/vite.config.ts b/vite.config.ts index 2b114a6..eabda6e 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -11,17 +11,16 @@ export default defineConfig(({ mode }) => { compression({ algorithms: ["gzip", "brotliCompress"], threshold: 1024 }), ], server: { - port: 5174, + port: 5173, host: "127.0.0.1", proxy: { "/api": { - target: env.VITE_DEV_PROXY || "http://47.110.225.76:3600", + target: env.VITE_DEV_PROXY || "https://omniai.net.cn", changeOrigin: true, }, "/dashscope-api": { - target: "https://dashscope.aliyuncs.com", + target: "https://omniai.net.cn", changeOrigin: true, - rewrite: (path) => path.replace(/^\/dashscope-api/, "/compatible-mode/v1"), }, }, },