+
+
+
+ {online ? 'Online' : 'Offline'}
+
- ) : (
-
- )}
-
- {avatarPickerVisible &&
}
-
-
+
{summaryInputVisible ? (
<>
{
+ setSummaryValue(e.target.value)
+ setIsTyping(true)
+ setTimeout(() => setIsTyping(false), 500)
+ }}
onKeyDown={handleKeyDown}
- onChange={(e) => setSummaryValue(e.target.value)}
- onCompositionStart={() => setIsTyping(true)}
- onCompositionEnd={() => setIsTyping(false)}
- className="w-80"
- sx={{
- '.MuiInput-input': {
- padding: 0
- }
+ onBlur={() => {
+ setSummaryInputVisible(false)
}}
+ autoFocus
+ />
+
-
>
) : (
{summary}
- {!!currConversation &&
}
+ {!!conversation && (
+
+ )}
)}
+
+
-
-
-
- {isOnline ? 'Online' : 'Offline'}
-
-
-
+
- {currConversation && (
-
- )}
)
}
diff --git a/src/components/ChatBox/ContactHeaderNew.tsx b/src/components/ChatBox/ContactHeaderNew.tsx
new file mode 100644
index 00000000..e69de29b
diff --git a/src/components/ChatBox/InputBox.tsx b/src/components/ChatBox/InputBox.tsx
index c38de5cc..5d49163b 100644
--- a/src/components/ChatBox/InputBox.tsx
+++ b/src/components/ChatBox/InputBox.tsx
@@ -1,157 +1,38 @@
+import { useChat } from '@ai-sdk/react'
import classNames from 'classnames'
-import { produce } from 'immer'
-import {
- ChatCompletionContentPart,
- ChatCompletionContentPartImage,
- ChatCompletionContentPartText
-} from 'openai/resources'
+import { useAtom, useAtomValue } from 'jotai'
import { FC, memo, useEffect, useRef, useState } from 'react'
-import { useRecoilState, useRecoilValue } from 'recoil'
-import items from 'src/components/Sidebar/Items'
-import {
- useAudio,
- useChatCompletion,
- useCompletion,
- useImageGeneration
-} from 'src/hooks'
-import {
- audioFileState,
- base64ImagesState,
- currConversationState,
- loadingState,
- userInputState
-} from 'src/stores/conversation'
-import { currProductState } from 'src/stores/global'
-import { settingsState } from 'src/stores/settings'
-import { AudioContentPart } from 'src/types/conversation'
-import { Products } from 'src/types/global'
-import { LoadingIcon, SolidCloseIcon, SolidSendIcon } from '../Icons'
-import WaveForm from '../Waveform'
-import MediaUploader from './MediaUploader'
+import { useChatCompletion } from 'src/hooks'
+import { base64FilePromptAtom, inputTextAtom } from 'src/stores/conversation'
+import { loadingAtom, settingsAtom } from 'src/stores/global'
+import { LoadingIcon, SolidSendIcon } from '../Icons'
+import { Textarea } from '../ui/textarea'
+import AttachmentPreview from './AttachmentPreview'
+import AttachmentUploader from './AttachmentUploader'
import AudioRecorder from './Recorder'
+import TokenCount from './TokenCount'
+import { Button } from '../ui/button'
const InputBox: FC = () => {
- const currConversation = useRecoilValue(currConversationState)
- const currProduct = useRecoilValue(currProductState)
- const settings = useRecoilValue(settingsState)
- const loading = useRecoilValue(loadingState)
- const [userInput, setUserInput] = useRecoilState(userInputState)
- const [audioFile, setAudioFile] = useRecoilState(audioFileState)
- const [base64Images, setBase64Images] = useRecoilState(base64ImagesState)
+ const { messages, input, handleInputChange, handleSubmit } = useChat({
+ api: 'http://localhost:8965/api/chat'
+ })
+ const settings = useAtomValue(settingsAtom)
+ const loading = useAtomValue(loadingAtom)
+ const [inputText, setInputText] = useAtom(inputTextAtom)
const createChatCompletion = useChatCompletion()
- const createAudio = useAudio()
- const createImage = useImageGeneration()
- const createCompletion = useCompletion()
const textareaRef = useRef
(null)
const [isTyping, setIsTyping] = useState(false)
- const mediaType = items.find(
- (item) => item.product === currProduct
- )?.multiMedia
-
- const deleteBase64Image = (idx: number) => {
- setBase64Images(
- produce(base64Images, (draft) => {
- draft?.splice(idx, 1)
- })
- )
- }
+ const [base64FilePrompt, setBase64FilePrompt] = useAtom(base64FilePromptAtom)
const resetInput = () => {
- setUserInput('')
- setAudioFile({
- filename: '',
- binary: undefined
- })
- setBase64Images(null)
+ setInputText('')
+ setBase64FilePrompt([])
}
const validate = () => {
if (loading) return false
- return userInput.trim().length !== 0
- }
-
- const handleRequest = () => {
- if (!settings || !validate()) return
-
- if (currProduct === Products.ChatCompletion) {
- const chatMessageImageContent:
- | ChatCompletionContentPartImage[]
- | undefined = base64Images?.map((imageUrl) => ({
- type: 'image_url',
- image_url: {
- url: imageUrl
- }
- }))
-
- const chatMessageTextContent: ChatCompletionContentPartText = {
- type: 'text',
- text: userInput
- }
-
- const chatCompletionUserMessage: ChatCompletionContentPart[] = [
- ...(chatMessageImageContent || []),
- chatMessageTextContent
- ]
-
- if (createChatCompletion) {
- createChatCompletion(chatCompletionUserMessage)
- }
- }
-
- if (
- currProduct === Products.AudioTranscription ||
- currProduct === Products.AudioTranslation
- ) {
- const audioContentPart: AudioContentPart[] = [
- {
- type: 'audio',
- audioUrl: { url: audioFile.filename },
- text: userInput,
- binary: audioFile.binary
- }
- ]
-
- if (createAudio) {
- if (currProduct === Products.AudioTranscription) {
- const createAudioTranscription =
- createAudio[Products.AudioTranscription]
- createAudioTranscription(audioContentPart)
- }
-
- if (currProduct === Products.AudioTranslation) {
- const createAudioTranslation = createAudio[Products.AudioTranslation]
- createAudioTranslation(audioContentPart)
- }
- }
- }
-
- if (currProduct === Products.ImageGeneration) {
- const imageGenerationTextContent: ChatCompletionContentPartText[] = [
- {
- type: 'text',
- text: userInput
- }
- ]
-
- if (createImage) {
- createImage(imageGenerationTextContent)
- }
- }
-
- if (currProduct === Products.Completion) {
- const completionTextContent: ChatCompletionContentPartText[] = [
- {
- type: 'text',
- text: userInput
- }
- ]
-
- if (createCompletion) {
- createCompletion(completionTextContent)
- }
- }
-
- resetInput()
+ return inputText.trim().length !== 0
}
// FIXME: I cannot declare the type of `event` correctly.
@@ -165,7 +46,7 @@ const InputBox: FC = () => {
const end = event.target.selectionEnd
const value = event.target.value
- setUserInput(value.substring(0, start) + '\n' + value.substring(end))
+ setInputText(value.substring(0, start) + '\n' + value.substring(end))
event.target.selectionStart = event.target.selectionEnd = start + 1
}
@@ -175,6 +56,21 @@ const InputBox: FC = () => {
}
}
+ const handleRequest = () => {
+ if (!settings || !validate()) return
+
+ // const textPrompt: TextPrompt[] = [
+ // {
+ // type: ContentPartType.TextPrompt,
+ // text: inputText
+ // }
+ // ]
+
+ // createChatCompletion([...textPrompt, ...base64FilePrompt])
+
+ resetInput()
+ }
+
useEffect(() => {
if (textareaRef && textareaRef.current) {
textareaRef.current.style.height = 'inherit'
@@ -183,45 +79,20 @@ const InputBox: FC = () => {
textareaRef?.current?.scrollHeight > 400 ? 'auto' : 'hidden'
}`
}
- }, [userInput])
-
- if (!currConversation) return null
+ }, [inputText])
return (
-
- {Array.isArray(base64Images) && base64Images.length > 0 && (
-
- {base64Images.map((image, idx) => (
-
-
- deleteBase64Image(idx)}
- />
-
-
-
- ))}
-
- )}
- {audioFile.filename && (
-
- )}
-
- {mediaType && (
-
- )}
-
-
)
}
diff --git a/src/components/ChatBox/TokenCount.tsx b/src/components/ChatBox/TokenCount.tsx
new file mode 100644
index 00000000..2516e033
--- /dev/null
+++ b/src/components/ChatBox/TokenCount.tsx
@@ -0,0 +1,25 @@
+import { useAtomValue } from 'jotai'
+import { FC } from 'react'
+import configurations from 'src/configurations'
+import { configurationAtom, conversationAtom } from 'src/stores/conversation'
+import { companyAtom } from 'src/stores/global'
+
+const TokenCount: FC = () => {
+ const conversation = useAtomValue(conversationAtom)
+ const company = useAtomValue(companyAtom)
+ const configuration = useAtomValue(configurationAtom)
+ const { models } = configurations[company]
+ const { maxInput } =
+ models.find((m) => m.modelName === configuration.model) ?? {}
+ const usedTokenCount =
+ conversation?.messages.reduce((acc, val) => acc + val.tokenCount, 0) +
+ configuration.systemMessageTokensCount
+
+ return (
+
+ Token count: {usedTokenCount} / {maxInput}
+
+ )
+}
+
+export default TokenCount
diff --git a/src/components/ChatBox/ToolsBox.tsx b/src/components/ChatBox/ToolsBox.tsx
new file mode 100644
index 00000000..b9c6ce0a
--- /dev/null
+++ b/src/components/ChatBox/ToolsBox.tsx
@@ -0,0 +1,51 @@
+import { Copy, Speaker } from 'lucide-react'
+import { DateTime } from 'luxon'
+import { FC, useState } from 'react'
+import { useTTS } from 'src/hooks'
+import { ContentPartType, Message, Roles } from 'src/types/conversation'
+
+interface Props {
+ message: Message
+}
+
+const ToolsBox: FC = ({ message: { createdAt, content, role } }) => {
+ const [audioUrl, setAudioUrl] = useState('')
+ const createSpeech = useTTS()
+
+ const createTTSUrl = async () => {
+ if (typeof createSpeech === 'function') {
+ const textPrompt = content.find(
+ (item) => item.type === ContentPartType.TextPrompt
+ )
+
+ if (textPrompt) {
+ const url = await createSpeech(textPrompt.text)
+ if (url) {
+ setAudioUrl(url)
+ }
+ }
+ }
+ }
+
+ return (
+
+ {role === Roles.Assistant && (
+ <>
+
+
+ >
+ )}
+
+ {DateTime.fromMillis(createdAt).toLocaleString(
+ DateTime.DATETIME_SHORT_WITH_SECONDS
+ )}
+
+ {audioUrl && }
+
+ )
+}
+
+export default ToolsBox
diff --git a/src/components/ChatBox/index.tsx b/src/components/ChatBox/index.tsx
index c74861e6..994a30e4 100644
--- a/src/components/ChatBox/index.tsx
+++ b/src/components/ChatBox/index.tsx
@@ -1,5 +1,5 @@
import { FC } from 'react'
-import Divider from '../Divider'
+import { Separator } from 'src/components/ui/separator'
import ChatMessages from './ChatMessages'
import ContractHeader from './ContactHeader'
import InputBox from './InputBox'
@@ -8,7 +8,7 @@ const ChatBox: FC = () => {
return (
diff --git a/src/components/Configuration/AudioTranscription.tsx b/src/components/Configuration/AudioTranscription.tsx
deleted file mode 100644
index 7d072af6..00000000
--- a/src/components/Configuration/AudioTranscription.tsx
+++ /dev/null
@@ -1,185 +0,0 @@
-import Autocomplete from '@mui/material/Autocomplete'
-import Box from '@mui/material/Box'
-import Drawer from '@mui/material/Drawer'
-import FormControl from '@mui/material/FormControl'
-import InputLabel from '@mui/material/InputLabel'
-import MenuItem from '@mui/material/MenuItem'
-import Select from '@mui/material/Select'
-import TextField from '@mui/material/TextField'
-import { Formik, useFormikContext } from 'formik'
-import { FC, useEffect } from 'react'
-import { useRecoilState } from 'recoil'
-import {
- AudioTranscriptionConfiguration,
- models,
- responseFormats
-} from 'src/configurations/audioTranscription'
-import { useDB } from 'src/hooks'
-import { countries } from 'src/shared/countries'
-import { currConversationState } from 'src/stores/conversation'
-import { configurationDrawerVisibleState } from 'src/stores/global'
-import Divider from '../Divider'
-import InputSlider from '../InputSlider'
-
-const Configuration: FC = () => {
- const [visible, setVisible] = useRecoilState(configurationDrawerVisibleState)
- const [currConversation, setCurrConversation] = useRecoilState(
- currConversationState
- )
- const { updateOneById } = useDB('conversations')
-
- const updateConfiguration = async (
- values: AudioTranscriptionConfiguration
- ) => {
- if (!currConversation) {
- return
- }
-
- await updateOneById(currConversation.conversationId, {
- configuration: values,
- updatedAt: +new Date()
- })
-
- setCurrConversation({ ...currConversation, configuration: values })
- }
-
- const AutoSubmitToken = () => {
- const { submitForm } = useFormikContext()
- useEffect(() => {
- if (!visible) {
- submitForm()
- }
- }, [visible])
-
- return null
- }
-
- if (!currConversation) {
- return null
- }
-
- return (
- setVisible(false)}>
-
-
-
-
-
-
- initialValues={
- currConversation.configuration as AudioTranscriptionConfiguration
- }
- onSubmit={updateConfiguration}
- >
- {(formik) => (
-
-
-
- Model
-
-
-
-
-
- Response Format
-
-
-
-
- formik.setFieldValue('temperature', value)
- }
- />
-
- option.label}
- renderOption={(props, option) => (
- img': { mr: 2, flexShrink: 0 } }}
- {...props}
- >
-
- {option.label} ({option.code})
-
- )}
- renderInput={(params) => (
-
- )}
- value={countries.find(
- (country) => country.code === formik.values.language
- )}
- onChange={(_, value) =>
- formik.setFieldValue('language', value?.code)
- }
- />
-
-
-
- )}
-
-
-
- )
-}
-
-export default Configuration
diff --git a/src/components/Configuration/AudioTranslation.tsx b/src/components/Configuration/AudioTranslation.tsx
deleted file mode 100644
index 99439ff5..00000000
--- a/src/components/Configuration/AudioTranslation.tsx
+++ /dev/null
@@ -1,131 +0,0 @@
-import Drawer from '@mui/material/Drawer'
-import FormControl from '@mui/material/FormControl'
-import InputLabel from '@mui/material/InputLabel'
-import MenuItem from '@mui/material/MenuItem'
-import Select from '@mui/material/Select'
-import { Formik, useFormikContext } from 'formik'
-import { FC, useEffect } from 'react'
-import { useRecoilState } from 'recoil'
-import { models, responseFormats } from 'src/configurations/audioTranscription'
-import { AudioTranslationConfiguration } from 'src/configurations/audioTranslation'
-import { useDB } from 'src/hooks'
-import { currConversationState } from 'src/stores/conversation'
-import { configurationDrawerVisibleState } from 'src/stores/global'
-import Divider from '../Divider'
-import InputSlider from '../InputSlider'
-
-const Configuration: FC = () => {
- const [visible, setVisible] = useRecoilState(configurationDrawerVisibleState)
- const [currConversation, setCurrConversation] = useRecoilState(
- currConversationState
- )
- const { updateOneById } = useDB('conversations')
-
- const updateConfiguration = async (values: AudioTranslationConfiguration) => {
- if (!currConversation) {
- return
- }
-
- await updateOneById(currConversation.conversationId, {
- configuration: values,
- updatedAt: +new Date()
- })
-
- setCurrConversation({ ...currConversation, configuration: values })
- }
-
- const AutoSubmitToken = () => {
- const { submitForm } = useFormikContext()
- useEffect(() => {
- if (!visible) {
- submitForm()
- }
- }, [visible])
-
- return null
- }
-
- if (!currConversation) {
- return null
- }
-
- return (
- setVisible(false)}>
-
-
-
-
-
-
- initialValues={
- currConversation.configuration as AudioTranslationConfiguration
- }
- onSubmit={updateConfiguration}
- >
- {(formik) => (
-
-
-
- Model
-
-
-
-
-
- Response Format
-
-
-
-
- formik.setFieldValue('temperature', value)
- }
- />
-
-
- )}
-
-
-
- )
-}
-
-export default Configuration
diff --git a/src/components/Configuration/ChatCompletion.tsx b/src/components/Configuration/ChatCompletion.tsx
deleted file mode 100644
index 6fd46b62..00000000
--- a/src/components/Configuration/ChatCompletion.tsx
+++ /dev/null
@@ -1,223 +0,0 @@
-import Autocomplete from '@mui/material/Autocomplete'
-import Chip from '@mui/material/Chip'
-import Drawer from '@mui/material/Drawer'
-import FormControl from '@mui/material/FormControl'
-import InputLabel from '@mui/material/InputLabel'
-import MenuItem from '@mui/material/MenuItem'
-import Select from '@mui/material/Select'
-import TextField from '@mui/material/TextField'
-import Tooltip from '@mui/material/Tooltip'
-import { Formik, useFormikContext } from 'formik'
-import { FC, useEffect } from 'react'
-import { useRecoilState } from 'recoil'
-import { ChatConfiguration, models } from 'src/configurations/chatCompletion'
-import { useDB } from 'src/hooks'
-import { getTokensCount } from 'src/shared/utils'
-import { currConversationState } from 'src/stores/conversation'
-import { configurationDrawerVisibleState } from 'src/stores/global'
-import Divider from '../Divider'
-import InputSlider from '../InputSlider'
-
-const Configuration: FC = () => {
- const [visible, setVisible] = useRecoilState(configurationDrawerVisibleState)
- const [currConversation, setCurrConversation] = useRecoilState(
- currConversationState
- )
- const { updateOneById } = useDB('conversations')
-
- const updateConfiguration = async (values: ChatConfiguration) => {
- if (!currConversation) {
- return
- }
-
- // Regenerate `systemMessageTokensCount` if `systemMessage` changed.
- const prevConfiguration =
- currConversation.configuration as ChatConfiguration
- const configuration: ChatConfiguration = {
- ...values,
- systemMessageTokensCount:
- prevConfiguration.systemMessage !== values.systemMessage
- ? getTokensCount(values.systemMessage, values.model)
- : prevConfiguration.systemMessageTokensCount
- }
-
- await updateOneById(currConversation.conversationId, {
- configuration,
- updatedAt: +new Date()
- })
-
- setCurrConversation({ ...currConversation, configuration })
- }
-
- const AutoSubmitToken = () => {
- const { submitForm } = useFormikContext()
- useEffect(() => {
- if (!visible) {
- submitForm()
- }
- }, [visible])
-
- return null
- }
-
- if (!currConversation) {
- return null
- }
-
- return (
- setVisible(false)}>
-
-
-
-
-
-
- initialValues={currConversation.configuration as ChatConfiguration}
- onSubmit={updateConfiguration}
- >
- {(formik) => (
-
-
- Model
-
-
-
-
-
-
- formik.setFieldValue('maxTokens', value)
- }
- />
-
-
- formik.setFieldValue('temperature', value)
- }
- />
-
-
-
-
- value.map((option: string, index: number) => (
-
- ))
- }
- renderInput={(params) => (
-
- )}
- onChange={(_, value) => formik.setFieldValue('stop', value)}
- />
-
-
-
-
- formik.setFieldValue('topP', value)
- }
- />
-
- formik.setFieldValue('frequencyPenalty', value)
- }
- />
-
- formik.setFieldValue('frequencyPenalty', value)
- }
- />
-
-
- )}
-
-
-
- )
-}
-
-export default Configuration
diff --git a/src/components/Configuration/Completion.tsx b/src/components/Configuration/Completion.tsx
deleted file mode 100644
index 1dbca7e7..00000000
--- a/src/components/Configuration/Completion.tsx
+++ /dev/null
@@ -1,274 +0,0 @@
-import Autocomplete from '@mui/material/Autocomplete'
-import Checkbox from '@mui/material/Checkbox'
-import Chip from '@mui/material/Chip'
-import Drawer from '@mui/material/Drawer'
-import FormControl from '@mui/material/FormControl'
-import InputAdornment from '@mui/material/InputAdornment'
-import InputLabel from '@mui/material/InputLabel'
-import MenuItem from '@mui/material/MenuItem'
-import Select from '@mui/material/Select'
-import TextField from '@mui/material/TextField'
-import Tooltip from '@mui/material/Tooltip'
-import { Formik, useFormikContext } from 'formik'
-import { FC, useEffect } from 'react'
-import { useRecoilState } from 'recoil'
-import { CompletionConfiguration, models } from 'src/configurations/completion'
-import { useDB } from 'src/hooks'
-import { currConversationState } from 'src/stores/conversation'
-import { configurationDrawerVisibleState } from 'src/stores/global'
-import Divider from '../Divider'
-import InputSlider from '../InputSlider'
-
-const Configuration: FC = () => {
- const [visible, setVisible] = useRecoilState(configurationDrawerVisibleState)
- const [currConversation, setCurrConversation] = useRecoilState(
- currConversationState
- )
- const { updateOneById } = useDB('conversations')
-
- const updateConfiguration = async (values: CompletionConfiguration) => {
- if (!currConversation) {
- return
- }
- await updateOneById(currConversation.conversationId, {
- configuration: values,
- updatedAt: +new Date()
- })
-
- setCurrConversation({ ...currConversation, configuration: values })
- }
-
- const AutoSubmitToken = () => {
- const { submitForm } = useFormikContext()
- useEffect(() => {
- if (!visible) {
- submitForm()
- }
- }, [visible])
-
- return null
- }
-
- if (!currConversation) {
- return null
- }
-
- return (
- setVisible(false)}>
-
-
-
-
-
-
- initialValues={
- currConversation.configuration as CompletionConfiguration
- }
- onSubmit={updateConfiguration}
- >
- {(formik) => (
-
-
- Model
-
-
-
-
- formik.setFieldValue('maxTokens', value)
- }
- />
-
-
-
-
- value.map((option: string, index: number) => (
-
- ))
- }
- renderInput={(params) => (
-
- )}
- onChange={(_, value) => formik.setFieldValue('stop', value)}
- />
-
-
-
-
- formik.setFieldValue('temperature', value)
- }
- />
-
- formik.setFieldValue('topP', value)
- }
- />
-
- formik.setFieldValue('frequencyPenalty', value)
- }
- />
-
-
- formik.setFieldValue('frequencyPenalty', value)
- }
- />
-
-
-
- {
- formik.setFieldValue('preResponse', {
- ...formik.values.preResponse,
- content: event?.target.value
- })
- }}
- InputProps={{
- startAdornment: (
-
-
- formik.setFieldValue('preResponse', {
- ...formik.values.preResponse,
- checked
- })
- }
- />
-
- )
- }}
- />
-
-
-
-
-
- {
- formik.setFieldValue('postResponse', {
- ...formik.values.postResponse,
- content: event?.target.value
- })
- }}
- InputProps={{
- startAdornment: (
-
-
- formik.setFieldValue('postResponse', {
- ...formik.values.postResponse,
- checked
- })
- }
- />
-
- )
- }}
- />
-
-
-
-
-
- )}
-
-
-
- )
-}
-
-export default Configuration
diff --git a/src/components/Configuration/ConfigurationWrapper.tsx b/src/components/Configuration/ConfigurationWrapper.tsx
deleted file mode 100644
index e3b5e5b8..00000000
--- a/src/components/Configuration/ConfigurationWrapper.tsx
+++ /dev/null
@@ -1,76 +0,0 @@
-import Drawer from '@mui/material/Drawer'
-import { Formik, useFormikContext } from 'formik'
-import { FC, ReactElement, cloneElement, useEffect } from 'react'
-import { useRecoilState } from 'recoil'
-import Divider from 'src/components/Divider'
-import { ChatConfiguration } from 'src/configurations/chatCompletion'
-import { useDB } from 'src/hooks'
-import { currConversationState } from 'src/stores/conversation'
-import { configurationDrawerVisibleState } from 'src/stores/global'
-
-interface Props {
- children: ReactElement
-}
-
-const ConfigurationWrapper: FC = ({ children }) => {
- const [visible, setVisible] = useRecoilState(configurationDrawerVisibleState)
- const [currConversation, setCurrConversation] = useRecoilState(
- currConversationState
- )
- const { updateOneById } = useDB('conversations')
-
- const updateConfiguration = async (values: ChatConfiguration) => {
- if (!currConversation) {
- return
- }
-
- await updateOneById(currConversation.conversationId, {
- configuration: values
- })
-
- setCurrConversation({ ...currConversation, configuration: values })
- }
-
- const AutoSubmitToken = () => {
- const { submitForm } = useFormikContext()
- useEffect(() => {
- if (!visible) {
- submitForm()
- }
- }, [visible])
-
- return null
- }
-
- if (!currConversation) {
- return null
- }
-
- return (
- setVisible(false)}>
-
-
-
-
-
-
- initialValues={currConversation.configuration as ChatConfiguration}
- onSubmit={updateConfiguration}
- >
- {(formik) => (
-
- {cloneElement(children, { formik })}
-
-
- )}
-
-
-
- )
-}
-
-export default ConfigurationWrapper
diff --git a/src/components/Configuration/ImageGeneration.tsx b/src/components/Configuration/ImageGeneration.tsx
deleted file mode 100644
index 0abb1749..00000000
--- a/src/components/Configuration/ImageGeneration.tsx
+++ /dev/null
@@ -1,144 +0,0 @@
-import Drawer from '@mui/material/Drawer'
-import FormControl from '@mui/material/FormControl'
-import FormHelperText from '@mui/material/FormHelperText'
-import InputLabel from '@mui/material/InputLabel'
-import MenuItem from '@mui/material/MenuItem'
-import Select from '@mui/material/Select'
-import { Formik, useFormikContext } from 'formik'
-import { FC, useEffect } from 'react'
-import { useRecoilState } from 'recoil'
-import {
- ImageGenerationConfiguration,
- responseFormats,
- sizes
-} from 'src/configurations/imageGeneration'
-import { useDB } from 'src/hooks'
-import { currConversationState } from 'src/stores/conversation'
-import { configurationDrawerVisibleState } from 'src/stores/global'
-import Divider from '../Divider'
-import InputSlider from '../InputSlider'
-
-const Configuration: FC = () => {
- const [visible, setVisible] = useRecoilState(configurationDrawerVisibleState)
- const [currConversation, setCurrConversation] = useRecoilState(
- currConversationState
- )
- const { updateOneById } = useDB('conversations')
-
- const updateConfiguration = async (values: ImageGenerationConfiguration) => {
- if (!currConversation) {
- return
- }
-
- await updateOneById(currConversation.conversationId, {
- configuration: values,
- updatedAt: +new Date()
- })
-
- setCurrConversation({ ...currConversation, configuration: values })
- }
-
- const AutoSubmitToken = () => {
- const { submitForm } = useFormikContext()
- useEffect(() => {
- if (!visible) {
- submitForm()
- }
- }, [visible])
-
- return null
- }
-
- if (!currConversation) {
- return null
- }
-
- return (
- setVisible(false)}>
-
-
-
-
-
-
- initialValues={
- currConversation.configuration as ImageGenerationConfiguration
- }
- onSubmit={updateConfiguration}
- >
- {(formik) => (
-
-
- formik.setFieldValue('n', value)
- }
- />
-
-
-
- Size
-
-
-
- The size of the generated images.
-
-
-
-
-
- Response Format
-
-
-
- The format in which the generated images are returned.
-
-
-
-
-
- )}
-
-
-
- )
-}
-
-export default Configuration
diff --git a/src/components/Configuration/index.ts b/src/components/Configuration/index.ts
deleted file mode 100644
index cb4fb583..00000000
--- a/src/components/Configuration/index.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import AudioTranscriptionConfiguration from './AudioTranscription'
-import AudioTranslationConfiguration from './AudioTranslation'
-import ChatCompletionConfiguration from './ChatCompletion'
-import CompletionConfiguration from './Completion'
-import ImageGenerationConfiguration from './ImageGeneration'
-
-export {
- AudioTranscriptionConfiguration,
- AudioTranslationConfiguration,
- ChatCompletionConfiguration,
- CompletionConfiguration,
- ImageGenerationConfiguration
-}
diff --git a/src/components/Configuration/index.tsx b/src/components/Configuration/index.tsx
new file mode 100644
index 00000000..3587de6e
--- /dev/null
+++ b/src/components/Configuration/index.tsx
@@ -0,0 +1,272 @@
+import { useAtomValue } from 'jotai'
+import { X } from 'lucide-react'
+import { FC, memo, useEffect } from 'react'
+import { Controller, useForm } from 'react-hook-form'
+import { Badge } from 'src/components/ui/badge'
+import { Button } from 'src/components/ui/button'
+import {
+ Form,
+ FormControl,
+ FormDescription,
+ FormField,
+ FormItem,
+ FormLabel
+} from 'src/components/ui/form'
+import { Input } from 'src/components/ui/input'
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue
+} from 'src/components/ui/select'
+import { Separator } from 'src/components/ui/separator'
+import { Textarea } from 'src/components/ui/textarea'
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipProvider,
+ TooltipTrigger
+} from 'src/components/ui/tooltip'
+import configurations from 'src/configurations'
+import { useDB } from 'src/hooks'
+import { configurationAtom } from 'src/stores/conversation'
+import { companyAtom } from 'src/stores/global'
+import { Configuration as IConfiguration } from 'src/types/conversation'
+import InputSlider from '../InputSlider'
+
+const Configuration: FC = () => {
+ const company = useAtomValue(companyAtom)
+ const configuration = useAtomValue(configurationAtom)
+ const { updateOneById } = useDB('configurations')
+ const availableModels = configurations[company]?.models
+
+ const form = useForm({
+ defaultValues: configuration
+ })
+
+ // Update form values when configuration changes
+ useEffect(() => {
+ if (configuration) {
+ form.reset(configuration)
+ }
+ }, [configuration, form])
+
+ const onSubmit = async (values: IConfiguration) => {
+ await updateOneById(values.company, values)
+ }
+
+ const maxOutput = availableModels?.find(
+ (availableModel) => availableModel.modelName === form.watch('model')
+ )?.maxOutput
+
+ return (
+
+ )
+}
+
+export default memo(Configuration)
diff --git a/src/components/Conversation/index.tsx b/src/components/Conversation/index.tsx
new file mode 100644
index 00000000..04cd9bf8
--- /dev/null
+++ b/src/components/Conversation/index.tsx
@@ -0,0 +1,51 @@
+import { useLiveQuery } from 'dexie-react-hooks'
+import { useAtom, useAtomValue } from 'jotai'
+import { FC, useEffect } from 'react'
+import ChatBox from 'src/components/ChatBox'
+import Configuration from 'src/components/Configuration'
+import ConversationList from 'src/components/ConversationList'
+import { Separator } from 'src/components/ui/separator'
+import { db } from 'src/db'
+import { conversationAtom } from 'src/stores/conversation'
+import { companyAtom } from 'src/stores/global'
+import { Conversation as IConversation } from 'src/types/conversation'
+
+const Conversation: FC = () => {
+ const [conversation, setConversation] = useAtom(conversationAtom)
+ const company = useAtomValue(companyAtom)
+ const conversations = useLiveQuery(
+ () =>
+ db
+ .table('conversations')
+ .where('company')
+ .equals(company)
+ .reverse()
+ .sortBy('updatedAt'),
+ [company]
+ )
+
+ useEffect(() => {
+ if (
+ // Initializing App
+ !conversation ||
+ // Switching company
+ conversation?.company !== company ||
+ // Deleting a conversation
+ conversations.findIndex((c) => c.id === conversation.id) === -1
+ ) {
+ setConversation(conversations?.[0])
+ }
+ }, [conversation, conversations, company, setConversation])
+
+ return (
+
+ )
+}
+
+export default Conversation
diff --git a/src/components/ConversationList/ConversationItem.tsx b/src/components/ConversationList/ConversationItem.tsx
index 9e3a875d..ed7d33d4 100644
--- a/src/components/ConversationList/ConversationItem.tsx
+++ b/src/components/ConversationList/ConversationItem.tsx
@@ -1,8 +1,8 @@
import classNames from 'classnames'
import { FC } from 'react'
-import ChatGPTLogoImg from 'src/assets/chatbot.png'
+import HyperChatLogo from 'src/assets/images/logo.png'
import { formatDate } from 'src/shared/utils'
-import { Conversation } from 'src/types/conversation'
+import { ContentPartType, Conversation } from 'src/types/conversation'
import ItemWrapper from './ItemWrapper'
interface Props {
@@ -18,9 +18,7 @@ const ConversationItem: FC = ({ active, conversation, onClick }) => {
const { content } = conversation.messages[conversation.messages.length - 1]
const lastMessage = content[content.length - 1]
- if (lastMessage.type === 'image_url') {
- return '[Image]'
- } else if (lastMessage.type === 'text') {
+ if (lastMessage.type === ContentPartType.TextPrompt) {
return lastMessage.text
}
@@ -35,7 +33,7 @@ const ConversationItem: FC = ({ active, conversation, onClick }) => {
) : (