Small feature changes and bug fixes

This commit is contained in:
2026-02-16 13:17:08 +01:00
parent 04c9b8da1e
commit 25bcca213e
20 changed files with 2193 additions and 125 deletions

View File

@@ -14,14 +14,23 @@ struct ConversationListView: View {
@State private var conversations: [Conversation] = []
@State private var selectedConversations: Set<UUID> = []
@State private var isSelecting = false
@State private var useSemanticSearch = false
@State private var semanticResults: [Conversation] = []
@State private var isSearching = false
private let settings = SettingsService.shared
var onLoad: ((Conversation) -> Void)?
private var filteredConversations: [Conversation] {
if searchText.isEmpty {
return conversations
}
return conversations.filter {
$0.name.lowercased().contains(searchText.lowercased())
if useSemanticSearch && settings.embeddingsEnabled {
return semanticResults
} else {
return conversations.filter {
$0.name.lowercased().contains(searchText.lowercased())
}
}
}
@@ -79,6 +88,11 @@ struct ConversationListView: View {
.foregroundStyle(.secondary)
TextField("Search conversations...", text: $searchText)
.textFieldStyle(.plain)
.onChange(of: searchText) {
if useSemanticSearch && settings.embeddingsEnabled && !searchText.isEmpty {
performSemanticSearch()
}
}
if !searchText.isEmpty {
Button { searchText = "" } label: {
Image(systemName: "xmark.circle.fill")
@@ -86,6 +100,25 @@ struct ConversationListView: View {
}
.buttonStyle(.plain)
}
if settings.embeddingsEnabled {
Divider()
.frame(height: 16)
Toggle("Semantic", isOn: $useSemanticSearch)
.toggleStyle(.switch)
.controlSize(.small)
.onChange(of: useSemanticSearch) {
if useSemanticSearch && !searchText.isEmpty {
performSemanticSearch()
}
}
.help("Use AI-powered semantic search instead of keyword matching")
}
if isSearching {
ProgressView()
.controlSize(.small)
}
}
.padding(10)
.background(.quaternary.opacity(0.5), in: RoundedRectangle(cornerRadius: 8))
@@ -231,6 +264,52 @@ struct ConversationListView: View {
}
}
private func performSemanticSearch() {
guard !searchText.isEmpty else {
semanticResults = []
return
}
isSearching = true
Task {
do {
// Use user's selected provider, or fall back to best available
guard let provider = EmbeddingService.shared.getSelectedProvider() else {
Log.api.warning("No embedding providers available - skipping semantic search")
await MainActor.run {
isSearching = false
}
return
}
// Generate embedding for search query
let embedding = try await EmbeddingService.shared.generateEmbedding(
text: searchText,
provider: provider
)
// Search conversations
let results = try DatabaseService.shared.searchConversationsBySemantic(
queryEmbedding: embedding,
limit: 20
)
await MainActor.run {
semanticResults = results.map { $0.0 }
isSearching = false
Log.ui.info("Semantic search found \(results.count) results using \(provider.displayName)")
}
} catch {
await MainActor.run {
semanticResults = []
isSearching = false
Log.ui.error("Semantic search failed: \(error)")
}
}
}
}
private func exportConversation(_ conversation: Conversation) {
guard let (_, loadedMessages) = try? DatabaseService.shared.loadConversation(id: conversation.id),
!loadedMessages.isEmpty else {