PrometheusGroup commited on
Commit
379a97e
·
verified ·
1 Parent(s): 1aad3b6

create a web page to work with this file:

Browse files

import React, { useState, useMemo } from 'react';
import { createRoot } from 'react-dom/client';

type ToolName = 'list_files' | 'read_file' | 'create_file' | 'write_file' | 'delete_file' | 'identify_log_errors';

interface Tool {
value: ToolName;
label: string;
description: string;
params: { name: string; type: 'text' | 'textarea'; label: string }[];
}

const TOOLS: Tool[] = [
{ value: 'list_files', label: 'List Files', description: 'Lists files and directories in a given path.', params: [{ name: 'path', type: 'text', label: 'Directory Path' }] },
{ value: 'read_file', label: 'Read File', description: 'Reads the content of a specific file.', params: [{ name: 'path', type: 'text', label: 'File Path' }] },
{ value: 'create_file', label: 'Create File', description: 'Creates a new file with optional content.', params: [{ name: 'path', type: 'text', label: 'New File Path' }, { name: 'content', type: 'textarea', label: 'Initial Content (optional)' }] },
{ value: 'write_file', label: 'Write to File', description: 'Writes content to a file, creating a backup first.', params: [{ name: 'path', type: 'text', label: 'File Path' }, { name: 'content', type: 'textarea', label: 'New Content' }] },
{ value: 'delete_file', label: 'Delete File', description: 'Deletes a specific file.', params: [{ name: 'path', type: 'text', label: 'File to Delete' }] },
{ value: 'identify_log_errors', label: 'Identify Log Errors', description: 'Scans a log file for common error messages.', params: [{ name: 'log_file_path', type: 'text', label: 'Log File Path' }] },
];

const App = () => {
const [selectedTool, setSelectedTool] = useState<ToolName>(TOOLS[0].value);
const [parameters, setParameters] = useState<Record<string, string>>({ path: 'C:\\temp' });
const [isLoading, setIsLoading] = useState(false);
const [response, setResponse] = useState<any>(null);
const [error, setError] = useState('');

const currentTool = useMemo(() => TOOLS.find(t => t.value === selectedTool)!, [selectedTool]);

const handleToolChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
const newToolName = e.target.value as ToolName;
setSelectedTool(newToolName);
// Reset parameters when tool changes
setParameters({});
setResponse(null);
setError('');
};

const handleParamChange = (name: string, value: string) => {
setParameters(prev => ({ ...prev, [name]: value }));
};

const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setIsLoading(true);
setResponse(null);
setError('');

const invocation_id = `mcp-ui-${Date.now()}`;
const endpoint = `http://127.0.0.1:8000/mcp/invoke/${selectedTool}`;

try {
const res = await fetch(endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
tool_name: selectedTool,
invocation_id,
parameters,
}),
});

const data = await res.json();
if (!res.ok || data.result?.status === 'error') {
throw new Error(data.result?.message || `Server responded with status: ${res.status}`);
}
setResponse(data);
} catch (err) {
setError(err instanceof Error ? err.message : 'An unknown error occurred.');
} finally {
setIsLoading(false);
}
};

return (
<div className="container">
<header>
<h1>MCP Toolkit Control Panel</h1>
<p>A client to interact with the Maintenance & Control Program toolkit server.</p>
</header>

<main>
<div className="card form-card">
<h2>Select a Tool</h2>
<form onSubmit={handleSubmit}>
<div className="form-group">
<label htmlFor="tool-select">Tool</label>
<select id="tool-select" value={selectedTool} onChange={handleToolChange}>
{TOOLS.map(tool => (
<option key={tool.value} value={tool.value}>{tool.label}</option>
))}
</select>
<p>{currentTool.description}</p>
</div>

{currentTool.params.map(param => (
<div className="form-group" key={param.name}>
<label htmlFor={param.name}>{param.label}</label>
{param.type === 'textarea' ? (
<textarea
id={param.name}
value={parameters[param.name] || ''}
onChange={(e) => handleParamChange(param.name, e.target.value)}
required
/>
) : (
<input
id={param.name}
type="text"
value={parameters[param.name] || ''}
onChange={(e) => handleParamChange(param.name, e.target.value)}
required
/>
)}
</div>
))}

<button type="submit" disabled={isLoading}>
{isLoading ? 'Running...' : `Run: ${currentTool.label}`}
</button>
</form>
</div>

{isLoading && (
<div className="card status-card">
<div className="loader">
<span></span>
<span></span>
<span></span>
</div>
<p>Communicating with MCP Server...</p>
</div>
)}

{error && (
<div className="card result-card error">
<h2>Error</h2>
<pre>{error}</pre>
</div>
)}

{response && (
<div className="card result-card success">
<h2>Result for Invocation: {response.invocation_id}</h2>
<pre>{JSON.stringify(response.result, null, 2)}</pre>
</div>
)}
</main>
</div>
);
};

const container = document.getElementById('root');
const root = createRoot(container!);
root.render(<App />);

Files changed (6) hide show
  1. README.md +8 -5
  2. components/footer.js +57 -0
  3. components/navbar.js +76 -0
  4. index.html +101 -19
  5. script.js +185 -0
  6. style.css +26 -18
README.md CHANGED
@@ -1,10 +1,13 @@
1
  ---
2
- title: Filemaster Control Panel
3
- emoji: 👁
4
- colorFrom: red
5
- colorTo: red
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
1
  ---
2
+ title: FileMaster Control Panel 🛠️
3
+ colorFrom: purple
4
+ colorTo: yellow
5
+ emoji: 🐳
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite-v3
10
  ---
11
 
12
+ # Welcome to your new DeepSite project!
13
+ This project was created with [DeepSite](https://huggingface.co/deepsite).
components/footer.js ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class CustomFooter extends HTMLElement {
2
+ connectedCallback() {
3
+ this.attachShadow({ mode: 'open' });
4
+ this.shadowRoot.innerHTML = `
5
+ <style>
6
+ footer {
7
+ background: #1e293b;
8
+ color: white;
9
+ padding: 2rem;
10
+ text-align: center;
11
+ margin-top: auto;
12
+ }
13
+ .footer-content {
14
+ max-width: 1200px;
15
+ margin: 0 auto;
16
+ }
17
+ .footer-links {
18
+ display: flex;
19
+ justify-content: center;
20
+ gap: 2rem;
21
+ margin-bottom: 1.5rem;
22
+ }
23
+ .footer-links a {
24
+ color: #93c5fd;
25
+ text-decoration: none;
26
+ transition: color 0.2s;
27
+ }
28
+ .footer-links a:hover {
29
+ color: white;
30
+ }
31
+ .copyright {
32
+ font-size: 0.875rem;
33
+ color: #9ca3af;
34
+ }
35
+ @media (max-width: 640px) {
36
+ .footer-links {
37
+ flex-direction: column;
38
+ gap: 1rem;
39
+ }
40
+ }
41
+ </style>
42
+ <footer>
43
+ <div class="footer-content">
44
+ <div class="footer-links">
45
+ <a href="#"><i data-feather="github"></i> GitHub</a>
46
+ <a href="#"><i data-feather="file-text"></i> Documentation</a>
47
+ <a href="#"><i data-feather="mail"></i> Contact</a>
48
+ </div>
49
+ <div class="copyright">
50
+ &copy; ${new Date().getFullYear()} FileMaster Control Panel. All rights reserved.
51
+ </div>
52
+ </div>
53
+ </footer>
54
+ `;
55
+ }
56
+ }
57
+ customElements.define('custom-footer', CustomFooter);
components/navbar.js ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class CustomNavbar extends HTMLElement {
2
+ connectedCallback() {
3
+ this.attachShadow({ mode: 'open' });
4
+ this.shadowRoot.innerHTML = `
5
+ <style>
6
+ nav {
7
+ background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%);
8
+ padding: 1rem 2rem;
9
+ display: flex;
10
+ justify-content: space-between;
11
+ align-items: center;
12
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
13
+ position: relative;
14
+ z-index: 50;
15
+ }
16
+ .logo {
17
+ color: white;
18
+ font-weight: bold;
19
+ font-size: 1.5rem;
20
+ display: flex;
21
+ align-items: center;
22
+ gap: 0.5rem;
23
+ }
24
+ .logo-icon {
25
+ width: 24px;
26
+ height: 24px;
27
+ }
28
+ ul {
29
+ display: flex;
30
+ gap: 1.5rem;
31
+ list-style: none;
32
+ margin: 0;
33
+ padding: 0;
34
+ }
35
+ a {
36
+ color: white;
37
+ text-decoration: none;
38
+ font-weight: 500;
39
+ padding: 0.5rem 1rem;
40
+ border-radius: 0.375rem;
41
+ transition: background-color 0.2s;
42
+ }
43
+ a:hover {
44
+ background-color: rgba(255, 255, 255, 0.1);
45
+ }
46
+ @media (max-width: 640px) {
47
+ nav {
48
+ flex-direction: column;
49
+ padding: 1rem;
50
+ }
51
+ ul {
52
+ margin-top: 1rem;
53
+ width: 100%;
54
+ justify-content: space-around;
55
+ }
56
+ a {
57
+ padding: 0.5rem;
58
+ }
59
+ }
60
+ </style>
61
+ <nav>
62
+ <div class="logo">
63
+ <svg class="logo-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
64
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
65
+ </svg>
66
+ <span>FileMaster</span>
67
+ </div>
68
+ <ul>
69
+ <li><a href="#"><i data-feather="home"></i> Home</a></li>
70
+ <li><a href="#"><i data-feather="help-circle"></i> Documentation</a></li>
71
+ </ul>
72
+ </nav>
73
+ `;
74
+ }
75
+ }
76
+ customElements.define('custom-navbar', CustomNavbar);
index.html CHANGED
@@ -1,19 +1,101 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>FileMaster Control Panel</title>
7
+ <link rel="stylesheet" href="style.css">
8
+ <script src="https://cdn.tailwindcss.com"></script>
9
+ <script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
10
+ <script src="https://unpkg.com/feather-icons"></script>
11
+ <script src="https://cdn.jsdelivr.net/npm/vanta@latest/dist/vanta.globe.min.js"></script>
12
+ <script src="components/navbar.js"></script>
13
+ <script src="components/footer.js"></script>
14
+ </head>
15
+ <body class="bg-gray-100 min-h-screen flex flex-col">
16
+ <custom-navbar></custom-navbar>
17
+
18
+ <main class="flex-grow container mx-auto px-4 py-8">
19
+ <div id="vanta-bg" class="absolute top-0 left-0 w-full h-full z-0"></div>
20
+
21
+ <div class="relative z-10 max-w-4xl mx-auto">
22
+ <header class="text-center mb-8">
23
+ <h1 class="text-4xl font-bold text-gray-800 mb-2">FileMaster Control Panel</h1>
24
+ <p class="text-xl text-gray-600">A client to interact with the File Operations toolkit server</p>
25
+ </header>
26
+
27
+ <div class="bg-white rounded-xl shadow-lg p-6 mb-8">
28
+ <div class="mb-6">
29
+ <label for="tool-select" class="block text-lg font-medium text-gray-700 mb-2">Select a Tool</label>
30
+ <select id="tool-select" class="w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
31
+ <option value="list_files">List Files</option>
32
+ <option value="read_file">Read File</option>
33
+ <option value="create_file">Create File</option>
34
+ <option value="write_file">Write to File</option>
35
+ <option value="delete_file">Delete File</option>
36
+ <option value="identify_log_errors">Identify Log Errors</option>
37
+ </select>
38
+ <p id="tool-description" class="mt-2 text-gray-600">Lists files and directories in a given path.</p>
39
+ </div>
40
+
41
+ <form id="tool-form">
42
+ <div id="parameters-container">
43
+ <div class="mb-4">
44
+ <label for="path" class="block text-sm font-medium text-gray-700 mb-1">Directory Path</label>
45
+ <input type="text" id="path" name="path" class="w-full p-2 border border-gray-300 rounded-md" value="C:\temp" required>
46
+ </div>
47
+ </div>
48
+
49
+ <button type="submit" class="w-full bg-blue-600 hover:bg-blue-700 text-white font-bold py-3 px-4 rounded-lg transition duration-200 flex items-center justify-center">
50
+ <span id="submit-text">Run: List Files</span>
51
+ <div id="loader" class="hidden ml-2">
52
+ <div class="inline-block animate-spin rounded-full h-5 w-5 border-b-2 border-white"></div>
53
+ </div>
54
+ </button>
55
+ </form>
56
+ </div>
57
+
58
+ <div id="status-card" class="hidden bg-blue-50 rounded-xl shadow-lg p-6 mb-8 text-center">
59
+ <div class="flex justify-center mb-4">
60
+ <div class="animate-pulse flex space-x-2">
61
+ <div class="h-3 w-3 bg-blue-500 rounded-full"></div>
62
+ <div class="h-3 w-3 bg-blue-500 rounded-full"></div>
63
+ <div class="h-3 w-3 bg-blue-500 rounded-full"></div>
64
+ </div>
65
+ </div>
66
+ <p class="text-blue-700">Communicating with FileMaster Server...</p>
67
+ </div>
68
+
69
+ <div id="error-card" class="hidden bg-red-50 rounded-xl shadow-lg p-6 mb-8">
70
+ <h2 class="text-xl font-bold text-red-700 mb-2">Error</h2>
71
+ <pre id="error-message" class="text-red-600 whitespace-pre-wrap"></pre>
72
+ </div>
73
+
74
+ <div id="result-card" class="hidden bg-green-50 rounded-xl shadow-lg p-6">
75
+ <h2 id="result-title" class="text-xl font-bold text-green-700 mb-2"></h2>
76
+ <pre id="result-content" class="text-gray-800 whitespace-pre-wrap"></pre>
77
+ </div>
78
+ </div>
79
+ </main>
80
+
81
+ <custom-footer></custom-footer>
82
+
83
+ <script src="script.js"></script>
84
+ <script>
85
+ feather.replace();
86
+ VANTA.GLOBE({
87
+ el: "#vanta-bg",
88
+ mouseControls: true,
89
+ touchControls: true,
90
+ gyroControls: false,
91
+ minHeight: 200.00,
92
+ minWidth: 200.00,
93
+ scale: 1.00,
94
+ scaleMobile: 1.00,
95
+ color: 0x3b82f6,
96
+ backgroundColor: 0xf1f5f9
97
+ });
98
+ </script>
99
+ <script src="https://huggingface.co/deepsite/deepsite-badge.js"></script>
100
+ </body>
101
+ </html>
script.js ADDED
@@ -0,0 +1,185 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Tool definitions
2
+ const TOOLS = [
3
+ {
4
+ value: 'list_files',
5
+ label: 'List Files',
6
+ description: 'Lists files and directories in a given path.',
7
+ params: [{ name: 'path', type: 'text', label: 'Directory Path' }]
8
+ },
9
+ {
10
+ value: 'read_file',
11
+ label: 'Read File',
12
+ description: 'Reads the content of a specific file.',
13
+ params: [{ name: 'path', type: 'text', label: 'File Path' }]
14
+ },
15
+ {
16
+ value: 'create_file',
17
+ label: 'Create File',
18
+ description: 'Creates a new file with optional content.',
19
+ params: [
20
+ { name: 'path', type: 'text', label: 'New File Path' },
21
+ { name: 'content', type: 'textarea', label: 'Initial Content (optional)' }
22
+ ]
23
+ },
24
+ {
25
+ value: 'write_file',
26
+ label: 'Write to File',
27
+ description: 'Writes content to a file, creating a backup first.',
28
+ params: [
29
+ { name: 'path', type: 'text', label: 'File Path' },
30
+ { name: 'content', type: 'textarea', label: 'New Content' }
31
+ ]
32
+ },
33
+ {
34
+ value: 'delete_file',
35
+ label: 'Delete File',
36
+ description: 'Deletes a specific file.',
37
+ params: [{ name: 'path', type: 'text', label: 'File to Delete' }]
38
+ },
39
+ {
40
+ value: 'identify_log_errors',
41
+ label: 'Identify Log Errors',
42
+ description: 'Scans a log file for common error messages.',
43
+ params: [{ name: 'log_file_path', type: 'text', label: 'Log File Path' }]
44
+ }
45
+ ];
46
+
47
+ // DOM elements
48
+ const toolSelect = document.getElementById('tool-select');
49
+ const toolDescription = document.getElementById('tool-description');
50
+ const parametersContainer = document.getElementById('parameters-container');
51
+ const toolForm = document.getElementById('tool-form');
52
+ const submitText = document.getElementById('submit-text');
53
+ const loader = document.getElementById('loader');
54
+ const statusCard = document.getElementById('status-card');
55
+ const errorCard = document.getElementById('error-card');
56
+ const errorMessage = document.getElementById('error-message');
57
+ const resultCard = document.getElementById('result-card');
58
+ const resultTitle = document.getElementById('result-title');
59
+ const resultContent = document.getElementById('result-content');
60
+
61
+ // Current tool state
62
+ let currentTool = TOOLS[0];
63
+ let parameters = { path: 'C:\\temp' };
64
+
65
+ // Initialize the form
66
+ function initializeForm() {
67
+ // Set initial tool description
68
+ toolDescription.textContent = currentTool.description;
69
+
70
+ // Set initial submit button text
71
+ submitText.textContent = `Run: ${currentTool.label}`;
72
+
73
+ // Build parameters UI
74
+ buildParametersUI();
75
+ }
76
+
77
+ // Build parameters UI based on current tool
78
+ function buildParametersUI() {
79
+ parametersContainer.innerHTML = '';
80
+
81
+ currentTool.params.forEach(param => {
82
+ const div = document.createElement('div');
83
+ div.className = 'mb-4';
84
+
85
+ const label = document.createElement('label');
86
+ label.className = 'block text-sm font-medium text-gray-700 mb-1';
87
+ label.htmlFor = param.name;
88
+ label.textContent = param.label;
89
+
90
+ let input;
91
+
92
+ if (param.type === 'textarea') {
93
+ input = document.createElement('textarea');
94
+ input.className = 'w-full p-2 border border-gray-300 rounded-md';
95
+ input.id = param.name;
96
+ input.name = param.name;
97
+ input.value = parameters[param.name] || '';
98
+ } else {
99
+ input = document.createElement('input');
100
+ input.type = 'text';
101
+ input.className = 'w-full p-2 border border-gray-300 rounded-md';
102
+ input.id = param.name;
103
+ input.name = param.name;
104
+ input.value = parameters[param.name] || '';
105
+ }
106
+
107
+ input.required = true;
108
+
109
+ div.appendChild(label);
110
+ div.appendChild(input);
111
+ parametersContainer.appendChild(div);
112
+
113
+ // Add event listener to update parameters state
114
+ input.addEventListener('input', (e) => {
115
+ parameters[param.name] = e.target.value;
116
+ });
117
+ });
118
+ }
119
+
120
+ // Handle tool selection change
121
+ toolSelect.addEventListener('change', (e) => {
122
+ const toolValue = e.target.value;
123
+ currentTool = TOOLS.find(tool => tool.value === toolValue);
124
+
125
+ // Update UI
126
+ toolDescription.textContent = currentTool.description;
127
+ submitText.textContent = `Run: ${currentTool.label}`;
128
+
129
+ // Reset parameters
130
+ parameters = {};
131
+
132
+ // Rebuild parameters UI
133
+ buildParametersUI();
134
+ });
135
+
136
+ // Handle form submission
137
+ toolForm.addEventListener('submit', async (e) => {
138
+ e.preventDefault();
139
+
140
+ // Show loading state
141
+ loader.classList.remove('hidden');
142
+ submitText.textContent = 'Running...';
143
+ statusCard.classList.remove('hidden');
144
+ errorCard.classList.add('hidden');
145
+ resultCard.classList.add('hidden');
146
+
147
+ const invocation_id = `filemaster-ui-${Date.now()}`;
148
+ const endpoint = `http://127.0.0.1:8000/mcp/invoke/${currentTool.value}`;
149
+
150
+ try {
151
+ const res = await fetch(endpoint, {
152
+ method: 'POST',
153
+ headers: { 'Content-Type': 'application/json' },
154
+ body: JSON.stringify({
155
+ tool_name: currentTool.value,
156
+ invocation_id,
157
+ parameters,
158
+ }),
159
+ });
160
+
161
+ const data = await res.json();
162
+
163
+ if (!res.ok || data.result?.status === 'error') {
164
+ throw new Error(data.result?.message || `Server responded with status: ${res.status}`);
165
+ }
166
+
167
+ // Show success result
168
+ resultTitle.textContent = `Result for Invocation: ${data.invocation_id}`;
169
+ resultContent.textContent = JSON.stringify(data.result, null, 2);
170
+ resultCard.classList.remove('hidden');
171
+
172
+ } catch (err) {
173
+ // Show error
174
+ errorMessage.textContent = err instanceof Error ? err.message : 'An unknown error occurred.';
175
+ errorCard.classList.remove('hidden');
176
+ } finally {
177
+ // Hide loading state
178
+ loader.classList.add('hidden');
179
+ submitText.textContent = `Run: ${currentTool.label}`;
180
+ statusCard.classList.add('hidden');
181
+ }
182
+ });
183
+
184
+ // Initialize the app
185
+ initializeForm();
style.css CHANGED
@@ -1,28 +1,36 @@
 
 
1
  body {
2
- padding: 2rem;
3
- font-family: -apple-system, BlinkMacSystemFont, "Arial", sans-serif;
4
  }
5
 
6
- h1 {
7
- font-size: 16px;
8
- margin-top: 0;
 
 
 
 
 
9
  }
10
 
11
- p {
12
- color: rgb(107, 114, 128);
13
- font-size: 15px;
14
- margin-bottom: 10px;
15
- margin-top: 5px;
16
  }
17
 
18
- .card {
19
- max-width: 620px;
20
- margin: 0 auto;
21
- padding: 16px;
22
- border: 1px solid lightgray;
23
- border-radius: 16px;
24
  }
25
 
26
- .card p:last-child {
27
- margin-bottom: 0;
 
28
  }
 
 
 
 
 
1
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
2
+
3
  body {
4
+ font-family: 'Inter', sans-serif;
 
5
  }
6
 
7
+ #vanta-bg {
8
+ position: fixed;
9
+ top: 0;
10
+ left: 0;
11
+ width: 100%;
12
+ height: 100%;
13
+ z-index: -1;
14
+ opacity: 0.2;
15
  }
16
 
17
+ pre {
18
+ background-color: rgba(0,0,0,0.05);
19
+ padding: 1rem;
20
+ border-radius: 0.5rem;
21
+ overflow-x: auto;
22
  }
23
 
24
+ textarea {
25
+ min-height: 150px;
26
+ resize: vertical;
 
 
 
27
  }
28
 
29
+ @keyframes spin {
30
+ from { transform: rotate(0deg); }
31
+ to { transform: rotate(360deg); }
32
  }
33
+
34
+ .animate-spin {
35
+ animation: spin 1s linear infinite;
36
+ }