Spaces:
Sleeping
Sleeping
Remove fixed researcher access code
Browse files
app.py
CHANGED
|
@@ -19,7 +19,6 @@ from gradio_client import Client
|
|
| 19 |
from pydantic import BaseModel, Field
|
| 20 |
|
| 21 |
|
| 22 |
-
ACCESS_CODE = os.getenv("ACCESS_CODE", "")
|
| 23 |
SESSION_SECRET = os.getenv("SESSION_SECRET", "")
|
| 24 |
HF_TOKEN = os.getenv("HF_TOKEN", "")
|
| 25 |
TARGET_SPACE_ID = os.getenv("TARGET_SPACE_ID", "ASQ2/ASQ")
|
|
@@ -34,10 +33,6 @@ PAIRING_TTL_SECONDS = int(os.getenv("PAIRING_TTL_SECONDS", "600"))
|
|
| 34 |
logger = logging.getLogger("asq_gateway")
|
| 35 |
|
| 36 |
|
| 37 |
-
class AuthRequest(BaseModel):
|
| 38 |
-
code: str = Field(min_length=6, max_length=6, pattern=r"^\d{6}$")
|
| 39 |
-
|
| 40 |
-
|
| 41 |
class PrepareRequest(BaseModel):
|
| 42 |
background: str = Field(min_length=20, max_length=MAX_BACKGROUND_CHARS)
|
| 43 |
|
|
@@ -91,7 +86,7 @@ def _new_interview_code() -> str:
|
|
| 91 |
_cleanup_expired()
|
| 92 |
for _ in range(100):
|
| 93 |
code = f"{secrets.randbelow(1_000_000):06d}"
|
| 94 |
-
if code
|
| 95 |
return code
|
| 96 |
raise RuntimeError("Could not allocate an interview code")
|
| 97 |
|
|
@@ -100,7 +95,7 @@ def _new_pairing_code() -> str:
|
|
| 100 |
_cleanup_expired()
|
| 101 |
for _ in range(100):
|
| 102 |
code = f"{secrets.randbelow(1_000_000):06d}"
|
| 103 |
-
if code
|
| 104 |
return code
|
| 105 |
raise RuntimeError("Could not allocate a pairing code")
|
| 106 |
|
|
@@ -262,7 +257,7 @@ def session_view(session: dict[str, Any], role: str) -> dict[str, Any]:
|
|
| 262 |
return response
|
| 263 |
|
| 264 |
|
| 265 |
-
api = FastAPI(title="ASQ Rokid Gateway", version="0.
|
| 266 |
api.add_middleware(
|
| 267 |
CORSMiddleware,
|
| 268 |
allow_origins=["*"],
|
|
@@ -280,18 +275,16 @@ def mobile_app() -> str:
|
|
| 280 |
def health() -> dict[str, Any]:
|
| 281 |
return {
|
| 282 |
"ok": True,
|
| 283 |
-
"configured": bool(
|
| 284 |
"target": TARGET_SPACE_ID,
|
| 285 |
-
"version": "0.
|
| 286 |
}
|
| 287 |
|
| 288 |
|
| 289 |
@api.post("/auth")
|
| 290 |
-
def
|
| 291 |
-
if not
|
| 292 |
raise HTTPException(status_code=503, detail="Gateway is not configured")
|
| 293 |
-
if not hmac.compare_digest(request.code, ACCESS_CODE):
|
| 294 |
-
raise HTTPException(status_code=401, detail="Incorrect code")
|
| 295 |
_cleanup_expired()
|
| 296 |
session_id = secrets.token_urlsafe(18)
|
| 297 |
expires_at = int(time.time()) + SESSION_TTL_SECONDS
|
|
@@ -454,24 +447,24 @@ button{border:0;background:#087a54;color:#fff;padding:14px;font-weight:700;margi
|
|
| 454 |
.hypo{white-space:pre-wrap;line-height:1.5;background:#f7faf8;padding:14px;border-radius:12px;max-height:320px;overflow:auto}
|
| 455 |
</style></head><body><main class="card">
|
| 456 |
<h1>ASQ Interview Setup</h1><p class="muted">Open ASQ on the Rokid glasses first. The glasses will display a six-digit pairing code; enter that code on this phone.</p>
|
| 457 |
-
<section id="
|
| 458 |
-
<section id="prepare" class="step hidden"><label for="pairingCode">Code shown on the glasses</label><input id="pairingCode" inputmode="numeric" maxlength="6" placeholder="Six digits"><label for="background" style="margin-top:16px">Case and interview background</label><textarea id="background" placeholder="Enter the case facts, context, existing evidence, interview goals, and any constraints..."></textarea><button id="prepareBtn">Connect glasses and prepare interview</button></section>
|
| 459 |
<section id="progress" class="step hidden"><div id="status" class="status">Connecting the glasses…</div><div id="hypoWrap" class="hidden"><h2>Initial hypotheses</h2><div id="hypotheses" class="hypo"></div></div></section>
|
| 460 |
</main><script>
|
| 461 |
let token="", timer=null; const $=id=>document.getElementById(id);
|
| 462 |
async function api(path, options={}){const headers={"Content-Type":"application/json",...(options.headers||{})};if(token)headers.Authorization=`Bearer ${token}`;const r=await fetch(path,{...options,headers});let data={};try{data=await r.json()}catch{}if(!r.ok)throw new Error(data.detail||`Request failed (${r.status})`);return data}
|
| 463 |
function errorMessage(e){$("status").textContent=e.message;$("progress").classList.remove("hidden")}
|
| 464 |
-
|
| 465 |
$("prepareBtn").onclick=async()=>{const pairingCode=$("pairingCode").value.trim();const background=$("background").value.trim();if(!/^\d{6}$/.test(pairingCode)){errorMessage("Enter the six-digit code shown on the glasses.");return}if(background.length<20){errorMessage("Please provide at least 20 characters of background.");return}$("prepareBtn").disabled=true;$("progress").classList.remove("hidden");$("status").textContent="Connecting the glasses and generating initial hypotheses…";try{await api("/pair/claim",{method:"POST",body:JSON.stringify({code:pairingCode})});const data=await api("/prepare",{method:"POST",body:JSON.stringify({background})});show(data);timer=setInterval(poll,3000)}catch(e){errorMessage(e);$("prepareBtn").disabled=false}};
|
| 466 |
async function poll(){try{show(await api("/session"))}catch(e){clearInterval(timer);errorMessage(e)}}
|
| 467 |
function show(data){if(data.status==="ready"){clearInterval(timer);$("status").textContent="Ready. The glasses are connected and can begin the interview.";$("hypotheses").textContent=data.hypotheses||"No hypotheses returned.";$("hypoWrap").classList.remove("hidden")}else if(data.status==="error"){clearInterval(timer);$("status").textContent=data.error||"Model startup failed.";$("prepareBtn").disabled=false}else{$("status").textContent="Glasses connected. Starting the model and generating initial hypotheses…"}}
|
|
|
|
| 468 |
</script></body></html>"""
|
| 469 |
|
| 470 |
|
| 471 |
with gr.Blocks(title="ASQ Rokid Gateway") as status_page:
|
| 472 |
gr.Markdown(
|
| 473 |
"# ASQ Rokid Gateway\n"
|
| 474 |
-
"This service provides
|
| 475 |
"Open the site root on a phone to prepare an interview. Use `GET /health` for service status."
|
| 476 |
)
|
| 477 |
|
|
|
|
| 19 |
from pydantic import BaseModel, Field
|
| 20 |
|
| 21 |
|
|
|
|
| 22 |
SESSION_SECRET = os.getenv("SESSION_SECRET", "")
|
| 23 |
HF_TOKEN = os.getenv("HF_TOKEN", "")
|
| 24 |
TARGET_SPACE_ID = os.getenv("TARGET_SPACE_ID", "ASQ2/ASQ")
|
|
|
|
| 33 |
logger = logging.getLogger("asq_gateway")
|
| 34 |
|
| 35 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
class PrepareRequest(BaseModel):
|
| 37 |
background: str = Field(min_length=20, max_length=MAX_BACKGROUND_CHARS)
|
| 38 |
|
|
|
|
| 86 |
_cleanup_expired()
|
| 87 |
for _ in range(100):
|
| 88 |
code = f"{secrets.randbelow(1_000_000):06d}"
|
| 89 |
+
if code not in interview_codes:
|
| 90 |
return code
|
| 91 |
raise RuntimeError("Could not allocate an interview code")
|
| 92 |
|
|
|
|
| 95 |
_cleanup_expired()
|
| 96 |
for _ in range(100):
|
| 97 |
code = f"{secrets.randbelow(1_000_000):06d}"
|
| 98 |
+
if code not in pairing_codes and code not in interview_codes:
|
| 99 |
return code
|
| 100 |
raise RuntimeError("Could not allocate a pairing code")
|
| 101 |
|
|
|
|
| 257 |
return response
|
| 258 |
|
| 259 |
|
| 260 |
+
api = FastAPI(title="ASQ Rokid Gateway", version="0.4.0")
|
| 261 |
api.add_middleware(
|
| 262 |
CORSMiddleware,
|
| 263 |
allow_origins=["*"],
|
|
|
|
| 275 |
def health() -> dict[str, Any]:
|
| 276 |
return {
|
| 277 |
"ok": True,
|
| 278 |
+
"configured": bool(SESSION_SECRET and HF_TOKEN),
|
| 279 |
"target": TARGET_SPACE_ID,
|
| 280 |
+
"version": "0.4.0",
|
| 281 |
}
|
| 282 |
|
| 283 |
|
| 284 |
@api.post("/auth")
|
| 285 |
+
def create_owner_session() -> dict[str, Any]:
|
| 286 |
+
if not SESSION_SECRET:
|
| 287 |
raise HTTPException(status_code=503, detail="Gateway is not configured")
|
|
|
|
|
|
|
| 288 |
_cleanup_expired()
|
| 289 |
session_id = secrets.token_urlsafe(18)
|
| 290 |
expires_at = int(time.time()) + SESSION_TTL_SECONDS
|
|
|
|
| 447 |
.hypo{white-space:pre-wrap;line-height:1.5;background:#f7faf8;padding:14px;border-radius:12px;max-height:320px;overflow:auto}
|
| 448 |
</style></head><body><main class="card">
|
| 449 |
<h1>ASQ Interview Setup</h1><p class="muted">Open ASQ on the Rokid glasses first. The glasses will display a six-digit pairing code; enter that code on this phone.</p>
|
| 450 |
+
<section id="prepare"><label for="pairingCode">Code shown on the glasses</label><input id="pairingCode" inputmode="numeric" maxlength="6" placeholder="Six digits"><label for="background" style="margin-top:16px">Case and interview background</label><textarea id="background" placeholder="Enter the case facts, context, existing evidence, interview goals, and any constraints..."></textarea><button id="prepareBtn" disabled>Connect glasses and prepare interview</button></section>
|
|
|
|
| 451 |
<section id="progress" class="step hidden"><div id="status" class="status">Connecting the glasses…</div><div id="hypoWrap" class="hidden"><h2>Initial hypotheses</h2><div id="hypotheses" class="hypo"></div></div></section>
|
| 452 |
</main><script>
|
| 453 |
let token="", timer=null; const $=id=>document.getElementById(id);
|
| 454 |
async function api(path, options={}){const headers={"Content-Type":"application/json",...(options.headers||{})};if(token)headers.Authorization=`Bearer ${token}`;const r=await fetch(path,{...options,headers});let data={};try{data=await r.json()}catch{}if(!r.ok)throw new Error(data.detail||`Request failed (${r.status})`);return data}
|
| 455 |
function errorMessage(e){$("status").textContent=e.message;$("progress").classList.remove("hidden")}
|
| 456 |
+
async function begin(){try{const data=await api("/auth",{method:"POST"});token=data.token;$("prepareBtn").disabled=false}catch(e){errorMessage(e)}}
|
| 457 |
$("prepareBtn").onclick=async()=>{const pairingCode=$("pairingCode").value.trim();const background=$("background").value.trim();if(!/^\d{6}$/.test(pairingCode)){errorMessage("Enter the six-digit code shown on the glasses.");return}if(background.length<20){errorMessage("Please provide at least 20 characters of background.");return}$("prepareBtn").disabled=true;$("progress").classList.remove("hidden");$("status").textContent="Connecting the glasses and generating initial hypotheses…";try{await api("/pair/claim",{method:"POST",body:JSON.stringify({code:pairingCode})});const data=await api("/prepare",{method:"POST",body:JSON.stringify({background})});show(data);timer=setInterval(poll,3000)}catch(e){errorMessage(e);$("prepareBtn").disabled=false}};
|
| 458 |
async function poll(){try{show(await api("/session"))}catch(e){clearInterval(timer);errorMessage(e)}}
|
| 459 |
function show(data){if(data.status==="ready"){clearInterval(timer);$("status").textContent="Ready. The glasses are connected and can begin the interview.";$("hypotheses").textContent=data.hypotheses||"No hypotheses returned.";$("hypoWrap").classList.remove("hidden")}else if(data.status==="error"){clearInterval(timer);$("status").textContent=data.error||"Model startup failed.";$("prepareBtn").disabled=false}else{$("status").textContent="Glasses connected. Starting the model and generating initial hypotheses…"}}
|
| 460 |
+
begin();
|
| 461 |
</script></body></html>"""
|
| 462 |
|
| 463 |
|
| 464 |
with gr.Blocks(title="ASQ Rokid Gateway") as status_page:
|
| 465 |
gr.Markdown(
|
| 466 |
"# ASQ Rokid Gateway\n"
|
| 467 |
+
"This service provides phone preparation and Rokid interview pairing APIs.\n\n"
|
| 468 |
"Open the site root on a phone to prepare an interview. Use `GET /health` for service status."
|
| 469 |
)
|
| 470 |
|