diff --git a/app/page.tsx b/app/page.tsx
index 63df21c..92aa59e 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -7,7 +7,7 @@ import PromptInput from '@/components/PromptInput'
import { ComponentErrorBoundary } from '@/components/ErrorBoundary'
import { Button } from '@/components/ui/button'
import { Badge } from '@/components/ui/badge'
-import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
+import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog'
import { AlertCircle, CheckCircle, FileText, Layers, List, MessageSquare, Play, Loader2, Download, Key, Brain, Check, X } from 'lucide-react'
import { useSpecWorkflow } from '@/hooks/useSpecWorkflow'
@@ -17,8 +17,61 @@ import { useSimpleApiKeyStorage } from '@/hooks/useSimpleApiKeyStorage'
import { useModelStorage, usePromptStorage, useContextFilesStorage } from '@/hooks/useSessionStorage'
import { DotPattern } from '@/components/magicui/dot-pattern'
+// Helper function to extract Mermaid diagrams with proper naming
+function extractMermaidDiagrams(content: string): { name: string; content: string }[] {
+ const mermaidRegex = /```mermaid\n([\s\S]*?)\n```/g
+ const diagrams: { name: string; content: string }[] = []
+ let match
+
+ while ((match = mermaidRegex.exec(content)) !== null) {
+ const diagramContent = match[1].trim()
+ let diagramName = 'diagram'
+
+ // Determine diagram type based on content
+ if (diagramContent.includes('graph') || diagramContent.includes('flowchart')) {
+ if (diagramContent.includes('user') || diagramContent.includes('User')) {
+ diagramName = 'user-flow'
+ } else if (diagramContent.includes('admin') || diagramContent.includes('Admin')) {
+ diagramName = 'admin-flow'
+ } else if (diagramContent.includes('system') || diagramContent.includes('System')) {
+ diagramName = 'system-flow'
+ } else if (diagramContent.includes('data') || diagramContent.includes('Data')) {
+ diagramName = 'data-flow'
+ } else if (diagramContent.includes('sequence') || diagramContent.includes('Sequence')) {
+ diagramName = 'sequence-diagram'
+ } else if (diagramContent.includes('class') || diagramContent.includes('Class')) {
+ diagramName = 'class-diagram'
+ } else if (diagramContent.includes('component') || diagramContent.includes('Component')) {
+ diagramName = 'component-diagram'
+ } else {
+ diagramName = 'flowchart'
+ }
+ } else if (diagramContent.includes('erDiagram') || diagramContent.includes('ER')) {
+ diagramName = 'entity-relationship'
+ } else if (diagramContent.includes('gantt') || diagramContent.includes('Gantt')) {
+ diagramName = 'gantt-chart'
+ } else if (diagramContent.includes('journey') || diagramContent.includes('Journey')) {
+ diagramName = 'user-journey'
+ } else if (diagramContent.includes('pie') || diagramContent.includes('Pie')) {
+ diagramName = 'pie-chart'
+ } else if (diagramContent.includes('git') || diagramContent.includes('Git')) {
+ diagramName = 'git-graph'
+ }
+
+ // If we have multiple diagrams of the same type, add a suffix
+ const existingCount = diagrams.filter(d => d.name.startsWith(diagramName)).length
+ if (existingCount > 0) {
+ diagramName = `${diagramName}-${existingCount + 1}`
+ }
+
+ diagrams.push({ name: diagramName, content: diagramContent })
+ }
+
+ return diagrams
+}
+
export default function Home() {
- const { value: apiKey, hasValidKey, setAPIKey, clearAPIKey } = useSimpleApiKeyStorage()
+ const { value: apiKey, hasValidKey, clearAPIKey } = useSimpleApiKeyStorage()
const { selectedModel, setModel, clearModel } = useModelStorage()
const { prompt, setPrompt, clearPrompt } = usePromptStorage()
@@ -40,6 +93,7 @@ export default function Home() {
const [justDidReset, setJustDidReset] = useState(false) // Track if we just did a reset
const [expandedSpecs, setExpandedSpecs] = useState<{[key: string]: boolean}>({}) // Track expanded specifications
const [showNewProjectNotification, setShowNewProjectNotification] = useState(false) // Track new project started
+ const [activeTab, setActiveTab] = useState<'documents' | 'diagrams'>('documents') // Track active tab in results
const hasApiKey = Boolean(apiKey && hasValidKey)
const hasModel = Boolean(selectedModel)
@@ -1114,9 +1168,40 @@ export default function Home() {
{/* Generated Content Previews */}
-
Generated Specifications
+
+
Generated Specifications
+
+ {/* Tab Toggle */}
+
+
+
+
+
- {(['requirements', 'design', 'tasks'] as const).map((phase) => {
+ {/* Documents Tab */}
+ {activeTab === 'documents' && (
+ <>
+ {(['requirements', 'design', 'tasks'] as const).map((phase) => {
const content = workflow.state[phase]
if (!content) return null
@@ -1156,7 +1241,79 @@ export default function Home() {
)
- })}
+ })}
+ >
+ )}
+
+ {/* Diagrams Tab */}
+ {activeTab === 'diagrams' && (
+ <>
+ {(() => {
+ // Extract diagrams from all phases
+ const allDiagrams: { name: string; content: string; source: string }[] = []
+
+ // Check each phase for diagrams
+ if (workflow.state.requirements) {
+ const reqDiagrams = extractMermaidDiagrams(workflow.state.requirements)
+ reqDiagrams.forEach(d => allDiagrams.push({ ...d, source: 'requirements' }))
+ }
+
+ if (workflow.state.design) {
+ const designDiagrams = extractMermaidDiagrams(workflow.state.design)
+ designDiagrams.forEach(d => allDiagrams.push({ ...d, source: 'design' }))
+ }
+
+ if (workflow.state.tasks) {
+ const taskDiagrams = extractMermaidDiagrams(workflow.state.tasks)
+ taskDiagrams.forEach(d => allDiagrams.push({ ...d, source: 'tasks' }))
+ }
+
+ if (allDiagrams.length === 0) {
+ return (
+
+
+
No Diagrams Generated
+
+ No Mermaid diagrams were found in the generated specifications.
+
+
+ )
+ }
+
+ return allDiagrams.map((diagram, index) => {
+ const isExpanded = expandedSpecs[`diagram-${index}`] || false
+
+ return (
+
+
+
+
+ {diagram.name.replace(/-/g, ' ')}
+
+ from {diagram.source}
+
+
+
+
+
+
+
+
+
+ )
+ })
+ })()}
+ >
+ )}
{/* Debug: Add dummy data for testing */}
diff --git a/lib/exportUtils.ts b/lib/exportUtils.ts
index 5d7fb2e..541f332 100644
--- a/lib/exportUtils.ts
+++ b/lib/exportUtils.ts
@@ -61,15 +61,55 @@ function extractProjectNameFromTasks(tasks: string, fallbackFeatureName: string)
}
/**
- * Extract Mermaid diagram code blocks from markdown content
+ * Extract Mermaid diagram code blocks from markdown content with proper naming
*/
-function extractMermaidDiagrams(content: string): string[] {
+function extractMermaidDiagrams(content: string): { name: string; content: string }[] {
const mermaidRegex = /```mermaid\n([\s\S]*?)\n```/g
- const diagrams: string[] = []
+ const diagrams: { name: string; content: string }[] = []
let match
while ((match = mermaidRegex.exec(content)) !== null) {
- diagrams.push(match[1].trim())
+ const diagramContent = match[1].trim()
+ let diagramName = 'diagram'
+
+ // Determine diagram type based on content
+ if (diagramContent.includes('graph') || diagramContent.includes('flowchart')) {
+ if (diagramContent.includes('user') || diagramContent.includes('User')) {
+ diagramName = 'user-flow'
+ } else if (diagramContent.includes('admin') || diagramContent.includes('Admin')) {
+ diagramName = 'admin-flow'
+ } else if (diagramContent.includes('system') || diagramContent.includes('System')) {
+ diagramName = 'system-flow'
+ } else if (diagramContent.includes('data') || diagramContent.includes('Data')) {
+ diagramName = 'data-flow'
+ } else if (diagramContent.includes('sequence') || diagramContent.includes('Sequence')) {
+ diagramName = 'sequence-diagram'
+ } else if (diagramContent.includes('class') || diagramContent.includes('Class')) {
+ diagramName = 'class-diagram'
+ } else if (diagramContent.includes('component') || diagramContent.includes('Component')) {
+ diagramName = 'component-diagram'
+ } else {
+ diagramName = 'flowchart'
+ }
+ } else if (diagramContent.includes('erDiagram') || diagramContent.includes('ER')) {
+ diagramName = 'entity-relationship'
+ } else if (diagramContent.includes('gantt') || diagramContent.includes('Gantt')) {
+ diagramName = 'gantt-chart'
+ } else if (diagramContent.includes('journey') || diagramContent.includes('Journey')) {
+ diagramName = 'user-journey'
+ } else if (diagramContent.includes('pie') || diagramContent.includes('Pie')) {
+ diagramName = 'pie-chart'
+ } else if (diagramContent.includes('git') || diagramContent.includes('Git')) {
+ diagramName = 'git-graph'
+ }
+
+ // If we have multiple diagrams of the same type, add a suffix
+ const existingCount = diagrams.filter(d => d.name.startsWith(diagramName)).length
+ if (existingCount > 0) {
+ diagramName = `${diagramName}-${existingCount + 1}`
+ }
+
+ diagrams.push({ name: diagramName, content: diagramContent })
}
return diagrams
@@ -89,17 +129,12 @@ export async function createSpecificationZip(data: SpecificationData): Promise 0) {
- // If multiple diagrams, create numbered files
- if (mermaidDiagrams.length === 1) {
- zip.file('architecture.mermaid', mermaidDiagrams[0])
- } else {
- mermaidDiagrams.forEach((diagram, index) => {
- zip.file(`diagram-${index + 1}.mermaid`, diagram)
- })
- }
+ mermaidDiagrams.forEach((diagram) => {
+ zip.file(`${diagram.name}.mermaid`, diagram.content)
+ })
}
}
@@ -138,7 +173,7 @@ Generated by OpenSpec on ${new Date().toLocaleDateString()}
- **requirements.md** - Functional requirements and user stories
- **design.md** - Technical design and architecture
- **tasks.md** - Implementation tasks breakdown
-- **architecture.mermaid** - System architecture diagrams (if generated)
+- **{diagram-name}.mermaid** - Diagram files with descriptive names (e.g., user-flow.mermaid, system-architecture.mermaid)
- **metadata.json** - Generation metadata and statistics
## Usage