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:
@@ -4,25 +4,36 @@ struct SettingsView: View {
|
||||
@Bindable var viewModel: BookmarksViewModel
|
||||
let onDisconnect: () -> Void
|
||||
|
||||
@State private var claudeKey = UserDefaults.standard.string(forKey: ClaudeService.defaultsKey) ?? ""
|
||||
@State private var backendUrl = UserDefaults.standard.string(forKey: ClaudeService.urlKey) ?? ""
|
||||
@State private var claudeKey = UserDefaults.standard.string(forKey: ClaudeService.apiKeyKey) ?? ""
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
List {
|
||||
Section("AI Features") {
|
||||
Section {
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
Text("OpenRouter API Key")
|
||||
Text("Backend URL")
|
||||
.font(.system(size: 13, weight: .medium))
|
||||
.foregroundStyle(.secondary)
|
||||
SecureField("sk-or-…", text: $claudeKey)
|
||||
TextField("https://api.yourserver.com", text: $backendUrl)
|
||||
.textContentType(.URL)
|
||||
.autocorrectionDisabled()
|
||||
.textInputAutocapitalization(.never)
|
||||
.keyboardType(.URL)
|
||||
.onChange(of: backendUrl) { _, _ in reloadClaude() }
|
||||
}
|
||||
.padding(.vertical, 4)
|
||||
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
Text("API Key")
|
||||
.font(.system(size: 13, weight: .medium))
|
||||
.foregroundStyle(.secondary)
|
||||
SecureField("Bearer token", text: $claudeKey)
|
||||
.textContentType(.password)
|
||||
.autocorrectionDisabled()
|
||||
.textInputAutocapitalization(.never)
|
||||
.onChange(of: claudeKey) { _, new in
|
||||
ClaudeService.save(apiKey: new)
|
||||
viewModel.updateClaude(new.isEmpty ? nil : ClaudeService(apiKey: new))
|
||||
}
|
||||
.onChange(of: claudeKey) { _, _ in reloadClaude() }
|
||||
}
|
||||
.padding(.vertical, 4)
|
||||
|
||||
@@ -31,10 +42,12 @@ struct SettingsView: View {
|
||||
.font(.system(size: 13))
|
||||
.foregroundStyle(.secondary)
|
||||
} else {
|
||||
Text("Add an OpenRouter API key to enable AI features. Uses Gemini 2.0 Flash Lite — very cheap.")
|
||||
Text("Enter your backend URL and API key to enable AI features.")
|
||||
.font(.system(size: 13))
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
} header: {
|
||||
Text("AI Features")
|
||||
}
|
||||
|
||||
Section("Server") {
|
||||
@@ -55,4 +68,12 @@ struct SettingsView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func reloadClaude() {
|
||||
ClaudeService.save(backendUrl: backendUrl, apiKey: claudeKey)
|
||||
let service = backendUrl.isEmpty || claudeKey.isEmpty
|
||||
? nil
|
||||
: ClaudeService(backendUrl: backendUrl, apiKey: claudeKey)
|
||||
viewModel.updateClaude(service)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user