mirror of
https://github.com/danswer-ai/danswer.git
synced 2025-03-17 21:32:36 +01:00
k
This commit is contained in:
parent
d187b7e9e0
commit
cd618edce0
@ -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
|
||||
|
@ -319,6 +319,7 @@ class ConnectorIndexingStatus(ConnectorStatus):
|
||||
latest_index_attempt: IndexAttemptSnapshot | None
|
||||
docs_indexed: int
|
||||
in_progress: bool
|
||||
is_seeded: bool
|
||||
|
||||
|
||||
class ConnectorCredentialPairIdentifier(BaseModel):
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
@ -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'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>
|
||||
);
|
||||
}
|
||||
|
@ -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>
|
||||
</>
|
||||
);
|
||||
|
@ -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}
|
||||
|
@ -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 && (
|
||||
|
@ -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
|
||||
|
@ -35,6 +35,7 @@ interface ChatContextProps {
|
||||
refreshInputPrompts: () => Promise<void>;
|
||||
inputPrompts: InputPrompt[];
|
||||
proSearchToggled: boolean;
|
||||
showNoSourcesMessage: boolean;
|
||||
}
|
||||
|
||||
const ChatContext = createContext<ChatContextProps | undefined>(undefined);
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
@ -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: {
|
||||
|
@ -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";
|
||||
|
@ -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 = {
|
||||
|
Loading…
x
Reference in New Issue
Block a user