fix: some bugs for import model (#2181)

* fix: some bugs for import model

Signed-off-by: James <james@jan.ai>

Signed-off-by: James <james@jan.ai>
Co-authored-by: James <james@jan.ai>
This commit is contained in:
NamH 2024-02-27 23:59:37 +07:00 committed by GitHub
parent 95946ab9f2
commit d7070d8c4a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 212 additions and 116 deletions

View File

@ -49,7 +49,7 @@ export enum DownloadEvent {
export enum LocalImportModelEvent { export enum LocalImportModelEvent {
onLocalImportModelUpdate = 'onLocalImportModelUpdate', onLocalImportModelUpdate = 'onLocalImportModelUpdate',
onLocalImportModelError = 'onLocalImportModelError', onLocalImportModelFailed = 'onLocalImportModelFailed',
onLocalImportModelSuccess = 'onLocalImportModelSuccess', onLocalImportModelSuccess = 'onLocalImportModelSuccess',
onLocalImportModelFinished = 'onLocalImportModelFinished', onLocalImportModelFinished = 'onLocalImportModelFinished',
} }

View File

@ -65,7 +65,7 @@ const joinPath: (paths: string[]) => Promise<string> = (paths) => global.core.ap
* @param path - The path to retrieve. * @param path - The path to retrieve.
* @returns {Promise<string>} A promise that resolves with the basename. * @returns {Promise<string>} A promise that resolves with the basename.
*/ */
const baseName: (paths: string[]) => Promise<string> = (path) => global.core.api?.baseName(path) const baseName: (paths: string) => Promise<string> = (path) => global.core.api?.baseName(path)
/** /**
* Opens an external URL in the default web browser. * Opens an external URL in the default web browser.

View File

@ -19,4 +19,5 @@ export type ImportingModel = {
status: ImportingModelStatus status: ImportingModelStatus
format: string format: string
percentage?: number percentage?: number
error?: string
} }

View File

@ -16,6 +16,7 @@ import {
OptionType, OptionType,
ImportingModel, ImportingModel,
LocalImportModelEvent, LocalImportModelEvent,
baseName,
} from '@janhq/core' } from '@janhq/core'
import { extractFileName } from './helpers/path' import { extractFileName } from './helpers/path'
@ -488,7 +489,7 @@ export default class JanModelExtension extends ModelExtension {
return return
} }
const binaryFileName = extractFileName(modelBinaryPath, '') const binaryFileName = await baseName(modelBinaryPath)
const model: Model = { const model: Model = {
...defaultModel, ...defaultModel,
@ -555,7 +556,7 @@ export default class JanModelExtension extends ModelExtension {
model: ImportingModel, model: ImportingModel,
optionType: OptionType optionType: OptionType
): Promise<Model> { ): Promise<Model> {
const binaryName = extractFileName(model.path, '').replace(/\s/g, '') const binaryName = (await baseName(model.path)).replace(/\s/g, '')
let modelFolderName = binaryName let modelFolderName = binaryName
if (binaryName.endsWith(JanModelExtension._supportedModelFormat)) { if (binaryName.endsWith(JanModelExtension._supportedModelFormat)) {
@ -568,7 +569,7 @@ export default class JanModelExtension extends ModelExtension {
const modelFolderPath = await this.getModelFolderName(modelFolderName) const modelFolderPath = await this.getModelFolderName(modelFolderName)
await fs.mkdirSync(modelFolderPath) await fs.mkdirSync(modelFolderPath)
const uniqueFolderName = modelFolderPath.split('/').pop() const uniqueFolderName = await baseName(modelFolderPath)
const modelBinaryFile = binaryName.endsWith( const modelBinaryFile = binaryName.endsWith(
JanModelExtension._supportedModelFormat JanModelExtension._supportedModelFormat
) )
@ -637,14 +638,21 @@ export default class JanModelExtension extends ModelExtension {
for (const model of models) { for (const model of models) {
events.emit(LocalImportModelEvent.onLocalImportModelUpdate, model) events.emit(LocalImportModelEvent.onLocalImportModelUpdate, model)
try {
const importedModel = await this.importModel(model, optionType) const importedModel = await this.importModel(model, optionType)
events.emit(LocalImportModelEvent.onLocalImportModelSuccess, { events.emit(LocalImportModelEvent.onLocalImportModelSuccess, {
...model, ...model,
modelId: importedModel.id, modelId: importedModel.id,
}) })
importedModels.push(importedModel) importedModels.push(importedModel)
} catch (err) {
events.emit(LocalImportModelEvent.onLocalImportModelFailed, {
...model,
error: err,
})
} }
}
events.emit( events.emit(
LocalImportModelEvent.onLocalImportModelFinished, LocalImportModelEvent.onLocalImportModelFinished,
importedModels importedModels

View File

@ -12,6 +12,7 @@ import { useSetAtom } from 'jotai'
import { snackbar } from '../Toast' import { snackbar } from '../Toast'
import { import {
setImportingModelErrorAtom,
setImportingModelSuccessAtom, setImportingModelSuccessAtom,
updateImportingModelProgressAtom, updateImportingModelProgressAtom,
} from '@/helpers/atoms/Model.atom' } from '@/helpers/atoms/Model.atom'
@ -21,6 +22,7 @@ const ModelImportListener = ({ children }: PropsWithChildren) => {
updateImportingModelProgressAtom updateImportingModelProgressAtom
) )
const setImportingModelSuccess = useSetAtom(setImportingModelSuccessAtom) const setImportingModelSuccess = useSetAtom(setImportingModelSuccessAtom)
const setImportingModelFailed = useSetAtom(setImportingModelErrorAtom)
const onImportModelUpdate = useCallback( const onImportModelUpdate = useCallback(
async (state: ImportingModel) => { async (state: ImportingModel) => {
@ -30,6 +32,14 @@ const ModelImportListener = ({ children }: PropsWithChildren) => {
[updateImportingModelProgress] [updateImportingModelProgress]
) )
const onImportModelFailed = useCallback(
async (state: ImportingModel) => {
if (!state.importId) return
setImportingModelFailed(state.importId, state.error ?? '')
},
[setImportingModelFailed]
)
const onImportModelSuccess = useCallback( const onImportModelSuccess = useCallback(
(state: ImportingModel) => { (state: ImportingModel) => {
if (!state.modelId) return if (!state.modelId) return
@ -62,6 +72,10 @@ const ModelImportListener = ({ children }: PropsWithChildren) => {
LocalImportModelEvent.onLocalImportModelFinished, LocalImportModelEvent.onLocalImportModelFinished,
onImportModelFinished onImportModelFinished
) )
events.on(
LocalImportModelEvent.onLocalImportModelFailed,
onImportModelFailed
)
return () => { return () => {
console.debug('ModelImportListener: unregistering event listeners...') console.debug('ModelImportListener: unregistering event listeners...')
@ -77,8 +91,17 @@ const ModelImportListener = ({ children }: PropsWithChildren) => {
LocalImportModelEvent.onLocalImportModelFinished, LocalImportModelEvent.onLocalImportModelFinished,
onImportModelFinished onImportModelFinished
) )
events.off(
LocalImportModelEvent.onLocalImportModelFailed,
onImportModelFailed
)
} }
}, [onImportModelUpdate, onImportModelSuccess, onImportModelFinished]) }, [
onImportModelUpdate,
onImportModelSuccess,
onImportModelFinished,
onImportModelFailed,
])
return <Fragment>{children}</Fragment> return <Fragment>{children}</Fragment>
} }

View File

@ -67,6 +67,24 @@ export const updateImportingModelProgressAtom = atom(
} }
) )
export const setImportingModelErrorAtom = atom(
null,
(get, set, importId: string, error: string) => {
const model = get(importingModelsAtom).find((x) => x.importId === importId)
if (!model) return
const newModel: ImportingModel = {
...model,
status: 'FAILED',
}
console.error(`Importing model ${model} failed`, error)
const newList = get(importingModelsAtom).map((m) =>
m.importId === importId ? newModel : m
)
set(importingModelsAtom, newList)
}
)
export const setImportingModelSuccessAtom = atom( export const setImportingModelSuccessAtom = atom(
null, null,
(get, set, importId: string, modelId: string) => { (get, set, importId: string, modelId: string) => {

View File

@ -0,0 +1,55 @@
import { useCallback } from 'react'
import { ImportingModel } from '@janhq/core'
import { useSetAtom } from 'jotai'
import { v4 as uuidv4 } from 'uuid'
import { snackbar } from '@/containers/Toast'
import { getFileInfoFromFile } from '@/utils/file'
import { setImportModelStageAtom } from './useImportModel'
import { importingModelsAtom } from '@/helpers/atoms/Model.atom'
export default function useDropModelBinaries() {
const setImportingModels = useSetAtom(importingModelsAtom)
const setImportModelStage = useSetAtom(setImportModelStageAtom)
const onDropModels = useCallback(
async (acceptedFiles: File[]) => {
const files = await getFileInfoFromFile(acceptedFiles)
const unsupportedFiles = files.filter(
(file) => !file.path.endsWith('.gguf')
)
const supportedFiles = files.filter((file) => file.path.endsWith('.gguf'))
const importingModels: ImportingModel[] = supportedFiles.map((file) => ({
importId: uuidv4(),
modelId: undefined,
name: file.name.replace('.gguf', ''),
description: '',
path: file.path,
tags: [],
size: file.size,
status: 'PREPARING',
format: 'gguf',
}))
if (unsupportedFiles.length > 0) {
snackbar({
description: `File has to be a .gguf file`,
type: 'error',
})
}
if (importingModels.length === 0) return
setImportingModels(importingModels)
setImportModelStage('MODEL_SELECTED')
},
[setImportModelStage, setImportingModels]
)
return { onDropModels }
}

View File

@ -1,6 +1,12 @@
import { useCallback, useEffect, useMemo, useState } from 'react' import { useCallback, useEffect, useState } from 'react'
import { Model, ModelEvent, events, openFileExplorer } from '@janhq/core' import {
Model,
ModelEvent,
events,
joinPath,
openFileExplorer,
} from '@janhq/core'
import { import {
Modal, Modal,
ModalContent, ModalContent,
@ -47,6 +53,7 @@ const EditModelInfoModal: React.FC = () => {
const janDataFolder = useAtomValue(janDataFolderPathAtom) const janDataFolder = useAtomValue(janDataFolderPathAtom)
const updateImportingModel = useSetAtom(updateImportingModelAtom) const updateImportingModel = useSetAtom(updateImportingModelAtom)
const { updateModelInfo } = useImportModel() const { updateModelInfo } = useImportModel()
const [modelPath, setModelPath] = useState<string>('')
const editingModel = importingModels.find( const editingModel = importingModels.find(
(model) => model.importId === editingModelId (model) => model.importId === editingModelId
@ -88,13 +95,19 @@ const EditModelInfoModal: React.FC = () => {
setEditingModelId(undefined) setEditingModelId(undefined)
} }
const modelFolderPath = useMemo(() => { useEffect(() => {
return `${janDataFolder}/models/${editingModel?.modelId}` const getModelPath = async () => {
const modelId = editingModel?.modelId
if (!modelId) return ''
const path = await joinPath([janDataFolder, 'models', modelId])
setModelPath(path)
}
getModelPath()
}, [janDataFolder, editingModel]) }, [janDataFolder, editingModel])
const onShowInFinderClick = useCallback(() => { const onShowInFinderClick = useCallback(() => {
openFileExplorer(modelFolderPath) openFileExplorer(modelPath)
}, [modelFolderPath]) }, [modelPath])
if (!editingModel) { if (!editingModel) {
setImportModelStage('IMPORTING_MODEL') setImportModelStage('IMPORTING_MODEL')
@ -104,7 +117,10 @@ const EditModelInfoModal: React.FC = () => {
} }
return ( return (
<Modal open={importModelStage === 'EDIT_MODEL_INFO'}> <Modal
open={importModelStage === 'EDIT_MODEL_INFO'}
onOpenChange={onCancelClick}
>
<ModalContent> <ModalContent>
<ModalHeader> <ModalHeader>
<ModalTitle>Edit Model Information</ModalTitle> <ModalTitle>Edit Model Information</ModalTitle>
@ -130,7 +146,7 @@ const EditModelInfoModal: React.FC = () => {
</div> </div>
<div className="mt-1 flex flex-row items-center space-x-2"> <div className="mt-1 flex flex-row items-center space-x-2">
<span className="line-clamp-1 text-xs font-normal text-[#71717A]"> <span className="line-clamp-1 text-xs font-normal text-[#71717A]">
{modelFolderPath} {modelPath}
</span> </span>
<Button themes="ghost" onClick={onShowInFinderClick}> <Button themes="ghost" onClick={onShowInFinderClick}>
{openFileTitle()} {openFileTitle()}

View File

@ -15,7 +15,8 @@ const ImportInProgressIcon: React.FC<Props> = ({
const [isHovered, setIsHovered] = useState(false) const [isHovered, setIsHovered] = useState(false)
const onMouseOver = () => { const onMouseOver = () => {
setIsHovered(true) // for now we don't allow user to cancel importing
setIsHovered(false)
} }
const onMouseOut = () => { const onMouseOut = () => {

View File

@ -1,6 +1,10 @@
import { useCallback, useMemo } from 'react'
import { ImportingModel } from '@janhq/core/.' import { ImportingModel } from '@janhq/core/.'
import { useSetAtom } from 'jotai' import { useSetAtom } from 'jotai'
import { AlertCircle } from 'lucide-react'
import { setImportModelStageAtom } from '@/hooks/useImportModel' import { setImportModelStageAtom } from '@/hooks/useImportModel'
import { toGibibytes } from '@/utils/converter' import { toGibibytes } from '@/utils/converter'
@ -16,28 +20,39 @@ type Props = {
const ImportingModelItem: React.FC<Props> = ({ model }) => { const ImportingModelItem: React.FC<Props> = ({ model }) => {
const setImportModelStage = useSetAtom(setImportModelStageAtom) const setImportModelStage = useSetAtom(setImportModelStageAtom)
const setEditingModelId = useSetAtom(editingModelIdAtom) const setEditingModelId = useSetAtom(editingModelIdAtom)
const sizeInGb = toGibibytes(model.size)
const onEditModelInfoClick = () => { const onEditModelInfoClick = useCallback(() => {
setEditingModelId(model.importId) setEditingModelId(model.importId)
setImportModelStage('EDIT_MODEL_INFO') setImportModelStage('EDIT_MODEL_INFO')
} }, [setImportModelStage, setEditingModelId, model.importId])
const onDeleteModelClick = () => {} const onDeleteModelClick = useCallback(() => {}, [])
const displayStatus = useMemo(() => {
if (model.status === 'FAILED') {
return 'Failed'
} else {
return toGibibytes(model.size)
}
}, [model.status, model.size])
return ( return (
<div className="flex w-full flex-row items-center space-x-3 rounded-lg border px-4 py-3"> <div className="flex w-full flex-row items-center space-x-3 rounded-lg border px-4 py-3">
<p className="line-clamp-1 flex-1">{model.name}</p> <p className="line-clamp-1 flex-1 font-semibold text-[#09090B]">
<p>{sizeInGb}</p> {model.name}
</p>
<p className="text-[#71717A]">{displayStatus}</p>
{model.status === 'IMPORTED' || model.status === 'FAILED' ? ( {model.status === 'IMPORTED' && (
<ImportSuccessIcon onEditModelClick={onEditModelInfoClick} /> <ImportSuccessIcon onEditModelClick={onEditModelInfoClick} />
) : ( )}
{(model.status === 'IMPORTING' || model.status === 'PREPARING') && (
<ImportInProgressIcon <ImportInProgressIcon
percentage={model.percentage ?? 0} percentage={model.percentage ?? 0}
onDeleteModelClick={onDeleteModelClick} onDeleteModelClick={onDeleteModelClick}
/> />
)} )}
{model.status === 'FAILED' && <AlertCircle size={24} color="#F00" />}
</div> </div>
) )
} }

View File

@ -1,6 +1,6 @@
import { useCallback, useMemo } from 'react' import { useCallback, useEffect, useState } from 'react'
import { openFileExplorer } from '@janhq/core' import { joinPath, openFileExplorer } from '@janhq/core'
import { import {
Button, Button,
Modal, Modal,
@ -31,7 +31,15 @@ const ImportingModelModal: React.FC = () => {
const setImportModelStage = useSetAtom(setImportModelStageAtom) const setImportModelStage = useSetAtom(setImportModelStageAtom)
const janDataFolder = useAtomValue(janDataFolderPathAtom) const janDataFolder = useAtomValue(janDataFolderPathAtom)
const modelFolder = useMemo(() => `${janDataFolder}/models`, [janDataFolder]) const [modelFolder, setModelFolder] = useState('')
useEffect(() => {
const getModelPath = async () => {
const modelPath = await joinPath([janDataFolder, 'models'])
setModelFolder(modelPath)
}
getModelPath()
}, [janDataFolder])
const finishedImportModel = importingModels.filter( const finishedImportModel = importingModels.filter(
(model) => model.status === 'IMPORTED' (model) => model.status === 'IMPORTED'

View File

@ -2,7 +2,6 @@ import { useCallback, useState } from 'react'
import { useDropzone } from 'react-dropzone' import { useDropzone } from 'react-dropzone'
import { ImportingModel } from '@janhq/core'
import { Button, Input, ScrollArea } from '@janhq/uikit' import { Button, Input, ScrollArea } from '@janhq/uikit'
import { useAtomValue, useSetAtom } from 'jotai' import { useAtomValue, useSetAtom } from 'jotai'
@ -10,60 +9,29 @@ import { Plus, SearchIcon, UploadCloudIcon } from 'lucide-react'
import { twMerge } from 'tailwind-merge' import { twMerge } from 'tailwind-merge'
import { v4 as uuidv4 } from 'uuid' import useDropModelBinaries from '@/hooks/useDropModelBinaries'
import { setImportModelStageAtom } from '@/hooks/useImportModel' import { setImportModelStageAtom } from '@/hooks/useImportModel'
import { getFileInfoFromFile } from '@/utils/file'
import RowModel from './Row' import RowModel from './Row'
import { import { downloadedModelsAtom } from '@/helpers/atoms/Model.atom'
downloadedModelsAtom,
importingModelsAtom,
} from '@/helpers/atoms/Model.atom'
const Column = ['Name', 'Model ID', 'Size', 'Version', 'Status', ''] const Column = ['Name', 'Model ID', 'Size', 'Version', 'Status', '']
const Models: React.FC = () => { const Models: React.FC = () => {
const downloadedModels = useAtomValue(downloadedModelsAtom) const downloadedModels = useAtomValue(downloadedModelsAtom)
const setImportModelStage = useSetAtom(setImportModelStageAtom) const setImportModelStage = useSetAtom(setImportModelStageAtom)
const setImportingModels = useSetAtom(importingModelsAtom)
const [searchValue, setsearchValue] = useState('') const [searchValue, setsearchValue] = useState('')
const { onDropModels } = useDropModelBinaries()
const filteredDownloadedModels = downloadedModels const filteredDownloadedModels = downloadedModels
.filter((x) => x.name?.toLowerCase().includes(searchValue.toLowerCase())) .filter((x) => x.name?.toLowerCase().includes(searchValue.toLowerCase()))
.sort((a, b) => a.name.localeCompare(b.name)) .sort((a, b) => a.name.localeCompare(b.name))
const onDrop = useCallback(
(acceptedFiles: File[]) => {
const filePathWithSize = getFileInfoFromFile(acceptedFiles)
const importingModels: ImportingModel[] = filePathWithSize.map(
(file) => ({
importId: uuidv4(),
modelId: undefined,
name: file.name,
description: '',
path: file.path,
tags: [],
size: file.size,
status: 'PREPARING',
format: 'gguf',
})
)
if (importingModels.length === 0) return
setImportingModels(importingModels)
setImportModelStage('MODEL_SELECTED')
},
[setImportModelStage, setImportingModels]
)
const { getRootProps, isDragActive } = useDropzone({ const { getRootProps, isDragActive } = useDropzone({
noClick: true, noClick: true,
multiple: true, multiple: true,
onDrop, onDrop: onDropModels,
}) })
const onImportModelClick = useCallback(() => { const onImportModelClick = useCallback(() => {

View File

@ -1,7 +1,7 @@
import { useCallback } from 'react' import { useCallback } from 'react'
import { useDropzone } from 'react-dropzone' import { useDropzone } from 'react-dropzone'
import { ImportingModel, fs } from '@janhq/core' import { ImportingModel, baseName, fs } from '@janhq/core'
import { Modal, ModalContent, ModalHeader, ModalTitle } from '@janhq/uikit' import { Modal, ModalContent, ModalHeader, ModalTitle } from '@janhq/uikit'
import { useAtomValue, useSetAtom } from 'jotai' import { useAtomValue, useSetAtom } from 'jotai'
@ -9,16 +9,15 @@ import { UploadCloudIcon } from 'lucide-react'
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid'
import { snackbar } from '@/containers/Toast'
import useDropModelBinaries from '@/hooks/useDropModelBinaries'
import { import {
getImportModelStageAtom, getImportModelStageAtom,
setImportModelStageAtom, setImportModelStageAtom,
} from '@/hooks/useImportModel' } from '@/hooks/useImportModel'
import { import { FilePathWithSize } from '@/utils/file'
FilePathWithSize,
getFileInfoFromFile,
getFileNameFromPath,
} from '@/utils/file'
import { importingModelsAtom } from '@/helpers/atoms/Model.atom' import { importingModelsAtom } from '@/helpers/atoms/Model.atom'
@ -26,6 +25,7 @@ const SelectingModelModal: React.FC = () => {
const setImportModelStage = useSetAtom(setImportModelStageAtom) const setImportModelStage = useSetAtom(setImportModelStageAtom)
const importModelStage = useAtomValue(getImportModelStageAtom) const importModelStage = useAtomValue(getImportModelStageAtom)
const setImportingModels = useSetAtom(importingModelsAtom) const setImportingModels = useSetAtom(importingModelsAtom)
const { onDropModels } = useDropModelBinaries()
const onSelectFileClick = useCallback(async () => { const onSelectFileClick = useCallback(async () => {
const filePaths = await window.core?.api?.selectModelFiles() const filePaths = await window.core?.api?.selectModelFiles()
@ -36,7 +36,7 @@ const SelectingModelModal: React.FC = () => {
const fileStats = await fs.fileStat(filePath, true) const fileStats = await fs.fileStat(filePath, true)
if (!fileStats || fileStats.isDirectory) continue if (!fileStats || fileStats.isDirectory) continue
const fileName = getFileNameFromPath(filePath) const fileName = await baseName(filePath)
sanitizedFilePaths.push({ sanitizedFilePaths.push({
path: filePath, path: filePath,
name: fileName, name: fileName,
@ -44,12 +44,19 @@ const SelectingModelModal: React.FC = () => {
}) })
} }
const importingModels: ImportingModel[] = sanitizedFilePaths.map( const unsupportedFiles = sanitizedFilePaths.filter(
(file) => !file.path.endsWith('.gguf')
)
const supportedFiles = sanitizedFilePaths.filter((file) =>
file.path.endsWith('.gguf')
)
const importingModels: ImportingModel[] = supportedFiles.map(
({ path, name, size }: FilePathWithSize) => { ({ path, name, size }: FilePathWithSize) => {
return { return {
importId: uuidv4(), importId: uuidv4(),
modelId: undefined, modelId: undefined,
name: name, name: name.replace('.gguf', ''),
description: '', description: '',
path: path, path: path,
tags: [], tags: [],
@ -59,41 +66,22 @@ const SelectingModelModal: React.FC = () => {
} }
} }
) )
if (unsupportedFiles.length > 0) {
snackbar({
description: `File has to be a .gguf file`,
type: 'error',
})
}
if (importingModels.length === 0) return if (importingModels.length === 0) return
setImportingModels(importingModels) setImportingModels(importingModels)
setImportModelStage('MODEL_SELECTED') setImportModelStage('MODEL_SELECTED')
}, [setImportingModels, setImportModelStage]) }, [setImportingModels, setImportModelStage])
const onDrop = useCallback(
(acceptedFiles: File[]) => {
const filePathWithSize = getFileInfoFromFile(acceptedFiles)
const importingModels: ImportingModel[] = filePathWithSize.map(
(file) => ({
importId: uuidv4(),
modelId: undefined,
name: file.name,
description: '',
path: file.path,
tags: [],
size: file.size,
status: 'PREPARING',
format: 'gguf',
})
)
if (importingModels.length === 0) return
setImportingModels(importingModels)
setImportModelStage('MODEL_SELECTED')
},
[setImportModelStage, setImportingModels]
)
const { isDragActive, getRootProps } = useDropzone({ const { isDragActive, getRootProps } = useDropzone({
noClick: true, noClick: true,
multiple: true, multiple: true,
onDrop, onDrop: onDropModels,
}) })
const borderColor = isDragActive ? 'border-primary' : 'border-[#F4F4F5]' const borderColor = isDragActive ? 'border-primary' : 'border-[#F4F4F5]'

View File

@ -1,3 +1,5 @@
import { baseName } from '@janhq/core'
export type FilePathWithSize = { export type FilePathWithSize = {
path: string path: string
name: string name: string
@ -8,24 +10,17 @@ export interface FileWithPath extends File {
path?: string path?: string
} }
export const getFileNameFromPath = (filePath: string): string => { export const getFileInfoFromFile = async (
let fileName = filePath.split('/').pop() ?? ''
if (fileName.split('.').length > 1) {
fileName = fileName.split('.').slice(0, -1).join('.')
}
return fileName
}
export const getFileInfoFromFile = (
files: FileWithPath[] files: FileWithPath[]
): FilePathWithSize[] => { ): Promise<FilePathWithSize[]> => {
const result: FilePathWithSize[] = [] const result: FilePathWithSize[] = []
for (const file of files) { for (const file of files) {
if (file.path && file.path.length > 0) { if (file.path && file.path.length > 0) {
const fileName = await baseName(file.path)
result.push({ result.push({
path: file.path, path: file.path,
name: getFileNameFromPath(file.path), name: fileName,
size: file.size, size: file.size,
}) })
} }