diff --git a/apps/desktop/src/features/inference-server/instance-creator.tsx b/apps/desktop/src/features/inference-server/instance-creator.tsx
new file mode 100644
index 0000000..69ad87f
--- /dev/null
+++ b/apps/desktop/src/features/inference-server/instance-creator.tsx
@@ -0,0 +1,32 @@
+import { cn } from "@localai/theme/utils"
+import { SpinnerButton } from "@localai/ui/button"
+import { useState } from "react"
+
+export const InstanceCreator = () => {
+ const [isLoading, setIsLoading] = useState(false)
+
+ const handleClick = async () => {
+ setIsLoading(true)
+ // Add your logic here to handle adding a new instance
+ // For example:
+ await createNewInstance()
+ setIsLoading(false)
+ }
+
+ return (
+
+
+ {isLoading ? "..." : "Create New Instance"}
+
+
+ )
+}
+function createNewInstance() {
+ throw new Error("Function not implemented.")
+}
diff --git a/apps/desktop/src/features/inference-server/server-list-item.tsx b/apps/desktop/src/features/inference-server/server-list-item.tsx
new file mode 100644
index 0000000..887a3ca
--- /dev/null
+++ b/apps/desktop/src/features/inference-server/server-list-item.tsx
@@ -0,0 +1,77 @@
+import { cn } from "@localai/theme/utils"
+import { Button } from "@localai/ui/button"
+import { Input } from "@localai/ui/input"
+import { Textarea } from "@localai/ui/textarea"
+import {
+ ChevronLeftIcon,
+ ChevronRightIcon,
+ GearIcon
+} from "@radix-ui/react-icons"
+import dedent from "ts-dedent"
+
+import { useToggle } from "~features/layout/use-toggle"
+import { type ModelMetadata } from "~features/model-downloader/model-file"
+import { useGlobal } from "~providers/global"
+import { ModelProvider } from "~providers/model"
+
+import { ServerConfig } from "./server-config"
+
+export const ServerListItem = ({ model }: { model: ModelMetadata }) => {
+ const {
+ activeModelState: [activeModel]
+ } = useGlobal()
+ const [isConfigVisible, toggleConfig] = useToggle(false)
+ return (
+
+
+
+
+
+
+ {isConfigVisible && (
+ <>
+
+ >
+ )}
+
+
+ )
+}
diff --git a/apps/desktop/src/features/layout/index.tsx b/apps/desktop/src/features/layout/index.tsx
index a707550..e073ce9 100644
--- a/apps/desktop/src/features/layout/index.tsx
+++ b/apps/desktop/src/features/layout/index.tsx
@@ -72,6 +72,10 @@ const BottomBar = () => {
on={}
off={}
/>
+
+ Server Manager
+
+
)
}
diff --git a/apps/desktop/src/pages/index.tsx b/apps/desktop/src/pages/index.tsx
index f6d1f90..b13ba75 100644
--- a/apps/desktop/src/pages/index.tsx
+++ b/apps/desktop/src/pages/index.tsx
@@ -2,6 +2,7 @@ import { useMemo } from "react"
import { Route, useGlobal } from "~providers/global"
import { ModelManagerView } from "~views/model-manager"
+import { ServerManagerView } from "~views/server-manager"
import { ThreadView } from "~views/thread"
// Since NextJS router doesn't work with SPA yet, use manual router for now.
@@ -18,6 +19,8 @@ function IndexPage() {
case Route.ModelManager:
default:
return
+ case Route.ServerManager:
+ return
}
}, [currentRoute])
}
diff --git a/apps/desktop/src/providers/global.ts b/apps/desktop/src/providers/global.ts
index 102e5e5..1238704 100644
--- a/apps/desktop/src/providers/global.ts
+++ b/apps/desktop/src/providers/global.ts
@@ -21,7 +21,8 @@ import { getCachedIntegrity } from "~providers/model"
export enum Route {
ModelManager = "model-manager",
- Thread = "thread"
+ Thread = "thread",
+ ServerManager = "ServerManager"
}
let _prefix: string
@@ -146,6 +147,14 @@ const useGlobalProvider = () => {
}
}, [activeThread, activeRoute])
+ useEffect(() => {
+ if (routeState[0] === Route.ServerManager) {
+ setTitle("Server Manager")
+ } else if (activeThreadState[0]) {
+ setTitle(activeThreadState[0].name.slice(0, -2))
+ }
+ }, [activeThreadState[0], routeState[0]])
+
return {
getWindow: () => windowRef.current,
loadModel,
diff --git a/apps/desktop/src/providers/server.ts b/apps/desktop/src/providers/server.ts
new file mode 100644
index 0000000..81d5bce
--- /dev/null
+++ b/apps/desktop/src/providers/server.ts
@@ -0,0 +1,20 @@
+"use client"
+
+import { createProvider } from "puro"
+import { useContext } from "react"
+
+import type { ModelMetadata } from "~features/model-downloader/model-file"
+
+/**
+ * Requires a global provider
+ */
+const useServerProvider = ({ model }: { model: ModelMetadata }) => {
+ return {
+ model
+ }
+}
+
+const { BaseContext, Provider } = createProvider(useServerProvider)
+
+export const useServer = () => useContext(BaseContext)
+export const ServerProvider = Provider
diff --git a/apps/desktop/src/views/server-manager.tsx b/apps/desktop/src/views/server-manager.tsx
new file mode 100644
index 0000000..8381eaf
--- /dev/null
+++ b/apps/desktop/src/views/server-manager.tsx
@@ -0,0 +1,103 @@
+import { Button, SpinnerButton } from "@localai/ui/button"
+import { Input } from "@localai/ui/input"
+import {
+ DotsHorizontalIcon,
+ OpenInNewWindowIcon,
+ ReloadIcon
+} from "@radix-ui/react-icons"
+import { open as dialogOpen } from "@tauri-apps/api/dialog"
+
+import { InstanceCreator } from "~features/inference-server/instance-creator"
+import { ServerListItem } from "~features/inference-server/server-list-item"
+import { InvokeCommand, invoke } from "~features/invoke"
+import { ViewBody, ViewContainer, ViewHeader } from "~features/layout/view"
+import { ChatSideBarToggle } from "~features/thread/side-bar"
+import { useGlobal } from "~providers/global"
+
+export function ServerManagerView() {
+ const {
+ activeModelState: [activeModel],
+ modelsDirectoryState: {
+ isRefreshing,
+ modelsDirectory,
+ models,
+ updateModelsDirectory
+ }
+ } = useGlobal()
+
+ return (
+
+
+
+
+ {!!modelsDirectory && (
+ {
+ await updateModelsDirectory()
+ }}
+ />
+ )}
+
+
+
+
+
+
+
+
+
+ {models.length === 0 && (
+
+ {`Here you can create and save custom instances of your models to use as servers. To begin, just click the "+" button.`}
+
+ )}
+
+ {models
+ .sort((a, b) =>
+ activeModel?.path === a.path
+ ? -1
+ : activeModel?.path === b.path
+ ? 1
+ : 0
+ )
+ .map((model) => (
+
+ ))}
+
+
+
+ )
+}
diff --git a/models/_shared.ts b/models/_shared.ts
index ae39fba..63eca80 100644
--- a/models/_shared.ts
+++ b/models/_shared.ts
@@ -21,6 +21,7 @@ export enum LicenseType {
}
export type ModelInfo = {
+ path: any
name?: string
size: number
downloadUrl: string