// ***************************************************************************** // Copyright (C) 2025 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 { ChatWelcomeMessageProvider } from '@theia/ai-chat-ui/lib/browser/chat-tree-view'; import * as React from '@theia/core/shared/react'; import { nls } from '@theia/core/lib/common/nls'; import { inject, injectable, postConstruct } from '@theia/core/shared/inversify'; import { CommonCommands, LocalizedMarkdown, MarkdownRenderer } from '@theia/core/lib/browser'; import { AlertMessage } from '@theia/core/lib/browser/widgets/alert-message'; import { OPEN_AI_CONFIG_VIEW } from './ai-configuration/ai-configuration-view-contribution'; import { CommandRegistry, DisposableCollection, Emitter, Event, PreferenceScope } from '@theia/core'; import { AgentService, FrontendLanguageModelRegistry } from '@theia/ai-core/lib/common'; import { PreferenceService } from '@theia/core/lib/common'; import { DEFAULT_CHAT_AGENT_PREF, BYPASS_MODEL_REQUIREMENT_PREF } from '@theia/ai-chat/lib/common/ai-chat-preferences'; import { ChatAgentRecommendationService, ChatAgentService } from '@theia/ai-chat/lib/common'; const TheiaIdeAiLogo = ({ width = 200, height = 200, className = '' }) => ; @injectable() export class IdeChatWelcomeMessageProvider implements ChatWelcomeMessageProvider { @inject(MarkdownRenderer) protected readonly markdownRenderer: MarkdownRenderer; @inject(CommandRegistry) protected readonly commandRegistry: CommandRegistry; @inject(FrontendLanguageModelRegistry) protected languageModelRegistry: FrontendLanguageModelRegistry; @inject(PreferenceService) protected preferenceService: PreferenceService; @inject(ChatAgentRecommendationService) protected recommendationService: ChatAgentRecommendationService; @inject(ChatAgentService) protected chatAgentService: ChatAgentService; @inject(AgentService) protected agentService: AgentService; protected readonly toDispose = new DisposableCollection(); protected _hasReadyModels = false; protected _modelRequirementBypassed = false; protected _defaultAgent = ''; protected modelConfig: { hasModels: boolean; errorMessages: string[] } | undefined; protected readonly onStateChangedEmitter = new Emitter(); get onStateChanged(): Event { return this.onStateChangedEmitter.event; } @postConstruct() protected init(): void { this.checkLanguageModelStatus(); this.toDispose.push( this.languageModelRegistry.onChange(() => { this.checkLanguageModelStatus(); }) ); this.toDispose.push( this.preferenceService.onPreferenceChanged(e => { if (e.preferenceName === DEFAULT_CHAT_AGENT_PREF) { const effectiveValue = this.preferenceService.get(DEFAULT_CHAT_AGENT_PREF, ''); if (this._defaultAgent !== effectiveValue) { this._defaultAgent = effectiveValue; this.notifyStateChanged(); } } else if (e.preferenceName === BYPASS_MODEL_REQUIREMENT_PREF) { const effectiveValue = this.preferenceService.get(BYPASS_MODEL_REQUIREMENT_PREF, false); if (this._modelRequirementBypassed !== effectiveValue) { this._modelRequirementBypassed = effectiveValue; this.notifyStateChanged(); } } }) ); this.toDispose.push( this.agentService.onDidChangeAgents(() => { this.notifyStateChanged(); }) ); this.analyzeModelConfiguration().then(config => { this.modelConfig = config; this.notifyStateChanged(); }); this.preferenceService.ready.then(() => { const defaultAgentValue = this.preferenceService.get(DEFAULT_CHAT_AGENT_PREF, ''); const bypassValue = this.preferenceService.get(BYPASS_MODEL_REQUIREMENT_PREF, false); this._defaultAgent = defaultAgentValue; this._modelRequirementBypassed = bypassValue; this.notifyStateChanged(); }); } protected async checkLanguageModelStatus(): Promise { const models = await this.languageModelRegistry.getLanguageModels(); this._hasReadyModels = models.some(model => model.status.status === 'ready'); this.modelConfig = await this.analyzeModelConfiguration(); this.notifyStateChanged(); } protected async analyzeModelConfiguration(): Promise<{ hasModels: boolean; errorMessages: string[] }> { const models = await this.languageModelRegistry.getLanguageModels(); const hasModels = models.length > 0; const unavailableModels = models.filter(model => model.status.status === 'unavailable'); const errorMessages = unavailableModels .map(model => model.status.message) .filter((msg): msg is string => !!msg); const uniqueErrorMessages = [...new Set(errorMessages)]; return { hasModels, errorMessages: uniqueErrorMessages }; } protected notifyStateChanged(): void { this.onStateChangedEmitter.fire(); } get hasReadyModels(): boolean { return this._hasReadyModels; } get modelRequirementBypassed(): boolean { return this._modelRequirementBypassed; } get defaultAgent(): string { return this._defaultAgent; } protected setModelRequirementBypassed(bypassed: boolean): void { this.preferenceService.set(BYPASS_MODEL_REQUIREMENT_PREF, bypassed, PreferenceScope.User); } protected setDefaultAgent(agentId: string): void { this.preferenceService.set(DEFAULT_CHAT_AGENT_PREF, agentId, PreferenceScope.User); } dispose(): void { this.toDispose.dispose(); this.onStateChangedEmitter.dispose(); } renderWelcomeMessage(): React.ReactNode { if (!this._hasReadyModels && !this._modelRequirementBypassed) { return this.renderModelConfigurationScreen(); } if (!this._defaultAgent) { return this.renderAgentSelectionScreen(); } return this.renderWelcomeScreen(); } protected renderWelcomeScreen(): React.ReactNode { return
']} markdownRenderer={this.markdownRenderer} className="theia-WelcomeMessage-Content" markdownOptions={{ supportHtml: true }} />
; } protected renderModelConfigurationScreen(): React.ReactNode { const config = this.modelConfig ?? { hasModels: false, errorMessages: [] }; const { hasModels, errorMessages } = config; if (!hasModels) { return
⚠️
{nls.localize('theia/ai/ide/bypassHint', 'Some agents like Claude Code don\'t require Theia Language Models')}
; } return
{errorMessages.length > 0 && ( <>
    {errorMessages.map((msg, idx) =>
  • {msg}
  • )}
)}
{nls.localize('theia/ai/ide/bypassHint', 'Some agents like Claude Code don\'t require Theia Language Models')}
; } protected renderAgentSelectionScreen(): React.ReactNode { const recommendedAgents = this.recommendationService.getRecommendedAgents() .filter(agent => this.chatAgentService.getAgent(agent.id) !== undefined); return
{recommendedAgents.length > 0 && (

{nls.localize('theia/ai/ide/recommendedAgents', 'Recommended agents:')}

)} {recommendedAgents.length > 0 ? ( <>
{recommendedAgents.map(agent => ( ))}

{nls.localize('theia/ai/ide/or', 'or')}

) : ( )} 0 ? nls.localize('theia/ai/ide/moreAgentsAvailable/header', 'More agents are available') : nls.localize('theia/ai/ide/configureAgent/header', 'Configure a default agent')}>
; } renderDisabledMessage(): React.ReactNode { const openAiHistory = 'aiHistory:open'; return
{ nls.localize('theia/ai/chat-ui/chat-view-tree-widget/aiFeatureHeader', '🚀 AI Features Available (Beta Version)!')}

{nls.localize('theia/ai/chat-ui/chat-view-tree-widget/featuresDisabled', 'Currently, all AI Features are disabled!')}

{nls.localize('theia/ai/chat-ui/chat-view-tree-widget/howToEnable', 'How to Enable the AI Features:')}

{nls.localize('theia/ai/ide/chatDisabledMessage/featuresTitle', 'Currently Supported Views and Features:')}

; } }