mirror of
https://github.com/danswer-ai/danswer.git
synced 2025-07-28 13:53:28 +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,83 +111,83 @@ 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>
|
||||||
<TextArrayField
|
<div className="px-6 pb-6">
|
||||||
name="channel_names"
|
<TextArrayField
|
||||||
label="Channel Names:"
|
name="channel_names"
|
||||||
values={values}
|
label="Channel Names:"
|
||||||
subtext={
|
values={values}
|
||||||
<div>
|
subtext={
|
||||||
The names of the Slack channels you want this
|
<div>
|
||||||
configuration to apply to. For example,
|
The names of the Slack channels you want this
|
||||||
'#ask-danswer'.
|
configuration to apply to. For example,
|
||||||
<br />
|
'#ask-danswer'.
|
||||||
<br />
|
<br />
|
||||||
<i>NOTE</i>: you still need to add DanswerBot to the
|
<br />
|
||||||
channel(s) in Slack itself. Setting this config will not
|
<i>NOTE</i>: you still need to add DanswerBot to the
|
||||||
auto-add the bot to the channel.
|
channel(s) in Slack itself. Setting this config will not
|
||||||
</div>
|
auto-add the bot to the channel.
|
||||||
}
|
</div>
|
||||||
/>
|
}
|
||||||
<div className="border-t border-gray-700 py-2" />
|
/>
|
||||||
<BooleanFormField
|
<div className="border-t border-gray-600 py-2" />
|
||||||
name="answer_validity_check_enabled"
|
<BooleanFormField
|
||||||
label="Hide Non-Answers"
|
name="answer_validity_check_enabled"
|
||||||
subtext="If set, will only answer questions that the model determines it can answer"
|
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
|
<div className="border-t border-gray-600 py-2" />
|
||||||
name="questionmark_prefilter_enabled"
|
<BooleanFormField
|
||||||
label="Only respond to questions"
|
name="questionmark_prefilter_enabled"
|
||||||
subtext="If set, will only respond to messages that contain a question mark"
|
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
|
<div className="border-t border-gray-600 py-2" />
|
||||||
name="respond_sender_only"
|
<BooleanFormField
|
||||||
label="Respond to Sender Only"
|
name="respond_tag_only"
|
||||||
subtext="If set, will respond with a message that is only visible to the sender"
|
label="Respond to @DanswerBot Only"
|
||||||
/>
|
subtext="If set, DanswerBot will only respond when directly tagged"
|
||||||
<div className="border-t border-gray-700 py-2" />
|
/>
|
||||||
<TextArrayField
|
<div className="border-t border-gray-600 py-2" />
|
||||||
name="respond_team_member_list"
|
<TextArrayField
|
||||||
label="Team Members Emails:"
|
name="respond_team_member_list"
|
||||||
subtext={`If specified, DanswerBot responses will only be
|
label="Team Members Emails:"
|
||||||
|
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
|
||||||
"assistant" mode, where it helps the team members find
|
"assistant" mode, where it helps the team members find
|
||||||
answers, but let's them build on top of DanswerBot's response / throw
|
answers, but let's them build on top of DanswerBot's response / throw
|
||||||
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>
|
||||||
Document Sets:
|
<div>
|
||||||
<br />
|
<p className="font-medium">Document Sets:</p>
|
||||||
<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">
|
{documentSets.map((documentSet) => {
|
||||||
{documentSets.map((documentSet) => {
|
const ind = values.document_sets.indexOf(
|
||||||
const ind = values.document_sets.indexOf(
|
documentSet.id
|
||||||
documentSet.id
|
);
|
||||||
);
|
let isSelected = ind !== -1;
|
||||||
let isSelected = ind !== -1;
|
return (
|
||||||
return (
|
<div
|
||||||
<div
|
key={documentSet.id}
|
||||||
key={documentSet.id}
|
className={
|
||||||
className={
|
`
|
||||||
`
|
|
||||||
px-3
|
px-3
|
||||||
py-1
|
py-1
|
||||||
rounded-lg
|
rounded-lg
|
||||||
@@ -195,38 +196,42 @@ export const SlackBotCreationForm = ({
|
|||||||
w-fit
|
w-fit
|
||||||
flex
|
flex
|
||||||
cursor-pointer ` +
|
cursor-pointer ` +
|
||||||
(isSelected
|
(isSelected
|
||||||
? " bg-gray-600"
|
? " bg-gray-600"
|
||||||
: " bg-gray-900 hover:bg-gray-700")
|
: " bg-gray-900 hover:bg-gray-700")
|
||||||
}
|
|
||||||
onClick={() => {
|
|
||||||
if (isSelected) {
|
|
||||||
arrayHelpers.remove(ind);
|
|
||||||
} else {
|
|
||||||
arrayHelpers.push(documentSet.id);
|
|
||||||
}
|
}
|
||||||
}}
|
onClick={() => {
|
||||||
>
|
if (isSelected) {
|
||||||
<div className="my-auto">{documentSet.name}</div>
|
arrayHelpers.remove(ind);
|
||||||
</div>
|
} else {
|
||||||
);
|
arrayHelpers.push(documentSet.id);
|
||||||
})}
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="my-auto">
|
||||||
|
{documentSet.name}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
)}
|
/>
|
||||||
/>
|
<div className="border-t border-gray-600 py-2" />
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
className={
|
className={
|
||||||
"bg-slate-500 hover:bg-slate-700 text-white " +
|
"bg-slate-500 hover:bg-slate-700 text-white " +
|
||||||
"font-bold py-2 px-4 rounded focus:outline-none " +
|
"font-bold py-2 px-4 rounded focus:outline-none " +
|
||||||
"focus:shadow-outline w-full max-w-sm mx-auto"
|
"focus:shadow-outline w-full max-w-sm mx-auto"
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{isUpdate ? "Update!" : "Create!"}
|
{isUpdate ? "Update!" : "Create!"}
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
)}
|
)}
|
||||||
|
@@ -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