mirror of
https://github.com/ggml-org/llama.cpp.git
synced 2026-09-17 20:31:47 +02:00
ui : resolve API requests against the active backend
Assisted-by: pi:llama.cpp/DeepSeek-V4.1-Flash
This commit is contained in:
@@ -1,13 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { AlertTriangle, CheckCircle, Key, RefreshCw, XCircle } from '@lucide/svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { base } from '$app/paths';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import Label from '$lib/components/ui/label/label.svelte';
|
||||
import { HEADERS, ICON_CLASS_DEFAULT, ROUTES, SETTINGS_KEYS } from '$lib/constants';
|
||||
import { KeyboardKey } from '$lib/enums';
|
||||
import { serverStore, settingsStore } from '$lib/stores';
|
||||
import { apiUrl } from '$lib/utils/api-base';
|
||||
import { fade, fly, scale } from 'svelte/transition';
|
||||
|
||||
interface Props {
|
||||
@@ -67,7 +67,7 @@
|
||||
settingsStore.updateConfig(SETTINGS_KEYS.API_KEY, apiKeyInput.trim());
|
||||
|
||||
// Test the API key by making a real request to the server
|
||||
const response = await fetch(`${base}/props`, {
|
||||
const response = await fetch(apiUrl('/props'), {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
[HEADERS.AUTHORIZATION]: `${HEADERS.BEARER}${apiKeyInput.trim()}`
|
||||
|
||||
@@ -42,6 +42,7 @@ import type {
|
||||
ApiStreamSession
|
||||
} from '$lib/types/api';
|
||||
import { isAbortError } from '$lib/utils/abort';
|
||||
import { apiUrl } from '$lib/utils/api-base';
|
||||
import { ApiError } from '$lib/utils/api-fetch';
|
||||
import { getAuthHeaders, getJsonHeaders } from '$lib/utils/api-headers';
|
||||
import { formatAttachmentText } from '$lib/utils/formatters';
|
||||
@@ -88,7 +89,7 @@ export class ChatService {
|
||||
static async areAllSlotsIdle(model?: string | null, signal?: AbortSignal): Promise<boolean> {
|
||||
try {
|
||||
const url = model ? `${API_SLOTS.LIST}?model=${encodeURIComponent(model)}` : API_SLOTS.LIST;
|
||||
const res = await fetch(url, { signal });
|
||||
const res = await fetch(apiUrl(url), { signal });
|
||||
|
||||
if (!res.ok) return true;
|
||||
|
||||
@@ -781,7 +782,7 @@ export class ChatService {
|
||||
* conv::model identity when a model was bound at POST time.
|
||||
*/
|
||||
static async lookupStreamSessions(conversationIds: string[]): Promise<ApiStreamSession[]> {
|
||||
const resp = await fetch(API_STREAM.LOOKUP, {
|
||||
const resp = await fetch(apiUrl(API_STREAM.LOOKUP), {
|
||||
body: JSON.stringify({ conversation_ids: conversationIds }),
|
||||
headers: getJsonHeaders(),
|
||||
method: 'POST'
|
||||
@@ -869,7 +870,7 @@ export class ChatService {
|
||||
}
|
||||
|
||||
try {
|
||||
await fetch(API_CHAT.COMPLETIONS, {
|
||||
await fetch(apiUrl(API_CHAT.COMPLETIONS), {
|
||||
body: JSON.stringify(requestBody),
|
||||
headers: getJsonHeaders(),
|
||||
method: 'POST',
|
||||
@@ -1230,7 +1231,7 @@ export class ChatService {
|
||||
ChatService.saveStreamState(conversationId, 0, options.model ?? null);
|
||||
}
|
||||
|
||||
const response = await fetch(API_CHAT.COMPLETIONS, {
|
||||
const response = await fetch(apiUrl(API_CHAT.COMPLETIONS), {
|
||||
body: JSON.stringify(requestBody),
|
||||
headers,
|
||||
method: 'POST',
|
||||
@@ -1342,7 +1343,7 @@ export class ChatService {
|
||||
if (model) body.model = model;
|
||||
|
||||
try {
|
||||
const res = await fetch(API_CHAT.CONTROL, {
|
||||
const res = await fetch(apiUrl(API_CHAT.CONTROL), {
|
||||
body: JSON.stringify(body),
|
||||
headers: getJsonHeaders(),
|
||||
method: 'POST'
|
||||
@@ -1373,7 +1374,7 @@ export class ChatService {
|
||||
const query = `${STREAM_QUERY_PARAMS.CONV_ID}=${encodeURIComponent(streamId)}`;
|
||||
const offset = from === undefined ? '' : `&${STREAM_QUERY_PARAMS.FROM}=${from}`;
|
||||
|
||||
return `${API_STREAM.BASE}?${query}${offset}`;
|
||||
return apiUrl(`${API_STREAM.BASE}?${query}${offset}`);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
* modelsStore and its status manager.
|
||||
*/
|
||||
|
||||
import { base } from '$app/paths';
|
||||
import { API_MODELS, MODEL_ID, type ModelSidecar, SIDECAR_TOKENS } from '$lib/constants';
|
||||
import { ServerModelStatus } from '$lib/enums';
|
||||
import type { ParsedModelId } from '$lib/types/models';
|
||||
@@ -14,6 +13,7 @@ import {
|
||||
apiDelete,
|
||||
apiFetch,
|
||||
apiPost,
|
||||
apiUrl,
|
||||
extractSseDataPayload,
|
||||
normalizeModelName,
|
||||
sidecarFromFileToken,
|
||||
@@ -345,7 +345,7 @@ export class ModelsService {
|
||||
|
||||
while (!signal.aborted) {
|
||||
try {
|
||||
const response = await fetch(`${base}${API_MODELS.SSE}`, {
|
||||
const response = await fetch(apiUrl(API_MODELS.SSE), {
|
||||
headers: getAuthHeaders(),
|
||||
signal
|
||||
});
|
||||
|
||||
@@ -5,11 +5,10 @@
|
||||
* No reactive state; consumed by toolsStore.
|
||||
*/
|
||||
|
||||
import { base } from '$app/paths';
|
||||
import { API_TOOLS, HEADERS } from '$lib/constants';
|
||||
import { ToolResponseField } from '$lib/enums';
|
||||
import type { ServerToolInfo, ToolExecutionResult } from '$lib/types';
|
||||
import { apiFetch } from '$lib/utils';
|
||||
import { apiFetch, apiUrl } from '$lib/utils';
|
||||
import { getJsonHeaders } from '$lib/utils/api-headers';
|
||||
import { parseSseJsonStream, type SseJsonEvent } from '$lib/utils/sse';
|
||||
|
||||
@@ -108,7 +107,7 @@ export class ToolsService {
|
||||
|
||||
if (cwd) headers[HEADERS.X_TOOL_CWD_HEADER] = cwd;
|
||||
|
||||
const response = await fetch(`${base}${API_TOOLS.EXECUTE}`, {
|
||||
const response = await fetch(apiUrl(API_TOOLS.EXECUTE), {
|
||||
body: JSON.stringify({ params, stream: true, tool: toolName }),
|
||||
headers,
|
||||
method: 'POST',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { apiUrl } from './api-base';
|
||||
import { getAuthHeaders, getJsonHeaders } from './api-headers';
|
||||
import { base } from '$app/paths';
|
||||
import { API_ABSOLUTE_URL_PROTOCOLS, ERROR_MESSAGES, HTTP_CODE_TO_STRING } from '$lib/constants';
|
||||
import { ERROR_MESSAGES, HTTP_CODE_TO_STRING } from '$lib/constants';
|
||||
|
||||
/**
|
||||
* API Fetch Utilities
|
||||
@@ -62,8 +62,7 @@ export async function apiFetch<T>(path: string, options: ApiFetchOptions = {}):
|
||||
const { authOnly = false, headers: customHeaders, ...fetchOptions } = options;
|
||||
const baseHeaders = authOnly ? getAuthHeaders() : getJsonHeaders();
|
||||
const headers = { ...baseHeaders, ...customHeaders };
|
||||
// absolute URLs with an allowed protocol pass through untouched; relative paths get the base prefix
|
||||
const url = API_ABSOLUTE_URL_PROTOCOLS.some((p) => path.startsWith(p)) ? path : `${base}${path}`;
|
||||
const url = apiUrl(path);
|
||||
|
||||
let response;
|
||||
|
||||
@@ -106,7 +105,7 @@ export async function apiFetchWithParams<T>(
|
||||
params: Record<string, string>,
|
||||
options: ApiFetchOptions = {}
|
||||
): Promise<T> {
|
||||
const url = new URL(basePath, window.location.href);
|
||||
const url = new URL(apiUrl(basePath), window.location.href);
|
||||
|
||||
for (const [key, value] of Object.entries(params)) {
|
||||
if (value !== undefined && value !== null) {
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import { getBackend } from './api-base';
|
||||
import { redactValue } from './redact';
|
||||
import { CORS_PROXY, HEADERS } from '$lib/constants';
|
||||
import { CORS_PROXY, HEADERS, LOCAL_BACKEND_ID } from '$lib/constants';
|
||||
import { MimeTypeApplication } from '$lib/enums';
|
||||
import { settingsStore } from '$lib/stores/settings/index.svelte';
|
||||
|
||||
/**
|
||||
* Get authorization headers for API requests
|
||||
* Includes Bearer token if API key is configured
|
||||
* Get authorization headers for API requests to a backend.
|
||||
* External backends carry their own key; the local backend reuses the global
|
||||
* API key setting.
|
||||
*/
|
||||
export function getAuthHeaders(): Record<string, string> {
|
||||
const currentConfig = settingsStore.config;
|
||||
const apiKey = currentConfig.apiKey?.toString().trim();
|
||||
export function getAuthHeaders(backendId?: string): Record<string, string> {
|
||||
const apiKey = resolveBackendApiKey(backendId);
|
||||
|
||||
return apiKey ? { [HEADERS.AUTHORIZATION]: `${HEADERS.BEARER}${apiKey}` } : {};
|
||||
}
|
||||
@@ -17,13 +18,25 @@ export function getAuthHeaders(): Record<string, string> {
|
||||
/**
|
||||
* Get standard JSON headers with optional authorization
|
||||
*/
|
||||
export function getJsonHeaders(): Record<string, string> {
|
||||
export function getJsonHeaders(backendId?: string): Record<string, string> {
|
||||
return {
|
||||
[HEADERS.CONTENT_TYPE]: MimeTypeApplication.JSON,
|
||||
...getAuthHeaders()
|
||||
...getAuthHeaders(backendId)
|
||||
};
|
||||
}
|
||||
|
||||
function resolveBackendApiKey(backendId?: string): string | undefined {
|
||||
const backend = getBackend(backendId);
|
||||
const backendKey = backend?.apiKey?.trim();
|
||||
|
||||
if (backendKey) return backendKey;
|
||||
|
||||
// an external backend without its own key must not receive the local key
|
||||
if (backend && backend.id !== LOCAL_BACKEND_ID) return undefined;
|
||||
|
||||
return settingsStore.config.apiKey?.toString().trim() || undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize HTTP headers by redacting sensitive values.
|
||||
* Known sensitive headers (from HEADERS.REDACTED) and any extra headers
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { error } from '@sveltejs/kit';
|
||||
import { browser } from '$app/environment';
|
||||
import { base } from '$app/paths';
|
||||
import { HEADERS } from '$lib/constants';
|
||||
import { MimeTypeApplication } from '$lib/enums';
|
||||
import { settingsStore } from '$lib/stores/settings/index.svelte';
|
||||
import { apiUrl } from '$lib/utils/api-base';
|
||||
|
||||
/**
|
||||
* Validates API key by making a request to the server props endpoint
|
||||
@@ -28,7 +28,7 @@ export async function validateApiKey(fetch: typeof globalThis.fetch): Promise<vo
|
||||
headers[HEADERS.AUTHORIZATION] = `${HEADERS.BEARER}${apiKey}`;
|
||||
}
|
||||
|
||||
const response = await fetch(`${base}/props`, { headers });
|
||||
const response = await fetch(apiUrl('/props'), { headers });
|
||||
|
||||
if (!response.ok) {
|
||||
if (response.status === 401 || response.status === 403) {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* CORS Proxy utility for routing requests through llama-server's CORS proxy.
|
||||
*/
|
||||
|
||||
import { base } from '$app/paths';
|
||||
import { apiUrl } from './api-base';
|
||||
import { CORS_PROXY, CORS_PROXY_ENDPOINT } from '$lib/constants';
|
||||
|
||||
/**
|
||||
@@ -11,7 +11,7 @@ import { CORS_PROXY, CORS_PROXY_ENDPOINT } from '$lib/constants';
|
||||
* @returns URL pointing to the CORS proxy with target encoded
|
||||
*/
|
||||
export function buildProxiedUrl(targetUrl: string): URL {
|
||||
const proxyPath = `${base}${CORS_PROXY_ENDPOINT}`;
|
||||
const proxyPath = apiUrl(CORS_PROXY_ENDPOINT);
|
||||
const proxyUrl = new URL(proxyPath, window.location.origin);
|
||||
|
||||
proxyUrl.searchParams.set(CORS_PROXY.URL_PARAM, targetUrl);
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import '../app.css';
|
||||
import { browser } from '$app/environment';
|
||||
import { goto } from '$app/navigation';
|
||||
import { base } from '$app/paths';
|
||||
import { page } from '$app/state';
|
||||
import { SidebarNavigation } from '$lib/components/app';
|
||||
import { PwaMetaTags, PwaRefreshAlert } from '$lib/components/pwa';
|
||||
@@ -31,6 +30,7 @@
|
||||
versionStore
|
||||
} from '$lib/stores';
|
||||
import { initStores } from '$lib/stores/init';
|
||||
import { apiUrl } from '$lib/utils/api-base';
|
||||
import { ModeWatcher } from 'mode-watcher';
|
||||
import { untrack } from 'svelte';
|
||||
import { onMount } from 'svelte';
|
||||
@@ -167,7 +167,7 @@
|
||||
[HEADERS.AUTHORIZATION]: `${HEADERS.BEARER}${apiKey.trim()}`
|
||||
};
|
||||
|
||||
fetch(`${base}/props`, { headers })
|
||||
fetch(apiUrl('/props'), { headers })
|
||||
.then((response) => {
|
||||
if (response.status === 401 || response.status === 403) {
|
||||
window.location.reload();
|
||||
|
||||
Reference in New Issue
Block a user