Skip to content

UI API Guide

The UI API provides plugins with ways to interact with users through notifications, dialogs, status bar items, and more.

Overview

typescript
interface UIAPI {
    // Notifications
    showNotification(message: string, options?: NotificationOptions): void;
    showError(message: string): void;
    showWarning(message: string): void;

    // Dialogs
    showQuickPick<T>(items: QuickPickItem<T>[], options?: QuickPickOptions): Promise<T | undefined>;
    showInputBox(options?: InputBoxOptions): Promise<string | undefined>;
    showConfirmDialog(message: string, options?: ConfirmOptions): Promise<boolean>;

    // Progress
    withProgress<T>(options: ProgressOptions, task: ProgressTask<T>): Promise<T>;

    // Status Bar
    createStatusBarItem(options: StatusBarItemOptions): StatusBarItem;

    // Theme
    readonly theme: {
        current: Theme;
        onChange: Event<Theme>;
    };
}

Notifications

Show brief messages to users.

Basic Notifications

typescript
const { ui } = context;

// Simple notification
ui.showNotification('Operation completed!');

// With type
ui.showNotification('File saved', { type: 'success' });
ui.showNotification('Check your settings', { type: 'info' });
ui.showWarning('Disk space is low');
ui.showError('Failed to connect');

Notification Options

typescript
interface NotificationOptions {
    type?: 'info' | 'success' | 'warning' | 'error';
    duration?: number;  // Milliseconds (0 = persistent)
    action?: {
        label: string;
        callback: () => void;
    };
}

With Action Button

typescript
ui.showNotification('Update available!', {
    type: 'info',
    duration: 0,  // Persistent until dismissed
    action: {
        label: 'Install',
        callback: () => {
            installUpdate();
        },
    },
});

Custom Duration

typescript
// Show for 10 seconds
ui.showNotification('Long message', { duration: 10000 });

// Show until dismissed
ui.showNotification('Important notice', { duration: 0 });

Dialogs

Quick Pick (Selection Dialog)

Let users choose from a list of options.

typescript
interface QuickPickItem<T = string> {
    label: string;
    description?: string;
    detail?: string;
    value: T;
}

interface QuickPickOptions {
    title?: string;
    placeholder?: string;
    canSelectMany?: boolean;
}

Example: Simple Selection

typescript
const model = await ui.showQuickPick([
    { label: 'Claude 3 Opus', value: 'claude-3-opus' },
    { label: 'Claude 3 Sonnet', value: 'claude-3-sonnet' },
    { label: 'GPT-4', value: 'gpt-4' },
], {
    title: 'Select a Model',
    placeholder: 'Choose an AI model',
});

if (model) {
    logger.info(`Selected: ${model}`);
}

Example: Rich Items

typescript
const action = await ui.showQuickPick([
    {
        label: 'Export as PDF',
        description: 'Download conversation',
        detail: 'Exports the current thread as a formatted PDF document',
        value: 'export-pdf',
    },
    {
        label: 'Share Link',
        description: 'Create shareable link',
        detail: 'Generate a public link that others can view',
        value: 'share-link',
    },
    {
        label: 'Archive Thread',
        description: 'Move to archive',
        detail: 'Hides the thread from the main list',
        value: 'archive',
    },
]);

Example: Multi-Select

typescript
const features = await ui.showQuickPick([
    { label: 'Syntax Highlighting', value: 'syntax' },
    { label: 'Auto-complete', value: 'autocomplete' },
    { label: 'Error Detection', value: 'errors' },
], {
    title: 'Enable Features',
    canSelectMany: true,
});

if (features) {
    logger.info(`Enabled: ${features.join(', ')}`);
}

Input Box

Get text input from users.

typescript
interface InputBoxOptions {
    title?: string;
    prompt?: string;
    placeholder?: string;
    value?: string;
    password?: boolean;
    validateInput?: (value: string) => string | undefined;
}

Example: Simple Input

typescript
const name = await ui.showInputBox({
    title: 'Enter Name',
    prompt: 'What should we call you?',
    placeholder: 'Your name',
});

if (name) {
    ui.showNotification(`Hello, ${name}!`);
}

Example: With Default Value

typescript
const timeout = await ui.showInputBox({
    title: 'Request Timeout',
    prompt: 'Enter timeout in seconds',
    value: '30',  // Pre-filled value
});

Example: Password Input

typescript
const apiKey = await ui.showInputBox({
    title: 'API Key',
    prompt: 'Enter your API key',
    password: true,  // Masked input
});

if (apiKey) {
    await storage.secrets.set('apiKey', apiKey);
}

Example: With Validation

typescript
const port = await ui.showInputBox({
    title: 'Server Port',
    prompt: 'Enter a port number (1024-65535)',
    value: '3000',
    validateInput: (value) => {
        const num = parseInt(value, 10);
        if (isNaN(num)) {
            return 'Must be a number';
        }
        if (num < 1024 || num > 65535) {
            return 'Port must be between 1024 and 65535';
        }
        return undefined;  // Valid
    },
});

Confirm Dialog

Get yes/no confirmation from users.

typescript
interface ConfirmOptions {
    title?: string;
    confirmLabel?: string;
    cancelLabel?: string;
    type?: 'info' | 'warning' | 'danger';
}

Example: Simple Confirm

typescript
const confirmed = await ui.showConfirmDialog('Delete this thread?');

if (confirmed) {
    await deleteThread(threadId);
}

Example: Warning Dialog

typescript
const confirmed = await ui.showConfirmDialog(
    'This will permanently delete all messages. Continue?',
    {
        title: 'Clear History',
        type: 'warning',
        confirmLabel: 'Delete All',
        cancelLabel: 'Keep',
    }
);

Example: Danger Dialog

typescript
const confirmed = await ui.showConfirmDialog(
    'This action cannot be undone. Your account and all data will be permanently deleted.',
    {
        title: 'Delete Account',
        type: 'danger',
        confirmLabel: 'Delete Forever',
    }
);

Progress Indicator

Show progress for long-running operations.

typescript
interface ProgressOptions {
    title: string;
    cancellable?: boolean;
}

interface ProgressReport {
    increment?: number;  // Percentage increase
    message?: string;    // Status message
}

type ProgressTask<T> = (
    progress: { report: (value: ProgressReport) => void },
    token: { isCancellationRequested: boolean }
) => Promise<T>;

Example: Indeterminate Progress

typescript
const result = await ui.withProgress(
    { title: 'Processing...' },
    async (progress) => {
        progress.report({ message: 'Connecting...' });
        await connect();

        progress.report({ message: 'Downloading...' });
        const data = await download();

        progress.report({ message: 'Parsing...' });
        return parse(data);
    }
);

Example: Determinate Progress

typescript
const results = await ui.withProgress(
    { title: 'Processing Files' },
    async (progress) => {
        const files = await getFiles();
        const results = [];

        for (let i = 0; i < files.length; i++) {
            progress.report({
                increment: 100 / files.length,
                message: `Processing ${files[i].name}...`,
            });

            results.push(await processFile(files[i]));
        }

        return results;
    }
);

Example: Cancellable Progress

typescript
try {
    const result = await ui.withProgress(
        {
            title: 'Uploading Files',
            cancellable: true,
        },
        async (progress, token) => {
            for (const file of files) {
                // Check for cancellation
                if (token.isCancellationRequested) {
                    throw new Error('Upload cancelled');
                }

                progress.report({ message: `Uploading ${file.name}...` });
                await upload(file);
            }
        }
    );
} catch (error) {
    if (error.message === 'Upload cancelled') {
        ui.showNotification('Upload cancelled', { type: 'info' });
    } else {
        ui.showError(`Upload failed: ${error.message}`);
    }
}

Status Bar

Add persistent items to the application status bar.

typescript
interface StatusBarItemOptions {
    id: string;
    alignment: 'left' | 'right';
    priority?: number;  // Higher = closer to edge
}

interface StatusBarItem extends Disposable {
    text: string;
    tooltip?: string;
    command?: string;
    show(): void;
    hide(): void;
}

Example: Simple Status Item

typescript
const statusItem = ui.createStatusBarItem({
    id: 'my-plugin-status',
    alignment: 'right',
    priority: 100,
});

statusItem.text = 'Ready';
statusItem.tooltip = 'My Plugin is active';
statusItem.show();

// Update later
statusItem.text = 'Processing...';

// Clean up when done
statusItem.dispose();

Example: With Command

typescript
// Register a command first
commands.register('myPlugin.showStats', () => {
    ui.showNotification('Stats: 100 tokens used');
});

// Create status item that triggers the command
const statusItem = ui.createStatusBarItem({
    id: 'token-counter',
    alignment: 'right',
});

statusItem.text = 'Tokens: 100';
statusItem.tooltip = 'Click for details';
statusItem.command = 'myPlugin.showStats';
statusItem.show();

Example: Real-time Updates

typescript
const statusItem = ui.createStatusBarItem({
    id: 'live-counter',
    alignment: 'right',
});

let count = 0;

events.on('chat.message.didReceive', (input) => {
    if (input.response.usage) {
        count += input.response.usage.totalTokens;
        statusItem.text = `Tokens: ${count.toLocaleString()}`;
        statusItem.tooltip = [
            `Prompt: ${input.response.usage.promptTokens}`,
            `Completion: ${input.response.usage.completionTokens}`,
        ].join('\n');
    }
});

statusItem.text = 'Tokens: 0';
statusItem.show();

Theme API

Access and react to theme changes.

typescript
interface Theme {
    id: string;
    name: string;
    type: 'dark' | 'light';
}

Example: Get Current Theme

typescript
const { ui, logger } = context;

const currentTheme = ui.theme.current;
logger.info(`Current theme: ${currentTheme.name} (${currentTheme.type})`);

if (currentTheme.type === 'dark') {
    // Adjust for dark mode
}

Example: React to Theme Changes

typescript
const disposable = ui.theme.onChange((theme) => {
    logger.info(`Theme changed to: ${theme.name}`);

    if (theme.type === 'dark') {
        statusItem.text = '🌙 Dark Mode';
    } else {
        statusItem.text = '☀️ Light Mode';
    }
});

Best Practices

1. Keep Messages Concise

typescript
// Good
ui.showNotification('File saved');

// Avoid
ui.showNotification('The file has been successfully saved to the disk. You can now close the editor.');

2. Use Appropriate Notification Types

typescript
ui.showNotification('Connected', { type: 'success' });  // Positive outcomes
ui.showNotification('Processing', { type: 'info' });     // Informational
ui.showWarning('Disk space low');                        // Warnings
ui.showError('Connection failed');                       // Errors only

3. Provide Escape Hatches

Always allow users to cancel or dismiss:

typescript
// Input can be cancelled with ESC
const input = await ui.showInputBox({ ... });
if (!input) return;  // User cancelled

// Confirm dialogs have cancel option
const confirmed = await ui.showConfirmDialog('Continue?');
if (!confirmed) return;

4. Clean Up Status Items

typescript
export async function activate(context) {
    const statusItem = ui.createStatusBarItem({ ... });
    statusItem.show();

    return {
        dispose: () => {
            statusItem.dispose();  // Always clean up
        },
    };
}

5. Use Progress for Long Operations

typescript
// Bad: No feedback
await longOperation();

// Good: Shows progress
await ui.withProgress(
    { title: 'Processing...', cancellable: true },
    async (progress, token) => {
        await longOperation();
    }
);