feat: 完善模型平台相关功能
This commit is contained in:
+7
-2
@@ -4,9 +4,14 @@ RUN corepack enable && corepack prepare pnpm@10.15.1 --activate
|
||||
COPY frontend/package.json frontend/pnpm-lock.yaml ./
|
||||
RUN pnpm install --frozen-lockfile
|
||||
COPY frontend/ ./
|
||||
RUN pnpm build
|
||||
RUN pnpm typecheck && pnpm build
|
||||
|
||||
FROM nginx:alpine
|
||||
COPY ./default.conf /etc/nginx/conf.d/default.conf
|
||||
COPY ./default.conf /etc/nginx/conf.d/default.conf.template
|
||||
COPY ./scripts/nginx-entrypoint.sh /usr/local/bin/model-platform-entrypoint.sh
|
||||
RUN sed -i 's/\r$//' /usr/local/bin/model-platform-entrypoint.sh \
|
||||
&& chmod +x /usr/local/bin/model-platform-entrypoint.sh
|
||||
COPY --from=frontend-build /app/build/client /usr/share/nginx/html
|
||||
EXPOSE 80
|
||||
ENTRYPOINT ["/usr/local/bin/model-platform-entrypoint.sh"]
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { FormEvent, useEffect, useState } from "react";
|
||||
import { type FormEvent, useEffect, useState } from "react";
|
||||
|
||||
import {
|
||||
ApiRequestError,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {
|
||||
type ChangeEvent,
|
||||
FormEvent,
|
||||
type FormEvent,
|
||||
type MouseEvent as ReactMouseEvent,
|
||||
useEffect,
|
||||
useMemo,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import {
|
||||
DragEvent,
|
||||
FormEvent,
|
||||
MouseEvent as ReactMouseEvent,
|
||||
PointerEvent as ReactPointerEvent,
|
||||
type DragEvent,
|
||||
type FormEvent,
|
||||
type MouseEvent as ReactMouseEvent,
|
||||
type PointerEvent as ReactPointerEvent,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -8,11 +8,11 @@ export default defineConfig({
|
||||
port: 5173,
|
||||
proxy: {
|
||||
"/api": {
|
||||
target: process.env.VITE_GATEWAY_URL || "http://127.0.0.1:8081",
|
||||
target: process.env.VITE_GATEWAY_URL || "http://127.0.0.1:8890",
|
||||
changeOrigin: true,
|
||||
},
|
||||
"/jupyter": {
|
||||
target: process.env.VITE_GATEWAY_URL || "http://127.0.0.1:8081",
|
||||
target: process.env.VITE_GATEWAY_URL || "http://127.0.0.1:8890",
|
||||
changeOrigin: true,
|
||||
ws: true,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user