@adia-ai/a2ui-retrieval 0.6.4 → 0.6.6
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/CHANGELOG.md +21 -0
- package/domain-router.js +362 -117
- package/embedding/chunk-embedding-retriever.js +47 -79
- package/embedding/chunk-embedding-retriever.ts +2 -2
- package/embedding/embedding-provider.js +35 -71
- package/embedding/index.js +2 -10
- package/feedback/dialog-recorder.js +61 -145
- package/feedback/dialog-recorder.ts +11 -8
- package/feedback/feedback-analyzer.js +46 -102
- package/feedback/feedback-analyzer.ts +2 -2
- package/feedback/feedback-store.js +91 -107
- package/feedback/feedback-store.ts +1 -1
- package/feedback/feedback.js +36 -117
- package/feedback/gap-registry.js +40 -82
- package/feedback/index.js +14 -12
- package/index.js +53 -16
- package/intent/clarity.js +61 -129
- package/intent/decomposer.js +51 -143
- package/intent/decomposer.ts +1 -1
- package/intent/index.js +18 -14
- package/intent/intent-alignment.js +79 -150
- package/intent/intent-alignment.ts +5 -5
- package/intent/intent-categorizer.js +34 -62
- package/intent/intent-gate.js +43 -102
- package/intent/prompt-analyzer.js +68 -126
- package/intent/prompt-analyzer.ts +1 -1
- package/package.json +1 -1
- package/wiring-catalog.js +95 -146
package/intent/clarity.js
CHANGED
|
@@ -1,196 +1,125 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
* to generate quality UI, or needs clarifying questions first.
|
|
4
|
-
*
|
|
5
|
-
* Scores intents across 5 dimensions:
|
|
6
|
-
* domain — Is the domain clear? (forms, data, layout, etc.)
|
|
7
|
-
* scope — Is the scope defined? (how many items, what sections?)
|
|
8
|
-
* content — Is the content specified? (labels, values, data?)
|
|
9
|
-
* layout — Is the layout preference stated? (grid, stack, sidebar?)
|
|
10
|
-
* action — Are actions/interactions mentioned? (buttons, forms, links?)
|
|
11
|
-
*
|
|
12
|
-
* Returns a clarity score (0-1) and targeted questions for what's missing.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import { classifyIntent } from '../domain-router.js';
|
|
16
|
-
import { searchAll as searchCompositions } from '../../compose/strategies/zettel/composition-library.js';
|
|
17
|
-
|
|
18
|
-
// ── Dimension detectors ──────────────────────────────────────────────────
|
|
19
|
-
|
|
20
|
-
/** Scope indicators: quantity, enumeration, specificity */
|
|
1
|
+
import { classifyIntent } from "../domain-router.js";
|
|
2
|
+
import { searchAll as searchCompositions } from "../../compose/strategies/zettel/composition-library.js";
|
|
21
3
|
const SCOPE_SIGNALS = [
|
|
22
|
-
/\b\d+\b/,
|
|
23
|
-
|
|
24
|
-
/\b(
|
|
25
|
-
|
|
26
|
-
/\b(
|
|
4
|
+
/\b\d+\b/,
|
|
5
|
+
// contains a number
|
|
6
|
+
/\b(three|two|four|five|six)\b/i,
|
|
7
|
+
// spelled-out numbers
|
|
8
|
+
/\b(with|containing|including|showing|displaying)\b/i,
|
|
9
|
+
// enumerates content
|
|
10
|
+
/\b(and|plus|also)\b/i,
|
|
11
|
+
// multiple items
|
|
12
|
+
/\b(columns?|rows?|items?|cards?|sections?|fields?|buttons?|tabs?|steps?)\b/i
|
|
13
|
+
// structural counts
|
|
27
14
|
];
|
|
28
|
-
|
|
29
|
-
/** Content specificity: labels, values, named data */
|
|
30
15
|
const CONTENT_SIGNALS = [
|
|
31
16
|
/\b(name|email|password|title|description|price|date|status|role|avatar)\b/i,
|
|
32
17
|
/\b(revenue|users|growth|sales|orders|metrics|analytics)\b/i,
|
|
33
18
|
/\b(todo|done|in progress|pending|active|completed)\b/i,
|
|
34
19
|
/\b(bleed|margin|trim|crop|preview|artwork|brand|design system)\b/i,
|
|
35
20
|
/\b(approved|approval|production|settings|configure|preference)\b/i,
|
|
36
|
-
/["'][\w\s]+["']/,
|
|
37
|
-
|
|
38
|
-
|
|
21
|
+
/["'][\w\s]+["']/,
|
|
22
|
+
// quoted strings (specific labels)
|
|
23
|
+
/\$[\d,.]+/,
|
|
24
|
+
// dollar amounts
|
|
25
|
+
/\d+%/
|
|
26
|
+
// percentages
|
|
39
27
|
];
|
|
40
|
-
|
|
41
|
-
/** Layout preferences */
|
|
42
28
|
const LAYOUT_SIGNALS = [
|
|
43
29
|
/\b(grid|row|column|sidebar|split|horizontal|vertical|stack|centered)\b/i,
|
|
44
30
|
/\b(full.?width|responsive|mobile|compact|wide|narrow)\b/i,
|
|
45
|
-
/\b(\d+.?col(umn)?s?)\b/i,
|
|
46
|
-
|
|
31
|
+
/\b(\d+.?col(umn)?s?)\b/i,
|
|
32
|
+
// "3 columns", "2-col"
|
|
33
|
+
/\b(left|right|top|bottom|center)\b/i
|
|
47
34
|
];
|
|
48
|
-
|
|
49
|
-
/** Action/interaction indicators */
|
|
50
35
|
const ACTION_SIGNALS = [
|
|
51
36
|
/\b(button|submit|cancel|save|delete|edit|close|open|toggle|click)\b/i,
|
|
52
37
|
/\b(form|input|select|search|filter|sort|upload|download)\b/i,
|
|
53
38
|
/\b(login|signup|register|checkout|confirm|approve|reject)\b/i,
|
|
54
39
|
/\b(navigate|link|redirect|route)\b/i,
|
|
55
|
-
/\b(drag|drop|resize|expand|collapse)\b/i
|
|
40
|
+
/\b(drag|drop|resize|expand|collapse)\b/i
|
|
56
41
|
];
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
* Assess clarity of a user intent for UI generation.
|
|
60
|
-
*
|
|
61
|
-
* @param {string} intent — Raw user input
|
|
62
|
-
* @param {{ domain: string, confidence: number, matchedSignals: string[] }} [classification] — Pre-computed classification
|
|
63
|
-
* @returns {{
|
|
64
|
-
* clear: boolean,
|
|
65
|
-
* score: number,
|
|
66
|
-
* dimensions: { domain: number, scope: number, content: number, layout: number, action: number },
|
|
67
|
-
* questions: { text: string, dimension: string, priority: number }[],
|
|
68
|
-
* summary: string,
|
|
69
|
-
* }}
|
|
70
|
-
*/
|
|
71
|
-
export function assessClarity(intent, classification) {
|
|
72
|
-
const text = (intent ?? '').trim();
|
|
42
|
+
function assessClarity(intent, classification) {
|
|
43
|
+
const text = (intent ?? "").trim();
|
|
73
44
|
if (!text) {
|
|
74
45
|
return {
|
|
75
46
|
clear: false,
|
|
76
47
|
score: 0,
|
|
77
48
|
dimensions: { domain: 0, scope: 0, content: 0, layout: 0, action: 0 },
|
|
78
|
-
questions: [{ text:
|
|
79
|
-
summary:
|
|
49
|
+
questions: [{ text: "What would you like me to build?", dimension: "domain", priority: 1 }],
|
|
50
|
+
summary: "No intent provided"
|
|
80
51
|
};
|
|
81
52
|
}
|
|
82
|
-
|
|
83
53
|
const cls = classification || classifyIntent(text);
|
|
84
54
|
const compositionMatches = searchCompositions(text);
|
|
85
|
-
const lower = text.toLowerCase();
|
|
86
55
|
const wordCount = text.split(/\s+/).length;
|
|
87
|
-
|
|
88
|
-
// ── Score each dimension ──
|
|
89
|
-
|
|
90
|
-
// Domain: how confident is the domain classification?
|
|
91
56
|
const domainScore = cls.confidence >= 0.3 ? 1 : cls.confidence >= 0.15 ? 0.6 : cls.matchedSignals.length > 0 ? 0.3 : 0;
|
|
92
|
-
|
|
93
|
-
// Scope: is the scope specific?
|
|
94
|
-
const scopeHits = SCOPE_SIGNALS.filter(r => r.test(text)).length;
|
|
57
|
+
const scopeHits = SCOPE_SIGNALS.filter((r) => r.test(text)).length;
|
|
95
58
|
const scopeScore = Math.min(1, scopeHits / 2);
|
|
96
|
-
|
|
97
|
-
// Content: are concrete labels/values mentioned?
|
|
98
|
-
const contentHits = CONTENT_SIGNALS.filter(r => r.test(text)).length;
|
|
59
|
+
const contentHits = CONTENT_SIGNALS.filter((r) => r.test(text)).length;
|
|
99
60
|
const contentScore = Math.min(1, contentHits / 2);
|
|
100
|
-
|
|
101
|
-
// Layout: is a layout preference stated?
|
|
102
|
-
const layoutHits = LAYOUT_SIGNALS.filter(r => r.test(text)).length;
|
|
61
|
+
const layoutHits = LAYOUT_SIGNALS.filter((r) => r.test(text)).length;
|
|
103
62
|
const layoutScore = Math.min(1, layoutHits);
|
|
104
|
-
|
|
105
|
-
// Action: are interactions mentioned?
|
|
106
|
-
const actionHits = ACTION_SIGNALS.filter(r => r.test(text)).length;
|
|
63
|
+
const actionHits = ACTION_SIGNALS.filter((r) => r.test(text)).length;
|
|
107
64
|
const actionScore = Math.min(1, actionHits / 2);
|
|
108
|
-
|
|
109
|
-
// ── Overall score (weighted) ──
|
|
110
|
-
const score = (
|
|
111
|
-
domainScore * 0.25 +
|
|
112
|
-
scopeScore * 0.25 +
|
|
113
|
-
contentScore * 0.20 +
|
|
114
|
-
layoutScore * 0.15 +
|
|
115
|
-
actionScore * 0.15
|
|
116
|
-
);
|
|
117
|
-
|
|
118
|
-
// ── Bonus: composition match adds confidence ──
|
|
65
|
+
const score = domainScore * 0.25 + scopeScore * 0.25 + contentScore * 0.2 + layoutScore * 0.15 + actionScore * 0.15;
|
|
119
66
|
const patternBonus = compositionMatches.length > 0 ? 0.15 : 0;
|
|
120
|
-
// Bonus: long intents are usually more specific
|
|
121
67
|
const lengthBonus = wordCount > 10 ? 0.1 : wordCount > 6 ? 0.05 : 0;
|
|
122
|
-
|
|
123
68
|
const finalScore = Math.min(1, score + patternBonus + lengthBonus);
|
|
124
|
-
|
|
125
|
-
// ── Generate targeted questions for weak dimensions ──
|
|
126
69
|
const questions = [];
|
|
127
|
-
|
|
128
70
|
if (domainScore < 0.3) {
|
|
129
71
|
questions.push({
|
|
130
|
-
text:
|
|
131
|
-
dimension:
|
|
132
|
-
priority: 1
|
|
72
|
+
text: "What type of UI is this? (e.g., a form, dashboard, profile card, settings page)",
|
|
73
|
+
dimension: "domain",
|
|
74
|
+
priority: 1
|
|
133
75
|
});
|
|
134
76
|
}
|
|
135
|
-
|
|
136
77
|
if (scopeScore < 0.5) {
|
|
137
78
|
const domainQuestions = {
|
|
138
|
-
forms:
|
|
139
|
-
data:
|
|
140
|
-
layout:
|
|
141
|
-
agent:
|
|
142
|
-
navigation:
|
|
79
|
+
forms: "What fields should the form include?",
|
|
80
|
+
data: "What data should be displayed? How many items or metrics?",
|
|
81
|
+
layout: "How many sections or cards should it have?",
|
|
82
|
+
agent: "What actions should the assistant support?",
|
|
83
|
+
navigation: "What pages or sections should be navigable?"
|
|
143
84
|
};
|
|
144
85
|
questions.push({
|
|
145
|
-
text: domainQuestions[cls.domain]
|
|
146
|
-
dimension:
|
|
147
|
-
priority: 2
|
|
86
|
+
text: domainQuestions[cls.domain] ?? "Can you be more specific about what it should contain?",
|
|
87
|
+
dimension: "scope",
|
|
88
|
+
priority: 2
|
|
148
89
|
});
|
|
149
90
|
}
|
|
150
|
-
|
|
151
91
|
if (contentScore < 0.3 && scopeScore >= 0.3) {
|
|
152
92
|
const contentQuestions = {
|
|
153
93
|
forms: 'What labels should the fields have? (e.g., "Email", "Password", "Full Name")',
|
|
154
|
-
data:
|
|
155
|
-
layout:
|
|
156
|
-
agent:
|
|
157
|
-
navigation:
|
|
94
|
+
data: "What metrics or values should be shown? (e.g., revenue, users, growth rate)",
|
|
95
|
+
layout: "What content goes in each section?",
|
|
96
|
+
agent: "What kind of messages or responses should be shown?",
|
|
97
|
+
navigation: "What should the menu items or links be labeled?"
|
|
158
98
|
};
|
|
159
99
|
questions.push({
|
|
160
|
-
text: contentQuestions[cls.domain]
|
|
161
|
-
dimension:
|
|
162
|
-
priority: 3
|
|
100
|
+
text: contentQuestions[cls.domain] ?? "What specific content should be displayed?",
|
|
101
|
+
dimension: "content",
|
|
102
|
+
priority: 3
|
|
163
103
|
});
|
|
164
104
|
}
|
|
165
|
-
|
|
166
105
|
if (layoutScore < 0.3 && scopeScore >= 0.5) {
|
|
167
106
|
questions.push({
|
|
168
|
-
text:
|
|
169
|
-
dimension:
|
|
170
|
-
priority: 4
|
|
107
|
+
text: "Any layout preference? (e.g., grid of cards, single column, sidebar + content)",
|
|
108
|
+
dimension: "layout",
|
|
109
|
+
priority: 4
|
|
171
110
|
});
|
|
172
111
|
}
|
|
173
|
-
|
|
174
112
|
if (actionScore < 0.3 && domainScore >= 0.3) {
|
|
175
113
|
questions.push({
|
|
176
|
-
text:
|
|
177
|
-
dimension:
|
|
178
|
-
priority: 5
|
|
114
|
+
text: "What actions should users be able to take? (e.g., submit, edit, delete, filter)",
|
|
115
|
+
dimension: "action",
|
|
116
|
+
priority: 5
|
|
179
117
|
});
|
|
180
118
|
}
|
|
181
|
-
|
|
182
|
-
// Sort by priority, limit to 3
|
|
183
119
|
questions.sort((a, b) => a.priority - b.priority);
|
|
184
120
|
const topQuestions = questions.slice(0, 3);
|
|
185
|
-
|
|
186
|
-
// ── Determine if clear enough to proceed ──
|
|
187
|
-
// Clear only if dimensional score meets threshold
|
|
188
121
|
const clear = finalScore >= 0.4;
|
|
189
|
-
|
|
190
|
-
const summary = clear
|
|
191
|
-
? `Intent is ${finalScore >= 0.7 ? 'clear' : 'adequate'} (${Math.round(finalScore * 100)}%)`
|
|
192
|
-
: `Intent needs clarification (${Math.round(finalScore * 100)}% — below 40% threshold)`;
|
|
193
|
-
|
|
122
|
+
const summary = clear ? `Intent is ${finalScore >= 0.7 ? "clear" : "adequate"} (${Math.round(finalScore * 100)}%)` : `Intent needs clarification (${Math.round(finalScore * 100)}% \u2014 below 40% threshold)`;
|
|
194
123
|
return {
|
|
195
124
|
clear,
|
|
196
125
|
score: Math.round(finalScore * 100) / 100,
|
|
@@ -199,9 +128,12 @@ export function assessClarity(intent, classification) {
|
|
|
199
128
|
scope: Math.round(scopeScore * 100) / 100,
|
|
200
129
|
content: Math.round(contentScore * 100) / 100,
|
|
201
130
|
layout: Math.round(layoutScore * 100) / 100,
|
|
202
|
-
action: Math.round(actionScore * 100) / 100
|
|
131
|
+
action: Math.round(actionScore * 100) / 100
|
|
203
132
|
},
|
|
204
133
|
questions: topQuestions,
|
|
205
|
-
summary
|
|
134
|
+
summary
|
|
206
135
|
};
|
|
207
136
|
}
|
|
137
|
+
export {
|
|
138
|
+
assessClarity
|
|
139
|
+
};
|
package/intent/decomposer.js
CHANGED
|
@@ -1,216 +1,124 @@
|
|
|
1
|
-
|
|
2
|
-
* Intent Decomposer — breaks complex intents into independent subtasks.
|
|
3
|
-
*
|
|
4
|
-
* When an intent describes multiple sections, areas, or features,
|
|
5
|
-
* the decomposer splits it into atomic generation units that can be
|
|
6
|
-
* generated independently and composed into a layout.
|
|
7
|
-
*
|
|
8
|
-
* Example:
|
|
9
|
-
* "settings page with profile, notifications, security, and billing"
|
|
10
|
-
* → 4 subtasks + a composition plan (Tabs layout)
|
|
11
|
-
*
|
|
12
|
-
* The decomposition is the single most important capability per the
|
|
13
|
-
* Software Factory Manifesto: "Get decomposition right and generation
|
|
14
|
-
* is almost trivial."
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import { classifyIntent } from '../domain-router.js';
|
|
18
|
-
|
|
19
|
-
// ── Section Detection ────────────────────────────────────────────────────
|
|
20
|
-
|
|
21
|
-
/** Connectors that separate sections in an intent */
|
|
1
|
+
import { classifyIntent } from "../domain-router.js";
|
|
22
2
|
const SECTION_SPLITTERS = /\band\b|\bwith\b|\bplus\b|\balso\b|,\s*(?:and\s+)?/gi;
|
|
23
|
-
|
|
24
|
-
/** Words that signal enumerated sections */
|
|
25
3
|
const SECTION_SIGNALS = [
|
|
26
4
|
/\b(\d+)\s+(sections?|areas?|parts?|panels?|columns?|cards?|tabs?|pages?|views?)\b/i,
|
|
27
5
|
/\bsections?:\s/i,
|
|
28
6
|
/\bincluding\b/i,
|
|
29
|
-
/\beach\s+(with|having|containing)\b/i
|
|
7
|
+
/\beach\s+(with|having|containing)\b/i
|
|
30
8
|
];
|
|
31
|
-
|
|
32
|
-
/** Layout containers that imply multi-section structure */
|
|
33
9
|
const LAYOUT_KEYWORDS = {
|
|
34
|
-
tabs: { component:
|
|
35
|
-
sections: { component:
|
|
36
|
-
columns: { component:
|
|
37
|
-
cards: { component:
|
|
38
|
-
panels: { component:
|
|
39
|
-
pages: { component:
|
|
40
|
-
areas: { component:
|
|
41
|
-
steps: { component:
|
|
10
|
+
tabs: { component: "Tabs", child: "Tab" },
|
|
11
|
+
sections: { component: "Column", child: "Card" },
|
|
12
|
+
columns: { component: "Grid", child: "Column" },
|
|
13
|
+
cards: { component: "Grid", child: "Card" },
|
|
14
|
+
panels: { component: "Accordion", child: "Panel" },
|
|
15
|
+
pages: { component: "Tabs", child: "Tab" },
|
|
16
|
+
areas: { component: "Grid", child: "Card" },
|
|
17
|
+
steps: { component: "Steps", child: "Step" }
|
|
42
18
|
};
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
* Analyze an intent for decomposition potential.
|
|
46
|
-
*
|
|
47
|
-
* @param {string} intent
|
|
48
|
-
* @returns {{ shouldDecompose: boolean, subtasks: { intent: string, label: string }[], layout: { component: string, child: string } | null, original: string }}
|
|
49
|
-
*/
|
|
50
|
-
export function decomposeIntent(intent) {
|
|
51
|
-
const trimmed = (intent || '').trim();
|
|
19
|
+
function decomposeIntent(intent) {
|
|
20
|
+
const trimmed = (intent || "").trim();
|
|
52
21
|
if (!trimmed) return { shouldDecompose: false, subtasks: [], layout: null, original: trimmed };
|
|
53
|
-
|
|
54
|
-
// ── Detect explicit section count ──
|
|
55
22
|
for (const pattern of SECTION_SIGNALS) {
|
|
56
23
|
if (pattern.test(trimmed)) {
|
|
57
|
-
const
|
|
58
|
-
if (
|
|
59
|
-
const layout = detectLayout(trimmed,
|
|
24
|
+
const sections2 = extractSections(trimmed);
|
|
25
|
+
if (sections2.length >= 2) {
|
|
26
|
+
const layout = detectLayout(trimmed, sections2.length);
|
|
60
27
|
return {
|
|
61
28
|
shouldDecompose: true,
|
|
62
|
-
subtasks:
|
|
29
|
+
subtasks: sections2,
|
|
63
30
|
layout,
|
|
64
|
-
original: trimmed
|
|
31
|
+
original: trimmed
|
|
65
32
|
};
|
|
66
33
|
}
|
|
67
34
|
}
|
|
68
35
|
}
|
|
69
|
-
|
|
70
|
-
// ── Detect enumerated items ──
|
|
71
36
|
const sections = extractSections(trimmed);
|
|
72
37
|
if (sections.length >= 2) {
|
|
73
|
-
// 2+ distinct sections → decompose
|
|
74
38
|
const layout = detectLayout(trimmed, sections.length);
|
|
75
39
|
return {
|
|
76
40
|
shouldDecompose: true,
|
|
77
41
|
subtasks: sections,
|
|
78
42
|
layout,
|
|
79
|
-
original: trimmed
|
|
43
|
+
original: trimmed
|
|
80
44
|
};
|
|
81
45
|
}
|
|
82
|
-
|
|
83
|
-
// ── Short or simple intent → don't decompose ──
|
|
84
46
|
return { shouldDecompose: false, subtasks: [], layout: null, original: trimmed };
|
|
85
47
|
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Extract individual section descriptions from an intent.
|
|
89
|
-
*
|
|
90
|
-
* @param {string} intent
|
|
91
|
-
* @returns {{ intent: string, label: string }[]}
|
|
92
|
-
*/
|
|
93
48
|
function extractSections(intent) {
|
|
94
|
-
// Find the "with X, Y, Z, and W" pattern
|
|
95
49
|
const withMatch = intent.match(/\bwith\s+(.+)$/i);
|
|
96
|
-
const payload = withMatch ? withMatch[1] : intent;
|
|
97
|
-
|
|
98
|
-
// Split on connectors
|
|
99
|
-
const parts = payload.split(SECTION_SPLITTERS)
|
|
100
|
-
.map(s => s.trim())
|
|
101
|
-
.filter(s => s.length > 2);
|
|
102
|
-
|
|
50
|
+
const payload = withMatch ? withMatch[1] ?? intent : intent;
|
|
51
|
+
const parts = payload.split(SECTION_SPLITTERS).map((s) => s.trim()).filter((s) => s.length > 2);
|
|
103
52
|
if (parts.length < 2) return [];
|
|
104
|
-
|
|
105
|
-
// Extract the base context (everything before "with")
|
|
106
|
-
const baseContext = withMatch ? intent.slice(0, withMatch.index).trim() : '';
|
|
53
|
+
const baseContext = withMatch ? intent.slice(0, withMatch.index).trim() : "";
|
|
107
54
|
const domain = classifyIntent(intent).domain;
|
|
108
|
-
|
|
109
|
-
return parts.map(part => {
|
|
110
|
-
|
|
111
|
-
const
|
|
112
|
-
const subtaskIntent = baseContext
|
|
113
|
-
? `${label} section for a ${baseContext}`
|
|
114
|
-
: `${label}`;
|
|
115
|
-
|
|
55
|
+
void domain;
|
|
56
|
+
return parts.map((part) => {
|
|
57
|
+
const label = part.replace(/\b(section|area|panel|tab|page)\b/gi, "").trim();
|
|
58
|
+
const subtaskIntent = baseContext ? `${label} section for a ${baseContext}` : `${label}`;
|
|
116
59
|
return { intent: subtaskIntent, label: capitalizeFirst(label) };
|
|
117
60
|
});
|
|
118
61
|
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Detect the best layout container for the decomposed sections.
|
|
122
|
-
*
|
|
123
|
-
* @param {string} intent
|
|
124
|
-
* @param {number} sectionCount
|
|
125
|
-
* @returns {{ component: string, child: string }}
|
|
126
|
-
*/
|
|
127
62
|
function detectLayout(intent, sectionCount) {
|
|
128
63
|
const lower = intent.toLowerCase();
|
|
129
|
-
|
|
130
|
-
// Explicit layout keywords in the intent
|
|
131
64
|
for (const [keyword, layout] of Object.entries(LAYOUT_KEYWORDS)) {
|
|
132
65
|
if (lower.includes(keyword)) return layout;
|
|
133
66
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
if (lower.includes('settings') || lower.includes('preferences') || lower.includes('account')) {
|
|
137
|
-
return { component: 'Tabs', child: 'Tab' };
|
|
67
|
+
if (lower.includes("settings") || lower.includes("preferences") || lower.includes("account")) {
|
|
68
|
+
return { component: "Tabs", child: "Tab" };
|
|
138
69
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
if (lower.includes('dashboard') || lower.includes('overview') || lower.includes('stat')) {
|
|
142
|
-
return { component: 'Grid', child: 'Card' };
|
|
70
|
+
if (lower.includes("dashboard") || lower.includes("overview") || lower.includes("stat")) {
|
|
71
|
+
return { component: "Grid", child: "Card" };
|
|
143
72
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
if (sectionCount <= 4) return { component: 'Tabs', child: 'Tab' };
|
|
147
|
-
return { component: 'Grid', child: 'Card' };
|
|
73
|
+
if (sectionCount <= 4) return { component: "Tabs", child: "Tab" };
|
|
74
|
+
return { component: "Grid", child: "Card" };
|
|
148
75
|
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Compose independently generated subtask results into a layout.
|
|
152
|
-
*
|
|
153
|
-
* @param {{ component: string, child: string }} layout
|
|
154
|
-
* @param {{ label: string, messages: object[] }[]} subtaskResults
|
|
155
|
-
* @returns {object[]} — A2UI messages for the composed layout
|
|
156
|
-
*/
|
|
157
|
-
export function composeSubtasks(layout, subtaskResults) {
|
|
76
|
+
function composeSubtasks(layout, subtaskResults) {
|
|
158
77
|
const rootChildren = [];
|
|
159
78
|
const allComponents = [];
|
|
160
79
|
let idCounter = 0;
|
|
161
|
-
|
|
162
80
|
for (const { label, messages } of subtaskResults) {
|
|
163
81
|
const prefix = `s${++idCounter}`;
|
|
164
|
-
const subtaskComponents = messages?.[0]?.components
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
const remapped = subtaskComponents.map(c => {
|
|
169
|
-
const newId = c.id === 'root' ? prefix : `${prefix}-${c.id}`;
|
|
82
|
+
const subtaskComponents = messages?.[0]?.components ?? [];
|
|
83
|
+
const idMap = /* @__PURE__ */ new Map();
|
|
84
|
+
const remapped = subtaskComponents.map((c) => {
|
|
85
|
+
const newId = c.id === "root" ? prefix : `${prefix}-${c.id}`;
|
|
170
86
|
idMap.set(c.id, newId);
|
|
171
87
|
return { ...c, id: newId };
|
|
172
88
|
});
|
|
173
|
-
|
|
174
|
-
// Fix child references
|
|
175
89
|
for (const c of remapped) {
|
|
176
90
|
if (c.children) {
|
|
177
|
-
c.children = c.children.map(id => idMap.get(id)
|
|
91
|
+
c.children = c.children.map((id) => idMap.get(id) ?? id);
|
|
178
92
|
}
|
|
179
93
|
}
|
|
180
|
-
|
|
181
|
-
// Create the layout wrapper for this subtask
|
|
182
|
-
if (layout.child === 'Tab') {
|
|
183
|
-
// Tabs: wrap in a Tab with a label
|
|
94
|
+
if (layout.child === "Tab") {
|
|
184
95
|
const tabId = `${prefix}-tab`;
|
|
185
|
-
allComponents.push({ id: tabId, component:
|
|
96
|
+
allComponents.push({ id: tabId, component: "Tab", text: label, children: [prefix] });
|
|
186
97
|
rootChildren.push(tabId);
|
|
187
98
|
} else {
|
|
188
|
-
// Grid/Column: subtask root becomes a direct child
|
|
189
99
|
rootChildren.push(prefix);
|
|
190
100
|
}
|
|
191
|
-
|
|
192
101
|
allComponents.push(...remapped);
|
|
193
102
|
}
|
|
194
|
-
|
|
195
|
-
// Build the root layout
|
|
196
103
|
const root = {
|
|
197
|
-
id:
|
|
104
|
+
id: "root",
|
|
198
105
|
component: layout.component,
|
|
199
|
-
children: rootChildren
|
|
106
|
+
children: rootChildren
|
|
200
107
|
};
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
root
|
|
204
|
-
root.gap = 'md';
|
|
108
|
+
if (layout.component === "Grid") {
|
|
109
|
+
root["columns"] = String(Math.min(subtaskResults.length, 4));
|
|
110
|
+
root["gap"] = "md";
|
|
205
111
|
}
|
|
206
|
-
|
|
207
112
|
return [{
|
|
208
|
-
type:
|
|
209
|
-
surfaceId:
|
|
210
|
-
components: [root, ...allComponents]
|
|
113
|
+
type: "updateComponents",
|
|
114
|
+
surfaceId: "default",
|
|
115
|
+
components: [root, ...allComponents]
|
|
211
116
|
}];
|
|
212
117
|
}
|
|
213
|
-
|
|
214
118
|
function capitalizeFirst(str) {
|
|
215
119
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
216
120
|
}
|
|
121
|
+
export {
|
|
122
|
+
composeSubtasks,
|
|
123
|
+
decomposeIntent
|
|
124
|
+
};
|
package/intent/decomposer.ts
CHANGED
|
@@ -97,7 +97,7 @@ export function decomposeIntent(intent: string): DecomposeResult {
|
|
|
97
97
|
function extractSections(intent: string): Subtask[] {
|
|
98
98
|
// Find the "with X, Y, Z, and W" pattern
|
|
99
99
|
const withMatch = intent.match(/\bwith\s+(.+)$/i);
|
|
100
|
-
const payload = withMatch ? withMatch[1] : intent;
|
|
100
|
+
const payload = withMatch ? (withMatch[1] ?? intent) : intent;
|
|
101
101
|
|
|
102
102
|
// Split on connectors
|
|
103
103
|
const parts = payload.split(SECTION_SPLITTERS)
|
package/intent/index.js
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
1
|
+
import { extractExpectations, verifyAlignment, checkIntentAlignment } from "./intent-alignment.js";
|
|
2
|
+
import { categorizeIntent } from "./intent-categorizer.js";
|
|
3
|
+
import { isConversational } from "./intent-gate.js";
|
|
4
|
+
import { analyzePrompt, formatAnalysisForPrompt } from "./prompt-analyzer.js";
|
|
5
|
+
import { decomposeIntent, composeSubtasks } from "./decomposer.js";
|
|
6
|
+
import { assessClarity } from "./clarity.js";
|
|
7
|
+
export {
|
|
8
|
+
analyzePrompt,
|
|
9
|
+
assessClarity,
|
|
10
|
+
categorizeIntent,
|
|
11
|
+
checkIntentAlignment,
|
|
12
|
+
composeSubtasks,
|
|
13
|
+
decomposeIntent,
|
|
14
|
+
extractExpectations,
|
|
15
|
+
formatAnalysisForPrompt,
|
|
16
|
+
isConversational,
|
|
17
|
+
verifyAlignment
|
|
18
|
+
};
|