🌐 feat: Add support to SubDirectory hosting (#9155)

* feat: Add support to SubDirectory hosting

* fix: address linting and failing test

* fix: browser context validation

---------

Co-authored-by: Danny Avila <danny@librechat.ai>
This commit is contained in:
José Pedro Silva 2025-08-27 07:00:18 +01:00 committed by GitHub
parent a820863e8b
commit 18d5a75cdc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 252 additions and 199 deletions

View file

@ -49,7 +49,7 @@
"pluginKey": "image_gen_oai", "pluginKey": "image_gen_oai",
"toolkit": true, "toolkit": true,
"description": "Image Generation and Editing using OpenAI's latest state-of-the-art models", "description": "Image Generation and Editing using OpenAI's latest state-of-the-art models",
"icon": "/assets/image_gen_oai.png", "icon": "assets/image_gen_oai.png",
"authConfig": [ "authConfig": [
{ {
"authField": "IMAGE_GEN_OAI_API_KEY", "authField": "IMAGE_GEN_OAI_API_KEY",
@ -75,7 +75,7 @@
"name": "Browser", "name": "Browser",
"pluginKey": "web-browser", "pluginKey": "web-browser",
"description": "Scrape and summarize webpage data", "description": "Scrape and summarize webpage data",
"icon": "/assets/web-browser.svg", "icon": "assets/web-browser.svg",
"authConfig": [ "authConfig": [
{ {
"authField": "OPENAI_API_KEY", "authField": "OPENAI_API_KEY",
@ -170,7 +170,7 @@
"name": "OpenWeather", "name": "OpenWeather",
"pluginKey": "open_weather", "pluginKey": "open_weather",
"description": "Get weather forecasts and historical data from the OpenWeather API", "description": "Get weather forecasts and historical data from the OpenWeather API",
"icon": "/assets/openweather.png", "icon": "assets/openweather.png",
"authConfig": [ "authConfig": [
{ {
"authField": "OPENWEATHER_API_KEY", "authField": "OPENWEATHER_API_KEY",

View file

@ -52,7 +52,20 @@ const startServer = async () => {
const appConfig = await getAppConfig(); const appConfig = await getAppConfig();
await updateInterfacePermissions(appConfig); await updateInterfacePermissions(appConfig);
const indexPath = path.join(appConfig.paths.dist, 'index.html'); const indexPath = path.join(appConfig.paths.dist, 'index.html');
const indexHTML = fs.readFileSync(indexPath, 'utf8'); let indexHTML = fs.readFileSync(indexPath, 'utf8');
// In order to provide support to serving the application in a sub-directory
// We need to update the base href if the DOMAIN_CLIENT is specified and not the root path
if (process.env.DOMAIN_CLIENT) {
const clientUrl = new URL(process.env.DOMAIN_CLIENT);
const baseHref = clientUrl.pathname.endsWith('/')
? clientUrl.pathname
: `${clientUrl.pathname}/`;
if (baseHref !== '/') {
logger.info(`Setting base href to ${baseHref}`);
indexHTML = indexHTML.replace(/base href="\/"/, `base href="${baseHref}"`);
}
}
app.get('/health', (_req, res) => res.status(200).send('OK')); app.get('/health', (_req, res) => res.status(200).send('OK'));
@ -135,7 +148,8 @@ const startServer = async () => {
const lang = req.cookies.lang || req.headers['accept-language']?.split(',')[0] || 'en-US'; const lang = req.cookies.lang || req.headers['accept-language']?.split(',')[0] || 'en-US';
const saneLang = lang.replace(/"/g, '&quot;'); const saneLang = lang.replace(/"/g, '&quot;');
const updatedIndexHtml = indexHTML.replace(/lang="en-US"/g, `lang="${saneLang}"`); let updatedIndexHtml = indexHTML.replace(/lang="en-US"/g, `lang="${saneLang}"`);
res.type('html'); res.type('html');
res.send(updatedIndexHtml); res.send(updatedIndexHtml);
}); });

View file

@ -2,15 +2,16 @@
<html lang="en-US"> <html lang="en-US">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<base href="/" />
<meta name="theme-color" content="#171717" /> <meta name="theme-color" content="#171717" />
<meta name="mobile-web-app-capable" content="yes" /> <meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" /> <meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" /> <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="description" content="LibreChat - An open source chat application with support for multiple AI models" /> <meta name="description" content="LibreChat - An open source chat application with support for multiple AI models" />
<title>LibreChat</title> <title>LibreChat</title>
<link rel="icon" type="image/png" sizes="32x32" href="/assets/favicon-32x32.png" /> <link rel="icon" type="image/png" sizes="32x32" href="assets/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/assets/favicon-16x16.png" /> <link rel="icon" type="image/png" sizes="16x16" href="assets/favicon-16x16.png" />
<link rel="apple-touch-icon" href="/assets/apple-touch-icon-180x180.png" /> <link rel="apple-touch-icon" href="assets/apple-touch-icon-180x180.png" />
<meta name="viewport" content="width=device-width, initial-scale=1, interactive-widget=resizes-content" /> <meta name="viewport" content="width=device-width, initial-scale=1, interactive-widget=resizes-content" />
<style> <style>
html, html,

View file

@ -62,7 +62,7 @@ export default () => (
<ScreenshotProvider> <ScreenshotProvider>
<App /> <App />
<iframe <iframe
src="/assets/silence.mp3" src="assets/silence.mp3"
allow="autoplay" allow="autoplay"
id="audio" id="audio"
title="audio-silence" title="audio-silence"

View file

@ -62,7 +62,7 @@ function AuthLayout({
<BlinkAnimation active={isFetching}> <BlinkAnimation active={isFetching}>
<div className="mt-6 h-10 w-full bg-cover"> <div className="mt-6 h-10 w-full bg-cover">
<img <img
src="/assets/logo.svg" src="assets/logo.svg"
className="h-full w-full object-contain" className="h-full w-full object-contain"
alt={localize('com_ui_logo', { 0: startupConfig?.appTitle ?? 'LibreChat' })} alt={localize('com_ui_logo', { 0: startupConfig?.appTitle ?? 'LibreChat' })}
/> />

View file

@ -1,7 +1,7 @@
import React, { memo, useMemo, useRef, useEffect } from 'react'; import React, { memo, useMemo, useRef, useEffect } from 'react';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { useToastContext } from '@librechat/client'; import { useToastContext } from '@librechat/client';
import { PermissionTypes, Permissions } from 'librechat-data-provider'; import { PermissionTypes, Permissions, dataService } from 'librechat-data-provider';
import CodeBlock from '~/components/Messages/Content/CodeBlock'; import CodeBlock from '~/components/Messages/Content/CodeBlock';
import useHasAccess from '~/hooks/Roles/useHasAccess'; import useHasAccess from '~/hooks/Roles/useHasAccess';
import { useFileDownload } from '~/data-provider'; import { useFileDownload } from '~/data-provider';
@ -135,9 +135,15 @@ export const a: React.ElementType = memo(({ href, children }: TAnchorProps) => {
props.onClick = handleDownload; props.onClick = handleDownload;
props.target = '_blank'; props.target = '_blank';
const domainServerBaseUrl = dataService.getDomainServerBaseUrl();
return ( return (
<a <a
href={filepath?.startsWith('files/') ? `/api/${filepath}` : `/api/files/${filepath}`} href={
filepath?.startsWith('files/')
? `${domainServerBaseUrl}/${filepath}`
: `${domainServerBaseUrl}/files/${filepath}`
}
{...props} {...props}
> >
{children} {children}

View file

@ -65,7 +65,7 @@ export default function ApiKeyDialog({
{languageIcons.map((icon) => ( {languageIcons.map((icon) => (
<div key={icon} className="h-6 w-6"> <div key={icon} className="h-6 w-6">
<img <img
src={`/assets/${icon}`} src={`assets/${icon}`}
alt="" alt=""
className="h-full w-full object-contain opacity-[0.85] dark:invert" className="h-full w-full object-contain opacity-[0.85] dark:invert"
/> />

View file

@ -5,24 +5,24 @@ import { IconContext } from '~/common';
import { cn } from '~/utils'; import { cn } from '~/utils';
const knownEndpointAssets = { const knownEndpointAssets = {
[KnownEndpoints.anyscale]: '/assets/anyscale.png', [KnownEndpoints.anyscale]: 'assets/anyscale.png',
[KnownEndpoints.apipie]: '/assets/apipie.png', [KnownEndpoints.apipie]: 'assets/apipie.png',
[KnownEndpoints.cohere]: '/assets/cohere.png', [KnownEndpoints.cohere]: 'assets/cohere.png',
[KnownEndpoints.deepseek]: '/assets/deepseek.svg', [KnownEndpoints.deepseek]: 'assets/deepseek.svg',
[KnownEndpoints.fireworks]: '/assets/fireworks.png', [KnownEndpoints.fireworks]: 'assets/fireworks.png',
[KnownEndpoints.google]: '/assets/google.svg', [KnownEndpoints.google]: 'assets/google.svg',
[KnownEndpoints.groq]: '/assets/groq.png', [KnownEndpoints.groq]: 'assets/groq.png',
[KnownEndpoints.huggingface]: '/assets/huggingface.svg', [KnownEndpoints.huggingface]: 'assets/huggingface.svg',
[KnownEndpoints.mistral]: '/assets/mistral.png', [KnownEndpoints.mistral]: 'assets/mistral.png',
[KnownEndpoints.mlx]: '/assets/mlx.png', [KnownEndpoints.mlx]: 'assets/mlx.png',
[KnownEndpoints.ollama]: '/assets/ollama.png', [KnownEndpoints.ollama]: 'assets/ollama.png',
[KnownEndpoints.openai]: '/assets/openai.svg', [KnownEndpoints.openai]: 'assets/openai.svg',
[KnownEndpoints.openrouter]: '/assets/openrouter.png', [KnownEndpoints.openrouter]: 'assets/openrouter.png',
[KnownEndpoints.perplexity]: '/assets/perplexity.png', [KnownEndpoints.perplexity]: 'assets/perplexity.png',
[KnownEndpoints.qwen]: '/assets/qwen.svg', [KnownEndpoints.qwen]: 'assets/qwen.svg',
[KnownEndpoints.shuttleai]: '/assets/shuttleai.png', [KnownEndpoints.shuttleai]: 'assets/shuttleai.png',
[KnownEndpoints['together.ai']]: '/assets/together.png', [KnownEndpoints['together.ai']]: 'assets/together.png',
[KnownEndpoints.unify]: '/assets/unify.webp', [KnownEndpoints.unify]: 'assets/unify.webp',
}; };
const knownEndpointClasses = { const knownEndpointClasses = {

View file

@ -11,7 +11,7 @@ export default function useAttachmentHandler(queryClient?: QueryClient) {
return ({ data }: { data: TAttachment; submission: EventSubmission }) => { return ({ data }: { data: TAttachment; submission: EventSubmission }) => {
const { messageId } = data; const { messageId } = data;
if (queryClient && data?.filepath && !data.filepath.startsWith('/api/files')) { if (queryClient && data?.filepath && !data.filepath.includes('/api/files')) {
queryClient.setQueryData([QueryKeys.files], (oldData: TAttachment[] | undefined) => { queryClient.setQueryData([QueryKeys.files], (oldData: TAttachment[] | undefined) => {
return [data, ...(oldData || [])]; return [data, ...(oldData || [])];
}); });

View file

@ -27,95 +27,101 @@ const AuthLayout = () => (
</AuthContextProvider> </AuthContextProvider>
); );
export const router = createBrowserRouter([ const baseEl = document.querySelector('base');
{ const baseHref = baseEl?.getAttribute('href') || '/';
path: 'share/:shareId',
element: <ShareRoute />, export const router = createBrowserRouter(
errorElement: <RouteErrorBoundary />, [
}, {
{ path: 'share/:shareId',
path: 'oauth', element: <ShareRoute />,
errorElement: <RouteErrorBoundary />, errorElement: <RouteErrorBoundary />,
children: [ },
{ {
path: 'success', path: 'oauth',
element: <OAuthSuccess />, errorElement: <RouteErrorBoundary />,
}, children: [
{ {
path: 'error', path: 'success',
element: <OAuthError />, element: <OAuthSuccess />,
}, },
], {
}, path: 'error',
{ element: <OAuthError />,
path: '/', },
element: <StartupLayout />, ],
errorElement: <RouteErrorBoundary />, },
children: [ {
{ path: '/',
path: 'register', element: <StartupLayout />,
element: <Registration />, errorElement: <RouteErrorBoundary />,
}, children: [
{ {
path: 'forgot-password', path: 'register',
element: <RequestPasswordReset />, element: <Registration />,
}, },
{ {
path: 'reset-password', path: 'forgot-password',
element: <ResetPassword />, element: <RequestPasswordReset />,
}, },
], {
}, path: 'reset-password',
{ element: <ResetPassword />,
path: 'verify', },
element: <VerifyEmail />, ],
errorElement: <RouteErrorBoundary />, },
}, {
{ path: 'verify',
element: <AuthLayout />, element: <VerifyEmail />,
errorElement: <RouteErrorBoundary />, errorElement: <RouteErrorBoundary />,
children: [ },
{ {
path: '/', element: <AuthLayout />,
element: <LoginLayout />, errorElement: <RouteErrorBoundary />,
children: [ children: [
{ {
path: 'login', path: '/',
element: <Login />, element: <LoginLayout />,
}, children: [
{ {
path: 'login/2fa', path: 'login',
element: <TwoFactorScreen />, element: <Login />,
}, },
], {
}, path: 'login/2fa',
dashboardRoutes, element: <TwoFactorScreen />,
{ },
path: '/', ],
element: <Root />, },
children: [ dashboardRoutes,
{ {
index: true, path: '/',
element: <Navigate to="/c/new" replace={true} />, element: <Root />,
}, children: [
{ {
path: 'c/:conversationId?', index: true,
element: <ChatRoute />, element: <Navigate to="/c/new" replace={true} />,
}, },
{ {
path: 'search', path: 'c/:conversationId?',
element: <Search />, element: <ChatRoute />,
}, },
{ {
path: 'agents', path: 'search',
element: <AgentMarketplace />, element: <Search />,
}, },
{ {
path: 'agents/:category', path: 'agents',
element: <AgentMarketplace />, element: <AgentMarketplace />,
}, },
], {
}, path: 'agents/:category',
], element: <AgentMarketplace />,
}, },
]); ],
},
],
},
],
{ basename: baseHref },
);

View file

@ -8,6 +8,7 @@ import { VitePWA } from 'vite-plugin-pwa';
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig(({ command }) => ({ export default defineConfig(({ command }) => ({
base: '',
server: { server: {
host: 'localhost', host: 'localhost',
port: 3090, port: 3090,
@ -54,33 +55,32 @@ export default defineConfig(({ command }) => ({
manifest: { manifest: {
name: 'LibreChat', name: 'LibreChat',
short_name: 'LibreChat', short_name: 'LibreChat',
start_url: '/',
display: 'standalone', display: 'standalone',
background_color: '#000000', background_color: '#000000',
theme_color: '#009688', theme_color: '#009688',
icons: [ icons: [
{ {
src: '/assets/favicon-32x32.png', src: 'assets/favicon-32x32.png',
sizes: '32x32', sizes: '32x32',
type: 'image/png', type: 'image/png',
}, },
{ {
src: '/assets/favicon-16x16.png', src: 'assets/favicon-16x16.png',
sizes: '16x16', sizes: '16x16',
type: 'image/png', type: 'image/png',
}, },
{ {
src: '/assets/apple-touch-icon-180x180.png', src: 'assets/apple-touch-icon-180x180.png',
sizes: '180x180', sizes: '180x180',
type: 'image/png', type: 'image/png',
}, },
{ {
src: '/assets/icon-192x192.png', src: 'assets/icon-192x192.png',
sizes: '192x192', sizes: '192x192',
type: 'image/png', type: 'image/png',
}, },
{ {
src: '/assets/maskable-icon.png', src: 'assets/maskable-icon.png',
sizes: '512x512', sizes: '512x512',
type: 'image/png', type: 'image/png',
purpose: 'maskable', purpose: 'maskable',

View file

@ -2,6 +2,21 @@ import type { AssistantsEndpoint } from './schemas';
import * as q from './types/queries'; import * as q from './types/queries';
import { ResourceType } from './accessPermissions'; import { ResourceType } from './accessPermissions';
let BASE_URL = '';
if (typeof process === 'undefined' || process.browser === true) {
// process is only available in node context, or process.browser is true in client-side code
// This is to ensure that the BASE_URL is set correctly based on the <base>
// element in the HTML document, if it exists.
const baseEl = document.querySelector('base');
BASE_URL = baseEl?.getAttribute('href') || '/';
}
if (BASE_URL && BASE_URL.endsWith('/')) {
BASE_URL = BASE_URL.slice(0, -1);
}
export const apiBaseUrl = () => BASE_URL;
// Testing this buildQuery function // Testing this buildQuery function
const buildQuery = (params: Record<string, unknown>): string => { const buildQuery = (params: Record<string, unknown>): string => {
const query = Object.entries(params) const query = Object.entries(params)
@ -21,30 +36,34 @@ const buildQuery = (params: Record<string, unknown>): string => {
return query ? `?${query}` : ''; return query ? `?${query}` : '';
}; };
export const health = () => '/health'; export const health = () => `${BASE_URL}/health`;
export const user = () => '/api/user'; export const user = () => `${BASE_URL}/api/user`;
export const balance = () => '/api/balance'; export const balance = () => `${BASE_URL}/api/balance`;
export const userPlugins = () => '/api/user/plugins'; export const userPlugins = () => `${BASE_URL}/api/user/plugins`;
export const deleteUser = () => '/api/user/delete'; export const deleteUser = () => `${BASE_URL}/api/user/delete`;
const messagesRoot = `${BASE_URL}/api/messages`;
export const messages = (params: q.MessagesListParams) => { export const messages = (params: q.MessagesListParams) => {
const { conversationId, messageId, ...rest } = params; const { conversationId, messageId, ...rest } = params;
if (conversationId && messageId) { if (conversationId && messageId) {
return `/api/messages/${conversationId}/${messageId}`; return `${messagesRoot}/${conversationId}/${messageId}`;
} }
if (conversationId) { if (conversationId) {
return `/api/messages/${conversationId}`; return `${messagesRoot}/${conversationId}`;
} }
return `/api/messages${buildQuery(rest)}`; return `${messagesRoot}{buildQuery(rest)}`;
}; };
const shareRoot = '/api/share'; export const messagesArtifacts = (messageId: string) => `${messagesRoot}/artifacts/${messageId}`;
const shareRoot = `${BASE_URL}/api/share`;
export const shareMessages = (shareId: string) => `${shareRoot}/${shareId}`; export const shareMessages = (shareId: string) => `${shareRoot}/${shareId}`;
export const getSharedLink = (conversationId: string) => `${shareRoot}/link/${conversationId}`; export const getSharedLink = (conversationId: string) => `${shareRoot}/link/${conversationId}`;
export const getSharedLinks = ( export const getSharedLinks = (
@ -61,7 +80,7 @@ export const getSharedLinks = (
export const createSharedLink = (conversationId: string) => `${shareRoot}/${conversationId}`; export const createSharedLink = (conversationId: string) => `${shareRoot}/${conversationId}`;
export const updateSharedLink = (shareId: string) => `${shareRoot}/${shareId}`; export const updateSharedLink = (shareId: string) => `${shareRoot}/${shareId}`;
const keysEndpoint = '/api/keys'; const keysEndpoint = `${BASE_URL}/api/keys`;
export const keys = () => keysEndpoint; export const keys = () => keysEndpoint;
@ -71,7 +90,7 @@ export const revokeUserKey = (name: string) => `${keysEndpoint}/${name}`;
export const revokeAllUserKeys = () => `${keysEndpoint}?all=true`; export const revokeAllUserKeys = () => `${keysEndpoint}?all=true`;
export const conversationsRoot = '/api/convos'; export const conversationsRoot = `${BASE_URL}/api/convos`;
export const conversations = (params: q.ConversationListParams) => { export const conversations = (params: q.ConversationListParams) => {
return `${conversationsRoot}${buildQuery(params)}`; return `${conversationsRoot}${buildQuery(params)}`;
@ -94,60 +113,62 @@ export const forkConversation = () => `${conversationsRoot}/fork`;
export const duplicateConversation = () => `${conversationsRoot}/duplicate`; export const duplicateConversation = () => `${conversationsRoot}/duplicate`;
export const search = (q: string, cursor?: string | null) => export const search = (q: string, cursor?: string | null) =>
`/api/search?q=${q}${cursor ? `&cursor=${cursor}` : ''}`; `${BASE_URL}/api/search?q=${q}${cursor ? `&cursor=${cursor}` : ''}`;
export const searchEnabled = () => '/api/search/enable'; export const searchEnabled = () => `${BASE_URL}/api/search/enable`;
export const presets = () => '/api/presets'; export const presets = () => `${BASE_URL}/api/presets`;
export const deletePreset = () => '/api/presets/delete'; export const deletePreset = () => `${BASE_URL}/api/presets/delete`;
export const aiEndpoints = () => '/api/endpoints'; export const aiEndpoints = () => `${BASE_URL}/api/endpoints`;
export const models = () => '/api/models'; export const models = () => `${BASE_URL}/api/models`;
export const tokenizer = () => '/api/tokenizer'; export const tokenizer = () => `${BASE_URL}/api/tokenizer`;
export const login = () => '/api/auth/login'; export const login = () => `${BASE_URL}/api/auth/login`;
export const logout = () => '/api/auth/logout'; export const logout = () => `${BASE_URL}/api/auth/logout`;
export const register = () => '/api/auth/register'; export const register = () => `${BASE_URL}/api/auth/register`;
export const loginFacebook = () => '/api/auth/facebook'; export const loginFacebook = () => `${BASE_URL}/api/auth/facebook`;
export const loginGoogle = () => '/api/auth/google'; export const loginGoogle = () => `${BASE_URL}/api/auth/google`;
export const refreshToken = (retry?: boolean) => export const refreshToken = (retry?: boolean) =>
`/api/auth/refresh${retry === true ? '?retry=true' : ''}`; `${BASE_URL}/api/auth/refresh${retry === true ? '?retry=true' : ''}`;
export const requestPasswordReset = () => '/api/auth/requestPasswordReset'; export const requestPasswordReset = () => `${BASE_URL}/api/auth/requestPasswordReset`;
export const resetPassword = () => '/api/auth/resetPassword'; export const resetPassword = () => `${BASE_URL}/api/auth/resetPassword`;
export const verifyEmail = () => '/api/user/verify'; export const verifyEmail = () => `${BASE_URL}/api/user/verify`;
export const resendVerificationEmail = () => '/api/user/verify/resend'; export const resendVerificationEmail = () => `${BASE_URL}/api/user/verify/resend`;
export const plugins = () => '/api/plugins'; export const plugins = () => `${BASE_URL}/api/plugins`;
export const mcpReinitialize = (serverName: string) => `/api/mcp/${serverName}/reinitialize`; export const mcpReinitialize = (serverName: string) =>
export const mcpConnectionStatus = () => '/api/mcp/connection/status'; `${BASE_URL}/api/mcp/${serverName}/reinitialize`;
export const mcpConnectionStatus = () => `${BASE_URL}/api/mcp/connection/status`;
export const mcpServerConnectionStatus = (serverName: string) => export const mcpServerConnectionStatus = (serverName: string) =>
`/api/mcp/connection/status/${serverName}`; `${BASE_URL}/api/mcp/connection/status/${serverName}`;
export const mcpAuthValues = (serverName: string) => { export const mcpAuthValues = (serverName: string) => {
return `/api/mcp/${serverName}/auth-values`; return `${BASE_URL}/api/mcp/${serverName}/auth-values`;
}; };
export const cancelMCPOAuth = (serverName: string) => { export const cancelMCPOAuth = (serverName: string) => {
return `/api/mcp/oauth/cancel/${serverName}`; return `${BASE_URL}/api/mcp/oauth/cancel/${serverName}`;
}; };
export const config = () => '/api/config'; export const config = () => `${BASE_URL}/api/config`;
export const prompts = () => '/api/prompts'; export const prompts = () => `${BASE_URL}/api/prompts`;
export const addPromptToGroup = (groupId: string) => `/api/prompts/groups/${groupId}/prompts`; export const addPromptToGroup = (groupId: string) =>
`${BASE_URL}/api/prompts/groups/${groupId}/prompts`;
export const assistants = ({ export const assistants = ({
path = '', path = '',
@ -162,7 +183,7 @@ export const assistants = ({
version: number | string; version: number | string;
isAvatar?: boolean; isAvatar?: boolean;
}) => { }) => {
let url = isAvatar === true ? `${images()}/assistants` : `/api/assistants/v${version}`; let url = isAvatar === true ? `${images()}/assistants` : `${BASE_URL}/api/assistants/v${version}`;
if (path && path !== '') { if (path && path !== '') {
url += `/${path}`; url += `/${path}`;
@ -184,7 +205,7 @@ export const assistants = ({
}; };
export const agents = ({ path = '', options }: { path?: string; options?: object }) => { export const agents = ({ path = '', options }: { path?: string; options?: object }) => {
let url = '/api/agents'; let url = `${BASE_URL}/api/agents`;
if (path && path !== '') { if (path && path !== '') {
url += `/${path}`; url += `/${path}`;
@ -200,13 +221,13 @@ export const agents = ({ path = '', options }: { path?: string; options?: object
export const revertAgentVersion = (agent_id: string) => `${agents({ path: `${agent_id}/revert` })}`; export const revertAgentVersion = (agent_id: string) => `${agents({ path: `${agent_id}/revert` })}`;
export const files = () => '/api/files'; export const files = () => `${BASE_URL}/api/files`;
export const fileUpload = () => '/api/files'; export const fileUpload = () => `${BASE_URL}/api/files`;
export const fileDelete = () => '/api/files'; export const fileDelete = () => `${BASE_URL}/api/files`;
export const fileDownload = (userId: string, fileId: string) => export const fileDownload = (userId: string, fileId: string) =>
`/api/files/download/${userId}/${fileId}`; `${BASE_URL}/api/files/download/${userId}/${fileId}`;
export const fileConfig = () => '/api/files/config'; export const fileConfig = () => `${BASE_URL}/api/files/config`;
export const agentFiles = (agentId: string) => `/api/files/agent/${agentId}`; export const agentFiles = (agentId: string) => `${BASE_URL}/api/files/agent/${agentId}`;
export const images = () => `${files()}/images`; export const images = () => `${files()}/images`;
@ -263,12 +284,12 @@ export const deletePrompt = ({ _id, groupId }: { _id: string; groupId: string })
return `${prompts()}/${_id}?groupId=${groupId}`; return `${prompts()}/${_id}?groupId=${groupId}`;
}; };
export const getCategories = () => '/api/categories'; export const getCategories = () => `${BASE_URL}/api/categories`;
export const getAllPromptGroups = () => `${prompts()}/all`; export const getAllPromptGroups = () => `${prompts()}/all`;
/* Roles */ /* Roles */
export const roles = () => '/api/roles'; export const roles = () => `${BASE_URL}/api/roles`;
export const getRole = (roleName: string) => `${roles()}/${roleName.toLowerCase()}`; export const getRole = (roleName: string) => `${roles()}/${roleName.toLowerCase()}`;
export const updatePromptPermissions = (roleName: string) => `${getRole(roleName)}/prompts`; export const updatePromptPermissions = (roleName: string) => `${getRole(roleName)}/prompts`;
export const updateMemoryPermissions = (roleName: string) => `${getRole(roleName)}/memories`; export const updateMemoryPermissions = (roleName: string) => `${getRole(roleName)}/memories`;
@ -281,7 +302,7 @@ export const updateMarketplacePermissions = (roleName: string) =>
/* Conversation Tags */ /* Conversation Tags */
export const conversationTags = (tag?: string) => export const conversationTags = (tag?: string) =>
`/api/tags${tag != null && tag ? `/${encodeURIComponent(tag)}` : ''}`; `${BASE_URL}/api/tags${tag != null && tag ? `/${encodeURIComponent(tag)}` : ''}`;
export const conversationTagsList = (pageNumber: string, sort?: string, order?: string) => export const conversationTagsList = (pageNumber: string, sort?: string, order?: string) =>
`${conversationTags()}/list?pageNumber=${pageNumber}${sort ? `&sort=${sort}` : ''}${ `${conversationTags()}/list?pageNumber=${pageNumber}${sort ? `&sort=${sort}` : ''}${
@ -291,30 +312,30 @@ export const conversationTagsList = (pageNumber: string, sort?: string, order?:
export const addTagToConversation = (conversationId: string) => export const addTagToConversation = (conversationId: string) =>
`${conversationTags()}/convo/${conversationId}`; `${conversationTags()}/convo/${conversationId}`;
export const userTerms = () => '/api/user/terms'; export const userTerms = () => `${BASE_URL}/api/user/terms`;
export const acceptUserTerms = () => '/api/user/terms/accept'; export const acceptUserTerms = () => `${BASE_URL}/api/user/terms/accept`;
export const banner = () => '/api/banner'; export const banner = () => `${BASE_URL}/api/banner`;
// Message Feedback // Message Feedback
export const feedback = (conversationId: string, messageId: string) => export const feedback = (conversationId: string, messageId: string) =>
`/api/messages/${conversationId}/${messageId}/feedback`; `${BASE_URL}/api/messages/${conversationId}/${messageId}/feedback`;
// Two-Factor Endpoints // Two-Factor Endpoints
export const enableTwoFactor = () => '/api/auth/2fa/enable'; export const enableTwoFactor = () => `${BASE_URL}/api/auth/2fa/enable`;
export const verifyTwoFactor = () => '/api/auth/2fa/verify'; export const verifyTwoFactor = () => `${BASE_URL}/api/auth/2fa/verify`;
export const confirmTwoFactor = () => '/api/auth/2fa/confirm'; export const confirmTwoFactor = () => `${BASE_URL}/api/auth/2fa/confirm`;
export const disableTwoFactor = () => '/api/auth/2fa/disable'; export const disableTwoFactor = () => `${BASE_URL}/api/auth/2fa/disable`;
export const regenerateBackupCodes = () => '/api/auth/2fa/backup/regenerate'; export const regenerateBackupCodes = () => `${BASE_URL}/api/auth/2fa/backup/regenerate`;
export const verifyTwoFactorTemp = () => '/api/auth/2fa/verify-temp'; export const verifyTwoFactorTemp = () => `${BASE_URL}/api/auth/2fa/verify-temp`;
/* Memories */ /* Memories */
export const memories = () => '/api/memories'; export const memories = () => `${BASE_URL}/api/memories`;
export const memory = (key: string) => `${memories()}/${encodeURIComponent(key)}`; export const memory = (key: string) => `${memories()}/${encodeURIComponent(key)}`;
export const memoryPreferences = () => `${memories()}/preferences`; export const memoryPreferences = () => `${memories()}/preferences`;
export const searchPrincipals = (params: q.PrincipalSearchParams) => { export const searchPrincipals = (params: q.PrincipalSearchParams) => {
const { q: query, limit, types } = params; const { q: query, limit, types } = params;
let url = `/api/permissions/search-principals?q=${encodeURIComponent(query)}`; let url = `${BASE_URL}/api/permissions/search-principals?q=${encodeURIComponent(query)}`;
if (limit !== undefined) { if (limit !== undefined) {
url += `&limit=${limit}`; url += `&limit=${limit}`;
@ -328,17 +349,17 @@ export const searchPrincipals = (params: q.PrincipalSearchParams) => {
}; };
export const getAccessRoles = (resourceType: ResourceType) => export const getAccessRoles = (resourceType: ResourceType) =>
`/api/permissions/${resourceType}/roles`; `${BASE_URL}/api/permissions/${resourceType}/roles`;
export const getResourcePermissions = (resourceType: ResourceType, resourceId: string) => export const getResourcePermissions = (resourceType: ResourceType, resourceId: string) =>
`/api/permissions/${resourceType}/${resourceId}`; `${BASE_URL}/api/permissions/${resourceType}/${resourceId}`;
export const updateResourcePermissions = (resourceType: ResourceType, resourceId: string) => export const updateResourcePermissions = (resourceType: ResourceType, resourceId: string) =>
`/api/permissions/${resourceType}/${resourceId}`; `${BASE_URL}/api/permissions/${resourceType}/${resourceId}`;
export const getEffectivePermissions = (resourceType: ResourceType, resourceId: string) => export const getEffectivePermissions = (resourceType: ResourceType, resourceId: string) =>
`/api/permissions/${resourceType}/${resourceId}/effective`; `${BASE_URL}/api/permissions/${resourceType}/${resourceId}/effective`;
// SharePoint Graph API Token // SharePoint Graph API Token
export const graphToken = (scopes: string) => export const graphToken = (scopes: string) =>
`/api/auth/graph-token?scopes=${encodeURIComponent(scopes)}`; `${BASE_URL}/api/auth/graph-token?scopes=${encodeURIComponent(scopes)}`;

View file

@ -6,6 +6,7 @@ import { specsConfigSchema, TSpecsConfig } from './models';
import { fileConfigSchema } from './file-config'; import { fileConfigSchema } from './file-config';
import { FileSources } from './types/files'; import { FileSources } from './types/files';
import { MCPServersSchema } from './mcp'; import { MCPServersSchema } from './mcp';
import { apiBaseUrl } from './api-endpoints';
export const defaultSocialLogins = ['google', 'facebook', 'openid', 'github', 'discord', 'saml']; export const defaultSocialLogins = ['google', 'facebook', 'openid', 'github', 'discord', 'saml'];
@ -1043,9 +1044,9 @@ export const initialModelsConfig: TModelsConfig = {
}; };
export const EndpointURLs = { export const EndpointURLs = {
[EModelEndpoint.assistants]: '/api/assistants/v2/chat', [EModelEndpoint.assistants]: `${apiBaseUrl()}/api/assistants/v2/chat`,
[EModelEndpoint.azureAssistants]: '/api/assistants/v1/chat', [EModelEndpoint.azureAssistants]: `${apiBaseUrl()}/api/assistants/v1/chat`,
[EModelEndpoint.agents]: `/api/${EModelEndpoint.agents}/chat`, [EModelEndpoint.agents]: `${apiBaseUrl()}/api/${EModelEndpoint.agents}/chat`,
} as const; } as const;
export const modularEndpoints = new Set<EModelEndpoint | string>([ export const modularEndpoints = new Set<EModelEndpoint | string>([

View file

@ -684,7 +684,7 @@ export const editArtifact = async ({
messageId, messageId,
...params ...params
}: m.TEditArtifactRequest): Promise<m.TEditArtifactResponse> => { }: m.TEditArtifactRequest): Promise<m.TEditArtifactResponse> => {
return request.post(`/api/messages/artifact/${messageId}`, params); return request.post(endpoints.messagesArtifacts(messageId), params);
}; };
export function getMessagesByConvoId(conversationId: string): Promise<s.TMessage[]> { export function getMessagesByConvoId(conversationId: string): Promise<s.TMessage[]> {
@ -952,3 +952,7 @@ export function getEffectivePermissions(
export function getGraphApiToken(params: q.GraphTokenParams): Promise<q.GraphTokenResponse> { export function getGraphApiToken(params: q.GraphTokenParams): Promise<q.GraphTokenResponse> {
return request.get(endpoints.graphToken(params.scopes)); return request.get(endpoints.graphToken(params.scopes));
} }
export function getDomainServerBaseUrl(): string {
return `${endpoints.apiBaseUrl()}/api`;
}