fix: set cortex data folder path when starting jan (#3252)
* fix: set cortex data folder path when starting jan * fix: change port to 1338 * fix: add migration in advanced setting * update * update new cortex * feat: add import model error handler Signed-off-by: James <namnh0122@gmail.com> --------- Signed-off-by: James <namnh0122@gmail.com>
This commit is contained in:
parent
224ca3f7cc
commit
91c77eda78
@ -129,4 +129,5 @@ export interface DownloadStateEvent {
|
||||
export enum DownloadType2 {
|
||||
Model = 'model',
|
||||
Miscelanous = 'miscelanous',
|
||||
Engine = 'engine',
|
||||
}
|
||||
|
||||
@ -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<void> => {
|
||||
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
|
||||
|
||||
@ -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<string, string> = {
|
||||
'-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) {
|
||||
|
||||
@ -1 +1 @@
|
||||
0.5.0-27
|
||||
0.5.0-29
|
||||
|
||||
@ -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 (
|
||||
<Fragment>
|
||||
{Object.values(downloadStates)?.length > 0 && (
|
||||
<Modal
|
||||
title="Downloading model"
|
||||
title={downloadTitle}
|
||||
trigger={
|
||||
<div className="flex cursor-pointer items-center gap-2">
|
||||
<Button size="small" theme="ghost">
|
||||
<span className="font-medium">
|
||||
Downloading model{' '}
|
||||
{downloadTitle}{' '}
|
||||
{Object.values(downloadStates).length > 1 &&
|
||||
`1/${Object.values(downloadStates).length}`}
|
||||
</span>
|
||||
|
||||
@ -15,6 +15,8 @@ import TopPanel from '@/containers/Layout/TopPanel'
|
||||
|
||||
import { getImportModelStageAtom } from '@/hooks/useImportModel'
|
||||
|
||||
import useMigratingData from '@/hooks/useMigratingData'
|
||||
|
||||
import DownloadLocalModelModal from '@/screens/HubScreen2/components/DownloadLocalModelModal'
|
||||
import InferenceErrorModal from '@/screens/HubScreen2/components/InferenceErrorModal'
|
||||
import SetUpApiKeyModal from '@/screens/HubScreen2/components/SetUpApiKeyModal'
|
||||
@ -33,17 +35,24 @@ import LoadingModal from '../LoadingModal'
|
||||
|
||||
import MainViewContainer from '../MainViewContainer'
|
||||
|
||||
import ModalMigrations, {
|
||||
showMigrationModalAtom,
|
||||
} from '../Providers/ModalMigrations'
|
||||
import WaitingForCortexModal from '../WaitingCortexModal'
|
||||
|
||||
import InstallingExtensionModal from './BottomPanel/InstallingExtension/InstallingExtensionModal'
|
||||
|
||||
import { MainViewState, mainViewStateAtom } from '@/helpers/atoms/App.atom'
|
||||
import { didShowMigrationWarningAtom } from '@/helpers/atoms/AppConfig.atom'
|
||||
import { reduceTransparentAtom } from '@/helpers/atoms/Setting.atom'
|
||||
|
||||
const BaseLayout = () => {
|
||||
const didShowMigrationWarning = useAtomValue(didShowMigrationWarningAtom)
|
||||
const setShowMigrationModal = useSetAtom(showMigrationModalAtom)
|
||||
const setMainViewState = useSetAtom(mainViewStateAtom)
|
||||
const importModelStage = useAtomValue(getImportModelStageAtom)
|
||||
const reduceTransparent = useAtomValue(reduceTransparentAtom)
|
||||
const { getJanThreadsAndMessages, getJanLocalModels } = useMigratingData()
|
||||
|
||||
useEffect(() => {
|
||||
if (localStorage.getItem(SUCCESS_SET_NEW_DESTINATION) === 'true') {
|
||||
@ -51,6 +60,29 @@ const BaseLayout = () => {
|
||||
}
|
||||
}, [setMainViewState])
|
||||
|
||||
useEffect(() => {
|
||||
if (didShowMigrationWarning) return
|
||||
|
||||
const isUserHaveData = async (): Promise<boolean> => {
|
||||
const threadAndMessageData = await getJanThreadsAndMessages()
|
||||
const isUserHaveAnyModel = await getJanLocalModels()
|
||||
return threadAndMessageData.threads.length > 0 || isUserHaveAnyModel
|
||||
}
|
||||
|
||||
isUserHaveData()
|
||||
.then((isUserHaveData) => {
|
||||
if (isUserHaveData === true) {
|
||||
setShowMigrationModal(true)
|
||||
}
|
||||
})
|
||||
.catch((e) => console.error('Error checking user data', e))
|
||||
}, [
|
||||
didShowMigrationWarning,
|
||||
getJanThreadsAndMessages,
|
||||
getJanLocalModels,
|
||||
setShowMigrationModal,
|
||||
])
|
||||
|
||||
return (
|
||||
<div
|
||||
className={twMerge(
|
||||
@ -92,6 +124,7 @@ const BaseLayout = () => {
|
||||
<ChooseWhatToImportModal />
|
||||
<InstallingExtensionModal />
|
||||
<HuggingFaceRepoDetailModal />
|
||||
<ModalMigrations />
|
||||
</div>
|
||||
<BottomPanel />
|
||||
</div>
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import React, { Fragment, useCallback, useEffect } from 'react'
|
||||
import React, { Fragment, useCallback, useMemo, useState } from 'react'
|
||||
|
||||
import { Button, Modal, Badge } from '@janhq/joi'
|
||||
|
||||
import { useAtom, useAtomValue } from 'jotai'
|
||||
import { atom, useAtom, useSetAtom } from 'jotai'
|
||||
import { AlertTriangleIcon } from 'lucide-react'
|
||||
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
@ -11,25 +11,24 @@ import Spinner from '@/containers/Loader/Spinner'
|
||||
|
||||
import useMigratingData from '@/hooks/useMigratingData'
|
||||
|
||||
import {
|
||||
didShowMigrationWarningAtom,
|
||||
modelsMigrationSuccessAtom,
|
||||
threadsMessagesMigrationSuccessAtom,
|
||||
skipMigrationAtom,
|
||||
} from '@/helpers/atoms/AppConfig.atom'
|
||||
import { didShowMigrationWarningAtom } from '@/helpers/atoms/AppConfig.atom'
|
||||
|
||||
export const showMigrationModalAtom = atom<boolean>(false)
|
||||
|
||||
const MigrationStates = ['idle', 'in_progress', 'failed', 'success'] as const
|
||||
type MigrationState = (typeof MigrationStates)[number]
|
||||
|
||||
const ModalMigrations = () => {
|
||||
const [didShowMigrationWarning, setDidShowMigrationWarning] = useAtom(
|
||||
didShowMigrationWarningAtom
|
||||
)
|
||||
const [skipMigration, setSkipMigration] = useAtom(skipMigrationAtom)
|
||||
const modelsMigrationSuccess = useAtomValue(modelsMigrationSuccessAtom)
|
||||
const threadsMessagesMigrationSuccess = useAtomValue(
|
||||
threadsMessagesMigrationSuccessAtom
|
||||
const setDidShowMigrationModal = useSetAtom(didShowMigrationWarningAtom)
|
||||
const [showMigrationModal, setShowMigrationModal] = useAtom(
|
||||
showMigrationModalAtom
|
||||
)
|
||||
const [step, setStep] = React.useState(1)
|
||||
const [loaderThreads, setLoaderThreads] = React.useState(false)
|
||||
const [loaderModels, setLoaderModels] = React.useState(false)
|
||||
const { migrateModels, migrateThreadsAndMessages } = useMigratingData()
|
||||
const [threadAndMessageMigrationState, setThreadAndMessageMigrationState] =
|
||||
useState<MigrationState>('idle')
|
||||
const [modelMigrationState, setModelMigrationState] =
|
||||
useState<MigrationState>('idle')
|
||||
|
||||
const getStepTitle = () => {
|
||||
switch (step) {
|
||||
@ -37,84 +36,63 @@ const ModalMigrations = () => {
|
||||
return 'Important Update: Data Migration Needed'
|
||||
|
||||
default:
|
||||
return loaderThreads || loaderModels
|
||||
? 'Migration In Progress'
|
||||
return threadAndMessageMigrationState === 'in_progress' ||
|
||||
modelMigrationState === 'in_progress'
|
||||
? 'Migrating'
|
||||
: 'Migration Completed'
|
||||
}
|
||||
}
|
||||
|
||||
const handleStartMigration = async () => {
|
||||
setStep(2)
|
||||
await handleStartMigrationModels()
|
||||
await handleStartMigrationThreads()
|
||||
}
|
||||
|
||||
const handleStartMigrationThreads = async () => {
|
||||
setLoaderThreads(true)
|
||||
await migrateThreadsAndMessages()
|
||||
setTimeout(() => {
|
||||
setLoaderThreads(false)
|
||||
}, 1200)
|
||||
}
|
||||
|
||||
const handleStartMigrationModels = async () => {
|
||||
setLoaderModels(true)
|
||||
await migrateModels()
|
||||
setTimeout(() => {
|
||||
setLoaderModels(false)
|
||||
}, 1200)
|
||||
}
|
||||
|
||||
const {
|
||||
getJanThreadsAndMessages,
|
||||
migrateModels,
|
||||
migrateThreadsAndMessages,
|
||||
getJanLocalModels,
|
||||
} = useMigratingData()
|
||||
|
||||
const getMigrationNotif = useCallback(async () => {
|
||||
const migrationThreadsAndMessages = useCallback(async () => {
|
||||
setThreadAndMessageMigrationState('in_progress')
|
||||
try {
|
||||
const resultThreadsAndMessages = await getJanThreadsAndMessages()
|
||||
const resultLocalModels = await getJanLocalModels()
|
||||
if (
|
||||
resultThreadsAndMessages.threads.length > 0 ||
|
||||
resultThreadsAndMessages.messages.length > 0 ||
|
||||
resultLocalModels
|
||||
) {
|
||||
setDidShowMigrationWarning(true)
|
||||
} else {
|
||||
setDidShowMigrationWarning(false)
|
||||
}
|
||||
} catch (error) {
|
||||
setDidShowMigrationWarning(false)
|
||||
console.error(error)
|
||||
await migrateThreadsAndMessages()
|
||||
setThreadAndMessageMigrationState('success')
|
||||
console.debug('Migrating threads and messages successfully!')
|
||||
} catch (err) {
|
||||
console.error('Migrating threads and messages error', err)
|
||||
setThreadAndMessageMigrationState('failed')
|
||||
}
|
||||
}, [getJanLocalModels, getJanThreadsAndMessages, setDidShowMigrationWarning])
|
||||
}, [setThreadAndMessageMigrationState, migrateThreadsAndMessages])
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
skipMigration ||
|
||||
(threadsMessagesMigrationSuccess && modelsMigrationSuccess)
|
||||
) {
|
||||
return setDidShowMigrationWarning(false)
|
||||
} else {
|
||||
getMigrationNotif()
|
||||
const migratingModels = useCallback(async () => {
|
||||
setModelMigrationState('in_progress')
|
||||
try {
|
||||
await migrateModels()
|
||||
setModelMigrationState('success')
|
||||
console.debug('Migrating models successfully!')
|
||||
} catch (err) {
|
||||
console.error('Migrating models error', err)
|
||||
setModelMigrationState('failed')
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [
|
||||
skipMigration,
|
||||
setDidShowMigrationWarning,
|
||||
threadsMessagesMigrationSuccess,
|
||||
modelsMigrationSuccess,
|
||||
])
|
||||
}, [migrateModels, setModelMigrationState])
|
||||
|
||||
const onStartMigrationClick = useCallback(async () => {
|
||||
setStep(2)
|
||||
await migratingModels()
|
||||
await migrationThreadsAndMessages()
|
||||
}, [migratingModels, migrationThreadsAndMessages])
|
||||
|
||||
const onDismiss = useCallback(() => {
|
||||
setStep(1)
|
||||
setShowMigrationModal(false)
|
||||
setDidShowMigrationModal(true)
|
||||
}, [setDidShowMigrationModal, setShowMigrationModal])
|
||||
|
||||
const disableDismissButton = useMemo(
|
||||
() =>
|
||||
threadAndMessageMigrationState === 'in_progress' ||
|
||||
modelMigrationState === 'in_progress',
|
||||
[threadAndMessageMigrationState, modelMigrationState]
|
||||
)
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={didShowMigrationWarning}
|
||||
open={showMigrationModal}
|
||||
hideClose
|
||||
title={getStepTitle()}
|
||||
content={
|
||||
<>
|
||||
<Fragment>
|
||||
{step === 1 && (
|
||||
<Fragment>
|
||||
<p className="text-[hsla(var(--text-secondary))]">
|
||||
@ -149,19 +127,10 @@ const ModalMigrations = () => {
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<Button
|
||||
className="mt-4"
|
||||
theme="ghost"
|
||||
onClick={() => {
|
||||
setSkipMigration(true)
|
||||
}}
|
||||
>
|
||||
<Button className="mt-4" theme="ghost" onClick={onDismiss}>
|
||||
Skip
|
||||
</Button>
|
||||
<Button
|
||||
className="ml-2 mt-4"
|
||||
onClick={() => handleStartMigration()}
|
||||
>
|
||||
<Button className="ml-2 mt-4" onClick={onStartMigrationClick}>
|
||||
Migrate Now
|
||||
</Button>
|
||||
</div>
|
||||
@ -172,16 +141,17 @@ const ModalMigrations = () => {
|
||||
<div
|
||||
className={twMerge(
|
||||
'mb-2 mt-4 flex justify-between rounded-lg border border-[hsla(var(--app-border))] p-3',
|
||||
threadsMessagesMigrationSuccess
|
||||
threadAndMessageMigrationState === 'success'
|
||||
? 'bg-[hsla(var(--success-bg-soft))]'
|
||||
: 'bg-[hsla(var(--destructive-bg-soft))]',
|
||||
loaderThreads && 'bg-trasparent'
|
||||
threadAndMessageMigrationState === 'in_progress' &&
|
||||
'bg-trasparent'
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center gap-x-1.5">
|
||||
{!loaderThreads && (
|
||||
{threadAndMessageMigrationState !== 'in_progress' && (
|
||||
<>
|
||||
{threadsMessagesMigrationSuccess ? (
|
||||
{threadAndMessageMigrationState === 'success' ? (
|
||||
<Badge theme="success">Success</Badge>
|
||||
) : (
|
||||
<Badge theme="destructive">Failed</Badge>
|
||||
@ -190,14 +160,14 @@ const ModalMigrations = () => {
|
||||
)}
|
||||
<p className="font-bold">Threads</p>
|
||||
</div>
|
||||
{loaderThreads ? (
|
||||
{threadAndMessageMigrationState === 'in_progress' ? (
|
||||
<Spinner />
|
||||
) : (
|
||||
!threadsMessagesMigrationSuccess && (
|
||||
threadAndMessageMigrationState !== 'success' && (
|
||||
<Button
|
||||
size="small"
|
||||
theme="ghost"
|
||||
onClick={() => handleStartMigrationThreads()}
|
||||
onClick={migrateThreadsAndMessages}
|
||||
>
|
||||
Retry
|
||||
</Button>
|
||||
@ -207,16 +177,16 @@ const ModalMigrations = () => {
|
||||
<div
|
||||
className={twMerge(
|
||||
'my-2 flex justify-between rounded-lg border border-[hsla(var(--app-border))] p-3',
|
||||
modelsMigrationSuccess
|
||||
modelMigrationState === 'success'
|
||||
? 'bg-[hsla(var(--success-bg-soft))]'
|
||||
: 'bg-[hsla(var(--destructive-bg-soft))]',
|
||||
loaderModels && 'bg-trasparent'
|
||||
modelMigrationState === 'in_progress' && 'bg-trasparent'
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center gap-x-1.5">
|
||||
{!loaderModels && (
|
||||
{modelMigrationState !== 'in_progress' && (
|
||||
<>
|
||||
{modelsMigrationSuccess ? (
|
||||
{modelMigrationState === 'success' ? (
|
||||
<Badge theme="success">Success</Badge>
|
||||
) : (
|
||||
<Badge theme="destructive">Failed</Badge>
|
||||
@ -225,14 +195,14 @@ const ModalMigrations = () => {
|
||||
)}
|
||||
<p className="font-bold">Models</p>
|
||||
</div>
|
||||
{loaderModels ? (
|
||||
{modelMigrationState === 'in_progress' ? (
|
||||
<Spinner />
|
||||
) : (
|
||||
!modelsMigrationSuccess && (
|
||||
modelMigrationState === 'failed' && (
|
||||
<Button
|
||||
size="small"
|
||||
theme="ghost"
|
||||
onClick={() => handleStartMigrationModels()}
|
||||
onClick={migratingModels}
|
||||
>
|
||||
Retry
|
||||
</Button>
|
||||
@ -242,17 +212,15 @@ const ModalMigrations = () => {
|
||||
<div className="flex justify-end">
|
||||
<Button
|
||||
className="mt-2"
|
||||
disabled={loaderThreads || loaderModels}
|
||||
onClick={() => {
|
||||
setDidShowMigrationWarning(false)
|
||||
}}
|
||||
disabled={disableDismissButton}
|
||||
onClick={onDismiss}
|
||||
>
|
||||
Done
|
||||
</Button>
|
||||
</div>
|
||||
</Fragment>
|
||||
)}
|
||||
</>
|
||||
</Fragment>
|
||||
}
|
||||
/>
|
||||
)
|
||||
|
||||
@ -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) => {
|
||||
<Toaster />
|
||||
</Fragment>
|
||||
)}
|
||||
<ModalMigrations />
|
||||
</QueryClientProvider>
|
||||
</JotaiWrapper>
|
||||
</ThemeWrapper>
|
||||
|
||||
@ -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')
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -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<boolean> => {
|
||||
// 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
|
||||
|
||||
// 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
|
||||
)
|
||||
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 ?? ''
|
||||
|
||||
// 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)
|
||||
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)
|
||||
|
||||
// we finished with thread, now continue with messages
|
||||
const janMessages = threadsAndMessages.messages.filter(
|
||||
(m) => m.thread_id === thread.id
|
||||
)
|
||||
|
||||
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.log(err)
|
||||
setThreadsMessagesMigrationSuccess(false)
|
||||
}
|
||||
}, [
|
||||
assistants,
|
||||
getJanThreadsAndMessages,
|
||||
threadsMessagesMigrationSuccess,
|
||||
createThread,
|
||||
updateThread,
|
||||
setThreadsMessagesMigrationSuccess,
|
||||
createMessage,
|
||||
])
|
||||
|
||||
|
||||
@ -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 (
|
||||
<div className="flex w-full flex-col items-start justify-between gap-4 border-b border-[hsla(var(--app-border))] py-4 first:pt-0 last:border-none sm:flex-row">
|
||||
<div className="flex-shrink-0 space-y-1">
|
||||
<div className="flex gap-x-2">
|
||||
<h6 className="font-semibold capitalize">Clear logs</h6>
|
||||
</div>
|
||||
<p className="font-medium leading-relaxed text-[hsla(var(--text-secondary))]">
|
||||
Clear all logs from Jan app.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-row gap-x-2">
|
||||
<Button theme="primary" onClick={onStartMigrationClick}>
|
||||
Start migration
|
||||
</Button>
|
||||
<Button theme="destructive" onClick={onCleanUpDataClick}>
|
||||
Remove threads and messages
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default DataMigration
|
||||
@ -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 */}
|
||||
{/* <FactoryReset /> */}
|
||||
{experimentalEnabled && <DataMigration />}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
)
|
||||
|
||||
@ -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={
|
||||
<div>
|
||||
<div className="flex flex-row items-center space-x-2 pb-3">
|
||||
{/* <label className="text-[hsla(var(--text-secondary)] text-xs">
|
||||
{modelFolder}
|
||||
</label>
|
||||
<Button
|
||||
theme="ghost"
|
||||
size="small"
|
||||
variant="outline"
|
||||
onClick={onOpenModelFolderClick}
|
||||
>
|
||||
{openFileTitle()}
|
||||
</Button> */}
|
||||
</div>
|
||||
|
||||
<Fragment>
|
||||
<div className="mb-2 space-y-3">
|
||||
{importingModels.map((model) => (
|
||||
<ImportingModelItem key={model.importId} model={model} />
|
||||
@ -75,7 +74,7 @@ const ImportingModelModal = () => {
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Fragment>
|
||||
}
|
||||
/>
|
||||
)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user