diff --git a/backend/src/backend/scripts.py b/backend/src/backend/scripts.py index b322d23..84a36b6 100644 --- a/backend/src/backend/scripts.py +++ b/backend/src/backend/scripts.py @@ -21,6 +21,7 @@ from fastapi import ( from sqlalchemy import func, select from sqlalchemy.ext.asyncio import AsyncSession +from common.config import settings from common.db.models import ( Scripts, StorageObjects, @@ -356,19 +357,40 @@ async def create_script_record( detail=exc.detail, ) from exc - # Synthesise a storage_data-shaped dict from the Jupyter response - # so the existing script_payload + response shape keep working. - # The storage_object_id is a fresh ULID — there is no real - # StorageObject row for this file; downstream list/get operations - # that JOIN StorageObjects will skip jupyter-only scripts. + # Build a real StorageObjects row so the file participates in + # workspace-tree / list / get queries that JOIN this table. The + # bytes live in the Jupyter mount; rclone replicates them to + # RustFS asynchronously. We mark the row "available" because the + # file is queryable as a workspace file from the user's POV; the + # storage_uri points at where the replicated bytes will land. object_id = new_ulid() - storage_data = { - "storage_object_id": object_id, - "relative_path": jupyter_name, - "object_key": f"{workspace_id}/{jupyter_name}", - "content_hash": content_hash, - "size_bytes": size_bytes, - } + object_key = f"{workspace_id}/{jupyter_name}" + bucket_name = settings.rustfs_workspace_bucket + relative_path = user_relative_path(context, jupyter_name) + mime_type = mimetypes.guess_type(jupyter_name)[0] + storage_object = StorageObjects( + storage_object_id=object_id, + workspace_id=context.workspace.workspace_id, + owner_user_id=context.user.user_id, + object_type="file", + usage_type="working_copy", + storage_backend="rustfs", + bucket_name=bucket_name, + object_key=object_key, + object_key_hash=hashlib.sha256(object_key.encode("utf-8")).digest(), + storage_uri=f"s3://{bucket_name}/{object_key}", + file_name=name, + file_extension=PurePosixPath(jupyter_name).suffix.lower() or None, + mime_type=mime_type, + size_bytes=size_bytes, + content_hash=content_hash, + visibility=visibility, + is_immutable=0, + object_status="available", + created_by=context.user.user_id, + relative_path=relative_path, + path_hash=hashlib.sha256(relative_path.encode("utf-8")).digest(), + ) script = Scripts( script_id=script_id, workspace_id=context.workspace.workspace_id, @@ -379,10 +401,12 @@ async def create_script_record( visibility=visibility, status="active", ) + session.add(storage_object) session.add(script) await session.flush() + await session.refresh(storage_object) await session.refresh(script) - return script, storage_data + return script, storage_object @router.post("/api/v1/scripts", status_code=status.HTTP_201_CREATED) diff --git a/frontend/app/context/AuthContext.tsx b/frontend/app/context/AuthContext.tsx index aeda940..81b45c6 100644 --- a/frontend/app/context/AuthContext.tsx +++ b/frontend/app/context/AuthContext.tsx @@ -222,7 +222,6 @@ export function useApi(): WorkspaceBoundApi { rawApi.createJupyterAccessTicket(workspaceId, session), getLatestScriptVersion: (scriptId) => rawApi.getLatestScriptVersion(workspaceId, scriptId), - listScriptVersions: (scriptId) => rawApi.listScriptVersions(workspaceId, scriptId), publishScriptVersion: (input) => rawApi.publishScriptVersion(workspaceId, input), listSchedules: () => rawApi.listSchedules(workspaceId), getSchedule: (scheduleId) => rawApi.getSchedule(workspaceId, scheduleId), diff --git a/frontend/app/features/platform/ModelPlatformApp.tsx b/frontend/app/features/platform/ModelPlatformApp.tsx index 947e8c2..a677676 100644 --- a/frontend/app/features/platform/ModelPlatformApp.tsx +++ b/frontend/app/features/platform/ModelPlatformApp.tsx @@ -162,8 +162,6 @@ function AuthenticatedModelPlatformApp() { scriptId: string; message: string; } | null>(null); - const [versions, setVersions] = useState([]); - const [versionsLoading, setVersionsLoading] = useState(false); const [latestVersion, setLatestVersion] = useState( null, ); @@ -248,12 +246,10 @@ function AuthenticatedModelPlatformApp() { useEffect(() => { if (!selectedId) { - setVersions([]); setLatestVersion(null); return; } let ignore = false; - setVersionsLoading(true); setLatestVersionLoading(true); void api.getLatestScriptVersion(selectedId) .then((item) => { @@ -273,21 +269,6 @@ function AuthenticatedModelPlatformApp() { .finally(() => { if (!ignore) setLatestVersionLoading(false); }); - void api.listScriptVersions(selectedId) - .then((items) => { - if (!ignore) setVersions(items); - }) - .catch((error) => { - if (!ignore) { - setToast({ - tone: "error", - message: error instanceof Error ? error.message : "版本列表加载失败", - }); - } - }) - .finally(() => { - if (!ignore) setVersionsLoading(false); - }); return () => { ignore = true; }; @@ -595,10 +576,6 @@ function AuthenticatedModelPlatformApp() { releaseNote, visibility: publishVisibility, }); - setVersions((items) => [ - version, - ...items.filter((item) => item.versions_id !== version.versions_id), - ]); setPublishTarget(null); setPublishedVersion(version); setToast({ diff --git a/frontend/app/services/api.ts b/frontend/app/services/api.ts index 9920693..86f0e50 100644 --- a/frontend/app/services/api.ts +++ b/frontend/app/services/api.ts @@ -574,17 +574,6 @@ export async function getLatestScriptVersion( ); } -export async function listScriptVersions( - workspaceId: string, - scriptId: string, -): Promise { - return apiRequest( - `/api/v1/scripts/${scriptId}/versions`, - {}, - workspaceId, - ); -} - export async function publishScriptVersion( workspaceId: string, input: { @@ -1119,7 +1108,6 @@ export type WorkspaceBoundApi = { session: ActiveEditSession, ) => Promise; getLatestScriptVersion: (scriptId: string) => Promise; - listScriptVersions: (scriptId: string) => Promise; publishScriptVersion: ( input: Parameters[1], ) => Promise;