Skip to content

Commit

Permalink
Merge branch 'master' into issues/14111
Browse files Browse the repository at this point in the history
  • Loading branch information
rschnekenbu authored Sep 23, 2024
2 parents 5ccfa86 + dedfe25 commit a3af47d
Show file tree
Hide file tree
Showing 67 changed files with 1,007 additions and 387 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@
<!-- ## Unreleased
- [plugin] move stubbed API TerminalShellIntegration into main API [#14168](https://github.com/eclipse-theia/theia/pull/14168) - Contributed on behalf of STMicroelectronics
- [plugin] support evolution on proposed API extensionAny [#14199](https://github.com/eclipse-theia/theia/pull/14199) - Contributed on behalf of STMicroelectronics
- [test] support TestMessage stack traces [#14154](https://github.com/eclipse-theia/theia/pull/14154) - Contributed on behalf of STMicroelectronics
<a name="breaking_changes_1.54.0">[Breaking Changes:](#breaking_changes_1.54.0)</a> -->
- [core] Updated AuthenticationService to handle multiple accounts per provider [#14149](https://github.com/eclipse-theia/theia/pull/14149) - Contributed on behalf of STMicroelectronics
- [ai] Add toolbar actions on chat nodes [#14181](https://github.com/eclipse-theia/theia/pull/14181) - Contributed on behalf of STMicroelectronics

## 1.53.0 - 08/29/2024

Expand Down
1 change: 1 addition & 0 deletions examples/api-samples/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"version": "1.53.0",
"description": "Theia - Example code to demonstrate Theia API",
"dependencies": {
"@theia/ai-chat-ui": "1.53.0",
"@theia/core": "1.53.0",
"@theia/file-search": "1.53.0",
"@theia/filesystem": "1.53.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,15 @@ import { rebindOVSXClientFactory } from '../common/vsx/sample-ovsx-client-factor
import { bindSampleAppInfo } from './vsx/sample-frontend-app-info';
import { bindTestSample } from './test/sample-test-contribution';
import { bindSampleFileSystemCapabilitiesCommands } from './file-system/sample-file-system-capabilities';
import { bindChatNodeToolbarActionContribution } from './chat/chat-node-toolbar-action-contribution';

export default new ContainerModule((
bind: interfaces.Bind,
unbind: interfaces.Unbind,
isBound: interfaces.IsBound,
rebind: interfaces.Rebind,
) => {
bindChatNodeToolbarActionContribution(bind);
bindDynamicLabelProvider(bind);
bindSampleUnclosableView(bind);
bindSampleOutputChannelWithSeverity(bind);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// *****************************************************************************
// Copyright (C) 2024 STMicroelectronics and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************

import {
ChatNodeToolbarActionContribution
} from '@theia/ai-chat-ui/lib/browser/chat-node-toolbar-action-contribution';
import {
isResponseNode,
RequestNode,
ResponseNode
} from '@theia/ai-chat-ui/lib/browser/chat-tree-view';
import { interfaces } from '@theia/core/shared/inversify';

export function bindChatNodeToolbarActionContribution(bind: interfaces.Bind): void {
bind(ChatNodeToolbarActionContribution).toDynamicValue(context => ({
getToolbarActions: (args: RequestNode | ResponseNode) => {
if (isResponseNode(args)) {
return [{
commandId: 'sample-command',
icon: 'codicon codicon-feedback',
tooltip: 'Example command'
}];
} else {
return [];
}
}
}));
}
3 changes: 3 additions & 0 deletions examples/api-samples/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
{
"path": "../../dev-packages/ovsx-client"
},
{
"path": "../../packages/ai-chat-ui"
},
{
"path": "../../packages/core"
},
Expand Down
12 changes: 7 additions & 5 deletions packages/ai-chat-ui/src/browser/ai-chat-ui-frontend-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,27 +15,28 @@
// *****************************************************************************

import { bindContributionProvider, CommandContribution, MenuContribution } from '@theia/core';
import { bindViewContribution, FrontendApplicationContribution, WidgetFactory, } from '@theia/core/lib/browser';
import { bindViewContribution, FrontendApplicationContribution, WidgetFactory } from '@theia/core/lib/browser';
import { TabBarToolbarContribution } from '@theia/core/lib/browser/shell/tab-bar-toolbar';
import { ContainerModule, interfaces } from '@theia/core/shared/inversify';
import { EditorManager } from '@theia/editor/lib/browser';
import '../../src/browser/style/index.css';
import { AIChatContribution } from './ai-chat-ui-contribution';
import { AIChatInputWidget } from './chat-input-widget';
import { CodePartRenderer, CommandPartRenderer, HorizontalLayoutPartRenderer, MarkdownPartRenderer, ErrorPartRenderer, ToolCallPartRenderer } from './chat-response-renderer';
import { ChatNodeToolbarActionContribution } from './chat-node-toolbar-action-contribution';
import { ChatResponsePartRenderer } from './chat-response-part-renderer';
import { CodePartRenderer, CommandPartRenderer, ErrorPartRenderer, HorizontalLayoutPartRenderer, MarkdownPartRenderer, ToolCallPartRenderer } from './chat-response-renderer';
import {
AIEditorManager, AIEditorSelectionResolver,
GitHubSelectionResolver, TextFragmentSelectionResolver, TypeDocSymbolSelectionResolver
} from './chat-response-renderer/ai-editor-manager';
import { createChatViewTreeWidget } from './chat-tree-view';
import { ChatViewTreeWidget } from './chat-tree-view/chat-view-tree-widget';
import { ChatViewLanguageContribution } from './chat-view-language-contribution';
import { ChatViewMenuContribution } from './chat-view-contribution';
import { ChatViewLanguageContribution } from './chat-view-language-contribution';
import { ChatViewWidget } from './chat-view-widget';
import { ChatViewWidgetToolbarContribution } from './chat-view-widget-toolbar-contribution';
import { ChatResponsePartRenderer } from './chat-response-part-renderer';

export default new ContainerModule((bind, _ubind, _isBound, rebind) => {
export default new ContainerModule((bind, _unbind, _isBound, rebind) => {
bindViewContribution(bind, AIChatContribution);
bind(TabBarToolbarContribution).toService(AIChatContribution);

Expand Down Expand Up @@ -81,6 +82,7 @@ export default new ContainerModule((bind, _ubind, _isBound, rebind) => {

bind(FrontendApplicationContribution).to(ChatViewLanguageContribution).inSingletonScope();

bindContributionProvider(bind, ChatNodeToolbarActionContribution);
});

function bindChatViewWidget(bind: interfaces.Bind): void {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// *****************************************************************************
// Copyright (C) 2024 EclipseSource GmbH.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
import { RequestNode, ResponseNode } from './chat-tree-view';

export interface ChatNodeToolbarAction {
/**
* The command to execute when the item is selected. The handler will receive the `RequestNode` or `ResponseNode` as first argument.
*/
commandId: string;
/**
* Icon class name(s) for the item (e.g. 'codicon codicon-feedback').
*/
icon: string;
/**
* Priority among the items. Can be negative. The smaller the number the left-most the item will be placed in the toolbar. It is `0` by default.
*/
priority?: number;
/**
* Optional tooltip for the item.
*/
tooltip?: string;
}

/**
* Clients implement this interface if they want to contribute to the toolbar of chat nodes.
*
* ### Example
* ```ts
* bind(ChatNodeToolbarActionContribution).toDynamicValue(context => ({
* getToolbarActions: (args: RequestNode | ResponseNode) => {
* if (isResponseNode(args)) {
* return [{
* commandId: 'core.about',
* icon: 'codicon codicon-feedback',
* tooltip: 'Show about dialog on response nodes'
* }];
* } else {
* return [];
* }
* }
* }));
* ```
*/
export const ChatNodeToolbarActionContribution = Symbol('ChatNodeToolbarActionContribution');
export interface ChatNodeToolbarActionContribution {
/**
* Returns the toolbar actions for the given node.
*/
getToolbarActions(node: RequestNode | ResponseNode): ChatNodeToolbarAction[];
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
import {
ChatResponseContent,
ChatAgentService,
ChatModel,
ChatProgressMessage,
ChatRequestModel,
ChatResponseContent,
ChatResponseModel,
} from '@theia/ai-chat';
import { CommandRegistry, ContributionProvider } from '@theia/core';
Expand All @@ -40,13 +40,14 @@ import {
inject,
injectable,
named,
postConstruct,
postConstruct
} from '@theia/core/shared/inversify';
import * as React from '@theia/core/shared/react';

import { MarkdownRenderer } from '@theia/core/lib/browser/markdown-rendering/markdown-renderer';
import { MarkdownWrapper } from '../chat-response-renderer/markdown-part-renderer';
import { ChatNodeToolbarActionContribution } from '../chat-node-toolbar-action-contribution';
import { ChatResponsePartRenderer } from '../chat-response-part-renderer';
import { MarkdownWrapper } from '../chat-response-renderer/markdown-part-renderer';

// TODO Instead of directly operating on the ChatRequestModel we could use an intermediate view model
export interface RequestNode extends TreeNode {
Expand All @@ -72,6 +73,9 @@ export class ChatViewTreeWidget extends TreeWidget {
@inject(ContributionProvider) @named(ChatResponsePartRenderer)
protected readonly chatResponsePartRenderers: ContributionProvider<ChatResponsePartRenderer<ChatResponseContent>>;

@inject(ContributionProvider) @named(ChatNodeToolbarActionContribution)
protected readonly chatNodeToolbarActionContributions: ContributionProvider<ChatNodeToolbarActionContribution>;

@inject(MarkdownRenderer)
private renderer: MarkdownRenderer;

Expand Down Expand Up @@ -147,11 +151,11 @@ export class ChatViewTreeWidget extends TreeWidget {
<div className="section-content">
<p>To enable the experimental AI features, please go to &nbsp;
{this.renderLinkButton('the settings menu', CommonCommands.OPEN_PREFERENCES.id)}
&nbsp;and locate the <strong>Extensions &gt; ✨ AI Features [Experimental]</strong> section.</p>
&nbsp;and locate the <strong>AI Features</strong> section.</p>
<ol>
<li>Toggle the switch for <strong>'Ai-features: Enable'</strong>.</li>
<li>Provide an OpenAI API Key through the <strong>'OpenAI: API Key'</strong> setting or by
setting the <strong>OPENAI_API_KEY</strong> environment variable.</li>
<li>Provide at least one LLM provider (e.g. OpenAI), also see <a href="https://theia-ide.org/docs/user_ai/" target="_blank">the documentation</a>
for more information.</li>
</ol>
<p>This will activate the new AI capabilities in the app. Please remember, these features are still in development, so they may change or be unstable. 🚧</p>
</div>
Expand All @@ -163,11 +167,19 @@ export class ChatViewTreeWidget extends TreeWidget {
<p>Once the experimental AI features are enabled, you can access the following views and features:</p>
<ul>
<li>Code Completion</li>
<li>Quick Fixes</li>
<li>Terminal Assistance</li>
<li>Terminal Assistance (via CTRL+I in a terminal)</li>
<li>This Chat View (features the following agents):
<ul>
<li>Universal Chat Agent</li>
<li>Workspace Chat Agent</li>
<li>Command Chat Agent</li>
<li>Orchestrator Chat Agent</li>
</ul>
</li>
<li>{this.renderLinkButton('AI History View', 'aiHistory:open')}</li>
<li>{this.renderLinkButton('AI Configuration View', 'aiConfiguration:open')}</li>
</ul>
<p>See <a href="https://theia-ide.org/docs/user_ai/" target="_blank">the documentation</a> for more information.</p>
</div>
</div>
</div>
Expand Down Expand Up @@ -257,16 +269,45 @@ export class ChatViewTreeWidget extends TreeWidget {
</div>
</React.Fragment>;
}

private renderAgent(node: RequestNode | ResponseNode): React.ReactNode {
const inProgress = isResponseNode(node) && !node.response.isComplete && !node.response.isCanceled && !node.response.isError;
const toolbarContributions = !inProgress
? this.chatNodeToolbarActionContributions.getContributions()
.flatMap(c => c.getToolbarActions(node))
.filter(action => this.commandRegistry.isEnabled(action.commandId, node))
.sort((a, b) => (a.priority ?? 0) - (b.priority ?? 0))
: [];
return <React.Fragment>
<div className='theia-ChatNodeHeader'>
<div className={`theia-AgentAvatar ${this.getAgentIconClassName(node)}`}></div>
<h3 className='theia-AgentLabel'>{this.getAgentLabel(node)}</h3>
{inProgress && <span className='theia-ChatContentInProgress'>Generating</span>}
<div className='theia-ChatNodeToolbar'>
{!inProgress &&
toolbarContributions.length > 0 &&
toolbarContributions.map(action =>
<span
className={`theia-ChatNodeToolbarAction ${action.icon}`}
title={action.tooltip}
onClick={e => {
e.stopPropagation();
this.commandRegistry.executeCommand(action.commandId, node);
}}
onKeyDown={e => {
if (isEnterKey(e)) {
e.stopPropagation();
this.commandRegistry.executeCommand(action.commandId, node);
}
}}
role='button'
></span>
)}
</div>
</div>
</React.Fragment>;
}

private getAgentLabel(node: RequestNode | ResponseNode): string {
if (isRequestNode(node)) {
// TODO find user name
Expand All @@ -275,6 +316,7 @@ export class ChatViewTreeWidget extends TreeWidget {
const agent = node.response.agentId ? this.chatAgentService.getAgent(node.response.agentId) : undefined;
return agent?.name ?? 'AI';
}

private getAgentIconClassName(node: RequestNode | ResponseNode): string | undefined {
if (isRequestNode(node)) {
return codicon('account');
Expand Down
21 changes: 20 additions & 1 deletion packages/ai-chat-ui/src/browser/style/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,13 @@ div:last-child > .theia-ChatNode {
.theia-ChatNodeHeader {
align-items: center;
display: flex;
justify-content: space-between;
height: 24px;
gap: 8px;
width: 100%;
}

.theia-ChatNodeHeader .theia-AgentAvatar {
display: flex;
pointer-events: none;
user-select: none;
font-size: 20px;
Expand Down Expand Up @@ -91,6 +92,24 @@ div:last-child > .theia-ChatNode {
font-weight: 600;
}

.theia-ChatNode .theia-ChatNodeToolbar {
margin-left: auto;
line-height: 18px;
}
.theia-ChatNodeToolbar .theia-ChatNodeToolbarAction {
display: none;
align-items: center;
padding: 4px;
border-radius: 5px;
}
.theia-ChatNode:hover .theia-ChatNodeToolbar .theia-ChatNodeToolbarAction {
display: inline-block;
}
.theia-ChatNodeToolbar .theia-ChatNodeToolbarAction:hover {
cursor: pointer;
background-color: var(--theia-toolbar-hoverBackground);
}

.theia-ChatNode .rendered-markdown p {
margin: 0 0 16px;
}
Expand Down
5 changes: 3 additions & 2 deletions packages/ai-chat/src/browser/ai-chat-preferences.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,15 @@
import { AI_CORE_PREFERENCES_TITLE } from '@theia/ai-core/lib/browser/ai-core-preferences';
import { PreferenceSchema } from '@theia/core/lib/browser/preferences/preference-contribution';

export const DEFAULT_CHAT_AGENT_PREF = 'ai-features.chat.default-chat-agent';
export const DEFAULT_CHAT_AGENT_PREF = 'ai-features.chat.defaultChatAgent';

export const aiChatPreferences: PreferenceSchema = {
type: 'object',
properties: {
[DEFAULT_CHAT_AGENT_PREF]: {
type: 'string',
description: '<agent-name> of the Chat Agent that shall be invoked, if no agent is explicitly mentioned with @<agent-name> in the user query.',
description: 'Optional: <agent-name> of the Chat Agent that shall be invoked, if no agent is explicitly mentioned with @<agent-name> in the user query.\
If no Default Agent is configured, Theia´s defaults will be applied.',
title: AI_CORE_PREFERENCES_TITLE,
}
}
Expand Down
7 changes: 7 additions & 0 deletions packages/ai-chat/src/common/chat-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -767,3 +767,10 @@ class ChatResponseModelImpl implements ChatResponseModel {
return this._isError;
}
}

export class ErrorChatResponseModelImpl extends ChatResponseModelImpl {
constructor(requestId: string, error: Error, agentId?: string) {
super(requestId, agentId);
this.error(error);
}
}
Loading

0 comments on commit a3af47d

Please sign in to comment.