改写回合推演技能合同,并接上画面工作流库。
旧产物名仍能读到,避免改名后上下文对不上。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* 本机 ComfyUI 工作流库。
|
||||
* 目录:{用户数据}/image-workflows/<id>/
|
||||
* meta.json 短名、一句画面说明、提示词节点
|
||||
* workflow.json ComfyUI「API 格式」导出的节点图
|
||||
* 创作模型只看短目录;节点图留在磁盘,提交生图时才读。
|
||||
*/
|
||||
|
||||
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { getUserDataDir } from "../config/user-data-dir.js";
|
||||
|
||||
export type ImageWorkflowEntry = {
|
||||
id: string;
|
||||
name: string;
|
||||
/** 给创作模型看的一句画面说明 */
|
||||
summary: string;
|
||||
/** ComfyUI API JSON 里的节点 id */
|
||||
promptNode: string;
|
||||
/** 该节点上要覆盖的字段,CLIP 文本一般为 text */
|
||||
promptField: string;
|
||||
/** 可选:这种风格的提示词习惯,仍不是节点图 */
|
||||
promptNotes?: string;
|
||||
};
|
||||
|
||||
const DIR_NAME = "image-workflows";
|
||||
|
||||
export function getImageWorkflowsDir(): string {
|
||||
return path.join(getUserDataDir(), DIR_NAME);
|
||||
}
|
||||
|
||||
function asString(v: unknown): string | undefined {
|
||||
if (typeof v !== "string") return undefined;
|
||||
const t = v.trim();
|
||||
return t || undefined;
|
||||
}
|
||||
|
||||
function readJson(file: string): unknown | null {
|
||||
try {
|
||||
return JSON.parse(readFileSync(file, "utf8")) as unknown;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function parseEntry(folderName: string, dir: string): ImageWorkflowEntry | null {
|
||||
const metaPath = path.join(dir, "meta.json");
|
||||
const workflowPath = path.join(dir, "workflow.json");
|
||||
if (!existsSync(metaPath) || !existsSync(workflowPath)) return null;
|
||||
const meta = readJson(metaPath);
|
||||
const workflow = readJson(workflowPath);
|
||||
if (!meta || typeof meta !== "object" || Array.isArray(meta)) return null;
|
||||
if (!workflow || typeof workflow !== "object" || Array.isArray(workflow)) return null;
|
||||
const row = meta as Record<string, unknown>;
|
||||
const id = asString(row.id) ?? folderName.trim();
|
||||
const name = asString(row.name);
|
||||
const summary = asString(row.summary);
|
||||
const promptNode = asString(row.prompt_node) ?? asString(row.promptNode);
|
||||
const promptField = asString(row.prompt_field) ?? asString(row.promptField);
|
||||
if (!id || !name || !summary || !promptNode || !promptField) return null;
|
||||
const promptNotes = asString(row.prompt_notes) ?? asString(row.promptNotes);
|
||||
return {
|
||||
id,
|
||||
name,
|
||||
summary,
|
||||
promptNode,
|
||||
promptField,
|
||||
...(promptNotes ? { promptNotes } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
/** 扫描目录。缺文件或字段不全的文件夹跳过。 */
|
||||
export function listImageWorkflows(): ImageWorkflowEntry[] {
|
||||
const root = getImageWorkflowsDir();
|
||||
if (!existsSync(root)) return [];
|
||||
const out: ImageWorkflowEntry[] = [];
|
||||
let names: string[] = [];
|
||||
try {
|
||||
names = readdirSync(root);
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
for (const name of names) {
|
||||
if (!name || name.startsWith(".")) continue;
|
||||
const dir = path.join(root, name);
|
||||
try {
|
||||
if (!statSync(dir).isDirectory()) continue;
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
const entry = parseEntry(name, dir);
|
||||
if (entry) out.push(entry);
|
||||
}
|
||||
out.sort((a, b) => a.name.localeCompare(b.name, "zh"));
|
||||
return out;
|
||||
}
|
||||
|
||||
export function getImageWorkflow(id: string): ImageWorkflowEntry | null {
|
||||
const key = id.trim();
|
||||
if (!key) return null;
|
||||
return listImageWorkflows().find((entry) => entry.id === key) ?? null;
|
||||
}
|
||||
Reference in New Issue
Block a user