@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,508 @@
|
|
|
1
|
+
import { patchObject } from "./plan-normalizer.js";
|
|
2
|
+
export function extractAudienceTarget(message) {
|
|
3
|
+
return extractAudienceTargets(message)[0];
|
|
4
|
+
}
|
|
5
|
+
function cleanAudienceCandidate(raw) {
|
|
6
|
+
const cleaned = raw
|
|
7
|
+
.replace(/\b(?:an?|the|only|just)\b/g, " ")
|
|
8
|
+
.replace(/\b(?:audience|audiences|segment|segments)\b/g, " ")
|
|
9
|
+
.replace(/[.?!:;"'`()[\]{}]+/g, " ")
|
|
10
|
+
.replace(/\s+/g, " ")
|
|
11
|
+
.trim()
|
|
12
|
+
.slice(0, 80);
|
|
13
|
+
if (cleaned.length <= 1)
|
|
14
|
+
return undefined;
|
|
15
|
+
const rejectWords = new Set([
|
|
16
|
+
"a while",
|
|
17
|
+
"now",
|
|
18
|
+
"later",
|
|
19
|
+
"free",
|
|
20
|
+
"this",
|
|
21
|
+
"these",
|
|
22
|
+
"that",
|
|
23
|
+
"those",
|
|
24
|
+
"me",
|
|
25
|
+
"you",
|
|
26
|
+
"testing",
|
|
27
|
+
"demo",
|
|
28
|
+
"it",
|
|
29
|
+
"here",
|
|
30
|
+
"there",
|
|
31
|
+
"sure",
|
|
32
|
+
"fun",
|
|
33
|
+
"good",
|
|
34
|
+
"better",
|
|
35
|
+
"best",
|
|
36
|
+
"while"
|
|
37
|
+
]);
|
|
38
|
+
if (rejectWords.has(cleaned.toLowerCase()))
|
|
39
|
+
return undefined;
|
|
40
|
+
const stopwords = new Set(["a", "an", "the", "is", "it", "to", "of", "in", "on", "for", "and", "or", "so", "if"]);
|
|
41
|
+
const words = cleaned.toLowerCase().split(/\s+/);
|
|
42
|
+
if (words.every((w) => stopwords.has(w)))
|
|
43
|
+
return undefined;
|
|
44
|
+
return cleaned;
|
|
45
|
+
}
|
|
46
|
+
function splitAudienceList(raw) {
|
|
47
|
+
return raw
|
|
48
|
+
.split(/\s*(?:,|&|\band\b|\bor\b|\/)\s*/i)
|
|
49
|
+
.map((part) => cleanAudienceCandidate(part))
|
|
50
|
+
.filter((part) => Boolean(part));
|
|
51
|
+
}
|
|
52
|
+
export function extractAudienceTargets(message) {
|
|
53
|
+
const lower = message.toLowerCase();
|
|
54
|
+
const patternMatches = [
|
|
55
|
+
lower.match(/\bfor\s+([a-z0-9 ,&/-]{2,80}?)\s+(?:audience|users?|customers?|buyers?|founders?|teams?|developers?|marketers?|parents?|students?)\b/),
|
|
56
|
+
lower.match(/\bfor\s+([a-z0-9 ,&/-]{2,160})$/),
|
|
57
|
+
lower.match(/\btarget(?:ing)?\s+([a-z0-9 ,&/-]{2,80})\b/),
|
|
58
|
+
lower.match(/\bpages?\s+for\s+([a-z0-9 ,&/-]{2,160})(?:$|[.!?])/),
|
|
59
|
+
lower.match(/\bfor\s+([a-z0-9 ,&/-]{2,160})\s+pages?\b/),
|
|
60
|
+
lower.match(/\b(?:create|generate|build|make|draft)\s+(?:only\s+)?([a-z0-9 ,&/-]{2,160})\s+pages?\b/)
|
|
61
|
+
];
|
|
62
|
+
const raw = patternMatches.find(Boolean)?.[1];
|
|
63
|
+
if (!raw)
|
|
64
|
+
return [];
|
|
65
|
+
const candidates = splitAudienceList(raw);
|
|
66
|
+
return [...new Set(candidates)];
|
|
67
|
+
}
|
|
68
|
+
export function titleCaseWords(text) {
|
|
69
|
+
return text
|
|
70
|
+
.split(/\s+/)
|
|
71
|
+
.filter(Boolean)
|
|
72
|
+
.map((word) => (word.length > 2 ? word[0].toUpperCase() + word.slice(1) : word))
|
|
73
|
+
.join(" ");
|
|
74
|
+
}
|
|
75
|
+
export function addAudienceSuffix(value, audience) {
|
|
76
|
+
const normalized = value.trim();
|
|
77
|
+
if (!normalized)
|
|
78
|
+
return normalized;
|
|
79
|
+
const audienceRe = new RegExp(`\\b${audience.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`, "i");
|
|
80
|
+
if (audienceRe.test(normalized))
|
|
81
|
+
return normalized;
|
|
82
|
+
return `${normalized} for ${audience}`;
|
|
83
|
+
}
|
|
84
|
+
export function audiencePatchForBlock(block, audience) {
|
|
85
|
+
const props = block.props;
|
|
86
|
+
if (block.type === "Hero") {
|
|
87
|
+
const heading = typeof props.heading === "string" ? props.heading : "";
|
|
88
|
+
const subheading = typeof props.subheading === "string" ? props.subheading : "";
|
|
89
|
+
const nextHeading = addAudienceSuffix(heading, audience);
|
|
90
|
+
const nextSubheading = addAudienceSuffix(subheading, audience);
|
|
91
|
+
const patch = {};
|
|
92
|
+
if (nextHeading && nextHeading !== heading)
|
|
93
|
+
patch.heading = nextHeading;
|
|
94
|
+
if (nextSubheading && nextSubheading !== subheading)
|
|
95
|
+
patch.subheading = nextSubheading;
|
|
96
|
+
return patch;
|
|
97
|
+
}
|
|
98
|
+
if (block.type === "RichText") {
|
|
99
|
+
const body = typeof props.body === "string" ? props.body : "";
|
|
100
|
+
const nextBody = body.toLowerCase().includes(audience.toLowerCase()) ? body : `For ${audience}: ${body}`;
|
|
101
|
+
return nextBody !== body ? { body: nextBody } : {};
|
|
102
|
+
}
|
|
103
|
+
if (block.type === "CTA") {
|
|
104
|
+
const title = typeof props.title === "string" ? props.title : "";
|
|
105
|
+
const nextTitle = addAudienceSuffix(title, audience);
|
|
106
|
+
return nextTitle !== title ? { title: nextTitle } : {};
|
|
107
|
+
}
|
|
108
|
+
if (block.type === "FeatureGrid" || block.type === "Testimonials" || block.type === "FAQAccordion" || block.type === "CardGrid" || block.type === "Card") {
|
|
109
|
+
const title = typeof props.title === "string" ? props.title : "";
|
|
110
|
+
const nextTitle = addAudienceSuffix(title, audience);
|
|
111
|
+
return nextTitle !== title ? { title: nextTitle } : {};
|
|
112
|
+
}
|
|
113
|
+
return {};
|
|
114
|
+
}
|
|
115
|
+
export function coercePatchForBlock(block, rawPatch) {
|
|
116
|
+
if (!rawPatch || typeof rawPatch !== "object" || Array.isArray(rawPatch))
|
|
117
|
+
return {};
|
|
118
|
+
const source = "props" in rawPatch &&
|
|
119
|
+
rawPatch.props &&
|
|
120
|
+
typeof rawPatch.props === "object" &&
|
|
121
|
+
!Array.isArray(rawPatch.props)
|
|
122
|
+
? rawPatch.props
|
|
123
|
+
: rawPatch;
|
|
124
|
+
const allowed = Object.keys(block.props);
|
|
125
|
+
const normalizedToAllowed = new Map();
|
|
126
|
+
for (const key of allowed)
|
|
127
|
+
normalizedToAllowed.set(key.toLowerCase(), key);
|
|
128
|
+
const out = {};
|
|
129
|
+
for (const [key, value] of Object.entries(source)) {
|
|
130
|
+
if (allowed.includes(key)) {
|
|
131
|
+
out[key] = value;
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
const mapped = normalizedToAllowed.get(key.toLowerCase());
|
|
135
|
+
if (mapped)
|
|
136
|
+
out[mapped] = value;
|
|
137
|
+
}
|
|
138
|
+
if (block.type === "RichText" && typeof out.body === "string") {
|
|
139
|
+
out.body = out.body
|
|
140
|
+
.replace(/\r\n?/g, "\n")
|
|
141
|
+
.replace(/([.!?])([A-Z])/g, "$1 $2")
|
|
142
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
143
|
+
.trim();
|
|
144
|
+
}
|
|
145
|
+
return out;
|
|
146
|
+
}
|
|
147
|
+
export function parseIndexedPath(path) {
|
|
148
|
+
const match = /^([a-zA-Z0-9_]+)\[(\d+)\](?:\.(.+))?$/.exec(path.trim());
|
|
149
|
+
if (!match)
|
|
150
|
+
return null;
|
|
151
|
+
return {
|
|
152
|
+
listKey: match[1],
|
|
153
|
+
index: Number(match[2]),
|
|
154
|
+
leaf: match[3]
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
export function inferSimpleFieldPatchFromMessage(message) {
|
|
158
|
+
const normalized = message
|
|
159
|
+
.replace(/\u201c|\u201d/g, '"')
|
|
160
|
+
.replace(/\u2018|\u2019/g, "'");
|
|
161
|
+
const m = normalized
|
|
162
|
+
.match(/\b(?:change|set|update|edit|replace)\b[\s\w]*?\b(heading|subheading|title|description|image(?:\s*url)?|photo|picture|cta\s*text|button\s*text|cta|link|href|quote|author|question|answer|q|a)\b[\s\w]*?\b(?:to|as|with)\b\s+['"]?([^'"\n]+)['"]?/i);
|
|
163
|
+
if (!m)
|
|
164
|
+
return null;
|
|
165
|
+
const rawField = m[1].toLowerCase().replace(/\s+/g, "");
|
|
166
|
+
const value = m[2]?.trim();
|
|
167
|
+
if (!value)
|
|
168
|
+
return null;
|
|
169
|
+
const map = {
|
|
170
|
+
heading: "heading",
|
|
171
|
+
subheading: "subheading",
|
|
172
|
+
title: "title",
|
|
173
|
+
description: "description",
|
|
174
|
+
image: "imageUrl",
|
|
175
|
+
imageurl: "imageUrl",
|
|
176
|
+
photo: "imageUrl",
|
|
177
|
+
picture: "imageUrl",
|
|
178
|
+
ctatext: "ctaText",
|
|
179
|
+
buttontext: "ctaText",
|
|
180
|
+
cta: "ctaText",
|
|
181
|
+
link: "ctaHref",
|
|
182
|
+
href: "ctaHref",
|
|
183
|
+
quote: "quote",
|
|
184
|
+
author: "author",
|
|
185
|
+
question: "q",
|
|
186
|
+
answer: "a",
|
|
187
|
+
q: "q",
|
|
188
|
+
a: "a"
|
|
189
|
+
};
|
|
190
|
+
const key = map[rawField];
|
|
191
|
+
if (!key)
|
|
192
|
+
return null;
|
|
193
|
+
return { [key]: value };
|
|
194
|
+
}
|
|
195
|
+
export function isRewriteRequest(message) {
|
|
196
|
+
const lower = message.toLowerCase();
|
|
197
|
+
return (/\brewrit\w*\b/.test(lower) ||
|
|
198
|
+
/\breword\w*\b/.test(lower) ||
|
|
199
|
+
/\brephras\w*\b/.test(lower) ||
|
|
200
|
+
/\bpolish\w*\b/.test(lower) ||
|
|
201
|
+
/\brefin\w*\b/.test(lower) ||
|
|
202
|
+
/\brefresh\w*\b/.test(lower) ||
|
|
203
|
+
/\btighten\w*\b/.test(lower) ||
|
|
204
|
+
/\bclarif\w*\b/.test(lower) ||
|
|
205
|
+
/\bclean\s*up\b/.test(lower) ||
|
|
206
|
+
/\bfreshen\s*up\b/.test(lower) ||
|
|
207
|
+
/\bredo\b.*\b(copy|text|wording|messaging)\b/.test(lower) ||
|
|
208
|
+
/\bimprove\b/.test(lower) ||
|
|
209
|
+
/\bsimplif\w*\b/.test(lower) ||
|
|
210
|
+
/\bmake\b.*\b(shorter|clearer|crisper|concise)\b/.test(lower) ||
|
|
211
|
+
/\bshorten\w*\b/.test(lower));
|
|
212
|
+
}
|
|
213
|
+
const TRANSLATION_KEYWORD_RE = /\btranslat\w*\b|\blocali[zs]\w*\b/;
|
|
214
|
+
const LANGUAGE_NAME_RE = /\bin\s+(?:spanish|french|german|italian|portuguese|chinese|japanese|korean|arabic|russian|hindi|dutch|swedish|norwegian|danish|finnish|polish|turkish|greek|hebrew|thai|vietnamese|indonesian|malay|czech|hungarian|romanian|ukrainian|bulgarian|croatian|serbian|slovak|slovenian|latvian|lithuanian|estonian|bengali|tamil|telugu|urdu|persian|swahili|catalan|basque|galician|tagalog)\b/;
|
|
215
|
+
export function isTranslationRequest(message) {
|
|
216
|
+
const lower = message.toLowerCase();
|
|
217
|
+
return TRANSLATION_KEYWORD_RE.test(lower) || LANGUAGE_NAME_RE.test(lower);
|
|
218
|
+
}
|
|
219
|
+
export function shouldKeepRichTextTitleOnTranslate(args) {
|
|
220
|
+
const { target, activeEditablePath, message, fullPatch } = args;
|
|
221
|
+
if (target.type !== "RichText")
|
|
222
|
+
return false;
|
|
223
|
+
if (activeEditablePath !== "body")
|
|
224
|
+
return false;
|
|
225
|
+
if (!isTranslationRequest(message))
|
|
226
|
+
return false;
|
|
227
|
+
return Object.prototype.hasOwnProperty.call(fullPatch, "title");
|
|
228
|
+
}
|
|
229
|
+
// Keys shorter than this are not matched by name: a prop literally called "a"
|
|
230
|
+
// or "q" (FAQAccordion items) would otherwise match the article "a" in almost
|
|
231
|
+
// any sentence. Those keys are reachable through the synonym table below.
|
|
232
|
+
const MIN_NAMED_KEY_LENGTH = 3;
|
|
233
|
+
// "ctaText" -> /\bcta[\s_-]*text\b/ , "heading" -> /\bheading\b/. The word
|
|
234
|
+
// boundary is what keeps "heading" from matching inside "subheading".
|
|
235
|
+
function fieldNamePattern(key) {
|
|
236
|
+
const words = key
|
|
237
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1 $2")
|
|
238
|
+
.split(/[\s_-]+/)
|
|
239
|
+
.filter(Boolean)
|
|
240
|
+
.map((word) => word.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
|
|
241
|
+
if (words.length === 0)
|
|
242
|
+
return null;
|
|
243
|
+
return new RegExp(`\\b${words.join("[\\s_-]*")}\\b`, "i");
|
|
244
|
+
}
|
|
245
|
+
export function inferFieldHintFromMessage(message, allowedKeys) {
|
|
246
|
+
const lower = message.toLowerCase();
|
|
247
|
+
// 1. The message names one of this block's own props. A site's blocks are its
|
|
248
|
+
// own — their props are called whatever the site calls them ("heading",
|
|
249
|
+
// "eyebrow", "leadText"), so match those before falling back to Avocado's
|
|
250
|
+
// house vocabulary. Longest first, so "subheading" beats "heading" when a
|
|
251
|
+
// block happens to expose both.
|
|
252
|
+
const named = [...allowedKeys]
|
|
253
|
+
.filter((key) => key.length >= MIN_NAMED_KEY_LENGTH)
|
|
254
|
+
.sort((a, b) => b.length - a.length);
|
|
255
|
+
for (const key of named) {
|
|
256
|
+
const pattern = fieldNamePattern(key);
|
|
257
|
+
if (pattern?.test(lower))
|
|
258
|
+
return key;
|
|
259
|
+
}
|
|
260
|
+
// 2. Otherwise map the wording onto a synonym. Each entry lists its candidate
|
|
261
|
+
// keys in preference order, so "change the heading" still resolves to
|
|
262
|
+
// `title` on a canonical Hero, and to `heading` on a block that calls it that.
|
|
263
|
+
const keyMap = [
|
|
264
|
+
{ test: /\btitle\b|\bheading\b|\bheadline\b/, keys: ["title", "heading", "headline"] },
|
|
265
|
+
{ test: /\bdescription\b|\bbody\b|\bcopy\b/, keys: ["description", "body", "copy"] },
|
|
266
|
+
{ test: /\bcta\s*text\b|\bbutton\s*text\b/, keys: ["ctaText", "buttonText"] },
|
|
267
|
+
{ test: /\bcta\s*link\b|\bhref\b|\blink\b|\burl\b/, keys: ["ctaHref", "href", "link", "url"] },
|
|
268
|
+
{ test: /\bquote\b/, keys: ["quote"] },
|
|
269
|
+
{ test: /\bauthor\b/, keys: ["author"] },
|
|
270
|
+
{ test: /\bquestion\b|\bfaq\s*q\b/, keys: ["q", "question"] },
|
|
271
|
+
{ test: /\banswer\b|\bfaq\s*a\b/, keys: ["a", "answer"] }
|
|
272
|
+
];
|
|
273
|
+
for (const entry of keyMap) {
|
|
274
|
+
if (!entry.test.test(lower))
|
|
275
|
+
continue;
|
|
276
|
+
const hit = entry.keys.find((key) => allowedKeys.includes(key));
|
|
277
|
+
if (hit)
|
|
278
|
+
return hit;
|
|
279
|
+
}
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
export function rewriteFromExisting(existing, message) {
|
|
283
|
+
let next = existing
|
|
284
|
+
.replace(/\bamazing\b/gi, "great")
|
|
285
|
+
.replace(/\bincredible\b/gi, "powerful")
|
|
286
|
+
.replace(/\breally\b/gi, "")
|
|
287
|
+
.replace(/\bvery\b/gi, "")
|
|
288
|
+
.replace(/\s{2,}/g, " ")
|
|
289
|
+
.trim();
|
|
290
|
+
if (/short|shorter|concise|brief/i.test(message)) {
|
|
291
|
+
const sentence = next.split(/[.!?]/)[0]?.trim();
|
|
292
|
+
if (sentence)
|
|
293
|
+
next = sentence.endsWith(".") ? sentence : `${sentence}.`;
|
|
294
|
+
}
|
|
295
|
+
if (next === existing) {
|
|
296
|
+
next = existing.endsWith(".") ? `${existing.slice(0, -1)} today.` : `${existing} today.`;
|
|
297
|
+
}
|
|
298
|
+
return next;
|
|
299
|
+
}
|
|
300
|
+
export function coercePatchForEditablePath(block, editablePath, rawPatch, message) {
|
|
301
|
+
if (!editablePath)
|
|
302
|
+
return null;
|
|
303
|
+
const directKey = editablePath.trim();
|
|
304
|
+
const blockProps = block.props;
|
|
305
|
+
if (directKey && Object.prototype.hasOwnProperty.call(blockProps, directKey)) {
|
|
306
|
+
const source = patchObject(rawPatch) ?? inferSimpleFieldPatchFromMessage(message);
|
|
307
|
+
let value;
|
|
308
|
+
if (source) {
|
|
309
|
+
if (Object.prototype.hasOwnProperty.call(source, directKey))
|
|
310
|
+
value = source[directKey];
|
|
311
|
+
else {
|
|
312
|
+
const mapped = Object.keys(source).find((key) => key.toLowerCase() === directKey.toLowerCase());
|
|
313
|
+
if (mapped)
|
|
314
|
+
value = source[mapped];
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
if (value === undefined) {
|
|
318
|
+
const quoted = quotedText(message);
|
|
319
|
+
if (quoted)
|
|
320
|
+
value = quoted;
|
|
321
|
+
}
|
|
322
|
+
if (value === undefined && isRewriteRequest(message)) {
|
|
323
|
+
const existing = blockProps[directKey];
|
|
324
|
+
if (typeof existing === "string" && existing.trim().length > 0)
|
|
325
|
+
value = rewriteFromExisting(existing, message);
|
|
326
|
+
}
|
|
327
|
+
if (value === undefined && /(?:imageurl|href|url|ogimage)/i.test(directKey)) {
|
|
328
|
+
const match = message.match(/https?:\/\/[^\s<>"')]+/i);
|
|
329
|
+
if (match?.[0])
|
|
330
|
+
value = match[0].trim();
|
|
331
|
+
}
|
|
332
|
+
if (value === undefined)
|
|
333
|
+
return null;
|
|
334
|
+
return {
|
|
335
|
+
patch: { [directKey]: value },
|
|
336
|
+
changedKeys: [directKey],
|
|
337
|
+
rootKey: directKey
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
const parsed = parseIndexedPath(editablePath);
|
|
341
|
+
if (!parsed)
|
|
342
|
+
return null;
|
|
343
|
+
const list = block.props[parsed.listKey];
|
|
344
|
+
if (!Array.isArray(list) || parsed.index < 0 || parsed.index >= list.length)
|
|
345
|
+
return null;
|
|
346
|
+
const rowRaw = list[parsed.index];
|
|
347
|
+
if (!rowRaw || typeof rowRaw !== "object" || Array.isArray(rowRaw))
|
|
348
|
+
return null;
|
|
349
|
+
const row = rowRaw;
|
|
350
|
+
const source = patchObject(rawPatch) ?? inferSimpleFieldPatchFromMessage(message);
|
|
351
|
+
const allowedItemKeys = Object.keys(row);
|
|
352
|
+
const normalizedToAllowed = new Map();
|
|
353
|
+
for (const key of allowedItemKeys)
|
|
354
|
+
normalizedToAllowed.set(key.toLowerCase(), key);
|
|
355
|
+
const itemPatch = {};
|
|
356
|
+
if (source) {
|
|
357
|
+
for (const [key, value] of Object.entries(source)) {
|
|
358
|
+
const normalized = key.trim();
|
|
359
|
+
const fromPathPrefix = `${parsed.listKey}[${parsed.index}].`;
|
|
360
|
+
const childKey = normalized.startsWith(fromPathPrefix) ? normalized.slice(fromPathPrefix.length) : normalized;
|
|
361
|
+
const mapped = normalizedToAllowed.get(childKey.toLowerCase());
|
|
362
|
+
if (mapped)
|
|
363
|
+
itemPatch[mapped] = value;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
if (Object.keys(itemPatch).length === 0 && isRewriteRequest(message)) {
|
|
367
|
+
const preferredKey = (parsed.leaf && normalizedToAllowed.get(parsed.leaf.toLowerCase())) ?? inferFieldHintFromMessage(message, allowedItemKeys);
|
|
368
|
+
if (preferredKey) {
|
|
369
|
+
const existing = row[preferredKey];
|
|
370
|
+
if (typeof existing === "string" && existing.trim().length > 0) {
|
|
371
|
+
itemPatch[preferredKey] = rewriteFromExisting(existing, message);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
if (Object.keys(itemPatch).length === 0)
|
|
376
|
+
return null;
|
|
377
|
+
const nextList = list.map((entry, idx) => {
|
|
378
|
+
if (idx !== parsed.index || !entry || typeof entry !== "object" || Array.isArray(entry))
|
|
379
|
+
return entry;
|
|
380
|
+
return { ...entry, ...itemPatch };
|
|
381
|
+
});
|
|
382
|
+
return {
|
|
383
|
+
patch: { [parsed.listKey]: nextList },
|
|
384
|
+
changedKeys: Object.keys(itemPatch),
|
|
385
|
+
rootKey: parsed.listKey
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
export function quotedText(message) {
|
|
389
|
+
const normalized = message
|
|
390
|
+
.replace(/\u201c|\u201d/g, '"')
|
|
391
|
+
.replace(/\u2018|\u2019/g, "'");
|
|
392
|
+
return /"([^"]+)"/.exec(normalized)?.[1]?.trim()
|
|
393
|
+
?? /'([^']+)'/.exec(normalized)?.[1]?.trim();
|
|
394
|
+
}
|
|
395
|
+
const NUMBER_WORDS = {
|
|
396
|
+
one: 1, two: 2, three: 3, four: 4, five: 5, six: 6, seven: 7, eight: 8, nine: 9, ten: 10
|
|
397
|
+
};
|
|
398
|
+
const MAX_APPEND_COUNT = 10;
|
|
399
|
+
/**
|
|
400
|
+
* How many items a list-append request asks for. Only a quantity that directly follows
|
|
401
|
+
* the add verb counts ("add 3 cards", "add two more faqs") — a number elsewhere ("cards
|
|
402
|
+
* about our 5 values") is content, not a count. Recognises digits, number words, and the
|
|
403
|
+
* vague quantifiers "a couple" (2) / "a few" / "several" (3). Clamped to [1, 10] so a
|
|
404
|
+
* typo like "add 500 cards" can't blow up the patch.
|
|
405
|
+
*/
|
|
406
|
+
export function parseRequestedCount(message) {
|
|
407
|
+
const lower = message.toLowerCase();
|
|
408
|
+
const m = lower.match(/\b(?:add|insert|append|create|include|put)\s+(?:another\s+|more\s+)?(\d{1,2}|one|two|three|four|five|six|seven|eight|nine|ten)\b/);
|
|
409
|
+
if (m) {
|
|
410
|
+
const raw = m[1];
|
|
411
|
+
const n = /^\d+$/.test(raw) ? parseInt(raw, 10) : NUMBER_WORDS[raw] ?? 1;
|
|
412
|
+
return Math.max(1, Math.min(MAX_APPEND_COUNT, n));
|
|
413
|
+
}
|
|
414
|
+
if (/\ba\s+couple\b/.test(lower))
|
|
415
|
+
return 2;
|
|
416
|
+
if (/\b(?:a\s+few|several)\b/.test(lower))
|
|
417
|
+
return 3;
|
|
418
|
+
return 1;
|
|
419
|
+
}
|
|
420
|
+
/** Append `count` freshly-built items (index passed to the factory) onto `existing`. */
|
|
421
|
+
function appendItems(existing, count, makeItem) {
|
|
422
|
+
return [...existing, ...Array.from({ length: count }, (_, i) => makeItem(i))];
|
|
423
|
+
}
|
|
424
|
+
export function buildListAppendPatch(block, message) {
|
|
425
|
+
const lower = message.toLowerCase();
|
|
426
|
+
const allQuoted = [...message.matchAll(/['"]([^'"]+)['"]/g)].map((m) => m[1].trim()).filter(Boolean);
|
|
427
|
+
const quoted = allQuoted[0] ?? null;
|
|
428
|
+
// Honour a requested quantity ("add 3 testimonials" → three items). The first item
|
|
429
|
+
// carries any quoted content; extras get a numbered default so they're distinguishable.
|
|
430
|
+
const count = parseRequestedCount(message);
|
|
431
|
+
const sfx = (i) => (count > 1 ? ` ${i + 1}` : "");
|
|
432
|
+
if (block.type === "FAQAccordion") {
|
|
433
|
+
const existing = Array.isArray(block.props.items) ? block.props.items : [];
|
|
434
|
+
const baseQ = quoted ?? (lower.includes("question") ? "New question" : "How does this work?");
|
|
435
|
+
return {
|
|
436
|
+
items: appendItems(existing, count, (i) => ({
|
|
437
|
+
q: i === 0 ? baseQ : `New question${sfx(i)}`,
|
|
438
|
+
a: i === 0 ? allQuoted[1] ?? "Add answer here." : "Add answer here."
|
|
439
|
+
}))
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
if (block.type === "Testimonials") {
|
|
443
|
+
const existing = Array.isArray(block.props.items) ? block.props.items : [];
|
|
444
|
+
return {
|
|
445
|
+
items: appendItems(existing, count, (i) => ({
|
|
446
|
+
quote: i === 0 && quoted ? quoted : "Great experience.",
|
|
447
|
+
author: "Customer"
|
|
448
|
+
}))
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
if (block.type === "FeatureGrid") {
|
|
452
|
+
const existing = Array.isArray(block.props.features) ? block.props.features : [];
|
|
453
|
+
return {
|
|
454
|
+
features: appendItems(existing, count, (i) => ({
|
|
455
|
+
title: i === 0 && quoted ? quoted : `New feature${sfx(i)}`,
|
|
456
|
+
description: "Describe this feature."
|
|
457
|
+
}))
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
if (block.type === "CardGrid") {
|
|
461
|
+
const existing = Array.isArray(block.props.cards) ? block.props.cards : [];
|
|
462
|
+
return {
|
|
463
|
+
cards: appendItems(existing, count, (i) => ({
|
|
464
|
+
title: i === 0 && quoted ? quoted : `New card${sfx(i)}`,
|
|
465
|
+
description: "Add card description.",
|
|
466
|
+
ctaText: "Learn more",
|
|
467
|
+
ctaHref: "/"
|
|
468
|
+
}))
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
if (block.type === "Tabs") {
|
|
472
|
+
const existing = Array.isArray(block.props.tabs) ? block.props.tabs : [];
|
|
473
|
+
return {
|
|
474
|
+
tabs: appendItems(existing, count, (i) => ({
|
|
475
|
+
label: i === 0 && quoted ? quoted : `New tab${sfx(i)}`,
|
|
476
|
+
content: i === 0 ? allQuoted[1] ?? "Add tab content here." : "Add tab content here."
|
|
477
|
+
}))
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
if (block.type === "Stats") {
|
|
481
|
+
const existing = Array.isArray(block.props.stats) ? block.props.stats : [];
|
|
482
|
+
return {
|
|
483
|
+
stats: appendItems(existing, count, (i) => ({
|
|
484
|
+
value: i === 0 && quoted ? quoted : "100+",
|
|
485
|
+
label: i === 0 ? allQuoted[1] ?? "New metric" : `New metric${sfx(i)}`
|
|
486
|
+
}))
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
if (block.type === "Carousel") {
|
|
490
|
+
const existing = Array.isArray(block.props.items) ? block.props.items : [];
|
|
491
|
+
return {
|
|
492
|
+
items: appendItems(existing, count, (i) => ({
|
|
493
|
+
imageUrl: "/hero-generated.svg",
|
|
494
|
+
imageAlt: "New slide",
|
|
495
|
+
heading: i === 0 && quoted ? quoted : `New slide${sfx(i)}`
|
|
496
|
+
}))
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
if (block.type === "Table") {
|
|
500
|
+
const existing = Array.isArray(block.props.rows) ? block.props.rows : [];
|
|
501
|
+
const headers = Array.isArray(block.props.headers) ? block.props.headers : [];
|
|
502
|
+
const width = headers.length || existing[0]?.length || 1;
|
|
503
|
+
return {
|
|
504
|
+
rows: appendItems(existing, count, (i) => Array.from({ length: width }, (_, c) => (c === 0 ? (i === 0 && quoted ? quoted : `New${sfx(i)}`) : "")))
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
return null;
|
|
508
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { BlockType, PageDoc } from "@avocadostudio-ai/shared";
|
|
2
|
+
export declare function inferAddedBlockTypeFromMessage(message: string): BlockType | undefined;
|
|
3
|
+
export declare function resolveBlockRef(args: {
|
|
4
|
+
ref?: string | null;
|
|
5
|
+
currentPage: PageDoc;
|
|
6
|
+
activeBlockId?: string;
|
|
7
|
+
fallbackType?: BlockType | null;
|
|
8
|
+
}): PageDoc["blocks"][number] | null;
|
|
9
|
+
export declare function ordinalToIndex(value: string): 1 | 0 | -1 | 2 | 3 | 4 | null;
|
|
10
|
+
export declare function resolveByDescriptor(args: {
|
|
11
|
+
descriptor: string;
|
|
12
|
+
currentPage: PageDoc;
|
|
13
|
+
activeBlockId?: string;
|
|
14
|
+
}): import("@avocadostudio-ai/shared").BlockInstance | null;
|
|
15
|
+
export declare function resolveReferencesFromMessage(args: {
|
|
16
|
+
message: string;
|
|
17
|
+
currentPage: PageDoc;
|
|
18
|
+
activeBlockId?: string;
|
|
19
|
+
}): {
|
|
20
|
+
target: {
|
|
21
|
+
id: string;
|
|
22
|
+
type: BlockType;
|
|
23
|
+
} | null;
|
|
24
|
+
anchor: {
|
|
25
|
+
id: string;
|
|
26
|
+
type: BlockType;
|
|
27
|
+
} | null;
|
|
28
|
+
mentionedBlocks: {
|
|
29
|
+
id: string;
|
|
30
|
+
type: BlockType;
|
|
31
|
+
reason: string;
|
|
32
|
+
}[];
|
|
33
|
+
};
|