diff --git a/backend/danswer/background/update.py b/backend/danswer/background/update.py index 5ba1fbde9..ac8e876d8 100755 --- a/backend/danswer/background/update.py +++ b/backend/danswer/background/update.py @@ -2,6 +2,8 @@ import time from typing import cast from danswer.configs.constants import DocumentSource +from danswer.connectors.factory import build_connector +from danswer.connectors.models import InputType from danswer.connectors.slack.config import get_pull_frequency from danswer.connectors.slack.pull import PeriodicSlackLoader from danswer.connectors.web.pull import WebLoader @@ -35,6 +37,8 @@ def run_update() -> None: current_time = int(time.time()) # Slack + # TODO (chris): make Slack use the same approach as other connectors / + # make other connectors periodic try: pull_frequency = get_pull_frequency() except ConfigNotFoundError: @@ -56,17 +60,15 @@ def run_update() -> None: indexing_pipeline(doc_batch) dynamic_config_store.store(last_slack_pull_key, current_time) - # Web # TODO (chris): make this more efficient / in a single transaction to # prevent race conditions across multiple background jobs. For now, # this assumes we only ever run a single background job at a time - # TODO (chris): make this generic for all pull connectors (not just web) not_started_index_attempts = fetch_index_attempts( - sources=[DocumentSource.WEB], statuses=[IndexingStatus.NOT_STARTED] + input_types=[InputType.PULL], statuses=[IndexingStatus.NOT_STARTED] ) for not_started_index_attempt in not_started_index_attempts: logger.info( - "Attempting to index website with IndexAttempt id: " + "Attempting to index with IndexAttempt id: " f"{not_started_index_attempt.id}, source: " f"{not_started_index_attempt.source}, input_type: " f"{not_started_index_attempt.input_type}, and connector_specific_config: " @@ -78,17 +80,25 @@ def run_update() -> None: ) error_msg = None - base_url = not_started_index_attempt.connector_specific_config["url"] try: # TODO (chris): spawn processes to parallelize / take advantage of # multiple cores + implement retries + connector = build_connector( + source=not_started_index_attempt.source, + input_type=InputType.PULL, + connector_specific_config=not_started_index_attempt.connector_specific_config, + ) + document_ids: list[str] = [] - for doc_batch in WebLoader(base_url=base_url).load(): + for doc_batch in connector.load(): chunks = indexing_pipeline(doc_batch) document_ids.extend([chunk.source_document.id for chunk in chunks]) except Exception as e: logger.exception( - "Failed to index website with url %s due to: %s", base_url, e + "Failed to index for source %s with config %s due to: %s", + not_started_index_attempt.source, + not_started_index_attempt.connector_specific_config, + e, ) error_msg = str(e) diff --git a/backend/danswer/connectors/factory.py b/backend/danswer/connectors/factory.py new file mode 100644 index 000000000..dd3a20106 --- /dev/null +++ b/backend/danswer/connectors/factory.py @@ -0,0 +1,40 @@ +from typing import Any + +from danswer.configs.constants import DocumentSource +from danswer.connectors.github.batch import BatchGithubLoader +from danswer.connectors.google_drive.batch import BatchGoogleDriveLoader +from danswer.connectors.interfaces import PullLoader +from danswer.connectors.interfaces import RangePullLoader +from danswer.connectors.models import InputType +from danswer.connectors.slack.batch import BatchSlackLoader +from danswer.connectors.slack.pull import PeriodicSlackLoader +from danswer.connectors.web.pull import WebLoader + + +class ConnectorMissingException(Exception): + pass + + +def build_connector( + source: DocumentSource, + input_type: InputType, + connector_specific_config: dict[str, Any], +) -> PullLoader | RangePullLoader: + if source == DocumentSource.SLACK: + if input_type == InputType.PULL: + return PeriodicSlackLoader(**connector_specific_config) + if input_type == InputType.LOAD_STATE: + return BatchSlackLoader(**connector_specific_config) + elif source == DocumentSource.GOOGLE_DRIVE: + if input_type == InputType.PULL: + return BatchGoogleDriveLoader(**connector_specific_config) + elif source == DocumentSource.GITHUB: + if input_type == InputType.PULL: + return BatchGithubLoader(**connector_specific_config) + elif source == DocumentSource.WEB: + if input_type == InputType.PULL: + return WebLoader(**connector_specific_config) + + raise ConnectorMissingException( + f"Connector not found for source={source}, input_type={input_type}" + ) diff --git a/backend/danswer/connectors/interfaces.py b/backend/danswer/connectors/interfaces.py index d10463b91..f2977e0fc 100644 --- a/backend/danswer/connectors/interfaces.py +++ b/backend/danswer/connectors/interfaces.py @@ -9,6 +9,7 @@ from danswer.connectors.models import Document SecondsSinceUnixEpoch = float +# TODO (chris): rename from Loader -> Connector class PullLoader: @abc.abstractmethod def load(self) -> Generator[List[Document], None, None]: diff --git a/backend/danswer/db/index_attempt.py b/backend/danswer/db/index_attempt.py index 05bbe9717..dd4c985b2 100644 --- a/backend/danswer/db/index_attempt.py +++ b/backend/danswer/db/index_attempt.py @@ -1,4 +1,5 @@ from danswer.configs.constants import DocumentSource +from danswer.connectors.models import InputType from danswer.db.engine import build_engine from danswer.db.models import IndexAttempt from danswer.db.models import IndexingStatus @@ -20,6 +21,7 @@ def fetch_index_attempts( *, sources: list[DocumentSource] | None = None, statuses: list[IndexingStatus] | None = None, + input_types: list[InputType] | None = None, ) -> list[IndexAttempt]: with Session(build_engine(), future=True, expire_on_commit=False) as session: stmt = select(IndexAttempt) @@ -27,6 +29,8 @@ def fetch_index_attempts( stmt = stmt.where(IndexAttempt.source.in_(sources)) if statuses: stmt = stmt.where(IndexAttempt.status.in_(statuses)) + if input_types: + stmt = stmt.where(IndexAttempt.input_type.in_(input_types)) results = session.scalars(stmt) return list(results.all()) diff --git a/backend/danswer/server/admin.py b/backend/danswer/server/admin.py index eddcef82f..a43ec5cb8 100644 --- a/backend/danswer/server/admin.py +++ b/backend/danswer/server/admin.py @@ -1,6 +1,9 @@ +from typing import Any + from danswer.auth.users import current_admin_user from danswer.configs.constants import DocumentSource from danswer.configs.constants import NO_AUTH_USER +from danswer.connectors.factory import build_connector from danswer.connectors.google_drive.connector_auth import get_auth_url from danswer.connectors.google_drive.connector_auth import get_drive_tokens from danswer.connectors.google_drive.connector_auth import save_access_tokens @@ -19,11 +22,10 @@ from danswer.server.models import AuthStatus from danswer.server.models import AuthUrl from danswer.server.models import GDriveCallback from danswer.server.models import IndexAttemptSnapshot -from danswer.server.models import ListWebsiteIndexAttemptsResponse -from danswer.server.models import WebIndexAttemptRequest from danswer.utils.logging import setup_logger from fastapi import APIRouter from fastapi import Depends +from pydantic import BaseModel router = APIRouter(prefix="/admin") @@ -67,29 +69,51 @@ def modify_slack_config( update_slack_config(slack_config) -@router.post("/connectors/web/index-attempt", status_code=201) -def index_website( - web_index_attempt_request: WebIndexAttemptRequest, +class IndexAttemptRequest(BaseModel): + input_type: InputType = InputType.PULL + connector_specific_config: dict[str, Any] + + +@router.post("/connectors/{source}/index-attempt", status_code=201) +def index( + source: DocumentSource, + index_attempt_request: IndexAttemptRequest, _: User = Depends(current_admin_user), ) -> None: - index_request = IndexAttempt( - source=DocumentSource.WEB, - input_type=InputType.PULL, - connector_specific_config={"url": web_index_attempt_request.url}, - status=IndexingStatus.NOT_STARTED, + # validate that the connector specified by the source / input_type combination + # exists AND that the connector_specific_config is valid for that connector type + build_connector( + source=source, + input_type=index_attempt_request.input_type, + connector_specific_config=index_attempt_request.connector_specific_config, + ) + + # once validated, insert the index attempt into the database where it will + # get picked up by a background job + insert_index_attempt( + index_attempt=IndexAttempt( + source=source, + input_type=index_attempt_request.input_type, + connector_specific_config=index_attempt_request.connector_specific_config, + status=IndexingStatus.NOT_STARTED, + ) ) - insert_index_attempt(index_request) -@router.get("/connectors/web/index-attempt") -def list_website_index_attempts( +class ListIndexAttemptsResponse(BaseModel): + index_attempts: list[IndexAttemptSnapshot] + + +@router.get("/connectors/{source}/index-attempt") +def list_index_attempts( + source: DocumentSource, _: User = Depends(current_admin_user), -) -> ListWebsiteIndexAttemptsResponse: - index_attempts = fetch_index_attempts(sources=[DocumentSource.WEB]) - return ListWebsiteIndexAttemptsResponse( +) -> ListIndexAttemptsResponse: + index_attempts = fetch_index_attempts(sources=[source]) + return ListIndexAttemptsResponse( index_attempts=[ IndexAttemptSnapshot( - url=index_attempt.connector_specific_config["url"], + connector_specific_config=index_attempt.connector_specific_config, status=index_attempt.status, time_created=index_attempt.time_created, time_updated=index_attempt.time_updated, diff --git a/backend/danswer/server/models.py b/backend/danswer/server/models.py index 530ed995c..f2e52bc50 100644 --- a/backend/danswer/server/models.py +++ b/backend/danswer/server/models.py @@ -1,4 +1,5 @@ from datetime import datetime +from typing import Any from danswer.datastores.interfaces import DatastoreFilter from danswer.db.models import IndexingStatus @@ -49,12 +50,8 @@ class UserByEmail(BaseModel): user_email: str -class WebIndexAttemptRequest(BaseModel): - url: str - - class IndexAttemptSnapshot(BaseModel): - url: str + connector_specific_config: dict[str, Any] status: IndexingStatus time_created: datetime time_updated: datetime diff --git a/web/src/app/admin/connectors/github/page.tsx b/web/src/app/admin/connectors/github/page.tsx new file mode 100644 index 000000000..1504ed2be --- /dev/null +++ b/web/src/app/admin/connectors/github/page.tsx @@ -0,0 +1,58 @@ +"use client"; + +import * as Yup from "yup"; +import { IndexForm } from "@/components/admin/connectors/Form"; +import { + ConnectorStatus, + ReccuringConnectorStatus, +} from "@/components/admin/connectors/RecurringConnectorStatus"; +import { GithubIcon } from "@/components/icons/icons"; +import { TextFormField } from "@/components/admin/connectors/Field"; + +export default function Page() { + return ( +
+
+ +

Github PRs

+
+ +

+ Status +

+ + + {/* TODO: make this periodic */} +

+ Request Indexing +

+
+ + + + + } + validationSchema={Yup.object().shape({ + repo_owner: Yup.string().required( + "Please enter the owner of the repo scrape e.g. danswer-ai" + ), + repo_name: Yup.string().required( + "Please enter the name of the repo scrape e.g. danswer " + ), + })} + initialValues={{ + repo_owner: "", + repo_name: "", + }} + onSubmit={(isSuccess) => console.log(isSuccess)} + /> +
+
+ ); +} diff --git a/web/src/app/admin/connectors/interfaces.ts b/web/src/app/admin/connectors/interfaces.ts new file mode 100644 index 000000000..fa32867e9 --- /dev/null +++ b/web/src/app/admin/connectors/interfaces.ts @@ -0,0 +1,17 @@ +export interface SlackConfig { + slack_bot_token: string; + workspace_id: string; + pull_frequency: number; +} + +export interface IndexAttempt { + connector_specific_config: { [key: string]: any }; + status: "success" | "failure" | "in_progress" | "not_started"; + time_created: string; + time_updated: string; + docs_indexed: number; +} + +export interface ListIndexingResponse { + index_attempts: IndexAttempt[]; +} diff --git a/web/src/app/admin/connectors/slack/SlackForm.tsx b/web/src/app/admin/connectors/slack/SlackForm.tsx index 2b1b1adff..9dc23234c 100644 --- a/web/src/app/admin/connectors/slack/SlackForm.tsx +++ b/web/src/app/admin/connectors/slack/SlackForm.tsx @@ -3,7 +3,7 @@ import { Formik, Form, FormikHelpers } from "formik"; import * as Yup from "yup"; import { Popup } from "../../../../components/admin/connectors/Popup"; import { TextFormField } from "../../../../components/admin/connectors/Field"; -import { SlackConfig } from "./interfaces"; +import { SlackConfig } from "../interfaces"; const validationSchema = Yup.object().shape({ slack_bot_token: Yup.string().required("Please enter your Slack Bot Token"), diff --git a/web/src/app/admin/connectors/slack/interfaces.ts b/web/src/app/admin/connectors/slack/interfaces.ts deleted file mode 100644 index daa89952d..000000000 --- a/web/src/app/admin/connectors/slack/interfaces.ts +++ /dev/null @@ -1,11 +0,0 @@ -export interface SlackConfig { - slack_bot_token: string; - workspace_id: string; - pull_frequency: number; -} - -// interface SlackIndexAttempt {} - -// interface ListSlackIndexingResponse { -// index_attempts: SlackIndexAttempt[]; -// } diff --git a/web/src/app/admin/connectors/slack/page.tsx b/web/src/app/admin/connectors/slack/page.tsx index d24e64d67..7a8727cc5 100644 --- a/web/src/app/admin/connectors/slack/page.tsx +++ b/web/src/app/admin/connectors/slack/page.tsx @@ -8,7 +8,7 @@ import { SlackForm } from "@/app/admin/connectors/slack/SlackForm"; import { SlackIcon } from "@/components/icons/icons"; import { fetcher } from "@/lib/fetcher"; import useSWR, { useSWRConfig } from "swr"; -import { SlackConfig } from "./interfaces"; +import { SlackConfig } from "../interfaces"; import { ThinkingAnimation } from "@/components/Thinking"; const MainSection = () => { @@ -47,6 +47,7 @@ const MainSection = () => { ? ConnectorStatus.Running : ConnectorStatus.NotSetup } + source="slack" /> } diff --git a/web/src/app/admin/connectors/web/WebIndexForm.tsx b/web/src/app/admin/connectors/web/WebIndexForm.tsx deleted file mode 100644 index 3e1ef532e..000000000 --- a/web/src/app/admin/connectors/web/WebIndexForm.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import React, { useState } from "react"; -import { Formik, Form, FormikHelpers } from "formik"; -import * as Yup from "yup"; -import { Popup } from "../../../../components/admin/connectors/Popup"; -import { TextFormField } from "../../../../components/admin/connectors/Field"; - -interface FormData { - url: string; -} - -const validationSchema = Yup.object().shape({ - url: Yup.string().required( - "Please enter the website URL to scrape e.g. https://docs.github.com/en/actions" - ), -}); - -const handleSubmit = async ( - values: FormData, - { setSubmitting }: FormikHelpers, - setPopup: ( - popup: { message: string; type: "success" | "error" } | null - ) => void -): Promise => { - setSubmitting(true); - let isSuccess = false; - try { - const response = await fetch("/api/admin/connectors/web/index-attempt", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(values), - }); - - if (response.ok) { - isSuccess = true; - setPopup({ message: "Success!", type: "success" }); - } else { - const errorData = await response.json(); - setPopup({ message: `Error: ${errorData.detail}`, type: "error" }); - } - } catch (error) { - setPopup({ message: `Error: ${error}`, type: "error" }); - } finally { - setSubmitting(false); - setTimeout(() => { - setPopup(null); - }, 3000); - return isSuccess; - } -}; - -interface SlackFormProps { - onSubmit: (isSuccess: boolean) => void; -} - -export const WebIndexForm: React.FC = ({ onSubmit }) => { - const [popup, setPopup] = useState<{ - message: string; - type: "success" | "error"; - } | null>(null); - - return ( - <> - {popup && } - - handleSubmit(values, formikHelpers, setPopup).then((isSuccess) => - onSubmit(isSuccess) - ) - } - > - {({ isSubmitting }) => ( -
- - - - )} -
- - ); -}; diff --git a/web/src/app/admin/connectors/web/page.tsx b/web/src/app/admin/connectors/web/page.tsx index 99247652c..3fde88e69 100644 --- a/web/src/app/admin/connectors/web/page.tsx +++ b/web/src/app/admin/connectors/web/page.tsx @@ -1,25 +1,16 @@ "use client"; import useSWR, { useSWRConfig } from "swr"; +import * as Yup from "yup"; import { BasicTable } from "@/components/admin/connectors/BasicTable"; -import { WebIndexForm } from "@/app/admin/connectors/web/WebIndexForm"; import { ThinkingAnimation } from "@/components/Thinking"; import { timeAgo } from "@/lib/time"; import { GlobeIcon } from "@/components/icons/icons"; import { fetcher } from "@/lib/fetcher"; - -interface WebsiteIndexAttempt { - url: string; - status: "success" | "failure" | "in_progress" | "not_started"; - time_created: string; - time_updated: string; - docs_indexed: number; -} - -interface ListWebIndexingResponse { - index_attempts: WebsiteIndexAttempt[]; -} +import { IndexAttempt, ListIndexingResponse } from "../interfaces"; +import { IndexForm } from "@/components/admin/connectors/Form"; +import { TextFormField } from "@/components/admin/connectors/Field"; const COLUMNS = [ { header: "Base URL", key: "url" }, @@ -30,28 +21,29 @@ const COLUMNS = [ export default function Web() { const { mutate } = useSWRConfig(); - const { data, isLoading, error } = useSWR( + const { data, isLoading, error } = useSWR( "/api/admin/connectors/web/index-attempt", fetcher ); - const urlToLatestIndexAttempt = new Map(); + const urlToLatestIndexAttempt = new Map(); const urlToLatestIndexSuccess = new Map(); data?.index_attempts?.forEach((indexAttempt) => { - const latestIndexAttempt = urlToLatestIndexAttempt.get(indexAttempt.url); + const url = indexAttempt.connector_specific_config.base_url; + const latestIndexAttempt = urlToLatestIndexAttempt.get(url); if ( !latestIndexAttempt || indexAttempt.time_created > latestIndexAttempt.time_created ) { - urlToLatestIndexAttempt.set(indexAttempt.url, indexAttempt); + urlToLatestIndexAttempt.set(url, indexAttempt); } - const latestIndexSuccess = urlToLatestIndexSuccess.get(indexAttempt.url); + const latestIndexSuccess = urlToLatestIndexSuccess.get(url); if ( indexAttempt.status === "success" && (!latestIndexSuccess || indexAttempt.time_updated > latestIndexSuccess) ) { - urlToLatestIndexSuccess.set(indexAttempt.url, indexAttempt.time_updated); + urlToLatestIndexSuccess.set(url, indexAttempt.time_updated); } }); @@ -65,7 +57,15 @@ export default function Web() { Request Indexing
- } + validationSchema={Yup.object().shape({ + base_url: Yup.string().required( + "Please enter the website URL to scrape e.g. https://docs.github.com/en/actions" + ), + })} + initialValues={{ base_url: "" }} onSubmit={(success) => { if (success) { mutate("/api/admin/connectors/web/index-attempt"); @@ -87,22 +87,21 @@ export default function Web() { data={ urlToLatestIndexAttempt.size > 0 ? Array.from(urlToLatestIndexAttempt.values()).map( - (indexAttempt) => ({ - ...indexAttempt, - indexed_at: - timeAgo(urlToLatestIndexSuccess.get(indexAttempt.url)) || - "-", - docs_indexed: indexAttempt.docs_indexed || "-", - url: ( - - {indexAttempt.url} - - ), - }) + (indexAttempt) => { + const url = indexAttempt.connector_specific_config + .base_url as string; + return { + indexed_at: + timeAgo(urlToLatestIndexSuccess.get(url)) || "-", + docs_indexed: indexAttempt.docs_indexed || "-", + url: ( + + {url} + + ), + status: indexAttempt.status, + }; + } ) : [] } diff --git a/web/src/app/admin/layout.tsx b/web/src/app/admin/layout.tsx index 24eaea3c5..064b9af31 100644 --- a/web/src/app/admin/layout.tsx +++ b/web/src/app/admin/layout.tsx @@ -1,6 +1,6 @@ import { Header } from "@/components/Header"; import { Sidebar } from "@/components/admin/connectors/Sidebar"; -import { GlobeIcon, SlackIcon } from "@/components/icons/icons"; +import { GithubIcon, GlobeIcon, SlackIcon } from "@/components/icons/icons"; import { getCurrentUserSS } from "@/lib/userSS"; import { redirect } from "next/navigation"; @@ -45,6 +45,15 @@ export default async function AdminLayout({ ), link: "/admin/connectors/web", }, + { + name: ( +
+ +
Github
+
+ ), + link: "/admin/connectors/github", + }, ], }, ]} diff --git a/web/src/components/admin/connectors/Form.tsx b/web/src/components/admin/connectors/Form.tsx new file mode 100644 index 000000000..2daba47a0 --- /dev/null +++ b/web/src/components/admin/connectors/Form.tsx @@ -0,0 +1,106 @@ +import React, { useState } from "react"; +import { Formik, Form, FormikHelpers } from "formik"; +import * as Yup from "yup"; +import { Popup } from "./Popup"; + +type ValidSources = "web" | "github"; + +const handleSubmit = async ( + source: ValidSources, + values: Yup.AnyObject, + { setSubmitting }: FormikHelpers, + setPopup: ( + popup: { message: string; type: "success" | "error" } | null + ) => void +): Promise => { + setSubmitting(true); + let isSuccess = false; + try { + const response = await fetch( + `/api/admin/connectors/${source}/index-attempt`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ connector_specific_config: values }), + } + ); + + if (response.ok) { + isSuccess = true; + setPopup({ message: "Success!", type: "success" }); + } else { + const errorData = await response.json(); + setPopup({ message: `Error: ${errorData.detail}`, type: "error" }); + } + } catch (error) { + setPopup({ message: `Error: ${error}`, type: "error" }); + } finally { + setSubmitting(false); + setTimeout(() => { + setPopup(null); + }, 3000); + return isSuccess; + } +}; + +interface IndexFormProps { + source: ValidSources; + formBody: JSX.Element | null; + validationSchema: Yup.ObjectSchema; + initialValues: YupObjectType; + onSubmit: (isSuccess: boolean) => void; + additionalNonFormValues?: Yup.AnyObject; +} + +export function IndexForm({ + source, + formBody, + validationSchema, + initialValues, + onSubmit, + additionalNonFormValues = {}, +}: IndexFormProps): JSX.Element { + const [popup, setPopup] = useState<{ + message: string; + type: "success" | "error"; + } | null>(null); + + return ( + <> + {popup && } + { + handleSubmit( + source, + { ...values, ...additionalNonFormValues }, + formikHelpers as FormikHelpers, + setPopup + ).then((isSuccess) => onSubmit(isSuccess)); + }} + > + {({ isSubmitting }) => ( +
+ {formBody} +
+ +
+
+ )} +
+ + ); +} diff --git a/web/src/components/admin/connectors/RecurringConnectorStatus.tsx b/web/src/components/admin/connectors/RecurringConnectorStatus.tsx index 02acb0b62..30b34a852 100644 --- a/web/src/components/admin/connectors/RecurringConnectorStatus.tsx +++ b/web/src/components/admin/connectors/RecurringConnectorStatus.tsx @@ -1,4 +1,10 @@ +"use client"; + +import { ListIndexingResponse } from "@/app/admin/connectors/interfaces"; +import { fetcher } from "@/lib/fetcher"; +import { timeAgo } from "@/lib/time"; import { CheckCircle, MinusCircle } from "@phosphor-icons/react"; +import useSWR from "swr"; export enum ConnectorStatus { Running = "Running", @@ -7,16 +13,39 @@ export enum ConnectorStatus { interface ReccuringConnectorStatusProps { status: ConnectorStatus; + source: string; } export const ReccuringConnectorStatus = ({ status, + source, }: ReccuringConnectorStatusProps) => { + const { data } = useSWR( + `/api/admin/connectors/${source}/index-attempt`, + fetcher + ); + + const lastSuccessfulAttempt = data?.index_attempts + .filter((attempt) => attempt.status === "success") + .sort((a, b) => { + if (a.time_updated === b.time_updated) { + return 0; + } + return a.time_updated > b.time_updated ? -1 : 1; + })[0]; + if (status === ConnectorStatus.Running) { return ( -
- -

{status}

+
+
+ +

{status}

+
+ {lastSuccessfulAttempt && ( +

+ Last updated {timeAgo(lastSuccessfulAttempt.time_updated)} +

+ )}
); } diff --git a/web/src/components/icons/icons.tsx b/web/src/components/icons/icons.tsx index f7b576bb8..8c232e62c 100644 --- a/web/src/components/icons/icons.tsx +++ b/web/src/components/icons/icons.tsx @@ -1,6 +1,6 @@ "use client"; -import { Globe, SlackLogo } from "@phosphor-icons/react"; +import { Globe, SlackLogo, GithubLogo } from "@phosphor-icons/react"; interface IconProps { size?: string; @@ -22,3 +22,10 @@ export const SlackIcon = ({ }: IconProps) => { return ; }; + +export const GithubIcon = ({ + size = "16", + className = defaultTailwindCSS, +}: IconProps) => { + return ; +}; diff --git a/web/src/components/search/SearchResultsDisplay.tsx b/web/src/components/search/SearchResultsDisplay.tsx index 7fb981616..8a2e3d793 100644 --- a/web/src/components/search/SearchResultsDisplay.tsx +++ b/web/src/components/search/SearchResultsDisplay.tsx @@ -3,6 +3,7 @@ import { Globe, SlackLogo, GoogleDriveLogo } from "@phosphor-icons/react"; import "tailwindcss/tailwind.css"; import { Quote, Document } from "./types"; import { ThinkingAnimation } from "../Thinking"; +import { GithubIcon } from "../icons/icons"; interface SearchResultsDisplayProps { answer: string | null; @@ -22,6 +23,8 @@ const getSourceIcon = (sourceType: string) => { return ; case "google_drive": return ; + case "github": + return ; default: return null; } @@ -102,7 +105,7 @@ export const SearchResultsDisplay: React.FC = ({ > {getSourceIcon(doc.source_type)}

- {doc.semantic_name || doc.document_id} + {doc.semantic_identifier || doc.document_id}

{doc.blurb}

diff --git a/web/src/components/search/types.tsx b/web/src/components/search/types.tsx index 928d006ee..b5f0213dd 100644 --- a/web/src/components/search/types.tsx +++ b/web/src/components/search/types.tsx @@ -11,7 +11,7 @@ export interface Document { link: string; source_type: string; blurb: string; - semantic_name: string | null; + semantic_identifier: string | null; } export interface SearchResponse {