feat: add code analysisi

This commit is contained in:
cfngc4594 2025-06-20 15:18:41 +08:00
parent a1b09453d0
commit f4735f315a
8 changed files with 414 additions and 6 deletions

View File

@ -0,0 +1,23 @@
-- CreateTable
CREATE TABLE "CodeAnalysis" (
"id" TEXT NOT NULL,
"submissionId" TEXT NOT NULL,
"timeComplexity" TEXT,
"spaceComplexity" TEXT,
"overallScore" INTEGER,
"styleScore" INTEGER,
"readabilityScore" INTEGER,
"efficiencyScore" INTEGER,
"correctnessScore" INTEGER,
"feedback" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "CodeAnalysis_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "CodeAnalysis_submissionId_key" ON "CodeAnalysis"("submissionId");
-- AddForeignKey
ALTER TABLE "CodeAnalysis" ADD CONSTRAINT "CodeAnalysis_submissionId_fkey" FOREIGN KEY ("submissionId") REFERENCES "Submission"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@ -130,6 +130,7 @@ model Submission {
memoryUsage Int? memoryUsage Int?
testcaseResults TestcaseResult[] testcaseResults TestcaseResult[]
codeAnalysis CodeAnalysis?
userId String userId String
problemId String problemId String
@ -141,6 +142,27 @@ model Submission {
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
} }
model CodeAnalysis {
id String @id @default(cuid())
submissionId String @unique
timeComplexity String?
spaceComplexity String?
overallScore Int?
styleScore Int?
readabilityScore Int?
efficiencyScore Int?
correctnessScore Int?
feedback String?
submission Submission @relation(fields: [submissionId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Testcase { model Testcase {
id String @id @default(cuid()) id String @id @default(cuid())
expectedOutput String expectedOutput String

View File

@ -1,11 +1,12 @@
import { TestcasePanel } from "@/features/problems/testcase/panel"; import { TestcasePanel } from "@/features/problems/testcase/panel";
import { BotPanel } from "@/features/problems/bot/components/panel"; // import { BotPanel } from "@/features/problems/bot/components/panel";
import { CodePanel } from "@/features/problems/code/components/panel"; import { CodePanel } from "@/features/problems/code/components/panel";
import { DetailPanel } from "@/features/problems/detail/components/panel"; import { DetailPanel } from "@/features/problems/detail/components/panel";
import { SolutionPanel } from "@/features/problems/solution/components/panel"; import { SolutionPanel } from "@/features/problems/solution/components/panel";
import { SubmissionPanel } from "@/features/problems/submission/components/panel"; import { SubmissionPanel } from "@/features/problems/submission/components/panel";
import { DescriptionPanel } from "@/features/problems/description/components/panel"; import { DescriptionPanel } from "@/features/problems/description/components/panel";
import { ProblemFlexLayout } from "@/features/problems/components/problem-flexlayout"; import { ProblemFlexLayout } from "@/features/problems/components/problem-flexlayout";
import { AnalysisPanel } from "@/features/problems/analysis/components/panel";
interface ProblemPageProps { interface ProblemPageProps {
params: Promise<{ problemId: string }>; params: Promise<{ problemId: string }>;
@ -28,7 +29,7 @@ export default async function ProblemPage({
detail: <DetailPanel submissionId={submissionId} />, detail: <DetailPanel submissionId={submissionId} />,
code: <CodePanel problemId={problemId} />, code: <CodePanel problemId={problemId} />,
testcase: <TestcasePanel problemId={problemId} />, testcase: <TestcasePanel problemId={problemId} />,
bot: <BotPanel problemId={problemId} />, bot: <AnalysisPanel submissionId={submissionId} />,
}; };
return ( return (

View File

@ -0,0 +1,144 @@
import { z } from "zod";
import prisma from "@/lib/prisma";
import { generateText, tool } from "ai";
import { deepseek } from "@ai-sdk/deepseek";
import { Complexity } from "@/types/complexity";
interface analyzeCodeProps {
content: string;
submissionId: string;
}
export const analyzeCode = async ({
content,
submissionId,
}: analyzeCodeProps) => {
try {
const result = await generateText({
model: deepseek("deepseek-chat"),
system: `You are an AI assistant that rigorously analyzes code for time and space complexity, and assesses overall code quality.
**Time/Space Complexity MUST be one of these values:**
- O(1)
- O(logN)
- O(N)
- O(N)
- O(NlogN)
- O(N^2)
- O(2^N)
- O(N!)
---
**Scoring Guidelines:**
**Overall Score (0-100):** Represents the comprehensive quality of the code, encompassing all other metrics.
- **90-100 (Exceptional):** Highly optimized, perfectly readable, robust, and well-structured.
- **70-89 (Good):** Minor areas for improvement, generally readable, efficient, and functional.
- **50-69 (Acceptable):** Noticeable areas for improvement in style, readability, or efficiency; may have minor bugs.
- **30-49 (Poor):** Significant issues in multiple areas; code is difficult to understand or maintain.
- **0-29 (Unacceptable):** Major flaws, likely non-functional, extremely difficult to maintain, or fundamentally incorrect.
**Code Style Score (0-100):** Adherence to common coding conventions (e.g., consistent indentation, naming conventions, proper use of whitespace, clear file organization).
- **90-100 (Flawless):** Adheres to all best practices, making the code visually clean and consistent.
- **70-89 (Good):** Minor style inconsistencies, but generally well-formatted.
- **50-69 (Acceptable):** Noticeable style issues affecting consistency and readability.
- **30-49 (Poor):** Significant style deviations, making the code messy and hard to read.
- **0-29 (Unacceptable):** Extremely poor style, rendering the code almost unreadable due to formatting issues.
**Readability Score (0-100):** How easy the code is to understand (e.g., clear variable/function names, concise logic, effective comments, modularity).
- **90-100 (Exceptional):** Exceptionally clear and easy to understand, even for complex logic; excellent use of comments and modular design.
- **70-89 (Good):** Generally clear, minor effort required for understanding; good variable names and some helpful comments.
- **50-69 (Acceptable):** Some parts are hard to follow; could benefit from better naming, more comments, or clearer logic.
- **30-49 (Poor):** Difficult to understand without significant effort; cryptic names, missing comments, or convoluted logic.
- **0-29 (Unacceptable):** Almost impossible to decipher; requires extensive time to understand basic functionality.
**Efficiency Score (0-100):** How well the code utilizes computational resources (time and space complexity, optimized algorithms, unnecessary computations).
- **90-100 (Optimal):** Achieves optimal time and space complexity; highly optimized algorithm with no redundant operations.
- **70-89 (Good):** Good efficiency, slight room for minor optimization; chosen algorithm is appropriate but could be subtly refined.
- **50-69 (Acceptable):** Acceptable efficiency, but clear areas for significant improvement in algorithm choice or implementation.
- **30-49 (Poor):** Inefficient, uses excessive resources; likely to cause performance issues on larger inputs.
- **0-29 (Unacceptable):** Extremely inefficient; likely to time out or crash on even small to medium-sized inputs.
**Correctness Score (0-100):** Whether the code produces the correct output for all valid inputs and handles edge cases appropriately.
- **90-100 (Flawless):** Produces correct output for all specified requirements, including comprehensive handling of edge cases and invalid inputs.
- **70-89 (Mostly Correct):** Mostly correct, with minor bugs or issues in specific edge cases; may not handle all invalid inputs gracefully.
- **50-69 (Acceptable):** Contains noticeable bugs, fails on some common inputs, or has significant limitations in handling edge cases.
- **30-49 (Many Bugs):** Produces incorrect output frequently; fails on many common inputs.
- **0-29 (Completely Incorrect):** Non-functional or fundamentally incorrect; fails on most inputs.
---
**Important Considerations for Scoring:**
- **Objectivity:** Base your scores strictly on the provided definitions and common best practices, not on personal preference.
- **Justification:** For each score, especially those below 90, provide clear and concise justifications in the 'feedback' field, explaining *why* a particular score was given and *how* it can be improved.
- **Actionable Feedback:** Ensure your feedback is actionable, offering specific, practical suggestions for improvement rather than vague statements.
---
**Your response MUST call the 'saveCodeAnalysis' tool with the following:**
- Time Complexity (choose from the list above)
- Space Complexity (choose from the list above)
- Overall Score (0-100)
- Code Style Score (0-100)
- Readability Score (0-100)
- Efficiency Score (0-100)
- Correctness Score (0-100)
- Feedback (detailed, actionable suggestions)
**DO NOT return plain textonly call the tool!**`,
messages: [{ role: "user", content: content }],
tools: {
saveCodeAnalysis: tool({
description:
"Stores the AI's code analysis results into the database.",
parameters: z.object({
timeComplexity: Complexity.optional(),
spaceComplexity: Complexity.optional(),
overallScore: z.number().int().min(0).max(100).optional(),
styleScore: z.number().int().min(0).max(100).optional(),
readabilityScore: z.number().int().min(0).max(100).optional(),
efficiencyScore: z.number().int().min(0).max(100).optional(),
correctnessScore: z.number().int().min(0).max(100).optional(),
feedback: z.string().optional(),
}),
execute: async ({
timeComplexity,
spaceComplexity,
overallScore,
styleScore,
readabilityScore,
efficiencyScore,
correctnessScore,
feedback,
}) => {
const codeAnalysis = await prisma.codeAnalysis.create({
data: {
submissionId,
timeComplexity,
spaceComplexity,
overallScore,
styleScore,
readabilityScore,
efficiencyScore,
correctnessScore,
feedback,
},
});
return {
success: true,
message: "Code analysis saved successfully",
data: codeAnalysis,
};
},
}),
},
toolChoice: { type: "tool", toolName: "saveCodeAnalysis" },
});
return result;
} catch (error) {
console.error("Error analyzing code:", error);
throw new Error("Failed to analyze code");
}
};

View File

@ -1,6 +1,60 @@
import { Complexity } from "@/types/complexity"; import {
AnalyzeComplexityResponse,
AnalyzeComplexityResponseSchema,
Complexity,
} from "@/types/complexity";
import { deepseek } from "@ai-sdk/deepseek";
import { CoreMessage, generateText } from "ai";
export const analyzeComplexity = async (content: string) => { export const analyzeComplexity = async (
console.log("🚀 ~ analyzeComplexity ~ content:", content); content: string
return { time: Complexity.Enum["O(N)"], space: Complexity.Enum["O(1)"] }; ): Promise<AnalyzeComplexityResponse> => {
const prompt = `
Analyze the time and space complexity of the following programming code snippet.
Determine the Big O notation from this list: ${Complexity.options.join(", ")}.
Provide your response as a JSON object with one key:
1. "time": A string representing the time complexity (e.g., "O(N)", "O(logN)").
2. "space": A string representing the space complexity (e.g., "O(N)", "O(1)").
Code to analyze:
\`\`\`
${content}
\`\`\`
Respond ONLY with the JSON object. Do not include any other text or markdown formmating like \`\`\`json before or after the object.
`;
const messages: CoreMessage[] = [{ role: "user", content: prompt }];
let text;
try {
const response = await generateText({
model: deepseek("deepseek-chat"),
messages: messages,
});
text = response.text;
} catch (error) {
console.error("Error generating text:", error);
throw new Error("Failed to generate response from LLM");
}
let llmResponseJson;
try {
const cleanedText = text.trim();
llmResponseJson = JSON.parse(cleanedText);
} catch (error) {
console.error("Failed to parse LLM response as JSON:", error);
console.error("LLM raw output:", text);
throw new Error("Invalid JSON response from LLM");
}
const validationResult =
AnalyzeComplexityResponseSchema.safeParse(llmResponseJson);
if (!validationResult.success) {
console.error("Zod validation failed:", validationResult.error.format());
throw new Error("Response validation failed");
}
return validationResult.data;
}; };

View File

@ -7,6 +7,7 @@ import { compile } from "./compile";
import { auth, signIn } from "@/lib/auth"; import { auth, signIn } from "@/lib/auth";
import { revalidatePath } from "next/cache"; import { revalidatePath } from "next/cache";
import { Language, Status } from "@/generated/client"; import { Language, Status } from "@/generated/client";
import { analyzeCode } from "@/app/actions/analyze-code";
import { createContainer, createTarStream, prepareEnvironment } from "./docker"; import { createContainer, createTarStream, prepareEnvironment } from "./docker";
export const judge = async ( export const judge = async (
@ -119,6 +120,11 @@ export const judge = async (
}, },
}); });
await analyzeCode({
content,
submissionId: submission.id,
});
// Upload code to the container // Upload code to the container
const tarStream = createTarStream( const tarStream = createTarStream(
getFileNameForLanguage(language), getFileNameForLanguage(language),

View File

@ -0,0 +1,113 @@
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
CardFooter,
} from "@/components/ui/card";
import prisma from "@/lib/prisma";
import { ChartDataPoint, CodeAnalysisRadarChart } from "./radar-chart";
export const description = "A server component to fetch code analysis data.";
interface AnalysisPanelProps {
submissionId: string | undefined;
}
export const AnalysisPanel = async ({ submissionId }: AnalysisPanelProps) => {
if (!submissionId) {
return (
<div className="p-4 text-center text-muted-foreground">
No submission ID provided.
</div>
);
}
const codeAnalysisData = await prisma.codeAnalysis.findUnique({
where: {
submissionId: submissionId,
},
});
if (!codeAnalysisData) {
return (
<div className="p-4 text-center text-muted-foreground">
No analysis data found for this submission.
</div>
);
}
// Transform the data into a format suitable for the RadarChart
const chartData: ChartDataPoint[] = [
{
kind: "overall",
score: codeAnalysisData.overallScore ?? 0,
fullMark: 100,
},
{
kind: "style",
score: codeAnalysisData.styleScore ?? 0,
fullMark: 100,
},
{
kind: "readability",
score: codeAnalysisData.readabilityScore ?? 0,
fullMark: 100,
},
{
kind: "efficiency",
score: codeAnalysisData.efficiencyScore ?? 0,
fullMark: 100,
},
{
kind: "correctness",
score: codeAnalysisData.correctnessScore ?? 0,
fullMark: 100,
},
];
return (
<Card className="w-full max-w-2xl mx-auto shadow-lg rounded-xl overflow-hidden border-0 bg-background/50 backdrop-blur-sm animate-fade-in">
<CardHeader className="items-center pb-2 space-y-1 px-6 pt-6">
<CardTitle className="text-2xl font-bold bg-gradient-to-r from-primary to-foreground bg-clip-text text-transparent">
Code Analysis
</CardTitle>
<CardDescription className="text-muted-foreground">
Detailed evaluation of your code submission
</CardDescription>
</CardHeader>
<CardContent className="p-6">
<CodeAnalysisRadarChart chartData={chartData} />
</CardContent>
<CardFooter className="flex-col items-start gap-4 p-6 pt-0">
<div className="w-full space-y-3">
<div className="flex justify-between text-sm font-medium">
<span className="text-muted-foreground">Overall Score</span>
<span className="text-primary">
{codeAnalysisData.overallScore ?? "N/A"}
<span className="text-muted-foreground">/100</span>
</span>
</div>
<div className="relative h-2.5 w-full overflow-hidden rounded-full bg-muted">
<div
className="h-full bg-gradient-to-r from-primary to-purple-500 rounded-full transition-all duration-700 ease-out"
style={{
width: `${codeAnalysisData.overallScore ?? 0}%`,
transitionProperty: "width",
}}
/>
</div>
</div>
<div className="text-muted-foreground bg-muted/40 p-4 rounded-lg w-full border">
<h3 className="font-medium mb-2 text-foreground">Feedback</h3>
<p className="whitespace-pre-wrap leading-relaxed">
{codeAnalysisData.feedback}
</p>
</div>
</CardFooter>
</Card>
);
};

View File

@ -0,0 +1,45 @@
"use client";
import {
PolarAngleAxis,
PolarGrid,
PolarRadiusAxis,
Radar,
RadarChart,
} from "recharts";
import {
ChartContainer,
ChartTooltip,
ChartTooltipContent,
} from "@/components/ui/chart";
export interface ChartDataPoint {
kind: string;
score: number;
fullMark: number;
}
interface CodeAnalysisRadarChartProps {
chartData: ChartDataPoint[];
}
export function CodeAnalysisRadarChart({
chartData,
}: CodeAnalysisRadarChartProps) {
return (
<ChartContainer config={{}} className="mx-auto aspect-square max-w-[345px]">
<RadarChart outerRadius={90} data={chartData}>
<ChartTooltip cursor={false} content={<ChartTooltipContent />} />
<PolarAngleAxis dataKey="kind" />
<PolarGrid />
<PolarRadiusAxis domain={[0, 100]} axisLine={false} tick={false} />
<Radar
name="Score"
dataKey="score"
stroke="rgb(95, 134, 196)"
fill="rgba(95, 134, 196, 0.1)"
/>
</RadarChart>
</ChartContainer>
);
}