"use client"; import { ChevronLeft, ChevronRight, Download, Globe } from 'lucide-react'; import { useRouter, useSearchParams } from 'next/navigation'; import { useCallback, useEffect, useId, useState } from 'react'; import TimezoneSelect, { type ITimezoneOption } from 'react-timezone-select'; import { toast } from 'sonner'; import { downloadUsageRunsReportApiV1OrganizationsUsageRunsReportGet, getDailyUsageBreakdownApiV1OrganizationsUsageDailyBreakdownGet, getMpsCreditsApiV1OrganizationsUsageMpsCreditsGet, getPreferencesApiV1OrganizationsPreferencesGet, getUsageHistoryApiV1OrganizationsUsageRunsGet, savePreferencesApiV1OrganizationsPreferencesPut } from '@/client/sdk.gen'; import type { DailyUsageBreakdownResponse, MpsCreditsResponse, OrganizationPreferences, UsageHistoryResponse, WorkflowRunUsageResponse } from '@/client/types.gen'; import { CallTypeCell } from '@/components/CallTypeCell'; import { DailyUsageTable } from '@/components/DailyUsageTable'; import { FilterBuilder } from '@/components/filters/FilterBuilder'; import { MediaPreviewButton, MediaPreviewDialog } from '@/components/MediaPreviewDialog'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Progress } from '@/components/ui/progress'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table'; import { useUserConfig } from '@/context/UserConfigContext'; import { useAuth } from '@/lib/auth'; import { usageFilterAttributes } from '@/lib/filterAttributes'; import { decodeFiltersFromURL, encodeFiltersToURL } from '@/lib/filters'; import { ActiveFilter, DateRangeValue } from '@/types/filters'; // Get local timezone const getLocalTimezone = () => Intl.DateTimeFormat().resolvedOptions().timeZone; export default function UsagePage() { const router = useRouter(); const searchParams = useSearchParams(); const { organizationPricing } = useUserConfig(); const auth = useAuth(); // MPS credits state const [mpsCredits, setMpsCredits] = useState(null); const [isLoadingCredits, setIsLoadingCredits] = useState(true); // Usage history state const [usageHistory, setUsageHistory] = useState(null); const [isLoadingHistory, setIsLoadingHistory] = useState(false); const [currentPage, setCurrentPage] = useState(() => { const pageParam = searchParams.get('page'); return pageParam ? parseInt(pageParam, 10) : 1; }); const [isExecutingFilters, setIsExecutingFilters] = useState(false); const [isDownloadingReport, setIsDownloadingReport] = useState(false); // Daily usage breakdown state (only for paid orgs) const [dailyUsage, setDailyUsage] = useState(null); const [isLoadingDaily, setIsLoadingDaily] = useState(false); // Initialize filters from URL. `activeFilters` tracks the in-progress // edits in the FilterBuilder; `appliedFilters` is what's actually been // committed via Apply (and what drives fetching + the download button). const [activeFilters, setActiveFilters] = useState(() => { return decodeFiltersFromURL(searchParams, usageFilterAttributes); }); const [appliedFilters, setAppliedFilters] = useState(() => { return decodeFiltersFromURL(searchParams, usageFilterAttributes); }); // Media preview dialog const mediaPreview = MediaPreviewDialog(); // Timezone state - initialize with empty string to avoid hydration mismatch const localTimezone = getLocalTimezone(); const [selectedTimezone, setSelectedTimezone] = useState(''); const [savingTimezone, setSavingTimezone] = useState(false); const [preferences, setPreferences] = useState({}); const [preferencesLoading, setPreferencesLoading] = useState(true); const timezoneSelectId = useId(); // Stable ID for react-select to prevent hydration mismatch // Fetch MPS credits const fetchMpsCredits = useCallback(async () => { if (!auth.isAuthenticated) return; try { const response = await getMpsCreditsApiV1OrganizationsUsageMpsCreditsGet(); if (response.data) { setMpsCredits(response.data); } } catch (error) { console.error('Failed to fetch MPS credits:', error); } finally { setIsLoadingCredits(false); } }, [auth.isAuthenticated]); // Translate the FilterBuilder state into the query-param shape the // backend expects. Shared between the listing fetch and the CSV export // so they stay in lockstep. const buildUsageQueryParams = (filters?: ActiveFilter[]) => { let filterParam: string | undefined; let startDate = ''; let endDate = ''; if (filters && filters.length > 0) { const dateRangeFilter = filters.find(f => f.attribute.id === 'dateRange'); if (dateRangeFilter && dateRangeFilter.value) { const dateValue = dateRangeFilter.value as DateRangeValue; if (dateValue.from) startDate = dateValue.from.toISOString(); if (dateValue.to) endDate = dateValue.to.toISOString(); } const otherFilters = filters.filter(f => f.attribute.id !== 'dateRange'); if (otherFilters.length > 0) { const filterData = otherFilters.map(filter => ({ attribute: filter.attribute.id, type: filter.attribute.type, value: filter.value, })); filterParam = JSON.stringify(filterData); } } return { ...(startDate && { start_date: startDate }), ...(endDate && { end_date: endDate }), ...(filterParam && { filters: filterParam }), }; }; // Fetch usage history const fetchUsageHistory = useCallback(async (page: number, filters?: ActiveFilter[]) => { if (!auth.isAuthenticated) return; setIsLoadingHistory(true); try { const response = await getUsageHistoryApiV1OrganizationsUsageRunsGet({ query: { page, limit: 50, ...buildUsageQueryParams(filters), }, }); if (response.data) { setUsageHistory(response.data); } } catch (error) { console.error('Failed to fetch usage history:', error); } finally { setIsLoadingHistory(false); } }, [auth.isAuthenticated]); // Fetch daily usage breakdown const fetchDailyUsage = useCallback(async () => { if (!auth.isAuthenticated || !organizationPricing?.price_per_second_usd) return; setIsLoadingDaily(true); try { const response = await getDailyUsageBreakdownApiV1OrganizationsUsageDailyBreakdownGet({ query: { days: 7 }, }); if (response.data) { setDailyUsage(response.data); } } catch (error) { console.error('Failed to fetch daily usage:', error); } finally { setIsLoadingDaily(false); } }, [auth.isAuthenticated, organizationPricing]); const fetchPreferences = useCallback(async () => { if (!auth.isAuthenticated) return; setPreferencesLoading(true); try { const response = await getPreferencesApiV1OrganizationsPreferencesGet(); const nextPreferences = response.data || {}; setPreferences(nextPreferences); setSelectedTimezone(nextPreferences.timezone || localTimezone); } catch (error) { console.error('Failed to fetch organization preferences:', error); setSelectedTimezone(localTimezone); } finally { setPreferencesLoading(false); } }, [auth.isAuthenticated, localTimezone]); // Download a CSV of all runs matching the current filters. const handleDownloadReport = async () => { if (!auth.isAuthenticated) return; setIsDownloadingReport(true); try { const response = await downloadUsageRunsReportApiV1OrganizationsUsageRunsReportGet({ query: buildUsageQueryParams(appliedFilters), parseAs: 'blob', }); if (response.data) { const blob = response.data as Blob; const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'usage_runs_report.csv'; document.body.appendChild(a); a.click(); a.remove(); window.URL.revokeObjectURL(url); } else { toast.error('Failed to download report'); } } catch (error) { console.error('Failed to download usage report:', error); toast.error('Failed to download report'); } finally { setIsDownloadingReport(false); } }; // Handle timezone change const handleTimezoneChange = async (timezone: ITimezoneOption | string) => { setSelectedTimezone(timezone); setSavingTimezone(true); const previousTimezone = preferences.timezone || localTimezone; try { const tzValue = typeof timezone === 'string' ? timezone : timezone.value; const response = await savePreferencesApiV1OrganizationsPreferencesPut({ body: { ...preferences, timezone: tzValue, }, }); if (response.error) { throw new Error('Failed to save timezone'); } setPreferences(response.data || { ...preferences, timezone: tzValue }); } catch (error) { console.error('Failed to save timezone:', error); setSelectedTimezone(previousTimezone); } finally { setSavingTimezone(false); } }; // Update timezone when organization preferences load. useEffect(() => { fetchPreferences(); }, [fetchPreferences]); // Initial load - fetch when auth becomes available useEffect(() => { if (auth.isAuthenticated) { fetchMpsCredits(); fetchUsageHistory(currentPage, appliedFilters); } }, [auth.isAuthenticated, currentPage, appliedFilters, fetchUsageHistory, fetchMpsCredits]); // Fetch daily usage when organizationPricing becomes available useEffect(() => { if (auth.isAuthenticated && organizationPricing?.price_per_second_usd) { fetchDailyUsage(); } }, [auth.isAuthenticated, organizationPricing, fetchDailyUsage]); // Update URL with query parameters const updateUrlParams = useCallback((params: { page?: number; filters?: ActiveFilter[] }) => { const newParams = new URLSearchParams(); if (params.page !== undefined) { newParams.set('page', params.page.toString()); } // Add filters to URL if present if (params.filters && params.filters.length > 0) { const filterString = encodeFiltersToURL(params.filters); if (filterString) { const filterParams = new URLSearchParams(filterString); filterParams.forEach((value, key) => newParams.set(key, value)); } } router.push(`/usage?${newParams.toString()}`); }, [router]); const handleApplyFilters = useCallback(async () => { setIsExecutingFilters(true); setCurrentPage(1); // Reset to first page when applying filters setAppliedFilters(activeFilters); updateUrlParams({ page: 1, filters: activeFilters }); await fetchUsageHistory(1, activeFilters); setIsExecutingFilters(false); }, [activeFilters, fetchUsageHistory, updateUrlParams]); const handleFiltersChange = useCallback((filters: ActiveFilter[]) => { setActiveFilters(filters); }, []); const handleClearFilters = useCallback(async () => { setIsExecutingFilters(true); setCurrentPage(1); setActiveFilters([]); setAppliedFilters([]); updateUrlParams({ page: 1, filters: [] }); // Clear filters from URL await fetchUsageHistory(1, []); // Fetch all runs without filters setIsExecutingFilters(false); }, [fetchUsageHistory, updateUrlParams]); // Handle page change const handlePageChange = (newPage: number) => { setCurrentPage(newPage); updateUrlParams({ page: newPage, filters: appliedFilters }); fetchUsageHistory(newPage, appliedFilters); }; // Handle row click to navigate to workflow run const handleRowClick = (run: WorkflowRunUsageResponse) => { router.push(`/workflow/${run.workflow_id}/run/${run.id}`); }; // Format datetime for display with timezone support const formatDateTime = (dateString: string) => { const date = new Date(dateString); const tzValue = typeof selectedTimezone === 'string' ? selectedTimezone : selectedTimezone.value; // Use local timezone if none selected (during loading) const effectiveTz = tzValue || localTimezone; return date.toLocaleString('en-US', { timeZone: effectiveTz, year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit', hour12: true }); }; // Format duration for display const formatDuration = (seconds: number) => { const minutes = Math.floor(seconds / 60); const remainingSeconds = seconds % 60; if (minutes === 0) return `${remainingSeconds}s`; if (remainingSeconds === 0) return `${minutes}m`; return `${minutes}m ${remainingSeconds}s`; }; return (

Agent Runs

See all your Agent Runs across all Voice Agents. You can use filters to filter out required Agent Runs.

({ ...base, minHeight: '36px', fontSize: '14px', backgroundColor: 'var(--background)', borderColor: state.isFocused ? 'var(--ring)' : 'var(--border)', boxShadow: state.isFocused ? '0 0 0 2px color-mix(in srgb, var(--ring) 20%, transparent)' : 'none', '&:hover': { borderColor: 'var(--border)', }, }), menu: (base) => ({ ...base, zIndex: 9999, backgroundColor: 'var(--popover)', border: '1px solid var(--border)', boxShadow: '0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)', }), menuList: (base) => ({ ...base, backgroundColor: 'var(--popover)', padding: 0, }), option: (base, state) => ({ ...base, backgroundColor: state.isSelected ? 'var(--accent)' : state.isFocused ? 'var(--accent)' : 'var(--popover)', color: 'var(--foreground)', cursor: 'pointer', '&:active': { backgroundColor: 'var(--accent)', }, }), singleValue: (base) => ({ ...base, color: 'var(--foreground)', }), input: (base) => ({ ...base, color: 'var(--foreground)', }), placeholder: (base) => ({ ...base, color: 'var(--muted-foreground)', }), indicatorSeparator: (base) => ({ ...base, backgroundColor: 'var(--border)', }), dropdownIndicator: (base) => ({ ...base, color: 'var(--muted-foreground)', '&:hover': { color: 'var(--foreground)', }, }), }} />
{/* MPS Credits Card */} Dograh Model Credits These track usage of Dograh models using Dograh Service Keys. {isLoadingCredits ? (
) : mpsCredits ? (

{mpsCredits.total_credits_used.toFixed(2)} / {mpsCredits.total_quota.toFixed(2)}

Credits Used

{mpsCredits.remaining_credits.toFixed(2)}

Remaining

{mpsCredits.total_quota > 0 && ( )}
) : (

No Dograh service keys configured. Set up a service key in your model configuration to see usage.

)}
{/* Daily Usage Table - Only for paid organizations */} {organizationPricing?.price_per_second_usd && (
)} {/* Filter Builder */}
{appliedFilters.length > 0 && (
)}
{/* Usage History */}
All Runs Every agent run across your organization, with usage details
{isLoadingHistory ? (
{[...Array(5)].map((_, i) => (
))}
) : usageHistory && usageHistory.runs.length > 0 ? ( <>
Run ID Agent Name Call Type Phone Number Disposition Date Duration {organizationPricing?.price_per_second_usd ? 'Cost (USD)' : 'Tokens'} Actions {usageHistory.runs.map((run) => ( handleRowClick(run)} > #{run.id} {run.workflow_name || 'Unknown'} {(run.call_type === 'inbound' ? run.caller_number : run.called_number) || '-'} {run.disposition ? ( {run.disposition} ) : ( - )} {formatDateTime(run.created_at)} {formatDuration(run.call_duration_seconds)} {organizationPricing?.price_per_second_usd && run.charge_usd !== undefined && run.charge_usd !== null ? `$${run.charge_usd.toFixed(2)}` : run.dograh_token_usage.toLocaleString() } ))}
{/* Summary */} {appliedFilters.length > 0 && (

Total for filtered period: {usageHistory.total_dograh_tokens.toLocaleString()} Dograh Tokens {' • '} {formatDuration(usageHistory.total_duration_seconds)}

)} {/* Pagination */} {usageHistory.total_pages > 1 && (

Page {usageHistory.page} of {usageHistory.total_pages} ({usageHistory.total_count} total runs)

)} ) : (

No runs found

)}
{/* Media Preview Dialog */} {mediaPreview.dialog}
); }