feat: nav reorder, model deploy button with background loading

This commit is contained in:
Xianren Studio
2026-08-13 15:55:09 +08:00
parent 451cc75cd9
commit ebe3591445
8 changed files with 255 additions and 18 deletions
+2
View File
@@ -168,6 +168,7 @@ export default function ChatPage() {
}
const engineRunning = useStore((s) => s.engine?.running ?? false);
const engineModel = useStore((s) => s.engine?.model ?? null);
const selectedIsRemote = useMemo(
() => models.find((m) => m.id === selectedModelId)?.kind === "remote",
[models, selectedModelId],
@@ -217,6 +218,7 @@ export default function ChatPage() {
<option key={m.id} value={m.id}>
{m.file_name}
{m.kind === "remote" ? "API" : ""}
{engineRunning && engineModel === m.file_name ? " ●已部署" : ""}
</option>
))}
</select>
+96 -10
View File
@@ -11,6 +11,8 @@ function fmtSize(bytes: number) {
export default function ModelsPage() {
const models = useStore((s) => s.models);
const refreshModels = useStore((s) => s.refreshModels);
const engine = useStore((s) => s.engine);
const deployStates = useStore((s) => s.deployStates);
const [showRemoteForm, setShowRemoteForm] = useState(false);
const [scanning, setScanning] = useState(false);
const [remote, setRemote] = useState({
@@ -82,13 +84,36 @@ export default function ModelsPage() {
await api.openPath(info.models_dir);
}
async function handleDeploy(id: string, params?: Partial<import("../api").ChatParams>) {
try {
await api.deployModel(id, {
temperature: 0.7,
top_p: 0.9,
max_tokens: 2048,
ctx_size: 4096,
ngl: 99,
...params,
});
} catch (e) {
alert(String(e));
}
}
async function handleStopEngine() {
try {
await api.engineStop();
} catch (e) {
alert(String(e));
}
}
return (
<div className="h-full overflow-auto p-6">
<div className="mb-4 flex items-center justify-between">
<div>
<h1 className="text-xl font-semibold"></h1>
<h1 className="text-xl font-semibold"></h1>
<p className="mt-1 text-sm text-slate-400">
GGUF 线 API
</p>
</div>
<div className="flex gap-2">
@@ -208,19 +233,33 @@ export default function ModelsPage() {
{m.kind === "remote" ? "-" : fmtSize(m.file_size)}
</td>
<td className="px-4 py-3">
{m.status === "ready" ? (
{m.kind === "remote" ? (
<span className="text-slate-400">-</span>
) : m.status === "ready" ? (
<span className="text-emerald-400">{m.status}</span>
) : (
<span className="text-amber-400">{m.status}</span>
)}
</td>
<td className="px-4 py-3 text-right">
<button
className="text-xs text-slate-400 hover:text-red-400"
onClick={() => handleRemove(m.id, m.file_name)}
>
</button>
<td className="px-4 py-3">
<div className="flex items-center justify-end gap-2">
{m.kind === "local" ? (
<DeployButton
model={m}
engineRunning={engine?.running ?? false}
deployed={engine?.model === m.file_name}
deployState={deployStates[m.id]}
onDeploy={handleDeploy}
onStop={handleStopEngine}
/>
) : null}
<button
className="text-xs text-slate-400 hover:text-red-400"
onClick={() => handleRemove(m.id, m.file_name)}
>
</button>
</div>
</td>
</tr>
))}
@@ -232,3 +271,50 @@ export default function ModelsPage() {
);
}
function DeployButton({
model,
engineRunning,
deployed,
deployState,
onDeploy,
onStop,
}: {
model: { id: string; file_name: string };
engineRunning: boolean;
deployed: boolean;
deployState?: string;
onDeploy: (id: string) => void;
onStop: () => void;
}) {
if (deployed) {
return (
<div className="flex items-center gap-2">
<span className="rounded bg-emerald-500/20 px-2 py-1 text-xs text-emerald-300">
</span>
<button
className="text-xs text-slate-400 hover:text-red-400"
onClick={onStop}
disabled={!engineRunning}
>
</button>
</div>
);
}
if (deployState === "loading") {
return (
<button className="btn-secondary !px-3 !py-1 text-xs" disabled>
</button>
);
}
return (
<button
className="btn-secondary !px-3 !py-1 text-xs"
onClick={() => onDeploy(model.id)}
>
{deployState === "error" ? "重试部署" : "部署"}
</button>
);
}