Skip to content

feat: HuggingFace GGUF Explorer & Downloader integration#82

Merged
Siddhesh2377 merged 3 commits intoSiddhesh2377:re-writefrom
Godzilla675:feature/huggingface-gguf-explorer
Mar 7, 2026
Merged

feat: HuggingFace GGUF Explorer & Downloader integration#82
Siddhesh2377 merged 3 commits intoSiddhesh2377:re-writefrom
Godzilla675:feature/huggingface-gguf-explorer

Conversation

@Godzilla675
Copy link
Copy Markdown
Contributor

Summary

Implements Issue #81 — adds a HuggingFace GGUF Explorer to the Model Store Settings tab.

Features

  • HuggingFace Hub API integration for searching GGUF model repositories
  • New HuggingFaceExplorerRepository with searchGgufRepositories()
  • Explorer UI card in Settings tab with search, animated results, download/like counts
  • One-tap add of discovered repos to the model store
  • Animations: expand/collapse, staggered slideInVertically, AnimatedContent transitions
  • Matches existing design system (StandardCard, CaptionText, ActionButton, StatusBadge)

Additional Fixes

  • Fixed UnusedMaterial3ScaffoldPaddingParameter lint error in IntroScreen
  • Added missing test dependencies to all modules

Build Verification

✅ assembleDebug ✅ assembleRelease ✅ lintDebug ✅ testDebugUnitTest (all modules)

Closes #81

Implements Issue Siddhesh2377#81 - HuggingFace GGUF repository explorer in Model Store.

Features:
- HuggingFace Hub API integration for searching GGUF model repositories
- New HuggingFaceExplorerRepository with searchGgufRepositories()
- Explorer UI card in Model Store Settings tab with search, results display
- Animated expand/collapse, staggered result entry, status transitions
- One-tap add of discovered repos to the model store

Additional fixes:
- Fix UnusedMaterial3ScaffoldPaddingParameter lint error in IntroScreen
- Add missing test dependencies (junit, androidx-junit, espresso-core) to all modules

Closes Siddhesh2377#81
Copilot AI review requested due to automatic review settings February 28, 2026 03:24
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a HuggingFace GGUF Explorer to the Model Store Settings tab, enabling users to search HuggingFace for GGUF repositories and add them to the app’s repository list.

Changes:

  • Introduces HuggingFace Hub search API support and a new HuggingFaceExplorerRepository for GGUF repo discovery.
  • Adds Explorer state + actions to ModelStoreViewModel and a new Explorer UI card (search, animated results, add button) to the Settings tab.
  • Applies a Scaffold padding lint fix in IntroScreen and adds missing test dependencies across modules.

Reviewed changes

Copilot reviewed 8 out of 9 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
neuron-packet/build.gradle.kts Adds unit + instrumentation test dependencies.
memory-vault/build.gradle.kts Adds unit + instrumentation test dependencies.
app/build.gradle.kts Adds unit + instrumentation test dependencies for the app module.
app/src/main/java/com/dark/tool_neuron/viewmodel/ModelStoreViewModel.kt Adds explorer query/results/loading/error state plus search/add actions.
app/src/main/java/com/dark/tool_neuron/ui/screen/ModelStoreScreen.kt Adds Explorer card UI in Settings tab with animations and add-to-store action.
app/src/main/java/com/dark/tool_neuron/ui/screen/IntroScreen.kt Fixes Scaffold inner padding usage.
app/src/main/java/com/dark/tool_neuron/repo/HuggingFaceExplorerRepository.kt New repository wrapper for searching GGUF repos via HuggingFace API.
app/src/main/java/com/dark/tool_neuron/network/HuggingFaceApi.kt Adds searchModels() endpoint + response model.
README.md Updates product description text (contains a new typo).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1155 to +1183
results.take(8).forEachIndexed { index, repo ->
val isAdded = existingRepoPaths.contains(repo.id.lowercase())

var visible by remember(repo.id) { mutableStateOf(false) }
LaunchedEffect(repo.id) {
delay(index * 60L)
visible = true
}

AnimatedVisibility(
visible = visible,
enter = slideInVertically(
initialOffsetY = { it / 2 },
animationSpec = spring(
dampingRatio = 0.75f,
stiffness = 300f
)
) + fadeIn(spring(stiffness = 300f))
) {
Column {
ExplorerResultRow(
repo = repo,
isAdded = isAdded,
onAdd = { onAdd(repo) }
)
if (index < results.take(8).lastIndex) {
HorizontalDivider(
color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.3f)
)
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

results.take(8) is recomputed multiple times inside the results loop (including inside the divider condition), which creates unnecessary list allocations on recomposition. Consider computing a single val displayedResults = results.take(8) before the loop and reuse it.

Copilot uses AI. Check for mistakes.
Comment thread README.md Outdated
</p>

ToolNeuron is the most advanced offline-first AI assistant for Android, featuring complete on-device processing with enterprise-grade encryption, intelligent document understanding through RAG (Retrieval-Augmented Generation), text-to-speech, an extensible plugin system, AI character cards (TavernAI v2 compatible), persistent AI memory, and sophisticated memory management. Your data never leaves your device. No cloud dependencies. No subscriptions. True digital sovereignty.
ToolNeuron is the most advanced offline-first AI assistant for Android, featuring complete on-device processing with enterprise-grade encryption, intelligent document understanding through RAG (Retrieval-Augmented Generation), text-to-speech, an extensible plugin system, AI character cards (TavernAI v2 compatible), persistent AImemory, and sophisticated memory management. Your data never leaves your device. No cloud dependencies. No subscriptions. True digital sovereignty.
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

README sentence now reads "persistent AImemory" (missing space), which looks like a typo in the project description. Please change it back to "AI memory" (or the preferred wording used elsewhere).

Suggested change
ToolNeuron is the most advanced offline-first AI assistant for Android, featuring complete on-device processing with enterprise-grade encryption, intelligent document understanding through RAG (Retrieval-Augmented Generation), text-to-speech, an extensible plugin system, AI character cards (TavernAI v2 compatible), persistent AImemory, and sophisticated memory management. Your data never leaves your device. No cloud dependencies. No subscriptions. True digital sovereignty.
ToolNeuron is the most advanced offline-first AI assistant for Android, featuring complete on-device processing with enterprise-grade encryption, intelligent document understanding through RAG (Retrieval-Augmented Generation), text-to-speech, an extensible plugin system, AI character cards (TavernAI v2 compatible), persistent AI memory, and sophisticated memory management. Your data never leaves your device. No cloud dependencies. No subscriptions. True digital sovereignty.

Copilot uses AI. Check for mistakes.
Comment on lines +504 to +527
fun searchExplorerRepositories() {
viewModelScope.launch {
val query = _explorerQuery.value.trim()
if (query.isBlank()) {
_explorerError.value = "Enter a search term"
_explorerResults.value = emptyList()
return@launch
}

_isExplorerLoading.value = true
_explorerError.value = null

explorerRepository.searchGgufRepositories(query).onSuccess { repos ->
_explorerResults.value = repos
if (repos.isEmpty()) {
_explorerError.value = "No repositories found"
}
}.onFailure { exception ->
_explorerResults.value = emptyList()
_explorerError.value = exception.message ?: "Search failed"
}

_isExplorerLoading.value = false
}
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

searchExplorerRepositories() performs a network request and response mapping inside viewModelScope.launch without switching to an IO dispatcher. This work can run on the Main thread and cause UI jank; consider wrapping the search call (and mapping) in withContext(Dispatchers.IO) (similar to RepositoryValidator.validateRepository).

Copilot uses AI. Check for mistakes.
Comment on lines +504 to +527
fun searchExplorerRepositories() {
viewModelScope.launch {
val query = _explorerQuery.value.trim()
if (query.isBlank()) {
_explorerError.value = "Enter a search term"
_explorerResults.value = emptyList()
return@launch
}

_isExplorerLoading.value = true
_explorerError.value = null

explorerRepository.searchGgufRepositories(query).onSuccess { repos ->
_explorerResults.value = repos
if (repos.isEmpty()) {
_explorerError.value = "No repositories found"
}
}.onFailure { exception ->
_explorerResults.value = emptyList()
_explorerError.value = exception.message ?: "Search failed"
}

_isExplorerLoading.value = false
}
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

searchExplorerRepositories() can be triggered multiple times, launching concurrent coroutines. This can lead to out-of-order results and incorrect _isExplorerLoading toggling (an earlier request finishing last can overwrite newer state). Consider tracking a search Job and cancelling previous searches, and set _isExplorerLoading back to false in a finally block tied to the latest request.

Copilot uses AI. Check for mistakes.
Comment on lines +16 to +48
suspend fun searchGgufRepositories(query: String, limit: Int = 20): Result<List<HuggingFaceExplorerRepo>> {
return try {
val response = HuggingFaceClient.api.searchModels(
filter = "gguf",
search = query.trim(),
sort = "downloads",
direction = -1,
limit = limit.coerceIn(1, 50)
)

if (!response.isSuccessful) {
return Result.failure(Exception("Search failed (${response.code()})"))
}

val repositories = response.body().orEmpty()
.mapNotNull { repo ->
val repoId = repo.id
if (repoId.isBlank() || !repoId.contains("/")) return@mapNotNull null
HuggingFaceExplorerRepo(
id = repoId,
author = repo.author ?: repoId.substringBefore("/"),
downloads = repo.downloads ?: 0L,
likes = repo.likes ?: 0L,
gated = repo.gated ?: false,
tags = repo.tags.orEmpty().filter { it.isNotBlank() }.take(6)
)
}
.distinctBy { it.id }

Result.success(repositories)
} catch (e: Exception) {
Result.failure(e)
}
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

searchGgufRepositories() performs the Retrofit call and maps the full response without enforcing an IO dispatcher. Since callers may invoke this from the Main thread, consider wrapping the body of this method with withContext(Dispatchers.IO) (and keep only lightweight state updates on Main) to avoid accidental Main-thread work.

Copilot uses AI. Check for mistakes.
Comment on lines +1232 to +1234
ActionButton(
onClickListener = {},
icon = Icons.Default.CheckCircle,
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When isAdded is true, the UI renders an ActionButton with an empty onClickListener. This remains focusable/clickable but does nothing, which is confusing and hurts accessibility. Consider rendering a non-clickable icon (or add an enabled parameter to ActionButton and set it to false, with disabled semantics/colors).

Suggested change
ActionButton(
onClickListener = {},
icon = Icons.Default.CheckCircle,
Icon(
imageVector = Icons.Default.CheckCircle,

Copilot uses AI. Check for mistakes.
- Extract displayedResults to avoid repeated results.take(8) allocations
- Wrap searchGgufRepositories() in withContext(Dispatchers.IO)
- Track search Job and cancel previous searches to prevent race conditions
- Use finally block for _isExplorerLoading to ensure proper cleanup
- Replace clickable ActionButton with non-clickable Icon for added state
- Fix 'AImemory' typo in README
@Godzilla675
Copy link
Copy Markdown
Contributor Author

@Siddhesh2377 can you review the pr?

@Siddhesh2377
Copy link
Copy Markdown
Owner

@Siddhesh2377 can you review the pr?

Hey working on some stuff, the pr review will take some time

@Siddhesh2377
Copy link
Copy Markdown
Owner

Hey @Godzilla675 I have made some major changes in the code base so i think this pr is not a good option, maybe update the fork or re-fork it and create a pr, as it will be very veyr easy now that most of the use less code has been deleted

Resolve remaining Model Store merge conflicts after syncing with the latest base branch.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@Godzilla675
Copy link
Copy Markdown
Contributor Author

@Siddhesh2377 I fixed the merge conflicts.

@Siddhesh2377 Siddhesh2377 merged commit dd44ae0 into Siddhesh2377:re-write Mar 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Festure Request: HuggingFace explorer and Downloader

3 participants