@paroicms/site-generator-plugin 0.9.1 → 0.11.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.
Files changed (48) hide show
  1. package/gen-backend/ddl/site-generator.ddl.sql +57 -9
  2. package/gen-backend/dist/commands/execute-command.js +35 -9
  3. package/gen-backend/dist/commands/generator-session.js +49 -10
  4. package/gen-backend/dist/data-format.js +32 -4
  5. package/gen-backend/dist/db/db-init.js +3 -1
  6. package/gen-backend/dist/db/db-read.queries.js +142 -0
  7. package/gen-backend/dist/db/db-write.queries.js +144 -0
  8. package/gen-backend/dist/db/ddl-migration.js +8 -6
  9. package/gen-backend/dist/db/formatters.js +46 -0
  10. package/gen-backend/dist/generator/fake-content-generator.ts/content-report.js +9 -5
  11. package/gen-backend/dist/generator/fake-content-generator.ts/create-database-with-fake-content.js +18 -13
  12. package/gen-backend/dist/generator/fake-content-generator.ts/generate-fake-content.js +16 -12
  13. package/gen-backend/dist/generator/fake-content-generator.ts/invoke-generate-fake-content.js +26 -17
  14. package/gen-backend/dist/generator/lib/calling-llm-anthropic.js +33 -0
  15. package/gen-backend/dist/generator/lib/calling-llm-mistral.js +156 -0
  16. package/gen-backend/dist/generator/lib/create-prompt.js +2 -2
  17. package/gen-backend/dist/generator/lib/debug-utils.js +74 -48
  18. package/gen-backend/dist/generator/lib/llm-tokens.js +7 -9
  19. package/gen-backend/dist/generator/lib/llm-utils.js +8 -0
  20. package/gen-backend/dist/generator/lib/prompt-template.js +10 -0
  21. package/gen-backend/dist/generator/lib/session-utils.js +31 -0
  22. package/gen-backend/dist/generator/llm-queries/invoke-message-guard.js +20 -9
  23. package/gen-backend/dist/generator/llm-queries/invoke-new-site-analysis.js +73 -47
  24. package/gen-backend/dist/generator/llm-queries/invoke-update-site-schema.js +106 -43
  25. package/gen-backend/dist/generator/site-generator/site-generator.js +26 -18
  26. package/gen-backend/dist/lib/create-raw-context.js +31 -0
  27. package/gen-backend/dist/lib/site-remover.js +1 -1
  28. package/gen-backend/dist/plugin.js +8 -54
  29. package/gen-backend/prompts/generate-fake-content-multiple-documents.md +5 -5
  30. package/gen-backend/prompts/generate-fake-content-multiple-parts.md +5 -5
  31. package/gen-backend/prompts/generate-fake-content-single.md +4 -4
  32. package/gen-backend/prompts/{new-site-1-analysis.md → initial-1-analysis.md} +38 -29
  33. package/gen-backend/prompts/{new-site-2-fields.md → initial-2-fields.md} +3 -3
  34. package/gen-backend/prompts/message-guard.md +1 -1
  35. package/gen-backend/prompts/update-site-schema-1-write-details.md +5 -5
  36. package/gen-backend/prompts/update-site-schema-2-execute.md +29 -29
  37. package/gen-front/dist/gen-front.css +1 -1
  38. package/gen-front/dist/gen-front.mjs +128 -1175
  39. package/package.json +30 -32
  40. package/gen-backend/dist/db/db.queries.js +0 -60
  41. package/gen-backend/prompts/test-message1.txt +0 -1
  42. package/gen-front/dist/gen-front.eot +0 -0
  43. package/gen-front/dist/gen-front.svg +0 -345
  44. package/gen-front/dist/gen-front.ttf +0 -0
  45. package/gen-front/dist/gen-front.woff +0 -0
  46. package/gen-front/dist/gen-front.woff2 +0 -0
  47. package/gen-front/dist/gen-front2.woff2 +0 -0
  48. package/gen-front/dist/gen-front3.woff2 +0 -0
@@ -0,0 +1,8 @@
1
+ export class LlmError extends Error {
2
+ report;
3
+ constructor(message, report) {
4
+ super(message);
5
+ this.report = report;
6
+ this.name = "LlmError";
7
+ }
8
+ }
@@ -0,0 +1,10 @@
1
+ export function buildPromptTemplate(template) {
2
+ return (variables) => {
3
+ let result = template;
4
+ for (const [key, value] of Object.entries(variables)) {
5
+ const placeholder = `{{${key}}}`;
6
+ result = result.replace(new RegExp(placeholder, "g"), value ?? "");
7
+ }
8
+ return result;
9
+ };
10
+ }
@@ -0,0 +1,31 @@
1
+ import { messageOf } from "@paroi/data-formatters-lib";
2
+ import { insertIssueEvent, updateStep } from "../../db/db-write.queries.js";
3
+ export function safeCallStep(ctx, stepHandle, cb) {
4
+ void safeCallStepExec(ctx, stepHandle, cb);
5
+ }
6
+ async function safeCallStepExec(ctx, stepHandle, cb) {
7
+ const { logger, sessionId } = ctx;
8
+ const { stepNumber } = stepHandle;
9
+ try {
10
+ await cb();
11
+ }
12
+ catch (error) {
13
+ logger.error(`[${sessionId}]`, error);
14
+ try {
15
+ await updateStep(ctx, stepHandle, {
16
+ status: "failed",
17
+ currentActivity: null,
18
+ explanation: null, // TODO: implement error explanation (localized)
19
+ });
20
+ await insertIssueEvent(ctx, {
21
+ eventType: "stepError",
22
+ issueMessage: messageOf(error),
23
+ llmTaskName: undefined, // TODO: implement StepError exception with task name
24
+ stepNumber,
25
+ });
26
+ }
27
+ catch (error) {
28
+ logger.error(`[${sessionId}] Error after error`, error);
29
+ }
30
+ }
31
+ }
@@ -1,5 +1,6 @@
1
1
  import { boolVal, listValOrUndef, strVal, strValOrUndef, } from "@paroi/data-formatters-lib";
2
- import { updateSession } from "../../db/db.queries.js";
2
+ import { insertIssueEvent } from "../../db/db-write.queries.js";
3
+ import { invokeClaude } from "../lib/calling-llm-anthropic.js";
3
4
  import { createPromptTemplate } from "../lib/create-prompt.js";
4
5
  import { debugLlmOutput } from "../lib/debug-utils.js";
5
6
  import { parseLlmResponseAsProperties } from "../lib/parse-llm-response.js";
@@ -8,19 +9,25 @@ const guardPrompt = await createPromptTemplate({
8
9
  });
9
10
  const invalidCauses = new Set(["rude", "malicious", "outOfScope", "technicalLimits", "noSense"]);
10
11
  export async function invokeMessageGuard(ctx, input, { skipOutOfScopeCheck = false } = {}) {
11
- const debugName = "guard";
12
+ const llmTaskName = "guard";
12
13
  const llmInput = {
13
14
  message: input.prompt,
14
15
  };
15
- const debug = await debugLlmOutput(ctx, debugName, ctx.goodModelName, {
16
+ const debug = await debugLlmOutput(ctx, llmTaskName, ctx.anthropicModelName, undefined, {
16
17
  message: llmInput.message,
17
18
  });
18
- let llmMessageContent = debug.storedContent;
19
- if (!llmMessageContent) {
20
- const llmMessage = await guardPrompt.pipe(ctx.goodModel).invoke(llmInput);
21
- llmMessageContent = await debug.getMessageContent(llmMessage);
19
+ let llmOutput = debug.stored;
20
+ if (!llmOutput) {
21
+ const { messageContent, report } = await invokeClaude(ctx, {
22
+ llmTaskName,
23
+ prompt: guardPrompt(llmInput),
24
+ maxTokens: 200,
25
+ temperature: 0.2,
26
+ systemInstruction: "beFast",
27
+ });
28
+ llmOutput = await debug.getMessageContent(messageContent, report);
22
29
  }
23
- const rawReport = parseLlmResponseAsProperties(llmMessageContent, [
30
+ const rawReport = parseLlmResponseAsProperties(llmOutput.output, [
24
31
  {
25
32
  tagName: "guard_yaml",
26
33
  key: "guard",
@@ -36,7 +43,11 @@ export async function invokeMessageGuard(ctx, input, { skipOutOfScopeCheck = fal
36
43
  }
37
44
  if (report.valid)
38
45
  return;
39
- await updateSession(ctx, { guardCountInc: 1 });
46
+ await insertIssueEvent(ctx, {
47
+ eventType: "blockedByGuard",
48
+ llmTaskName: "guard",
49
+ issueMessage: report.explanation,
50
+ });
40
51
  return {
41
52
  success: false,
42
53
  language: report.language,
@@ -1,72 +1,90 @@
1
- import { updateSession } from "../../db/db.queries.js";
1
+ import { loadStep } from "../../db/db-read.queries.js";
2
+ import { insertStep, saveCompletedSchemaStep, updateStep, } from "../../db/db-write.queries.js";
2
3
  import { reorderObjectKeys } from "../helpers/js-utils.js";
4
+ import { invokeClaude } from "../lib/calling-llm-anthropic.js";
3
5
  import { createPromptTemplate, getPredefinedFields, getSiteSchemaTsDefs, } from "../lib/create-prompt.js";
4
6
  import { debugLlmOutput } from "../lib/debug-utils.js";
5
7
  import { parseMarkdownBulletedList } from "../lib/markdown-bulleted-list-parser.js";
6
8
  import { parseLlmResponseAsProperties } from "../lib/parse-llm-response.js";
9
+ import { safeCallStep } from "../lib/session-utils.js";
7
10
  import { createL10n } from "../site-schema-generator/create-l10n.js";
8
11
  import { createSiteSchemaFromAnalysis } from "../site-schema-generator/create-site-schema.js";
9
12
  import { invokeUpdateSiteSchema } from "./invoke-update-site-schema.js";
10
13
  export const analyzePrompt = await createPromptTemplate({
11
- fileName: "new-site-1-analysis.md",
14
+ fileName: "initial-1-analysis.md",
12
15
  withSiteSchemaTsDefs: true,
13
16
  });
14
17
  const fieldsPrompt = await createPromptTemplate({
15
- fileName: "new-site-2-fields.md",
18
+ fileName: "initial-2-fields.md",
16
19
  withSiteSchemaTsDefs: true,
17
20
  });
18
- export async function invokeNewSiteAnalysis(ctx, input) {
19
- const { analysis, explanation, unusedInformation } = await invokeAnalysisStep1(ctx, input);
21
+ export async function startInitialAnalysis(ctx, input) {
22
+ const stepHandle = await insertStep(ctx, {
23
+ kind: "initialSchema",
24
+ status: "pending",
25
+ currentActivity: "initial1",
26
+ });
27
+ safeCallStep(ctx, stepHandle, () => invokeInitialAnalysis(ctx, stepHandle, input));
28
+ return await loadStep(ctx, stepHandle.stepNumber);
29
+ }
30
+ export async function invokeInitialAnalysis(ctx, stepHandle, input) {
31
+ const { analysis, unusedInformation, explanation, llmReport: llmReport1, } = await invokeAnalysisStep1(ctx, input, stepHandle);
20
32
  const siteSchema = createSiteSchemaFromAnalysis(analysis);
21
- const { unusedInformation: unusedInformation2 } = await invokeAnalysisStep2(ctx, { prompt: createUnusedInformationPrompt(unusedInformation, analysis) ?? "" }, siteSchema);
33
+ await updateStep(ctx, stepHandle, {
34
+ currentActivity: "initial2",
35
+ explanation: explanation ?? null,
36
+ });
37
+ const { unusedInformation: unusedInformation2, llmReport: llmReport2 } = await invokeAnalysisStep2(ctx, { prompt: createUnusedInformationPrompt(unusedInformation, analysis) ?? "" }, siteSchema, stepHandle);
22
38
  reorderSiteSchemaNodeTypes(siteSchema);
23
39
  const l10n = createL10n(analysis, siteSchema);
24
40
  const siteTitle = {
25
41
  [analysis.siteProperties.language]: analysis.siteProperties.title,
26
42
  };
43
+ const completedValues = {
44
+ status: "completed",
45
+ siteSchema,
46
+ l10n,
47
+ localizedValues: { siteTitle },
48
+ inputTokenCount: llmReport1.inputTokenCount + llmReport2.inputTokenCount,
49
+ outputTokenCount: (llmReport1.outputTokenCount ?? 0) + (llmReport2.outputTokenCount ?? 0),
50
+ promptTitle: undefined, // TODO: implement prompt title
51
+ };
27
52
  if (!unusedInformation2) {
28
- await updateSession(ctx, { status: "analyzed", promptCountInc: 1 });
29
- return {
30
- siteTitle,
31
- siteSchema,
32
- l10n,
33
- changed: true,
34
- explanation,
35
- };
53
+ await saveCompletedSchemaStep(ctx, stepHandle, completedValues);
54
+ return;
36
55
  }
37
56
  ctx.logger.debug("Unused information:", unusedInformation2);
38
- const updated = await invokeUpdateSiteSchema(ctx, {
57
+ await invokeUpdateSiteSchema(ctx, stepHandle, {
39
58
  prompt: unusedInformation2,
40
- generatedSchema: {
41
- siteTitle,
42
- siteSchema,
43
- l10n,
44
- },
45
- }, { asRemainingPrompt: true });
46
- await updateSession(ctx, { status: "analyzed", promptCountInc: 1 });
47
- return {
48
- siteTitle,
49
- siteSchema: updated.siteSchema,
50
- l10n: updated.l10n,
51
- changed: true,
52
- explanation: updated.changed ? `${explanation}\n\n${updated.explanation}` : explanation,
53
- };
59
+ fromStepSchema: completedValues,
60
+ }, {
61
+ asRemainingOf: completedValues,
62
+ });
54
63
  }
55
- async function invokeAnalysisStep1(ctx, input) {
56
- const debugName = "analysis";
64
+ async function invokeAnalysisStep1(ctx, input, stepHandle) {
65
+ const llmTaskName = "analysis-1";
57
66
  const llmInput = {
58
67
  message: input.prompt,
59
68
  siteSchemaTsDefs: getSiteSchemaTsDefs(),
60
69
  };
61
- const debug = await debugLlmOutput(ctx, debugName, ctx.bestModelName, {
70
+ const debug = await debugLlmOutput(ctx, llmTaskName, ctx.anthropicModelName, stepHandle, {
62
71
  message: llmInput.message,
63
72
  });
64
- let llmMessageContent = debug.storedContent;
65
- if (!llmMessageContent) {
66
- const llmMessage = await analyzePrompt.pipe(ctx.bestModel).invoke(llmInput);
67
- llmMessageContent = await debug.getMessageContent(llmMessage);
73
+ let llmOutput = debug.stored;
74
+ if (!llmOutput) {
75
+ // Create formatted messages using the template
76
+ const prompt = analyzePrompt(llmInput);
77
+ // Call the model
78
+ const { messageContent, report } = await invokeClaude(ctx, {
79
+ llmTaskName,
80
+ prompt,
81
+ maxTokens: 4_000,
82
+ temperature: 0.1,
83
+ systemInstruction: "beSmart",
84
+ });
85
+ llmOutput = await debug.getMessageContent(messageContent, report);
68
86
  }
69
- const rawAnalysis = parseLlmResponseAsProperties(llmMessageContent, [
87
+ const rawAnalysis = parseLlmResponseAsProperties(llmOutput.output, [
70
88
  {
71
89
  tagName: "website_properties_yaml",
72
90
  key: "siteProperties",
@@ -102,30 +120,38 @@ async function invokeAnalysisStep1(ctx, input) {
102
120
  };
103
121
  return {
104
122
  analysis,
105
- explanation: rawAnalysis.explanation,
106
123
  unusedInformation: rawAnalysis.unusedInformation,
124
+ explanation: rawAnalysis.explanation,
125
+ llmReport: llmOutput.llmReport,
107
126
  };
108
127
  }
109
128
  async function invokeAnalysisStep2(ctx, input,
110
129
  /** Will be mutated. */
111
- siteSchema) {
112
- const debugName = "assign-fields";
130
+ siteSchema, stepHandle) {
131
+ const llmTaskName = "analysis-2";
113
132
  const llmInput = {
114
133
  siteSchemaTsDefs: getSiteSchemaTsDefs(),
115
134
  predefinedFields: JSON.stringify(getPredefinedFields(), undefined, 2),
116
135
  siteSchemaJson: JSON.stringify(siteSchema, undefined, 2),
117
136
  message: input.prompt,
118
137
  };
119
- const debug = await debugLlmOutput(ctx, debugName, ctx.goodModelName, {
138
+ const debug = await debugLlmOutput(ctx, llmTaskName, ctx.anthropicModelName, stepHandle, {
120
139
  message: llmInput.message,
121
140
  siteSchemaJson: llmInput.siteSchemaJson,
122
141
  });
123
- let llmMessageContent = debug.storedContent;
124
- if (!llmMessageContent) {
125
- const llmMessage = await fieldsPrompt.pipe(ctx.goodModel).invoke(llmInput);
126
- llmMessageContent = await debug.getMessageContent(llmMessage);
142
+ let llmOutput = debug.stored;
143
+ if (!llmOutput) {
144
+ const prompt = fieldsPrompt(llmInput);
145
+ const { messageContent, report } = await invokeClaude(ctx, {
146
+ llmTaskName,
147
+ prompt,
148
+ maxTokens: 700,
149
+ temperature: 0.1,
150
+ systemInstruction: "beFast",
151
+ });
152
+ llmOutput = await debug.getMessageContent(messageContent, report);
127
153
  }
128
- const { assignedFields, unusedInformation } = parseLlmResponseAsProperties(llmMessageContent, [
154
+ const { assignedFields, unusedInformation } = parseLlmResponseAsProperties(llmOutput.output, [
129
155
  {
130
156
  tagName: "yaml_result",
131
157
  key: "assignedFields",
@@ -141,7 +167,7 @@ siteSchema) {
141
167
  if (siteSchema.nodeTypes) {
142
168
  assignFieldsToNodeTypes(ctx, assignedFields, siteSchema.nodeTypes);
143
169
  }
144
- return { unusedInformation };
170
+ return { unusedInformation, llmReport: llmOutput.llmReport };
145
171
  }
146
172
  function assignFieldsToNodeTypes(ctx, assignedFields, nodeTypes) {
147
173
  const remainingTypeNames = new Set(Object.keys(assignedFields));
@@ -1,56 +1,92 @@
1
- import { updateSession } from "../../db/db.queries.js";
1
+ import { loadStep, readStepSchema } from "../../db/db-read.queries.js";
2
+ import { insertStep, saveCompletedSchemaStep, updateStep, } from "../../db/db-write.queries.js";
3
+ import { invokeClaude } from "../lib/calling-llm-anthropic.js";
2
4
  import { createPromptTemplate, getPredefinedFields, getSiteSchemaTsDefs, } from "../lib/create-prompt.js";
3
5
  import { debugLlmOutput } from "../lib/debug-utils.js";
4
6
  import { parseLlmResponseAsProperties } from "../lib/parse-llm-response.js";
7
+ import { safeCallStep } from "../lib/session-utils.js";
5
8
  const prompt1Tpl = await createPromptTemplate({
6
9
  fileName: "update-site-schema-1-write-details.md",
7
10
  });
8
11
  const prompt2Tpl = await createPromptTemplate({
9
12
  fileName: "update-site-schema-2-execute.md",
10
13
  });
11
- export async function invokeUpdateSiteSchema(ctx, input, { asRemainingPrompt = false } = {}) {
12
- const task = await invokeUpdateSiteSchemaStep1(ctx, input);
13
- if (!task.taskDetailsMd) {
14
- // no changes
15
- return {
16
- ...input.generatedSchema,
17
- changed: false,
18
- explanation: task.explanation,
19
- };
20
- }
21
- const genSchema = await invokeUpdateSiteSchemaStep2(ctx, {
22
- taskDetailsMd: task.taskDetailsMd,
23
- generatedSchema: input.generatedSchema,
14
+ export async function startUpdateSiteSchema(ctx, input) {
15
+ const fromStepSchema = await readStepSchema(ctx, input.fromStepNumber);
16
+ const stepHandle = await insertStep(ctx, {
17
+ kind: "updateSchema",
18
+ status: "pending",
19
+ currentActivity: "updating1",
24
20
  });
25
- if (!asRemainingPrompt) {
26
- await updateSession(ctx, { status: "updated", promptCountInc: 1 });
21
+ safeCallStep(ctx, stepHandle, () => invokeUpdateSiteSchema(ctx, stepHandle, { prompt: input.prompt, fromStepSchema }));
22
+ return await loadStep(ctx, stepHandle.stepNumber);
23
+ }
24
+ export async function invokeUpdateSiteSchema(ctx, stepHandle, input, { asRemainingOf } = {}) {
25
+ const { properties, llmReport: llmReport1 } = await invokeUpdateSiteSchemaStep1(ctx, input, stepHandle);
26
+ if (!properties.taskDetailsMd) {
27
+ await updateStep(ctx, stepHandle, {
28
+ status: "noEffect",
29
+ currentActivity: null,
30
+ explanation: properties.explanation,
31
+ });
32
+ return;
27
33
  }
28
- return {
29
- ...genSchema,
30
- changed: true,
31
- explanation: task.explanation,
34
+ await updateStep(ctx, stepHandle, {
35
+ currentActivity: "updating2",
36
+ explanation: properties.explanation,
37
+ });
38
+ const { stepSchema, llmReport: llmReport2 } = await invokeUpdateSiteSchemaStep2(ctx, {
39
+ taskDetailsMd: properties.taskDetailsMd,
40
+ fromStepSchema: input.fromStepSchema,
41
+ }, stepHandle);
42
+ const completedValues = {
43
+ ...stepSchema,
44
+ status: "completed",
45
+ inputTokenCount: llmReport1.inputTokenCount + llmReport2.inputTokenCount,
46
+ outputTokenCount: (llmReport1.outputTokenCount ?? 0) + (llmReport2.outputTokenCount ?? 0),
47
+ promptTitle: undefined, // TODO: implement prompt title
32
48
  };
49
+ if (asRemainingOf) {
50
+ completedValues.inputTokenCount += asRemainingOf.inputTokenCount;
51
+ completedValues.outputTokenCount += asRemainingOf.outputTokenCount;
52
+ completedValues.promptTitle = undefined;
53
+ if (asRemainingOf.explanation) {
54
+ completedValues.explanation = properties.explanation
55
+ ? `${asRemainingOf.explanation}\n\n${properties.explanation}`
56
+ : asRemainingOf.explanation;
57
+ }
58
+ }
59
+ await saveCompletedSchemaStep(ctx, stepHandle, completedValues);
33
60
  }
34
- async function invokeUpdateSiteSchemaStep1(ctx, input) {
35
- const debugName = "update-step1";
61
+ async function invokeUpdateSiteSchemaStep1(ctx, input, stepHandle) {
62
+ const llmTaskName = "update-step1";
36
63
  const llmInput = {
37
64
  siteSchemaTsDefs: getSiteSchemaTsDefs(),
38
65
  predefinedFields: JSON.stringify(getPredefinedFields(), undefined, 2),
39
- siteSchemaJson: JSON.stringify(input.generatedSchema.siteSchema, undefined, 2),
40
- l10nJson: JSON.stringify(input.generatedSchema.l10n, undefined, 2),
66
+ siteSchemaJson: JSON.stringify(input.fromStepSchema.siteSchema, undefined, 2),
67
+ l10nJson: JSON.stringify(input.fromStepSchema.l10n, undefined, 2),
41
68
  updateMessage: input.prompt,
42
69
  };
43
- const debug = await debugLlmOutput(ctx, debugName, ctx.goodModelName, {
70
+ const debug = await debugLlmOutput(ctx, llmTaskName, ctx.anthropicModelName, stepHandle, {
44
71
  updateMessage: llmInput.updateMessage,
45
72
  siteSchemaJson: llmInput.siteSchemaJson,
46
73
  l10nJson: llmInput.l10nJson,
47
74
  });
48
- let llmMessageContent = debug.storedContent;
49
- if (!llmMessageContent) {
50
- const llmMessage = await prompt1Tpl.pipe(ctx.goodModel).invoke(llmInput);
51
- llmMessageContent = await debug.getMessageContent(llmMessage);
75
+ let llmOutput = debug.stored;
76
+ if (!llmOutput) {
77
+ // Create formatted messages using the template
78
+ const message = prompt1Tpl(llmInput);
79
+ // Call the model
80
+ const { messageContent, report } = await invokeClaude(ctx, {
81
+ llmTaskName,
82
+ prompt: message,
83
+ maxTokens: 1_500,
84
+ temperature: 0.1,
85
+ systemInstruction: "beSmart",
86
+ });
87
+ llmOutput = await debug.getMessageContent(messageContent, report);
52
88
  }
53
- return parseLlmResponseAsProperties(llmMessageContent, [
89
+ const properties = parseLlmResponseAsProperties(llmOutput.output, [
54
90
  {
55
91
  tagName: "task_details_md",
56
92
  key: "taskDetailsMd",
@@ -63,27 +99,40 @@ async function invokeUpdateSiteSchemaStep1(ctx, input) {
63
99
  format: "markdown",
64
100
  },
65
101
  ]);
102
+ return {
103
+ properties,
104
+ llmReport: llmOutput.llmReport,
105
+ };
66
106
  }
67
- async function invokeUpdateSiteSchemaStep2(ctx, input) {
68
- const debugName = "update-step2";
107
+ async function invokeUpdateSiteSchemaStep2(ctx, input, stepHandle) {
108
+ const llmTaskName = "update-step2";
69
109
  const llmInput = {
70
110
  siteSchemaTsDefs: getSiteSchemaTsDefs(),
71
111
  predefinedFields: JSON.stringify(getPredefinedFields(), undefined, 2),
72
- siteSchemaJson: JSON.stringify(input.generatedSchema.siteSchema, undefined, 2),
73
- l10nJson: JSON.stringify(input.generatedSchema.l10n, undefined, 2),
112
+ siteSchemaJson: JSON.stringify(input.fromStepSchema.siteSchema, undefined, 2),
113
+ l10nJson: JSON.stringify(input.fromStepSchema.l10n, undefined, 2),
74
114
  taskDetailsMd: input.taskDetailsMd,
75
115
  };
76
- const debug = await debugLlmOutput(ctx, debugName, ctx.bestModelName, {
116
+ const debug = await debugLlmOutput(ctx, llmTaskName, ctx.anthropicModelName, stepHandle, {
77
117
  taskDetailsMd: llmInput.taskDetailsMd,
78
118
  siteSchemaJson: llmInput.siteSchemaJson,
79
119
  l10nJson: llmInput.l10nJson,
80
120
  });
81
- let llmMessageContent = debug.storedContent;
82
- if (!llmMessageContent) {
83
- const llmMessage = await prompt2Tpl.pipe(ctx.bestModel).invoke(llmInput);
84
- llmMessageContent = await debug.getMessageContent(llmMessage);
121
+ let llmOutput = debug.stored;
122
+ if (!llmOutput) {
123
+ // Create formatted messages using the template
124
+ const message = prompt2Tpl(llmInput);
125
+ // Call the model
126
+ const { messageContent, report } = await invokeClaude(ctx, {
127
+ llmTaskName,
128
+ prompt: message,
129
+ maxTokens: 7_000,
130
+ temperature: 0.1,
131
+ systemInstruction: "beSmart",
132
+ });
133
+ llmOutput = await debug.getMessageContent(messageContent, report);
85
134
  }
86
- const parsed = parseLlmResponseAsProperties(llmMessageContent, [
135
+ const parsed = parseLlmResponseAsProperties(llmOutput.output, [
87
136
  {
88
137
  tagName: "updated_site_schema_json",
89
138
  key: "siteSchema",
@@ -98,7 +147,7 @@ async function invokeUpdateSiteSchemaStep2(ctx, input) {
98
147
  },
99
148
  ]);
100
149
  const result = {
101
- ...input.generatedSchema,
150
+ ...input.fromStepSchema,
102
151
  };
103
152
  if (parsed.siteSchema) {
104
153
  result.siteSchema = fixSiteSchema(parsed.siteSchema);
@@ -106,16 +155,30 @@ async function invokeUpdateSiteSchemaStep2(ctx, input) {
106
155
  if (parsed.l10n) {
107
156
  result.l10n = parsed.l10n;
108
157
  }
109
- return result;
158
+ return {
159
+ stepSchema: result,
160
+ llmReport: llmOutput.llmReport,
161
+ };
110
162
  }
111
163
  function fixSiteSchema(siteSchema) {
112
164
  for (const nodeType of siteSchema.nodeTypes ?? []) {
113
- for (const field of nodeType.fields ?? []) {
165
+ if (!nodeType.fields)
166
+ continue;
167
+ // Add Quill plugin to fields with renderAs "html"
168
+ for (const field of nodeType.fields) {
114
169
  if (typeof field !== "string" && field.renderAs === "html") {
115
170
  field.dataType = "json";
116
171
  field.plugin = "@paroicms/quill-editor-plugin";
117
172
  }
118
173
  }
174
+ // Move labeling fields to the beginning of the fields array
175
+ const labelingFields = nodeType.fields.filter((f) => typeof f !== "string" && f.storedAs === "labeling");
176
+ if (labelingFields.length > 0) {
177
+ nodeType.fields = [
178
+ ...labelingFields,
179
+ ...nodeType.fields.filter((f) => typeof f === "string" || f.storedAs !== "labeling"),
180
+ ];
181
+ }
119
182
  }
120
183
  return siteSchema;
121
184
  }
@@ -1,13 +1,22 @@
1
1
  import { generateSlug } from "@paroicms/public-anywhere-lib";
2
+ import { randomUUID } from "node:crypto";
2
3
  import { mkdir, writeFile } from "node:fs/promises";
3
4
  import { join } from "node:path";
4
- import { updateSession } from "../../db/db.queries.js";
5
+ import { loadStep, readStepSchema } from "../../db/db-read.queries.js";
6
+ import { insertStep, saveGeneratedSiteStep, } from "../../db/db-write.queries.js";
5
7
  import { fillSiteWithFakeContent } from "../fake-content-generator.ts/create-database-with-fake-content.js";
8
+ import { safeCallStep } from "../lib/session-utils.js";
6
9
  import { createTheme } from "./theme-creator.js";
7
10
  export async function generateSite(ctx, input) {
8
11
  const { service, logger, sitesDir, packConf: { packName }, } = ctx;
9
- const { generatedSchema: { l10n, siteSchema, siteTitle }, withFakeContent, } = input;
10
- const siteId = ctx.sessionId;
12
+ const { fromStepNumber, withSampleData } = input;
13
+ const stepHandle = await insertStep(ctx, {
14
+ kind: "generateSite",
15
+ status: "pending",
16
+ currentActivity: "generatingSite",
17
+ });
18
+ const { l10n, siteSchema, localizedValues } = await readStepSchema(ctx, fromStepNumber);
19
+ const siteId = randomUUID();
11
20
  logger.info(`Generating site: ${siteId}…`);
12
21
  const siteDir = join(sitesDir, siteId);
13
22
  await mkdir(siteDir);
@@ -16,7 +25,9 @@ export async function generateSite(ctx, input) {
16
25
  await writeFile(join(siteDir, `site-schema.l10n.${language}.json`), JSON.stringify(l10nData, null, 2), "utf-8");
17
26
  }
18
27
  await writeFile(join(siteDir, "package.json"), JSON.stringify(getPackageJsonContent({
19
- siteTitle: siteTitle.en ?? Object.values(siteTitle)[0] ?? "new-website",
28
+ siteTitle: localizedValues.siteTitle.en ??
29
+ Object.values(localizedValues.siteTitle)[0] ??
30
+ "new-website",
20
31
  }), null, 2), "utf-8");
21
32
  await createTheme(ctx, siteDir, siteSchema);
22
33
  const regSite = await service.connector.registerNewSite({
@@ -25,12 +36,6 @@ export async function generateSite(ctx, input) {
25
36
  domain: siteId,
26
37
  version: "0.0.0",
27
38
  });
28
- if (withFakeContent) {
29
- const report = await fillSiteWithFakeContent(ctx, { regSite, siteTitle });
30
- await updateSession(ctx, {
31
- contentCountInc: report.getContentCount(),
32
- });
33
- }
34
39
  const account = {
35
40
  kind: "local",
36
41
  email: `${siteId}@yopmail.com`,
@@ -39,16 +44,19 @@ export async function generateSite(ctx, input) {
39
44
  };
40
45
  await ctx.service.connector.createAccount(regSite.fqdn, account, { asContactEmail: true });
41
46
  const { siteUrl } = regSite;
42
- await updateSession(ctx, {
43
- status: "generated",
44
- nodeTypeCount: siteSchema.nodeTypes?.length ?? 0,
45
- });
46
- return {
47
+ const values = {
48
+ status: withSampleData ? "pending" : "completed",
47
49
  siteId,
48
- url: siteUrl,
49
- boUrl: `${siteUrl}/adm`,
50
- account,
50
+ siteUrl,
51
+ localizedValues,
52
+ loginEmail: account.email,
53
+ loginPassword: account.password,
51
54
  };
55
+ await saveGeneratedSiteStep(ctx, stepHandle, values);
56
+ if (withSampleData) {
57
+ safeCallStep(ctx, stepHandle, () => fillSiteWithFakeContent(ctx, stepHandle, { regSite, localizedValues }));
58
+ }
59
+ return await loadStep(ctx, stepHandle.stepNumber);
52
60
  }
53
61
  function getPackageJsonContent(options) {
54
62
  return {
@@ -0,0 +1,31 @@
1
+ import Anthropic from "@anthropic-ai/sdk";
2
+ import { Mistral } from "@mistralai/mistralai";
3
+ import { readOrCreateJwtSecretSync } from "@paroicms/internal-server-lib";
4
+ import { join } from "node:path";
5
+ export function createRawContext(service, options) {
6
+ const { cn, logNextQuery, pluginConf, debugDir } = options;
7
+ const packConf = service.connector.getSitePackConf(pluginConf.packName);
8
+ const { sitesDir, packName } = packConf;
9
+ if (!sitesDir || packConf.serveOn !== "subDomain") {
10
+ throw new Error(`Site-generator plugin can generate sites only for sub-domain pack with "sitesDir", but pack "${packName}" doesn't have it`);
11
+ }
12
+ return {
13
+ cn,
14
+ logNextQuery,
15
+ jwtSecret: readOrCreateJwtSecretSync(join(service.registeredSite.dataDir, "site-generator-secret.txt")),
16
+ pluginConf,
17
+ debugDir,
18
+ sitesDir,
19
+ packConf,
20
+ service,
21
+ logger: service.logger,
22
+ anthropic: new Anthropic({
23
+ apiKey: pluginConf.anthropicApiKey,
24
+ }),
25
+ mistral: new Mistral({
26
+ apiKey: pluginConf.mistralApiKey,
27
+ }),
28
+ mistralModelName: "ministral-8b-2410",
29
+ anthropicModelName: "claude-3-7-sonnet-20250219",
30
+ };
31
+ }
@@ -13,7 +13,7 @@ export function startSiteRemover(ctx) {
13
13
  catch (error) {
14
14
  ctx.logger.error("[site-remover]", error);
15
15
  }
16
- }, 1000 * 60 * 60).unref(); // Check every hour
16
+ }, 1000 * 60 * 60).unref();
17
17
  return {
18
18
  stop() {
19
19
  clearInterval(runningId);