# /iblai-vibe-history

> Mirrored from [`iblai/vibe`](https://github.com/iblai/vibe) · [`skills/users/iblai-vibe-history/SKILL.md`](https://github.com/iblai/vibe/blob/main/skills/users/iblai-vibe-history/SKILL.md). This page is generated — edit it in the repository, not here.

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).

![History — Conversations tab](https://raw.githubusercontent.com/iblai/vibe/refs/heads/main/skills/users/iblai-vibe-history/iblai-vibe-history.png)

![Conversations — transcript preview with attachments and per-conversation Download](https://raw.githubusercontent.com/iblai/vibe/refs/heads/main/skills/users/iblai-vibe-history/iblai-vibe-history-conversation.png)

![History — Exports tab](https://raw.githubusercontent.com/iblai/vibe/refs/heads/main/skills/users/iblai-vibe-history/iblai-vibe-history-exports.png)

![Admin viewing another user's history — email instead of "You", full date + time-ago](https://raw.githubusercontent.com/iblai/vibe/refs/heads/main/skills/users/iblai-vibe-history/iblai-vibe-history-admin-view.png)

> **Common setup (brand, conventions, env files, verification):** see [docs/skill-setup.md](https://raw.githubusercontent.com/iblai/vibe/refs/heads/main/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/mcp` in `.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:

```tsx
// 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 `url` opens 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` (carries
  `human_files` / `ai_files`: `TranscriptFile[]` with a presigned `url`),
  `TranscriptFile`,
  `UserChatHistoryReportListItem`, `UserChatHistoryReportState`,
  `CreateUserChatHistoryReportBody`

## Step 4: Verify

Run `/iblai-vibe-ops-test` before telling the user the work is ready:

1. `pnpm build` -- must pass with zero errors
2. `pnpm test` -- vitest must pass
3. Touch test `/profile/history` in the browser as `/iblai-vibe-ops-test` says: open it,
   use every control this skill added, read the console, check the phone width.

## Important Notes

- **Redux store**: Must include `mentorReducer` and `mentorMiddleware`
- **`initializeDataLayer()`**: 5 args (v1.2+)
- **`@reduxjs/toolkit`**: Deduplicated via webpack aliases in `next.config.ts`
- **Peer deps**: `sonner` and `@iblai/iblai-web-mentor` must be installed
  (`pnpm add sonner @iblai/iblai-web-mentor`)
- **Not standalone (yet)**: `ChatHistoryTab` is internal to the SDK's
  `Profile` component. 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
  `canViewOtherUsersChatHistory` passes, 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_files` render as file
  cards with the payload's presigned `url` — 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 shared `resolveUserIdentity` rule instead, which prefers
  a real full name.
- **Message shape tolerance**: the list endpoint may serialize
  `messages` as a JSON string while the detail endpoint returns a
  proper array — custom consumers should accept both (the SDK
  normalizes them).
- **Brand guidelines**: [BRAND.md](https://raw.githubusercontent.com/iblai/vibe/refs/heads/main/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.
