Skip to main content

Frontend Architecture

Version: 2.0 (theia-based) Date: 2025-10-06 Status: Active


Overview

The AZ1.AI llm IDE frontend is built on Eclipse theia with custom browser extensions. We migrated from a standalone React prototype to a professional theia-based architecture.


Architecture Evolution

Version 1.0: React Prototype (Archived)

Location: archive/react-prototype/ Purpose: Initial proof of concept Status: ✅ Archived (reference only)

Stack:

  • React 18 + TypeScript
  • Chakra UI
  • Monaco editor (@monaco-editor/react)
  • xterm.js
  • Zustand (state management)
  • Vite (build)

Served: Port 5173

Version 2.0: theia Extensions (Production) ⭐

Location: src/browser/ Purpose: Production application Status: ✅ Active development

Stack:

  • Eclipse theia 1.65.0 (IDE framework)
  • TypeScript 5.3 (strict mode)
  • React (via theia's ReactWidget)
  • Inversify (dependency injection)
  • Monaco editor (built-in theia)
  • xterm.js (built-in theia)

Served: Port 3000


Current Structure

Frontend Components (Production)

├── Eclipse theia Core ✅
│ ├── File Explorer (built-in)
│ ├── Monaco editor (built-in)
│ ├── terminal (xterm.js, built-in)
│ ├── Settings (built-in)
│ └── Keybindings (built-in)

└── Custom Extensions 🔧
├── llm-integration/ ✅ # Phase 3 - Complete
│ ├── llm Chat Widget
│ ├── Multi-provider support (6)
│ ├── 4 workflow modes
│ └── Model selection UI

├── session-management/ 🔲 # Phase 4 - Planned
│ ├── Multi-session tabs
│ ├── Session isolation
│ └── FoundationDB persistence

├── mcp-integration/ 🔲 # Phase 5 - Planned
│ ├── MCP client
│ ├── Tool/resource discovery
│ └── Context management

└── agent-system/ 🔲 # Phase 6 - Planned
├── Agent hierarchy
├── A2A protocol
└── Status visualization

Directory Structure

Production (src/browser/)

src/
├── browser/ # theia browser extensions
│ ├── llm-integration/ # llm extension ✅
│ │ ├── llm-chat-widget.tsx # Main widget
│ │ ├── llm-contribution.ts # theia contribution
│ │ ├── llm-frontend-module.ts # DI container
│ │ ├── package.json # Extension metadata
│ │ └── services/
│ │ └── llm-service.ts # Multi-provider llm
│ │
│ ├── session-management/ # Coming in Phase 4
│ ├── mcp-integration/ # Coming in Phase 5
│ └── agent-system/ # Coming in Phase 6

└── types/ # Shared TypeScript types
└── index.ts

Archive (archive/react-prototype/)

archive/
└── react-prototype/ # Original React app
├── components/ # React components
│ ├── editor/
│ ├── llm/
│ ├── terminal/
│ ├── file-explorer/
│ └── layout/
├── services/ # API services
├── store/ # Zustand stores
├── theme/ # Chakra theme
├── app.tsx
├── app-test.tsx
└── main.tsx

Extension Architecture

theia Extension Pattern

Each extension follows this structure:

// 1. Extension Module (DI Container)
import { ContainerModule } from '@theia/core/shared/inversify';

export default new ContainerModule(bind => {
bind(MyService).toSelf().inSingletonScope();
bind(MyWidget).toSelf();
bind(WidgetFactory).toDynamicValue(...);
bindViewContribution(bind, MyContribution);
});

// 2. Contribution (Commands, Menus)
@injectable()
export class MyContribution extends AbstractViewContribution<MyWidget> {
registerCommands(commands: CommandRegistry): void {
// Register commands
}

registerMenus(menus: MenuModelRegistry): void {
// Register menus
}
}

// 3. Widget (UI Component)
@injectable()
export class MyWidget extends ReactWidget {
@inject(MyService)
protected readonly myService!: MyService;

protected render(): React.ReactNode {
return <div>My UI</div>;
}
}

// 4. Service (Business Logic)
@injectable()
export class MyService {
async doSomething(): Promise<void> {
// Business logic
}
}

Dependency Injection

Inversify Container:

  • Services bound as singletons
  • Widgets bound per instance
  • Automatic dependency resolution

Benefits:

  • Testable (mock services)
  • Modular (replace implementations)
  • Clean (no direct imports)

llm Integration Extension (Phase 3)

Architecture

llm Integration Extension

├── llm-chat-widget.tsx
│ ├── State Management (class properties)
│ ├── UI Rendering (React)
│ ├── Event Handling
│ └── Update Lifecycle

├── llm-contribution.ts
│ ├── Commands (llm-chat.open)
│ ├── Menus (View → Open llm Chat)
│ └── Keybindings

├── llm-frontend-module.ts
│ ├── DI Bindings
│ ├── Service Registration
│ └── Widget Factory

└── services/llm-service.ts
├── Provider Abstraction
├── API Clients (6 providers)
├── Streaming Support
└── Error Handling

Widget Lifecycle

@injectable()
export class llmChatWidget extends ReactWidget {
// 1. Construction
constructor() { super(); }

// 2. Post-construction (DI complete)
@postConstruct()
protected async init(): Promise<void> {
this.id = llm_CHAT_WIDGET_ID;
this.title.label = 'llm Chat';
await this.loadModels();
this.update();
}

// 3. Activation (widget shown)
protected onActivateRequest(msg: Message): void {
super.onActivateRequest(msg);
this.focusInput();
}

// 4. Rendering (React)
protected render(): React.ReactNode {
return <ChatUI />;
}

// 5. Updates
private handleChange(): void {
this.update(); // Triggers re-render
}
}

Multi-Provider llm Service

@injectable()
export class llmService {
// Provider URLs
private readonly LM_STUDIO_BASE_URL = ...;
private readonly OLLAMA_BASE_URL = ...;
private readonly OPENAI_BASE_URL = ...;
// ... etc

// Unified interface
async chatCompletion(
model: string,
messages: Message[],
temperature?: number,
maxTokens?: number
): Promise<string> {
const provider = this.getProviderFromModel(model);

switch (provider) {
case 'claude':
return this.claudeCodeChat(...);
case 'gemini':
return this.geminiChat(...);
default:
return this.openAICompatibleChat(...);
}
}

// Provider detection
private getProviderFromModel(modelId: string): llmProvider {
if (modelId.startsWith('claude')) return 'claude';
if (modelId.startsWith('gpt')) return 'openai';
if (modelId.startsWith('gemini')) return 'gemini';
// ... etc
}
}

theia Built-in Features (Don't Rebuild)

What theia Provides

File Management:

  • ✅ File Explorer (tree view)
  • ✅ File operations (CRUD)
  • ✅ File watchers
  • ✅ Search & replace

editor:

  • ✅ Monaco editor (VS Code engine)
  • ✅ Multi-tab support
  • ✅ Syntax highlighting
  • ✅ IntelliSense
  • ✅ Keybindings

terminal:

  • ✅ xterm.js integration
  • ✅ Multiple terminals
  • ✅ Split terminals
  • ✅ Shell integration

UI Framework:

  • ✅ Menus & toolbars
  • ✅ Status bar
  • ✅ Notifications
  • ✅ Dialogs
  • ✅ layouts

Extension System:

  • ✅ DI container
  • ✅ Contribution system
  • ✅ Widget management
  • ✅ Command registry

What We Build

Custom Extensions:

  • 🔧 llm integration
  • 🔧 Multi-session management
  • 🔧 MCP protocol integration
  • 🔧 Agent system
  • 🔧 OPFS integration (when ready)

State Management

theia Approach (No Zustand)

Services (via DI):

@injectable()
export class MyService {
private state = { ... };

private readonly onStateChange = new Emitter<State>();
readonly onStateChange$ = this.onStateChange.event;

updateState(newState: State): void {
this.state = newState;
this.onStateChange.fire(this.state);
}
}

Widgets (class properties):

export class MyWidget extends ReactWidget {
protected state = { ... };

protected handleChange(): void {
this.state = { ...this.state, updated: true };
this.update(); // Re-render
}
}

No Zustand - Use theia's patterns instead


Styling

theia Theme System

Built-in Themes:

  • Dark theme (default)
  • Light theme
  • VS Code compatible

Custom Styling:

// Use theia CSS variables
<div style={{
color: 'var(--theia-foreground)',
background: 'var(--theia-editor-background)',
border: '1px solid var(--theia-panel-border)'
}}>

Theme Variables:

  • --theia-foreground
  • --theia-editor-background
  • --theia-panel-border
  • --theia-button-background
  • ... and many more

Build System

Development

# Watch theia extensions
npm run theia:watch

# Watch & serve
npm run dev:all

Production

# Build theia application
npm run theia:build

# Optimize bundle
NODE_ENV=production npm run theia:build

Architecture

Build Pipeline

├── TypeScript Compilation
│ ├── src/browser/ → lib/browser/
│ └── tsconfig.json

├── theia Build
│ ├── Webpack bundling
│ ├── Extension discovery
│ └── Asset optimization

└── Output
├── lib/ (compiled JS)
└── dist/ (bundled app)

Testing

Unit Tests

// Widget test
describe('llmChatWidget', () => {
let widget: llmChatWidget;
let mockService: llmService;

beforeEach(() => {
mockService = mock(llmService);
widget = new llmChatWidget();
(widget as any).llmService = mockService;
});

it('should render chat UI', () => {
const node = widget.render();
expect(node).toBeDefined();
});
});

Integration Tests

// Extension test
describe('llm Extension', () => {
it('should register commands', () => {
const commands = container.get(CommandRegistry);
expect(commands.getCommand('llm-chat.open')).toBeDefined();
});
});

Performance

Optimization Strategies

  1. Lazy Loading:

    • Load extensions on demand
    • Defer heavy operations
  2. Virtual Scrolling:

    • Large message lists
    • File trees
  3. Memoization:

    • React.memo for components
    • useMemo for expensive calculations
  4. Web Workers:

    • Background processing
    • File operations
  5. Code Splitting:

    • Separate bundles per extension
    • Dynamic imports

Migration Guide

From React to theia

ComponentsWidgets:

// Before (React)
export function MyComponent() {
const [state, setState] = useState(...);
return <div>{state}</div>;
}

// After (theia)
@injectable()
export class MyWidget extends ReactWidget {
protected state = ...;

protected render(): React.ReactNode {
return <div>{this.state}</div>;
}
}

ServicesInjectable Services:

// Before
export const myService = new MyService();

// After
@injectable()
export class MyService {
// Service implementation
}

State ManagementDI + Events:

// Before (Zustand)
const useStore = create(set => ({
state: {},
update: (newState) => set(newState)
}));

// After (theia)
@injectable()
export class MyService {
private readonly onUpdate = new Emitter<State>();
readonly onUpdate$ = this.onUpdate.event;
}

Best Practices

DO ✅

  1. Use theia's built-in features - Don't rebuild file explorer, editor, terminal
  2. Follow DI pattern - Use @injectable() and @inject()
  3. Extend theia classes - AbstractViewContribution, ReactWidget, etc.
  4. Use theia's theme - CSS variables, not hardcoded colors
  5. Document extensions - README in each extension folder

DON'T ❌

  1. Don't bypass DI - Always inject dependencies
  2. Don't use global state - Use services and events
  3. Don't import theia internals - Use public APIs only
  4. Don't skip error handling - Handle all edge cases
  5. Don't commit without types - Full TypeScript coverage

Future Extensions

Roadmap

Phase 4: Session Management (next)

  • Multi-session tabs
  • Session isolation
  • FoundationDB persistence

Phase 5: MCP Integration

  • Full MCP client
  • Tool/resource discovery
  • Claude Code integration

Phase 6: Agent System

  • Hierarchical agents
  • A2A protocol
  • Agent visualization

Phase 7+:

  • OPFS file system
  • Plugin marketplace
  • Collaboration features


Last Updated: 2025-10-06 Version: 2.0 (theia-based) Status: Production Active