Files
humanlayer/humanlayer-wui/src/components/SessionTableSearch.tsx
dexhorthy 08bca9744d Add first user message injection to session detail message stream
- Create synthetic ConversationEvent from session.query to show first user message
- Add User icon for user role messages in eventToDisplayObject function
- Inject first user message at beginning of conversation events list
- Resolve merge conflicts in SessionTable.tsx to combine search highlighting with collapsible queries
- Fix TypeScript and linting issues with proper imports and types
- Ensure consistent message display with timestamps and proper formatting

The first user message (session.query) now appears in the conversation stream
instead of only being shown as a truncated header, providing better context
for understanding the conversation flow.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-06-24 13:18:31 -07:00

95 lines
2.6 KiB
TypeScript

import { Search } from 'lucide-react'
import { cn } from '@/lib/utils'
import { Input } from './ui/input'
import { useHotkeys } from 'react-hotkeys-hook'
import { SessionTableHotkeysScope } from './internal/SessionTable'
interface SessionTableSearchProps {
value: string
onChange: (value: string) => void
placeholder?: string
className?: string
statusFilter?: string | null
onEscape?: () => void
}
export function SessionTableSearch({
value,
onChange,
placeholder = 'Search sessions...',
className,
statusFilter,
}: SessionTableSearchProps) {
const inputClassId = 'session-table-search-input'
// For unknown reasons, I can't seem to detect a 'slash' character here. This is the silliest.
// But maybe we're all the silliest and this continues a long and highly venerated tradition of silliness.
useHotkeys(
'*',
e => {
if (e.key === '/') {
e.preventDefault()
e.stopPropagation()
const input = document.getElementById(inputClassId) as HTMLInputElement
if (input) {
input.focus()
input.select()
}
}
},
{
scopes: SessionTableHotkeysScope,
enabled: true,
},
)
useHotkeys(
'escape',
() => {
const input = document.getElementById(inputClassId) as HTMLInputElement
if (input) {
input.blur()
}
},
{
scopes: SessionTableHotkeysScope,
enabled: true,
enableOnFormTags: ['INPUT'],
},
)
return (
<div className={cn('relative flex items-center gap-2', className)}>
<div className="relative flex-1">
<Search className="absolute left-3 h-4 w-4 text-muted-foreground top-1/2 -translate-y-1/2" />
<Input
id={inputClassId}
type="text"
value={value}
onChange={e => onChange(e.target.value)}
placeholder={placeholder}
className={cn(
'w-full h-9 pl-10 pr-3 text-sm',
'font-mono',
'bg-background border rounded-md',
'transition-all duration-200',
'placeholder:text-muted-foreground/60',
'border-border hover:border-primary/50 focus:border-primary focus:ring-2 focus:ring-primary/20',
'focus:outline-none',
)}
spellCheck={false}
/>
</div>
{statusFilter && (
<span
className="px-2 py-1 text-xs text-accent-foreground rounded whitespace-nowrap"
style={{ backgroundColor: 'var(--terminal-accent)' }}
>
status: {statusFilter.toLowerCase()}
</span>
)}
</div>
)
}