- 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>
80 lines
3.2 KiB
Swift
80 lines
3.2 KiB
Swift
import SwiftUI
|
|
|
|
struct SettingsView: View {
|
|
@Bindable var viewModel: BookmarksViewModel
|
|
let onDisconnect: () -> Void
|
|
|
|
@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 {
|
|
VStack(alignment: .leading, spacing: 6) {
|
|
Text("Backend URL")
|
|
.font(.system(size: 13, weight: .medium))
|
|
.foregroundStyle(.secondary)
|
|
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) { _, _ in reloadClaude() }
|
|
}
|
|
.padding(.vertical, 4)
|
|
|
|
if viewModel.claude != nil {
|
|
Label("AI enabled — semantic search, auto-tagging, and smart collections active.", systemImage: "sparkles")
|
|
.font(.system(size: 13))
|
|
.foregroundStyle(.secondary)
|
|
} else {
|
|
Text("Enter your backend URL and API key to enable AI features.")
|
|
.font(.system(size: 13))
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
} header: {
|
|
Text("AI Features")
|
|
}
|
|
|
|
Section("Server") {
|
|
Button(role: .destructive) {
|
|
dismiss()
|
|
onDisconnect()
|
|
} label: {
|
|
Label("Disconnect", systemImage: "person.crop.circle.badge.minus")
|
|
}
|
|
}
|
|
}
|
|
.navigationTitle("Settings")
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
.toolbar {
|
|
ToolbarItem(placement: .topBarTrailing) {
|
|
Button("Done") { dismiss() }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private func reloadClaude() {
|
|
ClaudeService.save(backendUrl: backendUrl, apiKey: claudeKey)
|
|
let service = backendUrl.isEmpty || claudeKey.isEmpty
|
|
? nil
|
|
: ClaudeService(backendUrl: backendUrl, apiKey: claudeKey)
|
|
viewModel.updateClaude(service)
|
|
}
|
|
}
|