fix: manually attached files should take precedence over suggested files (#234291)

This commit is contained in:
Joyce Er 2024-11-20 14:22:45 -05:00 коммит произвёл GitHub
Родитель 543d860c85
Коммит b958720bd5
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
6 изменённых файлов: 38 добавлений и 18 удалений

Просмотреть файл

@ -22,7 +22,7 @@ import { GroupsOrder, IEditorGroupsService } from '../../../../services/editor/c
import { IEditorService } from '../../../../services/editor/common/editorService.js'; import { IEditorService } from '../../../../services/editor/common/editorService.js';
import { ChatAgentLocation } from '../../common/chatAgents.js'; import { ChatAgentLocation } from '../../common/chatAgents.js';
import { ChatContextKeys } from '../../common/chatContextKeys.js'; import { ChatContextKeys } from '../../common/chatContextKeys.js';
import { applyingChatEditsFailedContextKey, CHAT_EDITING_MULTI_DIFF_SOURCE_RESOLVER_SCHEME, chatEditingResourceContextKey, chatEditingWidgetFileStateContextKey, decidedChatEditingResourceContextKey, hasAppliedChatEditsContextKey, hasUndecidedChatEditingResourceContextKey, IChatEditingService, IChatEditingSession, WorkingSetEntryState } from '../../common/chatEditingService.js'; import { applyingChatEditsFailedContextKey, CHAT_EDITING_MULTI_DIFF_SOURCE_RESOLVER_SCHEME, chatEditingResourceContextKey, chatEditingWidgetFileStateContextKey, decidedChatEditingResourceContextKey, hasAppliedChatEditsContextKey, hasUndecidedChatEditingResourceContextKey, IChatEditingService, IChatEditingSession, WorkingSetEntryRemovalReason, WorkingSetEntryState } from '../../common/chatEditingService.js';
import { IChatService } from '../../common/chatService.js'; import { IChatService } from '../../common/chatService.js';
import { isRequestVM, isResponseVM } from '../../common/chatViewModel.js'; import { isRequestVM, isResponseVM } from '../../common/chatViewModel.js';
import { CHAT_CATEGORY } from '../actions/chatActions.js'; import { CHAT_CATEGORY } from '../actions/chatActions.js';
@ -96,7 +96,7 @@ registerAction2(class RemoveFileFromWorkingSet extends WorkingSetAction {
async runWorkingSetAction(accessor: ServicesAccessor, currentEditingSession: IChatEditingSession, chatWidget: IChatWidget, ...uris: URI[]): Promise<void> { async runWorkingSetAction(accessor: ServicesAccessor, currentEditingSession: IChatEditingSession, chatWidget: IChatWidget, ...uris: URI[]): Promise<void> {
// Remove from working set // Remove from working set
currentEditingSession.remove(...uris); currentEditingSession.remove(WorkingSetEntryRemovalReason.User, ...uris);
// Remove from chat input part // Remove from chat input part
const resourceSet = new ResourceSet(uris); const resourceSet = new ResourceSet(uris);

Просмотреть файл

@ -30,7 +30,7 @@ import { IEditorService } from '../../../../services/editor/common/editorService
import { MultiDiffEditor } from '../../../multiDiffEditor/browser/multiDiffEditor.js'; import { MultiDiffEditor } from '../../../multiDiffEditor/browser/multiDiffEditor.js';
import { MultiDiffEditorInput } from '../../../multiDiffEditor/browser/multiDiffEditorInput.js'; import { MultiDiffEditorInput } from '../../../multiDiffEditor/browser/multiDiffEditorInput.js';
import { ChatAgentLocation, IChatAgentService } from '../../common/chatAgents.js'; import { ChatAgentLocation, IChatAgentService } from '../../common/chatAgents.js';
import { ChatEditingSessionChangeType, ChatEditingSessionState, ChatEditKind, IChatEditingSession, WorkingSetDisplayMetadata, WorkingSetEntryState } from '../../common/chatEditingService.js'; import { ChatEditingSessionChangeType, ChatEditingSessionState, ChatEditKind, IChatEditingSession, WorkingSetDisplayMetadata, WorkingSetEntryRemovalReason, WorkingSetEntryState } from '../../common/chatEditingService.js';
import { IChatResponseModel } from '../../common/chatModel.js'; import { IChatResponseModel } from '../../common/chatModel.js';
import { IChatWidgetService } from '../chat.js'; import { IChatWidgetService } from '../chat.js';
import { ChatEditingMultiDiffSourceResolver } from './chatEditingService.js'; import { ChatEditingMultiDiffSourceResolver } from './chatEditingService.js';
@ -328,7 +328,7 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio
this._entriesObs.set(entriesArr, undefined); this._entriesObs.set(entriesArr, undefined);
} }
remove(...uris: URI[]): void { remove(reason: WorkingSetEntryRemovalReason, ...uris: URI[]): void {
this._assertNotDisposed(); this._assertNotDisposed();
let didRemoveUris = false; let didRemoveUris = false;
@ -338,7 +338,7 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio
continue; continue;
} }
didRemoveUris = this._workingSet.delete(uri) || didRemoveUris; didRemoveUris = this._workingSet.delete(uri) || didRemoveUris;
if (state.state === WorkingSetEntryState.Transient || state.state === WorkingSetEntryState.Suggested) { if (reason === WorkingSetEntryRemovalReason.User && (state.state === WorkingSetEntryState.Transient || state.state === WorkingSetEntryState.Suggested)) {
this._removedTransientEntries.add(uri); this._removedTransientEntries.add(uri);
} }
} }

Просмотреть файл

@ -1085,22 +1085,36 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
// The user tried to attach too many files, we have to drop anything after the limit // The user tried to attach too many files, we have to drop anything after the limit
const entriesToPreserve: IChatCollapsibleListItem[] = []; const entriesToPreserve: IChatCollapsibleListItem[] = [];
const newEntries: IChatCollapsibleListItem[] = []; const newEntries: IChatCollapsibleListItem[] = [];
const suggestedFiles: IChatCollapsibleListItem[] = [];
for (let i = 0; i < entries.length; i += 1) { for (let i = 0; i < entries.length; i += 1) {
const entry = entries[i]; const entry = entries[i];
// If this entry was here earlier and is still here, we should prioritize preserving it if (entry.kind !== 'reference' || !URI.isUri(entry.reference)) {
// so that nothing existing gets evicted continue;
const currentEntryUri = entry.kind === 'reference' && URI.isUri(entry.reference) ? entry.reference : undefined; }
if (this._combinedChatEditWorkingSetEntries.find((e) => e.toString() === currentEntryUri?.toString()) && remainingFileEntriesBudget > 0) { const currentEntryUri = entry.reference;
entriesToPreserve.push(entry); if (entry.state === WorkingSetEntryState.Suggested) {
remainingFileEntriesBudget -= 1; // Keep track of suggested files for now, they should not take precedence over newly added files
suggestedFiles.push(entry);
} else if (this._combinedChatEditWorkingSetEntries.find((e) => e.toString() === currentEntryUri?.toString())) {
// If this entry was here earlier and is still here, we should prioritize preserving it
// so that nothing existing gets evicted
if (remainingFileEntriesBudget > 0) {
entriesToPreserve.push(entry);
remainingFileEntriesBudget -= 1;
}
} else { } else {
newEntries.push(entry); newEntries.push(entry);
} }
} }
const newEntriesThatFit = newEntries.slice(0, remainingFileEntriesBudget); const newEntriesThatFit = remainingFileEntriesBudget > 0 ? newEntries.slice(0, remainingFileEntriesBudget) : [];
entries = [...entriesToPreserve, ...newEntriesThatFit];
remainingFileEntriesBudget -= newEntriesThatFit.length; remainingFileEntriesBudget -= newEntriesThatFit.length;
const suggestedFilesThatFit = remainingFileEntriesBudget > 0 ? suggestedFiles.slice(0, remainingFileEntriesBudget) : [];
// Intentional: to make bad suggestions less annoying,
// here we don't count the suggested files against the budget,
// so that the Add Files button remains enabled and the user can easily
// override the suggestions with their own manual file selections
entries = [...entriesToPreserve, ...newEntriesThatFit, ...suggestedFilesThatFit];
} }
if (entries.length > 1) { if (entries.length > 1) {
overviewText.textContent += ' ' + localize('chatEditingSession.manyFiles', '({0} files)', entries.length); overviewText.textContent += ' ' + localize('chatEditingSession.manyFiles', '({0} files)', entries.length);
@ -1184,6 +1198,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
supportIcons: true, supportIcons: true,
secondary: true secondary: true
})); }));
// Disable the button if the entries that are not suggested exceed the budget
button.enabled = remainingFileEntriesBudget > 0; button.enabled = remainingFileEntriesBudget > 0;
button.label = localize('chatAddFiles', '{0} Add Files...', '$(add)'); button.label = localize('chatAddFiles', '{0} Add Files...', '$(add)');
button.setTitle(button.enabled ? localize('addFiles.label', 'Add files to your working set') : localize('addFilesDisabled.label', 'You have reached the maximum number of files that can be added to the working set.')); button.setTitle(button.enabled ? localize('addFiles.label', 'Add files to your working set') : localize('addFilesDisabled.label', 'You have reached the maximum number of files that can be added to the working set.'));

Просмотреть файл

@ -35,7 +35,7 @@ import { asCssVariable } from '../../../../platform/theme/common/colorUtils.js';
import { IThemeService } from '../../../../platform/theme/common/themeService.js'; import { IThemeService } from '../../../../platform/theme/common/themeService.js';
import { ChatAgentLocation, IChatAgentCommand, IChatAgentData, IChatAgentService, IChatWelcomeMessageContent, isChatWelcomeMessageContent } from '../common/chatAgents.js'; import { ChatAgentLocation, IChatAgentCommand, IChatAgentData, IChatAgentService, IChatWelcomeMessageContent, isChatWelcomeMessageContent } from '../common/chatAgents.js';
import { ChatContextKeys } from '../common/chatContextKeys.js'; import { ChatContextKeys } from '../common/chatContextKeys.js';
import { IChatEditingService, IChatEditingSession, WorkingSetEntryState } from '../common/chatEditingService.js'; import { IChatEditingService, IChatEditingSession, WorkingSetEntryRemovalReason, WorkingSetEntryState } from '../common/chatEditingService.js';
import { IChatModel, IChatRequestVariableEntry, IChatResponseModel } from '../common/chatModel.js'; import { IChatModel, IChatRequestVariableEntry, IChatResponseModel } from '../common/chatModel.js';
import { ChatRequestAgentPart, IParsedChatRequest, chatAgentLeader, chatSubcommandLeader, formatChatQuestion } from '../common/chatParserTypes.js'; import { ChatRequestAgentPart, IParsedChatRequest, chatAgentLeader, chatSubcommandLeader, formatChatQuestion } from '../common/chatParserTypes.js';
import { ChatRequestParser } from '../common/chatRequestParser.js'; import { ChatRequestParser } from '../common/chatRequestParser.js';
@ -1052,7 +1052,7 @@ export class ChatWidget extends Disposable implements IChatWidget {
actualSize: number; actualSize: number;
}; };
this.telemetryService.publicLog2<ChatEditingWorkingSetEvent, ChatEditingWorkingSetClassification>('chatEditing/workingSetSize', { originalSize: this.inputPart.attemptedWorkingSetEntriesCount, actualSize: uniqueWorkingSetEntries.size }); this.telemetryService.publicLog2<ChatEditingWorkingSetEvent, ChatEditingWorkingSetClassification>('chatEditing/workingSetSize', { originalSize: this.inputPart.attemptedWorkingSetEntriesCount, actualSize: uniqueWorkingSetEntries.size });
currentEditingSession?.remove(...unconfirmedSuggestions); currentEditingSession?.remove(WorkingSetEntryRemovalReason.Programmatic, ...unconfirmedSuggestions);
} }
const result = await this.chatService.sendRequest(this.viewModel.sessionId, input, { const result = await this.chatService.sendRequest(this.viewModel.sessionId, input, {

Просмотреть файл

@ -10,7 +10,7 @@ import { ResourceSet } from '../../../../../base/common/map.js';
import { URI } from '../../../../../base/common/uri.js'; import { URI } from '../../../../../base/common/uri.js';
import { localize } from '../../../../../nls.js'; import { localize } from '../../../../../nls.js';
import { IWorkbenchContribution } from '../../../../common/contributions.js'; import { IWorkbenchContribution } from '../../../../common/contributions.js';
import { ChatEditingSessionChangeType, IChatEditingService, WorkingSetEntryState } from '../../common/chatEditingService.js'; import { ChatEditingSessionChangeType, IChatEditingService, WorkingSetEntryRemovalReason, WorkingSetEntryState } from '../../common/chatEditingService.js';
import { IChatWidgetService } from '../chat.js'; import { IChatWidgetService } from '../chat.js';
export class ChatRelatedFilesContribution extends Disposable implements IWorkbenchContribution { export class ChatRelatedFilesContribution extends Disposable implements IWorkbenchContribution {
@ -80,7 +80,7 @@ export class ChatRelatedFilesContribution extends Disposable implements IWorkben
existingSuggestedEntriesToRemove.push(entry[0]); existingSuggestedEntriesToRemove.push(entry[0]);
} }
} }
currentEditingSession?.remove(...existingSuggestedEntriesToRemove); currentEditingSession?.remove(WorkingSetEntryRemovalReason.Programmatic, ...existingSuggestedEntriesToRemove);
// Add the new related file suggestions to the working set // Add the new related file suggestions to the working set
for (const file of newSuggestions) { for (const file of newSuggestions) {

Просмотреть файл

@ -79,7 +79,7 @@ export interface IChatEditingSession {
readonly isVisible: boolean; readonly isVisible: boolean;
addFileToWorkingSet(uri: URI, description?: string, kind?: WorkingSetEntryState.Transient | WorkingSetEntryState.Suggested): void; addFileToWorkingSet(uri: URI, description?: string, kind?: WorkingSetEntryState.Transient | WorkingSetEntryState.Suggested): void;
show(): Promise<void>; show(): Promise<void>;
remove(...uris: URI[]): void; remove(reason: WorkingSetEntryRemovalReason, ...uris: URI[]): void;
accept(...uris: URI[]): Promise<void>; accept(...uris: URI[]): Promise<void>;
reject(...uris: URI[]): Promise<void>; reject(...uris: URI[]): Promise<void>;
/** /**
@ -91,6 +91,11 @@ export interface IChatEditingSession {
redoInteraction(): Promise<void>; redoInteraction(): Promise<void>;
} }
export const enum WorkingSetEntryRemovalReason {
User,
Programmatic
}
export const enum WorkingSetEntryState { export const enum WorkingSetEntryState {
Modified, Modified,
Accepted, Accepted,