mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-05 06:59:42 +02:00
fix: subagent tabs render to correct parent session after restart
- restoreSubagentWindowStates now discovers parent sessions before restoring - closeSubagentWindow discovers parent before minimizing to prevent wrong tab - Fixed async handling in subagent:completed event handler chore: bump version to 0.1389 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
+282
-11
@@ -1764,7 +1764,7 @@ class ClaudemanApp {
|
||||
}
|
||||
});
|
||||
|
||||
this.eventSource.addEventListener('subagent:completed', (e) => {
|
||||
this.eventSource.addEventListener('subagent:completed', async (e) => {
|
||||
const data = JSON.parse(e.data);
|
||||
const existing = this.subagents.get(data.agentId);
|
||||
if (existing) {
|
||||
@@ -1778,7 +1778,7 @@ class ClaudemanApp {
|
||||
if (this.subagentWindows.has(data.agentId)) {
|
||||
const windowData = this.subagentWindows.get(data.agentId);
|
||||
if (windowData && !windowData.minimized) {
|
||||
this.closeSubagentWindow(data.agentId); // This minimizes to tab
|
||||
await this.closeSubagentWindow(data.agentId); // This minimizes to tab
|
||||
this.saveSubagentWindowStates(); // Persist the minimized state
|
||||
}
|
||||
}
|
||||
@@ -2346,6 +2346,245 @@ class ClaudemanApp {
|
||||
this.selectSession(ids[nextIndex]);
|
||||
}
|
||||
|
||||
// ========== Navigation ==========
|
||||
|
||||
goHome() {
|
||||
// Deselect active session and show welcome screen
|
||||
this.activeSessionId = null;
|
||||
this.terminal.clear();
|
||||
this.showWelcome();
|
||||
this.renderSessionTabs();
|
||||
this.renderRalphStatePanel();
|
||||
}
|
||||
|
||||
// ========== Ralph Loop Wizard ==========
|
||||
|
||||
// Wizard state
|
||||
ralphWizardStep = 1;
|
||||
ralphWizardConfig = {
|
||||
taskDescription: '',
|
||||
completionPhrase: 'COMPLETE',
|
||||
maxIterations: 10,
|
||||
caseName: 'testcase',
|
||||
enableRespawn: true,
|
||||
};
|
||||
|
||||
showRalphWizard() {
|
||||
// Reset wizard state
|
||||
this.ralphWizardStep = 1;
|
||||
this.ralphWizardConfig = {
|
||||
taskDescription: '',
|
||||
completionPhrase: 'COMPLETE',
|
||||
maxIterations: 10,
|
||||
caseName: document.getElementById('quickStartCase')?.value || 'testcase',
|
||||
enableRespawn: true,
|
||||
};
|
||||
|
||||
// Reset UI
|
||||
document.getElementById('ralphTaskDescription').value = '';
|
||||
document.getElementById('ralphCompletionPhrase').value = 'COMPLETE';
|
||||
this.selectIterationPreset(10);
|
||||
|
||||
// Populate case selector
|
||||
this.populateRalphCaseSelector();
|
||||
|
||||
// Show wizard modal
|
||||
this.updateRalphWizardUI();
|
||||
document.getElementById('ralphWizardModal').classList.add('active');
|
||||
document.getElementById('ralphTaskDescription').focus();
|
||||
}
|
||||
|
||||
closeRalphWizard() {
|
||||
document.getElementById('ralphWizardModal')?.classList.remove('active');
|
||||
}
|
||||
|
||||
populateRalphCaseSelector() {
|
||||
const select = document.getElementById('ralphCaseSelect');
|
||||
const quickStartSelect = document.getElementById('quickStartCase');
|
||||
|
||||
if (quickStartSelect && select) {
|
||||
select.innerHTML = quickStartSelect.innerHTML;
|
||||
select.value = this.ralphWizardConfig.caseName;
|
||||
}
|
||||
}
|
||||
|
||||
selectIterationPreset(iterations) {
|
||||
this.ralphWizardConfig.maxIterations = iterations;
|
||||
|
||||
// Update button states
|
||||
document.querySelectorAll('.iteration-preset-btn').forEach(btn => {
|
||||
const btnIterations = parseInt(btn.dataset.iterations);
|
||||
btn.classList.toggle('active', btnIterations === iterations);
|
||||
});
|
||||
}
|
||||
|
||||
ralphWizardNext() {
|
||||
if (this.ralphWizardStep === 1) {
|
||||
// Validate step 1
|
||||
const taskDescription = document.getElementById('ralphTaskDescription').value.trim();
|
||||
const completionPhrase = document.getElementById('ralphCompletionPhrase').value.trim() || 'COMPLETE';
|
||||
const caseName = document.getElementById('ralphCaseSelect').value;
|
||||
|
||||
if (!taskDescription) {
|
||||
this.showToast('Please enter a task description', 'error');
|
||||
document.getElementById('ralphTaskDescription').focus();
|
||||
return;
|
||||
}
|
||||
|
||||
// Save config
|
||||
this.ralphWizardConfig.taskDescription = taskDescription;
|
||||
this.ralphWizardConfig.completionPhrase = completionPhrase.toUpperCase();
|
||||
this.ralphWizardConfig.caseName = caseName;
|
||||
|
||||
// Generate preview
|
||||
this.updateRalphPromptPreview();
|
||||
|
||||
// Move to step 2
|
||||
this.ralphWizardStep = 2;
|
||||
this.updateRalphWizardUI();
|
||||
}
|
||||
}
|
||||
|
||||
ralphWizardBack() {
|
||||
if (this.ralphWizardStep === 2) {
|
||||
this.ralphWizardStep = 1;
|
||||
this.updateRalphWizardUI();
|
||||
}
|
||||
}
|
||||
|
||||
updateRalphWizardUI() {
|
||||
const step = this.ralphWizardStep;
|
||||
|
||||
// Update progress indicators
|
||||
document.querySelectorAll('.wizard-step').forEach(el => {
|
||||
const stepNum = parseInt(el.dataset.step);
|
||||
el.classList.toggle('active', stepNum === step);
|
||||
el.classList.toggle('completed', stepNum < step);
|
||||
});
|
||||
|
||||
// Show/hide pages
|
||||
document.getElementById('ralphWizardStep1').classList.toggle('hidden', step !== 1);
|
||||
document.getElementById('ralphWizardStep2').classList.toggle('hidden', step !== 2);
|
||||
|
||||
// Show/hide buttons
|
||||
document.getElementById('ralphBackBtn').style.display = step === 1 ? 'none' : 'block';
|
||||
document.getElementById('ralphNextBtn').style.display = step === 2 ? 'none' : 'block';
|
||||
document.getElementById('ralphStartBtn').style.display = step === 2 ? 'block' : 'none';
|
||||
}
|
||||
|
||||
updateRalphPromptPreview() {
|
||||
const config = this.ralphWizardConfig;
|
||||
const preview = document.getElementById('ralphPromptPreview');
|
||||
|
||||
// Build the formatted prompt
|
||||
let prompt = config.taskDescription;
|
||||
|
||||
// Add completion instructions
|
||||
prompt += '\n\n---\n\n';
|
||||
prompt += '## Completion Criteria\n\n';
|
||||
prompt += 'When ALL requirements above are complete:\n';
|
||||
prompt += '1. Run all tests to verify\n';
|
||||
prompt += '2. Commit your changes\n';
|
||||
prompt += `3. Output: <promise>${config.completionPhrase}</promise>\n`;
|
||||
|
||||
// Show preview with highlighting
|
||||
const highlightedPrompt = prompt
|
||||
.replace(/<promise>/g, '<span class="preview-highlight"><promise>')
|
||||
.replace(/<\/promise>/g, '</promise></span>');
|
||||
|
||||
preview.innerHTML = highlightedPrompt;
|
||||
|
||||
// Update summary
|
||||
document.getElementById('summaryPhrase').textContent = config.completionPhrase;
|
||||
document.getElementById('summaryIterations').textContent =
|
||||
config.maxIterations === 0 ? 'Unlimited' : config.maxIterations;
|
||||
document.getElementById('summaryCase').textContent = config.caseName;
|
||||
}
|
||||
|
||||
async startRalphLoop() {
|
||||
const config = this.ralphWizardConfig;
|
||||
|
||||
// Read advanced options
|
||||
config.enableRespawn = document.getElementById('ralphEnableRespawn')?.checked ?? true;
|
||||
|
||||
// Close wizard
|
||||
this.closeRalphWizard();
|
||||
|
||||
// Build the full prompt
|
||||
let fullPrompt = config.taskDescription;
|
||||
fullPrompt += '\n\n---\n\n';
|
||||
fullPrompt += '## Completion Criteria\n\n';
|
||||
fullPrompt += 'When ALL requirements above are complete:\n';
|
||||
fullPrompt += '1. Run all tests to verify\n';
|
||||
fullPrompt += '2. Commit your changes\n';
|
||||
fullPrompt += `3. Output: <promise>${config.completionPhrase}</promise>\n`;
|
||||
|
||||
try {
|
||||
// Step 1: Create/verify case exists and start session
|
||||
this.terminal.clear();
|
||||
this.terminal.writeln(`\x1b[1;33m Starting Ralph Loop in ${config.caseName}...\x1b[0m`);
|
||||
this.terminal.writeln('');
|
||||
|
||||
// Create session via quick-start
|
||||
const res = await fetch('/api/quick-start', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ caseName: config.caseName, mode: 'claude' })
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
if (!data.success) {
|
||||
this.showToast(data.error || 'Failed to start session', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const sessionId = data.sessionId;
|
||||
|
||||
// Step 2: Configure Ralph tracker for this session
|
||||
await fetch(`/api/sessions/${sessionId}/ralph-config`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
enabled: true,
|
||||
completionPhrase: config.completionPhrase,
|
||||
maxIterations: config.maxIterations || null,
|
||||
})
|
||||
});
|
||||
|
||||
// Step 3: Enable respawn if requested
|
||||
if (config.enableRespawn) {
|
||||
await fetch(`/api/sessions/${sessionId}/respawn/enable`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
prompt: 'continue',
|
||||
sendClear: true,
|
||||
sendInit: true,
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
// Step 4: Switch to session and wait for it to be ready
|
||||
await this.selectSession(sessionId);
|
||||
|
||||
// Wait a moment for session to be ready, then send the prompt
|
||||
await new Promise(r => setTimeout(r, 2000));
|
||||
|
||||
// Send the prompt to start the loop
|
||||
await fetch(`/api/sessions/${sessionId}/input`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ input: fullPrompt + '\n' })
|
||||
});
|
||||
|
||||
this.showToast(`Ralph Loop started in ${config.caseName}`, 'success');
|
||||
|
||||
} catch (err) {
|
||||
console.error('Failed to start Ralph loop:', err);
|
||||
this.showToast('Failed to start Ralph loop: ' + err.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Quick Start ==========
|
||||
|
||||
async loadQuickStartCases(selectCaseName = null) {
|
||||
@@ -4287,13 +4526,35 @@ class ClaudemanApp {
|
||||
async restoreSubagentWindowStates() {
|
||||
const states = await this.loadSubagentWindowStates();
|
||||
|
||||
// Restore minimized state
|
||||
for (const [sessionId, agentIds] of Object.entries(states.minimized || {})) {
|
||||
// First, discover parent sessions for all subagents to establish correct mapping
|
||||
// This is important after server restart when parentSessionId isn't set
|
||||
const parentDiscoveryPromises = [];
|
||||
for (const [agentId, agent] of this.subagents) {
|
||||
if (!agent.parentSessionId) {
|
||||
parentDiscoveryPromises.push(this.findParentSessionForSubagent(agentId));
|
||||
}
|
||||
}
|
||||
if (parentDiscoveryPromises.length > 0) {
|
||||
await Promise.all(parentDiscoveryPromises);
|
||||
}
|
||||
|
||||
// Restore minimized state, but verify session mapping is correct
|
||||
for (const [savedSessionId, agentIds] of Object.entries(states.minimized || {})) {
|
||||
if (Array.isArray(agentIds) && agentIds.length > 0) {
|
||||
// Verify agents still exist
|
||||
const validAgents = agentIds.filter(id => this.subagents.has(id));
|
||||
if (validAgents.length > 0) {
|
||||
this.minimizedSubagents.set(sessionId, new Set(validAgents));
|
||||
for (const agentId of agentIds) {
|
||||
const agent = this.subagents.get(agentId);
|
||||
if (!agent) continue; // Agent no longer exists
|
||||
|
||||
// Use discovered parentSessionId, or validate saved sessionId exists
|
||||
const correctSessionId = agent.parentSessionId ||
|
||||
(this.sessions.has(savedSessionId) ? savedSessionId : null);
|
||||
|
||||
if (correctSessionId) {
|
||||
if (!this.minimizedSubagents.has(correctSessionId)) {
|
||||
this.minimizedSubagents.set(correctSessionId, new Set());
|
||||
}
|
||||
this.minimizedSubagents.get(correctSessionId).add(agentId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4326,6 +4587,7 @@ class ClaudemanApp {
|
||||
}
|
||||
|
||||
this.renderSessionTabs(); // Update tab badges
|
||||
this.saveSubagentWindowStates(); // Persist corrected mappings
|
||||
}
|
||||
|
||||
// ========== Help Modal ==========
|
||||
@@ -5804,14 +6066,23 @@ class ClaudemanApp {
|
||||
this.saveSubagentWindowStates();
|
||||
}
|
||||
|
||||
closeSubagentWindow(agentId) {
|
||||
async closeSubagentWindow(agentId) {
|
||||
const windowData = this.subagentWindows.get(agentId);
|
||||
if (!windowData) return;
|
||||
|
||||
const agent = this.subagents.get(agentId);
|
||||
const parentSessionId = agent?.parentSessionId || this.activeSessionId;
|
||||
|
||||
// Always minimize to tab (use active session if no parent)
|
||||
// Try to discover parent session if not already known
|
||||
// This ensures we minimize to the correct tab
|
||||
if (agent && !agent.parentSessionId) {
|
||||
await this.findParentSessionForSubagent(agentId);
|
||||
}
|
||||
|
||||
// Now get the correct parent session (re-read agent after discovery)
|
||||
const updatedAgent = this.subagents.get(agentId);
|
||||
const parentSessionId = updatedAgent?.parentSessionId || this.activeSessionId;
|
||||
|
||||
// Always minimize to tab (use active session if no parent found)
|
||||
windowData.element.style.display = 'none';
|
||||
windowData.minimized = true;
|
||||
|
||||
|
||||
+133
-1
@@ -17,7 +17,7 @@
|
||||
<!-- Compact Header with Session Tabs -->
|
||||
<header class="header">
|
||||
<div class="header-brand">
|
||||
<span class="logo">Claudeman</span>
|
||||
<span class="logo" onclick="app.goHome()" title="Go to main page">Claudeman</span>
|
||||
</div>
|
||||
|
||||
<!-- Session Tabs -->
|
||||
@@ -206,6 +206,9 @@
|
||||
<input type="number" id="shellCount" class="tab-count-input" value="1" min="1" max="20" readonly title="Instance count">
|
||||
<button class="tab-count-btn" onclick="app.incrementShellCount()" title="Instance count">+</button>
|
||||
</div>
|
||||
<button class="btn-toolbar btn-ralph" onclick="app.showRalphWizard()" title="Start Ralph Loop">
|
||||
Ralph Loop
|
||||
</button>
|
||||
<div class="case-select-group">
|
||||
<select id="quickStartCase" class="toolbar-select" title="Select case">
|
||||
<option value="testcase">testcase</option>
|
||||
@@ -854,6 +857,135 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Ralph Loop Wizard Modal -->
|
||||
<div class="modal" id="ralphWizardModal">
|
||||
<div class="modal-backdrop" onclick="app.closeRalphWizard()"></div>
|
||||
<div class="modal-content modal-lg">
|
||||
<div class="modal-header">
|
||||
<h3>Start Ralph Loop</h3>
|
||||
<button class="modal-close" onclick="app.closeRalphWizard()">×</button>
|
||||
</div>
|
||||
|
||||
<!-- Wizard Progress Steps -->
|
||||
<div class="wizard-progress">
|
||||
<div class="wizard-step active" data-step="1">
|
||||
<span class="wizard-step-number">1</span>
|
||||
<span class="wizard-step-label">Task Setup</span>
|
||||
</div>
|
||||
<div class="wizard-step" data-step="2">
|
||||
<span class="wizard-step-number">2</span>
|
||||
<span class="wizard-step-label">Launch</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<!-- Step 1: Task & Completion Setup -->
|
||||
<div class="wizard-page" id="ralphWizardStep1">
|
||||
<div class="form-row">
|
||||
<label>Task Description</label>
|
||||
<div class="textarea-with-assist">
|
||||
<textarea
|
||||
id="ralphTaskDescription"
|
||||
rows="6"
|
||||
placeholder="Describe what you want Claude to accomplish...
|
||||
|
||||
Example:
|
||||
Build a REST API for user authentication:
|
||||
- JWT token generation and validation
|
||||
- Login/logout endpoints
|
||||
- Password reset flow
|
||||
- Tests for all endpoints"></textarea>
|
||||
</div>
|
||||
<span class="form-hint">Be specific about requirements and success criteria</span>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label>Completion Phrase</label>
|
||||
<div class="phrase-input-group">
|
||||
<input type="text" id="ralphCompletionPhrase" value="COMPLETE" placeholder="COMPLETE" list="phrasesSuggestions">
|
||||
<datalist id="phrasesSuggestions">
|
||||
<option value="COMPLETE">
|
||||
<option value="DONE">
|
||||
<option value="FINISHED">
|
||||
<option value="TESTS_PASS">
|
||||
<option value="ALL_TASKS_COMPLETE">
|
||||
</datalist>
|
||||
</div>
|
||||
<span class="form-hint">Claude outputs <code><promise>PHRASE</promise></code> when done</span>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label>Max Iterations</label>
|
||||
<div class="iteration-presets">
|
||||
<button type="button" class="iteration-preset-btn active" data-iterations="10" onclick="app.selectIterationPreset(10)">10</button>
|
||||
<button type="button" class="iteration-preset-btn" data-iterations="25" onclick="app.selectIterationPreset(25)">25</button>
|
||||
<button type="button" class="iteration-preset-btn" data-iterations="50" onclick="app.selectIterationPreset(50)">50</button>
|
||||
<button type="button" class="iteration-preset-btn" data-iterations="100" onclick="app.selectIterationPreset(100)">100</button>
|
||||
<button type="button" class="iteration-preset-btn" data-iterations="0" onclick="app.selectIterationPreset(0)">∞</button>
|
||||
</div>
|
||||
<span class="form-hint">Safety limit - loop stops after this many iterations</span>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label>Working Directory</label>
|
||||
<div class="case-select-inline">
|
||||
<select id="ralphCaseSelect" class="form-select"></select>
|
||||
<button class="btn-toolbar btn-sm" onclick="app.showCreateCaseModal()">+ New</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Step 2: Preview & Launch -->
|
||||
<div class="wizard-page hidden" id="ralphWizardStep2">
|
||||
<div class="ralph-preview-section">
|
||||
<label>Prompt Preview</label>
|
||||
<div class="ralph-prompt-preview" id="ralphPromptPreview"></div>
|
||||
</div>
|
||||
|
||||
<div class="ralph-config-summary">
|
||||
<div class="config-summary-row">
|
||||
<span class="config-label">Completion Phrase:</span>
|
||||
<code id="summaryPhrase">COMPLETE</code>
|
||||
</div>
|
||||
<div class="config-summary-row">
|
||||
<span class="config-label">Max Iterations:</span>
|
||||
<span id="summaryIterations">10</span>
|
||||
</div>
|
||||
<div class="config-summary-row">
|
||||
<span class="config-label">Case:</span>
|
||||
<span id="summaryCase">testcase</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Advanced Options (Collapsible) -->
|
||||
<details class="advanced-options">
|
||||
<summary>Advanced Options</summary>
|
||||
<div class="advanced-options-content">
|
||||
<div class="form-row form-row-switch">
|
||||
<label>Enable Respawn</label>
|
||||
<label class="switch">
|
||||
<input type="checkbox" id="ralphEnableRespawn" checked>
|
||||
<span class="slider"></span>
|
||||
</label>
|
||||
<span class="form-hint">Auto-restart sessions when context fills up</span>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Wizard Footer -->
|
||||
<div class="wizard-footer">
|
||||
<button class="btn-toolbar" id="ralphBackBtn" onclick="app.ralphWizardBack()" style="display: none;">Back</button>
|
||||
<div class="wizard-footer-right">
|
||||
<button class="btn-toolbar" onclick="app.closeRalphWizard()">Cancel</button>
|
||||
<button class="btn-toolbar btn-primary" id="ralphNextBtn" onclick="app.ralphWizardNext()">Next</button>
|
||||
<button class="btn-toolbar btn-success" id="ralphStartBtn" onclick="app.startRalphLoop()" style="display: none;">Start Loop</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Kill All Confirmation Modal -->
|
||||
<div class="modal" id="killAllModal">
|
||||
<div class="modal-backdrop" onclick="app.closeKillAllModal()"></div>
|
||||
|
||||
@@ -67,6 +67,13 @@ body {
|
||||
font-weight: 600;
|
||||
color: var(--accent-hover);
|
||||
letter-spacing: -0.02em;
|
||||
cursor: pointer;
|
||||
transition: color 0.15s, text-shadow 0.15s;
|
||||
}
|
||||
|
||||
.logo:hover {
|
||||
color: #93c5fd;
|
||||
text-shadow: 0 0 8px rgba(96, 165, 250, 0.5);
|
||||
}
|
||||
|
||||
/* Session Tabs */
|
||||
@@ -1435,6 +1442,21 @@ body {
|
||||
color: #a8d8a8;
|
||||
}
|
||||
|
||||
/* Ralph Loop button - golden/yellow theme */
|
||||
.btn-toolbar.btn-ralph {
|
||||
background: linear-gradient(135deg, #3d3415 0%, #4a3f1a 100%);
|
||||
border-color: #6b5a1e;
|
||||
color: #eab308;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.btn-toolbar.btn-ralph:hover {
|
||||
background: linear-gradient(135deg, #4a3f1a 0%, #5a4d20 100%);
|
||||
border-color: #8b7a2e;
|
||||
box-shadow: 0 0 8px rgba(234, 179, 8, 0.4);
|
||||
color: #fcd34d;
|
||||
}
|
||||
|
||||
/* Button icon styling */
|
||||
.btn-toolbar .btn-icon {
|
||||
display: inline-flex;
|
||||
@@ -4600,3 +4622,259 @@ kbd {
|
||||
content: "📄 Click to stream: ";
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
/* ========== Ralph Loop Wizard ========== */
|
||||
|
||||
/* Wizard Progress Indicator */
|
||||
.wizard-progress {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 2rem;
|
||||
padding: 0.75rem 1rem;
|
||||
background: var(--bg-dark);
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.wizard-step {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
color: var(--text-muted);
|
||||
font-size: 0.8rem;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
.wizard-step.active {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.wizard-step.completed {
|
||||
color: var(--green);
|
||||
}
|
||||
|
||||
.wizard-step-number {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--bg-input);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 50%;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.wizard-step.active .wizard-step-number {
|
||||
background: var(--accent);
|
||||
border-color: var(--accent);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.wizard-step.completed .wizard-step-number {
|
||||
background: var(--green);
|
||||
border-color: var(--green);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.wizard-step-label {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Wizard Pages */
|
||||
.wizard-page {
|
||||
min-height: 280px;
|
||||
}
|
||||
|
||||
.wizard-page.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Wizard Footer */
|
||||
.wizard-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0.75rem 1rem;
|
||||
border-top: 1px solid var(--border);
|
||||
background: var(--bg-card);
|
||||
}
|
||||
|
||||
.wizard-footer-right {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
/* Textarea with AI Assist button */
|
||||
.textarea-with-assist {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.textarea-with-assist textarea {
|
||||
width: 100%;
|
||||
padding: 0.75rem;
|
||||
background: var(--bg-input);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 4px;
|
||||
color: var(--text);
|
||||
font-size: 0.85rem;
|
||||
font-family: 'SF Mono', Monaco, 'Courier New', monospace;
|
||||
resize: vertical;
|
||||
min-height: 140px;
|
||||
}
|
||||
|
||||
.textarea-with-assist textarea:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
/* Phrase Input Group */
|
||||
.phrase-input-group {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.phrase-input-group input {
|
||||
width: 100%;
|
||||
padding: 0.5rem 0.75rem;
|
||||
background: var(--bg-input);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 4px;
|
||||
color: var(--text);
|
||||
font-size: 0.85rem;
|
||||
font-family: 'SF Mono', Monaco, 'Courier New', monospace;
|
||||
}
|
||||
|
||||
.phrase-input-group input:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
/* Iteration Presets */
|
||||
.iteration-presets {
|
||||
display: flex;
|
||||
gap: 0.35rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.iteration-preset-btn {
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 500;
|
||||
background: var(--bg-input);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 4px;
|
||||
color: var(--text-dim);
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
min-width: 50px;
|
||||
}
|
||||
|
||||
.iteration-preset-btn:hover {
|
||||
border-color: var(--border-light);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.iteration-preset-btn.active {
|
||||
background: var(--accent);
|
||||
border-color: var(--accent);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* Case Select Inline */
|
||||
.case-select-inline {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.case-select-inline select {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* Ralph Prompt Preview */
|
||||
.ralph-preview-section {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.ralph-preview-section > label {
|
||||
display: block;
|
||||
font-size: 0.7rem;
|
||||
color: var(--text-dim);
|
||||
margin-bottom: 0.35rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.03em;
|
||||
}
|
||||
|
||||
.ralph-prompt-preview {
|
||||
background: var(--bg-dark);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 4px;
|
||||
padding: 0.75rem;
|
||||
font-family: 'SF Mono', Monaco, 'Courier New', monospace;
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-dim);
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.ralph-prompt-preview .preview-highlight {
|
||||
color: var(--accent);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Config Summary */
|
||||
.ralph-config-summary {
|
||||
background: var(--bg-input);
|
||||
border-radius: 4px;
|
||||
padding: 0.75rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.config-summary-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 0.35rem 0;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.config-summary-row:not(:last-child) {
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.config-label {
|
||||
color: var(--text-dim);
|
||||
}
|
||||
|
||||
.config-summary-row code {
|
||||
background: var(--bg-dark);
|
||||
padding: 0.15rem 0.4rem;
|
||||
border-radius: 3px;
|
||||
font-size: 0.75rem;
|
||||
color: var(--green);
|
||||
}
|
||||
|
||||
/* Advanced Options */
|
||||
.advanced-options {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.advanced-options summary {
|
||||
cursor: pointer;
|
||||
font-size: 0.8rem;
|
||||
color: var(--text-dim);
|
||||
padding: 0.5rem 0;
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.advanced-options summary:hover {
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.advanced-options[open] summary {
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.advanced-options-content {
|
||||
padding-left: 0.5rem;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user