diff --git a/core/src/types/file/index.ts b/core/src/types/file/index.ts index 0b1ea9fe08..a13a921361 100644 --- a/core/src/types/file/index.ts +++ b/core/src/types/file/index.ts @@ -129,4 +129,5 @@ export interface DownloadStateEvent { export enum DownloadType2 { Model = 'model', Miscelanous = 'miscelanous', + Engine = 'engine', } diff --git a/electron/handlers/native.ts b/electron/handlers/native.ts index ef62eac430..ee199fd76f 100644 --- a/electron/handlers/native.ts +++ b/electron/handlers/native.ts @@ -8,16 +8,15 @@ import { } from '@janhq/core/node' import { menu } from '../utils/menu' import { join } from 'path' -import { getJanDataFolderPath } from './../utils/path' +import { getAppConfigurations, getJanDataFolderPath } from './../utils/path' import { readdirSync, writeFileSync, readFileSync, existsSync, - mkdirSync + mkdirSync, } from 'fs' import { dump } from 'js-yaml' -import os from 'os' const isMac = process.platform === 'darwin' @@ -178,7 +177,7 @@ export function handleAppIPCs() { } ) - ipcMain.handle(NativeRoute.showOpenMenu, function (e, args) { + ipcMain.handle(NativeRoute.showOpenMenu, function (_e, args) { if (!isMac && windowManager.mainWindow) { menu.popup({ window: windowManager.mainWindow, @@ -209,10 +208,11 @@ export function handleAppIPCs() { }) ipcMain.handle(NativeRoute.openAppLog, async (_event): Promise => { - const cortexHomeDir = join(os.homedir(), 'cortex') + const configuration = getAppConfigurations() + const dataFolder = configuration.data_folder try { - const errorMessage = await shell.openPath(join(cortexHomeDir)) + const errorMessage = await shell.openPath(join(dataFolder)) if (errorMessage) { console.error(`An error occurred: ${errorMessage}`) } else { @@ -227,21 +227,19 @@ export function handleAppIPCs() { const janModelFolderPath = join(getJanDataFolderPath(), 'models') const allModelFolders = readdirSync(janModelFolderPath) - const cortexHomeDir = join(os.homedir(), 'cortex') - const cortexModelFolderPath = join(cortexHomeDir, 'models') + const configration = getAppConfigurations() + const destinationFolderPath = join(configration.data_folder, 'models') - if(!existsSync(cortexModelFolderPath)) - mkdirSync(cortexModelFolderPath) - console.log('cortexModelFolderPath', cortexModelFolderPath) + if (!existsSync(destinationFolderPath)) mkdirSync(destinationFolderPath) + console.log('destinationFolderPath', destinationFolderPath) const reflect = require('@alumna/reflect') for (const modelName of allModelFolders) { const modelFolderPath = join(janModelFolderPath, modelName) try { - const filesInModelFolder = readdirSync(modelFolderPath) - const destinationPath = join(cortexModelFolderPath, modelName) + const destinationPath = join(destinationFolderPath, modelName) const modelJsonFullPath = join( janModelFolderPath, @@ -253,15 +251,18 @@ export function handleAppIPCs() { const fileNames: string[] = model.sources.map((x: any) => x.filename) let files: string[] = [] - if(filesInModelFolder.length > 1) { - // prepend fileNames with cortexModelFolderPath + if (filesInModelFolder.length > 1) { + // prepend fileNames with model folder path files = fileNames.map((x: string) => - join(cortexModelFolderPath, model.id, x) + join(destinationFolderPath, model.id, x) ) - } else if(model.sources.length && !/^(http|https):\/\/[^/]+\/.*/.test(model.sources[0].url)) { + } else if ( + model.sources.length && + !/^(http|https):\/\/[^/]+\/.*/.test(model.sources[0].url) + ) { // Symlink case - files = [ model.sources[0].url ] - } else continue; + files = [model.sources[0].url] + } else continue // create folder if not exist // only for local model files @@ -269,7 +270,10 @@ export function handleAppIPCs() { mkdirSync(destinationPath, { recursive: true }) } - const engine = (model.engine === 'nitro' || model.engine === 'cortex') ? 'cortex.llamacpp' : (model.engine ?? 'cortex.llamacpp') + const engine = + model.engine === 'nitro' || model.engine === 'cortex' + ? 'cortex.llamacpp' + : (model.engine ?? 'cortex.llamacpp') const updatedModelFormat = { id: model.id, @@ -296,7 +300,7 @@ export function handleAppIPCs() { max_tokens: model.parameters?.max_tokens ?? 2048, stream: model.parameters?.stream ?? true, } - if(filesInModelFolder.length > 1 ) { + if (filesInModelFolder.length > 1) { const { err } = await reflect({ src: modelFolderPath, dest: destinationPath, @@ -307,14 +311,14 @@ export function handleAppIPCs() { errorOnExist: false, }) - if (err) { - console.error(err); - continue; + if (err) { + console.error(err) + continue } } // create the model.yml file const modelYamlData = dump(updatedModelFormat) - const modelYamlPath = join(cortexModelFolderPath, `${modelName}.yaml`) + const modelYamlPath = join(destinationFolderPath, `${modelName}.yaml`) writeFileSync(modelYamlPath, modelYamlData) } catch (err) { @@ -354,11 +358,11 @@ export function handleAppIPCs() { 'messages.jsonl' ) - if(!existsSync(messageFullPath)) continue; + if (!existsSync(messageFullPath)) continue const lines = readFileSync(messageFullPath, 'utf-8') - .toString() - .split('\n') - .filter((line: any) => line !== '') + .toString() + .split('\n') + .filter((line: any) => line !== '') for (const line of lines) { messages.push(JSON.parse(line)) } @@ -379,8 +383,10 @@ export function handleAppIPCs() { const janModelsFolderPath = join(getJanDataFolderPath(), 'models') if (!existsSync(janModelsFolderPath)) { + console.debug('No local models found') return false } + // get children of thread folder const allModelsFolders = readdirSync(janModelsFolderPath) let hasLocalModels = false diff --git a/electron/main.ts b/electron/main.ts index 5ce177e49c..02e174eab3 100644 --- a/electron/main.ts +++ b/electron/main.ts @@ -19,7 +19,7 @@ import { handleAppIPCs } from './handlers/native' * Utils **/ import { setupMenu } from './utils/menu' -import { createUserSpace } from './utils/path' +import { createUserSpace, getAppConfigurations } from './utils/path' import { migrate } from './utils/migration' import { cleanUpAndQuit } from './utils/clean' import { setupCore } from './utils/setup' @@ -58,12 +58,31 @@ Object.assign(console, log.functions) let cortexService: ChildProcess | undefined = undefined +const cortexJsPort = 1338 +const cortexCppPort = 3940 +const host = '127.0.0.1' + app .whenReady() - .then(() => killProcessesOnPort(3929)) - .then(() => killProcessesOnPort(1337)) + .then(() => killProcessesOnPort(cortexCppPort)) + .then(() => killProcessesOnPort(cortexJsPort)) .then(() => { - const command = `${cortexPath} -a 127.0.0.1 -p 1337` + const appConfiguration = getAppConfigurations() + const janDataFolder = appConfiguration.data_folder + + const cortexParams: Record = { + '-n': 'jan', + '-a': host, + '-p': cortexJsPort.toString(), + '-ep': cortexCppPort.toString(), + '--dataFolder': janDataFolder, + } + + // add cortex parameters to the command + const command = Object.entries(cortexParams).reduce( + (acc, [key, value]) => `${acc} ${key} ${value}`, + `${cortexPath}` + ) log.info('Starting cortex with command:', command) // init cortex @@ -154,7 +173,7 @@ async function stopCortexService() { async function stopApiServer() { // this function is not meant to be success. It will throw an error. try { - await fetch('http://localhost:1337/v1/system', { + await fetch(`http://${host}:${cortexJsPort}/v1/system`, { method: 'DELETE', }) } catch (error) { diff --git a/electron/resources/version.txt b/electron/resources/version.txt index 074967a2f3..88fa1cce07 100644 --- a/electron/resources/version.txt +++ b/electron/resources/version.txt @@ -1 +1 @@ -0.5.0-27 +0.5.0-29 diff --git a/web/containers/Layout/BottomPanel/DownloadingStatus/index.tsx b/web/containers/Layout/BottomPanel/DownloadingStatus/index.tsx index d2944a492f..fbf56349f0 100644 --- a/web/containers/Layout/BottomPanel/DownloadingStatus/index.tsx +++ b/web/containers/Layout/BottomPanel/DownloadingStatus/index.tsx @@ -40,16 +40,21 @@ const DownloadStatus: React.FC = () => { ? ((totalTransfferedSize / totalDownloadSize) * 100).toFixed(2) : 0 + const downloadTitle = `Downloading ${downloadStates + .map((state) => state.type) + .join(', ') + .trim()}` + return ( {Object.values(downloadStates)?.length > 0 && ( - @@ -172,16 +141,17 @@ const ModalMigrations = () => {
- {!loaderThreads && ( + {threadAndMessageMigrationState !== 'in_progress' && ( <> - {threadsMessagesMigrationSuccess ? ( + {threadAndMessageMigrationState === 'success' ? ( Success ) : ( Failed @@ -190,14 +160,14 @@ const ModalMigrations = () => { )}

Threads

- {loaderThreads ? ( + {threadAndMessageMigrationState === 'in_progress' ? ( ) : ( - !threadsMessagesMigrationSuccess && ( + threadAndMessageMigrationState !== 'success' && ( @@ -207,16 +177,16 @@ const ModalMigrations = () => {
- {!loaderModels && ( + {modelMigrationState !== 'in_progress' && ( <> - {modelsMigrationSuccess ? ( + {modelMigrationState === 'success' ? ( Success ) : ( Failed @@ -225,14 +195,14 @@ const ModalMigrations = () => { )}

Models

- {loaderModels ? ( + {modelMigrationState === 'in_progress' ? ( ) : ( - !modelsMigrationSuccess && ( + modelMigrationState === 'failed' && ( @@ -242,17 +212,15 @@ const ModalMigrations = () => {
)} - + } /> ) diff --git a/web/containers/Providers/index.tsx b/web/containers/Providers/index.tsx index e27fcaaf15..f5ed40929c 100644 --- a/web/containers/Providers/index.tsx +++ b/web/containers/Providers/index.tsx @@ -14,7 +14,6 @@ import ThemeWrapper from '@/containers/Providers/Theme' import { setupCoreServices } from '@/services/coreService' import DataLoader from './DataLoader' -import ModalMigrations from './ModalMigrations' import Responsive from './Responsive' @@ -42,7 +41,6 @@ const Providers = ({ children }: PropsWithChildren) => { )} - diff --git a/web/helpers/atoms/AppConfig.atom.ts b/web/helpers/atoms/AppConfig.atom.ts index d2db894d25..9b8cba186a 100644 --- a/web/helpers/atoms/AppConfig.atom.ts +++ b/web/helpers/atoms/AppConfig.atom.ts @@ -8,10 +8,6 @@ const IGNORE_SSL = 'ignoreSSLFeature' const HTTPS_PROXY_FEATURE = 'httpsProxyFeature' const QUICK_ASK_ENABLED = 'quickAskEnabled' const MIGRATION_WARNING = 'didShowMigrationWarning' -const THREADS_MESSAGES_MIGRATION_SUCCESS = 'threadsMessagesMigrationSuccess' -const MODELS_MIGRATION_SUCCESS = 'modelsMigrationSuccess' -const SKIP_MIGRATION = 'skipMigration' - export const janDataFolderPathAtom = atom('') export const experimentalFeatureEnabledAtom = atomWithStorage( @@ -27,26 +23,6 @@ export const vulkanEnabledAtom = atomWithStorage(VULKAN_ENABLED, false) export const quickAskEnabledAtom = atomWithStorage(QUICK_ASK_ENABLED, false) export const didShowMigrationWarningAtom = atomWithStorage( MIGRATION_WARNING, - false -) -export const threadsMessagesMigrationSuccessAtom = atomWithStorage( - THREADS_MESSAGES_MIGRATION_SUCCESS, - false, - undefined, - { - getOnInit: true, - } -) -export const modelsMigrationSuccessAtom = atomWithStorage( - MODELS_MIGRATION_SUCCESS, - false, - undefined, - { - getOnInit: true, - } -) -export const skipMigrationAtom = atomWithStorage( - SKIP_MIGRATION, false, undefined, { @@ -54,4 +30,4 @@ export const skipMigrationAtom = atomWithStorage( } ) -export const hostAtom = atom('http://localhost:1337/v1') +export const hostAtom = atom('http://127.0.0.1:1338/v1') diff --git a/web/hooks/useCortex.ts b/web/hooks/useCortex.ts index 491c646936..6f10ed2452 100644 --- a/web/hooks/useCortex.ts +++ b/web/hooks/useCortex.ts @@ -247,22 +247,24 @@ const useCortex = () => { const downloadModel = useCallback( async (modelId: string, fileName?: string, persistedModelId?: string) => { - try { - // return await cortex.models.download(modelId) - return await fetch(`${host}/models/${modelId}/pull`, { - method: 'POST', - headers: { - 'accept': 'application/json', - // eslint-disable-next-line @typescript-eslint/naming-convention - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - fileName: fileName, - persistedModelId: persistedModelId, - }), - }) - } catch (err) { - console.error(err) + const response = await fetch(`${host}/models/${modelId}/pull`, { + method: 'POST', + headers: { + 'accept': 'application/json', + // eslint-disable-next-line @typescript-eslint/naming-convention + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + fileName: fileName, + persistedModelId: persistedModelId, + }), + }) + if (!response.ok) { + const responseJson = await response.json() + const errorMessage: string = + (responseJson.error?.message as string) ?? + `Failed to download model ${modelId}` + throw new Error(errorMessage) } }, [host] diff --git a/web/hooks/useMigratingData.ts b/web/hooks/useMigratingData.ts index e19bff80e4..76e5d9e036 100644 --- a/web/hooks/useMigratingData.ts +++ b/web/hooks/useMigratingData.ts @@ -1,25 +1,13 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { useCallback } from 'react' -import { useAtom } from 'jotai' - import useAssistantQuery from './useAssistantQuery' import useCortex from './useCortex' import useMessageCreateMutation from './useMessageCreateMutation' import useThreads from './useThreads' -import { - threadsMessagesMigrationSuccessAtom, - modelsMigrationSuccessAtom, -} from '@/helpers/atoms/AppConfig.atom' - const useMigratingData = () => { - const [threadsMessagesMigrationSuccess, setThreadsMessagesMigrationSuccess] = - useAtom(threadsMessagesMigrationSuccessAtom) - const [modelsMigrationSuccess, setModelsMigrationSuccess] = useAtom( - modelsMigrationSuccessAtom - ) const { createThread } = useThreads() const { updateThread } = useCortex() const createMessage = useMessageCreateMutation() @@ -32,92 +20,69 @@ const useMigratingData = () => { return window?.electronAPI?.getAllMessagesAndThreads() }, []) - const getJanLocalModels = useCallback(async (): Promise<{ - hasLocalModels: boolean - }> => { + const getJanLocalModels = useCallback(async (): Promise => { + // TODO: change the name of this function return window?.electronAPI?.getAllLocalModels() }, []) const migrateModels = useCallback(async () => { - try { - if (!modelsMigrationSuccess) { - await window?.electronAPI?.syncModelFileToCortex() - setModelsMigrationSuccess(true) - } - } catch (err) { - console.log(err) - setModelsMigrationSuccess(false) - } - }, [modelsMigrationSuccess, setModelsMigrationSuccess]) + return window?.electronAPI?.syncModelFileToCortex() + }, []) const migrateThreadsAndMessages = useCallback(async () => { if (!assistants || assistants.length === 0) { console.error('No assistant found') return } - try { - if (threadsMessagesMigrationSuccess) return - const threadsAndMessages = await getJanThreadsAndMessages() - const janThreads = threadsAndMessages.threads - for (const thread of janThreads) { - const modelId: string | undefined = thread.assistants[0]?.model?.id - if (!modelId || modelId.trim().length === 0 || modelId === '*') { - console.error( - `Ignore thread ${thread.id} because modelId is not found` - ) - continue - } - const threadTitle: string = thread.title ?? 'New Thread' - const instructions: string = thread.assistants[0]?.instructions ?? '' - // currently, we don't have api support for creating thread with messages - const cortexThread = await createThread(modelId, assistants[0]) - console.log('createThread', cortexThread) - // update instruction - cortexThread.assistants[0].instructions = instructions - cortexThread.title = threadTitle + const threadsAndMessages = await getJanThreadsAndMessages() + const janThreads = threadsAndMessages.threads + + for (const thread of janThreads) { + const modelId: string | undefined = thread.assistants[0]?.model?.id + if (!modelId || modelId.trim().length === 0 || modelId === '*') { + console.error(`Ignore thread ${thread.id} because modelId is not found`) + continue + } + const threadTitle: string = thread.title ?? 'New Thread' + const instructions: string = thread.assistants[0]?.instructions ?? '' + // currently, we don't have api support for creating thread with messages + const cortexThread = await createThread(modelId, assistants[0]) + + console.log('createThread', cortexThread) + // update instruction + cortexThread.assistants[0].instructions = instructions + cortexThread.title = threadTitle + + // update thread name + await updateThread(cortexThread) + console.log('updateThread', cortexThread) - // update thread name - await updateThread(cortexThread) - console.log('updateThread', cortexThread) + // we finished with thread, now continue with messages + const janMessages = threadsAndMessages.messages.filter( + (m) => m.thread_id === thread.id + ) - // we finished with thread, now continue with messages - const janMessages = threadsAndMessages.messages.filter( - (m) => m.thread_id === thread.id - ) - console.log(janMessages) - for (let j = 0; j < janMessages.length; ++j) { - const janMessage = janMessages[j] - // filter out the system message if any - if (janMessage.role === 'system') continue - try { - const messageContent: string = - janMessage.content[0]?.text.value ?? '' + for (let j = 0; j < janMessages.length; ++j) { + const janMessage = janMessages[j] + // filter out the system message if any + if (janMessage.role === 'system') continue + const messageContent: string = janMessage.content[0]?.text.value ?? '' - // can speed up here with Promise.allSettled - await createMessage.mutateAsync({ - threadId: cortexThread.id, - createMessageParams: { - content: messageContent, - role: janMessage.role, - }, - }) - } catch (err) { - console.error(err) - } - } - setThreadsMessagesMigrationSuccess(true) + // can speed up here with Promise.allSettled + await createMessage.mutateAsync({ + threadId: cortexThread.id, + createMessageParams: { + content: messageContent, + role: janMessage.role, + }, + }) } - } catch (err) { - console.log(err) - setThreadsMessagesMigrationSuccess(false) } }, [ assistants, getJanThreadsAndMessages, - threadsMessagesMigrationSuccess, createThread, updateThread, - setThreadsMessagesMigrationSuccess, createMessage, ]) diff --git a/web/screens/Settings/Advanced/FactoryReset/components/DataMigration.tsx b/web/screens/Settings/Advanced/FactoryReset/components/DataMigration.tsx new file mode 100644 index 0000000000..3e41c9e584 --- /dev/null +++ b/web/screens/Settings/Advanced/FactoryReset/components/DataMigration.tsx @@ -0,0 +1,64 @@ +import { useCallback } from 'react' + +import { Button } from '@janhq/joi' +import { useAtomValue, useSetAtom } from 'jotai' + +import { showMigrationModalAtom } from '@/containers/Providers/ModalMigrations' + +import { toaster } from '@/containers/Toast' + +import useThreads from '@/hooks/useThreads' + +import { threadsAtom } from '@/helpers/atoms/Thread.atom' + +const DataMigration: React.FC = () => { + const setShowMigrationModal = useSetAtom(showMigrationModalAtom) + const threads = useAtomValue(threadsAtom) + const { deleteThread } = useThreads() + + const onStartMigrationClick = useCallback(() => { + setShowMigrationModal(true) + }, [setShowMigrationModal]) + + const onCleanUpDataClick = useCallback(async () => { + for (const thread of threads) { + try { + await deleteThread(thread.id) + } catch (err) { + console.error('Error deleting thread', err) + toaster({ + title: 'Delete thread failed', + description: `Failed to delete thread ${thread.title}`, + type: 'error', + }) + } + } + toaster({ + title: 'Delete thread successfully!', + type: 'success', + }) + }, [threads, deleteThread]) + + return ( +
+
+
+
Clear logs
+
+

+ Clear all logs from Jan app. +

+
+
+ + +
+
+ ) +} + +export default DataMigration diff --git a/web/screens/Settings/Advanced/index.tsx b/web/screens/Settings/Advanced/index.tsx index 030de9717d..9ac2ef0c33 100644 --- a/web/screens/Settings/Advanced/index.tsx +++ b/web/screens/Settings/Advanced/index.tsx @@ -13,6 +13,8 @@ import { toaster } from '@/containers/Toast' import useModelStop from '@/hooks/useModelStop' import { useSettings } from '@/hooks/useSettings' +import DataMigration from './FactoryReset/components/DataMigration' + import { experimentalFeatureEnabledAtom, ignoreSslAtom, @@ -21,6 +23,7 @@ import { vulkanEnabledAtom, quickAskEnabledAtom, } from '@/helpers/atoms/AppConfig.atom' + import { activeModelsAtom } from '@/helpers/atoms/Model.atom' // type GPU = { @@ -459,6 +462,7 @@ const Advanced = () => { {/* Factory Reset */} {/* */} + {experimentalEnabled && }
) diff --git a/web/screens/Settings/ImportingModelModal/index.tsx b/web/screens/Settings/ImportingModelModal/index.tsx index 3d168a1ae9..d54624dba0 100644 --- a/web/screens/Settings/ImportingModelModal/index.tsx +++ b/web/screens/Settings/ImportingModelModal/index.tsx @@ -1,4 +1,4 @@ -import { useEffect } from 'react' +import { Fragment, useEffect } from 'react' import { Modal } from '@janhq/joi' import { useAtomValue, useSetAtom } from 'jotai' @@ -13,11 +13,15 @@ import { import ImportingModelItem from './ImportingModelItem' -import { importingModelsAtom } from '@/helpers/atoms/Model.atom' +import { + importingModelsAtom, + setImportingModelErrorAtom, +} from '@/helpers/atoms/Model.atom' const ImportingModelModal = () => { const { downloadModel } = useCortex() const setImportModelStage = useSetAtom(setImportModelStageAtom) + const setImportModelError = useSetAtom(setImportingModelErrorAtom) const importingModels = useAtomValue(importingModelsAtom) const importModelStage = useAtomValue(getImportModelStageAtom) @@ -28,7 +32,16 @@ const ImportingModelModal = () => { useEffect(() => { const importModels = async () => { for (const model of importingModels) { - await downloadModel(model.path) + try { + await downloadModel(model.path) + } catch (error) { + let errorMessage = String(error) + if (error instanceof Error) { + errorMessage = error.message + } + + setImportModelError(model.path, errorMessage) + } } } importModels() @@ -41,21 +54,7 @@ const ImportingModelModal = () => { onOpenChange={() => setImportModelStage('NONE')} title={`Importing model (${finishedImportModel}/${importingModels.length})`} content={ -
-
- {/* - */} -
- +
{importingModels.map((model) => ( @@ -75,7 +74,7 @@ const ImportingModelModal = () => {

-
+
} /> )