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:
@@ -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 && (
|
||||
|
||||
Reference in New Issue
Block a user