mirror of
https://github.com/cfngc4594/monaco-editor-lsp-next.git
synced 2025-07-04 09:20:53 +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?
|
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
|
||||||
|
@ -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 (
|
||||||
|
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) => {
|
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;
|
||||||
};
|
};
|
||||||
|
@ -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),
|
||||||
|
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