import {
type ChangeEvent,
type FormEvent,
type MouseEvent as ReactMouseEvent,
useEffect,
useMemo,
useRef,
useState,
} from "react";
import { useLocation, useNavigate } from "react-router";
import {
type ActiveEditSession,
type LatestVersion,
type ScriptItem,
type ScriptType,
type StableVersion,
type Visibility,
type WorkspaceDirectory,
} from "../../services/api";
import { useApi, useAuth } from "../../context/AuthContext";
import Icon from "../../components/common/Icon";
import { Sidebar } from "../../components/common/Sidebar";
import { Topbar } from "../../components/common/Topbar";
import { ScriptExplorer } from "../../components/platform/ScriptExplorer";
import { CreateScriptModal } from "../../components/platform/CreateScriptModal";
import { CreateFolderModal } from "../../components/platform/CreateFolderModal";
import { TreeContextMenu } from "../../components/platform/TreeContextMenu";
import { PublishModal } from "../../components/platform/PublishModal";
import { VersionReceiptModal } from "../../components/platform/VersionReceiptModal";
import { Toast } from "../../components/common/Toast";
import { ScriptWorkspace } from "./ScriptWorkspace";
import SchedulePage from "../schedules/SchedulePage";
import { DashboardPage, SystemAdminPage } from "../admin/AdminPages";
import "../../styles/platform.css";
type NewScriptForm = {
name: string;
scriptType: ScriptType;
visibility: Visibility;
parentPath: string;
};
type ContextMenuState = {
x: number;
y: number;
kind: "root" | "directory" | "file";
path: string;
script?: ScriptItem;
};
type ToastState = {
tone: "success" | "error" | "info";
message: string;
};
type ActivePage = "home" | "scripts" | "schedules" | "system";
function pageFromPath(pathname: string): ActivePage {
const page = pathname.replace(/^\/+|\/+$/g, "");
return ["scripts", "schedules", "system"].includes(page)
? page as ActivePage
: "home";
}
function pathForPage(page: ActivePage): string {
return page === "home" ? "/workbench" : `/${page}`;
}
const initialForm: NewScriptForm = {
name: "",
scriptType: "notebook",
visibility: "workspace",
parentPath: "",
};
export default function ModelPlatformApp() {
const { currentWorkspace } = useAuth();
if (!currentWorkspace) {
return (
);
}
return ;
}
function AuthenticatedModelPlatformApp() {
const location = useLocation();
const navigate = useNavigate();
const activePage = pageFromPath(location.pathname);
const auth = useAuth();
const { user, workspaces, setCurrentWorkspace, logout } = auth;
const currentWorkspace = auth.currentWorkspace!;
const api = useApi();
// 状态管理
const [scripts, setScripts] = useState([]);
const [directories, setDirectories] = useState([]);
const [selectedId, setSelectedId] = useState(null);
const [openTabIds, setOpenTabIds] = useState([]);
const [keyword, setKeyword] = useState("");
const [loading, setLoading] = useState(true);
const [refreshing, setRefreshing] = useState(false);
const [apiOnline, setApiOnline] = useState(false);
// 创建脚本相关
const [createOpen, setCreateOpen] = useState(false);
const [creating, setCreating] = useState(false);
const [form, setForm] = useState(initialForm);
// 创建文件夹相关
const [folderDialog, setFolderDialog] = useState<{
open: boolean;
parentPath: string;
name: string;
busy: boolean;
}>({ open: false, parentPath: "", name: "", busy: false });
// 右键菜单
const [contextMenu, setContextMenu] = useState(null);
// Workspace 菜单
const [workspaceMenuOpen, setWorkspaceMenuOpen] = useState(false);
// 上传相关
const [uploadParentPath, setUploadParentPath] = useState("");
const [uploading, setUploading] = useState(false);
const uploadInputRef = useRef(null);
// Toast 通知
const [toast, setToast] = useState(null);
// 编辑会话相关 - 为每个标签维护独立 iframe 和状态
const [editSession, setEditSession] = useState(null);
const editSessionRef = useRef(null);
const selectedIdRef = useRef(null);
const editorOpenRequestRef = useRef(0);
const editorOpeningRef = useRef(false);
const [embeddedJupyterUrl, setEmbeddedJupyterUrl] = useState(null);
const [editBusy, setEditBusy] = useState(false);
const [editorOpenError, setEditorOpenError] = useState<{
scriptId: string;
message: string;
} | null>(null);
// 为每个打开的标签维护独立的缓存会话(多 iframe 共存方案)
interface CachedSession {
session: ActiveEditSession;
jupyterUrl: string;
lastActiveTime: number; // 最后激活时间戳(用于清理)
}
const sessionCacheRef = useRef