From 3da2a8d088d4822a2f200354a1988f8a5cca2a85 Mon Sep 17 00:00:00 2001 From: wren <“porlong@qq.com”> Date: Fri, 8 May 2026 10:59:04 +0800 Subject: [PATCH] fix: stabilize review detail and collabora loading --- app/api/dify-chat/chat.ts | 206 ++++++++----------- app/api/dify-chat/client.server.ts | 79 +++---- app/api/login/auth.server.ts | 33 ++- app/components/collabora/CollaboraViewer.tsx | 3 +- app/components/collabora/hooks.ts | 62 +++++- app/components/dify-chat/index.tsx | 2 +- app/components/dify-chat/sidebar.tsx | 2 +- app/config/api-config.ts | 2 +- app/routes/api.parameters.tsx | 100 ++++----- app/routes/api.v3.dify.chat-apps.default.tsx | 34 +-- app/routes/api.v3.dify.chat-apps.my.tsx | 42 ++-- app/routes/document-types._index.tsx | 3 +- app/routes/document-types.new.tsx | 3 +- app/routes/reviewsTest.tsx | 5 +- app/routes/rule-groups._index.tsx | 3 +- app/routes/rules.list.tsx | 6 +- app/routes/rules.tsx | 3 +- app/routes/rulesTest.detail.tsx | 3 +- app/services/collabora.config.server.ts | 5 +- public/min-maps/vs/loader.js.map | 7 + 20 files changed, 319 insertions(+), 284 deletions(-) create mode 100644 public/min-maps/vs/loader.js.map diff --git a/app/api/dify-chat/chat.ts b/app/api/dify-chat/chat.ts index 09508f4..9fccf83 100644 --- a/app/api/dify-chat/chat.ts +++ b/app/api/dify-chat/chat.ts @@ -1,82 +1,97 @@ /** - * Dify Chat API 模块 + * 自有 RAG Chat API 模块 * - * 提供客户端调用 Dify API 的函数 - * 用于 Remix loader/action 中调用 Dify API + * 保持前端 dify-chat 调用面不变,内部转发到新的 /api/v3/rag/* 接口。 * - * @module api/dify/chat + * @module api/dify-chat/chat */ import { difyFetch } from './client.server'; -// ============================================================================ -// Dify Chat API 客户端 -// ============================================================================ +function unwrapResult(payload: any): T { + if (payload && typeof payload === 'object' && 'data' in payload) { + return payload.data as T; + } + return payload as T; +} + +function toIntAppId(appId?: string): string | undefined { + if (!appId) return undefined; + const parsed = Number(appId); + return Number.isFinite(parsed) ? String(parsed) : undefined; +} + +function normalizeConversationName(name: string): string { + const compact = (name || '').replace(/\s+/g, ' ').trim(); + if (!compact) return '新对话'; + return compact.length > 20 ? `${compact.slice(0, 20)}...` : compact; +} -/** - * Dify Chat API 客户端 - * - * @param jwt - JWT 认证令牌 - * user 参数由后端自动从 JWT 中提取 - */ export const difyClient = { - /** - * 获取应用参数 - */ - async getApplicationParameters(jwt?: string): Promise { - const response = await difyFetch('parameters', { + async getApplicationParameters(jwt?: string, appId?: string): Promise { + const response = await difyFetch('chat/parameters', { method: 'GET', + appId: toIntAppId(appId), }, jwt); - return response.json(); + const payload = unwrapResult(await response.json()); + return { + opening_statement: payload?.openingStatement || '', + suggested_questions: payload?.suggestedQuestions || [], + user_input_form: payload?.userInputForm || [], + file_upload: payload?.fileUpload || { enabled: false }, + }; }, - /** - * 获取会话列表 - * - * @param jwt - JWT 认证令牌 - * @param appId - 对话应用 ID(可选,用于获取特定应用的会话列表) - */ async getConversations(jwt?: string, appId?: string): Promise { const params = new URLSearchParams({ - limit: '100', - first_id: '', + page: '1', + pageSize: '100', }); + const normalizedAppId = toIntAppId(appId); + if (normalizedAppId) { + params.set('appId', normalizedAppId); + } - const response = await difyFetch(`conversations?${params}`, { + const response = await difyFetch(`chat/conversations?${params.toString()}`, { method: 'GET', - appId, // 传递应用 ID,会在请求头中添加 X-Dify-App-Id }, jwt); - return response.json(); + const payload = unwrapResult(await response.json()); + return { + data: (payload?.data || []).map((item: any) => ({ + id: item.id, + name: item.name, + introduction: item.introduction || '', + created_at: item.createdAt || 0, + updated_at: item.updatedAt || 0, + })), + has_more: Boolean(payload?.hasMore), + limit: payload?.limit || 100, + }; }, - /** - * 获取会话消息 - */ async getConversationMessages(conversationId: string, jwt?: string): Promise { const params = new URLSearchParams({ - conversation_id: conversationId, - limit: '20', - last_id: '', + page: '1', + pageSize: '100', }); - - const response = await difyFetch(`messages?${params}`, { + const response = await difyFetch(`chat/conversations/${conversationId}/messages?${params.toString()}`, { method: 'GET', }, jwt); - return response.json(); + const payload = unwrapResult(await response.json()); + return { + data: (payload?.data || []).map((item: any) => ({ + id: item.id, + query: item.query, + answer: item.answer, + feedback: item.feedback || undefined, + retriever_resources: item.retrieverResources || [], + created_at: item.createdAt || 0, + })), + has_more: Boolean(payload?.hasMore), + limit: payload?.limit || 100, + }; }, - /** - * 发送聊天消息 - * - * @param inputs - 输入参数 - * @param query - 用户问题 - * @param responseMode - 响应模式 ('streaming' | 'blocking') - * @param conversationId - 会话 ID - * @param files - 附件文件 - * @param jwt - JWT 认证令牌 - * @param appId - 对话应用 ID(可选,用于切换不同的 Dify 应用) - * @returns 对于流式响应返回 Response 对象,否则返回 JSON - */ async createChatMessage( inputs: Record, query: string, @@ -92,99 +107,58 @@ export const difyClient = { response_mode: responseMode, conversation_id: conversationId, files: files || [], + appId: toIntAppId(appId) ? Number(appId) : null, + conversationId: conversationId || null, }; - const response = await difyFetch('chat-messages', { + const response = await difyFetch('chat/messages', { method: 'POST', body: JSON.stringify(body), - appId, // 传递应用 ID,会在请求头中添加 X-Dify-App-Id }, jwt); - // 对于流式响应,直接返回 Response 对象 if (responseMode === 'streaming') { return response; } - console.log('[Dify Chat] 解析 JSON 响应'); return response.json(); }, - /** - * 重命名会话 - */ async renameConversation( conversationId: string, name: string, autoGenerate: boolean = false, jwt?: string ): Promise { - const body = { - name, - auto_generate: autoGenerate, - }; + let nextName = name?.trim() || ''; - const response = await difyFetch(`conversations/${conversationId}/name`, { - method: 'POST', - body: JSON.stringify(body), - }, jwt); - return response.json(); - }, - - /** - * 删除会话 - */ - async deleteConversation(conversationId: string, jwt?: string): Promise { - console.log('[Dify Chat] 删除会话:', conversationId); - - try { - const response = await difyFetch(`conversations/${conversationId}`, { - method: 'DELETE', - body: JSON.stringify({}), - }, jwt); - - // 对于 204 No Content 响应,直接返回成功 - if (response.status === 204) { - console.log('[Dify Chat] 删除会话成功:', conversationId); - return { result: 'success' }; - } - - const contentType = response.headers.get('Content-Type'); - - if (contentType && contentType.includes('application/json')) { - const data = await response.json(); - return data; - } - - const text = await response.text(); - console.log('[Dify Chat] 删除会话文本响应:', text); - - return { result: 'success' }; - } catch (error: any) { - // 权限不足等明确错误需要抛出,不能吞掉 - if (error.message?.includes('403') || error.message?.includes('401')) { - throw error; - } - // 网络超时等不确定错误才降级为成功(Dify 可能已执行删除) - console.warn('[Dify Chat] 删除会话请求失败,但可能已成功删除:', error.message); - return { result: 'success' }; + if (autoGenerate || !nextName) { + const messages = await this.getConversationMessages(conversationId, jwt); + const firstQuestion = messages?.data?.find((item: any) => item?.query)?.query || ''; + nextName = normalizeConversationName(firstQuestion); } + + const response = await difyFetch(`chat/conversations/${conversationId}`, { + method: 'PATCH', + body: JSON.stringify({ name: nextName }), + }, jwt); + return unwrapResult(await response.json()); + }, + + async deleteConversation(conversationId: string, jwt?: string): Promise { + const response = await difyFetch(`chat/conversations/${conversationId}`, { + method: 'DELETE', + }, jwt); + return unwrapResult(await response.json()); }, - /** - * 更新消息反馈 - */ async updateMessageFeedback( messageId: string, rating: 'like' | 'dislike' | null, jwt?: string ): Promise { - const body = { - rating, - }; - - const response = await difyFetch(`messages/${messageId}/feedbacks`, { + const response = await difyFetch(`chat/messages/${messageId}/feedback`, { method: 'POST', - body: JSON.stringify(body), + body: JSON.stringify({ rating }), }, jwt); - return response.json(); + return unwrapResult(await response.json()); }, }; diff --git a/app/api/dify-chat/client.server.ts b/app/api/dify-chat/client.server.ts index a993afa..97c0e2d 100644 --- a/app/api/dify-chat/client.server.ts +++ b/app/api/dify-chat/client.server.ts @@ -1,98 +1,71 @@ /** - * Dify Chat 服务端 API 模块 + * RAG Chat 服务端 API 模块 * - * 提供 Node.js 服务端调用 FastAPI 后端的基础功能 - * Dify 的 API_KEY 和 APP_ID 由 FastAPI 后端管理,前端只负责转发请求 - * - * 调用链路: - * Remix Server → FastAPI /dify_chat/* → Dify + * 提供 Node.js 服务端调用 FastAPI 后端的基础功能。 + * 现已改为走自有 RAG 接口: + * Remix Server -> FastAPI /api/v3/rag/* * * @module api/dify-chat/client.server */ import { API_BASE_URL } from '~/config/api-config'; -// ============================================================================ -// 配置 -// ============================================================================ +const RAG_API_ROOT = `${API_BASE_URL}/v3/rag`; -/** - * Dify Chat API 代理地址 - * 通过 FastAPI 后端的 /dify_chat 路由代理访问 Dify - * Dify 的认证(API_KEY)由 FastAPI 后端处理 - */ -const DIFY_CHAT_API_URL = `${API_BASE_URL}/dify_chat`; - -// ============================================================================ -// 基础请求函数 -// ============================================================================ - -/** - * Dify Fetch 请求选项 - */ export interface DifyFetchOptions extends RequestInit { - /** 对话应用 ID,用于切换不同的 Dify 应用 */ appId?: string; } -/** - * Dify Chat API 基础请求函数 - * - * 使用用户 JWT 认证通过 FastAPI 代理访问 Dify - * FastAPI 后端会验证 JWT 并添加 Dify API_KEY - * - * @param endpoint - API 端点路径 - * @param options - fetch 请求选项(可包含 appId) - * @param jwt - 用户 JWT 认证令牌 - * @returns Response 对象 - */ -export async function difyFetch( - endpoint: string, - options: DifyFetchOptions = {}, - jwt?: string -): Promise { - const { appId, ...fetchOptions } = options; - const url = `${DIFY_CHAT_API_URL}/${endpoint.replace(/^\//, '')}`; - +function buildHeaders(fetchOptions: RequestInit, jwt?: string): HeadersInit { const headers: HeadersInit = { 'Content-Type': 'application/json', ...fetchOptions.headers, }; if (jwt) { - (headers as Record)['Authorization'] = `Bearer ${jwt}`; + (headers as Record).Authorization = `Bearer ${jwt}`; } else { - console.warn('[Dify Chat] 没有提供 JWT,FastAPI 请求可能失败'); + console.warn('[RAG Chat] 没有提供 JWT,FastAPI 请求可能失败'); } - // 如果指定了应用 ID,添加 X-Dify-App-Id 请求头 + return headers; +} + +export async function difyFetch( + endpoint: string, + options: DifyFetchOptions = {}, + jwt?: string +): Promise { + const { appId, ...fetchOptions } = options; + const cleanEndpoint = endpoint.replace(/^\//, ''); + let url = `${RAG_API_ROOT}/${cleanEndpoint}`; + if (appId) { - (headers as Record)['X-Dify-App-Id'] = appId; - console.log('[Dify Chat] 使用应用 ID:', appId); + const separator = url.includes('?') ? '&' : '?'; + url = `${url}${separator}appId=${encodeURIComponent(appId)}`; } const response = await fetch(url, { ...fetchOptions, - headers, + headers: buildHeaders(fetchOptions, jwt), }); if (!response.ok) { const errorText = await response.text(); - console.error('[Dify Chat] API 转发错误:', { + console.error('[RAG Chat] API 转发错误:', { status: response.status, statusText: response.statusText, - error: errorText + error: errorText, }); if (response.status === 401) { throw new Error('JWT认证失败,请重新登录'); } - throw new Error(`Dify API Error: ${response.status} ${response.statusText}`); + throw new Error(`RAG API Error: ${response.status} ${response.statusText}`); } return response; } -// 重新导出 chat 模块的 difyClient export { difyClient } from './chat'; diff --git a/app/api/login/auth.server.ts b/app/api/login/auth.server.ts index b086e41..0fff9fa 100644 --- a/app/api/login/auth.server.ts +++ b/app/api/login/auth.server.ts @@ -77,6 +77,26 @@ export interface SsoUser { deleted_at?: string; } +function compactUserInfoForSession(userInfo?: UserInfo, userRole?: string): UserInfo | undefined { + if (!userInfo) { + return undefined; + } + + // Cookie Session 直接存整份 userInfo 很容易超过浏览器 4KB 限制; + // 服务端鉴权实际只依赖这几个核心字段,其余信息交给接口按需取回。 + return { + user_id: userInfo.user_id, + sub: userInfo.sub, + username: userInfo.username, + nick_name: userInfo.nick_name || userInfo.nickname || userInfo.name, + ou_id: userInfo.ou_id, + ou_name: userInfo.ou_name, + is_leader: userInfo.is_leader, + area: userInfo.area, + user_role: userInfo.user_role || userRole, + }; +} + /** * 会话存储配置 * @@ -201,7 +221,14 @@ export async function getUserSession(request: Request) { const refreshToken = session.get("refreshToken"); const tokenIssuedAt = session.get("tokenIssuedAt"); let tokenExpiresIn = session.get("tokenExpiresIn"); - const userInfo = session.get("userInfo"); + const storedUserInfo = session.get("userInfo"); + const userInfo = storedUserInfo + ? { + ...storedUserInfo, + role: storedUserInfo.role || storedUserInfo.user_role || userRole, + user_role: storedUserInfo.user_role || userRole, + } + : storedUserInfo; const frontendJWT = session.get("frontendJWT"); // 🔑 检查是否是公共路径(不需要认证的路径) @@ -369,7 +396,7 @@ export async function createUserSession(params: { // 用户信息和JWT if (params.userInfo) { - session.set("userInfo", params.userInfo); + session.set("userInfo", compactUserInfoForSession(params.userInfo, params.userRole)); } if (params.frontendJWT) { session.set("frontendJWT", params.frontendJWT); @@ -534,4 +561,4 @@ async function callIDaaSLogout(accessToken: string, appId: string): Promise { @@ -1194,4 +1194,3 @@ export const CollaboraViewer = forwardRef) { +export function useDocumentReady( + iframeRef: RefObject, + iframeUrl?: string +) { const [isDocumentLoaded, setIsDocumentLoaded] = useState(false); useEffect(() => { - const handleMessage = (event: MessageEvent) => { - // 验证消息来源 - const collaboraOrigin = new URL(COLLABORA_URL).origin; + setIsDocumentLoaded(false); - if (event.origin !== collaboraOrigin) { + const iframe = iframeRef.current; + const expectedOrigin = iframeUrl + ? new URL(iframeUrl).origin + : new URL(COLLABORA_URL).origin; + let fallbackTimer: ReturnType | null = null; + + const markLoaded = (source: string) => { + setIsDocumentLoaded((prev) => { + if (!prev) { + console.log(`[DocumentReady] 文档已就绪(${source})`); + } + return true; + }); + }; + + const handleIframeLoad = () => { + // 某些环境下 Collabora 不一定会抛出 Document_Loaded 消息, + // 先在 iframe load 后给一个兜底超时,避免页面一直被遮罩层盖住。 + fallbackTimer = setTimeout(() => { + markLoaded('iframe-load-fallback'); + }, 1500); + }; + + const handleMessage = (event: MessageEvent) => { + if (event.origin !== expectedOrigin) { + return; + } + + if (iframeRef.current?.contentWindow && event.source !== iframeRef.current.contentWindow) { return; } try { const msg = typeof event.data === 'string' ? JSON.parse(event.data) : event.data; - if (msg.MessageId === 'App_LoadingStatus' && msg.Values?.Status === 'Document_Loaded') { - console.log('[DocumentReady] 文档加载完成'); - setIsDocumentLoaded(true); + if ( + msg?.MessageId === 'App_LoadingStatus' && + ['Document_Loaded', 'Document_Loaded_Editing', 'UI_Loaded'].includes(msg.Values?.Status) + ) { + if (fallbackTimer) { + clearTimeout(fallbackTimer); + fallbackTimer = null; + } + markLoaded(`postmessage:${msg.Values?.Status}`); } - } catch (err) { - console.warn('[DocumentReady] 解析消息失败:', err); + } catch { + // Collabora 也会发送非 JSON 消息,这里忽略即可。 } }; + iframe?.addEventListener('load', handleIframeLoad); window.addEventListener('message', handleMessage); return () => { + if (fallbackTimer) { + clearTimeout(fallbackTimer); + } + iframe?.removeEventListener('load', handleIframeLoad); window.removeEventListener('message', handleMessage); }; - }, [iframeRef]); + }, [iframeRef, iframeUrl]); return { isDocumentLoaded }; } diff --git a/app/components/dify-chat/index.tsx b/app/components/dify-chat/index.tsx index 10d0656..56f75a2 100644 --- a/app/components/dify-chat/index.tsx +++ b/app/components/dify-chat/index.tsx @@ -49,7 +49,7 @@ export default function Chat() { // 权限检查 const { hasPermission: checkPerm } = usePermission(); - const canChat = checkPerm('dify:chat:use'); + const canChat = checkPerm('rag:chat:use'); // 侧边栏状态 const [sidebarCollapsed, setSidebarCollapsed] = useState(false); diff --git a/app/components/dify-chat/sidebar.tsx b/app/components/dify-chat/sidebar.tsx index 192cbd9..3d108b2 100644 --- a/app/components/dify-chat/sidebar.tsx +++ b/app/components/dify-chat/sidebar.tsx @@ -61,7 +61,7 @@ const ChatSidebar = forwardRef(({ conversationReadOnly = false, }, ref) => { const { hasPermission } = usePermission(); - const canDeleteConversation = hasPermission('dify:conversation:delete'); + const canDeleteConversation = hasPermission('rag:conversation:delete'); const [searchValue, setSearchValue] = useState(''); const [renameModalVisible, setRenameModalVisible] = useState(false); const [deleteModalVisible, setDeleteModalVisible] = useState(false); diff --git a/app/config/api-config.ts b/app/config/api-config.ts index fc732b5..65854e9 100644 --- a/app/config/api-config.ts +++ b/app/config/api-config.ts @@ -180,7 +180,7 @@ const configs: Record = { // documentUrl: 'http://172.16.0.84:8073/docauditai/', // uploadUrl: 'http://172.16.0.84:8073/api/v2/documents', - // 公网访问 reviewsTest 时,iframe 不能再直连内网 Collabora,否则浏览器会拦截。 + // 从公网页面发起到 172.16.* 的 iframe 请求会触发浏览器私网访问拦截,联调环境必须走公网可达地址。 collaboraUrl: 'http://nas.7bm.co:9980', appUrl: 'http://nas.7bm.co:5173', diff --git a/app/routes/api.parameters.tsx b/app/routes/api.parameters.tsx index 588d1d0..61ca9f4 100644 --- a/app/routes/api.parameters.tsx +++ b/app/routes/api.parameters.tsx @@ -1,50 +1,52 @@ -import { json, type LoaderFunctionArgs } from '@remix-run/node'; -import { difyClient } from '~/api/dify-chat/client.server'; -import { commitSession, getSessionInfo } from '../utils/session.server'; - -export async function loader({ request }: LoaderFunctionArgs) { - try { - // 获取用户会话信息和 JWT - const { getUserSession } = await import("~/api/login/auth.server"); - const { frontendJWT } = await getUserSession(request); - const { session } = await getSessionInfo(request); - - // 检查 JWT 是否存在 - if (!frontendJWT) { - console.error('❌ [API] Parameters API - JWT不存在'); - return json( - { error: 'JWT认证失败,请重新登录' }, - { - status: 401, - headers: { - 'Set-Cookie': await commitSession(session), - }, - } - ); - } - - const data = await difyClient.getApplicationParameters(frontendJWT); - - return json(data, { - headers: { - 'Set-Cookie': await commitSession(session), - }, - }); - } catch (error: any) { - console.error('❌ [API] Parameters API - Error:', error); - - // 检查是否是JWT认证失败 - const sm = error.message?.match(/(\d{3})/); const os = sm ? parseInt(sm[1]) : 0; - const status = error.message?.includes('JWT认证失败') ? 401 : os >= 400 && os < 500 ? os : 500; - - return json( - { error: error.message || 'Failed to fetch parameters' }, - { - status, - headers: { - 'Set-Cookie': await commitSession((await getSessionInfo(request)).session), - }, - } - ); - } +import { json, type LoaderFunctionArgs } from '@remix-run/node'; +import { difyClient } from '~/api/dify-chat/client.server'; +import { commitSession, getSessionInfo } from '../utils/session.server'; + +export async function loader({ request }: LoaderFunctionArgs) { + try { + // 获取用户会话信息和 JWT + const { getUserSession } = await import("~/api/login/auth.server"); + const { frontendJWT } = await getUserSession(request); + const { session } = await getSessionInfo(request); + + // 检查 JWT 是否存在 + if (!frontendJWT) { + console.error('❌ [API] Parameters API - JWT不存在'); + return json( + { error: 'JWT认证失败,请重新登录' }, + { + status: 401, + headers: { + 'Set-Cookie': await commitSession(session), + }, + } + ); + } + + const url = new URL(request.url); + const appId = url.searchParams.get('app_id') || undefined; + const data = await difyClient.getApplicationParameters(frontendJWT, appId); + + return json(data, { + headers: { + 'Set-Cookie': await commitSession(session), + }, + }); + } catch (error: any) { + console.error('❌ [API] Parameters API - Error:', error); + + // 检查是否是JWT认证失败 + const sm = error.message?.match(/(\d{3})/); const os = sm ? parseInt(sm[1]) : 0; + const status = error.message?.includes('JWT认证失败') ? 401 : os >= 400 && os < 500 ? os : 500; + + return json( + { error: error.message || 'Failed to fetch parameters' }, + { + status, + headers: { + 'Set-Cookie': await commitSession((await getSessionInfo(request)).session), + }, + } + ); + } } \ No newline at end of file diff --git a/app/routes/api.v3.dify.chat-apps.default.tsx b/app/routes/api.v3.dify.chat-apps.default.tsx index 4e55db0..bd859b9 100644 --- a/app/routes/api.v3.dify.chat-apps.default.tsx +++ b/app/routes/api.v3.dify.chat-apps.default.tsx @@ -1,14 +1,21 @@ -/** - * GET /api/v3/dify/chat-apps/default - 获取默认对话应用 - * - * 转发请求到后端 API,后端从配置文件读取默认对话应用 - * 参考文档:docs/new-dify/dify_api_doc.md - 对话应用多实例支持 - */ - import { LoaderFunctionArgs, json } from '@remix-run/node'; import { API_BASE_URL } from '~/config/api-config'; import { getUserSession } from '~/api/login/auth.server'; +function normalizeApp(payload: any) { + const app = payload?.data?.data || payload?.data || null; + if (!app) return null; + return { + app_id: String(app.appId), + app_name: app.appName, + description: app.description || '', + is_default: Boolean(app.isDefault), + type: 'rag', + created_at: '', + updated_at: '', + }; +} + export async function loader({ request }: LoaderFunctionArgs) { try { const { frontendJWT } = await getUserSession(request); @@ -20,23 +27,16 @@ export async function loader({ request }: LoaderFunctionArgs) { ); } - console.log('[API] Get Default Chat App - Forwarding to backend'); - - // 转发请求到后端 - const apiUrl = `${API_BASE_URL}/v3/dify/chat-apps/default`; - const response = await fetch(apiUrl, { + const response = await fetch(`${API_BASE_URL}/v3/rag/apps/default`, { method: 'GET', headers: { 'Content-Type': 'application/json', - 'Authorization': `Bearer ${frontendJWT}`, + Authorization: `Bearer ${frontendJWT}`, }, }); const data = await response.json(); - console.log('[API] Get Default Chat App - Backend response:', data); - - return json(data, { status: response.status }); - + return json({ data: normalizeApp(data) }, { status: response.status }); } catch (error: any) { console.error('[API] Get Default Chat App - Error:', error.message); return json( diff --git a/app/routes/api.v3.dify.chat-apps.my.tsx b/app/routes/api.v3.dify.chat-apps.my.tsx index 008c0e5..91310bf 100644 --- a/app/routes/api.v3.dify.chat-apps.my.tsx +++ b/app/routes/api.v3.dify.chat-apps.my.tsx @@ -1,14 +1,27 @@ -/** - * GET /api/v3/dify/chat-apps/my - 获取当前用户可访问的对话应用列表 - * - * 转发请求到后端 API,后端从配置文件读取对话应用列表 - * 参考文档:docs/new-dify/dify_api_doc.md - 对话应用多实例支持 - */ - import { LoaderFunctionArgs, json } from '@remix-run/node'; import { API_BASE_URL } from '~/config/api-config'; import { getUserSession } from '~/api/login/auth.server'; +function normalizeApps(payload: any) { + const apps = payload?.data?.data || payload?.data || []; + return { + data: Array.isArray(apps) + ? apps.map((app: any) => ({ + app_id: String(app.appId), + app_name: app.appName, + description: app.description || '', + is_default: Boolean(app.isDefault), + type: 'rag', + created_at: '', + updated_at: '', + })) + : [], + total: Array.isArray(apps) ? apps.length : 0, + page: 1, + page_size: Array.isArray(apps) ? apps.length : 0, + }; +} + export async function loader({ request }: LoaderFunctionArgs) { try { const { frontendJWT } = await getUserSession(request); @@ -20,24 +33,17 @@ export async function loader({ request }: LoaderFunctionArgs) { ); } - console.log('[API] Get My Chat Apps - Forwarding to backend'); - - // 转发请求到后端 - 使用正确的接口路径 - // 根据文档:GET /api/v3/dify/chat-apps - const apiUrl = `${API_BASE_URL}/v3/dify/chat-apps`; - const response = await fetch(apiUrl, { + const response = await fetch(`${API_BASE_URL}/v3/rag/apps`, { method: 'GET', headers: { 'Content-Type': 'application/json', - 'Authorization': `Bearer ${frontendJWT}`, + Authorization: `Bearer ${frontendJWT}`, }, }); const data = await response.json(); - console.log('[API] Get My Chat Apps - Backend response:', data); - - return json(data, { status: response.status }); - + const normalized = normalizeApps(data); + return json(normalized, { status: response.status }); } catch (error: any) { console.error('[API] Get My Chat Apps - Error:', error.message); return json( diff --git a/app/routes/document-types._index.tsx b/app/routes/document-types._index.tsx index 36267fb..d5a1c17 100644 --- a/app/routes/document-types._index.tsx +++ b/app/routes/document-types._index.tsx @@ -28,8 +28,9 @@ export async function loader({ request }: LoaderFunctionArgs) { const { getUserSession } = await import("~/api/login/auth.server"); const { frontendJWT, userInfo } = await getUserSession(request); const { requireRoutePermission } = await import("~/api/auth/check-route-permission.server"); + const userRole = userInfo?.role || userInfo?.user_role || ""; - await requireRoutePermission("/document-types", userInfo?.role || "", frontendJWT || undefined); + await requireRoutePermission("/document-types", userRole, frontendJWT || undefined); const rootsRes = await getDocumentTypeRoots({}, frontendJWT); return { diff --git a/app/routes/document-types.new.tsx b/app/routes/document-types.new.tsx index 2373c24..c34bedf 100644 --- a/app/routes/document-types.new.tsx +++ b/app/routes/document-types.new.tsx @@ -35,7 +35,8 @@ export async function loader({ request }: LoaderFunctionArgs) { const { getUserSession } = await import("~/api/login/auth.server"); const { frontendJWT, userInfo } = await getUserSession(request); const { requireRoutePermission } = await import("~/api/auth/check-route-permission.server"); - await requireRoutePermission("/document-types/new", userInfo?.role || "", frontendJWT || undefined); + const userRole = userInfo?.role || userInfo?.user_role || ""; + await requireRoutePermission("/document-types/new", userRole, frontendJWT || undefined); const url = new URL(request.url); const editId = url.searchParams.get("id"); diff --git a/app/routes/reviewsTest.tsx b/app/routes/reviewsTest.tsx index e00c0e2..018faa7 100644 --- a/app/routes/reviewsTest.tsx +++ b/app/routes/reviewsTest.tsx @@ -356,12 +356,13 @@ export async function loader({ request }: LoaderFunctionArgs): Promise const { getUserSession } = await import("~/api/login/auth.server"); const { userInfo, frontendJWT } = await getUserSession(request); - if (!frontendJWT || !userInfo?.role) { + const userRole = userInfo?.role || userInfo?.user_role || ''; + if (!frontendJWT || !userRole) { throw redirect('/login'); } const { requireRoutePermission } = await import("~/api/auth/check-route-permission.server"); - await requireRoutePermission('/reviewsTest', userInfo.role, frontendJWT); + await requireRoutePermission('/reviewsTest', userRole, frontendJWT); const reviewData = await getReviewPoints_fromApi(id, request); diff --git a/app/routes/rule-groups._index.tsx b/app/routes/rule-groups._index.tsx index 1ca0d28..f0fbc59 100644 --- a/app/routes/rule-groups._index.tsx +++ b/app/routes/rule-groups._index.tsx @@ -148,8 +148,9 @@ export async function loader({ request }: LoaderFunctionArgs) { const { getUserSession } = await import("~/api/login/auth.server"); const { frontendJWT, userInfo } = await getUserSession(request); const { requireRoutePermission } = await import("~/api/auth/check-route-permission.server"); + const userRole = userInfo?.role || userInfo?.user_role || ""; - await requireRoutePermission("/rule-groups", userInfo?.role || "", frontendJWT || undefined); + await requireRoutePermission("/rule-groups", userRole, frontendJWT || undefined); try { const [groups, docTypesRes, entryModulesRes, ruleSetsRes] = await Promise.all([ diff --git a/app/routes/rules.list.tsx b/app/routes/rules.list.tsx index 533a1fb..b926278 100644 --- a/app/routes/rules.list.tsx +++ b/app/routes/rules.list.tsx @@ -240,7 +240,8 @@ export async function loader({ request }: LoaderFunctionArgs) { const { getUserSession } = await import("~/api/login/auth.server"); const { frontendJWT, userInfo } = await getUserSession(request); const { requireRoutePermission } = await import("~/api/auth/check-route-permission.server"); - await requireRoutePermission("/rules/list", userInfo?.role || "", frontendJWT || undefined); + const userRole = userInfo?.role || userInfo?.user_role || ""; + await requireRoutePermission("/rules/list", userRole, frontendJWT || undefined); // 从 URL 参数中提取查询条件 const params = { @@ -287,7 +288,8 @@ export async function action({ request }: LoaderFunctionArgs) { const { getUserSession } = await import("~/api/login/auth.server"); const { frontendJWT, userInfo } = await getUserSession(request); const { requireRoutePermission } = await import("~/api/auth/check-route-permission.server"); - await requireRoutePermission("/rules/list", userInfo?.role || "", frontendJWT || undefined); + const userRole = userInfo?.role || userInfo?.user_role || ""; + await requireRoutePermission("/rules/list", userRole, frontendJWT || undefined); const formData = await request.formData(); const _action = formData.get('_action'); const ruleId = formData.get('ruleId'); diff --git a/app/routes/rules.tsx b/app/routes/rules.tsx index bd7833c..38b4a07 100644 --- a/app/routes/rules.tsx +++ b/app/routes/rules.tsx @@ -21,8 +21,9 @@ export async function loader({ request }: LoaderFunctionArgs) { const { getUserSession } = await import("~/api/login/auth.server"); const { frontendJWT, userInfo } = await getUserSession(request); const { requireRoutePermission } = await import("~/api/auth/check-route-permission.server"); + const userRole = userInfo?.role || userInfo?.user_role || ""; - await requireRoutePermission("/rules", userInfo?.role || "", frontendJWT || undefined); + await requireRoutePermission("/rules", userRole, frontendJWT || undefined); if (url.pathname === '/rules') { const query = url.searchParams.toString(); diff --git a/app/routes/rulesTest.detail.tsx b/app/routes/rulesTest.detail.tsx index 08f169d..dea6caa 100644 --- a/app/routes/rulesTest.detail.tsx +++ b/app/routes/rulesTest.detail.tsx @@ -446,7 +446,8 @@ export async function loader({ request }: LoaderFunctionArgs) { export async function action({ request }: ActionFunctionArgs) { const { frontendJWT, userInfo } = await getUserSession(request); const { requireRoutePermission } = await import('~/api/auth/check-route-permission.server'); - await requireRoutePermission('/rulesTest/detail', userInfo?.role || '', frontendJWT || undefined); + const userRole = userInfo?.role || userInfo?.user_role || ''; + await requireRoutePermission('/rulesTest/detail', userRole, frontendJWT || undefined); if (!frontendJWT) { return json({ success: false, intent: 'save', message: '登录已失效,请重新登录后再保存。' }, { status: 401 }); } diff --git a/app/services/collabora.config.server.ts b/app/services/collabora.config.server.ts index bfe3e55..1865353 100644 --- a/app/services/collabora.config.server.ts +++ b/app/services/collabora.config.server.ts @@ -87,9 +87,8 @@ function buildCollaboraIframeUrl(params: { }): string { const { collaboraUrl, wopiSrc, accessToken, mode } = params; - // Collabora iframe 基础 URL - // fa80579 是 Collabora 的版本号标识,实际部署时可能需要调整 - const baseUrl = `${collaboraUrl}/browser/fa80579/cool.html`; + // 使用稳定的 dist 入口,避免 Collabora 升级后版本号路径失效。 + const baseUrl = `${collaboraUrl}/browser/dist/cool.html`; const url = new URL(baseUrl); diff --git a/public/min-maps/vs/loader.js.map b/public/min-maps/vs/loader.js.map new file mode 100644 index 0000000..31d9949 --- /dev/null +++ b/public/min-maps/vs/loader.js.map @@ -0,0 +1,7 @@ +{ + "version": 3, + "file": "loader.js", + "sources": [], + "names": [], + "mappings": "" +}