Syntux Documentation

repository·master·Indexed 19 days ago

https://github.com/puffinsoft/syntux

Syntux is a generative UI library for the web (version 1.0.0) that uses a React Interface Schema (RIS)—a flat JSON-DSL—to stream and render dynamic UIs. It features the GeneratedUI component for displaying data, the useSyntux hook for reactive updates, and the @getsyntux/cli for project initialization and automatic generation of component definitions from TypeScript files.

Tokens
5.7K
Snippets
20
Records
24
Agent score
67%

What's inside Syntux

  1. How the React Interface Schema (RIS) works

    master

    syntux does not generate HTML or JSX source code. Instead, it generates a React Interface Schema (RIS), which is a JSON-DSL representation of the UI.

    Key characteristics of RIS:

    • Flat JSON List: Unlike older versions that used deep trees, the current RIS is a flat list of JSON objects, where each object contains an id and a parentId. This allows the UI to be built progressively (streamed).
    • Value Binding: The schema does not hardcode values. Instead, it uses a $bind mechanism to link UI elements to properties of the provided value.
    • Built-in Iterators: It uses special types like __ForEach__ to handle arrays efficiently without duplicating data.

    Example RIS structure:

    {"id":"loop_1", "parentId":"root", "type":"__ForEach__", "props":{"source":"authors"}}
    {"id":"card_1", "parentId":"loop_1", "type":"div", "props":{"className":"card"}, "content": {"$bind": "$item.name"}}
  2. Define a UISchema structure

    master

    A UISchema is the core data structure used to represent a UI tree. It consists of a componentMap containing all nodes, a childrenMap defining the hierarchy, and a root node pointer. Each node in the componentMap is a SchemaNode which includes an id, parentId, type, optional props, and optional content (which can support a "$bind": string pattern for data binding).

    export type SchemaNode = {
        id: string;
        parentId: string | null;
        type: string;
        props?: Record<string, any>;
        content?: any | { "$bind": string };
    }
    
    export type ComponentMap = Record<string, SchemaNode>;
    export type ChildrenMap = Record<string, string[]>;
    
    export type UISchema = {
        componentMap: ComponentMap;
        childrenMap: ChildrenMap;
        root: SchemaNode | null;
    }
  3. Use the GeneratedUI component to display data

    master

    The core of syntux is the GeneratedUI component. You provide it with a value (the data to display), an endpoint (the API route that handles generation), and an optional hint to guide the UI design.

    If you are passing a large array as a value, use the skeletonize property to optimize performance.

    import { GeneratedUI } from 'getsyntux/client';
    
    const valueToDisplay = {
        "username": "John",
        "email": "john@gmail.com",
        "age": 22
    }
    
    <GeneratedUI
        endpoint="/api/syntux"
        value={valueToDisplay}
        hint="UI should look like..."   
    />
  4. Configure custom components for GeneratedUI

    master

    You can instruct syntux to use your own React components instead of standard HTML elements by passing a components array to the GeneratedUI component. Each object in the array must include:

    • name: The name of the component.
    • props: A string representation of the expected props (e.g., '{ title: string, body: string }').
    • component: The actual React component reference.
    • context (optional): A description of what the component does to help the generator.

    Note: All custom components must be marked with "use client".

    You can automatically generate these component definitions using the CLI: npx @getsyntux/cli generate-defs <component.tsx>.

    import { GeneratedUI } from 'getsyntux/client';
    import { Card, Avatar } from '@/components/ui';
    
    export default function Page() {
      const value = { username: 'John', email: 'john@gmail.com', avatar: '/john.png' };
    
      return (
        <GeneratedUI
          endpoint="/api/syntux"
          value={value}
          hint="use custom components when possible"
    
          components={[
            {
              name: 'Card',
              props: '{ title: string, body: string }',
              component: Card,
            },
            {
              name: 'Avatar',
              props: '{ src: string, alt: string }',
              component: Avatar,
              context: 'Displays a circular profile image.',
            },
          ]}
        />
      );
    }
  5. Implement reactive UI updates with useSyntux

    master

    To update the UI dynamically in response to user actions, you must provide a rerenderEndpoint to the GeneratedUI component. You can then use the useSyntux hook to manage the value and trigger regenerations.

    When calling setValue, you can pass an options object with regenerate: true to force the UI to be re-generated based on the new hint or value. If regenerate is set to false, the update is treated as static.

    // 1. Configure the component with a rerenderEndpoint
    <GeneratedUI
      endpoint="/api/syntux"
      rerenderEndpoint="/api/syntux/rerender"
      value={value}
      hint="display as a profile card"
    />
    
    // 2. Use the hook in a client component to trigger updates
    "use client";
    import { useSyntux } from 'getsyntux/client';
    
    export default function CustomComponent() {
      const { value, setValue } = useSyntux();
    
      return (
        <button
          onClick={() => {
            setValue(value, {
                regenerate: true,
                hint: "Change the style to be more..."
            });
          }}
        >
          Update UI!
        </button>
      );
    }
  6. Generate component definitions with @getsyntux/cli

    master

    You can use the @getsyntux/cli to automatically generate the components array configuration required by the GeneratedUI component. This saves you from manually writing out the name, props string, and context for your existing React components.

    npx @getsyntux/cli generate-defs <component.tsx>
  7. Use the GeneratedUI component

    master

    The GeneratedUI component is the primary interface for rendering user interface sections generated by an LLM. It takes a data value and an endpoint, then communicates with a Syntux handler to stream and render a dynamic UI based on a schema.

    Required Props

    • value: The data (object, primitive, or array) that the LLM should use to generate the UI.
    • endpoint: The relative URL endpoint created with createSyntuxHandler.

    Key Features

    • Custom Instructions: Use the hint prop to provide specific instructions to the LLM for this specific UI section.
    • Component Restriction: Pass an array of components (either component names as strings or SyntuxComponent objects) to restrict the LLM to a specific set of allowed UI elements.
    • Caching: To avoid redundant API calls, provide a cached string (the pre-generated schema) and an onGenerate callback to capture the schema for future use.
    • Skeletonization: Enable skeletonize for large arrays or untrusted input to compress the value before sending it to the LLM.
    • Regeneration: Provide a rerenderEndpoint to allow the UI to be regenerated via a different endpoint.
    • Animations: Use the animate prop to configure on-mount animations for the generated components.
    import { GeneratedUI } from 'getsyntux';
    
    function MyComponent({ data }) {
      return (
        <GeneratedUI
          value={data}
          endpoint="/api/generate-ui"
          hint="Render this data as a dashboard with cards."
          components={['Card', 'Button', 'Chart']}
          placeholder={<LoadingSpinner />}
          onGenerate={(schema) => console.log('Generated schema:', schema)}
          animate={{ type: 'fade-in' }}
        />
      );
    }
  8. Parse streaming UI schema data with ResponseParser

    master

    The ResponseParser class is a utility for incrementally assembling a UISchema from a stream of data chunks (deltas). It handles multiline input by buffering data until a newline character is encountered, ensuring that only complete JSON objects (representing SchemaNodes) are processed.

    To use it, call addDelta(delta) for each incoming chunk. If addDelta returns true, it means a complete line (and thus a complete node) was processed and the schema has been updated. When the stream ends, call finish() to process any remaining data in the buffer.

    import { ResponseParser } from './ResponseParser';
    
    const parser = new ResponseParser();
    
    // Simulating streaming chunks
    parser.addDelta('{"id": "root", "parentId": null, "type": "container"}\n');
    parser.addDelta('{"id": "child1", "parentId": "root", "type": "button"}'); // No newline yet
    
    // Finalize the parsing
    parser.finish();
    
    const finalSchema = parser.schema;
    console.log(finalSchema);
  9. Create a UI generation handler with createSyntuxHandler

    master

    Use createSyntuxHandler to create a framework-agnostic HTTP handler for initial UI generation requests. This handler expects a JSON request body containing value, hint, components, and skeletonize. It uses the provided LanguageModel and model specification (spec) to stream a response.

    Request Body Schema:

    • value: (string/any) The input value for generation.
    • hint: (string) A hint or instruction for the model.
    • components: (array) The list of available components.
    • skeletonize: (boolean) Whether to use skeleton loading states.
    import { createSyntuxHandler } from './path-to-syntux/server';
    import { openai } from '@ai-sdk/openai';
    
    const handler = createSyntuxHandler({
      model: openai('gpt-4o'),
      spec: 'Your model specification string',
      onGenerate: (schema) => console.log('Generated schema:', schema),
    });
    
    // Example usage in a generic fetch handler:
    // const response = await handler(request);
  10. Define custom SyntuxComponent metadata

    master

    When providing context for custom components (used in AllowedComponents and ComponentContext), use the SyntuxComponent type. This combines ComponentMetadata (name, props description, and optional context description) with the actual React.ComponentType.

    export type ComponentMetadata = {
        name: string;
        props: string;
        context?: string;
    }
    
    export type SyntuxComponent = ComponentMetadata & {
        component: React.ComponentType<any>;
    }
  11. Create a UI rerender handler with createSyntuxRerenderHandler

    master

    Use createSyntuxRerenderHandler to create a framework-agnostic HTTP handler specifically for UI rerendering requests. This handler is used when you need to modify an existing UI based on new context or user input. It expects a JSON request body containing context, existing, and hint.

    Request Body Schema:

    • context: (string) The current context.
    • existing: (string) The existing UI/schema to be modified.
    • hint: (string) The user's new instruction or context.
    import { createSyntuxRerenderHandler } from './path-to-syntux/server';
    import { openai } from '@ai-sdk/openai';
    
    const rerenderHandler = createSyntuxRerenderHandler({
      model: openai('gpt-4o'),
      spec: 'Your model specification string',
      onGenerate: (schema) => console.log('Rerendered schema:', schema),
    });
    
    // Example usage in a generic fetch handler:
    // const response = await rerenderHandler(request);