(apiKey, file, onProgress)
| 191 | } |
| 192 | |
| 193 | export function uploadFile(apiKey, file, onProgress) { |
| 194 | return new Promise((resolve, reject) => { |
| 195 | const url = `${BASE_URL}/api/v1/upload_file`; |
| 196 | const formData = new FormData(); |
| 197 | formData.append('file', file); |
| 198 | |
| 199 | const xhr = new XMLHttpRequest(); |
| 200 | xhr.open('POST', url); |
| 201 | xhr.setRequestHeader('x-api-key', apiKey); |
| 202 | |
| 203 | if (onProgress) { |
| 204 | xhr.upload.onprogress = (event) => { |
| 205 | if (event.lengthComputable) { |
| 206 | const percentComplete = Math.round((event.loaded / event.total) * 100); |
| 207 | onProgress(percentComplete); |
| 208 | } |
| 209 | }; |
| 210 | } |
| 211 | |
| 212 | xhr.onload = () => { |
| 213 | if (xhr.status >= 200 && xhr.status < 300) { |
| 214 | try { |
| 215 | const data = JSON.parse(xhr.responseText); |
| 216 | const fileUrl = data.url || data.file_url || data.data?.url; |
| 217 | if (!fileUrl) { |
| 218 | reject(new Error('No URL returned from file upload')); |
| 219 | } else { |
| 220 | resolve(fileUrl); |
| 221 | } |
| 222 | } catch (e) { |
| 223 | reject(new Error('Failed to parse upload response')); |
| 224 | } |
| 225 | } else { |
| 226 | let detail = xhr.statusText; |
| 227 | try { |
| 228 | const errObj = JSON.parse(xhr.responseText); |
| 229 | detail = errObj.detail || detail; |
| 230 | } catch (e) { |
| 231 | // fallback to statusText |
| 232 | } |
| 233 | notifyAuthRequired(xhr.status, detail); |
| 234 | reject(new Error(`File upload failed: ${xhr.status} - ${detail}`)); |
| 235 | } |
| 236 | }; |
| 237 | |
| 238 | xhr.onerror = () => reject(new Error('Network error during file upload')); |
| 239 | xhr.send(formData); |
| 240 | }); |
| 241 | } |
| 242 | |
| 243 | export async function getUserBalance(apiKey) { |
| 244 | const response = await fetch(`${BASE_URL}/api/v1/account/balance`, { |
no test coverage detected