rspace-online/lib/canvas-tools.ts

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);
}