/* eslint-disable @typescript-eslint/no-explicit-any */ import React, { useState, useEffect, useRef, useCallback } from 'react' import { InferenceEngine } from '@janhq/core' import { Button, ScrollArea, Badge, Switch, Input } from '@janhq/joi' import { useAtom } from 'jotai' import { SearchIcon } from 'lucide-react' import { Marked, Renderer } from 'marked' import Loader from '@/containers/Loader' import SetupRemoteModel from '@/containers/SetupRemoteModel' import { formatExtensionsName } from '@/utils/converter' import { extensionManager } from '@/extension' import Extension from '@/extension/Extension' import { inActiveEngineProviderAtom } from '@/helpers/atoms/Extension.atom' type EngineExtension = { provider: InferenceEngine } & Extension const ExtensionCatalog = () => { const [coreActiveExtensions, setCoreActiveExtensions] = useState( [] ) const [engineActiveExtensions, setEngineActiveExtensions] = useState< EngineExtension[] >([]) const [searchText, setSearchText] = useState('') const [showLoading, setShowLoading] = useState(false) const fileInputRef = useRef(null) useEffect(() => { const getAllSettings = async () => { const extensionsMenu = [] const engineMenu = [] const extensions = extensionManager.getAll() for (const extension of extensions) { const settings = await extension.getSettings() if ( typeof extension.getSettings === 'function' && 'provider' in extension && typeof extension.provider === 'string' ) { if ( (settings && settings.length > 0) || (await extension.installationState()) !== 'NotRequired' ) { engineMenu.push({ ...extension, provider: 'provider' in extension && typeof extension.provider === 'string' ? extension.provider : '', }) } } else { extensionsMenu.push({ ...extension, }) } } setCoreActiveExtensions(extensionsMenu) setEngineActiveExtensions(engineMenu as any) } getAllSettings() }, []) /** * Installs a extension by calling the `extensions.install` function with the extension file path. * If the installation is successful, the application is relaunched using the `coreAPI.relaunch` function. * @param e - The event object. */ const install = async (e: any) => { e.preventDefault() const extensionFile = e.target.files?.[0].path // Send the filename of the to be installed extension // to the main process for installation const installed = await extensionManager.install([extensionFile]) if (installed) window.core?.api?.relaunch() } /** * Uninstalls a extension by calling the `extensions.uninstall` function with the extension name. * If the uninstallation is successful, the application is relaunched using the `coreAPI.relaunch` function. * @param name - The name of the extension to uninstall. */ // eslint-disable-next-line @typescript-eslint/no-unused-vars const uninstall = async (name: string) => { // Send the filename of the to be uninstalled extension // to the main process for removal const res = await extensionManager.uninstall([name]) if (res) window.core?.api?.relaunch() } /** * Handles the change event of the extension file input element by setting the file name state. * Its to be used to display the extension file name of the selected file. * @param event - The change event object. */ const handleFileChange = (event: React.ChangeEvent) => { const file = event.target.files?.[0] if (file) { setShowLoading(true) install(event) } } const [inActiveEngineProvider, setInActiveEngineProvider] = useAtom( inActiveEngineProviderAtom ) const onSwitchChange = useCallback( (name: string) => { if (inActiveEngineProvider.includes(name)) { setInActiveEngineProvider( [...inActiveEngineProvider].filter((x) => x !== name) ) } else { setInActiveEngineProvider([...inActiveEngineProvider, name]) } }, [inActiveEngineProvider, setInActiveEngineProvider] ) return ( <>
} placeholder="Search" value={searchText} onChange={(e) => setSearchText(e.target.value)} clearable={searchText.length > 0} onClear={() => setSearchText('')} />
{engineActiveExtensions.length !== 0 && (
Model Providers
)} {engineActiveExtensions .filter((x) => x.name.includes(searchText.toLowerCase().trim())) .sort((a, b) => a.provider.localeCompare(b.provider)) .map((item, i) => { return (
{item.productName?.replace('Inference Engine', '') ?? formatExtensionsName(item.name)}
v{item.version}

{item.provider}

{!inActiveEngineProvider.includes(item.provider) && ( )} onSwitchChange(item.provider)} />
{
}
) })} {coreActiveExtensions.length > 0 && (
Core Extension
)} {coreActiveExtensions .filter((x) => x.name.includes(searchText.toLowerCase().trim())) .sort((a, b) => a.name.localeCompare(b.name)) .map((item, i) => { return (
{item.productName ?? formatExtensionsName(item.name)}
v{item.version}
{
}
) })}
{showLoading && } ) } const marked: Marked = new Marked({ renderer: { link: (href, title, text) => { return Renderer.prototype.link ?.apply(this, [href, title, text]) .replace( '