feat: AI 서버 관리 페이지 Phase 2 고급 기능 구현
🤖 모델 관리 고도화: - 모델 다운로드: 인기 모델들 원클릭 설치 (llama, qwen, gemma, codellama, mistral) - 모델 삭제: 확인 모달과 함께 안전한 삭제 기능 - 사용 가능한 모델 목록: 태그별 분류 (chat, code, lightweight 등) - 모델 상세 정보: 설명, 크기, 용도별 태그 표시 �� 실시간 시스템 모니터링: - CPU/메모리/디스크/GPU 사용률 원형 프로그레스바 - 색상 코딩: 사용률에 따른 시각적 구분 (녹색/주황/빨강) - 실시간 업데이트: 30초마다 자동 새로고침 - 시스템 리소스 상세 정보 (코어 수, 용량, 온도 등) 🎨 고급 UI/UX: - 모달 창: 부드러운 애니메이션과 블러 효과 - 원형 프로그레스바: CSS 기반 실시간 업데이트 - 반응형 디자인: 모바일 최적화 - 태그 시스템: 모델 분류 및 시각화 🔧 새 API 엔드포인트: - POST /admin/models/download - 모델 다운로드 - DELETE /admin/models/{model_name} - 모델 삭제 - GET /admin/models/available - 다운로드 가능한 모델 목록 - GET /admin/system/stats - 시스템 리소스 사용률 수정된 파일: - server/main.py: Phase 2 API 엔드포인트 추가 - test_admin.py: 테스트 모드 Phase 2 기능 추가 - templates/admin.html: 시스템 모니터링 섹션, 모달 창 추가 - static/admin.css: 모니터링 차트, 모달 스타일 추가 - static/admin.js: Phase 2 기능 JavaScript 구현
This commit is contained in:
151
server/main.py
151
server/main.py
@@ -595,3 +595,154 @@ async def admin_delete_api_key(key_id: str, api_key: str = Depends(require_api_k
|
||||
else:
|
||||
raise HTTPException(status_code=404, detail="API key not found")
|
||||
|
||||
|
||||
# Phase 2: Advanced Model Management
|
||||
@app.post("/admin/models/download")
|
||||
async def admin_download_model(request: dict, api_key: str = Depends(require_api_key)):
|
||||
"""모델 다운로드"""
|
||||
model_name = request.get("model")
|
||||
if not model_name:
|
||||
raise HTTPException(status_code=400, detail="Model name is required")
|
||||
|
||||
try:
|
||||
# Ollama pull 명령 실행
|
||||
result = await ollama.pull_model(model_name)
|
||||
return {
|
||||
"success": True,
|
||||
"message": f"Model '{model_name}' download started",
|
||||
"details": result
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
"success": False,
|
||||
"error": f"Failed to download model: {str(e)}"
|
||||
}
|
||||
|
||||
|
||||
@app.delete("/admin/models/{model_name}")
|
||||
async def admin_delete_model(model_name: str, api_key: str = Depends(require_api_key)):
|
||||
"""모델 삭제"""
|
||||
try:
|
||||
# Ollama 모델 삭제
|
||||
result = await ollama.delete_model(model_name)
|
||||
return {
|
||||
"success": True,
|
||||
"message": f"Model '{model_name}' deleted successfully",
|
||||
"details": result
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
"success": False,
|
||||
"error": f"Failed to delete model: {str(e)}"
|
||||
}
|
||||
|
||||
|
||||
@app.get("/admin/models/available")
|
||||
async def admin_get_available_models(api_key: str = Depends(require_api_key)):
|
||||
"""다운로드 가능한 모델 목록"""
|
||||
# 인기 있는 모델들 목록 (실제로는 Ollama 레지스트리에서 가져와야 함)
|
||||
available_models = [
|
||||
{
|
||||
"name": "llama3.2:1b",
|
||||
"description": "Meta의 Llama 3.2 1B 모델 - 가벼운 작업용",
|
||||
"size": "1.3GB",
|
||||
"tags": ["chat", "lightweight"]
|
||||
},
|
||||
{
|
||||
"name": "llama3.2:3b",
|
||||
"description": "Meta의 Llama 3.2 3B 모델 - 균형잡힌 성능",
|
||||
"size": "2.0GB",
|
||||
"tags": ["chat", "recommended"]
|
||||
},
|
||||
{
|
||||
"name": "qwen2.5:7b",
|
||||
"description": "Alibaba의 Qwen 2.5 7B 모델 - 다국어 지원",
|
||||
"size": "4.1GB",
|
||||
"tags": ["chat", "multilingual"]
|
||||
},
|
||||
{
|
||||
"name": "gemma2:2b",
|
||||
"description": "Google의 Gemma 2 2B 모델 - 효율적인 추론",
|
||||
"size": "1.6GB",
|
||||
"tags": ["chat", "efficient"]
|
||||
},
|
||||
{
|
||||
"name": "codellama:7b",
|
||||
"description": "Meta의 Code Llama 7B - 코드 생성 특화",
|
||||
"size": "3.8GB",
|
||||
"tags": ["code", "programming"]
|
||||
},
|
||||
{
|
||||
"name": "mistral:7b",
|
||||
"description": "Mistral AI의 7B 모델 - 고성능 추론",
|
||||
"size": "4.1GB",
|
||||
"tags": ["chat", "performance"]
|
||||
}
|
||||
]
|
||||
|
||||
return {"available_models": available_models}
|
||||
|
||||
|
||||
# Phase 2: System Monitoring
|
||||
@app.get("/admin/system/stats")
|
||||
async def admin_get_system_stats(api_key: str = Depends(require_api_key)):
|
||||
"""시스템 리소스 사용률 조회"""
|
||||
import psutil
|
||||
import GPUtil
|
||||
|
||||
try:
|
||||
# CPU 사용률
|
||||
cpu_percent = psutil.cpu_percent(interval=1)
|
||||
cpu_count = psutil.cpu_count()
|
||||
|
||||
# 메모리 사용률
|
||||
memory = psutil.virtual_memory()
|
||||
memory_percent = memory.percent
|
||||
memory_used = memory.used // (1024**3) # GB
|
||||
memory_total = memory.total // (1024**3) # GB
|
||||
|
||||
# 디스크 사용률
|
||||
disk = psutil.disk_usage('/')
|
||||
disk_percent = (disk.used / disk.total) * 100
|
||||
disk_used = disk.used // (1024**3) # GB
|
||||
disk_total = disk.total // (1024**3) # GB
|
||||
|
||||
# GPU 사용률 (NVIDIA GPU가 있는 경우)
|
||||
gpu_stats = []
|
||||
try:
|
||||
gpus = GPUtil.getGPUs()
|
||||
for gpu in gpus:
|
||||
gpu_stats.append({
|
||||
"name": gpu.name,
|
||||
"load": gpu.load * 100,
|
||||
"memory_used": gpu.memoryUsed,
|
||||
"memory_total": gpu.memoryTotal,
|
||||
"temperature": gpu.temperature
|
||||
})
|
||||
except:
|
||||
gpu_stats = []
|
||||
|
||||
return {
|
||||
"cpu": {
|
||||
"usage_percent": cpu_percent,
|
||||
"core_count": cpu_count
|
||||
},
|
||||
"memory": {
|
||||
"usage_percent": memory_percent,
|
||||
"used_gb": memory_used,
|
||||
"total_gb": memory_total
|
||||
},
|
||||
"disk": {
|
||||
"usage_percent": disk_percent,
|
||||
"used_gb": disk_used,
|
||||
"total_gb": disk_total
|
||||
},
|
||||
"gpu": gpu_stats,
|
||||
"timestamp": datetime.now().isoformat()
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
"error": f"Failed to get system stats: {str(e)}",
|
||||
"timestamp": datetime.now().isoformat()
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user