diff --git a/web-app/src/containers/DownloadManegement.tsx b/web-app/src/containers/DownloadManegement.tsx
index 4aecfc5fd..89cd0ddfe 100644
--- a/web-app/src/containers/DownloadManegement.tsx
+++ b/web-app/src/containers/DownloadManegement.tsx
@@ -5,12 +5,15 @@ import {
} from '@/components/ui/popover'
import { Progress } from '@/components/ui/progress'
import { useDownloadStore } from '@/hooks/useDownloadStore'
+import { useModelProvider } from '@/hooks/useModelProvider'
import { abortDownload } from '@/services/models'
+import { getProviders } from '@/services/providers'
import { DownloadEvent, DownloadState, events } from '@janhq/core'
import { IconX } from '@tabler/icons-react'
import { useCallback, useEffect, useMemo } from 'react'
export function DownloadManagement() {
+ const { setProviders } = useModelProvider()
const { downloads, updateProgress, removeDownload } = useDownloadStore()
const downloadCount = useMemo(
() => Object.keys(downloads).length,
@@ -72,8 +75,9 @@ export function DownloadManagement() {
async (state: DownloadState) => {
console.debug('onFileDownloadSuccess', state)
removeDownload(state.modelId)
+ getProviders().then(setProviders)
},
- [removeDownload]
+ [removeDownload, setProviders]
)
useEffect(() => {
diff --git a/web-app/src/containers/dialogs/DeleteModel.tsx b/web-app/src/containers/dialogs/DeleteModel.tsx
index 9d428a502..60e86debe 100644
--- a/web-app/src/containers/dialogs/DeleteModel.tsx
+++ b/web-app/src/containers/dialogs/DeleteModel.tsx
@@ -9,22 +9,37 @@ import {
DialogTitle,
DialogTrigger,
} from '@/components/ui/dialog'
+import { useModelProvider } from '@/hooks/useModelProvider'
+import { deleteModel } from '@/services/models'
+import { getProviders } from '@/services/providers'
import { IconTrash } from '@tabler/icons-react'
import { useState, useEffect } from 'react'
import { toast } from 'sonner'
-type DialoDeleteModelProps = {
+type DialogDeleteModelProps = {
provider: ModelProvider
modelId?: string
}
-export const DialoDeleteModel = ({
+export const DialogDeleteModel = ({
provider,
modelId,
-}: DialoDeleteModelProps) => {
+}: DialogDeleteModelProps) => {
const [selectedModelId, setSelectedModelId] = useState('')
+ const { setProviders, deleteModel: deleteModelCache } = useModelProvider()
+
+ const removeModel = async () => {
+ deleteModelCache(selectedModelId)
+ deleteModel(selectedModelId).then(() => {
+ getProviders().then(setProviders)
+ toast.success('Delete Model', {
+ id: `delete-model-${selectedModel?.id}`,
+ description: `Model ${selectedModel?.id} has been permanently deleted.`,
+ })
+ })
+ }
// Initialize with the provided model ID or the first model if available
useEffect(() => {
@@ -68,16 +83,7 @@ export const DialoDeleteModel = ({
-
diff --git a/web-app/src/hooks/useLocalApiServer.ts b/web-app/src/hooks/useLocalApiServer.ts
index 65d1c1efa..efb6df71b 100644
--- a/web-app/src/hooks/useLocalApiServer.ts
+++ b/web-app/src/hooks/useLocalApiServer.ts
@@ -21,6 +21,9 @@ type LocalApiServerState = {
// Verbose server logs
verboseLogs: boolean
setVerboseLogs: (value: boolean) => void
+ // Server status
+ serverStatus: 'running' | 'stopped' | 'pending'
+ setServerStatus: (value: 'running' | 'stopped' | 'pending') => void
}
export const useLocalApiServer = create()(
@@ -38,6 +41,8 @@ export const useLocalApiServer = create()(
setCorsEnabled: (value) => set({ corsEnabled: value }),
verboseLogs: true,
setVerboseLogs: (value) => set({ verboseLogs: value }),
+ serverStatus: 'stopped',
+ setServerStatus: (value) => set({ serverStatus: value }),
}),
{
name: localStoregeKey.settingLocalApiServer,
diff --git a/web-app/src/hooks/useModelProvider.ts b/web-app/src/hooks/useModelProvider.ts
index 04288fea1..24a2084cc 100644
--- a/web-app/src/hooks/useModelProvider.ts
+++ b/web-app/src/hooks/useModelProvider.ts
@@ -13,6 +13,7 @@ type ModelProviderState = {
providerName: string,
modelName: string
) => Model | undefined
+ deleteModel: (modelId: string) => void
}
export const useModelProvider = create()(
@@ -31,7 +32,9 @@ export const useModelProvider = create()(
const models = existingProvider?.models || []
const mergedModels = [
...(provider?.models ?? []),
- ...models.filter((e) => !provider?.models.some((m) => m.id === e.id)),
+ ...models.filter(
+ (e) => !provider?.models.some((m) => m.id === e.id)
+ ),
]
return {
...provider,
@@ -98,6 +101,19 @@ export const useModelProvider = create()(
return modelObject
},
+ deleteModel: (modelId: string) => {
+ set((state) => ({
+ providers: state.providers.map((provider) => {
+ const models = provider.models.filter(
+ (model) => model.id !== modelId
+ )
+ return {
+ ...provider,
+ models,
+ }
+ }),
+ }))
+ },
}),
{
name: localStoregeKey.modelProvider,
diff --git a/web-app/src/routes/settings/local-api-server.tsx b/web-app/src/routes/settings/local-api-server.tsx
index 98839ba4f..07e0e7e29 100644
--- a/web-app/src/routes/settings/local-api-server.tsx
+++ b/web-app/src/routes/settings/local-api-server.tsx
@@ -19,8 +19,44 @@ export const Route = createFileRoute(route.settings.local_api_server as any)({
function LocalAPIServer() {
const { t } = useTranslation()
- const { corsEnabled, setCorsEnabled, verboseLogs, setVerboseLogs } =
- useLocalApiServer()
+ const {
+ corsEnabled,
+ setCorsEnabled,
+ verboseLogs,
+ setVerboseLogs,
+ serverHost,
+ serverPort,
+ apiPrefix,
+ serverStatus,
+ setServerStatus,
+ } = useLocalApiServer()
+
+ const toggleAPIServer = async () => {
+ setServerStatus('pending')
+ if (serverStatus === 'stopped') {
+ window.core?.api
+ ?.startServer({
+ host: serverHost,
+ port: serverPort,
+ prefix: apiPrefix,
+ isCorsEnabled: corsEnabled,
+ isVerboseEnabled: verboseLogs,
+ })
+ .then(() => {
+ setServerStatus('running')
+ })
+ } else {
+ window.core?.api
+ ?.stopServer()
+ .then(() => {
+ setServerStatus('stopped')
+ })
+ .catch((error: unknown) => {
+ console.error('Error stopping server:', error)
+ setServerStatus('stopped')
+ })
+ }
+ }
const handleOpenLogs = async () => {
try {
@@ -78,7 +114,10 @@ function LocalAPIServer() {
Start an OpenAI-compatible local HTTP server.
- Start Server
+
+ {`${serverStatus === 'running' ? 'Stop' : 'Start'}`}{' '}
+ Server
+
}
>
diff --git a/web-app/src/routes/settings/providers/$providerName.tsx b/web-app/src/routes/settings/providers/$providerName.tsx
index 83dc77269..3e8e8a0df 100644
--- a/web-app/src/routes/settings/providers/$providerName.tsx
+++ b/web-app/src/routes/settings/providers/$providerName.tsx
@@ -18,7 +18,7 @@ import { RenderMarkdown } from '@/containers/RenderMarkdown'
import { DialogEditModel } from '@/containers/dialogs/EditModel'
import { DialogAddModel } from '@/containers/dialogs/AddModel'
import { ModelSetting } from '@/containers/ModelSetting'
-import { DialoDeleteModel } from '@/containers/dialogs/DeleteModel'
+import { DialogDeleteModel } from '@/containers/dialogs/DeleteModel'
import Joyride, { CallBackProps, STATUS } from 'react-joyride'
import { CustomTooltipJoyRide } from '@/containers/CustomeTooltipJoyRide'
import { route } from '@/constants/routes'
@@ -250,7 +250,7 @@ function ProviderDetail() {
{model.settings && (
)}
-
diff --git a/web-app/src/services/models.ts b/web-app/src/services/models.ts
index a6420f8e4..575b882d5 100644
--- a/web-app/src/services/models.ts
+++ b/web-app/src/services/models.ts
@@ -35,6 +35,16 @@ export const fetchModelSources = async () => {
}
}
+/**
+ * Fetches the model hub.
+ * @returns A promise that resolves to the model hub.
+ */
+export const fetchModelHub = async () => {
+ return ExtensionManager.getInstance()
+ .get(ExtensionTypeEnum.Model)
+ ?.fetchModelsHub()
+}
+
/**
* Adds a new model source.
* @param source The source to add.
@@ -137,3 +147,23 @@ export const abortDownload = async (id: string) => {
throw error
}
}
+
+/**
+ * Deletes a model.
+ * @param id
+ * @returns
+ */
+export const deleteModel = async (id: string) => {
+ const extension = ExtensionManager.getInstance().get(
+ ExtensionTypeEnum.Model
+ )
+
+ if (!extension) throw new Error('Model extension not found')
+
+ try {
+ return await extension.deleteModel(id)
+ } catch (error) {
+ console.error('Failed to delete model:', error)
+ throw error
+ }
+}
diff --git a/web-app/src/services/providers.ts b/web-app/src/services/providers.ts
index 484b5255a..4c3b9e7b7 100644
--- a/web-app/src/services/providers.ts
+++ b/web-app/src/services/providers.ts
@@ -1,8 +1,9 @@
import { models as providerModels } from 'token.js'
import { mockModelProvider } from '@/mock/data'
-import { EngineManager, ModelManager } from '@janhq/core'
+import { EngineManager } from '@janhq/core'
import { ModelCapabilities } from '@/types/models'
import { modelSettings } from '@/lib/predefined'
+import { fetchModels } from './models'
export const getProviders = async (): Promise => {
const builtinProviders = mockModelProvider.map((provider) => {
@@ -42,8 +43,9 @@ export const getProviders = async (): Promise => {
for (const [key, value] of EngineManager.instance().engines) {
// TODO: Remove this when the cortex extension is removed
const providerName = key === 'cortex' ? 'llama.cpp' : key
+
const models =
- Array.from(ModelManager.instance().models.values()).filter(
+ ((await fetchModels()) ?? []).filter(
(model) =>
(model.engine === 'llama-cpp' ? 'llama.cpp' : model.engine) ===
providerName &&