Closes #97 — message action row: copy (raw markdown + code-block copy), thumbs up/down with optimistic updates + down-reason popover, and export menu (PDF / DOCX / CSV / XLSX / TXT) for a single message plus conversation-level export in the chat toolbar.
Closes #98 — versioned WS citations frame parsing, Sources list under assistant bubbles, clickable [n] markers, and history hydrate from Prompt.citations.
Backend companion for ratings: chat_backend#67. Thumbs UI posts to prompt_feedback once that lands; votes rehydrate from conversation_details.feedback.
Export libs (pdfmake, docx, papaparse, xlsx) are dynamically imported so they stay out of the main path until used.
Test plan
Stream a grounded answer → Sources appear after stream; inline [n] highlights the matching source; reload keeps Sources.
Thumbs up/down optimistic UI; clear by re-click; down opens reason popover without blocking the vote (needs chat_backend#67).
Export one message and whole conversation in all five formats; check searchable PDF text and DOCX structure.
Action row: hover reveal on desktop, always visible on last/touch; hidden while streaming.
npm test -- --testPathPattern='wsFrames|clipboard|exportChat|promptFeedback|ConversationDetailCard'
## Summary
- Closes [#97](https://git.aimloperations.com/ai_ml_operations/chat_web_app/issues/97) — message action row: copy (raw markdown + code-block copy), thumbs up/down with optimistic updates + down-reason popover, and export menu (PDF / DOCX / CSV / XLSX / TXT) for a single message plus conversation-level export in the chat toolbar.
- Closes [#98](https://git.aimloperations.com/ai_ml_operations/chat_web_app/issues/98) — versioned WS `citations` frame parsing, Sources list under assistant bubbles, clickable `[n]` markers, and history hydrate from `Prompt.citations`.
- Backend companion for ratings: [chat_backend#67](https://git.aimloperations.com/ai_ml_operations/chat_backend/issues/67). Thumbs UI posts to `prompt_feedback` once that lands; votes rehydrate from `conversation_details.feedback`.
- Export libs (`pdfmake`, `docx`, `papaparse`, `xlsx`) are dynamically imported so they stay out of the main path until used.
## Test plan
- [ ] Stream a grounded answer → Sources appear after stream; inline `[n]` highlights the matching source; reload keeps Sources.
- [ ] Non-grounded turn → no Sources header.
- [ ] Copy message + code block; confirm checkmark ~2s; failure path shows toast.
- [ ] Thumbs up/down optimistic UI; clear by re-click; down opens reason popover without blocking the vote (needs chat_backend#67).
- [ ] Export one message and whole conversation in all five formats; check searchable PDF text and DOCX structure.
- [ ] Action row: hover reveal on desktop, always visible on last/touch; hidden while streaming.
- [ ] `npm test -- --testPathPattern='wsFrames|clipboard|exportChat|promptFeedback|ConversationDetailCard'`
Wire copy/thumbs/export on chat bubbles and render live + hydrated Sources from citations frames so users can rate, export, and inspect grounded answers.
Unit Tests failed at Install dependencies (npm ci), not at runtime against the backend.
Cause: merge of master into this branch dropped the top-level stylis peer lock entry required by stylis-plugin-rtl → Missing: stylis@4.4.0 from lock file.
Fix pushed: restore node_modules/stylis@4.3.6 (peer) in package-lock.json.
Matches this PR's promptFeedback.ts / MessageActions contract.
## Note on CI failure
Unit Tests failed at **Install dependencies** (`npm ci`), not at runtime against the backend.
Cause: merge of `master` into this branch dropped the top-level `stylis` peer lock entry required by `stylis-plugin-rtl` → `Missing: stylis@4.4.0 from lock file`.
Fix pushed: restore `node_modules/stylis@4.3.6` (peer) in `package-lock.json`.
## Backend companion
[chat_backend#67](https://git.aimloperations.com/ai_ml_operations/chat_backend/issues/67) implemented in [chat_backend#70](https://git.aimloperations.com/ai_ml_operations/chat_backend/pulls/70):
- `POST /api/prompt_feedback` upsert `{prompt_id, rating, reason?, comment?}`
- `DELETE /api/prompt_feedback?prompt_id=`
- `conversation_details` nests caller `feedback: {rating, reason, comment}` (or null)
Matches this PR's `promptFeedback.ts` / MessageActions contract.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
citationsframe parsing, Sources list under assistant bubbles, clickable[n]markers, and history hydrate fromPrompt.citations.prompt_feedbackonce that lands; votes rehydrate fromconversation_details.feedback.pdfmake,docx,papaparse,xlsx) are dynamically imported so they stay out of the main path until used.Test plan
[n]highlights the matching source; reload keeps Sources.npm test -- --testPathPattern='wsFrames|clipboard|exportChat|promptFeedback|ConversationDetailCard'Note on CI failure
Unit Tests failed at Install dependencies (
npm ci), not at runtime against the backend.Cause: merge of
masterinto this branch dropped the top-levelstylispeer lock entry required bystylis-plugin-rtl→Missing: stylis@4.4.0 from lock file.Fix pushed: restore
node_modules/stylis@4.3.6(peer) inpackage-lock.json.Backend companion
chat_backend#67 implemented in chat_backend#70:
POST /api/prompt_feedbackupsert{prompt_id, rating, reason?, comment?}DELETE /api/prompt_feedback?prompt_id=conversation_detailsnests callerfeedback: {rating, reason, comment}(or null)Matches this PR's
promptFeedback.ts/ MessageActions contract.