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
3 changes: 3 additions & 0 deletions src/app/(general)/_components/landing-page/navbar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ export const Navbar = () => {
</h1>
</HStack>
<HStack>
<Link href="/models">
<Button variant="ghost">Models</Button>
</Link>
<AuthModal>
<Button className="user-message">Playground</Button>
</AuthModal>
Expand Down
7 changes: 6 additions & 1 deletion src/app/(general)/_components/sidebar/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import Link from "next/link";

import { Edit } from "lucide-react";
import { BarChart3, Edit } from "lucide-react";

import {
SidebarGroup,
Expand All @@ -24,6 +24,11 @@ export const NavMain = () => {
url: workbenchId ? `/workbench/${workbenchId}` : "/",
icon: Edit,
},
{
title: "Models",
url: "/models",
icon: BarChart3,
},
];

return (
Expand Down
163 changes: 163 additions & 0 deletions src/app/(general)/models/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import { Activity, Bot, Database, Gauge, Trophy } from "lucide-react";

import { Badge } from "@/components/ui/badge";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { HStack, VStack } from "@/components/ui/stack";
import { api } from "@/trpc/server";

export const metadata = {
title: "Model Usage Leaderboard | Toolkit.dev",
description:
"Most-used language models across Toolkit.dev assistant messages.",
};

const numberFormatter = new Intl.NumberFormat("en-US");

export default async function ModelUsageLeaderboardPage() {
const { models, totalMessages } = await api.messages.getTopModelUsage({
limit: 50,
});
const maxCount = Math.max(...models.map((model) => model.count), 1);

return (
<main className="h-full overflow-y-auto">
<div className="mx-auto flex w-full max-w-5xl flex-col gap-8 px-4 py-8 md:px-8 md:py-12">
<VStack className="items-start gap-4">
<Badge variant="glass" className="gap-1">
<Trophy className="size-3" />
Rankings
</Badge>
<div className="space-y-3">
<h1 className="text-3xl font-bold tracking-normal md:text-4xl">
Model usage leaderboard
</h1>
<p className="text-muted-foreground max-w-2xl text-sm md:text-base">
The most-used assistant models, ranked by messages generated in
Toolkit.dev.
</p>
</div>
<HStack className="text-muted-foreground gap-2 text-sm">
<Database className="size-4" />
<span>
{numberFormatter.format(totalMessages)} assistant messages
</span>
</HStack>
</VStack>

<div className="overflow-hidden rounded-lg border">
<Table>
<TableHeader>
<TableRow>
<TableHead className="w-12">#</TableHead>
<TableHead>Model</TableHead>
<TableHead>Provider</TableHead>
<TableHead className="min-w-44">Usage</TableHead>
<TableHead>Context</TableHead>
<TableHead>Capabilities</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{models.length === 0 ? (
<TableRow>
<TableCell
colSpan={6}
className="text-muted-foreground h-24 text-center"
>
No model usage has been recorded yet.
</TableCell>
</TableRow>
) : (
models.map((model, index) => {
const width = `${(model.count / maxCount) * 100}%`;

return (
<TableRow key={model.modelId}>
<TableCell className="text-muted-foreground font-mono text-xs">
{index + 1}
</TableCell>
<TableCell>
<HStack className="gap-3">
<div className="bg-muted flex size-8 shrink-0 items-center justify-center rounded-full border">
<Bot className="size-4" />
</div>
<VStack className="items-start gap-0">
<span className="font-medium">{model.name}</span>
<span className="text-muted-foreground max-w-72 truncate text-xs">
{model.providerModelId}
</span>
</VStack>
</HStack>
</TableCell>
<TableCell>
<Badge variant="outline" className="capitalize">
{model.provider}
</Badge>
</TableCell>
<TableCell>
<VStack className="items-start gap-1">
<HStack className="text-sm">
<Activity className="size-3" />
<span>{numberFormatter.format(model.count)}</span>
<span className="text-muted-foreground">
{model.percentage}%
</span>
</HStack>
<div className="bg-muted h-2 w-full overflow-hidden rounded-full">
<div
className="bg-primary h-full"
style={{ width }}
/>
</div>
</VStack>
</TableCell>
<TableCell>
{model.contextLength ? (
<HStack className="text-sm">
<Gauge className="size-3" />
<span>
{numberFormatter.format(model.contextLength)}
</span>
</HStack>
) : (
<span className="text-muted-foreground text-sm">
Unknown
</span>
)}
</TableCell>
<TableCell>
<HStack className="max-w-72 flex-wrap justify-start gap-1">
{model.capabilities.length > 0 ? (
model.capabilities.map((capability) => (
<Badge
key={capability}
variant="capability"
className="capitalize"
>
{capability.replaceAll("-", " ")}
</Badge>
))
) : (
<span className="text-muted-foreground text-sm">
None listed
</span>
)}
</HStack>
</TableCell>
</TableRow>
);
})
)}
</TableBody>
</Table>
</div>
</div>
</main>
);
}
109 changes: 108 additions & 1 deletion src/server/api/routers/messages.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { z } from "zod";

import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
import {
createTRPCRouter,
protectedProcedure,
publicProcedure,
} from "@/server/api/trpc";
import { FILE_NAME_MAX_LENGTH } from "@/lib/constants";
import { languageModels } from "@/ai/language";

const messagePartSchema = z.discriminatedUnion("type", [
z.object({
Expand Down Expand Up @@ -43,6 +48,58 @@ const messagePartSchema = z.discriminatedUnion("type", [
}),
]);
export const messagesRouter = createTRPCRouter({
getTopModelUsage: publicProcedure
.input(
z
.object({
limit: z.number().min(1).max(100).default(25),
})
.optional(),
)
.query(async ({ ctx, input }) => {
const limit = input?.limit ?? 25;

const [modelRows, totalMessages] = await Promise.all([
ctx.db.message.groupBy({
by: ["modelId"],
where: {
role: "assistant",
},
_count: {
_all: true,
},
orderBy: {
_count: {
modelId: "desc",
},
},
take: limit,
}),
ctx.db.message.count({
where: {
role: "assistant",
},
}),
]);

return {
totalMessages,
models: modelRows.map((row) => {
const resolvedModel = resolveModel(row.modelId);
const count = row._count._all;

return {
...resolvedModel,
count,
percentage:
totalMessages === 0
? 0
: Math.round((count / totalMessages) * 1000) / 10,
};
}),
};
}),

getMessagesForChat: protectedProcedure
.input(
z.object({
Expand Down Expand Up @@ -138,3 +195,53 @@ export const messagesRouter = createTRPCRouter({
});
}),
});

function resolveModel(storedModelId: string) {
const separatorIndex = getModelSeparatorIndex(storedModelId);
const provider =
separatorIndex > 0 ? storedModelId.slice(0, separatorIndex) : "unknown";
const providerModelId =
separatorIndex > 0
? storedModelId.slice(separatorIndex + 1)
: storedModelId;
const model = languageModels.find(
(model) =>
model.provider === provider &&
(model.modelId === providerModelId ||
`${model.provider}/${model.modelId}` === storedModelId ||
`${model.provider}:${model.modelId}` === storedModelId),
);

return {
modelId: storedModelId,
provider,
providerModelId,
name: model?.name ?? formatModelName(providerModelId),
description: model?.description,
contextLength: model?.contextLength,
capabilities: model?.capabilities ?? [],
};
}

function getModelSeparatorIndex(modelId: string) {
const slashIndex = modelId.indexOf("/");
const colonIndex = modelId.indexOf(":");

if (slashIndex === -1) {
return colonIndex;
}

if (colonIndex === -1) {
return slashIndex;
}

return Math.min(slashIndex, colonIndex);
}

function formatModelName(modelId: string) {
return modelId
.split(/[-_:/.]+/)
.filter(Boolean)
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
.join(" ");
}