295 lines
11 KiB
TypeScript
295 lines
11 KiB
TypeScript
/**
|
|
* Canvas Tool Registry — shared by server (Gemini function declarations) and client (shape spawning).
|
|
* Pure TypeScript, no DOM or server dependencies.
|
|
*/
|
|
|
|
export interface CanvasToolDefinition {
|
|
declaration: {
|
|
name: string;
|
|
description: string;
|
|
parameters: {
|
|
type: "object";
|
|
properties: Record<string, { type: string; description: string; enum?: string[] }>;
|
|
required: string[];
|
|
};
|
|
};
|
|
tagName: string;
|
|
buildProps: (args: Record<string, any>) => Record<string, any>;
|
|
actionLabel: (args: Record<string, any>) => string;
|
|
}
|
|
|
|
const registry: CanvasToolDefinition[] = [
|
|
{
|
|
declaration: {
|
|
name: "create_map",
|
|
description: "Create an interactive map centered on a location. Use when the user wants to see a place, get directions, or explore a geographic area.",
|
|
parameters: {
|
|
type: "object",
|
|
properties: {
|
|
latitude: { type: "number", description: "Latitude of the center point" },
|
|
longitude: { type: "number", description: "Longitude of the center point" },
|
|
zoom: { type: "number", description: "Zoom level (1-18, default 12)" },
|
|
location_name: { type: "string", description: "Human-readable name of the location" },
|
|
},
|
|
required: ["latitude", "longitude", "location_name"],
|
|
},
|
|
},
|
|
tagName: "folk-map",
|
|
buildProps: (args) => ({
|
|
center: [args.longitude, args.latitude],
|
|
zoom: args.zoom || 12,
|
|
}),
|
|
actionLabel: (args) => `Created map: ${args.location_name}`,
|
|
},
|
|
{
|
|
declaration: {
|
|
name: "create_note",
|
|
description: "Create a markdown note on the canvas. Use for text content, lists, summaries, instructions, or any written information.",
|
|
parameters: {
|
|
type: "object",
|
|
properties: {
|
|
content: { type: "string", description: "Markdown content for the note" },
|
|
title: { type: "string", description: "Optional title for the note" },
|
|
},
|
|
required: ["content"],
|
|
},
|
|
},
|
|
tagName: "folk-markdown",
|
|
buildProps: (args) => ({
|
|
value: args.title ? `# ${args.title}\n\n${args.content}` : args.content,
|
|
}),
|
|
actionLabel: (args) => `Created note${args.title ? `: ${args.title}` : ""}`,
|
|
},
|
|
{
|
|
declaration: {
|
|
name: "create_embed",
|
|
description: "Embed a webpage or web app on the canvas. Use for websites, search results, booking sites, videos, or any URL the user wants to view inline.",
|
|
parameters: {
|
|
type: "object",
|
|
properties: {
|
|
url: { type: "string", description: "The URL to embed" },
|
|
title: { type: "string", description: "Descriptive title for the embed" },
|
|
},
|
|
required: ["url"],
|
|
},
|
|
},
|
|
tagName: "folk-embed",
|
|
buildProps: (args) => ({
|
|
url: args.url,
|
|
}),
|
|
actionLabel: (args) => `Embedded: ${args.title || args.url}`,
|
|
},
|
|
{
|
|
declaration: {
|
|
name: "create_image",
|
|
description: "Display an image on the canvas from a URL. Use when showing an existing image, photo, diagram, or any direct image link.",
|
|
parameters: {
|
|
type: "object",
|
|
properties: {
|
|
src: { type: "string", description: "Image URL" },
|
|
alt: { type: "string", description: "Alt text describing the image" },
|
|
},
|
|
required: ["src"],
|
|
},
|
|
},
|
|
tagName: "folk-image",
|
|
buildProps: (args) => ({
|
|
src: args.src,
|
|
alt: args.alt || "",
|
|
}),
|
|
actionLabel: (args) => `Created image${args.alt ? `: ${args.alt}` : ""}`,
|
|
},
|
|
{
|
|
declaration: {
|
|
name: "create_bookmark",
|
|
description: "Create a bookmark card for a URL. Use when the user wants to save or reference a link without embedding the full page.",
|
|
parameters: {
|
|
type: "object",
|
|
properties: {
|
|
url: { type: "string", description: "The URL to bookmark" },
|
|
},
|
|
required: ["url"],
|
|
},
|
|
},
|
|
tagName: "folk-bookmark",
|
|
buildProps: (args) => ({
|
|
url: args.url,
|
|
}),
|
|
actionLabel: (args) => `Bookmarked: ${args.url}`,
|
|
},
|
|
{
|
|
declaration: {
|
|
name: "create_image_gen",
|
|
description: "Generate an AI image from a text prompt. Use when the user wants to create, generate, or imagine a new image that doesn't exist yet.",
|
|
parameters: {
|
|
type: "object",
|
|
properties: {
|
|
prompt: { type: "string", description: "Text prompt describing the image to generate" },
|
|
style: {
|
|
type: "string",
|
|
description: "Visual style for the generated image",
|
|
enum: ["photorealistic", "illustration", "painting", "sketch", "punk-zine", "collage", "vintage", "minimalist"],
|
|
},
|
|
},
|
|
required: ["prompt"],
|
|
},
|
|
},
|
|
tagName: "folk-image-gen",
|
|
buildProps: (args) => ({
|
|
prompt: args.prompt,
|
|
style: args.style || "photorealistic",
|
|
}),
|
|
actionLabel: (args) => `Generating image: ${args.prompt.slice(0, 50)}${args.prompt.length > 50 ? "..." : ""}`,
|
|
},
|
|
// ── Trip Planning Tools ──
|
|
{
|
|
declaration: {
|
|
name: "create_destination",
|
|
description: "Create a destination card for a trip location. Use when the user mentions a city, place, or stop on their trip.",
|
|
parameters: {
|
|
type: "object",
|
|
properties: {
|
|
destName: { type: "string", description: "Name of the destination (city or place)" },
|
|
country: { type: "string", description: "Country name" },
|
|
lat: { type: "number", description: "Latitude coordinate" },
|
|
lng: { type: "number", description: "Longitude coordinate" },
|
|
arrivalDate: { type: "string", description: "Arrival date in YYYY-MM-DD format" },
|
|
departureDate: { type: "string", description: "Departure date in YYYY-MM-DD format" },
|
|
notes: { type: "string", description: "Additional notes about this destination" },
|
|
},
|
|
required: ["destName"],
|
|
},
|
|
},
|
|
tagName: "folk-destination",
|
|
buildProps: (args) => ({
|
|
destName: args.destName,
|
|
...(args.country ? { country: args.country } : {}),
|
|
...(args.lat != null ? { lat: args.lat } : {}),
|
|
...(args.lng != null ? { lng: args.lng } : {}),
|
|
...(args.arrivalDate ? { arrivalDate: args.arrivalDate } : {}),
|
|
...(args.departureDate ? { departureDate: args.departureDate } : {}),
|
|
...(args.notes ? { notes: args.notes } : {}),
|
|
}),
|
|
actionLabel: (args) => `Created destination: ${args.destName}${args.country ? `, ${args.country}` : ""}`,
|
|
},
|
|
{
|
|
declaration: {
|
|
name: "create_itinerary",
|
|
description: "Create an itinerary card with a list of activities/events organized by date. Use when planning a schedule or day-by-day plan.",
|
|
parameters: {
|
|
type: "object",
|
|
properties: {
|
|
tripTitle: { type: "string", description: "Title for the itinerary" },
|
|
itemsJson: { type: "string", description: 'JSON array of items. Each: {"id":"<uuid>","title":"...","date":"YYYY-MM-DD","startTime":"HH:MM","category":"ACTIVITY|TRANSPORT|MEAL|FREE_TIME|FLIGHT"}' },
|
|
},
|
|
required: ["tripTitle", "itemsJson"],
|
|
},
|
|
},
|
|
tagName: "folk-itinerary",
|
|
buildProps: (args) => {
|
|
let items: any[] = [];
|
|
try { items = JSON.parse(args.itemsJson); } catch { items = []; }
|
|
return { tripTitle: args.tripTitle, items };
|
|
},
|
|
actionLabel: (args) => `Created itinerary: ${args.tripTitle}`,
|
|
},
|
|
{
|
|
declaration: {
|
|
name: "create_booking",
|
|
description: "Create a booking card for a flight, hotel, transport, activity, or restaurant reservation.",
|
|
parameters: {
|
|
type: "object",
|
|
properties: {
|
|
bookingType: {
|
|
type: "string",
|
|
description: "Type of booking",
|
|
enum: ["FLIGHT", "HOTEL", "CAR_RENTAL", "TRAIN", "BUS", "FERRY", "ACTIVITY", "RESTAURANT", "OTHER"],
|
|
},
|
|
provider: { type: "string", description: "Provider/company name (e.g. airline, hotel name)" },
|
|
cost: { type: "number", description: "Cost amount" },
|
|
currency: { type: "string", description: "ISO currency code (e.g. USD, EUR)" },
|
|
startDate: { type: "string", description: "Start/check-in date in YYYY-MM-DD format" },
|
|
endDate: { type: "string", description: "End/check-out date in YYYY-MM-DD format" },
|
|
bookingStatus: { type: "string", description: "Booking status", enum: ["PENDING", "CONFIRMED", "CANCELLED"] },
|
|
details: { type: "string", description: "Additional booking details or notes" },
|
|
},
|
|
required: ["bookingType", "provider"],
|
|
},
|
|
},
|
|
tagName: "folk-booking",
|
|
buildProps: (args) => ({
|
|
bookingType: args.bookingType,
|
|
provider: args.provider,
|
|
...(args.cost != null ? { cost: args.cost } : {}),
|
|
...(args.currency ? { currency: args.currency } : {}),
|
|
...(args.startDate ? { startDate: args.startDate } : {}),
|
|
...(args.endDate ? { endDate: args.endDate } : {}),
|
|
...(args.bookingStatus ? { bookingStatus: args.bookingStatus } : {}),
|
|
...(args.details ? { details: args.details } : {}),
|
|
}),
|
|
actionLabel: (args) => `Created booking: ${args.bookingType} — ${args.provider}`,
|
|
},
|
|
{
|
|
declaration: {
|
|
name: "create_budget",
|
|
description: "Create a budget tracker card with total budget and expense line items. Use when the user wants to track trip costs.",
|
|
parameters: {
|
|
type: "object",
|
|
properties: {
|
|
budgetTotal: { type: "number", description: "Total budget amount" },
|
|
currency: { type: "string", description: "ISO currency code (e.g. USD, EUR)" },
|
|
expensesJson: { type: "string", description: 'JSON array of expenses. Each: {"id":"<uuid>","category":"TRANSPORT|ACCOMMODATION|FOOD|ACTIVITY|SHOPPING|OTHER","description":"...","amount":123,"date":"YYYY-MM-DD"}' },
|
|
},
|
|
required: ["budgetTotal"],
|
|
},
|
|
},
|
|
tagName: "folk-budget",
|
|
buildProps: (args) => {
|
|
let expenses: any[] = [];
|
|
try { expenses = JSON.parse(args.expensesJson); } catch { expenses = []; }
|
|
return {
|
|
budgetTotal: args.budgetTotal,
|
|
...(args.currency ? { currency: args.currency } : {}),
|
|
expenses,
|
|
};
|
|
},
|
|
actionLabel: (args) => `Created budget: ${args.currency || "USD"} ${args.budgetTotal}`,
|
|
},
|
|
{
|
|
declaration: {
|
|
name: "create_packing_list",
|
|
description: "Create a packing list card with checkable items organized by category. Use when the user needs help with what to pack.",
|
|
parameters: {
|
|
type: "object",
|
|
properties: {
|
|
itemsJson: { type: "string", description: 'JSON array of packing items. Each: {"id":"<uuid>","name":"...","category":"CLOTHING|FOOTWEAR|ELECTRONICS|GEAR|PERSONAL|DOCUMENTS|SAFETY|SUPPLIES","quantity":1,"packed":false}' },
|
|
},
|
|
required: ["itemsJson"],
|
|
},
|
|
},
|
|
tagName: "folk-packing-list",
|
|
buildProps: (args) => {
|
|
let items: any[] = [];
|
|
try { items = JSON.parse(args.itemsJson); } catch { items = []; }
|
|
return { items };
|
|
},
|
|
actionLabel: (args) => {
|
|
let count = 0;
|
|
try { count = JSON.parse(args.itemsJson).length; } catch {}
|
|
return `Created packing list (${count} items)`;
|
|
},
|
|
},
|
|
];
|
|
|
|
export const CANVAS_TOOLS: CanvasToolDefinition[] = [...registry];
|
|
|
|
export const CANVAS_TOOL_DECLARATIONS = CANVAS_TOOLS.map((t) => t.declaration);
|
|
|
|
export function findTool(name: string): CanvasToolDefinition | undefined {
|
|
return CANVAS_TOOLS.find((t) => t.declaration.name === name);
|
|
}
|
|
|
|
export function registerCanvasTool(def: CanvasToolDefinition): void {
|
|
CANVAS_TOOLS.push(def);
|
|
}
|