Resizable workbench with proper scroll and fix chat message contrast.

Workbench drag handle for SQL/agent panels; dark-theme user bubbles and input field readable.
This commit is contained in:
mo
2026-06-25 01:42:25 +00:00
parent 4ff9f5589e
commit 41a8a3b18e
6 changed files with 65 additions and 18 deletions
@@ -34,7 +34,7 @@ export function AgentWorkbench({ agent, lines, busy, onSendPrompt, compact }: Pr
}, [lines, busy])
return (
<div className="flex h-full min-h-0 flex-col">
<div className="flex h-full min-h-0 flex-col overflow-hidden">
<header className={cn('flex shrink-0 items-center gap-2 border-b border-border/60 px-2', compact ? 'py-1' : 'py-3')}>
<span
className={cn('flex shrink-0 items-center justify-center rounded-lg border border-border/80 bg-surface-overlay', compact ? 'h-7 w-7' : 'h-10 w-10')}
+1 -1
View File
@@ -34,7 +34,7 @@ export function CommandBar({ busy, selectedAgent, onSubmit }: Props) {
onChange={(e) => setInput(e.target.value)}
placeholder={selectedAgent ? `Command ${selectedAgent.name.split(' ·')[0]}` : 'Enter command — auto-routed to specialist…'}
disabled={busy}
className="flex-1"
className="flex-1 bg-surface text-foreground placeholder:text-foreground-faint dark:bg-[#0d1520] dark:text-slate-100 dark:placeholder:text-slate-500"
/>
<Button type="submit" disabled={busy || !input.trim()}>
<Send className="h-3.5 w-3.5" />
+2 -2
View File
@@ -45,8 +45,8 @@ export function CommsPanel({ messages, agents, selectedAgent, busy }: Props) {
const agMeta = ag ? getAgentMeta(ag.id) : null
return (
<div key={i} className={cn('flex gap-2', m.role === 'user' && 'flex-row-reverse')}>
<div className={cn('max-w-[85%] rounded-lg border px-2 py-1.5', m.role === 'user' ? 'border-docker/25 bg-docker-light' : 'border-border bg-surface-overlay')}>
<p className="mb-0.5 text-[8px] text-foreground-muted">
<div className={cn('max-w-[85%] rounded-lg border px-2 py-1.5', m.role === 'user' ? 'border-docker/40 bg-docker/15 dark:border-blue-400/35 dark:bg-[#0f2744]' : 'border-border bg-surface-overlay')}>
<p className={cn('mb-0.5 text-[8px]', m.role === 'user' ? 'text-docker dark:text-blue-300' : 'text-foreground-muted')}>
{m.role === 'user' ? 'You' : ag?.name || m.agent}
{m.ts && ` · ${new Date(m.ts).toLocaleTimeString('en-US', { hour12: false })}`}
</p>
+5 -5
View File
@@ -144,10 +144,10 @@ export function SqlWorkbench({ engine, compact }: Props) {
</div>
</header>
<div className="flex min-h-0 flex-1">
<aside className={cn('shrink-0 border-r border-border/60 p-1.5', compact ? 'w-44' : 'w-52')}>
<div className="flex min-h-0 flex-1 overflow-hidden">
<aside className={cn('flex min-h-0 shrink-0 flex-col border-r border-border/60 p-1.5', compact ? 'w-44' : 'w-52')}>
<p className="mb-0.5 text-[8px] font-semibold uppercase tracking-wider text-foreground-faint">10 demo commands</p>
<div className="scrollbar-thin max-h-full space-y-0.5 overflow-y-auto">
<div className="scrollbar-thin min-h-0 flex-1 space-y-0.5 overflow-y-auto">
{samples.map((s) => (
<button
key={s.id}
@@ -166,8 +166,8 @@ export function SqlWorkbench({ engine, compact }: Props) {
value={sql}
onChange={(e) => setSql(e.target.value)}
className={cn(
'shrink-0 resize-none border-b border-border/60 bg-black/40 p-1.5 font-mono text-[10px] text-emerald-100 outline-none focus:ring-1 focus:ring-docker/40',
compact ? 'min-h-[40px]' : 'min-h-[56px]',
'shrink-0 resize-y border-b border-border/60 bg-[#0d1117] p-1.5 font-mono text-[10px] text-emerald-100 outline-none focus:ring-1 focus:ring-docker/40',
compact ? 'min-h-[48px] max-h-[96px]' : 'min-h-[56px] max-h-[120px]',
)}
spellCheck={false}
placeholder={`${meta.queryLabel}`}
+55 -8
View File
@@ -1,3 +1,5 @@
import { useEffect, useRef, useState } from 'react'
import { GripHorizontal } from 'lucide-react'
import type { Agent, TerminalLine } from '../../types'
import { AgentWorkbench } from './AgentWorkbench'
import { SqlWorkbench } from './SqlWorkbench'
@@ -10,18 +12,63 @@ type Props = {
onSendPrompt: (message: string, agentId?: string) => void
}
const MIN_H = 200
const DEFAULT_H = 300
const MAX_VH = 58
export function WorkbenchPanel({ mode, agent, lines, busy, onSendPrompt }: Props) {
const [height, setHeight] = useState(DEFAULT_H)
const dragging = useRef(false)
const startY = useRef(0)
const startH = useRef(0)
useEffect(() => {
const onMove = (e: MouseEvent) => {
if (!dragging.current) return
const maxH = window.innerHeight * (MAX_VH / 100)
const next = startH.current + (startY.current - e.clientY)
setHeight(Math.max(MIN_H, Math.min(maxH, next)))
}
const onUp = () => { dragging.current = false }
window.addEventListener('mousemove', onMove)
window.addEventListener('mouseup', onUp)
return () => {
window.removeEventListener('mousemove', onMove)
window.removeEventListener('mouseup', onUp)
}
}, [])
if (!mode) return null
return (
<section className="flex h-[190px] max-h-[26vh] shrink-0 flex-col border-t border-docker/25 bg-[#0a0e14] shadow-[0_-4px_20px_rgba(0,0,0,0.25)]">
{mode === 'agent' && agent && (
<AgentWorkbench agent={agent} lines={lines} busy={busy} onSendPrompt={onSendPrompt} compact />
)}
{mode === 'sql-postgres' && <SqlWorkbench engine="postgres" compact />}
{mode === 'sql-mysql' && <SqlWorkbench engine="mysql" compact />}
{mode === 'sql-mongodb' && <SqlWorkbench engine="mongodb" compact />}
{mode === 'sql-trino' && <SqlWorkbench engine="trino" compact />}
<section
style={{ height }}
className="flex shrink-0 flex-col overflow-hidden border-t border-docker/25 bg-[#0a0e14] shadow-[0_-4px_20px_rgba(0,0,0,0.25)]"
>
<div
role="separator"
aria-label="Resize workbench"
onMouseDown={(e) => {
dragging.current = true
startY.current = e.clientY
startH.current = height
e.preventDefault()
}}
className="flex h-2.5 shrink-0 cursor-ns-resize items-center justify-center border-b border-border/50 bg-surface-overlay/60 hover:bg-docker/10"
title="Sleep omhoog/omlaag om grootte aan te passen"
>
<GripHorizontal className="h-3 w-3 text-foreground-faint" />
</div>
<div className="min-h-0 flex-1 overflow-hidden">
{mode === 'agent' && agent && (
<AgentWorkbench agent={agent} lines={lines} busy={busy} onSendPrompt={onSendPrompt} compact />
)}
{mode === 'sql-postgres' && <SqlWorkbench engine="postgres" compact />}
{mode === 'sql-mysql' && <SqlWorkbench engine="mysql" compact />}
{mode === 'sql-mongodb' && <SqlWorkbench engine="mongodb" compact />}
{mode === 'sql-trino' && <SqlWorkbench engine="trino" compact />}
</div>
</section>
)
}