diff --git a/src/App.tsx b/src/App.tsx index 8871018..473fcf5 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -42,8 +42,6 @@ const CommunityReviewPage = lazy(() => import("./features/community-review/Commu const AvatarConsolePage = lazy(() => import("./features/digital-human/AvatarConsolePage")); const DigitalHumanPage = lazy(() => import("./features/digital-human/DigitalHumanPage")); const EcommercePage = lazy(() => import("./features/ecommerce/EcommercePage")); -const EcommerceTemplatesPage = lazy(() => import("./features/ecommerce/EcommerceTemplatesPage")); -import type { TemplateCase } from "./features/ecommerce/ecommerceTemplates"; const HomePage = lazy(() => import("./features/home/HomePage")); const ImageWorkbenchPage = lazy(() => import("./features/image-workbench/ImageWorkbenchPage")); const MorePage = lazy(() => import("./features/more/MorePage")); @@ -54,7 +52,6 @@ const ResolutionUpscalePage = lazy(() => import("./features/resolution-upscale/R const WatermarkRemovalPage = lazy(() => import("./features/watermark-removal/WatermarkRemovalPage")); const SubtitleRemovalPage = lazy(() => import("./features/subtitle-removal/SubtitleRemovalPage")); const ScriptTokensPage = lazy(() => import("./features/script-tokens/ScriptTokensPage")); -const SizeTemplatePage = lazy(() => import("./features/size-template/SizeTemplatePage")); const TokenUsagePage = lazy(() => import("./features/script-tokens/TokenUsagePage")); const SettingsPage = lazy(() => import("./features/settings/SettingsPage")); const WorkbenchPage = lazy(() => import("./features/workbench/WorkbenchPage")); @@ -101,7 +98,6 @@ const VIEW_KEYS = new Set([ "assets", "ecommerceHub", "ecommerce", - "ecommerceTemplates", "scriptTokens", "tokenUsage", "settings", @@ -113,14 +109,13 @@ const VIEW_KEYS = new Set([ "avatarConsole", "characterMix", "more", - "sizeTemplate", "communityReview", "communityCaseAdd", "report", "providerHealth", ]); -const PUBLIC_VIEW_SET = new Set(["home", "login", "community", "more", "ecommerceTemplates", "sizeTemplate"]); +const PUBLIC_VIEW_SET = new Set(["home", "login", "community", "more"]); function normalizeViewKey(rawView: string): WebViewKey { const normalized = @@ -312,12 +307,6 @@ function App() { hint: "AI创作与海报生成", icon: , }, - { - key: "sizeTemplate", - label: "示例模板", - hint: "平台比例与导出尺寸", - icon: , - }, { key: "canvas", label: "画布", hint: "进入自由画布编排", icon: }, { key: "community", label: "社区", hint: "案例分享与导入", icon: }, { key: "scriptTokens", label: "剧本评分", hint: "剧本评分系统", icon: }, @@ -380,11 +369,6 @@ function App() { }; }, [showSessionReplacedModal]); - const handleOpenEcommerceTemplate = useCallback((template: TemplateCase) => { - setPendingEcommerceTemplate(template); - handleSetView("ecommerce"); - }, [setPendingEcommerceTemplate, handleSetView]); - const hydrateAccountData = useCallback(async (nextSession: WebUserSession | null) => { setProjectsLoaded(false); if (!nextSession) { @@ -681,11 +665,14 @@ function App() { } canvasAutoOpenedRecentRef.current = true; - void handleOpenProject(projects[0]); + handleOpenProject(projects[0]).catch(() => { + // Reset flag on failure so auto-open can retry on next dependency change + canvasAutoOpenedRecentRef.current = false; + }); }, [ activeView, - canvasWorkflow?.nodes.length, - canvasWorkflow?.source, + canvasWorkflow.nodes.length, + canvasWorkflow.source, currentCanvasProjectId, handleOpenProject, projects, @@ -987,25 +974,6 @@ function App() { }, [activeView, session]); // eslint-disable-line react-hooks/exhaustive-deps const activePage = (() => { - if (!session && !PUBLIC_VIEWS.has(activeView)) { - return ( - handleSetView("workbench")} - onOpenCommunity={() => handleSetView("community")} - onDeleteProject={handleDeleteProject} - /> - ); - } switch (activeView) { case "login": return ( @@ -1049,7 +1017,7 @@ function App() { case "canvas": return ( setPendingEcommerceTemplate(null)} /> ); - case "ecommerceTemplates": - return ( - handleSetView("more")} - onOpenEcommerce={() => handleSetView("ecommerce")} - onSelectTemplate={handleOpenEcommerceTemplate} - onStartCreate={handleStartTemplateCanvasCreate} - onOpenProject={handleOpenProject} - onDeleteProject={handleDeleteProject} - /> - ); case "digitalHuman": return ( ; - case "sizeTemplate": - return ( - handleSetView("more")} - onOpenEcommerce={() => handleSetView("ecommerce")} - onSelectView={handleSetView} - /> - ); case "scriptTokens": return ; case "tokenUsage": diff --git a/src/api/scriptEvalClient.ts b/src/api/scriptEvalClient.ts index e930e79..c4f1dc5 100644 --- a/src/api/scriptEvalClient.ts +++ b/src/api/scriptEvalClient.ts @@ -1,5 +1,3 @@ -import { buildApiUrl, buildAuthHeaders } from "./serverConnection"; - export interface ScriptEvalResult { totalScore: number; grade: string; @@ -10,6 +8,10 @@ export interface ScriptEvalResult { suggestions: string[]; } +const DASHSCOPE_API_KEY = import.meta.env.VITE_DASHSCOPE_API_KEY || ""; +const DASHSCOPE_ENDPOINT = "/dashscope-api/chat/completions"; +const MODEL = "qwen3.7-max"; + const EVAL_SYSTEM_PROMPT = `你是一位资深影视剧本评审专家,拥有二十年以上的编剧、制片和剧本医生经验。你精通各类影视叙事理论(三幕式、英雄之旅、起承转合、序列法),同时深度跟踪AIGC短剧/漫剧行业最新趋势。你的任务是对用户提供的剧本进行严谨、系统、多维度的量化评分。 【剧本类型识别】 @@ -67,31 +69,35 @@ function extractJson(text: string): unknown { } export async function evaluateScript(script: string, signal?: AbortSignal): Promise { - console.log("[API] 发送评测请求,剧本长度:", script.slice(0, 8000).length, "字符"); - const res = await fetch(buildApiUrl("ai/chat"), { + if (!DASHSCOPE_API_KEY) { + throw new Error("DashScope API key 未配置,请在 .env.local 中设置 VITE_DASHSCOPE_API_KEY"); + } + + const res = await fetch(DASHSCOPE_ENDPOINT, { method: "POST", - headers: buildAuthHeaders(), + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${DASHSCOPE_API_KEY}`, + }, body: JSON.stringify({ - model: "qwen3.7-max", + model: MODEL, messages: [ { role: "system", content: EVAL_SYSTEM_PROMPT }, { role: "user", content: `请评测以下剧本:\n\n${script.slice(0, 8000)}` }, ], stream: false, temperature: 0.3, + max_tokens: 4096, }), signal, }); - console.log("[API] 响应状态:", res.status, res.statusText); - if (!res.ok) { - throw new Error(`评测请求失败 (${res.status})`); + const errText = await res.text().catch(() => ""); + throw new Error(`评测请求失败 (${res.status}): ${errText.slice(0, 200)}`); } const payload = await res.json(); - console.log("[API] 原始响应体:", payload); - const content: string = payload?.choices?.[0]?.message?.content ?? payload?.result?.content ?? payload?.content @@ -100,11 +106,7 @@ export async function evaluateScript(script: string, signal?: AbortSignal): Prom if (!content) throw new Error("模型未返回有效内容"); - console.log("[API] 模型返回内容 (前500字符):", content.slice(0, 500)); - const parsed = extractJson(content) as Record; - console.log("[API] 解析后的JSON:", parsed); - const dimensionScores: Record = {}; const rawScores = parsed.dimensionScores as Record | undefined; if (!rawScores || typeof rawScores !== "object") throw new Error("评分格式异常"); @@ -115,7 +117,6 @@ export async function evaluateScript(script: string, signal?: AbortSignal): Prom } const { totalScore, grade } = computeTotalAndGrade(dimensionScores); - console.log("[API] 计算后总分:", totalScore, "等级:", grade); return { totalScore, diff --git a/src/api/serverConnection.ts b/src/api/serverConnection.ts index bf8d9be..6b72197 100644 --- a/src/api/serverConnection.ts +++ b/src/api/serverConnection.ts @@ -234,6 +234,10 @@ function notifySessionExpired(status: number, response: Response, payload: unkno if (/\/auth\//i.test(response.url)) return; // SESSION_REPLACED has its own dedicated handling/modal. if (getPayloadCode(payload) === "SESSION_REPLACED") return; + // If the user never had a session, a 401 is expected — not a session expiry. + if (!readStoredSession()) return; + // Deliberate early-exit for unauthenticated users — not a real auth failure. + if (getPayloadCode(payload) === "NOT_LOGGED_IN") return; const now = Date.now(); if (now - lastSessionExpiredEventAt < 1500) return; @@ -250,6 +254,7 @@ function notifySessionReplaced(status: number, payload: unknown, fallbackMessage const message = getPayloadMessage(payload) || fallbackMessage || "您已在别处登录"; const isSessionReplaced = code === "SESSION_REPLACED" || message.includes("您已在别处登录"); if (!isSessionReplaced || typeof window === "undefined") return; + if (!readStoredSession()) return; const now = Date.now(); if (now - lastSessionReplacedEventAt < 1500) return; diff --git a/src/assets/home-features/home-ecommerce-template-1.png b/src/assets/home-features/home-ecommerce-template-1.png new file mode 100644 index 0000000..aa4091f Binary files /dev/null and b/src/assets/home-features/home-ecommerce-template-1.png differ diff --git a/src/assets/home-features/home-ecommerce-template-2.png b/src/assets/home-features/home-ecommerce-template-2.png new file mode 100644 index 0000000..3ff5165 Binary files /dev/null and b/src/assets/home-features/home-ecommerce-template-2.png differ diff --git a/src/assets/home-features/home-ecommerce-template-3.png b/src/assets/home-features/home-ecommerce-template-3.png new file mode 100644 index 0000000..c620c4d Binary files /dev/null and b/src/assets/home-features/home-ecommerce-template-3.png differ diff --git a/src/components/AppShell.tsx b/src/components/AppShell.tsx index 6178646..48be19d 100644 --- a/src/components/AppShell.tsx +++ b/src/components/AppShell.tsx @@ -3,8 +3,12 @@ import { ArrowUpOutlined, CheckCircleOutlined, FlagOutlined, + InfoCircleOutlined, LoginOutlined, LogoutOutlined, + PhoneOutlined, + SafetyOutlined, + EnvironmentOutlined, PlusCircleOutlined, UserOutlined, WalletOutlined, @@ -61,6 +65,8 @@ function AppShell({ const submenuHideTimerRef = useRef(null); const [profileOpen, setProfileOpen] = useState(false); const [rechargeOpen, setRechargeOpen] = useState(false); + const [infoOpen, setInfoOpen] = useState(false); + const infoRef = useRef(null); const [openSubmenuKey, setOpenSubmenuKey] = useState(null); const prevActiveViewRef = useRef(activeView); const [navJustActivated, setNavJustActivated] = useState(null); @@ -140,6 +146,17 @@ function AppShell({ return () => document.removeEventListener("pointerdown", handlePointerDown); }, [profileOpen]); + useEffect(() => { + if (!infoOpen) return; + const handleInfoOutside = (event: PointerEvent) => { + if (!infoRef.current?.contains(event.target as Node)) { + setInfoOpen(false); + } + }; + document.addEventListener("pointerdown", handleInfoOutside); + return () => document.removeEventListener("pointerdown", handleInfoOutside); + }, [infoOpen]); + useEffect(() => { if (!session) { setProfileOpen(false); @@ -307,6 +324,30 @@ function AppShell({ onMarkAllRead={onMarkAllNotificationsRead} /> )} +
+ + +
+
备案信息
+
苏ICP备2026021747号-1
+
公司地址
+
江苏省南京市江北新区扬子江数字视听产业园9栋A楼501
+
联系电话
+
15155073618
+
+ +
+
) : null} - {!shouldShowEmptyProjectState && recentProjectsOpen ? ( + {(!shouldShowEmptyProjectState || isWaitingForProjects) && recentProjectsOpen ? (