improved a lot of things

This commit is contained in:
SinachPat
2026-04-26 09:09:55 +01:00
parent 2911f22df8
commit 97315846be
70 changed files with 4967 additions and 10 deletions
@@ -0,0 +1,61 @@
import type { AIGateway } from '../gateway.js';
import { buildSystemPrompt } from '../prompts/system.js';
export interface AgentQAInput {
/** Question from the coding agent (e.g. Cursor or Claude Code) */
question: string;
/** The diff ID this question is about */
diffId: string;
/** Full artboard context as JSON */
artboardContextJson: string;
/** Active DLF */
dlfJson?: string;
}
export interface AgentQAOutput {
answer: string;
/** Optional visual reference description (e.g. "see padding token in section 3") */
visualReference?: string;
}
export async function answerAgentQuestion(
gateway: AIGateway,
input: AgentQAInput
): Promise<AgentQAOutput> {
const system = buildSystemPrompt({
role: 'a design agent answering questions from a coding agent implementing a design diff',
...(input.dlfJson !== undefined ? { dlfJson: input.dlfJson } : {}),
});
const response = await gateway.complete({
system,
messages: [
{
role: 'user',
content: `<artboard_context>\n${input.artboardContextJson}\n</artboard_context>\n\n<diff_id>${input.diffId}</diff_id>\n\n<question>\n${input.question}\n</question>\n\nAnswer the coding agent's question concisely and precisely. If the answer references a specific design token, component, or visual region, include a "visualReference" field. Return JSON:\n{\n "answer": "...",\n "visualReference": "..." (optional)\n}\n\nRespond ONLY with valid JSON.`,
},
],
maxTokens: 1024,
temperature: 0.7,
});
let parsed: unknown;
try {
parsed = JSON.parse(response.text);
} catch (err) {
throw new Error(
`Design agent returned unparseable JSON: ${String(err)}. ` +
`Raw response (first 300 chars): ${response.text.slice(0, 300)}`
);
}
const output = parsed as AgentQAOutput;
if (!output.answer || typeof output.answer !== 'string') {
throw new Error(
`Design agent response missing required "answer" field. ` +
`Raw response (first 300 chars): ${response.text.slice(0, 300)}`
);
}
return output;
}
@@ -0,0 +1,53 @@
import type { AIGateway } from '../gateway.js';
import { buildSystemPrompt } from '../prompts/system.js';
export interface ArtboardQueryInput {
/** Natural language query from the user */
query: string;
/** Array of artboard metadata objects as JSON */
artboardsJson: string;
}
export interface ArtboardQueryResult {
artboardId: string;
relevanceScore: number;
reason: string;
}
export interface ArtboardQueryOutput {
results: ArtboardQueryResult[];
reasoning: string;
}
export async function queryCrossArtboard(
gateway: AIGateway,
input: ArtboardQueryInput
): Promise<ArtboardQueryOutput> {
const system = buildSystemPrompt({ role: 'a search agent filtering artboards by design intent' });
const response = await gateway.complete({
system,
messages: [
{
role: 'user',
content: `<artboards>\n${input.artboardsJson}\n</artboards>\n\n<query>\n${input.query}\n</query>\n\nReturn a JSON object:\n{\n "results": [{"artboardId": "...", "relevanceScore": 0-1, "reason": "..."}],\n "reasoning": "brief explanation"\n}\n\nOnly include artboards with relevanceScore > 0.3. Respond ONLY with valid JSON.`,
},
],
maxTokens: 1024,
temperature: 0.3,
});
// Returning empty results on parse failure is indistinguishable from "no match".
// Throw so the UI can show an error rather than a misleading empty state.
let parsed: unknown;
try {
parsed = JSON.parse(response.text);
} catch (err) {
throw new Error(
`Artboard query returned unparseable JSON: ${String(err)}. ` +
`Raw response (first 300 chars): ${response.text.slice(0, 300)}`
);
}
return parsed as ArtboardQueryOutput;
}
@@ -0,0 +1,76 @@
import type Anthropic from '@anthropic-ai/sdk';
import { AIGateway } from '../gateway.js';
import { buildSystemPrompt } from '../prompts/system.js';
import type { GatewayResponse } from '../gateway.js';
// ── Types ─────────────────────────────────────────────────────────────────────
export interface CompletionZoneInput {
/** JSON of the surrounding component tree */
componentTreeJson: string;
/** User intent / what the zone should be filled with */
intent: string;
/** Active Design Language File as JSON string */
dlfJson?: string;
/** Before screenshot as base64 data URL (optional) */
screenshotBase64?: string;
}
export interface CompletionZoneOutput {
/** Proposed component tree changes as structured JSON */
proposedTree: unknown;
/** Natural language description of the proposed change */
description: string;
raw: GatewayResponse;
}
// ── Feature ───────────────────────────────────────────────────────────────────
export async function fillCompletionZone(
gateway: AIGateway,
input: CompletionZoneInput,
maxRetries = 3
): Promise<CompletionZoneOutput> {
const system = buildSystemPrompt({
role: 'a Completion Zone design agent',
...(input.dlfJson !== undefined ? { dlfJson: input.dlfJson } : {}),
});
const userContent: Anthropic.Messages.ContentBlockParam[] = [
{
type: 'text',
text: `<component_tree>\n${input.componentTreeJson}\n</component_tree>\n\n<intent>\n${input.intent}\n</intent>\n\nReturn a JSON object with two fields:\n- "proposedTree": the updated component tree matching the intent\n- "description": a one-sentence description of the change\n\nRespond ONLY with valid JSON.`,
},
];
if (input.screenshotBase64) {
userContent.unshift({
type: 'image',
source: { type: 'base64', media_type: 'image/png', data: input.screenshotBase64.replace(/^data:image\/\w+;base64,/, '') },
} as Anthropic.Messages.ImageBlockParam);
}
// Retry up to maxRetries times on invalid JSON output
let lastError: Error | null = null;
for (let attempt = 0; attempt < maxRetries; attempt++) {
const response = await gateway.complete({
system,
messages: [{ role: 'user', content: userContent }],
maxTokens: 4096,
temperature: 0.3,
});
try {
const parsed = JSON.parse(response.text);
return { proposedTree: parsed.proposedTree as unknown, description: String(parsed.description ?? ''), raw: response };
} catch (parseErr) {
lastError = new Error(
`AI returned invalid JSON on attempt ${attempt + 1}: ${String(parseErr)}. ` +
`Raw response (first 200 chars): ${response.text.slice(0, 200)}`
);
}
}
throw lastError ?? new Error('Completion zone fill failed after retries');
}
@@ -0,0 +1,34 @@
import type { AIGateway } from '../gateway.js';
import { buildSystemPrompt } from '../prompts/system.js';
export interface DiffSummaryInput {
/** Serialized component-level changes (JSON) */
changesJson: string;
/** Component name */
componentName: string;
}
export interface DiffSummaryOutput {
summary: string;
}
export async function generateDiffSummary(
gateway: AIGateway,
input: DiffSummaryInput
): Promise<DiffSummaryOutput> {
const system = buildSystemPrompt({ role: 'a technical writer summarizing UI component changes' });
const response = await gateway.complete({
system,
messages: [
{
role: 'user',
content: `Summarize the following component changes for "${input.componentName}" in one concise sentence (max 20 words) suitable for a developer reviewing a pull request. Focus on what changed and its purpose.\n\n<changes>\n${input.changesJson}\n</changes>\n\nRespond with only the summary sentence.`,
},
],
maxTokens: 128,
temperature: 0.3,
});
return { summary: response.text.trim() };
}
@@ -0,0 +1,73 @@
import type { AIGateway } from '../gateway.js';
import { buildSystemPrompt } from '../prompts/system.js';
export interface DriftReportInput {
/** Screenshot of the live app as base64 data URL */
screenshotBase64: string;
/** Active Design Language File as JSON string */
dlfJson: string;
}
export interface DriftViolation {
component: string;
property: string;
currentValue: string;
expectedValue: string;
severity: 'critical' | 'warning';
description: string;
}
export interface DriftReportOutput {
violations: DriftViolation[];
summary: string;
violationCount: number;
}
export async function generateDriftReport(
gateway: AIGateway,
input: DriftReportInput
): Promise<DriftReportOutput> {
const system = buildSystemPrompt({
role: 'a design system compliance auditor',
dlfJson: input.dlfJson,
});
const response = await gateway.complete({
system,
messages: [
{
role: 'user',
content: [
{
type: 'image',
source: {
type: 'base64',
media_type: 'image/png',
data: input.screenshotBase64.replace(/^data:image\/\w+;base64,/, ''),
},
},
{
type: 'text',
text: 'Compare this screenshot against the design language file provided in your system context. Identify all design system drift violations.\n\nReturn a JSON object:\n{\n "violations": [{"component":"...", "property":"...", "currentValue":"...", "expectedValue":"...", "severity":"critical|warning", "description":"..."}],\n "summary": "...",\n "violationCount": N\n}\n\nRespond ONLY with valid JSON.',
},
],
},
],
maxTokens: 4096,
temperature: 0.3,
});
// Returning empty violations on parse failure would be a false-negative in a
// compliance feature. Throw so the caller can surface the error clearly.
let parsed: unknown;
try {
parsed = JSON.parse(response.text);
} catch (err) {
throw new Error(
`Drift report returned unparseable JSON: ${String(err)}. ` +
`Raw response (first 300 chars): ${response.text.slice(0, 300)}`
);
}
return parsed as DriftReportOutput;
}