diff --git a/src/pages/playground/apis/index.ts b/src/pages/playground/apis/index.ts index 1826fc23..c1b2bacc 100644 --- a/src/pages/playground/apis/index.ts +++ b/src/pages/playground/apis/index.ts @@ -1,5 +1,9 @@ import { GPUSTACK_API_BASE_URL, OPENAI_COMPATIBLE } from '@/config/settings'; -import { createFormData, errorHandler } from '@/utils/fetch-chunk-data'; +import { + createFormData, + errorHandler, + tenantHeaders +} from '@/utils/fetch-chunk-data'; import { request } from '@umijs/max'; export { GPUSTACK_API_BASE_URL, OPENAI_COMPATIBLE }; @@ -92,7 +96,8 @@ export const createImages = async ( body: JSON.stringify(params), signal: options.signal, headers: { - 'Content-Type': 'application/json' + 'Content-Type': 'application/json', + ...tenantHeaders() } }); if (!res.ok) { @@ -112,7 +117,10 @@ export const editImage = async (params: { const response = await fetch(EDIT_IMAGE_API, { method: 'POST', body: createFormData(params.data), - signal: params.signal + signal: params.signal, + headers: { + ...tenantHeaders() + } }); if (!response.ok) { return await errorHandler(response); @@ -129,7 +137,8 @@ export const createImage = async (params: { body: JSON.stringify(params.data), signal: params.signal, headers: { - 'Content-Type': 'application/json' + 'Content-Type': 'application/json', + ...tenantHeaders() } }); @@ -156,7 +165,8 @@ export const textToSpeech = async (params: any, options?: any) => { method: 'POST', body: JSON.stringify(params.data), headers: { - 'Content-Type': 'application/json' + 'Content-Type': 'application/json', + ...tenantHeaders() }, signal: params.signal }); diff --git a/src/utils/fetch-chunk-data.ts b/src/utils/fetch-chunk-data.ts index addf999d..66618aa9 100644 --- a/src/utils/fetch-chunk-data.ts +++ b/src/utils/fetch-chunk-data.ts @@ -3,6 +3,48 @@ import qs from 'query-string'; const extractStreamRegx = /(data|error):\s*({.*?})(?=\n|$)/g; +const readJsonNumber = ( + storage: Storage | null, + key: string +): number | null => { + if (storage == null) { + return null; + } + try { + const raw = storage.getItem(key); + if (raw == null) { + return null; + } + const parsed = JSON.parse(raw); + return typeof parsed === 'number' ? parsed : null; + } catch { + return null; + } +}; + +/** + * Read the active Org id the same way the umi request interceptor in + * ``request.extensions.ts`` does, then translate it to the header the + * backend's tenant resolver expects. Lets non-umi ``fetch()`` paths — + * streaming Playground completions, raw image / TTS POSTs — pin + * tenant context with the same precedence rules as everywhere else + * (createScope override first, current org second). Returns an empty + * object when no active org context is set. + */ +export const tenantHeaders = (): Record => { + if (typeof window === 'undefined') { + return {}; + } + try { + const orgId = + readJsonNumber(window.sessionStorage, 'createScopeOrgOverride') ?? + readJsonNumber(window.localStorage, 'currentOrganizationId'); + return orgId == null ? {} : { 'X-Organization-Id': String(orgId) }; + } catch { + return {}; + } +}; + const extractJSON = ( dataStr: string ): { results: any[]; remaining: string } => { @@ -71,7 +113,8 @@ export const fetchChunkedData = async (params: { signal: params.signal, headers: { 'Content-Type': 'application/json', - ...params.headers + ...tenantHeaders(), + ...(params.headers || {}) } }); @@ -122,7 +165,11 @@ export const fetchChunkedDataPostFormData = async (params: { const response = await fetch(url, { method: 'POST', body: createFormData(params.data), - signal: params.signal + signal: params.signal, + headers: { + ...tenantHeaders(), + ...(params.headers || {}) + } }); if (!response.ok) { return await errorHandler(response);