refactor: api.ts

This commit is contained in:
tao.chen
2026-09-01 11:10:39 +08:00
parent 4bfd4ac4c2
commit 0deb2d205b
13 changed files with 2157 additions and 1964 deletions
+299
View File
@@ -0,0 +1,299 @@
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,
);
}