@codeguide/core 0.0.28 → 0.0.29

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 (79) hide show
  1. package/__tests__/services/usage/usage-service.test.ts +442 -83
  2. package/codeguide.ts +3 -0
  3. package/dist/codeguide.d.ts +2 -1
  4. package/dist/codeguide.js +1 -0
  5. package/dist/index.d.ts +4 -3
  6. package/dist/services/base/base-service.d.ts +21 -0
  7. package/dist/services/base/base-service.js +114 -0
  8. package/dist/services/codespace/codespace-service.d.ts +55 -1
  9. package/dist/services/codespace/codespace-service.js +257 -0
  10. package/dist/services/codespace/codespace-types.d.ts +192 -12
  11. package/dist/services/codespace/index.d.ts +1 -1
  12. package/dist/services/index.d.ts +2 -0
  13. package/dist/services/index.js +4 -1
  14. package/dist/services/projects/project-types.d.ts +66 -32
  15. package/dist/services/starter-kits/index.d.ts +2 -0
  16. package/dist/services/starter-kits/index.js +20 -0
  17. package/dist/services/starter-kits/starter-kits-service.d.ts +13 -0
  18. package/dist/services/starter-kits/starter-kits-service.js +27 -0
  19. package/dist/services/starter-kits/starter-kits-types.d.ts +34 -0
  20. package/dist/services/starter-kits/starter-kits-types.js +2 -0
  21. package/dist/services/tasks/task-service.d.ts +2 -1
  22. package/dist/services/tasks/task-service.js +8 -0
  23. package/dist/services/tasks/task-types.d.ts +26 -7
  24. package/dist/services/usage/usage-service.d.ts +5 -2
  25. package/dist/services/usage/usage-service.js +58 -9
  26. package/dist/services/usage/usage-types.d.ts +150 -26
  27. package/docs/.vitepress/README.md +51 -0
  28. package/docs/.vitepress/config.ts +139 -0
  29. package/docs/.vitepress/theme/custom.css +80 -0
  30. package/docs/.vitepress/theme/index.ts +13 -0
  31. package/docs/.vitepress/tsconfig.json +19 -0
  32. package/docs/QUICKSTART.md +77 -0
  33. package/docs/README.md +134 -0
  34. package/docs/README_SETUP.md +46 -0
  35. package/docs/authentication.md +351 -0
  36. package/docs/codeguide-client.md +350 -0
  37. package/docs/codespace-models.md +1004 -0
  38. package/docs/codespace-service.md +444 -0
  39. package/docs/index.md +135 -0
  40. package/docs/package.json +14 -0
  41. package/docs/projects-service.md +688 -0
  42. package/docs/security-keys-service.md +773 -0
  43. package/docs/starter-kits-service.md +249 -0
  44. package/docs/task-service.md +955 -0
  45. package/docs/testsprite_tests/TC001_Homepage_Load_and_Hero_Section_Display.py +70 -0
  46. package/docs/testsprite_tests/TC002_Sidebar_Navigation_ExpandCollapse_Functionality.py +73 -0
  47. package/docs/testsprite_tests/TC003_Full_Text_Local_Search_with_Keyboard_Shortcut.py +90 -0
  48. package/docs/testsprite_tests/TC004_Dark_Mode_Toggle_and_Persistence.py +73 -0
  49. package/docs/testsprite_tests/TC005_Mobile_Responsiveness_and_Touch_Navigation.py +113 -0
  50. package/docs/testsprite_tests/TC006_GitHub_Integration_Edit_this_page_Links.py +73 -0
  51. package/docs/testsprite_tests/TC007_Syntax_Highlighting_and_Code_Copy_Functionality.py +73 -0
  52. package/docs/testsprite_tests/TC008_Auto_Generated_Table_of_Contents_Accuracy.py +73 -0
  53. package/docs/testsprite_tests/TC009_SEO_and_Content_Discoverability_Verification.py +73 -0
  54. package/docs/testsprite_tests/TC010_Accessibility_Compliance_WCAG_AA.py +73 -0
  55. package/docs/testsprite_tests/TC011_Local_Development_Workflow_Build_and_Hot_Reload.py +74 -0
  56. package/docs/testsprite_tests/TC012_Performance_Metrics_Compliance.py +73 -0
  57. package/docs/testsprite_tests/standard_prd.json +122 -0
  58. package/docs/testsprite_tests/testsprite-mcp-test-report.html +2508 -0
  59. package/docs/testsprite_tests/testsprite-mcp-test-report.md +273 -0
  60. package/docs/testsprite_tests/testsprite_frontend_test_plan.json +390 -0
  61. package/docs/usage-service.md +291 -1
  62. package/index.ts +11 -3
  63. package/package.json +16 -2
  64. package/plans/CODESPACE_LOGS_STREAMING_GUIDE.md +320 -0
  65. package/plans/CODESPACE_TASK_LOGS_API_COMPLETE_GUIDE.md +821 -0
  66. package/services/base/base-service.ts +130 -0
  67. package/services/codespace/codespace-service.ts +337 -0
  68. package/services/codespace/codespace-types.ts +262 -13
  69. package/services/codespace/index.ts +16 -1
  70. package/services/index.ts +2 -0
  71. package/services/projects/README.md +107 -34
  72. package/services/projects/project-types.ts +69 -32
  73. package/services/starter-kits/index.ts +2 -0
  74. package/services/starter-kits/starter-kits-service.ts +33 -0
  75. package/services/starter-kits/starter-kits-types.ts +38 -0
  76. package/services/tasks/task-service.ts +10 -0
  77. package/services/tasks/task-types.ts +29 -7
  78. package/services/usage/usage-service.ts +59 -10
  79. package/services/usage/usage-types.ts +178 -27
@@ -0,0 +1,821 @@
1
+ # Codespace Task Logs API - Complete Implementation Guide
2
+
3
+ ## Overview
4
+ This guide provides complete documentation for the codespace task logs API implementation, including response types, usage examples, and integration details.
5
+
6
+ ## 📊 Database Schema
7
+
8
+ ```sql
9
+ CREATE TABLE public.codespace_task_logs (
10
+ id uuid NOT NULL DEFAULT gen_random_uuid(),
11
+ codespace_task_id uuid NOT NULL,
12
+ step_name text NOT NULL,
13
+ log_type text NOT NULL DEFAULT 'info'::text,
14
+ message text NOT NULL,
15
+ created_at timestamp with time zone NULL DEFAULT now(),
16
+ progress_percentage integer NULL,
17
+ metadata jsonb NULL,
18
+ CONSTRAINT codespace_task_logs_pkey PRIMARY KEY (id),
19
+ CONSTRAINT codespace_task_logs_codespace_task_id_fkey
20
+ FOREIGN KEY (codespace_task_id) REFERENCES codespace_tasks (id) ON DELETE CASCADE,
21
+ CONSTRAINT codespace_task_logs_progress_percentage_check
22
+ CHECK ((progress_percentage >= 0 AND progress_percentage <= 100)),
23
+ CONSTRAINT codespace_task_logs_type_check
24
+ CHECK ((log_type = ANY (ARRAY['thinking'::text, 'coding'::text, 'info'::text, 'error'::text, 'success'::text])))
25
+ );
26
+
27
+ -- Indexes for performance
28
+ CREATE INDEX idx_codespace_task_logs_task_id ON public.codespace_task_logs USING btree (codespace_task_id);
29
+ CREATE INDEX idx_codespace_task_logs_task_id_created ON public.codespace_task_logs USING btree (codespace_task_id, created_at);
30
+ CREATE INDEX idx_codespace_task_logs_log_type ON public.codespace_task_logs USING btree (log_type);
31
+ ```
32
+
33
+ ## 🏗️ Data Models
34
+
35
+ ### 1. Database Model
36
+ ```python
37
+ class CodespaceTaskLogInDB(BaseModel):
38
+ """Codespace task log database model."""
39
+
40
+ id: str
41
+ codespace_task_id: str
42
+ step_name: str
43
+ log_type: Literal["thinking", "coding", "info", "error", "success"]
44
+ message: str
45
+ created_at: datetime
46
+ progress_percentage: Optional[int] = None
47
+ metadata: Optional[Dict[str, Any]] = None
48
+
49
+ model_config = {"from_attributes": True}
50
+ ```
51
+
52
+ ### 2. Response Model
53
+ ```python
54
+ class CodespaceTaskLogResponse(BaseModel):
55
+ """Response model for a single codespace task log."""
56
+
57
+ id: str
58
+ step_name: str
59
+ log_type: str
60
+ message: str
61
+ created_at: datetime
62
+ progress_percentage: Optional[int] = None
63
+ metadata: Optional[Dict[str, Any]] = None
64
+ ```
65
+
66
+ ### 3. Paginated List Response
67
+ ```python
68
+ class CodespaceTaskLogsListResponse(BaseModel):
69
+ """Response model for getting a list of codespace task logs."""
70
+
71
+ status: str = "success"
72
+ data: List[CodespaceTaskLogResponse]
73
+ total_count: int = Field(..., description="Total number of logs matching the criteria")
74
+ has_more: bool = Field(..., description="Whether there are more logs available")
75
+ next_offset: Optional[int] = Field(None, description="Next offset to use for pagination")
76
+ message: str = "Codespace task logs retrieved successfully"
77
+ ```
78
+
79
+ ## 🚀 API Endpoints
80
+
81
+ ### 1. Standard REST API Endpoint
82
+
83
+ **GET `/codespace/task/{codespace_task_id}/logs`**
84
+
85
+ #### Query Parameters
86
+ | Parameter | Type | Default | Constraints | Description |
87
+ |-----------|------|---------|-------------|-------------|
88
+ | `limit` | int | 50 | 1-500 | Number of logs to return (custom rows) |
89
+ | `offset` | int | 0 | ≥0 | Number of logs to skip for pagination |
90
+ | `log_type` | string | optional | thinking,coding,info,error,success | Filter by log type |
91
+ | `step_name` | string | optional | text | Filter by step name (partial match) |
92
+ | `search` | string | optional | text | Search in message content |
93
+ | `sort_by` | string | created_at | created_at,step_name,log_type | Sort field |
94
+ | `sort_order` | string | desc | asc,desc | Sort order |
95
+ | `since` | string | optional | ISO timestamp | Get logs after timestamp |
96
+
97
+ #### Response Example
98
+ ```json
99
+ {
100
+ "status": "success",
101
+ "data": [
102
+ {
103
+ "id": "123e4567-e89b-12d3-a456-426614174000",
104
+ "step_name": "task_creation",
105
+ "log_type": "info",
106
+ "message": "🚀 Your coding task is ready!",
107
+ "created_at": "2024-01-15T10:30:00.000Z",
108
+ "progress_percentage": 10,
109
+ "metadata": null
110
+ },
111
+ {
112
+ "id": "123e4567-e89b-12d3-a456-426614174001",
113
+ "step_name": "ai_summary",
114
+ "log_type": "info",
115
+ "message": "🔍 Preparing project foundation (description + outline)",
116
+ "created_at": "2024-01-15T10:30:15.000Z",
117
+ "progress_percentage": 25,
118
+ "metadata": null
119
+ }
120
+ ],
121
+ "total_count": 150,
122
+ "has_more": true,
123
+ "next_offset": 50,
124
+ "message": "Retrieved 50 logs"
125
+ }
126
+ ```
127
+
128
+ ### 2. Real-time Streaming Endpoint (SSE)
129
+
130
+ **GET `/codespace/task/{codespace_task_id}/logs/stream`**
131
+
132
+ #### Query Parameters
133
+ | Parameter | Type | Default | Constraints | Description |
134
+ |-----------|------|---------|-------------|-------------|
135
+ | `since` | string | optional | ISO timestamp | Start from this timestamp |
136
+ | `timeout` | int | 300 | 30-1800 | Stream timeout in seconds |
137
+
138
+ #### SSE Events
139
+
140
+ **`log` Event**
141
+ ```
142
+ event: log
143
+ data: {
144
+ "id": "123e4567-e89b-12d3-a456-426614174000",
145
+ "step_name": "task_creation",
146
+ "log_type": "info",
147
+ "message": "🚀 Your coding task is ready!",
148
+ "created_at": "2024-01-15T10:30:00.000Z",
149
+ "progress_percentage": 10,
150
+ "metadata": {}
151
+ }
152
+ ```
153
+
154
+ **`heartbeat` Event**
155
+ ```
156
+ event: heartbeat
157
+ data: {
158
+ "timestamp": "2024-01-15T10:30:30.000Z"
159
+ }
160
+ ```
161
+
162
+ **`complete` Event**
163
+ ```
164
+ event: complete
165
+ data: {
166
+ "message": "Task completed"
167
+ }
168
+ ```
169
+
170
+ **`timeout` Event**
171
+ ```
172
+ event: timeout
173
+ data: {
174
+ "message": "Stream timeout reached"
175
+ }
176
+ ```
177
+
178
+ **`error` Event**
179
+ ```
180
+ event: error
181
+ data: {
182
+ "error": "Task not found or access denied"
183
+ }
184
+ ```
185
+
186
+ ## 💻 Implementation Guide
187
+
188
+ ### Frontend Integration Examples
189
+
190
+ #### React Hook (Recommended)
191
+ ```typescript
192
+ import { useEffect, useState, useCallback } from 'react';
193
+
194
+ interface CodespaceLog {
195
+ id: string;
196
+ step_name: string;
197
+ log_type: 'thinking' | 'coding' | 'info' | 'error' | 'success';
198
+ message: string;
199
+ created_at: string;
200
+ progress_percentage?: number;
201
+ metadata?: any;
202
+ }
203
+
204
+ interface UseCodespaceLogsReturn {
205
+ logs: CodespaceLog[];
206
+ isLoading: boolean;
207
+ isStreaming: boolean;
208
+ error: string | null;
209
+ hasMore: boolean;
210
+ nextOffset: number | null;
211
+ loadMore: () => void;
212
+ startStreaming: () => void;
213
+ stopStreaming: () => void;
214
+ }
215
+
216
+ export const useCodespaceLogs = (
217
+ taskId: string,
218
+ authToken: string,
219
+ options: {
220
+ pageSize?: number;
221
+ enableStreaming?: boolean;
222
+ filters?: {
223
+ log_type?: string;
224
+ step_name?: string;
225
+ search?: string;
226
+ };
227
+ } = {}
228
+ ): UseCodespaceLogsReturn => {
229
+ const [logs, setLogs] = useState<CodespaceLog[]>([]);
230
+ const [isLoading, setIsLoading] = useState(false);
231
+ const [isStreaming, setIsStreaming] = useState(false);
232
+ const [error, setError] = useState<string | null>(null);
233
+ const [hasMore, setHasMore] = useState(false);
234
+ const [nextOffset, setNextOffset] = useState<number | null>(null);
235
+ const [eventSource, setEventSource] = useState<EventSource | null>(null);
236
+
237
+ const { pageSize = 50, enableStreaming = true, filters = {} } = options;
238
+
239
+ // Load initial logs
240
+ const loadLogs = useCallback(async (offset = 0) => {
241
+ try {
242
+ setIsLoading(true);
243
+ setError(null);
244
+
245
+ const params = new URLSearchParams({
246
+ limit: pageSize.toString(),
247
+ offset: offset.toString(),
248
+ ...filters
249
+ });
250
+
251
+ const response = await fetch(
252
+ `/codespace/task/${taskId}/logs?${params}`,
253
+ {
254
+ headers: {
255
+ 'Authorization': `Bearer ${authToken}`,
256
+ 'Content-Type': 'application/json'
257
+ }
258
+ }
259
+ );
260
+
261
+ if (!response.ok) {
262
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
263
+ }
264
+
265
+ const data: CodespaceTaskLogsListResponse = await response.json();
266
+
267
+ if (offset === 0) {
268
+ setLogs(data.data);
269
+ } else {
270
+ setLogs(prev => [...prev, ...data.data]);
271
+ }
272
+
273
+ setHasMore(data.has_more);
274
+ setNextOffset(data.next_offset);
275
+
276
+ } catch (err) {
277
+ setError(err instanceof Error ? err.message : 'Failed to load logs');
278
+ } finally {
279
+ setIsLoading(false);
280
+ }
281
+ }, [taskId, authToken, pageSize, filters]);
282
+
283
+ // Start streaming
284
+ const startStreaming = useCallback(() => {
285
+ if (!enableStreaming || eventSource) return;
286
+
287
+ const url = `/codespace/task/${taskId}/logs/stream`;
288
+ const source = new EventSource(url);
289
+
290
+ source.addEventListener('log', (event) => {
291
+ try {
292
+ const log: CodespaceLog = JSON.parse(event.data);
293
+ setLogs(prev => [...prev, log]);
294
+ } catch (err) {
295
+ console.error('Failed to parse log data:', err);
296
+ }
297
+ });
298
+
299
+ source.addEventListener('complete', () => {
300
+ setIsStreaming(false);
301
+ source.close();
302
+ setEventSource(null);
303
+ });
304
+
305
+ source.addEventListener('error', () => {
306
+ setError('Stream error occurred');
307
+ setIsStreaming(false);
308
+ source.close();
309
+ setEventSource(null);
310
+ });
311
+
312
+ source.addEventListener('timeout', () => {
313
+ setError('Stream timeout');
314
+ setIsStreaming(false);
315
+ source.close();
316
+ setEventSource(null);
317
+ });
318
+
319
+ setEventSource(source);
320
+ setIsStreaming(true);
321
+ }, [taskId, enableStreaming, eventSource]);
322
+
323
+ // Stop streaming
324
+ const stopStreaming = useCallback(() => {
325
+ if (eventSource) {
326
+ eventSource.close();
327
+ setEventSource(null);
328
+ }
329
+ setIsStreaming(false);
330
+ }, [eventSource]);
331
+
332
+ // Load more logs
333
+ const loadMore = useCallback(() => {
334
+ if (nextOffset !== null && !isLoading) {
335
+ loadLogs(nextOffset);
336
+ }
337
+ }, [nextOffset, isLoading, loadLogs]);
338
+
339
+ // Initial load
340
+ useEffect(() => {
341
+ if (taskId) {
342
+ loadLogs(0);
343
+ }
344
+ }, [taskId, loadLogs]);
345
+
346
+ // Auto-start streaming
347
+ useEffect(() => {
348
+ if (enableStreaming && taskId && !eventSource && !isStreaming) {
349
+ startStreaming();
350
+ }
351
+ }, [enableStreaming, taskId, eventSource, isStreaming, startStreaming]);
352
+
353
+ // Cleanup
354
+ useEffect(() => {
355
+ return () => {
356
+ stopStreaming();
357
+ };
358
+ }, [stopStreaming]);
359
+
360
+ return {
361
+ logs,
362
+ isLoading,
363
+ isStreaming,
364
+ error,
365
+ hasMore,
366
+ nextOffset,
367
+ loadMore,
368
+ startStreaming,
369
+ stopStreaming
370
+ };
371
+ };
372
+ ```
373
+
374
+ #### React Component Example
375
+ ```typescript
376
+ import React from 'react';
377
+ import { useCodespaceLogs } from './useCodespaceLogs';
378
+
379
+ interface LogsViewerProps {
380
+ taskId: string;
381
+ authToken: string;
382
+ }
383
+
384
+ export const LogsViewer: React.FC<LogsViewerProps> = ({ taskId, authToken }) => {
385
+ const {
386
+ logs,
387
+ isLoading,
388
+ isStreaming,
389
+ error,
390
+ hasMore,
391
+ loadMore,
392
+ stopStreaming
393
+ } = useCodespaceLogs(taskId, authToken, {
394
+ pageSize: 100,
395
+ enableStreaming: true,
396
+ filters: {
397
+ // log_type: 'error', // Uncomment to filter errors only
398
+ }
399
+ });
400
+
401
+ const getLogIcon = (logType: string) => {
402
+ switch (logType) {
403
+ case 'thinking': return '🤔';
404
+ case 'coding': return '💻';
405
+ case 'info': return 'ℹ️';
406
+ case 'error': return '❌';
407
+ case 'success': return '✅';
408
+ default: return '📝';
409
+ }
410
+ };
411
+
412
+ const getLogColor = (logType: string) => {
413
+ switch (logType) {
414
+ case 'thinking': return 'text-blue-600';
415
+ case 'coding': return 'text-green-600';
416
+ case 'info': return 'text-gray-600';
417
+ case 'error': return 'text-red-600';
418
+ case 'success': return 'text-green-500';
419
+ default: return 'text-gray-600';
420
+ }
421
+ };
422
+
423
+ if (error) {
424
+ return (
425
+ <div className="bg-red-50 border border-red-200 rounded-md p-4">
426
+ <div className="text-red-800">Error: {error}</div>
427
+ </div>
428
+ );
429
+ }
430
+
431
+ return (
432
+ <div className="flex flex-col h-full">
433
+ {/* Header */}
434
+ <div className="flex items-center justify-between p-4 border-b">
435
+ <h3 className="text-lg font-semibold">Task Logs</h3>
436
+ <div className="flex items-center space-x-4">
437
+ <span className="text-sm text-gray-500">
438
+ {logs.length} logs
439
+ </span>
440
+ <span className={`text-sm ${isStreaming ? 'text-green-600' : 'text-gray-500'}`}>
441
+ {isStreaming ? '🔄 Streaming...' : '⏸️ Paused'}
442
+ </span>
443
+ {isStreaming && (
444
+ <button
445
+ onClick={stopStreaming}
446
+ className="px-3 py-1 text-sm bg-gray-100 hover:bg-gray-200 rounded"
447
+ >
448
+ Stop
449
+ </button>
450
+ )}
451
+ </div>
452
+ </div>
453
+
454
+ {/* Logs */}
455
+ <div className="flex-1 overflow-y-auto p-4 space-y-2">
456
+ {isLoading && logs.length === 0 && (
457
+ <div className="text-center text-gray-500 py-8">
458
+ Loading logs...
459
+ </div>
460
+ )}
461
+
462
+ {logs.map((log, index) => (
463
+ <div
464
+ key={log.id || index}
465
+ className="flex items-start space-x-3 p-3 bg-gray-50 rounded-lg"
466
+ >
467
+ <span className="text-lg">{getLogIcon(log.log_type)}</span>
468
+ <div className="flex-1 min-w-0">
469
+ <div className="flex items-center space-x-2">
470
+ <span className={`text-sm font-medium ${getLogColor(log.log_type)}`}>
471
+ {log.step_name}
472
+ </span>
473
+ <span className="text-xs text-gray-400">
474
+ {new Date(log.created_at).toLocaleTimeString()}
475
+ </span>
476
+ {log.progress_percentage !== null && (
477
+ <span className="text-xs text-gray-500">
478
+ {log.progress_percentage}%
479
+ </span>
480
+ )}
481
+ </div>
482
+ <div className="text-sm text-gray-700 mt-1 break-words">
483
+ {log.message}
484
+ </div>
485
+ </div>
486
+ </div>
487
+ ))}
488
+
489
+ {hasMore && (
490
+ <div className="text-center py-4">
491
+ <button
492
+ onClick={loadMore}
493
+ disabled={isLoading}
494
+ className="px-4 py-2 bg-blue-100 text-blue-700 rounded hover:bg-blue-200 disabled:opacity-50"
495
+ >
496
+ {isLoading ? 'Loading...' : 'Load More'}
497
+ </button>
498
+ </div>
499
+ )}
500
+ </div>
501
+ </div>
502
+ );
503
+ };
504
+ ```
505
+
506
+ #### Vue.js Example
507
+ ```typescript
508
+ import { ref, onMounted, onUnmounted } from 'vue';
509
+
510
+ export function useCodespaceLogs(taskId: string, authToken: string) {
511
+ const logs = ref<CodespaceLog[]>([]);
512
+ const isStreaming = ref(false);
513
+ const error = ref<string | null>(null);
514
+ let eventSource: EventSource | null = null;
515
+
516
+ const startStreaming = () => {
517
+ if (eventSource) return;
518
+
519
+ eventSource = new EventSource(`/codespace/task/${taskId}/logs/stream`);
520
+
521
+ eventSource.addEventListener('log', (event) => {
522
+ const log = JSON.parse(event.data);
523
+ logs.value.push(log);
524
+ });
525
+
526
+ eventSource.addEventListener('complete', () => {
527
+ isStreaming.value = false;
528
+ eventSource?.close();
529
+ eventSource = null;
530
+ });
531
+
532
+ eventSource.addEventListener('error', () => {
533
+ error.value = 'Stream error occurred';
534
+ isStreaming.value = false;
535
+ eventSource?.close();
536
+ eventSource = null;
537
+ });
538
+
539
+ isStreaming.value = true;
540
+ };
541
+
542
+ const stopStreaming = () => {
543
+ if (eventSource) {
544
+ eventSource.close();
545
+ eventSource = null;
546
+ }
547
+ isStreaming.value = false;
548
+ };
549
+
550
+ onMounted(() => {
551
+ startStreaming();
552
+ });
553
+
554
+ onUnmounted(() => {
555
+ stopStreaming();
556
+ });
557
+
558
+ return {
559
+ logs,
560
+ isStreaming,
561
+ error,
562
+ startStreaming,
563
+ stopStreaming
564
+ };
565
+ }
566
+ ```
567
+
568
+ ### Backend Integration
569
+
570
+ #### Service Method Usage
571
+ ```python
572
+ from app.services.codespace_service import CodespaceService
573
+
574
+ async def get_logs_example():
575
+ """Example of using the logs service method."""
576
+ service = CodespaceService()
577
+
578
+ try:
579
+ # Get paginated logs
580
+ logs, total_count, has_more = await service.get_codespace_task_logs(
581
+ codespace_task_id="task-uuid",
582
+ user_id="user-uuid",
583
+ limit=100,
584
+ offset=0,
585
+ log_type="error",
586
+ search="failed",
587
+ sort_by="created_at",
588
+ sort_order="desc"
589
+ )
590
+
591
+ return {
592
+ "logs": logs,
593
+ "total_count": total_count,
594
+ "has_more": has_more
595
+ }
596
+
597
+ except Exception as e:
598
+ logger.error(f"Failed to get logs: {e}")
599
+ raise
600
+ ```
601
+
602
+ ## 🔧 Configuration & Deployment
603
+
604
+ ### Environment Variables
605
+ ```bash
606
+ # Required for streaming
607
+ APP_BASE_URL=https://your-app.com
608
+ CLERK_SECRET_KEY=your-clerk-secret-key
609
+ SUPABASE_URL=your-supabase-url
610
+ SUPABASE_KEY=your-supabase-key
611
+ ```
612
+
613
+ ### Nginx Configuration (for streaming)
614
+ ```nginx
615
+ location /codespace/task/*/logs/stream {
616
+ proxy_pass http://backend;
617
+ proxy_set_header Host $host;
618
+ proxy_set_header Connection '';
619
+ proxy_http_version 1.1;
620
+ proxy_set_header X-Accel-Buffering no;
621
+ proxy_cache off;
622
+ proxy_set_header Proxy '';
623
+ }
624
+ ```
625
+
626
+ ### Database Indexes (Performance)
627
+ ```sql
628
+ -- Create indexes for optimal performance
629
+ CREATE INDEX CONCURRENTLY idx_codespace_logs_composite
630
+ ON codespace_task_logs (codespace_task_id, created_at DESC);
631
+
632
+ CREATE INDEX CONCURRENTLY idx_codespace_logs_type_task
633
+ ON codespace_task_logs (log_type, codespace_task_id);
634
+
635
+ CREATE INDEX CONCURRENTLY idx_codespace_logs_step_task
636
+ ON codespace_task_logs (step_name, codespace_task_id);
637
+
638
+ -- For text search (if using PostgreSQL)
639
+ CREATE INDEX CONCURRENTLY idx_codespace_logs_message_gin
640
+ ON codespace_task_logs USING gin(to_tsvector('english', message));
641
+ ```
642
+
643
+ ## 🧪 Testing
644
+
645
+ ### Unit Tests
646
+ ```python
647
+ import pytest
648
+ from app.services.codespace_service import CodespaceService
649
+
650
+ @pytest.mark.asyncio
651
+ async def test_get_codespace_task_logs():
652
+ """Test the logs retrieval method."""
653
+ service = CodespaceService()
654
+
655
+ logs, total_count, has_more = await service.get_codespace_task_logs(
656
+ codespace_task_id="test-task-id",
657
+ user_id="test-user-id",
658
+ limit=10,
659
+ offset=0
660
+ )
661
+
662
+ assert isinstance(logs, list)
663
+ assert isinstance(total_count, int)
664
+ assert isinstance(has_more, bool)
665
+ assert len(logs) <= 10
666
+
667
+ @pytest.mark.asyncio
668
+ async def test_stream_codespace_task_logs():
669
+ """Test the streaming method."""
670
+ service = CodespaceService()
671
+
672
+ log_stream = service.stream_codespace_task_logs(
673
+ codespace_task_id="test-task-id",
674
+ user_id="test-user-id",
675
+ timeout_seconds=10
676
+ )
677
+
678
+ events = []
679
+ async for event in log_stream:
680
+ events.append(event)
681
+ if event.startswith('event: complete'):
682
+ break
683
+
684
+ assert len(events) > 0
685
+ ```
686
+
687
+ ### Integration Tests
688
+ ```python
689
+ import pytest
690
+ from fastapi.testclient import TestClient
691
+ from app.main import app
692
+
693
+ client = TestClient(app)
694
+
695
+ def test_get_logs_endpoint():
696
+ """Test the REST API endpoint."""
697
+ response = client.get(
698
+ "/codespace/task/test-task-id/logs",
699
+ headers={"Authorization": "Bearer test-token"},
700
+ params={"limit": 10, "log_type": "info"}
701
+ )
702
+
703
+ assert response.status_code == 200
704
+ data = response.json()
705
+ assert "data" in data
706
+ assert "total_count" in data
707
+ assert "has_more" in data
708
+ assert len(data["data"]) <= 10
709
+
710
+ def test_stream_logs_endpoint():
711
+ """Test the streaming endpoint."""
712
+ with client.stream(
713
+ "GET",
714
+ "/codespace/task/test-task-id/logs/stream",
715
+ headers={"Authorization": "Bearer test-token"}
716
+ ) as response:
717
+ assert response.status_code == 200
718
+ assert response.headers["content-type"] == "text/event-stream; charset=utf-8"
719
+
720
+ # Read first few events
721
+ events = []
722
+ for line in response.iter_lines():
723
+ if line:
724
+ events.append(line.decode('utf-8'))
725
+ if len(events) >= 5:
726
+ break
727
+
728
+ assert len(events) > 0
729
+ ```
730
+
731
+ ## 📈 Performance Considerations
732
+
733
+ ### Pagination Strategy
734
+ - Use `limit` parameter to control memory usage
735
+ - Implement client-side caching for recent logs
736
+ - Consider virtual scrolling for large log lists
737
+
738
+ ### Streaming Optimization
739
+ - Set appropriate timeout values (5-30 minutes)
740
+ - Implement client-side reconnection logic
741
+ - Monitor concurrent connection limits
742
+
743
+ ### Database Optimization
744
+ - Use composite indexes for common query patterns
745
+ - Consider log archiving for old completed tasks
746
+ - Implement database connection pooling
747
+
748
+ ### Caching Strategy
749
+ ```python
750
+ # Redis caching example
751
+ import redis
752
+
753
+ redis_client = redis.Redis(host='localhost', port=6379, db=0)
754
+
755
+ async def get_cached_logs(task_id: str, user_id: str, limit: int):
756
+ cache_key = f"logs:{task_id}:{user_id}:{limit}"
757
+ cached = redis_client.get(cache_key)
758
+
759
+ if cached:
760
+ return json.loads(cached)
761
+
762
+ # Fetch from database
763
+ logs, total_count, has_more = await service.get_codespace_task_logs(...)
764
+
765
+ # Cache for 30 seconds
766
+ redis_client.setex(cache_key, 30, json.dumps({
767
+ 'logs': logs,
768
+ 'total_count': total_count,
769
+ 'has_more': has_more
770
+ }))
771
+
772
+ return logs, total_count, has_more
773
+ ```
774
+
775
+ ## 🔍 Troubleshooting
776
+
777
+ ### Common Issues
778
+
779
+ 1. **SSE Connection Closes Immediately**
780
+ - Check authentication token
781
+ - Verify user has permission to access the task
782
+ - Check CORS headers
783
+
784
+ 2. **No Logs Received**
785
+ - Verify task ID exists
786
+ - Check if task has generated any logs yet
787
+ - Ensure database connection is working
788
+
789
+ 3. **Performance Issues**
790
+ - Add database indexes
791
+ - Reduce `limit` parameter
792
+ - Implement caching
793
+
794
+ 4. **Memory Issues**
795
+ - Implement log pagination on client
796
+ - Use virtual scrolling for UI
797
+ - Consider log archiving
798
+
799
+ ### Debug Mode
800
+ ```python
801
+ # Enable debug logging
802
+ import logging
803
+ logging.basicConfig(level=logging.DEBUG)
804
+
805
+ # Add debug headers to streaming response
806
+ headers = {
807
+ "Cache-Control": "no-cache",
808
+ "Connection": "keep-alive",
809
+ "Access-Control-Allow-Origin": "*",
810
+ "Access-Control-Allow-Headers": "Cache-Control",
811
+ "X-Accel-Buffering": "no",
812
+ "X-Debug-Mode": "true" # Enable debug information
813
+ }
814
+ ```
815
+
816
+ ## 📚 Additional Resources
817
+
818
+ - [Server-Sent Events MDN](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events)
819
+ - [FastAPI StreamingResponse](https://fastapi.tiangolo.com/advanced/custom-response/#streamingresponse)
820
+ - [PostgreSQL Index Optimization](https://www.postgresql.org/docs/current/indexes.html)
821
+ - [Real-time Web Applications](https://ably.com/topic/realtime-web-applications)