@theia/ai-chat-ui 1.65.0-next.19 → 1.65.0-next.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/browser/ai-chat-ui-frontend-module.d.ts.map +1 -1
- package/lib/browser/ai-chat-ui-frontend-module.js +9 -1
- package/lib/browser/ai-chat-ui-frontend-module.js.map +1 -1
- package/lib/browser/chat-input-history-contribution.d.ts +16 -0
- package/lib/browser/chat-input-history-contribution.d.ts.map +1 -0
- package/lib/browser/chat-input-history-contribution.js +143 -0
- package/lib/browser/chat-input-history-contribution.js.map +1 -0
- package/lib/browser/chat-input-history.d.ts +32 -0
- package/lib/browser/chat-input-history.d.ts.map +1 -0
- package/lib/browser/chat-input-history.js +125 -0
- package/lib/browser/chat-input-history.js.map +1 -0
- package/lib/browser/chat-input-widget.d.ts +16 -0
- package/lib/browser/chat-input-widget.d.ts.map +1 -1
- package/lib/browser/chat-input-widget.js +98 -4
- package/lib/browser/chat-input-widget.js.map +1 -1
- package/lib/browser/chat-response-renderer/toolcall-part-renderer.d.ts +2 -1
- package/lib/browser/chat-response-renderer/toolcall-part-renderer.d.ts.map +1 -1
- package/lib/browser/chat-response-renderer/toolcall-part-renderer.js +4 -3
- package/lib/browser/chat-response-renderer/toolcall-part-renderer.js.map +1 -1
- package/lib/browser/chat-view-widget.d.ts +2 -2
- package/lib/browser/chat-view-widget.d.ts.map +1 -1
- package/lib/browser/chat-view-widget.js +1 -1
- package/lib/browser/chat-view-widget.js.map +1 -1
- package/package.json +10 -10
- package/src/browser/ai-chat-ui-frontend-module.ts +11 -2
- package/src/browser/chat-input-history-contribution.ts +150 -0
- package/src/browser/chat-input-history.ts +138 -0
- package/src/browser/chat-input-widget.tsx +119 -2
- package/src/browser/chat-response-renderer/toolcall-part-renderer.tsx +2 -1
- package/src/browser/chat-view-widget.tsx +2 -2
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2025 STMicroelectronics and others.
|
|
3
|
+
//
|
|
4
|
+
// This program and the accompanying materials are made available under the
|
|
5
|
+
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
//
|
|
8
|
+
// This Source Code may also be made available under the following Secondary
|
|
9
|
+
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
// with the GNU Classpath Exception which is available at
|
|
12
|
+
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
//
|
|
14
|
+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
+
// *****************************************************************************
|
|
16
|
+
|
|
17
|
+
import { Command, CommandContribution, CommandRegistry } from '@theia/core';
|
|
18
|
+
import { ApplicationShell, KeybindingContribution, KeybindingRegistry } from '@theia/core/lib/browser';
|
|
19
|
+
import { inject, injectable } from '@theia/core/shared/inversify';
|
|
20
|
+
import { AIChatInputWidget } from './chat-input-widget';
|
|
21
|
+
import { ChatInputHistoryService } from './chat-input-history';
|
|
22
|
+
|
|
23
|
+
const CHAT_INPUT_PREVIOUS_PROMPT_COMMAND = Command.toDefaultLocalizedCommand({
|
|
24
|
+
id: 'chat-input:previous-prompt',
|
|
25
|
+
label: 'Previous Prompt'
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const CHAT_INPUT_NEXT_PROMPT_COMMAND = Command.toDefaultLocalizedCommand({
|
|
29
|
+
id: 'chat-input:next-prompt',
|
|
30
|
+
label: 'Next Prompt'
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const CHAT_INPUT_CLEAR_HISTORY_COMMAND = Command.toDefaultLocalizedCommand({
|
|
34
|
+
id: 'chat-input:clear-history',
|
|
35
|
+
category: 'Chat',
|
|
36
|
+
label: 'Clear Input Prompt History'
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
@injectable()
|
|
40
|
+
export class ChatInputHistoryContribution implements CommandContribution, KeybindingContribution {
|
|
41
|
+
|
|
42
|
+
@inject(ApplicationShell)
|
|
43
|
+
protected readonly shell: ApplicationShell;
|
|
44
|
+
|
|
45
|
+
@inject(ChatInputHistoryService)
|
|
46
|
+
protected readonly historyService: ChatInputHistoryService;
|
|
47
|
+
|
|
48
|
+
registerCommands(commands: CommandRegistry): void {
|
|
49
|
+
commands.registerCommand(CHAT_INPUT_PREVIOUS_PROMPT_COMMAND, {
|
|
50
|
+
execute: () => this.executeNavigatePrevious(),
|
|
51
|
+
isEnabled: () => this.isNavigationEnabled()
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
commands.registerCommand(CHAT_INPUT_NEXT_PROMPT_COMMAND, {
|
|
55
|
+
execute: () => this.executeNavigateNext(),
|
|
56
|
+
isEnabled: () => this.isNavigationEnabled()
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
commands.registerCommand(CHAT_INPUT_CLEAR_HISTORY_COMMAND, {
|
|
60
|
+
execute: () => this.historyService.clearHistory(),
|
|
61
|
+
isEnabled: () => this.historyService.getPrompts().length > 0
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
registerKeybindings(keybindings: KeybindingRegistry): void {
|
|
66
|
+
keybindings.registerKeybinding({
|
|
67
|
+
command: CHAT_INPUT_PREVIOUS_PROMPT_COMMAND.id,
|
|
68
|
+
keybinding: 'up',
|
|
69
|
+
when: 'chatInputFocus && chatInputFirstLine && !suggestWidgetVisible'
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
keybindings.registerKeybinding({
|
|
73
|
+
command: CHAT_INPUT_NEXT_PROMPT_COMMAND.id,
|
|
74
|
+
keybinding: 'down',
|
|
75
|
+
when: 'chatInputFocus && chatInputLastLine && !suggestWidgetVisible'
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
protected executeNavigatePrevious(): void {
|
|
80
|
+
const chatInputWidget = this.findFocusedChatInput();
|
|
81
|
+
if (!chatInputWidget || !chatInputWidget.editor) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const currentInput = chatInputWidget.editor.getControl().getValue();
|
|
86
|
+
const previousPrompt = chatInputWidget.getPreviousPrompt(currentInput);
|
|
87
|
+
|
|
88
|
+
if (previousPrompt !== undefined) {
|
|
89
|
+
chatInputWidget.editor.getControl().setValue(previousPrompt);
|
|
90
|
+
this.positionCursorAtEnd(chatInputWidget);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
protected executeNavigateNext(): void {
|
|
95
|
+
const chatInputWidget = this.findFocusedChatInput();
|
|
96
|
+
if (!chatInputWidget || !chatInputWidget.editor) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const nextPrompt = chatInputWidget.getNextPrompt();
|
|
101
|
+
|
|
102
|
+
if (nextPrompt !== undefined) {
|
|
103
|
+
chatInputWidget.editor.getControl().setValue(nextPrompt);
|
|
104
|
+
this.positionCursorAtEnd(chatInputWidget);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
protected positionCursorAtEnd(widget: AIChatInputWidget): void {
|
|
109
|
+
const editor = widget.editor?.getControl();
|
|
110
|
+
const model = editor?.getModel();
|
|
111
|
+
|
|
112
|
+
if (editor && model) {
|
|
113
|
+
const lastLine = model.getLineCount();
|
|
114
|
+
const lastColumn = model.getLineContent(lastLine).length + 1;
|
|
115
|
+
editor.setPosition({ lineNumber: lastLine, column: lastColumn });
|
|
116
|
+
editor.focus();
|
|
117
|
+
|
|
118
|
+
setTimeout(() => {
|
|
119
|
+
// Trigger cursor position update after setting value
|
|
120
|
+
if (widget.editor?.getControl().hasWidgetFocus()) {
|
|
121
|
+
widget.updateCursorPositionKeys();
|
|
122
|
+
}
|
|
123
|
+
}, 0);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
protected findFocusedChatInput(): AIChatInputWidget | undefined {
|
|
128
|
+
const activeElement = document.activeElement;
|
|
129
|
+
if (!(activeElement instanceof HTMLElement)) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const activeWidget = this.shell.findWidgetForElement(activeElement);
|
|
133
|
+
if (!(activeWidget instanceof AIChatInputWidget)) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (!activeWidget.inputConfiguration?.enablePromptHistory) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
if (!activeWidget.editor?.getControl().hasWidgetFocus()) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
return activeWidget;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
protected isNavigationEnabled(): boolean {
|
|
146
|
+
const chatInputWidget = this.findFocusedChatInput();
|
|
147
|
+
return chatInputWidget !== undefined &&
|
|
148
|
+
chatInputWidget.inputConfiguration?.enablePromptHistory !== false;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2025 STMicroelectronics and others.
|
|
3
|
+
//
|
|
4
|
+
// This program and the accompanying materials are made available under the
|
|
5
|
+
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
//
|
|
8
|
+
// This Source Code may also be made available under the following Secondary
|
|
9
|
+
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
// with the GNU Classpath Exception which is available at
|
|
12
|
+
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
//
|
|
14
|
+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
+
// *****************************************************************************
|
|
16
|
+
|
|
17
|
+
import { inject, injectable } from '@theia/core/shared/inversify';
|
|
18
|
+
import { StorageService } from '@theia/core/lib/browser';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Manages navigation state for a single chat input widget.
|
|
22
|
+
* Each widget has its own independent navigation state while sharing the same history.
|
|
23
|
+
*/
|
|
24
|
+
export class ChatInputNavigationState {
|
|
25
|
+
private currentIndex: number;
|
|
26
|
+
private preservedInput?: string;
|
|
27
|
+
private isNavigating = false;
|
|
28
|
+
|
|
29
|
+
constructor(private readonly historyService: ChatInputHistoryService) {
|
|
30
|
+
this.currentIndex = historyService.getPrompts().length;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
getPreviousPrompt(currentInput: string): string | undefined {
|
|
34
|
+
const history = this.historyService.getPrompts();
|
|
35
|
+
|
|
36
|
+
if (history.length === 0) {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (!this.isNavigating) {
|
|
41
|
+
this.preservedInput = currentInput;
|
|
42
|
+
this.isNavigating = true;
|
|
43
|
+
this.currentIndex = history.length;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (this.currentIndex <= 0) {
|
|
47
|
+
// Already at the oldest prompt
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
this.currentIndex--;
|
|
52
|
+
return history[this.currentIndex];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
getNextPrompt(): string | undefined {
|
|
56
|
+
const history = this.historyService.getPrompts();
|
|
57
|
+
|
|
58
|
+
if (!this.isNavigating || this.currentIndex >= history.length) {
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
this.currentIndex++;
|
|
63
|
+
|
|
64
|
+
if (this.currentIndex >= history.length) {
|
|
65
|
+
// Reached end of history - return to preserved input
|
|
66
|
+
this.isNavigating = false;
|
|
67
|
+
const preserved = this.preservedInput;
|
|
68
|
+
this.preservedInput = undefined;
|
|
69
|
+
this.currentIndex = history.length;
|
|
70
|
+
return preserved || '';
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return history[this.currentIndex];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
stopNavigation(): void {
|
|
77
|
+
this.isNavigating = false;
|
|
78
|
+
this.preservedInput = undefined;
|
|
79
|
+
this.currentIndex = this.historyService.getPrompts().length;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const CHAT_PROMPT_HISTORY_STORAGE_KEY = 'ai-chat-prompt-history';
|
|
85
|
+
const MAX_HISTORY_SIZE = 100;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Manages shared prompt history across all chat input widgets.
|
|
89
|
+
* Each prompt is stored only once and shared between all chat inputs.
|
|
90
|
+
*/
|
|
91
|
+
@injectable()
|
|
92
|
+
export class ChatInputHistoryService {
|
|
93
|
+
|
|
94
|
+
@inject(StorageService)
|
|
95
|
+
protected readonly storageService: StorageService;
|
|
96
|
+
|
|
97
|
+
protected history: string[] = [];
|
|
98
|
+
|
|
99
|
+
async init(): Promise<void> {
|
|
100
|
+
const data = await this.storageService.getData<{ prompts: string[] }>(CHAT_PROMPT_HISTORY_STORAGE_KEY, { prompts: [] });
|
|
101
|
+
this.history = data.prompts || [];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Get read-only access to the current prompt history.
|
|
106
|
+
*/
|
|
107
|
+
getPrompts(): readonly string[] {
|
|
108
|
+
return this.history;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
clearHistory(): void {
|
|
112
|
+
this.history = [];
|
|
113
|
+
this.persistHistory();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
addToHistory(prompt: string): void {
|
|
117
|
+
const trimmed = prompt.trim();
|
|
118
|
+
if (!trimmed) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Remove existing instance and add to end (most recent)
|
|
123
|
+
this.history = this.history
|
|
124
|
+
.filter(item => item !== trimmed)
|
|
125
|
+
.concat(trimmed)
|
|
126
|
+
.slice(-MAX_HISTORY_SIZE);
|
|
127
|
+
|
|
128
|
+
this.persistHistory();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
protected async persistHistory(): Promise<void> {
|
|
132
|
+
try {
|
|
133
|
+
await this.storageService.setData(CHAT_PROMPT_HISTORY_STORAGE_KEY, { prompts: this.history });
|
|
134
|
+
} catch (error) {
|
|
135
|
+
console.warn('Failed to persist chat prompt history:', error);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
@@ -23,6 +23,7 @@ import { AIVariableResolutionRequest } from '@theia/ai-core';
|
|
|
23
23
|
import { AgentCompletionNotificationService, FrontendVariableService, AIActivationService } from '@theia/ai-core/lib/browser';
|
|
24
24
|
import { DisposableCollection, Emitter, InMemoryResources, URI, nls } from '@theia/core';
|
|
25
25
|
import { ContextMenuRenderer, LabelProvider, Message, OpenerService, ReactWidget } from '@theia/core/lib/browser';
|
|
26
|
+
import { ContextKey, ContextKeyService } from '@theia/core/lib/browser/context-key-service';
|
|
26
27
|
import { Deferred } from '@theia/core/lib/common/promise-util';
|
|
27
28
|
import { inject, injectable, optional, postConstruct } from '@theia/core/shared/inversify';
|
|
28
29
|
import * as React from '@theia/core/shared/react';
|
|
@@ -35,6 +36,7 @@ import { CHAT_VIEW_LANGUAGE_EXTENSION } from './chat-view-language-contribution'
|
|
|
35
36
|
import { ContextVariablePicker } from './context-variable-picker';
|
|
36
37
|
import { TASK_CONTEXT_VARIABLE } from '@theia/ai-chat/lib/browser/task-context-variable';
|
|
37
38
|
import { IModelDeltaDecoration } from '@theia/monaco-editor-core/esm/vs/editor/common/model';
|
|
39
|
+
import { ChatInputHistoryService, ChatInputNavigationState } from './chat-input-history';
|
|
38
40
|
|
|
39
41
|
type Query = (query: string) => Promise<void>;
|
|
40
42
|
type Unpin = () => void;
|
|
@@ -49,6 +51,7 @@ export interface AIChatInputConfiguration {
|
|
|
49
51
|
showPinnedAgent?: boolean;
|
|
50
52
|
showChangeSet?: boolean;
|
|
51
53
|
showSuggestions?: boolean;
|
|
54
|
+
enablePromptHistory?: boolean;
|
|
52
55
|
}
|
|
53
56
|
|
|
54
57
|
@injectable()
|
|
@@ -95,9 +98,43 @@ export class AIChatInputWidget extends ReactWidget {
|
|
|
95
98
|
@inject(AIActivationService)
|
|
96
99
|
protected readonly aiActivationService: AIActivationService;
|
|
97
100
|
|
|
101
|
+
@inject(ChatInputHistoryService)
|
|
102
|
+
protected readonly historyService: ChatInputHistoryService;
|
|
103
|
+
|
|
104
|
+
protected navigationState: ChatInputNavigationState;
|
|
105
|
+
|
|
106
|
+
@inject(ContextKeyService)
|
|
107
|
+
protected readonly contextKeyService: ContextKeyService;
|
|
108
|
+
|
|
98
109
|
protected editorRef: SimpleMonacoEditor | undefined = undefined;
|
|
99
110
|
protected readonly editorReady = new Deferred<void>();
|
|
100
111
|
|
|
112
|
+
get editor(): SimpleMonacoEditor | undefined {
|
|
113
|
+
return this.editorRef;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
get inputConfiguration(): AIChatInputConfiguration | undefined {
|
|
117
|
+
return this.configuration;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
getPreviousPrompt(currentInput: string): string | undefined {
|
|
121
|
+
if (!this.navigationState) {
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
124
|
+
return this.navigationState.getPreviousPrompt(currentInput);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
getNextPrompt(): string | undefined {
|
|
128
|
+
if (!this.navigationState) {
|
|
129
|
+
return undefined;
|
|
130
|
+
}
|
|
131
|
+
return this.navigationState.getNextPrompt();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
protected chatInputFocusKey: ContextKey<boolean>;
|
|
135
|
+
protected chatInputFirstLineKey: ContextKey<boolean>;
|
|
136
|
+
protected chatInputLastLineKey: ContextKey<boolean>;
|
|
137
|
+
|
|
101
138
|
protected isEnabled = false;
|
|
102
139
|
protected heightInLines = 12;
|
|
103
140
|
|
|
@@ -111,7 +148,13 @@ export class AIChatInputWidget extends ReactWidget {
|
|
|
111
148
|
|
|
112
149
|
protected _onQuery: Query;
|
|
113
150
|
set onQuery(query: Query) {
|
|
114
|
-
this._onQuery =
|
|
151
|
+
this._onQuery = (prompt: string) => {
|
|
152
|
+
if (this.configuration?.enablePromptHistory !== false && prompt.trim()) {
|
|
153
|
+
this.historyService.addToHistory(prompt);
|
|
154
|
+
this.navigationState.stopNavigation();
|
|
155
|
+
}
|
|
156
|
+
return query(prompt);
|
|
157
|
+
};
|
|
115
158
|
}
|
|
116
159
|
protected _onUnpin: Unpin;
|
|
117
160
|
set onUnpin(unpin: Unpin) {
|
|
@@ -167,9 +210,79 @@ export class AIChatInputWidget extends ReactWidget {
|
|
|
167
210
|
}));
|
|
168
211
|
this.toDispose.push(this.onDidResizeEmitter);
|
|
169
212
|
this.setEnabled(this.aiActivationService.isActive);
|
|
213
|
+
this.historyService.init().then(() => {
|
|
214
|
+
this.navigationState = new ChatInputNavigationState(this.historyService);
|
|
215
|
+
});
|
|
216
|
+
this.initializeContextKeys();
|
|
170
217
|
this.update();
|
|
171
218
|
}
|
|
172
219
|
|
|
220
|
+
protected initializeContextKeys(): void {
|
|
221
|
+
this.chatInputFocusKey = this.contextKeyService.createKey<boolean>('chatInputFocus', false);
|
|
222
|
+
this.chatInputFirstLineKey = this.contextKeyService.createKey<boolean>('chatInputFirstLine', false);
|
|
223
|
+
this.chatInputLastLineKey = this.contextKeyService.createKey<boolean>('chatInputLastLine', false);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
updateCursorPositionKeys(): void {
|
|
227
|
+
if (!this.editorRef) {
|
|
228
|
+
this.chatInputFirstLineKey.set(false);
|
|
229
|
+
this.chatInputLastLineKey.set(false);
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const editor = this.editorRef.getControl();
|
|
234
|
+
const position = editor.getPosition();
|
|
235
|
+
const model = editor.getModel();
|
|
236
|
+
|
|
237
|
+
if (!position || !model) {
|
|
238
|
+
this.chatInputFirstLineKey.set(false);
|
|
239
|
+
this.chatInputLastLineKey.set(false);
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const isFirstLine = position.lineNumber === 1;
|
|
244
|
+
const isLastLine = position.lineNumber === model.getLineCount();
|
|
245
|
+
|
|
246
|
+
this.chatInputFirstLineKey.set(isFirstLine);
|
|
247
|
+
this.chatInputLastLineKey.set(isLastLine);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
protected setupEditorEventListeners(): void {
|
|
251
|
+
if (!this.editorRef) {
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const editor = this.editorRef.getControl();
|
|
256
|
+
|
|
257
|
+
this.toDispose.push(editor.onDidFocusEditorWidget(() => {
|
|
258
|
+
this.chatInputFocusKey.set(true);
|
|
259
|
+
this.updateCursorPositionKeys();
|
|
260
|
+
}));
|
|
261
|
+
|
|
262
|
+
this.toDispose.push(editor.onDidBlurEditorWidget(() => {
|
|
263
|
+
this.chatInputFocusKey.set(false);
|
|
264
|
+
this.chatInputFirstLineKey.set(false);
|
|
265
|
+
this.chatInputLastLineKey.set(false);
|
|
266
|
+
}));
|
|
267
|
+
|
|
268
|
+
this.toDispose.push(editor.onDidChangeCursorPosition(() => {
|
|
269
|
+
if (editor.hasWidgetFocus()) {
|
|
270
|
+
this.updateCursorPositionKeys();
|
|
271
|
+
}
|
|
272
|
+
}));
|
|
273
|
+
|
|
274
|
+
this.toDispose.push(editor.onDidChangeModelContent(() => {
|
|
275
|
+
if (editor.hasWidgetFocus()) {
|
|
276
|
+
this.updateCursorPositionKeys();
|
|
277
|
+
}
|
|
278
|
+
}));
|
|
279
|
+
|
|
280
|
+
if (editor.hasWidgetFocus()) {
|
|
281
|
+
this.chatInputFocusKey.set(true);
|
|
282
|
+
this.updateCursorPositionKeys();
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
173
286
|
protected override onActivateRequest(msg: Message): void {
|
|
174
287
|
super.onActivateRequest(msg);
|
|
175
288
|
this.editorReady.promise.then(() => {
|
|
@@ -206,6 +319,7 @@ export class AIChatInputWidget extends ReactWidget {
|
|
|
206
319
|
const isEditing = !!(currentRequest && (EditableChatRequestModel.isEditing(currentRequest)));
|
|
207
320
|
const isPending = () => !!(currentRequest && !isEditing && ChatRequestModel.isInProgress(currentRequest));
|
|
208
321
|
const pending = isPending();
|
|
322
|
+
const hasPromptHistory = this.configuration?.enablePromptHistory && this.historyService.getPrompts().length > 0;
|
|
209
323
|
|
|
210
324
|
return (
|
|
211
325
|
<ChatInput
|
|
@@ -232,12 +346,14 @@ export class AIChatInputWidget extends ReactWidget {
|
|
|
232
346
|
isEnabled={this.isEnabled}
|
|
233
347
|
setEditorRef={editor => {
|
|
234
348
|
this.editorRef = editor;
|
|
349
|
+
this.setupEditorEventListeners();
|
|
235
350
|
this.editorReady.resolve();
|
|
236
351
|
}}
|
|
237
352
|
showContext={this.configuration?.showContext}
|
|
238
353
|
showPinnedAgent={this.configuration?.showPinnedAgent}
|
|
239
354
|
showChangeSet={this.configuration?.showChangeSet}
|
|
240
355
|
showSuggestions={this.configuration?.showSuggestions}
|
|
356
|
+
hasPromptHistory={hasPromptHistory}
|
|
241
357
|
labelProvider={this.labelProvider}
|
|
242
358
|
actionService={this.changeSetActionService}
|
|
243
359
|
decoratorService={this.changeSetDecoratorService}
|
|
@@ -388,6 +504,7 @@ interface ChatInputProperties {
|
|
|
388
504
|
showPinnedAgent?: boolean;
|
|
389
505
|
showChangeSet?: boolean;
|
|
390
506
|
showSuggestions?: boolean;
|
|
507
|
+
hasPromptHistory?: boolean;
|
|
391
508
|
labelProvider: LabelProvider;
|
|
392
509
|
actionService: ChangeSetActionService;
|
|
393
510
|
decoratorService: ChangeSetDecoratorService;
|
|
@@ -439,7 +556,7 @@ const ChatInput: React.FunctionComponent<ChatInputProperties> = (props: ChatInpu
|
|
|
439
556
|
? nls.localize('theia/ai/chat-ui/aiDisabled', 'AI features are disabled')
|
|
440
557
|
: shouldUseTaskPlaceholder
|
|
441
558
|
? taskPlaceholder
|
|
442
|
-
: nls.localizeByDefault('Ask a question');
|
|
559
|
+
: nls.localizeByDefault('Ask a question') + (props.hasPromptHistory ? nls.localizeByDefault(' ({0} for history)', '⇅') : '');
|
|
443
560
|
|
|
444
561
|
// Handle paste events on the container
|
|
445
562
|
const handlePaste = React.useCallback((event: ClipboardEvent) => {
|
|
@@ -22,10 +22,11 @@ import { nls } from '@theia/core/lib/common/nls';
|
|
|
22
22
|
import { codicon, OpenerService } from '@theia/core/lib/browser';
|
|
23
23
|
import * as React from '@theia/core/shared/react';
|
|
24
24
|
import { ToolConfirmation, ToolConfirmationState } from './tool-confirmation';
|
|
25
|
-
import {
|
|
25
|
+
import { ToolConfirmationMode } from '@theia/ai-chat/lib/common/chat-tool-preferences';
|
|
26
26
|
import { ResponseNode } from '../chat-tree-view';
|
|
27
27
|
import { useMarkdownRendering } from './markdown-part-renderer';
|
|
28
28
|
import { ToolCallResult } from '@theia/ai-core';
|
|
29
|
+
import { ToolConfirmationManager } from '@theia/ai-chat/lib/browser/chat-tool-preference-bindings';
|
|
29
30
|
|
|
30
31
|
@injectable()
|
|
31
32
|
export class ToolCallPartRenderer implements ChatResponsePartRenderer<ToolCallChatResponseContent> {
|
|
@@ -13,9 +13,9 @@
|
|
|
13
13
|
//
|
|
14
14
|
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
15
|
// *****************************************************************************
|
|
16
|
-
import { CommandService, deepClone, Emitter, Event, MessageService, URI } from '@theia/core';
|
|
16
|
+
import { CommandService, deepClone, Emitter, Event, MessageService, PreferenceService, URI } from '@theia/core';
|
|
17
17
|
import { ChatRequest, ChatRequestModel, ChatService, ChatSession, isActiveSessionChangedEvent, MutableChatModel } from '@theia/ai-chat';
|
|
18
|
-
import { BaseWidget, codicon, ExtractableWidget, Message, PanelLayout,
|
|
18
|
+
import { BaseWidget, codicon, ExtractableWidget, Message, PanelLayout, StatefulWidget } from '@theia/core/lib/browser';
|
|
19
19
|
import { nls } from '@theia/core/lib/common/nls';
|
|
20
20
|
import { inject, injectable, postConstruct } from '@theia/core/shared/inversify';
|
|
21
21
|
import { AIChatInputWidget } from './chat-input-widget';
|