This commit is contained in:
pablonyx 2025-03-02 10:44:43 -08:00
parent d187b7e9e0
commit cd618edce0
15 changed files with 236 additions and 31 deletions

View File

@ -92,6 +92,7 @@ from onyx.db.enums import IndexingMode
from onyx.db.index_attempt import get_index_attempts_for_cc_pair
from onyx.db.index_attempt import get_latest_index_attempts_by_status
from onyx.db.index_attempt import get_latest_index_attempts_parallel
from onyx.db.models import Connector
from onyx.db.models import ConnectorCredentialPair
from onyx.db.models import IndexAttempt
from onyx.db.models import IndexingStatus
@ -789,6 +790,7 @@ def get_connector_indexing_status(
if latest_index_attempt
else None
),
is_seeded=is_connector_seeded(connector),
)
)
@ -1243,9 +1245,18 @@ def get_connector_by_id(
class BasicCCPairInfo(BaseModel):
has_successful_run: bool
has_successful_sync_if_needs_sync: bool
seeded: bool
source: DocumentSource
def is_connector_seeded(connector: Connector) -> bool:
return (
connector.connector_specific_config.get("base_url")
== "https://docs.onyx.app/more/use_cases"
)
@router.get("/connector-status")
def get_basic_connector_indexing_status(
user: User = Depends(current_chat_accesssible_user),
@ -1257,10 +1268,16 @@ def get_basic_connector_indexing_status(
get_editable=False,
user=user,
)
return [
BasicCCPairInfo(
has_successful_run=cc_pair.last_successful_index_time is not None,
has_successful_sync_if_needs_sync=(
cc_pair.last_time_perm_sync is not None
or cc_pair.access_type != AccessType.SYNC
),
source=cc_pair.connector.source,
seeded=is_connector_seeded(cc_pair.connector),
)
for cc_pair in cc_pairs
if cc_pair.connector.source != DocumentSource.INGESTION_API

View File

@ -319,6 +319,7 @@ class ConnectorIndexingStatus(ConnectorStatus):
latest_index_attempt: IndexAttemptSnapshot | None
docs_indexed: int
in_progress: bool
is_seeded: bool
class ConnectorCredentialPairIdentifier(BaseModel):

View File

@ -421,7 +421,7 @@ function Main({ ccPairId }: { ccPairId: number }) {
<p className="text-sm text-blue-600 dark:text-blue-300 mt-1">
{!ccPair.last_successful_index_time
? "This connector has never been successfully indexed. Documents from this connector will not appear in search results until indexing completes successfully."
: "Permissions synchronization is still in progress for this connector. Some documents may not appear in search results until this process completes."}
: "Permissions sync is still in progress for this connector. Some documents may not appear in search results until this process completes."}
</p>
</div>
</div>

View File

@ -0,0 +1,71 @@
"use client";
import { useState } from "react";
import { useRouter } from "next/navigation";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogDescription,
DialogFooter,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { CheckmarkIcon } from "@/components/icons/icons";
export function ConnectorCreatedSuccessModal() {
const [open, setOpen] = useState(true);
const router = useRouter();
// Close the modal and update the URL to remove the query param
const handleClose = () => {
setOpen(false);
router.replace("/admin/indexing/status");
};
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogContent className="sm:max-w-md p-6">
<DialogHeader className="flex flex-col items-center text-center gap-4">
<div className="flex items-center justify-center w-16 h-16 rounded-full bg-green-100 dark:bg-green-950 transition-all duration-200 animate-in fade-in">
<CheckmarkIcon
size={32}
className="text-green-600 dark:text-green-400"
/>
</div>
<div className="space-y-2">
<DialogTitle className="text-2xl font-bold">
Congratulations!
</DialogTitle>
<DialogDescription className="text-lg">
You've successfully created your first connector.
</DialogDescription>
</div>
</DialogHeader>
<div className="bg-neutral-100 dark:bg-neutral-900 p-5 rounded-lg my-4 border border-neutral-200 dark:border-neutral-700 shadow-sm dark:shadow-md dark:shadow-black/10">
<h3 className="font-semibold text-lg mb-2 flex items-center">
<div className="w-2 h-2 rounded-full bg-blue-500 dark:bg-blue-400 mr-2 animate-pulse"></div>
Syncing in progress
</h3>
<p className="text-neutral-600 dark:text-neutral-300 leading-relaxed">
It will take some time to sync your documents. You'll know it's
complete when the "Last Indexed" field is filled in on the
Connectors page.
</p>
</div>
<DialogFooter className="flex justify-center sm:justify-center pt-2">
<Button
onClick={handleClose}
variant="default"
size="lg"
className="font-medium transition-all duration-200 hover:shadow-md dark:hover:bg-primary/90 dark:hover:shadow-lg dark:hover:shadow-primary/20"
>
Understood
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}

View File

@ -7,10 +7,19 @@ import { AdminPageTitle } from "@/components/admin/Title";
import Link from "next/link";
import Text from "@/components/ui/text";
import { useConnectorCredentialIndexingStatus } from "@/lib/hooks";
import { usePopupFromQuery } from "@/components/popup/PopupFromQuery";
import {
PopupMessages,
usePopupFromQuery,
} from "@/components/popup/PopupFromQuery";
import { Button } from "@/components/ui/button";
import { useSearchParams } from "next/navigation";
import { ConnectorCreatedSuccessModal } from "./ConnectorCreatedSuccessModal";
import { useMemo } from "react";
function Main() {
// Constants
const ADD_CONNECTOR_PATH = "/admin/add-connector";
const ConnectorStatusList = () => {
const {
data: indexAttemptData,
isLoading: indexAttemptIsLoading,
@ -23,10 +32,12 @@ function Main() {
error: editableIndexAttemptError,
} = useConnectorCredentialIndexingStatus(undefined, true);
// Handle loading state
if (indexAttemptIsLoading || editableIndexAttemptIsLoading) {
return <LoadingAnimation text="" />;
}
// Handle error states
if (
indexAttemptError ||
!indexAttemptData ||
@ -42,11 +53,12 @@ function Main() {
);
}
// Show empty state when no connectors
if (indexAttemptData.length === 0) {
return (
<Text>
It looks like you don&apos;t have any connectors setup yet. Visit the{" "}
<Link className="text-link" href="/admin/add-connector">
<Link className="text-link" href={ADD_CONNECTOR_PATH}>
Add Connector
</Link>{" "}
page to get started!
@ -54,36 +66,59 @@ function Main() {
);
}
// sort by source name
indexAttemptData.sort((a, b) => {
if (a.connector.source < b.connector.source) {
return -1;
} else if (a.connector.source > b.connector.source) {
return 1;
} else {
return 0;
}
});
// Sort data by source name
const sortedIndexAttemptData = [...indexAttemptData].sort((a, b) =>
a.connector.source.localeCompare(b.connector.source)
);
return (
<CCPairIndexingStatusTable
ccPairsIndexingStatuses={indexAttemptData}
editableCcPairsIndexingStatuses={editableIndexAttemptData}
/>
<>
<CCPairIndexingStatusTable
ccPairsIndexingStatuses={sortedIndexAttemptData}
editableCcPairsIndexingStatuses={editableIndexAttemptData}
/>
</>
);
}
};
export default function Status() {
const { popup } = usePopupFromQuery({
"connector-created": {
message: "Connector created successfully",
type: "success",
},
const searchParams = useSearchParams();
const justCreatedConnector =
searchParams.get("message") === "connector-created";
// Use data to determine if we should show the popup or modal
const { data: indexAttemptData, isLoading: indexAttemptIsLoading } =
useConnectorCredentialIndexingStatus();
// Only show popup if we're not showing the success modal and there's exactly one seeded connector
const showSuccessModal = useMemo(() => {
return (
!indexAttemptIsLoading &&
indexAttemptData &&
justCreatedConnector &&
indexAttemptData.filter((attempt) => attempt.is_seeded).length === 1
);
}, [indexAttemptIsLoading, indexAttemptData]);
// Create popup messages based on query parameters
const popupMessages: PopupMessages = {
"connector-deleted": {
message: "Connector deleted successfully",
type: "success",
},
});
};
// Conditionally add connector-created message
if (!showSuccessModal) {
Object.assign(popupMessages, {
"connector-created": {
message: "Connector created successfully",
type: "success",
},
});
}
const { popup } = usePopupFromQuery(popupMessages);
return (
<div className="mx-auto container">
@ -92,13 +127,15 @@ export default function Status() {
icon={<NotebookIcon size={32} />}
title="Existing Connectors"
farRightElement={
<Link href="/admin/add-connector">
<Link href={ADD_CONNECTOR_PATH}>
<Button variant="success-reverse">Add Connector</Button>
</Link>
}
/>
<Main />
{showSuccessModal && <ConnectorCreatedSuccessModal />}
<ConnectorStatusList />
</div>
);
}

View File

@ -73,7 +73,7 @@ import { DocumentResults } from "./documentSidebar/DocumentResults";
import { OnyxInitializingLoader } from "@/components/OnyxInitializingLoader";
import { FeedbackModal } from "./modal/FeedbackModal";
import { ShareChatSessionModal } from "./modal/ShareChatSessionModal";
import { FiArrowDown } from "react-icons/fi";
import { FiArrowDown, FiExternalLink } from "react-icons/fi";
import { ChatIntro } from "./ChatIntro";
import { AIMessage, HumanMessage } from "./message/Messages";
import { StarterMessages } from "../../components/assistants/StarterMessage";
@ -138,6 +138,10 @@ import { useSidebarShortcut } from "@/lib/browserUtilities";
import { ConfirmEntityModal } from "@/components/modals/ConfirmEntityModal";
import { ChatSearchModal } from "./chat_search/ChatSearchModal";
import { ErrorBanner } from "./message/Resubmit";
import { ExternalLinkIcon } from "lucide-react";
import { XIcon } from "@/components/icons/icons";
import { HIDE_NO_SOURCES_MESSAGE_COOKIE_NAME } from "@/lib/constants";
import Link from "next/link";
const TEMP_USER_MESSAGE_ID = -1;
const TEMP_ASSISTANT_MESSAGE_ID = -2;
@ -166,9 +170,19 @@ export function ChatPage({
folders,
shouldShowWelcomeModal,
refreshChatSessions,
showNoSourcesMessage: initialShowNoSourcesMessage,
proSearchToggled,
} = useChatContext();
const [showNoSourcesMessage, setShowNoSourcesMessage] = useState(
initialShowNoSourcesMessage
);
const dismissNoSourcesMessage = () => {
Cookies.set(HIDE_NO_SOURCES_MESSAGE_COOKIE_NAME, "true");
setShowNoSourcesMessage(false);
};
const defaultAssistantIdRaw = searchParams.get(SEARCH_PARAM_NAMES.PERSONA_ID);
const defaultAssistantId = defaultAssistantIdRaw
? parseInt(defaultAssistantIdRaw)
@ -201,6 +215,7 @@ export function ChatPage({
// available in server-side components
const settings = useContext(SettingsContext);
const enterpriseSettings = settings?.enterpriseSettings;
const [ready, setReady] = useState(false);
const [documentSidebarVisible, setDocumentSidebarVisible] = useState(false);
const [proSearchEnabled, setProSearchEnabled] = useState(proSearchToggled);
@ -3118,6 +3133,39 @@ export function ChatPage({
</div>
)}
{showNoSourcesMessage && (
<div className="pointer-events-auto flex items-center justify-center w-full mb-2">
<div className="bg-transparent border border-neutral-200 dark:border-neutral-700 rounded-lg p-3 flex items-center justify-between w-fit shadow-sm dark:shadow-md dark:shadow-black/20">
<div className="flex gap-x-2 items-center">
<p className="text-neutral-500 dark:text-neutral-300 text-sm font-medium">
No sources have been completed. Answers will
only include the seeded doc
</p>
<Link
href="/admin/indexing/status"
className="underline flex items-center inline-flex"
>
<ExternalLinkIcon
className="text-neutral-500 hover:text-neutral-800 dark:text-neutral-400 dark:hover:text-neutral-200"
size={16}
/>
</Link>
</div>
<div className="w-[1px] ml-2 -my-3 h-auto self-stretch bg-neutral-200 dark:bg-neutral-700"></div>
<button
className="ml-2 text-neutral-600 dark:text-neutral-400 hover:text-neutral-800 dark:hover:text-neutral-200 transition-colors"
aria-label="Dismiss"
onClick={dismissNoSourcesMessage}
>
<XIcon
size={16}
className="text-neutral-500 cursor-pointer hover:text-neutral-800 dark:text-neutral-400 dark:hover:text-neutral-200"
/>
</button>
</div>
</div>
)}
<div className="pointer-events-auto w-[95%] mx-auto relative mb-8">
<ChatInputBar
proSearchEnabled={proSearchEnabled}
@ -3218,7 +3266,6 @@ export function ChatPage({
</div>
<FixedLogo backgroundToggled={sidebarVisible || showHistorySidebar} />
</div>
{/* Right Sidebar - DocumentSidebar */}
</div>
</>
);

View File

@ -37,6 +37,7 @@ export default async function Layout({
ccPairs,
inputPrompts,
proSearchToggled,
showNoSourcesMessage,
} = data;
return (
@ -59,6 +60,7 @@ export default async function Layout({
openedFolders,
shouldShowWelcomeModal,
defaultAssistantId,
showNoSourcesMessage,
}}
>
{children}

View File

@ -36,6 +36,7 @@ export default async function GalleryPage(props: {
defaultAssistantId,
inputPrompts,
proSearchToggled,
showNoSourcesMessage,
} = data;
return (
@ -56,6 +57,7 @@ export default async function GalleryPage(props: {
openedFolders,
shouldShowWelcomeModal,
defaultAssistantId,
showNoSourcesMessage,
}}
>
{shouldShowWelcomeModal && (

View File

@ -62,6 +62,7 @@ export async function Layout({ children }: { children: React.ReactNode }) {
shouldShowWelcomeModal,
ccPairs,
inputPrompts,
showNoSourcesMessage,
proSearchToggled,
} = data;
@ -83,6 +84,7 @@ export async function Layout({ children }: { children: React.ReactNode }) {
openedFolders,
shouldShowWelcomeModal,
defaultAssistantId,
showNoSourcesMessage,
}}
>
<ClientLayout

View File

@ -35,6 +35,7 @@ interface ChatContextProps {
refreshInputPrompts: () => Promise<void>;
inputPrompts: InputPrompt[];
proSearchToggled: boolean;
showNoSourcesMessage: boolean;
}
const ChatContext = createContext<ChatContextProps | undefined>(undefined);

View File

@ -4,7 +4,7 @@ import { usePopup } from "../admin/connectors/Popup";
import { PopupSpec } from "../admin/connectors/Popup";
import { useRouter } from "next/navigation";
interface PopupMessages {
export interface PopupMessages {
[key: string]: PopupSpec;
}

View File

@ -26,6 +26,7 @@ import {
} from "@/components/resizable/constants";
import { hasCompletedWelcomeFlowSS } from "@/components/initialSetup/welcome/WelcomeModalWrapper";
import {
HIDE_NO_SOURCES_MESSAGE_COOKIE_NAME,
NEXT_PUBLIC_DEFAULT_SIDEBAR_OPEN,
NEXT_PUBLIC_ENABLE_CHROME_EXTENSION,
} from "../constants";
@ -47,6 +48,7 @@ interface FetchChatDataResult {
shouldShowWelcomeModal: boolean;
inputPrompts: InputPrompt[];
proSearchToggled: boolean;
showNoSourcesMessage: boolean;
}
export async function fetchChatData(searchParams: {
@ -130,6 +132,21 @@ export async function fetchChatData(searchParams: {
} else {
console.log(`Failed to fetch connectors - ${ccPairsResponse?.status}`);
}
const hideNoSourcesMessage =
requestCookies
.get(HIDE_NO_SOURCES_MESSAGE_COOKIE_NAME)
?.value.toLowerCase() === "true";
const showNoSourcesMessage = !(
ccPairs.some(
(ccPair) =>
!ccPair.seeded &&
ccPair.has_successful_run &&
ccPair.has_successful_sync_if_needs_sync
) || hideNoSourcesMessage
);
const availableSources: ValidSources[] = [];
ccPairs.forEach((ccPair) => {
if (!availableSources.includes(ccPair.source)) {
@ -234,5 +251,6 @@ export async function fetchChatData(searchParams: {
shouldShowWelcomeModal,
inputPrompts,
proSearchToggled,
showNoSourcesMessage,
};
}

View File

@ -45,7 +45,9 @@ export async function updateConnectorCredentialPairName(
newName: string
): Promise<Response> {
return fetch(
`/api/manage/admin/cc-pair/${ccPairId}/name?new_name=${encodeURIComponent(newName)}`,
`/api/manage/admin/cc-pair/${ccPairId}/name?new_name=${encodeURIComponent(
newName
)}`,
{
method: "PUT",
headers: {

View File

@ -94,3 +94,5 @@ export const NEXT_PUBLIC_INCLUDE_ERROR_POPUP_SUPPORT_LINK =
export const NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY =
process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY;
export const HIDE_NO_SOURCES_MESSAGE_COOKIE_NAME = "hide_no_sources_message";

View File

@ -162,6 +162,7 @@ export interface ConnectorIndexingStatus<
cc_pair_status: ConnectorCredentialPairStatus;
latest_index_attempt: IndexAttemptSnapshot | null;
docs_indexed: number;
is_seeded: boolean;
}
export interface OAuthPrepareAuthorizationResponse {
@ -203,6 +204,8 @@ export interface OAuthConfluenceFinalizeResponse {
export interface CCPairBasicInfo {
has_successful_run: boolean;
source: ValidSources;
seeded: boolean;
has_successful_sync_if_needs_sync: boolean;
}
export type ConnectorSummary = {