Imrpove cue scanner and remove false positives
This commit is contained in:
@@ -770,6 +770,81 @@ async def iknowthat(request: Request) -> dict:
|
||||
}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# /models — LLM model management
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@app.get("/models")
|
||||
async def list_models(request: Request) -> dict:
|
||||
pool = request.app.state.pool
|
||||
async with pool.acquire() as conn:
|
||||
rows = await conn.fetch(
|
||||
"SELECT id, provider, model_name, created_at FROM models ORDER BY id"
|
||||
)
|
||||
return {"models": [
|
||||
{"id": r["id"], "provider": r["provider"], "model_name": r["model_name"],
|
||||
"created_at": r["created_at"].isoformat()}
|
||||
for r in rows
|
||||
]}
|
||||
|
||||
|
||||
@app.post("/models")
|
||||
async def create_model(request: Request) -> dict:
|
||||
pool = request.app.state.pool
|
||||
data = await request.json()
|
||||
provider = data.get("provider", "").strip()
|
||||
model_name = data.get("model_name", "").strip()
|
||||
api_key = data.get("api_key", "").strip()
|
||||
if not provider or not model_name or not api_key:
|
||||
return {"error": "provider, model_name, and api_key are required"}
|
||||
if provider not in ("claude", "openai"):
|
||||
return {"error": "provider must be 'claude' or 'openai'"}
|
||||
async with pool.acquire() as conn:
|
||||
row = await conn.fetchrow(
|
||||
"INSERT INTO models (provider, model_name, api_key) VALUES ($1,$2,$3) RETURNING id",
|
||||
provider, model_name, api_key,
|
||||
)
|
||||
log.info("model created id=%d provider=%s model=%s", row["id"], provider, model_name)
|
||||
return {"status": "ok", "id": row["id"]}
|
||||
|
||||
|
||||
@app.delete("/models/{model_id}")
|
||||
async def delete_model(model_id: int, request: Request) -> dict:
|
||||
pool = request.app.state.pool
|
||||
async with pool.acquire() as conn:
|
||||
result = await conn.execute("DELETE FROM models WHERE id=$1", model_id)
|
||||
deleted = int(result.split()[-1]) if result else 0
|
||||
if not deleted:
|
||||
return {"error": f"model {model_id} not found"}
|
||||
log.info("model deleted id=%d", model_id)
|
||||
return {"status": "ok", "deleted": model_id}
|
||||
|
||||
|
||||
@app.get("/config")
|
||||
async def get_all_config(request: Request) -> dict:
|
||||
pool = request.app.state.pool
|
||||
async with pool.acquire() as conn:
|
||||
rows = await conn.fetch("SELECT key, value, updated_at FROM config ORDER BY key")
|
||||
return {"config": {r["key"]: r["value"] for r in rows}}
|
||||
|
||||
|
||||
@app.post("/config")
|
||||
async def update_config(request: Request) -> dict:
|
||||
pool = request.app.state.pool
|
||||
data = await request.json()
|
||||
key = data.get("key", "").strip()
|
||||
value = str(data.get("value", "")).strip()
|
||||
if not key:
|
||||
return {"error": "key is required"}
|
||||
async with pool.acquire() as conn:
|
||||
await conn.execute(
|
||||
"UPDATE config SET value=$1, updated_at=now() WHERE key=$2",
|
||||
value, key,
|
||||
)
|
||||
log.info("config updated key=%s value=%s", key, value)
|
||||
return {"status": "ok", "key": key, "value": value}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# /resolve/run — manually trigger resolution job
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -897,15 +972,19 @@ async def kg_log(request: Request, limit: int = 100, offset: int = 0, op: str =
|
||||
count_query = "SELECT COUNT(*) FROM kg_write_log {where}"
|
||||
|
||||
if op:
|
||||
where = "WHERE op = $3"
|
||||
async with pool.acquire() as conn:
|
||||
rows = await conn.fetch(query.format(where=where), limit, offset, op)
|
||||
total = await conn.fetchval(count_query.format(where=where), op)
|
||||
rows = await conn.fetch(
|
||||
query.format(where="WHERE op = $3"),
|
||||
limit, offset, op,
|
||||
)
|
||||
total = await conn.fetchval(
|
||||
"SELECT COUNT(*) FROM kg_write_log WHERE op = $1",
|
||||
op,
|
||||
)
|
||||
else:
|
||||
where = ""
|
||||
async with pool.acquire() as conn:
|
||||
rows = await conn.fetch(query.format(where=where), limit, offset)
|
||||
total = await conn.fetchval(count_query.format(where=where))
|
||||
rows = await conn.fetch(query.format(where=""), limit, offset)
|
||||
total = await conn.fetchval("SELECT COUNT(*) FROM kg_write_log")
|
||||
|
||||
def fmt(r):
|
||||
return {
|
||||
@@ -1642,6 +1721,34 @@ ADMIN_HTML = """<!DOCTYPE html>
|
||||
<div class="stat"><div class="stat-label">Last resolution</div><div class="stat-value" style="font-size:0.85em" id="s-lastrun">…</div></div>
|
||||
</div>
|
||||
|
||||
<h2>Resolution model</h2>
|
||||
<div id="models-section">
|
||||
<table id="models-table" style="margin-bottom:0.8em">
|
||||
<thead><tr><th>ID</th><th>Provider</th><th>Model name</th><th>resolve?</th><th>write?</th><th></th></tr></thead>
|
||||
<tbody id="models-tbody"><tr><td colspan="6">Loading…</td></tr></tbody>
|
||||
</table>
|
||||
<details style="margin-bottom:1em">
|
||||
<summary style="cursor:pointer;font-size:0.9em;color:#555">Add model…</summary>
|
||||
<div style="margin-top:0.6em;display:flex;gap:0.7em;flex-wrap:wrap;align-items:flex-end">
|
||||
<label style="font-size:0.85em">Provider
|
||||
<select id="m-provider" style="font-family:monospace;padding:4px 8px;display:block;margin-top:2px">
|
||||
<option value="claude">claude</option>
|
||||
<option value="openai">openai</option>
|
||||
</select>
|
||||
</label>
|
||||
<label style="font-size:0.85em">Model name
|
||||
<input id="m-name" type="text" value="claude-opus-4-6"
|
||||
style="font-family:monospace;padding:5px 8px;border:1px solid #ccc;border-radius:3px;display:block;margin-top:2px;width:200px">
|
||||
</label>
|
||||
<label style="font-size:0.85em">API key
|
||||
<input id="m-key" type="password" placeholder="sk-ant-…"
|
||||
style="font-family:monospace;padding:5px 8px;border:1px solid #ccc;border-radius:3px;display:block;margin-top:2px;width:260px">
|
||||
</label>
|
||||
<button onclick="addModel(this)" style="height:32px">Add</button>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
<h2>Actions</h2>
|
||||
<div class="actions">
|
||||
<button class="primary" onclick="runResolution(this)">Run conflict resolution now</button>
|
||||
@@ -1694,6 +1801,70 @@ ADMIN_HTML = """<!DOCTYPE html>
|
||||
: 'never';
|
||||
}}
|
||||
|
||||
let _cfg = {{}};
|
||||
|
||||
async function loadModels() {{
|
||||
const [mr, cr] = await Promise.all([fetch('/models'), fetch('/config')]);
|
||||
const md = await mr.json();
|
||||
_cfg = (await cr.json()).config;
|
||||
const resolveId = _cfg['resolve_model_id'] || '';
|
||||
const writeId = _cfg['write_model_id'] || '';
|
||||
|
||||
const tbody = document.getElementById('models-tbody');
|
||||
if (!md.models.length) {{
|
||||
tbody.innerHTML = '<tr><td colspan="6" style="color:#999">No models yet — add one below.</td></tr>';
|
||||
return;
|
||||
}}
|
||||
tbody.innerHTML = md.models.map(m => `
|
||||
<tr>
|
||||
<td>${{m.id}}</td>
|
||||
<td>${{m.provider}}</td>
|
||||
<td>${{m.model_name}}</td>
|
||||
<td><button onclick="setConfig('resolve_model_id','${{m.id}}')" style="padding:2px 8px;font-size:0.8em;${{resolveId==String(m.id)?'background:#2a7a2a;color:#fff;border-color:#2a7a2a':''}}">${{resolveId==String(m.id)?'✓ active':'set'}}</button></td>
|
||||
<td><button onclick="setConfig('write_model_id','${{m.id}}')" style="padding:2px 8px;font-size:0.8em;${{writeId==String(m.id)?'background:#2a7a2a;color:#fff;border-color:#2a7a2a':''}}">${{writeId==String(m.id)?'✓ active':'set'}}</button></td>
|
||||
<td><button onclick="deleteModel(${{m.id}},this)" style="padding:2px 8px;font-size:0.8em;color:#b00;border-color:#b00">✕</button></td>
|
||||
</tr>`).join('');
|
||||
}}
|
||||
|
||||
async function addModel(btn) {{
|
||||
const provider = document.getElementById('m-provider').value;
|
||||
const model_name = document.getElementById('m-name').value.trim();
|
||||
const api_key = document.getElementById('m-key').value.trim();
|
||||
if (!model_name || !api_key) {{ alert('Model name and API key are required.'); return; }}
|
||||
btn.disabled = true;
|
||||
try {{
|
||||
const r = await fetch('/models', {{method:'POST', headers:{{'Content-Type':'application/json'}},
|
||||
body: JSON.stringify({{provider, model_name, api_key}})}});
|
||||
const d = await r.json();
|
||||
if (d.error) {{ showResult('Error: ' + d.error, false); return; }}
|
||||
showResult('Model added (id=' + d.id + '). You can now set it as the resolve model.', true);
|
||||
document.getElementById('m-key').value = '';
|
||||
await loadModels();
|
||||
}} catch(e) {{ showResult('Error: ' + e.message, false); }}
|
||||
finally {{ btn.disabled = false; }}
|
||||
}}
|
||||
|
||||
async function deleteModel(id, btn) {{
|
||||
if (!confirm('Delete model ' + id + '?')) return;
|
||||
btn.disabled = true;
|
||||
try {{
|
||||
const r = await fetch('/models/' + id, {{method:'DELETE'}});
|
||||
const d = await r.json();
|
||||
if (d.error) {{ showResult('Error: ' + d.error, false); return; }}
|
||||
await loadModels();
|
||||
}} catch(e) {{ showResult('Error: ' + e.message, false); }}
|
||||
finally {{ btn.disabled = false; }}
|
||||
}}
|
||||
|
||||
async function setConfig(key, value) {{
|
||||
const r = await fetch('/config', {{method:'POST', headers:{{'Content-Type':'application/json'}},
|
||||
body: JSON.stringify({{key, value}})}});
|
||||
const d = await r.json();
|
||||
if (d.error) {{ showResult('Error: ' + d.error, false); return; }}
|
||||
showResult('Config updated: ' + key + ' = ' + value, true);
|
||||
await loadModels();
|
||||
}}
|
||||
|
||||
async function loadConflicts() {{
|
||||
const r = await fetch('/conflicts');
|
||||
const d = await r.json();
|
||||
@@ -1819,6 +1990,7 @@ ADMIN_HTML = """<!DOCTYPE html>
|
||||
loadStats();
|
||||
loadConflicts();
|
||||
loadLog(0);
|
||||
loadModels();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user