Merge origin/develop into feature/a-card-operations
This commit is contained in:
@@ -0,0 +1,298 @@
|
||||
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,
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user