@slopit/dashboard-ui
v0.1.0
Published
React dashboard UI for slopit behavioral analytics
Maintainers
Readme
@slopit/dashboard-ui
React components for the slopit behavioral analytics dashboard. Displays session data, analysis verdicts, and flags from the Python backend. Can be used as React components in a React application or as a standalone Web Component in any HTML page.
Installation
pnpm add @slopit/dashboard-uiFor React applications, ensure you have React 18 or 19 installed:
pnpm add react react-domQuick Start
React Application
import { Dashboard } from "@slopit/dashboard-ui";
import "@slopit/dashboard-ui/style.css";
function App() {
return (
<Dashboard
config={{
apiUrl: "http://localhost:8000/api/v1",
enableWebSocket: true,
theme: "system",
}}
/>
);
}Web Component (Standalone HTML)
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="https://unpkg.com/@slopit/dashboard-ui/dist/slopit-dashboard.css" />
<script type="module" src="https://unpkg.com/@slopit/dashboard-ui/dist/slopit-dashboard.js"></script>
</head>
<body>
<slopit-dashboard
api-url="http://localhost:8000/api/v1"
theme="dark"
enable-websocket="true">
</slopit-dashboard>
</body>
</html>Features
- Session list with filtering and pagination
- Detailed session view with trial timeline
- Verdict badges (clean, suspicious, flagged)
- Flag list with severity indicators
- Analytics charts (flag distribution, confidence histogram)
- Real-time updates via WebSocket
- Light and dark theme support
- Web Component wrapper for non-React applications
React Components
Dashboard
The main dashboard component that combines all subcomponents. Provides navigation between session list and analytics views.
import { Dashboard } from "@slopit/dashboard-ui";
<Dashboard
config={{
apiUrl: "http://localhost:8000/api/v1",
studyId: "my-study",
theme: "dark",
pollingInterval: 30000,
enableWebSocket: true,
}}
/>;DashboardConfig
| Property | Type | Default | Description |
|----------|------|---------|-------------|
| apiUrl | string | required | Base URL for the dashboard API |
| studyId | string | undefined | Optional study ID to display |
| theme | "light" \| "dark" \| "system" | "system" | Color theme |
| pollingInterval | number | undefined | Polling interval in milliseconds |
| enableWebSocket | boolean | true | Enable real-time WebSocket updates |
SessionList
Table displaying sessions with verdict badges, pagination, and filtering.
import { SessionList, createApiClient } from "@slopit/dashboard-ui";
const client = createApiClient("http://localhost:8000/api/v1");
<SessionList
apiClient={client}
onSessionSelect={(sessionId) => console.log("Selected:", sessionId)}
filters={{ verdict: "suspicious" }}
pageSize={20}
/>;SessionListProps
| Property | Type | Default | Description |
|----------|------|---------|-------------|
| apiClient | DashboardApiClient | required | API client instance |
| onSessionSelect | (sessionId: string) => void | undefined | Callback when a session row is clicked |
| filters | SessionFilters | undefined | Filter options for the list |
| pageSize | number | 20 | Number of sessions per page |
SessionDetail
Detailed view of a single session with tabs for trials, flags, and raw data.
import { SessionDetail, createApiClient } from "@slopit/dashboard-ui";
const client = createApiClient("http://localhost:8000/api/v1");
<SessionDetail
apiClient={client}
sessionId="abc123"
onBack={() => navigate("/sessions")}
/>;SessionDetailProps
| Property | Type | Default | Description |
|----------|------|---------|-------------|
| apiClient | DashboardApiClient | required | API client instance |
| sessionId | string | required | ID of the session to display |
| onBack | () => void | undefined | Callback for back button |
TrialTimeline
Timeline view of trials with expandable behavioral data sections.
import { TrialTimeline } from "@slopit/dashboard-ui";
<TrialTimeline
trials={session.trials}
onTrialSelect={(index) => console.log("Selected trial:", index)}
/>;TrialTimelineProps
| Property | Type | Default | Description |
|----------|------|---------|-------------|
| trials | SlopitTrial[] | required | Array of trial objects |
| onTrialSelect | (trialIndex: number) => void | undefined | Callback when a trial is expanded |
VerdictBadge
Badge displaying the verdict status with optional confidence score.
import { VerdictBadge } from "@slopit/dashboard-ui";
<VerdictBadge verdict="suspicious" confidence={0.75} showConfidence />;VerdictBadgeProps
| Property | Type | Default | Description |
|----------|------|---------|-------------|
| verdict | "clean" \| "suspicious" \| "flagged" | required | Verdict type to display |
| confidence | number | undefined | Confidence score (0 to 1) |
| showConfidence | boolean | false | Show confidence percentage |
| className | string | "" | Additional CSS class |
FlagList
List of analysis flags with severity indicators.
import { FlagList } from "@slopit/dashboard-ui";
<FlagList flags={verdict.flags} groupByAnalyzer />;FlagListProps
| Property | Type | Default | Description |
|----------|------|---------|-------------|
| flags | VerdictFlag[] | required | Array of flag objects |
| groupByAnalyzer | boolean | false | Group flags by their analyzer |
FlagDistribution
Horizontal bar chart showing the distribution of flag types.
import { FlagDistribution } from "@slopit/dashboard-ui";
<FlagDistribution
distribution={{
paste_detected: 15,
excessive_blur: 8,
timing_anomaly: 3,
}}
title="Flag Distribution"
/>;FlagDistributionProps
| Property | Type | Default | Description |
|----------|------|---------|-------------|
| distribution | Record<string, number> | required | Map of flag type to count |
| title | string | undefined | Optional chart title |
ConfidenceHistogram
Histogram showing the distribution of confidence scores.
import { ConfidenceHistogram } from "@slopit/dashboard-ui";
<ConfidenceHistogram
scores={[0.2, 0.4, 0.6, 0.8, 0.95]}
bins={10}
title="Confidence Distribution"
/>;ConfidenceHistogramProps
| Property | Type | Default | Description |
|----------|------|---------|-------------|
| scores | number[] | required | Array of confidence scores (0 to 1) |
| bins | number | 10 | Number of histogram bins |
| title | string | undefined | Optional chart title |
ConnectionStatus
Visual indicator for WebSocket connection status.
import { ConnectionStatus, useWebSocket } from "@slopit/dashboard-ui";
const { connectionState } = useWebSocket({ url: "ws://localhost:8000/ws" });
<ConnectionStatus state={connectionState} showLabel />;ConnectionStatusProps
| Property | Type | Default | Description |
|----------|------|---------|-------------|
| state | "connecting" \| "connected" \| "disconnected" \| "error" | required | Connection state |
| showLabel | boolean | true | Show text label next to indicator |
AnalysisSummary
Summary cards showing overall analysis statistics.
import { AnalysisSummary } from "@slopit/dashboard-ui";
<AnalysisSummary
summary={{
totalSessions: 150,
analyzedSessions: 142,
verdictDistribution: { clean: 100, suspicious: 30, flagged: 12 },
flagDistribution: { paste_detected: 25, timing_anomaly: 18 },
averageConfidence: 0.78,
}}
/>;AnalysisSummaryProps
| Property | Type | Default | Description |
|----------|------|---------|-------------|
| summary | AnalysisSummary | required | Analysis summary data object |
Web Component
The <slopit-dashboard> custom element wraps the React Dashboard component for use in non-React applications. It uses Shadow DOM for style isolation.
Attributes
| Attribute | Type | Default | Description |
|-----------|------|---------|-------------|
| api-url | string | "http://localhost:8000/api/v1" | Base URL for the dashboard API |
| study-id | string | undefined | Optional study ID to display |
| theme | "light" \| "dark" \| "system" | "system" | Color theme |
| polling-interval | number | undefined | Polling interval in milliseconds |
| enable-websocket | "true" \| "false" | "true" | Enable real-time WebSocket updates |
Example
<slopit-dashboard
api-url="http://localhost:8000/api/v1"
study-id="experiment-1"
theme="dark"
polling-interval="30000"
enable-websocket="true">
</slopit-dashboard>Dynamic Attribute Updates
Attributes can be updated dynamically and the component will re-render:
const dashboard = document.querySelector("slopit-dashboard");
dashboard.setAttribute("theme", "light");
dashboard.setAttribute("study-id", "new-study");API Client
The DashboardApiClient class provides methods for interacting with the slopit backend REST API.
Creating a Client
import { createApiClient, DashboardApiClient } from "@slopit/dashboard-ui";
// using factory function
const client = createApiClient("http://localhost:8000/api/v1");
// using constructor
const client = new DashboardApiClient({
baseUrl: "http://localhost:8000/api/v1",
headers: { Authorization: "Bearer token" },
});Session Methods
// list sessions with pagination and filters
const response = await client.listSessions(1, 20, {
verdict: "suspicious",
minConfidence: 0.5,
startDate: "2024-01-01",
});
// returns: PaginatedResponse<SessionSummary>
// get a single session
const session = await client.getSession("session-id");
// returns: SlopitSession
// get trials for a session
const trials = await client.getSessionTrials("session-id");
// returns: SlopitTrial[]
// get verdict for a session
const verdict = await client.getSessionVerdict("session-id");
// returns: SessionVerdictAnalysis Methods
// get summary statistics
const summary = await client.getAnalysisSummary();
// returns: AnalysisSummary
// get flag distribution
const flags = await client.getFlagDistribution();
// returns: Record<string, number>
// get verdict distribution
const verdicts = await client.getVerdictDistribution();
// returns: Record<string, number>
// trigger batch analysis
const task = await client.triggerBatchAnalysis(["session-1", "session-2"]);
// returns: { taskId: string }Export Methods
// export sessions to CSV or JSON
const blob = await client.exportSessions({
format: "csv",
includeTrials: true,
includeMetrics: true,
filters: { verdict: "flagged" },
});
// download the blob
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "sessions.csv";
a.click();JATOS Integration
// connect to JATOS
await client.connectJatos("https://jatos.example.com", "api-token");
// list available studies
const studies = await client.listJatosStudies();
// returns: Array<{ id: string; title: string }>
// sync study results
const task = await client.syncJatos("study-id");
// returns: { taskId: string }
// check connection status
const status = await client.getJatosStatus();
// returns: { connected: boolean; url?: string }Prolific Integration
// connect to Prolific
await client.connectProlific("api-token");
// list studies
const studies = await client.listProlificStudies();
// returns: Array<{ id: string; name: string; status: string }>
// get participant recommendations
const recommendations = await client.getProlificRecommendations("study-id");
// returns: Array<{ participantId, sessionId, verdict, recommendation }>WebSocket Hook
The useWebSocket hook manages a WebSocket connection to the backend for real-time updates.
Basic Usage
import { useWebSocket } from "@slopit/dashboard-ui";
function MyComponent() {
const { connectionState, lastMessage, connect, disconnect } = useWebSocket({
url: "ws://localhost:8000/ws",
onSessionNew: (data) => {
console.log("New session:", data.sessionId);
},
onVerdictComputed: (data) => {
console.log("Verdict computed:", data.verdict, data.confidence);
},
onSyncProgress: (data) => {
console.log(`Sync progress: ${data.progress}/${data.total}`);
},
});
return (
<div>
<p>Status: {connectionState}</p>
<button onClick={disconnect}>Disconnect</button>
</div>
);
}UseWebSocketOptions
| Property | Type | Default | Description |
|----------|------|---------|-------------|
| url | string | required | WebSocket server URL |
| reconnect | boolean | true | Automatically reconnect on disconnect |
| reconnectInterval | number | 3000 | Milliseconds between reconnect attempts |
| maxReconnectAttempts | number | 5 | Maximum reconnection attempts |
| onSessionNew | (data: SessionNewEventData) => void | undefined | Callback for new session events |
| onVerdictComputed | (data: VerdictComputedEventData) => void | undefined | Callback for verdict events |
| onSyncProgress | (data: SyncProgressEventData) => void | undefined | Callback for sync progress events |
| onError | (error: Event) => void | undefined | Callback for connection errors |
UseWebSocketReturn
| Property | Type | Description |
|----------|------|-------------|
| connectionState | "connecting" \| "connected" \| "disconnected" \| "error" | Current connection state |
| lastMessage | WebSocketMessage \| null | Most recent message received |
| connect | () => void | Manually initiate connection |
| disconnect | () => void | Close connection and stop reconnection |
Types
VerdictType
type VerdictType = "clean" | "suspicious" | "flagged";SessionVerdict
interface SessionVerdict {
sessionId: string;
verdict: VerdictType;
confidence: number;
flags: VerdictFlag[];
analyzedAt: string;
}VerdictFlag
interface VerdictFlag {
type: string;
severity: "low" | "medium" | "high";
message: string;
analyzer: string;
trialIndex?: number;
}SessionSummary
interface SessionSummary {
sessionId: string;
participantId?: string;
startTime: string;
endTime?: string;
trialCount: number;
verdict?: VerdictType;
confidence?: number;
flagCount: number;
}SessionFilters
interface SessionFilters {
verdict?: VerdictType;
minConfidence?: number;
maxConfidence?: number;
startDate?: string;
endDate?: string;
participantId?: string;
}AnalysisSummary
interface AnalysisSummary {
totalSessions: number;
analyzedSessions: number;
verdictDistribution: Record<VerdictType, number>;
flagDistribution: Record<string, number>;
averageConfidence: number;
}PaginatedResponse
interface PaginatedResponse<T> {
items: T[];
total: number;
page: number;
pageSize: number;
hasMore: boolean;
}Theming
The dashboard supports light and dark themes via CSS custom properties.
Theme Selection
Set the theme via the theme config option:
// react
<Dashboard config={{ apiUrl: "...", theme: "dark" }} />
// web component
<slopit-dashboard api-url="..." theme="dark"></slopit-dashboard>Available values:
"light": Light theme"dark": Dark theme"system": Follow system preference (default)
CSS Custom Properties
Override theme variables for custom styling:
:root {
/* background colors */
--slopit-bg: #ffffff;
--slopit-bg-secondary: #f8f9fa;
--slopit-bg-tertiary: #e9ecef;
/* text colors */
--slopit-text: #212529;
--slopit-text-secondary: #6c757d;
/* border */
--slopit-border: #dee2e6;
/* verdict colors */
--slopit-clean: #28a745;
--slopit-clean-bg: #d4edda;
--slopit-suspicious: #ffc107;
--slopit-suspicious-bg: #fff3cd;
--slopit-flagged: #dc3545;
--slopit-flagged-bg: #f8d7da;
/* interactive */
--slopit-primary: #0d6efd;
--slopit-primary-hover: #0b5ed7;
/* spacing */
--slopit-space-xs: 0.25rem;
--slopit-space-sm: 0.5rem;
--slopit-space-md: 1rem;
--slopit-space-lg: 1.5rem;
/* border radius */
--slopit-radius-sm: 0.25rem;
--slopit-radius-md: 0.375rem;
--slopit-radius-lg: 0.5rem;
}Integration with Python Backend
The dashboard is designed to work with the slopit Python FastAPI backend.
Backend Setup
from slopit.dashboard import create_dashboard_app
app = create_dashboard_app(
database_url="sqlite:///./slopit.db",
cors_origins=["http://localhost:3000"],
)
# run with: uvicorn main:app --reloadServing the Web Component
The Python package includes the built Web Component assets. Serve them from your FastAPI app:
from fastapi.staticfiles import StaticFiles
from slopit.dashboard import get_static_path
app.mount("/static", StaticFiles(directory=get_static_path()), name="static")Then include in your HTML:
<link rel="stylesheet" href="/static/slopit-dashboard.css" />
<script type="module" src="/static/slopit-dashboard.js"></script>
<slopit-dashboard api-url="/api/v1"></slopit-dashboard>CORS Configuration
If running the frontend on a different port, configure CORS:
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:3000"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)WebSocket Endpoint
The backend exposes a WebSocket endpoint at /ws for real-time updates:
@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
await websocket.accept()
# send events for new sessions, computed verdicts, sync progressLicense
MIT
