feat: chat page right sidebar for model selection and deploy/request params
This commit is contained in:
+97
-87
@@ -207,46 +207,6 @@ export default function ChatPage() {
|
|||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-3 border-t border-border pt-3">
|
|
||||||
<label className="mb-1 block text-xs text-slate-400">当前模型</label>
|
|
||||||
<select
|
|
||||||
className="input w-full"
|
|
||||||
value={selectedModelId}
|
|
||||||
onChange={(e) => setSelectedModelId(e.target.value)}
|
|
||||||
>
|
|
||||||
{models.map((m) => (
|
|
||||||
<option key={m.id} value={m.id}>
|
|
||||||
{m.file_name}
|
|
||||||
{m.kind === "remote" ? "(API)" : ""}
|
|
||||||
{engineRunning && engineModel === m.file_name ? " ●已部署" : ""}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
{selectedIsRemote ? (
|
|
||||||
<div className="mt-2 rounded-lg border border-sky-500/30 bg-sky-500/10 px-3 py-1.5 text-xs text-sky-300">
|
|
||||||
在线 API 模型,无需本地引擎
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="mt-2 flex gap-2">
|
|
||||||
<button
|
|
||||||
className="flex-1 rounded-lg border border-border bg-panel-2 py-1.5 text-xs hover:bg-panel-2/60"
|
|
||||||
onClick={() =>
|
|
||||||
api.engineStart(selectedModelId, params).catch((e) => alert(String(e)))
|
|
||||||
}
|
|
||||||
disabled={engineRunning}
|
|
||||||
>
|
|
||||||
启动引擎
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className="flex-1 rounded-lg border border-border bg-panel-2 py-1.5 text-xs hover:bg-panel-2/60"
|
|
||||||
onClick={() => api.engineStop().catch((e) => alert(String(e)))}
|
|
||||||
disabled={!engineRunning}
|
|
||||||
>
|
|
||||||
停止引擎
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-1 flex-col">
|
<div className="flex flex-1 flex-col">
|
||||||
@@ -291,49 +251,6 @@ export default function ChatPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="border-t border-border bg-panel p-4">
|
<div className="border-t border-border bg-panel p-4">
|
||||||
<div className="mb-2 flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-slate-400">
|
|
||||||
<ParamSlider
|
|
||||||
label="temperature"
|
|
||||||
value={params.temperature}
|
|
||||||
min={0}
|
|
||||||
max={2}
|
|
||||||
step={0.05}
|
|
||||||
onChange={(v) => setParams({ ...params, temperature: v })}
|
|
||||||
/>
|
|
||||||
<ParamSlider
|
|
||||||
label="top_p"
|
|
||||||
value={params.top_p}
|
|
||||||
min={0}
|
|
||||||
max={1}
|
|
||||||
step={0.05}
|
|
||||||
onChange={(v) => setParams({ ...params, top_p: v })}
|
|
||||||
/>
|
|
||||||
<ParamSlider
|
|
||||||
label="max_tokens"
|
|
||||||
value={params.max_tokens}
|
|
||||||
min={64}
|
|
||||||
max={8192}
|
|
||||||
step={64}
|
|
||||||
onChange={(v) => setParams({ ...params, max_tokens: v })}
|
|
||||||
/>
|
|
||||||
<ParamSlider
|
|
||||||
label="ctx_size"
|
|
||||||
value={params.ctx_size}
|
|
||||||
min={512}
|
|
||||||
max={32768}
|
|
||||||
step={512}
|
|
||||||
onChange={(v) => setParams({ ...params, ctx_size: v })}
|
|
||||||
/>
|
|
||||||
<ParamSlider
|
|
||||||
label="ngl(全部=99)"
|
|
||||||
value={params.ngl}
|
|
||||||
min={0}
|
|
||||||
max={99}
|
|
||||||
step={1}
|
|
||||||
onChange={(v) => setParams({ ...params, ngl: v })}
|
|
||||||
/>
|
|
||||||
<span className="ml-auto text-slate-500">{engineLabel}</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-end gap-3">
|
<div className="flex items-end gap-3">
|
||||||
<textarea
|
<textarea
|
||||||
className="input min-h-[72px] flex-1 resize-none"
|
className="input min-h-[72px] flex-1 resize-none"
|
||||||
@@ -357,6 +274,97 @@ export default function ChatPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* 右侧边栏:当前模型 + 部署/请求参数 */}
|
||||||
|
<div className="flex w-72 flex-col overflow-y-auto border-l border-border bg-panel p-3">
|
||||||
|
<div className="mb-1 text-xs text-slate-400">当前模型</div>
|
||||||
|
<select
|
||||||
|
className="input w-full"
|
||||||
|
value={selectedModelId}
|
||||||
|
onChange={(e) => setSelectedModelId(e.target.value)}
|
||||||
|
>
|
||||||
|
{models.map((m) => (
|
||||||
|
<option key={m.id} value={m.id}>
|
||||||
|
{m.file_name}
|
||||||
|
{m.kind === "remote" ? "(API)" : ""}
|
||||||
|
{engineRunning && engineModel === m.file_name ? " ●已部署" : ""}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
{selectedIsRemote ? (
|
||||||
|
<div className="mt-2 rounded-lg border border-sky-500/30 bg-sky-500/10 px-3 py-1.5 text-xs text-sky-300">
|
||||||
|
在线 API 模型,无需本地引擎
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="mt-2 flex gap-2">
|
||||||
|
<button
|
||||||
|
className="flex-1 rounded-lg border border-border bg-panel-2 py-1.5 text-xs hover:bg-panel-2/60"
|
||||||
|
onClick={() =>
|
||||||
|
api.engineStart(selectedModelId, params).catch((e) => alert(String(e)))
|
||||||
|
}
|
||||||
|
disabled={engineRunning}
|
||||||
|
>
|
||||||
|
启动引擎
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="flex-1 rounded-lg border border-border bg-panel-2 py-1.5 text-xs hover:bg-panel-2/60"
|
||||||
|
onClick={() => api.engineStop().catch((e) => alert(String(e)))}
|
||||||
|
disabled={!engineRunning}
|
||||||
|
>
|
||||||
|
停止引擎
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="mt-1 truncate text-xs text-slate-500">{engineLabel}</div>
|
||||||
|
|
||||||
|
<div className="mt-4 border-t border-border pt-3">
|
||||||
|
<div className="mb-3 text-xs uppercase text-slate-400">部署参数</div>
|
||||||
|
<ParamSlider
|
||||||
|
label="上下文长度 ctx_size"
|
||||||
|
value={params.ctx_size}
|
||||||
|
min={512}
|
||||||
|
max={32768}
|
||||||
|
step={512}
|
||||||
|
onChange={(v) => setParams({ ...params, ctx_size: v })}
|
||||||
|
/>
|
||||||
|
<ParamSlider
|
||||||
|
label="GPU 层数 ngl(99=全部)"
|
||||||
|
value={params.ngl}
|
||||||
|
min={0}
|
||||||
|
max={99}
|
||||||
|
step={1}
|
||||||
|
onChange={(v) => setParams({ ...params, ngl: v })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-4 border-t border-border pt-3">
|
||||||
|
<div className="mb-3 text-xs uppercase text-slate-400">请求参数</div>
|
||||||
|
<ParamSlider
|
||||||
|
label="temperature"
|
||||||
|
value={params.temperature}
|
||||||
|
min={0}
|
||||||
|
max={2}
|
||||||
|
step={0.05}
|
||||||
|
onChange={(v) => setParams({ ...params, temperature: v })}
|
||||||
|
/>
|
||||||
|
<ParamSlider
|
||||||
|
label="top_p"
|
||||||
|
value={params.top_p}
|
||||||
|
min={0}
|
||||||
|
max={1}
|
||||||
|
step={0.05}
|
||||||
|
onChange={(v) => setParams({ ...params, top_p: v })}
|
||||||
|
/>
|
||||||
|
<ParamSlider
|
||||||
|
label="max_tokens"
|
||||||
|
value={params.max_tokens}
|
||||||
|
min={64}
|
||||||
|
max={8192}
|
||||||
|
step={64}
|
||||||
|
onChange={(v) => setParams({ ...params, max_tokens: v })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -377,18 +385,20 @@ function ParamSlider({
|
|||||||
onChange: (v: number) => void;
|
onChange: (v: number) => void;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<label className="flex items-center gap-2">
|
<label className="mb-3 block">
|
||||||
<span className="w-24">{label}</span>
|
<div className="mb-1 flex items-center justify-between text-xs text-slate-400">
|
||||||
|
<span>{label}</span>
|
||||||
|
<span className="font-mono text-slate-300">{value}</span>
|
||||||
|
</div>
|
||||||
<input
|
<input
|
||||||
type="range"
|
type="range"
|
||||||
className="w-28 accent-indigo-500"
|
className="w-full accent-indigo-500"
|
||||||
min={min}
|
min={min}
|
||||||
max={max}
|
max={max}
|
||||||
step={step}
|
step={step}
|
||||||
value={value}
|
value={value}
|
||||||
onChange={(e) => onChange(Number(e.target.value))}
|
onChange={(e) => onChange(Number(e.target.value))}
|
||||||
/>
|
/>
|
||||||
<span className="w-12 font-mono text-slate-300">{value}</span>
|
|
||||||
</label>
|
</label>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user