mirror of
https://github.com/andrepimenta/claude-code-chat.git
synced 2025-12-08 22:39:43 +00:00
Merge pull request #4 from erwinh22/add-settings-ui
Add settings UI with WSL configuration support
This commit is contained in:
31
CHANGELOG.md
31
CHANGELOG.md
@@ -4,6 +4,35 @@ 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.
|
||||
|
||||
## [Unreleased]
|
||||
## [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
|
||||
|
||||
### Added
|
||||
- WSL (Windows Subsystem for Linux) configuration support
|
||||
- New setting: `claudeCodeChat.wsl.enabled` to enable WSL integration
|
||||
- New setting: `claudeCodeChat.wsl.distro` to specify WSL distribution
|
||||
- New setting: `claudeCodeChat.wsl.nodePath` to configure Node.js path in WSL
|
||||
- New setting: `claudeCodeChat.wsl.claudePath` to configure Claude path in WSL
|
||||
- Automatic detection of execution environment (native vs WSL)
|
||||
- WSL support for Claude login terminal command
|
||||
|
||||
### Changed
|
||||
- Claude execution now supports both native and WSL environments based on configuration
|
||||
- Terminal login command adapts to WSL settings when enabled
|
||||
|
||||
## [0.0.7] - Previous Release
|
||||
|
||||
- Initial release
|
||||
30
README.md
30
README.md
@@ -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
|
||||
📁 **Smart File Context** - Reference any file with simple @ mentions
|
||||
🛑 **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
|
||||
- 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**
|
||||
@@ -160,6 +169,27 @@ If you want to revert these changes, just click "Restore Checkpoint" to go back
|
||||
| `Enter` | Send message |
|
||||
| `@` | Open file picker |
|
||||
|
||||
### WSL Configuration (Windows Users)
|
||||
If you're using Claude Code through WSL (Windows Subsystem for Linux), you can configure the extension to use WSL:
|
||||
|
||||
1. Open VS Code Settings (`Ctrl+,` or `Cmd+,`)
|
||||
2. Search for "Claude Code Chat"
|
||||
3. Configure these settings:
|
||||
- **Claude Code Chat: WSL Enabled** - Enable WSL integration
|
||||
- **Claude Code Chat: WSL Distro** - Your WSL distribution name (e.g., `Ubuntu`, `Debian`)
|
||||
- **Claude Code Chat: WSL Node Path** - Path to Node.js in WSL (default: `/usr/bin/node`)
|
||||
- **Claude Code Chat: WSL Claude Path** - Path to Claude in WSL (default: `/usr/local/bin/claude`)
|
||||
|
||||
Example configuration in `settings.json`:
|
||||
```json
|
||||
{
|
||||
"claudeCodeChat.wsl.enabled": true,
|
||||
"claudeCodeChat.wsl.distro": "Ubuntu",
|
||||
"claudeCodeChat.wsl.nodePath": "/usr/bin/node",
|
||||
"claudeCodeChat.wsl.claudePath": "/usr/local/bin/claude"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 **Pro Tips & Tricks**
|
||||
|
||||
55
TEST_SETTINGS.md
Normal file
55
TEST_SETTINGS.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# Settings Interface Test Plan
|
||||
|
||||
## Overview
|
||||
Added a settings interface to the Claude Code Chat webview that allows users to configure WSL settings.
|
||||
|
||||
## Changes Made
|
||||
|
||||
### 1. UI Changes (ui.ts)
|
||||
- Added a settings button (⚙️) in the header that's always visible
|
||||
- Created a settings modal with WSL configuration options:
|
||||
- Enable WSL Integration checkbox
|
||||
- WSL Distribution input field
|
||||
- Node.js Path in WSL input field
|
||||
- Claude Path in WSL input field
|
||||
- Added JavaScript functions:
|
||||
- `toggleSettings()` - Shows/hides the settings modal
|
||||
- `hideSettingsModal()` - Hides the settings modal
|
||||
- `updateSettings()` - Sends settings changes to VS Code
|
||||
- Event listeners for modal interaction
|
||||
|
||||
### 2. Extension Changes (extension.ts)
|
||||
- Added message handlers:
|
||||
- `getSettings` - Retrieves current settings from VS Code configuration
|
||||
- `updateSettings` - Updates VS Code configuration with new settings
|
||||
- Added methods:
|
||||
- `_sendCurrentSettings()` - Sends current settings to webview
|
||||
- `_updateSettings()` - Updates VS Code configuration
|
||||
|
||||
### 3. Configuration (package.json)
|
||||
- Already has WSL configuration properties defined:
|
||||
- `claudeCodeChat.wsl.enabled`
|
||||
- `claudeCodeChat.wsl.distro`
|
||||
- `claudeCodeChat.wsl.nodePath`
|
||||
- `claudeCodeChat.wsl.claudePath`
|
||||
|
||||
## Testing Steps
|
||||
|
||||
1. Open VS Code with the extension
|
||||
2. Open Claude Code Chat (Ctrl+Shift+C)
|
||||
3. Click the settings button (⚙️) in the header
|
||||
4. Verify the settings modal appears
|
||||
5. Check that current WSL settings are loaded
|
||||
6. Toggle "Enable WSL Integration" and verify:
|
||||
- WSL options show/hide accordingly
|
||||
- Settings are saved when changed
|
||||
7. Modify WSL settings and verify they persist
|
||||
8. Close and reopen the settings to confirm values are saved
|
||||
|
||||
## Features
|
||||
|
||||
- Settings button is always visible in the header
|
||||
- Modal design matches the existing tools modal
|
||||
- Real-time show/hide of WSL options based on enabled state
|
||||
- Settings persist across sessions
|
||||
- Success/error notifications when saving settings
|
||||
3150
package-lock.json
generated
3150
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
39
package.json
39
package.json
@@ -2,7 +2,7 @@
|
||||
"name": "claude-code-chat",
|
||||
"displayName": "Claude Code Chat",
|
||||
"description": "Beautiful Claude Code Chat Interface for VS Code",
|
||||
"version": "0.0.7",
|
||||
"version": "0.0.9",
|
||||
"publisher": "AndrePimenta",
|
||||
"author": "Andre Pimenta",
|
||||
"repository": {
|
||||
@@ -47,6 +47,9 @@
|
||||
],
|
||||
"icon": "icon.png",
|
||||
"main": "./out/extension.js",
|
||||
"activationEvents": [
|
||||
"onStartupFinished"
|
||||
],
|
||||
"contributes": {
|
||||
"commands": [
|
||||
{
|
||||
@@ -142,6 +145,31 @@
|
||||
"icon": "icon.png"
|
||||
}
|
||||
]
|
||||
},
|
||||
"configuration": {
|
||||
"title": "Claude Code Chat",
|
||||
"properties": {
|
||||
"claudeCodeChat.wsl.enabled": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Enable WSL integration for running Claude"
|
||||
},
|
||||
"claudeCodeChat.wsl.distro": {
|
||||
"type": "string",
|
||||
"default": "Ubuntu",
|
||||
"description": "WSL distribution name (e.g., Ubuntu, Debian)"
|
||||
},
|
||||
"claudeCodeChat.wsl.nodePath": {
|
||||
"type": "string",
|
||||
"default": "/usr/bin/node",
|
||||
"description": "Path to Node.js in the WSL distribution"
|
||||
},
|
||||
"claudeCodeChat.wsl.claudePath": {
|
||||
"type": "string",
|
||||
"default": "/usr/local/bin/claude",
|
||||
"description": "Path to Claude executable in the WSL distribution"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
@@ -153,14 +181,15 @@
|
||||
"test": "vscode-test"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/vscode": "^1.95.0",
|
||||
"@types/mocha": "^10.0.10",
|
||||
"@types/node": "20.x",
|
||||
"@types/vscode": "^1.95.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.31.1",
|
||||
"@typescript-eslint/parser": "^8.31.1",
|
||||
"eslint": "^9.25.1",
|
||||
"typescript": "^5.8.3",
|
||||
"@vscode/test-cli": "^0.0.10",
|
||||
"@vscode/test-electron": "^2.5.2"
|
||||
"@vscode/test-electron": "^2.5.2",
|
||||
"@vscode/vsce": "^3.5.0",
|
||||
"eslint": "^9.25.1",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
152
src/extension.ts
152
src/extension.ts
@@ -7,9 +7,11 @@ import html from './ui';
|
||||
const exec = util.promisify(cp.exec);
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
console.log('Claude Code Chat extension is being activated!');
|
||||
const provider = new ClaudeChatProvider(context.extensionUri, context);
|
||||
|
||||
const disposable = vscode.commands.registerCommand('claude-code-chat.openChat', () => {
|
||||
console.log('Claude Code Chat command executed!');
|
||||
provider.show();
|
||||
});
|
||||
|
||||
@@ -33,6 +35,7 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
statusBarItem.show();
|
||||
|
||||
context.subscriptions.push(disposable, loadConversationDisposable, statusBarItem);
|
||||
console.log('Claude Code Chat extension activation completed successfully!');
|
||||
}
|
||||
|
||||
export function deactivate() { }
|
||||
@@ -125,6 +128,7 @@ class ClaudeChatProvider {
|
||||
}> = [];
|
||||
private _treeProvider: ClaudeChatViewProvider | undefined;
|
||||
private _currentClaudeProcess: cp.ChildProcess | undefined;
|
||||
private _selectedModel: string = 'opus'; // Default model
|
||||
|
||||
constructor(
|
||||
private readonly _extensionUri: vscode.Uri,
|
||||
@@ -138,6 +142,9 @@ class ClaudeChatProvider {
|
||||
// Load conversation index from workspace state
|
||||
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
|
||||
const latestConversation = this._getLatestConversation();
|
||||
this._currentSessionId = latestConversation?.sessionId;
|
||||
@@ -197,6 +204,18 @@ class ClaudeChatProvider {
|
||||
case 'stopRequest':
|
||||
this._stopClaudeProcess();
|
||||
return;
|
||||
case 'getSettings':
|
||||
this._sendCurrentSettings();
|
||||
return;
|
||||
case 'updateSettings':
|
||||
this._updateSettings(message.settings);
|
||||
return;
|
||||
case 'getClipboardText':
|
||||
this._getClipboardText();
|
||||
return;
|
||||
case 'selectModel':
|
||||
this._setSelectedModel(message.model);
|
||||
return;
|
||||
}
|
||||
},
|
||||
null,
|
||||
@@ -228,6 +247,12 @@ class ClaudeChatProvider {
|
||||
type: 'ready',
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -252,7 +277,7 @@ class ClaudeChatProvider {
|
||||
await this._createBackupCommit(message);
|
||||
}
|
||||
catch (e) {
|
||||
console.log("error", e)
|
||||
console.log("error", e);
|
||||
}
|
||||
|
||||
// Show loading indicator
|
||||
@@ -271,6 +296,12 @@ class ClaudeChatProvider {
|
||||
'--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
|
||||
if (this._currentSessionId) {
|
||||
args.push('--resume', this._currentSessionId);
|
||||
@@ -281,7 +312,19 @@ class ClaudeChatProvider {
|
||||
|
||||
console.log('Claude command args:', args);
|
||||
|
||||
const claudeProcess = cp.spawn('claude', args, {
|
||||
// Get configuration
|
||||
const config = vscode.workspace.getConfiguration('claudeCodeChat');
|
||||
const wslEnabled = config.get<boolean>('wsl.enabled', false);
|
||||
const wslDistro = config.get<string>('wsl.distro', 'Ubuntu');
|
||||
const nodePath = config.get<string>('wsl.nodePath', '/usr/bin/node');
|
||||
const claudePath = config.get<string>('wsl.claudePath', '/usr/local/bin/claude');
|
||||
|
||||
let claudeProcess: cp.ChildProcess;
|
||||
|
||||
if (wslEnabled) {
|
||||
// Use WSL
|
||||
console.log('Using WSL configuration:', { wslDistro, nodePath, claudePath });
|
||||
claudeProcess = cp.spawn('wsl', ['-d', wslDistro, nodePath, '--no-warnings', '--enable-source-maps', claudePath, ...args], {
|
||||
cwd: cwd,
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
env: {
|
||||
@@ -290,6 +333,19 @@ class ClaudeChatProvider {
|
||||
NO_COLOR: '1'
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Use native claude command
|
||||
console.log('Using native Claude command');
|
||||
claudeProcess = cp.spawn('claude', args, {
|
||||
cwd: cwd,
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
env: {
|
||||
...process.env,
|
||||
FORCE_COLOR: '0',
|
||||
NO_COLOR: '1'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Store process reference for potential termination
|
||||
this._currentClaudeProcess = claudeProcess;
|
||||
@@ -582,9 +638,19 @@ class ClaudeChatProvider {
|
||||
type: 'loginRequired'
|
||||
});
|
||||
|
||||
// Get configuration to check if WSL is enabled
|
||||
const config = vscode.workspace.getConfiguration('claudeCodeChat');
|
||||
const wslEnabled = config.get<boolean>('wsl.enabled', false);
|
||||
const wslDistro = config.get<string>('wsl.distro', 'Ubuntu');
|
||||
const claudePath = config.get<string>('wsl.claudePath', '/usr/local/bin/claude');
|
||||
|
||||
// Open terminal and run claude login
|
||||
const terminal = vscode.window.createTerminal('Claude Login');
|
||||
if (wslEnabled) {
|
||||
terminal.sendText(`wsl -d ${wslDistro} ${claudePath}`);
|
||||
} else {
|
||||
terminal.sendText('claude');
|
||||
}
|
||||
terminal.show();
|
||||
|
||||
// Show info message
|
||||
@@ -597,7 +663,7 @@ class ClaudeChatProvider {
|
||||
private async _initializeBackupRepo(): Promise<void> {
|
||||
try {
|
||||
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
|
||||
if (!workspaceFolder) return;
|
||||
if (!workspaceFolder) {return;}
|
||||
|
||||
const storagePath = this._context.storageUri?.fsPath;
|
||||
if (!storagePath) {
|
||||
@@ -630,7 +696,7 @@ class ClaudeChatProvider {
|
||||
private async _createBackupCommit(userMessage: string): Promise<void> {
|
||||
try {
|
||||
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
|
||||
if (!workspaceFolder || !this._backupRepoPath) return;
|
||||
if (!workspaceFolder || !this._backupRepoPath) {return;}
|
||||
|
||||
const workspacePath = workspaceFolder.uri.fsPath;
|
||||
const now = new Date();
|
||||
@@ -739,10 +805,10 @@ class ClaudeChatProvider {
|
||||
private async _initializeConversations(): Promise<void> {
|
||||
try {
|
||||
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
|
||||
if (!workspaceFolder) return;
|
||||
if (!workspaceFolder) {return;}
|
||||
|
||||
const storagePath = this._context.storageUri?.fsPath;
|
||||
if (!storagePath) return;
|
||||
if (!storagePath) {return;}
|
||||
|
||||
this._conversationsPath = path.join(storagePath, 'conversations');
|
||||
|
||||
@@ -765,7 +831,7 @@ class ClaudeChatProvider {
|
||||
}
|
||||
|
||||
if (message.type === 'sessionInfo') {
|
||||
message.data.sessionId
|
||||
message.data.sessionId;
|
||||
}
|
||||
|
||||
// Send to UI
|
||||
@@ -783,7 +849,7 @@ class ClaudeChatProvider {
|
||||
}
|
||||
|
||||
private async _saveCurrentConversation(): Promise<void> {
|
||||
if (!this._conversationsPath || this._currentConversation.length === 0) return;
|
||||
if (!this._conversationsPath || this._currentConversation.length === 0) {return;}
|
||||
|
||||
try {
|
||||
// Create filename from first user message and timestamp
|
||||
@@ -1009,12 +1075,12 @@ class ClaudeChatProvider {
|
||||
}
|
||||
|
||||
private async _loadConversationHistory(filename: string): Promise<void> {
|
||||
console.log("_loadConversationHistory")
|
||||
if (!this._conversationsPath) return;
|
||||
console.log("_loadConversationHistory");
|
||||
if (!this._conversationsPath) {return;}
|
||||
|
||||
try {
|
||||
const filePath = path.join(this._conversationsPath, filename);
|
||||
console.log("filePath", filePath)
|
||||
console.log("filePath", filePath);
|
||||
|
||||
let conversationData;
|
||||
try {
|
||||
@@ -1025,7 +1091,7 @@ class ClaudeChatProvider {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("conversationData", conversationData)
|
||||
console.log("conversationData", conversationData);
|
||||
// Load conversation into current state
|
||||
this._currentConversation = conversationData.messages || [];
|
||||
this._conversationStartTime = conversationData.startTime;
|
||||
@@ -1069,7 +1135,67 @@ class ClaudeChatProvider {
|
||||
}
|
||||
|
||||
private _getHtmlForWebview(): string {
|
||||
return html
|
||||
return html;
|
||||
}
|
||||
|
||||
private _sendCurrentSettings(): void {
|
||||
const config = vscode.workspace.getConfiguration('claudeCodeChat');
|
||||
const settings = {
|
||||
'wsl.enabled': config.get<boolean>('wsl.enabled', false),
|
||||
'wsl.distro': config.get<string>('wsl.distro', 'Ubuntu'),
|
||||
'wsl.nodePath': config.get<string>('wsl.nodePath', '/usr/bin/node'),
|
||||
'wsl.claudePath': config.get<string>('wsl.claudePath', '/usr/local/bin/claude')
|
||||
};
|
||||
|
||||
this._panel?.webview.postMessage({
|
||||
type: 'settingsData',
|
||||
data: settings
|
||||
});
|
||||
}
|
||||
|
||||
private async _updateSettings(settings: { [key: string]: any }): Promise<void> {
|
||||
const config = vscode.workspace.getConfiguration('claudeCodeChat');
|
||||
|
||||
try {
|
||||
for (const [key, value] of Object.entries(settings)) {
|
||||
await config.update(key, value, vscode.ConfigurationTarget.Global);
|
||||
}
|
||||
|
||||
vscode.window.showInformationMessage('Settings updated successfully');
|
||||
} catch (error) {
|
||||
console.error('Failed to update settings:', error);
|
||||
vscode.window.showErrorMessage('Failed to update settings');
|
||||
}
|
||||
}
|
||||
|
||||
private async _getClipboardText(): Promise<void> {
|
||||
try {
|
||||
const text = await vscode.env.clipboard.readText();
|
||||
this._panel?.webview.postMessage({
|
||||
type: 'clipboardText',
|
||||
data: text
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to read clipboard:', error);
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
|
||||
369
src/ui.ts
369
src/ui.ts
@@ -484,13 +484,40 @@ const html = `<!DOCTYPE html>
|
||||
.input-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: end;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
padding: 2px 4px;
|
||||
border-top: 1px solid var(--vscode-panel-border);
|
||||
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 {
|
||||
background-color: rgba(128, 128, 128, 0.15);
|
||||
color: var(--vscode-foreground);
|
||||
@@ -727,7 +754,7 @@ const html = `<!DOCTYPE html>
|
||||
background-color: var(--vscode-editor-background);
|
||||
border: 1px solid var(--vscode-panel-border);
|
||||
border-radius: 4px;
|
||||
width: 450px;
|
||||
width: 500px;
|
||||
max-height: 600px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -797,6 +824,17 @@ const html = `<!DOCTYPE html>
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.settings-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.settings-group h3 {
|
||||
margin: 0 0 12px 0;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--vscode-foreground);
|
||||
}
|
||||
|
||||
.status {
|
||||
padding: 8px 12px;
|
||||
background: linear-gradient(135deg, #1e1e1e 0%, #2d2d2d 100%);
|
||||
@@ -1071,6 +1109,7 @@ const html = `<!DOCTYPE html>
|
||||
</div>
|
||||
<div style="display: flex; gap: 8px; align-items: center;">
|
||||
<div id="sessionStatus" class="session-status" style="display: none;">No session</div>
|
||||
<button class="btn outlined" id="settingsBtn" onclick="toggleSettings()" title="Settings">⚙️</button>
|
||||
<button class="btn outlined" id="historyBtn" onclick="toggleConversationHistory()" style="display: none;">📚 History</button>
|
||||
<button class="btn primary" id="newSessionBtn" onclick="newSession()" style="display: none;">New Chat</button>
|
||||
</div>
|
||||
@@ -1092,12 +1131,21 @@ const html = `<!DOCTYPE html>
|
||||
<div class="textarea-wrapper">
|
||||
<textarea class="input-field" id="messageInput" placeholder="Type your message to Claude Code..." rows="1"></textarea>
|
||||
<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">
|
||||
Tools: All
|
||||
<svg width="8" height="8" viewBox="0 0 8 8" fill="currentColor">
|
||||
<path d="M1 2.5l3 3 3-3"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="right-controls">
|
||||
<button class="at-btn" onclick="showFilePicker()" title="Reference files">@</button>
|
||||
<button class="image-btn" id="imageBtn" onclick="selectImage()" title="Attach images">
|
||||
<svg
|
||||
@@ -1132,6 +1180,7 @@ const html = `<!DOCTYPE html>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="status ready" id="status">
|
||||
<div class="status-indicator"></div>
|
||||
@@ -1216,6 +1265,96 @@ const html = `<!DOCTYPE html>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Settings modal -->
|
||||
<div id="settingsModal" class="tools-modal" style="display: none;">
|
||||
<div class="tools-modal-content">
|
||||
<div class="tools-modal-header">
|
||||
<span>Claude Code Chat Settings</span>
|
||||
<button class="tools-close-btn" onclick="hideSettingsModal()">✕</button>
|
||||
</div>
|
||||
<div class="tools-list">
|
||||
<h3 style="margin-top: 0; margin-bottom: 16px; font-size: 14px; font-weight: 600;">WSL Configuration</h3>
|
||||
|
||||
<div class="settings-group">
|
||||
<div class="tool-item">
|
||||
<input type="checkbox" id="wsl-enabled" onchange="updateSettings()">
|
||||
<label for="wsl-enabled">Enable WSL Integration</label>
|
||||
</div>
|
||||
|
||||
<div id="wslOptions" style="margin-left: 24px; margin-top: 12px;">
|
||||
<div style="margin-bottom: 12px;">
|
||||
<label style="display: block; margin-bottom: 4px; font-size: 12px; color: var(--vscode-descriptionForeground);">WSL Distribution</label>
|
||||
<input type="text" id="wsl-distro" class="file-search-input" style="width: 100%;" placeholder="Ubuntu" onchange="updateSettings()">
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 12px;">
|
||||
<label style="display: block; margin-bottom: 4px; font-size: 12px; color: var(--vscode-descriptionForeground);">Node.js Path in WSL</label>
|
||||
<input type="text" id="wsl-node-path" class="file-search-input" style="width: 100%;" placeholder="/usr/bin/node" onchange="updateSettings()">
|
||||
<p style="font-size: 11px; color: var(--vscode-descriptionForeground); margin: 4px 0 0 0;">
|
||||
Find your node installation path in WSL by running: <code style="background: var(--vscode-textCodeBlock-background); padding: 2px 4px; border-radius: 3px;">which node</code>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 12px;">
|
||||
<label style="display: block; margin-bottom: 4px; font-size: 12px; color: var(--vscode-descriptionForeground);">Claude Path in WSL</label>
|
||||
<input type="text" id="wsl-claude-path" class="file-search-input" style="width: 100%;" placeholder="/usr/local/bin/claude" onchange="updateSettings()">
|
||||
<p style="font-size: 11px; color: var(--vscode-descriptionForeground); margin: 4px 0 0 0;">
|
||||
Find your claude installation path in WSL by running: <code style="background: var(--vscode-textCodeBlock-background); padding: 2px 4px; border-radius: 3px;">which claude</code>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 20px; padding-top: 16px; border-top: 1px solid var(--vscode-panel-border);">
|
||||
<p style="font-size: 11px; color: var(--vscode-descriptionForeground); margin: 0;">
|
||||
WSL integration allows you to run Claude Code from within Windows Subsystem for Linux.
|
||||
This is useful if you have Claude installed in WSL instead of Windows.
|
||||
</p>
|
||||
</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>
|
||||
const vscode = acquireVsCodeApi();
|
||||
const messagesDiv = document.getElementById('messages');
|
||||
@@ -1602,7 +1741,77 @@ const html = `<!DOCTYPE html>
|
||||
} else if (e.key === 'Escape' && filePickerModal.style.display === 'flex') {
|
||||
e.preventDefault();
|
||||
hideFilePicker();
|
||||
} else if (e.key === 'v' && (e.ctrlKey || e.metaKey)) {
|
||||
// Handle Ctrl+V/Cmd+V explicitly in case paste event doesn't fire
|
||||
// Don't prevent default - let browser handle it first
|
||||
setTimeout(() => {
|
||||
// If value hasn't changed, manually trigger paste
|
||||
const currentValue = messageInput.value;
|
||||
setTimeout(() => {
|
||||
if (messageInput.value === currentValue) {
|
||||
// Value didn't change, request clipboard from VS Code
|
||||
vscode.postMessage({
|
||||
type: 'getClipboardText'
|
||||
});
|
||||
}
|
||||
}, 50);
|
||||
}, 0);
|
||||
}
|
||||
});
|
||||
|
||||
// Add explicit paste event handler for better clipboard support in VSCode webviews
|
||||
messageInput.addEventListener('paste', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
try {
|
||||
// Try to get clipboard data from the event first
|
||||
const clipboardData = e.clipboardData;
|
||||
let text = '';
|
||||
|
||||
if (clipboardData) {
|
||||
text = clipboardData.getData('text/plain');
|
||||
}
|
||||
|
||||
// If no text from event, try navigator.clipboard API
|
||||
if (!text && navigator.clipboard && navigator.clipboard.readText) {
|
||||
try {
|
||||
text = await navigator.clipboard.readText();
|
||||
} catch (err) {
|
||||
console.log('Clipboard API failed:', err);
|
||||
}
|
||||
}
|
||||
|
||||
// If still no text, request from VS Code extension
|
||||
if (!text) {
|
||||
vscode.postMessage({
|
||||
type: 'getClipboardText'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Insert text at cursor position
|
||||
const start = messageInput.selectionStart;
|
||||
const end = messageInput.selectionEnd;
|
||||
const currentValue = messageInput.value;
|
||||
|
||||
const newValue = currentValue.substring(0, start) + text + currentValue.substring(end);
|
||||
messageInput.value = newValue;
|
||||
|
||||
// Set cursor position after pasted text
|
||||
const newCursorPos = start + text.length;
|
||||
messageInput.setSelectionRange(newCursorPos, newCursorPos);
|
||||
|
||||
// Trigger input event to adjust height
|
||||
messageInput.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
} catch (error) {
|
||||
console.error('Paste error:', error);
|
||||
}
|
||||
});
|
||||
|
||||
// Handle context menu paste
|
||||
messageInput.addEventListener('contextmenu', (e) => {
|
||||
// Don't prevent default - allow context menu to show
|
||||
// but ensure paste will work when selected
|
||||
});
|
||||
|
||||
// Initialize textarea height
|
||||
@@ -1654,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
|
||||
function showStopButton() {
|
||||
document.getElementById('stopBtn').style.display = 'flex';
|
||||
@@ -1915,6 +2187,14 @@ const html = `<!DOCTYPE html>
|
||||
case 'conversationList':
|
||||
displayConversationList(message.data);
|
||||
break;
|
||||
case 'clipboardText':
|
||||
handleClipboardText(message.data);
|
||||
break;
|
||||
case 'modelSelected':
|
||||
// Update the UI with the current model
|
||||
currentModel = message.model;
|
||||
selectModel(message.model, true);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2281,6 +2561,91 @@ const html = `<!DOCTYPE html>
|
||||
listDiv.appendChild(item);
|
||||
});
|
||||
}
|
||||
|
||||
function handleClipboardText(text) {
|
||||
if (!text) return;
|
||||
|
||||
// Insert text at cursor position
|
||||
const start = messageInput.selectionStart;
|
||||
const end = messageInput.selectionEnd;
|
||||
const currentValue = messageInput.value;
|
||||
|
||||
const newValue = currentValue.substring(0, start) + text + currentValue.substring(end);
|
||||
messageInput.value = newValue;
|
||||
|
||||
// Set cursor position after pasted text
|
||||
const newCursorPos = start + text.length;
|
||||
messageInput.setSelectionRange(newCursorPos, newCursorPos);
|
||||
|
||||
// Trigger input event to adjust height
|
||||
messageInput.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
}
|
||||
|
||||
// Settings functions
|
||||
|
||||
function toggleSettings() {
|
||||
const settingsModal = document.getElementById('settingsModal');
|
||||
if (settingsModal.style.display === 'none') {
|
||||
// Request current settings from VS Code
|
||||
vscode.postMessage({
|
||||
type: 'getSettings'
|
||||
});
|
||||
settingsModal.style.display = 'flex';
|
||||
} else {
|
||||
hideSettingsModal();
|
||||
}
|
||||
}
|
||||
|
||||
function hideSettingsModal() {
|
||||
document.getElementById('settingsModal').style.display = 'none';
|
||||
}
|
||||
|
||||
function updateSettings() {
|
||||
const wslEnabled = document.getElementById('wsl-enabled').checked;
|
||||
const wslDistro = document.getElementById('wsl-distro').value;
|
||||
const wslNodePath = document.getElementById('wsl-node-path').value;
|
||||
const wslClaudePath = document.getElementById('wsl-claude-path').value;
|
||||
|
||||
// Update WSL options visibility
|
||||
document.getElementById('wslOptions').style.display = wslEnabled ? 'block' : 'none';
|
||||
|
||||
// Send settings to VS Code immediately
|
||||
vscode.postMessage({
|
||||
type: 'updateSettings',
|
||||
settings: {
|
||||
'wsl.enabled': wslEnabled,
|
||||
'wsl.distro': wslDistro || 'Ubuntu',
|
||||
'wsl.nodePath': wslNodePath || '/usr/bin/node',
|
||||
'wsl.claudePath': wslClaudePath || '/usr/local/bin/claude'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Close settings modal when clicking outside
|
||||
document.getElementById('settingsModal').addEventListener('click', (e) => {
|
||||
if (e.target === document.getElementById('settingsModal')) {
|
||||
hideSettingsModal();
|
||||
}
|
||||
});
|
||||
|
||||
// Add settings message handler to window message event
|
||||
const originalMessageHandler = window.onmessage;
|
||||
window.addEventListener('message', event => {
|
||||
const message = event.data;
|
||||
|
||||
if (message.type === 'settingsData') {
|
||||
// Update UI with current settings
|
||||
document.getElementById('wsl-enabled').checked = message.data['wsl.enabled'] || false;
|
||||
document.getElementById('wsl-distro').value = message.data['wsl.distro'] || 'Ubuntu';
|
||||
document.getElementById('wsl-node-path').value = message.data['wsl.nodePath'] || '/usr/bin/node';
|
||||
document.getElementById('wsl-claude-path').value = message.data['wsl.claudePath'] || '/usr/local/bin/claude';
|
||||
|
||||
// Show/hide WSL options
|
||||
document.getElementById('wslOptions').style.display = message.data['wsl.enabled'] ? 'block' : 'none';
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
Reference in New Issue
Block a user