### What problem does this PR solve? Feat: Automatically save agent canvas content Feat: Replace the link of the old version of the agent module #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)tags/v0.20.0
| @@ -21,6 +21,7 @@ import React, { ReactNode, useEffect, useState } from 'react'; | |||
| import { ThemeProvider, useTheme } from './components/theme-provider'; | |||
| import { SidebarProvider } from './components/ui/sidebar'; | |||
| import { TooltipProvider } from './components/ui/tooltip'; | |||
| import { ThemeEnum } from './constants/common'; | |||
| import storage from './utils/authorization-util'; | |||
| dayjs.extend(customParseFormat); | |||
| @@ -101,7 +102,10 @@ const RootProvider = ({ children }: React.PropsWithChildren) => { | |||
| return ( | |||
| <TooltipProvider> | |||
| <QueryClientProvider client={queryClient}> | |||
| <ThemeProvider defaultTheme="light" storageKey="ragflow-ui-theme"> | |||
| <ThemeProvider | |||
| defaultTheme={ThemeEnum.Light} | |||
| storageKey="ragflow-ui-theme" | |||
| > | |||
| <Root>{children}</Root> | |||
| </ThemeProvider> | |||
| </QueryClientProvider> | |||
| @@ -1,20 +1,19 @@ | |||
| import { ThemeEnum } from '@/constants/common'; | |||
| import React, { createContext, useContext, useEffect, useState } from 'react'; | |||
| type Theme = 'dark' | 'light' | 'system'; | |||
| type ThemeProviderProps = { | |||
| children: React.ReactNode; | |||
| defaultTheme?: Theme; | |||
| defaultTheme?: ThemeEnum; | |||
| storageKey?: string; | |||
| }; | |||
| type ThemeProviderState = { | |||
| theme: Theme; | |||
| setTheme: (theme: Theme) => void; | |||
| theme: ThemeEnum; | |||
| setTheme: (theme: ThemeEnum) => void; | |||
| }; | |||
| const initialState: ThemeProviderState = { | |||
| theme: 'light', | |||
| theme: ThemeEnum.Light, | |||
| setTheme: () => null, | |||
| }; | |||
| @@ -22,17 +21,17 @@ const ThemeProviderContext = createContext<ThemeProviderState>(initialState); | |||
| export function ThemeProvider({ | |||
| children, | |||
| defaultTheme = 'light', | |||
| defaultTheme = ThemeEnum.Light, | |||
| storageKey = 'vite-ui-theme', | |||
| ...props | |||
| }: ThemeProviderProps) { | |||
| const [theme, setTheme] = useState<Theme>( | |||
| () => (localStorage.getItem(storageKey) as Theme) || defaultTheme, | |||
| const [theme, setTheme] = useState<ThemeEnum>( | |||
| () => (localStorage.getItem(storageKey) as ThemeEnum) || defaultTheme, | |||
| ); | |||
| useEffect(() => { | |||
| const root = window.document.documentElement; | |||
| root.classList.remove('light', 'dark'); | |||
| root.classList.remove(ThemeEnum.Light, ThemeEnum.Dark); | |||
| localStorage.setItem(storageKey, theme); | |||
| root.classList.add(theme); | |||
| }, [storageKey, theme]); | |||
| @@ -62,5 +61,13 @@ export const useTheme = () => { | |||
| export const useIsDarkTheme = () => { | |||
| const { theme } = useTheme(); | |||
| return theme === 'dark'; | |||
| return theme === ThemeEnum.Dark; | |||
| }; | |||
| export function useSwitchToDarkThemeOnMount() { | |||
| const { setTheme } = useTheme(); | |||
| useEffect(() => { | |||
| setTheme(ThemeEnum.Dark); | |||
| }, [setTheme]); | |||
| } | |||
| @@ -150,3 +150,9 @@ export enum Platform { | |||
| FastGPT = 'FastGPT', | |||
| Coze = 'Coze', | |||
| } | |||
| export enum ThemeEnum { | |||
| Dark = 'dark', | |||
| Light = 'light', | |||
| System = 'system', | |||
| } | |||
| @@ -266,7 +266,7 @@ export const useResetAgent = () => { | |||
| return { data, loading, resetAgent: mutateAsync }; | |||
| }; | |||
| export const useSetAgent = () => { | |||
| export const useSetAgent = (showMessage: boolean = true) => { | |||
| const queryClient = useQueryClient(); | |||
| const { | |||
| data, | |||
| @@ -282,9 +282,11 @@ export const useSetAgent = () => { | |||
| }) => { | |||
| const { data = {} } = await agentService.setCanvas(params); | |||
| if (data.code === 0) { | |||
| message.success( | |||
| i18n.t(`message.${params?.id ? 'modified' : 'created'}`), | |||
| ); | |||
| if (showMessage) { | |||
| message.success( | |||
| i18n.t(`message.${params?.id ? 'modified' : 'created'}`), | |||
| ); | |||
| } | |||
| queryClient.invalidateQueries({ | |||
| queryKey: [AgentApiAction.FetchAgentList], | |||
| }); | |||
| @@ -29,7 +29,7 @@ const RagHeader = () => { | |||
| { path: '/knowledge', name: t('knowledgeBase'), icon: KnowledgeBaseIcon }, | |||
| { path: '/chat', name: t('chat'), icon: MessageOutlined }, | |||
| { path: '/search', name: t('search'), icon: SearchOutlined }, | |||
| { path: '/flow', name: t('flow'), icon: GraphIcon }, | |||
| { path: '/agent-list', name: t('flow'), icon: GraphIcon }, | |||
| { path: '/file', name: t('fileManager'), icon: FileIcon }, | |||
| ], | |||
| [t], | |||
| @@ -6,7 +6,7 @@ import React, { useCallback, useMemo } from 'react'; | |||
| import User from '../user'; | |||
| import { useTheme } from '@/components/theme-provider'; | |||
| import { LanguageList, LanguageMap } from '@/constants/common'; | |||
| import { LanguageList, LanguageMap, ThemeEnum } from '@/constants/common'; | |||
| import { useChangeLanguage } from '@/hooks/logic-hooks'; | |||
| import { useFetchUserInfo, useListTenant } from '@/hooks/user-setting-hooks'; | |||
| import { TenantRole } from '@/pages/user-setting/constants'; | |||
| @@ -58,10 +58,10 @@ const RightToolBar = () => { | |||
| }, []); | |||
| const onMoonClick = React.useCallback(() => { | |||
| setTheme('light'); | |||
| setTheme(ThemeEnum.Light); | |||
| }, [setTheme]); | |||
| const onSunClick = React.useCallback(() => { | |||
| setTheme('dark'); | |||
| setTheme(ThemeEnum.Dark); | |||
| }, [setTheme]); | |||
| const handleBellClick = useCallback(() => { | |||
| @@ -9,7 +9,7 @@ import { | |||
| DropdownMenuTrigger, | |||
| } from '@/components/ui/dropdown-menu'; | |||
| import { Segmented, SegmentedValue } from '@/components/ui/segmented'; | |||
| import { LanguageList, LanguageMap } from '@/constants/common'; | |||
| import { LanguageList, LanguageMap, ThemeEnum } from '@/constants/common'; | |||
| import { useChangeLanguage } from '@/hooks/logic-hooks'; | |||
| import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks'; | |||
| import { useNavigateWithFromState } from '@/hooks/route-hook'; | |||
| @@ -67,7 +67,7 @@ export function Header() { | |||
| })); | |||
| const onThemeClick = React.useCallback(() => { | |||
| setTheme(theme === 'dark' ? 'light' : 'dark'); | |||
| setTheme(theme === ThemeEnum.Dark ? ThemeEnum.Light : ThemeEnum.Dark); | |||
| }, [setTheme, theme]); | |||
| const handleBellClick = useCallback(() => { | |||
| @@ -4,16 +4,16 @@ import { | |||
| useSetAgent, | |||
| } from '@/hooks/use-agent-request'; | |||
| import { RAGFlowNodeType } from '@/interfaces/database/flow'; | |||
| import { formatDate } from '@/utils/date'; | |||
| import { useDebounceEffect } from 'ahooks'; | |||
| import dayjs from 'dayjs'; | |||
| import { useCallback, useEffect, useState } from 'react'; | |||
| import { useParams } from 'umi'; | |||
| import useGraphStore from '../store'; | |||
| import { useBuildDslData } from './use-build-dsl'; | |||
| export const useSaveGraph = () => { | |||
| export const useSaveGraph = (showMessage: boolean = true) => { | |||
| const { data } = useFetchAgent(); | |||
| const { setAgent, loading } = useSetAgent(); | |||
| const { setAgent, loading } = useSetAgent(showMessage); | |||
| const { id } = useParams(); | |||
| const { buildDslData } = useBuildDslData(); | |||
| @@ -57,11 +57,11 @@ export const useWatchAgentChange = (chatDrawerVisible: boolean) => { | |||
| const [time, setTime] = useState<string>(); | |||
| const nodes = useGraphStore((state) => state.nodes); | |||
| const edges = useGraphStore((state) => state.edges); | |||
| const { saveGraph } = useSaveGraph(); | |||
| const { saveGraph } = useSaveGraph(false); | |||
| const { data: flowDetail } = useFetchAgent(); | |||
| const setSaveTime = useCallback((updateTime: number) => { | |||
| setTime(dayjs(updateTime).format('YYYY-MM-DD HH:mm:ss')); | |||
| setTime(formatDate(updateTime)); | |||
| }, []); | |||
| useEffect(() => { | |||
| @@ -77,7 +77,7 @@ export const useWatchAgentChange = (chatDrawerVisible: boolean) => { | |||
| useDebounceEffect( | |||
| () => { | |||
| // saveAgent(); | |||
| saveAgent(); | |||
| }, | |||
| [nodes, edges], | |||
| { | |||
| @@ -1,4 +1,5 @@ | |||
| import { PageHeader } from '@/components/page-header'; | |||
| import { useSwitchToDarkThemeOnMount } from '@/components/theme-provider'; | |||
| import { | |||
| Breadcrumb, | |||
| BreadcrumbItem, | |||
| @@ -40,6 +41,7 @@ import { useGetBeginNodeDataInputs } from './hooks/use-get-begin-query'; | |||
| import { | |||
| useSaveGraph, | |||
| useSaveGraphBeforeOpeningDebugDrawer, | |||
| useWatchAgentChange, | |||
| } from './hooks/use-save-graph'; | |||
| import { useShowEmbedModal } from './hooks/use-show-dialog'; | |||
| import { UploadAgentDialog } from './upload-agent-dialog'; | |||
| @@ -94,23 +96,31 @@ export default function Agent() { | |||
| const { showEmbedModal, hideEmbedModal, embedVisible, beta } = | |||
| useShowEmbedModal(); | |||
| const { navigateToAgentLogs } = useNavigatePage(); | |||
| const time = useWatchAgentChange(chatDrawerVisible); | |||
| useSwitchToDarkThemeOnMount(); | |||
| return ( | |||
| <section className="h-full"> | |||
| <PageHeader> | |||
| <Breadcrumb> | |||
| <BreadcrumbList> | |||
| <BreadcrumbItem> | |||
| <BreadcrumbLink onClick={navigateToAgentList}> | |||
| Agent | |||
| </BreadcrumbLink> | |||
| </BreadcrumbItem> | |||
| <BreadcrumbSeparator /> | |||
| <BreadcrumbItem> | |||
| <BreadcrumbPage>{agentDetail.title}</BreadcrumbPage> | |||
| </BreadcrumbItem> | |||
| </BreadcrumbList> | |||
| </Breadcrumb> | |||
| <section> | |||
| <Breadcrumb> | |||
| <BreadcrumbList> | |||
| <BreadcrumbItem> | |||
| <BreadcrumbLink onClick={navigateToAgentList}> | |||
| Agent | |||
| </BreadcrumbLink> | |||
| </BreadcrumbItem> | |||
| <BreadcrumbSeparator /> | |||
| <BreadcrumbItem> | |||
| <BreadcrumbPage>{agentDetail.title}</BreadcrumbPage> | |||
| </BreadcrumbItem> | |||
| </BreadcrumbList> | |||
| </Breadcrumb> | |||
| <div className="text-xs text-text-sub-title translate-y-3"> | |||
| {t('flow.autosaved')} {time} | |||
| </div> | |||
| </section> | |||
| <div className="flex items-center gap-5"> | |||
| <ButtonLoading | |||
| variant={'secondary'} | |||
| @@ -3,6 +3,7 @@ import { NextMessageInput } from '@/components/message-input/next'; | |||
| import MessageItem from '@/components/next-message-item'; | |||
| import PdfDrawer from '@/components/pdf-drawer'; | |||
| import { useClickDrawer } from '@/components/pdf-drawer/hooks'; | |||
| import { useSwitchToDarkThemeOnMount } from '@/components/theme-provider'; | |||
| import { MessageType } from '@/constants/chat'; | |||
| import { | |||
| useFetchExternalAgentInputs, | |||
| @@ -91,6 +92,8 @@ const ChatContainer = () => { | |||
| } | |||
| }, [data, showParameterDialog]); | |||
| useSwitchToDarkThemeOnMount(); | |||
| if (!conversationId) { | |||
| return <div>empty</div>; | |||
| } | |||
| @@ -32,7 +32,7 @@ export default function McpServer() { | |||
| ); | |||
| return ( | |||
| <section className="p-4"> | |||
| <section className="p-4 w-full"> | |||
| <div className="text-text-title text-2xl">MCP Servers</div> | |||
| <section className="flex items-center justify-between pb-5"> | |||
| <div className="text-text-sub-title"> | |||
| @@ -2,6 +2,7 @@ import { useIsDarkTheme, useTheme } from '@/components/theme-provider'; | |||
| import { Button } from '@/components/ui/button'; | |||
| import { Label } from '@/components/ui/label'; | |||
| import { Switch } from '@/components/ui/switch'; | |||
| import { ThemeEnum } from '@/constants/common'; | |||
| import { useLogout } from '@/hooks/login-hooks'; | |||
| import { useSecondPathName } from '@/hooks/route-hook'; | |||
| import { cn } from '@/lib/utils'; | |||
| @@ -60,7 +61,7 @@ export function SideBar() { | |||
| const handleThemeChange = useCallback( | |||
| (checked: boolean) => { | |||
| setTheme(checked ? 'dark' : 'light'); | |||
| setTheme(checked ? ThemeEnum.Dark : ThemeEnum.Light); | |||
| }, | |||
| [setTheme], | |||
| ); | |||
| @@ -81,7 +81,7 @@ | |||
| --text-sub-title: rgba(151, 154, 171, 1); | |||
| --text-sub-title-invert: rgba(91, 93, 106, 1); | |||
| --background-header-bar: rgba(11, 11, 12, 1); | |||
| --background-header-bar: rgba(0, 0, 0, 0.05); | |||
| --text-title-invert: rgba(255, 255, 255, 1); | |||
| --background-card: rgba(22, 22, 24, 0.05); | |||