Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
432 changes: 432 additions & 0 deletions EPIC_004_FRONTEND_SUMMARY.md

Large diffs are not rendered by default.

213 changes: 213 additions & 0 deletions app/e2e/observability.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
import { test, expect } from '@playwright/test';

/**
* E2E Tests for LangSmith Trace Display (Epic 004: Observability)
*
* Tests the frontend display of LangSmith trace links and metadata
* in the generation preview page.
*/

test.describe('LangSmith Trace Display', () => {
test.beforeEach(async ({ page }) => {
// Start at the home page
await page.goto('/');
});

test('displays observability section when generation completes', async ({ page }) => {
// NOTE: This test verifies the UI structure is present
// Backend trace URL generation is tested separately

// Mock the generation API response to include trace data
await page.route('**/api/v1/generation/generate', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
code: {
component: 'export function TestButton() { return <button>Test</button>; }',
stories: 'export default { title: "TestButton" };',
},
metadata: {
pattern_used: 'shadcn-button',
pattern_version: '1.0.0',
tokens_applied: 5,
requirements_implemented: 3,
lines_of_code: 50,
imports_count: 2,
has_typescript_errors: false,
has_accessibility_warnings: false,
llm_token_usage: {
prompt_tokens: 500,
completion_tokens: 750,
total_tokens: 1250,
},
trace_url: 'https://smith.langchain.com/o/default/projects/p/test/r/abc123',
session_id: 'test-session-123',
},
timing: {
total_ms: 5000,
llm_generating_ms: 3000,
validating_ms: 1500,
post_processing_ms: 500,
},
provenance: {
pattern_id: 'shadcn-button',
pattern_version: '1.0.0',
generated_at: new Date().toISOString(),
tokens_hash: 'abc123',
requirements_hash: 'def456',
},
status: 'completed',
}),
});
});

// Navigate through the workflow to trigger generation
// (In a real E2E test, you'd go through the full workflow)
// For now, we'll test the component rendering directly by navigating to preview

// Skip the full workflow and test the preview page structure
// This assumes the page can handle missing workflow state gracefully
await page.goto('/preview');

// Wait for the page to potentially show an error or redirect
await page.waitForTimeout(1000);
});

test('displays trace link when trace_url is provided', async ({ page }) => {
// This test would be part of a full workflow E2E test
// Testing the component in isolation via Storybook or component tests
// is more practical for unit-level checks

// For now, document what should be tested:
// 1. Trace link appears with correct URL
// 2. Link opens in new tab
// 3. Tooltip shows session ID
// 4. External link icon is visible
});

test('handles missing trace URL gracefully', async ({ page }) => {
// Mock response without trace_url
await page.route('**/api/v1/generation/generate', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
code: {
component: 'export function TestButton() { return <button>Test</button>; }',
stories: 'export default { title: "TestButton" };',
},
metadata: {
pattern_used: 'shadcn-button',
pattern_version: '1.0.0',
tokens_applied: 5,
requirements_implemented: 3,
lines_of_code: 50,
imports_count: 2,
has_typescript_errors: false,
has_accessibility_warnings: false,
// No trace_url or session_id
},
timing: {
total_ms: 5000,
},
provenance: {
pattern_id: 'shadcn-button',
pattern_version: '1.0.0',
generated_at: new Date().toISOString(),
tokens_hash: 'abc123',
requirements_hash: 'def456',
},
status: 'completed',
}),
});
});

// Navigate to preview page
await page.goto('/preview');
await page.waitForTimeout(1000);

// Verify fallback message is shown when trace URL is missing
// In a real test, we'd check for the "Trace link will appear here..." message
});

test('displays generation metadata (latency, tokens, cost)', async ({ page }) => {
// Mock response with full metadata
await page.route('**/api/v1/generation/generate', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
code: {
component: 'export function TestButton() { return <button>Test</button>; }',
stories: 'export default { title: "TestButton" };',
},
metadata: {
pattern_used: 'shadcn-button',
pattern_version: '1.0.0',
tokens_applied: 5,
requirements_implemented: 3,
lines_of_code: 50,
imports_count: 2,
has_typescript_errors: false,
has_accessibility_warnings: false,
llm_token_usage: {
prompt_tokens: 500,
completion_tokens: 750,
total_tokens: 1250,
},
trace_url: 'https://smith.langchain.com/trace/abc123',
session_id: 'session-123',
},
timing: {
total_ms: 5000,
llm_generating_ms: 3000,
validating_ms: 1500,
post_processing_ms: 500,
},
provenance: {
pattern_id: 'shadcn-button',
pattern_version: '1.0.0',
generated_at: new Date().toISOString(),
tokens_hash: 'abc123',
requirements_hash: 'def456',
},
status: 'completed',
}),
});
});

// Navigate to preview
await page.goto('/preview');
await page.waitForTimeout(1000);

// In a real test, we would verify:
// - Latency is displayed (5.0s)
// - Token count is displayed (1,250)
// - Token breakdown shows prompt and completion tokens
// - Stage breakdown shows llm_generating, validating, post_processing
});

test('displays stage breakdown with progress bars', async ({ page }) => {
// Test that stage latencies are visualized with progress bars
// This would check for the presence of progress indicators
// showing relative time spent in each stage
});
});

/**
* NOTE: These E2E tests are currently placeholders that document
* the expected behavior. Full E2E testing requires:
*
* 1. Complete workflow state setup (tokens, requirements, patterns)
* 2. Backend API mocking or test environment
* 3. More sophisticated page interactions
*
* The component-level unit tests provide better coverage for
* the observability components themselves.
*
* For full integration testing, consider:
* - Running against a test backend with LangSmith configured
* - Using fixtures to set up complete workflow state
* - Testing the full flow from upload → extract → requirements → patterns → preview
*/
45 changes: 43 additions & 2 deletions app/src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
"use client";

import Link from "next/link";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { MetricCard } from "@/components/composite/MetricCard";
import { useWorkflowStore } from "@/stores/useWorkflowStore";
import { WorkflowStep } from "@/types";
import { Sparkles, Palette, Component, RotateCcw } from "lucide-react";
import { Sparkles, Palette, Component, RotateCcw, ExternalLink } from "lucide-react";

export default function Dashboard() {
const completedSteps = useWorkflowStore((state) => state.completedSteps);
Expand Down Expand Up @@ -91,6 +91,47 @@ export default function Dashboard() {
</div>
</CardContent>
</Card>

{/* AI Observability Card (Epic 004) */}
<Card>
<CardHeader>
<CardTitle>AI Observability</CardTitle>
<CardDescription>
Monitor AI operations, token usage, and performance metrics
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<p className="text-sm text-muted-foreground">
View detailed traces of AI operations, including:
</p>
<ul className="text-sm text-muted-foreground space-y-1 list-disc list-inside ml-2">
<li>Token extraction with GPT-4V</li>
<li>Requirement classification and proposals</li>
<li>Code generation workflows</li>
<li>Token usage and cost tracking</li>
<li>Performance metrics and latency</li>
</ul>
</div>

<Button asChild variant="outline" size="lg" className="w-full sm:w-auto">
<a
href={`https://smith.langchain.com/o/default/projects/p/${process.env.NEXT_PUBLIC_LANGSMITH_PROJECT || 'component-forge'}`}
target="_blank"
rel="noopener noreferrer"
className="gap-2"
>
<ExternalLink className="h-4 w-4" />
Open LangSmith Dashboard
</a>
</Button>

<p className="text-xs text-muted-foreground">
Note: LangSmith traces appear after AI operations are performed.
Each generation result includes a direct link to its trace.
</p>
</CardContent>
</Card>
</main>
);
}
50 changes: 50 additions & 0 deletions app/src/app/preview/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import { GenerationProgress } from "@/components/composite/GenerationProgress";
import { ValidationErrorsDisplay } from "@/components/preview/ValidationErrorsDisplay";
import { QualityScoresDisplay } from "@/components/preview/QualityScoresDisplay";
import { SecurityIssuesPanel } from "@/components/preview/SecurityIssuesPanel";
import { LangSmithTraceLink } from "@/components/observability/LangSmithTraceLink";
import { GenerationMetadataDisplay } from "@/components/observability/GenerationMetadataDisplay";
import { useWorkflowStore } from "@/stores/useWorkflowStore";
import { useTokenStore } from "@/stores/useTokenStore";
import { usePatternSelection } from "@/store/patternSelectionStore";
Expand Down Expand Up @@ -348,6 +350,54 @@ export default function PreviewPage() {
</div>
)}

{/* Observability Section - Trace Link & Metadata (Epic 004) */}
{isComplete && metadata && (
<div className="grid grid-cols-1 lg:grid-cols-3 gap-4">
{/* LangSmith Trace Link */}
<Card className="lg:col-span-1">
<CardHeader>
<CardTitle className="text-sm font-semibold">AI Observability</CardTitle>
</CardHeader>
<CardContent className="space-y-3">
<p className="text-xs text-muted-foreground">
View detailed AI operation logs and metrics in LangSmith
</p>
<LangSmithTraceLink
traceUrl={metadata.trace_url}
sessionId={metadata.session_id}
variant="outline"
size="default"
className="w-full"
/>
{!metadata.trace_url && (
<p className="text-xs text-muted-foreground italic">
Trace link will appear here when LangSmith is configured
</p>
)}
</CardContent>
</Card>

{/* Generation Metadata Display */}
<div className="lg:col-span-2">
<GenerationMetadataDisplay
metadata={{
latency_ms: timing?.total_ms,
stage_latencies: timing?.llm_generating_ms && timing?.validating_ms && timing?.post_processing_ms
? {
llm_generating: timing.llm_generating_ms,
validating: timing.validating_ms,
post_processing: timing.post_processing_ms,
}
: undefined,
token_count: metadata.llm_token_usage?.total_tokens,
llm_token_usage: metadata.llm_token_usage,
estimated_cost: undefined, // Backend will provide this in future
}}
/>
</div>
</div>
)}

{/* Component Tabs (show when complete) */}
{isComplete && componentCode && (
<Tabs defaultValue="preview" className="space-y-4">
Expand Down
Loading