Better enabled functionalities evidenced

parent bbbfbef8
......@@ -2,7 +2,7 @@
![CoderAI](CoderAI.gif)
An OpenAI-compatible API server with web administration dashboard, supporting multiple GPU backends: NVIDIA (CUDA), AMD (Vulkan), and Intel (Vulkan). Configuration-driven architecture with per-model settings and full multi-modal support.
An OpenAI-compatible API server to run models on your local GPU with web administration dashboard, supporting multiple GPU backends: NVIDIA (CUDA), AMD (Vulkan), and Intel (Vulkan). Configuration-driven architecture with per-model settings and full multi-modal support.
## Features
......
......@@ -357,6 +357,17 @@ class SessionManager:
self._save_auth_data(auth_data)
return True
def verify_token(self, token: str) -> bool:
"""Verify an API bearer token."""
if not token:
return False
auth_data = self._load_auth_data()
for t in auth_data.get("tokens", []):
stored = t.get("token", "")
if stored and hmac.compare_digest(stored, token):
return True
return False
def delete_user(self, username: str) -> bool:
"""Delete a user.
......
......@@ -73,6 +73,7 @@
font-size:9px; font-weight:700; letter-spacing:.04em; text-transform:uppercase;
background:var(--surface-3,#333); color:var(--text-2);
}
.t1btn.state-ready .tab-status, .t2btn.state-ready .tab-status { background:#0d2e18; color:#4ade80; }
.t1btn.state-partial .tab-status, .t2btn.state-partial .tab-status { background:#3a2510; color:#f0c060; }
.t1btn.state-unavailable .tab-status, .t2btn.state-unavailable .tab-status { background:var(--surface-2); color:var(--text-3); }
.t1btn.active .tab-status { background:rgba(255,255,255,.18); color:#fff; }
......@@ -173,6 +174,7 @@ a.dl { display:inline-block; margin-top:.4rem; }
font-size:10px; border-radius:999px; padding:.16rem .45rem;
background:var(--surface-2); color:var(--text-2); border:1px solid var(--border);
}
.cap-chip.ok { background:#0d2e18; color:#4ade80; border-color:transparent; }
.cap-chip.warn { background:#3a2510; color:#f0c060; border-color:transparent; }
.cap-chip.dim { opacity:.72; }
.cap-missing, .cap-note { font-size:12px; color:var(--text-2); }
......@@ -1494,7 +1496,7 @@ const SUB_PANEL_ALIAS = {
// Video models also enable all video sub-tabs
const VIDEO_EXTRA_SUBS = ['vid-ti2v', 'vid-dub', 'vid-v2v', 'vid-sub', 'vid-interp', 'vid-up', 'vid-faceswap', 'vid-outfit'];
const TAB_STATE = {
available: { label:'', className:'' },
available: { label:'Ready', className:'state-ready' },
partial: { label:'Partial', className:'state-partial' },
unavailable: { label:'Unavailable', className:'state-unavailable' },
};
......@@ -1680,7 +1682,7 @@ function evaluateCategoryState(cat, subStates, caps, type) {
function setTabVisualState(btn, state) {
if (!btn) return;
btn.classList.remove('state-partial', 'state-unavailable');
btn.classList.remove('state-ready', 'state-partial', 'state-unavailable');
const def = TAB_STATE[state] || TAB_STATE.unavailable;
if (def.className) btn.classList.add(def.className);
const badge = btn.querySelector('.tab-status');
......@@ -1736,8 +1738,8 @@ function renderCapabilityCard(sub) {
shell.classList.remove('state-partial', 'state-unavailable');
if (details.availability === 'partial') shell.classList.add('state-partial');
if (details.availability === 'unavailable') shell.classList.add('state-unavailable');
const availabilityLabel = details.availability === 'available' ? 'Available' : details.availability === 'partial' ? 'Partial' : 'Unavailable';
const availabilityClass = details.availability === 'available' ? '' : details.availability === 'partial' ? ' warn' : ' dim';
const availabilityLabel = details.availability === 'available' ? 'Ready' : details.availability === 'partial' ? 'Partial' : 'Unavailable';
const availabilityClass = details.availability === 'available' ? ' ok' : details.availability === 'partial' ? ' warn' : ' dim';
const missingBits = [];
if (details.missingRequired.length) missingBits.push(`<div class="cap-missing"><strong>Missing required:</strong> ${details.missingRequired.join(', ')}</div>`);
if (details.missingOptional.length) missingBits.push(`<div class="cap-missing"><strong>Limited without:</strong> ${details.missingOptional.join(', ')}</div>`);
......@@ -2138,15 +2140,18 @@ function buildDubPreviewData() {
}
function buildCodeSnippet(kind, preview) {
const origin = window.location.hostname === '0.0.0.0'
? window.location.origin.replace('0.0.0.0', '127.0.0.1')
: window.location.origin;
if (kind === 'curl') {
return `curl -X POST http://localhost:8000${preview.endpoint} \\
return `curl -X POST ${origin}${preview.endpoint} \\
-H "Content-Type: application/json" \\
-d '${preview.json.replace(/'/g, "'\\''")}'`;
}
if (kind === 'python') {
return `import requests\n\npayload = ${preview.json}\nresponse = requests.post(\n "http://localhost:8000${preview.endpoint}",\n json=payload,\n timeout=300,\n)\nprint(response.json())`;
return `import requests\n\npayload = ${preview.json}\nresponse = requests.post(\n "${origin}${preview.endpoint}",\n json=payload,\n timeout=300,\n)\nprint(response.json())`;
}
return `const payload = ${preview.json};\nconst response = await fetch("${preview.endpoint}", {\n method: "POST",\n headers: { "Content-Type": "application/json" },\n body: JSON.stringify(payload),\n});\nconst data = await response.json();\nconsole.log(data);`;
return `const payload = ${preview.json};\nconst response = await fetch("${origin}${preview.endpoint}", {\n method: "POST",\n headers: { "Content-Type": "application/json" },\n body: JSON.stringify(payload),\n});\nconst data = await response.json();\nconsole.log(data);`;
}
function renderRequestPreview(panel, config) {
......
......@@ -105,9 +105,10 @@ from codai.admin.routes import router as admin_router
# Import and add middleware
from codai.api.log import log_requests
from codai.api.ratelimit import RateLimitMiddleware
from codai.api.ratelimit import RateLimitMiddleware, BearerAuthMiddleware
app.middleware("http")(log_requests)
app.add_middleware(RateLimitMiddleware)
app.add_middleware(BearerAuthMiddleware)
# Mount static files for admin dashboard
from fastapi.staticfiles import StaticFiles
......
......@@ -31,6 +31,45 @@ from fastapi.responses import JSONResponse
from starlette.middleware.base import BaseHTTPMiddleware
class BearerAuthMiddleware(BaseHTTPMiddleware):
"""Reject /v1/ API requests that lack a valid Bearer token or active web session."""
async def dispatch(self, request: Request, call_next):
path = request.url.path
if not path.startswith("/v1/"):
return await call_next(request)
from codai.admin import routes as _admin_routes
sm = _admin_routes.session_manager
if sm is None:
return await call_next(request)
# Accept a valid Bearer token
auth_header = request.headers.get("authorization", "")
if auth_header.lower().startswith("bearer "):
token = auth_header[7:].strip()
if sm.verify_token(token):
return await call_next(request)
# Accept a valid web session cookie (logged-in browser user)
cookie = request.cookies.get("session", "")
if cookie.endswith(".MUST_CHANGE"):
cookie = cookie[:-12]
if cookie and sm.validate_session(cookie):
return await call_next(request)
return JSONResponse(
status_code=401,
content={
"error": {
"message": "Invalid API key. Provide a valid Bearer token.",
"type": "invalid_request_error",
"code": "invalid_api_key",
}
},
)
# Per-route-prefix defaults: (max_requests, window_seconds)
_DEFAULT_LIMITS: Dict[str, Tuple[int, int]] = {
"/v1/chat/completions": (60, 60),
......
......@@ -239,6 +239,8 @@ def choose_mode_interactively() -> str:
for idx, mode in enumerate(MODES, start=1):
print(f"{idx}. {mode}")
raw = input("Choose mode: ").strip()
if raw in MODES:
return raw
selected = int(raw)
if selected < 1 or selected > len(MODES):
raise ValueError(f"Invalid mode selection: {raw}")
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment