@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,15 @@
|
|
|
1
|
+
export declare function toSeedSlug(raw: string): string;
|
|
2
|
+
export declare function normalizeRouteCandidate(candidate: unknown): string | null;
|
|
3
|
+
export declare function firstRouteMention(message?: string): string | null;
|
|
4
|
+
export declare function extractRouteMentions(message?: string): string[];
|
|
5
|
+
export declare function isLikelyClarificationFollowUp(message: string): boolean;
|
|
6
|
+
export declare function isStandalonePageOperation(message: string): boolean;
|
|
7
|
+
export declare function parseCreatePageRequest(message: string): string | null;
|
|
8
|
+
export declare function parseDuplicatePageRequest(message: string, args?: {
|
|
9
|
+
currentSlug?: string;
|
|
10
|
+
}): {
|
|
11
|
+
sourceSlug: string | null;
|
|
12
|
+
targetSlug: string;
|
|
13
|
+
} | null;
|
|
14
|
+
/** Returns true when the message asks for AI-generated content alongside a page create. */
|
|
15
|
+
export declare function requestsContentGeneration(message: string): boolean;
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
export function toSeedSlug(raw) {
|
|
2
|
+
return raw
|
|
3
|
+
.toLowerCase()
|
|
4
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
5
|
+
.replace(/^-+|-+$/g, "")
|
|
6
|
+
.slice(0, 48);
|
|
7
|
+
}
|
|
8
|
+
export function normalizeRouteCandidate(candidate) {
|
|
9
|
+
if (typeof candidate !== "string")
|
|
10
|
+
return null;
|
|
11
|
+
const trimmed = candidate.trim();
|
|
12
|
+
if (!trimmed)
|
|
13
|
+
return null;
|
|
14
|
+
if (trimmed === "/")
|
|
15
|
+
return "/";
|
|
16
|
+
if (trimmed.startsWith("/"))
|
|
17
|
+
return trimmed;
|
|
18
|
+
if (/^[a-z0-9][a-z0-9/_-]*$/i.test(trimmed))
|
|
19
|
+
return `/${trimmed}`;
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
export function firstRouteMention(message) {
|
|
23
|
+
if (!message)
|
|
24
|
+
return null;
|
|
25
|
+
const match = message.match(/\/[a-z0-9/_-]*/i);
|
|
26
|
+
if (!match)
|
|
27
|
+
return null;
|
|
28
|
+
return normalizeRouteCandidate(match[0]);
|
|
29
|
+
}
|
|
30
|
+
export function extractRouteMentions(message) {
|
|
31
|
+
if (!message)
|
|
32
|
+
return [];
|
|
33
|
+
const matches = message.match(/\/[a-z0-9/_-]*/gi) ?? [];
|
|
34
|
+
const out = [];
|
|
35
|
+
for (const item of matches) {
|
|
36
|
+
const normalized = normalizeRouteCandidate(item);
|
|
37
|
+
if (!normalized)
|
|
38
|
+
continue;
|
|
39
|
+
if (out.includes(normalized))
|
|
40
|
+
continue;
|
|
41
|
+
out.push(normalized);
|
|
42
|
+
}
|
|
43
|
+
return out;
|
|
44
|
+
}
|
|
45
|
+
export function isLikelyClarificationFollowUp(message) {
|
|
46
|
+
const normalized = message.toLowerCase().trim().replace(/\s+/g, " ");
|
|
47
|
+
if (!normalized)
|
|
48
|
+
return false;
|
|
49
|
+
const words = normalized.split(" ").filter(Boolean);
|
|
50
|
+
// Detect common clarification openers regardless of length
|
|
51
|
+
const hasClarificationOpener = /^(i mean\b|i meant\b|no[,.]|sorry[,.]|what i mean|not that[,.]|i was (?:talking|asking) about\b)/.test(normalized);
|
|
52
|
+
if (hasClarificationOpener)
|
|
53
|
+
return true;
|
|
54
|
+
// Detect confirmation / go-ahead responses to a previous clarification offer
|
|
55
|
+
const hasConfirmationOpener = /^(yes\b|yeah\b|yep\b|sure\b|ok\b|okay\b|go ahead\b|do it\b|let'?s (?:see|go|do)\b|show me\b|sounds good\b|please\b)/.test(normalized);
|
|
56
|
+
if (hasConfirmationOpener)
|
|
57
|
+
return true;
|
|
58
|
+
if (isStandalonePageOperation(normalized))
|
|
59
|
+
return false;
|
|
60
|
+
const hasReferenceCue = /\b(selected|this|that|it|them|those|these|one|ones|same)\b/.test(normalized) ||
|
|
61
|
+
/\bfirst|second|third|last\b/.test(normalized) ||
|
|
62
|
+
/\b(?:all|the|both)\s+\d+\b/.test(normalized);
|
|
63
|
+
const hasActionVerb = /\b(add|update|change|edit|remove|delete|move|rename|create|duplicate|set|rewrite|replace|translate|redesign|refocus|overhaul|rebuild|make|hide|show|capitali[sz]e|bold|italici[sz]e|underline|uppercase|lowercase|format|align|cent(?:er|re)|resize|shorten|lengthen|expand|shrink|reword|reorder|sort|swap|enable|disable|increase|decrease|turn)\b/.test(normalized);
|
|
64
|
+
if (hasActionVerb && /\b(this|current|selected|the)\s+page\b/.test(normalized))
|
|
65
|
+
return false;
|
|
66
|
+
// A message that re-states a full action against a concrete target ("delete the last
|
|
67
|
+
// card in the grid", "capitalize all the headings") is a fresh, self-contained command
|
|
68
|
+
// — not an answer to a pending "which one?". Genuine clarification answers ("the first
|
|
69
|
+
// one", "yes, the blue") confirm or name an option without re-issuing the verb against a
|
|
70
|
+
// concrete block/content noun. Without this guard a stale or just-raised clarification
|
|
71
|
+
// hijacks the next unrelated command, composing two unrelated requests (#26).
|
|
72
|
+
const namesConcreteObject = /\b(cards?|sections?|blocks?|headings?|titles?|subheadings?|buttons?|images?|photos?|grids?|heroe?s?|footers?|headers?|paragraphs?|testimonials?|faqs?|ctas?|tabs?|columns?|rows?|prices?|menus?|navs?|logos?|galler(?:y|ies)|videos?)\b/.test(normalized);
|
|
73
|
+
if (hasActionVerb && namesConcreteObject)
|
|
74
|
+
return false;
|
|
75
|
+
return (words.length <= 8 && hasReferenceCue) || (!hasActionVerb && words.length <= 5);
|
|
76
|
+
}
|
|
77
|
+
export function isStandalonePageOperation(message) {
|
|
78
|
+
const normalized = message.toLowerCase().trim().replace(/\s+/g, " ");
|
|
79
|
+
if (/\b(create|generate|add|make|build|populate|fill|remove|delete|rename|move|translate|redesign|refocus|overhaul|rebuild)\b.*\bpage\b/.test(normalized))
|
|
80
|
+
return true;
|
|
81
|
+
// "rename to Olive oil" — implicit page rename even without "page" keyword
|
|
82
|
+
if (/\brename\b.*\bto\s+[a-z]/i.test(normalized) && !/\bto\s+(first|last|top|bottom|start|end|beginning)\b/i.test(normalized))
|
|
83
|
+
return true;
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
export function parseCreatePageRequest(message) {
|
|
87
|
+
// Strip [site context]...[/site context] metadata to prevent false route matches
|
|
88
|
+
const stripped = message.replace(/\n?\[site context\][\s\S]*?\[\/site context\]\s*$/i, "").trim();
|
|
89
|
+
const lower = stripped.toLowerCase().replace(/\s+/g, " ").trim();
|
|
90
|
+
// Don't treat "remove/delete new page" as a create request
|
|
91
|
+
if (/\b(remove|delete|drop|destroy)\b/.test(lower))
|
|
92
|
+
return null;
|
|
93
|
+
const mentionsCurrentPage = /\b(this|current|selected)\s+page\b/.test(lower);
|
|
94
|
+
const hasExplicitRoute = Boolean(firstRouteMention(stripped) ?? extractRouteMentions(stripped)[0]);
|
|
95
|
+
const asksNewPage = /\bnew\s+page\b/.test(lower);
|
|
96
|
+
const isCurrentPagePlacementPhrase = /\b(?:on|at|in|within|inside)\s+(?:this|the|current|selected)\s+page\b/.test(lower) ||
|
|
97
|
+
/\b(?:end|top|bottom|start|beginning)\s+of\s+(?:this|the|current|selected)\s+page\b/.test(lower);
|
|
98
|
+
if (mentionsCurrentPage && !asksNewPage && !hasExplicitRoute)
|
|
99
|
+
return null;
|
|
100
|
+
if (isCurrentPagePlacementPhrase && !asksNewPage && !hasExplicitRoute)
|
|
101
|
+
return null;
|
|
102
|
+
// Bare "the page" is a generic reference to the CURRENT page — "make the page
|
|
103
|
+
// copy shorter", "shorten the page" are edits, not creates. Without this the loose
|
|
104
|
+
// "make…page" create phrase extracted "the" as a page name → junk /the (issue #24).
|
|
105
|
+
// Excludes the create forms "the page called/named/titled/for/about X".
|
|
106
|
+
const editsBareCurrentPage = /\bthe\s+pages?\b/.test(lower) && !/\bthe\s+pages?\s+(?:called|named|titled|for|about|on|with)\b/.test(lower);
|
|
107
|
+
if (editsBareCurrentPage && !asksNewPage && !hasExplicitRoute)
|
|
108
|
+
return null;
|
|
109
|
+
// Definite reference to an EXISTING named page ("make the home page better",
|
|
110
|
+
// "polish the about page"). Like "this page", this is a create request ONLY when paired
|
|
111
|
+
// with an explicit new-page cue (a route or "new page"); otherwise it's an edit/improvement.
|
|
112
|
+
// Without this, "make the home page better" matched the loose `make…page` create phrase and
|
|
113
|
+
// was misread as "create a page named The Home" (issue #17C).
|
|
114
|
+
const refersExistingNamedPage = /\b(?:the|our|my)\s+[a-z][a-z0-9-]*(?:\s+[a-z][a-z0-9-]*){0,2}\s+pages?\b/.test(lower);
|
|
115
|
+
if (refersExistingNamedPage && !asksNewPage && !hasExplicitRoute)
|
|
116
|
+
return null;
|
|
117
|
+
const hasPageWord = /\bpages?\b/.test(lower);
|
|
118
|
+
if (!hasPageWord)
|
|
119
|
+
return null;
|
|
120
|
+
const explicitCreatePhrase = /\b(create|generate|make|build|draft)\b[^.\n]{0,24}\b(new\s+)?pages?\b/.test(lower) ||
|
|
121
|
+
/\badd\s+(a\s+)?(new\s+)?[^.\n]{0,24}\bpages?\b/.test(lower) ||
|
|
122
|
+
/\bnew\s+pages?\b/.test(lower);
|
|
123
|
+
const hasCreateVerb = /\b(create|generate|add|make|build|draft)\b/.test(lower);
|
|
124
|
+
const hasRouteHint = /\/[a-z0-9/_-]*/i.test(stripped);
|
|
125
|
+
if (!explicitCreatePhrase && !(hasCreateVerb && hasRouteHint && hasPageWord))
|
|
126
|
+
return null;
|
|
127
|
+
if (mentionsCurrentPage && !asksNewPage && !hasExplicitRoute)
|
|
128
|
+
return null;
|
|
129
|
+
const directRoute = firstRouteMention(stripped) ?? extractRouteMentions(stripped)[0];
|
|
130
|
+
if (directRoute) {
|
|
131
|
+
const normalized = normalizeRouteCandidate(directRoute);
|
|
132
|
+
if (normalized && normalized !== "/")
|
|
133
|
+
return normalized;
|
|
134
|
+
}
|
|
135
|
+
// Quoted page name: 'create a new page "Test"' → /test, "page called 'About Us'" → /about-us
|
|
136
|
+
const quotedName = stripped.match(/["'\u201C\u201D\u2018\u2019]([^"'\u201C\u201D\u2018\u2019]{1,60})["'\u201C\u201D\u2018\u2019]/);
|
|
137
|
+
if (quotedName) {
|
|
138
|
+
const seed = toSeedSlug(quotedName[1].trim());
|
|
139
|
+
if (seed)
|
|
140
|
+
return `/${seed}`;
|
|
141
|
+
}
|
|
142
|
+
const forAudience = lower.match(/\bpage\s+for\s+([a-z0-9 -]{2,60})$/)?.[1];
|
|
143
|
+
if (forAudience) {
|
|
144
|
+
const seed = toSeedSlug(forAudience);
|
|
145
|
+
if (seed)
|
|
146
|
+
return `/for-${seed}`;
|
|
147
|
+
}
|
|
148
|
+
const aboutTopic = lower.match(/\b(?:new\s+)?page\s+(?:about|on|for)\s+([a-z0-9 -]{2,60})\b/)?.[1];
|
|
149
|
+
if (aboutTopic) {
|
|
150
|
+
// Trim at structural words that introduce block layout ("with a Hero section...")
|
|
151
|
+
const trimmed = aboutTopic.replace(/\s+(?:with|including|containing|featuring|that\s+has|having)\s+.*$/, "").trim();
|
|
152
|
+
const seed = toSeedSlug(trimmed || aboutTopic);
|
|
153
|
+
if (seed)
|
|
154
|
+
return `/${seed}`;
|
|
155
|
+
}
|
|
156
|
+
// "create/add/make [a] [new] <PageName> page" — extract name before "page"
|
|
157
|
+
const namedPage = lower.match(/\b(?:create|add|make|build|generate)\b\s+(?:a\s+)?(?:new\s+)?([a-z][a-z0-9 ]{1,40}?)\s+pages?\b/);
|
|
158
|
+
if (namedPage) {
|
|
159
|
+
const namedPageName = namedPage[1].trim();
|
|
160
|
+
const seed = toSeedSlug(namedPageName);
|
|
161
|
+
// A bare determiner ("the"/"this"/"a") isn't a page name — "make the page …"
|
|
162
|
+
// must not become /the (#24). Let the "page called X" form below win instead.
|
|
163
|
+
const CREATE_NAME_STOPWORDS = new Set(["the", "this", "that", "a", "an", "our", "my", "your", "its", "current", "existing", "same"]);
|
|
164
|
+
if (seed && seed !== "new" && !CREATE_NAME_STOPWORDS.has(namedPageName))
|
|
165
|
+
return `/${seed}`;
|
|
166
|
+
}
|
|
167
|
+
// "page called/named/titled <PageName>"
|
|
168
|
+
const calledPage = lower.match(/\bpages?\s+(?:called|named|titled)\s+['"]?([a-z][a-z0-9 ]{1,40})['"]?$/);
|
|
169
|
+
if (calledPage) {
|
|
170
|
+
const seed = toSeedSlug(calledPage[1].trim());
|
|
171
|
+
if (seed)
|
|
172
|
+
return `/${seed}`;
|
|
173
|
+
}
|
|
174
|
+
return "/new-page";
|
|
175
|
+
}
|
|
176
|
+
export function parseDuplicatePageRequest(message, args) {
|
|
177
|
+
const stripped = message.replace(/\n?\[site context\][\s\S]*?\[\/site context\]\s*$/i, "").trim();
|
|
178
|
+
const lower = stripped.toLowerCase().replace(/\s+/g, " ").trim();
|
|
179
|
+
if (!/\b(duplicate|copy|clone)\b/.test(lower))
|
|
180
|
+
return null;
|
|
181
|
+
if (!/\bpage\b/.test(lower) && !/\b(this|current|selected)\b/.test(lower) && !/\/[a-z0-9/_-]+/i.test(stripped))
|
|
182
|
+
return null;
|
|
183
|
+
const routeMentions = extractRouteMentions(stripped);
|
|
184
|
+
const byCommandRoute = stripped.match(/\b(?:duplicate|copy|clone)\s+(?:the\s+)?(?:page\s+)?(\/[a-z0-9/_-]+)/i)?.[1];
|
|
185
|
+
const byToRoute = stripped.match(/\b(?:to|into|as)\s+(\/[a-z0-9/_-]+)/i)?.[1]
|
|
186
|
+
?? stripped.match(/\b(?:with\s+)?(?:url|path|route|slug)\s+(\/[a-z0-9/_-]+)/i)?.[1];
|
|
187
|
+
const usesCurrentPage = /\b(this|current|selected)\s+page\b/.test(lower) ||
|
|
188
|
+
/\b(?:duplicate|copy|clone)\s+(this|current|selected)\b/.test(lower);
|
|
189
|
+
// When user says "this page", source is the current page — don't grab route mentions as source
|
|
190
|
+
const normalizedByTo = normalizeRouteCandidate(byToRoute ?? null);
|
|
191
|
+
const sourceRouteCandidate = usesCurrentPage
|
|
192
|
+
? null
|
|
193
|
+
: byCommandRoute ?? routeMentions.find((r) => normalizeRouteCandidate(r) !== normalizedByTo) ?? null;
|
|
194
|
+
let sourceSlug = normalizeRouteCandidate(sourceRouteCandidate);
|
|
195
|
+
if (!sourceSlug && usesCurrentPage)
|
|
196
|
+
sourceSlug = normalizeRouteCandidate(args?.currentSlug ?? null);
|
|
197
|
+
let targetSlug = normalizeRouteCandidate(byToRoute ?? null);
|
|
198
|
+
if (!targetSlug) {
|
|
199
|
+
const secondRoute = routeMentions.length >= 2 ? normalizeRouteCandidate(routeMentions[1] ?? null) : null;
|
|
200
|
+
if (secondRoute && secondRoute !== sourceSlug)
|
|
201
|
+
targetSlug = secondRoute;
|
|
202
|
+
}
|
|
203
|
+
if (!targetSlug) {
|
|
204
|
+
const nameMatch = stripped.match(/\b(?:called|named)\s+["']?([a-z0-9][a-z0-9 _-]{1,60})["']?/i)?.[1]?.trim();
|
|
205
|
+
if (nameMatch) {
|
|
206
|
+
const seed = toSeedSlug(nameMatch);
|
|
207
|
+
if (seed)
|
|
208
|
+
targetSlug = `/${seed}`;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
if (!targetSlug || targetSlug === "/")
|
|
212
|
+
return null;
|
|
213
|
+
if (!sourceSlug)
|
|
214
|
+
sourceSlug = normalizeRouteCandidate(args?.currentSlug ?? null);
|
|
215
|
+
if (!sourceSlug || sourceSlug === "/")
|
|
216
|
+
return {
|
|
217
|
+
sourceSlug: sourceSlug ?? null,
|
|
218
|
+
targetSlug
|
|
219
|
+
};
|
|
220
|
+
return { sourceSlug, targetSlug };
|
|
221
|
+
}
|
|
222
|
+
/** Returns true when the message asks for AI-generated content alongside a page create. */
|
|
223
|
+
export function requestsContentGeneration(message) {
|
|
224
|
+
const stripped = message.replace(/\n?\[site context\][\s\S]*?\[\/site context\]\s*$/i, "").trim();
|
|
225
|
+
const lower = stripped.toLowerCase();
|
|
226
|
+
const asksContent = /\bcontent\b/.test(lower) ||
|
|
227
|
+
/\b(fill|populate)\b.*\b(page|it)\b/.test(lower) ||
|
|
228
|
+
/\b(write|describe|explain)\b.*\b(about|for|on)\b/.test(lower) ||
|
|
229
|
+
/\b(analyze|analyse|suggest|improve|rethink|rework)\b/.test(lower) ||
|
|
230
|
+
/\balternate\s+(version|layout)\b/.test(lower) ||
|
|
231
|
+
/\bbased\s+on\s+(the\s+)?(existing|current|original)\b/.test(lower);
|
|
232
|
+
// Strip route paths before checking for block types so /faq doesn't match "faq"
|
|
233
|
+
const withoutRoutes = lower.replace(/\/[a-z0-9/_-]+/g, "");
|
|
234
|
+
const hasExplicitBlockTypes = /\b(hero|cta|call to action|rich\s?text|text\s+(?:section|block)|feature|testimonial|faq|card)\b/.test(withoutRoutes);
|
|
235
|
+
// When a topic is specified AND multiple block types are enumerated
|
|
236
|
+
// (e.g. "page about avocado recipes with a Hero, CardGrid, FAQ"),
|
|
237
|
+
// defer to the LLM — the user wants AI-generated content, not empty scaffolds.
|
|
238
|
+
const hasTopic = /\bpage\s+(?:about|on|for)\s+[a-z]/.test(lower);
|
|
239
|
+
const blockTypeMatches = withoutRoutes.match(/\b(hero|cta|call to action|rich\s?text|text\s+(?:section|block)|feature|testimonial|faq|card)\b/g);
|
|
240
|
+
if (hasTopic && blockTypeMatches && blockTypeMatches.length >= 2)
|
|
241
|
+
return true;
|
|
242
|
+
return asksContent && !hasExplicitBlockTypes;
|
|
243
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Consolidated intent-pattern registry.
|
|
3
|
+
*
|
|
4
|
+
* All regex/string-array constants used for intent classification live here.
|
|
5
|
+
* Functions that *use* the patterns stay in their original modules — only the
|
|
6
|
+
* pattern definitions are centralised.
|
|
7
|
+
*/
|
|
8
|
+
import type { BlockType } from "@avocadostudio-ai/shared";
|
|
9
|
+
/** Matches "block(s)", "component(s)", "section(s)", "element(s)", "widget(s)" */
|
|
10
|
+
export declare const UNIT: string;
|
|
11
|
+
/** Matches "block type(s)", "component type(s)", etc. */
|
|
12
|
+
export declare const UNIT_TYPE: string;
|
|
13
|
+
/**
|
|
14
|
+
* Fuzzy "populate" — tolerates common typos (op-transposition "opoulate",
|
|
15
|
+
* insertion "popoulate", single-p omission "poulate", l/u transposition
|
|
16
|
+
* "popluate", l-insertion "polulate"). Used in batch / page-wide rewrite
|
|
17
|
+
* patterns so a typo doesn't silently downgrade a populate-the-page request
|
|
18
|
+
* to a single update_props (trace c245295d).
|
|
19
|
+
*/
|
|
20
|
+
export declare const POPULATE_FUZZY: string;
|
|
21
|
+
export declare const BLOCK_CATALOG_PATTERNS: RegExp[];
|
|
22
|
+
export declare const BATCH_ADD_PATTERNS: RegExp[];
|
|
23
|
+
export declare const BATCH_UPDATE_PATTERNS: RegExp[];
|
|
24
|
+
export declare const BATCH_PAGE_CREATE_PATTERNS: RegExp[];
|
|
25
|
+
export declare const COUNTED_MULTI_BLOCK_ADD_PATTERN: RegExp;
|
|
26
|
+
export declare const ADD_ACTION_PATTERN: RegExp;
|
|
27
|
+
export declare const FIELD_CONTENT_UPDATE_PATTERN: RegExp;
|
|
28
|
+
export declare const BLOCK_TYPE_KEYWORDS: Array<{
|
|
29
|
+
key: string;
|
|
30
|
+
pattern: RegExp;
|
|
31
|
+
}>;
|
|
32
|
+
export declare const KEYWORD_TO_BLOCK_TYPE: Record<string, BlockType>;
|
|
33
|
+
export declare const EACH_BLOCK_TYPE_PATTERN: RegExp;
|
|
34
|
+
export declare const PAGE_WIDE_REWRITE_PATTERNS: RegExp[];
|
|
35
|
+
export declare const BATCH_REORDER_PATTERNS: RegExp[];
|
|
36
|
+
export declare const PAGE_LIST_PATTERNS: RegExp[];
|
|
37
|
+
export declare const CROSS_PAGE_SCOPE_PATTERN: RegExp;
|
|
38
|
+
export declare const CONTENT_QUERY_PATTERNS: RegExp[];
|
|
39
|
+
export declare const EXCEPT_PATTERN: RegExp;
|
|
40
|
+
export declare const THIS_ONE_PATTERN: RegExp;
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Consolidated intent-pattern registry.
|
|
3
|
+
*
|
|
4
|
+
* All regex/string-array constants used for intent classification live here.
|
|
5
|
+
* Functions that *use* the patterns stay in their original modules — only the
|
|
6
|
+
* pattern definitions are centralised.
|
|
7
|
+
*/
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// Shared building-block fragments
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
/** Matches "block(s)", "component(s)", "section(s)", "element(s)", "widget(s)" */
|
|
12
|
+
export const UNIT = String.raw `(?:blocks?|components?|sections?|elements?|widgets?)`;
|
|
13
|
+
/** Matches "block type(s)", "component type(s)", etc. */
|
|
14
|
+
export const UNIT_TYPE = String.raw `(?:block|component|section|element|widget)\s+types?`;
|
|
15
|
+
/**
|
|
16
|
+
* Fuzzy "populate" — tolerates common typos (op-transposition "opoulate",
|
|
17
|
+
* insertion "popoulate", single-p omission "poulate", l/u transposition
|
|
18
|
+
* "popluate", l-insertion "polulate"). Used in batch / page-wide rewrite
|
|
19
|
+
* patterns so a typo doesn't silently downgrade a populate-the-page request
|
|
20
|
+
* to a single update_props (trace c245295d).
|
|
21
|
+
*/
|
|
22
|
+
export const POPULATE_FUZZY = String.raw `(?:populate|opoulate|popoulate|poulate|popluate|polulate)`;
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Block catalog — "what blocks can I add?"
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
export const BLOCK_CATALOG_PATTERNS = [
|
|
27
|
+
new RegExp(String.raw `\bwhat\s+(?:other\s+)?${UNIT}\s+(?:can|do)\s+(?:you|i|we)\s+(?:have|add)\b`),
|
|
28
|
+
new RegExp(String.raw `\bwhich\s+(?:other\s+)?${UNIT}\s+(?:can|do)\s+(?:you|i|we)\s+(?:have|add)\b`),
|
|
29
|
+
new RegExp(String.raw `\bwhat\s+(?:other\s+)?${UNIT_TYPE}\s+(?:can|do)\s+(?:you|i|we)\s+(?:have|add)\b`),
|
|
30
|
+
new RegExp(String.raw `\bwhich\s+(?:other\s+)?${UNIT_TYPE}\s+(?:can|do)\s+(?:you|i|we)\s+(?:have|add)\b`),
|
|
31
|
+
new RegExp(String.raw `\bwhat\s+${UNIT}\s+(?:are|is)\s+(?:available|supported)\b`),
|
|
32
|
+
new RegExp(String.raw `\bwhat\s+${UNIT}\s+are\b.{0,20}\badd\b`),
|
|
33
|
+
new RegExp(String.raw `\bavailabl\w*\s+${UNIT}\b`),
|
|
34
|
+
new RegExp(String.raw `\bavailabl\w*\s+${UNIT_TYPE}\b`),
|
|
35
|
+
/\bwhat\s+else\s+can\s+i\s+add\b/,
|
|
36
|
+
/\bwhat\s+other\s+content\b/,
|
|
37
|
+
/\blist\s+(all\s+)?(the\s+)?(?:blocks?|components?|sections?)\b/
|
|
38
|
+
];
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
// Batch add — "add all available block types", "scaffold the page"
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
export const BATCH_ADD_PATTERNS = [
|
|
43
|
+
new RegExp(String.raw `\badd\s+(?:all|every|each|the\s+remaining|the\s+rest\s+of(?:\s+the)?)\s+${UNIT}\b`),
|
|
44
|
+
new RegExp(String.raw `\badd\s+(?:all|every|each|the\s+remaining|the\s+rest\s+of(?:\s+the)?)\s+${UNIT_TYPE}\b`),
|
|
45
|
+
/\bscaffold\b/,
|
|
46
|
+
/\bfill\s+(?:out\s+)?(?:the\s+)?page\b/,
|
|
47
|
+
/\badd\s+(?:all|every)\s+(?:available|missing|remaining)\b/,
|
|
48
|
+
new RegExp(String.raw `\b(?:all|every)\s+(?:available|missing|remaining)\s+${UNIT}\b`),
|
|
49
|
+
/\bbuild\s+(?:out|up)\s+(?:the\s+)?(?:whole\s+)?page\b/,
|
|
50
|
+
// "showcasing/featuring all (available) components/blocks" — page creation with all block types
|
|
51
|
+
new RegExp(String.raw `\b(?:showcas\w*|featuring|demonstrat\w*)\s+(?:all\s+)?(?:(?:the\s+)?available\s+)?${UNIT}\b`),
|
|
52
|
+
// "all available componenzs" and similar typos — fuzzy match for "all available" + unit-like word
|
|
53
|
+
/\ball\s+(?:available|existing)\s+\w*(?:componen|block|section|element|widget)\w*\b/,
|
|
54
|
+
// "add more content/sections to this page", "add content to the page"
|
|
55
|
+
/\badd\s+(?:more\s+)?(?:content|sections?)\s+(?:to\s+)?(?:this|the)\s+page\b/,
|
|
56
|
+
// "expand this page", "flesh out this page", "enrich this page"
|
|
57
|
+
/\b(?:expand|flesh\s+out|enrich|grow|extend|bulk\s+up)\s+(?:this|the)\s+page\b/
|
|
58
|
+
];
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
// Batch update — "populate all components", "update all blocks"
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
export const BATCH_UPDATE_PATTERNS = [
|
|
63
|
+
new RegExp(String.raw `\b(?:${POPULATE_FUZZY}|update|edit|change|rewrite|refresh)\s+(?:all|every|each)\s+${UNIT}\b`),
|
|
64
|
+
new RegExp(String.raw `\b(?:${POPULATE_FUZZY}|update|edit|change|rewrite|refresh)\s+(?:all|every|each)\s+${UNIT_TYPE}\b`),
|
|
65
|
+
new RegExp(String.raw `\b(?:${POPULATE_FUZZY}|fill\s+in|fill)\s+(?:all|every|each)\s+(?:the\s+)?${UNIT}\b`),
|
|
66
|
+
new RegExp(String.raw `\b(?:${POPULATE_FUZZY}|update|edit|change|rewrite|refresh)\s+(?:all|every)\s+(?:existing\s+)?(?:content|blocks?|components?|sections?)\b`),
|
|
67
|
+
new RegExp(String.raw `\b${POPULATE_FUZZY}\s+(?:\w+\s+){0,2}page\b`),
|
|
68
|
+
new RegExp(String.raw `\b${POPULATE_FUZZY}\b.{0,60}\b(?:with|w[io]th)\b.{0,30}\bcontent\b`),
|
|
69
|
+
/\b(?:sample|placeholder|demo)\s+content\s+(?:for|to|in|on)\s+(?:all|every|each)\b/,
|
|
70
|
+
new RegExp(String.raw `\b(?:all|every|each)\s+(?:the\s+)?${UNIT}\s+with\s+(?:sample|placeholder|demo|real)\s+content\b`)
|
|
71
|
+
];
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
// Batch page creation — "create pages for these audiences"
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
export const BATCH_PAGE_CREATE_PATTERNS = [
|
|
76
|
+
/\b(?:create|generate|build|make|draft|add)\b[^.\n]{0,140}\bpages\b/,
|
|
77
|
+
/\b(?:create|generate|build|make|draft)\b[^.\n]{0,140}\bonly\b[^.\n]{0,140}\bpages\b/,
|
|
78
|
+
/\bpages?\s+for\s+(?:these|those|the following|multiple|several)\b/,
|
|
79
|
+
/\bfor\s+.+\b(?:and|,|&)\b.+\bpages?\b/,
|
|
80
|
+
/\bfor\s+.+\b(?:and|,|&)\b.+\b(?:audiences|users?|customers?|buyers?|founders?|teams?|developers?|marketers?|parents?|students?)\b/
|
|
81
|
+
];
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
// Counted multi-block add — "add 3 blocks", "add two sections"
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
export const COUNTED_MULTI_BLOCK_ADD_PATTERN = /\b(?:add|insert|include|create|generate|build)\s+(?:\d+|two|three|four|five|six|seven|eight|nine|ten)\s+(?:(?:\w+[-\s])*)(?:block\w*|component\w*|section\w*|element\w*|widget\w*)\b/;
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
// Generic add-action verb
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
export const ADD_ACTION_PATTERN = /\b(?:add|insert|include|create|generate|build)\b/;
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
// Field-content update disguised as add — "add X in/to [the Y] heading"
|
|
92
|
+
// "add numbers in the Hero subheading" is an update to Hero.subheading,
|
|
93
|
+
// NOT a batch add (even when "numbers"/"copy"/etc. alias block-type keywords).
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
const FIELD_NAMES = String.raw `(?:heading|subheading|sub-?title|title|copy|body|description|caption|label|paragraph|button\s*text|cta\s*text)`;
|
|
96
|
+
export const FIELD_CONTENT_UPDATE_PATTERN = new RegExp(String.raw `\b(?:add|insert|include|append|put)\b[\s\S]*?\b(?:in|into|to|within|inside)\s+(?:the\s+|a\s+|an\s+)?(?:\w+[\s-]+){0,3}` +
|
|
97
|
+
FIELD_NAMES +
|
|
98
|
+
String.raw `\b`, "i");
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
// Block-type keyword lookup — maps natural language to BlockType values
|
|
101
|
+
// ---------------------------------------------------------------------------
|
|
102
|
+
export const BLOCK_TYPE_KEYWORDS = [
|
|
103
|
+
{ key: "hero", pattern: /\bhero\b/ },
|
|
104
|
+
{ key: "featuregrid", pattern: /\bfeature\s*grid\b|\bfeatures?\b/ },
|
|
105
|
+
{ key: "testimonials", pattern: /\btestimonials?\b|\breviews?\b(?!\s+(?:the|this|it|my|our|heading|page|content|copy|text|block|section))|\bsocial proof\b/ },
|
|
106
|
+
{ key: "faq", pattern: /\bfaq\b/ },
|
|
107
|
+
{ key: "cta", pattern: /\bcta\b|\bcall to action\b/ },
|
|
108
|
+
{ key: "cardgrid", pattern: /\bcard\s*grid\b|\bcardgrid\b|\bpricing\b/ },
|
|
109
|
+
{ key: "card", pattern: /\bcard\b/ },
|
|
110
|
+
{ key: "richtext", pattern: /\brich[\s-]?text\b|\btext block\b|\bparagraph\b|\bcopy\b(?!\s+(?:the|this|it|of|from|to))/ },
|
|
111
|
+
{ key: "twocolumn", pattern: /\btwo\s*column\b|\btwocolumn\b|\b2\s*column\b/ },
|
|
112
|
+
{ key: "stats", pattern: /\bstats?\b|\bstatistics\b|\bmetrics\b|\bnumbers\b/ },
|
|
113
|
+
{ key: "carousel", pattern: /\bcarousel\b|\bslideshow\b|\bslider\b/ },
|
|
114
|
+
{ key: "gallery", pattern: /\bgallery\b|\bimage grid\b/ },
|
|
115
|
+
{ key: "tabs", pattern: /\btabs?\b|\btabbed\b/ },
|
|
116
|
+
{ key: "table", pattern: /\btable\b/ },
|
|
117
|
+
{ key: "quote", pattern: /\bblockquote\b/ },
|
|
118
|
+
{ key: "video", pattern: /\bvideo\b/ },
|
|
119
|
+
{ key: "embed", pattern: /\bembed\b|\biframe\b/ },
|
|
120
|
+
{ key: "banner", pattern: /\bbanner\b|\bannouncement\b/ }
|
|
121
|
+
];
|
|
122
|
+
export const KEYWORD_TO_BLOCK_TYPE = {
|
|
123
|
+
hero: "Hero",
|
|
124
|
+
featuregrid: "FeatureGrid",
|
|
125
|
+
testimonials: "Testimonials",
|
|
126
|
+
faq: "FAQAccordion",
|
|
127
|
+
cta: "CTA",
|
|
128
|
+
cardgrid: "CardGrid",
|
|
129
|
+
card: "Card",
|
|
130
|
+
richtext: "RichText",
|
|
131
|
+
twocolumn: "TwoColumn",
|
|
132
|
+
stats: "Stats",
|
|
133
|
+
carousel: "Carousel",
|
|
134
|
+
gallery: "Gallery",
|
|
135
|
+
tabs: "Tabs",
|
|
136
|
+
table: "Table",
|
|
137
|
+
quote: "Quote",
|
|
138
|
+
video: "Video",
|
|
139
|
+
embed: "Embed",
|
|
140
|
+
banner: "Banner"
|
|
141
|
+
};
|
|
142
|
+
// ---------------------------------------------------------------------------
|
|
143
|
+
// "each card", "every feature" — batch op across specific block type items
|
|
144
|
+
// ---------------------------------------------------------------------------
|
|
145
|
+
export const EACH_BLOCK_TYPE_PATTERN = new RegExp(String.raw `\b(?:each|every|all)\s+(?:` +
|
|
146
|
+
BLOCK_TYPE_KEYWORDS.map((entry) => entry.pattern.source).join("|") +
|
|
147
|
+
String.raw `)\b`);
|
|
148
|
+
// ---------------------------------------------------------------------------
|
|
149
|
+
// Page-wide rewrite / rebrand — "refocus this page on X"
|
|
150
|
+
// ---------------------------------------------------------------------------
|
|
151
|
+
export const PAGE_WIDE_REWRITE_PATTERNS = [
|
|
152
|
+
/\b(?:refocus|rebrand|retheme|overhaul|redesign|transform)\s+(?:this|the)\s+page\b/,
|
|
153
|
+
/\brewrite\s+(?:all|the|this)\s+(?:page|content)\b/,
|
|
154
|
+
/\bredo\s+(?:this|the)\s+(?:whole\s+|entire\s+)?page\b/,
|
|
155
|
+
/\b(?:update|change)\s+(?:this|the)\s+(?:whole|entire)\s+page\b/,
|
|
156
|
+
new RegExp(String.raw `\b(?:${POPULATE_FUZZY}|fill)\s+(?:this\s+|the\s+)?page\b`),
|
|
157
|
+
new RegExp(String.raw `\b(?:${POPULATE_FUZZY}|fill)\b.*\b(?:with|using|w[io]th)\b.*\bcontent\b`),
|
|
158
|
+
// Structural audit requests — "review heading hierarchy", "fix heading structure", "review Grapefruits page heading tag hierarchy"
|
|
159
|
+
/\b(?:review|audit|fix|check)\b[\s\w]*\bheadings?\s*(?:tags?\s*)?(?:hierarchy|structure|levels?|order)\b/,
|
|
160
|
+
// Tonal / stylistic sweeps — "make this page more playful", "make /about more professional"
|
|
161
|
+
// Any "make (this|the|/slug) (whole|entire)? page more|less <adjective>" triggers page-wide treatment.
|
|
162
|
+
/\bmake\s+(?:this|the|our|my)\s+(?:whole\s+|entire\s+)?page\s+(?:more|less|feel|sound|look)\s+\w+/,
|
|
163
|
+
/\bmake\s+\/\w[\w/-]*\s+(?:more|less|feel|sound|look)\s+\w+/,
|
|
164
|
+
// Emoji sweeps across many blocks — "add emojis to headings, card titles, and CTA text"
|
|
165
|
+
// Require a breadth signal (multiple targets or all/every/everything) to avoid catching narrow single-field asks.
|
|
166
|
+
/\badd\s+emoji(?:s)?\b[\s\S]*\b(?:all|every|everything|headings?|titles?|labels?)\b/,
|
|
167
|
+
/\badd\s+emoji(?:s)?\b[\s\S]*,[\s\S]*\band\b/,
|
|
168
|
+
// Multi-part sweeping edits — "<change X>, <change Y>, and <change Z>" combined with a tonal/sweeping verb
|
|
169
|
+
/\bmake\s+(?:this|the)\s+page\s+more\s+\w+[\s\S]*\b(?:add|insert|include|update|rewrite|refresh)\b/,
|
|
170
|
+
];
|
|
171
|
+
// ---------------------------------------------------------------------------
|
|
172
|
+
// Batch reorder / rearrange — "reorder blocks", "rearrange sections"
|
|
173
|
+
// ---------------------------------------------------------------------------
|
|
174
|
+
export const BATCH_REORDER_PATTERNS = [
|
|
175
|
+
/\b(?:reorder|re-order|rearrange|reorganize|re-organize|sort|shuffle)\s+(?:the\s+|all\s+)?(?:blocks?|sections?|components?|elements?)\b/,
|
|
176
|
+
/\b(?:reorder|re-order|rearrange|reorganize|re-organize|sort|shuffle)\s+(?:blocks?|sections?|components?|elements?)?\s*(?:in|into|to|for)\b/,
|
|
177
|
+
/\b(?:move|reorder|rearrange)\s+(?:all|every|each)\s+(?:blocks?|sections?|components?)\b/,
|
|
178
|
+
/\breoder\b/, // common typo for "reorder"
|
|
179
|
+
];
|
|
180
|
+
// ---------------------------------------------------------------------------
|
|
181
|
+
// Page listing — "list all pages", "what pages do I have"
|
|
182
|
+
// ---------------------------------------------------------------------------
|
|
183
|
+
export const PAGE_LIST_PATTERNS = [
|
|
184
|
+
/\blist\s+(all\s+)?(the\s+)?pages\b/,
|
|
185
|
+
/\bshow\s+(me\s+)?(all\s+)?(the\s+)?pages\b/,
|
|
186
|
+
/\bwhat\s+pages\s+(are|do|does|is)\b/,
|
|
187
|
+
/\bwhich\s+pages\s+(are|do|does|is)\b/,
|
|
188
|
+
/\bhow\s+many\s+pages\b/,
|
|
189
|
+
/\bwhat\s+pages\s+(do\s+)?(i|we)\s+(have|got)\b/,
|
|
190
|
+
/\b(all|my|the|our)\s+pages\b/
|
|
191
|
+
];
|
|
192
|
+
// ---------------------------------------------------------------------------
|
|
193
|
+
// Cross-page scope — a mutation that should span the whole site, not just the
|
|
194
|
+
// current page ("update the CTA on all pages", "make the footer site-wide").
|
|
195
|
+
// The single-page deterministic path can't honor these, so they defer to the
|
|
196
|
+
// LLM (whose ops carry pageSlug and can fan out). See planner-quality #29.
|
|
197
|
+
// ---------------------------------------------------------------------------
|
|
198
|
+
export const CROSS_PAGE_SCOPE_PATTERN = /\b(?:all|every|each)\s+(?:the\s+)?pages?\b|\bsite[\s-]?wide\b|\bacross\s+(?:the\s+)?(?:whole\s+|entire\s+)?site\b|\bthroughout\s+(?:the\s+)?(?:whole\s+|entire\s+)?site\b|\bon\s+(?:the\s+)?(?:whole|entire)\s+site\b|\beverywhere\b|\bglobally\b/i;
|
|
199
|
+
// ---------------------------------------------------------------------------
|
|
200
|
+
// Content queries — read-only questions about what's on the page
|
|
201
|
+
// "list all CTA buttons", "what images are on this page", "show me all links"
|
|
202
|
+
// ---------------------------------------------------------------------------
|
|
203
|
+
export const CONTENT_QUERY_PATTERNS = [
|
|
204
|
+
/\b(?:list|show|display|enumerate|find|what are)\b.*\b(?:cta|buttons?|links?|images?|headings?|titles?|texts?|urls?|hrefs?|sections?|blocks?)/,
|
|
205
|
+
/\b(?:what|which|where|how many)\b.*\b(?:cta|buttons?|links?|images?|headings?|titles?|urls?|hrefs?)\b/,
|
|
206
|
+
/\b(?:list|show|find)\s+(?:me\s+)?(?:all|every|each)\b.*\b(?:on|in|across)\s+(?:this|the)\s+(?:page|site)\b/,
|
|
207
|
+
/\bhow\s+many\s+(?:cta|button|link|image|heading|section|block|card|feature|item|testimonial)/,
|
|
208
|
+
/\bwhat\s+(?:does|is)\s+(?:the|this)\s+(?:page|site)\s+(?:about|contain|have|include)\b/,
|
|
209
|
+
/\bwhat\s+(?:content|copy|text)\s+(?:is|does)\b.*\b(?:on|in)\s+(?:this|the)\s+page\b/,
|
|
210
|
+
/\bsummarize\s+(?:this|the)\s+(?:page|content)\b/,
|
|
211
|
+
/\bwhat\s+(?:blocks?|sections?)\s+(?:are|is)\s+(?:on|in)\s+(?:this|the)\s+page\b/,
|
|
212
|
+
/\b(?:list|show|tell me)\b.*\b(?:all|every)\b.*\b(?:blocks?|sections?|components?)\b.*\b(?:on|in)\b/,
|
|
213
|
+
/\b(?:audit|review|check|inspect|analyze)\s+(?:the\s+)?(?:page|content|copy|text|links?|images?|buttons?|hero|cta|testimonials?|faq|feature\s*grid|card\s*grid|stats?|footer|contact\s*form|rich\s*text|two\s*column|carousel|gallery|tabs?|table|quote|video|embed|banner|section|block)\b/,
|
|
214
|
+
/\bdescribe\s+(?:this|the|that)\s+(?:image|photo|picture|icon|logo|illustration|page|site|content|section)\b/,
|
|
215
|
+
];
|
|
216
|
+
// ---------------------------------------------------------------------------
|
|
217
|
+
// Exception modifier — "except", "but not", "other than", "all but"
|
|
218
|
+
// ---------------------------------------------------------------------------
|
|
219
|
+
export const EXCEPT_PATTERN = /\b(except|but not|other than|besides|everything but)\b|\ball\s+but\b/i;
|
|
220
|
+
// ---------------------------------------------------------------------------
|
|
221
|
+
// "this one" / "this block" — refers to the currently-selected block
|
|
222
|
+
// ---------------------------------------------------------------------------
|
|
223
|
+
export const THIS_ONE_PATTERN = /\b(this\s+(?:one|block|section|component)|the\s+(?:selected|current|active)\s+(?:one|block|section|component))\b|\bthis\s*$/i;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type BlockType, type PageDoc } from "@avocadostudio-ai/shared";
|
|
2
|
+
export declare function extractJsonObject(input: string): string | null;
|
|
3
|
+
/**
|
|
4
|
+
* Attempt to repair common JSON malformations from LLM output:
|
|
5
|
+
* - Bare `-` used as markdown bullets inside arrays: `[- "item"]` → `["item"]`
|
|
6
|
+
* - Trailing commas before `]` or `}`
|
|
7
|
+
* Returns repaired string or original if no repair was needed.
|
|
8
|
+
*/
|
|
9
|
+
export declare function repairJson(raw: string): string;
|
|
10
|
+
export type JsonRepairStrategy = "basic_repair" | "bare_minus_fix" | "close_brackets" | "truncate_at_boundary" | "none";
|
|
11
|
+
export type JsonRepairResult = {
|
|
12
|
+
parsed: unknown;
|
|
13
|
+
strategy: JsonRepairStrategy;
|
|
14
|
+
/** Number of character-level mutations applied (for bare_minus_fix). */
|
|
15
|
+
mutationCount?: number;
|
|
16
|
+
/** Bytes discarded by truncation strategies. */
|
|
17
|
+
discardedBytes?: number;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Attempt multiple JSON repair strategies. Returns the first one that
|
|
21
|
+
* produces parseable JSON along with metadata about what was done,
|
|
22
|
+
* or throws the original error.
|
|
23
|
+
*/
|
|
24
|
+
export declare function repairAndParseJsonWithMeta(raw: string): JsonRepairResult;
|
|
25
|
+
/**
|
|
26
|
+
* Attempt multiple JSON repair strategies. Returns the first one that
|
|
27
|
+
* produces parseable JSON, or throws the original error.
|
|
28
|
+
*/
|
|
29
|
+
export declare function repairAndParseJson(raw: string): unknown;
|
|
30
|
+
export declare function normalizeOpName(op: unknown): unknown;
|
|
31
|
+
export declare function pageIdFromSlug(slug: string): string;
|
|
32
|
+
export declare function pageTitleFromSlug(slug: string): string;
|
|
33
|
+
export declare function inferBlockTypeFromText(text: string): BlockType | undefined;
|
|
34
|
+
export declare function nextBlockId(type: BlockType, page: PageDoc): string;
|
|
35
|
+
export declare function defaultPropsForType(type: BlockType): Record<string, unknown>;
|
|
36
|
+
export declare function patchObject(rawPatch: unknown): Record<string, unknown> | null;
|
|
37
|
+
export declare function normalizePlanCandidate(input: unknown, args?: {
|
|
38
|
+
defaultSlug?: string;
|
|
39
|
+
currentPage?: PageDoc;
|
|
40
|
+
userMessage?: string;
|
|
41
|
+
}): unknown;
|