Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions Sources/CodexBar/PreferencesAdvancedPane.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import CodexBarCore
import KeyboardShortcuts
import SwiftUI

Expand All @@ -6,6 +7,7 @@ struct AdvancedPane: View {
@Bindable var settings: SettingsStore
@State private var isInstallingCLI = false
@State private var cliStatus: String?
@StateObject private var proxyManager = ProxyManager()

var body: some View {
ScrollView(.vertical, showsIndicators: true) {
Expand Down Expand Up @@ -92,6 +94,60 @@ struct AdvancedPane: View {
subtitle: "Prevents any Keychain access while enabled.",
binding: self.$settings.debugDisableKeychainAccess)
}

Divider()

SettingsSection(
title: "Local Proxy",
caption: """
Run a local HTTP proxy to intercept API responses and track token usage. \
Set your API client's base URL to http://127.0.0.1:<port>.
""") {
PreferenceToggleRow(
title: "Enable local proxy",
subtitle: self.proxyManager.isRunning
? "Running on port \(self.proxyManager.activePort)"
: "Not running",
binding: Binding(
get: { self.settings.proxyEnabled },
set: { newValue in
self.settings.proxyEnabled = newValue
if newValue {
self.proxyManager.start(port: self.settings.proxyPort)
Comment on lines +112 to +116
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Initialize proxy from persisted enabled state

The proxy is only started inside the toggle setter. When proxyEnabled is already true from saved defaults (e.g., app relaunch), this setter is never invoked, so the UI can show the feature enabled while no proxy is running until the user toggles it again. Add an initialization path (e.g., on appear/app startup) that starts the proxy when persisted state is enabled.

Useful? React with 👍 / 👎.

} else {
self.proxyManager.stop()
}
}))

HStack(spacing: 12) {
Text("Port")
.font(.body)
Spacer()
TextField("9876", value: self.$settings.proxyPort, format: .number)
.textFieldStyle(.roundedBorder)
.frame(width: 80)
.multilineTextAlignment(.trailing)
}

if self.proxyManager.isRunning {
HStack(spacing: 8) {
Circle()
.fill(.green)
.frame(width: 8, height: 8)
Text("Listening on 127.0.0.1:\(self.proxyManager.activePort)")
.font(.footnote)
.foregroundStyle(.secondary)
Spacer()
Button("Copy URL") {
NSPasteboard.general.clearContents()
NSPasteboard.general.setString(
"http://127.0.0.1:\(self.proxyManager.activePort)",
forType: .string)
}
.controlSize(.small)
}
}
}
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal, 20)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import AppKit
import CodexBarCore
import CodexBarMacroSupport
import Foundation
import SwiftUI

@ProviderImplementationRegistration
struct DoubaoProviderImplementation: ProviderImplementation {
let id: UsageProvider = .doubao

@MainActor
func presentation(context _: ProviderPresentationContext) -> ProviderPresentation {
ProviderPresentation { _ in "web" }
}

@MainActor
func observeSettings(_ settings: SettingsStore) {
_ = settings.doubaoCookieSource
_ = settings.doubaoManualCookieHeader
}

@MainActor
func settingsSnapshot(context: ProviderSettingsSnapshotContext) -> ProviderSettingsSnapshotContribution? {
.doubao(context.settings.doubaoSettingsSnapshot(tokenOverride: context.tokenOverride))
}

@MainActor
func isAvailable(context: ProviderAvailabilityContext) -> Bool {
if DoubaoSettingsReader.apiKey(environment: context.environment) != nil {
return true
}
return !context.settings.tokenAccounts(for: .doubao).isEmpty
}

@MainActor
func settingsPickers(context: ProviderSettingsContext) -> [ProviderSettingsPickerDescriptor] {
let cookieBinding = Binding(
get: { context.settings.doubaoCookieSource.rawValue },
set: { raw in
context.settings.doubaoCookieSource = ProviderCookieSource(rawValue: raw) ?? .auto
})
let options = ProviderCookieSourceUI.options(
allowsOff: true,
keychainDisabled: context.settings.debugDisableKeychainAccess)

let subtitle: () -> String? = {
ProviderCookieSourceUI.subtitle(
source: context.settings.doubaoCookieSource,
keychainDisabled: context.settings.debugDisableKeychainAccess,
auto: "Automatic imports browser cookies.",
manual: "Paste the full Cookie header value.",
off: "Doubao cookies are disabled.")
}

return [
ProviderSettingsPickerDescriptor(
id: "doubao-cookie-source",
title: "Cookie source",
subtitle: "Automatic imports browser cookies.",
dynamicSubtitle: subtitle,
binding: cookieBinding,
options: options,
isVisible: nil,
onChange: nil),
]
}

@MainActor
func settingsFields(context: ProviderSettingsContext) -> [ProviderSettingsFieldDescriptor] {
[
ProviderSettingsFieldDescriptor(
id: "doubao-cookie",
title: "",
subtitle: "",
kind: .secure,
placeholder: "Cookie: \u{2026}\n\nPaste the full Cookie header from console.volcengine.com",
binding: context.stringBinding(\.doubaoManualCookieHeader),
actions: [
ProviderSettingsActionDescriptor(
id: "doubao-open-console",
title: "Open Console",
style: .link,
isVisible: nil,
perform: {
if let url = URL(string: "https://console.volcengine.com/ark") {
NSWorkspace.shared.open(url)
}
}),
],
isVisible: { context.settings.doubaoCookieSource == .manual },
onActivate: nil),
]
}
}
33 changes: 33 additions & 0 deletions Sources/CodexBar/Providers/Doubao/DoubaoSettingsStore.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import CodexBarCore
import Foundation

extension SettingsStore {
var doubaoManualCookieHeader: String {
get { self.configSnapshot.providerConfig(for: .doubao)?.sanitizedCookieHeader ?? "" }
set {
self.updateProviderConfig(provider: .doubao) { entry in
entry.cookieHeader = self.normalizedConfigValue(newValue)
}
self.logSecretUpdate(provider: .doubao, field: "cookieHeader", value: newValue)
}
}

var doubaoCookieSource: ProviderCookieSource {
get { self.resolvedCookieSource(provider: .doubao, fallback: .auto) }
set {
self.updateProviderConfig(provider: .doubao) { entry in
entry.cookieSource = newValue
}
self.logProviderModeChange(provider: .doubao, field: "cookieSource", value: newValue.rawValue)
}
}
}

extension SettingsStore {
func doubaoSettingsSnapshot(tokenOverride: TokenAccountOverride?) -> ProviderSettingsSnapshot.DoubaoProviderSettings {
_ = tokenOverride
return ProviderSettingsSnapshot.DoubaoProviderSettings(
cookieSource: self.doubaoCookieSource,
manualCookieHeader: self.doubaoManualCookieHeader)
}
}
94 changes: 94 additions & 0 deletions Sources/CodexBar/Providers/Ernie/ErnieProviderImplementation.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import AppKit
import CodexBarCore
import CodexBarMacroSupport
import Foundation
import SwiftUI

@ProviderImplementationRegistration
struct ErnieProviderImplementation: ProviderImplementation {
let id: UsageProvider = .ernie

@MainActor
func presentation(context _: ProviderPresentationContext) -> ProviderPresentation {
ProviderPresentation { _ in "web" }
}

@MainActor
func observeSettings(_ settings: SettingsStore) {
_ = settings.ernieCookieSource
_ = settings.ernieManualCookieHeader
}

@MainActor
func settingsSnapshot(context: ProviderSettingsSnapshotContext) -> ProviderSettingsSnapshotContribution? {
.ernie(context.settings.ernieSettingsSnapshot(tokenOverride: context.tokenOverride))
}

@MainActor
func isAvailable(context: ProviderAvailabilityContext) -> Bool {
if ErnieSettingsReader.apiKey(environment: context.environment) != nil {
return true
}
return !context.settings.tokenAccounts(for: .ernie).isEmpty
}

@MainActor
func settingsPickers(context: ProviderSettingsContext) -> [ProviderSettingsPickerDescriptor] {
let cookieBinding = Binding(
get: { context.settings.ernieCookieSource.rawValue },
set: { raw in
context.settings.ernieCookieSource = ProviderCookieSource(rawValue: raw) ?? .auto
})
let options = ProviderCookieSourceUI.options(
allowsOff: true,
keychainDisabled: context.settings.debugDisableKeychainAccess)

let subtitle: () -> String? = {
ProviderCookieSourceUI.subtitle(
source: context.settings.ernieCookieSource,
keychainDisabled: context.settings.debugDisableKeychainAccess,
auto: "Automatic imports browser cookies.",
manual: "Paste the full Cookie header value.",
off: "ERNIE cookies are disabled.")
}

return [
ProviderSettingsPickerDescriptor(
id: "ernie-cookie-source",
title: "Cookie source",
subtitle: "Automatic imports browser cookies.",
dynamicSubtitle: subtitle,
binding: cookieBinding,
options: options,
isVisible: nil,
onChange: nil),
]
}

@MainActor
func settingsFields(context: ProviderSettingsContext) -> [ProviderSettingsFieldDescriptor] {
[
ProviderSettingsFieldDescriptor(
id: "ernie-cookie",
title: "",
subtitle: "",
kind: .secure,
placeholder: "Cookie: \u{2026}\n\nPaste the full Cookie header from console.bce.baidu.com",
binding: context.stringBinding(\.ernieManualCookieHeader),
actions: [
ProviderSettingsActionDescriptor(
id: "ernie-open-console",
title: "Open Console",
style: .link,
isVisible: nil,
perform: {
if let url = URL(string: "https://console.bce.baidu.com/qianfan/overview") {
NSWorkspace.shared.open(url)
}
}),
],
isVisible: { context.settings.ernieCookieSource == .manual },
onActivate: nil),
]
}
}
33 changes: 33 additions & 0 deletions Sources/CodexBar/Providers/Ernie/ErnieSettingsStore.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import CodexBarCore
import Foundation

extension SettingsStore {
var ernieManualCookieHeader: String {
get { self.configSnapshot.providerConfig(for: .ernie)?.sanitizedCookieHeader ?? "" }
set {
self.updateProviderConfig(provider: .ernie) { entry in
entry.cookieHeader = self.normalizedConfigValue(newValue)
}
self.logSecretUpdate(provider: .ernie, field: "cookieHeader", value: newValue)
}
}

var ernieCookieSource: ProviderCookieSource {
get { self.resolvedCookieSource(provider: .ernie, fallback: .auto) }
set {
self.updateProviderConfig(provider: .ernie) { entry in
entry.cookieSource = newValue
}
self.logProviderModeChange(provider: .ernie, field: "cookieSource", value: newValue.rawValue)
}
}
}

extension SettingsStore {
func ernieSettingsSnapshot(tokenOverride: TokenAccountOverride?) -> ProviderSettingsSnapshot.ErnieProviderSettings {
_ = tokenOverride
return ProviderSettingsSnapshot.ErnieProviderSettings(
cookieSource: self.ernieCookieSource,
manualCookieHeader: self.ernieManualCookieHeader)
}
}
Loading