diff --git a/CHATBOT_FEATURE.md b/CHATBOT_FEATURE.md new file mode 100644 index 00000000..e69de29b diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e6950a7c..9c409641 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ # Contributing to QyverixAI -Thank you for wanting to contribute! QyverixAI is a GSSoC 2026 project and welcomes all levels of contributors โ€” from first-timers to veterans. +Thank you for wanting to contribute! QyverixAI is a GSSoC 2026 project and welcomes all levels of contributors - from first-timers to veterans. --- @@ -37,7 +37,7 @@ git checkout -b feat/your-feature-name cd backend pip install -r requirements.txt -# 5. Run tests โ€” all must pass before submitting +# 5. Run tests - all must pass before submitting pytest -v # 6. Start the dev server @@ -48,11 +48,11 @@ uvicorn app.main:app --reload ## Ways to Contribute -### ๐Ÿ› Bug Fixes +### Bug Fixes - Open an issue first if the bug isn't already reported - Include the code snippet that triggers it + expected vs actual behavior -### โœจ New Bug Detection Patterns +### New Bug Detection Patterns Bug patterns live in `backend/app/services/code_assistant.py` in the `BUG_PATTERNS` list. Each pattern is a `BugPattern` dataclass: @@ -62,7 +62,7 @@ BugPattern( name="Pattern Name", pattern=r"regex_to_match", description="What the bug is and why it's a problem.", - suggestion="How to fix it โ€” be specific and actionable.", + suggestion="How to fix it - be specific and actionable.", severity="error", # "error" | "warning" | "info" languages=["Python"], # which languages this applies to ) @@ -78,19 +78,19 @@ def test_debug_detects_your_pattern(): assert "Pattern Name" in types ``` -### ๐Ÿ’ก New Suggestion Rules +### New Suggestion Rules Suggestion logic is in the `run_suggestions()` function in `code_assistant.py`. Add a new `if` block that appends to the `suggestions` list. -### ๐ŸŽจ Frontend Improvements -The entire frontend is `frontend/index.html` โ€” one self-contained file. No build step, no Node.js required. Just edit and open in your browser. +### Frontend Improvements +The entire frontend is `frontend/index.html` - one self-contained file. No build step, no Node.js required. Just edit and open in your browser. -### ๐Ÿ“– Documentation +### Documentation - Fix typos, improve clarity, add examples - Update the README if you add/change a feature - Add changelog entries for user-facing changes and fixes in `docs/CHANGELOG.md` - Add docstrings to functions that lack them -### ๐Ÿงช Tests +### Tests - Add test cases for edge cases - Improve coverage for existing features - Parametrize tests where appropriate @@ -107,6 +107,66 @@ The entire frontend is `frontend/index.html` โ€” one self-contained file. No bui --- +## Optional LLM / API Key setup (safe for open-source) + +QyverixAI can run fully offline using the built-in rule-based engine. If you opt-in to richer LLM-powered replies, follow these steps to provide an API key safely. + +- Use the provided example file: copy `.env.example` to `.env` and edit values locally. The repo already includes `.env.example` and `.gitignore` ignores `.env`. + + ```bash + # from repo root (Unix/macOS) + cp .env.example .env + # or on Windows PowerShell + Copy-Item .env.example .env + ``` + +- Edit `backend/.env` (or `backend/.env.local`) and set these values: + + ```text + LLM_ENABLED=true + LLM_API_KEY=sk_your_openai_key_here + LLM_BASE_URL=https://api.openai.com/v1 + LLM_MODEL=gpt-4o-mini + ``` + +- Important: do NOT commit `.env`. The repository `.gitignore` already excludes `.env`. To be safe, check with: + + ```bash + git status --ignored -- .env + ``` + +- Alternative: set env vars only for your shell session (no file written): + + PowerShell (temporary for session): + ```powershell + $env:LLM_ENABLED = "true" + $env:LLM_API_KEY = "sk_..." + cd backend + python -m uvicorn app.main:app --reload + ``` + + Unix / macOS (temporary for session): + ```bash + export LLM_ENABLED=true + export LLM_API_KEY=sk_... + cd backend + python -m uvicorn app.main:app --reload + ``` + +- CI / Deployment: configure the provider's secrets or environment variables (GitHub Actions Secrets, Render dashboard, Docker secrets, etc.) rather than storing keys in the repo. Example for GitHub Actions `workflow.yml`: + + ```yaml + env: + LLM_ENABLED: true + secrets: + LLM_API_KEY: ${{ secrets.LLM_API_KEY }} + ``` + +- Local LLM option: If you prefer no external keys, you can run an on-host LLM (Ollama, local Llama) and set `LLM_BASE_URL` to the local endpoint. This keeps everything on your machine. + +- If you want us to improve the built-in fallback (rule-based) to provide more detailed, actionable answers without an API key, we can do that โ€” it's the default behavior. + +If you want, I can add a short `LLM_SETUP.md` with screenshots and copy-ready snippets for Render/GitHub Actions โ€” tell me which host you'd like docs for. ## Large Files Policy CI automatically rejects PRs that contain files larger than **5 MB**. This keeps the repo lean and CI fast. @@ -288,4 +348,4 @@ Be respectful, inclusive, and constructive. We're here to learn and build togeth --- -Thank you for contributing! ๐Ÿš€ \ No newline at end of file +Thank you for contributing! diff --git a/FEATURE_LINE_HIGHLIGHTING.md b/FEATURE_LINE_HIGHLIGHTING.md new file mode 100644 index 00000000..1c68a932 --- /dev/null +++ b/FEATURE_LINE_HIGHLIGHTING.md @@ -0,0 +1,179 @@ +# Line Highlighting Feature - Implementation Complete โœ“ + +## Feature Summary +Implemented visual line highlighting in the code editor to mark detected issue lines during code analysis. Issues are highlighted with color-coded severity levels and interactive hover tooltips. + +## What Was Enhanced + +### 1. **Visual Line Highlighting** +- Lines with detected issues are now highlighted directly in the code editor +- Highlights appear behind the text with semi-transparent backgrounds +- 3px left border accent for easy visibility +- Smooth opacity transitions on hover + +### 2. **Color-Coded Severity Levels** +- **Errors** (Red): `rgba(239, 68, 68, 0.15)` with `#ef4444` accent +- **Warnings** (Yellow): `rgba(234, 179, 8, 0.15)` with `#eab308` accent +- **Info** (Blue): `rgba(59, 130, 246, 0.15)` with `#3b82f6` accent + +### 3. **Interactive Tooltips** +- Hover over any highlighted line to reveal a tooltip +- Tooltip shows: Issue Type, Description, Fix Suggestion +- Styled to match the app's design with proper contrast + +### 4. **Automatic Lifecycle Management** +- **Applied**: When debug analysis completes +- **Updated**: Highest severity takes precedence if multiple issues on same line +- **Cleared**: When code is edited, new analysis runs, or Clear button is clicked + +## Files Modified + +### `frontend/style.css` +Added comprehensive styling for: +- `.editor-wrap` - Main editor container with line numbers +- `.line-numbers` - Line number gutter display +- `.code-editor-shell` - Container for highlights overlay +- `.issue-line-highlights` - Highlight decorator container +- `.issue-line-highlight` - Individual highlight decorations +- `.issue-tooltip` - Hover tooltip styling +- Light theme color scheme support + +### `frontend/script.js` +Added new functions: +- `applyLineHighlights(issues)` - Processes issues and creates visual decorations +- `clearLineHighlights()` - Removes all highlights from editor + +Updated existing code: +- Changed all `codeInput` references to `codeEditor` (sync with HTML) +- Integrated highlights clearing into: + - Input event listener (when code is edited) + - Clear button handler + - RunAnalysis startup + - Code upload handler + +### `frontend/index.html` +- Already had proper HTML structure prepared with `issue-line-highlights` div +- Line numbers gutter already present + +## How to Use + +1. **Paste or upload code** into the editor +2. **Select "Debug" mode** from the mode tabs or select "Full" for comprehensive analysis +3. **Click "Analyze Code"** button +4. **Visual highlights appear** on lines with detected issues +5. **Hover over any highlighted line** to see issue details +6. **Edit code** to automatically clear highlights +7. **Run new analysis** to update highlights + +## API Integration + +Backend returns issues with these fields (already supported): +```python +class Issue(BaseModel): + type: str # e.g., "SyntaxError", "NameError" + line: int | None # Line number where issue occurs + description: str # Detailed issue description + suggestion: str # How to fix the issue + severity: str # "error", "warning", or "info" + code_snippet: str # Optional code snippet + code_context: str # Optional additional context +``` + +## Technical Details + +### Z-Index Layering +- Highlights container: `z-index: 0` (behind text) +- Code editor textarea: `z-index: 1` (on top) +- Tooltips: `z-index: 1000` (above everything) + +### Line Height Calculation +- Line height: 1.7 (from CSS) +- Font size: 13px +- Highlight height: `calc(1.7 * 13px)` = 22.1px per line +- Padding adjustment: 16px top padding on editor + +### Hover Behavior +- Highlights have 0% opacity by default +- Hover reveals highlight at 60% opacity (class: `.active`) +- Tooltip displays on hover with full opacity +- Smooth transitions via `var(--transition)` (0.18s ease) + +### Theme Support +- Dark mode: Default colors (see above) +- Light theme: CSS variables adapt automatically +- All colors maintain proper contrast ratios + +## Edge Cases Handled + +1. **Multiple issues on same line** + - Only highest severity is highlighted + - Priority: error (3) > warning (2) > info (1) + +2. **Missing line numbers** + - Issues without line numbers are skipped + - Invalid line numbers (0 or negative) are ignored + +3. **Code editing** + - Highlights clear immediately on input + - Prevents stale highlights during editing + +4. **Theme switching** + - Highlights update with theme change + - Tooltip colors adapt to theme + +5. **Empty code** + - No highlights applied to empty editor + - Error message shown instead + +## Performance Considerations + +- Highlights use CSS transforms (GPU-accelerated) +- Tooltip creation deferred until hover +- Container innerHTML cleared instead of individual removals +- No scroll sync required (CSS positioned absolutely) + +## Browser Compatibility + +- Works with all modern browsers supporting: + - CSS Grid and Flexbox + - CSS Custom Properties (CSS Variables) + - ES6 JavaScript (const, arrow functions, template literals) + - Event listeners and DOM manipulation + +## Future Enhancement Ideas + +1. **Gutter Decorations** + - Add small icons in line number gutter for each issue + - Click to jump to issue details + +2. **Inline Code Actions** + - Quick fix buttons directly in highlights + - Apply suggestions with one click + +3. **Filter/Search Issues** + - Filter highlights by severity + - Search for specific issue types + +4. **Keyboard Navigation** + - Arrow keys to navigate between issues + - Keyboard shortcuts to apply fixes + +5. **Copy/Export** + - Export highlighted issues as report + - Copy highlighted section with annotations + +## Testing Checklist + +- [ ] Paste Python code with syntax errors and run Debug analysis +- [ ] Verify error lines are highlighted in red +- [ ] Hover over highlight to see tooltip +- [ ] Edit code and verify highlights clear +- [ ] Run new analysis to see updated highlights +- [ ] Test with warnings and info-level issues +- [ ] Verify light theme colors are visible and accessible +- [ ] Test with multiple issues on same line (should show highest severity) +- [ ] Test with code that has no issues (should show "No issues" message) +- [ ] Test keyboard shortcut (Ctrl+Enter) to run analysis + +## Status +โœ… **IMPLEMENTATION COMPLETE** - Ready for testing and deployment diff --git a/README.md b/README.md index 8c3a8982..cee96703 100644 --- a/README.md +++ b/README.md @@ -130,8 +130,15 @@ cd AI-dev-assistant ```bash cd backend pip install -r requirements.txt -uvicorn app.main:app --reload +python -m uvicorn app.main:app --reload ``` + +If `uvicorn` is not recognized on Windows, make sure you installed dependencies in the same Python environment and run: + +```powershell +python -m uvicorn app.main:app --reload +``` + ### Environment Setup Copy `.env.example` to `.env` diff --git a/backend/app/routers/chat.py b/backend/app/routers/chat.py index 8093a374..5db45029 100644 --- a/backend/app/routers/chat.py +++ b/backend/app/routers/chat.py @@ -10,10 +10,14 @@ @router.post("", response_model=ChatResponse) async def chat(payload: ChatRequest) -> ChatResponse: + prompt = payload.message or "" + if payload.context: + prompt = f"{payload.context}\n\n{prompt}" if prompt else payload.context + if llm_analysis_client.enabled: try: reply = await llm_analysis_client.chat_reply( - message=payload.message, + message=prompt, code=payload.code, history=payload.history, level="intermediate", @@ -23,7 +27,7 @@ async def chat(payload: ChatRequest) -> ChatResponse: pass fallback_reply = chat_fallback_reply( - message=payload.message, + message=prompt, code=payload.code, history=payload.history, level="beginner", @@ -33,10 +37,14 @@ async def chat(payload: ChatRequest) -> ChatResponse: @router.post("/message", response_model=ChatMessageResponse) async def chat_message(payload: ChatMessageRequest) -> ChatMessageResponse: + prompt = payload.message or "" + if payload.context: + prompt = f"{payload.context}\n\n{prompt}" if prompt else payload.context + if llm_analysis_client.enabled: try: reply = await llm_analysis_client.chat_reply( - message=payload.message, + message=prompt, code=payload.code, history=payload.history, level=payload.level, @@ -51,7 +59,7 @@ async def chat_message(payload: ChatMessageRequest) -> ChatMessageResponse: pass fallback_reply = chat_fallback_reply( - message=payload.message, + message=prompt, code=payload.code, history=payload.history, level=payload.level, diff --git a/backend/app/schemas.py b/backend/app/schemas.py index be0af6be..3dc4d17f 100644 --- a/backend/app/schemas.py +++ b/backend/app/schemas.py @@ -894,31 +894,25 @@ class ShareRecord(BaseModel): # โ”€โ”€ Chat โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ class ChatRequest(BaseModel): - """Request body for the AI chat endpoint.""" - - message: str = Field( - ..., - min_length=1, - max_length=4_000, - description="The user's message or question.", - example="Why does my divide function crash?", - ) - code: str | None = Field( - default=None, - max_length=settings.max_code_chars, - description="Optional code snippet to provide as context for the conversation.", - example="def divide(a, b):\n return a / b", - ) - history: list[str] = Field( - default_factory=list, - max_length=20, - description="Previous conversation turns as a flat list of alternating user/assistant strings (max 20).", - example=["Why does it crash?", "Because b is zero."], - ) + message: str | None = Field(default=None, max_length=4_000) + question: str | None = Field(default=None, max_length=4_000) + code: str | None = Field(default=None, max_length=settings.max_code_chars) + context: str | None = Field(default=None, max_length=8_000) + analysis_id: str | None = Field(default=None, max_length=200) + history: list[str] = Field(default_factory=list, max_length=20) + + @field_validator("message", "question") + @classmethod + def sanitize_optional_text(cls, v: str | None) -> str | None: + if v is None: + return None + return validate_stored_action(v) - @field_validator("message") + @field_validator("context") @classmethod - def sanitize_message(cls, v: str) -> str: + def sanitize_context(cls, v: str | None) -> str | None: + if v is None: + return None return validate_stored_action(v) @field_validator("code") @@ -933,6 +927,14 @@ def sanitize_code(cls, v: str | None) -> str | None: def sanitize_history(cls, v: list[str]) -> list[str]: return validate_chat_history(v) + @model_validator(mode="after") + def ensure_prompt_present(self) -> "ChatRequest": + if not (self.message or self.question): + raise ValueError("message or question must be provided") + if not self.message and self.question: + self.message = self.question + return self + class ChatResponse(BaseModel): """Simple chat response.""" @@ -945,35 +947,26 @@ class ChatResponse(BaseModel): class ChatMessageRequest(BaseModel): - """Extended chat request with skill-level control.""" - - message: str = Field( - ..., - min_length=1, - max_length=4_000, - description="The user's message or question.", - example="Explain what a ZeroDivisionError is.", - ) - code: str | None = Field( - default=None, - max_length=settings.max_code_chars, - description="Optional code context for the conversation.", - example="def divide(a, b):\n return a / b", - ) - history: list[str] = Field( - default_factory=list, - max_length=20, - description="Previous conversation turns (max 20 entries).", - ) - level: str = Field( - default="beginner", - description="Explanation depth: `beginner`, `intermediate`, or `advanced`.", - example="beginner", - ) + message: str | None = Field(default=None, max_length=4_000) + question: str | None = Field(default=None, max_length=4_000) + code: str | None = Field(default=None, max_length=settings.max_code_chars) + context: str | None = Field(default=None, max_length=8_000) + analysis_id: str | None = Field(default=None, max_length=200) + history: list[str] = Field(default_factory=list, max_length=20) + level: str = Field(default="beginner") + + @field_validator("message", "question") + @classmethod + def sanitize_optional_text(cls, v: str | None) -> str | None: + if v is None: + return None + return validate_stored_action(v) - @field_validator("message") + @field_validator("context") @classmethod - def sanitize_message(cls, v: str) -> str: + def sanitize_context(cls, v: str | None) -> str | None: + if v is None: + return None return validate_stored_action(v) @field_validator("code") @@ -993,6 +986,14 @@ def sanitize_history(cls, v: list[str]) -> list[str]: def sanitize_level(cls, v: str) -> str: return validate_stored_action(v) + @model_validator(mode="after") + def ensure_prompt_present(self) -> "ChatMessageRequest": + if not (self.message or self.question): + raise ValueError("message or question must be provided") + if not self.message and self.question: + self.message = self.question + return self + class ChatMessageResponse(BaseModel): """Extended chat response with provider and mode metadata.""" diff --git a/backend/tests/test_chat.py b/backend/tests/test_chat.py new file mode 100644 index 00000000..ea74af0f --- /dev/null +++ b/backend/tests/test_chat.py @@ -0,0 +1,22 @@ +from fastapi.testclient import TestClient + +from app.main import app + +client = TestClient(app) + + +def test_chat_endpoint_uses_context_in_fallback_response(): + response = client.post( + "/chat/", + json={ + "message": "How do I fix this?", + "code": "def divide(a, b):\n return a / b\n", + "context": "This code divides by a runtime value and may fail with ZeroDivisionError.", + "history": [], + }, + ) + + assert response.status_code == 200 + body = response.json() + assert "response" in body + assert "division" in body["response"].lower() or "zerodivision" in body["response"].lower() diff --git a/frontend/assets/icon.svg b/frontend/assets/icon.svg new file mode 100644 index 00000000..21636a4d --- /dev/null +++ b/frontend/assets/icon.svg @@ -0,0 +1,14 @@ + + QyverixAI icon + QyverixAI hexagon icon mark with search lens + + + + + + + + + + + diff --git a/frontend/index.html b/frontend/index.html index 4bc37488..a6d4fc3e 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -2,8 +2,9 @@ - - + + + QyverixAI โ€” AI Developer Assistant @@ -255,6 +256,96 @@ gap: 8px } + .chat-panel { + border: 1px solid var(--border); + border-radius: var(--r); + padding: 12px; + background: var(--bg2); + box-shadow: var(--shadow2); + } + + .chat-panel-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 10px; + } + + .chat-panel-title { + font-weight: 700; + font-size: 0.95rem; + } + + .chat-panel-subtitle { + color: var(--text2); + font-size: 0.8rem; + margin-top: 2px; + } + + .chat-messages { + display: flex; + flex-direction: column; + gap: 8px; + min-height: 120px; + max-height: 220px; + overflow: auto; + padding: 6px 0 4px; + margin-bottom: 8px; + } + + .chat-empty { + color: var(--text2); + font-size: 0.9rem; + padding: 12px 4px; + } + + .chat-bubble { + padding: 8px 10px; + border-radius: 10px; + font-size: 0.9rem; + line-height: 1.45; + white-space: pre-wrap; + max-width: 100%; + } + + .chat-bubble.user { + background: var(--accent); + color: white; + align-self: flex-end; + } + + .chat-bubble.assistant { + background: var(--bg3); + color: var(--text); + align-self: flex-start; + border: 1px solid var(--border); + } + + .chat-input-row { + display: flex; + gap: 8px; + } + + .chat-input { + flex: 1; + border: 1px solid var(--border); + border-radius: 10px; + background: var(--bg); + color: var(--text); + padding: 10px 12px; + font: inherit; + } + + .chat-send { + border: none; + border-radius: 10px; + background: linear-gradient(135deg, var(--accent), var(--accent2)); + color: white; + font-weight: 600; + padding: 0 14px; + cursor: pointer; + } + .nav-link { display: flex; align-items: center; @@ -736,11 +827,128 @@ background: var(--bg3); border-right: 1px solid var(--border); overflow: hidden; + z-index: 2; + } + + .line-number-row { + display: block; + height: 22.1px; + padding-right: 10px; + position: relative; + } + + .line-number-row.has-issue { + color: var(--text); + font-weight: 600; + } + + .line-number-row.has-issue::before { + content: ''; + position: absolute; + left: 4px; + top: 50%; + width: 6px; + height: 6px; + border-radius: 50%; + transform: translateY(-50%); + background: var(--accent); + box-shadow: 0 0 0 3px rgba(91, 156, 246, 0.14); + } + + .line-number-row.issue-error::before { + background: var(--red); + box-shadow: 0 0 0 3px rgba(242, 87, 87, 0.14); + } + + .line-number-row.issue-warning::before { + background: var(--yellow); + box-shadow: 0 0 0 3px rgba(245, 200, 66, 0.14); + } + + .line-number-row.issue-info::before { + background: var(--accent); + box-shadow: 0 0 0 3px rgba(91, 156, 246, 0.14); + } + + .code-editor-shell { + flex: 1; + min-width: 0; + position: relative; + } + + .issue-line-highlights { + position: absolute; + inset: 0; + padding: 16px 0; pointer-events: none; - min-height: 300px; - max-height: 500px; - height: 100%; - } + overflow: hidden; + z-index: 0; + } + + .issue-highlight-track { + transform: translateY(0); + will-change: transform; + } + + .issue-highlight-row { + height: 22.1px; + border-left: 3px solid transparent; + opacity: 0.95; + position: relative; + } + + .issue-highlight-row::after { + content: attr(data-label); + position: absolute; + top: 3px; + right: 12px; + padding: 1px 7px; + border-radius: 999px; + font-family: var(--font-ui); + font-size: 0.62rem; + font-weight: 700; + letter-spacing: 0.04em; + line-height: 1.4; + text-transform: uppercase; + opacity: 0; + transform: translateX(4px); + transition: opacity var(--transition), transform var(--transition); + } + + .editor-wrap:hover .issue-highlight-row::after { + opacity: 1; + transform: translateX(0); + } + + .issue-highlight-row.issue-error { + border-left-color: var(--red); + background: linear-gradient(90deg, rgba(242, 87, 87, 0.24), rgba(242, 87, 87, 0.07) 70%, transparent); + } + + .issue-highlight-row.issue-error::after { + color: var(--red); + background: rgba(242, 87, 87, 0.14); + } + + .issue-highlight-row.issue-warning { + border-left-color: var(--yellow); + background: linear-gradient(90deg, rgba(245, 200, 66, 0.24), rgba(245, 200, 66, 0.07) 70%, transparent); + } + + .issue-highlight-row.issue-warning::after { + color: var(--yellow); + background: rgba(245, 200, 66, 0.14); + } + + .issue-highlight-row.issue-info { + border-left-color: var(--accent); + background: linear-gradient(90deg, rgba(91, 156, 246, 0.22), rgba(91, 156, 246, 0.06) 70%, transparent); + } + + .issue-highlight-row.issue-info::after { + color: var(--accent); + background: rgba(91, 156, 246, 0.14); + } #codeEditor { width: 100%; @@ -760,6 +968,8 @@ outline: none; resize: vertical; tab-size: 2; + position: relative; + z-index: 1; } .editor-footer { @@ -2789,7 +2999,10 @@

Debug. Understand.
Ship faster.

.py  ยท  .js  ยท  .ts  ยท  .java  ยท  .cpp  ยท  .zip
1
- +
+ + +
+
+
+
+
Ask follow-up questions
+
Use the current analysis as context
+
+
+
+
Ask something like โ€œWhy is this bug happening?โ€
+
+
+ + +
+
+ `).join('')} `; text += issues.map(i => `${i.type}: ${i.description}\nFix: ${i.suggestion}`).join('\n') + '\n\n'; + + // Apply line highlights if issues exist + if (issues.length > 0) { + applyLineHighlights(issues); + } } if (data.suggestions) { const sg = data.suggestions; @@ -570,11 +784,14 @@ function renderResult(data, mode) { ${i.line ? `Line ${i.line}` : ''} ${renderComplexityBadge(i)}

${escHtml(i.description || '')}

- ${i.suggestion ? `

โ†’ ${escHtml(i.suggestion)}

` : ''} + ${getFixSuggestionMarkup(i)} `).join('')} `; text = issues.map(i => `[${i.type}] Line ${i.line}: ${i.description}\nFix: ${i.suggestion}`).join('\n'); + + // Apply line highlights for debugging mode + applyLineHighlights(issues); } else if (mode === 'suggestions') { const cards = data.suggestions || []; html += `
diff --git a/frontend/style.css b/frontend/style.css index 6bfe3c5c..512dc85a 100644 --- a/frontend/style.css +++ b/frontend/style.css @@ -361,8 +361,10 @@ body { } .code-editor { + position: relative; + z-index: 1; flex: 1; - background: var(--bg); + background: transparent; border: none; outline: none; color: var(--text); @@ -372,9 +374,146 @@ body { padding: 16px; resize: none; min-height: 380px; + caret-color: var(--text); } .code-editor::placeholder { color: var(--text-3); } +/* โ”€โ”€ Line highlighting system โ”€โ”€ */ +.editor-wrap { + position: relative; + flex: 1; + display: flex; + overflow: hidden; +} + +.line-numbers { + display: flex; + flex-direction: column; + padding: 16px 8px; + background: var(--bg-2); + border-right: 1px solid var(--border); + color: var(--text-3); + font-family: var(--font-mono); + font-size: 13px; + line-height: 1.7; + text-align: right; + user-select: none; + min-width: 40px; + pointer-events: none; +} + +.code-editor-shell { + position: relative; + flex: 1; + display: flex; + flex-direction: column; + background: var(--bg); +} + +.issue-line-highlights { + position: absolute; + top: 0; + left: 0; + right: 0; + z-index: 0; + pointer-events: none; +} + +.issue-line-highlight { + position: absolute; + left: 0; + right: 0; + height: calc(1.7 * 13px); + opacity: 0; + transition: opacity var(--transition); + pointer-events: auto; +} + +.issue-line-highlight:hover { + opacity: 1 !important; +} + +.issue-line-highlight.error { + background: rgba(239, 68, 68, 0.15); + border-left: 3px solid #ef4444; +} + +.issue-line-highlight.warning { + background: rgba(234, 179, 8, 0.15); + border-left: 3px solid #eab308; +} + +.issue-line-highlight.info { + background: rgba(59, 130, 246, 0.15); + border-left: 3px solid #3b82f6; +} + +.issue-line-highlight.active { + opacity: 0.6; +} + +.issue-tooltip { + position: absolute; + background: var(--bg-3); + color: var(--text); + border: 1px solid var(--border); + border-radius: 4px; + padding: 8px 10px; + font-size: 11px; + font-family: var(--font-mono); + line-height: 1.4; + white-space: nowrap; + pointer-events: none; + z-index: 1000; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4); +} + +.issue-tooltip.error { + border-left: 3px solid #ef4444; +} + +.issue-tooltip.warning { + border-left: 3px solid #eab308; +} + +.issue-tooltip.info { + border-left: 3px solid #3b82f6; +} + +.issue-gutter-marker { + position: absolute; + left: 4px; + width: 20px; + height: 20px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + font-size: 10px; + font-weight: bold; + cursor: pointer; + transition: all var(--transition); +} + +.issue-gutter-marker.error { + background: rgba(239, 68, 68, 0.2); + color: #ef4444; +} + +.issue-gutter-marker.warning { + background: rgba(234, 179, 8, 0.2); + color: #eab308; +} + +.issue-gutter-marker.info { + background: rgba(59, 130, 246, 0.2); + color: #3b82f6; +} + +.issue-gutter-marker:hover { + transform: scale(1.2); +} + .editor-footer { display: flex; justify-content: space-between; @@ -440,6 +579,40 @@ body { border-color: rgba(59, 130, 246, 0.35); background: rgba(59, 130, 246, 0.12); } + +.fix-suggestion-row { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 10px; + margin-top: 6px; +} + +.fix-suggestion-text { + color: var(--accent-green); + font-size: 13px; + line-height: 1.5; +} + +.goto-line-btn { + border: 1px solid rgba(34, 197, 94, 0.25); + background: transparent; + color: var(--accent-green); + border-radius: 6px; + padding: 5px 10px; + font-family: var(--font-mono); + font-size: 12px; + cursor: pointer; + transition: background var(--transition), color var(--transition), transform var(--transition); +} + +.goto-line-btn:hover, +.goto-line-btn:focus-visible { + background: rgba(34, 197, 94, 0.12); + color: var(--text); + transform: translateY(-1px); +} + .engine-badge.llm { color: #86efac; border-color: rgba(34, 197, 94, 0.35);