fix: render the assistant answer as markdown - #745
Conversation
The final natural-language answer is written by the model in markdown, but it was rendered as plain text, so readers saw literal `**bold**` markers and `-` bullets, and paragraph structure was lost. Render only that message through react-markdown (remark-gfm for tables and strikethrough, remark-breaks to keep the single-newline behaviour the old `whitespace-pre-line` gave). The generated SQL and the results table are untouched — they must stay verbatim. Raw HTML is not enabled, so model output cannot inject markup. Closes #310 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
This PR was not deployed automatically as @Anchel123 does not have access to the Railway project. In order to get automatic PR deploys, please add @Anchel123 to your workspace on Railway. |
Dependency ReviewThe following issues were found:
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
Included review availability: Your plan provides up to 4 included reviews per hour; 1 remains after this review. 📝 WalkthroughWalkthroughAI chat messages now render Markdown with GitHub Flavored Markdown, preserved line breaks, custom styling, and image placeholders. ChangesMarkdown chat rendering
Estimated code review effort: 2 (Simple) | ~10 minutes Merge Risk: 🔵 Low · up to The change makes links in AI-generated answers clickable, creating a bounded risk of directing users to deceptive external destinations. Existing protections limit the impact, so the PR is mergeable with owner awareness or follow-up to enforce an explicit link policy. 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Full details: Docstring CoverageExplanation No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 1 files. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
This PR fixes chat readability by rendering only the assistant’s natural-language response as Markdown (GFM + single-newline breaks) while leaving SQL and result-table rendering unchanged.
Changes:
- Render
type === 'ai'message content viareact-markdownwithremark-gfmandremark-breaks. - Add a local
componentsmap to style common Markdown elements using existing theme tokens. - Add frontend dependencies:
react-markdown,remark-gfm, andremark-breaks.
Reviewed changes
Copilot reviewed 2 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| app/src/components/chat/ChatMessage.tsx | Renders assistant messages as Markdown with custom-styled elements. |
| app/package.json | Adds Markdown rendering dependencies to the frontend app. |
| app/package-lock.json | Locks newly added Markdown-related packages and transitive deps. |
Files not reviewed (1)
- app/package-lock.json: Generated file
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
The repo root depends on the app via `file:app`, so its lockfile inlines the app's tree. Updating only `app/package-lock.json` left `npm ci` at the root out of sync and failed the test job. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 4 changed files in this pull request and generated 1 comment.
Files not reviewed (1)
- app/package-lock.json: Generated file
Suppressed comments (1)
app/src/components/chat/ChatMessage.tsx:68
codeblocks are detected viaclassName?.startsWith('language-'), which misclassifies fenced code blocks that omit a language (withoutlang). Those blocks will get the inline-code styling (bg/padding) even though they render inside<pre>. Use react-markdown’sinlineflag to distinguish inline vs block code, and keep any existingclassNameon block code for future highlighting.
code: ({ className, children }) => {
const isBlock = Boolean(className?.startsWith('language-'));
if (isBlock) {
return <code className="font-mono text-sm">{children}</code>;
}
Two review findings on the markdown renderer: * The inline-vs-block check keyed off `language-*`, so a fence opened without a language got the inline chip styling inside its own frame. Style `code` once and cancel it for descendants of `pre` instead. * Markdown image syntax rendered a real `<img>`, so the answer could make the browser fetch an arbitrary URL chosen by the model. Render the alt text instead. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 4 changed files in this pull request and generated 1 comment.
Files not reviewed (1)
- app/package-lock.json: Generated file
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
app/src/components/chat/ChatMessage.tsx:63
arenderer passeshrefthrough directly even when it’s undefined (allowed by thereact-markdowntypes). This can produce non-link<a>elements withtarget/rel, which is confusing for keyboard users and can surface as invalid markup when the model emits[]()or other malformed links. Guardhrefand render a non-link fallback when missing.
a: ({ children, href }) => (
<a href={href} target="_blank" rel="noopener noreferrer" className="text-primary underline underline-offset-2">
{children}
</a>
),
react-markdown puts the fence's `language-*` marker on `code`; dropping it threw away the only hint about the language. Merge it with the local styling instead. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Closes #310
Problem
The final natural-language answer is written by the model in markdown, but the chat rendered it as plain text. Users saw literal
**bold**markers and-bullets instead of formatting, and the structure of multi-section answers was hard to read.Change
Render only the
aimessage (the string answer) throughreact-markdown:remark-gfm— tables, strikethrough, autolinks.remark-breaks— keeps single newlines as line breaks, matching thewhitespace-pre-linebehaviour it replaces.componentsmap using the existing theme tokens, so no Tailwind typography plugin wiring is needed.The generated SQL block and the query-results table are deliberately untouched — those must stay verbatim.
Security
Raw HTML is not enabled (no
rehype-raw), so model output cannot inject markup.Verification
npx tsc --noEmit— no new errors.npm run lint— clean for the touched file.npm run build— succeeds.**London**becomes<strong>,-lines become a real list.Summary by CodeRabbit