-- ============================================================ -- AI Console & Hybrid AI Routing — Database Schema -- Sections A-D: AI Console, Sessions, Health Log, Settings -- ============================================================ -- ─── AI Console Settings ───────────────────────────────────── CREATE TABLE IF NOT EXISTS public.ai_console_settings ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), ollama_endpoint text NOT NULL DEFAULT '', ollama_model_default text NOT NULL DEFAULT '', ollama_model_translation text NOT NULL DEFAULT '', ollama_model_background text NOT NULL DEFAULT '', auto_failover boolean NOT NULL DEFAULT true, failover_timeout_seconds integer NOT NULL DEFAULT 8, default_site_id integer NOT NULL DEFAULT 1, default_pen_name_bb text NOT NULL DEFAULT 'Michael Strand', default_pen_name_av text NOT NULL DEFAULT 'Rex Chandler', save_session_history boolean NOT NULL DEFAULT true, history_retention_days integer NOT NULL DEFAULT 30, streaming_enabled boolean NOT NULL DEFAULT true, background_use_local boolean NOT NULL DEFAULT true, background_timeout_seconds integer NOT NULL DEFAULT 120, priority_queue_enabled boolean NOT NULL DEFAULT true, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now() ); -- ─── AI Console Sessions ───────────────────────────────────── CREATE TABLE IF NOT EXISTS public.ai_console_sessions ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), user_id uuid NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, site_id integer NOT NULL DEFAULT 1, pen_name text, first_message_preview text NOT NULL DEFAULT '', provider_used text NOT NULL DEFAULT 'anthropic', model_used text NOT NULL DEFAULT 'claude-sonnet-4-20250514', last_message_at timestamptz NOT NULL DEFAULT now(), created_at timestamptz NOT NULL DEFAULT now() ); CREATE INDEX IF NOT EXISTS idx_ai_console_sessions_user_id ON public.ai_console_sessions(user_id); CREATE INDEX IF NOT EXISTS idx_ai_console_sessions_last_message ON public.ai_console_sessions(last_message_at DESC); -- ─── AI Console Messages ───────────────────────────────────── CREATE TABLE IF NOT EXISTS public.ai_console_messages ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), session_id uuid NOT NULL REFERENCES public.ai_console_sessions(id) ON DELETE CASCADE, role text NOT NULL CHECK (role IN ('user', 'assistant', 'system')), content text NOT NULL DEFAULT '', provider_used text, created_at timestamptz NOT NULL DEFAULT now() ); CREATE INDEX IF NOT EXISTS idx_ai_console_messages_session_id ON public.ai_console_messages(session_id); -- ─── AI Health Log ──────────────────────────────────────────── CREATE TABLE IF NOT EXISTS public.ai_health_log ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), provider text NOT NULL CHECK (provider IN ('ollama', 'anthropic')), success boolean NOT NULL DEFAULT true, latency_ms integer NOT NULL DEFAULT 0, model_used text NOT NULL DEFAULT '', priority integer NOT NULL DEFAULT 4, error_message text, is_failover boolean NOT NULL DEFAULT false, logged_at timestamptz NOT NULL DEFAULT now() ); CREATE INDEX IF NOT EXISTS idx_ai_health_log_logged_at ON public.ai_health_log(logged_at DESC); CREATE INDEX IF NOT EXISTS idx_ai_health_log_provider ON public.ai_health_log(provider, logged_at DESC); -- Auto-purge logs older than 30 days (keep table lean) -- This is handled by a periodic cleanup; no trigger needed for now. -- ─── RLS Policies ──────────────────────────────────────────── -- ai_console_settings: admin read/write only ALTER TABLE public.ai_console_settings ENABLE ROW LEVEL SECURITY; DO $$ BEGIN IF NOT EXISTS ( SELECT 1 FROM pg_policies WHERE tablename = 'ai_console_settings' AND policyname = 'admin_all' ) THEN CREATE POLICY admin_all ON public.ai_console_settings FOR ALL USING ( EXISTS ( SELECT 1 FROM public.user_profiles WHERE id = auth.uid() AND role IN ('administrator', 'admin') ) ); END IF; END $$; -- ai_console_sessions: users own their sessions ALTER TABLE public.ai_console_sessions ENABLE ROW LEVEL SECURITY; DO $$ BEGIN IF NOT EXISTS ( SELECT 1 FROM pg_policies WHERE tablename = 'ai_console_sessions' AND policyname = 'owner_all' ) THEN CREATE POLICY owner_all ON public.ai_console_sessions FOR ALL USING (user_id = auth.uid()); END IF; END $$; -- ai_console_messages: users own messages via session ownership ALTER TABLE public.ai_console_messages ENABLE ROW LEVEL SECURITY; DO $$ BEGIN IF NOT EXISTS ( SELECT 1 FROM pg_policies WHERE tablename = 'ai_console_messages' AND policyname = 'owner_via_session' ) THEN CREATE POLICY owner_via_session ON public.ai_console_messages FOR ALL USING ( EXISTS ( SELECT 1 FROM public.ai_console_sessions WHERE id = ai_console_messages.session_id AND user_id = auth.uid() ) ); END IF; END $$; -- ai_health_log: admin read, service role write (health-log route uses service role implicitly) ALTER TABLE public.ai_health_log ENABLE ROW LEVEL SECURITY; DO $$ BEGIN IF NOT EXISTS ( SELECT 1 FROM pg_policies WHERE tablename = 'ai_health_log' AND policyname = 'admin_read' ) THEN CREATE POLICY admin_read ON public.ai_health_log FOR SELECT USING ( EXISTS ( SELECT 1 FROM public.user_profiles WHERE id = auth.uid() AND role IN ('administrator', 'admin') ) ); END IF; END $$; DO $$ BEGIN IF NOT EXISTS ( SELECT 1 FROM pg_policies WHERE tablename = 'ai_health_log' AND policyname = 'service_insert' ) THEN CREATE POLICY service_insert ON public.ai_health_log FOR INSERT WITH CHECK (true); END IF; END $$;