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
send_to: list[str] | None = None
respond_sender_only = False
respond_tag_only = False
respond_team_member_list = None
if slack_bot_config and 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']}"
)
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 = (
channel_conf.get("respond_team_member_list") or None
)
if sender_id and (respond_sender_only or is_bot_msg):
send_to = [sender_id]
elif respond_team_member_list:
# `skip_filters=True` -> this is a tag, so we *should* respond
if respond_tag_only and not skip_filters:
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)
# 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
# the ephemeral message. This also will give the user a notification which ephemeral message does not.
if respond_sender_only:
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:
if respond_team_member_list:
respond_in_thread(
client=client,
channel=channel,

View File

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

View File

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

View File

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

View File

@ -26,11 +26,11 @@ export const SlackBotCreationForm = ({
return (
<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}
>
<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()}
>
<Formik
@ -44,8 +44,8 @@ export const SlackBotCreationForm = ({
questionmark_prefilter_enabled: (
existingSlackBotConfig?.channel_config?.answer_filters || []
).includes("questionmark_prefilter"),
respond_sender_only:
existingSlackBotConfig?.channel_config?.respond_sender_only ||
respond_tag_only:
existingSlackBotConfig?.channel_config?.respond_tag_only ||
false,
respond_team_member_list:
existingSlackBotConfig?.channel_config
@ -60,7 +60,7 @@ export const SlackBotCreationForm = ({
channel_names: Yup.array().of(Yup.string()),
answer_validity_check_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(),
document_sets: Yup.array().of(Yup.number()),
})}
@ -98,7 +98,8 @@ export const SlackBotCreationForm = ({
});
onClose();
} else {
const errorMsg = (await response.json()).detail;
const responseJson = await response.json();
const errorMsg = responseJson.detail || responseJson.message;
setPopup({
message: isUpdate
? `Error updating DanswerBot config - ${errorMsg}`
@ -110,83 +111,83 @@ export const SlackBotCreationForm = ({
>
{({ isSubmitting, values }) => (
<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
? "Update a DanswerBot Config"
: "Create a new DanswerBot Config"}
</h2>
<TextArrayField
name="channel_names"
label="Channel Names:"
values={values}
subtext={
<div>
The names of the Slack channels you want this
configuration to apply to. For example,
&apos;#ask-danswer&apos;.
<br />
<br />
<i>NOTE</i>: you still need to add DanswerBot to the
channel(s) in Slack itself. Setting this config will not
auto-add the bot to the channel.
</div>
}
/>
<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" />
<BooleanFormField
name="questionmark_prefilter_enabled"
label="Only respond to questions"
subtext="If set, will only respond to messages that contain a question mark"
/>
<div className="border-t border-gray-700 py-2" />
<BooleanFormField
name="respond_sender_only"
label="Respond to Sender Only"
subtext="If set, will respond with a message that is only visible to the sender"
/>
<div className="border-t border-gray-700 py-2" />
<TextArrayField
name="respond_team_member_list"
label="Team Members Emails:"
subtext={`If specified, DanswerBot responses will only be
<div className="px-6 pb-6">
<TextArrayField
name="channel_names"
label="Channel Names:"
values={values}
subtext={
<div>
The names of the Slack channels you want this
configuration to apply to. For example,
&apos;#ask-danswer&apos;.
<br />
<br />
<i>NOTE</i>: you still need to add DanswerBot to the
channel(s) in Slack itself. Setting this config will not
auto-add the bot to the channel.
</div>
}
/>
<div className="border-t border-gray-600 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-600 py-2" />
<BooleanFormField
name="questionmark_prefilter_enabled"
label="Only respond to questions"
subtext="If set, will only respond to messages that contain a question mark"
/>
<div className="border-t border-gray-600 py-2" />
<BooleanFormField
name="respond_tag_only"
label="Respond to @DanswerBot Only"
subtext="If set, DanswerBot will only respond when directly tagged"
/>
<div className="border-t border-gray-600 py-2" />
<TextArrayField
name="respond_team_member_list"
label="Team Members Emails:"
subtext={`If specified, DanswerBot responses will only be
visible to members in this list. This is
useful if you want DanswerBot to operate in an
"assistant" mode, where it helps the team members find
answers, but let's them build on top of DanswerBot's response / throw
out the occasional incorrect answer.`}
values={values}
/>
<div className="border-t border-gray-700 py-2" />
<FieldArray
name="document_sets"
render={(arrayHelpers: ArrayHelpers) => (
<div>
values={values}
/>
<div className="border-t border-gray-600 py-2" />
<FieldArray
name="document_sets"
render={(arrayHelpers: ArrayHelpers) => (
<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>
<p className="font-medium">Document Sets:</p>
<div className="text-xs">
The document sets that DanswerBot should search
through. If left blank, DanswerBot will search
through all documents.
</div>
</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={
`
<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
@ -195,38 +196,42 @@ export const SlackBotCreationForm = ({
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);
(isSelected
? " bg-gray-600"
: " bg-gray-900 hover:bg-gray-700")
}
}}
>
<div className="my-auto">{documentSet.name}</div>
</div>
);
})}
onClick={() => {
if (isSelected) {
arrayHelpers.remove(ind);
} else {
arrayHelpers.push(documentSet.id);
}
}}
>
<div className="my-auto">
{documentSet.name}
</div>
</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 className="border-t border-gray-600 py-2" />
<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>
</div>
</Form>
)}

View File

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

View File

@ -101,6 +101,10 @@ const SlackBotConfigsTable = ({
header: "Questions Only",
key: "question_mark_only",
},
{
header: "Tags Only",
key: "respond_tag_only",
},
{
header: "Delete",
key: "delete",
@ -154,6 +158,12 @@ const SlackBotConfigsTable = ({
) : (
<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: (
<div
className="cursor-pointer"

View File

@ -33,7 +33,7 @@ export const TextFormField = ({
}: TextFormFieldProps) => {
return (
<div className="mb-4">
<label htmlFor={name} className="block">
<label htmlFor={name} className="block font-medium">
{label}
</label>
{subtext && <p className="text-xs mb-1">{subtext}</p>}
@ -82,7 +82,7 @@ export const BooleanFormField = ({
<label className="flex text-sm">
<Field name={name} type="checkbox" className="mx-3 px-5" />
<div>
{label}
<p className="font-medium">{label}</p>
{subtext && <p className="text-xs">{subtext}</p>}
</div>
</label>
@ -113,7 +113,7 @@ export function TextArrayField<T extends Yup.AnyObject>({
}: TextArrayFieldProps<T>) {
return (
<div className="mb-4">
<label htmlFor={name} className="block">
<label htmlFor={name} className="block font-medium">
{label}
</label>
{subtext && <p className="text-xs">{subtext}</p>}

View File

@ -7,7 +7,7 @@ export interface PopupSpec {
export const Popup: React.FC<PopupSpec> = ({ message, type }) => (
<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"
}`}
>

View File

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