Add the user profile History surface -- "Review and export your
conversations with AI agents." A user's own chat history across every
agent, in two tabs: Conversations (agent / date-range / sentiment /
topic filters, a two-column conversation list + transcript preview,
per-conversation CSV download, and a filtered Export) and
Exports (previously generated reports with status, the filters
used, expiry, and a re-download action). Everything rides the
user-scoped my-chat-history* endpoints. The backend serves a user's
sessions to the user themselves, tenant admins, and watchers of
that user — so the tab renders on your own profile, and on someone
else's profile (opened from the Management tab) only for
admin/watcher viewers; any other caller gets a 403.
This is the user-facing counterpart of /iblai-vibe-agent-history
(the admin History tab in agent settings, which shows every user's
conversations for one agent).




Common setup (brand, conventions, env files, verification): see docs/skill-setup.md.
Related History surfaces
/iblai-vibe-agent-history— the admin History tab in agent settings: one agent, every user's conversations, RBAC-gated. This skill is the mirror image: one user, every agent, own data only./iblai-vibe-profile— the<Profile>page hosts this surface as its History tab; that is also how it is mounted (Step 2).
Prerequisites
- Auth must be set up first (
/iblai-vibe-auth) - MCP server + skills configured (
@iblai/mcpin.mcp.json) - No RBAC needed for a user's own history. Viewing another
user's history requires the viewer to be a tenant admin or a
watcher of that user — enforced server-side, mirrored client-side
by
canViewOtherUsersChatHistory(see the admin view section).
Step 1: Check Environment
Before proceeding, check for an iblai.env in the project root. Look for
PLATFORM, DOMAIN, and TOKEN variables. If the file does not exist or
is missing these variables, tell the user:
"You need an iblai.env with your platform configuration. Download the
template and fill in your values:
curl -o iblai.env https://raw.githubusercontent.com/iblai/vibe/refs/heads/main/iblai.env"
Step 2: Mount it (via the Profile page's History tab)
The History surface (ChatHistoryTab) is not exported as a
standalone component — it ships as the History tab inside
<Profile>. Mount the profile page and deep-link to the tab:
// app/(app)/profile/history/page.tsx
"use client";
import { Profile } from "@iblai/iblai-js/web-containers";
export default function ProfileHistoryPage() {
return (
<div className="flex h-full flex-col bg-white">
<Profile {...profileProps} targetTab="chatHistory" /* profileProps = your usual Profile wiring */ />
</div>
);
}
See /iblai-vibe-profile Step 3 for the full <Profile> prop wiring
(tenant, username, etc.) — this skill only adds the
targetTab="chatHistory" entry point. On a read-only profile preview
of another user, the tab stays visible only for tenant admins and
watchers (see the next section) and is hidden for everyone else. If
you need the surface standalone, build it from the data-layer hooks
listed below; the SDK source paths in the component map are the
reference implementation.
Admin & watcher view (via the Management tab)
Tenant admins can read any user's history without leaving the admin
area: the Management tab's user list opens a user's profile preview,
and for admin/watcher viewers that preview now includes the History
tab, mounted with the viewed user's username. What changes in
this mode:
- Who sees it — the SDK decides with
canViewOtherUsersChatHistory(rbacPermissions, tenant)(from@iblai/iblai-js/web-utils): true for a tenant admin (tenant.is_admin) or a holder of the watcher RBAC permission (/watchedgroups/#list— watchers only pass the server check for users they actually watch). - Email instead of "You" — on your own profile the human turns are labeled "You"; on someone else's history they show that user's email (resolved from user metadata, falling back to the email on the session payload, then the username).
- Server is the authority — the backend serves a user's
my-chat-history*data to the user, tenant admins, and that user's watchers; anyone else gets a 403, which the tab surfaces as a friendly "no access" message with Export disabled. - Exports on behalf — admins and watchers can generate exports for the viewed user; the Exports tab shows a "created by {owner}" line when a report was generated by someone other than the user.
Step 3: Use MCP Tools for Customization
get_component_info("Profile")
get_hook_info("useGetUserChatHistoryQuery")
Component map (web-containers source)
Everything lives under
packages/web-containers/src/components/profile/chat-history/ in the
SDK. These pieces are internal to the tab (not exported individually)
— listed with paths as the reference for custom builds:
| Component / hook | Path | Role |
|---|---|---|
ChatHistoryTab |
profile/chat-history/chat-history-tab.tsx |
The surface: Conversations / Exports sub-tabs, filters row, two-column list + preview (dialog preview on mobile), per-conversation CSV download |
ReportHistory |
profile/chat-history/report-history.tsx |
The Exports table: state badge (Pending / Processing / Completed / Error / Cancelled / Expired), filters summary, expiry, re-download |
useUserChatHistory |
profile/chat-history/use-user-chat-history.ts |
Filter + pagination + data state (10 per page); exports buildUserChatHistoryReportBody mapping the UI filters onto the report POST body |
useChatHistoryExport |
profile/chat-history/use-chat-history-export.ts |
Server-side export flow: POST a report task, poll its status every 3s, then stream the finished file down as a browser save |
TranscriptFileCards |
chat/transcript-file-cards.tsx |
The attachment cards under a message — the same component the live chat uses. Publicly exported, so custom history UI can render attachments the same way |
SearchSelect |
spend-caps/search-select.tsx |
The shared debounced agent autocomplete used by the "Search Agents" filter (min 2 characters) |
What each tab renders
Conversations tab
- Filters row — agent autocomplete ("Search Agents"), a two-month Pick a Date Range calendar, All Sentiments (values come from the filter endpoint; positive/neutral/negative as the fallback), All Topics (from the filter endpoint), and an Export button that generates a server-side report of everything matching the current filters.
- Conversation list (left) — 10 per page: the conversation's full date and recency ("Jul 30, 2026, 9:46 PM · about 1 month ago"), sentiment badge (green/gray/red), agent name, title, and a one-line plain-text preview of the first AI reply. Markdown is stripped for list rows.
- Transcript preview (right) — the selected conversation as
alternating You / agent messages (the viewed user's email
instead of "You" in the admin/watcher view) rendered as Markdown,
with the files and images the user attached shown as file cards
under the message (the payload's presigned
urlopens each one; a turn that was only an attachment has empty text), the same date + time-ago header and the user's real avatar (edX profile image → Gravatar → initial) and the agent's avatar. A Download button saves just this conversation as a client-side CSV. When the list payload carried only session metadata, the full transcript is fetched lazily. On mobile the preview opens in a dialog instead of the second column. - Empty state: "Select a conversation to view details."
Exports tab
Previously generated reports, newest first: Status badge, Created (time-ago), Filters summary (agent count or "All agents", date range or "All time", plus topic/sentiment when set), Expires, and a Download button while the report is still available. In-progress states keep a blue badge until the poll reaches a terminal state. When someone other than the user generated a report (an admin or watcher exporting on their behalf), the row carries a "created by {owner}" line.
Related Exports
From @iblai/iblai-js/web-containers:
Profile— the profile page hosting this surface as its History tab (targetTab="chatHistory").TranscriptFileCards(+TranscriptFileCardFile) — the attachment cards this surface renders under a message; take{ name, contentType, url }per file.resolveUserIdentity— the shared "what do we call this user" rule the admin transcript surfaces use. This learner surface keeps its own simpler rule (see below); reach for the helper when you build your own.
From @iblai/iblai-js/web-utils:
canViewOtherUsersChatHistory(rbacPermissions, tenant)— whether the current viewer may open another user's history (tenant admin or watcher RBAC).WATCHER_RBAC_RESOURCE— the watcher permission the check reads (/watchedgroups/#list).
From @iblai/iblai-js/data-layer — the RTK Query hooks the surface
uses, for custom UI on the same endpoints:
- List + detail:
useGetUserChatHistoryQuery,useGetUserChatHistorySessionQuery - Filter options:
useGetUserChatHistoryFilterQuery(the authority on which sentiment/topic values the backend filters by — never send values it didn't offer) - Reports:
useCreateUserChatHistoryReportMutation,useGetUserChatHistoryReportStatusQuery(poll until a terminal state),useGetUserChatHistoryReportsQuery(the Exports list),useLazyGetDownloadReportFromURLQuery(stream the finished file) - Constants + types:
USER_CHAT_HISTORY_REPORT_ACTIVE_STATES,UserChatHistorySession,UserChatHistoryMessage(carrieshuman_files/ai_files:TranscriptFile[]with a presignedurl),TranscriptFile,UserChatHistoryReportListItem,UserChatHistoryReportState,CreateUserChatHistoryReportBody
Step 4: Verify
Run /iblai-vibe-ops-test before telling the user the work is ready:
pnpm build-- must pass with zero errorspnpm test-- vitest must pass- Start dev server and touch test:
pnpm dev & npx playwright screenshot http://localhost:3000/profile/history /tmp/user-history.png
Important Notes
- Redux store: Must include
mentorReducerandmentorMiddleware initializeDataLayer(): 5 args (v1.2+)@reduxjs/toolkit: Deduplicated via webpack aliases innext.config.ts- Peer deps:
sonnerand@iblai/iblai-web-mentormust be installed (pnpm add sonner @iblai/iblai-web-mentor) - Not standalone (yet):
ChatHistoryTabis internal to the SDK'sProfilecomponent. Reach it with<Profile targetTab="chatHistory">; for a bare custom page, rebuild from the data-layer hooks using the component map above as the reference. - Cross-user access is admin/watcher only: on another user's
profile preview the tab appears only when
canViewOtherUsersChatHistorypasses, and the backend independently enforces the same rule (403 otherwise — even for a watcher on a user they don't watch). Per-user history here complements/iblai-vibe-agent-history(one agent, all users). - Two export paths: the filters-row Export POSTs a server-side report (async task, polled every 3s, listed under Exports with an expiry); the preview's Download builds a CSV of that single conversation client-side — no report involved.
- Attachments: a turn's
human_files/ai_filesrender as file cards with the payload's presignedurl— those links expire, so re-fetch rather than caching them. A turn that was only an attachment carries empty text, which is why the card renders even with no message body. - Who the message is from: on your own profile the human turns read
You; in the admin/watcher view they show the viewed user's email
(user metadata → the session payload's email → username). The admin
surfaces (
/iblai-vibe-agent-history, the analytics Transcripts tab) use the richer sharedresolveUserIdentityrule instead, which prefers a real full name. - Message shape tolerance: the list endpoint may serialize
messagesas a JSON string while the detail endpoint returns a proper array — custom consumers should accept both (the SDK normalizes them). - Brand guidelines: BRAND.md
User Chat History REST API
For custom UI beyond the tab. All endpoints are prefixed with
${dmUrl}/api/ai-analytics/orgs/{org}/users/{user_id}/ where dmUrl
is NEXT_PUBLIC_API_BASE_URL. Auth: Authorization: Token <token>.
{user_id} is the user whose history is read: the endpoints answer
for the user themselves, tenant admins, and watchers of that user —
any other caller gets a 403.
| Method | Path | Purpose |
|---|---|---|
| GET | my-chat-history/ |
List the user's sessions — page, page_size, plus mentor_id, start_date, end_date, topic, sentiment filters |
| GET | my-chat-history/{session_id}/ |
One session with its full messages transcript |
| GET | my-chat-history-filter/ |
The sentiment/topic values available to filter by |
| POST | my-chat-history-report/ |
Start an export — { format: "csv" | "json", start_date?, end_date?, filters?: { mentor_id?: [..], topic?, sentiment? } } (mentor_id is an array even for one agent) |
| GET | my-chat-history-report/ |
List generated reports (the Exports tab) |
| GET | my-chat-history-report/{task_id}/ |
Poll a report task's state (pending / running / accumulating / processing / storing → completed / error / cancelled / expired) |
| GET | my-chat-history-report/{task_id}/download/ |
Download the finished file (?format=csv|json) |
Reports expire — the Exports tab shows the expiry and the Download action disappears once a report is no longer available.