@theia/ai-chat-ui 1.56.0 → 1.57.0-next.112

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 (40) hide show
  1. package/README.md +2 -1
  2. package/lib/browser/ai-chat-ui-frontend-module.d.ts.map +1 -1
  3. package/lib/browser/ai-chat-ui-frontend-module.js +9 -1
  4. package/lib/browser/ai-chat-ui-frontend-module.js.map +1 -1
  5. package/lib/browser/chat-input-widget.d.ts +18 -5
  6. package/lib/browser/chat-input-widget.d.ts.map +1 -1
  7. package/lib/browser/chat-input-widget.js +228 -80
  8. package/lib/browser/chat-input-widget.js.map +1 -1
  9. package/lib/browser/chat-response-renderer/code-part-renderer.d.ts +30 -2
  10. package/lib/browser/chat-response-renderer/code-part-renderer.d.ts.map +1 -1
  11. package/lib/browser/chat-response-renderer/code-part-renderer.js +45 -10
  12. package/lib/browser/chat-response-renderer/code-part-renderer.js.map +1 -1
  13. package/lib/browser/chat-response-renderer/markdown-part-renderer.d.ts +8 -3
  14. package/lib/browser/chat-response-renderer/markdown-part-renderer.d.ts.map +1 -1
  15. package/lib/browser/chat-response-renderer/markdown-part-renderer.js +38 -10
  16. package/lib/browser/chat-response-renderer/markdown-part-renderer.js.map +1 -1
  17. package/lib/browser/chat-response-renderer/toolcall-part-renderer.d.ts.map +1 -1
  18. package/lib/browser/chat-response-renderer/toolcall-part-renderer.js +8 -2
  19. package/lib/browser/chat-response-renderer/toolcall-part-renderer.js.map +1 -1
  20. package/lib/browser/chat-tree-view/chat-view-tree-widget.d.ts +2 -1
  21. package/lib/browser/chat-tree-view/chat-view-tree-widget.d.ts.map +1 -1
  22. package/lib/browser/chat-tree-view/chat-view-tree-widget.js +8 -4
  23. package/lib/browser/chat-tree-view/chat-view-tree-widget.js.map +1 -1
  24. package/lib/browser/chat-view-language-contribution.d.ts.map +1 -1
  25. package/lib/browser/chat-view-language-contribution.js +0 -1
  26. package/lib/browser/chat-view-language-contribution.js.map +1 -1
  27. package/lib/browser/chat-view-widget.d.ts +4 -1
  28. package/lib/browser/chat-view-widget.d.ts.map +1 -1
  29. package/lib/browser/chat-view-widget.js +14 -4
  30. package/lib/browser/chat-view-widget.js.map +1 -1
  31. package/package.json +12 -12
  32. package/src/browser/ai-chat-ui-frontend-module.ts +27 -5
  33. package/src/browser/chat-input-widget.tsx +337 -100
  34. package/src/browser/chat-response-renderer/code-part-renderer.tsx +48 -9
  35. package/src/browser/chat-response-renderer/markdown-part-renderer.tsx +39 -12
  36. package/src/browser/chat-response-renderer/toolcall-part-renderer.tsx +8 -2
  37. package/src/browser/chat-tree-view/chat-view-tree-widget.tsx +10 -4
  38. package/src/browser/chat-view-language-contribution.ts +0 -1
  39. package/src/browser/chat-view-widget.tsx +19 -6
  40. package/src/browser/style/index.css +186 -22
@@ -15,7 +15,7 @@
15
15
  // *****************************************************************************
16
16
 
17
17
  import { ChatResponsePartRenderer } from '../chat-response-part-renderer';
18
- import { injectable } from '@theia/core/shared/inversify';
18
+ import { inject, injectable } from '@theia/core/shared/inversify';
19
19
  import {
20
20
  ChatResponseContent,
21
21
  InformationalChatResponseContent,
@@ -26,9 +26,12 @@ import * as React from '@theia/core/shared/react';
26
26
  import * as markdownit from '@theia/core/shared/markdown-it';
27
27
  import * as DOMPurify from '@theia/core/shared/dompurify';
28
28
  import { MarkdownString } from '@theia/core/lib/common/markdown-rendering';
29
+ import { OpenerService, open } from '@theia/core/lib/browser';
30
+ import { URI } from '@theia/core';
29
31
 
30
32
  @injectable()
31
33
  export class MarkdownPartRenderer implements ChatResponsePartRenderer<MarkdownChatResponseContent | InformationalChatResponseContent> {
34
+ @inject(OpenerService) protected readonly openerService: OpenerService;
32
35
  protected readonly markdownIt = markdownit();
33
36
  canHandle(response: ChatResponseContent): number {
34
37
  if (MarkdownChatResponseContent.is(response)) {
@@ -47,13 +50,12 @@ export class MarkdownPartRenderer implements ChatResponsePartRenderer<MarkdownCh
47
50
  return null;
48
51
  }
49
52
 
50
- return <MarkdownRender response={response} />;
53
+ return <MarkdownRender response={response} openerService={this.openerService} />;
51
54
  }
52
-
53
55
  }
54
56
 
55
- const MarkdownRender = ({ response }: { response: MarkdownChatResponseContent | InformationalChatResponseContent }) => {
56
- const ref = useMarkdownRendering(response.content);
57
+ const MarkdownRender = ({ response, openerService }: { response: MarkdownChatResponseContent | InformationalChatResponseContent; openerService: OpenerService }) => {
58
+ const ref = useMarkdownRendering(response.content, openerService);
57
59
 
58
60
  return <div ref={ref}></div>;
59
61
  };
@@ -62,31 +64,56 @@ const MarkdownRender = ({ response }: { response: MarkdownChatResponseContent |
62
64
  * This hook uses markdown-it directly to render markdown.
63
65
  * The reason to use markdown-it directly is that the MarkdownRenderer is
64
66
  * 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
+ * tags from the markdown with empty content. This leads to unexpected behavior when
68
+ * rendering markdown with html tags.
69
+ *
70
+ * Moreover, we want to intercept link clicks to use the Theia OpenerService instead of the default browser behavior.
67
71
  *
68
72
  * @param markdown the string to render as markdown
69
73
  * @param skipSurroundingParagraph whether to remove a surrounding paragraph element (default: false)
74
+ * @param openerService the service to handle link opening
70
75
  * @returns the ref to use in an element to render the markdown
71
76
  */
72
- export const useMarkdownRendering = (markdown: string | MarkdownString, skipSurroundingParagraph: boolean = false) => {
77
+ export const useMarkdownRendering = (markdown: string | MarkdownString, openerService: OpenerService, skipSurroundingParagraph: boolean = false) => {
78
+ // null is valid in React
73
79
  // eslint-disable-next-line no-null/no-null
74
80
  const ref = useRef<HTMLDivElement | null>(null);
75
81
  const markdownString = typeof markdown === 'string' ? markdown : markdown.value;
76
82
  useEffect(() => {
77
83
  const markdownIt = markdownit();
78
84
  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
85
+
86
+ // markdownIt always puts the content in a paragraph element, so we remove it if we don't want that
80
87
  const html = skipSurroundingParagraph ? markdownIt.render(markdownString).replace(/^<p>|<\/p>|<p><\/p>$/g, '') : markdownIt.render(markdownString);
88
+
81
89
  host.innerHTML = DOMPurify.sanitize(html, {
82
- ALLOW_UNKNOWN_PROTOCOLS: true // DOMPurify usually strips non http(s) links from hrefs
90
+ // DOMPurify usually strips non http(s) links from hrefs
91
+ // but we want to allow them (see handleClick via OpenerService below)
92
+ ALLOW_UNKNOWN_PROTOCOLS: true
83
93
  });
84
94
  while (ref?.current?.firstChild) {
85
95
  ref.current.removeChild(ref.current.firstChild);
86
96
  }
87
-
88
97
  ref?.current?.appendChild(host);
89
- }, [markdownString]);
98
+
99
+ // intercept link clicks to use the Theia OpenerService instead of the default browser behavior
100
+ const handleClick = (event: MouseEvent) => {
101
+ let target = event.target as HTMLElement;
102
+ while (target && target.tagName !== 'A') {
103
+ target = target.parentElement as HTMLElement;
104
+ }
105
+ if (target && target.tagName === 'A') {
106
+ const href = target.getAttribute('href');
107
+ if (href) {
108
+ open(openerService, new URI(href));
109
+ event.preventDefault();
110
+ }
111
+ }
112
+ };
113
+
114
+ ref?.current?.addEventListener('click', handleClick);
115
+ return () => ref.current?.removeEventListener('click', handleClick);
116
+ }, [markdownString, skipSurroundingParagraph, openerService]);
90
117
 
91
118
  return ref;
92
119
  };
@@ -74,10 +74,16 @@ export class ToolCallPartRenderer implements ChatResponsePartRenderer<ToolCallCh
74
74
  private tryPrettyPrintJson(response: ToolCallChatResponseContent): string | undefined {
75
75
  let responseContent = response.result;
76
76
  try {
77
- if (response.result) {
78
- responseContent = JSON.stringify(JSON.parse(response.result), undefined, 2);
77
+ if (responseContent) {
78
+ if (typeof responseContent === 'string') {
79
+ responseContent = JSON.parse(responseContent);
80
+ }
81
+ responseContent = JSON.stringify(responseContent, undefined, 2);
79
82
  }
80
83
  } catch (e) {
84
+ if (typeof responseContent !== 'string') {
85
+ responseContent = `The content could not be converted to string: '${e.message}'. This is the original content: '${responseContent}'.`;
86
+ }
81
87
  // fall through
82
88
  }
83
89
  return responseContent;
@@ -34,6 +34,7 @@ import {
34
34
  Key,
35
35
  KeyCode,
36
36
  NodeProps,
37
+ OpenerService,
37
38
  TreeModel,
38
39
  TreeNode,
39
40
  TreeProps,
@@ -88,6 +89,9 @@ export class ChatViewTreeWidget extends TreeWidget {
88
89
  @inject(CommandRegistry)
89
90
  private commandRegistry: CommandRegistry;
90
91
 
92
+ @inject(OpenerService)
93
+ protected readonly openerService: OpenerService;
94
+
91
95
  @inject(HoverService)
92
96
  private hoverService: HoverService;
93
97
 
@@ -303,7 +307,7 @@ export class ChatViewTreeWidget extends TreeWidget {
303
307
  }}>
304
308
  {this.getAgentLabel(node)}
305
309
  </h3>
306
- {inProgress && <span className='theia-ChatContentInProgress'>Generating</span>}
310
+ {inProgress && !waitingForInput && <span className='theia-ChatContentInProgress'>Generating</span>}
307
311
  {inProgress && waitingForInput && <span className='theia-ChatContentInProgress'>Waiting for input</span>}
308
312
  <div className='theia-ChatNodeToolbar'>
309
313
  {!inProgress &&
@@ -370,6 +374,7 @@ export class ChatViewTreeWidget extends TreeWidget {
370
374
  hoverService={this.hoverService}
371
375
  chatAgentService={this.chatAgentService}
372
376
  variableService={this.variableService}
377
+ openerService={this.openerService}
373
378
  />;
374
379
  }
375
380
 
@@ -432,12 +437,13 @@ export class ChatViewTreeWidget extends TreeWidget {
432
437
 
433
438
  const ChatRequestRender = (
434
439
  {
435
- node, hoverService, chatAgentService, variableService
440
+ node, hoverService, chatAgentService, variableService, openerService
436
441
  }: {
437
442
  node: RequestNode,
438
443
  hoverService: HoverService,
439
444
  chatAgentService: ChatAgentService,
440
- variableService: AIVariableService
445
+ variableService: AIVariableService,
446
+ openerService: OpenerService
441
447
  }) => {
442
448
  const parts = node.request.message.parts;
443
449
  return (
@@ -465,7 +471,7 @@ const ChatRequestRender = (
465
471
  );
466
472
  } else {
467
473
  // maintain the leading and trailing spaces with explicit `&nbsp;`, otherwise they would get trimmed by the markdown renderer
468
- const ref = useMarkdownRendering(part.text.replace(/^\s|\s$/g, '&nbsp;'), true);
474
+ const ref = useMarkdownRendering(part.text.replace(/^\s|\s$/g, '&nbsp;'), openerService, true);
469
475
  return (
470
476
  <span key={index} ref={ref}></span>
471
477
  );
@@ -39,7 +39,6 @@ export class ChatViewLanguageContribution implements FrontendApplicationContribu
39
39
  private providers: ContributionProvider<ToolProvider>;
40
40
 
41
41
  onStart(_app: FrontendApplication): MaybePromise<void> {
42
- console.log('ChatViewLanguageContribution started');
43
42
  monaco.languages.register({ id: CHAT_VIEW_LANGUAGE_ID, extensions: [CHAT_VIEW_LANGUAGE_EXTENSION] });
44
43
 
45
44
  monaco.languages.registerCompletionItemProvider(CHAT_VIEW_LANGUAGE_ID, {
@@ -14,8 +14,8 @@
14
14
  // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
15
  // *****************************************************************************
16
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';
17
+ import { ChatRequest, ChatRequestModel, ChatService, ChatSession } from '@theia/ai-chat';
18
+ import { BaseWidget, codicon, ExtractableWidget, Message, PanelLayout, PreferenceService, 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';
@@ -93,6 +93,8 @@ export class ChatViewWidget extends BaseWidget implements ExtractableWidget, Sta
93
93
  this.inputWidget.onQuery = this.onQuery.bind(this);
94
94
  this.inputWidget.onCancel = this.onCancel.bind(this);
95
95
  this.inputWidget.chatModel = this.chatSession.model;
96
+ this.inputWidget.onDeleteChangeSet = this.onDeleteChangeSet.bind(this);
97
+ this.inputWidget.onDeleteChangeSetElement = this.onDeleteChangeSetElement.bind(this);
96
98
  this.treeWidget.trackChatModel(this.chatSession.model);
97
99
 
98
100
  this.initListeners();
@@ -125,6 +127,11 @@ export class ChatViewWidget extends BaseWidget implements ExtractableWidget, Sta
125
127
  );
126
128
  }
127
129
 
130
+ protected override onActivateRequest(msg: Message): void {
131
+ super.onActivateRequest(msg);
132
+ this.inputWidget.activate();
133
+ }
134
+
128
135
  storeState(): object {
129
136
  return this.state;
130
137
  }
@@ -160,7 +167,7 @@ export class ChatViewWidget extends BaseWidget implements ExtractableWidget, Sta
160
167
  const requestProgress = await this.chatService.sendRequest(this.chatSession.id, chatRequest);
161
168
  requestProgress?.responseCompleted.then(responseModel => {
162
169
  if (responseModel.isError) {
163
- this.messageService.error(responseModel.errorObject?.message ?? 'An error occurred druring chat service invocation.');
170
+ this.messageService.error(responseModel.errorObject?.message ?? 'An error occurred during chat service invocation.');
164
171
  }
165
172
  });
166
173
  if (!requestProgress) {
@@ -171,9 +178,15 @@ export class ChatViewWidget extends BaseWidget implements ExtractableWidget, Sta
171
178
  }
172
179
 
173
180
  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();
181
+ this.chatService.cancelRequest(requestModel.session.id, requestModel.id);
182
+ }
183
+
184
+ protected onDeleteChangeSet(sessionId: string): void {
185
+ this.chatService.deleteChangeSet(sessionId);
186
+ }
187
+
188
+ protected onDeleteChangeSetElement(sessionId: string, index: number): void {
189
+ this.chatService.deleteChangeSetElement(sessionId, index);
177
190
  }
178
191
 
179
192
  lock(): void {
@@ -153,51 +153,191 @@ div:last-child > .theia-ChatNode {
153
153
  }
154
154
 
155
155
  .theia-ChatInput {
156
+ margin-top: 16px;
156
157
  position: relative;
157
158
  width: 100%;
158
159
  box-sizing: border-box;
159
160
  gap: 4px;
160
161
  }
161
162
 
162
- .theia-ChatInput-Editor-Box {
163
- margin-bottom: 2px;
164
- padding: 10px;
163
+ .theia-ChatInput-ChangeSet-Box {
164
+ margin: 0 16px -5px 16px;
165
+ padding: 2px;
166
+ padding-bottom: 12px;
165
167
  height: auto;
168
+ border: var(--theia-border-width) solid var(--theia-dropdown-border);
169
+ border-radius: 4px 4px 0 0;
170
+ background-color: var(--theia-activityBar-background);
166
171
  display: flex;
167
172
  flex-direction: column;
168
- justify-content: flex-end;
173
+ }
174
+
175
+ .theia-ChatInput-ChangeSet-Header {
176
+ display: flex;
177
+ justify-content: space-between;
178
+ align-items: center;
179
+ margin: 4px;
180
+ }
181
+
182
+ .theia-ChatInput-ChangeSet-Box h3 {
183
+ font-size: 12px;
184
+ color: var(--theia-disabledForeground);
185
+ padding-top: 0;
186
+ margin: 0;
187
+ display: flex;
188
+ }
189
+
190
+ .theia-ChatInput-ChangeSet-Header-Actions button {
191
+ font-size: 12px;
192
+ padding: 2px 4px;
193
+ min-width: 40px;
194
+ margin-left: 6px;
195
+ }
196
+
197
+ .theia-ChatInput-ChangeSet-List ul {
198
+ list-style-type: none;
199
+ padding: 0;
200
+ margin: 4px;
201
+ }
202
+
203
+ .theia-ChatInput-ChangeSet-List ul li {
204
+ display: flex;
205
+ flex-direction: row;
206
+ line-height: 18px;
207
+ padding: 0;
208
+ padding-top: 2px;
209
+ margin: 0 2px 2px 0px;
210
+ border-radius: 4px;
211
+ position: relative;
212
+ }
213
+
214
+ .theia-ChatInput-ChangeSet-List ul li:hover {
215
+ background-color: var(--theia-toolbar-hoverBackground);
216
+ }
217
+
218
+ .theia-ChatInput-ChangeSet-Actions {
219
+ display: none;
220
+ position: absolute;
221
+ right: 8px;
222
+ top: 2px;
223
+ gap: 4px;
224
+ }
225
+
226
+ .theia-ChatInput-ChangeSet-List ul {
227
+ max-height: 150px;
228
+ overflow-y: auto;
229
+ }
230
+
231
+ .theia-ChatInput-ChangeSet-List ul li {
232
+ cursor: pointer;
233
+ }
234
+
235
+ .theia-ChatInput-ChangeSet-List ul li:hover .theia-ChatInput-ChangeSet-Actions {
236
+ display: flex;
237
+ }
238
+
239
+ .theia-ChatInput-ChangeSet-List .theia-ChatInput-ChangeSet-Icon {
240
+ padding-left: 2px;
241
+ padding-right: 4px;
242
+ min-width: var(--theia-icon-size);
243
+ display: flex;
244
+ justify-content: center;
245
+ align-items: center;
246
+ }
247
+
248
+ .theia-ChatInput-ChangeSet-List .theia-ChatInput-ChangeSet-Icon::before {
249
+ text-align: center;
250
+ margin-right: 4px;
251
+ font-size: calc(var(--theia-content-font-size) * 0.8);
252
+ }
253
+
254
+ .theia-ChatInput-ChangeSet-List
255
+ .theia-ChatInput-ChangeSet-Icon.codicon::before {
256
+ font-size: var(--theia-ui-font-size1);
257
+ }
258
+
259
+ .theia-ChatInput-ChangeSet-Actions .action {
260
+ width: 16px;
261
+ height: 16px;
262
+ cursor: pointer;
263
+ }
264
+
265
+ .theia-ChatInput-ChangeSet-additionalInfo {
266
+ margin-left: 8px;
267
+ color: var(--theia-disabledForeground);
268
+ }
269
+
270
+ .theia-ChatInput-ChangeSet-title {
271
+ white-space: nowrap;
272
+ overflow: visible;
273
+ }
274
+
275
+ .theia-ChatInput-ChangeSet-Header-Actions,
276
+ .theia-ChatInput-ChangeSet-Box h3,
277
+ .theia-ChatInput-ChangeSet-additionalInfo {
278
+ white-space: nowrap;
169
279
  overflow: hidden;
280
+ text-overflow: ellipsis;
170
281
  }
171
282
 
172
- .theia-ChatInput-Editor {
173
- width: 100%;
283
+ .theia-ChatInput-ChangeSet-Header-Actions .codicon.action {
284
+ font-size: 18px;
285
+ height: 20px;
286
+ vertical-align: middle;
287
+ margin-left: 4px;
288
+ border-radius: 4px;
289
+ cursor: pointer;
290
+ }
291
+ .theia-ChatInput-ChangeSet-Header-Actions .codicon.action:hover {
292
+ background-color: var(--theia-toolbar-hoverBackground);
293
+ }
294
+
295
+ .theia-ChatInput-ChangeSet-title.rejected {
296
+ text-decoration: line-through;
297
+ }
298
+
299
+ .theia-ChatInput-ChangeSet-title.add.pending {
300
+ color: var(--theia-charts-green);
301
+ }
302
+ .theia-ChatInput-ChangeSet-title.modify.pending {
303
+ color: var(--theia-charts-orange);
304
+ }
305
+ .theia-ChatInput-ChangeSet-title.delete.pending {
306
+ color: var(--theia-charts-red);
307
+ }
308
+
309
+ .theia-ChatInput-Editor-Box {
310
+ margin: 0 16px 16px 16px;
311
+ padding: 2px;
174
312
  height: auto;
175
313
  border: var(--theia-border-width) solid var(--theia-dropdown-border);
176
314
  border-radius: 4px;
315
+ background-color: var(--theia-editor-background);
177
316
  display: flex;
178
- flex-direction: column-reverse;
317
+ flex-direction: column;
318
+ justify-content: flex-end;
179
319
  overflow: hidden;
180
- transition: height 0.05s ease-in-out;
181
320
  }
182
321
 
183
- .theia-ChatInput-Editor:has(.monaco-editor.focused) {
322
+ .theia-ChatInput-Editor-Box:has(.monaco-editor.focused) {
184
323
  border-color: var(--theia-focusBorder);
185
324
  }
186
325
 
187
- .theia-ChatInput-Editor .monaco-editor {
188
- display: flex;
326
+ .theia-ChatInput-Editor {
189
327
  width: 100%;
190
- height: 100%;
328
+ height: auto;
329
+ display: flex;
330
+ flex-direction: column-reverse;
191
331
  overflow: hidden;
332
+ transition: height 0.05s ease-in-out;
192
333
  position: relative;
193
334
  }
194
335
 
195
336
  .theia-ChatInput-Editor-Placeholder {
196
337
  position: absolute;
197
- top: -3px;
198
- left: 19px;
338
+ top: 9px;
339
+ left: 8px;
199
340
  right: 0;
200
- bottom: 0;
201
341
  display: flex;
202
342
  align-items: center;
203
343
  color: var(--theia-descriptionForeground);
@@ -209,6 +349,14 @@ div:last-child > .theia-ChatNode {
209
349
  display: none;
210
350
  }
211
351
 
352
+ .theia-ChatInput-Editor .monaco-editor {
353
+ display: flex;
354
+ width: 100%;
355
+ height: 100%;
356
+ overflow: hidden;
357
+ position: relative;
358
+ }
359
+
212
360
  .theia-ChatInput-Editor .monaco-editor .margin,
213
361
  .theia-ChatInput-Editor .monaco-editor .monaco-editor-background,
214
362
  .theia-ChatInput-Editor .monaco-editor .inputarea.ime-input {
@@ -216,29 +364,45 @@ div:last-child > .theia-ChatNode {
216
364
  }
217
365
 
218
366
  .theia-ChatInputOptions {
219
- position: absolute;
220
- bottom: 31px;
221
- right: 26px;
222
- width: 10px;
223
- height: 10px;
367
+ width: 100%;
368
+ height: 25px;
369
+ padding-left: 6px;
370
+ padding-right: 6px;
371
+ display: flex;
372
+ justify-content: space-between;
373
+ }
374
+
375
+ .theia-ChatInputOptions .theia-ChatInputOptions-left,
376
+ .theia-ChatInputOptions .theia-ChatInputOptions-right {
377
+ display: flex;
378
+ }
379
+ .theia-ChatInputOptions .theia-ChatInputOptions-right {
380
+ margin-right: 12px;
224
381
  }
225
382
 
226
383
  .theia-ChatInputOptions .option {
227
384
  width: 21px;
228
385
  height: 21px;
229
- margin-top: 2px;
386
+ padding: 2px;
230
387
  display: inline-block;
231
388
  box-sizing: border-box;
232
389
  user-select: none;
233
390
  background-repeat: no-repeat;
234
391
  background-position: center;
392
+ border-radius: 5px;
235
393
  border: var(--theia-border-width) solid transparent;
236
- opacity: 0.7;
237
394
  cursor: pointer;
238
395
  }
239
396
 
397
+ .theia-ChatInputOptions .option.disabled {
398
+ cursor: default;
399
+ opacity: var(--theia-mod-disabled-opacity);
400
+ pointer-events: none;
401
+ }
402
+
240
403
  .theia-ChatInputOptions .option:hover {
241
404
  opacity: 1;
405
+ background-color: var(--theia-toolbar-hoverBackground);
242
406
  }
243
407
 
244
408
  .theia-CodePartRenderer-root {