diff --git a/ui/src/App.tsx b/ui/src/App.tsx index 8017847..e9fe436 100644 --- a/ui/src/App.tsx +++ b/ui/src/App.tsx @@ -1,48 +1,61 @@ -import { useState } from 'react' -import Header from '@/components/Header' -import InstanceList from '@/components/InstanceList' -import InstanceModal from '@/components/InstanceModal' -import { CreateInstanceOptions, Instance } from '@/types/instance' -import { useInstances } from '@/contexts/InstancesContext' +import { useState } from "react"; +import Header from "@/components/Header"; +import InstanceList from "@/components/InstanceList"; +import InstanceModal from "@/components/InstanceModal"; +import { CreateInstanceOptions, Instance } from "@/types/instance"; +import { useInstances } from "@/contexts/InstancesContext"; +import SystemInfoModal from "./components/SystemInfoModal"; function App() { - const [isModalOpen, setIsModalOpen] = useState(false) - const [editingInstance, setEditingInstance] = useState(undefined) - const { createInstance, updateInstance } = useInstances() + const [isInstanceModalOpen, setIsInstanceModalOpen] = useState(false); + const [isSystemInfoModalOpen, setIsSystemInfoModalOpen] = useState(false); + const [editingInstance, setEditingInstance] = useState( + undefined + ); + const { createInstance, updateInstance } = useInstances(); const handleCreateInstance = () => { - setEditingInstance(undefined) - setIsModalOpen(true) - } + setEditingInstance(undefined); + setIsInstanceModalOpen(true); + }; const handleEditInstance = (instance: Instance) => { - setEditingInstance(instance) - setIsModalOpen(true) - } + setEditingInstance(instance); + setIsInstanceModalOpen(true); + }; const handleSaveInstance = (name: string, options: CreateInstanceOptions) => { if (editingInstance) { - updateInstance(editingInstance.name, options) + updateInstance(editingInstance.name, options); } else { - createInstance(name, options) + createInstance(name, options); } - } + }; + + const handleShowSystemInfo = () => { + setIsSystemInfoModalOpen(true); + }; return (
-
+
- + + +
- ) + ); } -export default App \ No newline at end of file +export default App; diff --git a/ui/src/components/Header.tsx b/ui/src/components/Header.tsx index 83257c2..c74d294 100644 --- a/ui/src/components/Header.tsx +++ b/ui/src/components/Header.tsx @@ -1,10 +1,12 @@ -import { Button } from '@/components/ui/button' +import { Button } from "@/components/ui/button"; +import { HelpCircle } from "lucide-react"; interface HeaderProps { - onCreateInstance: () => void + onCreateInstance: () => void; + onShowSystemInfo: () => void; } -function Header({ onCreateInstance }: HeaderProps) { +function Header({ onCreateInstance, onShowSystemInfo }: HeaderProps) { return (
@@ -12,14 +14,23 @@ function Header({ onCreateInstance }: HeaderProps) {

LlamaCtl Dashboard

- - + +
+ + + +
- ) + ); } -export default Header \ No newline at end of file +export default Header; diff --git a/ui/src/components/SystemInfoModal.tsx b/ui/src/components/SystemInfoModal.tsx new file mode 100644 index 0000000..3386677 --- /dev/null +++ b/ui/src/components/SystemInfoModal.tsx @@ -0,0 +1,184 @@ +// ui/src/components/SystemInfoModal.tsx +import React, { useState, useEffect } from 'react' +import { Button } from '@/components/ui/button' +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog' +import { + RefreshCw, + AlertCircle, + Loader2, + ChevronDown, + ChevronRight, + Monitor, + HelpCircle +} from 'lucide-react' +import { serverApi } from '@/lib/api' + +interface SystemInfoModalProps { + open: boolean + onOpenChange: (open: boolean) => void +} + +interface SystemInfo { + version: string + devices: string + help: string +} + +const SystemInfoModal: React.FC = ({ + open, + onOpenChange +}) => { + const [systemInfo, setSystemInfo] = useState(null) + const [loading, setLoading] = useState(false) + const [error, setError] = useState(null) + const [showHelp, setShowHelp] = useState(false) + + // Fetch system info + const fetchSystemInfo = async () => { + setLoading(true) + setError(null) + + try { + const [version, devices, help] = await Promise.all([ + serverApi.getVersion(), + serverApi.getDevices(), + serverApi.getHelp() + ]) + + setSystemInfo({ version, devices, help }) + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to fetch system info') + } finally { + setLoading(false) + } + } + + // Load data when modal opens + useEffect(() => { + if (open) { + fetchSystemInfo() + } + }, [open]) + + return ( + + + +
+
+ + + System Information + + + Llama.cpp server environment and capabilities + +
+ + +
+
+ +
+ {loading && !systemInfo ? ( +
+ + Loading system information... +
+ ) : error ? ( +
+ + {error} +
+ ) : systemInfo ? ( +
+ {/* Version Section */} +
+

Version

+ +
+
+ $ llama-server --version +
+
+                    {systemInfo.version}
+                  
+
+
+ + {/* Devices Section */} +
+
+

Available Devices

+
+ +
+
+ $ llama-server --list-devices +
+
+                    {systemInfo.devices}
+                  
+
+
+ + {/* Help Section */} +
+ + + {showHelp && ( +
+
+ $ llama-server --help +
+
+                      {systemInfo.help}
+                    
+
+ )} +
+
+ ) : null} +
+ + + + +
+
+ ) +} + +export default SystemInfoModal \ No newline at end of file diff --git a/ui/src/lib/api.ts b/ui/src/lib/api.ts index 24be12e..1e827bb 100644 --- a/ui/src/lib/api.ts +++ b/ui/src/lib/api.ts @@ -68,6 +68,30 @@ async function apiCall( } } +// Server API functions +export const serverApi = { + // GET /server/help + getHelp: async (): Promise => { + const response = await fetch(`${API_BASE}/server/help`) + if (!response.ok) throw new Error(`HTTP ${response.status}`) + return response.text() + }, + + // GET /server/version + getVersion: async (): Promise => { + const response = await fetch(`${API_BASE}/server/version`) + if (!response.ok) throw new Error(`HTTP ${response.status}`) + return response.text() + }, + + // GET /server/devices + getDevices: async (): Promise => { + const response = await fetch(`${API_BASE}/server/devices`) + if (!response.ok) throw new Error(`HTTP ${response.status}`) + return response.text() + }, +} + // Instance API functions export const instancesApi = { // GET /instances