push (#414)
This commit is contained in:
@@ -2,11 +2,8 @@ import { Conversation } from '@/types/chat';
|
||||
import { KeyValuePair } from '@/types/data';
|
||||
import { SupportedExportFormats } from '@/types/export';
|
||||
import { Folder } from '@/types/folder';
|
||||
import {
|
||||
IconFolderPlus,
|
||||
IconMessagesOff,
|
||||
IconPlus,
|
||||
} from '@tabler/icons-react';
|
||||
import { PluginKey } from '@/types/plugin';
|
||||
import { IconFolderPlus, IconMessagesOff, IconPlus } from '@tabler/icons-react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { ChatFolders } from '../Folders/Chat/ChatFolders';
|
||||
@@ -20,6 +17,7 @@ interface Props {
|
||||
lightMode: 'light' | 'dark';
|
||||
selectedConversation: Conversation;
|
||||
apiKey: string;
|
||||
pluginKeys: PluginKey[];
|
||||
folders: Folder[];
|
||||
onCreateFolder: (name: string) => void;
|
||||
onDeleteFolder: (folderId: string) => void;
|
||||
@@ -36,6 +34,8 @@ interface Props {
|
||||
onClearConversations: () => void;
|
||||
onExportConversations: () => void;
|
||||
onImportConversations: (data: SupportedExportFormats) => void;
|
||||
onPluginKeyChange: (pluginKey: PluginKey) => void;
|
||||
onClearPluginKey: (pluginKey: PluginKey) => void;
|
||||
}
|
||||
|
||||
export const Chatbar: FC<Props> = ({
|
||||
@@ -44,6 +44,7 @@ export const Chatbar: FC<Props> = ({
|
||||
lightMode,
|
||||
selectedConversation,
|
||||
apiKey,
|
||||
pluginKeys,
|
||||
folders,
|
||||
onCreateFolder,
|
||||
onDeleteFolder,
|
||||
@@ -57,6 +58,8 @@ export const Chatbar: FC<Props> = ({
|
||||
onClearConversations,
|
||||
onExportConversations,
|
||||
onImportConversations,
|
||||
onPluginKeyChange,
|
||||
onClearPluginKey,
|
||||
}) => {
|
||||
const { t } = useTranslation('sidebar');
|
||||
const [searchTerm, setSearchTerm] = useState<string>('');
|
||||
@@ -185,7 +188,7 @@ export const Chatbar: FC<Props> = ({
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col gap-3 items-center text-sm leading-normal mt-8 text-white opacity-50">
|
||||
<div className="mt-8 flex flex-col items-center gap-3 text-sm leading-normal text-white opacity-50">
|
||||
<IconMessagesOff />
|
||||
{t('No conversations.')}
|
||||
</div>
|
||||
@@ -195,12 +198,15 @@ export const Chatbar: FC<Props> = ({
|
||||
<ChatbarSettings
|
||||
lightMode={lightMode}
|
||||
apiKey={apiKey}
|
||||
pluginKeys={pluginKeys}
|
||||
conversationsCount={conversations.length}
|
||||
onToggleLightMode={onToggleLightMode}
|
||||
onApiKeyChange={onApiKeyChange}
|
||||
onClearConversations={onClearConversations}
|
||||
onExportConversations={onExportConversations}
|
||||
onImportConversations={onImportConversations}
|
||||
onPluginKeyChange={onPluginKeyChange}
|
||||
onClearPluginKey={onClearPluginKey}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { SupportedExportFormats } from '@/types/export';
|
||||
import { PluginKey } from '@/types/plugin';
|
||||
import { IconFileExport, IconMoon, IconSun } from '@tabler/icons-react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { FC } from 'react';
|
||||
@@ -6,29 +7,37 @@ import { Import } from '../Settings/Import';
|
||||
import { Key } from '../Settings/Key';
|
||||
import { SidebarButton } from '../Sidebar/SidebarButton';
|
||||
import { ClearConversations } from './ClearConversations';
|
||||
import { PluginKeys } from './PluginKeys';
|
||||
|
||||
interface Props {
|
||||
lightMode: 'light' | 'dark';
|
||||
apiKey: string;
|
||||
pluginKeys: PluginKey[];
|
||||
conversationsCount: number;
|
||||
onToggleLightMode: (mode: 'light' | 'dark') => void;
|
||||
onApiKeyChange: (apiKey: string) => void;
|
||||
onClearConversations: () => void;
|
||||
onExportConversations: () => void;
|
||||
onImportConversations: (data: SupportedExportFormats) => void;
|
||||
onPluginKeyChange: (pluginKey: PluginKey) => void;
|
||||
onClearPluginKey: (pluginKey: PluginKey) => void;
|
||||
}
|
||||
|
||||
export const ChatbarSettings: FC<Props> = ({
|
||||
lightMode,
|
||||
apiKey,
|
||||
pluginKeys,
|
||||
conversationsCount,
|
||||
onToggleLightMode,
|
||||
onApiKeyChange,
|
||||
onClearConversations,
|
||||
onExportConversations,
|
||||
onImportConversations,
|
||||
onPluginKeyChange,
|
||||
onClearPluginKey,
|
||||
}) => {
|
||||
const { t } = useTranslation('sidebar');
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center space-y-1 border-t border-white/20 pt-1 text-sm">
|
||||
{conversationsCount > 0 ? (
|
||||
@@ -54,6 +63,12 @@ export const ChatbarSettings: FC<Props> = ({
|
||||
/>
|
||||
|
||||
<Key apiKey={apiKey} onApiKeyChange={onApiKeyChange} />
|
||||
|
||||
<PluginKeys
|
||||
pluginKeys={pluginKeys}
|
||||
onPluginKeyChange={onPluginKeyChange}
|
||||
onClearPluginKey={onClearPluginKey}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,232 @@
|
||||
import { PluginID, PluginKey } from '@/types/plugin';
|
||||
import { IconKey } from '@tabler/icons-react';
|
||||
import { FC, KeyboardEvent, useEffect, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { SidebarButton } from '../Sidebar/SidebarButton';
|
||||
|
||||
interface Props {
|
||||
pluginKeys: PluginKey[];
|
||||
onPluginKeyChange: (pluginKey: PluginKey) => void;
|
||||
onClearPluginKey: (pluginKey: PluginKey) => void;
|
||||
}
|
||||
|
||||
export const PluginKeys: FC<Props> = ({
|
||||
pluginKeys,
|
||||
onPluginKeyChange,
|
||||
onClearPluginKey,
|
||||
}) => {
|
||||
const { t } = useTranslation('sidebar');
|
||||
|
||||
const [isChanging, setIsChanging] = useState(false);
|
||||
|
||||
const modalRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const handleEnter = (e: KeyboardEvent<HTMLDivElement>) => {
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
setIsChanging(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const handleMouseDown = (e: MouseEvent) => {
|
||||
if (modalRef.current && !modalRef.current.contains(e.target as Node)) {
|
||||
window.addEventListener('mouseup', handleMouseUp);
|
||||
}
|
||||
};
|
||||
|
||||
const handleMouseUp = (e: MouseEvent) => {
|
||||
window.removeEventListener('mouseup', handleMouseUp);
|
||||
setIsChanging(false);
|
||||
};
|
||||
|
||||
window.addEventListener('mousedown', handleMouseDown);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('mousedown', handleMouseDown);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<SidebarButton
|
||||
text={t('Plugin Keys')}
|
||||
icon={<IconKey size={18} />}
|
||||
onClick={() => setIsChanging(true)}
|
||||
/>
|
||||
|
||||
{isChanging && (
|
||||
<div
|
||||
className="z-100 fixed inset-0 flex items-center justify-center bg-black bg-opacity-50"
|
||||
onKeyDown={handleEnter}
|
||||
>
|
||||
<div className="fixed inset-0 z-10 overflow-y-auto">
|
||||
<div className="flex min-h-screen items-center justify-center px-4 pt-4 pb-20 text-center sm:block sm:p-0">
|
||||
<div
|
||||
className="hidden sm:inline-block sm:h-screen sm:align-middle"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
|
||||
<div
|
||||
ref={modalRef}
|
||||
className="dark:border-netural-400 inline-block max-h-[400px] transform overflow-hidden 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"
|
||||
role="dialog"
|
||||
>
|
||||
<div className="mb-10 text-4xl">Plugin Keys</div>
|
||||
|
||||
<div className="mt-6 rounded border p-4">
|
||||
<div className="text-xl font-bold">Google Search Plugin</div>
|
||||
<div className="mt-4 italic">
|
||||
Please enter your Google API Key and Google CSE ID to enable
|
||||
the Google Search Plugin.
|
||||
</div>
|
||||
|
||||
<div className="mt-6 text-sm font-bold text-black dark:text-neutral-200">
|
||||
Google API Key
|
||||
</div>
|
||||
<input
|
||||
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"
|
||||
type="password"
|
||||
value={
|
||||
pluginKeys
|
||||
.find((p) => p.pluginId === PluginID.GOOGLE_SEARCH)
|
||||
?.requiredKeys.find((k) => k.key === 'GOOGLE_API_KEY')
|
||||
?.value
|
||||
}
|
||||
onChange={(e) => {
|
||||
const pluginKey = pluginKeys.find(
|
||||
(p) => p.pluginId === PluginID.GOOGLE_SEARCH,
|
||||
);
|
||||
|
||||
if (pluginKey) {
|
||||
const requiredKey = pluginKey.requiredKeys.find(
|
||||
(k) => k.key === 'GOOGLE_API_KEY',
|
||||
);
|
||||
|
||||
if (requiredKey) {
|
||||
const updatedPluginKey = {
|
||||
...pluginKey,
|
||||
requiredKeys: pluginKey.requiredKeys.map((k) => {
|
||||
if (k.key === 'GOOGLE_API_KEY') {
|
||||
return {
|
||||
...k,
|
||||
value: e.target.value,
|
||||
};
|
||||
}
|
||||
|
||||
return k;
|
||||
}),
|
||||
};
|
||||
|
||||
onPluginKeyChange(updatedPluginKey);
|
||||
}
|
||||
} else {
|
||||
const newPluginKey: PluginKey = {
|
||||
pluginId: PluginID.GOOGLE_SEARCH,
|
||||
requiredKeys: [
|
||||
{
|
||||
key: 'GOOGLE_API_KEY',
|
||||
value: e.target.value,
|
||||
},
|
||||
{
|
||||
key: 'GOOGLE_CSE_ID',
|
||||
value: '',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
onPluginKeyChange(newPluginKey);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="mt-6 text-sm font-bold text-black dark:text-neutral-200">
|
||||
Google CSE ID
|
||||
</div>
|
||||
<input
|
||||
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"
|
||||
type="password"
|
||||
value={
|
||||
pluginKeys
|
||||
.find((p) => p.pluginId === PluginID.GOOGLE_SEARCH)
|
||||
?.requiredKeys.find((k) => k.key === 'GOOGLE_CSE_ID')
|
||||
?.value
|
||||
}
|
||||
onChange={(e) => {
|
||||
const pluginKey = pluginKeys.find(
|
||||
(p) => p.pluginId === PluginID.GOOGLE_SEARCH,
|
||||
);
|
||||
|
||||
if (pluginKey) {
|
||||
const requiredKey = pluginKey.requiredKeys.find(
|
||||
(k) => k.key === 'GOOGLE_CSE_ID',
|
||||
);
|
||||
|
||||
if (requiredKey) {
|
||||
const updatedPluginKey = {
|
||||
...pluginKey,
|
||||
requiredKeys: pluginKey.requiredKeys.map((k) => {
|
||||
if (k.key === 'GOOGLE_CSE_ID') {
|
||||
return {
|
||||
...k,
|
||||
value: e.target.value,
|
||||
};
|
||||
}
|
||||
|
||||
return k;
|
||||
}),
|
||||
};
|
||||
|
||||
onPluginKeyChange(updatedPluginKey);
|
||||
}
|
||||
} else {
|
||||
const newPluginKey: PluginKey = {
|
||||
pluginId: PluginID.GOOGLE_SEARCH,
|
||||
requiredKeys: [
|
||||
{
|
||||
key: 'GOOGLE_API_KEY',
|
||||
value: '',
|
||||
},
|
||||
{
|
||||
key: 'GOOGLE_CSE_ID',
|
||||
value: e.target.value,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
onPluginKeyChange(newPluginKey);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
<button
|
||||
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"
|
||||
onClick={() => {
|
||||
const pluginKey = pluginKeys.find(
|
||||
(p) => p.pluginId === PluginID.GOOGLE_SEARCH,
|
||||
);
|
||||
|
||||
if (pluginKey) {
|
||||
onClearPluginKey(pluginKey);
|
||||
}
|
||||
}}
|
||||
>
|
||||
Clear Google Search Plugin Keys
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
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"
|
||||
onClick={() => setIsChanging(false)}
|
||||
>
|
||||
{t('Save')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user