mirror of
https://github.com/lordmathis/llamactl.git
synced 2025-12-23 09:34:23 +00:00
Improve InstanceCard to display models for llama.cpp instances
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
// ui/src/components/InstanceCard.tsx
|
// ui/src/components/InstanceCard.tsx
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
import type { Instance } from "@/types/instance";
|
import type { Instance } from "@/types/instance";
|
||||||
import { Edit, FileText, Play, Square, Trash2, MoreHorizontal, Download, Boxes } from "lucide-react";
|
import { Edit, FileText, Play, Square, Trash2, MoreHorizontal, Download, Boxes } from "lucide-react";
|
||||||
import LogsDialog from "@/components/LogDialog";
|
import LogsDialog from "@/components/LogDialog";
|
||||||
@@ -9,7 +10,7 @@ import HealthBadge from "@/components/HealthBadge";
|
|||||||
import BackendBadge from "@/components/BackendBadge";
|
import BackendBadge from "@/components/BackendBadge";
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { useInstanceHealth } from "@/hooks/useInstanceHealth";
|
import { useInstanceHealth } from "@/hooks/useInstanceHealth";
|
||||||
import { instancesApi, llamaCppApi } from "@/lib/api";
|
import { instancesApi, llamaCppApi, type Model } from "@/lib/api";
|
||||||
|
|
||||||
interface InstanceCardProps {
|
interface InstanceCardProps {
|
||||||
instance: Instance;
|
instance: Instance;
|
||||||
@@ -29,29 +30,33 @@ function InstanceCard({
|
|||||||
const [isLogsOpen, setIsLogsOpen] = useState(false);
|
const [isLogsOpen, setIsLogsOpen] = useState(false);
|
||||||
const [isModelsOpen, setIsModelsOpen] = useState(false);
|
const [isModelsOpen, setIsModelsOpen] = useState(false);
|
||||||
const [showAllActions, setShowAllActions] = useState(false);
|
const [showAllActions, setShowAllActions] = useState(false);
|
||||||
const [modelCount, setModelCount] = useState(0);
|
const [models, setModels] = useState<Model[]>([]);
|
||||||
const health = useInstanceHealth(instance.name, instance.status);
|
const health = useInstanceHealth(instance.name, instance.status);
|
||||||
|
|
||||||
const running = instance.status === "running";
|
const running = instance.status === "running";
|
||||||
const isLlamaCpp = instance.options?.backend_type === "llama_cpp";
|
const isLlamaCpp = instance.options?.backend_type === "llama_cpp";
|
||||||
|
|
||||||
// Fetch model count for llama.cpp instances
|
// Fetch models for llama.cpp instances
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isLlamaCpp || !running) {
|
if (!isLlamaCpp || !running) {
|
||||||
setModelCount(0);
|
setModels([]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
void (async () => {
|
void (async () => {
|
||||||
try {
|
try {
|
||||||
const models = await llamaCppApi.getModels(instance.name);
|
const fetchedModels = await llamaCppApi.getModels(instance.name);
|
||||||
setModelCount(models.length);
|
setModels(fetchedModels);
|
||||||
} catch {
|
} catch {
|
||||||
setModelCount(0);
|
setModels([]);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
}, [instance.name, isLlamaCpp, running]);
|
}, [instance.name, isLlamaCpp, running]);
|
||||||
|
|
||||||
|
// Calculate model counts
|
||||||
|
const totalModels = models.length;
|
||||||
|
const loadedModels = models.filter(m => m.status.value === "loaded").length;
|
||||||
|
|
||||||
const handleStart = () => {
|
const handleStart = () => {
|
||||||
startInstance(instance.name);
|
startInstance(instance.name);
|
||||||
};
|
};
|
||||||
@@ -124,6 +129,12 @@ function InstanceCard({
|
|||||||
<div className="flex items-center gap-2 flex-wrap">
|
<div className="flex items-center gap-2 flex-wrap">
|
||||||
<BackendBadge backend={instance.options?.backend_type} docker={instance.options?.docker_enabled} />
|
<BackendBadge backend={instance.options?.backend_type} docker={instance.options?.docker_enabled} />
|
||||||
{running && <HealthBadge health={health} />}
|
{running && <HealthBadge health={health} />}
|
||||||
|
{isLlamaCpp && running && totalModels > 0 && (
|
||||||
|
<Badge variant="secondary" className="text-xs">
|
||||||
|
<Boxes className="h-3 w-3 mr-1" />
|
||||||
|
{loadedModels}/{totalModels} models
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
@@ -174,30 +185,28 @@ function InstanceCard({
|
|||||||
|
|
||||||
{/* Secondary actions - collapsible */}
|
{/* Secondary actions - collapsible */}
|
||||||
{showAllActions && (
|
{showAllActions && (
|
||||||
<div className="flex items-center gap-2 pt-2 border-t border-border">
|
<div className="flex items-center gap-2 pt-2 border-t border-border flex-wrap">
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={handleLogs}
|
onClick={handleLogs}
|
||||||
title="View logs"
|
title="View logs"
|
||||||
data-testid="view-logs-button"
|
data-testid="view-logs-button"
|
||||||
className="flex-1"
|
|
||||||
>
|
>
|
||||||
<FileText className="h-4 w-4 mr-1" />
|
<FileText className="h-4 w-4 mr-1" />
|
||||||
Logs
|
Logs
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{isLlamaCpp && modelCount > 1 && (
|
{isLlamaCpp && totalModels > 1 && (
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={handleModels}
|
onClick={handleModels}
|
||||||
title="Manage models"
|
title="Manage models"
|
||||||
data-testid="manage-models-button"
|
data-testid="manage-models-button"
|
||||||
className="flex-1"
|
|
||||||
>
|
>
|
||||||
<Boxes className="h-4 w-4 mr-1" />
|
<Boxes className="h-4 w-4 mr-1" />
|
||||||
Models ({modelCount})
|
Models
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -207,7 +216,6 @@ function InstanceCard({
|
|||||||
onClick={handleExport}
|
onClick={handleExport}
|
||||||
title="Export instance"
|
title="Export instance"
|
||||||
data-testid="export-instance-button"
|
data-testid="export-instance-button"
|
||||||
className="flex-1"
|
|
||||||
>
|
>
|
||||||
<Download className="h-4 w-4 mr-1" />
|
<Download className="h-4 w-4 mr-1" />
|
||||||
Export
|
Export
|
||||||
|
|||||||
@@ -237,19 +237,21 @@ export const llamaCppApi = {
|
|||||||
|
|
||||||
// POST /llama-cpp/{name}/models/{model}/load
|
// POST /llama-cpp/{name}/models/{model}/load
|
||||||
loadModel: (instanceName: string, modelName: string) =>
|
loadModel: (instanceName: string, modelName: string) =>
|
||||||
apiCall<{ status: string; message: string }>(
|
apiCall<{ success: boolean }>(
|
||||||
`/llama-cpp/${encodeURIComponent(instanceName)}/models/${encodeURIComponent(modelName)}/load`,
|
`/llama-cpp/${encodeURIComponent(instanceName)}/models/${encodeURIComponent(modelName)}/load`,
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
body: JSON.stringify({ model: modelName }),
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
|
||||||
// POST /llama-cpp/{name}/models/{model}/unload
|
// POST /llama-cpp/{name}/models/{model}/unload
|
||||||
unloadModel: (instanceName: string, modelName: string) =>
|
unloadModel: (instanceName: string, modelName: string) =>
|
||||||
apiCall<{ status: string; message: string }>(
|
apiCall<{ success: boolean }>(
|
||||||
`/llama-cpp/${encodeURIComponent(instanceName)}/models/${encodeURIComponent(modelName)}/unload`,
|
`/llama-cpp/${encodeURIComponent(instanceName)}/models/${encodeURIComponent(modelName)}/unload`,
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
body: JSON.stringify({ model: modelName }),
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user