tersa

repository·main·Indexed 21 days ago

https://github.com/vercel-labs/tersa

A visual AI playground for building and running AI workflows using a node-based drag-and-drop canvas. Powered by the Vercel AI SDK Gateway, it supports multi-model integration across various providers. The project includes a comprehensive UI library featuring a Tiptap-based rich text editor with slash commands and table manipulation, as well as a customizable Dropzone component for file uploads.

Tokens
8.9K
Snippets
34
Records
42
Agent score
77%

What's inside tersa

  1. Use the Tersa visual workflow builder

    main

    Tersa allows you to build AI workflows using a drag-and-drop canvas.

    1. Add Nodes: Use the toolbar to place nodes on the canvas.
    2. Connect Nodes: Drag from a node's output port to another node's input port to create a workflow.
    3. Select Models: Choose from supported providers within the nodes.
    4. Execute: Run the workflow to process data through the connected AI models.
  2. Install Tersa locally

    main

    To set up a local development environment for Tersa, ensure you have Node.js (v20+) and PNPM installed. Follow these steps to clone the repository, install dependencies, and start the development server.

    # 1. Clone the repository
    git clone https://github.com/vercel-labs/tersa.git
    cd tersa
    
    # 2. Install dependencies
    pnpm install
    
    # 3. Run the development server
    pnpm dev
  3. How Dropzone sub-components work together

    main

    The Dropzone component uses a React Context (DropzoneContext) to share its configuration and current file state (src) with its children. This allows you to build conditional UIs that react to the upload state.

    Common Pattern: Use DropzoneEmptyState when no files are present, and DropzoneContent when files have been selected. You can use the src prop on the parent Dropzone to control this transition.

    const [files, setFiles] = useState<File[]>([]);
    
    <Dropzone 
      src={files} 
      onDrop={(accepted) => setFiles(accepted)}
    >
      {files.length === 0 ? (
        <DropzoneEmptyState />
      ) : (
        <DropzoneContent />
      )}
    </Dropzone>
  4. Use Slash Commands for quick formatting

    main

    The editor features a slash command system. Typing / triggers a suggestion menu that allows users to quickly insert nodes like Headings, Lists, Tables, or Code Blocks.

    The menu is powered by Fuse.js for fuzzy searching through defaultSlashSuggestions.

    Default slash commands include:

    • Text (paragraph)
    • To-do List (taskList)
    • Heading 1, Heading 2, Heading 3
    • Bullet List and Numbered List
    • Quote (blockquote)
    • Code (codeBlock)
    • Table (inserts a 3x3 table with a header row)
  5. Tersa model type definitions for Gateway models

    main

    When using the Gateway provider, models are returned in specialized formats based on their modality. All models include a label, a chef (derived from the model ID prefix), and a priceIndicator.

    Text Models (TersaTextModel)

    Text models include a getCost function that calculates cost based on input and output tokens: getCost({ input: number; output: number }) => number.

    Image Models (TersaImageModel)

    Image models include a getCost function that returns a flat cost per image: getCost() => number.

    Video Models (TersaVideoModel)

    Video models include a getCost function that returns a flat cost per video: getCost() => number.

    Price Brackets

    The priceIndicator property uses the PriceBracket type to indicate relative cost compared to other models in the set:

    • lowest (bottom 20th percentile)
    • low (up to 40th percentile)
    • high (above 60th percentile)
    • highest (above 80th percentile)
    • undefined (middle 20%, considered 'on par')
  6. Define TextNodeProps for Text nodes

    main

    The TextNodeProps interface defines the data structure required to render a text node. It supports both plain text and structured Tiptap JSON content.

    Properties

    • id: string - A unique identifier for the node.
    • type: string - The node type.
    • data: object - The payload of the node, which can include:
      • text: string - Plain text content.
      • content: JSONContent - Tiptap-formatted JSON content.
      • generated: object - Contains text: string if the content was AI-generated.
      • model: string - The name of the model used to generate the text.
      • instructions: string - The instructions used to generate the text.
      • updatedAt: string - Timestamp of the last update.
  7. Extract text from text nodes using getTextFromTextNodes

    main

    Use getTextFromTextNodes to retrieve all text content from a collection of XYFlow nodes. It extracts both the original text from standard text nodes and the generated?.text from nodes marked as generated. The function returns an array of non-empty strings.

    import { getTextFromTextNodes } from '@/lib/xyflow';
    import type { Node } from '@xyflow/react';
    
    const textContent = getTextFromTextNodes(nodes as Node[]);
  8. Show an empty state with DropzoneEmptyState

    main

    The DropzoneEmptyState component displays a placeholder UI when no files are currently selected (when src is empty).

    • If children are provided, they are rendered instead.
    • If no children are provided, it renders an upload icon, a prompt (e.g., "Upload a file"), and an automatic caption based on the Dropzone configuration:
      • Lists accepted file types if accept is set.
      • Displays size constraints (e.g., "between 10 KB and 1 MB") if minSize and maxSize are set.
      • Displays minimum or maximum size limits if only one is provided.
    • It must be used within a Dropzone component.
    <Dropzone accept={{ 'image/*': ['.png'] }}>
      <DropzoneEmptyState />
    </Dropzone>
  9. Access the registry of available node types

    main

    The nodeTypes object serves as the central registry for all available node components used within the Tersa workflow canvas. It maps unique string identifiers to their corresponding React component implementations. This registry is used by the canvas engine to determine which component to render when a specific node type is instantiated in a workflow.

    import { nodeTypes } from './components/nodes';
    
    // nodeTypes contains:
    // - 'image': ImageNode
    // - 'text': TextNode
    // - 'drop': DropNode
    // - 'video': VideoNode
  10. Load canvas state from localStorage with loadCanvas

    main

    Use loadCanvas to retrieve the previously saved canvas state from localStorage. It looks for data stored under the key tersa-canvas.

    • If data exists and is valid, it returns a CanvasData object containing nodes and edges.
    • If no data is found, or if the stored data is corrupted/invalid, it returns null.
    import { loadCanvas } from './lib/canvas-storage';
    import type { CanvasData } from './lib/canvas-storage';
    
    const savedData = loadCanvas();
    
    if (savedData) {
      const { nodes, edges } = savedData;
      // Apply nodes and edges to your canvas
    } else {
      // No saved state found
    }
  11. Use built-in Bubble Menu formatting buttons

    main

    The editor exports several pre-configured bubble menu buttons for common formatting tasks. These components automatically handle the editor commands and active state detection.

    Text Formatting:

    • EditorFormatBold: Toggles bold.
    • EditorFormatItalic: Toggles italic.
    • EditorFormatStrike: Toggles strikethrough.
    • EditorFormatCode: Toggles inline code.
    • EditorFormatUnderline: Toggles underline.
    • EditorFormatSubscript: Toggles subscript.
    • EditorFormatSuperscript: Toggles superscript.
    • EditorClearFormatting: Clears all nodes and marks.

    Node Formatting:

    • EditorNodeText: Sets node to paragraph.
    • EditorNodeHeading1, EditorNodeHeading2, EditorNodeHeading3: Sets heading levels.
    • EditorNodeBulletList: Toggles bullet list.
    • EditorNodeOrderedList: Toggles ordered list.
    • EditorNodeTaskList: Toggles to-do list.
    • EditorNodeQuote: Toggles blockquote.
    • EditorNodeCode: Toggles code block.
    • EditorNodeTable: Inserts a table.

    Note: Most of these accept a hideName prop (boolean) to toggle whether the text label is displayed alongside the icon.