add metrics + streaming works pretty well!

This commit is contained in:
Andrew Pareles 2025-01-13 21:48:04 -08:00
parent 86612fd5ed
commit 37200dacbd
7 changed files with 114 additions and 55 deletions

View file

@ -25,6 +25,7 @@ export class MetricsService implements IMetricsService {
constructor(
@IMainProcessService mainProcessService: IMainProcessService // (only usable on client side)
) {
// creates an IPC proxy to use metricsMainService.ts
this.metricsService = ProxyChannel.toService<IMetricsService>(mainProcessService.getChannel('void-channel-metrics'));
}

View file

@ -4,6 +4,8 @@
*--------------------------------------------------------------------------------------*/
import { Disposable } from '../../../base/common/lifecycle.js';
import { isLinux, isMacintosh, isWindows } from '../../../base/common/platform.js';
import { IProductService } from '../../product/common/productService.js';
import { ITelemetryService } from '../../telemetry/common/telemetry.js';
import { IMetricsService } from '../common/metricsService.js';
@ -24,14 +26,20 @@ export class MetricsMainService extends Disposable implements IMetricsService {
readonly client: PostHog
constructor(
@ITelemetryService private readonly _telemetryService: ITelemetryService
@ITelemetryService private readonly _telemetryService: ITelemetryService,
@IProductService private readonly _productService: IProductService
) {
super()
this.client = new PostHog('phc_UanIdujHiLp55BkUTjB1AuBXcasVkdqRwgnwRlWESH2', { host: 'https://us.i.posthog.com', })
const { devDeviceId, firstSessionDate, machineId } = this._telemetryService
this._distinctId = devDeviceId
this.client.identify({ distinctId: devDeviceId, properties: { firstSessionDate, machineId } })
const { commit, version } = this._productService
const os = isWindows ? 'windows' : isMacintosh ? 'mac' : isLinux ? 'linux' : null
this.client.identify({ distinctId: this._distinctId, properties: { firstSessionDate, machineId, commit, version, os } })
console.log('Void posthog metrics info:', JSON.stringify({ devDeviceId, firstSessionDate, machineId }))
}

View file

@ -652,7 +652,8 @@ export class AutocompleteService extends Disposable implements IAutocompleteServ
// newAutocompletion.abortRef = { current: () => { } }
newAutocompletion.status = 'finished'
// newAutocompletion.promise = undefined
newAutocompletion.insertText = postprocessResult(extractCodeFromRegular(fullText))
const [text, _] = extractCodeFromRegular({ text: fullText, recentlyAddedTextLen: 0 })
newAutocompletion.insertText = postprocessResult(text)
resolve(newAutocompletion.insertText)

View file

@ -22,7 +22,7 @@ class SurroundingsRemover {
// returns whether it removed the whole prefix
removePrefix = (prefix: string): boolean => {
let offset = 0
console.log('prefix', prefix, Math.min(this.j, prefix.length - 1))
// console.log('prefix', prefix, Math.min(this.j, prefix.length - 1))
while (this.i <= this.j && offset <= prefix.length - 1) {
if (this.originalS.charAt(this.i) !== prefix.charAt(offset))
break
@ -64,7 +64,7 @@ class SurroundingsRemover {
this.i = this.j + 1
return false
}
console.log('index', index, until.length)
// console.log('index', index, until.length)
if (alsoRemoveUntilStr)
this.i = index + until.length
@ -90,11 +90,21 @@ class SurroundingsRemover {
}
actualRecentlyAdded = (recentlyAddedTextLen: number) => {
// aaaaaatextaaaaaa{recentlyAdded}
// i ^ j
// |
// recentyAddedIdx
const recentlyAddedIdx = this.j - recentlyAddedTextLen + 1
return this.originalS.substring(Math.max(this.i, recentlyAddedIdx), this.j + 1)
}
}
export const extractCodeFromRegular = (text: string): string => {
export const extractCodeFromRegular = ({ text, recentlyAddedTextLen }: { text: string, recentlyAddedTextLen: number }): [string, string] => {
// Match either:
// 1. ```language\n<code>```
// 2. ```<code>```
@ -104,7 +114,9 @@ export const extractCodeFromRegular = (text: string): string => {
pm.removeCodeBlock()
const s = pm.value()
return s
const actual = pm.actualRecentlyAdded(recentlyAddedTextLen)
return [s, actual]
}
@ -112,7 +124,7 @@ export const extractCodeFromRegular = (text: string): string => {
// Ollama has its own FIM, we should not use this if we use that
export const extractCodeFromFIM = ({ text, midTag }: { text: string, midTag: string }): string => {
export const extractCodeFromFIM = ({ text, recentlyAddedTextLen, midTag, }: { text: string, recentlyAddedTextLen: number, midTag: string }): [string, string] => {
/* ------------- summary of the regex -------------
[optional ` | `` | ```]
@ -126,23 +138,17 @@ export const extractCodeFromFIM = ({ text, midTag }: { text: string, midTag: str
const pm = new SurroundingsRemover(text)
console.log('ORIGIINAL CODE', text)
pm.removeCodeBlock()
console.log('D', pm.i, pm.j)
const foundMid = pm.removePrefix(`<${midTag}>`)
console.log('E', midTag, pm.i, pm.j)
if (foundMid) {
pm.removeSuffix(`</${midTag}>`)
console.log('F', pm.i, pm.j)
}
const s = pm.value()
return s
const actual = pm.actualRecentlyAdded(recentlyAddedTextLen)
return [s, actual]
// // const regex = /[\s\S]*?(?:`{1,3}\s*([a-zA-Z_]+[\w]*)?[\s\S]*?)?<MID>([\s\S]*?)(?:<\/MID>|`{1,3}|$)/;

View file

@ -38,7 +38,6 @@ import { extractCodeFromFIM, extractCodeFromRegular } from './helpers/extractCod
import { IMetricsService } from '../../../../platform/void/common/metricsService.js';
import { InlineDecorationType } from '../../../../editor/common/viewModel.js';
import { filenameToVscodeLanguage } from './helpers/detectLanguage.js';
import { BaseEditorSimpleWorker } from '../../../../editor/common/services/editorSimpleWorker.js';
import { INotificationService, Severity } from '../../../../platform/notification/common/notification.js';
import { isMacintosh } from '../../../../base/common/platform.js';
// import { Action2, registerAction2 } from '../../../../platform/actions/common/actions.js';
@ -130,6 +129,8 @@ type CtrlKZone = {
refresh: () => void;
}
_linkedStreamingDiffZone: number | null; // diffareaid of the diffZone currently streaming here
} & CommonZoneProps
@ -292,13 +293,15 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService {
// sweepLine ... sweepLine
const fn1 = this._addLineDecoration(model, diffArea._streamState.line, diffArea._streamState.line, 'void-sweepIdxBG')
// sweepLine+1 ... endLine
const fn2 = this._addLineDecoration(model, diffArea._streamState.line + 1, diffArea.endLine, 'void-sweepBG')
const fn2 = diffArea._streamState.line + 1 <= diffArea.endLine ?
this._addLineDecoration(model, diffArea._streamState.line + 1, diffArea.endLine, 'void-sweepBG')
: null
diffArea._removeStylesFns.add(() => { fn1?.(); fn2?.(); })
}
}
else if (diffArea.type === 'CtrlKZone') {
else if (diffArea.type === 'CtrlKZone' && diffArea._linkedStreamingDiffZone === null) {
// highlight zone's text
const fn = this._addLineDecoration(model, diffArea.startLine, diffArea.endLine, 'void-highlightBG')
diffArea._removeStylesFns.add(() => fn?.());
@ -478,7 +481,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService {
minWidthInPx: result.minWidthInPx,
domNode: domNode,
marginDomNode: document.createElement('div'), // displayed to left
suppressMouseDown: true,
suppressMouseDown: false,
showInHiddenAreas: true,
};
@ -548,24 +551,30 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService {
}
weAreWriting = false
worker = new BaseEditorSimpleWorker()
private async _writeText(uri: URI, text: string, range: IRange, { shouldRealignDiffAreas }: { shouldRealignDiffAreas: boolean }) {
const model = this._getModel(uri)
if (!model) return
const uriStr = this._readURI(uri, range)
if (!uriStr) return
if (uriStr === null) return
// minimal edits so not so flashy
// __TODO__ THIS IS NOT INSIDE A WORKER, SO IT MIGHT BE SLOW, we should instead just do an optimal write ourselves
const edits = this.worker.$Void_computeMoreMinimalEdits(uri.toString(), [{ range, text }], false)
if (edits) {
this.weAreWriting = true
model.applyEdits(edits)
this.weAreWriting = false
// heuristic check if don't need to make edits
const dontNeedToWrite = uriStr === text
if (dontNeedToWrite) {
// at the end of a write, we still expect to refresh all styles
// e.g. sometimes we expect to restore all the decorations even if no edits were made when _writeText is used
this._refreshStylesAndDiffsInURI(uri)
return
}
// minimal edits so not so flashy
// const edits = this.worker.$Void_computeMoreMinimalEdits(uri.toString(), [{ range, text }], false)
this.weAreWriting = true
model.applyEdits([{ range, text }])
this.weAreWriting = false
this._onInternalChangeContent(uri, { shouldRealign: shouldRealignDiffAreas && { newText: text, oldRange: range } })
}
@ -627,6 +636,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService {
_URI: uri,
_removeStylesFns: new Set(),
_mountInfo: null,
_linkedStreamingDiffZone: null,
}
}
this.diffAreasOfURI[uri.fsPath].add(diffareaid)
@ -636,16 +646,11 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService {
const numLines = this._getNumLines(uri)
if (numLines === null) return
const hasWriteChange = this._readURI(uri) !== entireModelCode // a heuristic check
if (hasWriteChange)
this._writeText(uri, entireModelCode,
{ startColumn: 1, startLineNumber: 1, endLineNumber: numLines, endColumn: Number.MAX_SAFE_INTEGER },
{ shouldRealignDiffAreas: false }
)
else {
// restore all the decorations
this._refreshStylesAndDiffsInURI(uri)
}
this._writeText(uri, entireModelCode,
{ startColumn: 1, startLineNumber: 1, endLineNumber: numLines, endColumn: Number.MAX_SAFE_INTEGER },
{ shouldRealignDiffAreas: false }
)
}
const beforeSnapshot: HistorySnapshot = getCurrentSnapshot()
@ -844,7 +849,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService {
// @throttle(100)
private _writeDiffZoneLLMText(diffZone: DiffZone, llmText: string) {
private _writeStreamedDiffZoneLLMText(diffZone: DiffZone, llmText: string, deltaText: string, latest: { line: number, col: number, addedSplitYet: boolean, originalCodeStartLine: number }) {
// ----------- 1. Write the new code to the document -----------
// figure out where to highlight based on where the AI is in the stream right now, use the last diff to figure that out
@ -881,17 +886,41 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService {
}
// lines are 1-indexed
const newCodeTop = llmText.split('\n').slice(0, (newCodeEndLine - 1) + 1).join('\n')
const oldFileBottom = diffZone.originalCode.split('\n').slice((originalCodeStartLine - 1) + 1, Infinity).join('\n')
// oriignalCode[1 + line...Infinity]. Must make sure 1 + line < originalCode.length. This is another way to check:
const newCode = (newCodeTop && oldFileBottom) ? `${newCodeTop}\n${oldFileBottom}` : (oldFileBottom || newCodeTop)
this._writeText(uri, newCode,
{ startLineNumber: diffZone.startLine, startColumn: 1, endLineNumber: diffZone.endLine, endColumn: Number.MAX_SAFE_INTEGER, }, // 1-indexed
// insert at latest line and col
this._writeText(uri, deltaText,
{ startLineNumber: latest.line, startColumn: latest.col, endLineNumber: latest.line, endColumn: latest.col },
{ shouldRealignDiffAreas: true }
)
latest.line += deltaText.split('\n').length - 1
const lastNewlineIdx = deltaText.lastIndexOf('\n')
latest.col = lastNewlineIdx === -1 ? latest.col + deltaText.length : deltaText.length - lastNewlineIdx
if (!latest.addedSplitYet) {
this._writeText(uri, '',
{ startLineNumber: latest.line, startColumn: latest.col, endLineNumber: latest.line, endColumn: latest.col, },
{ shouldRealignDiffAreas: true }
)
latest.addedSplitYet = true
}
// delete or insert to get original up to speed
if (latest.originalCodeStartLine < originalCodeStartLine) {
// moved up, delete
const numLinesDeleted = originalCodeStartLine - latest.originalCodeStartLine
this._writeText(uri, '',
{ startLineNumber: latest.line, startColumn: latest.col, endLineNumber: latest.line + numLinesDeleted, endColumn: Number.MAX_SAFE_INTEGER, },
{ shouldRealignDiffAreas: true }
)
}
else if (latest.originalCodeStartLine > originalCodeStartLine) {
this._writeText(uri, '\n' + diffZone.originalCode.split('\n').slice((latest.originalCodeStartLine - 1), (originalCodeStartLine - 1) + 1).join('\n'),
{ startLineNumber: latest.line, startColumn: latest.col, endLineNumber: latest.line, endColumn: latest.col },
{ shouldRealignDiffAreas: true }
)
}
latest.originalCodeStartLine = originalCodeStartLine
// add diffZone.startLine to convert to right coordinate system (line in file, not in diffarea)
diffZone._streamState.line = (diffZone.startLine - 1) + newCodeEndLine
@ -980,6 +1009,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService {
_URI: uri,
_removeStylesFns: new Set(),
_mountInfo: null,
_linkedStreamingDiffZone: null,
}
const ctrlKZone = this._addDiffArea(adding)
this._refreshStylesAndDiffsInURI(uri)
@ -1054,7 +1084,6 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService {
}
else if (featureName === 'Ctrl+K') {
const { diffareaid } = opts
const ctrlKZone = this.diffAreaOfId[diffareaid]
if (ctrlKZone.type !== 'CtrlKZone') return
@ -1101,7 +1130,14 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService {
_removeStylesFns: new Set(),
}
const diffZone = this._addDiffArea(adding)
if (featureName === 'Ctrl+K') {
const { diffareaid } = opts
const ctrlKZone = this.diffAreaOfId[diffareaid]
if (ctrlKZone.type !== 'CtrlKZone') return
ctrlKZone._linkedStreamingDiffZone = diffZone.diffareaid
}
// now handle messages
let messages: LLMMessage[]
if (featureName === 'Ctrl+L') {
@ -1147,29 +1183,33 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService {
this._refreshStylesAndDiffsInURI(uri)
const extractText = (fullText: string) => {
const extractText = (fullText: string, recentlyAddedTextLen: number) => {
if (featureName === 'Ctrl+K') {
if (ollamaStyleFIM) return fullText
return extractCodeFromFIM({ text: fullText, midTag: modelFimTags.midTag })
return extractCodeFromFIM({ text: fullText, recentlyAddedTextLen, midTag: modelFimTags.midTag })
}
else if (featureName === 'Ctrl+L') {
return extractCodeFromRegular(fullText)
return extractCodeFromRegular({ text: fullText, recentlyAddedTextLen })
}
throw 1
}
const latestStreamInfo = { line: diffZone.startLine, addedSplitYet: false, col: 1, originalCodeStartLine: 1 }
streamRequestIdRef.current = this._llmMessageService.sendLLMMessage({
featureName,
logging: { loggingName: `startApplying - ${featureName}` },
messages,
onText: ({ newText, fullText }) => {
this._writeDiffZoneLLMText(diffZone, extractText(fullText))
const [text, deltaText] = extractText(fullText, newText.length)
this._writeStreamedDiffZoneLLMText(diffZone, text, deltaText, latestStreamInfo)
this._refreshStylesAndDiffsInURI(uri)
},
onFinalMessage: ({ fullText }) => {
// console.log('DONE! FULL TEXT\n', extractText(fullText), diffZone.startLine, diffZone.endLine)
// at the end, re-write whole thing to make sure no sync errors
this._writeText(uri, extractText(fullText),
const [text, _] = extractText(fullText, 0)
this._writeText(uri, text,
{ startLineNumber: diffZone.startLine, startColumn: 1, endLineNumber: diffZone.endLine, endColumn: Number.MAX_SAFE_INTEGER }, // 1-indexed
{ shouldRealignDiffAreas: true }
)

View file

@ -381,7 +381,7 @@ Note that the SELECTION has code that comes before it. This code is indicated wi
Note also that the SELECTION has code that comes after it. This code is indicated with <${sufTag}>...after<${sufTag}/>.
Instructions:
1. Your OUTPUT should be a SINGLE PIECE OF CODE of the form <${midTag}>...new_selection<${midTag}/>. Do not give any explanation before or after this. ONLY output this format, nothing more.
1. Your OUTPUT should be a SINGLE PIECE OF CODE of the form <${midTag}>...new_selection<${midTag}/>. Do NOT output any text or explanations before or after this.
2. You may ONLY CHANGE the original SELECTION, and NOT the content in the <${preTag}>...<${preTag}/> or <${sufTag}>...<${sufTag}/> tags.
3. Make sure all brackets in the new selection are balanced the same as in the original selection.
4. Be careful not to duplicate or remove variables, comments, or other syntax by mistake.

View file

@ -60,9 +60,12 @@ registerAction2(class extends Action2 {
const { startLineNumber: startLine, endLineNumber: endLine } = selection
// deselect - clear selection
editor.setSelection({ startLineNumber: startLine, endLineNumber: startLine, startColumn: 1, endColumn: 1 })
editor.revealLine(startLine) // important
const inlineDiffsService = accessor.get(IInlineDiffsService)
inlineDiffsService.addCtrlKZone({ startLine, endLine, editor })
}