mirror of
https://github.com/danswer-ai/danswer.git
synced 2025-09-22 17:16:20 +02:00
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:
@@ -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">
|
||||
|
@@ -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,
|
||||
});
|
||||
};
|
||||
|
||||
|
@@ -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>
|
||||
|
@@ -161,6 +161,7 @@ export interface StandardAnswer {
|
||||
id: number;
|
||||
keyword: string;
|
||||
answer: string;
|
||||
match_regex: boolean;
|
||||
categories: StandardAnswerCategory[];
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user