From 33006eafa40cbb7b21676cc9a7727a77f997a148 Mon Sep 17 00:00:00 2001 From: Daishan Peng Date: Thu, 17 Oct 2024 01:13:01 -0700 Subject: [PATCH 1/6] Knowledge UX revamp Signed-off-by: Daishan Peng --- apiclient/types/remoteKnowledgeSource.go | 20 +- apiclient/types/zz_generated.deepcopy.go | 46 +- pkg/api/handlers/remoteknowledgesource.go | 5 +- .../handlers/knowledge/knowledge.go | 6 - .../openapi/generated/openapi_generated.go | 82 ++- ui/admin/app/components/agent/Agent.tsx | 8 +- .../app/components/knowledge/AddFileModal.tsx | 176 ++++--- .../knowledge/AgentKnowledgePanel.tsx | 320 ++---------- .../app/components/knowledge/FileSource.tsx | 479 ++++++++++++++++++ .../components/knowledge/IngestionStatus.tsx | 128 +++++ .../components/knowledge/RemoteFileAvatar.tsx | 10 +- .../knowledge/RemoteKnowledgeSourceStatus.tsx | 16 +- .../knowledge/RemoteSourceSettingModal.tsx | 87 ++++ .../knowledge/notion/NotionModal.tsx | 112 ++-- .../knowledge/onedrive/OneDriveModal.tsx | 359 ++++++++----- .../knowledge/website/WebsiteModal.tsx | 305 ++++++----- ui/admin/app/lib/model/knowledge.ts | 13 +- 17 files changed, 1458 insertions(+), 714 deletions(-) create mode 100644 ui/admin/app/components/knowledge/FileSource.tsx create mode 100644 ui/admin/app/components/knowledge/IngestionStatus.tsx create mode 100644 ui/admin/app/components/knowledge/RemoteSourceSettingModal.tsx diff --git a/apiclient/types/remoteKnowledgeSource.go b/apiclient/types/remoteKnowledgeSource.go index b1beb13b..d7d936d0 100644 --- a/apiclient/types/remoteKnowledgeSource.go +++ b/apiclient/types/remoteKnowledgeSource.go @@ -39,9 +39,7 @@ type OneDriveConfig struct { SharedLinks []string `json:"sharedLinks,omitempty"` } -type NotionConfig struct { - Pages []string `json:"pages,omitempty"` -} +type NotionConfig struct{} type WebsiteCrawlingConfig struct { URLs []string `json:"urls,omitempty"` @@ -56,6 +54,12 @@ type RemoteKnowledgeSourceState struct { type OneDriveLinksConnectorState struct { Folders FolderSet `json:"folders,omitempty"` Files map[string]FileState `json:"files,omitempty"` + Links map[string]LinkState `json:"links,omitempty"` +} + +type LinkState struct { + IsFolder bool `json:"isFolder,omitempty"` + Name string `json:"name,omitempty"` } type FileState struct { @@ -75,7 +79,11 @@ type NotionPage struct { } type WebsiteCrawlingConnectorState struct { - ScrapeJobIds map[string]string `json:"scrapeJobIds"` - Folders FolderSet `json:"folders"` - Pages map[string]Item `json:"pages"` + ScrapeJobIds map[string]string `json:"scrapeJobIds"` + Folders FolderSet `json:"folders"` + Pages map[string]PageDetails `json:"pages"` +} + +type PageDetails struct { + ParentURL string `json:"parentUrl"` } diff --git a/apiclient/types/zz_generated.deepcopy.go b/apiclient/types/zz_generated.deepcopy.go index 33af6a08..a50b891c 100644 --- a/apiclient/types/zz_generated.deepcopy.go +++ b/apiclient/types/zz_generated.deepcopy.go @@ -435,6 +435,21 @@ func (in *KnowledgeFileList) DeepCopy() *KnowledgeFileList { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LinkState) DeepCopyInto(out *LinkState) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LinkState. +func (in *LinkState) DeepCopy() *LinkState { + if in == nil { + return nil + } + out := new(LinkState) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Metadata) DeepCopyInto(out *Metadata) { *out = *in @@ -472,11 +487,6 @@ func (in *Metadata) DeepCopy() *Metadata { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *NotionConfig) DeepCopyInto(out *NotionConfig) { *out = *in - if in.Pages != nil { - in, out := &in.Pages, &out.Pages - *out = make([]string, len(*in)) - copy(*out, *in) - } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NotionConfig. @@ -632,6 +642,13 @@ func (in *OneDriveLinksConnectorState) DeepCopyInto(out *OneDriveLinksConnectorS (*out)[key] = val } } + if in.Links != nil { + in, out := &in.Links, &out.Links + *out = make(map[string]LinkState, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OneDriveLinksConnectorState. @@ -644,6 +661,21 @@ func (in *OneDriveLinksConnectorState) DeepCopy() *OneDriveLinksConnectorState { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PageDetails) DeepCopyInto(out *PageDetails) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PageDetails. +func (in *PageDetails) DeepCopy() *PageDetails { + if in == nil { + return nil + } + out := new(PageDetails) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Progress) DeepCopyInto(out *Progress) { *out = *in @@ -758,7 +790,7 @@ func (in *RemoteKnowledgeSourceInput) DeepCopyInto(out *RemoteKnowledgeSourceInp if in.NotionConfig != nil { in, out := &in.NotionConfig, &out.NotionConfig *out = new(NotionConfig) - (*in).DeepCopyInto(*out) + **out = **in } if in.WebsiteCrawlingConfig != nil { in, out := &in.WebsiteCrawlingConfig, &out.WebsiteCrawlingConfig @@ -1326,7 +1358,7 @@ func (in *WebsiteCrawlingConnectorState) DeepCopyInto(out *WebsiteCrawlingConnec } if in.Pages != nil { in, out := &in.Pages, &out.Pages - *out = make(map[string]Item, len(*in)) + *out = make(map[string]PageDetails, len(*in)) for key, val := range *in { (*out)[key] = val } diff --git a/pkg/api/handlers/remoteknowledgesource.go b/pkg/api/handlers/remoteknowledgesource.go index e6754f74..591af6f7 100644 --- a/pkg/api/handlers/remoteknowledgesource.go +++ b/pkg/api/handlers/remoteknowledgesource.go @@ -209,8 +209,9 @@ func checkConfigChanged(input types.RemoteKnowledgeSourceInput, remoteKnowledgeS return !equality.Semantic.DeepEqual(*input.OneDriveConfig, *remoteKnowledgeSource.Spec.Manifest.OneDriveConfig) } - if input.NotionConfig != nil && remoteKnowledgeSource.Spec.Manifest.NotionConfig != nil { - return !equality.Semantic.DeepEqual(*input.NotionConfig, *remoteKnowledgeSource.Spec.Manifest.NotionConfig) + if remoteKnowledgeSource.Spec.Manifest.SourceType == types.RemoteKnowledgeSourceTypeNotion { + // we never resync notion on update, this is because by default we sync every page that it has access to + return false } if input.WebsiteCrawlingConfig != nil && remoteKnowledgeSource.Spec.Manifest.WebsiteCrawlingConfig != nil { diff --git a/pkg/controller/handlers/knowledge/knowledge.go b/pkg/controller/handlers/knowledge/knowledge.go index 82589d9a..23666fc7 100644 --- a/pkg/controller/handlers/knowledge/knowledge.go +++ b/pkg/controller/handlers/knowledge/knowledge.go @@ -135,7 +135,6 @@ func (a *Handler) IngestKnowledge(req router.Request, resp router.Response) erro digest.Write([]byte(file.Status.FileDetails.UpdatedAt)) digest.Write([]byte{0}) } - var syncNeeded bool hash := fmt.Sprintf("%x", digest.Sum(nil)) @@ -264,11 +263,6 @@ func compileFileStatuses(ctx context.Context, client kclient.Client, ws *v1.Work errs = append(errs, fmt.Errorf("failed to get knowledge file: %s", err)) } - if ingestionStatus.Status == "skipped" { - // Don't record the rather useless skipped messages - continue - } - if ingestionStatus.Status == "finished" { delete(final, file.Name) } diff --git a/pkg/storage/openapi/generated/openapi_generated.go b/pkg/storage/openapi/generated/openapi_generated.go index aa4c6903..07bf84c3 100644 --- a/pkg/storage/openapi/generated/openapi_generated.go +++ b/pkg/storage/openapi/generated/openapi_generated.go @@ -35,6 +35,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/otto8-ai/otto8/apiclient/types.Item": schema_otto8_ai_otto8_apiclient_types_Item(ref), "github.com/otto8-ai/otto8/apiclient/types.KnowledgeFile": schema_otto8_ai_otto8_apiclient_types_KnowledgeFile(ref), "github.com/otto8-ai/otto8/apiclient/types.KnowledgeFileList": schema_otto8_ai_otto8_apiclient_types_KnowledgeFileList(ref), + "github.com/otto8-ai/otto8/apiclient/types.LinkState": schema_otto8_ai_otto8_apiclient_types_LinkState(ref), "github.com/otto8-ai/otto8/apiclient/types.Metadata": schema_otto8_ai_otto8_apiclient_types_Metadata(ref), "github.com/otto8-ai/otto8/apiclient/types.NotionConfig": schema_otto8_ai_otto8_apiclient_types_NotionConfig(ref), "github.com/otto8-ai/otto8/apiclient/types.NotionConnectorState": schema_otto8_ai_otto8_apiclient_types_NotionConnectorState(ref), @@ -45,6 +46,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/otto8-ai/otto8/apiclient/types.OAuthAppManifest": schema_otto8_ai_otto8_apiclient_types_OAuthAppManifest(ref), "github.com/otto8-ai/otto8/apiclient/types.OneDriveConfig": schema_otto8_ai_otto8_apiclient_types_OneDriveConfig(ref), "github.com/otto8-ai/otto8/apiclient/types.OneDriveLinksConnectorState": schema_otto8_ai_otto8_apiclient_types_OneDriveLinksConnectorState(ref), + "github.com/otto8-ai/otto8/apiclient/types.PageDetails": schema_otto8_ai_otto8_apiclient_types_PageDetails(ref), "github.com/otto8-ai/otto8/apiclient/types.Progress": schema_otto8_ai_otto8_apiclient_types_Progress(ref), "github.com/otto8-ai/otto8/apiclient/types.Prompt": schema_otto8_ai_otto8_apiclient_types_Prompt(ref), "github.com/otto8-ai/otto8/apiclient/types.RemoteKnowledgeSource": schema_otto8_ai_otto8_apiclient_types_RemoteKnowledgeSource(ref), @@ -1055,6 +1057,30 @@ func schema_otto8_ai_otto8_apiclient_types_KnowledgeFileList(ref common.Referenc } } +func schema_otto8_ai_otto8_apiclient_types_LinkState(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "isFolder": { + SchemaProps: spec.SchemaProps{ + Type: []string{"boolean"}, + Format: "", + }, + }, + "name": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + func schema_otto8_ai_otto8_apiclient_types_Metadata(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -1120,22 +1146,6 @@ func schema_otto8_ai_otto8_apiclient_types_NotionConfig(ref common.ReferenceCall Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "pages": { - SchemaProps: spec.SchemaProps{ - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - }, - }, - }, - }, }, }, } @@ -1439,11 +1449,45 @@ func schema_otto8_ai_otto8_apiclient_types_OneDriveLinksConnectorState(ref commo }, }, }, + "links": { + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/otto8-ai/otto8/apiclient/types.LinkState"), + }, + }, + }, + }, + }, }, }, }, Dependencies: []string{ - "github.com/otto8-ai/otto8/apiclient/types.FileState", "github.com/otto8-ai/otto8/apiclient/types.Item"}, + "github.com/otto8-ai/otto8/apiclient/types.FileState", "github.com/otto8-ai/otto8/apiclient/types.Item", "github.com/otto8-ai/otto8/apiclient/types.LinkState"}, + } +} + +func schema_otto8_ai_otto8_apiclient_types_PageDetails(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "parentUrl": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"parentUrl"}, + }, + }, } } @@ -2879,7 +2923,7 @@ func schema_otto8_ai_otto8_apiclient_types_WebsiteCrawlingConnectorState(ref com Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("github.com/otto8-ai/otto8/apiclient/types.Item"), + Ref: ref("github.com/otto8-ai/otto8/apiclient/types.PageDetails"), }, }, }, @@ -2890,7 +2934,7 @@ func schema_otto8_ai_otto8_apiclient_types_WebsiteCrawlingConnectorState(ref com }, }, Dependencies: []string{ - "github.com/otto8-ai/otto8/apiclient/types.Item"}, + "github.com/otto8-ai/otto8/apiclient/types.Item", "github.com/otto8-ai/otto8/apiclient/types.PageDetails"}, } } diff --git a/ui/admin/app/components/agent/Agent.tsx b/ui/admin/app/components/agent/Agent.tsx index bffffb5b..13785f27 100644 --- a/ui/admin/app/components/agent/Agent.tsx +++ b/ui/admin/app/components/agent/Agent.tsx @@ -1,4 +1,10 @@ -import { LibraryIcon, RotateCcw, WrenchIcon } from "lucide-react"; +import { + LibraryIcon, + PlugIcon, + Plus, + RotateCcw, + WrenchIcon, +} from "lucide-react"; import { useCallback, useState } from "react"; import { Agent as AgentType } from "~/lib/model/agents"; diff --git a/ui/admin/app/components/knowledge/AddFileModal.tsx b/ui/admin/app/components/knowledge/AddFileModal.tsx index 58091c3c..98a6bb39 100644 --- a/ui/admin/app/components/knowledge/AddFileModal.tsx +++ b/ui/admin/app/components/knowledge/AddFileModal.tsx @@ -1,5 +1,4 @@ -import { Globe, Plus } from "lucide-react"; -import { RefObject, useState } from "react"; +import { CheckIcon, Globe } from "lucide-react"; import { RemoteKnowledgeSource } from "~/lib/model/knowledge"; import { KnowledgeService } from "~/lib/service/api/knowledgeService"; @@ -15,49 +14,101 @@ import { DialogTitle, } from "~/components/ui/dialog"; -import { NotionModal } from "./notion/NotionModal"; -import { OnedriveModal } from "./onedrive/OneDriveModal"; -import { WebsiteModal } from "./website/WebsiteModal"; - interface AddFileModalProps { - fileInputRef: RefObject; agentId: string; isOpen: boolean; - startPolling: () => void; onOpenChange: (open: boolean) => void; remoteKnowledgeSources: RemoteKnowledgeSource[]; + onWebsiteModalOpen: (open: boolean) => void; + onOneDriveModalOpen: (open: boolean) => void; + onNotionModalOpen: (open: boolean) => void; + getRemoteKnowledgeSources: any; } export const AddFileModal = ({ - fileInputRef, agentId, isOpen, - startPolling, onOpenChange, remoteKnowledgeSources, + onWebsiteModalOpen, + onOneDriveModalOpen, + onNotionModalOpen, + getRemoteKnowledgeSources, }: AddFileModalProps) => { - const [isOnedriveModalOpen, setIsOnedriveModalOpen] = useState(false); - const [isNotionModalOpen, setIsNotionModalOpen] = useState(false); - const [isWebsiteModalOpen, setIsWebsiteModalOpen] = useState(false); - - const getNotionSource = async () => { - const notionSource = remoteKnowledgeSources.find( - (source) => source.sourceType === "notion" - ); - return notionSource; - }; + const isNotionSourceEnabled = remoteKnowledgeSources.some( + (source) => source.sourceType === "notion" + ); + const isOnedriveSourceEnabled = remoteKnowledgeSources.some( + (source) => source.sourceType === "onedrive" + ); + const isWebsiteSourceEnabled = remoteKnowledgeSources.some( + (source) => source.sourceType === "website" + ); + let notionSource = remoteKnowledgeSources.find( + (source) => source.sourceType === "notion" + ); + let onedriveSource = remoteKnowledgeSources.find( + (source) => source.sourceType === "onedrive" + ); + let websiteSource = remoteKnowledgeSources.find( + (source) => source.sourceType === "website" + ); const onClickNotion = async () => { // For notion, we need to ensure the remote knowledge source is created so that client can fetch a list of pages - const notionSource = await getNotionSource(); if (!notionSource) { await KnowledgeService.createRemoteKnowledgeSource(agentId, { sourceType: "notion", + disableIngestionAfterSync: true, + }); + const intervalId = setInterval(() => { + getRemoteKnowledgeSources.mutate(); + notionSource = remoteKnowledgeSources.find( + (source) => source.sourceType === "notion" + ); + if (notionSource?.runID) { + clearInterval(intervalId); + } + }, 1000); + setTimeout(() => { + clearInterval(intervalId); + }, 10000); + } + onOpenChange(false); + onNotionModalOpen(true); + }; + + const onClickOnedrive = async () => { + if (!onedriveSource) { + await KnowledgeService.createRemoteKnowledgeSource(agentId, { + sourceType: "onedrive", }); + const intervalId = setInterval(() => { + getRemoteKnowledgeSources.mutate(); + onedriveSource = remoteKnowledgeSources.find( + (source) => source.sourceType === "onedrive" + ); + if (onedriveSource?.runID) { + clearInterval(intervalId); + } + }, 1000); + setTimeout(() => { + clearInterval(intervalId); + }, 10000); } onOpenChange(false); - setIsNotionModalOpen(true); - startPolling(); + onOneDriveModalOpen(true); + }; + + const onClickWebsite = async () => { + if (!websiteSource) { + await KnowledgeService.createRemoteKnowledgeSource(agentId, { + sourceType: "website", + }); + getRemoteKnowledgeSources.mutate(); + } + onOpenChange(false); + onWebsiteModalOpen(true); }; return ( @@ -74,21 +125,14 @@ export const AddFileModal = ({ className="flex flex-col gap-2" aria-describedby="add-files" > - - - - ); }; diff --git a/ui/admin/app/components/knowledge/AgentKnowledgePanel.tsx b/ui/admin/app/components/knowledge/AgentKnowledgePanel.tsx index d4dff4f1..83431189 100644 --- a/ui/admin/app/components/knowledge/AgentKnowledgePanel.tsx +++ b/ui/admin/app/components/knowledge/AgentKnowledgePanel.tsx @@ -1,14 +1,10 @@ -import { CheckIcon, Info, PlusIcon, XCircleIcon } from "lucide-react"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import useSWR from "swr"; import { IngestionStatus, KnowledgeFile, - KnowledgeIngestionStatus, getIngestionStatus, - getMessage, - getRemoteFileDisplayName, } from "~/lib/model/knowledge"; import { ApiRoutes } from "~/lib/routers/apiRoutes"; import { KnowledgeService } from "~/lib/service/api/knowledgeService"; @@ -16,21 +12,11 @@ import { cn, getErrorMessage } from "~/lib/utils"; import { Button } from "~/components/ui/button"; import { ScrollArea } from "~/components/ui/scroll-area"; -import { - Tooltip, - TooltipContent, - TooltipProvider, - TooltipTrigger, -} from "~/components/ui/tooltip"; -import { useAsync } from "~/hooks/useAsync"; import { useMultiAsync } from "~/hooks/useMultiAsync"; -import { LoadingSpinner } from "../ui/LoadingSpinner"; import { Input } from "../ui/input"; -import { AddFileModal } from "./AddFileModal"; import { FileChip } from "./FileItem"; -import RemoteFileItemChip from "./RemoteFileItemChip"; -import RemoteKnowledgeSourceStatus from "./RemoteKnowledgeSourceStatus"; +import FileSource from "./FileSource"; export function AgentKnowledgePanel({ agentId, @@ -42,7 +28,7 @@ export function AgentKnowledgePanel({ const [blockPolling, setBlockPolling] = useState(false); const [isAddFileModalOpen, setIsAddFileModalOpen] = useState(false); - const getKnowledge = useSWR( + const getKnowledgeFiles = useSWR( KnowledgeService.getKnowledgeForAgent.key(agentId), ({ agentId }) => KnowledgeService.getKnowledgeForAgent(agentId).then((items) => @@ -67,7 +53,7 @@ export function AgentKnowledgePanel({ refreshInterval: blockPolling ? undefined : 1000, } ); - const knowledge = getKnowledge.data || []; + const knowledge = getKnowledgeFiles.data || []; const getRemoteKnowledgeSources = useSWR( KnowledgeService.getRemoteKnowledgeSource.key(agentId), @@ -82,32 +68,6 @@ export function AgentKnowledgePanel({ [getRemoteKnowledgeSources.data] ); - const deleteKnowledge = useAsync(async (item: KnowledgeFile) => { - await KnowledgeService.deleteKnowledgeFromAgent(agentId, item.fileName); - - const remoteKnowledgeSource = remoteKnowledgeSources?.find( - (source) => source.sourceType === item.remoteKnowledgeSourceType - ); - if (remoteKnowledgeSource) { - await KnowledgeService.updateRemoteKnowledgeSource( - agentId, - remoteKnowledgeSource.id, - { - ...remoteKnowledgeSource, - exclude: [ - ...(remoteKnowledgeSource.exclude || []), - item.uploadID || "", - ], - } - ); - } - - // optomistic update without cache revalidation - getKnowledge.mutate((prev) => - prev?.filter((prevItem) => prevItem.fileName !== item.fileName) - ); - }); - const handleAddKnowledge = useCallback( async (_index: number, file: File) => { await new Promise((resolve) => setTimeout(resolve, 1000)); @@ -125,7 +85,7 @@ export function AgentKnowledgePanel({ fileDetails: {}, }; - getKnowledge.mutate( + getKnowledgeFiles.mutate( (prev) => { const existingItemIndex = prev?.findIndex( (item) => item.fileName === newItem.fileName @@ -142,8 +102,9 @@ export function AgentKnowledgePanel({ revalidate: false, } ); + setBlockPolling(false); }, - [agentId, getKnowledge] + [agentId, getKnowledgeFiles] ); // use multi async to handle uploading multiple files at once @@ -176,26 +137,19 @@ export function AgentKnowledgePanel({ ); useEffect(() => { - // we can assume that the knowledge is completely ingested if all items have a status of completed or skipped - // if that is the case, then we can block polling for updates - const hasCompleteIngestion = getKnowledge.data?.every((item) => { - const ingestionStatus = getIngestionStatus(item.ingestionStatus); - return ( - ingestionStatus === IngestionStatus.Finished || - ingestionStatus === IngestionStatus.Skipped + if (knowledge.length > 0) { + setBlockPolling( + remoteKnowledgeSources.every((source) => !source.runID) && + knowledge.every( + (item) => + item.ingestionStatus?.status === + IngestionStatus.Finished || + item.ingestionStatus?.status === + IngestionStatus.Skipped + ) ); - }); - - const hasIncompleteUpload = uploadKnowledge.states.some( - (state) => state.isLoading - ); - - setBlockPolling( - hasCompleteIngestion || - hasIncompleteUpload || - deleteKnowledge.isLoading - ); - }, [uploadKnowledge.states, deleteKnowledge.isLoading, getKnowledge.data]); + } + }, [remoteKnowledgeSources, knowledge]); useEffect(() => { remoteKnowledgeSources?.forEach((source) => { @@ -233,24 +187,6 @@ export function AgentKnowledgePanel({ }); }, [remoteKnowledgeSources]); - const handleRemoteKnowledgeSourceSync = useCallback(async () => { - try { - for (const source of remoteKnowledgeSources!) { - await KnowledgeService.resyncRemoteKnowledgeSource( - agentId, - source.id - ); - } - setTimeout(() => { - getRemoteKnowledgeSources.mutate(); - }, 1000); - } catch (error) { - console.error("Failed to resync remote knowledge source:", error); - } finally { - setBlockPolling(false); - } - }, [agentId, getRemoteKnowledgeSources, remoteKnowledgeSources]); - return (
@@ -275,173 +211,27 @@ export function AgentKnowledgePanel({
)} -
- {knowledge.map((item) => { - if (item.remoteKnowledgeSourceType) { - return ( - - deleteKnowledge.execute(item) - } - statusIcon={renderStatusIcon( - item.ingestionStatus - )} - isLoading={ - deleteKnowledge.isLoading && - deleteKnowledge.lastCallParams?.[0] - .fileName === item.fileName - } - remoteKnowledgeSourceType={ - item.remoteKnowledgeSourceType - } - /> - ); - } - return ( - deleteKnowledge.execute(item)} - statusIcon={renderStatusIcon( - item.ingestionStatus - )} - isLoading={ - deleteKnowledge.isLoading && - deleteKnowledge.lastCallParams?.[0] - .fileName === item.fileName - } - fileName={item.fileName} - /> - ); - })} -
+ setBlockPolling(false)} + /> -