Support regex in standard answers (#2377)

* Support regex in standard answers

* fix mypy

* Add match_regex boolean column to StandardAnswer

* Add match_regex flag and validation to Pydantic models

* GET /manage/admin/standard-answer: add match_regex to create_standard_answer

* PATCH /manage/admin/standard-answer/🆔 add match_regex to update_standard_answer

* Add "Match Regex" toggle to standard answer form

* Decode error pattern in case it's bytes

* Refactor regex support to use match_regex flag instead of supplemental tuple

* Better error handling for invalid regexes

* Show "match regex" in table and style keywords appropriately

* Fix stale UI copy for non-"match_regex" branch

* Fix stale docstring in find_matching_standard_answers

* Update down_revision to reflect most recent migration

* Update UI copy

* Initial implementation of match group display

* Fix pydantic StandardAnswer vs SQLAlchemy StandardAnswer model usage

* Update docstring return type

* Fix missing key prop

---------

Co-authored-by: Hyeong Joon Suh <hyeongjoonsuh@Hyeongs-MacBook-Pro.local>
Co-authored-by: danswer-trial <danswer-trial@danswer-trials-MacBook-Pro.local>
This commit is contained in:
hj-danswer
2024-09-13 17:07:42 -07:00
committed by GitHub
parent da6e46ae75
commit 3cb00de6d4
10 changed files with 171 additions and 41 deletions

View File

@@ -14,6 +14,7 @@ import {
import {
TextFormField,
MarkdownFormField,
BooleanFormField,
} from "@/components/admin/connectors/Field";
import MultiSelectDropdown from "@/components/MultiSelectDropdown";
@@ -41,10 +42,13 @@ export const StandardAnswerCreationForm = ({
categories: existingStandardAnswer
? existingStandardAnswer.categories
: [],
matchRegex: existingStandardAnswer
? existingStandardAnswer.match_regex
: false,
}}
validationSchema={Yup.object().shape({
keyword: Yup.string()
.required("Keyword or phrase is required")
.required("Keywords or pattern is required")
.max(255)
.min(1),
answer: Yup.string().required("Answer is required").min(1),
@@ -86,18 +90,34 @@ export const StandardAnswerCreationForm = ({
>
{({ isSubmitting, values, setFieldValue }) => (
<Form>
<TextFormField
name="keyword"
label="Keywords"
tooltip="If all specified keywords are in the question, then we will respond with the answer below"
placeholder="e.g. Wifi Password"
autoCompleteDisabled={true}
{values.matchRegex ? (
<TextFormField
name="keyword"
label="Regex pattern"
isCode
tooltip="Triggers if the question matches this regex pattern (using Python `re.search()`)"
placeholder="(?:it|support)\s*ticket"
/>
) : (
<TextFormField
name="keyword"
label="Keywords"
tooltip="Triggers if the question contains all of these keywords, in any order."
placeholder="it ticket"
autoCompleteDisabled={true}
/>
)}
<BooleanFormField
subtext="Match a regex pattern instead of an exact keyword"
optional
label="Match regex"
name="matchRegex"
/>
<div className="w-full">
<MarkdownFormField
name="answer"
label="Answer"
placeholder="The answer in markdown"
placeholder="The answer in Markdown. Example: If you need any help from the IT team, please email internalsupport@company.com"
/>
</div>
<div className="w-4/12">

View File

@@ -6,6 +6,7 @@ export interface StandardAnswerCreationRequest {
keyword: string;
answer: string;
categories: number[];
matchRegex: boolean;
}
const buildRequestBodyFromStandardAnswerCategoryCreationRequest = (
@@ -48,6 +49,7 @@ const buildRequestBodyFromStandardAnswerCreationRequest = (
keyword: request.keyword,
answer: request.answer,
categories: request.categories,
match_regex: request.matchRegex,
});
};

View File

@@ -26,6 +26,7 @@ import { FilterDropdown } from "@/components/search/filtering/FilterDropdown";
import { FiTag } from "react-icons/fi";
import { SelectedBubble } from "@/components/search/filtering/Filters";
import { PageSelector } from "@/components/PageSelector";
import { CustomCheckbox } from "@/components/CustomCheckbox";
const NUM_RESULTS_PER_PAGE = 10;
@@ -36,15 +37,23 @@ const RowTemplate = ({
entries,
}: {
id: number;
entries: [Displayable, Displayable, Displayable, Displayable, Displayable];
entries: [
Displayable,
Displayable,
Displayable,
Displayable,
Displayable,
Displayable,
];
}) => {
return (
<TableRow key={id}>
<TableCell className="w-1/24">{entries[0]}</TableCell>
<TableCell className="w-2/12">{entries[1]}</TableCell>
<TableCell className="w-2/12">{entries[2]}</TableCell>
<TableCell className="w-7/12 overflow-auto">{entries[3]}</TableCell>
<TableCell className="w-1/24">{entries[4]}</TableCell>
<TableCell className="w-1/24">{entries[3]}</TableCell>
<TableCell className="w-7/12 overflow-auto">{entries[4]}</TableCell>
<TableCell className="w-1/24">{entries[5]}</TableCell>
</TableRow>
);
};
@@ -108,7 +117,15 @@ const StandardAnswersTableRow = ({
<CategoryBubble key={category.id} name={category.name} />
))}
</div>,
standardAnswer.keyword,
<ReactMarkdown key={`keyword-${standardAnswer.id}`}>
{standardAnswer.match_regex
? `\`${standardAnswer.keyword}\``
: standardAnswer.keyword}
</ReactMarkdown>,
<CustomCheckbox
key={`match_regex-${standardAnswer.id}`}
checked={standardAnswer.match_regex}
/>,
<ReactMarkdown
key={`answer-${standardAnswer.id}`}
className="prose"
@@ -147,13 +164,15 @@ const StandardAnswersTable = ({
const columns = [
{ name: "", key: "edit" },
{ name: "Categories", key: "category" },
{ name: "Keyword/Phrase", key: "keyword" },
{ name: "Keywords/Pattern", key: "keyword" },
{ name: "Match regex?", key: "match_regex" },
{ name: "Answer", key: "answer" },
{ name: "", key: "delete" },
];
const filteredStandardAnswers = standardAnswers.filter((standardAnswer) => {
const { answer, id, categories, ...fieldsToSearch } = standardAnswer;
const { answer, id, categories, match_regex, ...fieldsToSearch } =
standardAnswer;
const cleanedQuery = query.toLowerCase();
const searchMatch = Object.values(fieldsToSearch).some((value) => {
return value.toLowerCase().includes(cleanedQuery);
@@ -285,7 +304,7 @@ const StandardAnswersTable = ({
/>
))
) : (
<RowTemplate id={0} entries={["", "", "", "", ""]} />
<RowTemplate id={0} entries={["", "", "", "", "", ""]} />
)}
</TableBody>
</Table>

View File

@@ -161,6 +161,7 @@ export interface StandardAnswer {
id: number;
keyword: string;
answer: string;
match_regex: boolean;
categories: StandardAnswerCategory[];
}