mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-04 21:23:07 +00:00
feat(markdown): integrate Markdown component for enhanced text rendering
- Added a reusable Markdown component to render markdown content with styled typography for dark mode. - Updated KanbanCard to utilize the new Markdown component for displaying feature summaries, improving readability and presentation. - Included the `react-markdown` library as a dependency for markdown parsing. Modified: package.json, package-lock.json, markdown.tsx, kanban-card.tsx
This commit is contained in:
1168
app/package-lock.json
generated
1168
app/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -34,6 +34,7 @@
|
|||||||
"next": "16.0.7",
|
"next": "16.0.7",
|
||||||
"react": "19.2.0",
|
"react": "19.2.0",
|
||||||
"react-dom": "19.2.0",
|
"react-dom": "19.2.0",
|
||||||
|
"react-markdown": "^10.1.0",
|
||||||
"sonner": "^2.0.7",
|
"sonner": "^2.0.7",
|
||||||
"tailwind-merge": "^3.4.0",
|
"tailwind-merge": "^3.4.0",
|
||||||
"zustand": "^5.0.9"
|
"zustand": "^5.0.9"
|
||||||
|
|||||||
48
app/src/components/ui/markdown.tsx
Normal file
48
app/src/components/ui/markdown.tsx
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import ReactMarkdown from "react-markdown";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
interface MarkdownProps {
|
||||||
|
children: string;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reusable Markdown component for rendering markdown content
|
||||||
|
* Styled for dark mode with proper typography
|
||||||
|
*/
|
||||||
|
export function Markdown({ children, className }: MarkdownProps) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"prose prose-sm prose-invert max-w-none",
|
||||||
|
// Headings
|
||||||
|
"[&_h1]:text-xl [&_h1]:text-zinc-200 [&_h1]:font-semibold [&_h1]:mt-4 [&_h1]:mb-2",
|
||||||
|
"[&_h2]:text-lg [&_h2]:text-zinc-200 [&_h2]:font-semibold [&_h2]:mt-4 [&_h2]:mb-2",
|
||||||
|
"[&_h3]:text-base [&_h3]:text-zinc-200 [&_h3]:font-semibold [&_h3]:mt-3 [&_h3]:mb-2",
|
||||||
|
"[&_h4]:text-sm [&_h4]:text-zinc-200 [&_h4]:font-semibold [&_h4]:mt-2 [&_h4]:mb-1",
|
||||||
|
// Paragraphs
|
||||||
|
"[&_p]:text-zinc-300 [&_p]:leading-relaxed [&_p]:my-2",
|
||||||
|
// Lists
|
||||||
|
"[&_ul]:my-2 [&_ul]:pl-4 [&_ol]:my-2 [&_ol]:pl-4",
|
||||||
|
"[&_li]:text-zinc-300 [&_li]:my-0.5",
|
||||||
|
// Code
|
||||||
|
"[&_code]:text-cyan-400 [&_code]:bg-zinc-800/50 [&_code]:px-1.5 [&_code]:py-0.5 [&_code]:rounded [&_code]:text-sm",
|
||||||
|
"[&_pre]:bg-zinc-900/80 [&_pre]:border [&_pre]:border-white/10 [&_pre]:rounded-lg [&_pre]:my-2 [&_pre]:p-3 [&_pre]:overflow-x-auto",
|
||||||
|
"[&_pre_code]:bg-transparent [&_pre_code]:p-0",
|
||||||
|
// Strong/Bold
|
||||||
|
"[&_strong]:text-zinc-200 [&_strong]:font-semibold",
|
||||||
|
// Links
|
||||||
|
"[&_a]:text-blue-400 [&_a]:no-underline hover:[&_a]:underline",
|
||||||
|
// Blockquotes
|
||||||
|
"[&_blockquote]:border-l-2 [&_blockquote]:border-zinc-600 [&_blockquote]:pl-4 [&_blockquote]:text-zinc-400 [&_blockquote]:italic [&_blockquote]:my-2",
|
||||||
|
// Horizontal rules
|
||||||
|
"[&_hr]:border-zinc-700 [&_hr]:my-4",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<ReactMarkdown>{children}</ReactMarkdown>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -50,6 +50,7 @@ import {
|
|||||||
formatModelName,
|
formatModelName,
|
||||||
DEFAULT_MODEL,
|
DEFAULT_MODEL,
|
||||||
} from "@/lib/agent-context-parser";
|
} from "@/lib/agent-context-parser";
|
||||||
|
import { Markdown } from "@/components/ui/markdown";
|
||||||
|
|
||||||
interface KanbanCardProps {
|
interface KanbanCardProps {
|
||||||
feature: Feature;
|
feature: Feature;
|
||||||
@@ -674,7 +675,7 @@ export function KanbanCard({
|
|||||||
{/* Summary Modal */}
|
{/* Summary Modal */}
|
||||||
<Dialog open={isSummaryDialogOpen} onOpenChange={setIsSummaryDialogOpen}>
|
<Dialog open={isSummaryDialogOpen} onOpenChange={setIsSummaryDialogOpen}>
|
||||||
<DialogContent
|
<DialogContent
|
||||||
className="max-w-2xl max-h-[80vh] overflow-hidden flex flex-col"
|
className="max-w-4xl max-h-[80vh] overflow-hidden flex flex-col"
|
||||||
data-testid={`summary-dialog-${feature.id}`}
|
data-testid={`summary-dialog-${feature.id}`}
|
||||||
>
|
>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
@@ -682,16 +683,16 @@ export function KanbanCard({
|
|||||||
<Sparkles className="w-5 h-5 text-green-400" />
|
<Sparkles className="w-5 h-5 text-green-400" />
|
||||||
Implementation Summary
|
Implementation Summary
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogDescription className="text-sm">
|
<DialogDescription className="text-sm" title={feature.description}>
|
||||||
{feature.description}
|
{feature.description.length > 100
|
||||||
|
? `${feature.description.slice(0, 100)}...`
|
||||||
|
: feature.description}
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="flex-1 overflow-y-auto">
|
<div className="flex-1 overflow-y-auto p-4 bg-zinc-900/50 rounded-lg border border-white/10">
|
||||||
<div className="prose prose-sm prose-invert max-w-none">
|
<Markdown>
|
||||||
<pre className="whitespace-pre-wrap text-sm text-zinc-300 bg-zinc-900/50 p-4 rounded-lg border border-white/10">
|
{feature.summary || summary || agentInfo?.summary || "No summary available"}
|
||||||
{feature.summary || summary || agentInfo?.summary || "No summary available"}
|
</Markdown>
|
||||||
</pre>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -132,20 +132,21 @@ function getCurrentPhase(content: string): "planning" | "action" | "verification
|
|||||||
* Extracts a summary from completed feature context
|
* Extracts a summary from completed feature context
|
||||||
*/
|
*/
|
||||||
function extractSummary(content: string): string | undefined {
|
function extractSummary(content: string): string | undefined {
|
||||||
|
// Look for summary sections - capture everything including subsections (###)
|
||||||
|
// Stop at same-level ## sections (but not ###), or tool markers, or end
|
||||||
|
const summaryMatch = content.match(/## Summary[^\n]*\n([\s\S]*?)(?=\n## [^#]|\n🔧|$)/i);
|
||||||
|
if (summaryMatch) {
|
||||||
|
return summaryMatch[1].trim();
|
||||||
|
}
|
||||||
|
|
||||||
// Look for completion markers and extract surrounding text
|
// Look for completion markers and extract surrounding text
|
||||||
const completionMatch = content.match(/✓ (?:Feature|Verification|Task) (?:successfully|completed|verified)[^\n]*(?:\n[^\n]{1,200})?/i);
|
const completionMatch = content.match(/✓ (?:Feature|Verification|Task) (?:successfully|completed|verified)[^\n]*(?:\n[^\n]{1,200})?/i);
|
||||||
if (completionMatch) {
|
if (completionMatch) {
|
||||||
return completionMatch[0].trim();
|
return completionMatch[0].trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Look for summary sections
|
|
||||||
const summaryMatch = content.match(/## Summary[^\n]*\n([\s\S]{1,500}?)(?=\n##|\n🔧|$)/i);
|
|
||||||
if (summaryMatch) {
|
|
||||||
return summaryMatch[1].trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Look for "What was done" type sections
|
// Look for "What was done" type sections
|
||||||
const whatWasDoneMatch = content.match(/(?:What was done|Changes made|Implemented)[^\n]*\n([\s\S]{1,500}?)(?=\n##|\n🔧|$)/i);
|
const whatWasDoneMatch = content.match(/(?:What was done|Changes made|Implemented)[^\n]*\n([\s\S]*?)(?=\n## [^#]|\n🔧|$)/i);
|
||||||
if (whatWasDoneMatch) {
|
if (whatWasDoneMatch) {
|
||||||
return whatWasDoneMatch[1].trim();
|
return whatWasDoneMatch[1].trim();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user