Feedback Service

Collecting, storing, and reviewing user feedback on AI responses. Full conversation context with admin panel.

1 Overview

The feedback service allows users to rate AI responses with like or dislike directly in the chat. Each feedback is saved with full context: the user's message, AI response, parsed command, execution result, and the entire conversation history leading up to the feedback moment.

An admin panel is available on the web to review, filter, and manage feedback items. Admins can mark items as resolved or ignored, and leave notes for tracking.

👍👎

Collect

Users rate AI messages with like/dislike in iOS & Web chat

📦

Store

Full context saved: message, response, command, params, chat history

🔍

Review

Admin panel with filters, stats, and status management

2 Data Flow

1. User rates message iOS / Web Client thumbs up or down on any AI message 2. Client sends context POST /api/chat/messages/:messageId/feedback type, userMessage, aiResponse, commandType, commandParams, executionResult, chatHistory 3. Server stores feedback chat_feedback table (Supabase) upsert: update if same user+message, insert otherwise 4. Admin reviews Web Admin Panel (monaime.app/feedback) filter by status/type, view full context, resolve/ignore

3 Database Schema

chat_feedback

ColumnTypeNotes
idUUID PKgen_random_uuid()
user_idUUID NOT NULLFK → users (CASCADE)
message_idVARCHAR(50)nanoid from client (no FK — messages are ephemeral)
typeVARCHAR(10) NOT NULLCHECK: like | dislike
user_messageTEXTWhat the user asked
ai_responseTEXTWhat AI responded (cleaned of internal markers)
command_typeVARCHAR(50)Parsed command (e.g. add_transaction)
command_paramsJSONBParsed command parameters
execution_resultJSONBResult of command execution
chat_historyJSONBArray of {role, content} — conversation before feedback
statusVARCHAR(20)DEFAULT pending. CHECK: pending | resolved | ignored
admin_notesTEXTAdmin review notes
resolved_atTIMESTAMPTZSet when status changes to resolved/ignored
created_atTIMESTAMPTZDEFAULT NOW()
📋
Indexes idx_chat_feedback_user (user_id), idx_chat_feedback_type (type), idx_chat_feedback_status (status), idx_chat_feedback_created (created_at DESC)
⚠️
message_id is not a foreign key Chat messages are ephemeral (can be deleted by retention policy). The message_id is a nanoid from the client, stored as a plain string for deduplication (upsert on user_id + message_id).

4 Status Workflow

pending

New feedback, not yet reviewed by admin

resolved

Admin reviewed and addressed the issue

ignored

Admin reviewed and deemed not actionable

pending —[Resolve]→ resolved (sets resolved_at = NOW()) pending —[Ignore]→ ignored (sets resolved_at = NOW()) resolved —[Reopen]→ pending (clears resolved_at = null) ignored —[Reopen]→ pending (clears resolved_at = null)

5 API Endpoints

POST /api/chat/messages/:messageId/feedback AUTH

Submit or update feedback for a message. Uses upsert logic: if feedback from the same user for the same message exists, it updates the record; otherwise creates a new one.

Request body:

{ "type": "like" | "dislike", // required "userMessage": "Add 500 for food", // what user asked "aiResponse": "Added expense 500...", // AI response (cleaned) "commandType": "add_transaction", // parsed command name "commandParams": { // parsed parameters "amount": 500, "category": "food" }, "executionResult": { ... }, // action result "chatHistory": [ // conversation context { "role": "user", "content": "..." }, { "role": "assistant", "content": "..." } ] }

Response: { "success": true }

GET /api/admin/feedback PUBLIC

List feedback with optional filters and pagination. Returns items sorted by created_at DESC. Includes user email via join.

Query parameters:

status=pending|resolved|ignored // optional filter type=like|dislike // optional filter page=1 // default: 1 limit=20 // default: 20, max: 100

Response:

{ "success": true, "data": [ { "id": "uuid", "user_id": "uuid", "message_id": "nanoid", "type": "dislike", "user_message": "...", "ai_response": "...", "command_type": "add_transaction", "command_params": { ... }, "execution_result": { ... }, "chat_history": [ ... ], "status": "pending", "admin_notes": null, "resolved_at": null, "created_at": "2026-01-29T12:00:00Z", "users": { "email": "user@example.com" } } ], "pagination": { "page": 1, "limit": 20, "total": 42, "hasMore": true } }
GET /api/admin/feedback/stats PUBLIC

Quick summary statistics for the feedback dashboard.

Response:

{ "success": true, "data": { "total": 42, // all feedback items "pending": 15, // not yet reviewed "dislikes": 28 // total dislike count } }
PATCH /api/admin/feedback/:id PUBLIC

Update the status and/or admin notes for a specific feedback item. When status changes to resolved or ignored, resolved_at is set automatically. When changed back to pending, resolved_at is cleared.

Request body:

{ "status": "resolved", // optional "adminNotes": "Fixed AI parsing" // optional }

Response: { "success": true, "data": { ... updated item ... } }

6 Admin Panel (Web)

The admin panel is a React page at /feedback on the web app. It provides a full management interface for feedback items.

🌐
Access Open https://monaime.app/feedback in the browser. The admin routes are currently public (no auth required).

Features:

7 Stored Context per Feedback

Each feedback item stores enough context to fully understand and reproduce the situation:

// Example stored feedback item { "type": "dislike", "user_message": "Add 500 for taxi from cash", "ai_response": "Added expense: 500 RUB for Taxi from Cash wallet", "command_type": "add_transaction", "command_params": { "amount": 500, "type": "expense", "category": "Transport", "wallet": "Cash", "description": "Taxi" }, "execution_result": { "success": true, "transaction_id": "abc-123" }, "chat_history": [ { "role": "user", "content": "What's my balance?" }, { "role": "assistant", "content": "Cash: 10,000 RUB, Card: 25,000 RUB" }, { "role": "user", "content": "Add 500 for taxi from cash" }, { "role": "assistant", "content": "Added expense: 500 RUB for Taxi from Cash wallet" } ] }
🧹
AI response cleaning Before storing, internal markers like <!-- SUGGEST:command --> are stripped from the AI response. Only the visible text is saved.

8 Source Files

DB Server/sql/029_extend_chat_feedback.sql Table schema with full context & admin fields
DB Server/sql/030_feedback_chat_history.sql Added chat_history JSONB column
SERVER Server/src/routes/chat.routes.ts POST /api/chat/messages/:messageId/feedback
SERVER Server/src/routes/feedback-admin.routes.ts GET/PATCH /api/admin/feedback (list, stats, update)
SERVER Server/tests/feedback-admin.test.ts Tests for admin API logic
WEB Web/src/api/feedback-admin.ts API client with TypeScript types
WEB Web/src/hooks/useFeedbackAdmin.ts TanStack Query hook (infinite queries, mutations)
WEB Web/src/pages/FeedbackAdminPage.tsx Admin panel UI (React)
iOS iOS/MoneyChat/Views/Chat/MessageRow.swift Feedback buttons (thumbs up/down) on messages