diff --git a/src/components/Inputs.tsx b/src/components/Inputs.tsx new file mode 100644 index 0000000..1ae3b77 --- /dev/null +++ b/src/components/Inputs.tsx @@ -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 ( +
+ +
+ setValue(Number(e.target.value))} + className="flex-1 accent-violet-400" + /> + {value} +
+
+ ); +} + +type NumberInputProps = { + label: string; + value?: number; + setValue: (val: number | undefined) => void; +}; + +export function NumberInput({ + label, + value, + setValue, +}: NumberInputProps) { + return ( +
+ + + setValue(e.target.value ? Number(e.target.value) : undefined) + } + className="w-full mt-2 p-2 rounded bg-black/20 border border-muted" + /> +
+ ); +} + +type TextInputProps = { + label: string; + value?: string; + setValue: (val: string | undefined) => void; + placeholder?: string; +}; + +export function TextInput({ + label, + value, + setValue, + placeholder, +}: TextInputProps) { + return ( +
+ + setValue(e.target.value || undefined)} + placeholder={placeholder} + className="w-full mt-2 p-2 rounded bg-black/20 border border-muted" + /> +
+ ); +} + +type ToggleProps = { + label: string; + value: boolean; + setValue: (val: boolean) => void; +}; + +export function Toggle({ + label, + value, + setValue, +}: ToggleProps) { + return ( +
+ {label} + +
+ ); +} \ No newline at end of file diff --git a/src/components/chat/ChatUI.tsx b/src/components/chat/ChatUI.tsx index 8567785..a0ccf53 100644 --- a/src/components/chat/ChatUI.tsx +++ b/src/components/chat/ChatUI.tsx @@ -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([]); const [input, setInput] = useState(''); const [loading, setLoading] = useState(false); + const [settings, setSettings] = useState({ + 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 (
{/* ── Sidebar (icon rail + sliding panel) ── */} - + {/* ── Main chat area ── */}
@@ -71,7 +100,7 @@ export default function ChatUI({ {/* Messages */} -
+
{messages.map((msg, i) => (
diff --git a/src/components/chat/chat.panels.tsx b/src/components/chat/chat.panels.tsx index bb6b1e4..c52b508 100644 --- a/src/components/chat/chat.panels.tsx +++ b/src/components/chat/chat.panels.tsx @@ -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>; +}; + +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 = + (key: K) => + (val: Settings[K]) => + setSettings((prev) => ({ ...prev, [key]: val })); return ( -
-
- -
- setTemp(Number(e.target.value))} - className="flex-1 accent-violet-400" - /> - {temp} -
+
+
+

Sampling

+ + +
-
- Streaming - +
+

Generation

+ + + + set('stop')(val ? val.split(',').map((s) => s.trim()) : undefined) + } placeholder="Comma separated" /> +
+
+

Control

+ + +
+
+

System

+ +
); @@ -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[] = [ /> ), - component: , + component: , }, { id: 'info', diff --git a/src/types/chat-api.tsx b/src/types/chat-api.tsx index b38e5a6..52983da 100644 --- a/src/types/chat-api.tsx +++ b/src/types/chat-api.tsx @@ -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; +}; \ No newline at end of file