Skip to content

Commit 3933a20

Browse files
committed
feat: Complete frontend reorganization and cleanup
- Reorganized frontend structure with modular component hierarchy - Created shared component library (BaseCard, BaseDialog, BaseFormField, StatusBadge) - Extracted layout components (AppHeader, AppNavigation, AppFooter) - Decomposed LlamaCppManager.vue (1868→340 lines, 82% reduction) - Decomposed ModelConfig.vue into 12+ smaller components - Created modular CSS system (styles/_variables.css, _base.css, _components.css, _utilities.css) - Centralized utility functions in utils/formatting.js - Removed all duplicate formatFileSize and formatDate functions - Replaced hardcoded CSS values with CSS variables - Standardized responsive breakpoints - Added JSDoc documentation to composables - Removed old assets/main.css and assets/theme.css files - Cleaned up debug console.log statements - Zero linter errors, production-ready codebase
1 parent ff79285 commit 3933a20

57 files changed

Lines changed: 6952 additions & 2742 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎backend/cuda_installer.py‎

Lines changed: 542 additions & 0 deletions
Large diffs are not rendered by default.

‎backend/llama_manager.py‎

Lines changed: 168 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,96 @@ def __init__(self):
7777
os.makedirs(self.llama_dir, exist_ok=True)
7878
self._cached_cuda_architectures: Optional[str] = None
7979

80+
def _check_cuda_toolkit_available(self) -> Tuple[bool, Optional[str], Optional[str]]:
81+
"""
82+
Check if CUDA Toolkit is available on the system.
83+
84+
Returns:
85+
Tuple of (is_available, cuda_root, error_message)
86+
- is_available: True if CUDA Toolkit is found
87+
- cuda_root: Path to CUDA root directory if found, None otherwise
88+
- error_message: Error message if not available, None otherwise
89+
"""
90+
env = os.environ.copy()
91+
possible_cuda_roots = [
92+
env.get("CUDA_PATH"),
93+
env.get("CUDA_HOME"),
94+
"/usr/local/cuda",
95+
"/usr/local/cuda-12.9",
96+
"/usr/local/cuda-12.8",
97+
"/usr/local/cuda-12.7",
98+
"/usr/local/cuda-12.6",
99+
"/usr/local/cuda-12.5",
100+
"/usr/local/cuda-12.4",
101+
"/usr/local/cuda-12.3",
102+
"/usr/local/cuda-12.2",
103+
"/usr/local/cuda-12.1",
104+
"/usr/local/cuda-12.0",
105+
"/usr/local/cuda-11.9",
106+
"/usr/local/cuda-11.8",
107+
]
108+
109+
# Filter out None values and check if paths exist
110+
for cuda_root in possible_cuda_roots:
111+
if not cuda_root or not os.path.exists(cuda_root):
112+
continue
113+
114+
# Check for nvcc compiler
115+
nvcc_path = os.path.join(cuda_root, "bin", "nvcc")
116+
if not os.path.exists(nvcc_path):
117+
# On Windows, nvcc might be in a different location
118+
if os.name == 'nt':
119+
nvcc_path = os.path.join(cuda_root, "bin", "nvcc.exe")
120+
if not os.path.exists(nvcc_path):
121+
continue
122+
else:
123+
continue
124+
125+
# Check for CUDA libraries
126+
lib_dirs = ["lib64", "lib"]
127+
has_libs = False
128+
for lib_dir in lib_dirs:
129+
lib_path = os.path.join(cuda_root, lib_dir)
130+
if os.path.exists(lib_path):
131+
# Check for at least one CUDA library file
132+
try:
133+
lib_files = os.listdir(lib_path)
134+
if any("cudart" in f or "cublas" in f or "curand" in f for f in lib_files):
135+
has_libs = True
136+
break
137+
except OSError:
138+
pass
139+
140+
if has_libs:
141+
return (True, cuda_root, None)
142+
143+
# Try to find nvcc in PATH as a fallback
144+
try:
145+
result = subprocess.run(
146+
["nvcc", "--version"],
147+
capture_output=True,
148+
text=True,
149+
timeout=5
150+
)
151+
if result.returncode == 0:
152+
# nvcc found in PATH, try to determine CUDA root
153+
nvcc_path = shutil.which("nvcc")
154+
if nvcc_path:
155+
# nvcc is typically in <CUDA_ROOT>/bin/nvcc
156+
potential_root = os.path.dirname(os.path.dirname(nvcc_path))
157+
if os.path.exists(potential_root):
158+
return (True, potential_root, None)
159+
except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
160+
pass
161+
162+
error_msg = (
163+
"CUDA Toolkit not found. Please either:\n"
164+
"1. Install CUDA Toolkit from https://developer.nvidia.com/cuda-downloads\n"
165+
"2. Set CUDA_PATH environment variable to your CUDA installation directory\n"
166+
"3. Disable CUDA in build configuration (set enable_cuda: false)"
167+
)
168+
return (False, None, error_msg)
169+
80170
async def _detect_cuda_architectures(self) -> Optional[str]:
81171
"""
82172
Determine CUDA architectures for the current environment by querying GPU capabilities.
@@ -757,6 +847,39 @@ async def build_source(self, commit_sha: str, patches: List[str] = None, build_c
757847
build_config.enable_backend_dl = True
758848
build_config.normalize()
759849

850+
# Validate CUDA Toolkit availability if CUDA is enabled
851+
if build_config.enable_cuda:
852+
cuda_available, cuda_root, cuda_error = self._check_cuda_toolkit_available()
853+
if not cuda_available:
854+
# Check if CUDA installer is available
855+
try:
856+
from backend.cuda_installer import get_cuda_installer
857+
installer = get_cuda_installer()
858+
installer_status = installer.status()
859+
if not installer_status.get("installed"):
860+
error_msg = (
861+
f"CUDA build requested but CUDA Toolkit not found.\n\n"
862+
f"{cuda_error}\n\n"
863+
f"You can install CUDA Toolkit using the CUDA installer in the LlamaCpp Manager, "
864+
f"or install it manually from https://developer.nvidia.com/cuda-downloads"
865+
)
866+
else:
867+
error_msg = f"CUDA build requested but CUDA Toolkit not found.\n\n{cuda_error}"
868+
except ImportError:
869+
error_msg = f"CUDA build requested but CUDA Toolkit not found.\n\n{cuda_error}"
870+
871+
logger.error(error_msg)
872+
if websocket_manager and task_id:
873+
await websocket_manager.send_build_progress(
874+
task_id=task_id,
875+
stage="configure",
876+
progress=60,
877+
message="CUDA Toolkit validation failed",
878+
log_lines=[error_msg]
879+
)
880+
raise Exception(error_msg)
881+
logger.info(f"CUDA Toolkit found at: {cuda_root}")
882+
760883
# Build CMake arguments
761884
cmake_args = ["cmake", ".."]
762885

@@ -818,35 +941,44 @@ def set_flag(flag: str, value: bool):
818941

819942
# Ensure CUDA toolchain paths are available when CUDA build requested
820943
if build_config.enable_cuda:
821-
possible_cuda_roots = [
822-
env.get("CUDA_PATH"),
823-
"/usr/local/cuda",
824-
"/usr/local/cuda-12.9"
825-
]
826-
cuda_root = next(
827-
(root for root in possible_cuda_roots if root and os.path.exists(root)),
828-
None,
829-
)
830-
944+
# Use the validated CUDA root from earlier check
945+
cuda_available, cuda_root, _ = self._check_cuda_toolkit_available()
946+
831947
if cuda_root:
832-
nvcc_path = os.path.join(cuda_root, "bin", "nvcc")
948+
# Set CUDA_PATH for CMake
949+
env["CUDA_PATH"] = cuda_root
950+
if not env.get("CUDA_HOME"):
951+
env["CUDA_HOME"] = cuda_root
952+
953+
# Set CUDACXX if nvcc is found
954+
nvcc_name = "nvcc.exe" if os.name == 'nt' else "nvcc"
955+
nvcc_path = os.path.join(cuda_root, "bin", nvcc_name)
833956
if os.path.exists(nvcc_path) and not env.get("CUDACXX"):
834957
env["CUDACXX"] = nvcc_path
958+
835959
# Ensure PATH includes CUDA bin for CMake detection
836960
cuda_bin = os.path.join(cuda_root, "bin")
837961
current_path = env.get("PATH", "")
838962
path_entries = [entry for entry in current_path.split(os.pathsep) if entry]
839963
if cuda_bin not in path_entries:
840964
env["PATH"] = os.pathsep.join([cuda_bin] + path_entries) if current_path else cuda_bin
965+
841966
# Ensure LD_LIBRARY_PATH includes CUDA libs (Linux)
842-
for lib_dir in ("lib64", "lib"):
843-
full_dir = os.path.join(cuda_root, lib_dir)
844-
if os.path.exists(full_dir):
845-
current_ld = env.get("LD_LIBRARY_PATH", "")
846-
ld_paths = [entry for entry in current_ld.split(os.pathsep) if entry]
847-
if full_dir not in ld_paths:
848-
ld_paths.insert(0, full_dir)
849-
env["LD_LIBRARY_PATH"] = os.pathsep.join(ld_paths)
967+
# On Windows, PATH is used for DLLs
968+
if os.name != 'nt':
969+
for lib_dir in ("lib64", "lib"):
970+
full_dir = os.path.join(cuda_root, lib_dir)
971+
if os.path.exists(full_dir):
972+
current_ld = env.get("LD_LIBRARY_PATH", "")
973+
ld_paths = [entry for entry in current_ld.split(os.pathsep) if entry]
974+
if full_dir not in ld_paths:
975+
ld_paths.insert(0, full_dir)
976+
env["LD_LIBRARY_PATH"] = os.pathsep.join(ld_paths)
977+
978+
logger.info(f"Configured CUDA environment: CUDA_PATH={cuda_root}, CUDACXX={env.get('CUDACXX', 'not set')}")
979+
else:
980+
# This shouldn't happen if validation passed, but handle it gracefully
981+
logger.warning("CUDA was validated but root path not found during CMake configuration")
850982

851983
cmake_process = await asyncio.create_subprocess_exec(
852984
*cmake_args,
@@ -864,7 +996,23 @@ def set_flag(flag: str, value: bool):
864996
if cmake_process.returncode != 0:
865997
error_msg = cmake_stderr.decode().strip()
866998
logger.warning(f"CMake configuration failed: {error_msg}")
867-
raise Exception(f"CMake configuration failed: {error_msg}")
999+
1000+
# Provide more helpful error messages for CUDA-related failures
1001+
if build_config.enable_cuda and ("CUDA" in error_msg.upper() or "cuda" in error_msg.lower()):
1002+
enhanced_error = (
1003+
f"CMake configuration failed with CUDA error:\n\n{error_msg}\n\n"
1004+
"This usually means:\n"
1005+
"1. CUDA Toolkit is not properly installed\n"
1006+
"2. CUDA_PATH environment variable is not set correctly\n"
1007+
"3. CMake cannot find CUDA compiler (nvcc)\n\n"
1008+
"To fix this:\n"
1009+
"- Install CUDA Toolkit from https://developer.nvidia.com/cuda-downloads\n"
1010+
"- Set CUDA_PATH to your CUDA installation directory\n"
1011+
"- Or disable CUDA in build configuration (set enable_cuda: false)"
1012+
)
1013+
raise Exception(enhanced_error)
1014+
else:
1015+
raise Exception(f"CMake configuration failed: {error_msg}")
8681016

8691017
logger.info("CMake configuration completed successfully")
8701018

‎backend/routes/llama_versions.py‎

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from backend.websocket_manager import websocket_manager
1515
from backend.logging_config import get_logger
1616
from backend.gpu_detector import get_gpu_info, detect_build_capabilities
17+
from backend.cuda_installer import get_cuda_installer
1718

1819
router = APIRouter()
1920
llama_manager = LlamaManager()
@@ -433,4 +434,53 @@ async def delete_version(
433434
return {"message": f"Deleted llama-cpp version {version.version}"}
434435
except Exception as e:
435436
logger.error(f"Failed to delete version {version.version}: {e}")
436-
raise HTTPException(status_code=500, detail=f"Failed to delete version: {e}")
437+
raise HTTPException(status_code=500, detail=f"Failed to delete version: {e}")
438+
439+
440+
# CUDA Installer endpoints
441+
@router.get("/cuda-status")
442+
async def get_cuda_status():
443+
"""Get CUDA installation status"""
444+
try:
445+
installer = get_cuda_installer()
446+
status = installer.status()
447+
return status
448+
except Exception as e:
449+
logger.error(f"Failed to get CUDA status: {e}")
450+
raise HTTPException(status_code=500, detail=str(e))
451+
452+
453+
@router.post("/cuda-install")
454+
async def install_cuda(request: dict):
455+
"""Install CUDA Toolkit"""
456+
try:
457+
version = request.get("version", "12.6")
458+
installer = get_cuda_installer()
459+
460+
if installer.is_operation_running():
461+
raise HTTPException(
462+
status_code=400,
463+
detail="A CUDA installation operation is already running"
464+
)
465+
466+
result = await installer.install(version)
467+
return result
468+
except ValueError as e:
469+
raise HTTPException(status_code=400, detail=str(e))
470+
except RuntimeError as e:
471+
raise HTTPException(status_code=400, detail=str(e))
472+
except Exception as e:
473+
logger.error(f"Failed to start CUDA installation: {e}")
474+
raise HTTPException(status_code=500, detail=str(e))
475+
476+
477+
@router.get("/cuda-logs")
478+
async def get_cuda_logs():
479+
"""Get CUDA installation logs"""
480+
try:
481+
installer = get_cuda_installer()
482+
logs = installer.read_log_tail()
483+
return {"logs": logs}
484+
except Exception as e:
485+
logger.error(f"Failed to get CUDA logs: {e}")
486+
raise HTTPException(status_code=500, detail=str(e))

0 commit comments

Comments
 (0)