From e5933471f2e3e3f2a75d248e20ada8e0c87da4b6 Mon Sep 17 00:00:00 2001 From: sigma-andex <77549848+sigma-andex@users.noreply.github.com> Date: Fri, 20 Mar 2026 09:37:04 +0000 Subject: [PATCH] Various improvements --- apps/backend/src/services/generate/prompts.ts | 24 ++++++++--- apps/backend/src/services/generate/service.ts | 43 +++++++++++-------- apps/backend/src/services/generate/types.ts | 2 + apps/webpage/src/main.ts | 1 + 4 files changed, 46 insertions(+), 24 deletions(-) diff --git a/apps/backend/src/services/generate/prompts.ts b/apps/backend/src/services/generate/prompts.ts index ad1bdc5..b8ae102 100644 --- a/apps/backend/src/services/generate/prompts.ts +++ b/apps/backend/src/services/generate/prompts.ts @@ -1,6 +1,6 @@ import type { GenerationError } from "./errors.js"; -import type { Patch } from "../vdom/index.js"; import type { Action } from "@cuttlekit/common/client"; +import type { LLMResponse } from "./types.js"; export const MAX_RETRY_ATTEMPTS = 3; @@ -20,6 +20,7 @@ COMPONENTS: Register reusable UI components with define, then use custom tags in - Use custom tags in patches: {"selector":"#root","append":""} - Define components BEFORE first use. One define per line. - Components persist across requests — do NOT re-emit define unless restyling. Check [COMPONENTS] for already-defined tags. +- When creating 3+ similar elements, ALWAYS define a component first. Repeated inline HTML wastes context — components keep page state compact. JSON ESCAPING: Use single quotes for HTML attributes to avoid escaping. CORRECT: {"html":"
"} @@ -50,6 +51,7 @@ INTERACTIVITY - NO JavaScript/onclick (won't work): Use " for JSON in data-action-data. Input values auto-sent with actions. ACTIONS: Update data only — don't redesign or restyle the UI. Exception: inherently visual actions (color pickers, theme toggles). +RESTYLING: Visual-only changes must preserve existing data — never silently replace content. IDs REQUIRED: All interactive/dynamic elements need unique id. Containers: id="todo-list". Items: id="todo-1". Buttons: id="add-btn". @@ -105,12 +107,22 @@ export const buildSystemPrompt = (deps?: PackageInfo[]): string => // Build corrective prompt for retry after error export const buildCorrectivePrompt = ( error: GenerationError, - successfulPatches: readonly Patch[] = [], + successfulOps: readonly LLMResponse[] = [], currentHtml?: string, ): string => { + const defines = successfulOps.filter((o) => o.op === "define"); + const patches = successfulOps.flatMap((o) => + o.op === "patches" ? o.patches : [], + ); + + const defined = + defines.length > 0 + ? `\nDEFINED: ${defines.map((d) => d.tag).join(", ")} (already registered — do NOT re-emit)` + : ""; + const applied = - successfulPatches.length > 0 - ? `\nAPPLIED: ${JSON.stringify(successfulPatches)}\nContinue from here.` + patches.length > 0 + ? `\nAPPLIED: ${JSON.stringify(patches)}\nContinue from here.` : ""; const pageState = currentHtml @@ -120,11 +132,11 @@ export const buildCorrectivePrompt = ( if (error._tag === "JsonParseError") { return `${pageState}JSON ERROR: ${error.message} Bad: ${error.line.slice(0, 100)} -Fix: valid JSONL, one JSON/line, single quotes in HTML attrs${applied}`; +Fix: valid JSONL, one JSON/line, single quotes in HTML attrs${defined}${applied}`; } return `${pageState}PATCH ERROR "${error.patch.selector}": ${error.reason} -Fix: selector must exist, use #id only${applied}`; +Fix: selector must exist, use #id only${defined}${applied}`; }; // Build a compact, LLM-readable description of a single action. diff --git a/apps/backend/src/services/generate/service.ts b/apps/backend/src/services/generate/service.ts index 22487a4..695406b 100644 --- a/apps/backend/src/services/generate/service.ts +++ b/apps/backend/src/services/generate/service.ts @@ -4,13 +4,14 @@ import { z } from "zod"; import type { LanguageModelConfig } from "@cuttlekit/common/server"; import { MemoryService, type MemorySearchResult } from "../memory/index.js"; import { accumulateLinesWithFlush } from "../../stream/utils.js"; -import { PatchValidator, renderCETree, getCompactHtmlFromCtx, type Patch, type ValidationContext } from "../vdom/index.js"; +import { PatchValidator, renderCETree, getCompactHtmlFromCtx, type ValidationContext } from "../vdom/index.js"; import { ModelRegistry } from "../model-registry.js"; import { loadAppConfig } from "../app-config.js"; import { PatchSchema, LLMResponseSchema, JsonParseError, + type LLMResponse, type UnifiedResponse, type UnifiedGenerateOptions, type Message, @@ -255,7 +256,7 @@ export class GenerateService extends Effect.Service()( validationCtx: ValidationContext, usageRef: Ref.Ref, ttftRef: Ref.Ref, - patchesRef: Ref.Ref, + opsRef: Ref.Ref, modeRef: Ref.Ref<"patches" | "full">, attempt: number, modelConfig: LanguageModelConfig, @@ -277,15 +278,13 @@ export class GenerateService extends Effect.Service()( requestTools, ), - // Track successful patches, mode, and log + // Track successful operations, mode, and log Stream.tap((response) => Effect.gen(function* () { - if (response.op === "patches") { - yield* Ref.update(patchesRef, (ps) => [ - ...ps, - ...response.patches, - ]); - } else if (response.op === "full") { + if (response.op !== "stats") { + yield* Ref.update(opsRef, (ops) => [...ops, response]); + } + if (response.op === "full") { yield* Ref.set(modeRef, "full"); } yield* Effect.log(`[Attempt ${attempt}] Emitting response`, { @@ -304,14 +303,18 @@ export class GenerateService extends Effect.Service()( const genError = error as GenerationError; return Stream.unwrap( Effect.gen(function* () { - const successfulPatches = yield* Ref.get(patchesRef); - yield* Ref.set(patchesRef, []); // Reset for next attempt + const successfulOps = yield* Ref.get(opsRef); + // Keep defines (persist in registry across retries), reset patches/full + yield* Ref.set( + opsRef, + successfulOps.filter((o) => o.op === "define"), + ); const compactHtml = yield* getCompactHtmlFromCtx(validationCtx); yield* Effect.log( `[Attempt ${attempt}] ${genError._tag}, retrying...`, { error: genError.message, - successfulPatches: successfulPatches.length, + successfulOps: successfulOps.length, }, ); @@ -324,7 +327,7 @@ export class GenerateService extends Effect.Service()( role: "user", content: buildCorrectivePrompt( genError, - successfulPatches, + successfulOps, compactHtml, ), }, @@ -332,7 +335,7 @@ export class GenerateService extends Effect.Service()( validationCtx, usageRef, ttftRef, - patchesRef, + opsRef, modeRef, attempt + 1, modelConfig, @@ -507,7 +510,7 @@ export class GenerateService extends Effect.Service()( // Create Refs to track state across retries const usageRef = yield* Ref.make([]); const ttftRef = yield* Ref.make(0); - const patchesRef = yield* Ref.make([]); + const opsRef = yield* Ref.make([]); const modeRef = yield* Ref.make<"patches" | "full">("patches"); const startTime = yield* DateTime.now; @@ -517,7 +520,7 @@ export class GenerateService extends Effect.Service()( validationCtx, usageRef, ttftRef, - patchesRef, + opsRef, modeRef, 0, modelConfig, @@ -577,7 +580,11 @@ export class GenerateService extends Effect.Service()( }); const mode = yield* Ref.get(modeRef); - const patches = yield* Ref.get(patchesRef); + const ops = yield* Ref.get(opsRef); + const patchCount = ops.reduce( + (n, o) => n + (o.op === "patches" ? o.patches.length : 0), + 0, + ); const ttft = yield* Ref.get(ttftRef); return { @@ -585,7 +592,7 @@ export class GenerateService extends Effect.Service()( cacheRate: Math.round(cacheRate), tokensPerSecond: Math.round(tokensPerSecond), mode, - patchCount: patches.length, + patchCount, ttft: Math.round(ttft), ttc: Math.round(elapsedMs), }; diff --git a/apps/backend/src/services/generate/types.ts b/apps/backend/src/services/generate/types.ts index 92e0c4c..1da7088 100644 --- a/apps/backend/src/services/generate/types.ts +++ b/apps/backend/src/services/generate/types.ts @@ -43,6 +43,8 @@ export const LLMResponseSchema = z.union([ DefineOpSchema, ]); +export type LLMResponse = z.infer; + // Full response schema includes stats (generated by code, not LLM) export const UnifiedResponseSchema = z.union([ LLMResponseSchema, diff --git a/apps/webpage/src/main.ts b/apps/webpage/src/main.ts index 33fa4c2..0134886 100644 --- a/apps/webpage/src/main.ts +++ b/apps/webpage/src/main.ts @@ -182,6 +182,7 @@ const app = { `TTC ${ttc}`, `${s.tokensPerSecond} tok/s`, `${s.cacheRate}% cache`, + `${s.patchCount} patches`, ].join(" · "); statsEl.style.display = "flex"; },