@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.
Files changed (123) hide show
  1. package/README.md +33 -0
  2. package/lib/browser/ai-chat-ui-contribution.d.ts +24 -0
  3. package/lib/browser/ai-chat-ui-contribution.d.ts.map +1 -0
  4. package/lib/browser/ai-chat-ui-contribution.js +161 -0
  5. package/lib/browser/ai-chat-ui-contribution.js.map +1 -0
  6. package/lib/browser/ai-chat-ui-frontend-module.d.ts +5 -0
  7. package/lib/browser/ai-chat-ui-frontend-module.d.ts.map +1 -0
  8. package/lib/browser/ai-chat-ui-frontend-module.js +87 -0
  9. package/lib/browser/ai-chat-ui-frontend-module.js.map +1 -0
  10. package/lib/browser/chat-input-widget.d.ts +32 -0
  11. package/lib/browser/chat-input-widget.d.ts.map +1 -0
  12. package/lib/browser/chat-input-widget.js +217 -0
  13. package/lib/browser/chat-input-widget.js.map +1 -0
  14. package/lib/browser/chat-node-toolbar-action-contribution.d.ts +47 -0
  15. package/lib/browser/chat-node-toolbar-action-contribution.d.ts.map +1 -0
  16. package/lib/browser/chat-node-toolbar-action-contribution.js +25 -0
  17. package/lib/browser/chat-node-toolbar-action-contribution.js.map +1 -0
  18. package/lib/browser/chat-response-part-renderer.d.ts +10 -0
  19. package/lib/browser/chat-response-part-renderer.d.ts.map +1 -0
  20. package/lib/browser/chat-response-part-renderer.js +20 -0
  21. package/lib/browser/chat-response-part-renderer.js.map +1 -0
  22. package/lib/browser/chat-response-renderer/ai-editor-manager.d.ts +36 -0
  23. package/lib/browser/chat-response-renderer/ai-editor-manager.d.ts.map +1 -0
  24. package/lib/browser/chat-response-renderer/ai-editor-manager.js +184 -0
  25. package/lib/browser/chat-response-renderer/ai-editor-manager.js.map +1 -0
  26. package/lib/browser/chat-response-renderer/code-part-renderer.d.ts +45 -0
  27. package/lib/browser/chat-response-renderer/code-part-renderer.d.ts.map +1 -0
  28. package/lib/browser/chat-response-renderer/code-part-renderer.js +190 -0
  29. package/lib/browser/chat-response-renderer/code-part-renderer.js.map +1 -0
  30. package/lib/browser/chat-response-renderer/command-part-renderer.d.ts +12 -0
  31. package/lib/browser/chat-response-renderer/command-part-renderer.d.ts.map +1 -0
  32. package/lib/browser/chat-response-renderer/command-part-renderer.js +70 -0
  33. package/lib/browser/chat-response-renderer/command-part-renderer.js.map +1 -0
  34. package/lib/browser/chat-response-renderer/error-part-renderer.d.ts +9 -0
  35. package/lib/browser/chat-response-renderer/error-part-renderer.d.ts.map +1 -0
  36. package/lib/browser/chat-response-renderer/error-part-renderer.js +40 -0
  37. package/lib/browser/chat-response-renderer/error-part-renderer.js.map +1 -0
  38. package/lib/browser/chat-response-renderer/horizontal-layout-part-renderer.d.ts +12 -0
  39. package/lib/browser/chat-response-renderer/horizontal-layout-part-renderer.d.ts.map +1 -0
  40. package/lib/browser/chat-response-renderer/horizontal-layout-part-renderer.js +54 -0
  41. package/lib/browser/chat-response-renderer/horizontal-layout-part-renderer.js.map +1 -0
  42. package/lib/browser/chat-response-renderer/index.d.ts +9 -0
  43. package/lib/browser/chat-response-renderer/index.d.ts.map +1 -0
  44. package/lib/browser/chat-response-renderer/index.js +27 -0
  45. package/lib/browser/chat-response-renderer/index.js.map +1 -0
  46. package/lib/browser/chat-response-renderer/markdown-part-renderer.d.ts +25 -0
  47. package/lib/browser/chat-response-renderer/markdown-part-renderer.d.ts.map +1 -0
  48. package/lib/browser/chat-response-renderer/markdown-part-renderer.js +89 -0
  49. package/lib/browser/chat-response-renderer/markdown-part-renderer.js.map +1 -0
  50. package/lib/browser/chat-response-renderer/question-part-renderer.d.ts +10 -0
  51. package/lib/browser/chat-response-renderer/question-part-renderer.d.ts.map +1 -0
  52. package/lib/browser/chat-response-renderer/question-part-renderer.js +43 -0
  53. package/lib/browser/chat-response-renderer/question-part-renderer.js.map +1 -0
  54. package/lib/browser/chat-response-renderer/text-part-renderer.d.ts +9 -0
  55. package/lib/browser/chat-response-renderer/text-part-renderer.d.ts.map +1 -0
  56. package/lib/browser/chat-response-renderer/text-part-renderer.js +41 -0
  57. package/lib/browser/chat-response-renderer/text-part-renderer.js.map +1 -0
  58. package/lib/browser/chat-response-renderer/text-part-renderer.spec.d.ts +2 -0
  59. package/lib/browser/chat-response-renderer/text-part-renderer.spec.d.ts.map +1 -0
  60. package/lib/browser/chat-response-renderer/text-part-renderer.spec.js +46 -0
  61. package/lib/browser/chat-response-renderer/text-part-renderer.spec.js.map +1 -0
  62. package/lib/browser/chat-response-renderer/toolcall-part-renderer.d.ts +12 -0
  63. package/lib/browser/chat-response-renderer/toolcall-part-renderer.d.ts.map +1 -0
  64. package/lib/browser/chat-response-renderer/toolcall-part-renderer.js +81 -0
  65. package/lib/browser/chat-response-renderer/toolcall-part-renderer.js.map +1 -0
  66. package/lib/browser/chat-tree-view/chat-view-tree-container.d.ts +4 -0
  67. package/lib/browser/chat-tree-view/chat-view-tree-container.d.ts.map +1 -0
  68. package/lib/browser/chat-tree-view/chat-view-tree-container.js +33 -0
  69. package/lib/browser/chat-tree-view/chat-view-tree-container.js.map +1 -0
  70. package/lib/browser/chat-tree-view/chat-view-tree-widget.d.ts +56 -0
  71. package/lib/browser/chat-tree-view/chat-view-tree-widget.d.ts.map +1 -0
  72. package/lib/browser/chat-tree-view/chat-view-tree-widget.js +388 -0
  73. package/lib/browser/chat-tree-view/chat-view-tree-widget.js.map +1 -0
  74. package/lib/browser/chat-tree-view/index.d.ts +3 -0
  75. package/lib/browser/chat-tree-view/index.d.ts.map +1 -0
  76. package/lib/browser/chat-tree-view/index.js +21 -0
  77. package/lib/browser/chat-tree-view/index.js.map +1 -0
  78. package/lib/browser/chat-view-commands.d.ts +8 -0
  79. package/lib/browser/chat-view-commands.d.ts.map +1 -0
  80. package/lib/browser/chat-view-commands.js +44 -0
  81. package/lib/browser/chat-view-commands.js.map +1 -0
  82. package/lib/browser/chat-view-contribution.d.ts +18 -0
  83. package/lib/browser/chat-view-contribution.d.ts.map +1 -0
  84. package/lib/browser/chat-view-contribution.js +153 -0
  85. package/lib/browser/chat-view-contribution.js.map +1 -0
  86. package/lib/browser/chat-view-language-contribution.d.ts +20 -0
  87. package/lib/browser/chat-view-language-contribution.d.ts.map +1 -0
  88. package/lib/browser/chat-view-language-contribution.js +98 -0
  89. package/lib/browser/chat-view-language-contribution.js.map +1 -0
  90. package/lib/browser/chat-view-widget-toolbar-contribution.d.ts +11 -0
  91. package/lib/browser/chat-view-widget-toolbar-contribution.d.ts.map +1 -0
  92. package/lib/browser/chat-view-widget-toolbar-contribution.js +65 -0
  93. package/lib/browser/chat-view-widget-toolbar-contribution.js.map +1 -0
  94. package/lib/browser/chat-view-widget.d.ts +41 -0
  95. package/lib/browser/chat-view-widget.d.ts.map +1 -0
  96. package/lib/browser/chat-view-widget.js +182 -0
  97. package/lib/browser/chat-view-widget.js.map +1 -0
  98. package/package.json +59 -0
  99. package/src/browser/ai-chat-ui-contribution.ts +171 -0
  100. package/src/browser/ai-chat-ui-frontend-module.ts +105 -0
  101. package/src/browser/chat-input-widget.tsx +262 -0
  102. package/src/browser/chat-node-toolbar-action-contribution.ts +63 -0
  103. package/src/browser/chat-response-part-renderer.ts +25 -0
  104. package/src/browser/chat-response-renderer/ai-editor-manager.ts +183 -0
  105. package/src/browser/chat-response-renderer/code-part-renderer.tsx +211 -0
  106. package/src/browser/chat-response-renderer/command-part-renderer.tsx +60 -0
  107. package/src/browser/chat-response-renderer/error-part-renderer.tsx +35 -0
  108. package/src/browser/chat-response-renderer/horizontal-layout-part-renderer.tsx +59 -0
  109. package/src/browser/chat-response-renderer/index.ts +23 -0
  110. package/src/browser/chat-response-renderer/markdown-part-renderer.tsx +92 -0
  111. package/src/browser/chat-response-renderer/question-part-renderer.tsx +59 -0
  112. package/src/browser/chat-response-renderer/text-part-renderer.spec.ts +50 -0
  113. package/src/browser/chat-response-renderer/text-part-renderer.tsx +35 -0
  114. package/src/browser/chat-response-renderer/toolcall-part-renderer.tsx +89 -0
  115. package/src/browser/chat-tree-view/chat-view-tree-container.ts +32 -0
  116. package/src/browser/chat-tree-view/chat-view-tree-widget.tsx +526 -0
  117. package/src/browser/chat-tree-view/index.ts +18 -0
  118. package/src/browser/chat-view-commands.ts +45 -0
  119. package/src/browser/chat-view-contribution.ts +154 -0
  120. package/src/browser/chat-view-language-contribution.ts +141 -0
  121. package/src/browser/chat-view-widget-toolbar-contribution.tsx +54 -0
  122. package/src/browser/chat-view-widget.tsx +194 -0
  123. package/src/browser/style/index.css +415 -0
@@ -0,0 +1,211 @@
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 {
18
+ ChatResponseContent,
19
+ CodeChatResponseContent,
20
+ } from '@theia/ai-chat/lib/common';
21
+ import { UntitledResourceResolver, URI } from '@theia/core';
22
+ import { ContextMenuRenderer, TreeNode } from '@theia/core/lib/browser';
23
+ import { ClipboardService } from '@theia/core/lib/browser/clipboard-service';
24
+ import { inject, injectable } from '@theia/core/shared/inversify';
25
+ import * as React from '@theia/core/shared/react';
26
+ import { ReactNode } from '@theia/core/shared/react';
27
+ import { Position } from '@theia/core/shared/vscode-languageserver-protocol';
28
+ import { EditorManager, EditorWidget } from '@theia/editor/lib/browser';
29
+ import { MonacoEditor } from '@theia/monaco/lib/browser/monaco-editor';
30
+ import { MonacoEditorProvider } from '@theia/monaco/lib/browser/monaco-editor-provider';
31
+ import { MonacoLanguages } from '@theia/monaco/lib/browser/monaco-languages';
32
+ import { ChatResponsePartRenderer } from '../chat-response-part-renderer';
33
+ import { ChatViewTreeWidget, ResponseNode } from '../chat-tree-view/chat-view-tree-widget';
34
+ import { IMouseEvent } from '@theia/monaco-editor-core';
35
+
36
+ @injectable()
37
+ export class CodePartRenderer
38
+ implements ChatResponsePartRenderer<CodeChatResponseContent> {
39
+
40
+ @inject(ClipboardService)
41
+ protected readonly clipboardService: ClipboardService;
42
+ @inject(EditorManager)
43
+ protected readonly editorManager: EditorManager;
44
+ @inject(UntitledResourceResolver)
45
+ protected readonly untitledResourceResolver: UntitledResourceResolver;
46
+ @inject(MonacoEditorProvider)
47
+ protected readonly editorProvider: MonacoEditorProvider;
48
+ @inject(MonacoLanguages)
49
+ protected readonly languageService: MonacoLanguages;
50
+ @inject(ContextMenuRenderer)
51
+ protected readonly contextMenuRenderer: ContextMenuRenderer;
52
+
53
+ canHandle(response: ChatResponseContent): number {
54
+ if (CodeChatResponseContent.is(response)) {
55
+ return 10;
56
+ }
57
+ return -1;
58
+ }
59
+
60
+ render(response: CodeChatResponseContent, parentNode: ResponseNode): ReactNode {
61
+ const language = response.language ? this.languageService.getExtension(response.language) : undefined;
62
+
63
+ return (
64
+ <div className="theia-CodePartRenderer-root">
65
+ <div className="theia-CodePartRenderer-top">
66
+ <div className="theia-CodePartRenderer-left">{this.renderTitle(response)}</div>
67
+ <div className="theia-CodePartRenderer-right">
68
+ <CopyToClipboardButton code={response.code} clipboardService={this.clipboardService} />
69
+ <InsertCodeAtCursorButton code={response.code} editorManager={this.editorManager} />
70
+ </div>
71
+ </div>
72
+ <div className="theia-CodePartRenderer-separator"></div>
73
+ <div className="theia-CodePartRenderer-bottom">
74
+ <CodeWrapper
75
+ content={response.code}
76
+ language={language}
77
+ editorProvider={this.editorProvider}
78
+ untitledResourceResolver={this.untitledResourceResolver}
79
+ contextMenuCallback={e => this.handleContextMenuEvent(parentNode, e, response.code)}></CodeWrapper>
80
+ </div>
81
+ </div>
82
+ );
83
+ }
84
+
85
+ protected renderTitle(response: CodeChatResponseContent): ReactNode {
86
+ const uri = response.location?.uri;
87
+ const position = response.location?.position;
88
+ if (uri && position) {
89
+ return <a onClick={this.openFileAtPosition.bind(this, uri, position)}>{this.getTitle(response.location?.uri, response.language)}</a>;
90
+ }
91
+ return this.getTitle(response.location?.uri, response.language);
92
+ }
93
+
94
+ private getTitle(uri: URI | undefined, language: string | undefined): string {
95
+ // If there is a URI, use the file name as the title. Otherwise, use the language as the title.
96
+ // If there is no language, use a generic fallback title.
97
+ return uri?.path?.toString().split('/').pop() ?? language ?? 'Generated Code';
98
+ }
99
+
100
+ /**
101
+ * Opens a file and moves the cursor to the specified position.
102
+ *
103
+ * @param uri - The URI of the file to open.
104
+ * @param position - The position to move the cursor to, specified as {line, character}.
105
+ */
106
+ async openFileAtPosition(uri: URI, position: Position): Promise<void> {
107
+ const editorWidget = await this.editorManager.open(uri) as EditorWidget;
108
+ if (editorWidget) {
109
+ const editor = editorWidget.editor;
110
+ editor.revealPosition(position);
111
+ editor.focus();
112
+ editor.cursor = position;
113
+ }
114
+ }
115
+
116
+ protected handleContextMenuEvent(node: TreeNode | undefined, event: IMouseEvent, code: string): void {
117
+ this.contextMenuRenderer.render({
118
+ menuPath: ChatViewTreeWidget.CONTEXT_MENU,
119
+ anchor: { x: event.posx, y: event.posy },
120
+ args: [node, { code }]
121
+ });
122
+ event.preventDefault();
123
+ }
124
+ }
125
+
126
+ const CopyToClipboardButton = (props: { code: string, clipboardService: ClipboardService }) => {
127
+ const { code, clipboardService } = props;
128
+ const copyCodeToClipboard = React.useCallback(() => {
129
+ clipboardService.writeText(code);
130
+ }, [code, clipboardService]);
131
+ return <div className='button codicon codicon-copy' title='Copy' role='button' onClick={copyCodeToClipboard}></div>;
132
+ };
133
+
134
+ const InsertCodeAtCursorButton = (props: { code: string, editorManager: EditorManager }) => {
135
+ const { code, editorManager } = props;
136
+ const insertCode = React.useCallback(() => {
137
+ const editor = editorManager.currentEditor;
138
+ if (editor) {
139
+ const currentEditor = editor.editor;
140
+ const selection = currentEditor.selection;
141
+
142
+ // Insert the text at the current cursor position
143
+ // If there is a selection, replace the selection with the text
144
+ currentEditor.executeEdits([{
145
+ range: {
146
+ start: selection.start,
147
+ end: selection.end
148
+ },
149
+ newText: code
150
+ }]);
151
+ }
152
+ }, [code, editorManager]);
153
+ return <div className='button codicon codicon-insert' title='Insert at Cursor' role='button' onClick={insertCode}></div>;
154
+ };
155
+
156
+ /**
157
+ * Renders the given code within a Monaco Editor
158
+ */
159
+ export const CodeWrapper = (props: {
160
+ content: string,
161
+ language?: string,
162
+ untitledResourceResolver: UntitledResourceResolver,
163
+ editorProvider: MonacoEditorProvider,
164
+ contextMenuCallback: (e: IMouseEvent) => void
165
+ }) => {
166
+ // eslint-disable-next-line no-null/no-null
167
+ const ref = React.useRef<HTMLDivElement | null>(null);
168
+ const editorRef = React.useRef<MonacoEditor | undefined>(undefined);
169
+
170
+ const createInputElement = async () => {
171
+ const resource = await props.untitledResourceResolver.createUntitledResource(undefined, props.language);
172
+ const editor = await props.editorProvider.createInline(resource.uri, ref.current!, {
173
+ readOnly: true,
174
+ autoSizing: true,
175
+ scrollBeyondLastLine: false,
176
+ scrollBeyondLastColumn: 0,
177
+ renderFinalNewline: 'off',
178
+ maxHeight: -1,
179
+ scrollbar: {
180
+ vertical: 'hidden',
181
+ alwaysConsumeMouseWheel: false
182
+ },
183
+ wordWrap: 'off',
184
+ codeLens: false,
185
+ inlayHints: { enabled: 'off' },
186
+ hover: { enabled: false }
187
+ });
188
+ editor.document.textEditorModel.setValue(props.content);
189
+ editor.getControl().onContextMenu(e => props.contextMenuCallback(e.event));
190
+ editorRef.current = editor;
191
+ };
192
+
193
+ React.useEffect(() => {
194
+ createInputElement();
195
+ return () => {
196
+ if (editorRef.current) {
197
+ editorRef.current.dispose();
198
+ }
199
+ };
200
+ }, []);
201
+
202
+ React.useEffect(() => {
203
+ if (editorRef.current) {
204
+ editorRef.current.document.textEditorModel.setValue(props.content);
205
+ }
206
+ }, [props.content]);
207
+
208
+ editorRef.current?.resizeToFit();
209
+
210
+ return <div className='theia-CodeWrapper' ref={ref}></div>;
211
+ };
@@ -0,0 +1,60 @@
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 { ChatResponsePartRenderer } from '../chat-response-part-renderer';
18
+ import { inject, injectable } from '@theia/core/shared/inversify';
19
+ import { ChatResponseContent, CommandChatResponseContent } from '@theia/ai-chat/lib/common';
20
+ import { ReactNode } from '@theia/core/shared/react';
21
+ import * as React from '@theia/core/shared/react';
22
+ import { CommandRegistry, CommandService } from '@theia/core';
23
+
24
+ @injectable()
25
+ export class CommandPartRenderer implements ChatResponsePartRenderer<CommandChatResponseContent> {
26
+ @inject(CommandService) private commandService: CommandService;
27
+ @inject(CommandRegistry) private commandRegistry: CommandRegistry;
28
+ canHandle(response: ChatResponseContent): number {
29
+ if (CommandChatResponseContent.is(response)) {
30
+ return 10;
31
+ }
32
+ return -1;
33
+ }
34
+ render(response: CommandChatResponseContent): ReactNode {
35
+ const label =
36
+ response.customCallback?.label ??
37
+ response.command?.label ??
38
+ response.command?.id
39
+ .split('-')
40
+ .map(s => s[0].toUpperCase() + s.substring(1))
41
+ .join(' ') ?? 'Execute';
42
+ if (!response.customCallback && response.command) {
43
+ const isCommandEnabled = this.commandRegistry.isEnabled(response.command.id);
44
+ if (!isCommandEnabled) {
45
+ return <div>The command has the id "{response.command.id}" but it is not executable from the Chat window.</div>;
46
+
47
+ }
48
+ }
49
+ return <button className='theia-button main' onClick={this.onCommand.bind(this, response)}>{label}</button>;
50
+ }
51
+ private onCommand(arg: CommandChatResponseContent): void {
52
+ if (arg.customCallback) {
53
+ arg.customCallback.callback().catch(e => { console.error(e); });
54
+ } else if (arg.command) {
55
+ this.commandService.executeCommand(arg.command.id, ...(arg.arguments ?? [])).catch(e => { console.error(e); });
56
+ } else {
57
+ console.warn('No command or custom callback provided in command chat response content');
58
+ }
59
+ }
60
+ }
@@ -0,0 +1,35 @@
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 { ChatResponsePartRenderer } from '../chat-response-part-renderer';
18
+ import { injectable } from '@theia/core/shared/inversify';
19
+ import { ChatResponseContent, ErrorChatResponseContent } from '@theia/ai-chat/lib/common';
20
+ import { ReactNode } from '@theia/core/shared/react';
21
+ import * as React from '@theia/core/shared/react';
22
+
23
+ @injectable()
24
+ export class ErrorPartRenderer implements ChatResponsePartRenderer<ErrorChatResponseContent> {
25
+ canHandle(response: ChatResponseContent): number {
26
+ if (ErrorChatResponseContent.is(response)) {
27
+ return 10;
28
+ }
29
+ return -1;
30
+ }
31
+ render(response: ErrorChatResponseContent): ReactNode {
32
+ return <div className='theia-ChatPart-Error'><span className='codicon codicon-error' /><span>{response.error.message}</span></div>;
33
+ }
34
+
35
+ }
@@ -0,0 +1,59 @@
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 { ChatResponsePartRenderer } from '../chat-response-part-renderer';
18
+ import { inject, injectable, named } from '@theia/core/shared/inversify';
19
+ import {
20
+ ChatResponseContent,
21
+ HorizontalLayoutChatResponseContent,
22
+ } from '@theia/ai-chat/lib/common';
23
+ import { ReactNode } from '@theia/core/shared/react';
24
+ import * as React from '@theia/core/shared/react';
25
+ import { ContributionProvider } from '@theia/core';
26
+ import { ResponseNode } from '../chat-tree-view/chat-view-tree-widget';
27
+
28
+ @injectable()
29
+ export class HorizontalLayoutPartRenderer
30
+ implements ChatResponsePartRenderer<ChatResponseContent> {
31
+ @inject(ContributionProvider)
32
+ @named(ChatResponsePartRenderer)
33
+ protected readonly chatResponsePartRenderers: ContributionProvider<
34
+ ChatResponsePartRenderer<ChatResponseContent>
35
+ >;
36
+
37
+ canHandle(response: ChatResponseContent): number {
38
+ if (HorizontalLayoutChatResponseContent.is(response)) {
39
+ return 10;
40
+ }
41
+ return -1;
42
+ }
43
+ render(response: HorizontalLayoutChatResponseContent, parentNode: ResponseNode): ReactNode {
44
+ const contributions = this.chatResponsePartRenderers.getContributions();
45
+ return (
46
+ <div className="ai-chat-horizontal-layout" style={{ display: 'flex', flexDirection: 'row', flexWrap: 'wrap' }}>
47
+ {response.content.map(content => {
48
+ const renderer = contributions
49
+ .map(c => ({
50
+ prio: c.canHandle(content),
51
+ renderer: c,
52
+ }))
53
+ .sort((a, b) => b.prio - a.prio)[0].renderer;
54
+ return renderer.render(content, parentNode);
55
+ })}
56
+ </div>
57
+ );
58
+ }
59
+ }
@@ -0,0 +1,23 @@
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
+ export * from './ai-editor-manager';
17
+ export * from './code-part-renderer';
18
+ export * from './command-part-renderer';
19
+ export * from './error-part-renderer';
20
+ export * from './horizontal-layout-part-renderer';
21
+ export * from './markdown-part-renderer';
22
+ export * from './text-part-renderer';
23
+ export * from './toolcall-part-renderer';
@@ -0,0 +1,92 @@
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 { ChatResponsePartRenderer } from '../chat-response-part-renderer';
18
+ import { injectable } from '@theia/core/shared/inversify';
19
+ import {
20
+ ChatResponseContent,
21
+ InformationalChatResponseContent,
22
+ MarkdownChatResponseContent,
23
+ } from '@theia/ai-chat/lib/common';
24
+ import { ReactNode, useEffect, useRef } from '@theia/core/shared/react';
25
+ import * as React from '@theia/core/shared/react';
26
+ import * as markdownit from '@theia/core/shared/markdown-it';
27
+ import * as DOMPurify from '@theia/core/shared/dompurify';
28
+ import { MarkdownString } from '@theia/core/lib/common/markdown-rendering';
29
+
30
+ @injectable()
31
+ export class MarkdownPartRenderer implements ChatResponsePartRenderer<MarkdownChatResponseContent | InformationalChatResponseContent> {
32
+ protected readonly markdownIt = markdownit();
33
+ canHandle(response: ChatResponseContent): number {
34
+ if (MarkdownChatResponseContent.is(response)) {
35
+ return 10;
36
+ }
37
+ if (InformationalChatResponseContent.is(response)) {
38
+ return 10;
39
+ }
40
+ return -1;
41
+ }
42
+ render(response: MarkdownChatResponseContent | InformationalChatResponseContent): ReactNode {
43
+ // TODO let the user configure whether they want to see informational content
44
+ if (InformationalChatResponseContent.is(response)) {
45
+ // null is valid in React
46
+ // eslint-disable-next-line no-null/no-null
47
+ return null;
48
+ }
49
+
50
+ return <MarkdownRender response={response} />;
51
+ }
52
+
53
+ }
54
+
55
+ const MarkdownRender = ({ response }: { response: MarkdownChatResponseContent | InformationalChatResponseContent }) => {
56
+ const ref = useMarkdownRendering(response.content);
57
+
58
+ return <div ref={ref}></div>;
59
+ };
60
+
61
+ /**
62
+ * This hook uses markdown-it directly to render markdown.
63
+ * The reason to use markdown-it directly is that the MarkdownRenderer is
64
+ * overridden by theia with a monaco version. This monaco version strips all html
65
+ * tags from the markdown with empty content.
66
+ * This leads to unexpected behavior when rendering markdown with html tags.
67
+ *
68
+ * @param markdown the string to render as markdown
69
+ * @param skipSurroundingParagraph whether to remove a surrounding paragraph element (default: false)
70
+ * @returns the ref to use in an element to render the markdown
71
+ */
72
+ export const useMarkdownRendering = (markdown: string | MarkdownString, skipSurroundingParagraph: boolean = false) => {
73
+ // eslint-disable-next-line no-null/no-null
74
+ const ref = useRef<HTMLDivElement | null>(null);
75
+ const markdownString = typeof markdown === 'string' ? markdown : markdown.value;
76
+ useEffect(() => {
77
+ const markdownIt = markdownit();
78
+ const host = document.createElement('div');
79
+ // markdownIt always puts the content in a paragraph element, so we remove it if we don't want it
80
+ const html = skipSurroundingParagraph ? markdownIt.render(markdownString).replace(/^<p>|<\/p>|<p><\/p>$/g, '') : markdownIt.render(markdownString);
81
+ host.innerHTML = DOMPurify.sanitize(html, {
82
+ ALLOW_UNKNOWN_PROTOCOLS: true // DOMPurify usually strips non http(s) links from hrefs
83
+ });
84
+ while (ref?.current?.firstChild) {
85
+ ref.current.removeChild(ref.current.firstChild);
86
+ }
87
+
88
+ ref?.current?.appendChild(host);
89
+ }, [markdownString]);
90
+
91
+ return ref;
92
+ };
@@ -0,0 +1,59 @@
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 { ChatResponseContent, QuestionResponseContent } from '@theia/ai-chat';
17
+ import { injectable } from '@theia/core/shared/inversify';
18
+ import * as React from '@theia/core/shared/react';
19
+ import { ReactNode } from '@theia/core/shared/react';
20
+ import { ChatResponsePartRenderer } from '../chat-response-part-renderer';
21
+ import { ResponseNode } from '../chat-tree-view';
22
+
23
+ @injectable()
24
+ export class QuestionPartRenderer
25
+ implements ChatResponsePartRenderer<QuestionResponseContent> {
26
+
27
+ canHandle(response: ChatResponseContent): number {
28
+ if (QuestionResponseContent.is(response)) {
29
+ return 10;
30
+ }
31
+ return -1;
32
+ }
33
+
34
+ render(question: QuestionResponseContent, node: ResponseNode): ReactNode {
35
+ return (
36
+ <div className="theia-QuestionPartRenderer-root">
37
+ <div className="theia-QuestionPartRenderer-question">{question.question}</div>
38
+ <div className="theia-QuestionPartRenderer-options">
39
+ {
40
+ question.options.map((option, index) => (
41
+ <button
42
+ className={`theia-button theia-QuestionPartRenderer-option ${question.selectedOption === option ? 'selected' : ''}`}
43
+ onClick={() => {
44
+ question.selectedOption = option;
45
+ question.handler(option);
46
+ }}
47
+ disabled={question.selectedOption !== undefined || !node.response.isWaitingForInput}
48
+ key={index}
49
+ >
50
+ {option.text}
51
+ </button>
52
+ ))
53
+ }
54
+ </div>
55
+ </div>
56
+ );
57
+ }
58
+
59
+ }
@@ -0,0 +1,50 @@
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 { TextPartRenderer } from './text-part-renderer';
18
+ import { expect } from 'chai';
19
+ import { ChatResponseContent } from '@theia/ai-chat';
20
+
21
+ describe('TextPartRenderer', () => {
22
+
23
+ it('accepts all parts', () => {
24
+ const renderer = new TextPartRenderer();
25
+ expect(renderer.canHandle({ kind: 'text' })).to.be.greaterThan(0);
26
+ expect(renderer.canHandle({ kind: 'code' })).to.be.greaterThan(0);
27
+ expect(renderer.canHandle({ kind: 'command' })).to.be.greaterThan(0);
28
+ expect(renderer.canHandle({ kind: 'error' })).to.be.greaterThan(0);
29
+ expect(renderer.canHandle({ kind: 'horizontal' })).to.be.greaterThan(0);
30
+ expect(renderer.canHandle({ kind: 'informational' })).to.be.greaterThan(0);
31
+ expect(renderer.canHandle({ kind: 'markdownContent' })).to.be.greaterThan(0);
32
+ expect(renderer.canHandle({ kind: 'toolCall' })).to.be.greaterThan(0);
33
+ expect(renderer.canHandle(undefined as unknown as ChatResponseContent)).to.be.greaterThan(0);
34
+ });
35
+
36
+ it('renders text correctly', () => {
37
+ const renderer = new TextPartRenderer();
38
+ const part = { kind: 'text', asString: () => 'Hello, World!' };
39
+ const node = renderer.render(part);
40
+ expect(JSON.stringify(node)).to.contain('Hello, World!');
41
+ });
42
+
43
+ it('handles undefined content gracefully', () => {
44
+ const renderer = new TextPartRenderer();
45
+ const part = undefined as unknown as ChatResponseContent;
46
+ const node = renderer.render(part);
47
+ expect(node).to.exist;
48
+ });
49
+
50
+ });
@@ -0,0 +1,35 @@
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 { ChatResponsePartRenderer } from '../chat-response-part-renderer';
18
+ import { injectable } from '@theia/core/shared/inversify';
19
+ import { ChatResponseContent } from '@theia/ai-chat/lib/common';
20
+ import { ReactNode } from '@theia/core/shared/react';
21
+ import * as React from '@theia/core/shared/react';
22
+
23
+ @injectable()
24
+ export class TextPartRenderer implements ChatResponsePartRenderer<ChatResponseContent> {
25
+ canHandle(_reponse: ChatResponseContent): number {
26
+ // this is the fallback renderer
27
+ return 1;
28
+ }
29
+ render(response: ChatResponseContent): ReactNode {
30
+ if (response && ChatResponseContent.hasAsString(response)) {
31
+ return <span>{response.asString()}</span>;
32
+ }
33
+ return <span>Can't display response, please check your ChatResponsePartRenderers! {JSON.stringify(response)}</span>;
34
+ }
35
+ }