matt HOFFNER commited on
Commit
a646f5a
·
1 Parent(s): 2670b73
components/Chatbar/Chatbar.tsx CHANGED
@@ -16,11 +16,6 @@ import { PluginKey } from '@/types/plugin';
16
 
17
  import HomeContext from '@/pages/api/home/home.context';
18
 
19
- import { ChatFolders } from './components/ChatFolders';
20
- import { ChatbarSettings } from './components/ChatbarSettings';
21
- import { Conversations } from './components/Conversations';
22
-
23
- import Sidebar from '../Sidebar';
24
  import ChatbarContext from './Chatbar.context';
25
  import { ChatbarInitialState, initialState } from './Chatbar.state';
26
 
@@ -36,7 +31,6 @@ export const Chatbar = () => {
36
  const {
37
  state: { conversations, showChatbar, defaultModelId, folders, pluginKeys },
38
  dispatch: homeDispatch,
39
- handleCreateFolder,
40
  handleNewConversation,
41
  handleUpdateConversation,
42
  } = useContext(HomeContext);
@@ -219,23 +213,7 @@ export const Chatbar = () => {
219
  handleApiKeyChange,
220
  }}
221
  >
222
- <Sidebar<Conversation>
223
- side={'left'}
224
- isOpen={showChatbar}
225
- addItemButtonTitle={t('New chat')}
226
- itemComponent={<Conversations conversations={filteredConversations} />}
227
- folderComponent={<ChatFolders searchTerm={searchTerm} />}
228
- items={filteredConversations}
229
- searchTerm={searchTerm}
230
- handleSearchTerm={(searchTerm: string) =>
231
- chatDispatch({ field: 'searchTerm', value: searchTerm })
232
- }
233
- toggleOpen={handleToggleChatbar}
234
- handleCreateItem={handleNewConversation}
235
- handleCreateFolder={() => handleCreateFolder(t('New folder'), 'chat')}
236
- handleDrop={handleDrop}
237
- footerComponent={<ChatbarSettings />}
238
- />
239
  </ChatbarContext.Provider>
240
  );
241
  };
 
16
 
17
  import HomeContext from '@/pages/api/home/home.context';
18
 
 
 
 
 
 
19
  import ChatbarContext from './Chatbar.context';
20
  import { ChatbarInitialState, initialState } from './Chatbar.state';
21
 
 
31
  const {
32
  state: { conversations, showChatbar, defaultModelId, folders, pluginKeys },
33
  dispatch: homeDispatch,
 
34
  handleNewConversation,
35
  handleUpdateConversation,
36
  } = useContext(HomeContext);
 
213
  handleApiKeyChange,
214
  }}
215
  >
216
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
217
  </ChatbarContext.Provider>
218
  );
219
  };
components/Chatbar/components/ChatFolders.tsx DELETED
@@ -1,64 +0,0 @@
1
- import { useContext } from 'react';
2
-
3
- import { FolderInterface } from '@/types/folder';
4
-
5
- import HomeContext from '@/pages/api/home/home.context';
6
-
7
- import Folder from '@/components/Folder';
8
-
9
- import { ConversationComponent } from './Conversation';
10
-
11
- interface Props {
12
- searchTerm: string;
13
- }
14
-
15
- export const ChatFolders = ({ searchTerm }: Props) => {
16
- const {
17
- state: { folders, conversations },
18
- handleUpdateConversation,
19
- } = useContext(HomeContext);
20
-
21
- const handleDrop = (e: any, folder: FolderInterface) => {
22
- if (e.dataTransfer) {
23
- const conversation = JSON.parse(e.dataTransfer.getData('conversation'));
24
- handleUpdateConversation(conversation, {
25
- key: 'folderId',
26
- value: folder.id,
27
- });
28
- }
29
- };
30
-
31
- const ChatFolders = (currentFolder: FolderInterface) => {
32
- return (
33
- conversations &&
34
- conversations
35
- .filter((conversation) => conversation.folderId)
36
- .map((conversation, index) => {
37
- if (conversation.folderId === currentFolder.id) {
38
- return (
39
- <div key={index} className="ml-5 gap-2 border-l pl-2">
40
- <ConversationComponent conversation={conversation} />
41
- </div>
42
- );
43
- }
44
- })
45
- );
46
- };
47
-
48
- return (
49
- <div className="flex w-full flex-col pt-2">
50
- {folders
51
- .filter((folder) => folder.type === 'chat')
52
- .sort((a, b) => a.name.localeCompare(b.name))
53
- .map((folder, index) => (
54
- <Folder
55
- key={index}
56
- searchTerm={searchTerm}
57
- currentFolder={folder}
58
- handleDrop={handleDrop}
59
- folderComponent={ChatFolders(folder)}
60
- />
61
- ))}
62
- </div>
63
- );
64
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/Chatbar/components/ChatbarSettings.tsx CHANGED
@@ -9,10 +9,8 @@ import { SettingDialog } from '@/components/Settings/SettingDialog';
9
 
10
  import { Import } from '../../Settings/Import';
11
  import { Key } from '../../Settings/Key';
12
- import { SidebarButton } from '../../Sidebar/SidebarButton';
13
  import ChatbarContext from '../Chatbar.context';
14
  import { ClearConversations } from './ClearConversations';
15
- import { PluginKeys } from './PluginKeys';
16
 
17
  export const ChatbarSettings = () => {
18
  const { t } = useTranslation('sidebar');
@@ -44,24 +42,10 @@ export const ChatbarSettings = () => {
44
 
45
  <Import onImport={handleImportConversations} />
46
 
47
- <SidebarButton
48
- text={t('Export data')}
49
- icon={<IconFileExport size={18} />}
50
- onClick={() => handleExportData()}
51
- />
52
-
53
- <SidebarButton
54
- text={t('Settings')}
55
- icon={<IconSettings size={18} />}
56
- onClick={() => setIsSettingDialog(true)}
57
- />
58
-
59
  {!serverSideApiKeyIsSet ? (
60
  <Key apiKey={apiKey} onApiKeyChange={handleApiKeyChange} />
61
  ) : null}
62
 
63
- {!serverSidePluginKeysSet ? <PluginKeys /> : null}
64
-
65
  <SettingDialog
66
  open={isSettingDialogOpen}
67
  onClose={() => {
 
9
 
10
  import { Import } from '../../Settings/Import';
11
  import { Key } from '../../Settings/Key';
 
12
  import ChatbarContext from '../Chatbar.context';
13
  import { ClearConversations } from './ClearConversations';
 
14
 
15
  export const ChatbarSettings = () => {
16
  const { t } = useTranslation('sidebar');
 
42
 
43
  <Import onImport={handleImportConversations} />
44
 
 
 
 
 
 
 
 
 
 
 
 
 
45
  {!serverSideApiKeyIsSet ? (
46
  <Key apiKey={apiKey} onApiKeyChange={handleApiKeyChange} />
47
  ) : null}
48
 
 
 
49
  <SettingDialog
50
  open={isSettingDialogOpen}
51
  onClose={() => {
components/Chatbar/components/ClearConversations.tsx CHANGED
@@ -3,7 +3,6 @@ import { FC, useState } from 'react';
3
 
4
  import { useTranslation } from 'next-i18next';
5
 
6
- import { SidebarButton } from '@/components/Sidebar/SidebarButton';
7
 
8
  interface Props {
9
  onClearConversations: () => void;
@@ -48,10 +47,6 @@ export const ClearConversations: FC<Props> = ({ onClearConversations }) => {
48
  </div>
49
  </div>
50
  ) : (
51
- <SidebarButton
52
- text={t('Clear conversations')}
53
- icon={<IconTrash size={18} />}
54
- onClick={() => setIsConfirming(true)}
55
- />
56
  );
57
  };
 
3
 
4
  import { useTranslation } from 'next-i18next';
5
 
 
6
 
7
  interface Props {
8
  onClearConversations: () => void;
 
47
  </div>
48
  </div>
49
  ) : (
50
+ null
 
 
 
 
51
  );
52
  };
components/Chatbar/components/PluginKeys.tsx DELETED
@@ -1,235 +0,0 @@
1
- import { IconKey } from '@tabler/icons-react';
2
- import { KeyboardEvent, useContext, useEffect, useRef, useState } from 'react';
3
- import { useTranslation } from 'react-i18next';
4
-
5
- import { PluginID, PluginKey } from '@/types/plugin';
6
-
7
- import HomeContext from '@/pages/api/home/home.context';
8
-
9
- import { SidebarButton } from '@/components/Sidebar/SidebarButton';
10
-
11
- import ChatbarContext from '../Chatbar.context';
12
-
13
- export const PluginKeys = () => {
14
- const { t } = useTranslation('sidebar');
15
-
16
- const {
17
- state: { pluginKeys },
18
- } = useContext(HomeContext);
19
-
20
- const { handlePluginKeyChange, handleClearPluginKey } =
21
- useContext(ChatbarContext);
22
-
23
- const [isChanging, setIsChanging] = useState(false);
24
-
25
- const modalRef = useRef<HTMLDivElement>(null);
26
-
27
- const handleEnter = (e: KeyboardEvent<HTMLDivElement>) => {
28
- if (e.key === 'Enter' && !e.shiftKey) {
29
- e.preventDefault();
30
- setIsChanging(false);
31
- }
32
- };
33
-
34
- useEffect(() => {
35
- const handleMouseDown = (e: MouseEvent) => {
36
- if (modalRef.current && !modalRef.current.contains(e.target as Node)) {
37
- window.addEventListener('mouseup', handleMouseUp);
38
- }
39
- };
40
-
41
- const handleMouseUp = (e: MouseEvent) => {
42
- window.removeEventListener('mouseup', handleMouseUp);
43
- setIsChanging(false);
44
- };
45
-
46
- window.addEventListener('mousedown', handleMouseDown);
47
-
48
- return () => {
49
- window.removeEventListener('mousedown', handleMouseDown);
50
- };
51
- }, []);
52
-
53
- return (
54
- <>
55
- <SidebarButton
56
- text={t('Plugin Keys')}
57
- icon={<IconKey size={18} />}
58
- onClick={() => setIsChanging(true)}
59
- />
60
-
61
- {isChanging && (
62
- <div
63
- className="z-100 fixed inset-0 flex items-center justify-center bg-black bg-opacity-50"
64
- onKeyDown={handleEnter}
65
- >
66
- <div className="fixed inset-0 z-10 overflow-hidden">
67
- <div className="flex min-h-screen items-center justify-center px-4 pt-4 pb-20 text-center sm:block sm:p-0">
68
- <div
69
- className="hidden sm:inline-block sm:h-screen sm:align-middle"
70
- aria-hidden="true"
71
- />
72
-
73
- <div
74
- ref={modalRef}
75
- className="dark:border-netural-400 inline-block max-h-[400px] transform overflow-y-auto rounded-lg border border-gray-300 bg-white px-4 pt-5 pb-4 text-left align-bottom shadow-xl transition-all dark:bg-[#202123] sm:my-8 sm:max-h-[600px] sm:w-full sm:max-w-lg sm:p-6 sm:align-middle"
76
- role="dialog"
77
- >
78
- <div className="mb-10 text-4xl">Plugin Keys</div>
79
-
80
- <div className="mt-6 rounded border p-4">
81
- <div className="text-xl font-bold">Google Search Plugin</div>
82
- <div className="mt-4 italic">
83
- Please enter your Google API Key and Google CSE ID to enable
84
- the Google Search Plugin.
85
- </div>
86
-
87
- <div className="mt-6 text-sm font-bold text-black dark:text-neutral-200">
88
- Google API Key
89
- </div>
90
- <input
91
- className="mt-2 w-full rounded-lg border border-neutral-500 px-4 py-2 text-neutral-900 shadow focus:outline-none dark:border-neutral-800 dark:border-opacity-50 dark:bg-[#40414F] dark:text-neutral-100"
92
- type="password"
93
- value={
94
- pluginKeys
95
- .find((p) => p.pluginId === PluginID.GOOGLE_SEARCH)
96
- ?.requiredKeys.find((k) => k.key === 'GOOGLE_API_KEY')
97
- ?.value
98
- }
99
- onChange={(e) => {
100
- const pluginKey = pluginKeys.find(
101
- (p) => p.pluginId === PluginID.GOOGLE_SEARCH,
102
- );
103
-
104
- if (pluginKey) {
105
- const requiredKey = pluginKey.requiredKeys.find(
106
- (k) => k.key === 'GOOGLE_API_KEY',
107
- );
108
-
109
- if (requiredKey) {
110
- const updatedPluginKey = {
111
- ...pluginKey,
112
- requiredKeys: pluginKey.requiredKeys.map((k) => {
113
- if (k.key === 'GOOGLE_API_KEY') {
114
- return {
115
- ...k,
116
- value: e.target.value,
117
- };
118
- }
119
-
120
- return k;
121
- }),
122
- };
123
-
124
- handlePluginKeyChange(updatedPluginKey);
125
- }
126
- } else {
127
- const newPluginKey: PluginKey = {
128
- pluginId: PluginID.GOOGLE_SEARCH,
129
- requiredKeys: [
130
- {
131
- key: 'GOOGLE_API_KEY',
132
- value: e.target.value,
133
- },
134
- {
135
- key: 'GOOGLE_CSE_ID',
136
- value: '',
137
- },
138
- ],
139
- };
140
-
141
- handlePluginKeyChange(newPluginKey);
142
- }
143
- }}
144
- />
145
-
146
- <div className="mt-6 text-sm font-bold text-black dark:text-neutral-200">
147
- Google CSE ID
148
- </div>
149
- <input
150
- className="mt-2 w-full rounded-lg border border-neutral-500 px-4 py-2 text-neutral-900 shadow focus:outline-none dark:border-neutral-800 dark:border-opacity-50 dark:bg-[#40414F] dark:text-neutral-100"
151
- type="password"
152
- value={
153
- pluginKeys
154
- .find((p) => p.pluginId === PluginID.GOOGLE_SEARCH)
155
- ?.requiredKeys.find((k) => k.key === 'GOOGLE_CSE_ID')
156
- ?.value
157
- }
158
- onChange={(e) => {
159
- const pluginKey = pluginKeys.find(
160
- (p) => p.pluginId === PluginID.GOOGLE_SEARCH,
161
- );
162
-
163
- if (pluginKey) {
164
- const requiredKey = pluginKey.requiredKeys.find(
165
- (k) => k.key === 'GOOGLE_CSE_ID',
166
- );
167
-
168
- if (requiredKey) {
169
- const updatedPluginKey = {
170
- ...pluginKey,
171
- requiredKeys: pluginKey.requiredKeys.map((k) => {
172
- if (k.key === 'GOOGLE_CSE_ID') {
173
- return {
174
- ...k,
175
- value: e.target.value,
176
- };
177
- }
178
-
179
- return k;
180
- }),
181
- };
182
-
183
- handlePluginKeyChange(updatedPluginKey);
184
- }
185
- } else {
186
- const newPluginKey: PluginKey = {
187
- pluginId: PluginID.GOOGLE_SEARCH,
188
- requiredKeys: [
189
- {
190
- key: 'GOOGLE_API_KEY',
191
- value: '',
192
- },
193
- {
194
- key: 'GOOGLE_CSE_ID',
195
- value: e.target.value,
196
- },
197
- ],
198
- };
199
-
200
- handlePluginKeyChange(newPluginKey);
201
- }
202
- }}
203
- />
204
-
205
- <button
206
- className="mt-6 w-full rounded-lg border border-neutral-500 px-4 py-2 text-neutral-900 shadow hover:bg-neutral-100 focus:outline-none dark:border-neutral-800 dark:border-opacity-50 dark:bg-white dark:text-black dark:hover:bg-neutral-300"
207
- onClick={() => {
208
- const pluginKey = pluginKeys.find(
209
- (p) => p.pluginId === PluginID.GOOGLE_SEARCH,
210
- );
211
-
212
- if (pluginKey) {
213
- handleClearPluginKey(pluginKey);
214
- }
215
- }}
216
- >
217
- Clear Google Search Plugin Keys
218
- </button>
219
- </div>
220
-
221
- <button
222
- type="button"
223
- className="mt-6 w-full rounded-lg border border-neutral-500 px-4 py-2 text-neutral-900 shadow hover:bg-neutral-100 focus:outline-none dark:border-neutral-800 dark:border-opacity-50 dark:bg-white dark:text-black dark:hover:bg-neutral-300"
224
- onClick={() => setIsChanging(false)}
225
- >
226
- {t('Save')}
227
- </button>
228
- </div>
229
- </div>
230
- </div>
231
- </div>
232
- )}
233
- </>
234
- );
235
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/Folder/Folder.tsx DELETED
@@ -1,192 +0,0 @@
1
- import {
2
- IconCaretDown,
3
- IconCaretRight,
4
- IconCheck,
5
- IconPencil,
6
- IconTrash,
7
- IconX,
8
- } from '@tabler/icons-react';
9
- import {
10
- KeyboardEvent,
11
- ReactElement,
12
- useContext,
13
- useEffect,
14
- useState,
15
- } from 'react';
16
-
17
- import { FolderInterface } from '@/types/folder';
18
-
19
- import HomeContext from '@/pages/api/home/home.context';
20
-
21
- import SidebarActionButton from '@/components/Buttons/SidebarActionButton';
22
-
23
- interface Props {
24
- currentFolder: FolderInterface;
25
- searchTerm: string;
26
- handleDrop: (e: any, folder: FolderInterface) => void;
27
- folderComponent: (ReactElement | undefined)[];
28
- }
29
-
30
- const Folder = ({
31
- currentFolder,
32
- searchTerm,
33
- handleDrop,
34
- folderComponent,
35
- }: Props) => {
36
- const { handleDeleteFolder, handleUpdateFolder } = useContext(HomeContext);
37
-
38
- const [isDeleting, setIsDeleting] = useState(false);
39
- const [isRenaming, setIsRenaming] = useState(false);
40
- const [renameValue, setRenameValue] = useState('');
41
- const [isOpen, setIsOpen] = useState(false);
42
-
43
- const handleEnterDown = (e: KeyboardEvent<HTMLDivElement>) => {
44
- if (e.key === 'Enter') {
45
- e.preventDefault();
46
- handleRename();
47
- }
48
- };
49
-
50
- const handleRename = () => {
51
- handleUpdateFolder(currentFolder.id, renameValue);
52
- setRenameValue('');
53
- setIsRenaming(false);
54
- };
55
-
56
- const dropHandler = (e: any) => {
57
- if (e.dataTransfer) {
58
- setIsOpen(true);
59
-
60
- handleDrop(e, currentFolder);
61
-
62
- e.target.style.background = 'none';
63
- }
64
- };
65
-
66
- const allowDrop = (e: any) => {
67
- e.preventDefault();
68
- };
69
-
70
- const highlightDrop = (e: any) => {
71
- e.target.style.background = '#343541';
72
- };
73
-
74
- const removeHighlight = (e: any) => {
75
- e.target.style.background = 'none';
76
- };
77
-
78
- useEffect(() => {
79
- if (isRenaming) {
80
- setIsDeleting(false);
81
- } else if (isDeleting) {
82
- setIsRenaming(false);
83
- }
84
- }, [isRenaming, isDeleting]);
85
-
86
- useEffect(() => {
87
- if (searchTerm) {
88
- setIsOpen(true);
89
- } else {
90
- setIsOpen(false);
91
- }
92
- }, [searchTerm]);
93
-
94
- return (
95
- <>
96
- <div className="relative flex items-center">
97
- {isRenaming ? (
98
- <div className="flex w-full items-center gap-3 bg-[#343541]/90 p-3">
99
- {isOpen ? (
100
- <IconCaretDown size={18} />
101
- ) : (
102
- <IconCaretRight size={18} />
103
- )}
104
- <input
105
- className="mr-12 flex-1 overflow-hidden overflow-ellipsis border-neutral-400 bg-transparent text-left text-[12.5px] leading-3 text-white outline-none focus:border-neutral-100"
106
- type="text"
107
- value={renameValue}
108
- onChange={(e) => setRenameValue(e.target.value)}
109
- onKeyDown={handleEnterDown}
110
- autoFocus
111
- />
112
- </div>
113
- ) : (
114
- <button
115
- className={`flex w-full cursor-pointer items-center gap-3 rounded-lg p-3 text-sm transition-colors duration-200 hover:bg-[#343541]/90`}
116
- onClick={() => setIsOpen(!isOpen)}
117
- onDrop={(e) => dropHandler(e)}
118
- onDragOver={allowDrop}
119
- onDragEnter={highlightDrop}
120
- onDragLeave={removeHighlight}
121
- >
122
- {isOpen ? (
123
- <IconCaretDown size={18} />
124
- ) : (
125
- <IconCaretRight size={18} />
126
- )}
127
-
128
- <div className="relative max-h-5 flex-1 overflow-hidden text-ellipsis whitespace-nowrap break-all text-left text-[12.5px] leading-3">
129
- {currentFolder.name}
130
- </div>
131
- </button>
132
- )}
133
-
134
- {(isDeleting || isRenaming) && (
135
- <div className="absolute right-1 z-10 flex text-gray-300">
136
- <SidebarActionButton
137
- handleClick={(e) => {
138
- e.stopPropagation();
139
-
140
- if (isDeleting) {
141
- handleDeleteFolder(currentFolder.id);
142
- } else if (isRenaming) {
143
- handleRename();
144
- }
145
-
146
- setIsDeleting(false);
147
- setIsRenaming(false);
148
- }}
149
- >
150
- <IconCheck size={18} />
151
- </SidebarActionButton>
152
- <SidebarActionButton
153
- handleClick={(e) => {
154
- e.stopPropagation();
155
- setIsDeleting(false);
156
- setIsRenaming(false);
157
- }}
158
- >
159
- <IconX size={18} />
160
- </SidebarActionButton>
161
- </div>
162
- )}
163
-
164
- {!isDeleting && !isRenaming && (
165
- <div className="absolute right-1 z-10 flex text-gray-300">
166
- <SidebarActionButton
167
- handleClick={(e) => {
168
- e.stopPropagation();
169
- setIsRenaming(true);
170
- setRenameValue(currentFolder.name);
171
- }}
172
- >
173
- <IconPencil size={18} />
174
- </SidebarActionButton>
175
- <SidebarActionButton
176
- handleClick={(e) => {
177
- e.stopPropagation();
178
- setIsDeleting(true);
179
- }}
180
- >
181
- <IconTrash size={18} />
182
- </SidebarActionButton>
183
- </div>
184
- )}
185
- </div>
186
-
187
- {isOpen ? folderComponent : null}
188
- </>
189
- );
190
- };
191
-
192
- export default Folder;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/Folder/index.ts DELETED
@@ -1 +0,0 @@
1
- export { default } from './Folder';
 
 
components/Promptbar/Promptbar.tsx CHANGED
@@ -10,11 +10,8 @@ import { Prompt } from '@/types/prompt';
10
 
11
  import HomeContext from '@/pages/api/home/home.context';
12
 
13
- import { PromptFolders } from './components/PromptFolders';
14
- import { PromptbarSettings } from './components/PromptbarSettings';
15
  import { Prompts } from './components/Prompts';
16
 
17
- import Sidebar from '../Sidebar';
18
  import PromptbarContext from './PromptBar.context';
19
  import { PromptbarInitialState, initialState } from './Promptbar.state';
20
 
@@ -29,8 +26,7 @@ const Promptbar = () => {
29
 
30
  const {
31
  state: { prompts, defaultModelId, showPromptbar },
32
- dispatch: homeDispatch,
33
- handleCreateFolder,
34
  } = useContext(HomeContext);
35
 
36
  const {
@@ -125,26 +121,7 @@ const Promptbar = () => {
125
  handleUpdatePrompt,
126
  }}
127
  >
128
- <Sidebar<Prompt>
129
- side={'right'}
130
- isOpen={showPromptbar}
131
- addItemButtonTitle={t('New prompt')}
132
- itemComponent={
133
- <Prompts
134
- prompts={filteredPrompts.filter((prompt) => !prompt.folderId)}
135
- />
136
- }
137
- folderComponent={<PromptFolders />}
138
- items={filteredPrompts}
139
- searchTerm={searchTerm}
140
- handleSearchTerm={(searchTerm: string) =>
141
- promptDispatch({ field: 'searchTerm', value: searchTerm })
142
- }
143
- toggleOpen={handleTogglePromptbar}
144
- handleCreateItem={handleCreatePrompt}
145
- handleCreateFolder={() => handleCreateFolder(t('New folder'), 'prompt')}
146
- handleDrop={handleDrop}
147
- />
148
  </PromptbarContext.Provider>
149
  );
150
  };
 
10
 
11
  import HomeContext from '@/pages/api/home/home.context';
12
 
 
 
13
  import { Prompts } from './components/Prompts';
14
 
 
15
  import PromptbarContext from './PromptBar.context';
16
  import { PromptbarInitialState, initialState } from './Promptbar.state';
17
 
 
26
 
27
  const {
28
  state: { prompts, defaultModelId, showPromptbar },
29
+ dispatch: homeDispatch
 
30
  } = useContext(HomeContext);
31
 
32
  const {
 
121
  handleUpdatePrompt,
122
  }}
123
  >
124
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
125
  </PromptbarContext.Provider>
126
  );
127
  };
components/Promptbar/components/PromptFolders.tsx DELETED
@@ -1,64 +0,0 @@
1
- import { useContext } from 'react';
2
-
3
- import { FolderInterface } from '@/types/folder';
4
-
5
- import HomeContext from '@/pages/api/home/home.context';
6
-
7
- import Folder from '@/components/Folder';
8
- import { PromptComponent } from '@/components/Promptbar/components/Prompt';
9
-
10
- import PromptbarContext from '../PromptBar.context';
11
-
12
- export const PromptFolders = () => {
13
- const {
14
- state: { folders },
15
- } = useContext(HomeContext);
16
-
17
- const {
18
- state: { searchTerm, filteredPrompts },
19
- handleUpdatePrompt,
20
- } = useContext(PromptbarContext);
21
-
22
- const handleDrop = (e: any, folder: FolderInterface) => {
23
- if (e.dataTransfer) {
24
- const prompt = JSON.parse(e.dataTransfer.getData('prompt'));
25
-
26
- const updatedPrompt = {
27
- ...prompt,
28
- folderId: folder.id,
29
- };
30
-
31
- handleUpdatePrompt(updatedPrompt);
32
- }
33
- };
34
-
35
- const PromptFolders = (currentFolder: FolderInterface) =>
36
- filteredPrompts
37
- .filter((p) => p.folderId)
38
- .map((prompt, index) => {
39
- if (prompt.folderId === currentFolder.id) {
40
- return (
41
- <div key={index} className="ml-5 gap-2 border-l pl-2">
42
- <PromptComponent prompt={prompt} />
43
- </div>
44
- );
45
- }
46
- });
47
-
48
- return (
49
- <div className="flex w-full flex-col pt-2">
50
- {folders
51
- .filter((folder) => folder.type === 'prompt')
52
- .sort((a, b) => a.name.localeCompare(b.name))
53
- .map((folder, index) => (
54
- <Folder
55
- key={index}
56
- searchTerm={searchTerm}
57
- currentFolder={folder}
58
- handleDrop={handleDrop}
59
- folderComponent={PromptFolders(folder)}
60
- />
61
- ))}
62
- </div>
63
- );
64
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/Settings/Import.tsx CHANGED
@@ -5,7 +5,6 @@ import { useTranslation } from 'next-i18next';
5
 
6
  import { SupportedExportFormats } from '@/types/export';
7
 
8
- import { SidebarButton } from '../Sidebar/SidebarButton';
9
 
10
  interface Props {
11
  onImport: (data: SupportedExportFormats) => void;
@@ -34,18 +33,7 @@ export const Import: FC<Props> = ({ onImport }) => {
34
  }}
35
  />
36
 
37
- <SidebarButton
38
- text={t('Import data')}
39
- icon={<IconFileImport size={18} />}
40
- onClick={() => {
41
- const importFile = document.querySelector(
42
- '#import-file',
43
- ) as HTMLInputElement;
44
- if (importFile) {
45
- importFile.click();
46
- }
47
- }}
48
- />
49
  </>
50
  );
51
  };
 
5
 
6
  import { SupportedExportFormats } from '@/types/export';
7
 
 
8
 
9
  interface Props {
10
  onImport: (data: SupportedExportFormats) => void;
 
33
  }}
34
  />
35
 
36
+
 
 
 
 
 
 
 
 
 
 
 
37
  </>
38
  );
39
  };
components/Settings/Key.tsx CHANGED
@@ -1,9 +1,4 @@
1
- import { IconCheck, IconKey, IconX } from '@tabler/icons-react';
2
- import { FC, KeyboardEvent, useEffect, useRef, useState } from 'react';
3
-
4
- import { useTranslation } from 'next-i18next';
5
-
6
- import { SidebarButton } from '../Sidebar/SidebarButton';
7
 
8
  interface Props {
9
  apiKey: string;
 
1
+ import { FC } from 'react';
 
 
 
 
 
2
 
3
  interface Props {
4
  apiKey: string;
components/Sidebar/Sidebar.tsx DELETED
@@ -1,123 +0,0 @@
1
- import { IconFolderPlus, IconMistOff, IconPlus } from '@tabler/icons-react';
2
- import { ReactNode } from 'react';
3
- import { useTranslation } from 'react-i18next';
4
-
5
- import {
6
- CloseSidebarButton,
7
- OpenSidebarButton,
8
- } from './components/OpenCloseButton';
9
-
10
- import Search from '../Search';
11
-
12
- interface Props<T> {
13
- isOpen: boolean;
14
- addItemButtonTitle: string;
15
- side: 'left' | 'right';
16
- items: T[];
17
- itemComponent: ReactNode;
18
- folderComponent: ReactNode;
19
- footerComponent?: ReactNode;
20
- searchTerm: string;
21
- handleSearchTerm: (searchTerm: string) => void;
22
- toggleOpen: () => void;
23
- handleCreateItem: () => void;
24
- handleCreateFolder: () => void;
25
- handleDrop: (e: any) => void;
26
- }
27
-
28
- const Sidebar = <T,>({
29
- isOpen,
30
- addItemButtonTitle,
31
- side,
32
- items,
33
- itemComponent,
34
- folderComponent,
35
- footerComponent,
36
- searchTerm,
37
- handleSearchTerm,
38
- toggleOpen,
39
- handleCreateItem,
40
- handleCreateFolder,
41
- handleDrop,
42
- }: Props<T>) => {
43
- const { t } = useTranslation('promptbar');
44
-
45
- const allowDrop = (e: any) => {
46
- e.preventDefault();
47
- };
48
-
49
- const highlightDrop = (e: any) => {
50
- e.target.style.background = '#343541';
51
- };
52
-
53
- const removeHighlight = (e: any) => {
54
- e.target.style.background = 'none';
55
- };
56
-
57
- return isOpen ? (
58
- <div>
59
- <div
60
- className={`fixed top-0 ${side}-0 z-40 flex h-full w-[260px] flex-none flex-col space-y-2 bg-[#202123] p-2 text-[14px] transition-all sm:relative sm:top-0`}
61
- >
62
- <div className="flex items-center">
63
- <button
64
- className="text-sidebar flex w-[190px] flex-shrink-0 cursor-pointer select-none items-center gap-3 rounded-md border border-white/20 p-3 text-white transition-colors duration-200 hover:bg-gray-500/10"
65
- onClick={() => {
66
- handleCreateItem();
67
- handleSearchTerm('');
68
- }}
69
- >
70
- <IconPlus size={16} />
71
- {addItemButtonTitle}
72
- </button>
73
-
74
- <button
75
- className="ml-2 flex flex-shrink-0 cursor-pointer items-center gap-3 rounded-md border border-white/20 p-3 text-sm text-white transition-colors duration-200 hover:bg-gray-500/10"
76
- onClick={handleCreateFolder}
77
- >
78
- <IconFolderPlus size={16} />
79
- </button>
80
- </div>
81
- <Search
82
- placeholder={t('Search...') || ''}
83
- searchTerm={searchTerm}
84
- onSearch={handleSearchTerm}
85
- />
86
-
87
- <div className="flex-grow overflow-auto">
88
- {items?.length > 0 && (
89
- <div className="flex border-b border-white/20 pb-2">
90
- {folderComponent}
91
- </div>
92
- )}
93
-
94
- {items?.length > 0 ? (
95
- <div
96
- className="pt-2"
97
- onDrop={handleDrop}
98
- onDragOver={allowDrop}
99
- onDragEnter={highlightDrop}
100
- onDragLeave={removeHighlight}
101
- >
102
- {itemComponent}
103
- </div>
104
- ) : (
105
- <div className="mt-8 select-none text-center text-white opacity-50">
106
- <IconMistOff className="mx-auto mb-3" />
107
- <span className="text-[14px] leading-normal">
108
- {t('No data.')}
109
- </span>
110
- </div>
111
- )}
112
- </div>
113
- {footerComponent}
114
- </div>
115
-
116
- <CloseSidebarButton onClick={toggleOpen} side={side} />
117
- </div>
118
- ) : (
119
- <OpenSidebarButton onClick={toggleOpen} side={side} />
120
- );
121
- };
122
-
123
- export default Sidebar;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/Sidebar/SidebarButton.tsx DELETED
@@ -1,19 +0,0 @@
1
- import { FC } from 'react';
2
-
3
- interface Props {
4
- text: string;
5
- icon: JSX.Element;
6
- onClick: () => void;
7
- }
8
-
9
- export const SidebarButton: FC<Props> = ({ text, icon, onClick }) => {
10
- return (
11
- <button
12
- className="flex w-full cursor-pointer select-none items-center gap-3 rounded-md py-3 px-3 text-[14px] leading-3 text-white transition-colors duration-200 hover:bg-gray-500/10"
13
- onClick={onClick}
14
- >
15
- <div>{icon}</div>
16
- <span>{text}</span>
17
- </button>
18
- );
19
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/Sidebar/components/OpenCloseButton.tsx DELETED
@@ -1,42 +0,0 @@
1
- import { IconArrowBarLeft, IconArrowBarRight } from '@tabler/icons-react';
2
-
3
- interface Props {
4
- onClick: any;
5
- side: 'left' | 'right';
6
- }
7
-
8
- export const CloseSidebarButton = ({ onClick, side }: Props) => {
9
- return (
10
- <>
11
- <button
12
- className={`fixed top-5 ${
13
- side === 'right' ? 'right-[270px]' : 'left-[270px]'
14
- } z-50 h-7 w-7 hover:text-gray-400 dark:text-white dark:hover:text-gray-300 sm:top-0.5 sm:${
15
- side === 'right' ? 'right-[270px]' : 'left-[270px]'
16
- } sm:h-8 sm:w-8 sm:text-neutral-700`}
17
- onClick={onClick}
18
- >
19
- {side === 'right' ? <IconArrowBarRight /> : <IconArrowBarLeft />}
20
- </button>
21
- <div
22
- onClick={onClick}
23
- className="absolute top-0 left-0 z-10 h-full w-full bg-black opacity-70 sm:hidden"
24
- ></div>
25
- </>
26
- );
27
- };
28
-
29
- export const OpenSidebarButton = ({ onClick, side }: Props) => {
30
- return (
31
- <button
32
- className={`fixed top-2.5 ${
33
- side === 'right' ? 'right-2' : 'left-2'
34
- } z-50 h-7 w-7 text-white hover:text-gray-400 dark:text-white dark:hover:text-gray-300 sm:top-0.5 sm:${
35
- side === 'right' ? 'right-2' : 'left-2'
36
- } sm:h-8 sm:w-8 sm:text-neutral-700`}
37
- onClick={onClick}
38
- >
39
- {side === 'right' ? <IconArrowBarLeft /> : <IconArrowBarRight />}
40
- </button>
41
- );
42
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/Sidebar/index.ts DELETED
@@ -1 +0,0 @@
1
- export { default } from './Sidebar';
 
 
next.config.js CHANGED
@@ -1,9 +1,5 @@
1
- const { i18n } = require('./next-i18next.config');
2
-
3
  /** @type {import('next').NextConfig} */
4
  const nextConfig = {
5
- i18n,
6
- output: "standalone",
7
  reactStrictMode: true,
8
 
9
  webpack(config, { isServer, dev }) {
 
 
 
1
  /** @type {import('next').NextConfig} */
2
  const nextConfig = {
 
 
3
  reactStrictMode: true,
4
 
5
  webpack(config, { isServer, dev }) {
package-lock.json CHANGED
@@ -1,18 +1,18 @@
1
  {
2
- "name": "ai-chatbot-starter",
3
  "version": "0.1.0",
4
  "lockfileVersion": 2,
5
  "requires": true,
6
  "packages": {
7
  "": {
8
- "name": "ai-chatbot-starter",
9
  "version": "0.1.0",
10
  "dependencies": {
11
  "@dqbd/tiktoken": "^1.0.2",
12
  "@tabler/icons-react": "^2.9.0",
13
  "eventsource-parser": "^0.1.0",
14
  "i18next": "^22.4.13",
15
- "next": "13.2.4",
16
  "next-i18next": "^13.2.2",
17
  "openai": "^3.2.1",
18
  "react": "18.2.0",
 
1
  {
2
+ "name": "chatbot-mini",
3
  "version": "0.1.0",
4
  "lockfileVersion": 2,
5
  "requires": true,
6
  "packages": {
7
  "": {
8
+ "name": "chatbot-mini",
9
  "version": "0.1.0",
10
  "dependencies": {
11
  "@dqbd/tiktoken": "^1.0.2",
12
  "@tabler/icons-react": "^2.9.0",
13
  "eventsource-parser": "^0.1.0",
14
  "i18next": "^22.4.13",
15
+ "next": "^13.2.4",
16
  "next-i18next": "^13.2.2",
17
  "openai": "^3.2.1",
18
  "react": "18.2.0",