@avocadostudio-ai/orchestrator-core 0.1.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.
- package/LICENSE +201 -0
- package/dist/agent/agent-context.d.ts +19 -0
- package/dist/agent/agent-context.js +67 -0
- package/dist/agent/agent-logger.d.ts +5 -0
- package/dist/agent/agent-logger.js +22 -0
- package/dist/agent/agent-loop-openai.d.ts +8 -0
- package/dist/agent/agent-loop-openai.js +172 -0
- package/dist/agent/agent-loop.d.ts +56 -0
- package/dist/agent/agent-loop.js +167 -0
- package/dist/agent/agent-provider.d.ts +28 -0
- package/dist/agent/agent-provider.js +63 -0
- package/dist/agent/agent-tools.d.ts +28 -0
- package/dist/agent/agent-tools.js +899 -0
- package/dist/agent/context/editing-guidelines.md +46 -0
- package/dist/agent/context/role.md +39 -0
- package/dist/agent/integration-prompt.d.ts +9 -0
- package/dist/agent/integration-prompt.js +154 -0
- package/dist/agent/sites-agent-context.d.ts +12 -0
- package/dist/agent/sites-agent-context.js +316 -0
- package/dist/agent/sites-agent-shared.d.ts +161 -0
- package/dist/agent/sites-agent-shared.js +1101 -0
- package/dist/agent/sites-agent-tools.d.ts +18 -0
- package/dist/agent/sites-agent-tools.js +1227 -0
- package/dist/chat/anthropic-cache.d.ts +20 -0
- package/dist/chat/anthropic-cache.js +54 -0
- package/dist/chat/anthropic-planner.d.ts +98 -0
- package/dist/chat/anthropic-planner.js +1012 -0
- package/dist/chat/changelog-coverage-validator.d.ts +37 -0
- package/dist/chat/changelog-coverage-validator.js +215 -0
- package/dist/chat/chat-pipeline-context.d.ts +211 -0
- package/dist/chat/chat-pipeline-context.js +249 -0
- package/dist/chat/chat-pipeline-deterministic.d.ts +61 -0
- package/dist/chat/chat-pipeline-deterministic.js +407 -0
- package/dist/chat/chat-pipeline-image.d.ts +86 -0
- package/dist/chat/chat-pipeline-image.js +897 -0
- package/dist/chat/chat-pipeline-shared.d.ts +69 -0
- package/dist/chat/chat-pipeline-shared.js +212 -0
- package/dist/chat/chat-pipeline-translation.d.ts +27 -0
- package/dist/chat/chat-pipeline-translation.js +417 -0
- package/dist/chat/chat-pipeline-ui.d.ts +14 -0
- package/dist/chat/chat-pipeline-ui.js +244 -0
- package/dist/chat/chat-pipeline.d.ts +99 -0
- package/dist/chat/chat-pipeline.js +3999 -0
- package/dist/chat/decomposer.d.ts +21 -0
- package/dist/chat/decomposer.js +65 -0
- package/dist/chat/gemini-planner.d.ts +70 -0
- package/dist/chat/gemini-planner.js +541 -0
- package/dist/chat/hallucination-validator.d.ts +36 -0
- package/dist/chat/hallucination-validator.js +110 -0
- package/dist/chat/locale-strings.d.ts +47 -0
- package/dist/chat/locale-strings.js +100 -0
- package/dist/chat/plan-json-schema.d.ts +133 -0
- package/dist/chat/plan-json-schema.js +112 -0
- package/dist/chat/planner-types.d.ts +120 -0
- package/dist/chat/planner-types.js +66 -0
- package/dist/chat/planner.d.ts +148 -0
- package/dist/chat/planner.js +1361 -0
- package/dist/chat/prompts.d.ts +67 -0
- package/dist/chat/prompts.js +356 -0
- package/dist/chat/provider-routing.d.ts +14 -0
- package/dist/chat/provider-routing.js +27 -0
- package/dist/chat/variation-pipeline.d.ts +135 -0
- package/dist/chat/variation-pipeline.js +837 -0
- package/dist/chat/vision-alt-generator.d.ts +35 -0
- package/dist/chat/vision-alt-generator.js +152 -0
- package/dist/cms/adapter.d.ts +62 -0
- package/dist/cms/adapter.js +1 -0
- package/dist/cms/bootstrap.d.ts +17 -0
- package/dist/cms/bootstrap.js +85 -0
- package/dist/cms/editor-api-adapter.d.ts +23 -0
- package/dist/cms/editor-api-adapter.js +71 -0
- package/dist/cms/index.d.ts +4 -0
- package/dist/cms/index.js +3 -0
- package/dist/cms/json-file-adapter.d.ts +11 -0
- package/dist/cms/json-file-adapter.js +62 -0
- package/dist/demo-mode.d.ts +59 -0
- package/dist/demo-mode.js +201 -0
- package/dist/errors.d.ts +67 -0
- package/dist/errors.js +129 -0
- package/dist/http/chat-stream-resumable.d.ts +108 -0
- package/dist/http/chat-stream-resumable.js +290 -0
- package/dist/http/chat-stream.d.ts +99 -0
- package/dist/http/chat-stream.js +92 -0
- package/dist/image/gdrive-client.d.ts +22 -0
- package/dist/image/gdrive-client.js +215 -0
- package/dist/image/image-helpers.d.ts +95 -0
- package/dist/image/image-helpers.js +488 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/jira/jira-approval.d.ts +22 -0
- package/dist/jira/jira-approval.js +51 -0
- package/dist/jira/jira-client.d.ts +44 -0
- package/dist/jira/jira-client.js +313 -0
- package/dist/jira/jira-poller.d.ts +46 -0
- package/dist/jira/jira-poller.js +184 -0
- package/dist/jira/jira-processor.d.ts +103 -0
- package/dist/jira/jira-processor.js +1085 -0
- package/dist/jira/jira-types.d.ts +117 -0
- package/dist/jira/jira-types.js +38 -0
- package/dist/logger.d.ts +12 -0
- package/dist/logger.js +28 -0
- package/dist/migration/mcp-server-stdio.d.ts +8 -0
- package/dist/migration/mcp-server-stdio.js +672 -0
- package/dist/migration/migration-prompt.d.ts +7 -0
- package/dist/migration/migration-prompt.js +197 -0
- package/dist/migration/migration-tools.d.ts +17 -0
- package/dist/migration/migration-tools.js +159 -0
- package/dist/migration/scrape-cache.d.ts +9 -0
- package/dist/migration/scrape-cache.js +19 -0
- package/dist/nlp/deterministic-planner-context.d.ts +141 -0
- package/dist/nlp/deterministic-planner-context.js +362 -0
- package/dist/nlp/deterministic-planner-pages.d.ts +26 -0
- package/dist/nlp/deterministic-planner-pages.js +170 -0
- package/dist/nlp/deterministic-planner-patches.d.ts +80 -0
- package/dist/nlp/deterministic-planner-patches.js +508 -0
- package/dist/nlp/deterministic-planner-refs.d.ts +33 -0
- package/dist/nlp/deterministic-planner-refs.js +164 -0
- package/dist/nlp/deterministic-planner-suggestions.d.ts +49 -0
- package/dist/nlp/deterministic-planner-suggestions.js +579 -0
- package/dist/nlp/deterministic-planner.d.ts +85 -0
- package/dist/nlp/deterministic-planner.js +1631 -0
- package/dist/nlp/intent-detection.d.ts +309 -0
- package/dist/nlp/intent-detection.js +730 -0
- package/dist/nlp/intent-helpers.d.ts +15 -0
- package/dist/nlp/intent-helpers.js +243 -0
- package/dist/nlp/intent-patterns.d.ts +40 -0
- package/dist/nlp/intent-patterns.js +223 -0
- package/dist/nlp/plan-normalizer.d.ts +41 -0
- package/dist/nlp/plan-normalizer.js +1537 -0
- package/dist/ops/destructive-action-gate.d.ts +44 -0
- package/dist/ops/destructive-action-gate.js +90 -0
- package/dist/ops/ops-engine.d.ts +151 -0
- package/dist/ops/ops-engine.js +1394 -0
- package/dist/publish/diff-engine.d.ts +18 -0
- package/dist/publish/diff-engine.js +305 -0
- package/dist/publish/publish-helpers.d.ts +87 -0
- package/dist/publish/publish-helpers.js +521 -0
- package/dist/publish/publish-target-registry.d.ts +7 -0
- package/dist/publish/publish-target-registry.js +61 -0
- package/dist/publish/publish-target.d.ts +81 -0
- package/dist/publish/publish-target.js +1 -0
- package/dist/publish/targets/deploy-hook.d.ts +13 -0
- package/dist/publish/targets/deploy-hook.js +123 -0
- package/dist/publish/targets/git.d.ts +13 -0
- package/dist/publish/targets/git.js +55 -0
- package/dist/publish/targets/site-contract.d.ts +19 -0
- package/dist/publish/targets/site-contract.js +124 -0
- package/dist/state/content-source.d.ts +17 -0
- package/dist/state/content-source.js +1 -0
- package/dist/state/in-memory-content-source.d.ts +27 -0
- package/dist/state/in-memory-content-source.js +51 -0
- package/dist/state/session-lock.d.ts +13 -0
- package/dist/state/session-lock.js +29 -0
- package/dist/state/session-state.d.ts +310 -0
- package/dist/state/session-state.js +1083 -0
- package/dist/state/sqlite-store-singleton.d.ts +31 -0
- package/dist/state/sqlite-store-singleton.js +170 -0
- package/dist/state/sqlite-store.d.ts +135 -0
- package/dist/state/sqlite-store.js +421 -0
- package/dist/telemetry/chat-telemetry.d.ts +105 -0
- package/dist/telemetry/chat-telemetry.js +247 -0
- package/dist/telemetry/eval-candidate-store.d.ts +50 -0
- package/dist/telemetry/eval-candidate-store.js +120 -0
- package/dist/telemetry/feedback-store.d.ts +34 -0
- package/dist/telemetry/feedback-store.js +76 -0
- package/dist/telemetry/jira-telemetry.d.ts +57 -0
- package/dist/telemetry/jira-telemetry.js +68 -0
- package/dist/telemetry/migration-telemetry.d.ts +35 -0
- package/dist/telemetry/migration-telemetry.js +40 -0
- package/dist/telemetry/usage.d.ts +24 -0
- package/dist/telemetry/usage.js +80 -0
- package/dist/tools/builtin-registrations.d.ts +12 -0
- package/dist/tools/builtin-registrations.js +33 -0
- package/dist/tools/builtins/gdrive-browse.d.ts +3 -0
- package/dist/tools/builtins/gdrive-browse.js +68 -0
- package/dist/tools/builtins/image-generate.d.ts +3 -0
- package/dist/tools/builtins/image-generate.js +211 -0
- package/dist/tools/builtins/unsplash-get-by-id.d.ts +23 -0
- package/dist/tools/builtins/unsplash-get-by-id.js +119 -0
- package/dist/tools/builtins/unsplash-search.d.ts +3 -0
- package/dist/tools/builtins/unsplash-search.js +74 -0
- package/dist/tools/executor.d.ts +23 -0
- package/dist/tools/executor.js +169 -0
- package/dist/tools/index.d.ts +5 -0
- package/dist/tools/index.js +5 -0
- package/dist/tools/registry.d.ts +21 -0
- package/dist/tools/registry.js +75 -0
- package/dist/tools/runtime.d.ts +27 -0
- package/dist/tools/runtime.js +48 -0
- package/dist/tools/schema-validator.d.ts +24 -0
- package/dist/tools/schema-validator.js +88 -0
- package/dist/tools/types.d.ts +86 -0
- package/dist/tools/types.js +1 -0
- package/dist/variation-images.d.ts +19 -0
- package/dist/variation-images.js +12 -0
- package/package.json +78 -0
|
@@ -0,0 +1,579 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { allowedBlockTypes, blockSchemas, getAllBlockMeta, getBlockMeta, getPropDisplayName, deriveFieldMetaFromSchema } from "@avocadostudio-ai/shared";
|
|
3
|
+
export function editablePropsFromBlock(block) {
|
|
4
|
+
if (!block || !block.props || typeof block.props !== "object")
|
|
5
|
+
return [];
|
|
6
|
+
return Object.keys(block.props);
|
|
7
|
+
}
|
|
8
|
+
const CLARIFICATION_LABELS = {
|
|
9
|
+
heading: "Edit heading",
|
|
10
|
+
subheading: "Edit subheading",
|
|
11
|
+
ctaText: "Edit CTA",
|
|
12
|
+
ctaHref: "Update link",
|
|
13
|
+
imageUrl: "Replace image",
|
|
14
|
+
imageAlt: "Write alt",
|
|
15
|
+
secondaryCtaText: "Add CTA",
|
|
16
|
+
secondaryCtaHref: "Update link",
|
|
17
|
+
body: "Edit body",
|
|
18
|
+
title: "Edit title",
|
|
19
|
+
description: "Edit description",
|
|
20
|
+
features: "Edit features",
|
|
21
|
+
items: "Edit items",
|
|
22
|
+
cards: "Edit cards",
|
|
23
|
+
left: "Edit left column",
|
|
24
|
+
right: "Edit right column",
|
|
25
|
+
columns: "Edit columns",
|
|
26
|
+
children: "Edit content",
|
|
27
|
+
content: "Edit content"
|
|
28
|
+
};
|
|
29
|
+
export function promptFromPropKey(propKey, blockType) {
|
|
30
|
+
if (CLARIFICATION_LABELS[propKey])
|
|
31
|
+
return CLARIFICATION_LABELS[propKey];
|
|
32
|
+
const human = blockType ? getPropDisplayName(blockType, propKey) : propKey;
|
|
33
|
+
// Fall back to the registry-declared label so host-app overrides (e.g.
|
|
34
|
+
// villa's TwoColumn adding `sectionId`) surface as "Edit Section id" rather
|
|
35
|
+
// than leaking the raw camelCase key into the UI.
|
|
36
|
+
return human === propKey ? `Edit ${propKey}` : `Edit ${human.toLowerCase()}`;
|
|
37
|
+
}
|
|
38
|
+
// Keys we never want to surface as clarification chips — they're structural
|
|
39
|
+
// identifiers, layout style enums, or other non-content props that users don't
|
|
40
|
+
// "edit" as a primary action. Host apps add these via TwoColumn / Card / etc.
|
|
41
|
+
const NON_CONTENT_PROP_KEYS = new Set([
|
|
42
|
+
"sectionId",
|
|
43
|
+
"anchor",
|
|
44
|
+
"id",
|
|
45
|
+
"variant",
|
|
46
|
+
"style",
|
|
47
|
+
"theme",
|
|
48
|
+
"layout",
|
|
49
|
+
"align",
|
|
50
|
+
"alignment"
|
|
51
|
+
]);
|
|
52
|
+
function rankPropKeysForClarification(blockType, keys) {
|
|
53
|
+
const meta = getBlockMeta(blockType);
|
|
54
|
+
const listKeys = new Set(meta?.listFields ? Object.keys(meta.listFields) : []);
|
|
55
|
+
// Derive from CLARIFICATION_LABELS so adding a new label automatically
|
|
56
|
+
// updates the ranker — single source of truth.
|
|
57
|
+
const known = new Set(Object.keys(CLARIFICATION_LABELS));
|
|
58
|
+
const score = (k) => {
|
|
59
|
+
if (NON_CONTENT_PROP_KEYS.has(k))
|
|
60
|
+
return 3;
|
|
61
|
+
if (listKeys.has(k))
|
|
62
|
+
return 0;
|
|
63
|
+
if (known.has(k))
|
|
64
|
+
return 1;
|
|
65
|
+
return 2;
|
|
66
|
+
};
|
|
67
|
+
return [...keys].sort((a, b) => score(a) - score(b));
|
|
68
|
+
}
|
|
69
|
+
export function userFacingPropNames(blockType, keys) {
|
|
70
|
+
return keys.map((key) => getPropDisplayName(blockType, key));
|
|
71
|
+
}
|
|
72
|
+
export const ORDINALS = ["first", "second", "third", "fourth", "fifth", "sixth", "seventh", "eighth", "ninth", "tenth"];
|
|
73
|
+
export function humanizeArrayPath(root) {
|
|
74
|
+
const match = root.match(/^([a-zA-Z_]+)\[(\d+)\]$/);
|
|
75
|
+
if (!match)
|
|
76
|
+
return root;
|
|
77
|
+
const [, listName, indexStr] = match;
|
|
78
|
+
const index = Number(indexStr);
|
|
79
|
+
const ordinal = ORDINALS[index] ?? `#${index + 1}`;
|
|
80
|
+
const singular = {
|
|
81
|
+
cards: "card",
|
|
82
|
+
features: "feature",
|
|
83
|
+
items: "item",
|
|
84
|
+
stats: "stat",
|
|
85
|
+
columns: "column"
|
|
86
|
+
};
|
|
87
|
+
const noun = singular[listName] ?? listName.replace(/s$/, "");
|
|
88
|
+
return `the ${ordinal} ${noun}`;
|
|
89
|
+
}
|
|
90
|
+
export function childSuggestions(args) {
|
|
91
|
+
const { selected, editablePath } = args;
|
|
92
|
+
const path = editablePath.trim();
|
|
93
|
+
if (!path)
|
|
94
|
+
return [];
|
|
95
|
+
const root = path.split(".")[0] ?? path;
|
|
96
|
+
const human = humanizeArrayPath(root);
|
|
97
|
+
if (selected.type === "CardGrid" && root.startsWith("cards[")) {
|
|
98
|
+
return [
|
|
99
|
+
`Edit ${human}'s title`,
|
|
100
|
+
`Edit ${human}'s description`,
|
|
101
|
+
`Edit ${human}'s CTA`,
|
|
102
|
+
`Update ${human}'s link`
|
|
103
|
+
];
|
|
104
|
+
}
|
|
105
|
+
if (selected.type === "FeatureGrid" && root.startsWith("features[")) {
|
|
106
|
+
return [`Edit ${human}'s title`, `Edit ${human}'s description`];
|
|
107
|
+
}
|
|
108
|
+
if (selected.type === "Testimonials" && root.startsWith("items[")) {
|
|
109
|
+
return [`Edit ${human}'s quote`, `Edit ${human}'s author`];
|
|
110
|
+
}
|
|
111
|
+
if (selected.type === "FAQAccordion" && root.startsWith("items[")) {
|
|
112
|
+
return [`Edit ${human}'s question`, `Edit ${human}'s answer`];
|
|
113
|
+
}
|
|
114
|
+
return [`Edit ${human}`];
|
|
115
|
+
}
|
|
116
|
+
export function clarificationSuggestions(args) {
|
|
117
|
+
const { selected, current } = args;
|
|
118
|
+
if (selected) {
|
|
119
|
+
const allKeys = editablePropsFromBlock(selected);
|
|
120
|
+
const ranked = rankPropKeysForClarification(selected.type, allKeys);
|
|
121
|
+
// Drop non-content props (sectionId, variant, etc.) unless we have nothing else.
|
|
122
|
+
const contentKeys = ranked.filter((k) => !NON_CONTENT_PROP_KEYS.has(k));
|
|
123
|
+
const candidate = contentKeys.length > 0 ? contentKeys : ranked;
|
|
124
|
+
if (candidate.length > 0)
|
|
125
|
+
return candidate.slice(0, 4).map((k) => promptFromPropKey(k, selected.type));
|
|
126
|
+
return [
|
|
127
|
+
`Edit ${selected.type}`,
|
|
128
|
+
`Move ${selected.type}`
|
|
129
|
+
];
|
|
130
|
+
}
|
|
131
|
+
const suggestions = [];
|
|
132
|
+
const existingTypes = new Set(current.blocks.map((b) => b.type));
|
|
133
|
+
const first = current.blocks[0];
|
|
134
|
+
if (first)
|
|
135
|
+
suggestions.push(`Edit ${first.type}`);
|
|
136
|
+
if (!existingTypes.has("Testimonials"))
|
|
137
|
+
suggestions.push("Add Testimonials");
|
|
138
|
+
else if (!existingTypes.has("FAQAccordion"))
|
|
139
|
+
suggestions.push("Add FAQ");
|
|
140
|
+
else if (!existingTypes.has("CardGrid"))
|
|
141
|
+
suggestions.push("Add CardGrid");
|
|
142
|
+
if (existingTypes.has("FAQAccordion"))
|
|
143
|
+
suggestions.push("Move FAQ");
|
|
144
|
+
else if (existingTypes.has("CTA"))
|
|
145
|
+
suggestions.push("Edit CTA");
|
|
146
|
+
if (suggestions.length === 0)
|
|
147
|
+
suggestions.push("Edit heading", "Add Testimonials");
|
|
148
|
+
return suggestions.slice(0, 4);
|
|
149
|
+
}
|
|
150
|
+
// Action-oriented follow-ups shown right after a block is added. A bare
|
|
151
|
+
// "Edit <Type>" reads like a chore; these nudge the user toward a concrete,
|
|
152
|
+
// engaging next move. Only suggest actions the block actually supports (see the
|
|
153
|
+
// item shapes in `_blockNotes`). Falls back to "Edit <Type>" for unlisted types.
|
|
154
|
+
const ADD_BLOCK_FOLLOWUPS = {
|
|
155
|
+
Testimonials: ["Add another testimonial", "Add author photos", "Add roles & companies"],
|
|
156
|
+
FAQAccordion: ["Add another question", "Rewrite the answers"],
|
|
157
|
+
FeatureGrid: ["Add another feature", "Add icons to the features"],
|
|
158
|
+
Stats: ["Add another stat", "Add icons to the stats"],
|
|
159
|
+
CardGrid: ["Add another card", "Add images to the cards"]
|
|
160
|
+
};
|
|
161
|
+
export function postEditSuggestions(args) {
|
|
162
|
+
const { plan, current } = args;
|
|
163
|
+
if (plan.ops.some((op) => op.op === "remove_page"))
|
|
164
|
+
return [];
|
|
165
|
+
const suggestions = [];
|
|
166
|
+
const existingTypes = new Set(current.blocks.map((b) => b.type));
|
|
167
|
+
// `current` is the pre-apply page, so a block we just added isn't in it yet.
|
|
168
|
+
// Track added types separately so the forward-looking section nudges below
|
|
169
|
+
// don't suggest "Add Testimonials" immediately after one was added.
|
|
170
|
+
const addedTypes = new Set(plan.ops.flatMap((op) => (op.op === "add_block" ? [op.block.type] : [])));
|
|
171
|
+
const hasType = (type) => existingTypes.has(type) || addedTypes.has(type);
|
|
172
|
+
const pushUniqueSuggestion = (value) => {
|
|
173
|
+
if (suggestions.includes(value))
|
|
174
|
+
return;
|
|
175
|
+
suggestions.push(value);
|
|
176
|
+
};
|
|
177
|
+
// Detect if this was primarily an image update
|
|
178
|
+
const isImageUpdate = plan.change_log.some((entry) => /\b(image|images|unsplash|photo)\b/i.test(entry));
|
|
179
|
+
for (const op of plan.ops) {
|
|
180
|
+
if (op.op === "update_props") {
|
|
181
|
+
const block = current.blocks.find((b) => b.id === op.blockId);
|
|
182
|
+
if (block) {
|
|
183
|
+
if (isImageUpdate) {
|
|
184
|
+
// After image updates, suggest content edits relevant to the block
|
|
185
|
+
const blockType = block.type;
|
|
186
|
+
if (blockType === "CardGrid" || blockType === "Card") {
|
|
187
|
+
pushUniqueSuggestion("Edit cards");
|
|
188
|
+
pushUniqueSuggestion("Update links");
|
|
189
|
+
}
|
|
190
|
+
else if (blockType === "FeatureGrid") {
|
|
191
|
+
pushUniqueSuggestion("Edit features");
|
|
192
|
+
}
|
|
193
|
+
else if (blockType === "Hero") {
|
|
194
|
+
pushUniqueSuggestion("Edit heading");
|
|
195
|
+
pushUniqueSuggestion("Edit CTA");
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
pushUniqueSuggestion(`Edit ${blockType}`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
const patchKeys = Object.keys(op.patch);
|
|
203
|
+
const otherKeys = editablePropsFromBlock(block)
|
|
204
|
+
.filter((k) => !patchKeys.includes(k) && !NON_CONTENT_PROP_KEYS.has(k));
|
|
205
|
+
for (const key of otherKeys.slice(0, 2)) {
|
|
206
|
+
pushUniqueSuggestion(promptFromPropKey(key, block.type));
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
else if (op.op === "create_page") {
|
|
212
|
+
pushUniqueSuggestion("Generate image");
|
|
213
|
+
}
|
|
214
|
+
else if (op.op === "add_block") {
|
|
215
|
+
const followups = ADD_BLOCK_FOLLOWUPS[op.block.type];
|
|
216
|
+
if (followups)
|
|
217
|
+
for (const f of followups)
|
|
218
|
+
pushUniqueSuggestion(f);
|
|
219
|
+
else
|
|
220
|
+
pushUniqueSuggestion(`Edit ${op.block.type}`);
|
|
221
|
+
}
|
|
222
|
+
else if (op.op === "remove_block") {
|
|
223
|
+
if (!hasType("CTA"))
|
|
224
|
+
pushUniqueSuggestion("Add CTA");
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
// Keep one or two forward-looking section suggestions even after non-structural edits.
|
|
228
|
+
if (!hasType("Testimonials") && suggestions.length < 4)
|
|
229
|
+
pushUniqueSuggestion("Add Testimonials");
|
|
230
|
+
if (!hasType("FAQAccordion") && suggestions.length < 4)
|
|
231
|
+
pushUniqueSuggestion("Add FAQ");
|
|
232
|
+
if (!hasType("Stats") && suggestions.length < 4)
|
|
233
|
+
pushUniqueSuggestion("Add Stats");
|
|
234
|
+
return suggestions.slice(0, 4);
|
|
235
|
+
}
|
|
236
|
+
export function demoPlanFromMessage(message, slug, activeBlockId, activeBlockType) {
|
|
237
|
+
const lower = message.toLowerCase();
|
|
238
|
+
const quoted = /"([^"]+)"/.exec(message)?.[1];
|
|
239
|
+
// SEO metadata patterns — checked early so "seo title" isn't mistaken for hero heading
|
|
240
|
+
const hasSeoKeyword = /\b(seo|meta\s*desc(?:ription)?|meta\s*title|metadata|og\s*image|open\s*graph)\b/.test(lower);
|
|
241
|
+
if (hasSeoKeyword) {
|
|
242
|
+
// Multi-field metadata: "add metadata with the title '...' and description '...'"
|
|
243
|
+
const multiFieldMatch = lower.match(/\b(?:set|change|update|add)\b.*?\bmetadata\b/);
|
|
244
|
+
if (multiFieldMatch) {
|
|
245
|
+
const patch = {};
|
|
246
|
+
const changeLog = [];
|
|
247
|
+
const titleMatch = message.match(/\btitle\s+['"]([^'"]+)['"]/i)
|
|
248
|
+
?? message.match(/\btitle\s+to\s+['"]([^'"]+)['"]/i);
|
|
249
|
+
if (titleMatch) {
|
|
250
|
+
patch.title = titleMatch[1];
|
|
251
|
+
changeLog.push(`Title \u2192 "${titleMatch[1]}".`);
|
|
252
|
+
}
|
|
253
|
+
const descMatch = message.match(/\bdescription\s+['"]([^'"]+)['"]/i)
|
|
254
|
+
?? message.match(/\bdescription\s+to\s+['"]([^'"]+)['"]/i);
|
|
255
|
+
if (descMatch) {
|
|
256
|
+
patch.description = descMatch[1];
|
|
257
|
+
changeLog.push(`Description \u2192 "${descMatch[1]}".`);
|
|
258
|
+
}
|
|
259
|
+
const ogMatch = message.match(/\b(?:og\s*image|open\s*graph\s*image)\s+['"]([^'"]+)['"]/i)
|
|
260
|
+
?? message.match(/\b(?:og\s*image|open\s*graph\s*image)\s+to\s+['"]([^'"]+)['"]/i);
|
|
261
|
+
if (ogMatch) {
|
|
262
|
+
patch.ogImage = ogMatch[1];
|
|
263
|
+
changeLog.push(`OG image \u2192 "${ogMatch[1]}".`);
|
|
264
|
+
}
|
|
265
|
+
if (Object.keys(patch).length > 0) {
|
|
266
|
+
const fields = changeLog.length === 1 ? changeLog[0].split(" \u2192")[0] : "page metadata";
|
|
267
|
+
return {
|
|
268
|
+
intent: "edit_plan",
|
|
269
|
+
summary_for_user: `Updated ${fields}.`,
|
|
270
|
+
change_log: changeLog,
|
|
271
|
+
ops: [{ op: "update_page_meta", pageSlug: slug, patch }]
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
const seoGenerate = /\b(write|generate|create|add)\b.*\b(seo|meta)\b/.test(lower) && !quoted;
|
|
276
|
+
const seoSetMatch = lower.match(/\b(?:set|change|update|add)\b.*?\b(meta\s*desc(?:ription)?|seo\s*desc(?:ription)?)\b/)
|
|
277
|
+
?? lower.match(/\b(?:set|change|update|add)\b.*?\b(seo\s*title|meta\s*title)\b/)
|
|
278
|
+
?? lower.match(/\b(?:set|change|update|add)\b.*?\b(og\s*image|open\s*graph\s*image)\b/);
|
|
279
|
+
if (seoSetMatch) {
|
|
280
|
+
const fieldRaw = seoSetMatch[1].toLowerCase();
|
|
281
|
+
const isTitle = /title/.test(fieldRaw);
|
|
282
|
+
const isOgImage = /og|open\s*graph/.test(fieldRaw);
|
|
283
|
+
const extractedQuoted = quoted;
|
|
284
|
+
const singleQuoted = /'([^']+)'/.exec(message)?.[1];
|
|
285
|
+
const afterTo = message.match(/\bto\s+(.+)$/i)?.[1]?.trim();
|
|
286
|
+
const value = extractedQuoted ?? singleQuoted ?? afterTo ?? "";
|
|
287
|
+
if (!value) {
|
|
288
|
+
return {
|
|
289
|
+
intent: "needs_clarification",
|
|
290
|
+
summary_for_user: `Please provide the value — e.g. set ${isTitle ? "SEO title" : isOgImage ? "OG image" : "meta description"} to "Your value here".`,
|
|
291
|
+
change_log: [],
|
|
292
|
+
ops: []
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
const patch = {};
|
|
296
|
+
const fieldLabel = isTitle ? "SEO title" : isOgImage ? "OG image" : "Meta description";
|
|
297
|
+
if (isTitle)
|
|
298
|
+
patch.title = value;
|
|
299
|
+
else if (isOgImage)
|
|
300
|
+
patch.ogImage = value;
|
|
301
|
+
else
|
|
302
|
+
patch.description = value;
|
|
303
|
+
return {
|
|
304
|
+
intent: "edit_plan",
|
|
305
|
+
summary_for_user: `Updated the ${fieldLabel}.`,
|
|
306
|
+
change_log: [`${fieldLabel} \u2192 "${value}".`],
|
|
307
|
+
ops: [{ op: "update_page_meta", pageSlug: slug, patch }]
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
if (seoGenerate) {
|
|
311
|
+
return {
|
|
312
|
+
intent: "needs_clarification",
|
|
313
|
+
summary_for_user: "Demo mode cannot generate SEO metadata automatically. Please provide the exact title or description you'd like to set, for example: set meta description to \"Your description here\".",
|
|
314
|
+
change_log: [],
|
|
315
|
+
ops: []
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
if (lower.includes("make this shorter") && activeBlockId && activeBlockType === "Hero") {
|
|
320
|
+
return {
|
|
321
|
+
intent: "edit_plan",
|
|
322
|
+
summary_for_user: "Shortened the selected hero copy.",
|
|
323
|
+
change_log: ["Updated hero heading and subheading to be more concise."],
|
|
324
|
+
ops: [
|
|
325
|
+
{
|
|
326
|
+
op: "update_props",
|
|
327
|
+
pageSlug: slug,
|
|
328
|
+
blockId: activeBlockId,
|
|
329
|
+
patch: {
|
|
330
|
+
heading: "Edit your site in seconds",
|
|
331
|
+
subheading: "Describe a change and preview it instantly."
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
]
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
if ((lower.includes("title") || lower.includes("heading")) && activeBlockId && activeBlockType === "Hero") {
|
|
338
|
+
const headingText = quoted ??
|
|
339
|
+
message
|
|
340
|
+
.replace(/change/i, "")
|
|
341
|
+
.replace(/hero/i, "")
|
|
342
|
+
.replace(/title/i, "")
|
|
343
|
+
.replace(/heading/i, "")
|
|
344
|
+
.replace(/\bto\b/i, "")
|
|
345
|
+
.trim();
|
|
346
|
+
if (headingText) {
|
|
347
|
+
return {
|
|
348
|
+
intent: "edit_plan",
|
|
349
|
+
summary_for_user: "Updated the hero title.",
|
|
350
|
+
change_log: [`Changed hero heading to "${headingText}".`],
|
|
351
|
+
ops: [
|
|
352
|
+
{
|
|
353
|
+
op: "update_props",
|
|
354
|
+
pageSlug: slug,
|
|
355
|
+
blockId: activeBlockId,
|
|
356
|
+
patch: { heading: headingText }
|
|
357
|
+
}
|
|
358
|
+
]
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
if (lower.includes("rich text") || lower.includes("richtext") || lower.includes("text block") || lower.includes("prose")) {
|
|
363
|
+
if (lower.includes("add") || lower.includes("insert") || lower.includes("create")) {
|
|
364
|
+
return {
|
|
365
|
+
intent: "edit_plan",
|
|
366
|
+
summary_for_user: "Added a rich text section.",
|
|
367
|
+
change_log: ["Inserted RichText block."],
|
|
368
|
+
ops: [
|
|
369
|
+
{
|
|
370
|
+
op: "add_block",
|
|
371
|
+
pageSlug: slug,
|
|
372
|
+
block: {
|
|
373
|
+
id: `b_richtext_${Date.now()}`,
|
|
374
|
+
type: "RichText",
|
|
375
|
+
props: {
|
|
376
|
+
title: "",
|
|
377
|
+
body: "Add your content here.\n\nUse a second paragraph to break up the text into readable sections."
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
]
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
if (activeBlockId && activeBlockType === "RichText" && quoted) {
|
|
385
|
+
return {
|
|
386
|
+
intent: "edit_plan",
|
|
387
|
+
summary_for_user: "Updated the rich text body.",
|
|
388
|
+
change_log: [`Set body to "${quoted}".`],
|
|
389
|
+
ops: [{ op: "update_props", pageSlug: slug, blockId: activeBlockId, patch: { body: quoted } }]
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
if (lower.includes("add testimonials")) {
|
|
394
|
+
return {
|
|
395
|
+
intent: "edit_plan",
|
|
396
|
+
summary_for_user: "Added a testimonials section below the hero.",
|
|
397
|
+
change_log: ["Inserted Testimonials block after the hero section."],
|
|
398
|
+
ops: [
|
|
399
|
+
{
|
|
400
|
+
op: "add_block",
|
|
401
|
+
pageSlug: slug,
|
|
402
|
+
afterBlockId: "b_hero_home",
|
|
403
|
+
block: {
|
|
404
|
+
id: `b_testimonials_${Date.now()}`,
|
|
405
|
+
type: "Testimonials",
|
|
406
|
+
props: {
|
|
407
|
+
title: "Loved by small teams",
|
|
408
|
+
items: [
|
|
409
|
+
{ quote: "We launched in a day.", author: "Ana, Founder" },
|
|
410
|
+
{ quote: "Edits are now effortless.", author: "Chris, Marketer" }
|
|
411
|
+
]
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
]
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
if (lower.includes("remove") && activeBlockId) {
|
|
419
|
+
return {
|
|
420
|
+
intent: "edit_plan",
|
|
421
|
+
summary_for_user: "Removed the selected block.",
|
|
422
|
+
change_log: ["Deleted selected section from the page."],
|
|
423
|
+
ops: [{ op: "remove_block", pageSlug: slug, blockId: activeBlockId }]
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
return {
|
|
427
|
+
intent: "needs_clarification",
|
|
428
|
+
summary_for_user: "I need one clarification: what section should I change and what exactly should be updated?",
|
|
429
|
+
change_log: [],
|
|
430
|
+
ops: []
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
export function titleCaseSentence(text) {
|
|
434
|
+
const trimmed = text.trim();
|
|
435
|
+
if (!trimmed)
|
|
436
|
+
return trimmed;
|
|
437
|
+
return trimmed.charAt(0).toUpperCase() + trimmed.slice(1);
|
|
438
|
+
}
|
|
439
|
+
export function pageMetaContractSummary() {
|
|
440
|
+
return {
|
|
441
|
+
op: "update_page_meta",
|
|
442
|
+
fields: {
|
|
443
|
+
title: "SEO/og title (falls back to page.title if absent). Max 60 chars; put primary keyword near front; avoid generic words like 'Home' or 'Welcome'; must be unique per page.",
|
|
444
|
+
description: "Meta description for search engines and social sharing. 150-160 chars; front-load key info in first 110 chars (mobile truncation); include a CTA verb; use active voice; never use quotes; never repeat the title verbatim.",
|
|
445
|
+
ogImage: "Open Graph image URL for social previews. Must be HTTPS; recommended dimensions 1200x630px."
|
|
446
|
+
},
|
|
447
|
+
notes: "Merge-patch semantics: only supplied keys are updated. Set a field to empty string to clear it. Always include the actual values you set in change_log so the user can see them (meta tags are not visible in the page preview)."
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Per-block notes that supplement the auto-derived contract.
|
|
452
|
+
* Only add entries here when the block needs guidance beyond what the registry
|
|
453
|
+
* metadata can auto-generate (list-field shapes are derived automatically).
|
|
454
|
+
*/
|
|
455
|
+
const _blockNotes = {
|
|
456
|
+
Hero: "Use heading for the main headline; never invent prop names. imagePosition controls layout and must be 'left', 'right', or 'full' (default 'right'). textAlign can be 'left' or 'center' (default 'left') — use 'center' for centered hero layouts especially with imagePosition 'full'. eyebrow is optional short text above the heading (e.g. 'New', 'Coming Soon'). For imageUrl: use any placeholder value (the system resolves images separately); if the user provides an explicit URL, use that. imageAlt is accessibility alt text describing what is depicted — never the user's instruction. Omit imageAlt from the patch unless the user explicitly provides a caption. Do NOT mention a specific image source in summary_for_user. secondaryCtaText/secondaryCtaHref are optional: set them to add a ghost/outline secondary button beside the primary CTA; omit or set to empty string to hide it.",
|
|
457
|
+
CTA: "Keep existing props unless the user asks to change them. secondaryCtaText/secondaryCtaHref are optional: set both to show a secondary button next to the primary CTA.",
|
|
458
|
+
Card: "A standalone card with one CTA. variant can be 'default' or 'full-bleed'. Use 'full-bleed' when a background image with dark overlay and white text is desired — requires imageUrl.",
|
|
459
|
+
RichText: "body is a string; use \\n\\n to separate paragraphs. Supported inline syntax: **word** for bold, *word* for italic, [text](url) for links, '# Heading' lines become h3 headings. title is an optional section heading. Never invent prop names.",
|
|
460
|
+
Stats: "stats must be a non-empty array of {value, label, icon?, description?}. value is a short string like '10K+' or '99.9%'. icon is optional and MUST be a single emoji such as '📈' or '🌍' — never an icon-library name like 'chevronRight' and never an invented/relative image URL (only use a full https:// URL if the user explicitly provides one). description is optional explanatory text below the label. title is an optional section heading.",
|
|
461
|
+
FeatureGrid: "features is a non-empty array of {icon?, title, description}. icon is optional and MUST be a single emoji such as '⚡' or '🔒' — never an icon-library name like 'chevronRight' and never an invented/relative image URL (only use a full https:// URL if the user explicitly provides one). columns controls the grid layout: '2', '3' (default), or '4'.",
|
|
462
|
+
Testimonials: "items is a non-empty array of {quote, author, role?, icon?, imageUrl?, imageAlt?}. role is the author's job title or company. icon is optional and MUST be a single emoji (e.g. a company/brand glyph) — never an icon-library name and never an invented image URL; it renders at the top of the card. imageUrl/imageAlt are for an optional circular author avatar photo.",
|
|
463
|
+
CardGrid: "cards is a non-empty array of {title, description, ctaText, ctaHref, imageUrl?, imageAlt?}. columns controls the grid layout: '2', '3' (default), or '4'. cardVariant can be 'default' or 'full-bleed' — applies to all cards in the grid. Use 'full-bleed' for background images with dark overlay and white text (requires imageUrl on cards). subtitle is optional.",
|
|
464
|
+
Tabs: "tabs is a non-empty array of {label, content}. content supports richtext markdown. title is an optional section heading above the tab bar.",
|
|
465
|
+
Banner: "Full-width announcement bar. variant sets preset theme: 'info' (default blue), 'success' (green), or 'warning' (amber). For custom brand colors, set backgroundColor and/or textColor (CSS color strings, e.g. '#1a1a2e' or 'rgb(26,26,46)'). Custom colors override the variant background/text. ctaText/ctaHref are optional: set both to show a button.",
|
|
466
|
+
FAQAccordion: "items is a non-empty array of {q, a}. The answer (a) supports richtext: **bold**, *italic*, [link](url), lists (- item), and paragraph breaks (\\n\\n).",
|
|
467
|
+
TwoColumn: "Image + text side-by-side layout. imagePosition is 'left' or 'right' (default 'right'). body supports inline markdown (**bold**, *italic*, [link](url)). ctaText/ctaHref are optional: set both to show a CTA button. For imageUrl: use any placeholder value (the system resolves images separately).",
|
|
468
|
+
Footer: "columns must be a non-empty array of {title, links}. links is a string with one 'Label|URL' per line (use \\n to separate). Example: 'Home|/\\nAbout|/about\\nBlog|/blog'."
|
|
469
|
+
};
|
|
470
|
+
/**
|
|
471
|
+
* Derive block contracts from the registry so new blocks are automatically
|
|
472
|
+
* included without maintaining a parallel hardcoded map.
|
|
473
|
+
*
|
|
474
|
+
* When a `manifest` is provided (external site), manifest-declared props are
|
|
475
|
+
* merged into the contract for shared-name blocks (e.g. Hero with extra
|
|
476
|
+
* `logoUrl`, `carouselImages`), and manifest-only blocks (e.g. ContactForm)
|
|
477
|
+
* get a contract derived from their JSON schema.
|
|
478
|
+
*/
|
|
479
|
+
export function blockContractsSummary(manifest) {
|
|
480
|
+
const allMeta = getAllBlockMeta();
|
|
481
|
+
const result = {};
|
|
482
|
+
// Build a lookup of manifest block types → propsSchema properties
|
|
483
|
+
const manifestByType = new Map();
|
|
484
|
+
if (manifest) {
|
|
485
|
+
for (const def of manifest.blocks) {
|
|
486
|
+
const props = def.propsSchema?.properties;
|
|
487
|
+
if (props && typeof props === "object" && !Array.isArray(props)) {
|
|
488
|
+
manifestByType.set(def.type, props);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
for (const type of allowedBlockTypes) {
|
|
493
|
+
const schema = blockSchemas[type];
|
|
494
|
+
const meta = allMeta[type];
|
|
495
|
+
if (!schema || !meta)
|
|
496
|
+
continue;
|
|
497
|
+
// Derive allowed/required/optional from Zod schema shape
|
|
498
|
+
const shape = schema.shape;
|
|
499
|
+
const allProps = Object.keys(shape);
|
|
500
|
+
const required = [];
|
|
501
|
+
const optional = [];
|
|
502
|
+
for (const key of allProps) {
|
|
503
|
+
if (shape[key].isOptional() || shape[key] instanceof z.ZodDefault) {
|
|
504
|
+
optional.push(key);
|
|
505
|
+
}
|
|
506
|
+
else {
|
|
507
|
+
required.push(key);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
// Merge manifest-declared props that the shared Zod schema doesn't know about
|
|
511
|
+
const manifestProps = manifestByType.get(type);
|
|
512
|
+
if (manifestProps) {
|
|
513
|
+
for (const key of Object.keys(manifestProps)) {
|
|
514
|
+
if (!allProps.includes(key)) {
|
|
515
|
+
allProps.push(key);
|
|
516
|
+
optional.push(key);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
manifestByType.delete(type); // consumed — remaining are manifest-only blocks
|
|
520
|
+
}
|
|
521
|
+
// Collect image spec hints for scalar fields
|
|
522
|
+
const imageHints = [];
|
|
523
|
+
for (const [fieldKey, fieldMeta] of Object.entries(meta.fields)) {
|
|
524
|
+
if (fieldMeta.imageSpec) {
|
|
525
|
+
const s = fieldMeta.imageSpec;
|
|
526
|
+
imageHints.push(`${fieldKey}: recommended ${s.aspectRatio} ${s.width}\u00d7${s.height}`);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
// Append list-field item shapes to notes for array props
|
|
530
|
+
let autoNotes = "";
|
|
531
|
+
if (meta.listFields) {
|
|
532
|
+
const parts = [];
|
|
533
|
+
for (const [listKey, listMeta] of Object.entries(meta.listFields)) {
|
|
534
|
+
const itemKeys = Object.keys(listMeta.itemFields).join(", ");
|
|
535
|
+
parts.push(`${listKey} must be a non-empty array of {${itemKeys}}`);
|
|
536
|
+
// Collect image spec hints for list item fields
|
|
537
|
+
for (const [itemKey, itemMeta] of Object.entries(listMeta.itemFields)) {
|
|
538
|
+
if (itemMeta.imageSpec) {
|
|
539
|
+
const s = itemMeta.imageSpec;
|
|
540
|
+
imageHints.push(`${listKey}[].${itemKey}: recommended ${s.aspectRatio} ${s.width}\u00d7${s.height}`);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
if (parts.length > 0)
|
|
545
|
+
autoNotes = parts.join(". ") + ".";
|
|
546
|
+
}
|
|
547
|
+
const imageHintStr = imageHints.length > 0 ? imageHints.join(". ") + "." : "";
|
|
548
|
+
let notes = _blockNotes[type] ?? (autoNotes || `${meta.description ?? type} Never invent prop names.`);
|
|
549
|
+
if (imageHintStr) {
|
|
550
|
+
notes += (notes ? " " : "") + imageHintStr;
|
|
551
|
+
}
|
|
552
|
+
const entry = {
|
|
553
|
+
allowedProps: allProps,
|
|
554
|
+
required,
|
|
555
|
+
notes
|
|
556
|
+
};
|
|
557
|
+
if (optional.length > 0)
|
|
558
|
+
entry.optional = optional;
|
|
559
|
+
result[type] = entry;
|
|
560
|
+
}
|
|
561
|
+
// Add manifest-only blocks (not in shared registry) — derive contracts from JSON schema
|
|
562
|
+
for (const [type, manifestProps] of manifestByType) {
|
|
563
|
+
const derived = deriveFieldMetaFromSchema({ type: "object", properties: manifestProps });
|
|
564
|
+
const allProps = Object.keys(manifestProps);
|
|
565
|
+
// Derive list-field notes
|
|
566
|
+
const listParts = [];
|
|
567
|
+
for (const [listKey, listMeta] of Object.entries(derived.listFields)) {
|
|
568
|
+
const itemKeys = Object.keys(listMeta.itemFields).join(", ");
|
|
569
|
+
listParts.push(`${listKey} must be a non-empty array of {${itemKeys}}`);
|
|
570
|
+
}
|
|
571
|
+
const autoNotes = listParts.length > 0 ? listParts.join(". ") + "." : `${type} block. Never invent prop names.`;
|
|
572
|
+
result[type] = {
|
|
573
|
+
allowedProps: allProps,
|
|
574
|
+
required: allProps, // conservative: treat all as required
|
|
575
|
+
notes: autoNotes
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
return result;
|
|
579
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { type BlockType, type EditPlan, type PageDoc } from "@avocadostudio-ai/shared";
|
|
3
|
+
export declare const blockTypeEnum: z.ZodEnum<{
|
|
4
|
+
[x: BlockType]: BlockType;
|
|
5
|
+
}>;
|
|
6
|
+
export declare const intentSchema: z.ZodObject<{
|
|
7
|
+
action: z.ZodEnum<{
|
|
8
|
+
info: "info";
|
|
9
|
+
add: "add";
|
|
10
|
+
update: "update";
|
|
11
|
+
remove: "remove";
|
|
12
|
+
move: "move";
|
|
13
|
+
clarify: "clarify";
|
|
14
|
+
}>;
|
|
15
|
+
target_block_ref: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
16
|
+
target_block_type: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
|
|
17
|
+
[x: BlockType]: BlockType;
|
|
18
|
+
}>>>;
|
|
19
|
+
new_block_type: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
|
|
20
|
+
[x: BlockType]: BlockType;
|
|
21
|
+
}>>>;
|
|
22
|
+
position: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
|
|
23
|
+
after: "after";
|
|
24
|
+
bottom: "bottom";
|
|
25
|
+
top: "top";
|
|
26
|
+
before: "before";
|
|
27
|
+
}>>>;
|
|
28
|
+
anchor_block_ref: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
29
|
+
patch: z.ZodOptional<z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
|
|
30
|
+
summary: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
31
|
+
assumption: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
32
|
+
complexity: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
|
|
33
|
+
simple: "simple";
|
|
34
|
+
standard: "standard";
|
|
35
|
+
complex: "complex";
|
|
36
|
+
}>>>;
|
|
37
|
+
}, z.core.$strip>;
|
|
38
|
+
export type ParsedIntent = z.infer<typeof intentSchema>;
|
|
39
|
+
export declare function inferDeterministicIntent(args: {
|
|
40
|
+
message: string;
|
|
41
|
+
currentPage: PageDoc;
|
|
42
|
+
activeBlockId?: string;
|
|
43
|
+
activeEditablePath?: string;
|
|
44
|
+
}): ParsedIntent | null;
|
|
45
|
+
export declare function isMultiIntentRequest(raw: string): boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Returns true only when UI context makes the intent completely unambiguous,
|
|
48
|
+
* so the deterministic planner can handle it without an LLM call.
|
|
49
|
+
*/
|
|
50
|
+
export declare function isHighConfidenceDeterministicCase(args: {
|
|
51
|
+
message: string;
|
|
52
|
+
currentPage: PageDoc;
|
|
53
|
+
activeBlockId?: string;
|
|
54
|
+
activeEditablePath?: string;
|
|
55
|
+
}): boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Attempts to handle a compound message ("remove the hero and add a CTA")
|
|
58
|
+
* by decomposing it into sub-intents and running each through the
|
|
59
|
+
* deterministic planner. Returns null if any sub-intent can't be handled
|
|
60
|
+
* deterministically.
|
|
61
|
+
*/
|
|
62
|
+
export declare function tryCompoundDeterministicPlan(args: {
|
|
63
|
+
session: string;
|
|
64
|
+
message: string;
|
|
65
|
+
slug: string;
|
|
66
|
+
currentPage: PageDoc;
|
|
67
|
+
activeBlockId?: string;
|
|
68
|
+
activeEditablePath?: string;
|
|
69
|
+
}): EditPlan | null;
|
|
70
|
+
export declare function compileDeterministicPlan(args: {
|
|
71
|
+
session: string;
|
|
72
|
+
intent: ParsedIntent;
|
|
73
|
+
message: string;
|
|
74
|
+
slug: string;
|
|
75
|
+
currentPage: PageDoc;
|
|
76
|
+
activeBlockId?: string;
|
|
77
|
+
activeEditablePath?: string;
|
|
78
|
+
locale?: string;
|
|
79
|
+
}): EditPlan | null;
|
|
80
|
+
export { extractAudienceTarget, extractAudienceTargets, titleCaseWords, addAudienceSuffix, audiencePatchForBlock, coercePatchForBlock, parseIndexedPath, inferSimpleFieldPatchFromMessage, isRewriteRequest, isTranslationRequest, shouldKeepRichTextTitleOnTranslate, inferFieldHintFromMessage, rewriteFromExisting, coercePatchForEditablePath, quotedText, buildListAppendPatch } from "./deterministic-planner-patches.ts";
|
|
81
|
+
export { editablePropsFromBlock, promptFromPropKey, userFacingPropNames, ORDINALS, humanizeArrayPath, childSuggestions, clarificationSuggestions, postEditSuggestions, demoPlanFromMessage, titleCaseSentence, pageMetaContractSummary, blockContractsSummary } from "./deterministic-planner-suggestions.ts";
|
|
82
|
+
export { nextAvailableSlug, createPageBlocks, buildCreatePagePlan, isPageRouteRenameRequest } from "./deterministic-planner-pages.ts";
|
|
83
|
+
export { readPathValue, resolveImageUrlForAltField, fetchImageAsBase64, resolveAttachmentsForLlm, selectedBlockSnapshot, arrayPropLengths, pageIntentSummary, plannerContextPack } from "./deterministic-planner-context.ts";
|
|
84
|
+
export type { ResolvedLlmAttachment } from "./deterministic-planner-context.ts";
|
|
85
|
+
export { inferAddedBlockTypeFromMessage, resolveBlockRef, ordinalToIndex, resolveByDescriptor, resolveReferencesFromMessage } from "./deterministic-planner-refs.ts";
|