@theia/ai-chat-ui 1.55.0 → 1.56.0

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 (32) hide show
  1. package/lib/browser/ai-chat-ui-contribution.js +1 -1
  2. package/lib/browser/ai-chat-ui-contribution.js.map +1 -1
  3. package/lib/browser/ai-chat-ui-frontend-module.d.ts.map +1 -1
  4. package/lib/browser/ai-chat-ui-frontend-module.js +2 -0
  5. package/lib/browser/ai-chat-ui-frontend-module.js.map +1 -1
  6. package/lib/browser/chat-input-widget.js +34 -22
  7. package/lib/browser/chat-input-widget.js.map +1 -1
  8. package/lib/browser/chat-response-renderer/markdown-part-renderer.d.ts +3 -2
  9. package/lib/browser/chat-response-renderer/markdown-part-renderer.d.ts.map +1 -1
  10. package/lib/browser/chat-response-renderer/markdown-part-renderer.js +5 -3
  11. package/lib/browser/chat-response-renderer/markdown-part-renderer.js.map +1 -1
  12. package/lib/browser/chat-response-renderer/question-part-renderer.d.ts +10 -0
  13. package/lib/browser/chat-response-renderer/question-part-renderer.d.ts.map +1 -0
  14. package/lib/browser/chat-response-renderer/question-part-renderer.js +43 -0
  15. package/lib/browser/chat-response-renderer/question-part-renderer.js.map +1 -0
  16. package/lib/browser/chat-response-renderer/toolcall-part-renderer.d.ts +2 -0
  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 +30 -10
  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 +4 -0
  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 +80 -13
  23. package/lib/browser/chat-tree-view/chat-view-tree-widget.js.map +1 -1
  24. package/package.json +11 -11
  25. package/src/browser/ai-chat-ui-contribution.ts +1 -1
  26. package/src/browser/ai-chat-ui-frontend-module.ts +2 -0
  27. package/src/browser/chat-input-widget.tsx +35 -20
  28. package/src/browser/chat-response-renderer/markdown-part-renderer.tsx +5 -3
  29. package/src/browser/chat-response-renderer/question-part-renderer.tsx +59 -0
  30. package/src/browser/chat-response-renderer/toolcall-part-renderer.tsx +38 -9
  31. package/src/browser/chat-tree-view/chat-view-tree-widget.tsx +134 -11
  32. package/src/browser/style/index.css +58 -1
@@ -14,12 +14,15 @@
14
14
  // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
15
  // *****************************************************************************
16
16
  import {
17
+ ChatAgent,
17
18
  ChatAgentService,
18
19
  ChatModel,
19
20
  ChatProgressMessage,
20
21
  ChatRequestModel,
21
22
  ChatResponseContent,
22
23
  ChatResponseModel,
24
+ ParsedChatRequestAgentPart,
25
+ ParsedChatRequestVariablePart,
23
26
  } from '@theia/ai-chat';
24
27
  import { CommandRegistry, ContributionProvider } from '@theia/core';
25
28
  import {
@@ -27,6 +30,7 @@ import {
27
30
  CommonCommands,
28
31
  CompositeTreeNode,
29
32
  ContextMenuRenderer,
33
+ HoverService,
30
34
  Key,
31
35
  KeyCode,
32
36
  NodeProps,
@@ -46,6 +50,7 @@ import * as React from '@theia/core/shared/react';
46
50
  import { ChatNodeToolbarActionContribution } from '../chat-node-toolbar-action-contribution';
47
51
  import { ChatResponsePartRenderer } from '../chat-response-part-renderer';
48
52
  import { useMarkdownRendering } from '../chat-response-renderer/markdown-part-renderer';
53
+ import { AIVariableService } from '@theia/ai-core';
49
54
 
50
55
  // TODO Instead of directly operating on the ChatRequestModel we could use an intermediate view model
51
56
  export interface RequestNode extends TreeNode {
@@ -77,9 +82,15 @@ export class ChatViewTreeWidget extends TreeWidget {
77
82
  @inject(ChatAgentService)
78
83
  protected chatAgentService: ChatAgentService;
79
84
 
85
+ @inject(AIVariableService)
86
+ protected readonly variableService: AIVariableService;
87
+
80
88
  @inject(CommandRegistry)
81
89
  private commandRegistry: CommandRegistry;
82
90
 
91
+ @inject(HoverService)
92
+ private hoverService: HoverService;
93
+
83
94
  protected _shouldScrollToEnd = true;
84
95
 
85
96
  protected isEnabled = false;
@@ -267,22 +278,39 @@ export class ChatViewTreeWidget extends TreeWidget {
267
278
 
268
279
  private renderAgent(node: RequestNode | ResponseNode): React.ReactNode {
269
280
  const inProgress = isResponseNode(node) && !node.response.isComplete && !node.response.isCanceled && !node.response.isError;
281
+ const waitingForInput = isResponseNode(node) && node.response.isWaitingForInput;
270
282
  const toolbarContributions = !inProgress
271
283
  ? this.chatNodeToolbarActionContributions.getContributions()
272
284
  .flatMap(c => c.getToolbarActions(node))
273
285
  .filter(action => this.commandRegistry.isEnabled(action.commandId, node))
274
286
  .sort((a, b) => (a.priority ?? 0) - (b.priority ?? 0))
275
287
  : [];
288
+ const agentLabel = React.createRef<HTMLHeadingElement>();
289
+ const agentDescription = this.getAgent(node)?.description;
276
290
  return <React.Fragment>
277
291
  <div className='theia-ChatNodeHeader'>
278
292
  <div className={`theia-AgentAvatar ${this.getAgentIconClassName(node)}`}></div>
279
- <h3 className='theia-AgentLabel'>{this.getAgentLabel(node)}</h3>
293
+ <h3 ref={agentLabel}
294
+ className='theia-AgentLabel'
295
+ onMouseEnter={() => {
296
+ if (agentDescription) {
297
+ this.hoverService.requestHover({
298
+ content: agentDescription,
299
+ target: agentLabel.current!,
300
+ position: 'right'
301
+ });
302
+ }
303
+ }}>
304
+ {this.getAgentLabel(node)}
305
+ </h3>
280
306
  {inProgress && <span className='theia-ChatContentInProgress'>Generating</span>}
307
+ {inProgress && waitingForInput && <span className='theia-ChatContentInProgress'>Waiting for input</span>}
281
308
  <div className='theia-ChatNodeToolbar'>
282
309
  {!inProgress &&
283
310
  toolbarContributions.length > 0 &&
284
311
  toolbarContributions.map(action =>
285
312
  <span
313
+ key={action.commandId}
286
314
  className={`theia-ChatNodeToolbarAction ${action.icon}`}
287
315
  title={action.tooltip}
288
316
  onClick={e => {
@@ -308,8 +336,14 @@ export class ChatViewTreeWidget extends TreeWidget {
308
336
  // TODO find user name
309
337
  return 'You';
310
338
  }
311
- const agent = node.response.agentId ? this.chatAgentService.getAgent(node.response.agentId) : undefined;
312
- return agent?.name ?? 'AI';
339
+ return this.getAgent(node)?.name ?? 'AI';
340
+ }
341
+
342
+ private getAgent(node: RequestNode | ResponseNode): ChatAgent | undefined {
343
+ if (isRequestNode(node)) {
344
+ return undefined;
345
+ }
346
+ return node.response.agentId ? this.chatAgentService.getAgent(node.response.agentId) : undefined;
313
347
  }
314
348
 
315
349
  private getAgentIconClassName(node: RequestNode | ResponseNode): string | undefined {
@@ -331,7 +365,12 @@ export class ChatViewTreeWidget extends TreeWidget {
331
365
  }
332
366
 
333
367
  private renderChatRequest(node: RequestNode): React.ReactNode {
334
- return <ChatRequestRender node={node} />;
368
+ return <ChatRequestRender
369
+ node={node}
370
+ hoverService={this.hoverService}
371
+ chatAgentService={this.chatAgentService}
372
+ variableService={this.variableService}
373
+ />;
335
374
  }
336
375
 
337
376
  private renderChatResponse(node: ResponseNode): React.ReactNode {
@@ -339,12 +378,28 @@ export class ChatViewTreeWidget extends TreeWidget {
339
378
  <div className={'theia-ResponseNode'}>
340
379
  {!node.response.isComplete
341
380
  && node.response.response.content.length === 0
342
- && node.response.progressMessages.map((c, i) =>
343
- <ProgressMessage {...c} key={`${node.id}-progress-${i}`} />
344
- )}
381
+ && node.response.progressMessages
382
+ .filter(c => c.show === 'untilFirstContent')
383
+ .map((c, i) =>
384
+ <ProgressMessage {...c} key={`${node.id}-progress-untilFirstContent-${i}`} />
385
+ )
386
+ }
345
387
  {node.response.response.content.map((c, i) =>
346
388
  <div className='theia-ResponseNode-Content' key={`${node.id}-content-${i}`}>{this.getChatResponsePartRenderer(c, node)}</div>
347
389
  )}
390
+ {!node.response.isComplete
391
+ && node.response.progressMessages
392
+ .filter(c => c.show === 'whileIncomplete')
393
+ .map((c, i) =>
394
+ <ProgressMessage {...c} key={`${node.id}-progress-whileIncomplete-${i}`} />
395
+ )
396
+ }
397
+ {node.response.progressMessages
398
+ .filter(c => c.show === 'forever')
399
+ .map((c, i) =>
400
+ <ProgressMessage {...c} key={`${node.id}-progress-afterComplete-${i}`} />
401
+ )
402
+ }
348
403
  </div>
349
404
  );
350
405
  }
@@ -375,11 +430,79 @@ export class ChatViewTreeWidget extends TreeWidget {
375
430
  }
376
431
  }
377
432
 
378
- const ChatRequestRender = ({ node }: { node: RequestNode }) => {
379
- const text = node.request.request.displayText ?? node.request.request.text;
380
- const ref = useMarkdownRendering(text);
433
+ const ChatRequestRender = (
434
+ {
435
+ node, hoverService, chatAgentService, variableService
436
+ }: {
437
+ node: RequestNode,
438
+ hoverService: HoverService,
439
+ chatAgentService: ChatAgentService,
440
+ variableService: AIVariableService
441
+ }) => {
442
+ const parts = node.request.message.parts;
443
+ return (
444
+ <div className="theia-RequestNode">
445
+ <p>
446
+ {parts.map((part, index) => {
447
+ if (part instanceof ParsedChatRequestAgentPart || part instanceof ParsedChatRequestVariablePart) {
448
+ let description = undefined;
449
+ let className = '';
450
+ if (part instanceof ParsedChatRequestAgentPart) {
451
+ description = chatAgentService.getAgent(part.agentId)?.description;
452
+ className = 'theia-RequestNode-AgentLabel';
453
+ } else if (part instanceof ParsedChatRequestVariablePart) {
454
+ description = variableService.getVariable(part.variableName)?.description;
455
+ className = 'theia-RequestNode-VariableLabel';
456
+ }
457
+ return (
458
+ <HoverableLabel
459
+ key={index}
460
+ text={part.text}
461
+ description={description}
462
+ hoverService={hoverService}
463
+ className={className}
464
+ />
465
+ );
466
+ } else {
467
+ // 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);
469
+ return (
470
+ <span key={index} ref={ref}></span>
471
+ );
472
+ }
473
+ })}
474
+ </p>
475
+ </div>
476
+ );
477
+ };
381
478
 
382
- return <div className={'theia-RequestNode'} ref={ref}></div>;
479
+ const HoverableLabel = (
480
+ {
481
+ text, description, hoverService, className
482
+ }: {
483
+ text: string,
484
+ description?: string,
485
+ hoverService: HoverService,
486
+ className: string
487
+ }) => {
488
+ const spanRef = React.createRef<HTMLSpanElement>();
489
+ return (
490
+ <span
491
+ className={className}
492
+ ref={spanRef}
493
+ onMouseEnter={() => {
494
+ if (description) {
495
+ hoverService.requestHover({
496
+ content: description,
497
+ target: spanRef.current!,
498
+ position: 'right'
499
+ });
500
+ }
501
+ }}
502
+ >
503
+ {text}
504
+ </span>
505
+ );
383
506
  };
384
507
 
385
508
  const ProgressMessage = (c: ChatProgressMessage) => (
@@ -132,6 +132,20 @@ div:last-child > .theia-ChatNode {
132
132
  font-size: var(--theia-code-font-size);
133
133
  }
134
134
 
135
+ .theia-RequestNode > p div {
136
+ display: inline;
137
+ }
138
+
139
+ .theia-RequestNode .theia-RequestNode-AgentLabel,
140
+ .theia-RequestNode .theia-RequestNode-VariableLabel {
141
+ padding: calc(var(--theia-ui-padding) * 2 / 3);
142
+ padding-top: 0px;
143
+ padding-bottom: 0px;
144
+ border-radius: calc(var(--theia-ui-padding) * 2 / 3);
145
+ background: var(--theia-badge-background);
146
+ color: var(--theia-badge-foreground);
147
+ }
148
+
135
149
  .chat-input-widget {
136
150
  align-items: flex-end;
137
151
  display: flex;
@@ -163,6 +177,7 @@ div:last-child > .theia-ChatNode {
163
177
  display: flex;
164
178
  flex-direction: column-reverse;
165
179
  overflow: hidden;
180
+ transition: height 0.05s ease-in-out;
166
181
  }
167
182
 
168
183
  .theia-ChatInput-Editor:has(.monaco-editor.focused) {
@@ -230,7 +245,7 @@ div:last-child > .theia-ChatNode {
230
245
  display: flex;
231
246
  flex-direction: column;
232
247
  gap: 2px;
233
- border: 1px solid var(--theia-input-border);
248
+ border: var(--theia-border-width) solid var(--theia-input-border);
234
249
  border-radius: 4px;
235
250
  }
236
251
 
@@ -264,6 +279,33 @@ div:last-child > .theia-ChatNode {
264
279
  background-color: var(--theia-input-border);
265
280
  }
266
281
 
282
+ .theia-QuestionPartRenderer-root {
283
+ display: flex;
284
+ flex-direction: column;
285
+ gap: 8px;
286
+ border: var(--theia-border-width) solid
287
+ var(--theia-sideBarSectionHeader-border);
288
+ padding: 8px 12px 12px;
289
+ border-radius: 5px;
290
+ margin: 0 0 8px 0;
291
+ }
292
+ .theia-QuestionPartRenderer-options {
293
+ display: flex;
294
+ flex-wrap: wrap;
295
+ gap: 12px;
296
+ }
297
+ .theia-QuestionPartRenderer-option {
298
+ min-width: 100px;
299
+ flex: 1 1 auto;
300
+ margin: 0;
301
+ }
302
+ .theia-QuestionPartRenderer-option.selected:disabled:hover {
303
+ background-color: var(--theia-button-disabledBackground);
304
+ }
305
+ .theia-QuestionPartRenderer-option:disabled:not(.selected) {
306
+ background-color: var(--theia-button-secondaryBackground);
307
+ }
308
+
267
309
  .theia-toolCall {
268
310
  font-weight: normal;
269
311
  color: var(--theia-descriptionForeground);
@@ -288,6 +330,21 @@ div:last-child > .theia-ChatNode {
288
330
  overflow: auto;
289
331
  }
290
332
 
333
+ .collapsible-arguments {
334
+ display: inline-block;
335
+ }
336
+
337
+ .collapsible-arguments .collapsible-arguments-summary {
338
+ display: inline-block;
339
+ white-space: nowrap;
340
+ text-decoration: underline;
341
+ }
342
+
343
+ details[open].collapsible-arguments,
344
+ details[open].collapsible-arguments .collapsible-arguments-summary {
345
+ display: unset;
346
+ }
347
+
291
348
  .theia-ResponseNode-ProgressMessage {
292
349
  font-weight: normal;
293
350
  color: var(--theia-descriptionForeground);