Add page LLM configuration settings

This commit is contained in:
Feige-cn 2025-03-18 00:54:47 +08:00
parent 38e34219d3
commit 8439fdc7ab
4 changed files with 468 additions and 5 deletions

67
app.py
View File

@ -253,6 +253,61 @@ async def get_task(task_id: str):
return task_manager.tasks[task_id]
@app.get("/config/status")
async def check_config_status():
config_path = Path(__file__).parent / "config" / "config.toml"
example_config_path = Path(__file__).parent / "config" / "config.example.toml"
if config_path.exists():
return {"status": "exists"}
elif example_config_path.exists():
try:
with open(example_config_path, "rb") as f:
example_config = tomllib.load(f)
return {"status": "missing", "example_config": example_config}
except Exception as e:
return {"status": "error", "message": str(e)}
else:
return {"status": "no_example"}
@app.post("/config/save")
async def save_config(config_data: dict = Body(...)):
try:
config_dir = Path(__file__).parent / "config"
config_dir.mkdir(exist_ok=True)
config_path = config_dir / "config.toml"
toml_content = ""
if "llm" in config_data:
toml_content += "# Global LLM configuration\n[llm]\n"
llm_config = config_data["llm"]
for key, value in llm_config.items():
if key != "vision":
if isinstance(value, str):
toml_content += f'{key} = "{value}"\n'
else:
toml_content += f"{key} = {value}\n"
if "server" in config_data:
toml_content += "\n# Server configuration\n[server]\n"
server_config = config_data["server"]
for key, value in server_config.items():
if isinstance(value, str):
toml_content += f'{key} = "{value}"\n'
else:
toml_content += f"{key} = {value}\n"
with open(config_path, "w", encoding="utf-8") as f:
f.write(toml_content)
return {"status": "success"}
except Exception as e:
return {"status": "error", "message": str(e)}
@app.exception_handler(Exception)
async def generic_exception_handler(request: Request, exc: Exception):
return JSONResponse(
@ -268,18 +323,20 @@ def load_config():
try:
config_path = Path(__file__).parent / "config" / "config.toml"
if not config_path.exists():
return {"host": "localhost", "port": 5172}
with open(config_path, "rb") as f:
config = tomllib.load(f)
return {"host": config["server"]["host"], "port": config["server"]["port"]}
except FileNotFoundError:
raise RuntimeError(
"Configuration file not found, please check if config/fig.toml exists"
)
return {"host": "localhost", "port": 5172}
except KeyError as e:
raise RuntimeError(
f"The configuration file is missing necessary fields: {str(e)}"
print(
f"The configuration file is missing necessary fields: {str(e)}, use default configuration"
)
return {"host": "localhost", "port": 5172}
if __name__ == "__main__":

View File

@ -1,5 +1,163 @@
let currentEventSource = null;
let exampleApiKey = '';
function checkConfigStatus() {
fetch('/config/status')
.then(response => response.json())
.then(data => {
if (data.status === 'missing') {
showConfigModal(data.example_config);
} else if (data.status === 'no_example') {
alert('Error: Missing configuration example file! Please ensure that the config/config.example.toml file exists.');
} else if (data.status === 'error') {
alert('Configuration check error:' + data.message);
}
})
.catch(error => {
console.error('Configuration check failed:', error);
});
}
// Display configuration pop-up and fill in sample configurations
function showConfigModal(exampleConfig) {
const configModal = document.getElementById('config-modal');
if (!configModal) return;
configModal.classList.add('active');
if (exampleConfig) {
fillConfigForm(exampleConfig);
}
const saveButton = document.getElementById('save-config-btn');
if (saveButton) {
saveButton.onclick = saveConfig;
}
}
// Use example configuration to fill in the form
function fillConfigForm(exampleConfig) {
if (exampleConfig.llm) {
const llm = exampleConfig.llm;
setInputValue('llm-model', llm.model);
setInputValue('llm-base-url', llm.base_url);
setInputValue('llm-api-key', llm.api_key);
// Save example API key for later verification
exampleApiKey = llm.api_key || '';
setInputValue('llm-max-tokens', llm.max_tokens);
setInputValue('llm-temperature', llm.temperature);
}
if (exampleConfig.server) {
setInputValue('server-host', exampleConfig.server.host);
setInputValue('server-port', exampleConfig.server.port);
}
}
function setInputValue(id, value) {
const input = document.getElementById(id);
if (input && value !== undefined) {
input.value = value;
}
}
function saveConfig() {
const configData = collectFormData();
const requiredFields = [
{ id: 'llm-model', name: 'Model Name' },
{ id: 'llm-base-url', name: 'API Base URL' },
{ id: 'llm-api-key', name: 'API Key' },
{ id: 'server-host', name: 'Server Host' },
{ id: 'server-port', name: 'Server Port' }
];
let missingFields = [];
requiredFields.forEach(field => {
if (!document.getElementById(field.id).value.trim()) {
missingFields.push(field.name);
}
});
if (missingFields.length > 0) {
document.getElementById('config-error').textContent =
`Please fill in the necessary configuration information: ${missingFields.join(', ')}`;
return;
}
// Check if the API key is the same as the example configuration
const apiKey = document.getElementById('llm-api-key').value.trim();
if (apiKey === exampleApiKey && exampleApiKey.includes('sk-')) {
document.getElementById('config-error').textContent =
`Please enter your own API key`;
// Highlight the API key input box
document.getElementById('llm-api-key').parentElement.classList.add('error');
return;
} else {
document.getElementById('llm-api-key').parentElement.classList.remove('error');
}
// Send configuration to server
fetch('/config/save', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(configData)
})
.then(response => response.json())
.then(data => {
if (data.status === 'success') {
// Close pop-up
document.getElementById('config-modal').classList.remove('active');
// Show success message
alert('Configuration saved successfully! The application will use the new configuration on next startup.');
// Refresh page
window.location.reload();
} else {
document.getElementById('config-error').textContent =
`Save failed: ${data.message}`;
}
})
.catch(error => {
document.getElementById('config-error').textContent =
`Request error: ${error.message}`;
});
}
// Collect form data
function collectFormData() {
const configData = {
llm: {
model: document.getElementById('llm-model').value,
base_url: document.getElementById('llm-base-url').value,
api_key: document.getElementById('llm-api-key').value
},
server: {
host: document.getElementById('server-host').value,
port: parseInt(document.getElementById('server-port').value || '5172')
}
};
// Add optional fields
const maxTokens = document.getElementById('llm-max-tokens').value;
if (maxTokens) {
configData.llm.max_tokens = parseInt(maxTokens);
}
const temperature = document.getElementById('llm-temperature').value;
if (temperature) {
configData.llm.temperature = parseFloat(temperature);
}
return configData;
}
function createTask() {
const promptInput = document.getElementById('prompt-input');
const prompt = promptInput.value.trim();
@ -495,6 +653,9 @@ print("Hello from Python Simulated environment!")
}
document.addEventListener('DOMContentLoaded', () => {
// Check configuration status
checkConfigStatus();
loadHistory();
document.getElementById('prompt-input').addEventListener('keydown', (e) => {
@ -535,6 +696,8 @@ document.addEventListener('DOMContentLoaded', () => {
if (pythonModal && pythonModal.classList.contains('active')) {
pythonModal.classList.remove('active');
}
// Do not close the configuration pop-up, because the configuration is required
}
});
});

View File

@ -520,3 +520,186 @@ pre {
white-space: pre-wrap;
word-break: break-word;
}
.config-modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 2000;
justify-content: center;
align-items: center;
}
.config-modal.active {
display: flex;
}
.config-modal-content {
background-color: white;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
width: 80%;
max-width: 800px;
max-height: 90vh;
overflow-y: auto;
display: flex;
flex-direction: column;
}
.config-modal-header {
padding: 20px;
border-bottom: 1px solid var(--border-color);
background-color: var(--info-color);
color: white;
border-top-left-radius: 8px;
border-top-right-radius: 8px;
}
.config-modal-header h2 {
margin-bottom: 10px;
font-size: 1.8rem;
}
.config-modal-body {
padding: 20px;
overflow-y: auto;
max-height: calc(90vh - 140px);
}
.config-modal-footer {
padding: 20px;
border-top: 1px solid var(--border-color);
display: flex;
justify-content: space-between;
align-items: center;
}
.config-section {
margin-bottom: 25px;
padding-bottom: 20px;
border-bottom: 1px solid var(--border-color);
}
.config-section:last-child {
border-bottom: none;
}
.config-section h3 {
margin-bottom: 15px;
color: var(--info-color);
}
.form-group {
margin-bottom: 15px;
transition: all 0.3s ease;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: bold;
color: var(--text-color);
}
.form-group input {
width: 100%;
padding: 10px;
border: 1px solid var(--border-color);
border-radius: 4px;
font-size: 0.9rem;
transition: border-color 0.3s ease, box-shadow 0.3s ease;
}
.form-group input:focus {
outline: none;
border-color: var(--info-color);
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
}
.config-actions {
display: flex;
gap: 10px;
}
.primary-btn {
background-color: var(--info-color);
transition: background-color 0.3s ease;
}
.primary-btn:hover {
background-color: #0069d9;
}
.secondary-btn {
background-color: var(--text-light);
}
.config-error {
color: var(--error-color);
margin-right: 15px;
font-weight: bold;
padding: 8px 0;
transition: all 0.3s ease;
}
.form-group.error input {
border-color: var(--error-color);
box-shadow: 0 0 0 2px rgba(220, 53, 69, 0.25);
background-color: rgba(220, 53, 69, 0.05);
}
.form-group.error label {
color: var(--error-color);
}
.form-group .error-message {
color: var(--error-color);
font-size: 0.8rem;
margin-top: 5px;
display: block;
animation: errorAppear 0.3s ease;
}
@keyframes errorAppear {
from {
opacity: 0;
transform: translateY(-5px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.note-box {
background-color: #fff8e1;
border-left: 4px solid #ffc107;
padding: 15px;
margin-bottom: 20px;
border-radius: 4px;
}
.note-box p {
margin: 0 0 8px 0;
color: #856404;
}
.note-box p:last-child {
margin-bottom: 0;
}
.field-help {
font-size: 0.8rem;
color: #666;
margin-top: 4px;
display: block;
}
.required-mark {
color: var(--error-color);
font-weight: bold;
}

View File

@ -34,6 +34,66 @@
</div>
</div>
<div id="config-modal" class="config-modal">
<div class="config-modal-content">
<div class="config-modal-header">
<h2>System Configuration</h2>
<p>Please fill in the necessary configuration information to continue using the system</p>
</div>
<div class="config-modal-body">
<div class="note-box">
<p>⚠️ Please ensure that the following configuration information is correct.</p>
<p>If you do not have an API key, please obtain one from the corresponding AI service provider.</p>
</div>
<div class="config-section">
<h3>LLM Configuration</h3>
<div class="form-group">
<label for="llm-model">Model Name <span class="required-mark">*</span></label>
<input type="text" id="llm-model" name="llm.model" placeholder="例如: claude-3-5-sonnet">
</div>
<div class="form-group">
<label for="llm-base-url">API Base URL <span class="required-mark">*</span></label>
<input type="text" id="llm-base-url" name="llm.base_url" placeholder="例如: https://api.openai.com/v1">
</div>
<div class="form-group">
<label for="llm-api-key">API Key <span class="required-mark">*</span></label>
<input type="password" id="llm-api-key" name="llm.api_key" placeholder="Your API key, for example: sk-...">
<span class="field-help">Must be your own valid API key, not the placeholder in the example</span>
</div>
<div class="form-group">
<label for="llm-max-tokens">Max Tokens</label>
<input type="number" id="llm-max-tokens" name="llm.max_tokens" placeholder="例如: 4096">
</div>
<div class="form-group">
<label for="llm-temperature">Temperature</label>
<input type="number" id="llm-temperature" name="llm.temperature" step="0.1" placeholder="例如: 0.0">
</div>
</div>
<div class="config-section">
<h3>Server Configuration</h3>
<div class="form-group">
<label for="server-host">Host <span class="required-mark">*</span></label>
<input type="text" id="server-host" name="server.host" placeholder="例如: localhost">
</div>
<div class="form-group">
<label for="server-port">Port <span class="required-mark">*</span></label>
<input type="number" id="server-port" name="server.port" placeholder="例如: 5172">
</div>
</div>
</div>
<div class="config-modal-footer">
<p id="config-error" class="config-error"></p>
<div class="config-actions">
<button id="save-config-btn" class="primary-btn">Save Configuration</button>
</div>
</div>
</div>
</div>
<script src="/static/main.js"></script>
</body>
</html>