bazilion 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.understand-anything/.understandignore +25 -0
- package/.understand-anything/fingerprints.json +14267 -0
- package/.understand-anything/knowledge-graph.json +18128 -0
- package/.understand-anything/meta.json +6 -0
- package/CLAUDE.md +164 -0
- package/LICENSE +21 -0
- package/README.md +195 -0
- package/apps/cli/package.json +21 -0
- package/apps/cli/src/auth-file.ts +18 -0
- package/apps/cli/src/client.ts +37 -0
- package/apps/cli/src/columnize.ts +32 -0
- package/apps/cli/src/commands/agent.ts +574 -0
- package/apps/cli/src/commands/auth.ts +110 -0
- package/apps/cli/src/commands/backup.ts +135 -0
- package/apps/cli/src/commands/completion.ts +155 -0
- package/apps/cli/src/commands/config.ts +95 -0
- package/apps/cli/src/commands/doctor.ts +131 -0
- package/apps/cli/src/commands/group.ts +132 -0
- package/apps/cli/src/commands/inbox.ts +82 -0
- package/apps/cli/src/commands/login.ts +73 -0
- package/apps/cli/src/commands/memory.ts +106 -0
- package/apps/cli/src/commands/profile.ts +259 -0
- package/apps/cli/src/commands/provider.ts +170 -0
- package/apps/cli/src/commands/send.ts +24 -0
- package/apps/cli/src/commands/serve.ts +89 -0
- package/apps/cli/src/commands/skill.ts +120 -0
- package/apps/cli/src/commands/token.ts +148 -0
- package/apps/cli/src/commands/trigger.ts +129 -0
- package/apps/cli/src/commands/uninstall.ts +156 -0
- package/apps/cli/src/index.ts +196 -0
- package/apps/cli/src/paths.ts +12 -0
- package/apps/cli/test/agent.test.ts +213 -0
- package/apps/cli/test/backup.test.ts +95 -0
- package/apps/cli/test/chat.test.ts +539 -0
- package/apps/cli/test/columnize.test.ts +29 -0
- package/apps/cli/test/completion.test.ts +45 -0
- package/apps/cli/test/config-page.test.ts +151 -0
- package/apps/cli/test/group.test.ts +74 -0
- package/apps/cli/test/helpers.ts +70 -0
- package/apps/cli/test/inbox-autodeliver.test.ts +143 -0
- package/apps/cli/test/inbox.test.ts +147 -0
- package/apps/cli/test/memory.test.ts +69 -0
- package/apps/cli/test/profile.test.ts +110 -0
- package/apps/cli/test/send.test.ts +30 -0
- package/apps/cli/test/server-fixture.ts +212 -0
- package/apps/cli/test/session-head.test.ts +45 -0
- package/apps/cli/test/skill.test.ts +128 -0
- package/apps/cli/test/token.test.ts +109 -0
- package/apps/cli/test/trigger.test.ts +245 -0
- package/apps/cli/tsconfig.json +4 -0
- package/apps/daemon/package.json +31 -0
- package/apps/daemon/src/app.ts +41 -0
- package/apps/daemon/src/core/agent/archive.ts +8 -0
- package/apps/daemon/src/core/agent/delete.ts +30 -0
- package/apps/daemon/src/core/agent/resolve.ts +31 -0
- package/apps/daemon/src/core/agent/spawn.ts +95 -0
- package/apps/daemon/src/core/agent/unarchive.ts +11 -0
- package/apps/daemon/src/core/availableModels.ts +58 -0
- package/apps/daemon/src/core/db/client.ts +113 -0
- package/apps/daemon/src/core/db/migrate.ts +41 -0
- package/apps/daemon/src/core/db/migrations/0001_init.sql +179 -0
- package/apps/daemon/src/core/group/delete.ts +21 -0
- package/apps/daemon/src/core/group/register.ts +68 -0
- package/apps/daemon/src/core/index.ts +71 -0
- package/apps/daemon/src/core/paths.ts +56 -0
- package/apps/daemon/src/core/profile/create.ts +78 -0
- package/apps/daemon/src/core/profile/delete.ts +26 -0
- package/apps/daemon/src/core/profile/identity.ts +70 -0
- package/apps/daemon/src/core/profile/load.ts +45 -0
- package/apps/daemon/src/core/profile/seed.ts +74 -0
- package/apps/daemon/src/core/profile/templates.ts +60 -0
- package/apps/daemon/src/core/profile/update.ts +57 -0
- package/apps/daemon/src/core/profile/validate.ts +9 -0
- package/apps/daemon/src/core/repos/agents.ts +202 -0
- package/apps/daemon/src/core/repos/config.ts +78 -0
- package/apps/daemon/src/core/repos/groups.ts +55 -0
- package/apps/daemon/src/core/repos/messages.ts +127 -0
- package/apps/daemon/src/core/repos/profiles.ts +83 -0
- package/apps/daemon/src/core/repos/providerModels.ts +58 -0
- package/apps/daemon/src/core/repos/providerState.ts +37 -0
- package/apps/daemon/src/core/repos/secrets.ts +145 -0
- package/apps/daemon/src/core/repos/skillMeta.ts +49 -0
- package/apps/daemon/src/core/repos/triggers.ts +101 -0
- package/apps/daemon/src/core/repos/webTokens.ts +87 -0
- package/apps/daemon/src/core/secrets.ts +65 -0
- package/apps/daemon/src/core/services.ts +264 -0
- package/apps/daemon/src/core/skills/discover.ts +28 -0
- package/apps/daemon/src/core/skills/import.ts +136 -0
- package/apps/daemon/src/core/skills/parse.ts +52 -0
- package/apps/daemon/src/core/skills/resolve.ts +50 -0
- package/apps/daemon/src/index.ts +45 -0
- package/apps/daemon/src/lib/agent-cancel.ts +48 -0
- package/apps/daemon/src/lib/agent-id.ts +13 -0
- package/apps/daemon/src/lib/agent-turn.ts +56 -0
- package/apps/daemon/src/lib/api-key.ts +56 -0
- package/apps/daemon/src/lib/auth.ts +41 -0
- package/apps/daemon/src/lib/cron.ts +93 -0
- package/apps/daemon/src/lib/ctx.ts +80 -0
- package/apps/daemon/src/lib/messaging-host.ts +34 -0
- package/apps/daemon/src/lib/middleware-auth.ts +52 -0
- package/apps/daemon/src/lib/scheduler.ts +294 -0
- package/apps/daemon/src/routes/agents.ts +772 -0
- package/apps/daemon/src/routes/auth-login.ts +193 -0
- package/apps/daemon/src/routes/config.ts +267 -0
- package/apps/daemon/src/routes/groups.ts +133 -0
- package/apps/daemon/src/routes/messages.ts +29 -0
- package/apps/daemon/src/routes/misc.ts +239 -0
- package/apps/daemon/src/routes/profiles.ts +197 -0
- package/apps/daemon/src/routes/skills.ts +123 -0
- package/apps/daemon/src/routes/triggers.ts +29 -0
- package/apps/daemon/src/runtime/auth/openai-codex.ts +121 -0
- package/apps/daemon/src/runtime/auto-reply/heartbeat.ts +31 -0
- package/apps/daemon/src/runtime/index.ts +77 -0
- package/apps/daemon/src/runtime/memory/files.ts +103 -0
- package/apps/daemon/src/runtime/memory/qmd.ts +152 -0
- package/apps/daemon/src/runtime/memory/types.ts +16 -0
- package/apps/daemon/src/runtime/pi/events.ts +173 -0
- package/apps/daemon/src/runtime/pi/session.ts +536 -0
- package/apps/daemon/src/runtime/pi/tools.ts +85 -0
- package/apps/daemon/src/runtime/providers/catalog.ts +145 -0
- package/apps/daemon/src/runtime/providers/pi-adapter.ts +272 -0
- package/apps/daemon/src/runtime/providers/registry.ts +374 -0
- package/apps/daemon/src/runtime/providers/retry.ts +176 -0
- package/apps/daemon/src/runtime/providers/types.ts +33 -0
- package/apps/daemon/src/runtime/session/prompt.ts +83 -0
- package/apps/daemon/src/runtime/tools/bootstrap.ts +22 -0
- package/apps/daemon/src/runtime/tools/home.ts +114 -0
- package/apps/daemon/src/runtime/tools/memory.ts +81 -0
- package/apps/daemon/src/runtime/tools/messaging.ts +127 -0
- package/apps/daemon/src/runtime/tools/registry.ts +29 -0
- package/apps/daemon/src/runtime/tools/types.ts +13 -0
- package/apps/daemon/src/runtime/tools/web-extract.ts +110 -0
- package/apps/daemon/src/runtime/tools/web-ssrf.ts +245 -0
- package/apps/daemon/src/runtime/tools/web.ts +273 -0
- package/apps/daemon/src/runtime/worker/entry.ts +221 -0
- package/apps/daemon/src/runtime/worker/ipc-protocol.ts +75 -0
- package/apps/daemon/src/runtime/worker/spawn.ts +249 -0
- package/apps/daemon/test/core/agents.test.ts +319 -0
- package/apps/daemon/test/core/available-models.test.ts +63 -0
- package/apps/daemon/test/core/config.test.ts +99 -0
- package/apps/daemon/test/core/groups.test.ts +63 -0
- package/apps/daemon/test/core/helpers.ts +50 -0
- package/apps/daemon/test/core/identity.test.ts +85 -0
- package/apps/daemon/test/core/migrations.test.ts +83 -0
- package/apps/daemon/test/core/profiles.test.ts +182 -0
- package/apps/daemon/test/core/provider-models.test.ts +57 -0
- package/apps/daemon/test/core/provider-state.test.ts +36 -0
- package/apps/daemon/test/core/skill-meta.test.ts +45 -0
- package/apps/daemon/test/core/skills.test.ts +271 -0
- package/apps/daemon/test/core/triggers.test.ts +182 -0
- package/apps/daemon/test/core/web-tokens.test.ts +79 -0
- package/apps/daemon/test/cron.test.ts +90 -0
- package/apps/daemon/test/runtime/heartbeat.test.ts +34 -0
- package/apps/daemon/test/runtime/memory-qmd.test.ts +97 -0
- package/apps/daemon/test/runtime/memory.test.ts +78 -0
- package/apps/daemon/test/runtime/messaging.test.ts +213 -0
- package/apps/daemon/test/runtime/mock-server.ts +65 -0
- package/apps/daemon/test/runtime/openai-codex-auth.test.ts +116 -0
- package/apps/daemon/test/runtime/providers.test.ts +306 -0
- package/apps/daemon/test/runtime/retry.test.ts +191 -0
- package/apps/daemon/test/runtime/session-head.test.ts +90 -0
- package/apps/daemon/test/runtime/tools-home.test.ts +102 -0
- package/apps/daemon/test/runtime/tools-web.test.ts +206 -0
- package/apps/daemon/tsconfig.json +4 -0
- package/apps/mobile/README.md +60 -0
- package/apps/mobile/app/_layout.tsx +58 -0
- package/apps/mobile/app/agents/[id]/chat.tsx +486 -0
- package/apps/mobile/app/agents/[id]/index.tsx +166 -0
- package/apps/mobile/app/agents/index.tsx +212 -0
- package/apps/mobile/app/index.tsx +21 -0
- package/apps/mobile/app/pair.tsx +226 -0
- package/apps/mobile/app/settings.tsx +419 -0
- package/apps/mobile/app.json +49 -0
- package/apps/mobile/assets/adaptive-icon.png +0 -0
- package/apps/mobile/assets/favicon.png +0 -0
- package/apps/mobile/assets/icon.png +0 -0
- package/apps/mobile/assets/splash-icon.png +0 -0
- package/apps/mobile/babel.config.js +6 -0
- package/apps/mobile/metro.config.js +28 -0
- package/apps/mobile/package.json +44 -0
- package/apps/mobile/src/auth.ts +66 -0
- package/apps/mobile/src/pair-url.ts +48 -0
- package/apps/mobile/src/theme-context.tsx +88 -0
- package/apps/mobile/src/theme.ts +135 -0
- package/apps/mobile/test/pair-url.test.ts +46 -0
- package/apps/mobile/tsconfig.json +23 -0
- package/apps/web/components.json +25 -0
- package/apps/web/package.json +39 -0
- package/apps/web/public/baziu.svg +8 -0
- package/apps/web/src/components/AgentTabs.tsx +45 -0
- package/apps/web/src/components/BaziuLogo.tsx +21 -0
- package/apps/web/src/components/ChatPane.tsx +1033 -0
- package/apps/web/src/components/ConfigTabs.tsx +29 -0
- package/apps/web/src/components/CopyButton.tsx +68 -0
- package/apps/web/src/components/CreateGroupDialog.tsx +127 -0
- package/apps/web/src/components/FieldRow.tsx +94 -0
- package/apps/web/src/components/Footer.tsx +10 -0
- package/apps/web/src/components/PawIcon.tsx +15 -0
- package/apps/web/src/components/Sidebar.tsx +287 -0
- package/apps/web/src/components/SpawnDialog.tsx +129 -0
- package/apps/web/src/components/ThemeToggle.tsx +75 -0
- package/apps/web/src/components/TopNav.tsx +34 -0
- package/apps/web/src/components/ui/button.tsx +67 -0
- package/apps/web/src/components/ui/card.tsx +103 -0
- package/apps/web/src/components/ui/checkbox.tsx +31 -0
- package/apps/web/src/components/ui/dialog.tsx +168 -0
- package/apps/web/src/components/ui/input.tsx +19 -0
- package/apps/web/src/components/ui/label.tsx +22 -0
- package/apps/web/src/components/ui/radio-group.tsx +44 -0
- package/apps/web/src/components/ui/select.tsx +192 -0
- package/apps/web/src/components/ui/separator.tsx +26 -0
- package/apps/web/src/components/ui/table.tsx +116 -0
- package/apps/web/src/components/ui/tabs.tsx +88 -0
- package/apps/web/src/components/ui/textarea.tsx +18 -0
- package/apps/web/src/lib/auth.ts +50 -0
- package/apps/web/src/lib/daemon-client.ts +34 -0
- package/apps/web/src/lib/md.ts +45 -0
- package/apps/web/src/lib/utils.ts +6 -0
- package/apps/web/src/lib/wire-constants.ts +27 -0
- package/apps/web/src/routeTree.gen.ts +408 -0
- package/apps/web/src/router.tsx +20 -0
- package/apps/web/src/routes/__root.tsx +123 -0
- package/apps/web/src/routes/agents/$id/inbox.tsx +207 -0
- package/apps/web/src/routes/agents/$id/index.tsx +527 -0
- package/apps/web/src/routes/agents/$id/triggers.tsx +239 -0
- package/apps/web/src/routes/agents/index.tsx +265 -0
- package/apps/web/src/routes/api/$.ts +88 -0
- package/apps/web/src/routes/config/index.tsx +315 -0
- package/apps/web/src/routes/config/services.tsx +49 -0
- package/apps/web/src/routes/config/tokens.tsx +192 -0
- package/apps/web/src/routes/groups/$id/index.tsx +153 -0
- package/apps/web/src/routes/groups/$id/memory.tsx +321 -0
- package/apps/web/src/routes/groups/index.tsx +191 -0
- package/apps/web/src/routes/index.tsx +133 -0
- package/apps/web/src/routes/login.tsx +63 -0
- package/apps/web/src/routes/profiles/$id.tsx +549 -0
- package/apps/web/src/routes/profiles/index.tsx +458 -0
- package/apps/web/src/routes/skills/index.tsx +297 -0
- package/apps/web/src/routes/welcome.tsx +61 -0
- package/apps/web/src/styles.css +449 -0
- package/apps/web/tsconfig.json +25 -0
- package/apps/web/vite.config.ts +25 -0
- package/biome.json +23 -0
- package/docs/agent-engine.md +219 -0
- package/docs/architecture.md +627 -0
- package/docs/backlog/README.md +42 -0
- package/docs/backlog/draft/BAZ-001-a2a-federation-spike.md +125 -0
- package/docs/openclaw-reference.md +210 -0
- package/package.json +38 -0
- package/packages/api-types/package.json +11 -0
- package/packages/api-types/src/entities.ts +146 -0
- package/packages/api-types/src/events.ts +55 -0
- package/packages/api-types/src/index.ts +488 -0
- package/packages/api-types/src/memory.ts +15 -0
- package/packages/client/package.json +13 -0
- package/packages/client/src/index.ts +117 -0
- package/pnpm-workspace.yaml +3 -0
- package/tsconfig.base.json +23 -0
- package/tsconfig.json +11 -0
- package/vitest.config.ts +22 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import type { ResolvedAgent } from '@bazilion/api-types'
|
|
2
|
+
import { ApiClientError } from '@bazilion/client'
|
|
3
|
+
import { router, useLocalSearchParams } from 'expo-router'
|
|
4
|
+
import { useCallback, useEffect, useMemo, useState } from 'react'
|
|
5
|
+
import { ActivityIndicator, Pressable, ScrollView, StyleSheet, Text, View } from 'react-native'
|
|
6
|
+
import { clearCredentials, clientFor, loadCredentials } from '@/src/auth'
|
|
7
|
+
import { useColors } from '@/src/theme-context'
|
|
8
|
+
import { type Colors, fonts, radii } from '@/src/theme'
|
|
9
|
+
|
|
10
|
+
type Load =
|
|
11
|
+
| { kind: 'loading' }
|
|
12
|
+
| { kind: 'ready'; agent: ResolvedAgent }
|
|
13
|
+
| { kind: 'error'; message: string }
|
|
14
|
+
|
|
15
|
+
export default function AgentDetail() {
|
|
16
|
+
const { id } = useLocalSearchParams<{ id: string }>()
|
|
17
|
+
const [load, setLoad] = useState<Load>({ kind: 'loading' })
|
|
18
|
+
const colors = useColors()
|
|
19
|
+
const styles = useMemo(() => makeStyles(colors), [colors])
|
|
20
|
+
|
|
21
|
+
const fetchAgent = useCallback(async () => {
|
|
22
|
+
setLoad({ kind: 'loading' })
|
|
23
|
+
const creds = await loadCredentials()
|
|
24
|
+
if (!creds) {
|
|
25
|
+
router.replace('/pair')
|
|
26
|
+
return
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
const agent = await clientFor(creds).get<ResolvedAgent>(`/api/agents/${id}`)
|
|
30
|
+
setLoad({ kind: 'ready', agent })
|
|
31
|
+
} catch (err) {
|
|
32
|
+
if (err instanceof ApiClientError && err.status === 401) {
|
|
33
|
+
await clearCredentials()
|
|
34
|
+
router.replace('/pair')
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
const message = err instanceof Error ? err.message : 'unknown error'
|
|
38
|
+
setLoad({ kind: 'error', message })
|
|
39
|
+
}
|
|
40
|
+
}, [id])
|
|
41
|
+
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
fetchAgent()
|
|
44
|
+
}, [fetchAgent])
|
|
45
|
+
|
|
46
|
+
if (load.kind === 'loading') {
|
|
47
|
+
return (
|
|
48
|
+
<View style={styles.centered}>
|
|
49
|
+
<ActivityIndicator />
|
|
50
|
+
</View>
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (load.kind === 'error') {
|
|
55
|
+
return (
|
|
56
|
+
<View style={styles.centered}>
|
|
57
|
+
<Text style={styles.errorTitle}>Couldn't load agent</Text>
|
|
58
|
+
<Text style={styles.errorBody}>{load.message}</Text>
|
|
59
|
+
<Pressable style={styles.primaryBtn} onPress={fetchAgent}>
|
|
60
|
+
<Text style={styles.primaryBtnText}>Retry</Text>
|
|
61
|
+
</Pressable>
|
|
62
|
+
</View>
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const a = load.agent
|
|
67
|
+
return (
|
|
68
|
+
<ScrollView style={styles.screen} contentContainerStyle={styles.content}>
|
|
69
|
+
<Text style={styles.name}>{a.agent.name}</Text>
|
|
70
|
+
<Text style={styles.subtitle}>{a.agent.id}</Text>
|
|
71
|
+
|
|
72
|
+
<Section label="status">
|
|
73
|
+
<Text style={styles.mono}>{a.agent.status}</Text>
|
|
74
|
+
</Section>
|
|
75
|
+
|
|
76
|
+
<Section label="model">
|
|
77
|
+
<Text style={styles.mono}>{a.model}</Text>
|
|
78
|
+
</Section>
|
|
79
|
+
|
|
80
|
+
<Section label="profile">
|
|
81
|
+
<Text style={styles.mono}>{a.agent.profileId}</Text>
|
|
82
|
+
</Section>
|
|
83
|
+
|
|
84
|
+
<Section label="reasoning">
|
|
85
|
+
<Text style={styles.mono}>{a.reasoningLevel}</Text>
|
|
86
|
+
</Section>
|
|
87
|
+
|
|
88
|
+
<Section label="group">
|
|
89
|
+
<Text style={styles.mono}>{a.group.name}</Text>
|
|
90
|
+
<Text style={styles.dim}>{a.group.path}</Text>
|
|
91
|
+
</Section>
|
|
92
|
+
|
|
93
|
+
<Section label={`skills (${a.skills.length})`}>
|
|
94
|
+
{a.skills.length === 0 ? (
|
|
95
|
+
<Text style={styles.dim}>(none attached)</Text>
|
|
96
|
+
) : (
|
|
97
|
+
a.skills.map((name) => (
|
|
98
|
+
<Text key={name} style={styles.mono}>
|
|
99
|
+
{name}
|
|
100
|
+
</Text>
|
|
101
|
+
))
|
|
102
|
+
)}
|
|
103
|
+
</Section>
|
|
104
|
+
|
|
105
|
+
<Pressable
|
|
106
|
+
style={({ pressed }) => [styles.primaryBtn, pressed && styles.primaryBtnPressed]}
|
|
107
|
+
onPress={() => router.push(`/agents/${a.agent.id}/chat`)}
|
|
108
|
+
>
|
|
109
|
+
<Text style={styles.primaryBtnText}>Open chat</Text>
|
|
110
|
+
</Pressable>
|
|
111
|
+
</ScrollView>
|
|
112
|
+
)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function Section({ label, children }: { label: string; children: React.ReactNode }) {
|
|
116
|
+
const colors = useColors()
|
|
117
|
+
const styles = useMemo(() => makeStyles(colors), [colors])
|
|
118
|
+
return (
|
|
119
|
+
<View style={styles.section}>
|
|
120
|
+
<Text style={styles.sectionLabel}>{label}</Text>
|
|
121
|
+
<View style={styles.sectionBody}>{children}</View>
|
|
122
|
+
</View>
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const makeStyles = (colors: Colors) =>
|
|
127
|
+
StyleSheet.create({
|
|
128
|
+
screen: { flex: 1, backgroundColor: colors.background },
|
|
129
|
+
content: { padding: 20, gap: 8 },
|
|
130
|
+
centered: { flex: 1, justifyContent: 'center', alignItems: 'center', padding: 24, gap: 12 },
|
|
131
|
+
name: { fontSize: 26, fontFamily: fonts.display, color: colors.foreground },
|
|
132
|
+
subtitle: { fontFamily: fonts.mono, fontSize: 12, color: colors.mochaLight, marginBottom: 8 },
|
|
133
|
+
section: { marginTop: 12 },
|
|
134
|
+
sectionLabel: {
|
|
135
|
+
fontSize: 11,
|
|
136
|
+
color: colors.mocha,
|
|
137
|
+
textTransform: 'uppercase',
|
|
138
|
+
letterSpacing: 0.5,
|
|
139
|
+
marginBottom: 4,
|
|
140
|
+
fontFamily: fonts.bodyMedium,
|
|
141
|
+
},
|
|
142
|
+
sectionBody: { gap: 2 },
|
|
143
|
+
mono: { fontFamily: fonts.mono, fontSize: 13, color: colors.charcoal },
|
|
144
|
+
dim: { color: colors.mochaLight, fontSize: 13, fontFamily: fonts.body },
|
|
145
|
+
placeholder: {
|
|
146
|
+
marginTop: 32,
|
|
147
|
+
padding: 16,
|
|
148
|
+
borderRadius: radii.md,
|
|
149
|
+
backgroundColor: colors.ivory,
|
|
150
|
+
gap: 6,
|
|
151
|
+
},
|
|
152
|
+
placeholderTitle: { fontSize: 14, fontFamily: fonts.bodyMedium, color: colors.mocha },
|
|
153
|
+
placeholderBody: { fontSize: 12, color: colors.mochaLight, lineHeight: 18, fontFamily: fonts.body },
|
|
154
|
+
errorTitle: { fontSize: 18, fontFamily: fonts.bodyBold, color: colors.foreground },
|
|
155
|
+
errorBody: { color: colors.destructive, textAlign: 'center', fontFamily: fonts.body },
|
|
156
|
+
primaryBtn: {
|
|
157
|
+
marginTop: 24,
|
|
158
|
+
paddingHorizontal: 20,
|
|
159
|
+
paddingVertical: 12,
|
|
160
|
+
borderRadius: radii.md,
|
|
161
|
+
backgroundColor: colors.sapphire,
|
|
162
|
+
alignItems: 'center',
|
|
163
|
+
},
|
|
164
|
+
primaryBtnPressed: { backgroundColor: colors.sapphireDeep },
|
|
165
|
+
primaryBtnText: { color: colors.primaryForeground, fontSize: 15, fontFamily: fonts.bodyMedium },
|
|
166
|
+
})
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import type { Agent } from '@bazilion/api-types'
|
|
2
|
+
import { ApiClientError } from '@bazilion/client'
|
|
3
|
+
import { router, useFocusEffect } from 'expo-router'
|
|
4
|
+
import { useCallback, useMemo, useRef, useState } from 'react'
|
|
5
|
+
import {
|
|
6
|
+
ActivityIndicator,
|
|
7
|
+
FlatList,
|
|
8
|
+
Pressable,
|
|
9
|
+
RefreshControl,
|
|
10
|
+
StyleSheet,
|
|
11
|
+
Text,
|
|
12
|
+
View,
|
|
13
|
+
} from 'react-native'
|
|
14
|
+
import {
|
|
15
|
+
clearCredentials,
|
|
16
|
+
clientFor,
|
|
17
|
+
type Credentials,
|
|
18
|
+
loadCredentials,
|
|
19
|
+
} from '@/src/auth'
|
|
20
|
+
import { useColors } from '@/src/theme-context'
|
|
21
|
+
import { type Colors, fonts, radii } from '@/src/theme'
|
|
22
|
+
|
|
23
|
+
type Load =
|
|
24
|
+
| { kind: 'loading' }
|
|
25
|
+
| { kind: 'ready'; agents: Agent[]; creds: Credentials }
|
|
26
|
+
| { kind: 'error'; message: string }
|
|
27
|
+
|
|
28
|
+
export default function AgentsList() {
|
|
29
|
+
const [load, setLoad] = useState<Load>({ kind: 'loading' })
|
|
30
|
+
const [refreshing, setRefreshing] = useState(false)
|
|
31
|
+
// Skip the loading-spinner on re-focus (navigating back from chat /
|
|
32
|
+
// settings) — show the stale list while the background refetch runs so
|
|
33
|
+
// the screen doesn't flash empty every time.
|
|
34
|
+
const hasLoaded = useRef(false)
|
|
35
|
+
const colors = useColors()
|
|
36
|
+
const styles = useMemo(() => makeStyles(colors), [colors])
|
|
37
|
+
|
|
38
|
+
const fetchAgents = useCallback(async (initial: boolean) => {
|
|
39
|
+
if (initial) setLoad({ kind: 'loading' })
|
|
40
|
+
const creds = await loadCredentials()
|
|
41
|
+
if (!creds) {
|
|
42
|
+
router.replace('/pair')
|
|
43
|
+
return
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
const agents = await clientFor(creds).get<Agent[]>('/api/agents')
|
|
47
|
+
setLoad({ kind: 'ready', agents, creds })
|
|
48
|
+
} catch (err) {
|
|
49
|
+
if (err instanceof ApiClientError && err.status === 401) {
|
|
50
|
+
// Token was revoked or the daemon's auth was rotated. Force re-pair.
|
|
51
|
+
await clearCredentials()
|
|
52
|
+
router.replace('/pair')
|
|
53
|
+
return
|
|
54
|
+
}
|
|
55
|
+
const message = err instanceof Error ? err.message : 'unknown error'
|
|
56
|
+
setLoad({ kind: 'error', message })
|
|
57
|
+
}
|
|
58
|
+
}, [])
|
|
59
|
+
|
|
60
|
+
useFocusEffect(
|
|
61
|
+
useCallback(() => {
|
|
62
|
+
fetchAgents(!hasLoaded.current)
|
|
63
|
+
hasLoaded.current = true
|
|
64
|
+
}, [fetchAgents]),
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
const onRefresh = useCallback(async () => {
|
|
68
|
+
setRefreshing(true)
|
|
69
|
+
try {
|
|
70
|
+
await fetchAgents(false)
|
|
71
|
+
} finally {
|
|
72
|
+
setRefreshing(false)
|
|
73
|
+
}
|
|
74
|
+
}, [fetchAgents])
|
|
75
|
+
|
|
76
|
+
const onUnpair = useCallback(async () => {
|
|
77
|
+
await clearCredentials()
|
|
78
|
+
router.replace('/pair')
|
|
79
|
+
}, [])
|
|
80
|
+
|
|
81
|
+
if (load.kind === 'loading') {
|
|
82
|
+
return (
|
|
83
|
+
<View style={styles.centered}>
|
|
84
|
+
<ActivityIndicator />
|
|
85
|
+
</View>
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (load.kind === 'error') {
|
|
90
|
+
return (
|
|
91
|
+
<View style={styles.centered}>
|
|
92
|
+
<Text style={styles.errorTitle}>Couldn't load agents</Text>
|
|
93
|
+
<Text style={styles.errorBody}>{load.message}</Text>
|
|
94
|
+
<Pressable style={styles.primaryBtn} onPress={() => fetchAgents(true)}>
|
|
95
|
+
<Text style={styles.primaryBtnText}>Retry</Text>
|
|
96
|
+
</Pressable>
|
|
97
|
+
<Pressable style={styles.linkBtn} onPress={onUnpair}>
|
|
98
|
+
<Text style={styles.linkBtnText}>Unpair</Text>
|
|
99
|
+
</Pressable>
|
|
100
|
+
</View>
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<View style={styles.screen}>
|
|
106
|
+
<View style={styles.header}>
|
|
107
|
+
<Text style={styles.headerServer}>{load.creds.server}</Text>
|
|
108
|
+
<Pressable onPress={() => router.push('/settings')} hitSlop={10}>
|
|
109
|
+
<Text style={styles.headerSettings}>Settings</Text>
|
|
110
|
+
</Pressable>
|
|
111
|
+
</View>
|
|
112
|
+
<FlatList
|
|
113
|
+
data={load.agents}
|
|
114
|
+
keyExtractor={(a) => a.id}
|
|
115
|
+
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />}
|
|
116
|
+
ListEmptyComponent={
|
|
117
|
+
<View style={styles.empty}>
|
|
118
|
+
<Text style={styles.emptyTitle}>No agents yet</Text>
|
|
119
|
+
<Text style={styles.emptyBody}>
|
|
120
|
+
Spawn one from the web UI or `bazilion agent spawn` on the server.
|
|
121
|
+
</Text>
|
|
122
|
+
</View>
|
|
123
|
+
}
|
|
124
|
+
renderItem={({ item }) => <AgentRow agent={item} />}
|
|
125
|
+
ItemSeparatorComponent={() => <View style={styles.separator} />}
|
|
126
|
+
/>
|
|
127
|
+
</View>
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function AgentRow({ agent }: { agent: Agent }) {
|
|
132
|
+
const colors = useColors()
|
|
133
|
+
const styles = useMemo(() => makeStyles(colors), [colors])
|
|
134
|
+
return (
|
|
135
|
+
<Pressable
|
|
136
|
+
style={({ pressed }) => [styles.row, pressed && styles.rowPressed]}
|
|
137
|
+
onPress={() => router.push(`/agents/${agent.id}/chat`)}
|
|
138
|
+
onLongPress={() => router.push(`/agents/${agent.id}`)}
|
|
139
|
+
>
|
|
140
|
+
<View style={styles.rowMain}>
|
|
141
|
+
<Text style={styles.rowName} numberOfLines={1}>
|
|
142
|
+
{agent.name}
|
|
143
|
+
</Text>
|
|
144
|
+
<Text style={styles.rowModel} numberOfLines={1}>
|
|
145
|
+
{agent.modelOverride ?? '(profile default)'}
|
|
146
|
+
</Text>
|
|
147
|
+
</View>
|
|
148
|
+
<View style={[styles.pill, agent.status === 'archived' ? styles.pillArchived : styles.pillActive]}>
|
|
149
|
+
<Text style={styles.pillText}>{agent.status}</Text>
|
|
150
|
+
</View>
|
|
151
|
+
</Pressable>
|
|
152
|
+
)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const makeStyles = (colors: Colors) =>
|
|
156
|
+
StyleSheet.create({
|
|
157
|
+
screen: { flex: 1, backgroundColor: colors.background },
|
|
158
|
+
header: {
|
|
159
|
+
flexDirection: 'row',
|
|
160
|
+
justifyContent: 'space-between',
|
|
161
|
+
alignItems: 'center',
|
|
162
|
+
paddingHorizontal: 16,
|
|
163
|
+
paddingVertical: 10,
|
|
164
|
+
borderBottomWidth: StyleSheet.hairlineWidth,
|
|
165
|
+
borderBottomColor: colors.border,
|
|
166
|
+
backgroundColor: colors.ivory,
|
|
167
|
+
},
|
|
168
|
+
headerServer: {
|
|
169
|
+
fontFamily: fonts.mono,
|
|
170
|
+
fontSize: 12,
|
|
171
|
+
color: colors.mocha,
|
|
172
|
+
flex: 1,
|
|
173
|
+
},
|
|
174
|
+
headerSettings: { color: colors.sapphireDeep, fontSize: 14, fontFamily: fonts.bodyMedium },
|
|
175
|
+
centered: { flex: 1, justifyContent: 'center', alignItems: 'center', padding: 24, gap: 12 },
|
|
176
|
+
errorTitle: { fontSize: 18, fontFamily: fonts.bodyBold, color: colors.foreground },
|
|
177
|
+
errorBody: { color: colors.destructive, textAlign: 'center', fontFamily: fonts.body },
|
|
178
|
+
primaryBtn: {
|
|
179
|
+
marginTop: 12,
|
|
180
|
+
paddingHorizontal: 20,
|
|
181
|
+
paddingVertical: 10,
|
|
182
|
+
borderRadius: radii.md,
|
|
183
|
+
backgroundColor: colors.sapphire,
|
|
184
|
+
},
|
|
185
|
+
primaryBtnText: { color: colors.primaryForeground, fontSize: 14, fontFamily: fonts.bodyMedium },
|
|
186
|
+
linkBtn: { marginTop: 4, padding: 8 },
|
|
187
|
+
linkBtnText: { color: colors.mocha, fontSize: 13, fontFamily: fonts.body },
|
|
188
|
+
row: {
|
|
189
|
+
flexDirection: 'row',
|
|
190
|
+
alignItems: 'center',
|
|
191
|
+
paddingHorizontal: 16,
|
|
192
|
+
paddingVertical: 14,
|
|
193
|
+
backgroundColor: colors.card,
|
|
194
|
+
},
|
|
195
|
+
rowPressed: { backgroundColor: colors.frost },
|
|
196
|
+
rowMain: { flex: 1, marginRight: 12 },
|
|
197
|
+
rowName: { fontSize: 16, fontFamily: fonts.bodyMedium, color: colors.foreground },
|
|
198
|
+
rowModel: { fontSize: 12, color: colors.mochaLight, marginTop: 2, fontFamily: fonts.mono },
|
|
199
|
+
separator: { height: StyleSheet.hairlineWidth, backgroundColor: colors.frost, marginLeft: 16 },
|
|
200
|
+
pill: { paddingHorizontal: 8, paddingVertical: 3, borderRadius: radii.md },
|
|
201
|
+
pillActive: { backgroundColor: colors.sapphireGlow },
|
|
202
|
+
pillArchived: { backgroundColor: colors.frost },
|
|
203
|
+
pillText: {
|
|
204
|
+
fontSize: 11,
|
|
205
|
+
fontFamily: fonts.bodyMedium,
|
|
206
|
+
textTransform: 'lowercase',
|
|
207
|
+
color: colors.mocha,
|
|
208
|
+
},
|
|
209
|
+
empty: { padding: 32, alignItems: 'center', gap: 8 },
|
|
210
|
+
emptyTitle: { fontSize: 16, fontFamily: fonts.bodyMedium, color: colors.foreground },
|
|
211
|
+
emptyBody: { color: colors.mocha, textAlign: 'center', fontSize: 13, fontFamily: fonts.body },
|
|
212
|
+
})
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Redirect } from 'expo-router'
|
|
2
|
+
import { useEffect, useState } from 'react'
|
|
3
|
+
import { loadCredentials } from '@/src/auth'
|
|
4
|
+
|
|
5
|
+
type Gate = { kind: 'loading' } | { kind: 'unpaired' } | { kind: 'paired' }
|
|
6
|
+
|
|
7
|
+
export default function Index() {
|
|
8
|
+
const [gate, setGate] = useState<Gate>({ kind: 'loading' })
|
|
9
|
+
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
loadCredentials()
|
|
12
|
+
.then((creds) => setGate({ kind: creds ? 'paired' : 'unpaired' }))
|
|
13
|
+
.catch(() => setGate({ kind: 'unpaired' }))
|
|
14
|
+
}, [])
|
|
15
|
+
|
|
16
|
+
// Render nothing while SecureStore resolves. The native splash is still up
|
|
17
|
+
// at this point on cold launch; rendering an ActivityIndicator briefly
|
|
18
|
+
// produced a visible flash between splash dismissal and the redirect.
|
|
19
|
+
if (gate.kind === 'loading') return null
|
|
20
|
+
return <Redirect href={gate.kind === 'paired' ? '/agents' : '/pair'} />
|
|
21
|
+
}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { CameraView, useCameraPermissions } from 'expo-camera'
|
|
2
|
+
import { router } from 'expo-router'
|
|
3
|
+
import { useCallback, useMemo, useRef, useState } from 'react'
|
|
4
|
+
import {
|
|
5
|
+
ActivityIndicator,
|
|
6
|
+
Alert,
|
|
7
|
+
Button,
|
|
8
|
+
Platform,
|
|
9
|
+
Pressable,
|
|
10
|
+
StyleSheet,
|
|
11
|
+
Text,
|
|
12
|
+
TextInput,
|
|
13
|
+
View,
|
|
14
|
+
} from 'react-native'
|
|
15
|
+
import { saveCredentials, verifyCredentials } from '@/src/auth'
|
|
16
|
+
import { PairUrlError, parsePairingUrl } from '@/src/pair-url'
|
|
17
|
+
import { useColors } from '@/src/theme-context'
|
|
18
|
+
import { type Colors, fonts, radii } from '@/src/theme'
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Browsers require a secure context (HTTPS or localhost) for
|
|
22
|
+
* `navigator.mediaDevices.getUserMedia`. Over LAN / Tailscale on http://,
|
|
23
|
+
* `requestPermission()` silently rejects without ever prompting. Detect
|
|
24
|
+
* that up front so the user doesn't tap a button that does nothing.
|
|
25
|
+
*/
|
|
26
|
+
function webCameraBlockedReason(): string | null {
|
|
27
|
+
if (Platform.OS !== 'web') return null
|
|
28
|
+
if (typeof window === 'undefined') return null
|
|
29
|
+
const { protocol, hostname } = window.location
|
|
30
|
+
if (protocol === 'https:') return null
|
|
31
|
+
if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '::1') return null
|
|
32
|
+
return `Camera access on the web requires HTTPS. You are on ${protocol}//${hostname} — use the manual-paste flow below, or open this page from a phone via Expo Go.`
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
type Mode = 'scan' | 'paste'
|
|
36
|
+
|
|
37
|
+
export default function PairScreen() {
|
|
38
|
+
const [mode, setMode] = useState<Mode>('scan')
|
|
39
|
+
const [permission, requestPermission] = useCameraPermissions()
|
|
40
|
+
const [busy, setBusy] = useState(false)
|
|
41
|
+
const [error, setError] = useState<string | null>(null)
|
|
42
|
+
const [pasteValue, setPasteValue] = useState('')
|
|
43
|
+
// Expo fires the barcode callback repeatedly on every frame — dedupe.
|
|
44
|
+
const handled = useRef(false)
|
|
45
|
+
const colors = useColors()
|
|
46
|
+
const styles = useMemo(() => makeStyles(colors), [colors])
|
|
47
|
+
|
|
48
|
+
const handlePairingUrl = useCallback(async (raw: string) => {
|
|
49
|
+
if (handled.current) return
|
|
50
|
+
handled.current = true
|
|
51
|
+
setBusy(true)
|
|
52
|
+
setError(null)
|
|
53
|
+
try {
|
|
54
|
+
const creds = parsePairingUrl(raw)
|
|
55
|
+
await verifyCredentials(creds)
|
|
56
|
+
await saveCredentials(creds)
|
|
57
|
+
router.replace('/agents')
|
|
58
|
+
} catch (err) {
|
|
59
|
+
handled.current = false
|
|
60
|
+
const msg =
|
|
61
|
+
err instanceof PairUrlError
|
|
62
|
+
? `QR content is not a pairing URL: ${err.message}`
|
|
63
|
+
: err instanceof Error
|
|
64
|
+
? err.message
|
|
65
|
+
: 'unknown error'
|
|
66
|
+
setError(msg)
|
|
67
|
+
Alert.alert('Pairing failed', msg)
|
|
68
|
+
} finally {
|
|
69
|
+
setBusy(false)
|
|
70
|
+
}
|
|
71
|
+
}, [])
|
|
72
|
+
|
|
73
|
+
if (mode === 'paste') {
|
|
74
|
+
return (
|
|
75
|
+
<View style={styles.container}>
|
|
76
|
+
<Text style={styles.title}>Paste pairing URL</Text>
|
|
77
|
+
<Text style={styles.hint}>
|
|
78
|
+
Run `bazilion token create <label> --qr` on your server to generate one.
|
|
79
|
+
</Text>
|
|
80
|
+
<TextInput
|
|
81
|
+
value={pasteValue}
|
|
82
|
+
onChangeText={setPasteValue}
|
|
83
|
+
placeholder="bazilion://pair?server=…&token=…"
|
|
84
|
+
autoCapitalize="none"
|
|
85
|
+
autoCorrect={false}
|
|
86
|
+
style={styles.input}
|
|
87
|
+
multiline
|
|
88
|
+
/>
|
|
89
|
+
{error ? <Text style={styles.error}>{error}</Text> : null}
|
|
90
|
+
<View style={styles.row}>
|
|
91
|
+
<Button
|
|
92
|
+
title={busy ? 'Verifying…' : 'Pair'}
|
|
93
|
+
onPress={() => handlePairingUrl(pasteValue.trim())}
|
|
94
|
+
disabled={busy || !pasteValue.trim()}
|
|
95
|
+
/>
|
|
96
|
+
<Button title="Scan instead" onPress={() => setMode('scan')} disabled={busy} />
|
|
97
|
+
</View>
|
|
98
|
+
</View>
|
|
99
|
+
)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (!permission) {
|
|
103
|
+
return (
|
|
104
|
+
<View style={[styles.container, styles.centered]}>
|
|
105
|
+
<ActivityIndicator />
|
|
106
|
+
</View>
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (!permission.granted) {
|
|
111
|
+
const webBlocked = webCameraBlockedReason()
|
|
112
|
+
return (
|
|
113
|
+
<View style={[styles.container, styles.centered]}>
|
|
114
|
+
<Text style={styles.title}>Camera access needed</Text>
|
|
115
|
+
<Text style={styles.hint}>
|
|
116
|
+
Bazilion uses the camera only to scan pairing QR codes. Nothing is recorded.
|
|
117
|
+
</Text>
|
|
118
|
+
<Text style={styles.diag}>
|
|
119
|
+
status: {permission.status} · canAskAgain: {String(permission.canAskAgain)}
|
|
120
|
+
</Text>
|
|
121
|
+
{webBlocked ? <Text style={styles.error}>{webBlocked}</Text> : null}
|
|
122
|
+
{!permission.canAskAgain && !webBlocked ? (
|
|
123
|
+
<Text style={styles.error}>
|
|
124
|
+
The OS won't prompt again — camera was denied earlier. Grant it in Settings → Expo Go
|
|
125
|
+
→ Camera, then restart Expo Go.
|
|
126
|
+
</Text>
|
|
127
|
+
) : null}
|
|
128
|
+
<Button
|
|
129
|
+
title="Grant camera access"
|
|
130
|
+
onPress={async () => {
|
|
131
|
+
try {
|
|
132
|
+
const r = await requestPermission()
|
|
133
|
+
if (!r.granted) {
|
|
134
|
+
Alert.alert(
|
|
135
|
+
'Camera not granted',
|
|
136
|
+
`status=${r.status} canAskAgain=${r.canAskAgain}`,
|
|
137
|
+
)
|
|
138
|
+
}
|
|
139
|
+
} catch (err) {
|
|
140
|
+
Alert.alert('requestPermission threw', String(err))
|
|
141
|
+
}
|
|
142
|
+
}}
|
|
143
|
+
disabled={!!webBlocked}
|
|
144
|
+
/>
|
|
145
|
+
<View style={{ height: 16 }} />
|
|
146
|
+
<Button title="Paste URL instead" onPress={() => setMode('paste')} />
|
|
147
|
+
</View>
|
|
148
|
+
)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return (
|
|
152
|
+
<View style={styles.container}>
|
|
153
|
+
<CameraView
|
|
154
|
+
style={styles.camera}
|
|
155
|
+
facing="back"
|
|
156
|
+
barcodeScannerSettings={{ barcodeTypes: ['qr'] }}
|
|
157
|
+
onBarcodeScanned={busy || handled.current ? undefined : ({ data }) => handlePairingUrl(data)}
|
|
158
|
+
>
|
|
159
|
+
<View style={styles.overlay}>
|
|
160
|
+
<Text style={styles.overlayTitle}>Scan pairing QR</Text>
|
|
161
|
+
<Text style={styles.overlayHint}>
|
|
162
|
+
Point the camera at the QR code emitted by `bazilion token create --qr`.
|
|
163
|
+
</Text>
|
|
164
|
+
{busy ? <ActivityIndicator style={{ marginTop: 12 }} /> : null}
|
|
165
|
+
{error ? <Text style={[styles.error, styles.onCamera]}>{error}</Text> : null}
|
|
166
|
+
</View>
|
|
167
|
+
</CameraView>
|
|
168
|
+
<Pressable style={styles.fallback} onPress={() => setMode('paste')}>
|
|
169
|
+
<Text style={styles.fallbackText}>Paste URL instead</Text>
|
|
170
|
+
</Pressable>
|
|
171
|
+
</View>
|
|
172
|
+
)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const makeStyles = (colors: Colors) =>
|
|
176
|
+
StyleSheet.create({
|
|
177
|
+
container: { flex: 1, backgroundColor: colors.background },
|
|
178
|
+
centered: { justifyContent: 'center', alignItems: 'center', padding: 24, gap: 12 },
|
|
179
|
+
title: {
|
|
180
|
+
fontSize: 22,
|
|
181
|
+
fontFamily: fonts.display,
|
|
182
|
+
color: colors.foreground,
|
|
183
|
+
marginBottom: 8,
|
|
184
|
+
},
|
|
185
|
+
hint: { color: colors.mocha, marginBottom: 16, textAlign: 'center', fontFamily: fonts.body },
|
|
186
|
+
input: {
|
|
187
|
+
margin: 16,
|
|
188
|
+
padding: 12,
|
|
189
|
+
borderWidth: 1,
|
|
190
|
+
borderColor: colors.border,
|
|
191
|
+
borderRadius: radii.md,
|
|
192
|
+
minHeight: 80,
|
|
193
|
+
fontFamily: fonts.mono,
|
|
194
|
+
color: colors.foreground,
|
|
195
|
+
backgroundColor: colors.ivory,
|
|
196
|
+
},
|
|
197
|
+
row: { flexDirection: 'row', gap: 12, justifyContent: 'center', marginTop: 8 },
|
|
198
|
+
error: { color: colors.destructive, margin: 16, textAlign: 'center', fontFamily: fonts.body },
|
|
199
|
+
diag: { color: colors.mochaLight, fontFamily: fonts.mono, fontSize: 12, marginBottom: 12 },
|
|
200
|
+
// Camera-overlay backdrop is intentionally theme-independent: sits on top
|
|
201
|
+
// of live camera video, needs to read as a translucent dark scrim regardless
|
|
202
|
+
// of light/dark theme.
|
|
203
|
+
onCamera: { backgroundColor: 'rgba(0,0,0,0.6)', padding: 8, borderRadius: 4 },
|
|
204
|
+
camera: { flex: 1 },
|
|
205
|
+
overlay: {
|
|
206
|
+
flex: 1,
|
|
207
|
+
justifyContent: 'flex-end',
|
|
208
|
+
padding: 24,
|
|
209
|
+
paddingBottom: 80,
|
|
210
|
+
},
|
|
211
|
+
overlayTitle: {
|
|
212
|
+
color: '#FFFFFF',
|
|
213
|
+
fontSize: 20,
|
|
214
|
+
fontFamily: fonts.display,
|
|
215
|
+
marginBottom: 4,
|
|
216
|
+
},
|
|
217
|
+
overlayHint: { color: '#ddd', fontFamily: fonts.body },
|
|
218
|
+
fallback: {
|
|
219
|
+
position: 'absolute',
|
|
220
|
+
bottom: 24,
|
|
221
|
+
left: 0,
|
|
222
|
+
right: 0,
|
|
223
|
+
alignItems: 'center',
|
|
224
|
+
},
|
|
225
|
+
fallbackText: { color: '#FFFFFF', textDecorationLine: 'underline', fontFamily: fonts.body },
|
|
226
|
+
})
|