mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-07-09 21:04:49 +08:00
refactor(ui): update ui for user settings, etc. (#13532)
### What problem does this PR solve? Update UI styles: - **User settings** - Component styles: - `ui/button.tsx` - `ui/checkbox.tsx` - `avatar-upload.tsx` - `file-uploader.tsx` - `icon-font.tsx` ### Type of change - [x] Refactoring
This commit is contained in:
@@ -318,7 +318,7 @@ export const AvatarUpload = forwardRef<HTMLInputElement, AvatarUploadProps>(
|
||||
data-testid={uploadInputTestId}
|
||||
/>
|
||||
</div>
|
||||
<div className="margin-1 text-sm text-text-secondary">
|
||||
<div className="ms-1 text-xs text-text-secondary">
|
||||
{tips ?? t('knowledgeConfiguration.photoTip')}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ function UploadForm({ submit, showParseOnCreation }: UploadFormProps) {
|
||||
data-testid="parse-on-creation-toggle"
|
||||
onCheckedChange={field.onChange}
|
||||
checked={field.value}
|
||||
></Switch>
|
||||
/>
|
||||
)}
|
||||
</RAGFlowFormItem>
|
||||
)}
|
||||
@@ -85,7 +85,7 @@ function UploadForm({ submit, showParseOnCreation }: UploadFormProps) {
|
||||
<FileUploader
|
||||
value={field.value}
|
||||
onValueChange={field.onChange}
|
||||
accept={{ '*': [] }}
|
||||
accept={{}}
|
||||
data-testid="dataset-upload-dropzone"
|
||||
/>
|
||||
)}
|
||||
@@ -124,10 +124,7 @@ export function FileUploadDialog({
|
||||
</TabsContent>
|
||||
<TabsContent value="password">{t('common.comingSoon')}</TabsContent>
|
||||
</Tabs> */}
|
||||
<UploadForm
|
||||
submit={onOk!}
|
||||
showParseOnCreation={showParseOnCreation}
|
||||
></UploadForm>
|
||||
<UploadForm submit={onOk!} showParseOnCreation={showParseOnCreation} />
|
||||
<DialogFooter>
|
||||
<ButtonLoading type="submit" loading={loading} form={UploadFormId}>
|
||||
{t('common.save')}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
'use client';
|
||||
|
||||
import { FileText, FolderUp, Upload, X } from 'lucide-react';
|
||||
import { FileText, FolderUp, LucideTrash2, Upload } from 'lucide-react';
|
||||
import * as React from 'react';
|
||||
import Dropzone, {
|
||||
type DropzoneProps,
|
||||
@@ -80,12 +80,12 @@ function FileCard({ file, progress, onRemove }: FileCardProps) {
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
variant="delete"
|
||||
size="icon"
|
||||
className="size-7"
|
||||
onClick={onRemove}
|
||||
>
|
||||
<X className="size-4" aria-hidden="true" />
|
||||
<LucideTrash2 className="size-4" aria-hidden="true" />
|
||||
<span className="sr-only">Remove file</span>
|
||||
</Button>
|
||||
</div>
|
||||
@@ -300,71 +300,67 @@ export function FileUploader(props: FileUploaderProps) {
|
||||
|
||||
const isDisabled = disabled || (files?.length ?? 0) >= maxFileCount;
|
||||
|
||||
const renderDropzone = (isFolderMode: boolean = false) => (
|
||||
<Dropzone
|
||||
onDrop={onDrop}
|
||||
accept={isFolderMode ? undefined : accept}
|
||||
maxSize={maxSize}
|
||||
maxFiles={maxFileCount}
|
||||
multiple={maxFileCount > 1 || multiple}
|
||||
disabled={isDisabled}
|
||||
noClick={isFolderMode}
|
||||
noDrag={isFolderMode}
|
||||
>
|
||||
{({ getRootProps, getInputProps, isDragActive }) => (
|
||||
<div
|
||||
{...getRootProps()}
|
||||
className={cn(
|
||||
'group relative grid h-72 w-full cursor-pointer place-items-center rounded-lg border border-dashed border-border-default px-5 py-2.5 text-center transition hover:bg-border-button bg-bg-card',
|
||||
'ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
|
||||
isDragActive && 'border-border-button',
|
||||
isDisabled && 'pointer-events-none opacity-60',
|
||||
className,
|
||||
)}
|
||||
{...dropzoneProps}
|
||||
>
|
||||
{!isFolderMode && <input {...getInputProps()} />}
|
||||
{isDragActive && !isFolderMode ? (
|
||||
<div className="flex flex-col items-center justify-center gap-4 sm:px-5">
|
||||
<div className="rounded-full border border-dashed p-3">
|
||||
<Upload
|
||||
className="size-7 text-text-secondary transition-colors group-hover:text-text-primary"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
<p className="font-medium text-text-secondary">
|
||||
{t('fileManager.dropFilesHere', 'Drop the files here')}
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
className="flex flex-col items-center justify-center gap-4 sm:px-5"
|
||||
onClick={() => {
|
||||
if (isFolderMode && !isDisabled) {
|
||||
folderInputRef.current?.click();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="rounded-full border border-dashed p-3">
|
||||
{isFolderMode ? (
|
||||
<FolderUp
|
||||
className="size-7 text-text-secondary transition-colors group-hover:text-text-primary"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
) : (
|
||||
const renderDropzone = (isFolderMode: boolean = false) => {
|
||||
const IconComponent = isFolderMode ? FolderUp : Upload;
|
||||
|
||||
return (
|
||||
<Dropzone
|
||||
onDrop={onDrop}
|
||||
accept={isFolderMode ? undefined : accept}
|
||||
maxSize={maxSize}
|
||||
maxFiles={maxFileCount}
|
||||
multiple={maxFileCount > 1 || multiple}
|
||||
disabled={isDisabled}
|
||||
noClick={isFolderMode}
|
||||
noDrag={isFolderMode}
|
||||
>
|
||||
{({ getRootProps, getInputProps, isDragActive }) => (
|
||||
<div
|
||||
{...getRootProps()}
|
||||
className={cn(
|
||||
'group relative grid h-72 w-full cursor-pointer place-items-center rounded-lg border border-dashed border-border-default',
|
||||
'px-5 py-2.5 text-center transition hover:bg-bg-card outline-none',
|
||||
'focus-visible:border-accent-primary focus-visible:bg-bg-card',
|
||||
isDragActive && 'border-border-button',
|
||||
isDisabled && 'pointer-events-none opacity-60',
|
||||
className,
|
||||
)}
|
||||
{...dropzoneProps}
|
||||
>
|
||||
{!isFolderMode && <input {...getInputProps()} />}
|
||||
{isDragActive && !isFolderMode ? (
|
||||
<div className="flex flex-col items-center justify-center gap-4 sm:px-5">
|
||||
<div>
|
||||
<Upload
|
||||
className="size-7 text-text-secondary transition-colors group-hover:text-text-primary"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<p className="font-medium text-text-secondary">
|
||||
{t('fileManager.dropFilesHere', 'Drop the files here')}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col gap-px">
|
||||
<p className="font-medium text-text-secondary ">
|
||||
) : (
|
||||
<div
|
||||
className="flex flex-col items-center justify-center gap-4 sm:px-5"
|
||||
onClick={() => {
|
||||
if (isFolderMode && !isDisabled) {
|
||||
folderInputRef.current?.click();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<IconComponent
|
||||
className="size-12 stroke-1 text-text-secondary transition-colors group-hover:text-text-primary"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
|
||||
<p className="font-medium text-text-secondary">
|
||||
{title ||
|
||||
(isFolderMode
|
||||
? t('fileManager.uploadFolderTitle', 'Upload Folder')
|
||||
: t('knowledgeDetails.uploadTitle'))}
|
||||
</p>
|
||||
|
||||
<p className="text-sm text-text-disabled">
|
||||
{description ||
|
||||
(isFolderMode
|
||||
@@ -375,12 +371,12 @@ export function FileUploader(props: FileUploaderProps) {
|
||||
: t('knowledgeDetails.uploadDescription'))}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Dropzone>
|
||||
);
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Dropzone>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative flex flex-col gap-4 overflow-hidden">
|
||||
@@ -417,7 +413,7 @@ export function FileUploader(props: FileUploaderProps) {
|
||||
)}
|
||||
|
||||
{files?.length ? (
|
||||
<div className="h-fit w-full px-3">
|
||||
<div className="h-fit w-full">
|
||||
<div className="flex max-h-48 flex-col gap-4 overflow-auto scrollbar-auto">
|
||||
{files?.map((file, index) => (
|
||||
<FileCard
|
||||
|
||||
@@ -21,14 +21,12 @@ export function IconFontFill({
|
||||
isFill = true,
|
||||
}: IconFontType & { isFill?: boolean }) {
|
||||
return (
|
||||
<span className={cn('size-4', className)}>
|
||||
<svg
|
||||
className={cn('size-4', className)}
|
||||
style={{ fill: isFill ? 'currentColor' : '' }}
|
||||
>
|
||||
<use xlinkHref={`#icon-${name}`} />
|
||||
</svg>
|
||||
</span>
|
||||
<svg
|
||||
className={cn('size-4', className)}
|
||||
style={{ fill: isFill ? 'currentColor' : '' }}
|
||||
>
|
||||
<use xlinkHref={`#icon-${name}`} />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -68,6 +68,13 @@ const buttonVariants = cva(
|
||||
hover:bg-state-error/10 focus-visible:bg-state-error/10
|
||||
`,
|
||||
|
||||
'danger-hover': `
|
||||
bg-bg-input border-border-button
|
||||
hover:bg-state-error/10 focus-visible:bg-state-error/10
|
||||
hover:text-state-error focus-visible:text-state-error
|
||||
hover:border-state-error focus-visible:border-state-error
|
||||
`,
|
||||
|
||||
// Ghost variant series
|
||||
// Button has transparent background, without borders
|
||||
ghost: `
|
||||
@@ -91,11 +98,11 @@ const buttonVariants = cva(
|
||||
size: {
|
||||
auto: '',
|
||||
|
||||
xl: 'h-12 rounded-xl px-5',
|
||||
xl: 'h-12 rounded-xl px-5 gap-3',
|
||||
lg: 'h-10 rounded-lg px-4',
|
||||
default: 'h-8 rounded px-3',
|
||||
sm: 'h-7 rounded-sm px-2',
|
||||
xs: 'h-6 rounded-xs px-1',
|
||||
sm: 'h-7 rounded-sm px-2 gap-1',
|
||||
xs: 'h-6 rounded-xs px-1 gap-0.5',
|
||||
|
||||
'icon-xl': 'size-12 rounded-xl',
|
||||
'icon-lg': 'size-10 rounded-lg',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
|
||||
import { LucideCheck } from 'lucide-react';
|
||||
import { LucideCheck, LucideMinus } from 'lucide-react';
|
||||
import * as React from 'react';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
@@ -23,7 +23,12 @@ const Checkbox = React.forwardRef<
|
||||
{...props}
|
||||
>
|
||||
<CheckboxPrimitive.Indicator className="flex items-center justify-center text-current">
|
||||
<LucideCheck className="size-2.5 stroke-[3]" />
|
||||
{props.checked === 'indeterminate' && (
|
||||
<LucideMinus className="size-2.5 stroke-[3]" />
|
||||
)}
|
||||
{props.checked === true && (
|
||||
<LucideCheck className="size-2.5 stroke-[3]" />
|
||||
)}
|
||||
</CheckboxPrimitive.Indicator>
|
||||
</CheckboxPrimitive.Root>
|
||||
));
|
||||
|
||||
@@ -41,6 +41,7 @@ export const fileIconMap = {
|
||||
xml: 'xml.svg',
|
||||
};
|
||||
|
||||
// TODO: Need to migrate to standard BCP 47 language tag
|
||||
export const LanguageList = [
|
||||
'English',
|
||||
'Chinese',
|
||||
@@ -68,7 +69,7 @@ export const LanguageMap = {
|
||||
Vietnamese: 'Tiếng việt',
|
||||
Japanese: '日本語',
|
||||
'Portuguese BR': 'Português BR',
|
||||
German: 'German',
|
||||
German: 'Deutsch',
|
||||
French: 'Français',
|
||||
Italian: 'Italiano',
|
||||
Bulgarian: 'Български',
|
||||
|
||||
@@ -360,11 +360,11 @@ Example: A 1 KB message with 1024-dim embedding uses ~9 KB. The 5 MB default lim
|
||||
filesSelected: 'Files selected',
|
||||
upload: 'Upload',
|
||||
run: 'Parse',
|
||||
runningStatus0: 'PENDING',
|
||||
runningStatus1: 'PARSING',
|
||||
runningStatus2: 'CANCELED',
|
||||
runningStatus3: 'SUCCESS',
|
||||
runningStatus4: 'FAIL',
|
||||
runningStatus0: 'Pending',
|
||||
runningStatus1: 'Parsing',
|
||||
runningStatus2: 'Cancelled',
|
||||
runningStatus3: 'Success',
|
||||
runningStatus4: 'Fail',
|
||||
pageRanges: 'Page ranges',
|
||||
pageRangesTip:
|
||||
'Range of pages to be parsed; pages outside this range will not be processed.',
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import FileStatusBadge from '@/components/file-status-badge';
|
||||
import { FilterCollection } from '@/components/list-filter-bar/interface';
|
||||
import SvgIcon from '@/components/svg-icon';
|
||||
import { useIsDarkTheme } from '@/components/theme-provider';
|
||||
@@ -192,12 +191,7 @@ const FileLogsPage: FC = () => {
|
||||
return {
|
||||
id: value,
|
||||
// label: RunningStatusMap[value].label,
|
||||
label: (
|
||||
<FileStatusBadge
|
||||
status={value as RunningStatus}
|
||||
name={RunningStatusMap[value as RunningStatus]}
|
||||
/>
|
||||
),
|
||||
label: RunningStatusMap[value],
|
||||
};
|
||||
}),
|
||||
},
|
||||
|
||||
@@ -72,9 +72,9 @@ export function SideBar({ refreshCount }: PropType) {
|
||||
}, [t, routerData]);
|
||||
|
||||
return (
|
||||
<aside className="w-64 relative px-5 space-y-8">
|
||||
<aside className="flex flex-col w-64 relative">
|
||||
<header
|
||||
className="grid grid-cols-[auto_1fr] grid-rows-[auto_auto] gap-x-3"
|
||||
className="px-5 pb-4 grid grid-cols-[auto_1fr] grid-rows-[auto_auto] gap-x-3"
|
||||
style={{
|
||||
gridTemplateAreas: '"avatar title" "avatar stats"',
|
||||
}}
|
||||
@@ -110,27 +110,31 @@ export function SideBar({ refreshCount }: PropType) {
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div className="flex flex-col gap-5">
|
||||
{items.map((item, itemIdx) => {
|
||||
const active = '/' + pathName === item.key;
|
||||
<nav className="px-5 pt-1 pb-5 overflow-y-auto">
|
||||
<ul className="space-y-5">
|
||||
{items.map((item) => {
|
||||
const active = '/' + pathName === item.key;
|
||||
|
||||
return (
|
||||
<Button
|
||||
key={itemIdx}
|
||||
asLink
|
||||
variant="ghost"
|
||||
className={cn(
|
||||
'w-full justify-start gap-2.5 px-3 relative h-10 text-base',
|
||||
active && 'bg-bg-card text-text-primary',
|
||||
)}
|
||||
to={`${Routes.DatasetBase}${item.key}/${id}`}
|
||||
>
|
||||
{item.icon}
|
||||
<span>{item.label}</span>
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
return (
|
||||
<li key={item.key}>
|
||||
<Button
|
||||
asLink
|
||||
block
|
||||
variant="ghost"
|
||||
className={cn(
|
||||
'justify-start gap-2.5 px-3 relative h-10 text-base',
|
||||
active && 'bg-bg-card text-text-primary',
|
||||
)}
|
||||
to={`${Routes.DatasetBase}${item.key}/${id}`}
|
||||
>
|
||||
{item.icon}
|
||||
<span>{item.label}</span>
|
||||
</Button>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</nav>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -78,12 +78,12 @@ export function ActionCell({
|
||||
}, [handleRemoveFile, documentId]);
|
||||
|
||||
return (
|
||||
<section className="flex gap-4 items-center text-text-sub-title-invert opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<section className="flex gap-2 items-center text-text-sub-title-invert opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
{isKnowledgeBase || (
|
||||
<Button
|
||||
variant="transparent"
|
||||
className="border-none hover:bg-bg-card text-text-primary"
|
||||
size={'sm'}
|
||||
size="icon-sm"
|
||||
onClick={handleShowConnectToKnowledgeModal}
|
||||
>
|
||||
<Link2 />
|
||||
@@ -93,7 +93,7 @@ export function ActionCell({
|
||||
<Button
|
||||
variant="transparent"
|
||||
className="border-none hover:bg-bg-card text-text-primary"
|
||||
size={'sm'}
|
||||
size="icon-sm"
|
||||
onClick={handleShowMoveFileModal}
|
||||
>
|
||||
<FolderInput />
|
||||
@@ -103,7 +103,7 @@ export function ActionCell({
|
||||
<Button
|
||||
variant="transparent"
|
||||
className="border-none hover:bg-bg-card text-text-primary"
|
||||
size={'sm'}
|
||||
size="icon-sm"
|
||||
onClick={handleShowFileRenameModal}
|
||||
>
|
||||
<FolderPen />
|
||||
@@ -113,7 +113,7 @@ export function ActionCell({
|
||||
<Button
|
||||
variant="transparent"
|
||||
className="border-none hover:bg-bg-card text-text-primary"
|
||||
size={'sm'}
|
||||
size="icon-sm"
|
||||
onClick={onDownloadDocument}
|
||||
>
|
||||
<ArrowDownToLine />
|
||||
@@ -129,7 +129,7 @@ export function ActionCell({
|
||||
<Button
|
||||
variant="transparent"
|
||||
className="border-none hover:bg-bg-card text-text-primary"
|
||||
size={'sm'}
|
||||
size="icon-sm"
|
||||
>
|
||||
<Eye />
|
||||
</Button>
|
||||
@@ -185,7 +185,7 @@ export function ActionCell({
|
||||
<Button
|
||||
variant="transparent"
|
||||
className="border-none hover:bg-bg-card text-text-primary"
|
||||
size={'sm'}
|
||||
size="icon-sm"
|
||||
>
|
||||
<Trash2 />
|
||||
</Button>
|
||||
|
||||
@@ -36,7 +36,6 @@ import {
|
||||
import { UseRowSelectionType } from '@/hooks/logic-hooks/use-row-selection';
|
||||
import { useFetchFileList } from '@/hooks/use-file-request';
|
||||
import { IFile } from '@/interfaces/database/file-manager';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { formatFileSize } from '@/utils/common-util';
|
||||
import { formatDate } from '@/utils/date';
|
||||
import { pick } from 'lodash';
|
||||
@@ -122,13 +121,18 @@ export function FilesTable({
|
||||
accessorKey: 'name',
|
||||
header: ({ column }) => {
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
||||
>
|
||||
<div className="flex items-center gap-1">
|
||||
{t('name')}
|
||||
<ArrowUpDown />
|
||||
</Button>
|
||||
<Button
|
||||
size="icon-xs"
|
||||
variant="ghost"
|
||||
onClick={() =>
|
||||
column.toggleSorting(column.getIsSorted() === 'asc')
|
||||
}
|
||||
>
|
||||
<ArrowUpDown />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
meta: { cellClassName: 'max-w-[20vw]' },
|
||||
@@ -147,21 +151,18 @@ export function FilesTable({
|
||||
return (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div className="flex gap-2">
|
||||
<span className="size-4">
|
||||
<FileIcon name={name} type={type}></FileIcon>
|
||||
</span>
|
||||
<span
|
||||
className={cn('truncate', { ['cursor-pointer']: isFolder })}
|
||||
onClick={handleNameClick}
|
||||
>
|
||||
{name}
|
||||
</span>
|
||||
</div>
|
||||
<Button
|
||||
variant="static"
|
||||
onClick={handleNameClick}
|
||||
className="max-w-full p-0 flex justify-start gap-2 text-text-primary"
|
||||
>
|
||||
<FileIcon name={name} type={type} />
|
||||
|
||||
<span className="truncate">{name}</span>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>{name}</p>
|
||||
</TooltipContent>
|
||||
|
||||
<TooltipContent>{name}</TooltipContent>
|
||||
</Tooltip>
|
||||
);
|
||||
},
|
||||
@@ -170,13 +171,18 @@ export function FilesTable({
|
||||
accessorKey: 'create_time',
|
||||
header: ({ column }) => {
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
||||
>
|
||||
<div className="flex items-center gap-1">
|
||||
{t('uploadDate')}
|
||||
<ArrowUpDown />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon-xs"
|
||||
onClick={() =>
|
||||
column.toggleSorting(column.getIsSorted() === 'asc')
|
||||
}
|
||||
>
|
||||
<ArrowUpDown />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
cell: ({ row }) => (
|
||||
@@ -189,13 +195,18 @@ export function FilesTable({
|
||||
accessorKey: 'size',
|
||||
header: ({ column }) => {
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
||||
>
|
||||
<div className="flex items-center gap-1">
|
||||
{t('size')}
|
||||
<ArrowUpDown />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon-xs"
|
||||
onClick={() =>
|
||||
column.toggleSorting(column.getIsSorted() === 'asc')
|
||||
}
|
||||
>
|
||||
<ArrowUpDown />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
cell: ({ row }) => (
|
||||
@@ -213,6 +224,9 @@ export function FilesTable({
|
||||
{
|
||||
id: 'actions',
|
||||
header: t('action'),
|
||||
meta: {
|
||||
headerCellClassName: 'w-0',
|
||||
},
|
||||
enableHiding: false,
|
||||
enablePinning: true,
|
||||
cell: ({ row }) => {
|
||||
@@ -222,7 +236,7 @@ export function FilesTable({
|
||||
showConnectToKnowledgeModal={showConnectToKnowledgeModal}
|
||||
showFileRenameModal={showFileRenameModal}
|
||||
showMoveFileModal={showMoveFileModal}
|
||||
></ActionCell>
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
@@ -271,7 +285,12 @@ export function FilesTable({
|
||||
<TableRow key={headerGroup.id}>
|
||||
{headerGroup.headers.map((header) => {
|
||||
return (
|
||||
<TableHead key={header.id}>
|
||||
<TableHead
|
||||
key={header.id}
|
||||
className={
|
||||
header.column.columnDef.meta?.headerCellClassName
|
||||
}
|
||||
>
|
||||
{header.isPlaceholder
|
||||
? null
|
||||
: flexRender(
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { useRowSelection } from '@/hooks/logic-hooks/use-row-selection';
|
||||
import { useFetchFileList } from '@/hooks/use-file-request';
|
||||
import { Upload } from 'lucide-react';
|
||||
import { LucidePlus } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { CreateFolderDialog } from './create-folder-dialog';
|
||||
import { FileBreadcrumb } from './file-breadcrumb';
|
||||
@@ -87,35 +87,38 @@ export default function Files() {
|
||||
);
|
||||
|
||||
return (
|
||||
<section className="p-8">
|
||||
<ListFilterBar
|
||||
leftPanel={leftPanel}
|
||||
searchString={searchString}
|
||||
onSearchChange={handleInputChange}
|
||||
showFilter={false}
|
||||
icon={'file'}
|
||||
>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button>
|
||||
<Upload />
|
||||
{t('knowledgeDetails.addFile')}
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="w-56">
|
||||
<DropdownMenuItem onClick={showFileUploadModal}>
|
||||
{t('fileManager.uploadFile')}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem onClick={showFolderCreateModal}>
|
||||
{t('fileManager.newFolder')}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</ListFilterBar>
|
||||
{!rowSelectionIsEmpty && (
|
||||
<BulkOperateBar list={list} count={selectedCount}></BulkOperateBar>
|
||||
)}
|
||||
<article className="p-8">
|
||||
<header>
|
||||
<ListFilterBar
|
||||
className="mb-4"
|
||||
leftPanel={leftPanel}
|
||||
searchString={searchString}
|
||||
onSearchChange={handleInputChange}
|
||||
showFilter={false}
|
||||
icon={'file'}
|
||||
>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button>
|
||||
<LucidePlus />
|
||||
{t('knowledgeDetails.addFile')}
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="w-56">
|
||||
<DropdownMenuItem onClick={showFileUploadModal}>
|
||||
{t('fileManager.uploadFile')}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem onClick={showFolderCreateModal}>
|
||||
{t('fileManager.newFolder')}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</ListFilterBar>
|
||||
{!rowSelectionIsEmpty && (
|
||||
<BulkOperateBar className="mb-4" list={list} count={selectedCount} />
|
||||
)}
|
||||
</header>
|
||||
<FilesTable
|
||||
files={files}
|
||||
total={total}
|
||||
@@ -148,6 +151,6 @@ export default function Files() {
|
||||
loading={moveFileLoading}
|
||||
></MoveDialog>
|
||||
)}
|
||||
</section>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,26 +1,7 @@
|
||||
import Spotlight from '@/components/spotlight';
|
||||
import { Card, CardContent, CardHeader } from '@/components/ui/card';
|
||||
import { PropsWithChildren } from 'react';
|
||||
|
||||
export const UserSettingHeader = ({
|
||||
name,
|
||||
description,
|
||||
}: {
|
||||
name: string;
|
||||
description?: string;
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<header className="flex flex-col gap-1.5 justify-between items-start p-0">
|
||||
<div className="text-2xl font-medium text-text-primary">{name}</div>
|
||||
{description && (
|
||||
<div className="text-sm text-text-secondary ">{description}</div>
|
||||
)}
|
||||
</header>
|
||||
{/* <Separator className="border-border-button bg-border-button h-[0.5px]" /> */}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export function Title({ children }: PropsWithChildren) {
|
||||
return <span className="font-bold text-xl">{children}</span>;
|
||||
}
|
||||
@@ -34,11 +15,17 @@ export function ProfileSettingWrapperCard({
|
||||
children,
|
||||
}: ProfileSettingWrapperCardProps) {
|
||||
return (
|
||||
<Card className="w-full border-border-button bg-transparent relative border-[0.5px]">
|
||||
<CardHeader className="border-b-[0.5px] border-border-button p-5 ">
|
||||
<Card
|
||||
as="article"
|
||||
className="relative w-full border-border-button bg-transparent border-0.5 flex flex-col"
|
||||
>
|
||||
<CardHeader className="flex-0 border-b-0.5 border-border-button p-5">
|
||||
{header}
|
||||
</CardHeader>
|
||||
<CardContent className="p-5">{children}</CardContent>
|
||||
|
||||
<CardContent className="flex-1 h-0 p-0">{children}</CardContent>
|
||||
|
||||
<Spotlight />
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,19 +1,62 @@
|
||||
import { CardTitle } from '@/components/ui/card';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import Spotlight from '@/components/spotlight';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Plus } from 'lucide-react';
|
||||
import {
|
||||
ProfileSettingWrapperCard,
|
||||
UserSettingHeader,
|
||||
} from '../components/user-setting-header';
|
||||
import { ProfileSettingWrapperCard } from '../components/user-setting-header';
|
||||
import AddDataSourceModal from './add-datasource-modal';
|
||||
import { AddedSourceCard } from './component/added-source-card';
|
||||
import { DataSourceKey, useDataSourceInfo } from './constant';
|
||||
import { useAddDataSource, useListDataSource } from './hooks';
|
||||
import { IDataSorceInfo } from './interface';
|
||||
|
||||
const AvailableSourceCard = ({
|
||||
name,
|
||||
description,
|
||||
icon,
|
||||
onAdd,
|
||||
}: IDataSorceInfo & { onAdd: () => void }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<article
|
||||
className="
|
||||
size-full p-2.5 border-0.5 border-border-button rounded-lg relative group hover:bg-bg-card focus-within:bg-bg-card
|
||||
grid grid-cols-[auto_1fr] grid-rows-[auto_1fr] gap-x-2.5 gap-y-1"
|
||||
style={{
|
||||
gridTemplateAreas: '"icon title" "icon description"',
|
||||
}}
|
||||
onClick={() => onAdd()}
|
||||
>
|
||||
<span className="w-6" style={{ gridArea: 'icon' }}>
|
||||
{icon}
|
||||
</span>
|
||||
|
||||
<header className="flex items-center gap-2" style={{ gridArea: 'title' }}>
|
||||
<h3 className="text-base text-text-primary">{name}</h3>
|
||||
|
||||
<Button
|
||||
size="auto"
|
||||
className="ml-auto px-1 py-0.5 gap-0.5 text-xs items-center opacity-0 transition-all group-hover:opacity-100 group-focus-within:opacity-100"
|
||||
onClick={(e: any) => {
|
||||
e.stopPropagation();
|
||||
onAdd();
|
||||
}}
|
||||
>
|
||||
<Plus className="size-[1em]" />
|
||||
{t('setting.add')}
|
||||
</Button>
|
||||
</header>
|
||||
|
||||
<p
|
||||
style={{ gridArea: 'description' }}
|
||||
className="text-xs text-text-secondary"
|
||||
>
|
||||
{description}
|
||||
</p>
|
||||
</article>
|
||||
);
|
||||
};
|
||||
|
||||
const DataSource = () => {
|
||||
const { t } = useTranslation();
|
||||
const { dataSourceInfo } = useDataSourceInfo();
|
||||
@@ -38,97 +81,68 @@ const DataSource = () => {
|
||||
showAddingModal,
|
||||
} = useAddDataSource();
|
||||
|
||||
const AbailableSourceCard = ({
|
||||
id,
|
||||
name,
|
||||
description,
|
||||
icon,
|
||||
}: IDataSorceInfo) => {
|
||||
return (
|
||||
<div
|
||||
className="p-[10px] border border-border-button rounded-lg relative group hover:bg-bg-card"
|
||||
onClick={() =>
|
||||
showAddingModal({
|
||||
id,
|
||||
name,
|
||||
description,
|
||||
icon,
|
||||
})
|
||||
}
|
||||
>
|
||||
<div className="flex gap-2">
|
||||
<div className="w-6 h-6">{icon}</div>
|
||||
<div className="flex flex-1 flex-col items-start gap-2">
|
||||
<div className="text-base text-text-primary">{name}</div>
|
||||
<div className="text-xs text-text-secondary">{description}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className=" absolute top-2 right-2">
|
||||
<Button className=" rounded-md px-1 text-bg-base gap-1 bg-text-primary text-xs py-0 h-6 items-center hidden group-hover:flex">
|
||||
<Plus size={12} />
|
||||
{t('setting.add')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<ProfileSettingWrapperCard
|
||||
header={
|
||||
<UserSettingHeader
|
||||
name={t('setting.dataSources')}
|
||||
description={t('setting.datasourceDescription')}
|
||||
/>
|
||||
<header>
|
||||
<h2 className="text-2xl font-medium text-text-primary">
|
||||
{t('setting.dataSources')}
|
||||
</h2>
|
||||
<p className="mt-1 text-sm text-text-secondary ">
|
||||
{t('setting.datasourceDescription')}
|
||||
</p>
|
||||
</header>
|
||||
}
|
||||
>
|
||||
<Spotlight />
|
||||
<div className="relative">
|
||||
<div className=" flex flex-col gap-4 max-h-[calc(100vh-235px)] overflow-y-auto overflow-x-hidden scrollbar-auto">
|
||||
<div className="flex flex-col gap-3">
|
||||
{categorizedList?.length <= 0 && (
|
||||
<div className="text-text-secondary w-full flex justify-center items-center h-20">
|
||||
{t('setting.sourceEmptyTip')}
|
||||
</div>
|
||||
)}
|
||||
{categorizedList.map((item, index) => (
|
||||
<AddedSourceCard key={index} {...item} />
|
||||
))}
|
||||
</div>
|
||||
<section className="bg-transparent border-none mt-8">
|
||||
<header className="flex flex-row items-center justify-between space-y-0 p-0 pb-4">
|
||||
{/* <Users className="mr-2 h-5 w-5 text-[#1677ff]" /> */}
|
||||
<CardTitle className="text-2xl font-semibold ">
|
||||
{t('setting.availableSources')}
|
||||
<div className="text-sm text-text-secondary font-normal mt-1.5">
|
||||
{t('setting.availableSourcesDescription')}
|
||||
</div>
|
||||
</CardTitle>
|
||||
</header>
|
||||
<main className="p-0">
|
||||
{/* <TenantTable searchTerm={searchTerm}></TenantTable> */}
|
||||
<div className="grid sm:grid-cols-1 lg:grid-cols-2 xl:grid-cols-2 2xl:grid-cols-4 3xl:grid-cols-4 gap-4">
|
||||
{dataSourceTemplates.map((item, index) => (
|
||||
<AbailableSourceCard {...item} key={index} />
|
||||
))}
|
||||
</div>
|
||||
</main>
|
||||
</section>
|
||||
</div>
|
||||
<div className="h-full p-5 overflow-x-hidden overflow-y-auto">
|
||||
<section className="flex flex-col gap-3">
|
||||
{categorizedList?.length <= 0 && (
|
||||
<div className="text-text-secondary w-full flex justify-center items-center h-20">
|
||||
{t('setting.sourceEmptyTip')}
|
||||
</div>
|
||||
)}
|
||||
{categorizedList.map((item, index) => (
|
||||
<AddedSourceCard key={index} {...item} />
|
||||
))}
|
||||
</section>
|
||||
|
||||
{addingModalVisible && (
|
||||
<AddDataSourceModal
|
||||
visible
|
||||
loading={addLoading}
|
||||
hideModal={hideAddingModal}
|
||||
onOk={(data) => {
|
||||
console.log(data);
|
||||
handleAddOk(data);
|
||||
}}
|
||||
sourceData={addSource}
|
||||
></AddDataSourceModal>
|
||||
)}
|
||||
<section className="mt-8">
|
||||
<header className="flex flex-row items-center justify-between space-y-0 p-0 pb-4">
|
||||
{/* <Users className="mr-2 h-5 w-5 text-[#1677ff]" /> */}
|
||||
<h2 className="text-2xl font-medium">
|
||||
{t('setting.availableSources')}
|
||||
<div className="text-sm text-text-secondary font-normal mt-1.5">
|
||||
{t('setting.availableSourcesDescription')}
|
||||
</div>
|
||||
</h2>
|
||||
</header>
|
||||
|
||||
{/* <TenantTable searchTerm={searchTerm}></TenantTable> */}
|
||||
<ul className="@container grid sm:grid-cols-1 lg:grid-cols-2 xl:grid-cols-2 2xl:grid-cols-4 3xl:grid-cols-4 gap-4">
|
||||
{dataSourceTemplates.map((item) => (
|
||||
<li key={item.id} className="h-full">
|
||||
<AvailableSourceCard
|
||||
{...item}
|
||||
onAdd={() => showAddingModal(item)}
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
{addingModalVisible && (
|
||||
<AddDataSourceModal
|
||||
visible
|
||||
loading={addLoading}
|
||||
hideModal={hideAddingModal}
|
||||
onOk={(data) => {
|
||||
console.log(data);
|
||||
handleAddOk(data);
|
||||
}}
|
||||
sourceData={addSource}
|
||||
/>
|
||||
)}
|
||||
</ProfileSettingWrapperCard>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -196,7 +196,7 @@ export function EditMcpDialog({
|
||||
form={form}
|
||||
setFieldChanged={setFieldChanged}
|
||||
></EditMcpForm>
|
||||
<Card className="bg-transparent">
|
||||
<Card className="bg-transparent mt-4">
|
||||
<CardContent className="p-3">
|
||||
<Collapse
|
||||
title={
|
||||
|
||||
@@ -3,7 +3,6 @@ import {
|
||||
ConfirmDeleteDialog,
|
||||
ConfirmDeleteDialogNode,
|
||||
} from '@/components/confirm-delete-dialog';
|
||||
import Spotlight from '@/components/spotlight';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import { SearchInput } from '@/components/ui/input';
|
||||
@@ -61,112 +60,119 @@ export default function McpServer() {
|
||||
return (
|
||||
<ProfileSettingWrapperCard
|
||||
header={
|
||||
<>
|
||||
<div className="text-text-primary text-2xl font-semibold">
|
||||
{t('mcp.mcpServers')}
|
||||
</div>
|
||||
<section className="flex items-center justify-between">
|
||||
<div className="text-text-secondary">
|
||||
<header className="flex flex-row gap-1.5 justify-between items-end">
|
||||
<div>
|
||||
<h2 className="text-text-primary text-2xl font-medium">
|
||||
{t('mcp.mcpServers')}
|
||||
</h2>
|
||||
|
||||
<p className="mt-1 text-text-secondary text-sm">
|
||||
{t('mcp.customizeTheListOfMcpServers')}
|
||||
</div>
|
||||
<div className="flex gap-5">
|
||||
<SearchInput
|
||||
className="w-40"
|
||||
value={searchString}
|
||||
onChange={handleInputChange}
|
||||
placeholder={t('common.search')}
|
||||
></SearchInput>
|
||||
<Button variant={'secondary'} onClick={switchSelectionMode}>
|
||||
{isSelectionMode ? (
|
||||
<ListChecks className="size-3.5" />
|
||||
) : (
|
||||
<LayoutList className="size-3.5" />
|
||||
)}
|
||||
{t(`mcp.${isSelectionMode ? 'exitBulkManage' : 'bulkManage'}`)}
|
||||
</Button>
|
||||
<Button variant={'secondary'} onClick={showImportModal}>
|
||||
<Download className="size-3.5" />
|
||||
{t('mcp.import')}
|
||||
</Button>
|
||||
<Button onClick={showEditModal('')}>
|
||||
<Plus className="size-3.5 font-medium" /> {t('mcp.addMCP')}
|
||||
</Button>
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-4" role="toolbar">
|
||||
<SearchInput
|
||||
className="w-40"
|
||||
value={searchString}
|
||||
onChange={handleInputChange}
|
||||
placeholder={t('common.search')}
|
||||
/>
|
||||
<Button variant="outline" onClick={switchSelectionMode}>
|
||||
{isSelectionMode ? (
|
||||
<ListChecks className="size-3.5" />
|
||||
) : (
|
||||
<LayoutList className="size-3.5" />
|
||||
)}
|
||||
{t(`mcp.${isSelectionMode ? 'exitBulkManage' : 'bulkManage'}`)}
|
||||
</Button>
|
||||
<Button variant="outline" onClick={showImportModal}>
|
||||
<Download className="size-3.5" />
|
||||
{t('mcp.import')}
|
||||
</Button>
|
||||
<Button onClick={showEditModal('')}>
|
||||
<Plus /> {t('mcp.addMCP')}
|
||||
</Button>
|
||||
</div>
|
||||
</header>
|
||||
}
|
||||
>
|
||||
{!data.mcp_servers?.length && (
|
||||
<div
|
||||
className="flex items-center justify-between border border-dashed border-border-button rounded-md p-10 cursor-pointer w-[590px]"
|
||||
onClick={showEditModal('')}
|
||||
>
|
||||
<div className="text-text-secondary text-sm">{t('empty.noMCP')}</div>
|
||||
<Button variant={'ghost'} className="border border-border-button">
|
||||
<Plus className="size-3.5 font-medium" /> {t('empty.addNow')}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{!!data.mcp_servers?.length && (
|
||||
<>
|
||||
{isSelectionMode && (
|
||||
<section className="pb-5 flex items-center">
|
||||
<Checkbox id="all" onCheckedChange={handleSelectAll} />
|
||||
<Label
|
||||
className="pl-2 text-text-primary cursor-pointer"
|
||||
htmlFor="all"
|
||||
>
|
||||
{t('common.selectAll')}
|
||||
</Label>
|
||||
<span className="text-text-secondary pr-10 pl-5">
|
||||
{t('mcp.selected')} {selectedList.length}
|
||||
</span>
|
||||
<div className="flex gap-10 items-center">
|
||||
<Button variant={'secondary'} onClick={handleExportMcp}>
|
||||
<Upload className="size-3.5"></Upload>
|
||||
{t('mcp.export')}
|
||||
</Button>
|
||||
<ConfirmDeleteDialog
|
||||
onOk={handleDelete}
|
||||
title={t('common.delete') + ' ' + t('mcp.mcpServers')}
|
||||
content={{
|
||||
title: t('common.deleteThem'),
|
||||
node: (
|
||||
<ConfirmDeleteDialogNode
|
||||
name={`${t('mcp.selected')} ${selectedList.length} ${t('mcp.mcpServers')}`}
|
||||
></ConfirmDeleteDialogNode>
|
||||
),
|
||||
}}
|
||||
<div className="h-full p-5 overflow-x-hidden overflow-y-auto">
|
||||
{data.mcp_servers?.length ? (
|
||||
<>
|
||||
{isSelectionMode && (
|
||||
<section className="pb-5 flex items-center">
|
||||
<Checkbox id="all" onCheckedChange={handleSelectAll} />
|
||||
<Label
|
||||
className="pl-2 text-text-primary cursor-pointer"
|
||||
htmlFor="all"
|
||||
>
|
||||
<Button variant={'danger'}>
|
||||
<Trash2 className="size-3.5 cursor-pointer" />
|
||||
{t('common.delete')}
|
||||
{t('common.selectAll')}
|
||||
</Label>
|
||||
<span className="text-text-secondary pr-10 pl-5">
|
||||
{t('mcp.selected')} {selectedList.length}
|
||||
</span>
|
||||
<div className="flex gap-10 items-center">
|
||||
<Button variant={'secondary'} onClick={handleExportMcp}>
|
||||
<Upload className="size-3.5"></Upload>
|
||||
{t('mcp.export')}
|
||||
</Button>
|
||||
</ConfirmDeleteDialog>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
<CardContainer>
|
||||
{data.mcp_servers.map((item) => (
|
||||
<McpCard
|
||||
key={item.id}
|
||||
data={item}
|
||||
selectedList={selectedList}
|
||||
handleSelectChange={handleSelectChange}
|
||||
showEditModal={showEditModal}
|
||||
isSelectionMode={isSelectionMode}
|
||||
></McpCard>
|
||||
))}
|
||||
</CardContainer>
|
||||
<div className="mt-8">
|
||||
<RAGFlowPagination
|
||||
{...pick(pagination, 'current', 'pageSize')}
|
||||
total={pagination.total || 0}
|
||||
onChange={handlePageChange}
|
||||
></RAGFlowPagination>
|
||||
<ConfirmDeleteDialog
|
||||
onOk={handleDelete}
|
||||
title={t('common.delete') + ' ' + t('mcp.mcpServers')}
|
||||
content={{
|
||||
title: t('common.deleteThem'),
|
||||
node: (
|
||||
<ConfirmDeleteDialogNode
|
||||
name={`${t('mcp.selected')} ${selectedList.length} ${t('mcp.mcpServers')}`}
|
||||
></ConfirmDeleteDialogNode>
|
||||
),
|
||||
}}
|
||||
>
|
||||
<Button variant={'danger'}>
|
||||
<Trash2 className="size-3.5 cursor-pointer" />
|
||||
{t('common.delete')}
|
||||
</Button>
|
||||
</ConfirmDeleteDialog>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
<CardContainer>
|
||||
{data.mcp_servers.map((item) => (
|
||||
<McpCard
|
||||
key={item.id}
|
||||
data={item}
|
||||
selectedList={selectedList}
|
||||
handleSelectChange={handleSelectChange}
|
||||
showEditModal={showEditModal}
|
||||
isSelectionMode={isSelectionMode}
|
||||
></McpCard>
|
||||
))}
|
||||
</CardContainer>
|
||||
<div className="mt-8">
|
||||
<RAGFlowPagination
|
||||
{...pick(pagination, 'current', 'pageSize')}
|
||||
total={pagination.total || 0}
|
||||
onChange={handlePageChange}
|
||||
></RAGFlowPagination>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div
|
||||
className="flex items-center justify-between border border-dashed border-border-button rounded-md p-10 cursor-pointer w-[590px]"
|
||||
onClick={showEditModal('')}
|
||||
>
|
||||
<div className="text-text-secondary text-sm">
|
||||
{t('empty.noMCP')}
|
||||
</div>
|
||||
<Button variant="outline">
|
||||
<Plus />
|
||||
{t('empty.addNow')}
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
)}
|
||||
</div>
|
||||
|
||||
{editVisible && (
|
||||
<EditMcpDialog
|
||||
hideModal={hideEditModal}
|
||||
@@ -181,7 +187,6 @@ export default function McpServer() {
|
||||
onOk={onImportOk}
|
||||
></ImportMcpDialog>
|
||||
)}
|
||||
<Spotlight />
|
||||
</ProfileSettingWrapperCard>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -22,10 +22,7 @@ import { Loader2Icon, PenLine } from 'lucide-react';
|
||||
import { FC, useEffect } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
import {
|
||||
ProfileSettingWrapperCard,
|
||||
UserSettingHeader,
|
||||
} from '../components/user-setting-header';
|
||||
import { ProfileSettingWrapperCard } from '../components/user-setting-header';
|
||||
import { EditType, modalTitle, useProfile } from './hooks/use-profile';
|
||||
|
||||
const baseSchema = z.object({
|
||||
@@ -123,10 +120,14 @@ const ProfilePage: FC = () => {
|
||||
// <div className="h-full w-full text-text-secondary relative flex flex-col gap-4">
|
||||
<ProfileSettingWrapperCard
|
||||
header={
|
||||
<UserSettingHeader
|
||||
name={t('profile')}
|
||||
description={t('profileDescription')}
|
||||
/>
|
||||
<header>
|
||||
<h2 className="text-2xl font-medium text-text-primary">
|
||||
{t('profile')}
|
||||
</h2>
|
||||
<p className="mt-1 text-sm text-text-secondary ">
|
||||
{t('profileDescription')}
|
||||
</p>
|
||||
</header>
|
||||
}
|
||||
>
|
||||
<Spotlight />
|
||||
@@ -142,11 +143,11 @@ const ProfilePage: FC = () => {
|
||||
<div className="text-sm text-text-primary border border-border-button flex-1 rounded-md py-1.5 px-2">
|
||||
{profile.userName}
|
||||
</div>
|
||||
|
||||
<Button
|
||||
variant={'ghost'}
|
||||
variant="outline"
|
||||
type="button"
|
||||
onClick={() => handleEditClick(EditType.editName)}
|
||||
className="text-sm text-text-secondary flex gap-1 px-1 border border-border-button"
|
||||
>
|
||||
<PenLine size={12} /> {t('edit')}
|
||||
</Button>
|
||||
@@ -175,10 +176,9 @@ const ProfilePage: FC = () => {
|
||||
{profile.timeZone}
|
||||
</div>
|
||||
<Button
|
||||
variant={'ghost'}
|
||||
variant="outline"
|
||||
type="button"
|
||||
onClick={() => handleEditClick(EditType.editTimeZone)}
|
||||
className="text-sm text-text-secondary flex gap-1 px-1 border border-border-button"
|
||||
>
|
||||
<PenLine size={12} /> {t('edit')}
|
||||
</Button>
|
||||
@@ -208,10 +208,9 @@ const ProfilePage: FC = () => {
|
||||
{profile.currPasswd ? '********' : ''}
|
||||
</div>
|
||||
<Button
|
||||
variant={'ghost'}
|
||||
variant="outline"
|
||||
type="button"
|
||||
onClick={() => handleEditClick(EditType.editPassword)}
|
||||
className="text-sm text-text-secondary flex gap-1 px-1 border border-border-button"
|
||||
>
|
||||
<PenLine size={12} /> {t('edit')}
|
||||
</Button>
|
||||
|
||||
@@ -93,26 +93,22 @@ export const ModelProviderCard: FC<IModelCardProps> = ({
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button
|
||||
variant={'ghost'}
|
||||
variant="outline"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleApiKeyClick();
|
||||
}}
|
||||
className="px-3 py-1 text-sm rounded-md transition-colors flex items-center space-x-1 border border-border-default"
|
||||
>
|
||||
<SettingOutlined />
|
||||
<span>
|
||||
{isLocalLlmFactory(item.name) ? t('addTheModel') : 'API-Key'}
|
||||
</span>
|
||||
{isLocalLlmFactory(item.name) ? t('addTheModel') : 'API-Key'}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant={'ghost'}
|
||||
variant="outline"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleShowMoreClick();
|
||||
}}
|
||||
className="px-3 py-1 text-sm rounded-md transition-colors flex items-center space-x-1 border border-border-default"
|
||||
>
|
||||
<span>{visible ? t('hideModels') : t('showMoreModels')}</span>
|
||||
{!visible ? <ChevronsDown /> : <ChevronsUp />}
|
||||
@@ -133,12 +129,12 @@ export const ModelProviderCard: FC<IModelCardProps> = ({
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
variant={'ghost'}
|
||||
size="icon"
|
||||
variant="danger-hover"
|
||||
// onClick={(e) => {
|
||||
// e.stopPropagation();
|
||||
// handleDeleteFactory(item);
|
||||
// }}
|
||||
className=" hover:text-state-error hover:bg-state-error-5 transition-colors border border-border-default"
|
||||
>
|
||||
<Trash2 />
|
||||
</Button>
|
||||
@@ -161,9 +157,9 @@ export const ModelProviderCard: FC<IModelCardProps> = ({
|
||||
))}
|
||||
</div>
|
||||
<div className="m-4 bg-bg-card rounded-lg max-h-96 overflow-auto scrollbar-auto">
|
||||
<div className="">
|
||||
<ul>
|
||||
{item.llm.map((model) => (
|
||||
<div
|
||||
<li
|
||||
key={model.name}
|
||||
className="flex items-center border-b-[0.5px] border-border-button justify-between p-3 hover:bg-bg-card transition-colors"
|
||||
>
|
||||
@@ -176,36 +172,38 @@ export const ModelProviderCard: FC<IModelCardProps> = ({
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="flex items-center gap-3">
|
||||
{isLocalLlmFactory(item.name) && (
|
||||
<Button
|
||||
variant={'secondary'}
|
||||
variant="secondary"
|
||||
size="icon-sm"
|
||||
onClick={() => handleEditModel(model, item)}
|
||||
className="p-1 text-text-primary transition-colors"
|
||||
>
|
||||
<EditOutlined />
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Switch
|
||||
checked={model.status === '1'}
|
||||
onCheckedChange={(value) => {
|
||||
handleEnableLlm(model.name, value);
|
||||
}}
|
||||
/>
|
||||
|
||||
<Button
|
||||
variant={'ghost'}
|
||||
size="icon-sm"
|
||||
variant="danger-hover"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleDeleteLlm(model.name);
|
||||
}}
|
||||
className="p-1 hover:text-state-error hover:bg-state-error-5 transition-colors border-none"
|
||||
>
|
||||
<Trash2 size={16} />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</div>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -173,13 +173,16 @@ const SystemSetting = ({ onOk, loading }: IProps) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="rounded-lg w-full">
|
||||
<div className="flex flex-col py-4">
|
||||
<div className="text-2xl font-medium">{t('systemModelSettings')}</div>
|
||||
<div className="text-sm text-text-secondary">
|
||||
<article className="rounded-lg w-full">
|
||||
<header className="py-5">
|
||||
<h2 className="text-2xl font-medium text-text-primary">
|
||||
{t('systemModelSettings')}
|
||||
</h2>
|
||||
<p className="mt-1 text-sm text-text-secondary ">
|
||||
{t('systemModelDescription')}
|
||||
</div>
|
||||
</div>
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<div className="px-7 py-6 space-y-6 max-h-[70vh] overflow-y-auto border border-border-button rounded-lg">
|
||||
{llmList.map((item) => (
|
||||
<Items key={item.id} {...item} />
|
||||
@@ -194,7 +197,7 @@ const SystemSetting = ({ onOk, loading }: IProps) => {
|
||||
{t('common:cancel')}
|
||||
</Button>
|
||||
</div> */}
|
||||
</div>
|
||||
</article>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -79,97 +79,97 @@ export const AvailableModels: FC<{
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className=" text-text-primary h-full p-4"
|
||||
<aside
|
||||
className="text-text-primary h-full flex flex-col"
|
||||
data-testid="available-models-section"
|
||||
>
|
||||
<div className="text-text-primary text-base mb-4">
|
||||
{t('availableModels')}
|
||||
</div>
|
||||
{/* Search Bar */}
|
||||
<div className="mb-6">
|
||||
{/* <div className="relative"> */}
|
||||
<SearchInput
|
||||
data-testid="model-providers-search"
|
||||
type="text"
|
||||
placeholder={t('search')}
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="w-full px-4 py-2 pl-10 bg-bg-input border border-border-default rounded-lg focus:outline-none focus:ring-1 focus:ring-border-button transition-colors"
|
||||
/>
|
||||
{/* <Search className="absolute left-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-text-secondary" /> */}
|
||||
{/* </div> */}
|
||||
</div>
|
||||
<header className="p-4 space-y-3">
|
||||
<h3 className="text-text-primary text-base">{t('availableModels')}</h3>
|
||||
{/* Search Bar */}
|
||||
<div>
|
||||
{/* <div className="relative"> */}
|
||||
<SearchInput
|
||||
data-testid="model-providers-search"
|
||||
type="text"
|
||||
placeholder={t('search')}
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="w-full px-4 py-2 pl-10 bg-bg-input border border-border-default rounded-lg focus:outline-none focus:ring-1 focus:ring-border-button transition-colors"
|
||||
/>
|
||||
{/* <Search className="absolute left-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-text-secondary" /> */}
|
||||
{/* </div> */}
|
||||
</div>
|
||||
|
||||
{/* Tags Filter */}
|
||||
<div className="flex flex-wrap gap-2 mb-6">
|
||||
<Button
|
||||
variant={'secondary'}
|
||||
onClick={() => setSelectedTag(null)}
|
||||
className={`px-1 py-1 text-xs rounded-sm bg-bg-card h-5 transition-colors ${
|
||||
selectedTag === null
|
||||
? ' text-bg-base bg-text-primary '
|
||||
: 'text-text-secondary bg-bg-card border-none'
|
||||
}`}
|
||||
>
|
||||
All
|
||||
</Button>
|
||||
{allTags.map((tag) => (
|
||||
{/* Tags Filter */}
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Button
|
||||
variant={'secondary'}
|
||||
key={tag}
|
||||
onClick={() => handleTagClick(tag)}
|
||||
className={`px-1 py-1 text-xs rounded-sm bg-bg-card h-5 transition-colors ${
|
||||
selectedTag === tag
|
||||
? ' text-bg-base bg-text-primary '
|
||||
: 'text-text-secondary border-none bg-bg-card'
|
||||
}`}
|
||||
variant={selectedTag === null ? 'default' : 'secondary'}
|
||||
size="auto"
|
||||
className="leading-none px-1 py-0.5 rounded-sm text-xs"
|
||||
onClick={() => setSelectedTag(null)}
|
||||
>
|
||||
{mapModelKey[tag.trim() as keyof typeof mapModelKey] || tag.trim()}
|
||||
All
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{allTags.map((tag) => (
|
||||
<Button
|
||||
variant={selectedTag === tag ? 'default' : 'secondary'}
|
||||
size="auto"
|
||||
className="leading-none px-1 py-0.5 rounded-sm text-xs"
|
||||
key={tag}
|
||||
onClick={() => handleTagClick(tag)}
|
||||
>
|
||||
{mapModelKey[tag.trim() as keyof typeof mapModelKey] ||
|
||||
tag.trim()}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Models List */}
|
||||
<div className="flex flex-col gap-4 overflow-auto h-[calc(100vh-300px)] scrollbar-auto">
|
||||
<div className="p-4 pt-0 flex flex-col gap-4 overflow-auto h-full scrollbar-auto">
|
||||
{filteredModels.map((model) => (
|
||||
<div
|
||||
key={model.name}
|
||||
data-testid="available-model-card"
|
||||
data-provider={model.name}
|
||||
className=" border border-border-button rounded-lg p-3 hover:bg-bg-input transition-colors group"
|
||||
className="group border border-border-button rounded-lg p-3 hover:bg-bg-input transition-colors"
|
||||
onClick={() => handleAddModel(model.name)}
|
||||
>
|
||||
<div className="flex items-center space-x-3 mb-3">
|
||||
<LlmIcon name={model.name} imgClass="h-8 w-8 text-text-primary" />
|
||||
<div className="flex flex-1 gap-1 items-center">
|
||||
<div className="flex flex-1 gap-1.5 items-center">
|
||||
<div className="font-normal text-base truncate">
|
||||
{model.name}
|
||||
</div>
|
||||
{!!APIMapUrl[model.name as keyof typeof APIMapUrl] && (
|
||||
<Button
|
||||
variant={'ghost'}
|
||||
className=" bg-transparent w-4 h-5"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
window.open(
|
||||
APIMapUrl[model.name as keyof typeof APIMapUrl],
|
||||
);
|
||||
}}
|
||||
// target="_blank"
|
||||
asLink
|
||||
variant="ghost"
|
||||
size="icon-xs"
|
||||
className="size-4"
|
||||
to={APIMapUrl[model.name as keyof typeof APIMapUrl]}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
onClick={(e: React.MouseEvent<HTMLButtonElement>) =>
|
||||
e.stopPropagation()
|
||||
}
|
||||
>
|
||||
<ArrowUpRight size={16} />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<Button className=" px-2 items-center gap-0 text-xs h-6 rounded-md transition-colors hidden group-hover:flex">
|
||||
|
||||
<Button
|
||||
size="xs"
|
||||
className="px-2 opacity-0 transition-all group-hover:opacity-100 group-focus-within:opacity-100"
|
||||
>
|
||||
<Plus size={12} />
|
||||
{t('addTheModel')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap gap-1 mb-3">
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{sortTags(model.tags).map((tag, index) => (
|
||||
<span
|
||||
key={index}
|
||||
@@ -184,6 +184,6 @@ export const AvailableModels: FC<{
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -10,10 +10,7 @@ import Spotlight from '@/components/spotlight';
|
||||
import { SearchInput } from '@/components/ui/input';
|
||||
import { UserPlus } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
import {
|
||||
ProfileSettingWrapperCard,
|
||||
UserSettingHeader,
|
||||
} from '../components/user-setting-header';
|
||||
import { ProfileSettingWrapperCard } from '../components/user-setting-header';
|
||||
import AddingUserModal from './add-user-modal';
|
||||
import { useAddUser } from './hooks';
|
||||
import TenantTable from './tenant-table';
|
||||
@@ -40,53 +37,60 @@ const UserSettingTeam = () => {
|
||||
// />
|
||||
<ProfileSettingWrapperCard
|
||||
header={
|
||||
<UserSettingHeader
|
||||
name={userInfo?.nickname + ' ' + t('setting.workspace')}
|
||||
/>
|
||||
<header>
|
||||
<h2 className="text-2xl font-medium text-text-primary">
|
||||
{userInfo?.nickname + ' ' + t('setting.workspace')}
|
||||
</h2>
|
||||
</header>
|
||||
}
|
||||
>
|
||||
<Spotlight />
|
||||
<Card className="bg-transparent border-none">
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 p-4 pb-4">
|
||||
{/* <User className="mr-2 h-5 w-5 text-[#1677ff]" /> */}
|
||||
<CardTitle className="text-base">
|
||||
{t('setting.teamMembers')}
|
||||
</CardTitle>
|
||||
<section className="flex gap-4 items-center">
|
||||
|
||||
<div className="h-full overflow-x-hidden overflow-y-auto">
|
||||
<Card className="bg-transparent border-none">
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 p-4">
|
||||
{/* <User className="mr-2 h-5 w-5 text-[#1677ff]" /> */}
|
||||
<CardTitle className="text-base">
|
||||
{t('setting.teamMembers')}
|
||||
</CardTitle>
|
||||
|
||||
<section className="flex gap-4 items-center">
|
||||
<SearchInput
|
||||
className="bg-bg-input border-border-default w-32"
|
||||
placeholder={t('common.search')}
|
||||
value={searchUser}
|
||||
onChange={(e) => setSearchUser(e.target.value)}
|
||||
/>
|
||||
<Button onClick={showAddingTenantModal}>
|
||||
<UserPlus className=" h-4 w-4" />
|
||||
{t('setting.invite')}
|
||||
</Button>
|
||||
</section>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent className="p-4 pt-0">
|
||||
<UserTable searchUser={searchUser}></UserTable>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="bg-transparent border-none mt-8">
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 p-4">
|
||||
{/* <Users className="mr-2 h-5 w-5 text-[#1677ff]" /> */}
|
||||
<CardTitle className="text-base w-fit">
|
||||
{t('setting.joinedTeams')}
|
||||
</CardTitle>
|
||||
<SearchInput
|
||||
className="bg-bg-input border-border-default w-32"
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
placeholder={t('common.search')}
|
||||
value={searchUser}
|
||||
onChange={(e) => setSearchUser(e.target.value)}
|
||||
/>
|
||||
<Button onClick={showAddingTenantModal}>
|
||||
<UserPlus className=" h-4 w-4" />
|
||||
{t('setting.invite')}
|
||||
</Button>
|
||||
</section>
|
||||
</CardHeader>
|
||||
<CardContent className="p-4">
|
||||
<UserTable searchUser={searchUser}></UserTable>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="bg-transparent border-none mt-8">
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 p-4 pb-4">
|
||||
{/* <Users className="mr-2 h-5 w-5 text-[#1677ff]" /> */}
|
||||
<CardTitle className="text-base w-fit">
|
||||
{t('setting.joinedTeams')}
|
||||
</CardTitle>
|
||||
<SearchInput
|
||||
className="bg-bg-input border-border-default w-32"
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
placeholder={t('common.search')}
|
||||
/>
|
||||
</CardHeader>
|
||||
<CardContent className="p-4">
|
||||
<TenantTable searchTerm={searchTerm}></TenantTable>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</CardHeader>
|
||||
<CardContent className="p-4 pt-0">
|
||||
<TenantTable searchTerm={searchTerm}></TenantTable>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{addingTenantModalVisible && (
|
||||
<AddingUserModal
|
||||
|
||||
@@ -63,11 +63,11 @@ const TenantTable = ({ searchTerm }: { searchTerm: string }) => {
|
||||
|
||||
const renderSortIcon = () => {
|
||||
if (sortOrder === 'asc') {
|
||||
return <ArrowUp className="ml-1 h-4 w-4" />;
|
||||
return <ArrowUp className="size-[1em]" />;
|
||||
} else if (sortOrder === 'desc') {
|
||||
return <ArrowDown className="ml-1 h-4 w-4" />;
|
||||
return <ArrowDown className="size-[1em]" />;
|
||||
} else {
|
||||
return <ArrowUpDown className="ml-1 h-4 w-4" />;
|
||||
return <ArrowUpDown className="size-[1em]" />;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -77,13 +77,16 @@ const TenantTable = ({ searchTerm }: { searchTerm: string }) => {
|
||||
<TableHeader className="bg-bg-title">
|
||||
<TableRow className="hover:bg-bg-title">
|
||||
<TableHead className="h-12 px-4">{t('common.name')}</TableHead>
|
||||
<TableHead
|
||||
className="h-12 px-4 cursor-pointer"
|
||||
onClick={toggleSortOrder}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<TableHead className="h-12 px-4">
|
||||
<div className="flex items-center gap-1">
|
||||
{t('setting.updateDate')}
|
||||
{renderSortIcon()}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon-xs"
|
||||
onClick={toggleSortOrder}
|
||||
>
|
||||
{renderSortIcon()}
|
||||
</Button>
|
||||
</div>
|
||||
</TableHead>
|
||||
<TableHead className="h-12 px-4">{t('setting.email')}</TableHead>
|
||||
|
||||
@@ -71,11 +71,11 @@ const UserTable = ({ searchUser }: { searchUser: string }) => {
|
||||
|
||||
const renderSortIcon = () => {
|
||||
if (sortOrder === 'asc') {
|
||||
return <ArrowUp className="ml-1 h-4 w-4 " />;
|
||||
return <ArrowUp className="size-[1em] " />;
|
||||
} else if (sortOrder === 'desc') {
|
||||
return <ArrowDown className="ml-1 h-4 w-4" />;
|
||||
return <ArrowDown className="size-[1em]" />;
|
||||
} else {
|
||||
return <ArrowUpDown className="ml-1 h-4 w-4" />;
|
||||
return <ArrowUpDown className="size-[1em]" />;
|
||||
}
|
||||
};
|
||||
return (
|
||||
@@ -84,13 +84,16 @@ const UserTable = ({ searchUser }: { searchUser: string }) => {
|
||||
<TableHeader className="bg-bg-title">
|
||||
<TableRow className="hover:bg-bg-title">
|
||||
<TableHead className="h-12 px-4">{t('common.name')}</TableHead>
|
||||
<TableHead
|
||||
className="h-12 px-4 cursor-pointer"
|
||||
onClick={toggleSortOrder}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<TableHead className="h-12 px-4">
|
||||
<div className="flex items-center gap-1">
|
||||
{t('setting.updateDate')}
|
||||
{renderSortIcon()}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon-xs"
|
||||
onClick={toggleSortOrder}
|
||||
>
|
||||
{renderSortIcon()}
|
||||
</Button>
|
||||
</div>
|
||||
</TableHead>
|
||||
<TableHead className="h-12 px-4">{t('setting.email')}</TableHead>
|
||||
@@ -110,7 +113,7 @@ const UserTable = ({ searchUser }: { searchUser: string }) => {
|
||||
) : sortedData && sortedData.length > 0 ? (
|
||||
sortedData.map((record) => (
|
||||
<TableRow key={record.user_id} className="hover:bg-bg-card">
|
||||
<TableCell className="p-4 ">
|
||||
<TableCell className="p-4">
|
||||
<div className="flex gap-1 items-center">
|
||||
<RAGFlowAvatar
|
||||
isPerson
|
||||
|
||||
@@ -3,7 +3,6 @@ import { RAGFlowAvatar } from '@/components/ragflow-avatar';
|
||||
import ThemeSwitch from '@/components/theme-switch';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Domain } from '@/constants/common';
|
||||
import { useSecondPathName } from '@/hooks/route-hook';
|
||||
import { useLogout } from '@/hooks/use-login-request';
|
||||
import {
|
||||
useFetchSystemVersion,
|
||||
@@ -12,18 +11,49 @@ import {
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Routes } from '@/routes';
|
||||
import { TFunction } from 'i18next';
|
||||
import { Banknote, Box, Server, Unplug, User, Users } from 'lucide-react';
|
||||
import {
|
||||
LucideBox,
|
||||
LucideServer,
|
||||
LucideUnplug,
|
||||
LucideUser,
|
||||
LucideUsers,
|
||||
} from 'lucide-react';
|
||||
import { useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useHandleMenuClick } from './hooks';
|
||||
|
||||
const menuItems = (t: TFunction) => [
|
||||
{ icon: Server, label: t('setting.dataSources'), key: Routes.DataSource },
|
||||
{ icon: Box, label: t('setting.model'), key: Routes.Model },
|
||||
{ icon: Banknote, label: 'MCP', key: Routes.Mcp },
|
||||
{ icon: Users, label: t('setting.team'), key: Routes.Team },
|
||||
{ icon: User, label: t('setting.profile'), key: Routes.Profile },
|
||||
{ icon: Unplug, label: t('setting.api'), key: Routes.Api },
|
||||
{
|
||||
icon: <LucideServer className="size-[1em]" />,
|
||||
label: t('setting.dataSources'),
|
||||
key: Routes.DataSource,
|
||||
},
|
||||
{
|
||||
icon: <LucideBox className="size-[1em]" />,
|
||||
label: t('setting.model'),
|
||||
key: Routes.Model,
|
||||
'data-testid': 'settings-nav-model-providers',
|
||||
},
|
||||
{
|
||||
icon: <IconFontFill name="mcp" className="size-[1em]" />,
|
||||
label: 'MCP',
|
||||
key: Routes.Mcp,
|
||||
},
|
||||
{
|
||||
icon: <LucideUsers className="size-[1em]" />,
|
||||
label: t('setting.team'),
|
||||
key: Routes.Team,
|
||||
},
|
||||
{
|
||||
icon: <LucideUser className="size-[1em]" />,
|
||||
label: t('setting.profile'),
|
||||
key: Routes.Profile,
|
||||
},
|
||||
{
|
||||
icon: <LucideUnplug className="size-[1em]" />,
|
||||
label: t('setting.api'),
|
||||
key: Routes.Api,
|
||||
},
|
||||
// {
|
||||
// icon: MessageSquareQuote,
|
||||
// label: 'Prompt Templates',
|
||||
@@ -33,10 +63,10 @@ const menuItems = (t: TFunction) => [
|
||||
// { icon: Cog, label: t('setting.system'), key: Routes.System },
|
||||
// { icon: Banknote, label: 'Plan', key: Routes.Plan },
|
||||
];
|
||||
|
||||
export function SideBar() {
|
||||
const pathName = useSecondPathName();
|
||||
const { data: userInfo } = useFetchUserInfo();
|
||||
const { handleMenuClick, active } = useHandleMenuClick();
|
||||
const { handleMenuClick, active: activeItemKey } = useHandleMenuClick();
|
||||
const { version, fetchSystemVersion } = useFetchSystemVersion();
|
||||
const { t } = useTranslation();
|
||||
useEffect(() => {
|
||||
@@ -48,40 +78,38 @@ export function SideBar() {
|
||||
|
||||
return (
|
||||
<aside className="w-[303px] bg-bg-base flex flex-col">
|
||||
<div className="px-6 flex gap-2 items-center">
|
||||
<RAGFlowAvatar
|
||||
avatar={userInfo?.avatar}
|
||||
name={userInfo?.nickname}
|
||||
isPerson
|
||||
/>
|
||||
<p className="text-sm text-text-primary">{userInfo?.email}</p>
|
||||
</div>
|
||||
<div className="flex-1 overflow-auto">
|
||||
{menuItems(t).map((item, idx) => {
|
||||
const hoverKey = pathName === item.key;
|
||||
return (
|
||||
<div key={idx}>
|
||||
<div key={idx} className="mx-6 my-5 ">
|
||||
<header>
|
||||
<h1 className="px-6 flex gap-2.5 items-center font-normal">
|
||||
<RAGFlowAvatar
|
||||
avatar={userInfo?.avatar}
|
||||
name={userInfo?.nickname}
|
||||
isPerson
|
||||
/>
|
||||
|
||||
<p className="text-sm text-text-primary">{userInfo?.email}</p>
|
||||
</h1>
|
||||
</header>
|
||||
|
||||
<nav className="flex-1 overflow-auto mt-4 py-1">
|
||||
<ul className="px-6 flex flex-col gap-5">
|
||||
{menuItems(t).map((item) => {
|
||||
const { key, icon, label, ...rest } = item;
|
||||
|
||||
return (
|
||||
<li key={key}>
|
||||
<Button
|
||||
variant={hoverKey ? 'secondary' : 'ghost'}
|
||||
className={cn('w-full justify-between gap-2.5 p-3 relative', {
|
||||
'bg-bg-card text-text-primary': active === item.key,
|
||||
'bg-bg-base text-text-secondary': active !== item.key,
|
||||
})}
|
||||
data-testid={
|
||||
item.key === Routes.Model
|
||||
? 'settings-nav-model-providers'
|
||||
: undefined
|
||||
}
|
||||
onClick={handleMenuClick(item.key)}
|
||||
{...rest}
|
||||
block
|
||||
variant="ghost"
|
||||
className={cn(
|
||||
'justify-start gap-2.5 px-3 relative h-10 text-base',
|
||||
activeItemKey === key && 'bg-bg-card text-text-primary',
|
||||
)}
|
||||
onClick={handleMenuClick(key)}
|
||||
>
|
||||
<section className="flex items-center gap-2.5">
|
||||
{item.key === Routes.Mcp ? (
|
||||
<IconFontFill name={'mcp'} className="size-4 w-4 h-4" />
|
||||
) : (
|
||||
<item.icon className="w-6 h-6" />
|
||||
)}
|
||||
<span>{item.label}</span>
|
||||
{icon}
|
||||
<span>{label}</span>
|
||||
</section>
|
||||
{/* {item.key === Routes.System && (
|
||||
<div className="mr-2 px-2 bg-accent-primary-5 text-accent-primary rounded-md">
|
||||
@@ -92,24 +120,22 @@ export function SideBar() {
|
||||
<div className="absolute right-0 w-[5px] h-[66px] bg-primary rounded-l-xl shadow-[0_0_5.94px_#7561ff,0_0_11.88px_#7561ff,0_0_41.58px_#7561ff,0_0_83.16px_#7561ff,0_0_142.56px_#7561ff,0_0_249.48px_#7561ff]" />
|
||||
)} */}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<div className="p-6 mt-auto ">
|
||||
<footer className="p-6 mt-auto">
|
||||
<div className="flex items-center gap-2 mb-6 justify-between">
|
||||
<div className="mr-2 px-2 text-accent-primary rounded-md">
|
||||
{version}
|
||||
</div>
|
||||
<span className="text-accent-primary">{version}</span>
|
||||
<ThemeSwitch />
|
||||
</div>
|
||||
|
||||
<Button block size="lg" variant="transparent" onClick={() => logout()}>
|
||||
{t('setting.logout')}
|
||||
</Button>
|
||||
</div>
|
||||
</footer>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user