import 'dart:io'; class AiConstants { AiConstants._(); // Server ports static const int chatServerPort = 8080; static const int embeddingServerPort = 8081; // API endpoints static String get chatApiUrl => 'http://localhost:$chatServerPort/v1/chat/completions'; static String get embeddingApiUrl => 'http://localhost:$embeddingServerPort/v1/embeddings'; static String chatHealthUrl = 'http://localhost:$chatServerPort/health'; static String embeddingHealthUrl = 'http://localhost:$embeddingServerPort/health'; // Model files static const String qwenModelFile = 'qwen3-4b-instruct-2507-q4_k_m.gguf'; static const String nomicModelFile = 'nomic-embed-text-v1.5.Q4_K_M.gguf'; static const String nomicModelName = 'nomic-embed-text-v1.5.Q4_K_M'; // llama.cpp binary static const String llamaBuild = 'b8130'; static String get serverBinaryName => Platform.isWindows ? 'llama-server.exe' : 'llama-server'; // Model download URLs static const String nomicModelUrl = 'https://huggingface.co/nomic-ai/nomic-embed-text-v1.5-GGUF/resolve/main/nomic-embed-text-v1.5.Q4_K_M.gguf'; static const String qwenModelUrl = 'https://huggingface.co/bartowski/Qwen_Qwen3-4B-Instruct-2507-GGUF/resolve/main/Qwen_Qwen3-4B-Instruct-2507-Q4_K_M.gguf'; // Human-readable model labels (single source of truth for UI copy) static const String chatModelLabel = 'Qwen3 4B Instruct Q4_K_M'; static const String embeddingModelLabel = 'Nomic Embed Text v1.5 Q4_K_M'; // Minimum plausible file sizes — a partially downloaded file is smaller. static const int qwenModelMinBytes = 2_000_000_000; // full file ≈ 2.4 GB static const int nomicModelMinBytes = 50_000_000; // full file ≈ 84 MB static const int serverBinaryMinBytes = 1_000_000; // Server configuration static const int qwenContextSize = 8192; static const int nomicContextSize = 2048; static const int gpuLayerOffload = 99; static const double chatTemperature = 0.7; static const int chatMaxTokens = 1536; /// How many most-recent messages are sent back to the model as history. static const int chatHistoryLimit = 12; /// Nomic v1.5 requires task prefixes for good retrieval quality. static const String nomicDocumentPrefix = 'search_document: '; static const String nomicQueryPrefix = 'search_query: '; // Timeouts static const Duration serverConnectTimeout = Duration(seconds: 30); static const Duration serverReceiveTimeout = Duration(minutes: 5); static const Duration embeddingConnectTimeout = Duration(seconds: 10); static const Duration embeddingReceiveTimeout = Duration(seconds: 60); /// Cold-loading a multi-GB model can take minutes: on the first Vulkan /// run llama.cpp additionally compiles GPU pipelines for the device. static const Duration serverStartupTimeout = Duration(minutes: 5); /// CPU fallback loads via mmap and needs no pipeline compilation. static const Duration serverStartupTimeoutCpu = Duration(minutes: 3); static const Duration healthPollInterval = Duration(milliseconds: 750); // System prompt static const String baseSystemPrompt = 'You are the AI coach inside TrainHub, a training hub for ITF Taekwon-Do. ' 'You help with: designing drills and training plans (patterns/tul, ' 'sparring/matsogi, kicks, conditioning), analysing technique, belt ' 'grading preparation, and sports science questions. ' 'Use correct ITF terminology (e.g. dollyo chagi, yop chagi, tul names). ' 'Always answer in the same language the user writes in. ' 'Be concise and practical — coaches read this between rounds.'; }