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.
Objective
Section titled “Objective”- 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.
Data model (existing)
Section titled “Data model (existing)”- ChatSession:
id,shopId,visitorId,startedAt,endedAt. Relation:messages(ChatMessage[]). - ChatMessage:
id,sessionId,role(user | assistant | system),messageText,createdAt; forrole = "assistant"only:modelUsed,openrouterCost,chargedCost,markupFactor.
Sessions are scoped by shopId; messages are ordered by createdAt within a session.
1. Route and loader
Section titled “1. Route and loader”File: app/routes/app.chat-logs.jsx (or app.logs.jsx), URL /app/chat-logs (or /app/logs).
Loader:
- Authenticate with
authenticate.admin(request)and get currentsession.shop. - Resolve the shop:
db.shop.findFirst({ where: { shopDomain: session.shop }, select: { id: true } }). - 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
messagesin the initial load is acceptable up to a limit (e.g. last 100 sessions, each with up to N messages).
- Return
{ sessions }(and optionallyshopId). Each session object includesid,visitorId,startedAt,endedAt, andmessages(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.
2. UI: session list and expandable rows
Section titled “2. UI: session list and expandable rows”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
idor “Session from <startedAt date/time>”). - Visitor (
visitorIdor “Anonymous” if null). - Started (
startedAtformatted). - Messages (count:
messages.lengthor count of user + assistant pairs). - Action: Click row (or a “Expand” control) to expand.
- Session (e.g. truncated
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” fromchargedCost, optionally “OpenRouter: $X.XX” fromopenrouterCost), and date/time (createdAt).
- For each user message: show “User” (or “Customer”), the question text (
- Order by
createdAtascending so the thread reads top to bottom. - Optionally show
modelUsedfor 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 ors-data-tableif 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
sessionIdto 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.
3. Nav link
Section titled “3. Nav link”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.
4. Edge cases
Section titled “4. Edge cases”- 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
chargedCostoropenrouterCostis 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.
5. Checklist
Section titled “5. Checklist”- 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
startedAtdesc, 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.
6. File summary
Section titled “6. File summary”| Item | File / location |
|---|---|
| Route + loader + page | app/routes/app.chat-logs.jsx |
| Nav link | app/routes/app.jsx |
| Data | Existing 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.