File size: 4,745 Bytes
6242ddb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
import type { AnalysisResult, ComparisonResult, FilterParams, JobStatus } from '../types';

const API_BASE = '/api/v1';

class ApiError extends Error {
  constructor(
    public status: number,
    message: string,
    public correlationId?: string,
  ) {
    super(message);
    this.name = 'ApiError';
  }
}

function getHeaders(): Record<string, string> {
  const apiKey = localStorage.getItem('api_key') || 'dev-key-1';
  return {
    'X-API-Key': apiKey,
    'Content-Type': 'application/json',
  };
}

async function handleResponse<T>(response: Response): Promise<T> {
  if (!response.ok) {
    const body = await response.json().catch(() => ({ detail: response.statusText }));
    throw new ApiError(response.status, body.detail || 'Request failed', body.correlation_id);
  }
  return response.json();
}

export const api = {
  async uploadFile(file: File, source?: string): Promise<JobStatus> {
    const formData = new FormData();
    formData.append('file', file);

    const params = new URLSearchParams();
    if (source) params.set('source', source);

    const apiKey = localStorage.getItem('api_key') || 'dev-key-1';
    const response = await fetch(`${API_BASE}/upload?${params}`, {
      method: 'POST',
      headers: { 'X-API-Key': apiKey },
      body: formData,
    });

    return handleResponse<JobStatus>(response);
  },

  async uploadChunked(
    file: File,
    onProgress?: (progress: number) => void,
  ): Promise<JobStatus> {
    const CHUNK_SIZE = 10 * 1024 * 1024; // 10MB
    const totalChunks = Math.ceil(file.size / CHUNK_SIZE);
    let uploadId: string | undefined;
    let lastStatus: JobStatus | undefined;

    for (let i = 0; i < totalChunks; i++) {
      const start = i * CHUNK_SIZE;
      const end = Math.min(start + CHUNK_SIZE, file.size);
      const chunk = file.slice(start, end);

      const formData = new FormData();
      formData.append('file', chunk, file.name);

      const params = new URLSearchParams({
        chunk_index: String(i),
        total_chunks: String(totalChunks),
      });
      if (uploadId) params.set('upload_id', uploadId);

      const apiKey = localStorage.getItem('api_key') || 'dev-key-1';
      const response = await fetch(`${API_BASE}/upload/chunked?${params}`, {
        method: 'POST',
        headers: { 'X-API-Key': apiKey },
        body: formData,
      });

      lastStatus = await handleResponse<JobStatus>(response);
      uploadId = lastStatus.job_id;
      onProgress?.((i + 1) / totalChunks);
    }

    return lastStatus!;
  },

  async getJobs(): Promise<JobStatus[]> {
    const response = await fetch(`${API_BASE}/jobs`, { headers: getHeaders() });
    return handleResponse<JobStatus[]>(response);
  },

  async getJobResult(jobId: string): Promise<AnalysisResult> {
    const response = await fetch(`${API_BASE}/jobs/${jobId}`, { headers: getHeaders() });
    return handleResponse<AnalysisResult>(response);
  },

  async getJobStatus(jobId: string): Promise<JobStatus> {
    const response = await fetch(`${API_BASE}/jobs/${jobId}/status`, { headers: getHeaders() });
    return handleResponse<JobStatus>(response);
  },

  async filterResults(jobId: string, filters: FilterParams) {
    const response = await fetch(`${API_BASE}/jobs/${jobId}/filter`, {
      method: 'POST',
      headers: getHeaders(),
      body: JSON.stringify(filters),
    });
    return handleResponse<{ total: number; page: number; entries: AnalysisResult['entries'] }>(response);
  },

  async compareSegments(jobId: string, segmentA: FilterParams, segmentB: FilterParams): Promise<ComparisonResult> {
    const response = await fetch(`${API_BASE}/jobs/${jobId}/compare`, {
      method: 'POST',
      headers: getHeaders(),
      body: JSON.stringify({ segment_a: segmentA, segment_b: segmentB }),
    });
    return handleResponse<ComparisonResult>(response);
  },

  async exportResults(jobId: string, format: 'csv' | 'json' | 'pdf', filters?: FilterParams): Promise<Blob> {
    const response = await fetch(`${API_BASE}/jobs/${jobId}/export?fmt=${format}`, {
      method: 'POST',
      headers: getHeaders(),
      body: filters ? JSON.stringify(filters) : '{}',
    });

    if (!response.ok) {
      throw new ApiError(response.status, 'Export failed');
    }
    return response.blob();
  },

  subscribeToEvents(onMessage: (data: Record<string, unknown>) => void): EventSource {
    const apiKey = localStorage.getItem('api_key') || 'dev-key-1';
    const es = new EventSource(`${API_BASE}/events/analysis?api_key=${apiKey}`);

    es.addEventListener('analysis_update', (event) => {
      try {
        const data = JSON.parse(event.data);
        onMessage(data);
      } catch {
        // ignore parse errors
      }
    });

    return es;
  },
};