@burdenoff/microfe-buildmyiq 2026.1028.2 → 2026.1029.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TutorReviewPage.d.ts","sourceRoot":"","sources":["../../../../src/buildmyiq/pages/tutorReview/TutorReviewPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAqB,KAAK,EAAE,EAAE,MAAM,OAAO,CAAC;AA+WnD,eAAO,MAAM,eAAe,EAAE,
|
|
1
|
+
{"version":3,"file":"TutorReviewPage.d.ts","sourceRoot":"","sources":["../../../../src/buildmyiq/pages/tutorReview/TutorReviewPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAqB,KAAK,EAAE,EAAE,MAAM,OAAO,CAAC;AA+WnD,eAAO,MAAM,eAAe,EAAE,EAqN7B,CAAC"}
|
|
@@ -418,9 +418,9 @@ var V = 20, H = "__all__", U = ({ courseId: e, lessonId: t }) => {
|
|
|
418
418
|
})]
|
|
419
419
|
}),
|
|
420
420
|
/* @__PURE__ */ j(P, {
|
|
421
|
-
open: G != null,
|
|
421
|
+
open: G != null && D == null,
|
|
422
422
|
onOpenChange: (e) => {
|
|
423
|
-
e
|
|
423
|
+
!e && D == null && E(null);
|
|
424
424
|
},
|
|
425
425
|
children: /* @__PURE__ */ j(F, {
|
|
426
426
|
side: h ? "right" : "bottom",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TutorReviewPage.js","names":[],"sources":["../../../../src/buildmyiq/pages/tutorReview/TutorReviewPage.tsx"],"sourcesContent":["/**\n * TutorReviewPage — educator review of AI tutor conversations (BOFF-9663).\n * @module buildmyiq/pages/tutorReview\n *\n * Lists the workspace's tutor conversations (`buiListTutorSessions`, EDITOR+),\n * optionally filtered to one course. Opening a conversation shows every turn;\n * for each tutor answer a reviewer can annotate it, correct it, redact it, or\n * clear an earlier review (`buiReviewTutorAnswer`). The original answer is\n * never overwritten: it stays visible here with the full review history, while\n * the learner sees the reviewed version with a \"reviewed by your instructor\"\n * marker.\n */\n\nimport { useMemo, useState, type FC } from 'react';\nimport { Bot, EyeOff, History, MessageSquareText, PencilLine, Undo2 } from 'lucide-react';\nimport {\n Button,\n Sheet,\n SheetContent,\n SheetDescription,\n SheetHeader,\n SheetTitle,\n Textarea,\n} from '@burdenoff/fe-libs/ui';\nimport { useMediaQuery } from '@burdenoff/fe-libs/shared/hooks';\n\nimport {\n Avatar,\n DataTable,\n EmptyState,\n ErrorView,\n LoadingView,\n PageLayout,\n Pagination,\n TutorReviewMarker,\n type Column,\n} from '../../components';\nimport { BuiPermissionButton } from '../../components/BuiPermissionButton';\nimport { useBuildmyiq } from '../../context';\nimport {\n useBuiCourse,\n useBuiCourses,\n useBuiReviewTutorAnswer,\n useBuiTutorReviewSessions,\n useCursorPager,\n} from '../../hooks';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { formatDateTime } from '../../utils/formatters';\nimport type { BuiTutorMessage, BuiTutorReviewAction, BuiTutorSession } from '../../types';\nimport { Field, Modal, SelectField } from '../authoring/_forms';\nimport {\n MAX_CORRECTED_CONTENT_LENGTH,\n MAX_REVIEW_NOTE_LENGTH,\n buildReviewInput,\n summarizeSession,\n} from './tutorReviewModel';\n\nconst PAGE_SIZE = 20;\nconst ALL_COURSES = '__all__';\n\nconst LessonTitle: FC<{ courseId: string; lessonId: string }> = ({ courseId, lessonId }) => {\n const { data } = useBuiCourse(courseId);\n const title = data?.lessons.find((l) => l.id === lessonId)?.title;\n return (\n <span className=\"text-text-secondary\" title={lessonId}>\n {title ?? `${lessonId.slice(0, 8)}…`}\n </span>\n );\n};\n\ninterface ReviewTarget {\n message: BuiTutorMessage;\n action: BuiTutorReviewAction;\n}\n\nconst ReviewDialog: FC<{ target: ReviewTarget; onClose: () => void }> = ({ target, onClose }) => {\n const tr = useTr();\n const review = useBuiReviewTutorAnswer();\n const { message, action } = target;\n const [note, setNote] = useState(action === 'CLEAR' ? '' : (message.review?.note ?? ''));\n const [corrected, setCorrected] = useState(\n message.review?.status === 'CORRECTED'\n ? message.content\n : (message.originalContent ?? message.content)\n );\n const built = buildReviewInput(message.id, { action, note, correctedContent: corrected });\n\n const copy: Record<BuiTutorReviewAction, { title: string; description: string; cta: string }> = {\n ANNOTATE: {\n title: tr('buildmyiq.tutorReview.annotateTitle', 'Annotate answer'),\n description: tr(\n 'buildmyiq.tutorReview.annotateDescription',\n 'The answer stays as it is. The learner sees your note under it.'\n ),\n cta: tr('buildmyiq.tutorReview.annotate', 'Annotate'),\n },\n CORRECT: {\n title: tr('buildmyiq.tutorReview.correctTitle', 'Correct answer'),\n description: tr(\n 'buildmyiq.tutorReview.correctDescription',\n 'The learner sees your corrected answer instead of the original, which is kept for audit.'\n ),\n cta: tr('buildmyiq.tutorReview.correct', 'Correct'),\n },\n REDACT: {\n title: tr('buildmyiq.tutorReview.redactTitle', 'Redact answer'),\n description: tr(\n 'buildmyiq.tutorReview.redactDescription',\n 'The learner no longer sees this answer, and the tutor will not reuse it. The original is kept for audit.'\n ),\n cta: tr('buildmyiq.tutorReview.redact', 'Redact'),\n },\n CLEAR: {\n title: tr('buildmyiq.tutorReview.clearTitle', 'Clear review'),\n description: tr(\n 'buildmyiq.tutorReview.clearDescription',\n 'The learner sees the original answer again. The review stays in the history.'\n ),\n cta: tr('buildmyiq.tutorReview.clear', 'Clear review'),\n },\n };\n\n const handleSave = async (): Promise<void> => {\n if (!built.ok) return;\n try {\n await review.mutateAsync(built.input);\n onClose();\n } catch {\n // Shown inline from `review.error`.\n }\n };\n\n return (\n <Modal\n open\n onClose={onClose}\n title={copy[action].title}\n description={copy[action].description}\n footer={\n <>\n <Button variant=\"outline\" onClick={onClose}>\n {tr('common.cancel', 'Cancel')}\n </Button>\n <Button\n variant={action === 'REDACT' ? 'destructive' : 'default'}\n onClick={() => void handleSave()}\n disabled={review.isPending || !built.ok}\n data-testid=\"tutor-review-save\"\n >\n {copy[action].cta}\n </Button>\n </>\n }\n >\n <div className=\"grid gap-3 py-2\">\n <div className=\"rounded-md border border-border-seam bg-bg-sunken p-3\">\n <p className=\"mb-1 text-xs font-medium text-text-tertiary\">\n {tr('buildmyiq.tutorReview.originalAnswer', 'Original answer')}\n </p>\n <p className=\"max-h-40 overflow-y-auto whitespace-pre-wrap break-words text-sm text-text-secondary\">\n {message.originalContent ?? message.content}\n </p>\n </div>\n {action === 'CORRECT' && (\n <Field\n label={tr('buildmyiq.tutorReview.correctedLabel', 'Corrected answer')}\n htmlFor=\"tutor-review-corrected\"\n required\n hint={tr('buildmyiq.tutorReview.maxChars', 'Up to {{max}} characters.', {\n max: MAX_CORRECTED_CONTENT_LENGTH,\n })}\n >\n <Textarea\n id=\"tutor-review-corrected\"\n rows={6}\n value={corrected}\n onChange={(e) => setCorrected(e.target.value)}\n />\n </Field>\n )}\n {action !== 'CLEAR' && (\n <Field\n label={tr('buildmyiq.tutorReview.noteLabel', 'Note for the learner')}\n htmlFor=\"tutor-review-note\"\n required={action === 'ANNOTATE'}\n hint={tr('buildmyiq.tutorReview.maxChars', 'Up to {{max}} characters.', {\n max: MAX_REVIEW_NOTE_LENGTH,\n })}\n >\n <Textarea\n id=\"tutor-review-note\"\n rows={3}\n value={note}\n onChange={(e) => setNote(e.target.value)}\n />\n </Field>\n )}\n {!built.ok && (note.trim() || action === 'CORRECT') && (\n <p className=\"text-xs text-text-tertiary\">{built.reason}</p>\n )}\n {review.isError && (\n <p className=\"text-sm text-status-error-text\">\n {review.error instanceof Error\n ? review.error.message\n : tr('buildmyiq.tutorReview.saveError', 'Could not save the review.')}\n </p>\n )}\n </div>\n </Modal>\n );\n};\n\nconst ACTION_LABEL: Record<BuiTutorReviewAction, string> = {\n ANNOTATE: 'Annotated',\n CORRECT: 'Corrected',\n REDACT: 'Redacted',\n CLEAR: 'Review cleared',\n};\n\nconst AnswerCard: FC<{\n message: BuiTutorMessage;\n onAction: (target: ReviewTarget) => void;\n}> = ({ message, onAction }) => {\n const tr = useTr();\n const [showHistory, setShowHistory] = useState(false);\n const status = message.review?.status;\n const replaced = status === 'CORRECTED' || status === 'REDACTED';\n const history = message.reviewHistory ?? [];\n\n return (\n <div\n className=\"min-w-0 flex-1\"\n data-testid=\"tutor-review-answer\"\n data-message-id={message.id}\n data-review-status={status ?? 'NONE'}\n >\n <p className=\"text-sm font-semibold text-text-primary\">\n {tr('buildmyiq.tutorReview.tutor', 'AI Tutor')}\n </p>\n {replaced && (\n <p className=\"mt-1 text-xs font-medium text-text-tertiary\">\n {tr('buildmyiq.tutorReview.learnerSees', 'Learner sees')}\n </p>\n )}\n <p\n className={\n status === 'REDACTED'\n ? 'mt-0.5 whitespace-pre-wrap break-words text-sm italic text-text-tertiary'\n : 'mt-0.5 whitespace-pre-wrap break-words text-sm text-text-secondary'\n }\n >\n {message.content}\n </p>\n {message.review && <TutorReviewMarker review={message.review} audience=\"reviewer\" />}\n {replaced && message.originalContent != null && (\n <details className=\"mt-2 rounded-md border border-border-seam px-2.5 py-1.5\">\n <summary className=\"cursor-pointer text-xs font-medium text-text-secondary\">\n {tr('buildmyiq.tutorReview.originalAnswer', 'Original answer')}\n </summary>\n <p className=\"mt-1 whitespace-pre-wrap break-words text-sm text-text-secondary\">\n {message.originalContent}\n </p>\n </details>\n )}\n\n <BuiPermissionButton resource=\"buildmyiq-tutor\" action=\"update\" deniedBehavior=\"hide\">\n <div className=\"mt-2 flex flex-wrap gap-1.5\">\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={() => onAction({ message, action: 'ANNOTATE' })}\n >\n <MessageSquareText className=\"mr-1 h-4 w-4\" />\n {tr('buildmyiq.tutorReview.annotate', 'Annotate')}\n </Button>\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={() => onAction({ message, action: 'CORRECT' })}\n >\n <PencilLine className=\"mr-1 h-4 w-4\" />\n {tr('buildmyiq.tutorReview.correct', 'Correct')}\n </Button>\n <Button\n size=\"sm\"\n variant=\"ghost\"\n className=\"text-status-error-text hover:text-status-error-text\"\n onClick={() => onAction({ message, action: 'REDACT' })}\n >\n <EyeOff className=\"mr-1 h-4 w-4\" />\n {tr('buildmyiq.tutorReview.redact', 'Redact')}\n </Button>\n {status && (\n <Button\n size=\"sm\"\n variant=\"ghost\"\n onClick={() => onAction({ message, action: 'CLEAR' })}\n >\n <Undo2 className=\"mr-1 h-4 w-4\" />\n {tr('buildmyiq.tutorReview.clear', 'Clear review')}\n </Button>\n )}\n </div>\n </BuiPermissionButton>\n\n {history.length > 0 && (\n <div className=\"mt-2\">\n <button\n type=\"button\"\n className=\"inline-flex items-center gap-1 text-xs text-text-secondary hover:text-text-primary\"\n onClick={() => setShowHistory((v) => !v)}\n aria-expanded={showHistory}\n >\n <History className=\"h-3.5 w-3.5\" />\n {tr('buildmyiq.tutorReview.history', 'Review history ({{count}})', {\n count: history.length,\n })}\n </button>\n {showHistory && (\n <ol className=\"mt-1.5 grid gap-1.5 border-l border-border-seam pl-3\">\n {history.map((entry) => (\n <li key={entry.id} className=\"text-xs text-text-secondary\">\n <span className=\"font-medium text-text-primary\">\n {tr(`buildmyiq.tutorReview.action.${entry.action}`, ACTION_LABEL[entry.action])}\n </span>{' '}\n · {formatDateTime(entry.createdAt)} ·{' '}\n <span className=\"font-mono\" title={entry.reviewerId}>\n {entry.reviewerId.slice(0, 8)}…\n </span>\n {entry.note && <p className=\"mt-0.5 whitespace-pre-wrap\">{entry.note}</p>}\n </li>\n ))}\n </ol>\n )}\n </div>\n )}\n </div>\n );\n};\n\nconst Transcript: FC<{ session: BuiTutorSession; onAction: (target: ReviewTarget) => void }> = ({\n session,\n onAction,\n}) => {\n const tr = useTr();\n if (session.messages.length === 0) {\n return (\n <p className=\"text-sm text-text-secondary\">\n {tr('buildmyiq.tutorReview.noMessages', 'This conversation has no messages yet.')}\n </p>\n );\n }\n return (\n <ul className=\"flex flex-col gap-5\">\n {session.messages.map((message) =>\n message.role === 'ASSISTANT' ? (\n <li key={message.id} className=\"flex gap-3\">\n <span className=\"flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-[var(--color-accent-soft)]\">\n <Bot className=\"h-4 w-4 text-[var(--color-accent-teal)]\" />\n </span>\n <AnswerCard message={message} onAction={onAction} />\n </li>\n ) : (\n <li key={message.id} className=\"flex gap-3\">\n <Avatar size=\"sm\" seed={session.learnerId} name={null} />\n <div className=\"min-w-0 flex-1\">\n <p className=\"text-sm font-semibold text-text-primary\">\n {tr('buildmyiq.tutorReview.learner', 'Learner')}\n </p>\n <p className=\"mt-0.5 whitespace-pre-wrap break-words text-sm text-text-secondary\">\n {message.content}\n </p>\n </div>\n </li>\n )\n )}\n </ul>\n );\n};\n\nexport const TutorReviewPage: FC = () => {\n const tr = useTr();\n const { currentUser } = useBuildmyiq();\n const isDesktop = useMediaQuery('(min-width: 768px)');\n const pager = useCursorPager();\n const [courseId, setCourseId] = useState<string>(ALL_COURSES);\n const [activeId, setActiveId] = useState<string | null>(null);\n const [target, setTarget] = useState<ReviewTarget | null>(null);\n\n const coursesQuery = useBuiCourses({ limit: 100 });\n const sessionsQuery = useBuiTutorReviewSessions({\n courseId: courseId === ALL_COURSES ? undefined : courseId,\n cursor: pager.cursor,\n limit: PAGE_SIZE,\n });\n const rows = sessionsQuery.data?.items ?? [];\n const active = rows.find((s) => s.id === activeId) ?? null;\n\n const courseTitles = useMemo(\n () => new Map((coursesQuery.data?.items ?? []).map((c) => [c.id, c.title] as const)),\n [coursesQuery.data]\n );\n const courseOptions = [\n { value: ALL_COURSES, label: tr('buildmyiq.tutorReview.allCourses', 'All courses') },\n ...(coursesQuery.data?.items ?? []).map((c) => ({ value: c.id, label: c.title })),\n ];\n\n const learnerCell = (learnerId: string) =>\n learnerId === currentUser?.id ? (\n <div className=\"flex items-center gap-2.5\">\n <Avatar size=\"sm\" seed={learnerId} name={currentUser?.name ?? null} />\n <span className=\"font-medium text-text-primary\">{tr('common.you', 'You')}</span>\n </div>\n ) : (\n <div className=\"flex items-center gap-2.5\">\n <Avatar size=\"sm\" seed={learnerId} name={null} />\n <span className=\"font-mono text-xs text-text-tertiary\" title={learnerId}>\n {learnerId.slice(0, 8)}…\n </span>\n </div>\n );\n\n const columns: Column<BuiTutorSession>[] = [\n {\n key: 'learner',\n header: tr('buildmyiq.tutorReview.learner', 'Learner'),\n render: (s) => learnerCell(s.learnerId),\n },\n {\n key: 'course',\n header: tr('buildmyiq.tutorReview.course', 'Course'),\n render: (s) => (\n <span className=\"text-text-primary\" title={s.courseId}>\n {courseTitles.get(s.courseId) ?? `${s.courseId.slice(0, 8)}…`}\n </span>\n ),\n },\n {\n key: 'lesson',\n header: tr('buildmyiq.tutorReview.lesson', 'Lesson'),\n hideOnMobile: true,\n render: (s) => <LessonTitle courseId={s.courseId} lessonId={s.lessonId} />,\n },\n {\n key: 'answers',\n header: tr('buildmyiq.tutorReview.answers', 'Answers'),\n align: 'right',\n sortValue: (s) => summarizeSession(s).answers,\n render: (s) => <span className=\"tabular-nums\">{summarizeSession(s).answers}</span>,\n },\n {\n key: 'reviewed',\n header: tr('buildmyiq.tutorReview.reviewed', 'Reviewed'),\n align: 'right',\n sortValue: (s) => summarizeSession(s).reviewed,\n render: (s) => {\n const { reviewed } = summarizeSession(s);\n return (\n <span\n className={\n reviewed > 0\n ? 'font-medium tabular-nums text-status-success-text'\n : 'tabular-nums text-text-tertiary'\n }\n >\n {reviewed}\n </span>\n );\n },\n },\n {\n key: 'updatedAt',\n header: tr('buildmyiq.tutorReview.updated', 'Last activity'),\n hideOnMobile: true,\n sortValue: (s) => s.updatedAt,\n render: (s) => <span className=\"text-text-secondary\">{formatDateTime(s.updatedAt)}</span>,\n },\n ];\n\n return (\n <PageLayout\n title={tr('buildmyiq.tutorReview.title', 'Tutor review')}\n description={tr(\n 'buildmyiq.tutorReview.description',\n 'Read what the AI tutor told your learners, and annotate, correct or redact any answer. Learners see your reviewed version with a \"reviewed by your instructor\" marker.'\n )}\n >\n <div className=\"mb-5 max-w-sm\">\n <Field\n label={tr('buildmyiq.tutorReview.courseFilter', 'Course')}\n htmlFor=\"tutor-review-course\"\n >\n <SelectField\n id=\"tutor-review-course\"\n value={courseId}\n onChange={(value) => {\n setCourseId(value);\n pager.reset();\n }}\n options={courseOptions}\n />\n </Field>\n </div>\n\n {sessionsQuery.isLoading ? (\n <LoadingView label={tr('buildmyiq.tutorReview.loading', 'Loading tutor conversations…')} />\n ) : sessionsQuery.isError ? (\n <ErrorView\n title={tr('buildmyiq.tutorReview.loadError', \"Couldn't load tutor conversations\")}\n message={sessionsQuery.error instanceof Error ? sessionsQuery.error.message : undefined}\n onRetry={() => sessionsQuery.refetch()}\n />\n ) : rows.length === 0 ? (\n <EmptyState\n icon={<Bot className=\"h-10 w-10\" />}\n title={tr('buildmyiq.tutorReview.empty', 'No tutor conversations yet')}\n description={tr(\n 'buildmyiq.tutorReview.emptyDescription',\n 'Conversations appear here once learners ask the AI tutor about a lesson.'\n )}\n />\n ) : (\n <div className=\"flex flex-col gap-4\">\n <DataTable\n columns={columns}\n rows={rows}\n rowKey={(s) => s.id}\n onRowClick={(s) => setActiveId(s.id)}\n defaultSort={{ key: 'updatedAt', dir: 'desc' }}\n />\n <Pagination\n page={pager.page}\n pageSize={PAGE_SIZE}\n itemCount={rows.length}\n hasMore={sessionsQuery.data?.pageInfo?.hasMore ?? false}\n onPageChange={(page) => pager.onPageChange(page, sessionsQuery.data?.pageInfo?.cursor)}\n />\n </div>\n )}\n\n <Sheet\n open={active != null}\n onOpenChange={(open) => {\n if (!open) setActiveId(null);\n }}\n >\n <SheetContent\n side={isDesktop ? 'right' : 'bottom'}\n className={\n isDesktop\n ? 'flex w-full max-w-none flex-col gap-0 p-0 sm:max-w-xl'\n : 'flex max-h-[92vh] w-full flex-col gap-0 rounded-t-2xl p-0'\n }\n >\n {active && (\n <>\n <SheetHeader className=\"border-b border-border-seam p-5 pr-12\">\n <SheetTitle>\n {courseTitles.get(active.courseId) ??\n tr('buildmyiq.tutorReview.conversation', 'Tutor conversation')}\n </SheetTitle>\n <SheetDescription>\n <LessonTitle courseId={active.courseId} lessonId={active.lessonId} /> ·{' '}\n {formatDateTime(active.updatedAt)}\n </SheetDescription>\n </SheetHeader>\n <div\n className=\"flex-1 overflow-y-auto px-5 py-4\"\n data-testid=\"tutor-review-transcript\"\n >\n <Transcript session={active} onAction={setTarget} />\n </div>\n </>\n )}\n </SheetContent>\n </Sheet>\n\n {target && (\n <ReviewDialog\n key={`${target.message.id}:${target.action}`}\n target={target}\n onClose={() => setTarget(null)}\n />\n )}\n </PageLayout>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"TutorReviewPage.js","names":[],"sources":["../../../../src/buildmyiq/pages/tutorReview/TutorReviewPage.tsx"],"sourcesContent":["/**\n * TutorReviewPage — educator review of AI tutor conversations (BOFF-9663).\n * @module buildmyiq/pages/tutorReview\n *\n * Lists the workspace's tutor conversations (`buiListTutorSessions`, EDITOR+),\n * optionally filtered to one course. Opening a conversation shows every turn;\n * for each tutor answer a reviewer can annotate it, correct it, redact it, or\n * clear an earlier review (`buiReviewTutorAnswer`). The original answer is\n * never overwritten: it stays visible here with the full review history, while\n * the learner sees the reviewed version with a \"reviewed by your instructor\"\n * marker.\n */\n\nimport { useMemo, useState, type FC } from 'react';\nimport { Bot, EyeOff, History, MessageSquareText, PencilLine, Undo2 } from 'lucide-react';\nimport {\n Button,\n Sheet,\n SheetContent,\n SheetDescription,\n SheetHeader,\n SheetTitle,\n Textarea,\n} from '@burdenoff/fe-libs/ui';\nimport { useMediaQuery } from '@burdenoff/fe-libs/shared/hooks';\n\nimport {\n Avatar,\n DataTable,\n EmptyState,\n ErrorView,\n LoadingView,\n PageLayout,\n Pagination,\n TutorReviewMarker,\n type Column,\n} from '../../components';\nimport { BuiPermissionButton } from '../../components/BuiPermissionButton';\nimport { useBuildmyiq } from '../../context';\nimport {\n useBuiCourse,\n useBuiCourses,\n useBuiReviewTutorAnswer,\n useBuiTutorReviewSessions,\n useCursorPager,\n} from '../../hooks';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { formatDateTime } from '../../utils/formatters';\nimport type { BuiTutorMessage, BuiTutorReviewAction, BuiTutorSession } from '../../types';\nimport { Field, Modal, SelectField } from '../authoring/_forms';\nimport {\n MAX_CORRECTED_CONTENT_LENGTH,\n MAX_REVIEW_NOTE_LENGTH,\n buildReviewInput,\n summarizeSession,\n} from './tutorReviewModel';\n\nconst PAGE_SIZE = 20;\nconst ALL_COURSES = '__all__';\n\nconst LessonTitle: FC<{ courseId: string; lessonId: string }> = ({ courseId, lessonId }) => {\n const { data } = useBuiCourse(courseId);\n const title = data?.lessons.find((l) => l.id === lessonId)?.title;\n return (\n <span className=\"text-text-secondary\" title={lessonId}>\n {title ?? `${lessonId.slice(0, 8)}…`}\n </span>\n );\n};\n\ninterface ReviewTarget {\n message: BuiTutorMessage;\n action: BuiTutorReviewAction;\n}\n\nconst ReviewDialog: FC<{ target: ReviewTarget; onClose: () => void }> = ({ target, onClose }) => {\n const tr = useTr();\n const review = useBuiReviewTutorAnswer();\n const { message, action } = target;\n const [note, setNote] = useState(action === 'CLEAR' ? '' : (message.review?.note ?? ''));\n const [corrected, setCorrected] = useState(\n message.review?.status === 'CORRECTED'\n ? message.content\n : (message.originalContent ?? message.content)\n );\n const built = buildReviewInput(message.id, { action, note, correctedContent: corrected });\n\n const copy: Record<BuiTutorReviewAction, { title: string; description: string; cta: string }> = {\n ANNOTATE: {\n title: tr('buildmyiq.tutorReview.annotateTitle', 'Annotate answer'),\n description: tr(\n 'buildmyiq.tutorReview.annotateDescription',\n 'The answer stays as it is. The learner sees your note under it.'\n ),\n cta: tr('buildmyiq.tutorReview.annotate', 'Annotate'),\n },\n CORRECT: {\n title: tr('buildmyiq.tutorReview.correctTitle', 'Correct answer'),\n description: tr(\n 'buildmyiq.tutorReview.correctDescription',\n 'The learner sees your corrected answer instead of the original, which is kept for audit.'\n ),\n cta: tr('buildmyiq.tutorReview.correct', 'Correct'),\n },\n REDACT: {\n title: tr('buildmyiq.tutorReview.redactTitle', 'Redact answer'),\n description: tr(\n 'buildmyiq.tutorReview.redactDescription',\n 'The learner no longer sees this answer, and the tutor will not reuse it. The original is kept for audit.'\n ),\n cta: tr('buildmyiq.tutorReview.redact', 'Redact'),\n },\n CLEAR: {\n title: tr('buildmyiq.tutorReview.clearTitle', 'Clear review'),\n description: tr(\n 'buildmyiq.tutorReview.clearDescription',\n 'The learner sees the original answer again. The review stays in the history.'\n ),\n cta: tr('buildmyiq.tutorReview.clear', 'Clear review'),\n },\n };\n\n const handleSave = async (): Promise<void> => {\n if (!built.ok) return;\n try {\n await review.mutateAsync(built.input);\n onClose();\n } catch {\n // Shown inline from `review.error`.\n }\n };\n\n return (\n <Modal\n open\n onClose={onClose}\n title={copy[action].title}\n description={copy[action].description}\n footer={\n <>\n <Button variant=\"outline\" onClick={onClose}>\n {tr('common.cancel', 'Cancel')}\n </Button>\n <Button\n variant={action === 'REDACT' ? 'destructive' : 'default'}\n onClick={() => void handleSave()}\n disabled={review.isPending || !built.ok}\n data-testid=\"tutor-review-save\"\n >\n {copy[action].cta}\n </Button>\n </>\n }\n >\n <div className=\"grid gap-3 py-2\">\n <div className=\"rounded-md border border-border-seam bg-bg-sunken p-3\">\n <p className=\"mb-1 text-xs font-medium text-text-tertiary\">\n {tr('buildmyiq.tutorReview.originalAnswer', 'Original answer')}\n </p>\n <p className=\"max-h-40 overflow-y-auto whitespace-pre-wrap break-words text-sm text-text-secondary\">\n {message.originalContent ?? message.content}\n </p>\n </div>\n {action === 'CORRECT' && (\n <Field\n label={tr('buildmyiq.tutorReview.correctedLabel', 'Corrected answer')}\n htmlFor=\"tutor-review-corrected\"\n required\n hint={tr('buildmyiq.tutorReview.maxChars', 'Up to {{max}} characters.', {\n max: MAX_CORRECTED_CONTENT_LENGTH,\n })}\n >\n <Textarea\n id=\"tutor-review-corrected\"\n rows={6}\n value={corrected}\n onChange={(e) => setCorrected(e.target.value)}\n />\n </Field>\n )}\n {action !== 'CLEAR' && (\n <Field\n label={tr('buildmyiq.tutorReview.noteLabel', 'Note for the learner')}\n htmlFor=\"tutor-review-note\"\n required={action === 'ANNOTATE'}\n hint={tr('buildmyiq.tutorReview.maxChars', 'Up to {{max}} characters.', {\n max: MAX_REVIEW_NOTE_LENGTH,\n })}\n >\n <Textarea\n id=\"tutor-review-note\"\n rows={3}\n value={note}\n onChange={(e) => setNote(e.target.value)}\n />\n </Field>\n )}\n {!built.ok && (note.trim() || action === 'CORRECT') && (\n <p className=\"text-xs text-text-tertiary\">{built.reason}</p>\n )}\n {review.isError && (\n <p className=\"text-sm text-status-error-text\">\n {review.error instanceof Error\n ? review.error.message\n : tr('buildmyiq.tutorReview.saveError', 'Could not save the review.')}\n </p>\n )}\n </div>\n </Modal>\n );\n};\n\nconst ACTION_LABEL: Record<BuiTutorReviewAction, string> = {\n ANNOTATE: 'Annotated',\n CORRECT: 'Corrected',\n REDACT: 'Redacted',\n CLEAR: 'Review cleared',\n};\n\nconst AnswerCard: FC<{\n message: BuiTutorMessage;\n onAction: (target: ReviewTarget) => void;\n}> = ({ message, onAction }) => {\n const tr = useTr();\n const [showHistory, setShowHistory] = useState(false);\n const status = message.review?.status;\n const replaced = status === 'CORRECTED' || status === 'REDACTED';\n const history = message.reviewHistory ?? [];\n\n return (\n <div\n className=\"min-w-0 flex-1\"\n data-testid=\"tutor-review-answer\"\n data-message-id={message.id}\n data-review-status={status ?? 'NONE'}\n >\n <p className=\"text-sm font-semibold text-text-primary\">\n {tr('buildmyiq.tutorReview.tutor', 'AI Tutor')}\n </p>\n {replaced && (\n <p className=\"mt-1 text-xs font-medium text-text-tertiary\">\n {tr('buildmyiq.tutorReview.learnerSees', 'Learner sees')}\n </p>\n )}\n <p\n className={\n status === 'REDACTED'\n ? 'mt-0.5 whitespace-pre-wrap break-words text-sm italic text-text-tertiary'\n : 'mt-0.5 whitespace-pre-wrap break-words text-sm text-text-secondary'\n }\n >\n {message.content}\n </p>\n {message.review && <TutorReviewMarker review={message.review} audience=\"reviewer\" />}\n {replaced && message.originalContent != null && (\n <details className=\"mt-2 rounded-md border border-border-seam px-2.5 py-1.5\">\n <summary className=\"cursor-pointer text-xs font-medium text-text-secondary\">\n {tr('buildmyiq.tutorReview.originalAnswer', 'Original answer')}\n </summary>\n <p className=\"mt-1 whitespace-pre-wrap break-words text-sm text-text-secondary\">\n {message.originalContent}\n </p>\n </details>\n )}\n\n <BuiPermissionButton resource=\"buildmyiq-tutor\" action=\"update\" deniedBehavior=\"hide\">\n <div className=\"mt-2 flex flex-wrap gap-1.5\">\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={() => onAction({ message, action: 'ANNOTATE' })}\n >\n <MessageSquareText className=\"mr-1 h-4 w-4\" />\n {tr('buildmyiq.tutorReview.annotate', 'Annotate')}\n </Button>\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={() => onAction({ message, action: 'CORRECT' })}\n >\n <PencilLine className=\"mr-1 h-4 w-4\" />\n {tr('buildmyiq.tutorReview.correct', 'Correct')}\n </Button>\n <Button\n size=\"sm\"\n variant=\"ghost\"\n className=\"text-status-error-text hover:text-status-error-text\"\n onClick={() => onAction({ message, action: 'REDACT' })}\n >\n <EyeOff className=\"mr-1 h-4 w-4\" />\n {tr('buildmyiq.tutorReview.redact', 'Redact')}\n </Button>\n {status && (\n <Button\n size=\"sm\"\n variant=\"ghost\"\n onClick={() => onAction({ message, action: 'CLEAR' })}\n >\n <Undo2 className=\"mr-1 h-4 w-4\" />\n {tr('buildmyiq.tutorReview.clear', 'Clear review')}\n </Button>\n )}\n </div>\n </BuiPermissionButton>\n\n {history.length > 0 && (\n <div className=\"mt-2\">\n <button\n type=\"button\"\n className=\"inline-flex items-center gap-1 text-xs text-text-secondary hover:text-text-primary\"\n onClick={() => setShowHistory((v) => !v)}\n aria-expanded={showHistory}\n >\n <History className=\"h-3.5 w-3.5\" />\n {tr('buildmyiq.tutorReview.history', 'Review history ({{count}})', {\n count: history.length,\n })}\n </button>\n {showHistory && (\n <ol className=\"mt-1.5 grid gap-1.5 border-l border-border-seam pl-3\">\n {history.map((entry) => (\n <li key={entry.id} className=\"text-xs text-text-secondary\">\n <span className=\"font-medium text-text-primary\">\n {tr(`buildmyiq.tutorReview.action.${entry.action}`, ACTION_LABEL[entry.action])}\n </span>{' '}\n · {formatDateTime(entry.createdAt)} ·{' '}\n <span className=\"font-mono\" title={entry.reviewerId}>\n {entry.reviewerId.slice(0, 8)}…\n </span>\n {entry.note && <p className=\"mt-0.5 whitespace-pre-wrap\">{entry.note}</p>}\n </li>\n ))}\n </ol>\n )}\n </div>\n )}\n </div>\n );\n};\n\nconst Transcript: FC<{ session: BuiTutorSession; onAction: (target: ReviewTarget) => void }> = ({\n session,\n onAction,\n}) => {\n const tr = useTr();\n if (session.messages.length === 0) {\n return (\n <p className=\"text-sm text-text-secondary\">\n {tr('buildmyiq.tutorReview.noMessages', 'This conversation has no messages yet.')}\n </p>\n );\n }\n return (\n <ul className=\"flex flex-col gap-5\">\n {session.messages.map((message) =>\n message.role === 'ASSISTANT' ? (\n <li key={message.id} className=\"flex gap-3\">\n <span className=\"flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-[var(--color-accent-soft)]\">\n <Bot className=\"h-4 w-4 text-[var(--color-accent-teal)]\" />\n </span>\n <AnswerCard message={message} onAction={onAction} />\n </li>\n ) : (\n <li key={message.id} className=\"flex gap-3\">\n <Avatar size=\"sm\" seed={session.learnerId} name={null} />\n <div className=\"min-w-0 flex-1\">\n <p className=\"text-sm font-semibold text-text-primary\">\n {tr('buildmyiq.tutorReview.learner', 'Learner')}\n </p>\n <p className=\"mt-0.5 whitespace-pre-wrap break-words text-sm text-text-secondary\">\n {message.content}\n </p>\n </div>\n </li>\n )\n )}\n </ul>\n );\n};\n\nexport const TutorReviewPage: FC = () => {\n const tr = useTr();\n const { currentUser } = useBuildmyiq();\n const isDesktop = useMediaQuery('(min-width: 768px)');\n const pager = useCursorPager();\n const [courseId, setCourseId] = useState<string>(ALL_COURSES);\n const [activeId, setActiveId] = useState<string | null>(null);\n const [target, setTarget] = useState<ReviewTarget | null>(null);\n\n const coursesQuery = useBuiCourses({ limit: 100 });\n const sessionsQuery = useBuiTutorReviewSessions({\n courseId: courseId === ALL_COURSES ? undefined : courseId,\n cursor: pager.cursor,\n limit: PAGE_SIZE,\n });\n const rows = sessionsQuery.data?.items ?? [];\n const active = rows.find((s) => s.id === activeId) ?? null;\n\n const courseTitles = useMemo(\n () => new Map((coursesQuery.data?.items ?? []).map((c) => [c.id, c.title] as const)),\n [coursesQuery.data]\n );\n const courseOptions = [\n { value: ALL_COURSES, label: tr('buildmyiq.tutorReview.allCourses', 'All courses') },\n ...(coursesQuery.data?.items ?? []).map((c) => ({ value: c.id, label: c.title })),\n ];\n\n const learnerCell = (learnerId: string) =>\n learnerId === currentUser?.id ? (\n <div className=\"flex items-center gap-2.5\">\n <Avatar size=\"sm\" seed={learnerId} name={currentUser?.name ?? null} />\n <span className=\"font-medium text-text-primary\">{tr('common.you', 'You')}</span>\n </div>\n ) : (\n <div className=\"flex items-center gap-2.5\">\n <Avatar size=\"sm\" seed={learnerId} name={null} />\n <span className=\"font-mono text-xs text-text-tertiary\" title={learnerId}>\n {learnerId.slice(0, 8)}…\n </span>\n </div>\n );\n\n const columns: Column<BuiTutorSession>[] = [\n {\n key: 'learner',\n header: tr('buildmyiq.tutorReview.learner', 'Learner'),\n render: (s) => learnerCell(s.learnerId),\n },\n {\n key: 'course',\n header: tr('buildmyiq.tutorReview.course', 'Course'),\n render: (s) => (\n <span className=\"text-text-primary\" title={s.courseId}>\n {courseTitles.get(s.courseId) ?? `${s.courseId.slice(0, 8)}…`}\n </span>\n ),\n },\n {\n key: 'lesson',\n header: tr('buildmyiq.tutorReview.lesson', 'Lesson'),\n hideOnMobile: true,\n render: (s) => <LessonTitle courseId={s.courseId} lessonId={s.lessonId} />,\n },\n {\n key: 'answers',\n header: tr('buildmyiq.tutorReview.answers', 'Answers'),\n align: 'right',\n sortValue: (s) => summarizeSession(s).answers,\n render: (s) => <span className=\"tabular-nums\">{summarizeSession(s).answers}</span>,\n },\n {\n key: 'reviewed',\n header: tr('buildmyiq.tutorReview.reviewed', 'Reviewed'),\n align: 'right',\n sortValue: (s) => summarizeSession(s).reviewed,\n render: (s) => {\n const { reviewed } = summarizeSession(s);\n return (\n <span\n className={\n reviewed > 0\n ? 'font-medium tabular-nums text-status-success-text'\n : 'tabular-nums text-text-tertiary'\n }\n >\n {reviewed}\n </span>\n );\n },\n },\n {\n key: 'updatedAt',\n header: tr('buildmyiq.tutorReview.updated', 'Last activity'),\n hideOnMobile: true,\n sortValue: (s) => s.updatedAt,\n render: (s) => <span className=\"text-text-secondary\">{formatDateTime(s.updatedAt)}</span>,\n },\n ];\n\n return (\n <PageLayout\n title={tr('buildmyiq.tutorReview.title', 'Tutor review')}\n description={tr(\n 'buildmyiq.tutorReview.description',\n 'Read what the AI tutor told your learners, and annotate, correct or redact any answer. Learners see your reviewed version with a \"reviewed by your instructor\" marker.'\n )}\n >\n <div className=\"mb-5 max-w-sm\">\n <Field\n label={tr('buildmyiq.tutorReview.courseFilter', 'Course')}\n htmlFor=\"tutor-review-course\"\n >\n <SelectField\n id=\"tutor-review-course\"\n value={courseId}\n onChange={(value) => {\n setCourseId(value);\n pager.reset();\n }}\n options={courseOptions}\n />\n </Field>\n </div>\n\n {sessionsQuery.isLoading ? (\n <LoadingView label={tr('buildmyiq.tutorReview.loading', 'Loading tutor conversations…')} />\n ) : sessionsQuery.isError ? (\n <ErrorView\n title={tr('buildmyiq.tutorReview.loadError', \"Couldn't load tutor conversations\")}\n message={sessionsQuery.error instanceof Error ? sessionsQuery.error.message : undefined}\n onRetry={() => sessionsQuery.refetch()}\n />\n ) : rows.length === 0 ? (\n <EmptyState\n icon={<Bot className=\"h-10 w-10\" />}\n title={tr('buildmyiq.tutorReview.empty', 'No tutor conversations yet')}\n description={tr(\n 'buildmyiq.tutorReview.emptyDescription',\n 'Conversations appear here once learners ask the AI tutor about a lesson.'\n )}\n />\n ) : (\n <div className=\"flex flex-col gap-4\">\n <DataTable\n columns={columns}\n rows={rows}\n rowKey={(s) => s.id}\n onRowClick={(s) => setActiveId(s.id)}\n defaultSort={{ key: 'updatedAt', dir: 'desc' }}\n />\n <Pagination\n page={pager.page}\n pageSize={PAGE_SIZE}\n itemCount={rows.length}\n hasMore={sessionsQuery.data?.pageInfo?.hasMore ?? false}\n onPageChange={(page) => pager.onPageChange(page, sessionsQuery.data?.pageInfo?.cursor)}\n />\n </div>\n )}\n\n {/*\n The transcript sheet steps aside while a review dialog is open and\n comes back (with the refreshed transcript) when it closes. Two modal\n layers must not be stacked: the sheet overlay sits above the desktop\n dialog, so it would swallow every click on the dialog's buttons, and\n two focus traps would fight over keyboard focus.\n */}\n <Sheet\n open={active != null && target == null}\n onOpenChange={(open) => {\n if (!open && target == null) setActiveId(null);\n }}\n >\n <SheetContent\n side={isDesktop ? 'right' : 'bottom'}\n className={\n isDesktop\n ? 'flex w-full max-w-none flex-col gap-0 p-0 sm:max-w-xl'\n : 'flex max-h-[92vh] w-full flex-col gap-0 rounded-t-2xl p-0'\n }\n >\n {active && (\n <>\n <SheetHeader className=\"border-b border-border-seam p-5 pr-12\">\n <SheetTitle>\n {courseTitles.get(active.courseId) ??\n tr('buildmyiq.tutorReview.conversation', 'Tutor conversation')}\n </SheetTitle>\n <SheetDescription>\n <LessonTitle courseId={active.courseId} lessonId={active.lessonId} /> ·{' '}\n {formatDateTime(active.updatedAt)}\n </SheetDescription>\n </SheetHeader>\n <div\n className=\"flex-1 overflow-y-auto px-5 py-4\"\n data-testid=\"tutor-review-transcript\"\n >\n <Transcript session={active} onAction={setTarget} />\n </div>\n </>\n )}\n </SheetContent>\n </Sheet>\n\n {target && (\n <ReviewDialog\n key={`${target.message.id}:${target.action}`}\n target={target}\n onClose={() => setTarget(null)}\n />\n )}\n </PageLayout>\n );\n};\n"],"mappings":"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"}
|