gemma-4-31b-heretic-fp8 / settings_server.py
dev-willbird1936's picture
Extend chat context and response limits
b953c04 verified
Raw
History Blame Contribute Delete
5.89 kB
from __future__ import annotations
import json
import threading
import webbrowser
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
HOST = "127.0.0.1"
PORT = 8765
SETTINGS_PATH = Path(__file__).with_name("local_settings.json")
PAGE = """<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Gemma Heretic Demo Settings</title>
<style>
:root { color-scheme: light dark; font: 16px/1.45 system-ui, sans-serif; }
body { margin: 0; background: #16111f; color: #f5efff; }
main { width: min(760px, calc(100% - 2rem)); margin: 3rem auto; }
form { display: grid; gap: 1rem; padding: 1.5rem; border: 1px solid #604b78;
border-radius: 18px; background: #21172d; box-shadow: 0 18px 60px #0005; }
label { display: grid; gap: .4rem; font-weight: 650; }
input, textarea { box-sizing: border-box; width: 100%; padding: .75rem;
border: 1px solid #765c92; border-radius: 10px; background: #160f20; color: inherit; }
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 1rem; }
button { padding: .8rem 1.2rem; border: 0; border-radius: 10px; cursor: pointer;
background: #9d63da; color: white; font-weight: 750; }
#status { min-height: 1.5rem; color: #cdb0ec; }
</style>
</head>
<body>
<main>
<h1>Gemma Heretic Demo Settings</h1>
<p>These defaults apply the next time the local app starts.</p>
<form id="settings">
<label>Model ID<input name="model_id" required></label>
<label>System prompt<textarea name="system_prompt" rows="3"></textarea></label>
<div class="grid">
<label>Max new tokens<input name="max_new_tokens" type="number" min="128" max="1536" step="128"></label>
<label>Temperature<input name="temperature" type="number" min="0" max="1.5" step="0.05"></label>
<label>Top-p<input name="top_p" type="number" min="0.1" max="1" step="0.05"></label>
<label>Top-k<input name="top_k" type="number" min="1" max="128" step="1"></label>
<label>Repetition penalty<input name="repetition_penalty" type="number" min="1" max="1.3" step="0.01"></label>
<label>Max input tokens<input name="max_input_tokens" type="number" min="1024" max="32768" step="1024"></label>
<label>History messages<input name="history_messages" type="number" min="1" max="32" step="1"></label>
</div>
<label><span><input name="thinking" type="checkbox" style="width:auto"> Enable thinking by default</span></label>
<button type="submit">Save settings</button>
<div id="status" role="status"></div>
</form>
</main>
<script>
const form = document.querySelector("#settings");
const status = document.querySelector("#status");
const numeric = ["max_new_tokens","temperature","top_p","top_k","repetition_penalty","max_input_tokens","history_messages"];
fetch("/api/settings").then(r => r.json()).then(data => {
Object.entries(data).forEach(([key, value]) => {
const field = form.elements.namedItem(key);
if (!field) return;
if (field.type === "checkbox") field.checked = Boolean(value);
else field.value = value;
});
});
form.addEventListener("submit", async event => {
event.preventDefault();
const data = Object.fromEntries(new FormData(form));
numeric.forEach(key => data[key] = Number(data[key]));
data.thinking = form.elements.namedItem("thinking").checked;
const response = await fetch("/api/settings", {
method: "POST", headers: {"Content-Type":"application/json"}, body: JSON.stringify(data)
});
status.textContent = response.ok ? "Saved. Restart run.bat to apply changes." : "Save failed.";
});
</script>
</body>
</html>
"""
class SettingsHandler(BaseHTTPRequestHandler):
def _send(self, status: int, content_type: str, body: bytes) -> None:
self.send_response(status)
self.send_header("Content-Type", content_type)
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def do_GET(self) -> None:
if self.path == "/":
self._send(200, "text/html; charset=utf-8", PAGE.encode())
return
if self.path == "/api/settings":
self._send(200, "application/json", SETTINGS_PATH.read_bytes())
return
self._send(404, "text/plain", b"Not found")
def do_POST(self) -> None:
if self.path != "/api/settings":
self._send(404, "text/plain", b"Not found")
return
length = int(self.headers.get("Content-Length", "0"))
try:
data = json.loads(self.rfile.read(length))
required = {
"model_id",
"system_prompt",
"thinking",
"max_new_tokens",
"temperature",
"top_p",
"top_k",
"repetition_penalty",
"max_input_tokens",
"history_messages",
}
if set(data) != required:
raise ValueError("Unexpected settings fields.")
SETTINGS_PATH.write_text(
json.dumps(data, indent=2, ensure_ascii=False) + "\n",
encoding="utf-8",
)
except (ValueError, TypeError, json.JSONDecodeError):
self._send(400, "application/json", b'{"ok":false}')
return
self._send(200, "application/json", b'{"ok":true}')
def log_message(self, format: str, *args: object) -> None:
return
if __name__ == "__main__":
server = ThreadingHTTPServer((HOST, PORT), SettingsHandler)
url = f"http://{HOST}:{PORT}"
threading.Timer(0.4, webbrowser.open, args=(url,)).start()
print(f"Settings page: {url}")
print("Press Ctrl+C to stop.")
server.serve_forever()