mirror of
https://github.com/awizemann/scarf.git
synced 2026-05-10 10:36:35 +00:00
feat(settings): add Web Tools tab with v0.13 search/extract split
Adds a new "Web Tools" Settings tab (between Browser and Voice) with two distinct shapes that share the same chrome: - Pre-v0.13: a single "Backend" picker writing the legacy `web_tools.backend` key (so v0.12 users still configure web tools). - v0.13+: two pickers — Search backend writes `web_tools.search.backend` (SearXNG appears here only — Hermes registers it as a search-only dispatch), Extract backend writes `web_tools.extract.backend`. Capability gate: `hasWebToolsBackendSplit` chooses which shape renders. The tab itself is always visible — pre-v0.13 users would otherwise lose access to the legacy combined-backend picker. Model layer: - `HermesConfig.webToolsBackend` / `webToolsSearchBackend` / `webToolsExtractBackend` — three fields, each round-tripping its own YAML key. Defaults: `duckduckgo` / `duckduckgo` / `reader`. - YAML parser reads all three keys via the existing `str(...)` helper. Pre-v0.13 hosts populate only `webToolsBackend`; the split keys default to the same backend so the picker shows the same value the user already had. TODO markers (WS-7-Q6/Q7) flag the inline backend lists + legacy fallback semantics — verify against `~/.hermes/hermes-agent/ hermes_cli/web_tools.py` during integration. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -666,6 +666,18 @@ public struct HermesConfig: Sendable {
|
||||
/// final reply (provider/model/cost/turn count). Off by default;
|
||||
/// useful for cost auditing and screen-recording demos.
|
||||
public var runtimeMetadataFooter: Bool
|
||||
/// Pre-v0.13: single combined Web Tools backend at `web_tools.backend`.
|
||||
/// v0.13 split this into per-capability keys (see below). Kept readable
|
||||
/// for round-trip compatibility on hosts that never migrated; v0.13+
|
||||
/// hosts ignore this scalar and read the split keys instead.
|
||||
public var webToolsBackend: String
|
||||
/// v0.13+: `web_tools.search.backend`. SearXNG is search-only and
|
||||
/// can land here. Pre-v0.13 hosts default to the same value as the
|
||||
/// combined backend.
|
||||
public var webToolsSearchBackend: String
|
||||
/// v0.13+: `web_tools.extract.backend`. Pre-v0.13 hosts default to
|
||||
/// the same value as the combined backend.
|
||||
public var webToolsExtractBackend: String
|
||||
|
||||
// Grouped blocks
|
||||
public var display: DisplaySettings
|
||||
@@ -747,11 +759,17 @@ public struct HermesConfig: Sendable {
|
||||
homeAssistant: HomeAssistantSettings,
|
||||
cacheTTL: String = "5m",
|
||||
redactionEnabled: Bool = false,
|
||||
runtimeMetadataFooter: Bool = false
|
||||
runtimeMetadataFooter: Bool = false,
|
||||
webToolsBackend: String = "duckduckgo",
|
||||
webToolsSearchBackend: String = "duckduckgo",
|
||||
webToolsExtractBackend: String = "reader"
|
||||
) {
|
||||
self.cacheTTL = cacheTTL
|
||||
self.redactionEnabled = redactionEnabled
|
||||
self.runtimeMetadataFooter = runtimeMetadataFooter
|
||||
self.webToolsBackend = webToolsBackend
|
||||
self.webToolsSearchBackend = webToolsSearchBackend
|
||||
self.webToolsExtractBackend = webToolsExtractBackend
|
||||
self.model = model
|
||||
self.provider = provider
|
||||
self.maxTurns = maxTurns
|
||||
|
||||
@@ -284,7 +284,14 @@ public extension HermesConfig {
|
||||
homeAssistant: homeAssistant,
|
||||
cacheTTL: str("prompt_caching.cache_ttl", default: "5m"),
|
||||
redactionEnabled: bool("redaction.enabled", default: false),
|
||||
runtimeMetadataFooter: bool("agent.runtime_metadata_footer", default: false)
|
||||
runtimeMetadataFooter: bool("agent.runtime_metadata_footer", default: false),
|
||||
// Pre-v0.13 hosts wrote a single `web_tools.backend`. v0.13 split
|
||||
// it into per-capability keys. Read all three so the round-trip
|
||||
// never loses a value the user already set; the WebTools tab
|
||||
// chooses which to render based on `hasWebToolsBackendSplit`.
|
||||
webToolsBackend: str("web_tools.backend", default: "duckduckgo"),
|
||||
webToolsSearchBackend: str("web_tools.search.backend", default: "duckduckgo"),
|
||||
webToolsExtractBackend: str("web_tools.extract.backend", default: "reader")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,6 +143,16 @@ final class SettingsViewModel {
|
||||
func setBrowserAllowPrivateURLs(_ value: Bool) { setSetting("browser.allow_private_urls", value: value ? "true" : "false") }
|
||||
func setCamofoxManagedPersistence(_ value: Bool) { setSetting("browser.camofox.managed_persistence", value: value ? "true" : "false") }
|
||||
|
||||
// MARK: - Web Tools
|
||||
|
||||
/// Pre-v0.13 combined backend. Pre-v0.13 hosts read this; v0.13+
|
||||
/// hosts read it for back-compat but the WebToolsTab gates writes
|
||||
/// on `hasWebToolsBackendSplit` so the tab only writes the split
|
||||
/// keys on v0.13.
|
||||
func setWebToolsBackend(_ value: String) { setSetting("web_tools.backend", value: value) }
|
||||
func setWebToolsSearchBackend(_ value: String) { setSetting("web_tools.search.backend", value: value) }
|
||||
func setWebToolsExtractBackend(_ value: String) { setSetting("web_tools.extract.backend", value: value) }
|
||||
|
||||
// MARK: - Voice / TTS / STT
|
||||
|
||||
func setAutoTTS(_ value: Bool) { setSetting("voice.auto_tts", value: value ? "true" : "false") }
|
||||
|
||||
@@ -26,6 +26,7 @@ struct SettingsView: View {
|
||||
case agent = "Agent"
|
||||
case terminal = "Terminal"
|
||||
case browser = "Browser"
|
||||
case webTools = "Web Tools"
|
||||
case voice = "Voice"
|
||||
case memory = "Memory"
|
||||
case auxiliary = "Aux Models"
|
||||
@@ -41,6 +42,7 @@ struct SettingsView: View {
|
||||
case .agent: return "Agent"
|
||||
case .terminal: return "Terminal"
|
||||
case .browser: return "Browser"
|
||||
case .webTools: return "Web Tools"
|
||||
case .voice: return "Voice"
|
||||
case .memory: return "Memory"
|
||||
case .auxiliary: return "Aux Models"
|
||||
@@ -56,6 +58,7 @@ struct SettingsView: View {
|
||||
case .agent: return "brain.head.profile"
|
||||
case .terminal: return "terminal"
|
||||
case .browser: return "globe"
|
||||
case .webTools: return "globe.americas"
|
||||
case .voice: return "mic"
|
||||
case .memory: return "memorychip"
|
||||
case .auxiliary: return "sparkles.rectangle.stack"
|
||||
@@ -171,6 +174,7 @@ struct SettingsView: View {
|
||||
case .agent: AgentTab(viewModel: viewModel)
|
||||
case .terminal: TerminalTab(viewModel: viewModel)
|
||||
case .browser: BrowserTab(viewModel: viewModel)
|
||||
case .webTools: WebToolsTab(viewModel: viewModel)
|
||||
case .voice: VoiceTab(viewModel: viewModel)
|
||||
case .memory: MemoryTab(viewModel: viewModel)
|
||||
case .auxiliary: AuxiliaryTab(viewModel: viewModel)
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
import SwiftUI
|
||||
import ScarfCore
|
||||
import ScarfDesign
|
||||
|
||||
/// Web Tools tab — search + extract backend pickers. Pre-v0.13 hosts
|
||||
/// see a single "Combined backend" row writing to the legacy
|
||||
/// `web_tools.backend` key. v0.13+ hosts see two rows writing to the
|
||||
/// per-capability split keys (`web_tools.search.backend` +
|
||||
/// `web_tools.extract.backend`); SearXNG appears in the search picker
|
||||
/// only because Hermes registers it as a search-only backend.
|
||||
struct WebToolsTab: View {
|
||||
@Bindable var viewModel: SettingsViewModel
|
||||
@Environment(\.hermesCapabilities) private var capabilitiesStore
|
||||
|
||||
private var split: Bool {
|
||||
capabilitiesStore?.capabilities.hasWebToolsBackendSplit ?? false
|
||||
}
|
||||
|
||||
// TODO(WS-7-Q6): Backend lists are curated inline based on the v0.13
|
||||
// release notes ("SearXNG joined search-only"). The exact dispatch
|
||||
// table lives in `~/.hermes/hermes-agent/hermes_cli/web_tools.py` —
|
||||
// verify during integration. A wrong entry just produces a
|
||||
// `hermes config set` failure on save (recoverable, not silent).
|
||||
private static let searchBackends: [String] = [
|
||||
"duckduckgo", "tavily", "brave", "exa", "you", "searxng"
|
||||
]
|
||||
private static let extractBackends: [String] = [
|
||||
"reader", "browserless", "trafilatura", "firecrawl"
|
||||
]
|
||||
/// v0.12 combined-backend list — superset of the v0.13 search list
|
||||
/// minus SearXNG (which only dispatches as search) plus the v0.13
|
||||
/// extract-only entries that pre-v0.13 hosts handled under the
|
||||
/// combined key.
|
||||
private static let combinedBackends: [String] = [
|
||||
"duckduckgo", "tavily", "brave", "exa", "you",
|
||||
"reader", "browserless", "trafilatura", "firecrawl"
|
||||
]
|
||||
|
||||
var body: some View {
|
||||
if split {
|
||||
SettingsSection(title: "Web Tools", icon: "globe.americas") {
|
||||
PickerRow(
|
||||
label: "Search backend",
|
||||
selection: viewModel.config.webToolsSearchBackend,
|
||||
options: Self.searchBackends
|
||||
) { viewModel.setWebToolsSearchBackend($0) }
|
||||
PickerRow(
|
||||
label: "Extract backend",
|
||||
selection: viewModel.config.webToolsExtractBackend,
|
||||
options: Self.extractBackends
|
||||
) { viewModel.setWebToolsExtractBackend($0) }
|
||||
}
|
||||
Text("SearXNG joined v0.13 as a search-only backend. Backend-specific tuning (host URLs, API keys) lives in the raw YAML editor for now.")
|
||||
.scarfStyle(.caption)
|
||||
.foregroundStyle(ScarfColor.foregroundMuted)
|
||||
.padding(.horizontal, ScarfSpace.s4)
|
||||
} else {
|
||||
// TODO(WS-7-Q7): Pre-v0.13 hosts fall back to the legacy single
|
||||
// backend. v0.13 may or may not honour `web_tools.backend` as a
|
||||
// fallback when the split keys are absent — verify with Hermes
|
||||
// and consider a one-time migration prompt in a follow-up if
|
||||
// upgrading from v0.12 silently resets the user's backend.
|
||||
SettingsSection(title: "Web Tools", icon: "globe.americas") {
|
||||
PickerRow(
|
||||
label: "Backend",
|
||||
selection: viewModel.config.webToolsBackend,
|
||||
options: Self.combinedBackends
|
||||
) { viewModel.setWebToolsBackend($0) }
|
||||
}
|
||||
Text("Hermes v0.13 splits search and extract into separate backends. Update Hermes to access the per-capability picker.")
|
||||
.scarfStyle(.caption)
|
||||
.foregroundStyle(ScarfColor.foregroundFaint)
|
||||
.padding(.horizontal, ScarfSpace.s4)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user