94 lines
3.4 KiB
TypeScript
94 lines
3.4 KiB
TypeScript
import { useState } from "react";
|
|
import { api } from "../api";
|
|
import { useStore } from "../store";
|
|
|
|
export default function ServerPage() {
|
|
const server = useStore((s) => s.server);
|
|
const refreshServer = useStore((s) => s.refreshServer);
|
|
const [port, setPort] = useState("1234");
|
|
const [apiKey, setApiKey] = useState("");
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
const running = server?.running ?? false;
|
|
|
|
async function handleStart() {
|
|
setError(null);
|
|
try {
|
|
await api.serverStart(Number(port), apiKey);
|
|
await refreshServer();
|
|
} catch (e) {
|
|
setError(String(e));
|
|
}
|
|
}
|
|
|
|
async function handleStop() {
|
|
await api.serverStop();
|
|
await refreshServer();
|
|
}
|
|
|
|
const baseUrl = running ? `http://127.0.0.1:${server!.port}` : null;
|
|
|
|
return (
|
|
<div className="h-full overflow-auto p-6">
|
|
<h1 className="text-xl font-semibold">服务管理</h1>
|
|
<p className="mt-1 text-sm text-slate-400">
|
|
启动后即可用 OpenAI SDK / 任意 HTTP 客户端调用本地模型
|
|
</p>
|
|
|
|
<div className="mt-6 max-w-xl space-y-4">
|
|
<div className="rounded-xl border border-border bg-panel p-5">
|
|
<div className="flex items-center gap-2">
|
|
<span className={`h-2.5 w-2.5 rounded-full ${running ? "bg-emerald-400" : "bg-slate-500"}`} />
|
|
<span className="text-sm font-medium">{running ? `运行中 · ${baseUrl}` : "未启动"}</span>
|
|
</div>
|
|
<div className="mt-4 space-y-3">
|
|
<label className="block text-sm">
|
|
<span className="text-xs text-slate-400">端口</span>
|
|
<input
|
|
className="input mt-1 w-full"
|
|
value={port}
|
|
disabled={running}
|
|
onChange={(e) => setPort(e.target.value.replace(/\D/g, ""))}
|
|
/>
|
|
</label>
|
|
<label className="block text-sm">
|
|
<span className="text-xs text-slate-400">API Key(可选,留空则不鉴权)</span>
|
|
<input
|
|
className="input mt-1 w-full"
|
|
value={apiKey}
|
|
disabled={running}
|
|
onChange={(e) => setApiKey(e.target.value)}
|
|
placeholder="sk-..."
|
|
/>
|
|
</label>
|
|
</div>
|
|
<div className="mt-4 flex gap-2">
|
|
<button className="btn-primary" onClick={handleStart} disabled={running}>
|
|
启动服务
|
|
</button>
|
|
<button className="btn-secondary" onClick={handleStop} disabled={!running}>
|
|
停止服务
|
|
</button>
|
|
</div>
|
|
{error ? <div className="mt-3 text-sm text-red-300">{error}</div> : null}
|
|
</div>
|
|
|
|
{running ? (
|
|
<div className="rounded-xl border border-border bg-panel p-5">
|
|
<div className="text-xs uppercase text-slate-400">端点</div>
|
|
<div className="mt-2 space-y-1 font-mono text-sm">
|
|
<div>GET {baseUrl}/v1/models</div>
|
|
<div>POST {baseUrl}/v1/chat/completions</div>
|
|
<div>POST {baseUrl}/v1/embeddings</div>
|
|
<div>GET {baseUrl}/health</div>
|
|
</div>
|
|
<div className="mt-3 text-xs text-slate-500">
|
|
提示:请先启动引擎(聊天页),否则模型接口返回 503。
|
|
</div>
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|