Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions apps/backend/src/services/generate/prompts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,19 +104,24 @@ export const buildSystemPrompt = (deps?: PackageInfo[]): string =>
export const buildCorrectivePrompt = (
error: GenerationError,
successfulPatches: readonly Patch[] = [],
currentHtml?: string,
): string => {
const applied =
successfulPatches.length > 0
? `\nAPPLIED: ${JSON.stringify(successfulPatches)}\nContinue from here.`
: "";

const pageState = currentHtml
? `\nCURRENT PAGE STATE:\n${currentHtml}\n`
: "";

if (error._tag === "JsonParseError") {
return `JSON ERROR: ${error.message}
return `${pageState}JSON ERROR: ${error.message}
Bad: ${error.line.slice(0, 100)}
Fix: valid JSONL, one JSON/line, single quotes in HTML attrs${applied}`;
}

return `PATCH ERROR "${error.patch.selector}": ${error.reason}
return `${pageState}PATCH ERROR "${error.patch.selector}": ${error.reason}
Fix: selector must exist, use #id only${applied}`;
};

Expand Down
4 changes: 3 additions & 1 deletion apps/backend/src/services/generate/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { streamText, type TextStreamPart } from "ai";
import type { LanguageModelConfig } from "@cuttlekit/common/server";
import { MemoryService, type MemorySearchResult } from "../memory/index.js";
import { accumulateLinesWithFlush } from "../../stream/utils.js";
import { PatchValidator, renderCETree, type Patch, type ValidationContext } from "../vdom/index.js";
import { PatchValidator, renderCETree, getCompactHtmlFromCtx, type Patch, type ValidationContext } from "../vdom/index.js";
import { ModelRegistry } from "../model-registry.js";
import {
PatchSchema,
Expand Down Expand Up @@ -305,6 +305,7 @@ export class GenerateService extends Effect.Service<GenerateService>()(
Effect.gen(function* () {
const successfulPatches = yield* Ref.get(patchesRef);
yield* Ref.set(patchesRef, []); // Reset for next attempt
const compactHtml = yield* getCompactHtmlFromCtx(validationCtx);
yield* Effect.log(
`[Attempt ${attempt}] ${genError._tag}, retrying...`,
{
Expand All @@ -323,6 +324,7 @@ export class GenerateService extends Effect.Service<GenerateService>()(
content: buildCorrectivePrompt(
genError,
successfulPatches,
compactHtml,
),
},
],
Expand Down
34 changes: 33 additions & 1 deletion apps/backend/src/services/vdom/patch-validator.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Effect, Data, pipe } from "effect";
import { Window } from "happy-dom";
import { Window, HTMLElement as HappyHTMLElement } from "happy-dom";
import { applyPatch, type Patch } from "@cuttlekit/common/client";
import {
makeCEShell,
Expand Down Expand Up @@ -203,3 +203,35 @@ export class PatchValidator extends Effect.Service<PatchValidator>()(
}),
},
) {}

/**
* Extract compact HTML from a ValidationContext.
* Clones the DOM and strips CE template content, keeping only data-children content.
*/
export const getCompactHtmlFromCtx = (ctx: ValidationContext) =>
Effect.gen(function* () {
if (ctx.registry.size === 0) return ctx.window.document.body.innerHTML;

// Clone to avoid mutating real VDOM
const clone = yield* Effect.sync(() =>
ctx.window.document.body.cloneNode(true) as unknown as HappyHTMLElement
);

// Strip rendered CE template content, keeping only data-children content
yield* pipe(
[...ctx.registry.keys()],
Effect.forEach((tag) =>
pipe(
[...clone.querySelectorAll(tag)],
Effect.forEach((el) =>
Effect.sync(() => {
const childrenContainer = el.querySelector("[data-children]");
el.innerHTML = childrenContainer ? childrenContainer.innerHTML : "";
}),
),
),
),
);

return clone.innerHTML;
});
Loading