I3eka commented on code in PR #43133:
URL: https://github.com/apache/superset/pull/43133#discussion_r4002586099


##########
superset/models/ai.py:
##########
@@ -0,0 +1,270 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""
+Persistence for AI assistant conversations.
+
+Conversations live in Superset's own metadata database, which keeps the
+feature deployable with no extra infrastructure and makes ownership
+enforceable with the same DAO filters used everywhere else.
+
+These models live under ``superset/models/`` rather than inside
+``superset/ai/`` because background workers need them without importing the
+API module.
+"""
+
+from __future__ import annotations
+
+import uuid as uuid_module
+from typing import Any
+
+import sqlalchemy as sa
+from flask_appbuilder import Model
+from sqlalchemy.orm import relationship, validates
+from sqlalchemy_utils import UUIDType
+
+from superset.ai.types import (
+    MessageExtra,
+    MessageRole,
+    MessageStatus,
+    ThreadStatus,
+)
+from superset.models.helpers import AuditMixinNullable
+from superset.utils import json
+from superset.utils.core import MediumText
+
+#: Bumped when the meaning of keys inside an ``extra_json`` blob changes, so a
+#: reader can tell an old row from a new one instead of guessing.
+EXTRA_JSON_VERSION = 1
+
+
+class AIChatThread(AuditMixinNullable, Model):
+    """
+    One conversation between a user and the assistant.
+
+    Ownership is expressed through ``created_by_fk`` (supplied by
+    :class:`AuditMixinNullable`) and enforced by the DAO's base filter, so a
+    thread identifier is not by itself a capability.
+    """
+
+    __tablename__ = "ai_chat_threads"
+    __table_args__ = (
+        # Serves the "my threads, most recent first" list query.
+        sa.Index("ix_ai_chat_threads_owner_recent", "created_by_fk", 
"changed_on"),
+    )
+
+    id = sa.Column(sa.Integer, primary_key=True)
+    #: The only identifier exposed over HTTP. Integer ids stay internal.
+    uuid = sa.Column(
+        UUIDType(binary=True),
+        nullable=False,
+        unique=True,
+        default=uuid_module.uuid4,
+    )
+
+    title = sa.Column(sa.String(512), nullable=True)
+    status = sa.Column(
+        sa.String(32),
+        nullable=False,
+        default=ThreadStatus.ACTIVE.value,
+        server_default=ThreadStatus.ACTIVE.value,
+    )
+    #: Which agent profile this thread was last run with.
+    agent_key = sa.Column(sa.String(64), nullable=True)
+    extra_json = sa.Column(MediumText(), nullable=True)
+
+    # No ``passive_deletes``: SQLite does not enforce foreign keys unless
+    # ``PRAGMA foreign_keys=ON``, so deferring the cascade to the database
+    # would orphan messages there. The ORM deletes children itself, and the
+    # ``ON DELETE CASCADE`` in the DDL remains a backstop for direct SQL.
+    messages = relationship(
+        "AIChatMessage",
+        back_populates="thread",
+        cascade="all, delete-orphan",
+        order_by="AIChatMessage.created_on",
+    )
+
+    def __repr__(self) -> str:
+        return f"<AIChatThread {self.uuid} [{self.status}]>"
+
+    @validates("status")
+    def _validate_status(self, _key: str, value: Any) -> str:
+        """Reject unknown lifecycle values at assignment time."""
+        return ThreadStatus(value).value
+
+    @property
+    def message_count(self) -> int:
+        """
+        Number of stored messages.
+
+        Derived rather than denormalised: a counter column would have to be 
kept
+        in step with cascade deletes and retention pruning, and a counter that
+        drifts is worse than one query — it reports a conversation length 
nobody
+        can reconcile against the rows.
+        """
+        return len(self.messages)

Review Comment:
   The conversation-list count/pagination issue is still open in the inherited 
DAO/model/API path. This refresh does not replace transcript hydration with 
aggregate counts or bound the requested page size. Keeping it open for the base 
list-query fix.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to