M2c:前端镜树页(路径阅读流/候答切换/采用归真/局部树图/SSE 芽体)与灵胚页(创建/导入卡/照心试演/启镜),契约错误码段接入前端
This commit is contained in:
1 parent
30447fc40f
commit
2a640a901b
21 files changed
+2126
-4
No files matched your search
@@ -0,0 +1,51 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
vi.mock("./client", () => ({
|
||||
apiRequest: vi.fn(),
|
||||
apiGet: vi.fn(),
|
||||
apiPatch: vi.fn(),
|
||||
apiPost: vi.fn(),
|
||||
}));
|
||||
|
||||
import { apiRequest, apiGet, apiPost } from "./client";
|
||||
import {
|
||||
activateEmbryo,
|
||||
importCard,
|
||||
listEmbryos,
|
||||
rehearseEmbryo,
|
||||
} from "./embryos";
|
||||
|
||||
describe("embryos api", () => {
|
||||
it("listEmbryos GET /embryo-drafts", async () => {
|
||||
vi.mocked(apiGet).mockResolvedValue([]);
|
||||
await listEmbryos();
|
||||
expect(apiGet).toHaveBeenCalledWith("/embryo-drafts");
|
||||
});
|
||||
|
||||
it("rehearseEmbryo POST /embryo-drafts/:id/rehearse", async () => {
|
||||
vi.mocked(apiPost).mockResolvedValue({
|
||||
run_id: "run_1",
|
||||
kind: "rehearsal",
|
||||
});
|
||||
await rehearseEmbryo("embryo_1", "你是谁");
|
||||
expect(apiPost).toHaveBeenCalledWith("/embryo-drafts/embryo_1/rehearse", {
|
||||
prompt: "你是谁",
|
||||
});
|
||||
});
|
||||
|
||||
it("activateEmbryo POST /embryo-drafts/:id/activate", async () => {
|
||||
vi.mocked(apiPost).mockResolvedValue({ character_id: "character_1" });
|
||||
await activateEmbryo("embryo_1");
|
||||
expect(apiPost).toHaveBeenCalledWith("/embryo-drafts/embryo_1/activate");
|
||||
});
|
||||
|
||||
it("importCard 以 FormData POST /characters/import-card", async () => {
|
||||
vi.mocked(apiRequest).mockResolvedValue({ embryo_id: "embryo_2" });
|
||||
const file = new File(["{}"], "card.json", { type: "application/json" });
|
||||
await importCard(file);
|
||||
expect(apiRequest).toHaveBeenCalledWith(
|
||||
"/characters/import-card",
|
||||
expect.objectContaining({ method: "POST", body: expect.any(FormData) }),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,68 @@
|
||||
/** 灵胚 API Client(api-contracts §3.2)。 */
|
||||
|
||||
import { apiRequest } from "./client";
|
||||
import { apiGet, apiPatch, apiPost } from "./client";
|
||||
import type { EmbryoDraftView, ImportCardResult } from "@/types/mirror-tree";
|
||||
|
||||
export function listEmbryos() {
|
||||
return apiGet<EmbryoDraftView[]>("/embryo-drafts");
|
||||
}
|
||||
|
||||
export function getEmbryo(embryoId: string) {
|
||||
return apiGet<EmbryoDraftView>(`/embryo-drafts/${embryoId}`);
|
||||
}
|
||||
|
||||
export function createEmbryo(body: {
|
||||
source: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
identity_baseline?: Record<string, unknown>;
|
||||
}) {
|
||||
return apiPost<EmbryoDraftView>("/embryo-drafts", body);
|
||||
}
|
||||
|
||||
export function patchEmbryo(
|
||||
embryoId: string,
|
||||
body: {
|
||||
name?: string;
|
||||
description?: string;
|
||||
identity_baseline?: Record<string, unknown>;
|
||||
},
|
||||
) {
|
||||
return apiPatch<EmbryoDraftView>(`/embryo-drafts/${embryoId}`, body);
|
||||
}
|
||||
|
||||
export function rehearseEmbryo(embryoId: string, prompt: string) {
|
||||
return apiPost<{ run_id: string; kind: string }>(
|
||||
`/embryo-drafts/${embryoId}/rehearse`,
|
||||
{
|
||||
prompt,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export function activateEmbryo(embryoId: string) {
|
||||
return apiPost<{
|
||||
character_id: string;
|
||||
name: string;
|
||||
status: string;
|
||||
source: string;
|
||||
identity_record_version: number;
|
||||
}>(`/embryo-drafts/${embryoId}/activate`);
|
||||
}
|
||||
|
||||
export function abandonEmbryo(embryoId: string) {
|
||||
return apiRequest<void>(`/embryo-drafts/${embryoId}/abandon`, {
|
||||
method: "POST",
|
||||
});
|
||||
}
|
||||
|
||||
/** 导入 ST 角色卡(.png/.json/.charx)→ 产出可审阅灵胚。 */
|
||||
export function importCard(file: File) {
|
||||
const form = new FormData();
|
||||
form.append("file", file);
|
||||
return apiRequest<ImportCardResult>("/characters/import-card", {
|
||||
method: "POST",
|
||||
body: form,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
vi.mock("./client", () => ({
|
||||
apiGet: vi.fn(),
|
||||
apiPatch: vi.fn(),
|
||||
apiPost: vi.fn(),
|
||||
}));
|
||||
|
||||
import { apiGet, apiPatch, apiPost } from "./client";
|
||||
import {
|
||||
adoptNode,
|
||||
archiveTree,
|
||||
canonize,
|
||||
createQuestion,
|
||||
localGraph,
|
||||
spawnAnswer,
|
||||
treePath,
|
||||
} from "./trees";
|
||||
|
||||
describe("trees api", () => {
|
||||
it("treePath GET /trees/:id/path(无 entry 时省略查询)", async () => {
|
||||
vi.mocked(apiGet).mockResolvedValue({
|
||||
tree_id: "tree_1",
|
||||
entry_node_id: null,
|
||||
nodes: [],
|
||||
});
|
||||
await treePath("tree_1");
|
||||
expect(apiGet).toHaveBeenCalledWith("/trees/tree_1/path", undefined);
|
||||
|
||||
await treePath("tree_1", "node_9");
|
||||
expect(apiGet).toHaveBeenCalledWith("/trees/tree_1/path", {
|
||||
entry: "node_9",
|
||||
});
|
||||
});
|
||||
|
||||
it("localGraph GET /trees/:id/local-graph 携带深度", async () => {
|
||||
vi.mocked(apiGet).mockResolvedValue({
|
||||
tree_id: "tree_1",
|
||||
focus_node_id: null,
|
||||
nodes: [],
|
||||
});
|
||||
await localGraph("tree_1", "node_1");
|
||||
expect(apiGet).toHaveBeenCalledWith("/trees/tree_1/local-graph", {
|
||||
node: "node_1",
|
||||
depth: 3,
|
||||
});
|
||||
});
|
||||
|
||||
it("createQuestion POST /trees/:id/nodes", async () => {
|
||||
vi.mocked(apiPost).mockResolvedValue({ id: "node_q" });
|
||||
await createQuestion("tree_1", { content: "问", realm: "simulation" });
|
||||
expect(apiPost).toHaveBeenCalledWith("/trees/tree_1/nodes", {
|
||||
content: "问",
|
||||
realm: "simulation",
|
||||
});
|
||||
});
|
||||
|
||||
it("spawnAnswer POST /nodes/:id/answers", async () => {
|
||||
vi.mocked(apiPost).mockResolvedValue({
|
||||
node_id: "node_a",
|
||||
run_id: "run_1",
|
||||
});
|
||||
await spawnAnswer("node_q", "指引");
|
||||
expect(apiPost).toHaveBeenCalledWith("/nodes/node_q/answers", {
|
||||
instructions: "指引",
|
||||
});
|
||||
});
|
||||
|
||||
it("adoptNode POST /trees/:id/adopt", async () => {
|
||||
vi.mocked(apiPost).mockResolvedValue({
|
||||
tree_id: "tree_1",
|
||||
working_entry_node_id: "node_a",
|
||||
});
|
||||
await adoptNode("tree_1", "node_a");
|
||||
expect(apiPost).toHaveBeenCalledWith("/trees/tree_1/adopt", {
|
||||
node_id: "node_a",
|
||||
});
|
||||
});
|
||||
|
||||
it("canonize POST /trees/:id/canonize(境界带 world_id)", async () => {
|
||||
vi.mocked(apiPost).mockResolvedValue({ canonization_id: "canon_1" });
|
||||
await canonize("tree_1", {
|
||||
endpoint_node_id: "node_a",
|
||||
canonical_scope: "persistent_world",
|
||||
world_id: "world_1",
|
||||
});
|
||||
expect(apiPost).toHaveBeenCalledWith("/trees/tree_1/canonize", {
|
||||
endpoint_node_id: "node_a",
|
||||
canonical_scope: "persistent_world",
|
||||
world_id: "world_1",
|
||||
});
|
||||
});
|
||||
|
||||
it("archiveTree PATCH /trees/:id status=archived", async () => {
|
||||
vi.mocked(apiPatch).mockResolvedValue({ id: "tree_1", status: "archived" });
|
||||
await archiveTree("tree_1");
|
||||
expect(apiPatch).toHaveBeenCalledWith("/trees/tree_1", {
|
||||
status: "archived",
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,102 @@
|
||||
/** 镜树 API Client(api-contracts §3.3)。 */
|
||||
|
||||
import { apiGet, apiPatch, apiPost } from "./client";
|
||||
import type {
|
||||
CanonizeRequest,
|
||||
CanonizeResult,
|
||||
GraphResponse,
|
||||
MirrorTreeView,
|
||||
PathResponse,
|
||||
SpawnResult,
|
||||
TransplantResult,
|
||||
TreeNodeView,
|
||||
} from "@/types/mirror-tree";
|
||||
|
||||
export function listTrees(characterId?: string) {
|
||||
return apiGet<MirrorTreeView[]>(
|
||||
"/trees",
|
||||
characterId ? { character_id: characterId } : undefined,
|
||||
);
|
||||
}
|
||||
|
||||
export function getTree(treeId: string) {
|
||||
return apiGet<MirrorTreeView>(`/trees/${treeId}`);
|
||||
}
|
||||
|
||||
export function createTree(body: {
|
||||
character_id: string;
|
||||
title: string;
|
||||
default_realm?: string;
|
||||
}) {
|
||||
return apiPost<MirrorTreeView>("/trees", body);
|
||||
}
|
||||
|
||||
export function archiveTree(treeId: string) {
|
||||
return apiPatch<MirrorTreeView>(`/trees/${treeId}`, { status: "archived" });
|
||||
}
|
||||
|
||||
export function treePath(treeId: string, entryNodeId?: string | null) {
|
||||
return apiGet<PathResponse>(
|
||||
`/trees/${treeId}/path`,
|
||||
entryNodeId ? { entry: entryNodeId } : undefined,
|
||||
);
|
||||
}
|
||||
|
||||
export function localGraph(treeId: string, nodeId?: string | null, depth = 3) {
|
||||
return apiGet<GraphResponse>(`/trees/${treeId}/local-graph`, {
|
||||
node: nodeId ?? undefined,
|
||||
depth,
|
||||
});
|
||||
}
|
||||
|
||||
export function createQuestion(
|
||||
treeId: string,
|
||||
body: { content: string; realm?: string; parent_answer_id?: string },
|
||||
) {
|
||||
return apiPost<TreeNodeView>(`/trees/${treeId}/nodes`, body);
|
||||
}
|
||||
|
||||
export function spawnAnswer(nodeId: string, instructions?: string) {
|
||||
return apiPost<SpawnResult>(`/nodes/${nodeId}/answers`, { instructions });
|
||||
}
|
||||
|
||||
export function continueNode(nodeId: string, instructions?: string) {
|
||||
return apiPost<SpawnResult>(`/nodes/${nodeId}/continue`, { instructions });
|
||||
}
|
||||
|
||||
export function resumeNode(nodeId: string, instructions?: string) {
|
||||
return apiPost<SpawnResult>(`/nodes/${nodeId}/resume`, { instructions });
|
||||
}
|
||||
|
||||
export function editFork(nodeId: string, newContent: string) {
|
||||
return apiPost<{ node_id: string; supersedes_node_id: string }>(
|
||||
`/nodes/${nodeId}/edit-fork`,
|
||||
{
|
||||
new_content: newContent,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export function adoptNode(treeId: string, nodeId: string) {
|
||||
return apiPost<{ tree_id: string; working_entry_node_id: string }>(
|
||||
`/trees/${treeId}/adopt`,
|
||||
{
|
||||
node_id: nodeId,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export function canonize(treeId: string, body: CanonizeRequest) {
|
||||
return apiPost<CanonizeResult>(`/trees/${treeId}/canonize`, body);
|
||||
}
|
||||
|
||||
export function transplantBranch(
|
||||
treeId: string,
|
||||
nodeId: string,
|
||||
newTitle: string,
|
||||
) {
|
||||
return apiPost<TransplantResult>(`/trees/${treeId}/transplant`, {
|
||||
node_id: nodeId,
|
||||
new_title: newTitle,
|
||||
});
|
||||
}
|
||||
@@ -121,6 +121,12 @@ export const ERROR_CODE_LABEL: Record<ErrorCode, string> = {
|
||||
safety_rejected: "被安全策略拒绝",
|
||||
internal_error: "服务内部错误",
|
||||
not_implemented: "该能力尚未支持",
|
||||
"HJ-1101": "命台正被占用,请求已排队",
|
||||
"HJ-1102": "归真路径与主脉分叉,需先处理分歧",
|
||||
"HJ-1103": "当前状态不允许此操作",
|
||||
"HJ-1301": "禁域裁决拒绝",
|
||||
"HJ-2001": "无可用模型端点",
|
||||
"HJ-9000": "系统不变量校验失败",
|
||||
};
|
||||
|
||||
/** 九枢九象名(契约字典 §5.9),星盘标签与速览共用 */
|
||||
|
||||
Reference in new issue
Block a user