@@ -31,6 +31,7 @@ data class LoraUiState(
3131 val registeredAdapters : List <LoraAdapterCatalogEntry > = emptyList(),
3232 val loadedAdapters : List <LoRAAdapterInfo > = emptyList(),
3333 val compatibleAdapters : List <LoraAdapterCatalogEntry > = emptyList(),
34+ val downloadedAdapterPaths : Map <String , String > = emptyMap(),
3435 val downloadingAdapterId : String? = null ,
3536 val downloadProgress : Float = 0f ,
3637 val error : String? = null ,
@@ -58,13 +59,15 @@ class LoraViewModel(application: Application) : AndroidViewModel(application) {
5859 fun refresh () {
5960 viewModelScope.launch {
6061 try {
61- val (registered, loaded) = withContext(Dispatchers .IO ) {
62- RunAnywhere .allRegisteredLoraAdapters() to RunAnywhere .getLoadedLoraAdapters()
62+ val (registered, loaded, downloaded) = withContext(Dispatchers .IO ) {
63+ val reg = RunAnywhere .allRegisteredLoraAdapters()
64+ Triple (reg, RunAnywhere .getLoadedLoraAdapters(), scanDownloadedAdapters(reg))
6365 }
6466 _uiState .update {
6567 it.copy(
6668 registeredAdapters = registered,
6769 loadedAdapters = loaded,
70+ downloadedAdapterPaths = downloaded,
6871 error = null ,
6972 )
7073 }
@@ -79,13 +82,15 @@ class LoraViewModel(application: Application) : AndroidViewModel(application) {
7982 fun refreshForModel (modelId : String ) {
8083 viewModelScope.launch {
8184 try {
82- val (compatible, loaded) = withContext(Dispatchers .IO ) {
83- RunAnywhere .loraAdaptersForModel(modelId) to RunAnywhere .getLoadedLoraAdapters()
85+ val (compatible, loaded, downloaded) = withContext(Dispatchers .IO ) {
86+ val compat = RunAnywhere .loraAdaptersForModel(modelId)
87+ Triple (compat, RunAnywhere .getLoadedLoraAdapters(), scanDownloadedAdapters(compat))
8488 }
8589 _uiState .update {
8690 it.copy(
8791 compatibleAdapters = compatible,
8892 loadedAdapters = loaded,
93+ downloadedAdapterPaths = it.downloadedAdapterPaths + downloaded,
8994 error = null ,
9095 )
9196 }
@@ -151,15 +156,14 @@ class LoraViewModel(application: Application) : AndroidViewModel(application) {
151156 }
152157 }
153158
154- /* * Get the local file path for a catalog entry, or null if not downloaded. */
159+ /* * Get the local file path for a catalog entry, or null if not downloaded (reads from cached state) . */
155160 fun localPath (entry : LoraAdapterCatalogEntry ): String? {
156- val file = File (loraDir, entry.filename)
157- return if (file.exists()) file.absolutePath else null
161+ return _uiState .value.downloadedAdapterPaths[entry.id]
158162 }
159163
160- /* * Check if a catalog entry is already downloaded. */
164+ /* * Check if a catalog entry is already downloaded (reads from cached state) . */
161165 fun isDownloaded (entry : LoraAdapterCatalogEntry ): Boolean {
162- return File (loraDir, entry.filename).exists()
166+ return entry.id in _uiState .value.downloadedAdapterPaths
163167 }
164168
165169 /* * Check if a specific adapter is currently loaded. */
@@ -168,6 +172,14 @@ class LoraViewModel(application: Application) : AndroidViewModel(application) {
168172 return _uiState .value.loadedAdapters.any { it.path == path }
169173 }
170174
175+ /* * Scan disk for downloaded adapter files and return id->path map. Must be called on IO dispatcher. */
176+ private fun scanDownloadedAdapters (adapters : List <LoraAdapterCatalogEntry >): Map <String , String > {
177+ return adapters.mapNotNull { entry ->
178+ val file = File (loraDir, entry.filename)
179+ if (file.exists()) entry.id to file.absolutePath else null
180+ }.toMap()
181+ }
182+
171183 /* * Download a LoRA adapter GGUF file. */
172184 fun downloadAdapter (entry : LoraAdapterCatalogEntry ) {
173185 if (_uiState .value.downloadingAdapterId != null ) return
@@ -221,6 +233,7 @@ class LoraViewModel(application: Application) : AndroidViewModel(application) {
221233 it.copy(
222234 downloadingAdapterId = null ,
223235 downloadProgress = 0f ,
236+ downloadedAdapterPaths = it.downloadedAdapterPaths + (entry.id to destFile.absolutePath),
224237 )
225238 }
226239 } catch (e: Exception ) {
@@ -269,7 +282,12 @@ class LoraViewModel(application: Application) : AndroidViewModel(application) {
269282 }
270283 }
271284 val loaded = withContext(Dispatchers .IO ) { RunAnywhere .getLoadedLoraAdapters() }
272- _uiState .update { it.copy(loadedAdapters = loaded) }
285+ _uiState .update {
286+ it.copy(
287+ loadedAdapters = loaded,
288+ downloadedAdapterPaths = it.downloadedAdapterPaths - entry.id,
289+ )
290+ }
273291 } catch (e: Exception ) {
274292 Timber .e(e, " Failed to delete adapter: ${entry.filename} " )
275293 _uiState .update { it.copy(error = " Delete failed: ${e.message} " ) }
0 commit comments