mirror of
https://github.com/voideditor/void
synced 2026-05-24 01:48:25 +00:00
add metrics + streaming works pretty well!
This commit is contained in:
parent
86612fd5ed
commit
37200dacbd
7 changed files with 114 additions and 55 deletions
|
|
@ -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'));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 }))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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}|$)/;
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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 })
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue