327 lines
10 KiB
Python
327 lines
10 KiB
Python
"""
|
|
AI API routes for code assistance
|
|
"""
|
|
|
|
import time
|
|
from typing import List
|
|
from fastapi import APIRouter, HTTPException, BackgroundTasks
|
|
from fastapi.responses import StreamingResponse
|
|
from app.models.ai import (
|
|
AIRequest, AIResponse, CodeCompletionRequest, CodeCompletionResponse,
|
|
ExplainRequest, RefactorRequest, FixRequest, MultiFileRequest
|
|
)
|
|
from app.services.ollama_service import ollama_service
|
|
|
|
router = APIRouter()
|
|
|
|
@router.post("/process", response_model=AIResponse)
|
|
async def process_ai_request(request: AIRequest):
|
|
"""Process general AI request for code assistance"""
|
|
start_time = time.time()
|
|
|
|
try:
|
|
# Check if Ollama is available
|
|
if not await ollama_service.is_available():
|
|
raise HTTPException(
|
|
status_code=503,
|
|
detail="Ollama service is not available. Please ensure Ollama is running."
|
|
)
|
|
|
|
# Build prompt based on command
|
|
prompt = ollama_service.build_prompt(
|
|
command=request.command,
|
|
code=request.code,
|
|
language=request.language,
|
|
context=request.context
|
|
)
|
|
|
|
# Generate completion
|
|
result = await ollama_service.generate_completion(
|
|
prompt=prompt,
|
|
model=request.model
|
|
)
|
|
|
|
execution_time = time.time() - start_time
|
|
|
|
return AIResponse(
|
|
success=True,
|
|
result=result,
|
|
execution_time=execution_time,
|
|
model_used=request.model or ollama_service.default_model
|
|
)
|
|
|
|
except Exception as e:
|
|
execution_time = time.time() - start_time
|
|
return AIResponse(
|
|
success=False,
|
|
result=f"Error processing request: {str(e)}",
|
|
execution_time=execution_time
|
|
)
|
|
|
|
@router.post("/explain", response_model=AIResponse)
|
|
async def explain_code(request: ExplainRequest):
|
|
"""Explain code functionality"""
|
|
start_time = time.time()
|
|
|
|
try:
|
|
if not await ollama_service.is_available():
|
|
raise HTTPException(status_code=503, detail="Ollama service unavailable")
|
|
|
|
# Build explanation prompt
|
|
prompt = f"""
|
|
Explain the following {request.language.value if request.language else 'code'} code in {request.detail_level} detail:
|
|
|
|
```{request.language.value if request.language else 'code'}
|
|
{request.code}
|
|
```
|
|
|
|
Please provide a clear explanation that covers:
|
|
1. What this code does
|
|
2. How it works
|
|
3. Key concepts used
|
|
4. Any potential issues or improvements
|
|
|
|
Explanation:"""
|
|
|
|
result = await ollama_service.generate_completion(prompt=prompt)
|
|
execution_time = time.time() - start_time
|
|
|
|
return AIResponse(
|
|
success=True,
|
|
result=result,
|
|
execution_time=execution_time,
|
|
model_used=ollama_service.default_model
|
|
)
|
|
|
|
except Exception as e:
|
|
execution_time = time.time() - start_time
|
|
return AIResponse(
|
|
success=False,
|
|
result=f"Error explaining code: {str(e)}",
|
|
execution_time=execution_time
|
|
)
|
|
|
|
@router.post("/refactor", response_model=AIResponse)
|
|
async def refactor_code(request: RefactorRequest):
|
|
"""Refactor code for better quality"""
|
|
start_time = time.time()
|
|
|
|
try:
|
|
if not await ollama_service.is_available():
|
|
raise HTTPException(status_code=503, detail="Ollama service unavailable")
|
|
|
|
prompt = f"""
|
|
Refactor the following {request.language.value if request.language else 'code'} code to improve readability, performance, and maintainability:
|
|
|
|
```{request.language.value if request.language else 'code'}
|
|
{request.code}
|
|
```
|
|
|
|
Focus on {request.refactor_type} improvements. Please provide:
|
|
1. The refactored code
|
|
2. Explanation of changes made
|
|
3. Benefits of the refactoring
|
|
|
|
Refactored code:"""
|
|
|
|
result = await ollama_service.generate_completion(prompt=prompt)
|
|
execution_time = time.time() - start_time
|
|
|
|
return AIResponse(
|
|
success=True,
|
|
result=result,
|
|
execution_time=execution_time,
|
|
model_used=ollama_service.default_model
|
|
)
|
|
|
|
except Exception as e:
|
|
execution_time = time.time() - start_time
|
|
return AIResponse(
|
|
success=False,
|
|
result=f"Error refactoring code: {str(e)}",
|
|
execution_time=execution_time
|
|
)
|
|
|
|
@router.post("/fix", response_model=AIResponse)
|
|
async def fix_code(request: FixRequest):
|
|
"""Fix bugs in code"""
|
|
start_time = time.time()
|
|
|
|
try:
|
|
if not await ollama_service.is_available():
|
|
raise HTTPException(status_code=503, detail="Ollama service unavailable")
|
|
|
|
prompt = ollama_service.build_prompt(
|
|
command="fix",
|
|
code=request.code,
|
|
language=request.language,
|
|
error_message=request.error_message
|
|
)
|
|
|
|
result = await ollama_service.generate_completion(prompt=prompt)
|
|
execution_time = time.time() - start_time
|
|
|
|
return AIResponse(
|
|
success=True,
|
|
result=result,
|
|
execution_time=execution_time,
|
|
model_used=ollama_service.default_model
|
|
)
|
|
|
|
except Exception as e:
|
|
execution_time = time.time() - start_time
|
|
return AIResponse(
|
|
success=False,
|
|
result=f"Error fixing code: {str(e)}",
|
|
execution_time=execution_time
|
|
)
|
|
|
|
@router.post("/complete", response_model=CodeCompletionResponse)
|
|
async def complete_code(request: CodeCompletionRequest):
|
|
"""Generate code completion"""
|
|
start_time = time.time()
|
|
|
|
try:
|
|
if not await ollama_service.is_available():
|
|
raise HTTPException(status_code=503, detail="Ollama service unavailable")
|
|
|
|
# Extract context around cursor position
|
|
code_before = request.code[:request.cursor_position]
|
|
code_after = request.code[request.cursor_position:]
|
|
|
|
prompt = f"""
|
|
Complete the following {request.language.value if request.language else 'code'} code at the cursor position:
|
|
|
|
```{request.language.value if request.language else 'code'}
|
|
{code_before}<CURSOR>{code_after}
|
|
```
|
|
|
|
Provide only the code that should be inserted at the cursor position. Keep it concise and contextually appropriate.
|
|
|
|
Completion:"""
|
|
|
|
result = await ollama_service.generate_completion(
|
|
prompt=prompt,
|
|
max_tokens=request.max_tokens
|
|
)
|
|
|
|
execution_time = time.time() - start_time
|
|
|
|
return CodeCompletionResponse(
|
|
success=True,
|
|
completions=[result.strip()],
|
|
cursor_position=request.cursor_position,
|
|
execution_time=execution_time
|
|
)
|
|
|
|
except Exception as e:
|
|
execution_time = time.time() - start_time
|
|
return CodeCompletionResponse(
|
|
success=False,
|
|
completions=[],
|
|
cursor_position=request.cursor_position,
|
|
execution_time=execution_time
|
|
)
|
|
|
|
@router.post("/stream")
|
|
async def stream_ai_response(request: AIRequest):
|
|
"""Stream AI response for real-time feedback"""
|
|
try:
|
|
if not await ollama_service.is_available():
|
|
raise HTTPException(status_code=503, detail="Ollama service unavailable")
|
|
|
|
prompt = ollama_service.build_prompt(
|
|
command=request.command,
|
|
code=request.code,
|
|
language=request.language,
|
|
context=request.context
|
|
)
|
|
|
|
async def generate():
|
|
try:
|
|
async for chunk in ollama_service.generate_streaming(
|
|
prompt=prompt,
|
|
model=request.model
|
|
):
|
|
yield f"data: {chunk}\n\n"
|
|
yield "data: [DONE]\n\n"
|
|
except Exception as e:
|
|
yield f"data: ERROR: {str(e)}\n\n"
|
|
|
|
return StreamingResponse(
|
|
generate(),
|
|
media_type="text/plain",
|
|
headers={"Cache-Control": "no-cache"}
|
|
)
|
|
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
@router.post("/multifile", response_model=AIResponse)
|
|
async def process_multifile_request(request: MultiFileRequest):
|
|
"""Process multi-file analysis request"""
|
|
start_time = time.time()
|
|
|
|
try:
|
|
if not await ollama_service.is_available():
|
|
raise HTTPException(status_code=503, detail="Ollama service unavailable")
|
|
|
|
# Build context from multiple files
|
|
file_context = "\n\n".join([
|
|
f"File: {file_info['path']}\n```\n{file_info['content']}\n```"
|
|
for file_info in request.files
|
|
])
|
|
|
|
# Focus on specific file if provided
|
|
focus_context = ""
|
|
if request.focus_file:
|
|
focus_file = next(
|
|
(f for f in request.files if f['path'] == request.focus_file),
|
|
None
|
|
)
|
|
if focus_file:
|
|
focus_context = f"\n\nFocus on this file: {focus_file['path']}\n```\n{focus_file['content']}\n```"
|
|
|
|
prompt = f"""
|
|
Analyze the following multi-file codebase and {request.command.value}:
|
|
|
|
{file_context}
|
|
{focus_context}
|
|
|
|
{f"Additional context: {request.context}" if request.context else ""}
|
|
|
|
Please provide analysis considering the relationships between files and overall code structure.
|
|
|
|
Response:"""
|
|
|
|
result = await ollama_service.generate_completion(prompt=prompt)
|
|
execution_time = time.time() - start_time
|
|
|
|
return AIResponse(
|
|
success=True,
|
|
result=result,
|
|
execution_time=execution_time,
|
|
model_used=ollama_service.default_model,
|
|
metadata={"files_analyzed": len(request.files)}
|
|
)
|
|
|
|
except Exception as e:
|
|
execution_time = time.time() - start_time
|
|
return AIResponse(
|
|
success=False,
|
|
result=f"Error processing multifile request: {str(e)}",
|
|
execution_time=execution_time
|
|
)
|
|
|
|
@router.get("/health")
|
|
async def ai_health():
|
|
"""Check AI service health"""
|
|
is_available = await ollama_service.is_available()
|
|
models = await ollama_service.list_models() if is_available else []
|
|
|
|
return {
|
|
"ollama_available": is_available,
|
|
"models_count": len(models),
|
|
"default_model": ollama_service.default_model,
|
|
"base_url": ollama_service.base_url
|
|
} |