Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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