feat: multi-gateway profile support, provider management overhaul, and model settings tab

- Profile-aware proxy: inject API key from profile-specific .env, route requests via X-Hermes-Profile header
- Remove auth.json dependency: built-in providers use .env, custom providers use config.yaml
- Add allProviders field to available-models response with all hardcoded provider catalogs
- Add Models tab in Settings for editing provider API keys (built-in → .env, custom → config.yaml)
- Add PUT /api/config/providers/:poolKey for updating provider credentials
- ProviderFormModal uses backend allProviders for preset dropdown
- Gateway log format support: parse both agent and gateway log formats
- Add webui server.log to log viewer with log rotation at 3MB
- Fix provider delete loading state and OAuth provider cleanup
- Setup script: require Node.js 23+, auto-upgrade if version too low

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
ekko
2026-04-19 20:59:25 +08:00
parent e7e4c386c3
commit 562261d13f
19 changed files with 635 additions and 276 deletions
+15 -1
View File
@@ -2,7 +2,7 @@
import { spawn, execSync } from 'child_process'
import { resolve, dirname, join } from 'path'
import { fileURLToPath } from 'url'
import { readFileSync, writeFileSync, unlinkSync, mkdirSync, openSync, chmodSync } from 'fs'
import { readFileSync, writeFileSync, unlinkSync, mkdirSync, openSync, chmodSync, statSync } from 'fs'
import { randomBytes } from 'crypto'
import { homedir } from 'os'
@@ -118,6 +118,20 @@ function startDaemon(port) {
ensureNativeModules()
const token = ensureToken()
// Rotate log if over 3MB — keep last 2000 lines
const MAX_LOG_SIZE = 3 * 1024 * 1024
const MAX_LOG_LINES = 2000
try {
const stat = statSync(LOG_FILE)
if (stat.size > MAX_LOG_SIZE) {
const content = readFileSync(LOG_FILE, 'utf-8')
const lines = content.split('\n')
const kept = lines.slice(-MAX_LOG_LINES)
writeFileSync(LOG_FILE, kept.join('\n'), 'utf-8')
console.log(` ↻ Log rotated (${(stat.size / 1024 / 1024).toFixed(1)}MB → ${kept.length} lines)`)
}
} catch { }
const logStream = openSync(LOG_FILE, 'a')
const child = spawn(process.execPath, [serverEntry], {
detached: true,