Spaces:
Paused
Paused
| import { | |
| FC, | |
| KeyboardEvent, | |
| useCallback, | |
| useEffect, | |
| useRef, | |
| useState, | |
| } from 'react'; | |
| import { useTranslation } from 'next-i18next'; | |
| import { DEFAULT_SYSTEM_PROMPT } from '@/utils/app/const'; | |
| import { Conversation } from '@/types/chat'; | |
| import { Prompt } from '@/types/prompt'; | |
| import { PromptList } from './PromptList'; | |
| import { VariableModal } from './VariableModal'; | |
| interface Props { | |
| conversation: Conversation; | |
| prompts: Prompt[]; | |
| onChangePrompt: (prompt: string) => void; | |
| } | |
| export const SystemPrompt: FC<Props> = ({ | |
| conversation, | |
| prompts, | |
| onChangePrompt, | |
| }) => { | |
| const { t } = useTranslation('chat'); | |
| const [value, setValue] = useState<string>(''); | |
| const [activePromptIndex, setActivePromptIndex] = useState(0); | |
| const [showPromptList, setShowPromptList] = useState(false); | |
| const [promptInputValue, setPromptInputValue] = useState(''); | |
| const [variables, setVariables] = useState<string[]>([]); | |
| const [isModalVisible, setIsModalVisible] = useState(false); | |
| const textareaRef = useRef<HTMLTextAreaElement>(null); | |
| const promptListRef = useRef<HTMLUListElement | null>(null); | |
| const filteredPrompts = prompts.filter((prompt) => | |
| prompt.name.toLowerCase().includes(promptInputValue.toLowerCase()), | |
| ); | |
| const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => { | |
| const value = e.target.value; | |
| const maxLength = conversation.model.maxLength; | |
| if (value.length > maxLength) { | |
| alert( | |
| t( | |
| `Prompt limit is {{maxLength}} characters. You have entered {{valueLength}} characters.`, | |
| { maxLength, valueLength: value.length }, | |
| ), | |
| ); | |
| return; | |
| } | |
| setValue(value); | |
| updatePromptListVisibility(value); | |
| if (value.length > 0) { | |
| onChangePrompt(value); | |
| } | |
| }; | |
| const handleInitModal = () => { | |
| const selectedPrompt = filteredPrompts[activePromptIndex]; | |
| setValue((prevVal) => { | |
| const newContent = prevVal?.replace(/\/\w*$/, selectedPrompt.content); | |
| return newContent; | |
| }); | |
| handlePromptSelect(selectedPrompt); | |
| setShowPromptList(false); | |
| }; | |
| const parseVariables = (content: string) => { | |
| const regex = /{{(.*?)}}/g; | |
| const foundVariables = []; | |
| let match; | |
| while ((match = regex.exec(content)) !== null) { | |
| foundVariables.push(match[1]); | |
| } | |
| return foundVariables; | |
| }; | |
| const updatePromptListVisibility = useCallback((text: string) => { | |
| const match = text.match(/\/\w*$/); | |
| if (match) { | |
| setShowPromptList(true); | |
| setPromptInputValue(match[0].slice(1)); | |
| } else { | |
| setShowPromptList(false); | |
| setPromptInputValue(''); | |
| } | |
| }, []); | |
| const handlePromptSelect = (prompt: Prompt) => { | |
| const parsedVariables = parseVariables(prompt.content); | |
| setVariables(parsedVariables); | |
| if (parsedVariables.length > 0) { | |
| setIsModalVisible(true); | |
| } else { | |
| const updatedContent = value?.replace(/\/\w*$/, prompt.content); | |
| setValue(updatedContent); | |
| onChangePrompt(updatedContent); | |
| updatePromptListVisibility(prompt.content); | |
| } | |
| }; | |
| const handleSubmit = (updatedVariables: string[]) => { | |
| const newContent = value?.replace(/{{(.*?)}}/g, (match, variable) => { | |
| const index = variables.indexOf(variable); | |
| return updatedVariables[index]; | |
| }); | |
| setValue(newContent); | |
| onChangePrompt(newContent); | |
| if (textareaRef && textareaRef.current) { | |
| textareaRef.current.focus(); | |
| } | |
| }; | |
| const handleKeyDown = (e: KeyboardEvent<HTMLTextAreaElement>) => { | |
| if (showPromptList) { | |
| if (e.key === 'ArrowDown') { | |
| e.preventDefault(); | |
| setActivePromptIndex((prevIndex) => | |
| prevIndex < prompts.length - 1 ? prevIndex + 1 : prevIndex, | |
| ); | |
| } else if (e.key === 'ArrowUp') { | |
| e.preventDefault(); | |
| setActivePromptIndex((prevIndex) => | |
| prevIndex > 0 ? prevIndex - 1 : prevIndex, | |
| ); | |
| } else if (e.key === 'Tab') { | |
| e.preventDefault(); | |
| setActivePromptIndex((prevIndex) => | |
| prevIndex < prompts.length - 1 ? prevIndex + 1 : 0, | |
| ); | |
| } else if (e.key === 'Enter') { | |
| e.preventDefault(); | |
| handleInitModal(); | |
| } else if (e.key === 'Escape') { | |
| e.preventDefault(); | |
| setShowPromptList(false); | |
| } else { | |
| setActivePromptIndex(0); | |
| } | |
| } | |
| }; | |
| useEffect(() => { | |
| if (textareaRef && textareaRef.current) { | |
| textareaRef.current.style.height = 'inherit'; | |
| textareaRef.current.style.height = `${textareaRef.current?.scrollHeight}px`; | |
| } | |
| }, [value]); | |
| useEffect(() => { | |
| if (conversation.prompt) { | |
| setValue(conversation.prompt); | |
| } else { | |
| setValue(DEFAULT_SYSTEM_PROMPT); | |
| } | |
| }, [conversation]); | |
| useEffect(() => { | |
| const handleOutsideClick = (e: MouseEvent) => { | |
| if ( | |
| promptListRef.current && | |
| !promptListRef.current.contains(e.target as Node) | |
| ) { | |
| setShowPromptList(false); | |
| } | |
| }; | |
| window.addEventListener('click', handleOutsideClick); | |
| return () => { | |
| window.removeEventListener('click', handleOutsideClick); | |
| }; | |
| }, []); | |
| return ( | |
| <div className="flex flex-col"> | |
| <label className="mb-2 text-left text-neutral-700 dark:text-neutral-400"> | |
| {t('System Prompt')} | |
| </label> | |
| <textarea | |
| ref={textareaRef} | |
| className="w-full rounded-lg border border-neutral-200 bg-transparent px-4 py-3 text-neutral-900 dark:border-neutral-600 dark:text-neutral-100" | |
| style={{ | |
| resize: 'none', | |
| bottom: `${textareaRef?.current?.scrollHeight}px`, | |
| maxHeight: '300px', | |
| overflow: `${ | |
| textareaRef.current && textareaRef.current.scrollHeight > 400 | |
| ? 'auto' | |
| : 'hidden' | |
| }`, | |
| }} | |
| placeholder={ | |
| t(`Enter a prompt or type "/" to select a prompt...`) || '' | |
| } | |
| value={t(value) || ''} | |
| rows={1} | |
| onChange={handleChange} | |
| onKeyDown={handleKeyDown} | |
| /> | |
| {showPromptList && filteredPrompts.length > 0 && ( | |
| <div> | |
| <PromptList | |
| activePromptIndex={activePromptIndex} | |
| prompts={filteredPrompts} | |
| onSelect={handleInitModal} | |
| onMouseOver={setActivePromptIndex} | |
| promptListRef={promptListRef} | |
| /> | |
| </div> | |
| )} | |
| {isModalVisible && ( | |
| <VariableModal | |
| prompt={prompts[activePromptIndex]} | |
| variables={variables} | |
| onSubmit={handleSubmit} | |
| onClose={() => setIsModalVisible(false)} | |
| /> | |
| )} | |
| </div> | |
| ); | |
| }; | |