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
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
},
"dependencies": {
"@radix-ui/react-checkbox": "^1.0.3",
"@radix-ui/react-dropdown-menu": "^2.1.12",
"@radix-ui/react-popover": "^1.0.5",
"@radix-ui/react-select": "^1.2.0",
"@tanstack/react-query": "^4.20.2",
Expand All @@ -26,6 +27,7 @@
"graphemer": "^1.4.0",
"lucide-react": "^0.119.0",
"next": "^13.2.1",
"next-themes": "^0.4.6",
"react": "18.2.0",
"react-dom": "18.2.0",
"superjson": "1.9.1",
Expand Down
87 changes: 87 additions & 0 deletions src/components/ThemeToggle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
"use client";

import * as React from "react";
import { Moon, Sun, Monitor } from "lucide-react";
import { useTheme } from "next-themes";
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";

import { Button } from "~/components/Button";
import { cn } from "~/utils/cn";

// Basic styles for dropdown content and item - adapt as needed
const DropdownMenuContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border border-slate-200 bg-white p-1 text-slate-900 shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-50",
className
)}
{...props}
/>
</DropdownMenuPrimitive.Portal>
));
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;

const DropdownMenuItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean;
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-slate-100 focus:text-slate-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-slate-800 dark:focus:text-slate-50",
inset && "pl-8",
className
)}
{...props}
/>
));
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;

export function ThemeToggle() {
const { setTheme, theme } = useTheme();
const [mounted, setMounted] = React.useState(false);

// useEffect only runs on the client, so we can safely show the UI
React.useEffect(() => {
setMounted(true);
}, []);

if (!mounted) {
// Render a placeholder or null on the server to avoid hydration mismatch
return <div className="h-9 w-9" />; // Placeholder with same size as button
}

return (
<DropdownMenuPrimitive.Root>
<DropdownMenuPrimitive.Trigger asChild>
<Button variant="ghost" size="sm" aria-label="Toggle theme">
{theme === "light" && <Sun className="h-5 w-5" />}
{theme === "dark" && <Moon className="h-5 w-5" />}
{theme === "system" && <Monitor className="h-5 w-5" />}
</Button>
</DropdownMenuPrimitive.Trigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => setTheme("light")}>
<Sun className="mr-2 h-4 w-4" />
<span>Light</span>
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("dark")}>
<Moon className="mr-2 h-4 w-4" />
<span>Dark</span>
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("system")}>
<Monitor className="mr-2 h-4 w-4" />
<span>System</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenuPrimitive.Root>
);
}
5 changes: 3 additions & 2 deletions src/pages/_app.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { type AppType } from "next/app";
import { ThemeProvider } from 'next-themes';

import { api } from "~/utils/api";
import { Analytics } from "@vercel/analytics/react";
import "~/styles/globals.css";

const MyApp: AppType = ({ Component, pageProps }) => {
return (
<>
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
<Component {...pageProps} />
<Analytics />
</>
</ThemeProvider>
);
};

Expand Down
14 changes: 8 additions & 6 deletions src/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import Head from "next/head";
import { useMemo, useState } from "react";
import { Github, Twitter } from "lucide-react";

import { ThemeToggle } from "~/components/ThemeToggle";
import { ChatGPTEditor } from "../sections/ChatGPTEditor";
import { EncoderSelect } from "~/sections/EncoderSelect";
import { TokenViewer } from "~/sections/TokenViewer";
Expand Down Expand Up @@ -78,7 +79,7 @@ const Home: NextPage<
<TextArea
value={inputText}
onChange={(e) => setInputText(e.target.value)}
className="min-h-[256px] rounded-md border p-4 font-mono shadow-sm"
className="min-h-[256px] rounded-md border dark:border-gray-600 p-4 font-mono shadow-sm"
/>
</section>

Expand All @@ -103,12 +104,12 @@ const Home: NextPage<
`}
</style>
<div className="flex justify-between text-center md:mt-6">
<p className=" text-sm text-slate-400">
<p className=" text-sm text-slate-400 dark:text-slate-300">
Built by{" "}
<a
target="_blank"
rel="noreferrer"
className="text-slate-800"
className="text-slate-800 dark:text-slate-200"
href="https://duong.dev"
>
dqbd
Expand All @@ -117,7 +118,7 @@ const Home: NextPage<
<a
target="_blank"
rel="noreferrer"
className="diagram-link text-slate-800"
className="diagram-link text-slate-800 dark:text-slate-200"
href="https://diagram.com"
>
<svg
Expand All @@ -140,18 +141,19 @@ const Home: NextPage<
</p>

<div className="flex items-center gap-4">
<ThemeToggle />
<a
target="_blank"
rel="noreferrer"
className="text-slate-800"
className="text-slate-800 dark:text-slate-200"
href="https://github.com/dqbd/tiktokenizer"
>
<Github />
</a>
<a
target="_blank"
rel="noreferrer"
className="text-slate-800"
className="text-slate-800 dark:text-slate-200"
href="https://twitter.com/__dqbd"
>
<Twitter />
Expand Down
2 changes: 1 addition & 1 deletion src/sections/EncoderSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export function EncoderSelect(props: {
<span className="flex items-center gap-2">
<span>{value}</span>
{props.isLoading && (
<span className="h-4 w-4 animate-spin rounded-full border-2 border-gray-700 border-b-transparent" />
<span className="h-4 w-4 animate-spin rounded-full border-2 border-gray-700 border-b-transparent dark:border-gray-300 dark:border-b-transparent" />
)}
</span>
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
Expand Down
95 changes: 64 additions & 31 deletions src/sections/TokenViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,29 @@ const COLORS = [
"bg-teal-200",
];

// Map light colors to dark variants
const DARK_COLORS = {
"bg-sky-200": "dark:bg-sky-900",
"bg-amber-200": "dark:bg-amber-900",
"bg-blue-200": "dark:bg-blue-900",
"bg-green-200": "dark:bg-green-900",
"bg-orange-200": "dark:bg-orange-900",
"bg-cyan-200": "dark:bg-cyan-900",
"bg-gray-200": "dark:bg-gray-700", // Adjusted gray for potentially better contrast
"bg-purple-200": "dark:bg-purple-900",
"bg-indigo-200": "dark:bg-indigo-900",
"bg-lime-200": "dark:bg-lime-900",
"bg-rose-200": "dark:bg-rose-900",
"bg-violet-200": "dark:bg-violet-900",
"bg-yellow-200": "dark:bg-yellow-900",
"bg-emerald-200": "dark:bg-emerald-900",
"bg-zinc-200": "dark:bg-zinc-700", // Adjusted zinc
"bg-red-200": "dark:bg-red-900",
"bg-fuchsia-200": "dark:bg-fuchsia-900",
"bg-pink-200": "dark:bg-pink-900",
"bg-teal-200": "dark:bg-teal-900",
};

function encodeWhitespace(str: string) {
let result = str;

Expand Down Expand Up @@ -58,35 +81,39 @@ export function TokenViewer(props: {
return (
<>
<div className="flex gap-4">
<div className="flex-grow rounded-md border bg-slate-50 p-4 shadow-sm">
<div className="flex-grow rounded-md border dark:border-gray-700 bg-slate-50 dark:bg-slate-800 p-4 shadow-sm">
<p className="text-sm ">Token count</p>
<p className="text-lg">{tokenCount}</p>
</div>
</div>

<pre className="min-h-[256px] max-w-[100vw] overflow-auto whitespace-pre-wrap break-all rounded-md border bg-slate-50 p-4 shadow-sm">
{props.data?.segments?.map(({ text }, idx) => (
<span
key={idx}
onMouseEnter={() => setIndexHover(idx)}
onMouseLeave={() => setIndexHover(null)}
className={cn(
"transition-all",
(indexHover == null || indexHover === idx) &&
COLORS[idx % COLORS.length],
props.isFetching && "opacity-50"
)}
>
{showWhitespace || indexHover === idx
? encodeWhitespace(text)
: text}
</span>
))}
<pre className="min-h-[256px] max-w-[100vw] overflow-auto whitespace-pre-wrap break-all rounded-md border dark:border-gray-700 bg-slate-50 dark:bg-slate-800 p-4 shadow-sm">
{props.data?.segments?.map(({ text }, idx) => {
const lightColor = COLORS[idx % COLORS.length]!;
const darkColor = DARK_COLORS[lightColor as keyof typeof DARK_COLORS];
return (
<span
key={idx}
onMouseEnter={() => setIndexHover(idx)}
onMouseLeave={() => setIndexHover(null)}
className={cn(
"transition-all",
(indexHover == null || indexHover === idx) && lightColor,
(indexHover == null || indexHover === idx) && darkColor,
props.isFetching && "opacity-50"
)}
>
{showWhitespace || indexHover === idx
? encodeWhitespace(text)
: text}
</span>
);
})}
</pre>

<pre
className={
"min-h-[256px] max-w-[100vw] overflow-auto whitespace-pre-wrap break-all rounded-md border bg-slate-50 p-4 shadow-sm"
"min-h-[256px] max-w-[100vw] overflow-auto whitespace-pre-wrap break-all rounded-md border dark:border-gray-700 bg-slate-50 dark:bg-slate-800 p-4 shadow-sm"
}
>
{props.data && tokenCount > 0 && (
Expand All @@ -100,17 +127,23 @@ export function TokenViewer(props: {
<Fragment key={segmentIdx}>
{segment.tokens.map((token) => (
<Fragment key={token.idx}>
<span
onMouseEnter={() => setIndexHover(segmentIdx)}
onMouseLeave={() => setIndexHover(null)}
className={cn(
"transition-colors",
indexHover === segmentIdx &&
COLORS[segmentIdx % COLORS.length]
)}
>
{token.id}
</span>
{(() => {
const lightColor = COLORS[segmentIdx % COLORS.length]!;
const darkColor = DARK_COLORS[lightColor as keyof typeof DARK_COLORS];
return (
<span
onMouseEnter={() => setIndexHover(segmentIdx)}
onMouseLeave={() => setIndexHover(null)}
className={cn(
"transition-colors",
indexHover === segmentIdx && lightColor,
indexHover === segmentIdx && darkColor
)}
>
{token.id}
</span>
);
})()}
<span className="last-of-type:hidden">{", "}</span>
</Fragment>
))}
Expand Down
7 changes: 7 additions & 0 deletions src/styles/globals.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

@layer base {
body {
@apply bg-white text-black;
@apply dark:bg-gray-900 dark:text-white;
}
}
2 changes: 1 addition & 1 deletion tailwind.config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

/** @type {import('tailwindcss').Config} */
const config = {
darkMode: ["class", '[data-theme="dark"]'],
darkMode: "class",
content: ["./src/**/*.{js,ts,jsx,tsx}"],
theme: {
extend: {
Expand Down
Loading