🧪 feat: Prompt Dropdown Variable; style: Add Markdown Support (#3681)

* feat: Add extended inputs for promts library variables

* feat: Add maxRows prop to VariableForm input field

* 📩 feat: invite user (#3012)

* feat: basic invite-user script

* feat: add invite user functionality and registration validation middleware

* fix: invite user fixes

* refactor: consolidate direct model access to a central place of functions

* style(Registration): add spinner to continue button

* refactor: import ordrer

* feat: improve invite user script and error handling

* fix: merge conflict

* refactor: remove `console.log` and use `logger`

* fix: token operation and checkinvite issues

* bring back comment and remove console log

* fix: return invalid token when token is not found

* fix: getInvite fix

* refactor: Update Token.js to use async/await syntax for update and delete operations

* feat: Refactor Token.js to use async/await syntax for createToken and findToken functions

* refactor(inviteUser): define functions outside of module.exports

* Update AuthService.js

---------

Co-authored-by: Danny Avila <danny@librechat.ai>

* style: improve OpenAI.tsx input field focus styling

* refactor: update import statement in Input.tsx

* refactor: remove multi-line

* refactor:  update placeholder text to use localization

* style: new dropdown variable info and markdown styling for info

* Add ReactMarkdown

* chore: styling, import order

* refactor: update ReactMarkdown usage in VariableForm

* style: remove markdown class

* refactor: update mobile styling and use code renderer

* style(InputWithDropDown): update focus trigger style

* style(OptionsPopover): update Save As Preset `focus` and `dark:bg`

---------

Co-authored-by: Konstantin Meshcheryakov <kmeshcheryakov@klika-tech.com>
Co-authored-by: Marco Beretta <81851188+berry-13@users.noreply.github.com>
Co-authored-by: bsu3338 <bsu3338@users.noreply.github.com>
This commit is contained in:
Danny Avila 2024-08-18 05:52:05 -04:00 committed by GitHub
parent bbb9324447
commit d3a20357e9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 473 additions and 110 deletions

View file

@ -1,12 +1,64 @@
import { useMemo } from 'react';
import remarkGfm from 'remark-gfm';
import remarkMath from 'remark-math';
import supersub from 'remark-supersub';
import rehypeKatex from 'rehype-katex';
import ReactMarkdown from 'react-markdown';
import rehypeHighlight from 'rehype-highlight';
import { useForm, useFieldArray, Controller, useWatch } from 'react-hook-form';
import type { TPromptGroup } from 'librechat-data-provider';
import { extractVariableInfo, wrapVariable, replaceSpecialVars } from '~/utils';
import {
cn,
wrapVariable,
defaultTextProps,
replaceSpecialVars,
extractVariableInfo,
} from '~/utils';
import { useAuthContext, useLocalize, useSubmitMessage } from '~/hooks';
import { Textarea } from '~/components/ui';
import { TextareaAutosize, InputWithDropdown } from '~/components/ui';
import { code } from '~/components/Chat/Messages/Content/Markdown';
type FieldType = 'text' | 'select';
type FieldConfig = {
variable: string;
type: FieldType;
options?: string[];
};
type FormValues = {
fields: { variable: string; value: string }[];
fields: { variable: string; value: string; config: FieldConfig }[];
};
/**
* Variable Format Guide:
*
* Variables in prompts should be enclosed in double curly braces: {{variable}}
*
* Simple text input:
* {{variable_name}}
*
* Dropdown select with predefined options:
* {{variable_name:option1|option2|option3}}
*
* All dropdown selects allow custom input in addition to predefined options.
*
* Examples:
* {{name}} - Simple text input for a name
* {{tone:formal|casual|business casual}} - Dropdown for tone selection with custom input option
*
* Note: The order of variables in the prompt will be preserved in the input form.
*/
const parseFieldConfig = (variable: string): FieldConfig => {
const content = variable;
if (content.includes(':')) {
const [name, options] = content.split(':');
if (options && options.includes('|')) {
return { variable: name, type: 'select', options: options.split('|') };
}
}
return { variable: content, type: 'text' };
};
export default function VariableForm({
@ -32,7 +84,11 @@ export default function VariableForm({
const { submitPrompt } = useSubmitMessage();
const { control, handleSubmit } = useForm<FormValues>({
defaultValues: {
fields: uniqueVariables.map((variable) => ({ variable: wrapVariable(variable), value: '' })),
fields: uniqueVariables.map((variable) => ({
variable: wrapVariable(variable),
value: '',
config: parseFieldConfig(variable),
})),
},
});
@ -50,31 +106,16 @@ export default function VariableForm({
return null;
}
const generateHighlightedText = () => {
const generateHighlightedMarkdown = () => {
let tempText = mainText;
const parts: JSX.Element[] = [];
allVariables.forEach((variable, index) => {
allVariables.forEach((variable) => {
const placeholder = `{{${variable}}}`;
const partsBeforePlaceholder = tempText.split(placeholder);
const fieldIndex = variableIndexMap.get(variable) as string | number;
const fieldValue = fieldValues[fieldIndex].value as string;
parts.push(
<span key={`before-${index}`}>{partsBeforePlaceholder[0]}</span>,
<span
key={`highlight-${index}`}
className="rounded bg-yellow-100 p-1 font-medium dark:text-gray-800"
>
{fieldValue !== '' ? fieldValue : placeholder}
</span>,
);
tempText = partsBeforePlaceholder.slice(1).join(placeholder);
const highlightText = fieldValue !== '' ? fieldValue : placeholder;
tempText = tempText.replaceAll(placeholder, `**${highlightText}**`);
});
parts.push(<span key="last-part">{tempText}</span>);
return parts;
return tempText;
};
const onSubmit = (data: FormValues) => {
@ -91,32 +132,53 @@ export default function VariableForm({
};
return (
<div className="container mx-auto p-1">
<form onSubmit={handleSubmit(onSubmit)} className="space-y-6">
<div className="mb-6 max-h-screen overflow-auto rounded-md bg-gray-100 p-4 dark:bg-gray-700/50 dark:text-gray-300 md:max-h-80">
<p className="text-md whitespace-pre-wrap">{generateHighlightedText()}</p>
<div className="mx-auto p-1 md:container">
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
<div className="mb-6 max-h-screen max-w-[90vw] overflow-auto rounded-md bg-gray-100 p-4 text-text-secondary dark:bg-gray-700/50 sm:max-w-full md:max-h-80">
<ReactMarkdown
remarkPlugins={[supersub, remarkGfm, [remarkMath, { singleDollarTextMath: true }]]}
rehypePlugins={[
[rehypeKatex, { output: 'mathml' }],
[rehypeHighlight, { ignoreMissing: true }],
]}
components={{ code }}
className="prose dark:prose-invert light dark:text-gray-70 my-1 max-h-[50vh] break-words"
>
{generateHighlightedMarkdown()}
</ReactMarkdown>
</div>
<div className="grid grid-cols-2 gap-4 sm:grid-cols-3 md:grid-cols-4">
<div className="space-y-4">
{fields.map((field, index) => (
<div key={field.id} className="flex flex-col">
<div key={field.id} className="flex flex-col space-y-2">
<Controller
name={`fields.${index}.value`}
control={control}
render={({ field }) => (
<Textarea
{...field}
id={`fields.${index}.value`}
className="input text-grey-darker h-10 rounded border px-3 py-2 focus:bg-white dark:border-gray-500 dark:focus:bg-gray-700"
placeholder={uniqueVariables[index]}
onKeyDown={(e) => {
// Submit the form on enter like you would with an Input component
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
handleSubmit((data) => onSubmit(data))();
}
}}
/>
)}
render={({ field: inputField }) => {
if (field.config.type === 'select') {
return (
<InputWithDropdown
{...inputField}
id={`fields.${index}.value`}
className={cn(defaultTextProps, 'focus:bg-surface-tertiary')}
placeholder={localize('com_ui_enter_var', field.config.variable)}
options={field.config.options || []}
/>
);
}
return (
<TextareaAutosize
{...inputField}
id={`fields.${index}.value`}
className={cn(
defaultTextProps,
'rounded px-3 py-2 focus:bg-surface-tertiary',
)}
placeholder={localize('com_ui_enter_var', field.config.variable)}
maxRows={8}
/>
);
}}
/>
</div>
))}
@ -124,7 +186,7 @@ export default function VariableForm({
<div className="flex justify-end">
<button
type="submit"
className="btn rounded bg-green-500 font-bold text-white transition-all hover:bg-green-600"
className="btn rounded bg-green-500 px-4 py-2 font-bold text-white transition-all hover:bg-green-600"
>
{localize('com_ui_submit')}
</button>