Astrsk AI Storytelling and Roleplaying Platform

repository·develop·Indexed 41 days ago

https://github.com/astrskai/astrsk

A local-first, privacy-centric AI storytelling platform providing control over AI agents, conversation flows, and character definitions. Features a cross-platform architecture including an Electron app and a PWA built with Feature-Sliced Design (FSD), PGlite for WASM-based PostgreSQL storage, and Drizzle ORM. Includes specialized tools like LegacyCharacterRecovery for data migration and useFlowMutations for managing complex conversation flow states.

Tokens
51.4K
Snippets
171
Records
235
Agent score
99%

What's inside Astrsk

  1. Overview of astrsk core capabilities

    develop

    astrsk is an AI storytelling platform focused on agent control and immersive roleplaying. Key features include:

    • AI Agent Control: Design agents with custom prompts, output schemas, and response formatting. Supports 10+ providers (OpenAI, Anthropic, Google AI, DeepSeek, Ollama, xAI, etc.) and character card imports (v2/v3).
    • Visual Flow Editor: A drag-and-drop interface for designing complex conversation flows and branching narratives.
    • Local-First Privacy: All data is stored locally on your device using PGlite (PostgreSQL WASM). No accounts or data collection required.
    • Cross-Platform: Available as a Progressive Web App (PWA) and native desktop apps (Windows, macOS, Linux).
  2. Core Capabilities of astrsk.ai

    develop

    astrsk.ai is an AI storytelling platform with the following core features:

    • AI Agent Control: Design agents with custom prompts, output schemas, and response formatting. Supports importing character cards (v2/v3) and over 10 AI providers (OpenAI, Anthropic, Google AI, DeepSeek, Ollama, xAI, etc.).
    • Visual Flow Editor: A drag-and-drop interface for creating complex conversation flows with branching narratives and conditional logic.
    • Local-First Privacy: All data is stored locally on your device using PGlite (PostgreSQL compiled to WebAssembly). No accounts or data collection required.
    • Cross-Platform Support: Available as a Progressive Web App (PWA) for browsers and mobile, and as a native desktop app for Windows, macOS, and Linux.
  3. Understand the Initialization UI behavior

    develop

    The InitializationScreen is a transient UI component that appears only if the app initialization process takes longer than 1 second. It operates in two distinct modes depending on the success of the initialization steps:

    Normal Progress (Simple Mode)

    Designed for regular users to avoid information overload. It groups the 18 technical steps into 3 high-level categories:

    1. Database Setup
    2. Services Initialization
    3. Data Loading

    Group states are indicated by:

    • pending: Gray
    • running: Blue Loader2 (rotating animation)
    • success: Green CheckCircle2

    Error Mode (Detailed Mode)

    Triggered when a step fails. The UI shifts to provide debugging information:

    • The logo animation stops.
    • The progress bar is maintained.
    • Failed step details are displayed, including the step number, label, error message (in a red box), and duration.
    • Action buttons allow the user to "Retry Initialization" (page refresh) or "Copy Error Details" (to clipboard).
  4. How the Query Factory Pattern works

    develop

    To avoid scattered and error-prone cache keys, the project uses a Hierarchical Query Factory. This pattern centralizes all query keys into a single, type-safe object and provides corresponding queryOptions for fetching data.

    This approach ensures:

    • Single source of truth: Keys are defined once.
    • Type-safety: TypeScript autocomplete for keys and query functions.
    • Hierarchical structure: Keys follow a predictable path (e.g., flows → detail → nodes), allowing for granular or broad cache invalidation.
    • No typos: Prevents bugs caused by manual string/array key entry in components.
    // app/queries/flow/query-factory.ts
    export const flowKeys = {
      all: ['flows'] as const,
      lists: () => [...flowKeys.all, 'list'] as const,
      list: (filters?: FlowFilters) => [...flowKeys.lists(), { filters }] as const,
      details: () => [...flowKeys.all, 'detail'] as const,
      detail: (id: string) => [...flowKeys.details(), id] as const,
      nodes: (flowId: string) => [...flowKeys.detail(flowId), 'nodes'] as const,
      node: (flowId: string, nodeId: string) => [...flowKeys.nodes(flowId), nodeId] as const,
    }
    
    export const flowQueries = {
      detail: (id: string) => queryOptions({
        queryKey: flowKeys.detail(id),
        queryFn: async () => {
          const flow = await FlowService.getById(id);
          if (!flow) throw new Error('Flow not found');
          return flow;
        },
        staleTime: 1000 * 10,
      }),
    
      nodes: (flowId: string) => queryOptions({
        queryKey: flowKeys.nodes(flowId),
        queryFn: async () => FlowService.getNodes(flowId),
        staleTime: 1000 * 5,
      }),
    }
  5. Understand the Astrsk PWA architecture (FSD)

    develop

    The Astrsk PWA is built using Feature-Sliced Design (FSD), which organizes the codebase into layers based on responsibility and complexity. The hierarchy is as follows:

    • app/: App initialization, providers, and services.
    • pages/: Route-level components (e.g., home, sessions, settings, assets).
    • widgets/: Reusable UI blocks that combine features and entities.
    • features/: User interactions and business logic (e.g., character, flow, scenario, session, vibe).
    • entities/: Business domain models (e.g., agent, card, flow, session).
    • shared/: The foundation layer containing the UI kit (via shadcn/ui), custom hooks, Zustand stores, and utilities.
    • db/: Database schemas and migrations managed via Drizzle.
    • routes/: Route definitions using TanStack Router.
  6. Success criteria for Flow Query migration

    develop

    The migration is considered successful when the following criteria are met:

    • All components migrated: Every component in the system has transitioned to the new Flow Query model.
    • Performance metrics met: The new system meets or exceeds the established performance benchmarks.
    • No regression in functionality: The migration has not introduced any bugs or broken existing features.
    • Team trained on new patterns: The development team is proficient in using the new Flow Query patterns and infrastructure.
  7. How semantic tokens and themes work

    develop

    The design system uses semantic tokens to provide automatic dark/light theme support.

    • Semantic Tokens (Recommended): Use tokens like --bg-surface, --fg-default, and --border-default to ensure components adapt to the current theme.
    • Primitive Tokens (Avoid): Avoid using raw color tokens like --color-neutral-900 directly in your UI components.
    • Theme Switching: The system uses a dark theme by default. To switch to a light theme, add the .light class to your <html> element.
    <html class="light">
      <!-- Light theme content -->
    </html>
    /* Primitive (avoid direct use) */
    var(--color-neutral-900)
    
    /* Semantic (recommended) */
    var(--bg-surface)
    var(--fg-default)
    var(--border-default)
  8. How the local-first database works

    develop

    The Astrsk PWA uses a local-first architecture powered by PGlite (PostgreSQL compiled to WASM).

    Key characteristics:

    • Storage: Data is stored in the browser's OPFS (Origin Private File System).
    • ORM: Drizzle ORM is used for full SQL support.
    • Migrations: Database migrations are applied automatically when the application starts.
  9. Migrate to Hierarchical Flow Queries (v2)

    develop

    The project is migrating from monolithic flow queries (where flowQueries.detail() fetches the entire object) to a hierarchical, granular query structure. This change aims to reduce race conditions, enable efficient partial invalidation, and minimize unnecessary re-renders by allowing components to subscribe to specific segments of a flow.

    Key Principles

    • Granularity: Every piece of data has its own unique query key.
    • Hierarchy: Query keys follow the domain structure (e.g., detail -> graph -> nodes -> byId).
    • Selective Invalidation: Only invalidate the specific queries affected by a change rather than the entire flow.
    • Optimistic Updates: Update the UI immediately and roll back if the API call fails.
  10. Common TanStack Query usage patterns

    develop

    Create Entity

    Use onSuccess in a mutation to optimistically update lists and detail caches.

    Delete Entity

    Use onSuccess to removeQueries for the specific item and update parent lists by filtering the existing cache.

    Dependent Queries

    Use the enabled option to ensure a query only runs once the data from a previous query is available.

    Parallel Queries

    Use the useQueries hook to fetch multiple independent datasets simultaneously.

    // Dependent Queries
    const FlowNodes = ({ flowId }: { flowId: string }) => {
      const { data: flow } = useQuery(flowQueries.detail(flowId));
    
      const { data: nodes } = useQuery({
        ...flowQueries.nodes(flowId),
        enabled: !!flow, // ⭐ Dependent query
      });
    
      return <div>{nodes?.length} nodes</div>;
    };
    
    // Parallel Queries
    const FlowDashboard = ({ flowId }: { flowId: string }) => {
      const queries = useQueries({
        queries: [
          flowQueries.detail(flowId),
          flowQueries.nodes(flowId),
          flowQueries.metadata(flowId),
        ],
      });
      // ...
    };
  11. Recover lost character data using LegacyCharacterRecovery

    develop

    The LegacyCharacterRecovery service is designed to recover character and scenario data when migrations from old database schemas to new ones fail. It detects data in deprecated tables (character_cards, plot_cards, cards) and migrates it to the new tables (characters, scenarios).

    Safety and Behavior

    • Idempotent: Safe to run multiple times.
    • Non-destructive: It never deletes old tables.
    • Conflict handling: Uses ON CONFLICT DO NOTHING to prevent duplicate entries.

    When to use

    • Use if users report missing characters in their library or sessions after an upgrade.
    • Use if migration logs show errors related to the character_cards table.
    • Do not use if the migration completed successfully (old tables will have been dropped) or if the user simply needs to adjust search filters.
    import { LegacyCharacterRecovery } from "@/app/recovery-services";
    
    const recovery = new LegacyCharacterRecovery();
    
    // Check status
    const report = await recovery.checkLegacyData();
    
    if (report.canRecover) {
      // Recover all
      await recovery.recoverAll();
    }
  12. How the Three-Tier Initialization System works

    develop

    The astrsk PWA uses a sequential, three-tier initialization process on startup to ensure the database is ready, services are linked, and data is loaded into memory. The tiers are organized by their execution frequency and persistence:

    1. Tier 1: Database Migration (One-Time)

      • Purpose: Creates table structures in the PGlite database.
      • Persistence: Persisted in IndexedDB.
      • Execution: Runs only on the first initialization or when the database is missing (checked via isDatabaseInitialized()). If the database is already initialized, migration steps are marked as instant success.
    2. Tier 2: Services Initialization (Every Time)

      • Purpose: Handles dependency injection and linking repositories/functions.
      • Persistence: In-memory only (not persisted).
      • Execution: Runs on every app startup. This is necessary because service references (e.g., FlowService.init(AgentService.agentRepo, ...)) are in-memory and do not persist across page reloads.
    3. Tier 3: Stores Initialization (Every Time)

      • Purpose: Loads data from the database into in-memory caches.
      • Persistence: In-memory only (not persisted).
      • Execution: Runs on every app startup to repopulate memory caches (e.g., loading API connections, backgrounds, or sessions into the app state).
    main.tsx (Orchestration)
      ├── 1. Database Migration (ONE-TIME)
      │   └── migrate() - Runs only on first initialization or when DB is missing
      |
      ├── 2. Services Initialization (EVERY TIME)
      │   └── initServices() - Runs on every app startup (in-memory dependency injection)
      |
      └── 3. Stores Initialization (EVERY TIME)
          └── initStores() - Runs on every app startup (loads data from DB into memory)