| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104 |
- import type { Spec } from "./types";
- /**
- * Options for building a user prompt.
- */
- export interface UserPromptOptions {
- /** The user's text prompt */
- prompt: string;
- /** Existing spec to refine (triggers patch-only mode) */
- currentSpec?: Spec | null;
- /** Runtime state context to include */
- state?: Record<string, unknown> | null;
- /** Maximum length for the user's text prompt (applied before wrapping) */
- maxPromptLength?: number;
- }
- /**
- * Check whether a spec is non-empty (has a root and at least one element).
- */
- function isNonEmptySpec(spec: unknown): spec is Spec {
- if (!spec || typeof spec !== "object") return false;
- const s = spec as Record<string, unknown>;
- return (
- typeof s.root === "string" &&
- typeof s.elements === "object" &&
- s.elements !== null &&
- Object.keys(s.elements as object).length > 0
- );
- }
- const PATCH_INSTRUCTIONS = `IMPORTANT: The current UI is already loaded. Output ONLY the patches needed to make the requested change:
- - To add a new element: {"op":"add","path":"/elements/new-key","value":{...}}
- - To modify an existing element: {"op":"replace","path":"/elements/existing-key","value":{...}}
- - To remove an element: {"op":"remove","path":"/elements/old-key"}
- - To update the root: {"op":"replace","path":"/root","value":"new-root-key"}
- - To add children: update the parent element with new children array
- DO NOT output patches for elements that don't need to change. Only output what's necessary for the requested modification.`;
- /**
- * Build a user prompt for AI generation.
- *
- * Handles common patterns that every consuming app needs:
- * - Truncating the user's prompt to a max length
- * - Including the current spec for refinement (patch-only mode)
- * - Including runtime state context
- *
- * @example
- * ```ts
- * // Fresh generation
- * buildUserPrompt({ prompt: "create a todo app" })
- *
- * // Refinement with existing spec
- * buildUserPrompt({ prompt: "add a dark mode toggle", currentSpec: spec })
- *
- * // With state context
- * buildUserPrompt({ prompt: "show my data", state: { todos: [] } })
- * ```
- */
- export function buildUserPrompt(options: UserPromptOptions): string {
- const { prompt, currentSpec, state, maxPromptLength } = options;
- // Sanitize and optionally truncate the user's text
- let userText = String(prompt || "");
- if (maxPromptLength !== undefined && maxPromptLength > 0) {
- userText = userText.slice(0, maxPromptLength);
- }
- // --- Refinement mode: currentSpec is provided ---
- if (isNonEmptySpec(currentSpec)) {
- const parts: string[] = [];
- parts.push(
- `CURRENT UI STATE (already loaded, DO NOT recreate existing elements):`,
- );
- parts.push(JSON.stringify(currentSpec, null, 2));
- parts.push("");
- parts.push(`USER REQUEST: ${userText}`);
- // Append state context if provided
- if (state && Object.keys(state).length > 0) {
- parts.push("");
- parts.push(`AVAILABLE STATE:\n${JSON.stringify(state, null, 2)}`);
- }
- parts.push("");
- parts.push(PATCH_INSTRUCTIONS);
- return parts.join("\n");
- }
- // --- Fresh generation mode ---
- const parts: string[] = [userText];
- if (state && Object.keys(state).length > 0) {
- parts.push(`\nAVAILABLE STATE:\n${JSON.stringify(state, null, 2)}`);
- }
- parts.push(
- `\nRemember: Output /root first, then interleave /elements and /state patches so the UI fills in progressively as it streams. Output each state patch right after the elements that use it, one per array item.`,
- );
- return parts.join("\n");
- }
|