fix: download now change state immediately (#475)

* fix: download now change state immediately

Signed-off-by: James <james@jan.ai>

* fix: add back last message

Signed-off-by: James <james@jan.ai>

* update send button status

Signed-off-by: James <james@jan.ai>

---------

Signed-off-by: James <james@jan.ai>
Co-authored-by: James <james@jan.ai>
This commit is contained in:
NamH 2023-10-27 00:10:47 -07:00 committed by GitHub
parent 7b7d2db131
commit 0bd52e58ec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 120 additions and 30 deletions

View File

@ -6,6 +6,7 @@ export enum EventName {
OnNewMessageRequest = "onNewMessageRequest",
OnNewMessageResponse = "onNewMessageResponse",
OnMessageResponseUpdate = "onMessageResponseUpdate",
OnMessageResponseFinished = "OnMessageResponseFinished",
OnDownloadUpdate = "onDownloadUpdate",
OnDownloadSuccess = "onDownloadSuccess",
OnDownloadError = "onDownloadError",

View File

@ -140,6 +140,8 @@ async function handleMessageRequest(data: NewMessageRequest) {
message.message = message.message.trim();
// TODO: Common collections should be able to access via core functions instead of store
await store.updateOne("messages", message._id, message);
events.emit("OnMessageResponseFinished", message);
// events.emit(EventName.OnMessageResponseFinished, message);
},
error: async (err) => {
message.message =

View File

@ -1,7 +1,5 @@
import SimpleTag from '../SimpleTag'
import PrimaryButton from '../PrimaryButton'
import { formatDownloadPercentage, toGigabytes } from '@utils/converter'
import SecondaryButton from '../SecondaryButton'
import { useCallback, useEffect, useMemo } from 'react'
import useGetPerformanceTag from '@hooks/useGetPerformanceTag'
import useDownloadModel from '@hooks/useDownloadModel'
@ -58,7 +56,6 @@ const ExploreModelItemHeader: React.FC<Props> = ({
if (isDownloaded) {
downloadButton = (
<Button
size="sm"
themes="accent"
onClick={() => {
setMainViewState(MainViewState.MyModel)
@ -72,17 +69,20 @@ const ExploreModelItemHeader: React.FC<Props> = ({
if (downloadState != null) {
// downloading
downloadButton = (
<SecondaryButton
<Button
disabled
title={`Downloading (${formatDownloadPercentage(
downloadState.percent
)})`}
/>
themes="accent"
onClick={() => {
setMainViewState(MainViewState.MyModel)
}}
>
Downloading {formatDownloadPercentage(downloadState.percent)}
</Button>
)
}
return (
<div className="border-border bg-background/50 flex items-center justify-between rounded-t-md border-b px-4 py-2">
<div className="flex items-center justify-between rounded-t-md border-b border-border bg-background/50 px-4 py-2">
<div className="flex items-center gap-2">
<span>{exploreModel.name}</span>
{performanceTag && (

View File

@ -3,7 +3,6 @@ import { useAtomValue, useSetAtom } from 'jotai'
import {
getActiveConvoIdAtom,
setActiveConvoIdAtom,
updateConversationWaitingForResponseAtom,
} from '@helpers/atoms/Conversation.atom'
import {
setMainViewStateAtom,
@ -12,7 +11,6 @@ import {
import { displayDate } from '@utils/datetime'
import { twMerge } from 'tailwind-merge'
import { activeAssistantModelAtom } from '@helpers/atoms/Model.atom'
import { switchingModelConfirmationModalPropsAtom } from '@helpers/atoms/Modal.atom'
import useStartStopModel from '@hooks/useStartStopModel'
import useGetModelById from '@hooks/useGetModelById'
@ -37,10 +35,6 @@ const HistoryItem: React.FC<Props> = ({
const setMainViewState = useSetAtom(setMainViewStateAtom)
const setActiveConvoId = useSetAtom(setActiveConvoIdAtom)
const updateConvWaiting = useSetAtom(updateConversationWaitingForResponseAtom)
const setConfirmationModalProps = useSetAtom(
switchingModelConfirmationModalPropsAtom
)
const onClick = async () => {
if (conversation.modelId == null) {
@ -55,14 +49,13 @@ const HistoryItem: React.FC<Props> = ({
startModel(model._id)
} else if (activeModel._id !== model._id) {
// display confirmation modal
setConfirmationModalProps({
replacingModel: model,
})
// TODO: temporarily disabled
// setConfirmationModalProps({
// replacingModel: model,
// })
}
}
if (conversation._id) updateConvWaiting(conversation._id, true)
if (activeConvoId !== conversation._id) {
setMainViewState(MainViewState.Conversation)
setActiveConvoId(conversation._id)

View File

@ -82,11 +82,11 @@ const SwitchingModelConfirmationModal: React.FC = () => {
<p className="text-sm text-gray-500">
Selected conversation is using model{' '}
<span className="font-semibold text-black">
{props?.replacingModel._id}
{props?.replacingModel.name}
</span>
, but the active model is using{' '}
<span className="font-semibold text-black">
{activeModel?._id}
{activeModel?.name}
</span>
.
</p>
@ -95,7 +95,7 @@ const SwitchingModelConfirmationModal: React.FC = () => {
Switch to
<span className="font-semibold text-black">
{' '}
{props?.replacingModel._id}?
{props?.replacingModel.name}?
</span>
</p>
</div>

View File

@ -1,17 +1,35 @@
import { addNewMessageAtom, updateMessageAtom } from './atoms/ChatMessage.atom'
import { toChatMessage } from '@models/ChatMessage'
import { events, EventName, NewMessageResponse } from '@janhq/core'
import { events, EventName, NewMessageResponse, DataService } from '@janhq/core'
import { useSetAtom } from 'jotai'
import { ReactNode, useEffect } from 'react'
import useGetBots from '@hooks/useGetBots'
import useGetUserConversations from '@hooks/useGetUserConversations'
import {
updateConversationAtom,
updateConversationWaitingForResponseAtom,
} from './atoms/Conversation.atom'
import { executeSerial } from '../../electron/core/plugin-manager/execution/extension-manager'
import { debounce } from 'lodash'
let currentConversation: Conversation | undefined = undefined
const debouncedUpdateConversation = debounce(
async (updatedConv: Conversation) => {
await executeSerial(DataService.UpdateConversation, updatedConv)
},
1000
)
export default function EventHandler({ children }: { children: ReactNode }) {
const addNewMessage = useSetAtom(addNewMessageAtom)
const updateMessage = useSetAtom(updateMessageAtom)
const updateConversation = useSetAtom(updateConversationAtom)
const { getBotById } = useGetBots()
const { getConversationById } = useGetUserConversations()
const updateConvWaiting = useSetAtom(updateConversationWaitingForResponseAtom)
async function handleNewMessageResponse(message: NewMessageResponse) {
if (message.conversationId) {
const convo = await getConversationById(message.conversationId)
@ -34,18 +52,51 @@ export default function EventHandler({ children }: { children: ReactNode }) {
messageResponse.conversationId &&
messageResponse._id &&
messageResponse.message
)
) {
updateMessage(
messageResponse._id,
messageResponse.conversationId,
messageResponse.message
)
}
if (messageResponse.conversationId) {
if (
!currentConversation ||
currentConversation._id !== messageResponse.conversationId
) {
currentConversation = await getConversationById(
messageResponse.conversationId
)
}
const updatedConv: Conversation = {
...currentConversation,
lastMessage: messageResponse.message,
}
updateConversation(updatedConv)
debouncedUpdateConversation(updatedConv)
}
}
async function handleMessageResponseFinished(
messageResponse: NewMessageResponse
) {
if (!messageResponse.conversationId) return
console.debug('handleMessageResponseFinished', messageResponse)
updateConvWaiting(messageResponse.conversationId, false)
}
useEffect(() => {
if (window.corePlugin.events) {
events.on(EventName.OnNewMessageResponse, handleNewMessageResponse)
events.on(EventName.OnMessageResponseUpdate, handleMessageResponseUpdate)
events.on(
"OnMessageResponseFinished",
// EventName.OnMessageResponseFinished,
handleMessageResponseFinished
)
}
}, [])
@ -53,6 +104,11 @@ export default function EventHandler({ children }: { children: ReactNode }) {
return () => {
events.off(EventName.OnNewMessageResponse, handleNewMessageResponse)
events.off(EventName.OnMessageResponseUpdate, handleMessageResponseUpdate)
events.off(
"OnMessageResponseFinished",
// EventName.OnMessageResponseFinished,
handleMessageResponseFinished
)
}
}, [])
return <>{children}</>

View File

@ -1,7 +1,11 @@
import { executeSerial } from '@services/pluginService'
import { DataService, ModelManagementService } from '@janhq/core'
import { ModelManagementService } from '@janhq/core'
import { useSetAtom } from 'jotai'
import { setDownloadStateAtom } from '@helpers/atoms/DownloadState.atom'
export default function useDownloadModel() {
const setDownloadState = useSetAtom(setDownloadStateAtom)
const assistanModel = (
model: Product,
modelVersion: ModelVersion
@ -37,6 +41,22 @@ export default function useDownloadModel() {
}
const downloadModel = async (model: Product, modelVersion: ModelVersion) => {
// set an initial download state
setDownloadState({
modelId: modelVersion._id,
time: {
elapsed: 0,
remaining: 0,
},
speed: 0,
percent: 0,
size: {
total: 0,
transferred: 0,
},
fileName: modelVersion._id,
})
modelVersion.startDownloadAt = Date.now()
const assistantModel = assistanModel(model, modelVersion)
await executeSerial(ModelManagementService.StoreModel, assistantModel)

View File

@ -13,13 +13,14 @@ import { addNewMessageAtom } from '@helpers/atoms/ChatMessage.atom'
import {
currentConversationAtom,
updateConversationAtom,
updateConversationWaitingForResponseAtom,
} from '@helpers/atoms/Conversation.atom'
export default function useSendChatMessage() {
const currentConvo = useAtomValue(currentConversationAtom)
const addNewMessage = useSetAtom(addNewMessageAtom)
const updateConversation = useSetAtom(updateConversationAtom)
const updateConvWaiting = useSetAtom(updateConversationWaitingForResponseAtom)
const [currentPrompt, setCurrentPrompt] = useAtom(currentPromptAtom)
let timeout: any | undefined = undefined
@ -60,10 +61,15 @@ export default function useSendChatMessage() {
}
const sendChatMessage = async () => {
const convoId = currentConvo?._id
if (!convoId) return
setCurrentPrompt('')
updateConvWaiting(convoId, true)
const prompt = currentPrompt.trim()
const newMessage: RawMessage = {
conversationId: currentConvo?._id,
conversationId: convoId,
message: prompt,
user: 'user',
createdAt: new Date().toISOString(),
@ -77,10 +83,20 @@ export default function useSendChatMessage() {
events.emit(EventName.OnNewMessageRequest, newMessage)
if (!currentConvo?.summary && currentConvo) {
const updatedConv = {
const updatedConv: Conversation = {
...currentConvo,
lastMessage: prompt,
summary: `Prompt: ${prompt}`,
}
updateConversation(updatedConv)
await executeSerial(DataService.UpdateConversation, updatedConv)
} else {
const updatedConv: Conversation = {
...currentConvo,
lastMessage: prompt,
}
updateConversation(updatedConv)
await executeSerial(DataService.UpdateConversation, updatedConv)
}

View File

@ -28,10 +28,10 @@
"eslint-config-next": "13.4.10",
"framer-motion": "^10.16.4",
"highlight.js": "^11.9.0",
"react-intersection-observer": "^9.5.2",
"jotai": "^2.4.0",
"jotai-optics": "^0.3.1",
"jwt-decode": "^3.1.2",
"lodash": "^4.17.21",
"lucide-react": "^0.288.0",
"marked": "^9.1.2",
"marked-highlight": "^2.0.6",
@ -42,6 +42,7 @@
"react": "18.2.0",
"react-dom": "18.2.0",
"react-hook-form": "^7.45.4",
"react-intersection-observer": "^9.5.2",
"sass": "^1.69.4",
"tailwind-merge": "^1.14.0",
"tailwindcss": "3.3.3",
@ -50,6 +51,7 @@
},
"devDependencies": {
"@tailwindcss/forms": "^0.5.4",
"@types/lodash": "^4.14.200",
"@types/node": "20.6.5",
"@types/uuid": "^9.0.6",
"encoding": "^0.1.13",