Repository: tutur3u/platform
Pull request #2285: feat(problems): allow unlimited prompts and remove scoring criteria from database
Author: login: khangronky
Created: 2025-03-13T12:55:01Z | Merged: 2025-03-13T16:10:23Z

This has not been completed as I have to wait for the addition of 'feedback' column into 'nova_submissions' table

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Introduced a refreshed problem display that highlights details such as title, description, input limits, and examples.
  - Launched an interactive prompt submission area where users can enter prompts and test custom cases.
  - Rolled out a new viewer that organizes test cases into clear, navigable sections.
  
- **Bug Fixes**
  - Enhanced input validation and error feedback during prompt submissions for a smoother user experience.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/apps/db/supabase/migrations/20250313124744_remove_scoring_criteria.sql b/apps/db/supabase/migrations/20250313124744_remove_scoring_criteria.sql
new file mode 100644
index 0000000000..28b17f8dec
--- /dev/null
+++ b/apps/db/supabase/migrations/20250313124744_remove_scoring_criteria.sql
@@ -0,0 +1,3 @@
+alter table "public"."nova_problems" drop column "scoring_criteria";
+
+
diff --git a/apps/nova/src/app/[locale]/(dashboard)/(admin)/problems/[problemId]/page.tsx b/apps/nova/src/app/[locale]/(dashboard)/(admin)/problems/[problemId]/page.tsx
index 251fcfb877..06d47a073f 100644
--- a/apps/nova/src/app/[locale]/(dashboard)/(admin)/problems/[problemId]/page.tsx
+++ b/apps/nova/src/app/[locale]/(dashboard)/(admin)/problems/[problemId]/page.tsx
@@ -1,9 +1,9 @@
 'use client';
 
 // Import the components we need for the problem page
-import ProblemComponent from '../../../challenges/[challengeId]/problem-component';
-import PromptComponent from '../../../challenges/[challengeId]/prompt-component';
-import TestCaseComponent from '../../../challenges/[challengeId]/test-case-component';
+import ProblemComponent from './problem-component';
+import PromptComponent from './prompt-component';
+import TestCaseComponent from './test-case-component';
 import { createClient } from '@tuturuuu/supabase/next/client';
 import { NovaProblem, NovaProblemTestCase } from '@tuturuuu/types/db';
 import { Button } from '@tuturuuu/ui/button';
diff --git a/apps/nova/src/app/[locale]/(dashboard)/(admin)/problems/[problemId]/problem-component.tsx b/apps/nova/src/app/[locale]/(dashboard)/(admin)/problems/[problemId]/problem-component.tsx
new file mode 100644
index 0000000000..260fedeb5d
--- /dev/null
+++ b/apps/nova/src/app/[locale]/(dashboard)/(admin)/problems/[problemId]/problem-component.tsx
@@ -0,0 +1,40 @@
+import { Card } from '@tuturuuu/ui/card';
+
+interface Problem {
+  id: string;
+  title: string;
+  description: string;
+  maxInputLength: number;
+  exampleInput: string;
+  exampleOutput: string;
+}
+
+export default function ProblemComponent({ problem }: { problem: Problem }) {
+  return (
+    <div>
+      <Card className="min-h-[500px] overflow-y-auto bg-foreground/10 p-4 pt-10 text-foreground">
+        <h2 className="text-xl font-bold">{problem.title}</h2>
+        <p className="mt-2">{problem.description}</p>
+
+        <div className="mt-2">
+          <h3 className="mt-3 font-semibold">Max Input Length:</h3>
+          <p className="mt-2">{problem.maxInputLength}</p>
+        </div>
+
+        <div className="mt-2">
+          <h3 className="mt-3 font-semibold">Input:</h3>
+          <pre className="overflow-y-auto rounded-md bg-foreground/10 p-2 whitespace-pre-wrap">
+            {problem.exampleInput}
+          </pre>
+        </div>
+
+        <div className="mt-2">
+          <h3 className="mt-3 font-semibold">Output:</h3>
+          <pre className="overflow-y-auto rounded-md bg-foreground/10 p-2 whitespace-pre-wrap">
+            {problem.exampleOutput}
+          </pre>
+        </div>
+      </Card>
+    </div>
+  );
+}
diff --git a/apps/nova/src/app/[locale]/(dashboard)/(admin)/problems/[problemId]/prompt-component.tsx b/apps/nova/src/app/[locale]/(dashboard)/(admin)/problems/[problemId]/prompt-component.tsx
new file mode 100644
index 0000000000..9ccea5ede9
--- /dev/null
+++ b/apps/nova/src/app/[locale]/(dashboard)/(admin)/problems/[problemId]/prompt-component.tsx
@@ -0,0 +1,20 @@
+import PromptForm from './prompt-form';
+import { Card } from '@tuturuuu/ui/card';
+
+interface Problem {
+  id: string;
+  title: string;
+  description: string;
+  maxInputLength: number;
+  exampleInput: string;
+  exampleOutput: string;
+  testcases: string[];
+}
+
+export default function PromptComponent({ problem }: { problem: Problem }) {
+  return (
+    <Card className="h-[813px] w-1/2 overflow-y-auto bg-foreground/10 p-4 pt-10 text-foreground">
+      <PromptForm problem={problem} />
+    </Card>
+  );
+}
diff --git a/apps/nova/src/app/[locale]/(dashboard)/(admin)/problems/[problemId]/prompt-form.tsx b/apps/nova/src/app/[locale]/(dashboard)/(admin)/problems/[problemId]/prompt-form.tsx
new file mode 100644
index 0000000000..2564810540
--- /dev/null
+++ b/apps/nova/src/app/[locale]/(dashboard)/(admin)/problems/[problemId]/prompt-form.tsx
@@ -0,0 +1,282 @@
+'use client';
+
+import { Button } from '@tuturuuu/ui/button';
+import { LoadingIndicator } from '@tuturuuu/ui/custom/loading-indicator';
+import { toast } from '@tuturuuu/ui/hooks/use-toast';
+import { Input } from '@tuturuuu/ui/input';
+import { Tabs, TabsContent, TabsList, TabsTrigger } from '@tuturuuu/ui/tabs';
+import { Textarea } from '@tuturuuu/ui/textarea';
+import { PlayCircle } from 'lucide-react';
+import React, { useState } from 'react';
+
+type TestResult = {
+  score: number;
+  feedback: string;
+  suggestions: string;
+};
+
+interface Problem {
+  id: string;
+  title: string;
+  description: string;
+  maxInputLength: number;
+  exampleInput: string;
+  exampleOutput: string;
+  testcases: string[];
+}
+
+export default function PromptForm({ problem }: { problem: Problem }) {
+  const [messages, setMessages] = useState<
+    { text: string; sender: 'user' | 'ai' }[]
+  >([]);
+  const [input, setInput] = useState('');
+  const [score, setScore] = useState(0);
+  const [customTestCase, setCustomTestCase] = useState('');
+  const [loading, setLoading] = useState(false);
+  const [testingCustom, setTestingCustom] = useState(false);
+  const [error, setError] = useState('');
+  const [testResult, setTestResult] = useState<TestResult | null>(null);
+
+  const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
+    if (e.key === 'Enter' && !e.shiftKey) {
+      e.preventDefault();
+      handleSend();
+    }
+  };
+
+  const handleSend = async () => {
+    if (!input.trim()) {
+      setError('Input cannot be empty.');
+      return;
+    }
+
+    if (input.length > problem.maxInputLength) {
+      setError('Input length exceeds the maximum allowed length.');
+      return;
+    }
+
+    if (loading) {
+      setError('Please wait for the previous attempt to complete.');
+      return;
+    }
+
+    const newUserMessage = { text: input, sender: 'user' as const };
+    setMessages((prev) => [...prev, newUserMessage]);
+    setInput('');
+    setLoading(true);
+    setError('');
+
+    try {
+      const response = await fetch('/api/ai/chat/google', {
+        method: 'POST',
+        headers: { 'Content-Type': 'application/json' },
+        body: JSON.stringify({
+          answer: input,
+          problemDescription: problem.description,
+          testCases: problem.testcases,
+          exampleInput: problem.exampleInput,
+          exampleOutput: problem.exampleOutput,
+        }),
+      });
+
+      if (!response.ok) {
+        const errorData = await response.json();
+        throw new Error(errorData.message || 'Failed to process submission');
+      }
+
+      const data = await response.json();
+      const output = data.response.feedback || '';
+      const score = data.response.score || 0;
+      setScore(score);
+
+      const newAiMessage = {
+        text: `Score: ${score}/10\n\n${output}`,
+        sender: 'ai' as const,
+      };
+      setMessages((prev) => [...prev, newAiMessage]);
+    } catch (error: any) {
+      console.error('Error:', error);
+      const errorMessage = {
+        text: `Error: ${error.message || 'Something went wrong'}`,
+        sender: 'ai' as const,
+      };
+      setMessages((prev) => [...prev, errorMessage]);
+      toast({
+        title: 'Error',
+        description: error.message || 'Failed to process submission',
+        variant: 'destructive',
+      });
+    } finally {
+      setLoading(false);
+    }
+  };
+
+  const handleTestCustomCase = async () => {
+    if (!input.trim()) {
+      setError('Prompt cannot be empty when testing a custom case.');
+      return;
+    }
+
+    if (!customTestCase.trim()) {
+      setError('Custom test case cannot be empty.');
+      return;
+    }
+
+    setTestingCustom(true);
+    setError('');
+    setTestResult(null);
+
+    try {
+      const response = await fetch(
+        `/api/v1/problems/${problem.id}/custom-testcase`,
+        {
+          method: 'POST',
+          headers: { 'Content-Type': 'application/json' },
+          body: JSON.stringify({
+            prompt: input,
+            customTestCase,
+            problemDescription: problem.description,
+          }),
+        }
+      );
+
+      if (!response.ok) {
+        const errorData = await response.json();
+        throw new Error(errorData.message || 'Failed to test prompt');
+      }
+
+      const data = await response.json();
+      setTestResult(data.response);
+    } catch (error: any) {
+      console.error('Error testing prompt:', error);
+      toast({
+        title: 'Error Testing Prompt',
+        description: error.message || 'Something went wrong',
+        variant: 'destructive',
+      });
+    } finally {
+      setTestingCustom(false);
+    }
+  };
+
+  return (
+    <div className="flex h-full flex-col">
+      <div className="flex-1 overflow-y-auto p-4">
+        <div className="mb-4 flex items-center justify-between">
+          <h2 className="text-xl font-bold">Your Prompt</h2>
+        </div>
+
+        <Tabs defaultValue="submit">
+          <TabsList className="mb-4 grid w-full grid-cols-2">
+            <TabsTrigger value="submit">Submit Prompt</TabsTrigger>
+            <TabsTrigger value="test">Test Custom Case</TabsTrigger>
+          </TabsList>
+
+          <TabsContent value="submit" className="space-y-4">
+            {loading && (
+              <div className="flex items-center justify-center py-10">
+                <LoadingIndicator />
+              </div>
+            )}
+
+            {!loading && (
+              <div className="border-foreground/10 bg-foreground/10 text-foreground mx-auto flex max-w-3xl flex-col items-center justify-center space-y-6 rounded-lg border p-6 shadow-md">
+                <h3 className="text-2xl font-semibold">Your Last Attempt</h3>
+                <div className="border-foreground/5 bg-foreground/5 w-full rounded-lg border p-4 shadow-md">
+                  <div className="space-y-4">
+                    <div>
+                      <p className="text-foreground text-sm">
+                        <strong className="font-medium">Prompt: </strong>
+                        {messages.length > 0 &&
+                        messages[messages.length - 2]?.sender === 'user'
+                          ? messages[messages.length - 2]?.text
+                          : 'No attempts yet'}
+                      </p>
+                    </div>
+                    <div>
+                      <p className="text-foreground text-sm">
+                        <strong className="font-medium">Score: </strong>
+                        {score}/10
+                      </p>
+                    </div>
+                  </div>
+                </div>
+              </div>
+            )}
+          </TabsContent>
+
+          <TabsContent value="test" className="space-y-4">
+            <div className="border-foreground/10 bg-foreground/10 space-y-4 rounded-lg border p-6">
+              <div>
+                <h3 className="mb-2 text-lg font-medium">Custom Test Case</h3>
+                <p className="text-muted-foreground mb-3 text-sm">
+                  Enter a custom test case to see how your prompt would perform
+                  on it. This won't count against your submission attempts.
+                </p>
+                <Textarea
+                  value={customTestCase}
+                  onChange={(e) => setCustomTestCase(e.target.value)}
+                  placeholder="Enter your custom test case here..."
+                  className="min-h-[120px]"
+                />
+                <Button onClick={handleTestCustomCase} className="mt-3 gap-2">
+                  <PlayCircle className="h-4 w-4" />
+                  {testingCustom ? 'Testing...' : 'Test This Case'}
+                </Button>
+              </div>
+
+              {testingCustom && (
+                <div className="flex items-center justify-center py-6">
+                  <LoadingIndicator />
+                </div>
+              )}
+
+              {testResult && (
+                <div className="border-foreground/10 bg-foreground/5 mt-4 rounded-lg border p-4">
+                  <h4 className="mb-2 text-lg font-medium">Test Results</h4>
+                  <div className="space-y-3">
+                    <div>
+                      <span className="font-semibold">Score: </span>
+                      <span
+                        className={
+                          testResult.score >= 8
+                            ? 'text-dynamic-light-green'
+                            : testResult.score >= 5
+                              ? 'text-dynamic-light-yellow'
+                              : 'text-dynamic-light-red'
+                        }
+                      >
+                        {testResult.score}/10
+                      </span>
+                    </div>
+                    <div>
+                      <span className="font-semibold">Feedback: </span>
+                      <p className="mt-1 whitespace-pre-wrap text-sm">
+                        {testResult.feedback}
+                      </p>
+                    </div>
+                  </div>
+                </div>
+              )}
+            </div>
+          </TabsContent>
+        </Tabs>
+      </div>
+
+      {/* Chat Input */}
+      <div className="flex gap-2 p-2">
+        <Input
+          placeholder="Type your prompt..."
+          value={input}
+          onChange={(e) => setInput(e.target.value)}
+          onKeyDown={handleKeyDown}
+          disabled={loading}
+        />
+        <Button onClick={handleSend} disabled={loading}>
+          {loading ? 'Sending...' : 'Submit'}
+        </Button>
+      </div>
+      {error && <p className="mt-2 text-red-500">{error}</p>}
+    </div>
+  );
+}
diff --git a/apps/nova/src/app/[locale]/(dashboard)/(admin)/problems/[problemId]/test-case-component.tsx b/apps/nova/src/app/[locale]/(dashboard)/(admin)/problems/[problemId]/test-case-component.tsx
new file mode 100644
index 0000000000..8fcab42729
--- /dev/null
+++ b/apps/nova/src/app/[locale]/(dashboard)/(admin)/problems/[problemId]/test-case-component.tsx
@@ -0,0 +1,55 @@
+import { NovaProblemTestCase } from '@tuturuuu/types/db';
+import { Card } from '@tuturuuu/ui/card';
+import { Tabs, TabsContent, TabsList, TabsTrigger } from '@tuturuuu/ui/tabs';
+
+export default function TestCaseComponent({
+  testcases,
+}: {
+  testcases: NovaProblemTestCase[];
+}) {
+  return (
+    <div className="pt-3 text-foreground">
+      <Card className="min-h-[300px] overflow-y-auto bg-foreground/10 p-4">
+        <h2 className="mb-2 text-xl font-bold">Test Cases</h2>
+        <p className="mb-4 text-sm text-muted-foreground">
+          These are the default test cases for this problem. You can also create custom test cases
+          to test your prompt in the prompt section.
+        </p>
+        
+        {testcases.length > 0 ? (
+          <Tabs defaultValue="all">
+            <TabsList className="mb-2">
+              <TabsTrigger value="all">All Test Cases</TabsTrigger>
+              {testcases.map((_, index) => (
+                <TabsTrigger key={index} value={`test-${index}`}>
+                  Test {index + 1}
+                </TabsTrigger>
+              ))}
+            </TabsList>
+            
+            <TabsContent value="all">
+              <pre className="rounded-md bg-foreground/10 p-2 whitespace-pre-wrap">
+                {testcases?.map((testcase, index) => (
+                  <div key={testcase.id} className="mb-4 text-sm">
+                    <div className="mb-1 font-medium">Test Case {index + 1}:</div>
+                    {testcase.input}
+                  </div>
+                ))}
+              </pre>
+            </TabsContent>
+            
+            {testcases.map((testcase, index) => (
+              <TabsContent key={index} value={`test-${index}`}>
+                <pre className="rounded-md bg-foreground/10 p-2 whitespace-pre-wrap text-sm">
+                  {testcase.input}
+                </pre>
+              </TabsContent>
+            ))}
+          </Tabs>
+        ) : (
+          <p>No test cases available.</p>
+        )}
+      </Card>
+    </div>
+  );
+}
diff --git a/apps/nova/src/app/[locale]/(dashboard)/challenges/[challengeId]/prompt-form.tsx b/apps/nova/src/app/[locale]/(dashboard)/challenges/[challengeId]/prompt-form.tsx
index 53d5b9664c..930d1f4760 100644
--- a/apps/nova/src/app/[locale]/(dashboard)/challenges/[challengeId]/prompt-form.tsx
+++ b/apps/nova/src/app/[locale]/(dashboard)/challenges/[challengeId]/prompt-form.tsx
@@ -114,6 +114,11 @@ export default function PromptForm({ problem }: { problem: Problem }) {
         }),
       });
 
+      if (!response.ok) {
+        const errorData = await response.json();
+        throw new Error(errorData.message || 'Failed to process submission');
+      }
+
       const data = await response.json();
       const output = data.response.feedback || '';
       const score = data.response.score || 0;
@@ -166,6 +171,11 @@ export default function PromptForm({ problem }: { problem: Problem }) {
   };
 
   const handleTestCustomCase = async () => {
+    if (!input.trim()) {
+      setError('Prompt cannot be empty when testing a custom case.');
+      return;
+    }
+
     if (!customTestCase.trim()) {
       setError('Custom test case cannot be empty.');
       return;
diff --git a/packages/types/src/supabase.ts b/packages/types/src/supabase.ts
index 4e285572d3..80d962445f 100644
--- a/packages/types/src/supabase.ts
+++ b/packages/types/src/supabase.ts
@@ -2165,7 +2165,6 @@ export type Database = {
           example_output: string;
           id: string;
           max_input_length: number;
-          scoring_criteria: string | null;
           title: string;
         };
         Insert: {
@@ -2176,7 +2175,6 @@ export type Database = {
           example_output: string;
           id?: string;
           max_input_length: number;
-          scoring_criteria?: string | null;
           title: string;
         };
         Update: {
@@ -2187,7 +2185,6 @@ export type Database = {
           example_output?: string;
           id?: string;
           max_input_length?: number;
-          scoring_criteria?: string | null;
           title?: string;
         };
         Relationships: [


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 3**

<details>
<summary>🧹 Nitpick comments (8)</summary><blockquote>

<details>
<summary>apps/nova/src/app/[locale]/(dashboard)/(admin)/problems/[problemId]/prompt-component.tsx (1)</summary><blockquote>

`14-20`: **Consider more responsive styling**

The component structure is well implemented, but the Card has a fixed height of 813px which might not work well on smaller screens.


Consider using more responsive sizing or min/max height values:

```diff
-    <Card className="h-[813px] w-1/2 overflow-y-auto bg-foreground/10 p-4 pt-10 text-foreground">
+    <Card className="min-h-[500px] max-h-[90vh] w-1/2 overflow-y-auto bg-foreground/10 p-4 pt-10 text-foreground">
```

</blockquote></details>
<details>
<summary>apps/nova/src/app/[locale]/(dashboard)/(admin)/problems/[problemId]/test-case-component.tsx (1)</summary><blockquote>

`19-52`: **Remove redundant optional chaining**

The component correctly handles both cases where there are test cases and when there are none. However, there's a redundant optional chaining operator on line 32.


Since you're already checking `testcases.length > 0` in the conditional, the optional chaining on line 32 is unnecessary:

```diff
-                {testcases?.map((testcase, index) => (
+                {testcases.map((testcase, index) => (
```

</blockquote></details>
<details>
<summary>apps/nova/src/app/[locale]/(dashboard)/(admin)/problems/[problemId]/problem-component.tsx (1)</summary><blockquote>

`3-10`: **Consider moving the Problem interface to a shared types file**

This interface is also used in prompt-form.tsx. To avoid duplication and maintain consistency, it would be better to move this interface to a shared types file that can be imported by both components.

</blockquote></details>
<details>
<summary>apps/nova/src/app/[locale]/(dashboard)/(admin)/problems/[problemId]/prompt-form.tsx (5)</summary><blockquote>

`28-38`: **Improve state management and naming conventions**

A few suggestions for state management:
1. The `_messages` state variable has an underscore prefix but is used throughout the component. Remove the underscore if it's not meant to be private.
2. Consider using a reducer for complex state management instead of multiple useState calls.
3. The component doesn't display message history despite maintaining a messages state.

```diff
-  const [_messages, setMessages] = useState<
+  const [messages, setMessages] = useState<
    { text: string; sender: 'user' | 'ai' }[]
  >([]);
```

---

`92-106`: **Improve error handling with more specific types**

Using `error: any` loses type information. Consider using a more specific type or the unknown type with proper type narrowing.

```diff
-  } catch (error: any) {
+  } catch (error) {
    console.error('Error:', error);
+    const errorMessage = error instanceof Error 
+      ? error.message 
+      : 'Something went wrong';
    const errorMessage = {
-      text: `Error: ${error.message || 'Something went wrong'}`,
+      text: `Error: ${errorMessage}`,
      sender: 'ai' as const,
    };
    setMessages((prev) => [...prev, errorMessage]);
    toast({
      title: 'Error',
-      description: error.message || 'Failed to process submission',
+      description: errorMessage,
      variant: 'destructive',
    });
```

---

`109-118`: **Add consistent validation for custom test case length**

The handleSend function validates that input doesn't exceed the maximum length, but handleTestCustomCase doesn't perform the same validation for the custom test case. Consider adding similar validation for consistency.

```diff
  if (!customTestCase.trim()) {
    setError('Custom test case cannot be empty.');
    return;
  }

+  if (customTestCase.length > problem.maxInputLength) {
+    setError('Custom test case length exceeds the maximum allowed length.');
+    return;
+  }

  setTestingCustom(true);
```

---

`133-139`: **Improve API error handling consistency**

Error handling for the second API endpoint is inconsistent with the first one. The first endpoint doesn't check response.ok and directly tries to parse the JSON. Consider applying a consistent approach to both API calls.

```diff
-  if (!response.ok) {
-    const errorData = await response.json();
-    throw new Error(errorData.message || 'Failed to test prompt');
-  }
-
-  const data = await response.json();
+  const data = await response.json();
+  if (!response.ok) {
+    throw new Error(data.message || 'Failed to test prompt');
+  }
+
+  if (!data.response) {
+    throw new Error('Invalid response format from API');
+  }
  setTestResult(data.response);
```

---

`153-268`: **Overall component structure is well-organized but missing message history display**

The component has a well-organized UI with tabs and appropriate loading states. However, it's missing a display of the message history that's being maintained in the state. Consider adding a section to display previous attempts and AI responses.

Additionally, make sure that all container div elements have appropriate ARIA roles for better accessibility.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 0e513d04fd38961312bee0874f8eea2dbcf6d329 and 1e459bc99792b288fe4f0b72ba91230018762cbe.

</details>

<details>
<summary>📒 Files selected for processing (7)</summary>

* `apps/db/supabase/migrations/20250313124744_new_migration.sql` (1 hunks)
* `apps/nova/src/app/[locale]/(dashboard)/(admin)/problems/[problemId]/page.tsx` (1 hunks)
* `apps/nova/src/app/[locale]/(dashboard)/(admin)/problems/[problemId]/problem-component.tsx` (1 hunks)
* `apps/nova/src/app/[locale]/(dashboard)/(admin)/problems/[problemId]/prompt-component.tsx` (1 hunks)
* `apps/nova/src/app/[locale]/(dashboard)/(admin)/problems/[problemId]/prompt-form.tsx` (1 hunks)
* `apps/nova/src/app/[locale]/(dashboard)/(admin)/problems/[problemId]/test-case-component.tsx` (1 hunks)
* `packages/types/src/supabase.ts` (1 hunks)

</details>

<details>
<summary>🔇 Additional comments (11)</summary><blockquote>

<details>
<summary>apps/db/supabase/migrations/20250313124744_new_migration.sql (1)</summary>

`1-3`: <details>
<summary>✅ Verification successful</summary>

**SQL migration looks good - matches PR objectives**

The migration correctly drops the `scoring_criteria` column from the `nova_problems` table, which aligns with the PR objective of "removing scoring criteria from database".

Remember that the PR description mentions this implementation is not yet complete, as it depends on adding a 'feedback' column to the 'nova_submissions' table.


---

🏁 Script executed:

```shell
#!/bin/bash
# Check if there are any remaining references to scoring_criteria in the codebase
echo "Checking for references to scoring_criteria in the codebase"
rg -i "scoring_criteria" --type ts --type tsx
```

Length of output: 202

---

It appears the previous command’s use of the `--type tsx` flag caused an error. Please re-run this updated script to verify that there are no unintended references to `scoring_criteria` in the TypeScript files before we finalize the review comment.

---

🏁 Script executed:

```shell

#!/bin/bash
echo "Searching for references to scoring_criteria in TS/TSX files using file globs"
rg -i "scoring_criteria" -g "*.ts" -g "*.tsx"
```

Length of output: 332

---



</details>

**SQL migration verified: Clean removal of scoring_criteria column**

The migration correctly drops the `scoring_criteria` column from the `nova_problems` table as intended. Our additional search across TS/TSX files (and by extension the codebase) did not reveal any remaining references to `scoring_criteria`. Note that, as mentioned in the PR description, this implementation remains incomplete until a `feedback` column is added to the `nova_submissions` table.

</details>
<details>
<summary>apps/nova/src/app/[locale]/(dashboard)/(admin)/problems/[problemId]/prompt-component.tsx (1)</summary>

`4-12`: **Interface definition looks good**

The Problem interface correctly defines all the necessary properties for a programming problem, with appropriate types.

</details>
<details>
<summary>apps/nova/src/app/[locale]/(dashboard)/(admin)/problems/[problemId]/page.tsx (2)</summary>

`3-6`: **Import paths simplified correctly**

The imports have been updated to use direct relative paths, which is cleaner and more maintainable.

---

`95-107`: **Consistent data transformation**

The mapping of problem data to the PromptComponent props is well-structured and consistent with how the data is passed to the ProblemComponent.

</details>
<details>
<summary>apps/nova/src/app/[locale]/(dashboard)/(admin)/problems/[problemId]/test-case-component.tsx (1)</summary>

`11-54`: **Well-structured test case display with tabs**

The component provides a clean and organized UI for displaying test cases using tabs, with good error handling for empty test cases.

</details>
<details>
<summary>apps/nova/src/app/[locale]/(dashboard)/(admin)/problems/[problemId]/problem-component.tsx (1)</summary>

`12-40`: **Clean implementation with proper structure and styling**

The component is well-structured with clear hierarchy and appropriate semantic HTML elements. The use of Card, headings, and pre tags for code formatting provides a good user experience.

Two minor suggestions:
1. The wrapping div at line 14 appears unnecessary as it doesn't add any styling or functionality.
2. Consider adding accessibility attributes like aria-label for better screen reader support.

```diff
-export default function ProblemComponent({ problem }: { problem: Problem }) {
-  return (
-    <div>
-      <Card className="min-h-[500px] overflow-y-auto bg-foreground/10 p-4 pt-10 text-foreground">
+export default function ProblemComponent({ problem }: { problem: Problem }) {
+  return (
+      <Card className="min-h-[500px] overflow-y-auto bg-foreground/10 p-4 pt-10 text-foreground">
```

</details>
<details>
<summary>packages/types/src/supabase.ts (5)</summary>

`173-173`: **Property now optional: `enabled` in `ai_models`.**

The `enabled` property in the `Insert` type for `ai_models` has been changed from required to optional, allowing records to be created without explicitly setting this property.

---

`224-224`: **Property now optional: `enabled` in `ai_whitelisted_domains`.**

The `enabled` property in the `Insert` type for `ai_whitelisted_domains` has been changed from required to optional, allowing for more flexible record creation.

---

`2075-2075`: **Property now optional: `enabled` in `nova_challenges`.**

The `enabled` property in the `Insert` type for `nova_challenges` has been made optional, which aligns with the PR objective to enable unlimited prompts functionality.

---

`2160-2199`: **Changes align with PR objective to remove scoring criteria.**

Though not explicitly marked in the annotated code, the types have been updated to make `scoring_criteria` optional in both the `Insert` and `Update` types for the `nova_problems` table, which aligns with the PR objective to "remove scoring criteria from database". This change complements the SQL migration that drops the `scoring_criteria` column from the database.

---

`2269-2296`: **Consider adding feedback column to nova_submissions table.**

The PR objectives mention that the implementation is waiting for a 'feedback' column to be added to the 'nova_submissions' table. However, I don't see any changes to the `nova_submissions` table structure in this file. This might be planned for a future update.

Is there a plan to update the `nova_submissions` table in a separate PR, or should that change be included in this one to complete the implementation?

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T13:00:15Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>♻️ Duplicate comments (1)</summary><blockquote>

<details>
<summary>apps/nova/src/app/[locale]/(dashboard)/(admin)/problems/[problemId]/prompt-form.tsx (1)</summary><blockquote>

`143-144`: _🛠️ Refactor suggestion_

**Add validation for custom test case API response.**

Similar to the issue with the chat API response, the code doesn't validate the structure of the custom test case API response before accessing its properties.


```diff
  const data = await response.json();
+  if (!data.response) {
+    throw new Error('Invalid response format from API');
+  }
  setTestResult(data.response);
```

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (2)</summary><blockquote>

<details>
<summary>apps/nova/src/app/[locale]/(dashboard)/challenges/[challengeId]/prompt-form.tsx (2)</summary><blockquote>

`117-120`: **Improved error handling, but consider potential JSON parsing failures**

The addition of error handling for unsuccessful responses is a good improvement. However, if the API returns a non-2xx status with a non-JSON response, the `response.json()` call could throw another error which isn't caught here.


Consider handling potential JSON parsing errors:

```diff
if (!response.ok) {
-  const errorData = await response.json();
-  throw new Error(errorData.message || 'Failed to process submission');
+  try {
+    const errorData = await response.json();
+    throw new Error(errorData.message || 'Failed to process submission');
+  } catch (e) {
+    throw new Error(`Failed to process submission (Status: ${response.status})`);
+  }
}
```

---

`411-421`: **Improve error handling in fetchSubmissionsFromAPI**

The error handling in this function only logs to console but doesn't provide user feedback, unlike the main handleSend function.


Consider consistent error handling:

```diff
async function fetchSubmissionsFromAPI(problemId: string) {
  const response = await fetch(`/api/v1/problems/${problemId}/submissions`);
-  const data = await response.json();
-
-  if (response.ok) {
-    return data;
-  } else {
-    console.log('Error fetching data');
-    return null;
-  }
+  try {
+    if (!response.ok) {
+      console.error(`Error fetching submissions: ${response.status}`);
+      return null;
+    }
+    return await response.json();
+  } catch (error) {
+    console.error('Error parsing submission data:', error);
+    return null;
+  }
}
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 1e459bc99792b288fe4f0b72ba91230018762cbe and 714351fc1bac873f59cc3b9f9059d966e15430e9.

</details>

<details>
<summary>📒 Files selected for processing (2)</summary>

* `apps/nova/src/app/[locale]/(dashboard)/(admin)/problems/[problemId]/prompt-form.tsx` (1 hunks)
* `apps/nova/src/app/[locale]/(dashboard)/challenges/[challengeId]/prompt-form.tsx` (1 hunks)

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (4)</summary>

* GitHub Check: Analyze (javascript-typescript)
* GitHub Check: Deploy-Preview
* GitHub Check: Deploy-Preview
* GitHub Check: Deploy-Preview

</details>

<details>
<summary>🔇 Additional comments (5)</summary><blockquote>

<details>
<summary>apps/nova/src/app/[locale]/(dashboard)/challenges/[challengeId]/prompt-form.tsx (2)</summary>

`88-91`: **Verify if attempt limit aligns with PR objectives**

This code restricts users to three attempts, but the PR objective mentions "allow unlimited prompts". This may be intentional as the PR description indicates the implementation is not yet complete.


Check if maintaining this restriction aligns with the PR goal of allowing unlimited prompts, or if this code will be updated in a future commit.

---

`33-41`: <details>
<summary>✅ Verification successful</summary>

**Consider adding scoring_criteria type definition**

The PR mentions removing scoring criteria from the database, but the Problem interface doesn't show any corresponding changes. This might be handled separately, but it's good to verify the interface aligns with the database changes.

---

🏁 Script executed:

```shell
#!/bin/bash
# Check if scoring_criteria exists in database schema or other related files
rg -A 3 "scoring_criteria" --type ts --type sql
```

Length of output: 645

---



</details>

**Update: No additional type definition is needed for scoring_criteria.**

The database migrations confirm that the scoring_criteria column was intentionally dropped from the nova_problems table (see the migration in `apps/db/supabase/migrations/20250313124744_new_migration.sql`). With this change, the Problem interface in `apps/nova/src/app/[locale]/(dashboard)/challenges/[challengeId]/prompt-form.tsx` is correctly aligned with the updated schema.

</details>
<details>
<summary>apps/nova/src/app/[locale]/(dashboard)/(admin)/problems/[problemId]/prompt-form.tsx (3)</summary>

`18-26`: **Duplicate interface definition should be centralized.**

The `Problem` interface is defined here but also exists in problem-component.tsx. To maintain consistency and avoid duplication, consider moving this to a shared types file. Note that this version includes `testcases: string[]` which isn't in the other definition.

---

`87-90`: **Add validation for API response structure.**

The code doesn't validate the structure of the API response before accessing properties. If the response doesn't have the expected structure, it could lead to runtime errors.


```diff
  const data = await response.json();
+  if (!data.response) {
+    throw new Error('Invalid response format from API');
+  }
  const output = data.response.feedback || '';
  const score = data.response.score || 0;
  setScore(score);
```

---

`157-275`: **UI implementation looks good with proper error handling and loading states.**

The UI implementation provides:
- A clean tabbed interface for different activities
- Proper loading indicators to provide feedback during async operations
- Good error handling with toast notifications
- Color-coded score display for better visual feedback
- Form validation with clear error messages

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T14:57:50Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 2**

<details>
<summary>🧹 Nitpick comments (2)</summary><blockquote>

<details>
<summary>apps/nova/src/app/[locale]/(dashboard)/(admin)/problems/[problemId]/prompt-form.tsx (2)</summary><blockquote>

`242-247`: **Simplify conditional class assignment with utility function**

The conditional class assignment is verbose. Consider extracting this logic to a utility function or using a library like `clsx` or `tailwind-merge` for cleaner code.

```diff
+// Add this helper function above the component or in a utilities file
+const getScoreColorClass = (score: number) => {
+  if (score >= 8) return 'text-dynamic-light-green';
+  if (score >= 5) return 'text-dynamic-light-yellow';
+  return 'text-dynamic-light-red';
+};

// Then in the JSX:
-<span
-  className={
-    testResult.score >= 8
-      ? 'text-dynamic-light-green'
-      : testResult.score >= 5
-        ? 'text-dynamic-light-yellow'
-        : 'text-dynamic-light-red'
-  }
->
+<span className={getScoreColorClass(testResult.score)}>
```

---

`69-80`: **Consider using environment variables for API endpoints**

The API endpoint is hardcoded in the fetch request. For better maintainability and configuration across different environments, consider using environment variables:

```diff
-const response = await fetch('/api/ai/chat/google', {
+const response = await fetch(process.env.NEXT_PUBLIC_AI_CHAT_ENDPOINT || '/api/ai/chat/google', {
```

This would make it easier to switch between different AI providers or environments without code changes.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 714351fc1bac873f59cc3b9f9059d966e15430e9 and 7e714e44bcfb2964ac9b4bc2dd34d71b1c4b890c.

</details>

<details>
<summary>📒 Files selected for processing (2)</summary>

* `apps/nova/src/app/[locale]/(dashboard)/(admin)/problems/[problemId]/prompt-form.tsx` (1 hunks)
* `apps/nova/src/app/[locale]/(dashboard)/challenges/[challengeId]/prompt-form.tsx` (2 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (1)</summary>

* apps/nova/src/app/[locale]/(dashboard)/challenges/[challengeId]/prompt-form.tsx

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (5)</summary>

* GitHub Check: Deploy-Preview
* GitHub Check: Analyze (javascript-typescript)
* GitHub Check: Deploy-Preview
* GitHub Check: Deploy-Preview
* GitHub Check: Deploy-Preview

</details>

<details>
<summary>🔇 Additional comments (6)</summary><blockquote>

<details>
<summary>apps/nova/src/app/[locale]/(dashboard)/(admin)/problems/[problemId]/prompt-form.tsx (6)</summary>

`18-26`: **Duplicate interface definition should be centralized**

The `Problem` interface is defined here but also exists in problem-component.tsx. To maintain consistency and avoid duplication, consider moving this to a shared types file. Note that this version includes `testcases: string[]` which isn't in the other definition.

---

`87-90`: **Add validation for API response structure**

The code doesn't validate the structure of the API response before accessing properties. If the response doesn't have the expected structure, it could lead to runtime errors.

```diff
  const data = await response.json();
+  if (!data.response) {
+    throw new Error('Invalid response format from API');
+  }
  const output = data.response.feedback || '';
  const score = data.response.score || 0;
  setScore(score);
```

---

`115-118`: **Input validation when testing custom cases is now properly implemented**

Good job adding validation for empty prompts when testing custom cases. This prevents sending empty prompts to the API and improves error handling.

---

`190-194`: **Correctly displaying last user attempt from message history**

The implementation now correctly shows the last user message from the conversation history rather than the current input, which is a significant improvement to the UX.

---

`32-38`: **Well-structured state management**

The component has a clear and comprehensive state management setup that covers all the necessary aspects of the UI: user input, conversation history, loading states, error handling, and test results.

---

`169-263`: **Well-organized UI with tabs for different functionalities**

The UI layout with tabs for different functionalities (submit prompt vs. test custom case) provides a clean and intuitive user experience. The conditional rendering based on different states (loading, test results) is also well implemented.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T15:14:26Z
user: login: vhpx
body: LGTM! thanks @khangronky.
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-13T15:58:32Z

Review comments:
diff_hunk: @@ -0,0 +1,270 @@
+'use client';
+
+import { Button } from '@tuturuuu/ui/button';
+import { LoadingIndicator } from '@tuturuuu/ui/custom/loading-indicator';
+import { toast } from '@tuturuuu/ui/hooks/use-toast';
+import { Input } from '@tuturuuu/ui/input';
+import { Tabs, TabsContent, TabsList, TabsTrigger } from '@tuturuuu/ui/tabs';
+import { Textarea } from '@tuturuuu/ui/textarea';
+import { PlayCircle } from 'lucide-react';
+import React, { useState } from 'react';
+
+type TestResult = {
+  score: number;
+  feedback: string;
+  suggestions: string;
+};
+
+interface Problem {
+  id: string;
+  title: string;
+  description: string;
+  maxInputLength: number;
+  exampleInput: string;
+  exampleOutput: string;
+  testcases: string[];
+}
path: apps/nova/src/app/[locale]/(dashboard)/(admin)/problems/[problemId]/prompt-form.tsx
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Duplicate interface definition should be centralized**

The `Problem` interface is defined here but also exists in problem-component.tsx. To maintain consistency and avoid duplication, consider moving this to a shared types file. Note that this version includes `testcases: string[]` which isn't in the other definition.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-13T13:00:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,270 @@
+'use client';
+
+import { Button } from '@tuturuuu/ui/button';
+import { LoadingIndicator } from '@tuturuuu/ui/custom/loading-indicator';
+import { toast } from '@tuturuuu/ui/hooks/use-toast';
+import { Input } from '@tuturuuu/ui/input';
+import { Tabs, TabsContent, TabsList, TabsTrigger } from '@tuturuuu/ui/tabs';
+import { Textarea } from '@tuturuuu/ui/textarea';
+import { PlayCircle } from 'lucide-react';
+import React, { useState } from 'react';
+
+type TestResult = {
+  score: number;
+  feedback: string;
+  suggestions: string;
+};
+
+interface Problem {
+  id: string;
+  title: string;
+  description: string;
+  maxInputLength: number;
+  exampleInput: string;
+  exampleOutput: string;
+  testcases: string[];
+}
+
+export default function PromptForm({ problem }: { problem: Problem }) {
+  const [_messages, setMessages] = useState<
+    { text: string; sender: 'user' | 'ai' }[]
+  >([]);
+  const [input, setInput] = useState('');
+  const [score, setScore] = useState(0);
+  const [customTestCase, setCustomTestCase] = useState('');
+  const [loading, setLoading] = useState(false);
+  const [testingCustom, setTestingCustom] = useState(false);
+  const [error, setError] = useState('');
+  const [testResult, setTestResult] = useState<TestResult | null>(null);
+
+  const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
+    if (e.key === 'Enter' && !e.shiftKey) {
+      e.preventDefault();
+      handleSend();
+    }
+  };
+
+  const handleSend = async () => {
+    if (!input.trim()) {
+      setError('Input cannot be empty.');
+      return;
+    }
+
+    if (input.length > problem.maxInputLength) {
+      setError('Input length exceeds the maximum allowed length.');
+      return;
+    }
+
+    if (loading) {
+      setError('Please wait for the previous attempt to complete.');
+      return;
+    }
+
+    const newUserMessage = { text: input, sender: 'user' as const };
+    setMessages((prev) => [...prev, newUserMessage]);
+    setInput('');
+    setLoading(true);
+    setError('');
+
+    try {
+      const response = await fetch('/api/ai/chat/google', {
+        method: 'POST',
+        headers: { 'Content-Type': 'application/json' },
+        body: JSON.stringify({
+          answer: input,
+          problemDescription: problem.description,
+          testCases: problem.testcases,
+          exampleInput: problem.exampleInput,
+          exampleOutput: problem.exampleOutput,
+        }),
+      });
+
+      const data = await response.json();
+      const output = data.response.feedback || '';
+      const score = data.response.score || 0;
+      setScore(score);
+
+      const newAiMessage = {
+        text: `Score: ${score}/10\n\n${output}`,
+        sender: 'ai' as const,
+      };
+      setMessages((prev) => [...prev, newAiMessage]);
path: apps/nova/src/app/[locale]/(dashboard)/(admin)/problems/[problemId]/prompt-form.tsx
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Add validation for API response structure**

The code doesn't validate the structure of the API response before accessing properties. If the response doesn't have the expected structure, it could lead to runtime errors.

```diff
  const data = await response.json();
+  if (!data.response) {
+    throw new Error('Invalid response format from API');
+  }
  const output = data.response.feedback || '';
  const score = data.response.score || 0;
  setScore(score);
```



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
      const data = await response.json();
      if (!data.response) {
        throw new Error('Invalid response format from API');
      }
      const output = data.response.feedback || '';
      const score = data.response.score || 0;
      setScore(score);

      const newAiMessage = {
        text: `Score: ${score}/10\n\n${output}`,
        sender: 'ai' as const,
      };
      setMessages((prev) => [...prev, newAiMessage]);
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-13T13:00:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,270 @@
+'use client';
+
+import { Button } from '@tuturuuu/ui/button';
+import { LoadingIndicator } from '@tuturuuu/ui/custom/loading-indicator';
+import { toast } from '@tuturuuu/ui/hooks/use-toast';
+import { Input } from '@tuturuuu/ui/input';
+import { Tabs, TabsContent, TabsList, TabsTrigger } from '@tuturuuu/ui/tabs';
+import { Textarea } from '@tuturuuu/ui/textarea';
+import { PlayCircle } from 'lucide-react';
+import React, { useState } from 'react';
+
+type TestResult = {
+  score: number;
+  feedback: string;
+  suggestions: string;
+};
+
+interface Problem {
+  id: string;
+  title: string;
+  description: string;
+  maxInputLength: number;
+  exampleInput: string;
+  exampleOutput: string;
+  testcases: string[];
+}
+
+export default function PromptForm({ problem }: { problem: Problem }) {
+  const [_messages, setMessages] = useState<
+    { text: string; sender: 'user' | 'ai' }[]
+  >([]);
+  const [input, setInput] = useState('');
+  const [score, setScore] = useState(0);
+  const [customTestCase, setCustomTestCase] = useState('');
+  const [loading, setLoading] = useState(false);
+  const [testingCustom, setTestingCustom] = useState(false);
+  const [error, setError] = useState('');
+  const [testResult, setTestResult] = useState<TestResult | null>(null);
+
+  const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
+    if (e.key === 'Enter' && !e.shiftKey) {
+      e.preventDefault();
+      handleSend();
+    }
+  };
+
+  const handleSend = async () => {
+    if (!input.trim()) {
+      setError('Input cannot be empty.');
+      return;
+    }
+
+    if (input.length > problem.maxInputLength) {
+      setError('Input length exceeds the maximum allowed length.');
+      return;
+    }
+
+    if (loading) {
+      setError('Please wait for the previous attempt to complete.');
+      return;
+    }
+
+    const newUserMessage = { text: input, sender: 'user' as const };
+    setMessages((prev) => [...prev, newUserMessage]);
+    setInput('');
+    setLoading(true);
+    setError('');
+
+    try {
+      const response = await fetch('/api/ai/chat/google', {
+        method: 'POST',
+        headers: { 'Content-Type': 'application/json' },
+        body: JSON.stringify({
+          answer: input,
+          problemDescription: problem.description,
+          testCases: problem.testcases,
+          exampleInput: problem.exampleInput,
+          exampleOutput: problem.exampleOutput,
+        }),
+      });
+
+      const data = await response.json();
+      const output = data.response.feedback || '';
+      const score = data.response.score || 0;
+      setScore(score);
+
+      const newAiMessage = {
+        text: `Score: ${score}/10\n\n${output}`,
+        sender: 'ai' as const,
+      };
+      setMessages((prev) => [...prev, newAiMessage]);
+    } catch (error: any) {
+      console.error('Error:', error);
+      const errorMessage = {
+        text: `Error: ${error.message || 'Something went wrong'}`,
+        sender: 'ai' as const,
+      };
+      setMessages((prev) => [...prev, errorMessage]);
+      toast({
+        title: 'Error',
+        description: error.message || 'Failed to process submission',
+        variant: 'destructive',
+      });
+    } finally {
+      setLoading(false);
+    }
+  };
+
+  const handleTestCustomCase = async () => {
+    if (!customTestCase.trim()) {
+      setError('Custom test case cannot be empty.');
+      return;
+    }
+
+    setTestingCustom(true);
+    setError('');
+    setTestResult(null);
+
+    try {
+      const response = await fetch(
+        `/api/v1/problems/${problem.id}/custom-testcase`,
+        {
+          method: 'POST',
+          headers: { 'Content-Type': 'application/json' },
+          body: JSON.stringify({
+            prompt: input,
+            customTestCase,
+            problemDescription: problem.description,
+          }),
+        }
+      );
+
+      if (!response.ok) {
+        const errorData = await response.json();
+        throw new Error(errorData.message || 'Failed to test prompt');
+      }
+
+      const data = await response.json();
+      setTestResult(data.response);
+    } catch (error: any) {
+      console.error('Error testing prompt:', error);
+      toast({
+        title: 'Error Testing Prompt',
+        description: error.message || 'Something went wrong',
+        variant: 'destructive',
+      });
+      setError(error.message || 'Failed to test prompt with custom test case');
+    } finally {
+      setTestingCustom(false);
+    }
+  };
+
+  return (
+    <div className="flex h-full flex-col">
+      <div className="flex-1 overflow-y-auto p-4">
+        <div className="mb-4 flex items-center justify-between">
+          <h2 className="text-xl font-bold">Your Prompt</h2>
+        </div>
+
+        <Tabs defaultValue="submit">
+          <TabsList className="mb-4 grid w-full grid-cols-2">
+            <TabsTrigger value="submit">Submit Prompt</TabsTrigger>
+            <TabsTrigger value="test">Test Custom Case</TabsTrigger>
+          </TabsList>
+
+          <TabsContent value="submit" className="space-y-4">
+            {loading && (
+              <div className="flex items-center justify-center py-10">
+                <LoadingIndicator />
+              </div>
+            )}
+
+            {!loading && (
+              <div className="border-foreground/10 bg-foreground/10 text-foreground mx-auto flex max-w-3xl flex-col items-center justify-center space-y-6 rounded-lg border p-6 shadow-md">
+                <h3 className="text-2xl font-semibold">Your Last Attempt</h3>
+                <div className="border-foreground/5 bg-foreground/5 w-full rounded-lg border p-4 shadow-md">
+                  <div className="space-y-4">
+                    <div>
+                      <p className="text-foreground text-sm">
+                        <strong className="font-medium">Prompt: </strong>
+                        {input}
+                      </p>
+                    </div>
+                    <div>
+                      <p className="text-foreground text-sm">
+                        <strong className="font-medium">Score: </strong>
+                        {score}/10
+                      </p>
+                    </div>
+                  </div>
+                </div>
+              </div>
+            )}
path: apps/nova/src/app/[locale]/(dashboard)/(admin)/problems/[problemId]/prompt-form.tsx
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**UI displays current input instead of last attempt**

The UI section labeled "Your Last Attempt" shows the current input value (`{input}`), but this doesn't make sense because:
1. The input state is cleared after submission (`setInput('')` on line 65)
2. The messages state is designed to track conversation history but isn't displayed here

Consider displaying the last message from the messages state instead:

```diff
                <div className="border-foreground/5 bg-foreground/5 w-full rounded-lg border p-4 shadow-md">
                  <div className="space-y-4">
                    <div>
                      <p className="text-foreground text-sm">
                        <strong className="font-medium">Prompt: </strong>
-                       {input}
+                       {messages.length > 0 && messages[messages.length - 2]?.sender === 'user' 
+                         ? messages[messages.length - 2].text 
+                         : 'No attempts yet'}
                      </p>
                    </div>
```



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
            {!loading && (
              <div className="border-foreground/10 bg-foreground/10 text-foreground mx-auto flex max-w-3xl flex-col items-center justify-center space-y-6 rounded-lg border p-6 shadow-md">
                <h3 className="text-2xl font-semibold">Your Last Attempt</h3>
                <div className="border-foreground/5 bg-foreground/5 w-full rounded-lg border p-4 shadow-md">
                  <div className="space-y-4">
                    <div>
                      <p className="text-foreground text-sm">
                        <strong className="font-medium">Prompt: </strong>
                        {messages.length > 0 && messages[messages.length - 2]?.sender === 'user' 
                          ? messages[messages.length - 2].text 
                          : 'No attempts yet'}
                      </p>
                    </div>
                    <div>
                      <p className="text-foreground text-sm">
                        <strong className="font-medium">Score: </strong>
                        {score}/10
                      </p>
                    </div>
                  </div>
                </div>
              </div>
            )}
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-13T13:00:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,277 @@
+'use client';
+
+import { Button } from '@tuturuuu/ui/button';
+import { LoadingIndicator } from '@tuturuuu/ui/custom/loading-indicator';
+import { toast } from '@tuturuuu/ui/hooks/use-toast';
+import { Input } from '@tuturuuu/ui/input';
+import { Tabs, TabsContent, TabsList, TabsTrigger } from '@tuturuuu/ui/tabs';
+import { Textarea } from '@tuturuuu/ui/textarea';
+import { PlayCircle } from 'lucide-react';
+import React, { useState } from 'react';
+
+type TestResult = {
+  score: number;
+  feedback: string;
+  suggestions: string;
+};
+
+interface Problem {
+  id: string;
+  title: string;
+  description: string;
+  maxInputLength: number;
+  exampleInput: string;
+  exampleOutput: string;
+  testcases: string[];
+}
+
+export default function PromptForm({ problem }: { problem: Problem }) {
+  const [messages, setMessages] = useState<
+    { text: string; sender: 'user' | 'ai' }[]
+  >([]);
+  const [input, setInput] = useState('');
+  const [score, setScore] = useState(0);
+  const [customTestCase, setCustomTestCase] = useState('');
+  const [loading, setLoading] = useState(false);
+  const [testingCustom, setTestingCustom] = useState(false);
+  const [error, setError] = useState('');
+  const [testResult, setTestResult] = useState<TestResult | null>(null);
+
+  const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
+    if (e.key === 'Enter' && !e.shiftKey) {
+      e.preventDefault();
+      handleSend();
+    }
+  };
+
+  const handleSend = async () => {
+    if (!input.trim()) {
+      setError('Input cannot be empty.');
+      return;
+    }
+
+    if (input.length > problem.maxInputLength) {
+      setError('Input length exceeds the maximum allowed length.');
+      return;
+    }
+
+    if (loading) {
+      setError('Please wait for the previous attempt to complete.');
+      return;
+    }
+
+    const newUserMessage = { text: input, sender: 'user' as const };
+    setMessages((prev) => [...prev, newUserMessage]);
+    setInput('');
+    setLoading(true);
+    setError('');
+
+    try {
+      const response = await fetch('/api/ai/chat/google', {
+        method: 'POST',
+        headers: { 'Content-Type': 'application/json' },
+        body: JSON.stringify({
+          answer: input,
+          problemDescription: problem.description,
+          testCases: problem.testcases,
+          exampleInput: problem.exampleInput,
+          exampleOutput: problem.exampleOutput,
+        }),
+      });
+
+      if (!response.ok) {
+        const errorData = await response.json();
+        throw new Error(errorData.message || 'Failed to process submission');
+      }
+
+      const data = await response.json();
+      const output = data.response.feedback || '';
+      const score = data.response.score || 0;
+      setScore(score);
+
+      const newAiMessage = {
+        text: `Score: ${score}/10\n\n${output}`,
+        sender: 'ai' as const,
+      };
+      setMessages((prev) => [...prev, newAiMessage]);
+    } catch (error: any) {
+      console.error('Error:', error);
+      const errorMessage = {
+        text: `Error: ${error.message || 'Something went wrong'}`,
+        sender: 'ai' as const,
+      };
+      setMessages((prev) => [...prev, errorMessage]);
+      toast({
+        title: 'Error',
+        description: error.message || 'Failed to process submission',
+        variant: 'destructive',
+      });
+    } finally {
+      setLoading(false);
+    }
+  };
+
+  const handleTestCustomCase = async () => {
+    if (!customTestCase.trim()) {
+      setError('Custom test case cannot be empty.');
+      return;
+    }
+
+    setTestingCustom(true);
+    setError('');
+    setTestResult(null);
+
+    try {
+      const response = await fetch(
+        `/api/v1/problems/${problem.id}/custom-testcase`,
+        {
+          method: 'POST',
+          headers: { 'Content-Type': 'application/json' },
+          body: JSON.stringify({
+            prompt: input,
+            customTestCase,
+            problemDescription: problem.description,
+          }),
path: apps/nova/src/app/[locale]/(dashboard)/(admin)/problems/[problemId]/prompt-form.tsx
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Input validation missing when sending custom test case.**

When testing a custom case, the code sends the current `input` as the prompt, but doesn't validate that `input` is not empty. This could result in empty prompts being sent to the API.


```diff
  body: JSON.stringify({
-   prompt: input,
+   prompt: input.trim() ? input : 'No prompt provided',
    customTestCase,
    problemDescription: problem.description,
  }),
```

Better yet, consider adding validation before the API call:

```diff
  if (!customTestCase.trim()) {
    setError('Custom test case cannot be empty.');
    return;
  }
+ 
+ if (!input.trim()) {
+   setError('Prompt cannot be empty when testing a custom case.');
+   return;
+ }
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
          if (!customTestCase.trim()) {
            setError('Custom test case cannot be empty.');
            return;
          }
          
          if (!input.trim()) {
            setError('Prompt cannot be empty when testing a custom case.');
            return;
          }

          body: JSON.stringify({
            prompt: input.trim() ? input : 'No prompt provided',
            customTestCase,
            problemDescription: problem.description,
          }),
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-13T14:57:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,282 @@
+'use client';
+
+import { Button } from '@tuturuuu/ui/button';
+import { LoadingIndicator } from '@tuturuuu/ui/custom/loading-indicator';
+import { toast } from '@tuturuuu/ui/hooks/use-toast';
+import { Input } from '@tuturuuu/ui/input';
+import { Tabs, TabsContent, TabsList, TabsTrigger } from '@tuturuuu/ui/tabs';
+import { Textarea } from '@tuturuuu/ui/textarea';
+import { PlayCircle } from 'lucide-react';
+import React, { useState } from 'react';
+
+type TestResult = {
+  score: number;
+  feedback: string;
+  suggestions: string;
+};
+
+interface Problem {
+  id: string;
+  title: string;
+  description: string;
+  maxInputLength: number;
+  exampleInput: string;
+  exampleOutput: string;
+  testcases: string[];
+}
+
+export default function PromptForm({ problem }: { problem: Problem }) {
+  const [messages, setMessages] = useState<
+    { text: string; sender: 'user' | 'ai' }[]
+  >([]);
+  const [input, setInput] = useState('');
+  const [score, setScore] = useState(0);
+  const [customTestCase, setCustomTestCase] = useState('');
+  const [loading, setLoading] = useState(false);
+  const [testingCustom, setTestingCustom] = useState(false);
+  const [error, setError] = useState('');
+  const [testResult, setTestResult] = useState<TestResult | null>(null);
+
+  const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
+    if (e.key === 'Enter' && !e.shiftKey) {
+      e.preventDefault();
+      handleSend();
+    }
+  };
+
+  const handleSend = async () => {
+    if (!input.trim()) {
+      setError('Input cannot be empty.');
+      return;
+    }
+
+    if (input.length > problem.maxInputLength) {
+      setError('Input length exceeds the maximum allowed length.');
+      return;
+    }
+
+    if (loading) {
+      setError('Please wait for the previous attempt to complete.');
+      return;
+    }
+
+    const newUserMessage = { text: input, sender: 'user' as const };
+    setMessages((prev) => [...prev, newUserMessage]);
+    setInput('');
+    setLoading(true);
+    setError('');
+
+    try {
+      const response = await fetch('/api/ai/chat/google', {
+        method: 'POST',
+        headers: { 'Content-Type': 'application/json' },
+        body: JSON.stringify({
+          answer: input,
+          problemDescription: problem.description,
+          testCases: problem.testcases,
+          exampleInput: problem.exampleInput,
+          exampleOutput: problem.exampleOutput,
+        }),
+      });
+
+      if (!response.ok) {
+        const errorData = await response.json();
+        throw new Error(errorData.message || 'Failed to process submission');
+      }
+
+      const data = await response.json();
+      const output = data.response.feedback || '';
+      const score = data.response.score || 0;
+      setScore(score);
+
+      const newAiMessage = {
+        text: `Score: ${score}/10\n\n${output}`,
+        sender: 'ai' as const,
+      };
+      setMessages((prev) => [...prev, newAiMessage]);
+    } catch (error: any) {
+      console.error('Error:', error);
+      const errorMessage = {
+        text: `Error: ${error.message || 'Something went wrong'}`,
+        sender: 'ai' as const,
+      };
+      setMessages((prev) => [...prev, errorMessage]);
+      toast({
+        title: 'Error',
+        description: error.message || 'Failed to process submission',
+        variant: 'destructive',
+      });
+    } finally {
+      setLoading(false);
+    }
+  };
+
+  const handleTestCustomCase = async () => {
+    if (!input.trim()) {
+      setError('Prompt cannot be empty when testing a custom case.');
+      return;
+    }
+
+    if (!customTestCase.trim()) {
+      setError('Custom test case cannot be empty.');
+      return;
+    }
+
+    setTestingCustom(true);
+    setError('');
+    setTestResult(null);
+
+    try {
+      const response = await fetch(
+        `/api/v1/problems/${problem.id}/custom-testcase`,
+        {
+          method: 'POST',
+          headers: { 'Content-Type': 'application/json' },
+          body: JSON.stringify({
+            prompt: input,
+            customTestCase,
+            problemDescription: problem.description,
+          }),
+        }
+      );
+
+      if (!response.ok) {
+        const errorData = await response.json();
+        throw new Error(errorData.message || 'Failed to test prompt');
+      }
+
+      const data = await response.json();
+      setTestResult(data.response);
path: apps/nova/src/app/[locale]/(dashboard)/(admin)/problems/[problemId]/prompt-form.tsx
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Add validation for custom test case API response**

Similar to the main prompt submission flow, there's no validation of the API response structure before using it, which could lead to runtime errors.

```diff
  const data = await response.json();
+  if (!data.response) {
+    throw new Error('Invalid response format from API');
+  }
  setTestResult(data.response);
```



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
      const data = await response.json();
      if (!data.response) {
        throw new Error('Invalid response format from API');
      }
      setTestResult(data.response);
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-13T15:14:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,282 @@
+'use client';
+
+import { Button } from '@tuturuuu/ui/button';
+import { LoadingIndicator } from '@tuturuuu/ui/custom/loading-indicator';
+import { toast } from '@tuturuuu/ui/hooks/use-toast';
+import { Input } from '@tuturuuu/ui/input';
+import { Tabs, TabsContent, TabsList, TabsTrigger } from '@tuturuuu/ui/tabs';
+import { Textarea } from '@tuturuuu/ui/textarea';
+import { PlayCircle } from 'lucide-react';
+import React, { useState } from 'react';
+
+type TestResult = {
+  score: number;
+  feedback: string;
+  suggestions: string;
+};
+
+interface Problem {
+  id: string;
+  title: string;
+  description: string;
+  maxInputLength: number;
+  exampleInput: string;
+  exampleOutput: string;
+  testcases: string[];
+}
+
+export default function PromptForm({ problem }: { problem: Problem }) {
+  const [messages, setMessages] = useState<
+    { text: string; sender: 'user' | 'ai' }[]
+  >([]);
+  const [input, setInput] = useState('');
+  const [score, setScore] = useState(0);
+  const [customTestCase, setCustomTestCase] = useState('');
+  const [loading, setLoading] = useState(false);
+  const [testingCustom, setTestingCustom] = useState(false);
+  const [error, setError] = useState('');
+  const [testResult, setTestResult] = useState<TestResult | null>(null);
+
+  const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
+    if (e.key === 'Enter' && !e.shiftKey) {
+      e.preventDefault();
+      handleSend();
+    }
+  };
+
+  const handleSend = async () => {
+    if (!input.trim()) {
+      setError('Input cannot be empty.');
+      return;
+    }
+
+    if (input.length > problem.maxInputLength) {
+      setError('Input length exceeds the maximum allowed length.');
+      return;
+    }
+
+    if (loading) {
+      setError('Please wait for the previous attempt to complete.');
+      return;
+    }
+
+    const newUserMessage = { text: input, sender: 'user' as const };
+    setMessages((prev) => [...prev, newUserMessage]);
+    setInput('');
+    setLoading(true);
+    setError('');
+
+    try {
+      const response = await fetch('/api/ai/chat/google', {
+        method: 'POST',
+        headers: { 'Content-Type': 'application/json' },
+        body: JSON.stringify({
+          answer: input,
+          problemDescription: problem.description,
+          testCases: problem.testcases,
+          exampleInput: problem.exampleInput,
+          exampleOutput: problem.exampleOutput,
+        }),
+      });
+
+      if (!response.ok) {
+        const errorData = await response.json();
+        throw new Error(errorData.message || 'Failed to process submission');
+      }
+
+      const data = await response.json();
+      const output = data.response.feedback || '';
+      const score = data.response.score || 0;
+      setScore(score);
+
+      const newAiMessage = {
+        text: `Score: ${score}/10\n\n${output}`,
+        sender: 'ai' as const,
+      };
+      setMessages((prev) => [...prev, newAiMessage]);
+    } catch (error: any) {
+      console.error('Error:', error);
+      const errorMessage = {
+        text: `Error: ${error.message || 'Something went wrong'}`,
+        sender: 'ai' as const,
+      };
+      setMessages((prev) => [...prev, errorMessage]);
+      toast({
+        title: 'Error',
+        description: error.message || 'Failed to process submission',
+        variant: 'destructive',
+      });
+    } finally {
+      setLoading(false);
+    }
+  };
+
+  const handleTestCustomCase = async () => {
+    if (!input.trim()) {
+      setError('Prompt cannot be empty when testing a custom case.');
+      return;
+    }
+
+    if (!customTestCase.trim()) {
+      setError('Custom test case cannot be empty.');
+      return;
+    }
+
+    setTestingCustom(true);
+    setError('');
+    setTestResult(null);
+
+    try {
+      const response = await fetch(
+        `/api/v1/problems/${problem.id}/custom-testcase`,
+        {
+          method: 'POST',
+          headers: { 'Content-Type': 'application/json' },
+          body: JSON.stringify({
+            prompt: input,
+            customTestCase,
+            problemDescription: problem.description,
+          }),
+        }
+      );
+
+      if (!response.ok) {
+        const errorData = await response.json();
+        throw new Error(errorData.message || 'Failed to test prompt');
+      }
+
+      const data = await response.json();
+      setTestResult(data.response);
+    } catch (error: any) {
+      console.error('Error testing prompt:', error);
+      toast({
+        title: 'Error Testing Prompt',
+        description: error.message || 'Something went wrong',
+        variant: 'destructive',
+      });
+    } finally {
+      setTestingCustom(false);
+    }
+  };
+
+  return (
+    <div className="flex h-full flex-col">
+      <div className="flex-1 overflow-y-auto p-4">
+        <div className="mb-4 flex items-center justify-between">
+          <h2 className="text-xl font-bold">Your Prompt</h2>
+        </div>
+
+        <Tabs defaultValue="submit">
+          <TabsList className="mb-4 grid w-full grid-cols-2">
+            <TabsTrigger value="submit">Submit Prompt</TabsTrigger>
+            <TabsTrigger value="test">Test Custom Case</TabsTrigger>
+          </TabsList>
+
+          <TabsContent value="submit" className="space-y-4">
+            {loading && (
+              <div className="flex items-center justify-center py-10">
+                <LoadingIndicator />
+              </div>
+            )}
+
+            {!loading && (
+              <div className="border-foreground/10 bg-foreground/10 text-foreground mx-auto flex max-w-3xl flex-col items-center justify-center space-y-6 rounded-lg border p-6 shadow-md">
+                <h3 className="text-2xl font-semibold">Your Last Attempt</h3>
+                <div className="border-foreground/5 bg-foreground/5 w-full rounded-lg border p-4 shadow-md">
+                  <div className="space-y-4">
+                    <div>
+                      <p className="text-foreground text-sm">
+                        <strong className="font-medium">Prompt: </strong>
+                        {messages.length > 0 &&
+                        messages[messages.length - 2]?.sender === 'user'
+                          ? messages[messages.length - 2]?.text
+                          : 'No attempts yet'}
+                      </p>
+                    </div>
+                    <div>
+                      <p className="text-foreground text-sm">
+                        <strong className="font-medium">Score: </strong>
+                        {score}/10
+                      </p>
+                    </div>
+                  </div>
+                </div>
+              </div>
+            )}
+          </TabsContent>
+
+          <TabsContent value="test" className="space-y-4">
+            <div className="border-foreground/10 bg-foreground/10 space-y-4 rounded-lg border p-6">
+              <div>
+                <h3 className="mb-2 text-lg font-medium">Custom Test Case</h3>
+                <p className="text-muted-foreground mb-3 text-sm">
+                  Enter a custom test case to see how your prompt would perform
+                  on it. This won't count against your submission attempts.
+                </p>
+                <Textarea
+                  value={customTestCase}
+                  onChange={(e) => setCustomTestCase(e.target.value)}
+                  placeholder="Enter your custom test case here..."
+                  className="min-h-[120px]"
+                />
+                <Button onClick={handleTestCustomCase} className="mt-3 gap-2">
+                  <PlayCircle className="h-4 w-4" />
+                  {testingCustom ? 'Testing...' : 'Test This Case'}
+                </Button>
+              </div>
+
+              {testingCustom && (
+                <div className="flex items-center justify-center py-6">
+                  <LoadingIndicator />
+                </div>
+              )}
+
+              {testResult && (
+                <div className="border-foreground/10 bg-foreground/5 mt-4 rounded-lg border p-4">
+                  <h4 className="mb-2 text-lg font-medium">Test Results</h4>
+                  <div className="space-y-3">
+                    <div>
+                      <span className="font-semibold">Score: </span>
+                      <span
+                        className={
+                          testResult.score >= 8
+                            ? 'text-dynamic-light-green'
+                            : testResult.score >= 5
+                              ? 'text-dynamic-light-yellow'
+                              : 'text-dynamic-light-red'
+                        }
+                      >
+                        {testResult.score}/10
+                      </span>
+                    </div>
+                    <div>
+                      <span className="font-semibold">Feedback: </span>
+                      <p className="mt-1 whitespace-pre-wrap text-sm">
+                        {testResult.feedback}
+                      </p>
path: apps/nova/src/app/[locale]/(dashboard)/(admin)/problems/[problemId]/prompt-form.tsx
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Consider sanitizing feedback content before rendering**

The test result feedback is rendered directly as provided by the API. If the API response contains any HTML or malicious content, it could lead to XSS vulnerabilities.

```diff
-<p className="mt-1 whitespace-pre-wrap text-sm">
-  {testResult.feedback}
-</p>
+<p className="mt-1 whitespace-pre-wrap text-sm">
+  {testResult.feedback ? testResult.feedback.replace(/(<([^>]+)>)/gi, "") : ""}
+</p>
```

Alternatively, consider using a library like DOMPurify if you need to preserve some HTML formatting.



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
                      <p className="mt-1 whitespace-pre-wrap text-sm">
                        {testResult.feedback ? testResult.feedback.replace(/(<([^>]+)>)/gi, "") : ""}
                      </p>
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-13T15:14:26Z
author_association: CONTRIBUTOR

PR comments:
user: login: graphite-app[bot]
created_at: 2025-03-13T12:55:08Z
body: ## How to use the Graphite Merge Queue

Add either label to this PR to merge it via the merge queue:
- <kbd>merge-queue</kbd> - adds this PR to the back of the merge queue
- <kbd>hotfix</kbd> - for urgent hot fixes, skip the queue and merge this PR next

You must have a Graphite account in order to use the merge queue. Sign up using [this link](https://app.graphite.dev/invite/github/tutur3u?ref=merge-queue-instructions-comment&prId=5988255895).

<h2></h2>
<sub>

An organization admin has enabled the [Graphite Merge Queue](https://graphite.dev/docs/graphite-merge-queue) in this repository.

</sub>

<sub>

Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue.

</sub>
author_association: NONE
user: login: coderabbitai[bot]
created_at: 2025-03-13T12:55:10Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough
This pull request refactors import paths, introduces new React components, and updates existing components in the admin problems dashboard and challenges. The changes add components for problem display, prompt input, and test case visualization while enhancing error handling in prompt submission flows. Additionally, database type declarations are updated to allow optional properties, and a SQL migration removes the scoring criteria column from the nova_problems table.

## Changes

| File(s) | Change Summary |
|---------|----------------|
| `apps/nova/src/app/[locale]/(dashboard)/(admin)/problems/[problemId]/page.tsx` | Simplified import paths for `ProblemComponent`, `PromptComponent`, and `TestCaseComponent` by converting deep relative paths to local ones. |
| `apps/nova/src/app/[locale]/(dashboard)/(admin)/problems/[problemId]/problem-component.tsx`<br>`apps/nova/src/app/[locale]/(dashboard)/(admin)/problems/[problemId]/prompt-component.tsx`<br>`apps/nova/src/app/[locale]/(dashboard)/(admin)/problems/[problemId]/prompt-form.tsx`<br>`apps/nova/src/app/[locale]/(dashboard)/(admin)/problems/[problemId]/test-case-component.tsx` | Added new React components for displaying problems, handling prompt submissions, and visualizing test cases. |
| `apps/nova/src/app/[locale]/(dashboard)/challenges/[challengeId]/prompt-form.tsx` | Enhanced error handling in prompt submission and custom test case functions with improved conditional checks and error messaging. |
| `packages/types/src/supabase.ts`<br>`apps/db/supabase/migrations/20250313124744_remove_scoring_criteria.sql` | Updated database type definitions to make certain properties optional; removed the `scoring_criteria` column from the `nova_problems` table via migration. |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant U as User
    participant PF as PromptForm
    participant API as API Server
    U->>PF: Enters prompt & presses Enter
    PF->>API: Sends POST request with prompt & problem data
    API-->>PF: Returns response (feedback, score)
    PF->>U: Displays result and updates UI
```

```mermaid
sequenceDiagram
    participant U as User
    participant PF as PromptForm
    participant API as API Server
    U->>PF: Initiates custom test case input
    PF->>API: Sends POST request with custom test data
    API-->>PF: Returns test result
    PF->>U: Renders test case outcome
```

## Poem
> I'm a bunny hopping through the code,  
> Found new components down the road.  
> Imports now so neat and clear,  
> Data flow and tests appear.  
> With each line, my whiskers twitch in glee,  
> Carrots and code—what a spree!  
> Hop on, dear devs, and code with me!

<!-- walkthrough_end -->

<!-- announcements_start -->

> [!TIP]
> <details>
> <summary>⚡🧪 Multi-step agentic review comment chat (experimental)</summary>
> 
> - We're introducing multi-step agentic chat in review comments. This experimental feature enhances review discussions with the CodeRabbit agentic chat by enabling advanced interactions, including the ability to create pull requests directly from comments.
> 						- To enable this feature, set early_access to true under in the settings.
> 
> </details>

<!-- announcements_end -->

---

<details>
<summary>📜 Recent review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 7e714e44bcfb2964ac9b4bc2dd34d71b1c4b890c and 2e3a9b02a4f59d51853367b6dd6f4a4edea8ba33.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `apps/db/supabase/migrations/20250313124744_remove_scoring_criteria.sql` (1 hunks)

</details>

<details>
<summary>✅ Files skipped from review due to trivial changes (1)</summary>

* apps/db/supabase/migrations/20250313124744_remove_scoring_criteria.sql

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (5)</summary>

* GitHub Check: Analyze (javascript-typescript)
* GitHub Check: Deploy-Preview
* GitHub Check: Deploy-Preview
* GitHub Check: Deploy-Preview
* GitHub Check: Deploy-Preview

</details>

</details>
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- tips_start -->

---



<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=tutur3u/platform&utm_content=2285):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: CONTRIBUTOR