From 1d9e0faaa312263731cc91f505a0c29067519f72 Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Sat, 5 Apr 2025 20:40:59 -0700 Subject: [PATCH] make chats by reference --- .../contrib/void/browser/chatThreadService.ts | 69 ++++--- .../react/src/sidebar-tsx/SidebarChat.tsx | 96 +++------- .../contrib/void/browser/sidebarActions.ts | 88 +++++---- .../void/common/chatThreadServiceTypes.ts | 40 ++-- .../contrib/void/common/prompt/prompts.ts | 174 ++++++++++-------- 5 files changed, 220 insertions(+), 247 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/chatThreadService.ts b/src/vs/workbench/contrib/void/browser/chatThreadService.ts index 92a8c25a..87ba8fff 100644 --- a/src/vs/workbench/contrib/void/browser/chatThreadService.ts +++ b/src/vs/workbench/contrib/void/browser/chatThreadService.ts @@ -11,7 +11,7 @@ import { IStorageService, StorageScope, StorageTarget } from '../../../../platfo import { URI } from '../../../../base/common/uri.js'; import { Emitter, Event } from '../../../../base/common/event.js'; import { ILLMMessageService } from '../common/sendLLMMessageService.js'; -import { chat_userMessageContent, chat_systemMessage, chat_lastUserMessageWithFilesAdded, chat_selectionsString, voidTools } from '../common/prompt/prompts.js'; +import { chat_userMessageContent, chat_systemMessage, voidTools } from '../common/prompt/prompts.js'; import { getErrorMessage, LLMChatMessage, ToolCallType } from '../common/sendLLMMessageTypes.js'; import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js'; import { generateUuid } from '../../../../base/common/uuid.js'; @@ -186,9 +186,9 @@ export interface IChatThreadService { getCurrentFocusedMessageIdx(): number | undefined; isCurrentlyFocusingMessage(): boolean; setCurrentlyFocusedMessageIdx(messageIdx: number | undefined): void; - // current thread's staging selections - closeCurrentStagingSelectionsInMessage(opts: { messageIdx: number }): void; - closeCurrentStagingSelectionsInThread(): void; + // // current thread's staging selections + // closeCurrentStagingSelectionsInMessage(opts: { messageIdx: number }): void; + // closeCurrentStagingSelectionsInThread(): void; // codespan links (link to symbols in the markdown) getCodespanLink(opts: { codespanStr: string, messageIdx: number, threadId: string }): CodespanLocationLink | undefined; @@ -294,11 +294,9 @@ class ChatThreadService extends Disposable implements IChatThreadService { const newStagingSelection: StagingSelectionItem = { type: 'File', - fileURI: newModel.uri, + uri: newModel.uri, language: newModel.getLanguageId(), - selectionStr: null, - range: null, - state: { isOpened: false, wasAddedAsCurrentFile: true } + state: { wasAddedAsCurrentFile: true } } const focusedMessageIdx = this.getCurrentFocusedMessageIdx(); @@ -312,7 +310,7 @@ class ChatThreadService extends Disposable implements IChatThreadService { const newStagingSelections: StagingSelectionItem[] = oldStagingSelections.filter(s => !s.state?.wasAddedAsCurrentFile); // add the new file if it doesn't exist - const fileIsAdded = oldStagingSelections.some(s => s.type === 'File' && s.fileURI.fsPath === newStagingSelection.fileURI.fsPath) + const fileIsAdded = oldStagingSelections.some(s => s.type === 'File' && s.uri.fsPath === newStagingSelection.uri.fsPath) if (!fileIsAdded) { newStagingSelections.push(newStagingSelection) } @@ -549,8 +547,6 @@ class ChatThreadService extends Disposable implements IChatThreadService { private async _runChatAgent({ threadId, - prevSelns, - currSelns, modelSelection, modelSelectionOptions, userMessageContent, @@ -565,11 +561,7 @@ class ChatThreadService extends Disposable implements IChatThreadService { callThisToolFirst?: ToolRequestApproval }) { - - // define helper functions so we can tell what's going on - // for now, do not recompute selections as we run (it seems to confuse tool-use models) - const selectionsStr = await chat_selectionsString(prevSelns, currSelns, this._voidModelService) // all the file CONTENTS or "selections" de-duped - const userMessageFullContent = chat_lastUserMessageWithFilesAdded(userMessageContent, selectionsStr) // full last message: user message + CONTENTS of all files + const userMessageFullContent = userMessageContent const getLatestMessages = async () => { // replace last userMessage with userMessageFullContent (which contains all the files too) const thread = this.state.allThreads[threadId] @@ -1112,7 +1104,11 @@ We only need to do it for files that were edited since `from`, ie files between // add user's message to chat history const instructions = userMessage - const userMessageContent = await chat_userMessageContent(instructions, currSelns) // user message + names of files (NOT content) + const { chatMode } = this._settingsService.state.globalSettings + + const opts = chatMode !== 'normal' ? { type: 'references' } as const : { type: 'fullCode', voidModelService: this._voidModelService } as const + + const userMessageContent = await chat_userMessageContent(instructions, currSelns, opts) // user message + names of files (NOT content) const userHistoryElt: ChatMessage = { role: 'user', content: userMessageContent, displayContent: instructions, selections: currSelns, state: defaultMessageState } this._addMessageToThread(threadId, userHistoryElt) @@ -1166,7 +1162,7 @@ We only need to do it for files that were edited since `from`, ie files between // get history of all AI and user added files in conversation + store in reverse order (MRU) const prevUris = this._getAllSelections(threadId) - .map(s => s.fileURI) + .map(s => s.uri) .filter((uri, index, array) => array.findIndex(u => u.fsPath === uri.fsPath) === index) // O(n^2) but this is small .reverse() @@ -1407,12 +1403,9 @@ We only need to do it for files that were edited since `from`, ie files between this._setThreadState(this.state.currentThreadId, { stagingSelections: [{ type: 'File', - fileURI: model.uri, + uri: model.uri, language: model.getLanguageId(), - selectionStr: null, - range: null, state: { - isOpened: false, wasAddedAsCurrentFile: true } }] @@ -1523,31 +1516,31 @@ We only need to do it for files that were edited since `from`, ie files between } - closeCurrentStagingSelectionsInThread = () => { - const currThread = this.getCurrentThreadState() + // closeCurrentStagingSelectionsInThread = () => { + // const currThread = this.getCurrentThreadState() - // close all stagingSelections - const closedStagingSelections = currThread.stagingSelections.map(s => ({ ...s, state: { ...s.state, isOpened: false } })) + // // close all stagingSelections + // const closedStagingSelections = currThread.stagingSelections.map(s => ({ ...s, state: { ...s.state, isOpened: false } })) - const newThread = currThread - newThread.stagingSelections = closedStagingSelections + // const newThread = currThread + // newThread.stagingSelections = closedStagingSelections - this.setCurrentThreadState(newThread) + // this.setCurrentThreadState(newThread) - } + // } - closeCurrentStagingSelectionsInMessage: IChatThreadService['closeCurrentStagingSelectionsInMessage'] = ({ messageIdx }) => { - const currMessage = this.getCurrentMessageState(messageIdx) + // closeCurrentStagingSelectionsInMessage: IChatThreadService['closeCurrentStagingSelectionsInMessage'] = ({ messageIdx }) => { + // const currMessage = this.getCurrentMessageState(messageIdx) - // close all stagingSelections - const closedStagingSelections = currMessage.stagingSelections.map(s => ({ ...s, state: { ...s.state, isOpened: false } })) + // // close all stagingSelections + // const closedStagingSelections = currMessage.stagingSelections.map(s => ({ ...s, state: { ...s.state, isOpened: false } })) - const newMessage = currMessage - newMessage.stagingSelections = closedStagingSelections + // const newMessage = currMessage + // newMessage.stagingSelections = closedStagingSelections - this.setCurrentMessageState(messageIdx, newMessage) + // this.setCurrentMessageState(messageIdx, newMessage) - } + // } diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx index 204aa9e7..be8e7e57 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx @@ -507,18 +507,16 @@ export const SelectedFiles = ( useEffect(() => { const computeRecents = async () => { const prospectiveURIs = recentUris - .filter(uri => !selections.find(s => s.type === 'File' && s.fileURI.fsPath === uri.fsPath)) + .filter(uri => !selections.find(s => s.type === 'File' && s.uri.fsPath === uri.fsPath)) .slice(0, maxProspectiveFiles) const answer: StagingSelectionItem[] = [] for (const uri of prospectiveURIs) { answer.push({ type: 'File', - fileURI: uri, + uri: uri, language: (await modelReferenceService.getModelSafe(uri)).model?.getLanguageId() || 'plaintext', - selectionStr: null, - range: null, - state: { isOpened: false, wasAddedAsCurrentFile: false }, + state: { wasAddedAsCurrentFile: false }, }) } return answer @@ -545,19 +543,13 @@ export const SelectedFiles = ( {allSelections.map((selection, i) => { - const isThisSelectionOpened = (!!selection.selectionStr && selection.state.isOpened && type === 'staging') - const isThisSelectionAFile = selection.selectionStr === null const isThisSelectionProspective = i > selections.length - 1 - const isThisSelectionAddedAsCurrentFile = selection.state.wasAddedAsCurrentFile const thisKey = `${isThisSelectionProspective}-${i}-${selections.length}` return
{/* summarybox */}
{ - // const newS = [...s] - // newS[i] = !newS[i] - // return newS - // }); - + } + else if (selection.type === 'CodeSelection') { + commandService.executeCommand('vscode.open', selection.uri, { + preview: true, + // TODO!!! open in range + }); + } + else if (selection.type === 'Folder') { + // TODO!!! reveal in tree } }} > { // file name and range - getBasename(selection.fileURI.fsPath) - + (isThisSelectionAFile ? '' : ` (${selection.range.startLineNumber}-${selection.range.endLineNumber})`) + getBasename(selection.uri.fsPath) + + (selection.type === 'CodeSelection' ? ` (${selection.range[0]}-${selection.range[1]})` : '') } - {isThisSelectionAddedAsCurrentFile && messageIdx === undefined && currentURI?.fsPath === selection.fileURI.fsPath && + {selection.type === 'File' && selection.state.wasAddedAsCurrentFile && messageIdx === undefined && currentURI?.fsPath === selection.uri.fsPath ? {`(Current File)`} + : null } {type === 'staging' && !isThisSelectionProspective ? // X button @@ -642,27 +627,6 @@ export const SelectedFiles = ( : <> }
- - {/* code box */} - {isThisSelectionOpened ? -
{ - e.stopPropagation(); // don't focus input box - }} - > - -
- : <> - }
})} @@ -840,13 +804,13 @@ const UserMessageComponent = ({ chatMessage, messageIdx, isCommitted, _scrollToB const canInitialize = mode === 'edit' && textAreaRefState const shouldInitialize = _justEnabledEdit.current || _mustInitialize.current if (canInitialize && shouldInitialize) { - setStagingSelections((chatMessage.selections || []) - .map(s => { // quick hack so we dont have to do anything more - const sNew = s - sNew.state.wasAddedAsCurrentFile = false // wipe all "current file" info when the user first edits a message - return sNew + setStagingSelections( + (chatMessage.selections || []).map(s => { // quick hack so we dont have to do anything more + if (s.type === 'File') return { ...s, state: { ...s.state, wasAddedAsCurrentFile: false, } } + else return s }) ) + if (textAreaFnsRef.current) textAreaFnsRef.current.setValue(chatMessage.displayContent || '') @@ -896,7 +860,6 @@ const UserMessageComponent = ({ chatMessage, messageIdx, isCommitted, _scrollToB // update state setIsBeingEdited(false) chatThreadsService.setCurrentlyFocusedMessageIdx(undefined) - chatThreadsService.closeCurrentStagingSelectionsInMessage({ messageIdx }) // stream the edit const userMessage = textAreaRefState.value; @@ -2033,9 +1996,6 @@ export const SidebarChat = () => { const threadId = chatThreadsService.state.currentThreadId - // update state - chatThreadsService.closeCurrentStagingSelectionsInThread() // close all selections - // send message to LLM const userMessage = textAreaRef.current?.value ?? '' diff --git a/src/vs/workbench/contrib/void/browser/sidebarActions.ts b/src/vs/workbench/contrib/void/browser/sidebarActions.ts index b51f8198..ad453853 100644 --- a/src/vs/workbench/contrib/void/browser/sidebarActions.ts +++ b/src/vs/workbench/contrib/void/browser/sidebarActions.ts @@ -14,7 +14,6 @@ import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextke import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js'; import { IRange } from '../../../../editor/common/core/range.js'; -import { ITextModel } from '../../../../editor/common/model.js'; import { VOID_VIEW_ID } from './sidebarPane.js'; import { IMetricsService } from '../common/metricsService.js'; import { ISidebarStateService } from './sidebarStateService.js'; @@ -53,23 +52,41 @@ export const roundRangeToLines = (range: IRange | null | undefined, options: { e return newRange } -const getContentInRange = (model: ITextModel, range: IRange | null) => { - if (!range) - return null - const content = model.getValueInRange(range) - const trimmedContent = content - .replace(/^\s*\n/g, '') // trim pure whitespace lines from start - .replace(/\n\s*$/g, '') // trim pure whitespace lines from end - return trimmedContent -} +// const getContentInRange = (model: ITextModel, range: IRange | null) => { +// if (!range) +// return null +// const content = model.getValueInRange(range) +// const trimmedContent = content +// .replace(/^\s*\n/g, '') // trim pure whitespace lines from start +// .replace(/\n\s*$/g, '') // trim pure whitespace lines from end +// return trimmedContent +// } -const findMatchingStagingIndex = (currentSelections: StagingSelectionItem[] | undefined, newSelection: StagingSelectionItem) => { - return currentSelections?.findIndex(s => - s.fileURI.fsPath === newSelection.fileURI.fsPath - && s.range?.startLineNumber === newSelection.range?.startLineNumber - && s.range?.endLineNumber === newSelection.range?.endLineNumber - ) +const findStagingItemToReplace = (currentSelections: StagingSelectionItem[] | undefined, newSelection: StagingSelectionItem): [number, StagingSelectionItem] | null => { + if (!currentSelections) return null + + for (let i = 0; i < currentSelections.length; i += 1) { + const s = currentSelections[i] + + if (s.uri.fsPath !== newSelection.uri.fsPath) continue + + if (s.type === 'File' && newSelection.type === 'File') { + return [i, s] as const + } + if (s.type === 'CodeSelection' && newSelection.type === 'CodeSelection') { + if (s.uri.fsPath !== newSelection.uri.fsPath) continue + // if there's any collision return true + const [oldStart, oldEnd] = s.range + const [newStart, newEnd] = newSelection.range + if (oldStart !== newStart || oldEnd !== newEnd) continue + return [i, s] as const + } + if (s.type === 'Folder' && newSelection.type === 'Folder') { + return [i, s] as const + } + } + return null } const VOID_OPEN_SIDEBAR_ACTION_ID = 'void.sidebar.open' @@ -114,22 +131,18 @@ registerAction2(class extends Action2 { editor?.setSelection({ startLineNumber: selectionRange.startLineNumber, endLineNumber: selectionRange.endLineNumber, startColumn: 1, endColumn: Number.MAX_SAFE_INTEGER }) } - const selectionStr = getContentInRange(model, selectionRange) - const selection: StagingSelectionItem = !selectionRange || !selectionStr || (selectionRange.startLineNumber > selectionRange.endLineNumber) ? { + const selection: StagingSelectionItem = !selectionRange || (selectionRange.startLineNumber > selectionRange.endLineNumber) ? { type: 'File', - fileURI: model.uri, + uri: model.uri, language: model.getLanguageId(), - selectionStr: null, - range: null, - state: { isOpened: false, wasAddedAsCurrentFile: false } + state: { wasAddedAsCurrentFile: false } } : { - type: 'Selection', - fileURI: model.uri, + type: 'CodeSelection', + uri: model.uri, language: model.getLanguageId(), - selectionStr: selectionStr, - range: selectionRange, - state: { isOpened: true, wasAddedAsCurrentFile: false } + range: [selectionRange.startLineNumber, selectionRange.endLineNumber], + state: { wasAddedAsCurrentFile: false } } // update the staging selections @@ -149,17 +162,18 @@ registerAction2(class extends Action2 { setSelections = (s) => chatThreadService.setCurrentMessageState(focusedMessageIdx, { stagingSelections: s }) } - // close all selections besides the new one - selections = selections.map(s => ({ ...s, state: { ...s.state, isOpened: false } })) - // if matches with existing selection, overwrite (since text may change) - const matchingStagingEltIdx = findMatchingStagingIndex(selections, selection) - if (matchingStagingEltIdx !== undefined && matchingStagingEltIdx !== -1) { - setSelections([ - ...selections!.slice(0, matchingStagingEltIdx), - selection, - ...selections!.slice(matchingStagingEltIdx + 1, Infinity) - ]) + const replaceRes = findStagingItemToReplace(selections, selection) + if (replaceRes) { + const [idx, newSel] = replaceRes + + if (idx !== undefined && idx !== -1) { + setSelections([ + ...selections!.slice(0, idx), + newSel, + ...selections!.slice(idx + 1, Infinity) + ]) + } } // if no match, add it else { diff --git a/src/vs/workbench/contrib/void/common/chatThreadServiceTypes.ts b/src/vs/workbench/contrib/void/common/chatThreadServiceTypes.ts index b95bc67d..0c9cbebb 100644 --- a/src/vs/workbench/contrib/void/common/chatThreadServiceTypes.ts +++ b/src/vs/workbench/contrib/void/common/chatThreadServiceTypes.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------*/ import { URI } from '../../../../base/common/uri.js'; -import { IRange } from '../../../../editor/common/core/range.js'; import { VoidFileSnapshot } from './editCodeServiceTypes.js'; import { AnthropicReasoning } from './sendLLMMessageTypes.js'; import { ToolName, ToolCallParams, ToolResultType } from './toolsServiceTypes.js'; @@ -66,34 +65,25 @@ export type ChatMessage = | CheckpointEntry -// one of the square items that indicates a selection in a chat bubble (NOT a file, a Selection of text) -export type CodeSelection = { - type: 'Selection'; - fileURI: URI; - language: string; - selectionStr: string; - range: IRange; - state: { - isOpened: boolean; - wasAddedAsCurrentFile: boolean; - }; -} - -export type FileSelection = { +// one of the square items that indicates a selection in a chat bubble +export type StagingSelectionItem = { type: 'File'; - fileURI: URI; + uri: URI; language: string; - selectionStr: null; - range: null; - state: { - isOpened: boolean; - wasAddedAsCurrentFile: boolean; - }; + state: { wasAddedAsCurrentFile: boolean; }; +} | { + type: 'CodeSelection'; + range: [number, number]; + uri: URI; + language: string; + state: { wasAddedAsCurrentFile: boolean; }; +} | { + type: 'Folder'; + uri: URI; + language?: undefined; + state?: undefined; } -export type StagingSelectionItem = CodeSelection | FileSelection - - // a link to a symbol (an underlined link to a piece of code) export type CodespanLocationLink = { diff --git a/src/vs/workbench/contrib/void/common/prompt/prompts.ts b/src/vs/workbench/contrib/void/common/prompt/prompts.ts index 8724c51b..1cfa7c4e 100644 --- a/src/vs/workbench/contrib/void/common/prompt/prompts.ts +++ b/src/vs/workbench/contrib/void/common/prompt/prompts.ts @@ -3,13 +3,12 @@ * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information. *--------------------------------------------------------------------------------------*/ -import { URI } from '../../../../../base/common/uri.js'; import { os } from '../helpers/systemInfo.js'; -import { CodeSelection, FileSelection, StagingSelectionItem } from '../chatThreadServiceTypes.js'; +import { StagingSelectionItem } from '../chatThreadServiceTypes.js'; import { ChatMode } from '../voidSettingsTypes.js'; +import { InternalToolInfo } from '../toolsServiceTypes.js'; import { IVoidModelService } from '../voidModelService.js'; import { EndOfLinePreference } from '../../../../../editor/common/model.js'; -import { InternalToolInfo } from '../toolsServiceTypes.js'; // this is just for ease of readability export const tripleTick = ['```', '```'] @@ -232,96 +231,113 @@ The user's codebase is structured as follows:\n${directoryStr} // - If you wrote triple ticks and ___, then include the file's full path in the first line of the triple ticks. This is only for display purposes to the user, and it's preferred but optional. Never do this in a tool parameter, or if there's ambiguity about the full path. -type FileSelnLocal = { fileURI: URI, language: string, content: string } -const stringifyFileSelection = ({ fileURI, language, content }: FileSelnLocal) => { - return `\ -${fileURI.fsPath} -${tripleTick[0]}${language} -${content} -${tripleTick[1]} -` -} -const stringifyCodeSelection = ({ fileURI, language, selectionStr, range }: CodeSelection) => { - return `\ -${fileURI.fsPath} (lines ${range.startLineNumber}:${range.endLineNumber}) -${tripleTick[0]}${language} -${selectionStr} -${tripleTick[1]} -` -} +// type FileSelnLocal = { fileURI: URI, language: string, content: string } +// const stringifyFileSelection = ({ fileURI, language, content }: FileSelnLocal) => { +// return `\ +// ${fileURI.fsPath} +// ${tripleTick[0]}${language} +// ${content} +// ${tripleTick[1]} +// ` +// } +// const stringifyCodeSelection = ({ uri, language, range }: StagingSelectionItem & { type: 'CodeSelection' }) => { +// return `\ -const failToReadStr = 'Could not read content. This file may have been deleted. If you expected content here, you can tell the user about this as they might not know.' -const stringifyFileSelections = async (fileSelections: FileSelection[], voidModelService: IVoidModelService) => { - if (fileSelections.length === 0) return null - const fileSlns: FileSelnLocal[] = await Promise.all(fileSelections.map(async (sel) => { - const { model } = await voidModelService.getModelSafe(sel.fileURI) - const content = model?.getValue(EndOfLinePreference.LF) ?? failToReadStr - return { ...sel, content } - })) - return fileSlns.map(sel => stringifyFileSelection(sel)).join('\n') -} +// ${tripleTick[0]}${language} +// ${selectionStr} +// ${tripleTick[1]} +// ` +// } + +// const failToReadStr = 'Could not read content. This file may have been deleted. If you expected content here, you can tell the user about this as they might not know.' +// const stringifyFileSelections = async (fileSelections: FileSelection[], voidModelService: IVoidModelService) => { +// if (fileSelections.length === 0) return null +// const fileSlns: FileSelnLocal[] = await Promise.all(fileSelections.map(async (sel) => { +// const { model } = await voidModelService.getModelSafe(sel.fileURI) +// const content = model?.getValue(EndOfLinePreference.LF) ?? failToReadStr +// return { ...sel, content } +// })) +// return fileSlns.map(sel => stringifyFileSelection(sel)).join('\n') +// } -const stringifyCodeSelections = (codeSelections: CodeSelection[]) => { - return codeSelections.map(sel => { - stringifyCodeSelection(sel) - }).join('\n') || null -} - -const stringifySelectionNames = (currSelns: StagingSelectionItem[] | null): string => { - if (!currSelns) return '' - return currSelns.map(s => `${s.fileURI.fsPath}${s.range ? ` (lines ${s.range.startLineNumber}:${s.range.endLineNumber})` : ''}`).join('\n') -} -export const chat_userMessageContent = async (instructions: string, currSelns: StagingSelectionItem[] | null) => { +// export const chat_selectionsString = async ( +// prevSelns: StagingSelectionItem[] | null, currSelns: StagingSelectionItem[] | null, +// voidModelService: IVoidModelService, +// ) => { - const selnsStr = stringifySelectionNames(currSelns) +// // ADD IN FILES AT TOP +// const allSelections = [...currSelns || [], ...prevSelns || []] - let str = '' - if (selnsStr) { str += `SELECTIONS\n${selnsStr}\n` } - str += `\nINSTRUCTIONS\n${instructions}` - return str; -}; +// if (allSelections.length === 0) return null -export const chat_selectionsString = async ( - prevSelns: StagingSelectionItem[] | null, currSelns: StagingSelectionItem[] | null, - voidModelService: IVoidModelService, +// for (const selection of allSelections) { +// if (selection.type === 'Selection') { +// codeSelections.push(selection) +// } +// else if (selection.type === 'File') { +// const fileSelection = selection +// const path = fileSelection.fileURI.fsPath +// if (!filesURIs.has(path)) { +// filesURIs.add(path) +// fileSelections.push(fileSelection) +// } +// } +// } + +// const filesStr = await stringifyFileSelections(fileSelections, voidModelService) +// const selnsStr = stringifyCodeSelections(codeSelections) + +// const fileContents = [filesStr, selnsStr].filter(Boolean).join('\n') +// return fileContents || null +// } + +// export const chat_lastUserMessageWithFilesAdded = (userMessage: string, selectionsString: string | null) => { +// if (userMessage) return `${userMessage}${selectionsString ? `\n${selectionsString}` : ''}` +// else return userMessage +// } + +export const chat_userMessageContent = async (instructions: string, currSelns: StagingSelectionItem[] | null, + opts: { type: 'references' } | { type: 'fullCode', voidModelService: IVoidModelService } ) => { - // ADD IN FILES AT TOP - const allSelections = [...currSelns || [], ...prevSelns || []] + const lineNumAddition = (range: [number, number]) => ` (lines ${range[0]}:${range[1]})` + let selnsStrs: string[] = [] + if (opts.type === 'references') { + selnsStrs = currSelns?.map((s) => { + if (s.type === 'File') return `${s.uri.fsPath}` + if (s.type === 'CodeSelection') return `${s.uri.fsPath}${lineNumAddition(s.range)}` + if (s.type === 'Folder') return `${s.uri.fsPath}/` + return '' + }) ?? [] + } + if (opts.type === 'fullCode') { + selnsStrs = await Promise.all(currSelns?.map(async (s) => { + if (s.type === 'File' || s.type === 'CodeSelection') { + const voidModelService = opts.voidModelService + const { model } = await voidModelService.getModelSafe(s.uri) + if (!model) return '' + const val = model.getValue(EndOfLinePreference.LF) - if (allSelections.length === 0) return null - - const codeSelections: CodeSelection[] = [] - const fileSelections: FileSelection[] = [] - const filesURIs = new Set() - - for (const selection of allSelections) { - if (selection.type === 'Selection') { - codeSelections.push(selection) - } - else if (selection.type === 'File') { - const fileSelection = selection - const path = fileSelection.fileURI.fsPath - if (!filesURIs.has(path)) { - filesURIs.add(path) - fileSelections.push(fileSelection) + const lineNumAdd = s.type === 'CodeSelection' ? lineNumAddition(s.range) : '' + const str = `${s.uri.fsPath}${lineNumAdd}\n${tripleTick[0]}${s.language}\n${val}\n${tripleTick[1]}` + return str } - } + if (s.type === 'Folder') { + // TODO + return '' + } + return '' + }) ?? []) } - const filesStr = await stringifyFileSelections(fileSelections, voidModelService) - const selnsStr = stringifyCodeSelections(codeSelections) - - const fileContents = [filesStr, selnsStr].filter(Boolean).join('\n') - return fileContents || null -} - -export const chat_lastUserMessageWithFilesAdded = (userMessage: string, selectionsString: string | null) => { - if (userMessage) return `${userMessage}${selectionsString ? `\n${selectionsString}` : ''}` - else return userMessage + const selnsStr = selnsStrs.join('\n') ?? '' + let str = '' + str += `${instructions}` + if (selnsStr) str += `\n---\nSELECTIONS\n${selnsStr}` + return str; }