|
3 | 3 | * Licensed under the MIT License. See License.txt in the project root for license information.
|
4 | 4 | *--------------------------------------------------------------------------------------------*/
|
5 | 5 |
|
6 |
| -import * as dom from 'vs/base/browser/dom'; |
7 |
| -import { Toggle } from 'vs/base/browser/ui/toggle/toggle'; |
8 |
| -import { CancellationToken } from 'vs/base/common/cancellation'; |
9 |
| -import { Codicon } from 'vs/base/common/codicons'; |
10 |
| -import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; |
11 |
| -import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; |
12 |
| -import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; |
13 |
| -import { localize } from 'vs/nls'; |
14 |
| -import { Emitter, Event } from 'vs/base/common/event'; |
15 |
| -import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; |
16 |
| -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; |
17 |
| -import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; |
18 |
| -import { IQuickInputService, IQuickPick, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; |
19 |
| -import { asCssVariable, editorBackground, foreground, inputActiveOptionBackground, inputActiveOptionBorder, inputActiveOptionForeground } from 'vs/platform/theme/common/colorRegistry'; |
20 |
| -import { ChatListItemRenderer } from 'vs/workbench/contrib/chat/browser/chatListRenderer'; |
21 |
| -import { ChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatOptions'; |
22 |
| -import { ChatModel } from 'vs/workbench/contrib/chat/common/chatModel'; |
23 |
| -import { IChatReplyFollowup, IChatService } from 'vs/workbench/contrib/chat/common/chatService'; |
24 |
| -import { ChatViewModel } from 'vs/workbench/contrib/chat/common/chatViewModel'; |
25 |
| -import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions'; |
26 |
| -import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; |
27 |
| -import { CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys'; |
| 6 | +import { registerAction2 } from 'vs/platform/actions/common/actions'; |
| 7 | +import { AskQuickQuestionAction } from 'vs/workbench/contrib/chat/browser/actions/quickQuestionActions/quickQuestionAction'; |
28 | 8 |
|
29 |
| -export const ASK_QUICK_QUESTION_ACTION_ID = 'chat.action.askQuickQuestion'; |
| 9 | +import 'vs/workbench/contrib/chat/browser/actions/quickQuestionActions/singleQuickQuestionAction'; |
| 10 | +import 'vs/workbench/contrib/chat/browser/actions/quickQuestionActions/multipleByScrollQuickQuestionAction'; |
30 | 11 |
|
31 | 12 | export function registerChatQuickQuestionActions() {
|
32 | 13 | registerAction2(AskQuickQuestionAction);
|
33 | 14 | }
|
34 |
| - |
35 |
| -class AskQuickQuestionAction extends Action2 { |
36 |
| - |
37 |
| - private _currentSession: InteractiveQuickPickSession | undefined; |
38 |
| - private _currentQuery: string | undefined; |
39 |
| - private _lastAcceptedQuery: string | undefined; |
40 |
| - private _currentTimer: any | undefined; |
41 |
| - private _input: IQuickPick<IQuickPickItem> | undefined; |
42 |
| - |
43 |
| - constructor() { |
44 |
| - super({ |
45 |
| - id: ASK_QUICK_QUESTION_ACTION_ID, |
46 |
| - title: { value: localize('askQuickQuestion', "Ask Quick Question"), original: 'Ask Quick Question' }, |
47 |
| - precondition: CONTEXT_PROVIDER_EXISTS, |
48 |
| - f1: false, |
49 |
| - category: CHAT_CATEGORY, |
50 |
| - keybinding: { |
51 |
| - weight: KeybindingWeight.WorkbenchContrib, |
52 |
| - primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyI, |
53 |
| - linux: { |
54 |
| - primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyCode.KeyI |
55 |
| - } |
56 |
| - } |
57 |
| - }); |
58 |
| - } |
59 |
| - |
60 |
| - run(accessor: ServicesAccessor, query: string): void { |
61 |
| - const quickInputService = accessor.get(IQuickInputService); |
62 |
| - const chatService = accessor.get(IChatService); |
63 |
| - const instantiationService = accessor.get(IInstantiationService); |
64 |
| - |
65 |
| - // First things first, clear the existing timer that will dispose the session |
66 |
| - clearTimeout(this._currentTimer); |
67 |
| - this._currentTimer = undefined; |
68 |
| - |
69 |
| - // If the input is already shown, hide it. This provides a toggle behavior of the quick pick |
70 |
| - if (this._input !== undefined) { |
71 |
| - this._input.hide(); |
72 |
| - return; |
73 |
| - } |
74 |
| - |
75 |
| - // Check if any providers are available. If not, show nothing |
76 |
| - const providerInfo = chatService.getProviderInfos()[0]; |
77 |
| - if (!providerInfo) { |
78 |
| - return; |
79 |
| - } |
80 |
| - |
81 |
| - const disposableStore = new DisposableStore(); |
82 |
| - |
83 |
| - //#region Setup quick pick |
84 |
| - |
85 |
| - this._input = quickInputService.createQuickPick(); |
86 |
| - disposableStore.add(this._input); |
87 |
| - this._input.placeholder = localize('askabot', "Ask {0} a question...", providerInfo.displayName); |
88 |
| - |
89 |
| - // Setup toggle that will be used to open the chat view |
90 |
| - const openInChat = new Toggle({ |
91 |
| - title: 'Open in chat view', |
92 |
| - icon: Codicon.commentDiscussion, |
93 |
| - isChecked: false, |
94 |
| - inputActiveOptionBorder: asCssVariable(inputActiveOptionBorder), |
95 |
| - inputActiveOptionForeground: asCssVariable(inputActiveOptionForeground), |
96 |
| - inputActiveOptionBackground: asCssVariable(inputActiveOptionBackground) |
97 |
| - }); |
98 |
| - disposableStore.add(openInChat); |
99 |
| - disposableStore.add(openInChat.onChange(async () => { |
100 |
| - await this._currentSession?.openInChat(this._lastAcceptedQuery ?? this._input!.value); |
101 |
| - this._currentQuery = undefined; |
102 |
| - this._lastAcceptedQuery = undefined; |
103 |
| - this._currentSession?.dispose(); |
104 |
| - this._currentSession = undefined; |
105 |
| - })); |
106 |
| - this._input.toggles = [openInChat]; |
107 |
| - |
108 |
| - // Setup the widget that will be used to render the chat response |
109 |
| - const containerList = dom.$('.interactive-list'); |
110 |
| - const containerSession = dom.$('.interactive-session', undefined, containerList); |
111 |
| - containerList.style.position = 'relative'; |
112 |
| - this._input.widget = containerSession; |
113 |
| - |
114 |
| - //#endregion |
115 |
| - |
116 |
| - //#region quick pick events |
117 |
| - |
118 |
| - disposableStore.add(this._input.onDidChangeValue((value) => { |
119 |
| - if (value !== this._currentQuery) { |
120 |
| - this._currentQuery = value; |
121 |
| - } |
122 |
| - })); |
123 |
| - disposableStore.add(this._input.onDidHide(() => { |
124 |
| - disposableStore.dispose(); |
125 |
| - this._input = undefined; |
126 |
| - this._currentTimer = setTimeout(() => { |
127 |
| - this._currentQuery = undefined; |
128 |
| - this._lastAcceptedQuery = undefined; |
129 |
| - this._currentSession?.dispose(); |
130 |
| - this._currentSession = undefined; |
131 |
| - }, 1000 * 30); // 30 seconds |
132 |
| - })); |
133 |
| - disposableStore.add(this._input.onDidAccept(async () => { |
134 |
| - await this._currentSession?.accept(this._input!.value); |
135 |
| - this._lastAcceptedQuery = this._input!.value; |
136 |
| - })); |
137 |
| - |
138 |
| - //#endregion |
139 |
| - |
140 |
| - // If we were given a query (via executeCommand), then clear past state |
141 |
| - if (query) { |
142 |
| - this._currentSession?.dispose(); |
143 |
| - this._currentSession = undefined; |
144 |
| - } |
145 |
| - this._currentSession ??= instantiationService.createInstance(InteractiveQuickPickSession); |
146 |
| - this._input.show(); |
147 |
| - // This line must come after showing the input so the offsetWidth is correct |
148 |
| - this._currentSession.createList(containerList, containerList.offsetWidth); |
149 |
| - |
150 |
| - disposableStore.add(this._currentSession.onDidClickFollowup(async e => { |
151 |
| - this._input!.focusOnInput(); |
152 |
| - this._input!.value = e.message; |
153 |
| - await this._currentSession?.accept(e.message); |
154 |
| - })); |
155 |
| - |
156 |
| - // If we were given a query (via executeCommand), then accept it |
157 |
| - if (query) { |
158 |
| - this._input.value = query; |
159 |
| - this._input.valueSelection = [0, this._input.value.length]; |
160 |
| - this._currentQuery = query; |
161 |
| - this._currentSession.accept(query); |
162 |
| - } else if (this._currentQuery) { |
163 |
| - this._input.value = this._currentQuery; |
164 |
| - this._input.valueSelection = [0, this._input.value.length]; |
165 |
| - } |
166 |
| - } |
167 |
| -} |
168 |
| - |
169 |
| -class InteractiveQuickPickSession extends Disposable { |
170 |
| - |
171 |
| - private _model: ChatModel; |
172 |
| - private _viewModel: ChatViewModel; |
173 |
| - |
174 |
| - private readonly _onDidClickFollowup: Emitter<IChatReplyFollowup> = this._register(new Emitter<IChatReplyFollowup>()); |
175 |
| - onDidClickFollowup: Event<IChatReplyFollowup> = this._onDidClickFollowup.event; |
176 |
| - |
177 |
| - private _listDisposable: DisposableStore | undefined; |
178 |
| - |
179 |
| - constructor( |
180 |
| - @IInstantiationService private readonly _instantiationService: IInstantiationService, |
181 |
| - @IChatService private readonly _chatService: IChatService, |
182 |
| - @IChatWidgetService private readonly _chatWidgetService: IChatWidgetService |
183 |
| - ) { |
184 |
| - super(); |
185 |
| - |
186 |
| - const providerInfo = _chatService.getProviderInfos()[0]; |
187 |
| - this._model = this._register(_chatService.startSession(providerInfo.id, CancellationToken.None)!); |
188 |
| - this._viewModel = this._register(new ChatViewModel(this._model, _instantiationService)); |
189 |
| - } |
190 |
| - |
191 |
| - createList(container: HTMLElement, offsetWidth: number) { |
192 |
| - this._listDisposable?.dispose(); |
193 |
| - this._listDisposable = new DisposableStore(); |
194 |
| - const options = this._listDisposable.add(this._instantiationService.createInstance(ChatEditorOptions, 'quickpick-interactive', foreground, editorBackground, editorBackground)); |
195 |
| - const list = this._listDisposable.add(this._instantiationService.createInstance( |
196 |
| - ChatListItemRenderer, |
197 |
| - options, |
198 |
| - { |
199 |
| - getListLength: () => { |
200 |
| - return 1; |
201 |
| - }, |
202 |
| - getSlashCommands() { |
203 |
| - return []; |
204 |
| - }, |
205 |
| - } |
206 |
| - )); |
207 |
| - |
208 |
| - const template = list.renderTemplate(container); |
209 |
| - list.layout(offsetWidth); |
210 |
| - this._listDisposable.add(this._viewModel.onDidChange(() => { |
211 |
| - const items = this._viewModel.getItems(); |
212 |
| - const node = { |
213 |
| - element: items[items.length - 1], |
214 |
| - children: [], |
215 |
| - collapsed: false, |
216 |
| - collapsible: false, |
217 |
| - depth: 0, |
218 |
| - filterData: undefined, |
219 |
| - visible: true, |
220 |
| - visibleChildIndex: 0, |
221 |
| - visibleChildrenCount: 1, |
222 |
| - }; |
223 |
| - list.disposeElement(node, 0, template); |
224 |
| - list.renderElement(node, 0, template); |
225 |
| - })); |
226 |
| - |
227 |
| - if (this._viewModel.getItems().length) { |
228 |
| - const items = this._viewModel.getItems(); |
229 |
| - const node = { |
230 |
| - element: items[items.length - 1], |
231 |
| - children: [], |
232 |
| - collapsed: false, |
233 |
| - collapsible: false, |
234 |
| - depth: 0, |
235 |
| - filterData: undefined, |
236 |
| - visible: true, |
237 |
| - visibleChildIndex: 0, |
238 |
| - visibleChildrenCount: 1, |
239 |
| - }; |
240 |
| - list.disposeElement(node, 0, template); |
241 |
| - list.renderElement(node, 0, template); |
242 |
| - } |
243 |
| - |
244 |
| - this._listDisposable.add(list.onDidClickFollowup(e => { |
245 |
| - this._onDidClickFollowup.fire(e); |
246 |
| - })); |
247 |
| - } |
248 |
| - |
249 |
| - get providerId() { |
250 |
| - return this._model.providerId; |
251 |
| - } |
252 |
| - |
253 |
| - get sessionId() { |
254 |
| - return this._model.sessionId; |
255 |
| - } |
256 |
| - |
257 |
| - async accept(query: string) { |
258 |
| - await this._model.waitForInitialization(); |
259 |
| - const requests = this._model.getRequests(); |
260 |
| - const lastRequest = requests[requests.length - 1]; |
261 |
| - if (lastRequest?.message && lastRequest?.message === query) { |
262 |
| - return; |
263 |
| - } |
264 |
| - if (this._model.requestInProgress) { |
265 |
| - this._chatService.cancelCurrentRequestForSession(this.sessionId); |
266 |
| - } |
267 |
| - await this._chatService.sendRequest(this.sessionId, query); |
268 |
| - } |
269 |
| - |
270 |
| - async openInChat(value: string) { |
271 |
| - const widget = await this._chatWidgetService.revealViewForProvider(this.providerId); |
272 |
| - if (!widget?.viewModel) { |
273 |
| - return; |
274 |
| - } |
275 |
| - |
276 |
| - const requests = this._model.getRequests().reverse(); |
277 |
| - const response = requests.find(r => r.response?.response.value !== undefined); |
278 |
| - const message = response?.response?.response.value; |
279 |
| - if (message) { |
280 |
| - this._chatService.addCompleteRequest(widget.viewModel.sessionId, value, { message }); |
281 |
| - } else if (value) { |
282 |
| - this._chatService.sendRequest(widget.viewModel.sessionId, value); |
283 |
| - } |
284 |
| - widget.focusInput(); |
285 |
| - } |
286 |
| -} |
0 commit comments