|
|
|
@@ -87,6 +87,7 @@ export type ScriptItem = {
|
|
|
|
|
visibility: Visibility;
|
|
|
|
|
status: string;
|
|
|
|
|
relative_path: string;
|
|
|
|
|
jupyter_path: string;
|
|
|
|
|
content_hash: string;
|
|
|
|
|
size_bytes: number;
|
|
|
|
|
created_at: string;
|
|
|
|
@@ -195,16 +196,18 @@ function initialContent(scriptType: ScriptType): string {
|
|
|
|
|
{
|
|
|
|
|
cells: [
|
|
|
|
|
{
|
|
|
|
|
id: "intro",
|
|
|
|
|
cell_type: "markdown",
|
|
|
|
|
metadata: {},
|
|
|
|
|
source: ["# 新建模型实验\\n", "在这里开始数据探索与模型构建。"],
|
|
|
|
|
source: ["# 新建模型实验\n", "在这里开始数据探索与模型构建。"],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: "main",
|
|
|
|
|
cell_type: "code",
|
|
|
|
|
execution_count: null,
|
|
|
|
|
metadata: {},
|
|
|
|
|
outputs: [],
|
|
|
|
|
source: ["print('Hello, Model Platform!')\\n"],
|
|
|
|
|
source: ["print('Hello, Model Platform!')\n"],
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
metadata: {
|
|
|
|
@@ -325,6 +328,7 @@ export type FileLockSession = {
|
|
|
|
|
export type ActiveEditSession = FileLockSession & {
|
|
|
|
|
script_id: string;
|
|
|
|
|
script_name: string;
|
|
|
|
|
jupyter_path: string;
|
|
|
|
|
lock_token: string;
|
|
|
|
|
ticket_expires_at?: string;
|
|
|
|
|
};
|
|
|
|
@@ -356,70 +360,67 @@ export type StableVersion = {
|
|
|
|
|
export async function acquireFileLock(
|
|
|
|
|
script: ScriptItem,
|
|
|
|
|
): Promise<ActiveEditSession> {
|
|
|
|
|
const session = await apiRequest<FileLockSession>(
|
|
|
|
|
`/api/v1/files/${script.current_object_id}/lock`,
|
|
|
|
|
{ method: "POST" },
|
|
|
|
|
);
|
|
|
|
|
if (!session.lock_token) {
|
|
|
|
|
throw new Error("加锁成功响应缺少 lock_token");
|
|
|
|
|
}
|
|
|
|
|
const now = Date.now();
|
|
|
|
|
return {
|
|
|
|
|
...session,
|
|
|
|
|
edit_session_id: crypto.randomUUID().replaceAll("-", ""),
|
|
|
|
|
workspace_id: script.workspace_id,
|
|
|
|
|
storage_object_id: script.current_object_id,
|
|
|
|
|
user_id: demoContext.userId,
|
|
|
|
|
session_status: "active",
|
|
|
|
|
lease_seconds: 3600,
|
|
|
|
|
heartbeat_interval_seconds: 300,
|
|
|
|
|
expires_at: new Date(now + 3600_000).toISOString(),
|
|
|
|
|
runtime_id: script.workspace_id,
|
|
|
|
|
jupyter_session_id: "demo-session",
|
|
|
|
|
relative_path: script.relative_path,
|
|
|
|
|
lock_token: "demo-unlocked-session",
|
|
|
|
|
script_id: script.script_id,
|
|
|
|
|
script_name: script.script_name,
|
|
|
|
|
lock_token: session.lock_token,
|
|
|
|
|
jupyter_path: script.jupyter_path,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function heartbeatFileLock(
|
|
|
|
|
session: ActiveEditSession,
|
|
|
|
|
): Promise<FileLockSession> {
|
|
|
|
|
return apiRequest<FileLockSession>(
|
|
|
|
|
`/api/v1/file-locks/${session.edit_session_id}/heartbeat`,
|
|
|
|
|
{
|
|
|
|
|
method: "POST",
|
|
|
|
|
body: JSON.stringify({ lock_token: session.lock_token }),
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
return {
|
|
|
|
|
...session,
|
|
|
|
|
expires_at: new Date(Date.now() + 3600_000).toISOString(),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function releaseFileLock(
|
|
|
|
|
session: ActiveEditSession,
|
|
|
|
|
): Promise<FileLockSession> {
|
|
|
|
|
return apiRequest<FileLockSession>(
|
|
|
|
|
`/api/v1/file-locks/${session.edit_session_id}`,
|
|
|
|
|
{
|
|
|
|
|
method: "DELETE",
|
|
|
|
|
body: JSON.stringify({ lock_token: session.lock_token }),
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
return { ...session, session_status: "closed" };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function releaseFileLockOnUnload(session: ActiveEditSession): void {
|
|
|
|
|
void fetch(`/api/v1/file-locks/${session.edit_session_id}`, {
|
|
|
|
|
method: "DELETE",
|
|
|
|
|
credentials: "same-origin",
|
|
|
|
|
keepalive: true,
|
|
|
|
|
headers: {
|
|
|
|
|
"Content-Type": "application/json",
|
|
|
|
|
"X-User-ID": demoContext.userId,
|
|
|
|
|
"X-Workspace-ID": demoContext.workspaceId,
|
|
|
|
|
"X-Request-ID": crypto.randomUUID().replaceAll("-", ""),
|
|
|
|
|
},
|
|
|
|
|
body: JSON.stringify({ lock_token: session.lock_token }),
|
|
|
|
|
});
|
|
|
|
|
export function releaseFileLockOnUnload(_session: ActiveEditSession): void {
|
|
|
|
|
// The current Backend deliberately has no persisted file-lock API.
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function createJupyterAccessTicket(
|
|
|
|
|
session: ActiveEditSession,
|
|
|
|
|
): Promise<JupyterAccessTicket> {
|
|
|
|
|
return apiRequest<JupyterAccessTicket>("/api/v1/jupyter/access-tickets", {
|
|
|
|
|
method: "POST",
|
|
|
|
|
body: JSON.stringify({
|
|
|
|
|
edit_session_id: session.edit_session_id,
|
|
|
|
|
lock_token: session.lock_token,
|
|
|
|
|
}),
|
|
|
|
|
});
|
|
|
|
|
const result = await apiRequest<{ expires_at: number }>(
|
|
|
|
|
"/api/v1/auth/demo-session",
|
|
|
|
|
{
|
|
|
|
|
method: "POST",
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
const editorRoute = session.script_name.toLowerCase().endsWith(".ipynb")
|
|
|
|
|
? "notebooks"
|
|
|
|
|
: "edit";
|
|
|
|
|
const encodedPath = session.jupyter_path
|
|
|
|
|
.split("/")
|
|
|
|
|
.filter(Boolean)
|
|
|
|
|
.map(encodeURIComponent)
|
|
|
|
|
.join("/");
|
|
|
|
|
return {
|
|
|
|
|
edit_session_id: session.edit_session_id,
|
|
|
|
|
jupyter_url: `/jupyter/${encodeURIComponent(session.workspace_id)}/${editorRoute}/${encodedPath}`,
|
|
|
|
|
expires_at: new Date(result.expires_at * 1000).toISOString(),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function listScriptVersions(
|
|
|
|
|