mirror of
https://github.com/danswer-ai/danswer.git
synced 2025-09-25 11:16:43 +02:00
Slack bot management dashboard (#483)
This commit is contained in:
188
web/src/app/admin/bot/SlackBotConfigCreationForm.tsx
Normal file
188
web/src/app/admin/bot/SlackBotConfigCreationForm.tsx
Normal file
@@ -0,0 +1,188 @@
|
||||
import { ArrayHelpers, FieldArray, Form, Formik } from "formik";
|
||||
import * as Yup from "yup";
|
||||
import { PopupSpec } from "@/components/admin/connectors/Popup";
|
||||
import { DocumentSet, SlackBotConfig } from "@/lib/types";
|
||||
import {
|
||||
BooleanFormField,
|
||||
TextArrayField,
|
||||
} from "@/components/admin/connectors/Field";
|
||||
import { createSlackBotConfig, updateSlackBotConfig } from "./lib";
|
||||
import { channel } from "diagnostics_channel";
|
||||
|
||||
interface SetCreationPopupProps {
|
||||
onClose: () => void;
|
||||
setPopup: (popupSpec: PopupSpec | null) => void;
|
||||
documentSets: DocumentSet<any, any>[];
|
||||
existingSlackBotConfig?: SlackBotConfig;
|
||||
}
|
||||
|
||||
export const SlackBotCreationForm = ({
|
||||
onClose,
|
||||
setPopup,
|
||||
documentSets,
|
||||
existingSlackBotConfig,
|
||||
}: SetCreationPopupProps) => {
|
||||
const isUpdate = existingSlackBotConfig !== undefined;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50"
|
||||
onClick={onClose}
|
||||
>
|
||||
<div
|
||||
className="bg-gray-800 p-6 rounded border border-gray-700 shadow-lg relative w-1/2 text-sm"
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
>
|
||||
<Formik
|
||||
initialValues={{
|
||||
channel_names: existingSlackBotConfig
|
||||
? existingSlackBotConfig.channel_config.channel_names
|
||||
: ([] as string[]),
|
||||
answer_validity_check_enabled:
|
||||
existingSlackBotConfig?.channel_config
|
||||
?.answer_validity_check_enabled || false,
|
||||
document_sets: existingSlackBotConfig
|
||||
? existingSlackBotConfig.document_sets.map(
|
||||
(documentSet) => documentSet.id
|
||||
)
|
||||
: ([] as number[]),
|
||||
}}
|
||||
validationSchema={Yup.object().shape({
|
||||
channel_names: Yup.array().of(Yup.string()),
|
||||
answer_validity_check_enabled: Yup.boolean().required(),
|
||||
document_sets: Yup.array().of(Yup.number()),
|
||||
})}
|
||||
onSubmit={async (values, formikHelpers) => {
|
||||
formikHelpers.setSubmitting(true);
|
||||
|
||||
// remove empty channel names
|
||||
const cleanedValues = {
|
||||
...values,
|
||||
channel_names: values.channel_names.filter(
|
||||
(channelName) => channelName !== ""
|
||||
),
|
||||
};
|
||||
|
||||
let response;
|
||||
if (isUpdate) {
|
||||
response = await updateSlackBotConfig(
|
||||
existingSlackBotConfig.id,
|
||||
cleanedValues
|
||||
);
|
||||
} else {
|
||||
response = await createSlackBotConfig(cleanedValues);
|
||||
}
|
||||
formikHelpers.setSubmitting(false);
|
||||
if (response.ok) {
|
||||
setPopup({
|
||||
message: isUpdate
|
||||
? "Successfully updated DanswerBot config!"
|
||||
: "Successfully created DanswerBot config!",
|
||||
type: "success",
|
||||
});
|
||||
onClose();
|
||||
} else {
|
||||
const errorMsg = (await response.json()).detail;
|
||||
setPopup({
|
||||
message: isUpdate
|
||||
? `Error updating DanswerBot config - ${errorMsg}`
|
||||
: `Error creating DanswerBot config - ${errorMsg}`,
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
{({ isSubmitting, values }) => (
|
||||
<Form>
|
||||
<h2 className="text-lg font-bold mb-3">
|
||||
{isUpdate
|
||||
? "Update a DanswerBot Config"
|
||||
: "Create a new DanswerBot Config"}
|
||||
</h2>
|
||||
<TextArrayField
|
||||
name="channel_names"
|
||||
label="Channel Names:"
|
||||
values={values}
|
||||
subtext="The names of the Slack channels you want DanswerBot to assist in. For example, '#ask-danswer'."
|
||||
/>
|
||||
<div className="border-t border-gray-700 py-2" />
|
||||
<BooleanFormField
|
||||
name="answer_validity_check_enabled"
|
||||
label="Hide Non-Answers"
|
||||
subtext="If set, will only answer questions that the model determines it can answer"
|
||||
/>
|
||||
<div className="border-t border-gray-700 py-2" />
|
||||
<FieldArray
|
||||
name="document_sets"
|
||||
render={(arrayHelpers: ArrayHelpers) => (
|
||||
<div>
|
||||
<div>
|
||||
Document Sets:
|
||||
<br />
|
||||
<div className="text-xs">
|
||||
The document sets that DanswerBot should search
|
||||
through. If left blank, DanswerBot will search through
|
||||
all documents.
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-3 mt-2 flex gap-2 flex-wrap">
|
||||
{documentSets.map((documentSet) => {
|
||||
const ind = values.document_sets.indexOf(
|
||||
documentSet.id
|
||||
);
|
||||
let isSelected = ind !== -1;
|
||||
return (
|
||||
<div
|
||||
key={documentSet.id}
|
||||
className={
|
||||
`
|
||||
px-3
|
||||
py-1
|
||||
rounded-lg
|
||||
border
|
||||
border-gray-700
|
||||
w-fit
|
||||
flex
|
||||
cursor-pointer ` +
|
||||
(isSelected
|
||||
? " bg-gray-600"
|
||||
: " bg-gray-900 hover:bg-gray-700")
|
||||
}
|
||||
onClick={() => {
|
||||
if (isSelected) {
|
||||
arrayHelpers.remove(ind);
|
||||
} else {
|
||||
arrayHelpers.push(documentSet.id);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="my-auto">{documentSet.name}</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
<div className="flex">
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isSubmitting}
|
||||
className={
|
||||
"bg-slate-500 hover:bg-slate-700 text-white " +
|
||||
"font-bold py-2 px-4 rounded focus:outline-none " +
|
||||
"focus:shadow-outline w-full max-w-sm mx-auto"
|
||||
}
|
||||
>
|
||||
{isUpdate ? "Update!" : "Create!"}
|
||||
</button>
|
||||
</div>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
94
web/src/app/admin/bot/SlackBotTokensForm.tsx
Normal file
94
web/src/app/admin/bot/SlackBotTokensForm.tsx
Normal file
@@ -0,0 +1,94 @@
|
||||
import { Form, Formik } from "formik";
|
||||
import * as Yup from "yup";
|
||||
import { PopupSpec } from "@/components/admin/connectors/Popup";
|
||||
import { SlackBotTokens } from "@/lib/types";
|
||||
import {
|
||||
TextArrayField,
|
||||
TextFormField,
|
||||
} from "@/components/admin/connectors/Field";
|
||||
import {
|
||||
createSlackBotConfig,
|
||||
setSlackBotTokens,
|
||||
updateSlackBotConfig,
|
||||
} from "./lib";
|
||||
|
||||
interface SlackBotTokensFormProps {
|
||||
onClose: () => void;
|
||||
setPopup: (popupSpec: PopupSpec | null) => void;
|
||||
existingTokens?: SlackBotTokens;
|
||||
}
|
||||
|
||||
export const SlackBotTokensForm = ({
|
||||
onClose,
|
||||
setPopup,
|
||||
existingTokens,
|
||||
}: SlackBotTokensFormProps) => {
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50"
|
||||
onClick={onClose}
|
||||
>
|
||||
<div
|
||||
className="bg-gray-800 p-6 rounded border border-gray-700 shadow-lg relative w-1/2 text-sm"
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
>
|
||||
<Formik
|
||||
initialValues={existingTokens || { app_token: "", bot_token: "" }}
|
||||
validationSchema={Yup.object().shape({
|
||||
channel_names: Yup.array().of(Yup.string().required()),
|
||||
document_sets: Yup.array().of(Yup.number()),
|
||||
})}
|
||||
onSubmit={async (values, formikHelpers) => {
|
||||
formikHelpers.setSubmitting(true);
|
||||
const response = await setSlackBotTokens(values);
|
||||
formikHelpers.setSubmitting(false);
|
||||
if (response.ok) {
|
||||
setPopup({
|
||||
message: "Successfully set Slack tokens!",
|
||||
type: "success",
|
||||
});
|
||||
onClose();
|
||||
} else {
|
||||
const errorMsg = await response.text();
|
||||
setPopup({
|
||||
message: `Error setting Slack tokens - ${errorMsg}`,
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
{({ isSubmitting }) => (
|
||||
<Form>
|
||||
<h2 className="text-lg font-bold mb-3">Set Slack Bot Tokens</h2>
|
||||
<TextFormField
|
||||
name="bot_token"
|
||||
label="Slack Bot Token"
|
||||
type="password"
|
||||
/>
|
||||
<TextFormField
|
||||
name="app_token"
|
||||
label="Slack App Token"
|
||||
type="password"
|
||||
/>
|
||||
<div className="flex">
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isSubmitting}
|
||||
className={
|
||||
"bg-slate-500 hover:bg-slate-700 text-white " +
|
||||
"font-bold py-2 px-4 rounded focus:outline-none " +
|
||||
"focus:shadow-outline w-full max-w-sm mx-auto"
|
||||
}
|
||||
>
|
||||
Set Tokens
|
||||
</button>
|
||||
</div>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
23
web/src/app/admin/bot/hooks.ts
Normal file
23
web/src/app/admin/bot/hooks.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { errorHandlingFetcher } from "@/lib/fetcher";
|
||||
import { SlackBotConfig, SlackBotTokens } from "@/lib/types";
|
||||
import useSWR, { mutate } from "swr";
|
||||
|
||||
export const useSlackBotConfigs = () => {
|
||||
const url = "/api/manage/admin/slack-bot/config";
|
||||
const swrResponse = useSWR<SlackBotConfig[]>(url, errorHandlingFetcher);
|
||||
|
||||
return {
|
||||
...swrResponse,
|
||||
refreshSlackBotConfigs: () => mutate(url),
|
||||
};
|
||||
};
|
||||
|
||||
export const useSlackBotTokens = () => {
|
||||
const url = "/api/manage/admin/slack-bot/tokens";
|
||||
const swrResponse = useSWR<SlackBotTokens>(url, errorHandlingFetcher);
|
||||
|
||||
return {
|
||||
...swrResponse,
|
||||
refreshSlackBotTokens: () => mutate(url),
|
||||
};
|
||||
};
|
51
web/src/app/admin/bot/lib.ts
Normal file
51
web/src/app/admin/bot/lib.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { ChannelConfig, SlackBotTokens } from "@/lib/types";
|
||||
|
||||
interface SlackBotConfigCreationRequest {
|
||||
document_sets: number[];
|
||||
channel_names: string[];
|
||||
answer_validity_check_enabled: boolean;
|
||||
}
|
||||
|
||||
export const createSlackBotConfig = async (
|
||||
creationRequest: SlackBotConfigCreationRequest
|
||||
) => {
|
||||
return fetch("/api/manage/admin/slack-bot/config", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(creationRequest),
|
||||
});
|
||||
};
|
||||
|
||||
export const updateSlackBotConfig = async (
|
||||
id: number,
|
||||
creationRequest: SlackBotConfigCreationRequest
|
||||
) => {
|
||||
return fetch(`/api/manage/admin/slack-bot/config/${id}`, {
|
||||
method: "PATCH",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(creationRequest),
|
||||
});
|
||||
};
|
||||
|
||||
export const deleteSlackBotConfig = async (id: number) => {
|
||||
return fetch(`/api/manage/admin/slack-bot/config/${id}`, {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const setSlackBotTokens = async (slackBotTokens: SlackBotTokens) => {
|
||||
return fetch(`/api/manage/admin/slack-bot/tokens`, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(slackBotTokens),
|
||||
});
|
||||
};
|
300
web/src/app/admin/bot/page.tsx
Normal file
300
web/src/app/admin/bot/page.tsx
Normal file
@@ -0,0 +1,300 @@
|
||||
"use client";
|
||||
|
||||
import { Button } from "@/components/Button";
|
||||
import { ThreeDotsLoader } from "@/components/Loading";
|
||||
import { PageSelector } from "@/components/PageSelector";
|
||||
import { BasicTable } from "@/components/admin/connectors/BasicTable";
|
||||
import {
|
||||
BookmarkIcon,
|
||||
CPUIcon,
|
||||
EditIcon,
|
||||
TrashIcon,
|
||||
} from "@/components/icons/icons";
|
||||
import { DocumentSet, SlackBotConfig } from "@/lib/types";
|
||||
import { useState } from "react";
|
||||
import { useSlackBotConfigs, useSlackBotTokens } from "./hooks";
|
||||
import { PopupSpec, usePopup } from "@/components/admin/connectors/Popup";
|
||||
import { SlackBotCreationForm } from "./SlackBotConfigCreationForm";
|
||||
import { deleteSlackBotConfig } from "./lib";
|
||||
import { SlackBotTokensForm } from "./SlackBotTokensForm";
|
||||
import { useDocumentSets } from "../documents/sets/hooks";
|
||||
|
||||
const numToDisplay = 50;
|
||||
|
||||
const EditRow = ({
|
||||
existingSlackBotConfig,
|
||||
setPopup,
|
||||
documentSets,
|
||||
refreshSlackBotConfigs,
|
||||
}: {
|
||||
existingSlackBotConfig: SlackBotConfig;
|
||||
setPopup: (popupSpec: PopupSpec | null) => void;
|
||||
documentSets: DocumentSet<any, any>[];
|
||||
refreshSlackBotConfigs: () => void;
|
||||
}) => {
|
||||
const [isEditPopupOpen, setEditPopupOpen] = useState(false);
|
||||
return (
|
||||
<>
|
||||
{isEditPopupOpen && (
|
||||
<SlackBotCreationForm
|
||||
onClose={() => {
|
||||
setEditPopupOpen(false);
|
||||
refreshSlackBotConfigs();
|
||||
}}
|
||||
setPopup={setPopup}
|
||||
documentSets={documentSets}
|
||||
existingSlackBotConfig={existingSlackBotConfig}
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
className="cursor-pointer my-auto"
|
||||
onClick={() => setEditPopupOpen(true)}
|
||||
>
|
||||
<EditIcon />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
interface DocumentFeedbackTableProps {
|
||||
slackBotConfigs: SlackBotConfig[];
|
||||
documentSets: DocumentSet<any, any>[];
|
||||
refresh: () => void;
|
||||
setPopup: (popupSpec: PopupSpec | null) => void;
|
||||
}
|
||||
|
||||
const SlackBotConfigsTable = ({
|
||||
slackBotConfigs,
|
||||
documentSets,
|
||||
refresh,
|
||||
setPopup,
|
||||
}: DocumentFeedbackTableProps) => {
|
||||
const [page, setPage] = useState(1);
|
||||
|
||||
// sort by name for consistent ordering
|
||||
slackBotConfigs.sort((a, b) => {
|
||||
if (a.id < b.id) {
|
||||
return -1;
|
||||
} else if (a.id > b.id) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<BasicTable
|
||||
columns={[
|
||||
{
|
||||
header: "Channels",
|
||||
key: "channels",
|
||||
},
|
||||
{
|
||||
header: "Document Sets",
|
||||
key: "document_sets",
|
||||
},
|
||||
{
|
||||
header: "Hide Non-Answers",
|
||||
key: "answer_validity_check_enabled",
|
||||
},
|
||||
{
|
||||
header: "Delete",
|
||||
key: "delete",
|
||||
width: "50px",
|
||||
},
|
||||
]}
|
||||
data={slackBotConfigs
|
||||
.slice((page - 1) * numToDisplay, page * numToDisplay)
|
||||
.map((slackBotConfig) => {
|
||||
return {
|
||||
channels: (
|
||||
<div className="flex gap-x-2">
|
||||
<EditRow
|
||||
existingSlackBotConfig={slackBotConfig}
|
||||
setPopup={setPopup}
|
||||
refreshSlackBotConfigs={refresh}
|
||||
documentSets={documentSets}
|
||||
/>
|
||||
<div className="my-auto">
|
||||
{slackBotConfig.channel_config.channel_names
|
||||
.map((channel_name) => `#${channel_name}`)
|
||||
.join(", ")}
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
document_sets: (
|
||||
<div>
|
||||
{slackBotConfig.document_sets
|
||||
.map((documentSet) => documentSet.name)
|
||||
.join(", ")}
|
||||
</div>
|
||||
),
|
||||
answer_validity_check_enabled: slackBotConfig.channel_config
|
||||
.answer_validity_check_enabled ? (
|
||||
<div className="text-gray-300">Yes</div>
|
||||
) : (
|
||||
<div className="text-gray-300">No</div>
|
||||
),
|
||||
delete: (
|
||||
<div
|
||||
className="cursor-pointer"
|
||||
onClick={async () => {
|
||||
const response = await deleteSlackBotConfig(
|
||||
slackBotConfig.id
|
||||
);
|
||||
if (response.ok) {
|
||||
setPopup({
|
||||
message: `Slack bot config "${slackBotConfig.id}" deleted`,
|
||||
type: "success",
|
||||
});
|
||||
} else {
|
||||
const errorMsg = await response.text();
|
||||
setPopup({
|
||||
message: `Failed to delete Slack bot config - ${errorMsg}`,
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
refresh();
|
||||
}}
|
||||
>
|
||||
<TrashIcon />
|
||||
</div>
|
||||
),
|
||||
};
|
||||
})}
|
||||
/>
|
||||
<div className="mt-3 flex">
|
||||
<div className="mx-auto">
|
||||
<PageSelector
|
||||
totalPages={Math.ceil(slackBotConfigs.length / numToDisplay)}
|
||||
currentPage={page}
|
||||
onPageChange={(newPage) => setPage(newPage)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Main = () => {
|
||||
const [slackBotConfigModalIsOpen, setSlackBotConfigModalIsOpen] =
|
||||
useState(false);
|
||||
const [slackBotTokensModalIsOpen, setSlackBotTokensModalIsOpen] =
|
||||
useState(false);
|
||||
const { popup, setPopup } = usePopup();
|
||||
const {
|
||||
data: slackBotConfigs,
|
||||
isLoading: isSlackBotConfigsLoading,
|
||||
error: slackBotConfigsError,
|
||||
refreshSlackBotConfigs,
|
||||
} = useSlackBotConfigs();
|
||||
const {
|
||||
data: documentSets,
|
||||
isLoading: isDocumentSetsLoading,
|
||||
error: documentSetsError,
|
||||
} = useDocumentSets();
|
||||
|
||||
const { data: slackBotTokens, refreshSlackBotTokens } = useSlackBotTokens();
|
||||
|
||||
if (isSlackBotConfigsLoading || isDocumentSetsLoading) {
|
||||
return <ThreeDotsLoader />;
|
||||
}
|
||||
|
||||
if (slackBotConfigsError || !slackBotConfigs) {
|
||||
return <div>Error: {slackBotConfigsError}</div>;
|
||||
}
|
||||
|
||||
if (documentSetsError || !documentSets) {
|
||||
return <div>Error: {documentSetsError}</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mb-8">
|
||||
{popup}
|
||||
|
||||
<h2 className="text-lg font-bold mb-2">Step 1: Configure Slack Tokens</h2>
|
||||
{!slackBotTokens ? (
|
||||
<SlackBotTokensForm
|
||||
onClose={() => refreshSlackBotTokens()}
|
||||
setPopup={setPopup}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<div className="text-sm italic">Tokens saved!</div>
|
||||
<Button
|
||||
onClick={() => setSlackBotTokensModalIsOpen(true)}
|
||||
className="mt-2"
|
||||
>
|
||||
Edit Tokens
|
||||
</Button>
|
||||
{slackBotTokensModalIsOpen && (
|
||||
<SlackBotTokensForm
|
||||
onClose={() => {
|
||||
refreshSlackBotTokens();
|
||||
setSlackBotTokensModalIsOpen(false);
|
||||
}}
|
||||
setPopup={setPopup}
|
||||
existingTokens={slackBotTokens}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{slackBotTokens && (
|
||||
<>
|
||||
<h2 className="text-lg font-bold mb-2 mt-4">
|
||||
Step 2: Setup DanswerBot
|
||||
</h2>
|
||||
<div className="text-sm mb-3">
|
||||
Configure Danswer to automatically answer questions in Slack
|
||||
channels.
|
||||
</div>
|
||||
|
||||
<div className="mb-2"></div>
|
||||
|
||||
<div className="flex mb-3">
|
||||
<Button
|
||||
className="ml-2 my-auto"
|
||||
onClick={() => setSlackBotConfigModalIsOpen(true)}
|
||||
>
|
||||
New Slack Bot
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<SlackBotConfigsTable
|
||||
slackBotConfigs={slackBotConfigs}
|
||||
documentSets={documentSets}
|
||||
refresh={refreshSlackBotConfigs}
|
||||
setPopup={setPopup}
|
||||
/>
|
||||
|
||||
{slackBotConfigModalIsOpen && (
|
||||
<SlackBotCreationForm
|
||||
documentSets={documentSets}
|
||||
onClose={() => {
|
||||
refreshSlackBotConfigs();
|
||||
setSlackBotConfigModalIsOpen(false);
|
||||
}}
|
||||
setPopup={setPopup}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
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>
|
||||
|
||||
<Main />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Page;
|
@@ -1,10 +1,13 @@
|
||||
import { fetcher } from "@/lib/fetcher";
|
||||
import { errorHandlingFetcher } from "@/lib/fetcher";
|
||||
import { DocumentSet } from "@/lib/types";
|
||||
import useSWR, { mutate } from "swr";
|
||||
|
||||
export const useDocumentSets = () => {
|
||||
const url = "/api/manage/document-set";
|
||||
const swrResponse = useSWR<DocumentSet<any, any>[]>(url, fetcher);
|
||||
const swrResponse = useSWR<DocumentSet<any, any>[]>(
|
||||
url,
|
||||
errorHandlingFetcher
|
||||
);
|
||||
|
||||
return {
|
||||
...swrResponse,
|
||||
|
@@ -20,6 +20,7 @@ import {
|
||||
UsersIcon,
|
||||
ThumbsUpIcon,
|
||||
BookmarkIcon,
|
||||
CPUIcon,
|
||||
} from "@/components/icons/icons";
|
||||
import { DISABLE_AUTH } from "@/lib/constants";
|
||||
import { getCurrentUserSS } from "@/lib/userSS";
|
||||
@@ -44,7 +45,7 @@ export default async function AdminLayout({
|
||||
return (
|
||||
<div>
|
||||
<Header user={user} />
|
||||
<div className="bg-gray-900 pt-8 flex">
|
||||
<div className="bg-gray-900 pt-8 pb-8 flex">
|
||||
<Sidebar
|
||||
title="Connector"
|
||||
collections={[
|
||||
@@ -244,6 +245,20 @@ export default async function AdminLayout({
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Bots",
|
||||
items: [
|
||||
{
|
||||
name: (
|
||||
<div className="flex">
|
||||
<CPUIcon size={18} />
|
||||
<div className="ml-1">Slack Bot</div>
|
||||
</div>
|
||||
),
|
||||
link: "/admin/bot",
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<div className="px-12 min-h-screen bg-gray-900 text-gray-100 w-full">
|
||||
|
@@ -87,20 +87,22 @@ export const BooleanFormField = ({
|
||||
);
|
||||
};
|
||||
|
||||
interface TextArrayFieldProps {
|
||||
interface TextArrayFieldProps<T extends Yup.AnyObject> {
|
||||
name: string;
|
||||
label: string;
|
||||
values: T;
|
||||
subtext?: string;
|
||||
type?: string;
|
||||
}
|
||||
|
||||
export function TextArrayFieldBuilder<T extends Yup.AnyObject>({
|
||||
export function TextArrayField<T extends Yup.AnyObject>({
|
||||
name,
|
||||
label,
|
||||
values,
|
||||
subtext,
|
||||
type = "text",
|
||||
}: TextArrayFieldProps): FormBodyBuilder<T> {
|
||||
const TextArrayField: FormBodyBuilder<T> = (values) => (
|
||||
type,
|
||||
}: TextArrayFieldProps<T>) {
|
||||
return (
|
||||
<div className="mb-4">
|
||||
<label htmlFor={name} className="block">
|
||||
{label}
|
||||
@@ -153,5 +155,20 @@ export function TextArrayFieldBuilder<T extends Yup.AnyObject>({
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
return TextArrayField;
|
||||
}
|
||||
|
||||
interface TextArrayFieldBuilderProps<T extends Yup.AnyObject> {
|
||||
name: string;
|
||||
label: string;
|
||||
subtext?: string;
|
||||
type?: string;
|
||||
}
|
||||
|
||||
export function TextArrayFieldBuilder<T extends Yup.AnyObject>(
|
||||
props: TextArrayFieldBuilderProps<T>
|
||||
): FormBodyBuilder<T> {
|
||||
const _TextArrayField: FormBodyBuilder<T> = (values) => (
|
||||
<TextArrayField {...props} values={values} />
|
||||
);
|
||||
return _TextArrayField;
|
||||
}
|
||||
|
@@ -32,6 +32,7 @@ import {
|
||||
FiZoomIn,
|
||||
FiCopy,
|
||||
FiBookmark,
|
||||
FiCpu,
|
||||
} from "react-icons/fi";
|
||||
import { SiBookstack } from "react-icons/si";
|
||||
import Image from "next/image";
|
||||
@@ -251,6 +252,13 @@ export const BookmarkIcon = ({
|
||||
return <FiBookmark size={size} className={className} />;
|
||||
};
|
||||
|
||||
export const CPUIcon = ({
|
||||
size = 16,
|
||||
className = defaultTailwindCSS,
|
||||
}: IconProps) => {
|
||||
return <FiCpu size={size} className={className} />;
|
||||
};
|
||||
|
||||
//
|
||||
// COMPANY LOGOS
|
||||
//
|
||||
|
@@ -1 +1,25 @@
|
||||
export const fetcher = (url: string) => fetch(url).then((res) => res.json());
|
||||
|
||||
class FetchError extends Error {
|
||||
status: number;
|
||||
info: any;
|
||||
|
||||
constructor(message: string, status: number, info: any) {
|
||||
super(message);
|
||||
this.status = status;
|
||||
this.info = info;
|
||||
}
|
||||
}
|
||||
|
||||
export const errorHandlingFetcher = async (url: string) => {
|
||||
const res = await fetch(url);
|
||||
if (!res.ok) {
|
||||
const error = new FetchError(
|
||||
"An error occurred while fetching the data.",
|
||||
res.status,
|
||||
await res.json()
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
return res.json();
|
||||
};
|
||||
|
@@ -228,3 +228,21 @@ export interface DocumentSet<ConnectorType, CredentialType> {
|
||||
cc_pair_descriptors: CCPairDescriptor<ConnectorType, CredentialType>[];
|
||||
is_up_to_date: boolean;
|
||||
}
|
||||
|
||||
// SLACK BOT CONFIGS
|
||||
export interface ChannelConfig {
|
||||
channel_names: string[];
|
||||
answer_validity_check_enabled?: boolean;
|
||||
team_members?: string[];
|
||||
}
|
||||
|
||||
export interface SlackBotConfig {
|
||||
id: number;
|
||||
document_sets: DocumentSet<any, any>[];
|
||||
channel_config: ChannelConfig;
|
||||
}
|
||||
|
||||
export interface SlackBotTokens {
|
||||
bot_token: string;
|
||||
app_token: string;
|
||||
}
|
||||
|
Reference in New Issue
Block a user