fix(canvas): 角色提取 - 兼容带推理文本的 JSON 输出 (#164)

This commit is contained in:
dyhhhhh
2026-08-07 10:27:02 +08:00
committed by GitHub
parent 21850405ad
commit a2e0814265
3 changed files with 97 additions and 12 deletions

View File

@@ -75,19 +75,59 @@ export function normalizeCharacterName(value?: string) {
return (value || "").toLocaleLowerCase("zh-CN").replace(/^角色[::]\s*/, "").replace(/[\s·•・._-]+/g, "").trim();
}
function findJsonValueEnd(source: string, start: number) {
const stack: string[] = [];
let inString = false;
let escaped = false;
for (let index = start; index < source.length; index += 1) {
const character = source[index];
if (inString) {
if (escaped) {
escaped = false;
} else if (character === "\\") {
escaped = true;
} else if (character === '"') {
inString = false;
}
continue;
}
if (character === '"') {
inString = true;
continue;
}
if (character === "{" || character === "[") {
stack.push(character);
continue;
}
if (character !== "}" && character !== "]") continue;
const opener = stack.pop();
if ((character === "}" && opener !== "{") || (character === "]" && opener !== "[")) return -1;
if (!stack.length) return index;
}
return -1;
}
function extractCharacterBreakdownJson(raw: string) {
for (let start = 0; start < raw.length; start += 1) {
if (raw[start] !== "{" && raw[start] !== "[") continue;
const end = findJsonValueEnd(raw, start);
if (end < start) continue;
try {
const parsed: unknown = JSON.parse(raw.slice(start, end + 1));
// 模型的推理文字可能包含 aliases: [] 等合法 JSON 片段;角色契约只接受带 characters 字段的对象。
const candidates = parsed && typeof parsed === "object" && !Array.isArray(parsed) ? (parsed as { characters?: unknown }).characters : undefined;
if (Array.isArray(candidates)) return parsed;
} catch {
// Ignore unrelated braces in model prose and continue to the next complete JSON value.
}
}
throw new Error("角色拆解没有返回符合契约的 JSON");
}
export function parseCharacterBreakdown(raw: string): CharacterBreakdown[] {
const unfenced = raw.trim().replace(/^```(?:json)?\s*/i, "").replace(/\s*```$/i, "");
const starts = [unfenced.indexOf("{"), unfenced.indexOf("[")].filter((index) => index >= 0);
const start = starts.length ? Math.min(...starts) : -1;
const end = Math.max(unfenced.lastIndexOf("}"), unfenced.lastIndexOf("]"));
if (start < 0 || end < start) throw new Error("角色拆解没有返回可识别的 JSON");
let parsed: unknown;
try {
parsed = JSON.parse(unfenced.slice(start, end + 1));
} catch (error) {
throw new Error(`角色拆解结果格式不正确:${error instanceof Error ? error.message : "无法解析 JSON"}`);
}
const parsed = extractCharacterBreakdownJson(unfenced);
const candidates = Array.isArray(parsed) ? parsed : parsed && typeof parsed === "object" ? (parsed as { characters?: unknown }).characters : undefined;
if (!Array.isArray(candidates)) throw new Error("角色拆解结果缺少 characters 数组");