Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions modelmux/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useState } from "react";
import { ChatInterface } from "@/components/ChatInterface/chat-interface";
import { ThemeProvider } from "@/components/theme-provider";
import { ManageModel } from "@/components/manage-model";
import { ModelManagerInterface } from "@/components/ModelManager/model-manager-interface";
import Layout from "@/components/sidebar/layout";
import WorkspaceInterface from "@/components/Workspace/workspace-interface";

Expand All @@ -14,7 +14,7 @@ function App() {
<ThemeProvider defaultTheme="dark" storageKey="vite-ui-theme">
<Layout setCurrentView={setCurrentView}>
{currentView === "chat" && <ChatInterface />}
{currentView === "models" && <ManageModel />}
{currentView === "models" && <ModelManagerInterface />}
{currentView === "workspaces" && <WorkspaceInterface />}
</Layout>
</ThemeProvider>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,12 @@ import {
TooltipTrigger,
} from "@/components/ui/tooltip";
import { useEffect, useState, useContext } from "react";
import { getCurrentModel, modelList } from "@/lib/ollama";
import { ModelResponse } from "ollama";
import { getCurrentModel, modelList, type OllamaModel } from "@/lib/ollama";
import { RetryContext } from "./Context/ChatContext";

export function ChatMessageActions() {
const [modelName, setModelName] = useState<string | undefined>("");
const [models, setModels] = useState<ModelResponse[]>([]);
const [models, setModels] = useState<OllamaModel[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
const [dropdownOpen, setDropdownOpen] = useState(false);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { Download, Loader2 } from "lucide-react";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { Button } from "@/components/ui/button";
import { Progress } from "@/components/ui/progress";
import { type AvailableModel } from "@/lib/ollama";

interface AvailableModelsTableProps {
models: AvailableModel[];
loading: Record<string, boolean>;
progress: Record<string, { percentage: number; status: string }>;
onInstall: (modelName: string) => void;
}

export function AvailableModelsTable({
models,
loading,
progress,
onInstall,
}: AvailableModelsTableProps) {
return (
<div className="space-y-4">
<h2 className="text-xl font-semibold">
Available Models ({models.length})
</h2>
<div className="border rounded-lg">
<Table>
<TableHeader>
<TableRow>
<TableHead>Model Name</TableHead>
<TableHead>Description</TableHead>
<TableHead>Updated</TableHead>
<TableHead className="text-right">Action</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{models.length === 0 ? (
<TableRow>
<TableCell
colSpan={4}
className="text-center text-muted-foreground"
>
No available models to install
</TableCell>
</TableRow>
) : (
models
.slice(0, 20)
.map((model) => (
<TableRow key={model.model_identifier}>
<TableCell className="font-medium">
{model.model_name}
</TableCell>
<TableCell className="max-w-xs truncate">
{model.description}
</TableCell>
<TableCell>
{new Date(model.last_updated).toLocaleDateString()}
</TableCell>
<TableCell className="text-right">
<div className="space-y-2">
<Button
variant="default"
size="sm"
onClick={() => onInstall(model.model_name)}
disabled={loading[model.model_name]}
>
{loading[model.model_name] ? (
<Loader2 className="h-4 w-4 animate-spin mr-2" />
) : (
<Download className="h-4 w-4 mr-2" />
)}
Install
</Button>
{loading[model.model_name] &&
progress[model.model_name] && (
<div className="w-full space-y-1">
<Progress
value={progress[model.model_name].percentage}
className="h-1"
/>
<div className="text-xs text-muted-foreground text-center">
{progress[model.model_name].percentage}% -{" "}
{progress[model.model_name].status}
</div>
</div>
)}
</div>
</TableCell>
</TableRow>
))
)}
</TableBody>
</Table>
</div>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { Loader2 } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { type OllamaModel } from "@/lib/ollama";

interface DeleteConfirmationDialogProps {
open: boolean;
model: OllamaModel | null;
loading: boolean;
onOpenChange: (open: boolean) => void;
onConfirm: () => void;
onCancel: () => void;
}

export function DeleteConfirmationDialog({
open,
model,
loading,
onOpenChange,
onConfirm,
onCancel,
}: DeleteConfirmationDialogProps) {
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent>
<DialogHeader>
<DialogTitle>Confirm Uninstall</DialogTitle>
<DialogDescription>
Are you sure you want to uninstall "{model?.name}"?
This action cannot be undone.
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button variant="outline" onClick={onCancel}>
Cancel
</Button>
<Button variant="destructive" onClick={onConfirm} disabled={loading}>
{loading ? (
<Loader2 className="h-4 w-4 animate-spin mr-2" />
) : null}
Uninstall
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}
24 changes: 24 additions & 0 deletions modelmux/src/components/ModelManager/components/error-display.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Button } from "@/components/ui/button";

interface ErrorDisplayProps {
error: string;
onDismiss: () => void;
}

export function ErrorDisplay({ error, onDismiss }: ErrorDisplayProps) {
return (
<div className="bg-destructive/15 border border-destructive/20 rounded-lg p-4">
<div className="flex items-center">
<span className="text-destructive font-medium">{error}</span>
<Button
variant="ghost"
size="sm"
onClick={onDismiss}
className="ml-auto h-6 w-6 p-0"
>
×
</Button>
</div>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { Trash2, Loader2 } from "lucide-react";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { Button } from "@/components/ui/button";
import {
formatModelSize,
formatModelName,
getModelTag,
type OllamaModel,
} from "@/lib/ollama";

interface InstalledModelsTableProps {
models: OllamaModel[];
loading: Record<string, boolean>;
onUninstall: (model: OllamaModel) => void;
}

export function InstalledModelsTable({
models,
loading,
onUninstall,
}: InstalledModelsTableProps) {
return (
<div className="space-y-4">
<h2 className="text-xl font-semibold">
Installed Models ({models.length})
</h2>
<div className="border rounded-lg">
<Table>
<TableHeader>
<TableRow>
<TableHead>Model Name</TableHead>
<TableHead>Size</TableHead>
<TableHead>Modified</TableHead>
<TableHead className="text-right">Action</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{models.length === 0 ? (
<TableRow>
<TableCell
colSpan={4}
className="text-center text-muted-foreground"
>
No models installed
</TableCell>
</TableRow>
) : (
models.map((model) => (
<TableRow key={model.name}>
<TableCell className="font-medium">
{formatModelName(model.name)}
<span className="text-sm text-muted-foreground ml-2">
:{getModelTag(model.name)}
</span>
</TableCell>
<TableCell>{formatModelSize(model.size)}</TableCell>
<TableCell>
{new Date(model.modified_at).toLocaleDateString()}
</TableCell>
<TableCell className="text-right">
<Button
variant="destructive"
size="sm"
onClick={() => onUninstall(model)}
disabled={loading[model.name]}
>
{loading[model.name] ? (
<Loader2 className="h-4 w-4 animate-spin mr-2" />
) : (
<Trash2 className="h-4 w-4 mr-2" />
)}
Uninstall
</Button>
</TableCell>
</TableRow>
))
)}
</TableBody>
</Table>
</div>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Loader2 } from "lucide-react";

export function LoadingDisplay() {
return (
<div className="flex flex-col h-full p-4">
<h1 className="text-2xl font-bold mb-4">Manage Models</h1>
<div className="flex items-center justify-center h-64">
<Loader2 className="h-8 w-8 animate-spin" />
<span className="ml-2">Loading models...</span>
</div>
</div>
);
}
Loading