FE improvements to make initial setup more intuitive

This commit is contained in:
Weves
2023-12-02 15:12:34 -08:00
committed by Chris Weaver
parent 02095e9281
commit ac35719038
27 changed files with 566 additions and 613 deletions

View File

@@ -0,0 +1,90 @@
import { SourceIcon } from "@/components/SourceIcon";
import { AdminPageTitle } from "@/components/admin/Title";
import { ConnectorIcon } from "@/components/icons/icons";
import { SourceCategory, SourceMetadata } from "@/lib/search/interfaces";
import { listSourceMetadata } from "@/lib/sources";
import { Title, Text } from "@tremor/react";
import Link from "next/link";
function SourceTile({ sourceMetadata }: { sourceMetadata: SourceMetadata }) {
return (
<Link
className={`flex
flex-col
items-center
justify-center
bg-dark-tremor-background-muted
p-4
rounded-lg
w-40
cursor-pointer
shadow-md
hover:bg-gray-800
`}
href={sourceMetadata.adminUrl}
>
<SourceIcon sourceType={sourceMetadata.internalName} iconSize={24} />
<span className="font-medium text-sm text-gray-300 mt-2">
{sourceMetadata.displayName}
</span>
</Link>
);
}
export default function Page() {
const sources = listSourceMetadata();
const importedKnowledgeSources = sources.filter(
(source) => source.category === SourceCategory.ImportedKnowledge
);
const appConnectionSources = sources.filter(
(source) => source.category === SourceCategory.AppConnection
);
return (
<div className="mx-auto container dark">
<AdminPageTitle
icon={<ConnectorIcon size={32} />}
title="Add Connector"
/>
<div className="text-gray-300 text-sm">
Connect Danswer to your organization&apos;s knowledge sources.
We&apos;ll automatically sync your data into Danswer, so you can find
exactly what you&apos;re looking for in one place.
</div>
<div className="flex mt-8">
<Title>Import Knowledge</Title>
</div>
<div className="text-gray-300 text-sm">
Connect to pieces of knowledge that live outside your apps. Upload
files, scrape websites, or connect to your organization&apos;s Google
Site.
</div>
<div className="flex flex-wrap gap-4 p-4">
{importedKnowledgeSources.map((source) => {
return (
<SourceTile key={source.internalName} sourceMetadata={source} />
);
})}
</div>
<div className="flex mt-8">
<Title>Setup Auto-Syncing from Apps</Title>
</div>
<div className="text-gray-300 text-sm">
Setup auto-syncing from your organization&apos;s most used apps and
services. Unless otherwise specified during the connector setup, we will
pull in the latest updates from the source every 10 minutes.
</div>
<div className="flex flex-wrap gap-4 p-4">
{appConnectionSources.map((source) => {
return (
<SourceTile key={source.internalName} sourceMetadata={source} />
);
})}
</div>
</div>
);
}

View File

@@ -13,6 +13,7 @@ import { SlackBotCreationForm } from "./SlackBotConfigCreationForm";
import { deleteSlackBotConfig } from "./lib"; import { deleteSlackBotConfig } from "./lib";
import { SlackBotTokensForm } from "./SlackBotTokensForm"; import { SlackBotTokensForm } from "./SlackBotTokensForm";
import { useDocumentSets } from "../documents/sets/hooks"; import { useDocumentSets } from "../documents/sets/hooks";
import { AdminPageTitle } from "@/components/admin/Title";
const numToDisplay = 50; const numToDisplay = 50;
@@ -315,10 +316,10 @@ const Main = () => {
const Page = () => { const Page = () => {
return ( return (
<div> <div>
<div className="border-solid border-gray-600 border-b pb-2 mb-4 flex"> <AdminPageTitle
<CPUIcon size={32} /> icon={<CPUIcon size={32} />}
<h1 className="text-3xl font-bold pl-2">Slack Bot Configuration</h1> title="Slack Bot Configuration"
</div> />
<Main /> <Main />
</div> </div>

View File

@@ -4,7 +4,6 @@ import { adminSearch } from "./lib";
import { MagnifyingGlass } from "@phosphor-icons/react"; import { MagnifyingGlass } from "@phosphor-icons/react";
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { DanswerDocument } from "@/lib/search/interfaces"; import { DanswerDocument } from "@/lib/search/interfaces";
import { getSourceIcon } from "@/components/source";
import { buildDocumentSummaryDisplay } from "@/components/search/DocumentDisplay"; import { buildDocumentSummaryDisplay } from "@/components/search/DocumentDisplay";
import { CustomCheckbox } from "@/components/CustomCheckbox"; import { CustomCheckbox } from "@/components/CustomCheckbox";
import { updateHiddenStatus } from "../lib"; import { updateHiddenStatus } from "../lib";
@@ -17,6 +16,7 @@ import { useFilters } from "@/lib/hooks";
import { buildFilters } from "@/lib/search/utils"; import { buildFilters } from "@/lib/search/utils";
import { DocumentUpdatedAtBadge } from "@/components/search/DocumentUpdatedAtBadge"; import { DocumentUpdatedAtBadge } from "@/components/search/DocumentUpdatedAtBadge";
import { Connector, DocumentSet } from "@/lib/types"; import { Connector, DocumentSet } from "@/lib/types";
import { SourceIcon } from "@/components/SourceIcon";
const DocumentDisplay = ({ const DocumentDisplay = ({
document, document,
@@ -42,7 +42,7 @@ const DocumentDisplay = ({
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
> >
{getSourceIcon(document.source_type, 22)} <SourceIcon sourceType={document.source_type} iconSize={22} />
<p className="truncate break-all ml-2 my-auto text-base"> <p className="truncate break-all ml-2 my-auto text-base">
{document.semantic_identifier || document.document_id} {document.semantic_identifier || document.document_id}
</p> </p>
@@ -111,7 +111,6 @@ export function Explorer({
connectors: Connector<any>[]; connectors: Connector<any>[];
documentSets: DocumentSet[]; documentSets: DocumentSet[];
}) { }) {
console.log(connectors);
const router = useRouter(); const router = useRouter();
const { popup, setPopup } = usePopup(); const { popup, setPopup } = usePopup();

View File

@@ -5,6 +5,7 @@ import { ThumbsUpIcon } from "@/components/icons/icons";
import { useMostReactedToDocuments } from "@/lib/hooks"; import { useMostReactedToDocuments } from "@/lib/hooks";
import { DocumentFeedbackTable } from "./DocumentFeedbackTable"; import { DocumentFeedbackTable } from "./DocumentFeedbackTable";
import { numPages, numToDisplay } from "./constants"; import { numPages, numToDisplay } from "./constants";
import { AdminPageTitle } from "@/components/admin/Title";
const Main = () => { const Main = () => {
const { const {
@@ -61,10 +62,10 @@ const Main = () => {
const Page = () => { const Page = () => {
return ( return (
<div> <div>
<div className="border-solid border-gray-600 border-b pb-2 mb-4 flex"> <AdminPageTitle
<ThumbsUpIcon size={32} /> icon={<ThumbsUpIcon size={32} />}
<h1 className="text-3xl font-bold pl-2">Document Feedback</h1> title="Document Feedback"
</div> />
<Main /> <Main />
</div> </div>

View File

@@ -18,6 +18,8 @@ import { DocumentSetCreationForm } from "./DocumentSetCreationForm";
import { ConnectorTitle } from "@/components/admin/connectors/ConnectorTitle"; import { ConnectorTitle } from "@/components/admin/connectors/ConnectorTitle";
import { deleteDocumentSet } from "./lib"; import { deleteDocumentSet } from "./lib";
import { PopupSpec, usePopup } from "@/components/admin/connectors/Popup"; import { PopupSpec, usePopup } from "@/components/admin/connectors/Popup";
import { AdminPageTitle } from "@/components/admin/Title";
import { Text } from "@tremor/react";
const numToDisplay = 50; const numToDisplay = 50;
@@ -249,15 +251,15 @@ const Main = () => {
return ( return (
<div className="mb-8"> <div className="mb-8">
{popup} {popup}
<div className="text-sm mb-3"> <Text className="mb-3 text-gray-300">
<b>Document Sets</b> allow you to group logically connected documents <b>Document Sets</b> allow you to group logically connected documents
into a single bundle. These can then be used as filter when performing into a single bundle. These can then be used as filter when performing
searches in the web UI or attached to slack bots to limit the amount of searches in the web UI or attached to slack bots to limit the amount of
information the bot searches over when answering in a specific channel information the bot searches over when answering in a specific channel
or with a certain command. or with a certain command.
</div> </Text>
<div className="mb-2"></div> <div className="mb-6"></div>
<div className="flex mb-3"> <div className="flex mb-3">
<Button className="ml-2 my-auto" onClick={() => setIsOpen(true)}> <Button className="ml-2 my-auto" onClick={() => setIsOpen(true)}>
@@ -265,12 +267,14 @@ const Main = () => {
</Button> </Button>
</div> </div>
<DocumentSetTable {documentSets.length > 0 && (
documentSets={documentSets} <DocumentSetTable
ccPairs={ccPairs} documentSets={documentSets}
refresh={refreshDocumentSets} ccPairs={ccPairs}
setPopup={setPopup} refresh={refreshDocumentSets}
/> setPopup={setPopup}
/>
)}
{isOpen && ( {isOpen && (
<DocumentSetCreationForm <DocumentSetCreationForm
@@ -289,10 +293,7 @@ const Main = () => {
const Page = () => { const Page = () => {
return ( return (
<div> <div>
<div className="border-solid border-gray-600 border-b pb-2 mb-4 flex"> <AdminPageTitle icon={<BookmarkIcon size={32} />} title="Document Sets" />
<BookmarkIcon size={32} />
<h1 className="text-3xl font-bold pl-2">Document Sets</h1>
</div>
<Main /> <Main />
</div> </div>

View File

@@ -17,6 +17,7 @@ import { ConnectorTitle } from "@/components/admin/connectors/ConnectorTitle";
import { getDocsProcessedPerMinute } from "@/lib/indexAttempt"; import { getDocsProcessedPerMinute } from "@/lib/indexAttempt";
import Link from "next/link"; import Link from "next/link";
import { isCurrentlyDeleting } from "@/lib/documentDeletion"; import { isCurrentlyDeleting } from "@/lib/documentDeletion";
import { FiEdit, FiMaximize, FiMaximize2 } from "react-icons/fi";
const NUM_IN_PAGE = 20; const NUM_IN_PAGE = 20;
@@ -98,12 +99,17 @@ export function CCPairIndexingStatusTable({
"hover:bg-gradient-to-r hover:from-gray-800 hover:to-indigo-950 cursor-pointer relative" "hover:bg-gradient-to-r hover:from-gray-800 hover:to-indigo-950 cursor-pointer relative"
} }
> >
<TableCell className="whitespace-normal break-all"> <TableCell>
<ConnectorTitle <div className="flex my-auto">
connector={ccPairsIndexingStatus.connector} <FiEdit className="mr-4 my-auto text-blue-300" />
ccPairId={ccPairsIndexingStatus.cc_pair_id} <div className="whitespace-normal break-all max-w-3xl">
ccPairName={ccPairsIndexingStatus.name} <ConnectorTitle
/> connector={ccPairsIndexingStatus.connector}
ccPairId={ccPairsIndexingStatus.cc_pair_id}
ccPairName={ccPairsIndexingStatus.name}
/>
</div>
</div>
</TableCell> </TableCell>
<TableCell> <TableCell>
<CCPairIndexingStatusDisplay <CCPairIndexingStatusDisplay

View File

@@ -5,10 +5,11 @@ import useSWR from "swr";
import { LoadingAnimation } from "@/components/Loading"; import { LoadingAnimation } from "@/components/Loading";
import { NotebookIcon } from "@/components/icons/icons"; import { NotebookIcon } from "@/components/icons/icons";
import { fetcher } from "@/lib/fetcher"; import { fetcher } from "@/lib/fetcher";
import { HealthCheckBanner } from "@/components/health/healthcheck";
import { ConnectorIndexingStatus } from "@/lib/types"; import { ConnectorIndexingStatus } from "@/lib/types";
import { CCPairIndexingStatusTable } from "./CCPairIndexingStatusTable"; import { CCPairIndexingStatusTable } from "./CCPairIndexingStatusTable";
import { Divider } from "@tremor/react"; import { AdminPageTitle } from "@/components/admin/Title";
import Link from "next/link";
import { Button } from "@tremor/react";
function Main() { function Main() {
const { const {
@@ -29,6 +30,18 @@ function Main() {
return <div className="text-red-600">Error loading indexing history.</div>; return <div className="text-red-600">Error loading indexing history.</div>;
} }
if (indexAttemptData.length === 0) {
return (
<div className="text-gray-300 text-sm">
It looks like you don&apos;t have any connectors setup yet. Visit the{" "}
<Link className="text-blue-500" href="/admin/add-connector">
Add Connector
</Link>{" "}
page to get started!
</div>
);
}
// sort by source name // sort by source name
indexAttemptData.sort((a, b) => { indexAttemptData.sort((a, b) => {
if (a.connector.source < b.connector.source) { if (a.connector.source < b.connector.source) {
@@ -48,13 +61,17 @@ function Main() {
export default function Status() { export default function Status() {
return ( return (
<div className="mx-auto container dark"> <div className="mx-auto container dark">
<div className="mb-4"> <AdminPageTitle
<HealthCheckBanner /> icon={<NotebookIcon size={32} />}
</div> title="Existing Connectors"
<h1 className="text-3xl font-bold flex gap-x-2 mb-2"> farRightElement={
<NotebookIcon size={32} /> Indexing Status <Link href="/admin/add-connector">
</h1> <Button variant="secondary" size="xs">
<Divider /> Add Connector
</Button>
</Link>
}
/>
<Main /> <Main />
</div> </div>
); );

View File

@@ -1,6 +1,7 @@
"use client"; "use client";
import { LoadingAnimation } from "@/components/Loading"; import { LoadingAnimation } from "@/components/Loading";
import { AdminPageTitle } from "@/components/admin/Title";
import { KeyIcon, TrashIcon } from "@/components/icons/icons"; import { KeyIcon, TrashIcon } from "@/components/icons/icons";
import { ApiKeyForm } from "@/components/openai/ApiKeyForm"; import { ApiKeyForm } from "@/components/openai/ApiKeyForm";
import { GEN_AI_API_KEY_URL } from "@/components/openai/constants"; import { GEN_AI_API_KEY_URL } from "@/components/openai/constants";
@@ -49,10 +50,7 @@ const ExistingKeys = () => {
const Page = () => { const Page = () => {
return ( return (
<div> <div>
<div className="border-solid border-gray-600 border-b pb-2 mb-4 flex"> <AdminPageTitle title="OpenAI Keys" icon={<KeyIcon size={32} />} />
<KeyIcon size={32} />
<h1 className="text-3xl font-bold pl-2">OpenAI Keys</h1>
</div>
<ExistingKeys /> <ExistingKeys />

View File

@@ -6,6 +6,7 @@ import { fetchSS } from "@/lib/utilsSS";
import { ErrorCallout } from "@/components/ErrorCallout"; import { ErrorCallout } from "@/components/ErrorCallout";
import { Persona } from "./interfaces"; import { Persona } from "./interfaces";
import { RobotIcon } from "@/components/icons/icons"; import { RobotIcon } from "@/components/icons/icons";
import { AdminPageTitle } from "@/components/admin/Title";
export default async function Page() { export default async function Page() {
const personaResponse = await fetchSS("/persona"); const personaResponse = await fetchSS("/persona");
@@ -23,10 +24,7 @@ export default async function Page() {
return ( return (
<div> <div>
<div className="border-solid border-gray-600 border-b pb-2 mb-4 flex"> <AdminPageTitle icon={<RobotIcon size={32} />} title="Personas" />
<RobotIcon size={32} />
<h1 className="text-3xl font-bold pl-2">Personas</h1>
</div>
<div className="text-gray-300 text-sm mb-2"> <div className="text-gray-300 text-sm mb-2">
Personas are a way to build custom search/question-answering experiences Personas are a way to build custom search/question-answering experiences

View File

@@ -2,6 +2,7 @@
import { Button } from "@/components/Button"; import { Button } from "@/components/Button";
import { LoadingAnimation } from "@/components/Loading"; import { LoadingAnimation } from "@/components/Loading";
import { AdminPageTitle } from "@/components/admin/Title";
import { BasicTable } from "@/components/admin/connectors/BasicTable"; import { BasicTable } from "@/components/admin/connectors/BasicTable";
import { usePopup } from "@/components/admin/connectors/Popup"; import { usePopup } from "@/components/admin/connectors/Popup";
import { UsersIcon } from "@/components/icons/icons"; import { UsersIcon } from "@/components/icons/icons";
@@ -95,10 +96,7 @@ const UsersTable = () => {
const Page = () => { const Page = () => {
return ( return (
<div> <div>
<div className="border-solid border-gray-600 border-b pb-2 mb-4 flex"> <AdminPageTitle title="Manage Users" icon={<UsersIcon size={32} />} />
<UsersIcon size={32} />
<h1 className="text-3xl font-bold pl-2">Manage Users</h1>
</div>
<UsersTable /> <UsersTable />
</div> </div>

View File

@@ -9,6 +9,7 @@ import { Connector, DocumentSet, User } from "@/lib/types";
import { cookies } from "next/headers"; import { cookies } from "next/headers";
import { SearchType } from "@/lib/search/interfaces"; import { SearchType } from "@/lib/search/interfaces";
import { Persona } from "./admin/personas/interfaces"; import { Persona } from "./admin/personas/interfaces";
import { WelcomeModal } from "@/components/WelcomeModal";
export default async function Home() { export default async function Home() {
const tasks = [ const tasks = [
@@ -78,6 +79,7 @@ export default async function Home() {
<HealthCheckBanner /> <HealthCheckBanner />
</div> </div>
<ApiKeyModal /> <ApiKeyModal />
{connectors.length === 0 && connectorsResponse?.ok && <WelcomeModal />}
<div className="px-24 pt-10 flex flex-col items-center min-h-screen bg-gray-900 text-gray-100"> <div className="px-24 pt-10 flex flex-col items-center min-h-screen bg-gray-900 text-gray-100">
<div className="w-full"> <div className="w-full">
<SearchSection <SearchSection

View File

@@ -1,32 +1,28 @@
interface ModalProps { interface ModalProps {
children: JSX.Element | string; children: JSX.Element | string;
onOutsideClick: () => void;
title?: JSX.Element | string; title?: JSX.Element | string;
onOutsideClick?: () => void;
} }
export const Modal: React.FC<ModalProps> = ({ export function Modal({ children, title, onOutsideClick }: ModalProps) {
children,
onOutsideClick,
title,
}) => {
return ( return (
<div> <div>
<div <div
className={` className={`
fixed inset-0 bg-black bg-opacity-50 fixed inset-0 bg-black bg-opacity-50
flex items-center justify-center z-50 flex items-center justify-center z-50
`} `}
onClick={onOutsideClick} onClick={onOutsideClick}
> >
<div <div
className={` className={`
bg-gray-800 rounded-lg border border-gray-700 bg-gray-800 rounded-sm shadow-lg
shadow-lg relative w-1/2 text-sm shadow-lg relative w-1/2 text-sm
`} `}
onClick={(event) => event.stopPropagation()} onClick={(event) => event.stopPropagation()}
> >
{title && ( {title && (
<h2 className="text-xl font-bold mb-3 border-b border-gray-600 pt-4 pb-3 bg-gray-700 px-6"> <h2 className="text-xl font-bold mb-3 border-b border-gray-700 pt-4 pb-3 bg-gray-700 px-6">
{title} {title}
</h2> </h2>
)} )}
@@ -35,4 +31,4 @@ export const Modal: React.FC<ModalProps> = ({
</div> </div>
</div> </div>
); );
}; }

View File

@@ -0,0 +1,16 @@
"use client";
import { getSourceMetadata } from "@/lib/sources";
import { ValidSources } from "@/lib/types";
export function SourceIcon({
sourceType,
iconSize,
}: {
sourceType: ValidSources;
iconSize: number;
}) {
return getSourceMetadata(sourceType).icon({
size: iconSize,
});
}

View File

@@ -0,0 +1,44 @@
"use client";
import { Button } from "@tremor/react";
import { Modal } from "./Modal";
import Link from "next/link";
import { useState } from "react";
import { FiX } from "react-icons/fi";
export function WelcomeModal() {
const [isClosed, setIsClosed] = useState(false);
if (isClosed) {
return null;
}
return (
<Modal>
<div className="px-6 py-4">
<h2 className="text-xl font-bold mb-4 pb-2 border-b border-gray-700 flex">
Welcome to Danswer 🎉
</h2>
<div className="text-gray-100">
<p className="mb-4">
Danswer is the AI-powered search engine for your organization&apos;s
internal knowledge. Whenever you need to find any piece of internal
information, Danswer is there to help!
</p>
<p>
To get started, the first step is to configure some{" "}
<i>connectors</i>. Connectors are the way that Danswer gets data
from your organization&apos;s various data sources. Once setup,
we&apos;ll automatically sync data from your apps and docs into
Danswer, so you can search all through all of them in one place.
</p>
</div>
<div className="flex mt-3 dark">
<Button className="mx-auto">
<Link href="/admin/add-connector">Setup your first connector!</Link>
</Button>
</div>
</div>
</Modal>
);
}

View File

@@ -2,33 +2,14 @@ import { Header } from "@/components/Header";
import { Sidebar } from "@/components/admin/connectors/Sidebar"; import { Sidebar } from "@/components/admin/connectors/Sidebar";
import { import {
NotebookIcon, NotebookIcon,
GithubIcon,
GlobeIcon,
GoogleDriveIcon,
SlackIcon,
KeyIcon, KeyIcon,
BookstackIcon,
ConfluenceIcon,
GuruIcon,
FileIcon,
JiraIcon,
SlabIcon,
NotionIcon,
ZulipIcon,
ProductboardIcon,
LinearIcon,
UsersIcon, UsersIcon,
ThumbsUpIcon, ThumbsUpIcon,
HubSpotIcon,
BookmarkIcon, BookmarkIcon,
CPUIcon, CPUIcon,
Document360Icon,
RequestTrackerIcon,
GoogleSitesIcon,
GongIcon,
ZoomInIcon, ZoomInIcon,
ZendeskIcon,
RobotIcon, RobotIcon,
ConnectorIcon,
} from "@/components/icons/icons"; } from "@/components/icons/icons";
import { getAuthDisabledSS, getCurrentUserSS } from "@/lib/userSS"; import { getAuthDisabledSS, getCurrentUserSS } from "@/lib/userSS";
import { redirect } from "next/navigation"; import { redirect } from "next/navigation";
@@ -52,310 +33,119 @@ export async function Layout({ children }: { children: React.ReactNode }) {
<div> <div>
<Header user={user} /> <Header user={user} />
<div className="bg-gray-900 pt-8 pb-8 flex"> <div className="bg-gray-900 pt-8 pb-8 flex">
<Sidebar <div className="w-72">
title="Connector" <Sidebar
collections={[ collections={[
{ {
name: "Indexing", name: "Connectors",
items: [ items: [
{ {
name: ( name: (
<div className="flex"> <div className="flex">
<NotebookIcon size={18} /> <NotebookIcon size={18} />
<div className="ml-1">Status</div> <div className="ml-1">Existing Connectors</div>
</div> </div>
), ),
link: "/admin/indexing/status", link: "/admin/indexing/status",
}, },
], {
}, name: (
{ <div className="flex">
name: "Connector Settings", <ConnectorIcon size={18} />
items: [ <div className="ml-1.5">Add Connector</div>
{ </div>
name: ( ),
<div className="flex"> link: "/admin/add-connector",
<SlackIcon size={16} /> },
<div className="ml-1">Slack</div> ],
</div> },
), {
link: "/admin/connectors/slack", name: "Document Management",
}, items: [
{ {
name: ( name: (
<div className="flex"> <div className="flex">
<GithubIcon size={16} /> <BookmarkIcon size={18} />
<div className="ml-1">Github</div> <div className="ml-1">Document Sets</div>
</div> </div>
), ),
link: "/admin/connectors/github", link: "/admin/documents/sets",
}, },
{ {
name: ( name: (
<div className="flex"> <div className="flex">
<GoogleDriveIcon size={16} /> <ZoomInIcon size={18} />
<div className="ml-1">Google Drive</div> <div className="ml-1">Explorer</div>
</div> </div>
), ),
link: "/admin/connectors/google-drive", link: "/admin/documents/explorer",
}, },
{ {
name: ( name: (
<div className="flex"> <div className="flex">
<ConfluenceIcon size={16} /> <ThumbsUpIcon size={18} />
<div className="ml-1">Confluence</div> <div className="ml-1">Feedback</div>
</div> </div>
), ),
link: "/admin/connectors/confluence", link: "/admin/documents/feedback",
}, },
{ ],
name: ( },
<div className="flex"> {
<JiraIcon size={16} /> name: "Custom Assistants",
<div className="ml-1">Jira</div> items: [
</div> {
), name: (
link: "/admin/connectors/jira", <div className="flex">
}, <RobotIcon size={18} />
{ <div className="ml-1">Personas</div>
name: ( </div>
<div className="flex"> ),
<LinearIcon size={16} /> link: "/admin/personas",
<div className="ml-1">Linear</div> },
</div> {
), name: (
link: "/admin/connectors/linear", <div className="flex">
}, <CPUIcon size={18} />
{ <div className="ml-1">Slack Bots</div>
name: ( </div>
<div className="flex"> ),
<ProductboardIcon size={16} /> link: "/admin/bot",
<div className="ml-1">Productboard</div> },
</div> ],
), },
link: "/admin/connectors/productboard", {
}, name: "Keys",
{ items: [
name: ( {
<div className="flex"> name: (
<SlabIcon size={16} /> <div className="flex">
<div className="ml-1">Slab</div> <KeyIcon size={18} />
</div> <div className="ml-1">OpenAI</div>
), </div>
link: "/admin/connectors/slab", ),
}, link: "/admin/keys/openai",
{ },
name: ( ],
<div className="flex"> },
<NotionIcon size={16} /> {
<div className="ml-1">Notion</div> name: "User Management",
</div> items: [
), {
link: "/admin/connectors/notion", name: (
}, <div className="flex">
{ <UsersIcon size={18} />
name: ( <div className="ml-1">Users</div>
<div className="flex"> </div>
<GuruIcon size={16} /> ),
<div className="ml-1">Guru</div> link: "/admin/users",
</div> },
), ],
link: "/admin/connectors/guru", },
}, ]}
{ />
name: (
<div className="flex">
<BookstackIcon size={16} />
<div className="ml-1">BookStack</div>
</div>
),
link: "/admin/connectors/bookstack",
},
{
name: (
<div className="flex">
<ZulipIcon size={16} />
<div className="ml-1">Zulip</div>
</div>
),
link: "/admin/connectors/zulip",
},
{
name: (
<div className="flex">
<GongIcon size={16} />
<div className="ml-1">Gong</div>
</div>
),
link: "/admin/connectors/gong",
},
{
name: (
<div className="flex">
<GoogleSitesIcon size={16} />
<div className="ml-1">Google Sites</div>
</div>
),
link: "/admin/connectors/google-sites",
},
{
name: (
<div className="flex">
<GlobeIcon size={16} />
<div className="ml-1">Web</div>
</div>
),
link: "/admin/connectors/web",
},
{
name: (
<div className="flex">
<FileIcon size={16} />
<div className="ml-1">File</div>
</div>
),
link: "/admin/connectors/file",
},
{
name: (
<div className="flex">
<HubSpotIcon size={16} />
<div className="ml-1">HubSpot</div>
</div>
),
link: "/admin/connectors/hubspot",
},
{
name: (
<div className="flex">
<RequestTrackerIcon size={16} />
<div className="ml-1">Request Tracker</div>
</div>
),
link: "/admin/connectors/requesttracker",
},
{
name: (
<div className="flex">
<Document360Icon size={16} />
<div className="ml-1">Document360</div>
</div>
),
link: "/admin/connectors/document360",
},
{
name: (
<div className="flex">
<ZendeskIcon size={16} />
<div className="ml-1">Zendesk</div>
</div>
),
link: "/admin/connectors/zendesk",
},
],
},
{
name: "Keys",
items: [
{
name: (
<div className="flex">
<KeyIcon size={18} />
<div className="ml-1">OpenAI</div>
</div>
),
link: "/admin/keys/openai",
},
],
},
{
name: "User Management",
items: [
{
name: (
<div className="flex">
<UsersIcon size={18} />
<div className="ml-1">Users</div>
</div>
),
link: "/admin/users",
},
],
},
{
name: "Document Management",
items: [
{
name: (
<div className="flex">
<BookmarkIcon size={18} />
<div className="ml-1">Document Sets</div>
</div>
),
link: "/admin/documents/sets",
},
{
name: (
<div className="flex">
<ZoomInIcon size={18} />
<div className="ml-1">Explorer</div>
</div>
),
link: "/admin/documents/explorer",
},
{
name: (
<div className="flex">
<ThumbsUpIcon size={18} />
<div className="ml-1">Feedback</div>
</div>
),
link: "/admin/documents/feedback",
},
],
},
{
name: "Custom Assistants",
items: [
{
name: (
<div className="flex">
<RobotIcon size={18} />
<div className="ml-1">Personas</div>
</div>
),
link: "/admin/personas",
},
{
name: (
<div className="flex">
<CPUIcon size={18} />
<div className="ml-1">Slack Bots</div>
</div>
),
link: "/admin/bot",
},
],
},
{
name: "System Information",
items: [
{
name: (
<div className="flex">
<NotebookIcon size={18} />
<div className="ml-1">Version</div>
</div>
),
link: "/admin/systeminfo",
},
],
},
]}
/>
<div className="px-12 min-h-screen bg-gray-900 text-gray-100 w-full">
{children}
</div> </div>
<div className="px-12 bg-gray-900 text-gray-100 w-full">{children}</div>
</div> </div>
</div> </div>
); );

View File

@@ -4,19 +4,26 @@ import { Divider } from "@tremor/react";
export function AdminPageTitle({ export function AdminPageTitle({
icon, icon,
title, title,
farRightElement,
includeDivider = true,
}: { }: {
icon: JSX.Element; icon: JSX.Element;
title: string | JSX.Element; title: string | JSX.Element;
farRightElement?: JSX.Element;
includeDivider?: boolean;
}) { }) {
return ( return (
<div className="dark"> <div className="dark">
<div className="mb-4"> <div className="mb-4">
<HealthCheckBanner /> <HealthCheckBanner />
</div> </div>
<h1 className="text-3xl font-bold flex gap-x-2 mb-2"> <div className="flex">
{icon} {title} <h1 className="text-3xl font-bold flex gap-x-2">
</h1> {icon} {title}
<Divider /> </h1>
{farRightElement && <div className="ml-auto">{farRightElement}</div>}
</div>
{includeDivider && <Divider />}
</div> </div>
); );
} }

View File

@@ -1,13 +1,11 @@
import { getSourceMetadata } from "@/components/source"; import { getSourceMetadata } from "@/lib/sources";
import { import {
ConfluenceConfig, ConfluenceConfig,
Connector, Connector,
ConnectorIndexingStatus,
GithubConfig, GithubConfig,
GoogleDriveConfig, GoogleDriveConfig,
JiraConfig, JiraConfig,
SlackConfig, SlackConfig,
WebConfig,
ZulipConfig, ZulipConfig,
} from "@/lib/types"; } from "@/lib/types";
import Link from "next/link"; import Link from "next/link";
@@ -96,7 +94,7 @@ export const ConnectorTitle = ({
</> </>
); );
return ( return (
<div> <div className="my-auto">
{isLink ? ( {isLink ? (
<Link <Link
className={mainSectionClassName} className={mainSectionClassName}
@@ -107,7 +105,7 @@ export const ConnectorTitle = ({
) : ( ) : (
<div className={mainSectionClassName}>{mainDisplay}</div> <div className={mainSectionClassName}>{mainDisplay}</div>
)} )}
{showMetadata && ( {showMetadata && additionalMetadata.size > 0 && (
<div className="text-xs text-gray-300 mt-1"> <div className="text-xs text-gray-300 mt-1">
{Array.from(additionalMetadata.entries()).map(([key, value]) => { {Array.from(additionalMetadata.entries()).map(([key, value]) => {
return ( return (

View File

@@ -13,13 +13,12 @@ interface Collection {
} }
interface SidebarProps { interface SidebarProps {
title: string;
collections: Collection[]; collections: Collection[];
} }
export const Sidebar: React.FC<SidebarProps> = ({ collections }) => { export function Sidebar({ collections }: SidebarProps) {
return ( return (
<aside className="w-64 bg-gray-900 text-gray-100 pl-4"> <aside className="bg-gray-900 text-gray-100 pl-4">
<nav className="space-y-2 pl-4"> <nav className="space-y-2 pl-4">
{collections.map((collection, collectionInd) => ( {collections.map((collection, collectionInd) => (
<div key={collectionInd}> <div key={collectionInd}>
@@ -40,4 +39,4 @@ export const Sidebar: React.FC<SidebarProps> = ({ collections }) => {
</nav> </nav>
</aside> </aside>
); );
}; }

View File

@@ -35,6 +35,7 @@ import {
FiBookmark, FiBookmark,
FiCpu, FiCpu,
FiInfo, FiInfo,
FiUploadCloud,
} from "react-icons/fi"; } from "react-icons/fi";
import { SiBookstack } from "react-icons/si"; import { SiBookstack } from "react-icons/si";
import Image from "next/image"; import Image from "next/image";
@@ -289,6 +290,13 @@ export const RobotIcon = ({
return <FaRobot size={size} className={className} />; return <FaRobot size={size} className={className} />;
}; };
export const ConnectorIcon = ({
size = 16,
className = defaultTailwindCSS,
}: IconProps) => {
return <FiUploadCloud size={size} className={className} />;
};
// //
// COMPANY LOGOS // COMPANY LOGOS
// //

View File

@@ -1,11 +1,11 @@
import { DanswerDocument } from "@/lib/search/interfaces"; import { DanswerDocument } from "@/lib/search/interfaces";
import { DocumentFeedbackBlock } from "./DocumentFeedbackBlock"; import { DocumentFeedbackBlock } from "./DocumentFeedbackBlock";
import { getSourceIcon } from "../source";
import { useState } from "react"; import { useState } from "react";
import { PopupSpec } from "../admin/connectors/Popup"; import { PopupSpec } from "../admin/connectors/Popup";
import { HoverPopup } from "@/components/HoverPopup"; import { HoverPopup } from "@/components/HoverPopup";
import { DocumentUpdatedAtBadge } from "./DocumentUpdatedAtBadge"; import { DocumentUpdatedAtBadge } from "./DocumentUpdatedAtBadge";
import { FiCrosshair, FiInfo, FiRadio } from "react-icons/fi"; import { FiInfo, FiRadio } from "react-icons/fi";
import { SourceIcon } from "../SourceIcon";
export const buildDocumentSummaryDisplay = ( export const buildDocumentSummaryDisplay = (
matchHighlights: string[], matchHighlights: string[],
@@ -186,7 +186,7 @@ export const DocumentDisplay = ({
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
> >
{getSourceIcon(document.source_type, 22)} <SourceIcon sourceType={document.source_type} iconSize={22} />
<p className="truncate break-all ml-2 my-auto text-base"> <p className="truncate break-all ml-2 my-auto text-base">
{document.semantic_identifier || document.document_id} {document.semantic_identifier || document.document_id}
</p> </p>

View File

@@ -1,35 +1,14 @@
import React from "react"; import React from "react";
import { getSourceIcon } from "../../source";
import { DocumentSet, ValidSources } from "@/lib/types"; import { DocumentSet, ValidSources } from "@/lib/types";
import { Source } from "@/lib/search/interfaces"; import { SourceMetadata } from "@/lib/search/interfaces";
import { InfoIcon, defaultTailwindCSS } from "../../icons/icons"; import { InfoIcon, defaultTailwindCSS } from "../../icons/icons";
import { HoverPopup } from "../../HoverPopup"; import { HoverPopup } from "../../HoverPopup";
import { FiBook, FiBookmark, FiFilter, FiMap, FiX } from "react-icons/fi"; import { FiBook, FiBookmark, FiFilter, FiMap, FiX } from "react-icons/fi";
import { DateRangeSelector } from "../DateRangeSelector"; import { DateRangeSelector } from "../DateRangeSelector";
import { DateRangePickerValue } from "@tremor/react"; import { DateRangePickerValue } from "@tremor/react";
import { FilterDropdown } from "./FilterDropdown"; import { FilterDropdown } from "./FilterDropdown";
import { listSourceMetadata } from "@/lib/sources";
const sources: Source[] = [ import { SourceIcon } from "@/components/SourceIcon";
{ displayName: "Google Drive", internalName: "google_drive" },
{ displayName: "Slack", internalName: "slack" },
{ displayName: "BookStack", internalName: "bookstack" },
{ displayName: "Confluence", internalName: "confluence" },
{ displayName: "Jira", internalName: "jira" },
{ displayName: "Productboard", internalName: "productboard" },
{ displayName: "Slab", internalName: "slab" },
{ displayName: "Github PRs", internalName: "github" },
{ displayName: "Web", internalName: "web" },
{ displayName: "Guru", internalName: "guru" },
{ displayName: "Gong", internalName: "gong" },
{ displayName: "File", internalName: "file" },
{ displayName: "Notion", internalName: "notion" },
{ displayName: "Zulip", internalName: "zulip" },
{ displayName: "Linear", internalName: "linear" },
{ displayName: "HubSpot", internalName: "hubspot" },
{ displayName: "Document360", internalName: "document360" },
{ displayName: "Request Tracker", internalName: "requesttracker" },
{ displayName: "Google Sites", internalName: "google_sites" },
];
const SectionTitle = ({ children }: { children: string }) => ( const SectionTitle = ({ children }: { children: string }) => (
<div className="font-medium text-sm flex">{children}</div> <div className="font-medium text-sm flex">{children}</div>
@@ -40,8 +19,8 @@ interface SourceSelectorProps {
setTimeRange: React.Dispatch< setTimeRange: React.Dispatch<
React.SetStateAction<DateRangePickerValue | null> React.SetStateAction<DateRangePickerValue | null>
>; >;
selectedSources: Source[]; selectedSources: SourceMetadata[];
setSelectedSources: React.Dispatch<React.SetStateAction<Source[]>>; setSelectedSources: React.Dispatch<React.SetStateAction<SourceMetadata[]>>;
selectedDocumentSets: string[]; selectedDocumentSets: string[];
setSelectedDocumentSets: React.Dispatch<React.SetStateAction<string[]>>; setSelectedDocumentSets: React.Dispatch<React.SetStateAction<string[]>>;
availableDocumentSets: DocumentSet[]; availableDocumentSets: DocumentSet[];
@@ -58,8 +37,8 @@ export function SourceSelector({
availableDocumentSets, availableDocumentSets,
existingSources, existingSources,
}: SourceSelectorProps) { }: SourceSelectorProps) {
const handleSelect = (source: Source) => { const handleSelect = (source: SourceMetadata) => {
setSelectedSources((prev: Source[]) => { setSelectedSources((prev: SourceMetadata[]) => {
if (prev.includes(source)) { if (prev.includes(source)) {
return prev.filter((s) => s.internalName !== source.internalName); return prev.filter((s) => s.internalName !== source.internalName);
} else { } else {
@@ -96,7 +75,7 @@ export function SourceSelector({
<div className="mt-4"> <div className="mt-4">
<SectionTitle>Sources</SectionTitle> <SectionTitle>Sources</SectionTitle>
<div className="px-1"> <div className="px-1">
{sources {listSourceMetadata()
.filter((source) => existingSources.includes(source.internalName)) .filter((source) => existingSources.includes(source.internalName))
.map((source) => ( .map((source) => (
<div <div
@@ -110,7 +89,7 @@ export function SourceSelector({
} }
onClick={() => handleSelect(source)} onClick={() => handleSelect(source)}
> >
{getSourceIcon(source.internalName, 16)} <SourceIcon sourceType={source.internalName} iconSize={16} />
<span className="ml-2 text-sm text-gray-200"> <span className="ml-2 text-sm text-gray-200">
{source.displayName} {source.displayName}
</span> </span>
@@ -201,8 +180,8 @@ export function HorizontalFilters({
availableDocumentSets, availableDocumentSets,
existingSources, existingSources,
}: SourceSelectorProps) { }: SourceSelectorProps) {
const handleSourceSelect = (source: Source) => { const handleSourceSelect = (source: SourceMetadata) => {
setSelectedSources((prev: Source[]) => { setSelectedSources((prev: SourceMetadata[]) => {
const prevSourceNames = prev.map((source) => source.internalName); const prevSourceNames = prev.map((source) => source.internalName);
if (prevSourceNames.includes(source.internalName)) { if (prevSourceNames.includes(source.internalName)) {
return prev.filter((s) => s.internalName !== source.internalName); return prev.filter((s) => s.internalName !== source.internalName);
@@ -222,7 +201,8 @@ export function HorizontalFilters({
}); });
}; };
const availableSources = sources.filter((source) => const allSources = listSourceMetadata();
const availableSources = allSources.filter((source) =>
existingSources.includes(source.internalName) existingSources.includes(source.internalName)
); );
@@ -239,8 +219,7 @@ export function HorizontalFilters({
key: source.displayName, key: source.displayName,
display: ( display: (
<> <>
{" "} <SourceIcon sourceType={source.internalName} iconSize={16} />
{getSourceIcon(source.internalName, 16)}
<span className="ml-2 text-sm text-gray-200"> <span className="ml-2 text-sm text-gray-200">
{source.displayName} {source.displayName}
</span> </span>
@@ -251,7 +230,7 @@ export function HorizontalFilters({
selected={selectedSources.map((source) => source.displayName)} selected={selectedSources.map((source) => source.displayName)}
handleSelect={(option) => handleSelect={(option) =>
handleSourceSelect( handleSourceSelect(
sources.find((source) => source.displayName === option.key)! allSources.find((source) => source.displayName === option.key)!
) )
} }
icon={ icon={
@@ -305,7 +284,7 @@ export function HorizontalFilters({
onClick={() => handleSourceSelect(source)} onClick={() => handleSourceSelect(source)}
> >
<> <>
{getSourceIcon(source.internalName, 16)} <SourceIcon sourceType={source.internalName} iconSize={16} />
<span className="ml-2 text-sm text-gray-400"> <span className="ml-2 text-sm text-gray-400">
{source.displayName} {source.displayName}
</span> </span>

View File

@@ -1,8 +1,8 @@
import { Quote } from "@/lib/search/interfaces"; import { Quote } from "@/lib/search/interfaces";
import { ResponseSection, StatusOptions } from "./ResponseSection"; import { ResponseSection, StatusOptions } from "./ResponseSection";
import { getSourceIcon } from "@/components/source";
import { CheckmarkIcon, CopyIcon } from "@/components/icons/icons"; import { CheckmarkIcon, CopyIcon } from "@/components/icons/icons";
import { useState } from "react"; import { useState } from "react";
import { SourceIcon } from "@/components/SourceIcon";
const QuoteDisplay = ({ quoteInfo }: { quoteInfo: Quote }) => { const QuoteDisplay = ({ quoteInfo }: { quoteInfo: Quote }) => {
const [detailIsOpen, setDetailIsOpen] = useState(false); const [detailIsOpen, setDetailIsOpen] = useState(false);
@@ -54,7 +54,7 @@ const QuoteDisplay = ({ quoteInfo }: { quoteInfo: Quote }) => {
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
> >
{getSourceIcon(quoteInfo.source_type, 20)} <SourceIcon sourceType={quoteInfo.source_type} iconSize={20} />
<p className="truncate break-all ml-2 mr-2"> <p className="truncate break-all ml-2 mr-2">
{quoteInfo.semantic_identifier || quoteInfo.document_id} {quoteInfo.semantic_identifier || quoteInfo.document_id}
</p> </p>

View File

@@ -1,168 +0,0 @@
import { ValidSources } from "@/lib/types";
import {
BookstackIcon,
ConfluenceIcon,
FileIcon,
GithubIcon,
GlobeIcon,
GoogleDriveIcon,
GuruIcon,
GongIcon,
JiraIcon,
LinearIcon,
NotionIcon,
ProductboardIcon,
SlabIcon,
SlackIcon,
ZulipIcon,
HubSpotIcon,
Document360Icon,
GoogleSitesIcon,
RequestTrackerIcon,
ZendeskIcon,
} from "./icons/icons";
interface SourceMetadata {
icon: React.FC<{ size?: number; className?: string }>;
displayName: string;
adminPageLink: string;
}
export const getSourceMetadata = (sourceType: ValidSources): SourceMetadata => {
switch (sourceType) {
case "web":
return {
icon: GlobeIcon,
displayName: "Web",
adminPageLink: "/admin/connectors/web",
};
case "file":
return {
icon: FileIcon,
displayName: "File",
adminPageLink: "/admin/connectors/file",
};
case "slack":
return {
icon: SlackIcon,
displayName: "Slack",
adminPageLink: "/admin/connectors/slack",
};
case "google_drive":
return {
icon: GoogleDriveIcon,
displayName: "Google Drive",
adminPageLink: "/admin/connectors/google-drive",
};
case "github":
return {
icon: GithubIcon,
displayName: "Github PRs",
adminPageLink: "/admin/connectors/github",
};
case "bookstack":
return {
icon: BookstackIcon,
displayName: "BookStack",
adminPageLink: "/admin/connectors/bookstack",
};
case "confluence":
return {
icon: ConfluenceIcon,
displayName: "Confluence",
adminPageLink: "/admin/connectors/confluence",
};
case "jira":
return {
icon: JiraIcon,
displayName: "Jira",
adminPageLink: "/admin/connectors/jira",
};
case "productboard":
return {
icon: ProductboardIcon,
displayName: "Productboard",
adminPageLink: "/admin/connectors/productboard",
};
case "slab":
return {
icon: SlabIcon,
displayName: "Slab",
adminPageLink: "/admin/connectors/slab",
};
case "notion":
return {
icon: NotionIcon,
displayName: "Notion",
adminPageLink: "/admin/connectors/notion",
};
case "zulip":
return {
icon: ZulipIcon,
displayName: "Zulip",
adminPageLink: "/admin/connectors/zulip",
};
case "guru":
return {
icon: GuruIcon,
displayName: "Guru",
adminPageLink: "/admin/connectors/guru",
};
case "gong":
return {
icon: GongIcon,
displayName: "Gong",
adminPageLink: "/admin/connectors/gong",
};
case "linear":
return {
icon: LinearIcon,
displayName: "Linear",
adminPageLink: "/admin/connectors/linear",
};
case "hubspot":
return {
icon: HubSpotIcon,
displayName: "HubSpot",
adminPageLink: "/admin/connectors/hubspot",
};
case "document360":
return {
icon: Document360Icon,
displayName: "Document360",
adminPageLink: "/admin/connectors/document360",
};
case "requesttracker":
return {
icon: RequestTrackerIcon,
displayName: "Request Tracker",
adminPageLink: "/admin/connectors/requesttracker",
};
case "google_sites":
return {
icon: GoogleSitesIcon,
displayName: "Google Sites",
adminPageLink: "/admin/connectors/google-sites",
};
case "zendesk":
return {
icon: ZendeskIcon,
displayName: "Zendesk",
adminPageLink: "/admin/connectors/zendesk",
};
default:
throw new Error("Invalid source type");
}
};
export const getSourceIcon = (sourceType: ValidSources, iconSize: number) => {
return getSourceMetadata(sourceType).icon({
size: iconSize,
});
};
export const getSourceDisplayName = (
sourceType: ValidSources
): string | null => {
return getSourceMetadata(sourceType).displayName;
};

View File

@@ -7,7 +7,7 @@ import useSWR, { mutate, useSWRConfig } from "swr";
import { fetcher } from "./fetcher"; import { fetcher } from "./fetcher";
import { useState } from "react"; import { useState } from "react";
import { DateRangePickerValue } from "@tremor/react"; import { DateRangePickerValue } from "@tremor/react";
import { Source } from "./search/interfaces"; import { SourceMetadata } from "./search/interfaces";
const CREDENTIAL_URL = "/api/manage/admin/credential"; const CREDENTIAL_URL = "/api/manage/admin/credential";
@@ -77,7 +77,7 @@ export const useTimeRange = (initialValue?: DateRangePickerValue) => {
export function useFilters() { export function useFilters() {
const [timeRange, setTimeRange] = useTimeRange(); const [timeRange, setTimeRange] = useTimeRange();
const [selectedSources, setSelectedSources] = useState<Source[]>([]); const [selectedSources, setSelectedSources] = useState<SourceMetadata[]>([]);
const [selectedDocumentSets, setSelectedDocumentSets] = useState<string[]>( const [selectedDocumentSets, setSelectedDocumentSets] = useState<string[]>(
[] []
); );

View File

@@ -74,9 +74,18 @@ export interface SearchResponse {
queryEventId: number | null; queryEventId: number | null;
} }
export interface Source { export enum SourceCategory {
AppConnection = "Connect to Apps",
ImportedKnowledge = "Import Knowledge",
}
export interface SourceMetadata {
icon: React.FC<{ size?: number; className?: string }>;
displayName: string; displayName: string;
category: SourceCategory;
shortDescription?: string;
internalName: ValidSources; internalName: ValidSources;
adminUrl: string;
} }
export interface SearchDefaultOverrides { export interface SearchDefaultOverrides {
@@ -93,7 +102,7 @@ export interface Filters {
export interface SearchRequestArgs { export interface SearchRequestArgs {
query: string; query: string;
chatSessionId: number; chatSessionId: number;
sources: Source[]; sources: SourceMetadata[];
documentSets: string[]; documentSets: string[];
timeRange: DateRangePickerValue | null; timeRange: DateRangePickerValue | null;
updateCurrentAnswer: (val: string) => void; updateCurrentAnswer: (val: string) => void;

View File

@@ -1,8 +1,8 @@
import { Filters, Source } from "./interfaces"; import { Filters, SourceMetadata } from "./interfaces";
import { DateRangePickerValue } from "@tremor/react"; import { DateRangePickerValue } from "@tremor/react";
export const buildFilters = ( export const buildFilters = (
sources: Source[], sources: SourceMetadata[],
documentSets: string[], documentSets: string[],
timeRange: DateRangePickerValue | null timeRange: DateRangePickerValue | null
): Filters => { ): Filters => {

164
web/src/lib/sources.ts Normal file
View File

@@ -0,0 +1,164 @@
import {
BookstackIcon,
ConfluenceIcon,
Document360Icon,
FileIcon,
GithubIcon,
GlobeIcon,
GongIcon,
GoogleDriveIcon,
GoogleSitesIcon,
GuruIcon,
HubSpotIcon,
JiraIcon,
LinearIcon,
NotionIcon,
ProductboardIcon,
RequestTrackerIcon,
SlabIcon,
SlackIcon,
ZendeskIcon,
ZulipIcon,
} from "@/components/icons/icons";
import { ValidSources } from "./types";
import { SourceCategory, SourceMetadata } from "./search/interfaces";
interface PartialSourceMetadata {
icon: React.FC<{ size?: number; className?: string }>;
displayName: string;
category: SourceCategory;
}
type SourceMap = {
[K in ValidSources]: PartialSourceMetadata;
};
const SOURCE_METADATA_MAP: SourceMap = {
web: {
icon: GlobeIcon,
displayName: "Web",
category: SourceCategory.ImportedKnowledge,
},
file: {
icon: FileIcon,
displayName: "File",
category: SourceCategory.ImportedKnowledge,
},
slack: {
icon: SlackIcon,
displayName: "Slack",
category: SourceCategory.AppConnection,
},
google_drive: {
icon: GoogleDriveIcon,
displayName: "Google Drive",
category: SourceCategory.AppConnection,
},
github: {
icon: GithubIcon,
displayName: "Github",
category: SourceCategory.AppConnection,
},
confluence: {
icon: ConfluenceIcon,
displayName: "Confluence",
category: SourceCategory.AppConnection,
},
jira: {
icon: JiraIcon,
displayName: "Jira",
category: SourceCategory.AppConnection,
},
notion: {
icon: NotionIcon,
displayName: "Notion",
category: SourceCategory.AppConnection,
},
zendesk: {
icon: ZendeskIcon,
displayName: "Zendesk",
category: SourceCategory.AppConnection,
},
gong: {
icon: GongIcon,
displayName: "Gong",
category: SourceCategory.AppConnection,
},
linear: {
icon: LinearIcon,
displayName: "Linear",
category: SourceCategory.AppConnection,
},
productboard: {
icon: ProductboardIcon,
displayName: "Productboard",
category: SourceCategory.AppConnection,
},
slab: {
icon: SlabIcon,
displayName: "Slab",
category: SourceCategory.AppConnection,
},
zulip: {
icon: ZulipIcon,
displayName: "Zulip",
category: SourceCategory.AppConnection,
},
guru: {
icon: GuruIcon,
displayName: "Guru",
category: SourceCategory.AppConnection,
},
hubspot: {
icon: HubSpotIcon,
displayName: "HubSpot",
category: SourceCategory.AppConnection,
},
document360: {
icon: Document360Icon,
displayName: "Document360",
category: SourceCategory.AppConnection,
},
bookstack: {
icon: BookstackIcon,
displayName: "BookStack",
category: SourceCategory.AppConnection,
},
google_sites: {
icon: GoogleSitesIcon,
displayName: "Google Sites",
category: SourceCategory.ImportedKnowledge,
},
requesttracker: {
icon: RequestTrackerIcon,
displayName: "Request Tracker",
category: SourceCategory.AppConnection,
},
};
function fillSourceMetadata(
partialMetadata: PartialSourceMetadata,
internalName: ValidSources
): SourceMetadata {
return {
internalName: internalName,
...partialMetadata,
adminUrl: `/admin/connectors/${partialMetadata.displayName
.toLowerCase()
.replaceAll(" ", "-")}`,
};
}
export function getSourceMetadata(sourceType: ValidSources): SourceMetadata {
return fillSourceMetadata(SOURCE_METADATA_MAP[sourceType], sourceType);
}
export function listSourceMetadata(): SourceMetadata[] {
return Object.entries(SOURCE_METADATA_MAP).map(([source, metadata]) => {
return fillSourceMetadata(metadata, source as ValidSources);
});
}
export function getSourceDisplayName(sourceType: ValidSources): string | null {
return getSourceMetadata(sourceType).displayName;
}