From 9024aee25543119a32eb94f8cf995ecf5ceba0b1 Mon Sep 17 00:00:00 2001 From: Daishan Peng <daishan@acorn.io> Date: Mon, 21 Oct 2024 13:56:07 -0700 Subject: [PATCH] Address comments Signed-off-by: Daishan Peng <daishan@acorn.io> --- pkg/api/handlers/agent.go | 8 +- .../handlers/knowledge/knowledge.go | 11 +- .../handlers/uploads/remoteknowledgesource.go | 1 - pkg/controller/routes.go | 1 - pkg/knowledge/knowledge.go | 4 +- .../knowledge/AgentKnowledgePanel.tsx | 152 ++++++++++++------ .../app/components/knowledge/FileItem.tsx | 8 +- .../components/knowledge/FileStatusIcon.tsx | 2 +- .../knowledge/RemoteFileItemChip.tsx | 55 +++---- .../knowledge/RemoteSourceSettingModal.tsx | 57 +++---- .../components/knowledge/file/FileModal.tsx | 10 +- .../knowledge/notion/NotionModal.tsx | 16 +- .../knowledge/onedrive/AddLinkModal.tsx | 4 +- .../knowledge/onedrive/OneDriveModal.tsx | 16 +- .../knowledge/website/AddWebsiteModal.tsx | 2 +- .../knowledge/website/WebsiteModal.tsx | 6 +- ui/admin/app/lib/model/knowledge.ts | 10 +- 17 files changed, 211 insertions(+), 152 deletions(-) diff --git a/pkg/api/handlers/agent.go b/pkg/api/handlers/agent.go index 6cdcbaff3..d79aac923 100644 --- a/pkg/api/handlers/agent.go +++ b/pkg/api/handlers/agent.go @@ -200,9 +200,11 @@ func (a *AgentHandler) ApproveKnowledgeFile(req api.Context) error { return err } - file.Spec.Approved = &[]bool{body.Approve}[0] - - return req.Storage.Update(req.Context(), &file) + if file.Spec.Approved == nil || *file.Spec.Approved != body.Approve { + file.Spec.Approved = &body.Approve + return req.Storage.Update(req.Context(), &file) + } + return nil } func (a *AgentHandler) DeleteKnowledge(req api.Context) error { diff --git a/pkg/controller/handlers/knowledge/knowledge.go b/pkg/controller/handlers/knowledge/knowledge.go index 90333083a..2bf38f0a9 100644 --- a/pkg/controller/handlers/knowledge/knowledge.go +++ b/pkg/controller/handlers/knowledge/knowledge.go @@ -303,15 +303,6 @@ func compileFileStatuses(ctx context.Context, client kclient.Client, ws *v1.Work return final, errors.Join(errs...) } -func (a *Handler) BindWorkspace(req router.Request, resp router.Response) error { - kFile := req.Object.(*v1.KnowledgeFile) - // force to associate with workspace so that it can reenqueue workspace when file are changed - if err := req.Get(&v1.Workspace{}, kFile.Namespace, kFile.Spec.WorkspaceName); err != nil { - return err - } - return nil -} - func (a *Handler) CleanupFile(req router.Request, resp router.Response) error { kFile := req.Object.(*v1.KnowledgeFile) @@ -327,7 +318,7 @@ func (a *Handler) CleanupFile(req router.Request, resp router.Response) error { return err } - if _, err := a.ingester.DeleteKnowledgeFiles(req.Ctx, kFile.Namespace, filepath.Join(workspace.GetDir(ws.Status.WorkspaceID), kFile.Spec.FileName)); err != nil { + if _, err := a.ingester.DeleteKnowledgeFiles(req.Ctx, kFile.Namespace, filepath.Join(workspace.GetDir(ws.Status.WorkspaceID), kFile.Spec.FileName), ws.Spec.KnowledgeSetName); err != nil { return err } diff --git a/pkg/controller/handlers/uploads/remoteknowledgesource.go b/pkg/controller/handlers/uploads/remoteknowledgesource.go index f00ab68e0..9d33ff570 100644 --- a/pkg/controller/handlers/uploads/remoteknowledgesource.go +++ b/pkg/controller/handlers/uploads/remoteknowledgesource.go @@ -367,7 +367,6 @@ func compileKnowledgeFiles(ctx context.Context, c client.Client, } else if err != nil { errs = append(errs, err) } - } if len(errs) > 0 { diff --git a/pkg/controller/routes.go b/pkg/controller/routes.go index 8739291ef..7aa5ca642 100644 --- a/pkg/controller/routes.go +++ b/pkg/controller/routes.go @@ -80,7 +80,6 @@ func (c *Controller) setupRoutes() error { // Knowledge files root.Type(&v1.KnowledgeFile{}).HandlerFunc(cleanup.Cleanup) - root.Type(&v1.KnowledgeFile{}).HandlerFunc(knowledge.BindWorkspace) root.Type(&v1.KnowledgeFile{}).FinalizeFunc(v1.KnowledgeFileFinalizer, knowledge.CleanupFile) // Workspaces diff --git a/pkg/knowledge/knowledge.go b/pkg/knowledge/knowledge.go index cf5b36d5d..5774f8612 100644 --- a/pkg/knowledge/knowledge.go +++ b/pkg/knowledge/knowledge.go @@ -31,13 +31,15 @@ func (i *Ingester) IngestKnowledge(ctx context.Context, namespace, knowledgeSetN ) } -func (i *Ingester) DeleteKnowledgeFiles(ctx context.Context, namespace, knowledgeFilePath string) (*invoke.Response, error) { +func (i *Ingester) DeleteKnowledgeFiles(ctx context.Context, namespace, knowledgeFilePath string, knowledgeSetName string) (*invoke.Response, error) { return i.invoker.SystemAction( ctx, "ingest-delete-file-", namespace, system.KnowledgeDeleteFileTool, knowledgeFilePath, + "GPTSCRIPT_DATASET="+knowledgeSetName, + "KNOW_JSON=true", ) } diff --git a/ui/admin/app/components/knowledge/AgentKnowledgePanel.tsx b/ui/admin/app/components/knowledge/AgentKnowledgePanel.tsx index 677b3c855..c874d51ea 100644 --- a/ui/admin/app/components/knowledge/AgentKnowledgePanel.tsx +++ b/ui/admin/app/components/knowledge/AgentKnowledgePanel.tsx @@ -1,15 +1,17 @@ import { Globe, SettingsIcon, UploadIcon } from "lucide-react"; import { useEffect, useMemo, useState } from "react"; -import useSWR from "swr"; +import useSWR, { SWRResponse } from "swr"; import { IngestionStatus, KnowledgeFile, RemoteKnowledgeSourceType, + getIngestedFilesCount, getIngestionStatus, } from "~/lib/model/knowledge"; import { ApiRoutes } from "~/lib/routers/apiRoutes"; import { KnowledgeService } from "~/lib/service/api/knowledgeService"; +import { assetUrl } from "~/lib/utils"; import { Button } from "~/components/ui/button"; @@ -26,7 +28,7 @@ export function AgentKnowledgePanel({ agentId }: { agentId: string }) { const [isNotionModalOpen, setIsNotionModalOpen] = useState(false); const [isWebsiteModalOpen, setIsWebsiteModalOpen] = useState(false); - const getKnowledgeFiles = useSWR( + const getKnowledgeFiles: SWRResponse<KnowledgeFile[], Error> = useSWR( KnowledgeService.getKnowledgeForAgent.key(agentId), ({ agentId }) => KnowledgeService.getKnowledgeForAgent(agentId).then((items) => @@ -221,72 +223,132 @@ export function AgentKnowledgePanel({ agentId }: { agentId: string }) { } }; + const notionFiles = knowledge.filter( + (item) => item.remoteKnowledgeSourceType === "notion" + ); + const onedriveFiles = knowledge.filter( + (item) => item.remoteKnowledgeSourceType === "onedrive" + ); + const websiteFiles = knowledge.filter( + (item) => item.remoteKnowledgeSourceType === "website" + ); + const localFiles = knowledge.filter( + (item) => !item.remoteKnowledgeSourceType + ); + return ( <div className="flex flex-col gap-4 justify-center items-center"> - <div className="flex w-full justify-between items-center px-4"> + <div className="flex w-full items-center justify-between gap-3 rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus-visible:ring-transparent"> <div className="flex items-center gap-2"> <UploadIcon className="h-5 w-5" /> <span className="text-lg font-semibold">Files</span> </div> - <Button - onClick={() => setIsAddFileModalOpen(true)} - className="flex items-center gap-2" - variant="secondary" - > - <SettingsIcon className="h-5 w-5" /> - </Button> + <div className="flex flex-row items-center gap-2"> + <div className="flex items-center gap-2"> + {getIngestedFilesCount(localFiles) > 0 && ( + <span className="text-sm font-medium text-gray-500"> + {getIngestedFilesCount(localFiles)}{" "} + {getIngestedFilesCount(localFiles) === 1 + ? "file" + : "files"}{" "} + ingested + </span> + )} + </div> + <Button + onClick={() => setIsAddFileModalOpen(true)} + className="flex items-center gap-2" + variant="ghost" + > + <SettingsIcon className="h-5 w-5" /> + </Button> + </div> </div> - <div className="flex w-full justify-between items-center px-4"> + <div className="flex w-full items-center justify-between gap-3 rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus-visible:ring-transparent"> <div className="flex items-center gap-2"> <Avatar className="h-5 w-5"> - <img src="/notion.svg" alt="Notion logo" /> + <img src={assetUrl("/notion.svg")} alt="Notion logo" /> </Avatar> <span className="text-lg font-semibold">Notion</span> </div> - <Button - onClick={() => onClickNotion()} - className="flex items-center gap-2" - variant="secondary" - > - <SettingsIcon className="h-5 w-5" /> - </Button> + <div className="flex flex-row items-center gap-2"> + {getIngestedFilesCount(notionFiles) > 0 && ( + <span className="text-sm font-medium text-gray-500"> + {getIngestedFilesCount(notionFiles)}{" "} + {getIngestedFilesCount(notionFiles) === 1 + ? "file" + : "files"}{" "} + ingested + </span> + )} + <Button + onClick={() => onClickNotion()} + className="flex items-center gap-2" + variant="ghost" + > + <SettingsIcon className="h-5 w-5" /> + </Button> + </div> </div> - <div className="flex w-full justify-between items-center px-4"> + <div className="flex w-full items-center justify-between gap-3 rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus-visible:ring-transparent"> <div className="flex items-center gap-2"> <Avatar className="h-5 w-5"> - <img src="/onedrive.svg" alt="OneDrive logo" /> + <img + src={assetUrl("/onedrive.svg")} + alt="OneDrive logo" + /> </Avatar> <span className="text-lg font-semibold">OneDrive</span> </div> - <Button - onClick={() => onClickOnedrive()} - className="flex items-center gap-2" - variant="secondary" - > - <SettingsIcon className="h-5 w-5" /> - </Button> + <div className="flex flex-row items-center gap-2"> + {getIngestedFilesCount(onedriveFiles) > 0 && ( + <span className="text-sm font-medium text-gray-500"> + {getIngestedFilesCount(onedriveFiles)}{" "} + {getIngestedFilesCount(onedriveFiles) === 1 + ? "file" + : "files"}{" "} + ingested + </span> + )} + <Button + onClick={() => onClickOnedrive()} + className="flex items-center gap-2" + variant="ghost" + > + <SettingsIcon className="h-5 w-5" /> + </Button> + </div> </div> - <div className="flex w-full justify-between items-center px-4"> + <div className="flex w-full items-center justify-between gap-3 rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus-visible:ring-transparent"> <div className="flex items-center gap-2"> <Globe className="h-5 w-5" /> <span className="text-lg font-semibold">Website</span> </div> - <Button - onClick={() => onClickWebsite()} - className="flex items-center gap-2" - variant="secondary" - > - <SettingsIcon className="h-5 w-5" /> - </Button> + <div className="flex flex-row items-center gap-2"> + {getIngestedFilesCount(websiteFiles) > 0 && ( + <span className="text-sm font-medium text-gray-500"> + {getIngestedFilesCount(websiteFiles)}{" "} + {getIngestedFilesCount(websiteFiles) === 1 + ? "file" + : "files"}{" "} + ingested + </span> + )} + <Button + onClick={() => onClickWebsite()} + className="flex items-center gap-2" + variant="ghost" + > + <SettingsIcon className="h-5 w-5" /> + </Button> + </div> </div> <FileModal agentId={agentId} isOpen={isAddFileModalOpen} onOpenChange={setIsAddFileModalOpen} startPolling={startPolling} - knowledge={knowledge.filter( - (item) => !item.remoteKnowledgeSourceType - )} + knowledge={localFiles} getKnowledgeFiles={getKnowledgeFiles} /> <NotionModal @@ -295,9 +357,7 @@ export function AgentKnowledgePanel({ agentId }: { agentId: string }) { onOpenChange={setIsNotionModalOpen} remoteKnowledgeSources={remoteKnowledgeSources} startPolling={startPolling} - knowledgeFiles={knowledge.filter( - (item) => item.remoteKnowledgeSourceType === "notion" - )} + knowledgeFiles={notionFiles} handleRemoteKnowledgeSourceSync={ handleRemoteKnowledgeSourceSync } @@ -308,9 +368,7 @@ export function AgentKnowledgePanel({ agentId }: { agentId: string }) { onOpenChange={setIsOnedriveModalOpen} remoteKnowledgeSources={remoteKnowledgeSources} startPolling={startPolling} - knowledgeFiles={knowledge.filter( - (item) => item.remoteKnowledgeSourceType === "onedrive" - )} + knowledgeFiles={onedriveFiles} handleRemoteKnowledgeSourceSync={ handleRemoteKnowledgeSourceSync } @@ -321,9 +379,7 @@ export function AgentKnowledgePanel({ agentId }: { agentId: string }) { onOpenChange={setIsWebsiteModalOpen} remoteKnowledgeSources={remoteKnowledgeSources} startPolling={startPolling} - knowledgeFiles={knowledge.filter( - (item) => item.remoteKnowledgeSourceType === "website" - )} + knowledgeFiles={websiteFiles} handleRemoteKnowledgeSourceSync={ handleRemoteKnowledgeSourceSync } diff --git a/ui/admin/app/components/knowledge/FileItem.tsx b/ui/admin/app/components/knowledge/FileItem.tsx index 64a1f3f3e..f8aad5845 100644 --- a/ui/admin/app/components/knowledge/FileItem.tsx +++ b/ui/admin/app/components/knowledge/FileItem.tsx @@ -64,16 +64,16 @@ function FileItem({ </div> {isApproved ? ( - // eslint-disable-next-line - <div - className="hover:cursor-pointer" + <Button + variant="ghost" + size="icon" onClick={() => { setIsApproved(false); approveFile(file, false); }} > <FileStatusIcon status={file.ingestionStatus} /> - </div> + </Button> ) : ( <Button variant="ghost" diff --git a/ui/admin/app/components/knowledge/FileStatusIcon.tsx b/ui/admin/app/components/knowledge/FileStatusIcon.tsx index c1edd83e8..fa4454a89 100644 --- a/ui/admin/app/components/knowledge/FileStatusIcon.tsx +++ b/ui/admin/app/components/knowledge/FileStatusIcon.tsx @@ -35,7 +35,7 @@ const FileStatusIcon: React.FC<FileStatusIconProps> = ({ status }) => { const [Icon, className] = ingestionIcons[status.status]; return ( - <div className={cn("flex items-center mr-2", className)}> + <div className={cn("flex items-center", className)}> <TooltipProvider> <Tooltip> <TooltipTrigger asChild> diff --git a/ui/admin/app/components/knowledge/RemoteFileItemChip.tsx b/ui/admin/app/components/knowledge/RemoteFileItemChip.tsx index ea3fa39d9..a7fda75c7 100644 --- a/ui/admin/app/components/knowledge/RemoteFileItemChip.tsx +++ b/ui/admin/app/components/knowledge/RemoteFileItemChip.tsx @@ -1,5 +1,4 @@ import { PlusIcon } from "lucide-react"; -import { useEffect, useState } from "react"; import { KnowledgeFile, @@ -37,10 +36,6 @@ export default function RemoteFileItemChip({ approveFile, ...props }: RemoteFileItemProps) { - const [isApproved, setIsApproved] = useState(false); - useEffect(() => { - setIsApproved(file.approved!); - }, [file.approved]); return ( <TooltipProvider> <Tooltip> @@ -53,7 +48,7 @@ export default function RemoteFileItemChip({ { "bg-destructive-background border-destructive hover:cursor-pointer": error, - "grayscale opacity-60": !isApproved, + "grayscale opacity-60": !file.approved, }, className )} @@ -83,29 +78,31 @@ export default function RemoteFileItemChip({ </span> </div> - {isApproved ? ( - // eslint-disable-next-line - <div - className="hover:cursor-pointer" - onClick={() => { - setIsApproved(false); - approveFile(file, false); - }} - > - <FileStatusIcon status={file.ingestionStatus} /> - </div> - ) : ( - <Button - variant="ghost" - size="icon" - onClick={() => { - setIsApproved(true); - approveFile(file, true); - }} - > - <PlusIcon className="w-4 h-4" /> - </Button> - )} + <div className="mr-2"> + {file.approved ? ( + <Button + variant="ghost" + size="icon" + onClick={() => { + approveFile(file, false); + }} + > + <FileStatusIcon + status={file.ingestionStatus} + /> + </Button> + ) : ( + <Button + variant="ghost" + size="icon" + onClick={() => { + approveFile(file, true); + }} + > + <PlusIcon className="w-4 h-4" /> + </Button> + )} + </div> </div> </TooltipTrigger> </Tooltip> diff --git a/ui/admin/app/components/knowledge/RemoteSourceSettingModal.tsx b/ui/admin/app/components/knowledge/RemoteSourceSettingModal.tsx index 4078b4036..bc769804a 100644 --- a/ui/admin/app/components/knowledge/RemoteSourceSettingModal.tsx +++ b/ui/admin/app/components/knowledge/RemoteSourceSettingModal.tsx @@ -13,6 +13,7 @@ import { } from "~/components/ui/dialog"; import { Input } from "~/components/ui/input"; import { Switch } from "~/components/ui/switch"; +import { useAsync } from "~/hooks/useAsync"; type RemoteSourceSettingModalProps = { agentId: string; @@ -27,38 +28,37 @@ const RemoteSourceSettingModal: React.FC<RemoteSourceSettingModalProps> = ({ onOpenChange, remoteKnowledgeSource, }) => { - const [autoApprove, setAutoApprove] = useState( - remoteKnowledgeSource.autoApprove || false - ); + const [autoApprove, setAutoApprove] = useState(false); - const [syncSchedule, setSyncSchedule] = useState( - remoteKnowledgeSource.syncSchedule || "" - ); + useEffect(() => { + console.log("dbug"); + setAutoApprove(remoteKnowledgeSource?.autoApprove || false); + }, [remoteKnowledgeSource]); + + const [syncSchedule, setSyncSchedule] = useState(""); useEffect(() => { - setSyncSchedule(remoteKnowledgeSource.syncSchedule || ""); + setSyncSchedule(remoteKnowledgeSource?.syncSchedule || ""); }, [remoteKnowledgeSource]); - const handleSave = async () => { - try { - await KnowledgeService.updateRemoteKnowledgeSource( - agentId, - remoteKnowledgeSource.id, - { - ...remoteKnowledgeSource, - syncSchedule, - autoApprove, - } - ); - onOpenChange(false); - } catch (error) { - console.error("Failed to update sync schedule:", error); - } + const updateRemoteKnowledgeSource = async () => { + await KnowledgeService.updateRemoteKnowledgeSource( + agentId, + remoteKnowledgeSource.id, + { + ...remoteKnowledgeSource, + syncSchedule, + autoApprove, + } + ); + onOpenChange(false); }; + const handleSave = useAsync(updateRemoteKnowledgeSource); + return ( <Dialog open={isOpen} onOpenChange={onOpenChange}> - <DialogContent> + <DialogContent aria-describedby={undefined}> <DialogHeader> <DialogTitle>Update Source Settings</DialogTitle> </DialogHeader> @@ -91,14 +91,9 @@ const RemoteSourceSettingModal: React.FC<RemoteSourceSettingModalProps> = ({ id="autoApprove" className="mr-2" checked={autoApprove} - onChange={() => setAutoApprove(!autoApprove)} + onClick={() => setAutoApprove((prev) => !prev)} /> - <label - htmlFor="autoApprove" - className="text-sm text-gray-600 dark:text-gray-400 mr-2" - > - Include new pages - </label> + Include new pages </div> <p className="text-sm text-gray-500 mt-4"> If enabled, new pages will be added to the knowledge @@ -106,7 +101,7 @@ const RemoteSourceSettingModal: React.FC<RemoteSourceSettingModalProps> = ({ </p> </div> <DialogFooter> - <Button onClick={handleSave}>Save</Button> + <Button onClick={handleSave.execute}>Save</Button> </DialogFooter> </DialogContent> </Dialog> diff --git a/ui/admin/app/components/knowledge/file/FileModal.tsx b/ui/admin/app/components/knowledge/file/FileModal.tsx index 96b259907..d55bfe5e3 100644 --- a/ui/admin/app/components/knowledge/file/FileModal.tsx +++ b/ui/admin/app/components/knowledge/file/FileModal.tsx @@ -1,5 +1,6 @@ import { UploadIcon } from "lucide-react"; import { useCallback, useRef } from "react"; +import { SWRResponse } from "swr"; import { IngestionStatus, KnowledgeFile } from "~/lib/model/knowledge"; import { KnowledgeService } from "~/lib/service/api/knowledgeService"; @@ -23,8 +24,7 @@ import IngestionStatusComponent from "../IngestionStatus"; interface FileModalProps { agentId: string; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - getKnowledgeFiles: any; + getKnowledgeFiles: SWRResponse<KnowledgeFile[], Error>; isOpen: boolean; onOpenChange: (open: boolean) => void; startPolling: () => void; @@ -51,11 +51,13 @@ function FileModal({ // Revalidating here would cause knowledge to be refreshed // for each file being uploaded, which is not desirable. const newItem: KnowledgeFile = { + id: "", fileName: file.name, agentID: agentId, // set ingestion status to starting to ensure polling is enabled ingestionStatus: { status: IngestionStatus.Queued }, fileDetails: {}, + approved: true, }; // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -100,7 +102,7 @@ function FileModal({ await KnowledgeService.deleteKnowledgeFromAgent(agentId, item.fileName); // optomistic update without cache revalidation - getKnowledgeFiles.mutate((prev: KnowledgeFile[]) => + getKnowledgeFiles.mutate((prev: KnowledgeFile[] | undefined) => prev?.filter((prevItem) => prevItem.fileName !== item.fileName) ); }); @@ -119,7 +121,7 @@ function FileModal({ className="mr-2" onClick={() => fileInputRef.current?.click()} > - <UploadIcon className="upload-icon mr-2" /> + <UploadIcon className="upload-icon" /> </Button> </DialogHeader> <ScrollArea className="max-h-[800px] mt-4"> diff --git a/ui/admin/app/components/knowledge/notion/NotionModal.tsx b/ui/admin/app/components/knowledge/notion/NotionModal.tsx index 154e78316..8ff835247 100644 --- a/ui/admin/app/components/knowledge/notion/NotionModal.tsx +++ b/ui/admin/app/components/knowledge/notion/NotionModal.tsx @@ -7,6 +7,7 @@ import { RemoteKnowledgeSourceType, } from "~/lib/model/knowledge"; import { KnowledgeService } from "~/lib/service/api/knowledgeService"; +import { assetUrl } from "~/lib/utils"; import RemoteFileItemChip from "~/components/knowledge/RemoteFileItemChip"; import RemoteKnowledgeSourceStatus from "~/components/knowledge/RemoteKnowledgeSourceStatus"; @@ -54,11 +55,7 @@ export const NotionModal: FC<NotionModalProps> = ({ const handleApproveAll = async () => { for (const file of knowledgeFiles) { - await KnowledgeService.approveKnowledgeFile( - agentId, - file.id!, - true - ); + await KnowledgeService.approveKnowledgeFile(agentId, file.id, true); } startPolling(); }; @@ -73,7 +70,10 @@ export const NotionModal: FC<NotionModalProps> = ({ <DialogTitle className="flex flex-row items-center text-xl font-semibold mb-4 justify-between"> <div className="flex flex-row items-center"> <Avatar className="flex-row items-center w-6 h-6 mr-2"> - <img src="/notion.svg" alt="Notion logo" /> + <img + src={assetUrl("/notion.svg")} + alt="Notion logo" + /> </Avatar> Notion </div> @@ -100,7 +100,7 @@ export const NotionModal: FC<NotionModalProps> = ({ </div> </DialogTitle> </DialogHeader> - <ScrollArea> + <ScrollArea className="max-h-[800px]"> <div className="flex flex-col gap-2"> {knowledgeFiles.map((item) => ( <RemoteFileItemChip @@ -149,7 +149,7 @@ export const NotionModal: FC<NotionModalProps> = ({ {loading ? ( <LoadingSpinner className="w-4 h-4" /> ) : ( - "Add All" + "Ingest All" )} </Button> <Button diff --git a/ui/admin/app/components/knowledge/onedrive/AddLinkModal.tsx b/ui/admin/app/components/knowledge/onedrive/AddLinkModal.tsx index 414de6b69..d4a7f03dd 100644 --- a/ui/admin/app/components/knowledge/onedrive/AddLinkModal.tsx +++ b/ui/admin/app/components/knowledge/onedrive/AddLinkModal.tsx @@ -32,9 +32,11 @@ const AddLinkModal: FC<AddLinkModalProps> = ({ const [newLink, setNewLink] = useState(""); const handleSave = async () => { + if (!onedriveSource) return; + await KnowledgeService.updateRemoteKnowledgeSource( agentId, - onedriveSource!.id!, + onedriveSource!.id, { ...onedriveSource, onedriveConfig: { diff --git a/ui/admin/app/components/knowledge/onedrive/OneDriveModal.tsx b/ui/admin/app/components/knowledge/onedrive/OneDriveModal.tsx index 551ef3013..f90e51fcd 100644 --- a/ui/admin/app/components/knowledge/onedrive/OneDriveModal.tsx +++ b/ui/admin/app/components/knowledge/onedrive/OneDriveModal.tsx @@ -3,10 +3,10 @@ import { ChevronUp, FileIcon, FolderIcon, - PlusIcon, RefreshCcwIcon, SettingsIcon, Trash, + UploadIcon, } from "lucide-react"; import { FC, useEffect, useState } from "react"; @@ -16,6 +16,7 @@ import { RemoteKnowledgeSourceType, } from "~/lib/model/knowledge"; import { KnowledgeService } from "~/lib/service/api/knowledgeService"; +import { assetUrl } from "~/lib/utils"; import RemoteKnowledgeSourceStatus from "~/components/knowledge/RemoteKnowledgeSourceStatus"; import { LoadingSpinner } from "~/components/ui/LoadingSpinner"; @@ -103,7 +104,10 @@ export const OnedriveModal: FC<OnedriveModalProps> = ({ <DialogTitle className="flex flex-row items-center text-xl font-semibold mb-4 justify-between"> <div className="flex flex-row items-center"> <Avatar className="flex-row items-center w-6 h-6 mr-2"> - <img src="/onedrive.svg" alt="OneDrive logo" /> + <img + src={assetUrl("/onedrive.svg")} + alt="OneDrive logo" + /> </Avatar> OneDrive </div> @@ -114,7 +118,7 @@ export const OnedriveModal: FC<OnedriveModalProps> = ({ onClick={() => setIsAddLinkModalOpen(true)} className="mr-2" > - <PlusIcon className="w-4 h-4" /> + <UploadIcon className="w-4 h-4" /> </Button> <Button size="sm" @@ -173,7 +177,9 @@ export const OnedriveModal: FC<OnedriveModalProps> = ({ ) : ( <Avatar className="mr-2 h-4 w-4"> <img - src="/onedrive.svg" + src={assetUrl( + "/onedrive.svg" + )} alt="OneDrive logo" /> </Avatar> @@ -332,7 +338,7 @@ export const OnedriveModal: FC<OnedriveModalProps> = ({ {loading ? ( <LoadingSpinner className="w-4 h-4" /> ) : ( - "Add All" + "Ingest All" )} </Button> <Button diff --git a/ui/admin/app/components/knowledge/website/AddWebsiteModal.tsx b/ui/admin/app/components/knowledge/website/AddWebsiteModal.tsx index 36f45d432..7a0c5906c 100644 --- a/ui/admin/app/components/knowledge/website/AddWebsiteModal.tsx +++ b/ui/admin/app/components/knowledge/website/AddWebsiteModal.tsx @@ -56,7 +56,7 @@ const AddWebsiteModal: FC<AddWebsiteModalProps> = ({ <Dialog open={isOpen} onOpenChange={onOpenChange}> <DialogContent aria-describedby={undefined} - className="data-[state=open]:animate-contentShow fixed top-[50%] left-[50%] max-h-[85vh] w-[90vw] max-w-[900px] translate-x-[-50%] translate-y-[-50%] rounded-[6px] bg-white dark:bg-secondary p-[25px] shadow-[hsl(206_22%_7%_/_35%)_0px_10px_38px_-10px,_hsl(206_22%_7%_/_20%)_0px_10px_20px_-15px] focus:outline-none" + className="data-[state=open]:animate-contentShow fixed top-[50%] left-[50%] max-h-[85vh] w-[90vw] max-w-[400px] translate-x-[-50%] translate-y-[-50%] rounded-[6px] bg-white dark:bg-secondary p-[25px] shadow-[hsl(206_22%_7%_/_35%)_0px_10px_38px_-10px,_hsl(206_22%_7%_/_20%)_0px_10px_20px_-15px] focus:outline-none" > <DialogTitle className="flex flex-row items-center text-xl font-semibold mb-4 justify-between"> Add Website diff --git a/ui/admin/app/components/knowledge/website/WebsiteModal.tsx b/ui/admin/app/components/knowledge/website/WebsiteModal.tsx index 25be65528..8e707e625 100644 --- a/ui/admin/app/components/knowledge/website/WebsiteModal.tsx +++ b/ui/admin/app/components/knowledge/website/WebsiteModal.tsx @@ -2,10 +2,10 @@ import { ChevronDown, ChevronUp, Globe, - Plus, RefreshCcwIcon, SettingsIcon, Trash, + UploadIcon, } from "lucide-react"; import { FC, useEffect, useState } from "react"; @@ -113,7 +113,7 @@ export const WebsiteModal: FC<WebsiteModalProps> = ({ onClick={() => setIsAddWebsiteModalOpen(true)} className="mr-2" > - <Plus className="w-4 h-4" /> + <UploadIcon className="w-4 h-4" /> </Button> <Button size="sm" @@ -251,7 +251,7 @@ export const WebsiteModal: FC<WebsiteModalProps> = ({ {loading ? ( <LoadingSpinner className="w-4 h-4" /> ) : ( - "Add All" + "Ingest All" )} </Button> <Button diff --git a/ui/admin/app/lib/model/knowledge.ts b/ui/admin/app/lib/model/knowledge.ts index 71327d556..914f39667 100644 --- a/ui/admin/app/lib/model/knowledge.ts +++ b/ui/admin/app/lib/model/knowledge.ts @@ -122,7 +122,7 @@ type FileDetails = { }; export type KnowledgeFile = { - id?: string; + id: string; deleted?: string; fileName: string; agentID?: string; @@ -190,3 +190,11 @@ export function getMessage( return msg || "Queued"; } + +export function getIngestedFilesCount(knowledge: KnowledgeFile[]) { + return knowledge.filter( + (item) => + item.ingestionStatus?.status === IngestionStatus.Finished || + item.ingestionStatus?.status === IngestionStatus.Skipped + ).length; +}