Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
d48b368
Add chat_comments table migration
CREDO23 Jan 15, 2026
266a5be
Add chat_comment_mentions table migration
CREDO23 Jan 15, 2026
b06b3ba
Add ChatComment model to db.py
CREDO23 Jan 15, 2026
ee68fb8
Add ChatCommentMention model to db.py
CREDO23 Jan 15, 2026
b7a167d
Add comment permissions to Permission enum
CREDO23 Jan 15, 2026
c14776f
Add comment permissions to DEFAULT_ROLE_PERMISSIONS
CREDO23 Jan 15, 2026
43939d5
Add Pydantic schemas for comments
CREDO23 Jan 15, 2026
d24759f
Add comments service with get_comments_for_message method
CREDO23 Jan 15, 2026
5d9294b
Add create_comment method to comments service
CREDO23 Jan 15, 2026
c140268
Add create_reply method to comments service
CREDO23 Jan 15, 2026
9c965d5
Add update_comment method to comments service
CREDO23 Jan 15, 2026
41e0462
Add delete_comment method to comments service
CREDO23 Jan 15, 2026
7d43f1f
Add comments routes and register in app
CREDO23 Jan 15, 2026
c793e2d
Rename comments files to chat_comments and add mention parser utility
CREDO23 Jan 15, 2026
c82a94c
Add mention processing and rendering in chat comments service
CREDO23 Jan 15, 2026
7504411
Add mention service methods (get mentions, mark as read)
CREDO23 Jan 15, 2026
4792fb7
Add mention routes (list, mark as read, mark all as read)
CREDO23 Jan 15, 2026
37612f5
feat(web): add chat comments types
CREDO23 Jan 15, 2026
5c86214
feat(web): add chat comments API service
CREDO23 Jan 15, 2026
33670ac
feat(web): add comments and mentions cache keys
CREDO23 Jan 15, 2026
134f957
feat(web): add chat comments atoms and hooks
CREDO23 Jan 15, 2026
a353fce
refactor(web): remove hardcoded staleTime from mentions atoms
CREDO23 Jan 15, 2026
62a45e2
feat(web): add member mention picker component
CREDO23 Jan 15, 2026
90a8a17
refactor(web): remove barrel file, use direct imports
CREDO23 Jan 15, 2026
8bfcfdd
feat(web): add comment composer with robust mention detection
CREDO23 Jan 15, 2026
8a1e0fb
feat(web): add comment item component
CREDO23 Jan 15, 2026
a287145
feat(web): add comment thread component
CREDO23 Jan 15, 2026
0e8bdf7
chore: remove implementation guide from tracking
CREDO23 Jan 16, 2026
66275f1
feat(web): add comment panel component
CREDO23 Jan 16, 2026
d719370
fix(web): improve comment thread layout
CREDO23 Jan 16, 2026
8de448a
feat(web): add comment trigger and improve panel empty state
CREDO23 Jan 16, 2026
09317cd
chore: formatting cleanup
CREDO23 Jan 16, 2026
111ecc7
feat(web): add threading lines to comment replies
CREDO23 Jan 16, 2026
16ebbb0
chore: minor formatting
CREDO23 Jan 16, 2026
f591a87
feat(web): add comment panel container (data layer)
CREDO23 Jan 16, 2026
6b5468b
feat: integrate comments UI into chat messages
CREDO23 Jan 16, 2026
f37bf90
feat: add comments permissions to existing roles
CREDO23 Jan 16, 2026
47de91e
feat: add display name and avatar to member picker, filter self-mentions
CREDO23 Jan 16, 2026
0e48df6
fix: position comment panel below trigger button
CREDO23 Jan 16, 2026
15c9594
fix: add spacing in comment thread, show 'Me' for current user
CREDO23 Jan 16, 2026
25eb240
chore: remove implementation guide from tracking
CREDO23 Jan 16, 2026
80e19a5
refactor: remove read tracking from mentions (prep for notification c…
CREDO23 Jan 16, 2026
985f50b
fix: use delimited format for mention highlighting
CREDO23 Jan 16, 2026
13135ec
fix: eagerly load message relationship in get_user_mentions
CREDO23 Jan 16, 2026
9d11446
feat: implement inline editing for comments
CREDO23 Jan 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,5 @@ def upgrade() -> None:

def downgrade() -> None:
"""Remove author_id column from new_chat_messages table."""
op.execute(
"""
DROP INDEX IF EXISTS ix_new_chat_messages_author_id;
ALTER TABLE new_chat_messages
DROP COLUMN IF EXISTS author_id;
"""
)
op.execute("DROP INDEX IF EXISTS ix_new_chat_messages_author_id")
op.execute("ALTER TABLE new_chat_messages DROP COLUMN IF EXISTS author_id")
52 changes: 52 additions & 0 deletions surfsense_backend/alembic/versions/66_add_chat_comments_table.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
"""Add chat_comments table for comments on AI responses

Revision ID: 66
Revises: 65
"""

from collections.abc import Sequence

from alembic import op

revision: str = "66"
down_revision: str | None = "65"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None


def upgrade() -> None:
"""Create chat_comments table."""
op.execute(
"""
CREATE TABLE IF NOT EXISTS chat_comments (
id SERIAL PRIMARY KEY,
message_id INTEGER NOT NULL REFERENCES new_chat_messages(id) ON DELETE CASCADE,
parent_id INTEGER REFERENCES chat_comments(id) ON DELETE CASCADE,
author_id UUID REFERENCES "user"(id) ON DELETE SET NULL,
content TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
)
"""
)
op.execute(
"CREATE INDEX IF NOT EXISTS idx_chat_comments_message_id ON chat_comments(message_id)"
)
op.execute(
"CREATE INDEX IF NOT EXISTS idx_chat_comments_parent_id ON chat_comments(parent_id)"
)
op.execute(
"CREATE INDEX IF NOT EXISTS idx_chat_comments_author_id ON chat_comments(author_id)"
)
op.execute(
"CREATE INDEX IF NOT EXISTS idx_chat_comments_created_at ON chat_comments(created_at)"
)


def downgrade() -> None:
"""Drop chat_comments table."""
op.execute(
"""
DROP TABLE IF EXISTS chat_comments;
"""
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"""Add chat_comment_mentions table for @mentions in comments

Revision ID: 67
Revises: 66
"""

from collections.abc import Sequence

from alembic import op

revision: str = "67"
down_revision: str | None = "66"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None


def upgrade() -> None:
"""Create chat_comment_mentions table."""
op.execute(
"""
CREATE TABLE IF NOT EXISTS chat_comment_mentions (
id SERIAL PRIMARY KEY,
comment_id INTEGER NOT NULL REFERENCES chat_comments(id) ON DELETE CASCADE,
mentioned_user_id UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE (comment_id, mentioned_user_id)
)
"""
)
op.execute(
"CREATE INDEX IF NOT EXISTS idx_chat_comment_mentions_comment_id ON chat_comment_mentions(comment_id)"
)


def downgrade() -> None:
"""Drop chat_comment_mentions table."""
op.execute(
"""
DROP TABLE IF EXISTS chat_comment_mentions;
"""
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
"""Add comments permissions to existing roles

Revision ID: 68
Revises: 67
Create Date: 2024-01-16

"""

from alembic import op
from sqlalchemy import text

# revision identifiers, used by Alembic.
revision = "68"
down_revision = "67"
branch_labels = None
depends_on = None


def upgrade():
connection = op.get_bind()

# Add comments:create to Admin, Editor, Viewer roles (if not already present)
connection.execute(
text(
"""
UPDATE search_space_roles
SET permissions = array_append(permissions, 'comments:create')
WHERE name IN ('Admin', 'Editor', 'Viewer')
AND NOT ('comments:create' = ANY(permissions))
"""
)
)

# Add comments:read to Admin, Editor, Viewer roles (if not already present)
connection.execute(
text(
"""
UPDATE search_space_roles
SET permissions = array_append(permissions, 'comments:read')
WHERE name IN ('Admin', 'Editor', 'Viewer')
AND NOT ('comments:read' = ANY(permissions))
"""
)
)

# Add comments:delete to Admin roles only (if not already present)
connection.execute(
text(
"""
UPDATE search_space_roles
SET permissions = array_append(permissions, 'comments:delete')
WHERE name = 'Admin'
AND NOT ('comments:delete' = ANY(permissions))
"""
)
)


def downgrade():
connection = op.get_bind()

# Remove comments:create from Admin, Editor, Viewer roles
connection.execute(
text(
"""
UPDATE search_space_roles
SET permissions = array_remove(permissions, 'comments:create')
WHERE name IN ('Admin', 'Editor', 'Viewer')
"""
)
)

# Remove comments:read from Admin, Editor, Viewer roles
connection.execute(
text(
"""
UPDATE search_space_roles
SET permissions = array_remove(permissions, 'comments:read')
WHERE name IN ('Admin', 'Editor', 'Viewer')
"""
)
)

# Remove comments:delete from Admin roles only
connection.execute(
text(
"""
UPDATE search_space_roles
SET permissions = array_remove(permissions, 'comments:delete')
WHERE name = 'Admin'
"""
)
)
93 changes: 93 additions & 0 deletions surfsense_backend/app/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,11 @@ class Permission(str, Enum):
CHATS_UPDATE = "chats:update"
CHATS_DELETE = "chats:delete"

# Comments
COMMENTS_CREATE = "comments:create"
COMMENTS_READ = "comments:read"
COMMENTS_DELETE = "comments:delete"

# LLM Configs
LLM_CONFIGS_CREATE = "llm_configs:create"
LLM_CONFIGS_READ = "llm_configs:read"
Expand Down Expand Up @@ -209,6 +214,10 @@ class Permission(str, Enum):
Permission.CHATS_READ.value,
Permission.CHATS_UPDATE.value,
Permission.CHATS_DELETE.value,
# Comments
Permission.COMMENTS_CREATE.value,
Permission.COMMENTS_READ.value,
Permission.COMMENTS_DELETE.value,
# LLM Configs
Permission.LLM_CONFIGS_CREATE.value,
Permission.LLM_CONFIGS_READ.value,
Expand Down Expand Up @@ -252,6 +261,9 @@ class Permission(str, Enum):
Permission.CHATS_READ.value,
Permission.CHATS_UPDATE.value,
Permission.CHATS_DELETE.value,
# Comments (no delete)
Permission.COMMENTS_CREATE.value,
Permission.COMMENTS_READ.value,
# LLM Configs (read only)
Permission.LLM_CONFIGS_READ.value,
Permission.LLM_CONFIGS_CREATE.value,
Expand Down Expand Up @@ -279,6 +291,9 @@ class Permission(str, Enum):
Permission.DOCUMENTS_READ.value,
# Chats (read only)
Permission.CHATS_READ.value,
# Comments (no delete)
Permission.COMMENTS_CREATE.value,
Permission.COMMENTS_READ.value,
# LLM Configs (read only)
Permission.LLM_CONFIGS_READ.value,
# Podcasts (read only)
Expand Down Expand Up @@ -424,6 +439,84 @@ class NewChatMessage(BaseModel, TimestampMixin):
# Relationships
thread = relationship("NewChatThread", back_populates="messages")
author = relationship("User")
comments = relationship(
"ChatComment",
back_populates="message",
cascade="all, delete-orphan",
)


class ChatComment(BaseModel, TimestampMixin):
"""
Comment model for comments on AI chat responses.
Supports one level of nesting (replies to comments, but no replies to replies).
"""

__tablename__ = "chat_comments"

message_id = Column(
Integer,
ForeignKey("new_chat_messages.id", ondelete="CASCADE"),
nullable=False,
index=True,
)
parent_id = Column(
Integer,
ForeignKey("chat_comments.id", ondelete="CASCADE"),
nullable=True,
index=True,
)
author_id = Column(
UUID(as_uuid=True),
ForeignKey("user.id", ondelete="SET NULL"),
nullable=True,
index=True,
)
content = Column(Text, nullable=False)
updated_at = Column(
TIMESTAMP(timezone=True),
nullable=False,
default=lambda: datetime.now(UTC),
onupdate=lambda: datetime.now(UTC),
index=True,
)

# Relationships
message = relationship("NewChatMessage", back_populates="comments")
author = relationship("User")
parent = relationship(
"ChatComment", remote_side="ChatComment.id", backref="replies"
)
mentions = relationship(
"ChatCommentMention",
back_populates="comment",
cascade="all, delete-orphan",
)


class ChatCommentMention(BaseModel, TimestampMixin):
"""
Tracks @mentions in chat comments for notification purposes.
"""

__tablename__ = "chat_comment_mentions"

comment_id = Column(
Integer,
ForeignKey("chat_comments.id", ondelete="CASCADE"),
nullable=False,
index=True,
)
mentioned_user_id = Column(
UUID(as_uuid=True),
ForeignKey("user.id", ondelete="CASCADE"),
nullable=False,
index=True,
)

# Relationships
comment = relationship("ChatComment", back_populates="mentions")
mentioned_user = relationship("User")


class Document(BaseModel, TimestampMixin):
Expand Down
2 changes: 2 additions & 0 deletions surfsense_backend/app/routes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from .airtable_add_connector_route import (
router as airtable_add_connector_router,
)
from .chat_comments_routes import router as chat_comments_router
from .circleback_webhook_route import router as circleback_webhook_router
from .clickup_add_connector_route import router as clickup_add_connector_router
from .confluence_add_connector_route import router as confluence_add_connector_router
Expand Down Expand Up @@ -42,6 +43,7 @@
router.include_router(documents_router)
router.include_router(notes_router)
router.include_router(new_chat_router) # Chat with assistant-ui persistence
router.include_router(chat_comments_router)
router.include_router(podcasts_router) # Podcast task status and audio
router.include_router(search_source_connectors_router)
router.include_router(google_calendar_add_connector_router)
Expand Down
Loading
Loading