mirror of
https://github.com/hanzoai/agents.git
synced 2026-10-30 18:48:36 +00:00
This example shipped a live tailwind pipeline (tailwind.config.ts, a postcss
config running the tailwind plugin, @tailwind directives in globals.css), ten
@radix-ui packages, and fourteen vendored shadcn components. Tailwind classes
and radix primitives are DOM-only, so the example taught readers a pattern that
cannot run on iOS or Android — and examples propagate.
Everything now renders through @hanzo/ui on the @hanzo/gui backend, which
compiles to React Native. Utility strings became style props on View/XStack/
YStack/Text; the vendored ui/ directory is gone because @hanzo/ui already
exports every primitive it re-implemented, with matching variant names.
Tailwind and @hanzo/ui cannot coexist: @hanzo/ui ships plain CSS cascade layers
and tailwind's postcss plugin rejects `@layer base` without `@tailwind base`.
The pipeline had to come out for the library to load at all, which is why this
is a whole-app change rather than a partial one.
Also removed EvaluationForm.tsx — 273 lines of tailwind nothing imported.
Platform moves the conversion required: React 18 -> 19 and Next 14 -> 15 for
@hanzo/ui's peer range, react-native aliased to react-native-web for the web
bundle, and <Hanzo> mounted in the root layout to install the theme.
Three defects found by rendering it rather than trusting the build:
- Tabs defaults to flexDirection row, so tab panels laid out beside the tab
list and spilled outside the metric cards
- TabsContent computes flex 1 1 0, which collapses to height 0 inside a
content-sized column; it needs flexBasis auto there
- CollapsibleTrigger has no asChild — it wraps children itself, so the claim
rows rendered as empty boxes
Measured: 1465 tailwind utility tokens across 22 files -> 0; 10 radix packages
-> 0; 14 vendored shadcn files -> 0. next build green, tsc --noEmit clean,
every tab and the claim expand/collapse verified in a real browser with no
console errors.
142 lines
4.9 KiB
TypeScript
142 lines
4.9 KiB
TypeScript
'use client'
|
|
|
|
import { Loader2, CheckCircle, Circle, ArrowRight } from 'lucide-react'
|
|
import {
|
|
Badge,
|
|
Card,
|
|
CardContent,
|
|
ScrollView,
|
|
Text,
|
|
XStack,
|
|
YStack,
|
|
} from '@hanzo/ui'
|
|
import type { BadgeVariant } from '@hanzo/ui'
|
|
import { WorkflowNote } from '@/lib/types'
|
|
|
|
interface LoadingStateProps {
|
|
notes: WorkflowNote[]
|
|
currentStep: string | null
|
|
}
|
|
|
|
function getTagVariant(tag: string): BadgeVariant {
|
|
const variants: Record<string, BadgeVariant> = {
|
|
orchestration: 'default',
|
|
faithfulness: 'secondary',
|
|
relevance: 'secondary',
|
|
hallucination: 'secondary',
|
|
constitutional: 'secondary',
|
|
complete: 'default',
|
|
}
|
|
return variants[tag] || 'outline'
|
|
}
|
|
|
|
function formatTime(timestamp: string): string {
|
|
const diffSec = Math.floor((Date.now() - new Date(timestamp).getTime()) / 1000)
|
|
if (diffSec < 1) return 'now'
|
|
if (diffSec < 60) return `${diffSec}s ago`
|
|
return `${Math.floor(diffSec / 60)}m ago`
|
|
}
|
|
|
|
export function LoadingState({ notes, currentStep }: LoadingStateProps) {
|
|
return (
|
|
<YStack maxWidth={512} width="100%">
|
|
<Card>
|
|
<CardContent>
|
|
<YStack paddingTop="$8" paddingBottom="$6" gap="$6">
|
|
<YStack alignItems="center" gap="$2">
|
|
<Loader2 size={40} />
|
|
<Text fontSize={18} fontWeight="500">
|
|
Evaluating your response...
|
|
</Text>
|
|
<Text fontSize={14} color="$color11">
|
|
Multi-perspective analysis in progress
|
|
</Text>
|
|
</YStack>
|
|
|
|
{currentStep && (
|
|
<XStack alignItems="center" justifyContent="center" gap="$2" paddingVertical="$2" paddingHorizontal="$4" borderRadius="$4" backgroundColor="$color2">
|
|
<ArrowRight size={16} />
|
|
<Text fontSize={14} fontWeight="500" numberOfLines={1}>
|
|
{currentStep}
|
|
</Text>
|
|
</XStack>
|
|
)}
|
|
|
|
<YStack borderWidth={1} borderColor="$borderColor" borderRadius="$4">
|
|
<XStack
|
|
paddingHorizontal="$3"
|
|
paddingVertical="$2"
|
|
borderBottomWidth={1}
|
|
borderBottomColor="$borderColor"
|
|
alignItems="center"
|
|
justifyContent="space-between"
|
|
backgroundColor="$color2"
|
|
>
|
|
<Text fontSize={12} fontWeight="500" color="$color11" textTransform="uppercase">
|
|
Workflow Log
|
|
</Text>
|
|
<Text fontSize={12} color="$color11">
|
|
{notes.length} events
|
|
</Text>
|
|
</XStack>
|
|
<ScrollView height={224}>
|
|
<YStack padding="$2" gap="$1">
|
|
{notes.length === 0 ? (
|
|
<YStack alignItems="center" paddingVertical="$8" gap="$2">
|
|
<Loader2 size={20} opacity={0.5} />
|
|
<Text fontSize={14} color="$color11">
|
|
Waiting for workflow events...
|
|
</Text>
|
|
</YStack>
|
|
) : (
|
|
notes.map((note, index) => (
|
|
<XStack
|
|
key={`${note.timestamp}-${index}`}
|
|
alignItems="flex-start"
|
|
gap="$2"
|
|
paddingVertical={6}
|
|
paddingHorizontal="$2"
|
|
borderRadius="$2"
|
|
>
|
|
<YStack marginTop={2} flexShrink={0}>
|
|
{index === notes.length - 1 ? (
|
|
<Circle size={14} />
|
|
) : (
|
|
<CheckCircle size={14} />
|
|
)}
|
|
</YStack>
|
|
<YStack flex={1} minWidth={0} gap="$1">
|
|
<Text
|
|
fontSize={14}
|
|
color={index === notes.length - 1 ? '$color' : '$color11'}
|
|
>
|
|
{note.message}
|
|
</Text>
|
|
<XStack alignItems="center" gap={6} flexWrap="wrap">
|
|
{note.tags.slice(0, 3).map((tag) => (
|
|
<Badge key={tag} variant={getTagVariant(tag)}>
|
|
{tag}
|
|
</Badge>
|
|
))}
|
|
<Text fontSize={12} color="$color11">
|
|
{formatTime(note.timestamp)}
|
|
</Text>
|
|
</XStack>
|
|
</YStack>
|
|
</XStack>
|
|
))
|
|
)}
|
|
</YStack>
|
|
</ScrollView>
|
|
</YStack>
|
|
|
|
<Text textAlign="center" fontSize={12} color="$color11">
|
|
Typically takes 5-15 seconds
|
|
</Text>
|
|
</YStack>
|
|
</CardContent>
|
|
</Card>
|
|
</YStack>
|
|
)
|
|
}
|