──────────────────────────────────────────────────────────────────
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.
50 lines
1.6 KiB
TypeScript
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]);
|
|
}
|