feat: add parameters
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
type SliderProps = {
|
||||
label: string;
|
||||
value: number;
|
||||
setValue: (val: number) => void;
|
||||
min: number;
|
||||
max: number;
|
||||
step: number;
|
||||
};
|
||||
|
||||
export function Slider({
|
||||
label,
|
||||
value,
|
||||
setValue,
|
||||
min,
|
||||
max,
|
||||
step,
|
||||
}: SliderProps) {
|
||||
return (
|
||||
<div>
|
||||
<label className="text-xs uppercase tracking-widest">{label}</label>
|
||||
<div className="flex items-center gap-3 mt-2">
|
||||
<input
|
||||
type="range"
|
||||
min={min}
|
||||
max={max}
|
||||
step={step}
|
||||
value={value}
|
||||
onChange={(e) => setValue(Number(e.target.value))}
|
||||
className="flex-1 accent-violet-400"
|
||||
/>
|
||||
<span className="text-sm w-10 text-right">{value}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
type NumberInputProps = {
|
||||
label: string;
|
||||
value?: number;
|
||||
setValue: (val: number | undefined) => void;
|
||||
};
|
||||
|
||||
export function NumberInput({
|
||||
label,
|
||||
value,
|
||||
setValue,
|
||||
}: NumberInputProps) {
|
||||
return (
|
||||
<div>
|
||||
<label className="text-xs uppercase tracking-widest">{label}</label>
|
||||
<input
|
||||
type="number"
|
||||
value={value ?? ''}
|
||||
onChange={(e) =>
|
||||
setValue(e.target.value ? Number(e.target.value) : undefined)
|
||||
}
|
||||
className="w-full mt-2 p-2 rounded bg-black/20 border border-muted"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
type TextInputProps = {
|
||||
label: string;
|
||||
value?: string;
|
||||
setValue: (val: string | undefined) => void;
|
||||
placeholder?: string;
|
||||
};
|
||||
|
||||
export function TextInput({
|
||||
label,
|
||||
value,
|
||||
setValue,
|
||||
placeholder,
|
||||
}: TextInputProps) {
|
||||
return (
|
||||
<div>
|
||||
<label className="text-xs uppercase tracking-widest">{label}</label>
|
||||
<input
|
||||
type="text"
|
||||
value={value ?? ''}
|
||||
onChange={(e) => setValue(e.target.value || undefined)}
|
||||
placeholder={placeholder}
|
||||
className="w-full mt-2 p-2 rounded bg-black/20 border border-muted"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
type ToggleProps = {
|
||||
label: string;
|
||||
value: boolean;
|
||||
setValue: (val: boolean) => void;
|
||||
};
|
||||
|
||||
export function Toggle({
|
||||
label,
|
||||
value,
|
||||
setValue,
|
||||
}: ToggleProps) {
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm">{label}</span>
|
||||
<button
|
||||
onClick={() => setValue(!value)}
|
||||
className={`w-10 h-5 rounded-full ${
|
||||
value ? 'bg-violet-500' : 'bg-white/20'
|
||||
} relative`}
|
||||
>
|
||||
<span
|
||||
className={`absolute top-0.5 w-4 h-4 bg-white rounded-full transition-all ${
|
||||
value ? 'left-5' : 'left-0.5'
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -6,6 +6,8 @@ import Sidebar from '../Sidebar';
|
||||
import { SIDEBAR_PANELS } from './chat.panels';
|
||||
import { Session } from 'next-auth';
|
||||
import { Message, ModelInfo } from '@/types/chat-api';
|
||||
import { ChatRequest } from '@/types/chat-api';
|
||||
import { Settings } from './chat.panels';
|
||||
|
||||
type Props = {
|
||||
session: Session;
|
||||
@@ -23,6 +25,18 @@ export default function ChatUI({
|
||||
const [messages, setMessages] = useState<Message[]>([]);
|
||||
const [input, setInput] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [settings, setSettings] = useState<Settings>({
|
||||
temp: 0.7,
|
||||
topP: 0.9,
|
||||
topK: 40,
|
||||
maxTokens: 1000,
|
||||
numCtx: 128000,
|
||||
stop: [''],
|
||||
repeatPenalty: 1.1,
|
||||
seed: 0,
|
||||
stream: true,
|
||||
keepAlive: '5m',
|
||||
});
|
||||
|
||||
const sendMessage = async () => {
|
||||
if (!input.trim() || !selectedModel) return;
|
||||
@@ -33,6 +47,21 @@ export default function ChatUI({
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
const body: ChatRequest = {
|
||||
model: selectedModel!,
|
||||
messages: newMessages,
|
||||
temperature: settings.temp,
|
||||
top_p: settings.topP,
|
||||
top_k: settings.topK,
|
||||
num_predict: settings.maxTokens,
|
||||
num_ctx: settings.numCtx,
|
||||
repeat_penalty: settings.repeatPenalty,
|
||||
seed: settings.seed,
|
||||
stream: settings.stream,
|
||||
keep_alive: settings.keepAlive,
|
||||
stop: settings.stop,
|
||||
};
|
||||
|
||||
const res = await fetch(
|
||||
process.env.NEXT_PUBLIC_API_URL + '/chat/completions',
|
||||
{
|
||||
@@ -41,7 +70,7 @@ export default function ChatUI({
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${session?.accessToken}`,
|
||||
},
|
||||
body: JSON.stringify({ model: selectedModel, messages: newMessages }),
|
||||
body: JSON.stringify(body),
|
||||
},
|
||||
);
|
||||
const data = await res.json();
|
||||
@@ -57,7 +86,7 @@ export default function ChatUI({
|
||||
return (
|
||||
<div className="flex flex-1 bg-primary text-primary transition-theme">
|
||||
{/* ── Sidebar (icon rail + sliding panel) ── */}
|
||||
<Sidebar panels={SIDEBAR_PANELS} />
|
||||
<Sidebar panels={SIDEBAR_PANELS({ settings, setSettings })} />
|
||||
|
||||
{/* ── Main chat area ── */}
|
||||
<div className="flex-1 flex flex-col min-w-0">
|
||||
@@ -71,7 +100,7 @@ export default function ChatUI({
|
||||
</header>
|
||||
|
||||
{/* Messages */}
|
||||
<main className="flex-1 overflow-y-auto flex flex-col justify-end p-6">
|
||||
<main className="flex-1 overflow-y-auto flex flex-col p-6">
|
||||
<div className="w-full max-w-2xl mx-auto space-y-3">
|
||||
{messages.map((msg, i) => (
|
||||
<div key={i} className="text-sm">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState } from 'react';
|
||||
import { SidebarPanel } from '../Sidebar';
|
||||
import { Slider, NumberInput, TextInput, Toggle } from '../Inputs';
|
||||
|
||||
// ─── Example panels ───────────────────────────────────────────────────────────
|
||||
|
||||
@@ -24,43 +24,55 @@ function ConversationHistoryPanel() {
|
||||
);
|
||||
}
|
||||
|
||||
function SettingsPanel() {
|
||||
const [temp, setTemp] = useState(0.7);
|
||||
const [stream, setStream] = useState(true);
|
||||
type PanelProps = {
|
||||
settings: Settings;
|
||||
setSettings: React.Dispatch<React.SetStateAction<Settings>>;
|
||||
};
|
||||
|
||||
export type Settings = {
|
||||
temp: number;
|
||||
topP: number;
|
||||
topK: number | undefined;
|
||||
maxTokens: number | undefined;
|
||||
numCtx: number | undefined;
|
||||
stop: string[] | undefined;
|
||||
repeatPenalty: number;
|
||||
seed: number | undefined;
|
||||
stream: boolean;
|
||||
keepAlive: string | undefined;
|
||||
};
|
||||
|
||||
function SettingsPanel({ settings, setSettings }: PanelProps) {
|
||||
const set =
|
||||
<K extends keyof Settings>(key: K) =>
|
||||
(val: Settings[K]) =>
|
||||
setSettings((prev) => ({ ...prev, [key]: val }));
|
||||
|
||||
return (
|
||||
<div className="space-y-4 px-1">
|
||||
<div>
|
||||
<label className="text-xs text-primary uppercase tracking-widest transition-theme">
|
||||
Temperature
|
||||
</label>
|
||||
<div className="flex items-center gap-3 mt-2">
|
||||
<input
|
||||
type="range"
|
||||
min={0}
|
||||
max={2}
|
||||
step={0.1}
|
||||
value={temp}
|
||||
onChange={(e) => setTemp(Number(e.target.value))}
|
||||
className="flex-1 accent-violet-400"
|
||||
/>
|
||||
<span className="text-sm w-8 text-right">{temp}</span>
|
||||
</div>
|
||||
<div className="space-y-6 px-1">
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-xs uppercase text-accent tracking-widest">Sampling</h3>
|
||||
<Slider label="Temperature" value={settings.temp} setValue={set('temp')} min={0} max={2} step={0.1} />
|
||||
<Slider label="Top P" value={settings.topP} setValue={set('topP')} min={0} max={1} step={0.05} />
|
||||
<NumberInput label="Top K" value={settings.topK} setValue={set('topK')} />
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm">Streaming</span>
|
||||
<button
|
||||
onClick={() => setStream((s) => !s)}
|
||||
className={`w-10 h-5 rounded-full transition-colors ${
|
||||
stream ? 'bg-violet-500' : 'bg-white/20'
|
||||
} relative`}
|
||||
>
|
||||
<span
|
||||
className={`absolute top-0.5 w-4 h-4 rounded-full bg-white transition-all ${
|
||||
stream ? 'left-5' : 'left-0.5'
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-xs uppercase text-accent tracking-widest">Generation</h3>
|
||||
<NumberInput label="Max Tokens" value={settings.maxTokens} setValue={set('maxTokens')} />
|
||||
<NumberInput label="Context Size" value={settings.numCtx} setValue={set('numCtx')} />
|
||||
<TextInput label="Stop Sequences" value={settings.stop?.join(', ')} setValue={(val) =>
|
||||
set('stop')(val ? val.split(',').map((s) => s.trim()) : undefined)
|
||||
} placeholder="Comma separated" />
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-xs uppercase text-accent tracking-widest">Control</h3>
|
||||
<Slider label="Repeat Penalty" value={settings.repeatPenalty} setValue={set('repeatPenalty')} min={0} max={2} step={0.1} />
|
||||
<NumberInput label="Seed" value={settings.seed} setValue={set('seed')} />
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-xs uppercase text-accent tracking-widest">System</h3>
|
||||
<Toggle label="Streaming" value={settings.stream} setValue={set('stream')} />
|
||||
<TextInput label="Keep Alive" value={settings.keepAlive} setValue={set('keepAlive')} placeholder="e.g. 5m" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -90,7 +102,7 @@ function InfoPanel() {
|
||||
|
||||
// ─── Panel registry — add yours here ─────────────────────────────────────────
|
||||
|
||||
export const SIDEBAR_PANELS: SidebarPanel[] = [
|
||||
export const SIDEBAR_PANELS = ({ settings, setSettings }: PanelProps): SidebarPanel[] => [
|
||||
{
|
||||
id: 'history',
|
||||
label: 'History',
|
||||
@@ -117,7 +129,7 @@ export const SIDEBAR_PANELS: SidebarPanel[] = [
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
component: <SettingsPanel />,
|
||||
component: <SettingsPanel settings={settings} setSettings={setSettings} />,
|
||||
},
|
||||
{
|
||||
id: 'info',
|
||||
|
||||
@@ -15,3 +15,45 @@ export type Message = {
|
||||
role: Role;
|
||||
content: string;
|
||||
};
|
||||
|
||||
export type ChatRequest = {
|
||||
model: string;
|
||||
stream?: boolean;
|
||||
temperature?: number;
|
||||
top_p?: number;
|
||||
top_k?: number;
|
||||
repeat_penalty?: number;
|
||||
seed?: number;
|
||||
num_ctx?: number;
|
||||
num_predict?: number;
|
||||
stop?: string[];
|
||||
keep_alive?: string;
|
||||
messages: Message[];
|
||||
};
|
||||
|
||||
export type ChatCompletionResponse = {
|
||||
id: string;
|
||||
object: string;
|
||||
created: number;
|
||||
model: string;
|
||||
choices: ChatChoice[];
|
||||
usage?: Usage;
|
||||
};
|
||||
|
||||
export type FinishReason =
|
||||
| 'Stop'
|
||||
| 'Length'
|
||||
| 'ContentFilter'
|
||||
| 'ToolCalls';
|
||||
|
||||
export type ChatChoice = {
|
||||
index: number;
|
||||
message: Message;
|
||||
finish_reason: FinishReason;
|
||||
};
|
||||
|
||||
export type Usage = {
|
||||
prompt_tokens: number;
|
||||
completion_tokens: number;
|
||||
total_tokens: number;
|
||||
};
|
||||
Reference in New Issue
Block a user