prompt.ts 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. import type { Spec } from "./types";
  2. /**
  3. * Options for building a user prompt.
  4. */
  5. export interface UserPromptOptions {
  6. /** The user's text prompt */
  7. prompt: string;
  8. /** Existing spec to refine (triggers patch-only mode) */
  9. currentSpec?: Spec | null;
  10. /** Runtime state context to include */
  11. state?: Record<string, unknown> | null;
  12. /** Maximum length for the user's text prompt (applied before wrapping) */
  13. maxPromptLength?: number;
  14. }
  15. /**
  16. * Check whether a spec is non-empty (has a root and at least one element).
  17. */
  18. function isNonEmptySpec(spec: unknown): spec is Spec {
  19. if (!spec || typeof spec !== "object") return false;
  20. const s = spec as Record<string, unknown>;
  21. return (
  22. typeof s.root === "string" &&
  23. typeof s.elements === "object" &&
  24. s.elements !== null &&
  25. Object.keys(s.elements as object).length > 0
  26. );
  27. }
  28. const PATCH_INSTRUCTIONS = `IMPORTANT: The current UI is already loaded. Output ONLY the patches needed to make the requested change:
  29. - To add a new element: {"op":"add","path":"/elements/new-key","value":{...}}
  30. - To modify an existing element: {"op":"replace","path":"/elements/existing-key","value":{...}}
  31. - To remove an element: {"op":"remove","path":"/elements/old-key"}
  32. - To update the root: {"op":"replace","path":"/root","value":"new-root-key"}
  33. - To add children: update the parent element with new children array
  34. DO NOT output patches for elements that don't need to change. Only output what's necessary for the requested modification.`;
  35. /**
  36. * Build a user prompt for AI generation.
  37. *
  38. * Handles common patterns that every consuming app needs:
  39. * - Truncating the user's prompt to a max length
  40. * - Including the current spec for refinement (patch-only mode)
  41. * - Including runtime state context
  42. *
  43. * @example
  44. * ```ts
  45. * // Fresh generation
  46. * buildUserPrompt({ prompt: "create a todo app" })
  47. *
  48. * // Refinement with existing spec
  49. * buildUserPrompt({ prompt: "add a dark mode toggle", currentSpec: spec })
  50. *
  51. * // With state context
  52. * buildUserPrompt({ prompt: "show my data", state: { todos: [] } })
  53. * ```
  54. */
  55. export function buildUserPrompt(options: UserPromptOptions): string {
  56. const { prompt, currentSpec, state, maxPromptLength } = options;
  57. // Sanitize and optionally truncate the user's text
  58. let userText = String(prompt || "");
  59. if (maxPromptLength !== undefined && maxPromptLength > 0) {
  60. userText = userText.slice(0, maxPromptLength);
  61. }
  62. // --- Refinement mode: currentSpec is provided ---
  63. if (isNonEmptySpec(currentSpec)) {
  64. const parts: string[] = [];
  65. parts.push(
  66. `CURRENT UI STATE (already loaded, DO NOT recreate existing elements):`,
  67. );
  68. parts.push(JSON.stringify(currentSpec, null, 2));
  69. parts.push("");
  70. parts.push(`USER REQUEST: ${userText}`);
  71. // Append state context if provided
  72. if (state && Object.keys(state).length > 0) {
  73. parts.push("");
  74. parts.push(`AVAILABLE STATE:\n${JSON.stringify(state, null, 2)}`);
  75. }
  76. parts.push("");
  77. parts.push(PATCH_INSTRUCTIONS);
  78. return parts.join("\n");
  79. }
  80. // --- Fresh generation mode ---
  81. const parts: string[] = [userText];
  82. if (state && Object.keys(state).length > 0) {
  83. parts.push(`\nAVAILABLE STATE:\n${JSON.stringify(state, null, 2)}`);
  84. }
  85. parts.push(
  86. `\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.`,
  87. );
  88. return parts.join("\n");
  89. }