mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-03 05:59:43 +02:00
feat: unified Run button with mode selector dropdown
Replace separate "Run Claude" and "Run OC" buttons with a single split button: [Run ▾] where the chevron opens a dropdown to switch between Claude Code and OpenCode modes. - Run button label shows "Run" (Claude) or "Run OC" (OpenCode) - Button color reflects selected mode (blue=Claude, green=OpenCode) - Mode persisted in localStorage across sessions - Ctrl+Enter uses the selected mode - Welcome screen simplified to single "Run" button - Removed standalone btn-claude, btn-opencode, welcome-btn-opencode CSS - Updated welcome tagline: "Manage AI in persistent tmux sessions" Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+63
-1
@@ -2878,6 +2878,7 @@ class ClaudemanApp {
|
||||
// Share a single settings fetch between both consumers
|
||||
const settingsPromise = fetch('/api/settings').then(r => r.ok ? r.json() : null).catch(() => null);
|
||||
this.loadQuickStartCases(null, settingsPromise);
|
||||
this._initRunMode();
|
||||
this.setupEventListeners();
|
||||
// Mobile: ensure button taps register even when keyboard is visible.
|
||||
// On mobile, tapping a button while the soft keyboard is up causes the
|
||||
@@ -9286,10 +9287,71 @@ class ClaudemanApp {
|
||||
}
|
||||
|
||||
async quickStart() {
|
||||
// Alias for backward compatibility
|
||||
return this.run();
|
||||
}
|
||||
|
||||
/** Run using the selected mode (Claude Code or OpenCode) */
|
||||
async run() {
|
||||
const mode = this._runMode || 'claude';
|
||||
if (mode === 'opencode') {
|
||||
return this.runOpenCode();
|
||||
}
|
||||
return this.runClaude();
|
||||
}
|
||||
|
||||
/** Get/set the run mode, persisted in localStorage */
|
||||
get runMode() { return this._runMode || 'claude'; }
|
||||
|
||||
setRunMode(mode) {
|
||||
this._runMode = mode;
|
||||
try { localStorage.setItem('claudeman_runMode', mode); } catch {}
|
||||
this._applyRunMode();
|
||||
// Close menu
|
||||
document.getElementById('runModeMenu')?.classList.remove('active');
|
||||
}
|
||||
|
||||
toggleRunModeMenu(e) {
|
||||
e?.stopPropagation();
|
||||
const menu = document.getElementById('runModeMenu');
|
||||
if (!menu) return;
|
||||
menu.classList.toggle('active');
|
||||
// Update selected state
|
||||
menu.querySelectorAll('.run-mode-option').forEach(btn => {
|
||||
btn.classList.toggle('selected', btn.dataset.mode === this.runMode);
|
||||
});
|
||||
// Close on click outside
|
||||
if (menu.classList.contains('active')) {
|
||||
const close = (ev) => {
|
||||
if (!menu.contains(ev.target)) {
|
||||
menu.classList.remove('active');
|
||||
document.removeEventListener('click', close);
|
||||
}
|
||||
};
|
||||
setTimeout(() => document.addEventListener('click', close), 0);
|
||||
}
|
||||
}
|
||||
|
||||
_applyRunMode() {
|
||||
const mode = this.runMode;
|
||||
const runBtn = document.getElementById('runBtn');
|
||||
const gearBtn = runBtn?.nextElementSibling;
|
||||
const label = document.getElementById('runBtnLabel');
|
||||
if (runBtn) {
|
||||
runBtn.className = `btn-toolbar btn-run mode-${mode}`;
|
||||
}
|
||||
if (gearBtn) {
|
||||
gearBtn.className = `btn-toolbar btn-run-gear mode-${mode}`;
|
||||
}
|
||||
if (label) {
|
||||
label.textContent = mode === 'opencode' ? 'Run OC' : 'Run';
|
||||
}
|
||||
}
|
||||
|
||||
_initRunMode() {
|
||||
try { this._runMode = localStorage.getItem('claudeman_runMode') || 'claude'; } catch { this._runMode = 'claude'; }
|
||||
this._applyRunMode();
|
||||
}
|
||||
|
||||
// Tab count stepper functions
|
||||
incrementTabCount() {
|
||||
const input = document.getElementById('tabCount');
|
||||
|
||||
+21
-15
@@ -232,15 +232,11 @@
|
||||
<div class="welcome-overlay" id="welcomeOverlay">
|
||||
<div class="welcome-content">
|
||||
<h1 class="welcome-title">Claudeman</h1>
|
||||
<p class="welcome-desc">Manage Claude Code in persistent tmux sessions.</p>
|
||||
<p class="welcome-desc">Manage AI in persistent tmux sessions.</p>
|
||||
<div class="welcome-actions">
|
||||
<button class="welcome-btn welcome-btn-primary" onclick="app.runClaude()">
|
||||
<button class="welcome-btn welcome-btn-primary" onclick="app.run()">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="5 3 19 12 5 21 5 3"/></svg>
|
||||
Run Claude
|
||||
</button>
|
||||
<button class="welcome-btn welcome-btn-opencode" onclick="app.runOpenCode()">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="3"/><path d="M8 12h8M12 8v8"/></svg>
|
||||
Run OpenCode
|
||||
Run
|
||||
</button>
|
||||
<button class="welcome-btn welcome-btn-ralph" onclick="app.showRalphWizard()">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg>
|
||||
@@ -299,12 +295,25 @@
|
||||
<!-- Bottom Toolbar -->
|
||||
<footer class="toolbar">
|
||||
<div class="toolbar-left">
|
||||
<!-- Run Claude -->
|
||||
<!-- Run AI -->
|
||||
<div class="toolbar-group">
|
||||
<button class="btn-toolbar btn-claude" onclick="app.runClaude()" title="Run Claude (Ctrl+Enter)">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polygon points="5 3 19 12 5 21 5 3"/></svg>
|
||||
Run Claude
|
||||
</button>
|
||||
<div class="run-btn-group">
|
||||
<button class="btn-toolbar btn-run" id="runBtn" onclick="app.run()" title="Run (Ctrl+Enter)">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polygon points="5 3 19 12 5 21 5 3"/></svg>
|
||||
<span id="runBtnLabel">Run</span>
|
||||
</button>
|
||||
<button class="btn-toolbar btn-run-gear" onclick="app.toggleRunModeMenu(event)" title="Select AI backend">
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M6 9l6 6 6-6"/></svg>
|
||||
</button>
|
||||
<div class="run-mode-menu" id="runModeMenu">
|
||||
<button class="run-mode-option" data-mode="claude" onclick="app.setRunMode('claude')">
|
||||
<span class="run-mode-dot claude"></span>Claude Code
|
||||
</button>
|
||||
<button class="run-mode-option" data-mode="opencode" onclick="app.setRunMode('opencode')">
|
||||
<span class="run-mode-dot opencode"></span>OpenCode
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-count-group" title="Instance count">
|
||||
<button class="tab-count-btn" onclick="app.decrementTabCount()">−</button>
|
||||
<input type="number" id="tabCount" class="tab-count-input" value="1" min="1" max="20" readonly>
|
||||
@@ -321,9 +330,6 @@
|
||||
<input type="number" id="shellCount" class="tab-count-input" value="1" min="1" max="20" readonly>
|
||||
<button class="tab-count-btn" onclick="app.incrementShellCount()">+</button>
|
||||
</div>
|
||||
<button class="btn-toolbar btn-opencode" onclick="app.runOpenCode()" title="Run OpenCode (multi-model)">
|
||||
Run OC
|
||||
</button>
|
||||
<div class="case-select-group">
|
||||
<select id="quickStartCase" class="toolbar-select" title="Select case">
|
||||
<option value="testcase">testcase</option>
|
||||
|
||||
+94
-39
@@ -1732,19 +1732,6 @@ body {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.welcome-btn-opencode {
|
||||
background: linear-gradient(135deg, #0a2e2a 0%, #0d4a40 100%);
|
||||
border-color: #10b981;
|
||||
color: #6ee7b7;
|
||||
}
|
||||
|
||||
.welcome-btn-opencode:hover {
|
||||
background: linear-gradient(135deg, #0d4a40 0%, #116a58 100%);
|
||||
border-color: #34d399;
|
||||
box-shadow: 0 0 12px rgba(16, 185, 129, 0.4);
|
||||
color: #a7f3d0;
|
||||
}
|
||||
|
||||
.welcome-btn-ralph {
|
||||
background: linear-gradient(135deg, #3d3415 0%, #4a3f1a 100%);
|
||||
border-color: #6b5a1e;
|
||||
@@ -1856,29 +1843,109 @@ body {
|
||||
background: var(--accent-hover);
|
||||
}
|
||||
|
||||
/* Claude Button - Special styling (matches welcome-btn-primary) */
|
||||
.btn-toolbar.btn-claude {
|
||||
background: linear-gradient(135deg, #1e3a5f 0%, #2563eb 100%);
|
||||
border-color: #3b82f6;
|
||||
color: #e0e7ff;
|
||||
/* Run Button Group — split button with dropdown */
|
||||
.run-btn-group {
|
||||
display: flex;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.btn-toolbar.btn-run {
|
||||
border-radius: 4px 0 0 4px;
|
||||
border-right: 1px solid rgba(255,255,255,0.15);
|
||||
font-weight: 500;
|
||||
padding: 0.4rem 0.85rem;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
.btn-toolbar.btn-claude:hover {
|
||||
background: linear-gradient(135deg, #2563eb 0%, #3b82f6 100%);
|
||||
border-color: #3b82f6;
|
||||
box-shadow: 0 0 12px rgba(59, 130, 246, 0.4);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-toolbar.btn-claude svg {
|
||||
.btn-toolbar.btn-run svg {
|
||||
flex-shrink: 0;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
.btn-toolbar.btn-run-gear {
|
||||
border-radius: 0 4px 4px 0;
|
||||
padding: 0.4rem 0.4rem;
|
||||
min-width: unset;
|
||||
}
|
||||
|
||||
/* Claude mode colors (default) */
|
||||
.btn-toolbar.btn-run.mode-claude,
|
||||
.btn-toolbar.btn-run-gear.mode-claude {
|
||||
background: linear-gradient(135deg, #1e3a5f 0%, #2563eb 100%);
|
||||
border-color: #3b82f6;
|
||||
color: #e0e7ff;
|
||||
}
|
||||
.btn-toolbar.btn-run.mode-claude:hover,
|
||||
.btn-toolbar.btn-run-gear.mode-claude:hover {
|
||||
background: linear-gradient(135deg, #2563eb 0%, #3b82f6 100%);
|
||||
box-shadow: 0 0 12px rgba(59, 130, 246, 0.4);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* OpenCode mode colors */
|
||||
.btn-toolbar.btn-run.mode-opencode,
|
||||
.btn-toolbar.btn-run-gear.mode-opencode {
|
||||
background: linear-gradient(135deg, #0a2e2a 0%, #0d4a40 100%);
|
||||
border-color: #10b981;
|
||||
color: #6ee7b7;
|
||||
}
|
||||
.btn-toolbar.btn-run.mode-opencode:hover,
|
||||
.btn-toolbar.btn-run-gear.mode-opencode:hover {
|
||||
background: linear-gradient(135deg, #0d4a40 0%, #116a58 100%);
|
||||
box-shadow: 0 0 8px rgba(16, 185, 129, 0.4);
|
||||
color: #a7f3d0;
|
||||
}
|
||||
|
||||
/* Dropdown menu */
|
||||
.run-mode-menu {
|
||||
display: none;
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
left: 0;
|
||||
margin-bottom: 4px;
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
padding: 4px;
|
||||
z-index: 1000;
|
||||
min-width: 150px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.4);
|
||||
}
|
||||
.run-mode-menu.active {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
.run-mode-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 6px 10px;
|
||||
background: none;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
color: var(--text-primary);
|
||||
cursor: pointer;
|
||||
font-size: 0.8rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.run-mode-option:hover {
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
.run-mode-option.selected {
|
||||
background: var(--bg-hover);
|
||||
font-weight: 600;
|
||||
}
|
||||
.run-mode-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.run-mode-dot.claude { background: #3b82f6; }
|
||||
.run-mode-dot.opencode { background: #10b981; }
|
||||
|
||||
/* Shell Button - Special styling */
|
||||
.btn-toolbar.btn-shell {
|
||||
background: linear-gradient(135deg, #1a2e1a 0%, #234023 100%);
|
||||
@@ -1894,19 +1961,7 @@ body {
|
||||
color: #a8d8a8;
|
||||
}
|
||||
|
||||
.btn-toolbar.btn-opencode {
|
||||
background: linear-gradient(135deg, #0a2e2a 0%, #0d4a40 100%);
|
||||
border-color: #10b981;
|
||||
color: #6ee7b7;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.btn-toolbar.btn-opencode:hover {
|
||||
background: linear-gradient(135deg, #0d4a40 0%, #116a58 100%);
|
||||
border-color: #34d399;
|
||||
box-shadow: 0 0 8px rgba(16, 185, 129, 0.4);
|
||||
color: #a7f3d0;
|
||||
}
|
||||
/* btn-opencode standalone removed — now part of run-btn-group mode system */
|
||||
|
||||
/* Stop button - hidden on desktop, shown on mobile */
|
||||
.btn-toolbar.btn-stop {
|
||||
|
||||
Reference in New Issue
Block a user