@data-club/ai-hub 0.0.9 → 0.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/dist/auth/locales/cs.d.ts +1 -1
  2. package/dist/auth/locales/cs.d.ts.map +1 -1
  3. package/dist/auth/locales/cs.js +1 -12
  4. package/dist/auth/locales/cs.js.map +1 -1
  5. package/dist/auth/locales/sk.d.ts +1 -1
  6. package/dist/auth/locales/sk.d.ts.map +1 -1
  7. package/dist/auth/locales/sk.js +1 -12
  8. package/dist/auth/locales/sk.js.map +1 -1
  9. package/dist/auth/texts.d.ts +0 -17
  10. package/dist/auth/texts.d.ts.map +1 -1
  11. package/dist/auth/texts.js +0 -11
  12. package/dist/auth/texts.js.map +1 -1
  13. package/dist/client/AIHubClient.d.ts +2 -39
  14. package/dist/client/AIHubClient.d.ts.map +1 -1
  15. package/dist/client/AIHubClient.js +2 -13
  16. package/dist/client/AIHubClient.js.map +1 -1
  17. package/dist/components/DataClubAIHubAdmin/SettingsPage.d.ts +7 -8
  18. package/dist/components/DataClubAIHubAdmin/SettingsPage.d.ts.map +1 -1
  19. package/dist/components/DataClubAIHubAdmin/SettingsPage.js +8 -83
  20. package/dist/components/DataClubAIHubAdmin/SettingsPage.js.map +1 -1
  21. package/dist/components/MessageList/index.d.ts.map +1 -1
  22. package/dist/components/MessageList/index.js +1 -2
  23. package/dist/components/MessageList/index.js.map +1 -1
  24. package/dist/context/AIHubProvider.d.ts.map +1 -1
  25. package/dist/context/AIHubProvider.js +1 -31
  26. package/dist/context/AIHubProvider.js.map +1 -1
  27. package/dist/index.d.ts +1 -3
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +0 -1
  30. package/dist/index.js.map +1 -1
  31. package/dist/state/stateReducer.d.ts.map +1 -1
  32. package/dist/state/stateReducer.js +0 -1
  33. package/dist/state/stateReducer.js.map +1 -1
  34. package/dist/types.d.ts +1 -73
  35. package/dist/types.d.ts.map +1 -1
  36. package/package.json +2 -1
  37. package/src/auth/locales/cs.ts +1 -17
  38. package/src/auth/locales/sk.ts +1 -17
  39. package/src/auth/texts.ts +0 -34
  40. package/src/client/AIHubClient.ts +2 -42
  41. package/src/components/DataClubAIHubAdmin/SettingsPage.tsx +8 -177
  42. package/src/components/MessageList/index.tsx +0 -4
  43. package/src/context/AIHubProvider.tsx +1 -32
  44. package/src/index.ts +0 -8
  45. package/src/state/stateReducer.ts +0 -1
  46. package/src/styles/default.css +0 -63
  47. package/src/types.ts +0 -75
  48. package/src/components/CacheBadge/index.tsx +0 -245
@@ -1,245 +0,0 @@
1
- /**
2
- * `<CacheBadge />` — small per-message badge surfacing provider token
3
- * usage on an assistant bubble. Renders an inline icon + a compact
4
- * token count, with the full breakdown in a `title`-attribute tooltip
5
- * on hover.
6
- *
7
- * **What it shows.** Letta reports five things per assistant turn (via
8
- * `AIHubMessageUsage`): input tokens, output tokens, cache-read tokens
9
- * (prompt-cache HIT — provider charged ~0.1x), cache-write tokens
10
- * (provider charged ~1.25x, Anthropic only), reasoning tokens, and a
11
- * rolling context-window estimate.
12
- *
13
- * The headline number we show in the badge is the **cache-hit
14
- * percentage of the prompt** — `cache_read / (input + cache_read +
15
- * cache_write)`. That number is the single most useful at-a-glance
16
- * signal:
17
- * - 0% → cache is cold (cost is the worst case)
18
- * - 80% → ~10x cheaper system prompt than a cold turn
19
- *
20
- * The full numbers live in the tooltip. We use the native `title`
21
- * attribute instead of a custom popover to keep the component
22
- * lightweight and dependency-free (no portals, no animation, no
23
- * focus management — perfect for a peripheral info chip).
24
- *
25
- * **When to render.** Only when the parent passes a `usage` prop with
26
- * at least one non-null number. Messages without a usage stamp (e.g.
27
- * historical conversations from before this feature shipped, or
28
- * tool-only assistant turns where Letta didn't report anything)
29
- * simply don't render the badge.
30
- *
31
- * Exported under `AIHub.CacheBadge` (re-exported via the namespace).
32
- *
33
- * @see AIHubMessageUsage in `../../types.ts`
34
- * @see SPECIFICATIONS.md §9.4 — `usage` SSE event
35
- */
36
-
37
- import type { ReactNode } from "react";
38
-
39
- import type { AIHubMessageUsage } from "../../types.js";
40
-
41
- /** BEM class names emitted by `<CacheBadge />`. */
42
- export interface CacheBadgeClassNames {
43
- /** Outer span; `data-cache-state="hot|warm|cold|none"` for skinning. */
44
- root: string;
45
- /** The leading icon character (default 💾). */
46
- icon: string;
47
- /** The headline percentage label. */
48
- label: string;
49
- }
50
-
51
- const DEFAULT_CLASS_NAMES: CacheBadgeClassNames = Object.freeze({
52
- root: "data-club-ai-hub__cache-badge",
53
- icon: "data-club-ai-hub__cache-badge__icon",
54
- label: "data-club-ai-hub__cache-badge__label",
55
- }) as CacheBadgeClassNames;
56
-
57
- /** Localizable strings for the badge headline + tooltip. */
58
- export interface CacheBadgeTexts {
59
- /** Headline shown when there's no cache info at all (e.g. providers that don't report). */
60
- noCacheLabel: string;
61
- /** Headline shown when this turn was a full cache miss (no tokens served from cache). */
62
- missLabel: string;
63
- /** Headline shown when this turn was a cache priming write — read=0 but write>0. */
64
- writePrimingLabel: string;
65
- /** Headline prefix for cache hits — full text is `${hitLabel} ${N}%`. */
66
- hitLabel: string;
67
- /** Tooltip line prefixes. Keep short — the tooltip is `title` only. */
68
- hitTooltipLabel: string;
69
- writeTooltipLabel: string;
70
- inputLabel: string;
71
- outputLabel: string;
72
- reasoningLabel: string;
73
- contextLabel: string;
74
- totalLabel: string;
75
- }
76
-
77
- export const DEFAULT_TEXTS: CacheBadgeTexts = Object.freeze({
78
- noCacheLabel: "no data",
79
- missLabel: "MISS",
80
- writePrimingLabel: "PRIMING",
81
- hitLabel: "HIT",
82
- hitTooltipLabel: "Cache hit",
83
- writeTooltipLabel: "Cache write",
84
- inputLabel: "Input",
85
- outputLabel: "Output",
86
- reasoningLabel: "Reasoning",
87
- contextLabel: "Context",
88
- totalLabel: "Total",
89
- }) as CacheBadgeTexts;
90
-
91
- /** Props for `<CacheBadge />`. */
92
- export interface CacheBadgeProps {
93
- /** Per-message usage, as returned by `GET /sessions/:id/messages`. */
94
- usage: AIHubMessageUsage;
95
- /** Optional class-name overrides. */
96
- classNames?: Partial<CacheBadgeClassNames>;
97
- /** Optional text overrides (l10n). */
98
- texts?: Partial<CacheBadgeTexts>;
99
- }
100
-
101
- /**
102
- * Computes the cache-hit ratio (0..1) over the prompt-side tokens.
103
- *
104
- * Denominator: `input + cache_read + cache_write` — every prompt
105
- * token the provider saw, regardless of whether it served from cache
106
- * or wrote new. We exclude `output` and `reasoning` because those
107
- * are completion-side and can't benefit from prompt caching.
108
- *
109
- * Exported so the test suite can assert numerically.
110
- */
111
- export function computeCacheHitRatio(usage: AIHubMessageUsage): number | null {
112
- const read = usage.cache_read ?? 0;
113
- const write = usage.cache_write ?? 0;
114
- const input = usage.input ?? 0;
115
- const denom = read + write + input;
116
- if (denom === 0) return null;
117
- return read / denom;
118
- }
119
-
120
- /**
121
- * Bucket the usage into a discrete state for skinning + label choice.
122
- *
123
- * - `none` — no prompt-side tokens reported at all (provider
124
- * didn't expose usage, or empty turn). Badge shows
125
- * the "no data" label.
126
- * - `miss` — full cache miss: `cache_read = 0` AND
127
- * `cache_write = 0` (or not reported). The provider
128
- * had to reinitialize the whole prompt. Badge shows
129
- * "MISS" in cold (grey) styling.
130
- * - `priming` — `cache_read = 0` but `cache_write > 0`. This
131
- * turn's prompt was a CACHE WRITE — the provider
132
- * paid full price now and future turns benefit.
133
- * Anthropic only (the other providers don't report
134
- * writes). Badge shows "PRIMING".
135
- * - `cold` — `cache_read > 0` but ratio < 10%. Hit registered
136
- * but most of the prompt was new.
137
- * - `warm` — 10–60% of the prompt was cached.
138
- * - `hot` — 60%+ of the prompt was cached.
139
- */
140
- type CacheState = "none" | "miss" | "priming" | "cold" | "warm" | "hot";
141
-
142
- function bucketState(usage: AIHubMessageUsage, ratio: number | null): CacheState {
143
- if (ratio === null) return "none";
144
- if (ratio === 0) {
145
- // No cache hit. Distinguish full-miss (nothing cached anywhere)
146
- // from priming (we WROTE to the cache this turn — next turn will
147
- // benefit). The latter is good news, the former is bad news, so
148
- // the user-facing labels should differ.
149
- return (usage.cache_write ?? 0) > 0 ? "priming" : "miss";
150
- }
151
- if (ratio >= 0.6) return "hot";
152
- if (ratio >= 0.1) return "warm";
153
- return "cold";
154
- }
155
-
156
- /** Formats a token count as a compact human-readable string: 1234 → 1.2k. */
157
- function formatTokens(n: number | null | undefined): string {
158
- if (n === null || n === undefined) return "—";
159
- if (n < 1000) return String(n);
160
- if (n < 1_000_000) return `${(n / 1000).toFixed(1)}k`;
161
- return `${(n / 1_000_000).toFixed(2)}M`;
162
- }
163
-
164
- /** Builds the `title`-attribute tooltip text from the usage object. */
165
- function buildTooltip(usage: AIHubMessageUsage, texts: CacheBadgeTexts): string {
166
- const lines: string[] = [];
167
- if (usage.cache_read !== null && usage.cache_read !== undefined) {
168
- lines.push(`${texts.hitTooltipLabel}: ${formatTokens(usage.cache_read)}`);
169
- }
170
- if (usage.cache_write !== null && usage.cache_write !== undefined) {
171
- lines.push(`${texts.writeTooltipLabel}: ${formatTokens(usage.cache_write)}`);
172
- }
173
- if (usage.input !== null && usage.input !== undefined) {
174
- lines.push(`${texts.inputLabel}: ${formatTokens(usage.input)}`);
175
- }
176
- if (usage.output !== null && usage.output !== undefined) {
177
- lines.push(`${texts.outputLabel}: ${formatTokens(usage.output)}`);
178
- }
179
- if (usage.reasoning !== null && usage.reasoning !== undefined && usage.reasoning > 0) {
180
- lines.push(`${texts.reasoningLabel}: ${formatTokens(usage.reasoning)}`);
181
- }
182
- if (usage.context !== null && usage.context !== undefined) {
183
- lines.push(`${texts.contextLabel}: ${formatTokens(usage.context)}`);
184
- }
185
- if (usage.total !== null && usage.total !== undefined) {
186
- lines.push(`${texts.totalLabel}: ${formatTokens(usage.total)}`);
187
- }
188
- return lines.join("\n");
189
- }
190
-
191
- /** Returns true when the usage object has at least one usable numeric field. */
192
- function hasAnyData(usage: AIHubMessageUsage): boolean {
193
- return (
194
- (usage.input ?? null) !== null ||
195
- (usage.output ?? null) !== null ||
196
- (usage.cache_read ?? null) !== null ||
197
- (usage.cache_write ?? null) !== null ||
198
- (usage.reasoning ?? null) !== null ||
199
- (usage.context ?? null) !== null ||
200
- (usage.total ?? null) !== null
201
- );
202
- }
203
-
204
- export function CacheBadge({
205
- usage,
206
- classNames,
207
- texts,
208
- }: CacheBadgeProps): ReactNode {
209
- if (!hasAnyData(usage)) return null;
210
- const cls: CacheBadgeClassNames = { ...DEFAULT_CLASS_NAMES, ...(classNames ?? {}) };
211
- const t: CacheBadgeTexts = { ...DEFAULT_TEXTS, ...(texts ?? {}) };
212
-
213
- const ratio = computeCacheHitRatio(usage);
214
- const state = bucketState(usage, ratio);
215
- // Headline label — varies by state so the user gets an immediate
216
- // diagnostic signal at-a-glance.
217
- let headline: string;
218
- if (state === "none") {
219
- headline = t.noCacheLabel;
220
- } else if (state === "miss") {
221
- headline = t.missLabel;
222
- } else if (state === "priming") {
223
- headline = t.writePrimingLabel;
224
- } else {
225
- // hot / warm / cold — ratio is guaranteed non-null here.
226
- headline = `${t.hitLabel} ${Math.round((ratio ?? 0) * 100)}%`;
227
- }
228
- const tooltip = buildTooltip(usage, t);
229
-
230
- return (
231
- <span
232
- className={cls.root}
233
- data-testid="cache-badge"
234
- data-cache-state={state}
235
- title={tooltip}
236
- role="status"
237
- aria-label={tooltip}
238
- >
239
- <span className={cls.icon} aria-hidden="true">
240
- 💾
241
- </span>
242
- <span className={cls.label}>{headline}</span>
243
- </span>
244
- );
245
- }