feat:替换 Dify 为自建 RAG去实现

1、修复了若干无权限时的失败提示语
2、新增了一个生成后续建议问题的功能
3、重构了知识问答部分的权限管理模块
4、修复了若干渲染不恰当的样式渲染
This commit is contained in:
PingChuan
2026-04-10 16:20:32 +08:00
parent f525707358
commit 5bee9288b9
31 changed files with 407 additions and 304 deletions
+63 -30
View File
@@ -1,5 +1,5 @@
import { useBoolean, useGetState } from 'ahooks';
import { Layout, theme } from 'antd';
import { Layout } from 'antd';
import { useEffect, useRef, useState } from 'react';
import ChatInput from './chat-input';
import ChatMessage from './chat-message';
@@ -8,6 +8,7 @@ import ChatSidebar, { type ChatSidebarRef } from './sidebar';
import type { ChatItem, ConversationItem } from '~/api/dify-chat';
import { fetchAppParams, fetchChatList, fetchConversations } from '~/api/dify-chat';
import { CHAT_CONFIG } from '../../config/chat';
import { usePermission } from '~/hooks/usePermission';
import useChatMessage from '../../hooks/use-chat-message';
import useConversation from '../../hooks/use-conversation';
import { useChatApps } from '../../hooks/dify-chat-apps/useChatApps';
@@ -28,29 +29,34 @@ interface ChatTheme {
}
/**
* 获取主题token - 避免在SSR环境中调用
* 主题配置常量
* 避免 SSR 环境下调用 theme.useToken() 导致 CSS-in-JS 注入报错
*/
function useChatTheme(): ChatTheme {
// Ant Design的theme.useToken()必须在组件顶层调用,不能放在useEffect中
const antdToken = typeof window !== 'undefined' ? theme.useToken().token : null;
return {
colorBgContainer: antdToken?.colorBgContainer || '#ffffff',
borderRadiusLG: antdToken?.borderRadiusLG || 8,
};
}
const CHAT_THEME: ChatTheme = {
colorBgContainer: '#ffffff',
borderRadiusLG: 8,
};
/**
* 主聊天组件
* 实现单页面应用模式,参考webapp-conversation的初始化逻辑
*/
export default function Chat() {
// SSR 兼容:Ant Design 的 CSS-in-JS 在 Remix SSR 环境下
// hydration 时会因找不到样式容器而报错,需要客户端渲染
const [mounted, setMounted] = useState(false);
useEffect(() => { setMounted(true); }, []);
// 权限检查
const { hasPermission: checkPerm } = usePermission();
const canChat = checkPerm('dify:chat:use');
// 侧边栏状态
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
const [isMobile, setIsMobile] = useState(false);
// 获取主题配置,避免SSR错误
const { colorBgContainer, borderRadiusLG } = useChatTheme();
// 主题配置
const { colorBgContainer, borderRadiusLG } = CHAT_THEME;
// 对话应用管理
const {
@@ -162,8 +168,11 @@ export default function Chat() {
// 应用状态
const [appUnavailable, setAppUnavailable] = useState<boolean>(false);
const [isUnknownReason, setIsUnknownReason] = useState<boolean>(false);
const [conversationPermissionDenied, setConversationPermissionDenied] = useState<boolean>(false);
const [inited, setInited] = useState<boolean>(false);
const [promptConfig, setPromptConfig] = useState<any>(null);
// 防止重复初始化
const [initializing, setInitializing] = useState<boolean>(false);
// 会话状态管理
const [conversationIdChangeBecauseOfNew, setConversationIdChangeBecauseOfNew, getConversationIdChangeBecauseOfNew] = useGetState(false);
@@ -507,7 +516,8 @@ export default function Chat() {
};
/**
* 组件初始化 - 参考webapp-conversation的逻辑
* 组件初始化 - 等待 currentChatApp 就绪后再获取会话列表
* 确保按当前应用过滤会话,避免不同应用的会话混在一起
*/
useEffect(() => {
if (!hasSetAppConfig) {
@@ -516,14 +526,28 @@ export default function Chat() {
return;
}
// 必须等 currentChatApp 就绪,否则不知道该获取哪个应用的会话
if (!currentChatApp || initializing || inited) {
return;
}
setInitializing(true);
(async () => {
try {
// console.log('🚀 开始初始化聊天应用...');
console.log('🚀 [Chat] 开始初始化,当前应用:', currentChatApp.app_name, currentChatApp.app_id);
// 并行获取会话列表和应用参数
// 用当前应用的 appId 获取会话列表(失败时降级为空列表,不阻塞初始化)
const [conversationData, appParams] = await Promise.all([
fetchConversations(),
fetchAppParams()
fetchConversations(currentChatApp.app_id).catch(err => {
console.warn('⚠️ [Chat] 获取会话列表失败(权限不足或网络问题),降级为空列表:', err.message);
setConversationPermissionDenied(true);
return { data: [] };
}),
fetchAppParams().catch(err => {
console.warn('⚠️ [Chat] 获取应用参数失败,使用默认值:', err.message);
return { data: { user_input_form: [], opening_statement: '' } };
}),
]);
console.log('📋 [Chat] 获取到的数据:', { conversationData, appParams });
@@ -532,13 +556,8 @@ export default function Chat() {
const conversations = (conversationData as any).data || [];
console.log('📋 [Chat] 会话列表:', conversations);
if ((conversationData as any).error) {
console.error('❌ [Chat] 获取会话列表失败:', (conversationData as any).error);
throw new Error((conversationData as any).error);
}
// 处理当前会话ID
const _conversationId = getConversationIdFromStorage(CHAT_CONFIG.APP_ID);
const _conversationId = getConversationIdFromStorage(currentChatApp.app_id);
const isNotNewConversation = conversations.some((item: ConversationItem) => item.id === _conversationId);
console.log('💾 [Chat] 初始化 - 本地存储的会话ID:', {
@@ -568,15 +587,15 @@ export default function Chat() {
// 如果存在有效的会话ID,则设置为当前会话
if (isNotNewConversation) {
console.log('🎯 [Chat] 初始化 - 设置当前会话ID:', _conversationId);
setCurrConversationId(_conversationId, CHAT_CONFIG.APP_ID, false);
setCurrConversationId(_conversationId, currentChatApp.app_id, false);
} else {
// 如果localStorage为空或会话不存在,自动创建新会话
console.log('🆕 [Chat] 初始化 - localStorage为空或会话不存在,创建新会话');
setCurrConversationId('-1', CHAT_CONFIG.APP_ID, false);
setCurrConversationId('-1', currentChatApp.app_id, false);
}
setInited(true);
// console.log('✅ 聊天应用初始化完成');
console.log('✅ [Chat] 聊天应用初始化完成');
} catch (e: any) {
console.error('❌ 初始化失败:', e);
if (e.status === 404) {
@@ -587,7 +606,7 @@ export default function Chat() {
}
}
})();
}, []);
}, [currentChatApp]);
// 监听会话切换
useEffect(() => {
@@ -663,6 +682,18 @@ export default function Chat() {
const conversationName = currConversationInfo?.name || '新对话';
const conversationIntroduction = currConversationInfo?.introduction || '';
// SSR 兼容:等客户端挂载后再渲染 Ant Design 组件
if (!mounted) {
return (
<div className="flex items-center justify-center h-full">
<div className="text-center">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600 mx-auto mb-2"></div>
<p className="text-gray-600">...</p>
</div>
</div>
);
}
return (
<Layout style={{ height: '100%', display: 'flex', flexDirection: 'row', position: 'relative' }}>
{/* 移动端遮罩层 - 点击可收起侧边栏 */}
@@ -704,6 +735,7 @@ export default function Chat() {
loadingChatApps={loadingChatApps}
currentChatApp={currentChatApp}
onChatAppChange={handleChatAppChange}
conversationReadOnly={conversationPermissionDenied}
/>
{/* 主内容区域 */}
@@ -738,6 +770,7 @@ export default function Chat() {
message={item}
isResponding={isResponding && item.id === chatList[chatList.length - 1]?.id}
onFeedback={handleFeedback}
onSuggestedQuestionClick={(question) => handleSendMessage(question)}
/>
))}
</div>
@@ -747,8 +780,8 @@ export default function Chat() {
<div className="flex-shrink-0 bg-white">
<ChatInput
onSendMessage={handleSendMessage}
disabled={isResponding}
placeholder="有什么我能帮您的吗?"
disabled={isResponding || !canChat}
placeholder={canChat ? "有什么我能帮您的吗?" : "您没有发送消息的权限"}
onStop={stopResponding}
isResponding={isResponding}
/>