mirror of
https://github.com/voideditor/void
synced 2026-05-24 09:58:23 +00:00
update metrics
This commit is contained in:
parent
e6f6c7500d
commit
e1b8e49b13
10 changed files with 112 additions and 38 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}))
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
Loading…
Reference in a new issue