@adia-ai/a2ui-retrieval 0.6.4 → 0.6.7
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 +25 -0
- package/domain-router.js +362 -117
- package/embedding/chunk-embedding-retriever.js +47 -79
- package/embedding/embedding-provider.js +35 -71
- package/embedding/index.js +2 -10
- package/feedback/dialog-recorder.js +61 -145
- package/feedback/feedback-analyzer.js +46 -102
- package/feedback/feedback-store.js +91 -107
- package/feedback/feedback.js +36 -117
- package/feedback/gap-registry.js +40 -82
- package/feedback/index.js +14 -12
- package/index.d.ts +4 -0
- package/index.js +53 -16
- package/intent/clarity.js +61 -129
- package/intent/decomposer.js +51 -143
- package/intent/index.js +18 -14
- package/intent/intent-alignment.js +79 -150
- package/intent/intent-categorizer.js +34 -62
- package/intent/intent-gate.js +43 -102
- package/intent/prompt-analyzer.js +68 -126
- package/package.json +4 -2
- package/wiring-catalog.js +95 -146
- package/embedding/chunk-embedding-retriever.ts +0 -156
- package/embedding/embedding-provider.ts +0 -111
- package/embedding/index.ts +0 -10
- package/feedback/dialog-recorder.ts +0 -172
- package/feedback/feedback-analyzer.ts +0 -250
- package/feedback/feedback-store.ts +0 -229
- package/feedback/feedback.ts +0 -201
- package/feedback/gap-registry.ts +0 -137
- package/feedback/index.ts +0 -14
- package/intent/clarity.ts +0 -224
- package/intent/decomposer.ts +0 -229
- package/intent/index.ts +0 -20
- package/intent/intent-alignment.ts +0 -267
- package/intent/intent-categorizer.ts +0 -104
- package/intent/intent-gate.ts +0 -151
- package/intent/prompt-analyzer.ts +0 -231
package/intent/intent-gate.js
CHANGED
|
@@ -1,18 +1,5 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Intent Gate — Detect whether a user message is conversational (question,
|
|
3
|
-
* greeting, meta-question) vs. a UI generation request.
|
|
4
|
-
*
|
|
5
|
-
* Used by the chat setup to route non-generation messages to a text reply
|
|
6
|
-
* instead of the full 6-stage pipeline.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
/** Question starters that signal conversational intent */
|
|
10
1
|
const QUESTION_STARTERS = /^\s*(what|how|why|can you|could you|tell me|explain|describe|help|who|where|when|is there|are there|do you|does|did|will|should|would|which|list|show me how)\b/i;
|
|
11
|
-
|
|
12
|
-
/** Greetings and social signals */
|
|
13
2
|
const GREETINGS = /^\s*(hi|hey|hello|thanks|thank you|bye|goodbye|ok|okay|sure|great|nice|cool|awesome|good|perfect)\b/i;
|
|
14
|
-
|
|
15
|
-
/** Explicit meta-questions about the system itself */
|
|
16
3
|
const META_PATTERNS = [
|
|
17
4
|
/what\s+(components?|types?|elements?)\s+(are|do|can)/i,
|
|
18
5
|
/available\s+(components?|types?|elements?)/i,
|
|
@@ -25,131 +12,85 @@ const META_PATTERNS = [
|
|
|
25
12
|
/explain\s+(the|how|what|a)/i,
|
|
26
13
|
/how\s+(to|do\s+I)\s+(use|create|build|make)/i,
|
|
27
14
|
/what\s+would\s+.+\s+look\s+like/i,
|
|
28
|
-
/show\s+me\s+how/i
|
|
15
|
+
/show\s+me\s+how/i
|
|
29
16
|
];
|
|
30
|
-
|
|
31
|
-
/** Content that is clearly not a UI intent (injection, noise, gibberish) */
|
|
32
17
|
const NOISE_PATTERNS = [
|
|
33
|
-
/^<[^>]+>/,
|
|
34
|
-
|
|
18
|
+
/^<[^>]+>/,
|
|
19
|
+
// Starts with HTML tag
|
|
20
|
+
/^\s*[^\w\s]{3,}/
|
|
21
|
+
// Starts with 3+ non-word chars
|
|
35
22
|
];
|
|
36
|
-
|
|
37
|
-
/** Vague, motivational, or abstract phrases with no UI-describing language.
|
|
38
|
-
* These are common conversational inputs that the LLM hallucinates UIs from. */
|
|
39
23
|
const VAGUE_PATTERNS = [
|
|
40
|
-
/^(do|just|go|let'?s|get|keep|stay|stop|try|move|make it|finish|start|begin|end|run)\b.{0,30}$/i,
|
|
24
|
+
/^(do|just|go|let'?s|get|keep|stay|stop|try|move|make it|finish|start|begin|end|run)\b.{0,30}$/i,
|
|
25
|
+
// short imperative without UI nouns
|
|
41
26
|
/\b(you gotta|gotta do|no excuses|believe|motivat|inspir|goal|dream|life|love|hate|feel|think about)\b/i,
|
|
42
|
-
/\b(what is preventing|what stops|why can't|why won't)\b/i,
|
|
43
|
-
|
|
27
|
+
/\b(what is preventing|what stops|why can't|why won't)\b/i,
|
|
28
|
+
// philosophical/motivational questions
|
|
29
|
+
/\b(do it|get it done|let'?s go|make it happen|just do it|never give up)\b/i
|
|
30
|
+
// motivational slogans
|
|
44
31
|
];
|
|
45
|
-
|
|
46
|
-
/** Tool-use and action-oriented intents that should be handled conversationally,
|
|
47
|
-
* not as UI generation requests. These are commands directed at the system. */
|
|
48
32
|
const TOOL_USE_PATTERNS = [
|
|
49
33
|
/\b(create|file|write|open|submit|log)\s+(a\s+)?(ticket|bug|issue|report|feature request)/i,
|
|
50
34
|
/\b(report|flag)\s+(a\s+)?(bug|issue|problem|error|regression)/i,
|
|
51
|
-
/\b(why|how come)\s+(does|did|is|was|are)\b/i,
|
|
35
|
+
/\b(why|how come)\s+(does|did|is|was|are)\b/i,
|
|
36
|
+
// "why does X do Y" — asking for rationale
|
|
52
37
|
/\bthis\s+(is|looks?)\s+(broken|wrong|buggy|off|weird|strange)/i,
|
|
53
38
|
/\b(fix|repair|debug|investigate)\s+(this|the|that)/i,
|
|
54
39
|
/\bsomething\s+(is|seems|looks?)\s+(wrong|broken|off)/i,
|
|
55
40
|
/\bmark\s+(this|it)\s+as\b/i,
|
|
56
|
-
/\b(save|bookmark|pin)\s+(this|the)\s+(pattern|output|result)/i
|
|
41
|
+
/\b(save|bookmark|pin)\s+(this|the)\s+(pattern|output|result)/i
|
|
57
42
|
];
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
* @param {string} text — Raw user input
|
|
64
|
-
* @param {{ confidence: number, matchedSignals: string[] }} classifyResult — From classifyIntent()
|
|
65
|
-
* @returns {{ conversational: boolean, reason: string }}
|
|
66
|
-
*/
|
|
67
|
-
export function isConversational(text, classifyResult) {
|
|
68
|
-
const trimmed = (text ?? '').trim();
|
|
69
|
-
if (!classifyResult || typeof classifyResult !== 'object') {
|
|
70
|
-
classifyResult = { confidence: 0, matchedSignals: [] };
|
|
43
|
+
function isConversational(text, classifyResult) {
|
|
44
|
+
const trimmed = (text ?? "").trim();
|
|
45
|
+
let cls = classifyResult && typeof classifyResult === "object" ? classifyResult : { confidence: 0, matchedSignals: [] };
|
|
46
|
+
if (trimmed.length < 5 && cls.confidence === 0) {
|
|
47
|
+
return { conversational: true, reason: "short-input" };
|
|
71
48
|
}
|
|
72
|
-
|
|
73
|
-
// Very short messages without generation keywords are likely conversational
|
|
74
|
-
if (trimmed.length < 5 && classifyResult.confidence === 0) {
|
|
75
|
-
return { conversational: true, reason: 'short-input' };
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// Noise/injection detection — not a real intent
|
|
79
49
|
for (const pattern of NOISE_PATTERNS) {
|
|
80
|
-
if (pattern.test(trimmed) &&
|
|
81
|
-
return { conversational: true, reason:
|
|
50
|
+
if (pattern.test(trimmed) && cls.confidence === 0) {
|
|
51
|
+
return { conversational: true, reason: "noise-input" };
|
|
82
52
|
}
|
|
83
53
|
}
|
|
84
|
-
|
|
85
|
-
// Explicit meta-questions about the system → always conversational
|
|
86
54
|
for (const pattern of META_PATTERNS) {
|
|
87
55
|
if (pattern.test(trimmed)) {
|
|
88
|
-
return { conversational: true, reason:
|
|
56
|
+
return { conversational: true, reason: "meta-question" };
|
|
89
57
|
}
|
|
90
58
|
}
|
|
91
|
-
|
|
92
|
-
// Tool-use commands (create ticket, report bug, etc.) → conversational
|
|
93
59
|
for (const pattern of TOOL_USE_PATTERNS) {
|
|
94
60
|
if (pattern.test(trimmed)) {
|
|
95
|
-
return { conversational: true, reason:
|
|
61
|
+
return { conversational: true, reason: "tool-use" };
|
|
96
62
|
}
|
|
97
63
|
}
|
|
98
|
-
|
|
99
|
-
// Vague / motivational / abstract input with no or weak domain signals
|
|
100
|
-
// Catches "do what you gotta do", "just do it", "what is preventing you?", etc.
|
|
101
|
-
// These have zero genuine UI intent but the LLM hallucinates full UIs from them.
|
|
102
|
-
if (classifyResult.confidence < 0.2) {
|
|
64
|
+
if (cls.confidence < 0.2) {
|
|
103
65
|
for (const pattern of VAGUE_PATTERNS) {
|
|
104
66
|
if (pattern.test(trimmed)) {
|
|
105
|
-
return { conversational: true, reason:
|
|
67
|
+
return { conversational: true, reason: "vague-or-abstract" };
|
|
106
68
|
}
|
|
107
69
|
}
|
|
108
70
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
if (GREETINGS.test(trimmed) && classifyResult.confidence === 0) {
|
|
112
|
-
return { conversational: true, reason: 'greeting' };
|
|
71
|
+
if (GREETINGS.test(trimmed) && cls.confidence === 0) {
|
|
72
|
+
return { conversational: true, reason: "greeting" };
|
|
113
73
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
if (classifyResult.confidence >= 0.3) {
|
|
117
|
-
return { conversational: false, reason: 'high-domain-confidence' };
|
|
74
|
+
if (cls.confidence >= 0.3) {
|
|
75
|
+
return { conversational: false, reason: "high-domain-confidence" };
|
|
118
76
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
if (classifyResult.matchedSignals?.length >= 2) {
|
|
122
|
-
return { conversational: false, reason: 'multiple-domain-signals' };
|
|
77
|
+
if ((cls.matchedSignals?.length ?? 0) >= 2) {
|
|
78
|
+
return { conversational: false, reason: "multiple-domain-signals" };
|
|
123
79
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
// Key insight: "explain the card content model" has 1 signal ("card") but
|
|
127
|
-
// the leading "explain" means the user wants information, not generation.
|
|
128
|
-
if (QUESTION_STARTERS.test(trimmed) && classifyResult.matchedSignals?.length <= 1) {
|
|
129
|
-
return { conversational: true, reason: 'question-single-signal' };
|
|
80
|
+
if (QUESTION_STARTERS.test(trimmed) && (cls.matchedSignals?.length ?? 0) <= 1) {
|
|
81
|
+
return { conversational: true, reason: "question-single-signal" };
|
|
130
82
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
if (GREETINGS.test(trimmed) && classifyResult.confidence < 0.2) {
|
|
134
|
-
return { conversational: true, reason: 'greeting-low-confidence' };
|
|
83
|
+
if (GREETINGS.test(trimmed) && cls.confidence < 0.2) {
|
|
84
|
+
return { conversational: true, reason: "greeting-low-confidence" };
|
|
135
85
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
if (classifyResult.confidence > 0) {
|
|
139
|
-
return { conversational: false, reason: 'has-domain-signal' };
|
|
86
|
+
if (cls.confidence > 0) {
|
|
87
|
+
return { conversational: false, reason: "has-domain-signal" };
|
|
140
88
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
if (trimmed.endsWith('?')) {
|
|
144
|
-
return { conversational: true, reason: 'ends-with-question-mark' };
|
|
89
|
+
if (trimmed.endsWith("?")) {
|
|
90
|
+
return { conversational: true, reason: "ends-with-question-mark" };
|
|
145
91
|
}
|
|
146
|
-
|
|
147
|
-
// No domain signals at all — this input doesn't describe UI.
|
|
148
|
-
// Phrases like "do what you gotta do", "what is preventing you?", or
|
|
149
|
-
// motivational text have zero keyword overlap with any UI domain.
|
|
150
|
-
// Route to conversational so the system can ask for clarification
|
|
151
|
-
// instead of hallucinating a UI from abstract text.
|
|
152
|
-
// Legitimate UI intents like "sidebar with navigation" will have
|
|
153
|
-
// domain signals (sidebar → layout/navigation) and be caught above.
|
|
154
|
-
return { conversational: true, reason: 'no-ui-signals' };
|
|
92
|
+
return { conversational: true, reason: "no-ui-signals" };
|
|
155
93
|
}
|
|
94
|
+
export {
|
|
95
|
+
isConversational
|
|
96
|
+
};
|
|
@@ -1,91 +1,47 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Prompt Analyzer — baseline ingestion stage for the gen-ui pipeline.
|
|
3
|
-
*
|
|
4
|
-
* Every user intent enters through here, regardless of engine (monolithic,
|
|
5
|
-
* zettel) or mode (instant, pro, thinking). The analyzer extracts structured
|
|
6
|
-
* signals from the raw prompt — what concepts the user is gesturing at, which
|
|
7
|
-
* AdiaUI components are implied, what implicit requirements a good designer
|
|
8
|
-
* would add — and produces a "steelmanned" enriched brief that downstream
|
|
9
|
-
* stages reason against.
|
|
10
|
-
*
|
|
11
|
-
* Output schema:
|
|
12
|
-
* {
|
|
13
|
-
* raw: string // user's original intent, verbatim
|
|
14
|
-
* concepts: string[] // 2-5 short concept tags ("commerce", "auth", "data-display")
|
|
15
|
-
* entities: string[] // explicit nouns from the prompt ("image", "title", "price")
|
|
16
|
-
* impliedComponents: string[] // 4-12 AdiaUI component names ("Card", "Image", "Button")
|
|
17
|
-
* styleHints: string[] // visual/style hints ("3D", "minimal", "dense"), [] if none
|
|
18
|
-
* steelman: string // 1-2 sentence enriched brief
|
|
19
|
-
* analyzed: boolean // true if LLM call succeeded; false if fell back to passthrough
|
|
20
|
-
* }
|
|
21
|
-
*
|
|
22
|
-
* Failure mode: if the LLM call throws or returns unparseable JSON, we
|
|
23
|
-
* gracefully degrade to a passthrough analysis (steelman = raw intent, no
|
|
24
|
-
* extracted signals). Generation continues; quality may suffer but nothing
|
|
25
|
-
* breaks. See diagnosis report 2026-04-19 for why silent failure is the right
|
|
26
|
-
* default here (vs. raising) — the analyzer is enrichment, not a gate.
|
|
27
|
-
*
|
|
28
|
-
* Latency budget: one Haiku call, typically 800-1500ms. This is the new floor
|
|
29
|
-
* for every generation regardless of mode.
|
|
30
|
-
*/
|
|
31
|
-
|
|
32
|
-
// Component vocabulary derived from the catalog. Two artifacts:
|
|
33
|
-
// - displayList: human-readable list shown to the LLM, with aliases inlined
|
|
34
|
-
// ("Swiper (also: Carousel, Slideshow)") so the LLM knows it can use any.
|
|
35
|
-
// - allowedSet: canonical AND alias names accepted in impliedComponents,
|
|
36
|
-
// because the runtime registry already maps aliases to the right tag.
|
|
37
|
-
// Loaded once at module init from the catalog. Changes require server restart.
|
|
38
1
|
let _vocab = null;
|
|
39
2
|
async function getVocab() {
|
|
40
3
|
if (_vocab) return _vocab;
|
|
41
4
|
let catalogJson = {};
|
|
42
5
|
try {
|
|
43
|
-
const IS_NODE = typeof process !==
|
|
6
|
+
const IS_NODE = typeof process !== "undefined" && process.versions?.node;
|
|
44
7
|
if (IS_NODE) {
|
|
45
|
-
const fs = await import(
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
8
|
+
const fs = await import(
|
|
9
|
+
/* @vite-ignore */
|
|
10
|
+
"node:fs/promises"
|
|
11
|
+
);
|
|
12
|
+
const path = await import(
|
|
13
|
+
/* @vite-ignore */
|
|
14
|
+
"node:path"
|
|
15
|
+
);
|
|
16
|
+
const url = await import(
|
|
17
|
+
/* @vite-ignore */
|
|
18
|
+
"node:url"
|
|
19
|
+
);
|
|
52
20
|
const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
|
|
53
|
-
const raw = await fs.readFile(path.join(__dirname,
|
|
21
|
+
const raw = await fs.readFile(path.join(__dirname, "../../corpus/catalog-a2ui_0_9.json"), "utf8");
|
|
54
22
|
catalogJson = JSON.parse(raw);
|
|
55
23
|
} else {
|
|
56
|
-
const resp = await fetch(new URL(
|
|
24
|
+
const resp = await fetch(new URL("../../corpus/catalog-a2ui_0_9.json", import.meta.url));
|
|
57
25
|
if (resp.ok) catalogJson = await resp.json();
|
|
58
26
|
}
|
|
59
|
-
} catch {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
// → legacy `{Name: {aliases}}` shape this vocab builder expects.
|
|
63
|
-
const comps = catalogJson?.components || {};
|
|
27
|
+
} catch {
|
|
28
|
+
}
|
|
29
|
+
const comps = catalogJson?.["components"] ?? {};
|
|
64
30
|
const displayList = [];
|
|
65
|
-
const allowedSet = new Set();
|
|
31
|
+
const allowedSet = /* @__PURE__ */ new Set();
|
|
66
32
|
for (const [name, def] of Object.entries(comps)) {
|
|
67
33
|
allowedSet.add(name);
|
|
68
|
-
const ext = def?.[
|
|
69
|
-
const syns =
|
|
70
|
-
const
|
|
34
|
+
const ext = def?.["x-adiaui"] ?? {};
|
|
35
|
+
const syns = ext && typeof ext === "object" ? ext["synonyms"] : null;
|
|
36
|
+
const tags = syns && typeof syns === "object" ? syns["tags"] : null;
|
|
37
|
+
const aliases = Array.isArray(tags) ? tags.filter((t) => typeof t === "string") : [];
|
|
71
38
|
for (const a of aliases) allowedSet.add(a);
|
|
72
|
-
displayList.push(aliases.length ? `${name} (also: ${aliases.join(
|
|
39
|
+
displayList.push(aliases.length ? `${name} (also: ${aliases.join(", ")})` : name);
|
|
73
40
|
}
|
|
74
41
|
_vocab = { displayList, allowedSet };
|
|
75
42
|
return _vocab;
|
|
76
43
|
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Analyze a user prompt — extract concepts, entities, implied components,
|
|
80
|
-
* style hints, and produce a steelmanned brief.
|
|
81
|
-
*
|
|
82
|
-
* @param {object} opts
|
|
83
|
-
* @param {string} opts.intent — Raw user intent
|
|
84
|
-
* @param {object} [opts.llmAdapter] — LLM adapter (must implement .complete())
|
|
85
|
-
* @param {object} [opts.domain] — Domain classification from domain-router (optional context)
|
|
86
|
-
* @returns {Promise<object>} Analysis object (see module docstring for schema)
|
|
87
|
-
*/
|
|
88
|
-
export async function analyzePrompt({ intent, llmAdapter, domain }) {
|
|
44
|
+
async function analyzePrompt({ intent, llmAdapter, domain }) {
|
|
89
45
|
const passthrough = {
|
|
90
46
|
raw: intent,
|
|
91
47
|
concepts: [],
|
|
@@ -93,23 +49,16 @@ export async function analyzePrompt({ intent, llmAdapter, domain }) {
|
|
|
93
49
|
impliedComponents: [],
|
|
94
50
|
styleHints: [],
|
|
95
51
|
steelman: intent,
|
|
96
|
-
analyzed: false
|
|
52
|
+
analyzed: false
|
|
97
53
|
};
|
|
98
|
-
|
|
99
54
|
if (!intent || !llmAdapter) return passthrough;
|
|
100
|
-
|
|
101
55
|
const { displayList, allowedSet } = await getVocab();
|
|
102
|
-
const componentList = displayList.length
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
const domainHint = domain?.domain && domain.confidence > 0
|
|
107
|
-
? `\nDomain classifier suggests: ${domain.domain} (confidence ${domain.confidence})`
|
|
108
|
-
: '';
|
|
109
|
-
|
|
56
|
+
const componentList = displayList.length ? displayList.join(", ") : "Card, Section, Header, Footer, Column, Row, Grid, Button, Input, Text, Image, Badge, Icon, Avatar, Alert, Modal, Tabs, Accordion";
|
|
57
|
+
const domainHint = domain?.["domain"] && (domain?.["confidence"] ?? 0) > 0 ? `
|
|
58
|
+
Domain classifier suggests: ${domain["domain"]} (confidence ${domain["confidence"]})` : "";
|
|
110
59
|
const systemPrompt = `You are a UI brief enricher for the AdiaUI design system. Given a terse or under-specified user prompt, you extract structured signals and produce an enriched brief that a downstream UI generator can use to produce a polished, complete UI.
|
|
111
60
|
|
|
112
|
-
Your output is JSON with this exact shape
|
|
61
|
+
Your output is JSON with this exact shape \u2014 no markdown, no explanation:
|
|
113
62
|
{
|
|
114
63
|
"concepts": [string], // 2-5 short concept tags (e.g. "commerce", "authentication", "data-display", "navigation")
|
|
115
64
|
"entities": [string], // explicit nouns from the prompt (lowercase, singular)
|
|
@@ -121,7 +70,7 @@ Your output is JSON with this exact shape — no markdown, no explanation:
|
|
|
121
70
|
AdiaUI components you may name in implied_components (use these names exactly): ${componentList}
|
|
122
71
|
|
|
123
72
|
Rules:
|
|
124
|
-
- Extract entities verbatim from the prompt
|
|
73
|
+
- Extract entities verbatim from the prompt \u2014 do not invent.
|
|
125
74
|
- implied_components should include both the obvious (Card, Button) AND the ones a thoughtful designer would add (Badge for sale tag on a product, Avatar for a user row, EmptyState for empty data).
|
|
126
75
|
- The steelman expands the prompt into what the user MEANT, not what they SAID. Add the implicit requirements: typical sub-elements, sensible defaults, a header, a footer, common variants. Do NOT add scope the user wouldn't want (e.g. don't turn "login form" into "login form with social auth" unless that's a near-universal expectation).
|
|
127
76
|
- Keep steelman under 250 characters.
|
|
@@ -135,86 +84,79 @@ Input: "settings page with toggles for notifications, dark mode, and privacy"
|
|
|
135
84
|
Output: {"concepts":["settings","preferences"],"entities":["notifications","dark mode","privacy","toggles"],"implied_components":["Card","Header","Section","Toggle","Text","Divider"],"style_hints":[],"steelman":"Settings page with three toggle rows for notifications, dark mode, and privacy. Each row has a label, description, and toggle. Grouped under a header card."}
|
|
136
85
|
|
|
137
86
|
Input: "make it 3D"
|
|
138
|
-
Output: {"concepts":["style-modifier"],"entities":[],"implied_components":[],"style_hints":["3D","depth","shadow"],"steelman":"Modify the existing canvas to feel three-dimensional
|
|
139
|
-
|
|
87
|
+
Output: {"concepts":["style-modifier"],"entities":[],"implied_components":[],"style_hints":["3D","depth","shadow"],"steelman":"Modify the existing canvas to feel three-dimensional \u2014 add elevation, layered shadows, and depth-suggesting transforms. Apply to existing components without changing structure."}`;
|
|
140
88
|
const userMsg = `Prompt: "${intent}"${domainHint}`;
|
|
141
|
-
|
|
142
89
|
let response;
|
|
143
90
|
try {
|
|
144
91
|
response = await llmAdapter.complete({
|
|
145
|
-
messages: [{ role:
|
|
146
|
-
systemPrompt
|
|
92
|
+
messages: [{ role: "user", content: userMsg }],
|
|
93
|
+
systemPrompt
|
|
147
94
|
});
|
|
148
95
|
} catch {
|
|
149
96
|
return passthrough;
|
|
150
97
|
}
|
|
151
|
-
|
|
152
98
|
const text = response?.content;
|
|
153
99
|
if (!text) return passthrough;
|
|
154
|
-
|
|
155
|
-
// Truncation: if the analyzer LLM hit max_tokens we'd rather use the raw
|
|
156
|
-
// prompt than a half-parsed analysis that misleads downstream stages.
|
|
157
|
-
if (response.stopReason && response.stopReason !== 'end' && response.stopReason !== 'end_turn' && response.stopReason !== 'stop') {
|
|
100
|
+
if (response.stopReason && response.stopReason !== "end" && response.stopReason !== "end_turn" && response.stopReason !== "stop") {
|
|
158
101
|
return passthrough;
|
|
159
102
|
}
|
|
160
|
-
|
|
161
|
-
// Try direct parse, then code-fence extraction, then outermost JSON.
|
|
162
103
|
let parsed = null;
|
|
163
|
-
try {
|
|
104
|
+
try {
|
|
105
|
+
parsed = JSON.parse(text.trim());
|
|
106
|
+
} catch {
|
|
107
|
+
}
|
|
164
108
|
if (!parsed) {
|
|
165
109
|
const fence = text.match(/```(?:json)?\s*\n?([\s\S]*?)```/);
|
|
166
|
-
if (fence) try {
|
|
110
|
+
if (fence) try {
|
|
111
|
+
parsed = JSON.parse((fence[1] ?? "").trim());
|
|
112
|
+
} catch {
|
|
113
|
+
}
|
|
167
114
|
}
|
|
168
115
|
if (!parsed) {
|
|
169
|
-
const start = text.indexOf(
|
|
170
|
-
const end = text.lastIndexOf(
|
|
116
|
+
const start = text.indexOf("{");
|
|
117
|
+
const end = text.lastIndexOf("}");
|
|
171
118
|
if (start >= 0 && end > start) {
|
|
172
|
-
try {
|
|
119
|
+
try {
|
|
120
|
+
parsed = JSON.parse(text.slice(start, end + 1));
|
|
121
|
+
} catch {
|
|
122
|
+
}
|
|
173
123
|
}
|
|
174
124
|
}
|
|
175
|
-
if (!parsed || typeof parsed !==
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
// their aliases — runtime registry maps aliases to the right tag, and the
|
|
179
|
-
// LLM was told it can use either, so both are accepted here).
|
|
180
|
-
const impliedComponents = Array.isArray(parsed.implied_components)
|
|
181
|
-
? parsed.implied_components.filter(c => typeof c === 'string' && (allowedSet.size === 0 || allowedSet.has(c)))
|
|
182
|
-
: [];
|
|
183
|
-
|
|
125
|
+
if (!parsed || typeof parsed !== "object") return passthrough;
|
|
126
|
+
const p = parsed;
|
|
127
|
+
const impliedComponents = Array.isArray(p["implied_components"]) ? p["implied_components"].filter((c) => typeof c === "string" && (allowedSet.size === 0 || allowedSet.has(c))) : [];
|
|
184
128
|
return {
|
|
185
129
|
raw: intent,
|
|
186
|
-
concepts:
|
|
187
|
-
entities:
|
|
130
|
+
concepts: asStringArray(p["concepts"]).slice(0, 8),
|
|
131
|
+
entities: asStringArray(p["entities"]).slice(0, 16),
|
|
188
132
|
impliedComponents: impliedComponents.slice(0, 16),
|
|
189
|
-
styleHints:
|
|
190
|
-
steelman:
|
|
191
|
-
analyzed:
|
|
133
|
+
styleHints: asStringArray(p["style_hints"]).slice(0, 8),
|
|
134
|
+
steelman: typeof p["steelman"] === "string" && p["steelman"].trim() || intent,
|
|
135
|
+
analyzed: true
|
|
192
136
|
};
|
|
193
137
|
}
|
|
194
|
-
|
|
195
138
|
function asStringArray(v) {
|
|
196
139
|
if (!Array.isArray(v)) return [];
|
|
197
|
-
return v.filter(x => typeof x ===
|
|
140
|
+
return v.filter((x) => typeof x === "string" && x.trim().length > 0).map((x) => x.trim());
|
|
198
141
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
* Format an analysis object for inclusion in a downstream system prompt.
|
|
202
|
-
* Keeps the shape compact so it doesn't blow the context budget.
|
|
203
|
-
*/
|
|
204
|
-
export function formatAnalysisForPrompt(analysis) {
|
|
205
|
-
if (!analysis || !analysis.analyzed) return '';
|
|
142
|
+
function formatAnalysisForPrompt(analysis) {
|
|
143
|
+
if (!analysis || !analysis.analyzed) return "";
|
|
206
144
|
const parts = [];
|
|
207
145
|
if (analysis.steelman && analysis.steelman !== analysis.raw) {
|
|
208
146
|
parts.push(`ENRICHED BRIEF: ${analysis.steelman}`);
|
|
209
147
|
}
|
|
210
148
|
if (analysis.impliedComponents?.length) {
|
|
211
|
-
parts.push(`EXPECTED COMPONENTS: ${analysis.impliedComponents.join(
|
|
149
|
+
parts.push(`EXPECTED COMPONENTS: ${analysis.impliedComponents.join(", ")}`);
|
|
212
150
|
}
|
|
213
151
|
if (analysis.concepts?.length) {
|
|
214
|
-
parts.push(`CONCEPTS: ${analysis.concepts.join(
|
|
152
|
+
parts.push(`CONCEPTS: ${analysis.concepts.join(", ")}`);
|
|
215
153
|
}
|
|
216
154
|
if (analysis.styleHints?.length) {
|
|
217
|
-
parts.push(`STYLE HINTS: ${analysis.styleHints.join(
|
|
155
|
+
parts.push(`STYLE HINTS: ${analysis.styleHints.join(", ")}`);
|
|
218
156
|
}
|
|
219
|
-
return parts.join(
|
|
157
|
+
return parts.join("\n");
|
|
220
158
|
}
|
|
159
|
+
export {
|
|
160
|
+
analyzePrompt,
|
|
161
|
+
formatAnalysisForPrompt
|
|
162
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adia-ai/a2ui-retrieval",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.7",
|
|
4
4
|
"description": "AdiaUI A2UI retrieval layer \u2014 catalog lookup, intent classification, domain routing, pattern + anti-pattern matching, clarity + context assembly. Consumed by the compose engine and any A2UI-protocol tooling that needs to reason about user intent against the catalog.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.js",
|
|
@@ -63,7 +63,9 @@
|
|
|
63
63
|
"feedback/",
|
|
64
64
|
"authoring/",
|
|
65
65
|
"README.md",
|
|
66
|
-
"CHANGELOG.md"
|
|
66
|
+
"CHANGELOG.md",
|
|
67
|
+
"!**/*.ts",
|
|
68
|
+
"**/*.d.ts"
|
|
67
69
|
],
|
|
68
70
|
"license": "MIT",
|
|
69
71
|
"publishConfig": {
|