From 01fec497988f10b25f8f4ea18deb2d875eeb57a5 Mon Sep 17 00:00:00 2001 From: NamH Date: Mon, 5 Feb 2024 13:13:39 +0700 Subject: [PATCH] fix: reduce the number of api call (#1896) Signed-off-by: James Co-authored-by: James --- core/src/node/api/routes/common.ts | 11 +- core/src/node/api/routes/download.ts | 68 +++--- .../conversational-extension/src/index.ts | 43 ++-- web/containers/Layout/BottomBar/index.tsx | 6 +- .../CommandListDownloadedModel/index.tsx | 6 +- web/containers/Layout/TopBar/index.tsx | 11 +- web/containers/Providers/DataLoader.tsx | 21 ++ web/containers/Providers/EventHandler.tsx | 6 +- web/containers/Providers/EventListener.tsx | 10 +- web/containers/Providers/index.tsx | 6 +- web/helpers/atoms/Assistant.atom.ts | 4 + web/helpers/atoms/Model.atom.ts | 4 + web/hooks/useActiveModel.ts | 4 +- web/hooks/useAssistants.ts | 28 +++ web/hooks/useDeleteModel.ts | 7 +- web/hooks/useGetAssistants.ts | 27 --- web/hooks/useGetConfiguredModels.ts | 30 --- web/hooks/useGetDownloadedModels.ts | 27 --- web/hooks/useGetSystemResources.ts | 2 +- web/hooks/useModels.ts | 46 ++++ web/hooks/useRecommendedModel.ts | 15 +- web/hooks/useSetActiveThread.ts | 61 ++--- web/hooks/useThreads.ts | 26 +- web/screens/Chat/ChatBody/index.tsx | 6 +- web/screens/Chat/CleanThreadModal/index.tsx | 65 +++++ web/screens/Chat/DeleteThreadModal/index.tsx | 68 ++++++ .../Chat/RequestDownloadModel/index.tsx | 7 +- web/screens/Chat/ThreadList/index.tsx | 224 ++++-------------- .../ExploreModelItemHeader/index.tsx | 8 +- .../ExploreModels/ModelVersionItem/index.tsx | 6 +- web/screens/ExploreModels/index.tsx | 26 +- web/screens/Settings/Models/index.tsx | 7 +- 32 files changed, 467 insertions(+), 419 deletions(-) create mode 100644 web/containers/Providers/DataLoader.tsx create mode 100644 web/helpers/atoms/Assistant.atom.ts create mode 100644 web/hooks/useAssistants.ts delete mode 100644 web/hooks/useGetAssistants.ts delete mode 100644 web/hooks/useGetConfiguredModels.ts delete mode 100644 web/hooks/useGetDownloadedModels.ts create mode 100644 web/hooks/useModels.ts create mode 100644 web/screens/Chat/CleanThreadModal/index.tsx create mode 100644 web/screens/Chat/DeleteThreadModal/index.tsx diff --git a/core/src/node/api/routes/common.ts b/core/src/node/api/routes/common.ts index 27385e5619..8887755fe1 100644 --- a/core/src/node/api/routes/common.ts +++ b/core/src/node/api/routes/common.ts @@ -12,6 +12,8 @@ import { import { JanApiRouteConfiguration } from '../common/configuration' import { startModel, stopModel } from '../common/startStopModel' import { ModelSettingParams } from '../../../types' +import { getJanDataFolderPath } from '../../utils' +import { normalizeFilePath } from '../../path' export const commonRouter = async (app: HttpServer) => { // Common Routes @@ -52,7 +54,14 @@ export const commonRouter = async (app: HttpServer) => { // App Routes app.post(`/app/${AppRoute.joinPath}`, async (request: any, reply: any) => { const args = JSON.parse(request.body) as any[] - reply.send(JSON.stringify(join(...args[0]))) + + const paths = args[0].map((arg: string) => + typeof arg === 'string' && (arg.startsWith(`file:/`) || arg.startsWith(`file:\\`)) + ? join(getJanDataFolderPath(), normalizeFilePath(arg)) + : arg + ) + + reply.send(JSON.stringify(join(...paths))) }) app.post(`/app/${AppRoute.baseName}`, async (request: any, reply: any) => { diff --git a/core/src/node/api/routes/download.ts b/core/src/node/api/routes/download.ts index b4e11f9578..ab8c0bd373 100644 --- a/core/src/node/api/routes/download.ts +++ b/core/src/node/api/routes/download.ts @@ -4,55 +4,55 @@ import { DownloadManager } from '../../download' import { HttpServer } from '../HttpServer' import { createWriteStream } from 'fs' import { getJanDataFolderPath } from '../../utils' -import { normalizeFilePath } from "../../path"; +import { normalizeFilePath } from '../../path' export const downloadRouter = async (app: HttpServer) => { app.post(`/${DownloadRoute.downloadFile}`, async (req, res) => { - const strictSSL = !(req.query.ignoreSSL === "true"); - const proxy = req.query.proxy?.startsWith("http") ? req.query.proxy : undefined; - const body = JSON.parse(req.body as any); + const strictSSL = !(req.query.ignoreSSL === 'true') + const proxy = req.query.proxy?.startsWith('http') ? req.query.proxy : undefined + const body = JSON.parse(req.body as any) const normalizedArgs = body.map((arg: any) => { - if (typeof arg === "string") { - return join(getJanDataFolderPath(), normalizeFilePath(arg)); + if (typeof arg === 'string' && arg.startsWith('file:')) { + return join(getJanDataFolderPath(), normalizeFilePath(arg)) } - return arg; - }); + return arg + }) - const localPath = normalizedArgs[1]; - const fileName = localPath.split("/").pop() ?? ""; + const localPath = normalizedArgs[1] + const fileName = localPath.split('/').pop() ?? '' - const request = require("request"); - const progress = require("request-progress"); + const request = require('request') + const progress = require('request-progress') - const rq = request({ url: normalizedArgs[0], strictSSL, proxy }); + const rq = request({ url: normalizedArgs[0], strictSSL, proxy }) progress(rq, {}) - .on("progress", function (state: any) { - console.log("download onProgress", state); + .on('progress', function (state: any) { + console.log('download onProgress', state) }) - .on("error", function (err: Error) { - console.log("download onError", err); + .on('error', function (err: Error) { + console.log('download onError', err) }) - .on("end", function () { - console.log("download onEnd"); + .on('end', function () { + console.log('download onEnd') }) - .pipe(createWriteStream(normalizedArgs[1])); + .pipe(createWriteStream(normalizedArgs[1])) - DownloadManager.instance.setRequest(fileName, rq); - }); + DownloadManager.instance.setRequest(fileName, rq) + }) app.post(`/${DownloadRoute.abortDownload}`, async (req, res) => { - const body = JSON.parse(req.body as any); + const body = JSON.parse(req.body as any) const normalizedArgs = body.map((arg: any) => { - if (typeof arg === "string") { - return join(getJanDataFolderPath(), normalizeFilePath(arg)); + if (typeof arg === 'string' && arg.startsWith('file:')) { + return join(getJanDataFolderPath(), normalizeFilePath(arg)) } - return arg; - }); + return arg + }) - const localPath = normalizedArgs[0]; - const fileName = localPath.split("/").pop() ?? ""; - const rq = DownloadManager.instance.networkRequests[fileName]; - DownloadManager.instance.networkRequests[fileName] = undefined; - rq?.abort(); - }); -}; + const localPath = normalizedArgs[0] + const fileName = localPath.split('/').pop() ?? '' + const rq = DownloadManager.instance.networkRequests[fileName] + DownloadManager.instance.networkRequests[fileName] = undefined + rq?.abort() + }) +} diff --git a/extensions/conversational-extension/src/index.ts b/extensions/conversational-extension/src/index.ts index 3d28a9c1d5..bf8c213add 100644 --- a/extensions/conversational-extension/src/index.ts +++ b/extensions/conversational-extension/src/index.ts @@ -12,7 +12,7 @@ import { * functionality for managing threads. */ export default class JSONConversationalExtension extends ConversationalExtension { - private static readonly _homeDir = 'file://threads' + private static readonly _threadFolder = 'file://threads' private static readonly _threadInfoFileName = 'thread.json' private static readonly _threadMessagesFileName = 'messages.jsonl' @@ -20,8 +20,8 @@ export default class JSONConversationalExtension extends ConversationalExtension * Called when the extension is loaded. */ async onLoad() { - if (!(await fs.existsSync(JSONConversationalExtension._homeDir))) - await fs.mkdirSync(JSONConversationalExtension._homeDir) + if (!(await fs.existsSync(JSONConversationalExtension._threadFolder))) + await fs.mkdirSync(JSONConversationalExtension._threadFolder) console.debug('JSONConversationalExtension loaded') } @@ -68,7 +68,7 @@ export default class JSONConversationalExtension extends ConversationalExtension async saveThread(thread: Thread): Promise { try { const threadDirPath = await joinPath([ - JSONConversationalExtension._homeDir, + JSONConversationalExtension._threadFolder, thread.id, ]) const threadJsonPath = await joinPath([ @@ -92,7 +92,7 @@ export default class JSONConversationalExtension extends ConversationalExtension */ async deleteThread(threadId: string): Promise { const path = await joinPath([ - JSONConversationalExtension._homeDir, + JSONConversationalExtension._threadFolder, `${threadId}`, ]) try { @@ -109,7 +109,7 @@ export default class JSONConversationalExtension extends ConversationalExtension async addNewMessage(message: ThreadMessage): Promise { try { const threadDirPath = await joinPath([ - JSONConversationalExtension._homeDir, + JSONConversationalExtension._threadFolder, message.thread_id, ]) const threadMessagePath = await joinPath([ @@ -177,7 +177,7 @@ export default class JSONConversationalExtension extends ConversationalExtension ): Promise { try { const threadDirPath = await joinPath([ - JSONConversationalExtension._homeDir, + JSONConversationalExtension._threadFolder, threadId, ]) const threadMessagePath = await joinPath([ @@ -205,7 +205,7 @@ export default class JSONConversationalExtension extends ConversationalExtension private async readThread(threadDirName: string): Promise { return fs.readFileSync( await joinPath([ - JSONConversationalExtension._homeDir, + JSONConversationalExtension._threadFolder, threadDirName, JSONConversationalExtension._threadInfoFileName, ]), @@ -219,14 +219,14 @@ export default class JSONConversationalExtension extends ConversationalExtension */ private async getValidThreadDirs(): Promise { const fileInsideThread: string[] = await fs.readdirSync( - JSONConversationalExtension._homeDir + JSONConversationalExtension._threadFolder ) const threadDirs: string[] = [] for (let i = 0; i < fileInsideThread.length; i++) { if (fileInsideThread[i].includes('.DS_Store')) continue const path = await joinPath([ - JSONConversationalExtension._homeDir, + JSONConversationalExtension._threadFolder, fileInsideThread[i], ]) @@ -246,7 +246,7 @@ export default class JSONConversationalExtension extends ConversationalExtension async getAllMessages(threadId: string): Promise { try { const threadDirPath = await joinPath([ - JSONConversationalExtension._homeDir, + JSONConversationalExtension._threadFolder, threadId, ]) @@ -263,22 +263,17 @@ export default class JSONConversationalExtension extends ConversationalExtension JSONConversationalExtension._threadMessagesFileName, ]) - const result = await fs - .readFileSync(messageFilePath, 'utf-8') - .then((content) => - content - .toString() - .split('\n') - .filter((line) => line !== '') - ) + let readResult = await fs.readFileSync(messageFilePath, 'utf-8') + + if (typeof readResult === 'object') { + readResult = JSON.stringify(readResult) + } + + const result = readResult.split('\n').filter((line) => line !== '') const messages: ThreadMessage[] = [] result.forEach((line: string) => { - try { - messages.push(JSON.parse(line) as ThreadMessage) - } catch (err) { - console.error(err) - } + messages.push(JSON.parse(line)) }) return messages } catch (err) { diff --git a/web/containers/Layout/BottomBar/index.tsx b/web/containers/Layout/BottomBar/index.tsx index 6e334b9ef5..7dc5a94447 100644 --- a/web/containers/Layout/BottomBar/index.tsx +++ b/web/containers/Layout/BottomBar/index.tsx @@ -26,11 +26,12 @@ import { MainViewState } from '@/constants/screens' import { useActiveModel } from '@/hooks/useActiveModel' import { useDownloadState } from '@/hooks/useDownloadState' -import { useGetDownloadedModels } from '@/hooks/useGetDownloadedModels' + import useGetSystemResources from '@/hooks/useGetSystemResources' import { useMainViewState } from '@/hooks/useMainViewState' import { serverEnabledAtom } from '@/helpers/atoms/LocalServer.atom' +import { downloadedModelsAtom } from '@/helpers/atoms/Model.atom' const menuLinks = [ { @@ -49,7 +50,8 @@ const BottomBar = () => { const { activeModel, stateModel } = useActiveModel() const { ram, cpu } = useGetSystemResources() const progress = useAtomValue(appDownloadProgress) - const { downloadedModels } = useGetDownloadedModels() + const downloadedModels = useAtomValue(downloadedModelsAtom) + const { setMainViewState } = useMainViewState() const { downloadStates } = useDownloadState() const setShowSelectModelModal = useSetAtom(showSelectModelModalAtom) diff --git a/web/containers/Layout/TopBar/CommandListDownloadedModel/index.tsx b/web/containers/Layout/TopBar/CommandListDownloadedModel/index.tsx index 3edce06eb5..ac5756e9f3 100644 --- a/web/containers/Layout/TopBar/CommandListDownloadedModel/index.tsx +++ b/web/containers/Layout/TopBar/CommandListDownloadedModel/index.tsx @@ -11,7 +11,7 @@ import { Badge, } from '@janhq/uikit' -import { useAtom } from 'jotai' +import { useAtom, useAtomValue } from 'jotai' import { DatabaseIcon, CpuIcon } from 'lucide-react' import { showSelectModelModalAtom } from '@/containers/Providers/KeyListener' @@ -19,14 +19,14 @@ import { showSelectModelModalAtom } from '@/containers/Providers/KeyListener' import { MainViewState } from '@/constants/screens' import { useActiveModel } from '@/hooks/useActiveModel' -import { useGetDownloadedModels } from '@/hooks/useGetDownloadedModels' import { useMainViewState } from '@/hooks/useMainViewState' import { serverEnabledAtom } from '@/helpers/atoms/LocalServer.atom' +import { downloadedModelsAtom } from '@/helpers/atoms/Model.atom' export default function CommandListDownloadedModel() { const { setMainViewState } = useMainViewState() - const { downloadedModels } = useGetDownloadedModels() + const downloadedModels = useAtomValue(downloadedModelsAtom) const { activeModel, startModel, stopModel } = useActiveModel() const [serverEnabled] = useAtom(serverEnabledAtom) const [showSelectModelModal, setShowSelectModelModal] = useAtom( diff --git a/web/containers/Layout/TopBar/index.tsx b/web/containers/Layout/TopBar/index.tsx index f72f5f066a..206a9013d3 100644 --- a/web/containers/Layout/TopBar/index.tsx +++ b/web/containers/Layout/TopBar/index.tsx @@ -20,7 +20,6 @@ import { MainViewState } from '@/constants/screens' import { useClickOutside } from '@/hooks/useClickOutside' import { useCreateNewThread } from '@/hooks/useCreateNewThread' -import useGetAssistants, { getAssistants } from '@/hooks/useGetAssistants' import { useMainViewState } from '@/hooks/useMainViewState' import { usePath } from '@/hooks/usePath' @@ -29,13 +28,14 @@ import { showRightSideBarAtom } from '@/screens/Chat/Sidebar' import { openFileTitle } from '@/utils/titleUtils' +import { assistantsAtom } from '@/helpers/atoms/Assistant.atom' import { activeThreadAtom } from '@/helpers/atoms/Thread.atom' const TopBar = () => { const activeThread = useAtomValue(activeThreadAtom) const { mainViewState } = useMainViewState() const { requestCreateNewThread } = useCreateNewThread() - const { assistants } = useGetAssistants() + const assistants = useAtomValue(assistantsAtom) const [showRightSideBar, setShowRightSideBar] = useAtom(showRightSideBarAtom) const [showLeftSideBar, setShowLeftSideBar] = useAtom(showLeftSideBarAtom) const showing = useAtomValue(showRightSideBarAtom) @@ -61,12 +61,7 @@ const TopBar = () => { const onCreateConversationClick = async () => { if (assistants.length === 0) { - const res = await getAssistants() - if (res.length === 0) { - alert('No assistant available') - return - } - requestCreateNewThread(res[0]) + alert('No assistant available') } else { requestCreateNewThread(assistants[0]) } diff --git a/web/containers/Providers/DataLoader.tsx b/web/containers/Providers/DataLoader.tsx new file mode 100644 index 0000000000..2b6675d98a --- /dev/null +++ b/web/containers/Providers/DataLoader.tsx @@ -0,0 +1,21 @@ +'use client' + +import { Fragment, ReactNode } from 'react' + +import useAssistants from '@/hooks/useAssistants' +import useModels from '@/hooks/useModels' +import useThreads from '@/hooks/useThreads' + +type Props = { + children: ReactNode +} + +const DataLoader: React.FC = ({ children }) => { + useModels() + useThreads() + useAssistants() + + return {children} +} + +export default DataLoader diff --git a/web/containers/Providers/EventHandler.tsx b/web/containers/Providers/EventHandler.tsx index ec0fbfc907..e9d70d5d29 100644 --- a/web/containers/Providers/EventHandler.tsx +++ b/web/containers/Providers/EventHandler.tsx @@ -18,7 +18,6 @@ import { loadModelErrorAtom, stateModelAtom, } from '@/hooks/useActiveModel' -import { useGetDownloadedModels } from '@/hooks/useGetDownloadedModels' import { queuedMessageAtom } from '@/hooks/useSendChatMessage' @@ -29,6 +28,7 @@ import { addNewMessageAtom, updateMessageAtom, } from '@/helpers/atoms/ChatMessage.atom' +import { downloadedModelsAtom } from '@/helpers/atoms/Model.atom' import { updateThreadWaitingForResponseAtom, threadsAtom, @@ -38,7 +38,7 @@ import { export default function EventHandler({ children }: { children: ReactNode }) { const addNewMessage = useSetAtom(addNewMessageAtom) const updateMessage = useSetAtom(updateMessageAtom) - const { downloadedModels } = useGetDownloadedModels() + const downloadedModels = useAtomValue(downloadedModelsAtom) const setActiveModel = useSetAtom(activeModelAtom) const setStateModel = useSetAtom(stateModelAtom) const setQueuedMessage = useSetAtom(queuedMessageAtom) @@ -143,7 +143,7 @@ export default function EventHandler({ children }: { children: ReactNode }) { ?.addNewMessage(message) } }, - [updateMessage, updateThreadWaiting] + [updateMessage, updateThreadWaiting, setIsGeneratingResponse] ) useEffect(() => { diff --git a/web/containers/Providers/EventListener.tsx b/web/containers/Providers/EventListener.tsx index 62d4cacb61..5e8556f33b 100644 --- a/web/containers/Providers/EventListener.tsx +++ b/web/containers/Providers/EventListener.tsx @@ -3,10 +3,9 @@ import { PropsWithChildren, useEffect, useRef } from 'react' import { baseName } from '@janhq/core' -import { useAtomValue, useSetAtom } from 'jotai' +import { useAtom, useAtomValue, useSetAtom } from 'jotai' import { useDownloadState } from '@/hooks/useDownloadState' -import { useGetDownloadedModels } from '@/hooks/useGetDownloadedModels' import { modelBinFileName } from '@/utils/model' @@ -14,14 +13,17 @@ import EventHandler from './EventHandler' import { appDownloadProgress } from './Jotai' -import { downloadingModelsAtom } from '@/helpers/atoms/Model.atom' +import { + downloadedModelsAtom, + downloadingModelsAtom, +} from '@/helpers/atoms/Model.atom' export default function EventListenerWrapper({ children }: PropsWithChildren) { const setProgress = useSetAtom(appDownloadProgress) const models = useAtomValue(downloadingModelsAtom) const modelsRef = useRef(models) - const { setDownloadedModels, downloadedModels } = useGetDownloadedModels() + const [downloadedModels, setDownloadedModels] = useAtom(downloadedModelsAtom) const { setDownloadState, setDownloadStateSuccess, diff --git a/web/containers/Providers/index.tsx b/web/containers/Providers/index.tsx index c8a20bca71..e7a179ec43 100644 --- a/web/containers/Providers/index.tsx +++ b/web/containers/Providers/index.tsx @@ -23,6 +23,8 @@ import Umami from '@/utils/umami' import Loader from '../Loader' +import DataLoader from './DataLoader' + import KeyListener from './KeyListener' import { extensionManager } from '@/extension' @@ -81,7 +83,9 @@ const Providers = (props: PropsWithChildren) => { - {children} + + {children} + {!isMac && } diff --git a/web/helpers/atoms/Assistant.atom.ts b/web/helpers/atoms/Assistant.atom.ts new file mode 100644 index 0000000000..e90923d3d2 --- /dev/null +++ b/web/helpers/atoms/Assistant.atom.ts @@ -0,0 +1,4 @@ +import { Assistant } from '@janhq/core/.' +import { atom } from 'jotai' + +export const assistantsAtom = atom([]) diff --git a/web/helpers/atoms/Model.atom.ts b/web/helpers/atoms/Model.atom.ts index 6eb7f2ad68..5c9188ad7b 100644 --- a/web/helpers/atoms/Model.atom.ts +++ b/web/helpers/atoms/Model.atom.ts @@ -24,3 +24,7 @@ export const removeDownloadingModelAtom = atom( ) } ) + +export const downloadedModelsAtom = atom([]) + +export const configuredModelsAtom = atom([]) diff --git a/web/hooks/useActiveModel.ts b/web/hooks/useActiveModel.ts index 54a1fdbe06..1b61a0dd19 100644 --- a/web/hooks/useActiveModel.ts +++ b/web/hooks/useActiveModel.ts @@ -3,9 +3,9 @@ import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai' import { toaster } from '@/containers/Toast' -import { useGetDownloadedModels } from './useGetDownloadedModels' import { LAST_USED_MODEL_ID } from './useRecommendedModel' +import { downloadedModelsAtom } from '@/helpers/atoms/Model.atom' import { activeThreadAtom } from '@/helpers/atoms/Thread.atom' export const activeModelAtom = atom(undefined) @@ -21,7 +21,7 @@ export function useActiveModel() { const [activeModel, setActiveModel] = useAtom(activeModelAtom) const activeThread = useAtomValue(activeThreadAtom) const [stateModel, setStateModel] = useAtom(stateModelAtom) - const { downloadedModels } = useGetDownloadedModels() + const downloadedModels = useAtomValue(downloadedModelsAtom) const setLoadModelError = useSetAtom(loadModelErrorAtom) const startModel = async (modelId: string) => { diff --git a/web/hooks/useAssistants.ts b/web/hooks/useAssistants.ts new file mode 100644 index 0000000000..8f2c4a92ca --- /dev/null +++ b/web/hooks/useAssistants.ts @@ -0,0 +1,28 @@ +import { useEffect } from 'react' + +import { Assistant, AssistantExtension, ExtensionTypeEnum } from '@janhq/core' + +import { useSetAtom } from 'jotai' + +import { extensionManager } from '@/extension' +import { assistantsAtom } from '@/helpers/atoms/Assistant.atom' + +const useAssistants = () => { + const setAssistants = useSetAtom(assistantsAtom) + + useEffect(() => { + const getAssistants = async () => { + const assistants = await getLocalAssistants() + setAssistants(assistants) + } + + getAssistants() + }, [setAssistants]) +} + +const getLocalAssistants = async (): Promise => + extensionManager + .get(ExtensionTypeEnum.Assistant) + ?.getAssistants() ?? [] + +export default useAssistants diff --git a/web/hooks/useDeleteModel.ts b/web/hooks/useDeleteModel.ts index fa0cfb45ea..d9f2b94be2 100644 --- a/web/hooks/useDeleteModel.ts +++ b/web/hooks/useDeleteModel.ts @@ -1,13 +1,14 @@ import { ExtensionTypeEnum, ModelExtension, Model } from '@janhq/core' -import { toaster } from '@/containers/Toast' +import { useAtom } from 'jotai' -import { useGetDownloadedModels } from '@/hooks/useGetDownloadedModels' +import { toaster } from '@/containers/Toast' import { extensionManager } from '@/extension/ExtensionManager' +import { downloadedModelsAtom } from '@/helpers/atoms/Model.atom' export default function useDeleteModel() { - const { setDownloadedModels, downloadedModels } = useGetDownloadedModels() + const [downloadedModels, setDownloadedModels] = useAtom(downloadedModelsAtom) const deleteModel = async (model: Model) => { await extensionManager diff --git a/web/hooks/useGetAssistants.ts b/web/hooks/useGetAssistants.ts deleted file mode 100644 index 2b34bfbd14..0000000000 --- a/web/hooks/useGetAssistants.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { useEffect, useState } from 'react' - -import { Assistant, ExtensionTypeEnum, AssistantExtension } from '@janhq/core' - -import { extensionManager } from '@/extension/ExtensionManager' - -export const getAssistants = async (): Promise => - extensionManager - .get(ExtensionTypeEnum.Assistant) - ?.getAssistants() ?? [] - -/** - * Hooks for get assistants - * - * @returns assistants - */ -export default function useGetAssistants() { - const [assistants, setAssistants] = useState([]) - - useEffect(() => { - getAssistants() - .then((data) => setAssistants(data)) - .catch((err) => console.error(err)) - }, []) - - return { assistants } -} diff --git a/web/hooks/useGetConfiguredModels.ts b/web/hooks/useGetConfiguredModels.ts deleted file mode 100644 index 8be052ae27..0000000000 --- a/web/hooks/useGetConfiguredModels.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { useCallback, useEffect, useState } from 'react' - -import { ExtensionTypeEnum, ModelExtension, Model } from '@janhq/core' - -import { extensionManager } from '@/extension/ExtensionManager' - -export function useGetConfiguredModels() { - const [loading, setLoading] = useState(false) - const [models, setModels] = useState([]) - - const fetchModels = useCallback(async () => { - setLoading(true) - const models = await getConfiguredModels() - setLoading(false) - setModels(models) - }, []) - - useEffect(() => { - fetchModels() - }, [fetchModels]) - - return { loading, models } -} - -const getConfiguredModels = async (): Promise => { - const models = await extensionManager - .get(ExtensionTypeEnum.Model) - ?.getConfiguredModels() - return models ?? [] -} diff --git a/web/hooks/useGetDownloadedModels.ts b/web/hooks/useGetDownloadedModels.ts deleted file mode 100644 index bba420858b..0000000000 --- a/web/hooks/useGetDownloadedModels.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { useEffect } from 'react' - -import { ExtensionTypeEnum, ModelExtension, Model } from '@janhq/core' - -import { atom, useAtom } from 'jotai' - -import { extensionManager } from '@/extension/ExtensionManager' - -export const downloadedModelsAtom = atom([]) - -export function useGetDownloadedModels() { - const [downloadedModels, setDownloadedModels] = useAtom(downloadedModelsAtom) - - useEffect(() => { - getDownloadedModels().then((downloadedModels) => { - setDownloadedModels(downloadedModels) - }) - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []) - - return { downloadedModels, setDownloadedModels } -} - -export const getDownloadedModels = async (): Promise => - extensionManager - .get(ExtensionTypeEnum.Model) - ?.getDownloadedModels() ?? [] diff --git a/web/hooks/useGetSystemResources.ts b/web/hooks/useGetSystemResources.ts index de595ad7b4..3429a93aa4 100644 --- a/web/hooks/useGetSystemResources.ts +++ b/web/hooks/useGetSystemResources.ts @@ -58,7 +58,7 @@ export default function useGetSystemResources() { // There is a possibility that this will be removed and replaced by the process event hook? const intervalId = setInterval(() => { getSystemResources() - }, 500) + }, 5000) // clean up interval return () => clearInterval(intervalId) diff --git a/web/hooks/useModels.ts b/web/hooks/useModels.ts new file mode 100644 index 0000000000..23e098007e --- /dev/null +++ b/web/hooks/useModels.ts @@ -0,0 +1,46 @@ +import { useEffect } from 'react' + +import { ExtensionTypeEnum, Model, ModelExtension } from '@janhq/core' + +import { useSetAtom } from 'jotai' + +import { extensionManager } from '@/extension' +import { + configuredModelsAtom, + downloadedModelsAtom, +} from '@/helpers/atoms/Model.atom' + +const useModels = () => { + const setDownloadedModels = useSetAtom(downloadedModelsAtom) + const setConfiguredModels = useSetAtom(configuredModelsAtom) + + useEffect(() => { + const getDownloadedModels = async () => { + const models = await getLocalDownloadedModels() + setDownloadedModels(models) + } + + getDownloadedModels() + }, [setDownloadedModels]) + + useEffect(() => { + const getConfiguredModels = async () => { + const models = await getLocalConfiguredModels() + setConfiguredModels(models) + } + + getConfiguredModels() + }, [setConfiguredModels]) +} + +const getLocalConfiguredModels = async (): Promise => + extensionManager + .get(ExtensionTypeEnum.Model) + ?.getConfiguredModels() ?? [] + +const getLocalDownloadedModels = async (): Promise => + extensionManager + .get(ExtensionTypeEnum.Model) + ?.getDownloadedModels() ?? [] + +export default useModels diff --git a/web/hooks/useRecommendedModel.ts b/web/hooks/useRecommendedModel.ts index 427d2bf731..8122e2b77a 100644 --- a/web/hooks/useRecommendedModel.ts +++ b/web/hooks/useRecommendedModel.ts @@ -5,9 +5,9 @@ import { Model, InferenceEngine } from '@janhq/core' import { atom, useAtomValue } from 'jotai' import { activeModelAtom } from './useActiveModel' -import { getDownloadedModels } from './useGetDownloadedModels' -import { activeThreadAtom, threadStatesAtom } from '@/helpers/atoms/Thread.atom' +import { downloadedModelsAtom } from '@/helpers/atoms/Model.atom' +import { activeThreadAtom } from '@/helpers/atoms/Thread.atom' export const lastUsedModel = atom(undefined) @@ -24,19 +24,20 @@ export const LAST_USED_MODEL_ID = 'last-used-model-id' */ export default function useRecommendedModel() { const activeModel = useAtomValue(activeModelAtom) - const [downloadedModels, setDownloadedModels] = useState([]) + const [sortedModels, setSortedModels] = useState([]) const [recommendedModel, setRecommendedModel] = useState() const activeThread = useAtomValue(activeThreadAtom) + const downloadedModels = useAtomValue(downloadedModelsAtom) const getAndSortDownloadedModels = useCallback(async (): Promise => { - const models = (await getDownloadedModels()).sort((a, b) => + const models = downloadedModels.sort((a, b) => a.engine !== InferenceEngine.nitro && b.engine === InferenceEngine.nitro ? 1 : -1 ) - setDownloadedModels(models) + setSortedModels(models) return models - }, []) + }, [downloadedModels]) const getRecommendedModel = useCallback(async (): Promise< Model | undefined @@ -98,5 +99,5 @@ export default function useRecommendedModel() { getRecommendedModel() }, [getRecommendedModel]) - return { recommendedModel, downloadedModels } + return { recommendedModel, downloadedModels: sortedModels } } diff --git a/web/hooks/useSetActiveThread.ts b/web/hooks/useSetActiveThread.ts index f5649ccaff..6cf94d45da 100644 --- a/web/hooks/useSetActiveThread.ts +++ b/web/hooks/useSetActiveThread.ts @@ -1,3 +1,5 @@ +import { useCallback } from 'react' + import { InferenceEvent, ExtensionTypeEnum, @@ -6,7 +8,7 @@ import { ConversationalExtension, } from '@janhq/core' -import { useAtomValue, useSetAtom } from 'jotai' +import { useSetAtom } from 'jotai' import { loadModelErrorAtom } from './useActiveModel' @@ -14,43 +16,46 @@ import { extensionManager } from '@/extension' import { setConvoMessagesAtom } from '@/helpers/atoms/ChatMessage.atom' import { ModelParams, - getActiveThreadIdAtom, isGeneratingResponseAtom, setActiveThreadIdAtom, setThreadModelParamsAtom, } from '@/helpers/atoms/Thread.atom' export default function useSetActiveThread() { - const activeThreadId = useAtomValue(getActiveThreadIdAtom) const setActiveThreadId = useSetAtom(setActiveThreadIdAtom) const setThreadMessage = useSetAtom(setConvoMessagesAtom) const setThreadModelParams = useSetAtom(setThreadModelParamsAtom) const setIsGeneratingResponse = useSetAtom(isGeneratingResponseAtom) const setLoadModelError = useSetAtom(loadModelErrorAtom) - const setActiveThread = async (thread: Thread) => { - if (activeThreadId === thread.id) { - console.debug('Thread already active') - return - } - - setIsGeneratingResponse(false) - setLoadModelError(undefined) - events.emit(InferenceEvent.OnInferenceStopped, thread.id) - - // load the corresponding messages - const messages = await extensionManager - .get(ExtensionTypeEnum.Conversational) - ?.getAllMessages(thread.id) - setThreadMessage(thread.id, messages ?? []) - - setActiveThreadId(thread.id) - const modelParams: ModelParams = { - ...thread.assistants[0]?.model?.parameters, - ...thread.assistants[0]?.model?.settings, - } - setThreadModelParams(thread.id, modelParams) - } - - return { activeThreadId, setActiveThread } + const setActiveThread = useCallback( + async (thread: Thread) => { + setIsGeneratingResponse(false) + events.emit(InferenceEvent.OnInferenceStopped, thread.id) + + // load the corresponding messages + const messages = await getLocalThreadMessage(thread.id) + setThreadMessage(thread.id, messages) + + setActiveThreadId(thread.id) + const modelParams: ModelParams = { + ...thread.assistants[0]?.model?.parameters, + ...thread.assistants[0]?.model?.settings, + } + setThreadModelParams(thread.id, modelParams) + }, + [ + setActiveThreadId, + setThreadMessage, + setThreadModelParams, + setIsGeneratingResponse, + ] + ) + + return { setActiveThread } } + +const getLocalThreadMessage = async (threadId: string) => + extensionManager + .get(ExtensionTypeEnum.Conversational) + ?.getAllMessages(threadId) ?? [] diff --git a/web/hooks/useThreads.ts b/web/hooks/useThreads.ts index b7de014cc0..1ac038b26a 100644 --- a/web/hooks/useThreads.ts +++ b/web/hooks/useThreads.ts @@ -1,3 +1,5 @@ +import { useEffect } from 'react' + import { ExtensionTypeEnum, Thread, @@ -5,14 +7,13 @@ import { ConversationalExtension, } from '@janhq/core' -import { useAtomValue, useSetAtom } from 'jotai' +import { useSetAtom } from 'jotai' import useSetActiveThread from './useSetActiveThread' import { extensionManager } from '@/extension/ExtensionManager' import { ModelParams, - activeThreadAtom, threadModelParamsAtom, threadStatesAtom, threadsAtom, @@ -22,11 +23,10 @@ const useThreads = () => { const setThreadStates = useSetAtom(threadStatesAtom) const setThreads = useSetAtom(threadsAtom) const setThreadModelRuntimeParams = useSetAtom(threadModelParamsAtom) - const activeThread = useAtomValue(activeThreadAtom) const { setActiveThread } = useSetActiveThread() - const getThreads = async () => { - try { + useEffect(() => { + const getThreads = async () => { const localThreads = await getLocalThreads() const localThreadStates: Record = {} const threadModelParams: Record = {} @@ -54,17 +54,19 @@ const useThreads = () => { setThreadStates(localThreadStates) setThreads(localThreads) setThreadModelRuntimeParams(threadModelParams) - if (localThreads.length && !activeThread) { + + if (localThreads.length > 0) { setActiveThread(localThreads[0]) } - } catch (error) { - console.error(error) } - } - return { - getThreads, - } + getThreads() + }, [ + setActiveThread, + setThreadModelRuntimeParams, + setThreadStates, + setThreads, + ]) } const getLocalThreads = async (): Promise => diff --git a/web/screens/Chat/ChatBody/index.tsx b/web/screens/Chat/ChatBody/index.tsx index 66f14d076e..c67d6a538a 100644 --- a/web/screens/Chat/ChatBody/index.tsx +++ b/web/screens/Chat/ChatBody/index.tsx @@ -11,7 +11,6 @@ import LogoMark from '@/containers/Brand/Logo/Mark' import { MainViewState } from '@/constants/screens' import { loadModelErrorAtom } from '@/hooks/useActiveModel' -import { useGetDownloadedModels } from '@/hooks/useGetDownloadedModels' import { useMainViewState } from '@/hooks/useMainViewState' @@ -20,10 +19,13 @@ import ChatItem from '../ChatItem' import ErrorMessage from '../ErrorMessage' import { getCurrentChatMessagesAtom } from '@/helpers/atoms/ChatMessage.atom' +import { downloadedModelsAtom } from '@/helpers/atoms/Model.atom' const ChatBody: React.FC = () => { const messages = useAtomValue(getCurrentChatMessagesAtom) - const { downloadedModels } = useGetDownloadedModels() + + const downloadedModels = useAtomValue(downloadedModelsAtom) + const { setMainViewState } = useMainViewState() if (downloadedModels.length === 0) diff --git a/web/screens/Chat/CleanThreadModal/index.tsx b/web/screens/Chat/CleanThreadModal/index.tsx new file mode 100644 index 0000000000..6ef505e6f6 --- /dev/null +++ b/web/screens/Chat/CleanThreadModal/index.tsx @@ -0,0 +1,65 @@ +import React, { useCallback } from 'react' + +import { + Button, + Modal, + ModalClose, + ModalContent, + ModalFooter, + ModalHeader, + ModalPortal, + ModalTitle, + ModalTrigger, +} from '@janhq/uikit' +import { Paintbrush } from 'lucide-react' + +import useDeleteThread from '@/hooks/useDeleteThread' + +type Props = { + threadId: string +} + +const CleanThreadModal: React.FC = ({ threadId }) => { + const { cleanThread } = useDeleteThread() + const onCleanThreadClick = useCallback( + (e: React.MouseEvent) => { + e.stopPropagation() + cleanThread(threadId) + }, + [cleanThread, threadId] + ) + + return ( + + e.stopPropagation()}> +
+ + + Clean thread + +
+
+ + + + Clean Thread + +

Are you sure you want to clean this thread?

+ +
+ e.stopPropagation()}> + + + + + +
+
+
+
+ ) +} + +export default React.memo(CleanThreadModal) diff --git a/web/screens/Chat/DeleteThreadModal/index.tsx b/web/screens/Chat/DeleteThreadModal/index.tsx new file mode 100644 index 0000000000..edbdb09b49 --- /dev/null +++ b/web/screens/Chat/DeleteThreadModal/index.tsx @@ -0,0 +1,68 @@ +import React, { useCallback } from 'react' + +import { + Modal, + ModalTrigger, + ModalPortal, + ModalContent, + ModalHeader, + ModalTitle, + ModalFooter, + ModalClose, + Button, +} from '@janhq/uikit' +import { Trash2Icon } from 'lucide-react' + +import useDeleteThread from '@/hooks/useDeleteThread' + +type Props = { + threadId: string +} + +const DeleteThreadModal: React.FC = ({ threadId }) => { + const { deleteThread } = useDeleteThread() + const onDeleteThreadClick = useCallback( + (e: React.MouseEvent) => { + e.stopPropagation() + deleteThread(threadId) + }, + [deleteThread, threadId] + ) + + return ( + + e.stopPropagation()}> +
+ + + Delete thread + +
+
+ + + + Delete Thread + +

+ Are you sure you want to delete this thread? This action cannot be + undone. +

+ +
+ e.stopPropagation()}> + + + + + +
+
+
+
+ ) +} + +export default React.memo(DeleteThreadModal) diff --git a/web/screens/Chat/RequestDownloadModel/index.tsx b/web/screens/Chat/RequestDownloadModel/index.tsx index e62dc562dd..88fdadd573 100644 --- a/web/screens/Chat/RequestDownloadModel/index.tsx +++ b/web/screens/Chat/RequestDownloadModel/index.tsx @@ -2,15 +2,18 @@ import React, { Fragment, useCallback } from 'react' import { Button } from '@janhq/uikit' +import { useAtomValue } from 'jotai' + import LogoMark from '@/containers/Brand/Logo/Mark' import { MainViewState } from '@/constants/screens' -import { useGetDownloadedModels } from '@/hooks/useGetDownloadedModels' import { useMainViewState } from '@/hooks/useMainViewState' +import { downloadedModelsAtom } from '@/helpers/atoms/Model.atom' + const RequestDownloadModel: React.FC = () => { - const { downloadedModels } = useGetDownloadedModels() + const downloadedModels = useAtomValue(downloadedModelsAtom) const { setMainViewState } = useMainViewState() const onClick = useCallback(() => { diff --git a/web/screens/Chat/ThreadList/index.tsx b/web/screens/Chat/ThreadList/index.tsx index b4a045b1d7..8f5bfb8f28 100644 --- a/web/screens/Chat/ThreadList/index.tsx +++ b/web/screens/Chat/ThreadList/index.tsx @@ -1,76 +1,39 @@ -import { useEffect, useState } from 'react' +import { useCallback } from 'react' -import { - Modal, - ModalTrigger, - ModalClose, - ModalFooter, - ModalPortal, - ModalContent, - ModalHeader, - ModalTitle, - Button, -} from '@janhq/uikit' +import { Thread } from '@janhq/core/' import { motion as m } from 'framer-motion' import { useAtomValue } from 'jotai' -import { - GalleryHorizontalEndIcon, - MoreVerticalIcon, - Trash2Icon, - Paintbrush, -} from 'lucide-react' +import { GalleryHorizontalEndIcon, MoreVerticalIcon } from 'lucide-react' import { twMerge } from 'tailwind-merge' -import { useCreateNewThread } from '@/hooks/useCreateNewThread' -import useDeleteThread from '@/hooks/useDeleteThread' - -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 CleanThreadModal from '../CleanThreadModal' + +import DeleteThreadModal from '../DeleteThreadModal' + import { - activeThreadAtom, + getActiveThreadIdAtom, threadStatesAtom, threadsAtom, } from '@/helpers/atoms/Thread.atom' export default function ThreadList() { - const threads = useAtomValue(threadsAtom) const threadStates = useAtomValue(threadStatesAtom) - const { getThreads } = useThreads() - const { assistants } = useGetAssistants() - const { requestCreateNewThread } = useCreateNewThread() - const activeThread = useAtomValue(activeThreadAtom) - const { deleteThread, cleanThread } = useDeleteThread() - const { downloadedModels } = useGetDownloadedModels() - const [isThreadsReady, setIsThreadsReady] = useState(false) - - const { activeThreadId, setActiveThread: onThreadClick } = - useSetActiveThread() - - useEffect(() => { - getThreads().then(() => setIsThreadsReady(true)) - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []) + const threads = useAtomValue(threadsAtom) + const activeThreadId = useAtomValue(getActiveThreadIdAtom) + const { setActiveThread } = useSetActiveThread() - useEffect(() => { - if ( - isThreadsReady && - downloadedModels.length !== 0 && - threads.length === 0 && - assistants.length !== 0 && - !activeThread - ) { - requestCreateNewThread(assistants[0]) - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [assistants, threads, downloadedModels, activeThread, isThreadsReady]) + const onThreadClick = useCallback( + (thread: Thread) => { + setActiveThread(thread) + }, + [setActiveThread] + ) return (
@@ -83,133 +46,44 @@ export default function ThreadList() {

No Thread History

) : ( - threads.map((thread, i) => { - const lastMessage = - threadStates[thread.id]?.lastMessage ?? 'No new message' - return ( + threads.map((thread) => ( +
{ + onThreadClick(thread) + }} + > +
+

+ {thread.updated && displayDate(thread.updated)} +

+

{thread.title}

+

+ {threadStates[thread.id]?.lastMessage ?? 'No new message'} +

+
{ - onThreadClick(thread) - }} > -
-

- {thread.updated && displayDate(thread.updated)} -

-

{thread.title}

-

- {lastMessage || 'No new message'} -

-
-
- -
- - e.stopPropagation()}> -
- - - Clean thread - -
-
- - - - Clean Thread - -

Are you sure you want to clean this thread?

- -
- e.stopPropagation()} - > - - - - - -
-
-
-
- - e.stopPropagation()}> -
- - - Delete thread - -
-
- - - - Delete Thread - -

- Are you sure you want to delete this thread? This action - cannot be undone. -

- -
- e.stopPropagation()} - > - - - - - -
-
-
-
-
+ +
+ +
- {activeThreadId === thread.id && ( - - )}
- ) - }) + {activeThreadId === thread.id && ( + + )} +
+ )) )}
) diff --git a/web/screens/ExploreModels/ExploreModelItemHeader/index.tsx b/web/screens/ExploreModels/ExploreModelItemHeader/index.tsx index 3ffe2cbace..755494ee3b 100644 --- a/web/screens/ExploreModels/ExploreModelItemHeader/index.tsx +++ b/web/screens/ExploreModels/ExploreModelItemHeader/index.tsx @@ -27,14 +27,14 @@ import useDownloadModel from '@/hooks/useDownloadModel' import { useDownloadState } from '@/hooks/useDownloadState' -import { getAssistants } from '@/hooks/useGetAssistants' -import { downloadedModelsAtom } from '@/hooks/useGetDownloadedModels' import { useMainViewState } from '@/hooks/useMainViewState' import { toGibibytes } from '@/utils/converter' +import { assistantsAtom } from '@/helpers/atoms/Assistant.atom' import { serverEnabledAtom } from '@/helpers/atoms/LocalServer.atom' +import { downloadedModelsAtom } from '@/helpers/atoms/Model.atom' import { totalRamAtom } from '@/helpers/atoms/SystemBar.atom' type Props = { @@ -49,7 +49,9 @@ const ExploreModelItemHeader: React.FC = ({ model, onClick, open }) => { const { modelDownloadStateAtom } = useDownloadState() const { requestCreateNewThread } = useCreateNewThread() const totalRam = useAtomValue(totalRamAtom) + const serverEnabled = useAtomValue(serverEnabledAtom) + const assistants = useAtomValue(assistantsAtom) const downloadAtom = useMemo( () => atom((get) => get(modelDownloadStateAtom)[model.id]), @@ -60,7 +62,6 @@ const ExploreModelItemHeader: React.FC = ({ model, onClick, open }) => { const onDownloadClick = useCallback(() => { downloadModel(model) - // eslint-disable-next-line react-hooks/exhaustive-deps }, [model]) const isDownloaded = downloadedModels.find((md) => md.id === model.id) != null @@ -70,7 +71,6 @@ const ExploreModelItemHeader: React.FC = ({ model, onClick, open }) => { ) const onUseModelClick = useCallback(async () => { - const assistants = await getAssistants() if (assistants.length === 0) { alert('No assistant available') return diff --git a/web/screens/ExploreModels/ModelVersionItem/index.tsx b/web/screens/ExploreModels/ModelVersionItem/index.tsx index 50d71b161d..3a93856704 100644 --- a/web/screens/ExploreModels/ModelVersionItem/index.tsx +++ b/web/screens/ExploreModels/ModelVersionItem/index.tsx @@ -10,9 +10,11 @@ import { MainViewState } from '@/constants/screens' import useDownloadModel from '@/hooks/useDownloadModel' import { useDownloadState } from '@/hooks/useDownloadState' -import { useGetDownloadedModels } from '@/hooks/useGetDownloadedModels' + import { useMainViewState } from '@/hooks/useMainViewState' +import { downloadedModelsAtom } from '@/helpers/atoms/Model.atom' + type Props = { model: Model isRecommended: boolean @@ -20,7 +22,7 @@ type Props = { const ModelVersionItem: React.FC = ({ model }) => { const { downloadModel } = useDownloadModel() - const { downloadedModels } = useGetDownloadedModels() + const downloadedModels = useAtomValue(downloadedModelsAtom) const { setMainViewState } = useMainViewState() const isDownloaded = downloadedModels.find( diff --git a/web/screens/ExploreModels/index.tsx b/web/screens/ExploreModels/index.tsx index 398b2db089..7002c60b7b 100644 --- a/web/screens/ExploreModels/index.tsx +++ b/web/screens/ExploreModels/index.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react' +import { useCallback, useState } from 'react' import { openExternalUrl } from '@janhq/core' import { @@ -12,24 +12,24 @@ import { SelectItem, } from '@janhq/uikit' +import { useAtomValue } from 'jotai' import { SearchIcon } from 'lucide-react' -import Loader from '@/containers/Loader' - -import { useGetConfiguredModels } from '@/hooks/useGetConfiguredModels' - -import { useGetDownloadedModels } from '@/hooks/useGetDownloadedModels' - import ExploreModelList from './ExploreModelList' +import { + configuredModelsAtom, + downloadedModelsAtom, +} from '@/helpers/atoms/Model.atom' + const ExploreModelsScreen = () => { - const { loading, models } = useGetConfiguredModels() + const configuredModels = useAtomValue(configuredModelsAtom) + const downloadedModels = useAtomValue(downloadedModelsAtom) const [searchValue, setsearchValue] = useState('') - const { downloadedModels } = useGetDownloadedModels() const [sortSelected, setSortSelected] = useState('All Models') const sortMenu = ['All Models', 'Recommended', 'Downloaded'] - const filteredModels = models.filter((x) => { + const filteredModels = configuredModels.filter((x) => { if (sortSelected === 'Downloaded') { return ( x.name.toLowerCase().includes(searchValue.toLowerCase()) && @@ -45,11 +45,9 @@ const ExploreModelsScreen = () => { } }) - const onHowToImportModelClick = () => { + const onHowToImportModelClick = useCallback(() => { openExternalUrl('https://jan.ai/guides/using-models/import-manually/') - } - - if (loading) return + }, []) return (
{