Facets Architecture Guide

This guide provides a comprehensive understanding of the Facets system in Infactory - a sophisticated rendering architecture that transforms structured data from query programs into interactive UI components.

Overview

The Facets system is the heart of Infactory’s dynamic UI rendering. It creates a seamless bridge between backend data processing and frontend visualization, enabling intelligent, data-driven interfaces that adapt based on the type of data being displayed.

High-Level Architecture

The Facets system follows a clear data flow from backend processing to frontend rendering:

Core Concepts

1. Structured Data

Structured Data is the universal data format that flows through the Infactory system. It serves as the contract between backend and frontend, ensuring consistent data handling across language boundaries.

Key Components:

  • Items: The actual data (DataFrames, scalars, dictionaries, lists)
    • Keyed by store object names (e.g., “MAIN”, “RELATED”)
    • Each item has a specific type defined in the data structure
  • Metadata: Additional information about the data
    • Free-form dictionary with defined keys
    • Contains result codes, IDs for streaming
    • Includes rendering hints that determine facet selection

Implementation:

// TypeScript implementation (frontend)
interface StructuredData {
  items: {
    [key: string]: DataFrame | Scalar | Dict | List
  };
  metadata: {
    rendering_hint?: string;
    result_code?: string;
    stream_id?: string;
    [key: string]: any;
  };
}
# Python implementation (backend)
class StructuredData:
    def __init__(self):
        self.items = {}  # Store objects from query programs
        self.metadata = {}  # Additional data and hints

2. Streaming and Accumulation

The system supports real-time data streaming through an accumulator pattern:
  • Accumulator Pattern: Start with initial structured data, then accumulate changes
  • Merge Logic: Both backend and frontend know how to merge overlays
  • Incremental Updates: Send only changes, not full datasets
  • Consistency: Guaranteed order and merge behavior
  • Accumulator Pattern: Start with initial structured data, then accumulate changes
  • Merge Logic: Both backend and frontend know how to merge overlays
  • Incremental Updates: Send only changes, not full datasets
  • Consistency: Guaranteed order and merge behavior

3. Rendering Hints

Rendering hints are the key mechanism for selecting which facet renders specific data:
// Example rendering hints
"dataframe"           // Simple DataFrame display
"etf_data"           // ETF-specific rendering
"video_search"       // Video search results
"text_revision"      // Text comparison view
"news_articles"      // News article layout

Facet System Architecture

Facet Compositions

Facet Compositions define how different facets work together for a specific use case:
interface FacetComposition {
  modality: 'chat' | 'search' | 'landing_page';
  showMessageIcons: boolean;
  tabs: Tab[];
  responseFacets: {
    [rendering_hint: string]: FacetStackItem[];
  };
}

Key Elements:

  1. Workshop Variants: Different UI configurations for different use cases
  2. Modality: Determines the overall interaction pattern
  3. Tabs: Different request types and API templates
  4. Response Facets: Stack of facets for each rendering hint

Two Types of Facets

The Facets system has two distinct categories of components:

1. Control Facets

Interactive components that can trigger API calls Characteristics:
  • Accept user input
  • Issue API calls to backend
  • Update dialog state
  • Manage request/response cycle

2. Renderer Facets

Display components that visualize structured data Characteristics:
  • Parse and display structured data
  • No direct API interaction
  • Focus on visualization
  • Support various data types

Facet Selection Process

The system automatically selects the appropriate facet based on data type:

Dialog System

Architecture Overview

The Dialog system manages conversations and data flow:

Dialog Components

Dialogues

  • Represent complete conversations
  • Belong to a project
  • Contain multiple messages
  • Track conversation state

Dialog Messages

  • Individual messages in a dialogue
  • Contains sdjson field with structured data
  • References a turn (User/Tool/Assistant)
  • Ordered sequence

Dialog Turns

  • User Turn: User’s input/question
  • Tool Turn: API call or computation
  • Assistant Turn: Results delivered to user

Modalities

Different ways to render and interact with dialogs:

1. Chat Modality

{
  modality: 'chat',
  // Shows all messages in continuous flow
  // Input at bottom
  // Message history visible
}

2. Search Modality

{
  modality: 'search',
  // Shows only latest result
  // Input at top
  // Previous results in back/forward navigation
}

3. Landing Page Modality (New)

{
  modality: 'landing_page',
  // Custom layout for landing pages
  // Supports anonymous sessions
  // Optimized for first-time users
}

Implementation Guide

File Structure

Key Directories:
  • base/ - Abstract base classes that all facets inherit from
  • implementations/ - 20+ concrete facet implementations for specific data types
  • areas/ - High-level dialog view components that orchestrate facets
  • facet-compositions.ts - Central registry mapping variants to facet stacks

Creating a New Facet

Step 1: Define the Facet Class

// implementations/my_custom_renderer_facet.tsx
import { RendererFacet } from '../base/base_facet';
import { StructuredData } from '@/lib/structured_data';

export class MyCustomRendererFacet extends RendererFacet {
  name = 'my_custom_renderer';

  render(data: StructuredData) {
    // Access data from structured data
    const mainData = data.items['MAIN'];

    return (
      <div className="my-custom-facet">
        {/* Render your UI */}
      </div>
    );
  }
}

Step 2: Register in Facet Compositions

// facet-compositions.ts
import { MyCustomRendererFacet } from './implementations/my_custom_renderer_facet';

// Add factory function
export const MyCustomRenderer = (config: Partial<ResponseFacetStackItem> = {}) => {
  const { params, ...stackConfig } = config;
  return {
    component: new MyCustomRendererFacet(params),
    ...stackConfig
  };
};

// Add to a composition
const myComposition: FacetComposition = {
  modality: 'search',
  responseFacets: {
    'my_custom_hint': [
      UserMessageRenderer(),
      MyCustomRenderer(),  // Your new facet
      RawDataRenderer()
    ]
  }
};

Step 3: Set Rendering Hint in Query Program

# In your query program
def run(self, **kwargs):
    # Process data
    result = process_data()

    # Set structured data with rendering hint
    structured_data = StructuredData()
    structured_data.items['MAIN'] = result
    structured_data.metadata['rendering_hint'] = 'my_custom_hint'

    return structured_data

Creating a New Modality

For implementing a new interaction pattern (like landing page):

Step 1: Define the Modality

// Add to facet-compositions.ts
export const landingPageComposition: FacetComposition = {
  modality: 'landing_page',  // New modality
  showMessageIcons: false,
  tabs: [{
    name: 'search',
    controls: [
      NavigationControl(),
      ConversationalControl()
    ],
    apiTemplate: 'video_search'
  }],
  responseFacets: {
    // Define facet stacks
  }
};

Step 2: Create Dialog View Component

// areas/dialog_landing_view.tsx
export function DialogLandingView({
  tree,
  variant
}: DialogViewProps) {
  // Implement landing page specific rendering
  return (
    <div className="landing-view">
      {/* Custom layout */}
    </div>
  );
}

Step 3: Update Page Component

// In your page component
if (modality === 'landing_page') {
  return <DialogLandingView tree={tree} variant={variant} />;
}

Practical Examples

Example 1: Video Search Implementation

The video search feature demonstrates the complete facet flow:
// 1. User enters search query
// 2. Query program processes request
// 3. Returns structured data with video results

{
  items: {
    MAIN: videoDataFrame,
    RELATED: relatedContent
  },
  metadata: {
    rendering_hint: 'video_search',
    total_results: 42
  }
}

// 4. Facet composition selects VideoRendererFacet
// 5. Facet renders video grid with results

Example 2: Anonymous Session Handling

For landing pages with anonymous users:
// 1. Create orphaned/anonymous project
const anonProject = await createAnonymousProject();

// 2. Create dialogue in anonymous project
const dialogue = await createDialogue(anonProject.id);

// 3. Render with landing page modality
<DialogLandingView
  dialogueId={dialogue.id}
  modality="landing_page"
/>

// 4. On user login, sync anonymous project
await syncAnonymousProject(anonProject.id, userId);

Best Practices

1. Facet Development

  • Keep facets focused on single responsibility
  • Use TypeScript types for structured data
  • Handle loading and error states
  • Implement proper cleanup in useEffect

2. Streaming Optimization

  • Use accumulator pattern for large datasets
  • Send minimal updates in streams
  • Implement debouncing for rapid updates
  • Handle connection interruptions gracefully

3. Modality Selection

  • Chat: For conversational, multi-turn interactions
  • Search: For query-response with history
  • Landing Page: For public, anonymous access

4. Performance

  • Lazy load heavy facets
  • Use React.memo for expensive renders
  • Implement virtual scrolling for large datasets
  • Cache structured data when appropriate

Troubleshooting

Common Issues

Facet Not Rendering

  1. Check rendering hint matches exactly
  2. Verify facet is registered in composition
  3. Confirm workshop variant is correct
  4. Check structured data format

Streaming Not Working

  1. Verify accumulator initialization
  2. Check stream IDs match
  3. Ensure merge logic is correct
  4. Monitor WebSocket connection

Wrong Modality Display

  1. Verify modality in facet composition
  2. Check page component logic
  3. Ensure dialog state is correct

Advanced Topics

Custom Accumulator Logic

class CustomAccumulator {
  accumulate(base: StructuredData, update: StructuredData) {
    // Custom merge logic
    return mergedData;
  }
}

Dynamic Facet Loading

const DynamicFacet = lazy(() =>
  import('./implementations/heavy_facet')
);

Cross-Facet Communication

// Use context or event system
const FacetContext = createContext<FacetState>();

// In parent
<FacetContext.Provider value={sharedState}>
  {facets}
</FacetContext.Provider>

Summary

The Facets architecture provides a powerful, flexible system for rendering dynamic UIs based on structured data. By understanding the flow from Query Programs through Structured Data to Facet rendering, developers can create sophisticated, data-driven interfaces that adapt intelligently to different types of content and user interactions. Key takeaways:
  • Structured Data is the universal format bridging backend and frontend
  • Facets are either Controls (interactive) or Renderers (display)
  • Modalities define overall interaction patterns
  • The Dialog system manages conversation flow and state
  • Facet Compositions tie everything together based on rendering hints
This architecture enables Infactory to provide consistent, intelligent interfaces across different data types and use cases, from simple data tables to complex video search interfaces and interactive landing pages.