mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-23 06:46:40 +08:00
Fix: Bug fixes (#11960)
### What problem does this PR solve? Fix: Bug fixes New search popup style modification Fixed multilingual settings not updating immediately on personal center page Changed overlapped percent to percentage format, with maximum value of 30% ### Type of change - [x] Bug Fix (non-breaking change which fixes an issue)
This commit is contained in:
@ -11,6 +11,7 @@ import {
|
|||||||
DefaultValues,
|
DefaultValues,
|
||||||
FieldValues,
|
FieldValues,
|
||||||
SubmitHandler,
|
SubmitHandler,
|
||||||
|
UseFormTrigger,
|
||||||
useForm,
|
useForm,
|
||||||
useFormContext,
|
useFormContext,
|
||||||
} from 'react-hook-form';
|
} from 'react-hook-form';
|
||||||
@ -99,8 +100,9 @@ interface DynamicFormProps<T extends FieldValues> {
|
|||||||
// Form ref interface
|
// Form ref interface
|
||||||
export interface DynamicFormRef {
|
export interface DynamicFormRef {
|
||||||
submit: () => void;
|
submit: () => void;
|
||||||
getValues: () => any;
|
getValues: (name?: string) => any;
|
||||||
reset: (values?: any) => void;
|
reset: (values?: any) => void;
|
||||||
|
trigger: UseFormTrigger<any>;
|
||||||
watch: (field: string, callback: (value: any) => void) => () => void;
|
watch: (field: string, callback: (value: any) => void) => () => void;
|
||||||
updateFieldType: (fieldName: string, newType: FormFieldType) => void;
|
updateFieldType: (fieldName: string, newType: FormFieldType) => void;
|
||||||
onFieldUpdate: (
|
onFieldUpdate: (
|
||||||
@ -704,8 +706,8 @@ const DynamicForm = {
|
|||||||
useImperativeHandle(
|
useImperativeHandle(
|
||||||
ref,
|
ref,
|
||||||
() => ({
|
() => ({
|
||||||
submit: () => form.handleSubmit(onSubmit)(),
|
submit: form.handleSubmit,
|
||||||
getValues: () => form.getValues(),
|
getValues: form.getValues,
|
||||||
reset: (values?: T) => {
|
reset: (values?: T) => {
|
||||||
if (values) {
|
if (values) {
|
||||||
form.reset(values);
|
form.reset(values);
|
||||||
|
|||||||
@ -26,6 +26,7 @@ type SliderInputFormFieldProps = {
|
|||||||
defaultValue?: number;
|
defaultValue?: number;
|
||||||
className?: string;
|
className?: string;
|
||||||
numberInputClassName?: string;
|
numberInputClassName?: string;
|
||||||
|
percentage?: boolean;
|
||||||
} & FormLayoutType;
|
} & FormLayoutType;
|
||||||
|
|
||||||
export function SliderInputFormField({
|
export function SliderInputFormField({
|
||||||
@ -39,11 +40,14 @@ export function SliderInputFormField({
|
|||||||
className,
|
className,
|
||||||
numberInputClassName,
|
numberInputClassName,
|
||||||
layout = FormLayout.Horizontal,
|
layout = FormLayout.Horizontal,
|
||||||
|
percentage = false,
|
||||||
}: SliderInputFormFieldProps) {
|
}: SliderInputFormFieldProps) {
|
||||||
const form = useFormContext();
|
const form = useFormContext();
|
||||||
|
|
||||||
const isHorizontal = useMemo(() => layout !== FormLayout.Vertical, [layout]);
|
const isHorizontal = useMemo(() => layout !== FormLayout.Vertical, [layout]);
|
||||||
|
const displayMax = percentage ? (max || 1) * 100 : max;
|
||||||
|
const displayMin = percentage ? (min || 0) * 100 : min;
|
||||||
|
const displayStep = percentage ? (step || 0.01) * 100 : step;
|
||||||
return (
|
return (
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
@ -71,12 +75,13 @@ export function SliderInputFormField({
|
|||||||
<FormControl>
|
<FormControl>
|
||||||
<SingleFormSlider
|
<SingleFormSlider
|
||||||
{...field}
|
{...field}
|
||||||
max={max}
|
value={percentage ? field.value * 100 : field.value}
|
||||||
min={min}
|
onChange={(value) =>
|
||||||
step={step}
|
field.onChange(percentage ? value / 100 : value)
|
||||||
// defaultValue={
|
}
|
||||||
// typeof defaultValue === 'number' ? [defaultValue] : undefined
|
max={displayMax}
|
||||||
// }
|
min={displayMin}
|
||||||
|
step={displayStep}
|
||||||
></SingleFormSlider>
|
></SingleFormSlider>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
@ -86,11 +91,20 @@ export function SliderInputFormField({
|
|||||||
'[appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none',
|
'[appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none',
|
||||||
numberInputClassName,
|
numberInputClassName,
|
||||||
)}
|
)}
|
||||||
max={max}
|
max={displayMax}
|
||||||
min={min}
|
min={displayMin}
|
||||||
step={step}
|
step={displayStep}
|
||||||
{...field}
|
value={
|
||||||
// defaultValue={defaultValue}
|
percentage ? (field.value * 100).toFixed(0) : field.value
|
||||||
|
}
|
||||||
|
onChange={(val) => {
|
||||||
|
const value = Number(val || 0);
|
||||||
|
if (!isNaN(value)) {
|
||||||
|
field.onChange(
|
||||||
|
percentage ? (value / 100).toFixed(0) : value,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
></NumberInput>
|
></NumberInput>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -174,20 +174,23 @@ const Modal: ModalType = ({
|
|||||||
onClick={() => maskClosable && onOpenChange?.(false)}
|
onClick={() => maskClosable && onOpenChange?.(false)}
|
||||||
>
|
>
|
||||||
<DialogPrimitive.Content
|
<DialogPrimitive.Content
|
||||||
className={`relative w-[700px] ${full ? 'max-w-full' : sizeClasses[size]} ${className} bg-bg-base rounded-lg shadow-lg border border-border-default transition-all focus-visible:!outline-none`}
|
className={cn(
|
||||||
|
`relative w-[700px] ${full ? 'max-w-full' : sizeClasses[size]} ${className} bg-bg-base rounded-lg shadow-lg border border-border-default transition-all focus-visible:!outline-none`,
|
||||||
|
{ 'pt-10': closable && !title },
|
||||||
|
)}
|
||||||
style={style}
|
style={style}
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
{/* title */}
|
{/* title */}
|
||||||
{(title || closable) && (
|
{title && (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex items-start px-6 py-4',
|
'flex items-start px-6 py-4 justify-start',
|
||||||
{
|
// {
|
||||||
'justify-end': closable && !title,
|
// 'justify-end': closable && !title,
|
||||||
'justify-between': closable && title,
|
// 'justify-between': closable && title,
|
||||||
'justify-start': !closable,
|
// 'justify-start': !closable,
|
||||||
},
|
// },
|
||||||
titleClassName,
|
titleClassName,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@ -196,19 +199,19 @@ const Modal: ModalType = ({
|
|||||||
{title}
|
{title}
|
||||||
</DialogPrimitive.Title>
|
</DialogPrimitive.Title>
|
||||||
)}
|
)}
|
||||||
{closable && (
|
|
||||||
<DialogPrimitive.Close asChild>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="flex h-7 w-7 items-center justify-center text-text-secondary rounded-full hover:text-text-primary focus-visible:outline-none"
|
|
||||||
onClick={handleCancel}
|
|
||||||
>
|
|
||||||
{closeIcon}
|
|
||||||
</button>
|
|
||||||
</DialogPrimitive.Close>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{closable && (
|
||||||
|
<DialogPrimitive.Close asChild>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="flex absolute right-5 top-5 h-7 w-7 items-center justify-center text-text-secondary rounded-full hover:text-text-primary focus-visible:outline-none"
|
||||||
|
onClick={handleCancel}
|
||||||
|
>
|
||||||
|
{closeIcon}
|
||||||
|
</button>
|
||||||
|
</DialogPrimitive.Close>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* content */}
|
{/* content */}
|
||||||
<div className="py-2 px-6 overflow-y-auto scrollbar-auto max-h-[calc(100vh-280px)] focus-visible:!outline-none">
|
<div className="py-2 px-6 overflow-y-auto scrollbar-auto max-h-[calc(100vh-280px)] focus-visible:!outline-none">
|
||||||
|
|||||||
@ -159,6 +159,7 @@ export default {
|
|||||||
doc: 'Docs',
|
doc: 'Docs',
|
||||||
searchKnowledgePlaceholder: 'Search',
|
searchKnowledgePlaceholder: 'Search',
|
||||||
noMoreData: `That's all. Nothing more.`,
|
noMoreData: `That's all. Nothing more.`,
|
||||||
|
parserRequired: 'Chunk method is required',
|
||||||
},
|
},
|
||||||
knowledgeDetails: {
|
knowledgeDetails: {
|
||||||
localUpload: 'Local upload',
|
localUpload: 'Local upload',
|
||||||
@ -329,7 +330,7 @@ export default {
|
|||||||
reRankModelWaring: 'Re-rank model is very time consuming.',
|
reRankModelWaring: 'Re-rank model is very time consuming.',
|
||||||
},
|
},
|
||||||
knowledgeConfiguration: {
|
knowledgeConfiguration: {
|
||||||
overlappedPercent: 'Overlapped percent',
|
overlappedPercent: 'Overlapped percent(%)',
|
||||||
generationScopeTip:
|
generationScopeTip:
|
||||||
'Determines whether RAPTOR is generated for the entire dataset or for a single file.',
|
'Determines whether RAPTOR is generated for the entire dataset or for a single file.',
|
||||||
scopeDataset: 'Dataset',
|
scopeDataset: 'Dataset',
|
||||||
|
|||||||
@ -101,6 +101,7 @@ export default {
|
|||||||
doc: '文档',
|
doc: '文档',
|
||||||
searchKnowledgePlaceholder: '搜索',
|
searchKnowledgePlaceholder: '搜索',
|
||||||
noMoreData: '没有更多数据了',
|
noMoreData: '没有更多数据了',
|
||||||
|
parserRequired: '分块方法必填',
|
||||||
},
|
},
|
||||||
knowledgeDetails: {
|
knowledgeDetails: {
|
||||||
localUpload: '本地上传',
|
localUpload: '本地上传',
|
||||||
|
|||||||
@ -24,7 +24,8 @@ import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
|
|||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { PipelineResultSearchParams } from '@/pages/dataflow-result/constant';
|
import { PipelineResultSearchParams } from '@/pages/dataflow-result/constant';
|
||||||
import { NavigateToDataflowResultProps } from '@/pages/dataflow-result/interface';
|
import { NavigateToDataflowResultProps } from '@/pages/dataflow-result/interface';
|
||||||
import { DataSourceInfo } from '@/pages/user-setting/data-source/contant';
|
import { useDataSourceInfo } from '@/pages/user-setting/data-source/contant';
|
||||||
|
import { IDataSourceInfoMap } from '@/pages/user-setting/data-source/interface';
|
||||||
import { formatDate, formatSecondsToHumanReadable } from '@/utils/date';
|
import { formatDate, formatSecondsToHumanReadable } from '@/utils/date';
|
||||||
import {
|
import {
|
||||||
ColumnDef,
|
ColumnDef,
|
||||||
@ -54,6 +55,7 @@ export const getFileLogsTableColumns = (
|
|||||||
navigateToDataflowResult: (
|
navigateToDataflowResult: (
|
||||||
props: NavigateToDataflowResultProps,
|
props: NavigateToDataflowResultProps,
|
||||||
) => () => void,
|
) => () => void,
|
||||||
|
dataSourceInfo: IDataSourceInfoMap,
|
||||||
) => {
|
) => {
|
||||||
// const { t } = useTranslate('knowledgeDetails');
|
// const { t } = useTranslate('knowledgeDetails');
|
||||||
const columns: ColumnDef<IFileLogItem & DocumentLog>[] = [
|
const columns: ColumnDef<IFileLogItem & DocumentLog>[] = [
|
||||||
@ -117,8 +119,8 @@ export const getFileLogsTableColumns = (
|
|||||||
) : (
|
) : (
|
||||||
<div className="w-6 h-6 flex items-center justify-center">
|
<div className="w-6 h-6 flex items-center justify-center">
|
||||||
{
|
{
|
||||||
DataSourceInfo[
|
dataSourceInfo[
|
||||||
row.original.source_from as keyof typeof DataSourceInfo
|
row.original.source_from as keyof typeof dataSourceInfo
|
||||||
].icon
|
].icon
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
@ -368,7 +370,7 @@ const FileLogsTable: FC<FileLogsTableProps> = ({
|
|||||||
setLogInfo(logDetail);
|
setLogInfo(logDetail);
|
||||||
setIsModalVisible(true);
|
setIsModalVisible(true);
|
||||||
};
|
};
|
||||||
|
const { dataSourceInfo } = useDataSourceInfo();
|
||||||
const columns = useMemo(() => {
|
const columns = useMemo(() => {
|
||||||
return active === LogTabs.FILE_LOGS
|
return active === LogTabs.FILE_LOGS
|
||||||
? getFileLogsTableColumns(
|
? getFileLogsTableColumns(
|
||||||
@ -376,6 +378,7 @@ const FileLogsTable: FC<FileLogsTableProps> = ({
|
|||||||
showLog,
|
showLog,
|
||||||
kowledgeId || '',
|
kowledgeId || '',
|
||||||
navigateToDataflowResult,
|
navigateToDataflowResult,
|
||||||
|
dataSourceInfo,
|
||||||
)
|
)
|
||||||
: getDatasetLogsTableColumns(t, showLog);
|
: getDatasetLogsTableColumns(t, showLog);
|
||||||
}, [active, t]);
|
}, [active, t]);
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import {
|
|||||||
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
|
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
|
||||||
import { IConnector } from '@/interfaces/database/knowledge';
|
import { IConnector } from '@/interfaces/database/knowledge';
|
||||||
import { delSourceModal } from '@/pages/user-setting/data-source/component/delete-source-modal';
|
import { delSourceModal } from '@/pages/user-setting/data-source/component/delete-source-modal';
|
||||||
import { DataSourceInfo } from '@/pages/user-setting/data-source/contant';
|
import { useDataSourceInfo } from '@/pages/user-setting/data-source/contant';
|
||||||
import { useDataSourceRebuild } from '@/pages/user-setting/data-source/hooks';
|
import { useDataSourceRebuild } from '@/pages/user-setting/data-source/hooks';
|
||||||
import { IDataSourceBase } from '@/pages/user-setting/data-source/interface';
|
import { IDataSourceBase } from '@/pages/user-setting/data-source/interface';
|
||||||
import { Link, Settings, Unlink } from 'lucide-react';
|
import { Link, Settings, Unlink } from 'lucide-react';
|
||||||
@ -41,6 +41,7 @@ interface DataSourceItemProps extends IDataSourceNodeProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const DataSourceItem = (props: DataSourceItemProps) => {
|
const DataSourceItem = (props: DataSourceItemProps) => {
|
||||||
|
const { dataSourceInfo } = useDataSourceInfo();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { id, name, icon, source, auto_parse, unbindFunc, handleAutoParse } =
|
const { id, name, icon, source, auto_parse, unbindFunc, handleAutoParse } =
|
||||||
props;
|
props;
|
||||||
@ -56,7 +57,7 @@ const DataSourceItem = (props: DataSourceItemProps) => {
|
|||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<div className="w-6 h-6 flex-shrink-0">{icon}</div>
|
<div className="w-6 h-6 flex-shrink-0">{icon}</div>
|
||||||
<div className="text-base text-text-primary">
|
<div className="text-base text-text-primary">
|
||||||
{DataSourceInfo[source].name}
|
{dataSourceInfo[source].name}
|
||||||
</div>
|
</div>
|
||||||
<div>{name}</div>
|
<div>{name}</div>
|
||||||
</div>
|
</div>
|
||||||
@ -114,6 +115,7 @@ const DataSourceItem = (props: DataSourceItemProps) => {
|
|||||||
delSourceModal({
|
delSourceModal({
|
||||||
data: props,
|
data: props,
|
||||||
type: 'unlink',
|
type: 'unlink',
|
||||||
|
dataSourceInfo: dataSourceInfo,
|
||||||
onOk: (data) => unbindFunc?.(data as DataSourceItemProps),
|
onOk: (data) => unbindFunc?.(data as DataSourceItemProps),
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
@ -134,6 +136,7 @@ const LinkDataSource = (props: ILinkDataSourceProps) => {
|
|||||||
handleAutoParse,
|
handleAutoParse,
|
||||||
} = props;
|
} = props;
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const { dataSourceInfo } = useDataSourceInfo();
|
||||||
const [openLinkModal, setOpenLinkModal] = useState(false);
|
const [openLinkModal, setOpenLinkModal] = useState(false);
|
||||||
|
|
||||||
const pipelineNode: IDataSourceNodeProps[] = useMemo(() => {
|
const pipelineNode: IDataSourceNodeProps[] = useMemo(() => {
|
||||||
@ -144,7 +147,7 @@ const LinkDataSource = (props: ILinkDataSourceProps) => {
|
|||||||
id: item?.id,
|
id: item?.id,
|
||||||
name: item?.name,
|
name: item?.name,
|
||||||
icon:
|
icon:
|
||||||
DataSourceInfo[item?.source as keyof typeof DataSourceInfo]?.icon ||
|
dataSourceInfo[item?.source as keyof typeof dataSourceInfo]?.icon ||
|
||||||
'',
|
'',
|
||||||
} as IDataSourceNodeProps;
|
} as IDataSourceNodeProps;
|
||||||
});
|
});
|
||||||
|
|||||||
@ -291,9 +291,10 @@ export function EnableTocToggle() {
|
|||||||
export function OverlappedPercent() {
|
export function OverlappedPercent() {
|
||||||
return (
|
return (
|
||||||
<SliderInputFormField
|
<SliderInputFormField
|
||||||
|
percentage={true}
|
||||||
name="parser_config.overlapped_percent"
|
name="parser_config.overlapped_percent"
|
||||||
label={t('knowledgeConfiguration.overlappedPercent')}
|
label={t('knowledgeConfiguration.overlappedPercent')}
|
||||||
max={0.5}
|
max={0.3}
|
||||||
step={0.01}
|
step={0.01}
|
||||||
></SliderInputFormField>
|
></SliderInputFormField>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import { FormLayout } from '@/constants/form';
|
|||||||
import { DocumentParserType } from '@/constants/knowledge';
|
import { DocumentParserType } from '@/constants/knowledge';
|
||||||
import { PermissionRole } from '@/constants/permission';
|
import { PermissionRole } from '@/constants/permission';
|
||||||
import { IConnector } from '@/interfaces/database/knowledge';
|
import { IConnector } from '@/interfaces/database/knowledge';
|
||||||
import { DataSourceInfo } from '@/pages/user-setting/data-source/contant';
|
import { useDataSourceInfo } from '@/pages/user-setting/data-source/contant';
|
||||||
import { IDataSourceBase } from '@/pages/user-setting/data-source/interface';
|
import { IDataSourceBase } from '@/pages/user-setting/data-source/interface';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
@ -89,6 +89,7 @@ export default function DatasetSettings() {
|
|||||||
connectors: [],
|
connectors: [],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
const { dataSourceInfo } = useDataSourceInfo();
|
||||||
const knowledgeDetails = useFetchKnowledgeConfigurationOnMount(form);
|
const knowledgeDetails = useFetchKnowledgeConfigurationOnMount(form);
|
||||||
// const [pipelineData, setPipelineData] = useState<IDataPipelineNodeProps>();
|
// const [pipelineData, setPipelineData] = useState<IDataPipelineNodeProps>();
|
||||||
const [sourceData, setSourceData] = useState<IDataSourceNodeProps[]>();
|
const [sourceData, setSourceData] = useState<IDataSourceNodeProps[]>();
|
||||||
@ -113,7 +114,7 @@ export default function DatasetSettings() {
|
|||||||
return {
|
return {
|
||||||
...connector,
|
...connector,
|
||||||
icon:
|
icon:
|
||||||
DataSourceInfo[connector.source as keyof typeof DataSourceInfo]
|
dataSourceInfo[connector.source as keyof typeof dataSourceInfo]
|
||||||
?.icon || '',
|
?.icon || '',
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@ -159,7 +160,7 @@ export default function DatasetSettings() {
|
|||||||
...connector,
|
...connector,
|
||||||
auto_parse: connector.auto_parse === '0' ? '0' : '1',
|
auto_parse: connector.auto_parse === '0' ? '0' : '1',
|
||||||
icon:
|
icon:
|
||||||
DataSourceInfo[connector.source as keyof typeof DataSourceInfo]
|
dataSourceInfo[connector.source as keyof typeof dataSourceInfo]
|
||||||
?.icon || '',
|
?.icon || '',
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
|
|||||||
import { useSetDocumentStatus } from '@/hooks/use-document-request';
|
import { useSetDocumentStatus } from '@/hooks/use-document-request';
|
||||||
import { IDocumentInfo } from '@/interfaces/database/document';
|
import { IDocumentInfo } from '@/interfaces/database/document';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { DataSourceInfo } from '@/pages/user-setting/data-source/contant';
|
import { useDataSourceInfo } from '@/pages/user-setting/data-source/contant';
|
||||||
import { formatDate } from '@/utils/date';
|
import { formatDate } from '@/utils/date';
|
||||||
import { ColumnDef } from '@tanstack/table-core';
|
import { ColumnDef } from '@tanstack/table-core';
|
||||||
import { ArrowUpDown, MonitorUp } from 'lucide-react';
|
import { ArrowUpDown, MonitorUp } from 'lucide-react';
|
||||||
@ -35,7 +35,7 @@ export function useDatasetTableColumns({
|
|||||||
const { t } = useTranslation('translation', {
|
const { t } = useTranslation('translation', {
|
||||||
keyPrefix: 'knowledgeDetails',
|
keyPrefix: 'knowledgeDetails',
|
||||||
});
|
});
|
||||||
|
const { dataSourceInfo } = useDataSourceInfo();
|
||||||
const { navigateToChunkParsedResult } = useNavigatePage();
|
const { navigateToChunkParsedResult } = useNavigatePage();
|
||||||
const { setDocumentStatus } = useSetDocumentStatus();
|
const { setDocumentStatus } = useSetDocumentStatus();
|
||||||
|
|
||||||
@ -134,8 +134,8 @@ export function useDatasetTableColumns({
|
|||||||
) : (
|
) : (
|
||||||
<div className="w-6 h-6 flex items-center justify-center">
|
<div className="w-6 h-6 flex items-center justify-center">
|
||||||
{
|
{
|
||||||
DataSourceInfo[
|
dataSourceInfo[
|
||||||
row.original.source_type as keyof typeof DataSourceInfo
|
row.original.source_type as keyof typeof dataSourceInfo
|
||||||
]?.icon
|
]?.icon
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import { CardContainer } from '@/components/card-container';
|
import { CardContainer } from '@/components/card-container';
|
||||||
import { EmptyCardType } from '@/components/empty/constant';
|
import { EmptyCardType } from '@/components/empty/constant';
|
||||||
import { EmptyAppCard } from '@/components/empty/empty';
|
import { EmptyAppCard } from '@/components/empty/empty';
|
||||||
import { IconFont } from '@/components/icon-font';
|
|
||||||
import ListFilterBar from '@/components/list-filter-bar';
|
import ListFilterBar from '@/components/list-filter-bar';
|
||||||
import { RenameDialog } from '@/components/rename-dialog';
|
import { RenameDialog } from '@/components/rename-dialog';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
@ -149,7 +148,7 @@ export default function SearchList() {
|
|||||||
onOk={onSearchRenameConfirm}
|
onOk={onSearchRenameConfirm}
|
||||||
initialName={initialSearchName}
|
initialName={initialSearchName}
|
||||||
loading={searchRenameLoading}
|
loading={searchRenameLoading}
|
||||||
title={<IconFont name="search" className="size-6"></IconFont>}
|
title={initialSearchName || t('createSearch')}
|
||||||
></RenameDialog>
|
></RenameDialog>
|
||||||
)}
|
)}
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { Button } from '@/components/ui/button';
|
|||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
|
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
|
||||||
import { Settings, Trash2 } from 'lucide-react';
|
import { Settings, Trash2 } from 'lucide-react';
|
||||||
|
import { useDataSourceInfo } from '../contant';
|
||||||
import { useDeleteDataSource } from '../hooks';
|
import { useDeleteDataSource } from '../hooks';
|
||||||
import { IDataSorceInfo, IDataSourceBase } from '../interface';
|
import { IDataSorceInfo, IDataSourceBase } from '../interface';
|
||||||
import { delSourceModal } from './delete-source-modal';
|
import { delSourceModal } from './delete-source-modal';
|
||||||
@ -13,6 +14,7 @@ export const AddedSourceCard = (props: IAddedSourceCardProps) => {
|
|||||||
const { list, name, icon } = props;
|
const { list, name, icon } = props;
|
||||||
const { handleDelete } = useDeleteDataSource();
|
const { handleDelete } = useDeleteDataSource();
|
||||||
const { navigateToDataSourceDetail } = useNavigatePage();
|
const { navigateToDataSourceDetail } = useNavigatePage();
|
||||||
|
const { dataSourceInfo } = useDataSourceInfo();
|
||||||
const toDetail = (id: string) => {
|
const toDetail = (id: string) => {
|
||||||
navigateToDataSourceDetail(id);
|
navigateToDataSourceDetail(id);
|
||||||
};
|
};
|
||||||
@ -49,6 +51,7 @@ export const AddedSourceCard = (props: IAddedSourceCardProps) => {
|
|||||||
onClick={() =>
|
onClick={() =>
|
||||||
delSourceModal({
|
delSourceModal({
|
||||||
data: item,
|
data: item,
|
||||||
|
dataSourceInfo: dataSourceInfo,
|
||||||
onOk: () => {
|
onOk: () => {
|
||||||
handleDelete(item);
|
handleDelete(item);
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,20 +1,19 @@
|
|||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Modal, ModalType } from '@/components/ui/modal/modal';
|
import { Modal, ModalType } from '@/components/ui/modal/modal';
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
import { DataSourceInfo } from '../contant';
|
import { IDataSourceBase, IDataSourceInfoMap } from '../interface';
|
||||||
import { IDataSourceBase } from '../interface';
|
|
||||||
|
|
||||||
export type IDelSourceModalProps<T> = Partial<ModalType> & {
|
export type IDelSourceModalProps<T> = Partial<ModalType> & {
|
||||||
data?: T;
|
data?: T;
|
||||||
type?: 'delete' | 'unlink';
|
type?: 'delete' | 'unlink';
|
||||||
onOk?: (data?: T) => void;
|
onOk?: (data?: T) => void;
|
||||||
|
dataSourceInfo: IDataSourceInfoMap;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const delSourceModal = <T extends IDataSourceBase>(
|
export const delSourceModal = <T extends IDataSourceBase>(
|
||||||
props: IDelSourceModalProps<T>,
|
props: IDelSourceModalProps<T>,
|
||||||
) => {
|
) => {
|
||||||
const { data, onOk, type = 'delete', ...otherProps } = props;
|
const { data, onOk, type = 'delete', dataSourceInfo, ...otherProps } = props;
|
||||||
console.log('data', data);
|
|
||||||
const config = {
|
const config = {
|
||||||
title:
|
title:
|
||||||
type === 'delete'
|
type === 'delete'
|
||||||
@ -39,7 +38,7 @@ export const delSourceModal = <T extends IDataSourceBase>(
|
|||||||
)}
|
)}
|
||||||
<div className="flex items-center gap-1 p-2 border border-border-button rounded-md mb-3">
|
<div className="flex items-center gap-1 p-2 border border-border-button rounded-md mb-3">
|
||||||
<div className="w-6 h-6 flex-shrink-0">
|
<div className="w-6 h-6 flex-shrink-0">
|
||||||
{data?.source ? DataSourceInfo[data?.source].icon : ''}
|
{data?.source ? dataSourceInfo[data?.source].icon : ''}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 text-text-secondary text-xs">
|
<div className="flex items-center gap-2 text-text-secondary text-xs">
|
||||||
{/* <div className="h-6 flex-shrink-0 text-text-primary text-base">
|
{/* <div className="h-6 flex-shrink-0 text-text-primary text-base">
|
||||||
|
|||||||
@ -1,11 +1,13 @@
|
|||||||
import { FormFieldType } from '@/components/dynamic-form';
|
import { FormFieldType } from '@/components/dynamic-form';
|
||||||
import SvgIcon from '@/components/svg-icon';
|
import SvgIcon from '@/components/svg-icon';
|
||||||
import { t } from 'i18next';
|
import { t, TFunction } from 'i18next';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import BoxTokenField from './component/box-token-field';
|
import BoxTokenField from './component/box-token-field';
|
||||||
import { ConfluenceIndexingModeField } from './component/confluence-token-field';
|
import { ConfluenceIndexingModeField } from './component/confluence-token-field';
|
||||||
import GmailTokenField from './component/gmail-token-field';
|
import GmailTokenField from './component/gmail-token-field';
|
||||||
import GoogleDriveTokenField from './component/google-drive-token-field';
|
import GoogleDriveTokenField from './component/google-drive-token-field';
|
||||||
|
import { IDataSourceInfoMap } from './interface';
|
||||||
export enum DataSourceKey {
|
export enum DataSourceKey {
|
||||||
CONFLUENCE = 'confluence',
|
CONFLUENCE = 'confluence',
|
||||||
S3 = 's3',
|
S3 = 's3',
|
||||||
@ -23,62 +25,75 @@ export enum DataSourceKey {
|
|||||||
// TEAMS = 'teams',
|
// TEAMS = 'teams',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DataSourceInfo = {
|
export const generateDataSourceInfo = (t: TFunction) => {
|
||||||
[DataSourceKey.S3]: {
|
return {
|
||||||
name: 'S3',
|
[DataSourceKey.S3]: {
|
||||||
description: t(`setting.${DataSourceKey.S3}Description`),
|
name: 'S3',
|
||||||
icon: <SvgIcon name={'data-source/s3'} width={38} />,
|
description: t(`setting.${DataSourceKey.S3}Description`),
|
||||||
},
|
icon: <SvgIcon name={'data-source/s3'} width={38} />,
|
||||||
[DataSourceKey.NOTION]: {
|
},
|
||||||
name: 'Notion',
|
[DataSourceKey.NOTION]: {
|
||||||
description: t(`setting.${DataSourceKey.NOTION}Description`),
|
name: 'Notion',
|
||||||
icon: <SvgIcon name={'data-source/notion'} width={38} />,
|
description: t(`setting.${DataSourceKey.NOTION}Description`),
|
||||||
},
|
icon: <SvgIcon name={'data-source/notion'} width={38} />,
|
||||||
[DataSourceKey.DISCORD]: {
|
},
|
||||||
name: 'Discord',
|
[DataSourceKey.DISCORD]: {
|
||||||
description: t(`setting.${DataSourceKey.DISCORD}Description`),
|
name: 'Discord',
|
||||||
icon: <SvgIcon name={'data-source/discord'} width={38} />,
|
description: t(`setting.${DataSourceKey.DISCORD}Description`),
|
||||||
},
|
icon: <SvgIcon name={'data-source/discord'} width={38} />,
|
||||||
[DataSourceKey.CONFLUENCE]: {
|
},
|
||||||
name: 'Confluence',
|
[DataSourceKey.CONFLUENCE]: {
|
||||||
description: t(`setting.${DataSourceKey.CONFLUENCE}Description`),
|
name: 'Confluence',
|
||||||
icon: <SvgIcon name={'data-source/confluence'} width={38} />,
|
description: t(`setting.${DataSourceKey.CONFLUENCE}Description`),
|
||||||
},
|
icon: <SvgIcon name={'data-source/confluence'} width={38} />,
|
||||||
[DataSourceKey.GOOGLE_DRIVE]: {
|
},
|
||||||
name: 'Google Drive',
|
[DataSourceKey.GOOGLE_DRIVE]: {
|
||||||
description: t(`setting.${DataSourceKey.GOOGLE_DRIVE}Description`),
|
name: 'Google Drive',
|
||||||
icon: <SvgIcon name={'data-source/google-drive'} width={38} />,
|
description: t(`setting.${DataSourceKey.GOOGLE_DRIVE}Description`),
|
||||||
},
|
icon: <SvgIcon name={'data-source/google-drive'} width={38} />,
|
||||||
[DataSourceKey.GMAIL]: {
|
},
|
||||||
name: 'Gmail',
|
[DataSourceKey.GMAIL]: {
|
||||||
description: t(`setting.${DataSourceKey.GMAIL}Description`),
|
name: 'Gmail',
|
||||||
icon: <SvgIcon name={'data-source/gmail'} width={38} />,
|
description: t(`setting.${DataSourceKey.GMAIL}Description`),
|
||||||
},
|
icon: <SvgIcon name={'data-source/gmail'} width={38} />,
|
||||||
[DataSourceKey.MOODLE]: {
|
},
|
||||||
name: 'Moodle',
|
[DataSourceKey.MOODLE]: {
|
||||||
description: t(`setting.${DataSourceKey.MOODLE}Description`),
|
name: 'Moodle',
|
||||||
icon: <SvgIcon name={'data-source/moodle'} width={38} />,
|
description: t(`setting.${DataSourceKey.MOODLE}Description`),
|
||||||
},
|
icon: <SvgIcon name={'data-source/moodle'} width={38} />,
|
||||||
[DataSourceKey.JIRA]: {
|
},
|
||||||
name: 'Jira',
|
[DataSourceKey.JIRA]: {
|
||||||
description: t(`setting.${DataSourceKey.JIRA}Description`),
|
name: 'Jira',
|
||||||
icon: <SvgIcon name={'data-source/jira'} width={38} />,
|
description: t(`setting.${DataSourceKey.JIRA}Description`),
|
||||||
},
|
icon: <SvgIcon name={'data-source/jira'} width={38} />,
|
||||||
[DataSourceKey.WEBDAV]: {
|
},
|
||||||
name: 'WebDAV',
|
[DataSourceKey.WEBDAV]: {
|
||||||
description: t(`setting.${DataSourceKey.WEBDAV}Description`),
|
name: 'WebDAV',
|
||||||
icon: <SvgIcon name={'data-source/webdav'} width={38} />,
|
description: t(`setting.${DataSourceKey.WEBDAV}Description`),
|
||||||
},
|
icon: <SvgIcon name={'data-source/webdav'} width={38} />,
|
||||||
[DataSourceKey.DROPBOX]: {
|
},
|
||||||
name: 'Dropbox',
|
[DataSourceKey.DROPBOX]: {
|
||||||
description: t(`setting.${DataSourceKey.DROPBOX}Description`),
|
name: 'Dropbox',
|
||||||
icon: <SvgIcon name={'data-source/dropbox'} width={38} />,
|
description: t(`setting.${DataSourceKey.DROPBOX}Description`),
|
||||||
},
|
icon: <SvgIcon name={'data-source/dropbox'} width={38} />,
|
||||||
[DataSourceKey.BOX]: {
|
},
|
||||||
name: 'Box',
|
[DataSourceKey.BOX]: {
|
||||||
description: t(`setting.${DataSourceKey.BOX}Description`),
|
name: 'Box',
|
||||||
icon: <SvgIcon name={'data-source/box'} width={38} />,
|
description: t(`setting.${DataSourceKey.BOX}Description`),
|
||||||
},
|
icon: <SvgIcon name={'data-source/box'} width={38} />,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useDataSourceInfo = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [dataSourceInfo, setDataSourceInfo] = useState<IDataSourceInfoMap>(
|
||||||
|
generateDataSourceInfo(t) as IDataSourceInfoMap,
|
||||||
|
);
|
||||||
|
useEffect(() => {
|
||||||
|
setDataSourceInfo(generateDataSourceInfo(t));
|
||||||
|
}, [t]);
|
||||||
|
return { dataSourceInfo };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DataSourceFormBaseFields = [
|
export const DataSourceFormBaseFields = [
|
||||||
|
|||||||
@ -18,7 +18,7 @@ import {
|
|||||||
DataSourceFormBaseFields,
|
DataSourceFormBaseFields,
|
||||||
DataSourceFormDefaultValues,
|
DataSourceFormDefaultValues,
|
||||||
DataSourceFormFields,
|
DataSourceFormFields,
|
||||||
DataSourceInfo,
|
useDataSourceInfo,
|
||||||
} from '../contant';
|
} from '../contant';
|
||||||
import {
|
import {
|
||||||
useAddDataSource,
|
useAddDataSource,
|
||||||
@ -32,10 +32,10 @@ const SourceDetailPage = () => {
|
|||||||
|
|
||||||
const { data: detail } = useFetchDataSourceDetail();
|
const { data: detail } = useFetchDataSourceDetail();
|
||||||
const { handleResume } = useDataSourceResume();
|
const { handleResume } = useDataSourceResume();
|
||||||
|
const { dataSourceInfo } = useDataSourceInfo();
|
||||||
const detailInfo = useMemo(() => {
|
const detailInfo = useMemo(() => {
|
||||||
if (detail) {
|
if (detail) {
|
||||||
return DataSourceInfo[detail.source];
|
return dataSourceInfo[detail.source];
|
||||||
}
|
}
|
||||||
}, [detail]);
|
}, [detail]);
|
||||||
|
|
||||||
|
|||||||
@ -12,10 +12,11 @@ import { useQuery, useQueryClient } from '@tanstack/react-query';
|
|||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
import { useCallback, useMemo, useState } from 'react';
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
import { useParams, useSearchParams } from 'umi';
|
import { useParams, useSearchParams } from 'umi';
|
||||||
import { DataSourceInfo, DataSourceKey } from './contant';
|
import { DataSourceKey, useDataSourceInfo } from './contant';
|
||||||
import { IDataSorceInfo, IDataSource, IDataSourceBase } from './interface';
|
import { IDataSorceInfo, IDataSource, IDataSourceBase } from './interface';
|
||||||
|
|
||||||
export const useListDataSource = () => {
|
export const useListDataSource = () => {
|
||||||
|
const { dataSourceInfo } = useDataSourceInfo();
|
||||||
const { data: list, isFetching } = useQuery<IDataSource[]>({
|
const { data: list, isFetching } = useQuery<IDataSource[]>({
|
||||||
queryKey: ['data-source'],
|
queryKey: ['data-source'],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
@ -49,12 +50,12 @@ export const useListDataSource = () => {
|
|||||||
[];
|
[];
|
||||||
Object.keys(categorizedData).forEach((key: string) => {
|
Object.keys(categorizedData).forEach((key: string) => {
|
||||||
const k = key as DataSourceKey;
|
const k = key as DataSourceKey;
|
||||||
if (DataSourceInfo[k]) {
|
if (dataSourceInfo[k]) {
|
||||||
sourceList.push({
|
sourceList.push({
|
||||||
id: k,
|
id: k,
|
||||||
name: DataSourceInfo[k].name,
|
name: dataSourceInfo[k].name,
|
||||||
description: DataSourceInfo[k].description,
|
description: dataSourceInfo[k].description,
|
||||||
icon: DataSourceInfo[k].icon,
|
icon: dataSourceInfo[k].icon,
|
||||||
list: categorizedData[k] || [],
|
list: categorizedData[k] || [],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,21 +10,21 @@ import {
|
|||||||
} from '../components/user-setting-header';
|
} from '../components/user-setting-header';
|
||||||
import AddDataSourceModal from './add-datasource-modal';
|
import AddDataSourceModal from './add-datasource-modal';
|
||||||
import { AddedSourceCard } from './component/added-source-card';
|
import { AddedSourceCard } from './component/added-source-card';
|
||||||
import { DataSourceInfo, DataSourceKey } from './contant';
|
import { DataSourceKey, useDataSourceInfo } from './contant';
|
||||||
import { useAddDataSource, useListDataSource } from './hooks';
|
import { useAddDataSource, useListDataSource } from './hooks';
|
||||||
import { IDataSorceInfo } from './interface';
|
import { IDataSorceInfo } from './interface';
|
||||||
|
|
||||||
const dataSourceTemplates = Object.values(DataSourceKey).map((id) => {
|
|
||||||
return {
|
|
||||||
id,
|
|
||||||
name: DataSourceInfo[id].name,
|
|
||||||
description: DataSourceInfo[id].description,
|
|
||||||
icon: DataSourceInfo[id].icon,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const DataSource = () => {
|
const DataSource = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const { dataSourceInfo } = useDataSourceInfo();
|
||||||
|
const dataSourceTemplates = Object.values(DataSourceKey).map((id) => {
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
name: dataSourceInfo[id].name,
|
||||||
|
description: dataSourceInfo[id].description,
|
||||||
|
icon: dataSourceInfo[id].icon,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
// useListTenantUser();
|
// useListTenantUser();
|
||||||
const { categorizedList } = useListDataSource();
|
const { categorizedList } = useListDataSource();
|
||||||
|
|||||||
@ -43,3 +43,11 @@ export interface IDataSourceLog {
|
|||||||
tenant_id: string;
|
tenant_id: string;
|
||||||
timeout_secs: number;
|
timeout_secs: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface IDataSourceInfoItem {
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
icon: JSX.Element;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type IDataSourceInfoMap = Record<DataSourceKey, IDataSourceInfoItem>;
|
||||||
|
|||||||
@ -11,12 +11,13 @@ import {
|
|||||||
} from '@/hooks/use-user-setting-request';
|
} from '@/hooks/use-user-setting-request';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { Routes } from '@/routes';
|
import { Routes } from '@/routes';
|
||||||
import { t } from 'i18next';
|
import { TFunction } from 'i18next';
|
||||||
import { Banknote, Box, Server, Unplug, User, Users } from 'lucide-react';
|
import { Banknote, Box, Server, Unplug, User, Users } from 'lucide-react';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useHandleMenuClick } from './hooks';
|
import { useHandleMenuClick } from './hooks';
|
||||||
|
|
||||||
const menuItems = [
|
const menuItems = (t: TFunction) => [
|
||||||
{ icon: Server, label: t('setting.dataSources'), key: Routes.DataSource },
|
{ icon: Server, label: t('setting.dataSources'), key: Routes.DataSource },
|
||||||
{ icon: Box, label: t('setting.model'), key: Routes.Model },
|
{ icon: Box, label: t('setting.model'), key: Routes.Model },
|
||||||
{ icon: Banknote, label: 'MCP', key: Routes.Mcp },
|
{ icon: Banknote, label: 'MCP', key: Routes.Mcp },
|
||||||
@ -32,12 +33,12 @@ const menuItems = [
|
|||||||
// { icon: Cog, label: t('setting.system'), key: Routes.System },
|
// { icon: Cog, label: t('setting.system'), key: Routes.System },
|
||||||
// { icon: Banknote, label: 'Plan', key: Routes.Plan },
|
// { icon: Banknote, label: 'Plan', key: Routes.Plan },
|
||||||
];
|
];
|
||||||
|
|
||||||
export function SideBar() {
|
export function SideBar() {
|
||||||
const pathName = useSecondPathName();
|
const pathName = useSecondPathName();
|
||||||
const { data: userInfo } = useFetchUserInfo();
|
const { data: userInfo } = useFetchUserInfo();
|
||||||
const { handleMenuClick, active } = useHandleMenuClick();
|
const { handleMenuClick, active } = useHandleMenuClick();
|
||||||
const { version, fetchSystemVersion } = useFetchSystemVersion();
|
const { version, fetchSystemVersion } = useFetchSystemVersion();
|
||||||
|
const { t } = useTranslation();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (location.host !== Domain) {
|
if (location.host !== Domain) {
|
||||||
fetchSystemVersion();
|
fetchSystemVersion();
|
||||||
@ -56,7 +57,7 @@ export function SideBar() {
|
|||||||
<p className="text-sm text-text-primary">{userInfo?.email}</p>
|
<p className="text-sm text-text-primary">{userInfo?.email}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 overflow-auto">
|
<div className="flex-1 overflow-auto">
|
||||||
{menuItems.map((item, idx) => {
|
{menuItems(t).map((item, idx) => {
|
||||||
const hoverKey = pathName === item.key;
|
const hoverKey = pathName === item.key;
|
||||||
return (
|
return (
|
||||||
<div key={idx}>
|
<div key={idx}>
|
||||||
|
|||||||
Reference in New Issue
Block a user