mirror of
https://github.com/Luchexx/wacrm.git
synced 2026-11-02 17:29:45 +00:00
Makes the AI auto-reply bot visible and controllable from the inbox, completes the handoff, and records token spend. Builds on the existing lib/ai engine rather than replacing it. Track A — user-facing - messages.ai_generated flag → small "AI" badge on bot-generated bubbles - "Take over" / "Resume AI" banner per conversation (POST /api/ai/autoreply/[id]) — take-over pauses + assigns to the agent - handoff now assigns to a configurable target (agent or unassigned queue) and writes an internal note summarizing the exchange; the existing on_conversation_assigned trigger notifies the assignee - Settings → AI: "Hand off to" target picker Track B — observability + safety - providers return normalized token usage; logged per run to ai_usage_log (admin-readable) from both auto-reply and draft - account-wide auto-reply rate limit (30/min) on top of the per-conversation cap Migration: 033_ai_reply_polish.sql (messages.ai_generated, ai_configs.handoff_agent_id, conversations.ai_handoff_summary, ai_usage_log). Tests + typecheck + lint pass (607 tests). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
87 lines
4.2 KiB
SQL
87 lines
4.2 KiB
SQL
-- ============================================================
|
|
-- 033_ai_reply_polish.sql — AI reply assistant polish
|
|
--
|
|
-- Follow-ups to 029_ai_reply / 030_ai_knowledge that make the
|
|
-- auto-reply bot visible and controllable from the inbox, complete the
|
|
-- handoff, and record token spend:
|
|
--
|
|
-- 1. messages.ai_generated — marks a reply the bot sent (vs a
|
|
-- deterministic Flow/bot send), so
|
|
-- the inbox can badge it "AI".
|
|
-- 2. ai_configs.handoff_agent_id — where a handed-off conversation is
|
|
-- routed. NULL = leave unassigned
|
|
-- (drop into the shared queue).
|
|
-- 3. conversations.ai_handoff_summary
|
|
-- — a short internal note the bot writes
|
|
-- when it hands off, surfaced to the
|
|
-- agent who takes over.
|
|
-- 4. ai_usage_log — per-run provider token usage, for
|
|
-- cost visibility on the account's BYO
|
|
-- key. Written by the service role from
|
|
-- the draft route + auto-reply bot.
|
|
--
|
|
-- Idempotent — safe to run multiple times.
|
|
-- ============================================================
|
|
|
|
-- ============================================================
|
|
-- 1. Mark AI-generated messages.
|
|
--
|
|
-- Auto-replies are inserted as sender_type='bot' (same as Flow sends);
|
|
-- this column is the only thing that distinguishes an LLM reply from a
|
|
-- deterministic one, so the inbox can show the "AI" badge on the right
|
|
-- bubbles only.
|
|
-- ============================================================
|
|
ALTER TABLE messages
|
|
ADD COLUMN IF NOT EXISTS ai_generated boolean NOT NULL DEFAULT false;
|
|
|
|
-- ============================================================
|
|
-- 2. Handoff routing target + 3. handoff summary.
|
|
-- ============================================================
|
|
ALTER TABLE ai_configs
|
|
ADD COLUMN IF NOT EXISTS handoff_agent_id uuid REFERENCES auth.users(id) ON DELETE SET NULL;
|
|
|
|
ALTER TABLE conversations
|
|
ADD COLUMN IF NOT EXISTS ai_handoff_summary text;
|
|
|
|
-- ============================================================
|
|
-- 4. Per-run token-usage log.
|
|
--
|
|
-- One row per LLM call (draft or auto-reply). Best-effort: the writer
|
|
-- never blocks a reply on a failed insert. Kept append-only; prune with
|
|
-- a scheduled job if it grows (an active account writes a handful of
|
|
-- rows per conversation).
|
|
--
|
|
-- RLS: admin+ read (spend is billing-class, not something a viewer/agent
|
|
-- needs). Writes come from the service-role client (webhook + route),
|
|
-- which bypasses RLS, so there is no INSERT policy for `authenticated`.
|
|
-- ============================================================
|
|
CREATE TABLE IF NOT EXISTS ai_usage_log (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
account_id uuid NOT NULL REFERENCES accounts(id) ON DELETE CASCADE,
|
|
conversation_id uuid REFERENCES conversations(id) ON DELETE SET NULL,
|
|
-- 'auto_reply' | 'draft' — which surface spent the tokens.
|
|
mode text NOT NULL CHECK (mode IN ('auto_reply', 'draft')),
|
|
provider text NOT NULL CHECK (provider IN ('openai', 'anthropic')),
|
|
model text NOT NULL,
|
|
prompt_tokens integer NOT NULL DEFAULT 0,
|
|
completion_tokens integer NOT NULL DEFAULT 0,
|
|
total_tokens integer NOT NULL DEFAULT 0,
|
|
created_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
|
|
-- Account-scoped, newest-first reads (usage dashboards, "spend this
|
|
-- month") — the only access pattern.
|
|
CREATE INDEX IF NOT EXISTS idx_ai_usage_log_account_created
|
|
ON ai_usage_log(account_id, created_at DESC);
|
|
|
|
ALTER TABLE ai_usage_log ENABLE ROW LEVEL SECURITY;
|
|
|
|
-- SELECT: admin+ only (spend visibility is settings/billing-class).
|
|
DROP POLICY IF EXISTS ai_usage_log_select ON ai_usage_log;
|
|
CREATE POLICY ai_usage_log_select ON ai_usage_log FOR SELECT
|
|
USING (is_account_member(account_id, 'admin'));
|
|
|
|
-- No INSERT/UPDATE/DELETE policies for `authenticated`: the log is
|
|
-- written exclusively by the service role (webhook + draft route) and
|
|
-- is never mutated from the client.
|