@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,407 @@
|
|
|
1
|
+
import { isStandalonePageOperation, normalizeRouteCandidate, parseCreatePageRequest, parseDuplicatePageRequest, requestsContentGeneration } from "../nlp/intent-helpers.js";
|
|
2
|
+
import { buildCreatePagePlan, quotedText } from "../nlp/deterministic-planner.js";
|
|
3
|
+
import { getPage, getSessionDraft } from "../state/session-state.js";
|
|
4
|
+
import { inferTranslationScopeFromMessage } from "./chat-pipeline-translation.js";
|
|
5
|
+
export function isRewriteLikeMessage(message) {
|
|
6
|
+
const lower = message.toLowerCase();
|
|
7
|
+
return (/\brewrit\w*\b/.test(lower) ||
|
|
8
|
+
/\brephras\w*\b/.test(lower) ||
|
|
9
|
+
/\breword\w*\b/.test(lower) ||
|
|
10
|
+
/\bpolish\w*\b/.test(lower) ||
|
|
11
|
+
/\brefin\w*\b/.test(lower) ||
|
|
12
|
+
/\boptimiz\w*\b/.test(lower) ||
|
|
13
|
+
/\brefresh\w*\b/.test(lower) ||
|
|
14
|
+
/\btighten\w*\b/.test(lower) ||
|
|
15
|
+
/\bclarif\w*\b/.test(lower) ||
|
|
16
|
+
/\bclean\s*up\b/.test(lower) ||
|
|
17
|
+
/\bfreshen\s*up\b/.test(lower) ||
|
|
18
|
+
/\bredo\b.*\b(copy|text|wording|messaging)\b/.test(lower) ||
|
|
19
|
+
/\bmake\b.*\b(shorter|clearer|crisper|concise)\b/.test(lower) ||
|
|
20
|
+
/\bimprove\b.*\b(copy|text|wording|messaging)\b/.test(lower) ||
|
|
21
|
+
/\bchange\b.*\b(tone|copy|wording|text|messaging)\b/.test(lower) ||
|
|
22
|
+
/\breview\b.*\b(copy|text|wording|content|messaging)\b.*\bfor\b/.test(lower));
|
|
23
|
+
}
|
|
24
|
+
export function isPerformanceAwareMessage(message) {
|
|
25
|
+
const lower = message.toLowerCase();
|
|
26
|
+
return (/\bseo\b/.test(lower) ||
|
|
27
|
+
/\bkeyword/.test(lower) ||
|
|
28
|
+
/\bsemantic/.test(lower) ||
|
|
29
|
+
/\bconversion/.test(lower) ||
|
|
30
|
+
/\baccessibility/.test(lower) ||
|
|
31
|
+
/\breadability/.test(lower) ||
|
|
32
|
+
/\bcta\b/.test(lower) ||
|
|
33
|
+
/\bperformance\b/.test(lower));
|
|
34
|
+
}
|
|
35
|
+
export function isLikelyTextField(key) {
|
|
36
|
+
if (!key)
|
|
37
|
+
return false;
|
|
38
|
+
return !/(^|\.)(?:href|url|image|icon|id)$/i.test(key);
|
|
39
|
+
}
|
|
40
|
+
export function collectChangedTextFields(ops) {
|
|
41
|
+
const out = new Set();
|
|
42
|
+
for (const op of ops) {
|
|
43
|
+
if (op.op === "update_props") {
|
|
44
|
+
const patch = op.patch;
|
|
45
|
+
for (const [key, value] of Object.entries(patch ?? {})) {
|
|
46
|
+
if (typeof value !== "string" || value.trim().length === 0)
|
|
47
|
+
continue;
|
|
48
|
+
if (!isLikelyTextField(key))
|
|
49
|
+
continue;
|
|
50
|
+
out.add(key);
|
|
51
|
+
}
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (op.op === "update_item") {
|
|
55
|
+
const patch = op.patch;
|
|
56
|
+
for (const [key, value] of Object.entries(patch ?? {})) {
|
|
57
|
+
if (typeof value !== "string" || value.trim().length === 0)
|
|
58
|
+
continue;
|
|
59
|
+
if (!isLikelyTextField(key))
|
|
60
|
+
continue;
|
|
61
|
+
out.add(`${op.listKey}.${key}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return Array.from(out);
|
|
66
|
+
}
|
|
67
|
+
export function buildMetaChangeLogEntries(ops) {
|
|
68
|
+
const lines = [];
|
|
69
|
+
for (const op of ops) {
|
|
70
|
+
if (op.op !== "update_page_meta")
|
|
71
|
+
continue;
|
|
72
|
+
const patch = op.patch;
|
|
73
|
+
if (typeof patch.title === "string" && patch.title.length > 0) {
|
|
74
|
+
lines.push(`SEO title \u2192 "${patch.title}"`);
|
|
75
|
+
}
|
|
76
|
+
if (typeof patch.description === "string" && patch.description.length > 0) {
|
|
77
|
+
lines.push(`Meta description \u2192 "${patch.description}"`);
|
|
78
|
+
}
|
|
79
|
+
if (typeof patch.ogImage === "string" && patch.ogImage.length > 0) {
|
|
80
|
+
lines.push(`OG image \u2192 ${patch.ogImage}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return lines;
|
|
84
|
+
}
|
|
85
|
+
const AI_JUSTIFICATION_PREFIX = "__ai_justification__:";
|
|
86
|
+
const AI_PERFORMANCE_PREFIX = "__ai_performance__:";
|
|
87
|
+
export function buildAiInsightChanges(args) {
|
|
88
|
+
if (args.plan.intent !== "edit_plan" || args.plan.ops.length === 0)
|
|
89
|
+
return [];
|
|
90
|
+
if (inferTranslationScopeFromMessage(args.message) !== "none")
|
|
91
|
+
return [];
|
|
92
|
+
const textFields = collectChangedTextFields(args.plan.ops);
|
|
93
|
+
if (textFields.length === 0)
|
|
94
|
+
return [];
|
|
95
|
+
const rewriteLike = isRewriteLikeMessage(args.message);
|
|
96
|
+
const performanceAware = isPerformanceAwareMessage(args.message);
|
|
97
|
+
const lines = [];
|
|
98
|
+
if (rewriteLike) {
|
|
99
|
+
lines.push(`${AI_JUSTIFICATION_PREFIX}This version is more benefit-driven and action-oriented.`);
|
|
100
|
+
}
|
|
101
|
+
if (performanceAware) {
|
|
102
|
+
lines.push(`${AI_PERFORMANCE_PREFIX}This wording improves semantic relevance and supports SEO, accessibility, and conversion checks.`);
|
|
103
|
+
}
|
|
104
|
+
return lines;
|
|
105
|
+
}
|
|
106
|
+
// ---------------------------------------------------------------------------
|
|
107
|
+
// Deterministic operation change log — factual descriptions of what each op did
|
|
108
|
+
// ---------------------------------------------------------------------------
|
|
109
|
+
function isImageField(key) {
|
|
110
|
+
return /image(Url|Src)?$|^image$|^src$|^url$/i.test(key);
|
|
111
|
+
}
|
|
112
|
+
function describeUpdatePropsFields(patch) {
|
|
113
|
+
let hasImage = false;
|
|
114
|
+
const textFields = [];
|
|
115
|
+
// Handle nested { props: { ... } } wrapper
|
|
116
|
+
const fields = (typeof patch.props === "object" && patch.props !== null && !Array.isArray(patch.props))
|
|
117
|
+
? patch.props
|
|
118
|
+
: patch;
|
|
119
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
120
|
+
if (isImageField(key)) {
|
|
121
|
+
hasImage = true;
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (typeof value === "string" && value.trim().length > 0 && isLikelyTextField(key)) {
|
|
125
|
+
textFields.push(key);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return { hasImage, textFields };
|
|
129
|
+
}
|
|
130
|
+
/** Format a page slug for display — avoids double-slash for home page "/" or slugs that already start with "/" */
|
|
131
|
+
export function fmtSlug(slug) {
|
|
132
|
+
if (!slug)
|
|
133
|
+
return "/";
|
|
134
|
+
return slug.startsWith("/") ? slug : `/${slug}`;
|
|
135
|
+
}
|
|
136
|
+
export function buildOpChangeLogEntries(ops, ctx) {
|
|
137
|
+
const lines = [];
|
|
138
|
+
for (const op of ops) {
|
|
139
|
+
switch (op.op) {
|
|
140
|
+
case "create_page": {
|
|
141
|
+
const title = op.page.title ?? op.page.slug;
|
|
142
|
+
lines.push(`Created page "${title}" (${fmtSlug(op.page.slug)})`);
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
case "add_block": {
|
|
146
|
+
const bt = op.block.type;
|
|
147
|
+
lines.push(`Added ${bt} block to ${fmtSlug(op.pageSlug)}`);
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
case "remove_block": {
|
|
151
|
+
const bt = ctx.getBlockType(op.pageSlug, op.blockId) ?? "unknown";
|
|
152
|
+
lines.push(`Removed ${bt} block from ${fmtSlug(op.pageSlug)}`);
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
case "move_block": {
|
|
156
|
+
const bt = ctx.getBlockType(op.pageSlug, op.blockId) ?? "block";
|
|
157
|
+
lines.push(`Moved ${bt} block on ${fmtSlug(op.pageSlug)}`);
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
case "duplicate_block": {
|
|
161
|
+
const bt = ctx.getBlockType(op.pageSlug, op.blockId) ?? "block";
|
|
162
|
+
lines.push(`Duplicated ${bt} block on ${fmtSlug(op.pageSlug)}`);
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
case "update_props": {
|
|
166
|
+
const bt = ctx.getBlockType(op.pageSlug, op.blockId) ?? "block";
|
|
167
|
+
const { hasImage, textFields } = describeUpdatePropsFields(op.patch);
|
|
168
|
+
if (hasImage && textFields.length > 0) {
|
|
169
|
+
lines.push(`Updated ${bt} image and ${textFields.join(", ")} on ${fmtSlug(op.pageSlug)}`);
|
|
170
|
+
}
|
|
171
|
+
else if (hasImage) {
|
|
172
|
+
lines.push(`Updated ${bt} image on ${fmtSlug(op.pageSlug)}`);
|
|
173
|
+
}
|
|
174
|
+
else if (textFields.length > 0) {
|
|
175
|
+
lines.push(`Updated ${bt} ${textFields.join(", ")} on ${fmtSlug(op.pageSlug)}`);
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
lines.push(`Updated ${bt} on ${fmtSlug(op.pageSlug)}`);
|
|
179
|
+
}
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
case "add_item": {
|
|
183
|
+
const bt = ctx.getBlockType(op.pageSlug, op.blockId) ?? "block";
|
|
184
|
+
lines.push(`Added item to ${bt} ${op.listKey} on ${fmtSlug(op.pageSlug)}`);
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
case "update_item": {
|
|
188
|
+
const bt = ctx.getBlockType(op.pageSlug, op.blockId) ?? "block";
|
|
189
|
+
const patch = op.patch;
|
|
190
|
+
const hasImg = Object.keys(patch).some(isImageField);
|
|
191
|
+
if (hasImg) {
|
|
192
|
+
lines.push(`Updated item image in ${bt} ${op.listKey} on ${fmtSlug(op.pageSlug)}`);
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
lines.push(`Updated item in ${bt} ${op.listKey} on ${fmtSlug(op.pageSlug)}`);
|
|
196
|
+
}
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
case "remove_item": {
|
|
200
|
+
const bt = ctx.getBlockType(op.pageSlug, op.blockId) ?? "block";
|
|
201
|
+
lines.push(`Removed item from ${bt} ${op.listKey} on ${fmtSlug(op.pageSlug)}`);
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
case "move_item": {
|
|
205
|
+
const bt = ctx.getBlockType(op.pageSlug, op.blockId) ?? "block";
|
|
206
|
+
lines.push(`Reordered item in ${bt} ${op.listKey} on ${fmtSlug(op.pageSlug)}`);
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
case "reorder_items": {
|
|
210
|
+
const bt = ctx.getBlockType(op.pageSlug, op.blockId) ?? "block";
|
|
211
|
+
lines.push(`Reordered ${bt} ${op.listKey} on ${fmtSlug(op.pageSlug)}`);
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
case "reorder_blocks":
|
|
215
|
+
lines.push(`Reordered sections on ${fmtSlug(op.pageSlug)}`);
|
|
216
|
+
break;
|
|
217
|
+
case "rename_page": {
|
|
218
|
+
const targetSlug = op.newPageSlug ?? op.pageSlug;
|
|
219
|
+
const label = op.newTitle
|
|
220
|
+
? (op.newPageSlug && op.newPageSlug !== op.pageSlug
|
|
221
|
+
? `"${op.newTitle}" (${fmtSlug(targetSlug)})`
|
|
222
|
+
: `"${op.newTitle}"`)
|
|
223
|
+
: fmtSlug(targetSlug);
|
|
224
|
+
lines.push(`Renamed page ${fmtSlug(op.pageSlug)} → ${label}`);
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
case "remove_page":
|
|
228
|
+
lines.push(`Removed page ${fmtSlug(op.pageSlug)}`);
|
|
229
|
+
break;
|
|
230
|
+
case "duplicate_page": {
|
|
231
|
+
const target = op.newPageSlug ?? "copy";
|
|
232
|
+
lines.push(`Duplicated page ${fmtSlug(op.pageSlug)} → ${fmtSlug(target)}`);
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
235
|
+
case "move_page":
|
|
236
|
+
lines.push(`Reordered page ${fmtSlug(op.pageSlug)}`);
|
|
237
|
+
break;
|
|
238
|
+
case "update_page_meta":
|
|
239
|
+
// Handled by buildMetaChangeLogEntries — skip to avoid duplication
|
|
240
|
+
break;
|
|
241
|
+
case "update_site_config": {
|
|
242
|
+
const patch = op.patch;
|
|
243
|
+
if (typeof patch.name === "string")
|
|
244
|
+
lines.push(`Site name → "${patch.name}"`);
|
|
245
|
+
if (typeof patch.logo === "string")
|
|
246
|
+
lines.push(`Site logo updated`);
|
|
247
|
+
if (patch.navLabels)
|
|
248
|
+
lines.push(`Navigation labels updated`);
|
|
249
|
+
if (patch.navGroups)
|
|
250
|
+
lines.push(`Navigation groups updated`);
|
|
251
|
+
break;
|
|
252
|
+
}
|
|
253
|
+
case "update_theme": {
|
|
254
|
+
// Semantic token keys (brandColor, headingFont, radius, …) read more
|
|
255
|
+
// naturally spaced and lower-cased: "brand color, heading font".
|
|
256
|
+
const tokens = Object.keys(op.patch)
|
|
257
|
+
.filter((k) => op.patch[k] !== undefined)
|
|
258
|
+
.map((k) => k.replace(/([A-Z])/g, " $1").toLowerCase().trim());
|
|
259
|
+
if (op.cssVars && Object.keys(op.cssVars).length > 0)
|
|
260
|
+
tokens.push("custom CSS");
|
|
261
|
+
lines.push(tokens.length > 0 ? `Theme updated: ${tokens.join(", ")}` : "Theme updated");
|
|
262
|
+
break;
|
|
263
|
+
}
|
|
264
|
+
default: {
|
|
265
|
+
// Exhaustiveness guard: adding a new Operation variant without a
|
|
266
|
+
// changelog line here becomes a compile error rather than a silently
|
|
267
|
+
// empty `changes` list in the UI.
|
|
268
|
+
const _exhaustive = op;
|
|
269
|
+
void _exhaustive;
|
|
270
|
+
break;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return lines;
|
|
275
|
+
}
|
|
276
|
+
// ---------------------------------------------------------------------------
|
|
277
|
+
// Deterministic create page shortcut
|
|
278
|
+
// ---------------------------------------------------------------------------
|
|
279
|
+
export function looksLikeDetailedPageSpec(message) {
|
|
280
|
+
const stripped = message.replace(/\n?\[site context\][\s\S]*?\[\/site context\]\s*$/i, "");
|
|
281
|
+
const lower = stripped.toLowerCase();
|
|
282
|
+
if (/\bblocks\s*\(\s*in\s*order\s*\)/i.test(stripped))
|
|
283
|
+
return true;
|
|
284
|
+
if (/\bbuild\s+(?:the\s+)?page\s+to\s+match\b/i.test(lower))
|
|
285
|
+
return true;
|
|
286
|
+
if (/\bmatch\s+this\s+spec\b/i.test(lower))
|
|
287
|
+
return true;
|
|
288
|
+
if (/\bpage\s+spec\s*:/i.test(lower))
|
|
289
|
+
return true;
|
|
290
|
+
// Numbered block list like "1. Hero", "2. CardGrid" — two or more numbered items
|
|
291
|
+
const numbered = stripped.match(/(?:^|\n|\s)\d+[.)]\s*[A-Z][A-Za-z]+/g) ?? [];
|
|
292
|
+
if (numbered.length >= 2)
|
|
293
|
+
return true;
|
|
294
|
+
return false;
|
|
295
|
+
}
|
|
296
|
+
export function deterministicCreatePagePlan(args) {
|
|
297
|
+
const requestedSlug = parseCreatePageRequest(args.message);
|
|
298
|
+
if (!requestedSlug)
|
|
299
|
+
return null;
|
|
300
|
+
// When the user mentions a template and templates are available, defer to AI planner
|
|
301
|
+
if (/\btemplate\b/i.test(args.message) && args.hasPageTemplates)
|
|
302
|
+
return null;
|
|
303
|
+
// When the user specifies block content (quoted titles, descriptions), defer to AI planner
|
|
304
|
+
if (quotedText(args.message) || /'[^']{2,}'/.test(args.message))
|
|
305
|
+
return null;
|
|
306
|
+
// When the user asks for content generation beyond simple scaffolding,
|
|
307
|
+
// defer to the AI planner which can produce meaningful content.
|
|
308
|
+
if (requestsContentGeneration(args.message))
|
|
309
|
+
return null;
|
|
310
|
+
// When the message looks like a detailed page spec (numbered block list,
|
|
311
|
+
// "blocks (in order)", "build page to match", etc.), defer to LLM so it can
|
|
312
|
+
// honor the full structure — deterministic keyword matching misses types
|
|
313
|
+
// like CardGrid/FeatureGrid/FAQAccordion and can't read per-block content.
|
|
314
|
+
if (looksLikeDetailedPageSpec(args.message))
|
|
315
|
+
return null;
|
|
316
|
+
// When the slug is the generic fallback, defer to the LLM so it can derive
|
|
317
|
+
// a meaningful slug from the page name (e.g. "Mountain Climbers" → /mountain-climbers).
|
|
318
|
+
if (requestedSlug === "/new-page")
|
|
319
|
+
return null;
|
|
320
|
+
// When the target slug already exists, this is almost certainly an EDIT
|
|
321
|
+
// request phrased like a create ("make page /test have: 1. Hero 2. CTA" or
|
|
322
|
+
// "Apply ALL of the following changes to /test … 1. Hero …"). The old
|
|
323
|
+
// behavior returned a "Page X already exists" clarification, which hides
|
|
324
|
+
// the user's real intent. Defer to the LLM so it can plan edits on the
|
|
325
|
+
// existing page instead.
|
|
326
|
+
const draft = getSessionDraft(args.session);
|
|
327
|
+
const normalizedSlug = normalizeRouteCandidate(requestedSlug);
|
|
328
|
+
if (normalizedSlug && draft.has(normalizedSlug))
|
|
329
|
+
return null;
|
|
330
|
+
return buildCreatePagePlan({ session: args.session, requestedSlug, userMessage: args.message });
|
|
331
|
+
}
|
|
332
|
+
export function deterministicDuplicatePagePlan(args) {
|
|
333
|
+
const parsed = parseDuplicatePageRequest(args.message, { currentSlug: args.effectiveSlug });
|
|
334
|
+
if (!parsed?.targetSlug)
|
|
335
|
+
return null;
|
|
336
|
+
// When the user asks for content generation, suggestions, or population alongside
|
|
337
|
+
// the duplicate (e.g. "duplicate this and suggest components and populate them"),
|
|
338
|
+
// defer to the LLM so it can produce a multi-op plan (duplicate_page + add_block +
|
|
339
|
+
// update_props) instead of just the bare duplicate_page op.
|
|
340
|
+
if (requestsContentGeneration(args.message))
|
|
341
|
+
return null;
|
|
342
|
+
const sourceSlug = normalizeRouteCandidate(parsed.sourceSlug ?? args.effectiveSlug);
|
|
343
|
+
const targetSlug = normalizeRouteCandidate(parsed.targetSlug);
|
|
344
|
+
if (!sourceSlug || !targetSlug)
|
|
345
|
+
return null;
|
|
346
|
+
if (sourceSlug === targetSlug) {
|
|
347
|
+
return {
|
|
348
|
+
intent: "needs_clarification",
|
|
349
|
+
summary_for_user: "Source and target page are the same. Provide a different target page path.",
|
|
350
|
+
change_log: [],
|
|
351
|
+
ops: []
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
const sourcePage = getPage(args.session, sourceSlug);
|
|
355
|
+
if (!sourcePage) {
|
|
356
|
+
return {
|
|
357
|
+
intent: "needs_clarification",
|
|
358
|
+
summary_for_user: `I couldn't find source page ${sourceSlug}. Select a page to duplicate first.`,
|
|
359
|
+
change_log: [],
|
|
360
|
+
ops: []
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
const draft = getSessionDraft(args.session);
|
|
364
|
+
let finalTarget = targetSlug;
|
|
365
|
+
if (draft.has(finalTarget)) {
|
|
366
|
+
// Auto-suffix to find an available slug (e.g. /test -> /test-2, /test-3, ...)
|
|
367
|
+
for (let i = 2; i <= 99; i++) {
|
|
368
|
+
const candidate = `${targetSlug}-${i}`;
|
|
369
|
+
if (!draft.has(candidate)) {
|
|
370
|
+
finalTarget = candidate;
|
|
371
|
+
break;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
if (finalTarget === targetSlug) {
|
|
375
|
+
return {
|
|
376
|
+
intent: "needs_clarification",
|
|
377
|
+
summary_for_user: `Page ${targetSlug} already exists. Choose a different target path.`,
|
|
378
|
+
change_log: [],
|
|
379
|
+
ops: []
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
return {
|
|
384
|
+
intent: "edit_plan",
|
|
385
|
+
summary_for_user: `Duplicate ${sourceSlug} into ${finalTarget}.`,
|
|
386
|
+
change_log: [`Duplicate page ${sourceSlug} into ${finalTarget} with all blocks and content.`],
|
|
387
|
+
ops: [{ op: "duplicate_page", pageSlug: sourceSlug, newPageSlug: finalTarget }]
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
export function deterministicSelectedTextRewritePlan(args) {
|
|
391
|
+
const sanitizeRewriteToPlainText = (value) => value
|
|
392
|
+
.replace(/\[([^\]]+)\]\((https?:\/\/[^)]+)\)/gi, "$1")
|
|
393
|
+
.replace(/[*_`~#>]+/g, "")
|
|
394
|
+
.replace(/\s{2,}/g, " ")
|
|
395
|
+
.trim();
|
|
396
|
+
// Disabled: deterministic rewrite is too simplistic (word substitutions / appending "today").
|
|
397
|
+
// Let the AI planner handle rewrite requests so it can generate a proper creative rewrite.
|
|
398
|
+
return null;
|
|
399
|
+
}
|
|
400
|
+
export function shouldReturnDeterministicClarification(message) {
|
|
401
|
+
const lower = message.toLowerCase();
|
|
402
|
+
if (/\b(populate|fill)\b/.test(lower))
|
|
403
|
+
return false;
|
|
404
|
+
return (isStandalonePageOperation(message) ||
|
|
405
|
+
/\b(delete|remove)\b.*\b(page|home)\b/.test(lower) ||
|
|
406
|
+
/\b(rename|move)\b.*\bpage\b/.test(lower));
|
|
407
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { Logger } from "../logger.ts";
|
|
2
|
+
import { type EditPlan, type PageDoc } from "@avocadostudio-ai/shared";
|
|
3
|
+
import type { PendingImageGeneration } from "../state/session-state.ts";
|
|
4
|
+
export declare function blockHasImageUrlProp(block: PageDoc["blocks"][number] | null | undefined): block is PageDoc["blocks"][number];
|
|
5
|
+
export declare function parsePath(path: string): Array<string | number>;
|
|
6
|
+
export declare function getValueAtPath(root: unknown, path: string): unknown;
|
|
7
|
+
export declare function setValueAtPath(root: Record<string, unknown>, path: string, value: unknown): void;
|
|
8
|
+
export declare function deleteValueAtPath(root: Record<string, unknown>, path: string): void;
|
|
9
|
+
export declare function extractIndexedQueries(message: string): Map<number, string>;
|
|
10
|
+
export declare function extractReferencedItemIndices(message: string): {
|
|
11
|
+
numeric: Set<number>;
|
|
12
|
+
includesLast: boolean;
|
|
13
|
+
hasConstraint: boolean;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Check whether a block type's schema supports imageUrl at a given path.
|
|
17
|
+
* E.g. "imageUrl" → true for Hero, "features[0].imageUrl" → false for FeatureGrid.
|
|
18
|
+
*/
|
|
19
|
+
export declare function blockSupportsImageAtPath(blockType: string, imagePath: string): boolean;
|
|
20
|
+
export declare function detectImagePaths(value: unknown, basePath?: string, acc?: Set<string>): Set<string>;
|
|
21
|
+
export declare function imageQueryFromItem(item: Record<string, unknown>, sectionContext?: string): string;
|
|
22
|
+
export declare function shouldPopulateAllChildImages(message: string): boolean;
|
|
23
|
+
export declare function findImageTargets(args: {
|
|
24
|
+
message: string;
|
|
25
|
+
currentPage: PageDoc;
|
|
26
|
+
targetBlock: PageDoc["blocks"][number];
|
|
27
|
+
patchCandidate: Record<string, unknown>;
|
|
28
|
+
}): {
|
|
29
|
+
path: string;
|
|
30
|
+
altPath: string;
|
|
31
|
+
query: string;
|
|
32
|
+
}[];
|
|
33
|
+
export declare function rewriteAddBlockToChildImageUpdate(args: {
|
|
34
|
+
plan: EditPlan;
|
|
35
|
+
message: string;
|
|
36
|
+
currentPage: PageDoc;
|
|
37
|
+
slug: string;
|
|
38
|
+
}): EditPlan;
|
|
39
|
+
export declare function withUnsplashHeroImage(args: {
|
|
40
|
+
plan: EditPlan;
|
|
41
|
+
message: string;
|
|
42
|
+
slug: string;
|
|
43
|
+
currentPage: PageDoc;
|
|
44
|
+
preferredImageOps?: PendingImageGeneration[];
|
|
45
|
+
activeBlockId?: string;
|
|
46
|
+
activeEditablePath?: string;
|
|
47
|
+
chatRequestId?: string;
|
|
48
|
+
gdriveFolderId?: string;
|
|
49
|
+
log: Logger;
|
|
50
|
+
onStatusUpdate?: (message: string) => void;
|
|
51
|
+
onImageProgress?: (event: {
|
|
52
|
+
percent: number;
|
|
53
|
+
stage: string;
|
|
54
|
+
}) => void;
|
|
55
|
+
}): Promise<EditPlan>;
|
|
56
|
+
export declare function shouldResolveCreatePageHeroImage(imageUrl: string): boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Resolve a Hero image for a deferred create_page op.
|
|
59
|
+
* Tries DALL-E first, then falls back to Unsplash.
|
|
60
|
+
*/
|
|
61
|
+
export declare function resolveHeroImageForCreatePage(args: {
|
|
62
|
+
query: string;
|
|
63
|
+
pageTitle: string;
|
|
64
|
+
pageSlug: string;
|
|
65
|
+
sectionContext: string;
|
|
66
|
+
chatRequestId?: string;
|
|
67
|
+
gdriveFolderId?: string;
|
|
68
|
+
log: Logger;
|
|
69
|
+
onStatusUpdate?: (message: string) => void;
|
|
70
|
+
onImageProgress?: (event: {
|
|
71
|
+
percent: number;
|
|
72
|
+
stage: string;
|
|
73
|
+
}) => void;
|
|
74
|
+
}): Promise<{
|
|
75
|
+
url: string;
|
|
76
|
+
alt: string;
|
|
77
|
+
source: "ai-generated" | "gdrive" | "unsplash";
|
|
78
|
+
} | null>;
|
|
79
|
+
export declare function detectImageOps(args: {
|
|
80
|
+
plan: EditPlan;
|
|
81
|
+
message: string;
|
|
82
|
+
slug: string;
|
|
83
|
+
currentPage: PageDoc;
|
|
84
|
+
activeBlockId?: string;
|
|
85
|
+
activeEditablePath?: string;
|
|
86
|
+
}): PendingImageGeneration[];
|