YourOwnAI follows Clean Architecture principles with MVVM pattern using Jetpack Compose.
Responsibility: UI and user interaction
- Jetpack Compose - Declarative UI
- ViewModels - UI state management
- StateFlow - Reactive state updates
- Navigation - Screen transitions
Key Components:
ChatScreen.kt/ChatViewModel.kt- Main chat interfaceSettingsScreen.kt/SettingsViewModel.kt- App settingsHomeScreen.kt/HomeViewModel.kt- Conversations listOnboardingScreen.kt/OnboardingViewModel.kt- First launch setup
Responsibility: Business logic and models
- Models - Data classes and enums
- Service interfaces - Abstract AI operations
- Business rules - Validation, transformation
Key Components:
ModelProvider.kt- AI model definitions (Deepseek, OpenAI, x.ai, Local)LocalModel.kt- Local model specificationsSettings.kt- User preferencesAIConfig.kt- AI generation parametersLlamaService.kt- Local inference interfaceAIService.kt- Unified AI service interface
Responsibility: Data sources and repositories
Submodules:
- Room Database - Conversations, messages, API keys
- DAO interfaces - Database access
- Entities - Database tables
- API clients - HTTP communication
DeepseekClient.kt- Deepseek APIOpenAIClient.kt- OpenAI API (GPT-5, GPT-4o, o1/o3)XAIClient.kt- x.ai Grok API
- Models - Request/response DTOs
- Streaming - SSE (Server-Sent Events) handling
- Repositories - Abstract data sources
ConversationRepository.kt- Chat historyMessageRepository.kt- Individual messagesApiKeyRepository.kt- Encrypted key storageLocalModelRepository.kt- Model downloads and management
- Service implementations
AIServiceImpl.kt- Unified AI serviceLlamaServiceImpl.kt- Local inference with Llamatik
LlamaCppWrapper.kt- Llamatik JNI wrapper
Responsibility: Provide dependencies via Hilt
NetworkModule.kt- OkHttpClient, API clients@ApiClient- For API calls (with logging)@DownloadClient- For model downloads (no logging)
DatabaseModule.kt- Room DB, DAOsRepositoryModule.kt- Repositories
User types message
↓
ChatScreen (Compose UI)
↓
ChatViewModel.sendMessage()
↓
AIService.generateResponse()
↓
[API Client OR LlamaService]
↓
Flow<String> (streaming chunks)
↓
ChatViewModel.collect { chunk }
↓
Update _uiState.messages (local state)
↓
ChatScreen recomposes (shows streaming text)
↓
After streaming completes
↓
MessageRepository.updateMessage() (save to DB)
User taps Download
↓
SettingsDialogs.LocalModelsDialog
↓
SettingsViewModel.downloadModel()
↓
LocalModelRepository.downloadModel()
↓
Mutex.withLock (queue if another download active)
↓
Set status to Queued (show "В очереди..." in UI)
↓
Wait for queue
↓
Set status to Downloading(0%)
↓
OkHttp streams file to disk (4KB buffer)
↓
Update progress every 500ms or 1%
↓
Verify GGUF header after download
↓
Set status to Downloaded or Failed
- UI rendering (Jetpack Compose)
- ViewModel state updates
- Navigation
- Database operations (Room)
- File operations (model downloads)
- Network calls (API clients)
- Local model inference (Llamatik)
- Mutex for model loading (LlamaService) - prevents concurrent JNI calls
- Mutex for model generation (LlamaService) - thread-safe inference
- Mutex for model downloads (LocalModelRepository) - one download at a time
- StateFlow for reactive state updates
- distinctUntilChanged() to prevent redundant UI updates
User enters API key
↓
Encrypted with Android Keystore System
↓
Stored in EncryptedSharedPreferences
↓
Retrieved and decrypted on demand
↓
Used in API calls (Authorization header)
↓
Redacted in logs
network_security_config.xml- Enforce HTTPS- Certificate pinning configuration ready
- No cleartext traffic allowed
- ProGuard/R8 - Code obfuscation
- Resource shrinking - Remove unused resources
- Debug log removal - Keep only error logs
- String encryption - Obfuscate sensitive strings
Problem: llama.cpp is NOT thread-safe Solution:
- Singleton
LlamaServiceImplwith Mutex - Only one
loadModel()at a time - Only one
generateResponse()at a time - Proper unload before loading new model
Problem: LoggingInterceptor with Level.BODY loads entire file (1GB+) into memory
Solution:
- Two separate OkHttpClient instances:
@ApiClient- For API calls (with body logging)@DownloadClient- For downloads (NO body logging)
- 4KB buffer for streaming downloads
largeHeap="true"for 512MB memory limit
Problem: Different models have different parameter requirements Solution:
- Detection sets:
NEW_API_MODELS,REASONING_MODELS - Conditional parameter passing:
- GPT-5/4.1: use
max_completion_tokens - o1/o3: omit
temperatureandtop_p - Legacy: use
max_tokens, includetemperature
- GPT-5/4.1: use
Problem: Chat UI jitters during streaming Solution:
- Update local
_uiStateduring streaming (not DB) - Single DB write after streaming completes
contentTypein LazyColumn for optimizationdistinctUntilChanged()on conversation flowsscrollToItem()without animation during streaming
interface ConversationRepository {
fun getConversations(): Flow<List<Conversation>>
suspend fun createConversation(): Conversation
}
class ConversationRepositoryImpl @Inject constructor(
private val conversationDao: ConversationDao
) : ConversationRepository {
// Implementation
}interface AIService {
suspend fun generateResponse(...): Flow<String>
}
class AIServiceImpl @Inject constructor(
private val deepseekClient: DeepseekClient,
private val openAIClient: OpenAIClient,
// ...
) : AIService {
// Unified interface for all providers
}class ChatViewModel @Inject constructor(
private val aiService: AIService,
private val conversationRepository: ConversationRepository
) : ViewModel() {
private val _uiState = MutableStateFlow(ChatUiState())
val uiState: StateFlow<ChatUiState> = _uiState.asStateFlow()
fun sendMessage() {
viewModelScope.launch {
aiService.generateResponse(...).collect { chunk ->
// Update UI
}
}
}
}- Create client in
data/remote/[provider]/
class NewProviderClient @Inject constructor(
private val okHttpClient: OkHttpClient,
private val gson: Gson
) {
suspend fun chatCompletion(...): String
fun chatCompletionStream(...): Flow<String>
}- Add to NetworkModule
@Provides
@Singleton
fun provideNewProviderClient(...): NewProviderClient- Add enum to ModelProvider.kt
enum class NewProviderModel {
MODEL_1(...),
MODEL_2(...);
fun toModelProvider() = ModelProvider.API(...)
}- Add to AIServiceImpl
AIProvider.NEW_PROVIDER -> generateNewProviderResponse(...)- Update network_security_config.xml
<domain>api.newprovider.com</domain>- Create in
presentation/[feature]/ - Add ViewModel with
@HiltViewModel - Use
StateFlowfor UI state - Add to navigation in
MainActivity.kt
Tag: ChatViewModel|LlamaService|LocalModelRepository|AIService
- Hilt errors - Rebuild project
- Room schema changes - Update version or use
fallbackToDestructiveMigration - ProGuard crashes - Check
mapping/release/for obfuscated names - Native crashes - Check Llamatik threading (use Mutex)
- ARCHITECTURE.md - Detailed architecture documentation
- CHAT_IMPLEMENTATION_PLAN.md - Chat feature specs
- LLAMA_CPP_INTEGRATION.md - Local inference details
Questions? Open a GitHub Discussion!