UNPKG

@paroicms/site-generator-plugin

Version:

ParoiCMS Site Generator Plugin

241 lines (240 loc) 9.31 kB
import { loadStep, readStepSchema } from "../../db/db-read.queries.js"; import { insertStep, saveCompletedSchemaStep, updateStep, } from "../../db/db-write.queries.js"; import { invokeClaude } from "../lib/calling-llm-anthropic.js"; import { createPromptTemplate, getPredefinedFields, getSiteSchemaTsDefs, } from "../lib/create-prompt.js"; import { debugLlmOutput } from "../lib/debug-utils.js"; import { parseLlmResponseAsProperties } from "../lib/parse-llm-response.js"; import { safeCallStep } from "../lib/session-utils.js"; const prompt1Tpl = await createPromptTemplate({ filename: "update-site-schema-1-write-details.md", }); const prompt2Tpl = await createPromptTemplate({ filename: "update-site-schema-2-execute.md", }); export async function startUpdateSiteSchema(ctx, input) { const fromStepSchema = await readStepSchema(ctx, input.fromStepNumber); const stepHandle = await insertStep(ctx, { kind: "updateSchema", status: "pending", currentActivity: "updating1", }); safeCallStep(ctx, stepHandle, () => invokeUpdateSiteSchema(ctx, stepHandle, { prompt: input.prompt, fromStepSchema })); return await loadStep(ctx, stepHandle.stepNumber); } export async function invokeUpdateSiteSchema(ctx, stepHandle, input, { asRemainingOf } = {}) { const { properties, llmReport: llmReport1 } = await invokeUpdateSiteSchemaStep1(ctx, input, stepHandle); if (!properties.taskDetailsMd) { await updateStep(ctx, stepHandle, { status: "noEffect", currentActivity: null, explanation: properties.explanation, }); return; } await updateStep(ctx, stepHandle, { currentActivity: "updating2", explanation: properties.explanation, }); const { stepSchema, llmReport: llmReport2 } = await invokeUpdateSiteSchemaStep2(ctx, { taskDetailsMd: properties.taskDetailsMd, fromStepSchema: input.fromStepSchema, }, stepHandle); const completedValues = { ...stepSchema, status: "completed", inputTokenCount: llmReport1.inputTokenCount + llmReport2.inputTokenCount, outputTokenCount: (llmReport1.outputTokenCount ?? 0) + (llmReport2.outputTokenCount ?? 0), promptTitle: undefined, // TODO: implement prompt title }; if (asRemainingOf) { completedValues.inputTokenCount += asRemainingOf.inputTokenCount; completedValues.outputTokenCount += asRemainingOf.outputTokenCount; completedValues.promptTitle = undefined; if (asRemainingOf.explanation) { completedValues.explanation = properties.explanation ? `${asRemainingOf.explanation}\n\n${properties.explanation}` : asRemainingOf.explanation; } } await saveCompletedSchemaStep(ctx, stepHandle, completedValues); } async function invokeUpdateSiteSchemaStep1(ctx, input, stepHandle) { const llmTaskName = "update-step1"; const llmInput = { siteSchemaTsDefs: getSiteSchemaTsDefs(), predefinedFields: JSON.stringify(getPredefinedFields(), undefined, 2), siteSchemaJson: JSON.stringify(input.fromStepSchema.siteSchema, undefined, 2), l10nJson: JSON.stringify(input.fromStepSchema.l10n, undefined, 2), updateMessage: input.prompt, }; const debug = await debugLlmOutput(ctx, llmTaskName, ctx.anthropicModelName, stepHandle, { updateMessage: llmInput.updateMessage, siteSchemaJson: llmInput.siteSchemaJson, l10nJson: llmInput.l10nJson, }); let llmOutput = debug.stored; if (!llmOutput) { // Create formatted messages using the template const message = prompt1Tpl(llmInput); // Call the model const { messageContent, report } = await invokeClaude(ctx, { llmTaskName, prompt: message, maxTokens: 1_500, temperature: 0.1, systemInstruction: "beSmart", }); llmOutput = await debug.getMessageContent(messageContent, report); } const properties = parseLlmResponseAsProperties(llmOutput.output, [ { tagName: "task_details_md", key: "taskDetailsMd", format: "markdown", optional: true, }, { tagName: "explanation_md", key: "explanation", format: "markdown", }, ]); return { properties, llmReport: llmOutput.llmReport, }; } async function invokeUpdateSiteSchemaStep2(ctx, input, stepHandle) { const llmTaskName = "update-step2"; const llmInput = { siteSchemaTsDefs: getSiteSchemaTsDefs(), predefinedFields: JSON.stringify(getPredefinedFields(), undefined, 2), siteSchemaJson: JSON.stringify(input.fromStepSchema.siteSchema, undefined, 2), l10nJson: JSON.stringify(input.fromStepSchema.l10n, undefined, 2), taskDetailsMd: input.taskDetailsMd, }; const debug = await debugLlmOutput(ctx, llmTaskName, ctx.anthropicModelName, stepHandle, { taskDetailsMd: llmInput.taskDetailsMd, siteSchemaJson: llmInput.siteSchemaJson, l10nJson: llmInput.l10nJson, }); let llmOutput = debug.stored; if (!llmOutput) { // Create formatted messages using the template const message = prompt2Tpl(llmInput); // Call the model const { messageContent, report } = await invokeClaude(ctx, { llmTaskName, prompt: message, maxTokens: 7_000, temperature: 0.1, systemInstruction: "beSmart", }); llmOutput = await debug.getMessageContent(messageContent, report); } const parsed = parseLlmResponseAsProperties(llmOutput.output, [ { tagName: "updated_site_schema_json", key: "siteSchema", format: "json", optional: true, }, { tagName: "updated_l10n_json", key: "l10n", format: "json", optional: true, }, ]); const result = { ...input.fromStepSchema, }; if (parsed.siteSchema) { result.siteSchema = fixSiteSchema(parsed.siteSchema); } if (parsed.l10n) { result.l10n = parsed.l10n; } return { stepSchema: result, llmReport: llmOutput.llmReport, }; } function fixSiteSchema(siteSchema) { const nodeTypes = siteSchema.nodeTypes ?? []; for (const nodeType of nodeTypes) { if (!nodeType.fields) continue; // Add Tiptap plugin to fields with renderAs "html" for (const field of nodeType.fields) { if (typeof field !== "string" && field.renderAs === "html" && field.storedAs !== "labeling" && field.storedAs !== "partField") { field.dataType = "json"; field.plugin = "@paroicms/tiptap-editor-plugin"; } } // Move labeling fields to the beginning of the fields array const labelingFields = nodeType.fields.filter((f) => typeof f !== "string" && f.storedAs === "labeling"); if (labelingFields.length > 0) { nodeType.fields = [ ...labelingFields, ...nodeType.fields.filter((f) => typeof f === "string" || f.storedAs !== "labeling"), ]; } } validatePartFieldReferences(nodeTypes); setMenuPlacementOnTaxonomies(nodeTypes); return siteSchema; } function validatePartFieldReferences(nodeTypes) { if (!nodeTypes) return; const partTypeNames = new Set(nodeTypes.filter((nt) => nt.kind === "part").map((nt) => nt.typeName)); for (const nodeType of nodeTypes) { if (!nodeType.fields) continue; nodeType.fields = nodeType.fields.filter((field) => { if (typeof field === "string") return true; if (field.storedAs !== "partField") return true; if (typeof field.partType !== "string" || !partTypeNames.has(field.partType)) { return false; } return true; }); } } function setMenuPlacementOnTaxonomies(nodeTypes) { if (!nodeTypes) return; const taxonomyTypeNames = new Set(); for (const nodeType of nodeTypes) { if (!nodeType.fields) continue; for (const field of nodeType.fields) { if (typeof field !== "string" && field.storedAs === "labeling") { taxonomyTypeNames.add(field.taxonomy); } } } const homeNode = nodeTypes.find((nt) => nt.kind === "document" && nt.typeName === "home"); if (homeNode?.kind !== "document") return; const homeRoutingChildren = new Set(homeNode.routingChildren ?? []); for (const nodeType of nodeTypes) { if (nodeType.kind !== "document") continue; if (nodeType.documentKind !== "routing") continue; if (nodeType.adminUi) continue; if (!taxonomyTypeNames.has(nodeType.typeName)) continue; if (homeRoutingChildren.has(nodeType.typeName)) continue; nodeType.adminUi = { menuPlacement: "popup" }; } }