codacus commited on
Commit
b0743e0
·
unverified ·
2 Parent(s): 9eca2cf 7e18820

Merge branch 'main' into github-import

Browse files
.github/ISSUE_TEMPLATE/bug_report.yml CHANGED
@@ -56,6 +56,16 @@ body:
56
  - OS: [e.g. macOS, Windows, Linux]
57
  - Browser: [e.g. Chrome, Safari, Firefox]
58
  - Version: [e.g. 91.1]
 
 
 
 
 
 
 
 
 
 
59
  - type: textarea
60
  id: additional
61
  attributes:
 
56
  - OS: [e.g. macOS, Windows, Linux]
57
  - Browser: [e.g. Chrome, Safari, Firefox]
58
  - Version: [e.g. 91.1]
59
+ - type: input
60
+ id: provider
61
+ attributes:
62
+ label: Provider Used
63
+ description: Tell us the provider you are using.
64
+ - type: input
65
+ id: model
66
+ attributes:
67
+ label: Model Used
68
+ description: Tell us the model you are using.
69
  - type: textarea
70
  id: additional
71
  attributes:
.github/workflows/stale.yml CHANGED
@@ -16,10 +16,10 @@ jobs:
16
  repo-token: ${{ secrets.GITHUB_TOKEN }}
17
  stale-issue-message: "This issue has been marked as stale due to inactivity. If no further activity occurs, it will be closed in 7 days."
18
  stale-pr-message: "This pull request has been marked as stale due to inactivity. If no further activity occurs, it will be closed in 7 days."
19
- days-before-stale: 14 # Number of days before marking an issue or PR as stale
20
- days-before-close: 7 # Number of days after being marked stale before closing
21
  stale-issue-label: "stale" # Label to apply to stale issues
22
  stale-pr-label: "stale" # Label to apply to stale pull requests
23
  exempt-issue-labels: "pinned,important" # Issues with these labels won't be marked stale
24
  exempt-pr-labels: "pinned,important" # PRs with these labels won't be marked stale
25
- operations-per-run: 90 # Limits the number of actions per run to avoid API rate limits
 
16
  repo-token: ${{ secrets.GITHUB_TOKEN }}
17
  stale-issue-message: "This issue has been marked as stale due to inactivity. If no further activity occurs, it will be closed in 7 days."
18
  stale-pr-message: "This pull request has been marked as stale due to inactivity. If no further activity occurs, it will be closed in 7 days."
19
+ days-before-stale: 10 # Number of days before marking an issue or PR as stale
20
+ days-before-close: 4 # Number of days after being marked stale before closing
21
  stale-issue-label: "stale" # Label to apply to stale issues
22
  stale-pr-label: "stale" # Label to apply to stale pull requests
23
  exempt-issue-labels: "pinned,important" # Issues with these labels won't be marked stale
24
  exempt-pr-labels: "pinned,important" # PRs with these labels won't be marked stale
25
+ operations-per-run: 75 # Limits the number of actions per run to avoid API rate limits
.husky/pre-commit CHANGED
@@ -17,7 +17,7 @@ fi
17
 
18
  echo "Running lint..."
19
  if ! pnpm lint; then
20
- echo "❌ Linting failed! 'pnpm lint:check' will help you fix the easy ones."
21
  echo "Once you're done, don't forget to add your beautification to the commit! 🤩"
22
  echo "lint exit code: $?"
23
  exit 1
 
17
 
18
  echo "Running lint..."
19
  if ! pnpm lint; then
20
+ echo "❌ Linting failed! 'pnpm lint:fix' will help you fix the easy ones."
21
  echo "Once you're done, don't forget to add your beautification to the commit! 🤩"
22
  echo "lint exit code: $?"
23
  exit 1
README.md CHANGED
@@ -4,10 +4,13 @@
4
 
5
  This fork of Bolt.new (oTToDev) allows you to choose the LLM that you use for each prompt! Currently, you can use OpenAI, Anthropic, Ollama, OpenRouter, Gemini, LMStudio, Mistral, xAI, HuggingFace, DeepSeek, or Groq models - and it is easily extended to use any other model supported by the Vercel AI SDK! See the instructions below for running this locally and extending it to include more models.
6
 
 
 
7
  ## Join the community for oTToDev!
8
 
9
  https://thinktank.ottomator.ai
10
 
 
11
  ## Requested Additions - Feel Free to Contribute!
12
 
13
  - ✅ OpenRouter Integration (@coleam00)
@@ -31,6 +34,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
  - ✅ Prompt caching (@SujalXplores)
35
  - ✅ Load local projects into the app (@wonderwhy-er)
36
  - ✅ Together Integration (@mouimet-infinisoft)
 
4
 
5
  This fork of Bolt.new (oTToDev) allows you to choose the LLM that you use for each prompt! Currently, you can use OpenAI, Anthropic, Ollama, OpenRouter, Gemini, LMStudio, Mistral, xAI, HuggingFace, DeepSeek, or Groq models - and it is easily extended to use any other model supported by the Vercel AI SDK! See the instructions below for running this locally and extending it to include more models.
6
 
7
+ Check the [oTToDev Docs](https://coleam00.github.io/bolt.new-any-llm/) for more information.
8
+
9
  ## Join the community for oTToDev!
10
 
11
  https://thinktank.ottomator.ai
12
 
13
+
14
  ## Requested Additions - Feel Free to Contribute!
15
 
16
  - ✅ OpenRouter Integration (@coleam00)
 
34
  - ✅ Ability to revert code to earlier version (@wonderwhy-er)
35
  - ✅ Cohere Integration (@hasanraiyan)
36
  - ✅ Dynamic model max token length (@hasanraiyan)
37
+ - ✅ Better prompt enhancing (@SujalXplores)
38
  - ✅ Prompt caching (@SujalXplores)
39
  - ✅ Load local projects into the app (@wonderwhy-er)
40
  - ✅ Together Integration (@mouimet-infinisoft)
app/components/chat/BaseChat.tsx CHANGED
@@ -255,6 +255,7 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
255
  <span>Model Settings</span>
256
  </button>
257
  </div>
 
258
  <div className={isModelSettingsCollapsed ? 'hidden' : ''}>
259
  <ModelSelector
260
  key={provider?.name + ':' + modelList.length}
 
255
  <span>Model Settings</span>
256
  </button>
257
  </div>
258
+
259
  <div className={isModelSettingsCollapsed ? 'hidden' : ''}>
260
  <ModelSelector
261
  key={provider?.name + ':' + modelList.length}
app/components/header/Header.tsx CHANGED
@@ -24,17 +24,19 @@ export function Header() {
24
  <span className="i-bolt:logo-text?mask w-[46px] inline-block" />
25
  </a>
26
  </div>
27
- <span className="flex-1 px-4 truncate text-center text-bolt-elements-textPrimary">
28
- <ClientOnly>{() => <ChatDescription />}</ClientOnly>
29
- </span>
30
- {chat.started && (
31
- <ClientOnly>
32
- {() => (
33
- <div className="mr-1">
34
- <HeaderActionButtons />
35
- </div>
36
- )}
37
- </ClientOnly>
 
 
38
  )}
39
  </header>
40
  );
 
24
  <span className="i-bolt:logo-text?mask w-[46px] inline-block" />
25
  </a>
26
  </div>
27
+ {chat.started && ( // Display ChatDescription and HeaderActionButtons only when the chat has started.
28
+ <>
29
+ <span className="flex-1 px-4 truncate text-center text-bolt-elements-textPrimary">
30
+ <ClientOnly>{() => <ChatDescription />}</ClientOnly>
31
+ </span>
32
+ <ClientOnly>
33
+ {() => (
34
+ <div className="mr-1">
35
+ <HeaderActionButtons />
36
+ </div>
37
+ )}
38
+ </ClientOnly>
39
+ </>
40
  )}
41
  </header>
42
  );
app/components/header/HeaderActionButtons.client.tsx CHANGED
@@ -19,7 +19,7 @@ export function HeaderActionButtons({}: HeaderActionButtonsProps) {
19
  <div className="flex border border-bolt-elements-borderColor rounded-md overflow-hidden">
20
  <Button
21
  active={showChat}
22
- disabled={!canHideChat || isSmallViewport} // expand button is disabled on mobile as it's needed
23
  onClick={() => {
24
  if (canHideChat) {
25
  chatStore.setKey('showChat', !showChat);
 
19
  <div className="flex border border-bolt-elements-borderColor rounded-md overflow-hidden">
20
  <Button
21
  active={showChat}
22
+ disabled={!canHideChat || isSmallViewport} // expand button is disabled on mobile as it's not needed
23
  onClick={() => {
24
  if (canHideChat) {
25
  chatStore.setKey('showChat', !showChat);
app/components/sidebar/HistoryItem.tsx CHANGED
@@ -1,6 +1,9 @@
 
 
1
  import * as Dialog from '@radix-ui/react-dialog';
2
  import { type ChatHistoryItem } from '~/lib/persistence';
3
  import WithTooltip from '~/components/ui/Tooltip';
 
4
 
5
  interface HistoryItemProps {
6
  item: ChatHistoryItem;
@@ -10,48 +13,115 @@ interface HistoryItemProps {
10
  }
11
 
12
  export function HistoryItem({ item, onDelete, onDuplicate, exportChat }: HistoryItemProps) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  return (
14
- <div className="group rounded-md text-bolt-elements-textSecondary hover:text-bolt-elements-textPrimary hover:bg-bolt-elements-background-depth-3 overflow-hidden flex justify-between items-center px-2 py-1">
15
- <a href={`/chat/${item.urlId}`} className="flex w-full relative truncate block">
16
- {item.description}
17
- <div className="absolute right-0 z-1 top-0 bottom-0 bg-gradient-to-l from-bolt-elements-background-depth-2 group-hover:from-bolt-elements-background-depth-3 box-content pl-3 to-transparent w-10 flex justify-end group-hover:w-15 group-hover:from-99%">
18
- <div className="flex items-center p-1 text-bolt-elements-textSecondary opacity-0 group-hover:opacity-100 transition-opacity">
19
- <WithTooltip tooltip="Export chat">
20
- <button
21
- type="button"
22
- className="i-ph:download-simple scale-110 mr-2 hover:text-bolt-elements-item-contentAccent"
 
 
 
 
 
 
 
 
 
 
 
 
23
  onClick={(event) => {
24
  event.preventDefault();
25
  exportChat(item.id);
26
  }}
27
- title="Export chat"
28
  />
29
- </WithTooltip>
30
- {onDuplicate && (
31
- <WithTooltip tooltip="Duplicate chat">
32
- <button
33
- type="button"
34
- className="i-ph:copy scale-110 mr-2 hover:text-bolt-elements-item-contentAccent"
35
  onClick={() => onDuplicate?.(item.id)}
36
- title="Duplicate chat"
37
  />
38
- </WithTooltip>
39
- )}
40
- <Dialog.Trigger asChild>
41
- <WithTooltip tooltip="Delete chat">
42
- <button
43
- type="button"
44
- className="i-ph:trash scale-110 hover:text-bolt-elements-button-danger-text"
 
 
 
 
 
 
 
45
  onClick={(event) => {
46
  event.preventDefault();
47
  onDelete?.(event);
48
  }}
49
  />
50
- </WithTooltip>
51
- </Dialog.Trigger>
52
  </div>
53
- </div>
54
- </a>
55
  </div>
56
  );
57
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useParams } from '@remix-run/react';
2
+ import { classNames } from '~/utils/classNames';
3
  import * as Dialog from '@radix-ui/react-dialog';
4
  import { type ChatHistoryItem } from '~/lib/persistence';
5
  import WithTooltip from '~/components/ui/Tooltip';
6
+ import { useEditChatDescription } from '~/lib/hooks';
7
 
8
  interface HistoryItemProps {
9
  item: ChatHistoryItem;
 
13
  }
14
 
15
  export function HistoryItem({ item, onDelete, onDuplicate, exportChat }: HistoryItemProps) {
16
+ const { id: urlId } = useParams();
17
+ const isActiveChat = urlId === item.urlId;
18
+
19
+ const { editing, handleChange, handleBlur, handleSubmit, handleKeyDown, currentDescription, toggleEditMode } =
20
+ useEditChatDescription({
21
+ initialDescription: item.description,
22
+ customChatId: item.id,
23
+ syncWithGlobalStore: isActiveChat,
24
+ });
25
+
26
+ const renderDescriptionForm = (
27
+ <form onSubmit={handleSubmit} className="flex-1 flex items-center">
28
+ <input
29
+ type="text"
30
+ className="flex-1 bg-bolt-elements-background-depth-1 text-bolt-elements-textPrimary rounded px-2 mr-2"
31
+ autoFocus
32
+ value={currentDescription}
33
+ onChange={handleChange}
34
+ onBlur={handleBlur}
35
+ onKeyDown={handleKeyDown}
36
+ />
37
+ <button
38
+ type="submit"
39
+ className="i-ph:check scale-110 hover:text-bolt-elements-item-contentAccent"
40
+ onMouseDown={handleSubmit}
41
+ />
42
+ </form>
43
+ );
44
+
45
  return (
46
+ <div
47
+ className={classNames(
48
+ 'group rounded-md text-bolt-elements-textSecondary hover:text-bolt-elements-textPrimary hover:bg-bolt-elements-background-depth-3 overflow-hidden flex justify-between items-center px-2 py-1',
49
+ { '[&&]:text-bolt-elements-textPrimary bg-bolt-elements-background-depth-3': isActiveChat },
50
+ )}
51
+ >
52
+ {editing ? (
53
+ renderDescriptionForm
54
+ ) : (
55
+ <a href={`/chat/${item.urlId}`} className="flex w-full relative truncate block">
56
+ {currentDescription}
57
+ <div
58
+ className={classNames(
59
+ 'absolute right-0 z-1 top-0 bottom-0 bg-gradient-to-l from-bolt-elements-background-depth-2 group-hover:from-bolt-elements-background-depth-3 box-content pl-3 to-transparent w-10 flex justify-end group-hover:w-22 group-hover:from-99%',
60
+ { 'from-bolt-elements-background-depth-3 w-10 ': isActiveChat },
61
+ )}
62
+ >
63
+ <div className="flex items-center p-1 text-bolt-elements-textSecondary opacity-0 group-hover:opacity-100 transition-opacity">
64
+ <ChatActionButton
65
+ toolTipContent="Export chat"
66
+ icon="i-ph:download-simple"
67
  onClick={(event) => {
68
  event.preventDefault();
69
  exportChat(item.id);
70
  }}
 
71
  />
72
+ {onDuplicate && (
73
+ <ChatActionButton
74
+ toolTipContent="Duplicate chat"
75
+ icon="i-ph:copy"
 
 
76
  onClick={() => onDuplicate?.(item.id)}
 
77
  />
78
+ )}
79
+ <ChatActionButton
80
+ toolTipContent="Rename chat"
81
+ icon="i-ph:pencil-fill"
82
+ onClick={(event) => {
83
+ event.preventDefault();
84
+ toggleEditMode();
85
+ }}
86
+ />
87
+ <Dialog.Trigger asChild>
88
+ <ChatActionButton
89
+ toolTipContent="Delete chat"
90
+ icon="i-ph:trash"
91
+ className="[&&]:hover:text-bolt-elements-button-danger-text"
92
  onClick={(event) => {
93
  event.preventDefault();
94
  onDelete?.(event);
95
  }}
96
  />
97
+ </Dialog.Trigger>
98
+ </div>
99
  </div>
100
+ </a>
101
+ )}
102
  </div>
103
  );
104
  }
105
+
106
+ const ChatActionButton = ({
107
+ toolTipContent,
108
+ icon,
109
+ className,
110
+ onClick,
111
+ }: {
112
+ toolTipContent: string;
113
+ icon: string;
114
+ className?: string;
115
+ onClick: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
116
+ btnTitle?: string;
117
+ }) => {
118
+ return (
119
+ <WithTooltip tooltip={toolTipContent}>
120
+ <button
121
+ type="button"
122
+ className={`scale-110 mr-2 hover:text-bolt-elements-item-contentAccent ${icon} ${className ? className : ''}`}
123
+ onClick={onClick}
124
+ />
125
+ </WithTooltip>
126
+ );
127
+ };
app/lib/hooks/index.ts CHANGED
@@ -2,4 +2,5 @@ export * from './useMessageParser';
2
  export * from './usePromptEnhancer';
3
  export * from './useShortcuts';
4
  export * from './useSnapScroll';
 
5
  export { default } from './useViewport';
 
2
  export * from './usePromptEnhancer';
3
  export * from './useShortcuts';
4
  export * from './useSnapScroll';
5
+ export * from './useEditChatDescription';
6
  export { default } from './useViewport';
app/lib/hooks/useEditChatDescription.ts ADDED
@@ -0,0 +1,163 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useStore } from '@nanostores/react';
2
+ import { useCallback, useEffect, useState } from 'react';
3
+ import { toast } from 'react-toastify';
4
+ import {
5
+ chatId as chatIdStore,
6
+ description as descriptionStore,
7
+ db,
8
+ updateChatDescription,
9
+ getMessages,
10
+ } from '~/lib/persistence';
11
+
12
+ interface EditChatDescriptionOptions {
13
+ initialDescription?: string;
14
+ customChatId?: string;
15
+ syncWithGlobalStore?: boolean;
16
+ }
17
+
18
+ type EditChatDescriptionHook = {
19
+ editing: boolean;
20
+ handleChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
21
+ handleBlur: () => Promise<void>;
22
+ handleSubmit: (event: React.FormEvent) => Promise<void>;
23
+ handleKeyDown: (event: React.KeyboardEvent<HTMLInputElement>) => Promise<void>;
24
+ currentDescription: string;
25
+ toggleEditMode: () => void;
26
+ };
27
+
28
+ /**
29
+ * Hook to manage the state and behavior for editing chat descriptions.
30
+ *
31
+ * Offers functions to:
32
+ * - Switch between edit and view modes.
33
+ * - Manage input changes, blur, and form submission events.
34
+ * - Save updates to IndexedDB and optionally to the global application state.
35
+ *
36
+ * @param {Object} options
37
+ * @param {string} options.initialDescription - The current chat description.
38
+ * @param {string} options.customChatId - Optional ID for updating the description via the sidebar.
39
+ * @param {boolean} options.syncWithGlobalStore - Flag to indicate global description store synchronization.
40
+ * @returns {EditChatDescriptionHook} Methods and state for managing description edits.
41
+ */
42
+ export function useEditChatDescription({
43
+ initialDescription = descriptionStore.get()!,
44
+ customChatId,
45
+ syncWithGlobalStore,
46
+ }: EditChatDescriptionOptions): EditChatDescriptionHook {
47
+ const chatIdFromStore = useStore(chatIdStore);
48
+ const [editing, setEditing] = useState(false);
49
+ const [currentDescription, setCurrentDescription] = useState(initialDescription);
50
+
51
+ const [chatId, setChatId] = useState<string>();
52
+
53
+ useEffect(() => {
54
+ setChatId(customChatId || chatIdFromStore);
55
+ }, [customChatId, chatIdFromStore]);
56
+ useEffect(() => {
57
+ setCurrentDescription(initialDescription);
58
+ }, [initialDescription]);
59
+
60
+ const toggleEditMode = useCallback(() => setEditing((prev) => !prev), []);
61
+
62
+ const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
63
+ setCurrentDescription(e.target.value);
64
+ }, []);
65
+
66
+ const fetchLatestDescription = useCallback(async () => {
67
+ if (!db || !chatId) {
68
+ return initialDescription;
69
+ }
70
+
71
+ try {
72
+ const chat = await getMessages(db, chatId);
73
+ return chat?.description || initialDescription;
74
+ } catch (error) {
75
+ console.error('Failed to fetch latest description:', error);
76
+ return initialDescription;
77
+ }
78
+ }, [db, chatId, initialDescription]);
79
+
80
+ const handleBlur = useCallback(async () => {
81
+ const latestDescription = await fetchLatestDescription();
82
+ setCurrentDescription(latestDescription);
83
+ toggleEditMode();
84
+ }, [fetchLatestDescription, toggleEditMode]);
85
+
86
+ const isValidDescription = useCallback((desc: string): boolean => {
87
+ const trimmedDesc = desc.trim();
88
+
89
+ if (trimmedDesc === initialDescription) {
90
+ toggleEditMode();
91
+ return false; // No change, skip validation
92
+ }
93
+
94
+ const lengthValid = trimmedDesc.length > 0 && trimmedDesc.length <= 100;
95
+ const characterValid = /^[a-zA-Z0-9\s]+$/.test(trimmedDesc);
96
+
97
+ if (!lengthValid) {
98
+ toast.error('Description must be between 1 and 100 characters.');
99
+ return false;
100
+ }
101
+
102
+ if (!characterValid) {
103
+ toast.error('Description can only contain alphanumeric characters and spaces.');
104
+ return false;
105
+ }
106
+
107
+ return true;
108
+ }, []);
109
+
110
+ const handleSubmit = useCallback(
111
+ async (event: React.FormEvent) => {
112
+ event.preventDefault();
113
+
114
+ if (!isValidDescription(currentDescription)) {
115
+ return;
116
+ }
117
+
118
+ try {
119
+ if (!db) {
120
+ toast.error('Chat persistence is not available');
121
+ return;
122
+ }
123
+
124
+ if (!chatId) {
125
+ toast.error('Chat Id is not available');
126
+ return;
127
+ }
128
+
129
+ await updateChatDescription(db, chatId, currentDescription);
130
+
131
+ if (syncWithGlobalStore) {
132
+ descriptionStore.set(currentDescription);
133
+ }
134
+
135
+ toast.success('Chat description updated successfully');
136
+ } catch (error) {
137
+ toast.error('Failed to update chat description: ' + (error as Error).message);
138
+ }
139
+
140
+ toggleEditMode();
141
+ },
142
+ [currentDescription, db, chatId, initialDescription, customChatId],
143
+ );
144
+
145
+ const handleKeyDown = useCallback(
146
+ async (e: React.KeyboardEvent<HTMLInputElement>) => {
147
+ if (e.key === 'Escape') {
148
+ await handleBlur();
149
+ }
150
+ },
151
+ [handleBlur],
152
+ );
153
+
154
+ return {
155
+ editing,
156
+ handleChange,
157
+ handleBlur,
158
+ handleSubmit,
159
+ handleKeyDown,
160
+ currentDescription,
161
+ toggleEditMode,
162
+ };
163
+ }
app/lib/persistence/ChatDescription.client.tsx CHANGED
@@ -1,6 +1,68 @@
1
  import { useStore } from '@nanostores/react';
2
- import { description } from './useChatHistory';
 
 
 
3
 
4
  export function ChatDescription() {
5
- return useStore(description);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
  }
 
1
  import { useStore } from '@nanostores/react';
2
+ import { TooltipProvider } from '@radix-ui/react-tooltip';
3
+ import WithTooltip from '~/components/ui/Tooltip';
4
+ import { useEditChatDescription } from '~/lib/hooks';
5
+ import { description as descriptionStore } from '~/lib/persistence';
6
 
7
  export function ChatDescription() {
8
+ const initialDescription = useStore(descriptionStore)!;
9
+
10
+ const { editing, handleChange, handleBlur, handleSubmit, handleKeyDown, currentDescription, toggleEditMode } =
11
+ useEditChatDescription({
12
+ initialDescription,
13
+ syncWithGlobalStore: true,
14
+ });
15
+
16
+ if (!initialDescription) {
17
+ // doing this to prevent showing edit button until chat description is set
18
+ return null;
19
+ }
20
+
21
+ return (
22
+ <div className="flex items-center justify-center">
23
+ {editing ? (
24
+ <form onSubmit={handleSubmit} className="flex items-center justify-center">
25
+ <input
26
+ type="text"
27
+ className="bg-bolt-elements-background-depth-1 text-bolt-elements-textPrimary rounded px-2 mr-2 w-fit"
28
+ autoFocus
29
+ value={currentDescription}
30
+ onChange={handleChange}
31
+ onBlur={handleBlur}
32
+ onKeyDown={handleKeyDown}
33
+ style={{ width: `${Math.max(currentDescription.length * 8, 100)}px` }}
34
+ />
35
+ <TooltipProvider>
36
+ <WithTooltip tooltip="Save title">
37
+ <div className="flex justify-between items-center p-2 rounded-md bg-bolt-elements-item-backgroundAccent">
38
+ <button
39
+ type="submit"
40
+ className="i-ph:check-bold scale-110 hover:text-bolt-elements-item-contentAccent"
41
+ onMouseDown={handleSubmit}
42
+ />
43
+ </div>
44
+ </WithTooltip>
45
+ </TooltipProvider>
46
+ </form>
47
+ ) : (
48
+ <>
49
+ {currentDescription}
50
+ <TooltipProvider>
51
+ <WithTooltip tooltip="Rename chat">
52
+ <div className="flex justify-between items-center p-2 rounded-md bg-bolt-elements-item-backgroundAccent ml-2">
53
+ <button
54
+ type="button"
55
+ className="i-ph:pencil-fill scale-110 hover:text-bolt-elements-item-contentAccent"
56
+ onClick={(event) => {
57
+ event.preventDefault();
58
+ toggleEditMode();
59
+ }}
60
+ />
61
+ </div>
62
+ </WithTooltip>
63
+ </TooltipProvider>
64
+ </>
65
+ )}
66
+ </div>
67
+ );
68
  }
app/lib/persistence/db.ts CHANGED
@@ -52,17 +52,23 @@ export async function setMessages(
52
  messages: Message[],
53
  urlId?: string,
54
  description?: string,
 
55
  ): Promise<void> {
56
  return new Promise((resolve, reject) => {
57
  const transaction = db.transaction('chats', 'readwrite');
58
  const store = transaction.objectStore('chats');
59
 
 
 
 
 
 
60
  const request = store.put({
61
  id,
62
  messages,
63
  urlId,
64
  description,
65
- timestamp: new Date().toISOString(),
66
  });
67
 
68
  request.onsuccess = () => resolve();
@@ -212,3 +218,17 @@ export async function createChatFromMessages(
212
 
213
  return newUrlId; // Return the urlId instead of id for navigation
214
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  messages: Message[],
53
  urlId?: string,
54
  description?: string,
55
+ timestamp?: string,
56
  ): Promise<void> {
57
  return new Promise((resolve, reject) => {
58
  const transaction = db.transaction('chats', 'readwrite');
59
  const store = transaction.objectStore('chats');
60
 
61
+ if (timestamp && isNaN(Date.parse(timestamp))) {
62
+ reject(new Error('Invalid timestamp'));
63
+ return;
64
+ }
65
+
66
  const request = store.put({
67
  id,
68
  messages,
69
  urlId,
70
  description,
71
+ timestamp: timestamp ?? new Date().toISOString(),
72
  });
73
 
74
  request.onsuccess = () => resolve();
 
218
 
219
  return newUrlId; // Return the urlId instead of id for navigation
220
  }
221
+
222
+ export async function updateChatDescription(db: IDBDatabase, id: string, description: string): Promise<void> {
223
+ const chat = await getMessages(db, id);
224
+
225
+ if (!chat) {
226
+ throw new Error('Chat not found');
227
+ }
228
+
229
+ if (!description.trim()) {
230
+ throw new Error('Description cannot be empty');
231
+ }
232
+
233
+ await setMessages(db, id, chat.messages, chat.urlId, description, chat.timestamp);
234
+ }
app/lib/runtime/action-runner.ts CHANGED
@@ -100,6 +100,10 @@ export class ActionRunner {
100
  .catch((error) => {
101
  console.error('Action failed:', error);
102
  });
 
 
 
 
103
  }
104
 
105
  async #executeAction(actionId: string, isStreaming: boolean = false) {
 
100
  .catch((error) => {
101
  console.error('Action failed:', error);
102
  });
103
+
104
+ await this.#currentExecutionPromise;
105
+
106
+ return;
107
  }
108
 
109
  async #executeAction(actionId: string, isStreaming: boolean = false) {
app/routes/api.enhancer.ts CHANGED
@@ -44,8 +44,9 @@ async function enhancerAction({ context, request }: ActionFunctionArgs) {
44
  content:
45
  `[Model: ${model}]\n\n[Provider: ${providerName}]\n\n` +
46
  stripIndents`
47
- You are a professional prompt engineer specializing in crafting precise, effective prompts.
48
  Your task is to enhance prompts by making them more specific, actionable, and effective.
 
49
  I want you to improve the user prompt that is wrapped in \`<original_prompt>\` tags.
50
 
51
  For valid prompts:
@@ -55,12 +56,14 @@ async function enhancerAction({ context, request }: ActionFunctionArgs) {
55
  - Maintain the core intent
56
  - Ensure the prompt is self-contained
57
  - Use professional language
 
58
  For invalid or unclear prompts:
59
  - Respond with a clear, professional guidance message
60
  - Keep responses concise and actionable
61
  - Maintain a helpful, constructive tone
62
  - Focus on what the user should provide
63
  - Use a standard template for consistency
 
64
  IMPORTANT: Your response must ONLY contain the enhanced prompt text.
65
  Do not include any explanations, metadata, or wrapper tags.
66
 
 
44
  content:
45
  `[Model: ${model}]\n\n[Provider: ${providerName}]\n\n` +
46
  stripIndents`
47
+ You are a professional prompt engineer specializing in crafting precise, effective prompts.
48
  Your task is to enhance prompts by making them more specific, actionable, and effective.
49
+
50
  I want you to improve the user prompt that is wrapped in \`<original_prompt>\` tags.
51
 
52
  For valid prompts:
 
56
  - Maintain the core intent
57
  - Ensure the prompt is self-contained
58
  - Use professional language
59
+
60
  For invalid or unclear prompts:
61
  - Respond with a clear, professional guidance message
62
  - Keep responses concise and actionable
63
  - Maintain a helpful, constructive tone
64
  - Focus on what the user should provide
65
  - Use a standard template for consistency
66
+
67
  IMPORTANT: Your response must ONLY contain the enhanced prompt text.
68
  Do not include any explanations, metadata, or wrapper tags.
69