diff --git a/backend/alembic/env.py b/backend/alembic/env.py index 2361292a..2b521236 100644 --- a/backend/alembic/env.py +++ b/backend/alembic/env.py @@ -16,6 +16,7 @@ from app.models.copilot_conversation import CopilotConversation from app.models.assistant_chat import AssistantChat from app.models.survey_response import SurveyResponse from app.models.survey_invite import SurveyInvite +from app.models.ai_suggestion import AISuggestion # noqa: F401 from app.core.config import settings # this is the Alembic Config object diff --git a/backend/alembic/versions/052_add_ai_suggestion_table.py b/backend/alembic/versions/052_add_ai_suggestion_table.py new file mode 100644 index 00000000..0727d945 --- /dev/null +++ b/backend/alembic/versions/052_add_ai_suggestion_table.py @@ -0,0 +1,37 @@ +"""add ai suggestion table + +Revision ID: 052 +Revises: 051 +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects.postgresql import UUID, JSONB + +revision = "052" +down_revision = "051" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + op.create_table( + "ai_suggestions", + sa.Column("id", UUID(as_uuid=True), primary_key=True), + sa.Column("tree_id", UUID(as_uuid=True), sa.ForeignKey("trees.id", ondelete="CASCADE"), nullable=False), + sa.Column("user_id", UUID(as_uuid=True), sa.ForeignKey("users.id", ondelete="CASCADE"), nullable=False), + sa.Column("session_id", UUID(as_uuid=True), sa.ForeignKey("ai_chat_sessions.id", ondelete="SET NULL"), nullable=True), + sa.Column("action_type", sa.String(50), nullable=False), + sa.Column("target_node_id", sa.String(255), nullable=True), + sa.Column("changes_json", JSONB, nullable=False, server_default="{}"), + sa.Column("status", sa.String(20), nullable=False, server_default="pending"), + sa.Column("created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()), + sa.Column("resolved_at", sa.DateTime(timezone=True), nullable=True), + ) + op.create_index("ix_ai_suggestions_tree_id", "ai_suggestions", ["tree_id"]) + op.create_index("ix_ai_suggestions_user_id", "ai_suggestions", ["user_id"]) + + +def downgrade() -> None: + op.drop_index("ix_ai_suggestions_user_id", table_name="ai_suggestions") + op.drop_index("ix_ai_suggestions_tree_id", table_name="ai_suggestions") + op.drop_table("ai_suggestions") diff --git a/backend/app/models/ai_suggestion.py b/backend/app/models/ai_suggestion.py new file mode 100644 index 00000000..8ee65dd5 --- /dev/null +++ b/backend/app/models/ai_suggestion.py @@ -0,0 +1,55 @@ +"""AI Suggestion model for tracking AI-applied changes to flows.""" +import uuid +from datetime import datetime, timezone +from typing import Optional + +from sqlalchemy import DateTime, ForeignKey, String +from sqlalchemy.dialects.postgresql import JSONB, UUID +from sqlalchemy.orm import Mapped, mapped_column + +from app.core.database import Base + + +class AISuggestion(Base): + __tablename__ = "ai_suggestions" + + id: Mapped[uuid.UUID] = mapped_column( + UUID(as_uuid=True), primary_key=True, default=uuid.uuid4 + ) + tree_id: Mapped[uuid.UUID] = mapped_column( + UUID(as_uuid=True), + ForeignKey("trees.id", ondelete="CASCADE"), + nullable=False, + index=True, + ) + user_id: Mapped[uuid.UUID] = mapped_column( + UUID(as_uuid=True), + ForeignKey("users.id", ondelete="CASCADE"), + nullable=False, + index=True, + ) + session_id: Mapped[Optional[uuid.UUID]] = mapped_column( + UUID(as_uuid=True), + ForeignKey("ai_chat_sessions.id", ondelete="SET NULL"), + nullable=True, + ) + action_type: Mapped[str] = mapped_column( + String(50), nullable=False + ) + target_node_id: Mapped[Optional[str]] = mapped_column( + String(255), nullable=True + ) + changes_json: Mapped[dict] = mapped_column( + JSONB, nullable=False, default=dict + ) + status: Mapped[str] = mapped_column( + String(20), nullable=False, default="pending" + ) + created_at: Mapped[datetime] = mapped_column( + DateTime(timezone=True), + default=lambda: datetime.now(timezone.utc), + nullable=False, + ) + resolved_at: Mapped[Optional[datetime]] = mapped_column( + DateTime(timezone=True), nullable=True + )