Spaces:
Sleeping
Sleeping
| import { getApiKeys } from "@/lib/localDb"; | |
| import { UPDATER_CONFIG } from "@/shared/constants/config"; | |
| import { getConsistentMachineId } from "@/shared/utils/machineId"; | |
| const CLI_TOKEN_SALT = "9r-cli-auth"; | |
| function createSilentWavFile() { | |
| const sampleRate = 16000; | |
| const channels = 1; | |
| const bitsPerSample = 16; | |
| const durationMs = 250; | |
| const sampleCount = Math.max(1, Math.floor((sampleRate * durationMs) / 1000)); | |
| const dataSize = sampleCount * channels * (bitsPerSample / 8); | |
| const buffer = new ArrayBuffer(44 + dataSize); | |
| const view = new DataView(buffer); | |
| const writeAscii = (offset, value) => { | |
| for (let i = 0; i < value.length; i += 1) { | |
| view.setUint8(offset + i, value.charCodeAt(i)); | |
| } | |
| }; | |
| writeAscii(0, "RIFF"); | |
| view.setUint32(4, 36 + dataSize, true); | |
| writeAscii(8, "WAVE"); | |
| writeAscii(12, "fmt "); | |
| view.setUint32(16, 16, true); | |
| view.setUint16(20, 1, true); | |
| view.setUint16(22, channels, true); | |
| view.setUint32(24, sampleRate, true); | |
| view.setUint32(28, sampleRate * channels * (bitsPerSample / 8), true); | |
| view.setUint16(32, channels * (bitsPerSample / 8), true); | |
| view.setUint16(34, bitsPerSample, true); | |
| writeAscii(36, "data"); | |
| view.setUint32(40, dataSize, true); | |
| return new Blob([buffer], { type: "audio/wav" }); | |
| } | |
| async function getInternalHeaders() { | |
| let apiKey = null; | |
| try { | |
| const keys = await getApiKeys(); | |
| apiKey = keys.find((k) => k.isActive !== false)?.key || null; | |
| } catch {} | |
| const headers = { "Content-Type": "application/json" }; | |
| if (apiKey) headers["Authorization"] = `Bearer ${apiKey}`; | |
| headers["x-9r-cli-token"] = await getConsistentMachineId(CLI_TOKEN_SALT); | |
| return headers; | |
| } | |
| export async function pingModelByKind(model, kind, baseUrl = `http://127.0.0.1:${process.env.PORT || UPDATER_CONFIG.appPort}`) { | |
| const headers = await getInternalHeaders(); | |
| const start = Date.now(); | |
| if (kind === "embedding") { | |
| const res = await fetch(`${baseUrl}/api/v1/embeddings`, { | |
| method: "POST", | |
| headers, | |
| body: JSON.stringify({ model, input: "test" }), | |
| signal: AbortSignal.timeout(15000), | |
| }); | |
| const latencyMs = Date.now() - start; | |
| const rawText = await res.text().catch(() => ""); | |
| let parsed = null; | |
| try { parsed = rawText ? JSON.parse(rawText) : null; } catch {} | |
| if (!res.ok) { | |
| const detail = parsed?.error?.message || parsed?.error || rawText; | |
| return { ok: false, latencyMs, error: `HTTP ${res.status}${detail ? `: ${String(detail).slice(0, 240)}` : ""}`, status: res.status }; | |
| } | |
| const hasEmbedding = Array.isArray(parsed?.data) && parsed.data.length > 0 && Array.isArray(parsed.data[0]?.embedding); | |
| if (!hasEmbedding) { | |
| return { ok: false, latencyMs, status: res.status, error: "Provider returned no embedding data" }; | |
| } | |
| return { ok: true, latencyMs, error: null, status: res.status }; | |
| } | |
| if (kind === "image") { | |
| const res = await fetch(`${baseUrl}/api/v1/images/generations`, { | |
| method: "POST", | |
| headers, | |
| body: JSON.stringify({ model, prompt: "test" }), | |
| signal: AbortSignal.timeout(15000), | |
| }); | |
| const latencyMs = Date.now() - start; | |
| const rawText = await res.text().catch(() => ""); | |
| let parsed = null; | |
| try { parsed = rawText ? JSON.parse(rawText) : null; } catch {} | |
| if (!res.ok) { | |
| const detail = parsed?.error?.message || parsed?.msg || parsed?.message || parsed?.error || rawText; | |
| return { ok: false, latencyMs, error: `HTTP ${res.status}${detail ? `: ${String(detail).slice(0, 240)}` : ""}`, status: res.status }; | |
| } | |
| const hasImages = Array.isArray(parsed?.data) && parsed.data.length > 0; | |
| if (!hasImages) { | |
| return { ok: false, latencyMs, status: res.status, error: "Provider returned no image data for this model" }; | |
| } | |
| return { ok: true, latencyMs, error: null, status: res.status }; | |
| } | |
| if (kind === "stt") { | |
| const form = new FormData(); | |
| const sampleAudio = createSilentWavFile(); | |
| form.append("file", sampleAudio, "test.wav"); | |
| form.append("model", model); | |
| const res = await fetch(`${baseUrl}/api/v1/audio/transcriptions`, { | |
| method: "POST", | |
| headers: Object.fromEntries(Object.entries(headers).filter(([key]) => key.toLowerCase() !== "content-type")), | |
| body: form, | |
| signal: AbortSignal.timeout(15000), | |
| }); | |
| const latencyMs = Date.now() - start; | |
| const rawText = await res.text().catch(() => ""); | |
| let parsed = null; | |
| try { parsed = rawText ? JSON.parse(rawText) : null; } catch {} | |
| if (!res.ok) { | |
| const detail = parsed?.error?.message || parsed?.msg || parsed?.message || parsed?.error || rawText; | |
| return { ok: false, latencyMs, error: `HTTP ${res.status}${detail ? `: ${String(detail).slice(0, 240)}` : ""}`, status: res.status }; | |
| } | |
| const text = typeof parsed?.text === "string" ? parsed.text : ""; | |
| if (!text.trim()) { | |
| return { ok: false, latencyMs, status: res.status, error: "Provider returned no transcription text for this model" }; | |
| } | |
| return { ok: true, latencyMs, error: null, status: res.status }; | |
| } | |
| const res = await fetch(`${baseUrl}/api/v1/chat/completions`, { | |
| method: "POST", | |
| headers, | |
| body: JSON.stringify({ | |
| model, | |
| // Claude-on-Copilot returns empty choices at max_tokens:1 (budget is spent | |
| // before a content token emits), so a 1-token probe yields a false negative. | |
| max_tokens: 16, | |
| stream: false, | |
| messages: [{ role: "user", content: "hi" }], | |
| }), | |
| signal: AbortSignal.timeout(15000), | |
| }); | |
| const latencyMs = Date.now() - start; | |
| const rawText = await res.text().catch(() => ""); | |
| let parsed = null; | |
| try { parsed = rawText ? JSON.parse(rawText) : null; } catch {} | |
| if (!res.ok) { | |
| const detail = parsed?.error?.message || parsed?.msg || parsed?.message || parsed?.error || rawText; | |
| return { ok: false, latencyMs, error: `HTTP ${res.status}${detail ? `: ${String(detail).slice(0, 240)}` : ""}`, status: res.status }; | |
| } | |
| const providerStatus = parsed?.status; | |
| const providerMsg = parsed?.msg || parsed?.message; | |
| const hasProviderErrorStatus = providerStatus !== undefined | |
| && providerStatus !== null | |
| && String(providerStatus) !== "200" | |
| && String(providerStatus) !== "0"; | |
| if (hasProviderErrorStatus && providerMsg) { | |
| return { | |
| ok: false, | |
| latencyMs, | |
| status: res.status, | |
| error: `Provider status ${providerStatus}: ${String(providerMsg).slice(0, 240)}`, | |
| }; | |
| } | |
| if (parsed?.error) { | |
| const providerError = parsed?.error?.message || parsed?.error || "Provider returned an error"; | |
| return { | |
| ok: false, | |
| latencyMs, | |
| status: res.status, | |
| error: String(providerError).slice(0, 240), | |
| }; | |
| } | |
| const hasChoices = Array.isArray(parsed?.choices) && parsed.choices.length > 0; | |
| if (!hasChoices) { | |
| return { | |
| ok: false, | |
| latencyMs, | |
| status: res.status, | |
| error: "Provider returned no completion choices for this model", | |
| }; | |
| } | |
| return { ok: true, latencyMs, error: null, status: res.status }; | |
| } | |