mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
Dry up drawers (#27)
This commit is contained in:
parent
d7a2997bac
commit
4a0697c09f
14 changed files with 195 additions and 174 deletions
|
|
@ -5,7 +5,7 @@ import { eq, gte, and, desc } from 'drizzle-orm';
|
|||
import { db } from '@/db';
|
||||
import { auditLogs } from '@/db/schema';
|
||||
import { logger } from '@/lib/server/logger';
|
||||
import { AuditLogType } from '@/db/schema';
|
||||
import { AuditLog } from '@/db/schema';
|
||||
|
||||
/**
|
||||
* getAuditLogs
|
||||
|
|
@ -20,7 +20,7 @@ export const getAuditLogs = async ({
|
|||
orgId?: string | null;
|
||||
limit?: number;
|
||||
nRecentDays?: number;
|
||||
}): Promise<AuditLogType[]> => {
|
||||
}): Promise<AuditLog[]> => {
|
||||
if (!orgId) {
|
||||
return [];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { useState } from 'react';
|
|||
import { useOrganization } from '@clerk/nextjs';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import type { AuditLogType } from '@/db/schema';
|
||||
import type { AuditLog } from '@/db/schema';
|
||||
import { timePeriods, type TimePeriod } from '@/schemas';
|
||||
import { getAuditLogs } from '@/actions/auditLogs';
|
||||
import {
|
||||
|
|
@ -14,20 +14,16 @@ import {
|
|||
CardTitle,
|
||||
CardDescription,
|
||||
CardContent,
|
||||
Drawer,
|
||||
DrawerContent,
|
||||
DrawerHeader,
|
||||
DrawerTitle,
|
||||
Skeleton,
|
||||
} from '@/components/ui';
|
||||
import { AuditLogDetails } from '@/components/audit-logs/AuditLogDetails';
|
||||
import { AuditLogEntry } from '@/components/audit-logs/AuditLogEntry';
|
||||
import { AuditLogEntry, AuditLogDrawer } from '@/components/audit-logs';
|
||||
|
||||
export const AuditLogs = () => {
|
||||
const { organization } = useOrganization();
|
||||
const [timePeriod, setTimePeriod] = useState<TimePeriod>(7);
|
||||
const [selectedLog, setSelectedLog] = useState<AuditLogType | null>(null);
|
||||
const [selectedLog, setSelectedLog] = useState<AuditLog | null>(null);
|
||||
|
||||
const { data: logs = [], isLoading } = useQuery({
|
||||
const { data: logs = [], isPending } = useQuery({
|
||||
queryKey: ['auditLogs', organization?.id, timePeriod],
|
||||
queryFn: () =>
|
||||
getAuditLogs({
|
||||
|
|
@ -60,37 +56,33 @@ export const AuditLogs = () => {
|
|||
</Button>
|
||||
))}
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
{isLoading ? (
|
||||
<div className="py-8 text-center">
|
||||
<p className="text-sm text-muted-foreground">Loading...</p>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
{isPending ? (
|
||||
<>
|
||||
<Skeleton className="h-[64px] w-full" />
|
||||
<Skeleton className="h-[64px] w-full" />
|
||||
<Skeleton className="h-[64px] w-full" />
|
||||
</>
|
||||
) : logs.length > 0 ? (
|
||||
logs.map((log: AuditLogType) => (
|
||||
logs.map((log: AuditLog) => (
|
||||
<AuditLogEntry
|
||||
key={log.id}
|
||||
log={log}
|
||||
onClick={(log: AuditLogType) => setSelectedLog(log)}
|
||||
onClick={(log: AuditLog) => setSelectedLog(log)}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<div className="py-8 text-center">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
No activity found
|
||||
</p>
|
||||
<div className="text-center text-sm text-muted-foreground">
|
||||
No activity found.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Drawer open={!!selectedLog} onClose={() => setSelectedLog(null)}>
|
||||
<DrawerContent>
|
||||
<DrawerHeader>
|
||||
<DrawerTitle>Activity Details</DrawerTitle>
|
||||
</DrawerHeader>
|
||||
{selectedLog && <AuditLogDetails log={selectedLog} />}
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
<AuditLogDrawer
|
||||
selectedLog={selectedLog}
|
||||
onClose={() => setSelectedLog(null)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -17,79 +17,72 @@ export const TaskDetails = ({
|
|||
task ? (
|
||||
<Drawer open={true} onOpenChange={onClose} direction="right">
|
||||
<DrawerContent>
|
||||
<div className="mx-auto w-full max-w-sm">
|
||||
<DrawerHeader>
|
||||
<DrawerTitle>Task Details</DrawerTitle>
|
||||
</DrawerHeader>
|
||||
<div className="p-4 pb-0">
|
||||
<div className="mb-6 space-y-2">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-sm text-muted-foreground">Task ID:</span>
|
||||
<span className="font-medium">{task.id}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-sm text-muted-foreground">Date:</span>
|
||||
<span className="font-medium">
|
||||
{task.date.toLocaleString()}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-sm text-muted-foreground">
|
||||
Developer:
|
||||
</span>
|
||||
<span className="font-medium">{task.developerName}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-sm text-muted-foreground">Model:</span>
|
||||
<span className="font-medium">{task.modelName}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-sm text-muted-foreground">Tokens:</span>
|
||||
<span className="font-medium">
|
||||
{task.tokensConsumed.toLocaleString()}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-sm text-muted-foreground">Cost:</span>
|
||||
<span className="font-medium">${task.cost.toFixed(2)}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-sm text-muted-foreground">Status:</span>
|
||||
<span
|
||||
className={
|
||||
task.status === 'completed'
|
||||
? 'font-medium text-green-500'
|
||||
: task.status === 'started'
|
||||
? 'font-medium text-blue-500'
|
||||
: 'font-medium text-red-500'
|
||||
}
|
||||
>
|
||||
{task.status.charAt(0).toUpperCase() + task.status.slice(1)}
|
||||
</span>
|
||||
</div>
|
||||
<DrawerHeader>
|
||||
<DrawerTitle>Task Details</DrawerTitle>
|
||||
</DrawerHeader>
|
||||
<div className="p-4">
|
||||
<div className="mb-6 space-y-2">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-sm text-muted-foreground">Task ID:</span>
|
||||
<span className="font-medium">{task.id}</span>
|
||||
</div>
|
||||
<h3 className="mb-2 text-lg font-medium">Conversation</h3>
|
||||
<div className="space-y-4">
|
||||
{task.conversation.map((message) => (
|
||||
<div
|
||||
key={message.id}
|
||||
className={`rounded-lg p-3 ${
|
||||
message.role === 'user'
|
||||
? 'ml-4 bg-primary/10'
|
||||
: message.role === 'assistant'
|
||||
? 'mr-4 bg-secondary/10'
|
||||
: 'bg-muted'
|
||||
}`}
|
||||
>
|
||||
<div className="mb-1 text-xs font-medium text-muted-foreground">
|
||||
{message.role.charAt(0).toUpperCase() +
|
||||
message.role.slice(1)}{' '}
|
||||
•{message.timestamp.toLocaleTimeString()}
|
||||
</div>
|
||||
<div className="text-sm">{message.content}</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-sm text-muted-foreground">Date:</span>
|
||||
<span className="font-medium">{task.date.toLocaleString()}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-sm text-muted-foreground">Developer:</span>
|
||||
<span className="font-medium">{task.developerName}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-sm text-muted-foreground">Model:</span>
|
||||
<span className="font-medium">{task.modelName}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-sm text-muted-foreground">Tokens:</span>
|
||||
<span className="font-medium">
|
||||
{task.tokensConsumed.toLocaleString()}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-sm text-muted-foreground">Cost:</span>
|
||||
<span className="font-medium">${task.cost.toFixed(2)}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-sm text-muted-foreground">Status:</span>
|
||||
<span
|
||||
className={
|
||||
task.status === 'completed'
|
||||
? 'font-medium text-green-500'
|
||||
: task.status === 'started'
|
||||
? 'font-medium text-blue-500'
|
||||
: 'font-medium text-red-500'
|
||||
}
|
||||
>
|
||||
{task.status.charAt(0).toUpperCase() + task.status.slice(1)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<h3 className="mb-2 text-lg font-medium">Conversation</h3>
|
||||
<div className="space-y-4">
|
||||
{task.conversation.map((message) => (
|
||||
<div
|
||||
key={message.id}
|
||||
className={`rounded-lg p-3 ${
|
||||
message.role === 'user'
|
||||
? 'ml-4 bg-primary/10'
|
||||
: message.role === 'assistant'
|
||||
? 'mr-4 bg-secondary/10'
|
||||
: 'bg-muted'
|
||||
}`}
|
||||
>
|
||||
<div className="mb-1 text-xs font-medium text-muted-foreground">
|
||||
{message.role.charAt(0).toUpperCase() + message.role.slice(1)}{' '}
|
||||
•{message.timestamp.toLocaleTimeString()}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="text-sm">{message.content}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</DrawerContent>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { useOrganization } from '@clerk/nextjs';
|
|||
import { useQuery } from '@tanstack/react-query';
|
||||
import { ArrowRightIcon } from 'lucide-react';
|
||||
|
||||
import type { AuditLogType } from '@/db/schema';
|
||||
import type { AuditLog } from '@/db/schema';
|
||||
import { getAuditLogs } from '@/actions/auditLogs';
|
||||
import {
|
||||
Card,
|
||||
|
|
@ -15,21 +15,20 @@ import {
|
|||
CardTitle,
|
||||
CardDescription,
|
||||
CardContent,
|
||||
Drawer,
|
||||
DrawerContent,
|
||||
DrawerHeader,
|
||||
DrawerTitle,
|
||||
Skeleton,
|
||||
} from '@/components/ui';
|
||||
import { Button as EnhancedButton } from '@/components/ui/ecosystem';
|
||||
import { AuditLogDetails, AuditLogEntry } from '@/components/audit-logs';
|
||||
import { AuditLogEntry } from '@/components/audit-logs';
|
||||
|
||||
import { AuditLogDrawer } from './AuditLogDrawer';
|
||||
|
||||
export function AuditLogCard() {
|
||||
const [selectedLog, setSelectedLog] = useState<AuditLogType | null>(null);
|
||||
const [selectedLog, setSelectedLog] = useState<AuditLog | null>(null);
|
||||
const { organization } = useOrganization();
|
||||
|
||||
const path = usePathname();
|
||||
|
||||
const { data: logs = [], isLoading } = useQuery({
|
||||
const { data: logs = [], isPending } = useQuery({
|
||||
queryKey: ['auditLogs', organization?.id, 5],
|
||||
queryFn: () => getAuditLogs({ orgId: organization?.id, limit: 5 }),
|
||||
enabled: !!organization?.id,
|
||||
|
|
@ -61,37 +60,33 @@ export function AuditLogCard() {
|
|||
)}
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-1">
|
||||
{isLoading ? (
|
||||
<div className="py-8 text-center">
|
||||
<p className="text-sm text-muted-foreground">Loading...</p>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
{isPending ? (
|
||||
<>
|
||||
<Skeleton className="h-[64px] w-full" />
|
||||
<Skeleton className="h-[64px] w-full" />
|
||||
<Skeleton className="h-[64px] w-full" />
|
||||
</>
|
||||
) : logs.length > 0 ? (
|
||||
logs.map((log: AuditLogType) => (
|
||||
logs.map((log: AuditLog) => (
|
||||
<AuditLogEntry
|
||||
key={log.id}
|
||||
log={log}
|
||||
onClick={(log: AuditLogType) => setSelectedLog(log)}
|
||||
onClick={(log: AuditLog) => setSelectedLog(log)}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<div className="py-8 text-center">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
No recent activity
|
||||
</p>
|
||||
<div className="text-center text-sm text-muted-foreground">
|
||||
No recent activity.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Drawer open={!!selectedLog} onClose={() => setSelectedLog(null)}>
|
||||
<DrawerContent>
|
||||
<DrawerHeader>
|
||||
<DrawerTitle>Activity Details</DrawerTitle>
|
||||
</DrawerHeader>
|
||||
{selectedLog && <AuditLogDetails log={selectedLog} />}
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
<AuditLogDrawer
|
||||
selectedLog={selectedLog}
|
||||
onClose={() => setSelectedLog(null)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,10 +3,10 @@
|
|||
import { ArrowRight, Calendar, Clock, User } from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
|
||||
import type { AuditLogType } from '@/db/schema';
|
||||
import type { AuditLog } from '@/db/schema';
|
||||
|
||||
type AuditLogDetailsProps = {
|
||||
log: AuditLogType;
|
||||
log: AuditLog;
|
||||
};
|
||||
|
||||
const formatValue = (value: unknown) => {
|
||||
|
|
|
|||
31
src/components/audit-logs/AuditLogDrawer.tsx
Normal file
31
src/components/audit-logs/AuditLogDrawer.tsx
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import type { AuditLog } from '@/db/schema';
|
||||
import {
|
||||
Drawer,
|
||||
DrawerContent,
|
||||
DrawerHeader,
|
||||
DrawerTitle,
|
||||
} from '@/components/ui';
|
||||
import { AuditLogDetails } from '@/components/audit-logs';
|
||||
|
||||
type AuditLogDrawerProps = {
|
||||
selectedLog: AuditLog | null;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
export const AuditLogDrawer = ({
|
||||
selectedLog,
|
||||
onClose,
|
||||
}: AuditLogDrawerProps) => {
|
||||
return (
|
||||
<Drawer open={!!selectedLog} onClose={onClose} direction="right">
|
||||
<DrawerContent>
|
||||
<DrawerHeader>
|
||||
<DrawerTitle>Activity Details</DrawerTitle>
|
||||
</DrawerHeader>
|
||||
<div className="p-4">
|
||||
{selectedLog && <AuditLogDetails log={selectedLog} />}
|
||||
</div>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,12 +1,11 @@
|
|||
import { Settings, Sliders, Users } from 'lucide-react';
|
||||
import { formatDistance } from 'date-fns';
|
||||
import { Settings, Sliders, Users } from 'lucide-react';
|
||||
|
||||
import { type AuditLogType, AuditLogTargetType } from '@/db/schema';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { type AuditLog, AuditLogTargetType } from '@/db/schema';
|
||||
|
||||
type AuditLogEntryProps = {
|
||||
log: AuditLogType;
|
||||
onClick: (log: AuditLogType) => void;
|
||||
log: AuditLog;
|
||||
onClick: (log: AuditLog) => void;
|
||||
};
|
||||
|
||||
const getIconByType = (type: AuditLogTargetType) => {
|
||||
|
|
@ -23,24 +22,21 @@ const getIconByType = (type: AuditLogTargetType) => {
|
|||
};
|
||||
|
||||
export const AuditLogEntry = ({ log, onClick }: AuditLogEntryProps) => (
|
||||
<button
|
||||
<div
|
||||
onClick={() => onClick(log)}
|
||||
className={cn(
|
||||
'w-full rounded-md p-3 text-left transition-colors',
|
||||
'hover:bg-muted focus:bg-muted focus:outline-none',
|
||||
)}
|
||||
className="rounded-md p-3 transition-colors hover:bg-muted active:opacity-80 cursor-pointer"
|
||||
>
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="mt-0.5 shrink-0">{getIconByType(log.targetType)}</div>
|
||||
<div className="flex-1 space-y-1">
|
||||
<p className="text-sm text-foreground">{log.description}</p>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex flex-row gap-2">
|
||||
<div className="shrink-0 pt-1">{getIconByType(log.targetType)}</div>
|
||||
<div className="flex flex-row justify-between items-center gap-2 flex-1">
|
||||
<div className="flex flex-col gap-1">
|
||||
<p className="text-sm text-foreground">{log.description}</p>
|
||||
<p className="text-xs text-muted-foreground">{log.userId}</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{formatDistance(log.createdAt, new Date(), { addSuffix: true })}
|
||||
</p>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground hidden sm:block">
|
||||
{formatDistance(log.createdAt, new Date(), { addSuffix: true })}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
export { AuditLogCard } from './AuditLogCard';
|
||||
export { AuditLogDetails } from './AuditLogDetails';
|
||||
export { AuditLogDrawer } from './AuditLogDrawer';
|
||||
export { AuditLogEntry } from './AuditLogEntry';
|
||||
|
|
|
|||
|
|
@ -22,9 +22,11 @@ export const Logo = ({
|
|||
const { resolvedTheme } = useTheme();
|
||||
const [fill, setFill] = useState('#000');
|
||||
|
||||
useEffect(() => {
|
||||
setFill(resolvedTheme === 'dark' ? '#fff' : '#000');
|
||||
}, [resolvedTheme]);
|
||||
// Fixes hydration error.
|
||||
useEffect(
|
||||
() => setFill(resolvedTheme === 'dark' ? '#fff' : '#000'),
|
||||
[resolvedTheme],
|
||||
);
|
||||
|
||||
return (
|
||||
<svg
|
||||
|
|
@ -52,6 +54,7 @@ export const HoppingLogo = (props: LogoProps) => {
|
|||
|
||||
useEffect(() => {
|
||||
const element = ref.current;
|
||||
|
||||
const isHopping =
|
||||
element !== null && element.classList.contains('animate-hop');
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useTheme } from 'next-themes';
|
||||
import { Moon, Sun } from 'lucide-react';
|
||||
|
||||
|
|
@ -7,11 +8,18 @@ import { Switch } from '@/components/ui';
|
|||
|
||||
export function ThemeSwitcher() {
|
||||
const { resolvedTheme, setTheme } = useTheme();
|
||||
const [mode, setMode] = useState<'light' | 'dark'>('light');
|
||||
|
||||
// Fixes hydration error.
|
||||
useEffect(
|
||||
() => setMode(resolvedTheme === 'dark' ? 'dark' : 'light'),
|
||||
[resolvedTheme],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<Switch
|
||||
checked={resolvedTheme === 'dark'}
|
||||
checked={mode === 'dark'}
|
||||
onCheckedChange={(checked) => setTheme(checked ? 'dark' : 'light')}
|
||||
/>
|
||||
<div className="relative h-[1.2rem] w-[1.2rem]">
|
||||
|
|
|
|||
|
|
@ -4,7 +4,11 @@ function Skeleton({ className, ...props }: React.ComponentProps<'div'>) {
|
|||
return (
|
||||
<div
|
||||
data-slot="skeleton"
|
||||
className={cn('bg-accent animate-pulse rounded-md', className)}
|
||||
className={cn(
|
||||
'bg-accent animate-pulse rounded-md',
|
||||
'dark:bg-accent/25',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
import { Skeleton } from '../ui';
|
||||
import { formatNumber } from '@/lib/formatters';
|
||||
import { Skeleton } from '@/components/ui';
|
||||
|
||||
type MetricProps = {
|
||||
label: string;
|
||||
value?: number | string;
|
||||
isLoading: boolean;
|
||||
isPending: boolean;
|
||||
};
|
||||
|
||||
export const Metric = ({ label, value, isLoading }: MetricProps) => (
|
||||
export const Metric = ({ label, value, isPending }: MetricProps) => (
|
||||
<div className="flex flex-col gap-1 rounded-lg border border-secondary bg-background px-3 py-2">
|
||||
<div className="text-xs text-muted-foreground">{label}</div>
|
||||
<div className="font-mono font-semibold text-2xl h-8">
|
||||
{isLoading ? (
|
||||
{isPending ? (
|
||||
<div>
|
||||
<Skeleton className="h-6 w-12 my-1" />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ export const UsageCard = () => {
|
|||
|
||||
const path = usePathname();
|
||||
|
||||
const usage = useQuery({
|
||||
const { data: usage = {}, isPending } = useQuery({
|
||||
queryKey: ['usage', orgId, timePeriod],
|
||||
queryFn: () => getUsage({ orgId, timePeriod }),
|
||||
});
|
||||
|
|
@ -79,34 +79,30 @@ export const UsageCard = () => {
|
|||
<div className="grid grid-cols-2 gap-4 md:grid-cols-5">
|
||||
<Metric
|
||||
label={t('analytics_active_developers')}
|
||||
value={usage.data?.[TelemetryEventName.LLM_COMPLETION]?.userCount}
|
||||
isLoading={usage.isLoading}
|
||||
value={usage[TelemetryEventName.TASK_CREATED]?.userCount}
|
||||
isPending={isPending}
|
||||
/>
|
||||
<Metric
|
||||
label={t('analytics_tasks_started')}
|
||||
value={usage.data?.[TelemetryEventName.TASK_CREATED]?.eventCount}
|
||||
isLoading={usage.isLoading}
|
||||
value={usage[TelemetryEventName.TASK_CREATED]?.eventCount}
|
||||
isPending={isPending}
|
||||
/>
|
||||
<Metric
|
||||
label={t('analytics_tasks_completed')}
|
||||
value={
|
||||
usage.data?.[TelemetryEventName.TASK_COMPLETED]?.eventCount
|
||||
}
|
||||
isLoading={usage.isLoading}
|
||||
value={usage[TelemetryEventName.TASK_COMPLETED]?.eventCount}
|
||||
isPending={isPending}
|
||||
/>
|
||||
<Metric
|
||||
label={t('analytics_tokens_consumed')}
|
||||
value={
|
||||
usage.data?.[TelemetryEventName.LLM_COMPLETION]?.inputTokens
|
||||
}
|
||||
isLoading={usage.isLoading}
|
||||
value={usage[TelemetryEventName.LLM_COMPLETION]?.inputTokens}
|
||||
isPending={isPending}
|
||||
/>
|
||||
<Metric
|
||||
label={t('analytics_llm_costs')}
|
||||
value={formatCurrency(
|
||||
usage.data?.[TelemetryEventName.LLM_COMPLETION]?.cost,
|
||||
usage[TelemetryEventName.LLM_COMPLETION]?.cost,
|
||||
)}
|
||||
isLoading={usage.isLoading}
|
||||
isPending={isPending}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -26,4 +26,6 @@ export enum AuditLogTargetType {
|
|||
MEMBER_CHANGE = 3, // TODO: Currently no logs of this type are collected.
|
||||
}
|
||||
|
||||
export type AuditLogType = typeof auditLogs.$inferSelect;
|
||||
export type AuditLog = typeof auditLogs.$inferSelect & {
|
||||
targetType: AuditLogTargetType;
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue