feat(iframe): add iframe bridge for ragflow integration

- Implement Penpal-based iframe communication bridge between host and child apps
- Add route handling for ragflow integration with '/route-ragflow' prefix
- Update navigation hooks to support embedded mode via iframe bridge
- Configure build and dependencies for new iframe-bridge package
- Adjust nginx config for proper SPA routing in subpath deployments
This commit is contained in:
2025-11-10 16:11:21 +08:00
parent 81fa34669a
commit a3ff72e575
17 changed files with 377 additions and 37137 deletions

View File

@@ -0,0 +1,3 @@
PORT=9222
RAGFLOW_BASE=/ragflow/
UMI_APP_API_BASE_URL=http://150.158.121.95

View File

@@ -11,6 +11,7 @@ export default defineConfig({
outputPath: 'dist',
alias: { '@parent': path.resolve(__dirname, '../') },
npmClient: 'pnpm',
mfsu: false,
base: RAGFLOW_BASE,
routes,
publicPath: RAGFLOW_BASE,

File diff suppressed because it is too large Load Diff

View File

@@ -21,6 +21,8 @@
]
},
"dependencies": {
"@teres/iframe-bridge": "workspace:*",
"penpal": "^6.2.1",
"@ant-design/icons": "^5.2.6",
"@ant-design/pro-components": "^2.6.46",
"@ant-design/pro-layout": "^7.17.16",

View File

@@ -2,6 +2,7 @@ import { AgentCategory, AgentQuery } from '@/constants/agent';
import { NavigateToDataflowResultProps } from '@/pages/dataflow-result/interface';
import { Routes } from '@/routes';
import { useCallback } from 'react';
import Bridge from '@teres/iframe-bridge';
import { useNavigate, useParams, useSearchParams } from 'umi';
export enum QueryStringMap {
@@ -14,106 +15,131 @@ export const useNavigatePage = () => {
const [searchParams] = useSearchParams();
const { id } = useParams();
// 统一由 @teres/iframe-bridge 提供嵌入判断与消息封装
// 统一的跳转封装:嵌入模式下向父页面发送消息,否则使用内部 navigate
const navigateOrPost = useCallback(
(to: string, options?: { hostAgents?: boolean }) => {
console.log('Bridge: ', Bridge);
console.log('Bridge.isEmbedded', Bridge.isEmbedded());
if (Bridge.isEmbedded()) {
if (options?.hostAgents) {
// 返回到宿主应用的 /agents
if (typeof Bridge.clientClose === 'function') {
Bridge.clientClose();
} else {
Bridge.clientNavigate(Bridge.toHostPath(Routes.Agents));
}
return;
}
Bridge.clientNavigate(Bridge.toHostPath(to));
return;
}
navigate(to);
},
[navigate],
);
const navigateToDatasetList = useCallback(() => {
navigate(Routes.Datasets);
}, [navigate]);
navigateOrPost(Routes.Datasets);
}, [navigateOrPost]);
const navigateToDataset = useCallback(
(id: string) => () => {
// navigate(`${Routes.DatasetBase}${Routes.DataSetOverview}/${id}`);
navigate(`${Routes.Dataset}/${id}`);
navigateOrPost(`${Routes.Dataset}/${id}`);
},
[navigate],
[navigateOrPost],
);
const navigateToDatasetOverview = useCallback(
(id: string) => () => {
navigate(`${Routes.DatasetBase}${Routes.DataSetOverview}/${id}`);
navigateOrPost(`${Routes.DatasetBase}${Routes.DataSetOverview}/${id}`);
},
[navigate],
[navigateOrPost],
);
const navigateToDataFile = useCallback(
(id: string) => () => {
navigate(`${Routes.DatasetBase}${Routes.DatasetBase}/${id}`);
navigateOrPost(`${Routes.DatasetBase}${Routes.DatasetBase}/${id}`);
},
[navigate],
[navigateOrPost],
);
const navigateToHome = useCallback(() => {
navigate(Routes.Root);
}, [navigate]);
navigateOrPost(Routes.Root);
}, [navigateOrPost]);
const navigateToProfile = useCallback(() => {
navigate(Routes.ProfileSetting);
}, [navigate]);
navigateOrPost(Routes.ProfileSetting);
}, [navigateOrPost]);
const navigateToOldProfile = useCallback(() => {
navigate(Routes.UserSetting);
}, [navigate]);
navigateOrPost(Routes.UserSetting);
}, [navigateOrPost]);
const navigateToChatList = useCallback(() => {
navigate(Routes.Chats);
}, [navigate]);
navigateOrPost(Routes.Chats);
}, [navigateOrPost]);
const navigateToChat = useCallback(
(id: string) => () => {
navigate(`${Routes.Chat}/${id}`);
navigateOrPost(`${Routes.Chat}/${id}`);
},
[navigate],
[navigateOrPost],
);
const navigateToAgents = useCallback(() => {
navigate(Routes.Agents);
}, [navigate]);
// 嵌入模式下返回宿主应用的 /agents独立模式跳转 ragflow 内部 /agents
navigateOrPost(Routes.Agents, { hostAgents: true });
}, [navigateOrPost]);
const navigateToAgentList = useCallback(() => {
navigate(Routes.AgentList);
}, [navigate]);
navigateOrPost(Routes.AgentList);
}, [navigateOrPost]);
const navigateToAgent = useCallback(
(id: string, category?: AgentCategory) => () => {
navigate(`${Routes.Agent}/${id}?${AgentQuery.Category}=${category}`);
navigateOrPost(`${Routes.Agent}/${id}?${AgentQuery.Category}=${category}`);
},
[navigate],
[navigateOrPost],
);
const navigateToDataflow = useCallback(
(id: string) => () => {
navigate(`${Routes.DataFlow}/${id}`);
navigateOrPost(`${Routes.DataFlow}/${id}`);
},
[navigate],
[navigateOrPost],
);
const navigateToAgentLogs = useCallback(
(id: string) => () => {
navigate(`${Routes.AgentLogPage}/${id}`);
navigateOrPost(`${Routes.AgentLogPage}/${id}`);
},
[navigate],
[navigateOrPost],
);
const navigateToAgentTemplates = useCallback(() => {
navigate(Routes.AgentTemplates);
}, [navigate]);
navigateOrPost(Routes.AgentTemplates);
}, [navigateOrPost]);
const navigateToSearchList = useCallback(() => {
navigate(Routes.Searches);
}, [navigate]);
navigateOrPost(Routes.Searches);
}, [navigateOrPost]);
const navigateToSearch = useCallback(
(id: string) => () => {
navigate(`${Routes.Search}/${id}`);
navigateOrPost(`${Routes.Search}/${id}`);
},
[navigate],
[navigateOrPost],
);
const navigateToChunkParsedResult = useCallback(
(id: string, knowledgeId?: string) => () => {
navigate(
navigateOrPost(
`${Routes.ParsedResult}/chunks?id=${knowledgeId}&doc_id=${id}`,
// `${Routes.DataflowResult}?id=${knowledgeId}&doc_id=${id}&type=chunk`,
);
},
[navigate],
[navigateOrPost],
);
const getQueryString = useCallback(
@@ -134,34 +160,36 @@ export const useNavigatePage = () => {
const navigateToChunk = useCallback(
(route: Routes) => {
navigate(
navigateOrPost(
`${route}/${id}?${QueryStringMap.KnowledgeId}=${getQueryString(QueryStringMap.KnowledgeId)}`,
);
},
[getQueryString, id, navigate],
[getQueryString, id, navigateOrPost],
);
const navigateToFiles = useCallback(
(folderId?: string) => {
navigate(`${Routes.Files}?folderId=${folderId}`);
navigateOrPost(`${Routes.Files}?folderId=${folderId}`);
},
[navigate],
[navigateOrPost],
);
const navigateToDataflowResult = useCallback(
(props: NavigateToDataflowResultProps) => () => {
let params: string[] = [];
Object.keys(props).forEach((key) => {
// @ts-ignore
if (props[key]) {
// @ts-ignore
params.push(`${key}=${props[key]}`);
}
});
navigate(
navigateOrPost(
// `${Routes.ParsedResult}/${id}?${QueryStringMap.KnowledgeId}=${knowledgeId}`,
`${Routes.DataflowResult}?${params.join('&')}`,
);
},
[navigate],
[navigateOrPost],
);
return {