mirror of
https://github.com/danswer-ai/danswer.git
synced 2025-09-20 21:33:56 +02:00
FE improvements to make initial setup more intuitive
This commit is contained in:
90
web/src/app/admin/add-connector/page.tsx
Normal file
90
web/src/app/admin/add-connector/page.tsx
Normal 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's knowledge sources.
|
||||
We'll automatically sync your data into Danswer, so you can find
|
||||
exactly what you'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'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'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>
|
||||
);
|
||||
}
|
@@ -13,6 +13,7 @@ import { SlackBotCreationForm } from "./SlackBotConfigCreationForm";
|
||||
import { deleteSlackBotConfig } from "./lib";
|
||||
import { SlackBotTokensForm } from "./SlackBotTokensForm";
|
||||
import { useDocumentSets } from "../documents/sets/hooks";
|
||||
import { AdminPageTitle } from "@/components/admin/Title";
|
||||
|
||||
const numToDisplay = 50;
|
||||
|
||||
@@ -315,10 +316,10 @@ const Main = () => {
|
||||
const Page = () => {
|
||||
return (
|
||||
<div>
|
||||
<div className="border-solid border-gray-600 border-b pb-2 mb-4 flex">
|
||||
<CPUIcon size={32} />
|
||||
<h1 className="text-3xl font-bold pl-2">Slack Bot Configuration</h1>
|
||||
</div>
|
||||
<AdminPageTitle
|
||||
icon={<CPUIcon size={32} />}
|
||||
title="Slack Bot Configuration"
|
||||
/>
|
||||
|
||||
<Main />
|
||||
</div>
|
||||
|
@@ -4,7 +4,6 @@ import { adminSearch } from "./lib";
|
||||
import { MagnifyingGlass } from "@phosphor-icons/react";
|
||||
import { useState, useEffect } from "react";
|
||||
import { DanswerDocument } from "@/lib/search/interfaces";
|
||||
import { getSourceIcon } from "@/components/source";
|
||||
import { buildDocumentSummaryDisplay } from "@/components/search/DocumentDisplay";
|
||||
import { CustomCheckbox } from "@/components/CustomCheckbox";
|
||||
import { updateHiddenStatus } from "../lib";
|
||||
@@ -17,6 +16,7 @@ import { useFilters } from "@/lib/hooks";
|
||||
import { buildFilters } from "@/lib/search/utils";
|
||||
import { DocumentUpdatedAtBadge } from "@/components/search/DocumentUpdatedAtBadge";
|
||||
import { Connector, DocumentSet } from "@/lib/types";
|
||||
import { SourceIcon } from "@/components/SourceIcon";
|
||||
|
||||
const DocumentDisplay = ({
|
||||
document,
|
||||
@@ -42,7 +42,7 @@ const DocumentDisplay = ({
|
||||
target="_blank"
|
||||
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">
|
||||
{document.semantic_identifier || document.document_id}
|
||||
</p>
|
||||
@@ -111,7 +111,6 @@ export function Explorer({
|
||||
connectors: Connector<any>[];
|
||||
documentSets: DocumentSet[];
|
||||
}) {
|
||||
console.log(connectors);
|
||||
const router = useRouter();
|
||||
const { popup, setPopup } = usePopup();
|
||||
|
||||
|
@@ -5,6 +5,7 @@ import { ThumbsUpIcon } from "@/components/icons/icons";
|
||||
import { useMostReactedToDocuments } from "@/lib/hooks";
|
||||
import { DocumentFeedbackTable } from "./DocumentFeedbackTable";
|
||||
import { numPages, numToDisplay } from "./constants";
|
||||
import { AdminPageTitle } from "@/components/admin/Title";
|
||||
|
||||
const Main = () => {
|
||||
const {
|
||||
@@ -61,10 +62,10 @@ const Main = () => {
|
||||
const Page = () => {
|
||||
return (
|
||||
<div>
|
||||
<div className="border-solid border-gray-600 border-b pb-2 mb-4 flex">
|
||||
<ThumbsUpIcon size={32} />
|
||||
<h1 className="text-3xl font-bold pl-2">Document Feedback</h1>
|
||||
</div>
|
||||
<AdminPageTitle
|
||||
icon={<ThumbsUpIcon size={32} />}
|
||||
title="Document Feedback"
|
||||
/>
|
||||
|
||||
<Main />
|
||||
</div>
|
||||
|
@@ -18,6 +18,8 @@ import { DocumentSetCreationForm } from "./DocumentSetCreationForm";
|
||||
import { ConnectorTitle } from "@/components/admin/connectors/ConnectorTitle";
|
||||
import { deleteDocumentSet } from "./lib";
|
||||
import { PopupSpec, usePopup } from "@/components/admin/connectors/Popup";
|
||||
import { AdminPageTitle } from "@/components/admin/Title";
|
||||
import { Text } from "@tremor/react";
|
||||
|
||||
const numToDisplay = 50;
|
||||
|
||||
@@ -249,15 +251,15 @@ const Main = () => {
|
||||
return (
|
||||
<div className="mb-8">
|
||||
{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
|
||||
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
|
||||
information the bot searches over when answering in a specific channel
|
||||
or with a certain command.
|
||||
</div>
|
||||
</Text>
|
||||
|
||||
<div className="mb-2"></div>
|
||||
<div className="mb-6"></div>
|
||||
|
||||
<div className="flex mb-3">
|
||||
<Button className="ml-2 my-auto" onClick={() => setIsOpen(true)}>
|
||||
@@ -265,12 +267,14 @@ const Main = () => {
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<DocumentSetTable
|
||||
documentSets={documentSets}
|
||||
ccPairs={ccPairs}
|
||||
refresh={refreshDocumentSets}
|
||||
setPopup={setPopup}
|
||||
/>
|
||||
{documentSets.length > 0 && (
|
||||
<DocumentSetTable
|
||||
documentSets={documentSets}
|
||||
ccPairs={ccPairs}
|
||||
refresh={refreshDocumentSets}
|
||||
setPopup={setPopup}
|
||||
/>
|
||||
)}
|
||||
|
||||
{isOpen && (
|
||||
<DocumentSetCreationForm
|
||||
@@ -289,10 +293,7 @@ const Main = () => {
|
||||
const Page = () => {
|
||||
return (
|
||||
<div>
|
||||
<div className="border-solid border-gray-600 border-b pb-2 mb-4 flex">
|
||||
<BookmarkIcon size={32} />
|
||||
<h1 className="text-3xl font-bold pl-2">Document Sets</h1>
|
||||
</div>
|
||||
<AdminPageTitle icon={<BookmarkIcon size={32} />} title="Document Sets" />
|
||||
|
||||
<Main />
|
||||
</div>
|
||||
|
@@ -17,6 +17,7 @@ import { ConnectorTitle } from "@/components/admin/connectors/ConnectorTitle";
|
||||
import { getDocsProcessedPerMinute } from "@/lib/indexAttempt";
|
||||
import Link from "next/link";
|
||||
import { isCurrentlyDeleting } from "@/lib/documentDeletion";
|
||||
import { FiEdit, FiMaximize, FiMaximize2 } from "react-icons/fi";
|
||||
|
||||
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"
|
||||
}
|
||||
>
|
||||
<TableCell className="whitespace-normal break-all">
|
||||
<ConnectorTitle
|
||||
connector={ccPairsIndexingStatus.connector}
|
||||
ccPairId={ccPairsIndexingStatus.cc_pair_id}
|
||||
ccPairName={ccPairsIndexingStatus.name}
|
||||
/>
|
||||
<TableCell>
|
||||
<div className="flex my-auto">
|
||||
<FiEdit className="mr-4 my-auto text-blue-300" />
|
||||
<div className="whitespace-normal break-all max-w-3xl">
|
||||
<ConnectorTitle
|
||||
connector={ccPairsIndexingStatus.connector}
|
||||
ccPairId={ccPairsIndexingStatus.cc_pair_id}
|
||||
ccPairName={ccPairsIndexingStatus.name}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<CCPairIndexingStatusDisplay
|
||||
|
@@ -5,10 +5,11 @@ import useSWR from "swr";
|
||||
import { LoadingAnimation } from "@/components/Loading";
|
||||
import { NotebookIcon } from "@/components/icons/icons";
|
||||
import { fetcher } from "@/lib/fetcher";
|
||||
import { HealthCheckBanner } from "@/components/health/healthcheck";
|
||||
import { ConnectorIndexingStatus } from "@/lib/types";
|
||||
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() {
|
||||
const {
|
||||
@@ -29,6 +30,18 @@ function Main() {
|
||||
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'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
|
||||
indexAttemptData.sort((a, b) => {
|
||||
if (a.connector.source < b.connector.source) {
|
||||
@@ -48,13 +61,17 @@ function Main() {
|
||||
export default function Status() {
|
||||
return (
|
||||
<div className="mx-auto container dark">
|
||||
<div className="mb-4">
|
||||
<HealthCheckBanner />
|
||||
</div>
|
||||
<h1 className="text-3xl font-bold flex gap-x-2 mb-2">
|
||||
<NotebookIcon size={32} /> Indexing Status
|
||||
</h1>
|
||||
<Divider />
|
||||
<AdminPageTitle
|
||||
icon={<NotebookIcon size={32} />}
|
||||
title="Existing Connectors"
|
||||
farRightElement={
|
||||
<Link href="/admin/add-connector">
|
||||
<Button variant="secondary" size="xs">
|
||||
Add Connector
|
||||
</Button>
|
||||
</Link>
|
||||
}
|
||||
/>
|
||||
<Main />
|
||||
</div>
|
||||
);
|
||||
|
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { LoadingAnimation } from "@/components/Loading";
|
||||
import { AdminPageTitle } from "@/components/admin/Title";
|
||||
import { KeyIcon, TrashIcon } from "@/components/icons/icons";
|
||||
import { ApiKeyForm } from "@/components/openai/ApiKeyForm";
|
||||
import { GEN_AI_API_KEY_URL } from "@/components/openai/constants";
|
||||
@@ -49,10 +50,7 @@ const ExistingKeys = () => {
|
||||
const Page = () => {
|
||||
return (
|
||||
<div>
|
||||
<div className="border-solid border-gray-600 border-b pb-2 mb-4 flex">
|
||||
<KeyIcon size={32} />
|
||||
<h1 className="text-3xl font-bold pl-2">OpenAI Keys</h1>
|
||||
</div>
|
||||
<AdminPageTitle title="OpenAI Keys" icon={<KeyIcon size={32} />} />
|
||||
|
||||
<ExistingKeys />
|
||||
|
||||
|
@@ -6,6 +6,7 @@ import { fetchSS } from "@/lib/utilsSS";
|
||||
import { ErrorCallout } from "@/components/ErrorCallout";
|
||||
import { Persona } from "./interfaces";
|
||||
import { RobotIcon } from "@/components/icons/icons";
|
||||
import { AdminPageTitle } from "@/components/admin/Title";
|
||||
|
||||
export default async function Page() {
|
||||
const personaResponse = await fetchSS("/persona");
|
||||
@@ -23,10 +24,7 @@ export default async function Page() {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="border-solid border-gray-600 border-b pb-2 mb-4 flex">
|
||||
<RobotIcon size={32} />
|
||||
<h1 className="text-3xl font-bold pl-2">Personas</h1>
|
||||
</div>
|
||||
<AdminPageTitle icon={<RobotIcon size={32} />} title="Personas" />
|
||||
|
||||
<div className="text-gray-300 text-sm mb-2">
|
||||
Personas are a way to build custom search/question-answering experiences
|
||||
|
@@ -2,6 +2,7 @@
|
||||
|
||||
import { Button } from "@/components/Button";
|
||||
import { LoadingAnimation } from "@/components/Loading";
|
||||
import { AdminPageTitle } from "@/components/admin/Title";
|
||||
import { BasicTable } from "@/components/admin/connectors/BasicTable";
|
||||
import { usePopup } from "@/components/admin/connectors/Popup";
|
||||
import { UsersIcon } from "@/components/icons/icons";
|
||||
@@ -95,10 +96,7 @@ const UsersTable = () => {
|
||||
const Page = () => {
|
||||
return (
|
||||
<div>
|
||||
<div className="border-solid border-gray-600 border-b pb-2 mb-4 flex">
|
||||
<UsersIcon size={32} />
|
||||
<h1 className="text-3xl font-bold pl-2">Manage Users</h1>
|
||||
</div>
|
||||
<AdminPageTitle title="Manage Users" icon={<UsersIcon size={32} />} />
|
||||
|
||||
<UsersTable />
|
||||
</div>
|
||||
|
@@ -9,6 +9,7 @@ import { Connector, DocumentSet, User } from "@/lib/types";
|
||||
import { cookies } from "next/headers";
|
||||
import { SearchType } from "@/lib/search/interfaces";
|
||||
import { Persona } from "./admin/personas/interfaces";
|
||||
import { WelcomeModal } from "@/components/WelcomeModal";
|
||||
|
||||
export default async function Home() {
|
||||
const tasks = [
|
||||
@@ -78,6 +79,7 @@ export default async function Home() {
|
||||
<HealthCheckBanner />
|
||||
</div>
|
||||
<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="w-full">
|
||||
<SearchSection
|
||||
|
@@ -1,32 +1,28 @@
|
||||
interface ModalProps {
|
||||
children: JSX.Element | string;
|
||||
onOutsideClick: () => void;
|
||||
title?: JSX.Element | string;
|
||||
onOutsideClick?: () => void;
|
||||
}
|
||||
|
||||
export const Modal: React.FC<ModalProps> = ({
|
||||
children,
|
||||
onOutsideClick,
|
||||
title,
|
||||
}) => {
|
||||
export function Modal({ children, title, onOutsideClick }: ModalProps) {
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
className={`
|
||||
fixed inset-0 bg-black bg-opacity-50
|
||||
fixed inset-0 bg-black bg-opacity-50
|
||||
flex items-center justify-center z-50
|
||||
`}
|
||||
onClick={onOutsideClick}
|
||||
>
|
||||
<div
|
||||
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
|
||||
`}
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
>
|
||||
{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}
|
||||
</h2>
|
||||
)}
|
||||
@@ -35,4 +31,4 @@ export const Modal: React.FC<ModalProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
16
web/src/components/SourceIcon.tsx
Normal file
16
web/src/components/SourceIcon.tsx
Normal 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,
|
||||
});
|
||||
}
|
44
web/src/components/WelcomeModal.tsx
Normal file
44
web/src/components/WelcomeModal.tsx
Normal 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'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's various data sources. Once setup,
|
||||
we'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>
|
||||
);
|
||||
}
|
@@ -2,33 +2,14 @@ import { Header } from "@/components/Header";
|
||||
import { Sidebar } from "@/components/admin/connectors/Sidebar";
|
||||
import {
|
||||
NotebookIcon,
|
||||
GithubIcon,
|
||||
GlobeIcon,
|
||||
GoogleDriveIcon,
|
||||
SlackIcon,
|
||||
KeyIcon,
|
||||
BookstackIcon,
|
||||
ConfluenceIcon,
|
||||
GuruIcon,
|
||||
FileIcon,
|
||||
JiraIcon,
|
||||
SlabIcon,
|
||||
NotionIcon,
|
||||
ZulipIcon,
|
||||
ProductboardIcon,
|
||||
LinearIcon,
|
||||
UsersIcon,
|
||||
ThumbsUpIcon,
|
||||
HubSpotIcon,
|
||||
BookmarkIcon,
|
||||
CPUIcon,
|
||||
Document360Icon,
|
||||
RequestTrackerIcon,
|
||||
GoogleSitesIcon,
|
||||
GongIcon,
|
||||
ZoomInIcon,
|
||||
ZendeskIcon,
|
||||
RobotIcon,
|
||||
ConnectorIcon,
|
||||
} from "@/components/icons/icons";
|
||||
import { getAuthDisabledSS, getCurrentUserSS } from "@/lib/userSS";
|
||||
import { redirect } from "next/navigation";
|
||||
@@ -52,310 +33,119 @@ export async function Layout({ children }: { children: React.ReactNode }) {
|
||||
<div>
|
||||
<Header user={user} />
|
||||
<div className="bg-gray-900 pt-8 pb-8 flex">
|
||||
<Sidebar
|
||||
title="Connector"
|
||||
collections={[
|
||||
{
|
||||
name: "Indexing",
|
||||
items: [
|
||||
{
|
||||
name: (
|
||||
<div className="flex">
|
||||
<NotebookIcon size={18} />
|
||||
<div className="ml-1">Status</div>
|
||||
</div>
|
||||
),
|
||||
link: "/admin/indexing/status",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Connector Settings",
|
||||
items: [
|
||||
{
|
||||
name: (
|
||||
<div className="flex">
|
||||
<SlackIcon size={16} />
|
||||
<div className="ml-1">Slack</div>
|
||||
</div>
|
||||
),
|
||||
link: "/admin/connectors/slack",
|
||||
},
|
||||
{
|
||||
name: (
|
||||
<div className="flex">
|
||||
<GithubIcon size={16} />
|
||||
<div className="ml-1">Github</div>
|
||||
</div>
|
||||
),
|
||||
link: "/admin/connectors/github",
|
||||
},
|
||||
{
|
||||
name: (
|
||||
<div className="flex">
|
||||
<GoogleDriveIcon size={16} />
|
||||
<div className="ml-1">Google Drive</div>
|
||||
</div>
|
||||
),
|
||||
link: "/admin/connectors/google-drive",
|
||||
},
|
||||
{
|
||||
name: (
|
||||
<div className="flex">
|
||||
<ConfluenceIcon size={16} />
|
||||
<div className="ml-1">Confluence</div>
|
||||
</div>
|
||||
),
|
||||
link: "/admin/connectors/confluence",
|
||||
},
|
||||
{
|
||||
name: (
|
||||
<div className="flex">
|
||||
<JiraIcon size={16} />
|
||||
<div className="ml-1">Jira</div>
|
||||
</div>
|
||||
),
|
||||
link: "/admin/connectors/jira",
|
||||
},
|
||||
{
|
||||
name: (
|
||||
<div className="flex">
|
||||
<LinearIcon size={16} />
|
||||
<div className="ml-1">Linear</div>
|
||||
</div>
|
||||
),
|
||||
link: "/admin/connectors/linear",
|
||||
},
|
||||
{
|
||||
name: (
|
||||
<div className="flex">
|
||||
<ProductboardIcon size={16} />
|
||||
<div className="ml-1">Productboard</div>
|
||||
</div>
|
||||
),
|
||||
link: "/admin/connectors/productboard",
|
||||
},
|
||||
{
|
||||
name: (
|
||||
<div className="flex">
|
||||
<SlabIcon size={16} />
|
||||
<div className="ml-1">Slab</div>
|
||||
</div>
|
||||
),
|
||||
link: "/admin/connectors/slab",
|
||||
},
|
||||
{
|
||||
name: (
|
||||
<div className="flex">
|
||||
<NotionIcon size={16} />
|
||||
<div className="ml-1">Notion</div>
|
||||
</div>
|
||||
),
|
||||
link: "/admin/connectors/notion",
|
||||
},
|
||||
{
|
||||
name: (
|
||||
<div className="flex">
|
||||
<GuruIcon size={16} />
|
||||
<div className="ml-1">Guru</div>
|
||||
</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 className="w-72">
|
||||
<Sidebar
|
||||
collections={[
|
||||
{
|
||||
name: "Connectors",
|
||||
items: [
|
||||
{
|
||||
name: (
|
||||
<div className="flex">
|
||||
<NotebookIcon size={18} />
|
||||
<div className="ml-1">Existing Connectors</div>
|
||||
</div>
|
||||
),
|
||||
link: "/admin/indexing/status",
|
||||
},
|
||||
{
|
||||
name: (
|
||||
<div className="flex">
|
||||
<ConnectorIcon size={18} />
|
||||
<div className="ml-1.5">Add Connector</div>
|
||||
</div>
|
||||
),
|
||||
link: "/admin/add-connector",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
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: "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",
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<div className="px-12 bg-gray-900 text-gray-100 w-full">{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@@ -4,19 +4,26 @@ import { Divider } from "@tremor/react";
|
||||
export function AdminPageTitle({
|
||||
icon,
|
||||
title,
|
||||
farRightElement,
|
||||
includeDivider = true,
|
||||
}: {
|
||||
icon: JSX.Element;
|
||||
title: string | JSX.Element;
|
||||
farRightElement?: JSX.Element;
|
||||
includeDivider?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<div className="dark">
|
||||
<div className="mb-4">
|
||||
<HealthCheckBanner />
|
||||
</div>
|
||||
<h1 className="text-3xl font-bold flex gap-x-2 mb-2">
|
||||
{icon} {title}
|
||||
</h1>
|
||||
<Divider />
|
||||
<div className="flex">
|
||||
<h1 className="text-3xl font-bold flex gap-x-2">
|
||||
{icon} {title}
|
||||
</h1>
|
||||
{farRightElement && <div className="ml-auto">{farRightElement}</div>}
|
||||
</div>
|
||||
{includeDivider && <Divider />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@@ -1,13 +1,11 @@
|
||||
import { getSourceMetadata } from "@/components/source";
|
||||
import { getSourceMetadata } from "@/lib/sources";
|
||||
import {
|
||||
ConfluenceConfig,
|
||||
Connector,
|
||||
ConnectorIndexingStatus,
|
||||
GithubConfig,
|
||||
GoogleDriveConfig,
|
||||
JiraConfig,
|
||||
SlackConfig,
|
||||
WebConfig,
|
||||
ZulipConfig,
|
||||
} from "@/lib/types";
|
||||
import Link from "next/link";
|
||||
@@ -96,7 +94,7 @@ export const ConnectorTitle = ({
|
||||
</>
|
||||
);
|
||||
return (
|
||||
<div>
|
||||
<div className="my-auto">
|
||||
{isLink ? (
|
||||
<Link
|
||||
className={mainSectionClassName}
|
||||
@@ -107,7 +105,7 @@ export const ConnectorTitle = ({
|
||||
) : (
|
||||
<div className={mainSectionClassName}>{mainDisplay}</div>
|
||||
)}
|
||||
{showMetadata && (
|
||||
{showMetadata && additionalMetadata.size > 0 && (
|
||||
<div className="text-xs text-gray-300 mt-1">
|
||||
{Array.from(additionalMetadata.entries()).map(([key, value]) => {
|
||||
return (
|
||||
|
@@ -13,13 +13,12 @@ interface Collection {
|
||||
}
|
||||
|
||||
interface SidebarProps {
|
||||
title: string;
|
||||
collections: Collection[];
|
||||
}
|
||||
|
||||
export const Sidebar: React.FC<SidebarProps> = ({ collections }) => {
|
||||
export function Sidebar({ collections }: SidebarProps) {
|
||||
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">
|
||||
{collections.map((collection, collectionInd) => (
|
||||
<div key={collectionInd}>
|
||||
@@ -40,4 +39,4 @@ export const Sidebar: React.FC<SidebarProps> = ({ collections }) => {
|
||||
</nav>
|
||||
</aside>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
@@ -35,6 +35,7 @@ import {
|
||||
FiBookmark,
|
||||
FiCpu,
|
||||
FiInfo,
|
||||
FiUploadCloud,
|
||||
} from "react-icons/fi";
|
||||
import { SiBookstack } from "react-icons/si";
|
||||
import Image from "next/image";
|
||||
@@ -289,6 +290,13 @@ export const RobotIcon = ({
|
||||
return <FaRobot size={size} className={className} />;
|
||||
};
|
||||
|
||||
export const ConnectorIcon = ({
|
||||
size = 16,
|
||||
className = defaultTailwindCSS,
|
||||
}: IconProps) => {
|
||||
return <FiUploadCloud size={size} className={className} />;
|
||||
};
|
||||
|
||||
//
|
||||
// COMPANY LOGOS
|
||||
//
|
||||
|
@@ -1,11 +1,11 @@
|
||||
import { DanswerDocument } from "@/lib/search/interfaces";
|
||||
import { DocumentFeedbackBlock } from "./DocumentFeedbackBlock";
|
||||
import { getSourceIcon } from "../source";
|
||||
import { useState } from "react";
|
||||
import { PopupSpec } from "../admin/connectors/Popup";
|
||||
import { HoverPopup } from "@/components/HoverPopup";
|
||||
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 = (
|
||||
matchHighlights: string[],
|
||||
@@ -186,7 +186,7 @@ export const DocumentDisplay = ({
|
||||
target="_blank"
|
||||
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">
|
||||
{document.semantic_identifier || document.document_id}
|
||||
</p>
|
||||
|
@@ -1,35 +1,14 @@
|
||||
import React from "react";
|
||||
import { getSourceIcon } from "../../source";
|
||||
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 { HoverPopup } from "../../HoverPopup";
|
||||
import { FiBook, FiBookmark, FiFilter, FiMap, FiX } from "react-icons/fi";
|
||||
import { DateRangeSelector } from "../DateRangeSelector";
|
||||
import { DateRangePickerValue } from "@tremor/react";
|
||||
import { FilterDropdown } from "./FilterDropdown";
|
||||
|
||||
const sources: Source[] = [
|
||||
{ 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" },
|
||||
];
|
||||
import { listSourceMetadata } from "@/lib/sources";
|
||||
import { SourceIcon } from "@/components/SourceIcon";
|
||||
|
||||
const SectionTitle = ({ children }: { children: string }) => (
|
||||
<div className="font-medium text-sm flex">{children}</div>
|
||||
@@ -40,8 +19,8 @@ interface SourceSelectorProps {
|
||||
setTimeRange: React.Dispatch<
|
||||
React.SetStateAction<DateRangePickerValue | null>
|
||||
>;
|
||||
selectedSources: Source[];
|
||||
setSelectedSources: React.Dispatch<React.SetStateAction<Source[]>>;
|
||||
selectedSources: SourceMetadata[];
|
||||
setSelectedSources: React.Dispatch<React.SetStateAction<SourceMetadata[]>>;
|
||||
selectedDocumentSets: string[];
|
||||
setSelectedDocumentSets: React.Dispatch<React.SetStateAction<string[]>>;
|
||||
availableDocumentSets: DocumentSet[];
|
||||
@@ -58,8 +37,8 @@ export function SourceSelector({
|
||||
availableDocumentSets,
|
||||
existingSources,
|
||||
}: SourceSelectorProps) {
|
||||
const handleSelect = (source: Source) => {
|
||||
setSelectedSources((prev: Source[]) => {
|
||||
const handleSelect = (source: SourceMetadata) => {
|
||||
setSelectedSources((prev: SourceMetadata[]) => {
|
||||
if (prev.includes(source)) {
|
||||
return prev.filter((s) => s.internalName !== source.internalName);
|
||||
} else {
|
||||
@@ -96,7 +75,7 @@ export function SourceSelector({
|
||||
<div className="mt-4">
|
||||
<SectionTitle>Sources</SectionTitle>
|
||||
<div className="px-1">
|
||||
{sources
|
||||
{listSourceMetadata()
|
||||
.filter((source) => existingSources.includes(source.internalName))
|
||||
.map((source) => (
|
||||
<div
|
||||
@@ -110,7 +89,7 @@ export function SourceSelector({
|
||||
}
|
||||
onClick={() => handleSelect(source)}
|
||||
>
|
||||
{getSourceIcon(source.internalName, 16)}
|
||||
<SourceIcon sourceType={source.internalName} iconSize={16} />
|
||||
<span className="ml-2 text-sm text-gray-200">
|
||||
{source.displayName}
|
||||
</span>
|
||||
@@ -201,8 +180,8 @@ export function HorizontalFilters({
|
||||
availableDocumentSets,
|
||||
existingSources,
|
||||
}: SourceSelectorProps) {
|
||||
const handleSourceSelect = (source: Source) => {
|
||||
setSelectedSources((prev: Source[]) => {
|
||||
const handleSourceSelect = (source: SourceMetadata) => {
|
||||
setSelectedSources((prev: SourceMetadata[]) => {
|
||||
const prevSourceNames = prev.map((source) => source.internalName);
|
||||
if (prevSourceNames.includes(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)
|
||||
);
|
||||
|
||||
@@ -239,8 +219,7 @@ export function HorizontalFilters({
|
||||
key: source.displayName,
|
||||
display: (
|
||||
<>
|
||||
{" "}
|
||||
{getSourceIcon(source.internalName, 16)}
|
||||
<SourceIcon sourceType={source.internalName} iconSize={16} />
|
||||
<span className="ml-2 text-sm text-gray-200">
|
||||
{source.displayName}
|
||||
</span>
|
||||
@@ -251,7 +230,7 @@ export function HorizontalFilters({
|
||||
selected={selectedSources.map((source) => source.displayName)}
|
||||
handleSelect={(option) =>
|
||||
handleSourceSelect(
|
||||
sources.find((source) => source.displayName === option.key)!
|
||||
allSources.find((source) => source.displayName === option.key)!
|
||||
)
|
||||
}
|
||||
icon={
|
||||
@@ -305,7 +284,7 @@ export function HorizontalFilters({
|
||||
onClick={() => handleSourceSelect(source)}
|
||||
>
|
||||
<>
|
||||
{getSourceIcon(source.internalName, 16)}
|
||||
<SourceIcon sourceType={source.internalName} iconSize={16} />
|
||||
<span className="ml-2 text-sm text-gray-400">
|
||||
{source.displayName}
|
||||
</span>
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import { Quote } from "@/lib/search/interfaces";
|
||||
import { ResponseSection, StatusOptions } from "./ResponseSection";
|
||||
import { getSourceIcon } from "@/components/source";
|
||||
import { CheckmarkIcon, CopyIcon } from "@/components/icons/icons";
|
||||
import { useState } from "react";
|
||||
import { SourceIcon } from "@/components/SourceIcon";
|
||||
|
||||
const QuoteDisplay = ({ quoteInfo }: { quoteInfo: Quote }) => {
|
||||
const [detailIsOpen, setDetailIsOpen] = useState(false);
|
||||
@@ -54,7 +54,7 @@ const QuoteDisplay = ({ quoteInfo }: { quoteInfo: Quote }) => {
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{getSourceIcon(quoteInfo.source_type, 20)}
|
||||
<SourceIcon sourceType={quoteInfo.source_type} iconSize={20} />
|
||||
<p className="truncate break-all ml-2 mr-2">
|
||||
{quoteInfo.semantic_identifier || quoteInfo.document_id}
|
||||
</p>
|
||||
|
@@ -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;
|
||||
};
|
@@ -7,7 +7,7 @@ import useSWR, { mutate, useSWRConfig } from "swr";
|
||||
import { fetcher } from "./fetcher";
|
||||
import { useState } from "react";
|
||||
import { DateRangePickerValue } from "@tremor/react";
|
||||
import { Source } from "./search/interfaces";
|
||||
import { SourceMetadata } from "./search/interfaces";
|
||||
|
||||
const CREDENTIAL_URL = "/api/manage/admin/credential";
|
||||
|
||||
@@ -77,7 +77,7 @@ export const useTimeRange = (initialValue?: DateRangePickerValue) => {
|
||||
|
||||
export function useFilters() {
|
||||
const [timeRange, setTimeRange] = useTimeRange();
|
||||
const [selectedSources, setSelectedSources] = useState<Source[]>([]);
|
||||
const [selectedSources, setSelectedSources] = useState<SourceMetadata[]>([]);
|
||||
const [selectedDocumentSets, setSelectedDocumentSets] = useState<string[]>(
|
||||
[]
|
||||
);
|
||||
|
@@ -74,9 +74,18 @@ export interface SearchResponse {
|
||||
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;
|
||||
category: SourceCategory;
|
||||
shortDescription?: string;
|
||||
internalName: ValidSources;
|
||||
adminUrl: string;
|
||||
}
|
||||
|
||||
export interface SearchDefaultOverrides {
|
||||
@@ -93,7 +102,7 @@ export interface Filters {
|
||||
export interface SearchRequestArgs {
|
||||
query: string;
|
||||
chatSessionId: number;
|
||||
sources: Source[];
|
||||
sources: SourceMetadata[];
|
||||
documentSets: string[];
|
||||
timeRange: DateRangePickerValue | null;
|
||||
updateCurrentAnswer: (val: string) => void;
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import { Filters, Source } from "./interfaces";
|
||||
import { Filters, SourceMetadata } from "./interfaces";
|
||||
import { DateRangePickerValue } from "@tremor/react";
|
||||
|
||||
export const buildFilters = (
|
||||
sources: Source[],
|
||||
sources: SourceMetadata[],
|
||||
documentSets: string[],
|
||||
timeRange: DateRangePickerValue | null
|
||||
): Filters => {
|
||||
|
164
web/src/lib/sources.ts
Normal file
164
web/src/lib/sources.ts
Normal 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;
|
||||
}
|
Reference in New Issue
Block a user