import { API_URL } from './config.js' import { useSettingsStore } from '../stores/settings' const API_KEY = 'your-secret-key-here' let cachedIP = null function generateRequestId() { if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') { return crypto.randomUUID() } return `${Date.now()}-${Math.random().toString(16).slice(2)}` } function getCancelUrl(apiUrl) { const normalized = String(apiUrl || '').replace(/\/+$/, '') if (!normalized) return '/v1/completions/cancel' if (normalized.endsWith('/v1/completions')) { return `${normalized}/cancel` } return `${normalized}/cancel` } function normalizeAbortReason(reason) { if (typeof reason === 'string' && reason.trim()) { return reason.trim().slice(0, 64) } return 'abort' } async function sendCancelRequest(cancelUrl, requestId, reason) { try { await fetch(cancelUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-API-Key': API_KEY, }, body: JSON.stringify({ request_id: requestId, reason, }), }) } catch { // Cancel request failed silently } } async function getClientIP() { if (cachedIP) return cachedIP try { const controller = new AbortController() setTimeout(() => controller.abort(), 3000) const res = await fetch('https://api.ipify.org?format=json', { signal: controller.signal }) const data = await res.json() cachedIP = data.ip return cachedIP } catch { return null } } export async function fetchSuggestion(prefix, suffix, signal, apiUrl = API_URL) { const requestId = generateRequestId() const cancelUrl = getCancelUrl(apiUrl) const onAbort = () => { const reason = normalizeAbortReason(signal?.reason) void sendCancelRequest(cancelUrl, requestId, reason) } if (signal) { if (signal.aborted) { onAbort() } else { signal.addEventListener('abort', onAbort, { once: true }) } } try { const settings = useSettingsStore() const clientIP = await getClientIP() const headers = { 'Content-Type': 'application/json', 'X-API-Key': API_KEY, 'X-Request-Id': requestId, } // Only send IP if privacy mode is OFF if (clientIP && !settings.privacyMode) { headers['X-Client-IP'] = clientIP } const body = { prefix, suffix, languageId: 'markdown', model_thinking: settings.modelThinking, privacy_mode: settings.privacyMode, user_preferences: { language: settings.language, currency: settings.currency, timezone: settings.detectedTimezone, }, } const res = await fetch(apiUrl, { method: 'POST', headers, body: JSON.stringify(body), signal, }) if (!res.ok) { const errorText = await res.text() throw new Error(`HTTP ${res.status}: ${errorText}`) } const reader = res.body?.getReader() if (!reader) { throw new Error('No reader available') } let text = '' let buffer = '' while (true) { const { done, value } = await reader.read() if (done) break buffer += new TextDecoder().decode(value) const lines = buffer.split('\n') buffer = lines.pop() || '' for (const line of lines) { if (!line.startsWith('data: ')) continue const jsonStr = line.slice(6).trim() if (!jsonStr) continue try { const data = JSON.parse(jsonStr) if (data.content) { text += data.content } if (data.done || data.error) break } catch (e) { // skip invalid lines } } } return text } catch (e) { if (e.name === 'AbortError') { // ignore abort } else { throw e } } finally { if (signal) { signal.removeEventListener('abort', onAbort) } } }