Skip to content

Commit

Permalink
fix: duplicated messages when user switch between conversations
Browse files Browse the repository at this point in the history
Signed-off-by: James <[email protected]>
  • Loading branch information
James committed Oct 25, 2023
1 parent e05c08b commit 4023260
Show file tree
Hide file tree
Showing 15 changed files with 102 additions and 193 deletions.
1 change: 0 additions & 1 deletion electron/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@
"electron-store": "^8.1.0",
"electron-updater": "^6.1.4",
"pacote": "^17.0.4",
"react-intersection-observer": "^9.5.2",
"request": "^2.88.2",
"request-progress": "^3.0.0",
"use-debounce": "^9.0.4"
Expand Down
4 changes: 0 additions & 4 deletions web/app/_components/BasicPromptInput/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { currentPromptAtom } from '@helpers/JotaiWrapper'
import { getActiveConvoIdAtom } from '@helpers/atoms/Conversation.atom'
import { selectedModelAtom } from '@helpers/atoms/Model.atom'
import useCreateConversation from '@hooks/useCreateConversation'
import useInitModel from '@hooks/useInitModel'
import useSendChatMessage from '@hooks/useSendChatMessage'
import { useAtom, useAtomValue } from 'jotai'
import { ChangeEvent, useEffect, useRef } from 'react'
Expand All @@ -16,8 +15,6 @@ const BasicPromptInput: React.FC = () => {
const { sendChatMessage } = useSendChatMessage()
const { requestCreateConvo } = useCreateConversation()

const { initModel } = useInitModel()

const textareaRef = useRef<HTMLTextAreaElement>(null)

const handleKeyDown = async (
Expand All @@ -35,7 +32,6 @@ const BasicPromptInput: React.FC = () => {
}

await requestCreateConvo(selectedModel)
await initModel(selectedModel)
sendChatMessage()
}
}
Expand Down
55 changes: 6 additions & 49 deletions web/app/_components/ChatBody/index.tsx
Original file line number Diff line number Diff line change
@@ -1,60 +1,17 @@
'use client'

import React, { useCallback, useRef, useState, useEffect } from 'react'
import React from 'react'
import ChatItem from '../ChatItem'
import useChatMessages from '@hooks/useChatMessages'
import { useAtomValue } from 'jotai'
import { selectAtom } from 'jotai/utils'
import { getActiveConvoIdAtom } from '@helpers/atoms/Conversation.atom'
import { chatMessages } from '@helpers/atoms/ChatMessage.atom'

const ChatBody: React.FC = () => {
const activeConversationId = useAtomValue(getActiveConvoIdAtom) ?? ''
const messageList = useAtomValue(
selectAtom(
chatMessages,
useCallback((v) => v[activeConversationId], [activeConversationId])
)
)
const [content, setContent] = useState<React.JSX.Element[]>([])

const [offset, setOffset] = useState(0)
const { loading, hasMore } = useChatMessages(offset)
const intersectObs = useRef<any>(null)

const lastPostRef = useCallback(
(message: ChatMessage) => {
if (loading) return

if (intersectObs.current) intersectObs.current.disconnect()

intersectObs.current = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting && hasMore) {
setOffset((prevOffset) => prevOffset + 5)
}
})

if (message) intersectObs.current.observe(message)
},
[loading, hasMore]
)

useEffect(() => {
const list = messageList?.map((message, index) => {
if (messageList?.length === index + 1) {
return (
// @ts-ignore
<ChatItem ref={lastPostRef} message={message} key={message.id} />
)
}
return <ChatItem message={message} key={message.id} />
})
setContent(list)
}, [messageList, lastPostRef])
const { messages } = useChatMessages()

return (
<div className="[&>*:nth-child(odd)]:bg-background flex h-full flex-1 flex-col-reverse overflow-y-auto">
{content}
<div className="flex h-full flex-1 flex-col-reverse overflow-y-auto [&>*:nth-child(odd)]:bg-background">
{messages.map((message) => (
<ChatItem message={message} key={message.id} />
))}
</div>
)
}
Expand Down
13 changes: 0 additions & 13 deletions web/app/_components/HistoryItem/index.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
import React from 'react'
import { useAtomValue, useSetAtom } from 'jotai'
import Image from 'next/image'
import { ModelManagementService } from '@janhq/core'
import { executeSerial } from '../../../../electron/core/plugin-manager/execution/extension-manager'
import {
getActiveConvoIdAtom,
setActiveConvoIdAtom,
updateConversationErrorAtom,
updateConversationWaitingForResponseAtom,
} from '@helpers/atoms/Conversation.atom'
import {
setMainViewStateAtom,
MainViewState,
} from '@helpers/atoms/MainView.atom'
import useInitModel from '@hooks/useInitModel'
import { displayDate } from '@utils/datetime'
import { twMerge } from 'tailwind-merge'

Expand All @@ -36,25 +33,15 @@ const HistoryItem: React.FC<Props> = ({
const activeConvoId = useAtomValue(getActiveConvoIdAtom)
const setActiveConvoId = useSetAtom(setActiveConvoIdAtom)
const updateConvWaiting = useSetAtom(updateConversationWaitingForResponseAtom)
const updateConvError = useSetAtom(updateConversationErrorAtom)
const isSelected = activeConvoId === conversation._id

const { initModel } = useInitModel()

const onClick = async () => {
const model = await executeSerial(
ModelManagementService.GetModelById,
conversation.modelId
)

if (conversation._id) updateConvWaiting(conversation._id, true)
initModel(model).then((res: any) => {
if (conversation._id) updateConvWaiting(conversation._id, false)

if (res?.error && conversation._id) {
updateConvError(conversation._id, res.error)
}
})

if (activeConvoId !== conversation._id) {
setMainViewState(MainViewState.Conversation)
Expand Down
8 changes: 3 additions & 5 deletions web/app/_components/InputToolbar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@
import BasicPromptInput from '../BasicPromptInput'
import BasicPromptAccessories from '../BasicPromptAccessories'
import { useAtomValue, useSetAtom } from 'jotai'
import { showingAdvancedPromptAtom } from '@helpers/atoms/Modal.atom'
import SecondaryButton from '../SecondaryButton'
import { Fragment, useEffect, useState } from 'react'
import { useEffect, useState } from 'react'
import { PlusIcon } from '@heroicons/react/24/outline'
import useCreateConversation from '@hooks/useCreateConversation'
import { activeAssistantModelAtom } from '@helpers/atoms/Model.atom'
Expand All @@ -19,7 +18,6 @@ import { activeBotAtom } from '@helpers/atoms/Bot.atom'
import { useGetDownloadedModels } from '@hooks/useGetDownloadedModels'

const InputToolbar: React.FC = () => {
const showingAdvancedPrompt = useAtomValue(showingAdvancedPromptAtom)
const activeModel = useAtomValue(activeAssistantModelAtom)
const { requestCreateConvo } = useCreateConversation()
const currentConvoState = useAtomValue(currentConvoStateAtom)
Expand Down Expand Up @@ -76,15 +74,15 @@ const InputToolbar: React.FC = () => {

if (inputState === 'disabled')
return (
<div className="bg-background/90 sticky bottom-0 flex items-center justify-center">
<div className="sticky bottom-0 flex items-center justify-center bg-background/90">
<p className="mx-auto my-5 line-clamp-2 text-ellipsis text-center italic text-gray-600">
{error}
</p>
</div>
)

return (
<div className="bg-background/90 sticky bottom-0 w-full px-5 py-0">
<div className="sticky bottom-0 w-full bg-background/90 px-5 py-0">
{currentConvoState?.error && (
<div className="flex flex-row justify-center">
<span className="mx-5 my-2 text-sm text-red-500">
Expand Down
3 changes: 3 additions & 0 deletions web/app/_components/ModelActionButton/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,14 @@ const modelActionMapper: Record<ModelActionType, ModelActionStyle> = {
}

type Props = {
disabled?: boolean
type: ModelActionType
onActionClick: (type: ModelActionType) => void
onDeleteClick: () => void
}

const ModelActionButton: React.FC<Props> = ({
disabled = false,
type,
onActionClick,
onDeleteClick,
Expand All @@ -48,6 +50,7 @@ const ModelActionButton: React.FC<Props> = ({
<div className="flex items-center justify-end gap-x-4">
<ModelActionMenu onDeleteClick={onDeleteClick} />
<Button
disabled={disabled}
size="sm"
themes={styles.title === 'Start' ? 'accent' : 'default'}
onClick={() => onClick()}
Expand Down
3 changes: 2 additions & 1 deletion web/app/_components/ModelRow/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ type Props = {
}

const ModelRow: React.FC<Props> = ({ model }) => {
const { startModel, stopModel } = useStartStopModel()
const { loading, startModel, stopModel } = useStartStopModel()
const activeModel = useAtomValue(activeAssistantModelAtom)
const { deleteModel } = useDeleteModel()

Expand Down Expand Up @@ -57,6 +57,7 @@ const ModelRow: React.FC<Props> = ({ model }) => {
<ModelStatusComponent status={status} />
</td>
<ModelActionButton
disabled={loading}
type={actionButtonType}
onActionClick={onModelActionClick}
onDeleteClick={onDeleteClick}
Expand Down
20 changes: 5 additions & 15 deletions web/app/_components/SidebarEmptyHistory/index.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
import useCreateConversation from '@hooks/useCreateConversation'
import PrimaryButton from '../PrimaryButton'
import { useAtomValue, useSetAtom } from 'jotai'
import { useEffect, useState } from 'react'
import {
MainViewState,
setMainViewStateAtom,
} from '@helpers/atoms/MainView.atom'
import { activeAssistantModelAtom } from '@helpers/atoms/Model.atom'
import useInitModel from '@hooks/useInitModel'
import { useGetDownloadedModels } from '@hooks/useGetDownloadedModels'
import { Button } from '@uikit'

import {MessageCircle} from "lucide-react"
import { MessageCircle } from 'lucide-react'

enum ActionButton {
DownloadModel = 'Download a Model',
Expand All @@ -25,8 +22,6 @@ const SidebarEmptyHistory: React.FC = () => {
const { requestCreateConvo } = useCreateConversation()
const [action, setAction] = useState(ActionButton.DownloadModel)

const { initModel } = useInitModel()

useEffect(() => {
if (downloadedModels.length > 0) {
setAction(ActionButton.StartChat)
Expand All @@ -35,32 +30,27 @@ const SidebarEmptyHistory: React.FC = () => {
}
}, [downloadedModels])

const onClick = () => {
const onClick = async () => {
if (action === ActionButton.DownloadModel) {
setMainView(MainViewState.ExploreModel)
} else {
if (!activeModel) {
setMainView(MainViewState.ConversationEmptyModel)
} else {
createConversationAndInitModel(activeModel)
await requestCreateConvo(activeModel)
}
}
}

const createConversationAndInitModel = async (model: AssistantModel) => {
await requestCreateConvo(model)
await initModel(model)
}

return (
<div className="flex flex-col items-center gap-3 py-10">
<MessageCircle size={32} />
<div className="flex flex-col items-center gap-y-2">
<h6 className="text-center text-base">No Chat History</h6>
<p className="text-center text-muted-foreground mb-6">
<p className="mb-6 text-center text-muted-foreground">
Get started by creating a new chat.
</p>
<Button onClick={onClick} themes="accent" >
<Button onClick={onClick} themes="accent">
{action}
</Button>
</div>
Expand Down
24 changes: 22 additions & 2 deletions web/helpers/atoms/ChatMessage.atom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,34 @@ import { getActiveConvoIdAtom } from './Conversation.atom'
/**
* Stores all chat messages for all conversations
*/
export const chatMessages = atom<Record<string, ChatMessage[]>>({})
const chatMessages = atom<Record<string, ChatMessage[]>>({})

export const currentChatMessagesAtom = atom<ChatMessage[]>((get) => {
/**
* Return the chat messages for the current active conversation
*/
export const getCurrentChatMessagesAtom = atom<ChatMessage[]>((get) => {
const activeConversationId = get(getActiveConvoIdAtom)
if (!activeConversationId) return []
return get(chatMessages)[activeConversationId] ?? []
})

export const setCurrentChatMessagesAtom = atom(
null,
(get, set, messages: ChatMessage[]) => {
const currentConvoId = get(getActiveConvoIdAtom)
if (!currentConvoId) return

const newData: Record<string, ChatMessage[]> = {
...get(chatMessages),
}
newData[currentConvoId] = messages
set(chatMessages, newData)
}
)

/**
* Used for pagination. Add old messages to the current conversation
*/
export const addOldMessagesAtom = atom(
null,
(get, set, newMessages: ChatMessage[]) => {
Expand Down
Loading

0 comments on commit 4023260

Please sign in to comment.