mirror of
https://github.com/danswer-ai/danswer.git
synced 2025-07-21 10:33:03 +02:00
Replace 'respond_sender_only' with 'respond_tag_only' + prettify UI
This commit is contained in:
@ -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,
|
||||||
|
@ -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):
|
||||||
|
@ -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]:
|
||||||
|
@ -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:
|
||||||
|
@ -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>
|
||||||
|
@ -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),
|
||||||
|
@ -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"
|
||||||
|
@ -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>}
|
||||||
|
@ -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"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
|
@ -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[];
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user