mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-07-17 13:17:20 +08:00
feat: add /api/v1/language endpoint for Go/Python runtime detection (#16952)
Both backends serve GET /api/v1/language. Frontend calls it once and caches. By this way, front end can know the backend is go or python and thus can determine which part of logic to load. --------- Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -10,6 +10,11 @@ import {
|
||||
} from '@/interfaces/database/user-setting';
|
||||
import { ISetLangfuseConfigRequestBody } from '@/interfaces/request/system';
|
||||
import { DEFAULT_LANGUAGE_CODE, supportedLanguages } from '@/locales/config';
|
||||
import kbService from '@/services/knowledge-service';
|
||||
import {
|
||||
getBackendLanguage,
|
||||
subscribeBackendLanguage,
|
||||
} from '@/utils/backend-runtime';
|
||||
import userService, {
|
||||
addTenantUser,
|
||||
agreeTenant,
|
||||
@@ -19,6 +24,7 @@ import userService, {
|
||||
} from '@/services/user-service';
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useSyncExternalStore } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { useWarnEmptyModel } from './use-warn-empty-model';
|
||||
@@ -38,6 +44,7 @@ export const enum UserSettingApiAction {
|
||||
AgreeTenant = 'agreeTenant',
|
||||
SetLangfuseConfig = 'setLangfuseConfig',
|
||||
DeleteLangfuseConfig = 'deleteLangfuseConfig',
|
||||
ListPipelines = 'listPipelines',
|
||||
FetchLangfuseConfig = 'fetchLangfuseConfig',
|
||||
}
|
||||
|
||||
@@ -102,6 +109,27 @@ export const useSelectParserList = (): Array<{
|
||||
const { data: tenantInfo } = useFetchTenantInfo(true);
|
||||
const { t } = useTranslation();
|
||||
|
||||
// Detect backend runtime language (Go vs Python) so we can choose
|
||||
// the matching parser-list code path at runtime.
|
||||
// fetchBackendLanguage / getBackendLanguage handle their own caching
|
||||
// internally; no need for an extra useQuery layer.
|
||||
const backendLang = useSyncExternalStore(
|
||||
subscribeBackendLanguage,
|
||||
getBackendLanguage,
|
||||
getBackendLanguage,
|
||||
);
|
||||
|
||||
// Go backend: fetch pipeline catalog dynamically.
|
||||
const { data: pipelineListData } = useQuery({
|
||||
queryKey: [UserSettingApiAction.ListPipelines],
|
||||
queryFn: async () => {
|
||||
const { data } = await kbService.listPipelines();
|
||||
return data;
|
||||
},
|
||||
staleTime: Infinity,
|
||||
enabled: backendLang === 'go',
|
||||
});
|
||||
|
||||
const defaultParsers = useMemo(
|
||||
() => [
|
||||
{ value: 'naive', label: t('knowledgeConfiguration.parserLabel.naive') },
|
||||
@@ -135,6 +163,25 @@ export const useSelectParserList = (): Array<{
|
||||
);
|
||||
|
||||
const parserList = useMemo(() => {
|
||||
// Go backend: prefer the dynamic pipeline catalog from the API.
|
||||
if (backendLang === 'go') {
|
||||
const pipelineList: Array<{ parser_id: string; title: string; dsl: Record<string, any> }> =
|
||||
pipelineListData?.data ?? [];
|
||||
if (pipelineList.length > 0) {
|
||||
const labelFromAPI = (parserId: string, title: string) => {
|
||||
const key = `knowledgeConfiguration.parserLabel.${parserId}`;
|
||||
const translated = t(key);
|
||||
return translated !== key ? translated : title;
|
||||
};
|
||||
return pipelineList.map((item) => ({
|
||||
value: item.parser_id,
|
||||
label: labelFromAPI(item.parser_id, item.title),
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
// Python backend (or fallback): use tenant-level parser_ids or
|
||||
// the hardcoded default parsers.
|
||||
const parserArray: Array<string> = tenantInfo?.parser_ids?.split(',') ?? [];
|
||||
const filteredArray = parserArray.filter((x) => x.trim() !== '');
|
||||
|
||||
@@ -146,7 +193,7 @@ export const useSelectParserList = (): Array<{
|
||||
const arr = x.split(':');
|
||||
return { value: arr[0], label: arr[1] };
|
||||
});
|
||||
}, [tenantInfo, defaultParsers]);
|
||||
}, [tenantInfo, defaultParsers, backendLang, pipelineListData, t]);
|
||||
|
||||
return parserList;
|
||||
};
|
||||
|
||||
@@ -778,6 +778,7 @@ Example: A 1 KB message with 1024-dim embedding uses ~9 KB. The 5 MB default lim
|
||||
'Re-parse existing documents for the new column roles to take effect.',
|
||||
parserLabel: {
|
||||
naive: 'General',
|
||||
general: 'General',
|
||||
qa: 'Q&A',
|
||||
resume: 'Resume',
|
||||
manual: 'Manual',
|
||||
|
||||
@@ -19,6 +19,7 @@ const {
|
||||
documentThumbnails,
|
||||
documentIngest,
|
||||
listTagByKnowledgeIds,
|
||||
listPipelines,
|
||||
setMeta,
|
||||
getMeta,
|
||||
getMetaKeys,
|
||||
@@ -66,6 +67,10 @@ const methods = {
|
||||
url: retrievalTestShare,
|
||||
method: 'post',
|
||||
},
|
||||
listPipelines: {
|
||||
url: listPipelines,
|
||||
method: 'get',
|
||||
},
|
||||
pipelineRerun: {
|
||||
url: api.pipelineRerun,
|
||||
method: 'post',
|
||||
|
||||
@@ -181,6 +181,7 @@ export default {
|
||||
`${restAPIv1}/datasets/${datasetId}/ingestions/${logId}`,
|
||||
fetchPipelineDatasetLogs: (datasetId: string) =>
|
||||
`${restAPIv1}/datasets/${datasetId}/ingestions`,
|
||||
listPipelines: `${restAPIv1}/pipelines?type=builtin`,
|
||||
runIndex: (datasetId: string, indexType: string) =>
|
||||
`${restAPIv1}/datasets/${datasetId}/index?type=${indexType.toLowerCase()}`,
|
||||
traceIndex: (datasetId: string, indexType: string) =>
|
||||
|
||||
40
web/src/utils/backend-runtime.ts
Normal file
40
web/src/utils/backend-runtime.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Backend runtime language detection.
|
||||
*
|
||||
* Fetches /api/v1/language once at module load (app start) and caches the
|
||||
* result. Components subscribe via React's useSyncExternalStore; there is no
|
||||
* per-component fetch or useEffect.
|
||||
*
|
||||
* Pattern: module-level fetch + listener set, same subscription approach as
|
||||
* enterprise billingStatus.ts.
|
||||
*/
|
||||
|
||||
type Listener = () => void;
|
||||
const listeners = new Set<Listener>();
|
||||
|
||||
let backendLanguage: string | null = null;
|
||||
|
||||
// Kick off the fetch at module load — app start, not component mount.
|
||||
const promise: Promise<string> = fetch('/api/v1/language')
|
||||
.then((r) => r.json())
|
||||
.then((body: { data?: { language?: string } }) => {
|
||||
backendLanguage = body.data?.language === 'go' ? 'go' : 'python';
|
||||
listeners.forEach((fn) => fn());
|
||||
return backendLanguage;
|
||||
})
|
||||
.catch(() => {
|
||||
backendLanguage = 'python';
|
||||
listeners.forEach((fn) => fn());
|
||||
return 'python';
|
||||
});
|
||||
|
||||
export const fetchBackendLanguage = (): Promise<string> => promise;
|
||||
|
||||
export const getBackendLanguage = (): string | null => backendLanguage;
|
||||
|
||||
export const subscribeBackendLanguage = (listener: Listener): (() => void) => {
|
||||
listeners.add(listener);
|
||||
return () => {
|
||||
listeners.delete(listener);
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user