Initial commit: VoIdeaAI - voice-first AI idea assistant

This commit is contained in:
2026-05-13 12:51:42 +03:00
commit 688d043dad
421 changed files with 47915 additions and 0 deletions
+15
View File
@@ -0,0 +1,15 @@
# models Module - VoIdea
## Overview
[Auto-generated documentation]
## Files
| File | Purpose |
|------|---------|
| `agent.py` | Module file |
| `backlog.py` | Module file |
| `idea.py` | Module file |
| `log.py` | Module file |
| `user.py` | Module file |
+12
View File
@@ -0,0 +1,12 @@
from app.models.user import User
from app.models.idea import Idea
from app.models.session import Session
from app.models.voice_command import VoiceCommand
from app.models.agent import AgentConfig
from app.models.backlog import BacklogTask
from app.models.feedback import Feedback
from app.models.tariff import TariffPlan, UserSubscription
from app.models.log import LogEntry
from app.models.conductor import ConductorInteraction
from app.models.pipeline import PipelineStats
from app.models.bot_command import BotCommand
+35
View File
@@ -0,0 +1,35 @@
"""Agent configuration model for VoIdea."""
from datetime import datetime
from typing import Optional
from sqlalchemy import Boolean, DateTime, String, Text
from sqlalchemy.orm import Mapped, mapped_column
from app.core.base import SQLBase, TimestampMixin, UUIDMixin
class AgentConfig(SQLBase, UUIDMixin, TimestampMixin):
__tablename__ = "agent_configs"
agent_name: Mapped[str] = mapped_column(
String(100), unique=True, nullable=False, index=True
)
description: Mapped[Optional[str]] = mapped_column(
Text, nullable=True, default=""
)
is_enabled: Mapped[bool] = mapped_column(
Boolean, default=True, nullable=False
)
version: Mapped[str] = mapped_column(
String(20), default="1.0.0", nullable=False
)
checksum: Mapped[Optional[str]] = mapped_column(
String(64), nullable=True
)
config: Mapped[Optional[str]] = mapped_column(
Text, nullable=True
)
last_run_at: Mapped[Optional[datetime]] = mapped_column(
DateTime(timezone=True), nullable=True
)
+31
View File
@@ -0,0 +1,31 @@
"""Backlog task model for VoIdea."""
from typing import Optional
from sqlalchemy import String, Text
from sqlalchemy.orm import Mapped, mapped_column
from app.core.base import SQLBase, TimestampMixin, UUIDMixin
class BacklogTask(SQLBase, UUIDMixin, TimestampMixin):
__tablename__ = "backlog_tasks"
title: Mapped[str] = mapped_column(
String(255), nullable=False
)
description: Mapped[Optional[str]] = mapped_column(
Text, nullable=True
)
priority: Mapped[str] = mapped_column(
String(20), default="medium", nullable=False, index=True
)
status: Mapped[str] = mapped_column(
String(20), default="pending", nullable=False, index=True
)
source_agent: Mapped[Optional[str]] = mapped_column(
String(100), nullable=True
)
category: Mapped[str] = mapped_column(
String(50), default="general", nullable=False, index=True
)
+22
View File
@@ -0,0 +1,22 @@
"""BotCommand model — Telegram bot commands with enable/disable toggle."""
from sqlalchemy import Boolean, String
from sqlalchemy.orm import Mapped, mapped_column
from app.core.base import SQLBase, TimestampMixin, UUIDMixin
class BotCommand(SQLBase, UUIDMixin, TimestampMixin):
__tablename__ = "bot_commands"
name: Mapped[str] = mapped_column(
String(50), unique=True, nullable=False, index=True
)
description: Mapped[str] = mapped_column(
String(255), nullable=False
)
enabled: Mapped[bool] = mapped_column(
Boolean, default=True, nullable=False
)
requires_auth: Mapped[bool] = mapped_column(
Boolean, default=False, nullable=False
)
+45
View File
@@ -0,0 +1,45 @@
"""Conductor interaction model for VoIdeaAI.
Logs all Дирижёр interactions for self-learning and analytics.
"""
from typing import Optional
from sqlalchemy import Float, ForeignKey, Integer, String, Text
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import Mapped, mapped_column
from app.core.base import SQLBase, TimestampMixin, UUIDMixin
class ConductorInteraction(SQLBase, UUIDMixin, TimestampMixin):
__tablename__ = "conductor_interactions"
user_id: Mapped[Optional[UUID]] = mapped_column(
UUID(as_uuid=True),
ForeignKey("users.id", ondelete="SET NULL"),
nullable=True,
index=True,
)
session_id: Mapped[Optional[UUID]] = mapped_column(
UUID(as_uuid=True),
ForeignKey("sessions.id", ondelete="SET NULL"),
nullable=True,
index=True,
)
input_text: Mapped[str] = mapped_column(Text, nullable=False)
detected_intent: Mapped[str] = mapped_column(String(100), nullable=False)
selected_agent: Mapped[str] = mapped_column(String(100), nullable=False)
response_text: Mapped[str] = mapped_column(Text, nullable=False)
user_rating: Mapped[Optional[int]] = mapped_column(
Integer, nullable=True
)
confidence_score: Mapped[int] = mapped_column(
Integer, default=80, nullable=False
)
verification_status: Mapped[str] = mapped_column(
String(20), default="verified", nullable=False
)
was_auto_routed: Mapped[bool] = mapped_column(default=True)
processing_time_ms: Mapped[float] = mapped_column(Float, default=0.0)
context: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
+29
View File
@@ -0,0 +1,29 @@
"""Feedback model for VoIdea."""
from typing import Optional
from sqlalchemy import ForeignKey, String, Text
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import Mapped, mapped_column
from app.core.base import SQLBase, TimestampMixin, UUIDMixin
class Feedback(SQLBase, UUIDMixin, TimestampMixin):
__tablename__ = "feedback"
user_id: Mapped[UUID] = mapped_column(
UUID(as_uuid=True),
ForeignKey("users.id", ondelete="SET NULL"),
nullable=True,
index=True,
)
text: Mapped[str] = mapped_column(
Text, nullable=False
)
page_url: Mapped[Optional[str]] = mapped_column(
String(512), nullable=True
)
status: Mapped[str] = mapped_column(
String(20), default="new", nullable=False, index=True
)
+40
View File
@@ -0,0 +1,40 @@
"""Idea model for VoIdea."""
from typing import Optional
from sqlalchemy import Boolean, ForeignKey, String, Text
from sqlalchemy.dialects.postgresql import ARRAY, UUID
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.core.base import SQLBase, TimestampMixin, UUIDMixin
class Idea(SQLBase, UUIDMixin, TimestampMixin):
__tablename__ = "ideas"
user_id: Mapped[UUID] = mapped_column(
UUID(as_uuid=True),
ForeignKey("users.id", ondelete="CASCADE"),
nullable=False,
index=True,
)
title: Mapped[str] = mapped_column(
String(255), nullable=False, index=True
)
content: Mapped[str] = mapped_column(
Text, nullable=False
)
status: Mapped[str] = mapped_column(
String(20), default="draft", nullable=False, index=True
)
tags: Mapped[Optional[list[str]]] = mapped_column(
ARRAY(String(50)), nullable=True
)
is_public: Mapped[bool] = mapped_column(
Boolean, default=False, nullable=False
)
public_slug: Mapped[Optional[str]] = mapped_column(
String(64), unique=True, nullable=True, index=True
)
user = relationship("User", back_populates="ideas", lazy="selectin")
+32
View File
@@ -0,0 +1,32 @@
"""Log entry model for VoIdea."""
from typing import Optional
from sqlalchemy import ForeignKey, String, Text
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import Mapped, mapped_column
from app.core.base import SQLBase, TimestampMixin, UUIDMixin
class LogEntry(SQLBase, UUIDMixin, TimestampMixin):
__tablename__ = "log_entries"
level: Mapped[str] = mapped_column(
String(20), nullable=False, index=True
)
source: Mapped[str] = mapped_column(
String(100), nullable=False, index=True
)
message: Mapped[str] = mapped_column(
Text, nullable=False
)
details: Mapped[Optional[str]] = mapped_column(
Text, nullable=True
)
user_id: Mapped[Optional[UUID]] = mapped_column(
UUID(as_uuid=True),
ForeignKey("users.id", ondelete="SET NULL"),
nullable=True,
index=True,
)
+38
View File
@@ -0,0 +1,38 @@
"""Pipeline models for VoIdea - voice processing pipeline stages."""
from datetime import datetime
from typing import Optional
from sqlalchemy import Boolean, Float, ForeignKey, Integer, String
from sqlalchemy.dialects.postgresql import JSONB, UUID
from sqlalchemy.orm import Mapped, mapped_column
from app.core.base import SQLBase, TimestampMixin, UUIDMixin
class PipelineStats(SQLBase, TimestampMixin):
__tablename__ = "pipeline_stats"
id: Mapped[str] = mapped_column(
String(36), primary_key=True, default=lambda: str(__import__("uuid").uuid4())
)
user_id: Mapped[Optional[str]] = mapped_column(
String(36), ForeignKey("users.id", ondelete="CASCADE"),
nullable=True, index=True
)
stage: Mapped[str] = mapped_column(
String(50), nullable=False, index=True
)
passed: Mapped[bool] = mapped_column(
Boolean, nullable=False
)
reason: Mapped[Optional[str]] = mapped_column(
String(255), nullable=True
)
duration_ms: Mapped[Optional[int]] = mapped_column(
Integer, nullable=True
)
created_at: Mapped[datetime] = mapped_column(
nullable=False,
default=lambda: datetime.now(__import__("datetime").timezone.utc),
)
+31
View File
@@ -0,0 +1,31 @@
"""Session model — one chat session = one idea discussion."""
from typing import Optional
from sqlalchemy import ForeignKey, String, Text
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import Mapped, mapped_column
from app.core.base import SQLBase, TimestampMixin, UUIDMixin
class Session(SQLBase, UUIDMixin, TimestampMixin):
__tablename__ = "sessions"
user_id: Mapped[UUID] = mapped_column(
UUID(as_uuid=True),
ForeignKey("users.id", ondelete="CASCADE"),
nullable=False,
index=True,
)
title: Mapped[str] = mapped_column(
String(255), nullable=False, default="Новое обсуждение"
)
status: Mapped[str] = mapped_column(
String(20), nullable=False, default="active", index=True
)
idea_id: Mapped[Optional[UUID]] = mapped_column(
UUID(as_uuid=True),
ForeignKey("ideas.id", ondelete="SET NULL"),
nullable=True,
)
+69
View File
@@ -0,0 +1,69 @@
"""Tariff models for VoIdea."""
from datetime import datetime
from decimal import Decimal
from typing import Any, Optional
from sqlalchemy import Boolean, ForeignKey, Numeric, String, Text
from sqlalchemy.dialects.postgresql import JSONB, UUID
from sqlalchemy.orm import Mapped, mapped_column
from app.core.base import SQLBase, TimestampMixin, UUIDMixin
class TariffPlan(SQLBase, UUIDMixin, TimestampMixin):
__tablename__ = "tariff_plans"
name: Mapped[str] = mapped_column(
String(100), nullable=False
)
code: Mapped[str] = mapped_column(
String(50), unique=True, nullable=False, index=True
)
description: Mapped[Optional[str]] = mapped_column(
Text, nullable=True
)
price_monthly: Mapped[Decimal] = mapped_column(
Numeric(10, 2), default=0, nullable=False
)
price_yearly: Mapped[Optional[Decimal]] = mapped_column(
Numeric(10, 2), nullable=True
)
features: Mapped[Optional[dict[str, Any]]] = mapped_column(
JSONB, nullable=True, default=None
)
is_active: Mapped[bool] = mapped_column(
Boolean, default=True, nullable=False
)
sort_order: Mapped[int] = mapped_column(
default=0, nullable=False
)
class UserSubscription(SQLBase, UUIDMixin, TimestampMixin):
__tablename__ = "user_subscriptions"
user_id: Mapped[UUID] = mapped_column(
UUID(as_uuid=True),
ForeignKey("users.id", ondelete="CASCADE"),
unique=True,
nullable=False,
index=True,
)
plan_id: Mapped[UUID] = mapped_column(
UUID(as_uuid=True),
ForeignKey("tariff_plans.id", ondelete="RESTRICT"),
nullable=False,
)
status: Mapped[str] = mapped_column(
String(20), default="active", nullable=False, index=True
)
current_period_start: Mapped[Optional[datetime]] = mapped_column(
nullable=True
)
current_period_end: Mapped[Optional[datetime]] = mapped_column(
nullable=True
)
canceled_at: Mapped[Optional[datetime]] = mapped_column(
nullable=True
)
+63
View File
@@ -0,0 +1,63 @@
"""User model for VoIdea."""
from datetime import datetime
from typing import Any, Optional
from sqlalchemy import Boolean, DateTime, String
from sqlalchemy.dialects.postgresql import JSONB, UUID
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.core.base import SQLBase, TimestampMixin, UUIDMixin
class User(SQLBase, UUIDMixin, TimestampMixin):
__tablename__ = "users"
email: Mapped[str] = mapped_column(
String(255), unique=True, index=True, nullable=False
)
password_hash: Mapped[Optional[str]] = mapped_column(
String(255), nullable=True
)
display_name: Mapped[str] = mapped_column(
String(255), nullable=False
)
avatar_url: Mapped[Optional[str]] = mapped_column(
String(512), nullable=True
)
is_active: Mapped[bool] = mapped_column(
Boolean, default=True, nullable=False
)
is_superuser: Mapped[bool] = mapped_column(
Boolean, default=False, nullable=False
)
# v2.0 role system
role: Mapped[str] = mapped_column(
String(20), default="user", nullable=False, index=True
)
is_owner: Mapped[bool] = mapped_column(
Boolean, default=False, nullable=False
)
permissions: Mapped[Optional[dict[str, Any]]] = mapped_column(
JSONB, nullable=True, default=None
)
accepted_terms_at: Mapped[Optional[datetime]] = mapped_column(
nullable=True
)
accepted_terms_version: Mapped[Optional[str]] = mapped_column(
String(20), nullable=True
)
pipeline_tuning: Mapped[Optional[dict[str, Any]]] = mapped_column(
JSONB, nullable=True, default=None
)
oauth_provider: Mapped[Optional[str]] = mapped_column(
String(50), nullable=True
)
oauth_id: Mapped[Optional[str]] = mapped_column(
String(255), nullable=True
)
ideas = relationship("Idea", back_populates="user", lazy="selectin")
+35
View File
@@ -0,0 +1,35 @@
"""Voice command model — user-specific dynamic commands for self-learning."""
from typing import Optional
from sqlalchemy import Boolean, ForeignKey, Integer, String
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import Mapped, mapped_column
from app.core.base import SQLBase, TimestampMixin, UUIDMixin
class VoiceCommand(SQLBase, UUIDMixin, TimestampMixin):
__tablename__ = "voice_commands"
user_id: Mapped[UUID] = mapped_column(
UUID(as_uuid=True),
ForeignKey("users.id", ondelete="CASCADE"),
nullable=False,
index=True,
)
phrase: Mapped[str] = mapped_column(
String(255), nullable=False
)
action: Mapped[str] = mapped_column(
String(50), nullable=False
)
agent_name: Mapped[Optional[str]] = mapped_column(
String(100), nullable=True
)
count: Mapped[int] = mapped_column(
Integer, default=0, nullable=False
)
is_active: Mapped[bool] = mapped_column(
Boolean, default=True, nullable=False
)