import React, { useEffect, useMemo, useRef, useState } from "react";
import {
AlertTriangle,
Archive,
AudioLines,
Bell,
Bot,
BookOpen,
Building2,
CheckCircle2,
Clapperboard,
Coins,
Copy,
ChevronRight,
Download,
FileJson2,
Film,
FolderKanban,
Gauge,
HardDrive,
Images,
KeyRound,
Layers3,
Link2,
ListChecks,
ListPlus,
LogOut,
Mail,
Menu,
Mic2,
Network,
Palette,
Plus,
Play,
RadioTower,
RefreshCw,
Save,
Scale,
Search,
Settings2,
ShieldCheck,
Sparkles,
TimerReset,
UserPlus,
Users,
Volume2,
Wand2,
XCircle
} from "lucide-react";
import { adapters, marketBenchmarks, workflowTemplates } from "./data/sampleProject";
import { createProjectShell, emptyProject } from "./data/projectShell";
import { ScenePreview } from "./components/ScenePreview";
import {
AdminAuditPage,
AdminModelsPage,
AdminOverviewPage,
AdminPolicyCenterPage,
AdminStyleKitsPage,
AdminQueuePage,
AdminUsagePage,
AccountSecurityPage,
CreatorHomePage,
IdentityCenterPage,
LoginPage,
NotificationBell,
NotificationCenterPage,
TaskCenterPage,
RegisterPage,
SystemSettingsPage,
SystemUsersPage
} from "./components/EnterprisePages";
import { DeliveryPortalPage } from "./components/DeliveryPortalPage";
import {
AssetLibraryPage,
BatchProductionPage,
ProjectAssistantPage,
VoiceStudioPage
} from "./components/CreatorSuitePages";
import { KnowledgeLibraryPage } from "./components/KnowledgeLibraryPage";
import {
CastingBoardPage,
DeliveryAccessPage,
DeliveryOpsPage,
DirectorProductionPage,
GenerationQueuePage,
ProjectFactoryPage,
QaReviewPage,
ScriptProductionPage,
SeriesBiblePage
} from "./components/ProductionPages";
import { addProjectMember, changeProjectLifecycle, completeMfaEnrollment, createJob, createOrganization, createProject, createWorkspace, fetchAuthSession, fetchPlatformContext, fetchProject, getAuthSession, getClientContext, inviteMember, login, loginWithMfa, logout, redeemSsoTicket, resendOrganizationInvitation, revokeOrganizationInvitation, searchPlatform, startMfaEnrollment, updateOrganization, updateOrganizationMember, updateOrganizationRolePolicy, updateProject, updateProjectMember, updateWorkspace, updateWorkspaceMember } from "./lib/api";
import { buildAllExports, buildModelRequest, downloadJson } from "./lib/exporters";
import { projectQa } from "./lib/qa";
import { getPlatformSummary, platformData } from "./platform/platformData";
const tabs = [
{ id: "overview", label: "生产总控", icon: RadioTower },
{ id: "factory", label: "项目工厂", icon: Layers3 },
{ id: "script", label: "剧本拆解", icon: BookOpen },
{ id: "casting", label: "资产选角", icon: ShieldCheck },
{ id: "director", label: "导演工作台", icon: Clapperboard },
{ id: "jobs", label: "生成队列", icon: Wand2 },
{ id: "modelops", label: "模型中台", icon: Network },
{ id: "qa", label: "审片中心", icon: ListChecks },
{ id: "compliance", label: "成本合规", icon: Scale },
{ id: "export", label: "交付运营", icon: Download },
{ id: "admin", label: "平台管理", icon: KeyRound }
];
const navigationGroups = [
{
label: "创作套件",
items: [
{ id: "creator-home", label: "我的工作台", icon: RadioTower },
{ id: "tasks", label: "协作任务", icon: ListChecks, requiredPermission: "task:view" },
{ id: "assistant", label: "项目 AI 助手", icon: Bot },
{ id: "knowledge", label: "内容知识库", icon: FileJson2, requiredPermission: "script:read" },
{ id: "asset-library", label: "资产库", icon: Images, requiredAnyPermissions: ["asset:edit", "voice:edit", "compliance:manage"] },
{ id: "voice-studio", label: "声音与字幕", icon: AudioLines, requiredPermission: "voice:edit" },
{ id: "batch-production", label: "批量生产", icon: ListPlus, requiredPermission: "job:create" }
]
},
{
label: "账户",
items: [
{ id: "account", label: "账号与安全", icon: KeyRound },
{ id: "notifications", label: "通知中心", icon: Bell }
]
},
{
label: "生产流水线",
items: [
{ id: "overview", label: "生产总控", icon: RadioTower, requiredPermission: "usage:view" },
{ id: "factory", label: "项目工厂", icon: Layers3, requiredPermission: "project:create" },
{ id: "script", label: "剧本拆解", icon: BookOpen, requiredPermission: "script:edit" },
{ id: "casting", label: "资产与选角", icon: ShieldCheck, requiredAnyPermissions: ["asset:edit", "compliance:manage"] },
{ id: "director", label: "导演工作台", icon: Clapperboard, requiredPermission: "job:create" },
{ id: "jobs", label: "生成任务", icon: Wand2, requiredPermission: "job:create" },
{ id: "bible", label: "系列 Bible", icon: BookOpen, requiredPermission: "script:edit" },
{ id: "locks", label: "连续性锁", icon: ShieldCheck, requiredAnyPermissions: ["asset:edit", "compliance:manage"] },
{ id: "shots", label: "镜头清单", icon: Clapperboard, requiredPermission: "job:create" },
{ id: "modelops", label: "模型中台", icon: Network, requiredAnyPermissions: ["model:manage", "model:approve"] },
{ id: "qa", label: "审片中心", icon: ListChecks, requiredPermission: "qa:review" },
{ id: "compliance", label: "成本合规", icon: Scale, requiredPermission: "usage:view" },
{ id: "export", label: "交付运营", icon: Download, requiredPermission: "delivery:view" },
{ id: "delivery-portal", label: "客户交付门户", icon: Link2, requiredPermission: "delivery:view" }
]
},
{
label: "管理员后台",
items: [
{ id: "admin-overview", label: "管理概览", icon: Gauge, requiredPermission: "usage:view" },
{ id: "admin-organizations", label: "组织与工作区", icon: Building2, requiredPermission: "organization:manage" },
{ id: "admin-members", label: "用户与权限", icon: Users, requiredPermission: "organization:members:invite" },
{ id: "admin-policies", label: "组织策略", icon: ShieldCheck, requiredAnyPermissions: ["policy:read", "policy:manage", "organization:manage"] },
{ id: "admin-style-kits", label: "风格标准", icon: Palette, requiredAnyPermissions: ["style:read", "style:manage"] },
{ id: "admin-models", label: "模型与 Runner", icon: Network, requiredAnyPermissions: ["model:manage", "model:approve"] },
{ id: "admin-queue", label: "队列与任务", icon: Wand2, requiredPermission: "queue:manage" },
{ id: "admin-usage", label: "用量与成本", icon: Coins, requiredPermission: "usage:view" },
{ id: "admin-audit", label: "审计与合规", icon: ListChecks, requiredPermission: "audit:view" }
]
},
{
label: "系统设置",
items: [
{ id: "system-overview", label: "系统总览", icon: Settings2, requiredPermission: "system:settings:view" },
{ id: "system-identity", label: "企业身份", icon: KeyRound, requiredPermission: "system:settings:view" },
{ id: "system-users", label: "全局用户目录", icon: Users, requiredPermission: "system:settings:view" },
{ id: "system-generation", label: "生成策略", icon: ShieldCheck, requiredPermission: "system:settings:view" },
{ id: "system-storage", label: "存储与队列", icon: HardDrive, requiredPermission: "system:settings:view" },
{ id: "system-notifications", label: "通知与 API", icon: Mail, requiredPermission: "system:settings:view" },
{ id: "system-features", label: "功能开关", icon: Sparkles, requiredPermission: "feature_flag:manage" }
]
}
];
const navigationTabIds = new Set(navigationGroups.flatMap((group) => group.items.map((item) => item.id)));
function tabFromLocation() {
if (typeof window === "undefined") return "creator-home";
const candidate = window.location.hash.replace(/^#/, "");
return navigationTabIds.has(candidate) ? candidate : "creator-home";
}
function inviteTokenFromLocation() {
if (typeof window === "undefined") return "";
return new URLSearchParams(window.location.search).get("invite") || "";
}
function portalTokenFromLocation() {
if (typeof window === "undefined") return "";
const match = window.location.pathname.match(/^\/portal\/([^/]+)\/?$/);
return match ? decodeURIComponent(match[1]) : "";
}
function ssoTicketFromLocation() {
if (typeof window === "undefined") return "";
return new URLSearchParams(window.location.search).get("sso_ticket") || "";
}
function ssoErrorFromLocation() {
if (typeof window === "undefined") return "";
const params = new URLSearchParams(window.location.search);
const code = params.get("sso_error");
if (!code) return "";
return params.get("sso_error_description") || `企业 SSO 登录失败:${code}`;
}
function normalizeAdapter(adapter) {
return {
...adapter,
kind: adapter.kind || adapter.protocol || "http-json",
baseUrl: adapter.baseUrl || adapter.endpoint || "",
enabled: adapter.enabled ?? (adapter.status === "ready"),
status: adapter.status || (adapter.enabled ? "ready" : "planned"),
costMode: adapter.costMode || adapter.cost_mode || "local",
approvalRequired: adapter.approvalRequired ?? Boolean(adapter.approval_required),
capability: adapter.capability || adapter.capabilities || []
};
}
function adapterCatalogForContext(platformContext) {
const registry = platformContext?.platform?.adapterCatalog || platformContext?.platform?.modelRegistry;
if (Array.isArray(registry) && registry.length) return registry.map(normalizeAdapter);
return adapters.map(normalizeAdapter);
}
function preferredAdapterId(catalog, requestedId) {
if (requestedId && catalog.some((adapter) => adapter.id === requestedId)) return requestedId;
const imageAdapter = catalog.find((adapter) => (adapter.capability || []).some((item) => /image-to-video|i2v|video/i.test(item)));
return imageAdapter?.id || catalog.find((adapter) => adapter.status === "ready")?.id || catalog[0]?.id || "owned-model-platform";
}
function StatusPill({ tone = "neutral", children }) {
return {children};
}
function SectionTitle({ icon: Icon, title, action }) {
return (
);
}
function Metric({ label, value, icon: Icon }) {
return (
{label}
{value}
);
}
function ProgressBar({ value, tone = "ok" }) {
return (
);
}
function Cockpit({ project, qaResults, setActiveTab, setActiveShotId, platformContext, canCreateJob, canApproveDelivery }) {
const platform = platformContext?.platform || platformData;
const platformSummary = platformContext?.summary || getPlatformSummary();
const organization = platform.organization || platformData.workspace;
const plan = platform.billing || platform.plan;
const totalDuration = project.shots.reduce((sum, shot) => sum + shot.durationSec, 0);
const blockedItems = project.pipeline.filter((item) => item.status === "blocked").length;
return (
LOCAL AI DRAMA OPS
{project.series.title} · {project.episode.title}
{organization.name || "私有化内容组织"}:把剧本、资产、模型、队列、审片、成本、合规和交付纳入同一个私有生产系统。
{canCreateJob &&
}
{canApproveDelivery &&
}
商用准备度
{project.production.commercialReadiness}%
{plan ? `${plan.plan_name || plan.name} · ${plan.seat_limit || plan.seats} seats · ${plan.storage_gb || plan.storageGb}GB storage · 外部云连接需显式审批。` : "当前角色无计费数据查看权限。"}
生产流水线
{blockedItems ? `${blockedItems} 个阻塞` : "运行中"}
{project.pipeline.map((item) => {
const target = item.id === "script" ? "script" : item.id === "assets" ? "casting" : item.id === "qa" ? "qa" : item.id === "edit" ? "export" : "jobs";
return (
);})}
镜头生产状态
{project.shots.length} shots / {totalDuration}s
{project.shots.map((shot) => {
const qa = qaResults.find((item) => item.shotId === shot.id);
return (
);
})}
生成队列
local-only
任务
镜头
适配器
成本策略
状态
{project.productionJobs.map((job) => (
))}
QA 证据
需要真实产物回填
{project.qaEvidence.map((item) => (
{item.result === "pass" ?
:
}
{item.label}
{item.evidence}
))}
交付物
{project.deliverables.filter((item) => item.status === "ready").length}/{project.deliverables.length}
{project.deliverables.map((item) => (
{item.name}
{item.path}
{item.status}
))}
市面商业平台对标
已转成本地版工作流
{(platform.researchMatrix || platformSummary.researchMatrix || []).map((item) => (
{item.category}
{item.commercialNeed}
{item.localBuild}
))}
);
}
function ProjectFactory({ setActiveTab, platformContext }) {
const platform = platformContext?.platform || platformData;
const projects = platform.projects || platformData.projects;
return (
商业项目入口
{["原创 AI 漫剧", "小说解说漫", "动态漫拆层", "仿真人短剧审慎模式", "平台 Agent 流水线"].map((name) => (
))}
项目资产空间
{platformData.assetStorage.buckets.map((bucket) => (
{bucket.label}
{bucket.fileCount} files
{bucket.policy}
))}
项目列表
项目类型阶段准备度风险更新
{projects.map((project) => (
))}
);
}
function ScriptStudio({ project, canEditAssets }) {
return (
长文本导入
{project.scriptStudio.sourceType} · {project.scriptStudio.importLimit}
{canEditAssets && }
章节拆分
{project.scriptStudio.chapters.map((chapter) => (
{chapter.title}
{chapter.words} 字 · {chapter.scenes} 场 · {chapter.status}
))}
资产抽取
{project.scriptStudio.extracted.map((item) => (
{item.type}
{item.name}
{Math.round(item.confidence * 100)}%
{item.target}
))}
风格设定
{project.scriptStudio.stylePreset}
{project.scriptStudio.splitRule}
);
}
function CastingBoard({ project }) {
return (
{project.characters.map((character) => (
{character.name.slice(0, 1)}
{character.casting.turnarounds.map((item) => {item})}
{character.name} · {character.role}
{character.visualLock}
{character.casting.wardrobe.map((item) => {item})}
- 声线
- {character.voiceLock.voiceId}
- 复用
- {character.casting.reuseScope}
))}
场景库
{project.locations.map((location) => (
{location.name}
{location.weather}
{location.cameraLock}
))}
道具库
{project.props.map((prop) => (
))}
);
}
function ModelOps({ platformContext }) {
const platform = platformContext?.platform || platformData;
const models = platform.modelRegistry || platformData.modelRegistry;
const runners = platform.runnerHealth || platformData.runnerHealth;
return (
模型注册表
模型能力Endpoint成本审批状态
{models.map((model) => (
))}
Runner Health
{runners.map((runner) => (
{runner.name}
{runner.status}
queue {runner.queueDepth} · {runner.lastHeartbeat}
))}
);
}
function ComplianceCenter({ platformContext }) {
const platform = platformContext?.platform || platformData;
const plan = platform.billing || platform.plan || platformData.plan;
const costCenters = platform.costCenters || platformData.costCenters;
const policies = platform.compliancePolicies || platformData.compliancePolicies;
return (
套餐与额度
- 套餐
- {plan.plan_name || plan.name}
- Seats
- {plan.seat_limit || plan.seats}
- 存储
- {plan.storage_gb || plan.storageGb} GB
- 片段额度
- {plan.monthly_clip_quota || plan.monthlyClipQuota} clips/月
- 云连接
- {plan.cloud_connectors_require_approval ?? plan.cloudConnectorsRequireApproval ? "必须审批" : "可用"}
成本中心
{costCenters.map((cost) => (
{cost.label}
{cost.used}/{cost.monthBudget} {cost.unit}
))}
合规策略
{policies.map((policy) => (
{policy.label}
{policy.severity}
{policy.status}
))}
);
}
function AdminCenter({ platformContext, onRefresh, onSwitchOrganization, onInvite, onResendInvitation, onRevokeInvitation, onCreateOrganization, onCreateWorkspace, onCreateProject, onUpdateOrganization, onUpdateWorkspace, onUpdateProject, onUpdateOrganizationMember, onUpdateOrganizationRolePolicy, onUpdateWorkspaceMember, onUpdateProjectMember, onAddProjectMember }) {
const platform = platformContext?.platform || platformData;
const context = platformContext?.context;
const organizations = platform.organizations || [{ id: "ws-local-aidrama", name: platform.workspace?.name || "本地工作区", role_name: "平台管理员", workspace_count: 1 }];
const workspaces = platform.workspaces || [];
const projects = platform.projects || platformData.projects;
const members = platform.members || platformData.members;
const workspaceMembers = platform.workspaceMembers || [];
const projectMembers = platform.projectMembers || [];
const invitations = platform.invitations || [];
const auditLog = platform.auditLog || platformData.auditLog;
const rolePolicies = platform.rolePolicies || { roles: [], permissions: [] };
const permissionMatrix = platformContext?.rolePermissions?.length
? platformContext.rolePermissions.map((role) => ({ role: role.name, permissions: role.permissions || [] }))
: platformData.permissionMatrix;
const billing = platform.billing || platform.plan || platformData.plan;
const usage = platform.usage;
const [inviteEmail, setInviteEmail] = useState("");
const [inviteRole, setInviteRole] = useState("writer");
const [inviteScope, setInviteScope] = useState(workspaces.length ? "workspace" : "organization");
const [inviteWorkspaceId, setInviteWorkspaceId] = useState(workspaces[0]?.id || "");
const [inviteProjectId, setInviteProjectId] = useState("");
const [organizationName, setOrganizationName] = useState("");
const [workspaceName, setWorkspaceName] = useState("");
const [projectName, setProjectName] = useState("");
const [notice, setNotice] = useState("");
const [memberDrafts, setMemberDrafts] = useState({});
const [projectMemberDrafts, setProjectMemberDrafts] = useState({});
const [workspaceMemberDrafts, setWorkspaceMemberDrafts] = useState({});
const [organizationDraft, setOrganizationDraft] = useState({ name: "", slug: "" });
const [workspaceDraft, setWorkspaceDraft] = useState({ name: "", slug: "", description: "", status: "active" });
const [projectDraft, setProjectDraft] = useState({ name: "", type: "AI 漫剧", status: "production", visibility: "workspace", readiness: 0, risk: "low" });
const [newProjectMemberId, setNewProjectMemberId] = useState("");
const [selectedPolicyRoleKey, setSelectedPolicyRoleKey] = useState("");
const [policySearch, setPolicySearch] = useState("");
const [inviteLink, setInviteLink] = useState("");
const [inviteActionId, setInviteActionId] = useState("");
const inviteProjects = projects.filter((item) => !inviteWorkspaceId || item.workspace_id === inviteWorkspaceId || item.workspaceId === inviteWorkspaceId);
const inviteRoleOptions = inviteScope === "organization"
? [["org_member", "组织成员"], ["org_admin", "组织管理员"]]
: inviteScope === "project"
? [["project_editor", "项目编辑"], ["project_viewer", "项目查看者"]]
: [["writer", "编剧"], ["producer", "制片"], ["art_director", "资产美术"], ["voice_editor", "配音/字幕"], ["reviewer", "审片"]];
useEffect(() => {
setMemberDrafts(Object.fromEntries(members.map((member) => [member.user_id || member.id, { roleKey: member.role_key || "org_member", status: member.status || "active" }])));
}, [platform.members]);
useEffect(() => {
setProjectMemberDrafts(Object.fromEntries(projectMembers.map((member) => [member.user_id || member.id, { roleKey: member.role_key || "project_viewer", status: member.status || "active" }])));
}, [platform.projectMembers]);
useEffect(() => {
const organization = context?.currentOrganization;
const workspace = context?.currentWorkspace;
const project = context?.currentProject;
setOrganizationDraft({ name: organization?.name || "", slug: organization?.slug || "" });
setWorkspaceDraft({ name: workspace?.name || "", slug: workspace?.slug || "", description: workspace?.description || "", status: workspace?.status || "active" });
setProjectDraft({ name: project?.name || "", type: project?.type || "AI 漫剧", status: project?.status || "production", visibility: project?.visibility || "workspace", readiness: project?.readiness ?? 0, risk: project?.risk || "low" });
}, [context?.currentOrganization?.id, context?.currentWorkspace?.id, context?.currentProject?.id]);
useEffect(() => {
setWorkspaceMemberDrafts(Object.fromEntries((platform.workspaceMembers || []).map((member) => [member.user_id || member.id, { roleKey: member.role_key || "producer", accessMode: member.access_mode || "all", status: member.status || "active" }])));
}, [platform.workspaceMembers]);
useEffect(() => {
if (!workspaces.some((workspace) => workspace.id === inviteWorkspaceId)) setInviteWorkspaceId(workspaces[0]?.id || "");
}, [platform.workspaces]);
useEffect(() => {
if (!inviteProjects.some((project) => project.id === inviteProjectId)) setInviteProjectId(inviteProjects[0]?.id || "");
}, [platform.projects, inviteWorkspaceId]);
useEffect(() => {
const availableRole = rolePolicies.roles.find((role) => role.key === selectedPolicyRoleKey) || rolePolicies.roles[0];
if (availableRole && availableRole.key !== selectedPolicyRoleKey) setSelectedPolicyRoleKey(availableRole.key);
if (!rolePolicies.roles.length) setSelectedPolicyRoleKey("");
}, [rolePolicies.roles, selectedPolicyRoleKey]);
async function runAction(action, successMessage) {
try {
const result = await action();
setNotice(successMessage);
return result;
} catch (error) {
setNotice(error.message);
return null;
}
}
function changeInviteScope(scope) {
setInviteScope(scope);
if (scope === "organization") setInviteRole("org_member");
else if (scope === "project") setInviteRole("project_editor");
else setInviteRole("writer");
}
function inviteUrl(invitation) {
if (!invitation) return "";
const path = invitation.acceptUrl || (invitation.inviteToken ? `/register?invite=${encodeURIComponent(invitation.inviteToken)}` : "");
if (!path) return "";
if (typeof window === "undefined") return path;
return new URL(path, window.location.origin).toString();
}
const handleInvite = async (event) => {
event.preventDefault();
if (inviteScope !== "organization" && !inviteWorkspaceId) {
setNotice("工作区级或项目级邀请必须选择工作区");
return;
}
if (inviteScope === "project" && !inviteProjectId) {
setNotice("项目级邀请必须选择项目");
return;
}
const body = { email: inviteEmail, roleKey: inviteRole };
if (inviteScope !== "organization") body.workspaceId = inviteWorkspaceId;
if (inviteScope === "project") body.projectId = inviteProjectId;
const result = await runAction(() => onInvite(body), "邀请已创建;注册链接仅在当前管理会话中显示");
if (result?.invitation) {
setInviteLink(inviteUrl(result.invitation));
setInviteEmail("");
}
};
async function handleResendInvitation(invitation) {
setInviteActionId(invitation.id);
try {
const result = await runAction(() => onResendInvitation?.(invitation.id), "邀请已重新发送;旧注册链接已立即失效");
if (result?.invitation) setInviteLink(inviteUrl(result.invitation));
} finally {
setInviteActionId("");
}
}
async function handleRevokeInvitation(invitation) {
if (typeof window !== "undefined" && !window.confirm(`确定撤销发给 ${invitation.email} 的邀请吗?撤销后注册链接立即失效,席位会释放。`)) return;
setInviteActionId(invitation.id);
try {
const result = await runAction(() => onRevokeInvitation?.(invitation.id), "邀请已撤销,预留席位已释放");
if (result?.invitation) setInviteLink("");
} finally {
setInviteActionId("");
}
}
async function copyInviteLink() {
if (!inviteLink) return;
try {
if (navigator.clipboard?.writeText) {
await navigator.clipboard.writeText(inviteLink);
} else {
const input = document.querySelector("[data-invite-link]");
input?.select();
document.execCommand("copy");
}
setNotice("注册链接已复制");
} catch {
setNotice("浏览器未授权复制,请手动选择并复制注册链接");
}
}
const handleWorkspace = (event) => {
event.preventDefault();
runAction(() => onCreateWorkspace({ name: workspaceName }), "工作区已创建");
setWorkspaceName("");
};
const handleOrganization = (event) => {
event.preventDefault();
runAction(() => onCreateOrganization({ name: organizationName }), "组织已创建并切换");
setOrganizationName("");
};
const handleProject = (event) => {
event.preventDefault();
runAction(() => onCreateProject({ name: projectName, type: "AI 漫剧" }), "项目已创建");
setProjectName("");
};
function updateMemberDraft(userId, field, value) {
setMemberDrafts((current) => ({ ...current, [userId]: { ...(current[userId] || {}), [field]: value } }));
}
function updateProjectMemberDraft(userId, field, value) {
setProjectMemberDrafts((current) => ({ ...current, [userId]: { ...(current[userId] || {}), [field]: value } }));
}
function saveMember(member) {
const userId = member.user_id || member.id;
const draft = memberDrafts[userId];
runAction(() => onUpdateOrganizationMember(userId, draft), `${member.display_name || member.name} 的组织角色已更新`);
}
function saveProjectMember(member) {
const userId = member.user_id || member.id;
const draft = projectMemberDrafts[userId];
runAction(() => onUpdateProjectMember(userId, draft), `${member.display_name} 的项目角色已更新`);
}
function saveWorkspaceMember(member) {
const userId = member.user_id || member.id;
const draft = workspaceMemberDrafts[userId];
runAction(() => onUpdateWorkspaceMember?.(userId, draft), `${member.display_name} 的工作区角色已更新`);
}
function saveOrganizationSettings(event) {
event.preventDefault();
runAction(() => onUpdateOrganization?.(organizationDraft), "组织设置已更新");
}
function saveWorkspaceSettings(event) {
event.preventDefault();
runAction(() => onUpdateWorkspace?.(workspaceDraft), "工作区设置已更新");
}
function saveProjectSettings(event) {
event.preventDefault();
runAction(() => onUpdateProject?.(projectDraft), "项目设置已更新");
}
function addProjectMember(event) {
event.preventDefault();
if (!newProjectMemberId) return;
runAction(() => onAddProjectMember?.(newProjectMemberId, { roleKey: "project_editor" }), "项目成员已加入");
setNewProjectMemberId("");
}
const selectedPolicyRole = rolePolicies.roles.find((role) => role.key === selectedPolicyRoleKey) || rolePolicies.roles[0] || null;
const normalizedPolicySearch = policySearch.trim().toLowerCase();
const visiblePolicyPermissions = rolePolicies.permissions.filter((permission) => {
if (!normalizedPolicySearch) return true;
return `${permission.key} ${permission.description}`.toLowerCase().includes(normalizedPolicySearch);
});
const policyOverrides = new Map((selectedPolicyRole?.overrides || []).map((override) => [override.permissionKey, override]));
const canManageRolePolicies = Boolean(context?.permissions?.includes("organization:roles:manage"));
function savePolicyPermission(permission, enabled) {
if (!selectedPolicyRole || !onUpdateOrganizationRolePolicy) return;
runAction(
() => onUpdateOrganizationRolePolicy(selectedPolicyRole.key, { permissionKey: permission.key, enabled }),
`${selectedPolicyRole.name} · ${permission.description} 已${enabled ? "启用" : "撤销"}`
);
}
return (
刷新租户数据}
/>
当前组织
{context?.currentOrganization?.name || platform.workspace?.name || "本地组织"}
{context?.currentOrganization?.slug || "private-local"} · {context?.organizationRole || "platform-admin"}
{organizations.length}可访问组织
{workspaces.length}当前组织工作区
{members.length}组织成员
{context?.permissions?.length || permissionMatrix[0].permissions.length}当前权限
{notice && {notice}
}
TENANT DIRECTORY
组织与工作区
scope enforced
{organizations.map((organization) => (
))}
{workspaces.map((workspace) => {workspace.name}{workspace.projectCount || 0} 项目)}
TENANT SETTINGS
组织、工作区与项目设置
{members.map((member) => {
const name = member.display_name || member.name;
const role = member.role_name || member.role;
const status = member.status || "active";
const userId = member.user_id || member.id;
const draft = memberDrafts[userId] || { roleKey: member.role_key || "org_member", status };
const isOwner = member.role_key === "org_owner";
return
{name?.slice(0, 1)}{name}{member.email || role}
;
})}
{inviteLink && }
PENDING ACCESS
待处理邀请
{invitations.length} pending
{invitations.length ? invitations.map((invite) =>
{invite.email}{invite.role_name || invite.role_key} · {invite.project_name ? `${invite.workspace_name} / ${invite.project_name}` : invite.workspace_name || "组织级"}有效期至 {invite.expires_at ? new Date(invite.expires_at).toLocaleString("zh-CN", { dateStyle: "short", timeStyle: "short" }) : "-"} · {invite.token_hint || "token hidden"}
pending
) :
当前组织没有待处理邀请。
}
WORKSPACE ACCESS
当前工作区成员
{workspaceMembers.length} members
{workspaceMembers.length ? workspaceMembers.map((member) => {
const userId = member.user_id || member.id;
const draft = workspaceMemberDrafts[userId] || { roleKey: member.role_key || "producer", accessMode: member.access_mode || "all", status: member.status || "active" };
return
{member.display_name?.slice(0, 1)}{member.display_name}{member.email}
;
}) :
当前角色没有工作区成员管理权限。
}
项目类型状态所有者成员风险
{projects.map((project) =>
)}
CURRENT PROJECT
当前项目成员角色
{projectMembers.length} members
{projectMembers.length ? projectMembers.map((member) => { const userId = member.user_id || member.id; const draft = projectMemberDrafts[userId] || { roleKey: member.role_key || "project_viewer", status: member.status || "active" }; return
{member.display_name}{member.email} · {member.role_name}
; }) :
当前角色没有项目成员管理权限,或当前项目还没有显式成员。
}
{members.length > projectMembers.length && }
{permissionMatrix.map((role) =>
{role.role}{role.permissions.join(" · ")}
)}
当前用户有效权限{context?.roles?.map((role) => role.name).join(" / ") || "平台管理员"}{context?.permissions?.slice(0, 8).join(" · ") || "workspace:* · project:* · model:*"}
ORGANIZATION POLICY
组织角色权限策略
{canManageRolePolicies ? "可管理" : "只读"}
{rolePolicies.roles.length ?
角色{rolePolicies.roles.length} 个角色
{rolePolicies.roles.map((role) =>
)}
{selectedPolicyRole &&
{selectedPolicyRole.key === "org_owner" ? "组织所有者策略已锁定" : "组织级覆盖不修改内置角色模板"}
{policyOverrides.size} 项组织覆盖
}
{visiblePolicyPermissions.map((permission) => {
const checked = selectedPolicyRole?.permissions.includes(permission.key);
const override = policyOverrides.get(permission.key);
const locked = !canManageRolePolicies || selectedPolicyRole?.key === "org_owner" || permission.systemOnly;
return ;
})}
{!visiblePolicyPermissions.length &&
没有匹配的权限。
}
: 当前账号没有组织角色策略管理权限。
}
- 套餐
- {billing.plan_name || billing.name}
- 席位
- {billing.seat_limit || billing.seats}
- 存储
- {billing.storage_gb || billing.storageGb} GB
- 本月任务
- {usage?.totalCost ?? "-"} 计量成本
{usage?.quotas?.map((quota) =>
{quota.metric}{quota.used_value}/{quota.limit_value} {quota.unit} )}
AUDIT TRAIL
审计日志
immutable events
{auditLog.map((item) =>
{item.created_at || item.time}{item.action}{item.target_id || item.target}{item.result}
)}
);
}
function DirectorDesk({ project, activeShot, setActiveShotId }) {
return (
{project.directorDesk.batchActions.map((action) => (
))}
{project.directorDesk.columns.map((column) => {column})}
{project.shots.map((shot) => (
))}
视频提示
{activeShot.videoPrompt}
);
}
const searchTypeIcons = {
project: FolderKanban,
episode: BookOpen,
script: BookOpen,
shot: Clapperboard,
asset: Images,
task: ListChecks,
job: Wand2,
delivery: Download
};
const searchStatusLabels = {
production: "制作中",
draft: "草稿",
ready: "就绪",
open: "待处理",
in_progress: "进行中",
blocked: "阻塞",
done: "已完成",
queued: "排队中",
running: "运行中",
completed: "已完成",
failed: "失败",
published: "已发布",
submitted: "待审批",
approved: "已批准"
};
function GlobalSearchPalette({ open, onClose, contextOverrides, onNavigate }) {
const inputRef = useRef(null);
const [query, setQuery] = useState("");
const [scope, setScope] = useState("workspace");
const [selectedIndex, setSelectedIndex] = useState(0);
const [payload, setPayload] = useState({ results: [], total: 0 });
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
useEffect(() => {
if (!open) return;
setTimeout(() => inputRef.current?.focus(), 0);
}, [open]);
useEffect(() => {
if (!open) return undefined;
const normalized = query.trim();
if (!normalized) {
setPayload({ results: [], total: 0 });
setSelectedIndex(0);
setError("");
return undefined;
}
let cancelled = false;
const timer = setTimeout(async () => {
setLoading(true);
try {
const next = await searchPlatform(normalized, { scope, limit: 40 }, contextOverrides);
if (!cancelled) {
setPayload(next);
setSelectedIndex(0);
setError("");
}
} catch (requestError) {
if (!cancelled) {
setPayload({ results: [], total: 0 });
setError(requestError.message || "搜索失败");
}
} finally {
if (!cancelled) setLoading(false);
}
}, 180);
return () => {
cancelled = true;
clearTimeout(timer);
};
}, [open, query, scope, contextOverrides?.organizationId, contextOverrides?.workspaceId, contextOverrides?.projectId]);
useEffect(() => {
if (!open) return undefined;
function handleKeyDown(event) {
if (event.key === "Escape") {
event.preventDefault();
onClose();
} else if (event.key === "ArrowDown") {
event.preventDefault();
setSelectedIndex((current) => Math.min(current + 1, Math.max(0, payload.results.length - 1)));
} else if (event.key === "ArrowUp") {
event.preventDefault();
setSelectedIndex((current) => Math.max(0, current - 1));
} else if (event.key === "Enter" && payload.results[selectedIndex]) {
event.preventDefault();
onNavigate(payload.results[selectedIndex]);
onClose();
}
}
window.addEventListener("keydown", handleKeyDown);
return () => window.removeEventListener("keydown", handleKeyDown);
}, [open, onClose, onNavigate, payload.results, selectedIndex]);
if (!open) return null;
const results = payload.results || [];
return (
event.target === event.currentTarget && onClose()}>
{loading ? "搜索中…" : query.trim() ? `${payload.total || 0} 条结果` : "输入关键词开始搜索"}
{error && }
{!query.trim() &&
搜索整个生产工作区项目、剧本、分集、镜头、资产、任务、生成任务和交付记录都支持快速定位。
}
{query.trim() && !loading && !error && !results.length &&
没有找到匹配内容试试项目名、镜头编号、角色名或任务关键词。
}
{results.map((result, index) => {
const Icon = searchTypeIcons[result.type] || Search;
return
;
})}
↑↓选择Enter打开Esc关闭
);
}
function App() {
const storedContext = getClientContext();
const storedAuthSession = getAuthSession();
const publicPortalToken = portalTokenFromLocation();
const [project, setProject] = useState(emptyProject);
const [platformContext, setPlatformContext] = useState(null);
const [authSession, setAuthSession] = useState(storedAuthSession);
const [authLoading, setAuthLoading] = useState(Boolean(storedAuthSession?.token));
const [authError, setAuthError] = useState("");
const [pendingAuthChallenge, setPendingAuthChallenge] = useState(null);
const [loginPending, setLoginPending] = useState(false);
const [contextLoading, setContextLoading] = useState(true);
const [contextError, setContextError] = useState("");
const [activeOrgId, setActiveOrgId] = useState(storedContext.organizationId || "org-studio-lab");
const [activeWorkspaceId, setActiveWorkspaceId] = useState(storedContext.workspaceId || "ws-local-aidrama");
const [activeProjectId, setActiveProjectId] = useState(storedContext.projectId || "");
const [activeEpisodeId, setActiveEpisodeId] = useState(storedContext.episodeId || "");
const [activeTabState, setActiveTabState] = useState(tabFromLocation);
const [mobileNavOpen, setMobileNavOpen] = useState(false);
const [searchOpen, setSearchOpen] = useState(false);
const [activeShotId, setActiveShotId] = useState("");
const [adapterId, setAdapterId] = useState(emptyProject.production.defaultAdapter);
const [taskLog, setTaskLog] = useState([
"等待当前项目数据",
"单画面规则已启用",
"默认适配器:用户自有模型平台"
]);
function setActiveTab(nextTab) {
const next = navigationTabIds.has(nextTab) ? nextTab : "creator-home";
setActiveTabState(next);
setMobileNavOpen(false);
if (typeof window !== "undefined" && window.location.hash !== `#${next}`) {
window.history.replaceState(null, "", `${window.location.pathname}${window.location.search}#${next}`);
}
}
useEffect(() => {
function handleHashChange() {
const next = tabFromLocation();
setActiveTabState(next);
setMobileNavOpen(false);
}
window.addEventListener("hashchange", handleHashChange);
return () => window.removeEventListener("hashchange", handleHashChange);
}, []);
useEffect(() => {
function handleGlobalShortcut(event) {
if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "k") {
event.preventDefault();
setSearchOpen(true);
}
}
window.addEventListener("keydown", handleGlobalShortcut);
return () => window.removeEventListener("keydown", handleGlobalShortcut);
}, []);
const activeTab = activeTabState;
const adapterCatalog = useMemo(() => adapterCatalogForContext(platformContext), [platformContext?.platform?.adapterCatalog, platformContext?.platform?.modelRegistry]);
async function refreshContext(overrides = {}) {
const selection = {
userId: authSession?.user?.id || storedContext.userId || "u-owner",
organizationId: activeOrgId,
workspaceId: activeWorkspaceId,
projectId: activeProjectId,
...overrides
};
setContextLoading(true);
try {
const payload = await fetchPlatformContext(selection);
const current = payload.context;
setPlatformContext(payload);
setActiveOrgId(current.currentOrganization.id);
setActiveWorkspaceId(current.currentWorkspace.id);
setActiveProjectId(current.currentProject?.id || "");
const projectPayload = await fetchProject({
userId: current.currentUser.id,
organizationId: current.currentOrganization.id,
workspaceId: current.currentWorkspace.id,
projectId: current.currentProject?.id || ""
});
const nextProject = projectPayload.project || createProjectShell();
setProject(nextProject);
setActiveEpisodeId(nextProject.episode?.id || "");
setContextError("");
} catch (error) {
if (error.status === 401) {
setAuthSession(null);
setPlatformContext(null);
setAuthError("登录会话已失效,请重新登录");
}
setContextError(error.message);
} finally {
setContextLoading(false);
}
}
useEffect(() => {
let cancelled = false;
async function bootstrapAuth() {
if (publicPortalToken) {
setAuthLoading(false);
setContextLoading(false);
return;
}
const ssoTicket = ssoTicketFromLocation();
const ssoError = ssoErrorFromLocation();
if (ssoError) {
window.history.replaceState(null, "", `${window.location.pathname}${window.location.hash || ""}`);
setAuthError(ssoError);
}
if (!storedAuthSession?.token) {
if (ssoTicket) {
try {
const payload = await redeemSsoTicket(ssoTicket);
if (cancelled) return;
window.history.replaceState(null, "", `${window.location.pathname}${window.location.hash || ""}`);
if (payload.mfaRequired) {
setPendingAuthChallenge(payload);
setAuthError("");
} else if (payload.session) {
setAuthSession({ ...payload.session, user: payload.user });
const current = payload.context;
await refreshContext({ userId: payload.user.id, organizationId: current.currentOrganization.id, workspaceId: current.currentWorkspace.id, projectId: current.currentProject?.id || "" });
}
} catch (error) {
if (!cancelled) setAuthError(error.message);
}
}
setAuthLoading(false);
setContextLoading(false);
return;
}
try {
const payload = await fetchAuthSession();
if (cancelled) return;
setAuthSession((current) => ({ ...current, user: payload.user, expiresAt: payload.session?.expiresAt }));
await refreshContext({
userId: payload.user.id,
organizationId: payload.context.currentOrganization.id,
workspaceId: payload.context.currentWorkspace.id,
projectId: payload.context.currentProject?.id || ""
});
} catch (error) {
if (cancelled) return;
setAuthSession(null);
setAuthError(error.status === 401 ? "登录会话已失效,请重新登录" : error.message);
} finally {
if (!cancelled) setAuthLoading(false);
}
}
bootstrapAuth();
return () => { cancelled = true; };
}, [publicPortalToken]);
async function handleLogin(credentials) {
setLoginPending(true);
setAuthError("");
try {
if (credentials.enrollmentAction === "setup") return await startMfaEnrollment(credentials.enrollmentToken);
const payload = credentials.enrollmentAction === "complete" ? await completeMfaEnrollment(credentials) : credentials.challengeToken ? await loginWithMfa(credentials) : await login(credentials);
if (payload.mfaRequired) return payload;
if (!payload.session) return payload;
setPendingAuthChallenge(null);
setAuthSession({ ...payload.session, user: payload.user });
const current = payload.context;
await refreshContext({
userId: payload.user.id,
organizationId: current.currentOrganization.id,
workspaceId: current.currentWorkspace.id,
projectId: current.currentProject?.id || ""
});
} catch (error) {
setAuthError(error.message);
} finally {
setLoginPending(false);
}
}
async function handleRegistration(payload) {
setAuthSession({ ...payload.session, user: payload.user });
const current = payload.context;
await refreshContext({
userId: payload.user.id,
organizationId: current.currentOrganization.id,
workspaceId: current.currentWorkspace.id,
projectId: current.currentProject?.id || ""
});
if (typeof window !== "undefined" && window.history.replaceState) window.history.replaceState(null, "", `${window.location.pathname}#creator-home`);
setActiveTabState("creator-home");
}
async function handleLogout() {
await logout();
setAuthSession(null);
setPlatformContext(null);
setActiveTab("creator-home");
setContextError("");
setAuthError("");
}
useEffect(() => {
setActiveShotId(project.shots[0]?.id || "");
}, [project]);
useEffect(() => {
setAdapterId((current) => preferredAdapterId(adapterCatalog, current || project.production?.defaultAdapter));
}, [adapterCatalog, project.production?.defaultAdapter]);
function switchOrganization(organizationId) {
refreshContext({ organizationId, workspaceId: "", projectId: "" });
}
function switchWorkspace(workspaceId) {
refreshContext({ organizationId: activeOrgId, workspaceId, projectId: "" });
}
function switchProject(projectId) {
refreshContext({ organizationId: activeOrgId, workspaceId: activeWorkspaceId, projectId });
}
async function navigateFromSearch(result) {
const nextProjectId = result.projectId || (result.type === "project" ? result.id : activeProjectId);
await refreshContext({
organizationId: result.organizationId || activeOrgId,
workspaceId: result.workspaceId || activeWorkspaceId,
projectId: nextProjectId || ""
});
if (result.type === "shot" && typeof window !== "undefined") {
window.setTimeout(() => setActiveShotId(result.id), 0);
}
setActiveTab(result.targetTab || "creator-home");
}
async function inviteFromAdmin(body) {
const result = await inviteMember(activeOrgId, body, { organizationId: activeOrgId, workspaceId: activeWorkspaceId, projectId: activeProjectId });
await refreshContext();
return result;
}
async function resendInvitationFromAdmin(invitationId) {
const result = await resendOrganizationInvitation(activeOrgId, invitationId, { organizationId: activeOrgId, workspaceId: activeWorkspaceId, projectId: activeProjectId });
await refreshContext();
return result;
}
async function revokeInvitationFromAdmin(invitationId) {
const result = await revokeOrganizationInvitation(activeOrgId, invitationId, { organizationId: activeOrgId, workspaceId: activeWorkspaceId, projectId: activeProjectId });
await refreshContext();
return result;
}
async function updateOrganizationMemberFromAdmin(userId, body) {
await updateOrganizationMember(activeOrgId, userId, body, { organizationId: activeOrgId, workspaceId: activeWorkspaceId, projectId: activeProjectId });
await refreshContext();
}
async function updateOrganizationRolePolicyFromAdmin(roleKey, body) {
await updateOrganizationRolePolicy(activeOrgId, roleKey, body, { organizationId: activeOrgId, workspaceId: activeWorkspaceId, projectId: activeProjectId });
await refreshContext();
}
async function updateWorkspaceMemberFromAdmin(userId, body) {
await updateWorkspaceMember(activeWorkspaceId, userId, body, { organizationId: activeOrgId, workspaceId: activeWorkspaceId, projectId: activeProjectId });
await refreshContext();
}
async function updateProjectMemberFromAdmin(userId, body) {
await updateProjectMember(activeProjectId, userId, body, { organizationId: activeOrgId, workspaceId: activeWorkspaceId, projectId: activeProjectId });
await refreshContext();
}
async function addProjectMemberFromAdmin(userId, body) {
await addProjectMember(activeProjectId, { userId, ...body }, { organizationId: activeOrgId, workspaceId: activeWorkspaceId, projectId: activeProjectId });
await refreshContext();
}
async function updateOrganizationFromAdmin(body) {
await updateOrganization(activeOrgId, body, { organizationId: activeOrgId, workspaceId: activeWorkspaceId, projectId: activeProjectId });
await refreshContext();
}
async function updateWorkspaceFromAdmin(body) {
await updateWorkspace(activeWorkspaceId, body, { organizationId: activeOrgId, workspaceId: activeWorkspaceId, projectId: activeProjectId });
await refreshContext();
}
async function updateProjectFromAdmin(body) {
await updateProject(activeProjectId, body, { organizationId: activeOrgId, workspaceId: activeWorkspaceId, projectId: activeProjectId });
await refreshContext();
}
async function changeProjectLifecycleFromFactory(projectId, action) {
const result = await changeProjectLifecycle(projectId, action, { organizationId: activeOrgId, workspaceId: activeWorkspaceId, projectId });
const nextProjectId = action === "archive" && projectId === activeProjectId ? "" : activeProjectId;
await refreshContext({ projectId: nextProjectId });
return result;
}
async function createWorkspaceFromAdmin(body) {
const result = await createWorkspace(body, { organizationId: activeOrgId, workspaceId: activeWorkspaceId, projectId: activeProjectId });
await refreshContext({ workspaceId: result.workspace.id, projectId: "" });
}
async function createOrganizationFromAdmin(body) {
const result = await createOrganization(body, { organizationId: activeOrgId, workspaceId: activeWorkspaceId, projectId: activeProjectId });
await refreshContext({ organizationId: result.organization.id, workspaceId: "", projectId: "" });
}
async function createProjectFromAdmin(body) {
const result = await createProject(body, { organizationId: activeOrgId, workspaceId: activeWorkspaceId, projectId: activeProjectId });
await refreshContext({ projectId: result.project.id });
}
function syncModelRegistry(models) {
setPlatformContext((current) => current ? { ...current, platform: { ...current.platform, modelRegistry: models, adapterCatalog: models } } : current);
}
async function handleInvitationAccepted(invitation) {
await refreshContext({
organizationId: invitation.organization_id,
workspaceId: invitation.workspace_id || "",
projectId: invitation.project_id || ""
});
}
const activeShot = project.shots.find((shot) => shot.id === activeShotId) || project.shots[0] || {
id: "",
title: "尚未创建镜头",
durationSec: 6,
characterIds: [],
propIds: [],
locationId: "",
firstFrame: "",
lastFrame: "",
transitionFromPrevious: "episode-start",
voiceLines: [],
prompt: "",
negativePrompt: "",
videoPrompt: "",
seed: 0
};
const selectedAdapter = adapterCatalog.find((item) => item.id === adapterId) || adapterCatalog[0] || normalizeAdapter(adapters[0]);
const qaResults = useMemo(() => projectQa(project), [project]);
const activeQa = activeShot.id ? qaResults.find((item) => item.shotId === activeShot.id) : null;
const exports = useMemo(() => buildAllExports(project), [project]);
const requestPreview = useMemo(
() => activeShot.id ? buildModelRequest(project, activeShot, selectedAdapter) : { schema: "ai-drama.job.v1", message: "请先在导演工作台创建至少一个镜头。" },
[project, activeShot, selectedAdapter]
);
async function enqueueJob(kind, shotId = activeShot.id, options = {}) {
const targetShot = project.shots.find((shot) => shot.id === shotId) || activeShot;
if (!targetShot?.id) throw new Error("当前项目还没有可生成的镜头,请先在导演工作台创建镜头");
const line = `${new Date().toLocaleTimeString("zh-CN", { hour12: false })} 已生成 ${targetShot.id} 的${kind}请求包,等待接入本地模型平台`;
try {
const result = await createJob({
kind,
shotId,
adapter: options.adapter || adapterId,
output: options.output,
priority: options.priority,
approveExternal: Boolean(options.approveExternal),
dependsOnJobIds: options.dependsOnJobIds || []
}, { organizationId: activeOrgId, workspaceId: activeWorkspaceId, projectId: activeProjectId });
setProject((current) => ({ ...current, productionJobs: result.jobs || [result.job, ...(current.productionJobs || [])] }));
setTaskLog((items) => [line, ...items].slice(0, 8));
setContextError("");
return result;
} catch (error) {
setContextError(error.message);
setTaskLog((items) => [`请求未写入队列:${error.message}`, ...items].slice(0, 8));
throw error;
}
}
const qaPassCount = qaResults.flatMap((item) => item.gates).filter((gate) => gate.status === "pass").length;
const qaGateCount = qaResults.flatMap((item) => item.gates).length;
const contextPlatform = platformContext?.platform || {};
const organizations = contextPlatform.organizations || [];
const workspaces = contextPlatform.workspaces || [];
const projects = contextPlatform.projects || [];
const currentUser = platformContext?.context?.currentUser || authSession?.user;
const currentRole = platformContext?.context?.systemAdmin ? "系统管理员" : (platformContext?.context?.workspaceRole || platformContext?.context?.organizationRole || "local-admin");
const contextOverrides = {
userId: currentUser?.id || storedContext.userId || "u-owner",
organizationId: activeOrgId,
workspaceId: activeWorkspaceId,
projectId: activeProjectId,
episodeId: activeEpisodeId
};
const enterpriseArea = activeTab === "creator-home" || activeTab === "tasks" || activeTab === "assistant" || activeTab === "knowledge" || activeTab === "asset-library" || activeTab === "voice-studio" || activeTab === "batch-production" || activeTab === "account" || activeTab === "factory" || activeTab === "script" || activeTab === "casting" || activeTab === "director" || activeTab === "jobs" || activeTab === "bible" || activeTab === "locks" || activeTab === "shots" || activeTab === "modelops" || activeTab === "qa" || activeTab === "export" || activeTab === "delivery-portal" || activeTab.startsWith("admin-") || activeTab.startsWith("system-");
const effectivePermissions = new Set(platformContext?.context?.permissions || []);
const canViewAdmin = ["workspace:create", "model:manage", "model:approve", "policy:manage", "policy:read", "organization:manage", "style:manage", "style:read", "usage:view", "compliance:manage", "audit:view", "queue:manage"].some((permission) => effectivePermissions.has(permission));
const canViewSystem = platformContext?.context?.systemAdmin || effectivePermissions.has("system:settings:view");
const canCreateProject = effectivePermissions.has("project:create");
const canCreateJob = effectivePermissions.has("job:create");
const canCreateJobNow = canCreateJob && project.status !== "archived";
const canManageProject = effectivePermissions.has("project:manage");
const canApproveDelivery = effectivePermissions.has("delivery:approve");
const canApproveVoice = effectivePermissions.has("voice:approve");
const canApproveAssets = effectivePermissions.has("compliance:manage");
const canSeeNavigationItem = (item) => {
if (item.requiredAnyPermissions) return item.requiredAnyPermissions.some((permission) => effectivePermissions.has(permission));
return !item.requiredPermission || effectivePermissions.has(item.requiredPermission);
};
const visibleNavigationGroups = navigationGroups
.filter((group) => group.label === "创作套件" || group.label === "账户" || group.label === "生产流水线" || (group.label === "管理员后台" && canViewAdmin) || (group.label === "系统设置" && canViewSystem))
.map((group) => ({ ...group, items: group.items.filter(canSeeNavigationItem) }))
.filter((group) => group.items.length);
const activeNavigationItem = visibleNavigationGroups.flatMap((group) => group.items).find((item) => item.id === activeTab);
useEffect(() => {
if (platformContext && !activeNavigationItem) setActiveTab("creator-home");
}, [platformContext, activeNavigationItem?.id]);
if (authLoading) {
return 正在验证登录会话…
;
}
if (publicPortalToken) return ;
if (!authSession?.token) {
const inviteToken = inviteTokenFromLocation();
return inviteToken ? : ;
}
return (
{mobileNavOpen &&
);
}
export default App;