Revert "CF-1041 observability v2 " need more changes and testing (#2375)

Reverts codeflash-ai/codeflash-internal#2329
This commit is contained in:
Sarthak Agarwal 2026-02-06 01:18:17 +05:30 committed by GitHub
parent 07d33edd9f
commit 98fb2d1579
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
60 changed files with 4620 additions and 4069 deletions

1
.gitignore vendored
View file

@ -163,7 +163,6 @@ cython_debug/
#.idea/
.aider*
.serena/
.planning/
/js/common/node_modules/
/node_modules/
*.xml

View file

@ -15,8 +15,5 @@
],
"[python]": {
"editor.defaultFormatter": "charliermarsh.ruff"
},
"[typescriptreact]": {
"editor.defaultFormatter": "vscode.typescript-language-features"
}
}

View file

@ -23,8 +23,8 @@
**CRITICAL: IMPORT PATH RULES**:
- **NEVER add file extensions (.js, .ts, .tsx) to import paths** - The test framework resolves extensions automatically.
- **WRONG**: `import {{ fn }} from '../utils.js'` or `import {{ fn }} from '../utils.ts'`
- **CORRECT**: `import {{ fn }} from '../utils'`
- **WRONG**: `import {{fn}} from '../utils.js'` or `import {{fn}} from '../utils.ts'`
- **CORRECT**: `import {{fn}} from '../utils'`
- The user message provides the exact import statement to use - copy it exactly without modification.
**CRITICAL: VITEST IMPORTS REQUIRED**:

View file

@ -241,30 +241,8 @@ async def generate_and_validate_test_code(
call_sequence: int | None = None,
function_to_optimize: FunctionToOptimize | None = None,
module_path: str | None = None,
test_module_path: str | None = None,
helper_function_names: list[str] | None = None,
is_async: bool = False,
) -> str:
obs_context: dict | None = (
{
"call_sequence": call_sequence,
"module_path": module_path,
"test_module_path": test_module_path,
"helper_function_names": helper_function_names,
"is_async": is_async,
"function_to_optimize": {
"function_name": function_to_optimize.function_name,
"file_path": function_to_optimize.file_path,
"qualified_name": function_to_optimize.qualified_name,
"starting_line": function_to_optimize.starting_line,
"ending_line": function_to_optimize.ending_line,
}
if function_to_optimize is not None
else None,
}
if call_sequence is not None
else None
)
obs_context: dict | None = {"call_sequence": call_sequence} if call_sequence is not None else None
response = await call_llm(
llm=model,
messages=messages,
@ -340,9 +318,6 @@ async def generate_regression_tests_from_function(
call_sequence=call_sequence,
function_to_optimize=data.function_to_optimize,
module_path=data.module_path,
test_module_path=data.test_module_path,
helper_function_names=data.helper_function_names,
is_async=data.function_to_optimize.is_async or data.is_async or False,
)
total_llm_cost = sum(cost_tracker)
await update_optimization_cost(trace_id=trace_id, cost=total_llm_cost, user_id=user_id)

View file

@ -42,8 +42,4 @@ next-env.d.ts
/.npmrc
.npmrc
/.azure/config
*.next/*
# Generated WASM files (built by postinstall)
/public/web-tree-sitter.wasm
/public/tree-sitter-python.wasm
*.next/*

View file

@ -1,21 +1,11 @@
/** @type {import("next").NextConfig} */
let nextConfig = {
transpilePackages: ["@codeflash-ai/common"],
webpack: (config, { isServer }) => {
webpack: (config) => {
config.watchOptions = {
poll: 1000,
aggregateTimeout: 300,
}
// Handle web-tree-sitter's Node.js module imports in browser
if (!isServer) {
config.resolve.fallback = {
...config.resolve.fallback,
fs: false,
"fs/promises": false,
path: false,
module: false,
}
}
return config
},
experimental: {

File diff suppressed because it is too large Load diff

View file

@ -14,7 +14,6 @@
"prisma:generate": "npx prisma generate",
"prisma:migrate": "npx prisma migrate dev",
"prepare": "simple-git-hooks",
"postinstall": "cp node_modules/web-tree-sitter/web-tree-sitter.wasm public/ && npx tree-sitter build --wasm node_modules/tree-sitter-python -o public/tree-sitter-python.wasm",
"format": "prettier --write \"**/*.{js,ts,tsx,json,md}\"",
"format:check": "prettier --check \"**/*.{js,ts,tsx,json,md}\""
},
@ -25,8 +24,6 @@
"@hookform/resolvers": "^3.3.2",
"@monaco-editor/react": "^4.7.0",
"@prisma/client": "^6.7.0",
"@radix-ui/react-accordion": "^1.2.12",
"@radix-ui/react-collapsible": "^1.1.12",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-label": "^2.0.2",
@ -47,7 +44,6 @@
"chart.js": "^4.4.9",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"cmdk": "^1.1.1",
"date-fns": "^4.1.0",
"diff": "^8.0.2",
"framer-motion": "^12.12.1",
@ -66,7 +62,6 @@
"prism-react-renderer": "^2.4.1",
"react": "^18",
"react-chartjs-2": "^5.3.0",
"react-diff-viewer-continued": "^3.4.0",
"react-dom": "^18",
"react-hook-form": "^7.48.2",
"react-markdown": "^9.0.1",
@ -74,13 +69,10 @@
"react-syntax-highlighter": "^16.1.0",
"remark-gfm": "^4.0.0",
"sharp": "^0.34.2",
"shiki": "^3.21.0",
"sonner": "^2.0.6",
"tailwind-merge": "^2.0.0",
"tailwindcss": "^3.3.0",
"tailwindcss-animate": "^1.0.7",
"vaul": "^1.1.2",
"web-tree-sitter": "^0.26.3",
"zod": "^3.22.4"
},
"devDependencies": {
@ -100,12 +92,9 @@
"eslint-plugin-react": "^7.33.2",
"jsdom": "^24.1.0",
"lint-staged": "^15.4.3",
"postcss-import": "^16.1.1",
"prettier": "3.2.5",
"prisma": "^6.7.0",
"simple-git-hooks": "^2.9.0",
"tree-sitter-cli": "^0.26.3",
"tree-sitter-python": "^0.25.0",
"typescript": "^5.4.5",
"vitest": "^3.0.8"
},

View file

@ -3,7 +3,6 @@
import LogoBox from "@/components/dashboard/logo-box"
import { useState, useEffect } from "react"
import { useRouter, useSearchParams } from "next/navigation"
import Image from "next/image"
import { Loading } from "@/components/ui/loading"
import {
authorizeOAuth,
@ -288,11 +287,9 @@ export default function CodeFlashAuthContent() {
}`}
>
{userInfo?.avatarUrl ? (
<Image
<img
src={userInfo.avatarUrl}
alt={userInfo.name}
width={32}
height={32}
className="w-8 h-8 rounded-full"
/>
) : (
@ -330,7 +327,7 @@ export default function CodeFlashAuthContent() {
}`}
>
{org.avatarUrl ? (
<Image src={org.avatarUrl} alt={org.name} width={32} height={32} className="w-8 h-8 rounded-md" />
<img src={org.avatarUrl} alt={org.name} className="w-8 h-8 rounded-md" />
) : (
<div className="w-8 h-8 rounded-md bg-gradient-to-br from-orange-500 to-red-600 flex items-center justify-center text-white text-xs font-medium">
{getInitials(org.name)}

View file

@ -22,7 +22,6 @@ import React from "react"
import { generateToken } from "./tokenfuncs"
import { Plus, User, Building2, Check } from "lucide-react"
import { useViewMode } from "@/app/app/ViewModeContext"
import Image from "next/image"
import {
Select,
SelectContent,
@ -176,11 +175,9 @@ export function CreateApiKeyDialog(): React.JSX.Element {
<SelectItem key={org.id} value={org.id}>
<div className="flex items-center gap-2">
{org.avatarUrl ? (
<Image
<img
src={org.avatarUrl}
alt={org.name}
width={20}
height={20}
className="h-5 w-5 rounded-full"
/>
) : (

View file

@ -287,7 +287,6 @@ export default function OptimizationReviewPage() {
}
}
loadEvent()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [params.traceId, currentOrg?.id])
const loadComments = async (eventId: string) => {
@ -604,9 +603,9 @@ export default function OptimizationReviewPage() {
window.open(constructedUrl, "_blank")
}
}, 1000)
} catch (error: unknown) {
} catch (error: any) {
console.error("[handleCreatePR] Exception:", error)
const errorMessage = error instanceof Error ? error.message : "Failed to create pull request"
const errorMessage = error?.message || "Failed to create pull request"
toast.error(errorMessage, {
duration: 5000,
})
@ -683,7 +682,7 @@ export default function OptimizationReviewPage() {
)}
</h1>
{event.speedup_x && (
<span className="flex items-center gap-2 rounded-sm bg-zinc-900 border border-zinc-800 px-3 py-1 text-xs font-bold text-zinc-50">
<span className="flex items-center gap-2 rounded-md bg-gradient-to-r from-primary to-yellow-500 px-3 py-1 text-xs font-bold text-gray-900">
<svg className="h-3.5 w-3.5" fill="currentColor" viewBox="0 0 24 24">
<path d="M13 10V3L4 14h7v7l9-11h-7z" />
</svg>

View file

@ -122,7 +122,6 @@ export default function LineProfilerPage() {
}
loadEvent()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [params.traceId, currentOrg?.id])
const handleBack = () => {
@ -205,7 +204,7 @@ export default function LineProfilerPage() {
)}
</h1>
{event.speedup_x && (
<span className="flex items-center gap-2 rounded-sm bg-zinc-900 border border-zinc-800 px-3 py-1 text-xs font-bold text-zinc-50">
<span className="flex items-center gap-2 rounded-md bg-gradient-to-r from-primary to-yellow-500 px-3 py-1 text-xs font-bold text-gray-900">
<svg className="h-3.5 w-3.5" fill="currentColor" viewBox="0 0 24 24">
<path d="M13 10V3L4 14h7v7l9-11h-7z" />
</svg>

View file

@ -203,7 +203,6 @@ function Dashboard() {
setLoading(false)
fetchingRef.current = false
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedYear, currentOrgId])
useEffect(() => {

View file

@ -2,18 +2,84 @@
@tailwind components;
@tailwind utilities;
/* Import the design token system */
@import "../styles/tokens.css";
@import "../styles/typography.css";
@import "../styles/spacing.css";
@layer base {
/* Light mode removed - dark mode only implementation */
:root {
/* Background and foreground */
--background: 0 0% 99%;
--foreground: 222.2 84% 4.9%;
/* Card styles */
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;
/* Popover styles */
--popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%;
/* Codeflash primary colors - converted from hex to HSL */
--primary: 38 100% 63%; /* #d08e0d - Codeflash yellow */
--primary-foreground: 0 6% 4%;
/* Secondary colors - complementary to Codeflash yellow */
--secondary: 41 88% 95%; /* Lighter version of primary */
--secondary-foreground: 41 88% 20%; /* Darker version for contrast */
/* Accent colors - variation of the Codeflash yellow */
--accent: 41 70% 90%; /* Softer version of primary */
--accent-foreground: 41 88% 20%;
/* Other UI colors aligned with brand */
--muted: 41 20% 96%;
--muted-foreground: 41 8% 46%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 100%;
--border: 41 30% 90%;
--input: 41 30% 90%;
--ring: 38 100% 63%; /* Matching primary - Codeflash yellow */
--radius: 0.5rem;
/* Code highlighting */
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1);
}
.dark {
/* All color tokens are defined in tokens.css */
/* The .dark class enables Tailwind's dark: variant */
/* Since we're dark mode only, tokens are already set for dark mode */
/* Background and foreground */
--background: 0, 6%, 5%;
--foreground: 0 0% 100%;
/* Card styles */
--card: 0 3% 11%;
--card-foreground: 0 0% 100%;
/* Popover styles */
--popover: 222.2 84% 4.9%;
--popover-foreground: 0 0% 100%;
/* Codeflash primary colors for dark mode */
--primary: 38 100% 63%; /* #ffd227 - Codeflash yellow for dark mode */
--primary-foreground: 222.2 47.4% 11.2%;
/* Secondary colors - complementary to Codeflash yellow in dark mode */
--secondary: 48 60% 25%; /* Darker version of primary */
--secondary-foreground: 48 100% 80%; /* Lighter version for contrast */
/* Accent colors - variation of the Codeflash yellow */
--accent: 48 70% 30%; /* Softer version of primary */
--accent-foreground: 48 100% 80%;
/* Other UI colors aligned with brand */
--muted: 48 15% 20%;
--muted-foreground: 48 20% 65%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 0% 100%;
--border: 48 20% 25%;
--input: 48 20% 25%;
--ring: 38 100% 63%; /* Matching primary - Codeflash yellow */
/* Code highlighting */
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3);
@ -22,8 +88,8 @@
@layer base {
::selection {
background: rgb(113 113 122); /* zinc-500 - no brand colors */
color: rgb(250 250 250); /* zinc-50 for contrast */
background: #ffd227; /* CF brand color */
color: #1f2937; /* Tailwind's gray-800 for selected text color */
}
* {
@ -80,11 +146,11 @@
}
.prose code {
background-color: rgb(var(--muted));
background-color: hsl(var(--muted));
padding: 0.125em 0.25em;
border-radius: 0.25em;
font-size: 0.875em;
font-family: var(--font-mono);
font-family: ui-monospace, monospace;
}
.prose pre {
@ -98,10 +164,10 @@
}
.prose blockquote {
border-left: 3px solid rgb(var(--border));
border-left: 3px solid hsl(var(--border));
padding-left: 1em;
margin-left: 0;
color: rgb(var(--muted-foreground));
color: hsl(var(--muted-foreground));
}
/* Fixed list styles to show bullets and numbers */
@ -138,7 +204,7 @@
}
.prose a {
color: rgb(var(--primary));
color: hsl(var(--primary));
text-decoration: underline;
}
@ -173,42 +239,10 @@
/* Dark mode adjustments */
.dark .prose code {
background-color: rgb(var(--muted));
background-color: hsl(var(--muted));
}
.dark .prose blockquote {
border-left-color: rgb(var(--border));
color: rgb(var(--muted-foreground));
}
/* Typography utility classes for common patterns */
@layer utilities {
/* Apply monospace font for inline code */
.text-code {
font-family: var(--font-mono);
font-size: var(--text-sm);
}
/* Apply monospace font with tight line height for data */
.text-data {
font-family: var(--font-mono);
line-height: var(--leading-tight);
}
/* Apply sans font with medium weight for UI labels */
.text-label {
font-family: var(--font-sans);
font-weight: 500;
}
/* Standard container padding using spacing tokens */
.container-spacing {
padding-left: var(--space-4);
padding-right: var(--space-4);
}
/* Standard card internal spacing */
.card-spacing {
padding: var(--space-3);
}
border-left-color: hsl(var(--border));
color: hsl(var(--muted-foreground));
}

View file

@ -1,5 +1,5 @@
import type { Metadata } from "next"
import { Inter as FontSans, JetBrains_Mono } from "next/font/google"
import { Inter as FontSans } from "next/font/google"
import "./globals.css"
import { cn } from "@/lib/utils"
import { ThemeProvider } from "@/components/theme-provider"
@ -23,13 +23,6 @@ const fontSans = FontSans({
variable: "--font-sans",
})
const jetbrainsMono = JetBrains_Mono({
subsets: ["latin"],
weight: ["300", "400", "500", "600", "700"],
variable: "--font-jetbrains-mono",
display: "swap",
})
export const metadata: Metadata = {
title: "Codeflash",
description: "Optimize the performance of your code.",
@ -98,7 +91,7 @@ export default async function RootLayout({
}}
/>
</head>
<body className={cn("min-h-screen bg-background font-sans antialiased", fontSans.variable, jetbrainsMono.variable)}>
<body className={cn("min-h-screen bg-background font-sans antialiased", fontSans.variable)}>
<PostHogPageView />
<UserProvider>
<ThemeProvider

View file

@ -1,18 +1,11 @@
import { ReactNode } from "react"
import { ObservabilityNav } from "@/components/observability/observability-nav"
export default function Observability2Layout({ children }: { children: ReactNode }) {
export default function ObservabilityLayout({ children }: { children: ReactNode }) {
return (
<div className="min-h-screen bg-zinc-50 dark:bg-zinc-950">
<nav className="border-b border-zinc-200 dark:border-zinc-700 bg-white dark:bg-zinc-800">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex h-14 items-center justify-center">
<h1 className="text-lg font-semibold text-zinc-900 dark:text-zinc-50">
Observability v2
</h1>
</div>
</div>
</nav>
<main className="flex-1">{children}</main>
<div className="min-h-screen bg-gray-50 dark:bg-gray-900">
<ObservabilityNav />
<div className="flex-1">{children}</div>
</div>
)
}

View file

@ -0,0 +1,524 @@
import Link from "next/link"
import { Metadata } from "next"
import { notFound } from "next/navigation"
import {
CheckCircle,
XCircle,
Hash,
FileText,
Code,
AlertTriangle,
} from "lucide-react"
import { prisma } from "@/lib/prisma"
import { StatCard } from "@/components/observability/stat-card"
import { InfoIcon } from "@/components/observability/info-icon"
import { CopyButton } from "@/components/observability/copy-button"
import { ParsedResponseView } from "@/components/observability/parsed-response-view"
interface LLMCallDetailPageProps {
params: {
id: string
}
}
export async function generateMetadata({ params }: LLMCallDetailPageProps): Promise<Metadata> {
return {
title: `LLM Call ${params.id.substring(0, 8)} - Observability`,
description: "View LLM call details for prompt engineering analysis",
}
}
export default async function LLMCallDetailPage({ params }: LLMCallDetailPageProps) {
// Fetch LLM call details
const llmCall = await prisma.llm_calls.findUnique({
where: { id: params.id },
})
if (!llmCall) {
notFound()
}
// Fetch related errors
const relatedErrors = await prisma.optimization_errors.findMany({
where: { llm_call_id: params.id },
orderBy: { created_at: "desc" },
})
return (
<div className="container mx-auto px-4 py-8">
{/* Header */}
<div className="mb-8">
{/* Breadcrumb */}
<div className="flex items-center gap-2 text-sm text-gray-600 dark:text-gray-400 mb-4">
<Link
href="/observability/llm-calls"
className="hover:text-blue-600 dark:hover:text-blue-400"
>
LLM Calls
</Link>
<span>/</span>
<span className="text-gray-900 dark:text-white font-mono">
{llmCall.id.substring(0, 8)}...
</span>
</div>
<h1 className="text-3xl font-bold tracking-tight text-gray-900 dark:text-white mb-3">
LLM Call Detail
</h1>
{/* ID and Trace with Copy Buttons */}
<div className="space-y-2">
<div className="flex items-center gap-2">
<span className="text-sm text-gray-600 dark:text-gray-400">Call ID:</span>
<code className="text-sm font-mono text-gray-900 dark:text-white bg-gray-100 dark:bg-gray-700 px-2 py-1 rounded">
{llmCall.id}
</code>
<CopyButton text={llmCall.id} label="call ID" size="sm" />
</div>
{llmCall.trace_id && (
<div className="flex items-center gap-2">
<span className="text-sm text-gray-600 dark:text-gray-400">Trace:</span>
<Link
href={`/observability/trace/${llmCall.trace_id}`}
className="text-sm font-mono text-blue-600 dark:text-blue-400 hover:underline bg-gray-100 dark:bg-gray-700 px-2 py-1 rounded"
>
{llmCall.trace_id}
</Link>
<CopyButton text={llmCall.trace_id} label="trace ID" size="sm" />
</div>
)}
</div>
</div>
{/* Summary Cards */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-5 mb-8">
<StatCard
label="Status"
value={llmCall.status}
helpText="Call outcome: success, failed, partial_success, or in_progress"
icon={
llmCall.status === "success"
? "CheckCircle2"
: llmCall.status === "failed"
? "AlertTriangle"
: "Clock"
}
variant={
llmCall.status === "success"
? "success"
: llmCall.status === "failed"
? "error"
: "warning"
}
/>
<StatCard
label="Latency"
value={llmCall.latency_ms ? `${llmCall.latency_ms}ms` : "N/A"}
helpText="API response time in milliseconds. Time from request to completion."
icon="Clock"
/>
<StatCard
label="Tokens"
value={llmCall.total_tokens?.toLocaleString() ?? "N/A"}
helpText="Total tokens (prompt + completion) used in this call"
icon="Database"
/>
<StatCard
label="Cost"
value={llmCall.llm_cost ? `$${llmCall.llm_cost.toFixed(4)}` : "N/A"}
helpText="Cost in USD for this specific call based on model pricing"
icon="DollarSign"
/>
</div>
{/* Metadata */}
<div className="bg-white dark:bg-gray-800 rounded-lg shadow p-6 mb-8 border border-gray-200 dark:border-gray-700">
<h2 className="text-xl font-semibold mb-4 text-gray-900 dark:text-white">Metadata</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="flex items-start gap-2">
<div className="flex-1">
<div className="flex items-center gap-1.5 mb-1">
<span className="text-sm font-medium text-gray-600 dark:text-gray-400">
Call Type
</span>
<InfoIcon content="Purpose of this LLM operation (optimization, validation, line_profiler, etc.)" side="top" />
</div>
<span className="font-semibold text-gray-900 dark:text-gray-100">
{llmCall.call_type}
</span>
</div>
</div>
<div className="flex items-start gap-2">
<div className="flex-1">
<div className="flex items-center gap-1.5 mb-1">
<span className="text-sm font-medium text-gray-600 dark:text-gray-400">Model</span>
<InfoIcon content="AI model and version used for this call" side="top" />
</div>
<span className="font-semibold text-gray-900 dark:text-gray-100">
{llmCall.model_name}
</span>
</div>
</div>
<div className="flex items-start gap-2">
<div className="flex-1">
<div className="flex items-center gap-1.5 mb-1">
<span className="text-sm font-medium text-gray-600 dark:text-gray-400">
Temperature
</span>
<InfoIcon content="Randomness setting (0=deterministic, 1=creative). Controls output variability." side="top" />
</div>
<span className="font-semibold text-gray-900 dark:text-gray-100">
{llmCall.temperature || "default"}
</span>
</div>
</div>
<div className="flex items-start gap-2">
<div className="flex-1">
<div className="flex items-center gap-1.5 mb-1">
<span className="text-sm font-medium text-gray-600 dark:text-gray-400">
Candidates Requested
</span>
<InfoIcon content="Number of code variations requested from the model" side="top" />
</div>
<span className="font-semibold text-gray-900 dark:text-gray-100">
{llmCall.n_candidates || "N/A"}
</span>
</div>
</div>
<div className="flex items-start gap-2">
<div className="flex-1">
<div className="flex items-center gap-1.5 mb-1">
<span className="text-sm font-medium text-gray-600 dark:text-gray-400">
Created
</span>
<InfoIcon content="When this call was initiated" side="top" />
</div>
<span className="font-semibold text-gray-900 dark:text-gray-100">
{new Date(llmCall.created_at).toLocaleString()}
</span>
</div>
</div>
<div className="flex items-start gap-2">
<div className="flex-1">
<div className="flex items-center gap-1.5 mb-1">
<span className="text-sm font-medium text-gray-600 dark:text-gray-400">
Parsing Status
</span>
<InfoIcon content="Whether model output was successfully parsed into usable code candidates" side="top" />
</div>
<span
className={`font-semibold ${
llmCall.parsing_status === "success"
? "text-green-600 dark:text-green-400"
: "text-red-600 dark:text-red-400"
}`}
>
{llmCall.parsing_status || "N/A"}
</span>
</div>
</div>
</div>
</div>
{/* Token Breakdown */}
{llmCall.prompt_tokens && (
<div className="bg-white dark:bg-gray-800 rounded-lg shadow p-6 mb-8 border border-gray-200 dark:border-gray-700">
<div className="flex items-center gap-2 mb-4">
<Hash className="h-5 w-5 text-gray-600 dark:text-gray-400" />
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">Token Usage</h2>
</div>
{/* Visual Token Ratio Bar */}
<div className="mb-6">
<div className="flex items-center gap-2 mb-2">
<span className="text-sm font-medium text-gray-600 dark:text-gray-400">
Token Distribution
</span>
<InfoIcon content="Visual breakdown of prompt vs completion tokens" side="top" />
</div>
<div className="flex h-8 rounded-lg overflow-hidden border border-gray-200 dark:border-gray-600">
{(() => {
const promptTokens = llmCall.prompt_tokens ?? 0
const completionTokens = llmCall.completion_tokens ?? 0
const totalTokens = llmCall.total_tokens ?? (promptTokens + completionTokens)
// Use 1 as fallback only for division to prevent division by zero
const safeTotalTokens = totalTokens || 1
const promptPercent = Math.round((promptTokens / safeTotalTokens) * 100)
const completionPercent = Math.round((completionTokens / safeTotalTokens) * 100)
return (
<>
<div
className="bg-blue-500 dark:bg-blue-600 flex items-center justify-center text-white text-xs font-semibold"
style={{ width: `${promptPercent}%` }}
>
{promptPercent > 0 && <span className="px-2">{promptPercent}%</span>}
</div>
<div
className="bg-green-500 dark:bg-green-600 flex items-center justify-center text-white text-xs font-semibold"
style={{ width: `${completionPercent}%` }}
>
{completionPercent > 0 && <span className="px-2">{completionPercent}%</span>}
</div>
</>
)
})()}
</div>
</div>
<div className="grid grid-cols-3 gap-6">
{(() => {
const promptTokens = llmCall.prompt_tokens ?? 0
const completionTokens = llmCall.completion_tokens ?? 0
const totalTokens = llmCall.total_tokens ?? (promptTokens + completionTokens)
return (
<>
<div>
<div className="flex items-center gap-1.5 mb-2">
<div className="w-3 h-3 rounded-sm bg-blue-500 dark:bg-blue-600"></div>
<span className="text-sm font-medium text-gray-600 dark:text-gray-400">
Prompt Tokens
</span>
</div>
<div className="text-2xl font-bold text-gray-900 dark:text-white">
{promptTokens.toLocaleString()}
</div>
</div>
<div>
<div className="flex items-center gap-1.5 mb-2">
<div className="w-3 h-3 rounded-sm bg-green-500 dark:bg-green-600"></div>
<span className="text-sm font-medium text-gray-600 dark:text-gray-400">
Completion Tokens
</span>
</div>
<div className="text-2xl font-bold text-gray-900 dark:text-white">
{completionTokens.toLocaleString()}
</div>
</div>
<div>
<div className="flex items-center gap-1.5 mb-2">
<span className="text-sm font-medium text-gray-600 dark:text-gray-400">
Total Tokens
</span>
</div>
<div className="text-2xl font-bold text-gray-900 dark:text-white">
{totalTokens.toLocaleString()}
</div>
</div>
</>
)
})()}
</div>
</div>
)}
{/* Parsing Results */}
{llmCall.candidates_generated !== null && (
<div className="bg-white dark:bg-gray-800 rounded-lg shadow p-6 mb-8 border border-gray-200 dark:border-gray-700">
<div className="flex items-center gap-2 mb-4">
<Code className="h-5 w-5 text-gray-600 dark:text-gray-400" />
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">Parsing Results</h2>
<InfoIcon content="Results from parsing the model's response into code candidates" side="top" />
</div>
<div className="grid grid-cols-2 gap-6">
<div>
<div className="flex items-center gap-1.5 mb-2">
<span className="text-sm font-medium text-gray-600 dark:text-gray-400">
Candidates Generated
</span>
</div>
<div className="text-2xl font-bold text-gray-900 dark:text-white">
{llmCall.candidates_generated}
</div>
</div>
<div>
<div className="flex items-center gap-1.5 mb-2">
<CheckCircle className="h-4 w-4 text-green-600 dark:text-green-400" />
<span className="text-sm font-medium text-gray-600 dark:text-gray-400">
Candidates Valid
</span>
</div>
<div className="text-2xl font-bold text-green-600 dark:text-green-400">
{llmCall.candidates_valid}
</div>
</div>
</div>
{llmCall.parsing_errors && (
<div className="mt-6 p-4 bg-red-50 dark:bg-red-900/20 rounded-lg border border-red-200 dark:border-red-800">
<div className="flex items-center gap-2 mb-3">
<XCircle className="h-4 w-4 text-red-600 dark:text-red-400" />
<div className="text-sm font-semibold text-red-800 dark:text-red-300">
Parsing Errors
</div>
</div>
<pre className="bg-white dark:bg-gray-900 p-3 rounded-lg text-xs overflow-auto text-gray-900 dark:text-gray-100 border border-red-200 dark:border-red-800 font-mono">
{JSON.stringify(llmCall.parsing_errors, null, 2)}
</pre>
</div>
)}
</div>
)}
{/* Prompts */}
<div className="bg-white dark:bg-gray-800 rounded-lg shadow p-6 mb-8 border border-gray-200 dark:border-gray-700">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-2">
<FileText className="h-5 w-5 text-gray-600 dark:text-gray-400" />
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">System Prompt</h2>
<InfoIcon content="Instructions that guide the model's behavior throughout the conversation" side="top" />
</div>
<div className="flex items-center gap-2">
<span className="text-xs text-gray-500 dark:text-gray-400">
{llmCall.system_prompt?.length.toLocaleString() || 0} characters
</span>
<CopyButton text={llmCall.system_prompt || ""} label="system prompt" size="sm" />
</div>
</div>
<pre className="bg-gray-50 dark:bg-gray-900 p-4 rounded-lg text-sm overflow-auto whitespace-pre-wrap text-gray-900 dark:text-gray-100 leading-relaxed font-mono border border-gray-200 dark:border-gray-700">
{llmCall.system_prompt}
</pre>
</div>
<div className="bg-white dark:bg-gray-800 rounded-lg shadow p-6 mb-8 border border-gray-200 dark:border-gray-700">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-2">
<FileText className="h-5 w-5 text-gray-600 dark:text-gray-400" />
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">User Prompt</h2>
<InfoIcon content="Specific task and context for this call. Contains the code to optimize and requirements." side="top" />
</div>
<div className="flex items-center gap-2">
<span className="text-xs text-gray-500 dark:text-gray-400">
{llmCall.user_prompt?.length.toLocaleString() || 0} characters
</span>
<CopyButton text={llmCall.user_prompt || ""} label="user prompt" size="sm" />
</div>
</div>
<pre className="bg-gray-50 dark:bg-gray-900 p-4 rounded-lg text-sm overflow-auto whitespace-pre-wrap text-gray-900 dark:text-gray-100 leading-relaxed font-mono border border-gray-200 dark:border-gray-700">
{llmCall.user_prompt}
</pre>
</div>
{/* Response — parsed by call type (ranking: rank/explain; optimization: code blocks + text), with View raw */}
{llmCall.raw_response && (
<div className="bg-white dark:bg-gray-800 rounded-lg shadow p-6 mb-8 border border-gray-200 dark:border-gray-700">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-2">
<Code className="h-5 w-5 text-gray-600 dark:text-gray-400" />
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">LLM Response</h2>
<InfoIcon content="Parsed view by call type: ranking shows order and explanation; optimization shows code blocks. Use View raw for the full string." side="top" />
</div>
<div className="flex items-center gap-2">
<span className="text-xs text-gray-500 dark:text-gray-400">
{llmCall.raw_response.length.toLocaleString()} characters
</span>
<CopyButton text={llmCall.raw_response} label="response" size="sm" />
</div>
</div>
<ParsedResponseView
rawResponse={llmCall.raw_response}
callType={llmCall.call_type}
/>
</div>
)}
{/* Error Information */}
{llmCall.status === "failed" && llmCall.error_message && (
<div className="bg-red-50 dark:bg-red-900/20 rounded-lg shadow p-6 mb-8 border-l-4 border-red-500 dark:border-red-600">
<div className="flex items-start gap-3 mb-4">
<XCircle className="h-6 w-6 text-red-600 dark:text-red-400 mt-0.5 flex-shrink-0" />
<h2 className="text-xl font-semibold text-red-800 dark:text-red-400">
Error Information
</h2>
</div>
<div className="mb-4">
<div className="flex items-center gap-2 mb-2">
<span className="text-sm font-medium text-gray-700 dark:text-gray-300">Error Type:</span>
<span className="px-2 py-1 bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-300 rounded text-sm font-semibold">
{llmCall.error_type}
</span>
</div>
</div>
<div>
<div className="flex items-center justify-between mb-2">
<span className="text-sm font-medium text-gray-700 dark:text-gray-300">Error Message:</span>
<CopyButton text={llmCall.error_message} label="error message" size="sm" />
</div>
<pre className="bg-white dark:bg-gray-900 p-3 rounded-lg text-sm overflow-auto text-red-700 dark:text-red-400 border border-red-200 dark:border-red-800 font-mono leading-relaxed">
{llmCall.error_message}
</pre>
</div>
</div>
)}
{/* Related Errors */}
{relatedErrors.length > 0 && (
<div className="bg-white dark:bg-gray-800 rounded-lg shadow p-6 mb-8 border border-gray-200 dark:border-gray-700">
<div className="flex items-center gap-3 mb-4">
<AlertTriangle className="h-5 w-5 text-orange-600 dark:text-orange-400" />
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">Related Errors</h2>
<span className="bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-200 text-sm px-2.5 py-0.5 rounded font-semibold">
{relatedErrors.length}
</span>
</div>
<div className="space-y-4">
{relatedErrors.map(error => (
<div
key={error.id}
className="border-l-4 border-red-500 dark:border-red-600 pl-4 py-2 bg-gray-50 dark:bg-gray-900/50 rounded-r-lg"
>
<div className="flex items-center gap-2 mb-2">
<span
className={`px-2 py-1 rounded text-xs font-semibold ${
error.severity === "critical"
? "bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-200"
: error.severity === "error"
? "bg-orange-100 dark:bg-orange-900 text-orange-800 dark:text-orange-200"
: "bg-yellow-100 dark:bg-yellow-900 text-yellow-800 dark:text-yellow-200"
}`}
>
{error.severity}
</span>
<span className="text-sm font-medium text-gray-700 dark:text-gray-300">
{error.error_type}
</span>
<span className="text-xs text-gray-500 dark:text-gray-400 ml-auto">
{new Date(error.created_at).toLocaleString()}
</span>
</div>
<div className="text-sm mb-2 text-gray-900 dark:text-gray-100 font-medium">
{error.error_message}
</div>
{error.context && (
<details className="text-sm text-gray-600 dark:text-gray-400">
<summary className="cursor-pointer hover:text-blue-600 dark:hover:text-blue-400">
View context
</summary>
<pre className="bg-white dark:bg-gray-900 p-3 rounded-lg mt-2 overflow-auto text-gray-900 dark:text-gray-100 text-xs border border-gray-200 dark:border-gray-700 font-mono">
{JSON.stringify(error.context, null, 2)}
</pre>
</details>
)}
</div>
))}
</div>
</div>
)}
{/* Actions */}
<div className="flex flex-wrap gap-4">
<Link
href={`/observability/trace/${llmCall.trace_id}`}
className="inline-flex items-center px-6 py-3 bg-blue-600 dark:bg-blue-700 text-white rounded-lg hover:bg-blue-700 dark:hover:bg-blue-600 transition-colors font-medium shadow-sm"
>
View Full Trace
</Link>
<Link
href="/observability/llm-calls"
className="inline-flex items-center px-6 py-3 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 text-gray-900 dark:text-gray-100 transition-colors font-medium"
>
Back to List
</Link>
</div>
</div>
)
}

View file

@ -0,0 +1,800 @@
import Link from "next/link"
import { Metadata } from "next"
import { unstable_cache } from "next/cache"
import { Award, Database as DatabaseIcon, Github, Terminal } from "lucide-react"
import { prisma } from "@/lib/prisma"
import { getCallSource } from "@/lib/observability-utils"
import { HelpButton } from "@/components/observability/help-button"
import { StatCard } from "@/components/observability/stat-card"
import { ColumnHeader } from "@/components/observability/column-header"
import { InfoIcon } from "@/components/observability/info-icon"
export const metadata: Metadata = {
title: "LLM Calls - Observability",
description: "View all LLM API calls for prompt engineering analysis",
}
interface SearchParams {
call_type?: string
model?: string
status?: string
trace_id?: string
page?: string
organization?: string
}
// Cached function to get unique organizations list
// Revalidates every 5 minutes - organizations change infrequently
const getUniqueOrganizations = unstable_cache(
async () => {
const allOrganizations = await prisma.optimization_features.findMany({
select: { organization: true },
distinct: ["organization"],
where: { organization: { not: null } },
})
return allOrganizations
.map(f => f.organization)
.filter(Boolean)
.sort() as string[]
},
["unique-organizations"],
{ revalidate: 300 }, // 5 minutes
)
// Cached function to get unique call types
// Revalidates every 5 minutes - call types change infrequently
const getCallTypes = unstable_cache(
async () => {
const callTypes = await prisma.llm_calls.findMany({
select: { call_type: true },
distinct: ["call_type"],
})
return callTypes.filter(ct => ct.call_type !== null)
},
["call-types"],
{ revalidate: 300 }, // 5 minutes
)
// Cached function to get unique model names
// Revalidates every 5 minutes - models change infrequently
const getModels = unstable_cache(
async () => {
const models = await prisma.llm_calls.findMany({
select: { model_name: true },
distinct: ["model_name"],
})
return models.filter(m => m.model_name !== null)
},
["model-names"],
{ revalidate: 300 }, // 5 minutes
)
export default async function LLMCallsPage({ searchParams }: { searchParams: SearchParams }) {
try {
const page = parseInt(searchParams.page || "1")
const pageSize = 50
const skip = (page - 1) * pageSize
// Build where clause based on filters
type WhereClause = {
call_type?: string
model_name?: { contains: string }
status?: string
trace_id?: { startsWith: string } | { in: string[] } | { contains: string }
OR?: Array<{ trace_id: { startsWith: string } }>
}
const where: WhereClause = {}
if (searchParams.call_type) {
where.call_type = searchParams.call_type
}
if (searchParams.model) {
where.model_name = { contains: searchParams.model }
}
if (searchParams.status) {
where.status = searchParams.status
}
if (searchParams.trace_id) {
// Use startsWith for prefix matching to find multi-model related calls
where.trace_id = { startsWith: searchParams.trace_id }
}
// Get unique organizations for filter dropdown (cached)
const uniqueOrganizations = await getUniqueOrganizations()
// If organization filter is specified, get matching trace_ids
let filteredTraceIds: string[] = []
if (searchParams.organization) {
const orgFeatures = await prisma.optimization_features.findMany({
where: { organization: searchParams.organization },
select: { trace_id: true },
distinct: ["trace_id"],
})
filteredTraceIds = orgFeatures.map(f => f.trace_id).filter(Boolean) as string[]
// If organization filter is applied but no traces found, return empty result early
if (filteredTraceIds.length === 0) {
// Get unique call types and models for filters (cached)
const [callTypes, models] = await Promise.all([
getCallTypes(),
getModels(),
])
// Return early with empty results
return (
<div className="container mx-auto px-4 py-8">
<div className="mb-6">
{/* Title and Search Bar on Same Line */}
<div className="flex items-center justify-between gap-4 mb-2">
<h1 className="text-3xl font-bold text-gray-900 dark:text-white whitespace-nowrap">
LLM Calls
</h1>
{/* Compact Search Bar */}
<form method="get" className="flex items-center gap-2 flex-1 max-w-xl">
<input
type="text"
name="trace_id"
placeholder="Search by Trace ID..."
defaultValue={searchParams.trace_id || ""}
className="flex-1 px-3 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 placeholder-gray-500 dark:placeholder-gray-400"
/>
<button
type="submit"
className="px-4 py-2 text-sm bg-blue-600 dark:bg-blue-700 text-white rounded hover:bg-blue-700 dark:hover:bg-blue-600 whitespace-nowrap"
>
Search
</button>
{searchParams.trace_id && (
<Link
href="/observability/llm-calls"
className="px-4 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded hover:bg-gray-50 dark:hover:bg-gray-700 text-gray-900 dark:text-gray-100 whitespace-nowrap"
>
Clear
</Link>
)}
</form>
</div>
<p className="text-gray-600 dark:text-gray-400">
Track and analyze all LLM API calls for prompt engineering
</p>
</div>
{/* Filters */}
<div className="bg-white dark:bg-gray-800 rounded-lg shadow p-4 mb-6 border border-gray-200 dark:border-gray-700">
<form method="get" className="flex flex-wrap gap-4">
<div>
<label className="block text-sm font-medium mb-1 text-gray-700 dark:text-gray-300">
Call Type
</label>
<select
name="call_type"
className="px-3 py-2 border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100"
defaultValue={searchParams.call_type || ""}
>
<option value="">All</option>
{callTypes.map(ct => (
<option key={ct.call_type} value={ct.call_type}>
{ct.call_type}
</option>
))}
</select>
</div>
<div>
<label className="block text-sm font-medium mb-1 text-gray-700 dark:text-gray-300">
Model
</label>
<select
name="model"
className="px-3 py-2 border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100"
defaultValue={searchParams.model || ""}
>
<option value="">All</option>
{models.map(m => (
<option key={m.model_name} value={m.model_name}>
{m.model_name}
</option>
))}
</select>
</div>
<div>
<label className="block text-sm font-medium mb-1 text-gray-700 dark:text-gray-300">
Status
</label>
<select
name="status"
className="px-3 py-2 border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100"
defaultValue={searchParams.status || ""}
>
<option value="">All</option>
<option value="success">Success</option>
<option value="partial_success">Partial</option>
<option value="failed">Failed</option>
<option value="in_progress">In Progress</option>
</select>
</div>
<div>
<label className="block text-sm font-medium mb-1 text-gray-700 dark:text-gray-300">
Organization
</label>
<select
name="organization"
className="px-3 py-2 border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100"
defaultValue={searchParams.organization || ""}
>
<option value="">All Organizations</option>
{uniqueOrganizations.map(org => (
<option key={org} value={org}>
{org}
</option>
))}
</select>
</div>
<div className="flex items-end">
<button
type="submit"
className="px-4 py-2 bg-blue-600 dark:bg-blue-700 text-white rounded hover:bg-blue-700 dark:hover:bg-blue-600"
>
Filter
</button>
</div>
</form>
</div>
<div className="bg-white dark:bg-gray-800 rounded-lg shadow p-12 text-center border border-gray-200 dark:border-gray-700">
<p className="text-gray-500 dark:text-gray-400">
No LLM calls found for organization &quot;{searchParams.organization}&quot;.
</p>
</div>
</div>
)
}
}
// Apply organization filter using IN clause for exact trace ID matches
// NOTE: Filtering happens at DB level BEFORE pagination, not client-side.
// We use IN clause because there's no Prisma relation between llm_calls and optimization_features
// (they're only related by trace_id as a string field, not a foreign key relation)
if (filteredTraceIds.length > 0 && !searchParams.trace_id) {
// Use IN clause for exact trace ID matches - much more efficient than OR with startsWith
// For very large organizations (>10k traces), consider chunking the array
where.trace_id = { in: filteredTraceIds }
}
// Fetch LLM calls with pagination, aggregate stats
const [llmCalls, totalCount, aggregateStats, successCount] = await Promise.all([
prisma.llm_calls.findMany({
where,
orderBy: { created_at: "desc" },
take: pageSize,
skip,
select: {
id: true,
trace_id: true,
call_type: true,
model_name: true,
status: true,
parsing_status: true,
candidates_generated: true,
candidates_valid: true,
prompt_tokens: true,
completion_tokens: true,
llm_cost: true,
latency_ms: true,
created_at: true,
error_message: true,
context: true,
},
}),
prisma.llm_calls.count({ where }),
// Get aggregate stats for all filtered data (not just current page)
prisma.llm_calls.aggregate({
where,
_sum: {
llm_cost: true,
latency_ms: true,
},
_avg: {
latency_ms: true,
},
_count: {
status: true,
},
}),
// Get success count for success rate calculation
prisma.llm_calls.count({
where: {
...where,
status: "success",
},
}),
])
// Fetch optimization_features and optimization_events for the trace_ids we got
const traceIds = llmCalls.map(call => call.trace_id).filter(Boolean) as string[]
const uniqueTraceIdPrefixes = Array.from(
new Set(traceIds.map(id => id.substring(0, 36))), // Get base UUID (first 36 chars)
)
// NOTE: Using Promise.all for parallel fetching, not N+1 queries.
// Both queries execute simultaneously for the same set of trace_ids.
const [optimizationFeatures, optimizationEvents] = await Promise.all([
uniqueTraceIdPrefixes.length > 0
? prisma.optimization_features.findMany({
where: { trace_id: { in: uniqueTraceIdPrefixes } },
select: {
trace_id: true,
organization: true,
ranking: true,
},
})
: [],
uniqueTraceIdPrefixes.length > 0
? prisma.optimization_events.findMany({
where: { trace_id: { in: uniqueTraceIdPrefixes } },
select: {
trace_id: true,
event_type: true,
},
distinct: ["trace_id"],
})
: [],
])
// Create maps for trace_id to event_type and organization
const traceIdToEventType = new Map<string, string>()
optimizationEvents.forEach(event => {
if (event.trace_id) {
traceIdToEventType.set(event.trace_id, event.event_type)
}
})
const traceIdToOrganization = new Map<string, string>()
optimizationFeatures.forEach(feature => {
if (feature.trace_id && feature.organization) {
traceIdToOrganization.set(feature.trace_id, feature.organization)
}
})
// Trace IDs that have a chosen best candidate (ranking.ranking[0] present)
const traceIdsWithBest = new Set(
optimizationFeatures
.filter(
f =>
f.trace_id &&
(f.ranking as { ranking?: string[] } | null)?.ranking?.[0],
)
.map(f => f.trace_id.substring(0, 36)),
)
// Get unique call types and models for filters
const [callTypes, models] = await Promise.all([
prisma.llm_calls.findMany({
select: { call_type: true },
distinct: ["call_type"],
}),
prisma.llm_calls.findMany({
select: { model_name: true },
distinct: ["model_name"],
}),
])
const totalPages = Math.ceil(totalCount / pageSize)
return (
<div className="container mx-auto px-4 py-8">
<div className="mb-8">
{/* Title, Search Bar, and Help Button */}
<div className="flex items-center justify-between gap-4 mb-2">
<div className="flex items-center gap-3">
<h1 className="text-3xl font-bold tracking-tight text-gray-900 dark:text-white whitespace-nowrap">
LLM Calls
</h1>
<HelpButton
title="Understanding LLM Calls"
content={
<div className="space-y-4 text-sm text-gray-700 dark:text-gray-300">
<div>
<h4 className="font-semibold text-gray-900 dark:text-white mb-2">What is an LLM call?</h4>
<p>Each individual API request to an AI model (like GPT-4, Claude, etc.) for generating code optimizations, validations, or other tasks.</p>
</div>
<div>
<h4 className="font-semibold text-gray-900 dark:text-white mb-2">Call sequence (#)</h4>
<p>Shows the order of calls within a trace. Multiple calls may be part of the same optimization request.</p>
</div>
<div>
<h4 className="font-semibold text-gray-900 dark:text-white mb-2">Call types</h4>
<ul className="list-disc list-inside space-y-1 ml-2">
<li><strong>optimization:</strong> Generates optimized code</li>
<li><strong>validation:</strong> Checks quality of generated code</li>
<li><strong>line_profiler:</strong> Analyzes performance bottlenecks</li>
</ul>
</div>
<div>
<h4 className="font-semibold text-gray-900 dark:text-white mb-2">Parsing status</h4>
<p>Indicates whether the model&apos;s response was successfully parsed and extracted into usable code candidates.</p>
</div>
</div>
}
/>
</div>
{/* Compact Search Bar */}
<form method="get" className="flex items-center gap-2 flex-1 max-w-xl">
<input
type="text"
name="trace_id"
placeholder="Search by Trace ID..."
defaultValue={searchParams.trace_id || ""}
className="flex-1 px-3 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 placeholder-gray-500 dark:placeholder-gray-400 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-shadow"
/>
<button
type="submit"
className="px-4 py-2 text-sm bg-blue-600 dark:bg-blue-700 text-white rounded hover:bg-blue-700 dark:hover:bg-blue-600 whitespace-nowrap"
>
Search
</button>
{searchParams.trace_id && (
<Link
href="/observability/llm-calls"
className="px-4 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded hover:bg-gray-50 dark:hover:bg-gray-700 text-gray-900 dark:text-gray-100 whitespace-nowrap"
>
Clear
</Link>
)}
</form>
</div>
<p className="text-gray-600 dark:text-gray-400">
Track and analyze all LLM API calls for prompt engineering
</p>
</div>
{/* Filters */}
<div className="bg-white dark:bg-gray-800 rounded-lg shadow p-5 mb-8 border border-gray-200 dark:border-gray-700 hover:shadow-md transition-shadow">
<form method="get" className="flex flex-wrap gap-4">
<div>
<label className="flex items-center gap-1.5 text-sm font-semibold mb-1 text-gray-700 dark:text-gray-300">
Call Type
<InfoIcon content="Type of operation: optimization generates code, validation checks quality, etc." side="top" />
</label>
<select
name="call_type"
className="px-3 py-2 border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
defaultValue={searchParams.call_type || ""}
>
<option value="">All</option>
{callTypes.map(ct => (
<option key={ct.call_type} value={ct.call_type}>
{ct.call_type}
</option>
))}
</select>
</div>
<div>
<label className="flex items-center gap-1.5 text-sm font-semibold mb-1 text-gray-700 dark:text-gray-300">
Model
<InfoIcon content="AI model used (e.g., gpt-4, claude-3, etc.)" side="top" />
</label>
<select
name="model"
className="px-3 py-2 border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
defaultValue={searchParams.model || ""}
>
<option value="">All</option>
{models.map(m => (
<option key={m.model_name} value={m.model_name}>
{m.model_name}
</option>
))}
</select>
</div>
<div>
<label className="flex items-center gap-1.5 text-sm font-semibold mb-1 text-gray-700 dark:text-gray-300">
Status
<InfoIcon content="success = completed normally, failed = error occurred, partial = incomplete results" side="top" />
</label>
<select
name="status"
className="px-3 py-2 border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
defaultValue={searchParams.status || ""}
>
<option value="">All</option>
<option value="success">Success</option>
<option value="partial_success">Partial</option>
<option value="failed">Failed</option>
<option value="in_progress">In Progress</option>
</select>
</div>
<div>
<label className="flex items-center gap-1.5 text-sm font-semibold mb-1 text-gray-700 dark:text-gray-300">
Organization
<InfoIcon content="Organization that made the request" side="top" />
</label>
<select
name="organization"
className="px-3 py-2 border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
defaultValue={searchParams.organization || ""}
>
<option value="">All Organizations</option>
{uniqueOrganizations.map(org => (
<option key={org} value={org}>
{org}
</option>
))}
</select>
</div>
<div className="flex items-end gap-2">
<button
type="submit"
className="px-4 py-2 bg-blue-600 dark:bg-blue-700 text-white rounded hover:bg-blue-700 dark:hover:bg-blue-600 focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
>
Filter
</button>
{(searchParams.call_type || searchParams.model || searchParams.status || searchParams.organization) && (
<Link
href="/observability/llm-calls"
className="px-4 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded hover:bg-gray-50 dark:hover:bg-gray-700 text-gray-900 dark:text-gray-100"
>
Clear All
</Link>
)}
</div>
</form>
</div>
{/* Stats Summary */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-5 mb-8">
<StatCard
label="Total Calls"
value={totalCount}
helpText="Total number of LLM API calls matching your filters (across all pages)"
icon="Activity"
/>
<StatCard
label="Success Rate"
value={`${totalCount > 0 ? Math.round((successCount / totalCount) * 100) : 0}%`}
helpText="Percentage of successful calls vs total calls. Excludes in-progress calls."
icon="CheckCircle2"
variant="success"
/>
<StatCard
label="Total Cost"
value={`$${(aggregateStats._sum?.llm_cost ?? 0).toFixed(3)}`}
helpText="Cumulative cost of all calls matching filters. Based on model pricing."
icon="DollarSign"
/>
<StatCard
label="Avg Latency"
value={`${aggregateStats._avg?.latency_ms ? Math.round(aggregateStats._avg.latency_ms) : 0}ms`}
helpText="Average time taken for LLM API to respond. Excludes network latency."
icon="Clock"
/>
</div>
{/* LLM Calls Table */}
<div className="bg-white dark:bg-gray-800 rounded-lg shadow overflow-hidden border border-gray-200 dark:border-gray-700">
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
<thead className="bg-gray-50 dark:bg-gray-900">
<tr>
<ColumnHeader
label="#"
tooltip="Call sequence number within the trace. Shows execution order."
className="px-3 py-3"
/>
<ColumnHeader
label="Timestamp"
tooltip="When this API call was made. Click to see full details."
/>
<ColumnHeader
label="Trace ID"
tooltip="Parent trace containing this call. Click to view all calls in trace."
/>
<ColumnHeader
label="Organization"
tooltip="Organization from trace metadata"
/>
<ColumnHeader
label="Type"
tooltip="Call type: what this LLM operation was attempting"
/>
<ColumnHeader
label="Source"
tooltip="Origin of request: GitHub Action, CLI, VSCode extension, etc."
/>
<ColumnHeader
label="Model"
tooltip="AI model used for this call"
/>
<ColumnHeader
label="Status"
tooltip="Call outcome: success, failed, partial_success, or in_progress"
/>
<ColumnHeader
label="Tokens"
tooltip="Total tokens (prompt + completion) used in this call"
/>
<ColumnHeader
label="Cost"
tooltip="Cost in USD for this specific call"
/>
<ColumnHeader
label="Latency"
tooltip="API response time in milliseconds"
/>
<ColumnHeader
label="Candidates"
tooltip="Valid candidates / Total generated. For optimization calls only."
/>
</tr>
</thead>
<tbody className="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
{llmCalls.map(call => {
const ctx = call.context as { call_sequence?: number } | null
const callSequence = ctx?.call_sequence
const traceIdPrefix = call.trace_id?.substring(0, 36) || ""
const eventType = traceIdPrefix ? traceIdToEventType.get(traceIdPrefix) || null : null
const organization = traceIdPrefix ? traceIdToOrganization.get(traceIdPrefix) : null
const source = getCallSource(eventType, call.context as Record<string, unknown> | null)
const isBestOptimizationCall =
call.call_type === "optimization" && traceIdsWithBest.has(traceIdPrefix)
return (
<tr key={call.id} className="hover:bg-gray-50 dark:hover:bg-gray-700/50 hover:shadow-md transition-all duration-200">
<td className="px-3 py-5 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400 font-mono">
{callSequence ? `#${callSequence}` : "-"}
</td>
<td className="px-6 py-5 whitespace-nowrap text-sm">
<Link
href={`/observability/llm-call/${call.id}`}
className="text-blue-600 dark:text-blue-400 hover:underline"
>
{new Date(call.created_at).toLocaleString()}
</Link>
</td>
<td className="px-6 py-5 whitespace-nowrap text-sm">
{call.trace_id && call.trace_id.trim() ? (
<Link
href={`/observability/trace/${call.trace_id}`}
className="text-purple-600 dark:text-purple-400 hover:underline font-mono text-xs"
>
{call.trace_id.substring(0, 8)}...
</Link>
) : (
<span className="text-gray-400 dark:text-gray-500 font-mono text-xs">N/A</span>
)}
</td>
<td className="px-6 py-5 whitespace-nowrap text-sm text-gray-700 dark:text-gray-300">
{organization || "N/A"}
</td>
<td className="px-6 py-5 whitespace-nowrap text-sm">
<span className="inline-flex items-center gap-1.5">
<span className="px-2 py-1 bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200 rounded">
{call.call_type}
</span>
{isBestOptimizationCall && (
<span
className="inline-flex items-center gap-1 px-2 py-0.5 bg-amber-100 dark:bg-amber-900 text-amber-800 dark:text-amber-200 rounded text-xs font-semibold"
title="Optimization that produced the chosen candidate for this trace"
>
<Award className="h-3.5 w-3.5" />
Best
</span>
)}
</span>
</td>
<td className="px-6 py-5 whitespace-nowrap text-sm">
<span className="inline-flex items-center gap-1.5 px-2 py-1 bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 rounded text-xs">
{source.toLowerCase().includes("github") ? (
<Github className="h-3 w-3" />
) : source.toLowerCase().includes("vscode") || source.toLowerCase().includes("cli") ? (
<Terminal className="h-3 w-3" />
) : null}
{source}
</span>
</td>
<td className="px-6 py-5 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
{call.model_name}
</td>
<td className="px-6 py-5 whitespace-nowrap text-sm">
<span
className={`px-2 py-1 rounded ${
call.status === "success"
? "bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200"
: call.status === "failed"
? "bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-200"
: "bg-yellow-100 dark:bg-yellow-900 text-yellow-800 dark:text-yellow-200"
}`}
>
{call.status}
</span>
</td>
<td className="px-6 py-5 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
{call.prompt_tokens && call.completion_tokens
? `${call.prompt_tokens + call.completion_tokens}`
: "-"}
</td>
<td className="px-6 py-5 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
{call.llm_cost ? `$${call.llm_cost.toFixed(4)}` : "-"}
</td>
<td className="px-6 py-5 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
{call.latency_ms ? `${call.latency_ms}ms` : "-"}
</td>
<td className="px-6 py-5 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
{call.candidates_valid}/{call.candidates_generated || 0}
</td>
</tr>
)
})}
</tbody>
</table>
</div>
{llmCalls.length === 0 && (
<div className="flex flex-col items-center justify-center py-16 text-center">
<DatabaseIcon className="h-12 w-12 text-gray-400 dark:text-gray-500 mb-4" />
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-2">
No LLM Calls Found
</h3>
{searchParams.call_type || searchParams.model || searchParams.status || searchParams.organization || searchParams.trace_id ? (
<div className="space-y-3">
<p className="text-gray-500 dark:text-gray-400">
Try adjusting your filters above
</p>
<Link
href="/observability/llm-calls"
className="inline-flex items-center px-4 py-2 text-sm font-medium text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300 border border-blue-200 dark:border-blue-800 rounded-md hover:bg-blue-50 dark:hover:bg-blue-900/20 transition-colors"
>
Clear All Filters
</Link>
</div>
) : (
<div className="space-y-3">
<p className="text-gray-500 dark:text-gray-400">
Run the test script to generate sample data
</p>
<code className="block bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-200 px-4 py-2 rounded-md text-sm font-mono">
python django/aiservice/test_observability_local.py
</code>
</div>
)}
</div>
)}
</div>
{/* Pagination */}
{totalPages > 1 && (
<div className="mt-6 flex justify-center gap-2">
{page > 1 && (
<Link
href={`?page=${page - 1}${searchParams.call_type ? `&call_type=${searchParams.call_type}` : ""}${searchParams.model ? `&model=${searchParams.model}` : ""}${searchParams.status ? `&status=${searchParams.status}` : ""}${searchParams.organization ? `&organization=${searchParams.organization}` : ""}`}
className="px-4 py-2 border border-gray-300 dark:border-gray-600 rounded hover:bg-gray-50 dark:hover:bg-gray-700 text-gray-900 dark:text-gray-100"
>
Previous
</Link>
)}
<span className="px-4 py-2 text-gray-900 dark:text-gray-100">
Page {page} of {totalPages}
</span>
{page < totalPages && (
<Link
href={`?page=${page + 1}${searchParams.call_type ? `&call_type=${searchParams.call_type}` : ""}${searchParams.model ? `&model=${searchParams.model}` : ""}${searchParams.status ? `&status=${searchParams.status}` : ""}${searchParams.organization ? `&organization=${searchParams.organization}` : ""}`}
className="px-4 py-2 border border-gray-300 dark:border-gray-600 rounded hover:bg-gray-50 dark:hover:bg-gray-700 text-gray-900 dark:text-gray-100"
>
Next
</Link>
)}
</div>
)}
</div>
)
} catch (error) {
throw error
}
}

View file

@ -1,39 +0,0 @@
export default function ObservabilityLoading() {
return (
<div className="min-h-screen bg-zinc-50 dark:bg-zinc-900">
{/* Search Section Skeleton */}
<div className="border-b border-zinc-200 dark:border-zinc-700 bg-white dark:bg-zinc-800">
<div className="max-w-4xl mx-auto px-4 py-4">
<div className="w-full max-w-2xl mx-auto">
<div className="flex items-center gap-3">
<div className="flex-1 h-12 bg-zinc-200 dark:bg-zinc-700 rounded-md animate-pulse" />
<div className="w-24 h-12 bg-zinc-200 dark:bg-zinc-700 rounded-md animate-pulse" />
</div>
</div>
</div>
</div>
{/* Content Skeleton */}
<div className="max-w-4xl mx-auto px-4 py-8">
{/* Progress skeleton */}
<div className="mb-8">
<div className="h-4 w-48 bg-zinc-200 dark:bg-zinc-700 rounded mb-2 animate-pulse" />
<div className="h-1 w-full bg-zinc-200 dark:bg-zinc-700 rounded animate-pulse" />
</div>
{/* Timeline items skeleton */}
<div className="space-y-8">
{[1, 2, 3].map((i) => (
<div key={i} className="flex gap-4">
<div className="w-5 h-5 rounded-full bg-zinc-200 dark:bg-zinc-700 animate-pulse" />
<div className="flex-1">
<div className="h-6 w-32 bg-zinc-200 dark:bg-zinc-700 rounded mb-3 animate-pulse" />
<div className="h-48 bg-zinc-200 dark:bg-zinc-700 rounded animate-pulse" />
</div>
</div>
))}
</div>
</div>
</div>
)
}

View file

@ -1,328 +0,0 @@
import { Suspense } from "react"
import { unstable_cache } from "next/cache"
import { Search } from "lucide-react"
import { prisma } from "@/lib/prisma"
import { TraceSearch } from "@/components/observability/trace-search"
import { TimelinePageView } from "@/components/observability/timeline-page-view"
import { transformToTimelineSections } from "@/components/observability/timeline-types"
import { ErrorsSection } from "@/components/observability/errors-section"
import { FunctionToOptimizeSection } from "@/components/observability/function-to-optimize-section"
import { CodeContextSection } from "@/components/observability/code-context-section"
export const revalidate = 60
interface Observability2PageProps {
searchParams: Promise<{
trace_id?: string
}>
}
const getTraceData = unstable_cache(
async (tracePrefix: string) => {
const [rawLlmCalls, errors, optimizationFeatures, optimizationEvent] = await Promise.all([
prisma.llm_calls.findMany({
where: { trace_id: { startsWith: tracePrefix } },
orderBy: { created_at: "asc" },
}),
prisma.optimization_errors.findMany({
where: { trace_id: { startsWith: tracePrefix } },
orderBy: { created_at: "asc" },
}),
prisma.optimization_features.findFirst({
where: { trace_id: { startsWith: tracePrefix } },
}),
prisma.optimization_events.findFirst({
where: { trace_id: { startsWith: tracePrefix } },
select: { event_type: true, function_name: true, file_path: true },
}),
])
return { rawLlmCalls, errors, optimizationFeatures, optimizationEvent }
},
["observability-trace-detail"],
{ revalidate: 60 },
)
export default async function Observability2Page({ searchParams }: Observability2PageProps) {
const params = await searchParams
const traceId = params.trace_id?.trim()
let traceData: Awaited<ReturnType<typeof getTraceData>> | null = null
if (traceId) {
const tracePrefix = traceId.substring(0, 33)
traceData = await getTraceData(tracePrefix)
}
const hasResults = traceData
? traceData.rawLlmCalls.length > 0 || traceData.errors.length > 0
: false
return (
<div className="min-h-screen bg-zinc-50 dark:bg-zinc-900">
<div className="border-b border-zinc-200 dark:border-zinc-700 bg-white dark:bg-zinc-800">
<div className="max-w-4xl mx-auto px-4 py-4">
<Suspense fallback={<SearchSkeleton />}>
<TraceSearch initialTraceId={traceId || ""} hasResults={hasResults} />
</Suspense>
</div>
</div>
{traceId && traceData ? (
<Suspense fallback={<TraceContentSkeleton />}>
<TraceContent traceId={traceId} traceData={traceData} />
</Suspense>
) : traceId ? (
<TraceContentSkeleton />
) : (
<EmptyState />
)}
</div>
)
}
interface TraceData {
rawLlmCalls: Awaited<ReturnType<typeof getTraceData>>["rawLlmCalls"]
errors: Awaited<ReturnType<typeof getTraceData>>["errors"]
optimizationFeatures: Awaited<ReturnType<typeof getTraceData>>["optimizationFeatures"]
optimizationEvent: Awaited<ReturnType<typeof getTraceData>>["optimizationEvent"]
}
function TraceContent({ traceId, traceData }: { traceId: string; traceData: TraceData }) {
const { rawLlmCalls, errors, optimizationFeatures, optimizationEvent } = traceData
if (rawLlmCalls.length === 0 && errors.length === 0) {
return <NotFoundState traceId={traceId} />
}
const optimizationsOrigin =
(optimizationFeatures?.optimizations_origin as Record<
string,
{ source: string; model?: string; call_sequence?: number; parent?: string }
>) || {}
const candidateExplanations =
(optimizationFeatures?.explanations_post as Record<string, string>) || {}
const allCandidates = optimizationFeatures?.optimizations_post
? Object.entries(optimizationFeatures.optimizations_post as Record<string, string>).map(
([id, code]) => ({
id,
code: typeof code === "string" ? code : "",
source: optimizationsOrigin[id]?.source || "OPTIMIZE",
model: optimizationsOrigin[id]?.model,
callSequence: optimizationsOrigin[id]?.call_sequence,
explanation: candidateExplanations[id],
}),
)
: []
const optimizationCandidates = allCandidates
.filter(c => c.source === "OPTIMIZE")
.sort((a, b) => (a.callSequence ?? Infinity) - (b.callSequence ?? Infinity))
.map((c, index) => ({ ...c, index: index + 1 }))
const lineProfilerCandidates = allCandidates
.filter(c => c.source === "OPTIMIZE_LP")
.sort((a, b) => (a.callSequence ?? Infinity) - (b.callSequence ?? Infinity))
.map((c, index) => ({ ...c, index: index + 1 }))
const refinementCandidates = allCandidates
.filter(c => c.source === "REFINE")
.sort((a, b) => (a.callSequence ?? Infinity) - (b.callSequence ?? Infinity))
.map((c, index) => ({
...c,
index: index + 1,
parentId: optimizationsOrigin[c.id]?.parent || null,
}))
const rankingData = optimizationFeatures?.ranking as
| { ranking?: string[]; explanation?: string }
| null
const bestCandidateId = rankingData?.ranking?.[0] ?? null
const pullRequestRaw = optimizationFeatures?.pull_request
const usedForPr = Boolean(
pullRequestRaw != null &&
typeof pullRequestRaw === "object" &&
!Array.isArray(pullRequestRaw) &&
Object.keys(pullRequestRaw as Record<string, unknown>).length > 0,
)
const candidateRankMap: Record<string, number> = {}
if (rankingData?.ranking) {
rankingData.ranking.forEach((id, index) => {
candidateRankMap[id] = index + 1
})
}
const generatedTests = (optimizationFeatures?.generated_test ?? []).map((code, index) => ({
code,
index: index + 1,
}))
const instrumentedTests = (optimizationFeatures?.instrumented_generated_test ?? []).map((code, index) => ({
code,
index: index + 1,
}))
const instrumentedPerfTests = ((optimizationFeatures as Record<string, unknown>)?.instrumented_perf_test as string[] ?? []).map((code, index) => ({
code,
index: index + 1,
}))
const llmCalls = rawLlmCalls.sort((a, b) => {
const seqA = (a.context as { call_sequence?: number } | null)?.call_sequence ?? Infinity
const seqB = (b.context as { call_sequence?: number } | null)?.call_sequence ?? Infinity
if (seqA !== seqB) return seqA - seqB
return new Date(a.created_at).getTime() - new Date(b.created_at).getTime()
})
const transformedCalls = llmCalls.map(call => ({
id: call.id,
call_type: call.call_type,
model_name: call.model_name,
status: call.status,
latency_ms: call.latency_ms,
llm_cost: call.llm_cost,
total_tokens: call.total_tokens,
created_at: call.created_at,
context: call.context as { call_sequence?: number } | null,
}))
const { sections, totalDuration } = transformToTimelineSections({
calls: transformedCalls,
optimizationCandidates,
lineProfilerCandidates,
refinementCandidates,
generatedTests,
instrumentedTests,
instrumentedPerfTests,
originalCode: optimizationFeatures?.original_code ?? null,
testFramework: optimizationFeatures?.test_framework ?? null,
candidateRankMap,
bestCandidateId,
rankingExplanation: rankingData?.explanation ?? null,
usedForPr,
})
const transformedErrors = errors.map(error => ({
id: error.id,
error_type: error.error_type,
severity: error.severity,
error_message: error.error_message,
context: error.context as {
test_name?: string
failure_reason?: string
test_output?: string
expected?: string
actual?: string
} | null,
created_at: error.created_at,
}))
const functionName = (optimizationFeatures?.metadata as Record<string, unknown>)?.function_to_optimize as string ?? optimizationEvent?.function_name ?? null
const filePath = optimizationEvent?.file_path ?? null
const originalCode = optimizationFeatures?.original_code ?? null
const dependencyCode = optimizationFeatures?.dependency_code ?? null
return (
<div>
<div className="max-w-4xl mx-auto px-4 py-6 space-y-4">
<FunctionToOptimizeSection
functionName={functionName}
filePath={filePath}
originalCode={originalCode}
/>
<CodeContextSection
functionName={functionName}
filePath={filePath}
originalCode={originalCode}
dependencyCode={dependencyCode}
/>
</div>
<TimelinePageView
sections={sections}
totalDuration={totalDuration}
functionName={functionName}
filePath={filePath}
/>
{transformedErrors.length > 0 && (
<div className="max-w-4xl mx-auto px-4 pb-20">
<ErrorsSection errors={transformedErrors} />
</div>
)}
</div>
)
}
function EmptyState() {
return (
<div className="flex flex-col items-center justify-center py-32 text-center px-4">
<div className="w-20 h-20 mb-8 rounded-full bg-zinc-100 dark:bg-zinc-800 flex items-center justify-center">
<Search className="h-10 w-10 text-zinc-400" />
</div>
<h3 className="text-2xl font-semibold text-zinc-900 dark:text-zinc-50 mb-3">
Enter a Trace ID to Get Started
</h3>
<p className="text-zinc-500 dark:text-zinc-400 max-w-md leading-relaxed">
Paste or type a trace ID in the search box above to view the complete optimization timeline,
including all LLM calls, generated candidates, and any errors.
</p>
</div>
)
}
function NotFoundState({ traceId }: { traceId: string }) {
return (
<div className="flex flex-col items-center justify-center py-32 text-center px-4">
<div className="w-20 h-20 mb-8 rounded-full bg-red-100 dark:bg-red-900/30 flex items-center justify-center">
<Search className="h-10 w-10 text-red-500" />
</div>
<h3 className="text-2xl font-semibold text-zinc-900 dark:text-zinc-50 mb-3">Trace Not Found</h3>
<p className="text-zinc-500 dark:text-zinc-400 max-w-md mb-6">
No data was found for the trace ID:
</p>
<code className="text-sm font-mono text-zinc-900 dark:text-zinc-50 bg-zinc-100 dark:bg-zinc-700 px-4 py-2 rounded-md">
{traceId}
</code>
<p className="text-zinc-500 dark:text-zinc-400 max-w-md mt-6 text-sm">
Please check that the trace ID is correct and try again.
</p>
</div>
)
}
function SearchSkeleton() {
return (
<div className="w-full max-w-2xl mx-auto">
<div className="flex items-center gap-3">
<div className="flex-1 h-12 bg-zinc-200 dark:bg-zinc-700 rounded-md animate-pulse" />
<div className="w-24 h-12 bg-zinc-200 dark:bg-zinc-700 rounded-md animate-pulse" />
</div>
</div>
)
}
function TraceContentSkeleton() {
return (
<div className="max-w-4xl mx-auto px-4 py-8">
<div className="mb-8">
<div className="h-4 w-48 bg-zinc-200 dark:bg-zinc-700 rounded mb-2 animate-pulse" />
<div className="h-1 w-full bg-zinc-200 dark:bg-zinc-700 rounded animate-pulse" />
</div>
<div className="space-y-8">
{[1, 2, 3].map((i) => (
<div key={i} className="flex gap-4">
<div className="w-5 h-5 rounded-full bg-zinc-200 dark:bg-zinc-700 animate-pulse" />
<div className="flex-1">
<div className="h-6 w-32 bg-zinc-200 dark:bg-zinc-700 rounded mb-3 animate-pulse" />
<div className="h-48 bg-zinc-200 dark:bg-zinc-700 rounded animate-pulse" />
</div>
</div>
))}
</div>
</div>
)
}

View file

@ -0,0 +1,767 @@
import Link from "next/link"
import { notFound } from "next/navigation"
import { unstable_cache } from "next/cache"
import {
CheckCircle,
Timer,
DollarSign,
Hash,
Code as CodeIcon,
Github,
Terminal,
AlertCircle,
XCircle,
} from "lucide-react"
import { prisma } from "@/lib/prisma"
import { getCallSource } from "@/lib/observability-utils"
import { HelpButton } from "@/components/observability/help-button"
import { InfoIcon } from "@/components/observability/info-icon"
import { CopyButton } from "@/components/observability/copy-button"
export const revalidate = 60
interface TracePageProps {
params: {
trace_id: string
}
}
const getTraceData = unstable_cache(
async (tracePrefix: string) => {
const [rawLlmCalls, errors, optimizationFeatures, optimizationEvent] = await Promise.all([
prisma.llm_calls.findMany({
where: { trace_id: { startsWith: tracePrefix } },
orderBy: { created_at: "asc" },
}),
prisma.optimization_errors.findMany({
where: { trace_id: { startsWith: tracePrefix } },
orderBy: { created_at: "asc" },
}),
prisma.optimization_features.findFirst({
where: { trace_id: { startsWith: tracePrefix } },
}),
prisma.optimization_events.findFirst({
where: { trace_id: { startsWith: tracePrefix } },
select: { event_type: true },
}),
])
return { rawLlmCalls, errors, optimizationFeatures, optimizationEvent }
},
["trace-detail"],
{ revalidate: 60 },
)
export default async function TracePage({ params }: TracePageProps) {
const { trace_id } = params
// Use prefix matching (first 33 chars) to group multi-model calls that share the same base trace_id
const tracePrefix = trace_id.substring(0, 33)
const { rawLlmCalls, errors, optimizationFeatures, optimizationEvent } = await getTraceData(tracePrefix)
const traceSource = getCallSource(optimizationEvent?.event_type || null, null)
// Extract optimization candidates from optimization_features
// Also get the origin of each candidate (OPTIMIZE vs OPTIMIZE_LP) and model info
const optimizationsOrigin =
(optimizationFeatures?.optimizations_origin as Record<
string,
{ source: string; model?: string }
>) || {}
const allCandidates = optimizationFeatures?.optimizations_post
? Object.entries(optimizationFeatures.optimizations_post as Record<string, string>).map(
([id, code]) => ({
id,
code: typeof code === "string" ? code : "",
source: optimizationsOrigin[id]?.source || "OPTIMIZE",
model: optimizationsOrigin[id]?.model,
}),
)
: []
// Filter candidates by source for display under the correct section
const optimizationCandidates = allCandidates
.filter(c => c.source === "OPTIMIZE")
.map((c, index) => ({ ...c, index: index + 1 }))
const lineProfilerCandidates = allCandidates
.filter(c => c.source === "OPTIMIZE_LP")
.map((c, index) => ({ ...c, index: index + 1 }))
// Get explanations for candidates if available
const candidateExplanations =
(optimizationFeatures?.explanations_post as Record<string, string>) || {}
// Best candidate (first in ranking) and whether it was used for PR
const rankingData = optimizationFeatures?.ranking as
| { ranking?: string[]; explanation?: string }
| null
const bestCandidateId = rankingData?.ranking?.[0] ?? null
const pullRequestRaw = optimizationFeatures?.pull_request
const usedForPr = Boolean(
pullRequestRaw != null &&
typeof pullRequestRaw === "object" &&
!Array.isArray(pullRequestRaw) &&
Object.keys(pullRequestRaw as Record<string, unknown>).length > 0,
)
// Map candidate ID to rank position (1-based, 1 = best)
const candidateRankMap = new Map<string, number>()
if (rankingData?.ranking) {
rankingData.ranking.forEach((id, index) => {
candidateRankMap.set(id, index + 1)
})
}
// Sort by call_sequence from context if available, otherwise by created_at
const llmCalls = rawLlmCalls.sort((a, b) => {
const seqA = (a.context as { call_sequence?: number } | null)?.call_sequence ?? Infinity
const seqB = (b.context as { call_sequence?: number } | null)?.call_sequence ?? Infinity
if (seqA !== seqB) return seqA - seqB
return new Date(a.created_at).getTime() - new Date(b.created_at).getTime()
})
// If no data found, show 404
if (llmCalls.length === 0 && errors.length === 0) {
notFound()
}
// Calculate summary metrics
const totalCost = llmCalls.reduce((sum, call) => sum + (call.llm_cost ?? 0), 0)
const totalTokens = llmCalls.reduce((sum, call) => sum + (call.total_tokens ?? 0), 0)
const failedCalls = llmCalls.filter(c => c.status === "failed").length
// Calculate timeline data using Math.min/Math.max to handle out-of-order timestamps
const timestamps = llmCalls.map(call => new Date(call.created_at).getTime())
const minTimestamp = timestamps.length > 0 ? Math.min(...timestamps) : 0
const maxTimestamp = timestamps.length > 0 ? Math.max(...timestamps) : 0
const totalDuration = maxTimestamp > minTimestamp ? (maxTimestamp - minTimestamp) / 1000 : 0
// Status determination - check for partial_success, failed, or success
const hasPartial = llmCalls.some(c => c.status === "partial_success")
const status = failedCalls > 0 ? "Failed" : hasPartial ? "Partial" : "Completed"
const statusColor =
failedCalls > 0
? "text-red-600 dark:text-red-400"
: hasPartial
? "text-yellow-600 dark:text-yellow-400"
: "text-green-600 dark:text-green-400"
// Group calls by call_type
const groupedCalls = llmCalls.reduce(
(acc, call) => {
const type = call.call_type || "unknown"
if (!acc[type]) {
acc[type] = []
}
acc[type].push(call)
return acc
},
{} as Record<string, typeof llmCalls>,
)
// Get call types in order of first appearance
const orderedTypes = [...new Set(llmCalls.map(c => c.call_type || "unknown"))]
// Create a map of call_type to LLM call for candidate linking
const callTypeToLlmCall = new Map<string, typeof llmCalls[0]>()
llmCalls.forEach(call => {
if (call.call_type && !callTypeToLlmCall.has(call.call_type)) {
callTypeToLlmCall.set(call.call_type, call)
}
})
return (
<div className="container mx-auto p-6">
{/* Header */}
<div className="mb-8">
<div className="flex items-center gap-2 text-sm text-gray-600 dark:text-gray-400 mb-4">
<Link
href="/observability/traces"
className="hover:text-blue-600 dark:hover:text-blue-400"
>
Traces
</Link>
<span>/</span>
<span className="text-gray-900 dark:text-white font-mono">
{trace_id && trace_id.trim() ? `${trace_id.substring(0, 8)}...` : "N/A"}
</span>
</div>
<div className="flex items-center justify-between gap-4">
<div className="flex-1">
<h2 className="text-2xl font-bold tracking-tight text-gray-900 dark:text-white mb-2">
Trace Details
</h2>
<div className="flex items-center gap-2">
<p className="text-gray-600 dark:text-gray-400 font-mono text-sm">
{trace_id && trace_id.trim() ? trace_id : "Invalid Trace ID"}
</p>
{trace_id && trace_id.trim() && <CopyButton text={trace_id} label="trace ID" />}
</div>
</div>
<HelpButton
title="Trace Detail Page Guide"
content={
<div className="space-y-4 text-sm text-gray-700 dark:text-gray-300">
<div>
<h4 className="font-semibold text-gray-900 dark:text-white mb-2">
Summary Metrics
</h4>
<p>
View key metrics including status, source, duration, cost, tokens, and number
of generated candidates for this optimization request.
</p>
</div>
<div>
<h4 className="font-semibold text-gray-900 dark:text-white mb-2">
LLM Calls Timeline
</h4>
<p>
All LLM API calls grouped by type. Expand each call to see detailed metrics
including tokens, latency, and timestamp. For optimization and line_profiler
types, candidates are displayed directly.
</p>
</div>
<div>
<h4 className="font-semibold text-gray-900 dark:text-white mb-2">
Generated Candidates
</h4>
<p>
Code optimization candidates generated during this trace. Each candidate includes
an explanation and the generated code. Use the copy button to copy candidate code.
</p>
</div>
<div>
<h4 className="font-semibold text-gray-900 dark:text-white mb-2">
Error Handling
</h4>
<p>
If any errors occurred during optimization, they are displayed with severity
indicators, error messages, and detailed context for test failures.
</p>
</div>
</div>
}
/>
</div>
</div>
{/* Summary Card */}
<div className="bg-white dark:bg-gray-800 rounded-lg shadow p-6 border border-gray-200 dark:border-gray-700 mb-8">
<div className="grid grid-cols-2 md:grid-cols-6 gap-6">
<div>
<div className="flex items-center gap-1.5 text-sm text-gray-600 dark:text-gray-400 font-medium mb-2">
{status === "Completed" ? (
<CheckCircle className="h-4 w-4" />
) : status === "Failed" ? (
<XCircle className="h-4 w-4" />
) : (
<AlertCircle className="h-4 w-4" />
)}
<span>Status</span>
<InfoIcon
content="Overall trace status based on all contained calls"
side="top"
/>
</div>
<div className={`text-xl font-bold ${statusColor}`}>{status}</div>
</div>
<div>
<div className="flex items-center gap-1.5 text-sm text-gray-600 dark:text-gray-400 font-medium mb-2">
{traceSource.toLowerCase().includes("github") ? (
<Github className="h-4 w-4" />
) : (
<Terminal className="h-4 w-4" />
)}
<span>Source</span>
<InfoIcon
content="Where this optimization was triggered from"
side="top"
/>
</div>
<div className="text-xl font-bold text-gray-900 dark:text-white">
<span className="px-2 py-1 bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 rounded text-sm">
{traceSource}
</span>
</div>
</div>
<div>
<div className="flex items-center gap-1.5 text-sm text-gray-600 dark:text-gray-400 font-medium mb-2">
<Timer className="h-4 w-4" />
<span>Duration</span>
<InfoIcon
content="Total time from first call to last call completion"
side="top"
/>
</div>
<div className="text-xl font-bold text-gray-900 dark:text-white">
{(totalDuration / 1000).toFixed(2)}s
</div>
</div>
<div>
<div className="flex items-center gap-1.5 text-sm text-gray-600 dark:text-gray-400 font-medium mb-2">
<DollarSign className="h-4 w-4" />
<span>Cost</span>
<InfoIcon
content="Sum of all LLM call costs in this trace"
side="top"
/>
</div>
<div className="text-xl font-bold text-gray-900 dark:text-white">
${totalCost.toFixed(4)}
</div>
</div>
<div>
<div className="flex items-center gap-1.5 text-sm text-gray-600 dark:text-gray-400 font-medium mb-2">
<Hash className="h-4 w-4" />
<span>Tokens</span>
<InfoIcon
content="Total tokens (prompt + completion) across all calls"
side="top"
/>
</div>
<div className="text-xl font-bold text-gray-900 dark:text-white">
{totalTokens.toLocaleString()}
</div>
</div>
<div>
<div className="flex items-center gap-1.5 text-sm text-gray-600 dark:text-gray-400 font-medium mb-2">
<CodeIcon className="h-4 w-4" />
<span>Candidates</span>
<InfoIcon
content="Number of valid optimization candidates generated"
side="top"
/>
</div>
<div className="text-xl font-bold text-gray-900 dark:text-white">
{optimizationCandidates.length}
</div>
</div>
</div>
</div>
{/* Unified LLM Calls View */}
<div className="bg-white dark:bg-gray-800 rounded-lg shadow border border-gray-200 dark:border-gray-700 mb-8">
<div className="p-6 border-b border-gray-200 dark:border-gray-700">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<h2 className="text-xl font-bold text-gray-900 dark:text-white">LLM Calls</h2>
<span className="bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200 text-sm px-2.5 py-0.5 rounded font-semibold">
{llmCalls.length}
</span>
</div>
<HelpButton
title="LLM Calls Timeline"
content={
<div className="space-y-4 text-sm text-gray-700 dark:text-gray-300">
<div>
<h4 className="font-semibold text-gray-900 dark:text-white mb-2">
Call Sequence
</h4>
<p>
Calls are ordered by sequence number showing the execution order. Each call
displays its status, duration, cost, and model used.
</p>
</div>
<div>
<h4 className="font-semibold text-gray-900 dark:text-white mb-2">
Optimization & Line Profiler
</h4>
<p>
For these call types, generated candidates are displayed directly. Each
candidate includes the code and an explanation of the optimization.
</p>
</div>
<div>
<h4 className="font-semibold text-gray-900 dark:text-white mb-2">
Expanding Calls
</h4>
<p>
Click any call to expand and see detailed metrics including token usage,
latency, and timestamp. Use &quot;View full details&quot; to see prompts and responses.
</p>
</div>
</div>
}
size="sm"
/>
</div>
</div>
<div className="divide-y divide-gray-200 dark:divide-gray-700">
{orderedTypes.map(callType => {
const calls = groupedCalls[callType]
const isOptimizationType = callType === "optimization"
const isLineProfilerType = callType === "line_profiler"
// Get the candidates for this call type
const candidatesForType = isOptimizationType
? optimizationCandidates
: isLineProfilerType
? lineProfilerCandidates
: []
// For optimization/line_profiler types, only show candidates (not individual LLM calls)
const showIndividualCalls = !isOptimizationType && !isLineProfilerType
// Get the LLM call for this call type to link candidates
const llmCallForType = callTypeToLlmCall.get(callType)
return (
<div key={callType} className="p-4">
<div className="font-semibold text-gray-900 dark:text-white mb-3 flex items-center gap-2">
{callType}
{candidatesForType.length > 0 && (
<span className="bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200 text-xs px-2 py-0.5 rounded font-normal">
{candidatesForType.length} candidates
</span>
)}
</div>
{showIndividualCalls && (
<div className="space-y-2">
{calls.map((call, typeIndex) => {
const durationSec = ((call.latency_ms || 0) / 1000).toFixed(2)
const statusIcon = call.status === "success" ? "✓" : "✗"
const callStatusColor =
call.status === "success"
? "text-green-600 dark:text-green-400"
: "text-red-600 dark:text-red-400"
const ctx = call.context as { call_sequence?: number } | null
const callSequence = ctx?.call_sequence
return (
<details
key={call.id}
className="group border border-gray-200 dark:border-gray-700 rounded-lg"
>
<summary className="p-3 cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-700/50 flex items-center justify-between rounded-lg">
<div className="flex items-center gap-3 text-sm">
<span className="font-mono text-gray-500 dark:text-gray-400 w-8">
{callSequence ? `#${callSequence}` : "-"}
</span>
<span className={`font-mono ${callStatusColor}`}>{statusIcon}</span>
<span className="font-medium text-gray-900 dark:text-white">
{calls.length > 1 ? `${callType} ${typeIndex + 1}` : callType}
</span>
<span className="text-gray-500 dark:text-gray-400">
{durationSec}s · ${(call.llm_cost || 0).toFixed(4)} ·{" "}
{call.model_name}
</span>
</div>
<span className="text-gray-400 group-open:rotate-180 transition-transform">
</span>
</summary>
<div className="px-4 pb-4 pt-2 border-t border-gray-200 dark:border-gray-700 mt-2">
{/* Call details */}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-x-6 gap-y-3 text-sm">
<div className="flex flex-col">
<span className="text-gray-500 dark:text-gray-400 text-xs mb-1">Tokens</span>
<span className="text-gray-900 dark:text-white font-medium">
{call.total_tokens?.toLocaleString() ?? "N/A"}
</span>
</div>
<div className="flex flex-col">
<span className="text-gray-500 dark:text-gray-400 text-xs mb-1">Latency</span>
<span className="text-gray-900 dark:text-white font-medium">
{call.latency_ms ? `${call.latency_ms}ms` : "N/A"}
</span>
</div>
<div className="flex flex-col">
<span className="text-gray-500 dark:text-gray-400 text-xs mb-1">Time</span>
<span className="text-gray-900 dark:text-white font-medium">
{new Date(call.created_at).toLocaleTimeString()}
</span>
</div>
<div className="flex flex-col sm:col-span-2 lg:col-span-1">
<span className="text-gray-500 dark:text-gray-400 text-xs mb-1">Details</span>
<Link
href={`/observability/llm-call/${call.id}`}
className="text-blue-600 dark:text-blue-400 hover:underline text-sm font-medium inline-block"
>
View full details
</Link>
</div>
</div>
</div>
</details>
)
})}
</div>
)}
{/* Show candidates for optimization/line_profiler types */}
{candidatesForType.length > 0 && (
<div className={showIndividualCalls ? "mt-4 ml-4" : ""}>
<div className="flex items-center gap-2 mb-3">
<h4 className="text-sm font-medium text-gray-700 dark:text-gray-300">
Generated Candidates
</h4>
<InfoIcon
content="Code optimization candidates generated from this call. Each includes an explanation and optimized code."
side="top"
/>
</div>
<div className="space-y-2">
{candidatesForType.map(candidate => {
const isBest =
bestCandidateId != null && candidate.id === bestCandidateId
const showUsedForPr = isBest && usedForPr
const rank = candidateRankMap.get(candidate.id)
return (
<details
key={candidate.id}
className={`rounded-lg border ${
isBest
? "border-emerald-500 dark:border-emerald-600 bg-emerald-50/50 dark:bg-emerald-900/20"
: "border-gray-200 dark:border-gray-600"
}`}
>
<summary className="p-3 cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-700/50 flex items-center justify-between rounded-lg">
<div className="flex items-center gap-2 flex-wrap">
<span className="font-medium text-gray-900 dark:text-white">
Candidate {candidate.index}
</span>
<span className="font-mono text-xs text-gray-500 dark:text-gray-400">
{candidate.id.substring(0, 8)}...
</span>
{rank != null && (
<span className="bg-indigo-100 dark:bg-indigo-900 text-indigo-800 dark:text-indigo-200 text-xs px-2 py-0.5 rounded font-semibold">
Rank #{rank}
</span>
)}
{isBest && (
<span className="bg-emerald-100 dark:bg-emerald-900 text-emerald-800 dark:text-emerald-200 text-xs px-2 py-0.5 rounded font-semibold">
Best
</span>
)}
{showUsedForPr && (
<span className="bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200 text-xs px-2 py-0.5 rounded font-semibold">
Used for PR
</span>
)}
{candidate.model && (
<span className="bg-purple-100 dark:bg-purple-900 text-purple-800 dark:text-purple-200 text-xs px-2 py-0.5 rounded">
{candidate.model}
</span>
)}
</div>
<span className="text-gray-400 text-sm"></span>
</summary>
<div className="p-3 pt-0 border-t border-gray-200 dark:border-gray-600 mt-2 space-y-3">
{candidateExplanations[candidate.id] && (
<div>
<div className="flex items-center gap-1.5 mb-1">
<CodeIcon className="h-3.5 w-3.5 text-gray-500 dark:text-gray-400" />
<h5 className="text-xs font-medium text-gray-500 dark:text-gray-400">
Explanation
</h5>
</div>
<p className="text-sm text-gray-700 dark:text-gray-300 whitespace-pre-wrap leading-relaxed">
{candidateExplanations[candidate.id]}
</p>
</div>
)}
<div>
<div className="flex items-center justify-between mb-2">
<h5 className="text-xs font-medium text-gray-500 dark:text-gray-400">
Code
</h5>
<CopyButton text={candidate.code} label="candidate code" size="sm" />
</div>
<pre className="bg-gray-900 text-gray-100 p-3 rounded-lg overflow-x-auto text-xs font-mono leading-relaxed">
<code>{candidate.code}</code>
</pre>
</div>
{llmCallForType && (
<div>
<Link
href={`/observability/llm-call/${llmCallForType.id}`}
className="text-blue-600 dark:text-blue-400 hover:underline text-sm font-medium inline-flex items-center gap-1"
>
View LLM Call Details
</Link>
</div>
)}
</div>
</details>
)})}
</div>
</div>
)}
</div>
)
})}
</div>
</div>
{/* Ranking explanation — shown when ranker ran */}
{rankingData?.explanation && (
<div className="bg-white dark:bg-gray-800 rounded-lg shadow border border-gray-200 dark:border-gray-700 mb-8">
<div className="p-6 border-b border-gray-200 dark:border-gray-700">
<div className="flex items-center gap-2">
<h2 className="text-xl font-bold text-gray-900 dark:text-white">
Ranking explanation
</h2>
<InfoIcon
content="Explanation of why candidates were ranked in this order. Rank numbers are shown on each candidate card."
side="top"
/>
</div>
</div>
<div className="p-6">
<p className="text-sm text-gray-700 dark:text-gray-300 whitespace-pre-wrap leading-relaxed">
{rankingData.explanation}
</p>
</div>
</div>
)}
{/* Errors */}
<div className="bg-white dark:bg-gray-800 rounded-lg shadow border border-gray-200 dark:border-gray-700 mb-8">
<div className="p-6 border-b border-gray-200 dark:border-gray-700">
<div className="flex items-center gap-3">
{errors.length > 0 ? (
<>
<AlertCircle className="h-5 w-5 text-red-600 dark:text-red-400" />
<h2 className="text-xl font-bold text-red-600 dark:text-red-400">
Errors
</h2>
<span className="bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-200 text-sm px-2.5 py-0.5 rounded font-semibold">
{errors.length}
</span>
</>
) : (
<>
<CheckCircle className="h-5 w-5 text-green-600 dark:text-green-400" />
<h2 className="text-xl font-bold text-green-600 dark:text-green-400">
No Errors Detected
</h2>
</>
)}
</div>
</div>
{errors.length > 0 ? (
<div className="divide-y divide-gray-200 dark:divide-gray-700">
{errors.map(error => {
const isTestFailure = error.error_type === "test_failure"
const errorContext = error.context as
| {
test_name?: string
failure_reason?: string
test_output?: string
expected?: string
actual?: string
}
| null
return (
<div key={error.id} className="p-6 border-l-4 border-red-500">
<div className="flex items-start gap-3">
{error.severity === "error" ? (
<XCircle className="h-6 w-6 text-red-600 dark:text-red-400 mt-0.5 flex-shrink-0" />
) : (
<AlertCircle className="h-6 w-6 text-yellow-600 dark:text-yellow-400 mt-0.5 flex-shrink-0" />
)}
<div className="flex-1">
<div className="flex items-center gap-2 mb-2">
<span className="font-semibold text-gray-900 dark:text-white text-base">
{error.error_type}
</span>
<span
className={`px-2 py-1 text-xs font-semibold rounded ${
error.severity === "error"
? "bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-300"
: "bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-300"
}`}
>
{error.severity}
</span>
<span className="text-xs text-gray-500 dark:text-gray-400 ml-auto">
{new Date(error.created_at).toLocaleString()}
</span>
</div>
<div className="flex items-start gap-2 mb-3">
<p className="text-sm text-gray-700 dark:text-gray-300 flex-1">
{error.error_message}
</p>
<CopyButton text={error.error_message} label="error message" size="sm" />
</div>
{/* Test Failure Details */}
{isTestFailure && errorContext && (
<div className="mt-3 p-3 bg-red-50 dark:bg-red-900/20 rounded-lg border border-red-200 dark:border-red-800">
<h4 className="text-sm font-semibold text-red-800 dark:text-red-300 mb-2">
Test Failure Details
</h4>
{errorContext.test_name && (
<div className="mb-2">
<span className="text-xs font-medium text-gray-600 dark:text-gray-400">
Test:
</span>{" "}
<span className="text-sm text-gray-900 dark:text-white font-mono">
{errorContext.test_name}
</span>
</div>
)}
{errorContext.failure_reason && (
<div className="mb-2">
<span className="text-xs font-medium text-gray-600 dark:text-gray-400">
Reason:
</span>
<p className="text-sm text-gray-900 dark:text-white mt-1 whitespace-pre-wrap">
{errorContext.failure_reason}
</p>
</div>
)}
{errorContext.expected && (
<div className="mb-2">
<span className="text-xs font-medium text-gray-600 dark:text-gray-400">
Expected:
</span>
<pre className="text-xs text-gray-900 dark:text-white mt-1 bg-white dark:bg-gray-800 p-2 rounded overflow-x-auto">
{String(errorContext.expected)}
</pre>
</div>
)}
{errorContext.actual && (
<div className="mb-2">
<span className="text-xs font-medium text-gray-600 dark:text-gray-400">
Actual:
</span>
<pre className="text-xs text-gray-900 dark:text-white mt-1 bg-white dark:bg-gray-800 p-2 rounded overflow-x-auto">
{String(errorContext.actual)}
</pre>
</div>
)}
{errorContext.test_output && (
<div>
<span className="text-xs font-medium text-gray-600 dark:text-gray-400">
Test Output:
</span>
<pre className="text-xs text-gray-900 dark:text-white mt-1 bg-white dark:bg-gray-800 p-2 rounded overflow-x-auto whitespace-pre-wrap">
{String(errorContext.test_output)}
</pre>
</div>
)}
</div>
)}
</div>
</div>
</div>
)
})}
</div>
) : (
<div className="flex flex-col items-center justify-center py-12 text-center">
<CheckCircle className="h-16 w-16 text-green-500 dark:text-green-400 mb-4" />
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-2">
All Clear!
</h3>
<p className="text-gray-500 dark:text-gray-400 text-sm">
This trace completed successfully with no errors detected.
</p>
</div>
)}
</div>
</div>
)
}

View file

@ -0,0 +1,654 @@
import Link from "next/link"
import { unstable_cache } from "next/cache"
import { Search as SearchIcon } from "lucide-react"
import { prisma } from "@/lib/prisma"
import { safeCostTokens } from "@/lib/observability-utils"
import type { Prisma } from "@prisma/client"
import { HelpButton } from "@/components/observability/help-button"
import { StatCard } from "@/components/observability/stat-card"
import { ColumnHeader } from "@/components/observability/column-header"
import { InfoIcon } from "@/components/observability/info-icon"
// Revalidate every 30 seconds
export const revalidate = 30
interface SearchParams {
trace_id?: string
page?: string
organization?: string
}
// Cached function to get unique organizations list
// Revalidates every 5 minutes - organizations change infrequently
const getUniqueOrganizations = unstable_cache(
async () => {
const uniqueOrganizations = await prisma.optimization_features.findMany({
select: { organization: true },
distinct: ["organization"],
where: { organization: { not: null } },
})
return uniqueOrganizations.map(f => f.organization).filter(Boolean).sort() as string[]
},
["unique-organizations"],
{ revalidate: 300 }, // 5 minutes
)
// Optimized function to count distinct trace_ids using groupBy
const getTotalTracesCount = unstable_cache(
async (traceIdFilter: string | undefined, organizationFilter: string | undefined) => {
// Get trace IDs filtered by organization if specified
let traceIdPrefixes: string[] = []
if (organizationFilter) {
const orgFeatures = await prisma.optimization_features.findMany({
where: { organization: organizationFilter },
select: { trace_id: true },
distinct: ["trace_id"],
})
traceIdPrefixes = orgFeatures.map(f => f.trace_id).filter(Boolean) as string[]
if (traceIdPrefixes.length === 0) return 0
}
// Build where clause
const where: Prisma.llm_callsWhereInput = {}
if (traceIdFilter) {
where.trace_id = { contains: traceIdFilter }
} else if (traceIdPrefixes.length > 0) {
// Use IN clause for exact trace ID matches - much more efficient than OR with startsWith
where.trace_id = { in: traceIdPrefixes }
}
// Use groupBy for efficient distinct count
// Filter out null trace_ids in the result
const result = await prisma.llm_calls.groupBy({
by: ["trace_id"],
where,
})
// Filter out null trace_ids
return result.filter(r => r.trace_id !== null).length
},
["traces-count"],
{ revalidate: 30 },
)
export default async function TracesPage({ searchParams }: { searchParams: SearchParams }) {
try {
const page = parseInt(searchParams.page || "1")
const pageSize = 50
const skip = (page - 1) * pageSize
// Get trace IDs filtered by organization if specified
let filteredTraceIds: string[] = []
if (searchParams.organization) {
const orgFeatures = await prisma.optimization_features.findMany({
where: { organization: searchParams.organization },
select: { trace_id: true },
distinct: ["trace_id"],
})
filteredTraceIds = orgFeatures.map(f => f.trace_id).filter(Boolean) as string[]
// If organization filter is applied but no traces found, return empty result early
if (filteredTraceIds.length === 0) {
const uniqueOrganizations = await prisma.optimization_features.findMany({
select: { organization: true },
distinct: ["organization"],
where: { organization: { not: null } },
})
const orgs = uniqueOrganizations.map(f => f.organization).filter(Boolean).sort() as string[]
// Return early with empty results
return (
<div className="container mx-auto p-6">
{/* Header with search form */}
<div className="mb-8">
<div className="flex items-center justify-between gap-4 mb-2">
<div className="flex items-center gap-3">
<h2 className="text-2xl font-bold tracking-tight text-gray-900 dark:text-white whitespace-nowrap">
All Traces
</h2>
<HelpButton
title="Understanding Traces"
content={
<div className="space-y-4 text-sm text-gray-700 dark:text-gray-300">
<div>
<h4 className="font-semibold text-gray-900 dark:text-white mb-2">What is a trace?</h4>
<p>A trace represents a complete optimization request from start to finish. Each trace contains all the LLM API calls made during that optimization.</p>
</div>
<div>
<h4 className="font-semibold text-gray-900 dark:text-white mb-2">Multi-model traces</h4>
<p>When using multiple models for optimization, all calls share the same base trace_id (first 33 characters). This helps track related operations together.</p>
</div>
<div>
<h4 className="font-semibold text-gray-900 dark:text-white mb-2">Page sections</h4>
<ul className="list-disc list-inside space-y-1 ml-2">
<li><strong>Summary Stats:</strong> Quick overview of trace metrics on this page</li>
<li><strong>Traces Table:</strong> Detailed list of all traces with aggregated data</li>
<li><strong>Filters:</strong> Search by trace ID or filter by organization</li>
</ul>
</div>
</div>
}
/>
</div>
<form method="get" className="flex items-center gap-2 flex-1 max-w-2xl">
<div className="flex-1 relative">
<input
type="text"
name="trace_id"
placeholder="Search by Trace ID..."
defaultValue={searchParams.trace_id || ""}
className="w-full px-3 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 placeholder-gray-500 dark:placeholder-gray-400 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-shadow"
title="Search by full or partial trace ID. Finds all related calls."
/>
</div>
<div className="flex items-center gap-1.5">
<label htmlFor="organization-filter-empty" className="text-sm font-medium text-gray-700 dark:text-gray-300 whitespace-nowrap">
Organization
</label>
<InfoIcon
content="Filter by organization that initiated the optimization request"
side="top"
/>
</div>
<select
id="organization-filter-empty"
name="organization"
defaultValue={searchParams.organization || ""}
className="px-3 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
>
<option value="">All Organizations</option>
{orgs.map(org => (
<option key={org} value={org}>
{org}
</option>
))}
</select>
<button
type="submit"
className="px-4 py-2 text-sm bg-blue-600 dark:bg-blue-700 text-white rounded hover:bg-blue-700 dark:hover:bg-blue-600 whitespace-nowrap"
>
Search
</button>
<Link
href="/observability/traces"
className="px-4 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded hover:bg-gray-50 dark:hover:bg-gray-700 text-gray-900 dark:text-gray-100 whitespace-nowrap"
>
Clear
</Link>
</form>
</div>
<p className="text-gray-600 dark:text-gray-400">
View optimization request traces with aggregated metrics
</p>
</div>
<div className="bg-white dark:bg-gray-800 rounded-lg shadow p-16 text-center border border-gray-200 dark:border-gray-700">
<div className="flex flex-col items-center justify-center space-y-4">
<SearchIcon className="h-12 w-12 text-gray-400 dark:text-gray-500" />
<div>
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-1">
No Traces Found
</h3>
<p className="text-sm text-gray-500 dark:text-gray-400">
No traces found for organization &quot;{searchParams.organization}&quot;. Try selecting a different organization.
</p>
</div>
<Link
href="/observability/llm-calls"
className="text-sm text-blue-600 dark:text-blue-400 hover:underline"
>
View All LLM Calls
</Link>
</div>
</div>
</div>
)
}
}
// Build where clause for LLM calls with organization filter applied at database level
// NOTE: Filtering happens at DB level BEFORE pagination, not client-side.
// We use IN clause because there's no Prisma relation between llm_calls and optimization_features
// (they're only related by trace_id as a string field, not a foreign key relation)
const where: Prisma.llm_callsWhereInput = {}
if (searchParams.trace_id) {
where.trace_id = { contains: searchParams.trace_id }
} else if (filteredTraceIds.length > 0) {
// Use IN clause for exact trace ID matches - much more efficient than OR with startsWith
// For very large organizations (>10k traces), consider chunking the array
where.trace_id = { in: filteredTraceIds }
}
// STEP 1: Get distinct trace_ids with pagination using groupBy
const [distinctTraces, totalTracesCount] = await Promise.all([
prisma.llm_calls.groupBy({
by: ["trace_id"],
where,
orderBy: { _max: { created_at: "desc" } },
take: pageSize,
skip,
_max: { created_at: true },
}),
getTotalTracesCount(searchParams.trace_id, searchParams.organization),
])
// Extract trace_ids from the paginated results
const paginatedTraceIds = distinctTraces
.map(t => t.trace_id)
.filter(Boolean) as string[]
// STEP 2: Fetch all LLM calls ONLY for the paginated trace_ids
const llmCallsRaw = paginatedTraceIds.length > 0
? await prisma.llm_calls.findMany({
where: { trace_id: { in: paginatedTraceIds } },
orderBy: { created_at: "desc" },
select: {
trace_id: true,
created_at: true,
llm_cost: true,
total_tokens: true,
status: true,
call_type: true,
},
})
: []
// Filter out null trace_ids
const llmCalls = llmCallsRaw.filter(call => call.trace_id !== null)
// Fetch organizations ONLY for the paginated trace_ids
const [allOptimizationFeatures] = await Promise.all([
paginatedTraceIds.length > 0
? prisma.optimization_features.findMany({
where: { trace_id: { in: paginatedTraceIds } },
select: {
trace_id: true,
organization: true,
},
})
: [],
])
// Create a map of trace_id to organization
const traceIdToOrganization = new Map<string, string>()
allOptimizationFeatures.forEach(feature => {
if (feature.trace_id && feature.organization) {
traceIdToOrganization.set(feature.trace_id, feature.organization)
}
})
// Get unique organizations for filter dropdown (cached)
const orgs = await getUniqueOrganizations()
// Group by trace_id and calculate aggregates
const traceMap = new Map<
string,
{
trace_id: string
first_seen: Date
last_seen: Date
call_count: number
total_cost: number
total_tokens: number
failed_calls: number
status: string
call_types: Set<string>
}
>()
llmCalls.forEach(call => {
if (!call.trace_id) return
const callTimestamp = new Date(call.created_at).getTime()
const existing = traceMap.get(call.trace_id)
const { cost: callCost, tokens: callTokens } = safeCostTokens(call.llm_cost, call.total_tokens)
if (existing) {
existing.call_count++
existing.total_cost += callCost
existing.total_tokens += callTokens
if (call.status === "failed") existing.failed_calls++
if (call.call_type) existing.call_types.add(call.call_type)
// Use Math.min/Math.max to ensure correct first/last timestamps
existing.first_seen = new Date(Math.min(existing.first_seen.getTime(), callTimestamp))
existing.last_seen = new Date(Math.max(existing.last_seen.getTime(), callTimestamp))
} else {
traceMap.set(call.trace_id, {
trace_id: call.trace_id,
first_seen: new Date(callTimestamp),
last_seen: new Date(callTimestamp),
call_count: 1,
total_cost: callCost,
total_tokens: callTokens,
failed_calls: call.status === "failed" ? 1 : 0,
status: call.status || "unknown",
call_types: new Set(call.call_type ? [call.call_type] : []),
})
}
})
// Convert to array and sort by last_seen desc
// No need to paginate here - already done at database level
const traces = Array.from(traceMap.values()).sort(
(a, b) => b.last_seen.getTime() - a.last_seen.getTime(),
)
const totalPages = Math.ceil(totalTracesCount / pageSize)
return (
<div className="container mx-auto p-6">
{/* Header */}
<div className="mb-8">
{/* Title, Search Bar, and Help Button */}
<div className="flex items-center justify-between gap-4 mb-2">
<div className="flex items-center gap-3">
<h2 className="text-2xl font-bold tracking-tight text-gray-900 dark:text-white whitespace-nowrap">
All Traces
</h2>
<HelpButton
title="Understanding Traces"
content={
<div className="space-y-4 text-sm text-gray-700 dark:text-gray-300">
<div>
<h4 className="font-semibold text-gray-900 dark:text-white mb-2">What is a trace?</h4>
<p>A trace represents a complete optimization request from start to finish. Each trace contains all the LLM API calls made during that optimization.</p>
</div>
<div>
<h4 className="font-semibold text-gray-900 dark:text-white mb-2">Multi-model traces</h4>
<p>When using multiple models for optimization, all calls share the same base trace_id (first 33 characters). This helps track related operations together.</p>
</div>
<div>
<h4 className="font-semibold text-gray-900 dark:text-white mb-2">Page sections</h4>
<ul className="list-disc list-inside space-y-1 ml-2">
<li><strong>Summary Stats:</strong> Quick overview of trace metrics on this page</li>
<li><strong>Traces Table:</strong> Detailed list of all traces with aggregated data</li>
<li><strong>Filters:</strong> Search by trace ID or filter by organization</li>
</ul>
</div>
</div>
}
/>
</div>
{/* Compact Search Bar */}
<form method="get" className="flex items-center gap-2 flex-1 max-w-2xl">
<div className="flex-1 relative">
<input
type="text"
name="trace_id"
placeholder="Search by Trace ID..."
defaultValue={searchParams.trace_id || ""}
className="w-full px-3 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 placeholder-gray-500 dark:placeholder-gray-400 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-shadow"
title="Search by full or partial trace ID. Finds all related calls."
/>
</div>
<div className="flex items-center gap-1.5">
<label htmlFor="organization-filter" className="text-sm font-medium text-gray-700 dark:text-gray-300 whitespace-nowrap">
Organization
</label>
<InfoIcon
content="Filter by organization that initiated the optimization request"
side="top"
/>
</div>
<select
id="organization-filter"
name="organization"
defaultValue={searchParams.organization || ""}
className="px-3 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
>
<option value="">All Organizations</option>
{orgs.map(org => (
<option key={org} value={org}>
{org}
</option>
))}
</select>
<button
type="submit"
className="px-4 py-2 text-sm bg-blue-600 dark:bg-blue-700 text-white rounded hover:bg-blue-700 dark:hover:bg-blue-600 whitespace-nowrap"
>
Search
</button>
{(searchParams.trace_id || searchParams.organization) && (
<Link
href="/observability/traces"
className="px-4 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded hover:bg-gray-50 dark:hover:bg-gray-700 text-gray-900 dark:text-gray-100 whitespace-nowrap"
>
Clear
</Link>
)}
</form>
</div>
<p className="text-gray-600 dark:text-gray-400">
View optimization request traces with aggregated metrics
</p>
</div>
{/* Summary Stats */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-5 mb-8">
<StatCard
label="Total Traces"
value={totalTracesCount}
helpText="Total number of unique traces across all pages matching your filters"
icon="Database"
/>
<StatCard
label="LLM Calls (Page)"
value={llmCalls.length}
helpText="Number of individual LLM API calls on this page. Each trace may have multiple calls."
icon="Zap"
variant="default"
className="border-l-4 border-blue-500"
/>
<StatCard
label="Total Cost (Page)"
value={`$${traces.reduce((sum, t) => sum + t.total_cost, 0).toFixed(4)}`}
helpText="Total cost in USD for all LLM calls shown on this page. Based on model pricing and token usage."
icon="DollarSign"
/>
<StatCard
label="Failed Traces (Page)"
value={traces.filter(t => t.failed_calls > 0).length}
helpText="Traces containing at least one failed LLM call. Click a trace to see error details."
icon="AlertTriangle"
variant="error"
/>
</div>
{/* Traces Table */}
<div className="bg-white dark:bg-gray-800 rounded-lg shadow border border-gray-200 dark:border-gray-700">
<div className="overflow-x-auto">
<table className="w-full">
<thead className="bg-gray-50 dark:bg-gray-900">
<tr>
<ColumnHeader
label="Trace ID"
tooltip="Unique identifier for this optimization request. Click to view detailed timeline."
/>
<ColumnHeader
label="Organization"
tooltip="Organization that initiated this optimization (from GitHub/CLI metadata)"
/>
<ColumnHeader
label="Status"
tooltip="Overall status: Success (all calls succeeded), Partial (some succeeded), Failed (any call failed)"
/>
<ColumnHeader
label="Calls"
tooltip="Number of LLM API calls in this trace. Includes retries and multi-model attempts."
/>
<ColumnHeader
label="Call Types"
tooltip="Types of LLM operations: optimization, validation, line_profiler, etc."
/>
<ColumnHeader
label="Cost"
tooltip="Total cost in USD for all LLM calls in this trace"
/>
<ColumnHeader
label="Tokens"
tooltip="Combined prompt + completion tokens across all calls"
/>
<ColumnHeader
label="Duration"
tooltip="Time between first and last call in this trace"
/>
<ColumnHeader
label="Last Seen"
tooltip="Timestamp of the most recent call in this trace"
/>
</tr>
</thead>
<tbody className="divide-y divide-gray-200 dark:divide-gray-700">
{traces.length === 0 ? (
<tr>
<td
colSpan={9}
className="px-6 py-16 text-center"
>
<div className="flex flex-col items-center justify-center space-y-4">
<SearchIcon className="h-12 w-12 text-gray-400 dark:text-gray-500" />
<div>
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-1">
No Traces Found
</h3>
<p className="text-sm text-gray-500 dark:text-gray-400">
{searchParams.trace_id || searchParams.organization
? "Try adjusting your filters above"
: "Run an optimization to see traces here"}
</p>
</div>
{(searchParams.trace_id || searchParams.organization) && (
<Link
href="/observability/llm-calls"
className="text-sm text-blue-600 dark:text-blue-400 hover:underline"
>
View All LLM Calls
</Link>
)}
</div>
</td>
</tr>
) : (
traces.map(trace => {
// Ensure duration is never negative by using Math.max
const duration = Math.max(
0,
(trace.last_seen.getTime() - trace.first_seen.getTime()) / 1000,
)
// Determine status: check if any call has partial_success, failed, or all success
const hasPartial =
trace.status === "partial_success" ||
llmCalls.some(c => c.trace_id === trace.trace_id && c.status === "partial_success")
const statusColor =
trace.failed_calls > 0
? "bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-300"
: hasPartial
? "bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-300"
: "bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300"
const statusText = trace.failed_calls > 0 ? "Failed" : hasPartial ? "Partial" : "Success"
const statusBorderColor = trace.failed_calls > 0 ? "border-red-500" : hasPartial ? "border-yellow-500" : "border-green-500"
return (
<tr
key={trace.trace_id}
className={`border-l-4 border-transparent hover:bg-gray-50 dark:hover:bg-gray-700/50 hover:shadow-md hover:${statusBorderColor} transition-all duration-200`}
>
<td className="px-6 py-5">
{trace.trace_id && trace.trace_id.trim() ? (
<Link
href={`/observability/trace/${trace.trace_id}`}
className="text-blue-600 dark:text-blue-400 hover:underline font-mono text-sm"
>
{trace.trace_id.substring(0, 8)}...
</Link>
) : (
<span className="text-gray-400 dark:text-gray-500 font-mono text-sm">N/A</span>
)}
</td>
<td className="px-6 py-5 whitespace-nowrap text-sm text-gray-700 dark:text-gray-300">
{traceIdToOrganization.get(trace.trace_id) || "N/A"}
</td>
<td className="px-6 py-5 whitespace-nowrap">
<span className={`inline-flex px-2 py-1 text-xs font-semibold rounded-full ${statusColor}`}>
{statusText}
</span>
</td>
<td className="px-6 py-5 whitespace-nowrap text-sm text-gray-700 dark:text-gray-300">
{trace.call_count}
{trace.failed_calls > 0 && (
<span className="text-red-600 dark:text-red-400 ml-1">
({trace.failed_calls} failed)
</span>
)}
</td>
<td className="px-6 py-5 text-sm text-gray-700 dark:text-gray-300">
<div className="flex flex-wrap gap-1">
{Array.from(trace.call_types)
.slice(0, 3)
.map(type => (
<span
key={type}
className="px-2 py-0.5 text-xs bg-gray-100 dark:bg-gray-700 rounded"
>
{type}
</span>
))}
{trace.call_types.size > 3 && (
<span className="px-2 py-0.5 text-xs text-gray-500 dark:text-gray-400">
+{trace.call_types.size - 3}
</span>
)}
</div>
</td>
<td className="px-6 py-5 whitespace-nowrap text-sm text-gray-700 dark:text-gray-300">
${trace.total_cost.toFixed(4)}
</td>
<td className="px-6 py-5 whitespace-nowrap text-sm text-gray-700 dark:text-gray-300">
{trace.total_tokens.toLocaleString()}
</td>
<td className="px-6 py-5 whitespace-nowrap text-sm text-gray-700 dark:text-gray-300">
{duration.toFixed(2)}s
</td>
<td className="px-6 py-5 whitespace-nowrap text-sm text-gray-600 dark:text-gray-400">
{trace.last_seen.toLocaleString()}
</td>
</tr>
)
})
)}
</tbody>
</table>
</div>
</div>
{/* Pagination */}
{totalPages > 1 && (
<div className="mt-8 flex justify-center items-center gap-4">
{page > 1 && (
<Link
href={`/observability/traces?page=${page - 1}${searchParams.trace_id ? `&trace_id=${searchParams.trace_id}` : ""}${searchParams.organization ? `&organization=${searchParams.organization}` : ""}`}
className="px-4 py-2 border border-gray-300 dark:border-gray-600 rounded hover:bg-gray-50 dark:hover:bg-gray-700 text-gray-900 dark:text-gray-100"
>
Previous
</Link>
)}
<span className="text-gray-600 dark:text-gray-400">
Page {page} of {totalPages} Showing {traces.length} traces
</span>
{page < totalPages && (
<Link
href={`/observability/traces?page=${page + 1}${searchParams.trace_id ? `&trace_id=${searchParams.trace_id}` : ""}${searchParams.organization ? `&organization=${searchParams.organization}` : ""}`}
className="px-4 py-2 border border-gray-300 dark:border-gray-600 rounded hover:bg-gray-50 dark:hover:bg-gray-700 text-gray-900 dark:text-gray-100"
>
Next
</Link>
)}
</div>
)}
</div>
)
} catch (error) {
throw error
}
}

View file

@ -0,0 +1,174 @@
import { PrismaClient } from "@prisma/client"
import { notFound } from "next/navigation"
import Link from "next/link"
import { ExperimentMetadata } from "@/lib/types" // Your defined types
import MonacoDiffViewer from "@/components/trace/monaco-diff-viewer" // The client component
import { Metadata } from "next" // For Next.js metadata API
import { getSession } from "@auth0/nextjs-auth0"
import { isTeamMember } from "@/app/utils/auth"
interface TraceDetailsPageProps {
params: {
trace_id: string
}
}
const prisma = new PrismaClient()
// Function to generate dynamic metadata (e.g., page title)
export async function generateMetadata({ params }: TraceDetailsPageProps): Promise<Metadata> {
const { trace_id } = params
// Optionally fetch minimal data for title generation to avoid over-fetching
// For simplicity, we'll use a generic title or one derived if data is fetched quickly
// A more optimized approach might involve a separate lightweight query or using default values.
const optimizationFeature = await prisma.optimization_features.findUnique({
where: { trace_id },
select: {
experiment_metadata: true,
organization: true,
repository: true,
review_quality: true,
review_explanation: true,
},
})
let title = `Python Diff Trace: ${trace_id.substring(0, 8)}`
if (optimizationFeature?.experiment_metadata) {
const metadata = optimizationFeature.experiment_metadata as unknown as ExperimentMetadata // Type assertion
const repoName =
optimizationFeature.organization && optimizationFeature.repository
? `${optimizationFeature.organization}/${optimizationFeature.repository}`
: metadata.owner && metadata.repo
? `${metadata.owner}/${metadata.repo}`
: ""
if (metadata.prCommentFields?.function_name) {
title = `Diff: ${metadata.prCommentFields.function_name} (${repoName})`
} else if (repoName) {
title = `Diff: ${repoName} - Trace ${trace_id.substring(0, 8)}`
}
}
return {
title: `${title} | Codeflash AI`,
description: `Review CodeFlash Python code optimization diffs for trace ID ${trace_id}.`,
// You can add more OpenGraph tags, etc.
}
}
// The main page component
export default async function TraceDetailsPage({ params }: TraceDetailsPageProps) {
const { trace_id } = params
if (!trace_id) {
// This case should ideally be handled by Next.js routing if trace_id is missing in URL structure
notFound()
}
const session = await getSession()
if (!session?.user) return null
// Check team member access - only team members can view traces
const hasTeamAccess = await isTeamMember()
if (!hasTeamAccess) {
// Create a custom access denied page or redirect to a generic error
return (
<div className="flex flex-col items-center justify-center h-screen bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900 text-slate-200">
<div className="text-center max-w-md">
<div className="mb-6">
<svg
className="w-16 h-16 mx-auto text-red-400"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z"
/>
</svg>
</div>
<h1 className="text-2xl font-bold text-red-400 mb-4">Access Denied</h1>
<p className="text-slate-300 mb-6">
This trace is restricted to CodeFlash team members only.
</p>
<div className="text-sm text-slate-400 mb-6">
<p>
Logged in as:{" "}
<span className="font-mono">{session.user.email || session.user.nickname}</span>
</p>
<p>
Trace ID: <span className="font-mono">{trace_id}</span>
</p>
</div>
<Link
href="/app"
className="inline-block bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded-lg transition-colors"
>
Go to Dashboard
</Link>
</div>
</div>
)
}
let optimizationFeature: {
experiment_metadata: unknown
metadata: unknown
organization: string | null
repository: string | null
review_quality: string | null
review_explanation: string | null
} | null = null
try {
optimizationFeature = await prisma.optimization_features.findUnique({
where: { trace_id: trace_id },
select: {
experiment_metadata: true, // Prisma handles JSONB parsing
metadata: true, // Include metadata field which stores modified code
organization: true,
repository: true,
review_quality: true,
review_explanation: true,
// Select other fields if needed by MonacoDiffViewer for its header/display
},
})
} catch (error) {
console.error(`[TracePage] Failed to fetch data for trace_id ${trace_id}:`, error)
// Optionally, render a specific error UI component here instead of notFound()
// For now, notFound() will trigger the 404 page, which is reasonable if data fetch fails badly.
// Or you could pass an error state to MonacoDiffViewer to display.
// For this detailed guide, we assume MonacoDiffViewer will handle 'null' metadata.
}
// If feature is not found, or metadata is explicitly null (and you expect it for valid traces)
if (!optimizationFeature) {
notFound() // Triggers the Next.js 404 page
}
// Type assertion is safe here due to the check above or if your DB guarantees metadata for valid traces.
// If experiment_metadata can be legitimately null for an existing trace_id, handle it gracefully.
// Pass experiment metadata directly since modifications are now stored in diffContents
const metadata = optimizationFeature.experiment_metadata as ExperimentMetadata | null
const review_quality = optimizationFeature.review_quality as string | null
const review_explanation = optimizationFeature.review_explanation as string | null
// Determine repository full name for display
const repoFullName =
optimizationFeature.organization && optimizationFeature.repository
? `${optimizationFeature.organization}/${optimizationFeature.repository}`
: metadata?.owner && metadata?.repo
? `${metadata.owner}/${metadata.repo}`
: "N/A"
return (
<MonacoDiffViewer
metadata={metadata} // Pass the potentially null metadata; component handles it
repoFullName={repoFullName}
traceId={trace_id}
review_quality={review_quality || ""}
review_explanation={review_explanation || ""}
/>
)
}

View file

@ -23,6 +23,7 @@ export function ConditionalLayout({
const shouldHideLayout =
pathname !== null && (
HIDDEN_PAGES.includes(pathname) ||
pathname.startsWith("/trace/") ||
pathname.startsWith("/observability") ||
!user
)

View file

@ -301,7 +301,7 @@ export function Sidebar({ className, user, isLoading, error }: SidebarProps): JS
{/* Observability Group */}
<Link
href="/observability"
href="/observability/traces"
target="_blank"
rel="noopener noreferrer"
className="block mb-1"

View file

@ -1,378 +0,0 @@
"use client"
import { useState, useMemo, useCallback, memo } from "react"
import { ChevronDown, Code, FileText, Hash, FolderTree } from "lucide-react"
import { CodeHighlighter, CODE_STYLE } from "./code-highlighter"
import { CopyButton } from "./copy-button"
import { InfoIcon } from "./info-icon"
interface CodeContextSectionProps {
functionName: string | null
filePath: string | null
originalCode: string | null
dependencyCode: string | null
}
interface ParsedFile {
path: string
filename: string
language: string
code: string
tokens: number
}
function estimateTokens(text: string): number {
return Math.ceil(text.length / 4)
}
function getFilename(path: string): string {
return path.split("/").pop() || path
}
function parseMarkdownCodeBlocks(markdown: string): ParsedFile[] {
const files: ParsedFile[] = []
const regex = /```(\w+):([^\n]+)\n([\s\S]*?)```/g
let match
while ((match = regex.exec(markdown)) !== null) {
const [, language, path, code] = match
files.push({
path,
filename: getFilename(path),
language: language || "python",
code: code.trimEnd(),
tokens: estimateTokens(code),
})
}
return files
}
export const CodeContextSection = memo(function CodeContextSection({
functionName,
filePath,
originalCode,
dependencyCode,
}: CodeContextSectionProps) {
const [isExpanded, setIsExpanded] = useState(false)
const [expandedSections, setExpandedSections] = useState<Set<string>>(new Set())
const [expandedFiles, setExpandedFiles] = useState<Set<string>>(new Set())
const { rwFiles, roFiles, metrics } = useMemo(() => {
const rwFiles = originalCode ? parseMarkdownCodeBlocks(originalCode) : []
const roFiles = dependencyCode ? parseMarkdownCodeBlocks(dependencyCode) : []
const rwTokens = rwFiles.reduce((sum, f) => sum + f.tokens, 0)
const roTokens = roFiles.reduce((sum, f) => sum + f.tokens, 0)
const totalFiles = rwFiles.length + roFiles.length
const totalTokens = rwTokens + roTokens
const rwChars = rwFiles.reduce((sum, f) => sum + f.code.length, 0)
const roChars = roFiles.reduce((sum, f) => sum + f.code.length, 0)
return {
rwFiles,
roFiles,
metrics: { rwTokens, roTokens, totalFiles, totalTokens, rwChars, roChars },
}
}, [originalCode, dependencyCode])
const toggleSection = useCallback((section: string): void => {
setExpandedSections(prev => {
const next = new Set(prev)
if (next.has(section)) {
next.delete(section)
} else {
next.add(section)
}
return next
})
}, [])
const toggleFile = useCallback((fileKey: string): void => {
setExpandedFiles(prev => {
const next = new Set(prev)
if (next.has(fileKey)) {
next.delete(fileKey)
} else {
next.add(fileKey)
}
return next
})
}, [])
if (!originalCode && !dependencyCode) {
return null
}
return (
<div className="bg-white dark:bg-zinc-900 rounded-sm border border-zinc-200 dark:border-zinc-800">
<div
role="button"
tabIndex={0}
onClick={() => setIsExpanded(!isExpanded)}
onKeyDown={e => { if (e.key === "Enter" || e.key === " ") setIsExpanded(!isExpanded) }}
className="w-full p-6 flex items-center justify-between hover:bg-zinc-50 dark:hover:bg-zinc-800 transition-colors rounded-sm cursor-pointer"
>
<div className="flex items-center gap-2">
<Code className="h-4 w-4 text-zinc-400" />
<h2 className="text-xl font-bold text-zinc-900 dark:text-white">Code Context</h2>
<InfoIcon
content="The code provided to the LLM for optimization. Read-writable code can be modified, read-only dependencies are for reference only."
side="right"
/>
</div>
<div className="flex items-center gap-4">
<div className="flex items-center gap-4 text-sm text-zinc-500 dark:text-zinc-400">
<span className="flex items-center gap-1">
<Hash className="h-4 w-4" />
{metrics.totalTokens.toLocaleString()} tokens
</span>
<span className="flex items-center gap-1">
<FolderTree className="h-4 w-4" />
{metrics.totalFiles} files
</span>
</div>
<ChevronDown className={`h-4 w-4 text-zinc-400 transition-transform duration-200 ${isExpanded ? '' : '-rotate-90'}`} />
</div>
</div>
{isExpanded && (
<div className="px-6 pb-6 pt-0 border-t border-zinc-200 dark:border-zinc-800 space-y-4">
{(functionName || filePath) && (
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 py-4">
{functionName && (
<div className="flex flex-col">
<span className="text-xs text-zinc-500 dark:text-zinc-400 mb-1">Function</span>
<code className="text-sm font-mono text-zinc-900 dark:text-white bg-zinc-100 dark:bg-zinc-800 px-2 py-1 rounded-sm w-fit">
{functionName}
</code>
</div>
)}
{filePath && (
<div className="flex flex-col">
<span className="text-xs text-zinc-500 dark:text-zinc-400 mb-1">File</span>
<code className="text-sm font-mono text-zinc-900 dark:text-white bg-zinc-100 dark:bg-zinc-800 px-2 py-1 rounded-sm w-fit truncate max-w-full" title={filePath}>
{filePath}
</code>
</div>
)}
</div>
)}
{rwFiles.length > 0 && roFiles.length > 0 && (
<div className="p-4 bg-zinc-50 dark:bg-zinc-950 rounded-sm border border-zinc-200 dark:border-zinc-800">
<div className="flex items-center gap-2 mb-3">
<Hash className="h-4 w-4 text-zinc-400" />
<span className="text-sm font-medium text-zinc-700 dark:text-zinc-300">
Token Distribution (estimated)
</span>
</div>
<TokenDistributionBar
rwTokens={metrics.rwTokens}
roTokens={metrics.roTokens}
totalTokens={metrics.totalTokens || 1}
/>
<div className="flex gap-4 text-xs">
<div className="flex items-center gap-1.5">
<div className="w-3 h-3 rounded-sm bg-emerald-500" />
<span className="text-zinc-600 dark:text-zinc-400">
Read-Writable: {metrics.rwTokens.toLocaleString()} ({rwFiles.length} files)
</span>
</div>
<div className="flex items-center gap-1.5">
<div className="w-3 h-3 rounded-sm bg-slate-500" />
<span className="text-zinc-600 dark:text-zinc-400">
Read-Only: {metrics.roTokens.toLocaleString()} ({roFiles.length} files)
</span>
</div>
</div>
</div>
)}
{rwFiles.length > 0 && (
<CodeGroupSection
title="Read-Writable Code"
subtitle="Function to optimize + first-degree helpers"
accentColor="emerald"
tokenCount={metrics.rwTokens}
charCount={metrics.rwChars}
files={rwFiles}
isExpanded={expandedSections.has("rw")}
onToggle={() => toggleSection("rw")}
expandedFiles={expandedFiles}
onToggleFile={toggleFile}
sectionKey="rw"
/>
)}
{roFiles.length > 0 && (
<CodeGroupSection
title="Read-Only Dependencies"
subtitle="Transitive dependencies for context"
accentColor="slate"
tokenCount={metrics.roTokens}
charCount={metrics.roChars}
files={roFiles}
isExpanded={expandedSections.has("ro")}
onToggle={() => toggleSection("ro")}
expandedFiles={expandedFiles}
onToggleFile={toggleFile}
sectionKey="ro"
/>
)}
</div>
)}
</div>
)
})
interface CodeGroupSectionProps {
title: string
subtitle: string
accentColor: "emerald" | "slate"
tokenCount: number
charCount: number
files: ParsedFile[]
isExpanded: boolean
onToggle: () => void
expandedFiles: Set<string>
onToggleFile: (fileKey: string) => void
sectionKey: string
}
function getAccentColorClasses(accentColor: "emerald" | "slate"): { border: string; bg: string; icon: string } {
switch (accentColor) {
case "emerald":
return {
border: "border-zinc-200 dark:border-zinc-800",
bg: "bg-zinc-50 dark:bg-zinc-900",
icon: "text-emerald-500",
}
case "slate":
return {
border: "border-zinc-200 dark:border-zinc-800",
bg: "bg-zinc-50 dark:bg-zinc-900",
icon: "text-zinc-500",
}
}
}
const CodeGroupSection = memo(function CodeGroupSection({
title,
subtitle,
accentColor,
tokenCount,
charCount,
files,
isExpanded,
onToggle,
expandedFiles,
onToggleFile,
sectionKey,
}: CodeGroupSectionProps) {
const { border: borderColor, bg: bgColor, icon: iconColor } = getAccentColorClasses(accentColor)
return (
<div className={`rounded-sm border ${borderColor} overflow-hidden`}>
<div
role="button"
tabIndex={0}
onClick={onToggle}
onKeyDown={e => { if (e.key === "Enter" || e.key === " ") onToggle() }}
className={`w-full p-4 flex items-center justify-between hover:bg-zinc-100 dark:hover:bg-zinc-800 transition-colors cursor-pointer ${bgColor}`}
>
<div className="flex items-center gap-3">
<FileText className={`h-4 w-4 ${iconColor}`} />
<div className="text-left">
<span className="text-sm font-medium text-zinc-900 dark:text-white">{title}</span>
<span className="text-xs text-zinc-500 dark:text-zinc-400 ml-2">{subtitle}</span>
</div>
</div>
<div className="flex items-center gap-3">
<span className="text-xs text-zinc-500 dark:text-zinc-400">
{tokenCount.toLocaleString()} tokens · {charCount.toLocaleString()} chars · {files.length} files
</span>
<ChevronDown className={`h-4 w-4 text-zinc-400 transition-transform duration-200 ${isExpanded ? '' : '-rotate-90'}`} />
</div>
</div>
{isExpanded && (
<div className="border-t border-zinc-200 dark:border-zinc-800 divide-y divide-zinc-200 dark:divide-zinc-800">
{files.map((file, index) => {
const fileKey = `${sectionKey}-${index}`
const isFileExpanded = expandedFiles.has(fileKey)
return (
<div key={fileKey}>
<div className="px-4 py-2 flex items-center justify-between bg-zinc-100 dark:bg-zinc-950">
<div
role="button"
tabIndex={0}
onClick={() => onToggleFile(fileKey)}
onKeyDown={e => { if (e.key === "Enter" || e.key === " ") onToggleFile(fileKey) }}
className="flex items-center gap-2 cursor-pointer hover:opacity-80 flex-1"
>
<FileText className="h-3.5 w-3.5 text-zinc-400" />
<span className="text-sm font-mono font-medium text-zinc-900 dark:text-white">
{file.filename}
</span>
<span className="text-xs text-zinc-500 dark:text-zinc-400" title={file.path}>
{file.path !== file.filename && `(${file.path})`}
</span>
<ChevronDown className={`h-3.5 w-3.5 text-zinc-400 transition-transform duration-200 ${isFileExpanded ? '' : '-rotate-90'}`} />
</div>
<div className="flex items-center gap-2">
<span className="text-xs text-zinc-500 dark:text-zinc-400">
{file.code.split("\n").length} lines
</span>
<CopyButton text={file.code} label={file.filename} size="sm" />
</div>
</div>
{isFileExpanded && (
<div className="max-h-96 overflow-y-auto">
<CodeHighlighter
language={file.language}
code={file.code}
customStyle={CODE_STYLE}
/>
</div>
)}
</div>
)
})}
</div>
)}
</div>
)
})
interface TokenDistributionBarProps {
rwTokens: number
roTokens: number
totalTokens: number
}
const TokenDistributionBar = memo(function TokenDistributionBar({
rwTokens,
roTokens,
totalTokens,
}: TokenDistributionBarProps) {
const rwPercent = Math.round((rwTokens / totalTokens) * 100)
const roPercent = Math.round((roTokens / totalTokens) * 100)
return (
<div className="flex h-6 rounded-sm overflow-hidden border border-zinc-200 dark:border-zinc-800 mb-2">
<div
className="bg-emerald-500 flex items-center justify-center text-white text-xs font-semibold"
style={{ width: `${rwPercent}%` }}
>
{rwPercent > 15 && `${rwPercent}%`}
</div>
<div
className="bg-slate-500 flex items-center justify-center text-white text-xs font-semibold"
style={{ width: `${roPercent}%` }}
>
{roPercent > 15 && `${roPercent}%`}
</div>
</div>
)
})

View file

@ -1,172 +0,0 @@
"use client"
import dynamic from "next/dynamic"
import { memo } from "react"
const SyntaxHighlighter = dynamic(
() => import("react-syntax-highlighter").then(m => m.Prism),
{
ssr: false,
loading: () => (
<div className="animate-pulse bg-zinc-800 rounded p-4 min-h-[100px]">
<div className="h-4 bg-zinc-700 rounded w-3/4 mb-2" />
<div className="h-4 bg-zinc-700 rounded w-1/2 mb-2" />
<div className="h-4 bg-zinc-700 rounded w-2/3" />
</div>
),
}
)
export const zincDarkTheme = {
'code[class*="language-"]': {
color: 'rgb(250, 250, 250)',
background: 'none',
fontFamily: 'var(--font-mono)',
fontSize: '1em',
textAlign: 'left',
whiteSpace: 'pre',
wordSpacing: 'normal',
wordBreak: 'normal',
wordWrap: 'normal',
lineHeight: '1.5',
tabSize: 4,
hyphens: 'none',
},
'pre[class*="language-"]': {
color: 'rgb(250, 250, 250)',
background: 'rgb(24, 24, 27)',
fontFamily: 'var(--font-mono)',
fontSize: '1em',
textAlign: 'left',
whiteSpace: 'pre',
wordSpacing: 'normal',
wordBreak: 'normal',
wordWrap: 'normal',
lineHeight: '1.5',
tabSize: 4,
hyphens: 'none',
padding: '1em',
margin: '0',
overflow: 'auto',
},
comment: {
color: 'rgb(113, 113, 122)',
fontStyle: 'italic',
},
prolog: { color: 'rgb(113, 113, 122)' },
doctype: { color: 'rgb(113, 113, 122)' },
cdata: { color: 'rgb(113, 113, 122)' },
keyword: { color: 'rgb(96, 165, 250)' },
'control-flow': { color: 'rgb(96, 165, 250)' },
string: { color: 'rgb(134, 239, 172)' },
'attr-value': { color: 'rgb(134, 239, 172)' },
function: { color: 'rgb(253, 224, 71)' },
'class-name': { color: 'rgb(253, 224, 71)' },
number: { color: 'rgb(251, 146, 60)' },
boolean: { color: 'rgb(251, 146, 60)' },
operator: { color: 'rgb(161, 161, 170)' },
punctuation: { color: 'rgb(161, 161, 170)' },
variable: { color: 'rgb(250, 250, 250)' },
property: { color: 'rgb(250, 250, 250)' },
tag: { color: 'rgb(96, 165, 250)' },
'attr-name': { color: 'rgb(250, 250, 250)' },
namespace: { opacity: 0.7 },
selector: { color: 'rgb(253, 224, 71)' },
important: {
color: 'rgb(251, 146, 60)',
fontWeight: 'bold',
},
atrule: { color: 'rgb(96, 165, 250)' },
builtin: { color: 'rgb(253, 224, 71)' },
entity: {
color: 'rgb(250, 250, 250)',
cursor: 'help',
},
url: {
color: 'rgb(96, 165, 250)',
textDecoration: 'underline',
},
inserted: {
color: 'rgb(134, 239, 172)',
background: 'rgba(134, 239, 172, 0.1)',
},
deleted: {
color: 'rgb(248, 113, 113)',
background: 'rgba(248, 113, 113, 0.1)',
},
} as const
export const CODE_STYLE = {
margin: 0,
padding: "1rem",
fontSize: "0.875rem",
lineHeight: 1.5,
background: 'rgb(24, 24, 27)',
} as const
export const CODE_STYLE_RELAXED = {
margin: 0,
padding: "1rem",
fontSize: "0.875rem",
lineHeight: 1.6,
background: 'rgb(24, 24, 27)',
} as const
export const CODE_STYLE_SMALL = {
margin: 0,
padding: "1rem",
fontSize: "0.8125rem",
lineHeight: 1.5,
background: 'rgb(24, 24, 27)',
} as const
interface CodeHighlighterProps {
code: string
language: string
showLineNumbers?: boolean
customStyle?: React.CSSProperties
highlightLines?: number[]
}
const highlightStyle = {
backgroundColor: 'rgba(250, 204, 21, 0.15)',
display: 'block',
marginLeft: '-1rem',
marginRight: '-1rem',
paddingLeft: '1rem',
paddingRight: '1rem',
borderLeft: '3px solid rgb(250, 204, 21)',
}
export const CodeHighlighter = memo(function CodeHighlighter({
code,
language,
showLineNumbers = true,
customStyle = CODE_STYLE,
highlightLines,
}: CodeHighlighterProps) {
const lineProps = highlightLines && highlightLines.length > 0
? (lineNumber: number) => {
const isHighlighted = highlightLines.includes(lineNumber)
return {
style: isHighlighted ? highlightStyle : { display: 'block' },
'data-highlighted': isHighlighted ? 'true' : undefined,
}
}
: undefined
const shouldWrapLines = !!(highlightLines && highlightLines.length > 0)
return (
<SyntaxHighlighter
language={language}
style={zincDarkTheme}
customStyle={customStyle}
showLineNumbers={showLineNumbers}
wrapLines={shouldWrapLines}
lineProps={shouldWrapLines ? lineProps : undefined}
>
{code}
</SyntaxHighlighter>
)
})

View file

@ -0,0 +1,27 @@
"use client"
import { InfoIcon } from "./info-icon"
import { cn } from "@/lib/utils"
interface ColumnHeaderProps {
label: string
tooltip: string
className?: string
}
export function ColumnHeader({ label, tooltip, className }: ColumnHeaderProps) {
return (
<th
className={cn(
"px-6 py-3 text-left text-xs font-medium text-gray-700 dark:text-gray-300",
"uppercase tracking-wider",
className,
)}
>
<div className="flex items-center gap-1.5">
<span>{label}</span>
<InfoIcon content={tooltip} side="top" />
</div>
</th>
)
}

View file

@ -1,201 +0,0 @@
"use client"
import { useState, useCallback, memo } from "react"
import {
XCircle,
AlertCircle,
AlertTriangle,
ChevronDown,
} from "lucide-react"
import { CopyButton } from "./copy-button"
interface ErrorContext {
test_name?: string
failure_reason?: string
test_output?: string
expected?: string
actual?: string
}
interface TraceError {
id: string
error_type: string
severity: string
error_message: string
context: ErrorContext | null
created_at: Date
}
interface ErrorsSectionProps {
errors: TraceError[]
}
export const ErrorsSection = memo(function ErrorsSection({ errors }: ErrorsSectionProps) {
const [expandedErrors, setExpandedErrors] = useState<Set<string>>(new Set())
const toggleError = useCallback((errorId: string) => {
setExpandedErrors(prev => {
const next = new Set(prev)
if (next.has(errorId)) {
next.delete(errorId)
} else {
next.add(errorId)
}
return next
})
}, [])
if (errors.length === 0) {
return null
}
return (
<div className="bg-white dark:bg-zinc-900 rounded-sm border border-zinc-200 dark:border-zinc-700">
<div className="p-6 border-b border-zinc-200 dark:border-zinc-700">
<div className="flex items-center gap-3">
<AlertCircle className="h-5 w-5 text-red-600 dark:text-red-400" />
<h2 className="text-xl font-bold text-red-600 dark:text-red-400">Errors</h2>
<span className="text-red-400 border border-red-600 text-sm px-2.5 py-0.5 rounded-sm font-semibold">
{errors.length}
</span>
</div>
</div>
<div className="divide-y divide-zinc-200 dark:divide-zinc-700">
{errors.map(error => {
const isExpanded = expandedErrors.has(error.id)
const isTestFailure = error.error_type === "test_failure"
const hasContext = error.context && Object.keys(error.context).length > 0
const SeverityIcon =
error.severity === "critical" || error.severity === "error" ? XCircle : AlertTriangle
let severityColor: string
if (error.severity === "critical") {
severityColor = "text-red-400 border border-red-600 px-1.5 py-0.5"
} else if (error.severity === "error") {
severityColor = "text-orange-400 border border-orange-600 px-1.5 py-0.5"
} else {
severityColor = "text-yellow-400 border border-yellow-600 px-1.5 py-0.5"
}
return (
<div key={error.id} className="border-l-4 border-red-500">
<div className="w-full p-4 flex items-start gap-3">
<div
role="button"
tabIndex={0}
onClick={() => toggleError(error.id)}
onKeyDown={e => { if (e.key === "Enter" || e.key === " ") toggleError(error.id) }}
className="flex items-start gap-3 cursor-pointer hover:opacity-80 flex-1 transition-opacity duration-150"
>
<SeverityIcon className="h-5 w-5 text-red-600 dark:text-red-400 mt-0.5 flex-shrink-0" />
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-1 flex-wrap">
<span className="font-semibold text-zinc-900 dark:text-white">
{error.error_type}
</span>
<span className={`text-xs font-semibold rounded-sm ${severityColor}`}>
{error.severity}
</span>
<span className="text-xs text-zinc-500 dark:text-zinc-400 ml-auto">
{new Date(error.created_at).toLocaleString()}
</span>
</div>
<p className="text-sm text-zinc-700 dark:text-zinc-300 line-clamp-2">
{error.error_message}
</p>
</div>
{(hasContext || isTestFailure) && (
<ChevronDown className={`h-4 w-4 text-zinc-400 transition-transform duration-200 ${isExpanded ? '' : '-rotate-90'}`} />
)}
</div>
<div className="flex items-center gap-2">
<CopyButton text={error.error_message} label="error message" size="sm" />
</div>
</div>
{isExpanded && isTestFailure && error.context && (
<div className="px-4 pb-4 ml-8">
<div className="p-4 bg-zinc-50 dark:bg-zinc-800 rounded-sm border border-zinc-200 dark:border-zinc-700">
<h4 className="text-sm font-semibold text-red-600 dark:text-red-400 mb-3">
Test Failure Details
</h4>
{error.context.test_name && (
<div className="mb-3">
<span className="text-xs font-medium text-zinc-600 dark:text-zinc-400 uppercase tracking-wide">
Test Name
</span>
<p className="text-sm text-zinc-900 dark:text-white font-mono mt-1">
{error.context.test_name}
</p>
</div>
)}
{error.context.failure_reason && (
<div className="mb-3">
<span className="text-xs font-medium text-zinc-600 dark:text-zinc-400 uppercase tracking-wide">
Failure Reason
</span>
<p className="text-sm text-zinc-900 dark:text-white mt-1 whitespace-pre-wrap">
{error.context.failure_reason}
</p>
</div>
)}
{error.context.expected && (
<div className="mb-3">
<span className="text-xs font-medium text-zinc-600 dark:text-zinc-400 uppercase tracking-wide">
Expected
</span>
<pre className="text-xs text-zinc-900 dark:text-white mt-1 bg-white dark:bg-zinc-900 p-2 rounded-sm overflow-x-auto font-mono">
{String(error.context.expected)}
</pre>
</div>
)}
{error.context.actual && (
<div className="mb-3">
<span className="text-xs font-medium text-zinc-600 dark:text-zinc-400 uppercase tracking-wide">
Actual
</span>
<pre className="text-xs text-zinc-900 dark:text-white mt-1 bg-white dark:bg-zinc-900 p-2 rounded-sm overflow-x-auto font-mono">
{String(error.context.actual)}
</pre>
</div>
)}
{error.context.test_output && (
<div>
<span className="text-xs font-medium text-zinc-600 dark:text-zinc-400 uppercase tracking-wide">
Test Output
</span>
<pre className="text-xs text-zinc-900 dark:text-white mt-1 bg-white dark:bg-zinc-900 p-2 rounded-sm overflow-x-auto whitespace-pre-wrap font-mono max-h-48">
{String(error.context.test_output)}
</pre>
</div>
)}
</div>
</div>
)}
{isExpanded && !isTestFailure && hasContext && (
<div className="px-4 pb-4 ml-8">
<div className="p-4 bg-zinc-50 dark:bg-zinc-900 rounded-sm border border-zinc-200 dark:border-zinc-700">
<span className="text-xs font-medium text-zinc-600 dark:text-zinc-400 uppercase tracking-wide">
Context
</span>
<pre className="text-xs text-zinc-900 dark:text-white mt-2 overflow-auto whitespace-pre-wrap font-mono max-h-48">
{JSON.stringify(error.context, null, 2)}
</pre>
</div>
</div>
)}
</div>
)
})}
</div>
</div>
)
})

View file

@ -1,204 +0,0 @@
"use client"
import { memo, useMemo, useState, useRef, useEffect } from "react"
import { Code, FileText, ChevronDown } from "lucide-react"
import { CodeHighlighter, CODE_STYLE_RELAXED } from "./code-highlighter"
import { CopyButton } from "./copy-button"
import { findFunctionInCode, type FunctionLocation } from "./python-parser"
interface FunctionToOptimizeSectionProps {
functionName: string | null
filePath: string | null
originalCode: string | null
}
interface ParsedFile {
path: string
filename: string
language: string
code: string
}
function getFilename(path: string): string {
return path.split("/").pop() || path
}
function parseMarkdownCodeBlocks(markdown: string): ParsedFile[] {
const files: ParsedFile[] = []
const regex = /```(\w+):([^\n]+)\n([\s\S]*?)```/g
let match
while ((match = regex.exec(markdown)) !== null) {
const [, language, path, code] = match
files.push({
path,
filename: getFilename(path),
language: language || "python",
code: code.trimEnd(),
})
}
return files
}
export const FunctionToOptimizeSection = memo(function FunctionToOptimizeSection({
functionName,
filePath,
originalCode,
}: FunctionToOptimizeSectionProps) {
const [isExpanded, setIsExpanded] = useState(true)
const [functionLocation, setFunctionLocation] = useState<FunctionLocation | null>(null)
const [actualFile, setActualFile] = useState<ParsedFile | null>(null)
const codeContainerRef = useRef<HTMLDivElement>(null)
const allFiles = useMemo(() => {
if (!originalCode) return []
return parseMarkdownCodeBlocks(originalCode)
}, [originalCode])
useEffect(() => {
if (!functionName || allFiles.length === 0) {
setFunctionLocation(null)
setActualFile(null)
return
}
let cancelled = false
async function findFunction() {
const searchPromises = allFiles.map(async (file) => {
const location = await findFunctionInCode(file.code, functionName!)
return location ? { file, location } : null
})
const results = await Promise.all(searchPromises)
if (cancelled) return
const found = results.find(r => r !== null)
if (found) {
setFunctionLocation(found.location)
setActualFile(found.file)
return
}
let fallbackFile = allFiles[0]
if (filePath) {
const match = allFiles.find(f =>
filePath.endsWith(f.path) || f.path.endsWith(filePath) || f.path === filePath
)
if (match) fallbackFile = match
}
setFunctionLocation(null)
setActualFile(fallbackFile)
}
findFunction()
return () => { cancelled = true }
}, [functionName, filePath, allFiles])
const functionFile = actualFile ?? allFiles[0] ?? null
const functionLines = useMemo(() => {
if (!functionLocation) return null
const lines: number[] = []
for (let i = functionLocation.startLine; i <= functionLocation.endLine; i++) {
lines.push(i)
}
return lines
}, [functionLocation])
useEffect(() => {
if (!isExpanded || !functionLocation || !codeContainerRef.current) return
const scrollToFunction = () => {
if (!codeContainerRef.current) return
const container = codeContainerRef.current
const lineHeight = 22.4
const paddingTop = 16
const targetLine = functionLocation.startLine - 1
const scrollPosition = paddingTop + (targetLine * lineHeight) - (container.clientHeight / 3)
container.scrollTo({
top: Math.max(0, scrollPosition),
behavior: 'smooth'
})
}
const timer = setTimeout(scrollToFunction, 300)
return () => clearTimeout(timer)
}, [isExpanded, functionLocation])
if (!functionFile) {
return null
}
const highlightLines = functionLines && functionLines.length > 0 ? functionLines : undefined
return (
<div className="bg-white dark:bg-zinc-900 rounded-sm border border-zinc-200 dark:border-zinc-800 overflow-hidden">
<div className="w-full p-4 bg-zinc-950 flex items-center justify-between">
<div
role="button"
tabIndex={0}
onClick={() => setIsExpanded(!isExpanded)}
onKeyDown={e => { if (e.key === "Enter" || e.key === " ") setIsExpanded(!isExpanded) }}
className="flex items-center gap-3 cursor-pointer hover:opacity-80 flex-1"
>
<div className="p-2 bg-zinc-800 rounded-sm">
<Code className="h-4 w-4 text-zinc-400" />
</div>
<div className="text-left">
<h2 className="text-lg font-semibold text-zinc-50">
Function to Optimize
</h2>
<div className="flex items-center gap-2 mt-1">
{functionName && (
<code className="text-sm font-mono text-zinc-300 bg-zinc-800 px-2 py-0.5 rounded-sm">
{functionName}
</code>
)}
{functionLocation && (
<span className="text-xs text-zinc-500">
lines {functionLocation.startLine}-{functionLocation.endLine}
</span>
)}
</div>
</div>
<ChevronDown className={`h-4 w-4 text-zinc-500 transition-transform duration-200 ${isExpanded ? '' : '-rotate-90'}`} />
</div>
<div className="flex items-center gap-2">
<CopyButton text={functionFile.code} label="function code" />
</div>
</div>
{isExpanded && (
<>
<div className="px-4 py-2 bg-zinc-900 border-t border-b border-zinc-800 flex items-center gap-2">
<FileText className="h-4 w-4 text-zinc-500" />
<span className="text-sm font-mono font-medium text-zinc-300">
{functionFile.filename}
</span>
{functionFile.path !== functionFile.filename && (
<span className="text-xs font-mono text-zinc-500">
({functionFile.path})
</span>
)}
<span className="text-xs text-zinc-500 ml-auto">
{functionFile.code.split("\n").length} lines
</span>
</div>
<div ref={codeContainerRef} className="max-h-[500px] overflow-y-auto">
<CodeHighlighter
language={functionFile.language}
code={functionFile.code}
customStyle={CODE_STYLE_RELAXED}
highlightLines={highlightLines}
/>
</div>
</>
)}
</div>
)
})

View file

@ -0,0 +1,51 @@
"use client"
import { Info } from "lucide-react"
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
import { cn } from "@/lib/utils"
interface HelpButtonProps {
title: string
content: React.ReactNode
size?: "sm" | "md"
triggerClassName?: string
}
export function HelpButton({ title, content, size = "sm", triggerClassName }: HelpButtonProps) {
return (
<Dialog>
<DialogTrigger asChild>
<button
type="button"
className={cn(
"inline-flex items-center justify-center gap-2",
"text-gray-600 hover:text-blue-600 dark:text-gray-400 dark:hover:text-blue-400",
"transition-colors duration-200",
"focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 rounded-md",
size === "sm" ? "p-2" : "px-3 py-2",
triggerClassName,
)}
aria-label={`Open help: ${title}`}
>
<Info className={size === "sm" ? "h-5 w-5" : "h-4 w-4"} />
{size === "md" && <span className="text-sm font-medium">Help</span>}
</button>
</DialogTrigger>
<DialogContent className="max-w-2xl max-h-[80vh] overflow-y-auto">
<DialogHeader>
<DialogTitle>{title}</DialogTitle>
<DialogDescription asChild>
<div className="text-left pt-4">{content}</div>
</DialogDescription>
</DialogHeader>
</DialogContent>
</Dialog>
)
}

View file

@ -1,9 +0,0 @@
export { TraceSearch } from "./trace-search"
export { TraceSummary } from "./trace-summary"
export { TimelinePageView } from "./timeline-page-view"
export { transformToTimelineSections } from "./timeline-types"
export { ErrorsSection } from "./errors-section"
export { CodeHighlighter, CODE_STYLE, CODE_STYLE_RELAXED, CODE_STYLE_SMALL } from "./code-highlighter"
export { CopyButton } from "./copy-button"
export { InfoIcon } from "./info-icon"
export { getTraceSource } from "./utils"

View file

@ -0,0 +1,55 @@
"use client"
import Link from "next/link"
import { usePathname } from "next/navigation"
import { Activity, ListTree } from "lucide-react"
import { cn } from "@/lib/utils"
const navItems = [
{ href: "/observability/traces", label: "Traces", icon: ListTree },
{ href: "/observability/llm-calls", label: "LLM Calls", icon: Activity },
]
export function ObservabilityNav() {
const pathname = usePathname()
return (
<nav className="bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 sticky top-0 z-10">
<div className="container mx-auto px-6 py-3">
<div className="flex items-center justify-between">
{/* Logo/Title */}
<div className="flex items-center gap-2">
<Activity className="h-6 w-6 text-blue-600 dark:text-blue-400" />
<h1 className="text-xl font-bold text-gray-900 dark:text-white">
Codeflash Observability
</h1>
</div>
{/* Navigation Links */}
<div className="flex items-center gap-2">
{navItems.map(item => {
const Icon = item.icon
const isActive = pathname === item.href || pathname.startsWith(item.href + "/")
return (
<Link
key={item.href}
href={item.href}
className={cn(
"flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium transition-colors",
isActive
? "bg-blue-50 dark:bg-blue-900/20 text-blue-700 dark:text-blue-300"
: "text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-gray-900 dark:hover:text-white",
)}
>
<Icon className="h-4 w-4" />
{item.label}
</Link>
)
})}
</div>
</div>
</div>
</nav>
)
}

View file

@ -0,0 +1,223 @@
"use client"
import {
extractExplainTag,
extractRankTag,
getResponseContentForParsing,
splitMarkdownCodeBlocks,
type ResponseSegment,
} from "@/lib/observability-response-parse"
import { CopyButton } from "@/components/observability/copy-button"
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"
import { oneDark } from "react-syntax-highlighter/dist/esm/styles/prism"
import { useState } from "react"
interface ParsedResponseViewProps {
rawResponse: string
callType: string | null
}
/** Try to parse JSON and return pretty-printed version, or null if not JSON */
function tryFormatJSON(content: string): string | null {
try {
const parsed = JSON.parse(content)
return JSON.stringify(parsed, null, 2)
} catch {
return null
}
}
export function ParsedResponseView({ rawResponse, callType }: ParsedResponseViewProps) {
const [showRaw, setShowRaw] = useState(false)
const isRanking = callType === "ranking"
// Use inner message content when raw_response is API JSON (e.g. OpenAI)
const contentForParsing = getResponseContentForParsing(rawResponse)
const rankContent = isRanking ? extractRankTag(contentForParsing) : null
const explainContent = isRanking ? extractExplainTag(contentForParsing) : null
const hasRankingSections = isRanking && (rankContent != null || explainContent != null)
const segments: ResponseSegment[] = hasRankingSections
? []
: splitMarkdownCodeBlocks(contentForParsing)
const hasSegments = segments.length > 0
// Check if raw response is JSON (for fallback display)
const formattedJSON = tryFormatJSON(rawResponse)
const isJSON = formattedJSON != null
return (
<div className="space-y-4">
{/* View raw toggle button - always visible in header */}
<div className="flex items-center justify-end gap-2 pb-2 border-b border-gray-200 dark:border-gray-700">
<button
onClick={() => setShowRaw(!showRaw)}
className="text-sm font-medium text-gray-600 dark:text-gray-400 hover:text-blue-600 dark:hover:text-blue-400 flex items-center gap-1.5"
>
<span className={showRaw ? "rotate-90 transition-transform" : "transition-transform"}>
</span>
{showRaw ? "Hide raw" : "View raw"}
</button>
<CopyButton text={rawResponse} label="raw response" size="sm" />
</div>
{showRaw && (
<div className="rounded-lg border border-gray-200 dark:border-gray-700 overflow-hidden">
<pre className="bg-gray-50 dark:bg-gray-900 p-4 text-sm overflow-auto whitespace-pre-wrap text-gray-900 dark:text-gray-100 font-mono max-h-96">
{rawResponse}
</pre>
</div>
)}
{!showRaw && (
<div className="space-y-4">
{hasRankingSections && (
<div className="space-y-4">
{rankContent != null && (
<div className="rounded-lg border border-gray-200 dark:border-gray-700 overflow-hidden">
<div className="px-4 py-2 bg-gray-100 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between">
<h3 className="text-sm font-semibold text-gray-700 dark:text-gray-300">
Ranking (best first)
</h3>
<CopyButton text={rankContent} label="ranking" size="sm" />
</div>
<div className="p-4 bg-gray-50 dark:bg-gray-900">
<ol className="flex flex-wrap gap-2 list-none">
{rankContent
.split(/[\s,]+/)
.filter(Boolean)
.map((id, i) => {
const pos = i + 1
const label =
pos === 1
? "1st"
: pos === 2
? "2nd"
: pos === 3
? "3rd"
: `${pos}th`
return (
<li
key={`${id}-${i}`}
className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-md bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 text-sm font-mono"
>
<span className="text-gray-500 dark:text-gray-400 font-medium">
{label}
</span>
<span className="text-gray-900 dark:text-gray-100">
{id.trim()}
</span>
</li>
)
})}
</ol>
</div>
</div>
)}
{explainContent != null && (
<div className="rounded-lg border border-gray-200 dark:border-gray-700 overflow-hidden">
<div className="px-4 py-2 bg-gray-100 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700">
<h3 className="text-sm font-semibold text-gray-700 dark:text-gray-300">
Explanation
</h3>
</div>
<div className="p-4 bg-gray-50 dark:bg-gray-900">
<div className="flex items-start gap-2">
<p className="flex-1 text-sm text-gray-700 dark:text-gray-300 whitespace-pre-wrap leading-relaxed">
{explainContent}
</p>
<CopyButton text={explainContent} label="explanation" size="sm" />
</div>
</div>
</div>
)}
</div>
)}
{!hasRankingSections && hasSegments && (
<div className="space-y-4">
{segments.map((seg, i) => {
const textJSON = seg.kind === "text" ? tryFormatJSON(seg.content) : null
return seg.kind === "text" ? (
<div key={i} className="rounded-lg border border-gray-200 dark:border-gray-700 overflow-hidden">
{textJSON ? (
<SyntaxHighlighter
language="json"
style={oneDark}
PreTag="div"
customStyle={{
margin: 0,
padding: "1rem",
fontSize: "0.875rem",
lineHeight: 1.5,
}}
codeTagProps={{ className: "text-sm" }}
showLineNumbers={textJSON.split("\n").length > 10}
>
{textJSON}
</SyntaxHighlighter>
) : (
<pre className="bg-gray-50 dark:bg-gray-900 p-4 text-sm overflow-auto whitespace-pre-wrap text-gray-900 dark:text-gray-100 font-mono">
{seg.content}
</pre>
)}
</div>
) : (
<div key={i} className="rounded-lg overflow-hidden border border-gray-200 dark:border-gray-700">
<div className="flex items-center justify-between px-3 py-1.5 bg-gray-100 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700">
<span className="text-xs font-medium text-gray-600 dark:text-gray-400">
{seg.language || "code"}
</span>
<CopyButton text={seg.content} label="code block" size="sm" />
</div>
<SyntaxHighlighter
language={seg.language === "text" ? "plaintext" : seg.language}
style={oneDark}
PreTag="div"
customStyle={{
margin: 0,
padding: "1rem",
fontSize: "0.875rem",
lineHeight: 1.5,
}}
codeTagProps={{ className: "text-sm" }}
showLineNumbers={seg.content.split("\n").length > 5}
>
{seg.content}
</SyntaxHighlighter>
</div>
)
})}
</div>
)}
{!hasRankingSections && !hasSegments && (
<div className="rounded-lg border border-gray-200 dark:border-gray-700 overflow-hidden">
{isJSON ? (
<SyntaxHighlighter
language="json"
style={oneDark}
PreTag="div"
customStyle={{
margin: 0,
padding: "1rem",
fontSize: "0.875rem",
lineHeight: 1.5,
}}
codeTagProps={{ className: "text-sm" }}
showLineNumbers={formattedJSON.split("\n").length > 10}
>
{formattedJSON}
</SyntaxHighlighter>
) : (
<pre className="bg-gray-50 dark:bg-gray-900 p-4 text-sm overflow-auto whitespace-pre-wrap text-gray-900 dark:text-gray-100 font-mono max-h-96">
{rawResponse}
</pre>
)}
</div>
)}
</div>
)}
</div>
)
}

View file

@ -1,136 +0,0 @@
"use client"
import type { Node, Parser as ParserType } from "web-tree-sitter"
export interface FunctionLocation {
startLine: number
endLine: number
}
let parserPromise: Promise<ParserType | null> | null = null
async function getParser(): Promise<ParserType | null> {
if (typeof window === "undefined") {
return null
}
if (!parserPromise) {
parserPromise = (async () => {
try {
const { Parser, Language } = await import("web-tree-sitter")
await Parser.init({
locateFile: (scriptName: string) => `/${scriptName}`,
})
const parser = new Parser()
const Python = await Language.load("/tree-sitter-python.wasm")
parser.setLanguage(Python)
return parser
} catch (error) {
console.error("Tree-sitter initialization failed:", error)
parserPromise = null
throw error
}
})()
}
return parserPromise
}
export async function findFunctionInCode(
code: string,
functionName: string
): Promise<FunctionLocation | null> {
try {
const parser = await getParser()
if (parser) {
const tree = parser.parse(code)
if (tree) {
const result = findFunctionNode(tree.rootNode, functionName)
if (result) {
return {
startLine: result.startPosition.row + 1,
endLine: result.endPosition.row + 1,
}
}
}
}
} catch (error) {
console.warn("Tree-sitter parse failed, trying regex fallback:", error)
}
return findFunctionWithRegex(code, functionName)
}
function findFunctionWithRegex(
code: string,
functionName: string
): FunctionLocation | null {
const lines = code.split("\n")
const defPattern = new RegExp(
`^(\\s*)(async\\s+)?def\\s+${escapeRegex(functionName)}\\s*\\(`
)
let startLine = -1
let startIndent = -1
for (let i = 0; i < lines.length; i++) {
const line = lines[i]
if (startLine === -1) {
const match = line.match(defPattern)
if (match) {
startLine = i + 1
startIndent = match[1].length
}
} else {
const trimmed = line.trim()
if (trimmed === "" || trimmed.startsWith("#")) {
continue
}
const currentIndent = line.length - line.trimStart().length
if (currentIndent <= startIndent) {
return { startLine, endLine: i }
}
}
}
if (startLine !== -1) {
return { startLine, endLine: lines.length }
}
return null
}
function escapeRegex(str: string): string {
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
}
function findFunctionNode(node: Node, functionName: string): Node | null {
if (
node.type === "function_definition" ||
node.type === "async_function_definition"
) {
const nameNode = node.childForFieldName("name")
if (nameNode && nameNode.text === functionName) {
return node
}
}
if (node.type === "class_definition") {
const classBody = node.childForFieldName("body")
if (classBody) {
for (const child of classBody.children) {
const result = findFunctionNode(child, functionName)
if (result) return result
}
}
}
for (const child of node.children) {
const result = findFunctionNode(child, functionName)
if (result) return result
}
return null
}

View file

@ -0,0 +1,72 @@
"use client"
import {
Database,
Zap,
DollarSign,
AlertTriangle,
Activity,
CheckCircle2,
Clock,
} from "lucide-react"
import { InfoIcon } from "./info-icon"
import { cn } from "@/lib/utils"
const variantStyles = {
default: "",
success: "border-l-4 border-green-500",
warning: "border-l-4 border-yellow-500",
error: "border-l-4 border-red-500",
}
// Icon mapping - add more icons as needed
const iconMap = {
Database,
Zap,
DollarSign,
AlertTriangle,
Activity,
CheckCircle2,
Clock,
} as const
type IconName = keyof typeof iconMap
interface StatCardProps {
label: string
value: string | number
helpText?: string
icon?: IconName
variant?: "default" | "success" | "warning" | "error"
className?: string
}
export function StatCard({ label, value, helpText, icon, variant = "default", className }: StatCardProps) {
const IconComponent = icon ? iconMap[icon] : null
return (
<div
className={cn(
"bg-white dark:bg-gray-800 rounded-lg shadow p-5 border border-gray-200 dark:border-gray-700",
"hover:scale-[1.02] hover:shadow-lg transition-all duration-200",
variantStyles[variant],
className,
)}
>
<div className="flex items-start justify-between">
<div className="flex-1">
<div className="flex items-center gap-2 mb-1">
{IconComponent && <IconComponent className="h-4 w-4 text-gray-500 dark:text-gray-400" />}
<div className="text-sm text-gray-600 dark:text-gray-400 font-medium flex items-center gap-1.5">
{label}
{helpText && <InfoIcon content={helpText} side="top" />}
</div>
</div>
<div className="text-2xl font-bold text-gray-900 dark:text-white">
{value}
</div>
</div>
</div>
</div>
)
}

View file

@ -1,823 +0,0 @@
"use client"
import { useState, useRef, useEffect, memo, useMemo } from "react"
import {
Clock,
FlaskConical,
Activity,
Box,
RefreshCw,
ChevronDown,
FileText,
Code,
GitCompare,
CheckCircle2,
XCircle,
AlertCircle,
BarChart3,
} from "lucide-react"
import { CodeHighlighter, CODE_STYLE } from "./code-highlighter"
import type { TimelineSection, TimelineSectionContent } from "./timeline-types"
function stripCodeHeader(code: string): string {
let lines = code.split("\n")
if (lines[0] && /^`{3}[a-z]*(:.*)?$/i.test(lines[0].trim())) {
lines = lines.slice(1)
}
if (lines.length > 0 && lines[lines.length - 1]?.trim() === "```") {
lines = lines.slice(0, -1)
}
return lines.join("\n")
}
interface TimelinePageViewProps {
sections: TimelineSection[]
totalDuration: number
functionName?: string | null
filePath?: string | null
}
const TYPE_CONFIG = {
test_generation: { icon: FlaskConical },
optimization: { icon: Box },
line_profiler: { icon: Activity },
refinement: { icon: RefreshCw },
ranking: { icon: BarChart3 },
summary: { icon: CheckCircle2 },
}
function formatTime(ms: number): string {
if (ms < 1000) return `${Math.round(ms)}ms`
if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`
return `${(ms / 60000).toFixed(1)}m`
}
function getStatusIcon(status: string) {
switch (status) {
case "success":
return <CheckCircle2 className="h-4 w-4 text-green-500" />
case "failed":
return <XCircle className="h-4 w-4 text-red-500" />
case "partial":
return <AlertCircle className="h-4 w-4 text-yellow-500" />
default:
return <Clock className="h-4 w-4 text-zinc-400" />
}
}
interface ParsedCodeBlock {
language: string
filename: string | null
path: string | null
code: string
}
function parseCodeBlock(rawCode: string): ParsedCodeBlock {
const markdownMatch = rawCode.match(/^```(\w+)(?::([^\n]+))?\n([\s\S]*?)```\s*$/)
if (markdownMatch) {
const [, language, path, code] = markdownMatch
const filename = path ? path.split("/").pop() || null : null
return { language: language || "python", filename, path: path || null, code: code.trimEnd() }
}
return { language: "python", filename: null, path: null, code: rawCode }
}
function parseAllCodeBlocks(markdown: string): ParsedCodeBlock[] {
const files: ParsedCodeBlock[] = []
const regex = /```(\w+)(?::([^\n]+))?\n([\s\S]*?)```/g
let match
while ((match = regex.exec(markdown)) !== null) {
const [, language, path, code] = match
const filename = path ? path.split("/").pop() || null : null
files.push({
path: path || null,
filename,
language: language || "python",
code: code.trimEnd(),
})
}
if (files.length === 0 && markdown.trim()) {
return [parseCodeBlock(markdown)]
}
return files
}
function findMatchingFile(
files: ParsedCodeBlock[],
targetPath: string | null
): ParsedCodeBlock | null {
if (!targetPath || files.length === 0) return files[0] || null
const exactMatch = files.find(f => f.path === targetPath)
if (exactMatch) return exactMatch
const targetFilename = targetPath.split("/").pop()
const filenameMatch = files.find(f => f.filename === targetFilename)
if (filenameMatch) return filenameMatch
const partialMatch = files.find(f =>
f.path && (targetPath.endsWith(f.path) || f.path.endsWith(targetPath))
)
if (partialMatch) return partialMatch
return files[0] || null
}
const DiffView = memo(function DiffView({ diff }: { diff: string }) {
const lines = diff.split("\n")
return (
<div className="font-mono text-sm bg-zinc-900 overflow-x-auto">
{lines.map((line, index) => {
const isAddition = line.startsWith("+")
const isDeletion = line.startsWith("-")
const isHunkHeader = line.startsWith("@@")
const isNoNewline = line.startsWith("\\ No newline") || line.startsWith("\\")
if (index === lines.length - 1 && line === "") return null
if ((line === "+" || line === "-") || (isAddition && line.substring(1).trim() === "") || (isDeletion && line.substring(1).trim() === "")) {
return null
}
if (isNoNewline) return null
let bgClass = ""
let textClass = "text-zinc-300"
let lineContent = line
let indicator: React.ReactNode = null
let borderClass = "border-transparent"
if (isHunkHeader) {
bgClass = "bg-blue-900/30"
textClass = "text-blue-400"
} else if (isAddition) {
bgClass = "bg-green-900/40"
textClass = "text-green-300"
lineContent = line.substring(1)
indicator = <span className="text-green-500">+</span>
borderClass = "border-green-500"
} else if (isDeletion) {
bgClass = "bg-red-900/40"
textClass = "text-red-300"
lineContent = line.substring(1)
indicator = <span className="text-red-500"></span>
borderClass = "border-red-500"
} else if (line.startsWith(" ")) {
lineContent = line.substring(1)
}
return (
<div
key={index}
className={`flex ${bgClass} border-l-2 ${borderClass}`}
>
<div className="w-8 flex-shrink-0 text-right pr-2 select-none">
{indicator}
</div>
<pre className={`flex-1 px-2 py-0.5 ${textClass} whitespace-pre`}>
{lineContent || " "}
</pre>
</div>
)
})}
</div>
)
})
const TestContent = memo(function TestContent({ content }: { content: Extract<TimelineSectionContent, { type: "tests" }> }) {
const [showDetails, setShowDetails] = useState(false)
const [expandedTest, setExpandedTest] = useState<number | null>(null)
const [activeVariant, setActiveVariant] = useState<"generated" | "instrumented" | "instrumentedPerf">("generated")
const testCount = content.testGroups.length
const hasInstrumented = content.testGroups.some(g => g.instrumented)
const hasInstrumentedPerf = content.testGroups.some(g => g.instrumentedPerf)
return (
<div className="space-y-4">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="flex items-center gap-2">
<CheckCircle2 className="h-5 w-5 text-green-500" />
<span className="text-base font-medium text-zinc-700 dark:text-zinc-300">
{testCount} test{testCount !== 1 ? "s" : ""} generated
</span>
</div>
<div className="flex items-center gap-2">
{content.testFramework && (
<span className="text-xs px-1.5 py-0.5 bg-zinc-100 dark:bg-zinc-700 text-zinc-600 dark:text-zinc-300 rounded">
{content.testFramework}
</span>
)}
{hasInstrumented && (
<span className="text-xs px-1.5 py-0.5 bg-zinc-100 dark:bg-zinc-700 text-zinc-600 dark:text-zinc-300 rounded">
+behavior
</span>
)}
{hasInstrumentedPerf && (
<span className="text-xs px-1.5 py-0.5 bg-zinc-100 dark:bg-zinc-700 text-zinc-600 dark:text-zinc-300 rounded">
+perf
</span>
)}
</div>
</div>
<button
onClick={() => setShowDetails(!showDetails)}
className="px-3 py-1.5 text-xs rounded-md bg-zinc-100 dark:bg-zinc-800 text-zinc-600 dark:text-zinc-400 hover:bg-zinc-200 dark:hover:bg-zinc-700 transition-colors flex items-center gap-1.5"
>
{showDetails ? "Hide Details" : "View Details"}
<ChevronDown className={`h-3.5 w-3.5 transition-transform duration-200 ${showDetails ? "" : "-rotate-90"}`} />
</button>
</div>
{showDetails && (
<div className="space-y-3 pt-2 border-t border-zinc-200 dark:border-zinc-700">
{content.testGroups.map((group) => {
const isExpanded = expandedTest === group.index
const hasMultipleVariants = [group.generated, group.instrumented, group.instrumentedPerf].filter(Boolean).length > 1
const currentCode = activeVariant === "generated" ? group.generated
: activeVariant === "instrumented" ? group.instrumented
: group.instrumentedPerf
return (
<div key={group.index} className="rounded-md border border-zinc-200 dark:border-zinc-700 overflow-hidden">
<button
onClick={() => setExpandedTest(isExpanded ? null : group.index)}
className="w-full px-3 py-2 bg-zinc-100 dark:bg-zinc-800 flex items-center justify-between hover:bg-zinc-200 dark:hover:bg-zinc-700 transition-colors"
>
<div className="flex items-center gap-2">
<FlaskConical className="h-3.5 w-3.5 text-zinc-400" />
<span className="text-sm font-medium text-zinc-700 dark:text-zinc-300">
Test {group.index}
</span>
<div className="flex items-center gap-1">
{group.generated && (
<span className="text-[10px] px-1 py-0.5 bg-zinc-200 dark:bg-zinc-600 text-zinc-600 dark:text-zinc-300 rounded">
gen
</span>
)}
{group.instrumented && (
<span className="text-[10px] px-1 py-0.5 bg-zinc-200 dark:bg-zinc-600 text-zinc-600 dark:text-zinc-300 rounded">
beh
</span>
)}
{group.instrumentedPerf && (
<span className="text-[10px] px-1 py-0.5 bg-zinc-200 dark:bg-zinc-600 text-zinc-600 dark:text-zinc-300 rounded">
perf
</span>
)}
</div>
</div>
<div className="flex items-center gap-2">
<span className="text-xs text-zinc-500">
{group.generated?.lines ?? group.instrumented?.lines ?? group.instrumentedPerf?.lines} lines
</span>
<ChevronDown className={`h-4 w-4 text-zinc-400 transition-transform duration-200 ${isExpanded ? "" : "-rotate-90"}`} />
</div>
</button>
{isExpanded && (
<div>
{hasMultipleVariants && (
<div className="flex items-center gap-1 px-3 py-2 bg-zinc-50 dark:bg-zinc-900 border-b border-zinc-200 dark:border-zinc-700">
{group.generated && (
<button
onClick={() => setActiveVariant("generated")}
className={`px-2 py-1 text-xs rounded transition-colors ${
activeVariant === "generated"
? "bg-zinc-200 dark:bg-zinc-700 text-zinc-900 dark:text-zinc-100"
: "text-zinc-500 hover:text-zinc-700 dark:hover:text-zinc-300"
}`}
>
Generated
</button>
)}
{group.instrumented && (
<button
onClick={() => setActiveVariant("instrumented")}
className={`px-2 py-1 text-xs rounded transition-colors ${
activeVariant === "instrumented"
? "bg-zinc-200 dark:bg-zinc-700 text-zinc-900 dark:text-zinc-100"
: "text-zinc-500 hover:text-zinc-700 dark:hover:text-zinc-300"
}`}
>
Behavior
</button>
)}
{group.instrumentedPerf && (
<button
onClick={() => setActiveVariant("instrumentedPerf")}
className={`px-2 py-1 text-xs rounded transition-colors ${
activeVariant === "instrumentedPerf"
? "bg-zinc-200 dark:bg-zinc-700 text-zinc-900 dark:text-zinc-100"
: "text-zinc-500 hover:text-zinc-700 dark:hover:text-zinc-300"
}`}
>
Perf
</button>
)}
</div>
)}
<div className="max-h-[50vh] overflow-y-auto">
{currentCode ? (
<CodeHighlighter language="python" code={currentCode.code} customStyle={CODE_STYLE} />
) : (
<div className="p-4 text-sm text-zinc-500 dark:text-zinc-400 italic">
No {activeVariant === "generated" ? "generated" : activeVariant === "instrumented" ? "instrumented behavior" : "instrumented perf"} test available
</div>
)}
</div>
</div>
)}
</div>
)
})}
</div>
)}
</div>
)
})
const CandidateContent = memo(function CandidateContent({
content,
isActive,
}: {
content: Extract<TimelineSectionContent, { type: "candidate" | "refinement" }>
isActive: boolean
}) {
const [viewMode, setViewMode] = useState<"code" | "diff">("diff")
const [selectedFileIndex, setSelectedFileIndex] = useState(0)
const [unifiedDiff, setUnifiedDiff] = useState<string | null>(null)
const [diffLoading, setDiffLoading] = useState(false)
const originalCode = content.type === "refinement" ? content.parentCode : content.originalCode
const candidateFiles = useMemo(() => parseAllCodeBlocks(content.code), [content.code])
const originalFiles = useMemo(() => originalCode ? parseAllCodeBlocks(originalCode) : [], [originalCode])
const selectedCandidateFile = candidateFiles[selectedFileIndex] || candidateFiles[0]
const matchingOriginalFile = useMemo(() => {
if (!selectedCandidateFile || originalFiles.length === 0) return null
return findMatchingFile(originalFiles, selectedCandidateFile.path)
}, [selectedCandidateFile, originalFiles])
useEffect(() => {
setUnifiedDiff(null)
}, [selectedFileIndex])
useEffect(() => {
if (viewMode !== "diff" || !matchingOriginalFile || !selectedCandidateFile || unifiedDiff !== null) {
return
}
setDiffLoading(true)
import("diff").then(({ createTwoFilesPatch }) => {
const filename = selectedCandidateFile.filename || matchingOriginalFile.filename || "code.py"
const diff = createTwoFilesPatch(
`a/${filename}`,
`b/${filename}`,
matchingOriginalFile.code,
selectedCandidateFile.code,
"",
"",
{ context: 3 }
)
const lines = diff.split("\n")
const hunkStartIndex = lines.findIndex(line => line.startsWith("@@"))
setUnifiedDiff(hunkStartIndex > 0 ? lines.slice(hunkStartIndex).join("\n") : diff)
setDiffLoading(false)
}).catch(error => {
console.error("Failed to load diff library:", error)
setDiffLoading(false)
})
}, [viewMode, matchingOriginalFile, selectedCandidateFile, unifiedDiff])
const hasDiff = matchingOriginalFile !== null
const hasMultipleFiles = candidateFiles.length > 1
const codeContainerStyle = useMemo(
() => ({ maxHeight: isActive ? "70vh" : "200px" }),
[isActive]
)
return (
<div className="space-y-3">
<div className="flex items-center gap-2 flex-wrap">
{content.rank != null && (
<span className="text-xs px-1.5 py-0.5 bg-zinc-100 dark:bg-zinc-700 text-zinc-600 dark:text-zinc-300 rounded">
#{content.rank}
</span>
)}
{content.isBest && (
<span className="text-xs px-1.5 py-0.5 bg-zinc-200 dark:bg-zinc-600 text-zinc-700 dark:text-zinc-200 rounded font-medium">
Best
</span>
)}
</div>
{content.explanation && (
<p className="text-sm text-zinc-600 dark:text-zinc-400 leading-relaxed">
{content.explanation}
</p>
)}
<div className="flex items-center gap-2 flex-wrap">
{hasDiff && (
<div className="flex items-center gap-1">
<button
onClick={() => setViewMode("code")}
className={`px-3 py-1.5 text-xs rounded-md flex items-center gap-1.5 transition-colors ${
viewMode === "code"
? "bg-zinc-800 text-white"
: "bg-zinc-100 dark:bg-zinc-800 text-zinc-600 dark:text-zinc-400 hover:bg-zinc-200 dark:hover:bg-zinc-700"
}`}
>
<Code className="h-3.5 w-3.5" />
Code
</button>
<button
onClick={() => setViewMode("diff")}
className={`px-3 py-1.5 text-xs rounded-md flex items-center gap-1.5 transition-colors ${
viewMode === "diff"
? "bg-zinc-800 text-white"
: "bg-zinc-100 dark:bg-zinc-800 text-zinc-600 dark:text-zinc-400 hover:bg-zinc-200 dark:hover:bg-zinc-700"
}`}
>
<GitCompare className="h-3.5 w-3.5" />
Diff
</button>
</div>
)}
{hasMultipleFiles && (
<select
value={selectedFileIndex}
onChange={(e) => setSelectedFileIndex(Number(e.target.value))}
className="px-2 py-1.5 text-xs rounded-md bg-zinc-100 dark:bg-zinc-800 text-zinc-600 dark:text-zinc-300 border border-zinc-200 dark:border-zinc-700"
>
{candidateFiles.map((file, index) => (
<option key={index} value={index}>
{file.filename || file.path || `File ${index + 1}`}
</option>
))}
</select>
)}
</div>
{viewMode === "code" ? (
selectedCandidateFile ? (
<div className="rounded-md border border-zinc-200 dark:border-zinc-700 overflow-hidden">
<div className="px-3 py-2 bg-zinc-100 dark:bg-zinc-800 flex items-center justify-between">
<div className="flex items-center gap-2">
<FileText className="h-3.5 w-3.5 text-zinc-400" />
<span className="text-sm font-mono font-medium text-zinc-700 dark:text-zinc-300">
{selectedCandidateFile.filename || "Code"}
</span>
{selectedCandidateFile.path && selectedCandidateFile.path !== selectedCandidateFile.filename && (
<span className="text-xs text-zinc-500 dark:text-zinc-400">
({selectedCandidateFile.path})
</span>
)}
</div>
<span className="text-xs text-zinc-500">
{selectedCandidateFile.code.split("\n").length} lines
</span>
</div>
<div
className="overflow-y-auto transition-all duration-500 ease-out"
style={codeContainerStyle}
>
<CodeHighlighter language={selectedCandidateFile.language} code={selectedCandidateFile.code} customStyle={CODE_STYLE} />
</div>
</div>
) : (
<div className="text-sm text-zinc-500 dark:text-zinc-400 italic p-4 bg-zinc-50 dark:bg-zinc-800/50 rounded-md">
No code available
</div>
)
) : diffLoading ? (
<div className="rounded-md border border-zinc-200 dark:border-zinc-700 overflow-hidden">
<div className="p-4 bg-zinc-900 animate-pulse">
<div className="h-4 bg-zinc-700 rounded w-3/4 mb-2" />
<div className="h-4 bg-zinc-700 rounded w-1/2 mb-2" />
<div className="h-4 bg-zinc-700 rounded w-2/3" />
</div>
</div>
) : unifiedDiff ? (
<div
className="rounded-md border border-zinc-200 dark:border-zinc-700 overflow-hidden overflow-y-auto transition-all duration-500"
style={codeContainerStyle}
>
<DiffView diff={unifiedDiff} />
</div>
) : (
<div className="text-sm text-zinc-500 dark:text-zinc-400 italic p-4 bg-zinc-50 dark:bg-zinc-800/50 rounded-md">
No original code available for comparison
</div>
)}
</div>
)
})
const RankingContent = memo(function RankingContent({ content }: { content: Extract<TimelineSectionContent, { type: "ranking" }> }) {
return (
<div className="space-y-4">
{content.explanation && (
<div className="p-3 bg-zinc-50 dark:bg-zinc-800/50 rounded border border-zinc-200 dark:border-zinc-700">
<p className="text-sm text-zinc-600 dark:text-zinc-400 whitespace-pre-wrap">
{content.explanation}
</p>
</div>
)}
{content.rankings.length >= 1 && (
<div className="space-y-4">
{content.rankings.map((item) => (
<div
key={item.id}
className={`rounded border ${
item.isBest
? "border-emerald-300 dark:border-emerald-700"
: "border-zinc-300 dark:border-zinc-600"
}`}
>
<div className={`flex items-center gap-2 px-3 py-2 border-b flex-wrap ${
item.isBest
? "bg-emerald-50 dark:bg-emerald-900/20 border-emerald-200 dark:border-emerald-800"
: "bg-zinc-100 dark:bg-zinc-700 border-zinc-200 dark:border-zinc-700"
}`}>
<span className="text-sm font-medium text-zinc-900 dark:text-white">
{item.label}
</span>
<span className="bg-indigo-100 dark:bg-indigo-900 text-indigo-800 dark:text-indigo-200 text-xs px-2 py-0.5 rounded font-semibold">
Rank #{item.rank}
</span>
{item.isBest && (
<span className="bg-emerald-100 dark:bg-emerald-900 text-emerald-800 dark:text-emerald-200 text-xs px-2 py-0.5 rounded font-semibold">
Best
</span>
)}
{item.isBest && content.usedForPr && (
<span className="bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200 text-xs px-2 py-0.5 rounded font-semibold">
Used for PR
</span>
)}
</div>
<div className="max-h-80 overflow-auto">
<CodeHighlighter code={stripCodeHeader(item.code)} language="python" customStyle={CODE_STYLE} />
</div>
</div>
))}
</div>
)}
</div>
)
})
const SummaryContent = memo(function SummaryContent({ content }: { content: Extract<TimelineSectionContent, { type: "summary" }> }) {
const { metrics } = content
return (
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
<div className="p-4 bg-zinc-50 dark:bg-zinc-800/50 rounded-md">
<div className="text-xs text-zinc-500 dark:text-zinc-400 mb-1">Total Duration</div>
<div className="text-xl font-semibold text-zinc-900 dark:text-white">
{formatTime(metrics.totalDuration)}
</div>
</div>
<div className="p-4 bg-zinc-50 dark:bg-zinc-800/50 rounded-md">
<div className="text-xs text-zinc-500 dark:text-zinc-400 mb-1">Total Cost</div>
<div className="text-xl font-semibold text-zinc-900 dark:text-white">
${metrics.totalCost.toFixed(4)}
</div>
</div>
<div className="p-4 bg-zinc-50 dark:bg-zinc-800/50 rounded-md">
<div className="text-xs text-zinc-500 dark:text-zinc-400 mb-1">Total Tokens</div>
<div className="text-xl font-semibold text-zinc-900 dark:text-white">
{metrics.totalTokens.toLocaleString()}
</div>
</div>
<div className="p-4 bg-zinc-50 dark:bg-zinc-800/50 rounded-md">
<div className="text-xs text-zinc-500 dark:text-zinc-400 mb-1">Candidates</div>
<div className="text-xl font-semibold text-zinc-900 dark:text-white">
{metrics.candidatesCount}
</div>
</div>
</div>
)
})
const TimelineSectionCard = memo(function TimelineSectionCard({
section,
isActive,
index,
totalSections,
}: {
section: TimelineSection
isActive: boolean
index: number
totalSections: number
}) {
const config = TYPE_CONFIG[section.type]
const Icon = config.icon
return (
<div className={`relative transition-opacity duration-200 ${isActive ? "opacity-100" : "opacity-60"}`}>
<div className="absolute right-6 top-0 bottom-0 w-px bg-zinc-200 dark:bg-zinc-700" />
<div className="mr-14 mb-6">
<div className="flex items-center gap-3 mb-2">
<span className="text-xs font-mono text-zinc-500 dark:text-zinc-400">
+{formatTime(section.timestamp)}
</span>
{section.duration && (
<>
<span className="text-zinc-300 dark:text-zinc-600">·</span>
<span className="text-xs text-zinc-400 dark:text-zinc-500">
{formatTime(section.duration)}
</span>
</>
)}
<div className="flex-1" />
<span className="text-xs text-zinc-400 dark:text-zinc-500">
{index + 1}/{totalSections}
</span>
</div>
<div
className={`rounded-md border overflow-hidden transition-colors duration-200 ${
isActive
? "border-zinc-300 dark:border-zinc-600 bg-white dark:bg-zinc-800"
: "border-zinc-200 dark:border-zinc-700 bg-zinc-50 dark:bg-zinc-800/50"
}`}
>
<div className="px-4 py-3 border-b border-zinc-200 dark:border-zinc-700 bg-zinc-50 dark:bg-zinc-800">
<div className="flex items-center gap-3">
<Icon className="h-4 w-4 text-zinc-500 dark:text-zinc-400" />
<div className="flex-1">
<h3 className="text-sm font-medium text-zinc-900 dark:text-zinc-100">
{section.title}
</h3>
{section.subtitle && (
<p className="text-xs text-zinc-500 dark:text-zinc-400 mt-0.5">
{section.subtitle}
</p>
)}
</div>
<div className="flex items-center gap-2">
{getStatusIcon(section.status)}
{section.model && (
<span className="text-xs px-1.5 py-0.5 bg-zinc-100 dark:bg-zinc-700 text-zinc-600 dark:text-zinc-300 rounded">
{section.model}
</span>
)}
{section.cost != null && (
<span className="text-xs text-zinc-500 dark:text-zinc-400">
${section.cost.toFixed(4)}
</span>
)}
</div>
</div>
</div>
<div className="p-4 bg-white dark:bg-zinc-800">
{section.content.type === "tests" && <TestContent content={section.content} />}
{(section.content.type === "candidate" || section.content.type === "refinement") && (
<CandidateContent content={section.content} isActive={isActive} />
)}
{section.content.type === "ranking" && <RankingContent content={section.content} />}
{section.content.type === "summary" && <SummaryContent content={section.content} />}
</div>
</div>
</div>
</div>
)
})
export const TimelinePageView = memo(function TimelinePageView({
sections,
totalDuration,
functionName,
filePath,
}: TimelinePageViewProps) {
const [activeIndex, setActiveIndex] = useState(0)
const sectionRefs = useRef<(HTMLDivElement | null)[]>([])
const rafId = useRef<number | null>(null)
useEffect(() => {
const handleScroll = () => {
if (rafId.current !== null) return
rafId.current = requestAnimationFrame(() => {
rafId.current = null
const scrollTarget = window.innerHeight * 0.35
let closestIndex = 0
let closestDistance = Infinity
sectionRefs.current.forEach((ref, index) => {
if (ref) {
const rect = ref.getBoundingClientRect()
const sectionMiddle = rect.top + rect.height / 2
const distance = Math.abs(sectionMiddle - scrollTarget)
if (distance < closestDistance) {
closestDistance = distance
closestIndex = index
}
}
})
setActiveIndex(closestIndex)
})
}
window.addEventListener("scroll", handleScroll, { passive: true })
handleScroll()
return () => {
window.removeEventListener("scroll", handleScroll)
if (rafId.current !== null) {
cancelAnimationFrame(rafId.current)
}
}
}, [sections.length])
if (sections.length === 0) {
return (
<div className="flex items-center justify-center py-20 text-zinc-400">
No timeline data available
</div>
)
}
return (
<div className="relative">
<div className="sticky top-0 z-30 bg-white/80 dark:bg-zinc-900/80 backdrop-blur-sm border-b border-zinc-200 dark:border-zinc-700 mb-6">
<div className="max-w-6xl mx-auto px-4 py-3">
<div className="flex items-center justify-between mb-2">
<div>
<h2 className="text-sm font-medium text-zinc-900 dark:text-white">
Optimization Timeline
</h2>
{functionName && (
<p className="text-xs text-zinc-500 dark:text-zinc-400">
{functionName}
{filePath && <span className="ml-1 opacity-60">in {filePath}</span>}
</p>
)}
</div>
<div className="text-right">
<span className="text-xs text-zinc-500 dark:text-zinc-400">
{activeIndex + 1} of {sections.length} · {formatTime(totalDuration)}
</span>
</div>
</div>
<div className="h-1 bg-zinc-200 dark:bg-zinc-700 rounded-full overflow-hidden">
<div
className="h-full bg-zinc-400 dark:bg-zinc-500 transition-all duration-200"
style={{ width: `${((activeIndex + 1) / sections.length) * 100}%` }}
/>
</div>
</div>
</div>
<div className="max-w-6xl mx-auto px-4 pb-20 relative">
<div className="sticky top-1/2 -translate-y-1/2 z-20 pointer-events-none h-0">
<div className="absolute right-4 -top-2">
<div className="w-4 h-4 rounded-full bg-zinc-400 dark:bg-zinc-500 border-2 border-white dark:border-zinc-900" />
</div>
</div>
{sections.map((section, index) => (
<div
key={section.id}
ref={(el) => { sectionRefs.current[index] = el }}
className="scroll-mt-24"
>
<TimelineSectionCard
section={section}
isActive={index === activeIndex}
index={index}
totalSections={sections.length}
/>
</div>
))}
<div className="relative">
<div className="absolute right-6 top-0 h-6 w-px bg-zinc-200 dark:bg-zinc-700" />
<div className="absolute right-4 top-6 w-4 h-4 rounded-full bg-zinc-300 dark:bg-zinc-600 border-2 border-white dark:border-zinc-900 z-10" />
<div className="mr-14 py-4 text-right">
<span className="text-xs text-zinc-500 dark:text-zinc-400">
End
</span>
</div>
</div>
</div>
</div>
)
})

View file

@ -1,289 +0,0 @@
export interface TimelineSection {
id: string
type: "test_generation" | "optimization" | "line_profiler" | "refinement" | "ranking" | "summary"
title: string
subtitle?: string
timestamp: number
duration?: number
status: "success" | "failed" | "partial" | "pending"
model?: string | null
cost?: number | null
tokens?: number | null
content: TimelineSectionContent
}
export interface TestGroup {
index: number
generated?: { code: string; lines: number }
instrumented?: { code: string; lines: number }
instrumentedPerf?: { code: string; lines: number }
}
export type TimelineSectionContent =
| { type: "tests"; testGroups: TestGroup[]; testFramework?: string }
| { type: "candidate"; code: string; originalCode: string | null; explanation?: string; rank?: number; isBest?: boolean }
| { type: "refinement"; code: string; parentCode: string | null; explanation?: string; rank?: number; isBest?: boolean }
| { type: "ranking"; explanation: string; rankings: Array<{ id: string; rank: number; label: string; code: string; isBest: boolean }>; usedForPr: boolean }
| { type: "summary"; metrics: { totalCost: number; totalTokens: number; totalDuration: number; candidatesCount: number } }
export interface TransformInput {
calls: Array<{
id: string
call_type: string | null
model_name: string | null
status: string
latency_ms: number | null
llm_cost: number | null
total_tokens: number | null
created_at: Date
context: { call_sequence?: number } | null
}>
optimizationCandidates: Array<{
id: string
code: string
explanation?: string
index: number
}>
lineProfilerCandidates: Array<{
id: string
code: string
explanation?: string
index: number
}>
refinementCandidates: Array<{
id: string
code: string
explanation?: string
parentId: string | null
index: number
}>
generatedTests: Array<{ code: string; index: number }>
instrumentedTests: Array<{ code: string; index: number }>
instrumentedPerfTests: Array<{ code: string; index: number }>
originalCode: string | null
testFramework: string | null
candidateRankMap: Record<string, number>
bestCandidateId: string | null
rankingExplanation: string | null
usedForPr: boolean
}
export function transformToTimelineSections(input: TransformInput): { sections: TimelineSection[]; totalDuration: number } {
const { calls, optimizationCandidates, lineProfilerCandidates, refinementCandidates, generatedTests, instrumentedTests, instrumentedPerfTests, originalCode, testFramework, candidateRankMap, bestCandidateId, rankingExplanation, usedForPr } = input
if (calls.length === 0) {
return { sections: [], totalDuration: 0 }
}
const timestamps = calls.map(c => new Date(c.created_at).getTime())
const minTime = Math.min(...timestamps)
const maxTime = Math.max(...timestamps)
const maxLatency = Math.max(...calls.map(c => c.latency_ms ?? 0))
const totalDuration = maxTime - minTime + maxLatency
const sections: TimelineSection[] = []
const maxTestIndex = Math.max(
generatedTests.length,
instrumentedTests.length,
instrumentedPerfTests.length
)
const testGroups: TestGroup[] = []
for (let i = 1; i <= maxTestIndex; i++) {
const generated = generatedTests.find(t => t.index === i)
const instrumented = instrumentedTests.find(t => t.index === i)
const instrumentedPerf = instrumentedPerfTests.find(t => t.index === i)
if (generated || instrumented || instrumentedPerf) {
testGroups.push({
index: i,
generated: generated ? { code: generated.code, lines: generated.code.split("\n").length } : undefined,
instrumented: instrumented ? { code: instrumented.code, lines: instrumented.code.split("\n").length } : undefined,
instrumentedPerf: instrumentedPerf ? { code: instrumentedPerf.code, lines: instrumentedPerf.code.split("\n").length } : undefined,
})
}
}
const testCalls = calls.filter(c => c.call_type === "test_generation")
if (testCalls.length > 0 || testGroups.length > 0) {
const firstTestCall = testCalls[0]
const firstTimestamp = firstTestCall ? new Date(firstTestCall.created_at).getTime() - minTime : 0
const totalTestDuration = testCalls.reduce((sum, c) => sum + (c.latency_ms ?? 0), 0)
const totalTestCost = testCalls.reduce((sum, c) => sum + (c.llm_cost ?? 0), 0)
const totalTestTokens = testCalls.reduce((sum, c) => sum + (c.total_tokens ?? 0), 0)
const allSuccess = testCalls.length === 0 || testCalls.every(c => c.status === "success")
const anyFailed = testCalls.some(c => c.status === "failed")
const subtitle = testFramework
? `${testGroups.length} test${testGroups.length > 1 ? "s" : ""} using ${testFramework}`
: `${testGroups.length} test${testGroups.length > 1 ? "s" : ""} generated`
sections.push({
id: firstTestCall ? `tests-${firstTestCall.id}` : "tests",
type: "test_generation",
title: "Test Generation",
subtitle,
timestamp: firstTimestamp,
duration: totalTestDuration,
status: allSuccess ? "success" : anyFailed ? "failed" : "partial",
model: firstTestCall?.model_name ?? null,
cost: totalTestCost,
tokens: totalTestTokens,
content: {
type: "tests",
testGroups,
testFramework: testFramework ?? undefined,
},
})
}
const callIndexByType = new Map<string, number>()
for (const call of calls) {
const timestamp = new Date(call.created_at).getTime() - minTime
const callType = call.call_type || "unknown"
const typeIndex = callIndexByType.get(callType) ?? 0
callIndexByType.set(callType, typeIndex + 1)
if (callType === "optimization") {
const optIndex = typeIndex
const candidate = optimizationCandidates[optIndex]
if (candidate) {
const rank = candidateRankMap[candidate.id]
sections.push({
id: call.id,
type: "optimization",
title: `Optimization Candidate ${candidate.index}`,
timestamp,
duration: call.latency_ms ?? undefined,
status: call.status === "success" ? "success" : call.status === "failed" ? "failed" : "partial",
model: call.model_name,
cost: call.llm_cost,
tokens: call.total_tokens,
content: {
type: "candidate",
code: candidate.code,
originalCode,
explanation: candidate.explanation,
rank,
isBest: candidate.id === bestCandidateId,
},
})
}
} else if (callType === "line_profiler") {
const lpIndex = typeIndex
const candidate = lineProfilerCandidates[lpIndex]
if (candidate) {
const rank = candidateRankMap[candidate.id]
sections.push({
id: call.id,
type: "line_profiler",
title: `Line Profiler Candidate ${candidate.index}`,
subtitle: "Guided by profiling data",
timestamp,
duration: call.latency_ms ?? undefined,
status: call.status === "success" ? "success" : call.status === "failed" ? "failed" : "partial",
model: call.model_name,
cost: call.llm_cost,
tokens: call.total_tokens,
content: {
type: "candidate",
code: candidate.code,
originalCode,
explanation: candidate.explanation,
rank,
isBest: candidate.id === bestCandidateId,
},
})
}
} else if (callType === "refinement") {
const refIndex = typeIndex
const candidate = refinementCandidates[refIndex]
if (candidate) {
const rank = candidateRankMap[candidate.id]
const parentCandidate = [...optimizationCandidates, ...lineProfilerCandidates, ...refinementCandidates].find(c => c.id === candidate.parentId)
const parentLabel = parentCandidate
? (parentCandidate as { source?: string }).source === "REFINE"
? `From Refinement ${parentCandidate.index}`
: `From Candidate ${parentCandidate.index}`
: undefined
sections.push({
id: call.id,
type: "refinement",
title: `Refinement ${candidate.index}`,
subtitle: parentLabel,
timestamp,
duration: call.latency_ms ?? undefined,
status: call.status === "success" ? "success" : call.status === "failed" ? "failed" : "partial",
model: call.model_name,
cost: call.llm_cost,
tokens: call.total_tokens,
content: {
type: "refinement",
code: candidate.code,
parentCode: parentCandidate?.code ?? originalCode,
explanation: candidate.explanation,
rank,
isBest: candidate.id === bestCandidateId,
},
})
}
} else if (callType === "ranking") {
const allCandidates = [...optimizationCandidates, ...lineProfilerCandidates, ...refinementCandidates]
const rankings = Object.entries(candidateRankMap)
.sort(([, a], [, b]) => a - b)
.map(([id]) => {
const cand = allCandidates.find(c => c.id === id)
if (!cand) return null
const source = (cand as { source?: string }).source
const prefix = source === "REFINE" ? "Refinement" : source === "OPTIMIZE_LP" ? "LP Candidate" : "Candidate"
return { id, rank: 0, label: `${prefix} ${cand.index}`, code: cand.code, isBest: false }
})
.filter((r): r is NonNullable<typeof r> => r !== null)
.map((r, index) => ({ ...r, rank: index + 1, isBest: index === 0 }))
sections.push({
id: call.id,
type: "ranking",
title: "Candidate Ranking",
subtitle: "Selecting the best optimization",
timestamp,
duration: call.latency_ms ?? undefined,
status: call.status === "success" ? "success" : call.status === "failed" ? "failed" : "partial",
model: call.model_name,
cost: call.llm_cost,
tokens: call.total_tokens,
content: {
type: "ranking",
explanation: rankingExplanation ?? "",
rankings,
usedForPr,
},
})
}
}
const typeOrder: Record<string, number> = {
test_generation: 0,
optimization: 1,
line_profiler: 2,
refinement: 3,
ranking: 4,
summary: 5,
}
sections.sort((a, b) => {
const orderA = typeOrder[a.type] ?? 99
const orderB = typeOrder[b.type] ?? 99
if (orderA !== orderB) return orderA - orderB
const candidateTypes = ["optimization", "line_profiler", "refinement"]
if (candidateTypes.includes(a.type)) {
const indexA = parseInt(a.title.match(/\d+$/)?.[0] ?? "0", 10)
const indexB = parseInt(b.title.match(/\d+$/)?.[0] ?? "0", 10)
return indexA - indexB
}
return a.timestamp - b.timestamp
})
return { sections, totalDuration }
}

View file

@ -1,83 +0,0 @@
"use client"
import { useState, useCallback, type ChangeEvent } from "react"
import { Search, Loader2, CheckCircle } from "lucide-react"
import { useRouter } from "next/navigation"
interface TraceSearchProps {
initialTraceId?: string
isLoading?: boolean
hasResults?: boolean
}
export function TraceSearch({ initialTraceId = "", isLoading = false, hasResults = false }: TraceSearchProps) {
const [traceId, setTraceId] = useState(initialTraceId)
const router = useRouter()
const handleChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
setTraceId(e.target.value)
}, [])
const handleSearch = useCallback(() => {
const trimmedId = traceId.trim()
if (!trimmedId) return
const params = new URLSearchParams(window.location.search)
params.set("trace_id", trimmedId)
router.push(`/observability?${params.toString()}`)
}, [traceId, router])
const handleKeyDown = useCallback(
(e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter") {
handleSearch()
}
},
[handleSearch],
)
const inputBorderClass = hasResults
? "border-green-500 dark:border-green-500 focus:ring-green-500"
: "border-zinc-300 dark:border-zinc-600 focus:ring-blue-500"
return (
<div className="w-full max-w-2xl mx-auto">
<div className="relative flex items-center gap-3">
<div className="relative flex-1">
<input
type="text"
value={traceId}
onChange={handleChange}
onKeyDown={handleKeyDown}
placeholder="Enter trace ID..."
className={`w-full px-4 py-3 pl-11 ${hasResults ? "pr-11" : ""} text-base rounded-md border ${inputBorderClass} bg-white dark:bg-zinc-950 text-zinc-900 dark:text-zinc-50 placeholder-zinc-400 dark:placeholder-zinc-500 focus:outline-none focus:ring-2 focus:border-transparent transition-all duration-150`}
aria-label="Trace ID"
/>
<Search className="absolute left-4 top-1/2 -translate-y-1/2 h-4 w-4 text-zinc-400" />
{hasResults && (
<CheckCircle className="absolute right-4 top-1/2 -translate-y-1/2 h-5 w-5 text-green-500" />
)}
</div>
<button
onClick={handleSearch}
disabled={!traceId.trim() || isLoading}
className="px-6 py-3 bg-blue-600 hover:bg-blue-700 disabled:bg-blue-400 disabled:cursor-not-allowed text-white font-medium rounded-md transition-colors duration-150 flex items-center gap-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
>
{isLoading ? (
<>
<Loader2 className="h-5 w-5 animate-spin" />
Loading
</>
) : (
"Search"
)}
</button>
</div>
{!hasResults && (
<p className="mt-2 text-sm text-zinc-500 dark:text-zinc-400">
Paste or type a trace ID to view all associated LLM calls, candidates, and errors
</p>
)}
</div>
)
}

View file

@ -1,124 +0,0 @@
import {
CheckCircle,
XCircle,
AlertCircle,
Timer,
DollarSign,
Github,
Terminal,
Hash,
Code as CodeIcon,
} from "lucide-react"
import { InfoIcon } from "./info-icon"
interface TraceSummaryProps {
status: "Completed" | "Partial" | "Failed"
source: string
durationSeconds: number
totalCost: number
totalTokens: number
candidatesCount?: number
}
export function TraceSummary({
status,
source,
durationSeconds,
totalCost,
totalTokens,
candidatesCount,
}: TraceSummaryProps) {
let statusColor: string
if (status === "Failed") {
statusColor = "text-red-600 dark:text-red-400"
} else if (status === "Partial") {
statusColor = "text-yellow-600 dark:text-yellow-400"
} else {
statusColor = "text-green-600 dark:text-green-400"
}
let StatusIcon
if (status === "Completed") {
StatusIcon = CheckCircle
} else if (status === "Failed") {
StatusIcon = XCircle
} else {
StatusIcon = AlertCircle
}
const SourceIcon = source.toLowerCase().includes("github") ? Github : Terminal
return (
<div className="bg-white dark:bg-zinc-950 rounded-sm p-6 border border-zinc-200 dark:border-zinc-800">
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-6">
<div className="group">
<div className="flex items-center gap-1.5 text-sm text-zinc-600 dark:text-zinc-400 font-medium mb-2">
<StatusIcon className="h-4 w-4" />
<span>Status</span>
<InfoIcon content="Overall trace status based on all contained calls" side="top" />
</div>
<div className={`text-xl font-bold ${statusColor}`}>{status}</div>
</div>
<div className="group">
<div className="flex items-center gap-1.5 text-sm text-zinc-600 dark:text-zinc-400 font-medium mb-2">
<SourceIcon className="h-4 w-4" />
<span>Source</span>
<InfoIcon content="Where this optimization was triggered from" side="top" />
</div>
<div className="text-xl font-bold text-zinc-900 dark:text-zinc-50">
<span className="px-2 py-1 bg-zinc-100 dark:bg-zinc-700 text-zinc-700 dark:text-zinc-300 rounded-sm text-sm hover:bg-zinc-200 dark:hover:bg-zinc-800 transition-colors duration-150">
{source}
</span>
</div>
</div>
<div className="group">
<div className="flex items-center gap-1.5 text-sm text-zinc-600 dark:text-zinc-400 font-medium mb-2">
<Timer className="h-4 w-4" />
<span>Duration</span>
<InfoIcon content="Total time from first call to last call completion" side="top" />
</div>
<div className="text-xl font-bold text-zinc-900 dark:text-zinc-50">
{durationSeconds.toFixed(2)}s
</div>
</div>
<div className="group">
<div className="flex items-center gap-1.5 text-sm text-zinc-600 dark:text-zinc-400 font-medium mb-2">
<DollarSign className="h-4 w-4" />
<span>Cost</span>
<InfoIcon content="Sum of all LLM call costs in this trace" side="top" />
</div>
<div className="text-xl font-bold text-zinc-900 dark:text-zinc-50">
${totalCost.toFixed(4)}
</div>
</div>
<div className="group">
<div className="flex items-center gap-1.5 text-sm text-zinc-600 dark:text-zinc-400 font-medium mb-2">
<Hash className="h-4 w-4" />
<span>Tokens</span>
<InfoIcon content="Total tokens (prompt + completion) across all calls" side="top" />
</div>
<div className="text-xl font-bold text-zinc-900 dark:text-zinc-50">
{totalTokens.toLocaleString()}
</div>
</div>
{candidatesCount !== undefined && (
<div className="group">
<div className="flex items-center gap-1.5 text-sm text-zinc-600 dark:text-zinc-400 font-medium mb-2">
<CodeIcon className="h-4 w-4" />
<span>Candidates</span>
<InfoIcon content="Number of optimization candidates generated" side="top" />
</div>
<div className="text-xl font-bold text-zinc-900 dark:text-zinc-50">
{candidatesCount}
</div>
</div>
)}
</div>
</div>
)
}

View file

@ -1,16 +0,0 @@
/**
* Determines the source of an optimization based on event_type
*/
export function getTraceSource(eventType: string | null): string {
if (!eventType) return "Unknown"
if (eventType === "pr_created" || eventType === "pr_merged" || eventType === "pr_closed") {
return "GitHub Action"
}
if (eventType === "no-pr") {
return "CLI/VSCode"
}
return eventType
}

View file

@ -0,0 +1,928 @@
"use client"
import React, { useState, useEffect, useMemo, useCallback, useRef } from "react"
import { DiffEditor, useMonaco, DiffOnMount } from "@monaco-editor/react"
import {
CheckCircle2,
XCircle,
GitPullRequest,
Zap,
TestTube,
ChevronDown,
ChevronUp,
ExternalLink,
FileCode,
Edit3,
Save,
X,
Lock,
Monitor,
Smartphone,
BarChart3,
} from "lucide-react"
// Ensure you have lucide-react installed as per your package.json
import { Loader2, FileText, AlertTriangle } from "lucide-react"
import type { ExperimentMetadata, DiffContents } from "@/lib/types" // Adjust path if needed
import { getMonacoLanguage } from "@/lib/utils"
import ReactMarkdown from "react-markdown"
import remarkGfm from "remark-gfm"
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"
import { vscDarkPlus } from "react-syntax-highlighter/dist/esm/styles/prism"
interface MonacoDiffViewerProps {
metadata: ExperimentMetadata | null // Full metadata object
repoFullName: string // Formatted as "owner/repo"
traceId: string
review_quality: string
review_explanation: string
}
const MonacoDiffViewer: React.FC<MonacoDiffViewerProps> = ({
metadata,
repoFullName,
traceId,
review_quality,
review_explanation,
}) => {
const monaco = useMonaco()
const [activeFileKey, setActiveFileKey] = useState<string | null>(null)
const [showTestDetails, setShowTestDetails] = useState(false)
const [showGeneratedTests, setShowGeneratedTests] = useState(false)
const [showOptimizationQuality, setShowOptimizationQuality] = useState(false)
const [showOptimizationExplanation, setShowOptimizationExplanation] = useState(false)
const [isEditing, setIsEditing] = useState(false)
const [editSecret, setEditSecret] = useState("")
const [showSecretPrompt, setShowSecretPrompt] = useState(false)
const [currentEdit, setCurrentEdit] = useState<{ [key: string]: string }>({})
const [saveStatus, setSaveStatus] = useState<"idle" | "saving" | "saved" | "error">("idle")
const [savedChanges, setSavedChanges] = useState<{ [key: string]: string }>({})
const [isMobile, setIsMobile] = useState(false)
const [useInlineView, setUseInlineView] = useState(false)
const isEditingRef = useRef(isEditing)
const activeFileKeyRef = useRef(activeFileKey)
useEffect(() => {
isEditingRef.current = isEditing
}, [isEditing])
useEffect(() => {
activeFileKeyRef.current = activeFileKey
}, [activeFileKey])
const handleEditorOnMount: DiffOnMount = useCallback(editor => {
// Always set up the change listener, but only update state when editing
const modifiedEditor = editor.getModifiedEditor()
modifiedEditor.onDidChangeModelContent(() => {
if (isEditingRef.current) {
const value = modifiedEditor.getValue()
if (activeFileKeyRef.current && value !== undefined) {
setCurrentEdit(prev => ({
...prev,
[activeFileKeyRef.current!]: value,
}))
}
}
})
}, [])
// Merge metadata with saved changes for display
const diffContents: DiffContents | null = useMemo(() => {
if (!metadata?.diffContents) return null
const updatedContents = { ...metadata.diffContents }
Object.entries(savedChanges).forEach(([fileKey, newContent]) => {
if (updatedContents[fileKey]) {
updatedContents[fileKey] = {
...updatedContents[fileKey],
newContent,
}
}
})
return updatedContents
}, [metadata?.diffContents, savedChanges])
const prCommentFields = metadata?.prCommentFields
const fileKeys = useMemo(() => {
return diffContents ? Object.keys(diffContents) : []
}, [diffContents])
// Calculate test statistics
const testStats = useMemo(() => {
const stats = {
totalPassed: 0,
totalFailed: 0,
categories: [] as { name: string; passed: number; failed: number; icon: string }[],
}
if (prCommentFields?.report_table) {
Object.entries(prCommentFields.report_table).forEach(([category, results]) => {
stats.totalPassed += results.passed
stats.totalFailed += results.failed
// Map category names to icons
let icon = "🧪"
if (category.includes("Replay")) icon = "⏪"
else if (category.includes("Unit")) icon = "⚙️"
else if (category.includes("Coverage")) icon = "🔎"
else if (category.includes("Regression")) icon = "🌀"
else if (category.includes("Inspired")) icon = "🎨"
stats.categories.push({
name: category,
passed: results.passed,
failed: results.failed,
icon,
})
})
}
return stats
}, [prCommentFields])
const handleEditClick = () => {
setShowSecretPrompt(true)
}
const handleSecretSubmit = () => {
if (editSecret === "codeflash-edit-2025") {
setIsEditing(true)
setShowSecretPrompt(false)
// Initialize current edit with current content if not already set
if (activeFileKey && diffContents?.[activeFileKey] && !currentEdit[activeFileKey]) {
setCurrentEdit(prev => ({
...prev,
[activeFileKey]:
diffContents[activeFileKey].newContent || diffContents[activeFileKey].oldContent || "",
}))
}
} else {
alert("Invalid secret!")
}
}
const handleSaveCode = async () => {
if (!activeFileKey || !currentEdit[activeFileKey]) return
setSaveStatus("saving")
try {
const response = await fetch(`/api/traces/${traceId}/save-modified-code`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
fileKey: activeFileKey,
modifiedCode: currentEdit[activeFileKey],
secret: editSecret,
}),
})
if (response.ok) {
setSaveStatus("saved")
// Update local saved changes state to show the changes immediately
setSavedChanges(prev => ({
...prev,
[activeFileKey]: currentEdit[activeFileKey],
}))
// Clear current edit and auto-return to diff view after successful save
setTimeout(() => {
setSaveStatus("idle")
setIsEditing(false)
setCurrentEdit(prev => {
const newEdit = { ...prev }
delete newEdit[activeFileKey!]
return newEdit
})
}, 1500)
} else {
setSaveStatus("error")
setTimeout(() => setSaveStatus("idle"), 3000)
}
} catch (error) {
console.error("Failed to save modified code:", error)
setSaveStatus("error")
setTimeout(() => setSaveStatus("idle"), 3000)
}
}
const handleCancelEdit = () => {
setIsEditing(false)
setEditSecret("")
// Revert changes for current file
if (activeFileKey) {
setCurrentEdit(prev => {
const newEdit = { ...prev }
delete newEdit[activeFileKey]
return newEdit
})
}
}
useEffect(() => {
if (fileKeys.length > 0 && !activeFileKey) {
setActiveFileKey(fileKeys[0])
}
}, [fileKeys, activeFileKey])
// Mobile detection and responsive handler
useEffect(() => {
const checkMobile = () => {
const mobile = window.innerWidth < 768
setIsMobile(mobile)
setUseInlineView(mobile)
}
checkMobile()
window.addEventListener("resize", checkMobile)
return () => window.removeEventListener("resize", checkMobile)
}, [])
useEffect(() => {
if (monaco) {
// Define your custom dark theme for Monaco Editor
monaco.editor.defineTheme("codeflash-python-dark", {
base: "vs-dark",
inherit: true,
rules: [
{ token: "comment.python", foreground: "6A9955" }, // Python comments
{ token: "keyword.python", foreground: "569CD6" }, // Python keywords
{ token: "string.python", foreground: "CE9178" }, // Python strings
{ token: "number.python", foreground: "B5CEA8" }, // Python numbers
{ token: "identifier.python", foreground: "9CDCFE" },
{ token: "type.identifier.python", foreground: "4EC9B0" }, // class names, etc.
],
colors: {
"editor.background": "#0A0E14",
"editor.foreground": "#F8F8F2",
"editorLineNumber.foreground": "#6272A4",
"editor.selectionBackground": "#44475A",
"editor.lineHighlightBackground": "#1A1F29",
"diffEditor.insertedTextBackground": "#50FA7B33",
"diffEditor.removedTextBackground": "#FF555533",
"diffEditor.insertedLineBackground": "#50FA7B22",
"diffEditor.removedLineBackground": "#FF555522",
"diffEditorGutter.insertedLineBackground": "#50FA7B",
"diffEditorGutter.removedLineBackground": "#FF5555",
},
})
}
}, [monaco])
// Loading or error states - NOW AFTER ALL HOOKS
if (!metadata) {
return (
<div className="flex flex-col items-center justify-center h-screen bg-[#0f0f0f] text-slate-400">
<Loader2 className="h-12 w-12 animate-spin text-sky-500 mb-4" />
<p className="text-lg">
Loading trace details for <span className="font-mono">{traceId}</span>...
</p>
</div>
)
}
if (!diffContents || fileKeys.length === 0) {
return (
<div className="flex flex-col items-center justify-center h-screen bg-[#0f0f0f] text-slate-400">
<AlertTriangle className="h-12 w-12 text-amber-500 mb-4" />
<p className="text-lg">No diff content available for this trace.</p>
<p className="text-sm font-mono mt-2">Trace ID: {traceId}</p>
</div>
)
}
const currentDiff = activeFileKey && diffContents ? diffContents[activeFileKey] : null
const functionName = prCommentFields?.function_name || "N/A"
const speedup = prCommentFields?.speedup_pct || prCommentFields?.speedup_x || "N/A"
const explanation = prCommentFields?.optimization_explanation
return (
<div className="flex flex-col h-screen bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900 text-slate-200">
{/* Header Section - Mobile Optimized */}
<div className="px-3 sm:px-4 md:px-6 py-2 sm:py-3 md:py-4 border-b border-slate-700/50 overflow-y-auto max-h-[40vh] md:max-h-none">
<div className="flex flex-col gap-3 sm:gap-4 md:gap-6">
{/* Top Row - Title with PR Link and Observability Link */}
<div className="flex items-center justify-between gap-2">
<div className="flex items-center gap-2 sm:gap-3 min-w-0 flex-1">
<h1 className="text-lg sm:text-xl md:text-2xl lg:text-3xl font-bold bg-gradient-to-r from-cyan-400 via-blue-500 to-purple-600 text-transparent bg-clip-text truncate">
CodeFlash Optimization
</h1>
{metadata.pullNumber && (
<a
href={`https://github.com/${metadata.owner || repoFullName.split("/")[0]}/${metadata.repo || repoFullName.split("/")[1]}/pull/${metadata.pullNumber}`}
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-1 text-cyan-400 hover:text-cyan-300 transition-colors bg-slate-800/50 px-2 py-1 rounded-md text-xs sm:text-sm flex-shrink-0"
>
<GitPullRequest className="h-3 w-3 sm:h-4 sm:w-4" />
<span className="hidden sm:inline">PR #{metadata.pullNumber}</span>
<span className="sm:hidden">#{metadata.pullNumber}</span>
<ExternalLink className="h-2.5 w-2.5 sm:h-3 sm:w-3" />
</a>
)}
</div>
{/* Observability Link */}
<a
href={`/observability/trace/${traceId}`}
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-1 text-cyan-400 hover:text-cyan-300 transition-colors bg-slate-800/50 px-2 py-1 rounded-md text-xs sm:text-sm flex-shrink-0"
>
<BarChart3 className="h-3 w-3 sm:h-4 sm:w-4" />
<span className="hidden sm:inline">Observability</span>
<span className="sm:hidden">Obs</span>
<ExternalLink className="h-2.5 w-2.5 sm:h-3 sm:w-3" />
</a>
</div>
{/* Info Row - Repository and Function (Compact on Mobile) */}
<div className="flex flex-col sm:flex-row sm:items-center gap-2 sm:gap-4 text-xs sm:text-sm">
<div className="flex items-center gap-2 min-w-0">
<FileCode className="h-3 w-3 sm:h-4 sm:w-4 text-cyan-400 flex-shrink-0" />
<span className="text-slate-400 flex-shrink-0">Repo:</span>
<span className="text-slate-200 font-medium truncate">{repoFullName}</span>
</div>
<div className="flex items-center gap-2 min-w-0">
<span className="text-slate-400 flex-shrink-0">Function:</span>
<code className="text-purple-400 bg-slate-800/50 px-1.5 sm:px-2 py-0.5 sm:py-1 rounded font-mono text-xs sm:text-sm truncate">
{functionName}
</code>
</div>
</div>
{/* Performance Metrics Row - Compact on Mobile */}
<div className="flex flex-wrap items-center gap-2 sm:gap-4">
{/* Performance Boost - Smaller on Mobile */}
<div className="bg-slate-800/30 rounded-lg p-2 sm:p-3 md:p-4 border border-slate-700/30 flex items-center gap-1.5 sm:gap-2">
<Zap className="h-4 w-4 sm:h-5 sm:w-5 md:h-6 md:w-6 text-yellow-400 flex-shrink-0" />
<div>
<div className="text-[10px] sm:text-xs text-slate-400 uppercase tracking-wider">
Boost
</div>
<span className="text-lg sm:text-xl md:text-2xl lg:text-3xl font-bold bg-gradient-to-r from-green-400 to-emerald-500 text-transparent bg-clip-text block leading-tight">
{speedup}
</span>
</div>
</div>
{/* Additional Metrics - Hidden on very small screens */}
{prCommentFields?.loop_count && (
<div className="hidden sm:block text-center">
<div className="text-[10px] sm:text-xs text-slate-400 uppercase tracking-wider mb-1">
Loops
</div>
<div className="text-sm sm:text-base md:text-lg font-semibold text-blue-400">
{prCommentFields.loop_count.toLocaleString()}
</div>
</div>
)}
{prCommentFields?.original_runtime && prCommentFields?.best_runtime && (
<div className="hidden md:block text-center">
<div className="text-[10px] sm:text-xs text-slate-400 uppercase tracking-wider mb-1">
Runtime
</div>
<div className="text-xs sm:text-sm">
<span className="text-red-400 line-through">
{prCommentFields.original_runtime}
</span>
<span className="mx-1 sm:mx-2 text-slate-500"></span>
<span className="text-green-400">{prCommentFields.best_runtime}</span>
</div>
</div>
)}
{/* Edit Button - Compact on Mobile */}
<div className="ml-auto flex items-center gap-1.5 sm:gap-2">
{!isEditing ? (
<button
onClick={handleEditClick}
className="flex items-center gap-1 sm:gap-2 px-2 sm:px-3 py-1.5 sm:py-2 bg-purple-600 hover:bg-purple-700 text-white rounded-lg transition-colors text-xs sm:text-sm font-medium"
>
<Edit3 className="h-3 w-3 sm:h-4 sm:w-4" />
<span className="hidden sm:inline">Edit Code</span>
<span className="sm:hidden">Edit</span>
</button>
) : (
<div className="flex items-center gap-1.5 sm:gap-2">
<button
onClick={handleSaveCode}
disabled={saveStatus === "saving"}
className="flex items-center gap-1 sm:gap-2 px-2 sm:px-3 py-1.5 sm:py-2 bg-green-600 hover:bg-green-700 disabled:bg-green-800 text-white rounded-lg transition-colors text-xs sm:text-sm font-medium"
>
<Save className="h-3 w-3 sm:h-4 sm:w-4" />
<span className="hidden sm:inline">
{saveStatus === "saving"
? "Saving..."
: saveStatus === "saved"
? "Saved!"
: "Save"}
</span>
<span className="sm:hidden">
{saveStatus === "saving" ? "..." : saveStatus === "saved" ? "✓" : "Save"}
</span>
</button>
<button
onClick={handleCancelEdit}
className="flex items-center gap-1 sm:gap-2 px-2 sm:px-3 py-1.5 sm:py-2 bg-red-600 hover:bg-red-700 text-white rounded-lg transition-colors text-xs sm:text-sm font-medium"
>
<X className="h-3 w-3 sm:h-4 sm:w-4" />
<span className="hidden sm:inline">Cancel</span>
</button>
</div>
)}
</div>
</div>
{/* Test Results Summary - Collapsed by default on mobile */}
{testStats.totalPassed > 0 || testStats.totalFailed > 0 ? (
<div className="bg-slate-800/30 rounded-lg p-2 sm:p-3 md:p-4 border border-slate-700/30">
<div
className="flex items-center justify-between cursor-pointer gap-2"
onClick={() => setShowTestDetails(!showTestDetails)}
>
<div className="flex items-center gap-2 sm:gap-3 min-w-0 flex-1">
<TestTube className="h-4 w-4 sm:h-5 sm:w-5 text-blue-400 flex-shrink-0" />
<span className="font-semibold text-xs sm:text-sm md:text-base truncate">
Test Results
</span>
<div className="flex items-center gap-2 sm:gap-4 flex-shrink-0">
<div className="flex items-center gap-1">
<CheckCircle2 className="h-3 w-3 sm:h-4 sm:w-4 text-green-400" />
<span className="text-green-400 font-medium text-xs sm:text-sm">
{testStats.totalPassed}
</span>
</div>
{testStats.totalFailed > 0 && (
<div className="flex items-center gap-1">
<XCircle className="h-3 w-3 sm:h-4 sm:w-4 text-red-400" />
<span className="text-red-400 font-medium text-xs sm:text-sm">
{testStats.totalFailed}
</span>
</div>
)}
</div>
</div>
{showTestDetails ? (
<ChevronUp className="h-4 w-4 flex-shrink-0" />
) : (
<ChevronDown className="h-4 w-4 flex-shrink-0" />
)}
</div>
{showTestDetails && (
<div className="mt-3 sm:mt-4 grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-2 sm:gap-3">
{testStats.categories.map((category, idx) => (
<div
key={idx}
className="bg-slate-700/50 rounded-lg p-2 sm:p-3 flex items-center justify-between gap-2"
>
<div className="flex items-center gap-2 min-w-0 flex-1">
<span className="text-xs sm:text-sm text-slate-300 truncate">
{category.name}
</span>
</div>
<div className="flex items-center gap-2 text-xs sm:text-sm flex-shrink-0">
{category.passed > 0 && (
<span className="text-green-400">{category.passed}</span>
)}
{category.failed > 0 && (
<span className="text-red-400">{category.failed}</span>
)}
</div>
</div>
))}
</div>
)}
</div>
) : null}
</div>
</div>
{/* File Path/Tabs - Mobile Optimized */}
{fileKeys.length === 1 ? (
// Single file - show full path with view toggle
<div className="bg-[rgba(15,15,15,0.95)] border-b border-[rgba(255,255,255,0.05)] px-3 sm:px-4 md:px-5 py-2 sm:py-2.5 md:py-3 flex items-center justify-between gap-2">
<div className="flex items-center gap-1.5 sm:gap-2 min-w-0 flex-1">
<FileText size={12} className="text-sky-400 flex-shrink-0 sm:w-3.5 sm:h-3.5" />
<span className="text-xs sm:text-sm text-slate-300 font-mono truncate">
{fileKeys[0]}
</span>
</div>
{/* View Toggle for mobile compatibility */}
<div className="flex items-center gap-1.5 sm:gap-2 flex-shrink-0">
<button
onClick={() => setUseInlineView(!useInlineView)}
className={`flex items-center gap-1 sm:gap-1.5 px-2 sm:px-3 py-1 rounded-md text-[10px] sm:text-xs transition-colors ${
useInlineView
? "bg-blue-600/20 text-blue-400 border border-blue-600/30"
: "bg-slate-700/50 text-slate-400 hover:text-slate-300"
}`}
title={useInlineView ? "Switch to side-by-side view" : "Switch to inline view"}
>
{useInlineView ? (
<Smartphone size={10} className="sm:w-3 sm:h-3" />
) : (
<Monitor size={10} className="sm:w-3 sm:h-3" />
)}
<span className="hidden sm:inline">{useInlineView ? "Inline" : "Side-by-side"}</span>
</button>
</div>
</div>
) : (
// Multiple files - show tabs with full path on hover
<div className="bg-[rgba(15,15,15,0.95)] border-b border-[rgba(255,255,255,0.05)]">
<div className="flex items-center justify-between gap-2">
<div className="flex overflow-x-auto whitespace-nowrap scrollbar-thin scrollbar-thumb-slate-700 scrollbar-track-slate-800 flex-1 min-w-0">
{fileKeys.map(fileKey => (
<button
key={fileKey}
onClick={() => setActiveFileKey(fileKey)}
title={fileKey}
className={`px-3 sm:px-4 md:px-5 py-2 sm:py-2.5 md:py-3 text-xs sm:text-sm transition-all duration-200 ease-in-out focus:outline-none flex items-center gap-1.5 sm:gap-2 flex-shrink-0
${
activeFileKey === fileKey
? "text-white bg-[rgba(255,255,255,0.05)] border-b-2 border-sky-400"
: "text-slate-400 hover:text-slate-200 hover:bg-[rgba(255,255,255,0.03)] border-b-2 border-transparent"
}`}
>
<FileText
size={12}
className={`${activeFileKey === fileKey ? "text-sky-400" : "text-slate-500"} flex-shrink-0 sm:w-3.5 sm:h-3.5`}
/>
<span className="truncate max-w-[120px] sm:max-w-none">
{fileKey.split("/").pop()}
</span>
</button>
))}
</div>
{/* View Toggle for mobile compatibility */}
<div className="flex items-center gap-1.5 sm:gap-2 px-2 sm:px-3 md:px-5 flex-shrink-0">
<button
onClick={() => setUseInlineView(!useInlineView)}
className={`flex items-center gap-1 sm:gap-1.5 px-2 sm:px-3 py-1 rounded-md text-[10px] sm:text-xs transition-colors ${
useInlineView
? "bg-blue-600/20 text-blue-400 border border-blue-600/30"
: "bg-slate-700/50 text-slate-400 hover:text-slate-300"
}`}
title={useInlineView ? "Switch to side-by-side view" : "Switch to inline view"}
>
{useInlineView ? (
<Smartphone size={10} className="sm:w-3 sm:h-3" />
) : (
<Monitor size={10} className="sm:w-3 sm:h-3" />
)}
<span className="hidden sm:inline">
{useInlineView ? "Inline" : "Side-by-side"}
</span>
</button>
</div>
</div>
</div>
)}
{/* Monaco Editor Container */}
<div className="flex-grow relative min-h-0">
{activeFileKey && currentDiff && monaco ? (
// Check for empty diff scenarios first (only in non-editing mode)
!isEditing &&
(currentDiff.oldContent || "") === (currentDiff.newContent || "") &&
(currentDiff.oldContent || "").trim() === "" ? (
// Both contents are empty
<div className="absolute inset-0 flex flex-col items-center justify-center bg-[#0A0A0A] text-slate-400">
<FileText className="h-12 w-12 text-slate-500 mb-4" />
<h3 className="text-lg font-medium text-slate-200 mb-2">No Changes to Display</h3>
<p className="text-sm text-slate-400 max-w-md text-center px-4">
The file content is empty. The staging branch may have been merged or the changes
have been reverted.
</p>
</div>
) : !isEditing && (currentDiff.oldContent || "") === (currentDiff.newContent || "") ? (
// Contents are identical (no diff)
<div className="absolute inset-0 flex flex-col items-center justify-center bg-[#0A0A0A] text-slate-400">
<FileText className="h-12 w-12 text-slate-500 mb-4" />
<h3 className="text-lg font-medium text-slate-200 mb-2">No Differences Found</h3>
<p className="text-sm text-slate-400 max-w-md text-center px-4">
The original and optimized code are identical. The changes may have already been
applied or reverted.
</p>
</div>
) : isEditing ? (
// Edit Mode with Diff View - Like VS Code changes view
<DiffEditor
height="100%"
original={currentDiff.oldContent || ""}
modified={currentEdit[activeFileKey] || currentDiff.newContent || ""}
language={activeFileKey ? getMonacoLanguage(activeFileKey) : "python"}
theme="codeflash-python-dark"
onMount={handleEditorOnMount}
options={{
automaticLayout: true,
renderSideBySide: !useInlineView,
readOnly: false, // Allow editing on the modified side
scrollBeyondLastLine: false,
minimap: { enabled: !isMobile, scale: isMobile ? 0.5 : 1, size: "proportional" },
diffCodeLens: true,
renderIndicators: true,
ignoreTrimWhitespace: false,
renderWhitespace: "boundary",
fontSize: isMobile ? 12 : 13,
fontFamily: "'Fira Code', 'Cascadia Code', Consolas, 'Courier New', monospace",
fontLigatures: !isMobile, // Disable font ligatures on mobile for better performance
wordWrap: isMobile ? "on" : "off", // Enable word wrap on mobile
scrollbar: {
verticalScrollbarSize: isMobile ? 14 : 10,
horizontalScrollbarSize: isMobile ? 14 : 10,
useShadows: false,
},
originalEditable: false, // Original side stays read-only
enableSplitViewResizing: !isMobile,
// Enable diff review mode for better editing experience
diffWordWrap: isMobile ? "on" : "off",
// Mobile-specific optimizations
mouseWheelZoom: !isMobile,
contextmenu: !isMobile,
quickSuggestions: !isMobile,
parameterHints: { enabled: !isMobile },
suggest: !isMobile ? {} : undefined,
hover: { enabled: !isMobile },
}}
loading={
<div className="absolute inset-0 flex flex-col items-center justify-center bg-[#0A0A0A] text-slate-400">
<Loader2 className="h-10 w-10 animate-spin text-sky-500 mb-3" />
<p>Loading Diff Editor...</p>
</div>
}
/>
) : (
// Diff View Mode
<DiffEditor
height="100%"
original={currentDiff.oldContent || ""}
modified={currentDiff.newContent || ""}
language={activeFileKey ? getMonacoLanguage(activeFileKey) : "python"}
theme="codeflash-python-dark"
onMount={handleEditorOnMount}
options={{
automaticLayout: true,
renderSideBySide: !useInlineView,
readOnly: true,
scrollBeyondLastLine: false,
minimap: { enabled: !isMobile, scale: isMobile ? 0.5 : 1, size: "proportional" },
diffCodeLens: true,
renderIndicators: true,
ignoreTrimWhitespace: false,
renderWhitespace: "boundary",
fontSize: isMobile ? 12 : 13,
fontFamily: "'Fira Code', 'Cascadia Code', Consolas, 'Courier New', monospace",
fontLigatures: !isMobile, // Disable font ligatures on mobile for better performance
wordWrap: isMobile ? "on" : "off", // Enable word wrap on mobile
scrollbar: {
verticalScrollbarSize: isMobile ? 14 : 10,
horizontalScrollbarSize: isMobile ? 14 : 10,
useShadows: false,
},
originalEditable: false,
enableSplitViewResizing: !isMobile,
// Mobile-specific optimizations
mouseWheelZoom: !isMobile,
contextmenu: !isMobile,
quickSuggestions: false,
parameterHints: { enabled: false },
suggest: undefined,
hover: { enabled: !isMobile },
}}
loading={
<div className="absolute inset-0 flex flex-col items-center justify-center bg-[#0A0A0A] text-slate-400">
<Loader2 className="h-10 w-10 animate-spin text-sky-500 mb-3" />
<p>Loading Diff Editor...</p>
</div>
}
/>
)
) : (
<div className="absolute inset-0 flex flex-col items-center justify-center bg-[#0A0A0A] text-slate-500">
<Loader2 className="h-10 w-10 animate-spin text-sky-500 mb-3" />
<p>{!monaco ? "Initializing Editor Subsystem..." : "Preparing View..."}</p>
</div>
)}
</div>
{/* Bottom Section with Explanation and Generated Tests - Mobile Optimized */}
<div className="bg-gradient-to-t from-slate-900 to-slate-800/50 border-t border-slate-700/50 max-h-[30vh] sm:max-h-[35vh] md:max-h-none overflow-y-auto">
{/* Optimization review details */}
{review_quality && (
<div className="p-2 sm:p-3 md:p-4 border-b border-slate-700/30">
<div
className="flex items-center justify-between cursor-pointer gap-2"
onClick={() => setShowOptimizationQuality(!showOptimizationQuality)}
>
<h3 className="text-xs sm:text-sm font-semibold text-cyan-400 flex items-center gap-1.5 sm:gap-2 min-w-0 flex-1">
<Zap className="h-3 w-3 sm:h-4 sm:w-4 flex-shrink-0" />
<span className="truncate">🎯 Quality: {review_quality}</span>
</h3>
{showOptimizationQuality ? (
<ChevronUp className="h-3 w-3 sm:h-4 sm:w-4 text-slate-400 flex-shrink-0" />
) : (
<ChevronDown className="h-3 w-3 sm:h-4 sm:w-4 text-slate-400 flex-shrink-0" />
)}
</div>
{showOptimizationQuality && (
<div className="mt-2 text-xs sm:text-sm text-slate-300 whitespace-pre-wrap bg-slate-800/50 rounded-lg p-2 sm:p-3 max-h-24 sm:max-h-32 overflow-y-auto scrollbar-thin scrollbar-thumb-slate-600 scrollbar-track-slate-800">
{review_explanation}
</div>
)}
</div>
)}
{/* Optimization Explanation */}
{explanation && (
<div className="p-2 sm:p-3 md:p-4 border-b border-slate-700/30">
<div
className="flex items-center justify-between cursor-pointer gap-2"
onClick={() => setShowOptimizationExplanation(!showOptimizationExplanation)}
>
<h3 className="text-xs sm:text-sm font-semibold text-cyan-400 flex items-center gap-1.5 sm:gap-2">
<Zap className="h-3 w-3 sm:h-4 sm:w-4 flex-shrink-0" />
<span className="hidden sm:inline">Optimization Explanation</span>
<span className="sm:hidden">Explanation</span>
</h3>
{showOptimizationExplanation ? (
<ChevronUp className="h-3 w-3 sm:h-4 sm:w-4 text-slate-400 flex-shrink-0" />
) : (
<ChevronDown className="h-3 w-3 sm:h-4 sm:w-4 text-slate-400 flex-shrink-0" />
)}
</div>
{showOptimizationExplanation && (
<div className="mt-2 text-xs sm:text-sm text-slate-300 whitespace-pre-wrap bg-slate-800/50 rounded-lg p-2 sm:p-3 max-h-24 sm:max-h-32 overflow-y-auto scrollbar-thin scrollbar-thumb-slate-600 scrollbar-track-slate-800">
{explanation}
</div>
)}
</div>
)}
{/* Generated Tests Toggle */}
{metadata.generatedTests && (
<div className="p-2 sm:p-3 md:p-4">
<button
onClick={() => setShowGeneratedTests(!showGeneratedTests)}
className="flex items-center gap-1.5 sm:gap-2 text-xs sm:text-sm font-semibold text-purple-400 hover:text-purple-300 transition-colors w-full"
>
<TestTube className="h-3 w-3 sm:h-4 sm:w-4 flex-shrink-0" />
<span className="hidden sm:inline">Generated Tests</span>
<span className="sm:hidden">Tests</span>
{showGeneratedTests ? (
<ChevronUp className="h-3 w-3 sm:h-4 sm:w-4 ml-auto flex-shrink-0" />
) : (
<ChevronDown className="h-3 w-3 sm:h-4 sm:w-4 ml-auto flex-shrink-0" />
)}
</button>
{showGeneratedTests && (
<div className="mt-2 sm:mt-3 bg-slate-800/50 rounded-lg p-2 sm:p-3 max-h-48 sm:max-h-64 overflow-y-auto scrollbar-thin scrollbar-thumb-slate-600 scrollbar-track-slate-800">
<ReactMarkdown
className="prose prose-sm prose-invert max-w-none"
remarkPlugins={[remarkGfm]}
components={{
pre: ({ children, ...props }) => {
return (
<div className="not-prose">
<pre className="bg-slate-900/50 rounded-md overflow-x-auto" {...props}>
{children}
</pre>
</div>
)
},
code: props => {
const { inline, className, children, ...restProps } = props as {
inline?: boolean
className?: string
children: React.ReactNode
[key: string]: unknown
}
const match = /language-(\w+)/.exec(className || "")
const language = match ? match[1] : null
return !inline && language ? (
<SyntaxHighlighter
style={vscDarkPlus}
language={language}
PreTag="div"
className="!bg-slate-900/50 !text-[10px] sm:!text-xs rounded-md"
customStyle={{
margin: 0,
padding: "0.5rem",
backgroundColor: "rgb(15 23 42 / 0.5)",
fontSize: "0.625rem",
lineHeight: "1.4",
}}
{...restProps}
>
{String(children).replace(/\n$/, "")}
</SyntaxHighlighter>
) : (
<code
className="bg-slate-700/50 px-1 py-0.5 rounded text-[10px] sm:text-xs text-cyan-300 font-mono"
{...restProps}
>
{children}
</code>
)
},
p: ({ children }) => (
<p className="text-xs sm:text-sm text-slate-300 mb-2 sm:mb-3">{children}</p>
),
h1: ({ children }) => (
<h1 className="text-base sm:text-lg font-bold text-white mb-1.5 sm:mb-2">
{children}
</h1>
),
h2: ({ children }) => (
<h2 className="text-sm sm:text-base font-semibold text-white mb-1.5 sm:mb-2">
{children}
</h2>
),
h3: ({ children }) => (
<h3 className="text-xs sm:text-sm font-semibold text-white mb-1">
{children}
</h3>
),
ul: ({ children }) => (
<ul className="list-disc list-inside text-xs sm:text-sm text-slate-300 mb-2 sm:mb-3">
{children}
</ul>
),
ol: ({ children }) => (
<ol className="list-decimal list-inside text-xs sm:text-sm text-slate-300 mb-2 sm:mb-3">
{children}
</ol>
),
li: ({ children }) => (
<li className="text-xs sm:text-sm text-slate-300 mb-0.5 sm:mb-1">
{children}
</li>
),
}}
>
{(() => {
// Check if generatedTests already has markdown code blocks
const testsContent = metadata.generatedTests.trim()
const hasCodeBlocks = testsContent.includes("```")
// If it doesn't have code blocks, wrap it as Python code
if (!hasCodeBlocks) {
return "```python\n" + testsContent + "\n```"
}
// Otherwise, return as-is
return testsContent
})()}
</ReactMarkdown>
</div>
)}
</div>
)}
</div>
{/* Secret Prompt Modal - Mobile Optimized */}
{showSecretPrompt && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
<div className="bg-slate-800 rounded-lg p-4 sm:p-6 max-w-md w-full mx-4">
<div className="flex items-center gap-2 sm:gap-3 mb-3 sm:mb-4">
<Lock className="h-4 w-4 sm:h-5 sm:w-5 text-amber-400 flex-shrink-0" />
<h3 className="text-base sm:text-lg font-semibold text-white">Enter Edit Secret</h3>
</div>
<p className="text-slate-300 text-xs sm:text-sm mb-3 sm:mb-4">
Please enter the secret key to enable code editing.
</p>
<input
type="password"
placeholder="Enter secret..."
value={editSecret}
onChange={e => setEditSecret(e.target.value)}
onKeyDown={e => e.key === "Enter" && handleSecretSubmit()}
className="w-full px-3 py-2 bg-slate-700 border border-slate-600 rounded-lg text-white placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent mb-3 sm:mb-4 text-sm sm:text-base"
autoFocus
/>
<div className="flex gap-2 sm:gap-3 justify-end">
<button
onClick={() => {
setShowSecretPrompt(false)
setEditSecret("")
}}
className="px-3 sm:px-4 py-1.5 sm:py-2 text-slate-300 hover:text-white transition-colors text-sm sm:text-base"
>
Cancel
</button>
<button
onClick={handleSecretSubmit}
className="px-3 sm:px-4 py-1.5 sm:py-2 bg-purple-600 hover:bg-purple-700 text-white rounded-lg transition-colors text-sm sm:text-base"
>
Unlock
</button>
</div>
</div>
</div>
)}
</div>
)
}
export default MonacoDiffViewer

View file

@ -4,16 +4,16 @@ import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const badgeVariants = cva(
"inline-flex items-center rounded-sm border px-2 py-0.5 text-xs font-semibold transition-colors",
"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
{
variants: {
variant: {
default: "border-transparent bg-zinc-700 text-zinc-100 hover:bg-zinc-600",
default: "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
secondary:
"border-transparent bg-zinc-800 text-zinc-300 hover:bg-zinc-700",
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
destructive:
"border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
outline: "border-zinc-700 text-zinc-300",
outline: "text-foreground",
},
},
defaultVariants: {
@ -31,4 +31,4 @@ function Badge({ className, variant, ...props }: BadgeProps) {
return <div className={cn(badgeVariants({ variant }), className)} {...props} />
}
export { Badge, badgeVariants }
export { Badge, badgeVariants }

View file

@ -5,22 +5,22 @@ import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center whitespace-nowrap rounded-sm text-sm font-medium ring-offset-background transition-all duration-150 active:scale-[0.98] focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-zinc-600 disabled:pointer-events-none disabled:opacity-50",
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default: "bg-zinc-100 dark:bg-zinc-700 text-zinc-950 dark:text-zinc-50 hover:bg-zinc-200 dark:hover:bg-zinc-600",
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline: "border border-zinc-700 hover:bg-zinc-800 hover:text-zinc-50",
secondary: "bg-zinc-800 text-zinc-50 hover:bg-zinc-700",
ghost: "hover:bg-zinc-800 hover:text-zinc-50",
link: "text-zinc-400 underline-offset-4 hover:underline hover:text-zinc-300",
outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-9 px-3 py-2",
sm: "h-8 rounded-sm px-2",
lg: "h-10 rounded-sm px-4",
icon: "h-9 w-9",
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
},
},
defaultVariants: {

View file

@ -6,7 +6,7 @@ const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElemen
({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("rounded-sm border border-zinc-800 bg-zinc-900 text-zinc-50", className)}
className={cn("rounded-lg border bg-card text-card-foreground shadow-sm", className)}
{...props}
/>
),
@ -15,7 +15,7 @@ Card.displayName = "Card"
const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div ref={ref} className={cn("flex flex-col space-y-1.5 p-4", className)} {...props} />
<div ref={ref} className={cn("flex flex-col space-y-1.5 p-6", className)} {...props} />
),
)
CardHeader.displayName = "CardHeader"
@ -24,7 +24,7 @@ const CardTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HT
({ className, ...props }, ref) => (
<h3
ref={ref}
className={cn("text-lg font-semibold leading-none tracking-tight", className)}
className={cn("text-2xl font-semibold leading-none tracking-tight", className)}
{...props}
/>
),
@ -35,20 +35,20 @@ const CardDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<p ref={ref} className={cn("text-sm text-zinc-400", className)} {...props} />
<p ref={ref} className={cn("text-sm text-muted-foreground", className)} {...props} />
))
CardDescription.displayName = "CardDescription"
const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div ref={ref} className={cn("p-4 pt-0", className)} {...props} />
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
),
)
CardContent.displayName = "CardContent"
const CardFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div ref={ref} className={cn("flex items-center p-4 pt-0", className)} {...props} />
<div ref={ref} className={cn("flex items-center p-6 pt-0", className)} {...props} />
),
)
CardFooter.displayName = "CardFooter"

View file

@ -1,45 +0,0 @@
/**
* Icon Standardization Examples
*
* This file demonstrates the standardized icon treatment across components
* following the zinc color palette and consistent sizing patterns.
*
* Icon Guidelines:
* - Small contexts (buttons, badges): w-4 h-4
* - Medium contexts (headers, titles): w-5 h-5
* - Consistent stroke width: strokeWidth={2}
* - Color hierarchy: zinc-400 (muted) zinc-300 (hover) zinc-50 (active)
* - All icons from lucide-react should follow these standards
*/
import { Search, Settings, ChevronRight } from "lucide-react"
import { Button } from "./button"
export function IconExamples() {
return (
<div className="space-y-4 p-4">
{/* Button with icon - w-4 h-4 standard */}
<Button>
<Search className="mr-2 h-4 w-4 text-zinc-400" strokeWidth={2} />
Search
</Button>
{/* Icon button - icon only */}
<Button size="icon">
<Settings className="h-4 w-4 text-zinc-400 hover:text-zinc-300" strokeWidth={2} />
</Button>
{/* Card header icon - w-5 h-5 for larger contexts */}
<div className="flex items-center gap-2">
<ChevronRight className="h-5 w-5 text-zinc-400" strokeWidth={2} />
<h3 className="text-lg font-semibold">Card Title</h3>
</div>
{/* Active state example */}
<Button className="bg-zinc-700">
<Search className="mr-2 h-4 w-4 text-zinc-50" strokeWidth={2} />
Active Search
</Button>
</div>
)
}

View file

@ -10,7 +10,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
<input
type={type}
className={cn(
"flex h-9 w-full rounded-sm border border-zinc-700 bg-zinc-950 px-3 py-2 text-sm text-zinc-50 ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-zinc-500 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-zinc-600 disabled:cursor-not-allowed disabled:opacity-50",
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className,
)}
ref={ref}

View file

@ -19,14 +19,14 @@ const SelectTrigger = React.forwardRef<
<SelectPrimitive.Trigger
ref={ref}
className={cn(
"flex h-9 w-full items-center justify-between rounded-sm border border-zinc-700 bg-zinc-950 px-3 py-2 text-sm text-zinc-50 ring-offset-background placeholder:text-zinc-500 focus:outline-none focus:ring-1 focus:ring-zinc-600 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
"flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
className,
)}
{...props}
>
{children}
<SelectPrimitive.Icon asChild>
<ChevronDown className="h-4 w-4 text-zinc-400" strokeWidth={2} />
<ChevronDown className="h-4 w-4 opacity-50" />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
))
@ -41,7 +41,7 @@ const SelectScrollUpButton = React.forwardRef<
className={cn("flex cursor-default items-center justify-center py-1", className)}
{...props}
>
<ChevronUp className="h-4 w-4 text-zinc-400" strokeWidth={2} />
<ChevronUp className="h-4 w-4" />
</SelectPrimitive.ScrollUpButton>
))
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
@ -55,7 +55,7 @@ const SelectScrollDownButton = React.forwardRef<
className={cn("flex cursor-default items-center justify-center py-1", className)}
{...props}
>
<ChevronDown className="h-4 w-4 text-zinc-400" strokeWidth={2} />
<ChevronDown className="h-4 w-4" />
</SelectPrimitive.ScrollDownButton>
))
SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName
@ -69,7 +69,7 @@ const SelectContent = React.forwardRef<
<SelectPrimitive.Content
ref={ref}
className={cn(
"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-sm border border-zinc-700 bg-zinc-900 text-zinc-50 shadow-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
position === "popper" &&
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
className,
@ -112,14 +112,14 @@ const SelectItem = React.forwardRef<
<SelectPrimitive.Item
ref={ref}
className={cn(
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-zinc-800 focus:text-zinc-50 data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className,
)}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<SelectPrimitive.ItemIndicator>
<Check className="h-4 w-4 text-zinc-50" strokeWidth={2} />
<Check className="h-4 w-4" />
</SelectPrimitive.ItemIndicator>
</span>
@ -134,7 +134,7 @@ const SelectSeparator = React.forwardRef<
>(({ className, ...props }, ref) => (
<SelectPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-zinc-700", className)}
className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props}
/>
))

View file

@ -15,7 +15,7 @@ const Separator = React.forwardRef<
decorative={decorative}
orientation={orientation}
className={cn(
"shrink-0 bg-zinc-800",
"shrink-0 bg-border",
orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
className,
)}
@ -24,4 +24,4 @@ const Separator = React.forwardRef<
))
Separator.displayName = SeparatorPrimitive.Root.displayName
export { Separator }
export { Separator }

View file

@ -21,7 +21,7 @@ export function Switch({ checked, onCheckedChange, disabled, className, id }: Sw
disabled={disabled}
onClick={() => onCheckedChange(!checked)}
className={cn(
"peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-all duration-200 ease-in-out",
"peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors",
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
"disabled:cursor-not-allowed disabled:opacity-50",
checked ? "bg-primary" : "bg-muted-foreground/30",
@ -30,7 +30,7 @@ export function Switch({ checked, onCheckedChange, disabled, className, id }: Sw
>
<span
className={cn(
"pointer-events-none block h-4 w-4 rounded-full bg-background shadow-lg ring-0 transition-all duration-200 ease-in-out",
"pointer-events-none block h-4 w-4 rounded-full bg-background shadow-lg ring-0 transition-transform",
checked ? "translate-x-4" : "translate-x-0",
)}
/>

View file

@ -33,7 +33,7 @@ const TableFooter = React.forwardRef<
>(({ className, ...props }, ref) => (
<tfoot
ref={ref}
className={cn("border-t border-zinc-800 bg-zinc-900 font-medium [&>tr]:last:border-b-0", className)}
className={cn("border-t bg-muted/50 font-medium [&>tr]:last:border-b-0", className)}
{...props}
/>
))
@ -44,7 +44,7 @@ const TableRow = React.forwardRef<HTMLTableRowElement, React.HTMLAttributes<HTML
<tr
ref={ref}
className={cn(
"border-b border-zinc-800 transition-colors hover:bg-zinc-800/50 data-[state=selected]:bg-zinc-800",
"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
className,
)}
{...props}
@ -60,7 +60,7 @@ const TableHead = React.forwardRef<
<th
ref={ref}
className={cn(
"h-10 px-3 py-2 text-left align-middle font-medium font-mono text-zinc-400 [&:has([role=checkbox])]:pr-0",
"h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0",
className,
)}
{...props}
@ -74,7 +74,7 @@ const TableCell = React.forwardRef<
>(({ className, ...props }, ref) => (
<td
ref={ref}
className={cn("p-3 align-middle [&:has([role=checkbox])]:pr-0", className)}
className={cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className)}
{...props}
/>
))
@ -88,4 +88,4 @@ const TableCaption = React.forwardRef<
))
TableCaption.displayName = "TableCaption"
export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption }
export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption }

View file

@ -30,7 +30,7 @@ const TabsTrigger = React.forwardRef<
<TabsPrimitive.Trigger
ref={ref}
className={cn(
"inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm",
"inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm",
className,
)}
{...props}
@ -45,7 +45,7 @@ const TabsContent = React.forwardRef<
<TabsPrimitive.Content
ref={ref}
className={cn(
"mt-2 ring-offset-background transition-opacity duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
className,
)}
{...props}

View file

@ -0,0 +1,68 @@
/**
* Helpers to parse LLM raw_response for observability display.
* Ranking responses use <rank> and <explain> tags; optimization uses markdown code blocks.
* raw_response is often the full API JSON (e.g. OpenAI); we extract message content when present.
*/
/** Extract message content from OpenAI-style API response JSON, or return null */
export function extractMessageContentFromApiResponse(raw: string): string | null {
try {
const parsed = JSON.parse(raw) as {
choices?: Array<{ message?: { content?: string } }>
}
const content = parsed?.choices?.[0]?.message?.content
return typeof content === "string" ? content : null
} catch {
return null
}
}
/** Get the string to use for rank/explain and markdown parsing (inner content if API JSON, else raw) */
export function getResponseContentForParsing(rawResponse: string): string {
return extractMessageContentFromApiResponse(rawResponse) ?? rawResponse
}
/** Extract content inside <rank>...</rank> */
export function extractRankTag(content: string): string | null {
const m = content.match(/<rank>([\s\S]*?)<\/rank>/i)
return m ? m[1].trim() : null
}
/** Extract content inside <explain>...</explain> */
export function extractExplainTag(content: string): string | null {
const m = content.match(/<explain>([\s\S]*?)<\/explain>/i)
return m ? m[1].trim() : null
}
export type ResponseSegment =
| { kind: "text"; content: string }
| { kind: "code"; language: string; content: string }
/**
* Split markdown-like content into text and code blocks (```lang ... ```).
* Language is taken from the first word after ```; default "text".
*/
export function splitMarkdownCodeBlocks(content: string): ResponseSegment[] {
const segments: ResponseSegment[] = []
const re = /```(\w*)\n?([\s\S]*?)```/g
let lastEnd = 0
let m: RegExpExecArray | null
while ((m = re.exec(content)) !== null) {
if (m.index > lastEnd) {
const text = content.slice(lastEnd, m.index)
if (text.trim()) {
segments.push({ kind: "text", content: text })
}
}
const lang = m[1] || "text"
segments.push({ kind: "code", language: lang, content: m[2].trim() })
lastEnd = re.lastIndex
}
if (lastEnd < content.length) {
const text = content.slice(lastEnd)
if (text.trim()) {
segments.push({ kind: "text", content: text })
}
}
return segments
}

View file

@ -0,0 +1,38 @@
// Shared utilities for observability pages
/**
* Determines the source of an LLM call based on event_type and context
*/
export function getCallSource(
eventType: string | null,
context: Record<string, unknown> | null,
): string {
if (
context &&
typeof context === "object" &&
!Array.isArray(context) &&
"source" in context
) {
return String(context.source)
}
if (eventType) {
if (eventType === "pr_created" || eventType === "pr_merged" || eventType === "pr_closed") {
return "GitHub Action"
}
if (eventType === "no-pr") {
return "CLI/VSCode"
}
return eventType
}
return "Unknown"
}
/**
* Safely extracts cost and tokens from nullable values
*/
export function safeCostTokens(cost: number | null, tokens: number | null) {
return {
cost: cost ?? 0,
tokens: tokens ?? 0,
}
}

View file

@ -53,6 +53,7 @@ export const config = {
matcher: [
"/",
"/app/:path*",
"/trace/:path*",
"/billing",
"/billing/:path*",
"/apikeys",

View file

@ -1,47 +0,0 @@
/* Spacing and Layout Token System */
/* Based on 8px grid for consistent rhythm */
:root {
/* Base spacing unit - foundation of the 8px grid */
--space-unit: 8px;
/* Border Radius Tokens */
/* Professional, subtle radius values (2-4px max except for pills) */
--radius-sm: 2px; /* Tight, professional for small elements */
--radius-md: 3px; /* Default for cards, panels, buttons */
--radius-lg: 4px; /* Maximum for larger elements */
--radius-full: 9999px; /* Only for pills, badges, circular elements */
/* Spacing Tokens - 8px increments */
--space-0: 0px; /* 0 * 8px */
--space-px: 1px; /* For borders, hairlines */
--space-0\.5: 4px; /* 0.5 * 8px - fine adjustments */
--space-1: 8px; /* 1 * 8px */
--space-2: 16px; /* 2 * 8px */
--space-3: 24px; /* 3 * 8px */
--space-4: 32px; /* 4 * 8px */
--space-5: 40px; /* 5 * 8px */
--space-6: 48px; /* 6 * 8px */
--space-7: 56px; /* 7 * 8px */
--space-8: 64px; /* 8 * 8px */
--space-9: 72px; /* 9 * 8px */
--space-10: 80px; /* 10 * 8px */
/* Common Layout Spacing Patterns */
/* These map to the base spacing tokens for consistency */
--space-gap-xs: var(--space-1); /* 8px - Tight spacing */
--space-gap-sm: var(--space-2); /* 16px - Small spacing */
--space-gap-md: var(--space-3); /* 24px - Medium spacing */
--space-gap-lg: var(--space-4); /* 32px - Large spacing */
--space-gap-xl: var(--space-5); /* 40px - Extra large spacing */
/* Container and Content Spacing */
--space-container-sm: var(--space-2); /* 16px - Mobile/small screens */
--space-container-md: var(--space-3); /* 24px - Tablet/medium screens */
--space-container-lg: var(--space-4); /* 32px - Desktop/large screens */
/* Card and Panel Internal Spacing */
--space-card-sm: var(--space-2); /* 16px - Compact cards */
--space-card-md: var(--space-3); /* 24px - Standard cards */
--space-card-lg: var(--space-4); /* 32px - Spacious cards */
}

View file

@ -1,184 +0,0 @@
/**
* Design Token System
*
* Professional developer-focused design tokens using CSS custom properties.
* Dark mode only, zinc color scale, semantic status colors.
*
* RGB format (e.g., "24 24 27") for Tailwind's alpha channel support.
*/
:root {
/* ============================================
* ZINC COLOR SCALE
* Neutral colors for all UI elements
* RGB values without rgb() wrapper for Tailwind
* ============================================ */
/* Lightest to darkest */
--color-zinc-50: 250 250 250; /* Almost white */
--color-zinc-100: 244 244 245; /* Very light gray */
--color-zinc-200: 228 228 231; /* Light gray */
--color-zinc-300: 212 212 216; /* Medium light gray */
--color-zinc-400: 161 161 170; /* Medium gray */
--color-zinc-500: 113 113 122; /* True medium gray */
--color-zinc-600: 82 82 91; /* Medium dark gray */
--color-zinc-700: 63 63 70; /* Dark gray */
--color-zinc-800: 39 39 42; /* Very dark gray */
--color-zinc-900: 24 24 27; /* Near black */
--color-zinc-950: 9 9 11; /* Deep black */
/* ============================================
* SEMANTIC STATUS COLORS
* For errors, warnings, and success states
* ============================================ */
/* Error states (red-based) */
--color-error: 239 68 68; /* red-500 - Primary error */
--color-error-foreground: 254 242 242; /* red-50 - Error text on error bg */
--color-error-muted: 254 202 202; /* red-200 - Subtle error background */
--color-error-border: 248 113 113; /* red-400 - Error borders */
/* Success states (green-based) */
--color-success: 34 197 94; /* green-500 - Primary success */
--color-success-foreground: 240 253 244; /* green-50 - Success text on success bg */
--color-success-muted: 187 247 208; /* green-200 - Subtle success background */
--color-success-border: 74 222 128; /* green-400 - Success borders */
/* Warning states (amber-based, not bright yellow) */
--color-warning: 245 158 11; /* amber-500 - Primary warning */
--color-warning-foreground: 255 251 235; /* amber-50 - Warning text on warning bg */
--color-warning-muted: 254 215 170; /* amber-200 - Subtle warning background */
--color-warning-border: 251 191 36; /* amber-400 - Warning borders */
/* Info states (blue-based) */
--color-info: 59 130 246; /* blue-500 - Primary info */
--color-info-foreground: 239 246 255; /* blue-50 - Info text on info bg */
--color-info-muted: 191 219 254; /* blue-200 - Subtle info background */
--color-info-border: 96 165 250; /* blue-400 - Info borders */
/* ============================================
* FUNCTIONAL TOKENS
* High-level tokens that reference color scale
* These are what components should use
* ============================================ */
/* Core UI surfaces */
--background: var(--color-zinc-950); /* Main app background */
--foreground: var(--color-zinc-50); /* Main text color */
/* Card and panel surfaces */
--card: var(--color-zinc-900); /* Card background */
--card-foreground: var(--color-zinc-50); /* Card text */
/* Popover/dropdown surfaces */
--popover: var(--color-zinc-900); /* Popover background */
--popover-foreground: var(--color-zinc-50); /* Popover text */
/* Interactive elements */
--primary: var(--color-zinc-50); /* Primary button bg */
--primary-foreground: var(--color-zinc-950); /* Primary button text */
--secondary: var(--color-zinc-800); /* Secondary button bg */
--secondary-foreground: var(--color-zinc-50); /* Secondary button text */
--accent: var(--color-zinc-700); /* Accent elements */
--accent-foreground: var(--color-zinc-50); /* Accent text */
/* Muted states */
--muted: var(--color-zinc-800); /* Muted backgrounds */
--muted-foreground: var(--color-zinc-400); /* Muted text */
/* Destructive actions */
--destructive: var(--color-error); /* Destructive button bg */
--destructive-foreground: var(--color-error-foreground); /* Destructive button text */
/* Borders and inputs */
--border: var(--color-zinc-800); /* Default borders */
--input: var(--color-zinc-800); /* Input borders */
--ring: var(--color-zinc-600); /* Focus rings */
/* ============================================
* TYPOGRAPHY TOKENS
* Font stacks and text properties
* ============================================ */
/* Font families */
--font-mono: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', 'Roboto Mono', ui-monospace, 'Courier New', monospace;
--font-sans: system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
/* Font sizes - scale for data-dense layouts */
--text-xs: 0.75rem; /* 12px */
--text-sm: 0.875rem; /* 14px */
--text-base: 1rem; /* 16px */
--text-lg: 1.125rem; /* 18px */
--text-xl: 1.25rem; /* 20px */
--text-2xl: 1.5rem; /* 24px */
/* Line heights */
--leading-tight: 1.25;
--leading-normal: 1.5;
--leading-relaxed: 1.625;
/* ============================================
* SPACING TOKENS
* 8px grid system
* ============================================ */
--space-unit: 8px;
--space-0: 0;
--space-0-5: 4px; /* Half unit for fine adjustments */
--space-1: 8px; /* Base unit */
--space-1-5: 12px;
--space-2: 16px;
--space-2-5: 20px;
--space-3: 24px;
--space-4: 32px;
--space-5: 40px;
--space-6: 48px;
--space-7: 56px;
--space-8: 64px;
--space-9: 72px;
--space-10: 80px;
/* ============================================
* BORDER RADIUS TOKENS
* Flat, minimal rounded corners
* ============================================ */
--radius-none: 0;
--radius-sm: 2px; /* Subtle rounding */
--radius-md: 3px; /* Default */
--radius-lg: 4px; /* Maximum rounding */
--radius: var(--radius-md); /* Default radius */
/* ============================================
* SHADOW TOKENS
* Minimal shadows for depth
* ============================================ */
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.3);
--shadow-md: 0 2px 4px 0 rgba(0, 0, 0, 0.4);
--shadow-lg: 0 4px 6px 0 rgba(0, 0, 0, 0.5);
--shadow-xl: 0 8px 16px 0 rgba(0, 0, 0, 0.6);
/* ============================================
* TRANSITION TOKENS
* Consistent animation timing
* ============================================ */
--transition-fast: 150ms;
--transition-base: 250ms;
--transition-slow: 350ms;
--ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
}
/* ============================================
* DARK MODE ONLY
* We're not supporting light mode
* This ensures dark mode is always active
* ============================================ */
.dark {
/* All tokens are already defined for dark mode in :root */
/* This class exists for Tailwind's dark: variant to work */
}

View file

@ -1,29 +0,0 @@
/* Typography Token System */
:root {
/* Font Family Tokens */
--font-mono: 'JetBrains Mono', 'Fira Code', ui-monospace, 'SF Mono', 'Monaco', 'Cascadia Code', monospace;
--font-sans: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
/* Font Size Scale - optimized for dark mode readability */
--text-xs: 0.75rem; /* 12px */
--text-sm: 0.875rem; /* 14px */
--text-base: 1rem; /* 16px */
--text-lg: 1.125rem; /* 18px */
--text-xl: 1.25rem; /* 20px */
--text-2xl: 1.5rem; /* 24px */
--text-code: 0.875rem; /* 14px - inline code specific */
/* Font Weight Tokens - optimized for dark backgrounds */
--font-light: 300;
--font-normal: 400;
--font-medium: 500;
--font-semibold: 600;
--font-bold: 700;
/* Line Height Tokens - for data density control */
--leading-tight: 1.25;
--leading-normal: 1.5;
--leading-relaxed: 1.75;
--leading-code: 1.4; /* Specific for code blocks */
}

View file

@ -1,108 +1,20 @@
import type { Config } from "tailwindcss"
import { fontFamily } from "tailwindcss/defaultTheme"
const config: Config = {
darkMode: 'class',
content: [
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
],
theme: {
spacing: {
'px': '1px',
'0': '0',
'0.5': '4px',
'1': '8px',
'2': '16px',
'3': '24px',
'4': '32px',
'5': '40px',
'6': '48px',
'7': '56px',
'8': '64px',
'9': '72px',
'10': '80px',
},
borderRadius: {
'none': '0px',
'sm': 'var(--radius-sm)',
DEFAULT: 'var(--radius-md)',
'md': 'var(--radius-md)',
'lg': 'var(--radius-lg)',
'full': 'var(--radius-full)',
},
extend: {
colors: {
zinc: {
'50': 'rgb(var(--color-zinc-50) / <alpha-value>)',
'100': 'rgb(var(--color-zinc-100) / <alpha-value>)',
'200': 'rgb(var(--color-zinc-200) / <alpha-value>)',
'300': 'rgb(var(--color-zinc-300) / <alpha-value>)',
'400': 'rgb(var(--color-zinc-400) / <alpha-value>)',
'500': 'rgb(var(--color-zinc-500) / <alpha-value>)',
'600': 'rgb(var(--color-zinc-600) / <alpha-value>)',
'700': 'rgb(var(--color-zinc-700) / <alpha-value>)',
'800': 'rgb(var(--color-zinc-800) / <alpha-value>)',
'900': 'rgb(var(--color-zinc-900) / <alpha-value>)',
'950': 'rgb(var(--color-zinc-950) / <alpha-value>)',
},
background: 'rgb(var(--background) / <alpha-value>)',
foreground: 'rgb(var(--foreground) / <alpha-value>)',
card: {
DEFAULT: 'rgb(var(--card) / <alpha-value>)',
foreground: 'rgb(var(--card-foreground) / <alpha-value>)',
},
popover: {
DEFAULT: 'rgb(var(--popover) / <alpha-value>)',
foreground: 'rgb(var(--popover-foreground) / <alpha-value>)',
},
primary: {
DEFAULT: 'rgb(var(--primary) / <alpha-value>)',
foreground: 'rgb(var(--primary-foreground) / <alpha-value>)',
},
secondary: {
DEFAULT: 'rgb(var(--secondary) / <alpha-value>)',
foreground: 'rgb(var(--secondary-foreground) / <alpha-value>)',
},
muted: {
DEFAULT: 'rgb(var(--muted) / <alpha-value>)',
foreground: 'rgb(var(--muted-foreground) / <alpha-value>)',
},
accent: {
DEFAULT: 'rgb(var(--accent) / <alpha-value>)',
foreground: 'rgb(var(--accent-foreground) / <alpha-value>)',
},
error: {
DEFAULT: 'rgb(var(--error) / <alpha-value>)',
foreground: 'rgb(var(--error-foreground) / <alpha-value>)',
muted: 'rgb(var(--error-muted) / <alpha-value>)',
border: 'rgb(var(--error-border) / <alpha-value>)',
},
warning: {
DEFAULT: 'rgb(var(--warning) / <alpha-value>)',
foreground: 'rgb(var(--warning-foreground) / <alpha-value>)',
muted: 'rgb(var(--warning-muted) / <alpha-value>)',
border: 'rgb(var(--warning-border) / <alpha-value>)',
},
success: {
DEFAULT: 'rgb(var(--success) / <alpha-value>)',
foreground: 'rgb(var(--success-foreground) / <alpha-value>)',
muted: 'rgb(var(--success-muted) / <alpha-value>)',
border: 'rgb(var(--success-border) / <alpha-value>)',
},
info: {
DEFAULT: 'rgb(var(--info) / <alpha-value>)',
foreground: 'rgb(var(--info-foreground) / <alpha-value>)',
muted: 'rgb(var(--info-muted) / <alpha-value>)',
border: 'rgb(var(--info-border) / <alpha-value>)',
},
border: 'rgb(var(--border) / <alpha-value>)',
input: 'rgb(var(--input) / <alpha-value>)',
ring: 'rgb(var(--ring) / <alpha-value>)',
backgroundImage: {
"gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
"gradient-conic": "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
},
fontFamily: {
sans: ['var(--font-sans)'],
mono: ['var(--font-mono)'],
sans: ["var(--font-sans)", ...fontFamily.sans],
},
keyframes: {
shimmer: {

View file

@ -566,6 +566,7 @@
"integrity": "sha512-uug3FEEGv0r+jrecvUUpbY8lLisvIjg6AAic6a2bSP5OEOLeJsDSnvhCDov7ipFFMXS3orMpzlmi0ZcuGkBbow==",
"devOptional": true,
"license": "MIT",
"peer": true,
"dependencies": {
"undici-types": "~6.21.0"
}
@ -616,6 +617,7 @@
"integrity": "sha512-gTtSdWX9xiMPA/7MV9STjJOOYtWwIJIYxkQxnSV1U3xcE+mnJSH3f6zI0RYP+ew66WSlZ5ed+h0VCxsvdC1jJg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.41.0",
"@typescript-eslint/types": "8.41.0",
@ -881,6 +883,7 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@ -1890,6 +1893,7 @@
"deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.6.1",
@ -4075,6 +4079,7 @@
"devOptional": true,
"hasInstallScript": true,
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@prisma/config": "6.15.0",
"@prisma/engines": "6.15.0"
@ -5041,6 +5046,7 @@
"integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
"devOptional": true,
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"