feat(ai): on-device RAG over bookmarks + Spotlight indexing + unified logging
All checks were successful
CI / build-and-deploy (push) Successful in 22s
All checks were successful
CI / build-and-deploy (push) Successful in 22s
- SpotlightIndexer: actually push BookmarkEntity into the Spotlight index via indexAppEntities (IndexedEntity conformance alone indexes nothing). Wired into every sync/mutation point. This is what lets Apple Intelligence answer free-form Siri questions grounded in the user's bookmarks. - On-device RAG: BookmarkAssistant (LanguageModelSession) + BookmarkSearchTool + SpotlightBookmarkSearch (CSSearchQuery retrieval), surfaced via AskView with lightweight Markdown rendering of answers. - Log: os.Logger facility (com.magicive.marks) replacing ad-hoc print(); shared with ShareExtension. ClaudeService now logs server error bodies on non-2xx. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
40
Marks/Services/SpotlightIndexer.swift
Normal file
40
Marks/Services/SpotlightIndexer.swift
Normal file
@@ -0,0 +1,40 @@
|
||||
import Foundation
|
||||
import CoreSpotlight
|
||||
import AppIntents
|
||||
|
||||
/// Pushes Linkding bookmarks into the system Spotlight index via the
|
||||
/// `IndexedEntity` conformance on `BookmarkEntity`.
|
||||
///
|
||||
/// This is the step that actually makes bookmarks discoverable: conforming to
|
||||
/// `IndexedEntity` only describes *how* an entity would be indexed (its
|
||||
/// `attributeSet`) — nothing reaches Spotlight until the entities are handed to
|
||||
/// `CSSearchableIndex`. Once indexed, bookmarks show up in Spotlight search and
|
||||
/// are reachable by Siri / Apple Intelligence.
|
||||
///
|
||||
/// All calls are best-effort: failures are logged, never surfaced. If indexing
|
||||
/// is unavailable or fails, search degrades but nothing else breaks.
|
||||
enum SpotlightIndexer {
|
||||
/// Insert or update the given bookmarks in the Spotlight index.
|
||||
static func index(_ bookmarks: [Bookmark]) async {
|
||||
guard CSSearchableIndex.isIndexingAvailable(), !bookmarks.isEmpty else { return }
|
||||
do {
|
||||
try await CSSearchableIndex.default()
|
||||
.indexAppEntities(bookmarks.map(BookmarkEntity.init(from:)))
|
||||
Log.spotlight.info("Indexed \(bookmarks.count, privacy: .public) bookmarks")
|
||||
} catch {
|
||||
Log.spotlight.error("Index failed: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove bookmarks from the index by id (e.g. after delete / archive).
|
||||
static func remove(ids: [Int]) async {
|
||||
guard CSSearchableIndex.isIndexingAvailable(), !ids.isEmpty else { return }
|
||||
do {
|
||||
try await CSSearchableIndex.default()
|
||||
.deleteAppEntities(identifiedBy: ids, ofType: BookmarkEntity.self)
|
||||
Log.spotlight.info("Removed \(ids.count, privacy: .public) bookmarks")
|
||||
} catch {
|
||||
Log.spotlight.error("Delete failed: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user