mirror of
https://github.com/andrepimenta/claude-code-chat.git
synced 2025-12-13 13:49:47 +00:00
Add model selection feature with UI controls
- Add model selector dropdown in chat interface (Opus, Sonnet, Default) - Model preference persists across sessions using workspace state - Add model validation to prevent invalid selections - Display confirmation messages when switching models - Update Claude command to include --model flag when specific model selected - Reorganize input controls layout for better UX - Update documentation in README.md and CHANGELOG.md
This commit is contained in:
14
CHANGELOG.md
14
CHANGELOG.md
@@ -4,6 +4,20 @@ All notable changes to the "claude-code-chat" extension will be documented in th
|
|||||||
|
|
||||||
Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file.
|
Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file.
|
||||||
|
|
||||||
|
## [0.0.9] - 2025-06-19
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Model selector dropdown in the chat interface
|
||||||
|
- Located to the left of the tools selector at the bottom of the chat box
|
||||||
|
- Supports three models: Opus (most capable), Sonnet (balanced), and Default (smart allocation)
|
||||||
|
- Model preference is saved and persists across sessions
|
||||||
|
- Validates model selection to prevent invalid model names
|
||||||
|
- Shows confirmation message when switching models
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Reorganized input controls into left-controls and right-controls sections for better layout
|
||||||
|
- Claude command now includes the --model flag when a specific model is selected
|
||||||
|
|
||||||
## [0.0.8] - 2025-06-19
|
## [0.0.8] - 2025-06-19
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ Ditch the command line and experience Claude Code like never before. This extens
|
|||||||
🎨 **VS Code Native** - Seamlessly matches your editor's theme and design
|
🎨 **VS Code Native** - Seamlessly matches your editor's theme and design
|
||||||
📁 **Smart File Context** - Reference any file with simple @ mentions
|
📁 **Smart File Context** - Reference any file with simple @ mentions
|
||||||
🛑 **Full Control** - Start, stop, and manage AI processes with ease
|
🛑 **Full Control** - Start, stop, and manage AI processes with ease
|
||||||
|
🤖 **Model Selection** - Choose between Opus, Sonnet, or Default based on your needs
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -61,6 +62,14 @@ Ditch the command line and experience Claude Code like never before. This extens
|
|||||||
- Activity bar panel for quick access
|
- Activity bar panel for quick access
|
||||||
- Responsive design for any screen size
|
- Responsive design for any screen size
|
||||||
|
|
||||||
|
### 🤖 **Model Selection**
|
||||||
|
- **Opus** - Most capable model for complex tasks requiring deep reasoning
|
||||||
|
- **Sonnet** - Balanced model offering great performance for most use cases
|
||||||
|
- **Default** - Smart model allocation based on Claude's recommendations
|
||||||
|
- Model preference persists across sessions and is saved automatically
|
||||||
|
- Easy switching via dropdown selector in the chat interface
|
||||||
|
- Visual confirmation when switching between models
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🚀 **Getting Started**
|
## 🚀 **Getting Started**
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"name": "claude-code-chat",
|
"name": "claude-code-chat",
|
||||||
"displayName": "Claude Code Chat",
|
"displayName": "Claude Code Chat",
|
||||||
"description": "Beautiful Claude Code Chat Interface for VS Code",
|
"description": "Beautiful Claude Code Chat Interface for VS Code",
|
||||||
"version": "0.0.8",
|
"version": "0.0.9",
|
||||||
"publisher": "AndrePimenta",
|
"publisher": "AndrePimenta",
|
||||||
"author": "Andre Pimenta",
|
"author": "Andre Pimenta",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|||||||
@@ -128,6 +128,7 @@ class ClaudeChatProvider {
|
|||||||
}> = [];
|
}> = [];
|
||||||
private _treeProvider: ClaudeChatViewProvider | undefined;
|
private _treeProvider: ClaudeChatViewProvider | undefined;
|
||||||
private _currentClaudeProcess: cp.ChildProcess | undefined;
|
private _currentClaudeProcess: cp.ChildProcess | undefined;
|
||||||
|
private _selectedModel: string = 'opus'; // Default model
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly _extensionUri: vscode.Uri,
|
private readonly _extensionUri: vscode.Uri,
|
||||||
@@ -141,6 +142,9 @@ class ClaudeChatProvider {
|
|||||||
// Load conversation index from workspace state
|
// Load conversation index from workspace state
|
||||||
this._conversationIndex = this._context.workspaceState.get('claude.conversationIndex', []);
|
this._conversationIndex = this._context.workspaceState.get('claude.conversationIndex', []);
|
||||||
|
|
||||||
|
// Load saved model preference
|
||||||
|
this._selectedModel = this._context.workspaceState.get('claude.selectedModel', 'opus');
|
||||||
|
|
||||||
// Resume session from latest conversation
|
// Resume session from latest conversation
|
||||||
const latestConversation = this._getLatestConversation();
|
const latestConversation = this._getLatestConversation();
|
||||||
this._currentSessionId = latestConversation?.sessionId;
|
this._currentSessionId = latestConversation?.sessionId;
|
||||||
@@ -209,6 +213,9 @@ class ClaudeChatProvider {
|
|||||||
case 'getClipboardText':
|
case 'getClipboardText':
|
||||||
this._getClipboardText();
|
this._getClipboardText();
|
||||||
return;
|
return;
|
||||||
|
case 'selectModel':
|
||||||
|
this._setSelectedModel(message.model);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
@@ -240,6 +247,12 @@ class ClaudeChatProvider {
|
|||||||
type: 'ready',
|
type: 'ready',
|
||||||
data: 'Ready to chat with Claude Code! Type your message below.'
|
data: 'Ready to chat with Claude Code! Type your message below.'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Send current model to webview
|
||||||
|
this._panel?.webview.postMessage({
|
||||||
|
type: 'modelSelected',
|
||||||
|
model: this._selectedModel
|
||||||
|
});
|
||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,6 +296,12 @@ class ClaudeChatProvider {
|
|||||||
'--dangerously-skip-permissions'
|
'--dangerously-skip-permissions'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Add model selection if not using default
|
||||||
|
if (this._selectedModel && this._selectedModel !== 'default') {
|
||||||
|
args.push('--model', this._selectedModel);
|
||||||
|
console.log('Using model:', this._selectedModel);
|
||||||
|
}
|
||||||
|
|
||||||
// Add session resume if we have a current session
|
// Add session resume if we have a current session
|
||||||
if (this._currentSessionId) {
|
if (this._currentSessionId) {
|
||||||
args.push('--resume', this._currentSessionId);
|
args.push('--resume', this._currentSessionId);
|
||||||
@@ -1161,6 +1180,24 @@ class ClaudeChatProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _setSelectedModel(model: string): void {
|
||||||
|
// Validate model name to prevent issues mentioned in the GitHub issue
|
||||||
|
const validModels = ['opus', 'sonnet', 'default'];
|
||||||
|
if (validModels.includes(model)) {
|
||||||
|
this._selectedModel = model;
|
||||||
|
console.log('Model selected:', model);
|
||||||
|
|
||||||
|
// Store the model preference in workspace state
|
||||||
|
this._context.workspaceState.update('claude.selectedModel', model);
|
||||||
|
|
||||||
|
// Show confirmation
|
||||||
|
vscode.window.showInformationMessage(`Claude model switched to: ${model.charAt(0).toUpperCase() + model.slice(1)}`);
|
||||||
|
} else {
|
||||||
|
console.error('Invalid model selected:', model);
|
||||||
|
vscode.window.showErrorMessage(`Invalid model: ${model}. Please select Opus, Sonnet, or Default.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public dispose() {
|
public dispose() {
|
||||||
if (this._panel) {
|
if (this._panel) {
|
||||||
this._panel.dispose();
|
this._panel.dispose();
|
||||||
|
|||||||
146
src/ui.ts
146
src/ui.ts
@@ -484,13 +484,40 @@ const html = `<!DOCTYPE html>
|
|||||||
.input-controls {
|
.input-controls {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: end;
|
justify-content: space-between;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
padding: 2px 4px;
|
padding: 2px 4px;
|
||||||
border-top: 1px solid var(--vscode-panel-border);
|
border-top: 1px solid var(--vscode-panel-border);
|
||||||
background-color: var(--vscode-input-background);
|
background-color: var(--vscode-input-background);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.left-controls {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-selector {
|
||||||
|
background-color: rgba(128, 128, 128, 0.15);
|
||||||
|
color: var(--vscode-foreground);
|
||||||
|
border: none;
|
||||||
|
padding: 3px 7px;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
opacity: 0.9;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-selector:hover {
|
||||||
|
background-color: rgba(128, 128, 128, 0.25);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.tools-btn {
|
.tools-btn {
|
||||||
background-color: rgba(128, 128, 128, 0.15);
|
background-color: rgba(128, 128, 128, 0.15);
|
||||||
color: var(--vscode-foreground);
|
color: var(--vscode-foreground);
|
||||||
@@ -1104,12 +1131,21 @@ const html = `<!DOCTYPE html>
|
|||||||
<div class="textarea-wrapper">
|
<div class="textarea-wrapper">
|
||||||
<textarea class="input-field" id="messageInput" placeholder="Type your message to Claude Code..." rows="1"></textarea>
|
<textarea class="input-field" id="messageInput" placeholder="Type your message to Claude Code..." rows="1"></textarea>
|
||||||
<div class="input-controls">
|
<div class="input-controls">
|
||||||
|
<div class="left-controls">
|
||||||
|
<button class="model-selector" id="modelSelector" onclick="showModelSelector()" title="Select model">
|
||||||
|
<span id="selectedModel">Opus</span>
|
||||||
|
<svg width="8" height="8" viewBox="0 0 8 8" fill="currentColor">
|
||||||
|
<path d="M1 2.5l3 3 3-3"></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
<button class="tools-btn" onclick="showToolsModal()" title="Configure tools">
|
<button class="tools-btn" onclick="showToolsModal()" title="Configure tools">
|
||||||
Tools: All
|
Tools: All
|
||||||
<svg width="8" height="8" viewBox="0 0 8 8" fill="currentColor">
|
<svg width="8" height="8" viewBox="0 0 8 8" fill="currentColor">
|
||||||
<path d="M1 2.5l3 3 3-3"></path>
|
<path d="M1 2.5l3 3 3-3"></path>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="right-controls">
|
||||||
<button class="at-btn" onclick="showFilePicker()" title="Reference files">@</button>
|
<button class="at-btn" onclick="showFilePicker()" title="Reference files">@</button>
|
||||||
<button class="image-btn" id="imageBtn" onclick="selectImage()" title="Attach images">
|
<button class="image-btn" id="imageBtn" onclick="selectImage()" title="Attach images">
|
||||||
<svg
|
<svg
|
||||||
@@ -1144,6 +1180,7 @@ const html = `<!DOCTYPE html>
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="status ready" id="status">
|
<div class="status ready" id="status">
|
||||||
<div class="status-indicator"></div>
|
<div class="status-indicator"></div>
|
||||||
@@ -1279,6 +1316,45 @@ const html = `<!DOCTYPE html>
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Model selector modal -->
|
||||||
|
<div id="modelModal" class="tools-modal" style="display: none;">
|
||||||
|
<div class="tools-modal-content" style="width: 400px;">
|
||||||
|
<div class="tools-modal-header">
|
||||||
|
<span>Select Model</span>
|
||||||
|
<button class="tools-close-btn" onclick="hideModelModal()">✕</button>
|
||||||
|
</div>
|
||||||
|
<div class="tools-list">
|
||||||
|
<div class="tool-item" onclick="selectModel('opus')">
|
||||||
|
<input type="radio" name="model" id="model-opus" value="opus" checked>
|
||||||
|
<label for="model-opus" style="cursor: pointer;">
|
||||||
|
<strong>Opus</strong> - Most capable model
|
||||||
|
<div style="font-size: 11px; color: var(--vscode-descriptionForeground); margin-top: 2px;">
|
||||||
|
Best for complex tasks and highest quality output
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="tool-item" onclick="selectModel('sonnet')">
|
||||||
|
<input type="radio" name="model" id="model-sonnet" value="sonnet">
|
||||||
|
<label for="model-sonnet" style="cursor: pointer;">
|
||||||
|
<strong>Sonnet</strong> - Balanced model
|
||||||
|
<div style="font-size: 11px; color: var(--vscode-descriptionForeground); margin-top: 2px;">
|
||||||
|
Good balance of speed and capability
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="tool-item" onclick="selectModel('default')">
|
||||||
|
<input type="radio" name="model" id="model-default" value="default">
|
||||||
|
<label for="model-default" style="cursor: pointer;">
|
||||||
|
<strong>Default</strong> - Smart allocation
|
||||||
|
<div style="font-size: 11px; color: var(--vscode-descriptionForeground); margin-top: 2px;">
|
||||||
|
Uses Opus 4 for up to 50% of usage limits, then switches to Sonnet 4
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const vscode = acquireVsCodeApi();
|
const vscode = acquireVsCodeApi();
|
||||||
const messagesDiv = document.getElementById('messages');
|
const messagesDiv = document.getElementById('messages');
|
||||||
@@ -1787,6 +1863,69 @@ const html = `<!DOCTYPE html>
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Model selector functions
|
||||||
|
let currentModel = 'opus'; // Default model
|
||||||
|
|
||||||
|
function showModelSelector() {
|
||||||
|
document.getElementById('modelModal').style.display = 'flex';
|
||||||
|
// Select the current model radio button
|
||||||
|
const radioButton = document.getElementById('model-' + currentModel);
|
||||||
|
if (radioButton) {
|
||||||
|
radioButton.checked = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideModelModal() {
|
||||||
|
document.getElementById('modelModal').style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectModel(model, fromBackend = false) {
|
||||||
|
currentModel = model;
|
||||||
|
|
||||||
|
// Update the display text
|
||||||
|
const displayNames = {
|
||||||
|
'opus': 'Opus',
|
||||||
|
'sonnet': 'Sonnet',
|
||||||
|
'default': 'Default'
|
||||||
|
};
|
||||||
|
document.getElementById('selectedModel').textContent = displayNames[model] || model;
|
||||||
|
|
||||||
|
// Only send model selection to VS Code extension if not from backend
|
||||||
|
if (!fromBackend) {
|
||||||
|
vscode.postMessage({
|
||||||
|
type: 'selectModel',
|
||||||
|
model: model
|
||||||
|
});
|
||||||
|
|
||||||
|
// Save preference
|
||||||
|
localStorage.setItem('selectedModel', model);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update radio button if modal is open
|
||||||
|
const radioButton = document.getElementById('model-' + model);
|
||||||
|
if (radioButton) {
|
||||||
|
radioButton.checked = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
hideModelModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize model display without sending message
|
||||||
|
currentModel = 'opus';
|
||||||
|
const displayNames = {
|
||||||
|
'opus': 'Opus',
|
||||||
|
'sonnet': 'Sonnet',
|
||||||
|
'default': 'Default'
|
||||||
|
};
|
||||||
|
document.getElementById('selectedModel').textContent = displayNames[currentModel];
|
||||||
|
|
||||||
|
// Close model modal when clicking outside
|
||||||
|
document.getElementById('modelModal').addEventListener('click', (e) => {
|
||||||
|
if (e.target === document.getElementById('modelModal')) {
|
||||||
|
hideModelModal();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Stop button functions
|
// Stop button functions
|
||||||
function showStopButton() {
|
function showStopButton() {
|
||||||
document.getElementById('stopBtn').style.display = 'flex';
|
document.getElementById('stopBtn').style.display = 'flex';
|
||||||
@@ -2051,6 +2190,11 @@ const html = `<!DOCTYPE html>
|
|||||||
case 'clipboardText':
|
case 'clipboardText':
|
||||||
handleClipboardText(message.data);
|
handleClipboardText(message.data);
|
||||||
break;
|
break;
|
||||||
|
case 'modelSelected':
|
||||||
|
// Update the UI with the current model
|
||||||
|
currentModel = message.model;
|
||||||
|
selectModel(message.model, true);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user