Deminiko commited on
Commit
5071059
·
1 Parent(s): 6ee8e8f

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 ADDED
@@ -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
public/.gitignore ADDED
@@ -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
+ *~
public/ARCHITECTURE.md ADDED
@@ -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
public/Dockerfile ADDED
@@ -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"]
public/INSTALLATION.md ADDED
@@ -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
public/README.md ADDED
@@ -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
public/api.php DELETED
@@ -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
- ?>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
public/assets/css/styles.css ADDED
@@ -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
+ }
public/assets/js/app.js ADDED
@@ -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
+ });
public/assets/js/modules/api-client.js ADDED
@@ -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
+ }
public/assets/js/modules/chat.js ADDED
@@ -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
+ }
public/assets/js/modules/project.js ADDED
@@ -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
+ }
public/assets/js/modules/scanner.js ADDED
@@ -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
+ }
public/assets/js/modules/ui.js ADDED
@@ -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">&times;</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
+ }
public/index.html CHANGED
@@ -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>My Hugging Face Space</title>
7
- <link rel="stylesheet" href="styles.css">
8
  </head>
9
  <body>
10
- <div class="container">
11
- <header>
12
- <h1>Welcome to My Web App</h1>
13
- <p class="subtitle">Running on Hugging Face Spaces with Docker</p>
14
- </header>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
 
16
- <main>
17
- <section class="card">
18
- <h2>Static Content</h2>
19
- <p>This is a simple HTML page styled with CSS.</p>
20
- <button id="testButton">Click Me (JS Test)</button>
21
- <p id="jsOutput"></p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  </section>
23
 
24
- <section class="card">
25
- <h2>PHP Dynamic Content</h2>
26
- <div id="phpContent">
27
- <p>Loading PHP content...</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  </div>
29
- <button id="loadPhp">Reload PHP Data</button>
30
  </section>
31
 
32
- <section class="card">
33
- <h2>Form Example</h2>
34
- <form id="testForm">
35
- <input type="text" id="nameInput" placeholder="Enter your name" required>
36
- <button type="submit">Submit to PHP</button>
37
- </form>
38
- <div id="formResponse"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  </section>
40
- </main>
41
 
42
- <footer>
43
- <p>Synced from GitHub to Hugging Face Spaces</p>
44
- </footer>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
  </div>
46
 
47
- <script src="script.js"></script>
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>
public/package.json ADDED
@@ -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
+ }
public/process.php DELETED
@@ -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
- ?>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
public/script.js DELETED
@@ -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
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
public/src/agents/chat.js ADDED
@@ -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();
public/src/agents/scanner.js ADDED
@@ -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();
public/src/api/chat.js ADDED
@@ -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;
public/src/api/project.js ADDED
@@ -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;
public/src/api/scanner.js ADDED
@@ -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;
public/src/config/env.js ADDED
@@ -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
+ }
public/src/config/logger.js ADDED
@@ -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
+ };
public/src/server.js ADDED
@@ -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();
public/src/services/gemini.js ADDED
@@ -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();
public/src/services/github.js ADDED
@@ -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();
public/src/services/project.js ADDED
@@ -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();
public/src/utils/taskRunner.js ADDED
@@ -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();
public/styles.css DELETED
@@ -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
- }