Files
model-platform/frontend/app/services/api/scripts.ts
T
2026-09-02 10:10:41 +08:00

300 lines
7.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import {
apiRequest,
ApiRequestError,
createUuid,
type ApiErrorEnvelope,
} from "./_shared";
export type ScriptType = "python" | "notebook";
export type Visibility = "private" | "workspace" | "public";
export type ScriptItem = {
script_id: string;
workspace_id: string;
current_object_id: string;
owner_user_id: string;
owner_display_name: string | null;
script_name: string;
script_type: ScriptType;
visibility: Visibility;
status: string;
is_locked: boolean;
relative_path: string;
jupyter_path: string;
content_hash: string;
size_bytes: number;
created_at: string;
updated_at: string;
};
export async function listScripts(
workspaceId: string,
parentPath: string = "",
ownerUserId?: string,
): Promise<ScriptItem[]> {
// Default (no ownerUserId) scopes to the requester's own subtree; passing
// ownerUserId scopes to that owner's subtree (workspace/public only — the
// backend excludes their private) so the tree can lazily fetch another
// member's content when their group is expanded.
const parameters = new URLSearchParams();
if (parentPath) parameters.set("parent_path", parentPath);
if (ownerUserId) parameters.set("owner_user_id", ownerUserId);
const query = parameters.toString();
return apiRequest<ScriptItem[]>(
`/api/v1/scripts${query ? `?${query}` : ""}`,
{},
workspaceId,
);
}
export async function countScripts(
workspaceId: string,
): Promise<number> {
// Backend route /api/v1/scripts/count must be declared BEFORE the
// /scripts/{script_id} route on the server side. Returns
// { data: { total: number } } — the dashboard's single source of
// truth for "total active scripts in workspace", independent of the
// lazy-loaded scripts[] in the workspace store.
const envelope = await apiRequest<{ total: number }>(
"/api/v1/scripts/count",
{},
workspaceId,
);
return envelope.total;
}
function initialContent(scriptType: ScriptType): string {
if (scriptType === "python") {
return [
'"""模型实验开发平台构建脚本。"""',
"",
"",
"def main() -> None:",
' print("Hello, Model Platform!")',
"",
"",
'if __name__ == "__main__":',
" main()",
"",
].join("\n");
}
return JSON.stringify(
{
cells: [
{
id: "intro",
cell_type: "code",
execution_count: null,
metadata: {},
outputs: [],
source: ["print('Hello, Model Platform!')\n"],
},
],
metadata: {
kernelspec: {
display_name: "Python 3",
language: "python",
name: "python3",
},
language_info: {
name: "python",
version: "3.12",
},
},
nbformat: 4,
nbformat_minor: 5,
},
null,
2,
);
}
export async function createScript(
workspaceId: string,
input: {
name: string;
scriptType: ScriptType;
visibility: Visibility;
parentPath?: string | null;
},
): Promise<ScriptItem> {
return apiRequest<ScriptItem>(
"/api/v1/scripts",
{
method: "POST",
body: JSON.stringify({
script_name: input.name.trim(),
script_type: input.scriptType,
visibility: input.visibility,
content: initialContent(input.scriptType),
parent_path: input.parentPath,
}),
},
workspaceId,
);
}
export async function uploadScript(
workspaceId: string,
file: File,
parentPath = "",
visibility: Visibility = "workspace",
): Promise<ScriptItem> {
const parameters = new URLSearchParams({
file_name: file.name,
parent_path: parentPath,
visibility,
});
return apiRequest<ScriptItem>(
`/api/v1/scripts/upload?${parameters.toString()}`,
{
method: "POST",
headers: { "Content-Type": "application/octet-stream" },
body: file,
},
workspaceId,
);
}
export async function setScriptLock(
workspaceId: string,
scriptId: string,
isLocked: boolean,
): Promise<ScriptItem> {
return apiRequest<ScriptItem>(
`/api/v1/scripts/${encodeURIComponent(scriptId)}/lock`,
{
method: "PATCH",
body: JSON.stringify({ is_locked: isLocked }),
},
workspaceId,
);
}
export async function updateScript(
workspaceId: string,
scriptId: string,
input: { content: string },
): Promise<ScriptItem> {
return apiRequest<ScriptItem>(
`/api/v1/scripts/${scriptId}`,
{ method: "PUT", body: JSON.stringify(input) },
workspaceId,
);
}
export async function getScriptContent(
workspaceId: string,
scriptId: string,
): Promise<{
script_id: string;
script_type: ScriptType;
content: string | object;
format: string;
}> {
const response = await fetch(
`/api/v1/scripts/${scriptId}/content?workspace_id=${encodeURIComponent(workspaceId)}`,
{
credentials: "same-origin",
headers: {
"X-Request-ID": createUuid().replaceAll("-", ""),
},
},
);
if (response.status === 401 && typeof window !== "undefined") {
const here = window.location.pathname;
if (here !== "/login") {
window.location.assign("/login");
}
throw new ApiRequestError("未登录或登录已过期", 401);
}
const payload = await response.json();
if (!response.ok) {
const error = payload as ApiErrorEnvelope;
const detailMessage = typeof error.detail === "string"
? error.detail
: error.detail?.message;
throw new ApiRequestError(
detailMessage ?? `请求失败(HTTP ${response.status}`,
response.status,
);
}
const data = (payload as { data: { script_id: string; script_type: ScriptType; content: string | object; format: string } }).data;
if (!data || !data.script_type) {
throw new ApiRequestError("响应数据格式错误", response.status);
}
return data;
}
export async function deleteScript(
workspaceId: string,
scriptId: string,
): Promise<{ script_id: string; status: string; versions_preserved: boolean }> {
return apiRequest(
`/api/v1/scripts/${scriptId}`,
{ method: "DELETE" },
workspaceId,
);
}
export type StableVersion = {
versions_id: string;
workspace_id: string;
script_id: string;
source_object_id: string;
artifact_object_id: string;
version_no: number;
version_label: string;
source_path: string;
artifact_path: string;
content_hash: string;
file_size_bytes: number;
visibility: Visibility;
release_note: string | null;
created_by: string;
created_at: string;
};
export type LatestVersion = {
versions_id: string;
version_label: string;
};
export async function getLatestScriptVersion(
workspaceId: string,
scriptId: string,
): Promise<LatestVersion | null> {
return apiRequest<LatestVersion | null>(
`/api/v1/scripts/${scriptId}/latest-version`,
{},
workspaceId,
);
}
export async function publishScriptVersion(
workspaceId: string,
input: {
script: ScriptItem;
releaseNote: string;
visibility: Visibility;
},
): Promise<StableVersion> {
return apiRequest<StableVersion>(
`/api/v1/scripts/${input.script.script_id}/versions`,
{
method: "POST",
body: JSON.stringify({
source_object_id: input.script.current_object_id,
release_note: input.releaseNote.trim() || null,
visibility: input.visibility,
}),
},
workspaceId,
);
}