Files
zooqueenandHanzo Dev 3d8b2650be review: the remaining minors — honest phases, lean payloads, real probes
The fall-back UTF-8 decoder carries a split multibyte sequence between
chunks instead of reading past the array (utf8() is now the one per-stream
decoder, both faces, with a boundary-sweep test); a chat turn ends its run
as done/stopped/error instead of filing every ending as 'streaming'; a
generated image parks its bytes in a cache file so the 20-run ring never
pins megabytes of base64; the model picker offers Reload only on a service
refusal (permission and credential must not bait a retry); and the iOS
widget module probes the App Group with containerURL — the one call that
actually returns nil when the entitlement is missing — and says so in the
log instead of writing to an unshared bucket forever.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-05 23:00:50 -07:00

95 lines
3.2 KiB
TypeScript

import { Button, ScrollView, Sheet, Spinner, Text, XStack, YStack } from '@hanzo/gui'
import { Check } from '@hanzogui/lucide-icons-2'
import type { Refusal } from '@/lib/api'
// The model picker: the live catalog in a sheet, nothing invented. The
// three states are all honest — loading, the exact refusal sentence, or the
// list. Reload appears ONLY for a service refusal, the one kind whose
// message honestly says try again; a permission or credential refusal must
// never bait a retry that can only repeat itself.
export function Picker(props: {
open: boolean
onClose(): void
model: string | null
ids: string[] | null
note: Refusal | null
onPick(id: string): void
onReload(): void
}) {
return (
<Sheet
modal
open={props.open}
onOpenChange={(open: boolean) => {
if (!open) props.onClose()
}}
snapPointsMode="percent"
snapPoints={[70]}
dismissOnSnapToBottom
>
<Sheet.Overlay bg="rgba(0,0,0,0.6)" />
<Sheet.Frame bg="$background" borderTopWidth={1} borderColor="$borderColor" rounded="$6">
<Sheet.Handle bg="$color4" />
<YStack px="$4" pt="$2" pb="$3" borderBottomWidth={1} borderColor="$borderColor">
<Text color="$color" fontSize={16} fontWeight="600">
Model
</Text>
<Text color="$color10" fontSize={12} mt="$1">
Live from api.hanzo.ai
</Text>
</YStack>
{props.ids === null && !props.note ? (
<YStack flex={1} items="center" justify="center" gap="$3">
<Spinner color="$color10" />
<Text color="$color10" fontSize={13}>
Loading the catalog
</Text>
</YStack>
) : props.note ? (
<YStack flex={1} items="center" justify="center" p="$5" gap="$3">
<Text color="$color" fontSize={14} text="center" lineHeight={20}>
{props.note.message}
</Text>
{props.note.kind === 'service' ? (
<Button size="$3" bg="$color3" color="$color" onPress={props.onReload}>
Reload
</Button>
) : null}
</YStack>
) : props.ids && props.ids.length === 0 ? (
<YStack flex={1} items="center" justify="center" p="$5">
<Text color="$color10" fontSize={14} text="center">
The catalog returned no models.
</Text>
</YStack>
) : (
<ScrollView flex={1}>
<YStack py="$2">
{(props.ids ?? []).map((id) => (
<XStack
key={id}
px="$4"
py="$3"
items="center"
justify="space-between"
pressStyle={{ bg: '$color2' }}
onPress={() => {
props.onPick(id)
props.onClose()
}}
>
<Text color={id === props.model ? '$color' : '$color11'} fontSize={15}>
{id}
</Text>
{id === props.model ? <Check size={16} color="$color" /> : null}
</XStack>
))}
</YStack>
</ScrollView>
)}
</Sheet.Frame>
</Sheet>
)
}