React
A Next.js web dashboard for managing and interacting with OpenClaw, an AI agent platform. It provides a GUI frontend that proxies to the OpenClaw CLI/backend, allowing users to chat with AI agents, manage skills, channels, cron jobs, and sessions through a browser-based interface instead of the command line.
Tech Stack
Key Features
- AI Agent Chat Interface
- Skills Management
- Agents & Channels Overview
- Cron Job Monitoring
- Gateway Connection Settings
- Tabbed Navigation App Shell
- Theme & Appearance Management
Entry Points
Start here when working with this codebase
src/app/page.tsxThe root Next.js page component that dynamically loads the main application shell
Start here when debugging initial page load, understanding the app bootstrap sequence, or modifying what renders at the root URL
src/components/app-shell.tsxOrchestrates the main application layout, applies themes, and routes between different view components (chat, settings, etc.) based on user navigation state
Start here when adding a new top-level view/tab, changing the overall layout structure, or modifying how navigation between views works
src/stores/app-store.tsZustand store managing all global application state including user preferences, connection settings, active view, chat messages, and persistent storage
Start here when adding new global state, modifying user preferences, or understanding what data is available across the app
src/hooks/use-data.tsCustom React hooks that fetch data from API routes and synchronize results into the Zustand app store
Start here when adding a new data fetching hook for a new API endpoint, or debugging why data isn't loading into the UI
src/lib/openclaw-api.tsServer-side bridge to the OpenClaw CLI/config system, providing typed functions to read skills, agents, channels, and sessions
Start here when adding a new data source from the OpenClaw backend, or modifying how existing data is fetched from the CLI/config layer
src/app/layout.tsxConfigures app-wide metadata, viewport settings, fonts, and provides the root HTML structure wrapping all pages
Start here when changing global metadata, adding a new font, modifying the HTML document structure, or adding a global provider
How to Make Changes
Common modification patterns for this codebase
Add a new API endpoint
src/lib/openclaw-api.tsAdd a new typed async function that fetches data from the OpenClaw backend. Export a function like `getMyNewResource()` that returns typed data.
Pattern: Follow the pattern of `getSkills()` or `getAgents()` in src/lib/openclaw-api.ts — each function calls the OpenClaw CLI or reads config, parses the result, and returns a typed array/object
src/app/api/{resource}/route.tsCreate a new directory under src/app/api/ with a route.ts file. Export an async `GET` (or `POST`) function that calls the openclaw-api function and returns `NextResponse.json()`.
Pattern: Copy the exact structure of src/app/api/agents/route.ts — it imports from src/lib/openclaw-api.ts, calls the data function in a try/catch, and returns NextResponse.json() with the result or a 500 error response
src/hooks/use-data.tsAdd a new custom hook (e.g., `useMyNewResource`) that fetches from your new `/api/{resource}` endpoint and writes the result into the Zustand store.
Pattern: Follow the pattern of existing hooks in src/hooks/use-data.ts — each hook uses useEffect to fetch from an API route, parses the JSON response, and calls a setter from the app store (accessed via `useAppStore`)
src/stores/app-store.tsAdd new state fields and setter actions to the Zustand store for the new resource data (e.g., `myResources: []` and `setMyResources: (data) => set({ myResources: data })`).
Pattern: Follow how `skills`, `agents`, `channels` state slices are defined in src/stores/app-store.ts — each has a typed array field initialized to `[]` and a corresponding setter action
Conventions to follow:
Add a new feature view (e.g., a new tab/page in the app)
src/components/views/{feature}-view.tsxCreate a new view component file. This is the main UI for the feature. Export a default React component.
Pattern: Follow the structure of src/components/views/settings-view.tsx — it's a self-contained view component that reads from the Zustand store via `useAppStore()` and renders a complete feature UI with its own local state as needed
src/stores/app-store.tsAdd the new view name to the navigation/active-view type union and add any feature-specific state fields and setters the view needs.
Pattern: Look at how existing view names (like the chat and settings views) are defined as valid values for the active view state in src/stores/app-store.ts, and add your new view identifier to that type
src/components/app-shell.tsxImport the new view component and add a conditional render branch that displays it when the active view state matches your new view identifier.
Pattern: Follow how src/components/app-shell.tsx conditionally renders src/components/views/chat-view.tsx and src/components/views/settings-view.tsx based on the current active view from the store — add an equivalent branch for your new view
src/components/layout/sidebar.tsxAdd a new navigation item/tab button that sets the active view to your new view identifier when clicked.
Pattern: Follow the existing tab buttons in src/components/layout/sidebar.tsx — each has an icon, label, and onClick handler that updates the active view in the Zustand store
src/hooks/use-data.tsIf the feature needs backend data, add a data fetching hook here (see 'Add a new API endpoint' guide for the full backend chain).
Pattern: Follow existing hooks in src/hooks/use-data.ts that fetch from /api/ routes and sync into the store
Conventions to follow:
Add a new reusable UI component
src/components/ui/{component-name}.tsxCreate a new primitive UI component file. Export a named React component that accepts props and uses Tailwind CSS classes for styling.
Pattern: Follow the shadcn-style pattern used by existing components in src/components/ui/ — components use className merging, accept standard HTML element props via React.ComponentProps or similar, and are purely presentational with no business logic or store access
src/components/views/{consuming-view}.tsxImport and use the new UI component in the view or parent component that needs it.
Pattern: See how src/components/views/chat-view.tsx or src/components/views/settings-view.tsx import and compose UI primitives from src/components/ui/
Conventions to follow:
Modify the chat functionality
src/components/views/chat-view.tsxModify the chat UI, message display, user input handling, or how messages are sent/received. This file handles the POST to /api/chat and manages conversation state.
Pattern: The chat view in src/components/views/chat-view.tsx sends messages by POSTing to /api/chat with the user message, then reads the response and appends both user and assistant messages to the conversation state in the Zustand store
src/app/api/chat/route.tsModify the backend chat endpoint that bridges the Next.js app with the openclaw CLI agent. Change how messages are processed, add parameters, or modify the response format.
Pattern: src/app/api/chat/route.ts exports a POST handler that extracts the message from the request body, invokes the openclaw agent CLI tool, and returns the agent's text response as JSON
src/stores/app-store.tsModify chat-related state fields (messages array, active session, etc.) or add new chat state like typing indicators or message metadata.
Pattern: Chat state in src/stores/app-store.ts includes the messages array and related setters — follow the existing pattern of immutable state updates via the Zustand `set()` function
Conventions to follow:
Modify global app settings or preferences
src/components/views/settings-view.tsxAdd or modify settings UI controls (toggles, inputs, dropdowns) for the new preference.
Pattern: src/components/views/settings-view.tsx reads current settings from useAppStore() and calls store setter actions when the user changes a value — each setting maps to a specific store field
src/stores/app-store.tsAdd the new preference field with a default value and a setter action. If it should persist across sessions, ensure it's included in the persist configuration.
Pattern: Existing preferences in src/stores/app-store.ts use Zustand's persist middleware — add your new field to the state interface, provide a default in the initial state, add a setter, and include the field in the persist partialize config if it should survive page reloads
src/components/app-shell.tsxIf the setting affects the app shell (e.g., theme, layout), consume the new store value here and apply it.
Pattern: src/components/app-shell.tsx reads theme preferences from the store and applies them to the root layout — follow this pattern for any setting that affects the overall app appearance or behavior
Conventions to follow:
Modify build configuration or project tooling
next.config.tsModify Next.js build settings, add redirects/rewrites, configure image domains, or adjust webpack settings.
Pattern: next.config.ts exports a Next.js configuration object — add new configuration properties according to the Next.js docs while preserving existing settings
postcss.config.mjsModify PostCSS plugins if changing CSS processing (e.g., adding autoprefixer or other PostCSS plugins alongside Tailwind).
Pattern: postcss.config.mjs exports a plugins object with tailwindcss — add new plugins to the same plugins object
eslint.config.mjsAdd or modify ESLint rules, add ignore patterns, or configure new ESLint plugins.
Pattern: eslint.config.mjs uses the flat config format — add rules or overrides following the existing structure in the file
Conventions to follow:
Coding Conventions
Standards and patterns used in this codebase
Use Next.js App Router with client-side rendering for the main application shell by dynamically importing with SSR disabled, treating Next.js primarily as an API proxy and static shell
dynamic(() => import('@/components/app-shell'), { ssr: false })Use Zustand for global application state management with a single centralized store
Zustand store managing views, themes, chat sessions, and UI state in one app-store.ts fileUse custom React hooks for data fetching from internal API routes with automatic polling intervals (30-second refresh), not direct backend calls from the client
Custom hook that loads skills, channels, cron jobs, and agents from /api/* endpoints with setInterval-based refreshNext.js API routes act as a proxy layer to the OpenClaw CLI backend, keeping backend interaction server-side only via a dedicated API client library
API routes in src/app/api/ proxy requests to openclaw-api.ts which wraps CLI tool callsUse shadcn-style reusable primitive UI components in src/components/ui/ with Tailwind CSS for styling
Primitive components in src/components/ui/ directory following shadcn patterns, PostCSS configured with TailwindSeparate components into views (feature-level pages), layout (shell/navigation), and ui (primitives) directories under src/components/
chat-view.tsx in views/, sidebar.tsx in layout/, primitives in ui/App shell manages desktop/mobile responsive layout with view routing handled at the component level rather than via file-based routing
AppShell component manages layout, theme, and view routing for desktop/mobile responsive applicationUse TypeScript throughout with ESLint configured for Next.js core web vitals and TypeScript-specific rules
ESLint config extends next/core-web-vitals and next/typescript; config files use .ts/.mjs extensionsUse @/ path alias for absolute imports from the src directory
import('@/components/app-shell')