@kubuild/ai 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -64,6 +64,13 @@ var KubuildAiEngine = class {
64
64
  Tone: ${request.tone}`;
65
65
  if (request.locale) userPrompt += `
66
66
  Language/Locale: ${request.locale}`;
67
+ if (request.conversationHistory && request.conversationHistory.length > 0) {
68
+ const chatContext = request.conversationHistory.map((m) => `${m.role.toUpperCase()}: ${getMessageText(m)}`).join("\n");
69
+ userPrompt += `
70
+
71
+ Prior Conversation Discussion Context:
72
+ ${chatContext}`;
73
+ }
67
74
  const jsonSchema = buildJsonSchemaForMode("full-page");
68
75
  const result = await this.options.adapter.generate({
69
76
  systemPrompt,
@@ -202,18 +209,18 @@ ${JSON.stringify(request.node, null, 2)}`;
202
209
  };
203
210
  }
204
211
  }
205
- /**
206
- * Progressive Section Streaming Generator.
207
- * Emits structured SSE events: status -> metadata -> section (one by one) -> complete.
208
- */
209
- async *streamPage(request, context) {
210
- try {
211
- this.log("info", `[SSE] Starting streamPage for prompt: "${request.prompt}"`);
212
- yield {
213
- type: "status",
214
- message: "Analyzing requirements and planning page sections..."
215
- };
216
- const planSystemPrompt = `
212
+ buildPlanPrompts(request) {
213
+ let sectionGuidance = "Plan between 4 to 6 cohesive, essential sections (e.g., hero, features, testimonials, pricing, cta, footer) that fulfill the request thoroughly.";
214
+ if (typeof request.sectionCount === "number") {
215
+ sectionGuidance = `Plan exactly ${request.sectionCount} cohesive sections that fulfill the request.`;
216
+ } else if (request.sectionCount?.min || request.sectionCount?.max) {
217
+ const min = request.sectionCount.min ?? 3;
218
+ const max = request.sectionCount.max ?? 8;
219
+ sectionGuidance = `Plan between ${min} and ${max} cohesive sections that fulfill the request.`;
220
+ } else {
221
+ sectionGuidance += " If the user request mentions a specific number or list of sections, respect the user request.";
222
+ }
223
+ const planSystemPrompt = `
217
224
  You are a web architect for the KUBUILD page builder.
218
225
  Given the user's prompt, plan the website structure. Output pure JSON (no markdown fences, no explanatory text):
219
226
  {
@@ -235,44 +242,113 @@ Given the user's prompt, plan the website structure. Output pure JSON (no markdo
235
242
  }
236
243
  ]
237
244
  }
238
- Plan 3 cohesive, essential sections (e.g., hero, features, and cta) that fulfill the request.
245
+ ${sectionGuidance}
239
246
  `;
240
- let planUserPrompt = `User Request: ${request.prompt}`;
241
- if (request.stylePreference) {
242
- planUserPrompt += `
247
+ let planUserPrompt = `User Request: ${request.prompt}`;
248
+ if (request.stylePreference) {
249
+ planUserPrompt += `
243
250
  Style Preference: ${request.stylePreference}`;
244
- }
245
- if (request.tone) planUserPrompt += `
251
+ }
252
+ if (request.tone) planUserPrompt += `
246
253
  Tone: ${request.tone}`;
247
- if (request.locale) planUserPrompt += `
254
+ if (request.locale) planUserPrompt += `
248
255
  Locale: ${request.locale}`;
249
- this.log("info", "[SSE] Generating website layout plan...");
256
+ if (request.conversationHistory && request.conversationHistory.length > 0) {
257
+ const chatContext = request.conversationHistory.map((m) => `${m.role.toUpperCase()}: ${getMessageText(m)}`).join("\n");
258
+ planUserPrompt += `
259
+
260
+ Prior Conversation Discussion Context:
261
+ ${chatContext}`;
262
+ }
263
+ return { systemPrompt: planSystemPrompt, userPrompt: planUserPrompt };
264
+ }
265
+ async planPage(request, context) {
266
+ let rawText = "";
267
+ try {
268
+ const { systemPrompt, userPrompt } = this.buildPlanPrompts(request);
269
+ this.log("info", `Planning website layout for: "${request.prompt}"`);
250
270
  const planResult = await this.options.adapter.generate({
251
- systemPrompt: planSystemPrompt,
252
- userPrompt: planUserPrompt,
271
+ systemPrompt,
272
+ userPrompt,
253
273
  signal: context?.signal
254
274
  });
255
- this.log("debug", "[SSE] Raw plan response from model", planResult.text);
275
+ rawText = planResult.text;
276
+ const plan = extractJsonFromResponse(rawText);
277
+ return {
278
+ success: true,
279
+ data: plan,
280
+ usage: planResult.usage,
281
+ rawModelResponse: rawText
282
+ };
283
+ } catch (err) {
284
+ const message = err instanceof Error ? err.message : String(err);
285
+ this.log("warn", "Failed to generate plan JSON, using fallback plan", message);
286
+ const fallbackPlan = {
287
+ title: "AI Generated Page",
288
+ description: "Generated by KUBUILD AI",
289
+ sections: [
290
+ { type: "hero", title: "Hero Banner", prompt: `Hero section for: ${request.prompt}` },
291
+ { type: "features", title: "Key Features", prompt: `Features grid for: ${request.prompt}` },
292
+ { type: "testimonials", title: "Testimonials", prompt: `Social proof and customer reviews for: ${request.prompt}` },
293
+ { type: "cta", title: "Call To Action", prompt: `Call to action section for: ${request.prompt}` },
294
+ { type: "footer", title: "Footer", prompt: `Footer navigation and copyright for: ${request.prompt}` }
295
+ ]
296
+ };
297
+ return {
298
+ success: true,
299
+ data: fallbackPlan,
300
+ rawModelResponse: rawText || void 0
301
+ };
302
+ }
303
+ }
304
+ /**
305
+ * Progressive Section Streaming Generator.
306
+ * Emits structured SSE events: status -> metadata -> section (one by one) -> complete.
307
+ */
308
+ async *streamPage(request, context) {
309
+ try {
310
+ this.log("info", `[SSE] Starting streamPage for prompt: "${request.prompt}"`);
311
+ yield {
312
+ type: "status",
313
+ message: "Analyzing requirements and planning page sections..."
314
+ };
256
315
  let plan;
257
- try {
258
- plan = extractJsonFromResponse(planResult.text);
259
- this.log("debug", "[SSE] Parsed plan successfully", plan);
260
- } catch (parseErr) {
261
- this.log("warn", "[SSE] Failed to parse plan JSON, using fallback plan", parseErr);
262
- plan = {
263
- title: "AI Generated Page",
264
- description: "Generated by KUBUILD AI",
265
- sections: [
266
- { type: "hero", title: "Hero Banner", prompt: `Hero section for: ${request.prompt}` },
267
- { type: "features", title: "Key Features", prompt: `Features grid for: ${request.prompt}` },
268
- { type: "cta", title: "Call To Action", prompt: `Call to action section for: ${request.prompt}` }
269
- ]
270
- };
316
+ if (request.plan && Array.isArray(request.plan.sections) && request.plan.sections.length > 0) {
317
+ plan = request.plan;
318
+ this.log("info", `[SSE] Using approved pre-planned structure with ${plan.sections?.length ?? 0} sections`);
319
+ } else {
320
+ const { systemPrompt: planSystemPrompt, userPrompt: planUserPrompt } = this.buildPlanPrompts(request);
321
+ this.log("info", "[SSE] Generating website layout plan...");
322
+ const planResult = await this.options.adapter.generate({
323
+ systemPrompt: planSystemPrompt,
324
+ userPrompt: planUserPrompt,
325
+ signal: context?.signal
326
+ });
327
+ this.log("debug", "[SSE] Raw plan response from model", planResult.text);
328
+ try {
329
+ plan = extractJsonFromResponse(planResult.text);
330
+ this.log("debug", "[SSE] Parsed plan successfully", plan);
331
+ } catch (parseErr) {
332
+ this.log("warn", "[SSE] Failed to parse plan JSON, using fallback plan", parseErr);
333
+ plan = {
334
+ title: "AI Generated Page",
335
+ description: "Generated by KUBUILD AI",
336
+ sections: [
337
+ { type: "hero", title: "Hero Banner", prompt: `Hero section for: ${request.prompt}` },
338
+ { type: "features", title: "Key Features", prompt: `Features grid for: ${request.prompt}` },
339
+ { type: "testimonials", title: "Testimonials", prompt: `Social proof for: ${request.prompt}` },
340
+ { type: "cta", title: "Call To Action", prompt: `Call to action section for: ${request.prompt}` },
341
+ { type: "footer", title: "Footer", prompt: `Footer for: ${request.prompt}` }
342
+ ]
343
+ };
344
+ }
271
345
  }
272
346
  const sectionsToGenerate = Array.isArray(plan.sections) && plan.sections.length > 0 ? plan.sections : [
273
347
  { type: "hero", title: "Hero Section", prompt: `Hero banner for: ${request.prompt}` },
274
348
  { type: "features", title: "Features", prompt: `Features grid for: ${request.prompt}` },
275
- { type: "cta", title: "Call To Action", prompt: `CTA section for: ${request.prompt}` }
349
+ { type: "testimonials", title: "Testimonials", prompt: `Social proof for: ${request.prompt}` },
350
+ { type: "cta", title: "Call To Action", prompt: `CTA section for: ${request.prompt}` },
351
+ { type: "footer", title: "Footer", prompt: `Footer section for: ${request.prompt}` }
276
352
  ];
277
353
  const metadata = DocumentMetadataSchema.parse({
278
354
  title: plan.title || "AI Generated Page",
@@ -1667,7 +1743,10 @@ async function processAiRequest(engine, body, signal, agent) {
1667
1743
  stylePreference: payload.stylePreference,
1668
1744
  tone: payload.tone,
1669
1745
  locale: payload.locale,
1670
- metadata: payload.metadata
1746
+ metadata: payload.metadata,
1747
+ conversationHistory: payload.conversationHistory ?? payload.messages,
1748
+ sectionCount: payload.sectionCount,
1749
+ plan: payload.plan
1671
1750
  },
1672
1751
  { signal }
1673
1752
  );
@@ -1775,13 +1854,42 @@ async function processAiRequest(engine, body, signal, agent) {
1775
1854
  response: { success: result.stoppedBy !== "error", data: result }
1776
1855
  };
1777
1856
  }
1857
+ if (mode === "plan") {
1858
+ if (!payload.prompt || typeof payload.prompt !== "string") {
1859
+ return {
1860
+ status: 400,
1861
+ response: {
1862
+ success: false,
1863
+ error: {
1864
+ code: "INVALID_PROMPT",
1865
+ message: '"prompt" is required for planning mode'
1866
+ }
1867
+ }
1868
+ };
1869
+ }
1870
+ const result = await engine.planPage(
1871
+ {
1872
+ prompt: payload.prompt,
1873
+ stylePreference: payload.stylePreference,
1874
+ tone: payload.tone,
1875
+ locale: payload.locale,
1876
+ sectionCount: payload.sectionCount,
1877
+ conversationHistory: payload.conversationHistory ?? payload.messages
1878
+ },
1879
+ { signal }
1880
+ );
1881
+ return {
1882
+ status: result.success ? 200 : 500,
1883
+ response: result
1884
+ };
1885
+ }
1778
1886
  return {
1779
1887
  status: 400,
1780
1888
  response: {
1781
1889
  success: false,
1782
1890
  error: {
1783
1891
  code: "UNKNOWN_MODE",
1784
- message: `Unsupported mode: ${String(mode)}. Supported modes: 'full-page', 'section', 'refactor', 'chat', 'agent'`
1892
+ message: `Unsupported mode: ${String(mode)}. Supported modes: 'full-page', 'section', 'refactor', 'chat', 'agent', 'plan'`
1785
1893
  }
1786
1894
  }
1787
1895
  };
@@ -1964,7 +2072,10 @@ function createAiHandler(engine, options) {
1964
2072
  stylePreference: body.stylePreference,
1965
2073
  tone: body.tone,
1966
2074
  locale: body.locale,
1967
- metadata: body.metadata
2075
+ metadata: body.metadata,
2076
+ conversationHistory: body.conversationHistory ?? body.messages,
2077
+ sectionCount: body.sectionCount,
2078
+ plan: body.plan
1968
2079
  },
1969
2080
  { signal: request.signal }
1970
2081
  ),