oxios 1.5.2

Oxios Agent OS — Agent Operating System powered by oxi-sdk
import { useQuery } from '@tanstack/react-query'
import { MessageSquare, Zap } from 'lucide-react'
import { useTranslation } from 'react-i18next'
import { api } from '@/lib/api-client'
import { cn } from '@/lib/utils'
import { useChatStore } from '@/stores/chat'
import type { Session } from '@/types'

/**
 * Empty state shown when the chat has no messages yet.
 * Shows mode toggle and recent sessions.
 */
export function EmptyChatState() {
  const { t } = useTranslation()
  const loadSession = useChatStore((s) => s.loadSession)
  const specMode = useChatStore((s) => s.specMode)
  const toggleSpecMode = useChatStore((s) => s.toggleSpecMode)

  const { data: sessionsData } = useQuery({
    queryKey: ['sessions-recent'],
    queryFn: () => api.get<{ items: Session[]; total: number }>('/api/sessions'),
    refetchInterval: 30_000,
  })

  const sessions: Session[] = Array.isArray(sessionsData?.items)
    ? sessionsData.items.slice(0, 8)
    : []

  return (
    <div className="flex flex-col items-center gap-8 py-12 px-4">
      <div className="text-center space-y-3">
        <p className="text-lg font-semibold text-foreground">
          {t('chat.greeting', 'What can I help you with?')}
        </p>
        {/* Mode toggle — only visible before first message */}
        <button
          type="button"
          onClick={toggleSpecMode}
          className={cn(
            'mx-auto inline-flex items-center gap-1.5 text-2xs font-medium px-3 py-1.5 rounded-full border transition-all',
            specMode
              ? 'text-primary border-primary/30 bg-primary/5'
              : 'text-muted-foreground border-border bg-muted/40 hover:bg-muted',
          )}
        >
          {specMode ? <Zap className="h-3 w-3" /> : <MessageSquare className="h-3 w-3" />}
          {specMode ? 'Ouroboros' : 'Chat'}
        </button>
      </div>

      {sessions.length > 0 ? (
        <div className="w-full max-w-md space-y-1">
          <p className="text-xs font-medium text-muted-foreground mb-2">
            {t('chat.recentSessions', 'Recent conversations')}
          </p>
          <div className="space-y-1">
            {sessions.map((s) => {
              const isSpec = s.metadata?.mode === 'spec' || s.metadata?.mode === 'ouroboros'
              const timeStr = formatRelativeTime(s.created_at)

              return (
                <button
                  key={s.id}
                  type="button"
                  onClick={() => loadSession(s.id)}
                  className="flex items-center gap-3 w-full rounded-lg border bg-card px-3 py-2.5 text-left text-sm transition-all hover:bg-accent hover:border-primary/20 hover:shadow-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
                >
                  <div
                    className={cn(
                      'flex h-8 w-8 shrink-0 items-center justify-center rounded-full',
                      isSpec ? 'bg-primary/10 text-primary' : 'bg-muted text-muted-foreground',
                    )}
                  >
                    {isSpec ? <Zap className="h-4 w-4" /> : <MessageSquare className="h-4 w-4" />}
                  </div>
                  <div className="min-w-0 flex-1">
                    <p className="truncate text-foreground">{s.title ?? `${s.id.slice(0, 8)}…`}</p>
                    <p className="text-2xs text-muted-foreground">
                      {isSpec ? 'Ouroboros · ' : ''}
                      {timeStr}
                    </p>
                  </div>
                </button>
              )
            })}
          </div>
        </div>
      ) : null}
    </div>
  )
}

function formatRelativeTime(dateStr: string): string {
  const diff = Date.now() - new Date(dateStr).getTime()
  const minutes = Math.floor(diff / 60_000)
  if (minutes < 1) return 'just now'
  if (minutes < 60) return `${minutes}m ago`
  const hours = Math.floor(minutes / 60)
  if (hours < 24) return `${hours}h ago`
  const days = Math.floor(hours / 24)
  if (days < 7) return `${days}d ago`
  return new Date(dateStr).toLocaleDateString(undefined, {
    month: 'short',
    day: 'numeric',
  })
}