fix: metrics on posthog (#6024)

Co-authored-by: Zane Staggs <zane@squareup.com>
Co-authored-by: Zane <75694352+zanesq@users.noreply.github.com>
This commit is contained in:
Michael Neale
2025-12-10 11:28:18 +11:00
committed by GitHub
parent 02189f5555
commit 9b6a1c1297
20 changed files with 689 additions and 34 deletions
@@ -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>
@@ -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 && (