update metrics

This commit is contained in:
Mathew Pareles 2025-01-11 00:51:53 -08:00
parent e6f6c7500d
commit e1b8e49b13
10 changed files with 112 additions and 38 deletions

View file

@ -85,7 +85,7 @@ export class RefreshModelService extends Disposable implements IRefreshModelServ
for (const providerName of refreshableProviderNames) {
const { _enabled: enabled } = this.voidSettingsService.state.settingsOfProvider[providerName]
this.refreshModels(providerName, !enabled, { isPolling: true, isInternal: true })
this.refreshModels(providerName, !enabled, { isPolling: true, isInvisible: true })
// every time providerName.enabled changes, refresh models too, like a useEffect
let relevantVals = () => refreshBasedOn[providerName].map(settingName => this.voidSettingsService.state.settingsOfProvider[providerName][settingName])
@ -101,7 +101,7 @@ export class RefreshModelService extends Disposable implements IRefreshModelServ
// if it was just enabled, or there was a change and it wasn't to the enabled state, refresh
if ((enabled && !prevEnabled) || (!enabled && !prevEnabled)) {
// if user just clicked enable, refresh
this.refreshModels(providerName, !enabled, { isPolling: false, isInternal: true })
this.refreshModels(providerName, !enabled, { isPolling: false, isInvisible: true })
}
else {
// else if user just clicked disable, don't refresh
@ -134,34 +134,46 @@ export class RefreshModelService extends Disposable implements IRefreshModelServ
// start listening for models (and don't stop until success)
async refreshModels(providerName: RefreshableProviderName, enableProviderOnSuccess?: boolean, options?: { isPolling?: boolean, isInternal?: boolean }) {
async refreshModels(providerName: RefreshableProviderName, enableProviderOnSuccess?: boolean, options?: { isPolling?: boolean, isInvisible?: boolean }) {
const { isPolling, isInternal } = options ?? {}
const { isPolling, isInvisible } = options ?? {}
console.log(`refreshModels, isInternal ${isInternal} isPolling ${isPolling}`)
console.log(`refreshModels, isInvisible ${isInvisible} isPolling ${isPolling}`)
this._clearProviderTimeout(providerName)
// start loading models
if (!isInternal) this._setRefreshState(providerName, 'refreshing')
if (!isInvisible) this._setRefreshState(providerName, 'refreshing')
const fn = providerName === 'ollama' ? this.llmMessageService.ollamaList
const listFn = providerName === 'ollama' ? this.llmMessageService.ollamaList
: providerName === 'openAICompatible' ? this.llmMessageService.openAICompatibleList
: () => { }
fn({
listFn({
onSuccess: ({ models }) => {
this.voidSettingsService.setDefaultModels(providerName, models.map(model => {
if (providerName === 'ollama') return (model as OllamaModelResponse).name
else if (providerName === 'openAICompatible') return (model as OpenaiCompatibleModelResponse).id
else throw new Error('refreshMode fn: unknown provider', providerName)
}))
// set the models to the detected models
this.voidSettingsService.setAutodetectedModels(
providerName,
models.map(model => {
if (providerName === 'ollama') return (model as OllamaModelResponse).name;
else if (providerName === 'openAICompatible') return (model as OpenaiCompatibleModelResponse).id;
else throw new Error('refreshMode fn: unknown provider', providerName);
}),
{ enableProviderOnSuccess, isPolling, isInvisible }
)
// update state
if (enableProviderOnSuccess) {
this.voidSettingsService.setSettingOfProvider(providerName, '_enabled', true)
}
if (!isInternal) this._setRefreshState(providerName, 'finished')
if (!isInvisible) {
this._setRefreshState(providerName, 'finished')
} else if (isInvisible) {
this._setRefreshState(providerName, 'finished_invisible')
}
},
onError: ({ error }) => {
@ -169,7 +181,6 @@ export class RefreshModelService extends Disposable implements IRefreshModelServ
}
})
if (isInternal) this._setRefreshState(providerName, 'finished_invisible')
// check if we should poll
// if it was originally called as `isPolling` and if the `autoRefreshModels` flag is enabled

View file

@ -10,6 +10,7 @@ import { IEncryptionService } from '../../encryption/common/encryptionService.js
import { registerSingleton, InstantiationType } from '../../instantiation/common/extensions.js';
import { createDecorator } from '../../instantiation/common/instantiation.js';
import { IStorageService, StorageScope, StorageTarget } from '../../storage/common/storage.js';
import { IMetricsService } from './metricsService.js';
import { defaultSettingsOfProvider, FeatureName, ProviderName, ModelSelectionOfFeature, SettingsOfProvider, SettingName, providerNames, ModelSelection, modelSelectionsEqual, featureNames, modelInfoOfDefaultNames, VoidModelInfo, FeatureFlagSettings, FeatureFlagName, defaultFeatureFlagSettings } from './voidSettingsTypes.js';
@ -55,7 +56,7 @@ export interface IVoidSettingsService {
setModelSelectionOfFeature: SetModelSelectionOfFeatureFn;
setFeatureFlag: SetFeatureFlagFn;
setDefaultModels(providerName: ProviderName, modelNames: string[]): void;
setAutodetectedModels(providerName: ProviderName, modelNames: string[], logging: { enableProviderOnSuccess?: boolean, isPolling?: boolean, isInvisible?: boolean }): void;
toggleModelHidden(providerName: ProviderName, modelName: string): void;
addModel(providerName: ProviderName, modelName: string): void;
deleteModel(providerName: ProviderName, modelName: string): boolean;
@ -100,6 +101,7 @@ class VoidSettingsService extends Disposable implements IVoidSettingsService {
constructor(
@IStorageService private readonly _storageService: IStorageService,
@IEncryptionService private readonly _encryptionService: IEncryptionService,
@IMetricsService private readonly _metricsService: IMetricsService,
// could have used this, but it's clearer the way it is (+ slightly different eg StorageTarget.USER)
// @ISecretStorageService private readonly _secretStorageService: ISecretStorageService,
) {
@ -220,25 +222,45 @@ class VoidSettingsService extends Disposable implements IVoidSettingsService {
setDefaultModels(providerName: ProviderName, newDefaultModelNames: string[]) {
setAutodetectedModels(providerName: ProviderName, newDefaultModelNames: string[], logging: { enableProviderOnSuccess?: boolean, isPolling?: boolean, isInvisible?: boolean }) {
const { models } = this.state.settingsOfProvider[providerName]
const old_names = models.map(m => m.modelName)
const newDefaultModels = modelInfoOfDefaultNames(newDefaultModelNames, { isAutodetected: true, existingModels: models })
const newModels = [
...newDefaultModels,
...models.filter(m => !m.isDefault), // keep any non-default models
]
this.setSettingOfProvider(providerName, 'models', newModels)
// if the models changed, log it
const new_names = newModels.map(m => m.modelName)
if (!(old_names.length === new_names.length
&& old_names.every((_, i) => old_names[i] === new_names[i])
)) {
this._metricsService.capture('Autodetect Models', { providerName, newModels, ...logging })
}
}
toggleModelHidden(providerName: ProviderName, modelName: string) {
const { models } = this.state.settingsOfProvider[providerName]
const modelIdx = models.findIndex(m => m.modelName === modelName)
if (modelIdx === -1) return
const newIsHidden = !models[modelIdx].isHidden
const newModels: VoidModelInfo[] = [
...models.slice(0, modelIdx),
{ ...models[modelIdx], isHidden: !models[modelIdx].isHidden },
{ ...models[modelIdx], isHidden: newIsHidden },
...models.slice(modelIdx + 1, Infinity)
]
this.setSettingOfProvider(providerName, 'models', newModels)
this._metricsService.capture('Toggle Model Hidden', { providerName, modelName, newIsHidden })
}
addModel(providerName: ProviderName, modelName: string) {
const { models } = this.state.settingsOfProvider[providerName]
@ -249,6 +271,9 @@ class VoidSettingsService extends Disposable implements IVoidSettingsService {
{ modelName, isDefault: false, isHidden: false }
]
this.setSettingOfProvider(providerName, 'models', newModels)
this._metricsService.capture('Add Model', { providerName, modelName })
}
deleteModel(providerName: ProviderName, modelName: string): boolean {
const { models } = this.state.settingsOfProvider[providerName]
@ -259,6 +284,9 @@ class VoidSettingsService extends Disposable implements IVoidSettingsService {
...models.slice(delIdx + 1, Infinity)
]
this.setSettingOfProvider(providerName, 'models', newModels)
this._metricsService.capture('Delete Model', { providerName, modelName })
return true
}

View file

@ -14,26 +14,33 @@ export type VoidModelInfo = {
isAutodetected?: boolean, // whether the model was autodetected by polling
}
type ModelInfoOfDefaultNamesOptions = { isAutodetected: true, existingModels: VoidModelInfo[] } // | { isOtherOption: true, ...otherOptions }
export const modelInfoOfDefaultNames = (modelNames: string[], options?: ModelInfoOfDefaultNamesOptions): VoidModelInfo[] => {
// creates `modelInfo` from `modelNames`
export const modelInfoOfDefaultNames = (modelNames: string[], options?: { isAutodetected: true, existingModels: VoidModelInfo[] }): VoidModelInfo[] => {
const { isAutodetected, existingModels } = options ?? {}
const isDefault = true
const isHidden = modelNames.length >= 10 // hide all models if there are a ton of them, and make user enable them individually
if (!existingModels) {
if (!existingModels) { // default settings
return modelNames.map((modelName, i) => ({ modelName, isDefault, isAutodetected, isHidden, }))
return modelNames.map((modelName, i) => ({
modelName,
isDefault: true,
isAutodetected: isAutodetected,
isHidden: modelNames.length >= 10 // hide all models if there are a ton of them, and make user enable them individually
}))
} else {
// keep existing `isHidden` property
} else { // settings if there are existing models (keep existing `isHidden` property)
const existingModelsMap: Record<string, VoidModelInfo> = {}
for (const em of existingModels) {
existingModelsMap[em.modelName] = em
for (const existingModel of existingModels) {
existingModelsMap[existingModel.modelName] = existingModel
}
return modelNames.map((modelName, i) => ({ modelName, isDefault, isAutodetected, isHidden: !!existingModelsMap[modelName]?.isHidden, }))
return modelNames.map((modelName, i) => ({
modelName,
isDefault: true,
isAutodetected: isAutodetected,
isHidden: !!existingModelsMap[modelName]?.isHidden,
}))
}

View file

@ -34,6 +34,7 @@ export const sendLLMMessage = ({
const captureChatEvent = (eventId: string, extras?: object) => {
metricsService.capture(eventId, {
providerName,
modelName,
numMessages: messages?.length,
messagesShape: messages?.map(msg => ({ role: msg.role, length: msg.content.length })),
version: '2024-11-14',

View file

@ -37,6 +37,7 @@ import { InputBox } from '../../../../base/browser/ui/inputbox/inputBox.js';
import { LLMMessage } from '../../../../platform/void/common/llmMessageTypes.js';
import { IModelContentChangedEvent } from '../../../../editor/common/textModelEvents.js';
import { extractCodeFromFIM, extractCodeFromRegular } from './helpers/extractCodeFromResult.js';
import { IMetricsService } from '../../../../platform/void/common/metricsService.js';
const configOfBG = (color: Color) => {
return { dark: color, light: color, hcDark: color, hcLight: color, }
@ -198,6 +199,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService {
@IConsistentItemService private readonly _consistentItemService: IConsistentItemService,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@IConsistentEditorItemService private readonly _consistentEditorItemService: IConsistentEditorItemService,
@IMetricsService private readonly _metricsService: IMetricsService,
) {
super();
@ -480,8 +482,14 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService {
fn: (editor) => {
const buttonsWidget = new AcceptRejectWidget({
editor,
onAccept: () => { this.acceptDiff({ diffid }) },
onReject: () => { this.rejectDiff({ diffid }) },
onAccept: () => {
this.acceptDiff({ diffid })
this._metricsService.capture('Accept Diff', { batch: false })
},
onReject: () => {
this.rejectDiff({ diffid })
this._metricsService.capture('Reject Diff', { batch: false })
},
diffid: diffid.toString(),
startLine: diff.startLine,
})

View file

@ -49,7 +49,7 @@ registerAction2(class extends Action2 {
const editorService = accessor.get(ICodeEditorService)
const metricsService = accessor.get(IMetricsService)
metricsService.capture('User Action', { type: 'Open Ctrl+K' })
metricsService.capture('Ctrl+K', {})
const editor = editorService.getActiveCodeEditor()
if (!editor) return;

View file

@ -20,9 +20,12 @@ const COPY_FEEDBACK_TIMEOUT = 1000 // amount of time to say 'Copied!'
const CodeButtonsOnHover = ({ text }: { text: string }) => {
const accessor = useAccessor()
const [copyButtonState, setCopyButtonState] = useState(CopyButtonState.Copy)
const inlineDiffService = accessor.get('IInlineDiffsService')
const clipboardService = accessor.get('IClipboardService')
const metricsService = accessor.get('IMetricsService')
useEffect(() => {
if (copyButtonState !== CopyButtonState.Copy) {
@ -36,6 +39,8 @@ const CodeButtonsOnHover = ({ text }: { text: string }) => {
clipboardService.writeText(text)
.then(() => { setCopyButtonState(CopyButtonState.Copied) })
.catch(() => { setCopyButtonState(CopyButtonState.Error) })
metricsService.capture('Copy Code', { length: text.length }) // capture the length only
}, [text, clipboardService])
const onApply = useCallback(() => {
@ -43,6 +48,7 @@ const CodeButtonsOnHover = ({ text }: { text: string }) => {
featureName: 'Ctrl+L',
userMessage: text,
})
metricsService.capture('Apply Code', { length: text.length }) // capture the length only
}, [inlineDiffService])
const isSingleLine = !text.includes('\n')

View file

@ -604,7 +604,7 @@ export const VoidCodeEditor = ({ initValue, language, maxHeight, showScrollbars
}
export const VoidButton = ({ children, disabled, onClick }: { children: React.ReactNode; disabled?:boolean; onClick: () => void }) => {
export const VoidButton = ({ children, disabled, onClick }: { children: React.ReactNode; disabled?: boolean; onClick: () => void }) => {
return <button disabled={disabled}
className='px-3 py-1 bg-black/10 dark:bg-gray-200/10 rounded-sm overflow-hidden'
onClick={onClick}

View file

@ -36,6 +36,7 @@ const RefreshModelButton = ({ providerName }: { providerName: RefreshableProvide
const accessor = useAccessor()
const refreshModelService = accessor.get('IRefreshModelService')
const metricsService = accessor.get('IMetricsService')
const [justFinished, setJustFinished] = useState(false)
@ -56,7 +57,10 @@ const RefreshModelButton = ({ providerName }: { providerName: RefreshableProvide
const { title: providerTitle } = displayInfoOfProviderName(providerName)
return <SubtleButton
onClick={() => { refreshModelService.refreshModels(providerName) }}
onClick={() => {
refreshModelService.refreshModels(providerName)
metricsService.capture('Click', { providerName, action: 'Refresh Models' })
}}
text={justFinished ? `${providerTitle} Models are up-to-date!` : `Manually refresh models list for ${providerTitle}.`}
icon={isRefreshing ? <Loader2 className='size-3 animate-spin' /> : (justFinished ? <Check className='stroke-green-500 size-3' /> : <RefreshCw className='size-3' />)}
disabled={isRefreshing || justFinished}
@ -69,7 +73,7 @@ const RefreshableModels = () => {
const buttons = refreshableProviderNames.map(providerName => {
if (!settingsState.settingsOfProvider[providerName]._enabled) return null
return <div key={providerName} className='pb-4' >
return <div key={providerName} className='pb-4'>
<RefreshModelButton providerName={providerName} />
</div>
})
@ -218,7 +222,9 @@ export const ModelDump = () => {
<VoidSwitch
value={disabled ? false : !isHidden}
onChange={() => { settingsStateService.toggleModelHidden(providerName, modelName) }}
onChange={() => {
settingsStateService.toggleModelHidden(providerName, modelName)
}}
disabled={disabled}
size='sm'
/>
@ -245,6 +251,7 @@ const ProviderSetting = ({ providerName, settingName }: { providerName: Provider
const accessor = useAccessor()
const voidSettingsService = accessor.get('IVoidSettingsService')
const voidMetricsService = accessor.get('IMetricsService')
let weChangedTextRef = false
@ -278,10 +285,12 @@ const ProviderSetting = ({ providerName, settingName }: { providerName: Provider
if (shouldEnable) {
voidSettingsService.setSettingOfProvider(providerName, '_enabled', true)
voidMetricsService.capture('Enable Provider', { providerName })
}
if (shouldDisable) {
voidSettingsService.setSettingOfProvider(providerName, '_enabled', false)
voidMetricsService.capture('Disable Provider', { providerName })
}
}
@ -372,6 +381,7 @@ export const VoidFeatureFlagSettings = () => {
const accessor = useAccessor()
const voidSettingsService = accessor.get('IVoidSettingsService')
const metricsService = accessor.get('IMetricsService')
const voidSettingsState = useSettingsState()
@ -382,7 +392,10 @@ export const VoidFeatureFlagSettings = () => {
const { description } = displayInfoOfFeatureFlag(flagName)
return <SubtleButton key={flagName}
onClick={() => { voidSettingsService.setFeatureFlag(flagName, !enabled) }}
onClick={() => {
voidSettingsService.setFeatureFlag(flagName, !enabled)
metricsService.capture('Click', { action: 'Autorefresh Toggle', flagName, enabled: !enabled })
}}
text={description}
icon={enabled ? <Check className='stroke-green-500 size-3' /> : <X className='stroke-red-500 size-3' />}
disabled={false}

View file

@ -98,7 +98,7 @@ registerAction2(class extends Action2 {
const metricsService = accessor.get(IMetricsService)
const editorService = accessor.get(ICodeEditorService)
metricsService.capture('User Action', { type: 'Ctrl+L' })
metricsService.capture('Ctrl+L', {})
const editor = editorService.getActiveCodeEditor()
// accessor.get(IEditorService).activeTextEditorControl?.getSelection()