Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 24 additions & 4 deletions searchy/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,26 @@ struct ContentView: View {
}
}

private func selectTab(_ tab: AppTab) {
guard activeTab != tab else { return }
clearTransientSearchState()
activeTab = tab
}

private func clearTransientSearchState() {
searchDebounceTimer?.invalidate()
searchDebounceTimer = nil
previewTimer?.invalidate()
previewTimer = nil
searchText = ""
pastedImage = nil
previewResult = nil
showPreviewPanel = false
isSearchFocused = false
isGlobalSearchFocused = false
searchManager.clearResults()
}

// MARK: - Atelier Sidebar
private var atelierSidebar: some View {
let sidebarItems: [(id: AppTab, label: String, icon: String, count: Int?, badge: Bool)] = [
Expand Down Expand Up @@ -273,7 +293,7 @@ struct ContentView: View {
ForEach(sidebarItems, id: \.id) { item in
Button(action: {
withAnimation(.spring(response: 0.3, dampingFraction: 0.8)) {
activeTab = item.id
selectTab(item.id)
}
}) {
HStack(spacing: 10) {
Expand Down Expand Up @@ -320,7 +340,7 @@ struct ContentView: View {

ForEach(recentSearchQueries, id: \.self) { query in
Button(action: {
activeTab = .search
selectTab(.search)
searchText = query
performSearch()
}) {
Expand Down Expand Up @@ -5928,7 +5948,7 @@ struct ContentView: View {
.font(.system(size: 12))
.foregroundColor(p.ink3)
.multilineTextAlignment(.center)
Button(action: { activeTab = .setup }) {
Button(action: { selectTab(.setup) }) {
HStack(spacing: 6) {
Image(systemName: "folder.badge.plus")
.font(.system(size: 12))
Expand Down Expand Up @@ -6224,7 +6244,7 @@ struct ContentView: View {
let digit = chars.first?.wholeNumberValue, digit >= 1 && digit <= 6 {
let tabs: [AppTab] = [.faces, .search, .volumes, .duplicates, .gallery, .setup]
DispatchQueue.main.async {
self.activeTab = tabs[digit - 1]
self.selectTab(tabs[digit - 1])
}
return nil
}
Expand Down
50 changes: 46 additions & 4 deletions searchy/Managers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ class SearchManager: ObservableObject {
@Published private(set) var errorMessage: String? = nil
@Published private(set) var searchStats: SearchStats? = nil

private var searchTask: Task<Void, Never>?
private var searchGeneration = 0

private init() {}

private var serverURL: URL? {
Expand All @@ -20,17 +23,23 @@ class SearchManager: ObservableObject {

func search(query: String, numberOfResults: Int = 5, ocrWeight: Double = 0.5) {
guard !isSearching else { return }
searchTask?.cancel()
searchGeneration += 1
let generation = searchGeneration

DispatchQueue.main.async {
self.isSearching = true
self.errorMessage = nil
// Don't clear results — keep old results visible while searching
}

Task {
searchTask = Task {
do {
let response = try await self.performSearch(query: query, numberOfResults: numberOfResults, ocrWeight: ocrWeight)
guard !Task.isCancelled else { return }

DispatchQueue.main.async {
guard self.searchGeneration == generation else { return }
let filteredResults = response.results.filter {
$0.similarity >= SearchPreferences.shared.similarityThreshold
}
Expand All @@ -39,7 +48,10 @@ class SearchManager: ObservableObject {
self.isSearching = false
}
} catch {
guard !Task.isCancelled else { return }

DispatchQueue.main.async {
guard self.searchGeneration == generation else { return }
self.results = []
self.errorMessage = error.localizedDescription
self.isSearching = false
Expand Down Expand Up @@ -77,18 +89,39 @@ class SearchManager: ObservableObject {
return response
}
func cancelSearch() {
DispatchQueue.main.async {
searchTask?.cancel()
searchTask = nil
searchGeneration += 1

let reset = {
self.isSearching = false
self.errorMessage = nil
}

if Thread.isMainThread {
reset()
} else {
DispatchQueue.main.async(execute: reset)
}
}

func clearResults() {
DispatchQueue.main.async {
searchTask?.cancel()
searchTask = nil
searchGeneration += 1

let reset = {
self.results = []
self.isSearching = false
self.searchStats = nil
self.errorMessage = nil
}

if Thread.isMainThread {
reset()
} else {
DispatchQueue.main.async(execute: reset)
}
}

func loadRecentImages(completion: @escaping ([SearchResult]) -> Void) {
Expand Down Expand Up @@ -133,6 +166,9 @@ class SearchManager: ObservableObject {

func findSimilar(imagePath: String, numberOfResults: Int = 20) {
guard !isSearching else { return }
searchTask?.cancel()
searchGeneration += 1
let generation = searchGeneration

DispatchQueue.main.async {
self.isSearching = true
Expand All @@ -141,16 +177,22 @@ class SearchManager: ObservableObject {
self.searchStats = nil
}

Task {
searchTask = Task {
do {
let response = try await self.performFindSimilar(imagePath: imagePath, numberOfResults: numberOfResults)
guard !Task.isCancelled else { return }

DispatchQueue.main.async {
guard self.searchGeneration == generation else { return }
self.results = response.results
self.searchStats = response.stats
self.isSearching = false
}
} catch {
guard !Task.isCancelled else { return }

DispatchQueue.main.async {
guard self.searchGeneration == generation else { return }
self.errorMessage = error.localizedDescription
self.isSearching = false
}
Expand Down
2 changes: 2 additions & 0 deletions searchy/SettingsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1519,9 +1519,11 @@ struct SettingsView: View {
}
}

#if canImport(PreviewsMacros)
#Preview {
SettingsView()
}
#endif
// Extension to support Python file types
extension UTType {
static var pythonScript: UTType {
Expand Down
Loading