@affectively/aeon-pages 1.3.0

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 (124) hide show
  1. package/CHANGELOG.md +112 -0
  2. package/README.md +625 -0
  3. package/examples/basic/aeon.config.ts +39 -0
  4. package/examples/basic/components/Cursor.tsx +86 -0
  5. package/examples/basic/components/OfflineIndicator.tsx +103 -0
  6. package/examples/basic/components/PresenceBar.tsx +77 -0
  7. package/examples/basic/package.json +20 -0
  8. package/examples/basic/pages/index.tsx +80 -0
  9. package/package.json +101 -0
  10. package/packages/analytics/README.md +309 -0
  11. package/packages/analytics/build.ts +35 -0
  12. package/packages/analytics/package.json +50 -0
  13. package/packages/analytics/src/click-tracker.ts +368 -0
  14. package/packages/analytics/src/context-bridge.ts +319 -0
  15. package/packages/analytics/src/data-layer.ts +302 -0
  16. package/packages/analytics/src/gtm-loader.ts +239 -0
  17. package/packages/analytics/src/index.ts +230 -0
  18. package/packages/analytics/src/merkle-tree.ts +489 -0
  19. package/packages/analytics/src/provider.tsx +300 -0
  20. package/packages/analytics/src/types.ts +320 -0
  21. package/packages/analytics/src/use-analytics.ts +296 -0
  22. package/packages/analytics/tsconfig.json +19 -0
  23. package/packages/benchmarks/src/benchmark.test.ts +691 -0
  24. package/packages/cli/dist/index.js +61899 -0
  25. package/packages/cli/package.json +43 -0
  26. package/packages/cli/src/commands/build.test.ts +682 -0
  27. package/packages/cli/src/commands/build.ts +890 -0
  28. package/packages/cli/src/commands/dev.ts +473 -0
  29. package/packages/cli/src/commands/init.ts +409 -0
  30. package/packages/cli/src/commands/start.ts +297 -0
  31. package/packages/cli/src/index.ts +105 -0
  32. package/packages/directives/src/use-aeon.ts +272 -0
  33. package/packages/mcp-server/package.json +51 -0
  34. package/packages/mcp-server/src/index.ts +178 -0
  35. package/packages/mcp-server/src/resources.ts +346 -0
  36. package/packages/mcp-server/src/tools/index.ts +36 -0
  37. package/packages/mcp-server/src/tools/navigation.ts +545 -0
  38. package/packages/mcp-server/tsconfig.json +21 -0
  39. package/packages/react/package.json +40 -0
  40. package/packages/react/src/Link.tsx +388 -0
  41. package/packages/react/src/components/InstallPrompt.tsx +286 -0
  42. package/packages/react/src/components/OfflineDiagnostics.tsx +677 -0
  43. package/packages/react/src/components/PushNotifications.tsx +453 -0
  44. package/packages/react/src/hooks/useAeonNavigation.ts +219 -0
  45. package/packages/react/src/hooks/useConflicts.ts +277 -0
  46. package/packages/react/src/hooks/useNetworkState.ts +209 -0
  47. package/packages/react/src/hooks/usePilotNavigation.ts +254 -0
  48. package/packages/react/src/hooks/useServiceWorker.ts +278 -0
  49. package/packages/react/src/hooks.ts +195 -0
  50. package/packages/react/src/index.ts +151 -0
  51. package/packages/react/src/provider.tsx +467 -0
  52. package/packages/react/tsconfig.json +19 -0
  53. package/packages/runtime/README.md +399 -0
  54. package/packages/runtime/build.ts +48 -0
  55. package/packages/runtime/package.json +71 -0
  56. package/packages/runtime/schema.sql +40 -0
  57. package/packages/runtime/src/api-routes.ts +465 -0
  58. package/packages/runtime/src/benchmark.ts +171 -0
  59. package/packages/runtime/src/cache.ts +479 -0
  60. package/packages/runtime/src/durable-object.ts +1341 -0
  61. package/packages/runtime/src/index.ts +360 -0
  62. package/packages/runtime/src/navigation.test.ts +421 -0
  63. package/packages/runtime/src/navigation.ts +422 -0
  64. package/packages/runtime/src/nextjs-adapter.ts +272 -0
  65. package/packages/runtime/src/offline/encrypted-queue.test.ts +607 -0
  66. package/packages/runtime/src/offline/encrypted-queue.ts +478 -0
  67. package/packages/runtime/src/offline/encryption.test.ts +412 -0
  68. package/packages/runtime/src/offline/encryption.ts +397 -0
  69. package/packages/runtime/src/offline/types.ts +465 -0
  70. package/packages/runtime/src/predictor.ts +371 -0
  71. package/packages/runtime/src/registry.ts +351 -0
  72. package/packages/runtime/src/router/context-extractor.ts +661 -0
  73. package/packages/runtime/src/router/esi-control-react.tsx +2053 -0
  74. package/packages/runtime/src/router/esi-control.ts +541 -0
  75. package/packages/runtime/src/router/esi-cyrano.ts +779 -0
  76. package/packages/runtime/src/router/esi-format-react.tsx +1744 -0
  77. package/packages/runtime/src/router/esi-react.tsx +1065 -0
  78. package/packages/runtime/src/router/esi-translate-observer.ts +476 -0
  79. package/packages/runtime/src/router/esi-translate-react.tsx +556 -0
  80. package/packages/runtime/src/router/esi-translate.ts +503 -0
  81. package/packages/runtime/src/router/esi.ts +666 -0
  82. package/packages/runtime/src/router/heuristic-adapter.test.ts +295 -0
  83. package/packages/runtime/src/router/heuristic-adapter.ts +557 -0
  84. package/packages/runtime/src/router/index.ts +298 -0
  85. package/packages/runtime/src/router/merkle-capability.ts +473 -0
  86. package/packages/runtime/src/router/speculation.ts +451 -0
  87. package/packages/runtime/src/router/types.ts +630 -0
  88. package/packages/runtime/src/router.test.ts +470 -0
  89. package/packages/runtime/src/router.ts +302 -0
  90. package/packages/runtime/src/server.ts +481 -0
  91. package/packages/runtime/src/service-worker-push.ts +319 -0
  92. package/packages/runtime/src/service-worker.ts +553 -0
  93. package/packages/runtime/src/skeleton-hydrate.ts +237 -0
  94. package/packages/runtime/src/speculation.test.ts +389 -0
  95. package/packages/runtime/src/speculation.ts +486 -0
  96. package/packages/runtime/src/storage.test.ts +1297 -0
  97. package/packages/runtime/src/storage.ts +1048 -0
  98. package/packages/runtime/src/sync/conflict-resolver.test.ts +528 -0
  99. package/packages/runtime/src/sync/conflict-resolver.ts +565 -0
  100. package/packages/runtime/src/sync/coordinator.test.ts +608 -0
  101. package/packages/runtime/src/sync/coordinator.ts +596 -0
  102. package/packages/runtime/src/tree-compiler.ts +295 -0
  103. package/packages/runtime/src/types.ts +728 -0
  104. package/packages/runtime/src/worker.ts +327 -0
  105. package/packages/runtime/tsconfig.json +20 -0
  106. package/packages/runtime/wasm/aeon_pages_runtime.d.ts +504 -0
  107. package/packages/runtime/wasm/aeon_pages_runtime.js +1657 -0
  108. package/packages/runtime/wasm/aeon_pages_runtime_bg.wasm +0 -0
  109. package/packages/runtime/wasm/aeon_pages_runtime_bg.wasm.d.ts +196 -0
  110. package/packages/runtime/wasm/package.json +21 -0
  111. package/packages/runtime/wrangler.toml +41 -0
  112. package/packages/runtime-wasm/Cargo.lock +436 -0
  113. package/packages/runtime-wasm/Cargo.toml +29 -0
  114. package/packages/runtime-wasm/pkg/aeon_pages_runtime.d.ts +480 -0
  115. package/packages/runtime-wasm/pkg/aeon_pages_runtime.js +1568 -0
  116. package/packages/runtime-wasm/pkg/aeon_pages_runtime_bg.wasm +0 -0
  117. package/packages/runtime-wasm/pkg/aeon_pages_runtime_bg.wasm.d.ts +192 -0
  118. package/packages/runtime-wasm/pkg/package.json +21 -0
  119. package/packages/runtime-wasm/src/hydrate.rs +352 -0
  120. package/packages/runtime-wasm/src/lib.rs +191 -0
  121. package/packages/runtime-wasm/src/render.rs +629 -0
  122. package/packages/runtime-wasm/src/router.rs +298 -0
  123. package/packages/runtime-wasm/src/skeleton.rs +430 -0
  124. package/rfcs/RFC-001-ZERO-DEPENDENCY-RENDERING.md +1446 -0
@@ -0,0 +1,1065 @@
1
+ /**
2
+ * ESI React Components
3
+ *
4
+ * Bring AI to templates with declarative components.
5
+ *
6
+ * @example
7
+ * ```tsx
8
+ * import { ESI } from '@affectively/aeon-flux/esi';
9
+ *
10
+ * function PersonalizedGreeting() {
11
+ * return (
12
+ * <ESI.Infer
13
+ * model="llm"
14
+ * contextAware
15
+ * signals={['emotion', 'time']}
16
+ * >
17
+ * Generate a warm greeting for the user
18
+ * </ESI.Infer>
19
+ * );
20
+ * }
21
+ *
22
+ * function EmotionAwareContent({ content }) {
23
+ * return (
24
+ * <ESI.Infer
25
+ * model="llm"
26
+ * temperature={0.7}
27
+ * fallback="Here's today's content..."
28
+ * >
29
+ * {`Adapt this content to be emotionally supportive: ${content}`}
30
+ * </ESI.Infer>
31
+ * );
32
+ * }
33
+ * ```
34
+ */
35
+
36
+ import {
37
+ createContext,
38
+ useContext,
39
+ useEffect,
40
+ useState,
41
+ useCallback,
42
+ type ReactNode,
43
+ type FC,
44
+ } from 'react';
45
+ import type {
46
+ ESIConfig,
47
+ ESIDirective,
48
+ ESIModel,
49
+ ESIParams,
50
+ ESIProcessor,
51
+ ESIResult,
52
+ UserContext,
53
+ } from './types';
54
+ import {
55
+ EdgeWorkersESIProcessor,
56
+ esiInfer,
57
+ esiEmbed,
58
+ esiEmotion,
59
+ esiVision,
60
+ esiWithContext,
61
+ } from './esi';
62
+
63
+ // Import control components for ESI namespace extension
64
+ import {
65
+ ESIStructured,
66
+ ESIIf,
67
+ ESIShow,
68
+ ESIHide,
69
+ ESIWhen,
70
+ ESIUnless,
71
+ ESIMatch,
72
+ ESICase,
73
+ ESIDefault,
74
+ ESIFirst,
75
+ ESITierGate,
76
+ ESIEmotionGate,
77
+ ESITimeGate,
78
+ ESIForEach,
79
+ ESISelect,
80
+ ESIABTest,
81
+ ESIClamp,
82
+ ESIScore,
83
+ ESICollaborative,
84
+ ESIReflect,
85
+ ESIOptimize,
86
+ ESIAuto,
87
+ } from './esi-control-react';
88
+
89
+ // Import format components for ESI namespace extension
90
+ import {
91
+ ESIMarkdown,
92
+ ESILatex,
93
+ ESIJson,
94
+ ESIPlaintext,
95
+ ESICode,
96
+ ESISemantic,
97
+ } from './esi-format-react';
98
+
99
+ // Import translation components for ESI namespace extension
100
+ import { ESITranslate, TranslationProvider } from './esi-translate-react';
101
+
102
+ // ============================================================================
103
+ // ESI Context
104
+ // ============================================================================
105
+
106
+ interface ESIContextValue {
107
+ processor: ESIProcessor;
108
+ userContext: UserContext | null;
109
+ enabled: boolean;
110
+ process: (directive: ESIDirective) => Promise<ESIResult>;
111
+ processWithStream: (
112
+ directive: ESIDirective,
113
+ onChunk: (chunk: string) => void,
114
+ ) => Promise<ESIResult>;
115
+ }
116
+
117
+ const ESIContext = createContext<ESIContextValue | null>(null);
118
+
119
+ export interface ESIProviderProps {
120
+ children: ReactNode;
121
+ config?: Partial<ESIConfig>;
122
+ userContext?: UserContext;
123
+ processor?: ESIProcessor;
124
+ }
125
+
126
+ /**
127
+ * ESI Provider - enables ESI components in the tree
128
+ */
129
+ export const ESIProvider: FC<ESIProviderProps> = ({
130
+ children,
131
+ config,
132
+ userContext,
133
+ processor: customProcessor,
134
+ }) => {
135
+ const [processor] = useState(
136
+ () => customProcessor || new EdgeWorkersESIProcessor(config),
137
+ );
138
+
139
+ useEffect(() => {
140
+ processor.warmup?.();
141
+ }, [processor]);
142
+
143
+ const process = useCallback(
144
+ async (directive: ESIDirective) => {
145
+ if (!userContext) {
146
+ return {
147
+ id: directive.id,
148
+ success: false,
149
+ error: 'No user context available',
150
+ latencyMs: 0,
151
+ cached: false,
152
+ model: directive.params.model,
153
+ };
154
+ }
155
+ return processor.process(directive, userContext);
156
+ },
157
+ [processor, userContext],
158
+ );
159
+
160
+ const processWithStream = useCallback(
161
+ async (directive: ESIDirective, onChunk: (chunk: string) => void) => {
162
+ if (!userContext) {
163
+ return {
164
+ id: directive.id,
165
+ success: false,
166
+ error: 'No user context available',
167
+ latencyMs: 0,
168
+ cached: false,
169
+ model: directive.params.model,
170
+ };
171
+ }
172
+ if (!processor.stream) {
173
+ return processor.process(directive, userContext);
174
+ }
175
+ return processor.stream(directive, userContext, onChunk);
176
+ },
177
+ [processor, userContext],
178
+ );
179
+
180
+ return (
181
+ <ESIContext.Provider
182
+ value={{
183
+ processor,
184
+ userContext: userContext || null,
185
+ enabled: config?.enabled ?? true,
186
+ process,
187
+ processWithStream,
188
+ }}
189
+ >
190
+ {children}
191
+ </ESIContext.Provider>
192
+ );
193
+ };
194
+
195
+ /**
196
+ * Hook to access ESI context
197
+ */
198
+ export function useESI() {
199
+ const ctx = useContext(ESIContext);
200
+ if (!ctx) {
201
+ throw new Error('useESI must be used within an ESIProvider');
202
+ }
203
+ return ctx;
204
+ }
205
+
206
+ // ============================================================================
207
+ // ESI Components
208
+ // ============================================================================
209
+
210
+ export interface ESIInferProps {
211
+ /** The prompt - can be children or explicit prop */
212
+ children?: ReactNode;
213
+ prompt?: string;
214
+
215
+ /** Model to use */
216
+ model?: ESIModel;
217
+
218
+ /** Model variant */
219
+ variant?: string;
220
+
221
+ /** Temperature for generation */
222
+ temperature?: number;
223
+
224
+ /** Maximum tokens */
225
+ maxTokens?: number;
226
+
227
+ /** System prompt */
228
+ system?: string;
229
+
230
+ /** Enable streaming */
231
+ stream?: boolean;
232
+
233
+ /** Fallback content if inference fails */
234
+ fallback?: ReactNode;
235
+
236
+ /** Loading content */
237
+ loading?: ReactNode;
238
+
239
+ /** Inject user context */
240
+ contextAware?: boolean;
241
+
242
+ /** Context signals to include */
243
+ signals?: Array<'emotion' | 'preferences' | 'history' | 'time' | 'device'>;
244
+
245
+ /** Cache TTL in seconds */
246
+ cacheTtl?: number;
247
+
248
+ /** Custom render function */
249
+ render?: (result: ESIResult) => ReactNode;
250
+
251
+ /** Class name for wrapper */
252
+ className?: string;
253
+
254
+ /** Callback when inference completes */
255
+ onComplete?: (result: ESIResult) => void;
256
+
257
+ /** Callback on error */
258
+ onError?: (error: string) => void;
259
+ }
260
+
261
+ /**
262
+ * ESI Inference Component
263
+ *
264
+ * @example
265
+ * ```tsx
266
+ * <ESI.Infer model="llm" temperature={0.7}>
267
+ * Write a haiku about React
268
+ * </ESI.Infer>
269
+ * ```
270
+ */
271
+ export const ESIInfer: FC<ESIInferProps> = ({
272
+ children,
273
+ prompt,
274
+ model = 'llm',
275
+ variant,
276
+ temperature,
277
+ maxTokens,
278
+ system,
279
+ stream = false,
280
+ fallback,
281
+ loading = '...',
282
+ contextAware = false,
283
+ signals,
284
+ cacheTtl,
285
+ render,
286
+ className,
287
+ onComplete,
288
+ onError,
289
+ }) => {
290
+ const { process, processWithStream, enabled } = useESI();
291
+ const [output, setOutput] = useState<string>('');
292
+ const [isLoading, setIsLoading] = useState(true);
293
+ const [error, setError] = useState<string | null>(null);
294
+
295
+ const promptText =
296
+ prompt ||
297
+ (typeof children === 'string' ? children : String(children || ''));
298
+
299
+ useEffect(() => {
300
+ const fetchData = async () => {
301
+ if (!enabled) {
302
+ setOutput(typeof fallback === 'string' ? fallback : '');
303
+ setIsLoading(false);
304
+ return;
305
+ }
306
+
307
+ const directive: ESIDirective = contextAware
308
+ ? esiWithContext(promptText, signals, {
309
+ model,
310
+ variant,
311
+ temperature,
312
+ maxTokens,
313
+ system,
314
+ cacheTtl,
315
+ fallback: typeof fallback === 'string' ? fallback : undefined,
316
+ })
317
+ : esiInfer(promptText, {
318
+ model,
319
+ variant,
320
+ temperature,
321
+ maxTokens,
322
+ system,
323
+ cacheTtl,
324
+ fallback: typeof fallback === 'string' ? fallback : undefined,
325
+ });
326
+
327
+ if (stream) {
328
+ setOutput('');
329
+ await processWithStream(directive, (chunk) => {
330
+ setOutput((prev) => prev + chunk);
331
+ }).then((result) => {
332
+ setIsLoading(false);
333
+ if (!result.success) {
334
+ setError(result.error || 'Inference failed');
335
+ onError?.(result.error || 'Inference failed');
336
+ }
337
+ onComplete?.(result);
338
+ });
339
+ } else {
340
+ await process(directive).then((result) => {
341
+ setIsLoading(false);
342
+ if (result.success && result.output) {
343
+ setOutput(result.output);
344
+ } else {
345
+ setError(result.error || 'Inference failed');
346
+ onError?.(result.error || 'Inference failed');
347
+ }
348
+ onComplete?.(result);
349
+ });
350
+ }
351
+ };
352
+
353
+ fetchData();
354
+
355
+ return () => {};
356
+ }, [
357
+ promptText,
358
+ model,
359
+ variant,
360
+ temperature,
361
+ maxTokens,
362
+ system,
363
+ contextAware,
364
+ stream,
365
+ enabled,
366
+ fallback,
367
+ processWithStream,
368
+ process,
369
+ onComplete,
370
+ onError,
371
+ ]);
372
+
373
+ if (isLoading && !stream) {
374
+ return <span className={className}>{loading}</span>;
375
+ }
376
+
377
+ if (error && fallback) {
378
+ return <span className={className}>{fallback}</span>;
379
+ }
380
+
381
+ if (render) {
382
+ return (
383
+ <span className={className}>
384
+ {render({
385
+ id: '',
386
+ success: !error,
387
+ output,
388
+ error: error || undefined,
389
+ latencyMs: 0,
390
+ cached: false,
391
+ model,
392
+ })}
393
+ </span>
394
+ );
395
+ }
396
+
397
+ return (
398
+ <span className={className}>{output || (isLoading ? loading : '')}</span>
399
+ );
400
+ };
401
+
402
+ export interface ESIEmbedProps {
403
+ children: ReactNode;
404
+ onComplete?: (embedding: number[]) => void;
405
+ onError?: (error: string) => void;
406
+ }
407
+
408
+ /**
409
+ * ESI Embed Component - generates embeddings
410
+ *
411
+ * @example
412
+ * ```tsx
413
+ * <ESI.Embed onComplete={(embedding) => console.log(embedding)}>
414
+ * Text to embed
415
+ * </ESI.Embed>
416
+ * ```
417
+ */
418
+ export const ESIEmbed: FC<ESIEmbedProps> = ({
419
+ children,
420
+ onComplete,
421
+ onError,
422
+ }) => {
423
+ const { process, enabled } = useESI();
424
+ const text = typeof children === 'string' ? children : String(children || '');
425
+
426
+ useEffect(() => {
427
+ if (!enabled) return;
428
+
429
+ const directive = esiEmbed(text);
430
+ process(directive).then((result) => {
431
+ if (result.success && result.embedding) {
432
+ onComplete?.(result.embedding);
433
+ } else {
434
+ onError?.(result.error || 'Embedding failed');
435
+ }
436
+ });
437
+ }, [text, enabled]);
438
+
439
+ // Embed component is invisible - just triggers the embedding
440
+ return null;
441
+ };
442
+
443
+ export interface ESIEmotionProps {
444
+ children: ReactNode;
445
+ contextAware?: boolean;
446
+ onComplete?: (result: { emotion: string; confidence: number }) => void;
447
+ onError?: (error: string) => void;
448
+ }
449
+
450
+ /**
451
+ * ESI Emotion Component - detects emotion from text
452
+ *
453
+ * @example
454
+ * ```tsx
455
+ * <ESI.Emotion onComplete={({ emotion }) => setMood(emotion)}>
456
+ * {userInput}
457
+ * </ESI.Emotion>
458
+ * ```
459
+ */
460
+ export const ESIEmotion: FC<ESIEmotionProps> = ({
461
+ children,
462
+ contextAware = true,
463
+ onComplete,
464
+ onError,
465
+ }) => {
466
+ const { process, enabled } = useESI();
467
+ const text = typeof children === 'string' ? children : String(children || '');
468
+
469
+ useEffect(() => {
470
+ if (!enabled) return;
471
+
472
+ const directive = esiEmotion(text, contextAware);
473
+ process(directive).then((result) => {
474
+ if (result.success && result.output) {
475
+ try {
476
+ const parsed = JSON.parse(result.output);
477
+ onComplete?.(parsed);
478
+ } catch {
479
+ onComplete?.({ emotion: result.output, confidence: 1 });
480
+ }
481
+ } else {
482
+ onError?.(result.error || 'Emotion detection failed');
483
+ }
484
+ });
485
+ }, [text, contextAware, enabled]);
486
+
487
+ return null;
488
+ };
489
+
490
+ export interface ESIVisionProps {
491
+ src: string; // base64 image
492
+ prompt: string;
493
+ fallback?: ReactNode;
494
+ loading?: ReactNode;
495
+ className?: string;
496
+ onComplete?: (result: ESIResult) => void;
497
+ onError?: (error: string) => void;
498
+ }
499
+
500
+ /**
501
+ * ESI Vision Component - analyzes images
502
+ *
503
+ * @example
504
+ * ```tsx
505
+ * <ESI.Vision
506
+ * src={base64Image}
507
+ * prompt="Describe what you see"
508
+ * fallback="Unable to analyze image"
509
+ * />
510
+ * ```
511
+ */
512
+ export const ESIVision: FC<ESIVisionProps> = ({
513
+ src,
514
+ prompt,
515
+ fallback,
516
+ loading = '...',
517
+ className,
518
+ onComplete,
519
+ onError,
520
+ }) => {
521
+ const { process, enabled } = useESI();
522
+ const [output, setOutput] = useState<string>('');
523
+ const [isLoading, setIsLoading] = useState(true);
524
+ const [error, setError] = useState<string | null>(null);
525
+
526
+ useEffect(() => {
527
+ if (!enabled) {
528
+ setOutput(typeof fallback === 'string' ? fallback : '');
529
+ setIsLoading(false);
530
+ return;
531
+ }
532
+
533
+ const directive = esiVision(src, prompt);
534
+ process(directive).then((result) => {
535
+ setIsLoading(false);
536
+ if (result.success && result.output) {
537
+ setOutput(result.output);
538
+ } else {
539
+ setError(result.error || 'Vision analysis failed');
540
+ onError?.(result.error || 'Vision analysis failed');
541
+ }
542
+ onComplete?.(result);
543
+ });
544
+ }, [src, prompt, enabled]);
545
+
546
+ if (isLoading) {
547
+ return <span className={className}>{loading}</span>;
548
+ }
549
+
550
+ if (error && fallback) {
551
+ return <span className={className}>{fallback}</span>;
552
+ }
553
+
554
+ return <span className={className}>{output}</span>;
555
+ };
556
+
557
+ // ============================================================================
558
+ // Hook for programmatic ESI
559
+ // ============================================================================
560
+
561
+ export interface UseESIInferOptions
562
+ extends Omit<
563
+ ESIInferProps,
564
+ 'children' | 'loading' | 'fallback' | 'render' | 'className'
565
+ > {
566
+ autoRun?: boolean;
567
+ }
568
+
569
+ /**
570
+ * Hook for programmatic ESI inference
571
+ *
572
+ * @example
573
+ * ```tsx
574
+ * function MyComponent() {
575
+ * const { run, result, isLoading, error } = useESIInfer({
576
+ * model: 'llm',
577
+ * contextAware: true,
578
+ * });
579
+ *
580
+ * return (
581
+ * <button onClick={() => run('Generate a greeting')}>
582
+ * {isLoading ? 'Generating...' : result?.output || 'Click to generate'}
583
+ * </button>
584
+ * );
585
+ * }
586
+ * ```
587
+ */
588
+ export function useESIInfer(options: UseESIInferOptions = {}) {
589
+ const { process, processWithStream, enabled } = useESI();
590
+ const [result, setResult] = useState<ESIResult | null>(null);
591
+ const [isLoading, setIsLoading] = useState(false);
592
+ const [error, setError] = useState<string | null>(null);
593
+
594
+ const run = useCallback(
595
+ async (prompt: string) => {
596
+ if (!enabled) {
597
+ setError('ESI is disabled');
598
+ return null;
599
+ }
600
+
601
+ setIsLoading(true);
602
+ setError(null);
603
+
604
+ const directive: ESIDirective = options.contextAware
605
+ ? esiWithContext(prompt, options.signals, {
606
+ model: options.model,
607
+ variant: options.variant,
608
+ temperature: options.temperature,
609
+ maxTokens: options.maxTokens,
610
+ system: options.system,
611
+ cacheTtl: options.cacheTtl,
612
+ })
613
+ : esiInfer(prompt, {
614
+ model: options.model,
615
+ variant: options.variant,
616
+ temperature: options.temperature,
617
+ maxTokens: options.maxTokens,
618
+ system: options.system,
619
+ cacheTtl: options.cacheTtl,
620
+ });
621
+
622
+ try {
623
+ let inferenceResult: ESIResult;
624
+
625
+ if (options.stream) {
626
+ let output = '';
627
+ inferenceResult = await processWithStream(directive, (chunk) => {
628
+ output += chunk;
629
+ setResult((prev) => ({
630
+ ...prev!,
631
+ output,
632
+ }));
633
+ });
634
+ } else {
635
+ inferenceResult = await process(directive);
636
+ }
637
+
638
+ setResult(inferenceResult);
639
+ setIsLoading(false);
640
+
641
+ if (!inferenceResult.success) {
642
+ setError(inferenceResult.error || 'Inference failed');
643
+ }
644
+
645
+ options.onComplete?.(inferenceResult);
646
+ return inferenceResult;
647
+ } catch (err) {
648
+ const errorMsg = err instanceof Error ? err.message : 'Unknown error';
649
+ setError(errorMsg);
650
+ setIsLoading(false);
651
+ options.onError?.(errorMsg);
652
+ return null;
653
+ }
654
+ },
655
+ [process, processWithStream, enabled, options],
656
+ );
657
+
658
+ const reset = useCallback(() => {
659
+ setResult(null);
660
+ setError(null);
661
+ setIsLoading(false);
662
+ }, []);
663
+
664
+ return { run, result, isLoading, error, reset };
665
+ }
666
+
667
+ // ============================================================================
668
+ // Global ESI State Hook (consumes window.__AEON_ESI_STATE__)
669
+ // ============================================================================
670
+
671
+ /**
672
+ * Global ESI State type (matches ESIState from context-extractor)
673
+ */
674
+ export interface GlobalESIState {
675
+ userTier: 'free' | 'starter' | 'pro' | 'enterprise' | 'admin';
676
+ /** Admin flag - bypasses ALL tier restrictions */
677
+ isAdmin?: boolean;
678
+ emotionState?: {
679
+ primary: string;
680
+ valence: number;
681
+ arousal: number;
682
+ confidence?: number;
683
+ } | null;
684
+ preferences: {
685
+ theme?: 'light' | 'dark' | 'auto';
686
+ reducedMotion: boolean;
687
+ language?: string;
688
+ };
689
+ sessionId?: string;
690
+ localHour: number;
691
+ timezone: string;
692
+ features: {
693
+ aiInference: boolean;
694
+ emotionTracking: boolean;
695
+ collaboration: boolean;
696
+ advancedInsights: boolean;
697
+ customThemes: boolean;
698
+ voiceSynthesis: boolean;
699
+ imageAnalysis: boolean;
700
+ };
701
+ userId?: string;
702
+ isNewSession: boolean;
703
+ recentPages: string[];
704
+ viewport: {
705
+ width: number;
706
+ height: number;
707
+ };
708
+ connection: string;
709
+ // Runtime methods added by prerender script
710
+ update?: (partial: Partial<GlobalESIState>) => void;
711
+ subscribe?: (listener: (state: GlobalESIState) => void) => () => void;
712
+ }
713
+
714
+ declare global {
715
+ interface Window {
716
+ __AEON_ESI_STATE__?: GlobalESIState;
717
+ }
718
+ }
719
+
720
+ /**
721
+ * Default ESI state for SSR or when global state is not available
722
+ */
723
+ const DEFAULT_ESI_STATE: GlobalESIState = {
724
+ userTier: 'free',
725
+ emotionState: null,
726
+ preferences: {
727
+ theme: 'auto',
728
+ reducedMotion: false,
729
+ },
730
+ localHour: new Date().getHours(),
731
+ timezone: 'UTC',
732
+ features: {
733
+ aiInference: true,
734
+ emotionTracking: true,
735
+ collaboration: false,
736
+ advancedInsights: false,
737
+ customThemes: false,
738
+ voiceSynthesis: false,
739
+ imageAnalysis: false,
740
+ },
741
+ isNewSession: true,
742
+ recentPages: [],
743
+ viewport: { width: 1920, height: 1080 },
744
+ connection: '4g',
745
+ };
746
+
747
+ /**
748
+ * Hook to consume global ESI state from window.__AEON_ESI_STATE__
749
+ *
750
+ * This state is injected in the <head> at pre-render time and hydrated
751
+ * with actual user context at runtime. Components can use this to
752
+ * access tier, emotion state, preferences, etc. before full React hydration.
753
+ *
754
+ * @example
755
+ * ```tsx
756
+ * function TierGatedFeature() {
757
+ * const { userTier, features } = useGlobalESIState();
758
+ *
759
+ * if (!features.advancedInsights) {
760
+ * return <UpgradePrompt />;
761
+ * }
762
+ *
763
+ * return <AdvancedInsightsPanel />;
764
+ * }
765
+ * ```
766
+ */
767
+ export function useGlobalESIState(): GlobalESIState {
768
+ const [state, setState] = useState<GlobalESIState>(() => {
769
+ if (typeof window !== 'undefined' && window.__AEON_ESI_STATE__) {
770
+ return window.__AEON_ESI_STATE__;
771
+ }
772
+ return DEFAULT_ESI_STATE;
773
+ });
774
+
775
+ useEffect(() => {
776
+ // Subscribe to state updates if available
777
+ if (typeof window !== 'undefined' && window.__AEON_ESI_STATE__?.subscribe) {
778
+ const unsubscribe = window.__AEON_ESI_STATE__.subscribe((newState) => {
779
+ setState(newState);
780
+ });
781
+ return unsubscribe;
782
+ }
783
+ return () => {}; // Return a no-op cleanup function if subscribe is not available
784
+ }, []);
785
+
786
+ return state;
787
+ }
788
+
789
+ /**
790
+ * Hook to check if user has a specific feature enabled based on tier
791
+ *
792
+ * @example
793
+ * ```tsx
794
+ * function VoiceButton() {
795
+ * const hasVoice = useESIFeature('voiceSynthesis');
796
+ *
797
+ * return hasVoice ? <VoiceControl /> : <UpgradeToProBanner />;
798
+ * }
799
+ * ```
800
+ */
801
+ export function useESIFeature(
802
+ feature: keyof GlobalESIState['features'],
803
+ ): boolean {
804
+ const { features, isAdmin, userTier } = useGlobalESIState();
805
+ // Admins bypass ALL tier restrictions
806
+ if (isAdmin === true || userTier === 'admin') {
807
+ return true;
808
+ }
809
+ return features[feature] ?? false;
810
+ }
811
+
812
+ /**
813
+ * Hook to check if current user is an admin
814
+ * Admins bypass ALL tier restrictions
815
+ */
816
+ export function useIsAdmin(): boolean {
817
+ const { isAdmin, userTier } = useGlobalESIState();
818
+ return isAdmin === true || userTier === 'admin';
819
+ }
820
+
821
+ /**
822
+ * Hook to get user tier
823
+ *
824
+ * @example
825
+ * ```tsx
826
+ * function PricingBanner() {
827
+ * const tier = useESITier();
828
+ *
829
+ * if (tier === 'pro' || tier === 'enterprise') {
830
+ * return null; // Already on paid tier
831
+ * }
832
+ *
833
+ * return <UpgradeBanner currentTier={tier} />;
834
+ * }
835
+ * ```
836
+ */
837
+ export function useESITier(): GlobalESIState['userTier'] {
838
+ const { userTier } = useGlobalESIState();
839
+ return userTier;
840
+ }
841
+
842
+ /**
843
+ * Tier level ordering for comparison
844
+ */
845
+ const TIER_ORDER: Record<GlobalESIState['userTier'], number> = {
846
+ free: 0,
847
+ starter: 1,
848
+ pro: 2,
849
+ enterprise: 3,
850
+ admin: 999,
851
+ };
852
+
853
+ /**
854
+ * Hook to check if user meets minimum tier requirement
855
+ * Admins bypass ALL tier restrictions
856
+ *
857
+ * @example
858
+ * ```tsx
859
+ * function ProFeature() {
860
+ * const hasPro = useMeetsTierRequirement('pro');
861
+ *
862
+ * if (!hasPro) {
863
+ * return <UpgradePrompt tier="pro" />;
864
+ * }
865
+ *
866
+ * return <ProFeatureContent />;
867
+ * }
868
+ * ```
869
+ */
870
+ export function useMeetsTierRequirement(
871
+ requiredTier: GlobalESIState['userTier'],
872
+ ): boolean {
873
+ const { userTier, isAdmin } = useGlobalESIState();
874
+
875
+ // Admins bypass ALL tier restrictions
876
+ if (isAdmin === true || userTier === 'admin') {
877
+ return true;
878
+ }
879
+
880
+ const userLevel = TIER_ORDER[userTier] ?? 0;
881
+ const requiredLevel = TIER_ORDER[requiredTier] ?? 0;
882
+
883
+ return userLevel >= requiredLevel;
884
+ }
885
+
886
+ /**
887
+ * Hook to get current emotion state
888
+ *
889
+ * @example
890
+ * ```tsx
891
+ * function EmotionAwareUI() {
892
+ * const emotion = useESIEmotion();
893
+ *
894
+ * if (emotion?.valence < -0.3) {
895
+ * return <SupportiveContent />;
896
+ * }
897
+ *
898
+ * return <RegularContent />;
899
+ * }
900
+ * ```
901
+ */
902
+ export function useESIEmotionState(): GlobalESIState['emotionState'] {
903
+ const { emotionState } = useGlobalESIState();
904
+ return emotionState;
905
+ }
906
+
907
+ /**
908
+ * Hook to get user preferences
909
+ */
910
+ export function useESIPreferences(): GlobalESIState['preferences'] {
911
+ const { preferences } = useGlobalESIState();
912
+ return preferences;
913
+ }
914
+
915
+ /**
916
+ * Update global ESI state at runtime (e.g., after fetching user context)
917
+ *
918
+ * @example
919
+ * ```tsx
920
+ * useEffect(() => {
921
+ * fetchUserContext().then((ctx) => {
922
+ * updateGlobalESIState({
923
+ * userTier: ctx.tier,
924
+ * userId: ctx.id,
925
+ * emotionState: ctx.emotion,
926
+ * });
927
+ * });
928
+ * }, []);
929
+ * ```
930
+ */
931
+ export function updateGlobalESIState(partial: Partial<GlobalESIState>): void {
932
+ if (typeof window !== 'undefined' && window.__AEON_ESI_STATE__?.update) {
933
+ window.__AEON_ESI_STATE__.update(partial);
934
+ } else if (typeof window !== 'undefined' && window.__AEON_ESI_STATE__) {
935
+ Object.assign(window.__AEON_ESI_STATE__, partial);
936
+ }
937
+ }
938
+
939
+ // ============================================================================
940
+ // Navigation Hook (edge-ready, no Next.js dependency)
941
+ // ============================================================================
942
+
943
+ import { getNavigator } from '../navigation.js';
944
+
945
+ export interface NavigationRouter {
946
+ push: (url: string) => void;
947
+ replace: (url: string) => void;
948
+ back: () => void;
949
+ prefetch: (url: string) => void;
950
+ }
951
+
952
+ /**
953
+ * Edge-ready navigation hook - works in both Next.js and edge environments
954
+ *
955
+ * @example
956
+ * ```tsx
957
+ * function MyComponent() {
958
+ * const router = useNavigation();
959
+ *
960
+ * return (
961
+ * <button onClick={() => router.push('/dashboard')}>
962
+ * Go to Dashboard
963
+ * </button>
964
+ * );
965
+ * }
966
+ * ```
967
+ */
968
+ export function useNavigation(): NavigationRouter {
969
+ // Try to get the Aeon navigation engine
970
+ const aeonNavigator = typeof window !== 'undefined' ? getNavigator() : null;
971
+
972
+ const push = useCallback(
973
+ (url: string) => {
974
+ if (aeonNavigator) {
975
+ aeonNavigator.navigate(url);
976
+ } else if (typeof window !== 'undefined') {
977
+ window.location.href = url;
978
+ }
979
+ },
980
+ [aeonNavigator],
981
+ );
982
+
983
+ const replace = useCallback(
984
+ (url: string) => {
985
+ if (aeonNavigator) {
986
+ aeonNavigator.navigate(url, { replace: true });
987
+ } else if (typeof window !== 'undefined') {
988
+ window.location.replace(url);
989
+ }
990
+ },
991
+ [aeonNavigator],
992
+ );
993
+
994
+ const back = useCallback(() => {
995
+ if (aeonNavigator) {
996
+ aeonNavigator.back();
997
+ } else if (typeof window !== 'undefined') {
998
+ window.history.back();
999
+ }
1000
+ }, [aeonNavigator]);
1001
+
1002
+ const prefetch = useCallback(
1003
+ (url: string) => {
1004
+ if (aeonNavigator) {
1005
+ aeonNavigator.prefetch(url);
1006
+ }
1007
+ // No-op in fallback mode
1008
+ },
1009
+ [aeonNavigator],
1010
+ );
1011
+
1012
+ return { push, replace, back, prefetch };
1013
+ }
1014
+
1015
+ // ============================================================================
1016
+ // ESI Namespace Export
1017
+ // ============================================================================
1018
+
1019
+ export const ESI = {
1020
+ // Basic components
1021
+ Provider: ESIProvider,
1022
+ Infer: ESIInfer,
1023
+ Embed: ESIEmbed,
1024
+ Emotion: ESIEmotion,
1025
+ Vision: ESIVision,
1026
+ // Translation components (from esi-translate-react)
1027
+ Translate: ESITranslate,
1028
+ TranslationProvider: TranslationProvider,
1029
+ // Control flow components (from esi-control-react)
1030
+ Structured: ESIStructured,
1031
+ If: ESIIf,
1032
+ Show: ESIShow,
1033
+ Hide: ESIHide,
1034
+ When: ESIWhen,
1035
+ Unless: ESIUnless,
1036
+ Match: ESIMatch,
1037
+ Case: ESICase,
1038
+ Default: ESIDefault,
1039
+ First: ESIFirst,
1040
+ // Gates
1041
+ TierGate: ESITierGate,
1042
+ EmotionGate: ESIEmotionGate,
1043
+ TimeGate: ESITimeGate,
1044
+ // Iteration & Selection
1045
+ ForEach: ESIForEach,
1046
+ Select: ESISelect,
1047
+ ABTest: ESIABTest,
1048
+ // Numeric
1049
+ Clamp: ESIClamp,
1050
+ Score: ESIScore,
1051
+ // Advanced
1052
+ Collaborative: ESICollaborative,
1053
+ Reflect: ESIReflect,
1054
+ Optimize: ESIOptimize,
1055
+ Auto: ESIAuto,
1056
+ // Format transformations (from esi-format-react)
1057
+ Markdown: ESIMarkdown,
1058
+ Latex: ESILatex,
1059
+ Json: ESIJson,
1060
+ Plaintext: ESIPlaintext,
1061
+ Code: ESICode,
1062
+ Semantic: ESISemantic,
1063
+ };
1064
+
1065
+ export default ESI;