Compare commits

...
2 Commits
Author SHA1 Message Date
LucasDLTG df0428f240 fix: some design
CI / CI - Validation Technique (push) Successful in 35s
Publish & Deploy (Versioned) / Build and Push Docker Image (push) Successful in 27s
Publish & Deploy (Versioned) / Deploy via SSH (push) Successful in 8s
2026-04-29 15:37:02 +02:00
LucasDLTG 6e409592b5 fix: centered input + default stream 2026-04-29 15:12:44 +02:00
6 changed files with 57 additions and 49 deletions
+3
View File
@@ -142,3 +142,6 @@ git tag -d v1.0.0; git push origin :refs/tags/v1.0.0; git tag -a v1.0.0 -m "Rele
## To implement ## To implement
- Token refresh - Token refresh
- No error when streaming with too large model
- Translations
- qtop models during geenration
+5 -5
View File
@@ -26,7 +26,7 @@ export function Slider({
step={step} step={step}
value={value} value={value}
onChange={(e) => setValue(Number(e.target.value))} onChange={(e) => setValue(Number(e.target.value))}
className="flex-1 accent-violet-400" className="flex-1 accent-[var(--accent2)]"
/> />
<span className="text-sm w-10 text-right">{value}</span> <span className="text-sm w-10 text-right">{value}</span>
</div> </div>
@@ -50,7 +50,7 @@ export function NumberInput({ label, value, setValue }: NumberInputProps) {
onChange={(e) => onChange={(e) =>
setValue(e.target.value ? Number(e.target.value) : undefined) setValue(e.target.value ? Number(e.target.value) : undefined)
} }
className="w-full mt-2 p-2 rounded bg-black/20 border border-muted" className="w-full mt-2 p-2 rounded bg-secondary border border-muted"
/> />
</div> </div>
); );
@@ -77,7 +77,7 @@ export function TextInput({
value={value ?? ''} value={value ?? ''}
onChange={(e) => setValue(e.target.value || undefined)} onChange={(e) => setValue(e.target.value || undefined)}
placeholder={placeholder} placeholder={placeholder}
className="w-full mt-2 p-2 rounded bg-black/20 border border-muted" className="w-full mt-2 p-2 rounded bg-secondary border border-muted"
/> />
</div> </div>
); );
@@ -95,8 +95,8 @@ export function Toggle({ label, value, setValue }: ToggleProps) {
<span className="text-sm">{label}</span> <span className="text-sm">{label}</span>
<button <button
onClick={() => setValue(!value)} onClick={() => setValue(!value)}
className={`w-10 h-5 rounded-full ${ className={`w-10 h-5 rounded-full border border-muted ${
value ? 'bg-violet-500' : 'bg-white/20' value ? 'bg-[var(--accent2)]' : 'bg-secondary'
} relative`} } relative`}
> >
<span <span
+2 -2
View File
@@ -141,8 +141,8 @@ function IconRail({
key={panel.id} key={panel.id}
onClick={() => toggle(panel.id, effectiveId)} onClick={() => toggle(panel.id, effectiveId)}
title={panel.label} title={panel.label}
className={`w-8 h-8 rounded-lg flex items-center justify-center transition-theme ${ className={`w-8 h-8 rounded-lg flex items-center justify-center text-secondary ${
effectiveId === panel.id ? 'bg-accent2' : '' effectiveId === panel.id ? 'border-2 border-[var(--accent2)]' : ''
}`} }`}
> >
{panel.icon} {panel.icon}
+29 -23
View File
@@ -39,7 +39,7 @@ export default function ChatUI({
stop: [''], stop: [''],
repeatPenalty: 1.1, repeatPenalty: 1.1,
seed: 0, seed: 0,
stream: false, stream: true,
keepAlive: '5m', keepAlive: '5m',
}); });
@@ -108,7 +108,8 @@ export default function ChatUI({
if (!res.ok) { if (!res.ok) {
let message = 'Request failed'; let message = 'Request failed';
if (res.status === 500) message = 'Server error (likely model too large or OOM)'; if (res.status === 500)
message = 'Server error (likely model too large or OOM)';
else if (res.status === 401) message = 'Unauthorized'; else if (res.status === 401) message = 'Unauthorized';
else message = data?.message ?? data?.error ?? message; else message = data?.message ?? data?.error ?? message;
throw new Error(message); throw new Error(message);
@@ -179,13 +180,31 @@ export default function ChatUI({
/> />
</header> </header>
{/* Messages */} {/* Messages + centered input when empty */}
<main className="flex-1 overflow-y-auto flex flex-col p-6"> <main className="flex-1 overflow-y-auto flex flex-col p-6">
{messages.length === 0 ? (
/* ── Empty state: input centered within the chat column only ── */
<div className="flex-1 flex flex-col items-center justify-center">
<p className="text-primary text-sm mb-3">
Start a conversation…
</p>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyDown={(e) =>
e.key === 'Enter' && !loading && sendMessage()
}
disabled={loading}
className="w-full max-w-2xl p-3 rounded-xl bg-secondary border border-muted outline-none disabled:opacity-50"
placeholder="Message..."
/>
</div>
) : (
/* ── Active chat: messages list ── */
<div className="w-full max-w-2xl mx-auto space-y-3"> <div className="w-full max-w-2xl mx-auto space-y-3">
{messages.map((msg, i) => ( {messages.map((msg, i) => (
<div key={i} className="text-sm"> <div key={i} className="text-sm">
<b>{msg.role === 'user' ? 'You' : 'AI'}:</b> {msg.content} <b>{msg.role === 'user' ? 'You' : 'AI'}:</b> {msg.content}
{/* Blinking cursor shown on the last message while streaming */}
{loading && {loading &&
settings.stream && settings.stream &&
i === messages.length - 1 && i === messages.length - 1 &&
@@ -198,35 +217,22 @@ export default function ChatUI({
<div className="text-xs text-muted">Thinking...</div> <div className="text-xs text-muted">Thinking...</div>
)} )}
</div> </div>
)}
</main> </main>
{/* Input — floats center when empty, slides to bottom on first message */} {/* Input bar — only shown once conversation has started */}
<div {messages.length > 0 && (
className={` <div className="p-4">
transition-all duration-500 ease-in-out
${
messages.length === 0
? 'absolute inset-0 flex items-center justify-center pointer-events-none'
: 'p-4'
}
`}
>
<div className="w-full max-w-2xl mx-auto pointer-events-auto transition-all duration-500 ease-in-out">
{messages.length === 0 && (
<p className="text-center text-primary text-sm mb-3 opacity-60">
Start a conversation…
</p>
)}
<input <input
value={input} value={input}
onChange={(e) => setInput(e.target.value)} onChange={(e) => setInput(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && !loading && sendMessage()} onKeyDown={(e) => e.key === 'Enter' && !loading && sendMessage()}
disabled={loading} disabled={loading}
className="w-full p-3 rounded-xl bg-black/20 border border-muted outline-none disabled:opacity-50" className="w-full max-w-2xl mx-auto block p-3 rounded-xl bg-secondary border border-muted outline-none disabled:opacity-50"
placeholder="Message..." placeholder="Message..."
/> />
</div> </div>
</div> )}
</div> </div>
</div> </div>
); );
-1
View File
@@ -6,7 +6,6 @@ import { getToken } from 'next-auth/jwt';
const protectedRoutes: Record<string, string[]> = { const protectedRoutes: Record<string, string[]> = {
'/example': ['ad'], '/example': ['ad'],
'/dashboard': [],
}; };
const intlMiddleware = createIntlMiddleware(routing); const intlMiddleware = createIntlMiddleware(routing);