mirror of
https://github.com/stackblitz/bolt.new
synced 2025-03-12 14:58:30 +00:00
Merge pull request #493 from dustinwloring1988/stable-plus-ui-glow
UI glow and Prompt Caching
This commit is contained in:
commit
f20173fded
@ -31,6 +31,7 @@ https://thinktank.ottomator.ai
|
|||||||
- ✅ Ability to revert code to earlier version (@wonderwhy-er)
|
- ✅ Ability to revert code to earlier version (@wonderwhy-er)
|
||||||
- ✅ Cohere Integration (@hasanraiyan)
|
- ✅ Cohere Integration (@hasanraiyan)
|
||||||
- ✅ Dynamic model max token length (@hasanraiyan)
|
- ✅ Dynamic model max token length (@hasanraiyan)
|
||||||
|
- ✅ Prompt caching (@SujalXplores)
|
||||||
- ✅ **HIGH PRIORITY** - Load local projects into the app (@wonderwhy-er)
|
- ✅ **HIGH PRIORITY** - Load local projects into the app (@wonderwhy-er)
|
||||||
- ⬜ **HIGH PRIORITY** - ALMOST DONE - Attach images to prompts (@atrokhym)
|
- ⬜ **HIGH PRIORITY** - ALMOST DONE - Attach images to prompts (@atrokhym)
|
||||||
- ⬜ **HIGH PRIORITY** - Prevent Bolt from rewriting files as often (file locking and diffs)
|
- ⬜ **HIGH PRIORITY** - Prevent Bolt from rewriting files as often (file locking and diffs)
|
||||||
@ -42,7 +43,6 @@ https://thinktank.ottomator.ai
|
|||||||
- ⬜ Perplexity Integration
|
- ⬜ Perplexity Integration
|
||||||
- ⬜ Vertex AI Integration
|
- ⬜ Vertex AI Integration
|
||||||
- ⬜ Deploy directly to Vercel/Netlify/other similar platforms
|
- ⬜ Deploy directly to Vercel/Netlify/other similar platforms
|
||||||
- ⬜ Prompt caching
|
|
||||||
- ⬜ Better prompt enhancing
|
- ⬜ Better prompt enhancing
|
||||||
- ⬜ Have LLM plan the project in a MD file for better results/transparency
|
- ⬜ Have LLM plan the project in a MD file for better results/transparency
|
||||||
- ⬜ VSCode Integration with git-like confirmations
|
- ⬜ VSCode Integration with git-like confirmations
|
||||||
|
@ -17,3 +17,107 @@
|
|||||||
.Chat {
|
.Chat {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.RayContainer {
|
||||||
|
--gradient-opacity: 0.85;
|
||||||
|
--ray-gradient: radial-gradient(rgba(83, 196, 255, var(--gradient-opacity)) 0%, rgba(43, 166, 255, 0) 100%);
|
||||||
|
transition: opacity 0.25s linear;
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.LightRayOne {
|
||||||
|
width: 480px;
|
||||||
|
height: 680px;
|
||||||
|
transform: rotate(80deg);
|
||||||
|
top: -540px;
|
||||||
|
left: 250px;
|
||||||
|
filter: blur(110px);
|
||||||
|
position: absolute;
|
||||||
|
border-radius: 100%;
|
||||||
|
background: var(--ray-gradient);
|
||||||
|
}
|
||||||
|
|
||||||
|
.LightRayTwo {
|
||||||
|
width: 110px;
|
||||||
|
height: 400px;
|
||||||
|
transform: rotate(-20deg);
|
||||||
|
top: -280px;
|
||||||
|
left: 350px;
|
||||||
|
mix-blend-mode: overlay;
|
||||||
|
opacity: 0.6;
|
||||||
|
filter: blur(60px);
|
||||||
|
position: absolute;
|
||||||
|
border-radius: 100%;
|
||||||
|
background: var(--ray-gradient);
|
||||||
|
}
|
||||||
|
|
||||||
|
.LightRayThree {
|
||||||
|
width: 400px;
|
||||||
|
height: 370px;
|
||||||
|
top: -350px;
|
||||||
|
left: 200px;
|
||||||
|
mix-blend-mode: overlay;
|
||||||
|
opacity: 0.6;
|
||||||
|
filter: blur(21px);
|
||||||
|
position: absolute;
|
||||||
|
border-radius: 100%;
|
||||||
|
background: var(--ray-gradient);
|
||||||
|
}
|
||||||
|
|
||||||
|
.LightRayFour {
|
||||||
|
position: absolute;
|
||||||
|
width: 330px;
|
||||||
|
height: 370px;
|
||||||
|
top: -330px;
|
||||||
|
left: 50px;
|
||||||
|
mix-blend-mode: overlay;
|
||||||
|
opacity: 0.5;
|
||||||
|
filter: blur(21px);
|
||||||
|
border-radius: 100%;
|
||||||
|
background: var(--ray-gradient);
|
||||||
|
}
|
||||||
|
|
||||||
|
.LightRayFive {
|
||||||
|
position: absolute;
|
||||||
|
width: 110px;
|
||||||
|
height: 400px;
|
||||||
|
transform: rotate(-40deg);
|
||||||
|
top: -280px;
|
||||||
|
left: -10px;
|
||||||
|
mix-blend-mode: overlay;
|
||||||
|
opacity: 0.8;
|
||||||
|
filter: blur(60px);
|
||||||
|
border-radius: 100%;
|
||||||
|
background: var(--ray-gradient);
|
||||||
|
}
|
||||||
|
|
||||||
|
.PromptEffectContainer {
|
||||||
|
--prompt-container-offset: 50px;
|
||||||
|
--prompt-line-stroke-width: 1px;
|
||||||
|
position: absolute;
|
||||||
|
pointer-events: none;
|
||||||
|
inset: calc(var(--prompt-container-offset) / -2);
|
||||||
|
width: calc(100% + var(--prompt-container-offset));
|
||||||
|
height: calc(100% + var(--prompt-container-offset));
|
||||||
|
}
|
||||||
|
|
||||||
|
.PromptEffectLine {
|
||||||
|
width: calc(100% - var(--prompt-container-offset) + var(--prompt-line-stroke-width));
|
||||||
|
height: calc(100% - var(--prompt-container-offset) + var(--prompt-line-stroke-width));
|
||||||
|
x: calc(var(--prompt-container-offset) / 2 - var(--prompt-line-stroke-width) / 2);
|
||||||
|
y: calc(var(--prompt-container-offset) / 2 - var(--prompt-line-stroke-width) / 2);
|
||||||
|
rx: calc(8px - var(--prompt-line-stroke-width));
|
||||||
|
fill: transparent;
|
||||||
|
stroke-width: var(--prompt-line-stroke-width);
|
||||||
|
stroke: url(#line-gradient);
|
||||||
|
stroke-dasharray: 35px 65px;
|
||||||
|
stroke-dashoffset: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.PromptShine {
|
||||||
|
fill: url(#shine-gradient);
|
||||||
|
mix-blend-mode: overlay;
|
||||||
|
}
|
||||||
|
@ -168,6 +168,13 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
|
|||||||
)}
|
)}
|
||||||
data-chat-visible={showChat}
|
data-chat-visible={showChat}
|
||||||
>
|
>
|
||||||
|
<div className={classNames(styles.RayContainer)}>
|
||||||
|
<div className={classNames(styles.LightRayOne)}></div>
|
||||||
|
<div className={classNames(styles.LightRayTwo)}></div>
|
||||||
|
<div className={classNames(styles.LightRayThree)}></div>
|
||||||
|
<div className={classNames(styles.LightRayFour)}></div>
|
||||||
|
<div className={classNames(styles.LightRayFive)}></div>
|
||||||
|
</div>
|
||||||
<ClientOnly>{() => <Menu />}</ClientOnly>
|
<ClientOnly>{() => <Menu />}</ClientOnly>
|
||||||
<div ref={scrollRef} className="flex flex-col lg:flex-row overflow-y-auto w-full h-full">
|
<div ref={scrollRef} className="flex flex-col lg:flex-row overflow-y-auto w-full h-full">
|
||||||
<div className={classNames(styles.Chat, 'flex flex-col flex-grow lg:min-w-[var(--chat-min-width)] h-full')}>
|
<div className={classNames(styles.Chat, 'flex flex-col flex-grow lg:min-w-[var(--chat-min-width)] h-full')}>
|
||||||
@ -206,6 +213,32 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
|
|||||||
},
|
},
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
<svg className={classNames(styles.PromptEffectContainer)}>
|
||||||
|
<defs>
|
||||||
|
<linearGradient
|
||||||
|
id="line-gradient"
|
||||||
|
x1="20%"
|
||||||
|
y1="0%"
|
||||||
|
x2="-14%"
|
||||||
|
y2="10%"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
gradientTransform="rotate(-45)"
|
||||||
|
>
|
||||||
|
<stop offset="0%" stopColor="#1488fc" stopOpacity="0%"></stop>
|
||||||
|
<stop offset="40%" stopColor="#1488fc" stopOpacity="80%"></stop>
|
||||||
|
<stop offset="50%" stopColor="#1488fc" stopOpacity="80%"></stop>
|
||||||
|
<stop offset="100%" stopColor="#1488fc" stopOpacity="0%"></stop>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="shine-gradient">
|
||||||
|
<stop offset="0%" stopColor="white" stopOpacity="0%"></stop>
|
||||||
|
<stop offset="40%" stopColor="#8adaff" stopOpacity="80%"></stop>
|
||||||
|
<stop offset="50%" stopColor="#8adaff" stopOpacity="80%"></stop>
|
||||||
|
<stop offset="100%" stopColor="white" stopOpacity="0%"></stop>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<rect className={classNames(styles.PromptEffectLine)} pathLength="100" strokeLinecap="round"></rect>
|
||||||
|
<rect className={classNames(styles.PromptShine)} x="48" y="24" width="70" height="1"></rect>
|
||||||
|
</svg>
|
||||||
<div>
|
<div>
|
||||||
<div className="flex justify-between items-center mb-2">
|
<div className="flex justify-between items-center mb-2">
|
||||||
<button
|
<button
|
||||||
@ -245,12 +278,12 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'shadow-lg border border-bolt-elements-borderColor bg-bolt-elements-prompt-background backdrop-filter backdrop-blur-[8px] rounded-lg overflow-hidden transition-all',
|
'relative shadow-xs border border-bolt-elements-borderColor backdrop-blur rounded-lg',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<textarea
|
<textarea
|
||||||
ref={textareaRef}
|
ref={textareaRef}
|
||||||
className={`w-full pl-4 pt-4 pr-16 focus:outline-none focus:ring-0 focus:border-none focus:shadow-none resize-none text-md text-bolt-elements-textPrimary placeholder-bolt-elements-textTertiary bg-transparent transition-all`}
|
className={`w-full pl-4 pt-4 pr-16 focus:outline-none resize-none text-bolt-elements-textPrimary placeholder-bolt-elements-textTertiary bg-transparent text-sm`}
|
||||||
onKeyDown={(event) => {
|
onKeyDown={(event) => {
|
||||||
if (event.key === 'Enter') {
|
if (event.key === 'Enter') {
|
||||||
if (event.shiftKey) {
|
if (event.shiftKey) {
|
||||||
|
@ -6,19 +6,20 @@ import { useStore } from '@nanostores/react';
|
|||||||
import type { Message } from 'ai';
|
import type { Message } from 'ai';
|
||||||
import { useChat } from 'ai/react';
|
import { useChat } from 'ai/react';
|
||||||
import { useAnimate } from 'framer-motion';
|
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 { cssTransition, toast, ToastContainer } from 'react-toastify';
|
||||||
import { useMessageParser, usePromptEnhancer, useShortcuts, useSnapScroll } from '~/lib/hooks';
|
import { useMessageParser, usePromptEnhancer, useShortcuts, useSnapScroll } from '~/lib/hooks';
|
||||||
import { description, useChatHistory } from '~/lib/persistence';
|
import { description, useChatHistory } from '~/lib/persistence';
|
||||||
import { chatStore } from '~/lib/stores/chat';
|
import { chatStore } from '~/lib/stores/chat';
|
||||||
import { workbenchStore } from '~/lib/stores/workbench';
|
import { workbenchStore } from '~/lib/stores/workbench';
|
||||||
import { fileModificationsToHTML } from '~/utils/diff';
|
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 { cubicEasingFn } from '~/utils/easings';
|
||||||
import { createScopedLogger, renderLogger } from '~/utils/logger';
|
import { createScopedLogger, renderLogger } from '~/utils/logger';
|
||||||
import { BaseChat } from './BaseChat';
|
import { BaseChat } from './BaseChat';
|
||||||
import Cookies from 'js-cookie';
|
import Cookies from 'js-cookie';
|
||||||
import type { ProviderInfo } from '~/utils/types';
|
import type { ProviderInfo } from '~/utils/types';
|
||||||
|
import { debounce } from '~/utils/debounce';
|
||||||
|
|
||||||
const toastAnimation = cssTransition({
|
const toastAnimation = cssTransition({
|
||||||
enter: 'animated fadeInRight',
|
enter: 'animated fadeInRight',
|
||||||
@ -120,6 +121,7 @@ export const ChatImpl = memo(
|
|||||||
logger.debug('Finished streaming');
|
logger.debug('Finished streaming');
|
||||||
},
|
},
|
||||||
initialMessages,
|
initialMessages,
|
||||||
|
initialInput: Cookies.get(PROMPT_COOKIE_KEY) || '',
|
||||||
});
|
});
|
||||||
|
|
||||||
const { enhancingPrompt, promptEnhanced, enhancePrompt, resetEnhancer } = usePromptEnhancer();
|
const { enhancingPrompt, promptEnhanced, enhancePrompt, resetEnhancer } = usePromptEnhancer();
|
||||||
@ -225,12 +227,33 @@ export const ChatImpl = memo(
|
|||||||
}
|
}
|
||||||
|
|
||||||
setInput('');
|
setInput('');
|
||||||
|
Cookies.remove(PROMPT_COOKIE_KEY);
|
||||||
|
|
||||||
resetEnhancer();
|
resetEnhancer();
|
||||||
|
|
||||||
textareaRef.current?.blur();
|
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<HTMLTextAreaElement>) => {
|
||||||
|
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<HTMLTextAreaElement>) => {
|
||||||
|
const trimmedValue = event.target.value.trim();
|
||||||
|
Cookies.set(PROMPT_COOKIE_KEY, trimmedValue, { expires: 30 });
|
||||||
|
}, 1000),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
const [messageRef, scrollRef] = useSnapScroll();
|
const [messageRef, scrollRef] = useSnapScroll();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -268,7 +291,10 @@ export const ChatImpl = memo(
|
|||||||
setProvider={handleProviderChange}
|
setProvider={handleProviderChange}
|
||||||
messageRef={messageRef}
|
messageRef={messageRef}
|
||||||
scrollRef={scrollRef}
|
scrollRef={scrollRef}
|
||||||
handleInputChange={handleInputChange}
|
handleInputChange={(e) => {
|
||||||
|
onTextareaChange(e);
|
||||||
|
debouncedCachePrompt(e);
|
||||||
|
}}
|
||||||
handleStop={abort}
|
handleStop={abort}
|
||||||
description={description}
|
description={description}
|
||||||
importChat={importChat}
|
importChat={importChat}
|
||||||
|
@ -7,6 +7,7 @@ export const MODIFICATIONS_TAG_NAME = 'bolt_file_modifications';
|
|||||||
export const MODEL_REGEX = /^\[Model: (.*?)\]\n\n/;
|
export const MODEL_REGEX = /^\[Model: (.*?)\]\n\n/;
|
||||||
export const PROVIDER_REGEX = /\[Provider: (.*?)\]\n\n/;
|
export const PROVIDER_REGEX = /\[Provider: (.*?)\]\n\n/;
|
||||||
export const DEFAULT_MODEL = 'claude-3-5-sonnet-latest';
|
export const DEFAULT_MODEL = 'claude-3-5-sonnet-latest';
|
||||||
|
export const PROMPT_COOKIE_KEY = 'cachedPrompt';
|
||||||
|
|
||||||
const PROVIDER_LIST: ProviderInfo[] = [
|
const PROVIDER_LIST: ProviderInfo[] = [
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user