mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-11-10 21:32:43 +08:00
feat(web): add enterprise (ee) seam layer for build-time overlays (#21908)
This commit is contained in:
1 parent
5dc7683aa4
commit
d543182eab
136 files changed
+2534
-572
No files matched your search
+3
-1
@@ -31,10 +31,11 @@ import weekYear from 'dayjs/plugin/weekYear';
|
||||
import weekday from 'dayjs/plugin/weekday';
|
||||
import React from 'react';
|
||||
import { RouterProvider } from 'react-router';
|
||||
import StarModal from '@/ee/components/star-modal';
|
||||
import { routers } from '@/ee/merge-routes';
|
||||
import { ThemeProvider } from './components/theme-provider';
|
||||
import { TooltipProvider } from './components/ui/tooltip';
|
||||
import { ThemeEnum } from './constants/common';
|
||||
import { routers } from './routes';
|
||||
|
||||
import 'react-photo-view/dist/react-photo-view.css';
|
||||
|
||||
@@ -86,6 +87,7 @@ function Root({ children }: React.PropsWithChildren) {
|
||||
<Sonner position="top-right" expand richColors closeButton />
|
||||
|
||||
<Toaster />
|
||||
<StarModal></StarModal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { EnterpriseModalProvider } from '@/ee/components/modal-provider';
|
||||
import { ReactNode, useEffect, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
@@ -72,7 +73,13 @@ export const createPortalModal = () => {
|
||||
},
|
||||
...props,
|
||||
};
|
||||
root?.render(isVisible ? <PortalModal {...modalParam} /> : null);
|
||||
root?.render(
|
||||
isVisible ? (
|
||||
<EnterpriseModalProvider>
|
||||
<PortalModal {...modalParam} />
|
||||
</EnterpriseModalProvider>
|
||||
) : null,
|
||||
);
|
||||
};
|
||||
|
||||
const show = (props: PortalModalProps) => {
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { IModalProps } from '@/interfaces/common';
|
||||
|
||||
/**
|
||||
* Enterprise dialog for duplicating a resource. Open source has no duplicate
|
||||
* action, so the slot renders nothing; the shared call sites still mount it
|
||||
* with the same props.
|
||||
*/
|
||||
export default function DuplicateDialog(
|
||||
_props: IModalProps<any> & { initialName?: string },
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
/**
|
||||
* Provider wrapped around portal modals. The enterprise build replaces this
|
||||
* file with the React Query provider those modals need; open source renders
|
||||
* children directly.
|
||||
*/
|
||||
export function EnterpriseModalProvider({ children }: { children: ReactNode }) {
|
||||
return <>{children}</>;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { IModalProps } from '@/interfaces/common';
|
||||
|
||||
/**
|
||||
* Enterprise dialog for managing a resource's collaborators. Open source has
|
||||
* no collaborator permissions, so the slot renders nothing; the shared call
|
||||
* sites still mount it with the same props. The prop type stays opaque here
|
||||
* because the collaborator record only has a shape in the enterprise build.
|
||||
*/
|
||||
export function PrivilegeManagementDialog(
|
||||
_props: IModalProps<any> & {
|
||||
initialValues: Record<string, unknown>;
|
||||
},
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Enterprise slot inside the app root. The open-source build renders nothing;
|
||||
* the enterprise build replaces this file with the GitHub star prompt.
|
||||
*/
|
||||
const StarModal = () => null;
|
||||
|
||||
export default StarModal;
|
||||
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* Enterprise seam for collaborator permissions and team roles.
|
||||
*
|
||||
* The constants describe the enterprise permission bit set; open source keeps
|
||||
* them so shared code can compare against `operator_permission`, but every
|
||||
* resource is owned by its tenant, so the permission helpers treat the current
|
||||
* user as owner.
|
||||
*/
|
||||
|
||||
export enum TeamRole {
|
||||
Member = 'member',
|
||||
Department = 'department',
|
||||
Group = 'group',
|
||||
}
|
||||
|
||||
export const TeamRoleList = [
|
||||
TeamRole.Department,
|
||||
TeamRole.Member,
|
||||
TeamRole.Group,
|
||||
];
|
||||
|
||||
export enum Permission {
|
||||
Read = 1,
|
||||
Write = 2,
|
||||
Manage = 4,
|
||||
Owner = 8,
|
||||
}
|
||||
|
||||
export const PermissionList = [
|
||||
Permission.Read,
|
||||
Permission.Write,
|
||||
Permission.Manage,
|
||||
];
|
||||
|
||||
export function hasResourcePermission(permission: number, action: Permission) {
|
||||
return (permission & action) === action;
|
||||
}
|
||||
|
||||
export function getResourcePermissionActions(permission: number) {
|
||||
if (hasResourcePermission(permission, Permission.Owner)) {
|
||||
return [Permission.Owner];
|
||||
}
|
||||
return PermissionList.filter((action) =>
|
||||
hasResourcePermission(permission, action),
|
||||
);
|
||||
}
|
||||
|
||||
export function hasResourceOwnerPermission(
|
||||
permissions?: Record<string, number>,
|
||||
) {
|
||||
return Object.values(permissions ?? {}).some((permission) =>
|
||||
hasResourcePermission(permission, Permission.Owner),
|
||||
);
|
||||
}
|
||||
|
||||
export const LabelMap = {
|
||||
[Permission.Read]: 'read',
|
||||
[Permission.Write]: 'write',
|
||||
[Permission.Manage]: 'manage',
|
||||
[Permission.Owner]: 'owner',
|
||||
};
|
||||
|
||||
export enum PermissionResourceType {
|
||||
KnowledgeBase = 'kb',
|
||||
LLM = 'llm',
|
||||
Search = 'search',
|
||||
Dialog = 'dialog',
|
||||
Document = 'document',
|
||||
Canvas = 'canvas',
|
||||
Memory = 'memory',
|
||||
MCP = 'mcp',
|
||||
File = 'file',
|
||||
Folder = 'folder',
|
||||
ModelInstance = 'model_instance',
|
||||
}
|
||||
|
||||
export enum TenantRole {
|
||||
Owner = 'owner',
|
||||
Invite = 'invite',
|
||||
Normal = 'normal',
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export const useEnterpriseAuthRedirect = (): string | undefined => undefined;
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Enterprise permission seam. The enterprise build overlays this file with
|
||||
* role-based gating; open source has no edit restrictions.
|
||||
*/
|
||||
|
||||
export function useDatasetEditButtonDisabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
export function useDatasetManageButtonDisabled() {
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Enterprise seam for the canvas presence tracking. The enterprise build
|
||||
* replaces this file with the presence requests; open source has no shared
|
||||
* canvas presence, so the presence is always empty and the actions are
|
||||
* no-ops.
|
||||
*/
|
||||
|
||||
import type { ICanvasPresenceResponse } from '@/interfaces/database/agent';
|
||||
|
||||
const emptyPresence: ICanvasPresenceResponse = {
|
||||
canvas_id: '',
|
||||
online_user_count: 0,
|
||||
users: [],
|
||||
};
|
||||
|
||||
export const useFetchCanvasPresence = () => ({
|
||||
data: emptyPresence,
|
||||
loading: false,
|
||||
refetch: async () => {},
|
||||
});
|
||||
|
||||
export const useJoinCanvasPresence = (_canvasId?: string) => ({
|
||||
joinPresence: async (_payload: unknown) => {},
|
||||
loading: false,
|
||||
});
|
||||
|
||||
export const useHeartbeatCanvasPresence = () => ({
|
||||
heartbeat: async (_payload: unknown) => null,
|
||||
loading: false,
|
||||
data: null,
|
||||
});
|
||||
|
||||
export const useLeaveCanvasPresence = () => ({
|
||||
leavePresence: async (_payload: unknown) => {},
|
||||
loading: false,
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Enterprise seam for the empty-model warning. Open source has no admin
|
||||
* approval flow, so the upstream modal is always the right outcome.
|
||||
*/
|
||||
export function useEmptyModelWarning() {
|
||||
return {
|
||||
isChecking: false,
|
||||
showWarning: () => false,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Enterprise seam for the private-LLM admin flags. The enterprise build
|
||||
* replaces this file with the private deployment requests; open source is not
|
||||
* a private deployment, so the admin flags are off and the default-model
|
||||
* action is a no-op. Model providers and the API page are part of the open
|
||||
* source product, so their exposure flags stay on.
|
||||
*/
|
||||
|
||||
export const useFetchEnableAdmin = () => ({ data: false, loading: false });
|
||||
|
||||
export const useFetchIsAdmin = () => ({ data: false, loading: false });
|
||||
|
||||
export const useSetDefaultLlm = () => ({
|
||||
data: undefined,
|
||||
loading: false,
|
||||
setDefaultLlm: async (_params: {
|
||||
llm_factory: string;
|
||||
llm_name: string;
|
||||
}) => {},
|
||||
});
|
||||
|
||||
export const useFetchExposeModelProvider = () => ({
|
||||
data: true,
|
||||
loading: false,
|
||||
});
|
||||
|
||||
export const useFetchExposeApi = () => ({ data: true, loading: false });
|
||||
@@ -0,0 +1,18 @@
|
||||
export type UploadBillingResponse = {
|
||||
code?: number;
|
||||
message?: string;
|
||||
detail?: { current?: number; limit?: number; [key: string]: any };
|
||||
};
|
||||
|
||||
/**
|
||||
* Enterprise seam for the file-upload billing errors. Open source has no
|
||||
* billing, so every upload failure is handled by the default error path.
|
||||
*/
|
||||
export function useUploadBillingError() {
|
||||
const handleUploadError = (_ret?: UploadBillingResponse) => {
|
||||
void _ret;
|
||||
return false;
|
||||
};
|
||||
|
||||
return { handleUploadError };
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Enterprise slot above the app shell. The enterprise build replaces this
|
||||
* file with the notification banner; open source renders nothing.
|
||||
*/
|
||||
export function NotificationBanner() {
|
||||
return null;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { Routes } from '@/routes';
|
||||
import { PropsWithChildren } from 'react';
|
||||
import { Link } from 'react-router';
|
||||
|
||||
/**
|
||||
* Enterprise slot around the notification bell. The enterprise build replaces
|
||||
* this file with the team invitation dialog; open source keeps the bell as a
|
||||
* shortcut to the team page.
|
||||
*/
|
||||
export function TeamInvitationReminderDialog({ children }: PropsWithChildren) {
|
||||
return (
|
||||
<Link
|
||||
to={`${Routes.UserSetting}${Routes.Team}`}
|
||||
className="inline-flex items-center"
|
||||
>
|
||||
{children}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import React, { PropsWithChildren, useMemo } from 'react';
|
||||
|
||||
export type RootLayoutChrome = {
|
||||
showHeader: boolean;
|
||||
containerRef?: React.Ref<HTMLDivElement>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Enterprise seams for the application shell. Open source has no notification
|
||||
* banner, no billing wrapper and always shows the header, so every seam leaves
|
||||
* the upstream layout untouched.
|
||||
*/
|
||||
export function RootLayoutBanner() {
|
||||
return null;
|
||||
}
|
||||
|
||||
export function RootLayoutContent({ children }: PropsWithChildren) {
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
export function useRootLayoutChrome(): RootLayoutChrome {
|
||||
return useMemo(() => ({ showHeader: true }), []);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Enterprise route seam (open-source side).
|
||||
*
|
||||
* The enterprise build overlays this file with its own implementation, which
|
||||
* merges the enterprise route tree into the tree upstream already built.
|
||||
* Open source renders the upstream router unchanged.
|
||||
*/
|
||||
import { routers } from '@/routes';
|
||||
|
||||
export { routers };
|
||||
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Enterprise admin 404 page. The enterprise build replaces this file with the
|
||||
* "feature not enabled" page; open source renders nothing because no shared
|
||||
* page is gated behind an enterprise admin toggle.
|
||||
*/
|
||||
export default function AdminNotFoundPage() {
|
||||
return null;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Enterprise seam for the admin system variables. The enterprise build
|
||||
* replaces this file with the request that reads and writes them; open source
|
||||
* has no system-variable admin UI, so the values stay empty except for the
|
||||
* whitelist flag that keeps the open source whitelist page reachable.
|
||||
*/
|
||||
const openSourceVariables: Record<string, { value?: unknown } | undefined> = {
|
||||
enable_whitelist: { value: true },
|
||||
};
|
||||
|
||||
export default function useAdminVariables() {
|
||||
return {
|
||||
variables: openSourceVariables,
|
||||
isFetching: false,
|
||||
refetch: async () => {},
|
||||
setVariables: async (_variables: Record<string, unknown>) => {},
|
||||
isUpdating: false,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Enterprise slot inside the admin user detail card. The enterprise build
|
||||
* replaces this file with the user's datasets/agents tabs; open source renders
|
||||
* nothing.
|
||||
*/
|
||||
export default function UserDetailAssets({ userId }: { userId?: string }) {
|
||||
void userId;
|
||||
return null;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
type AgentEditGuardProps = {
|
||||
operatorPermission: number;
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
/**
|
||||
* Enterprise seam for the agent canvas editing controls. Open source has no
|
||||
* collaborator permissions, so every control stays visible.
|
||||
*/
|
||||
export function AgentEditGuard({ children }: AgentEditGuardProps) {
|
||||
return <>{children}</>;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Enterprise seam for canvas edit permissions. Open source has no collaborator
|
||||
* permissions, so every user may edit and autosave the agent.
|
||||
*/
|
||||
export function canEditAgent(_operatorPermission: number) {
|
||||
void _operatorPermission;
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Enterprise slot inside the agent debug header. The enterprise build replaces
|
||||
* this file with the online-presence list; open source renders nothing.
|
||||
*/
|
||||
export function OnlineUsers() {
|
||||
return null;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { IFlow } from '@/interfaces/database/agent';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
type AgentDropdownGuardProps = {
|
||||
agent: IFlow;
|
||||
showPrivilegeModal(): void;
|
||||
manageItems: ReactNode;
|
||||
deleteItems: ReactNode;
|
||||
};
|
||||
|
||||
/**
|
||||
* Enterprise seam for the agent dropdown. Open source has no collaborator
|
||||
* permissions, so every action the shared file provides is rendered as-is.
|
||||
*/
|
||||
export function AgentDropdownGuard({
|
||||
manageItems,
|
||||
deleteItems,
|
||||
}: AgentDropdownGuardProps) {
|
||||
return (
|
||||
<>
|
||||
{manageItems}
|
||||
{deleteItems}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { IFlow } from '@/interfaces/database/agent';
|
||||
|
||||
/**
|
||||
* Enterprise seam for the agent permission dialog. Open source has no
|
||||
* collaborator permissions, so the modal stays closed and the record is empty.
|
||||
*/
|
||||
export function useShowPrivilegeDialog() {
|
||||
return {
|
||||
privilegeModalVisible: false,
|
||||
hidePrivilegeModal: () => {},
|
||||
handShowPrivilegeModal: (_item: IFlow) => () => {},
|
||||
recordWithSourceType: {},
|
||||
};
|
||||
}
|
||||
|
||||
export type ShowPrivilegeDialogReturnType = ReturnType<
|
||||
typeof useShowPrivilegeDialog
|
||||
>;
|
||||
@@ -0,0 +1,28 @@
|
||||
import {
|
||||
IBuiltInMetadataItem,
|
||||
IMetaDataReturnJSONSettings,
|
||||
} from '@/pages/dataset/components/metedata/interface';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
export type SaveBuiltinMetadataParams = {
|
||||
kbId?: string;
|
||||
documentId?: string;
|
||||
builtInMetadata?: IBuiltInMetadataItem[];
|
||||
metadata: IMetaDataReturnJSONSettings;
|
||||
};
|
||||
|
||||
/**
|
||||
* Enterprise seam for persisting built-in metadata alongside a single
|
||||
* document update. Open source has no collaborator permissions, so the shared
|
||||
* save flow already covers it.
|
||||
*/
|
||||
export function useSaveBuiltinMetadata() {
|
||||
const saveBuiltinMetadata = useCallback(
|
||||
async (_params: SaveBuiltinMetadataParams) => {
|
||||
void _params;
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
return { saveBuiltinMetadata };
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { IDocumentInfo } from '@/interfaces/database/document';
|
||||
|
||||
export type DocumentActionCellOverrides = {
|
||||
actionClassName?: string;
|
||||
};
|
||||
|
||||
export type DocumentPrivilegeActionProps = {
|
||||
record: IDocumentInfo;
|
||||
disabled: boolean;
|
||||
};
|
||||
|
||||
const overrides: DocumentActionCellOverrides = {};
|
||||
|
||||
/**
|
||||
* Enterprise seams for the document row actions. Open source keeps the upstream
|
||||
* hover-only controls and exposes no permission entry.
|
||||
*/
|
||||
export function useDocumentActionCellOverrides(): DocumentActionCellOverrides {
|
||||
return overrides;
|
||||
}
|
||||
|
||||
export function DocumentPrivilegeAction(_props: DocumentPrivilegeActionProps) {
|
||||
void _props;
|
||||
return null;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { ShowPrivilegeModalReturnType } from '@/ee/pages/dataset/dataset/use-show-privilege-dialog';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
export type BulkOperationAction = {
|
||||
id: string;
|
||||
label: string;
|
||||
icon: ReactNode;
|
||||
onClick?: () => void;
|
||||
};
|
||||
|
||||
/**
|
||||
* Enterprise seam for dataset bulk-operation entries. Open source has no
|
||||
* collaborator permissions, so no extra entry is appended.
|
||||
*/
|
||||
export function useExtraBulkActions(
|
||||
_handShowPrivilegeModal: ShowPrivilegeModalReturnType['handShowPrivilegeModal'],
|
||||
_selectedRowKeys: string[],
|
||||
): BulkOperationAction[] {
|
||||
void _handShowPrivilegeModal;
|
||||
void _selectedRowKeys;
|
||||
return [];
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { IDocumentInfo } from '@/interfaces/database/document';
|
||||
import { createContext } from 'react';
|
||||
|
||||
/**
|
||||
* Enterprise seam for the document permission dialog. Open source has no
|
||||
* collaborator permissions, so the modal stays closed and the record is empty.
|
||||
*/
|
||||
export function useShowPrivilegeDialog() {
|
||||
return {
|
||||
privilegeModal: false,
|
||||
hidePrivilegeModal: () => {},
|
||||
handShowPrivilegeModal: (_item: IDocumentInfo | string[]) => () => {},
|
||||
recordWithSourceType: {},
|
||||
};
|
||||
}
|
||||
|
||||
export type ShowPrivilegeModalReturnType = ReturnType<
|
||||
typeof useShowPrivilegeDialog
|
||||
>;
|
||||
|
||||
export const ShowPrivilegeModalContext = createContext<
|
||||
Partial<ShowPrivilegeModalReturnType>
|
||||
>({});
|
||||
@@ -0,0 +1,25 @@
|
||||
import { IDataset } from '@/interfaces/database/dataset';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
type DatasetDropdownGuardProps = {
|
||||
dataset: IDataset;
|
||||
showPrivilegeModal(): void;
|
||||
manageItems: ReactNode;
|
||||
deleteItems: ReactNode;
|
||||
};
|
||||
|
||||
/**
|
||||
* Enterprise seam for the dataset dropdown. Open source has no collaborator
|
||||
* permissions, so every action the shared file provides is rendered as-is.
|
||||
*/
|
||||
export function DatasetDropdownGuard({
|
||||
manageItems,
|
||||
deleteItems,
|
||||
}: DatasetDropdownGuardProps) {
|
||||
return (
|
||||
<>
|
||||
{manageItems}
|
||||
{deleteItems}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { IDataset } from '@/interfaces/database/dataset';
|
||||
|
||||
/**
|
||||
* Enterprise seam for the dataset duplicate action. Open source has no
|
||||
* duplicate dialog, so the hook reports a permanently closed modal.
|
||||
*/
|
||||
export default function useDuplicateDataset() {
|
||||
return {
|
||||
isModalVisible: false,
|
||||
showModal: (_record: IDataset) => {},
|
||||
hideModal: () => {},
|
||||
initialName: '',
|
||||
onOk: async (_name: string) => {},
|
||||
loading: false,
|
||||
};
|
||||
}
|
||||
|
||||
export type DuplicateDatasetReturnType = ReturnType<typeof useDuplicateDataset>;
|
||||
@@ -0,0 +1,18 @@
|
||||
import { IDataset } from '@/interfaces/database/dataset';
|
||||
|
||||
/**
|
||||
* Enterprise seam for the dataset permission dialog. Open source has no
|
||||
* collaborator permissions, so the modal stays closed and the record is empty.
|
||||
*/
|
||||
export function useShowPrivilegeDialog() {
|
||||
return {
|
||||
privilegeModal: false,
|
||||
hidePrivilegeModal: () => {},
|
||||
handShowPrivilegeModal: (_item: IDataset) => () => {},
|
||||
recordWithSourceType: {},
|
||||
};
|
||||
}
|
||||
|
||||
export type ShowPrivilegeDialogReturnType = ReturnType<
|
||||
typeof useShowPrivilegeDialog
|
||||
>;
|
||||
@@ -0,0 +1,15 @@
|
||||
import { IFile } from '@/interfaces/database/file-manager';
|
||||
|
||||
type FilePrivilegeButtonProps = {
|
||||
record: IFile;
|
||||
showPrivilegeModal?(record: IFile): void;
|
||||
};
|
||||
|
||||
/**
|
||||
* Enterprise seam for the file permission entry point. Open source has no
|
||||
* collaborator permissions, so the control is never rendered.
|
||||
*/
|
||||
export function FilePrivilegeButton(_props: FilePrivilegeButtonProps) {
|
||||
void _props;
|
||||
return null;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { IMemory } from '@/pages/memories/interface';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
type MemoryDropdownGuardProps = {
|
||||
memory: IMemory;
|
||||
showPrivilegeModal(): void;
|
||||
manageItems: ReactNode;
|
||||
deleteItems: ReactNode;
|
||||
};
|
||||
|
||||
/**
|
||||
* Enterprise seam for the memory dropdown. Open source has no collaborator
|
||||
* permissions, so every action the shared file provides is rendered as-is.
|
||||
*/
|
||||
export function MemoryDropdownGuard({
|
||||
manageItems,
|
||||
deleteItems,
|
||||
}: MemoryDropdownGuardProps) {
|
||||
return (
|
||||
<>
|
||||
{manageItems}
|
||||
{deleteItems}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { IMemory } from '@/pages/memories/interface';
|
||||
|
||||
/**
|
||||
* Enterprise seam for the memory permission dialog. Open source has no
|
||||
* collaborator permissions, so the modal stays closed and the record is empty.
|
||||
*/
|
||||
export function useShowPrivilegeDialog() {
|
||||
return {
|
||||
privilegeModalVisible: false,
|
||||
hidePrivilegeModal: () => {},
|
||||
handShowPrivilegeModal: (_item: IMemory) => () => {},
|
||||
recordWithSourceType: {},
|
||||
};
|
||||
}
|
||||
|
||||
export type ShowPrivilegeDialogReturnType = ReturnType<
|
||||
typeof useShowPrivilegeDialog
|
||||
>;
|
||||
@@ -0,0 +1,25 @@
|
||||
import { IDialog } from '@/interfaces/database/chat';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
type ChatDropdownGuardProps = {
|
||||
chat: IDialog;
|
||||
showPrivilegeModal(): void;
|
||||
manageItems: ReactNode;
|
||||
deleteItems: ReactNode;
|
||||
};
|
||||
|
||||
/**
|
||||
* Enterprise seam for the chat dropdown. Open source has no collaborator
|
||||
* permissions, so every action the shared file provides is rendered as-is.
|
||||
*/
|
||||
export function ChatDropdownGuard({
|
||||
manageItems,
|
||||
deleteItems,
|
||||
}: ChatDropdownGuardProps) {
|
||||
return (
|
||||
<>
|
||||
{manageItems}
|
||||
{deleteItems}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Enterprise slot for the chat evaluation entry. The enterprise build replaces
|
||||
* this file with the evaluation button; open source has no evaluation module,
|
||||
* so the slot renders nothing.
|
||||
*/
|
||||
export function ChatEvaluationButton() {
|
||||
return null;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { IDialog } from '@/interfaces/database/chat';
|
||||
|
||||
/**
|
||||
* Enterprise seam for the chat permission dialog. Open source has no
|
||||
* collaborator permissions, so the modal stays closed and the record is empty.
|
||||
*/
|
||||
export function useShowPrivilegeDialog() {
|
||||
return {
|
||||
privilegeModalVisible: false,
|
||||
hidePrivilegeModal: () => {},
|
||||
privilegeRecord: {},
|
||||
handleShowPrivilegeModal: (_dialog: IDialog) => (_info?: any) => {},
|
||||
};
|
||||
}
|
||||
|
||||
export type ShowPrivilegeDialogReturnType = ReturnType<
|
||||
typeof useShowPrivilegeDialog
|
||||
>;
|
||||
@@ -0,0 +1,25 @@
|
||||
import { ISearchAppProps } from '@/pages/next-searches/hooks';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
type SearchDropdownGuardProps = {
|
||||
dataset: ISearchAppProps;
|
||||
showPrivilegeModal(): void;
|
||||
manageItems: ReactNode;
|
||||
deleteItems: ReactNode;
|
||||
};
|
||||
|
||||
/**
|
||||
* Enterprise seam for the search-app dropdown. Open source has no collaborator
|
||||
* permissions, so every action the shared file provides is rendered as-is.
|
||||
*/
|
||||
export function SearchDropdownGuard({
|
||||
manageItems,
|
||||
deleteItems,
|
||||
}: SearchDropdownGuardProps) {
|
||||
return (
|
||||
<>
|
||||
{manageItems}
|
||||
{deleteItems}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { ISearchAppProps } from '@/pages/next-searches/hooks';
|
||||
|
||||
/**
|
||||
* Enterprise seam for the search-app permission dialog. Open source has no
|
||||
* collaborator permissions, so the modal stays closed and the record is empty.
|
||||
*/
|
||||
export function useShowPrivilegeDialog() {
|
||||
return {
|
||||
privilegeModalVisible: false,
|
||||
hidePrivilegeModal: () => {},
|
||||
handShowPrivilegeModal: (_item: ISearchAppProps) => () => {},
|
||||
recordWithSourceType: {},
|
||||
};
|
||||
}
|
||||
|
||||
export type ShowPrivilegeDialogReturnType = ReturnType<
|
||||
typeof useShowPrivilegeDialog
|
||||
>;
|
||||
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Enterprise seam for the plan names. The enterprise build replaces this file
|
||||
* with the pricing catalogue; open source only needs the plan identifiers that
|
||||
* shared billing-gated code compares against.
|
||||
*/
|
||||
|
||||
export enum PriceName {
|
||||
Trial = 'Trial',
|
||||
Starter = 'Starter',
|
||||
Pro = 'Pro',
|
||||
Enterprise = 'Enterprise',
|
||||
}
|
||||
|
||||
export const PriceNameMapValue = {
|
||||
[PriceName.Trial]: 0,
|
||||
[PriceName.Starter]: 1,
|
||||
[PriceName.Pro]: 2,
|
||||
[PriceName.Enterprise]: 3,
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Enterprise seam for the upgrade prompts. The enterprise build replaces this
|
||||
* file with the events and modals that route the user to the price page; open
|
||||
* source has no billing, so every entry point is a no-op.
|
||||
*/
|
||||
|
||||
export const showUpgradeTipsModal = (_options?: unknown) => {};
|
||||
|
||||
export const showPriceConfirmModal = (_options?: unknown) => {};
|
||||
|
||||
export const showFreeUpgradeTipsModal = (_options?: unknown) => {};
|
||||
|
||||
export const showPriceModal = (_price?: unknown) => {};
|
||||
|
||||
export const useShowUpgradeTipsModal = () => ({
|
||||
upgradeTips: undefined,
|
||||
hideUpgradeTips: () => {},
|
||||
});
|
||||
|
||||
export const useShowFreeUpgradeTipsModal = () => ({
|
||||
freeUpgradeTips: undefined,
|
||||
hideFreeUpgradeTips: () => {},
|
||||
});
|
||||
|
||||
export const useShowConfirmPriceModal = () => ({
|
||||
confirmPrice: undefined,
|
||||
hideConfirmPrice: () => {},
|
||||
});
|
||||
|
||||
export const useComputedRouterChangeCount = () => 0;
|
||||
@@ -0,0 +1,34 @@
|
||||
import { ReactNode, createContext, useContext } from 'react';
|
||||
|
||||
/**
|
||||
* Enterprise seam for the global upgrade modal. The enterprise build replaces
|
||||
* this file with the price modal providers; open source never enables billing,
|
||||
* so the provider renders its children unchanged.
|
||||
*/
|
||||
|
||||
interface UpgradeModalContextType {
|
||||
isModalOpen: boolean;
|
||||
openModal: (modalType?: string, modalProps?: any) => void;
|
||||
closeModal: () => void;
|
||||
}
|
||||
|
||||
const UpgradeModalContext = createContext<UpgradeModalContextType | undefined>(
|
||||
undefined,
|
||||
);
|
||||
|
||||
export const UpgradeModalProvider = ({
|
||||
children,
|
||||
}: {
|
||||
children?: ReactNode;
|
||||
}) => <>{children}</>;
|
||||
|
||||
export const useUpgradeModal = () => {
|
||||
const context = useContext(UpgradeModalContext);
|
||||
return (
|
||||
context ?? {
|
||||
isModalOpen: false,
|
||||
openModal: () => {},
|
||||
closeModal: () => {},
|
||||
}
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Enterprise seam for the billing plan requests. The enterprise build replaces
|
||||
* this file with the price catalogue requests; open source has no billing, so
|
||||
* the current plan is empty.
|
||||
*/
|
||||
|
||||
export const useFetchCurrentPlan = () => ({
|
||||
data: { plan_name: '' },
|
||||
loading: false,
|
||||
});
|
||||
|
||||
export const useFetchPlanList = () => ({
|
||||
data: [],
|
||||
loading: false,
|
||||
});
|
||||
|
||||
export const useCharge = () => ({
|
||||
charge: async (_params: unknown) => {},
|
||||
loading: false,
|
||||
});
|
||||
|
||||
export const useCancelPlan = () => ({
|
||||
cancelPlan: async (_params?: unknown) => {},
|
||||
loading: false,
|
||||
});
|
||||
|
||||
export const useDirectPaymentConvergence = () => {};
|
||||
|
||||
export const useHandleTrialUpgradeSetupRetry = (_status: string | null) => {};
|
||||
|
||||
export const useSetupIntentPayment = () => ({
|
||||
setupIntentPayment: async (_params?: unknown) => {},
|
||||
loading: false,
|
||||
});
|
||||
|
||||
export const getNextMonth = () => '';
|
||||
@@ -0,0 +1,57 @@
|
||||
import { IMcpServer } from '@/interfaces/database/mcp';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
type McpToolbarGuardProps = {
|
||||
mcpList: IMcpServer[];
|
||||
bulkManageButton: ReactNode;
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
type McpSelectionBarGuardProps = {
|
||||
mcpList: IMcpServer[];
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
type McpEmptyStateGuardProps = {
|
||||
mcpList: IMcpServer[];
|
||||
addButton: ReactNode;
|
||||
onClickAdd(): void;
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
/**
|
||||
* Enterprise seams for the MCP server list. Open source has no collaborator
|
||||
* permissions, so the toolbar, the bulk-selection bar and the empty state are
|
||||
* always available with every action.
|
||||
*/
|
||||
export function McpToolbarGuard({
|
||||
bulkManageButton,
|
||||
children,
|
||||
}: McpToolbarGuardProps) {
|
||||
return (
|
||||
<>
|
||||
{bulkManageButton}
|
||||
{children}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function McpSelectionBarGuard({ children }: McpSelectionBarGuardProps) {
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
export function McpEmptyStateGuard({
|
||||
addButton,
|
||||
onClickAdd,
|
||||
children,
|
||||
}: McpEmptyStateGuardProps) {
|
||||
return (
|
||||
<div
|
||||
className="flex items-center justify-between border border-dashed border-border-button rounded-md p-10 cursor-pointer w-[590px]"
|
||||
onClick={onClickAdd}
|
||||
>
|
||||
{children}
|
||||
{addButton}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { IMcpServer } from '@/interfaces/database/mcp';
|
||||
import { UseEditMcpReturnType } from '@/pages/user-setting/mcp/use-edit-mcp';
|
||||
import { PropsWithChildren } from 'react';
|
||||
|
||||
export type McpOperationShellProps = PropsWithChildren<
|
||||
{ mcp: IMcpServer } & Pick<UseEditMcpReturnType, 'showEditModal'> & {
|
||||
showPrivilegeModal(): void;
|
||||
}
|
||||
>;
|
||||
|
||||
/**
|
||||
* Enterprise seam for the MCP server card actions. Open source renders the
|
||||
* upstream hover controls unchanged.
|
||||
*/
|
||||
export function McpOperationShell({ children }: McpOperationShellProps) {
|
||||
return <>{children}</>;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { IMcpServer } from '@/interfaces/database/mcp';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
type McpSelectionGuardProps = {
|
||||
mcp: IMcpServer;
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
/**
|
||||
* Enterprise seam for the MCP card selection control. Open source has no
|
||||
* collaborator permissions, so every listed server is selectable.
|
||||
*/
|
||||
export function McpSelectionGuard({ children }: McpSelectionGuardProps) {
|
||||
return <>{children}</>;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { IMcpServer } from '@/interfaces/database/mcp';
|
||||
|
||||
/**
|
||||
* Enterprise seam for the MCP bulk-selection availability. Open source has no
|
||||
* collaborator permissions, so a selection never has to be exited because the
|
||||
* operator lost owner access.
|
||||
*/
|
||||
export function useExitMcpSelectionWithoutOwner(
|
||||
_mcpList: IMcpServer[],
|
||||
_isSelectionMode: boolean,
|
||||
_exitSelectionMode: () => void,
|
||||
) {
|
||||
void _mcpList;
|
||||
void _isSelectionMode;
|
||||
void _exitSelectionMode;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Enterprise seam for the MCP server edit permission. Open source has no
|
||||
* collaborator permissions, so every visible server stays editable.
|
||||
*/
|
||||
export function useMcpReadOnly(_operatorPermission?: number) {
|
||||
void _operatorPermission;
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { IMcpServer } from '@/interfaces/database/mcp';
|
||||
|
||||
/**
|
||||
* Enterprise seam for the MCP bulk-selection scope. Open source has no
|
||||
* collaborator permissions, so every listed server can be selected.
|
||||
*/
|
||||
export function useOwnerMcpList(mcpList: IMcpServer[]) {
|
||||
return mcpList;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { IMcpServer } from '@/interfaces/database/mcp';
|
||||
|
||||
/**
|
||||
* Enterprise seam for the MCP server permission dialog. Open source has no
|
||||
* collaborator permissions, so the modal stays closed and the record is empty.
|
||||
*/
|
||||
export function useShowPrivilegeDialog() {
|
||||
return {
|
||||
privilegeModal: false,
|
||||
hidePrivilegeModal: () => {},
|
||||
handShowPrivilegeModal: (_item: IMcpServer) => () => {},
|
||||
recordWithSourceType: {},
|
||||
};
|
||||
}
|
||||
|
||||
export type ShowPrivilegeDialogReturnType = ReturnType<
|
||||
typeof useShowPrivilegeDialog
|
||||
>;
|
||||
@@ -0,0 +1,26 @@
|
||||
import { IProviderInstance } from '@/interfaces/database/llm';
|
||||
import { ProviderInstanceCard } from '@/pages/user-setting/setting-model/instance-card/provider-instance-card';
|
||||
|
||||
type ProviderInstanceCardWithPrivilegeProps = {
|
||||
providerName: string;
|
||||
instance: IProviderInstance;
|
||||
defaultOpen: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Enterprise seam around a provider instance card. Open source has no
|
||||
* collaborator permissions, so the card renders unchanged.
|
||||
*/
|
||||
export function ProviderInstanceCardWithPrivilege({
|
||||
providerName,
|
||||
instance,
|
||||
defaultOpen,
|
||||
}: ProviderInstanceCardWithPrivilegeProps) {
|
||||
return (
|
||||
<ProviderInstanceCard
|
||||
providerName={providerName}
|
||||
instance={instance}
|
||||
defaultOpen={defaultOpen}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
.teamWrapper {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
.teamCard {
|
||||
// width: 100%;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import UserSettingTeam from '@/pages/user-setting/setting-team/legacy-page';
|
||||
|
||||
/**
|
||||
* Enterprise slot for the team management page. The enterprise build replaces
|
||||
* this file with the department/group/member tabs; open source keeps the
|
||||
* upstream team page.
|
||||
*/
|
||||
export function TeamManagement() {
|
||||
return <UserSettingTeam />;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { ReactNode, useMemo } from 'react';
|
||||
|
||||
export type SettingMenuEntry = {
|
||||
icon: ReactNode;
|
||||
label: string;
|
||||
key: string;
|
||||
'data-testid'?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Enterprise seam for the settings sidebar entries. Open source has no billing
|
||||
* page and no model-provider exposure rules, so the upstream menu is shown
|
||||
* unchanged.
|
||||
*/
|
||||
export function useSettingMenuItems<T extends SettingMenuEntry>(
|
||||
items: T[],
|
||||
): T[] {
|
||||
return useMemo(() => items, [items]);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import type { ComponentType, ReactNode } from 'react';
|
||||
|
||||
/**
|
||||
* Enterprise seam for the private route table. The enterprise build replaces
|
||||
* this file with the enterprise routes merged over the upstream tree; open
|
||||
* source only needs the path identifiers that shared code navigates to.
|
||||
*/
|
||||
export enum PrivateRoutes {
|
||||
Price = '/price',
|
||||
Billing = '/billing',
|
||||
EvaluationDetail = '/evaluation-detail',
|
||||
Evaluation = '/evaluation',
|
||||
Tickets = '/support',
|
||||
TicketCreate = '/support/create',
|
||||
TicketDetail = '/support/:id',
|
||||
AdminRoleConfigModelProviders = '/admin/roles/:roleName/model-providers',
|
||||
AdminModelUsageStatistics = '/admin/model-usage-statistics',
|
||||
AdminSettings = '/admin/settings',
|
||||
AdminSSOProviders = '/admin/sso',
|
||||
AdminModelProviders = '/admin/model-providers',
|
||||
}
|
||||
|
||||
export type PrivateRoute = {
|
||||
path?: string;
|
||||
Component?: () => Promise<{ default: ComponentType<any> }>;
|
||||
errorElement?: ReactNode;
|
||||
children?: PrivateRoute[];
|
||||
replace?: string[];
|
||||
matchIndex?: number;
|
||||
};
|
||||
|
||||
export const privateRoutes: PrivateRoute[] = [];
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Enterprise seam for the runtime billing status lookup.
|
||||
*
|
||||
* The enterprise build replaces this file with a request to the billing
|
||||
* endpoint so the header and price pages can gate on the server-side flag.
|
||||
* Open source has no billing: the status is always disabled and the boot
|
||||
* sequence does not wait on a request.
|
||||
*/
|
||||
|
||||
export const fetchBillingStatus = async (): Promise<boolean> => false;
|
||||
|
||||
export const isBillingEnabled = (): boolean | null => false;
|
||||
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Enterprise seam for resource-level permission checks.
|
||||
*
|
||||
* The enterprise build replaces this file with collaborator-aware checks based
|
||||
* on `operator_permission`. Open source has no collaborator permissions: every
|
||||
* resource belongs to its tenant and every user who can see it can edit and
|
||||
* delete it.
|
||||
*/
|
||||
|
||||
export function hasPreviewPermission(operatorPermission: number) {
|
||||
void operatorPermission;
|
||||
return false;
|
||||
}
|
||||
|
||||
export function hasEditPermission(operatorPermission: number) {
|
||||
void operatorPermission;
|
||||
return true;
|
||||
}
|
||||
|
||||
export function hasManagementPermission(operatorPermission: number) {
|
||||
void operatorPermission;
|
||||
return true;
|
||||
}
|
||||
|
||||
export function hasOwnerPermission(operatorPermission: number) {
|
||||
void operatorPermission;
|
||||
return true;
|
||||
}
|
||||
|
||||
export function showEditButton(operatorPermission: number) {
|
||||
void operatorPermission;
|
||||
return true;
|
||||
}
|
||||
|
||||
export function disableEditButton(operatorPermission: number) {
|
||||
void operatorPermission;
|
||||
return false;
|
||||
}
|
||||
|
||||
export function hasManagePermissionPermission(operatorPermission: number) {
|
||||
void operatorPermission;
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Enterprise seam for the navigation helpers used by auth guards and the
|
||||
* request interceptors.
|
||||
*
|
||||
* The enterprise build sends users back to login with a `redirect` query so
|
||||
* they land on the page they were viewing. Open source keeps the upstream
|
||||
* redirect target and has no post-login redirect hop.
|
||||
*/
|
||||
|
||||
export { redirectToLogin } from '@/utils/authorization-util';
|
||||
|
||||
export function redirectToSpecifiedPage(page: string) {
|
||||
window.location.href = location.origin + page;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Enterprise seam for response-error handling.
|
||||
*
|
||||
* The enterprise build inspects the response code to open the billing
|
||||
* upgrade modal and to keep license-related login errors quiet, then returns
|
||||
* `true` so the shared interceptor skips its generic notification.
|
||||
* Open source has neither, so every error keeps the upstream notification.
|
||||
*/
|
||||
|
||||
export type ResponseErrorContext = {
|
||||
url?: string;
|
||||
};
|
||||
|
||||
export const handleEnterpriseResponseError = async (
|
||||
_data: { code?: number; message?: string } | undefined,
|
||||
_context?: ResponseErrorContext,
|
||||
): Promise<boolean> => false;
|
||||
@@ -14,6 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useEnterpriseAuthRedirect } from '@/ee/hooks/auth-hooks-private';
|
||||
import authorizationUtil, {
|
||||
type CredentialStorage,
|
||||
} from '@/utils/authorization-util';
|
||||
@@ -22,11 +23,13 @@ import { useEffect, useState } from 'react';
|
||||
export const useAuth = (
|
||||
credentialStorage: CredentialStorage = authorizationUtil,
|
||||
) => {
|
||||
const redirectUrl = useEnterpriseAuthRedirect();
|
||||
|
||||
const [isLogin, setIsLogin] = useState<Nullable<boolean>>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setIsLogin(!!credentialStorage.getAuthorization());
|
||||
}, [credentialStorage]);
|
||||
|
||||
return { isLogin };
|
||||
return { isLogin, redirectUrl };
|
||||
};
|
||||
@@ -18,6 +18,7 @@ import { FileUploadProps } from '@/components/file-upload';
|
||||
import { useHandleFilterSubmit } from '@/components/list-filter-bar/use-handle-filter-submit';
|
||||
import message from '@/components/ui/message';
|
||||
import { AgentCategory, AgentGlobals } from '@/constants/agent';
|
||||
import { Permission } from '@/ee/constants/team';
|
||||
import { ListDeletionKey } from '@/constants/list-deletion';
|
||||
import { useFetchTenantInfo } from '@/hooks/use-user-setting-request';
|
||||
import {
|
||||
@@ -105,6 +106,10 @@ export const enum AgentApiAction {
|
||||
CreateAgentSession = 'createAgentSession',
|
||||
DeleteAgentSession = 'deleteAgentSession',
|
||||
FetchSessionByIdManually = 'fetchSessionByIdManually',
|
||||
FetchCanvasPresence = 'fetchCanvasPresence',
|
||||
JoinCanvasPresence = 'joinCanvasPresence',
|
||||
HeartbeatCanvasPresence = 'heartbeatCanvasPresence',
|
||||
LeaveCanvasPresence = 'leaveCanvasPresence',
|
||||
FetchAgentLog = 'fetchAgentLog',
|
||||
FetchSharedAgent = 'fetchSharedAgent',
|
||||
FetchAgentTags = 'fetchAgentTags',
|
||||
@@ -379,7 +384,7 @@ export const useFetchAgentTags = (canvasCategory?: string) => {
|
||||
},
|
||||
true,
|
||||
);
|
||||
return data?.data ?? [];
|
||||
return Array.isArray(data?.data) ? data.data : [];
|
||||
},
|
||||
});
|
||||
return { data, loading };
|
||||
@@ -1014,22 +1019,34 @@ export const useFetchAgentLog = (searchParams: IAgentLogsRequest) => {
|
||||
|
||||
export const AgentSessionKeys = {
|
||||
all: [AgentApiAction.FetchSessionsByCanvasId] as const,
|
||||
list: (canvasId?: string, tenantId?: string) =>
|
||||
[...AgentSessionKeys.all, canvasId, tenantId] as const,
|
||||
list: (
|
||||
canvasId?: string,
|
||||
tenantId?: string,
|
||||
operatorPermission?: Permission,
|
||||
) =>
|
||||
[...AgentSessionKeys.all, canvasId, tenantId, operatorPermission] as const,
|
||||
};
|
||||
|
||||
export const useFetchSessionsByCanvasId = () => {
|
||||
const { id: canvasId } = useParams();
|
||||
const { data: tenantInfo } = useFetchTenantInfo();
|
||||
const { data: agentDetail } = useFetchAgent();
|
||||
const operatorPermission = agentDetail?.operator_permission;
|
||||
|
||||
return useSessionList<IAgentLogResponse>({
|
||||
queryKey: AgentSessionKeys.list(canvasId, tenantInfo.tenant_id),
|
||||
enabled: !!canvasId && !isEmpty(tenantInfo),
|
||||
queryKey: AgentSessionKeys.list(
|
||||
canvasId,
|
||||
tenantInfo.tenant_id,
|
||||
operatorPermission,
|
||||
),
|
||||
enabled:
|
||||
!!canvasId && !isEmpty(tenantInfo) && operatorPermission !== undefined,
|
||||
fetchPage: async (params) => {
|
||||
const shouldListAllSessions = operatorPermission !== Permission.Read;
|
||||
const { data } = await fetchAgentLogsByCanvasId(canvasId!, {
|
||||
...params,
|
||||
include_history: false,
|
||||
exp_user_id: tenantInfo.tenant_id,
|
||||
...(shouldListAllSessions ? {} : { exp_user_id: tenantInfo.tenant_id }),
|
||||
});
|
||||
|
||||
return { total: data?.total ?? 0, items: data?.data ?? [] };
|
||||
|
||||
@@ -92,6 +92,11 @@ export const useUploadFile = () => {
|
||||
formData.append('path', pathList[index]);
|
||||
});
|
||||
try {
|
||||
// Return the full response (code + message + detail) so the caller
|
||||
// can dispatch the upgrade modal for billing codes 2000-2007. The
|
||||
// global request interceptor already shows the modal for 2000-2005,
|
||||
// but returning the full payload keeps the page-level handler
|
||||
// consistent with the dataset upload flow.
|
||||
const ret = await fileManagerService.uploadFile(formData);
|
||||
if (ret?.data.code === 0) {
|
||||
message.success(t('message.uploaded'));
|
||||
@@ -100,9 +105,9 @@ export const useUploadFile = () => {
|
||||
queryKey: [FileApiAction.FetchFileList],
|
||||
});
|
||||
}
|
||||
return ret?.data?.code;
|
||||
return ret?.data;
|
||||
} catch {
|
||||
return;
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
import message from '@/components/ui/message';
|
||||
import { TenantRole } from '@/ee/constants/team';
|
||||
import { ResponseGetType } from '@/interfaces/database/base';
|
||||
import { IToken } from '@/interfaces/database/chat';
|
||||
import { ITenantInfo } from '@/interfaces/database/dataset';
|
||||
@@ -83,18 +84,27 @@ export const useFetchUserInfo = (): ResponseGetType<IUserInfo> => {
|
||||
return { data, loading };
|
||||
};
|
||||
|
||||
export const useFetchTenantData = (): ResponseGetType<ITenantInfo> => {
|
||||
/**
|
||||
* @param enabled Set to `false` where the tenant endpoint is not part of the
|
||||
* page's contract (e.g. the admin gateway, which serves every tenant and
|
||||
* has no `/users/me/models`). Defaults to `true`, so existing callers are
|
||||
* unchanged.
|
||||
*/
|
||||
export const useFetchTenantData = (
|
||||
enabled: boolean = true,
|
||||
): ResponseGetType<ITenantInfo> => {
|
||||
const { data, isFetching: loading } = useQuery({
|
||||
queryKey: [UserSettingApiAction.TenantInfo],
|
||||
initialData: {},
|
||||
gcTime: 0,
|
||||
enabled,
|
||||
queryFn: async () => {
|
||||
const { data: res } = await userService.getTenantInfo();
|
||||
if (res.code === 0) {
|
||||
return res.data ?? {};
|
||||
}
|
||||
|
||||
return res;
|
||||
return res ?? {};
|
||||
},
|
||||
});
|
||||
return { data, loading };
|
||||
@@ -311,9 +321,10 @@ export const useCreateSystemToken = () => {
|
||||
return { data, loading, createToken: mutateAsync };
|
||||
};
|
||||
|
||||
export const useListTenantUser = () => {
|
||||
const { data: tenantInfo } = useFetchTenantInfo();
|
||||
const tenantId = tenantInfo.tenant_id;
|
||||
export const useListTenantUser = (
|
||||
tenantId: string,
|
||||
excludePendingInvitations = false,
|
||||
) => {
|
||||
const {
|
||||
data,
|
||||
isFetching: loading,
|
||||
@@ -323,6 +334,10 @@ export const useListTenantUser = () => {
|
||||
initialData: [],
|
||||
gcTime: 0,
|
||||
enabled: !!tenantId,
|
||||
select: (data) =>
|
||||
excludePendingInvitations
|
||||
? data.filter((x) => x.role !== TenantRole.Invite)
|
||||
: data,
|
||||
queryFn: async () => {
|
||||
const { data } = await listTenantUser(tenantId);
|
||||
|
||||
@@ -380,9 +395,6 @@ export const useDeleteTenantUser = () => {
|
||||
});
|
||||
if (data.code === 0) {
|
||||
message.success(t('message.deleted'));
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [UserSettingApiAction.ListTenantUser],
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [UserSettingApiAction.ListTenant],
|
||||
});
|
||||
@@ -409,7 +421,7 @@ export const useListTenant = () => {
|
||||
queryFn: async () => {
|
||||
const { data } = await listTenant();
|
||||
|
||||
return data?.data ?? [];
|
||||
return Array.isArray(data?.data) ? data.data : [];
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
import { Modal } from '@/components/ui/modal/modal';
|
||||
import { useEmptyModelWarning } from '@/ee/hooks/use-empty-model-warning';
|
||||
import DOMPurify from 'dompurify';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { useEffect, useRef } from 'react';
|
||||
@@ -32,6 +33,7 @@ export const useWarnEmptyModel = (
|
||||
const { t } = useTranslation();
|
||||
const warnedRef = useRef(false);
|
||||
const { navigateToModelSetting } = useNavigatePage();
|
||||
const { isChecking, showWarning } = useEmptyModelWarning();
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
@@ -39,10 +41,15 @@ export const useWarnEmptyModel = (
|
||||
!warnedRef.current &&
|
||||
!isWarningVisible &&
|
||||
!loading &&
|
||||
!isChecking &&
|
||||
(isEmpty(embdId) || isEmpty(llmId)) &&
|
||||
typeof embdId === 'string' &&
|
||||
typeof llmId === 'string'
|
||||
) {
|
||||
if (showWarning()) {
|
||||
return;
|
||||
}
|
||||
|
||||
warnedRef.current = true;
|
||||
isWarningVisible = true;
|
||||
Modal.warning({
|
||||
@@ -62,5 +69,14 @@ export const useWarnEmptyModel = (
|
||||
},
|
||||
});
|
||||
}
|
||||
}, [showEmptyModelWarn, embdId, llmId, loading, navigateToModelSetting, t]);
|
||||
}, [
|
||||
showEmptyModelWarn,
|
||||
embdId,
|
||||
llmId,
|
||||
loading,
|
||||
navigateToModelSetting,
|
||||
t,
|
||||
isChecking,
|
||||
showWarning,
|
||||
]);
|
||||
};
|
||||
@@ -85,6 +85,31 @@ export declare interface IFlow {
|
||||
tags?: string;
|
||||
}
|
||||
|
||||
export interface ICanvasPresenceUser {
|
||||
user_id: string;
|
||||
display_name: string;
|
||||
active_tab_count: number;
|
||||
last_seen_at: number;
|
||||
permission: number;
|
||||
}
|
||||
|
||||
export interface ICanvasPresenceResponse {
|
||||
canvas_id: string;
|
||||
online_user_count: number;
|
||||
operator_permission?: number;
|
||||
users: ICanvasPresenceUser[];
|
||||
}
|
||||
|
||||
export interface ICanvasPresenceHeartbeatData {
|
||||
ok: boolean;
|
||||
code?: string;
|
||||
}
|
||||
|
||||
export interface IPresencePayload {
|
||||
canvas_id: string;
|
||||
tab_id: string;
|
||||
presence_session_id: string;
|
||||
}
|
||||
// GET /agents merges compilation template groups into the agent list when no
|
||||
// canvas_category is requested; every item carries this discriminator.
|
||||
export enum AgentListItemType {
|
||||
|
||||
@@ -108,13 +108,16 @@ export interface IDialog {
|
||||
similarity_threshold: number;
|
||||
top_k: number;
|
||||
top_n: number;
|
||||
meta_data_filter?: MetaDataFilter;
|
||||
operator_permission: number;
|
||||
nickname: string;
|
||||
prefetch_size: number;
|
||||
rerank_candidates_count: number;
|
||||
rerank_id?: string;
|
||||
tenant_rerank_id?: string;
|
||||
meta_data_filter: MetaDataFilter;
|
||||
}
|
||||
|
||||
interface MetaDataFilter {
|
||||
export interface MetaDataFilter {
|
||||
manual: Manual[];
|
||||
method: string;
|
||||
}
|
||||
@@ -158,11 +161,17 @@ export interface Message {
|
||||
export interface IReferenceChunk {
|
||||
citation_id?: string;
|
||||
id: string;
|
||||
chunk_id?: string;
|
||||
content: null;
|
||||
content_with_weight?: string;
|
||||
document_id: string;
|
||||
doc_id?: string;
|
||||
document_name: string;
|
||||
docnm_kwd?: string;
|
||||
dataset_id: string;
|
||||
kb_id?: string;
|
||||
image_id: string;
|
||||
img_id?: string;
|
||||
similarity: number;
|
||||
vector_similarity: number;
|
||||
term_similarity: number;
|
||||
|
||||
@@ -50,6 +50,7 @@ export interface IDataset {
|
||||
tenant_avatar: string;
|
||||
tenant_embd_id: number;
|
||||
tenant_id: string;
|
||||
operator_permission: number;
|
||||
token_num: number;
|
||||
update_date: string;
|
||||
update_time: number;
|
||||
|
||||
@@ -14,6 +14,7 @@ export interface IFile {
|
||||
update_time: number;
|
||||
source_type: string;
|
||||
has_child_folder?: boolean;
|
||||
operator_permission?: number;
|
||||
}
|
||||
|
||||
export interface IFolder {
|
||||
@@ -30,6 +31,7 @@ export interface IFolder {
|
||||
update_date: string;
|
||||
update_time: number;
|
||||
source_type: string;
|
||||
operator_permission?: number;
|
||||
}
|
||||
|
||||
export type IFetchFileListResult = {
|
||||
|
||||
@@ -62,6 +62,7 @@ export interface IProviderInstance {
|
||||
status: string;
|
||||
/** Whether the current user owns this model instance. */
|
||||
is_owner?: boolean;
|
||||
operator_permission?: number;
|
||||
/** Whether the server has a credential configured for this instance. */
|
||||
api_key_configured?: boolean;
|
||||
/** Safe provider configuration values; secret values are never returned. */
|
||||
|
||||
@@ -5,9 +5,12 @@ export interface IMcpServer {
|
||||
id: string;
|
||||
name: string;
|
||||
server_type: string;
|
||||
tenant_id: string;
|
||||
update_date: string;
|
||||
url: string;
|
||||
variables: Record<string, any> & { tools?: IMCPToolObject };
|
||||
operator_permission: number;
|
||||
nickname?: string;
|
||||
}
|
||||
|
||||
export type IMCPToolObject = Record<string, Omit<IMCPTool, 'name'>>;
|
||||
|
||||
@@ -4,7 +4,9 @@ export interface IMemory {
|
||||
id: string;
|
||||
memory_type: string[];
|
||||
name: string;
|
||||
owner_name: string;
|
||||
nickname?: string;
|
||||
owner_name?: string;
|
||||
operator_permission?: number;
|
||||
permissions: string;
|
||||
storage_type: string;
|
||||
tenant_id: string;
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Routes } from '@/routes';
|
||||
import { BellRing } from 'lucide-react';
|
||||
import { TeamInvitationReminderDialog } from '@/ee/layouts/components/team-invitation-reminder-dialog';
|
||||
|
||||
export function BellButton({ className }: { className?: string }) {
|
||||
return (
|
||||
<Button
|
||||
asLink
|
||||
to={`${Routes.UserSetting}${Routes.Team}`}
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className={cn('group size-10 shrink-0 lg:size-8', className)}
|
||||
dot
|
||||
>
|
||||
<BellRing className="size-5 animate-bell-shake group-hover:animate-none lg:size-4" />
|
||||
</Button>
|
||||
<TeamInvitationReminderDialog>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className={cn('group size-10 shrink-0 lg:size-8', className)}
|
||||
dot
|
||||
>
|
||||
<BellRing className="size-5 animate-bell-shake group-hover:animate-none lg:size-4" />
|
||||
</Button>
|
||||
</TeamInvitationReminderDialog>
|
||||
);
|
||||
}
|
||||
@@ -1,10 +1,24 @@
|
||||
import {
|
||||
RootLayoutBanner,
|
||||
RootLayoutContent,
|
||||
useRootLayoutChrome,
|
||||
} from '@/ee/layouts/root-layout-shell';
|
||||
import { Outlet } from 'react-router';
|
||||
import { Header } from './components/header';
|
||||
|
||||
export function RootLayoutContainer({ children }: React.PropsWithChildren) {
|
||||
const { containerRef, showHeader } = useRootLayoutChrome();
|
||||
|
||||
return (
|
||||
<div className="size-full min-w-0 grid grid-flow-col grid-cols-1 grid-rows-[auto_1fr]">
|
||||
<Header className="px-5 py-4" />
|
||||
<div
|
||||
className="size-full min-w-0 grid grid-flow-col grid-cols-1 grid-rows-[auto_auto_1fr]"
|
||||
ref={containerRef}
|
||||
>
|
||||
<div>
|
||||
<RootLayoutBanner />
|
||||
</div>
|
||||
|
||||
{showHeader && <Header className="px-5 py-4" />}
|
||||
|
||||
<main className="size-full min-w-0 overflow-hidden">{children}</main>
|
||||
</div>
|
||||
@@ -14,7 +28,9 @@ export function RootLayoutContainer({ children }: React.PropsWithChildren) {
|
||||
export default function RootLayout() {
|
||||
return (
|
||||
<RootLayoutContainer>
|
||||
<Outlet />
|
||||
<RootLayoutContent>
|
||||
<Outlet />
|
||||
</RootLayoutContent>
|
||||
</RootLayoutContainer>
|
||||
);
|
||||
}
|
||||
+11
-8
@@ -21,12 +21,15 @@ import '../tailwind.css';
|
||||
import App from './app';
|
||||
import './global.less';
|
||||
import { initLanguage } from './locales/config';
|
||||
import { fetchBillingStatus } from '@/ee/services/billingStatus';
|
||||
|
||||
initLanguage().then(() => {
|
||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||
<React.StrictMode>
|
||||
<Inspector keys={['alt', 'c']} />
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
);
|
||||
});
|
||||
initLanguage()
|
||||
.then(() => fetchBillingStatus())
|
||||
.then(() => {
|
||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||
<React.StrictMode>
|
||||
<Inspector keys={['alt', 'c']} />
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
);
|
||||
});
|
||||
@@ -16,6 +16,7 @@ import { Card, CardHeader } from '@/components/ui/card';
|
||||
import { getUserDetails } from '@/services/admin-service';
|
||||
|
||||
import EnterpriseFeature from './components/enterprise-feature';
|
||||
import UserDetailAssets from '@/ee/pages/admin/user-detail-assets';
|
||||
import { parseBooleanish } from './utils';
|
||||
|
||||
function AdminUserDetail() {
|
||||
@@ -126,6 +127,8 @@ function AdminUserDetail() {
|
||||
</div>
|
||||
</section>
|
||||
</CardHeader>
|
||||
|
||||
<UserDetailAssets userId={id} />
|
||||
</Card>
|
||||
</section>
|
||||
);
|
||||
|
||||
@@ -68,6 +68,9 @@ import useImportExcelForm, {
|
||||
ImportExcelFormData,
|
||||
} from './forms/import-excel-form';
|
||||
|
||||
import NotFound from '@/ee/pages/admin/404';
|
||||
import useAdminVariables from '@/ee/pages/admin/hooks/useAdminVariables';
|
||||
|
||||
const columnHelper = createColumnHelper<AdminService.ListWhitelistItem>();
|
||||
const globalFilterFn = createFuzzySearchFn<AdminService.ListWhitelistItem>([
|
||||
'email',
|
||||
@@ -541,4 +544,16 @@ function AdminWhitelist() {
|
||||
);
|
||||
}
|
||||
|
||||
export default AdminWhitelist;
|
||||
function AdminWhitelistGuard() {
|
||||
const { variables } = useAdminVariables();
|
||||
|
||||
const isWhitelistEnabled = !!variables.enable_whitelist?.value;
|
||||
|
||||
if (!isWhitelistEnabled) {
|
||||
return <NotFound />;
|
||||
}
|
||||
|
||||
return <AdminWhitelist />;
|
||||
}
|
||||
|
||||
export default AdminWhitelistGuard;
|
||||
@@ -7,11 +7,13 @@ import {
|
||||
useResetAgent,
|
||||
useSaveAgent,
|
||||
} from '@/hooks/use-agent-request';
|
||||
import { useFetchCanvasPresence } from '@/ee/hooks/use-agent-presence';
|
||||
import {
|
||||
GlobalVariableType,
|
||||
RAGFlowNodeType,
|
||||
} from '@/interfaces/database/agent';
|
||||
import { formatDate } from '@/utils/date';
|
||||
import { canEditAgent } from '@/ee/pages/agent/can-edit-agent';
|
||||
import i18n from '@/locales/config';
|
||||
import { useDebounceEffect } from 'ahooks';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
@@ -161,6 +163,7 @@ export const useWatchAgentChange = (chatDrawerVisible: boolean) => {
|
||||
const edges = useGraphStore((state) => state.edges);
|
||||
const { saveGraph } = useSaveGraph(false, true);
|
||||
const { data: flowDetail } = useFetchAgent();
|
||||
const { data: presence } = useFetchCanvasPresence();
|
||||
|
||||
const setSaveTime = useCallback((updateTime: number) => {
|
||||
setTime(formatDate(updateTime));
|
||||
@@ -178,7 +181,8 @@ export const useWatchAgentChange = (chatDrawerVisible: boolean) => {
|
||||
agentLoaded: Boolean(flowDetail?.id),
|
||||
nodeCount: nodes.length,
|
||||
edgeCount: edges.length,
|
||||
})
|
||||
}) ||
|
||||
!canEditAgent(presence.operator_permission ?? 0)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
@@ -194,6 +198,7 @@ export const useWatchAgentChange = (chatDrawerVisible: boolean) => {
|
||||
nodes.length,
|
||||
saveGraph,
|
||||
setSaveTime,
|
||||
presence.operator_permission,
|
||||
]);
|
||||
|
||||
useDebounceEffect(
|
||||
|
||||
@@ -26,6 +26,8 @@ import { SharedFrom } from '@/constants/chat';
|
||||
import { useSetModalState } from '@/hooks/common-hooks';
|
||||
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
|
||||
import { useSaveAgent } from '@/hooks/use-agent-request';
|
||||
import { useFetchCanvasPresence } from '@/ee/hooks/use-agent-presence';
|
||||
import { AgentEditGuard } from '@/ee/pages/agent/agent-edit-guard';
|
||||
import { ReactFlowProvider } from '@xyflow/react';
|
||||
import {
|
||||
ChevronDown,
|
||||
@@ -65,6 +67,7 @@ import {
|
||||
useSaveGraphBeforeOpeningDebugDrawer,
|
||||
useWatchAgentChange,
|
||||
} from './hooks/use-save-graph';
|
||||
import { OnlineUsers } from '@/ee/pages/agent/online-users';
|
||||
import { PipelineLogSheet } from './pipeline-log-sheet';
|
||||
import PipelineRunSheet from './pipeline-run-sheet';
|
||||
import { SettingDialog } from './setting-dialog';
|
||||
@@ -272,6 +275,8 @@ export default function Agent() {
|
||||
uploadedFileData,
|
||||
} = useRunDataflow({ showLogSheet: showPipelineLogSheet, setMessageId });
|
||||
|
||||
const { data: presence } = useFetchCanvasPresence();
|
||||
const operatorPermission = presence.operator_permission ?? 0;
|
||||
const initialWidgetSettings = useMemo<WidgetSettings>(() => {
|
||||
const widgetSettings =
|
||||
agentDetail?.dsl?.globals?.[AgentWidgetSettingsGlobalKey];
|
||||
@@ -325,30 +330,40 @@ export default function Agent() {
|
||||
</div>
|
||||
</section>
|
||||
<div className="flex items-center gap-5">
|
||||
<ButtonLoading
|
||||
variant={'secondary'}
|
||||
onClick={handleSave}
|
||||
loading={loading}
|
||||
>
|
||||
<LaptopMinimalCheck /> {t('flow.saveDraft')}
|
||||
</ButtonLoading>
|
||||
{showRunButton &&
|
||||
(runTooltipKey ? (
|
||||
<RunTooltip tooltip={runTooltipKey}>{runButton}</RunTooltip>
|
||||
) : (
|
||||
runButton
|
||||
))}
|
||||
{isConversationMode && (
|
||||
<Button variant={'secondary'} onClick={handleExploreClick}>
|
||||
<Compass />
|
||||
{t('explore.title')}
|
||||
</Button>
|
||||
)}
|
||||
<PublishConfirmDialog
|
||||
agentDetail={agentDetail}
|
||||
loading={loading}
|
||||
onPublish={handlePublish}
|
||||
/>
|
||||
<OnlineUsers></OnlineUsers>
|
||||
<AgentEditGuard operatorPermission={operatorPermission}>
|
||||
<ButtonLoading
|
||||
variant={'secondary'}
|
||||
onClick={handleSave}
|
||||
loading={loading}
|
||||
>
|
||||
<LaptopMinimalCheck /> {t('flow.saveDraft')}
|
||||
</ButtonLoading>
|
||||
|
||||
{showRunButton &&
|
||||
(runTooltipKey ? (
|
||||
<RunTooltip tooltip={runTooltipKey}>{runButton}</RunTooltip>
|
||||
) : (
|
||||
runButton
|
||||
))}
|
||||
{isConversationMode && (
|
||||
<Button variant={'secondary'} onClick={handleExploreClick}>
|
||||
<Compass />
|
||||
{t('explore.title')}
|
||||
</Button>
|
||||
)}
|
||||
{/* <PublishConfirmDialog
|
||||
agentDetail={agentDetail}
|
||||
loading={loading}
|
||||
onPublish={() => saveGraph(undefined, undefined, true)}
|
||||
onBeforeOpen={hasBlockingIssues}
|
||||
/> */}
|
||||
<PublishConfirmDialog
|
||||
agentDetail={agentDetail}
|
||||
loading={loading}
|
||||
onPublish={handlePublish}
|
||||
/>
|
||||
</AgentEditGuard>
|
||||
<CanvasChecklist issues={issues} />
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
@@ -357,11 +372,13 @@ export default function Agent() {
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<AgentDropdownMenuItem onClick={() => showGlobalParamSheet()}>
|
||||
<MessageSquareCode />
|
||||
{t('flow.conversationVariable')}
|
||||
</AgentDropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<AgentEditGuard operatorPermission={operatorPermission}>
|
||||
<AgentDropdownMenuItem onClick={() => showGlobalParamSheet()}>
|
||||
<MessageSquareCode />
|
||||
{t('flow.conversationVariable')}
|
||||
</AgentDropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
</AgentEditGuard>
|
||||
<AgentDropdownMenuItem onClick={showVersionDialog}>
|
||||
<History />
|
||||
{t('flow.historyVersion')}
|
||||
@@ -373,10 +390,12 @@ export default function Agent() {
|
||||
{t('flow.export')}
|
||||
</AgentDropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<AgentDropdownMenuItem onClick={showSettingDialog}>
|
||||
<Settings />
|
||||
{t('flow.setting')}
|
||||
</AgentDropdownMenuItem>
|
||||
<AgentEditGuard operatorPermission={operatorPermission}>
|
||||
<AgentDropdownMenuItem onClick={showSettingDialog}>
|
||||
<Settings />
|
||||
{t('flow.setting')}
|
||||
</AgentDropdownMenuItem>
|
||||
</AgentEditGuard>
|
||||
{isPipeline ||
|
||||
(location.hostname !== 'cloud.ragflow.io' && (
|
||||
<>
|
||||
|
||||
@@ -10,16 +10,18 @@ import {
|
||||
} from '@/components/ui/tooltip';
|
||||
import { AgentCategory } from '@/constants/agent';
|
||||
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
|
||||
import { useRef, useState } from 'react';
|
||||
import { AgentListItemType, IFlow } from '@/interfaces/database/agent';
|
||||
import { CanvasCategoryToFlowType, FlowType, FlowTypeConfig } from './constant';
|
||||
import { AgentDropdown } from './agent-dropdown';
|
||||
import { useRenameAgent } from './use-rename-agent';
|
||||
import { useRef, useState } from 'react';
|
||||
import { Tag } from 'lucide-react';
|
||||
|
||||
export type DatasetCardProps = {
|
||||
data: IFlow & { type?: AgentListItemType };
|
||||
} & Pick<ReturnType<typeof useRenameAgent>, 'showAgentRenameModal'>;
|
||||
data: IFlow;
|
||||
} & Pick<ReturnType<typeof useRenameAgent>, 'showAgentRenameModal'> & {
|
||||
showPrivilegeModal(): void;
|
||||
};
|
||||
|
||||
function AgentTypeIcon({
|
||||
data,
|
||||
@@ -73,6 +75,7 @@ function AgentTags({ tags }: { tags?: string }) {
|
||||
<Badge
|
||||
key={tag}
|
||||
variant="secondary"
|
||||
|
||||
className="text-xs font-normal mr-1 space-x-1"
|
||||
>
|
||||
<Tag className="size-3" />
|
||||
@@ -99,7 +102,11 @@ function AgentTags({ tags }: { tags?: string }) {
|
||||
);
|
||||
}
|
||||
|
||||
export function AgentCard({ data, showAgentRenameModal }: DatasetCardProps) {
|
||||
export function AgentCard({
|
||||
data,
|
||||
showAgentRenameModal,
|
||||
showPrivilegeModal,
|
||||
}: DatasetCardProps) {
|
||||
const { navigateToAgent } = useNavigatePage();
|
||||
|
||||
return (
|
||||
@@ -112,8 +119,12 @@ export function AgentCard({ data, showAgentRenameModal }: DatasetCardProps) {
|
||||
release_time: data.release_time,
|
||||
}}
|
||||
moreDropdown={
|
||||
<AgentDropdown showAgentRenameModal={showAgentRenameModal} agent={data}>
|
||||
<MoreButton></MoreButton>
|
||||
<AgentDropdown
|
||||
showAgentRenameModal={showAgentRenameModal}
|
||||
agent={data}
|
||||
showPrivilegeModal={showPrivilegeModal}
|
||||
>
|
||||
<MoreButton />
|
||||
</AgentDropdown>
|
||||
}
|
||||
sharedBadge={<SharedBadge>{data.nickname}</SharedBadge>}
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { AgentDropdownGuard } from '@/ee/pages/agents/agent-dropdown-guard';
|
||||
import { useDeleteAgent } from '@/hooks/use-agent-request';
|
||||
import { IFlow } from '@/interfaces/database/agent';
|
||||
import { PenLine, Tag, Trash2 } from 'lucide-react';
|
||||
@@ -25,10 +26,12 @@ import { useRenameAgent } from './use-rename-agent';
|
||||
export function AgentDropdown({
|
||||
children,
|
||||
showAgentRenameModal,
|
||||
agent: agent,
|
||||
agent,
|
||||
showPrivilegeModal,
|
||||
}: PropsWithChildren &
|
||||
Pick<ReturnType<typeof useRenameAgent>, 'showAgentRenameModal'> & {
|
||||
agent: IFlow;
|
||||
showPrivilegeModal(): void;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const { deleteAgent } = useDeleteAgent();
|
||||
@@ -57,37 +60,49 @@ export function AgentDropdown({
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>{children}</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuItem onClick={handleShowAgentRenameModal}>
|
||||
{t('common.rename')} <PenLine />
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={handleEditTags}>
|
||||
{t('flow.editTags')} <Tag />
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<ConfirmDeleteDialog
|
||||
onOk={handleDelete}
|
||||
title={t('deleteModal.delAgent')}
|
||||
content={{
|
||||
node: (
|
||||
<ConfirmDeleteDialogNode
|
||||
avatar={{ avatar: agent.avatar, name: agent.title }}
|
||||
name={agent.title}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
>
|
||||
<DropdownMenuItem
|
||||
className="text-state-error"
|
||||
onSelect={(e) => {
|
||||
e.preventDefault();
|
||||
}}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
{t('common.delete')} <Trash2 />
|
||||
</DropdownMenuItem>
|
||||
</ConfirmDeleteDialog>
|
||||
<AgentDropdownGuard
|
||||
agent={agent}
|
||||
showPrivilegeModal={showPrivilegeModal}
|
||||
manageItems={
|
||||
<>
|
||||
<DropdownMenuItem onClick={handleShowAgentRenameModal}>
|
||||
{t('common.rename')} <PenLine />
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={handleEditTags}>
|
||||
{t('flow.editTags')} <Tag />
|
||||
</DropdownMenuItem>
|
||||
</>
|
||||
}
|
||||
deleteItems={
|
||||
<>
|
||||
<DropdownMenuSeparator />
|
||||
<ConfirmDeleteDialog
|
||||
onOk={handleDelete}
|
||||
title={t('deleteModal.delAgent')}
|
||||
content={{
|
||||
node: (
|
||||
<ConfirmDeleteDialogNode
|
||||
avatar={{ avatar: agent.avatar, name: agent.title }}
|
||||
name={agent.title}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
>
|
||||
<DropdownMenuItem
|
||||
className="text-state-error"
|
||||
onSelect={(e) => {
|
||||
e.preventDefault();
|
||||
}}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
{t('common.delete')} <Trash2 />
|
||||
</DropdownMenuItem>
|
||||
</ConfirmDeleteDialog>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<AgentTagEditor
|
||||
|
||||
@@ -2,6 +2,7 @@ import { CardContainer } from '@/components/card-container';
|
||||
import { EmptyCardType } from '@/components/empty/constant';
|
||||
import { EmptyAppCard } from '@/components/empty/empty';
|
||||
import ListFilterBar from '@/components/list-filter-bar';
|
||||
import { PrivilegeManagementDialog } from '@/ee/components/privilege-management/privilege-management-dialog';
|
||||
import { RenameDialog } from '@/components/rename-dialog';
|
||||
import { RAGFlowPagination } from '@/components/ui/ragflow-pagination';
|
||||
import { ListDeletionKey } from '@/constants/list-deletion';
|
||||
@@ -34,6 +35,7 @@ import { useSelectFilters } from './hooks/use-select-filters';
|
||||
import { UploadAgentDialog } from './upload-agent-dialog';
|
||||
import { useHandleImportJsonFile } from './use-import-json';
|
||||
import { useRenameAgent } from './use-rename-agent';
|
||||
import { useShowPrivilegeDialog } from '@/ee/pages/agents/use-show-privilege-dialog';
|
||||
|
||||
export default function Agents() {
|
||||
const {
|
||||
@@ -108,6 +110,12 @@ export default function Agents() {
|
||||
handleImportJson,
|
||||
);
|
||||
|
||||
const {
|
||||
privilegeModalVisible,
|
||||
hidePrivilegeModal,
|
||||
handShowPrivilegeModal,
|
||||
recordWithSourceType,
|
||||
} = useShowPrivilegeDialog();
|
||||
const { deleteGroup } = useDeleteCompilationTemplateGroup();
|
||||
|
||||
const filters = useSelectFilters(activeCategory);
|
||||
@@ -224,6 +232,7 @@ export default function Agents() {
|
||||
key={x.id}
|
||||
data={x}
|
||||
showAgentRenameModal={showAgentRenameModal}
|
||||
showPrivilegeModal={handShowPrivilegeModal(x)}
|
||||
/>
|
||||
),
|
||||
)}
|
||||
@@ -297,6 +306,12 @@ export default function Agents() {
|
||||
onOk={onFileUploadOk}
|
||||
></UploadAgentDialog>
|
||||
)}
|
||||
{privilegeModalVisible && (
|
||||
<PrivilegeManagementDialog
|
||||
hideModal={hidePrivilegeModal}
|
||||
initialValues={recordWithSourceType}
|
||||
></PrivilegeManagementDialog>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
+4
@@ -7,6 +7,7 @@ import {
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from '@/components/ui/tooltip';
|
||||
import { useDatasetEditButtonDisabled } from '@/ee/hooks/logic-hooks/use-permission';
|
||||
import type { ChunkDocType, IChunk } from '@/interfaces/database/dataset';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { sanitizeHtmlWithImagesAsText } from '@/utils/dom-util';
|
||||
@@ -41,6 +42,8 @@ const ChunkCard = ({
|
||||
t: imageCacheKey,
|
||||
}: IProps) => {
|
||||
const { t } = useTranslation();
|
||||
const datasetEditButtonDisabled = useDatasetEditButtonDisabled();
|
||||
|
||||
const available = Number(item.available_int);
|
||||
const [enabled, setEnabled] = useState(false);
|
||||
|
||||
@@ -145,6 +148,7 @@ const ChunkCard = ({
|
||||
onCheckedChange={onChange}
|
||||
aria-readonly
|
||||
className="!m-0"
|
||||
disabled={datasetEditButtonDisabled}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+6
-1
@@ -1,6 +1,7 @@
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { useDatasetEditButtonDisabled } from '@/ee/hooks/logic-hooks/use-permission';
|
||||
import { cn } from '@/lib/utils';
|
||||
import {
|
||||
LucideToggleLeft,
|
||||
@@ -18,7 +19,9 @@ type ICheckboxSetProps = {
|
||||
checked: boolean;
|
||||
selectedChunkIds: string[];
|
||||
};
|
||||
|
||||
export default function CheckboxSets(props: ICheckboxSetProps) {
|
||||
const datasetEditButtonDisabled = useDatasetEditButtonDisabled();
|
||||
const {
|
||||
className,
|
||||
selectAllChunk,
|
||||
@@ -51,7 +54,9 @@ export default function CheckboxSets(props: ICheckboxSetProps) {
|
||||
const isSelected = useMemo(() => {
|
||||
return selectedChunkIds?.length > 0;
|
||||
}, [selectedChunkIds]);
|
||||
|
||||
if (datasetEditButtonDisabled) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<div className={cn('flex gap-4 text-sm text-text-secondary', className)}>
|
||||
<Label className="flex items-center gap-2">
|
||||
|
||||
+9
-2
@@ -7,6 +7,7 @@ import {
|
||||
} from '@/components/ui/popover';
|
||||
import { Radio } from '@/components/ui/radio';
|
||||
import { Segmented } from '@/components/ui/segmented';
|
||||
import { useDatasetEditButtonDisabled } from '@/ee/hooks/logic-hooks/use-permission';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { LucideFilter, Plus } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
@@ -32,6 +33,7 @@ export default function ChunkResultBar({
|
||||
handleInputChange,
|
||||
searchString,
|
||||
}: ChunkResultBarProps) {
|
||||
const datasetEditButtonDisabled = useDatasetEditButtonDisabled();
|
||||
const { t } = useTranslation('translation', { keyPrefix: 'chunk' });
|
||||
const [textSelectValue, setTextSelectValue] = useState<string | number>(
|
||||
ChunkTextMode.Full,
|
||||
@@ -71,12 +73,12 @@ export default function ChunkResultBar({
|
||||
value={textSelectValue}
|
||||
onChange={changeTextSelectValue}
|
||||
/>
|
||||
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
disabled={datasetEditButtonDisabled}
|
||||
>
|
||||
<LucideFilter />
|
||||
</Button>
|
||||
@@ -93,7 +95,12 @@ export default function ChunkResultBar({
|
||||
value={searchString}
|
||||
/>
|
||||
|
||||
<Button variant="outline" size="icon" onClick={() => createChunk()}>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={() => createChunk()}
|
||||
disabled={datasetEditButtonDisabled}
|
||||
>
|
||||
<Plus size={44} />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import message from '@/components/ui/message';
|
||||
import { useSaveBuiltinMetadata } from '@/ee/pages/dataset/components/metedata/hooks/use-save-builtin-metadata';
|
||||
import { useSetModalState } from '@/hooks/common-hooks';
|
||||
import { useSelectedIds } from '@/hooks/logic-hooks/use-row-selection';
|
||||
import { DocumentApiAction } from '@/hooks/use-document-request';
|
||||
@@ -294,6 +295,7 @@ export const useManageMetaDataModal = (
|
||||
const { id } = useParams();
|
||||
const { t } = useTranslation();
|
||||
const { data, loading } = useFetchMetaDataManageData(type, documentIds);
|
||||
const { saveBuiltinMetadata } = useSaveBuiltinMetadata();
|
||||
|
||||
const [tableData, setTableData] = useState<IMetaDataTableData[]>(metaData);
|
||||
const queryClient = useQueryClient();
|
||||
@@ -469,6 +471,12 @@ export const useManageMetaDataModal = (
|
||||
case MetadataType.UpdateSingle:
|
||||
// handleSaveUpdateSingle(callback);
|
||||
handleSaveManage(callback);
|
||||
await saveBuiltinMetadata({
|
||||
kbId: id,
|
||||
documentId: documentIds?.[0],
|
||||
builtInMetadata,
|
||||
metadata: util.tableDataToMetaDataSettingJSON(tableData),
|
||||
});
|
||||
break;
|
||||
case MetadataType.Manage:
|
||||
handleSaveManage(callback);
|
||||
@@ -484,8 +492,12 @@ export const useManageMetaDataModal = (
|
||||
}
|
||||
},
|
||||
[
|
||||
id,
|
||||
documentIds,
|
||||
tableData,
|
||||
handleSaveManage,
|
||||
type,
|
||||
saveBuiltinMetadata,
|
||||
// handleSaveUpdateSingle,
|
||||
handleSaveSettings,
|
||||
handleSaveSingleFileSettings,
|
||||
|
||||
@@ -6,9 +6,14 @@ import {
|
||||
HoverCardTrigger,
|
||||
} from '@/components/ui/hover-card';
|
||||
import { DocumentType } from '@/constants/knowledge';
|
||||
import {
|
||||
DocumentPrivilegeAction,
|
||||
useDocumentActionCellOverrides,
|
||||
} from '@/ee/pages/dataset/dataset/dataset-action-cell-seams';
|
||||
import { UseRowSelectionType } from '@/hooks/logic-hooks/use-row-selection';
|
||||
import { useRemoveDocument } from '@/hooks/use-document-request';
|
||||
import { IDocumentInfo } from '@/interfaces/database/document';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { downloadDatasetDocument } from '@/services/file-manager-service';
|
||||
import { formatFileSize } from '@/utils/common-util';
|
||||
import { formatDate } from '@/utils/date';
|
||||
@@ -27,15 +32,23 @@ const FunctionMap = {
|
||||
update_time: formatDate,
|
||||
};
|
||||
|
||||
type DatasetActionCellProps = {
|
||||
record: IDocumentInfo;
|
||||
datasetEditButtonDisabled?: boolean;
|
||||
} & UseRenameDocumentShowType &
|
||||
Pick<UseRowSelectionType, 'setRowSelection'>;
|
||||
|
||||
export function DatasetActionCell({
|
||||
record,
|
||||
showRenameModal,
|
||||
setRowSelection,
|
||||
}: { record: IDocumentInfo } & UseRenameDocumentShowType &
|
||||
Pick<UseRowSelectionType, 'setRowSelection'>) {
|
||||
datasetEditButtonDisabled,
|
||||
}: DatasetActionCellProps) {
|
||||
const { id, type } = record;
|
||||
const isRunning = isDocumentProcessing(record);
|
||||
const isVirtualDocument = type === DocumentType.Virtual;
|
||||
const actionDisabled = isRunning || Boolean(datasetEditButtonDisabled);
|
||||
const { actionClassName } = useDocumentActionCellOverrides();
|
||||
|
||||
const { removeDocument } = useRemoveDocument();
|
||||
|
||||
@@ -69,14 +82,16 @@ export function DatasetActionCell({
|
||||
|
||||
return (
|
||||
<div
|
||||
className="
|
||||
flex gap-2 items-center opacity-0
|
||||
transition-opacity group-hover:opacity-100 group-focus-within:opacity-100"
|
||||
className={cn(
|
||||
'flex gap-2 items-center opacity-0 transition-opacity group-hover:opacity-100 group-focus-within:opacity-100',
|
||||
actionClassName,
|
||||
)}
|
||||
>
|
||||
<DocumentPrivilegeAction record={record} disabled={actionDisabled} />
|
||||
<Button
|
||||
size="icon-xs"
|
||||
variant="ghost"
|
||||
disabled={isRunning}
|
||||
disabled={actionDisabled}
|
||||
onClick={handleRename}
|
||||
>
|
||||
<PenLine className="size-[1em]" />
|
||||
@@ -123,7 +138,7 @@ export function DatasetActionCell({
|
||||
data-testid="document-delete"
|
||||
size="icon-xs"
|
||||
variant="ghost"
|
||||
disabled={isRunning}
|
||||
disabled={actionDisabled}
|
||||
>
|
||||
<Trash2 className="size-[1em]" />
|
||||
</Button>
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
} from '@/components/bulk-operate-bar';
|
||||
import { FileUploadDialog } from '@/components/file-upload-dialog';
|
||||
import ListFilterBar from '@/components/list-filter-bar';
|
||||
import { PrivilegeManagementDialog } from '@/ee/components/privilege-management/privilege-management-dialog';
|
||||
import { RenameDialog } from '@/components/rename-dialog';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardHeader } from '@/components/ui/card';
|
||||
@@ -11,9 +12,9 @@ import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { useDatasetEditButtonDisabled } from '@/ee/hooks/logic-hooks/use-permission';
|
||||
import { useGoToPreviousPageOnEmpty } from '@/hooks/logic-hooks';
|
||||
import { useClearSelectionOnPageChange } from '@/hooks/logic-hooks/use-clear-selection-on-page-change';
|
||||
import {
|
||||
@@ -33,9 +34,15 @@ import { DatasetTable } from './dataset-table';
|
||||
import { useBulkOperateDataset } from './use-bulk-operate-dataset';
|
||||
import { useCreateEmptyDocument } from './use-create-empty-document';
|
||||
import { useSelectDatasetFilters } from './use-select-filters';
|
||||
import {
|
||||
ShowPrivilegeModalContext,
|
||||
useShowPrivilegeDialog,
|
||||
} from '@/ee/pages/dataset/dataset/use-show-privilege-dialog';
|
||||
import { useHandleUploadDocument } from './use-upload-document';
|
||||
|
||||
export default function Dataset() {
|
||||
const datasetEditButtonDisabled = useDatasetEditButtonDisabled();
|
||||
|
||||
const { t } = useTranslation();
|
||||
const { knowledgeBase } = useKnowledgeBaseContext();
|
||||
const {
|
||||
@@ -91,10 +98,18 @@ export default function Dataset() {
|
||||
|
||||
useClearSelectionOnPageChange(pagination, clearRowSelection);
|
||||
|
||||
const {
|
||||
handShowPrivilegeModal,
|
||||
hidePrivilegeModal,
|
||||
privilegeModal,
|
||||
recordWithSourceType,
|
||||
} = useShowPrivilegeDialog();
|
||||
|
||||
const { list } = useBulkOperateDataset({
|
||||
documents,
|
||||
rowSelection,
|
||||
setRowSelection,
|
||||
handShowPrivilegeModal,
|
||||
});
|
||||
|
||||
const { selectedIds: selectedRowKeys } = useSelectedIds(
|
||||
@@ -167,7 +182,7 @@ export default function Dataset() {
|
||||
<CancelParsingDropdown />
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button size="default">
|
||||
<Button size="default" disabled={datasetEditButtonDisabled}>
|
||||
<LucidePlus />
|
||||
{t('knowledgeDetails.addFile')}
|
||||
</Button>
|
||||
@@ -176,7 +191,6 @@ export default function Dataset() {
|
||||
<DropdownMenuItem onClick={showDocumentUploadModal}>
|
||||
{t('fileManager.uploadFile')}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem onClick={showCreateModal}>
|
||||
{t('knowledgeDetails.emptyFiles')}
|
||||
</DropdownMenuItem>
|
||||
@@ -195,16 +209,18 @@ export default function Dataset() {
|
||||
</CardHeader>
|
||||
|
||||
<CardContent className="px-5 py-0">
|
||||
<DatasetTable
|
||||
documents={documents}
|
||||
pagination={pagination}
|
||||
setPagination={setPagination}
|
||||
rowSelection={rowSelection}
|
||||
setRowSelection={setRowSelection}
|
||||
showManageMetadataModal={showManageMetadataModal}
|
||||
loading={loading}
|
||||
bulkOperateBarVisible={!rowSelectionIsEmpty}
|
||||
/>
|
||||
<ShowPrivilegeModalContext.Provider value={{ handShowPrivilegeModal }}>
|
||||
<DatasetTable
|
||||
documents={documents}
|
||||
pagination={pagination}
|
||||
setPagination={setPagination}
|
||||
rowSelection={rowSelection}
|
||||
setRowSelection={setRowSelection}
|
||||
showManageMetadataModal={showManageMetadataModal}
|
||||
loading={loading}
|
||||
bulkOperateBarVisible={!rowSelectionIsEmpty}
|
||||
></DatasetTable>
|
||||
</ShowPrivilegeModalContext.Provider>
|
||||
|
||||
{documentUploadVisible && (
|
||||
<FileUploadDialog
|
||||
@@ -256,6 +272,14 @@ export default function Dataset() {
|
||||
/>
|
||||
)}
|
||||
</CardContent>
|
||||
{/* </section> */}
|
||||
{privilegeModal && (
|
||||
<PrivilegeManagementDialog
|
||||
hideModal={hidePrivilegeModal}
|
||||
initialValues={recordWithSourceType}
|
||||
></PrivilegeManagementDialog>
|
||||
)}
|
||||
{/* </> */}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -140,10 +140,12 @@ export function ParseDropdownButton({
|
||||
export function ParsingStatusCell({
|
||||
record,
|
||||
showLog,
|
||||
datasetEditButtonDisabled = false,
|
||||
showChangeParserModal,
|
||||
}: {
|
||||
record: IDocumentInfo;
|
||||
showLog: (record: IDocumentInfo) => void;
|
||||
datasetEditButtonDisabled?: boolean;
|
||||
} & UseChangeDocumentParserShowType) {
|
||||
const { t } = useTranslation();
|
||||
const { progress, chunk_count, id } = record;
|
||||
@@ -168,6 +170,9 @@ export function ParsingStatusCell({
|
||||
delete: boolean;
|
||||
apply_kb: boolean;
|
||||
}) => {
|
||||
if (datasetEditButtonDisabled) {
|
||||
return;
|
||||
}
|
||||
handleRunDocumentByIds(record, isRunning, option);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Modal } from '@/components/ui/modal/modal';
|
||||
import { FileType } from '@/constants/file';
|
||||
import { useExtraBulkActions } from '@/ee/pages/dataset/dataset/use-extra-bulk-actions';
|
||||
import {
|
||||
UseRowSelectionType,
|
||||
useSelectedIds,
|
||||
@@ -23,6 +24,7 @@ import { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useParams } from 'react-router';
|
||||
import { toast } from 'sonner';
|
||||
import { ShowPrivilegeModalReturnType } from '@/ee/pages/dataset/dataset/use-show-privilege-dialog';
|
||||
import { DocumentType, ParserGapReason } from './constant';
|
||||
import { buildParserGapModalContent } from './parser-gap-content';
|
||||
import { useParserGapValidation } from './use-parser-gap-validation';
|
||||
@@ -32,9 +34,10 @@ export function useBulkOperateDataset({
|
||||
rowSelection,
|
||||
setRowSelection,
|
||||
documents,
|
||||
handShowPrivilegeModal,
|
||||
}: Pick<UseRowSelectionType, 'rowSelection' | 'setRowSelection'> & {
|
||||
documents: IDocumentInfo[];
|
||||
}) {
|
||||
} & Pick<ShowPrivilegeModalReturnType, 'handShowPrivilegeModal'>) {
|
||||
const { t } = useTranslation();
|
||||
const { selectedIds: selectedRowKeys } = useSelectedIds(
|
||||
rowSelection,
|
||||
@@ -46,6 +49,10 @@ export function useBulkOperateDataset({
|
||||
const { setDocumentStatus } = useSetDocumentStatus();
|
||||
const { removeDocument } = useRemoveDocument();
|
||||
const { findDocumentParseGaps } = useParserGapValidation();
|
||||
const extraBulkActions = useExtraBulkActions(
|
||||
handShowPrivilegeModal,
|
||||
selectedRowKeys,
|
||||
);
|
||||
|
||||
const runDocument = useCallback(
|
||||
async (run: number) => {
|
||||
@@ -209,6 +216,7 @@ export function useBulkOperateDataset({
|
||||
icon: <LucideCircleX />,
|
||||
onClick: handleCancelClick,
|
||||
},
|
||||
...extraBulkActions,
|
||||
{
|
||||
id: 'enabled',
|
||||
label: t('knowledgeDetails.enabled'),
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
TooltipTrigger,
|
||||
} from '@/components/ui/tooltip';
|
||||
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
|
||||
import { useDatasetEditButtonDisabled } from '@/ee/hooks/logic-hooks/use-permission';
|
||||
import { UseRowSelectionType } from '@/hooks/logic-hooks/use-row-selection';
|
||||
import { useSetDocumentStatus } from '@/hooks/use-document-request';
|
||||
import { IDocumentInfo } from '@/interfaces/database/document';
|
||||
@@ -39,6 +40,7 @@ export function useDatasetTableColumns({
|
||||
showLog,
|
||||
setRowSelection,
|
||||
}: UseDatasetTableColumnsType) {
|
||||
const datasetEditButtonDisabled = useDatasetEditButtonDisabled();
|
||||
const { t } = useTranslation('translation', {
|
||||
keyPrefix: 'knowledgeDetails',
|
||||
});
|
||||
@@ -61,6 +63,7 @@ export function useDatasetTableColumns({
|
||||
table.toggleAllPageRowsSelected(!!value)
|
||||
}
|
||||
aria-label="Select all"
|
||||
disabled={datasetEditButtonDisabled}
|
||||
/>
|
||||
),
|
||||
cell: ({ row }) => (
|
||||
@@ -68,6 +71,7 @@ export function useDatasetTableColumns({
|
||||
checked={row.getIsSelected()}
|
||||
onCheckedChange={(value) => row.toggleSelected(!!value)}
|
||||
aria-label="Select row"
|
||||
disabled={datasetEditButtonDisabled}
|
||||
/>
|
||||
),
|
||||
enableSorting: false,
|
||||
@@ -239,6 +243,7 @@ export function useDatasetTableColumns({
|
||||
record={row.original}
|
||||
showChangeParserModal={showChangeParserModal}
|
||||
showLog={showLog}
|
||||
datasetEditButtonDisabled={datasetEditButtonDisabled}
|
||||
/>
|
||||
);
|
||||
},
|
||||
@@ -255,6 +260,7 @@ export function useDatasetTableColumns({
|
||||
record={record}
|
||||
showRenameModal={showRenameModal}
|
||||
setRowSelection={setRowSelection}
|
||||
datasetEditButtonDisabled={datasetEditButtonDisabled}
|
||||
/>
|
||||
);
|
||||
},
|
||||
@@ -270,6 +276,7 @@ export function useDatasetTableColumns({
|
||||
showManageMetadataModal,
|
||||
showLog,
|
||||
setRowSelection,
|
||||
datasetEditButtonDisabled,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@@ -4,19 +4,29 @@ import { SharedBadge } from '@/components/shared-badge';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
|
||||
import { IDataset } from '@/interfaces/database/dataset';
|
||||
import { t } from 'i18next';
|
||||
import { ChevronRight } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { DatasetDropdown } from './dataset-dropdown';
|
||||
import useDuplicateDataset from '@/ee/pages/datasets/use-duplicate-dataset';
|
||||
import { useRenameDataset } from './use-rename-dataset';
|
||||
|
||||
export type DatasetCardProps = {
|
||||
dataset: IDataset;
|
||||
} & Pick<ReturnType<typeof useRenameDataset>, 'showDatasetRenameModal'>;
|
||||
showPrivilegeModal(): void;
|
||||
} & Pick<ReturnType<typeof useRenameDataset>, 'showDatasetRenameModal'> & {
|
||||
showDatasetDuplicateModal: ReturnType<
|
||||
typeof useDuplicateDataset
|
||||
>['showModal'];
|
||||
};
|
||||
|
||||
export function DatasetCard({
|
||||
dataset,
|
||||
showDatasetRenameModal,
|
||||
showDatasetDuplicateModal,
|
||||
showPrivilegeModal,
|
||||
}: DatasetCardProps) {
|
||||
const { t } = useTranslation();
|
||||
const { navigateToDataset } = useNavigatePage();
|
||||
|
||||
return (
|
||||
@@ -28,6 +38,8 @@ export function DatasetCard({
|
||||
moreDropdown={
|
||||
<DatasetDropdown
|
||||
showDatasetRenameModal={showDatasetRenameModal}
|
||||
showDatasetDuplicateModal={showDatasetDuplicateModal}
|
||||
showPrivilegeModal={showPrivilegeModal}
|
||||
dataset={dataset}
|
||||
>
|
||||
<MoreButton></MoreButton>
|
||||
@@ -35,12 +47,13 @@ export function DatasetCard({
|
||||
}
|
||||
sharedBadge={<SharedBadge>{dataset.nickname}</SharedBadge>}
|
||||
onClick={navigateToDataset(dataset.id)}
|
||||
/>
|
||||
></HomeCard>
|
||||
);
|
||||
}
|
||||
|
||||
export function SeeAllCard() {
|
||||
const { navigateToDatasetList } = useNavigatePage();
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Card
|
||||
|
||||
@@ -9,6 +9,8 @@ import {
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { DatasetDropdownGuard } from '@/ee/pages/datasets/dataset-dropdown-guard';
|
||||
import useDuplicateDataset from '@/ee/pages/datasets/use-duplicate-dataset';
|
||||
import { useDeleteKnowledge } from '@/hooks/use-knowledge-request';
|
||||
import { IDataset } from '@/interfaces/database/dataset';
|
||||
import { PenLine, Trash2 } from 'lucide-react';
|
||||
@@ -16,11 +18,21 @@ import { MouseEventHandler, PropsWithChildren, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useRenameDataset } from './use-rename-dataset';
|
||||
|
||||
type IDatasetDropdownProps = {
|
||||
dataset: IDataset;
|
||||
showPrivilegeModal(): void;
|
||||
} & Pick<ReturnType<typeof useRenameDataset>, 'showDatasetRenameModal'> & {
|
||||
showDatasetDuplicateModal: ReturnType<
|
||||
typeof useDuplicateDataset
|
||||
>['showModal'];
|
||||
};
|
||||
|
||||
export function DatasetDropdown({
|
||||
children,
|
||||
showDatasetRenameModal,
|
||||
dataset,
|
||||
}: PropsWithChildren &
|
||||
showPrivilegeModal,
|
||||
}: PropsWithChildren<IDatasetDropdownProps> &
|
||||
Pick<ReturnType<typeof useRenameDataset>, 'showDatasetRenameModal'> & {
|
||||
dataset: IDataset;
|
||||
}) {
|
||||
@@ -44,34 +56,44 @@ export function DatasetDropdown({
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>{children}</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuItem onClick={handleShowDatasetRenameModal}>
|
||||
{t('common.rename')} <PenLine />
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<ConfirmDeleteDialog
|
||||
onOk={handleDelete}
|
||||
title={t('deleteModal.delDataset')}
|
||||
content={{
|
||||
node: (
|
||||
<ConfirmDeleteDialogNode
|
||||
avatar={{ avatar: dataset.avatar, name: dataset.name }}
|
||||
name={dataset.name}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
>
|
||||
<DropdownMenuItem
|
||||
className="text-state-error"
|
||||
onSelect={(e) => {
|
||||
e.preventDefault();
|
||||
}}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
{t('common.delete')} <Trash2 />
|
||||
</DropdownMenuItem>
|
||||
</ConfirmDeleteDialog>
|
||||
<DatasetDropdownGuard
|
||||
dataset={dataset}
|
||||
showPrivilegeModal={showPrivilegeModal}
|
||||
manageItems={
|
||||
<DropdownMenuItem onClick={handleShowDatasetRenameModal}>
|
||||
{t('common.rename')} <PenLine />
|
||||
</DropdownMenuItem>
|
||||
}
|
||||
deleteItems={
|
||||
<>
|
||||
<DropdownMenuSeparator />
|
||||
<ConfirmDeleteDialog
|
||||
onOk={handleDelete}
|
||||
content={{
|
||||
node: (
|
||||
<ConfirmDeleteDialogNode
|
||||
avatar={{ avatar: dataset.avatar, name: dataset.name }}
|
||||
name={dataset.name}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
title={t('deleteModal.delDataset')}
|
||||
>
|
||||
<DropdownMenuItem
|
||||
className="text-state-error"
|
||||
onSelect={(e) => {
|
||||
e.preventDefault();
|
||||
}}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
{t('common.delete')} <Trash2 />
|
||||
</DropdownMenuItem>
|
||||
</ConfirmDeleteDialog>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { CardContainer } from '@/components/card-container';
|
||||
import DuplicateDialog from '@/ee/components/duplicate-dialog';
|
||||
import { EmptyCardType } from '@/components/empty/constant';
|
||||
import { EmptyAppCard } from '@/components/empty/empty';
|
||||
import ListFilterBar from '@/components/list-filter-bar';
|
||||
import { PrivilegeManagementDialog } from '@/ee/components/privilege-management/privilege-management-dialog';
|
||||
import { RenameDialog } from '@/components/rename-dialog';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { RAGFlowPagination } from '@/components/ui/ragflow-pagination';
|
||||
@@ -18,8 +20,10 @@ import { useSearchParams } from 'react-router';
|
||||
import { DatasetCard } from './dataset-card';
|
||||
import { DatasetCreatingDialog } from './dataset-creating-dialog';
|
||||
import { useSaveKnowledge } from './hooks';
|
||||
import useDuplicateDataset from '@/ee/pages/datasets/use-duplicate-dataset';
|
||||
import { useRenameDataset } from './use-rename-dataset';
|
||||
import { useSelectOwners } from './use-select-owners';
|
||||
import { useShowPrivilegeDialog } from '@/ee/pages/datasets/use-show-privilege-dialog';
|
||||
|
||||
export default function Datasets() {
|
||||
const { t } = useTranslation();
|
||||
@@ -56,6 +60,15 @@ export default function Datasets() {
|
||||
showDatasetRenameModal,
|
||||
} = useRenameDataset();
|
||||
|
||||
const {
|
||||
isModalVisible: datasetDuplicateModalVisible,
|
||||
showModal: showDatasetDuplicateModal,
|
||||
hideModal: hideDatasetDuplicateModal,
|
||||
initialName: initialDatasetDuplicateName,
|
||||
onOk: onDatasetDuplicateOk,
|
||||
loading: datasetDuplicateLoading,
|
||||
} = useDuplicateDataset();
|
||||
|
||||
const handlePageChange = useCallback(
|
||||
(page: number, pageSize?: number) => {
|
||||
setPagination({ page, pageSize });
|
||||
@@ -81,6 +94,13 @@ export default function Datasets() {
|
||||
}
|
||||
}, [isCreate, showModal, searchUrl, setSearchUrl, queryClient]);
|
||||
|
||||
const {
|
||||
privilegeModal,
|
||||
hidePrivilegeModal,
|
||||
handShowPrivilegeModal,
|
||||
recordWithSourceType,
|
||||
} = useShowPrivilegeDialog();
|
||||
|
||||
return (
|
||||
<>
|
||||
{loading && !kbs?.length ? (
|
||||
@@ -120,6 +140,8 @@ export default function Datasets() {
|
||||
dataset={dataset}
|
||||
key={dataset.id}
|
||||
showDatasetRenameModal={showDatasetRenameModal}
|
||||
showDatasetDuplicateModal={showDatasetDuplicateModal}
|
||||
showPrivilegeModal={handShowPrivilegeModal(dataset)}
|
||||
/>
|
||||
))}
|
||||
</CardContainer>
|
||||
@@ -166,6 +188,14 @@ export default function Datasets() {
|
||||
loading={creatingLoading}
|
||||
></DatasetCreatingDialog>
|
||||
)}
|
||||
{datasetDuplicateModalVisible && (
|
||||
<DuplicateDialog
|
||||
hideModal={hideDatasetDuplicateModal}
|
||||
onOk={onDatasetDuplicateOk}
|
||||
initialName={initialDatasetDuplicateName}
|
||||
loading={datasetDuplicateLoading}
|
||||
/>
|
||||
)}
|
||||
{datasetRenameVisible && (
|
||||
<RenameDialog
|
||||
hideModal={hideDatasetRenameModal}
|
||||
@@ -174,6 +204,12 @@ export default function Datasets() {
|
||||
loading={datasetRenameLoading}
|
||||
></RenameDialog>
|
||||
)}
|
||||
{privilegeModal && (
|
||||
<PrivilegeManagementDialog
|
||||
hideModal={hidePrivilegeModal}
|
||||
initialValues={recordWithSourceType}
|
||||
></PrivilegeManagementDialog>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
import { FileIcon } from '@/components/icon-font';
|
||||
import NewDocumentLink from '@/components/new-document-link';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { FilePrivilegeButton } from '@/ee/pages/files/file-privilege-button';
|
||||
import { UseRowSelectionType } from '@/hooks/logic-hooks/use-row-selection';
|
||||
import { useDownloadFile } from '@/hooks/use-file-request';
|
||||
import { IFile } from '@/interfaces/database/file-manager';
|
||||
@@ -36,7 +37,9 @@ type IProps = Pick<CellContext<IFile, unknown>, 'row'> &
|
||||
Pick<UseHandleConnectToKnowledgeReturnType, 'showConnectToKnowledgeModal'> &
|
||||
Pick<UseRenameCurrentFileReturnType, 'showFileRenameModal'> &
|
||||
Pick<UseRowSelectionType, 'setRowSelection'> &
|
||||
UseMoveDocumentShowType;
|
||||
UseMoveDocumentShowType & {
|
||||
showPrivilegeModal?(record: IFile): void;
|
||||
};
|
||||
|
||||
export function ActionCell({
|
||||
row,
|
||||
@@ -44,6 +47,7 @@ export function ActionCell({
|
||||
showFileRenameModal,
|
||||
showMoveFileModal,
|
||||
setRowSelection,
|
||||
showPrivilegeModal,
|
||||
}: IProps) {
|
||||
const record = row.original;
|
||||
const documentId = record.id;
|
||||
@@ -106,6 +110,10 @@ export function ActionCell({
|
||||
<Link2 />
|
||||
</Button>
|
||||
)}
|
||||
<FilePrivilegeButton
|
||||
record={record}
|
||||
showPrivilegeModal={showPrivilegeModal}
|
||||
/>
|
||||
{isKnowledgeBase || (
|
||||
<Button
|
||||
variant="transparent"
|
||||
|
||||
@@ -61,6 +61,7 @@ type FilesTableProps = Pick<
|
||||
Pick<UseRowSelectionType, 'rowSelection' | 'setRowSelection'> &
|
||||
UseMoveDocumentShowType & {
|
||||
connectKnowledgeModal: UseHandleConnectToKnowledgeReturnType;
|
||||
showPrivilegeModal?(record: IFile): void;
|
||||
};
|
||||
|
||||
export function FilesTable({
|
||||
@@ -73,6 +74,7 @@ export function FilesTable({
|
||||
setRowSelection,
|
||||
showMoveFileModal,
|
||||
connectKnowledgeModal,
|
||||
showPrivilegeModal,
|
||||
}: FilesTableProps) {
|
||||
const [sorting, setSorting] = React.useState<SortingState>([]);
|
||||
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
||||
@@ -273,6 +275,7 @@ export function FilesTable({
|
||||
showFileRenameModal={showFileRenameModal}
|
||||
showMoveFileModal={showMoveFileModal}
|
||||
setRowSelection={setRowSelection}
|
||||
showPrivilegeModal={showPrivilegeModal}
|
||||
/>
|
||||
);
|
||||
},
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { UploadFormSchemaType } from '@/components/file-upload-dialog';
|
||||
import { useUploadBillingError } from '@/ee/hooks/use-upload-billing-error';
|
||||
import { useSetModalState } from '@/hooks/common-hooks';
|
||||
import { useUploadFile } from '@/hooks/use-file-request';
|
||||
import { useCallback } from 'react';
|
||||
@@ -11,19 +12,30 @@ export const useHandleUploadFile = () => {
|
||||
showModal: showFileUploadModal,
|
||||
} = useSetModalState();
|
||||
const { uploadFile, loading } = useUploadFile();
|
||||
const { handleUploadError } = useUploadBillingError();
|
||||
const id = useGetFolderId();
|
||||
|
||||
const onFileUploadOk = useCallback(
|
||||
async ({ fileList }: UploadFormSchemaType): Promise<number | undefined> => {
|
||||
if (fileList.length > 0) {
|
||||
const ret: number = await uploadFile({ fileList, parentId: id });
|
||||
if (ret === 0) {
|
||||
hideFileUploadModal();
|
||||
const ret = await uploadFile({
|
||||
fileList: fileList as File[],
|
||||
parentId: id,
|
||||
});
|
||||
|
||||
if (handleUploadError(ret)) {
|
||||
return ret?.code;
|
||||
}
|
||||
return ret;
|
||||
|
||||
if (ret?.code === 0) {
|
||||
hideFileUploadModal();
|
||||
return 0;
|
||||
}
|
||||
|
||||
return ret?.code;
|
||||
}
|
||||
},
|
||||
[uploadFile, hideFileUploadModal, id],
|
||||
[uploadFile, hideFileUploadModal, id, handleUploadError],
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { MoreButton } from '@/components/more-button';
|
||||
import { RenameDialog } from '@/components/rename-dialog';
|
||||
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
|
||||
import { useFetchAgentListByPage } from '@/hooks/use-agent-request';
|
||||
import { IFlow } from '@/interfaces/database/agent';
|
||||
import { AgentListItem, AgentListItemType } from '@/interfaces/database/agent';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { AgentDropdown } from '../agents/agent-dropdown';
|
||||
@@ -11,9 +12,11 @@ import { useRenameAgent } from '../agents/use-rename-agent';
|
||||
export function Agents({
|
||||
setListLength,
|
||||
setLoading,
|
||||
showPrivilegeModal,
|
||||
}: {
|
||||
setListLength: (length: number) => void;
|
||||
setLoading?: (loading: boolean) => void;
|
||||
showPrivilegeModal: (agent: IFlow) => () => void;
|
||||
}) {
|
||||
const { data, loading } = useFetchAgentListByPage();
|
||||
const { navigateToAgent } = useNavigatePage();
|
||||
@@ -51,6 +54,7 @@ export function Agents({
|
||||
<AgentDropdown
|
||||
showAgentRenameModal={showAgentRenameModal}
|
||||
agent={x}
|
||||
showPrivilegeModal={showPrivilegeModal(x)}
|
||||
>
|
||||
<MoreButton></MoreButton>
|
||||
</AgentDropdown>
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { CardSineLineContainer } from '@/components/card-singleline-container';
|
||||
import { EmptyCardType } from '@/components/empty/constant';
|
||||
import { EmptyAppCard } from '@/components/empty/empty';
|
||||
import { PrivilegeManagementDialog } from '@/ee/components/privilege-management/privilege-management-dialog';
|
||||
import { HomeIcon } from '@/components/svg-icon';
|
||||
import { Segmented, SegmentedValue } from '@/components/ui/segmented';
|
||||
import { Routes } from '@/routes';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { useShowPrivilegeDialog as useAgentShowPrivilegeDialog } from '@/ee/pages/agents/use-show-privilege-dialog';
|
||||
import { Agents } from './agent-list';
|
||||
import { SeeAllAppCard } from './application-card';
|
||||
import { ChatList } from './chat-list';
|
||||
@@ -33,6 +35,12 @@ export function Applications() {
|
||||
const navigate = useNavigate();
|
||||
const [listLength, setListLength] = useState(0);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const {
|
||||
privilegeModalVisible,
|
||||
hidePrivilegeModal,
|
||||
handShowPrivilegeModal,
|
||||
recordWithSourceType,
|
||||
} = useAgentShowPrivilegeDialog();
|
||||
|
||||
const handleNavigate = useCallback(
|
||||
({ isCreate }: { isCreate?: boolean }) => {
|
||||
@@ -89,6 +97,7 @@ export function Applications() {
|
||||
<Agents
|
||||
setListLength={(length: number) => setListLength(length)}
|
||||
setLoading={(loading: boolean) => setLoading(loading)}
|
||||
showPrivilegeModal={handShowPrivilegeModal}
|
||||
/>
|
||||
)}
|
||||
{val === Routes.Chats && (
|
||||
@@ -122,6 +131,12 @@ export function Applications() {
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{privilegeModalVisible && (
|
||||
<PrivilegeManagementDialog
|
||||
hideModal={hidePrivilegeModal}
|
||||
initialValues={recordWithSourceType}
|
||||
/>
|
||||
)}
|
||||
{/* </div> */}
|
||||
</section>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { HomeCard } from '@/components/home-card';
|
||||
import { MoreButton } from '@/components/more-button';
|
||||
import { PrivilegeManagementDialog } from '@/ee/components/privilege-management/privilege-management-dialog';
|
||||
import { RenameDialog } from '@/components/rename-dialog';
|
||||
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
|
||||
import { useFetchChatList } from '@/hooks/use-chat-request';
|
||||
@@ -7,6 +8,7 @@ import { useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ChatDropdown } from '../next-chats/chat-dropdown';
|
||||
import { useRenameChat } from '../next-chats/hooks/use-rename-chat';
|
||||
import { useShowPrivilegeDialog } from '@/ee/pages/next-chats/use-show-privilege-dialog';
|
||||
|
||||
export function ChatList({
|
||||
setListLength,
|
||||
@@ -31,23 +33,37 @@ export function ChatList({
|
||||
setListLength(data?.chats?.length || 0);
|
||||
setLoading?.(loading || false);
|
||||
}, [data, setListLength, loading, setLoading]);
|
||||
|
||||
const {
|
||||
handleShowPrivilegeModal,
|
||||
privilegeRecord,
|
||||
hidePrivilegeModal,
|
||||
privilegeModalVisible,
|
||||
} = useShowPrivilegeDialog();
|
||||
|
||||
return (
|
||||
<>
|
||||
{(data?.chats ?? []).slice(0, 10).map((x) => (
|
||||
<HomeCard
|
||||
key={x.id}
|
||||
data={{
|
||||
avatar: x.icon,
|
||||
...x,
|
||||
}}
|
||||
onClick={navigateToChat(x.id)}
|
||||
moreDropdown={
|
||||
<ChatDropdown chat={x} showChatRenameModal={showChatRenameModal}>
|
||||
<MoreButton></MoreButton>
|
||||
</ChatDropdown>
|
||||
}
|
||||
></HomeCard>
|
||||
))}
|
||||
{data?.chats &&
|
||||
data.chats.length > 0 &&
|
||||
data.chats.slice(0, 10).map((x) => (
|
||||
<HomeCard
|
||||
key={x.id}
|
||||
data={{
|
||||
avatar: x.icon,
|
||||
...x,
|
||||
}}
|
||||
onClick={navigateToChat(x.id)}
|
||||
moreDropdown={
|
||||
<ChatDropdown
|
||||
chat={x}
|
||||
showChatRenameModal={showChatRenameModal}
|
||||
showPrivilegeModal={handleShowPrivilegeModal(x)}
|
||||
>
|
||||
<MoreButton></MoreButton>
|
||||
</ChatDropdown>
|
||||
}
|
||||
></HomeCard>
|
||||
))}
|
||||
{chatRenameVisible && (
|
||||
<RenameDialog
|
||||
hideModal={hideChatRenameModal}
|
||||
@@ -57,6 +73,12 @@ export function ChatList({
|
||||
title={initialChatName || t('chat.createChat')}
|
||||
></RenameDialog>
|
||||
)}
|
||||
{privilegeModalVisible && (
|
||||
<PrivilegeManagementDialog
|
||||
hideModal={hidePrivilegeModal}
|
||||
initialValues={privilegeRecord}
|
||||
></PrivilegeManagementDialog>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
Loaded 100 of 136 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user