420 lines
15 KiB
TypeScript
420 lines
15 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)`;
|
|
},
|
|
},
|
|
];
|
|
|
|
// ── Social Media / Campaign Tools ──
|
|
registry.push(
|
|
{
|
|
declaration: {
|
|
name: "create_social_post",
|
|
description: "Create a social media post card for scheduling across platforms.",
|
|
parameters: {
|
|
type: "object",
|
|
properties: {
|
|
platform: { type: "string", description: "Target platform", enum: ["x", "linkedin", "instagram", "youtube", "threads", "bluesky", "tiktok", "facebook"] },
|
|
content: { type: "string", description: "Post text content" },
|
|
postType: { type: "string", description: "Format", enum: ["text", "image", "video", "carousel", "thread", "article"] },
|
|
scheduledAt: { type: "string", description: "ISO datetime to schedule" },
|
|
hashtags: { type: "string", description: "Comma-separated hashtags" },
|
|
},
|
|
required: ["platform", "content"],
|
|
},
|
|
},
|
|
tagName: "folk-social-post",
|
|
buildProps: (args) => ({
|
|
platform: args.platform || "x",
|
|
content: args.content,
|
|
postType: args.postType || "text",
|
|
scheduledAt: args.scheduledAt || "",
|
|
hashtags: args.hashtags ? args.hashtags.split(",").map((t: string) => t.trim()).filter(Boolean) : [],
|
|
status: "draft",
|
|
}),
|
|
actionLabel: (args) => `Created ${args.platform || "social"} post`,
|
|
},
|
|
{
|
|
declaration: {
|
|
name: "create_social_thread",
|
|
description: "Create a tweet thread card on the canvas. Use when the user wants to draft a multi-tweet thread.",
|
|
parameters: {
|
|
type: "object",
|
|
properties: {
|
|
title: { type: "string", description: "Thread title" },
|
|
platform: { type: "string", description: "Target platform", enum: ["x", "bluesky", "threads"] },
|
|
tweetsJson: { type: "string", description: "JSON array of tweet strings" },
|
|
status: { type: "string", description: "Thread status", enum: ["draft", "ready", "published"] },
|
|
},
|
|
required: ["title"],
|
|
},
|
|
},
|
|
tagName: "folk-social-thread",
|
|
buildProps: (args) => {
|
|
let tweets: string[] = [];
|
|
try { tweets = JSON.parse(args.tweetsJson || "[]"); } catch { tweets = []; }
|
|
return {
|
|
title: args.title,
|
|
platform: args.platform || "x",
|
|
tweets,
|
|
status: args.status || "draft",
|
|
};
|
|
},
|
|
actionLabel: (args) => `Created thread: ${args.title}`,
|
|
},
|
|
{
|
|
declaration: {
|
|
name: "create_campaign_card",
|
|
description: "Create a campaign dashboard card on the canvas. Use when the user wants to plan or track a social media campaign.",
|
|
parameters: {
|
|
type: "object",
|
|
properties: {
|
|
title: { type: "string", description: "Campaign title" },
|
|
description: { type: "string", description: "Campaign description" },
|
|
platforms: { type: "string", description: "Comma-separated platform names" },
|
|
duration: { type: "string", description: "Campaign duration (e.g. '4 weeks')" },
|
|
},
|
|
required: ["title"],
|
|
},
|
|
},
|
|
tagName: "folk-social-campaign",
|
|
buildProps: (args) => ({
|
|
title: args.title,
|
|
description: args.description || "",
|
|
platforms: args.platforms ? args.platforms.split(",").map((p: string) => p.trim().toLowerCase()).filter(Boolean) : [],
|
|
duration: args.duration || "",
|
|
}),
|
|
actionLabel: (args) => `Created campaign: ${args.title}`,
|
|
},
|
|
{
|
|
declaration: {
|
|
name: "create_newsletter_card",
|
|
description: "Create a newsletter/email campaign card on the canvas. Use when the user wants to draft or schedule an email newsletter.",
|
|
parameters: {
|
|
type: "object",
|
|
properties: {
|
|
subject: { type: "string", description: "Email subject line" },
|
|
listName: { type: "string", description: "Mailing list name" },
|
|
status: { type: "string", description: "Newsletter status", enum: ["draft", "scheduled", "sent"] },
|
|
scheduledAt: { type: "string", description: "ISO datetime to schedule" },
|
|
},
|
|
required: ["subject"],
|
|
},
|
|
},
|
|
tagName: "folk-social-newsletter",
|
|
buildProps: (args) => ({
|
|
subject: args.subject,
|
|
listName: args.listName || "",
|
|
status: args.status || "draft",
|
|
scheduledAt: args.scheduledAt || "",
|
|
}),
|
|
actionLabel: (args) => `Created newsletter: ${args.subject}`,
|
|
},
|
|
);
|
|
|
|
// ── Design Agent Tool ──
|
|
registry.push({
|
|
declaration: {
|
|
name: "create_design_agent",
|
|
description: "Open the design agent to create print layouts in Scribus. Use when the user wants to design a poster, flyer, brochure, or any print-ready document.",
|
|
parameters: {
|
|
type: "object",
|
|
properties: {
|
|
brief: { type: "string", description: "Design brief describing what to create (e.g. 'A4 event poster for Mushroom Festival with title, date, and image area')" },
|
|
},
|
|
required: ["brief"],
|
|
},
|
|
},
|
|
tagName: "folk-design-agent",
|
|
buildProps: (args) => ({ brief: args.brief || "" }),
|
|
actionLabel: (args) => `Opened design agent${args.brief ? `: ${args.brief.slice(0, 50)}` : ""}`,
|
|
});
|
|
|
|
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);
|
|
}
|