fix(canvas): 角色提取 - 兼容带推理文本的 JSON 输出 (#164)
This commit is contained in:
@@ -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 数组");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user