Cole Medin commited on
Commit
f20173f
Β·
unverified Β·
2 Parent(s): c8b4eee 5ee2e90

Merge pull request #493 from dustinwloring1988/stable-plus-ui-glow

Browse files
README.md CHANGED
@@ -31,6 +31,7 @@ https://thinktank.ottomator.ai
31
  - βœ… Ability to revert code to earlier version (@wonderwhy-er)
32
  - βœ… Cohere Integration (@hasanraiyan)
33
  - βœ… Dynamic model max token length (@hasanraiyan)
 
34
  - βœ… **HIGH PRIORITY** - Load local projects into the app (@wonderwhy-er)
35
  - ⬜ **HIGH PRIORITY** - ALMOST DONE - Attach images to prompts (@atrokhym)
36
  - ⬜ **HIGH PRIORITY** - Prevent Bolt from rewriting files as often (file locking and diffs)
@@ -42,7 +43,6 @@ https://thinktank.ottomator.ai
42
  - ⬜ Perplexity Integration
43
  - ⬜ Vertex AI Integration
44
  - ⬜ Deploy directly to Vercel/Netlify/other similar platforms
45
- - ⬜ Prompt caching
46
  - ⬜ Better prompt enhancing
47
  - ⬜ Have LLM plan the project in a MD file for better results/transparency
48
  - ⬜ VSCode Integration with git-like confirmations
 
31
  - βœ… Ability to revert code to earlier version (@wonderwhy-er)
32
  - βœ… Cohere Integration (@hasanraiyan)
33
  - βœ… Dynamic model max token length (@hasanraiyan)
34
+ - βœ… Prompt caching (@SujalXplores)
35
  - βœ… **HIGH PRIORITY** - Load local projects into the app (@wonderwhy-er)
36
  - ⬜ **HIGH PRIORITY** - ALMOST DONE - Attach images to prompts (@atrokhym)
37
  - ⬜ **HIGH PRIORITY** - Prevent Bolt from rewriting files as often (file locking and diffs)
 
43
  - ⬜ Perplexity Integration
44
  - ⬜ Vertex AI Integration
45
  - ⬜ Deploy directly to Vercel/Netlify/other similar platforms
 
46
  - ⬜ Better prompt enhancing
47
  - ⬜ Have LLM plan the project in a MD file for better results/transparency
48
  - ⬜ VSCode Integration with git-like confirmations
app/components/chat/BaseChat.module.scss CHANGED
@@ -17,3 +17,107 @@
17
  .Chat {
18
  opacity: 1;
19
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  .Chat {
18
  opacity: 1;
19
  }
20
+
21
+ .RayContainer {
22
+ --gradient-opacity: 0.85;
23
+ --ray-gradient: radial-gradient(rgba(83, 196, 255, var(--gradient-opacity)) 0%, rgba(43, 166, 255, 0) 100%);
24
+ transition: opacity 0.25s linear;
25
+ position: fixed;
26
+ inset: 0;
27
+ pointer-events: none;
28
+ user-select: none;
29
+ }
30
+
31
+ .LightRayOne {
32
+ width: 480px;
33
+ height: 680px;
34
+ transform: rotate(80deg);
35
+ top: -540px;
36
+ left: 250px;
37
+ filter: blur(110px);
38
+ position: absolute;
39
+ border-radius: 100%;
40
+ background: var(--ray-gradient);
41
+ }
42
+
43
+ .LightRayTwo {
44
+ width: 110px;
45
+ height: 400px;
46
+ transform: rotate(-20deg);
47
+ top: -280px;
48
+ left: 350px;
49
+ mix-blend-mode: overlay;
50
+ opacity: 0.6;
51
+ filter: blur(60px);
52
+ position: absolute;
53
+ border-radius: 100%;
54
+ background: var(--ray-gradient);
55
+ }
56
+
57
+ .LightRayThree {
58
+ width: 400px;
59
+ height: 370px;
60
+ top: -350px;
61
+ left: 200px;
62
+ mix-blend-mode: overlay;
63
+ opacity: 0.6;
64
+ filter: blur(21px);
65
+ position: absolute;
66
+ border-radius: 100%;
67
+ background: var(--ray-gradient);
68
+ }
69
+
70
+ .LightRayFour {
71
+ position: absolute;
72
+ width: 330px;
73
+ height: 370px;
74
+ top: -330px;
75
+ left: 50px;
76
+ mix-blend-mode: overlay;
77
+ opacity: 0.5;
78
+ filter: blur(21px);
79
+ border-radius: 100%;
80
+ background: var(--ray-gradient);
81
+ }
82
+
83
+ .LightRayFive {
84
+ position: absolute;
85
+ width: 110px;
86
+ height: 400px;
87
+ transform: rotate(-40deg);
88
+ top: -280px;
89
+ left: -10px;
90
+ mix-blend-mode: overlay;
91
+ opacity: 0.8;
92
+ filter: blur(60px);
93
+ border-radius: 100%;
94
+ background: var(--ray-gradient);
95
+ }
96
+
97
+ .PromptEffectContainer {
98
+ --prompt-container-offset: 50px;
99
+ --prompt-line-stroke-width: 1px;
100
+ position: absolute;
101
+ pointer-events: none;
102
+ inset: calc(var(--prompt-container-offset) / -2);
103
+ width: calc(100% + var(--prompt-container-offset));
104
+ height: calc(100% + var(--prompt-container-offset));
105
+ }
106
+
107
+ .PromptEffectLine {
108
+ width: calc(100% - var(--prompt-container-offset) + var(--prompt-line-stroke-width));
109
+ height: calc(100% - var(--prompt-container-offset) + var(--prompt-line-stroke-width));
110
+ x: calc(var(--prompt-container-offset) / 2 - var(--prompt-line-stroke-width) / 2);
111
+ y: calc(var(--prompt-container-offset) / 2 - var(--prompt-line-stroke-width) / 2);
112
+ rx: calc(8px - var(--prompt-line-stroke-width));
113
+ fill: transparent;
114
+ stroke-width: var(--prompt-line-stroke-width);
115
+ stroke: url(#line-gradient);
116
+ stroke-dasharray: 35px 65px;
117
+ stroke-dashoffset: 10;
118
+ }
119
+
120
+ .PromptShine {
121
+ fill: url(#shine-gradient);
122
+ mix-blend-mode: overlay;
123
+ }
app/components/chat/BaseChat.tsx CHANGED
@@ -168,6 +168,13 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
168
  )}
169
  data-chat-visible={showChat}
170
  >
 
 
 
 
 
 
 
171
  <ClientOnly>{() => <Menu />}</ClientOnly>
172
  <div ref={scrollRef} className="flex flex-col lg:flex-row overflow-y-auto w-full h-full">
173
  <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>(
206
  },
207
  )}
208
  >
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
209
  <div>
210
  <div className="flex justify-between items-center mb-2">
211
  <button
@@ -245,12 +278,12 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
245
 
246
  <div
247
  className={classNames(
248
- 'shadow-lg border border-bolt-elements-borderColor bg-bolt-elements-prompt-background backdrop-filter backdrop-blur-[8px] rounded-lg overflow-hidden transition-all',
249
  )}
250
  >
251
  <textarea
252
  ref={textareaRef}
253
- 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`}
254
  onKeyDown={(event) => {
255
  if (event.key === 'Enter') {
256
  if (event.shiftKey) {
 
168
  )}
169
  data-chat-visible={showChat}
170
  >
171
+ <div className={classNames(styles.RayContainer)}>
172
+ <div className={classNames(styles.LightRayOne)}></div>
173
+ <div className={classNames(styles.LightRayTwo)}></div>
174
+ <div className={classNames(styles.LightRayThree)}></div>
175
+ <div className={classNames(styles.LightRayFour)}></div>
176
+ <div className={classNames(styles.LightRayFive)}></div>
177
+ </div>
178
  <ClientOnly>{() => <Menu />}</ClientOnly>
179
  <div ref={scrollRef} className="flex flex-col lg:flex-row overflow-y-auto w-full h-full">
180
  <div className={classNames(styles.Chat, 'flex flex-col flex-grow lg:min-w-[var(--chat-min-width)] h-full')}>
 
213
  },
214
  )}
215
  >
216
+ <svg className={classNames(styles.PromptEffectContainer)}>
217
+ <defs>
218
+ <linearGradient
219
+ id="line-gradient"
220
+ x1="20%"
221
+ y1="0%"
222
+ x2="-14%"
223
+ y2="10%"
224
+ gradientUnits="userSpaceOnUse"
225
+ gradientTransform="rotate(-45)"
226
+ >
227
+ <stop offset="0%" stopColor="#1488fc" stopOpacity="0%"></stop>
228
+ <stop offset="40%" stopColor="#1488fc" stopOpacity="80%"></stop>
229
+ <stop offset="50%" stopColor="#1488fc" stopOpacity="80%"></stop>
230
+ <stop offset="100%" stopColor="#1488fc" stopOpacity="0%"></stop>
231
+ </linearGradient>
232
+ <linearGradient id="shine-gradient">
233
+ <stop offset="0%" stopColor="white" stopOpacity="0%"></stop>
234
+ <stop offset="40%" stopColor="#8adaff" stopOpacity="80%"></stop>
235
+ <stop offset="50%" stopColor="#8adaff" stopOpacity="80%"></stop>
236
+ <stop offset="100%" stopColor="white" stopOpacity="0%"></stop>
237
+ </linearGradient>
238
+ </defs>
239
+ <rect className={classNames(styles.PromptEffectLine)} pathLength="100" strokeLinecap="round"></rect>
240
+ <rect className={classNames(styles.PromptShine)} x="48" y="24" width="70" height="1"></rect>
241
+ </svg>
242
  <div>
243
  <div className="flex justify-between items-center mb-2">
244
  <button
 
278
 
279
  <div
280
  className={classNames(
281
+ 'relative shadow-xs border border-bolt-elements-borderColor backdrop-blur rounded-lg',
282
  )}
283
  >
284
  <textarea
285
  ref={textareaRef}
286
+ 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`}
287
  onKeyDown={(event) => {
288
  if (event.key === 'Enter') {
289
  if (event.shiftKey) {
app/components/chat/Chat.client.tsx CHANGED
@@ -6,19 +6,20 @@ import { useStore } from '@nanostores/react';
6
  import type { Message } from 'ai';
7
  import { useChat } from 'ai/react';
8
  import { useAnimate } from 'framer-motion';
9
- import { memo, useEffect, useRef, useState } from 'react';
10
  import { cssTransition, toast, ToastContainer } from 'react-toastify';
11
  import { useMessageParser, usePromptEnhancer, useShortcuts, useSnapScroll } from '~/lib/hooks';
12
  import { description, useChatHistory } from '~/lib/persistence';
13
  import { chatStore } from '~/lib/stores/chat';
14
  import { workbenchStore } from '~/lib/stores/workbench';
15
  import { fileModificationsToHTML } from '~/utils/diff';
16
- import { DEFAULT_MODEL, DEFAULT_PROVIDER, PROVIDER_LIST } from '~/utils/constants';
17
  import { cubicEasingFn } from '~/utils/easings';
18
  import { createScopedLogger, renderLogger } from '~/utils/logger';
19
  import { BaseChat } from './BaseChat';
20
  import Cookies from 'js-cookie';
21
  import type { ProviderInfo } from '~/utils/types';
 
22
 
23
  const toastAnimation = cssTransition({
24
  enter: 'animated fadeInRight',
@@ -120,6 +121,7 @@ export const ChatImpl = memo(
120
  logger.debug('Finished streaming');
121
  },
122
  initialMessages,
 
123
  });
124
 
125
  const { enhancingPrompt, promptEnhanced, enhancePrompt, resetEnhancer } = usePromptEnhancer();
@@ -225,12 +227,33 @@ export const ChatImpl = memo(
225
  }
226
 
227
  setInput('');
 
228
 
229
  resetEnhancer();
230
 
231
  textareaRef.current?.blur();
232
  };
233
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
234
  const [messageRef, scrollRef] = useSnapScroll();
235
 
236
  useEffect(() => {
@@ -268,7 +291,10 @@ export const ChatImpl = memo(
268
  setProvider={handleProviderChange}
269
  messageRef={messageRef}
270
  scrollRef={scrollRef}
271
- handleInputChange={handleInputChange}
 
 
 
272
  handleStop={abort}
273
  description={description}
274
  importChat={importChat}
 
6
  import type { Message } from 'ai';
7
  import { useChat } from 'ai/react';
8
  import { useAnimate } from 'framer-motion';
9
+ import { memo, useCallback, useEffect, useRef, useState } from 'react';
10
  import { cssTransition, toast, ToastContainer } from 'react-toastify';
11
  import { useMessageParser, usePromptEnhancer, useShortcuts, useSnapScroll } from '~/lib/hooks';
12
  import { description, useChatHistory } from '~/lib/persistence';
13
  import { chatStore } from '~/lib/stores/chat';
14
  import { workbenchStore } from '~/lib/stores/workbench';
15
  import { fileModificationsToHTML } from '~/utils/diff';
16
+ import { DEFAULT_MODEL, DEFAULT_PROVIDER, PROMPT_COOKIE_KEY, PROVIDER_LIST } from '~/utils/constants';
17
  import { cubicEasingFn } from '~/utils/easings';
18
  import { createScopedLogger, renderLogger } from '~/utils/logger';
19
  import { BaseChat } from './BaseChat';
20
  import Cookies from 'js-cookie';
21
  import type { ProviderInfo } from '~/utils/types';
22
+ import { debounce } from '~/utils/debounce';
23
 
24
  const toastAnimation = cssTransition({
25
  enter: 'animated fadeInRight',
 
121
  logger.debug('Finished streaming');
122
  },
123
  initialMessages,
124
+ initialInput: Cookies.get(PROMPT_COOKIE_KEY) || '',
125
  });
126
 
127
  const { enhancingPrompt, promptEnhanced, enhancePrompt, resetEnhancer } = usePromptEnhancer();
 
227
  }
228
 
229
  setInput('');
230
+ Cookies.remove(PROMPT_COOKIE_KEY);
231
 
232
  resetEnhancer();
233
 
234
  textareaRef.current?.blur();
235
  };
236
 
237
+ /**
238
+ * Handles the change event for the textarea and updates the input state.
239
+ * @param event - The change event from the textarea.
240
+ */
241
+ const onTextareaChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
242
+ handleInputChange(event);
243
+ };
244
+
245
+ /**
246
+ * Debounced function to cache the prompt in cookies.
247
+ * Caches the trimmed value of the textarea input after a delay to optimize performance.
248
+ */
249
+ const debouncedCachePrompt = useCallback(
250
+ debounce((event: React.ChangeEvent<HTMLTextAreaElement>) => {
251
+ const trimmedValue = event.target.value.trim();
252
+ Cookies.set(PROMPT_COOKIE_KEY, trimmedValue, { expires: 30 });
253
+ }, 1000),
254
+ [],
255
+ );
256
+
257
  const [messageRef, scrollRef] = useSnapScroll();
258
 
259
  useEffect(() => {
 
291
  setProvider={handleProviderChange}
292
  messageRef={messageRef}
293
  scrollRef={scrollRef}
294
+ handleInputChange={(e) => {
295
+ onTextareaChange(e);
296
+ debouncedCachePrompt(e);
297
+ }}
298
  handleStop={abort}
299
  description={description}
300
  importChat={importChat}
app/utils/constants.ts CHANGED
@@ -7,6 +7,7 @@ export const MODIFICATIONS_TAG_NAME = 'bolt_file_modifications';
7
  export const MODEL_REGEX = /^\[Model: (.*?)\]\n\n/;
8
  export const PROVIDER_REGEX = /\[Provider: (.*?)\]\n\n/;
9
  export const DEFAULT_MODEL = 'claude-3-5-sonnet-latest';
 
10
 
11
  const PROVIDER_LIST: ProviderInfo[] = [
12
  {
 
7
  export const MODEL_REGEX = /^\[Model: (.*?)\]\n\n/;
8
  export const PROVIDER_REGEX = /\[Provider: (.*?)\]\n\n/;
9
  export const DEFAULT_MODEL = 'claude-3-5-sonnet-latest';
10
+ export const PROMPT_COOKIE_KEY = 'cachedPrompt';
11
 
12
  const PROVIDER_LIST: ProviderInfo[] = [
13
  {