diff --git a/README.md b/README.md index 0127dd0fa..b07a01f96 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ https://thinktank.ottomator.ai - ✅ Ability to revert code to earlier version (@wonderwhy-er) - ✅ Cohere Integration (@hasanraiyan) - ✅ Dynamic model max token length (@hasanraiyan) +- ✅ Prompt caching (@SujalXplores) - ⬜ **HIGH PRIORITY** - Prevent Bolt from rewriting files as often (file locking and diffs) - ⬜ **HIGH PRIORITY** - Better prompting for smaller LLMs (code window sometimes doesn't start) - ⬜ **HIGH PRIORITY** - Load local projects into the app @@ -42,7 +43,6 @@ https://thinktank.ottomator.ai - ⬜ Perplexity Integration - ⬜ Vertex AI Integration - ⬜ Deploy directly to Vercel/Netlify/other similar platforms -- ⬜ Prompt caching - ⬜ Better prompt enhancing - ⬜ Have LLM plan the project in a MD file for better results/transparency - ⬜ VSCode Integration with git-like confirmations diff --git a/app/components/chat/Chat.client.tsx b/app/components/chat/Chat.client.tsx index 984182072..6cbd96202 100644 --- a/app/components/chat/Chat.client.tsx +++ b/app/components/chat/Chat.client.tsx @@ -6,19 +6,20 @@ import { useStore } from '@nanostores/react'; import type { Message } from 'ai'; import { useChat } from 'ai/react'; import { useAnimate } from 'framer-motion'; -import { memo, useEffect, useRef, useState } from 'react'; +import { memo, useCallback, useEffect, useRef, useState } from 'react'; import { cssTransition, toast, ToastContainer } from 'react-toastify'; import { useMessageParser, usePromptEnhancer, useShortcuts, useSnapScroll } from '~/lib/hooks'; import { description, useChatHistory } from '~/lib/persistence'; import { chatStore } from '~/lib/stores/chat'; import { workbenchStore } from '~/lib/stores/workbench'; import { fileModificationsToHTML } from '~/utils/diff'; -import { DEFAULT_MODEL, DEFAULT_PROVIDER, PROVIDER_LIST } from '~/utils/constants'; +import { DEFAULT_MODEL, DEFAULT_PROVIDER, PROMPT_COOKIE_KEY, PROVIDER_LIST } from '~/utils/constants'; import { cubicEasingFn } from '~/utils/easings'; import { createScopedLogger, renderLogger } from '~/utils/logger'; import { BaseChat } from './BaseChat'; import Cookies from 'js-cookie'; import type { ProviderInfo } from '~/utils/types'; +import { debounce } from '~/utils/debounce'; const toastAnimation = cssTransition({ enter: 'animated fadeInRight', @@ -120,6 +121,7 @@ export const ChatImpl = memo( logger.debug('Finished streaming'); }, initialMessages, + initialInput: Cookies.get(PROMPT_COOKIE_KEY) || '', }); const { enhancingPrompt, promptEnhanced, enhancePrompt, resetEnhancer } = usePromptEnhancer(); @@ -225,12 +227,33 @@ export const ChatImpl = memo( } setInput(''); + Cookies.remove(PROMPT_COOKIE_KEY); resetEnhancer(); textareaRef.current?.blur(); }; + /** + * Handles the change event for the textarea and updates the input state. + * @param event - The change event from the textarea. + */ + const onTextareaChange = (event: React.ChangeEvent) => { + handleInputChange(event); + }; + + /** + * Debounced function to cache the prompt in cookies. + * Caches the trimmed value of the textarea input after a delay to optimize performance. + */ + const debouncedCachePrompt = useCallback( + debounce((event: React.ChangeEvent) => { + const trimmedValue = event.target.value.trim(); + Cookies.set(PROMPT_COOKIE_KEY, trimmedValue, { expires: 30 }); + }, 1000), + [], + ); + const [messageRef, scrollRef] = useSnapScroll(); useEffect(() => { @@ -268,7 +291,10 @@ export const ChatImpl = memo( setProvider={handleProviderChange} messageRef={messageRef} scrollRef={scrollRef} - handleInputChange={handleInputChange} + handleInputChange={(e) => { + onTextareaChange(e); + debouncedCachePrompt(e); + }} handleStop={abort} description={description} importChat={importChat} diff --git a/app/utils/constants.ts b/app/utils/constants.ts index fcbb3f724..e27062857 100644 --- a/app/utils/constants.ts +++ b/app/utils/constants.ts @@ -7,6 +7,7 @@ export const MODIFICATIONS_TAG_NAME = 'bolt_file_modifications'; export const MODEL_REGEX = /^\[Model: (.*?)\]\n\n/; export const PROVIDER_REGEX = /\[Provider: (.*?)\]\n\n/; export const DEFAULT_MODEL = 'claude-3-5-sonnet-latest'; +export const PROMPT_COOKIE_KEY = 'cachedPrompt'; const PROVIDER_LIST: ProviderInfo[] = [ {