From 4a903501dd9eae8e64b27c54f978cfd24130b632 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 22 Jan 2026 12:32:46 +0000 Subject: [PATCH] feat: add Noda VR mind mapping integration Add NodaAgent and tools for interacting with Noda.io VR mind mapping platform: - NodaAgent: Specialized agent for creating/managing 3D mind maps - Node tools: create, update, delete, list nodes - Link tools: create, update, delete, list links - Utility tools: get user info, build complete mind maps - TypeScript types for Noda API (nodes, links, events, filters) The integration uses Noda's Web API (window.noda) to programmatically build mind maps in VR environments. Includes helper methods for creating structured mind maps with proper 3D positioning. --- packages/eko-core/src/agent/index.ts | 3 + packages/eko-core/src/index.ts | 18 ++ packages/eko-core/src/noda/agent.ts | 252 ++++++++++++++++++ packages/eko-core/src/noda/index.ts | 60 +++++ .../eko-core/src/noda/tools/build-mindmap.ts | 234 ++++++++++++++++ .../eko-core/src/noda/tools/create-link.ts | 129 +++++++++ .../eko-core/src/noda/tools/create-node.ts | 176 ++++++++++++ .../eko-core/src/noda/tools/delete-link.ts | 77 ++++++ .../eko-core/src/noda/tools/delete-node.ts | 77 ++++++ packages/eko-core/src/noda/tools/get-user.ts | 71 +++++ packages/eko-core/src/noda/tools/index.ts | 63 +++++ .../eko-core/src/noda/tools/list-links.ts | 99 +++++++ .../eko-core/src/noda/tools/list-nodes.ts | 110 ++++++++ .../eko-core/src/noda/tools/update-link.ts | 139 ++++++++++ .../eko-core/src/noda/tools/update-node.ts | 179 +++++++++++++ packages/eko-core/src/noda/types.ts | 222 +++++++++++++++ packages/eko-core/src/types/index.ts | 20 ++ 17 files changed, 1929 insertions(+) create mode 100644 packages/eko-core/src/noda/agent.ts create mode 100644 packages/eko-core/src/noda/index.ts create mode 100644 packages/eko-core/src/noda/tools/build-mindmap.ts create mode 100644 packages/eko-core/src/noda/tools/create-link.ts create mode 100644 packages/eko-core/src/noda/tools/create-node.ts create mode 100644 packages/eko-core/src/noda/tools/delete-link.ts create mode 100644 packages/eko-core/src/noda/tools/delete-node.ts create mode 100644 packages/eko-core/src/noda/tools/get-user.ts create mode 100644 packages/eko-core/src/noda/tools/index.ts create mode 100644 packages/eko-core/src/noda/tools/list-links.ts create mode 100644 packages/eko-core/src/noda/tools/list-nodes.ts create mode 100644 packages/eko-core/src/noda/tools/update-link.ts create mode 100644 packages/eko-core/src/noda/tools/update-node.ts create mode 100644 packages/eko-core/src/noda/types.ts diff --git a/packages/eko-core/src/agent/index.ts b/packages/eko-core/src/agent/index.ts index d1a66de8..bae59d3e 100644 --- a/packages/eko-core/src/agent/index.ts +++ b/packages/eko-core/src/agent/index.ts @@ -5,6 +5,7 @@ import { BaseBrowserScreenAgent, } from "./browser"; import { Agent, AgentParams } from "./base"; +import { NodaAgent, NodaAgentParams } from "../noda/agent"; export default Eko; @@ -14,4 +15,6 @@ export { BaseBrowserAgent, BaseBrowserLabelsAgent, BaseBrowserScreenAgent, + NodaAgent, + type NodaAgentParams, }; diff --git a/packages/eko-core/src/index.ts b/packages/eko-core/src/index.ts index 9fa592ca..336dcf6f 100644 --- a/packages/eko-core/src/index.ts +++ b/packages/eko-core/src/index.ts @@ -56,6 +56,24 @@ export { VariableStorageTool, } from "./tools"; +// Noda VR Mind Mapping Integration +export { + NodaAgent, + type NodaAgentParams, + // Tools + NodaCreateNodeTool, + NodaUpdateNodeTool, + NodaDeleteNodeTool, + NodaListNodesTool, + NodaCreateLinkTool, + NodaUpdateLinkTool, + NodaDeleteLinkTool, + NodaListLinksTool, + NodaGetUserTool, + NodaBuildMindmapTool, + getAllNodaTools, +} from "./noda"; + export type { ChatService, BrowserService } from "./service"; export { diff --git a/packages/eko-core/src/noda/agent.ts b/packages/eko-core/src/noda/agent.ts new file mode 100644 index 00000000..a9be09f5 --- /dev/null +++ b/packages/eko-core/src/noda/agent.ts @@ -0,0 +1,252 @@ +/** + * NodaAgent - Specialized agent for mind mapping in Noda VR + * + * This agent uses Noda's Web API to create, modify, and manage + * 3D mind maps in virtual reality environments. + */ + +import { Agent, AgentParams } from "../agent/base"; +import { AgentContext } from "../agent/agent-context"; +import { Tool } from "../types/tools.types"; +import { getAllNodaTools } from "./tools"; +import { + NodaAPI, + NodaNodeProperties, + NodaLinkProperties, + NodaMindMap, + NodaEventHandlers, +} from "./types"; + +export interface NodaAgentParams extends Partial { + /** Custom name for the agent */ + name?: string; + /** Custom description */ + description?: string; + /** Additional tools to include */ + additionalTools?: Tool[]; + /** Event handlers for Noda events */ + eventHandlers?: NodaEventHandlers; +} + +/** + * NodaAgent - An AI agent specialized for creating and managing + * VR mind maps using Noda's Web API + */ +export class NodaAgent extends Agent { + private eventHandlers?: NodaEventHandlers; + + constructor(params: NodaAgentParams = {}) { + const nodaTools = getAllNodaTools(); + const allTools = params.additionalTools + ? [...nodaTools, ...params.additionalTools] + : nodaTools; + + super({ + name: params.name || "NodaAgent", + description: + params.description || + `A specialized agent for creating and managing 3D mind maps in Noda VR. +This agent can: +- Create, update, and delete nodes (ideas/concepts) in the VR mind map +- Create, update, and delete links (relationships) between nodes +- Build complete mind maps from structured data +- Query and list existing nodes and links +- Customize appearance with colors, shapes, sizes, and animations + +Use this agent for brainstorming, planning, visualizing concepts, +creating knowledge graphs, or any task that benefits from spatial thinking.`, + tools: allTools, + llms: params.llms, + mcpClient: params.mcpClient, + planDescription: + params.planDescription || + `Use this agent when you need to: +- Create visual representations of ideas or concepts +- Build mind maps, concept maps, or knowledge graphs +- Organize information spatially in 3D +- Brainstorm and capture ideas visually +- Create project plans or storyboards +- Visualize relationships between concepts`, + requestHandler: params.requestHandler, + }); + + this.eventHandlers = params.eventHandlers; + } + + /** + * Set up event handlers for Noda events + */ + public setupEventHandlers(nodaApi: NodaAPI): void { + if (!this.eventHandlers) return; + + if (this.eventHandlers.onNodeCreated) { + nodaApi.onNodeCreated = this.eventHandlers.onNodeCreated; + } + if (this.eventHandlers.onNodeUpdated) { + nodaApi.onNodeUpdated = this.eventHandlers.onNodeUpdated; + } + if (this.eventHandlers.onNodeDeleted) { + nodaApi.onNodeDeleted = this.eventHandlers.onNodeDeleted; + } + if (this.eventHandlers.onLinkCreated) { + nodaApi.onLinkCreated = this.eventHandlers.onLinkCreated; + } + if (this.eventHandlers.onLinkUpdated) { + nodaApi.onLinkUpdated = this.eventHandlers.onLinkUpdated; + } + if (this.eventHandlers.onLinkDeleted) { + nodaApi.onLinkDeleted = this.eventHandlers.onLinkDeleted; + } + } + + /** + * Extended system prompt for mind mapping context + */ + protected async extSysPrompt( + agentContext: AgentContext, + tools: Tool[] + ): Promise { + return ` +## Mind Mapping Best Practices + +When creating mind maps: +1. Start with a central/root node representing the main topic +2. Use descriptive titles that clearly convey each idea +3. Use colors consistently to group related concepts: + - Similar ideas should have similar colors + - Use contrasting colors for different categories +4. Position related nodes near each other in 3D space +5. Use link labels to describe relationships between concepts +6. Size nodes based on importance (larger = more important) +7. Use shapes to categorize nodes: + - Ball: General concepts + - Star: Key ideas or highlights + - Box: Tasks or action items + - Diamond: Decision points + - Plus: Additional/supporting ideas + +## Coordinate System +- X axis: Left (-) to Right (+) +- Y axis: Down (-) to Up (+) +- Z axis: Away (-) to Toward (+) the user + +## Tips +- Create nodes before creating links between them +- Use the build_mindmap tool for creating complete structures efficiently +- Query existing nodes/links before making modifications +- Use meaningful UUIDs for nodes you'll reference later +`; + } + + /** + * Static helper to create a basic mind map structure + */ + static createBasicMindMap( + centralTopic: string, + branches: Array<{ + title: string; + color?: string; + subnodes?: Array<{ title: string; color?: string }>; + }> + ): NodaMindMap { + const nodes: NodaNodeProperties[] = []; + const links: NodaLinkProperties[] = []; + + // Central node + const centralId = "central"; + nodes.push({ + uuid: centralId, + title: centralTopic, + shape: "Star", + size: 15, + color: "#FFD700", + location: { x: 0, y: 0, z: 0 }, + }); + + // Create branches in a circular pattern + const angleStep = (2 * Math.PI) / branches.length; + const radius = 2; + + branches.forEach((branch, i) => { + const branchId = `branch_${i}`; + const angle = i * angleStep; + const x = Math.cos(angle) * radius; + const z = Math.sin(angle) * radius; + + nodes.push({ + uuid: branchId, + title: branch.title, + shape: "Ball", + size: 10, + color: branch.color || this.getDefaultColor(i), + location: { x, y: 0, z }, + }); + + links.push({ + fromUuid: centralId, + toUuid: branchId, + shape: "Solid", + size: 2, + }); + + // Add subnodes if present + if (branch.subnodes) { + const subRadius = 1; + const subAngleStep = (Math.PI / 2) / (branch.subnodes.length + 1); + + branch.subnodes.forEach((subnode, j) => { + const subnodeId = `${branchId}_sub_${j}`; + const subAngle = angle - Math.PI / 4 + (j + 1) * subAngleStep; + const subX = x + Math.cos(subAngle) * subRadius; + const subZ = z + Math.sin(subAngle) * subRadius; + + nodes.push({ + uuid: subnodeId, + title: subnode.title, + shape: "Ball", + size: 6, + color: subnode.color || branch.color || this.getDefaultColor(i), + location: { x: subX, y: -0.5, z: subZ }, + }); + + links.push({ + fromUuid: branchId, + toUuid: subnodeId, + shape: "Solid", + size: 1, + }); + }); + } + }); + + return { + nodes, + links, + metadata: { + name: centralTopic, + createdAt: new Date().toISOString(), + }, + }; + } + + /** + * Get a default color based on index + */ + private static getDefaultColor(index: number): string { + const colors = [ + "#FF6B6B", // Red + "#4ECDC4", // Teal + "#45B7D1", // Blue + "#96CEB4", // Green + "#FFEAA7", // Yellow + "#DDA0DD", // Plum + "#98D8C8", // Mint + "#F7DC6F", // Gold + "#BB8FCE", // Purple + "#85C1E9", // Light Blue + ]; + return colors[index % colors.length]; + } +} + +export default NodaAgent; diff --git a/packages/eko-core/src/noda/index.ts b/packages/eko-core/src/noda/index.ts new file mode 100644 index 00000000..23d1e55f --- /dev/null +++ b/packages/eko-core/src/noda/index.ts @@ -0,0 +1,60 @@ +/** + * Noda Integration Module + * + * Provides tools and an agent for interacting with Noda VR mind mapping platform. + * @see https://noda.io/documentation/webapi.html + */ + +// Export agent +export { NodaAgent, type NodaAgentParams } from "./agent"; +export { NodaAgent as default } from "./agent"; + +// Export all tools +export { + // Node tools + NodaCreateNodeTool, + NodaUpdateNodeTool, + NodaDeleteNodeTool, + NodaListNodesTool, + // Link tools + NodaCreateLinkTool, + NodaUpdateLinkTool, + NodaDeleteLinkTool, + NodaListLinksTool, + // Utility tools + NodaGetUserTool, + NodaBuildMindmapTool, + // Tool name constants + NODA_CREATE_NODE, + NODA_UPDATE_NODE, + NODA_DELETE_NODE, + NODA_LIST_NODES, + NODA_CREATE_LINK, + NODA_UPDATE_LINK, + NODA_DELETE_LINK, + NODA_LIST_LINKS, + NODA_GET_USER, + NODA_BUILD_MINDMAP, + // Helper function + getAllNodaTools, +} from "./tools"; + +// Export types +export type { + NodaNodeShape, + NodaLinkShape, + NodaLinkCurve, + NodaLinkTrail, + NodaLocationFrame, + NodaLocation, + NodaNodeProperties, + NodaLinkProperties, + NodaNodeResponse, + NodaLinkResponse, + NodaUser, + NodaNodeFilter, + NodaLinkFilter, + NodaEventHandlers, + NodaAPI, + NodaMindMap, +} from "./types"; diff --git a/packages/eko-core/src/noda/tools/build-mindmap.ts b/packages/eko-core/src/noda/tools/build-mindmap.ts new file mode 100644 index 00000000..c5f897ef --- /dev/null +++ b/packages/eko-core/src/noda/tools/build-mindmap.ts @@ -0,0 +1,234 @@ +import { JSONSchema7 } from "json-schema"; +import { AgentContext } from "../../agent/agent-context"; +import { Tool, ToolResult } from "../../types/tools.types"; +import { NodaMindMap, NodaNodeProperties, NodaLinkProperties } from "../types"; + +export const TOOL_NAME = "noda_build_mindmap"; + +/** + * Tool for building complete mind maps from structured data + */ +export class NodaBuildMindmapTool implements Tool { + readonly name: string = TOOL_NAME; + readonly description: string; + readonly parameters: JSONSchema7; + readonly supportParallelCalls: boolean = false; + + constructor() { + this.description = `Builds a complete mind map in Noda from structured data. Provide an array of nodes and links to create a full mind map at once. This is more efficient than creating nodes and links individually.`; + this.parameters = { + type: "object", + properties: { + name: { + type: "string", + description: "Name of the mind map (for reference).", + }, + nodes: { + type: "array", + description: "Array of nodes to create in the mind map.", + items: { + type: "object", + properties: { + uuid: { + type: "string", + description: "Unique identifier for the node.", + }, + title: { + type: "string", + description: "Display text for the node.", + }, + color: { + type: "string", + description: "Hex color value (#RRGGBB).", + }, + shape: { + type: "string", + enum: [ + "Ball", + "Box", + "Tetra", + "Cylinder", + "Diamond", + "Hourglass", + "Plus", + "Star", + ], + }, + size: { + type: "number", + minimum: 1, + maximum: 45, + }, + notes: { + type: "string", + }, + x: { type: "number" }, + y: { type: "number" }, + z: { type: "number" }, + }, + required: ["uuid", "title"], + }, + }, + links: { + type: "array", + description: "Array of links connecting nodes.", + items: { + type: "object", + properties: { + uuid: { + type: "string", + description: "Unique identifier for the link.", + }, + fromUuid: { + type: "string", + description: "Source node UUID.", + }, + toUuid: { + type: "string", + description: "Target node UUID.", + }, + title: { + type: "string", + description: "Label for the link.", + }, + color: { + type: "string", + }, + shape: { + type: "string", + enum: ["Solid", "Dash", "Arrows"], + }, + }, + required: ["fromUuid", "toUuid"], + }, + }, + clearExisting: { + type: "boolean", + description: + "If true, clears all existing nodes and links before building. Default is false.", + }, + }, + required: ["nodes"], + }; + } + + async execute( + args: Record, + agentContext: AgentContext + ): Promise { + try { + const noda = this.getNodaAPI(agentContext); + if (!noda) { + return this.errorResult( + "Noda API not available. Make sure you are running inside the Noda VR environment." + ); + } + + const clearExisting = args.clearExisting as boolean || false; + const nodes = args.nodes as Array>; + const links = (args.links as Array>) || []; + + // Clear existing if requested + if (clearExisting) { + const existingNodes = await noda.listNodes(); + for (const node of existingNodes) { + await noda.deleteNode({ uuid: node.uuid }); + } + } + + const createdNodes: string[] = []; + const createdLinks: string[] = []; + const errors: string[] = []; + + // Create nodes first + for (const nodeData of nodes) { + try { + const nodeProps: NodaNodeProperties = { + uuid: nodeData.uuid as string, + title: nodeData.title as string, + color: nodeData.color as string | undefined, + shape: nodeData.shape as NodaNodeProperties["shape"], + size: nodeData.size as number | undefined, + notes: nodeData.notes as string | undefined, + opacity: nodeData.opacity as number | undefined, + imageUrl: nodeData.imageUrl as string | undefined, + pageUrl: nodeData.pageUrl as string | undefined, + }; + + if ( + nodeData.x !== undefined || + nodeData.y !== undefined || + nodeData.z !== undefined + ) { + nodeProps.location = { + x: (nodeData.x as number) || 0, + y: (nodeData.y as number) || 0, + z: (nodeData.z as number) || 0, + }; + } + + const result = await noda.createNode(nodeProps); + createdNodes.push(result.uuid); + } catch (e) { + errors.push(`Node ${nodeData.uuid || nodeData.title}: ${e}`); + } + } + + // Create links after all nodes exist + for (const linkData of links) { + try { + const linkProps: NodaLinkProperties = { + uuid: linkData.uuid as string | undefined, + fromUuid: linkData.fromUuid as string, + toUuid: linkData.toUuid as string, + title: linkData.title as string | undefined, + color: linkData.color as string | undefined, + shape: linkData.shape as NodaLinkProperties["shape"], + size: linkData.size as number | undefined, + }; + + const result = await noda.createLink(linkProps); + createdLinks.push(result.uuid); + } catch (e) { + errors.push( + `Link ${linkData.fromUuid} -> ${linkData.toUuid}: ${e}` + ); + } + } + + return { + content: [ + { + type: "text", + text: JSON.stringify({ + success: errors.length === 0, + message: `Created ${createdNodes.length} nodes and ${createdLinks.length} links`, + createdNodes, + createdLinks, + errors: errors.length > 0 ? errors : undefined, + }), + }, + ], + }; + } catch (error) { + return this.errorResult(`Failed to build mind map: ${error}`); + } + } + + private getNodaAPI(agentContext: AgentContext): typeof window.noda | null { + if (typeof window !== "undefined" && window.noda) { + return window.noda; + } + const nodaApi = agentContext.context.variables.get("nodaApi"); + return nodaApi || null; + } + + private errorResult(message: string): ToolResult { + return { + content: [{ type: "text", text: message }], + isError: true, + }; + } +} + +export default NodaBuildMindmapTool; diff --git a/packages/eko-core/src/noda/tools/create-link.ts b/packages/eko-core/src/noda/tools/create-link.ts new file mode 100644 index 00000000..d7fca57f --- /dev/null +++ b/packages/eko-core/src/noda/tools/create-link.ts @@ -0,0 +1,129 @@ +import { JSONSchema7 } from "json-schema"; +import { AgentContext } from "../../agent/agent-context"; +import { Tool, ToolResult } from "../../types/tools.types"; +import { NodaLinkProperties } from "../types"; + +export const TOOL_NAME = "noda_create_link"; + +/** + * Tool for creating links between nodes in Noda VR mind map + */ +export class NodaCreateLinkTool implements Tool { + readonly name: string = TOOL_NAME; + readonly description: string; + readonly parameters: JSONSchema7; + readonly supportParallelCalls: boolean = true; + + constructor() { + this.description = `Creates a link (connection) between two nodes in the Noda VR mind map. Links represent relationships between ideas or concepts. You can customize the link's appearance with different colors, patterns, and animations.`; + this.parameters = { + type: "object", + properties: { + uuid: { + type: "string", + description: + "Optional unique identifier for the link. If not provided, one will be generated.", + }, + fromUuid: { + type: "string", + description: "UUID of the starting (source) node.", + }, + toUuid: { + type: "string", + description: "UUID of the ending (target) node.", + }, + title: { + type: "string", + description: + "Display text shown on the link. Use to describe the relationship.", + }, + color: { + type: "string", + description: "Hex color value in #RRGGBB format for the link.", + }, + shape: { + type: "string", + description: "Link pattern/style.", + enum: ["Solid", "Dash", "Arrows"], + }, + size: { + type: "number", + description: "Thickness of the link from 1 to 10. Default is 1.", + minimum: 1, + maximum: 10, + }, + curve: { + type: "string", + description: "Curve type for the link path.", + enum: ["none", "cdown", "cup", "sdown", "sup"], + }, + trail: { + type: "string", + description: "Animation trail effect along the link.", + enum: ["none", "ring", "ball", "cone"], + }, + }, + required: ["fromUuid", "toUuid"], + }; + } + + async execute( + args: Record, + agentContext: AgentContext + ): Promise { + try { + const noda = this.getNodaAPI(agentContext); + if (!noda) { + return this.errorResult( + "Noda API not available. Make sure you are running inside the Noda VR environment." + ); + } + + const properties: NodaLinkProperties = { + uuid: args.uuid as string | undefined, + fromUuid: args.fromUuid as string, + toUuid: args.toUuid as string, + title: args.title as string | undefined, + color: args.color as string | undefined, + shape: args.shape as NodaLinkProperties["shape"], + size: args.size as number | undefined, + curve: args.curve as NodaLinkProperties["curve"], + trail: args.trail as NodaLinkProperties["trail"], + }; + + const result = await noda.createLink(properties); + + return { + content: [ + { + type: "text", + text: JSON.stringify({ + success: true, + message: `Link from "${result.fromUuid}" to "${result.toUuid}" created successfully`, + link: result, + }), + }, + ], + }; + } catch (error) { + return this.errorResult(`Failed to create link: ${error}`); + } + } + + private getNodaAPI(agentContext: AgentContext): typeof window.noda | null { + if (typeof window !== "undefined" && window.noda) { + return window.noda; + } + const nodaApi = agentContext.context.variables.get("nodaApi"); + return nodaApi || null; + } + + private errorResult(message: string): ToolResult { + return { + content: [{ type: "text", text: message }], + isError: true, + }; + } +} + +export default NodaCreateLinkTool; diff --git a/packages/eko-core/src/noda/tools/create-node.ts b/packages/eko-core/src/noda/tools/create-node.ts new file mode 100644 index 00000000..d84c6b3a --- /dev/null +++ b/packages/eko-core/src/noda/tools/create-node.ts @@ -0,0 +1,176 @@ +import { JSONSchema7 } from "json-schema"; +import { AgentContext } from "../../agent/agent-context"; +import { Tool, ToolResult } from "../../types/tools.types"; +import { NodaNodeProperties } from "../types"; + +export const TOOL_NAME = "noda_create_node"; + +/** + * Tool for creating nodes in Noda VR mind map + */ +export class NodaCreateNodeTool implements Tool { + readonly name: string = TOOL_NAME; + readonly description: string; + readonly parameters: JSONSchema7; + readonly supportParallelCalls: boolean = true; + + constructor() { + this.description = `Creates a new node in the Noda VR mind map. Nodes can represent ideas, concepts, or any element in your mind map. You can customize the node's appearance with different shapes, colors, sizes, and add notes or links.`; + this.parameters = { + type: "object", + properties: { + uuid: { + type: "string", + description: + "Optional unique identifier for the node. If not provided, one will be generated.", + }, + title: { + type: "string", + description: + "Display text shown above the node. This is the main label for the idea or concept.", + }, + color: { + type: "string", + description: + "Hex color value in #RRGGBB format (e.g., #FF5733). Determines the node color.", + }, + opacity: { + type: "number", + description: + "Opacity from 0 (transparent) to 1 (opaque). Default is 1.", + minimum: 0, + maximum: 1, + }, + shape: { + type: "string", + description: "The 3D shape of the node.", + enum: [ + "Ball", + "Box", + "Tetra", + "Cylinder", + "Diamond", + "Hourglass", + "Plus", + "Star", + ], + }, + imageUrl: { + type: "string", + description: + "Public HTTPS URL to an image to display on the node.", + }, + notes: { + type: "string", + description: + "Free-form text field for additional notes or details about this node.", + }, + pageUrl: { + type: "string", + description: "URL to associate with this node (opens when clicked).", + }, + size: { + type: "number", + description: "Size of the node from 1 to 45. Default is 5.", + minimum: 1, + maximum: 45, + }, + x: { + type: "number", + description: "X coordinate for positioning the node in 3D space.", + }, + y: { + type: "number", + description: "Y coordinate for positioning the node in 3D space.", + }, + z: { + type: "number", + description: "Z coordinate for positioning the node in 3D space.", + }, + relativeTo: { + type: "string", + description: + "Reference frame for positioning: 'world', 'user', or 'node'.", + enum: ["world", "user", "node"], + }, + }, + required: ["title"], + }; + } + + async execute( + args: Record, + agentContext: AgentContext + ): Promise { + try { + const noda = this.getNodaAPI(agentContext); + if (!noda) { + return this.errorResult( + "Noda API not available. Make sure you are running inside the Noda VR environment." + ); + } + + const properties: NodaNodeProperties = { + uuid: args.uuid as string | undefined, + title: args.title as string, + color: args.color as string | undefined, + opacity: args.opacity as number | undefined, + shape: args.shape as NodaNodeProperties["shape"], + imageUrl: args.imageUrl as string | undefined, + notes: args.notes as string | undefined, + pageUrl: args.pageUrl as string | undefined, + size: args.size as number | undefined, + }; + + // Build location if coordinates provided + if ( + args.x !== undefined || + args.y !== undefined || + args.z !== undefined + ) { + properties.location = { + x: (args.x as number) || 0, + y: (args.y as number) || 0, + z: (args.z as number) || 0, + relativeTo: args.relativeTo as "world" | "user" | "node" | undefined, + }; + } + + const result = await noda.createNode(properties); + + return { + content: [ + { + type: "text", + text: JSON.stringify({ + success: true, + message: `Node "${result.title || result.uuid}" created successfully`, + node: result, + }), + }, + ], + }; + } catch (error) { + return this.errorResult(`Failed to create node: ${error}`); + } + } + + private getNodaAPI(agentContext: AgentContext): typeof window.noda | null { + // Check if we're in a browser environment with Noda + if (typeof window !== "undefined" && window.noda) { + return window.noda; + } + // Check if Noda API is stored in agent context + const nodaApi = agentContext.context.variables.get("nodaApi"); + return nodaApi || null; + } + + private errorResult(message: string): ToolResult { + return { + content: [{ type: "text", text: message }], + isError: true, + }; + } +} + +export default NodaCreateNodeTool; diff --git a/packages/eko-core/src/noda/tools/delete-link.ts b/packages/eko-core/src/noda/tools/delete-link.ts new file mode 100644 index 00000000..e6141fbf --- /dev/null +++ b/packages/eko-core/src/noda/tools/delete-link.ts @@ -0,0 +1,77 @@ +import { JSONSchema7 } from "json-schema"; +import { AgentContext } from "../../agent/agent-context"; +import { Tool, ToolResult } from "../../types/tools.types"; + +export const TOOL_NAME = "noda_delete_link"; + +/** + * Tool for deleting links from Noda VR mind map + */ +export class NodaDeleteLinkTool implements Tool { + readonly name: string = TOOL_NAME; + readonly description: string; + readonly parameters: JSONSchema7; + readonly supportParallelCalls: boolean = true; + + constructor() { + this.description = `Deletes a link from the Noda VR mind map. This removes the connection between two nodes. Use with caution as this action cannot be undone.`; + this.parameters = { + type: "object", + properties: { + uuid: { + type: "string", + description: "The unique identifier of the link to delete.", + }, + }, + required: ["uuid"], + }; + } + + async execute( + args: Record, + agentContext: AgentContext + ): Promise { + try { + const noda = this.getNodaAPI(agentContext); + if (!noda) { + return this.errorResult( + "Noda API not available. Make sure you are running inside the Noda VR environment." + ); + } + + const uuid = args.uuid as string; + await noda.deleteLink({ uuid }); + + return { + content: [ + { + type: "text", + text: JSON.stringify({ + success: true, + message: `Link "${uuid}" deleted successfully`, + }), + }, + ], + }; + } catch (error) { + return this.errorResult(`Failed to delete link: ${error}`); + } + } + + private getNodaAPI(agentContext: AgentContext): typeof window.noda | null { + if (typeof window !== "undefined" && window.noda) { + return window.noda; + } + const nodaApi = agentContext.context.variables.get("nodaApi"); + return nodaApi || null; + } + + private errorResult(message: string): ToolResult { + return { + content: [{ type: "text", text: message }], + isError: true, + }; + } +} + +export default NodaDeleteLinkTool; diff --git a/packages/eko-core/src/noda/tools/delete-node.ts b/packages/eko-core/src/noda/tools/delete-node.ts new file mode 100644 index 00000000..21585787 --- /dev/null +++ b/packages/eko-core/src/noda/tools/delete-node.ts @@ -0,0 +1,77 @@ +import { JSONSchema7 } from "json-schema"; +import { AgentContext } from "../../agent/agent-context"; +import { Tool, ToolResult } from "../../types/tools.types"; + +export const TOOL_NAME = "noda_delete_node"; + +/** + * Tool for deleting nodes from Noda VR mind map + */ +export class NodaDeleteNodeTool implements Tool { + readonly name: string = TOOL_NAME; + readonly description: string; + readonly parameters: JSONSchema7; + readonly supportParallelCalls: boolean = true; + + constructor() { + this.description = `Deletes a node from the Noda VR mind map. This will also remove any links connected to this node. Use with caution as this action cannot be undone.`; + this.parameters = { + type: "object", + properties: { + uuid: { + type: "string", + description: "The unique identifier of the node to delete.", + }, + }, + required: ["uuid"], + }; + } + + async execute( + args: Record, + agentContext: AgentContext + ): Promise { + try { + const noda = this.getNodaAPI(agentContext); + if (!noda) { + return this.errorResult( + "Noda API not available. Make sure you are running inside the Noda VR environment." + ); + } + + const uuid = args.uuid as string; + await noda.deleteNode({ uuid }); + + return { + content: [ + { + type: "text", + text: JSON.stringify({ + success: true, + message: `Node "${uuid}" deleted successfully`, + }), + }, + ], + }; + } catch (error) { + return this.errorResult(`Failed to delete node: ${error}`); + } + } + + private getNodaAPI(agentContext: AgentContext): typeof window.noda | null { + if (typeof window !== "undefined" && window.noda) { + return window.noda; + } + const nodaApi = agentContext.context.variables.get("nodaApi"); + return nodaApi || null; + } + + private errorResult(message: string): ToolResult { + return { + content: [{ type: "text", text: message }], + isError: true, + }; + } +} + +export default NodaDeleteNodeTool; diff --git a/packages/eko-core/src/noda/tools/get-user.ts b/packages/eko-core/src/noda/tools/get-user.ts new file mode 100644 index 00000000..b8bfe4de --- /dev/null +++ b/packages/eko-core/src/noda/tools/get-user.ts @@ -0,0 +1,71 @@ +import { JSONSchema7 } from "json-schema"; +import { AgentContext } from "../../agent/agent-context"; +import { Tool, ToolResult } from "../../types/tools.types"; + +export const TOOL_NAME = "noda_get_user"; + +/** + * Tool for getting the current Noda user information + */ +export class NodaGetUserTool implements Tool { + readonly name: string = TOOL_NAME; + readonly description: string; + readonly parameters: JSONSchema7; + readonly supportParallelCalls: boolean = true; + + constructor() { + this.description = `Gets information about the current Noda VR user. Returns the userId associated with the current VR headset/installation.`; + this.parameters = { + type: "object", + properties: {}, + required: [], + }; + } + + async execute( + args: Record, + agentContext: AgentContext + ): Promise { + try { + const noda = this.getNodaAPI(agentContext); + if (!noda) { + return this.errorResult( + "Noda API not available. Make sure you are running inside the Noda VR environment." + ); + } + + const user = await noda.getUser(); + + return { + content: [ + { + type: "text", + text: JSON.stringify({ + success: true, + user: user, + }), + }, + ], + }; + } catch (error) { + return this.errorResult(`Failed to get user: ${error}`); + } + } + + private getNodaAPI(agentContext: AgentContext): typeof window.noda | null { + if (typeof window !== "undefined" && window.noda) { + return window.noda; + } + const nodaApi = agentContext.context.variables.get("nodaApi"); + return nodaApi || null; + } + + private errorResult(message: string): ToolResult { + return { + content: [{ type: "text", text: message }], + isError: true, + }; + } +} + +export default NodaGetUserTool; diff --git a/packages/eko-core/src/noda/tools/index.ts b/packages/eko-core/src/noda/tools/index.ts new file mode 100644 index 00000000..fd3464f5 --- /dev/null +++ b/packages/eko-core/src/noda/tools/index.ts @@ -0,0 +1,63 @@ +/** + * Noda Tools Index + * Export all Noda VR mind mapping tools + */ + +// Node tools +export { NodaCreateNodeTool } from "./create-node"; +export { NodaUpdateNodeTool } from "./update-node"; +export { NodaDeleteNodeTool } from "./delete-node"; +export { NodaListNodesTool } from "./list-nodes"; + +// Link tools +export { NodaCreateLinkTool } from "./create-link"; +export { NodaUpdateLinkTool } from "./update-link"; +export { NodaDeleteLinkTool } from "./delete-link"; +export { NodaListLinksTool } from "./list-links"; + +// Utility tools +export { NodaGetUserTool } from "./get-user"; +export { NodaBuildMindmapTool } from "./build-mindmap"; + +// Re-export tool names for convenience +export { TOOL_NAME as NODA_CREATE_NODE } from "./create-node"; +export { TOOL_NAME as NODA_UPDATE_NODE } from "./update-node"; +export { TOOL_NAME as NODA_DELETE_NODE } from "./delete-node"; +export { TOOL_NAME as NODA_LIST_NODES } from "./list-nodes"; +export { TOOL_NAME as NODA_CREATE_LINK } from "./create-link"; +export { TOOL_NAME as NODA_UPDATE_LINK } from "./update-link"; +export { TOOL_NAME as NODA_DELETE_LINK } from "./delete-link"; +export { TOOL_NAME as NODA_LIST_LINKS } from "./list-links"; +export { TOOL_NAME as NODA_GET_USER } from "./get-user"; +export { TOOL_NAME as NODA_BUILD_MINDMAP } from "./build-mindmap"; + +import { Tool } from "../../types/tools.types"; +import { NodaCreateNodeTool } from "./create-node"; +import { NodaUpdateNodeTool } from "./update-node"; +import { NodaDeleteNodeTool } from "./delete-node"; +import { NodaListNodesTool } from "./list-nodes"; +import { NodaCreateLinkTool } from "./create-link"; +import { NodaUpdateLinkTool } from "./update-link"; +import { NodaDeleteLinkTool } from "./delete-link"; +import { NodaListLinksTool } from "./list-links"; +import { NodaGetUserTool } from "./get-user"; +import { NodaBuildMindmapTool } from "./build-mindmap"; + +/** + * Get all Noda tools as an array + * Useful for registering all tools with an agent + */ +export function getAllNodaTools(): Tool[] { + return [ + new NodaCreateNodeTool(), + new NodaUpdateNodeTool(), + new NodaDeleteNodeTool(), + new NodaListNodesTool(), + new NodaCreateLinkTool(), + new NodaUpdateLinkTool(), + new NodaDeleteLinkTool(), + new NodaListLinksTool(), + new NodaGetUserTool(), + new NodaBuildMindmapTool(), + ]; +} diff --git a/packages/eko-core/src/noda/tools/list-links.ts b/packages/eko-core/src/noda/tools/list-links.ts new file mode 100644 index 00000000..427daec2 --- /dev/null +++ b/packages/eko-core/src/noda/tools/list-links.ts @@ -0,0 +1,99 @@ +import { JSONSchema7 } from "json-schema"; +import { AgentContext } from "../../agent/agent-context"; +import { Tool, ToolResult } from "../../types/tools.types"; +import { NodaLinkFilter } from "../types"; + +export const TOOL_NAME = "noda_list_links"; + +/** + * Tool for listing/querying links in Noda VR mind map + */ +export class NodaListLinksTool implements Tool { + readonly name: string = TOOL_NAME; + readonly description: string; + readonly parameters: JSONSchema7; + readonly supportParallelCalls: boolean = true; + + constructor() { + this.description = `Lists links from the Noda VR mind map. You can filter by UUID, source node, target node, or selection state. Returns all links if no filter is provided.`; + this.parameters = { + type: "object", + properties: { + uuid: { + type: "string", + description: "Filter by specific link UUID.", + }, + fromUuid: { + type: "string", + description: "Filter links by source node UUID.", + }, + toUuid: { + type: "string", + description: "Filter links by target node UUID.", + }, + selected: { + type: "boolean", + description: "Filter by selection state (true for selected links).", + }, + }, + required: [], + }; + } + + async execute( + args: Record, + agentContext: AgentContext + ): Promise { + try { + const noda = this.getNodaAPI(agentContext); + if (!noda) { + return this.errorResult( + "Noda API not available. Make sure you are running inside the Noda VR environment." + ); + } + + const filter: NodaLinkFilter = {}; + if (args.uuid !== undefined) filter.uuid = args.uuid as string; + if (args.fromUuid !== undefined) filter.fromUuid = args.fromUuid as string; + if (args.toUuid !== undefined) filter.toUuid = args.toUuid as string; + if (args.selected !== undefined) + filter.selected = args.selected as boolean; + + const links = await noda.listLinks( + Object.keys(filter).length > 0 ? filter : undefined + ); + + return { + content: [ + { + type: "text", + text: JSON.stringify({ + success: true, + count: links.length, + links: links, + }), + }, + ], + }; + } catch (error) { + return this.errorResult(`Failed to list links: ${error}`); + } + } + + private getNodaAPI(agentContext: AgentContext): typeof window.noda | null { + if (typeof window !== "undefined" && window.noda) { + return window.noda; + } + const nodaApi = agentContext.context.variables.get("nodaApi"); + return nodaApi || null; + } + + private errorResult(message: string): ToolResult { + return { + content: [{ type: "text", text: message }], + isError: true, + }; + } +} + +export default NodaListLinksTool; diff --git a/packages/eko-core/src/noda/tools/list-nodes.ts b/packages/eko-core/src/noda/tools/list-nodes.ts new file mode 100644 index 00000000..d7513da9 --- /dev/null +++ b/packages/eko-core/src/noda/tools/list-nodes.ts @@ -0,0 +1,110 @@ +import { JSONSchema7 } from "json-schema"; +import { AgentContext } from "../../agent/agent-context"; +import { Tool, ToolResult } from "../../types/tools.types"; +import { NodaNodeFilter } from "../types"; + +export const TOOL_NAME = "noda_list_nodes"; + +/** + * Tool for listing/querying nodes in Noda VR mind map + */ +export class NodaListNodesTool implements Tool { + readonly name: string = TOOL_NAME; + readonly description: string; + readonly parameters: JSONSchema7; + readonly supportParallelCalls: boolean = true; + + constructor() { + this.description = `Lists nodes from the Noda VR mind map. You can filter by UUID, title, selection state, or shape. Returns all nodes if no filter is provided.`; + this.parameters = { + type: "object", + properties: { + uuid: { + type: "string", + description: "Filter by specific node UUID.", + }, + title: { + type: "string", + description: "Filter nodes containing this title text.", + }, + selected: { + type: "boolean", + description: "Filter by selection state (true for selected nodes).", + }, + shape: { + type: "string", + description: "Filter by node shape.", + enum: [ + "Ball", + "Box", + "Tetra", + "Cylinder", + "Diamond", + "Hourglass", + "Plus", + "Star", + ], + }, + }, + required: [], + }; + } + + async execute( + args: Record, + agentContext: AgentContext + ): Promise { + try { + const noda = this.getNodaAPI(agentContext); + if (!noda) { + return this.errorResult( + "Noda API not available. Make sure you are running inside the Noda VR environment." + ); + } + + const filter: NodaNodeFilter = {}; + if (args.uuid !== undefined) filter.uuid = args.uuid as string; + if (args.title !== undefined) filter.title = args.title as string; + if (args.selected !== undefined) + filter.selected = args.selected as boolean; + if (args.shape !== undefined) + filter.shape = args.shape as NodaNodeFilter["shape"]; + + const nodes = await noda.listNodes( + Object.keys(filter).length > 0 ? filter : undefined + ); + + return { + content: [ + { + type: "text", + text: JSON.stringify({ + success: true, + count: nodes.length, + nodes: nodes, + }), + }, + ], + }; + } catch (error) { + return this.errorResult(`Failed to list nodes: ${error}`); + } + } + + private getNodaAPI(agentContext: AgentContext): typeof window.noda | null { + if (typeof window !== "undefined" && window.noda) { + return window.noda; + } + const nodaApi = agentContext.context.variables.get("nodaApi"); + return nodaApi || null; + } + + private errorResult(message: string): ToolResult { + return { + content: [{ type: "text", text: message }], + isError: true, + }; + } +} + +export default NodaListNodesTool; diff --git a/packages/eko-core/src/noda/tools/update-link.ts b/packages/eko-core/src/noda/tools/update-link.ts new file mode 100644 index 00000000..803e810c --- /dev/null +++ b/packages/eko-core/src/noda/tools/update-link.ts @@ -0,0 +1,139 @@ +import { JSONSchema7 } from "json-schema"; +import { AgentContext } from "../../agent/agent-context"; +import { Tool, ToolResult } from "../../types/tools.types"; +import { NodaLinkProperties } from "../types"; + +export const TOOL_NAME = "noda_update_link"; + +/** + * Tool for updating existing links in Noda VR mind map + */ +export class NodaUpdateLinkTool implements Tool { + readonly name: string = TOOL_NAME; + readonly description: string; + readonly parameters: JSONSchema7; + readonly supportParallelCalls: boolean = true; + + constructor() { + this.description = `Updates an existing link in the Noda VR mind map. Use this to modify a link's appearance, title, or other properties. Requires the link's UUID.`; + this.parameters = { + type: "object", + properties: { + uuid: { + type: "string", + description: "The unique identifier of the link to update.", + }, + fromUuid: { + type: "string", + description: "New starting node UUID (changes the connection).", + }, + toUuid: { + type: "string", + description: "New ending node UUID (changes the connection).", + }, + title: { + type: "string", + description: "New display text for the link.", + }, + color: { + type: "string", + description: "New hex color value in #RRGGBB format.", + }, + shape: { + type: "string", + description: "New link pattern/style.", + enum: ["Solid", "Dash", "Arrows"], + }, + size: { + type: "number", + description: "New thickness from 1 to 10.", + minimum: 1, + maximum: 10, + }, + selected: { + type: "boolean", + description: "Whether to select/deselect the link.", + }, + curve: { + type: "string", + description: "New curve type.", + enum: ["none", "cdown", "cup", "sdown", "sup"], + }, + trail: { + type: "string", + description: "New trail animation effect.", + enum: ["none", "ring", "ball", "cone"], + }, + }, + required: ["uuid"], + }; + } + + async execute( + args: Record, + agentContext: AgentContext + ): Promise { + try { + const noda = this.getNodaAPI(agentContext); + if (!noda) { + return this.errorResult( + "Noda API not available. Make sure you are running inside the Noda VR environment." + ); + } + + // We need fromUuid and toUuid for the interface, but they may not change + const properties: NodaLinkProperties = { + uuid: args.uuid as string, + fromUuid: args.fromUuid as string || "", + toUuid: args.toUuid as string || "", + }; + + // Only include properties that were explicitly provided + if (args.title !== undefined) properties.title = args.title as string; + if (args.color !== undefined) properties.color = args.color as string; + if (args.shape !== undefined) + properties.shape = args.shape as NodaLinkProperties["shape"]; + if (args.size !== undefined) properties.size = args.size as number; + if (args.selected !== undefined) + properties.selected = args.selected as boolean; + if (args.curve !== undefined) + properties.curve = args.curve as NodaLinkProperties["curve"]; + if (args.trail !== undefined) + properties.trail = args.trail as NodaLinkProperties["trail"]; + + const result = await noda.updateLink(properties); + + return { + content: [ + { + type: "text", + text: JSON.stringify({ + success: true, + message: `Link "${result.uuid}" updated successfully`, + link: result, + }), + }, + ], + }; + } catch (error) { + return this.errorResult(`Failed to update link: ${error}`); + } + } + + private getNodaAPI(agentContext: AgentContext): typeof window.noda | null { + if (typeof window !== "undefined" && window.noda) { + return window.noda; + } + const nodaApi = agentContext.context.variables.get("nodaApi"); + return nodaApi || null; + } + + private errorResult(message: string): ToolResult { + return { + content: [{ type: "text", text: message }], + isError: true, + }; + } +} + +export default NodaUpdateLinkTool; diff --git a/packages/eko-core/src/noda/tools/update-node.ts b/packages/eko-core/src/noda/tools/update-node.ts new file mode 100644 index 00000000..a1dcf405 --- /dev/null +++ b/packages/eko-core/src/noda/tools/update-node.ts @@ -0,0 +1,179 @@ +import { JSONSchema7 } from "json-schema"; +import { AgentContext } from "../../agent/agent-context"; +import { Tool, ToolResult } from "../../types/tools.types"; +import { NodaNodeProperties } from "../types"; + +export const TOOL_NAME = "noda_update_node"; + +/** + * Tool for updating existing nodes in Noda VR mind map + */ +export class NodaUpdateNodeTool implements Tool { + readonly name: string = TOOL_NAME; + readonly description: string; + readonly parameters: JSONSchema7; + readonly supportParallelCalls: boolean = true; + + constructor() { + this.description = `Updates an existing node in the Noda VR mind map. Use this to modify a node's title, appearance, position, or other properties. Requires the node's UUID.`; + this.parameters = { + type: "object", + properties: { + uuid: { + type: "string", + description: "The unique identifier of the node to update.", + }, + title: { + type: "string", + description: "New display text for the node.", + }, + color: { + type: "string", + description: "New hex color value in #RRGGBB format.", + }, + opacity: { + type: "number", + description: "New opacity from 0 to 1.", + minimum: 0, + maximum: 1, + }, + shape: { + type: "string", + description: "New 3D shape for the node.", + enum: [ + "Ball", + "Box", + "Tetra", + "Cylinder", + "Diamond", + "Hourglass", + "Plus", + "Star", + ], + }, + imageUrl: { + type: "string", + description: "New image URL to display on the node.", + }, + notes: { + type: "string", + description: "New notes text for the node.", + }, + pageUrl: { + type: "string", + description: "New URL to associate with this node.", + }, + size: { + type: "number", + description: "New size from 1 to 45.", + minimum: 1, + maximum: 45, + }, + x: { + type: "number", + description: "New X coordinate.", + }, + y: { + type: "number", + description: "New Y coordinate.", + }, + z: { + type: "number", + description: "New Z coordinate.", + }, + selected: { + type: "boolean", + description: "Whether to select/deselect the node.", + }, + collapsed: { + type: "boolean", + description: "Whether to collapse/expand child nodes.", + }, + }, + required: ["uuid"], + }; + } + + async execute( + args: Record, + agentContext: AgentContext + ): Promise { + try { + const noda = this.getNodaAPI(agentContext); + if (!noda) { + return this.errorResult( + "Noda API not available. Make sure you are running inside the Noda VR environment." + ); + } + + const properties: NodaNodeProperties = { + uuid: args.uuid as string, + }; + + // Only include properties that were explicitly provided + if (args.title !== undefined) properties.title = args.title as string; + if (args.color !== undefined) properties.color = args.color as string; + if (args.opacity !== undefined) + properties.opacity = args.opacity as number; + if (args.shape !== undefined) + properties.shape = args.shape as NodaNodeProperties["shape"]; + if (args.imageUrl !== undefined) + properties.imageUrl = args.imageUrl as string; + if (args.notes !== undefined) properties.notes = args.notes as string; + if (args.pageUrl !== undefined) + properties.pageUrl = args.pageUrl as string; + if (args.size !== undefined) properties.size = args.size as number; + if (args.selected !== undefined) + properties.selected = args.selected as boolean; + if (args.collapsed !== undefined) + properties.collapsed = args.collapsed as boolean; + + // Build location if any coordinate provided + if ( + args.x !== undefined || + args.y !== undefined || + args.z !== undefined + ) { + properties.location = { + x: (args.x as number) || 0, + y: (args.y as number) || 0, + z: (args.z as number) || 0, + }; + } + + const result = await noda.updateNode(properties); + + return { + content: [ + { + type: "text", + text: JSON.stringify({ + success: true, + message: `Node "${result.uuid}" updated successfully`, + node: result, + }), + }, + ], + }; + } catch (error) { + return this.errorResult(`Failed to update node: ${error}`); + } + } + + private getNodaAPI(agentContext: AgentContext): typeof window.noda | null { + if (typeof window !== "undefined" && window.noda) { + return window.noda; + } + const nodaApi = agentContext.context.variables.get("nodaApi"); + return nodaApi || null; + } + + private errorResult(message: string): ToolResult { + return { + content: [{ type: "text", text: message }], + isError: true, + }; + } +} + +export default NodaUpdateNodeTool; diff --git a/packages/eko-core/src/noda/types.ts b/packages/eko-core/src/noda/types.ts new file mode 100644 index 00000000..72df5d74 --- /dev/null +++ b/packages/eko-core/src/noda/types.ts @@ -0,0 +1,222 @@ +/** + * Noda.io Web API Types + * Types for interacting with Noda VR mind mapping platform + * @see https://noda.io/documentation/webapi.html + */ + +/** + * Available node shapes in Noda + */ +export type NodaNodeShape = + | "Ball" + | "Box" + | "Tetra" + | "Cylinder" + | "Diamond" + | "Hourglass" + | "Plus" + | "Star"; + +/** + * Available link shapes/patterns in Noda + */ +export type NodaLinkShape = "Solid" | "Dash" | "Arrows"; + +/** + * Link curve types + */ +export type NodaLinkCurve = "none" | "cdown" | "cup" | "sdown" | "sup"; + +/** + * Link trail animation types + */ +export type NodaLinkTrail = "none" | "ring" | "ball" | "cone"; + +/** + * Location reference frame for positioning + */ +export type NodaLocationFrame = "world" | "user" | "node"; + +/** + * 3D location for node positioning + */ +export interface NodaLocation { + x: number; + y: number; + z: number; + relativeTo?: NodaLocationFrame; + referenceUuid?: string; +} + +/** + * Properties for creating/updating a node + */ +export interface NodaNodeProperties { + /** Unique identifier (numeric or string) */ + uuid?: string; + /** Display text above the node */ + title?: string; + /** Hex color value (#000000 format) */ + color?: string; + /** Opacity from 0 (transparent) to 1 (opaque) */ + opacity?: number; + /** Node shape */ + shape?: NodaNodeShape; + /** Public image URL (https protocol) */ + imageUrl?: string; + /** Free-form text field for notes */ + notes?: string; + /** Associated webpage link */ + pageUrl?: string; + /** Size from 1-45 (default: 5) */ + size?: number; + /** 3D positioning */ + location?: NodaLocation; + /** Whether the node is selected */ + selected?: boolean; + /** Whether child nodes are collapsed */ + collapsed?: boolean; +} + +/** + * Properties for creating/updating a link + */ +export interface NodaLinkProperties { + /** Unique identifier */ + uuid?: string; + /** Starting node identifier */ + fromUuid: string; + /** Ending node identifier */ + toUuid: string; + /** Display text on the link */ + title?: string; + /** Hex color value */ + color?: string; + /** Link pattern */ + shape?: NodaLinkShape; + /** Thickness from 1-10 (default: 1) */ + size?: number; + /** Whether the link is selected */ + selected?: boolean; + /** Curve type */ + curve?: NodaLinkCurve; + /** Trail animation */ + trail?: NodaLinkTrail; +} + +/** + * Response from node operations + */ +export interface NodaNodeResponse { + uuid: string; + title?: string; + color?: string; + opacity?: number; + shape?: NodaNodeShape; + imageUrl?: string; + notes?: string; + pageUrl?: string; + size?: number; + location?: NodaLocation; + selected?: boolean; + collapsed?: boolean; +} + +/** + * Response from link operations + */ +export interface NodaLinkResponse { + uuid: string; + fromUuid: string; + toUuid: string; + title?: string; + color?: string; + shape?: NodaLinkShape; + size?: number; + selected?: boolean; + curve?: NodaLinkCurve; + trail?: NodaLinkTrail; +} + +/** + * User information from Noda + */ +export interface NodaUser { + userId: string; +} + +/** + * Filter criteria for listing nodes + */ +export interface NodaNodeFilter { + uuid?: string; + title?: string; + selected?: boolean; + shape?: NodaNodeShape; +} + +/** + * Filter criteria for listing links + */ +export interface NodaLinkFilter { + uuid?: string; + fromUuid?: string; + toUuid?: string; + selected?: boolean; +} + +/** + * Event handler types for Noda events + */ +export interface NodaEventHandlers { + onNodeCreated?: (node: NodaNodeResponse) => void; + onNodeUpdated?: (node: NodaNodeResponse) => void; + onNodeDeleted?: (node: { uuid: string }) => void; + onLinkCreated?: (link: NodaLinkResponse) => void; + onLinkUpdated?: (link: NodaLinkResponse) => void; + onLinkDeleted?: (link: { uuid: string }) => void; +} + +/** + * The Noda window API interface + */ +export interface NodaAPI { + createNode: (properties: NodaNodeProperties) => Promise; + updateNode: (properties: NodaNodeProperties) => Promise; + deleteNode: (properties: { uuid: string }) => Promise; + listNodes: (filter?: NodaNodeFilter) => Promise; + createLink: (properties: NodaLinkProperties) => Promise; + updateLink: (properties: NodaLinkProperties) => Promise; + deleteLink: (properties: { uuid: string }) => Promise; + listLinks: (filter?: NodaLinkFilter) => Promise; + getUser: () => Promise; + onNodeCreated?: (node: NodaNodeResponse) => void; + onNodeUpdated?: (node: NodaNodeResponse) => void; + onNodeDeleted?: (node: { uuid: string }) => void; + onLinkCreated?: (link: NodaLinkResponse) => void; + onLinkUpdated?: (link: NodaLinkResponse) => void; + onLinkDeleted?: (link: { uuid: string }) => void; +} + +/** + * Mind map structure for import/export + */ +export interface NodaMindMap { + nodes: NodaNodeProperties[]; + links: NodaLinkProperties[]; + metadata?: { + name?: string; + description?: string; + createdAt?: string; + updatedAt?: string; + }; +} + +/** + * Extend the Window interface to include Noda API + */ +declare global { + interface Window { + noda?: NodaAPI; + } +} diff --git a/packages/eko-core/src/types/index.ts b/packages/eko-core/src/types/index.ts index cedf9b3d..6c446720 100644 --- a/packages/eko-core/src/types/index.ts +++ b/packages/eko-core/src/types/index.ts @@ -89,3 +89,23 @@ export { type AgentStreamCallback as StreamCallback, type AgentStreamMessage as StreamCallbackMessage, } from "./agent.types"; + +// Noda VR Mind Mapping Types +export type { + NodaNodeShape, + NodaLinkShape, + NodaLinkCurve, + NodaLinkTrail, + NodaLocationFrame, + NodaLocation, + NodaNodeProperties, + NodaLinkProperties, + NodaNodeResponse, + NodaLinkResponse, + NodaUser, + NodaNodeFilter, + NodaLinkFilter, + NodaEventHandlers, + NodaAPI, + NodaMindMap, +} from "../noda/types";