Compare commits

...

10 Commits

Author SHA1 Message Date
pablonyx
a9aa5dae28 k 2025-03-02 10:54:17 -08:00
pablonyx
439764c5d1 fix build 2025-03-02 10:46:59 -08:00
pablonyx
cd618edce0 k 2025-03-02 10:44:43 -08:00
pablonyx
d187b7e9e0 update 2025-03-01 17:04:43 -08:00
pablonyx
0d64d70c59 align with comments 2025-03-01 16:55:34 -08:00
pablonyx
cc7b21afd2 update 2025-03-01 16:43:08 -08:00
pablonyx
3db8619d84 nit 2025-03-01 16:43:08 -08:00
pablonyx
e0b43dbca5 nit 2025-03-01 16:43:08 -08:00
pablonyx
1083d53bb5 quick nit 2025-03-01 16:43:07 -08:00
pablonyx
d3c6ff5e95 quick improvement 2025-03-01 16:42:44 -08:00
25 changed files with 408 additions and 94 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
@@ -774,6 +775,7 @@ def get_connector_indexing_status(
last_finished_status=(
latest_finished_attempt.status if latest_finished_attempt else None
),
perm_sync_completed=cc_pair.last_time_perm_sync is not None,
last_status=(
latest_index_attempt.status if latest_index_attempt else None
),
@@ -788,6 +790,7 @@ def get_connector_indexing_status(
if latest_index_attempt
else None
),
is_seeded=is_connector_seeded(connector),
)
)
@@ -1242,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),
@@ -1256,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

@@ -218,6 +218,8 @@ class CCPairFullInfo(BaseModel):
indexing: bool
creator: UUID | None
creator_email: str | None
last_successful_index_time: datetime | None
last_time_perm_sync: datetime | None
@classmethod
def from_models(
@@ -258,8 +260,10 @@ class CCPairFullInfo(BaseModel):
),
number_of_index_attempts=number_of_index_attempts,
last_index_attempt_status=last_indexing_status,
last_successful_index_time=cc_pair_model.last_successful_index_time,
latest_deletion_attempt=latest_deletion_attempt,
access_type=cc_pair_model.access_type,
last_time_perm_sync=cc_pair_model.last_time_perm_sync,
is_editable_for_current_user=is_editable_for_current_user,
deletion_failure_message=cc_pair_model.deletion_failure_message,
indexing=indexing,
@@ -311,9 +315,11 @@ class ConnectorIndexingStatus(ConnectorStatus):
last_finished_status: IndexingStatus | None
last_status: IndexingStatus | None
last_success: datetime | None
perm_sync_completed: bool
latest_index_attempt: IndexAttemptSnapshot | None
docs_indexed: int
in_progress: bool
is_seeded: bool
class ConnectorCredentialPairIdentifier(BaseModel):

View File

@@ -16,6 +16,7 @@ def load_settings() -> Settings:
kv_store = get_kv_store()
try:
stored_settings = kv_store.load(KV_SETTINGS_KEY)
settings = (
Settings.model_validate(stored_settings) if stored_settings else Settings()
)

View File

@@ -4,13 +4,10 @@ import { PopupSpec, usePopup } from "@/components/admin/connectors/Popup";
import { Button } from "@/components/ui/button";
import Text from "@/components/ui/text";
import { triggerIndexing } from "./lib";
import { mutate } from "swr";
import { buildCCPairInfoUrl, getTooltipMessage } from "./lib";
import { useState } from "react";
import { Modal } from "@/components/Modal";
import { Separator } from "@/components/ui/separator";
import { ConnectorCredentialPairStatus } from "./types";
import { CCPairStatus } from "@/components/Status";
import { getCCPairStatusMessage } from "@/lib/ccPair";
function ReIndexPopup({

View File

@@ -39,11 +39,14 @@ import EditPropertyModal from "@/components/modals/EditPropertyModal";
import * as Yup from "yup";
import { AlertCircle } from "lucide-react";
import { Badge } from "@/components/ui/badge";
import IndexAttemptErrorsModal from "./IndexAttemptErrorsModal";
import usePaginatedFetch from "@/hooks/usePaginatedFetch";
import { IndexAttemptSnapshot } from "@/lib/types";
import { Spinner } from "@/components/Spinner";
import { Callout } from "@/components/ui/callout";
import { FiAlertCircle } from "react-icons/fi";
import { Card, CardHeader } from "@/components/ui/card";
// synchronize these validations with the SQLAlchemy connector class until we have a
// centralized schema for both frontend and backend
@@ -378,10 +381,13 @@ function Main({ ccPairId }: { ccPairId: number }) {
</div>
)}
</div>
<CCPairStatus
status={ccPair.last_index_attempt_status || "not_started"}
ccPairStatus={ccPair.status}
/>
<div className="flex items-center gap-x-2">
<CCPairStatus
status={ccPair.last_index_attempt_status || "not_started"}
ccPairStatus={ccPair.status}
/>
</div>
<div className="text-sm mt-1">
Creator:{" "}
<b className="text-emphasis">{ccPair.creator_email ?? "Unknown"}</b>
@@ -400,6 +406,29 @@ function Main({ ccPairId }: { ccPairId: number }) {
</div>
)}
{(!ccPair.last_successful_index_time ||
(ccPair.access_type === "sync" && !ccPair.last_time_perm_sync)) && (
<div className="mt-4">
<Card className="max-w-2xl w-fit border-blue-500 bg-blue-50 dark:bg-blue-950/30 dark:border-blue-700 p-4">
<div className="flex items-start gap-2">
<FiAlertCircle className="h-4 w-4 text-blue-600 dark:text-blue-400 mt-1" />
<div>
<h4 className="font-semibold text-blue-700 dark:text-blue-400">
{!ccPair.last_successful_index_time
? "No Successful Indexing"
: "Permissions Sync In Progress"}
</h4>
<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 sync is still in progress for this connector. Some documents may not appear in search results until this process completes."}
</p>
</div>
</div>
</Card>
</div>
)}
{ccPair.deletion_failure_message &&
ccPair.status === ConnectorCredentialPairStatus.DELETING && (
<>

View File

@@ -40,9 +40,11 @@ export interface CCPairFullInfo {
access_type: AccessType;
is_editable_for_current_user: boolean;
deletion_failure_message: string | null;
last_time_perm_sync: string | null;
indexing: boolean;
creator: UUID | null;
creator_email: string | null;
last_successful_index_time: string | null;
}
export interface PaginatedIndexAttempts {

View File

@@ -26,6 +26,9 @@ import {
FiUnlock,
FiRefreshCw,
FiPauseCircle,
FiCheckCircle,
FiAlertCircle,
FiAlertTriangle,
} from "react-icons/fi";
import {
Tooltip,
@@ -77,9 +80,11 @@ function SummaryRow({
<TableCell>
<div className="text-sm text-neutral-500 dark:text-neutral-300">
Total Connectors
Ready Connectors
</div>
<div className="text-xl font-semibold">
{summary.complete}/{summary.count}
</div>
<div className="text-xl font-semibold">{summary.count}</div>
</TableCell>
<TableCell>
@@ -211,12 +216,54 @@ border border-border dark:border-neutral-700
}}
>
<TableCell className="">
<p className="lg:w-[200px] xl:w-[400px] inline-block ellipsis truncate">
{ccPairsIndexingStatus.name}
<p className="lg:w-[500px] flex gap-x-2 xl:w-[600px] inline-block ellipsis truncate">
<span>{ccPairsIndexingStatus.name}</span>
</p>
</TableCell>
<TableCell>
{timeAgo(ccPairsIndexingStatus?.last_success) || "-"}
<div className="flex items-center gap-x-1">
{timeAgo(ccPairsIndexingStatus?.last_success) ? (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<span>{timeAgo(ccPairsIndexingStatus?.last_success)}</span>
</TooltipTrigger>
<TooltipContent>
<p>Last successful indexing time</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
) : (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<span className="flex gap-x-1 items-center">
<FiAlertTriangle className="mr-1 text-yellow-600" />
</span>
</TooltipTrigger>
<TooltipContent>
<p>This connector has never been successfully indexed</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
)}
{!ccPairsIndexingStatus.perm_sync_completed &&
ccPairsIndexingStatus.access_type === "sync" && (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<span className="flex items-center">
<FiAlertTriangle className="mr-1 text-yellow-600" />
</span>
</TooltipTrigger>
<TooltipContent>
<p>Permissions sync is still in progress</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
)}
</div>
</TableCell>
<TableCell>{getActivityBadge()}</TableCell>
{isPaidEnterpriseFeaturesEnabled && (
@@ -226,12 +273,14 @@ border border-border dark:border-neutral-700
Public
</Badge>
) : ccPairsIndexingStatus.access_type === "sync" ? (
<Badge
variant={isEditable ? "auto-sync" : "default"}
icon={FiRefreshCw}
>
Auto-Sync
</Badge>
<div className="flex items-center gap-x-1">
<Badge
variant={isEditable ? "auto-sync" : "default"}
icon={FiRefreshCw}
>
<div className="flex items-center gap-x-1">Auto-Sync</div>
</Badge>
</div>
) : (
<Badge variant={isEditable ? "private" : "default"} icon={FiLock}>
Private
@@ -246,16 +295,6 @@ border border-border dark:border-neutral-700
errorMsg={ccPairsIndexingStatus?.latest_index_attempt?.error_msg}
/>
</TableCell>
<TableCell>
{isEditable && (
<CustomTooltip content="Manage Connector">
<FiSettings
className="cursor-pointer"
onClick={handleManageClick}
/>
</CustomTooltip>
)}
</TableCell>
</TableRow>
);
}
@@ -321,6 +360,17 @@ export function CCPairIndexingStatusTable({
const statuses = grouped[source];
summaries[source] = {
count: statuses.length,
not_ready: statuses.filter(
(status) =>
status.last_success === null ||
(status.connector.access_type === "sync" &&
!status.perm_sync_completed)
).length,
complete: statuses.filter(
(status) =>
status.last_success !== null &&
(status.access_type != "sync" || status.perm_sync_completed)
).length,
active: statuses.filter(
(status) =>
status.cc_pair_status === ConnectorCredentialPairStatus.ACTIVE
@@ -411,12 +461,14 @@ export function CCPairIndexingStatusTable({
credential_json: {},
admin_public: false,
},
perm_sync_completed: false,
access_type: "public",
docs_indexed: 1000,
last_success: "2023-07-01T12:00:00Z",
last_finished_status: "success",
latest_index_attempt: null,
groups: [], // Add this line
is_seeded: false,
}}
isEditable={false}
/>

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&apos;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&apos;ll know
it&apos;s complete when the &quot;Last Indexed&quot; 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

@@ -71,6 +71,8 @@ import remarkMath from "remark-math";
import rehypeKatex from "rehype-katex";
import "katex/dist/katex.min.css";
import { copyAll, handleCopy } from "./copyingUtils";
import Link from "next/link";
import { NoDocuments } from "./NoDocuments";
const TOOLS_WITH_CUSTOM_HANDLING = [
SEARCH_TOOL_NAME,
@@ -485,7 +487,7 @@ export const AIMessage = ({
/>
)}
{docs && docs.length > 0 && (
{docs && docs.length > 0 ? (
<div
className={`mobile:hidden ${
(query ||
@@ -518,6 +520,8 @@ export const AIMessage = ({
</div>
</div>
</div>
) : (
toolCall?.tool_result && <NoDocuments />
)}
{content || files ? (
@@ -746,8 +750,8 @@ function MessageSwitcher({
disableForStreaming
? () => null
: currentPage === 1
? undefined
: handlePrevious
? undefined
: handlePrevious
}
/>
</div>
@@ -774,8 +778,8 @@ function MessageSwitcher({
disableForStreaming
? () => null
: currentPage === totalPages
? undefined
: handleNext
? undefined
: handleNext
}
/>
</div>

View File

@@ -0,0 +1,20 @@
import Link from "next/link";
import { useUser } from "@/components/user/UserProvider";
export function NoDocuments() {
const { isAdmin } = useUser();
return (
<div className="text-xs py-2 text-neutral-800 dark:text-neutral-200 italic">
No documents found. This may be because the documents are still indexing
or syncing.{" "}
{isAdmin ? (
<Link href="/admin/indexing/status" className="underline">
Check indexing status
</Link>
) : (
<p>Check with your admin</p>
)}
</div>
);
}

View File

@@ -11,10 +11,13 @@ import {
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { useUser } from "@/components/user/UserProvider";
import { OnyxDocument } from "@/lib/search/interfaces";
import { ValidSources } from "@/lib/types";
import Link from "next/link";
import { useEffect, useRef, useState } from "react";
import { FiCheck, FiEdit2, FiSearch, FiX } from "react-icons/fi";
import { NoDocuments } from "./NoDocuments";
export function ShowHideDocsButton({
messageId,
@@ -63,6 +66,7 @@ export function SearchSummary({
const [isOverflowed, setIsOverflowed] = useState(false);
const searchingForRef = useRef<HTMLDivElement>(null);
const editQueryRef = useRef<HTMLInputElement>(null);
const { isAdmin } = useUser();
useEffect(() => {
const checkOverflow = () => {
@@ -120,7 +124,9 @@ export function SearchSummary({
<div className="desktop:hidden">
{" "}
{docs && (
{docs && docs.length === 0 ? (
finished && <NoDocuments />
) : (
<button
className="cursor-pointer mr-2 flex items-center gap-0.5"
onClick={() => toggleDocumentSelection()}

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

@@ -15,6 +15,7 @@ import {
Credential,
} from "@/lib/connectors/credentials";
import { Connector } from "@/lib/connectors/connectors";
import { ConfirmEntityModal } from "@/components/modals/ConfirmEntityModal";
const CredentialSelectionTable = ({
credentials,
@@ -188,35 +189,16 @@ export default function ModifyCredential({
return (
<>
{confirmDeletionCredential != null && (
<Modal
onOutsideClick={() => setConfirmDeletionCredential(null)}
className="max-w-sm"
>
<>
<p className="text-lg mb-2">
Are you sure you want to delete this credential? You cannot delete
credentials that are linked to live connectors.
</p>
<div className="mt-6 flex justify-between">
<button
className="rounded py-1.5 px-2 bg-background-800 text-text-200"
onClick={async () => {
await onDeleteCredential(confirmDeletionCredential);
setConfirmDeletionCredential(null);
}}
>
Yes
</button>
<button
onClick={() => setConfirmDeletionCredential(null)}
className="rounded py-1.5 px-2 bg-background-150 text-text-800"
>
{" "}
No
</button>
</div>
</>
</Modal>
<ConfirmEntityModal
entityType="Credential"
entityName={confirmDeletionCredential.name || "Unnamed Credential"}
onClose={() => setConfirmDeletionCredential(null)}
onSubmit={async () => {
await onDeleteCredential(confirmDeletionCredential);
setConfirmDeletionCredential(null);
}}
additionalDetails="You cannot delete credentials that are linked to live connectors."
/>
)}
<div className="mb-0">

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

@@ -17,6 +17,10 @@ const badgeVariants = cva(
"border-orange-200 bg-orange-50 text-orange-600 dark:border-orange-700 dark:bg-orange-900 dark:text-orange-50",
outline:
"border-neutral-200 bg-neutral-50 text-neutral-600 dark:border-neutral-700 dark:bg-neutral-900 dark:text-neutral-50",
fuchsia:
"border-fuchsia-200 bg-fuchsia-50 text-fuchsia-700 dark:border-fuchsia-700 dark:bg-fuchsia-900 dark:text-fuchsia-100",
purple:
"border-purple-200 bg-purple-50 text-purple-700 dark:border-purple-700 dark:bg-purple-900 dark:text-purple-100",
public:
@@ -39,6 +43,9 @@ const badgeVariants = cva(
"border-green-200 bg-emerald-50 text-green-600 dark:border-green-600 dark:bg-green-900 dark:text-green-50",
default:
"border-neutral-200 bg-neutral-50 text-neutral-600 dark:border-neutral-700 dark:bg-neutral-900 dark:text-neutral-50",
warning:
"border-orange-200 bg-orange-50 text-orange-600 dark:border-orange-700 dark:bg-orange-900 dark:text-orange-50",
secondary:
"border-neutral-200 bg-neutral-50 text-neutral-600 dark:border-neutral-700 dark:bg-neutral-900 dark:text-neutral-50",
destructive:

View File

@@ -47,7 +47,7 @@ const TooltipContent = React.forwardRef<
"bg-neutral-900 dark:bg-neutral-200 dark:text-neutral-900"
}
${width || "max-w-40"}
text-wrap
px-2 py-1.5 text-xs shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2`,
className
)}

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

@@ -158,9 +158,11 @@ export interface ConnectorIndexingStatus<
last_success: string | null;
last_status: ValidStatuses | null;
last_finished_status: ValidStatuses | null;
perm_sync_completed: boolean;
cc_pair_status: ConnectorCredentialPairStatus;
latest_index_attempt: IndexAttemptSnapshot | null;
docs_indexed: number;
is_seeded: boolean;
}
export interface OAuthPrepareAuthorizationResponse {
@@ -202,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 = {
@@ -209,6 +213,8 @@ export type ConnectorSummary = {
active: number;
public: number;
totalDocsIndexed: number;
not_ready: number;
complete: number;
errors: number; // New field for error count
};