@theia/ai-chat-ui 1.46.0-next.241
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/README.md +33 -0
- package/lib/browser/ai-chat-ui-contribution.d.ts +24 -0
- package/lib/browser/ai-chat-ui-contribution.d.ts.map +1 -0
- package/lib/browser/ai-chat-ui-contribution.js +161 -0
- package/lib/browser/ai-chat-ui-contribution.js.map +1 -0
- package/lib/browser/ai-chat-ui-frontend-module.d.ts +5 -0
- package/lib/browser/ai-chat-ui-frontend-module.d.ts.map +1 -0
- package/lib/browser/ai-chat-ui-frontend-module.js +87 -0
- package/lib/browser/ai-chat-ui-frontend-module.js.map +1 -0
- package/lib/browser/chat-input-widget.d.ts +32 -0
- package/lib/browser/chat-input-widget.d.ts.map +1 -0
- package/lib/browser/chat-input-widget.js +217 -0
- package/lib/browser/chat-input-widget.js.map +1 -0
- package/lib/browser/chat-node-toolbar-action-contribution.d.ts +47 -0
- package/lib/browser/chat-node-toolbar-action-contribution.d.ts.map +1 -0
- package/lib/browser/chat-node-toolbar-action-contribution.js +25 -0
- package/lib/browser/chat-node-toolbar-action-contribution.js.map +1 -0
- package/lib/browser/chat-response-part-renderer.d.ts +10 -0
- package/lib/browser/chat-response-part-renderer.d.ts.map +1 -0
- package/lib/browser/chat-response-part-renderer.js +20 -0
- package/lib/browser/chat-response-part-renderer.js.map +1 -0
- package/lib/browser/chat-response-renderer/ai-editor-manager.d.ts +36 -0
- package/lib/browser/chat-response-renderer/ai-editor-manager.d.ts.map +1 -0
- package/lib/browser/chat-response-renderer/ai-editor-manager.js +184 -0
- package/lib/browser/chat-response-renderer/ai-editor-manager.js.map +1 -0
- package/lib/browser/chat-response-renderer/code-part-renderer.d.ts +45 -0
- package/lib/browser/chat-response-renderer/code-part-renderer.d.ts.map +1 -0
- package/lib/browser/chat-response-renderer/code-part-renderer.js +190 -0
- package/lib/browser/chat-response-renderer/code-part-renderer.js.map +1 -0
- package/lib/browser/chat-response-renderer/command-part-renderer.d.ts +12 -0
- package/lib/browser/chat-response-renderer/command-part-renderer.d.ts.map +1 -0
- package/lib/browser/chat-response-renderer/command-part-renderer.js +70 -0
- package/lib/browser/chat-response-renderer/command-part-renderer.js.map +1 -0
- package/lib/browser/chat-response-renderer/error-part-renderer.d.ts +9 -0
- package/lib/browser/chat-response-renderer/error-part-renderer.d.ts.map +1 -0
- package/lib/browser/chat-response-renderer/error-part-renderer.js +40 -0
- package/lib/browser/chat-response-renderer/error-part-renderer.js.map +1 -0
- package/lib/browser/chat-response-renderer/horizontal-layout-part-renderer.d.ts +12 -0
- package/lib/browser/chat-response-renderer/horizontal-layout-part-renderer.d.ts.map +1 -0
- package/lib/browser/chat-response-renderer/horizontal-layout-part-renderer.js +54 -0
- package/lib/browser/chat-response-renderer/horizontal-layout-part-renderer.js.map +1 -0
- package/lib/browser/chat-response-renderer/index.d.ts +9 -0
- package/lib/browser/chat-response-renderer/index.d.ts.map +1 -0
- package/lib/browser/chat-response-renderer/index.js +27 -0
- package/lib/browser/chat-response-renderer/index.js.map +1 -0
- package/lib/browser/chat-response-renderer/markdown-part-renderer.d.ts +25 -0
- package/lib/browser/chat-response-renderer/markdown-part-renderer.d.ts.map +1 -0
- package/lib/browser/chat-response-renderer/markdown-part-renderer.js +89 -0
- package/lib/browser/chat-response-renderer/markdown-part-renderer.js.map +1 -0
- package/lib/browser/chat-response-renderer/question-part-renderer.d.ts +10 -0
- package/lib/browser/chat-response-renderer/question-part-renderer.d.ts.map +1 -0
- package/lib/browser/chat-response-renderer/question-part-renderer.js +43 -0
- package/lib/browser/chat-response-renderer/question-part-renderer.js.map +1 -0
- package/lib/browser/chat-response-renderer/text-part-renderer.d.ts +9 -0
- package/lib/browser/chat-response-renderer/text-part-renderer.d.ts.map +1 -0
- package/lib/browser/chat-response-renderer/text-part-renderer.js +41 -0
- package/lib/browser/chat-response-renderer/text-part-renderer.js.map +1 -0
- package/lib/browser/chat-response-renderer/text-part-renderer.spec.d.ts +2 -0
- package/lib/browser/chat-response-renderer/text-part-renderer.spec.d.ts.map +1 -0
- package/lib/browser/chat-response-renderer/text-part-renderer.spec.js +46 -0
- package/lib/browser/chat-response-renderer/text-part-renderer.spec.js.map +1 -0
- package/lib/browser/chat-response-renderer/toolcall-part-renderer.d.ts +12 -0
- package/lib/browser/chat-response-renderer/toolcall-part-renderer.d.ts.map +1 -0
- package/lib/browser/chat-response-renderer/toolcall-part-renderer.js +81 -0
- package/lib/browser/chat-response-renderer/toolcall-part-renderer.js.map +1 -0
- package/lib/browser/chat-tree-view/chat-view-tree-container.d.ts +4 -0
- package/lib/browser/chat-tree-view/chat-view-tree-container.d.ts.map +1 -0
- package/lib/browser/chat-tree-view/chat-view-tree-container.js +33 -0
- package/lib/browser/chat-tree-view/chat-view-tree-container.js.map +1 -0
- package/lib/browser/chat-tree-view/chat-view-tree-widget.d.ts +56 -0
- package/lib/browser/chat-tree-view/chat-view-tree-widget.d.ts.map +1 -0
- package/lib/browser/chat-tree-view/chat-view-tree-widget.js +388 -0
- package/lib/browser/chat-tree-view/chat-view-tree-widget.js.map +1 -0
- package/lib/browser/chat-tree-view/index.d.ts +3 -0
- package/lib/browser/chat-tree-view/index.d.ts.map +1 -0
- package/lib/browser/chat-tree-view/index.js +21 -0
- package/lib/browser/chat-tree-view/index.js.map +1 -0
- package/lib/browser/chat-view-commands.d.ts +8 -0
- package/lib/browser/chat-view-commands.d.ts.map +1 -0
- package/lib/browser/chat-view-commands.js +44 -0
- package/lib/browser/chat-view-commands.js.map +1 -0
- package/lib/browser/chat-view-contribution.d.ts +18 -0
- package/lib/browser/chat-view-contribution.d.ts.map +1 -0
- package/lib/browser/chat-view-contribution.js +153 -0
- package/lib/browser/chat-view-contribution.js.map +1 -0
- package/lib/browser/chat-view-language-contribution.d.ts +20 -0
- package/lib/browser/chat-view-language-contribution.d.ts.map +1 -0
- package/lib/browser/chat-view-language-contribution.js +98 -0
- package/lib/browser/chat-view-language-contribution.js.map +1 -0
- package/lib/browser/chat-view-widget-toolbar-contribution.d.ts +11 -0
- package/lib/browser/chat-view-widget-toolbar-contribution.d.ts.map +1 -0
- package/lib/browser/chat-view-widget-toolbar-contribution.js +65 -0
- package/lib/browser/chat-view-widget-toolbar-contribution.js.map +1 -0
- package/lib/browser/chat-view-widget.d.ts +41 -0
- package/lib/browser/chat-view-widget.d.ts.map +1 -0
- package/lib/browser/chat-view-widget.js +182 -0
- package/lib/browser/chat-view-widget.js.map +1 -0
- package/package.json +59 -0
- package/src/browser/ai-chat-ui-contribution.ts +171 -0
- package/src/browser/ai-chat-ui-frontend-module.ts +105 -0
- package/src/browser/chat-input-widget.tsx +262 -0
- package/src/browser/chat-node-toolbar-action-contribution.ts +63 -0
- package/src/browser/chat-response-part-renderer.ts +25 -0
- package/src/browser/chat-response-renderer/ai-editor-manager.ts +183 -0
- package/src/browser/chat-response-renderer/code-part-renderer.tsx +211 -0
- package/src/browser/chat-response-renderer/command-part-renderer.tsx +60 -0
- package/src/browser/chat-response-renderer/error-part-renderer.tsx +35 -0
- package/src/browser/chat-response-renderer/horizontal-layout-part-renderer.tsx +59 -0
- package/src/browser/chat-response-renderer/index.ts +23 -0
- package/src/browser/chat-response-renderer/markdown-part-renderer.tsx +92 -0
- package/src/browser/chat-response-renderer/question-part-renderer.tsx +59 -0
- package/src/browser/chat-response-renderer/text-part-renderer.spec.ts +50 -0
- package/src/browser/chat-response-renderer/text-part-renderer.tsx +35 -0
- package/src/browser/chat-response-renderer/toolcall-part-renderer.tsx +89 -0
- package/src/browser/chat-tree-view/chat-view-tree-container.ts +32 -0
- package/src/browser/chat-tree-view/chat-view-tree-widget.tsx +526 -0
- package/src/browser/chat-tree-view/index.ts +18 -0
- package/src/browser/chat-view-commands.ts +45 -0
- package/src/browser/chat-view-contribution.ts +154 -0
- package/src/browser/chat-view-language-contribution.ts +141 -0
- package/src/browser/chat-view-widget-toolbar-contribution.tsx +54 -0
- package/src/browser/chat-view-widget.tsx +194 -0
- package/src/browser/style/index.css +415 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2024 EclipseSource GmbH.
|
|
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, nls } from '@theia/core';
|
|
18
|
+
import { codicon } from '@theia/core/lib/browser';
|
|
19
|
+
|
|
20
|
+
export namespace ChatCommands {
|
|
21
|
+
const CHAT_CATEGORY = 'Chat';
|
|
22
|
+
const CHAT_CATEGORY_KEY = nls.getDefaultKey(CHAT_CATEGORY);
|
|
23
|
+
|
|
24
|
+
export const SCROLL_LOCK_WIDGET = Command.toLocalizedCommand({
|
|
25
|
+
id: 'chat:widget:lock',
|
|
26
|
+
category: CHAT_CATEGORY,
|
|
27
|
+
iconClass: codicon('unlock')
|
|
28
|
+
}, '', CHAT_CATEGORY_KEY);
|
|
29
|
+
|
|
30
|
+
export const SCROLL_UNLOCK_WIDGET = Command.toLocalizedCommand({
|
|
31
|
+
id: 'chat:widget:unlock',
|
|
32
|
+
category: CHAT_CATEGORY,
|
|
33
|
+
iconClass: codicon('lock')
|
|
34
|
+
}, '', CHAT_CATEGORY_KEY);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const AI_CHAT_NEW_CHAT_WINDOW_COMMAND: Command = {
|
|
38
|
+
id: 'ai-chat-ui.new-chat',
|
|
39
|
+
iconClass: codicon('add')
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const AI_CHAT_SHOW_CHATS_COMMAND: Command = {
|
|
43
|
+
id: 'ai-chat-ui.show-chats',
|
|
44
|
+
iconClass: codicon('history')
|
|
45
|
+
};
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2024 EclipseSource GmbH.
|
|
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
|
+
import { Command, CommandContribution, CommandRegistry, CommandService, isObject, MenuContribution, MenuModelRegistry } from '@theia/core';
|
|
17
|
+
import { CommonCommands, TreeNode } from '@theia/core/lib/browser';
|
|
18
|
+
import { ClipboardService } from '@theia/core/lib/browser/clipboard-service';
|
|
19
|
+
import { inject, injectable } from '@theia/core/shared/inversify';
|
|
20
|
+
import { ChatViewTreeWidget, isRequestNode, isResponseNode, RequestNode, ResponseNode } from './chat-tree-view/chat-view-tree-widget';
|
|
21
|
+
import { AIChatInputWidget } from './chat-input-widget';
|
|
22
|
+
|
|
23
|
+
export namespace ChatViewCommands {
|
|
24
|
+
export const COPY_MESSAGE = Command.toDefaultLocalizedCommand({
|
|
25
|
+
id: 'chat.copy.message',
|
|
26
|
+
label: 'Copy Message'
|
|
27
|
+
});
|
|
28
|
+
export const COPY_ALL = Command.toDefaultLocalizedCommand({
|
|
29
|
+
id: 'chat.copy.all',
|
|
30
|
+
label: 'Copy All'
|
|
31
|
+
});
|
|
32
|
+
export const COPY_CODE = Command.toDefaultLocalizedCommand({
|
|
33
|
+
id: 'chat.copy.code',
|
|
34
|
+
label: 'Copy Code Block'
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
@injectable()
|
|
39
|
+
export class ChatViewMenuContribution implements MenuContribution, CommandContribution {
|
|
40
|
+
|
|
41
|
+
@inject(ClipboardService)
|
|
42
|
+
protected readonly clipboardService: ClipboardService;
|
|
43
|
+
|
|
44
|
+
@inject(CommandService)
|
|
45
|
+
protected readonly commandService: CommandService;
|
|
46
|
+
|
|
47
|
+
registerCommands(commands: CommandRegistry): void {
|
|
48
|
+
commands.registerHandler(CommonCommands.COPY.id, {
|
|
49
|
+
execute: (...args: unknown[]) => {
|
|
50
|
+
if (window.getSelection()?.type !== 'Range' && containsRequestOrResponseNode(args)) {
|
|
51
|
+
this.copyMessage(extractRequestOrResponseNodes(args));
|
|
52
|
+
} else {
|
|
53
|
+
this.commandService.executeCommand(CommonCommands.COPY.id);
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
isEnabled: (...args: unknown[]) => containsRequestOrResponseNode(args)
|
|
57
|
+
});
|
|
58
|
+
commands.registerCommand(ChatViewCommands.COPY_MESSAGE, {
|
|
59
|
+
execute: (...args: unknown[]) => {
|
|
60
|
+
if (containsRequestOrResponseNode(args)) {
|
|
61
|
+
this.copyMessage(extractRequestOrResponseNodes(args));
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
isEnabled: (...args: unknown[]) => containsRequestOrResponseNode(args)
|
|
65
|
+
});
|
|
66
|
+
commands.registerCommand(ChatViewCommands.COPY_ALL, {
|
|
67
|
+
execute: (...args: unknown[]) => {
|
|
68
|
+
if (containsRequestOrResponseNode(args)) {
|
|
69
|
+
const parent = extractRequestOrResponseNodes(args).find(arg => arg.parent)?.parent;
|
|
70
|
+
const text = parent?.children
|
|
71
|
+
.filter(isRequestOrResponseNode)
|
|
72
|
+
.map(child => this.getText(child))
|
|
73
|
+
.join('\n\n---\n\n');
|
|
74
|
+
if (text) {
|
|
75
|
+
this.clipboardService.writeText(text);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
isEnabled: (...args: unknown[]) => containsRequestOrResponseNode(args)
|
|
80
|
+
});
|
|
81
|
+
commands.registerCommand(ChatViewCommands.COPY_CODE, {
|
|
82
|
+
execute: (...args: unknown[]) => {
|
|
83
|
+
if (containsCode(args)) {
|
|
84
|
+
const code = args
|
|
85
|
+
.filter(isCodeArg)
|
|
86
|
+
.map(arg => arg.code)
|
|
87
|
+
.join();
|
|
88
|
+
this.clipboardService.writeText(code);
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
isEnabled: (...args: unknown[]) => containsRequestOrResponseNode(args) && containsCode(args)
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
protected copyMessage(args: (RequestNode | ResponseNode)[]): void {
|
|
96
|
+
const text = this.getTextAndJoin(args);
|
|
97
|
+
this.clipboardService.writeText(text);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
protected getTextAndJoin(args: (RequestNode | ResponseNode)[] | undefined): string {
|
|
101
|
+
return args !== undefined ? args.map(arg => this.getText(arg)).join() : '';
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
protected getText(arg: RequestNode | ResponseNode): string {
|
|
105
|
+
if (isRequestNode(arg)) {
|
|
106
|
+
return arg.request.request.text;
|
|
107
|
+
} else if (isResponseNode(arg)) {
|
|
108
|
+
return arg.response.response.asString();
|
|
109
|
+
}
|
|
110
|
+
return '';
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
registerMenus(menus: MenuModelRegistry): void {
|
|
114
|
+
menus.registerMenuAction([...ChatViewTreeWidget.CONTEXT_MENU, '_1'], {
|
|
115
|
+
commandId: CommonCommands.COPY.id
|
|
116
|
+
});
|
|
117
|
+
menus.registerMenuAction([...ChatViewTreeWidget.CONTEXT_MENU, '_1'], {
|
|
118
|
+
commandId: ChatViewCommands.COPY_MESSAGE.id
|
|
119
|
+
});
|
|
120
|
+
menus.registerMenuAction([...ChatViewTreeWidget.CONTEXT_MENU, '_1'], {
|
|
121
|
+
commandId: ChatViewCommands.COPY_ALL.id
|
|
122
|
+
});
|
|
123
|
+
menus.registerMenuAction([...ChatViewTreeWidget.CONTEXT_MENU, '_1'], {
|
|
124
|
+
commandId: ChatViewCommands.COPY_CODE.id
|
|
125
|
+
});
|
|
126
|
+
menus.registerMenuAction([...AIChatInputWidget.CONTEXT_MENU, '_1'], {
|
|
127
|
+
commandId: CommonCommands.COPY.id
|
|
128
|
+
});
|
|
129
|
+
menus.registerMenuAction([...AIChatInputWidget.CONTEXT_MENU, '_1'], {
|
|
130
|
+
commandId: CommonCommands.PASTE.id
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function extractRequestOrResponseNodes(args: unknown[]): (RequestNode | ResponseNode)[] {
|
|
137
|
+
return args.filter(arg => isRequestOrResponseNode(arg)) as (RequestNode | ResponseNode)[];
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function containsRequestOrResponseNode(args: unknown[]): args is (unknown | RequestNode | ResponseNode)[] {
|
|
141
|
+
return extractRequestOrResponseNodes(args).length > 0;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function isRequestOrResponseNode(arg: unknown): arg is RequestNode | ResponseNode {
|
|
145
|
+
return TreeNode.is(arg) && (isRequestNode(arg) || isResponseNode(arg));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function containsCode(args: unknown[]): args is (unknown | { code: string })[] {
|
|
149
|
+
return args.filter(arg => isCodeArg(arg)).length > 0;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function isCodeArg(arg: unknown): arg is { code: string } {
|
|
153
|
+
return isObject(arg) && 'code' in arg;
|
|
154
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2024 EclipseSource GmbH.
|
|
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
|
+
import { inject, injectable, named } from '@theia/core/shared/inversify';
|
|
17
|
+
import { FrontendApplication, FrontendApplicationContribution } from '@theia/core/lib/browser';
|
|
18
|
+
import * as monaco from '@theia/monaco-editor-core';
|
|
19
|
+
import { ContributionProvider, MaybePromise } from '@theia/core';
|
|
20
|
+
import { ProviderResult } from '@theia/monaco-editor-core/esm/vs/editor/common/languages';
|
|
21
|
+
import { ChatAgentService } from '@theia/ai-chat';
|
|
22
|
+
import { AIVariableService } from '@theia/ai-core/lib/common';
|
|
23
|
+
import { ToolProvider } from '@theia/ai-core/lib/common/tool-invocation-registry';
|
|
24
|
+
|
|
25
|
+
export const CHAT_VIEW_LANGUAGE_ID = 'theia-ai-chat-view-language';
|
|
26
|
+
export const CHAT_VIEW_LANGUAGE_EXTENSION = 'aichatviewlanguage';
|
|
27
|
+
|
|
28
|
+
@injectable()
|
|
29
|
+
export class ChatViewLanguageContribution implements FrontendApplicationContribution {
|
|
30
|
+
|
|
31
|
+
@inject(ChatAgentService)
|
|
32
|
+
protected readonly agentService: ChatAgentService;
|
|
33
|
+
|
|
34
|
+
@inject(AIVariableService)
|
|
35
|
+
protected readonly variableService: AIVariableService;
|
|
36
|
+
|
|
37
|
+
@inject(ContributionProvider)
|
|
38
|
+
@named(ToolProvider)
|
|
39
|
+
private providers: ContributionProvider<ToolProvider>;
|
|
40
|
+
|
|
41
|
+
onStart(_app: FrontendApplication): MaybePromise<void> {
|
|
42
|
+
console.log('ChatViewLanguageContribution started');
|
|
43
|
+
monaco.languages.register({ id: CHAT_VIEW_LANGUAGE_ID, extensions: [CHAT_VIEW_LANGUAGE_EXTENSION] });
|
|
44
|
+
|
|
45
|
+
monaco.languages.registerCompletionItemProvider(CHAT_VIEW_LANGUAGE_ID, {
|
|
46
|
+
triggerCharacters: ['@'],
|
|
47
|
+
provideCompletionItems: (model, position, _context, _token): ProviderResult<monaco.languages.CompletionList> => this.provideAgentCompletions(model, position),
|
|
48
|
+
});
|
|
49
|
+
monaco.languages.registerCompletionItemProvider(CHAT_VIEW_LANGUAGE_ID, {
|
|
50
|
+
triggerCharacters: ['#'],
|
|
51
|
+
provideCompletionItems: (model, position, _context, _token): ProviderResult<monaco.languages.CompletionList> => this.provideVariableCompletions(model, position),
|
|
52
|
+
});
|
|
53
|
+
monaco.languages.registerCompletionItemProvider(CHAT_VIEW_LANGUAGE_ID, {
|
|
54
|
+
triggerCharacters: ['~'],
|
|
55
|
+
provideCompletionItems: (model, position, _context, _token): ProviderResult<monaco.languages.CompletionList> => this.provideToolCompletions(model, position),
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
getCompletionRange(model: monaco.editor.ITextModel, position: monaco.Position, triggerCharacter: string): monaco.Range | undefined {
|
|
60
|
+
// Check if the character before the current position is the trigger character
|
|
61
|
+
const lineContent = model.getLineContent(position.lineNumber);
|
|
62
|
+
const characterBefore = lineContent[position.column - 2]; // Get the character before the current position
|
|
63
|
+
|
|
64
|
+
if (characterBefore !== triggerCharacter) {
|
|
65
|
+
// Do not return agent suggestions if the user didn't just type the trigger character
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Calculate the range from the position of the '@' character
|
|
70
|
+
const wordInfo = model.getWordUntilPosition(position);
|
|
71
|
+
return new monaco.Range(
|
|
72
|
+
position.lineNumber,
|
|
73
|
+
wordInfo.startColumn,
|
|
74
|
+
position.lineNumber,
|
|
75
|
+
position.column
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private getSuggestions<T>(
|
|
80
|
+
model: monaco.editor.ITextModel,
|
|
81
|
+
position: monaco.Position,
|
|
82
|
+
triggerChar: string,
|
|
83
|
+
items: T[],
|
|
84
|
+
kind: monaco.languages.CompletionItemKind,
|
|
85
|
+
getId: (item: T) => string,
|
|
86
|
+
getName: (item: T) => string,
|
|
87
|
+
getDescription: (item: T) => string
|
|
88
|
+
): ProviderResult<monaco.languages.CompletionList> {
|
|
89
|
+
const completionRange = this.getCompletionRange(model, position, triggerChar);
|
|
90
|
+
if (completionRange === undefined) {
|
|
91
|
+
return { suggestions: [] };
|
|
92
|
+
}
|
|
93
|
+
const suggestions = items.map(item => ({
|
|
94
|
+
insertText: getId(item),
|
|
95
|
+
kind: kind,
|
|
96
|
+
label: getName(item),
|
|
97
|
+
range: completionRange,
|
|
98
|
+
detail: getDescription(item),
|
|
99
|
+
}));
|
|
100
|
+
return { suggestions };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
provideAgentCompletions(model: monaco.editor.ITextModel, position: monaco.Position): ProviderResult<monaco.languages.CompletionList> {
|
|
104
|
+
return this.getSuggestions(
|
|
105
|
+
model,
|
|
106
|
+
position,
|
|
107
|
+
'@',
|
|
108
|
+
this.agentService.getAgents(),
|
|
109
|
+
monaco.languages.CompletionItemKind.Value,
|
|
110
|
+
agent => agent.id,
|
|
111
|
+
agent => agent.name,
|
|
112
|
+
agent => agent.description
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
provideVariableCompletions(model: monaco.editor.ITextModel, position: monaco.Position): ProviderResult<monaco.languages.CompletionList> {
|
|
117
|
+
return this.getSuggestions(
|
|
118
|
+
model,
|
|
119
|
+
position,
|
|
120
|
+
'#',
|
|
121
|
+
this.variableService.getVariables(),
|
|
122
|
+
monaco.languages.CompletionItemKind.Variable,
|
|
123
|
+
variable => variable.name,
|
|
124
|
+
variable => variable.name,
|
|
125
|
+
variable => variable.description
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
provideToolCompletions(model: monaco.editor.ITextModel, position: monaco.Position): ProviderResult<monaco.languages.CompletionList> {
|
|
130
|
+
return this.getSuggestions(
|
|
131
|
+
model,
|
|
132
|
+
position,
|
|
133
|
+
'~',
|
|
134
|
+
this.providers.getContributions().map(provider => provider.getTool()),
|
|
135
|
+
monaco.languages.CompletionItemKind.Function,
|
|
136
|
+
tool => tool.id,
|
|
137
|
+
tool => tool.name,
|
|
138
|
+
tool => tool.description ?? ''
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2024 EclipseSource GmbH.
|
|
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, postConstruct } from '@theia/core/shared/inversify';
|
|
18
|
+
import { TabBarToolbarContribution, TabBarToolbarRegistry } from '@theia/core/lib/browser/shell/tab-bar-toolbar';
|
|
19
|
+
import { AIChatContribution } from './ai-chat-ui-contribution';
|
|
20
|
+
import { Emitter, nls } from '@theia/core';
|
|
21
|
+
import { ChatCommands } from './chat-view-commands';
|
|
22
|
+
|
|
23
|
+
@injectable()
|
|
24
|
+
export class ChatViewWidgetToolbarContribution implements TabBarToolbarContribution {
|
|
25
|
+
@inject(AIChatContribution)
|
|
26
|
+
protected readonly chatContribution: AIChatContribution;
|
|
27
|
+
|
|
28
|
+
protected readonly onChatWidgetStateChangedEmitter = new Emitter<void>();
|
|
29
|
+
protected readonly onChatWidgetStateChanged = this.onChatWidgetStateChangedEmitter.event;
|
|
30
|
+
|
|
31
|
+
@postConstruct()
|
|
32
|
+
protected init(): void {
|
|
33
|
+
this.chatContribution.widget.then(widget => {
|
|
34
|
+
widget.onStateChanged(() => this.onChatWidgetStateChangedEmitter.fire());
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
registerToolbarItems(registry: TabBarToolbarRegistry): void {
|
|
39
|
+
registry.registerItem({
|
|
40
|
+
id: ChatCommands.SCROLL_LOCK_WIDGET.id,
|
|
41
|
+
command: ChatCommands.SCROLL_LOCK_WIDGET.id,
|
|
42
|
+
tooltip: nls.localizeByDefault('Turn Auto Scrolling Off'),
|
|
43
|
+
onDidChange: this.onChatWidgetStateChanged,
|
|
44
|
+
priority: 2
|
|
45
|
+
});
|
|
46
|
+
registry.registerItem({
|
|
47
|
+
id: ChatCommands.SCROLL_UNLOCK_WIDGET.id,
|
|
48
|
+
command: ChatCommands.SCROLL_UNLOCK_WIDGET.id,
|
|
49
|
+
tooltip: nls.localizeByDefault('Turn Auto Scrolling On'),
|
|
50
|
+
onDidChange: this.onChatWidgetStateChanged,
|
|
51
|
+
priority: 2
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2024 EclipseSource GmbH.
|
|
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
|
+
import { CommandService, deepClone, Emitter, Event, MessageService } from '@theia/core';
|
|
17
|
+
import { ChatRequest, ChatRequestModel, ChatRequestModelImpl, ChatService, ChatSession } from '@theia/ai-chat';
|
|
18
|
+
import { BaseWidget, codicon, ExtractableWidget, PanelLayout, PreferenceService, StatefulWidget } from '@theia/core/lib/browser';
|
|
19
|
+
import { nls } from '@theia/core/lib/common/nls';
|
|
20
|
+
import { inject, injectable, postConstruct } from '@theia/core/shared/inversify';
|
|
21
|
+
import { AIChatInputWidget } from './chat-input-widget';
|
|
22
|
+
import { ChatViewTreeWidget } from './chat-tree-view/chat-view-tree-widget';
|
|
23
|
+
import { AIActivationService } from '@theia/ai-core/lib/browser/ai-activation-service';
|
|
24
|
+
|
|
25
|
+
export namespace ChatViewWidget {
|
|
26
|
+
export interface State {
|
|
27
|
+
locked?: boolean;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@injectable()
|
|
32
|
+
export class ChatViewWidget extends BaseWidget implements ExtractableWidget, StatefulWidget {
|
|
33
|
+
|
|
34
|
+
public static ID = 'chat-view-widget';
|
|
35
|
+
static LABEL = `✨ ${nls.localizeByDefault('Chat')} [Experimental]`;
|
|
36
|
+
|
|
37
|
+
@inject(ChatService)
|
|
38
|
+
protected chatService: ChatService;
|
|
39
|
+
|
|
40
|
+
@inject(MessageService)
|
|
41
|
+
protected messageService: MessageService;
|
|
42
|
+
|
|
43
|
+
@inject(PreferenceService)
|
|
44
|
+
protected readonly preferenceService: PreferenceService;
|
|
45
|
+
|
|
46
|
+
@inject(CommandService)
|
|
47
|
+
protected readonly commandService: CommandService;
|
|
48
|
+
|
|
49
|
+
@inject(AIActivationService)
|
|
50
|
+
protected readonly activationService: AIActivationService;
|
|
51
|
+
|
|
52
|
+
protected chatSession: ChatSession;
|
|
53
|
+
|
|
54
|
+
protected _state: ChatViewWidget.State = { locked: false };
|
|
55
|
+
protected readonly onStateChangedEmitter = new Emitter<ChatViewWidget.State>();
|
|
56
|
+
|
|
57
|
+
secondaryWindow: Window | undefined;
|
|
58
|
+
|
|
59
|
+
constructor(
|
|
60
|
+
@inject(ChatViewTreeWidget)
|
|
61
|
+
readonly treeWidget: ChatViewTreeWidget,
|
|
62
|
+
@inject(AIChatInputWidget)
|
|
63
|
+
readonly inputWidget: AIChatInputWidget
|
|
64
|
+
) {
|
|
65
|
+
super();
|
|
66
|
+
this.id = ChatViewWidget.ID;
|
|
67
|
+
this.title.label = ChatViewWidget.LABEL;
|
|
68
|
+
this.title.caption = ChatViewWidget.LABEL;
|
|
69
|
+
this.title.iconClass = codicon('comment-discussion');
|
|
70
|
+
this.title.closable = true;
|
|
71
|
+
this.node.classList.add('chat-view-widget');
|
|
72
|
+
this.update();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
@postConstruct()
|
|
76
|
+
protected init(): void {
|
|
77
|
+
this.toDispose.pushAll([
|
|
78
|
+
this.treeWidget,
|
|
79
|
+
this.inputWidget,
|
|
80
|
+
this.onStateChanged(newState => {
|
|
81
|
+
this.treeWidget.shouldScrollToEnd = !newState.locked;
|
|
82
|
+
this.update();
|
|
83
|
+
})
|
|
84
|
+
]);
|
|
85
|
+
const layout = this.layout = new PanelLayout();
|
|
86
|
+
|
|
87
|
+
this.treeWidget.node.classList.add('chat-tree-view-widget');
|
|
88
|
+
layout.addWidget(this.treeWidget);
|
|
89
|
+
this.inputWidget.node.classList.add('chat-input-widget');
|
|
90
|
+
layout.addWidget(this.inputWidget);
|
|
91
|
+
this.chatSession = this.chatService.createSession();
|
|
92
|
+
|
|
93
|
+
this.inputWidget.onQuery = this.onQuery.bind(this);
|
|
94
|
+
this.inputWidget.onCancel = this.onCancel.bind(this);
|
|
95
|
+
this.inputWidget.chatModel = this.chatSession.model;
|
|
96
|
+
this.treeWidget.trackChatModel(this.chatSession.model);
|
|
97
|
+
|
|
98
|
+
this.initListeners();
|
|
99
|
+
|
|
100
|
+
this.inputWidget.setEnabled(this.activationService.isActive);
|
|
101
|
+
this.treeWidget.setEnabled(this.activationService.isActive);
|
|
102
|
+
|
|
103
|
+
this.activationService.onDidChangeActiveStatus(change => {
|
|
104
|
+
this.treeWidget.setEnabled(change);
|
|
105
|
+
this.inputWidget.setEnabled(change);
|
|
106
|
+
this.update();
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
protected initListeners(): void {
|
|
111
|
+
this.toDispose.push(
|
|
112
|
+
this.chatService.onActiveSessionChanged(event => {
|
|
113
|
+
const session = event.sessionId ? this.chatService.getSession(event.sessionId) : this.chatService.createSession();
|
|
114
|
+
if (session) {
|
|
115
|
+
this.chatSession = session;
|
|
116
|
+
this.treeWidget.trackChatModel(this.chatSession.model);
|
|
117
|
+
this.inputWidget.chatModel = this.chatSession.model;
|
|
118
|
+
if (event.focus) {
|
|
119
|
+
this.show();
|
|
120
|
+
}
|
|
121
|
+
} else {
|
|
122
|
+
console.warn(`Session with ${event.sessionId} not found.`);
|
|
123
|
+
}
|
|
124
|
+
})
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
storeState(): object {
|
|
129
|
+
return this.state;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
restoreState(oldState: object & Partial<ChatViewWidget.State>): void {
|
|
133
|
+
const copy = deepClone(this.state);
|
|
134
|
+
if (oldState.locked) {
|
|
135
|
+
copy.locked = oldState.locked;
|
|
136
|
+
}
|
|
137
|
+
this.state = copy;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
protected get state(): ChatViewWidget.State {
|
|
141
|
+
return this._state;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
protected set state(state: ChatViewWidget.State) {
|
|
145
|
+
this._state = state;
|
|
146
|
+
this.onStateChangedEmitter.fire(this._state);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
get onStateChanged(): Event<ChatViewWidget.State> {
|
|
150
|
+
return this.onStateChangedEmitter.event;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
protected async onQuery(query: string): Promise<void> {
|
|
154
|
+
if (query.length === 0) { return; }
|
|
155
|
+
|
|
156
|
+
const chatRequest: ChatRequest = {
|
|
157
|
+
text: query
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const requestProgress = await this.chatService.sendRequest(this.chatSession.id, chatRequest);
|
|
161
|
+
requestProgress?.responseCompleted.then(responseModel => {
|
|
162
|
+
if (responseModel.isError) {
|
|
163
|
+
this.messageService.error(responseModel.errorObject?.message ?? 'An error occurred druring chat service invocation.');
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
if (!requestProgress) {
|
|
167
|
+
this.messageService.error(`Was not able to send request "${chatRequest.text}" to session ${this.chatSession.id}`);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
// Tree Widget currently tracks the ChatModel itself. Therefore no notification necessary.
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
protected onCancel(requestModel: ChatRequestModel): void {
|
|
174
|
+
// TODO we should pass a cancellation token with the request (or retrieve one from the request invocation) so we can cleanly cancel here
|
|
175
|
+
// For now we cancel manually via casting
|
|
176
|
+
(requestModel as ChatRequestModelImpl).response.cancel();
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
lock(): void {
|
|
180
|
+
this.state = { ...deepClone(this.state), locked: true };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
unlock(): void {
|
|
184
|
+
this.state = { ...deepClone(this.state), locked: false };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
get isLocked(): boolean {
|
|
188
|
+
return !!this.state.locked;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
get isExtractable(): boolean {
|
|
192
|
+
return this.secondaryWindow === undefined;
|
|
193
|
+
}
|
|
194
|
+
}
|