Skip to content

Option A — Phase 8: AI Chats / Logs Screen

This document is the plan for Phase 8 of AppiFire AI Chat (Option A): an admin screen that lists all chat sessions and, when a session is expanded, shows each user question, the AI reply, its cost, and date/time.

Prerequisites: Phases 1–5 (RAG, chat API, settings, billing) so that ChatSession and ChatMessage data exists.


  • Screen: “AI Chats” or “Chat logs” in the app nav.
  • List: All sessions for the current shop, one row per session.
  • Expand on click: Clicking a session row expands it to show the conversation: each user question, the AI reply, cost (charged and/or OpenRouter), and date/time for each message.

  • ChatSession: id, shopId, visitorId, startedAt, endedAt. Relation: messages (ChatMessage[]).
  • ChatMessage: id, sessionId, role (user | assistant | system), messageText, createdAt; for role = "assistant" only: modelUsed, openrouterCost, chargedCost, markupFactor.

Sessions are scoped by shopId; messages are ordered by createdAt within a session.


File: app/routes/app.chat-logs.jsx (or app.logs.jsx), URL /app/chat-logs (or /app/logs).

Loader:

  1. Authenticate with authenticate.admin(request) and get current session.shop.
  2. Resolve the shop: db.shop.findFirst({ where: { shopDomain: session.shop }, select: { id: true } }).
  3. Load sessions for that shop:
    • db.chatSession.findMany({ where: { shopId: shop.id }, orderBy: { startedAt: "desc" }, take: 100, include: { messages: { orderBy: { createdAt: "asc" } } } }).
    • Or: load sessions without messages first (lighter), and load messages when a session is expanded (e.g. via a resource route or fetcher). For a simple “expand inline” UX, including messages in the initial load is acceptable up to a limit (e.g. last 100 sessions, each with up to N messages).
  4. Return { sessions } (and optionally shopId). Each session object includes id, visitorId, startedAt, endedAt, and messages (array of { id, role, messageText, createdAt, modelUsed, openrouterCost, chargedCost } for assistant messages).

Pagination (optional): If you expect many sessions, use cursor-based or offset pagination (e.g. take: 50, skip or cursor from query params) and load more on “Load more” or infinite scroll.


Layout:

  • Page title: e.g. “AI Chats” or “Chat logs”.
  • Table or list: One row per session. Columns (or list item summary) can include:
    • Session (e.g. truncated id or “Session from <startedAt date/time>”).
    • Visitor (visitorId or “Anonymous” if null).
    • Started (startedAt formatted).
    • Messages (count: messages.length or count of user + assistant pairs).
    • Action: Click row (or a “Expand” control) to expand.

Expanded row (per session):

  • When a session row is expanded, show the conversation thread below it (or in a drawer/panel):
    • For each user message: show “User” (or “Customer”), the question text (messageText), and date/time (createdAt).
    • For each assistant message: show “AI”, the reply text (messageText), cost (e.g. “Charged: $X.XX” from chargedCost, optionally “OpenRouter: $X.XX” from openrouterCost), and date/time (createdAt).
  • Order by createdAt ascending so the thread reads top to bottom.
  • Optionally show modelUsed for assistant messages.

Implementation approach:

  • Use Polaris (or your app’s design system) components consistent with the rest of the app (e.g. s-page, s-section, s-box, tables or s-data-table if available).
  • Expand/collapse: Either:
    • A) All session data (including messages) is in loader data; clicking a row toggles local state to show/hide that session’s message list (no extra request).
    • B) Loader returns only session list; on expand, call a resource route or fetcher with sessionId to load that session’s messages, then show them below the row.
  • Prefer A if the number of sessions and messages per session is bounded (e.g. last 50–100 sessions, each with tens of messages); use B if you need to support very long histories.

File: app/routes/app.jsx

  • Add a link in the app nav, e.g. <s-link href="/app/chat-logs">AI Chats</s-link> (or “Chat logs”), so merchants can open the screen from the sidebar.

  • No sessions: Show an empty state (e.g. “No chat sessions yet”).
  • No messages in a session: Expanded row can show “No messages” or hide the session from the list if you only list sessions that have at least one message.
  • Cost null: If chargedCost or openrouterCost is null for an old assistant message, show “—” or “N/A”.
  • Long message text: Truncate in the list (e.g. first 100–200 chars) with “Show more” or a tooltip; full text in expanded view is fine.

  • Route app.chat-logs.jsx (or equivalent) created; path /app/chat-logs.
  • Loader: authenticate, resolve shop, load sessions for shop (with messages or message count), ordered by startedAt desc, with a sensible limit (e.g. 100).
  • UI: one row per session (session identifier, visitor, started at, message count); click row to expand.
  • Expanded content: for each message in the session, show role (user/assistant), text, date/time; for assistant messages show charged cost (and optionally OpenRouter cost).
  • “AI Chats” (or “Chat logs”) link added to app nav in app.jsx.
  • Empty state when there are no sessions.
  • Optional: pagination or “Load more” if session list is large.

ItemFile / location
Route + loader + pageapp/routes/app.chat-logs.jsx
Nav linkapp/routes/app.jsx
DataExisting ChatSession, ChatMessage (Prisma); no schema change required.

Next: after Phase 8, you can add filters (e.g. by date range, visitor), export, or search in a later phase if needed.