diff --git a/backend/ai_service.py b/backend/ai_service.py index 71aaf7e..eedd460 100644 --- a/backend/ai_service.py +++ b/backend/ai_service.py @@ -95,9 +95,21 @@ def get_explain_prompt(code: str, lang: str) -> str: [Identify the primary DSA patterns, algorithms, or data structures used in this code, e.g. Sliding Window, DFS, Hash Table, Stack, Binary Search. Provide 1-2 sentences explaining why this pattern fits the problem.] DSA_PATTERN_END -LEETCODE_PROBLEMS_START -[Suggest 3 related LeetCode problems that practice this pattern. For each, output: Title | Link (use standard https://leetcode.com/problems/... urls). Use newlines to separate.] -LEETCODE_PROBLEMS_END +PLATFORM_PROBLEMS_START +[Suggest 4-5 related practice problems for this pattern. CRITICAL ACCURACY MANDATE: You MUST include problem links ONLY for exact problem matches on that platform. Do NOT output a link if the platform does not feature that exact problem. Do NOT output generic or mismatched problem links. Mix of platforms (GeeksforGeeks, HackerRank, Codeforces, Coding Ninjas, and LeetCode). For each problem, output strictly on a new line: Platform | Title | Link (use actual valid problem URLs like https://www.geeksforgeeks.org/..., https://www.hackerrank.com/..., https://codeforces.com/..., https://leetcode.com/...).] +PLATFORM_PROBLEMS_END + +OPTIMIZED_CODE_START +[Provide the absolute most optimal version of the analyzed code in terms of time and space complexity. Do NOT use markdown code fences inside this tag. CRITICAL RULE: If the input code is ALREADY optimal in both time and space complexity, write strictly `ALREADY_OPTIMIZED` inside this block.] +OPTIMIZED_CODE_END + +OPTIMIZED_TIME_COMPLEXITY_START +[State the exact Big-O time complexity of the optimized code, e.g. O(N) linear time.] +OPTIMIZED_TIME_COMPLEXITY_END + +OPTIMIZED_SPACE_COMPLEXITY_START +[State the exact Big-O space complexity of the optimized code, e.g. O(1) constant space.] +OPTIMIZED_SPACE_COMPLEXITY_END PRACTICE_EXERCISES_START [Provide 3 practice questions (Beginner, Intermediate, Advanced) that build on this concept. For each, output the difficulty, aim, and a brief sample input/output. Use clean Markdown structure.] @@ -150,15 +162,18 @@ def get_dry_run_prompt(code: str, lang: str, test_case: str = None) -> str: CRITICAL VISUALIZATION RULES: 1. If the code uses, traverses, or modifies any array, vector, list, or string (like search, sort, two-sum, reverse, sub-array, etc.), you MUST set "ds_type": "array" and "ds_data" to the current state of that array (e.g. [2, 7, 11, 15] or [0, 1, 1, 0, 1]). You MUST track the pointer indices (like i, j, k, low, mid, high, left, right, slow, fast) inside the "variables" object (e.g. {{"i": 1, "target": 9}}) so the frontend can float pointer arrows above/below the array boxes. 2. If the code uses a map, dictionary, or set (like std::map, unordered_map, set, dict), set "ds_type": "map" or "ds_type": "set". Set "ds_data" to the key-value dictionary (e.g. {{ "2": 0, "7": 1 }}) or set element array (e.g. [2, 7]). -3. End the array with a step representing the final return/output value.] +3. Trace boundary cases and out-of-bound pointer index values (e.g. low > high, i = -1, i = N) inside the "variables" object when loop termination or out-of-bound checks occur so boundary conditions can be visually demonstrated. +4. End the array with a step representing the final return/output value.] DRY_RUN_END FLOWCHART_START -[Generate a beautiful, logical Mermaid.js control flow diagram (graph TD) showing the execution flow of the code. -Use clean shapes and semantic nodes: -- Start/End steps: Use rounded brackets with double-quoted labels like `A("Start"):::startEnd` or `Z("End"):::startEnd`. -- Conditionals/Decisions: Use brace nodes with double-quoted labels like `B{{"Is condition met?"}}:::decision` (write this with double curly braces) and label paths clearly using `-- Yes -->` or `-- No -->`. -- Standard statements/process: Use square brackets with double-quoted labels like `C["Process/Action"]:::default`. +[Generate a clean, valid Mermaid.js control flow diagram (graph TD) showing the execution flow of the code. +CRITICAL MERMAID SYNTAX RULES: +1. ALWAYS wrap ALL node label texts inside double quotes, e.g. `A["Start"]:::startEnd` or `B["Initialize maxi = 0"]:::default`. +2. NEVER put spaces before `:::` (write `:::default`, NOT ` ::: default`). +3. DO NOT use semicolons `;` at the end of lines. +4. If using subgraphs, ALWAYS wrap the subgraph name in double quotes, e.g. `subgraph "findi(root, &maxi)"`. +5. Label decision paths strictly using `-->|"Yes"|`. Include these class definitions in the flowchart output to apply our custom color theme: classDef default fill:#122858,stroke:#3ecfb2,stroke-width:1.5px,color:#f3f5ed; @@ -168,7 +183,12 @@ def get_dry_run_prompt(code: str, lang: str, test_case: str = None) -> str: FLOWCHART_END RECURSION_TREE_START -[If the code uses recursion, generate a beautiful, logical Mermaid.js graph TD diagram representing the recursion tree of the execution with the actual arguments and values. Use custom color classes. If the code does not use recursion, write RECURSION_NONE. +[If the code uses recursion, generate a clean, valid Mermaid.js graph TD diagram representing the recursion tree of the execution with the actual arguments and values. Use custom color classes. If the code does not use recursion, write RECURSION_NONE. +CRITICAL MERMAID SYNTAX RULES: +1. ALWAYS wrap ALL node label texts in double quotes, e.g. `A["solve(5)"]:::default`. +2. NEVER put spaces before `:::`. +3. If using subgraphs, ALWAYS wrap the title in double quotes, e.g. `subgraph "solve(n)"`. + Include these class definitions in the flowchart output to apply our custom color theme: classDef default fill:#122858,stroke:#3ecfb2,stroke-width:1.5px,color:#f3f5ed; classDef decision fill:#0f2348,stroke:#c9a84c,stroke-width:1.5px,color:#c9a84c; @@ -196,13 +216,13 @@ async def analyze_code(code: str, language: str, test_case: str = None, api_key: prompt2 = get_dry_run_prompt(code, language, test_case) async def call_gemini(prompt: str): - for attempt in range(2): + for attempt in range(3): try: response = await asyncio.to_thread(client.models.generate_content, model=MODEL_NAME, contents=prompt) return response.text except Exception as e: - if "429" in str(e) and attempt == 0: - await asyncio.sleep(5) + if ("429" in str(e) or "RESOURCE_EXHAUSTED" in str(e)) and attempt < 2: + await asyncio.sleep(4 * (attempt + 1)) else: raise e @@ -220,7 +240,10 @@ async def call_gemini(prompt: str): "space_complexity": _extract(r1, "SPACE_COMPLEXITY_START", "SPACE_COMPLEXITY_END"), "suggestions": _extract(r1, "SUGGESTIONS_START", "SUGGESTIONS_END"), "dsa_pattern": _extract(r1, "DSA_PATTERN_START", "DSA_PATTERN_END"), - "leetcode_problems": _extract(r1, "LEETCODE_PROBLEMS_START", "LEETCODE_PROBLEMS_END"), + "platform_problems": _extract(r1, "PLATFORM_PROBLEMS_START", "PLATFORM_PROBLEMS_END"), + "optimized_code": _extract(r1, "OPTIMIZED_CODE_START", "OPTIMIZED_CODE_END"), + "optimized_time_complexity": _extract(r1, "OPTIMIZED_TIME_COMPLEXITY_START", "OPTIMIZED_TIME_COMPLEXITY_END"), + "optimized_space_complexity": _extract(r1, "OPTIMIZED_SPACE_COMPLEXITY_START", "OPTIMIZED_SPACE_COMPLEXITY_END"), "practice_exercises":_extract(r1, "PRACTICE_EXERCISES_START", "PRACTICE_EXERCISES_END"), "interview_questions":_extract(r1, "INTERVIEW_QUESTIONS_START", "INTERVIEW_QUESTIONS_END"), "algorithm": _extract(r1, "ALGORITHM_START", "ALGORITHM_END"), diff --git a/backend/test_features.py b/backend/test_features.py index 471d88b..a4a0c22 100644 --- a/backend/test_features.py +++ b/backend/test_features.py @@ -35,7 +35,10 @@ async def test_all(): print(f" Time complexity: {res.get('time_complexity')}") print("\n--- NEW LEARNING PLATFORM FIELDS ---") print(f" DSA Pattern: {res.get('dsa_pattern')}") - print(f" LeetCode Problems: {res.get('leetcode_problems')}") + print(f" Platform Problems: {res.get('platform_problems')}") + print(f" Optimized Code: {res.get('optimized_code')}") + print(f" Optimized Time Complexity: {res.get('optimized_time_complexity')}") + print(f" Optimized Space Complexity: {res.get('optimized_space_complexity')}") print(f" Practice Exercises: {res.get('practice_exercises')}") print(f" Interview Questions: {res.get('interview_questions')}") print(f" Algorithm: {res.get('algorithm')}") diff --git a/backend/test_mock_ai.py b/backend/test_mock_ai.py index 518683e..565c097 100644 --- a/backend/test_mock_ai.py +++ b/backend/test_mock_ai.py @@ -42,9 +42,23 @@ Simple Output Pattern DSA_PATTERN_END -LEETCODE_PROBLEMS_START -Hello World | https://leetcode.com/problems/hello-world -LEETCODE_PROBLEMS_END +PLATFORM_PROBLEMS_START +LeetCode | Hello World | https://leetcode.com/problems/hello-world +GeeksforGeeks | Print Hello World | https://www.geeksforgeeks.org/print-hello-world +HackerRank | Say Hello World | https://www.hackerrank.com/challenges/say-hello-world +PLATFORM_PROBLEMS_END + +OPTIMIZED_CODE_START +print('hello') +OPTIMIZED_CODE_END + +OPTIMIZED_TIME_COMPLEXITY_START +O(1) constant time +OPTIMIZED_TIME_COMPLEXITY_END + +OPTIMIZED_SPACE_COMPLEXITY_START +O(1) constant space +OPTIMIZED_SPACE_COMPLEXITY_END PRACTICE_EXERCISES_START Q1. Print something else. @@ -106,7 +120,7 @@ async def run_mock_test(): # Side effect function to return different mock responses call_count = 0 - def mock_generate_content(model, contents): + def mock_generate_content(model, contents, **kwargs): nonlocal call_count call_count += 1 if call_count == 1: @@ -124,6 +138,10 @@ def mock_generate_content(model, contents): assert result["time_complexity"] == "O(1) constant time", f"Got {result['time_complexity']}" assert result["recursion_tree"] == "RECURSION_NONE", f"Got {result['recursion_tree']}" assert "Prints hello" in result["dry_run"], f"Got {result['dry_run']}" + assert "GeeksforGeeks" in result["platform_problems"], f"Got {result['platform_problems']}" + assert result["optimized_code"] == "print('hello')", f"Got {result['optimized_code']}" + assert result["optimized_time_complexity"] == "O(1) constant time", f"Got {result['optimized_time_complexity']}" + assert result["optimized_space_complexity"] == "O(1) constant space", f"Got {result['optimized_space_complexity']}" print("[SUCCESS] Unit test passed! Parallel prompt queries executed and parsed correctly.") diff --git a/frontend/index.html b/frontend/index.html index c9919ff..4a17465 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -49,6 +49,167 @@ --fm: 'JetBrains Mono', monospace; } + html[data-theme="light"] { + --bg: #ffffff; + --bg2: #f6f8fa; + --bg3: #eaeea2; + --bg3: #f0f3f6; + --bg4: #d0d7de; + --bgi: #f8fafc; + --tx1: #1f2328; + --tx2: #57606a; + --tx3: #8c959f; + --gold: #9a6700; + --gold2: rgba(154, 103, 0, .14); + --gold3: rgba(154, 103, 0, .06); + --blue: #0969da; + --blue2: rgba(9, 105, 218, .13); + --teal: #1a7f37; + --teal2: rgba(26, 127, 55, .13); + --red: #cf222e; + --red2: rgba(207, 34, 46, .13); + --purp: #8250df; + --purp2: rgba(130, 80, 223, .10); + --coral: #bc4c00; + --amber: #b08800; + --b1: rgba(31, 35, 40, .12); + --b2: rgba(31, 35, 40, .20); + } + + html[data-theme="dimmed"] { + --bg: #22272e; + --bg2: #2d333b; + --bg3: #373e47; + --bg4: #444c56; + --bgi: #1c2128; + --tx1: #adbac7; + --tx2: #768390; + --tx3: #636e7b; + --gold: #c69026; + --gold2: rgba(198, 144, 38, .16); + --gold3: rgba(198, 144, 38, .08); + --blue: #539bf5; + --blue2: rgba(83, 155, 245, .15); + --teal: #34d058; + --teal2: rgba(52, 208, 88, .15); + --red: #f47067; + --red2: rgba(244, 112, 103, .15); + --purp: #babbf9; + --purp2: rgba(186, 187, 249, .12); + --coral: #f69d50; + --amber: #daaa3f; + --b1: rgba(205, 217, 229, .10); + --b2: rgba(205, 217, 229, .18); + } + + html[data-theme="tritanopia"] { + --bg: #041724; + --bg2: #092233; + --bg3: #103047; + --bg4: #19415c; + --bgi: #020f18; + --tx1: #e0f2fe; + --tx2: #7dd3fc; + --tx3: #38bdf8; + --gold: #06b6d4; + --gold2: rgba(6, 182, 212, .18); + --gold3: rgba(6, 182, 212, .08); + --blue: #38bdf8; + --blue2: rgba(56, 189, 248, .18); + --teal: #14b8a6; + --teal2: rgba(20, 184, 166, .18); + --red: #f43f5e; + --red2: rgba(244, 63, 94, .18); + --purp: #c084fc; + --purp2: rgba(192, 132, 252, .15); + --coral: #fb7185; + --amber: #06b6d4; + --b1: rgba(56, 189, 248, .15); + --b2: rgba(56, 189, 248, .25); + } + + html[data-theme="deuteranopia"] { + --bg: #120e24; + --bg2: #1c1736; + --bg3: #28214a; + --bg4: #352c61; + --bgi: #0a0717; + --tx1: #f3e8ff; + --tx2: #c084fc; + --tx3: #a855f7; + --gold: #f59e0b; + --gold2: rgba(245, 158, 11, .18); + --gold3: rgba(245, 158, 11, .08); + --blue: #3b82f6; + --blue2: rgba(59, 130, 246, .18); + --teal: #60a5fa; + --teal2: rgba(96, 165, 250, .18); + --red: #f97316; + --red2: rgba(249, 115, 22, .18); + --purp: #d946ef; + --purp2: rgba(217, 70, 239, .15); + --coral: #f97316; + --amber: #f59e0b; + --b1: rgba(192, 132, 252, .15); + --b2: rgba(192, 132, 252, .25); + } + + html[data-theme="protanopia"] { + --bg: #1c1917; + --bg2: #292524; + --bg3: #3f3e3a; + --bg4: #52524e; + --bgi: #12100e; + --tx1: #fef08a; + --tx2: #fde047; + --tx3: #ca8a04; + --gold: #eab308; + --gold2: rgba(234, 179, 8, .18); + --gold3: rgba(234, 179, 8, .08); + --blue: #0284c7; + --blue2: rgba(2, 132, 199, .18); + --teal: #06b6d4; + --teal2: rgba(6, 182, 212, .18); + --red: #d97706; + --red2: rgba(217, 119, 6, .18); + --purp: #8b5cf6; + --purp2: rgba(139, 92, 246, .15); + --coral: #eab308; + --amber: #facc15; + --b1: rgba(234, 179, 8, .15); + --b2: rgba(234, 179, 8, .25); + } + + html[data-theme="high-contrast"] { + --bg: #000000; + --bg2: #0d0d0d; + --bg3: #1a1a1a; + --bg4: #262626; + --bgi: #000000; + --tx1: #ffffff; + --tx2: #e5e5e5; + --tx3: #a3a3a3; + --gold: #ffff00; + --gold2: rgba(255, 255, 0, .25); + --gold3: rgba(255, 255, 0, .12); + --blue: #00ffff; + --blue2: rgba(0, 255, 255, .25); + --teal: #00ff00; + --teal2: rgba(0, 255, 0, .25); + --red: #ff0055; + --red2: rgba(255, 0, 85, .25); + --purp: #ff00ff; + --purp2: rgba(255, 0, 255, .22); + --coral: #ffaa00; + --amber: #ffff00; + --b1: rgba(255, 255, 255, .28); + --b2: rgba(255, 255, 255, .45); + } + + html[data-theme="light"] body::before { + opacity: 0.15; + } + *, *::before, *::after { @@ -65,9 +226,12 @@ background: var(--bg); color: var(--tx1); font-family: var(--fb); + height: 100vh; min-height: 100vh; + display: flex; + flex-direction: column; -webkit-font-smoothing: antialiased; - overflow-x: hidden + overflow: hidden } .hidden { @@ -85,11 +249,11 @@ } .hdr { - position: sticky; - top: 0; + position: relative; z-index: 100; height: 56px; - background: rgba(10, 25, 49, .95); + flex-shrink: 0; + background: var(--bg2); backdrop-filter: blur(20px); border-bottom: 1px solid var(--b1) } @@ -218,14 +382,17 @@ .layout { max-width: 1480px; + width: 100%; margin: 0 auto; - padding: 18px 22px; + padding: 14px 22px; display: flex; flex-direction: row; - min-height: calc(100vh - 56px); + flex: 1 1 0%; + height: calc(100vh - 56px); position: relative; z-index: 1; gap: 0; + overflow: hidden; } .resizer { @@ -279,7 +446,8 @@ gap: 12px; box-shadow: 0 8px 32px rgba(0, 0, 0, .4); position: relative; - overflow: hidden + height: 100%; + overflow-y: auto; } .panel::before { @@ -292,9 +460,20 @@ background: linear-gradient(90deg, transparent, rgba(201, 168, 76, .22), transparent) } + .panel::-webkit-scrollbar, + .pr::-webkit-scrollbar { + width: 4px + } + + .panel::-webkit-scrollbar-thumb, + .pr::-webkit-scrollbar-thumb { + background: var(--b2); + border-radius: 2px + } + .pr { overflow-y: auto; - max-height: calc(100vh - 56px - 36px) + height: 100%; } .pr::-webkit-scrollbar { @@ -2090,6 +2269,100 @@ opacity: 1; transform: translateY(0); } + + /* Glassmorphic Multi-Platform Dropdown & Optimized Code Container */ + .dsa-hdr-wrap { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + margin-bottom: 10px; + } + + .dsa-platform-sel-wr { + position: relative; + display: inline-flex; + align-items: center; + } + + .dsa-platform-select { + background: var(--bg3); + border: 1px solid var(--b2); + border-radius: var(--r1); + color: var(--tx1); + font-family: var(--fui); + font-size: 0.68rem; + font-weight: 600; + padding: 5px 26px 5px 10px; + outline: none; + cursor: pointer; + appearance: none; + -webkit-appearance: none; + backdrop-filter: blur(8px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); + transition: all 0.25s ease; + } + + .dsa-platform-select:hover { + background: var(--bg4); + border-color: var(--gold); + color: var(--gold); + box-shadow: 0 4px 16px rgba(201, 168, 76, 0.15); + } + + .dsa-platform-select:focus { + border-color: var(--blue); + box-shadow: 0 0 0 2px rgba(74, 158, 255, 0.25); + } + + .optcode-card { + background: var(--bg3); + border: 1px solid var(--b1); + border-radius: var(--r2); + padding: 14px; + margin-top: 14px; + margin-bottom: 16px; + transition: all 0.3s ease; + } + + .optcode-card:hover { + border-color: rgba(62, 207, 178, 0.3); + box-shadow: 0 6px 20px rgba(62, 207, 178, 0.08); + } + + .optcode-hdr { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 10px; + } + + .optcode-badges { + display: flex; + gap: 8px; + align-items: center; + margin-bottom: 8px; + } + + .optcode-badge { + font-family: var(--fm); + font-size: 0.66rem; + padding: 3px 8px; + border-radius: 4px; + font-weight: 600; + } + + .optcode-badge.time { + background: var(--gold2); + border: 1px solid rgba(201, 168, 76, 0.25); + color: var(--gold); + } + + .optcode-badge.space { + background: var(--teal2); + border: 1px solid rgba(62, 207, 178, 0.25); + color: var(--teal); + } @@ -2110,7 +2383,21 @@ Powered by Gemini
Checking…
- +
+
+ Time: O(?) + Space: O(?) +
+
+

+            
+ +
+ +
+ +
+
+