Skip to content

Commit

Permalink
fix(Thread): #1042 allow create new thread by clicking Use in Jan Hub
Browse files Browse the repository at this point in the history
Signed-off-by: James <[email protected]>
  • Loading branch information
James committed Dec 19, 2023
1 parent 7a148ea commit 5bb8324
Show file tree
Hide file tree
Showing 11 changed files with 168 additions and 119 deletions.
2 changes: 1 addition & 1 deletion extensions/conversational-extension/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export default class JSONConversationalExtension
convos.sort(
(a, b) => new Date(b.updated).getTime() - new Date(a.updated).getTime()
)
console.debug('getThreads', JSON.stringify(convos, null, 2))

return convos
} catch (error) {
console.error(error)
Expand Down
10 changes: 5 additions & 5 deletions extensions/inference-nitro-extension/src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ async function loadModel(nitroResourceProbe: any | undefined) {
.then(() => loadLLMModel(currentSettings))
.then(validateModelStatus)
.catch((err) => {
console.log("error: ", err);
console.error("error: ", err);
// TODO: Broadcast error so app could display proper error message
return { error: err, currentModelFile };
});
Expand Down Expand Up @@ -172,7 +172,7 @@ async function validateModelStatus(): Promise<ModelOperationResponse> {
async function killSubprocess(): Promise<void> {
const controller = new AbortController();
setTimeout(() => controller.abort(), 5000);
console.log("Start requesting to kill Nitro...");
console.debug("Start requesting to kill Nitro...");
return fetch(NITRO_HTTP_KILL_URL, {
method: "DELETE",
signal: controller.signal,
Expand All @@ -183,15 +183,15 @@ async function killSubprocess(): Promise<void> {
})
.catch(() => {})
.then(() => tcpPortUsed.waitUntilFree(PORT, 300, 5000))
.then(() => console.log("Nitro is killed"));
.then(() => console.debug("Nitro is killed"));
}
/**
* Look for the Nitro binary and execute it
* Using child-process to spawn the process
* Should run exactly platform specified Nitro binary version
*/
function spawnNitroProcess(nitroResourceProbe: any): Promise<any> {
console.log("Starting Nitro subprocess...");
console.debug("Starting Nitro subprocess...");
return new Promise(async (resolve, reject) => {
let binaryFolder = path.join(__dirname, "bin"); // Current directory by default
let binaryName;
Expand Down Expand Up @@ -221,7 +221,7 @@ function spawnNitroProcess(nitroResourceProbe: any): Promise<any> {
});

subprocess.stderr.on("data", (data) => {
console.log("subprocess error:" + data.toString());
console.error("subprocess error:" + data.toString());
console.error(`stderr: ${data}`);
});

Expand Down
21 changes: 15 additions & 6 deletions web/hooks/useCreateNewThread.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@ import {
Thread,
ThreadAssistantInfo,
ThreadState,
Model,
} from '@janhq/core'
import { atom, useAtomValue, useSetAtom } from 'jotai'

import { generateThreadId } from '@/utils/thread'

import useDeleteThread from './useDeleteThread'

import { extensionManager } from '@/extension'
import {
threadsAtom,
Expand Down Expand Up @@ -46,27 +49,33 @@ export const useCreateNewThread = () => {
setThreadModelRuntimeParamsAtom
)

const requestCreateNewThread = async (assistant: Assistant) => {
const { deleteThread } = useDeleteThread()

const requestCreateNewThread = async (
assistant: Assistant,
model?: Model | undefined
) => {
// loop through threads state and filter if there's any thread that is not finish init
let hasUnfinishedInitThread = false
let unfinishedInitThreadId: string | undefined = undefined
for (const key in threadStates) {
const isFinishInit = threadStates[key].isFinishInit ?? true
if (!isFinishInit) {
hasUnfinishedInitThread = true
unfinishedInitThreadId = key
break
}
}

if (hasUnfinishedInitThread) {
return
if (unfinishedInitThreadId) {
await deleteThread(unfinishedInitThreadId)
}

const modelId = model ? model.id : '*'
const createdAt = Date.now()
const assistantInfo: ThreadAssistantInfo = {
assistant_id: assistant.id,
assistant_name: assistant.name,
model: {
id: '*',
id: modelId,
settings: {},
parameters: {
stream: true,
Expand Down
29 changes: 21 additions & 8 deletions web/hooks/useDeleteThread.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { ChatCompletionRole, ExtensionType } from '@janhq/core'
import { ConversationalExtension } from '@janhq/core'
import {
ChatCompletionRole,
ExtensionType,
ConversationalExtension,
} from '@janhq/core'

import { useAtom, useAtomValue, useSetAtom } from 'jotai'

import { currentPromptAtom } from '@/containers/Providers/Jotai'
Expand All @@ -19,6 +23,7 @@ import {
threadsAtom,
setActiveThreadIdAtom,
deleteThreadStateAtom,
threadStatesAtom,
} from '@/helpers/atoms/Thread.atom'

export default function useDeleteThread() {
Expand All @@ -32,6 +37,8 @@ export default function useDeleteThread() {
const cleanMessages = useSetAtom(cleanChatMessagesAtom)
const deleteThreadState = useSetAtom(deleteThreadStateAtom)

const threadStates = useAtomValue(threadStatesAtom)

const cleanThread = async (threadId: string) => {
if (threadId) {
const thread = threads.filter((c) => c.id === threadId)[0]
Expand Down Expand Up @@ -59,15 +66,21 @@ export default function useDeleteThread() {
const availableThreads = threads.filter((c) => c.id !== threadId)
setThreads(availableThreads)

const deletingThreadState = threadStates[threadId]
const isFinishInit = deletingThreadState?.isFinishInit ?? true

// delete the thread state
deleteThreadState(threadId)

deleteMessages(threadId)
setCurrentPrompt('')
toaster({
title: 'Thread successfully deleted.',
description: `Thread with ${activeModel?.name} has been successfully deleted.`,
})
if (isFinishInit) {
deleteMessages(threadId)
setCurrentPrompt('')
toaster({
title: 'Thread successfully deleted.',
description: `Thread with ${activeModel?.name} has been successfully deleted.`,
})
}

if (availableThreads.length > 0) {
setActiveThreadId(availableThreads[0].id)
} else {
Expand Down
58 changes: 0 additions & 58 deletions web/hooks/useGetAllThreads.ts

This file was deleted.

11 changes: 4 additions & 7 deletions web/hooks/useGetAssistants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,10 @@ import { Assistant, ExtensionType, AssistantExtension } from '@janhq/core'

import { extensionManager } from '@/extension/ExtensionManager'

export const getAssistants = async (): Promise<Assistant[]> => {
return (
extensionManager
.get<AssistantExtension>(ExtensionType.Assistant)
?.getAssistants() ?? []
)
}
export const getAssistants = async (): Promise<Assistant[]> =>
extensionManager
.get<AssistantExtension>(ExtensionType.Assistant)
?.getAssistants() ?? []

/**
* Hooks for get assistants
Expand Down
11 changes: 11 additions & 0 deletions web/hooks/useRecommendedModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,17 @@ export default function useRecommendedModel() {
}

return
} else {
const modelId = activeThread.assistants[0]?.model.id
if (modelId !== '*') {
const models = await getAndSortDownloadedModels()
const model = models.find((model) => model.id === modelId)

if (model) {
setRecommendedModel(model)
}
return
}
}

if (activeModel) {
Expand Down
95 changes: 95 additions & 0 deletions web/hooks/useThreads.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import {
ExtensionType,
ModelRuntimeParams,
Thread,
ThreadState,
} from '@janhq/core'
import { ConversationalExtension } from '@janhq/core'
import { useAtom } from 'jotai'

import { extensionManager } from '@/extension/ExtensionManager'
import {
threadModelRuntimeParamsAtom,
threadStatesAtom,
threadsAtom,
} from '@/helpers/atoms/Thread.atom'

const useThreads = () => {
const [threadStates, setThreadStates] = useAtom(threadStatesAtom)
const [threads, setThreads] = useAtom(threadsAtom)
const [threadModelRuntimeParams, setThreadModelRuntimeParams] = useAtom(
threadModelRuntimeParamsAtom
)

const getThreads = async () => {
try {
const localThreads = await getLocalThreads()
const localThreadStates: Record<string, ThreadState> = {}
const threadModelParams: Record<string, ModelRuntimeParams> = {}

localThreads.forEach((thread) => {
if (thread.id != null) {
const lastMessage = (thread.metadata?.lastMessage as string) ?? ''

localThreadStates[thread.id] = {
hasMore: false,
waitingForResponse: false,
lastMessage,
isFinishInit: true,
}

// model params
const modelParams = thread.assistants?.[0]?.model?.parameters
threadModelParams[thread.id] = modelParams
}
})

// allow at max 1 unfinished init thread and it should be at the top of the list
let unfinishedThreadId: string | undefined = undefined
const unfinishedThreadState: Record<string, ThreadState> = {}

for (const key of Object.keys(threadStates)) {
const threadState = threadStates[key]
if (threadState.isFinishInit === false) {
unfinishedThreadState[key] = threadState
unfinishedThreadId = key
break
}
}
const unfinishedThread: Thread | undefined = threads.find(
(thread) => thread.id === unfinishedThreadId
)

let allThreads: Thread[] = [...localThreads]
if (unfinishedThread) {
allThreads = [unfinishedThread, ...localThreads]
}

if (unfinishedThreadId) {
localThreadStates[unfinishedThreadId] =
unfinishedThreadState[unfinishedThreadId]

threadModelParams[unfinishedThreadId] =
threadModelRuntimeParams[unfinishedThreadId]
}

// updating app states
setThreadStates(localThreadStates)
setThreads(allThreads)
setThreadModelRuntimeParams(threadModelParams)
} catch (error) {
console.error(error)
}
}

return {
getAllThreads: getThreads,
}
}

const getLocalThreads = async (): Promise<Thread[]> =>
(await extensionManager
.get<ConversationalExtension>(ExtensionType.Conversational)
?.getThreads()) ?? []

export default useThreads
5 changes: 3 additions & 2 deletions web/screens/Chat/ThreadList/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@ import { twMerge } from 'tailwind-merge'

import { useCreateNewThread } from '@/hooks/useCreateNewThread'
import useDeleteThread from '@/hooks/useDeleteThread'
import useGetAllThreads from '@/hooks/useGetAllThreads'

import useGetAssistants from '@/hooks/useGetAssistants'
import { useGetDownloadedModels } from '@/hooks/useGetDownloadedModels'
import useSetActiveThread from '@/hooks/useSetActiveThread'

import useThreads from '@/hooks/useThreads'

import { displayDate } from '@/utils/datetime'

import {
Expand All @@ -30,7 +31,7 @@ import {
export default function ThreadList() {
const threads = useAtomValue(threadsAtom)
const threadStates = useAtomValue(threadStatesAtom)
const { getAllThreads } = useGetAllThreads()
const { getAllThreads } = useThreads()
const { assistants } = useGetAssistants()
const { requestCreateNewThread } = useCreateNewThread()
const activeThread = useAtomValue(activeThreadAtom)
Expand Down
6 changes: 2 additions & 4 deletions web/screens/Chat/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,14 +75,12 @@ const ChatScreen = () => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [waitingToSendMessage, activeThreadId])

const resizeTextArea = () => {
useEffect(() => {
if (textareaRef.current) {
textareaRef.current.style.height = '40px'
textareaRef.current.style.height = textareaRef.current.scrollHeight + 'px'
}
}

useEffect(resizeTextArea, [currentPrompt])
}, [currentPrompt])

const onKeyDown = async (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (e.key === 'Enter') {
Expand Down
Loading

0 comments on commit 5bb8324

Please sign in to comment.