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;
+}