Spaces:
Sleeping
Sleeping
File size: 4,975 Bytes
21ab33c | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 | import { Check, Copy, Layers3 } from "lucide-react";
import { useMemo, useState, type CSSProperties } from "react";
import type { TokenizationResult } from "../tokenizer/types";
const DISPLAY_LIMIT = 2500;
const TOKEN_COLOR_COUNT = 8;
type TokenView = "all" | "special";
function tokenColor(id: number): string {
return `var(--token-color-${id % TOKEN_COLOR_COUNT})`;
}
export function TokenOutput({ result }: { result: TokenizationResult | null }) {
const [copied, setCopied] = useState(false);
const [view, setView] = useState<TokenView>("all");
const filteredTokens = useMemo(
() =>
result?.tokens.filter((token) => view === "all" || token.special) ?? [],
[result, view],
);
async function copyIds() {
await navigator.clipboard.writeText(
result?.tokens.map((token) => token.id).join(", ") ?? "",
);
setCopied(true);
window.setTimeout(() => setCopied(false), 1600);
}
return (
<section className="surface overflow-hidden" aria-labelledby="output-heading">
<div className="token-output-header flex flex-wrap items-center justify-between gap-3 px-5 py-3.5">
<div className="flex items-center gap-2.5">
<h2 id="output-heading" className="text-sm font-semibold text-[var(--ink)]">
Tokens
</h2>
<span className="metric-chip">
<strong>{result?.tokenCount.toLocaleString() ?? 0}</strong> tokens
</span>
</div>
<div className="flex items-center gap-2">
<label className="flex items-center gap-2 text-[10px] text-[var(--muted)]">
View
<select
className="select-control"
value={view}
onChange={(event) => setView(event.target.value as TokenView)}
>
<option value="all">全部 Token</option>
<option value="special">Special Tokens</option>
</select>
</label>
<button
className="mini-icon-button"
type="button"
onClick={() => void copyIds()}
disabled={!result}
aria-label="复制所有 Token ID"
title="复制所有 Token ID"
>
{copied ? <Check size={14} /> : <Copy size={14} />}
</button>
</div>
</div>
{!result ? (
<div className="grid min-h-64 place-items-center px-6 text-center">
<div>
<span className="mx-auto grid size-11 place-items-center rounded-2xl border border-[var(--line)] bg-[var(--soft)] text-[var(--muted)]">
<Layers3 size={19} aria-hidden="true" />
</span>
<p className="mt-3 text-sm font-medium text-[var(--ink)]">等待 Tokenize</p>
<p className="mt-1 text-xs text-[var(--muted)]">Token 文本和 ID 会显示在这里。</p>
</div>
</div>
) : (
<div className="p-4 sm:p-5">
{filteredTokens.length ? (
<div className="token-stage flex min-h-36 flex-wrap content-start items-start gap-2" aria-label="Token 序列">
{filteredTokens.slice(0, DISPLAY_LIMIT).map((token) => {
const style = {
"--token-color": tokenColor(token.id),
} as CSSProperties;
return (
<span
className={`token-chip ${token.special ? "token-chip-special" : ""}`}
key={`${token.index}-${token.id}`}
style={style}
title={`#${token.index} · ID ${token.id}${token.special ? " · special" : ""}`}
>
<span className="token-text">{token.displayText}</span>
<span className="token-id">{token.id}</span>
</span>
);
})}
</div>
) : (
<div className="grid min-h-36 place-items-center text-xs text-[var(--muted)]">
当前结果中没有 Special Token。
</div>
)}
{filteredTokens.length > DISPLAY_LIMIT ? (
<p className="mt-4 rounded-xl bg-[var(--soft)] px-4 py-3 text-xs text-[var(--muted)]">
为保持页面流畅,仅展示前 {DISPLAY_LIMIT.toLocaleString()} 个 Token。
</p>
) : null}
<div className="token-map-row">
<span className="token-map-label">序列概览</span>
<div className="token-map" aria-hidden="true">
{result.tokens.slice(0, 400).map((token) => (
<span
className="token-map-segment"
key={`map-${token.index}`}
style={{
background: tokenColor(token.id),
flexGrow: Math.min(8, Math.max(1, token.text.length)),
}}
/>
))}
</div>
</div>
</div>
)}
</section>
);
}
|