86 lines
2.9 KiB
Python
86 lines
2.9 KiB
Python
"""``AskUserQuestion`` from inside a turn: answered or timed out."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import contextlib
|
|
from typing import TYPE_CHECKING, Any, cast
|
|
from uuid import uuid4
|
|
|
|
from beaver_gateway.conversations.rewinding import Rewinding
|
|
from beaver_gateway.conversations.state import Question
|
|
|
|
if TYPE_CHECKING:
|
|
from beaver_gateway.storage.models import Conversation
|
|
|
|
__all__ = ["Questions"]
|
|
|
|
|
|
class Questions(Rewinding):
|
|
async def ask(self, key: str, payload: dict[str, Any]) -> str | None:
|
|
"""Show the question and wait; ``None`` when nobody answered in time."""
|
|
conv = await self.get(key)
|
|
if conv is None:
|
|
return None
|
|
runner = self._runners.get(cast("int", conv.id))
|
|
question_id = f"q_{uuid4().hex[:10]}"
|
|
pending = Question(
|
|
conversation_id=key,
|
|
turn_id=runner.turn_id if runner is not None else None,
|
|
questions=list(payload.get("questions") or []),
|
|
answer=asyncio.get_running_loop().create_future(),
|
|
)
|
|
self._questions[question_id] = pending
|
|
await self._set_pending_question(conv, value=True)
|
|
self._bus.publish(
|
|
"question",
|
|
conversation_id=key,
|
|
turn_id=pending.turn_id,
|
|
question_id=question_id,
|
|
questions=pending.questions,
|
|
timeout=self._question_timeout,
|
|
)
|
|
try:
|
|
async with asyncio.timeout(self._question_timeout):
|
|
answer = await pending.answer
|
|
except TimeoutError:
|
|
self._bus.publish(
|
|
"question.timeout",
|
|
conversation_id=key,
|
|
turn_id=pending.turn_id,
|
|
question_id=question_id,
|
|
)
|
|
return None
|
|
finally:
|
|
self._questions.pop(question_id, None)
|
|
await self._set_pending_question(conv, value=False)
|
|
self._bus.publish(
|
|
"question.answered",
|
|
conversation_id=key,
|
|
turn_id=pending.turn_id,
|
|
question_id=question_id,
|
|
answer=answer,
|
|
)
|
|
return answer
|
|
|
|
def answer(self, question_id: str, answer: str) -> bool:
|
|
pending = self._questions.get(question_id)
|
|
if pending is None or pending.answer.done():
|
|
return False
|
|
pending.answer.set_result(answer)
|
|
return True
|
|
|
|
def answer_text(self, answer: str | None) -> str:
|
|
if answer is None:
|
|
return self._texts.unanswered.format(
|
|
minutes=round(self._question_timeout / 60)
|
|
)
|
|
return self._texts.answered.format(answer=answer)
|
|
|
|
async def _set_pending_question(self, conv: Conversation, *, value: bool) -> None:
|
|
async def apply(row: Conversation) -> None:
|
|
row.pending_question = value
|
|
|
|
with contextlib.suppress(LookupError):
|
|
await self._update(conv, apply)
|