mirror of
https://github.com/massbug/judge4c.git
synced 2025-07-03 23:30:50 +00:00
feat: add code analysisi
This commit is contained in:
parent
a1b09453d0
commit
f4735f315a
@ -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;
|
@ -130,6 +130,7 @@ model Submission {
|
||||
memoryUsage Int?
|
||||
|
||||
testcaseResults TestcaseResult[]
|
||||
codeAnalysis CodeAnalysis?
|
||||
|
||||
userId String
|
||||
problemId String
|
||||
@ -141,6 +142,27 @@ model Submission {
|
||||
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 {
|
||||
id String @id @default(cuid())
|
||||
expectedOutput String
|
||||
|
@ -1,11 +1,12 @@
|
||||
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 { DetailPanel } from "@/features/problems/detail/components/panel";
|
||||
import { SolutionPanel } from "@/features/problems/solution/components/panel";
|
||||
import { SubmissionPanel } from "@/features/problems/submission/components/panel";
|
||||
import { DescriptionPanel } from "@/features/problems/description/components/panel";
|
||||
import { ProblemFlexLayout } from "@/features/problems/components/problem-flexlayout";
|
||||
import { AnalysisPanel } from "@/features/problems/analysis/components/panel";
|
||||
|
||||
interface ProblemPageProps {
|
||||
params: Promise<{ problemId: string }>;
|
||||
@ -28,7 +29,7 @@ export default async function ProblemPage({
|
||||
detail: <DetailPanel submissionId={submissionId} />,
|
||||
code: <CodePanel problemId={problemId} />,
|
||||
testcase: <TestcasePanel problemId={problemId} />,
|
||||
bot: <BotPanel problemId={problemId} />,
|
||||
bot: <AnalysisPanel submissionId={submissionId} />,
|
||||
};
|
||||
|
||||
return (
|
||||
|
144
src/app/actions/analyze-code.ts
Normal file
144
src/app/actions/analyze-code.ts
Normal 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 text—only 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");
|
||||
}
|
||||
};
|
@ -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) => {
|
||||
console.log("🚀 ~ analyzeComplexity ~ content:", content);
|
||||
return { time: Complexity.Enum["O(N)"], space: Complexity.Enum["O(1)"] };
|
||||
export const analyzeComplexity = async (
|
||||
content: string
|
||||
): 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;
|
||||
};
|
||||
|
@ -7,6 +7,7 @@ import { compile } from "./compile";
|
||||
import { auth, signIn } from "@/lib/auth";
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { Language, Status } from "@/generated/client";
|
||||
import { analyzeCode } from "@/app/actions/analyze-code";
|
||||
import { createContainer, createTarStream, prepareEnvironment } from "./docker";
|
||||
|
||||
export const judge = async (
|
||||
@ -119,6 +120,11 @@ export const judge = async (
|
||||
},
|
||||
});
|
||||
|
||||
await analyzeCode({
|
||||
content,
|
||||
submissionId: submission.id,
|
||||
});
|
||||
|
||||
// Upload code to the container
|
||||
const tarStream = createTarStream(
|
||||
getFileNameForLanguage(language),
|
||||
|
113
src/features/problems/analysis/components/panel.tsx
Normal file
113
src/features/problems/analysis/components/panel.tsx
Normal 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>
|
||||
);
|
||||
};
|
45
src/features/problems/analysis/components/radar-chart.tsx
Normal file
45
src/features/problems/analysis/components/radar-chart.tsx
Normal 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>
|
||||
);
|
||||
}
|
Loading…
Reference in New Issue
Block a user