| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Name | Name | Last commit date | ||
|---|---|---|---|---|
PowerSync AI Hackathon 2026 · Local-First & Offline-Capable AI
An on-device AI assistant for wildlife park rangers. All AI inference runs locally using Apple Foundation Models and SpeechAnalyzer — no connectivity required for intelligence features. Data syncs to Supabase via PowerSync when back in range.
https://github.com/Clemo97/askari-ai/raw/main/Videos/RPReplay_Final1776686584.MP4
Patrol map — live incident pins with colour-coded severity, park boundary overlay, and tap-to-view incident detail
Incident report — full detail view with category, GPS coordinates, field notes, and photo evidence
AI Intelligence dashboard — natural-language query powered by Apple Foundation Models with tool-called results from the local PowerSync SQLite DB
| Feature | Stack |
|---|---|
| Natural language patrol queries | Apple Foundation Models + tool calling (local SQLite) |
| Voice incident note dictation (STT) | AVAudioRecorder + SpeechAnalyzer / SpeechTranscriber |
| Pre-patrol AI briefing | Apple Foundation Models + ephemeral session |
| Offline-first sync | PowerSync Sync Streams → Supabase |
| Reactive UI | SwiftUI + The Composable Architecture |
AskariAI/
├── App/ # App entry point, root navigation
├── Managers/ # SystemManager, SupabaseConnector, Schema
│ ├── Schema.swift # PowerSync local SQLite schema
│ ├── SystemManager.swift # PowerSync database + sync stream subscriptions + TCA dependency
│ ├── SupabaseConnector.swift
│ └── _Secrets.swift # ← gitignored, copy from template
├── Features/ # TCA Reducers + Views (co-located)
│ ├── App/ # AppFeature, MainFeature
│ ├── Auth/ # AuthFeature
│ ├── Missions/ # MissionsFeature
│ ├── ActiveMission/ # ActiveMissionFeature (GPS tracking)
│ ├── Dashboard/ # DashboardFeature (AI query + intel overview)
│ └── Incidents/ # LogIncidentFeature (voice dictation + media)
├── Models/ # Codable data models
├── Views/ # SwiftUI views
│ └── Copilot/ # CopilotChatView, AIBriefingView
└── AI/
├── AIManager.swift # Foundation Models sessions + SpeechAnalyzer lifecycle
└── Tools/
└── CactusTools.swift # Tool protocol implementations (DB query + incident logging)
supabase/
└── migrations/
├── 001_initial_schema.sql # Applied ✓
└── 002_rls_policies.sql # RLS policies
powersync/
└── sync-config.yaml # Sync Streams config (edition 3)
git clone https://github.com/Clemo97/askari-ai.git
cd askari-ai
open AskariAI.xcodeproj # or .xcworkspace if presentSwift Package dependencies resolve automatically on first open. If they don't, go to File → Packages → Resolve Package Versions.
The app reads credentials from AskariAI/Managers/_Secrets.swift, which is gitignored. A template is provided at AskariAI/Managers/_Secrets.template.swift.
Copy the template:
cp AskariAI/Managers/_Secrets.template.swift AskariAI/Managers/_Secrets.swiftThen fill in _Secrets.swift:
enum Secrets {
static let supabaseURL = URL(string: "https://<your-project-ref>.supabase.co")!
static let supabaseAnonKey = "<your-supabase-anon-key>"
static let powerSyncEndpoint = "https://<your-instance-id>.powersync.journeyapps.com"
// Optional — set to your Supabase Storage bucket name to enable media attachments
static let supabaseStorageBucket: String? = "incident-media"
}You can find these values at:
The full database definition is split into focused files under supabase/:
| File | Purpose |
|---|---|
| supabase/schema.dbml | Schema documentation — tables, PKs, FKs, checks (DBML format) |
| supabase/schema.sql | CREATE TABLE statements + spot_types seed data |
| supabase/indexes.sql | Performance indexes |
| supabase/functions.sql | Helper functions (auth_staff_id, auth_staff_park_id, auth_staff_rank, trigger_set_updated_at, handle_new_user) |
| supabase/triggers.sql | updated_at triggers + commented on_auth_user_created auth trigger |
| supabase/rls.sql | RLS enables + all policies |
| supabase/powersync.sql | PowerSync scoping notes (not executed in DB) |
Open the Supabase SQL Editor (Dashboard → SQL Editor → New query) and run each file in the order listed. Each query window is a fresh paste — run them one at a time.
| Step | File | Action |
|---|---|---|
| 1 | supabase/schema.sql | Creates all tables; seeds 14 spot_types rows |
| 2 | supabase/indexes.sql | Adds performance indexes |
| 3 | supabase/functions.sql | Creates helper functions + handle_new_user |
| 4 | supabase/triggers.sql | Attaches updated_at triggers; see note for auth trigger |
| 5 | supabase/rls.sql | Enables RLS and applies all policies |
Auth trigger (step 4): triggers.sql includes a commented-out CREATE TRIGGER on_auth_user_created on the auth.users table. Uncomment and run it in the Supabase SQL Editor (which executes as superuser). This enables automatic staff row creation on signup.
Sign up via the app — handle_new_user automatically creates a staff row linked to the first park in the database. To promote an account to admin:
UPDATE public.staff SET rank = 'admin' WHERE email = 'you@example.com';To assign a ranger to a specific park (if multiple parks exist):
UPDATE public.staff
SET park_id = (SELECT id FROM public.parks WHERE name = 'Nairobi National Park')
WHERE email = 'ranger@example.com';Follow the PowerSync Supabase integration guide to connect your PowerSync instance to your Supabase database.
npm install -g @powersync/cli
powersync login
powersync link cloud --project-id=<your-project-id>
powersync deploy sync-configThe config is at powersync/sync-config.yaml.
| Package | URL | Version |
|---|---|---|
| PowerSync Swift SDK | https://github.com/powersync-ja/powersync-swift | 1.13.0 |
| Supabase Swift | https://github.com/supabase/supabase-swift | >= 2.41.1 |
| TCA | https://github.com/pointfreeco/swift-composable-architecture | >= 1.25.1 |
All packages are declared in Package.swift and resolve automatically in Xcode.
Note: FoundationModels and Speech (SpeechAnalyzer) are Apple system frameworks — no SPM dependency required. They are available on iOS 26+.
Migrated from sync rules (bucket_definitions) to a single consolidated sync stream (migrated_to_streams). CTE-style with: parameters resolve per-user via auth.user_id() — ranger and admin data are filtered server-side, no client-side parameters required.
config:
edition: 3
streams:
migrated_to_streams:
auto_subscribe: true
with:
ranger_own_features_param: >-
SELECT staff.id AS staff_id FROM staff
WHERE staff.user_id = auth.user_id()
AND staff.rank = 'ranger' AND staff.is_active = TRUE
ranger_park_data_param: >-
SELECT staff.park_id AS park_id FROM staff
WHERE staff.user_id = auth.user_id()
AND staff.rank = 'ranger' AND staff.is_active = TRUE
admin_map_features_param: >-
SELECT staff.park_id AS park_id FROM staff
WHERE staff.user_id = auth.user_id()
AND staff.rank = 'admin' AND staff.is_active = TRUE
queries:
# Ranger: own incidents
- "SELECT map_features.* FROM map_features, ranger_own_features_param AS bucket WHERE map_features.created_by = bucket.staff_id"
# Ranger: park context
- "SELECT parks.* FROM parks, ranger_park_data_param AS bucket WHERE parks.id = bucket.park_id"
- "SELECT park_boundaries.* FROM park_boundaries, ranger_park_data_param AS bucket WHERE park_boundaries.park_id = bucket.park_id"
- "SELECT staff.* FROM staff, ranger_park_data_param AS bucket WHERE staff.park_id = bucket.park_id AND staff.is_active = TRUE"
# Admin: all park map features + context
- "SELECT map_features.* FROM map_features, admin_map_features_param AS bucket WHERE map_features.park_id = bucket.park_id"
- "SELECT parks.* FROM parks, admin_map_features_param AS bucket WHERE parks.id = bucket.park_id"
- "SELECT park_boundaries.* FROM park_boundaries, admin_map_features_param AS bucket WHERE park_boundaries.park_id = bucket.park_id"
- "SELECT staff.* FROM staff, admin_map_features_param AS bucket WHERE staff.park_id = bucket.park_id AND staff.is_active = TRUE"
# Global reference (all users)
- SELECT * FROM spot_types WHERE spot_types.is_active = TRUE
- SELECT * FROM mission_role_typesOn the client, SystemManager.connect() subscribes to the single stream and awaits its first sync:
let sub = try await db.syncStream(name: "migrated_to_streams", params: nil).subscribe()
try await sub.waitForFirstSync()auto_subscribe: true means the server begins syncing immediately on connection — the explicit subscribe call is used only to obtain a handle for waitForFirstSync(), which blocks until the local SQLite database has received its initial data.
All AI features use Apple system frameworks — no third-party model hosting, no network calls for inference.
| Feature | Session type | Notes |
|---|---|---|
| Ranger Copilot chat | LanguageModelSession (persistent) | Multi-turn; retains transcript |
| Pre-patrol briefing | LanguageModelSession (ephemeral) | Fresh session per briefing |
| Dashboard queries | LanguageModelSession (ephemeral) | Fresh session per query |
Voice note dictation uses SpeechAnalyzer + SpeechTranscriber (iOS 26 Speech framework), following the WWDC25 session 277 reference architecture.
| Component | Role |
|---|---|
| AVAudioRecorder | Captures 16 kHz mono PCM WAV to a temp file |
| SpeechAnalyzer | Orchestrates transcription pipeline |
| SpeechTranscriber | On-device locale model; options-based init with .volatileResults |
| AssetInventory | Downloads locale model assets on first use (~50–100 MB); no-op if already current |
| STTBufferConverter | Resamples AVAudioPCMBuffer for live streaming; primeMethod = .none avoids timestamp drift |
Device requirement: Apple Foundation Models and SpeechAnalyzer require a physical device running iOS 26 with Apple Intelligence enabled. Neither works in Simulator.
| Back | FazBrowse Home | New Git URL |