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:
@@ -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')}
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}…`}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user