learning_ai_common_plat/packages/ai-ui/src/useTokenCount.ts
saravanakumardb1 87e3bc490a feat: Wave 9.E (ai-ui@0.6.0) + Wave 13.D.1/.5 (motion@0.2.1)
──────────────────────────────────────────────────────────────────
motion@0.2.1 — Parallax + TiltGallery
──────────────────────────────────────────────────────────────────
  + Parallax.tsx
      - scroll-driven translate3d via rAF + window.scroll
      - speed multiplier + axis (y / x) + reduced-motion bypass
      - listener cleanup + cancelAnimationFrame on unmount
      - WAVE 13.D.1
  + TiltGallery.tsx
      - horizontally-scrolling rail of <TiltCard>-style tiles
      - per-tile cursor-tracking rotateX/Y + glare gradient
      - role=region + arrow-key scrolling (←/→) + scroll-snap
      - reduced-motion strips tilt + glare, keeps the rail
      - WAVE 13.D.5
  + 5 new tests (Parallax x 2, TiltGallery x 3) — 28/28 passing
  + index.ts: exports both + types
  + package.json: 0.2.0 → 0.2.1

──────────────────────────────────────────────────────────────────
ai-ui@0.6.0 — Wave 9.E composition surfaces
──────────────────────────────────────────────────────────────────
  + Markdown.tsx
      - dep-free subset renderer: h1-h3 / **bold** / *italic* /
        `code` / fenced code / ul + ol / [text](url)
      - inline `[cite:<id>]` chips resolved from a citations
        registry (missing ids render as [?] — failure mode is loud)
      - WAVE 9.E.1
  + CodeDiff.tsx
      - line-LCS diff in <100 LOC, zero deps
      - split (2-col) and unified views; tinted add/del rows
      - WAVE 9.E.2
  + ExplainThis.tsx
      - listens for selectionchange, pops 'Explain' CTA over the
        selection rect when inside the wrapper + ≥ minLength chars
      - fires onExplain({ text, rect }) so hosts can open a richer
        side panel if preferred
      - WAVE 9.E.3
  + usePromptHistory.ts
      - bash-style ↑/↓ recall with localStorage persistence
        (storage key configurable; null = in-memory for tests/SSR)
      - dedupes consecutive duplicates + trims to capacity
      - WAVE 9.E.4
  + useTokenCount.ts
      - cheap estimator (default ~4 chars/token; configurable for
        code/CJK) + optional USD cost
      - memoised — stable across re-renders
      - WAVE 9.E.5
  + 19 new tests in src/__tests__/composition.test.tsx — 98/98 passing
  + index.ts: '0.6 surfaces' section exports all 5 + types
  + package.json: 0.5.0 → 0.6.0

Showcase routes + roadmap flips land in the paired showcase commit.
2026-05-27 16:59:28 -07:00

50 lines
1.6 KiB
TypeScript

import { useMemo } from 'react';
export interface UseTokenCountOptions {
/**
* Per-1k-token USD cost (input price). Default 0 (no cost computed).
* Override per-model, e.g. `0.00015` for gpt-4o-mini input.
*/
costPer1kUsd?: number;
/**
* Chars-per-token approximation. Default 4 — the OpenAI rule-of-thumb
* for English. Pass 3.5 for code-heavy contexts, ~6 for tightly
* tokenised Indic / CJK scripts.
*/
charsPerToken?: number;
}
export interface UseTokenCountValue {
/** Approximate token count (rounded up). */
tokens: number;
/** Character count (raw length of `text`). */
chars: number;
/** Approximate USD cost. 0 if `costPer1kUsd` is 0 / undefined. */
costUsd: number;
}
/**
* `useTokenCount` — cheap, dependency-free token estimator for live
* input previews.
*
* Wave 9.E.5. Not for billing. Hosts that need accurate counts should
* call the model provider's tokenizer; this hook is for UI feedback in
* composer surfaces ("you're typing ~412 tokens, ≈ \$0.0001").
*
* Pure function under the hood — wrapped in `useMemo` so the result
* is stable across renders when `text` is unchanged.
*/
export function useTokenCount(
text: string,
options: UseTokenCountOptions = {},
): UseTokenCountValue {
const { costPer1kUsd = 0, charsPerToken = 4 } = options;
return useMemo(() => {
const chars = text.length;
const cpt = charsPerToken > 0 ? charsPerToken : 4;
const tokens = chars === 0 ? 0 : Math.ceil(chars / cpt);
const costUsd = costPer1kUsd > 0 ? (tokens / 1000) * costPer1kUsd : 0;
return { tokens, chars, costUsd };
}, [text, costPer1kUsd, charsPerToken]);
}