Add page LLM configuration settings
This commit is contained in:
parent
38e34219d3
commit
8439fdc7ab
67
app.py
67
app.py
@ -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__":
|
||||
|
163
static/main.js
163
static/main.js
@ -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
|
||||
}
|
||||
});
|
||||
});
|
||||
|
183
static/style.css
183
static/style.css
@ -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;
|
||||
}
|
||||
|
@ -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>
|
||||
|
Loading…
x
Reference in New Issue
Block a user