Migrate AI features to backend, add podcast generation

- ClaudeService: replace direct OpenRouter calls with backend endpoints
  (/v1/marks/enrich, /v1/marks/search, /v1/marks/collections)
- ClaudeService: add podcast methods (generate, status, downloadAudio)
- SettingsView: replace OpenRouter key field with backend URL + API key
- PodcastPlayerView: new sheet — generate → poll → AVPlayer playback
- BrowserView: headphones toolbar button triggers podcast for current URL
- BookmarksView: "Convert to Podcast" context menu item + sheet

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Krishna Kumar
2026-05-21 18:39:15 -05:00
parent ac2f9cf85d
commit bc16ee986d
6 changed files with 390 additions and 127 deletions

View File

@@ -115,15 +115,18 @@ private struct WebViewRepresentable: UIViewRepresentable {
struct BrowserView: View {
let initialURL: URL
let initialTitle: String
let claude: ClaudeService?
@Environment(\.dismiss) private var dismiss
@Environment(\.openURL) private var openURL
@State private var state: BrowserState
@State private var readerMode = false
@State private var showPodcast = false
init(url: URL, title: String) {
init(url: URL, title: String, claude: ClaudeService? = nil) {
self.initialURL = url
self.initialTitle = title
self.claude = claude
self._state = State(initialValue: BrowserState(url: url))
}
@@ -187,11 +190,28 @@ struct BrowserView: View {
Spacer()
if claude != nil {
Button {
showPodcast = true
} label: {
Image(systemName: "headphones")
}
.disabled(state.isLoading)
Spacer()
}
ShareLink(item: state.currentURL ?? initialURL) {
Image(systemName: "square.and.arrow.up")
}
}
}
}
.sheet(isPresented: $showPodcast) {
if let claude {
let url = (state.currentURL ?? initialURL).absoluteString
PodcastPlayerView(articleUrl: url, claude: claude)
}
}
}
}