ui: tweak vertical space for mobile (#343)
This commit is contained in:
@@ -5,7 +5,7 @@ import { useTheme } from "../contexts/ThemeProvider";
|
|||||||
import ConnectionStatusIcon from "./ConnectionStatus";
|
import ConnectionStatusIcon from "./ConnectionStatus";
|
||||||
|
|
||||||
export function Header() {
|
export function Header() {
|
||||||
const { screenWidth, toggleTheme, isDarkMode, appTitle, setAppTitle } = useTheme();
|
const { screenWidth, toggleTheme, isDarkMode, appTitle, setAppTitle, isNarrow } = useTheme();
|
||||||
const handleTitleChange = useCallback(
|
const handleTitleChange = useCallback(
|
||||||
(newTitle: string) => {
|
(newTitle: string) => {
|
||||||
setAppTitle(newTitle.replace(/\n/g, "").trim().substring(0, 64) || "llama-swap");
|
setAppTitle(newTitle.replace(/\n/g, "").trim().substring(0, 64) || "llama-swap");
|
||||||
@@ -17,7 +17,7 @@ export function Header() {
|
|||||||
`text-gray-600 hover:text-black dark:text-gray-300 dark:hover:text-gray-100 p-1 ${isActive ? "font-semibold" : ""}`;
|
`text-gray-600 hover:text-black dark:text-gray-300 dark:hover:text-gray-100 p-1 ${isActive ? "font-semibold" : ""}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className="flex items-center justify-between bg-surface border-b border-border p-2 px-4 h-[75px]">
|
<header className={`flex items-center justify-between bg-surface border-b border-border px-4 ${isNarrow ? "py-1 h-[60px]" : "p-2 h-[75px]"}`}>
|
||||||
{screenWidth !== "xs" && screenWidth !== "sm" && (
|
{screenWidth !== "xs" && screenWidth !== "sm" && (
|
||||||
<h1
|
<h1
|
||||||
contentEditable
|
contentEditable
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { LogPanel } from "./LogViewer";
|
|||||||
import { usePersistentState } from "../hooks/usePersistentState";
|
import { usePersistentState } from "../hooks/usePersistentState";
|
||||||
import { Panel, PanelGroup, PanelResizeHandle } from "react-resizable-panels";
|
import { Panel, PanelGroup, PanelResizeHandle } from "react-resizable-panels";
|
||||||
import { useTheme } from "../contexts/ThemeProvider";
|
import { useTheme } from "../contexts/ThemeProvider";
|
||||||
import { RiEyeFill, RiEyeOffFill, RiSwapBoxFill, RiEjectLine } from "react-icons/ri";
|
import { RiEyeFill, RiEyeOffFill, RiSwapBoxFill, RiEjectLine, RiMenuFill } from "react-icons/ri";
|
||||||
|
|
||||||
export default function ModelsPage() {
|
export default function ModelsPage() {
|
||||||
const { isNarrow } = useTheme();
|
const { isNarrow } = useTheme();
|
||||||
@@ -38,9 +38,11 @@ export default function ModelsPage() {
|
|||||||
|
|
||||||
function ModelsPanel() {
|
function ModelsPanel() {
|
||||||
const { models, loadModel, unloadAllModels, unloadSingleModel } = useAPI();
|
const { models, loadModel, unloadAllModels, unloadSingleModel } = useAPI();
|
||||||
|
const { isNarrow } = useTheme();
|
||||||
const [isUnloading, setIsUnloading] = useState(false);
|
const [isUnloading, setIsUnloading] = useState(false);
|
||||||
const [showUnlisted, setShowUnlisted] = usePersistentState("showUnlisted", true);
|
const [showUnlisted, setShowUnlisted] = usePersistentState("showUnlisted", true);
|
||||||
const [showIdorName, setShowIdorName] = usePersistentState<"id" | "name">("showIdorName", "id"); // true = show ID, false = show name
|
const [showIdorName, setShowIdorName] = usePersistentState<"id" | "name">("showIdorName", "id"); // true = show ID, false = show name
|
||||||
|
const [menuOpen, setMenuOpen] = useState(false);
|
||||||
|
|
||||||
const filteredModels = useMemo(() => {
|
const filteredModels = useMemo(() => {
|
||||||
return models.filter((model) => showUnlisted || !model.unlisted);
|
return models.filter((model) => showUnlisted || !model.unlisted);
|
||||||
@@ -66,33 +68,77 @@ function ModelsPanel() {
|
|||||||
return (
|
return (
|
||||||
<div className="card h-full flex flex-col">
|
<div className="card h-full flex flex-col">
|
||||||
<div className="shrink-0">
|
<div className="shrink-0">
|
||||||
<h2>Models</h2>
|
<div className="flex justify-between items-baseline">
|
||||||
<div className="flex justify-between">
|
<h2 className={isNarrow ? "text-xl" : ""}>Models</h2>
|
||||||
<div className="flex gap-2">
|
{isNarrow && (
|
||||||
<button
|
<div className="relative">
|
||||||
className="btn text-base flex items-center gap-2"
|
<button className="btn text-base flex items-center gap-2 py-1" onClick={() => setMenuOpen(!menuOpen)}>
|
||||||
onClick={toggleIdorName}
|
<RiMenuFill size="20" />
|
||||||
style={{ lineHeight: "1.2" }}
|
</button>
|
||||||
>
|
{menuOpen && (
|
||||||
<RiSwapBoxFill size="20" /> {showIdorName === "id" ? "ID" : "Name"}
|
<div className="absolute right-0 mt-2 w-48 bg-surface border border-gray-200 dark:border-white/10 rounded shadow-lg z-20">
|
||||||
</button>
|
<button
|
||||||
|
className="w-full text-left px-4 py-2 hover:bg-secondary-hover flex items-center gap-2"
|
||||||
|
onClick={() => {
|
||||||
|
toggleIdorName();
|
||||||
|
setMenuOpen(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<RiSwapBoxFill size="20" /> {showIdorName === "id" ? "Show Name" : "Show ID"}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="w-full text-left px-4 py-2 hover:bg-secondary-hover flex items-center gap-2"
|
||||||
|
onClick={() => {
|
||||||
|
setShowUnlisted(!showUnlisted);
|
||||||
|
setMenuOpen(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{showUnlisted ? <RiEyeOffFill size="20" /> : <RiEyeFill size="20" />}{" "}
|
||||||
|
{showUnlisted ? "Hide Unlisted" : "Show Unlisted"}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="w-full text-left px-4 py-2 hover:bg-secondary-hover flex items-center gap-2"
|
||||||
|
onClick={() => {
|
||||||
|
handleUnloadAllModels();
|
||||||
|
setMenuOpen(false);
|
||||||
|
}}
|
||||||
|
disabled={isUnloading}
|
||||||
|
>
|
||||||
|
<RiEjectLine size="24" /> {isUnloading ? "Unloading..." : "Unload All"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{!isNarrow && (
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<button
|
||||||
|
className="btn text-base flex items-center gap-2"
|
||||||
|
onClick={toggleIdorName}
|
||||||
|
style={{ lineHeight: "1.2" }}
|
||||||
|
>
|
||||||
|
<RiSwapBoxFill size="20" /> {showIdorName === "id" ? "ID" : "Name"}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
className="btn text-base flex items-center gap-2"
|
||||||
|
onClick={() => setShowUnlisted(!showUnlisted)}
|
||||||
|
style={{ lineHeight: "1.2" }}
|
||||||
|
>
|
||||||
|
{showUnlisted ? <RiEyeFill size="20" /> : <RiEyeOffFill size="20" />} unlisted
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<button
|
<button
|
||||||
className="btn text-base flex items-center gap-2"
|
className="btn text-base flex items-center gap-2"
|
||||||
onClick={() => setShowUnlisted(!showUnlisted)}
|
onClick={handleUnloadAllModels}
|
||||||
style={{ lineHeight: "1.2" }}
|
disabled={isUnloading}
|
||||||
>
|
>
|
||||||
{showUnlisted ? <RiEyeFill size="20" /> : <RiEyeOffFill size="20" />} unlisted
|
<RiEjectLine size="24" /> {isUnloading ? "Unloading..." : "Unload All"}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<button
|
)}
|
||||||
className="btn text-base flex items-center gap-2"
|
|
||||||
onClick={handleUnloadAllModels}
|
|
||||||
disabled={isUnloading}
|
|
||||||
>
|
|
||||||
<RiEjectLine size="24" /> {isUnloading ? "Unloading..." : "Unload All"}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex-1 overflow-y-auto">
|
<div className="flex-1 overflow-y-auto">
|
||||||
|
|||||||
Reference in New Issue
Block a user