Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions apps/tui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@
"postpublish": "chmod +x dist/ccflare"
},
"dependencies": {
"@ccflare/tui-core": "workspace:*",
"@ccflare/ui-common": "workspace:*",
"@ccflare/database": "workspace:*",
"@ccflare/core-di": "workspace:*",
"@ccflare/config": "workspace:*",
"@ccflare/core-di": "workspace:*",
"@ccflare/database": "workspace:*",
"@ccflare/logger": "workspace:*",
"@ccflare/server": "workspace:*",
"@ccflare/tui-core": "workspace:*",
"@ccflare/ui-common": "workspace:*",
"ink": "^6.0.0",
"ink-select-input": "^6.0.0",
"ink-spinner": "^5.0.0",
Expand Down
69 changes: 69 additions & 0 deletions apps/tui/src/components/MigrationProgress.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import type { MigrationProgress } from "@ccflare/database";
import { Box, Text, useApp } from "ink";
import Spinner from "ink-spinner";

interface MigrationProgressProps {
progress: MigrationProgress;
}

// Simple progress bar component without external dependencies
function SimpleProgressBar({ percent }: { percent: number }) {
const width = 40;
const filled = Math.round(width * percent);
const empty = width - filled;

return (
<Box>
<Text color="cyan">{"█".repeat(filled)}</Text>
<Text color="gray">{"░".repeat(empty)}</Text>
</Box>
);
}

export function MigrationProgressComponent({
progress,
}: MigrationProgressProps) {
const { exit } = useApp();

// Exit if progress is complete
if (progress.percentage === 100) {
setTimeout(() => exit(), 500);
}

return (
<Box
flexDirection="column"
borderStyle="round"
borderColor="cyan"
padding={1}
width={60}
>
<Box marginBottom={1}>
<Text bold color="cyan">
Database Migration in Progress
</Text>
</Box>

<Box marginBottom={1}>
<Text color="gray">{progress.operation}</Text>
</Box>

<Box marginBottom={1}>
<Text>
<Spinner type="dots" /> {progress.current.toLocaleString()} /{" "}
{progress.total.toLocaleString()} records
</Text>
</Box>

<Box flexDirection="column" marginBottom={1}>
<Text dimColor>Progress: {progress.percentage}%</Text>
<SimpleProgressBar percent={progress.percentage / 100} />
</Box>

<Text dimColor>
This is a one-time operation to enable full-text search.
</Text>
<Text dimColor>Please wait while we index your request data...</Text>
</Box>
);
}
28 changes: 25 additions & 3 deletions apps/tui/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { Config } from "@ccflare/config";
import { CLAUDE_MODEL_IDS, NETWORK, shutdown } from "@ccflare/core";
import { container, SERVICE_KEYS } from "@ccflare/core-di";
import { DatabaseFactory } from "@ccflare/database";
import { DatabaseFactory, type MigrationProgress } from "@ccflare/database";
import { Logger } from "@ccflare/logger";
// Import server
import startServer from "@ccflare/server";
Expand All @@ -11,6 +11,7 @@ import { parseArgs } from "@ccflare/tui-core";
import { render } from "ink";
import React from "react";
import { App } from "./App";
import { MigrationProgressComponent } from "./components/MigrationProgress";

// Global singleton for auto-started server
let runningServer: ReturnType<typeof startServer> | null = null;
Expand All @@ -27,11 +28,32 @@ async function main() {
container.registerInstance(SERVICE_KEYS.Config, new Config());
container.registerInstance(SERVICE_KEYS.Logger, new Logger("TUI"));

// Initialize database factory
DatabaseFactory.initialize();
// Track migration progress
let migrationProgress: MigrationProgress | null = null;
let migrationComplete = false;

// Initialize database factory with progress callback
DatabaseFactory.initialize(undefined, undefined, (progress) => {
migrationProgress = progress;
if (progress.percentage === 100) {
migrationComplete = true;
}
});

// Show migration progress if needed
const dbOps = DatabaseFactory.getInstance();
container.registerInstance(SERVICE_KEYS.Database, dbOps);

// If migration is in progress, show progress UI first
if (migrationProgress && !migrationComplete) {
const { waitUntilExit } = render(
React.createElement(MigrationProgressComponent, {
progress: migrationProgress,
}),
);
await waitUntilExit();
}

const args = process.argv.slice(2);
const parsed = parseArgs(args);

Expand Down
84 changes: 84 additions & 0 deletions packages/dashboard-web/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,47 @@ export type {
RequestResponse,
} from "@ccflare/types";

// Search types
export interface SearchFilters {
accountId?: string;
method?: string;
path?: string;
statusCode?: number;
success?: boolean;
dateFrom?: string;
dateTo?: string;
model?: string;
agentUsed?: string;
limit?: number;
offset?: number;
}

export interface SearchResult {
id: string;
timestamp: number;
method: string;
path: string;
accountUsed: string | null;
accountName: string | null;
statusCode: number | null;
success: boolean;
responseTime: number;
model: string | null;
agentUsed: string | null;
requestSnippet?: string;
responseSnippet?: string;
requestSnippets?: string[];
responseSnippets?: string[];
rank: number;
}

export interface SearchResponse {
results: SearchResult[];
total: number;
query: string;
filters: SearchFilters;
}

// Agent response interface
export interface AgentsResponse {
agents: Agent[];
Expand Down Expand Up @@ -297,6 +338,49 @@ class API extends HttpClient {
throw error;
}
}

async searchRequests(
query: string,
filters: SearchFilters = {},
): Promise<SearchResponse> {
const params = new URLSearchParams({ q: query });

if (filters.limit !== undefined) {
params.append("limit", filters.limit.toString());
}
if (filters.offset !== undefined) {
params.append("offset", filters.offset.toString());
}
if (filters.accountId) {
params.append("accountId", filters.accountId);
}
if (filters.method) {
params.append("method", filters.method);
}
if (filters.path) {
params.append("path", filters.path);
}
if (filters.statusCode !== undefined) {
params.append("statusCode", filters.statusCode.toString());
}
if (filters.success !== undefined) {
params.append("success", filters.success.toString());
}
if (filters.dateFrom) {
params.append("dateFrom", filters.dateFrom);
}
if (filters.dateTo) {
params.append("dateTo", filters.dateTo);
}
if (filters.model) {
params.append("model", filters.model);
}
if (filters.agentUsed) {
params.append("agentUsed", filters.agentUsed);
}

return this.get<SearchResponse>(`/api/requests/search?${params}`);
}
}

export const api = new API();
Loading