Make /ask free by default, Claude synthesis is opt-in

Search results (FTS5 + Ollama embeddings) are shown immediately at no
cost. AI synthesis via Claude is behind a "Synthesize" button that the
user must explicitly click. Results are cached permanently so repeat
visitors never trigger API calls.

- Split ask into search_only() (free) and ask() (paid, cached)
- GET /ask now uses search_only — no Claude tokens spent
- POST /api/ask/synthesize triggers Claude (Haiku, ~$0.001)
- Cached answers shown with "cached" badge, no re-generation
- Template shows sources immediately + optional synthesize button

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-07 21:15:07 +01:00
parent e65d1cfacd
commit 34c36f81f1
4 changed files with 161 additions and 64 deletions

View File

@@ -45,7 +45,8 @@ from webui.data import (
get_author_network_full,
get_citation_graph,
get_comparison_data,
get_ask_data,
get_ask_search,
get_ask_synthesize,
global_search,
)
@@ -325,20 +326,32 @@ def ask_page():
result = None
if question:
top_k = request.args.get("top", 5, type=int)
result = get_ask_data(db(), question, top_k=top_k)
# Search only (free) — returns sources + cached answer if available
result = get_ask_search(db(), question, top_k=top_k)
return render_template("ask.html", question=question, result=result)
@app.route("/api/ask", methods=["POST"])
def api_ask():
"""Answer a question via hybrid search + Claude. Returns JSON."""
@app.route("/api/ask/synthesize", methods=["POST"])
def api_ask_synthesize():
"""Synthesize an answer via Claude (costs tokens, cached permanently). Returns JSON."""
data = request.get_json(force=True, silent=True)
if not data or "question" not in data:
return jsonify({"error": "Missing 'question' in request body"}), 400
question = data["question"]
top_k = data.get("top_k", 5)
cheap = data.get("cheap", True)
result = get_ask_data(db(), question, top_k=top_k, cheap=cheap)
result = get_ask_synthesize(db(), question, top_k=top_k, cheap=True)
return jsonify(result)
@app.route("/api/ask", methods=["POST"])
def api_ask():
"""Search only (free). Returns JSON with sources + cached answer if available."""
data = request.get_json(force=True, silent=True)
if not data or "question" not in data:
return jsonify({"error": "Missing 'question' in request body"}), 400
question = data["question"]
top_k = data.get("top_k", 5)
result = get_ask_search(db(), question, top_k=top_k)
return jsonify(result)