mirror of
https://github.com/leonvanzyl/autocoder.git
synced 2026-01-30 06:12:06 +00:00
fix: address performance and code quality issues in conversation history
Performance improvements: - Fix N+1 query in get_conversations() using COUNT subquery instead of len(c.messages) which triggered lazy loading for each conversation - Add SQLAlchemy engine caching to avoid creating new database connections on every request - Add React.memo to ChatMessage component to prevent unnecessary re-renders during message streaming - Move BOLD_REGEX to module scope to avoid recreating on each render Code quality improvements: - Remove 10+ console.log debug statements from AssistantChat.tsx and AssistantPanel.tsx that were left from development - Add user feedback for delete errors in ConversationHistory - dialog now stays open and shows error message instead of silently failing - Update ConfirmDialog to accept ReactNode for message prop to support rich error content These changes address issues identified in the code review of PR #74 (conversation history feature). Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -11,13 +11,17 @@ from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from sqlalchemy import Column, DateTime, ForeignKey, Integer, String, Text, create_engine
|
||||
from sqlalchemy import Column, DateTime, ForeignKey, Integer, String, Text, create_engine, func
|
||||
from sqlalchemy.orm import declarative_base, relationship, sessionmaker
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
# Engine cache to avoid creating new engines for each request
|
||||
# Key: project directory path (as posix string), Value: SQLAlchemy engine
|
||||
_engine_cache: dict[str, object] = {}
|
||||
|
||||
|
||||
def _utc_now() -> datetime:
|
||||
"""Return current UTC time. Replacement for deprecated datetime.utcnow()."""
|
||||
@@ -56,13 +60,23 @@ def get_db_path(project_dir: Path) -> Path:
|
||||
|
||||
|
||||
def get_engine(project_dir: Path):
|
||||
"""Get or create a SQLAlchemy engine for a project's assistant database."""
|
||||
db_path = get_db_path(project_dir)
|
||||
# Use as_posix() for cross-platform compatibility with SQLite connection strings
|
||||
db_url = f"sqlite:///{db_path.as_posix()}"
|
||||
engine = create_engine(db_url, echo=False)
|
||||
Base.metadata.create_all(engine)
|
||||
return engine
|
||||
"""Get or create a SQLAlchemy engine for a project's assistant database.
|
||||
|
||||
Uses a cache to avoid creating new engines for each request, which improves
|
||||
performance by reusing database connections.
|
||||
"""
|
||||
cache_key = project_dir.as_posix()
|
||||
|
||||
if cache_key not in _engine_cache:
|
||||
db_path = get_db_path(project_dir)
|
||||
# Use as_posix() for cross-platform compatibility with SQLite connection strings
|
||||
db_url = f"sqlite:///{db_path.as_posix()}"
|
||||
engine = create_engine(db_url, echo=False)
|
||||
Base.metadata.create_all(engine)
|
||||
_engine_cache[cache_key] = engine
|
||||
logger.debug(f"Created new database engine for {cache_key}")
|
||||
|
||||
return _engine_cache[cache_key]
|
||||
|
||||
|
||||
def get_session(project_dir: Path):
|
||||
@@ -94,23 +108,44 @@ def create_conversation(project_dir: Path, project_name: str, title: Optional[st
|
||||
|
||||
|
||||
def get_conversations(project_dir: Path, project_name: str) -> list[dict]:
|
||||
"""Get all conversations for a project with message counts."""
|
||||
"""Get all conversations for a project with message counts.
|
||||
|
||||
Uses a subquery for message_count to avoid N+1 query problem.
|
||||
"""
|
||||
session = get_session(project_dir)
|
||||
try:
|
||||
# Subquery to count messages per conversation (avoids N+1 query)
|
||||
message_count_subquery = (
|
||||
session.query(
|
||||
ConversationMessage.conversation_id,
|
||||
func.count(ConversationMessage.id).label("message_count")
|
||||
)
|
||||
.group_by(ConversationMessage.conversation_id)
|
||||
.subquery()
|
||||
)
|
||||
|
||||
# Join conversation with message counts
|
||||
conversations = (
|
||||
session.query(Conversation)
|
||||
session.query(
|
||||
Conversation,
|
||||
func.coalesce(message_count_subquery.c.message_count, 0).label("message_count")
|
||||
)
|
||||
.outerjoin(
|
||||
message_count_subquery,
|
||||
Conversation.id == message_count_subquery.c.conversation_id
|
||||
)
|
||||
.filter(Conversation.project_name == project_name)
|
||||
.order_by(Conversation.updated_at.desc())
|
||||
.all()
|
||||
)
|
||||
return [
|
||||
{
|
||||
"id": c.id,
|
||||
"project_name": c.project_name,
|
||||
"title": c.title,
|
||||
"created_at": c.created_at.isoformat() if c.created_at else None,
|
||||
"updated_at": c.updated_at.isoformat() if c.updated_at else None,
|
||||
"message_count": len(c.messages),
|
||||
"id": c.Conversation.id,
|
||||
"project_name": c.Conversation.project_name,
|
||||
"title": c.Conversation.title,
|
||||
"created_at": c.Conversation.created_at.isoformat() if c.Conversation.created_at else None,
|
||||
"updated_at": c.Conversation.updated_at.isoformat() if c.Conversation.updated_at else None,
|
||||
"message_count": c.message_count,
|
||||
}
|
||||
for c in conversations
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user