mirror of
https://github.com/voideditor/void
synced 2026-05-24 09:58:23 +00:00
make chats by reference
This commit is contained in:
parent
b440c8732f
commit
1d9e0faaa3
5 changed files with 220 additions and 247 deletions
|
|
@ -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)
|
||||
|
||||
}
|
||||
// }
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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 ?? ''
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue