@theia/ai-chat-ui 1.55.1 → 1.57.0-next.22

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 (33) hide show
  1. package/README.md +2 -1
  2. package/lib/browser/ai-chat-ui-contribution.js +1 -1
  3. package/lib/browser/ai-chat-ui-contribution.js.map +1 -1
  4. package/lib/browser/ai-chat-ui-frontend-module.d.ts.map +1 -1
  5. package/lib/browser/ai-chat-ui-frontend-module.js +2 -0
  6. package/lib/browser/ai-chat-ui-frontend-module.js.map +1 -1
  7. package/lib/browser/chat-input-widget.js +34 -22
  8. package/lib/browser/chat-input-widget.js.map +1 -1
  9. package/lib/browser/chat-response-renderer/markdown-part-renderer.d.ts +10 -4
  10. package/lib/browser/chat-response-renderer/markdown-part-renderer.d.ts.map +1 -1
  11. package/lib/browser/chat-response-renderer/markdown-part-renderer.js +41 -11
  12. package/lib/browser/chat-response-renderer/markdown-part-renderer.js.map +1 -1
  13. package/lib/browser/chat-response-renderer/question-part-renderer.d.ts +10 -0
  14. package/lib/browser/chat-response-renderer/question-part-renderer.d.ts.map +1 -0
  15. package/lib/browser/chat-response-renderer/question-part-renderer.js +43 -0
  16. package/lib/browser/chat-response-renderer/question-part-renderer.js.map +1 -0
  17. package/lib/browser/chat-response-renderer/toolcall-part-renderer.d.ts +2 -0
  18. package/lib/browser/chat-response-renderer/toolcall-part-renderer.d.ts.map +1 -1
  19. package/lib/browser/chat-response-renderer/toolcall-part-renderer.js +30 -10
  20. package/lib/browser/chat-response-renderer/toolcall-part-renderer.js.map +1 -1
  21. package/lib/browser/chat-tree-view/chat-view-tree-widget.d.ts +6 -1
  22. package/lib/browser/chat-tree-view/chat-view-tree-widget.d.ts.map +1 -1
  23. package/lib/browser/chat-tree-view/chat-view-tree-widget.js +85 -14
  24. package/lib/browser/chat-tree-view/chat-view-tree-widget.js.map +1 -1
  25. package/package.json +11 -11
  26. package/src/browser/ai-chat-ui-contribution.ts +1 -1
  27. package/src/browser/ai-chat-ui-frontend-module.ts +2 -0
  28. package/src/browser/chat-input-widget.tsx +35 -20
  29. package/src/browser/chat-response-renderer/markdown-part-renderer.tsx +42 -13
  30. package/src/browser/chat-response-renderer/question-part-renderer.tsx +59 -0
  31. package/src/browser/chat-response-renderer/toolcall-part-renderer.tsx +38 -9
  32. package/src/browser/chat-tree-view/chat-view-tree-widget.tsx +141 -12
  33. 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,9 +30,11 @@ import {
27
30
  CommonCommands,
28
31
  CompositeTreeNode,
29
32
  ContextMenuRenderer,
33
+ HoverService,
30
34
  Key,
31
35
  KeyCode,
32
36
  NodeProps,
37
+ OpenerService,
33
38
  TreeModel,
34
39
  TreeNode,
35
40
  TreeProps,
@@ -46,6 +51,7 @@ import * as React from '@theia/core/shared/react';
46
51
  import { ChatNodeToolbarActionContribution } from '../chat-node-toolbar-action-contribution';
47
52
  import { ChatResponsePartRenderer } from '../chat-response-part-renderer';
48
53
  import { useMarkdownRendering } from '../chat-response-renderer/markdown-part-renderer';
54
+ import { AIVariableService } from '@theia/ai-core';
49
55
 
50
56
  // TODO Instead of directly operating on the ChatRequestModel we could use an intermediate view model
51
57
  export interface RequestNode extends TreeNode {
@@ -77,9 +83,18 @@ export class ChatViewTreeWidget extends TreeWidget {
77
83
  @inject(ChatAgentService)
78
84
  protected chatAgentService: ChatAgentService;
79
85
 
86
+ @inject(AIVariableService)
87
+ protected readonly variableService: AIVariableService;
88
+
80
89
  @inject(CommandRegistry)
81
90
  private commandRegistry: CommandRegistry;
82
91
 
92
+ @inject(OpenerService)
93
+ protected readonly openerService: OpenerService;
94
+
95
+ @inject(HoverService)
96
+ private hoverService: HoverService;
97
+
83
98
  protected _shouldScrollToEnd = true;
84
99
 
85
100
  protected isEnabled = false;
@@ -267,22 +282,39 @@ export class ChatViewTreeWidget extends TreeWidget {
267
282
 
268
283
  private renderAgent(node: RequestNode | ResponseNode): React.ReactNode {
269
284
  const inProgress = isResponseNode(node) && !node.response.isComplete && !node.response.isCanceled && !node.response.isError;
285
+ const waitingForInput = isResponseNode(node) && node.response.isWaitingForInput;
270
286
  const toolbarContributions = !inProgress
271
287
  ? this.chatNodeToolbarActionContributions.getContributions()
272
288
  .flatMap(c => c.getToolbarActions(node))
273
289
  .filter(action => this.commandRegistry.isEnabled(action.commandId, node))
274
290
  .sort((a, b) => (a.priority ?? 0) - (b.priority ?? 0))
275
291
  : [];
292
+ const agentLabel = React.createRef<HTMLHeadingElement>();
293
+ const agentDescription = this.getAgent(node)?.description;
276
294
  return <React.Fragment>
277
295
  <div className='theia-ChatNodeHeader'>
278
296
  <div className={`theia-AgentAvatar ${this.getAgentIconClassName(node)}`}></div>
279
- <h3 className='theia-AgentLabel'>{this.getAgentLabel(node)}</h3>
280
- {inProgress && <span className='theia-ChatContentInProgress'>Generating</span>}
297
+ <h3 ref={agentLabel}
298
+ className='theia-AgentLabel'
299
+ onMouseEnter={() => {
300
+ if (agentDescription) {
301
+ this.hoverService.requestHover({
302
+ content: agentDescription,
303
+ target: agentLabel.current!,
304
+ position: 'right'
305
+ });
306
+ }
307
+ }}>
308
+ {this.getAgentLabel(node)}
309
+ </h3>
310
+ {inProgress && !waitingForInput && <span className='theia-ChatContentInProgress'>Generating</span>}
311
+ {inProgress && waitingForInput && <span className='theia-ChatContentInProgress'>Waiting for input</span>}
281
312
  <div className='theia-ChatNodeToolbar'>
282
313
  {!inProgress &&
283
314
  toolbarContributions.length > 0 &&
284
315
  toolbarContributions.map(action =>
285
316
  <span
317
+ key={action.commandId}
286
318
  className={`theia-ChatNodeToolbarAction ${action.icon}`}
287
319
  title={action.tooltip}
288
320
  onClick={e => {
@@ -308,8 +340,14 @@ export class ChatViewTreeWidget extends TreeWidget {
308
340
  // TODO find user name
309
341
  return 'You';
310
342
  }
311
- const agent = node.response.agentId ? this.chatAgentService.getAgent(node.response.agentId) : undefined;
312
- return agent?.name ?? 'AI';
343
+ return this.getAgent(node)?.name ?? 'AI';
344
+ }
345
+
346
+ private getAgent(node: RequestNode | ResponseNode): ChatAgent | undefined {
347
+ if (isRequestNode(node)) {
348
+ return undefined;
349
+ }
350
+ return node.response.agentId ? this.chatAgentService.getAgent(node.response.agentId) : undefined;
313
351
  }
314
352
 
315
353
  private getAgentIconClassName(node: RequestNode | ResponseNode): string | undefined {
@@ -331,7 +369,13 @@ export class ChatViewTreeWidget extends TreeWidget {
331
369
  }
332
370
 
333
371
  private renderChatRequest(node: RequestNode): React.ReactNode {
334
- return <ChatRequestRender node={node} />;
372
+ return <ChatRequestRender
373
+ node={node}
374
+ hoverService={this.hoverService}
375
+ chatAgentService={this.chatAgentService}
376
+ variableService={this.variableService}
377
+ openerService={this.openerService}
378
+ />;
335
379
  }
336
380
 
337
381
  private renderChatResponse(node: ResponseNode): React.ReactNode {
@@ -339,12 +383,28 @@ export class ChatViewTreeWidget extends TreeWidget {
339
383
  <div className={'theia-ResponseNode'}>
340
384
  {!node.response.isComplete
341
385
  && node.response.response.content.length === 0
342
- && node.response.progressMessages.map((c, i) =>
343
- <ProgressMessage {...c} key={`${node.id}-progress-${i}`} />
344
- )}
386
+ && node.response.progressMessages
387
+ .filter(c => c.show === 'untilFirstContent')
388
+ .map((c, i) =>
389
+ <ProgressMessage {...c} key={`${node.id}-progress-untilFirstContent-${i}`} />
390
+ )
391
+ }
345
392
  {node.response.response.content.map((c, i) =>
346
393
  <div className='theia-ResponseNode-Content' key={`${node.id}-content-${i}`}>{this.getChatResponsePartRenderer(c, node)}</div>
347
394
  )}
395
+ {!node.response.isComplete
396
+ && node.response.progressMessages
397
+ .filter(c => c.show === 'whileIncomplete')
398
+ .map((c, i) =>
399
+ <ProgressMessage {...c} key={`${node.id}-progress-whileIncomplete-${i}`} />
400
+ )
401
+ }
402
+ {node.response.progressMessages
403
+ .filter(c => c.show === 'forever')
404
+ .map((c, i) =>
405
+ <ProgressMessage {...c} key={`${node.id}-progress-afterComplete-${i}`} />
406
+ )
407
+ }
348
408
  </div>
349
409
  );
350
410
  }
@@ -375,11 +435,80 @@ export class ChatViewTreeWidget extends TreeWidget {
375
435
  }
376
436
  }
377
437
 
378
- const ChatRequestRender = ({ node }: { node: RequestNode }) => {
379
- const text = node.request.request.displayText ?? node.request.request.text;
380
- const ref = useMarkdownRendering(text);
438
+ const ChatRequestRender = (
439
+ {
440
+ node, hoverService, chatAgentService, variableService, openerService
441
+ }: {
442
+ node: RequestNode,
443
+ hoverService: HoverService,
444
+ chatAgentService: ChatAgentService,
445
+ variableService: AIVariableService,
446
+ openerService: OpenerService
447
+ }) => {
448
+ const parts = node.request.message.parts;
449
+ return (
450
+ <div className="theia-RequestNode">
451
+ <p>
452
+ {parts.map((part, index) => {
453
+ if (part instanceof ParsedChatRequestAgentPart || part instanceof ParsedChatRequestVariablePart) {
454
+ let description = undefined;
455
+ let className = '';
456
+ if (part instanceof ParsedChatRequestAgentPart) {
457
+ description = chatAgentService.getAgent(part.agentId)?.description;
458
+ className = 'theia-RequestNode-AgentLabel';
459
+ } else if (part instanceof ParsedChatRequestVariablePart) {
460
+ description = variableService.getVariable(part.variableName)?.description;
461
+ className = 'theia-RequestNode-VariableLabel';
462
+ }
463
+ return (
464
+ <HoverableLabel
465
+ key={index}
466
+ text={part.text}
467
+ description={description}
468
+ hoverService={hoverService}
469
+ className={className}
470
+ />
471
+ );
472
+ } else {
473
+ // maintain the leading and trailing spaces with explicit `&nbsp;`, otherwise they would get trimmed by the markdown renderer
474
+ const ref = useMarkdownRendering(part.text.replace(/^\s|\s$/g, '&nbsp;'), openerService, true);
475
+ return (
476
+ <span key={index} ref={ref}></span>
477
+ );
478
+ }
479
+ })}
480
+ </p>
481
+ </div>
482
+ );
483
+ };
381
484
 
382
- return <div className={'theia-RequestNode'} ref={ref}></div>;
485
+ const HoverableLabel = (
486
+ {
487
+ text, description, hoverService, className
488
+ }: {
489
+ text: string,
490
+ description?: string,
491
+ hoverService: HoverService,
492
+ className: string
493
+ }) => {
494
+ const spanRef = React.createRef<HTMLSpanElement>();
495
+ return (
496
+ <span
497
+ className={className}
498
+ ref={spanRef}
499
+ onMouseEnter={() => {
500
+ if (description) {
501
+ hoverService.requestHover({
502
+ content: description,
503
+ target: spanRef.current!,
504
+ position: 'right'
505
+ });
506
+ }
507
+ }}
508
+ >
509
+ {text}
510
+ </span>
511
+ );
383
512
  };
384
513
 
385
514
  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);