make chats by reference

This commit is contained in:
Andrew Pareles 2025-04-05 20:40:59 -07:00
parent b440c8732f
commit 1d9e0faaa3
5 changed files with 220 additions and 247 deletions

View file

@ -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<ToolName>
}) {
// 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)
}
// }

View file

@ -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 <div // container for summarybox and code
key={thisKey}
className={`
flex flex-col space-y-[1px]
${isThisSelectionOpened ? 'w-full' : ''}
`}
className={`flex flex-col space-y-[1px]`}
>
{/* summarybox */}
<div
@ -571,9 +563,7 @@ export const SelectedFiles = (
${isThisSelectionProspective ? 'bg-void-bg-1 text-void-fg-3 opacity-80' : 'bg-void-bg-3 hover:brightness-95 text-void-fg-1'}
${isThisSelectionProspective
? 'border-void-border-2'
: isThisSelectionOpened
? 'border-void-border-1 ring-1 ring-void-blue'
: 'border-void-border-1'
: 'border-void-border-1'
}
hover:border-void-border-1
transition-all duration-150
@ -582,14 +572,16 @@ export const SelectedFiles = (
if (type !== 'staging') return; // (never)
if (isThisSelectionProspective) { // add prospective selection to selections
setSelections([...selections, selection])
} else if (isThisSelectionAFile) { // open files
}
else if (selection.type === 'File') { // open files
commandService.executeCommand('vscode.open', selection.fileURI, {
commandService.executeCommand('vscode.open', selection.uri, {
preview: true,
// preserveFocus: false,
});
if (isThisSelectionAddedAsCurrentFile) {
const wasAddedAsCurrentFile = selection.state.wasAddedAsCurrentFile
if (wasAddedAsCurrentFile) {
// make it so the file is added permanently, not just as the current file
const newSelection: StagingSelectionItem = { ...selection, state: { ...selection.state, wasAddedAsCurrentFile: false } }
setSelections([
@ -598,35 +590,28 @@ export const SelectedFiles = (
...selections.slice(i + 1)
])
}
} else { // show text
const selection = selections[i]
const newSelection = { ...selection, state: { ...selection.state, isOpened: !selection.state.isOpened } }
const newSelections = [
...selections.slice(0, i),
newSelection,
...selections.slice(i + 1)
]
setSelections(newSelections)
// setSelectionIsOpened(s => {
// 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 ?
<span className={`text-[8px] ml-0.5 'void-opacity-60 text-void-fg-4`}>
{`(Current File)`}
</span>
: null
}
{type === 'staging' && !isThisSelectionProspective ? // X button
@ -642,27 +627,6 @@ export const SelectedFiles = (
: <></>
}
</div>
{/* code box */}
{isThisSelectionOpened ?
<div
className={`
w-full rounded-sm border-vscode-editor-border
${isThisSelectionOpened ? 'ring-1 ring-void-blue' : ''}
`}
onClick={(e) => {
e.stopPropagation(); // don't focus input box
}}
>
<BlockCode
initValue={selection.selectionStr}
language={selection.language}
maxHeight={200}
showScrollbars={true}
/>
</div>
: <></>
}
</div>
})}
@ -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 ?? ''

View file

@ -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 {

View file

@ -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 = {

View file

@ -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<string>()
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;
}