feat: add FSM handlers and admin interface

- Add all user FSM states (INTRO through CONFIRM_SAVE)
- Add replica re-recording by number (ASK_REPLICA_NUMBER, REPEAT_REPLICA)
- Add admin interface with stats and scenario upload
- Add voice message handling and storage
This commit is contained in:
2026-02-02 21:25:19 +03:00
parent 661f1913af
commit 8fecb3d543
5 changed files with 986 additions and 12 deletions

View File

@@ -164,7 +164,11 @@ def get_or_create_user(telegram_id: int) -> User:
row = cursor.fetchone()
if row:
return User(id=row["id"], telegram_id=row["telegram_id"], created_at=row["created_at"])
return User(
id=row["id"],
telegram_id=row["telegram_id"],
created_at=row["created_at"],
)
cursor = conn.execute(
"INSERT INTO users (telegram_id) VALUES (?) RETURNING id, telegram_id, created_at",
@@ -173,7 +177,9 @@ def get_or_create_user(telegram_id: int) -> User:
row = cursor.fetchone()
conn.commit()
logger.info(f"Создан новый пользователь: dataset_speaker_id={row['id']}")
return User(id=row["id"], telegram_id=row["telegram_id"], created_at=row["created_at"])
return User(
id=row["id"], telegram_id=row["telegram_id"], created_at=row["created_at"]
)
def get_user_by_telegram_id(telegram_id: int) -> User | None:
@@ -185,7 +191,11 @@ def get_user_by_telegram_id(telegram_id: int) -> User | None:
)
row = cursor.fetchone()
if row:
return User(id=row["id"], telegram_id=row["telegram_id"], created_at=row["created_at"])
return User(
id=row["id"],
telegram_id=row["telegram_id"],
created_at=row["created_at"],
)
return None
@@ -221,8 +231,13 @@ def get_scenario(scenario_id: str) -> Scenario | None:
def get_all_scenarios() -> list[Scenario]:
"""Получает все сценарии."""
with get_connection() as conn:
cursor = conn.execute("SELECT id, created_at FROM scenarios ORDER BY created_at")
return [Scenario(id=row["id"], created_at=row["created_at"]) for row in cursor.fetchall()]
cursor = conn.execute(
"SELECT id, created_at FROM scenarios ORDER BY created_at"
)
return [
Scenario(id=row["id"], created_at=row["created_at"])
for row in cursor.fetchall()
]
# === Replicas CRUD ===
@@ -233,7 +248,10 @@ def create_replicas(scenario_id: str, replicas: list[tuple[int, int, str]]) -> N
with get_connection() as conn:
conn.executemany(
"INSERT INTO replicas (scenario_id, speaker_id, replica_index, text) VALUES (?, ?, ?, ?)",
[(scenario_id, speaker_id, idx, text) for speaker_id, idx, text in replicas],
[
(scenario_id, speaker_id, idx, text)
for speaker_id, idx, text in replicas
],
)
conn.commit()
@@ -383,7 +401,9 @@ def get_user_session(user_id: int) -> UserSession | None:
scenario_id=row["scenario_id"],
speaker_id=row["speaker_id"],
replica_index=row["replica_index"],
previous_state=UserState(row["previous_state"]) if row["previous_state"] else None,
previous_state=UserState(row["previous_state"])
if row["previous_state"]
else None,
last_bot_message_id=row["last_bot_message_id"],
)
return None
@@ -437,10 +457,14 @@ def get_stats() -> dict:
stats = {}
# Общее количество сценариев
stats["total_scenarios"] = conn.execute("SELECT COUNT(*) FROM scenarios").fetchone()[0]
stats["total_scenarios"] = conn.execute(
"SELECT COUNT(*) FROM scenarios"
).fetchone()[0]
# Общее количество реплик
stats["total_replicas"] = conn.execute("SELECT COUNT(*) FROM replicas").fetchone()[0]
stats["total_replicas"] = conn.execute(
"SELECT COUNT(*) FROM replicas"
).fetchone()[0]
# Общее количество дорожек
stats["total_tracks"] = conn.execute(
@@ -451,7 +475,9 @@ def get_stats() -> dict:
stats["total_users"] = conn.execute("SELECT COUNT(*) FROM users").fetchone()[0]
# Количество озвученных реплик
stats["total_recordings"] = conn.execute("SELECT COUNT(*) FROM recordings").fetchone()[0]
stats["total_recordings"] = conn.execute(
"SELECT COUNT(*) FROM recordings"
).fetchone()[0]
# Количество полностью озвученных дорожек (в data/)
# Это вычисляется по файловой системе, здесь примерная оценка