Skip to content

Commit 77069d7

Browse files
authored
enhance: include timestamps on history return (#583)
2 parents 00fba18 + a13e7de commit 77069d7

File tree

2 files changed

+35
-3
lines changed

2 files changed

+35
-3
lines changed

server/app/controller/chat/history_controller.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from fastapi_pagination.ext.sqlmodel import paginate
44
from app.model.chat.chat_history import ChatHistoryOut, ChatHistoryIn, ChatHistory, ChatHistoryUpdate
55
from fastapi_babel import _
6-
from sqlmodel import Session, select, desc
6+
from sqlmodel import Session, select, desc, case
77
from app.component.auth import Auth, auth_must
88
from app.component.database import session
99
from utils import traceroot_wrapper as traceroot
@@ -38,7 +38,19 @@ def create_chat_history(data: ChatHistoryIn, session: Session = Depends(session)
3838
def list_chat_history(session: Session = Depends(session), auth: Auth = Depends(auth_must)) -> Page[ChatHistoryOut]:
3939
"""List chat histories for current user."""
4040
user_id = auth.user.id
41-
stmt = select(ChatHistory).where(ChatHistory.user_id == user_id).order_by(desc(ChatHistory.created_at))
41+
42+
# Order by created_at descending, but fallback to id descending for old records without timestamps
43+
# This ensures newer records with timestamps come first, followed by old records ordered by id
44+
stmt = (
45+
select(ChatHistory)
46+
.where(ChatHistory.user_id == user_id)
47+
.order_by(
48+
desc(case((ChatHistory.created_at.is_(None), 0), else_=1)), # Non-null created_at first
49+
desc(ChatHistory.created_at), # Then by created_at descending
50+
desc(ChatHistory.id) # Finally by id descending for records with same/null created_at
51+
)
52+
)
53+
4254
result = paginate(session, stmt)
4355
total = result.total if hasattr(result, 'total') else 0
4456
logger.debug("Chat histories listed", extra={"user_id": user_id, "total": total})

server/app/model/chat/chat_history.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from sqlmodel import Field, SmallInteger, Column, JSON, String
33
from typing import Optional
44
from enum import IntEnum
5+
from datetime import datetime
56
from sqlalchemy_utils import ChoiceType
67
from app.model.abstract.model import AbstractModel, DefaultTimes
78
from pydantic import BaseModel, model_validator
@@ -13,6 +14,16 @@ class ChatStatus(IntEnum):
1314

1415

1516
class ChatHistory(AbstractModel, DefaultTimes, table=True):
17+
"""
18+
Chat history model with timestamp tracking.
19+
20+
Inherits from DefaultTimes which provides:
21+
- created_at: timestamp when record is created (auto-populated)
22+
- updated_at: timestamp when record is last modified (auto-updated)
23+
- deleted_at: timestamp for soft deletion (nullable)
24+
25+
For legacy records without timestamps, sorting falls back to id ordering.
26+
"""
1627
id: int = Field(default=None, primary_key=True)
1728
user_id: int = Field(index=True)
1829
task_id: str = Field(index=True, unique=True)
@@ -70,13 +81,22 @@ class ChatHistoryOut(BaseModel):
7081
summary: str | None = None
7182
tokens: int
7283
status: int
84+
created_at: Optional[datetime] = None
85+
updated_at: Optional[datetime] = None
7386

7487
@model_validator(mode="after")
7588
def fill_project_id_from_task_id(self):
76-
"""fill by task_id when project_id is None"""
89+
"""Fill project_id from task_id when project_id is None"""
7790
if self.project_id is None:
7891
self.project_id = self.task_id
7992
return self
93+
94+
@model_validator(mode="after")
95+
def handle_legacy_timestamps(self):
96+
"""Handle legacy records that might not have timestamp fields"""
97+
# For old records without timestamps, we rely on database-level defaults
98+
# The sorting in the controller will handle ordering appropriately
99+
return self
80100

81101

82102
class ChatHistoryUpdate(BaseModel):

0 commit comments

Comments
 (0)