|
1 | | -<!doctype html> |
| 1 | +<!DOCTYPE html> |
2 | 2 | <html lang="en"> |
3 | | - <head> |
4 | | - <meta charset="UTF-8" /> |
5 | | - <link rel="icon" type="image/svg+xml" href="/vite.svg" /> |
6 | | - <meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
7 | | - <title>Roo Commander Mode Configurator</title> |
8 | | - <meta name="description" content="A tool to select and configure custom modes for Roo Commander, generating a JSON output for easy integration." /> |
9 | | - </head> |
10 | | - <body> |
11 | | - <div id="app"></div> |
12 | | - <script type="module" src="/src/main.js"></script> |
13 | | - </body> |
| 3 | +<head> |
| 4 | + <meta charset="UTF-8"> |
| 5 | + <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| 6 | + <title>Roo Code Mode Filter</title> |
| 7 | + <style> |
| 8 | + body { |
| 9 | + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; |
| 10 | + margin: 20px; |
| 11 | + line-height: 1.6; |
| 12 | + background-color: #f8f9fa; |
| 13 | + color: #333; |
| 14 | + } |
| 15 | + .container { |
| 16 | + max-width: 900px; |
| 17 | + margin: 0 auto; |
| 18 | + background-color: #fff; |
| 19 | + padding: 25px; |
| 20 | + border-radius: 8px; |
| 21 | + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); |
| 22 | + } |
| 23 | + h1, h2 { |
| 24 | + border-bottom: 1px solid #eee; |
| 25 | + padding-bottom: 10px; |
| 26 | + margin-bottom: 20px; |
| 27 | + color: #007bff; |
| 28 | + } |
| 29 | + label { |
| 30 | + display: block; |
| 31 | + margin-bottom: 8px; |
| 32 | + font-weight: bold; |
| 33 | + } |
| 34 | + textarea { |
| 35 | + width: 95%; /* Adjusted width */ |
| 36 | + padding: 10px; |
| 37 | + margin-bottom: 15px; |
| 38 | + border: 1px solid #ccc; |
| 39 | + border-radius: 4px; |
| 40 | + font-family: monospace; |
| 41 | + font-size: 0.9em; |
| 42 | + min-height: 150px; |
| 43 | + resize: vertical; |
| 44 | + background-color: #fff; /* Ensure background is white */ |
| 45 | + color: #333; /* Ensure text color is dark */ |
| 46 | + } |
| 47 | + button, .file-upload-label { |
| 48 | + background-color: #007bff; |
| 49 | + color: white; |
| 50 | + border: none; |
| 51 | + padding: 10px 18px; |
| 52 | + border-radius: 4px; |
| 53 | + cursor: pointer; |
| 54 | + font-size: 1em; |
| 55 | + margin-right: 10px; |
| 56 | + transition: background-color 0.2s ease; |
| 57 | + display: inline-block; /* Needed for label styling */ |
| 58 | + vertical-align: middle; /* Align with other buttons */ |
| 59 | + margin-bottom: 10px; /* Spacing */ |
| 60 | + } |
| 61 | + button:hover, .file-upload-label:hover { |
| 62 | + background-color: #0056b3; |
| 63 | + } |
| 64 | + button:disabled { |
| 65 | + background-color: #cccccc; |
| 66 | + cursor: not-allowed; |
| 67 | + } |
| 68 | + #fileInput { |
| 69 | + display: none; /* Hide the actual file input */ |
| 70 | + } |
| 71 | + .input-area { |
| 72 | + border: 1px solid #eee; |
| 73 | + padding: 15px; |
| 74 | + margin-bottom: 20px; |
| 75 | + border-radius: 5px; |
| 76 | + } |
| 77 | + #modeListContainer { |
| 78 | + margin-top: 20px; |
| 79 | + } |
| 80 | + #modeList { |
| 81 | + margin-top: 10px; |
| 82 | + padding: 15px; |
| 83 | + border: 1px dashed #ddd; |
| 84 | + border-radius: 4px; |
| 85 | + max-height: 300px; |
| 86 | + overflow-y: auto; |
| 87 | + background-color: #fdfdfd; |
| 88 | + } |
| 89 | + #modeList div { |
| 90 | + margin-bottom: 8px; |
| 91 | + display: flex; |
| 92 | + align-items: center; |
| 93 | + } |
| 94 | + #modeList input[type="checkbox"] { |
| 95 | + margin-right: 10px; |
| 96 | + cursor: pointer; |
| 97 | + transform: scale(1.1); /* Slightly larger checkboxes */ |
| 98 | + } |
| 99 | + #modeList label { |
| 100 | + font-weight: normal; |
| 101 | + margin-bottom: 0; |
| 102 | + cursor: pointer; |
| 103 | + flex-grow: 1; /* Allow label to take space */ |
| 104 | + } |
| 105 | + #errorDisplay, #fileInfo { |
| 106 | + color: #dc3545; |
| 107 | + margin-top: 15px; |
| 108 | + font-weight: bold; |
| 109 | + min-height: 1.2em; /* Reserve space */ |
| 110 | + } |
| 111 | + #fileInfo { |
| 112 | + color: #28a745; /* Green for success */ |
| 113 | + } |
| 114 | + pre { |
| 115 | + background-color: #e9ecef; |
| 116 | + padding: 15px; |
| 117 | + border-radius: 4px; |
| 118 | + overflow-x: auto; |
| 119 | + white-space: pre-wrap; /* Wrap long lines */ |
| 120 | + word-wrap: break-word; /* Break words if necessary */ |
| 121 | + } |
| 122 | + code { |
| 123 | + font-family: monospace; |
| 124 | + font-size: 0.9em; |
| 125 | + color: #333; |
| 126 | + } |
| 127 | + .button-group button { |
| 128 | + margin-right: 5px; |
| 129 | + background-color: #6c757d; |
| 130 | + } |
| 131 | + .button-group button:hover { |
| 132 | + background-color: #5a6268; |
| 133 | + } |
| 134 | + |
| 135 | + </style> |
| 136 | +</head> |
| 137 | +<body> |
| 138 | + |
| 139 | +<div class="container"> |
| 140 | + <h1>Roo Code Mode Filter</h1> |
| 141 | + |
| 142 | + <p>Paste the full JSON content of a Roo Code custom_modes.json file into the input area below or select the file from your computer, click load the modes, select the ones you want, and generate the filtered JSON. For detailed explanations of each mode and how they work together, please refer to the main README.md file in the <a href="https://github.com/jezweb/roo-commander" target="_blank" rel="noopener noreferrer">repository</a>.</p> |
| 143 | + |
| 144 | + <div class="input-area"> |
| 145 | + <h2>Input Method</h2> |
| 146 | + <div> |
| 147 | + <label for="jsonInput">Option 1: Paste JSON</label> |
| 148 | + <textarea id="jsonInput" placeholder="Paste your Roo Code JSON here..."></textarea> |
| 149 | + </div> |
| 150 | + <div> |
| 151 | + <label for="fileInput" class="file-upload-label">Option 2: Upload JSON/TXT File</label> |
| 152 | + <input type="file" id="fileInput" accept=".json,.txt"> |
| 153 | + <div id="fileInfo"></div> |
| 154 | + </div> |
| 155 | + <button id="loadButton">Load Modes from Input</button> |
| 156 | + </div> |
| 157 | + |
| 158 | + <div id="errorDisplay"></div> |
| 159 | + |
| 160 | + <div id="modeListContainer" style="display: none;"> |
| 161 | + <h2>Select Modes:</h2> |
| 162 | + <div class="button-group"> |
| 163 | + <button id="selectAllButton">Select All</button> |
| 164 | + <button id="selectNoneButton">Select None</button> |
| 165 | + </div> |
| 166 | + <div id="modeList"> |
| 167 | + <!-- Mode checkboxes will be populated here --> |
| 168 | + <p>Load JSON first to see modes.</p> |
| 169 | + </div> |
| 170 | + <button id="generateButton" style="margin-top: 20px;" disabled>Generate Filtered JSON</button> |
| 171 | + </div> |
| 172 | + |
| 173 | + |
| 174 | + <div id="outputContainer" style="display: none;"> |
| 175 | + <h2>Filtered JSON Output:</h2> |
| 176 | + <label for="jsonOutput">Result:</label> |
| 177 | + <textarea id="jsonOutput" readonly placeholder="Filtered JSON will appear here..."></textarea> |
| 178 | + </div> |
| 179 | + |
| 180 | +</div> |
| 181 | + |
| 182 | +<script> |
| 183 | + const jsonInput = document.getElementById('jsonInput'); |
| 184 | + const fileInput = document.getElementById('fileInput'); |
| 185 | + const fileInfo = document.getElementById('fileInfo'); |
| 186 | + const loadButton = document.getElementById('loadButton'); |
| 187 | + const modeListDiv = document.getElementById('modeList'); |
| 188 | + const modeListContainer = document.getElementById('modeListContainer'); |
| 189 | + const generateButton = document.getElementById('generateButton'); |
| 190 | + const jsonOutput = document.getElementById('jsonOutput'); |
| 191 | + const outputContainer = document.getElementById('outputContainer'); |
| 192 | + const errorDisplay = document.getElementById('errorDisplay'); |
| 193 | + const selectAllButton = document.getElementById('selectAllButton'); |
| 194 | + const selectNoneButton = document.getElementById('selectNoneButton'); |
| 195 | + |
| 196 | + let allModesData = []; // To store the original mode objects |
| 197 | + |
| 198 | + // --- File Upload Handling --- |
| 199 | + fileInput.addEventListener('change', (event) => { |
| 200 | + const file = event.target.files[0]; |
| 201 | + if (!file) { |
| 202 | + fileInfo.textContent = ''; |
| 203 | + return; |
| 204 | + } |
| 205 | + |
| 206 | + // Basic type check (though 'accept' attribute is primary) |
| 207 | + if (!file.type.includes('json') && file.type !== 'text/plain') { |
| 208 | + errorDisplay.textContent = 'Warning: Selected file is not JSON or TXT. Attempting to read anyway.'; |
| 209 | + } else { |
| 210 | + errorDisplay.textContent = ''; // Clear previous warnings/errors |
| 211 | + } |
| 212 | + |
| 213 | + |
| 214 | + const reader = new FileReader(); |
| 215 | + |
| 216 | + reader.onload = (e) => { |
| 217 | + try { |
| 218 | + jsonInput.value = e.target.result; // Put file content into textarea |
| 219 | + fileInfo.textContent = `File "${file.name}" loaded into textarea. Click 'Load Modes'.`; |
| 220 | + errorDisplay.textContent = ''; // Clear errors on successful load |
| 221 | + } catch (err) { |
| 222 | + console.error("Error setting textarea value:", err); |
| 223 | + errorDisplay.textContent = "Error displaying file content."; |
| 224 | + fileInfo.textContent = ''; |
| 225 | + } |
| 226 | + }; |
| 227 | + |
| 228 | + reader.onerror = (e) => { |
| 229 | + console.error("FileReader Error:", e); |
| 230 | + errorDisplay.textContent = `Error reading file: ${file.name}`; |
| 231 | + fileInfo.textContent = ''; |
| 232 | + jsonInput.value = ''; // Clear textarea on read error |
| 233 | + }; |
| 234 | + |
| 235 | + reader.readAsText(file); // Read the file as text |
| 236 | + }); |
| 237 | + |
| 238 | + |
| 239 | + // --- Load Modes Button --- |
| 240 | + loadButton.addEventListener('click', () => { |
| 241 | + modeListDiv.innerHTML = ''; // Clear previous list |
| 242 | + jsonOutput.value = ''; // Clear previous output |
| 243 | + outputContainer.style.display = 'none'; // Hide output |
| 244 | + errorDisplay.textContent = ''; // Clear errors |
| 245 | + generateButton.disabled = true; // Disable generate button |
| 246 | + selectAllButton.disabled = true; |
| 247 | + selectNoneButton.disabled = true; |
| 248 | + modeListContainer.style.display = 'none'; // Hide mode list initially |
| 249 | + allModesData = []; |
| 250 | + |
| 251 | + const jsonString = jsonInput.value.trim(); |
| 252 | + if (!jsonString) { |
| 253 | + errorDisplay.textContent = 'Input JSON (from paste or file) cannot be empty.'; |
| 254 | + return; |
| 255 | + } |
| 256 | + |
| 257 | + fileInfo.textContent = ''; // Clear file loaded message |
| 258 | + |
| 259 | + try { |
| 260 | + const parsedJson = JSON.parse(jsonString); |
| 261 | + |
| 262 | + if (!parsedJson.customModes || !Array.isArray(parsedJson.customModes)) { |
| 263 | + errorDisplay.textContent = 'Invalid JSON format: Missing or invalid "customModes" array.'; |
| 264 | + modeListDiv.innerHTML = '<p>Invalid JSON format.</p>'; |
| 265 | + modeListContainer.style.display = 'block'; // Show container even on error |
| 266 | + return; |
| 267 | + } |
| 268 | + |
| 269 | + allModesData = parsedJson.customModes; // Store the data |
| 270 | + |
| 271 | + if (allModesData.length === 0) { |
| 272 | + modeListDiv.innerHTML = '<p>No modes found in the provided JSON.</p>'; |
| 273 | + modeListContainer.style.display = 'block'; // Show container |
| 274 | + return; |
| 275 | + } |
| 276 | + |
| 277 | + // Sort modes alphabetically by name for better display |
| 278 | + allModesData.sort((a, b) => (a.name || '').localeCompare(b.name || '')); |
| 279 | + |
| 280 | + allModesData.forEach(mode => { |
| 281 | + // Basic validation of mode object |
| 282 | + if (mode && mode.slug && mode.name) { |
| 283 | + const div = document.createElement('div'); |
| 284 | + const checkbox = document.createElement('input'); |
| 285 | + checkbox.type = 'checkbox'; |
| 286 | + checkbox.id = `mode-${mode.slug}`; |
| 287 | + checkbox.value = mode.slug; // Use slug as the identifier |
| 288 | + checkbox.checked = true; // Default to checked |
| 289 | + checkbox.dataset.name = mode.name; // Store name for potential future use |
| 290 | + |
| 291 | + const label = document.createElement('label'); |
| 292 | + label.htmlFor = `mode-${mode.slug}`; |
| 293 | + label.textContent = mode.name; // Display the mode name |
| 294 | + |
| 295 | + div.appendChild(checkbox); |
| 296 | + div.appendChild(label); |
| 297 | + modeListDiv.appendChild(div); |
| 298 | + } else { |
| 299 | + console.warn("Skipping invalid mode object:", mode); |
| 300 | + } |
| 301 | + }); |
| 302 | + |
| 303 | + modeListContainer.style.display = 'block'; // Show the list container |
| 304 | + generateButton.disabled = false; // Enable generate button |
| 305 | + selectAllButton.disabled = false; |
| 306 | + selectNoneButton.disabled = false; |
| 307 | + |
| 308 | + } catch (error) { |
| 309 | + console.error("JSON Parsing Error:", error); |
| 310 | + errorDisplay.textContent = `Error parsing JSON: ${error.message}`; |
| 311 | + modeListDiv.innerHTML = '<p>Error loading modes. Check JSON format.</p>'; |
| 312 | + modeListContainer.style.display = 'block'; // Show container even on error |
| 313 | + } |
| 314 | + }); |
| 315 | + |
| 316 | + // --- Select All / None Buttons --- |
| 317 | + selectAllButton.addEventListener('click', () => { |
| 318 | + const checkboxes = modeListDiv.querySelectorAll('input[type="checkbox"]'); |
| 319 | + checkboxes.forEach(checkbox => checkbox.checked = true); |
| 320 | + }); |
| 321 | + |
| 322 | + selectNoneButton.addEventListener('click', () => { |
| 323 | + const checkboxes = modeListDiv.querySelectorAll('input[type="checkbox"]'); |
| 324 | + checkboxes.forEach(checkbox => checkbox.checked = false); |
| 325 | + }); |
| 326 | + |
| 327 | + |
| 328 | + // --- Generate Button --- |
| 329 | + generateButton.addEventListener('click', () => { |
| 330 | + const selectedSlugs = []; |
| 331 | + const checkboxes = modeListDiv.querySelectorAll('input[type="checkbox"]:checked'); |
| 332 | + |
| 333 | + checkboxes.forEach(checkbox => { |
| 334 | + selectedSlugs.push(checkbox.value); |
| 335 | + }); |
| 336 | + |
| 337 | + // Filter the original allModesData based on selected slugs |
| 338 | + // Maintain original order if needed, or keep sorted order |
| 339 | + // Let's filter the already sorted allModesData |
| 340 | + const filteredModes = allModesData.filter(mode => mode && mode.slug && selectedSlugs.includes(mode.slug)); |
| 341 | + |
| 342 | + // Create the output structure |
| 343 | + const outputJson = { |
| 344 | + customModes: filteredModes |
| 345 | + }; |
| 346 | + |
| 347 | + // Display the filtered JSON beautifully formatted |
| 348 | + jsonOutput.value = JSON.stringify(outputJson, null, 2); // Pretty print with 2 spaces |
| 349 | + outputContainer.style.display = 'block'; // Show output area |
| 350 | + errorDisplay.textContent = ''; // Clear any previous errors |
| 351 | + }); |
| 352 | + |
| 353 | + // Initial state |
| 354 | + selectAllButton.disabled = true; |
| 355 | + selectNoneButton.disabled = true; |
| 356 | + |
| 357 | + |
| 358 | +</script> |
| 359 | + |
| 360 | +</body> |
14 | 361 | </html> |
0 commit comments