Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions docs/tool-catalog.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,24 @@ Retrieves a screen and downloads its screenshot image as base64.

**Returns:** The screen object with an added `screenshotBase64` field containing a base64-encoded PNG.

### `generate_screen_from_json`

Generates a new screen from a design prompt with live JSON data embedded. Wraps `generate_screen_from_text` with an enhanced prompt so the generated HTML renders real content instead of placeholder text.

**Input schema:**

```json
{
"projectId": "string (required)",
"prompt": "string (required, design description)",
"jsonData": "object | array | JSON string (required, data to render inline)"
}
```

**Returns:** An object with `generateResult` (the upstream `generate_screen_from_text` response), `dataBound` (boolean), `originalPrompt` (the unmodified design prompt), and `dataKeys` (top-level keys extracted from the data).

**Size limit:** `jsonData` is capped at 100,000 characters after serialization.

### `list_tools`

Lists all available tools with their descriptions and schemas.
Expand Down
125 changes: 125 additions & 0 deletions src/commands/tool/virtual-tools/generate-screen-from-json.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import type { StitchToolClient, Stitch } from '@google/stitch-sdk';
import type { VirtualTool } from '../spec.js';

// Maximum serialized JSON size in characters (~100KB)
const MAX_DATA_LENGTH = 100_000;

// Serializes jsonData to a normalized JSON string.
// Always round-trips through JSON.parse/stringify to validate inputs,
// escape characters that could break prompt fencing, and detect circular references.
function serializeJsonData(jsonData: unknown): string {
if (typeof jsonData === 'string') {
let parsed: unknown;
try {
parsed = JSON.parse(jsonData);
} catch {
throw new Error('jsonData string is not valid JSON');
}
return JSON.stringify(parsed, null, 2);
}
try {
return JSON.stringify(jsonData, null, 2);
} catch {
throw new Error(
'jsonData could not be serialized to JSON (circular reference or non-serializable value)',
);
}
}

// Builds an enhanced prompt that instructs Stitch to generate
// a screen whose HTML renders the provided JSON data.
function buildDataBoundPrompt(designPrompt: string, dataStr: string): string {
return [
designPrompt,
'',
'DATA BINDING REQUIREMENTS:',
'The generated HTML must display the following live data inline.',
'Render every field from the JSON below in the appropriate UI component.',
'Use semantic HTML elements. Do not fetch external data — embed the values directly.',
'',
'```json',
dataStr,
'```',
'',
'IMPORTANT: The HTML must be self-contained with all data rendered inline.',
'Use the data values above as the actual content in the UI components.',
].join('\n');
}

// Extracts top-level keys from the data for response metadata.
// For arrays, returns the keys of the first element.
function extractTopLevelKeys(data: unknown): string[] {
let parsed = data;
if (typeof parsed === 'string') {
try {
parsed = JSON.parse(parsed);
} catch {
return [];
}
}
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
return Object.keys(parsed);
}
if (Array.isArray(parsed) && parsed.length > 0 && typeof parsed[0] === 'object' && parsed[0] !== null) {
return Object.keys(parsed[0]);
}
return [];
}

export const generateScreenFromJsonTool: VirtualTool = {
name: 'generate_screen_from_json',
description: '(Virtual) Generates a new screen from a design prompt with live JSON data embedded. Combines a design description with actual API/app data so the generated HTML renders real content.',
inputSchema: {
type: 'object',
properties: {
projectId: {
type: 'string',
description: 'Required. The project ID to generate the screen in.',
},
prompt: {
type: 'string',
description: 'Required. The design prompt describing the desired screen layout and style.',
},
jsonData: {
type: 'object',
description: 'Required. The JSON data to bind into the generated screen. Accepts an object, array, or JSON string. All values will be rendered inline in the HTML.',
},
},
required: ['projectId', 'prompt', 'jsonData'],
},
execute: async (client: StitchToolClient, args: any, stitch?: Stitch) => {
const { projectId, prompt, jsonData } = args;

if (!projectId || typeof projectId !== 'string') {
throw new Error('projectId is required and must be a string');
}
if (!prompt || typeof prompt !== 'string') {
throw new Error('prompt is required and must be a string');
}
if (jsonData === undefined || jsonData === null) {
throw new Error('jsonData is required');
}

const dataStr = serializeJsonData(jsonData);

if (dataStr.length > MAX_DATA_LENGTH) {
throw new Error(
`jsonData is too large (${dataStr.length} chars). Maximum allowed: ${MAX_DATA_LENGTH}`,
);
}

const enhancedPrompt = buildDataBoundPrompt(prompt, dataStr);

const result = await client.callTool('generate_screen_from_text', {
projectId,
prompt: enhancedPrompt,
});

return {
generateResult: result,
dataBound: true,
originalPrompt: prompt,
dataKeys: extractTopLevelKeys(jsonData),
};
},
};
5 changes: 4 additions & 1 deletion src/commands/tool/virtual-tools/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,19 @@ export { getScreenCodeTool } from './get-screen-code.js';
export { getScreenImageTool } from './get-screen-image.js';
export { buildSiteTool } from './build-site.js';
export { listToolsTool } from './list-tools.js';
export { generateScreenFromJsonTool } from './generate-screen-from-json.js';

import type { VirtualTool } from '../spec.js';
import { getScreenCodeTool } from './get-screen-code.js';
import { getScreenImageTool } from './get-screen-image.js';
import { buildSiteTool } from './build-site.js';
import { listToolsTool } from './list-tools.js';
import type { VirtualTool } from '../spec.js';
import { generateScreenFromJsonTool } from './generate-screen-from-json.js';

export const virtualTools: VirtualTool[] = [
getScreenCodeTool,
getScreenImageTool,
buildSiteTool,
listToolsTool,
generateScreenFromJsonTool,
];
Loading
Loading