Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/chat/browser/chatAttachmentModel.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { URI } from '../../../../base/common/uri.js';
7
import { Emitter } from '../../../../base/common/event.js';
8
import { basename } from '../../../../base/common/resources.js';
9
import { IRange } from '../../../../editor/common/core/range.js';
10
import { Disposable } from '../../../../base/common/lifecycle.js';
11
import { IChatRequestFileEntry, IChatRequestVariableEntry, isPromptFileVariableEntry } from '../common/chatVariableEntries.js';
12
import { IFileService } from '../../../../platform/files/common/files.js';
13
import { ISharedWebContentExtractorService } from '../../../../platform/webContentExtractor/common/webContentExtractor.js';
14
import { Schemas } from '../../../../base/common/network.js';
15
import { IChatAttachmentResolveService } from './chatAttachmentResolveService.js';
16
import { CancellationToken } from '../../../../base/common/cancellation.js';
17
import { equals } from '../../../../base/common/objects.js';
18
import { Iterable } from '../../../../base/common/iterator.js';
19
20
export interface IChatAttachmentChangeEvent {
21
readonly deleted: readonly string[];
22
readonly added: readonly IChatRequestVariableEntry[];
23
readonly updated: readonly IChatRequestVariableEntry[];
24
}
25
26
export class ChatAttachmentModel extends Disposable {
27
28
private readonly _attachments = new Map<string, IChatRequestVariableEntry>();
29
30
private _onDidChange = this._register(new Emitter<IChatAttachmentChangeEvent>());
31
readonly onDidChange = this._onDidChange.event;
32
33
constructor(
34
@IFileService private readonly fileService: IFileService,
35
@ISharedWebContentExtractorService private readonly webContentExtractorService: ISharedWebContentExtractorService,
36
@IChatAttachmentResolveService private readonly chatAttachmentResolveService: IChatAttachmentResolveService
37
) {
38
super();
39
}
40
41
get attachments(): ReadonlyArray<IChatRequestVariableEntry> {
42
return Array.from(this._attachments.values());
43
}
44
45
get size(): number {
46
return this._attachments.size;
47
}
48
49
get fileAttachments(): URI[] {
50
return this.attachments.filter(file => file.kind === 'file' && URI.isUri(file.value))
51
.map(file => file.value as URI);
52
}
53
54
getAttachmentIDs() {
55
return new Set(this._attachments.keys());
56
}
57
58
async addFile(uri: URI, range?: IRange) {
59
if (/\.(png|jpe?g|gif|bmp|webp)$/i.test(uri.path)) {
60
const context = await this.asImageVariableEntry(uri);
61
if (context) {
62
this.addContext(context);
63
}
64
return;
65
} else {
66
this.addContext(this.asFileVariableEntry(uri, range));
67
}
68
}
69
70
addFolder(uri: URI) {
71
this.addContext({
72
kind: 'directory',
73
value: uri,
74
id: uri.toString(),
75
name: basename(uri),
76
});
77
}
78
79
clear(clearStickyAttachments: boolean = false): void {
80
if (clearStickyAttachments) {
81
const deleted = Array.from(this._attachments.keys());
82
this._attachments.clear();
83
this._onDidChange.fire({ deleted, added: [], updated: [] });
84
} else {
85
const deleted: string[] = [];
86
const allIds = Array.from(this._attachments.keys());
87
for (const id of allIds) {
88
const entry = this._attachments.get(id);
89
if (entry && !isPromptFileVariableEntry(entry)) {
90
this._attachments.delete(id);
91
deleted.push(id);
92
}
93
}
94
this._onDidChange.fire({ deleted, added: [], updated: [] });
95
}
96
}
97
98
addContext(...attachments: IChatRequestVariableEntry[]) {
99
attachments = attachments.filter(attachment => !this._attachments.has(attachment.id));
100
this.updateContext(Iterable.empty(), attachments);
101
}
102
103
clearAndSetContext(...attachments: IChatRequestVariableEntry[]) {
104
this.updateContext(Array.from(this._attachments.keys()), attachments);
105
}
106
107
delete(...variableEntryIds: string[]) {
108
this.updateContext(variableEntryIds, Iterable.empty());
109
}
110
111
updateContext(toDelete: Iterable<string>, upsert: Iterable<IChatRequestVariableEntry>) {
112
const deleted: string[] = [];
113
const added: IChatRequestVariableEntry[] = [];
114
const updated: IChatRequestVariableEntry[] = [];
115
116
for (const id of toDelete) {
117
const item = this._attachments.get(id);
118
if (item) {
119
this._attachments.delete(id);
120
deleted.push(id);
121
}
122
}
123
124
for (const item of upsert) {
125
const oldItem = this._attachments.get(item.id);
126
if (!oldItem) {
127
this._attachments.set(item.id, item);
128
added.push(item);
129
} else if (!equals(oldItem, item)) {
130
this._attachments.set(item.id, item);
131
updated.push(item);
132
}
133
}
134
135
if (deleted.length > 0 || added.length > 0 || updated.length > 0) {
136
this._onDidChange.fire({ deleted, added, updated });
137
}
138
}
139
140
// ---- create utils
141
142
asFileVariableEntry(uri: URI, range?: IRange): IChatRequestFileEntry {
143
return {
144
kind: 'file',
145
value: range ? { uri, range } : uri,
146
id: uri.toString() + (range?.toString() ?? ''),
147
name: basename(uri),
148
};
149
}
150
151
// Gets an image variable for a given URI, which may be a file or a web URL
152
async asImageVariableEntry(uri: URI): Promise<IChatRequestVariableEntry | undefined> {
153
if (uri.scheme === Schemas.file && await this.fileService.canHandleResource(uri)) {
154
return await this.chatAttachmentResolveService.resolveImageEditorAttachContext(uri);
155
} else if (uri.scheme === Schemas.http || uri.scheme === Schemas.https) {
156
const extractedImages = await this.webContentExtractorService.readImage(uri, CancellationToken.None);
157
if (extractedImages) {
158
return await this.chatAttachmentResolveService.resolveImageEditorAttachContext(uri, extractedImages);
159
}
160
}
161
162
return undefined;
163
}
164
165
}
166
167