PuneetP16
commited on
Commit
·
5335580
1
Parent(s):
0d49c74
[feat]: Implement chat description editing in sidebar and header, add visual cue for active chat in sidebar
Browse files
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 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
<
|
35 |
-
|
36 |
-
|
37 |
-
|
|
|
|
|
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/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
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
23 |
onClick={(event) => {
|
24 |
event.preventDefault();
|
25 |
exportChat(item.id);
|
26 |
}}
|
27 |
-
title="Export chat"
|
28 |
/>
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
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 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
45 |
onClick={(event) => {
|
46 |
event.preventDefault();
|
47 |
onDelete?.(event);
|
48 |
}}
|
49 |
/>
|
50 |
-
</
|
51 |
-
</
|
52 |
</div>
|
53 |
-
</
|
54 |
-
|
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-15 group-hover:from-99%',
|
60 |
+
{ 'from-bolt-elements-background-depth-3 w-15 from-99%': 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,152 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { useCallback, useEffect, useState } from 'react';
|
2 |
+
import { toast } from 'react-toastify';
|
3 |
+
import {
|
4 |
+
chatId as chatIdStore,
|
5 |
+
description as descriptionStore,
|
6 |
+
db,
|
7 |
+
updateChatDescription,
|
8 |
+
getMessages,
|
9 |
+
} from '~/lib/persistence';
|
10 |
+
|
11 |
+
interface EditChatDescriptionOptions {
|
12 |
+
initialDescription?: string;
|
13 |
+
customChatId?: string;
|
14 |
+
syncWithGlobalStore?: boolean;
|
15 |
+
}
|
16 |
+
|
17 |
+
type EditChatDescriptionHook = {
|
18 |
+
editing: boolean;
|
19 |
+
handleChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
20 |
+
handleBlur: () => Promise<void>;
|
21 |
+
handleSubmit: (event: React.FormEvent) => Promise<void>;
|
22 |
+
handleKeyDown: (event: React.KeyboardEvent<HTMLInputElement>) => Promise<void>;
|
23 |
+
currentDescription: string;
|
24 |
+
toggleEditMode: () => void;
|
25 |
+
};
|
26 |
+
|
27 |
+
/**
|
28 |
+
* Hook to manage the state and behavior for editing chat descriptions.
|
29 |
+
*
|
30 |
+
* Offers functions to:
|
31 |
+
* - Switch between edit and view modes.
|
32 |
+
* - Manage input changes, blur, and form submission events.
|
33 |
+
* - Save updates to IndexedDB and optionally to the global application state.
|
34 |
+
*
|
35 |
+
* @param {Object} options
|
36 |
+
* @param {string} options.initialDescription - The current chat description.
|
37 |
+
* @param {string} options.customChatId - Optional ID for updating the description via the sidebar.
|
38 |
+
* @param {boolean} options.syncWithGlobalStore - Flag to indicate global description store synchronization.
|
39 |
+
* @returns {EditChatDescriptionHook} Methods and state for managing description edits.
|
40 |
+
*/
|
41 |
+
export function useEditChatDescription({
|
42 |
+
initialDescription = descriptionStore.get()!,
|
43 |
+
customChatId,
|
44 |
+
syncWithGlobalStore,
|
45 |
+
}: EditChatDescriptionOptions): EditChatDescriptionHook {
|
46 |
+
const chatId = (customChatId || chatIdStore.get()) as string;
|
47 |
+
const [editing, setEditing] = useState(false);
|
48 |
+
const [currentDescription, setCurrentDescription] = useState(initialDescription);
|
49 |
+
|
50 |
+
useEffect(() => {
|
51 |
+
setCurrentDescription(initialDescription);
|
52 |
+
}, [initialDescription]);
|
53 |
+
|
54 |
+
const toggleEditMode = useCallback(() => setEditing((prev) => !prev), []);
|
55 |
+
|
56 |
+
const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
57 |
+
setCurrentDescription(e.target.value);
|
58 |
+
}, []);
|
59 |
+
|
60 |
+
const fetchLatestDescription = useCallback(async () => {
|
61 |
+
if (!db || !chatId) {
|
62 |
+
return initialDescription;
|
63 |
+
}
|
64 |
+
|
65 |
+
try {
|
66 |
+
const chat = await getMessages(db, chatId);
|
67 |
+
return chat?.description || initialDescription;
|
68 |
+
} catch (error) {
|
69 |
+
console.error('Failed to fetch latest description:', error);
|
70 |
+
return initialDescription;
|
71 |
+
}
|
72 |
+
}, [db, chatId, initialDescription]);
|
73 |
+
|
74 |
+
const handleBlur = useCallback(async () => {
|
75 |
+
const latestDescription = await fetchLatestDescription();
|
76 |
+
setCurrentDescription(latestDescription);
|
77 |
+
toggleEditMode();
|
78 |
+
}, [fetchLatestDescription, toggleEditMode]);
|
79 |
+
|
80 |
+
const isValidDescription = useCallback((desc: string): boolean => {
|
81 |
+
const trimmedDesc = desc.trim();
|
82 |
+
|
83 |
+
if (trimmedDesc === initialDescription) {
|
84 |
+
toggleEditMode();
|
85 |
+
return false; // No change, skip validation
|
86 |
+
}
|
87 |
+
|
88 |
+
const lengthValid = trimmedDesc.length > 0 && trimmedDesc.length <= 100;
|
89 |
+
const characterValid = /^[a-zA-Z0-9\s]+$/.test(trimmedDesc);
|
90 |
+
|
91 |
+
if (!lengthValid) {
|
92 |
+
toast.error('Description must be between 1 and 100 characters.');
|
93 |
+
return false;
|
94 |
+
}
|
95 |
+
|
96 |
+
if (!characterValid) {
|
97 |
+
toast.error('Description can only contain alphanumeric characters and spaces.');
|
98 |
+
return false;
|
99 |
+
}
|
100 |
+
|
101 |
+
return true;
|
102 |
+
}, []);
|
103 |
+
|
104 |
+
const handleSubmit = useCallback(
|
105 |
+
async (event: React.FormEvent) => {
|
106 |
+
event.preventDefault();
|
107 |
+
|
108 |
+
if (!isValidDescription(currentDescription)) {
|
109 |
+
return;
|
110 |
+
}
|
111 |
+
|
112 |
+
try {
|
113 |
+
if (!db) {
|
114 |
+
toast.error('Chat persistence is not available');
|
115 |
+
return;
|
116 |
+
}
|
117 |
+
|
118 |
+
await updateChatDescription(db, chatId, currentDescription);
|
119 |
+
|
120 |
+
if (syncWithGlobalStore) {
|
121 |
+
descriptionStore.set(currentDescription);
|
122 |
+
}
|
123 |
+
|
124 |
+
toast.success('Chat description updated successfully');
|
125 |
+
} catch (error) {
|
126 |
+
toast.error('Failed to update chat description: ' + (error as Error).message);
|
127 |
+
}
|
128 |
+
|
129 |
+
toggleEditMode();
|
130 |
+
},
|
131 |
+
[currentDescription, db, chatId, initialDescription, customChatId],
|
132 |
+
);
|
133 |
+
|
134 |
+
const handleKeyDown = useCallback(
|
135 |
+
async (e: React.KeyboardEvent<HTMLInputElement>) => {
|
136 |
+
if (e.key === 'Escape') {
|
137 |
+
await handleBlur();
|
138 |
+
}
|
139 |
+
},
|
140 |
+
[handleBlur],
|
141 |
+
);
|
142 |
+
|
143 |
+
return {
|
144 |
+
editing,
|
145 |
+
handleChange,
|
146 |
+
handleBlur,
|
147 |
+
handleSubmit,
|
148 |
+
handleKeyDown,
|
149 |
+
currentDescription,
|
150 |
+
toggleEditMode,
|
151 |
+
};
|
152 |
+
}
|
app/lib/persistence/ChatDescription.client.tsx
CHANGED
@@ -1,6 +1,68 @@
|
|
1 |
import { useStore } from '@nanostores/react';
|
2 |
-
import {
|
|
|
|
|
|
|
3 |
|
4 |
export function ChatDescription() {
|
5 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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-background-depth-3">
|
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-background-depth-3 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,9 @@ 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 |
+
// eslint-disable-next-line consistent-return -- TODO: fix this consistent-return error
|
105 |
+
return this.#currentExecutionPromise;
|
106 |
}
|
107 |
|
108 |
async #executeAction(actionId: string, isStreaming: boolean = false) {
|