Replace 'respond_sender_only' with 'respond_tag_only' + prettify UI

This commit is contained in:
Weves
2023-10-03 15:57:38 -07:00
committed by Chris Weaver
parent 59bac1ca8f
commit 29a0a45518
10 changed files with 154 additions and 133 deletions

View File

@ -55,7 +55,7 @@ def handle_message(
# List of user id to send message to, if None, send to everyone in channel # List of user id to send message to, if None, send to everyone in channel
send_to: list[str] | None = None send_to: list[str] | None = None
respond_sender_only = False respond_tag_only = False
respond_team_member_list = None respond_team_member_list = None
if slack_bot_config and slack_bot_config.channel_config: if slack_bot_config and slack_bot_config.channel_config:
channel_conf = slack_bot_config.channel_config channel_conf = slack_bot_config.channel_config
@ -77,14 +77,20 @@ def handle_message(
f"validity checks enabled: {channel_conf['answer_filters']}" f"validity checks enabled: {channel_conf['answer_filters']}"
) )
respond_sender_only = channel_conf.get("respond_sender_only") or False respond_tag_only = channel_conf.get("respond_tag_only") or False
respond_team_member_list = ( respond_team_member_list = (
channel_conf.get("respond_team_member_list") or None channel_conf.get("respond_team_member_list") or None
) )
if sender_id and (respond_sender_only or is_bot_msg): # `skip_filters=True` -> this is a tag, so we *should* respond
send_to = [sender_id] if respond_tag_only and not skip_filters:
elif respond_team_member_list: logger.info(
"Skipping message since the channel is configured such that "
"DanswerBot only responds to tags"
)
return
if respond_team_member_list:
send_to = fetch_userids_from_emails(respond_team_member_list, client) send_to = fetch_userids_from_emails(respond_team_member_list, client)
# If configured to respond to team members only, then cannot be used with a /danswerbot command # If configured to respond to team members only, then cannot be used with a /danswerbot command
@ -208,14 +214,7 @@ def handle_message(
# For DM (ephemeral message), we need to create a thread via a normal message so the user can see # For DM (ephemeral message), we need to create a thread via a normal message so the user can see
# the ephemeral message. This also will give the user a notification which ephemeral message does not. # the ephemeral message. This also will give the user a notification which ephemeral message does not.
if respond_sender_only: if respond_team_member_list:
respond_in_thread(
client=client,
channel=channel,
text="We've just DM-ed you the answer, hope you find it useful! 💃",
thread_ts=message_ts_to_respond_to,
)
elif respond_team_member_list:
respond_in_thread( respond_in_thread(
client=client, client=client,
channel=channel, channel=channel,

View File

@ -2,6 +2,7 @@ import datetime
from enum import Enum as PyEnum from enum import Enum as PyEnum
from typing import Any from typing import Any
from typing import List from typing import List
from typing import Literal
from typing import NotRequired from typing import NotRequired
from typing import TypedDict from typing import TypedDict
from uuid import UUID from uuid import UUID
@ -477,14 +478,19 @@ class ChatMessage(Base):
persona: Mapped[Persona | None] = relationship("Persona") persona: Mapped[Persona | None] = relationship("Persona")
AllowedAnswerFilters = (
Literal["well_answered_postfilter"] | Literal["questionmark_prefilter"]
)
class ChannelConfig(TypedDict): class ChannelConfig(TypedDict):
"""NOTE: is a `TypedDict` so it can be used a type hint for a JSONB column """NOTE: is a `TypedDict` so it can be used a type hint for a JSONB column
in Postgres""" in Postgres"""
channel_names: list[str] channel_names: list[str]
respond_sender_only: NotRequired[bool] # defaults to False respond_tag_only: NotRequired[bool] # defaults to False
respond_team_member_list: NotRequired[list[str]] respond_team_member_list: NotRequired[list[str]]
answer_filters: NotRequired[list[str]] answer_filters: NotRequired[list[AllowedAnswerFilters]]
class SlackBotConfig(Base): class SlackBotConfig(Base):

View File

@ -19,6 +19,7 @@ from danswer.configs.constants import QAFeedbackType
from danswer.configs.constants import SearchFeedbackType from danswer.configs.constants import SearchFeedbackType
from danswer.connectors.models import InputType from danswer.connectors.models import InputType
from danswer.datastores.interfaces import IndexFilter from danswer.datastores.interfaces import IndexFilter
from danswer.db.models import AllowedAnswerFilters
from danswer.db.models import ChannelConfig from danswer.db.models import ChannelConfig
from danswer.db.models import Connector from danswer.db.models import Connector
from danswer.db.models import Credential from danswer.db.models import Credential
@ -436,10 +437,10 @@ class SlackBotConfigCreationRequest(BaseModel):
# for now for simplicity / speed of development # for now for simplicity / speed of development
document_sets: list[int] document_sets: list[int]
channel_names: list[str] channel_names: list[str]
# If not responder_sender_only and no team members, assume respond in the channel to everyone respond_tag_only: bool = False
respond_sender_only: bool = False # If no team members, assume respond in the channel to everyone
respond_team_member_list: list[str] = [] respond_team_member_list: list[str] = []
answer_filters: list[str] = [] answer_filters: list[AllowedAnswerFilters] = []
@validator("answer_filters", pre=True) @validator("answer_filters", pre=True)
def validate_filters(cls, value: list[str]) -> list[str]: def validate_filters(cls, value: list[str]) -> list[str]:

View File

@ -29,7 +29,7 @@ def _form_channel_config(
db_session: Session, db_session: Session,
) -> ChannelConfig: ) -> ChannelConfig:
raw_channel_names = slack_bot_config_creation_request.channel_names raw_channel_names = slack_bot_config_creation_request.channel_names
respond_sender_only = slack_bot_config_creation_request.respond_sender_only respond_tag_only = slack_bot_config_creation_request.respond_tag_only
respond_team_member_list = ( respond_team_member_list = (
slack_bot_config_creation_request.respond_team_member_list slack_bot_config_creation_request.respond_team_member_list
) )
@ -53,17 +53,17 @@ def _form_channel_config(
detail=str(e), detail=str(e),
) )
if respond_sender_only and respond_team_member_list: if respond_tag_only and respond_team_member_list:
raise ValueError( raise ValueError(
"Cannot set DanswerBot to only respond to sender and " "Cannot set DanswerBot to only respond to tags only and "
"also respond to a predetermined set of users. This is not logically possible..." "also respond to a predetermined set of users."
) )
channel_config: ChannelConfig = { channel_config: ChannelConfig = {
"channel_names": cleaned_channel_names, "channel_names": cleaned_channel_names,
} }
if respond_sender_only is not None: if respond_tag_only is not None:
channel_config["respond_sender_only"] = respond_sender_only channel_config["respond_tag_only"] = respond_tag_only
if respond_team_member_list: if respond_team_member_list:
channel_config["respond_team_member_list"] = respond_team_member_list channel_config["respond_team_member_list"] = respond_team_member_list
if answer_filters: if answer_filters:

View File

@ -26,11 +26,11 @@ export const SlackBotCreationForm = ({
return ( return (
<div> <div>
<div <div
className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50" className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-10 overflow-y-auto overscroll-contain"
onClick={onClose} onClick={onClose}
> >
<div <div
className="bg-gray-800 p-6 rounded border border-gray-700 shadow-lg relative w-1/2 text-sm" className="bg-gray-800 rounded-lg border border-gray-700 shadow-lg relative w-1/2 text-sm"
onClick={(event) => event.stopPropagation()} onClick={(event) => event.stopPropagation()}
> >
<Formik <Formik
@ -44,8 +44,8 @@ export const SlackBotCreationForm = ({
questionmark_prefilter_enabled: ( questionmark_prefilter_enabled: (
existingSlackBotConfig?.channel_config?.answer_filters || [] existingSlackBotConfig?.channel_config?.answer_filters || []
).includes("questionmark_prefilter"), ).includes("questionmark_prefilter"),
respond_sender_only: respond_tag_only:
existingSlackBotConfig?.channel_config?.respond_sender_only || existingSlackBotConfig?.channel_config?.respond_tag_only ||
false, false,
respond_team_member_list: respond_team_member_list:
existingSlackBotConfig?.channel_config existingSlackBotConfig?.channel_config
@ -60,7 +60,7 @@ export const SlackBotCreationForm = ({
channel_names: Yup.array().of(Yup.string()), channel_names: Yup.array().of(Yup.string()),
answer_validity_check_enabled: Yup.boolean().required(), answer_validity_check_enabled: Yup.boolean().required(),
questionmark_prefilter_enabled: Yup.boolean().required(), questionmark_prefilter_enabled: Yup.boolean().required(),
respond_sender_only: Yup.boolean().required(), respond_tag_only: Yup.boolean().required(),
respond_team_member_list: Yup.array().of(Yup.string()).required(), respond_team_member_list: Yup.array().of(Yup.string()).required(),
document_sets: Yup.array().of(Yup.number()), document_sets: Yup.array().of(Yup.number()),
})} })}
@ -98,7 +98,8 @@ export const SlackBotCreationForm = ({
}); });
onClose(); onClose();
} else { } else {
const errorMsg = (await response.json()).detail; const responseJson = await response.json();
const errorMsg = responseJson.detail || responseJson.message;
setPopup({ setPopup({
message: isUpdate message: isUpdate
? `Error updating DanswerBot config - ${errorMsg}` ? `Error updating DanswerBot config - ${errorMsg}`
@ -110,11 +111,12 @@ export const SlackBotCreationForm = ({
> >
{({ isSubmitting, values }) => ( {({ isSubmitting, values }) => (
<Form> <Form>
<h2 className="text-lg font-bold mb-3"> <h2 className="text-xl font-bold mb-3 border-b border-gray-600 pt-4 pb-3 bg-gray-700 px-6">
{isUpdate {isUpdate
? "Update a DanswerBot Config" ? "Update a DanswerBot Config"
: "Create a new DanswerBot Config"} : "Create a new DanswerBot Config"}
</h2> </h2>
<div className="px-6 pb-6">
<TextArrayField <TextArrayField
name="channel_names" name="channel_names"
label="Channel Names:" label="Channel Names:"
@ -132,25 +134,25 @@ export const SlackBotCreationForm = ({
</div> </div>
} }
/> />
<div className="border-t border-gray-700 py-2" /> <div className="border-t border-gray-600 py-2" />
<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-700 py-2" /> <div className="border-t border-gray-600 py-2" />
<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-700 py-2" /> <div className="border-t border-gray-600 py-2" />
<BooleanFormField <BooleanFormField
name="respond_sender_only" name="respond_tag_only"
label="Respond to Sender Only" label="Respond to @DanswerBot Only"
subtext="If set, will respond with a message that is only visible to the sender" subtext="If set, DanswerBot will only respond when directly tagged"
/> />
<div className="border-t border-gray-700 py-2" /> <div className="border-t border-gray-600 py-2" />
<TextArrayField <TextArrayField
name="respond_team_member_list" name="respond_team_member_list"
label="Team Members Emails:" label="Team Members Emails:"
@ -162,18 +164,17 @@ export const SlackBotCreationForm = ({
out the occasional incorrect answer.`} out the occasional incorrect answer.`}
values={values} values={values}
/> />
<div className="border-t border-gray-700 py-2" /> <div className="border-t border-gray-600 py-2" />
<FieldArray <FieldArray
name="document_sets" name="document_sets"
render={(arrayHelpers: ArrayHelpers) => ( render={(arrayHelpers: ArrayHelpers) => (
<div> <div>
<div> <div>
Document Sets: <p className="font-medium">Document Sets:</p>
<br />
<div className="text-xs"> <div className="text-xs">
The document sets that DanswerBot should search The document sets that DanswerBot should search
through. If left blank, DanswerBot will search through through. If left blank, DanswerBot will search
all documents. through all documents.
</div> </div>
</div> </div>
<div className="mb-3 mt-2 flex gap-2 flex-wrap"> <div className="mb-3 mt-2 flex gap-2 flex-wrap">
@ -207,7 +208,9 @@ export const SlackBotCreationForm = ({
} }
}} }}
> >
<div className="my-auto">{documentSet.name}</div> <div className="my-auto">
{documentSet.name}
</div>
</div> </div>
); );
})} })}
@ -215,6 +218,7 @@ export const SlackBotCreationForm = ({
</div> </div>
)} )}
/> />
<div className="border-t border-gray-600 py-2" />
<div className="flex"> <div className="flex">
<button <button
type="submit" type="submit"
@ -228,6 +232,7 @@ export const SlackBotCreationForm = ({
{isUpdate ? "Update!" : "Create!"} {isUpdate ? "Update!" : "Create!"}
</button> </button>
</div> </div>
</div>
</Form> </Form>
)} )}
</Formik> </Formik>

View File

@ -5,7 +5,7 @@ interface SlackBotConfigCreationRequest {
channel_names: string[]; channel_names: string[];
answer_validity_check_enabled: boolean; answer_validity_check_enabled: boolean;
questionmark_prefilter_enabled: boolean; questionmark_prefilter_enabled: boolean;
respond_sender_only: boolean; respond_tag_only: boolean;
respond_team_member_list: string[]; respond_team_member_list: string[];
} }
@ -27,7 +27,7 @@ const buildRequestBodyFromCreationRequest = (
) => { ) => {
return JSON.stringify({ return JSON.stringify({
channel_names: creationRequest.channel_names, channel_names: creationRequest.channel_names,
respond_sender_only: creationRequest.respond_sender_only, respond_tag_only: creationRequest.respond_tag_only,
respond_team_member_list: creationRequest.respond_team_member_list, respond_team_member_list: creationRequest.respond_team_member_list,
document_sets: creationRequest.document_sets, document_sets: creationRequest.document_sets,
answer_filters: buildFiltersFromCreationRequest(creationRequest), answer_filters: buildFiltersFromCreationRequest(creationRequest),

View File

@ -101,6 +101,10 @@ const SlackBotConfigsTable = ({
header: "Questions Only", header: "Questions Only",
key: "question_mark_only", key: "question_mark_only",
}, },
{
header: "Tags Only",
key: "respond_tag_only",
},
{ {
header: "Delete", header: "Delete",
key: "delete", key: "delete",
@ -154,6 +158,12 @@ const SlackBotConfigsTable = ({
) : ( ) : (
<div className="text-gray-300">No</div> <div className="text-gray-300">No</div>
), ),
respond_tag_only:
slackBotConfig.channel_config.respond_tag_only || false ? (
<div className="text-gray-300">Yes</div>
) : (
<div className="text-gray-300">No</div>
),
delete: ( delete: (
<div <div
className="cursor-pointer" className="cursor-pointer"

View File

@ -33,7 +33,7 @@ export const TextFormField = ({
}: TextFormFieldProps) => { }: TextFormFieldProps) => {
return ( return (
<div className="mb-4"> <div className="mb-4">
<label htmlFor={name} className="block"> <label htmlFor={name} className="block font-medium">
{label} {label}
</label> </label>
{subtext && <p className="text-xs mb-1">{subtext}</p>} {subtext && <p className="text-xs mb-1">{subtext}</p>}
@ -82,7 +82,7 @@ export const BooleanFormField = ({
<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" />
<div> <div>
{label} <p className="font-medium">{label}</p>
{subtext && <p className="text-xs">{subtext}</p>} {subtext && <p className="text-xs">{subtext}</p>}
</div> </div>
</label> </label>
@ -113,7 +113,7 @@ export function TextArrayField<T extends Yup.AnyObject>({
}: TextArrayFieldProps<T>) { }: TextArrayFieldProps<T>) {
return ( return (
<div className="mb-4"> <div className="mb-4">
<label htmlFor={name} className="block"> <label htmlFor={name} className="block font-medium">
{label} {label}
</label> </label>
{subtext && <p className="text-xs">{subtext}</p>} {subtext && <p className="text-xs">{subtext}</p>}

View File

@ -7,7 +7,7 @@ export interface PopupSpec {
export const Popup: React.FC<PopupSpec> = ({ message, type }) => ( export const Popup: React.FC<PopupSpec> = ({ message, type }) => (
<div <div
className={`fixed bottom-4 left-4 p-4 rounded-md shadow-lg text-white ${ className={`fixed bottom-4 left-4 p-4 rounded-md shadow-lg text-white z-30 ${
type === "success" ? "bg-green-500" : "bg-red-500" type === "success" ? "bg-green-500" : "bg-red-500"
}`} }`}
> >

View File

@ -245,7 +245,7 @@ export type AnswerFilterOption =
export interface ChannelConfig { export interface ChannelConfig {
channel_names: string[]; channel_names: string[];
respond_sender_only?: boolean; respond_tag_only?: boolean;
respond_team_member_list?: string[]; respond_team_member_list?: string[];
answer_filters?: AnswerFilterOption[]; answer_filters?: AnswerFilterOption[];
} }