This commit is contained in:
Andrew Pareles 2025-02-27 20:13:50 -08:00
parent 661eba3ae9
commit aa835d468b
10 changed files with 139 additions and 281 deletions

View file

@ -3,185 +3,106 @@
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
*--------------------------------------------------------------------------------------*/
import { Emitter, Event } from '../../../../base/common/event.js';
import { Disposable } from '../../../../base/common/lifecycle.js';
// import { URI } from '../../../../base/common/uri.js';
import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';
import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
// import { IToolService, ToolService } from '../common/toolsService.js';
// 1. search(ai)
// - tool use to find all possible changes
// - if search only: is this file related to the search?
// - if search + replace: should I modify this file?
// 2. replace(ai)
// - what changes to make?
// 3. postprocess errors
// -fastapply changes simultaneously
// -iterate on syntax errors (all files can be changed from a syntax error, not just the one with the error)
// private async _searchUsingAI({ searchClause }: { searchClause: string }) {
export type ChatMessageLocation = {
threadId: string;
messageIdx: number;
}
// // const relevantURIs: URI[] = []
// // const gatherPrompt = `\
// // asdasdas
// // `
// // const filterPrompt = `\
// // Is this file relevant?
// // `
export type SearchAndReplaceBlock = {
search: string;
replace: string;
}
// // // optimizations (DO THESE LATER!!!!!!)
// // // if tool includes a uri in uriSet, skip it obviously
// // let uriSet = new Set<URI>()
// // // gather
// // let messages = []
// // while (true) {
// // const result = await new Promise((res, rej) => {
// // sendLLMMessage({
// // messages,
// // tools: ['search'],
// // onFinalMessage: ({ result: r, }) => {
// // res(r)
// // },
// // onError: (error) => {
// // rej(error)
// // }
// // })
// // })
// service that manages state
export type ApplyState = {
[applyBoxId: string]: {
searchAndReplaceBlocks: SearchAndReplaceBlock;
}
}
// // messages.push({ role: 'tool', content: turnToString(result) })
// the purpose of this service is to generate search and replace blocks for a given codeblock `codeblockId` and on a file `fileName` and version `fileVersion`
// // sendLLMMessage({
// // messages: { 'Output ': result },
// // onFinalMessage: (r) => {
// // // output is file1\nfile2\nfile3\n...
// // }
// // })
export interface IFastApplyService {
readonly _serviceBrand: undefined;
// // uriSet.add(...)
// // }
// readonly state: ApplyState; // readonly to the user
// setState(newState: Partial<ApplyState>): void;
// onDidChangeState: Event<void>;
}
// // // writes
// // if (!replaceClause) return
export const IVoidFastApplyService = createDecorator<IFastApplyService>('voidFastApplyService');
class VoidFastApplyService extends Disposable implements IFastApplyService {
_serviceBrand: undefined;
// static readonly ID = 'voidFastApplyService';
private readonly _onDidChangeState = new Emitter<void>();
readonly onDidChangeState: Event<void> = this._onDidChangeState.event;
// state
// state: ApplyState
constructor(
// @IToolService private readonly toolService: ToolService
) {
super()
// initial state
// this.state = { currentUri: undefined }
}
setState(newState: Partial<ApplyState>) {
// this.state = { ...this.state, ...newState }
this._onDidChangeState.fire()
}
aiSearch(searchStr: string) {
}
aiReplace(searchStr: string, replaceStr: string) {
}
// 1. search(ai)
// - tool use to find all possible changes
// - if search only: is this file related to the search?
// - if search + replace: should I modify this file?
// 2. replace(ai)
// - what changes to make?
// 3. postprocess errors
// -fastapply changes simultaneously
// -iterate on syntax errors (all files can be changed from a syntax error, not just the one with the error)
// private async _searchUsingAI({ searchClause }: { searchClause: string }) {
// // const relevantURIs: URI[] = []
// // const gatherPrompt = `\
// // asdasdas
// // `
// // const filterPrompt = `\
// // Is this file relevant?
// // `
// // // optimizations (DO THESE LATER!!!!!!)
// // // if tool includes a uri in uriSet, skip it obviously
// // let uriSet = new Set<URI>()
// // // gather
// // let messages = []
// // while (true) {
// // const result = await new Promise((res, rej) => {
// // sendLLMMessage({
// // messages,
// // tools: ['search'],
// // onFinalMessage: ({ result: r, }) => {
// // res(r)
// // },
// // onError: (error) => {
// // rej(error)
// // }
// // })
// // })
// // messages.push({ role: 'tool', content: turnToString(result) })
// // sendLLMMessage({
// // messages: { 'Output ': result },
// // onFinalMessage: (r) => {
// // // output is file1\nfile2\nfile3\n...
// // }
// // })
// // uriSet.add(...)
// // }
// // // writes
// // if (!replaceClause) return
// // for (const uri of uriSet) {
// // // in future, batch these
// // applyWorkflow({ uri, applyStr: replaceClause })
// // }
// // for (const uri of uriSet) {
// // // in future, batch these
// // applyWorkflow({ uri, applyStr: replaceClause })
// // }
// // while (true) {
// // const result = new Promise((res, rej) => {
// // sendLLMMessage({
// // messages,
// // tools: ['search'],
// // onResult: (r) => {
// // res(r)
// // }
// // })
// // })
// // while (true) {
// // const result = new Promise((res, rej) => {
// // sendLLMMessage({
// // messages,
// // tools: ['search'],
// // onResult: (r) => {
// // res(r)
// // }
// // })
// // })
// // messages.push(result)
// // messages.push(result)
// // }
// // }
// }
// }
// private async _replaceUsingAI({ searchClause, replaceClause, relevantURIs }: { searchClause: string, replaceClause: string, relevantURIs: URI[] }) {
// private async _replaceUsingAI({ searchClause, replaceClause, relevantURIs }: { searchClause: string, replaceClause: string, relevantURIs: URI[] }) {
// for (const uri of relevantURIs) {
// for (const uri of relevantURIs) {
// uri
// uri
// }
// }
// // should I change this file?
// // if so what changes to make?
// // should I change this file?
// // if so what changes to make?
// // fast apply the changes
// }
// // fast apply the changes
// }
}
registerSingleton(IVoidFastApplyService, VoidFastApplyService, InstantiationType.Eager);

View file

@ -6,7 +6,7 @@
import { URI } from '../../../../../base/common/uri.js';
import { filenameToVscodeLanguage } from '../helpers/detectLanguage.js';
import { CodeSelection, StagingSelectionItem, FileSelection } from '../chatThreadService.js';
import { CodeSelection, StagingSelectionItem, FileSelection } from '../../common/chatThreadService.js';
import { IModelService } from '../../../../../editor/common/services/model.js';
import { os } from '../helpers/systemInfo.js';
import { IVoidFileService } from '../../common/voidFileService.js';

View file

@ -6,10 +6,14 @@
import React, { JSX } from 'react'
import { marked, MarkedToken, Token } from 'marked'
import { BlockCode } from './BlockCode.js'
import { ChatMessageLocation, } from '../../../aiRegexService.js'
import { nameToVscodeLanguage } from '../../../helpers/detectLanguage.js'
import { ApplyBlockHoverButtons } from './ApplyBlockHoverButtons.js'
export type ChatMessageLocation = {
threadId: string;
messageIdx: number;
}
type ApplyBoxLocation = ChatMessageLocation & { tokenIdx: string }

View file

@ -7,10 +7,10 @@ import React, { ButtonHTMLAttributes, FormEvent, FormHTMLAttributes, Fragment, K
import { useAccessor, useSidebarState, useChatThreadsState, useChatThreadsStreamState, useUriState, useSettingsState } from '../util/services.js';
import { ChatMessage, StagingSelectionItem, ToolMessage } from '../../../chatThreadService.js';
import { ChatMessage, StagingSelectionItem, ToolMessage } from '../../../../common/chatThreadService.js';
import { BlockCode } from '../markdown/BlockCode.js';
import { ChatMarkdownRender } from '../markdown/ChatMarkdownRender.js';
import { ChatMarkdownRender, ChatMessageLocation } from '../markdown/ChatMarkdownRender.js';
import { URI } from '../../../../../../../base/common/uri.js';
import { IDisposable } from '../../../../../../../base/common/lifecycle.js';
import { ErrorDisplay } from './ErrorDisplay.js';
@ -24,7 +24,7 @@ import { VOID_OPEN_SETTINGS_ACTION_ID } from '../../../voidSettingsPane.js';
import { ChevronRight, Pencil, X } from 'lucide-react';
import { FeatureName, isFeatureNameDisabled } from '../../../../../../../workbench/contrib/void/common/voidSettingsTypes.js';
import { WarningBox } from '../void-settings-tsx/WarningBox.js';
import { ChatMessageLocation } from '../../../aiRegexService.js';
import { ToolCallReturnType, ToolName } from '../../../../common/toolsService.js';

View file

@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------*/
import React, { useState, useEffect, useCallback } from 'react'
import { ThreadStreamState, ThreadsState } from '../../../chatThreadService.js'
import { ThreadStreamState,IChatThreadService, ThreadsState } from '../../../../common/chatThreadService.js'
import { RefreshableProviderName, SettingsOfProvider } from '../../../../../../../workbench/contrib/void/common/voidSettingsTypes.js'
import { IDisposable } from '../../../../../../../base/common/lifecycle.js'
import { VoidSidebarState } from '../../../sidebarStateService.js'
@ -29,7 +29,6 @@ import { IEditCodeService, URIStreamState } from '../../../editCodeService.js';
import { IVoidUriStateService } from '../../../voidUriStateService.js';
import { IQuickEditStateService } from '../../../quickEditStateService.js';
import { ISidebarStateService } from '../../../sidebarStateService.js';
import { IChatThreadService } from '../../../chatThreadService.js';
import { IInstantiationService } from '../../../../../../../platform/instantiation/common/instantiation.js'
import { ICodeEditorService } from '../../../../../../../editor/browser/services/codeEditorService.js'
import { ICommandService } from '../../../../../../../platform/commands/common/commands.js'

View file

@ -11,7 +11,7 @@ import { ServicesAccessor } from '../../../../editor/browser/editorExtensions.js
import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js';
import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js';
import { StagingSelectionItem, IChatThreadService } from './chatThreadService.js';
import { StagingSelectionItem, IChatThreadService } from '../common/chatThreadService.js';
import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js';
import { IRange } from '../../../../editor/common/core/range.js';

View file

@ -15,8 +15,6 @@ import './sidebarStateService.js'
// register quick edit (Ctrl+K)
import './quickEditActions.js'
// register Thread History
import './chatThreadService.js'
// register Autocomplete
import './autocompleteService.js'
@ -56,3 +54,7 @@ import '../common/voidUpdateService.js'
// tools
import '../common/toolsService.js'
// register Thread History
import '../common/chatThreadService.js'

View file

@ -11,12 +11,12 @@ import { IStorageService, StorageScope, StorageTarget } from '../../../../platfo
import { URI } from '../../../../base/common/uri.js';
import { Emitter, Event } from '../../../../base/common/event.js';
import { IRange } from '../../../../editor/common/core/range.js';
import { ILLMMessageService } from '../common/llmMessageService.js';
import { chat_userMessageContent, chat_systemMessage, chat_userMessageContentWithAllFilesToo as chat_userMessageContentWithAllFiles, chat_selectionsString } from './prompt/prompts.js';
import { InternalToolInfo, IToolsService, ToolCallReturnType, ToolFns, ToolName, voidTools } from '../common/toolsService.js';
import { toLLMChatMessage } from '../common/llmMessageTypes.js';
import { ILLMMessageService } from './llmMessageService.js';
import { chat_userMessageContent, chat_systemMessage, chat_userMessageContentWithAllFilesToo as chat_userMessageContentWithAllFiles, chat_selectionsString } from '../browser/prompt/prompts.js';
import { InternalToolInfo, IToolsService, ToolCallReturnType, ToolFns, ToolName, voidTools } from './toolsService.js';
import { toLLMChatMessage } from './llmMessageTypes.js';
import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js';
import { IVoidFileService } from '../common/voidFileService.js';
import { IVoidFileService } from './voidFileService.js';
import { generateUuid } from '../../../../base/common/uuid.js';
@ -101,7 +101,6 @@ export type ChatThreads = {
type ThreadType = ChatThreads[string]
const defaultThreadState: ThreadType['state'] = { stagingSelections: [], focusedMessageIdx: undefined, isCheckedOfSelectionId: {} }
export type ThreadsState = {
allThreads: ChatThreads;
@ -134,10 +133,7 @@ const newThreadObject = () => {
} satisfies ChatThreads[string]
}
const THREAD_VERSION_KEY = 'void.chatThreadVersion'
const LATEST_THREAD_VERSION = 'v2'
const THREAD_STORAGE_KEY = 'void.chatThreadStorage'
export const THREAD_STORAGE_KEY = 'void.chatThreadStorage'
type ChatMode = 'agent' | 'chat'
@ -200,35 +196,11 @@ class ChatThreadService extends Disposable implements IChatThreadService {
@IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService,
) {
super()
this.state = { allThreads: {}, currentThreadId: null as unknown as string } // default state
const readThreads = this._readAllThreads() || {}
setInterval(() => {
const thread = this.getCurrentThread()
if (!thread) return
// print out all staging selections for all messages
for (const message of thread.messages) {
if (message.role === 'user' && message.state.stagingSelections.length > 0) {
console.log('Message staging selections:', message.state.stagingSelections)
}
}
// also print thread-level staging selections
if (thread.state.stagingSelections.length > 0) {
console.log('Thread staging selections:', thread.state.stagingSelections)
}
}, 1000)
const oldVersionNum = this._storageService.get(THREAD_VERSION_KEY, StorageScope.APPLICATION)
const readThreads = this._readAllThreads()
const updatedThreads = this._updatedThreadsToVersion(readThreads, oldVersionNum)
if (updatedThreads !== null) {
this._storeAllThreads(updatedThreads)
}
const allThreads = updatedThreads ?? readThreads
const allThreads = readThreads
this.state = {
allThreads: allThreads,
currentThreadId: null as unknown as string, // gets set in startNewThread()
@ -236,9 +208,6 @@ class ChatThreadService extends Disposable implements IChatThreadService {
// always be in a thread
this.openNewThread()
this._storageService.store(THREAD_VERSION_KEY, LATEST_THREAD_VERSION, StorageScope.APPLICATION, StorageTarget.USER)
}
// !!! this is important for properly restoring URIs from storage
@ -251,10 +220,10 @@ class ChatThreadService extends Disposable implements IChatThreadService {
});
}
private _readAllThreads(): ChatThreads {
private _readAllThreads(): ChatThreads | null {
const threadsStr = this._storageService.get(THREAD_STORAGE_KEY, StorageScope.APPLICATION);
if (!threadsStr) {
return {};
return null
}
return this._convertThreadDataFromStorage(threadsStr);
}
@ -270,48 +239,6 @@ class ChatThreadService extends Disposable implements IChatThreadService {
}
// returns if should update
private _updatedThreadsToVersion(oldThreadsObject: any, oldVersion: string | undefined): ChatThreads | null {
if (!oldVersion) {
// unknown, just reset chat?
return null
}
/** v1 -> v2
- threads.state.currentStagingSelections: CodeStagingSelection[] | null;
+ thread[threadIdx].state
+ message.state
+ chatMessage.staging: StagingInfo | null
*/
else if (oldVersion === 'v1') {
const threads = oldThreadsObject as Omit<ChatThreads, 'staging' | 'focusedMessageIdx'>
// update the threads
for (const thread of Object.values(threads)) {
if (!thread.state) {
thread.state = defaultThreadState
}
for (const chatMessage of Object.values(thread.messages)) {
if (chatMessage.role === 'user' && !chatMessage.state) {
chatMessage.state = defaultMessageState
}
}
}
// push the update
return threads
}
else if (oldVersion === 'v2') {
return null
}
// up to date
return null
}
// this should be the only place this.state = ... appears besides constructor
private _setState(state: Partial<ThreadsState>, affectsCurrent: boolean) {
this.state = {

View file

@ -3,7 +3,7 @@
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
*--------------------------------------------------------------------------------------*/
import { ChatMessage } from '../browser/chatThreadService.js'
import { ChatMessage } from './chatThreadService.js'
import { InternalToolInfo, ToolName } from './toolsService.js'
import { FeatureName, ProviderName, SettingsOfProvider } from './voidSettingsTypes.js'

View file

@ -28,7 +28,7 @@ type SetModelSelectionOfFeatureFn = <K extends FeatureName>(
options?: { doNotApplyEffects?: true }
) => Promise<void>;
type SetGlobalSettingFn = <T extends GlobalSettingName, >(settingName: T, newVal: GlobalSettings[T]) => void;
type SetGlobalSettingFn = <T extends GlobalSettingName>(settingName: T, newVal: GlobalSettings[T]) => void;
export type ModelOption = { name: string, selection: ModelSelection }
@ -49,6 +49,8 @@ export interface IVoidSettingsService {
readonly state: VoidSettingsState; // in order to play nicely with react, you should immutably change state
readonly waitForInitState: Promise<void>;
readAndInitializeState: (providedState?: VoidSettingsState) => Promise<void>;
onDidChangeState: Event<void>;
setSettingOfProvider: SetSettingOfProviderFn;
@ -168,6 +170,8 @@ class VoidSettingsService extends Disposable implements IVoidSettingsService {
readonly onDidChangeState: Event<void> = this._onDidChangeState.event; // this is primarily for use in react, so react can listen + update on state changes
state: VoidSettingsState;
private readonly _resolver: () => void
waitForInitState: Promise<void> // await this if you need a valid state initially
constructor(
@ -181,56 +185,57 @@ class VoidSettingsService extends Disposable implements IVoidSettingsService {
// at the start, we haven't read the partial config yet, but we need to set state to something
this.state = defaultState()
let resolver: () => void = () => { }
this.waitForInitState = new Promise((res, rej) => resolver = res)
this._resolver = resolver
// read and update the actual state immediately
this._readState().then(readS => {
this.readAndInitializeState()
}
// the stored data structure might be outdated, so we need to update it here (can do a more general solution later when we need to)
const newSettingsOfProvider = {
// A HACK BECAUSE WE ADDED DEEPSEEK (did not exist before, comes before readS)
...{ deepseek: defaultSettingsOfProvider.deepseek },
async readAndInitializeState(providedState?: VoidSettingsState) {
// If providedState is given, use it instead of reading from storage
const readS = providedState || await this._readState();
// A HACK BECAUSE WE ADDED XAI (did not exist before, comes before readS)
...{ xAI: defaultSettingsOfProvider.xAI },
// the stored data structure might be outdated, so we need to update it here
const newSettingsOfProvider = {
// A HACK BECAUSE WE ADDED DEEPSEEK (did not exist before, comes before readS)
...{ deepseek: defaultSettingsOfProvider.deepseek },
// A HACK BECAUSE WE ADDED VLLM (did not exist before, comes before readS)
...{ vLLM: defaultSettingsOfProvider.vLLM },
// A HACK BECAUSE WE ADDED XAI (did not exist before, comes before readS)
...{ xAI: defaultSettingsOfProvider.xAI },
// A HACK BECAUSE WE ADDED VLLM (did not exist before, comes before readS)
...{ vLLM: defaultSettingsOfProvider.vLLM },
...readS.settingsOfProvider,
...readS.settingsOfProvider,
// A HACK BECAUSE WE ADDED NEW GEMINI MODELS (existed before, comes after readS)
gemini: {
...readS.settingsOfProvider.gemini,
models: [
...readS.settingsOfProvider.gemini.models,
...defaultSettingsOfProvider.gemini.models.filter(m => /* if cant find the model in readS (yes this is O(n^2), very small) */ !readS.settingsOfProvider.gemini.models.find(m2 => m2.modelName === m.modelName))
]
}
// A HACK BECAUSE WE ADDED NEW GEMINI MODELS (existed before, comes after readS)
gemini: {
...readS.settingsOfProvider.gemini,
models: [
...readS.settingsOfProvider.gemini.models,
...defaultSettingsOfProvider.gemini.models.filter(m => /* if cant find the model in readS (yes this is O(n^2), very small) */ !readS.settingsOfProvider.gemini.models.find(m2 => m2.modelName === m.modelName))
]
}
};
const newModelSelectionOfFeature = {
// A HACK BECAUSE WE ADDED FastApply
...{ 'Apply': null },
...readS.modelSelectionOfFeature,
}
const newModelSelectionOfFeature = {
// A HACK BECAUSE WE ADDED FastApply
...{ 'Apply': null },
...readS.modelSelectionOfFeature,
};
readS = {
...readS,
settingsOfProvider: newSettingsOfProvider,
modelSelectionOfFeature: newModelSelectionOfFeature,
}
const finalState = {
...readS,
settingsOfProvider: newSettingsOfProvider,
modelSelectionOfFeature: newModelSelectionOfFeature,
};
this.state = _validatedState(readS)
resolver()
this._onDidChangeState.fire()
})
this.state = _validatedState(finalState);
this._resolver();
this._onDidChangeState.fire();
}