Spaces:
Sleeping
feat(ai-agent): complete modular AI agent system for HF Spaces
Browse files- Implemented microservices architecture with clear separation of concerns
- Services: Gemini (AI), GitHub (API), Project (analysis)
- Agents: Scanner (periodic scans), Chat (multi-turn conversations)
- Periodic project scanner (1hr interval, configurable)
- Chat interface with Gemini API and command support
- GitHub integration (create issues, PRs, trigger workflows)
- Responsive web UI with dark mode support
- Task runner with retry logic and priority queue
- Comprehensive logging and error handling
- Docker configured for HF Spaces (port 7860)
- Full documentation: README, INSTALLATION, ARCHITECTURE guides
Features:
Hourly automated issue scanning with AI analysis
Real-time chat with project context awareness
GitHub Actions workflow integration
Auto-create issues from scan results
Code analysis with structured recommendations
Project structure exploration
Settings and configuration UI
Architecture:
- Modular services (Gemini, GitHub, Project)
- Agent orchestration layer
- RESTful API with Express
- Modern vanilla JS frontend
- Task queue with retries
- Session management for chats
- Caching for performance
Deployment:
- Ready for HF Spaces
- Environment-based configuration
- Health check endpoint
- Graceful shutdown handling
- public/.env.example +83 -0
- public/.gitignore +13 -0
- public/ARCHITECTURE.md +455 -0
- public/Dockerfile +29 -0
- public/INSTALLATION.md +367 -0
- public/README.md +296 -0
- public/api.php +0 -14
- public/assets/css/styles.css +732 -0
- public/assets/js/app.js +105 -0
- public/assets/js/modules/api-client.js +54 -0
- public/assets/js/modules/chat.js +109 -0
- public/assets/js/modules/project.js +119 -0
- public/assets/js/modules/scanner.js +111 -0
- public/assets/js/modules/ui.js +50 -0
- public/index.html +154 -30
- public/package.json +27 -0
- public/process.php +0 -22
- public/script.js +0 -66
- public/src/agents/chat.js +271 -0
- public/src/agents/scanner.js +255 -0
- public/src/api/chat.js +121 -0
- public/src/api/project.js +136 -0
- public/src/api/scanner.js +116 -0
- public/src/config/env.js +45 -0
- public/src/config/logger.js +48 -0
- public/src/server.js +92 -0
- public/src/services/gemini.js +140 -0
- public/src/services/github.js +186 -0
- public/src/services/project.js +198 -0
- public/src/utils/taskRunner.js +178 -0
- public/styles.css +0 -140
|
@@ -0,0 +1,83 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# .env Template
|
| 2 |
+
# Copy this to .env and fill in your actual values
|
| 3 |
+
# NEVER commit the .env file with real tokens!
|
| 4 |
+
|
| 5 |
+
# =============================================================================
|
| 6 |
+
# API KEYS & AUTHENTICATION
|
| 7 |
+
# =============================================================================
|
| 8 |
+
|
| 9 |
+
# Google Generative AI (Gemini)
|
| 10 |
+
# Get from: https://makersuite.google.com/app/apikey
|
| 11 |
+
GEMINI_API_KEY=your_gemini_api_key_here
|
| 12 |
+
|
| 13 |
+
# GitHub Personal Access Token
|
| 14 |
+
# Get from: https://github.com/settings/tokens
|
| 15 |
+
# Required scopes: repo, issues, actions
|
| 16 |
+
GITHUB_TOKEN=your_github_token_here
|
| 17 |
+
|
| 18 |
+
# Hugging Face Token
|
| 19 |
+
# Get from: https://huggingface.co/settings/tokens
|
| 20 |
+
HF_TOKEN=your_huggingface_token_here
|
| 21 |
+
|
| 22 |
+
# =============================================================================
|
| 23 |
+
# GITHUB CONFIGURATION
|
| 24 |
+
# =============================================================================
|
| 25 |
+
|
| 26 |
+
# Repository details
|
| 27 |
+
GITHUB_REPO=NLarchive/my-webapp-hf
|
| 28 |
+
GITHUB_OWNER=NLarchive
|
| 29 |
+
GITHUB_BRANCH=main
|
| 30 |
+
|
| 31 |
+
# =============================================================================
|
| 32 |
+
# HUGGING FACE CONFIGURATION
|
| 33 |
+
# =============================================================================
|
| 34 |
+
|
| 35 |
+
# HF Space details
|
| 36 |
+
HF_SPACE_NAME=my-webapp-hf
|
| 37 |
+
HF_SPACE_URL=https://huggingface.co/spaces/NLarchive/my-webapp-hf
|
| 38 |
+
|
| 39 |
+
# =============================================================================
|
| 40 |
+
# SCANNER CONFIGURATION
|
| 41 |
+
# =============================================================================
|
| 42 |
+
|
| 43 |
+
# Scan interval in milliseconds (default: 1 hour = 3600000ms)
|
| 44 |
+
# Examples:
|
| 45 |
+
# 300000 = 5 minutes
|
| 46 |
+
# 900000 = 15 minutes
|
| 47 |
+
# 1800000 = 30 minutes
|
| 48 |
+
# 3600000 = 1 hour
|
| 49 |
+
# 86400000 = 1 day
|
| 50 |
+
SCAN_INTERVAL=3600000
|
| 51 |
+
|
| 52 |
+
# Enable automatic fixing of detected issues
|
| 53 |
+
# When true, scanner will attempt to fix issues and create PRs
|
| 54 |
+
ENABLE_AUTO_FIX=true
|
| 55 |
+
|
| 56 |
+
# Automatically commit fixes to GitHub
|
| 57 |
+
# When true, fixes will be committed without review
|
| 58 |
+
# When false, fixes are created as draft PRs for review
|
| 59 |
+
AUTO_COMMIT=false
|
| 60 |
+
|
| 61 |
+
# =============================================================================
|
| 62 |
+
# SERVER CONFIGURATION
|
| 63 |
+
# =============================================================================
|
| 64 |
+
|
| 65 |
+
# Server port (HF Spaces uses port 7860)
|
| 66 |
+
PORT=3000
|
| 67 |
+
|
| 68 |
+
# Environment: development, production
|
| 69 |
+
NODE_ENV=production
|
| 70 |
+
|
| 71 |
+
# Logging level: error, warn, info, debug
|
| 72 |
+
LOG_LEVEL=info
|
| 73 |
+
|
| 74 |
+
# Enable debug mode (verbose logging)
|
| 75 |
+
DEBUG=false
|
| 76 |
+
|
| 77 |
+
# =============================================================================
|
| 78 |
+
# OPTIONAL: Task Scheduling
|
| 79 |
+
# =============================================================================
|
| 80 |
+
|
| 81 |
+
# Uncomment to customize task behavior
|
| 82 |
+
# TASK_TIMEOUT=30000
|
| 83 |
+
# TASK_RETRIES=3
|
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
node_modules/
|
| 2 |
+
.env
|
| 3 |
+
.env.local
|
| 4 |
+
.env.*.local
|
| 5 |
+
*.log
|
| 6 |
+
.DS_Store
|
| 7 |
+
dist/
|
| 8 |
+
build/
|
| 9 |
+
.vscode/
|
| 10 |
+
.idea/
|
| 11 |
+
*.swp
|
| 12 |
+
*.swo
|
| 13 |
+
*~
|
|
@@ -0,0 +1,455 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# AI Agent Architecture & Microservices Design
|
| 2 |
+
|
| 3 |
+
## Overview
|
| 4 |
+
|
| 5 |
+
The AI Agent is built as a modular microservices system with clear separation of concerns. Each component is independently testable and can be extended without affecting others.
|
| 6 |
+
|
| 7 |
+
## Core Components
|
| 8 |
+
|
| 9 |
+
### 1. Services Layer (`src/services/`)
|
| 10 |
+
|
| 11 |
+
Services are stateless, reusable modules that handle specific functionality.
|
| 12 |
+
|
| 13 |
+
#### **Gemini Service** (`gemini.js`)
|
| 14 |
+
- Purpose: Handle all Google Generative AI interactions
|
| 15 |
+
- Methods:
|
| 16 |
+
- `generate(prompt, options)` - Single-turn generation
|
| 17 |
+
- `chat(message)` - Multi-turn conversation
|
| 18 |
+
- `analyzeCode(code, language)` - Code analysis with structured output
|
| 19 |
+
- `generateCommitMessage(changes)` - Create semantic commit messages
|
| 20 |
+
- `clearHistory()` - Reset conversation state
|
| 21 |
+
|
| 22 |
+
Example:
|
| 23 |
+
```javascript
|
| 24 |
+
import { geminiService } from './services/gemini.js';
|
| 25 |
+
|
| 26 |
+
const analysis = await geminiService.analyzeCode(code, 'javascript');
|
| 27 |
+
const response = await geminiService.chat("What does this function do?");
|
| 28 |
+
```
|
| 29 |
+
|
| 30 |
+
#### **GitHub Service** (`github.js`)
|
| 31 |
+
- Purpose: GitHub API integration
|
| 32 |
+
- Methods:
|
| 33 |
+
- `createIssue(issue)` - Create GitHub issue
|
| 34 |
+
- `createPullRequest(pr)` - Create PR
|
| 35 |
+
- `getFileContents(path)` - Read file from repo
|
| 36 |
+
- `listFiles(path)` - List directory contents
|
| 37 |
+
- `triggerWorkflow(workflowId, inputs)` - Trigger GitHub Actions
|
| 38 |
+
- `getRepoInfo()` - Repository metadata
|
| 39 |
+
- `listIssues(state)` - Get open/closed issues
|
| 40 |
+
- `addIssueComment(issueNumber, body)` - Comment on issue
|
| 41 |
+
|
| 42 |
+
Example:
|
| 43 |
+
```javascript
|
| 44 |
+
import { githubService } from './services/github.js';
|
| 45 |
+
|
| 46 |
+
const issue = await githubService.createIssue({
|
| 47 |
+
title: "Bug: Login fails",
|
| 48 |
+
body: "Steps to reproduce...",
|
| 49 |
+
labels: ['bug', 'critical']
|
| 50 |
+
});
|
| 51 |
+
```
|
| 52 |
+
|
| 53 |
+
#### **Project Service** (`project.js`)
|
| 54 |
+
- Purpose: Project analysis and exploration
|
| 55 |
+
- Methods:
|
| 56 |
+
- `getProjectStructure()` - Files and directories
|
| 57 |
+
- `getFileContent(path)` - Read specific file
|
| 58 |
+
- `scanForIssues()` - Automated issue detection
|
| 59 |
+
- `getReadme()` - README.md content
|
| 60 |
+
- `getDockerfile()` - Dockerfile content
|
| 61 |
+
- `getSourceFiles()` - List programming files
|
| 62 |
+
|
| 63 |
+
Example:
|
| 64 |
+
```javascript
|
| 65 |
+
import { projectService } from './services/project.js';
|
| 66 |
+
|
| 67 |
+
const issues = await projectService.scanForIssues();
|
| 68 |
+
const readme = await projectService.getReadme();
|
| 69 |
+
```
|
| 70 |
+
|
| 71 |
+
### 2. Agents Layer (`src/agents/`)
|
| 72 |
+
|
| 73 |
+
Agents orchestrate multiple services to accomplish complex tasks.
|
| 74 |
+
|
| 75 |
+
#### **Scanner Agent** (`scanner.js`)
|
| 76 |
+
- Purpose: Periodic project analysis and remediation
|
| 77 |
+
- Workflow:
|
| 78 |
+
1. Get project structure
|
| 79 |
+
2. Scan for issues
|
| 80 |
+
3. Analyze critical files with AI
|
| 81 |
+
4. Generate recommendations
|
| 82 |
+
5. Create GitHub issues if enabled
|
| 83 |
+
|
| 84 |
+
Key Methods:
|
| 85 |
+
```javascript
|
| 86 |
+
scannerAgent.startPeriodicScanning() // Begin hourly scans
|
| 87 |
+
scannerAgent.performScan() // One-time full scan
|
| 88 |
+
scannerAgent.getLastScan() // Get last report
|
| 89 |
+
```
|
| 90 |
+
|
| 91 |
+
Example Flow:
|
| 92 |
+
```
|
| 93 |
+
[Scan Triggered]
|
| 94 |
+
↓
|
| 95 |
+
[Get Project Structure] → GitHub Service
|
| 96 |
+
↓
|
| 97 |
+
[Detect Issues] → Project Service
|
| 98 |
+
↓
|
| 99 |
+
[AI Analysis] → Gemini Service
|
| 100 |
+
↓
|
| 101 |
+
[Generate Recommendations] → Gemini Service
|
| 102 |
+
↓
|
| 103 |
+
[Create Issue] → GitHub Service (if enabled)
|
| 104 |
+
```
|
| 105 |
+
|
| 106 |
+
#### **Chat Agent** (`chat.js`)
|
| 107 |
+
- Purpose: Multi-turn conversation management
|
| 108 |
+
- Workflow:
|
| 109 |
+
1. Start conversation session
|
| 110 |
+
2. Maintain context about project
|
| 111 |
+
3. Process commands or queries
|
| 112 |
+
4. Generate AI responses
|
| 113 |
+
5. Track conversation history
|
| 114 |
+
|
| 115 |
+
Key Methods:
|
| 116 |
+
```javascript
|
| 117 |
+
chatAgent.startConversation(sessionId) // Initialize session
|
| 118 |
+
chatAgent.processMessage(sessionId, msg) // Handle user input
|
| 119 |
+
chatAgent.getHistory(sessionId) // Get conversation
|
| 120 |
+
chatAgent.clearConversation(sessionId) // End session
|
| 121 |
+
```
|
| 122 |
+
|
| 123 |
+
Commands:
|
| 124 |
+
```
|
| 125 |
+
/scan → Trigger project scan
|
| 126 |
+
/status → Get repository status
|
| 127 |
+
/issues → List open issues
|
| 128 |
+
/help → Show available commands
|
| 129 |
+
```
|
| 130 |
+
|
| 131 |
+
### 3. API Routes (`src/api/`)
|
| 132 |
+
|
| 133 |
+
Express routes that expose services and agents via HTTP.
|
| 134 |
+
|
| 135 |
+
#### **Chat Routes** (`chat.js`)
|
| 136 |
+
```
|
| 137 |
+
POST /api/chat/start # Initialize session
|
| 138 |
+
POST /api/chat/message # Send message
|
| 139 |
+
GET /api/chat/history/:sessionId # Get conversation
|
| 140 |
+
DELETE /api/chat/clear/:sessionId # Clear conversation
|
| 141 |
+
GET /api/chat/stats # Chat statistics
|
| 142 |
+
```
|
| 143 |
+
|
| 144 |
+
#### **Scanner Routes** (`scanner.js`)
|
| 145 |
+
```
|
| 146 |
+
POST /api/scanner/scan # Manual scan
|
| 147 |
+
GET /api/scanner/last-report # Last report
|
| 148 |
+
GET /api/scanner/issues # Current issues
|
| 149 |
+
POST /api/scanner/start-continuous # Start auto-scanning
|
| 150 |
+
POST /api/scanner/stop-continuous # Stop auto-scanning
|
| 151 |
+
```
|
| 152 |
+
|
| 153 |
+
#### **Project Routes** (`project.js`)
|
| 154 |
+
```
|
| 155 |
+
GET /api/project/structure # Project layout
|
| 156 |
+
GET /api/project/file?path=... # File content
|
| 157 |
+
GET /api/project/readme # README.md
|
| 158 |
+
GET /api/project/dockerfile # Dockerfile
|
| 159 |
+
GET /api/project/source-files # Source files list
|
| 160 |
+
```
|
| 161 |
+
|
| 162 |
+
### 4. Configuration & Utilities
|
| 163 |
+
|
| 164 |
+
#### **Environment Config** (`config/env.js`)
|
| 165 |
+
- Centralized configuration management
|
| 166 |
+
- Validates required variables at startup
|
| 167 |
+
- Type-safe environment access
|
| 168 |
+
|
| 169 |
+
```javascript
|
| 170 |
+
import { config, validateConfig } from './config/env.js';
|
| 171 |
+
|
| 172 |
+
console.log(config.GEMINI_API_KEY);
|
| 173 |
+
validateConfig(); // Throws if missing required vars
|
| 174 |
+
```
|
| 175 |
+
|
| 176 |
+
#### **Logger** (`config/logger.js`)
|
| 177 |
+
- Structured logging with levels
|
| 178 |
+
- Timestamp and context support
|
| 179 |
+
- Configurable verbosity
|
| 180 |
+
|
| 181 |
+
```javascript
|
| 182 |
+
import { logger } from './config/logger.js';
|
| 183 |
+
|
| 184 |
+
logger.info('Something happened', { details: {...} });
|
| 185 |
+
logger.error('Failed', { error: err.message });
|
| 186 |
+
logger.debug('Debug info', { data });
|
| 187 |
+
```
|
| 188 |
+
|
| 189 |
+
#### **Task Runner** (`utils/taskRunner.js`)
|
| 190 |
+
- Task queue execution with retry logic
|
| 191 |
+
- Priority-based ordering
|
| 192 |
+
- Timeout management
|
| 193 |
+
- Execution history
|
| 194 |
+
|
| 195 |
+
```javascript
|
| 196 |
+
import { taskRunner } from './utils/taskRunner.js';
|
| 197 |
+
|
| 198 |
+
taskRunner.addTask({
|
| 199 |
+
id: 'analyze-code',
|
| 200 |
+
name: 'Analyze codebase',
|
| 201 |
+
priority: 10,
|
| 202 |
+
execute: async () => { /* task code */ },
|
| 203 |
+
retries: 3,
|
| 204 |
+
timeout: 30000
|
| 205 |
+
});
|
| 206 |
+
|
| 207 |
+
const results = await taskRunner.runAll();
|
| 208 |
+
```
|
| 209 |
+
|
| 210 |
+
## Data Flow Diagrams
|
| 211 |
+
|
| 212 |
+
### Chat Flow
|
| 213 |
+
```
|
| 214 |
+
User Input
|
| 215 |
+
↓
|
| 216 |
+
[Chat API] POST /api/chat/message
|
| 217 |
+
↓
|
| 218 |
+
[Chat Agent] processMessage()
|
| 219 |
+
↓
|
| 220 |
+
Load Project Context? → [Project Service] getProjectStructure()
|
| 221 |
+
↓
|
| 222 |
+
Is Command? (/scan, /status, etc)
|
| 223 |
+
├─ Yes → Execute specific handler
|
| 224 |
+
│ └─ May call GitHub Service
|
| 225 |
+
│
|
| 226 |
+
└─ No → [Gemini Service] chat()
|
| 227 |
+
└─ Multi-turn conversation
|
| 228 |
+
↓
|
| 229 |
+
[Chat Agent] Store in history
|
| 230 |
+
↓
|
| 231 |
+
Return Response to Client
|
| 232 |
+
↓
|
| 233 |
+
Update Chat UI
|
| 234 |
+
```
|
| 235 |
+
|
| 236 |
+
### Scanner Flow
|
| 237 |
+
```
|
| 238 |
+
[Timer] Every SCAN_INTERVAL
|
| 239 |
+
↓
|
| 240 |
+
[Scanner Agent] performScan()
|
| 241 |
+
↓
|
| 242 |
+
[Project Service] getProjectStructure()
|
| 243 |
+
↓
|
| 244 |
+
[Project Service] scanForIssues()
|
| 245 |
+
↓
|
| 246 |
+
[Gemini Service] analyzeCode() (per file)
|
| 247 |
+
↓
|
| 248 |
+
[Gemini Service] generateRecommendations()
|
| 249 |
+
↓
|
| 250 |
+
[Create Report]
|
| 251 |
+
↓
|
| 252 |
+
Issues Found + AUTO_FIX enabled?
|
| 253 |
+
├─ Yes → [GitHub Service] createIssue()
|
| 254 |
+
└─ No → [Store Report]
|
| 255 |
+
↓
|
| 256 |
+
Return Report to Client
|
| 257 |
+
```
|
| 258 |
+
|
| 259 |
+
### Request Flow
|
| 260 |
+
```
|
| 261 |
+
Browser Client
|
| 262 |
+
↓
|
| 263 |
+
JavaScript fetch() → API Request
|
| 264 |
+
↓
|
| 265 |
+
[Express Router]
|
| 266 |
+
├─ Parse request
|
| 267 |
+
├─ Call service/agent
|
| 268 |
+
├─ Handle errors
|
| 269 |
+
└─ Return JSON response
|
| 270 |
+
↓
|
| 271 |
+
Client Updates UI
|
| 272 |
+
```
|
| 273 |
+
|
| 274 |
+
## Extension Points
|
| 275 |
+
|
| 276 |
+
### Adding a New Scanner
|
| 277 |
+
|
| 278 |
+
1. Create analyzer in `src/agents/scanner.js`:
|
| 279 |
+
|
| 280 |
+
```javascript
|
| 281 |
+
async analyzeProject() {
|
| 282 |
+
const sourceFiles = await projectService.getSourceFiles();
|
| 283 |
+
const analysis = {
|
| 284 |
+
files: [],
|
| 285 |
+
overallHealth: 'unknown',
|
| 286 |
+
};
|
| 287 |
+
|
| 288 |
+
for (const file of sourceFiles.slice(0, 5)) {
|
| 289 |
+
try {
|
| 290 |
+
const content = await projectService.getFileContent(file.path);
|
| 291 |
+
// YOUR CUSTOM ANALYSIS HERE
|
| 292 |
+
const customAnalysis = await your_analysis(content);
|
| 293 |
+
analysis.files.push({
|
| 294 |
+
name: file.name,
|
| 295 |
+
analysis: customAnalysis,
|
| 296 |
+
});
|
| 297 |
+
} catch (e) {
|
| 298 |
+
logger.warn(`Failed to analyze ${file.name}`);
|
| 299 |
+
}
|
| 300 |
+
}
|
| 301 |
+
|
| 302 |
+
return analysis;
|
| 303 |
+
}
|
| 304 |
+
```
|
| 305 |
+
|
| 306 |
+
2. Results appear in scan reports and recommendations.
|
| 307 |
+
|
| 308 |
+
### Adding a New Command
|
| 309 |
+
|
| 310 |
+
1. Extend `src/agents/chat.js`:
|
| 311 |
+
|
| 312 |
+
```javascript
|
| 313 |
+
async handleMessage(message, context) {
|
| 314 |
+
if (message.startsWith('/mycommand')) {
|
| 315 |
+
return this.handleMyCommand(context);
|
| 316 |
+
}
|
| 317 |
+
// ... existing code
|
| 318 |
+
}
|
| 319 |
+
|
| 320 |
+
async handleMyCommand(context) {
|
| 321 |
+
// Your command logic
|
| 322 |
+
return "Command result";
|
| 323 |
+
}
|
| 324 |
+
```
|
| 325 |
+
|
| 326 |
+
2. Add to `/help` text:
|
| 327 |
+
|
| 328 |
+
```javascript
|
| 329 |
+
getHelpText() {
|
| 330 |
+
return `
|
| 331 |
+
/mycommand - Description of what it does
|
| 332 |
+
...existing commands...
|
| 333 |
+
`;
|
| 334 |
+
}
|
| 335 |
+
```
|
| 336 |
+
|
| 337 |
+
### Adding a New API Endpoint
|
| 338 |
+
|
| 339 |
+
1. Create route in `src/api/newfeature.js`:
|
| 340 |
+
|
| 341 |
+
```javascript
|
| 342 |
+
import express from 'express';
|
| 343 |
+
import { someService } from '../services/some.js';
|
| 344 |
+
|
| 345 |
+
const router = express.Router();
|
| 346 |
+
|
| 347 |
+
router.get('/endpoint', async (req, res) => {
|
| 348 |
+
try {
|
| 349 |
+
const result = await someService.doSomething();
|
| 350 |
+
res.json({ success: true, result });
|
| 351 |
+
} catch (error) {
|
| 352 |
+
res.status(500).json({ error: error.message });
|
| 353 |
+
}
|
| 354 |
+
});
|
| 355 |
+
|
| 356 |
+
export default router;
|
| 357 |
+
```
|
| 358 |
+
|
| 359 |
+
2. Mount in `src/server.js`:
|
| 360 |
+
|
| 361 |
+
```javascript
|
| 362 |
+
import newFeatureRoutes from './api/newfeature.js';
|
| 363 |
+
|
| 364 |
+
app.use('/api/newfeature', newFeatureRoutes);
|
| 365 |
+
```
|
| 366 |
+
|
| 367 |
+
## Performance Considerations
|
| 368 |
+
|
| 369 |
+
### Caching
|
| 370 |
+
- Project structure: 5-minute cache
|
| 371 |
+
- File contents: On-demand (no cache)
|
| 372 |
+
- API responses: Browser cache (Cache-Control headers)
|
| 373 |
+
|
| 374 |
+
### Optimization
|
| 375 |
+
- Analyze only first 5 source files
|
| 376 |
+
- Limit file size for AI analysis
|
| 377 |
+
- Use pagination for large lists
|
| 378 |
+
- Compress API responses
|
| 379 |
+
|
| 380 |
+
### Scaling
|
| 381 |
+
- Separate scanner into background job
|
| 382 |
+
- Use message queue for long-running tasks
|
| 383 |
+
- Cache expensive computations
|
| 384 |
+
- Add rate limiting for API endpoints
|
| 385 |
+
|
| 386 |
+
## Testing
|
| 387 |
+
|
| 388 |
+
Each component can be tested independently:
|
| 389 |
+
|
| 390 |
+
```javascript
|
| 391 |
+
// Test Gemini Service
|
| 392 |
+
const response = await geminiService.analyzeCode(code, 'javascript');
|
| 393 |
+
assert(response.issues !== undefined);
|
| 394 |
+
|
| 395 |
+
// Test GitHub Service
|
| 396 |
+
const issue = await githubService.createIssue({
|
| 397 |
+
title: 'Test',
|
| 398 |
+
body: 'Test body'
|
| 399 |
+
});
|
| 400 |
+
assert(issue.number !== undefined);
|
| 401 |
+
|
| 402 |
+
// Test Project Service
|
| 403 |
+
const structure = await projectService.getProjectStructure();
|
| 404 |
+
assert(structure.files.length > 0);
|
| 405 |
+
|
| 406 |
+
// Test Chat Agent
|
| 407 |
+
const session = chatAgent.startConversation('test-session');
|
| 408 |
+
assert(session.sessionId === 'test-session');
|
| 409 |
+
```
|
| 410 |
+
|
| 411 |
+
## Deployment Architecture
|
| 412 |
+
|
| 413 |
+
```
|
| 414 |
+
┌─────────────────────────────────────────┐
|
| 415 |
+
│ Hugging Face Space (Docker) │
|
| 416 |
+
├─────────────────────────────────────────┤
|
| 417 |
+
│ ┌───────────────────────────────────┐ │
|
| 418 |
+
│ │ Express Server (Port 7860) │ │
|
| 419 |
+
│ ├───────────────────────────────────┤ │
|
| 420 |
+
│ │ Static Files (HTML/CSS/JS) │ │
|
| 421 |
+
│ ├───────────────────────────────────┤ │
|
| 422 |
+
│ │ API Routes │ │
|
| 423 |
+
│ │ ├─ /api/chat │ │
|
| 424 |
+
│ │ ├─ /api/scanner │ │
|
| 425 |
+
│ │ └─ /api/project │ │
|
| 426 |
+
│ ├───────────────────────────────────┤ │
|
| 427 |
+
│ │ Agents │ │
|
| 428 |
+
│ │ ├─ Chat Agent (sessions) │ │
|
| 429 |
+
│ │ └─ Scanner Agent (periodic) │ │
|
| 430 |
+
│ ├───────────────────────────────────┤ │
|
| 431 |
+
│ │ Services │ │
|
| 432 |
+
│ │ ├─ Gemini Service │ │
|
| 433 |
+
│ │ ├─ GitHub Service │ │
|
| 434 |
+
│ │ └─ Project Service │ │
|
| 435 |
+
│ └───────────────────────────────────┘ │
|
| 436 |
+
└─────────────────────────────────────────┘
|
| 437 |
+
│
|
| 438 |
+
├─→ [Google Gemini API]
|
| 439 |
+
├─→ [GitHub API]
|
| 440 |
+
└─→ [Hugging Face API]
|
| 441 |
+
```
|
| 442 |
+
|
| 443 |
+
## Security Best Practices
|
| 444 |
+
|
| 445 |
+
1. **Environment Variables**: Never log or expose API keys
|
| 446 |
+
2. **Input Validation**: Sanitize all user inputs
|
| 447 |
+
3. **Error Handling**: Don't expose stack traces in production
|
| 448 |
+
4. **Rate Limiting**: Add per-IP or per-session limits
|
| 449 |
+
5. **Token Rotation**: Regularly refresh API tokens
|
| 450 |
+
6. **Logging**: Log security-relevant events
|
| 451 |
+
|
| 452 |
+
---
|
| 453 |
+
|
| 454 |
+
**Architecture Version**: 1.0.0
|
| 455 |
+
**Last Updated**: November 17, 2024
|
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Dockerfile for HF Spaces Deployment
|
| 2 |
+
|
| 3 |
+
FROM node:18-alpine
|
| 4 |
+
|
| 5 |
+
# Set working directory
|
| 6 |
+
WORKDIR /app
|
| 7 |
+
|
| 8 |
+
# Copy package files
|
| 9 |
+
COPY package*.json ./
|
| 10 |
+
|
| 11 |
+
# Install dependencies (production only)
|
| 12 |
+
RUN npm ci --omit=dev
|
| 13 |
+
|
| 14 |
+
# Copy application code
|
| 15 |
+
COPY . .
|
| 16 |
+
|
| 17 |
+
# Expose HF Spaces port (7860)
|
| 18 |
+
EXPOSE 7860
|
| 19 |
+
|
| 20 |
+
# Set production environment
|
| 21 |
+
ENV NODE_ENV=production
|
| 22 |
+
ENV PORT=7860
|
| 23 |
+
|
| 24 |
+
# Health check
|
| 25 |
+
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
|
| 26 |
+
CMD node -e "require('http').get('http://localhost:7860/health', (r) => {if (r.statusCode !== 200) throw new Error(r.statusCode)})"
|
| 27 |
+
|
| 28 |
+
# Start application
|
| 29 |
+
CMD ["npm", "start"]
|
|
@@ -0,0 +1,367 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# AI Agent - Installation & Deployment Guide
|
| 2 |
+
|
| 3 |
+
## Table of Contents
|
| 4 |
+
1. [Local Development](#local-development)
|
| 5 |
+
2. [Environment Setup](#environment-setup)
|
| 6 |
+
3. [HF Spaces Deployment](#hf-spaces-deployment)
|
| 7 |
+
4. [API Configuration](#api-configuration)
|
| 8 |
+
5. [Troubleshooting](#troubleshooting)
|
| 9 |
+
|
| 10 |
+
## Local Development
|
| 11 |
+
|
| 12 |
+
### Prerequisites
|
| 13 |
+
- Node.js 18 or higher
|
| 14 |
+
- npm or yarn
|
| 15 |
+
- Git
|
| 16 |
+
- Text editor (VS Code recommended)
|
| 17 |
+
|
| 18 |
+
### Step 1: Install Dependencies
|
| 19 |
+
|
| 20 |
+
```bash
|
| 21 |
+
cd public
|
| 22 |
+
npm install
|
| 23 |
+
```
|
| 24 |
+
|
| 25 |
+
This installs:
|
| 26 |
+
- Express.js (web server)
|
| 27 |
+
- @google/generative-ai (Gemini SDK)
|
| 28 |
+
- axios (HTTP client)
|
| 29 |
+
- cors & body-parser (middleware)
|
| 30 |
+
- dotenv (environment management)
|
| 31 |
+
|
| 32 |
+
### Step 2: Configure Environment
|
| 33 |
+
|
| 34 |
+
Copy `.env.example` to `.env`:
|
| 35 |
+
|
| 36 |
+
```bash
|
| 37 |
+
cp .env.example .env
|
| 38 |
+
```
|
| 39 |
+
|
| 40 |
+
Edit `.env` with your API keys:
|
| 41 |
+
|
| 42 |
+
```env
|
| 43 |
+
GEMINI_API_KEY=sk-...
|
| 44 |
+
GITHUB_TOKEN=ghp_...
|
| 45 |
+
HF_TOKEN=hf_...
|
| 46 |
+
GITHUB_REPO=YourUsername/repo-name
|
| 47 |
+
GITHUB_OWNER=YourUsername
|
| 48 |
+
```
|
| 49 |
+
|
| 50 |
+
### Step 3: Start Development Server
|
| 51 |
+
|
| 52 |
+
```bash
|
| 53 |
+
npm run dev
|
| 54 |
+
```
|
| 55 |
+
|
| 56 |
+
Output:
|
| 57 |
+
```
|
| 58 |
+
[INFO] ... - Server started on port 3000
|
| 59 |
+
```
|
| 60 |
+
|
| 61 |
+
Visit: http://localhost:3000
|
| 62 |
+
|
| 63 |
+
### Step 4: Test the App
|
| 64 |
+
|
| 65 |
+
1. **Chat**: Open "💬 Chat" tab and try `/help` command
|
| 66 |
+
2. **Scanner**: Click "🔍 Run Manual Scan"
|
| 67 |
+
3. **Project**: View "📁 Project" structure
|
| 68 |
+
|
| 69 |
+
## Environment Setup
|
| 70 |
+
|
| 71 |
+
### Getting API Keys
|
| 72 |
+
|
| 73 |
+
#### 1. Google Gemini API Key
|
| 74 |
+
```
|
| 75 |
+
1. Go: https://makersuite.google.com/app/apikey
|
| 76 |
+
2. Click "Create API key"
|
| 77 |
+
3. Copy the key
|
| 78 |
+
4. Paste into .env GEMINI_API_KEY=
|
| 79 |
+
```
|
| 80 |
+
|
| 81 |
+
#### 2. GitHub Personal Access Token
|
| 82 |
+
```
|
| 83 |
+
1. Go: https://github.com/settings/tokens
|
| 84 |
+
2. Click "Generate new token" → "Generate new token (classic)"
|
| 85 |
+
3. Name: "ai-agent-hf"
|
| 86 |
+
4. Select scopes:
|
| 87 |
+
✓ repo (full access)
|
| 88 |
+
✓ workflow (update GitHub Actions)
|
| 89 |
+
✓ gist (optional)
|
| 90 |
+
5. Generate and copy
|
| 91 |
+
6. Paste into .env GITHUB_TOKEN=
|
| 92 |
+
```
|
| 93 |
+
|
| 94 |
+
#### 3. Hugging Face Token
|
| 95 |
+
```
|
| 96 |
+
1. Go: https://huggingface.co/settings/tokens
|
| 97 |
+
2. Create new token
|
| 98 |
+
3. Name: "ai-agent"
|
| 99 |
+
4. Type: "read"
|
| 100 |
+
5. Copy token
|
| 101 |
+
6. Paste into .env HF_TOKEN=
|
| 102 |
+
```
|
| 103 |
+
|
| 104 |
+
### Environment Variables Reference
|
| 105 |
+
|
| 106 |
+
| Variable | Required | Example | Description |
|
| 107 |
+
|----------|----------|---------|-------------|
|
| 108 |
+
| GEMINI_API_KEY | ✓ | sk-... | Google Gemini API key |
|
| 109 |
+
| GITHUB_TOKEN | ✓ | ghp_... | GitHub Personal Access Token |
|
| 110 |
+
| HF_TOKEN | ✓ | hf_... | Hugging Face API token |
|
| 111 |
+
| GITHUB_REPO | ✓ | user/repo | GitHub repository |
|
| 112 |
+
| GITHUB_OWNER | ✓ | user | GitHub username |
|
| 113 |
+
| GITHUB_BRANCH | ✓ | main | Default branch |
|
| 114 |
+
| SCAN_INTERVAL | - | 3600000 | Scanner interval (ms) |
|
| 115 |
+
| ENABLE_AUTO_FIX | - | true | Enable auto-fixing |
|
| 116 |
+
| AUTO_COMMIT | - | false | Auto-commit fixes |
|
| 117 |
+
| PORT | - | 3000 | Server port |
|
| 118 |
+
| LOG_LEVEL | - | info | Logging level |
|
| 119 |
+
|
| 120 |
+
## HF Spaces Deployment
|
| 121 |
+
|
| 122 |
+
### Prerequisites
|
| 123 |
+
- HF Space created on Hugging Face
|
| 124 |
+
- GitHub repository connected
|
| 125 |
+
- All environment variables set in GitHub Secrets
|
| 126 |
+
|
| 127 |
+
### Step 1: Update Dockerfile
|
| 128 |
+
|
| 129 |
+
The `Dockerfile` is already configured for HF Spaces (port 7860).
|
| 130 |
+
|
| 131 |
+
Verify it exists in project root:
|
| 132 |
+
```
|
| 133 |
+
D:\mcp\mcp-2hackathon\Dockerfile
|
| 134 |
+
```
|
| 135 |
+
|
| 136 |
+
### Step 2: Update GitHub Actions Workflow
|
| 137 |
+
|
| 138 |
+
Update `.github/workflows/sync-to-hf.yml`:
|
| 139 |
+
|
| 140 |
+
```yaml
|
| 141 |
+
name: Sync to HF Space
|
| 142 |
+
on:
|
| 143 |
+
push:
|
| 144 |
+
branches: [main]
|
| 145 |
+
paths:
|
| 146 |
+
- 'public/**'
|
| 147 |
+
- 'Dockerfile'
|
| 148 |
+
- '.github/workflows/sync-to-hf.yml'
|
| 149 |
+
|
| 150 |
+
jobs:
|
| 151 |
+
sync:
|
| 152 |
+
runs-on: ubuntu-latest
|
| 153 |
+
steps:
|
| 154 |
+
- uses: actions/checkout@v3
|
| 155 |
+
with:
|
| 156 |
+
fetch-depth: 0
|
| 157 |
+
|
| 158 |
+
- name: Push to HF Space
|
| 159 |
+
run: |
|
| 160 |
+
git remote add hf https://${{ secrets.HF_USERNAME }}:${{ secrets.HF_TOKEN }}@huggingface.co/spaces/${{ secrets.HF_USERNAME }}/${{ secrets.HF_SPACE_NAME }}
|
| 161 |
+
git push hf HEAD:main --force
|
| 162 |
+
```
|
| 163 |
+
|
| 164 |
+
### Step 3: Set GitHub Secrets
|
| 165 |
+
|
| 166 |
+
In GitHub repo → Settings → Secrets and variables → Actions:
|
| 167 |
+
|
| 168 |
+
1. **HF_TOKEN**
|
| 169 |
+
- Value: Your HF token
|
| 170 |
+
- Use the token from HF Settings
|
| 171 |
+
|
| 172 |
+
2. **HF_USERNAME**
|
| 173 |
+
- Value: Your HF username
|
| 174 |
+
- Example: `NLarchive`
|
| 175 |
+
|
| 176 |
+
3. **HF_SPACE_NAME**
|
| 177 |
+
- Value: Your HF Space name
|
| 178 |
+
- Example: `my-webapp-hf`
|
| 179 |
+
|
| 180 |
+
4. **GEMINI_API_KEY** (optional, if using in GitHub Actions)
|
| 181 |
+
|
| 182 |
+
5. **GITHUB_TOKEN** (GitHub provides automatically)
|
| 183 |
+
|
| 184 |
+
### Step 4: Deploy
|
| 185 |
+
|
| 186 |
+
```bash
|
| 187 |
+
# Commit and push changes
|
| 188 |
+
git add .
|
| 189 |
+
git commit -m "feat: add ai agent to hf space"
|
| 190 |
+
git push origin main
|
| 191 |
+
```
|
| 192 |
+
|
| 193 |
+
GitHub Actions will automatically:
|
| 194 |
+
1. Build Docker image
|
| 195 |
+
2. Push to HF Space
|
| 196 |
+
3. Start the service on HF Spaces
|
| 197 |
+
|
| 198 |
+
Access at: `https://huggingface.co/spaces/YourUsername/your-space`
|
| 199 |
+
|
| 200 |
+
## API Configuration
|
| 201 |
+
|
| 202 |
+
### Base URL
|
| 203 |
+
- Local: `http://localhost:3000`
|
| 204 |
+
- HF Spaces: `https://huggingface.co/spaces/YourUsername/your-space`
|
| 205 |
+
|
| 206 |
+
### Authentication
|
| 207 |
+
All API calls include `Content-Type: application/json` header.
|
| 208 |
+
|
| 209 |
+
### Health Check
|
| 210 |
+
```bash
|
| 211 |
+
curl http://localhost:3000/health
|
| 212 |
+
```
|
| 213 |
+
|
| 214 |
+
Response:
|
| 215 |
+
```json
|
| 216 |
+
{
|
| 217 |
+
"status": "ok",
|
| 218 |
+
"timestamp": "2024-11-17T...",
|
| 219 |
+
"uptime": 123.45
|
| 220 |
+
}
|
| 221 |
+
```
|
| 222 |
+
|
| 223 |
+
### Example: Send Chat Message
|
| 224 |
+
|
| 225 |
+
```bash
|
| 226 |
+
# 1. Start conversation
|
| 227 |
+
curl -X POST http://localhost:3000/api/chat/start
|
| 228 |
+
|
| 229 |
+
# Response:
|
| 230 |
+
# {
|
| 231 |
+
# "success": true,
|
| 232 |
+
# "sessionId": "session_1234_xyz"
|
| 233 |
+
# }
|
| 234 |
+
|
| 235 |
+
# 2. Send message
|
| 236 |
+
curl -X POST http://localhost:3000/api/chat/message \
|
| 237 |
+
-H "Content-Type: application/json" \
|
| 238 |
+
-d '{
|
| 239 |
+
"sessionId": "session_1234_xyz",
|
| 240 |
+
"message": "What is this project?"
|
| 241 |
+
}'
|
| 242 |
+
|
| 243 |
+
# Response:
|
| 244 |
+
# {
|
| 245 |
+
# "success": true,
|
| 246 |
+
# "response": "Based on your project structure..."
|
| 247 |
+
# }
|
| 248 |
+
```
|
| 249 |
+
|
| 250 |
+
### Example: Run Scanner
|
| 251 |
+
|
| 252 |
+
```bash
|
| 253 |
+
curl -X POST http://localhost:3000/api/scanner/scan
|
| 254 |
+
|
| 255 |
+
# Response:
|
| 256 |
+
# {
|
| 257 |
+
# "success": true,
|
| 258 |
+
# "report": {
|
| 259 |
+
# "timestamp": "...",
|
| 260 |
+
# "duration": 1234,
|
| 261 |
+
# "issues": [...],
|
| 262 |
+
# "analysis": {...},
|
| 263 |
+
# "recommendations": [...]
|
| 264 |
+
# }
|
| 265 |
+
# }
|
| 266 |
+
```
|
| 267 |
+
|
| 268 |
+
## Troubleshooting
|
| 269 |
+
|
| 270 |
+
### Port Already in Use
|
| 271 |
+
```bash
|
| 272 |
+
# Windows PowerShell
|
| 273 |
+
Get-Process -Id (Get-NetTCPConnection -LocalPort 3000).OwningProcess
|
| 274 |
+
|
| 275 |
+
# Kill the process
|
| 276 |
+
Kill-Process -Id <PID> -Force
|
| 277 |
+
```
|
| 278 |
+
|
| 279 |
+
### API Key Invalid
|
| 280 |
+
```
|
| 281 |
+
Error: API key not valid
|
| 282 |
+
```
|
| 283 |
+
|
| 284 |
+
Solution:
|
| 285 |
+
1. Verify key in `.env` file
|
| 286 |
+
2. Check key hasn't expired
|
| 287 |
+
3. Test key directly: `curl https://generativelanguage.googleapis.com/v1/models/gemini-pro:generateContent?key=YOUR_KEY`
|
| 288 |
+
|
| 289 |
+
### GitHub Connection Failed
|
| 290 |
+
```
|
| 291 |
+
Error: Failed to create GitHub issue
|
| 292 |
+
```
|
| 293 |
+
|
| 294 |
+
Solution:
|
| 295 |
+
1. Verify `GITHUB_TOKEN` in `.env`
|
| 296 |
+
2. Check token has `repo` and `issues` scope
|
| 297 |
+
3. Verify `GITHUB_REPO` format: `owner/repo`
|
| 298 |
+
|
| 299 |
+
### Scanner Not Running
|
| 300 |
+
```
|
| 301 |
+
Scan failed: ...
|
| 302 |
+
```
|
| 303 |
+
|
| 304 |
+
Solution:
|
| 305 |
+
1. Check `ENABLE_AUTO_FIX=true` in `.env`
|
| 306 |
+
2. Verify GitHub token and permissions
|
| 307 |
+
3. Check server logs: `npm run dev` (development mode shows detailed logs)
|
| 308 |
+
|
| 309 |
+
### Slow Responses
|
| 310 |
+
```
|
| 311 |
+
Chat timeout or slow responses
|
| 312 |
+
```
|
| 313 |
+
|
| 314 |
+
Solutions:
|
| 315 |
+
1. Check internet connection
|
| 316 |
+
2. Verify API keys are valid
|
| 317 |
+
3. Increase timeout: Edit `src/config/env.js` → increase timeouts
|
| 318 |
+
4. Use simpler prompts to reduce processing time
|
| 319 |
+
|
| 320 |
+
### HF Space Build Failing
|
| 321 |
+
|
| 322 |
+
Check logs:
|
| 323 |
+
```
|
| 324 |
+
GitHub → Actions → View workflow run → HF Space build logs
|
| 325 |
+
```
|
| 326 |
+
|
| 327 |
+
Common issues:
|
| 328 |
+
- `npm install` failing: Check `package.json` syntax
|
| 329 |
+
- Port 7860 not exposed: Verify `Dockerfile` EXPOSE 7860
|
| 330 |
+
- Environment variables missing: Check GitHub Secrets
|
| 331 |
+
|
| 332 |
+
### Local Testing Before Deployment
|
| 333 |
+
|
| 334 |
+
```bash
|
| 335 |
+
# 1. Test with Docker locally
|
| 336 |
+
docker build -t ai-agent .
|
| 337 |
+
docker run -p 3000:7860 \
|
| 338 |
+
-e GEMINI_API_KEY=your_key \
|
| 339 |
+
-e GITHUB_TOKEN=your_token \
|
| 340 |
+
ai-agent
|
| 341 |
+
|
| 342 |
+
# 2. Access at http://localhost:3000
|
| 343 |
+
|
| 344 |
+
# 3. Check container logs
|
| 345 |
+
docker logs <container_id>
|
| 346 |
+
```
|
| 347 |
+
|
| 348 |
+
## Next Steps
|
| 349 |
+
|
| 350 |
+
1. ✅ Set up local environment
|
| 351 |
+
2. ✅ Configure API keys
|
| 352 |
+
3. ✅ Test locally
|
| 353 |
+
4. ✅ Deploy to HF Spaces
|
| 354 |
+
5. 🔄 Monitor scanner results
|
| 355 |
+
6. 🔄 Collect user feedback
|
| 356 |
+
7. 🔄 Add custom scanners/commands
|
| 357 |
+
|
| 358 |
+
## Support & Resources
|
| 359 |
+
|
| 360 |
+
- **Google Gemini**: https://ai.google.dev/
|
| 361 |
+
- **GitHub API**: https://docs.github.com/en/rest
|
| 362 |
+
- **HF Spaces**: https://huggingface.co/docs/hub/spaces
|
| 363 |
+
- **Node.js**: https://nodejs.org/docs/
|
| 364 |
+
|
| 365 |
+
---
|
| 366 |
+
|
| 367 |
+
**Last Updated**: November 17, 2024
|
|
@@ -0,0 +1,296 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# AI Agent for HF Spaces
|
| 2 |
+
|
| 3 |
+
A modular, scalable AI agent system that runs on Hugging Face Spaces with:
|
| 4 |
+
- **Periodic Issue Scanner** - Scans your project hourly for issues and suggests fixes
|
| 5 |
+
- **Chat Interface** - Talk to Gemini AI about your project in real-time
|
| 6 |
+
- **GitHub Integration** - Creates issues and PRs automatically
|
| 7 |
+
- **Microservices Architecture** - Modular, maintainable, extensible
|
| 8 |
+
|
| 9 |
+
## Features
|
| 10 |
+
|
| 11 |
+
### 🤖 AI Chat Agent
|
| 12 |
+
- Multi-turn conversations with Google Gemini
|
| 13 |
+
- Context-aware responses about your project
|
| 14 |
+
- Commands: `/scan`, `/status`, `/issues`, `/help`
|
| 15 |
+
- Session management and conversation history
|
| 16 |
+
|
| 17 |
+
### 🔍 Project Scanner
|
| 18 |
+
- Hourly automated scans (configurable interval)
|
| 19 |
+
- Detects:
|
| 20 |
+
- Missing critical files
|
| 21 |
+
- Security issues (exposed .env, etc)
|
| 22 |
+
- Code quality problems
|
| 23 |
+
- Dependency vulnerabilities
|
| 24 |
+
- AI-powered recommendations
|
| 25 |
+
- Auto-creates GitHub issues for critical findings
|
| 26 |
+
|
| 27 |
+
### 📁 Project Explorer
|
| 28 |
+
- View project structure
|
| 29 |
+
- Display README.md
|
| 30 |
+
- Show Dockerfile
|
| 31 |
+
- List source files with language detection
|
| 32 |
+
|
| 33 |
+
### 🔗 GitHub Integration
|
| 34 |
+
- Read repository structure
|
| 35 |
+
- Create issues from scan results
|
| 36 |
+
- Create pull requests with fixes
|
| 37 |
+
- Trigger GitHub Actions workflows
|
| 38 |
+
- List open issues
|
| 39 |
+
|
| 40 |
+
## Architecture
|
| 41 |
+
|
| 42 |
+
```
|
| 43 |
+
public/
|
| 44 |
+
├── src/
|
| 45 |
+
│ ├── server.js # Express server entry point
|
| 46 |
+
│ ├── config/
|
| 47 |
+
│ │ ├── env.js # Configuration management
|
| 48 |
+
│ │ └── logger.js # Logging service
|
| 49 |
+
│ ├── services/
|
| 50 |
+
│ │ ├── gemini.js # Google Generative AI service
|
| 51 |
+
│ │ ├── github.js # GitHub API service
|
| 52 |
+
│ │ └── project.js # Project analysis service
|
| 53 |
+
│ ├── agents/
|
| 54 |
+
│ │ ├── scanner.js # Periodic scanner agent
|
| 55 |
+
│ │ └── chat.js # Chat agent
|
| 56 |
+
│ ├── api/
|
| 57 |
+
│ │ ├── chat.js # Chat routes
|
| 58 |
+
│ │ ├── scanner.js # Scanner routes
|
| 59 |
+
│ │ └── project.js # Project routes
|
| 60 |
+
│ └── utils/
|
| 61 |
+
│ └── taskRunner.js # Task execution framework
|
| 62 |
+
├── assets/
|
| 63 |
+
│ ├── css/
|
| 64 |
+
│ │ └── styles.css # Responsive UI styles
|
| 65 |
+
│ └── js/
|
| 66 |
+
│ ├── app.js # Main app initialization
|
| 67 |
+
│ └── modules/
|
| 68 |
+
│ ├── chat.js # Chat UI manager
|
| 69 |
+
│ ├── scanner.js # Scanner UI manager
|
| 70 |
+
│ ├── project.js # Project UI manager
|
| 71 |
+
│ ├── ui.js # UI utilities
|
| 72 |
+
│ └── api-client.js # HTTP client
|
| 73 |
+
├── index.html # Main UI
|
| 74 |
+
└── package.json # Dependencies
|
| 75 |
+
```
|
| 76 |
+
|
| 77 |
+
## Installation & Setup
|
| 78 |
+
|
| 79 |
+
### 1. Prerequisites
|
| 80 |
+
- Node.js 18+
|
| 81 |
+
- Google Gemini API Key
|
| 82 |
+
- GitHub Token (Personal Access Token)
|
| 83 |
+
- Docker (for HF Spaces deployment)
|
| 84 |
+
|
| 85 |
+
### 2. Environment Variables
|
| 86 |
+
|
| 87 |
+
Create a `.env` file in the `public/` directory:
|
| 88 |
+
|
| 89 |
+
```bash
|
| 90 |
+
# API Keys
|
| 91 |
+
GEMINI_API_KEY=your_gemini_api_key_here
|
| 92 |
+
GITHUB_TOKEN=your_github_token_here
|
| 93 |
+
HF_TOKEN=your_hugging_face_token_here
|
| 94 |
+
|
| 95 |
+
# GitHub Configuration
|
| 96 |
+
GITHUB_REPO=YourUsername/your-repo
|
| 97 |
+
GITHUB_OWNER=YourUsername
|
| 98 |
+
GITHUB_BRANCH=main
|
| 99 |
+
|
| 100 |
+
# HF Space Configuration
|
| 101 |
+
HF_SPACE_NAME=your-space-name
|
| 102 |
+
HF_SPACE_URL=https://huggingface.co/spaces/YourUsername/your-space
|
| 103 |
+
|
| 104 |
+
# Scanner Configuration (milliseconds)
|
| 105 |
+
SCAN_INTERVAL=3600000 # 1 hour
|
| 106 |
+
ENABLE_AUTO_FIX=true
|
| 107 |
+
AUTO_COMMIT=false
|
| 108 |
+
|
| 109 |
+
# Server
|
| 110 |
+
PORT=3000
|
| 111 |
+
NODE_ENV=production
|
| 112 |
+
LOG_LEVEL=info
|
| 113 |
+
DEBUG=false
|
| 114 |
+
```
|
| 115 |
+
|
| 116 |
+
### 3. Install Dependencies
|
| 117 |
+
|
| 118 |
+
```bash
|
| 119 |
+
cd public
|
| 120 |
+
npm install
|
| 121 |
+
```
|
| 122 |
+
|
| 123 |
+
### 4. Start Server
|
| 124 |
+
|
| 125 |
+
**Development:**
|
| 126 |
+
```bash
|
| 127 |
+
npm run dev
|
| 128 |
+
```
|
| 129 |
+
|
| 130 |
+
**Production:**
|
| 131 |
+
```bash
|
| 132 |
+
npm start
|
| 133 |
+
```
|
| 134 |
+
|
| 135 |
+
### 5. Access the App
|
| 136 |
+
|
| 137 |
+
Open http://localhost:3000 in your browser
|
| 138 |
+
|
| 139 |
+
## API Endpoints
|
| 140 |
+
|
| 141 |
+
### Chat API
|
| 142 |
+
- `POST /api/chat/start` - Start new conversation
|
| 143 |
+
- `POST /api/chat/message` - Send message and get response
|
| 144 |
+
- `GET /api/chat/history/:sessionId` - Get conversation history
|
| 145 |
+
- `DELETE /api/chat/clear/:sessionId` - Clear conversation
|
| 146 |
+
- `GET /api/chat/stats` - Get chat statistics
|
| 147 |
+
|
| 148 |
+
### Scanner API
|
| 149 |
+
- `POST /api/scanner/scan` - Trigger manual scan
|
| 150 |
+
- `GET /api/scanner/last-report` - Get last scan report
|
| 151 |
+
- `GET /api/scanner/issues` - Get detected issues
|
| 152 |
+
- `POST /api/scanner/start-continuous` - Start continuous scanning
|
| 153 |
+
- `POST /api/scanner/stop-continuous` - Stop continuous scanning
|
| 154 |
+
|
| 155 |
+
### Project API
|
| 156 |
+
- `GET /api/project/structure` - Get project structure
|
| 157 |
+
- `GET /api/project/file?path=<filepath>` - Get file content
|
| 158 |
+
- `GET /api/project/readme` - Get README
|
| 159 |
+
- `GET /api/project/dockerfile` - Get Dockerfile
|
| 160 |
+
- `GET /api/project/source-files` - List source files
|
| 161 |
+
|
| 162 |
+
## Usage Examples
|
| 163 |
+
|
| 164 |
+
### Chat with AI
|
| 165 |
+
1. Navigate to "Chat" tab
|
| 166 |
+
2. Ask questions:
|
| 167 |
+
- "What are the main components?"
|
| 168 |
+
- "How do I deploy this?"
|
| 169 |
+
- "Analyze the Dockerfile"
|
| 170 |
+
3. Use commands:
|
| 171 |
+
- `/scan` - Run project scan
|
| 172 |
+
- `/status` - Get repo status
|
| 173 |
+
- `/issues` - List open issues
|
| 174 |
+
- `/help` - Show available commands
|
| 175 |
+
|
| 176 |
+
### Periodic Scanning
|
| 177 |
+
1. Navigate to "Scanner" tab
|
| 178 |
+
2. Click "Start Auto-Scan (1 hour)" to enable hourly scans
|
| 179 |
+
3. Or click "Run Manual Scan" for immediate scan
|
| 180 |
+
4. View detected issues and recommendations
|
| 181 |
+
5. Scanner auto-creates GitHub issues for critical findings
|
| 182 |
+
|
| 183 |
+
### Project Exploration
|
| 184 |
+
1. Navigate to "Project" tab
|
| 185 |
+
2. View directory structure
|
| 186 |
+
3. Read project documentation
|
| 187 |
+
4. Examine Dockerfile configuration
|
| 188 |
+
|
| 189 |
+
## Docker Deployment (HF Spaces)
|
| 190 |
+
|
| 191 |
+
The project includes a Dockerfile configured for HF Spaces:
|
| 192 |
+
|
| 193 |
+
```dockerfile
|
| 194 |
+
FROM node:18-alpine
|
| 195 |
+
|
| 196 |
+
WORKDIR /app
|
| 197 |
+
COPY public /app
|
| 198 |
+
|
| 199 |
+
RUN npm ci --omit=dev
|
| 200 |
+
|
| 201 |
+
EXPOSE 7860
|
| 202 |
+
|
| 203 |
+
CMD ["npm", "start"]
|
| 204 |
+
```
|
| 205 |
+
|
| 206 |
+
To deploy to HF Spaces:
|
| 207 |
+
1. Push to GitHub
|
| 208 |
+
2. GitHub Actions syncs to HF Space automatically
|
| 209 |
+
3. HF Space builds Docker image
|
| 210 |
+
4. App available at: https://huggingface.co/spaces/YourUsername/your-space
|
| 211 |
+
|
| 212 |
+
## Security Considerations
|
| 213 |
+
|
| 214 |
+
✅ **What's Protected:**
|
| 215 |
+
- API keys stored in environment variables
|
| 216 |
+
- GitHub token in GitHub Secrets
|
| 217 |
+
- Conversation history in-memory (not persisted)
|
| 218 |
+
- User input sanitization
|
| 219 |
+
|
| 220 |
+
⚠️ **When Deploying:**
|
| 221 |
+
- Never commit `.env` file
|
| 222 |
+
- Use strong GitHub tokens with limited scope
|
| 223 |
+
- Review auto-fix changes before auto-committing
|
| 224 |
+
- Monitor scan results for accuracy
|
| 225 |
+
|
| 226 |
+
## Performance
|
| 227 |
+
|
| 228 |
+
- **Chat Response:** ~2-5 seconds (depends on prompt complexity)
|
| 229 |
+
- **Project Scan:** ~30-60 seconds (depends on project size)
|
| 230 |
+
- **Memory Usage:** ~150-250MB at rest
|
| 231 |
+
- **Scanner Interval:** Configurable (default 1 hour)
|
| 232 |
+
|
| 233 |
+
## Customization
|
| 234 |
+
|
| 235 |
+
### Add Custom Scanners
|
| 236 |
+
Extend `src/agents/scanner.js`:
|
| 237 |
+
```javascript
|
| 238 |
+
async analyzeProject() {
|
| 239 |
+
// Add custom analysis
|
| 240 |
+
}
|
| 241 |
+
```
|
| 242 |
+
|
| 243 |
+
### Add Custom Commands
|
| 244 |
+
Extend `src/agents/chat.js`:
|
| 245 |
+
```javascript
|
| 246 |
+
if (message.startsWith('/custom')) {
|
| 247 |
+
return this.handleCustomCommand();
|
| 248 |
+
}
|
| 249 |
+
```
|
| 250 |
+
|
| 251 |
+
### Modify UI
|
| 252 |
+
Edit `public/assets/css/styles.css` for theming
|
| 253 |
+
Edit `public/index.html` for layout changes
|
| 254 |
+
|
| 255 |
+
## Troubleshooting
|
| 256 |
+
|
| 257 |
+
**API Keys Not Working?**
|
| 258 |
+
- Verify keys in `.env` file
|
| 259 |
+
- Check GitHub Secrets on GitHub Actions
|
| 260 |
+
|
| 261 |
+
**Scanner Not Running?**
|
| 262 |
+
- Check `ENABLE_AUTO_FIX=true` in `.env`
|
| 263 |
+
- Verify `SCAN_INTERVAL` is in milliseconds
|
| 264 |
+
- Check server logs: `npm run dev`
|
| 265 |
+
|
| 266 |
+
**Chat Responses Are Slow?**
|
| 267 |
+
- Increase timeout in `src/config/env.js`
|
| 268 |
+
- Reduce context size in `ChatAgent.buildAIContext()`
|
| 269 |
+
|
| 270 |
+
**Issues Not Created on GitHub?**
|
| 271 |
+
- Verify `GITHUB_TOKEN` has `repo` and `issues` scope
|
| 272 |
+
- Check GitHub Secrets configuration
|
| 273 |
+
|
| 274 |
+
## Development
|
| 275 |
+
|
| 276 |
+
```bash
|
| 277 |
+
# Start in watch mode
|
| 278 |
+
npm run dev
|
| 279 |
+
|
| 280 |
+
# Check server health
|
| 281 |
+
curl http://localhost:3000/health
|
| 282 |
+
|
| 283 |
+
# Run scanner agent directly
|
| 284 |
+
node src/agents/scanner.js
|
| 285 |
+
```
|
| 286 |
+
|
| 287 |
+
## License
|
| 288 |
+
|
| 289 |
+
MIT
|
| 290 |
+
|
| 291 |
+
## Support
|
| 292 |
+
|
| 293 |
+
For issues or questions:
|
| 294 |
+
1. Check the `/help` command in chat
|
| 295 |
+
2. Review project documentation
|
| 296 |
+
3. Create an issue on GitHub
|
|
@@ -1,14 +0,0 @@
|
|
| 1 |
-
<?php
|
| 2 |
-
header('Content-Type: application/json');
|
| 3 |
-
header('Access-Control-Allow-Origin: *');
|
| 4 |
-
|
| 5 |
-
$response = [
|
| 6 |
-
'message' => 'PHP is working correctly! ✓',
|
| 7 |
-
'timestamp' => date('Y-m-d H:i:s'),
|
| 8 |
-
'random' => rand(1, 1000),
|
| 9 |
-
'php_version' => phpversion(),
|
| 10 |
-
'server' => $_SERVER['SERVER_SOFTWARE'] ?? 'Unknown'
|
| 11 |
-
];
|
| 12 |
-
|
| 13 |
-
echo json_encode($response);
|
| 14 |
-
?>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -0,0 +1,732 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Main Styles
|
| 3 |
+
* Responsive design for AI Agent interface
|
| 4 |
+
*/
|
| 5 |
+
|
| 6 |
+
:root {
|
| 7 |
+
--primary-color: #667eea;
|
| 8 |
+
--secondary-color: #764ba2;
|
| 9 |
+
--success-color: #48bb78;
|
| 10 |
+
--warning-color: #ed8936;
|
| 11 |
+
--error-color: #f56565;
|
| 12 |
+
--bg-dark: #1a202c;
|
| 13 |
+
--bg-light: #f7fafc;
|
| 14 |
+
--text-dark: #2d3748;
|
| 15 |
+
--text-light: #718096;
|
| 16 |
+
--border-color: #e2e8f0;
|
| 17 |
+
--sidebar-width: 250px;
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
* {
|
| 21 |
+
margin: 0;
|
| 22 |
+
padding: 0;
|
| 23 |
+
box-sizing: border-box;
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
html, body {
|
| 27 |
+
height: 100%;
|
| 28 |
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
| 29 |
+
background: var(--bg-light);
|
| 30 |
+
color: var(--text-dark);
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
body {
|
| 34 |
+
overflow: hidden;
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
.app {
|
| 38 |
+
display: flex;
|
| 39 |
+
height: 100vh;
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
/* Sidebar */
|
| 43 |
+
.sidebar {
|
| 44 |
+
width: var(--sidebar-width);
|
| 45 |
+
background: var(--bg-dark);
|
| 46 |
+
color: white;
|
| 47 |
+
padding: 20px;
|
| 48 |
+
display: flex;
|
| 49 |
+
flex-direction: column;
|
| 50 |
+
border-right: 1px solid var(--border-color);
|
| 51 |
+
overflow-y: auto;
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
.logo {
|
| 55 |
+
margin-bottom: 30px;
|
| 56 |
+
text-align: center;
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
.logo h1 {
|
| 60 |
+
font-size: 20px;
|
| 61 |
+
font-weight: 600;
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
.nav-menu {
|
| 65 |
+
flex: 1;
|
| 66 |
+
display: flex;
|
| 67 |
+
flex-direction: column;
|
| 68 |
+
gap: 10px;
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
.nav-item {
|
| 72 |
+
background: transparent;
|
| 73 |
+
border: none;
|
| 74 |
+
color: var(--text-light);
|
| 75 |
+
padding: 12px 16px;
|
| 76 |
+
border-radius: 8px;
|
| 77 |
+
cursor: pointer;
|
| 78 |
+
font-size: 14px;
|
| 79 |
+
font-weight: 500;
|
| 80 |
+
transition: all 0.2s;
|
| 81 |
+
text-align: left;
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
.nav-item:hover {
|
| 85 |
+
background: rgba(255, 255, 255, 0.1);
|
| 86 |
+
color: white;
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
.nav-item.active {
|
| 90 |
+
background: var(--primary-color);
|
| 91 |
+
color: white;
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
.sidebar-footer {
|
| 95 |
+
padding-top: 20px;
|
| 96 |
+
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
.status {
|
| 100 |
+
display: flex;
|
| 101 |
+
align-items: center;
|
| 102 |
+
gap: 8px;
|
| 103 |
+
font-size: 14px;
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
.status-dot {
|
| 107 |
+
width: 8px;
|
| 108 |
+
height: 8px;
|
| 109 |
+
border-radius: 50%;
|
| 110 |
+
background: var(--error-color);
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
.status-dot.online {
|
| 114 |
+
background: var(--success-color);
|
| 115 |
+
animation: pulse 2s infinite;
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
@keyframes pulse {
|
| 119 |
+
0%, 100% { opacity: 1; }
|
| 120 |
+
50% { opacity: 0.5; }
|
| 121 |
+
}
|
| 122 |
+
|
| 123 |
+
/* Main */
|
| 124 |
+
.main {
|
| 125 |
+
flex: 1;
|
| 126 |
+
display: flex;
|
| 127 |
+
overflow: hidden;
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
.tab-content {
|
| 131 |
+
display: none;
|
| 132 |
+
width: 100%;
|
| 133 |
+
height: 100%;
|
| 134 |
+
overflow-y: auto;
|
| 135 |
+
padding: 30px;
|
| 136 |
+
}
|
| 137 |
+
|
| 138 |
+
.tab-content.active {
|
| 139 |
+
display: block;
|
| 140 |
+
}
|
| 141 |
+
|
| 142 |
+
/* Chat */
|
| 143 |
+
.chat-container {
|
| 144 |
+
display: flex;
|
| 145 |
+
flex-direction: column;
|
| 146 |
+
height: 100%;
|
| 147 |
+
max-width: 1000px;
|
| 148 |
+
margin: 0 auto;
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
.chat-header {
|
| 152 |
+
margin-bottom: 20px;
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
.chat-header h2 {
|
| 156 |
+
font-size: 24px;
|
| 157 |
+
margin-bottom: 5px;
|
| 158 |
+
}
|
| 159 |
+
|
| 160 |
+
.chat-header p {
|
| 161 |
+
color: var(--text-light);
|
| 162 |
+
}
|
| 163 |
+
|
| 164 |
+
.chat-messages {
|
| 165 |
+
flex: 1;
|
| 166 |
+
overflow-y: auto;
|
| 167 |
+
margin-bottom: 20px;
|
| 168 |
+
padding: 20px;
|
| 169 |
+
background: white;
|
| 170 |
+
border-radius: 12px;
|
| 171 |
+
border: 1px solid var(--border-color);
|
| 172 |
+
}
|
| 173 |
+
|
| 174 |
+
.message {
|
| 175 |
+
margin-bottom: 15px;
|
| 176 |
+
animation: slideIn 0.3s ease-out;
|
| 177 |
+
}
|
| 178 |
+
|
| 179 |
+
@keyframes slideIn {
|
| 180 |
+
from {
|
| 181 |
+
opacity: 0;
|
| 182 |
+
transform: translateY(10px);
|
| 183 |
+
}
|
| 184 |
+
to {
|
| 185 |
+
opacity: 1;
|
| 186 |
+
transform: translateY(0);
|
| 187 |
+
}
|
| 188 |
+
}
|
| 189 |
+
|
| 190 |
+
.message-user {
|
| 191 |
+
display: flex;
|
| 192 |
+
justify-content: flex-end;
|
| 193 |
+
}
|
| 194 |
+
|
| 195 |
+
.message-user .message-content {
|
| 196 |
+
background: var(--primary-color);
|
| 197 |
+
color: white;
|
| 198 |
+
padding: 12px 16px;
|
| 199 |
+
border-radius: 12px;
|
| 200 |
+
max-width: 70%;
|
| 201 |
+
}
|
| 202 |
+
|
| 203 |
+
.message-assistant .message-content {
|
| 204 |
+
background: var(--bg-light);
|
| 205 |
+
padding: 12px 16px;
|
| 206 |
+
border-radius: 12px;
|
| 207 |
+
max-width: 70%;
|
| 208 |
+
border: 1px solid var(--border-color);
|
| 209 |
+
}
|
| 210 |
+
|
| 211 |
+
.message-system {
|
| 212 |
+
text-align: center;
|
| 213 |
+
color: var(--text-light);
|
| 214 |
+
font-size: 12px;
|
| 215 |
+
margin: 10px 0;
|
| 216 |
+
}
|
| 217 |
+
|
| 218 |
+
.system-message {
|
| 219 |
+
display: inline-block;
|
| 220 |
+
background: var(--bg-light);
|
| 221 |
+
padding: 8px 12px;
|
| 222 |
+
border-radius: 6px;
|
| 223 |
+
}
|
| 224 |
+
|
| 225 |
+
.message-text {
|
| 226 |
+
line-height: 1.5;
|
| 227 |
+
}
|
| 228 |
+
|
| 229 |
+
.message-text code {
|
| 230 |
+
background: rgba(0, 0, 0, 0.1);
|
| 231 |
+
padding: 2px 6px;
|
| 232 |
+
border-radius: 4px;
|
| 233 |
+
font-family: 'Courier New', monospace;
|
| 234 |
+
font-size: 12px;
|
| 235 |
+
}
|
| 236 |
+
|
| 237 |
+
.message-time {
|
| 238 |
+
font-size: 11px;
|
| 239 |
+
opacity: 0.7;
|
| 240 |
+
margin-top: 5px;
|
| 241 |
+
}
|
| 242 |
+
|
| 243 |
+
.chat-input-area {
|
| 244 |
+
display: flex;
|
| 245 |
+
flex-direction: column;
|
| 246 |
+
gap: 12px;
|
| 247 |
+
}
|
| 248 |
+
|
| 249 |
+
.quick-commands {
|
| 250 |
+
display: flex;
|
| 251 |
+
gap: 8px;
|
| 252 |
+
flex-wrap: wrap;
|
| 253 |
+
}
|
| 254 |
+
|
| 255 |
+
.command-btn {
|
| 256 |
+
padding: 8px 12px;
|
| 257 |
+
background: white;
|
| 258 |
+
border: 1px solid var(--border-color);
|
| 259 |
+
border-radius: 6px;
|
| 260 |
+
cursor: pointer;
|
| 261 |
+
font-size: 12px;
|
| 262 |
+
transition: all 0.2s;
|
| 263 |
+
}
|
| 264 |
+
|
| 265 |
+
.command-btn:hover {
|
| 266 |
+
background: var(--bg-light);
|
| 267 |
+
border-color: var(--primary-color);
|
| 268 |
+
}
|
| 269 |
+
|
| 270 |
+
#chat-form {
|
| 271 |
+
display: flex;
|
| 272 |
+
gap: 10px;
|
| 273 |
+
}
|
| 274 |
+
|
| 275 |
+
#message-input {
|
| 276 |
+
flex: 1;
|
| 277 |
+
padding: 12px 16px;
|
| 278 |
+
border: 1px solid var(--border-color);
|
| 279 |
+
border-radius: 8px;
|
| 280 |
+
font-size: 14px;
|
| 281 |
+
outline: none;
|
| 282 |
+
transition: border-color 0.2s;
|
| 283 |
+
}
|
| 284 |
+
|
| 285 |
+
#message-input:focus {
|
| 286 |
+
border-color: var(--primary-color);
|
| 287 |
+
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
|
| 288 |
+
}
|
| 289 |
+
|
| 290 |
+
.send-btn {
|
| 291 |
+
padding: 12px 24px;
|
| 292 |
+
background: var(--primary-color);
|
| 293 |
+
color: white;
|
| 294 |
+
border: none;
|
| 295 |
+
border-radius: 8px;
|
| 296 |
+
cursor: pointer;
|
| 297 |
+
font-weight: 600;
|
| 298 |
+
transition: all 0.2s;
|
| 299 |
+
}
|
| 300 |
+
|
| 301 |
+
.send-btn:hover {
|
| 302 |
+
background: var(--secondary-color);
|
| 303 |
+
transform: translateY(-2px);
|
| 304 |
+
}
|
| 305 |
+
|
| 306 |
+
.send-btn:active {
|
| 307 |
+
transform: translateY(0);
|
| 308 |
+
}
|
| 309 |
+
|
| 310 |
+
/* Section Headers */
|
| 311 |
+
.section-header {
|
| 312 |
+
margin-bottom: 30px;
|
| 313 |
+
}
|
| 314 |
+
|
| 315 |
+
.section-header h2 {
|
| 316 |
+
font-size: 24px;
|
| 317 |
+
margin-bottom: 5px;
|
| 318 |
+
}
|
| 319 |
+
|
| 320 |
+
.section-header p {
|
| 321 |
+
color: var(--text-light);
|
| 322 |
+
}
|
| 323 |
+
|
| 324 |
+
/* Scanner */
|
| 325 |
+
.scanner-controls {
|
| 326 |
+
display: flex;
|
| 327 |
+
gap: 12px;
|
| 328 |
+
margin-bottom: 30px;
|
| 329 |
+
}
|
| 330 |
+
|
| 331 |
+
.btn {
|
| 332 |
+
padding: 12px 24px;
|
| 333 |
+
border: none;
|
| 334 |
+
border-radius: 8px;
|
| 335 |
+
cursor: pointer;
|
| 336 |
+
font-weight: 600;
|
| 337 |
+
font-size: 14px;
|
| 338 |
+
transition: all 0.2s;
|
| 339 |
+
}
|
| 340 |
+
|
| 341 |
+
.btn-primary {
|
| 342 |
+
background: var(--primary-color);
|
| 343 |
+
color: white;
|
| 344 |
+
}
|
| 345 |
+
|
| 346 |
+
.btn-primary:hover {
|
| 347 |
+
background: var(--secondary-color);
|
| 348 |
+
}
|
| 349 |
+
|
| 350 |
+
.btn-secondary {
|
| 351 |
+
background: white;
|
| 352 |
+
color: var(--text-dark);
|
| 353 |
+
border: 1px solid var(--border-color);
|
| 354 |
+
}
|
| 355 |
+
|
| 356 |
+
.btn-secondary:hover {
|
| 357 |
+
background: var(--bg-light);
|
| 358 |
+
}
|
| 359 |
+
|
| 360 |
+
.btn-secondary.active {
|
| 361 |
+
background: var(--warning-color);
|
| 362 |
+
color: white;
|
| 363 |
+
border-color: var(--warning-color);
|
| 364 |
+
}
|
| 365 |
+
|
| 366 |
+
.scanner-results {
|
| 367 |
+
background: white;
|
| 368 |
+
border-radius: 12px;
|
| 369 |
+
border: 1px solid var(--border-color);
|
| 370 |
+
padding: 20px;
|
| 371 |
+
}
|
| 372 |
+
|
| 373 |
+
.placeholder {
|
| 374 |
+
color: var(--text-light);
|
| 375 |
+
text-align: center;
|
| 376 |
+
padding: 40px;
|
| 377 |
+
}
|
| 378 |
+
|
| 379 |
+
.scan-report {
|
| 380 |
+
display: flex;
|
| 381 |
+
flex-direction: column;
|
| 382 |
+
gap: 20px;
|
| 383 |
+
}
|
| 384 |
+
|
| 385 |
+
.scan-header {
|
| 386 |
+
border-bottom: 1px solid var(--border-color);
|
| 387 |
+
padding-bottom: 15px;
|
| 388 |
+
}
|
| 389 |
+
|
| 390 |
+
.scan-header h3 {
|
| 391 |
+
margin-bottom: 10px;
|
| 392 |
+
}
|
| 393 |
+
|
| 394 |
+
.scan-time, .scan-duration {
|
| 395 |
+
font-size: 12px;
|
| 396 |
+
color: var(--text-light);
|
| 397 |
+
}
|
| 398 |
+
|
| 399 |
+
.issues-section, .recommendations-section {
|
| 400 |
+
display: flex;
|
| 401 |
+
flex-direction: column;
|
| 402 |
+
gap: 12px;
|
| 403 |
+
}
|
| 404 |
+
|
| 405 |
+
.issues-list, .recommendations-list {
|
| 406 |
+
list-style: none;
|
| 407 |
+
}
|
| 408 |
+
|
| 409 |
+
.issue-item, .recommendation-item {
|
| 410 |
+
display: flex;
|
| 411 |
+
align-items: flex-start;
|
| 412 |
+
gap: 12px;
|
| 413 |
+
padding: 12px;
|
| 414 |
+
background: var(--bg-light);
|
| 415 |
+
border-radius: 8px;
|
| 416 |
+
border-left: 4px solid var(--warning-color);
|
| 417 |
+
}
|
| 418 |
+
|
| 419 |
+
.issue-item.severity-critical {
|
| 420 |
+
border-left-color: var(--error-color);
|
| 421 |
+
}
|
| 422 |
+
|
| 423 |
+
.issue-item.severity-high {
|
| 424 |
+
border-left-color: var(--warning-color);
|
| 425 |
+
}
|
| 426 |
+
|
| 427 |
+
.issue-item.severity-medium {
|
| 428 |
+
border-left-color: #f6ad55;
|
| 429 |
+
}
|
| 430 |
+
|
| 431 |
+
.severity-badge, .priority-badge {
|
| 432 |
+
display: inline-block;
|
| 433 |
+
padding: 2px 8px;
|
| 434 |
+
border-radius: 4px;
|
| 435 |
+
font-size: 11px;
|
| 436 |
+
font-weight: 600;
|
| 437 |
+
text-transform: uppercase;
|
| 438 |
+
white-space: nowrap;
|
| 439 |
+
background: rgba(0, 0, 0, 0.1);
|
| 440 |
+
}
|
| 441 |
+
|
| 442 |
+
.issue-message {
|
| 443 |
+
flex: 1;
|
| 444 |
+
}
|
| 445 |
+
|
| 446 |
+
.issue-file {
|
| 447 |
+
color: var(--text-light);
|
| 448 |
+
font-size: 12px;
|
| 449 |
+
}
|
| 450 |
+
|
| 451 |
+
.success-message {
|
| 452 |
+
color: var(--success-color);
|
| 453 |
+
font-weight: 600;
|
| 454 |
+
padding: 20px;
|
| 455 |
+
text-align: center;
|
| 456 |
+
background: rgba(72, 187, 120, 0.1);
|
| 457 |
+
border-radius: 8px;
|
| 458 |
+
}
|
| 459 |
+
|
| 460 |
+
/* Project */
|
| 461 |
+
.project-grid {
|
| 462 |
+
display: grid;
|
| 463 |
+
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
|
| 464 |
+
gap: 20px;
|
| 465 |
+
}
|
| 466 |
+
|
| 467 |
+
.project-card {
|
| 468 |
+
background: white;
|
| 469 |
+
border: 1px solid var(--border-color);
|
| 470 |
+
border-radius: 12px;
|
| 471 |
+
padding: 20px;
|
| 472 |
+
overflow: hidden;
|
| 473 |
+
}
|
| 474 |
+
|
| 475 |
+
.project-card h3 {
|
| 476 |
+
margin-bottom: 15px;
|
| 477 |
+
font-size: 16px;
|
| 478 |
+
}
|
| 479 |
+
|
| 480 |
+
.file-tree, .readme-content, .dockerfile-content {
|
| 481 |
+
max-height: 400px;
|
| 482 |
+
overflow-y: auto;
|
| 483 |
+
}
|
| 484 |
+
|
| 485 |
+
.file-list {
|
| 486 |
+
list-style: none;
|
| 487 |
+
}
|
| 488 |
+
|
| 489 |
+
.category {
|
| 490 |
+
font-weight: 600;
|
| 491 |
+
color: var(--primary-color);
|
| 492 |
+
margin-top: 15px;
|
| 493 |
+
margin-bottom: 10px;
|
| 494 |
+
font-size: 12px;
|
| 495 |
+
text-transform: uppercase;
|
| 496 |
+
}
|
| 497 |
+
|
| 498 |
+
.file-item, .dir-item {
|
| 499 |
+
display: flex;
|
| 500 |
+
align-items: center;
|
| 501 |
+
gap: 10px;
|
| 502 |
+
padding: 8px;
|
| 503 |
+
border-radius: 6px;
|
| 504 |
+
transition: background 0.2s;
|
| 505 |
+
}
|
| 506 |
+
|
| 507 |
+
.file-item:hover, .dir-item:hover {
|
| 508 |
+
background: var(--bg-light);
|
| 509 |
+
}
|
| 510 |
+
|
| 511 |
+
.file-icon, .dir-icon {
|
| 512 |
+
font-size: 16px;
|
| 513 |
+
min-width: 20px;
|
| 514 |
+
}
|
| 515 |
+
|
| 516 |
+
.file-name, .dir-name {
|
| 517 |
+
flex: 1;
|
| 518 |
+
font-size: 13px;
|
| 519 |
+
}
|
| 520 |
+
|
| 521 |
+
.file-size {
|
| 522 |
+
font-size: 11px;
|
| 523 |
+
color: var(--text-light);
|
| 524 |
+
}
|
| 525 |
+
|
| 526 |
+
.markdown-content {
|
| 527 |
+
line-height: 1.6;
|
| 528 |
+
}
|
| 529 |
+
|
| 530 |
+
.markdown-content p {
|
| 531 |
+
margin-bottom: 10px;
|
| 532 |
+
}
|
| 533 |
+
|
| 534 |
+
.markdown-content pre {
|
| 535 |
+
background: var(--bg-light);
|
| 536 |
+
padding: 12px;
|
| 537 |
+
border-radius: 6px;
|
| 538 |
+
overflow-x: auto;
|
| 539 |
+
margin: 10px 0;
|
| 540 |
+
}
|
| 541 |
+
|
| 542 |
+
.markdown-content code {
|
| 543 |
+
font-family: 'Courier New', monospace;
|
| 544 |
+
font-size: 12px;
|
| 545 |
+
}
|
| 546 |
+
|
| 547 |
+
/* Settings */
|
| 548 |
+
.settings-grid {
|
| 549 |
+
display: flex;
|
| 550 |
+
flex-direction: column;
|
| 551 |
+
gap: 20px;
|
| 552 |
+
margin-bottom: 30px;
|
| 553 |
+
}
|
| 554 |
+
|
| 555 |
+
.setting-group {
|
| 556 |
+
background: white;
|
| 557 |
+
padding: 16px;
|
| 558 |
+
border-radius: 8px;
|
| 559 |
+
border: 1px solid var(--border-color);
|
| 560 |
+
}
|
| 561 |
+
|
| 562 |
+
.setting-group label {
|
| 563 |
+
display: flex;
|
| 564 |
+
align-items: center;
|
| 565 |
+
gap: 10px;
|
| 566 |
+
cursor: pointer;
|
| 567 |
+
font-weight: 500;
|
| 568 |
+
}
|
| 569 |
+
|
| 570 |
+
.setting-group input[type="checkbox"] {
|
| 571 |
+
width: 20px;
|
| 572 |
+
height: 20px;
|
| 573 |
+
cursor: pointer;
|
| 574 |
+
}
|
| 575 |
+
|
| 576 |
+
.setting-group input[type="number"] {
|
| 577 |
+
margin-left: 10px;
|
| 578 |
+
padding: 6px 12px;
|
| 579 |
+
border: 1px solid var(--border-color);
|
| 580 |
+
border-radius: 6px;
|
| 581 |
+
width: 100px;
|
| 582 |
+
}
|
| 583 |
+
|
| 584 |
+
.settings-info {
|
| 585 |
+
background: white;
|
| 586 |
+
padding: 20px;
|
| 587 |
+
border-radius: 12px;
|
| 588 |
+
border: 1px solid var(--border-color);
|
| 589 |
+
}
|
| 590 |
+
|
| 591 |
+
.settings-info h4 {
|
| 592 |
+
margin-bottom: 15px;
|
| 593 |
+
}
|
| 594 |
+
|
| 595 |
+
.info-box {
|
| 596 |
+
background: var(--bg-light);
|
| 597 |
+
padding: 15px;
|
| 598 |
+
border-radius: 8px;
|
| 599 |
+
}
|
| 600 |
+
|
| 601 |
+
.info-box dl {
|
| 602 |
+
display: grid;
|
| 603 |
+
grid-template-columns: 150px 1fr;
|
| 604 |
+
gap: 15px;
|
| 605 |
+
}
|
| 606 |
+
|
| 607 |
+
.info-box dt {
|
| 608 |
+
font-weight: 600;
|
| 609 |
+
color: var(--text-dark);
|
| 610 |
+
}
|
| 611 |
+
|
| 612 |
+
.info-box dd {
|
| 613 |
+
color: var(--text-light);
|
| 614 |
+
}
|
| 615 |
+
|
| 616 |
+
/* Notifications */
|
| 617 |
+
.notification {
|
| 618 |
+
position: fixed;
|
| 619 |
+
bottom: 20px;
|
| 620 |
+
right: 20px;
|
| 621 |
+
padding: 16px 20px;
|
| 622 |
+
border-radius: 8px;
|
| 623 |
+
background: white;
|
| 624 |
+
border: 1px solid var(--border-color);
|
| 625 |
+
display: flex;
|
| 626 |
+
align-items: center;
|
| 627 |
+
gap: 12px;
|
| 628 |
+
min-width: 300px;
|
| 629 |
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
| 630 |
+
animation: slideUp 0.3s ease-out;
|
| 631 |
+
}
|
| 632 |
+
|
| 633 |
+
@keyframes slideUp {
|
| 634 |
+
from {
|
| 635 |
+
opacity: 0;
|
| 636 |
+
transform: translateY(20px);
|
| 637 |
+
}
|
| 638 |
+
to {
|
| 639 |
+
opacity: 1;
|
| 640 |
+
transform: translateY(0);
|
| 641 |
+
}
|
| 642 |
+
}
|
| 643 |
+
|
| 644 |
+
.notification-success {
|
| 645 |
+
border-left: 4px solid var(--success-color);
|
| 646 |
+
color: var(--success-color);
|
| 647 |
+
}
|
| 648 |
+
|
| 649 |
+
.notification-error {
|
| 650 |
+
border-left: 4px solid var(--error-color);
|
| 651 |
+
color: var(--error-color);
|
| 652 |
+
}
|
| 653 |
+
|
| 654 |
+
.notification-info {
|
| 655 |
+
border-left: 4px solid var(--primary-color);
|
| 656 |
+
color: var(--primary-color);
|
| 657 |
+
}
|
| 658 |
+
|
| 659 |
+
.close-btn {
|
| 660 |
+
background: transparent;
|
| 661 |
+
border: none;
|
| 662 |
+
color: inherit;
|
| 663 |
+
cursor: pointer;
|
| 664 |
+
font-size: 20px;
|
| 665 |
+
margin-left: auto;
|
| 666 |
+
}
|
| 667 |
+
|
| 668 |
+
/* Responsive */
|
| 669 |
+
@media (max-width: 768px) {
|
| 670 |
+
.app {
|
| 671 |
+
flex-direction: column;
|
| 672 |
+
}
|
| 673 |
+
|
| 674 |
+
.sidebar {
|
| 675 |
+
width: 100%;
|
| 676 |
+
flex-direction: row;
|
| 677 |
+
padding: 15px;
|
| 678 |
+
}
|
| 679 |
+
|
| 680 |
+
.logo {
|
| 681 |
+
margin-bottom: 0;
|
| 682 |
+
}
|
| 683 |
+
|
| 684 |
+
.nav-menu {
|
| 685 |
+
flex-direction: row;
|
| 686 |
+
flex: 1;
|
| 687 |
+
gap: 5px;
|
| 688 |
+
margin: 0 20px;
|
| 689 |
+
}
|
| 690 |
+
|
| 691 |
+
.nav-item {
|
| 692 |
+
padding: 8px 12px;
|
| 693 |
+
font-size: 12px;
|
| 694 |
+
}
|
| 695 |
+
|
| 696 |
+
.main {
|
| 697 |
+
padding-bottom: 50px;
|
| 698 |
+
}
|
| 699 |
+
|
| 700 |
+
.tab-content {
|
| 701 |
+
padding: 15px;
|
| 702 |
+
}
|
| 703 |
+
|
| 704 |
+
.project-grid {
|
| 705 |
+
grid-template-columns: 1fr;
|
| 706 |
+
}
|
| 707 |
+
|
| 708 |
+
.message-user .message-content,
|
| 709 |
+
.message-assistant .message-content {
|
| 710 |
+
max-width: 100%;
|
| 711 |
+
}
|
| 712 |
+
|
| 713 |
+
.chat-messages {
|
| 714 |
+
padding: 15px;
|
| 715 |
+
}
|
| 716 |
+
|
| 717 |
+
.quick-commands {
|
| 718 |
+
flex-direction: column;
|
| 719 |
+
}
|
| 720 |
+
|
| 721 |
+
.command-btn {
|
| 722 |
+
width: 100%;
|
| 723 |
+
}
|
| 724 |
+
|
| 725 |
+
#chat-form {
|
| 726 |
+
flex-direction: column;
|
| 727 |
+
}
|
| 728 |
+
|
| 729 |
+
.send-btn {
|
| 730 |
+
width: 100%;
|
| 731 |
+
}
|
| 732 |
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Main Application JavaScript
|
| 3 |
+
* Frontend logic for AI Agent UI
|
| 4 |
+
*/
|
| 5 |
+
|
| 6 |
+
import { ChatManager } from './modules/chat.js';
|
| 7 |
+
import { ScannerManager } from './modules/scanner.js';
|
| 8 |
+
import { ProjectManager } from './modules/project.js';
|
| 9 |
+
import { UIManager } from './modules/ui.js';
|
| 10 |
+
|
| 11 |
+
class AIAgentApp {
|
| 12 |
+
constructor() {
|
| 13 |
+
this.chatManager = new ChatManager();
|
| 14 |
+
this.scannerManager = new ScannerManager();
|
| 15 |
+
this.projectManager = new ProjectManager();
|
| 16 |
+
this.uiManager = new UIManager();
|
| 17 |
+
|
| 18 |
+
this.initializeEventListeners();
|
| 19 |
+
this.loadInitialData();
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
initializeEventListeners() {
|
| 23 |
+
// Tab navigation
|
| 24 |
+
document.querySelectorAll('.nav-item').forEach((btn) => {
|
| 25 |
+
btn.addEventListener('click', (e) => this.handleTabChange(e));
|
| 26 |
+
});
|
| 27 |
+
|
| 28 |
+
// Chat
|
| 29 |
+
document.getElementById('chat-form').addEventListener('submit', (e) =>
|
| 30 |
+
this.chatManager.handleSubmit(e)
|
| 31 |
+
);
|
| 32 |
+
document.querySelectorAll('.command-btn').forEach((btn) => {
|
| 33 |
+
btn.addEventListener('click', (e) => this.chatManager.executeCommand(e));
|
| 34 |
+
});
|
| 35 |
+
|
| 36 |
+
// Scanner
|
| 37 |
+
document.getElementById('manual-scan-btn').addEventListener('click', () =>
|
| 38 |
+
this.scannerManager.performManualScan()
|
| 39 |
+
);
|
| 40 |
+
document.getElementById('auto-scan-toggle').addEventListener('click', () =>
|
| 41 |
+
this.scannerManager.toggleAutoScan()
|
| 42 |
+
);
|
| 43 |
+
|
| 44 |
+
// Settings
|
| 45 |
+
document.getElementById('auto-fix-toggle').addEventListener('change', (e) =>
|
| 46 |
+
this.handleSettingsChange('autoFix', e.target.checked)
|
| 47 |
+
);
|
| 48 |
+
document.getElementById('auto-commit-toggle').addEventListener('change', (e) =>
|
| 49 |
+
this.handleSettingsChange('autoCommit', e.target.checked)
|
| 50 |
+
);
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
async loadInitialData() {
|
| 54 |
+
try {
|
| 55 |
+
// Initialize chat
|
| 56 |
+
await this.chatManager.initialize();
|
| 57 |
+
|
| 58 |
+
// Load project info
|
| 59 |
+
await this.projectManager.loadProjectStructure();
|
| 60 |
+
await this.projectManager.loadReadme();
|
| 61 |
+
await this.projectManager.loadDockerfile();
|
| 62 |
+
|
| 63 |
+
// Load app info
|
| 64 |
+
this.uiManager.updateAppInfo({
|
| 65 |
+
status: 'Ready',
|
| 66 |
+
timestamp: new Date().toLocaleString(),
|
| 67 |
+
});
|
| 68 |
+
} catch (error) {
|
| 69 |
+
console.error('Failed to load initial data:', error);
|
| 70 |
+
this.uiManager.showError('Failed to initialize application');
|
| 71 |
+
}
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
handleTabChange(event) {
|
| 75 |
+
const tabName = event.target.dataset.tab;
|
| 76 |
+
|
| 77 |
+
// Update active nav item
|
| 78 |
+
document.querySelectorAll('.nav-item').forEach((btn) => {
|
| 79 |
+
btn.classList.toggle('active', btn === event.target);
|
| 80 |
+
});
|
| 81 |
+
|
| 82 |
+
// Update active tab
|
| 83 |
+
document.querySelectorAll('.tab-content').forEach((tab) => {
|
| 84 |
+
tab.classList.remove('active');
|
| 85 |
+
});
|
| 86 |
+
document.getElementById(`${tabName}-tab`).classList.add('active');
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
handleSettingsChange(setting, value) {
|
| 90 |
+
const settings = this.loadSettings();
|
| 91 |
+
settings[setting] = value;
|
| 92 |
+
localStorage.setItem('aiAgentSettings', JSON.stringify(settings));
|
| 93 |
+
console.log(`Setting ${setting} updated to ${value}`);
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
loadSettings() {
|
| 97 |
+
const stored = localStorage.getItem('aiAgentSettings');
|
| 98 |
+
return stored ? JSON.parse(stored) : {};
|
| 99 |
+
}
|
| 100 |
+
}
|
| 101 |
+
|
| 102 |
+
// Initialize app when DOM is ready
|
| 103 |
+
document.addEventListener('DOMContentLoaded', () => {
|
| 104 |
+
window.app = new AIAgentApp();
|
| 105 |
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* API Client Module
|
| 3 |
+
* Centralized HTTP client for API calls
|
| 4 |
+
*/
|
| 5 |
+
|
| 6 |
+
export class ApiClient {
|
| 7 |
+
constructor(baseUrl = '') {
|
| 8 |
+
this.baseUrl = baseUrl;
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
async request(method, endpoint, data = null) {
|
| 12 |
+
const url = `${this.baseUrl}/api${endpoint}`;
|
| 13 |
+
const options = {
|
| 14 |
+
method,
|
| 15 |
+
headers: {
|
| 16 |
+
'Content-Type': 'application/json',
|
| 17 |
+
},
|
| 18 |
+
};
|
| 19 |
+
|
| 20 |
+
if (data && (method === 'POST' || method === 'PUT')) {
|
| 21 |
+
options.body = JSON.stringify(data);
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
try {
|
| 25 |
+
const response = await fetch(url, options);
|
| 26 |
+
|
| 27 |
+
if (!response.ok) {
|
| 28 |
+
const error = await response.json();
|
| 29 |
+
throw new Error(error.error || `HTTP ${response.status}`);
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
return await response.json();
|
| 33 |
+
} catch (error) {
|
| 34 |
+
console.error(`API request failed: ${method} ${endpoint}`, error);
|
| 35 |
+
throw error;
|
| 36 |
+
}
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
get(endpoint) {
|
| 40 |
+
return this.request('GET', endpoint);
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
post(endpoint, data) {
|
| 44 |
+
return this.request('POST', endpoint, data);
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
put(endpoint, data) {
|
| 48 |
+
return this.request('PUT', endpoint, data);
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
delete(endpoint) {
|
| 52 |
+
return this.request('DELETE', endpoint);
|
| 53 |
+
}
|
| 54 |
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Chat Manager Module
|
| 3 |
+
* Handles chat functionality
|
| 4 |
+
*/
|
| 5 |
+
|
| 6 |
+
import { ApiClient } from './api-client.js';
|
| 7 |
+
import { UIManager } from './ui.js';
|
| 8 |
+
|
| 9 |
+
export class ChatManager {
|
| 10 |
+
constructor() {
|
| 11 |
+
this.api = new ApiClient();
|
| 12 |
+
this.ui = new UIManager();
|
| 13 |
+
this.sessionId = null;
|
| 14 |
+
this.messageHistory = [];
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
async initialize() {
|
| 18 |
+
try {
|
| 19 |
+
const response = await this.api.post('/chat/start', {});
|
| 20 |
+
this.sessionId = response.sessionId;
|
| 21 |
+
console.log('Chat session started:', this.sessionId);
|
| 22 |
+
|
| 23 |
+
this.addSystemMessage(
|
| 24 |
+
'Chat initialized. You can use /help for available commands.'
|
| 25 |
+
);
|
| 26 |
+
} catch (error) {
|
| 27 |
+
console.error('Failed to initialize chat:', error);
|
| 28 |
+
throw error;
|
| 29 |
+
}
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
async handleSubmit(event) {
|
| 33 |
+
event.preventDefault();
|
| 34 |
+
|
| 35 |
+
const input = document.getElementById('message-input');
|
| 36 |
+
const message = input.value.trim();
|
| 37 |
+
|
| 38 |
+
if (!message) return;
|
| 39 |
+
|
| 40 |
+
// Clear input
|
| 41 |
+
input.value = '';
|
| 42 |
+
input.focus();
|
| 43 |
+
|
| 44 |
+
// Add user message to UI
|
| 45 |
+
this.addMessage('user', message);
|
| 46 |
+
|
| 47 |
+
try {
|
| 48 |
+
// Send to API
|
| 49 |
+
const response = await this.api.post('/chat/message', {
|
| 50 |
+
sessionId: this.sessionId,
|
| 51 |
+
message,
|
| 52 |
+
});
|
| 53 |
+
|
| 54 |
+
// Add AI response to UI
|
| 55 |
+
this.addMessage('assistant', response.response);
|
| 56 |
+
this.messageHistory.push({ role: 'user', content: message });
|
| 57 |
+
this.messageHistory.push({ role: 'assistant', content: response.response });
|
| 58 |
+
} catch (error) {
|
| 59 |
+
console.error('Chat error:', error);
|
| 60 |
+
this.addMessage('system', `Error: ${error.message}`);
|
| 61 |
+
}
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
async executeCommand(event) {
|
| 65 |
+
const command = event.target.dataset.command;
|
| 66 |
+
const input = document.getElementById('message-input');
|
| 67 |
+
input.value = command;
|
| 68 |
+
input.focus();
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
addMessage(role, content) {
|
| 72 |
+
const messagesContainer = document.getElementById('chat-messages');
|
| 73 |
+
const messageEl = document.createElement('div');
|
| 74 |
+
messageEl.className = `message message-${role}`;
|
| 75 |
+
|
| 76 |
+
if (role === 'system') {
|
| 77 |
+
messageEl.innerHTML = `<p class="system-message">${this.escapeHtml(content)}</p>`;
|
| 78 |
+
} else {
|
| 79 |
+
messageEl.innerHTML = `
|
| 80 |
+
<div class="message-content">
|
| 81 |
+
<div class="message-text">${this.formatMessageText(content)}</div>
|
| 82 |
+
<div class="message-time">${new Date().toLocaleTimeString()}</div>
|
| 83 |
+
</div>
|
| 84 |
+
`;
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
messagesContainer.appendChild(messageEl);
|
| 88 |
+
messagesContainer.scrollTop = messagesContainer.scrollHeight;
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
addSystemMessage(content) {
|
| 92 |
+
this.addMessage('system', content);
|
| 93 |
+
}
|
| 94 |
+
|
| 95 |
+
formatMessageText(text) {
|
| 96 |
+
// Simple markdown-like formatting
|
| 97 |
+
return this.escapeHtml(text)
|
| 98 |
+
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
|
| 99 |
+
.replace(/\*(.*?)\*/g, '<em>$1</em>')
|
| 100 |
+
.replace(/\n/g, '<br>')
|
| 101 |
+
.replace(/`(.*?)`/g, '<code>$1</code>');
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
escapeHtml(text) {
|
| 105 |
+
const div = document.createElement('div');
|
| 106 |
+
div.textContent = text;
|
| 107 |
+
return div.innerHTML;
|
| 108 |
+
}
|
| 109 |
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Project Manager Module
|
| 3 |
+
* Handles project information display
|
| 4 |
+
*/
|
| 5 |
+
|
| 6 |
+
import { ApiClient } from './api-client.js';
|
| 7 |
+
|
| 8 |
+
export class ProjectManager {
|
| 9 |
+
constructor() {
|
| 10 |
+
this.api = new ApiClient();
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
async loadProjectStructure() {
|
| 14 |
+
try {
|
| 15 |
+
const response = await this.api.get('/project/structure');
|
| 16 |
+
this.displayProjectStructure(response.structure);
|
| 17 |
+
} catch (error) {
|
| 18 |
+
console.error('Failed to load project structure:', error);
|
| 19 |
+
this.updateElement(
|
| 20 |
+
'project-structure',
|
| 21 |
+
`<p class="error">Failed to load project structure</p>`
|
| 22 |
+
);
|
| 23 |
+
}
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
async loadReadme() {
|
| 27 |
+
try {
|
| 28 |
+
const response = await this.api.get('/project/readme');
|
| 29 |
+
if (response.success && response.content) {
|
| 30 |
+
const html = this.markdownToHtml(response.content);
|
| 31 |
+
this.updateElement('project-readme', html);
|
| 32 |
+
}
|
| 33 |
+
} catch (error) {
|
| 34 |
+
console.warn('README not available:', error);
|
| 35 |
+
this.updateElement('project-readme', '<p class="info">README not found</p>');
|
| 36 |
+
}
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
async loadDockerfile() {
|
| 40 |
+
try {
|
| 41 |
+
const response = await this.api.get('/project/dockerfile');
|
| 42 |
+
if (response.success && response.content) {
|
| 43 |
+
const html = `<pre><code>${this.escapeHtml(response.content)}</code></pre>`;
|
| 44 |
+
this.updateElement('project-dockerfile', html);
|
| 45 |
+
}
|
| 46 |
+
} catch (error) {
|
| 47 |
+
console.warn('Dockerfile not available:', error);
|
| 48 |
+
this.updateElement('project-dockerfile', '<p class="info">Dockerfile not found</p>');
|
| 49 |
+
}
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
displayProjectStructure(structure) {
|
| 53 |
+
let html = '<ul class="file-list">';
|
| 54 |
+
|
| 55 |
+
if (structure.files && structure.files.length > 0) {
|
| 56 |
+
html += '<li class="category">📄 Files</li>';
|
| 57 |
+
structure.files.forEach((file) => {
|
| 58 |
+
html += `
|
| 59 |
+
<li class="file-item">
|
| 60 |
+
<span class="file-icon">📄</span>
|
| 61 |
+
<span class="file-name">${file.name}</span>
|
| 62 |
+
<span class="file-size">${this.formatBytes(file.size)}</span>
|
| 63 |
+
</li>
|
| 64 |
+
`;
|
| 65 |
+
});
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
if (structure.directories && structure.directories.length > 0) {
|
| 69 |
+
html += '<li class="category">📁 Directories</li>';
|
| 70 |
+
structure.directories.forEach((dir) => {
|
| 71 |
+
html += `
|
| 72 |
+
<li class="dir-item">
|
| 73 |
+
<span class="dir-icon">📁</span>
|
| 74 |
+
<span class="dir-name">${dir.name}</span>
|
| 75 |
+
</li>
|
| 76 |
+
`;
|
| 77 |
+
});
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
html += '</ul>';
|
| 81 |
+
this.updateElement('project-structure', html);
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
markdownToHtml(markdown) {
|
| 85 |
+
// Simple markdown to HTML conversion
|
| 86 |
+
let html = markdown
|
| 87 |
+
.replace(/^# (.*?)$/gm, '<h1>$1</h1>')
|
| 88 |
+
.replace(/^## (.*?)$/gm, '<h2>$1</h2>')
|
| 89 |
+
.replace(/^### (.*?)$/gm, '<h3>$1</h3>')
|
| 90 |
+
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
|
| 91 |
+
.replace(/\*(.*?)\*/g, '<em>$1</em>')
|
| 92 |
+
.replace(/\n\n/g, '</p><p>')
|
| 93 |
+
.replace(/^- (.*?)$/gm, '<li>$1</li>')
|
| 94 |
+
.replace(/(<li>.*<\/li>)/s, '<ul>$1</ul>');
|
| 95 |
+
|
| 96 |
+
return `<div class="markdown-content"><p>${html}</p></div>`;
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
formatBytes(bytes) {
|
| 100 |
+
if (bytes === 0) return '0 B';
|
| 101 |
+
const k = 1024;
|
| 102 |
+
const sizes = ['B', 'KB', 'MB'];
|
| 103 |
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
| 104 |
+
return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i];
|
| 105 |
+
}
|
| 106 |
+
|
| 107 |
+
updateElement(elementId, html) {
|
| 108 |
+
const element = document.getElementById(elementId);
|
| 109 |
+
if (element) {
|
| 110 |
+
element.innerHTML = html;
|
| 111 |
+
}
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
escapeHtml(text) {
|
| 115 |
+
const div = document.createElement('div');
|
| 116 |
+
div.textContent = text;
|
| 117 |
+
return div.innerHTML;
|
| 118 |
+
}
|
| 119 |
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Scanner Manager Module
|
| 3 |
+
* Handles project scanning
|
| 4 |
+
*/
|
| 5 |
+
|
| 6 |
+
import { ApiClient } from './api-client.js';
|
| 7 |
+
import { UIManager } from './ui.js';
|
| 8 |
+
|
| 9 |
+
export class ScannerManager {
|
| 10 |
+
constructor() {
|
| 11 |
+
this.api = new ApiClient();
|
| 12 |
+
this.ui = new UIManager();
|
| 13 |
+
this.isAutoScanning = false;
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
async performManualScan() {
|
| 17 |
+
const btn = document.getElementById('manual-scan-btn');
|
| 18 |
+
btn.disabled = true;
|
| 19 |
+
btn.textContent = '⏳ Scanning...';
|
| 20 |
+
|
| 21 |
+
try {
|
| 22 |
+
const response = await this.api.post('/scanner/scan', {});
|
| 23 |
+
this.displayScanResults(response.report);
|
| 24 |
+
} catch (error) {
|
| 25 |
+
console.error('Scan failed:', error);
|
| 26 |
+
this.ui.showError(`Scan failed: ${error.message}`);
|
| 27 |
+
} finally {
|
| 28 |
+
btn.disabled = false;
|
| 29 |
+
btn.textContent = '🔍 Run Manual Scan';
|
| 30 |
+
}
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
async toggleAutoScan() {
|
| 34 |
+
const btn = document.getElementById('auto-scan-toggle');
|
| 35 |
+
|
| 36 |
+
try {
|
| 37 |
+
if (this.isAutoScanning) {
|
| 38 |
+
await this.api.post('/scanner/stop-continuous', {});
|
| 39 |
+
this.isAutoScanning = false;
|
| 40 |
+
btn.classList.remove('active');
|
| 41 |
+
btn.textContent = 'Start Auto-Scan (1 hour)';
|
| 42 |
+
this.ui.showSuccess('Auto-scan stopped');
|
| 43 |
+
} else {
|
| 44 |
+
await this.api.post('/scanner/start-continuous', {});
|
| 45 |
+
this.isAutoScanning = true;
|
| 46 |
+
btn.classList.add('active');
|
| 47 |
+
btn.textContent = 'Stop Auto-Scan';
|
| 48 |
+
this.ui.showSuccess('Auto-scan started (1 hour interval)');
|
| 49 |
+
}
|
| 50 |
+
} catch (error) {
|
| 51 |
+
console.error('Failed to toggle auto-scan:', error);
|
| 52 |
+
this.ui.showError(`Failed to toggle auto-scan: ${error.message}`);
|
| 53 |
+
}
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
displayScanResults(report) {
|
| 57 |
+
const resultsContainer = document.getElementById('scanner-results');
|
| 58 |
+
|
| 59 |
+
let html = `
|
| 60 |
+
<div class="scan-report">
|
| 61 |
+
<div class="scan-header">
|
| 62 |
+
<h3>Scan Report</h3>
|
| 63 |
+
<p class="scan-time">${new Date(report.timestamp).toLocaleString()}</p>
|
| 64 |
+
<p class="scan-duration">Duration: ${report.duration}ms</p>
|
| 65 |
+
</div>
|
| 66 |
+
`;
|
| 67 |
+
|
| 68 |
+
// Issues
|
| 69 |
+
if (report.issues && report.issues.length > 0) {
|
| 70 |
+
html += `
|
| 71 |
+
<div class="issues-section">
|
| 72 |
+
<h4>⚠️ Issues Found (${report.issues.length})</h4>
|
| 73 |
+
<ul class="issues-list">
|
| 74 |
+
`;
|
| 75 |
+
report.issues.forEach((issue) => {
|
| 76 |
+
const severityClass = `severity-${issue.severity}`;
|
| 77 |
+
html += `
|
| 78 |
+
<li class="issue-item ${severityClass}">
|
| 79 |
+
<span class="severity-badge">${issue.severity}</span>
|
| 80 |
+
<span class="issue-message">${issue.message}</span>
|
| 81 |
+
<span class="issue-file"><code>${issue.file}</code></span>
|
| 82 |
+
</li>
|
| 83 |
+
`;
|
| 84 |
+
});
|
| 85 |
+
html += '</ul></div>';
|
| 86 |
+
} else {
|
| 87 |
+
html += '<div class="success-message">✅ No issues detected!</div>';
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
// Recommendations
|
| 91 |
+
if (report.recommendations && report.recommendations.length > 0) {
|
| 92 |
+
html += `
|
| 93 |
+
<div class="recommendations-section">
|
| 94 |
+
<h4>💡 Recommendations</h4>
|
| 95 |
+
<ul class="recommendations-list">
|
| 96 |
+
`;
|
| 97 |
+
report.recommendations.forEach((rec) => {
|
| 98 |
+
html += `
|
| 99 |
+
<li class="recommendation-item">
|
| 100 |
+
<span class="priority-badge">${rec.priority}</span>
|
| 101 |
+
<div class="recommendation-text">${rec.suggestion}</div>
|
| 102 |
+
</li>
|
| 103 |
+
`;
|
| 104 |
+
});
|
| 105 |
+
html += '</ul></div>';
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
html += '</div>';
|
| 109 |
+
resultsContainer.innerHTML = html;
|
| 110 |
+
}
|
| 111 |
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* UI Manager Module
|
| 3 |
+
* Centralized UI utilities
|
| 4 |
+
*/
|
| 5 |
+
|
| 6 |
+
export class UIManager {
|
| 7 |
+
static showSuccess(message) {
|
| 8 |
+
this.showNotification(message, 'success');
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
static showError(message) {
|
| 12 |
+
this.showNotification(message, 'error');
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
static showNotification(message, type = 'info') {
|
| 16 |
+
// Create notification element
|
| 17 |
+
const notification = document.createElement('div');
|
| 18 |
+
notification.className = `notification notification-${type}`;
|
| 19 |
+
notification.innerHTML = `
|
| 20 |
+
<span>${message}</span>
|
| 21 |
+
<button class="close-btn">×</button>
|
| 22 |
+
`;
|
| 23 |
+
|
| 24 |
+
// Insert into page
|
| 25 |
+
document.body.appendChild(notification);
|
| 26 |
+
|
| 27 |
+
// Auto-remove after 5 seconds
|
| 28 |
+
const timeout = setTimeout(() => {
|
| 29 |
+
notification.remove();
|
| 30 |
+
}, 5000);
|
| 31 |
+
|
| 32 |
+
// Close button handler
|
| 33 |
+
notification.querySelector('.close-btn').addEventListener('click', () => {
|
| 34 |
+
clearTimeout(timeout);
|
| 35 |
+
notification.remove();
|
| 36 |
+
});
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
static updateAppInfo(info) {
|
| 40 |
+
const infoBox = document.getElementById('app-info');
|
| 41 |
+
if (infoBox) {
|
| 42 |
+
let html = '<dl>';
|
| 43 |
+
for (const [key, value] of Object.entries(info)) {
|
| 44 |
+
html += `<dt>${key}</dt><dd>${value}</dd>`;
|
| 45 |
+
}
|
| 46 |
+
html += '</dl>';
|
| 47 |
+
infoBox.innerHTML = html;
|
| 48 |
+
}
|
| 49 |
+
}
|
| 50 |
+
}
|
|
@@ -3,47 +3,171 @@
|
|
| 3 |
<head>
|
| 4 |
<meta charset="UTF-8">
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
-
<title>
|
| 7 |
-
<link rel="stylesheet" href="styles.css">
|
| 8 |
</head>
|
| 9 |
<body>
|
| 10 |
-
<div class="
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
<
|
| 14 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
<
|
| 21 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
</section>
|
| 23 |
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
<div
|
| 27 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
</div>
|
| 29 |
-
<button id="loadPhp">Reload PHP Data</button>
|
| 30 |
</section>
|
| 31 |
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
<
|
| 35 |
-
<
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
</section>
|
| 40 |
-
</main>
|
| 41 |
|
| 42 |
-
|
| 43 |
-
<
|
| 44 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
</div>
|
| 46 |
|
| 47 |
-
<script src="
|
| 48 |
</body>
|
| 49 |
</html>
|
|
|
|
| 3 |
<head>
|
| 4 |
<meta charset="UTF-8">
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>AI Agent - HF Space</title>
|
| 7 |
+
<link rel="stylesheet" href="/assets/css/styles.css">
|
| 8 |
</head>
|
| 9 |
<body>
|
| 10 |
+
<div class="app">
|
| 11 |
+
<!-- Sidebar -->
|
| 12 |
+
<aside class="sidebar">
|
| 13 |
+
<div class="logo">
|
| 14 |
+
<h1>🤖 AI Agent</h1>
|
| 15 |
+
</div>
|
| 16 |
+
|
| 17 |
+
<nav class="nav-menu">
|
| 18 |
+
<button class="nav-item active" data-tab="chat">
|
| 19 |
+
💬 Chat
|
| 20 |
+
</button>
|
| 21 |
+
<button class="nav-item" data-tab="scanner">
|
| 22 |
+
🔍 Scanner
|
| 23 |
+
</button>
|
| 24 |
+
<button class="nav-item" data-tab="project">
|
| 25 |
+
📁 Project
|
| 26 |
+
</button>
|
| 27 |
+
<button class="nav-item" data-tab="settings">
|
| 28 |
+
⚙️ Settings
|
| 29 |
+
</button>
|
| 30 |
+
</nav>
|
| 31 |
+
|
| 32 |
+
<div class="sidebar-footer">
|
| 33 |
+
<div class="status">
|
| 34 |
+
<div class="status-dot online"></div>
|
| 35 |
+
<span>Connected</span>
|
| 36 |
+
</div>
|
| 37 |
+
</div>
|
| 38 |
+
</aside>
|
| 39 |
|
| 40 |
+
<!-- Main Content -->
|
| 41 |
+
<main class="main">
|
| 42 |
+
<!-- Chat Tab -->
|
| 43 |
+
<section id="chat-tab" class="tab-content active">
|
| 44 |
+
<div class="chat-container">
|
| 45 |
+
<header class="chat-header">
|
| 46 |
+
<h2>Chat with AI Agent</h2>
|
| 47 |
+
<p>Ask about your project, scan for issues, or request changes</p>
|
| 48 |
+
</header>
|
| 49 |
+
|
| 50 |
+
<div id="chat-messages" class="chat-messages"></div>
|
| 51 |
+
|
| 52 |
+
<div class="chat-input-area">
|
| 53 |
+
<div class="quick-commands">
|
| 54 |
+
<button class="command-btn" data-command="/scan">🔍 Scan</button>
|
| 55 |
+
<button class="command-btn" data-command="/status">📊 Status</button>
|
| 56 |
+
<button class="command-btn" data-command="/issues">📋 Issues</button>
|
| 57 |
+
<button class="command-btn" data-command="/help">❓ Help</button>
|
| 58 |
+
</div>
|
| 59 |
+
|
| 60 |
+
<form id="chat-form">
|
| 61 |
+
<input
|
| 62 |
+
type="text"
|
| 63 |
+
id="message-input"
|
| 64 |
+
placeholder="Type your message or use commands (/scan, /status, /issues)..."
|
| 65 |
+
autocomplete="off"
|
| 66 |
+
>
|
| 67 |
+
<button type="submit" class="send-btn">Send</button>
|
| 68 |
+
</form>
|
| 69 |
+
</div>
|
| 70 |
+
</div>
|
| 71 |
</section>
|
| 72 |
|
| 73 |
+
<!-- Scanner Tab -->
|
| 74 |
+
<section id="scanner-tab" class="tab-content">
|
| 75 |
+
<div class="scanner-container">
|
| 76 |
+
<header class="section-header">
|
| 77 |
+
<h2>Project Scanner</h2>
|
| 78 |
+
<p>Scan for issues and get AI recommendations</p>
|
| 79 |
+
</header>
|
| 80 |
+
|
| 81 |
+
<div class="scanner-controls">
|
| 82 |
+
<button id="manual-scan-btn" class="btn btn-primary">
|
| 83 |
+
🔍 Run Manual Scan
|
| 84 |
+
</button>
|
| 85 |
+
<button id="auto-scan-toggle" class="btn btn-secondary">
|
| 86 |
+
Start Auto-Scan (1 hour)
|
| 87 |
+
</button>
|
| 88 |
+
</div>
|
| 89 |
+
|
| 90 |
+
<div id="scanner-results" class="scanner-results">
|
| 91 |
+
<p class="placeholder">No scan results yet. Click "Run Manual Scan" to start.</p>
|
| 92 |
+
</div>
|
| 93 |
</div>
|
|
|
|
| 94 |
</section>
|
| 95 |
|
| 96 |
+
<!-- Project Tab -->
|
| 97 |
+
<section id="project-tab" class="tab-content">
|
| 98 |
+
<div class="project-container">
|
| 99 |
+
<header class="section-header">
|
| 100 |
+
<h2>Project Information</h2>
|
| 101 |
+
<p>View project structure and files</p>
|
| 102 |
+
</header>
|
| 103 |
+
|
| 104 |
+
<div class="project-grid">
|
| 105 |
+
<div class="project-card">
|
| 106 |
+
<h3>📁 Project Structure</h3>
|
| 107 |
+
<div id="project-structure" class="file-tree">
|
| 108 |
+
<p class="loading">Loading project structure...</p>
|
| 109 |
+
</div>
|
| 110 |
+
</div>
|
| 111 |
+
|
| 112 |
+
<div class="project-card">
|
| 113 |
+
<h3>📄 README</h3>
|
| 114 |
+
<div id="project-readme" class="readme-content">
|
| 115 |
+
<p class="loading">Loading README...</p>
|
| 116 |
+
</div>
|
| 117 |
+
</div>
|
| 118 |
+
|
| 119 |
+
<div class="project-card">
|
| 120 |
+
<h3>🐳 Dockerfile</h3>
|
| 121 |
+
<div id="project-dockerfile" class="dockerfile-content">
|
| 122 |
+
<p class="loading">Loading Dockerfile...</p>
|
| 123 |
+
</div>
|
| 124 |
+
</div>
|
| 125 |
+
</div>
|
| 126 |
+
</div>
|
| 127 |
</section>
|
|
|
|
| 128 |
|
| 129 |
+
<!-- Settings Tab -->
|
| 130 |
+
<section id="settings-tab" class="tab-content">
|
| 131 |
+
<div class="settings-container">
|
| 132 |
+
<header class="section-header">
|
| 133 |
+
<h2>Settings & Configuration</h2>
|
| 134 |
+
<p>Configure AI Agent behavior</p>
|
| 135 |
+
</header>
|
| 136 |
+
|
| 137 |
+
<div class="settings-grid">
|
| 138 |
+
<div class="setting-group">
|
| 139 |
+
<label for="auto-fix-toggle">
|
| 140 |
+
<input type="checkbox" id="auto-fix-toggle" />
|
| 141 |
+
Enable Auto-Fix for detected issues
|
| 142 |
+
</label>
|
| 143 |
+
</div>
|
| 144 |
+
|
| 145 |
+
<div class="setting-group">
|
| 146 |
+
<label for="auto-commit-toggle">
|
| 147 |
+
<input type="checkbox" id="auto-commit-toggle" />
|
| 148 |
+
Auto-commit fixes to GitHub
|
| 149 |
+
</label>
|
| 150 |
+
</div>
|
| 151 |
+
|
| 152 |
+
<div class="setting-group">
|
| 153 |
+
<label for="scan-interval">
|
| 154 |
+
Scan Interval (minutes):
|
| 155 |
+
<input type="number" id="scan-interval" value="60" min="10" max="1440" />
|
| 156 |
+
</label>
|
| 157 |
+
</div>
|
| 158 |
+
</div>
|
| 159 |
+
|
| 160 |
+
<div class="settings-info">
|
| 161 |
+
<h4>ℹ️ Information</h4>
|
| 162 |
+
<div id="app-info" class="info-box">
|
| 163 |
+
<p>Loading...</p>
|
| 164 |
+
</div>
|
| 165 |
+
</div>
|
| 166 |
+
</div>
|
| 167 |
+
</section>
|
| 168 |
+
</main>
|
| 169 |
</div>
|
| 170 |
|
| 171 |
+
<script src="/assets/js/app.js" type="module"></script>
|
| 172 |
</body>
|
| 173 |
</html>
|
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "ai-agent-hf-space",
|
| 3 |
+
"version": "1.0.0",
|
| 4 |
+
"description": "AI Agent for GitHub→HF Space with periodic scanning and chat interface",
|
| 5 |
+
"type": "module",
|
| 6 |
+
"main": "src/server.js",
|
| 7 |
+
"scripts": {
|
| 8 |
+
"start": "node src/server.js",
|
| 9 |
+
"dev": "node --watch src/server.js",
|
| 10 |
+
"agent": "node src/agents/scanner.js",
|
| 11 |
+
"test": "echo 'Tests coming soon'"
|
| 12 |
+
},
|
| 13 |
+
"dependencies": {
|
| 14 |
+
"@google/generative-ai": "^0.7.0",
|
| 15 |
+
"express": "^4.18.2",
|
| 16 |
+
"axios": "^1.6.0",
|
| 17 |
+
"dotenv": "^16.3.1",
|
| 18 |
+
"cors": "^2.8.5",
|
| 19 |
+
"body-parser": "^1.20.2"
|
| 20 |
+
},
|
| 21 |
+
"devDependencies": {
|
| 22 |
+
"nodemon": "^3.0.1"
|
| 23 |
+
},
|
| 24 |
+
"engines": {
|
| 25 |
+
"node": ">=18.0.0"
|
| 26 |
+
}
|
| 27 |
+
}
|
|
@@ -1,22 +0,0 @@
|
|
| 1 |
-
<?php
|
| 2 |
-
header('Content-Type: application/json');
|
| 3 |
-
header('Access-Control-Allow-Origin: *');
|
| 4 |
-
|
| 5 |
-
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
| 6 |
-
$name = isset($_POST['name']) ? htmlspecialchars($_POST['name']) : 'Guest';
|
| 7 |
-
|
| 8 |
-
$response = [
|
| 9 |
-
'success' => true,
|
| 10 |
-
'greeting' => "Hello, {$name}! Your form was processed by PHP.",
|
| 11 |
-
'timestamp' => date('Y-m-d H:i:s'),
|
| 12 |
-
'name_length' => strlen($name)
|
| 13 |
-
];
|
| 14 |
-
} else {
|
| 15 |
-
$response = [
|
| 16 |
-
'success' => false,
|
| 17 |
-
'error' => 'Only POST requests are accepted'
|
| 18 |
-
];
|
| 19 |
-
}
|
| 20 |
-
|
| 21 |
-
echo json_encode($response);
|
| 22 |
-
?>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -1,66 +0,0 @@
|
|
| 1 |
-
// JavaScript functionality
|
| 2 |
-
document.addEventListener('DOMContentLoaded', function() {
|
| 3 |
-
|
| 4 |
-
// Button click test
|
| 5 |
-
const testButton = document.getElementById('testButton');
|
| 6 |
-
const jsOutput = document.getElementById('jsOutput');
|
| 7 |
-
|
| 8 |
-
testButton.addEventListener('click', function() {
|
| 9 |
-
const now = new Date().toLocaleString();
|
| 10 |
-
jsOutput.textContent = `✓ JavaScript is working! Clicked at: ${now}`;
|
| 11 |
-
jsOutput.style.display = 'block';
|
| 12 |
-
});
|
| 13 |
-
|
| 14 |
-
// Load PHP content
|
| 15 |
-
function loadPhpContent() {
|
| 16 |
-
const phpContent = document.getElementById('phpContent');
|
| 17 |
-
|
| 18 |
-
fetch('api.php')
|
| 19 |
-
.then(response => response.json())
|
| 20 |
-
.then(data => {
|
| 21 |
-
phpContent.innerHTML = `
|
| 22 |
-
<p><strong>Server Message:</strong> ${data.message}</p>
|
| 23 |
-
<p><strong>Server Time:</strong> ${data.timestamp}</p>
|
| 24 |
-
<p><strong>Random Number:</strong> ${data.random}</p>
|
| 25 |
-
`;
|
| 26 |
-
})
|
| 27 |
-
.catch(error => {
|
| 28 |
-
phpContent.innerHTML = `<p style="color: red;">Error loading PHP: ${error.message}</p>`;
|
| 29 |
-
});
|
| 30 |
-
}
|
| 31 |
-
|
| 32 |
-
// Load PHP on page load
|
| 33 |
-
loadPhpContent();
|
| 34 |
-
|
| 35 |
-
// Reload PHP button
|
| 36 |
-
document.getElementById('loadPhp').addEventListener('click', loadPhpContent);
|
| 37 |
-
|
| 38 |
-
// Form submission
|
| 39 |
-
const testForm = document.getElementById('testForm');
|
| 40 |
-
const formResponse = document.getElementById('formResponse');
|
| 41 |
-
|
| 42 |
-
testForm.addEventListener('submit', function(e) {
|
| 43 |
-
e.preventDefault();
|
| 44 |
-
|
| 45 |
-
const nameInput = document.getElementById('nameInput');
|
| 46 |
-
const formData = new FormData();
|
| 47 |
-
formData.append('name', nameInput.value);
|
| 48 |
-
|
| 49 |
-
fetch('process.php', {
|
| 50 |
-
method: 'POST',
|
| 51 |
-
body: formData
|
| 52 |
-
})
|
| 53 |
-
.then(response => response.json())
|
| 54 |
-
.then(data => {
|
| 55 |
-
formResponse.innerHTML = `
|
| 56 |
-
<p><strong>${data.greeting}</strong></p>
|
| 57 |
-
<p>Processed at: ${data.timestamp}</p>
|
| 58 |
-
`;
|
| 59 |
-
formResponse.style.display = 'block';
|
| 60 |
-
nameInput.value = '';
|
| 61 |
-
})
|
| 62 |
-
.catch(error => {
|
| 63 |
-
formResponse.innerHTML = `<p style="color: red;">Error: ${error.message}</p>`;
|
| 64 |
-
});
|
| 65 |
-
});
|
| 66 |
-
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -0,0 +1,271 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Chat Agent
|
| 3 |
+
* Handles user conversations and AI-powered requests
|
| 4 |
+
*/
|
| 5 |
+
|
| 6 |
+
import { geminiService } from '../services/gemini.js';
|
| 7 |
+
import { projectService } from '../services/project.js';
|
| 8 |
+
import { githubService } from '../services/github.js';
|
| 9 |
+
import { logger } from '../config/logger.js';
|
| 10 |
+
|
| 11 |
+
class ChatAgent {
|
| 12 |
+
constructor() {
|
| 13 |
+
this.conversations = new Map();
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
/**
|
| 17 |
+
* Start new conversation
|
| 18 |
+
* @param {string} sessionId - Unique session ID
|
| 19 |
+
* @returns {Object} Conversation context
|
| 20 |
+
*/
|
| 21 |
+
startConversation(sessionId) {
|
| 22 |
+
const context = {
|
| 23 |
+
sessionId,
|
| 24 |
+
startedAt: new Date().toISOString(),
|
| 25 |
+
messages: [],
|
| 26 |
+
projectContext: null,
|
| 27 |
+
};
|
| 28 |
+
|
| 29 |
+
this.conversations.set(sessionId, context);
|
| 30 |
+
logger.debug('Conversation started', { sessionId });
|
| 31 |
+
|
| 32 |
+
return context;
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
/**
|
| 36 |
+
* Process user message
|
| 37 |
+
* @param {string} sessionId - Session ID
|
| 38 |
+
* @param {string} message - User message
|
| 39 |
+
* @returns {Promise<string>} AI response
|
| 40 |
+
*/
|
| 41 |
+
async processMessage(sessionId, message) {
|
| 42 |
+
let context = this.conversations.get(sessionId);
|
| 43 |
+
|
| 44 |
+
if (!context) {
|
| 45 |
+
context = this.startConversation(sessionId);
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
// Load project context if needed
|
| 49 |
+
if (!context.projectContext) {
|
| 50 |
+
try {
|
| 51 |
+
context.projectContext = await projectService.getProjectStructure();
|
| 52 |
+
context.projectContext.readme = await projectService.getReadme();
|
| 53 |
+
} catch (e) {
|
| 54 |
+
logger.warn('Failed to load project context', { error: e.message });
|
| 55 |
+
}
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
// Add message to history
|
| 59 |
+
context.messages.push({
|
| 60 |
+
role: 'user',
|
| 61 |
+
content: message,
|
| 62 |
+
timestamp: new Date().toISOString(),
|
| 63 |
+
});
|
| 64 |
+
|
| 65 |
+
try {
|
| 66 |
+
// Check if message is a command
|
| 67 |
+
const response = await this.handleMessage(message, context);
|
| 68 |
+
|
| 69 |
+
// Add response to history
|
| 70 |
+
context.messages.push({
|
| 71 |
+
role: 'assistant',
|
| 72 |
+
content: response,
|
| 73 |
+
timestamp: new Date().toISOString(),
|
| 74 |
+
});
|
| 75 |
+
|
| 76 |
+
this.conversations.set(sessionId, context);
|
| 77 |
+
|
| 78 |
+
return response;
|
| 79 |
+
} catch (error) {
|
| 80 |
+
logger.error('Failed to process message', { error: error.message, sessionId });
|
| 81 |
+
const errorResponse = `Sorry, I encountered an error: ${error.message}`;
|
| 82 |
+
|
| 83 |
+
context.messages.push({
|
| 84 |
+
role: 'assistant',
|
| 85 |
+
content: errorResponse,
|
| 86 |
+
timestamp: new Date().toISOString(),
|
| 87 |
+
});
|
| 88 |
+
|
| 89 |
+
this.conversations.set(sessionId, context);
|
| 90 |
+
return errorResponse;
|
| 91 |
+
}
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
/**
|
| 95 |
+
* Handle different message types
|
| 96 |
+
* @param {string} message - User message
|
| 97 |
+
* @param {Object} context - Conversation context
|
| 98 |
+
* @returns {Promise<string>} Response
|
| 99 |
+
*/
|
| 100 |
+
async handleMessage(message, context) {
|
| 101 |
+
// Check for commands
|
| 102 |
+
if (message.startsWith('/scan')) {
|
| 103 |
+
return this.handleScanCommand(context);
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
if (message.startsWith('/status')) {
|
| 107 |
+
return this.handleStatusCommand(context);
|
| 108 |
+
}
|
| 109 |
+
|
| 110 |
+
if (message.startsWith('/issues')) {
|
| 111 |
+
return this.handleIssuesCommand(context);
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
if (message.startsWith('/help')) {
|
| 115 |
+
return this.getHelpText();
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
// Regular conversation with AI
|
| 119 |
+
const aiContext = this.buildAIContext(context);
|
| 120 |
+
const response = await geminiService.chat(message);
|
| 121 |
+
|
| 122 |
+
return response;
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
/**
|
| 126 |
+
* Handle /scan command
|
| 127 |
+
* @param {Object} context - Conversation context
|
| 128 |
+
* @returns {Promise<string>} Response
|
| 129 |
+
*/
|
| 130 |
+
async handleScanCommand(context) {
|
| 131 |
+
try {
|
| 132 |
+
const scanResults = await projectService.scanForIssues();
|
| 133 |
+
|
| 134 |
+
if (scanResults.issues.length === 0) {
|
| 135 |
+
return '✅ Scan complete! No issues detected in the project.';
|
| 136 |
+
}
|
| 137 |
+
|
| 138 |
+
const issuesList = scanResults.issues
|
| 139 |
+
.map(
|
| 140 |
+
(i) =>
|
| 141 |
+
`• [${i.severity.toUpperCase()}] ${i.message} (${i.file})`
|
| 142 |
+
)
|
| 143 |
+
.join('\n');
|
| 144 |
+
|
| 145 |
+
return `⚠️ Scan found ${scanResults.issues.length} issues:\n\n${issuesList}`;
|
| 146 |
+
} catch (error) {
|
| 147 |
+
return `❌ Scan failed: ${error.message}`;
|
| 148 |
+
}
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
/**
|
| 152 |
+
* Handle /status command
|
| 153 |
+
* @param {Object} context - Conversation context
|
| 154 |
+
* @returns {Promise<string>} Response
|
| 155 |
+
*/
|
| 156 |
+
async handleStatusCommand(context) {
|
| 157 |
+
try {
|
| 158 |
+
const repoInfo = await githubService.getRepoInfo();
|
| 159 |
+
const issues = await githubService.listIssues('open');
|
| 160 |
+
|
| 161 |
+
return `📊 Project Status:
|
| 162 |
+
- Repository: ${repoInfo.name}
|
| 163 |
+
- Default Branch: ${repoInfo.default_branch}
|
| 164 |
+
- Stars: ${repoInfo.stargazers_count}
|
| 165 |
+
- Open Issues: ${issues.length}
|
| 166 |
+
- Last Updated: ${new Date(repoInfo.updated_at).toLocaleDateString()}`;
|
| 167 |
+
} catch (error) {
|
| 168 |
+
return `❌ Failed to get status: ${error.message}`;
|
| 169 |
+
}
|
| 170 |
+
}
|
| 171 |
+
|
| 172 |
+
/**
|
| 173 |
+
* Handle /issues command
|
| 174 |
+
* @param {Object} context - Conversation context
|
| 175 |
+
* @returns {Promise<string>} Response
|
| 176 |
+
*/
|
| 177 |
+
async handleIssuesCommand(context) {
|
| 178 |
+
try {
|
| 179 |
+
const issues = await githubService.listIssues('open');
|
| 180 |
+
|
| 181 |
+
if (issues.length === 0) {
|
| 182 |
+
return '✅ No open issues!';
|
| 183 |
+
}
|
| 184 |
+
|
| 185 |
+
const issuesList = issues
|
| 186 |
+
.slice(0, 5)
|
| 187 |
+
.map((i) => `• #${i.number}: ${i.title}`)
|
| 188 |
+
.join('\n');
|
| 189 |
+
|
| 190 |
+
return `📋 Open Issues (showing ${Math.min(5, issues.length)} of ${issues.length}):\n\n${issuesList}`;
|
| 191 |
+
} catch (error) {
|
| 192 |
+
return `❌ Failed to get issues: ${error.message}`;
|
| 193 |
+
}
|
| 194 |
+
}
|
| 195 |
+
|
| 196 |
+
/**
|
| 197 |
+
* Build AI context from conversation
|
| 198 |
+
* @param {Object} context - Conversation context
|
| 199 |
+
* @returns {string} Context string
|
| 200 |
+
*/
|
| 201 |
+
buildAIContext(context) {
|
| 202 |
+
let aiContext = 'You are an AI assistant helping with software development.';
|
| 203 |
+
|
| 204 |
+
if (context.projectContext) {
|
| 205 |
+
const projectInfo = context.projectContext;
|
| 206 |
+
aiContext += `\n\nProject Information:
|
| 207 |
+
- Files: ${projectInfo.files?.length || 0}
|
| 208 |
+
- Directories: ${projectInfo.directories?.length || 0}`;
|
| 209 |
+
}
|
| 210 |
+
|
| 211 |
+
if (context.messages.length > 0) {
|
| 212 |
+
const recentMessages = context.messages.slice(-6); // Last 3 exchanges
|
| 213 |
+
aiContext += '\n\nRecent Context:\n';
|
| 214 |
+
recentMessages.forEach((msg) => {
|
| 215 |
+
aiContext += `${msg.role === 'user' ? 'User' : 'Assistant'}: ${msg.content}\n`;
|
| 216 |
+
});
|
| 217 |
+
}
|
| 218 |
+
|
| 219 |
+
return aiContext;
|
| 220 |
+
}
|
| 221 |
+
|
| 222 |
+
/**
|
| 223 |
+
* Get help text
|
| 224 |
+
* @returns {string} Help text
|
| 225 |
+
*/
|
| 226 |
+
getHelpText() {
|
| 227 |
+
return `🤖 AI Agent Help
|
| 228 |
+
|
| 229 |
+
Available Commands:
|
| 230 |
+
• /scan - Scan project for issues
|
| 231 |
+
• /status - Get project status
|
| 232 |
+
• /issues - List open GitHub issues
|
| 233 |
+
• /help - Show this help message
|
| 234 |
+
|
| 235 |
+
Or just chat normally for project assistance!
|
| 236 |
+
|
| 237 |
+
Examples:
|
| 238 |
+
- "What are the main components of this project?"
|
| 239 |
+
- "How do I deploy this?"
|
| 240 |
+
- "Analyze the Dockerfile for me"`;
|
| 241 |
+
}
|
| 242 |
+
|
| 243 |
+
/**
|
| 244 |
+
* Get conversation history
|
| 245 |
+
* @param {string} sessionId - Session ID
|
| 246 |
+
* @returns {Array} Messages
|
| 247 |
+
*/
|
| 248 |
+
getHistory(sessionId) {
|
| 249 |
+
const context = this.conversations.get(sessionId);
|
| 250 |
+
return context?.messages || [];
|
| 251 |
+
}
|
| 252 |
+
|
| 253 |
+
/**
|
| 254 |
+
* Clear conversation
|
| 255 |
+
* @param {string} sessionId - Session ID
|
| 256 |
+
*/
|
| 257 |
+
clearConversation(sessionId) {
|
| 258 |
+
this.conversations.delete(sessionId);
|
| 259 |
+
logger.debug('Conversation cleared', { sessionId });
|
| 260 |
+
}
|
| 261 |
+
|
| 262 |
+
/**
|
| 263 |
+
* Get all active conversations count
|
| 264 |
+
* @returns {number} Count
|
| 265 |
+
*/
|
| 266 |
+
getActiveConversationCount() {
|
| 267 |
+
return this.conversations.size;
|
| 268 |
+
}
|
| 269 |
+
}
|
| 270 |
+
|
| 271 |
+
export const chatAgent = new ChatAgent();
|
|
@@ -0,0 +1,255 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* AI Scanner Agent
|
| 3 |
+
* Periodically scans project for issues and suggests fixes
|
| 4 |
+
*/
|
| 5 |
+
|
| 6 |
+
import { projectService } from '../services/project.js';
|
| 7 |
+
import { geminiService } from '../services/gemini.js';
|
| 8 |
+
import { githubService } from '../services/github.js';
|
| 9 |
+
import { config } from '../config/env.js';
|
| 10 |
+
import { logger } from '../config/logger.js';
|
| 11 |
+
|
| 12 |
+
class ScannerAgent {
|
| 13 |
+
constructor() {
|
| 14 |
+
this.lastScan = null;
|
| 15 |
+
this.scanInterval = null;
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
/**
|
| 19 |
+
* Start periodic scanning
|
| 20 |
+
*/
|
| 21 |
+
startPeriodicScanning() {
|
| 22 |
+
if (this.scanInterval) {
|
| 23 |
+
logger.warn('Scanning already active');
|
| 24 |
+
return;
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
logger.info('Starting periodic scanning', {
|
| 28 |
+
interval: config.SCAN_INTERVAL,
|
| 29 |
+
});
|
| 30 |
+
|
| 31 |
+
// Run immediately
|
| 32 |
+
this.performScan();
|
| 33 |
+
|
| 34 |
+
// Then run at intervals
|
| 35 |
+
this.scanInterval = setInterval(() => {
|
| 36 |
+
this.performScan();
|
| 37 |
+
}, config.SCAN_INTERVAL);
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
/**
|
| 41 |
+
* Stop periodic scanning
|
| 42 |
+
*/
|
| 43 |
+
stopScanning() {
|
| 44 |
+
if (this.scanInterval) {
|
| 45 |
+
clearInterval(this.scanInterval);
|
| 46 |
+
this.scanInterval = null;
|
| 47 |
+
logger.info('Periodic scanning stopped');
|
| 48 |
+
}
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
/**
|
| 52 |
+
* Perform full project scan
|
| 53 |
+
* @returns {Promise<Object>} Scan results
|
| 54 |
+
*/
|
| 55 |
+
async performScan() {
|
| 56 |
+
const startTime = Date.now();
|
| 57 |
+
|
| 58 |
+
logger.info('Starting project scan...');
|
| 59 |
+
|
| 60 |
+
try {
|
| 61 |
+
// 1. Get project structure
|
| 62 |
+
const structure = await projectService.getProjectStructure();
|
| 63 |
+
|
| 64 |
+
// 2. Scan for issues
|
| 65 |
+
const issues = await projectService.scanForIssues();
|
| 66 |
+
|
| 67 |
+
// 3. Analyze critical files
|
| 68 |
+
const analysis = await this.analyzeProject();
|
| 69 |
+
|
| 70 |
+
// 4. Generate report
|
| 71 |
+
const report = {
|
| 72 |
+
timestamp: new Date().toISOString(),
|
| 73 |
+
duration: Date.now() - startTime,
|
| 74 |
+
structure,
|
| 75 |
+
issues: issues.issues,
|
| 76 |
+
analysis,
|
| 77 |
+
recommendations: await this.generateRecommendations(issues, analysis),
|
| 78 |
+
};
|
| 79 |
+
|
| 80 |
+
this.lastScan = report;
|
| 81 |
+
|
| 82 |
+
logger.info('Scan completed', {
|
| 83 |
+
duration: report.duration,
|
| 84 |
+
issuesFound: issues.issues.length,
|
| 85 |
+
});
|
| 86 |
+
|
| 87 |
+
// 5. Optionally create issue if problems found
|
| 88 |
+
if (config.ENABLE_AUTO_FIX && issues.issues.length > 0) {
|
| 89 |
+
await this.createIssueFromScan(report);
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
return report;
|
| 93 |
+
} catch (error) {
|
| 94 |
+
logger.error('Scan failed', { error: error.message });
|
| 95 |
+
throw error;
|
| 96 |
+
}
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
/**
|
| 100 |
+
* Analyze project files with AI
|
| 101 |
+
* @returns {Promise<Object>} Analysis results
|
| 102 |
+
*/
|
| 103 |
+
async analyzeProject() {
|
| 104 |
+
try {
|
| 105 |
+
const sourceFiles = await projectService.getSourceFiles();
|
| 106 |
+
const analysis = {
|
| 107 |
+
files: [],
|
| 108 |
+
overallHealth: 'unknown',
|
| 109 |
+
};
|
| 110 |
+
|
| 111 |
+
// Analyze first 5 source files for performance
|
| 112 |
+
for (const file of sourceFiles.slice(0, 5)) {
|
| 113 |
+
try {
|
| 114 |
+
const content = await projectService.getFileContent(file.path);
|
| 115 |
+
const codeAnalysis = await geminiService.analyzeCode(
|
| 116 |
+
content,
|
| 117 |
+
this.getLanguageFromFile(file.name)
|
| 118 |
+
);
|
| 119 |
+
|
| 120 |
+
analysis.files.push({
|
| 121 |
+
name: file.name,
|
| 122 |
+
analysis: codeAnalysis,
|
| 123 |
+
});
|
| 124 |
+
} catch (e) {
|
| 125 |
+
logger.warn(`Failed to analyze ${file.name}`, { error: e.message });
|
| 126 |
+
}
|
| 127 |
+
}
|
| 128 |
+
|
| 129 |
+
return analysis;
|
| 130 |
+
} catch (error) {
|
| 131 |
+
logger.error('Project analysis failed', { error: error.message });
|
| 132 |
+
return { files: [], overallHealth: 'error' };
|
| 133 |
+
}
|
| 134 |
+
}
|
| 135 |
+
|
| 136 |
+
/**
|
| 137 |
+
* Generate recommendations based on scan
|
| 138 |
+
* @param {Object} issues - Issues found
|
| 139 |
+
* @param {Object} analysis - AI analysis
|
| 140 |
+
* @returns {Promise<Array>} Recommendations
|
| 141 |
+
*/
|
| 142 |
+
async generateRecommendations(issues, analysis) {
|
| 143 |
+
if (issues.issues.length === 0) {
|
| 144 |
+
return [
|
| 145 |
+
{
|
| 146 |
+
priority: 'low',
|
| 147 |
+
suggestion: 'Keep code reviewed regularly',
|
| 148 |
+
reason: 'No critical issues found',
|
| 149 |
+
},
|
| 150 |
+
];
|
| 151 |
+
}
|
| 152 |
+
|
| 153 |
+
try {
|
| 154 |
+
const issuesText = issues.issues
|
| 155 |
+
.map((i) => `- [${i.severity}] ${i.message}`)
|
| 156 |
+
.join('\n');
|
| 157 |
+
|
| 158 |
+
const prompt = `Based on these project issues, suggest 3 actionable fixes:
|
| 159 |
+
|
| 160 |
+
${issuesText}
|
| 161 |
+
|
| 162 |
+
For each suggestion provide:
|
| 163 |
+
1. What to fix
|
| 164 |
+
2. Why it's important
|
| 165 |
+
3. How to implement (brief)`;
|
| 166 |
+
|
| 167 |
+
const response = await geminiService.generate(prompt);
|
| 168 |
+
|
| 169 |
+
return [
|
| 170 |
+
{
|
| 171 |
+
priority: 'high',
|
| 172 |
+
suggestion: response,
|
| 173 |
+
reason: 'AI-generated recommendations based on scan',
|
| 174 |
+
},
|
| 175 |
+
];
|
| 176 |
+
} catch (error) {
|
| 177 |
+
logger.error('Failed to generate recommendations', { error: error.message });
|
| 178 |
+
return [];
|
| 179 |
+
}
|
| 180 |
+
}
|
| 181 |
+
|
| 182 |
+
/**
|
| 183 |
+
* Create GitHub issue from scan results
|
| 184 |
+
* @param {Object} report - Scan report
|
| 185 |
+
*/
|
| 186 |
+
async createIssueFromScan(report) {
|
| 187 |
+
try {
|
| 188 |
+
const issueBody = this.formatScanReportAsIssue(report);
|
| 189 |
+
|
| 190 |
+
const issue = await githubService.createIssue({
|
| 191 |
+
title: `🤖 AI Scanner: Issues Detected`,
|
| 192 |
+
body: issueBody,
|
| 193 |
+
labels: ['ai-detected', 'needs-review'],
|
| 194 |
+
});
|
| 195 |
+
|
| 196 |
+
logger.info('Created issue from scan', { issueNumber: issue.number });
|
| 197 |
+
} catch (error) {
|
| 198 |
+
logger.error('Failed to create issue', { error: error.message });
|
| 199 |
+
}
|
| 200 |
+
}
|
| 201 |
+
|
| 202 |
+
/**
|
| 203 |
+
* Format scan report as GitHub issue
|
| 204 |
+
* @param {Object} report - Scan report
|
| 205 |
+
* @returns {string} Formatted issue body
|
| 206 |
+
*/
|
| 207 |
+
formatScanReportAsIssue(report) {
|
| 208 |
+
let body = '## 🤖 AI Scanner Report\n\n';
|
| 209 |
+
|
| 210 |
+
if (report.issues.length > 0) {
|
| 211 |
+
body += '### Issues Found\n';
|
| 212 |
+
report.issues.forEach((issue) => {
|
| 213 |
+
body += `- **[${issue.severity}]** ${issue.message}\n`;
|
| 214 |
+
body += ` - File: \`${issue.file}\`\n`;
|
| 215 |
+
body += ` - Type: ${issue.type}\n\n`;
|
| 216 |
+
});
|
| 217 |
+
}
|
| 218 |
+
|
| 219 |
+
if (report.recommendations.length > 0) {
|
| 220 |
+
body += '### Recommendations\n';
|
| 221 |
+
report.recommendations.forEach((rec) => {
|
| 222 |
+
body += `- **[${rec.priority}]** ${rec.suggestion}\n`;
|
| 223 |
+
});
|
| 224 |
+
}
|
| 225 |
+
|
| 226 |
+
body += `\n---\n_Scanned at: ${report.timestamp}_\n_Scan duration: ${report.duration}ms_`;
|
| 227 |
+
|
| 228 |
+
return body;
|
| 229 |
+
}
|
| 230 |
+
|
| 231 |
+
/**
|
| 232 |
+
* Get last scan results
|
| 233 |
+
* @returns {Object} Last scan report
|
| 234 |
+
*/
|
| 235 |
+
getLastScan() {
|
| 236 |
+
return this.lastScan;
|
| 237 |
+
}
|
| 238 |
+
|
| 239 |
+
/**
|
| 240 |
+
* Helper: Get language from filename
|
| 241 |
+
* @param {string} filename - Filename
|
| 242 |
+
* @returns {string} Language
|
| 243 |
+
*/
|
| 244 |
+
getLanguageFromFile(filename) {
|
| 245 |
+
if (filename.endsWith('.ts')) return 'typescript';
|
| 246 |
+
if (filename.endsWith('.jsx')) return 'jsx';
|
| 247 |
+
if (filename.endsWith('.tsx')) return 'tsx';
|
| 248 |
+
if (filename.endsWith('.py')) return 'python';
|
| 249 |
+
if (filename.endsWith('.java')) return 'java';
|
| 250 |
+
if (filename.endsWith('.go')) return 'go';
|
| 251 |
+
return 'javascript';
|
| 252 |
+
}
|
| 253 |
+
}
|
| 254 |
+
|
| 255 |
+
export const scannerAgent = new ScannerAgent();
|
|
@@ -0,0 +1,121 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Chat API Routes
|
| 3 |
+
* WebSocket and HTTP endpoints for chat functionality
|
| 4 |
+
*/
|
| 5 |
+
|
| 6 |
+
import express from 'express';
|
| 7 |
+
import { chatAgent } from '../agents/chat.js';
|
| 8 |
+
import { logger } from '../config/logger.js';
|
| 9 |
+
|
| 10 |
+
const router = express.Router();
|
| 11 |
+
|
| 12 |
+
/**
|
| 13 |
+
* POST /api/chat/message
|
| 14 |
+
* Send message and get response
|
| 15 |
+
*/
|
| 16 |
+
router.post('/message', async (req, res) => {
|
| 17 |
+
try {
|
| 18 |
+
const { sessionId, message } = req.body;
|
| 19 |
+
|
| 20 |
+
if (!sessionId || !message) {
|
| 21 |
+
return res.status(400).json({
|
| 22 |
+
error: 'Missing required fields: sessionId, message',
|
| 23 |
+
});
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
const response = await chatAgent.processMessage(sessionId, message);
|
| 27 |
+
|
| 28 |
+
res.json({
|
| 29 |
+
success: true,
|
| 30 |
+
response,
|
| 31 |
+
timestamp: new Date().toISOString(),
|
| 32 |
+
});
|
| 33 |
+
} catch (error) {
|
| 34 |
+
logger.error('Chat message processing failed', { error: error.message });
|
| 35 |
+
res.status(500).json({
|
| 36 |
+
error: 'Failed to process message',
|
| 37 |
+
details: error.message,
|
| 38 |
+
});
|
| 39 |
+
}
|
| 40 |
+
});
|
| 41 |
+
|
| 42 |
+
/**
|
| 43 |
+
* GET /api/chat/history/:sessionId
|
| 44 |
+
* Get conversation history
|
| 45 |
+
*/
|
| 46 |
+
router.get('/history/:sessionId', (req, res) => {
|
| 47 |
+
try {
|
| 48 |
+
const { sessionId } = req.params;
|
| 49 |
+
const history = chatAgent.getHistory(sessionId);
|
| 50 |
+
|
| 51 |
+
res.json({
|
| 52 |
+
sessionId,
|
| 53 |
+
messageCount: history.length,
|
| 54 |
+
messages: history,
|
| 55 |
+
});
|
| 56 |
+
} catch (error) {
|
| 57 |
+
logger.error('Failed to get chat history', { error: error.message });
|
| 58 |
+
res.status(500).json({ error: 'Failed to retrieve history' });
|
| 59 |
+
}
|
| 60 |
+
});
|
| 61 |
+
|
| 62 |
+
/**
|
| 63 |
+
* POST /api/chat/start
|
| 64 |
+
* Start new conversation
|
| 65 |
+
*/
|
| 66 |
+
router.post('/start', (req, res) => {
|
| 67 |
+
try {
|
| 68 |
+
const sessionId = `session_${Date.now()}_${Math.random()
|
| 69 |
+
.toString(36)
|
| 70 |
+
.substr(2, 9)}`;
|
| 71 |
+
const context = chatAgent.startConversation(sessionId);
|
| 72 |
+
|
| 73 |
+
res.json({
|
| 74 |
+
success: true,
|
| 75 |
+
sessionId,
|
| 76 |
+
context,
|
| 77 |
+
});
|
| 78 |
+
} catch (error) {
|
| 79 |
+
logger.error('Failed to start conversation', { error: error.message });
|
| 80 |
+
res.status(500).json({ error: 'Failed to start conversation' });
|
| 81 |
+
}
|
| 82 |
+
});
|
| 83 |
+
|
| 84 |
+
/**
|
| 85 |
+
* DELETE /api/chat/clear/:sessionId
|
| 86 |
+
* Clear conversation
|
| 87 |
+
*/
|
| 88 |
+
router.delete('/clear/:sessionId', (req, res) => {
|
| 89 |
+
try {
|
| 90 |
+
const { sessionId } = req.params;
|
| 91 |
+
chatAgent.clearConversation(sessionId);
|
| 92 |
+
|
| 93 |
+
res.json({
|
| 94 |
+
success: true,
|
| 95 |
+
message: 'Conversation cleared',
|
| 96 |
+
});
|
| 97 |
+
} catch (error) {
|
| 98 |
+
logger.error('Failed to clear conversation', { error: error.message });
|
| 99 |
+
res.status(500).json({ error: 'Failed to clear conversation' });
|
| 100 |
+
}
|
| 101 |
+
});
|
| 102 |
+
|
| 103 |
+
/**
|
| 104 |
+
* GET /api/chat/stats
|
| 105 |
+
* Get chat statistics
|
| 106 |
+
*/
|
| 107 |
+
router.get('/stats', (req, res) => {
|
| 108 |
+
try {
|
| 109 |
+
const stats = {
|
| 110 |
+
activeConversations: chatAgent.getActiveConversationCount(),
|
| 111 |
+
timestamp: new Date().toISOString(),
|
| 112 |
+
};
|
| 113 |
+
|
| 114 |
+
res.json(stats);
|
| 115 |
+
} catch (error) {
|
| 116 |
+
logger.error('Failed to get chat stats', { error: error.message });
|
| 117 |
+
res.status(500).json({ error: 'Failed to get stats' });
|
| 118 |
+
}
|
| 119 |
+
});
|
| 120 |
+
|
| 121 |
+
export default router;
|
|
@@ -0,0 +1,136 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Project API Routes
|
| 3 |
+
* Endpoints for project information and structure
|
| 4 |
+
*/
|
| 5 |
+
|
| 6 |
+
import express from 'express';
|
| 7 |
+
import { projectService } from '../services/project.js';
|
| 8 |
+
import { logger } from '../config/logger.js';
|
| 9 |
+
|
| 10 |
+
const router = express.Router();
|
| 11 |
+
|
| 12 |
+
/**
|
| 13 |
+
* GET /api/project/structure
|
| 14 |
+
* Get project structure
|
| 15 |
+
*/
|
| 16 |
+
router.get('/structure', async (req, res) => {
|
| 17 |
+
try {
|
| 18 |
+
const structure = await projectService.getProjectStructure();
|
| 19 |
+
|
| 20 |
+
res.json({
|
| 21 |
+
success: true,
|
| 22 |
+
structure,
|
| 23 |
+
});
|
| 24 |
+
} catch (error) {
|
| 25 |
+
logger.error('Failed to get structure', { error: error.message });
|
| 26 |
+
res.status(500).json({
|
| 27 |
+
error: 'Failed to retrieve project structure',
|
| 28 |
+
details: error.message,
|
| 29 |
+
});
|
| 30 |
+
}
|
| 31 |
+
});
|
| 32 |
+
|
| 33 |
+
/**
|
| 34 |
+
* GET /api/project/file
|
| 35 |
+
* Get file content (with path query param)
|
| 36 |
+
*/
|
| 37 |
+
router.get('/file', async (req, res) => {
|
| 38 |
+
try {
|
| 39 |
+
const { path } = req.query;
|
| 40 |
+
|
| 41 |
+
if (!path) {
|
| 42 |
+
return res.status(400).json({
|
| 43 |
+
error: 'Missing required query parameter: path',
|
| 44 |
+
});
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
const content = await projectService.getFileContent(path);
|
| 48 |
+
|
| 49 |
+
res.json({
|
| 50 |
+
success: true,
|
| 51 |
+
path,
|
| 52 |
+
content,
|
| 53 |
+
});
|
| 54 |
+
} catch (error) {
|
| 55 |
+
logger.error('Failed to get file', { error: error.message });
|
| 56 |
+
res.status(500).json({
|
| 57 |
+
error: 'Failed to retrieve file',
|
| 58 |
+
details: error.message,
|
| 59 |
+
});
|
| 60 |
+
}
|
| 61 |
+
});
|
| 62 |
+
|
| 63 |
+
/**
|
| 64 |
+
* GET /api/project/readme
|
| 65 |
+
* Get README content
|
| 66 |
+
*/
|
| 67 |
+
router.get('/readme', async (req, res) => {
|
| 68 |
+
try {
|
| 69 |
+
const content = await projectService.getReadme();
|
| 70 |
+
|
| 71 |
+
if (!content) {
|
| 72 |
+
return res.status(404).json({
|
| 73 |
+
error: 'README not found',
|
| 74 |
+
});
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
res.json({
|
| 78 |
+
success: true,
|
| 79 |
+
content,
|
| 80 |
+
});
|
| 81 |
+
} catch (error) {
|
| 82 |
+
logger.error('Failed to get README', { error: error.message });
|
| 83 |
+
res.status(500).json({
|
| 84 |
+
error: 'Failed to retrieve README',
|
| 85 |
+
});
|
| 86 |
+
}
|
| 87 |
+
});
|
| 88 |
+
|
| 89 |
+
/**
|
| 90 |
+
* GET /api/project/dockerfile
|
| 91 |
+
* Get Dockerfile content
|
| 92 |
+
*/
|
| 93 |
+
router.get('/dockerfile', async (req, res) => {
|
| 94 |
+
try {
|
| 95 |
+
const content = await projectService.getDockerfile();
|
| 96 |
+
|
| 97 |
+
if (!content) {
|
| 98 |
+
return res.status(404).json({
|
| 99 |
+
error: 'Dockerfile not found',
|
| 100 |
+
});
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
res.json({
|
| 104 |
+
success: true,
|
| 105 |
+
content,
|
| 106 |
+
});
|
| 107 |
+
} catch (error) {
|
| 108 |
+
logger.error('Failed to get Dockerfile', { error: error.message });
|
| 109 |
+
res.status(500).json({
|
| 110 |
+
error: 'Failed to retrieve Dockerfile',
|
| 111 |
+
});
|
| 112 |
+
}
|
| 113 |
+
});
|
| 114 |
+
|
| 115 |
+
/**
|
| 116 |
+
* GET /api/project/source-files
|
| 117 |
+
* Get list of source files
|
| 118 |
+
*/
|
| 119 |
+
router.get('/source-files', async (req, res) => {
|
| 120 |
+
try {
|
| 121 |
+
const files = await projectService.getSourceFiles();
|
| 122 |
+
|
| 123 |
+
res.json({
|
| 124 |
+
success: true,
|
| 125 |
+
files,
|
| 126 |
+
count: files.length,
|
| 127 |
+
});
|
| 128 |
+
} catch (error) {
|
| 129 |
+
logger.error('Failed to get source files', { error: error.message });
|
| 130 |
+
res.status(500).json({
|
| 131 |
+
error: 'Failed to retrieve source files',
|
| 132 |
+
});
|
| 133 |
+
}
|
| 134 |
+
});
|
| 135 |
+
|
| 136 |
+
export default router;
|
|
@@ -0,0 +1,116 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Scanner API Routes
|
| 3 |
+
* Endpoints for project scanning and issue detection
|
| 4 |
+
*/
|
| 5 |
+
|
| 6 |
+
import express from 'express';
|
| 7 |
+
import { scannerAgent } from '../agents/scanner.js';
|
| 8 |
+
import { projectService } from '../services/project.js';
|
| 9 |
+
import { logger } from '../config/logger.js';
|
| 10 |
+
|
| 11 |
+
const router = express.Router();
|
| 12 |
+
|
| 13 |
+
/**
|
| 14 |
+
* POST /api/scanner/scan
|
| 15 |
+
* Trigger manual scan
|
| 16 |
+
*/
|
| 17 |
+
router.post('/scan', async (req, res) => {
|
| 18 |
+
try {
|
| 19 |
+
logger.info('Manual scan triggered');
|
| 20 |
+
const report = await scannerAgent.performScan();
|
| 21 |
+
|
| 22 |
+
res.json({
|
| 23 |
+
success: true,
|
| 24 |
+
report,
|
| 25 |
+
});
|
| 26 |
+
} catch (error) {
|
| 27 |
+
logger.error('Scan failed', { error: error.message });
|
| 28 |
+
res.status(500).json({
|
| 29 |
+
error: 'Scan failed',
|
| 30 |
+
details: error.message,
|
| 31 |
+
});
|
| 32 |
+
}
|
| 33 |
+
});
|
| 34 |
+
|
| 35 |
+
/**
|
| 36 |
+
* GET /api/scanner/last-report
|
| 37 |
+
* Get last scan report
|
| 38 |
+
*/
|
| 39 |
+
router.get('/last-report', (req, res) => {
|
| 40 |
+
try {
|
| 41 |
+
const report = scannerAgent.getLastScan();
|
| 42 |
+
|
| 43 |
+
if (!report) {
|
| 44 |
+
return res.status(404).json({
|
| 45 |
+
error: 'No scan report available yet',
|
| 46 |
+
});
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
res.json({
|
| 50 |
+
success: true,
|
| 51 |
+
report,
|
| 52 |
+
});
|
| 53 |
+
} catch (error) {
|
| 54 |
+
logger.error('Failed to get report', { error: error.message });
|
| 55 |
+
res.status(500).json({ error: 'Failed to retrieve report' });
|
| 56 |
+
}
|
| 57 |
+
});
|
| 58 |
+
|
| 59 |
+
/**
|
| 60 |
+
* GET /api/scanner/issues
|
| 61 |
+
* Get detected issues
|
| 62 |
+
*/
|
| 63 |
+
router.get('/issues', async (req, res) => {
|
| 64 |
+
try {
|
| 65 |
+
const issues = await projectService.scanForIssues();
|
| 66 |
+
|
| 67 |
+
res.json({
|
| 68 |
+
success: true,
|
| 69 |
+
...issues,
|
| 70 |
+
});
|
| 71 |
+
} catch (error) {
|
| 72 |
+
logger.error('Failed to scan issues', { error: error.message });
|
| 73 |
+
res.status(500).json({
|
| 74 |
+
error: 'Failed to scan issues',
|
| 75 |
+
details: error.message,
|
| 76 |
+
});
|
| 77 |
+
}
|
| 78 |
+
});
|
| 79 |
+
|
| 80 |
+
/**
|
| 81 |
+
* POST /api/scanner/start-continuous
|
| 82 |
+
* Start continuous scanning
|
| 83 |
+
*/
|
| 84 |
+
router.post('/start-continuous', (req, res) => {
|
| 85 |
+
try {
|
| 86 |
+
scannerAgent.startPeriodicScanning();
|
| 87 |
+
|
| 88 |
+
res.json({
|
| 89 |
+
success: true,
|
| 90 |
+
message: 'Continuous scanning started',
|
| 91 |
+
});
|
| 92 |
+
} catch (error) {
|
| 93 |
+
logger.error('Failed to start scanning', { error: error.message });
|
| 94 |
+
res.status(500).json({ error: 'Failed to start scanning' });
|
| 95 |
+
}
|
| 96 |
+
});
|
| 97 |
+
|
| 98 |
+
/**
|
| 99 |
+
* POST /api/scanner/stop-continuous
|
| 100 |
+
* Stop continuous scanning
|
| 101 |
+
*/
|
| 102 |
+
router.post('/stop-continuous', (req, res) => {
|
| 103 |
+
try {
|
| 104 |
+
scannerAgent.stopScanning();
|
| 105 |
+
|
| 106 |
+
res.json({
|
| 107 |
+
success: true,
|
| 108 |
+
message: 'Continuous scanning stopped',
|
| 109 |
+
});
|
| 110 |
+
} catch (error) {
|
| 111 |
+
logger.error('Failed to stop scanning', { error: error.message });
|
| 112 |
+
res.status(500).json({ error: 'Failed to stop scanning' });
|
| 113 |
+
}
|
| 114 |
+
});
|
| 115 |
+
|
| 116 |
+
export default router;
|
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Environment Configuration
|
| 3 |
+
* Centralized configuration management for all services
|
| 4 |
+
*/
|
| 5 |
+
|
| 6 |
+
export const config = {
|
| 7 |
+
// Server
|
| 8 |
+
PORT: process.env.PORT || 3000,
|
| 9 |
+
NODE_ENV: process.env.NODE_ENV || 'development',
|
| 10 |
+
|
| 11 |
+
// API Keys
|
| 12 |
+
GEMINI_API_KEY: process.env.GEMINI_API_KEY || '',
|
| 13 |
+
GITHUB_TOKEN: process.env.GITHUB_TOKEN || '',
|
| 14 |
+
HF_TOKEN: process.env.HF_TOKEN || '',
|
| 15 |
+
|
| 16 |
+
// GitHub
|
| 17 |
+
GITHUB_REPO: process.env.GITHUB_REPO || 'NLarchive/my-webapp-hf',
|
| 18 |
+
GITHUB_OWNER: process.env.GITHUB_OWNER || 'NLarchive',
|
| 19 |
+
GITHUB_BRANCH: process.env.GITHUB_BRANCH || 'main',
|
| 20 |
+
|
| 21 |
+
// Hugging Face
|
| 22 |
+
HF_SPACE_NAME: process.env.HF_SPACE_NAME || 'my-webapp-hf',
|
| 23 |
+
HF_SPACE_URL: process.env.HF_SPACE_URL || 'https://huggingface.co/spaces/NLarchive/my-webapp-hf',
|
| 24 |
+
|
| 25 |
+
// Scanner Configuration
|
| 26 |
+
SCAN_INTERVAL: parseInt(process.env.SCAN_INTERVAL || '3600000', 10), // 1 hour in ms
|
| 27 |
+
ENABLE_AUTO_FIX: process.env.ENABLE_AUTO_FIX === 'true',
|
| 28 |
+
AUTO_COMMIT: process.env.AUTO_COMMIT === 'true',
|
| 29 |
+
|
| 30 |
+
// Logging
|
| 31 |
+
LOG_LEVEL: process.env.LOG_LEVEL || 'info',
|
| 32 |
+
DEBUG: process.env.DEBUG === 'true',
|
| 33 |
+
};
|
| 34 |
+
|
| 35 |
+
export function validateConfig() {
|
| 36 |
+
const required = ['GEMINI_API_KEY', 'GITHUB_TOKEN'];
|
| 37 |
+
const missing = required.filter(key => !config[key]);
|
| 38 |
+
|
| 39 |
+
if (missing.length > 0) {
|
| 40 |
+
console.error(`Missing required environment variables: ${missing.join(', ')}`);
|
| 41 |
+
process.exit(1);
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
return true;
|
| 45 |
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Logger Service
|
| 3 |
+
* Centralized logging for all modules
|
| 4 |
+
*/
|
| 5 |
+
|
| 6 |
+
import { config } from './env.js';
|
| 7 |
+
|
| 8 |
+
const LOG_LEVELS = {
|
| 9 |
+
error: 0,
|
| 10 |
+
warn: 1,
|
| 11 |
+
info: 2,
|
| 12 |
+
debug: 3,
|
| 13 |
+
};
|
| 14 |
+
|
| 15 |
+
const LEVEL_NAMES = {
|
| 16 |
+
0: 'ERROR',
|
| 17 |
+
1: 'WARN',
|
| 18 |
+
2: 'INFO',
|
| 19 |
+
3: 'DEBUG',
|
| 20 |
+
};
|
| 21 |
+
|
| 22 |
+
const currentLevel = LOG_LEVELS[config.LOG_LEVEL] ?? 2;
|
| 23 |
+
|
| 24 |
+
export const logger = {
|
| 25 |
+
error: (message, data = {}) => {
|
| 26 |
+
if (currentLevel >= LOG_LEVELS.error) {
|
| 27 |
+
console.error(`[ERROR] ${new Date().toISOString()} - ${message}`, data);
|
| 28 |
+
}
|
| 29 |
+
},
|
| 30 |
+
|
| 31 |
+
warn: (message, data = {}) => {
|
| 32 |
+
if (currentLevel >= LOG_LEVELS.warn) {
|
| 33 |
+
console.warn(`[WARN] ${new Date().toISOString()} - ${message}`, data);
|
| 34 |
+
}
|
| 35 |
+
},
|
| 36 |
+
|
| 37 |
+
info: (message, data = {}) => {
|
| 38 |
+
if (currentLevel >= LOG_LEVELS.info) {
|
| 39 |
+
console.log(`[INFO] ${new Date().toISOString()} - ${message}`, data);
|
| 40 |
+
}
|
| 41 |
+
},
|
| 42 |
+
|
| 43 |
+
debug: (message, data = {}) => {
|
| 44 |
+
if (currentLevel >= LOG_LEVELS.debug) {
|
| 45 |
+
console.log(`[DEBUG] ${new Date().toISOString()} - ${message}`, data);
|
| 46 |
+
}
|
| 47 |
+
},
|
| 48 |
+
};
|
|
@@ -0,0 +1,92 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Main Server Entry Point
|
| 3 |
+
* Express server setup with API routes and scheduled tasks
|
| 4 |
+
*/
|
| 5 |
+
|
| 6 |
+
import express from 'express';
|
| 7 |
+
import cors from 'cors';
|
| 8 |
+
import bodyParser from 'body-parser';
|
| 9 |
+
import { config, validateConfig } from './config/env.js';
|
| 10 |
+
import { logger } from './config/logger.js';
|
| 11 |
+
import { scannerAgent } from './agents/scanner.js';
|
| 12 |
+
|
| 13 |
+
// API Routes
|
| 14 |
+
import chatRoutes from './api/chat.js';
|
| 15 |
+
import scannerRoutes from './api/scanner.js';
|
| 16 |
+
import projectRoutes from './api/project.js';
|
| 17 |
+
|
| 18 |
+
const app = express();
|
| 19 |
+
|
| 20 |
+
// Middleware
|
| 21 |
+
app.use(cors());
|
| 22 |
+
app.use(bodyParser.json({ limit: '10mb' }));
|
| 23 |
+
app.use(bodyParser.urlencoded({ limit: '10mb', extended: true }));
|
| 24 |
+
|
| 25 |
+
// Request logging middleware
|
| 26 |
+
app.use((req, res, next) => {
|
| 27 |
+
logger.debug(`${req.method} ${req.path}`);
|
| 28 |
+
next();
|
| 29 |
+
});
|
| 30 |
+
|
| 31 |
+
// Health check
|
| 32 |
+
app.get('/health', (req, res) => {
|
| 33 |
+
res.json({
|
| 34 |
+
status: 'ok',
|
| 35 |
+
timestamp: new Date().toISOString(),
|
| 36 |
+
uptime: process.uptime(),
|
| 37 |
+
});
|
| 38 |
+
});
|
| 39 |
+
|
| 40 |
+
// API Routes
|
| 41 |
+
app.use('/api/chat', chatRoutes);
|
| 42 |
+
app.use('/api/scanner', scannerRoutes);
|
| 43 |
+
app.use('/api/project', projectRoutes);
|
| 44 |
+
|
| 45 |
+
// Serve static files
|
| 46 |
+
app.use(express.static('public'));
|
| 47 |
+
|
| 48 |
+
// 404 handler
|
| 49 |
+
app.use((req, res) => {
|
| 50 |
+
res.status(404).json({ error: 'Not found' });
|
| 51 |
+
});
|
| 52 |
+
|
| 53 |
+
// Error handler
|
| 54 |
+
app.use((err, req, res, next) => {
|
| 55 |
+
logger.error('Unhandled error', { error: err.message });
|
| 56 |
+
res.status(500).json({
|
| 57 |
+
error: 'Internal server error',
|
| 58 |
+
message: config.DEBUG ? err.message : 'Something went wrong',
|
| 59 |
+
});
|
| 60 |
+
});
|
| 61 |
+
|
| 62 |
+
// Start server
|
| 63 |
+
async function startServer() {
|
| 64 |
+
try {
|
| 65 |
+
// Validate configuration
|
| 66 |
+
validateConfig();
|
| 67 |
+
|
| 68 |
+
// Start server
|
| 69 |
+
app.listen(config.PORT, () => {
|
| 70 |
+
logger.info(`Server started on port ${config.PORT}`);
|
| 71 |
+
});
|
| 72 |
+
|
| 73 |
+
// Start scanner if enabled
|
| 74 |
+
if (config.ENABLE_AUTO_FIX) {
|
| 75 |
+
scannerAgent.startPeriodicScanning();
|
| 76 |
+
} else {
|
| 77 |
+
logger.info('Auto-fix is disabled');
|
| 78 |
+
}
|
| 79 |
+
} catch (error) {
|
| 80 |
+
logger.error('Failed to start server', { error: error.message });
|
| 81 |
+
process.exit(1);
|
| 82 |
+
}
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
// Graceful shutdown
|
| 86 |
+
process.on('SIGINT', () => {
|
| 87 |
+
logger.info('Shutting down gracefully...');
|
| 88 |
+
scannerAgent.stopScanning();
|
| 89 |
+
process.exit(0);
|
| 90 |
+
});
|
| 91 |
+
|
| 92 |
+
startServer();
|
|
@@ -0,0 +1,140 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Gemini AI Service
|
| 3 |
+
* Handles all interactions with Google's Generative AI API
|
| 4 |
+
*/
|
| 5 |
+
|
| 6 |
+
import { GoogleGenerativeAI } from '@google/generative-ai';
|
| 7 |
+
import { config } from '../config/env.js';
|
| 8 |
+
import { logger } from '../config/logger.js';
|
| 9 |
+
|
| 10 |
+
class GeminiService {
|
| 11 |
+
constructor() {
|
| 12 |
+
this.client = new GoogleGenerativeAI(config.GEMINI_API_KEY);
|
| 13 |
+
this.model = this.client.getGenerativeModel({ model: 'gemini-pro' });
|
| 14 |
+
this.conversationHistory = [];
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
/**
|
| 18 |
+
* Generate a response from Gemini
|
| 19 |
+
* @param {string} prompt - User prompt or system message
|
| 20 |
+
* @param {Object} options - Configuration options
|
| 21 |
+
* @returns {Promise<string>} AI response
|
| 22 |
+
*/
|
| 23 |
+
async generate(prompt, options = {}) {
|
| 24 |
+
try {
|
| 25 |
+
const context = options.context || '';
|
| 26 |
+
const fullPrompt = context ? `${context}\n\n${prompt}` : prompt;
|
| 27 |
+
|
| 28 |
+
const result = await this.model.generateContent(fullPrompt);
|
| 29 |
+
const response = await result.response;
|
| 30 |
+
const text = response.text();
|
| 31 |
+
|
| 32 |
+
logger.debug('Gemini response generated', { length: text.length });
|
| 33 |
+
return text;
|
| 34 |
+
} catch (error) {
|
| 35 |
+
logger.error('Gemini generation failed', { error: error.message });
|
| 36 |
+
throw error;
|
| 37 |
+
}
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
/**
|
| 41 |
+
* Multi-turn conversation
|
| 42 |
+
* @param {string} userMessage - User message
|
| 43 |
+
* @returns {Promise<string>} AI response
|
| 44 |
+
*/
|
| 45 |
+
async chat(userMessage) {
|
| 46 |
+
try {
|
| 47 |
+
this.conversationHistory.push({
|
| 48 |
+
role: 'user',
|
| 49 |
+
content: userMessage,
|
| 50 |
+
});
|
| 51 |
+
|
| 52 |
+
const chat = this.model.startChat({
|
| 53 |
+
history: this.conversationHistory,
|
| 54 |
+
});
|
| 55 |
+
|
| 56 |
+
const result = await chat.sendMessage(userMessage);
|
| 57 |
+
const response = await result.response;
|
| 58 |
+
const text = response.text();
|
| 59 |
+
|
| 60 |
+
this.conversationHistory.push({
|
| 61 |
+
role: 'assistant',
|
| 62 |
+
content: text,
|
| 63 |
+
});
|
| 64 |
+
|
| 65 |
+
return text;
|
| 66 |
+
} catch (error) {
|
| 67 |
+
logger.error('Chat failed', { error: error.message });
|
| 68 |
+
throw error;
|
| 69 |
+
}
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
/**
|
| 73 |
+
* Analyze code for issues
|
| 74 |
+
* @param {string} code - Code to analyze
|
| 75 |
+
* @param {string} language - Programming language
|
| 76 |
+
* @returns {Promise<Object>} Analysis results
|
| 77 |
+
*/
|
| 78 |
+
async analyzeCode(code, language = 'javascript') {
|
| 79 |
+
const prompt = `Analyze this ${language} code for issues, security problems, and improvements:
|
| 80 |
+
|
| 81 |
+
\`\`\`${language}
|
| 82 |
+
${code}
|
| 83 |
+
\`\`\`
|
| 84 |
+
|
| 85 |
+
Provide:
|
| 86 |
+
1. Issues found (if any)
|
| 87 |
+
2. Security concerns (if any)
|
| 88 |
+
3. Performance improvements
|
| 89 |
+
4. Suggested fixes (with code snippets)
|
| 90 |
+
|
| 91 |
+
Format as JSON with keys: "issues", "security", "performance", "fixes"`;
|
| 92 |
+
|
| 93 |
+
const response = await this.generate(prompt);
|
| 94 |
+
|
| 95 |
+
try {
|
| 96 |
+
// Extract JSON from response
|
| 97 |
+
const jsonMatch = response.match(/\{[\s\S]*\}/);
|
| 98 |
+
return jsonMatch ? JSON.parse(jsonMatch[0]) : { raw: response };
|
| 99 |
+
} catch (e) {
|
| 100 |
+
return { raw: response };
|
| 101 |
+
}
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
/**
|
| 105 |
+
* Generate commit message from changes
|
| 106 |
+
* @param {Array} changes - List of file changes
|
| 107 |
+
* @returns {Promise<string>} Suggested commit message
|
| 108 |
+
*/
|
| 109 |
+
async generateCommitMessage(changes) {
|
| 110 |
+
const changesSummary = changes
|
| 111 |
+
.map((c) => `- ${c.type}: ${c.file} - ${c.description}`)
|
| 112 |
+
.join('\n');
|
| 113 |
+
|
| 114 |
+
const prompt = `Based on these changes, generate a concise conventional commit message:
|
| 115 |
+
|
| 116 |
+
${changesSummary}
|
| 117 |
+
|
| 118 |
+
Follow the pattern: type(scope): description`;
|
| 119 |
+
|
| 120 |
+
return this.generate(prompt);
|
| 121 |
+
}
|
| 122 |
+
|
| 123 |
+
/**
|
| 124 |
+
* Clear conversation history
|
| 125 |
+
*/
|
| 126 |
+
clearHistory() {
|
| 127 |
+
this.conversationHistory = [];
|
| 128 |
+
logger.debug('Conversation history cleared');
|
| 129 |
+
}
|
| 130 |
+
|
| 131 |
+
/**
|
| 132 |
+
* Get conversation history
|
| 133 |
+
* @returns {Array} Conversation history
|
| 134 |
+
*/
|
| 135 |
+
getHistory() {
|
| 136 |
+
return this.conversationHistory;
|
| 137 |
+
}
|
| 138 |
+
}
|
| 139 |
+
|
| 140 |
+
export const geminiService = new GeminiService();
|
|
@@ -0,0 +1,186 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* GitHub Service
|
| 3 |
+
* Handles GitHub API interactions (PR creation, issue creation, commits, etc.)
|
| 4 |
+
*/
|
| 5 |
+
|
| 6 |
+
import axios from 'axios';
|
| 7 |
+
import { config } from '../config/env.js';
|
| 8 |
+
import { logger } from '../config/logger.js';
|
| 9 |
+
|
| 10 |
+
class GitHubService {
|
| 11 |
+
constructor() {
|
| 12 |
+
this.api = axios.create({
|
| 13 |
+
baseURL: 'https://api.github.com',
|
| 14 |
+
headers: {
|
| 15 |
+
Authorization: `token ${config.GITHUB_TOKEN}`,
|
| 16 |
+
Accept: 'application/vnd.github.v3+json',
|
| 17 |
+
},
|
| 18 |
+
});
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
/**
|
| 22 |
+
* Create a GitHub issue
|
| 23 |
+
* @param {Object} issue - Issue details
|
| 24 |
+
* @returns {Promise<Object>} Created issue
|
| 25 |
+
*/
|
| 26 |
+
async createIssue(issue) {
|
| 27 |
+
try {
|
| 28 |
+
const { title, body, labels = [], assignees = [] } = issue;
|
| 29 |
+
|
| 30 |
+
const response = await this.api.post(
|
| 31 |
+
`/repos/${config.GITHUB_REPO}/issues`,
|
| 32 |
+
{ title, body, labels, assignees }
|
| 33 |
+
);
|
| 34 |
+
|
| 35 |
+
logger.info('GitHub issue created', { issueNumber: response.data.number });
|
| 36 |
+
return response.data;
|
| 37 |
+
} catch (error) {
|
| 38 |
+
logger.error('Failed to create GitHub issue', { error: error.message });
|
| 39 |
+
throw error;
|
| 40 |
+
}
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
/**
|
| 44 |
+
* Create a pull request
|
| 45 |
+
* @param {Object} pr - PR details
|
| 46 |
+
* @returns {Promise<Object>} Created PR
|
| 47 |
+
*/
|
| 48 |
+
async createPullRequest(pr) {
|
| 49 |
+
try {
|
| 50 |
+
const {
|
| 51 |
+
title,
|
| 52 |
+
body,
|
| 53 |
+
head,
|
| 54 |
+
base = config.GITHUB_BRANCH,
|
| 55 |
+
draft = false,
|
| 56 |
+
} = pr;
|
| 57 |
+
|
| 58 |
+
const response = await this.api.post(
|
| 59 |
+
`/repos/${config.GITHUB_REPO}/pulls`,
|
| 60 |
+
{ title, body, head, base, draft }
|
| 61 |
+
);
|
| 62 |
+
|
| 63 |
+
logger.info('GitHub PR created', { prNumber: response.data.number });
|
| 64 |
+
return response.data;
|
| 65 |
+
} catch (error) {
|
| 66 |
+
logger.error('Failed to create GitHub PR', { error: error.message });
|
| 67 |
+
throw error;
|
| 68 |
+
}
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
/**
|
| 72 |
+
* Get repository contents
|
| 73 |
+
* @param {string} path - File path
|
| 74 |
+
* @returns {Promise<Object>} File contents
|
| 75 |
+
*/
|
| 76 |
+
async getFileContents(path = '') {
|
| 77 |
+
try {
|
| 78 |
+
const response = await this.api.get(
|
| 79 |
+
`/repos/${config.GITHUB_REPO}/contents/${path}`
|
| 80 |
+
);
|
| 81 |
+
|
| 82 |
+
return response.data;
|
| 83 |
+
} catch (error) {
|
| 84 |
+
logger.error('Failed to get file contents', { error: error.message, path });
|
| 85 |
+
throw error;
|
| 86 |
+
}
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
/**
|
| 90 |
+
* List files in directory
|
| 91 |
+
* @param {string} path - Directory path
|
| 92 |
+
* @returns {Promise<Array>} File list
|
| 93 |
+
*/
|
| 94 |
+
async listFiles(path = '') {
|
| 95 |
+
try {
|
| 96 |
+
const contents = await this.getFileContents(path);
|
| 97 |
+
|
| 98 |
+
if (Array.isArray(contents)) {
|
| 99 |
+
return contents;
|
| 100 |
+
}
|
| 101 |
+
return [contents];
|
| 102 |
+
} catch (error) {
|
| 103 |
+
logger.error('Failed to list files', { error: error.message, path });
|
| 104 |
+
throw error;
|
| 105 |
+
}
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
/**
|
| 109 |
+
* Trigger a GitHub Actions workflow
|
| 110 |
+
* @param {string} workflowId - Workflow ID or filename
|
| 111 |
+
* @param {Object} inputs - Workflow inputs
|
| 112 |
+
* @returns {Promise<Object>} Dispatch result
|
| 113 |
+
*/
|
| 114 |
+
async triggerWorkflow(workflowId, inputs = {}) {
|
| 115 |
+
try {
|
| 116 |
+
const response = await this.api.post(
|
| 117 |
+
`/repos/${config.GITHUB_REPO}/actions/workflows/${workflowId}/dispatches`,
|
| 118 |
+
{
|
| 119 |
+
ref: config.GITHUB_BRANCH,
|
| 120 |
+
inputs,
|
| 121 |
+
}
|
| 122 |
+
);
|
| 123 |
+
|
| 124 |
+
logger.info('Workflow triggered', { workflowId });
|
| 125 |
+
return response.data;
|
| 126 |
+
} catch (error) {
|
| 127 |
+
logger.error('Failed to trigger workflow', { error: error.message });
|
| 128 |
+
throw error;
|
| 129 |
+
}
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
/**
|
| 133 |
+
* Get repository information
|
| 134 |
+
* @returns {Promise<Object>} Repository info
|
| 135 |
+
*/
|
| 136 |
+
async getRepoInfo() {
|
| 137 |
+
try {
|
| 138 |
+
const response = await this.api.get(`/repos/${config.GITHUB_REPO}`);
|
| 139 |
+
return response.data;
|
| 140 |
+
} catch (error) {
|
| 141 |
+
logger.error('Failed to get repo info', { error: error.message });
|
| 142 |
+
throw error;
|
| 143 |
+
}
|
| 144 |
+
}
|
| 145 |
+
|
| 146 |
+
/**
|
| 147 |
+
* Get list of issues
|
| 148 |
+
* @param {string} state - 'open' or 'closed'
|
| 149 |
+
* @returns {Promise<Array>} Issues list
|
| 150 |
+
*/
|
| 151 |
+
async listIssues(state = 'open') {
|
| 152 |
+
try {
|
| 153 |
+
const response = await this.api.get(
|
| 154 |
+
`/repos/${config.GITHUB_REPO}/issues`,
|
| 155 |
+
{ params: { state } }
|
| 156 |
+
);
|
| 157 |
+
return response.data;
|
| 158 |
+
} catch (error) {
|
| 159 |
+
logger.error('Failed to list issues', { error: error.message });
|
| 160 |
+
throw error;
|
| 161 |
+
}
|
| 162 |
+
}
|
| 163 |
+
|
| 164 |
+
/**
|
| 165 |
+
* Add comment to issue
|
| 166 |
+
* @param {number} issueNumber - Issue number
|
| 167 |
+
* @param {string} body - Comment body
|
| 168 |
+
* @returns {Promise<Object>} Comment result
|
| 169 |
+
*/
|
| 170 |
+
async addIssueComment(issueNumber, body) {
|
| 171 |
+
try {
|
| 172 |
+
const response = await this.api.post(
|
| 173 |
+
`/repos/${config.GITHUB_REPO}/issues/${issueNumber}/comments`,
|
| 174 |
+
{ body }
|
| 175 |
+
);
|
| 176 |
+
|
| 177 |
+
logger.info('Issue comment added', { issueNumber });
|
| 178 |
+
return response.data;
|
| 179 |
+
} catch (error) {
|
| 180 |
+
logger.error('Failed to add issue comment', { error: error.message });
|
| 181 |
+
throw error;
|
| 182 |
+
}
|
| 183 |
+
}
|
| 184 |
+
}
|
| 185 |
+
|
| 186 |
+
export const githubService = new GitHubService();
|
|
@@ -0,0 +1,198 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Project Service
|
| 3 |
+
* Reads and analyzes project files from the repository
|
| 4 |
+
*/
|
| 5 |
+
|
| 6 |
+
import { githubService } from './github.js';
|
| 7 |
+
import { logger } from '../config/logger.js';
|
| 8 |
+
|
| 9 |
+
class ProjectService {
|
| 10 |
+
constructor() {
|
| 11 |
+
this.cachedProjectStructure = null;
|
| 12 |
+
this.cacheExpiry = 0;
|
| 13 |
+
this.CACHE_TTL = 5 * 60 * 1000; // 5 minutes
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
/**
|
| 17 |
+
* Get project structure
|
| 18 |
+
* @returns {Promise<Object>} Project structure
|
| 19 |
+
*/
|
| 20 |
+
async getProjectStructure() {
|
| 21 |
+
const now = Date.now();
|
| 22 |
+
|
| 23 |
+
if (this.cachedProjectStructure && now < this.cacheExpiry) {
|
| 24 |
+
logger.debug('Using cached project structure');
|
| 25 |
+
return this.cachedProjectStructure;
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
try {
|
| 29 |
+
const root = await githubService.listFiles('');
|
| 30 |
+
const structure = {
|
| 31 |
+
files: [],
|
| 32 |
+
directories: [],
|
| 33 |
+
timestamp: new Date().toISOString(),
|
| 34 |
+
};
|
| 35 |
+
|
| 36 |
+
for (const item of root) {
|
| 37 |
+
if (item.type === 'file') {
|
| 38 |
+
structure.files.push({
|
| 39 |
+
name: item.name,
|
| 40 |
+
path: item.path,
|
| 41 |
+
size: item.size,
|
| 42 |
+
});
|
| 43 |
+
} else if (item.type === 'dir') {
|
| 44 |
+
structure.directories.push({
|
| 45 |
+
name: item.name,
|
| 46 |
+
path: item.path,
|
| 47 |
+
});
|
| 48 |
+
}
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
this.cachedProjectStructure = structure;
|
| 52 |
+
this.cacheExpiry = now + this.CACHE_TTL;
|
| 53 |
+
|
| 54 |
+
logger.info('Project structure retrieved', {
|
| 55 |
+
files: structure.files.length,
|
| 56 |
+
dirs: structure.directories.length,
|
| 57 |
+
});
|
| 58 |
+
|
| 59 |
+
return structure;
|
| 60 |
+
} catch (error) {
|
| 61 |
+
logger.error('Failed to get project structure', { error: error.message });
|
| 62 |
+
throw error;
|
| 63 |
+
}
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
/**
|
| 67 |
+
* Get specific file content
|
| 68 |
+
* @param {string} filePath - Path to file
|
| 69 |
+
* @returns {Promise<string>} File content
|
| 70 |
+
*/
|
| 71 |
+
async getFileContent(filePath) {
|
| 72 |
+
try {
|
| 73 |
+
const response = await githubService.getFileContents(filePath);
|
| 74 |
+
|
| 75 |
+
if (response.type === 'file') {
|
| 76 |
+
// Decode base64 content
|
| 77 |
+
return Buffer.from(response.content, 'base64').toString('utf-8');
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
throw new Error('Path is not a file');
|
| 81 |
+
} catch (error) {
|
| 82 |
+
logger.error('Failed to read file', { error: error.message, filePath });
|
| 83 |
+
throw error;
|
| 84 |
+
}
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
/**
|
| 88 |
+
* Scan project for common issues
|
| 89 |
+
* @returns {Promise<Object>} Scan results
|
| 90 |
+
*/
|
| 91 |
+
async scanForIssues() {
|
| 92 |
+
try {
|
| 93 |
+
const structure = await this.getProjectStructure();
|
| 94 |
+
const issues = [];
|
| 95 |
+
|
| 96 |
+
// Check for missing critical files
|
| 97 |
+
const criticalFiles = ['package.json', 'README.md', 'Dockerfile'];
|
| 98 |
+
const fileNames = structure.files.map((f) => f.name);
|
| 99 |
+
|
| 100 |
+
for (const file of criticalFiles) {
|
| 101 |
+
if (!fileNames.includes(file)) {
|
| 102 |
+
issues.push({
|
| 103 |
+
type: 'missing_file',
|
| 104 |
+
severity: 'high',
|
| 105 |
+
file,
|
| 106 |
+
message: `Critical file missing: ${file}`,
|
| 107 |
+
});
|
| 108 |
+
}
|
| 109 |
+
}
|
| 110 |
+
|
| 111 |
+
// Check for common security issues
|
| 112 |
+
if (fileNames.includes('.env')) {
|
| 113 |
+
issues.push({
|
| 114 |
+
type: 'security',
|
| 115 |
+
severity: 'critical',
|
| 116 |
+
file: '.env',
|
| 117 |
+
message: '.env file should not be committed',
|
| 118 |
+
});
|
| 119 |
+
}
|
| 120 |
+
|
| 121 |
+
// Check for outdated dependencies (if package.json exists)
|
| 122 |
+
if (fileNames.includes('package.json')) {
|
| 123 |
+
try {
|
| 124 |
+
const packageJson = await this.getFileContent('package.json');
|
| 125 |
+
const pkg = JSON.parse(packageJson);
|
| 126 |
+
|
| 127 |
+
// Check if dependencies are very old (would need date comparison)
|
| 128 |
+
if (pkg.dependencies) {
|
| 129 |
+
issues.push({
|
| 130 |
+
type: 'warning',
|
| 131 |
+
severity: 'medium',
|
| 132 |
+
file: 'package.json',
|
| 133 |
+
message: 'Review and update dependencies periodically',
|
| 134 |
+
});
|
| 135 |
+
}
|
| 136 |
+
} catch (e) {
|
| 137 |
+
logger.warn('Failed to parse package.json', { error: e.message });
|
| 138 |
+
}
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
logger.info('Project scan completed', { issuesFound: issues.length });
|
| 142 |
+
return {
|
| 143 |
+
issues,
|
| 144 |
+
timestamp: new Date().toISOString(),
|
| 145 |
+
scanDuration: 'immediate',
|
| 146 |
+
};
|
| 147 |
+
} catch (error) {
|
| 148 |
+
logger.error('Project scan failed', { error: error.message });
|
| 149 |
+
throw error;
|
| 150 |
+
}
|
| 151 |
+
}
|
| 152 |
+
|
| 153 |
+
/**
|
| 154 |
+
* Get README content
|
| 155 |
+
* @returns {Promise<string>} README content
|
| 156 |
+
*/
|
| 157 |
+
async getReadme() {
|
| 158 |
+
try {
|
| 159 |
+
return await this.getFileContent('README.md');
|
| 160 |
+
} catch (error) {
|
| 161 |
+
logger.warn('Failed to read README', { error: error.message });
|
| 162 |
+
return null;
|
| 163 |
+
}
|
| 164 |
+
}
|
| 165 |
+
|
| 166 |
+
/**
|
| 167 |
+
* Get Dockerfile
|
| 168 |
+
* @returns {Promise<string>} Dockerfile content
|
| 169 |
+
*/
|
| 170 |
+
async getDockerfile() {
|
| 171 |
+
try {
|
| 172 |
+
return await this.getFileContent('Dockerfile');
|
| 173 |
+
} catch (error) {
|
| 174 |
+
logger.warn('Failed to read Dockerfile', { error: error.message });
|
| 175 |
+
return null;
|
| 176 |
+
}
|
| 177 |
+
}
|
| 178 |
+
|
| 179 |
+
/**
|
| 180 |
+
* Get list of source files (JS, TS, etc)
|
| 181 |
+
* @returns {Promise<Array>} Source files
|
| 182 |
+
*/
|
| 183 |
+
async getSourceFiles() {
|
| 184 |
+
try {
|
| 185 |
+
const structure = await this.getProjectStructure();
|
| 186 |
+
const extensions = ['.js', '.ts', '.jsx', '.tsx', '.py', '.java', '.go'];
|
| 187 |
+
|
| 188 |
+
return structure.files.filter((f) =>
|
| 189 |
+
extensions.some((ext) => f.name.endsWith(ext))
|
| 190 |
+
);
|
| 191 |
+
} catch (error) {
|
| 192 |
+
logger.error('Failed to get source files', { error: error.message });
|
| 193 |
+
return [];
|
| 194 |
+
}
|
| 195 |
+
}
|
| 196 |
+
}
|
| 197 |
+
|
| 198 |
+
export const projectService = new ProjectService();
|
|
@@ -0,0 +1,178 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Task Runner
|
| 3 |
+
* Manages task queue and execution order
|
| 4 |
+
*/
|
| 5 |
+
|
| 6 |
+
import { logger } from '../config/logger.js';
|
| 7 |
+
|
| 8 |
+
class TaskRunner {
|
| 9 |
+
constructor() {
|
| 10 |
+
this.tasks = [];
|
| 11 |
+
this.isRunning = false;
|
| 12 |
+
this.executedTasks = [];
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
/**
|
| 16 |
+
* Add task to queue
|
| 17 |
+
* @param {Object} task - Task definition
|
| 18 |
+
*/
|
| 19 |
+
addTask(task) {
|
| 20 |
+
const {
|
| 21 |
+
id,
|
| 22 |
+
name,
|
| 23 |
+
priority = 5,
|
| 24 |
+
execute,
|
| 25 |
+
retries = 3,
|
| 26 |
+
timeout = 30000,
|
| 27 |
+
} = task;
|
| 28 |
+
|
| 29 |
+
if (!id || !name || !execute) {
|
| 30 |
+
throw new Error('Task must have id, name, and execute function');
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
this.tasks.push({
|
| 34 |
+
id,
|
| 35 |
+
name,
|
| 36 |
+
priority,
|
| 37 |
+
execute,
|
| 38 |
+
retries,
|
| 39 |
+
timeout,
|
| 40 |
+
status: 'pending',
|
| 41 |
+
result: null,
|
| 42 |
+
error: null,
|
| 43 |
+
attempts: 0,
|
| 44 |
+
});
|
| 45 |
+
|
| 46 |
+
// Sort by priority (higher = execute first)
|
| 47 |
+
this.tasks.sort((a, b) => b.priority - a.priority);
|
| 48 |
+
|
| 49 |
+
logger.debug(`Task added: ${name} (priority: ${priority})`);
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
/**
|
| 53 |
+
* Run all tasks in queue
|
| 54 |
+
* @returns {Promise<Array>} Execution results
|
| 55 |
+
*/
|
| 56 |
+
async runAll() {
|
| 57 |
+
if (this.isRunning) {
|
| 58 |
+
logger.warn('Tasks already running');
|
| 59 |
+
return [];
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
this.isRunning = true;
|
| 63 |
+
const results = [];
|
| 64 |
+
|
| 65 |
+
logger.info('Starting task execution', { taskCount: this.tasks.length });
|
| 66 |
+
|
| 67 |
+
for (const task of this.tasks) {
|
| 68 |
+
try {
|
| 69 |
+
const result = await this.executeTask(task);
|
| 70 |
+
results.push(result);
|
| 71 |
+
} catch (error) {
|
| 72 |
+
logger.error('Task execution failed', {
|
| 73 |
+
taskId: task.id,
|
| 74 |
+
error: error.message,
|
| 75 |
+
});
|
| 76 |
+
}
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
+
this.isRunning = false;
|
| 80 |
+
logger.info('Task execution completed', { taskCount: results.length });
|
| 81 |
+
|
| 82 |
+
return results;
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
/**
|
| 86 |
+
* Execute single task with retries
|
| 87 |
+
* @param {Object} task - Task to execute
|
| 88 |
+
* @returns {Promise<Object>} Execution result
|
| 89 |
+
*/
|
| 90 |
+
async executeTask(task) {
|
| 91 |
+
let lastError;
|
| 92 |
+
|
| 93 |
+
for (let attempt = 1; attempt <= task.retries; attempt++) {
|
| 94 |
+
try {
|
| 95 |
+
task.attempts = attempt;
|
| 96 |
+
task.status = 'running';
|
| 97 |
+
|
| 98 |
+
logger.debug(`Executing task: ${task.name} (attempt ${attempt}/${task.retries})`);
|
| 99 |
+
|
| 100 |
+
const timeoutPromise = new Promise((_, reject) =>
|
| 101 |
+
setTimeout(
|
| 102 |
+
() => reject(new Error('Task timeout')),
|
| 103 |
+
task.timeout
|
| 104 |
+
)
|
| 105 |
+
);
|
| 106 |
+
|
| 107 |
+
const result = await Promise.race([
|
| 108 |
+
task.execute(),
|
| 109 |
+
timeoutPromise,
|
| 110 |
+
]);
|
| 111 |
+
|
| 112 |
+
task.status = 'completed';
|
| 113 |
+
task.result = result;
|
| 114 |
+
|
| 115 |
+
logger.info(`Task completed: ${task.name}`, { attempt });
|
| 116 |
+
this.executedTasks.push({ ...task });
|
| 117 |
+
|
| 118 |
+
return task;
|
| 119 |
+
} catch (error) {
|
| 120 |
+
lastError = error;
|
| 121 |
+
logger.warn(`Task attempt ${attempt} failed: ${task.name}`, {
|
| 122 |
+
error: error.message,
|
| 123 |
+
});
|
| 124 |
+
|
| 125 |
+
if (attempt < task.retries) {
|
| 126 |
+
// Wait before retrying
|
| 127 |
+
await new Promise((resolve) => setTimeout(resolve, 1000 * attempt));
|
| 128 |
+
}
|
| 129 |
+
}
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
task.status = 'failed';
|
| 133 |
+
task.error = lastError?.message;
|
| 134 |
+
|
| 135 |
+
logger.error(`Task failed after retries: ${task.name}`, {
|
| 136 |
+
error: lastError?.message,
|
| 137 |
+
});
|
| 138 |
+
|
| 139 |
+
this.executedTasks.push({ ...task });
|
| 140 |
+
return task;
|
| 141 |
+
}
|
| 142 |
+
|
| 143 |
+
/**
|
| 144 |
+
* Clear task queue
|
| 145 |
+
*/
|
| 146 |
+
clear() {
|
| 147 |
+
this.tasks = [];
|
| 148 |
+
this.executedTasks = [];
|
| 149 |
+
logger.debug('Task queue cleared');
|
| 150 |
+
}
|
| 151 |
+
|
| 152 |
+
/**
|
| 153 |
+
* Get execution history
|
| 154 |
+
* @returns {Array} Executed tasks
|
| 155 |
+
*/
|
| 156 |
+
getHistory() {
|
| 157 |
+
return this.executedTasks;
|
| 158 |
+
}
|
| 159 |
+
|
| 160 |
+
/**
|
| 161 |
+
* Get task stats
|
| 162 |
+
* @returns {Object} Stats
|
| 163 |
+
*/
|
| 164 |
+
getStats() {
|
| 165 |
+
const history = this.executedTasks;
|
| 166 |
+
const completed = history.filter((t) => t.status === 'completed').length;
|
| 167 |
+
const failed = history.filter((t) => t.status === 'failed').length;
|
| 168 |
+
|
| 169 |
+
return {
|
| 170 |
+
total: history.length,
|
| 171 |
+
completed,
|
| 172 |
+
failed,
|
| 173 |
+
successRate: history.length > 0 ? (completed / history.length) * 100 : 0,
|
| 174 |
+
};
|
| 175 |
+
}
|
| 176 |
+
}
|
| 177 |
+
|
| 178 |
+
export const taskRunner = new TaskRunner();
|
|
@@ -1,140 +0,0 @@
|
|
| 1 |
-
* {
|
| 2 |
-
margin: 0;
|
| 3 |
-
padding: 0;
|
| 4 |
-
box-sizing: border-box;
|
| 5 |
-
}
|
| 6 |
-
|
| 7 |
-
body {
|
| 8 |
-
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
| 9 |
-
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
| 10 |
-
min-height: 100vh;
|
| 11 |
-
padding: 20px;
|
| 12 |
-
color: #333;
|
| 13 |
-
}
|
| 14 |
-
|
| 15 |
-
.container {
|
| 16 |
-
max-width: 900px;
|
| 17 |
-
margin: 0 auto;
|
| 18 |
-
}
|
| 19 |
-
|
| 20 |
-
header {
|
| 21 |
-
text-align: center;
|
| 22 |
-
color: white;
|
| 23 |
-
padding: 40px 20px;
|
| 24 |
-
margin-bottom: 30px;
|
| 25 |
-
}
|
| 26 |
-
|
| 27 |
-
header h1 {
|
| 28 |
-
font-size: 2.5em;
|
| 29 |
-
margin-bottom: 10px;
|
| 30 |
-
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
|
| 31 |
-
}
|
| 32 |
-
|
| 33 |
-
.subtitle {
|
| 34 |
-
font-size: 1.2em;
|
| 35 |
-
opacity: 0.9;
|
| 36 |
-
}
|
| 37 |
-
|
| 38 |
-
main {
|
| 39 |
-
display: flex;
|
| 40 |
-
flex-direction: column;
|
| 41 |
-
gap: 20px;
|
| 42 |
-
}
|
| 43 |
-
|
| 44 |
-
.card {
|
| 45 |
-
background: white;
|
| 46 |
-
border-radius: 12px;
|
| 47 |
-
padding: 30px;
|
| 48 |
-
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
|
| 49 |
-
transition: transform 0.3s ease;
|
| 50 |
-
}
|
| 51 |
-
|
| 52 |
-
.card:hover {
|
| 53 |
-
transform: translateY(-5px);
|
| 54 |
-
}
|
| 55 |
-
|
| 56 |
-
.card h2 {
|
| 57 |
-
color: #667eea;
|
| 58 |
-
margin-bottom: 15px;
|
| 59 |
-
font-size: 1.8em;
|
| 60 |
-
}
|
| 61 |
-
|
| 62 |
-
.card p {
|
| 63 |
-
line-height: 1.6;
|
| 64 |
-
margin-bottom: 15px;
|
| 65 |
-
}
|
| 66 |
-
|
| 67 |
-
button {
|
| 68 |
-
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
| 69 |
-
color: white;
|
| 70 |
-
border: none;
|
| 71 |
-
padding: 12px 24px;
|
| 72 |
-
border-radius: 6px;
|
| 73 |
-
font-size: 1em;
|
| 74 |
-
cursor: pointer;
|
| 75 |
-
transition: opacity 0.3s ease;
|
| 76 |
-
margin-top: 10px;
|
| 77 |
-
}
|
| 78 |
-
|
| 79 |
-
button:hover {
|
| 80 |
-
opacity: 0.9;
|
| 81 |
-
}
|
| 82 |
-
|
| 83 |
-
button:active {
|
| 84 |
-
transform: scale(0.98);
|
| 85 |
-
}
|
| 86 |
-
|
| 87 |
-
#jsOutput, #formResponse {
|
| 88 |
-
margin-top: 15px;
|
| 89 |
-
padding: 15px;
|
| 90 |
-
background: #f0f0f0;
|
| 91 |
-
border-radius: 6px;
|
| 92 |
-
min-height: 20px;
|
| 93 |
-
font-weight: bold;
|
| 94 |
-
color: #667eea;
|
| 95 |
-
}
|
| 96 |
-
|
| 97 |
-
#phpContent {
|
| 98 |
-
padding: 15px;
|
| 99 |
-
background: #f9f9f9;
|
| 100 |
-
border-radius: 6px;
|
| 101 |
-
border-left: 4px solid #667eea;
|
| 102 |
-
margin-bottom: 10px;
|
| 103 |
-
}
|
| 104 |
-
|
| 105 |
-
form {
|
| 106 |
-
display: flex;
|
| 107 |
-
flex-direction: column;
|
| 108 |
-
gap: 10px;
|
| 109 |
-
}
|
| 110 |
-
|
| 111 |
-
input[type="text"] {
|
| 112 |
-
padding: 12px;
|
| 113 |
-
border: 2px solid #ddd;
|
| 114 |
-
border-radius: 6px;
|
| 115 |
-
font-size: 1em;
|
| 116 |
-
transition: border-color 0.3s ease;
|
| 117 |
-
}
|
| 118 |
-
|
| 119 |
-
input[type="text"]:focus {
|
| 120 |
-
outline: none;
|
| 121 |
-
border-color: #667eea;
|
| 122 |
-
}
|
| 123 |
-
|
| 124 |
-
footer {
|
| 125 |
-
text-align: center;
|
| 126 |
-
color: white;
|
| 127 |
-
padding: 30px 20px;
|
| 128 |
-
margin-top: 30px;
|
| 129 |
-
opacity: 0.9;
|
| 130 |
-
}
|
| 131 |
-
|
| 132 |
-
@media (max-width: 600px) {
|
| 133 |
-
header h1 {
|
| 134 |
-
font-size: 2em;
|
| 135 |
-
}
|
| 136 |
-
|
| 137 |
-
.card {
|
| 138 |
-
padding: 20px;
|
| 139 |
-
}
|
| 140 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|