feat(editor): save leptos island and page aggregate alignment progress

- switch main document flow toward leptos tiptap island host and generated runtime assets

- align page aggregate loading, page head single-source updates, and AI tool result recovery

- add tests and smoke scripts for title sync, AI route recovery, and editor host cutover
This commit is contained in:
lix-2026
2026-04-22 05:57:06 +08:00
parent 5d1c94eb9e
commit 8353aea2f9
105 changed files with 14768 additions and 4186 deletions
@@ -0,0 +1,131 @@
import { describe, expect, it } from "vitest";
import {
buildHermesRuntimeToolResultRequest,
parseHermesToolPreviewArgs,
readHermesToolArgsFromEvent,
readHermesToolResultFromEvent,
} from "./tool-result-recovery";
describe("tool-result-recovery", () => {
it("应从 JSON preview 中解析工具参数", () => {
expect(
parseHermesToolPreviewArgs('{"blockId":"block_1","text":"新的正文","mode":"replace"}', "doc_replace_range"),
).toEqual({
blockId: "block_1",
text: "新的正文",
mode: "replace",
});
});
it("应从 slash preview 中恢复 text 参数", () => {
expect(parseHermesToolPreviewArgs("slash_run /rename doc-1 新标题", "slash_run")).toEqual({
text: "/rename doc-1 新标题",
});
});
it("应优先读取 Hermes 事件里直接给出的 argsJson", () => {
expect(
readHermesToolArgsFromEvent(
{
preview: "ignored",
argsJson: { text: "/rename doc-1 直接参数" },
},
"slash_run",
),
).toEqual({
text: "/rename doc-1 直接参数",
});
});
it("应读取 Hermes 事件里直接给出的结构化 result", () => {
expect(
readHermesToolResultFromEvent({
result: {
ok: true,
parsed: {
command: "rename_doc",
},
},
}),
).toEqual({
ok: true,
parsed: {
command: "rename_doc",
},
});
});
it("应构造 mnote-web runtime tool result 请求体", () => {
expect(
buildHermesRuntimeToolResultRequest({
userId: "user-1",
tool: "doc_replace_range",
argsJson: {
blockId: "block_1",
text: "新的正文",
mode: "replace",
},
data: [
{
id: "block_1",
type: "paragraph",
content: "旧正文",
},
],
requestId: "req-1",
traceId: "trace-1",
target: {
pageId: "doc-1",
blockId: "block_1",
},
}),
).toEqual({
kind: "tool",
context: {
deploymentId: null,
projectId: null,
workspaceId: null,
requestId: "req-1",
traceId: "trace-1",
actor: {
actorType: "user",
actorId: "user-1",
sessionId: null,
},
source: {
channel: "next_route",
client: "wolai-frontend",
},
tenantId: null,
authToken: null,
idempotencyKey: null,
validateOnly: false,
dryRun: false,
},
tool: {
tool: "doc_replace_range",
kind: "command",
mode: "result",
argsJson: {
blockId: "block_1",
text: "新的正文",
mode: "replace",
},
target: {
workspaceId: null,
pageId: "doc-1",
blockId: "block_1",
},
reason: null,
refs: [],
},
data: [
{
id: "block_1",
type: "paragraph",
content: "旧正文",
},
],
});
});
});
@@ -0,0 +1,165 @@
type PlainObject = Record<string, unknown>;
function isPlainObject(value: unknown): value is PlainObject {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
function safeParseObject(raw: string): PlainObject | null {
const text = raw.trim();
if (!text) return null;
try {
const parsed = JSON.parse(text) as unknown;
if (typeof parsed === "string") {
return safeParseObject(parsed);
}
return isPlainObject(parsed) ? parsed : null;
} catch {
return null;
}
}
function extractJsonCandidate(raw: string): string | null {
const text = raw.trim();
if (!text) return null;
if (text.startsWith("{") && text.endsWith("}")) {
return text;
}
const start = text.indexOf("{");
const end = text.lastIndexOf("}");
if (start === -1 || end <= start) {
return null;
}
return text.slice(start, end + 1);
}
export function parseHermesToolPreviewArgs(
preview: string,
tool?: string | null,
): PlainObject | null {
const direct = safeParseObject(preview);
if (direct) {
return direct;
}
const candidate = extractJsonCandidate(preview);
if (candidate) {
const parsed = safeParseObject(candidate);
if (parsed) {
return parsed;
}
}
if (tool === "slash_run") {
const slashMatch = preview.match(/\/(?:new|new-doc|newdoc|rename|rename-doc|renamedoc)\b[\s\S]*/i);
if (slashMatch) {
return { text: slashMatch[0].trim() };
}
}
return null;
}
export function readHermesToolArgsFromEvent(
event: unknown,
tool?: string | null,
): PlainObject | null {
if (!isPlainObject(event)) {
return null;
}
const directArgs =
event.argsJson ??
event.args_json ??
event.args;
if (isPlainObject(directArgs)) {
return directArgs;
}
if (typeof directArgs === "string") {
const parsed = parseHermesToolPreviewArgs(directArgs, tool);
if (parsed) {
return parsed;
}
}
const preview = typeof event.preview === "string" ? event.preview : "";
return parseHermesToolPreviewArgs(preview, tool);
}
export function readHermesToolResultFromEvent(event: unknown): unknown | null {
if (!isPlainObject(event)) {
return null;
}
if ("result" in event && event.result != null) {
return event.result;
}
if ("data" in event && event.data != null) {
return event.data;
}
if (typeof event.output === "string") {
const parsed = safeParseObject(event.output);
if (parsed) {
return parsed;
}
}
return null;
}
export function buildHermesRuntimeToolResultRequest(input: {
userId: string;
tool: string;
argsJson: PlainObject;
data?: unknown;
requestId: string;
traceId: string;
workspaceId?: string | null;
target?: {
workspaceId?: string | null;
pageId?: string | null;
blockId?: string | null;
} | null;
reason?: string | null;
refs?: string[];
}): PlainObject {
return {
kind: "tool",
context: {
deploymentId: null,
projectId: null,
workspaceId: input.workspaceId ?? null,
requestId: input.requestId,
traceId: input.traceId,
actor: {
actorType: "user",
actorId: input.userId,
sessionId: null,
},
source: {
channel: "next_route",
client: "wolai-frontend",
},
tenantId: null,
authToken: null,
idempotencyKey: null,
validateOnly: false,
dryRun: false,
},
tool: {
tool: input.tool,
kind: "command",
mode: "result",
argsJson: input.argsJson,
target: input.target
? {
workspaceId: input.target.workspaceId ?? null,
pageId: input.target.pageId ?? null,
blockId: input.target.blockId ?? null,
}
: null,
reason: input.reason ?? null,
refs: Array.isArray(input.refs) ? input.refs : [],
},
...(typeof input.data === "undefined" ? {} : { data: input.data }),
};
}