| import type { Edge, Node } from "@xyflow/react"; |
|
|
| export type VeniceSource = { |
| title: string; |
| url: string; |
| snippet: string; |
| }; |
|
|
| export type VeniceVisualFact = { |
| label: string; |
| detail: string; |
| }; |
|
|
| export type GenerateNodeResponse = { |
| id: string; |
| title: string; |
| imageUrl: string; |
| imagePrompt: string; |
| summary: string; |
| visualFacts: VeniceVisualFact[]; |
| query: string; |
| sources: VeniceSource[]; |
| suggestedBranches: string[]; |
| }; |
|
|
| export type VeniceNodeData = { |
| title: string; |
| imageUrl: string; |
| imagePrompt: string; |
| summary: string; |
| visualFacts: VeniceVisualFact[]; |
| query: string; |
| sources: VeniceSource[]; |
| suggestedBranches: string[]; |
| parentId?: string; |
| }; |
|
|
| export type VeniceCanvasNode = Node<VeniceNodeData, "veniceImage">; |
|
|
| export type StoredCanvas = { |
| nodes: VeniceCanvasNode[]; |
| edges: Edge[]; |
| }; |
|
|
| export const NODE_WIDTH = 760; |
| export const NODE_HEIGHT = 820; |
| export const BRANCH_X_GAP = 880; |
| export const BRANCH_Y_GAP = 560; |
|
|
| export function createCanvasNodeFromGeneration( |
| generation: GenerateNodeResponse, |
| position: { x: number; y: number }, |
| parentId?: string, |
| ): VeniceCanvasNode { |
| return { |
| id: generation.id, |
| type: "veniceImage", |
| position, |
| data: { |
| title: generation.title, |
| imageUrl: generation.imageUrl, |
| imagePrompt: generation.imagePrompt, |
| summary: generation.summary, |
| visualFacts: generation.visualFacts, |
| query: generation.query, |
| sources: generation.sources, |
| suggestedBranches: generation.suggestedBranches, |
| parentId, |
| }, |
| }; |
| } |
|
|
| export function getBranchPosition( |
| parentPosition: { x: number; y: number }, |
| siblingIndex: number, |
| ): { x: number; y: number } { |
| return { |
| x: parentPosition.x + BRANCH_X_GAP, |
| y: parentPosition.y + siblingIndex * BRANCH_Y_GAP, |
| }; |
| } |
|
|
| export function buildCanvasEdge(parentId: string, childId: string): Edge { |
| return { |
| id: `${parentId}-${childId}`, |
| source: parentId, |
| target: childId, |
| type: "smoothstep", |
| animated: true, |
| style: { |
| stroke: "#7ec8c2", |
| strokeWidth: 2, |
| }, |
| }; |
| } |
|
|
| export function normalizeStoredCanvas(stored: unknown): StoredCanvas { |
| if (!stored || typeof stored !== "object") { |
| return { nodes: [], edges: [] }; |
| } |
|
|
| const canvas = stored as Partial<StoredCanvas>; |
| return { |
| nodes: Array.isArray(canvas.nodes) ? canvas.nodes.map(normalizeStoredNode) : [], |
| edges: Array.isArray(canvas.edges) ? canvas.edges : [], |
| }; |
| } |
|
|
| function normalizeStoredNode(node: VeniceCanvasNode): VeniceCanvasNode { |
| const data = node.data || ({} as VeniceNodeData); |
| const query = data.query || data.title || "this topic"; |
|
|
| return { |
| ...node, |
| type: "veniceImage", |
| data: { |
| title: data.title || query, |
| imageUrl: data.imageUrl || "", |
| imagePrompt: data.imagePrompt || "", |
| summary: data.summary || `Generated visual search node for ${query}.`, |
| visualFacts: Array.isArray(data.visualFacts) ? data.visualFacts : [], |
| query, |
| sources: Array.isArray(data.sources) ? data.sources : [], |
| suggestedBranches: Array.isArray(data.suggestedBranches) ? data.suggestedBranches : [], |
| parentId: data.parentId, |
| }, |
| }; |
| } |
|
|