Parcourir la source

Feat: Upload agent file #3221 (#5311)

### What problem does this PR solve?

Feat: Upload agent file #3221

### Type of change


- [x] New Feature (non-breaking change which adds functionality)
tags/v0.17.0
balibabu il y a 8 mois
Parent
révision
033a4cf21e
Aucun compte lié à l'adresse e-mail de l'auteur

+ 199
- 12
web/package-lock.json Voir le fichier

"@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-switch": "^1.1.1", "@radix-ui/react-switch": "^1.1.1",
"@radix-ui/react-tabs": "^1.1.1", "@radix-ui/react-tabs": "^1.1.1",
"@radix-ui/react-toast": "^1.2.2",
"@radix-ui/react-toast": "^1.2.6",
"@radix-ui/react-tooltip": "^1.1.4", "@radix-ui/react-tooltip": "^1.1.4",
"@tailwindcss/line-clamp": "^0.4.4", "@tailwindcss/line-clamp": "^0.4.4",
"@tanstack/react-query": "^5.40.0", "@tanstack/react-query": "^5.40.0",
} }
}, },
"node_modules/@radix-ui/react-toast": { "node_modules/@radix-ui/react-toast": {
"version": "1.2.2",
"resolved": "https://registry.npmmirror.com/@radix-ui/react-toast/-/react-toast-1.2.2.tgz",
"integrity": "sha512-Z6pqSzmAP/bFJoqMAston4eSNa+ud44NSZTiZUmUen+IOZ5nBY8kzuU5WDBVyFXPtcW6yUalOHsxM/BP6Sv8ww==",
"version": "1.2.6",
"resolved": "https://registry.npmmirror.com/@radix-ui/react-toast/-/react-toast-1.2.6.tgz",
"integrity": "sha512-gN4dpuIVKEgpLn1z5FhzT9mYRUitbfZq9XqN/7kkBMUgFTzTG8x/KszWJugJXHcwxckY8xcKDZPz7kG3o6DsUA==",
"license": "MIT",
"dependencies": { "dependencies": {
"@radix-ui/primitive": "1.1.0",
"@radix-ui/react-collection": "1.1.0",
"@radix-ui/react-compose-refs": "1.1.0",
"@radix-ui/primitive": "1.1.1",
"@radix-ui/react-collection": "1.1.2",
"@radix-ui/react-compose-refs": "1.1.1",
"@radix-ui/react-context": "1.1.1", "@radix-ui/react-context": "1.1.1",
"@radix-ui/react-dismissable-layer": "1.1.1",
"@radix-ui/react-portal": "1.1.2",
"@radix-ui/react-presence": "1.1.1",
"@radix-ui/react-primitive": "2.0.0",
"@radix-ui/react-dismissable-layer": "1.1.5",
"@radix-ui/react-portal": "1.1.4",
"@radix-ui/react-presence": "1.1.2",
"@radix-ui/react-primitive": "2.0.2",
"@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-callback-ref": "1.1.0",
"@radix-ui/react-use-controllable-state": "1.1.0", "@radix-ui/react-use-controllable-state": "1.1.0",
"@radix-ui/react-use-layout-effect": "1.1.0", "@radix-ui/react-use-layout-effect": "1.1.0",
"@radix-ui/react-visually-hidden": "1.1.0"
"@radix-ui/react-visually-hidden": "1.1.2"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-toast/node_modules/@radix-ui/primitive": {
"version": "1.1.1",
"resolved": "https://registry.npmmirror.com/@radix-ui/primitive/-/primitive-1.1.1.tgz",
"integrity": "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==",
"license": "MIT"
},
"node_modules/@radix-ui/react-toast/node_modules/@radix-ui/react-collection": {
"version": "1.1.2",
"resolved": "https://registry.npmmirror.com/@radix-ui/react-collection/-/react-collection-1.1.2.tgz",
"integrity": "sha512-9z54IEKRxIa9VityapoEYMuByaG42iSy1ZXlY2KcuLSEtq8x4987/N6m15ppoMffgZX72gER2uHe1D9Y6Unlcw==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.1",
"@radix-ui/react-context": "1.1.1",
"@radix-ui/react-primitive": "2.0.2",
"@radix-ui/react-slot": "1.1.2"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-toast/node_modules/@radix-ui/react-compose-refs": {
"version": "1.1.1",
"resolved": "https://registry.npmmirror.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz",
"integrity": "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==",
"license": "MIT",
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-toast/node_modules/@radix-ui/react-dismissable-layer": {
"version": "1.1.5",
"resolved": "https://registry.npmmirror.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.5.tgz",
"integrity": "sha512-E4TywXY6UsXNRhFrECa5HAvE5/4BFcGyfTyK36gP+pAW1ed7UTK4vKwdr53gAJYwqbfCWC6ATvJa3J3R/9+Qrg==",
"license": "MIT",
"dependencies": {
"@radix-ui/primitive": "1.1.1",
"@radix-ui/react-compose-refs": "1.1.1",
"@radix-ui/react-primitive": "2.0.2",
"@radix-ui/react-use-callback-ref": "1.1.0",
"@radix-ui/react-use-escape-keydown": "1.1.0"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-toast/node_modules/@radix-ui/react-portal": {
"version": "1.1.4",
"resolved": "https://registry.npmmirror.com/@radix-ui/react-portal/-/react-portal-1.1.4.tgz",
"integrity": "sha512-sn2O9k1rPFYVyKd5LAJfo96JlSGVFpa1fS6UuBJfrZadudiw5tAmru+n1x7aMRQ84qDM71Zh1+SzK5QwU0tJfA==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-primitive": "2.0.2",
"@radix-ui/react-use-layout-effect": "1.1.0"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-toast/node_modules/@radix-ui/react-presence": {
"version": "1.1.2",
"resolved": "https://registry.npmmirror.com/@radix-ui/react-presence/-/react-presence-1.1.2.tgz",
"integrity": "sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.1",
"@radix-ui/react-use-layout-effect": "1.1.0"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-toast/node_modules/@radix-ui/react-primitive": {
"version": "2.0.2",
"resolved": "https://registry.npmmirror.com/@radix-ui/react-primitive/-/react-primitive-2.0.2.tgz",
"integrity": "sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-slot": "1.1.2"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-toast/node_modules/@radix-ui/react-slot": {
"version": "1.1.2",
"resolved": "https://registry.npmmirror.com/@radix-ui/react-slot/-/react-slot-1.1.2.tgz",
"integrity": "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-toast/node_modules/@radix-ui/react-visually-hidden": {
"version": "1.1.2",
"resolved": "https://registry.npmmirror.com/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.1.2.tgz",
"integrity": "sha512-1SzA4ns2M1aRlvxErqhLHsBHoS5eI5UUcI2awAMgGUp4LoaoWOKYmvqDY2s/tltuPkh3Yk77YF/r3IRj+Amx4Q==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-primitive": "2.0.2"
}, },
"peerDependencies": { "peerDependencies": {
"@types/react": "*", "@types/react": "*",

+ 1
- 1
web/package.json Voir le fichier

"@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-switch": "^1.1.1", "@radix-ui/react-switch": "^1.1.1",
"@radix-ui/react-tabs": "^1.1.1", "@radix-ui/react-tabs": "^1.1.1",
"@radix-ui/react-toast": "^1.2.2",
"@radix-ui/react-toast": "^1.2.6",
"@radix-ui/react-tooltip": "^1.1.4", "@radix-ui/react-tooltip": "^1.1.4",
"@tailwindcss/line-clamp": "^0.4.4", "@tailwindcss/line-clamp": "^0.4.4",
"@tanstack/react-query": "^5.40.0", "@tanstack/react-query": "^5.40.0",

+ 2
- 2
web/src/app.tsx Voir le fichier

import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import { App, ConfigProvider, ConfigProviderProps, theme } from 'antd'; import { App, ConfigProvider, ConfigProviderProps, theme } from 'antd';
import pt_BR from 'antd/lib/locale/pt_BR';
import enUS from 'antd/locale/en_US'; import enUS from 'antd/locale/en_US';
import vi_VN from 'antd/locale/vi_VN'; import vi_VN from 'antd/locale/vi_VN';
import zhCN from 'antd/locale/zh_CN'; import zhCN from 'antd/locale/zh_CN';
import zh_HK from 'antd/locale/zh_HK'; import zh_HK from 'antd/locale/zh_HK';
import pt_BR from 'antd/lib/locale/pt_BR';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import advancedFormat from 'dayjs/plugin/advancedFormat'; import advancedFormat from 'dayjs/plugin/advancedFormat';
import customParseFormat from 'dayjs/plugin/customParseFormat'; import customParseFormat from 'dayjs/plugin/customParseFormat';
}} }}
locale={locale} locale={locale}
> >
<App> {children}</App>
<App>{children}</App>
</ConfigProvider> </ConfigProvider>
<ReactQueryDevtools buttonPosition={'top-left'} /> <ReactQueryDevtools buttonPosition={'top-left'} />
</> </>

+ 4
- 1
web/src/components/hooks/use-toast.tsx Voir le fichier

// Inspired by react-hot-toast library // Inspired by react-hot-toast library
import * as React from 'react'; import * as React from 'react';


import type { ToastActionElement, ToastProps } from '@/components/ui/toast';
import type {
ToastActionElement,
ToastProps,
} from '@/registry/default/ui/toast';


const TOAST_LIMIT = 1; const TOAST_LIMIT = 1;
const TOAST_REMOVE_DELAY = 1000000; const TOAST_REMOVE_DELAY = 1000000;

+ 22
- 11
web/src/components/ui/select.tsx Voir le fichier

import { ControllerRenderProps } from 'react-hook-form'; import { ControllerRenderProps } from 'react-hook-form';


import { FormControl } from '@/components/ui/form'; import { FormControl } from '@/components/ui/form';
import { useCallback, useEffect } from 'react';
import { forwardRef, useCallback, useEffect } from 'react';


const Select = SelectPrimitive.Root; const Select = SelectPrimitive.Root;


React.ElementRef<typeof SelectPrimitive.Trigger>, React.ElementRef<typeof SelectPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger> & { React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger> & {
onReset?: () => void; onReset?: () => void;
allowClear?: boolean;
} }
>(({ className, children, value, onReset, ...props }, ref) => (
>(({ className, children, value, onReset, allowClear, ...props }, ref) => (
<SelectPrimitive.Trigger <SelectPrimitive.Trigger
ref={ref} ref={ref}
className={cn( className={cn(
event.stopPropagation(); event.stopPropagation();
}} }}
> >
{value ? (
{value && allowClear ? (
<X className="h-4 w-4 opacity-50 cursor-pointer" onClick={onReset} /> <X className="h-4 w-4 opacity-50 cursor-pointer" onClick={onReset} />
) : ( ) : (
<ChevronDown className="h-4 w-4 opacity-50" /> <ChevronDown className="h-4 w-4 opacity-50" />
type RAGFlowSelectProps = Partial<ControllerRenderProps> & { type RAGFlowSelectProps = Partial<ControllerRenderProps> & {
FormControlComponent?: typeof FormControl; FormControlComponent?: typeof FormControl;
options?: (RAGFlowSelectOptionType | RAGFlowSelectGroupOptionType)[]; options?: (RAGFlowSelectOptionType | RAGFlowSelectGroupOptionType)[];
allowClear?: boolean;
}; };


/** /**
* } * }
* @return {*} * @return {*}
*/ */
export function RAGFlowSelect({
value: initialValue,
onChange,
FormControlComponent,
options = [],
}: RAGFlowSelectProps) {
export const RAGFlowSelect = forwardRef<
React.ElementRef<typeof SelectPrimitive.Trigger>,
RAGFlowSelectProps
>(function (
{
value: initialValue,
onChange,
FormControlComponent,
options = [],
allowClear,
},
ref,
) {
const [key, setKey] = React.useState(+new Date()); const [key, setKey] = React.useState(+new Date());
const [value, setValue] = React.useState<string | undefined>(undefined); const [value, setValue] = React.useState<string | undefined>(undefined);


const FormControlWidget = FormControlComponent const FormControlWidget = FormControlComponent
? FormControlComponent ? FormControlComponent
: React.Fragment;
: ({ children }: React.PropsWithChildren) => <div>{children}</div>;


const handleChange = useCallback( const handleChange = useCallback(
(val?: string) => { (val?: string) => {
className="bg-colors-background-inverse-weak" className="bg-colors-background-inverse-weak"
value={value} value={value}
onReset={handleReset} onReset={handleReset}
allowClear={allowClear}
ref={ref}
> >
<SelectValue placeholder="Select a verified email to display" /> <SelectValue placeholder="Select a verified email to display" />
</SelectTrigger> </SelectTrigger>
</SelectContent> </SelectContent>
</Select> </Select>
); );
}
});

+ 4
- 4
web/src/components/ui/toast.tsx Voir le fichier

ToastViewport.displayName = ToastPrimitives.Viewport.displayName; ToastViewport.displayName = ToastPrimitives.Viewport.displayName;


const toastVariants = cva( const toastVariants = cva(
'group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full',
'group pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-md border p-4 pr-6 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full',
{ {
variants: { variants: {
variant: { variant: {
<ToastPrimitives.Action <ToastPrimitives.Action
ref={ref} ref={ref}
className={cn( className={cn(
'inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium ring-offset-background transition-colors hover:bg-secondary focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive',
'inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium transition-colors hover:bg-secondary focus:outline-none focus:ring-1 focus:ring-ring disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive',
className, className,
)} )}
{...props} {...props}
<ToastPrimitives.Close <ToastPrimitives.Close
ref={ref} ref={ref}
className={cn( className={cn(
'absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600',
'absolute right-1 top-1 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-1 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600',
className, className,
)} )}
toast-close="" toast-close=""
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<ToastPrimitives.Title <ToastPrimitives.Title
ref={ref} ref={ref}
className={cn('text-sm font-semibold', className)}
className={cn('text-sm font-semibold [&+div]:text-xs', className)}
{...props} {...props}
/> />
)); ));

+ 1
- 1
web/src/components/ui/toaster.tsx Voir le fichier



return ( return (
<ToastProvider> <ToastProvider>
{toasts.map(function ({ id, title, description, action, ...props }: any) {
{toasts.map(function ({ id, title, description, action, ...props }) {
return ( return (
<Toast key={id} {...props}> <Toast key={id} {...props}>
<div className="grid gap-1"> <div className="grid gap-1">

+ 7
- 0
web/src/constants/common.ts Voir le fichier



export const SupportedPreviewDocumentTypes = [...ExceptiveType]; export const SupportedPreviewDocumentTypes = [...ExceptiveType];
//#endregion //#endregion

export enum Platform {
RAGFlow = 'RAGFlow',
Dify = 'Dify',
FastGPT = 'FastGPT',
Coze = 'Coze',
}

+ 15
- 6
web/src/pages/agent/hooks/use-export-json.ts Voir le fichier

import { FileMimeType } from '@/constants/common';
import { useToast } from '@/components/hooks/use-toast';
import { FileMimeType, Platform } from '@/constants/common';
import { useSetModalState } from '@/hooks/common-hooks'; import { useSetModalState } from '@/hooks/common-hooks';
import { useFetchFlow } from '@/hooks/flow-hooks'; import { useFetchFlow } from '@/hooks/flow-hooks';
import { IGraph } from '@/interfaces/database/flow'; import { IGraph } from '@/interfaces/database/flow';
import { downloadJsonFile } from '@/utils/file-util'; import { downloadJsonFile } from '@/utils/file-util';
import { message, UploadFile } from 'antd';
import { message } from 'antd';
import isEmpty from 'lodash/isEmpty'; import isEmpty from 'lodash/isEmpty';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
const setGraphInfo = useSetGraphInfo(); const setGraphInfo = useSetGraphInfo();
const { data } = useFetchFlow(); const { data } = useFetchFlow();
const { t } = useTranslation(); const { t } = useTranslation();
const { toast } = useToast();


const onFileUploadOk = useCallback( const onFileUploadOk = useCallback(
async (fileList: UploadFile[]) => {
async ({
fileList,
platform,
}: {
fileList: File[];
platform: Platform;
}) => {
console.log('🚀 ~ useHandleExportOrImportJsonFile ~ platform:', platform);
if (fileList.length > 0) { if (fileList.length > 0) {
const file: File = fileList[0] as unknown as File;
const file = fileList[0];
if (file.type !== FileMimeType.Json) { if (file.type !== FileMimeType.Json) {
message.error(t('flow.jsonUploadTypeErrorMessage'));
toast({ title: t('flow.jsonUploadTypeErrorMessage') });
return; return;
} }


} }
} }
}, },
[hideFileUploadModal, setGraphInfo, t],
[hideFileUploadModal, setGraphInfo, t, toast],
); );


const handleExportJson = useCallback(() => { const handleExportJson = useCallback(() => {

+ 68
- 7
web/src/pages/agent/index.tsx Voir le fichier

import { PageHeader } from '@/components/page-header'; import { PageHeader } from '@/components/page-header';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { SidebarProvider, SidebarTrigger } from '@/components/ui/sidebar'; import { SidebarProvider, SidebarTrigger } from '@/components/ui/sidebar';
import { useSetModalState } from '@/hooks/common-hooks'; import { useSetModalState } from '@/hooks/common-hooks';
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks'; import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
import { Trash2 } from 'lucide-react';
import { CodeXml, EllipsisVertical, Forward, Import, Key } from 'lucide-react';
import { ComponentPropsWithoutRef } from 'react';
import { useTranslation } from 'react-i18next';
import { AgentSidebar } from './agent-sidebar'; import { AgentSidebar } from './agent-sidebar';
import FlowCanvas from './canvas'; import FlowCanvas from './canvas';
import { useHandleExportOrImportJsonFile } from './hooks/use-export-json';
import { useFetchDataOnMount } from './hooks/use-fetch-data'; import { useFetchDataOnMount } from './hooks/use-fetch-data';
import { useOpenDocument } from './hooks/use-open-document';
import { UploadAgentDialog } from './upload-agent-dialog';

function AgentDropdownMenuItem({
children,
...props
}: ComponentPropsWithoutRef<typeof DropdownMenuItem>) {
return (
<DropdownMenuItem className="flex justify-between items-center" {...props}>
{children}
</DropdownMenuItem>
);
}


export default function Agent() { export default function Agent() {
const { navigateToAgentList } = useNavigatePage(); const { navigateToAgentList } = useNavigatePage();
hideModal: hideChatDrawer, hideModal: hideChatDrawer,
showModal: showChatDrawer, showModal: showChatDrawer,
} = useSetModalState(); } = useSetModalState();
const { t } = useTranslation();
const openDocument = useOpenDocument();
const {
handleExportJson,
handleImportJson,
fileUploadVisible,
onFileUploadOk,
hideFileUploadModal,
} = useHandleExportOrImportJsonFile();


useFetchDataOnMount(); useFetchDataOnMount();


<section> <section>
<PageHeader back={navigateToAgentList} title="Agent 01"> <PageHeader back={navigateToAgentList} title="Agent 01">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Button variant={'icon'} size={'icon'}>
<Trash2 />
</Button>
<DropdownMenu>
<DropdownMenuTrigger>
<Button variant={'icon'} size={'icon'}>
<EllipsisVertical />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<AgentDropdownMenuItem onClick={openDocument}>
API
<Key />
</AgentDropdownMenuItem>
<DropdownMenuSeparator />
<AgentDropdownMenuItem onClick={handleImportJson}>
Import
<Import />
</AgentDropdownMenuItem>
<DropdownMenuSeparator />
<AgentDropdownMenuItem onClick={handleExportJson}>
Export
<Forward />
</AgentDropdownMenuItem>
<DropdownMenuSeparator />
<AgentDropdownMenuItem>
{t('common.embedIntoSite')}
<CodeXml />
</AgentDropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>

<Button variant={'outline'} size={'sm'}> <Button variant={'outline'} size={'sm'}>
Save Save
</Button> </Button>
<Button variant={'outline'} size={'sm'}>
API
</Button>
<Button variant={'outline'} size={'sm'}> <Button variant={'outline'} size={'sm'}>
Run app Run app
</Button> </Button>
</div> </div>
</SidebarProvider> </SidebarProvider>
</div> </div>
{fileUploadVisible && (
<UploadAgentDialog
hideModal={hideFileUploadModal}
onOk={onFileUploadOk}
></UploadAgentDialog>
)}
</section> </section>
); );
} }

+ 36
- 0
web/src/pages/agent/upload-agent-dialog/index.tsx Voir le fichier

import {
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import { LoadingButton } from '@/components/ui/loading-button';
import { IModalProps } from '@/interfaces/common';
import { TagRenameId } from '@/pages/add-knowledge/constant';
import { useTranslation } from 'react-i18next';
import { UploadAgentForm } from './upload-agent-form';

export function UploadAgentDialog({
hideModal,
onOk,
loading,
}: IModalProps<any>) {
const { t } = useTranslation();

return (
<Dialog open onOpenChange={hideModal}>
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>{t('fileManager.uploadFile')}</DialogTitle>
</DialogHeader>
<UploadAgentForm hideModal={hideModal} onOk={onOk}></UploadAgentForm>
<DialogFooter>
<LoadingButton type="submit" form={TagRenameId} loading={loading}>
{t('common.save')}
</LoadingButton>
</DialogFooter>
</DialogContent>
</Dialog>
);
}

+ 91
- 0
web/src/pages/agent/upload-agent-dialog/upload-agent-form.tsx Voir le fichier

'use client';

import { zodResolver } from '@hookform/resolvers/zod';
import { useForm } from 'react-hook-form';
import { z } from 'zod';

import { FileUploader } from '@/components/file-uploader';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@/components/ui/form';
import { RAGFlowSelect } from '@/components/ui/select';
import { FileMimeType, Platform } from '@/constants/common';
import { IModalProps } from '@/interfaces/common';
import { TagRenameId } from '@/pages/add-knowledge/constant';
import { useTranslation } from 'react-i18next';

const options = Object.values(Platform).map((x) => ({ label: x, value: x }));

export function UploadAgentForm({ hideModal, onOk }: IModalProps<any>) {
const { t } = useTranslation();
const FormSchema = z.object({
platform: z
.string()
.min(1, {
message: t('common.namePlaceholder'),
})
.trim(),
fileList: z.array(z.instanceof(File)),
});

const form = useForm<z.infer<typeof FormSchema>>({
resolver: zodResolver(FormSchema),
defaultValues: { platform: Platform.RAGFlow },
});

async function onSubmit(data: z.infer<typeof FormSchema>) {
console.log('🚀 ~ onSubmit ~ data:', data);
const ret = await onOk?.(data);
if (ret) {
hideModal?.();
}
}

return (
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="space-y-6"
id={TagRenameId}
>
<FormField
control={form.control}
name="fileList"
render={({ field }) => (
<FormItem>
<FormLabel>{t('common.name')}</FormLabel>
<FormControl>
<FileUploader
value={field.value}
onValueChange={field.onChange}
maxFileCount={1}
maxSize={4 * 1024 * 1024}
accept={{ '*.json': [FileMimeType.Json] }}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="platform"
render={({ field }) => (
<FormItem>
<FormLabel>{t('common.name')}</FormLabel>
<FormControl>
<RAGFlowSelect {...field} options={options} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</form>
</Form>
);
}

Chargement…
Annuler
Enregistrer