"use client";
import type { ElementType, ReactNode } from "react";
import ReactMarkdown, { type Components } from "react-markdown";
import rehypeKatex from "rehype-katex";
import remarkGfm from "remark-gfm";
import remarkMath from "remark-math";
import { AlertTriangle, CheckCircle2, Quote } from "lucide-react";
import { AnswerTrustFooter, EvidenceBadge } from "@/components/dashboard/EvidenceBadge";
import type { BrainAskCitation } from "@/lib/api/types";
import type { StudyIntent } from "@/lib/study-intents";
import { mapSuggestedActionToIntent } from "./study-brain-context";
import type { StudyCardMeta } from "./types";
export const markdownComponents: Components = {
p: ({ children }) =>
{children}
,
ul: ({ children }) =>
{children}
,
ol: ({ children }) => {children},
li: ({ children }) =>
{children}
,
strong: ({ children }) => {children},
em: ({ children }) => {children},
a: ({ children, href }) => (
{children}
),
code: ({ children }) => (
{children}
),
};
export function Prose({ children }: { children: unknown }) {
// Defensive: AI/backend fields are sometimes numbers, arrays, or objects even
// when typed as string. Coerce so a non-string never crashes the whole card
// tree with "x.trim is not a function".
const text = typeof children === "string" ? children.trim() : children == null ? "" : String(children).trim();
if (!text) return null;
return (
{text}
);
}
export function Section({ title, children }: { title: unknown; children: ReactNode }) {
const safeTitle = typeof title === "string" ? title : title == null ? "" : String(title);
return (
{safeTitle}
{children}
);
}
export function BulletList({ items, limit = 8 }: { items: ReadonlyArray; limit?: number }) {
// Coerce each item to a string first — backend list fields occasionally
// contain numbers/objects, and item.trim() on those crashes the card tree.
const clean = (items ?? [])
.map((item) => (typeof item === "string" ? item : item == null ? "" : String(item)))
.filter((item) => item.trim().length > 0)
.slice(0, limit);
if (clean.length === 0) return
DocDoe will add this when the source has enough detail.
;
return (
{clean.map((item, index) => (
{item}
))}
);
}
export function TrustFooter({ meta }: { meta?: StudyCardMeta }) {
if (!meta?.evidenceLabel && !meta?.trustNote && !meta?.isFallback) {
return ;
}
return (
{meta?.isFallback ? (
Degraded answer — the AI pipeline used a fallback. Retry or attach a clearer source.