feat: nav reorder, model deploy button with background loading
This commit is contained in:
@@ -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
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user