mirror of
https://github.com/danswer-ai/danswer.git
synced 2025-06-07 13:39:50 +02:00
Make Slack Bot setup UI more similar to Persona setup
This commit is contained in:
parent
651de071f7
commit
5607fdcddd
1358
web/package-lock.json
generated
1358
web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,38 +1,36 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
import { ArrayHelpers, FieldArray, Form, Formik } from "formik";
|
import { ArrayHelpers, FieldArray, Form, Formik } from "formik";
|
||||||
import * as Yup from "yup";
|
import * as Yup from "yup";
|
||||||
import { PopupSpec } from "@/components/admin/connectors/Popup";
|
import { usePopup } from "@/components/admin/connectors/Popup";
|
||||||
import { DocumentSet, SlackBotConfig } from "@/lib/types";
|
import { DocumentSet, SlackBotConfig } from "@/lib/types";
|
||||||
import {
|
import {
|
||||||
BooleanFormField,
|
BooleanFormField,
|
||||||
|
Label,
|
||||||
|
SubLabel,
|
||||||
TextArrayField,
|
TextArrayField,
|
||||||
} from "@/components/admin/connectors/Field";
|
} from "@/components/admin/connectors/Field";
|
||||||
import { createSlackBotConfig, updateSlackBotConfig } from "./lib";
|
import { createSlackBotConfig, updateSlackBotConfig } from "./lib";
|
||||||
|
import { Card, Divider } from "@tremor/react";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
|
||||||
interface SetCreationPopupProps {
|
interface SetCreationPopupProps {
|
||||||
onClose: () => void;
|
|
||||||
setPopup: (popupSpec: PopupSpec | null) => void;
|
|
||||||
documentSets: DocumentSet[];
|
documentSets: DocumentSet[];
|
||||||
existingSlackBotConfig?: SlackBotConfig;
|
existingSlackBotConfig?: SlackBotConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SlackBotCreationForm = ({
|
export const SlackBotCreationForm = ({
|
||||||
onClose,
|
|
||||||
setPopup,
|
|
||||||
documentSets,
|
documentSets,
|
||||||
existingSlackBotConfig,
|
existingSlackBotConfig,
|
||||||
}: SetCreationPopupProps) => {
|
}: SetCreationPopupProps) => {
|
||||||
const isUpdate = existingSlackBotConfig !== undefined;
|
const isUpdate = existingSlackBotConfig !== undefined;
|
||||||
|
const { popup, setPopup } = usePopup();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="dark">
|
||||||
<div
|
<Card>
|
||||||
className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-10 overflow-y-auto overscroll-contain"
|
{popup}
|
||||||
onClick={onClose}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="bg-gray-800 rounded-lg border border-gray-700 shadow-lg relative w-1/2 text-sm"
|
|
||||||
onClick={(event) => event.stopPropagation()}
|
|
||||||
>
|
|
||||||
<Formik
|
<Formik
|
||||||
initialValues={{
|
initialValues={{
|
||||||
channel_names: existingSlackBotConfig
|
channel_names: existingSlackBotConfig
|
||||||
@ -45,8 +43,7 @@ export const SlackBotCreationForm = ({
|
|||||||
existingSlackBotConfig?.channel_config?.answer_filters || []
|
existingSlackBotConfig?.channel_config?.answer_filters || []
|
||||||
).includes("questionmark_prefilter"),
|
).includes("questionmark_prefilter"),
|
||||||
respond_tag_only:
|
respond_tag_only:
|
||||||
existingSlackBotConfig?.channel_config?.respond_tag_only ||
|
existingSlackBotConfig?.channel_config?.respond_tag_only || false,
|
||||||
false,
|
|
||||||
respond_team_member_list:
|
respond_team_member_list:
|
||||||
existingSlackBotConfig?.channel_config
|
existingSlackBotConfig?.channel_config
|
||||||
?.respond_team_member_list || ([] as string[]),
|
?.respond_team_member_list || ([] as string[]),
|
||||||
@ -73,8 +70,7 @@ export const SlackBotCreationForm = ({
|
|||||||
channel_names: values.channel_names.filter(
|
channel_names: values.channel_names.filter(
|
||||||
(channelName) => channelName !== ""
|
(channelName) => channelName !== ""
|
||||||
),
|
),
|
||||||
respond_team_member_list:
|
respond_team_member_list: values.respond_team_member_list.filter(
|
||||||
values.respond_team_member_list.filter(
|
|
||||||
(teamMemberEmail) => teamMemberEmail !== ""
|
(teamMemberEmail) => teamMemberEmail !== ""
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
@ -90,13 +86,7 @@ export const SlackBotCreationForm = ({
|
|||||||
}
|
}
|
||||||
formikHelpers.setSubmitting(false);
|
formikHelpers.setSubmitting(false);
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
setPopup({
|
router.push(`/admin/bot?u=${Date.now()}`);
|
||||||
message: isUpdate
|
|
||||||
? "Successfully updated DanswerBot config!"
|
|
||||||
: "Successfully created DanswerBot config!",
|
|
||||||
type: "success",
|
|
||||||
});
|
|
||||||
onClose();
|
|
||||||
} else {
|
} else {
|
||||||
const responseJson = await response.json();
|
const responseJson = await response.json();
|
||||||
const errorMsg = responseJson.detail || responseJson.message;
|
const errorMsg = responseJson.detail || responseJson.message;
|
||||||
@ -111,15 +101,10 @@ export const SlackBotCreationForm = ({
|
|||||||
>
|
>
|
||||||
{({ isSubmitting, values }) => (
|
{({ isSubmitting, values }) => (
|
||||||
<Form>
|
<Form>
|
||||||
<h2 className="text-xl font-bold mb-3 border-b border-gray-600 pt-4 pb-3 bg-gray-700 px-6">
|
|
||||||
{isUpdate
|
|
||||||
? "Update a DanswerBot Config"
|
|
||||||
: "Create a new DanswerBot Config"}
|
|
||||||
</h2>
|
|
||||||
<div className="px-6 pb-6">
|
<div className="px-6 pb-6">
|
||||||
<TextArrayField
|
<TextArrayField
|
||||||
name="channel_names"
|
name="channel_names"
|
||||||
label="Channel Names:"
|
label="Channel Names"
|
||||||
values={values}
|
values={values}
|
||||||
subtext={
|
subtext={
|
||||||
<div>
|
<div>
|
||||||
@ -134,28 +119,28 @@ export const SlackBotCreationForm = ({
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<div className="border-t border-gray-600 py-2" />
|
<Divider />
|
||||||
<BooleanFormField
|
<BooleanFormField
|
||||||
name="answer_validity_check_enabled"
|
name="answer_validity_check_enabled"
|
||||||
label="Hide Non-Answers"
|
label="Hide Non-Answers"
|
||||||
subtext="If set, will only answer questions that the model determines it can answer"
|
subtext="If set, will only answer questions that the model determines it can answer"
|
||||||
/>
|
/>
|
||||||
<div className="border-t border-gray-600 py-2" />
|
<Divider />
|
||||||
<BooleanFormField
|
<BooleanFormField
|
||||||
name="questionmark_prefilter_enabled"
|
name="questionmark_prefilter_enabled"
|
||||||
label="Only respond to questions"
|
label="Only respond to questions"
|
||||||
subtext="If set, will only respond to messages that contain a question mark"
|
subtext="If set, will only respond to messages that contain a question mark"
|
||||||
/>
|
/>
|
||||||
<div className="border-t border-gray-600 py-2" />
|
<Divider />
|
||||||
<BooleanFormField
|
<BooleanFormField
|
||||||
name="respond_tag_only"
|
name="respond_tag_only"
|
||||||
label="Respond to @DanswerBot Only"
|
label="Respond to @DanswerBot Only"
|
||||||
subtext="If set, DanswerBot will only respond when directly tagged"
|
subtext="If set, DanswerBot will only respond when directly tagged"
|
||||||
/>
|
/>
|
||||||
<div className="border-t border-gray-600 py-2" />
|
<Divider />
|
||||||
<TextArrayField
|
<TextArrayField
|
||||||
name="respond_team_member_list"
|
name="respond_team_member_list"
|
||||||
label="Team Members Emails:"
|
label="Team Members Emails"
|
||||||
subtext={`If specified, DanswerBot responses will only be
|
subtext={`If specified, DanswerBot responses will only be
|
||||||
visible to members in this list. This is
|
visible to members in this list. This is
|
||||||
useful if you want DanswerBot to operate in an
|
useful if you want DanswerBot to operate in an
|
||||||
@ -164,18 +149,18 @@ export const SlackBotCreationForm = ({
|
|||||||
out the occasional incorrect answer.`}
|
out the occasional incorrect answer.`}
|
||||||
values={values}
|
values={values}
|
||||||
/>
|
/>
|
||||||
<div className="border-t border-gray-600 py-2" />
|
<Divider />
|
||||||
<FieldArray
|
<FieldArray
|
||||||
name="document_sets"
|
name="document_sets"
|
||||||
render={(arrayHelpers: ArrayHelpers) => (
|
render={(arrayHelpers: ArrayHelpers) => (
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium">Document Sets:</p>
|
<Label>Document Sets</Label>
|
||||||
<div className="text-xs">
|
<SubLabel>
|
||||||
The document sets that DanswerBot should search
|
The document sets that DanswerBot should search
|
||||||
through. If left blank, DanswerBot will search
|
through. If left blank, DanswerBot will search through
|
||||||
through all documents.
|
all documents.
|
||||||
</div>
|
</SubLabel>
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-3 mt-2 flex gap-2 flex-wrap">
|
<div className="mb-3 mt-2 flex gap-2 flex-wrap">
|
||||||
{documentSets.map((documentSet) => {
|
{documentSets.map((documentSet) => {
|
||||||
@ -208,9 +193,7 @@ export const SlackBotCreationForm = ({
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="my-auto">
|
<div className="my-auto">{documentSet.name}</div>
|
||||||
{documentSet.name}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@ -218,7 +201,7 @@ export const SlackBotCreationForm = ({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<div className="border-t border-gray-600 py-2" />
|
<Divider />
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
@ -236,8 +219,7 @@ export const SlackBotCreationForm = ({
|
|||||||
</Form>
|
</Form>
|
||||||
)}
|
)}
|
||||||
</Formik>
|
</Formik>
|
||||||
</div>
|
</Card>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -11,6 +11,7 @@ import {
|
|||||||
setSlackBotTokens,
|
setSlackBotTokens,
|
||||||
updateSlackBotConfig,
|
updateSlackBotConfig,
|
||||||
} from "./lib";
|
} from "./lib";
|
||||||
|
import { Card } from "@tremor/react";
|
||||||
|
|
||||||
interface SlackBotTokensFormProps {
|
interface SlackBotTokensFormProps {
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
@ -24,15 +25,7 @@ export const SlackBotTokensForm = ({
|
|||||||
existingTokens,
|
existingTokens,
|
||||||
}: SlackBotTokensFormProps) => {
|
}: SlackBotTokensFormProps) => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<Card>
|
||||||
<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
|
<Formik
|
||||||
initialValues={existingTokens || { app_token: "", bot_token: "" }}
|
initialValues={existingTokens || { app_token: "", bot_token: "" }}
|
||||||
validationSchema={Yup.object().shape({
|
validationSchema={Yup.object().shape({
|
||||||
@ -60,7 +53,6 @@ export const SlackBotTokensForm = ({
|
|||||||
>
|
>
|
||||||
{({ isSubmitting }) => (
|
{({ isSubmitting }) => (
|
||||||
<Form>
|
<Form>
|
||||||
<h2 className="text-lg font-bold mb-3">Set Slack Bot Tokens</h2>
|
|
||||||
<TextFormField
|
<TextFormField
|
||||||
name="bot_token"
|
name="bot_token"
|
||||||
label="Slack Bot Token"
|
label="Slack Bot Token"
|
||||||
@ -87,8 +79,6 @@ export const SlackBotTokensForm = ({
|
|||||||
</Form>
|
</Form>
|
||||||
)}
|
)}
|
||||||
</Formik>
|
</Formik>
|
||||||
</div>
|
</Card>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
71
web/src/app/admin/bot/[id]/page.tsx
Normal file
71
web/src/app/admin/bot/[id]/page.tsx
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import { AdminPageTitle } from "@/components/admin/Title";
|
||||||
|
import { CPUIcon } from "@/components/icons/icons";
|
||||||
|
import { SlackBotCreationForm } from "../SlackBotConfigCreationForm";
|
||||||
|
import { fetchSS } from "@/lib/utilsSS";
|
||||||
|
import { ErrorCallout } from "@/components/ErrorCallout";
|
||||||
|
import { DocumentSet, SlackBotConfig } from "@/lib/types";
|
||||||
|
import { Text } from "@tremor/react";
|
||||||
|
import { BackButton } from "@/components/BackButton";
|
||||||
|
|
||||||
|
async function Page({ params }: { params: { id: string } }) {
|
||||||
|
const tasks = [
|
||||||
|
fetchSS("/manage/admin/slack-bot/config"),
|
||||||
|
fetchSS("/manage/document-set"),
|
||||||
|
];
|
||||||
|
|
||||||
|
const [slackBotsResponse, documentSetsResponse] = await Promise.all(tasks);
|
||||||
|
|
||||||
|
if (!slackBotsResponse.ok) {
|
||||||
|
return (
|
||||||
|
<ErrorCallout
|
||||||
|
errorTitle="Something went wrong :("
|
||||||
|
errorMsg={`Failed to fetch slack bots - ${await slackBotsResponse.text()}`}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const allSlackBotConfigs =
|
||||||
|
(await slackBotsResponse.json()) as SlackBotConfig[];
|
||||||
|
const slackBotConfig = allSlackBotConfigs.find(
|
||||||
|
(config) => config.id.toString() === params.id
|
||||||
|
);
|
||||||
|
if (!slackBotConfig) {
|
||||||
|
return (
|
||||||
|
<ErrorCallout
|
||||||
|
errorTitle="Something went wrong :("
|
||||||
|
errorMsg={`Did not find Slack Bot config with ID: ${params.id}`}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!documentSetsResponse.ok) {
|
||||||
|
return (
|
||||||
|
<ErrorCallout
|
||||||
|
errorTitle="Something went wrong :("
|
||||||
|
errorMsg={`Failed to fetch document sets - ${await documentSetsResponse.text()}`}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const documentSets = (await documentSetsResponse.json()) as DocumentSet[];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto dark">
|
||||||
|
<BackButton />
|
||||||
|
<AdminPageTitle
|
||||||
|
icon={<CPUIcon size={32} />}
|
||||||
|
title="Edit Slack Bot Config"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Text className="mb-8">
|
||||||
|
Edit the existing configuration below! This config will determine how
|
||||||
|
DanswerBot behaves in the specified channels.
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<SlackBotCreationForm
|
||||||
|
documentSets={documentSets}
|
||||||
|
existingSlackBotConfig={slackBotConfig}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Page;
|
40
web/src/app/admin/bot/new/page.tsx
Normal file
40
web/src/app/admin/bot/new/page.tsx
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { AdminPageTitle } from "@/components/admin/Title";
|
||||||
|
import { CPUIcon } from "@/components/icons/icons";
|
||||||
|
import { SlackBotCreationForm } from "../SlackBotConfigCreationForm";
|
||||||
|
import { fetchSS } from "@/lib/utilsSS";
|
||||||
|
import { ErrorCallout } from "@/components/ErrorCallout";
|
||||||
|
import { DocumentSet } from "@/lib/types";
|
||||||
|
import { BackButton } from "@/components/BackButton";
|
||||||
|
import { Text } from "@tremor/react";
|
||||||
|
|
||||||
|
async function Page() {
|
||||||
|
const documentSetsResponse = await fetchSS("/manage/document-set");
|
||||||
|
if (!documentSetsResponse.ok) {
|
||||||
|
return (
|
||||||
|
<ErrorCallout
|
||||||
|
errorTitle="Something went wrong :("
|
||||||
|
errorMsg={`Failed to fetch document sets - ${await documentSetsResponse.text()}`}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const documentSets = (await documentSetsResponse.json()) as DocumentSet[];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto dark">
|
||||||
|
<BackButton />
|
||||||
|
<AdminPageTitle
|
||||||
|
icon={<CPUIcon size={32} />}
|
||||||
|
title="New Slack Bot Config"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Text className="mb-8">
|
||||||
|
Define a new configuration below! This config will determine how
|
||||||
|
DanswerBot behaves in the specified channels.
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<SlackBotCreationForm documentSets={documentSets} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Page;
|
@ -1,70 +1,31 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Button } from "@/components/Button";
|
|
||||||
import { ThreeDotsLoader } from "@/components/Loading";
|
import { ThreeDotsLoader } from "@/components/Loading";
|
||||||
import { PageSelector } from "@/components/PageSelector";
|
import { PageSelector } from "@/components/PageSelector";
|
||||||
import { BasicTable } from "@/components/admin/connectors/BasicTable";
|
import { BasicTable } from "@/components/admin/connectors/BasicTable";
|
||||||
import { CPUIcon, EditIcon, TrashIcon } from "@/components/icons/icons";
|
import { CPUIcon, EditIcon, TrashIcon } from "@/components/icons/icons";
|
||||||
import { DocumentSet, SlackBotConfig } from "@/lib/types";
|
import { SlackBotConfig } from "@/lib/types";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useSlackBotConfigs, useSlackBotTokens } from "./hooks";
|
import { useSlackBotConfigs, useSlackBotTokens } from "./hooks";
|
||||||
import { PopupSpec, usePopup } from "@/components/admin/connectors/Popup";
|
import { PopupSpec, usePopup } from "@/components/admin/connectors/Popup";
|
||||||
import { SlackBotCreationForm } from "./SlackBotConfigCreationForm";
|
|
||||||
import { deleteSlackBotConfig } from "./lib";
|
import { deleteSlackBotConfig } from "./lib";
|
||||||
import { SlackBotTokensForm } from "./SlackBotTokensForm";
|
import { SlackBotTokensForm } from "./SlackBotTokensForm";
|
||||||
import { useDocumentSets } from "../documents/sets/hooks";
|
|
||||||
import { AdminPageTitle } from "@/components/admin/Title";
|
import { AdminPageTitle } from "@/components/admin/Title";
|
||||||
|
import { Button, Text, Title } from "@tremor/react";
|
||||||
|
import { FiChevronDown, FiChevronUp } from "react-icons/fi";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
const numToDisplay = 50;
|
const numToDisplay = 50;
|
||||||
|
|
||||||
const EditRow = ({
|
|
||||||
existingSlackBotConfig,
|
|
||||||
setPopup,
|
|
||||||
documentSets,
|
|
||||||
refreshSlackBotConfigs,
|
|
||||||
}: {
|
|
||||||
existingSlackBotConfig: SlackBotConfig;
|
|
||||||
setPopup: (popupSpec: PopupSpec | null) => void;
|
|
||||||
documentSets: DocumentSet[];
|
|
||||||
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[];
|
|
||||||
refresh: () => void;
|
|
||||||
setPopup: (popupSpec: PopupSpec | null) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const SlackBotConfigsTable = ({
|
const SlackBotConfigsTable = ({
|
||||||
slackBotConfigs,
|
slackBotConfigs,
|
||||||
documentSets,
|
|
||||||
refresh,
|
refresh,
|
||||||
setPopup,
|
setPopup,
|
||||||
}: DocumentFeedbackTableProps) => {
|
}: {
|
||||||
|
slackBotConfigs: SlackBotConfig[];
|
||||||
|
refresh: () => void;
|
||||||
|
setPopup: (popupSpec: PopupSpec | null) => void;
|
||||||
|
}) => {
|
||||||
const [page, setPage] = useState(1);
|
const [page, setPage] = useState(1);
|
||||||
|
|
||||||
// sort by name for consistent ordering
|
// sort by name for consistent ordering
|
||||||
@ -118,12 +79,12 @@ const SlackBotConfigsTable = ({
|
|||||||
return {
|
return {
|
||||||
channels: (
|
channels: (
|
||||||
<div className="flex gap-x-2">
|
<div className="flex gap-x-2">
|
||||||
<EditRow
|
<Link
|
||||||
existingSlackBotConfig={slackBotConfig}
|
className="cursor-pointer my-auto"
|
||||||
setPopup={setPopup}
|
href={`/admin/bot/${slackBotConfig.id}`}
|
||||||
refreshSlackBotConfigs={refresh}
|
>
|
||||||
documentSets={documentSets}
|
<EditIcon />
|
||||||
/>
|
</Link>
|
||||||
<div className="my-auto">
|
<div className="my-auto">
|
||||||
{slackBotConfig.channel_config.channel_names
|
{slackBotConfig.channel_config.channel_names
|
||||||
.map((channel_name) => `#${channel_name}`)
|
.map((channel_name) => `#${channel_name}`)
|
||||||
@ -207,8 +168,6 @@ const SlackBotConfigsTable = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const Main = () => {
|
const Main = () => {
|
||||||
const [slackBotConfigModalIsOpen, setSlackBotConfigModalIsOpen] =
|
|
||||||
useState(false);
|
|
||||||
const [slackBotTokensModalIsOpen, setSlackBotTokensModalIsOpen] =
|
const [slackBotTokensModalIsOpen, setSlackBotTokensModalIsOpen] =
|
||||||
useState(false);
|
useState(false);
|
||||||
const { popup, setPopup } = usePopup();
|
const { popup, setPopup } = usePopup();
|
||||||
@ -218,15 +177,10 @@ const Main = () => {
|
|||||||
error: slackBotConfigsError,
|
error: slackBotConfigsError,
|
||||||
refreshSlackBotConfigs,
|
refreshSlackBotConfigs,
|
||||||
} = useSlackBotConfigs();
|
} = useSlackBotConfigs();
|
||||||
const {
|
|
||||||
data: documentSets,
|
|
||||||
isLoading: isDocumentSetsLoading,
|
|
||||||
error: documentSetsError,
|
|
||||||
} = useDocumentSets();
|
|
||||||
|
|
||||||
const { data: slackBotTokens, refreshSlackBotTokens } = useSlackBotTokens();
|
const { data: slackBotTokens, refreshSlackBotTokens } = useSlackBotTokens();
|
||||||
|
|
||||||
if (isSlackBotConfigsLoading || isDocumentSetsLoading) {
|
if (isSlackBotConfigsLoading) {
|
||||||
return <ThreeDotsLoader />;
|
return <ThreeDotsLoader />;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -234,30 +188,69 @@ const Main = () => {
|
|||||||
return <div>Error: {slackBotConfigsError}</div>;
|
return <div>Error: {slackBotConfigsError}</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (documentSetsError || !documentSets) {
|
|
||||||
return <div>Error: {documentSetsError}</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mb-8">
|
<div className="mb-8 dark">
|
||||||
{popup}
|
{popup}
|
||||||
|
|
||||||
<h2 className="text-lg font-bold mb-2">Step 1: Configure Slack Tokens</h2>
|
<Text className="mb-2">
|
||||||
|
Setup a Slack bot that connects to Danswer. Once setup, you will be able
|
||||||
|
to ask questions to Danswer directly from Slack. Additionally, you can:
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<div className="text-dark-tremor-content text-sm mb-2">
|
||||||
|
<ul className="list-disc mt-2 ml-4">
|
||||||
|
<li>
|
||||||
|
Setup DanswerBot to automatically answer questions in certain
|
||||||
|
channels.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Choose which document sets DanswerBot should answer from, depending
|
||||||
|
on the channel the question is being asked.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Directly message DanswerBot to search just as you would in the web
|
||||||
|
UI.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Text className="mb-6">
|
||||||
|
Follow the{" "}
|
||||||
|
<a
|
||||||
|
className="text-blue-500"
|
||||||
|
href="https://docs.danswer.dev/slack_bot_setup"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
guide{" "}
|
||||||
|
</a>
|
||||||
|
found in the Danswer documentation to get started!
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Title>Step 1: Configure Slack Tokens</Title>
|
||||||
{!slackBotTokens ? (
|
{!slackBotTokens ? (
|
||||||
|
<div className="mt-3">
|
||||||
<SlackBotTokensForm
|
<SlackBotTokensForm
|
||||||
onClose={() => refreshSlackBotTokens()}
|
onClose={() => refreshSlackBotTokens()}
|
||||||
setPopup={setPopup}
|
setPopup={setPopup}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<div className="text-sm italic">Tokens saved!</div>
|
<Text className="italic mt-3">Tokens saved!</Text>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => setSlackBotTokensModalIsOpen(true)}
|
onClick={() => {
|
||||||
|
setSlackBotTokensModalIsOpen(!slackBotTokensModalIsOpen);
|
||||||
|
console.log(slackBotTokensModalIsOpen);
|
||||||
|
}}
|
||||||
|
variant="secondary"
|
||||||
|
size="xs"
|
||||||
className="mt-2"
|
className="mt-2"
|
||||||
|
icon={slackBotTokensModalIsOpen ? FiChevronUp : FiChevronDown}
|
||||||
>
|
>
|
||||||
Edit Tokens
|
{slackBotTokensModalIsOpen ? "Hide" : "Edit Tokens"}
|
||||||
</Button>
|
</Button>
|
||||||
{slackBotTokensModalIsOpen && (
|
{slackBotTokensModalIsOpen && (
|
||||||
|
<div className="mt-3">
|
||||||
<SlackBotTokensForm
|
<SlackBotTokensForm
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
refreshSlackBotTokens();
|
refreshSlackBotTokens();
|
||||||
@ -266,46 +259,35 @@ const Main = () => {
|
|||||||
setPopup={setPopup}
|
setPopup={setPopup}
|
||||||
existingTokens={slackBotTokens}
|
existingTokens={slackBotTokens}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{slackBotTokens && (
|
{slackBotTokens && (
|
||||||
<>
|
<>
|
||||||
<h2 className="text-lg font-bold mb-2 mt-4">
|
<Title className="mb-2 mt-4">Step 2: Setup DanswerBot</Title>
|
||||||
Step 2: Setup DanswerBot
|
<Text className="mb-3">
|
||||||
</h2>
|
|
||||||
<div className="text-sm mb-3">
|
|
||||||
Configure Danswer to automatically answer questions in Slack
|
Configure Danswer to automatically answer questions in Slack
|
||||||
channels.
|
channels. By default, Danswer only responds in channels where a
|
||||||
</div>
|
configuration is setup unless it is explicitly tagged.
|
||||||
|
</Text>
|
||||||
|
|
||||||
<div className="mb-2"></div>
|
<div className="mb-2"></div>
|
||||||
|
|
||||||
<div className="flex mb-3">
|
<Link className="flex mb-3" href="/admin/bot/new">
|
||||||
<Button
|
<Button className="my-auto" variant="secondary" size="xs">
|
||||||
className="ml-2 my-auto"
|
New Slack Bot Configuration
|
||||||
onClick={() => setSlackBotConfigModalIsOpen(true)}
|
|
||||||
>
|
|
||||||
New Slack Bot
|
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</Link>
|
||||||
|
|
||||||
|
{slackBotConfigs.length > 0 && (
|
||||||
|
<div className="mt-8">
|
||||||
<SlackBotConfigsTable
|
<SlackBotConfigsTable
|
||||||
slackBotConfigs={slackBotConfigs}
|
slackBotConfigs={slackBotConfigs}
|
||||||
documentSets={documentSets}
|
|
||||||
refresh={refreshSlackBotConfigs}
|
refresh={refreshSlackBotConfigs}
|
||||||
setPopup={setPopup}
|
setPopup={setPopup}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
{slackBotConfigModalIsOpen && (
|
|
||||||
<SlackBotCreationForm
|
|
||||||
documentSets={documentSets}
|
|
||||||
onClose={() => {
|
|
||||||
refreshSlackBotConfigs();
|
|
||||||
setSlackBotConfigModalIsOpen(false);
|
|
||||||
}}
|
|
||||||
setPopup={setPopup}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@ -315,7 +297,7 @@ const Main = () => {
|
|||||||
|
|
||||||
const Page = () => {
|
const Page = () => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="container mx-auto">
|
||||||
<AdminPageTitle
|
<AdminPageTitle
|
||||||
icon={<CPUIcon size={32} />}
|
icon={<CPUIcon size={32} />}
|
||||||
title="Slack Bot Configuration"
|
title="Slack Bot Configuration"
|
||||||
|
@ -18,6 +18,10 @@ import { usePopup } from "@/components/admin/connectors/Popup";
|
|||||||
import { Persona } from "./interfaces";
|
import { Persona } from "./interfaces";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import {
|
||||||
|
BooleanFormField,
|
||||||
|
TextFormField,
|
||||||
|
} from "@/components/admin/connectors/Field";
|
||||||
|
|
||||||
function SectionHeader({ children }: { children: string | JSX.Element }) {
|
function SectionHeader({ children }: { children: string | JSX.Element }) {
|
||||||
return <div className="mb-4 font-bold text-lg">{children}</div>;
|
return <div className="mb-4 font-bold text-lg">{children}</div>;
|
||||||
@ -33,101 +37,6 @@ function SubLabel({ children }: { children: string | JSX.Element }) {
|
|||||||
return <div className="text-sm text-gray-300 mb-2">{children}</div>;
|
return <div className="text-sm text-gray-300 mb-2">{children}</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: make this the default text input across all forms
|
|
||||||
function PersonaTextInput({
|
|
||||||
name,
|
|
||||||
label,
|
|
||||||
subtext,
|
|
||||||
placeholder,
|
|
||||||
onChange,
|
|
||||||
type = "text",
|
|
||||||
isTextArea = false,
|
|
||||||
disabled = false,
|
|
||||||
autoCompleteDisabled = true,
|
|
||||||
}: {
|
|
||||||
name: string;
|
|
||||||
label: string;
|
|
||||||
subtext?: string | JSX.Element;
|
|
||||||
placeholder?: string;
|
|
||||||
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
|
||||||
type?: string;
|
|
||||||
isTextArea?: boolean;
|
|
||||||
disabled?: boolean;
|
|
||||||
autoCompleteDisabled?: boolean;
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<div className="mb-4">
|
|
||||||
<Label>{label}</Label>
|
|
||||||
{subtext && <SubLabel>{subtext}</SubLabel>}
|
|
||||||
<Field
|
|
||||||
as={isTextArea ? "textarea" : "input"}
|
|
||||||
type={type}
|
|
||||||
name={name}
|
|
||||||
id={name}
|
|
||||||
className={
|
|
||||||
`
|
|
||||||
border
|
|
||||||
text-gray-200
|
|
||||||
border-gray-600
|
|
||||||
rounded
|
|
||||||
w-full
|
|
||||||
py-2
|
|
||||||
px-3
|
|
||||||
mt-1
|
|
||||||
${isTextArea ? " h-28" : ""}
|
|
||||||
` + (disabled ? " bg-gray-900" : " bg-gray-800")
|
|
||||||
}
|
|
||||||
disabled={disabled}
|
|
||||||
placeholder={placeholder}
|
|
||||||
autoComplete={autoCompleteDisabled ? "off" : undefined}
|
|
||||||
{...(onChange ? { onChange } : {})}
|
|
||||||
/>
|
|
||||||
<ErrorMessage
|
|
||||||
name={name}
|
|
||||||
component="div"
|
|
||||||
className="text-red-500 text-sm mt-1"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function PersonaBooleanInput({
|
|
||||||
name,
|
|
||||||
label,
|
|
||||||
subtext,
|
|
||||||
}: {
|
|
||||||
name: string;
|
|
||||||
label: string;
|
|
||||||
subtext?: string | JSX.Element;
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<div className="mb-4">
|
|
||||||
<Label>{label}</Label>
|
|
||||||
{subtext && <SubLabel>{subtext}</SubLabel>}
|
|
||||||
<Field
|
|
||||||
type="checkbox"
|
|
||||||
name={name}
|
|
||||||
id={name}
|
|
||||||
className={`
|
|
||||||
ml-2
|
|
||||||
border
|
|
||||||
text-gray-200
|
|
||||||
border-gray-600
|
|
||||||
rounded
|
|
||||||
py-2
|
|
||||||
px-3
|
|
||||||
mt-1
|
|
||||||
`}
|
|
||||||
/>
|
|
||||||
<ErrorMessage
|
|
||||||
name={name}
|
|
||||||
component="div"
|
|
||||||
className="text-red-500 text-sm mt-1"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function PersonaEditor({
|
export function PersonaEditor({
|
||||||
existingPersona,
|
existingPersona,
|
||||||
documentSets,
|
documentSets,
|
||||||
@ -226,14 +135,14 @@ export function PersonaEditor({
|
|||||||
<div className="pb-6">
|
<div className="pb-6">
|
||||||
<SectionHeader>Who am I?</SectionHeader>
|
<SectionHeader>Who am I?</SectionHeader>
|
||||||
|
|
||||||
<PersonaTextInput
|
<TextFormField
|
||||||
name="name"
|
name="name"
|
||||||
label="Name"
|
label="Name"
|
||||||
disabled={isUpdate}
|
disabled={isUpdate}
|
||||||
subtext="Users will be able to select this Persona based on this name."
|
subtext="Users will be able to select this Persona based on this name."
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<PersonaTextInput
|
<TextFormField
|
||||||
name="description"
|
name="description"
|
||||||
label="Description"
|
label="Description"
|
||||||
subtext="Provide a short descriptions which gives users a hint as to what they should use this Persona for."
|
subtext="Provide a short descriptions which gives users a hint as to what they should use this Persona for."
|
||||||
@ -243,7 +152,7 @@ export function PersonaEditor({
|
|||||||
|
|
||||||
<SectionHeader>Customize my response style</SectionHeader>
|
<SectionHeader>Customize my response style</SectionHeader>
|
||||||
|
|
||||||
<PersonaTextInput
|
<TextFormField
|
||||||
name="system_prompt"
|
name="system_prompt"
|
||||||
label="System Prompt"
|
label="System Prompt"
|
||||||
isTextArea={true}
|
isTextArea={true}
|
||||||
@ -256,7 +165,7 @@ export function PersonaEditor({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<PersonaTextInput
|
<TextFormField
|
||||||
name="task_prompt"
|
name="task_prompt"
|
||||||
label="Task Prompt"
|
label="Task Prompt"
|
||||||
isTextArea={true}
|
isTextArea={true}
|
||||||
@ -352,7 +261,7 @@ export function PersonaEditor({
|
|||||||
|
|
||||||
<SectionHeader>[Advanced] Retrieval Customization</SectionHeader>
|
<SectionHeader>[Advanced] Retrieval Customization</SectionHeader>
|
||||||
|
|
||||||
<PersonaTextInput
|
<TextFormField
|
||||||
name="num_chunks"
|
name="num_chunks"
|
||||||
label="Number of Chunks"
|
label="Number of Chunks"
|
||||||
subtext={
|
subtext={
|
||||||
@ -376,7 +285,7 @@ export function PersonaEditor({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<PersonaBooleanInput
|
<BooleanFormField
|
||||||
name="apply_llm_relevance_filter"
|
name="apply_llm_relevance_filter"
|
||||||
label="Apply LLM Relevance Filter"
|
label="Apply LLM Relevance Filter"
|
||||||
subtext={
|
subtext={
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { FaRobot } from "react-icons/fa";
|
|
||||||
import { PersonaEditor } from "../PersonaEditor";
|
import { PersonaEditor } from "../PersonaEditor";
|
||||||
import { fetchSS } from "@/lib/utilsSS";
|
import { fetchSS } from "@/lib/utilsSS";
|
||||||
import { ErrorCallout } from "@/components/ErrorCallout";
|
import { ErrorCallout } from "@/components/ErrorCallout";
|
||||||
@ -6,6 +5,7 @@ import { DocumentSet } from "@/lib/types";
|
|||||||
import { RobotIcon } from "@/components/icons/icons";
|
import { RobotIcon } from "@/components/icons/icons";
|
||||||
import { BackButton } from "@/components/BackButton";
|
import { BackButton } from "@/components/BackButton";
|
||||||
import { Card } from "@tremor/react";
|
import { Card } from "@tremor/react";
|
||||||
|
import { AdminPageTitle } from "@/components/admin/Title";
|
||||||
|
|
||||||
export default async function Page() {
|
export default async function Page() {
|
||||||
const documentSetsResponse = await fetchSS("/manage/document-set");
|
const documentSetsResponse = await fetchSS("/manage/document-set");
|
||||||
@ -24,10 +24,11 @@ export default async function Page() {
|
|||||||
return (
|
return (
|
||||||
<div className="dark">
|
<div className="dark">
|
||||||
<BackButton />
|
<BackButton />
|
||||||
<div className="border-solid border-gray-600 border-b pb-2 mb-4 flex">
|
|
||||||
<RobotIcon size={32} />
|
<AdminPageTitle
|
||||||
<h1 className="text-3xl font-bold pl-2">Create a New Persona</h1>
|
title="Create a New Persona"
|
||||||
</div>
|
icon={<RobotIcon size={32} />}
|
||||||
|
/>
|
||||||
|
|
||||||
<Card>
|
<Card>
|
||||||
<PersonaEditor documentSets={documentSets} />
|
<PersonaEditor documentSets={documentSets} />
|
||||||
|
@ -23,13 +23,15 @@ export default async function Page() {
|
|||||||
const personas = (await personaResponse.json()) as Persona[];
|
const personas = (await personaResponse.json()) as Persona[];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="dark">
|
||||||
<AdminPageTitle icon={<RobotIcon size={32} />} title="Personas" />
|
<AdminPageTitle icon={<RobotIcon size={32} />} title="Personas" />
|
||||||
|
|
||||||
<div className="text-gray-300 text-sm mb-2">
|
<Text className="mb-2">
|
||||||
Personas are a way to build custom search/question-answering experiences
|
Personas are a way to build custom search/question-answering experiences
|
||||||
for different use cases.
|
for different use cases.
|
||||||
<p className="mt-2">They allow you to customize:</p>
|
</Text>
|
||||||
|
<Text className="mt-2">They allow you to customize:</Text>
|
||||||
|
<div className="text-dark-tremor-content text-sm">
|
||||||
<ul className="list-disc mt-2 ml-4">
|
<ul className="list-disc mt-2 ml-4">
|
||||||
<li>
|
<li>
|
||||||
The prompt used by your LLM of choice to respond to the user query
|
The prompt used by your LLM of choice to respond to the user query
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Button } from "@/components/Button";
|
import { Button } from "@tremor/react";
|
||||||
import {
|
import {
|
||||||
ArrayHelpers,
|
ArrayHelpers,
|
||||||
ErrorMessage,
|
ErrorMessage,
|
||||||
@ -10,33 +10,45 @@ import {
|
|||||||
import * as Yup from "yup";
|
import * as Yup from "yup";
|
||||||
import { FormBodyBuilder } from "./types";
|
import { FormBodyBuilder } from "./types";
|
||||||
import { Dropdown, Option } from "@/components/Dropdown";
|
import { Dropdown, Option } from "@/components/Dropdown";
|
||||||
|
import { FiPlus, FiX } from "react-icons/fi";
|
||||||
|
|
||||||
interface TextFormFieldProps {
|
export function Label({ children }: { children: string | JSX.Element }) {
|
||||||
name: string;
|
return (
|
||||||
label: string;
|
<div className="block font-medium text-base text-gray-200">{children}</div>
|
||||||
subtext?: string;
|
);
|
||||||
placeholder?: string;
|
|
||||||
type?: string;
|
|
||||||
disabled?: boolean;
|
|
||||||
autoCompleteDisabled?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TextFormField = ({
|
export function SubLabel({ children }: { children: string | JSX.Element }) {
|
||||||
|
return <div className="text-sm text-gray-300 mb-2">{children}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function TextFormField({
|
||||||
name,
|
name,
|
||||||
label,
|
label,
|
||||||
subtext,
|
subtext,
|
||||||
placeholder,
|
placeholder,
|
||||||
|
onChange,
|
||||||
type = "text",
|
type = "text",
|
||||||
|
isTextArea = false,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
autoCompleteDisabled = false,
|
autoCompleteDisabled = true,
|
||||||
}: TextFormFieldProps) => {
|
}: {
|
||||||
|
name: string;
|
||||||
|
label: string;
|
||||||
|
subtext?: string | JSX.Element;
|
||||||
|
placeholder?: string;
|
||||||
|
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||||
|
type?: string;
|
||||||
|
isTextArea?: boolean;
|
||||||
|
disabled?: boolean;
|
||||||
|
autoCompleteDisabled?: boolean;
|
||||||
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<label htmlFor={name} className="block font-medium">
|
<Label>{label}</Label>
|
||||||
{label}
|
{subtext && <SubLabel>{subtext}</SubLabel>}
|
||||||
</label>
|
|
||||||
{subtext && <p className="text-xs mb-1">{subtext}</p>}
|
|
||||||
<Field
|
<Field
|
||||||
|
as={isTextArea ? "textarea" : "input"}
|
||||||
type={type}
|
type={type}
|
||||||
name={name}
|
name={name}
|
||||||
id={name}
|
id={name}
|
||||||
@ -44,17 +56,19 @@ export const TextFormField = ({
|
|||||||
`
|
`
|
||||||
border
|
border
|
||||||
text-gray-200
|
text-gray-200
|
||||||
border-gray-300
|
border-gray-600
|
||||||
rounded
|
rounded
|
||||||
w-full
|
w-full
|
||||||
py-2
|
py-2
|
||||||
px-3
|
px-3
|
||||||
mt-1
|
mt-1
|
||||||
` + (disabled ? " bg-slate-900" : " bg-slate-700")
|
${isTextArea ? " h-28" : ""}
|
||||||
|
` + (disabled ? " bg-gray-900" : " bg-gray-800")
|
||||||
}
|
}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
autoComplete={autoCompleteDisabled ? "off" : undefined}
|
autoComplete={autoCompleteDisabled ? "off" : undefined}
|
||||||
|
{...(onChange ? { onChange } : {})}
|
||||||
/>
|
/>
|
||||||
<ErrorMessage
|
<ErrorMessage
|
||||||
name={name}
|
name={name}
|
||||||
@ -63,7 +77,7 @@ export const TextFormField = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
interface BooleanFormFieldProps {
|
interface BooleanFormFieldProps {
|
||||||
name: string;
|
name: string;
|
||||||
@ -79,10 +93,14 @@ export const BooleanFormField = ({
|
|||||||
return (
|
return (
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<label className="flex text-sm">
|
<label className="flex text-sm">
|
||||||
<Field name={name} type="checkbox" className="mx-3 px-5" />
|
<Field
|
||||||
|
name={name}
|
||||||
|
type="checkbox"
|
||||||
|
className="mx-3 px-5 w-3.5 h-3.5 my-auto"
|
||||||
|
/>
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium">{label}</p>
|
<Label>{label}</Label>
|
||||||
{subtext && <p className="text-xs">{subtext}</p>}
|
{subtext && <SubLabel>{subtext}</SubLabel>}
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
@ -111,11 +129,9 @@ export function TextArrayField<T extends Yup.AnyObject>({
|
|||||||
type,
|
type,
|
||||||
}: TextArrayFieldProps<T>) {
|
}: TextArrayFieldProps<T>) {
|
||||||
return (
|
return (
|
||||||
<div className="mb-4">
|
<div className="mb-4 dark">
|
||||||
<label htmlFor={name} className="block font-medium">
|
<Label>{label}</Label>
|
||||||
{label}
|
{subtext && <SubLabel>{subtext}</SubLabel>}
|
||||||
</label>
|
|
||||||
{subtext && <p className="text-xs">{subtext}</p>}
|
|
||||||
|
|
||||||
<FieldArray
|
<FieldArray
|
||||||
name={name}
|
name={name}
|
||||||
@ -130,17 +146,26 @@ export function TextArrayField<T extends Yup.AnyObject>({
|
|||||||
type={type}
|
type={type}
|
||||||
name={`${name}.${index}`}
|
name={`${name}.${index}`}
|
||||||
id={name}
|
id={name}
|
||||||
className="border bg-slate-700 text-gray-200 border-gray-300 rounded w-full py-2 px-3 mr-2"
|
className={`
|
||||||
|
border
|
||||||
|
text-gray-200
|
||||||
|
border-gray-600
|
||||||
|
rounded
|
||||||
|
w-full
|
||||||
|
py-2
|
||||||
|
px-3
|
||||||
|
bg-gray-800
|
||||||
|
mr-4
|
||||||
|
`}
|
||||||
// Disable autocomplete since the browser doesn't know how to handle an array of text fields
|
// Disable autocomplete since the browser doesn't know how to handle an array of text fields
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
/>
|
/>
|
||||||
<Button
|
<div className="my-auto">
|
||||||
type="button"
|
<FiX
|
||||||
|
className="my-auto w-10 h-10 cursor-pointer hover:bg-gray-800 rounded p-2"
|
||||||
onClick={() => arrayHelpers.remove(index)}
|
onClick={() => arrayHelpers.remove(index)}
|
||||||
className="h-8 my-auto"
|
/>
|
||||||
>
|
</div>
|
||||||
Remove
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
<ErrorMessage
|
<ErrorMessage
|
||||||
name={`${name}.${index}`}
|
name={`${name}.${index}`}
|
||||||
@ -149,12 +174,16 @@ export function TextArrayField<T extends Yup.AnyObject>({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
arrayHelpers.push("");
|
arrayHelpers.push("");
|
||||||
}}
|
}}
|
||||||
className="mt-3"
|
className="mt-3"
|
||||||
|
variant="secondary"
|
||||||
|
size="xs"
|
||||||
|
type="button"
|
||||||
|
icon={FiPlus}
|
||||||
>
|
>
|
||||||
Add New
|
Add New
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -73,9 +73,9 @@ module.exports = {
|
|||||||
DEFAULT: "#1f2937", // gray-800
|
DEFAULT: "#1f2937", // gray-800
|
||||||
},
|
},
|
||||||
content: {
|
content: {
|
||||||
subtle: "#4b5563", // gray-600
|
subtle: "#6b7280", // gray-500
|
||||||
DEFAULT: "#9ca3af", // gray-400
|
DEFAULT: "#d1d5db", // gray-300
|
||||||
emphasis: "#e5e7eb", // gray-200
|
emphasis: "#f3f4f6", // gray-100
|
||||||
strong: "#f9fafb", // gray-50
|
strong: "#f9fafb", // gray-50
|
||||||
inverted: "#000000", // black
|
inverted: "#000000", // black
|
||||||
},
|
},
|
||||||
|
Loading…
x
Reference in New Issue
Block a user