Feat: Add LinkToDatasetDialog #3221 (#4500)

### What problem does this PR solve?

Feat: Add LinkToDatasetDialog #3221

### Type of change


- [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
balibabu 2025-01-16 11:35:39 +08:00 committed by GitHub
parent 961e8c4980
commit a75cda4957
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 267 additions and 45 deletions

View File

@ -16,7 +16,7 @@ const badgeVariants = cva(
'border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80',
outline: 'text-foreground',
tertiary:
'border-transparent bg-colors-text-core-standard text-foreground hover:bg-colors-text-core-standard/80',
'border-transparent bg-colors-background-core-strong text-colors-text-persist-light hover:bg-colors-background-core-strong/80',
},
},
defaultVariants: {

View File

@ -19,7 +19,7 @@ const buttonVariants = cva(
ghost: 'hover:bg-accent hover:text-accent-foreground',
link: 'text-primary underline-offset-4 hover:underline',
tertiary:
'bg-colors-text-core-standard text-foreground hover:bg-colors-text-core-standard/80',
'bg-colors-background-sentiment-solid-primary text-colors-text-persist-light hover:bg-colors-background-sentiment-solid-primary/80',
icon: 'bg-colors-background-inverse-standard text-foreground hover:bg-colors-background-inverse-standard/80',
},
size: {
@ -27,6 +27,7 @@ const buttonVariants = cva(
sm: 'h-9 rounded-md px-3',
lg: 'h-11 rounded-md px-8',
icon: 'h-10 w-10',
auto: 'h-full px-1',
},
},
defaultVariants: {

View File

@ -20,6 +20,8 @@ const buttonVariants = cva(
'bg-secondary text-secondary-foreground hover:bg-secondary/80',
ghost: 'hover:bg-accent hover:text-accent-foreground',
link: 'text-primary underline-offset-4 hover:underline',
tertiary:
'bg-colors-background-sentiment-solid-primary text-colors-text-persist-light hover:bg-colors-background-sentiment-solid-primary/80',
},
size: {
default: 'h-10 px-4 py-2',

View File

@ -72,6 +72,17 @@ export const useFetchKnowledgeList = (
return { list: data, loading };
};
export const useSelectKnowledgeOptions = () => {
const { list } = useFetchKnowledgeList();
const options = list?.map((item) => ({
label: item.name,
value: item.id,
}));
return options;
};
export const useInfiniteFetchKnowledgeList = () => {
const { searchString, handleInputChange } = useHandleSearchChange();
const debouncedSearchString = useDebounce(searchString, { wait: 500 });

View File

@ -0,0 +1,61 @@
import { ConfirmDeleteDialog } from '@/components/confirm-delete-dialog';
import { Button } from '@/components/ui/button';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { IFile } from '@/interfaces/database/file-manager';
import { CellContext } from '@tanstack/react-table';
import { EllipsisVertical, Link2, Trash2 } from 'lucide-react';
import { useCallback } from 'react';
import { UseHandleConnectToKnowledgeReturnType } from './hooks';
type IProps = Pick<CellContext<IFile, unknown>, 'row'> &
Pick<UseHandleConnectToKnowledgeReturnType, 'showConnectToKnowledgeModal'>;
export function ActionCell({ row, showConnectToKnowledgeModal }: IProps) {
const record = row.original;
const handleShowConnectToKnowledgeModal = useCallback(() => {
showConnectToKnowledgeModal(record);
}, [record, showConnectToKnowledgeModal]);
return (
<section className="flex gap-4 items-center">
<Button
variant="secondary"
size={'icon'}
onClick={handleShowConnectToKnowledgeModal}
>
<Link2 />
</Button>
<ConfirmDeleteDialog>
<Button variant="secondary" size={'icon'}>
<Trash2 />
</Button>
</ConfirmDeleteDialog>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="secondary" size={'icon'}>
<EllipsisVertical />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuLabel>Actions</DropdownMenuLabel>
<DropdownMenuItem
onClick={() => navigator.clipboard.writeText(record.id)}
>
Copy payment ID
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>View customer</DropdownMenuItem>
<DropdownMenuItem>View payment details</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</section>
);
}

View File

@ -11,22 +11,14 @@ import {
getSortedRowModel,
useReactTable,
} from '@tanstack/react-table';
import { ArrowUpDown, MoreHorizontal, Pencil } from 'lucide-react';
import { ArrowUpDown } from 'lucide-react';
import * as React from 'react';
import SvgIcon from '@/components/svg-icon';
import { TableEmpty, TableSkeleton } from '@/components/table-skeleton';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { Switch } from '@/components/ui/switch';
import {
Table,
TableBody,
@ -48,7 +40,9 @@ import { formatDate } from '@/utils/date';
import { getExtension } from '@/utils/document-util';
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useNavigateToOtherFolder } from './hooks';
import { ActionCell } from './action-cell';
import { useHandleConnectToKnowledge, useNavigateToOtherFolder } from './hooks';
import { LinkToDatasetDialog } from './link-to-dataset-dialog';
export function FilesTable() {
const [sorting, setSorting] = React.useState<SortingState>([]);
@ -62,6 +56,14 @@ export function FilesTable() {
keyPrefix: 'fileManager',
});
const navigateToOtherFolder = useNavigateToOtherFolder();
const {
connectToKnowledgeVisible,
hideConnectToKnowledgeModal,
showConnectToKnowledgeModal,
initialConnectedIds,
onConnectToKnowledgeOk,
connectToKnowledgeLoading,
} = useHandleConnectToKnowledge();
const { pagination, data, loading, setPagination } = useFetchFileList();
@ -176,44 +178,37 @@ export function FilesTable() {
{
accessorKey: 'kbs_info',
header: t('knowledgeBase'),
cell: ({ row }) => (
<Button variant="destructive" size={'sm'}>
{row.getValue('kbs_info')}
</Button>
),
cell: ({ row }) => {
const value = row.getValue('kbs_info');
return Array.isArray(value) ? (
<section className="flex gap-2 items-center">
{value?.slice(0, 2).map((x) => (
<Badge key={x.kb_id} className="" variant={'tertiary'}>
{x.kb_name}
</Badge>
))}
{value.length > 2 && (
<Button variant={'icon'} size={'auto'}>
+{value.length - 2}
</Button>
)}
</section>
) : (
''
);
},
},
{
id: 'actions',
header: t('action'),
enableHiding: false,
cell: ({ row }) => {
const payment = row.original;
return (
<section className="flex gap-4 items-center">
<Switch id="airplane-mode" />
<Button variant="secondary" size={'icon'}>
<Pencil />
</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="secondary" size={'icon'}>
<MoreHorizontal />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuLabel>Actions</DropdownMenuLabel>
<DropdownMenuItem
onClick={() => navigator.clipboard.writeText(payment.id)}
>
Copy payment ID
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>View customer</DropdownMenuItem>
<DropdownMenuItem>View payment details</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</section>
<ActionCell
row={row}
showConnectToKnowledgeModal={showConnectToKnowledgeModal}
></ActionCell>
);
},
},
@ -338,6 +333,14 @@ export function FilesTable() {
</Button>
</div>
</div>
{connectToKnowledgeVisible && (
<LinkToDatasetDialog
hideModal={hideConnectToKnowledgeModal}
initialConnectedIds={initialConnectedIds}
onConnectToKnowledgeOk={onConnectToKnowledgeOk}
loading={connectToKnowledgeLoading}
></LinkToDatasetDialog>
)}
</div>
);
}

View File

@ -224,7 +224,7 @@ export const useHandleConnectToKnowledge = () => {
);
return {
initialValue,
initialConnectedIds: initialValue,
connectToKnowledgeLoading: loading,
onConnectToKnowledgeOk,
connectToKnowledgeVisible,
@ -233,6 +233,10 @@ export const useHandleConnectToKnowledge = () => {
};
};
export type UseHandleConnectToKnowledgeReturnType = ReturnType<
typeof useHandleConnectToKnowledge
>;
export const useHandleBreadcrumbClick = () => {
const navigate = useNavigate();

View File

@ -0,0 +1,130 @@
import {
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@/components/ui/form';
import { LoadingButton } from '@/components/ui/loading-button';
import { MultiSelect } from '@/components/ui/multi-select';
import { useSelectKnowledgeOptions } from '@/hooks/knowledge-hooks';
import { IModalProps } from '@/interfaces/common';
import { zodResolver } from '@hookform/resolvers/zod';
import { Link2 } from 'lucide-react';
import { useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { z } from 'zod';
import { UseHandleConnectToKnowledgeReturnType } from './hooks';
const FormId = 'LinkToDatasetForm';
const FormSchema = z.object({
knowledgeIds: z.array(z.string()).min(0, {
message: 'Username must be at least 1 characters.',
}),
});
function LinkToDatasetForm({
initialConnectedIds,
onConnectToKnowledgeOk,
}: Pick<
UseHandleConnectToKnowledgeReturnType,
'initialConnectedIds' | 'onConnectToKnowledgeOk'
>) {
const { t } = useTranslation();
const form = useForm<z.infer<typeof FormSchema>>({
resolver: zodResolver(FormSchema),
defaultValues: {
knowledgeIds: initialConnectedIds,
},
});
const options = useSelectKnowledgeOptions();
function onSubmit(data: z.infer<typeof FormSchema>) {
onConnectToKnowledgeOk(data.knowledgeIds);
}
// useEffect(() => {
// form.setValue('knowledgeIds', initialConnectedIds); // this is invalid
// }, [form, initialConnectedIds]);
return (
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="space-y-6"
id={FormId}
>
<FormField
control={form.control}
name="knowledgeIds"
render={({ field }) => (
<FormItem>
<FormLabel>Name</FormLabel>
<FormControl>
<MultiSelect
options={options}
onValueChange={field.onChange}
defaultValue={field.value}
placeholder={t('fileManager.pleaseSelect')}
maxCount={100}
// {...field}
modalPopover
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</form>
</Form>
);
}
export function LinkToDatasetDialog({
hideModal,
initialConnectedIds,
onConnectToKnowledgeOk,
loading,
}: IModalProps<any> &
Pick<
UseHandleConnectToKnowledgeReturnType,
'initialConnectedIds' | 'onConnectToKnowledgeOk'
>) {
const { t } = useTranslation();
return (
<Dialog open onOpenChange={hideModal}>
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>{t('fileManager.addToKnowledge')}</DialogTitle>
</DialogHeader>
<LinkToDatasetForm
initialConnectedIds={initialConnectedIds}
onConnectToKnowledgeOk={onConnectToKnowledgeOk}
></LinkToDatasetForm>
<DialogFooter>
<LoadingButton
type="submit"
variant={'tertiary'}
form={FormId}
loading={loading}
>
<div className="flex gap-2 items-center">
<Link2 /> Save
</div>
</LoadingButton>
</DialogFooter>
</DialogContent>
</Dialog>
);
}

View File

@ -37,6 +37,7 @@ module.exports = {
'colors-text-neutral-standard': 'var(--colors-text-neutral-standard)',
'colors-text-functional-danger': 'var(--colors-text-functional-danger)',
'colors-text-inverse-strong': 'var(--colors-text-inverse-strong)',
'colors-text-persist-light': 'var(--colors-text-persist-light)',
primary: {
DEFAULT: 'hsl(var(--primary))',
@ -156,6 +157,10 @@ module.exports = {
DEFAULT: 'var(--colors-background-neutral-weak)',
foreground: 'var(--background-inverse-standard-foreground)',
},
'colors-background-sentiment-solid-primary': {
DEFAULT: 'var(--colors-background-sentiment-solid-primary)',
foreground: 'var(--background-inverse-standard-foreground)',
},
},
borderRadius: {
lg: `var(--radius)`,

View File

@ -42,6 +42,8 @@
--colors-background-inverse-weak: rgba(17, 16, 23, 0.1);
--colors-background-neutral-standard: white;
--colors-background-functional-solid-danger: rgba(222, 17, 53, 1);
--colors-background-core-strong: rgba(98, 72, 246, 1);
--colors-background-sentiment-solid-primary: rgba(127, 105, 255, 1);
--button-blue-text: rgb(22, 119, 255);
@ -54,6 +56,7 @@
--colors-text-neutral-standard: rgba(53, 51, 65, 1);
--colors-text-functional-danger: rgba(255, 81, 81, 1);
--colors-text-inverse-strong: rgba(255, 255, 255, 1);
--colors-text-persist-light: rgba(255, 255, 255, 1);
}
.dark {
@ -123,6 +126,7 @@
--colors-background-neutral-standard: rgba(11, 10, 18, 1);
--colors-background-neutral-strong: rgba(29, 26, 44, 1);
--colors-background-neutral-weak: rgba(17, 16, 23, 1);
--colors-background-sentiment-solid-primary: rgba(146, 118, 255, 1);
--colors-outline-sentiment-primary: rgba(146, 118, 255, 1);
--colors-outline-neutral-strong: rgba(255, 255, 255, 0.15);
@ -133,6 +137,7 @@
--colors-text-neutral-standard: rgba(230, 227, 246, 1);
--colors-text-functional-danger: rgba(255, 81, 81, 1);
--colors-text-inverse-strong: rgba(17, 16, 23, 1);
--colors-text-persist-light: rgba(255, 255, 255, 1);
}
}