Open kirin-ri opened 8 months ago
import { Prompt } from '@/types/prompt';
export interface PromptbarInitialState {
searchTerm2: string;
filteredPrompts: Prompt[];
}
export const initialState2: PromptbarInitialState = {
searchTerm2: '',
filteredPrompts: [],
};
const promptBarContextValue = useCreateReducer<PromptbarInitialState>({
initialState2,
});
import { useMemo, useReducer } from 'react';
// Extracts property names from initial state of reducer to allow typesafe dispatch objects
export type FieldNames<T> = {
[K in keyof T]: T[K] extends string ? K : K;
}[keyof T];
// Returns the Action Type for the dispatch object to be used for typing in things like context
export type ActionType<T> =
| { type: 'reset' }
| { type?: 'change'; field: FieldNames<T>; value: any };
// Returns a typed dispatch and state
export const useCreateReducer = <T>({ initialState }: { initialState: T }) => {
type Action =
| { type: 'reset' }
| { type?: 'change'; field: FieldNames<T>; value: any };
const reducer = (state: T, action: Action) => {
if (!action.type) return { ...state, [action.field]: action.value };
if (action.type === 'reset') return initialState;
throw new Error();
};
const [state, dispatch] = useReducer(reducer, initialState);
return useMemo(() => ({ state, dispatch }), [state, dispatch]);
`};```
import { useContext, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useCreateReducer } from '@/hooks/useCreateReducer';
import { savePrompts } from '@/utils/app/prompts';
import { OpenAIModels } from '@/types/openai';
import { Prompt } from '@/types/prompt';
import HomeContext from '@/pages/api/home/home.context';
import { PromptFolders } from './components/PromptFolders';
import { PromptbarSettings } from './components/PromptbarSettings';
import { Prompts } from './components/Prompts';
import Sidebar from '../Sidebar';
import PromptbarContext from './PromptBar.context';
import { PromptbarInitialState, initialState2 } from './Promptbar.state';
import { v4 as uuidv4 } from 'uuid';
const Promptbar = () => {
const { t } = useTranslation('promptbar');
const promptBarContextValue = useCreateReducer<PromptbarInitialState>({
initialState2,
});
const {
state: { prompts, defaultModelId, showPromptbar },
dispatch: homeDispatch,
handleCreateFolder,
} = useContext(HomeContext);
const {
state: { searchTerm2, filteredPrompts },
dispatch: promptDispatch,
} = promptBarContextValue;
const handleTogglePromptbar = () => {
homeDispatch({ field: 'showPromptbar', value: !showPromptbar });
localStorage.setItem('showPromptbar', JSON.stringify(!showPromptbar));
};
const handleCreatePrompt = () => {
if (defaultModelId) {
const newPrompt: Prompt = {
id: uuidv4(),
name: `Prompt ${prompts.length + 1}`,
description: '',
content: '',
model: OpenAIModels[defaultModelId],
folderId: null,
};
const updatedPrompts = [...prompts, newPrompt];
homeDispatch({ field: 'prompts', value: updatedPrompts });
savePrompts(updatedPrompts);
}
};
const handleDeletePrompt = (prompt: Prompt) => {
const updatedPrompts = prompts.filter((p) => p.id !== prompt.id);
homeDispatch({ field: 'prompts', value: updatedPrompts });
savePrompts(updatedPrompts);
};
const handleUpdatePrompt = (prompt: Prompt) => {
const updatedPrompts = prompts.map((p) => {
if (p.id === prompt.id) {
return prompt;
}
return p;
});
homeDispatch({ field: 'prompts', value: updatedPrompts });
savePrompts(updatedPrompts);
};
const handleDrop = (e: any) => {
if (e.dataTransfer) {
const prompt = JSON.parse(e.dataTransfer.getData('prompt'));
const updatedPrompt = {
...prompt,
folderId: e.target.dataset.folderId,
};
handleUpdatePrompt(updatedPrompt);
e.target.style.background = 'none';
}
};
useEffect(() => {
if (searchTerm2) {
promptDispatch({
field: 'filteredPrompts',
value: prompts.filter((prompt) => {
const searchable =
prompt.name.toLowerCase() +
' ' +
prompt.description.toLowerCase() +
' ' +
prompt.content.toLowerCase();
return searchable.includes(searchTerm2.toLowerCase());
}),
});
} else {
promptDispatch({ field: 'filteredPrompts', value: prompts });
}
}, [searchTerm2, prompts]);
return (
<PromptbarContext.Provider
value={{
...promptBarContextValue,
handleCreatePrompt,
handleDeletePrompt,
handleUpdatePrompt,
}}
>
<Sidebar<Prompt>
side={'right'}
isOpen={showPromptbar}
addItemButtonTitle={t('New prompt')}
itemComponent={
<Prompts
prompts={filteredPrompts.filter((prompt) => !prompt.folderId)}
/>
}
folderComponent={<PromptFolders />}
items={filteredPrompts}
searchTerm={searchTerm2}
handleSearchTerm={(searchTerm: string) =>
promptDispatch({ field: 'searchTerm2', value: searchTerm })
}
toggleOpen={handleTogglePromptbar}
handleCreateItem={handleCreatePrompt}
handleCreateFolder={() => handleCreateFolder(t('New folder'), 'prompt')}
handleDrop={handleDrop}
/>
</PromptbarContext.Provider>
);
};
export default Promptbar;
``
import { IconFolderPlus, IconMistOff, IconPlus } from '@tabler/icons-react';
import { ReactNode } from 'react';
import { useTranslation } from 'react-i18next';
import {
CloseSidebarButton,
OpenSidebarButton,
} from './components/OpenCloseButton';
import Search from '../Search';
interface Props<T> {
isOpen: boolean;
addItemButtonTitle: string;
addItemButtonTitle2: string;
side: 'left' | 'right';
items: T[];
items2: T[];
itemComponent: ReactNode;
itemComponent2: ReactNode;
folderComponent: ReactNode;
folderComponent2: ReactNode;
footerComponent?: ReactNode;
searchTerm: string;
searchTerm2: string;
handleSearchTerm: (searchTerm: string) => void;
handleSearchTerm2: (searchTerm: string) => void;
toggleOpen: () => void;
handleCreateItem: () => void;
handleCreateItem2: () => void;
handleCreateFolder: () => void;
handleCreateFolder2: () => void;
handleDrop: (e: any) => void;
handleDrop2: (e: any) => void;
}
const Sidebar = <T,>({
isOpen,
addItemButtonTitle,
addItemButtonTitle2,
side,
items,
items2,
itemComponent,
itemComponent2,
folderComponent,
folderComponent2,
footerComponent,
searchTerm,
searchTerm2,
handleSearchTerm,
handleSearchTerm2,
toggleOpen,
handleCreateItem,
handleCreateItem2,
handleCreateFolder,
handleCreateFolder2,
handleDrop,
handleDrop2,
}: Props<T>) => {
const { t } = useTranslation('promptbar');
const allowDrop = (e: any) => {
e.preventDefault();
};
const highlightDrop = (e: any) => {
e.target.style.background = '#343541';
};
const removeHighlight = (e: any) => {
e.target.style.background = 'none';
};
return isOpen ? (
<div>
<div
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`}
>
<div className="flex items-center">
<button
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"
onClick={() => {
handleCreateItem();
handleSearchTerm('');
}}
>
<IconPlus size={16} />
{addItemButtonTitle}
</button>
<button
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"
onClick={handleCreateFolder}
>
<IconFolderPlus size={16} />
</button>
</div>
<Search
placeholder={t('Search...') || ''}
searchTerm={searchTerm}
onSearch={handleSearchTerm}
/>
<div className="flex-grow overflow-auto">
{items?.length > 0 && (
<div className="flex border-b border-white/20 pb-2">
{folderComponent}
</div>
)}
{items?.length > 0 ? (
<div
className="pt-2"
onDrop={handleDrop}
onDragOver={allowDrop}
onDragEnter={highlightDrop}
onDragLeave={removeHighlight}
>
{itemComponent}
</div>
) : (
<div className="mt-8 select-none text-center text-white opacity-50">
<IconMistOff className="mx-auto mb-3" />
<span className="text-[14px] leading-normal">
{t('No data.')}
</span>
</div>
)}
</div>
<div className="flex items-center">
<button
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"
onClick={() => {
handleCreateItem2();
handleSearchTerm2('');
}}
>
<IconPlus size={16} />
{addItemButtonTitle2}
</button>
<button
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"
onClick={handleCreateFolder2}
>
<IconFolderPlus size={16} />
</button>
</div>
<Search
placeholder={t('Search...') || ''}
searchTerm={searchTerm2}
onSearch={handleSearchTerm2}
/>
<div className="flex-grow overflow-auto">
{items2?.length > 0 && (
<div className="flex border-b border-white/20 pb-2">
{folderComponent2}
</div>
)}
{items2?.length > 0 ? (
<div
className="pt-2"
onDrop={handleDrop2}
onDragOver={allowDrop}
onDragEnter={highlightDrop}
onDragLeave={removeHighlight}
>
{itemComponent2}
</div>
) : (
<div className="mt-8 select-none text-center text-white opacity-50">
<IconMistOff className="mx-auto mb-3" />
<span className="text-[14px] leading-normal">
{t('No data.')}
</span>
</div>
)}
</div>
{footerComponent}
</div>
<CloseSidebarButton onClick={toggleOpen} side={side} />
</div>
) : (
<OpenSidebarButton onClick={toggleOpen} side={side} />
);
};
export default Sidebar;
import { IconFolderPlus, IconMistOff, IconPlus } from '@tabler/icons-react';
import { ReactNode } from 'react';
import { useTranslation } from 'react-i18next';
import {
CloseSidebarButton,
OpenSidebarButton,
} from './components/OpenCloseButton';
import Search from '../Search';
interface Props<T> {
isOpen: boolean;
addItemButtonTitle: string;
addItemButtonTitle2: string;
side: 'left' | 'right';
items: T[];
items2: T[];
itemComponent: ReactNode;
itemComponent2: ReactNode;
folderComponent: ReactNode;
folderComponent2: ReactNode;
footerComponent?: ReactNode;
searchTerm: string;
searchTerm2: string;
handleSearchTerm: (searchTerm: string) => void;
handleSearchTerm2: (searchTerm: string) => void;
toggleOpen: () => void;
handleCreateItem: () => void;
handleCreateItem2: () => void;
handleCreateFolder: () => void;
handleCreateFolder2: () => void;
handleDrop: (e: any) => void;
handleDrop2: (e: any) => void;
}
const Sidebar = <T,>({
isOpen,
addItemButtonTitle,
addItemButtonTitle2,
side,
items,
items2,
itemComponent,
itemComponent2,
folderComponent,
folderComponent2,
footerComponent,
searchTerm,
searchTerm2,
handleSearchTerm,
handleSearchTerm2,
toggleOpen,
handleCreateItem,
handleCreateItem2,
handleCreateFolder,
handleCreateFolder2,
handleDrop,
handleDrop2,
}: Props<T>) => {
const { t } = useTranslation('promptbar');
const allowDrop = (e: any) => {
e.preventDefault();
};
const highlightDrop = (e: any) => {
e.target.style.background = '#343541';
};
const removeHighlight = (e: any) => {
e.target.style.background = 'none';
};
return isOpen ? (
<div>
<div
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`}
>
<div className="flex items-center">
<button
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"
onClick={() => {
handleCreateItem();
handleSearchTerm('');
}}
>
<IconPlus size={16} />
{addItemButtonTitle}
</button>
<button
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"
onClick={handleCreateFolder}
>
<IconFolderPlus size={16} />
</button>
</div>
<Search
placeholder={t('Search...') || ''}
searchTerm={searchTerm}
onSearch={handleSearchTerm}
/>
<div className="flex-grow overflow-auto">
{items?.length > 0 && (
<div className="flex border-b border-white/20 pb-2">
{folderComponent}
</div>
)}
{items?.length > 0 ? (
<div
className="pt-2"
onDrop={handleDrop}
onDragOver={allowDrop}
onDragEnter={highlightDrop}
onDragLeave={removeHighlight}
>
{itemComponent}
</div>
) : (
<div className="mt-8 select-none text-center text-white opacity-50">
<IconMistOff className="mx-auto mb-3" />
<span className="text-[14px] leading-normal">
{t('No data.')}
</span>
</div>
)}
</div>
<div className="flex items-center">
<button
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"
onClick={() => {
handleCreateItem2();
handleSearchTerm2('');
}}
>
<IconPlus size={16} />
{addItemButtonTitle2}
</button>
<button
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"
onClick={handleCreateFolder2}
>
<IconFolderPlus size={16} />
</button>
</div>
<Search
placeholder={t('Search...') || ''}
searchTerm={searchTerm2}
onSearch={handleSearchTerm2}
/>
<div className="flex-grow overflow-auto">
{items2?.length > 0 && (
<div className="flex border-b border-white/20 pb-2">
{folderComponent2}
</div>
)}
{items2?.length > 0 ? (
<div
className="pt-2"
onDrop={handleDrop2}
onDragOver={allowDrop}
onDragEnter={highlightDrop}
onDragLeave={removeHighlight}
>
{itemComponent2}
</div>
) : (
<div className="mt-8 select-none text-center text-white opacity-50">
<IconMistOff className="mx-auto mb-3" />
<span className="text-[14px] leading-normal">
{t('No data.')}
</span>
</div>
)}
</div>
{footerComponent}
</div>
<CloseSidebarButton onClick={toggleOpen} side={side} />
</div>
) : (
<OpenSidebarButton onClick={toggleOpen} side={side} />
);
};
export default Sidebar;
```import { IconFolderPlus, IconMistOff, IconPlus } from '@tabler/icons-react';
import { ReactNode } from 'react';
import { useTranslation } from 'react-i18next';
import {
CloseSidebarButton,
OpenSidebarButton,
} from './components/OpenCloseButton';
import Search from '../Search';
interface Props<T> {
isOpen: boolean;
addItemButtonTitle: string;
addItemButtonTitle2: string;
side: 'left' | 'right';
items: T[];
items2: T[];
itemComponent: ReactNode;
itemComponent2: ReactNode;
folderComponent: ReactNode;
folderComponent2: ReactNode;
footerComponent?: ReactNode;
searchTerm: string;
searchTerm2: string;
handleSearchTerm: (searchTerm: string) => void;
handleSearchTerm2: (searchTerm: string) => void;
toggleOpen: () => void;
handleCreateItem: () => void;
handleCreateItem2: () => void;
handleCreateFolder: () => void;
handleCreateFolder2: () => void;
handleDrop: (e: any) => void;
handleDrop2: (e: any) => void;
}
const Sidebar = <T,>({
isOpen,
addItemButtonTitle,
addItemButtonTitle2,
side,
items,
items2,
itemComponent,
itemComponent2,
folderComponent,
folderComponent2,
footerComponent,
searchTerm,
searchTerm2,
handleSearchTerm,
handleSearchTerm2,
toggleOpen,
handleCreateItem,
handleCreateItem2,
handleCreateFolder,
handleCreateFolder2,
handleDrop,
handleDrop2,
}: Props<T>) => {
const { t } = useTranslation('promptbar');
const allowDrop = (e: any) => {
e.preventDefault();
};
const highlightDrop = (e: any) => {
e.target.style.background = '#343541';
};
const removeHighlight = (e: any) => {
e.target.style.background = 'none';
};
return isOpen ? (
<div>
<div
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`}
>
<div className="flex items-center">
<button
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"
onClick={() => {
handleCreateItem();
handleSearchTerm('');
}}
>
<IconPlus size={16} />
{addItemButtonTitle}
</button>
<button
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"
onClick={handleCreateFolder}
>
<IconFolderPlus size={16} />
</button>
</div>
<Search
placeholder={t('Search...') || ''}
searchTerm={searchTerm}
onSearch={handleSearchTerm}
/>
<div className="flex-grow overflow-auto">
{items?.length > 0 && (
<div className="flex border-b border-white/20 pb-2">
{folderComponent}
</div>
)}
{items?.length > 0 ? (
<div
className="pt-2"
onDrop={handleDrop}
onDragOver={allowDrop}
onDragEnter={highlightDrop}
onDragLeave={removeHighlight}
>
{itemComponent}
</div>
) : (
<div className="mt-8 select-none text-center text-white opacity-50">
<IconMistOff className="mx-auto mb-3" />
<span className="text-[14px] leading-normal">
{t('No data.')}
</span>
</div>
)}
</div>
<div className="flex items-center">
<button
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"
onClick={() => {
handleCreateItem2();
handleSearchTerm2('');
}}
>
<IconPlus size={16} />
{addItemButtonTitle2}
</button>
<button
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"
onClick={handleCreateFolder2}
>
<IconFolderPlus size={16} />
</button>
</div>
<Search
placeholder={t('Search...') || ''}
searchTerm={searchTerm2}
onSearch={handleSearchTerm2}
/>
<div className="flex-grow overflow-auto">
{items2?.length > 0 && (
<div className="flex border-b border-white/20 pb-2">
{folderComponent2}
</div>
)}
{items2?.length > 0 ? (
<div
className="pt-2"
onDrop={handleDrop2}
onDragOver={allowDrop}
onDragEnter={highlightDrop}
onDragLeave={removeHighlight}
>
{itemComponent2}
</div>
) : (
<div className="mt-8 select-none text-center text-white opacity-50">
<IconMistOff className="mx-auto mb-3" />
<span className="text-[14px] leading-normal">
{t('No data.')}
</span>
</div>
)}
</div>
{footerComponent}
</div>
<CloseSidebarButton onClick={toggleOpen} side={side} />
</div>
) : (
<OpenSidebarButton onClick={toggleOpen} side={side} />
);
};
export default Sidebar;
Unhandled Runtime Error TypeError: Cannot destructure property 'dispatch' of '(0 , react__WEBPACK_IMPORTED_MODULE_1__.useContext)(...)' as it is undefined.
import { IconFileExport, IconSettings } from '@tabler/icons-react';
import { useContext, useState } from 'react';
import { useTranslation } from 'next-i18next';
import HomeContext from '@/pages/api/home/home.context';
import { SettingDialog } from '@/components/Settings/SettingDialog';
import { Import } from '../../Settings/Import';
import { Key } from '../../Settings/Key';
import { SidebarButton } from '../../Sidebar/SidebarButton';
import ChatbarContext from '../Chatbar.context';
import { ClearConversations } from './ClearConversations';
import { PluginKeys } from './PluginKeys';
import { KBPrompts } from './KBPrompts';
export const ChatbarSettings = () => {
const { t } = useTranslation('sidebar');
const [isSettingDialogOpen, setIsSettingDialog] = useState<boolean>(false);
const {
state: {
apiKey,
lightMode,
serverSideApiKeyIsSet,
serverSidePluginKeysSet,
conversations,
},
dispatch: homeDispatch,
} = useContext(HomeContext);
const {
handleClearConversations,
handleImportConversations,
handleExportData,
handleApiKeyChange,
} = useContext(ChatbarContext);
return (
<div className="flex flex-col items-center space-y-1 border-t border-white/20 pt-1 text-sm">
{conversations.length > 0 ? (
<ClearConversations onClearConversations={handleClearConversations} />
) : null}
<Import onImport={handleImportConversations} />
<SidebarButton
text={t('Export data')}
icon={<IconFileExport size={18} />}
onClick={() => handleExportData()}
/>
<SidebarButton
text={t('Settings')}
icon={<IconSettings size={18} />}
onClick={() => setIsSettingDialog(true)}
/>
{!serverSideApiKeyIsSet ? (
<Key apiKey={apiKey} onApiKeyChange={handleApiKeyChange} />
) : null}
{/* {!serverSidePluginKeysSet ? <PluginKeys /> : null} */}
<KBPrompts />
<SettingDialog
open={isSettingDialogOpen}
onClose={() => {
setIsSettingDialog(false);
}}
/>
</div>
);
};
import { FC, useContext, useEffect, useReducer, useRef } from 'react';
import { useTranslation } from 'next-i18next';
import { useCreateReducer } from '@/hooks/useCreateReducer';
import { getSettings, saveSettings } from '@/utils/app/settings';
import { Settings } from '@/types/settings';
import HomeContext from '@/pages/api/home/home.context';
interface Props {
open: boolean;
onClose: () => void;
}
export const SettingDialog: FC<Props> = ({ open, onClose }) => {
const { t } = useTranslation('settings');
const settings: Settings = getSettings();
const { state, dispatch } = useCreateReducer<Settings>({
initialState: settings,
});
const { dispatch: homeDispatch } = useContext(HomeContext);
const modalRef = useRef<HTMLDivElement>(null);
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);
onClose();
};
window.addEventListener('mousedown', handleMouseDown);
return () => {
window.removeEventListener('mousedown', handleMouseDown);
};
}, [onClose]);
const handleSave = () => {
homeDispatch({ field: 'lightMode', value: state.theme });
saveSettings(state);
};
// Render nothing if the dialog is not open.
if (!open) {
return <></>;
}
// Render the dialog.
return (
<div className="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50 z-50">
<div className="fixed inset-0 z-10 overflow-hidden">
<div className="flex items-center justify-center min-h-screen 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-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"
role="dialog"
>
<div className="text-lg pb-4 font-bold text-black dark:text-neutral-200">
{t('Settings')}
</div>
<div className="text-sm font-bold mb-2 text-black dark:text-neutral-200">
{t('Theme')}
</div>
<select
className="w-full cursor-pointer bg-transparent p-2 text-neutral-700 dark:text-neutral-200"
value={state.theme}
onChange={(event) =>
dispatch({ field: 'theme', value: event.target.value })
}
>
<option value="dark">{t('Dark mode')}</option>
<option value="light">{t('Light mode')}</option>
</select>
<button
type="button"
className="w-full px-4 py-2 mt-6 border rounded-lg shadow border-neutral-500 text-neutral-900 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={() => {
handleSave();
onClose();
}}
>
{t('Save')}
</button>
</div>
</div>
</div>
</div>
);
};
Type '(data: SupportedExportFormats) => void' is not assignable to type '() => void'. Target signature provides too few arguments. Expected 1 or more, but got 0.ts(2322) SettingDialog.tsx(16, 3): The expected type comes from property 'onImportConversations' which is declared here on type 'IntrinsicAttributes & Props'
Type '(data: SupportedExportFormats) => void' is not assignable to type 'MouseEventHandler
<div className="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50 z-50">
<div className="fixed inset-0 z-10 overflow-hidden">
<div className="flex items-center justify-center min-h-screen 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-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"
role="dialog"
>
<div className="text-lg pb-4 font-bold text-black dark:text-neutral-200">
{t('Settings')}
</div>
<div className="text-sm font-bold mb-2 text-black dark:text-neutral-200">
{t('Theme')}
</div>
<select
className="w-full cursor-pointer bg-transparent p-2 text-neutral-700 dark:text-neutral-200"
value={state.theme}
onChange={(event) =>
dispatch({ field: 'theme', value: event.target.value })
}
>
<option value="dark">{t('Dark mode')}</option>
<option value="light">{t('Light mode')}</option>
</select>
<div>
{onImportConversations && (
<Import onImport={onImportConversations} />
)}
{onExportData && (
<SidebarButton
text={t('Export data')}
icon={<IconFileExport size={18} />}
onClick={() => onExportData()}
/>
)}
</div>
<button
type="button"
className="w-full px-4 py-2 mt-6 border rounded-lg shadow border-neutral-500 text-neutral-900 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={() => {
handleSave();
onClose();
}}
>
{t('Save')}
</button>
</div>
</div>
</div>
</div>
const { answer } = await response.json();
const updatedMessages: Message[] = [
...updatedConversation.messages,
{ role: 'assistant', content: answer },
];
import { IconClearAll, IconSettings } from '@tabler/icons-react';
import {
MutableRefObject,
memo,
useCallback,
useContext,
useEffect,
useRef,
useState,
} from 'react';
import toast from 'react-hot-toast';
import { useTranslation } from 'next-i18next';
import { getEndpoint } from '@/utils/app/api';
import {
saveConversation,
saveConversations,
updateConversation,
} from '@/utils/app/conversation';
import { throttle } from '@/utils/data/throttle';
import { ChatBody, Conversation, Message } from '@/types/chat';
import { Plugin } from '@/types/plugin';
import HomeContext from '@/pages/api/home/home.context';
import Spinner from '../Spinner';
import { ChatInput } from './ChatInput';
import { ChatLoader } from './ChatLoader';
import { ErrorMessageDiv } from './ErrorMessageDiv';
import { ModelSelect } from './ModelSelect';
import { SystemPrompt } from './SystemPrompt';
import { TemperatureSlider } from './Temperature';
import { MemoizedChatMessage } from './MemoizedChatMessage';
import { useLocalStorage } from '@/hooks/useLocalStorage';
import { KB_USER_PROMPT, KB_SYSTEM_PROMPT, AZURE_COGNITIVE_SEARCH_INDEX, KB_QUERY_SYSTEM_ROLE } from '@/utils/app/const';
interface Props {
stopConversationRef: MutableRefObject<boolean>;
}
function getLocalStorage<S>(key: string, defaultValue: S) : S {
const jsonValue = localStorage.getItem(key);
if (jsonValue !== null) return JSON.parse(jsonValue);
return defaultValue;
}
export const Chat = memo(({ stopConversationRef }: Props) => {
const { t } = useTranslation('chat');
const {
state: {
selectedConversation,
conversations,
models,
apiKey,
pluginKeys,
serverSideApiKeyIsSet,
messageIsStreaming,
modelError,
loading,
prompts,
},
handleUpdateConversation,
dispatch: homeDispatch,
} = useContext(HomeContext);
const [answer,setAnswer] = useState(null);
const [currentMessage, setCurrentMessage] = useState<Message>();
const [autoScrollEnabled, setAutoScrollEnabled] = useState<boolean>(true);
const [showSettings, setShowSettings] = useState<boolean>(false);
const [showScrollDownButton, setShowScrollDownButton] =
useState<boolean>(false);
const [plugin, setPlugin] = useState<Plugin | null>(null);
const messagesEndRef = useRef<HTMLDivElement>(null);
const chatContainerRef = useRef<HTMLDivElement>(null);
const textareaRef = useRef<HTMLTextAreaElement>(null);
const handlePluginChange = ( p: Plugin ) => {
setPlugin( p );
}
const handleSend = useCallback(
async (message: Message, deleteCount = 0, plugin: Plugin | null) => {
if (selectedConversation) {
let updatedConversation: Conversation;
if (deleteCount) {
const updatedMessages = [...selectedConversation.messages];
for (let i = 0; i < deleteCount; i++) {
updatedMessages.pop();
}
updatedConversation = {
...selectedConversation,
messages: [...updatedMessages, message],
};
} else {
updatedConversation = {
...selectedConversation,
messages: [...selectedConversation.messages, message],
};
}
homeDispatch({
field: 'selectedConversation',
value: updatedConversation,
});
homeDispatch({ field: 'loading', value: true });
homeDispatch({ field: 'messageIsStreaming', value: true });
const chatBody: ChatBody = {
model: updatedConversation.model,
messages: updatedConversation.messages,
key: apiKey,
prompt: updatedConversation.prompt,
temperature: updatedConversation.temperature,
};
const endpoint = getEndpoint(plugin);
let body;
if (!plugin) {
body = JSON.stringify(chatBody);
} else if (plugin.id === 'qdrant-search') {
const kbUserPrompt = getLocalStorage('kbUserPrompt', KB_USER_PROMPT );
const kbSystemPrompt = getLocalStorage('kbSystemPrompt', KB_SYSTEM_PROMPT);
const kbCogSearchIndex = getLocalStorage('kbCogSearchIndex', AZURE_COGNITIVE_SEARCH_INDEX.split(',')[0]);
const kbSearchSystemPrompt = getLocalStorage('kbSearchSystemPrompt', KB_QUERY_SYSTEM_ROLE);
const kbDebugMode = getLocalStorage('kbDebugMode', false);
const kbSearchQueryEnabled = getLocalStorage('kbSearchQueryEnabled', true);
body = JSON.stringify({
...chatBody,
kbSearchQueryEnabled: kbSearchQueryEnabled,
kbSearchSystemPrompt: kbSearchSystemPrompt,
kbUserPrompt: kbUserPrompt,
kbSystemPrompt: kbSystemPrompt,
kbCogSearchIndex: kbCogSearchIndex,
kbDebugMode: kbDebugMode
});
} else if (plugin.id === 'google-search') {
body = JSON.stringify({
...chatBody,
googleAPIKey: pluginKeys
.find((key) => key.pluginId === 'google-search')
?.requiredKeys.find((key) => key.key === 'GOOGLE_API_KEY')?.value,
googleCSEId: pluginKeys
.find((key) => key.pluginId === 'google-search')
?.requiredKeys.find((key) => key.key === 'GOOGLE_CSE_ID')?.value,
});
}
const controller = new AbortController();
const response = await fetch(endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
signal: controller.signal,
body,
});
if (!response.ok) {
homeDispatch({ field: 'loading', value: false });
homeDispatch({ field: 'messageIsStreaming', value: false });
// toast.error(response.statusText);
const { error } = await response.json();
alert( error );
return;
}
const data = response.body;
if (!data) {
homeDispatch({ field: 'loading', value: false });
homeDispatch({ field: 'messageIsStreaming', value: false });
return;
}
if (!plugin) {
if (updatedConversation.messages.length === 1) {
const { content } = message;
const customName =
content.length > 30 ? content.substring(0, 30) + '...' : content;
updatedConversation = {
...updatedConversation,
name: customName,
};
}
homeDispatch({ field: 'loading', value: false });
const reader = data.getReader();
const decoder = new TextDecoder();
let done = false;
let isFirst = true;
let text = '';
while (!done) {
if (stopConversationRef.current === true) {
controller.abort();
done = true;
break;
}
const { value, done: doneReading } = await reader.read();
done = doneReading;
const chunkValue = decoder.decode(value);
text += chunkValue;
if (isFirst) {
isFirst = false;
const updatedMessages: Message[] = [
...updatedConversation.messages,
{ role: 'assistant', content: chunkValue },
];
updatedConversation = {
...updatedConversation,
messages: updatedMessages,
};
homeDispatch({
field: 'selectedConversation',
value: updatedConversation,
});
} else {
const updatedMessages: Message[] =
updatedConversation.messages.map((message, index) => {
if (index === updatedConversation.messages.length - 1) {
return {
...message,
content: text,
};
}
return message;
});
updatedConversation = {
...updatedConversation,
messages: updatedMessages,
};
homeDispatch({
field: 'selectedConversation',
value: updatedConversation,
});
}
}
saveConversation(updatedConversation);
const updatedConversations: Conversation[] = conversations.map(
(conversation) => {
if (conversation.id === selectedConversation.id) {
return updatedConversation;
}
return conversation;
},
);
if (updatedConversations.length === 0) {
updatedConversations.push(updatedConversation);
}
homeDispatch({ field: 'conversations', value: updatedConversations });
saveConversations(updatedConversations);
homeDispatch({ field: 'messageIsStreaming', value: false });
} else {
const { answer } = await response.json();
setAnswer(answer);
const updatedMessages: Message[] = [
...updatedConversation.messages,
{ role: 'assistant', content: answer },
];
if (updatedConversation.messages.length === 1) {
const { content } = message;
const customName =
content.length > 30 ? content.substring(0, 30) + '...' : content;
updatedConversation = {
...updatedConversation,
name: customName,
};
}
updatedConversation = {
...updatedConversation,
messages: updatedMessages,
};
homeDispatch({
field: 'selectedConversation',
value: updatedConversation,
});
saveConversation(updatedConversation);
const updatedConversations: Conversation[] = conversations.map(
(conversation) => {
if (conversation.id === selectedConversation.id) {
return updatedConversation;
}
return conversation;
},
);
if (updatedConversations.length === 0) {
updatedConversations.push(updatedConversation);
}
homeDispatch({ field: 'conversations', value: updatedConversations });
saveConversations(updatedConversations);
homeDispatch({ field: 'loading', value: false });
homeDispatch({ field: 'messageIsStreaming', value: false });
}
}
},
[
apiKey,
conversations,
pluginKeys,
selectedConversation,
stopConversationRef,
],
);
const scrollToBottom = useCallback(() => {
if (autoScrollEnabled) {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
textareaRef.current?.focus();
}
}, [autoScrollEnabled]);
const handleScroll = () => {
if (chatContainerRef.current) {
const { scrollTop, scrollHeight, clientHeight } =
chatContainerRef.current;
const bottomTolerance = 30;
if (scrollTop + clientHeight < scrollHeight - bottomTolerance) {
setAutoScrollEnabled(false);
setShowScrollDownButton(true);
} else {
setAutoScrollEnabled(true);
setShowScrollDownButton(false);
}
}
};
const handleScrollDown = () => {
chatContainerRef.current?.scrollTo({
top: chatContainerRef.current.scrollHeight,
behavior: 'smooth',
});
};
const handleSettings = () => {
setShowSettings(!showSettings);
};
const onClearAll = () => {
if (
confirm(t<string>('Are you sure you want to clear all messages?')) &&
selectedConversation
) {
handleUpdateConversation(selectedConversation, {
key: 'messages',
value: [],
});
}
};
const scrollDown = () => {
if (autoScrollEnabled) {
messagesEndRef.current?.scrollIntoView(true);
}
};
const throttledScrollDown = throttle(scrollDown, 250);
// useEffect(() => {
// console.log('currentMessage', currentMessage);
// if (currentMessage) {
// handleSend(currentMessage);
// homeDispatch({ field: 'currentMessage', value: undefined });
// }
// }, [currentMessage]);
useEffect(() => {
throttledScrollDown();
selectedConversation &&
setCurrentMessage(
selectedConversation.messages[selectedConversation.messages.length - 2],
);
}, [selectedConversation, throttledScrollDown]);
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
setAutoScrollEnabled(entry.isIntersecting);
if (entry.isIntersecting) {
textareaRef.current?.focus();
}
},
{
root: null,
threshold: 0.5,
},
);
const messagesEndElement = messagesEndRef.current;
if (messagesEndElement) {
observer.observe(messagesEndElement);
}
return () => {
if (messagesEndElement) {
observer.unobserve(messagesEndElement);
}
};
}, [messagesEndRef]);
return (
<div className="relative flex-1 overflow-hidden bg-white dark:bg-[#343541]">
{!(apiKey || serverSideApiKeyIsSet) ? (
<div className="mx-auto flex h-full w-[300px] flex-col justify-center space-y-6 sm:w-[600px]">
<div className="text-center text-4xl font-bold text-black dark:text-white">
Welcome to Chatbot UI
</div>
<div className="text-center text-lg text-black dark:text-white">
<div className="mb-8">{`Chatbot UI is an open source clone of OpenAI's ChatGPT UI.`}</div>
<div className="mb-2 font-bold">
Important: Chatbot UI is 100% unaffiliated with OpenAI.
</div>
</div>
<div className="text-center text-gray-500 dark:text-gray-400">
<div className="mb-2">
Chatbot UI allows you to plug in your API key to use this UI with
their API.
</div>
<div className="mb-2">
It is <span className="italic">only</span> used to communicate
with their API.
</div>
<div className="mb-2">
{t(
'Please set your OpenAI API key in the bottom left of the sidebar.',
)}
</div>
<div>
{t("If you don't have an OpenAI API key, you can get one here: ")}
<a
href="https://platform.openai.com/account/api-keys"
target="_blank"
rel="noreferrer"
className="text-blue-500 hover:underline"
>
openai.com
</a>
</div>
</div>
</div>
) : modelError ? (
<ErrorMessageDiv error={modelError} />
) : (
<>
<div
className="max-h-full overflow-x-hidden"
ref={chatContainerRef}
onScroll={handleScroll}
>
{selectedConversation?.messages.length === 0 ? (
<>
<div className="mx-auto flex flex-col space-y-5 md:space-y-10 px-3 pt-5 md:pt-12 sm:max-w-[600px]">
<div className="text-center text-3xl font-semibold text-gray-800 dark:text-gray-100">
{models.length === 0 ? (
<div>
<Spinner size="16px" className="mx-auto" />
</div>
) : (
'Chatbot UI'
)}
</div>
{models.length > 0 && (
<div className="flex h-full flex-col space-y-4 rounded-lg border border-neutral-200 p-4 dark:border-neutral-600">
<ModelSelect />
<SystemPrompt
conversation={selectedConversation}
prompts={prompts}
onChangePrompt={(prompt) =>
handleUpdateConversation(selectedConversation, {
key: 'prompt',
value: prompt,
})
}
/>
<TemperatureSlider
label={t('Temperature')}
onChangeTemperature={(temperature) =>
handleUpdateConversation(selectedConversation, {
key: 'temperature',
value: temperature,
})
}
/>
</div>
)}
</div>
</>
) : (
<>
<div className="sticky top-0 z-10 flex justify-center border border-b-neutral-300 bg-neutral-100 py-2 text-sm text-neutral-500 dark:border-none dark:bg-[#444654] dark:text-neutral-200">
{t('Model')}: {selectedConversation?.model.name} | {t('Temp')}
: {selectedConversation?.temperature} |
<button
className="ml-2 cursor-pointer hover:opacity-50"
onClick={handleSettings}
>
<IconSettings size={18} />
</button>
<button
className="ml-2 cursor-pointer hover:opacity-50"
onClick={onClearAll}
>
<IconClearAll size={18} />
</button>
</div>
{showSettings && (
<div className="flex flex-col space-y-10 md:mx-auto md:max-w-xl md:gap-6 md:py-3 md:pt-6 lg:max-w-2xl lg:px-0 xl:max-w-3xl">
<div className="flex h-full flex-col space-y-4 border-b border-neutral-200 p-4 dark:border-neutral-600 md:rounded-lg md:border">
<ModelSelect />
</div>
</div>
)}
{selectedConversation?.messages.map((message, index) => (
<MemoizedChatMessage
key={index}
message={message}
messageIndex={index}
onEdit={(editedMessage) => {
setCurrentMessage(editedMessage);
// discard edited message and the ones that come after then resend
handleSend(
editedMessage,
selectedConversation?.messages.length - index,
plugin
);
}}
/>
))}
{loading && <ChatLoader />}
<div
className="h-[162px] bg-white dark:bg-[#343541]"
ref={messagesEndRef}
/>
</>
)}
</div>
<ChatInput
stopConversationRef={stopConversationRef}
textareaRef={textareaRef}
onSend={(message, plugin) => {
setCurrentMessage(message);
handleSend(message, 0, plugin);
}}
onScrollDownClick={handleScrollDown}
onRegenerate={() => {
if (currentMessage) {
handleSend(currentMessage, 2, plugin);
}
}}
showScrollDownButton={showScrollDownButton}
plugin={plugin}
handlePluginChange={handlePluginChange}
/>
</>
)}
</div>
);
});
Chat.displayName = 'Chat';
import { NextApiRequest, NextApiResponse } from 'next';
import { CognitiveSearchContent, KbChatBody } from '@/types/cogsearch';
import { getAnswerFromChatGPT } from './chat_complete';
import { AZURE_COGNITIVE_SEARCH_ENDPOINT, AZURE_COGNITIVE_SEARCH_CREDENTIAL } from '@/utils/app/const'
import endent from "endent";
import { OpenAIModels } from '@/types/openai';
const { SearchClient, AzureKeyCredential } = require("@azure/search-documents");
// a handler for Qdrant
export default async function qdrantHandler(
req: NextApiRequest,
res: NextApiResponse<any>,
): Promise<any> {
try {
const { model, messages, key, prompt, temperature, kbSearchQueryEnabled, kbSearchSystemPrompt, kbUserPrompt, kbSystemPrompt, kbCogSearchIndex, kbDebugMode } = req.body as KbChatBody;
const userMessage = messages[messages.length - 1].content.trim(); // prompt from user
const client = new SearchClient(
AZURE_COGNITIVE_SEARCH_ENDPOINT,
kbCogSearchIndex,
new AzureKeyCredential(AZURE_COGNITIVE_SEARCH_CREDENTIAL)
);
// 1. get query from question using Chatbot AI
let query;
if (kbSearchQueryEnabled) {
query = await getAnswerFromChatGPT(
key,
model,
temperature,
userMessage,
kbSearchSystemPrompt
);
} else {
query = userMessage;
}
// console.log("keywords:" + keywords);
// 2. get a list of docs that are similar to the vector using Qdrant API
for (const [key, value] of Object.entries(OpenAIModels)) {
if (key === model.id) {
model.tokenLimit = value.tokenLimit;
}
}
const top = Math.min(Math.floor(model.tokenLimit / 1024), 8) - 1;
// console.log('top:' + top);
// const searchResult = await client.search(keyword.trim(), {top: 3}) ;
const searchResult = await client.search(query, {
queryType: 'semantic',
queryLanguage: "ja-JP",
semanticConfiguration: "senshin-semantic", //TODO need to be configurable.
top: top});
let sourcesWithText: CognitiveSearchContent[] = [];
for await (const result of searchResult.results) {
// console.log(result.score)
const cogsearchSource = {
id: result.document.id,
title: result.document.title,
source: result.document.source,
content: result.document.content,
score: result.score
}
sourcesWithText.push(cogsearchSource)
}
// console.log(sourcesWithText)
// 3. get an answer from ChatGPT(OpenAI API) using the augmented prompt
const userPrompt = endent`
${kbUserPrompt}
Input:
${userMessage}
Sources:
${sourcesWithText.map((source) => {
return endent`
[${source.title}] (${source.source}):
${source.content}
`;
}).join('\n\n')}
`;
const answerFromChatGPT = await getAnswerFromChatGPT(
key,
model,
temperature,
userPrompt,
kbSystemPrompt
);
// console.log(answerFromChatGPT)
let answer = answerFromChatGPT;
if (kbDebugMode) {
answer += '\n\n' + endent`
--------
## User Prompt:
${userPrompt}
## Search Query:
${query}
## Reference:
${sourcesWithText.map((source) => {
return endent`
**ID:** ${source.id}
**Source:** ${source.source}
**Score:** ${source.score}
**Title**: ${source.title}
**Content:**
${source.content}
`;
}).join('\n\n')}
`
}
console.log(answer)
res.status(200).json({ answer });
} catch (error) {
console.error(error);
res.status(500).json({ error: `An error occurred: ${error}` });
}
}
import { IconFolderPlus, IconMistOff, IconPlus, IconFile } from '@tabler/icons-react';
import { ReactNode, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { IconArrowBarLeft, IconArrowBarRight } from '@tabler/icons-react';
import {
CloseSidebarButton,
OpenSidebarButton,
} from './components/OpenCloseButton';
interface Reference {
id: string;
title: string;
description: string;
}
interface Props<T> {
isOpen: boolean;
side: 'left' | 'right';
items: T[];
toggleOpen: () => void;
}
const ReferenceSidebar = <T,>({
isOpen,
side,
items,
toggleOpen,
}: Props<T>) => {
const { t } = useTranslation('promptbar');
const [hoveredItemId, setHoveredItemId] = useState<string | null>(null);
const allowDrop = (e: any) => {
e.preventDefault();
};
const highlightDrop = (e: any) => {
e.target.style.background = '#343541';
};
const removeHighlight = (e: any) => {
e.target.style.background = 'none';
};
const itemsample = [
{id:'1',title:'実績発電原子炉に係る新規制基準の考え方について.pdf',description:'…見直しを行うために「発電用軽水型原子炉の新規制基準に関する検討チーム」(以下「検討チーム」という。)を組織し、発電用軽水型原子炉の新規制基準策定のための検討を開始した。検討チーム…'},
{id:'2',title:'実績発電原子炉に係る新規制基準の考え方について.pdf',description:'…見直しを行うために「発電用軽水型原子炉の新規制基準に関する検討チーム」(以下「検討チーム」という。)を組織し、発電用軽水型原子炉の新規制基準策定のための検討を開始した。検討チーム…'},
{id:'3',title:'実績発電原子炉に係る新規制基準の考え方について.pdf',description:'…見直しを行うために「発電用軽水型原子炉の新規制基準に関する検討チーム」(以下「検討チーム」という。)を組織し、発電用軽水型原子炉の新規制基準策定のための検討を開始した。検討チーム…'},
{id:'4',title:'実績発電原子炉に係る新規制基準の考え方について.pdf',description:'…見直しを行うために「発電用軽水型原子炉の新規制基準に関する検討チーム」(以下「検討チーム」という。)を組織し、発電用軽水型原子炉の新規制基準策定のための検討を開始した。検討チーム…'},
{id:'5',title:'実績発電原子炉に係る新規制基準の考え方について.pdf',description:'…見直しを行うために「発電用軽水型原子炉の新規制基準に関する検討チーム」(以下「検討チーム」という。)を組織し、発電用軽水型原子炉の新規制基準策定のための検討を開始した。検討チーム…'},
]
const titleStyle = {
display: 'flex',
alignItems: 'center',
marginBottom: '30px',
fontSize:'18px'
}
const headerStyle = {
display: 'flex',
justiftContent: 'space-between',
alignItems:'center',
fontSize: '15px',
marginBottom: '15px',
width: '100%',
}
const titleIcon = {
display: 'flex',
marginRight: '8px',
}
const listItemStyle = (isHovered: boolean) => ({
marginBottom: '10px',
border: '1px solid white',
padding: '20px',
borderRadius: '5px',
backgroundColor: isHovered ? '#0056b3' : '#343541', // Change background color on hover
transition: 'background-color 0.3s',
maxWidth: '350px',
});
const listTitleStyle = {
marginBottom: '10px',
}
return isOpen ? (
<div>
<div className={`fixed top-0 ${side}-0 z-40 flex h-full w-[400px] flex-none flex-col space-y-2 bg-[#202123] p-2 text-[14px] transition-all sm:relative sm:top-0`}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', width: '100%', padding: '0 10px' }}>
<div style={titleStyle}>
<IconFile style={{ marginRight: '8px' }} />
参照リスト
</div>
<div onClick={toggleOpen} style={titleStyle}>
{side === 'right' ? <IconArrowBarRight /> : <IconArrowBarLeft />}
</div>
</div>
<div style={{overflowY:'auto',flex:1,padding:'0 10px'}}>
<ul>
{itemsample.map((item) => (
<a href="test" target="_blank" rel="noopener noreferrer">
<li
style={listItemStyle(item.id === hoveredItemId)}
onMouseEnter={() => setHoveredItemId(item.id)}
onMouseLeave={() => setHoveredItemId(null)}
>
<h3 style={listTitleStyle}>{item.title}</h3>
<p>{item.description}</p>
</li>
</a>
))}
</ul>
</div>
</div>
</div>
) : (
<OpenSidebarButton onClick={toggleOpen} side={side} />
);
};
export default ReferenceSidebar;
import { IconFolderPlus, IconMistOff, IconPlus, IconFile } from '@tabler/icons-react';
import { ReactNode, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { IconArrowBarLeft, IconArrowBarRight } from '@tabler/icons-react';
import {
CloseSidebarButton,
OpenSidebarButton,
} from './components/OpenCloseButton';
interface Reference {
id: string;
title: string;
description: string;
}
interface Props<T> {
isOpen: boolean;
side: 'left' | 'right';
items: T[];
toggleOpen: () => void;
}
const ReferenceSidebar = <T,>({
isOpen,
side,
items,
toggleOpen,
}: Props<T>) => {
const { t } = useTranslation('promptbar');
const [hoveredItemId, setHoveredItemId] = useState<string | null>(null);
const allowDrop = (e: any) => {
e.preventDefault();
};
const highlightDrop = (e: any) => {
e.target.style.background = '#343541';
};
const removeHighlight = (e: any) => {
e.target.style.background = 'none';
};
const itemsample = [
{id:'1',title:'実績発電原子炉に係る新規制基準の考え方について.pdf',description:'…見直しを行うために「発電用軽水型原子炉の新規制基準に関する検討チーム」(以下「検討チーム」という。)を組織し、発電用軽水型原子炉の新規制基準策定のための検討を開始した。検討チーム…'},
{id:'2',title:'実績発電原子炉に係る新規制基準の考え方について.pdf',description:'…見直しを行うために「発電用軽水型原子炉の新規制基準に関する検討チーム」(以下「検討チーム」という。)を組織し、発電用軽水型原子炉の新規制基準策定のための検討を開始した。検討チーム…'},
{id:'3',title:'実績発電原子炉に係る新規制基準の考え方について.pdf',description:'…見直しを行うために「発電用軽水型原子炉の新規制基準に関する検討チーム」(以下「検討チーム」という。)を組織し、発電用軽水型原子炉の新規制基準策定のための検討を開始した。検討チーム…'},
{id:'4',title:'実績発電原子炉に係る新規制基準の考え方について.pdf',description:'…見直しを行うために「発電用軽水型原子炉の新規制基準に関する検討チーム」(以下「検討チーム」という。)を組織し、発電用軽水型原子炉の新規制基準策定のための検討を開始した。検討チーム…'},
{id:'5',title:'実績発電原子炉に係る新規制基準の考え方について.pdf',description:'…見直しを行うために「発電用軽水型原子炉の新規制基準に関する検討チーム」(以下「検討チーム」という。)を組織し、発電用軽水型原子炉の新規制基準策定のための検討を開始した。検討チーム…'},
]
const titleStyle = {
display: 'flex',
alignItems: 'center',
marginBottom: '30px',
fontSize:'18px'
}
const headerStyle = {
display: 'flex',
justiftContent: 'space-between',
alignItems:'center',
fontSize: '15px',
marginBottom: '15px',
width: '100%',
}
const titleIcon = {
display: 'flex',
marginRight: '8px',
}
const listItemStyle = (isHovered: boolean) => ({
marginBottom: '10px',
border: '1px solid white',
padding: '20px',
borderRadius: '5px',
backgroundColor: isHovered ? '#0056b3' : '#343541', // Change background color on hover
transition: 'background-color 0.3s',
maxWidth: '350px',
});
const listTitleStyle = {
marginBottom: '10px',
}
return isOpen ? (
<div>
<div className={`fixed top-0 ${side}-0 z-40 flex h-full w-[400px] flex-none flex-col space-y-2 bg-[#202123] p-2 text-[14px] transition-all sm:relative sm:top-0`}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', width: '100%', padding: '0 10px' }}>
<div style={titleStyle}>
<IconFile style={{ marginRight: '8px' }} />
参照リスト
</div>
<div onClick={toggleOpen} style={titleStyle}>
{side === 'right' ? <IconArrowBarRight /> : <IconArrowBarLeft />}
</div>
</div>
<div style={{overflowY:'auto',flex:1,padding:'0 10px'}}>
<ul>
{itemsample.map((item) => (
<a href="index01/実用発電用原子炉に係る新規制基準の考え方について.pdf" target="_blank" rel="noopener noreferrer">
<li
style={listItemStyle(item.id === hoveredItemId)}
onMouseEnter={() => setHoveredItemId(item.id)}
onMouseLeave={() => setHoveredItemId(null)}
>
<h3 style={listTitleStyle}>{item.title}</h3>
<p>{item.description}</p>
</li>
</a>
))}
</ul>
</div>
</div>
</div>
) : (
<OpenSidebarButton onClick={toggleOpen} side={side} />
);
};
export default ReferenceSidebar;
import { OpenAIModel } from './openai';
export interface Reference {
id: string;
name: string;
description: string;
}
{items.map((item,index) => (
<a href={'https://caramlops-saas-0.westus.cloudapp.azure.com/file/test/index01/実用発電用原子炉に係る新規制基準の考え方について.pdf'} target="_blank" rel="noopener noreferrer">
<li
style={listItemStyle(index === hoveredItemId)}
onMouseEnter={() => setHoveredItemId(index)}
onMouseLeave={() => setHoveredItemId(null)}
>
<h3 style={listTitleStyle}>{item.title}</h3>
<p>{item.description}</p>
</li>
</a>
))}
</ul>