@minnai/create-aura-app 0.0.12 → 0.0.15

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 (33) hide show
  1. package/package.json +1 -1
  2. package/templates/starter/package.json +2 -2
  3. package/templates/starter/src/ambiance/index.ts +1 -3
  4. package/templates/starter/src/components/Playground/Playground.tsx +1 -1
  5. package/templates/starter/src/src/App.css +0 -32
  6. package/templates/starter/src/src/App.tsx +0 -82
  7. package/templates/starter/src/src/ambiance/currency-air/index.tsx +0 -25
  8. package/templates/starter/src/src/ambiance/currency-air/logic.ts +0 -49
  9. package/templates/starter/src/src/ambiance/currency-air/manifest.ts +0 -15
  10. package/templates/starter/src/src/ambiance/currency-air/resources.ts +0 -16
  11. package/templates/starter/src/src/ambiance/currency-air/ui/index.tsx +0 -42
  12. package/templates/starter/src/src/ambiance/index.ts +0 -48
  13. package/templates/starter/src/src/ambiance/stocks-air/index.ts +0 -3
  14. package/templates/starter/src/src/ambiance/stocks-air/index.tsx +0 -28
  15. package/templates/starter/src/src/ambiance/stocks-air/logic.ts +0 -87
  16. package/templates/starter/src/src/ambiance/stocks-air/manifest.ts +0 -15
  17. package/templates/starter/src/src/ambiance/stocks-air/resources.ts +0 -23
  18. package/templates/starter/src/src/ambiance/stocks-air/ui/index.tsx +0 -67
  19. package/templates/starter/src/src/assets/react.svg +0 -1
  20. package/templates/starter/src/src/components/AnalyticsTracker.tsx +0 -13
  21. package/templates/starter/src/src/components/Playground/CodeEditor.tsx +0 -121
  22. package/templates/starter/src/src/components/Playground/Debugger.tsx +0 -71
  23. package/templates/starter/src/src/components/Playground/Playground.tsx +0 -221
  24. package/templates/starter/src/src/components/Playground/Sidebar.tsx +0 -68
  25. package/templates/starter/src/src/components/ProjectSidebar/ProjectSidebar.tsx +0 -219
  26. package/templates/starter/src/src/components/TourGuide/TourGuide.tsx +0 -16
  27. package/templates/starter/src/src/components/TourGuide/index.ts +0 -1
  28. package/templates/starter/src/src/components/TourGuide/tour-flow.yaml +0 -137
  29. package/templates/starter/src/src/components/TourGuide/useTourEngine.ts +0 -376
  30. package/templates/starter/src/src/index.css +0 -68
  31. package/templates/starter/src/src/main.tsx +0 -10
  32. package/templates/starter/src/src/services/AnalyticsService.ts +0 -181
  33. package/templates/starter/src/src/types/ContextHandler.ts +0 -13
@@ -1,376 +0,0 @@
1
- import { useState, useEffect, useRef, useCallback } from 'react';
2
- import { flux } from '@minnai/aura/flux/index';
3
-
4
- // Types for tour flow
5
- interface TourCommand {
6
- say?: string;
7
- spawn?: string;
8
- props?: any;
9
- wait?: string;
10
- match?: string | string[];
11
- air?: string;
12
- onError?: string;
13
- do?: string;
14
- label?: string;
15
- goto?: string;
16
- }
17
-
18
- interface TourStep {
19
- step: string;
20
- run: TourCommand[];
21
- }
22
-
23
- interface TourFlow {
24
- name: string;
25
- projectId: string;
26
- steps: TourStep[];
27
- }
28
-
29
- interface TourState {
30
- currentStep: string;
31
- commandIndex: number;
32
- waiting: boolean;
33
- }
34
-
35
- export function useTourEngine(flow: TourFlow, projectId: string) {
36
- const [state, setState] = useState<TourState>({
37
- currentStep: 'IDLE',
38
- commandIndex: 0,
39
- waiting: false
40
- });
41
-
42
- const initialized = useRef(false);
43
- const timers = useRef<any[]>([]);
44
-
45
- // Initialize from saved metadata
46
- useEffect(() => {
47
- if (projectId !== flow.projectId) {
48
- setState(s => ({ ...s, currentStep: 'DONE' }));
49
- return;
50
- }
51
-
52
- const unsubscribe = flux.subscribe((action: any) => {
53
- if (action.type === 'METADATA_LOADED' && !initialized.current) {
54
- const saved = action.payload.tourState as TourState;
55
- console.log('[TourEngine] Restored:', saved || 'Starting fresh');
56
- if (saved && saved.currentStep !== 'DONE') {
57
- setState(saved);
58
- } else {
59
- setState({ currentStep: 'INIT', commandIndex: 0, waiting: false });
60
- }
61
- initialized.current = true;
62
- }
63
- });
64
-
65
- // Fail-safe
66
- const timer = setTimeout(() => {
67
- if (!initialized.current) {
68
- console.log('[TourEngine] No metadata, starting fresh');
69
- setState({ currentStep: 'INIT', commandIndex: 0, waiting: false });
70
- initialized.current = true;
71
- }
72
- }, 1500);
73
-
74
- return () => {
75
- unsubscribe();
76
- clearTimeout(timer);
77
- };
78
- }, [projectId, flow.projectId]);
79
-
80
- // Persist state
81
- useEffect(() => {
82
- if (state.currentStep !== 'IDLE' && state.currentStep !== 'DONE') {
83
- flux.dispatch({
84
- type: 'SYNC_METADATA',
85
- payload: { tourState: state },
86
- to: 'controller'
87
- });
88
- }
89
- }, [state]);
90
-
91
- // Say helper
92
- const say = useCallback(async (text: string) => {
93
- flux.dispatch({
94
- type: 'SAY',
95
- payload: { text, role: 'assistant' },
96
- to: 'all'
97
- });
98
- const wordCount = text.split(/\s+/).length;
99
- const delay = wordCount * 333 + 666;
100
- await new Promise(resolve => {
101
- const timer = setTimeout(resolve, delay);
102
- timers.current.push(timer);
103
- });
104
- }, []);
105
-
106
- // Execute a single command
107
- const executeCommand = useCallback(async (cmd: TourCommand): Promise<string | null> => {
108
- if (cmd.say) {
109
- await say(cmd.say);
110
- return null;
111
- }
112
-
113
- if (cmd.spawn) {
114
- flux.dispatch({
115
- type: 'ADD_CHAT_MESSAGE',
116
- payload: {
117
- role: 'assistant',
118
- content: '',
119
- attachment: { id: cmd.spawn, props: cmd.props || {} }
120
- },
121
- to: 'all'
122
- });
123
- return null;
124
- }
125
-
126
- if (cmd.do === 'toggle_task') {
127
- flux.dispatch({
128
- type: 'TOGGLE_TASK',
129
- payload: { label: cmd.label, completed: true },
130
- to: 'all'
131
- });
132
- return null;
133
- }
134
-
135
- if (cmd.do === 'confetti') {
136
- flux.dispatch({ type: 'SHOW_CONFETTI', payload: {}, to: 'tasks-air' });
137
- return null;
138
- }
139
-
140
- if (cmd.goto) {
141
- return cmd.goto;
142
- }
143
-
144
- if (cmd.wait) {
145
- // Return signal to wait
146
- return 'WAIT';
147
- }
148
-
149
- return null;
150
- }, [say]);
151
-
152
- // Run current step
153
- useEffect(() => {
154
- if (state.currentStep === 'IDLE' || state.currentStep === 'DONE' || state.waiting) {
155
- return;
156
- }
157
-
158
- const step = flow.steps.find(s => s.step === state.currentStep);
159
- if (!step) {
160
- console.error('[TourEngine] Step not found:', state.currentStep);
161
- return;
162
- }
163
-
164
- let cancelled = false;
165
-
166
- const runCommands = async () => {
167
- let i = state.commandIndex;
168
-
169
- while (i < step.run.length && !cancelled) {
170
- const cmd = step.run[i];
171
-
172
- // Handle wait commands - need to pause and listen
173
- if (cmd.wait) {
174
- setState(s => ({ ...s, commandIndex: i, waiting: true }));
175
- return; // Stop execution, wait for event
176
- }
177
-
178
- const result = await executeCommand(cmd);
179
-
180
- if (result && result !== 'WAIT') {
181
- // goto command - switch step
182
- setState({ currentStep: result, commandIndex: 0, waiting: false });
183
- return;
184
- }
185
-
186
- i++;
187
- }
188
-
189
- // Reached end of step without goto
190
- if (i >= step.run.length) {
191
- setState(s => ({ ...s, currentStep: 'DONE' }));
192
- }
193
- };
194
-
195
- runCommands();
196
-
197
- return () => {
198
- cancelled = true;
199
- timers.current.forEach(t => clearTimeout(t));
200
- timers.current = [];
201
- };
202
- }, [state.currentStep, state.commandIndex, state.waiting, flow, executeCommand]);
203
-
204
- // Event listener for wait conditions
205
- useEffect(() => {
206
- if (!state.waiting) return;
207
-
208
- const step = flow.steps.find(s => s.step === state.currentStep);
209
- if (!step) return;
210
-
211
- const cmd = step.run[state.commandIndex];
212
- if (!cmd || !cmd.wait) return;
213
-
214
- const unsubscribe = flux.subscribe((action: any) => {
215
- let matched = false;
216
- let errorStep: string | null = null;
217
-
218
- // task_toggled - wait for TOGGLE_TASK or TASK_COMPLETED
219
- if (cmd.wait === 'task_toggled') {
220
- // Listen for the actual toggle/completion event
221
- if (action.type === 'TOGGLE_TASK' || action.type === 'TASK_COMPLETED') {
222
- const label = action.payload?.label?.toLowerCase() ||
223
- action.payload?.taskLabel?.toLowerCase() || '';
224
- matched = label.includes((cmd.match as string)?.toLowerCase() || '');
225
- }
226
-
227
- // Also intercept CHAT_PROMPT and dispatch TOGGLE_TASK if intent matches
228
- if (action.type === 'CHAT_PROMPT') {
229
- const text = action.payload?.text?.toLowerCase() || '';
230
- const matchStr = (cmd.match as string)?.toLowerCase() || '';
231
- const toggleIntents = ['complete', 'done', 'finish', 'check', 'mark', 'toggle'];
232
- const hasIntent = toggleIntents.some(i => text.includes(i));
233
- const hasMatch = matchStr.split(' ').some(word => text.includes(word));
234
-
235
- if (hasIntent && hasMatch) {
236
- console.log('[TourEngine] Intercepting chat for toggle:', cmd.match);
237
- // Dispatch TOGGLE_TASK - this will trigger TasksAIR which will dispatch TASK_COMPLETED
238
- queueMicrotask(() => {
239
- flux.dispatch({
240
- type: 'TOGGLE_TASK',
241
- payload: { label: cmd.match, completed: true },
242
- to: 'all'
243
- });
244
- });
245
- }
246
- }
247
-
248
- // Listen for explicit TASK_COMPLETED event
249
- if (action.type === 'TASK_COMPLETED') {
250
- console.log('[TourEngine] TASK_COMPLETED received:', action.payload);
251
- const matchStr = (cmd.match as string)?.toLowerCase() || '';
252
- const matchIndex = parseInt(matchStr, 10);
253
- const isIndexMatch = !isNaN(matchIndex) && action.payload?.taskOrder === matchIndex;
254
- console.log('[TourEngine] Match logic - matchStr:', matchStr, 'matchIndex:', matchIndex, 'taskOrder:', action.payload?.taskOrder, 'isIndexMatch:', isIndexMatch);
255
-
256
- const label = action.payload?.taskLabel?.toLowerCase() || '';
257
- const isTextMatch = label.includes(matchStr);
258
- console.log('[TourEngine] Match logic - label:', label, 'isTextMatch:', isTextMatch);
259
-
260
- if (isIndexMatch || isTextMatch) {
261
- console.log('[TourEngine] Task completion matched!');
262
- matched = true;
263
- }
264
- }
265
-
266
- // Listen for state updates from tasks-air (fallback/verification)
267
- if (action.type === 'UPDATE_STATE' && action.payload?.airId === 'tasks-air') {
268
- const tasks = action.payload?.tasks || [];
269
- const matchStr = (cmd.match as string)?.toLowerCase() || '';
270
- const matchIndex = parseInt(matchStr, 10);
271
-
272
- if (!isNaN(matchIndex)) {
273
- // Check by index
274
- if (tasks[matchIndex] && tasks[matchIndex].completed) {
275
- matched = true;
276
- }
277
- } else {
278
- // Check by label
279
- const targetTask = tasks.find((t: any) => t.label?.toLowerCase().includes(matchStr));
280
- if (targetTask && targetTask.completed) {
281
- matched = true;
282
- }
283
- }
284
- }
285
- }
286
-
287
- // task_added - wait for ADD_TASK or TASK_ADDED
288
- if (cmd.wait === 'task_added') {
289
- if (action.type === 'ADD_TASK' || action.type === 'TASK_ADDED') {
290
- const label = action.payload?.label?.toLowerCase() || '';
291
- matched = label.includes((cmd.match as string)?.toLowerCase() || '');
292
- }
293
-
294
- // Intercept CHAT_PROMPT for add task intent
295
- if (action.type === 'CHAT_PROMPT') {
296
- const text = action.payload?.text?.toLowerCase() || '';
297
- const addIntents = ['add task', 'add a task', 'create task', 'new task'];
298
- const hasIntent = addIntents.some(i => text.includes(i));
299
-
300
- if (hasIntent) {
301
- // Extract task label from text (after "add task:" or similar)
302
- const colonIdx = text.indexOf(':');
303
- const taskLabel = colonIdx > -1 ? text.substring(colonIdx + 1).trim() : text.replace(/add\s*(a\s*)?task\s*/i, '').trim();
304
-
305
- if (taskLabel) {
306
- console.log('[TourEngine] Intercepting chat for add task:', taskLabel);
307
- // Check if added task matches what we're waiting for
308
- if (taskLabel.includes((cmd.match as string)?.toLowerCase() || '')) {
309
- matched = true;
310
- }
311
- }
312
- }
313
- }
314
- }
315
-
316
- // air_spawned - wait for AIR to appear in Space
317
- if (cmd.wait === 'air_spawned') {
318
- console.log('[TourEngine] Waiting for air_spawned:', cmd.air, 'Got action:', action.type);
319
- const types = ['SPAWN_AIR', 'WINDOW_SPAWNED', 'ADD_WINDOW', 'ADD_CHAT_MESSAGE'];
320
- if (types.includes(action.type)) {
321
- const p = action.payload;
322
- const ids = [p.id, p.manifestId, p.props?.id, p.attachment?.id].filter(Boolean);
323
- console.log('[TourEngine] Checking ids:', ids, 'for air:', cmd.air);
324
- matched = ids.some((id: string) => id.includes(cmd.air || ''));
325
- if (matched) console.log('[TourEngine] air_spawned matched!');
326
- }
327
- // Also check controller state response
328
- if (action.type === 'CONTROLLER_STATE') {
329
- const windows = action.payload.windows || [];
330
- console.log('[TourEngine] Checking CONTROLLER_STATE windows:', windows);
331
- matched = windows.some((w: any) =>
332
- w.manifestId?.includes(cmd.air || '') || w.id?.includes(cmd.air || '')
333
- );
334
- if (matched) console.log('[TourEngine] air_spawned matched from CONTROLLER_STATE!');
335
- }
336
- }
337
-
338
- // air_success - wait for UPDATE_STATE from specific AIR
339
- if (cmd.wait === 'air_success') {
340
- console.log('[TourEngine] Waiting for air_success:', cmd.air, 'Got action:', action.type, action.payload);
341
- if (action.type === 'AIR_ERROR' && action.payload.airId?.includes(cmd.air || '')) {
342
- console.log('[TourEngine] AIR_ERROR detected, jumping to error step:', cmd.onError);
343
- errorStep = cmd.onError || null;
344
- }
345
- if (action.type === 'UPDATE_STATE' && action.payload.airId?.includes(cmd.air || '')) {
346
- console.log('[TourEngine] air_success matched!');
347
- matched = true;
348
- }
349
- }
350
-
351
- if (errorStep) {
352
- setState({ currentStep: errorStep, commandIndex: 0, waiting: false });
353
- } else if (matched) {
354
- setState(s => ({
355
- ...s,
356
- commandIndex: s.commandIndex + 1,
357
- waiting: false
358
- }));
359
- }
360
- });
361
-
362
- // For air_spawned, request controller state to check if already in space
363
- // Also immediately check if it's already spawned by looking at recent actions
364
- if (cmd.wait === 'air_spawned') {
365
- flux.dispatch({ type: 'REQUEST_CONTROLLER_STATE', payload: {}, to: 'controller' });
366
- // Give a small delay to ensure the air has time to mount and dispatch events
367
- setTimeout(() => {
368
- flux.dispatch({ type: 'REQUEST_CONTROLLER_STATE', payload: {}, to: 'controller' });
369
- }, 300);
370
- }
371
-
372
- return unsubscribe;
373
- }, [state.waiting, state.currentStep, state.commandIndex, flow]);
374
-
375
- return state;
376
- }
@@ -1,68 +0,0 @@
1
- :root {
2
- font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
3
- line-height: 1.5;
4
- font-weight: 400;
5
-
6
- color-scheme: light dark;
7
- color: rgba(255, 255, 255, 0.87);
8
- background-color: #242424;
9
-
10
- font-synthesis: none;
11
- text-rendering: optimizeLegibility;
12
- -webkit-font-smoothing: antialiased;
13
- -moz-osx-font-smoothing: grayscale;
14
- }
15
-
16
- a {
17
- font-weight: 500;
18
- color: #646cff;
19
- text-decoration: inherit;
20
- }
21
- a:hover {
22
- color: #535bf2;
23
- }
24
-
25
- body {
26
- margin: 0;
27
- display: flex;
28
- place-items: center;
29
- min-width: 320px;
30
- min-height: 100vh;
31
- }
32
-
33
- h1 {
34
- font-size: 3.2em;
35
- line-height: 1.1;
36
- }
37
-
38
- button {
39
- border-radius: 8px;
40
- border: 1px solid transparent;
41
- padding: 0.6em 1.2em;
42
- font-size: 1em;
43
- font-weight: 500;
44
- font-family: inherit;
45
- background-color: #1a1a1a;
46
- cursor: pointer;
47
- transition: border-color 0.25s;
48
- }
49
- button:hover {
50
- border-color: #646cff;
51
- }
52
- button:focus,
53
- button:focus-visible {
54
- outline: 4px auto -webkit-focus-ring-color;
55
- }
56
-
57
- @media (prefers-color-scheme: light) {
58
- :root {
59
- color: #213547;
60
- background-color: #ffffff;
61
- }
62
- a:hover {
63
- color: #747bff;
64
- }
65
- button {
66
- background-color: #f9f9f9;
67
- }
68
- }
@@ -1,10 +0,0 @@
1
- import { StrictMode } from 'react'
2
- import { createRoot } from 'react-dom/client'
3
- import './index.css'
4
- import App from './App.tsx'
5
-
6
- createRoot(document.getElementById('root')!).render(
7
- <StrictMode>
8
- <App />
9
- </StrictMode>,
10
- )
@@ -1,181 +0,0 @@
1
- import { initializeApp } from "firebase/app";
2
- import { getAnalytics, logEvent as firebaseLogEvent, Analytics } from "firebase/analytics";
3
- import { getPerformance, trace, FirebasePerformance, PerformanceTrace } from "firebase/performance";
4
- import { flux } from '@minnai/aura/flux/index';
5
-
6
- // Community Firebase Config (fallback when user doesn't provide their own)
7
- const communityFirebaseConfig = {
8
- apiKey: "AIzaSyBxP1CkhBFaHFDX9HcVQurcBDVzzFruc4k",
9
- authDomain: "saga-9dd98.firebaseapp.com",
10
- projectId: "saga-9dd98",
11
- storageBucket: "saga-9dd98.firebasestorage.app",
12
- messagingSenderId: "723636478082",
13
- appId: "1:723636478082:web:68b2fa0eb2010706e2549b",
14
- measurementId: "G-67DEXLGRDQ"
15
- };
16
-
17
- declare global {
18
- interface Window {
19
- __ANALYTICS_LOGS__: any[];
20
- }
21
- }
22
-
23
- export class AnalyticsService {
24
- private analytics: Analytics | null = null;
25
- private performance: FirebasePerformance | null = null;
26
- private currentTrace: PerformanceTrace | null = null;
27
- private ttvTrace: PerformanceTrace | null = null;
28
- private TTV_START_KEY = 'aura_ttv_start';
29
-
30
- constructor(userConfig?: any) {
31
- // 1. Try User Config (Environment Variables)
32
- const envConfig = {
33
- apiKey: import.meta.env.VITE_FIREBASE_API_KEY,
34
- authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN,
35
- projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID,
36
- appId: import.meta.env.VITE_FIREBASE_APP_ID,
37
- measurementId: import.meta.env.VITE_FIREBASE_MEASUREMENT_ID
38
- };
39
-
40
- const configToUse = (envConfig.apiKey) ? envConfig : (userConfig || communityFirebaseConfig);
41
-
42
- try {
43
- if (configToUse.apiKey && !configToUse.apiKey.includes("Dummy")) {
44
- const app = initializeApp(configToUse);
45
- this.analytics = getAnalytics(app);
46
- this.performance = getPerformance(app);
47
- console.log("[Analytics] Service Initialized (" + (envConfig.apiKey ? "User" : "Community") + ")");
48
- } else {
49
- console.log("[Analytics] No valid config found. Telemetry disabled.");
50
- }
51
-
52
- // Always setup listeners for local debugging/TTV, or if analytics is active
53
- if (this.analytics || import.meta.env.DEV) {
54
- this.setupListeners();
55
- }
56
- } catch (e) {
57
- console.warn("[Analytics] Initialization failed", e);
58
- }
59
- }
60
-
61
- private setupListeners() {
62
- // Listen to Aura Signals via Flux
63
- flux.subscribe((msg: any) => {
64
- // 1. Trace Generation Latency
65
- if (msg.type === 'CHAT_PROMPT') {
66
- // Stop existing if any (user spammed send)
67
- this.stopTrace(this.currentTrace);
68
- this.currentTrace = this.startTrace('air_generation_latency');
69
- }
70
-
71
- // 2. Stop Trace on AIR Spawn (Success)
72
- if (msg.type === 'SPAWN_AIR' || msg.type === 'WINDOW_SPAWNED') {
73
- this.log('air_generated', {
74
- air_type: msg.payload.id || msg.payload.manifestId,
75
- success: true
76
- });
77
-
78
- // Stop Generation Trace
79
- if (this.currentTrace) {
80
- this.currentTrace.putAttribute('air_id', msg.payload.id || msg.payload.manifestId || 'unknown');
81
- this.stopTrace(this.currentTrace);
82
- this.currentTrace = null;
83
- }
84
-
85
- this.completeTTVTimer();
86
- }
87
- // We can add more listeners here (e.g. Errors)
88
- });
89
- }
90
-
91
- public log(eventName: string, params?: any) {
92
- // Log to local global array for verification
93
- if (!window.__ANALYTICS_LOGS__) {
94
- window.__ANALYTICS_LOGS__ = [];
95
- }
96
- window.__ANALYTICS_LOGS__.push({ event: eventName, params, timestamp: Date.now() });
97
-
98
- // Always log in DEV mode for visibility, even if Analytics is disabled/misconfigured
99
- if (import.meta.env.DEV) {
100
- console.log(`[Analytics] ${this.analytics ? 'Sent' : 'Skipped (No Key)'}: ${eventName}`, params);
101
- }
102
-
103
- if (this.analytics) {
104
- try {
105
- firebaseLogEvent(this.analytics, eventName, params);
106
- } catch (e) {
107
- // Ignore send errors
108
- }
109
- }
110
- }
111
-
112
- public startTrace(name: string): PerformanceTrace | null {
113
- if (this.performance) {
114
- const t = trace(this.performance, name);
115
- t.start();
116
- if (import.meta.env.DEV) {
117
- console.log(`[Analytics] Trace Started: ${name}`);
118
- }
119
- return t;
120
- }
121
- return null;
122
- }
123
-
124
- public stopTrace(t: PerformanceTrace | null) {
125
- if (t) {
126
- t.stop();
127
- if (import.meta.env.DEV) {
128
- console.log(`[Analytics] Trace Stopped: ${t.getAttribute('name') || 'unknown'}`);
129
- }
130
- }
131
- }
132
-
133
- public startTTVTimer() {
134
- if (!sessionStorage.getItem(this.TTV_START_KEY)) {
135
- const now = Date.now();
136
- sessionStorage.setItem(this.TTV_START_KEY, now.toString());
137
- console.log('[Analytics] TTV Timer Started at', now);
138
-
139
- // Start Trace for TTV
140
- this.ttvTrace = this.startTrace('time_to_value_trace');
141
- }
142
- }
143
-
144
- public completeTTVTimer() {
145
- const startStr = sessionStorage.getItem(this.TTV_START_KEY);
146
- if (startStr) {
147
- const start = parseInt(startStr, 10);
148
- const end = Date.now();
149
- const duration = end - start;
150
-
151
- this.log('time_to_value', { duration_ms: duration, duration_sec: duration / 1000 });
152
- console.log(`[Analytics] TTV Complete: ${duration}ms`);
153
-
154
- // Stop Trace for TTV
155
- if (this.ttvTrace) {
156
- this.ttvTrace.putAttribute('duration_sec', (duration / 1000).toString());
157
- this.stopTrace(this.ttvTrace);
158
- this.ttvTrace = null;
159
- }
160
-
161
- // Clear so we only track first time to value per session/refresh logic if desired
162
- // For now, let's keep it to allow multiple measurements or clear it?
163
- // Usually TTV is once per session.
164
- sessionStorage.removeItem(this.TTV_START_KEY);
165
- }
166
- }
167
-
168
- public logLogin(method: string) {
169
- this.log('login', { method });
170
- }
171
-
172
- public logSignup(method: string) {
173
- this.log('sign_up', { method });
174
- }
175
-
176
- public logPageView(page_path: string) {
177
- this.log('page_view', { page_path });
178
- }
179
- }
180
-
181
- export const analyticsService = new AnalyticsService();
@@ -1,13 +0,0 @@
1
- export interface ContextHandler {
2
- /**
3
- * Returns the current context/state of the AIR.
4
- * This is used by the Intent Planner to understand what the user is looking at.
5
- */
6
- getContext(): Promise<any>;
7
-
8
- /**
9
- * List of capabilities this AIR supports.
10
- * e.g. ['text-editing', 'video-playback', 'market-data']
11
- */
12
- capabilities: string[];
13
- }