mirror of
https://github.com/block/goose.git
synced 2026-04-28 03:29:36 +00:00
fix: metrics on posthog (#6024)
Some checks are pending
Canary / Prepare Version (push) Waiting to run
Canary / build-cli (push) Blocked by required conditions
Canary / Upload Install Script (push) Blocked by required conditions
Canary / bundle-desktop (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
CI / changes (push) Waiting to run
CI / Check Rust Code Format (push) Blocked by required conditions
CI / Build and Test Rust Project (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check OpenAPI Schema is Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (push) Blocked by required conditions
Live Provider Tests / check-fork (push) Waiting to run
Live Provider Tests / changes (push) Blocked by required conditions
Live Provider Tests / Build Release Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Documentation Site Preview / deploy (push) Waiting to run
Publish Docker Image / docker (push) Waiting to run
Some checks are pending
Canary / Prepare Version (push) Waiting to run
Canary / build-cli (push) Blocked by required conditions
Canary / Upload Install Script (push) Blocked by required conditions
Canary / bundle-desktop (push) Blocked by required conditions
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
CI / changes (push) Waiting to run
CI / Check Rust Code Format (push) Blocked by required conditions
CI / Build and Test Rust Project (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check OpenAPI Schema is Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (push) Blocked by required conditions
Live Provider Tests / check-fork (push) Waiting to run
Live Provider Tests / changes (push) Blocked by required conditions
Live Provider Tests / Build Release Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Documentation Site Preview / deploy (push) Waiting to run
Publish Docker Image / docker (push) Waiting to run
Co-authored-by: Zane Staggs <zane@squareup.com> Co-authored-by: Zane <75694352+zanesq@users.noreply.github.com>
This commit is contained in:
parent
02189f5555
commit
9b6a1c1297
20 changed files with 689 additions and 34 deletions
51
Cargo.lock
generated
51
Cargo.lock
generated
|
|
@ -1964,6 +1964,37 @@ dependencies = [
|
|||
"syn 2.0.99",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_builder"
|
||||
version = "0.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947"
|
||||
dependencies = [
|
||||
"derive_builder_macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_builder_core"
|
||||
version = "0.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8"
|
||||
dependencies = [
|
||||
"darling 0.20.10",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.99",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_builder_macro"
|
||||
version = "0.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c"
|
||||
dependencies = [
|
||||
"derive_builder_core",
|
||||
"syn 2.0.99",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "devgen-tree-sitter-swift"
|
||||
version = "0.21.0"
|
||||
|
|
@ -2025,7 +2056,7 @@ dependencies = [
|
|||
"libc",
|
||||
"option-ext",
|
||||
"redox_users 0.5.2",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2679,6 +2710,7 @@ dependencies = [
|
|||
"opentelemetry-otlp",
|
||||
"opentelemetry_sdk",
|
||||
"paste",
|
||||
"posthog-rs",
|
||||
"rand 0.8.5",
|
||||
"regex",
|
||||
"reqwest 0.12.12",
|
||||
|
|
@ -4996,6 +5028,21 @@ dependencies = [
|
|||
"portable-atomic",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "posthog-rs"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "610d236077c94c96194b0d2b40989bda4bf8faf60e15a0da6ef8ffd4c78f6c1c"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"derive_builder",
|
||||
"reqwest 0.11.27",
|
||||
"semver",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "powerfmt"
|
||||
version = "0.2.0"
|
||||
|
|
@ -5545,6 +5592,7 @@ dependencies = [
|
|||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
"webpki-roots 0.25.4",
|
||||
"winreg 0.50.0",
|
||||
]
|
||||
|
||||
|
|
@ -7740,6 +7788,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "e0f540e3240398cce6128b64ba83fdbdd86129c16a3aa1a3a252efd66eb3d587"
|
||||
dependencies = [
|
||||
"getrandom 0.3.1",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
|||
|
|
@ -248,6 +248,8 @@ pub struct SessionSettings {
|
|||
}
|
||||
|
||||
pub async fn build_session(session_config: SessionBuilderConfig) -> CliSession {
|
||||
goose::posthog::set_session_context("cli", session_config.resume);
|
||||
|
||||
let config = Config::global();
|
||||
|
||||
let (saved_provider, saved_model_config) = if session_config.resume {
|
||||
|
|
|
|||
|
|
@ -105,6 +105,8 @@ async fn start_agent(
|
|||
State(state): State<Arc<AppState>>,
|
||||
Json(payload): Json<StartAgentRequest>,
|
||||
) -> Result<Json<Session>, ErrorResponse> {
|
||||
goose::posthog::set_session_context("desktop", false);
|
||||
|
||||
let StartAgentRequest {
|
||||
working_dir,
|
||||
recipe,
|
||||
|
|
@ -197,6 +199,8 @@ async fn resume_agent(
|
|||
State(state): State<Arc<AppState>>,
|
||||
Json(payload): Json<ResumeAgentRequest>,
|
||||
) -> Result<Json<Session>, ErrorResponse> {
|
||||
goose::posthog::set_session_context("desktop", true);
|
||||
|
||||
let session = SessionManager::get_session(&payload.session_id, true)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
|
|
|
|||
|
|
@ -111,6 +111,7 @@ shellexpand = "3.1.1"
|
|||
indexmap = "2.12.0"
|
||||
ignore = "0.4.25"
|
||||
which = "8.0.0"
|
||||
posthog-rs = "0.3.7"
|
||||
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
|
|
|
|||
|
|
@ -1221,7 +1221,8 @@ impl Agent {
|
|||
no_tools_called = false;
|
||||
}
|
||||
}
|
||||
Err(ProviderError::ContextLengthExceeded(_error_msg)) => {
|
||||
Err(ref provider_err @ ProviderError::ContextLengthExceeded(_)) => {
|
||||
crate::posthog::emit_error(provider_err.telemetry_type());
|
||||
yield AgentEvent::Message(
|
||||
Message::assistant().with_system_notification(
|
||||
SystemNotificationType::InlineMessage,
|
||||
|
|
@ -1255,11 +1256,12 @@ impl Agent {
|
|||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Error: {}", e);
|
||||
Err(ref provider_err) => {
|
||||
crate::posthog::emit_error(provider_err.telemetry_type());
|
||||
error!("Error: {}", provider_err);
|
||||
yield AgentEvent::Message(
|
||||
Message::assistant().with_text(
|
||||
format!("Ran into this error: {e}.\n\nPlease retry if you think this is a transient or recoverable error.")
|
||||
format!("Ran into this error: {provider_err}.\n\nPlease retry if you think this is a transient or recoverable error.")
|
||||
)
|
||||
);
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -38,9 +38,9 @@ impl ToolRouteManager {
|
|||
|
||||
pub async fn record_tool_requests(&self, requests: &[ToolRequest]) {
|
||||
let selector = self.router_tool_selector.lock().await.clone();
|
||||
if let Some(selector) = selector {
|
||||
for request in requests {
|
||||
if let Ok(tool_call) = &request.tool_call {
|
||||
for request in requests {
|
||||
if let Ok(tool_call) = &request.tool_call {
|
||||
if let Some(ref selector) = selector {
|
||||
if let Err(e) = selector.record_tool_call(&tool_call.name).await {
|
||||
error!("Failed to record tool call: {}", e);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ pub mod mcp_utils;
|
|||
pub mod model;
|
||||
pub mod oauth;
|
||||
pub mod permission;
|
||||
pub mod posthog;
|
||||
pub mod prompt_template;
|
||||
pub mod providers;
|
||||
pub mod recipe;
|
||||
|
|
|
|||
303
crates/goose/src/posthog.rs
Normal file
303
crates/goose/src/posthog.rs
Normal file
|
|
@ -0,0 +1,303 @@
|
|||
//! PostHog telemetry - fires once per session creation.
|
||||
|
||||
use crate::config::paths::Paths;
|
||||
use crate::config::{get_enabled_extensions, Config};
|
||||
use crate::session::SessionManager;
|
||||
use chrono::{DateTime, Utc};
|
||||
use once_cell::sync::Lazy;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fs;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Mutex;
|
||||
use uuid::Uuid;
|
||||
|
||||
const POSTHOG_API_KEY: &str = "phc_RyX5CaY01VtZJCQyhSR5KFh6qimUy81YwxsEpotAftT";
|
||||
|
||||
/// Config key for telemetry opt-out preference
|
||||
pub const TELEMETRY_ENABLED_KEY: &str = "GOOSE_TELEMETRY_ENABLED";
|
||||
|
||||
static TELEMETRY_DISABLED_BY_ENV: Lazy<AtomicBool> = Lazy::new(|| {
|
||||
std::env::var("GOOSE_TELEMETRY_OFF")
|
||||
.map(|v| v == "1" || v.to_lowercase() == "true")
|
||||
.unwrap_or(false)
|
||||
.into()
|
||||
});
|
||||
|
||||
/// Check if telemetry is enabled.
|
||||
///
|
||||
/// Returns false if:
|
||||
/// - GOOSE_TELEMETRY_OFF environment variable is set to "1" or "true"
|
||||
/// - GOOSE_TELEMETRY_ENABLED config value is set to false
|
||||
///
|
||||
/// Returns true otherwise (telemetry is opt-out, enabled by default)
|
||||
pub fn is_telemetry_enabled() -> bool {
|
||||
if TELEMETRY_DISABLED_BY_ENV.load(Ordering::Relaxed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let config = Config::global();
|
||||
config
|
||||
.get_param::<bool>(TELEMETRY_ENABLED_KEY)
|
||||
.unwrap_or(true)
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Installation Tracking
|
||||
// ============================================================================
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
struct InstallationData {
|
||||
installation_id: String,
|
||||
first_seen: DateTime<Utc>,
|
||||
session_count: u32,
|
||||
}
|
||||
|
||||
impl Default for InstallationData {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
installation_id: Uuid::new_v4().to_string(),
|
||||
first_seen: Utc::now(),
|
||||
session_count: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn installation_file_path() -> std::path::PathBuf {
|
||||
Paths::state_dir().join("telemetry_installation.json")
|
||||
}
|
||||
|
||||
fn load_or_create_installation() -> InstallationData {
|
||||
let path = installation_file_path();
|
||||
|
||||
if let Ok(contents) = fs::read_to_string(&path) {
|
||||
if let Ok(data) = serde_json::from_str::<InstallationData>(&contents) {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
let data = InstallationData::default();
|
||||
save_installation(&data);
|
||||
data
|
||||
}
|
||||
|
||||
fn save_installation(data: &InstallationData) {
|
||||
let path = installation_file_path();
|
||||
|
||||
if let Some(parent) = path.parent() {
|
||||
let _ = fs::create_dir_all(parent);
|
||||
}
|
||||
|
||||
if let Ok(json) = serde_json::to_string_pretty(data) {
|
||||
let _ = fs::write(path, json);
|
||||
}
|
||||
}
|
||||
|
||||
fn increment_session_count() -> InstallationData {
|
||||
let mut data = load_or_create_installation();
|
||||
data.session_count += 1;
|
||||
save_installation(&data);
|
||||
data
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Platform Info
|
||||
// ============================================================================
|
||||
|
||||
fn get_platform_version() -> Option<String> {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
std::process::Command::new("sw_vers")
|
||||
.arg("-productVersion")
|
||||
.output()
|
||||
.ok()
|
||||
.and_then(|o| String::from_utf8(o.stdout).ok())
|
||||
.map(|s| s.trim().to_string())
|
||||
}
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
fs::read_to_string("/etc/os-release")
|
||||
.ok()
|
||||
.and_then(|content| {
|
||||
content
|
||||
.lines()
|
||||
.find(|line| line.starts_with("VERSION_ID="))
|
||||
.map(|line| {
|
||||
line.trim_start_matches("VERSION_ID=")
|
||||
.trim_matches('"')
|
||||
.to_string()
|
||||
})
|
||||
})
|
||||
}
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
std::process::Command::new("cmd")
|
||||
.args(["/C", "ver"])
|
||||
.output()
|
||||
.ok()
|
||||
.and_then(|o| String::from_utf8(o.stdout).ok())
|
||||
.map(|s| s.trim().to_string())
|
||||
}
|
||||
#[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
|
||||
{
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn detect_install_method() -> String {
|
||||
let exe_path = std::env::current_exe().ok();
|
||||
|
||||
if let Some(path) = exe_path {
|
||||
let path_str = path.to_string_lossy().to_lowercase();
|
||||
|
||||
if path_str.contains("homebrew") || path_str.contains("/opt/homebrew") {
|
||||
return "homebrew".to_string();
|
||||
}
|
||||
if path_str.contains(".cargo") {
|
||||
return "cargo".to_string();
|
||||
}
|
||||
if path_str.contains("applications") || path_str.contains(".app") {
|
||||
return "desktop".to_string();
|
||||
}
|
||||
}
|
||||
|
||||
if std::env::var("GOOSE_DESKTOP").is_ok() {
|
||||
return "desktop".to_string();
|
||||
}
|
||||
|
||||
"binary".to_string()
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Session Context (set by CLI/Desktop at startup)
|
||||
// ============================================================================
|
||||
|
||||
static SESSION_INTERFACE: Lazy<Mutex<Option<String>>> = Lazy::new(|| Mutex::new(None));
|
||||
static SESSION_IS_RESUMED: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
pub fn set_session_context(interface: &str, is_resumed: bool) {
|
||||
if let Ok(mut iface) = SESSION_INTERFACE.lock() {
|
||||
*iface = Some(interface.to_string());
|
||||
}
|
||||
SESSION_IS_RESUMED.store(is_resumed, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
fn get_session_interface() -> String {
|
||||
SESSION_INTERFACE
|
||||
.lock()
|
||||
.ok()
|
||||
.and_then(|i| i.clone())
|
||||
.unwrap_or_else(|| "unknown".to_string())
|
||||
}
|
||||
|
||||
fn get_session_is_resumed() -> bool {
|
||||
SESSION_IS_RESUMED.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Telemetry Events
|
||||
// ============================================================================
|
||||
|
||||
pub fn emit_session_started() {
|
||||
if !is_telemetry_enabled() {
|
||||
return;
|
||||
}
|
||||
|
||||
let installation = increment_session_count();
|
||||
|
||||
tokio::spawn(async move {
|
||||
let _ = send_session_event(&installation).await;
|
||||
});
|
||||
}
|
||||
|
||||
pub fn emit_error(error_type: &str) {
|
||||
if !is_telemetry_enabled() {
|
||||
return;
|
||||
}
|
||||
|
||||
let installation = load_or_create_installation();
|
||||
let error_type = error_type.to_string();
|
||||
|
||||
tokio::spawn(async move {
|
||||
let _ = send_error_event(&installation, &error_type).await;
|
||||
});
|
||||
}
|
||||
|
||||
async fn send_error_event(installation: &InstallationData, error_type: &str) -> Result<(), String> {
|
||||
let client = posthog_rs::client(POSTHOG_API_KEY).await;
|
||||
let mut event = posthog_rs::Event::new("error", &installation.installation_id);
|
||||
|
||||
event.insert_prop("error_type", error_type).ok();
|
||||
event.insert_prop("version", env!("CARGO_PKG_VERSION")).ok();
|
||||
event.insert_prop("interface", get_session_interface()).ok();
|
||||
event.insert_prop("os", std::env::consts::OS).ok();
|
||||
event.insert_prop("arch", std::env::consts::ARCH).ok();
|
||||
|
||||
if let Some(platform_version) = get_platform_version() {
|
||||
event.insert_prop("platform_version", platform_version).ok();
|
||||
}
|
||||
|
||||
let config = Config::global();
|
||||
if let Ok(provider) = config.get_param::<String>("GOOSE_PROVIDER") {
|
||||
event.insert_prop("provider", provider).ok();
|
||||
}
|
||||
if let Ok(model) = config.get_param::<String>("GOOSE_MODEL") {
|
||||
event.insert_prop("model", model).ok();
|
||||
}
|
||||
|
||||
client.capture(event).await.map_err(|e| format!("{:?}", e))
|
||||
}
|
||||
|
||||
async fn send_session_event(installation: &InstallationData) -> Result<(), String> {
|
||||
let client = posthog_rs::client(POSTHOG_API_KEY).await;
|
||||
let mut event = posthog_rs::Event::new("session_started", &installation.installation_id);
|
||||
|
||||
event.insert_prop("os", std::env::consts::OS).ok();
|
||||
event.insert_prop("arch", std::env::consts::ARCH).ok();
|
||||
event.insert_prop("version", env!("CARGO_PKG_VERSION")).ok();
|
||||
|
||||
if let Some(platform_version) = get_platform_version() {
|
||||
event.insert_prop("platform_version", platform_version).ok();
|
||||
}
|
||||
|
||||
event
|
||||
.insert_prop("install_method", detect_install_method())
|
||||
.ok();
|
||||
|
||||
event.insert_prop("interface", get_session_interface()).ok();
|
||||
|
||||
event
|
||||
.insert_prop("is_resumed", get_session_is_resumed())
|
||||
.ok();
|
||||
|
||||
event
|
||||
.insert_prop("session_number", installation.session_count)
|
||||
.ok();
|
||||
let days_since_install = (Utc::now() - installation.first_seen).num_days();
|
||||
event
|
||||
.insert_prop("days_since_install", days_since_install)
|
||||
.ok();
|
||||
|
||||
let config = Config::global();
|
||||
if let Ok(provider) = config.get_param::<String>("GOOSE_PROVIDER") {
|
||||
event.insert_prop("provider", provider).ok();
|
||||
}
|
||||
if let Ok(model) = config.get_param::<String>("GOOSE_MODEL") {
|
||||
event.insert_prop("model", model).ok();
|
||||
}
|
||||
|
||||
let extensions = get_enabled_extensions();
|
||||
event.insert_prop("extensions_count", extensions.len()).ok();
|
||||
let extension_names: Vec<String> = extensions.iter().map(|e| e.name()).collect();
|
||||
event.insert_prop("extensions", extension_names).ok();
|
||||
|
||||
if let Ok(insights) = SessionManager::get_insights().await {
|
||||
event
|
||||
.insert_prop("total_sessions", insights.total_sessions)
|
||||
.ok();
|
||||
event
|
||||
.insert_prop("total_tokens", insights.total_tokens)
|
||||
.ok();
|
||||
}
|
||||
|
||||
client.capture(event).await.map_err(|e| format!("{:?}", e))
|
||||
}
|
||||
|
|
@ -32,6 +32,21 @@ pub enum ProviderError {
|
|||
NotImplemented(String),
|
||||
}
|
||||
|
||||
impl ProviderError {
|
||||
pub fn telemetry_type(&self) -> &'static str {
|
||||
match self {
|
||||
ProviderError::Authentication(_) => "auth",
|
||||
ProviderError::ContextLengthExceeded(_) => "context_length",
|
||||
ProviderError::RateLimitExceeded { .. } => "rate_limit",
|
||||
ProviderError::ServerError(_) => "server",
|
||||
ProviderError::RequestFailed(_) => "request",
|
||||
ProviderError::ExecutionError(_) => "execution",
|
||||
ProviderError::UsageError(_) => "usage",
|
||||
ProviderError::NotImplemented(_) => "not_implemented",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<anyhow::Error> for ProviderError {
|
||||
fn from(error: anyhow::Error) -> Self {
|
||||
if let Some(reqwest_err) = error.downcast_ref::<reqwest::Error>() {
|
||||
|
|
|
|||
|
|
@ -120,8 +120,8 @@ pub struct SessionUpdateBuilder {
|
|||
#[derive(Serialize, ToSchema, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SessionInsights {
|
||||
total_sessions: usize,
|
||||
total_tokens: i64,
|
||||
pub total_sessions: usize,
|
||||
pub total_tokens: i64,
|
||||
}
|
||||
|
||||
impl SessionUpdateBuilder {
|
||||
|
|
@ -887,6 +887,7 @@ impl SessionStorage {
|
|||
.await?;
|
||||
|
||||
tx.commit().await?;
|
||||
crate::posthog::emit_session_started();
|
||||
Ok(session)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import { ErrorUI } from './components/ErrorBoundary';
|
|||
import { ExtensionInstallModal } from './components/ExtensionInstallModal';
|
||||
import { ToastContainer } from 'react-toastify';
|
||||
import AnnouncementModal from './components/AnnouncementModal';
|
||||
import TelemetryOptOutModal from './components/TelemetryOptOutModal';
|
||||
import ProviderGuard from './components/ProviderGuard';
|
||||
import { createSession } from './sessions';
|
||||
|
||||
|
|
@ -702,6 +703,7 @@ export default function App() {
|
|||
<AppInner />
|
||||
</HashRouter>
|
||||
<AnnouncementModal />
|
||||
<TelemetryOptOutModal controlled={false} />
|
||||
</ModelAndProviderProvider>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import { OllamaSetup } from './OllamaSetup';
|
|||
import ApiKeyTester from './ApiKeyTester';
|
||||
import { SwitchModelModal } from './settings/models/subcomponents/SwitchModelModal';
|
||||
import { createNavigationHandler } from '../utils/navigationUtils';
|
||||
import TelemetrySettings from './settings/app/TelemetrySettings';
|
||||
|
||||
import { Goose, OpenRouter, Tetrate } from './icons';
|
||||
|
||||
|
|
@ -306,6 +307,9 @@ export default function ProviderGuard({ didSelectProvider, children }: ProviderG
|
|||
Go to Provider Settings →
|
||||
</button>
|
||||
</div>
|
||||
<div className="mt-6">
|
||||
<TelemetrySettings isWelcome />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
131
ui/desktop/src/components/TelemetryOptOutModal.tsx
Normal file
131
ui/desktop/src/components/TelemetryOptOutModal.tsx
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
import { useState, useEffect } from 'react';
|
||||
import { BaseModal } from './ui/BaseModal';
|
||||
import { Button } from './ui/button';
|
||||
import { Goose } from './icons/Goose';
|
||||
import { TELEMETRY_UI_ENABLED } from '../updates';
|
||||
import { toastService } from '../toasts';
|
||||
import { useConfig } from './ConfigContext';
|
||||
|
||||
const TELEMETRY_CONFIG_KEY = 'GOOSE_TELEMETRY_ENABLED';
|
||||
|
||||
type TelemetryOptOutModalProps =
|
||||
| { controlled: false }
|
||||
| { controlled: true; isOpen: boolean; onClose: () => void };
|
||||
|
||||
export default function TelemetryOptOutModal(props: TelemetryOptOutModalProps) {
|
||||
const { read, upsert } = useConfig();
|
||||
const isControlled = props.controlled;
|
||||
const controlledIsOpen = isControlled ? props.isOpen : undefined;
|
||||
const onClose = isControlled ? props.onClose : undefined;
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
// Only check telemetry choice on first launch in uncontrolled mode
|
||||
useEffect(() => {
|
||||
if (isControlled) return;
|
||||
|
||||
const checkTelemetryChoice = async () => {
|
||||
try {
|
||||
const provider = await read('GOOSE_PROVIDER', false);
|
||||
|
||||
if (!provider || provider === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
const telemetryEnabled = await read(TELEMETRY_CONFIG_KEY, false);
|
||||
|
||||
if (telemetryEnabled === null) {
|
||||
setShowModal(true);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to check telemetry config:', error);
|
||||
toastService.error({
|
||||
title: 'Configuration Error',
|
||||
msg: 'Failed to check telemetry configuration.',
|
||||
traceback: error instanceof Error ? error.stack || '' : '',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
checkTelemetryChoice();
|
||||
}, [isControlled, read]);
|
||||
|
||||
const handleChoice = async (enabled: boolean) => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
await upsert(TELEMETRY_CONFIG_KEY, enabled, false);
|
||||
setShowModal(false);
|
||||
onClose?.();
|
||||
} catch (error) {
|
||||
console.error('Failed to set telemetry preference:', error);
|
||||
setShowModal(false);
|
||||
onClose?.();
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (!TELEMETRY_UI_ENABLED) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isModalOpen = controlledIsOpen !== undefined ? controlledIsOpen : showModal;
|
||||
|
||||
if (!isModalOpen) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<BaseModal
|
||||
isOpen={isModalOpen}
|
||||
actions={
|
||||
<div className="flex flex-col gap-2 pb-3 px-3">
|
||||
<Button
|
||||
variant="default"
|
||||
onClick={() => handleChoice(true)}
|
||||
disabled={isLoading}
|
||||
className="w-full h-[44px] rounded-lg"
|
||||
>
|
||||
Yes, share anonymous usage data
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => handleChoice(false)}
|
||||
disabled={isLoading}
|
||||
className="w-full h-[44px] rounded-lg text-text-muted hover:text-text-default"
|
||||
>
|
||||
No thanks
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className="px-2 py-3">
|
||||
<div className="flex justify-center mb-4">
|
||||
<Goose className="size-10 text-text-default" />
|
||||
</div>
|
||||
<h2 className="text-2xl font-regular dark:text-white text-gray-900 text-center mb-3">
|
||||
Help improve goose
|
||||
</h2>
|
||||
<p className="text-text-default text-sm mb-3">
|
||||
Would you like to help improve goose by sharing anonymous usage data? This helps us
|
||||
understand how goose is used and identify areas for improvement.
|
||||
</p>
|
||||
<div className="text-text-muted text-xs space-y-1">
|
||||
<p className="font-medium text-text-default">What we collect:</p>
|
||||
<ul className="list-disc list-inside space-y-0.5 ml-1">
|
||||
<li>Operating system, version, and architecture</li>
|
||||
<li>goose version and install method</li>
|
||||
<li>Provider and model used</li>
|
||||
<li>Extensions and tool usage counts (names only)</li>
|
||||
<li>Session metrics (duration, interaction count, token usage)</li>
|
||||
<li>Error types (e.g., "rate_limit", "auth" - no details)</li>
|
||||
</ul>
|
||||
<p className="mt-3 text-text-muted">
|
||||
We never collect your conversations, code, tool arguments, error messages, or any
|
||||
personal data. You can change this setting anytime in Settings → App.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</BaseModal>
|
||||
);
|
||||
}
|
||||
|
|
@ -9,8 +9,8 @@ export default function Anthropic({ className = '' }) {
|
|||
aria-hidden="true"
|
||||
className={className}
|
||||
>
|
||||
<path
|
||||
d="M 233.959793 800.214905 L 468.644287 668.536987 L 472.590637 657.100647 L 468.644287 650.738403 L 457.208069 650.738403 L 417.986633 648.322144 L 283.892639 644.69812 L 167.597321 639.865845 L 54.926208 633.825623 L 26.577238 627.785339 L 3.3e-05 592.751709 L 2.73832 575.27533 L 26.577238 559.248352 L 60.724873 562.228149 L 136.187973 567.382629 L 249.422867 575.194763 L 331.570496 580.026978 L 453.261841 592.671082 L 472.590637 592.671082 L 475.328857 584.859009 L 468.724915 580.026978 L 463.570557 575.194763 L 346.389313 495.785217 L 219.543671 411.865906 L 153.100723 363.543762 L 117.181267 339.060425 L 99.060455 316.107361 L 91.248367 266.01355 L 123.865784 230.093994 L 167.677887 233.073853 L 178.872513 236.053772 L 223.248367 270.201477 L 318.040283 343.570496 L 441.825592 434.738342 L 459.946411 449.798706 L 467.194672 444.64447 L 468.080597 441.020203 L 459.946411 427.409485 L 392.617493 305.718323 L 320.778564 181.932983 L 288.80542 130.630859 L 280.348999 99.865845 C 277.369171 87.221436 275.194641 76.590698 275.194641 63.624268 L 312.322174 13.20813 L 332.8591 6.604126 L 382.389313 13.20813 L 403.248352 31.328979 L 434.013519 101.71814 L 483.865753 212.537048 L 561.181274 363.221497 L 583.812134 407.919434 L 595.892639 449.315491 L 600.40271 461.959839 L 608.214783 461.959839 L 608.214783 454.711609 L 614.577271 369.825623 L 626.335632 265.61084 L 637.771851 131.516846 L 641.718201 93.745117 L 660.402832 48.483276 L 697.530334 24.000122 L 726.52356 37.852417 L 750.362549 72 L 747.060486 94.067139 L 732.886047 186.201416 L 705.100708 330.52356 L 686.979919 427.167847 L 697.530334 427.167847 L 709.61084 415.087341 L 758.496704 350.174561 L 840.644348 247.490051 L 876.885925 206.738342 L 919.167847 161.71814 L 946.308838 140.29541 L 997.61084 140.29541 L 1035.38269 196.429626 L 1018.469849 254.416199 L 965.637634 321.422852 L 921.825562 378.201538 L 859.006714 462.765259 L 819.785278 530.41626 L 823.409424 535.812073 L 832.75177 534.92627 L 974.657776 504.724915 L 1051.328979 490.872559 L 1142.818848 475.167786 L 1184.214844 494.496582 L 1188.724854 514.147644 L 1172.456421 554.335693 L 1074.604126 578.496765 L 959.838989 601.449829 L 788.939636 641.879272 L 786.845764 643.409485 L 789.261841 646.389343 L 866.255127 653.637634 L 899.194702 655.409424 L 979.812134 655.409424 L 1129.932861 666.604187 L 1169.154419 692.537109 L 1192.671265 724.268677 L 1188.724854 748.429688 L 1128.322144 779.194641 L 1046.818848 759.865845 L 856.590759 714.604126 L 791.355774 698.335754 L 782.335693 698.335754 L 782.335693 703.731567 L 836.69812 756.885986 L 936.322205 846.845581 L 1061.073975 962.81897 L 1067.436279 991.490112 L 1051.409424 1014.120911 L 1034.496704 1011.704712 L 924.885986 929.234924 L 882.604126 892.107544 L 786.845764 811.48999 L 780.483276 811.48999 L 780.483276 819.946289 L 802.550415 852.241699 L 919.087341 1027.409424 L 925.127625 1081.127686 L 916.671204 1098.604126 L 886.469849 1109.154419 L 853.288696 1103.114136 L 785.073914 1007.355835 L 714.684631 899.516785 L 657.906067 802.872498 L 650.979858 806.81897 L 617.476624 1167.704834 L 601.771851 1186.147705 L 565.530212 1200 L 535.328857 1177.046997 L 519.302124 1139.919556 L 535.328857 1066.550537 L 554.657776 970.792053 L 570.362488 894.68457 L 584.536926 800.134277 L 592.993347 768.724976 L 592.429626 766.630859 L 585.503479 767.516968 L 514.22821 865.369263 L 405.825531 1011.865906 L 320.053711 1103.677979 L 299.516815 1111.812256 L 263.919525 1093.369263 L 267.221497 1060.429688 L 287.114136 1031.114136 L 405.825531 880.107361 L 477.422913 786.52356 L 523.651062 732.483276 L 523.328918 724.671265 L 520.590698 724.671265 L 205.288605 929.395935 L 149.154434 936.644409 L 124.993355 914.01355 L 127.973183 876.885986 L 139.409409 864.80542 L 234.201385 799.570435 L 233.879227 799.8927 Z"
|
||||
<path
|
||||
d="M 233.959793 800.214905 L 468.644287 668.536987 L 472.590637 657.100647 L 468.644287 650.738403 L 457.208069 650.738403 L 417.986633 648.322144 L 283.892639 644.69812 L 167.597321 639.865845 L 54.926208 633.825623 L 26.577238 627.785339 L 3.3e-05 592.751709 L 2.73832 575.27533 L 26.577238 559.248352 L 60.724873 562.228149 L 136.187973 567.382629 L 249.422867 575.194763 L 331.570496 580.026978 L 453.261841 592.671082 L 472.590637 592.671082 L 475.328857 584.859009 L 468.724915 580.026978 L 463.570557 575.194763 L 346.389313 495.785217 L 219.543671 411.865906 L 153.100723 363.543762 L 117.181267 339.060425 L 99.060455 316.107361 L 91.248367 266.01355 L 123.865784 230.093994 L 167.677887 233.073853 L 178.872513 236.053772 L 223.248367 270.201477 L 318.040283 343.570496 L 441.825592 434.738342 L 459.946411 449.798706 L 467.194672 444.64447 L 468.080597 441.020203 L 459.946411 427.409485 L 392.617493 305.718323 L 320.778564 181.932983 L 288.80542 130.630859 L 280.348999 99.865845 C 277.369171 87.221436 275.194641 76.590698 275.194641 63.624268 L 312.322174 13.20813 L 332.8591 6.604126 L 382.389313 13.20813 L 403.248352 31.328979 L 434.013519 101.71814 L 483.865753 212.537048 L 561.181274 363.221497 L 583.812134 407.919434 L 595.892639 449.315491 L 600.40271 461.959839 L 608.214783 461.959839 L 608.214783 454.711609 L 614.577271 369.825623 L 626.335632 265.61084 L 637.771851 131.516846 L 641.718201 93.745117 L 660.402832 48.483276 L 697.530334 24.000122 L 726.52356 37.852417 L 750.362549 72 L 747.060486 94.067139 L 732.886047 186.201416 L 705.100708 330.52356 L 686.979919 427.167847 L 697.530334 427.167847 L 709.61084 415.087341 L 758.496704 350.174561 L 840.644348 247.490051 L 876.885925 206.738342 L 919.167847 161.71814 L 946.308838 140.29541 L 997.61084 140.29541 L 1035.38269 196.429626 L 1018.469849 254.416199 L 965.637634 321.422852 L 921.825562 378.201538 L 859.006714 462.765259 L 819.785278 530.41626 L 823.409424 535.812073 L 832.75177 534.92627 L 974.657776 504.724915 L 1051.328979 490.872559 L 1142.818848 475.167786 L 1184.214844 494.496582 L 1188.724854 514.147644 L 1172.456421 554.335693 L 1074.604126 578.496765 L 959.838989 601.449829 L 788.939636 641.879272 L 786.845764 643.409485 L 789.261841 646.389343 L 866.255127 653.637634 L 899.194702 655.409424 L 979.812134 655.409424 L 1129.932861 666.604187 L 1169.154419 692.537109 L 1192.671265 724.268677 L 1188.724854 748.429688 L 1128.322144 779.194641 L 1046.818848 759.865845 L 856.590759 714.604126 L 791.355774 698.335754 L 782.335693 698.335754 L 782.335693 703.731567 L 836.69812 756.885986 L 936.322205 846.845581 L 1061.073975 962.81897 L 1067.436279 991.490112 L 1051.409424 1014.120911 L 1034.496704 1011.704712 L 924.885986 929.234924 L 882.604126 892.107544 L 786.845764 811.48999 L 780.483276 811.48999 L 780.483276 819.946289 L 802.550415 852.241699 L 919.087341 1027.409424 L 925.127625 1081.127686 L 916.671204 1098.604126 L 886.469849 1109.154419 L 853.288696 1103.114136 L 785.073914 1007.355835 L 714.684631 899.516785 L 657.906067 802.872498 L 650.979858 806.81897 L 617.476624 1167.704834 L 601.771851 1186.147705 L 565.530212 1200 L 535.328857 1177.046997 L 519.302124 1139.919556 L 535.328857 1066.550537 L 554.657776 970.792053 L 570.362488 894.68457 L 584.536926 800.134277 L 592.993347 768.724976 L 592.429626 766.630859 L 585.503479 767.516968 L 514.22821 865.369263 L 405.825531 1011.865906 L 320.053711 1103.677979 L 299.516815 1111.812256 L 263.919525 1093.369263 L 267.221497 1060.429688 L 287.114136 1031.114136 L 405.825531 880.107361 L 477.422913 786.52356 L 523.651062 732.483276 L 523.328918 724.671265 L 520.590698 724.671265 L 205.288605 929.395935 L 149.154434 936.644409 L 124.993355 914.01355 L 127.973183 876.885986 L 139.409409 864.80542 L 234.201385 799.570435 L 233.879227 799.8927 Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
|
|
|
|||
|
|
@ -22,7 +22,12 @@ export function Key({ className = '' }: KeyProps) {
|
|||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_986_19974">
|
||||
<rect width="16" height="16" fill="white" transform="translate(0.00708008 0.677979) rotate(0.0256089)" />
|
||||
<rect
|
||||
width="16"
|
||||
height="16"
|
||||
fill="white"
|
||||
transform="translate(0.00708008 0.677979) rotate(0.0256089)"
|
||||
/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
|
|
|||
|
|
@ -9,12 +9,12 @@ export default function Tetrate({ className = '' }) {
|
|||
aria-hidden="true"
|
||||
className={className}
|
||||
>
|
||||
<path
|
||||
d="M343.5 226C390.72 226 429 264.28 429 311.5C429 321.676 427.222 331.436 423.961 340.487C409.127 381.654 375 419.742 375 463.499C375 507.257 409.127 545.346 423.961 586.512C427.222 595.564 429 605.324 429 615.5C429 662.72 390.72 701 343.5 701C296.28 701 258 662.72 258 615.5C258 606.44 259.409 597.709 262.021 589.514C275.211 548.119 308 509.828 308 466.382V460.616C308 417.171 275.211 378.88 262.021 337.485C259.409 329.291 258 320.56 258 311.5C258 264.28 296.28 226 343.5 226Z"
|
||||
<path
|
||||
d="M343.5 226C390.72 226 429 264.28 429 311.5C429 321.676 427.222 331.436 423.961 340.487C409.127 381.654 375 419.742 375 463.499C375 507.257 409.127 545.346 423.961 586.512C427.222 595.564 429 605.324 429 615.5C429 662.72 390.72 701 343.5 701C296.28 701 258 662.72 258 615.5C258 606.44 259.409 597.709 262.021 589.514C275.211 548.119 308 509.828 308 466.382V460.616C308 417.171 275.211 378.88 262.021 337.485C259.409 329.291 258 320.56 258 311.5C258 264.28 296.28 226 343.5 226Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M272.622 39.9541C297.034 1.458 347.7 -11.255 387.546 11.75C395.392 16.2801 402.249 21.8668 408.04 28.2256C437.293 60.3466 454.06 107.888 491.685 129.61L496.678 132.493C534.303 154.216 583.859 144.966 626.303 154.239C634.705 156.075 642.971 159.22 650.817 163.75C691.711 187.36 705.722 239.652 682.112 280.546C658.502 321.44 606.211 335.451 565.317 311.841C556.505 306.753 548.941 300.333 542.733 292.982C514.499 259.553 498.577 210.955 460.682 189.076C436.139 174.906 406.497 174.321 376.956 173.566C358.811 173.102 340.613 173.735 322.491 174.754C290.52 176.551 257.865 176.779 231.635 192.853C194.326 215.717 179.682 264.716 152.333 298.874C146.319 306.385 138.926 313 130.25 318.317C89.9879 342.99 37.3483 330.353 12.6754 290.091C-11.9974 249.829 0.639271 197.189 40.901 172.517C48.6259 167.783 56.8065 164.422 65.1578 162.367C107.345 151.985 157.126 159.934 194.17 137.233L199.086 134.221C232.774 113.576 248.658 72.037 272.622 39.9541Z"
|
||||
<path
|
||||
d="M272.622 39.9541C297.034 1.458 347.7 -11.255 387.546 11.75C395.392 16.2801 402.249 21.8668 408.04 28.2256C437.293 60.3466 454.06 107.888 491.685 129.61L496.678 132.493C534.303 154.216 583.859 144.966 626.303 154.239C634.705 156.075 642.971 159.22 650.817 163.75C691.711 187.36 705.722 239.652 682.112 280.546C658.502 321.44 606.211 335.451 565.317 311.841C556.505 306.753 548.941 300.333 542.733 292.982C514.499 259.553 498.577 210.955 460.682 189.076C436.139 174.906 406.497 174.321 376.956 173.566C358.811 173.102 340.613 173.735 322.491 174.754C290.52 176.551 257.865 176.779 231.635 192.853C194.326 215.717 179.682 264.716 152.333 298.874C146.319 306.385 138.926 313 130.25 318.317C89.9879 342.99 37.3483 330.353 12.6754 290.091C-11.9974 249.829 0.639271 197.189 40.901 172.517C48.6259 167.783 56.8065 164.422 65.1578 162.367C107.345 151.985 157.126 159.934 194.17 137.233L199.086 134.221C232.774 113.576 248.658 72.037 272.622 39.9541Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../..
|
|||
import ThemeSelector from '../../GooseSidebar/ThemeSelector';
|
||||
import BlockLogoBlack from './icons/block-lockup_black.png';
|
||||
import BlockLogoWhite from './icons/block-lockup_white.png';
|
||||
import TelemetrySettings from './TelemetrySettings';
|
||||
|
||||
interface AppSettingsSectionProps {
|
||||
scrollToSection?: string;
|
||||
|
|
@ -288,7 +289,7 @@ export default function AppSettingsSection({ scrollToSection }: AppSettingsSecti
|
|||
<div>
|
||||
<h3 className="text-text-default text-xs">Prevent Sleep</h3>
|
||||
<p className="text-xs text-text-muted max-w-md mt-[2px]">
|
||||
Keep your computer awake while Goose is running a task (screen can still lock)
|
||||
Keep your computer awake while goose is running a task (screen can still lock)
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
|
|
@ -397,6 +398,8 @@ export default function AppSettingsSection({ scrollToSection }: AppSettingsSecti
|
|||
|
||||
<TunnelSection />
|
||||
|
||||
<TelemetrySettings isWelcome={false} />
|
||||
|
||||
<Card className="rounded-lg">
|
||||
<CardHeader className="pb-0">
|
||||
<CardTitle className="mb-1">Help & feedback</CardTitle>
|
||||
|
|
|
|||
128
ui/desktop/src/components/settings/app/TelemetrySettings.tsx
Normal file
128
ui/desktop/src/components/settings/app/TelemetrySettings.tsx
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { Switch } from '../../ui/switch';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../../ui/card';
|
||||
import { useConfig } from '../../ConfigContext';
|
||||
import { TELEMETRY_UI_ENABLED } from '../../../updates';
|
||||
import TelemetryOptOutModal from '../../TelemetryOptOutModal';
|
||||
import { toastService } from '../../../toasts';
|
||||
|
||||
const TELEMETRY_CONFIG_KEY = 'GOOSE_TELEMETRY_ENABLED';
|
||||
|
||||
interface TelemetrySettingsProps {
|
||||
isWelcome: boolean;
|
||||
}
|
||||
|
||||
export default function TelemetrySettings({ isWelcome = false }: TelemetrySettingsProps) {
|
||||
const { read, upsert } = useConfig();
|
||||
const [telemetryEnabled, setTelemetryEnabled] = useState(true);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
|
||||
const loadTelemetryStatus = useCallback(async () => {
|
||||
try {
|
||||
const value = await read(TELEMETRY_CONFIG_KEY, false);
|
||||
setTelemetryEnabled(value === null ? true : Boolean(value));
|
||||
} catch (error) {
|
||||
console.error('Failed to load telemetry status:', error);
|
||||
toastService.error({
|
||||
title: 'Configuration Error',
|
||||
msg: 'Failed to load telemetry settings.',
|
||||
traceback: error instanceof Error ? error.stack || '' : '',
|
||||
});
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [read]);
|
||||
|
||||
useEffect(() => {
|
||||
loadTelemetryStatus();
|
||||
}, [loadTelemetryStatus]);
|
||||
|
||||
const handleTelemetryToggle = async (checked: boolean) => {
|
||||
try {
|
||||
await upsert(TELEMETRY_CONFIG_KEY, checked, false);
|
||||
setTelemetryEnabled(checked);
|
||||
} catch (error) {
|
||||
console.error('Failed to update telemetry status:', error);
|
||||
toastService.error({
|
||||
title: 'Configuration Error',
|
||||
msg: 'Failed to update telemetry settings.',
|
||||
traceback: error instanceof Error ? error.stack || '' : '',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleModalClose = () => {
|
||||
setShowModal(false);
|
||||
loadTelemetryStatus();
|
||||
};
|
||||
|
||||
if (!TELEMETRY_UI_ENABLED) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const title = 'Privacy';
|
||||
const description = 'Control how your data is used';
|
||||
const toggleLabel = 'Anonymous usage data';
|
||||
const toggleDescription = 'Help improve goose by sharing anonymous usage statistics.';
|
||||
|
||||
const learnMoreLink = (
|
||||
<button
|
||||
onClick={() => setShowModal(true)}
|
||||
className="text-blue-600 dark:text-blue-400 hover:underline"
|
||||
>
|
||||
Learn more
|
||||
</button>
|
||||
);
|
||||
|
||||
const toggle = (
|
||||
<Switch
|
||||
checked={telemetryEnabled}
|
||||
onCheckedChange={handleTelemetryToggle}
|
||||
disabled={isLoading}
|
||||
variant="mono"
|
||||
/>
|
||||
);
|
||||
|
||||
const modal = <TelemetryOptOutModal controlled isOpen={showModal} onClose={handleModalClose} />;
|
||||
|
||||
const toggleRow = (
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h4 className={isWelcome ? 'text-text-default text-sm' : 'text-text-default text-xs'}>
|
||||
{toggleLabel}
|
||||
</h4>
|
||||
<p className={`${isWelcome ? 'text-sm' : 'text-xs'} text-text-muted max-w-md mt-[2px]`}>
|
||||
{toggleDescription} {learnMoreLink}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center">{toggle}</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
if (isWelcome) {
|
||||
return (
|
||||
<>
|
||||
<div className="w-full p-4 sm:p-6 bg-transparent border border-background-hover rounded-xl">
|
||||
<h3 className="font-medium text-text-standard text-sm sm:text-base mb-1">{title}</h3>
|
||||
<p className="text-text-muted text-sm sm:text-base mb-4">{description}</p>
|
||||
{toggleRow}
|
||||
</div>
|
||||
{modal}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Card className="rounded-lg">
|
||||
<CardHeader className="pb-0">
|
||||
<CardTitle className="mb-1">{title}</CardTitle>
|
||||
<CardDescription>{description}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-4 space-y-4 px-4">{toggleRow}</CardContent>
|
||||
</Card>
|
||||
{modal}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -92,6 +92,7 @@ export const SwitchModelModal = ({
|
|||
const [selectedPredefinedModel, setSelectedPredefinedModel] = useState<Model | null>(null);
|
||||
const [predefinedModels, setPredefinedModels] = useState<Model[]>([]);
|
||||
const [loadingModels, setLoadingModels] = useState<boolean>(false);
|
||||
const [userClearedModel, setUserClearedModel] = useState(false);
|
||||
|
||||
// Validate form data
|
||||
const validateForm = useCallback(() => {
|
||||
|
|
@ -265,7 +266,8 @@ export const SwitchModelModal = ({
|
|||
: [];
|
||||
|
||||
useEffect(() => {
|
||||
if (!provider || loadingModels || model || isCustomModel) return;
|
||||
// Don't auto-select if user explicitly cleared the model
|
||||
if (!provider || loadingModels || model || isCustomModel || userClearedModel) return;
|
||||
|
||||
const providerModels = modelOptions
|
||||
.filter((group) => group.options[0]?.provider === provider)
|
||||
|
|
@ -277,7 +279,7 @@ export const SwitchModelModal = ({
|
|||
setModel(preferredModel);
|
||||
}
|
||||
}
|
||||
}, [provider, modelOptions, loadingModels, model, isCustomModel]);
|
||||
}, [provider, modelOptions, loadingModels, model, isCustomModel, userClearedModel]);
|
||||
|
||||
// Handle model selection change
|
||||
const handleModelChange = (newValue: unknown) => {
|
||||
|
|
@ -285,9 +287,16 @@ export const SwitchModelModal = ({
|
|||
if (selectedOption?.value === 'custom') {
|
||||
setIsCustomModel(true);
|
||||
setModel('');
|
||||
setUserClearedModel(false);
|
||||
} else if (selectedOption === null) {
|
||||
// User cleared the selection
|
||||
setIsCustomModel(false);
|
||||
setModel('');
|
||||
setUserClearedModel(true);
|
||||
} else {
|
||||
setIsCustomModel(false);
|
||||
setModel(selectedOption?.value || '');
|
||||
setUserClearedModel(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -428,6 +437,7 @@ export const SwitchModelModal = ({
|
|||
setProvider(option?.value || null);
|
||||
setModel('');
|
||||
setIsCustomModel(false);
|
||||
setUserClearedModel(false);
|
||||
}
|
||||
}}
|
||||
placeholder="Provider, type to search"
|
||||
|
|
@ -445,26 +455,19 @@ export const SwitchModelModal = ({
|
|||
<Select
|
||||
options={
|
||||
loadingModels
|
||||
? [
|
||||
{
|
||||
options: [
|
||||
{
|
||||
value: '__loading__',
|
||||
label: 'Loading models…',
|
||||
provider: provider || '',
|
||||
isDisabled: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
? []
|
||||
: filteredModelOptions.length > 0
|
||||
? filteredModelOptions
|
||||
: []
|
||||
}
|
||||
onChange={handleModelChange}
|
||||
onInputChange={handleInputChange} // Added for input handling
|
||||
onInputChange={handleInputChange}
|
||||
value={model ? { value: model, label: model } : null}
|
||||
placeholder="Select a model, type to search"
|
||||
placeholder={
|
||||
loadingModels ? 'Loading models…' : 'Select a model, type to search'
|
||||
}
|
||||
isClearable
|
||||
isDisabled={loadingModels}
|
||||
/>
|
||||
|
||||
{attemptedSubmit && validationErrors.model && (
|
||||
|
|
|
|||
|
|
@ -3,3 +3,4 @@ export const COST_TRACKING_ENABLED = true;
|
|||
export const ANNOUNCEMENTS_ENABLED = false;
|
||||
export const CONFIGURATION_ENABLED = true;
|
||||
export const VOICE_DICTATION_ELEVENLABS_ENABLED = true;
|
||||
export const TELEMETRY_UI_ENABLED = true;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue