start adding toggle metrics

This commit is contained in:
Mathew Pareles 2025-06-21 03:40:37 -07:00
parent ad8fedc307
commit c6d210902b
6 changed files with 93 additions and 9 deletions

View file

@ -5,7 +5,7 @@
import React, { useState, useEffect, useCallback } from 'react'
import { MCPUserState, RefreshableProviderName, SettingsOfProvider } from '../../../../../../../workbench/contrib/void/common/voidSettingsTypes.js'
import { IDisposable } from '../../../../../../../base/common/lifecycle.js'
import { DisposableStore, IDisposable } from '../../../../../../../base/common/lifecycle.js'
import { VoidSettingsState } from '../../../../../../../workbench/contrib/void/common/voidSettingsService.js'
import { ColorScheme } from '../../../../../../../platform/theme/common/theme.js'
import { RefreshModelStateOfProvider } from '../../../../../../../workbench/contrib/void/common/refreshModelService.js'
@ -52,6 +52,8 @@ import { ITerminalService } from '../../../../../terminal/browser/terminal.js'
import { ISearchService } from '../../../../../../services/search/common/search.js'
import { IExtensionManagementService } from '../../../../../../../platform/extensionManagement/common/extensionManagement.js'
import { IMCPService } from '../../../../common/mcpService.js';
import { IStorageService, StorageScope } from '../../../../../../../platform/storage/common/storage.js'
import { OPT_OUT_KEY } from '../../../../common/storageKeys.js'
// normally to do this you'd use a useEffect that calls .onDidChangeState(), but useEffect mounts too late and misses initial state changes
@ -226,6 +228,8 @@ const getReactAccessor = (accessor: ServicesAccessor) => {
IExtensionTransferService: accessor.get(IExtensionTransferService),
IMCPService: accessor.get(IMCPService),
IStorageService: accessor.get(IStorageService),
} as const
return reactAccessor
}
@ -399,3 +403,28 @@ export const useMCPServiceState = () => {
return s
}
export const useIsOptedOut = () => {
const accessor = useAccessor()
const storageService = accessor.get('IStorageService')
const getVal = useCallback(() => {
return storageService.getBoolean(OPT_OUT_KEY, StorageScope.APPLICATION, false)
}, [storageService])
const [s, ss] = useState(getVal())
useEffect(() => {
const disposables = new DisposableStore();
const d = storageService.onDidChangeValue(StorageScope.APPLICATION, OPT_OUT_KEY, disposables)(e => {
console.log('VALUE!!!!!!!!', e.target, getVal())
ss(getVal())
})
disposables.add(d)
return () => disposables.clear()
}, [storageService, getVal])
return s
}

View file

@ -7,7 +7,7 @@ import React, { useCallback, useEffect, useMemo, useState, useRef } from 'react'
import { ProviderName, SettingName, displayInfoOfSettingName, providerNames, VoidStatefulModelInfo, customSettingNamesOfProvider, RefreshableProviderName, refreshableProviderNames, displayInfoOfProviderName, nonlocalProviderNames, localProviderNames, GlobalSettingName, featureNames, displayInfoOfFeatureName, isProviderNameDisabled, FeatureName, hasDownloadButtonsOnModelsProviderNames, subTextMdOfProviderName } from '../../../../common/voidSettingsTypes.js'
import ErrorBoundary from '../sidebar-tsx/ErrorBoundary.js'
import { VoidButtonBgDarken, VoidCustomDropdownBox, VoidInputBox2, VoidSimpleInputBox, VoidSwitch } from '../util/inputs.js'
import { useAccessor, useIsDark, useRefreshModelListener, useRefreshModelState, useSettingsState } from '../util/services.js'
import { useAccessor, useIsDark, useIsOptedOut, useRefreshModelListener, useRefreshModelState, useSettingsState } from '../util/services.js'
import { X, RefreshCw, Loader2, Check, Asterisk, Plus } from 'lucide-react'
import { URI } from '../../../../../../../base/common/uri.js'
import { ModelDropdown } from './ModelDropdown.js'
@ -21,6 +21,8 @@ import { getModelCapabilities, modelOverrideKeys, ModelOverrides } from '../../.
import { TransferEditorType, TransferFilesInfo } from '../../../extensionTransferTypes.js';
import { MCPServer } from '../../../../common/mcpServiceTypes.js';
import { useMCPServiceState } from '../util/services.js';
import { OPT_OUT_KEY } from '../../../../common/storageKeys.js';
import { StorageScope, StorageTarget } from '../../../../../../../platform/storage/common/storage.js';
type Tab =
| 'models'
@ -215,10 +217,10 @@ const SimpleModelSettingsDialog = ({
if (!isOpen || !modelInfo) return null;
const { modelName, providerName, type } = modelInfo;
const accessor = useAccessor();
const settingsState = useSettingsState();
const accessor = useAccessor()
const settingsState = useSettingsState()
const mouseDownInsideModal = useRef(false); // Ref to track mousedown origin
const settingsStateService = accessor.get('IVoidSettingsService');
const settingsStateService = accessor.get('IVoidSettingsService')
// current overrides and defaults
const defaultModelCapabilities = getModelCapabilities(providerName, modelName, undefined);
@ -1052,6 +1054,8 @@ export const Settings = () => {
const chatThreadsService = accessor.get('IChatThreadService')
const notificationService = accessor.get('INotificationService')
const mcpService = accessor.get('IMCPService')
const storageService = accessor.get('IStorageService')
const isOptedOut = useIsOptedOut()
const onDownload = (t: 'Chats' | 'Settings') => {
let dataStr: string
@ -1387,6 +1391,29 @@ export const Settings = () => {
{/* General section */}
<div className={`${shouldShowTab('general') ? `` : 'hidden'} flex flex-col gap-12`}>
{/* Telemetry section */}
<div className='w-full mt-8'>
<h4 className={`text-base`}>Telemetry</h4>
<div className='text-sm italic text-void-fg-3 mt-1'>Control what usage data Void collects.</div>
<div className='my-2'>
{/* Disable All Telemetry Switch */}
<ErrorBoundary>
<div className='flex items-center gap-x-2 my-2'>
<VoidSwitch
size='xs'
value={isOptedOut}
onChange={(newVal) => storageService.store(OPT_OUT_KEY, newVal, StorageScope.APPLICATION, StorageTarget.MACHINE)}
/>
<span className='text-void-fg-3 text-xs pointer-events-none'>{storageService.getBoolean(OPT_OUT_KEY, StorageScope.APPLICATION, false) ? 'Disabled' : 'Enabled'}</span>
</div>
<div className='text-xs text-void-fg-3 mt-1 max-w-[500px]'>
Void only tracks basic usage like whether you've opened the app today so we can understand usage. Opting out is optional (requires a restart), but without this we cannot see how many people are using Void. Regardless of this setting, Void never sees your code, messages, or API keys.
</div>
</ErrorBoundary>
</div>
</div>
{/* One-Click Switch section */}
<div>
<ErrorBoundary>

View file

@ -14,6 +14,7 @@ import { INotificationService } from '../../../../platform/notification/common/n
export interface IMetricsService {
readonly _serviceBrand: undefined;
capture(event: string, params: Record<string, any>): void;
setOptOut(val: boolean): void;
getDebuggingProperties(): Promise<object>;
}
@ -38,6 +39,11 @@ export class MetricsService implements IMetricsService {
this.metricsService.capture(...params);
}
setOptOut(...params: Parameters<IMetricsService['setOptOut']>) {
this.metricsService.setOptOut(...params);
}
// anything transmitted over a channel must be async even if it looks like it doesn't have to be
async getDebuggingProperties(): Promise<object> {
return this.metricsService.getDebuggingProperties()

View file

@ -17,3 +17,7 @@ export const VOID_SETTINGS_STORAGE_KEY = 'void.settingsServiceStorageII'
// 1.0.3
export const THREAD_STORAGE_KEY = 'void.chatThreadStorageII'
export const OPT_OUT_KEY = 'void.app.optOutAll'

View file

@ -231,12 +231,9 @@ export const displayInfoOfSettingName = (providerName: ProviderName, settingName
}
throw new Error(`displayInfo: Unknown setting name: "${settingName}"`)
}
const defaultCustomSettings: Record<CustomSettingName, undefined> = {
apiKey: undefined,
endpoint: undefined,

View file

@ -13,6 +13,7 @@ import { IApplicationStorageMainService } from '../../../../platform/storage/ele
import { IMetricsService } from '../common/metricsService.js';
import { PostHog } from 'posthog-node'
import { OPT_OUT_KEY } from '../common/storageKeys.js';
const os = isWindows ? 'windows' : isMacintosh ? 'mac' : isLinux ? 'linux' : null
@ -29,6 +30,8 @@ const osInfo = _getOSInfo()
// we'd like to use devDeviceId on telemetryService, but that gets sanitized by the time it gets here as 'someValue.devDeviceId'
export class MetricsMainService extends Disposable implements IMetricsService {
_serviceBrand: undefined;
@ -119,7 +122,17 @@ export class MetricsMainService extends Disposable implements IMetricsService {
distinctId: this.distinctId,
properties: this._initProperties,
}
this.client.identify(identifyMessage)
const didOptOut = this._appStorage.get(OPT_OUT_KEY, StorageScope.APPLICATION) !== undefined
if (didOptOut) {
this.client.optOut()
}
else {
this.client.identify(identifyMessage)
this.client.optIn()
}
console.log('Void posthog metrics info:', JSON.stringify(identifyMessage, null, 2))
}
@ -131,6 +144,14 @@ export class MetricsMainService extends Disposable implements IMetricsService {
this.client.capture(capture)
}
setOptOut: IMetricsService['setOptOut'] = (newVal: boolean) => {
if (newVal) {
this._appStorage.store(OPT_OUT_KEY, 'true', StorageScope.APPLICATION, StorageTarget.MACHINE)
}
else {
this._appStorage.remove(OPT_OUT_KEY, StorageScope.APPLICATION)
}
}
async getDebuggingProperties() {
return this._initProperties