添加全能创作功能

This commit is contained in:
Azmat@qq.com
2026-09-03 13:11:46 +08:00
parent 22ed2833ad
commit 6a628b0ca7
53 changed files with 9115 additions and 108 deletions
+127
View File
@@ -29,6 +29,10 @@ import type {
ImageConversation,
ImageConversationTrash,
ImageConversationTask,
CreationConversation,
CreationConversationDetail,
CreationMessage,
CreationRef,
ModelConfig,
ModelEntity,
Notification,
@@ -530,6 +534,129 @@ export const api = {
}
}
},
// ── 全能创作(契约见仓库根 `全能创作-契约-2026-09-02.md`)
/** @ 检索:输入框打 @ 或用户说了名字时用。返回的整条 Ref 要原样带进 send 的 refs。 */
searchMentions(params: { q?: string; types?: CreationRef["type"][]; limit?: number } = {}) {
const query = new URLSearchParams();
if (params.q) query.set("q", params.q);
if (params.types?.length) query.set("types", params.types.join(","));
if (params.limit) query.set("limit", String(params.limit));
const suffix = query.toString();
return request<{ results: CreationRef[]; type_labels: Record<string, string> }>(
`/api/ai/mentions/${suffix ? `?${suffix}` : ""}`
);
},
listCreations(params: { mode?: "video" | "image"; status?: string } = {}) {
const query = new URLSearchParams();
if (params.mode) query.set("mode", params.mode);
if (params.status) query.set("status", params.status);
const suffix = query.toString();
return request<Paginated<CreationConversation>>(`/api/ai/creations/${suffix ? `?${suffix}` : ""}`);
},
createCreation(payload: {
title?: string;
mode: "video" | "image";
preset?: string;
params?: Record<string, string>;
}) {
return request<CreationConversation>("/api/ai/creations/", {
method: "POST",
body: JSON.stringify(payload)
});
},
getCreation(id: string) {
return request<CreationConversationDetail>(`/api/ai/creations/${id}/`);
},
renameCreation(id: string, title: string) {
return request<CreationConversation>(`/api/ai/creations/${id}/`, {
method: "PATCH",
body: JSON.stringify({ title })
});
},
updateCreation(id: string, payload: { title?: string; params?: Record<string, string> }) {
return request<CreationConversation>(`/api/ai/creations/${id}/`, {
method: "PATCH",
body: JSON.stringify(payload)
});
},
deleteCreation(id: string) {
return request<void>(`/api/ai/creations/${id}/`, { method: "DELETE" });
},
/** 增量拉消息:轮询生成结果时只补 after_seq 之后的,不重拉整条会话。 */
creationMessages(id: string, afterSeq?: number) {
const suffix = afterSeq === undefined ? "" : `?after_seq=${afterSeq}`;
return request<CreationMessage[]>(`/api/ai/creations/${id}/messages/${suffix}`);
},
/**
* 点确认闸门 → 直接出片。**这个不是 SSE**:后端不跑模型,按方案卡存好的
* video_prompt 直接提交,同步返回「生成中」消息,之后靠轮询转成结果。
*/
confirmCreationPlan(id: string, replyTo: string, params?: Record<string, string>) {
return request<{ message: CreationMessage }>(`/api/ai/creations/${id}/send/`, {
method: "POST",
body: JSON.stringify({ kind: "confirm", reply_to: replyTo, params })
});
},
/**
* 发一条消息 → SSE 流。事件:tool / reasoning / delta / message / task / credits / done / error。
* 和 agentScriptStream 同一套 fetch + ReadableStream(EventSource 只支持 GET,这里要 POST 带 body)。
*/
async creationSendStream(
id: string,
payload: {
kind?: "text" | "elicit_answer";
text?: string;
refs?: CreationRef[];
reply_to?: string;
answers?: Record<string, string | string[]>;
model_config_id?: string;
params?: Record<string, string>;
},
onEvent: (evt: { type: string; [k: string]: unknown }) => void,
signal?: AbortSignal
): Promise<void> {
const token = getToken();
const headers = new Headers({ "Content-Type": "application/json", Accept: "text/event-stream" });
if (token) headers.set("Authorization", `Token ${token}`);
const response = await fetch(`${API_BASE}/api/ai/creations/${id}/send/`, {
method: "POST",
headers,
body: JSON.stringify(payload),
signal
});
if (!response.ok || !response.body) {
const text = await response.text().catch(() => "");
let message = text || "发送失败";
try {
const data = JSON.parse(text) as Record<string, unknown>;
if (typeof data.detail === "string") message = data.detail;
} catch {
/* 非 JSON 错误体,用原文 */
}
throw new ApiError(response.status, message);
}
const reader = response.body.getReader();
const decoder = new TextDecoder("utf-8");
let buffer = "";
for (;;) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
let sep: number;
while ((sep = buffer.indexOf("\n\n")) !== -1) {
const frame = buffer.slice(0, sep);
buffer = buffer.slice(sep + 2);
const dataLine = frame.split("\n").find((l) => l.startsWith("data:"));
if (!dataLine) continue;
try {
onEvent(JSON.parse(dataLine.slice(5).trim()));
} catch {
/* 跳过解析失败的帧 */
}
}
}
},
adoptScript(projectId: string, script_version_id: string) {
return request<ScriptVersion>(`/api/projects/${projectId}/adopt-script/`, {
method: "POST",