Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 8 additions & 0 deletions common/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,16 @@ func UsingLogDatabase(databaseType DatabaseType) bool {

var chatLogDatabaseType = DatabaseTypeSQLite

func ChatLogDatabaseType() DatabaseType {
return chatLogDatabaseType
}

func SetChatLogDatabaseType(databaseType DatabaseType) {
chatLogDatabaseType = databaseType
}

func UsingChatLogDatabase(databaseType DatabaseType) bool {
return chatLogDatabaseType == databaseType
}

var SQLitePath = "one-api.db?_busy_timeout=30000"
145 changes: 118 additions & 27 deletions controller/chat_log.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,42 @@ import (
"github.com/gin-gonic/gin"
)

type chatSessionMeta struct {
Id int `json:"id"`
TokenId int `json:"token_id"`
UserId int `json:"user_id"`
ModelName string `json:"model_name"`
TurnCount int `json:"turn_count"`
MessageCount int `json:"message_count"`
CreatedAt int64 `json:"created_at"`
LastActiveAt int64 `json:"last_active_at"`
}

func toChatSessionMeta(s *model.ChatSession) chatSessionMeta {
return chatSessionMeta{
Id: s.Id, TokenId: s.TokenId, UserId: s.UserId, ModelName: s.ModelName,
TurnCount: s.TurnCount, MessageCount: s.MessageCount,
CreatedAt: s.CreatedAt, LastActiveAt: s.LastActiveAt,
}
}

// normalizeChatLogPageLimit clamps the page-size query param once, before the
// hot-cache and database paths branch, so both see the same default (20) and
// ceiling (100).
func normalizeChatLogPageLimit(raw string) int {
limit, _ := strconv.Atoi(raw)
if limit < 1 {
limit = 20
}
if limit > 100 {
limit = 100
}
return limit
}

// AdminGetChatSessions lists sessions newest-first using cursor pagination.
// The unfiltered first page is served from the hot cache when possible; every
// other query falls through to keyset queries on the chat-log database.
func AdminGetChatSessions(c *gin.Context) {
if !model.ChatLogDBEnabled() {
c.JSON(http.StatusOK, gin.H{"success": false, "message": "对话详情库未配置"})
Expand All @@ -16,35 +52,60 @@ func AdminGetChatSessions(c *gin.Context) {
tokenId, _ := strconv.Atoi(c.Query("token_id"))
userId, _ := strconv.Atoi(c.Query("user_id"))
modelName := c.Query("model_name")
page, _ := strconv.Atoi(c.Query("page"))
pageSize, _ := strconv.Atoi(c.Query("page_size"))
startTs, _ := strconv.ParseInt(c.Query("start_ts"), 10, 64)
endTs, _ := strconv.ParseInt(c.Query("end_ts"), 10, 64)
limit := normalizeChatLogPageLimit(c.Query("limit"))
cursorStr := c.Query("cursor")

sessions, total, err := model.SearchChatSessions(tokenId, userId, modelName, page, pageSize)
if err != nil {
c.JSON(http.StatusOK, gin.H{"success": false, "message": err.Error()})
return
filter := model.ChatSessionFilter{
TokenId: tokenId, UserId: userId, ModelName: modelName,
StartTs: startTs, EndTs: endTs,
}
type chatSessionMeta struct {
Id int `json:"id"`
TokenId int `json:"token_id"`
UserId int `json:"user_id"`
ModelName string `json:"model_name"`
TurnCount int `json:"turn_count"`
MessageCount int `json:"message_count"`
CreatedAt int64 `json:"created_at"`
LastActiveAt int64 `json:"last_active_at"`

var sessions []*model.ChatSession
var hasMore bool
servedFromHot := false
if filter.Empty() && cursorStr == "" {
// 近期会话默认视图:优先走内存热缓存,未命中或刷新失败再回源数据库
if hot, hotHasMore, ok := model.ListRecentChatSessions(limit); ok {
sessions, hasMore, servedFromHot = hot, hotHasMore, true
}
}
if !servedFromHot {
cursor, err := model.DecodeChatSessionCursor(cursorStr)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "无效的分页游标"})
return
}
sessions, hasMore, err = model.ListChatSessions(filter, cursor, limit)
if err != nil {
c.JSON(http.StatusOK, gin.H{"success": false, "message": err.Error()})
return
}
}
out := make([]chatSessionMeta, 0, len(sessions))

items := make([]chatSessionMeta, 0, len(sessions))
for _, s := range sessions {
out = append(out, chatSessionMeta{
Id: s.Id, TokenId: s.TokenId, UserId: s.UserId, ModelName: s.ModelName,
TurnCount: s.TurnCount, MessageCount: s.MessageCount,
CreatedAt: s.CreatedAt, LastActiveAt: s.LastActiveAt,
})
items = append(items, toChatSessionMeta(s))
}
c.JSON(http.StatusOK, gin.H{"success": true, "data": out, "total": total})
var nextCursor any
if hasMore && len(sessions) > 0 {
last := sessions[len(sessions)-1]
nextCursor = model.ChatSessionCursor{LastActiveAt: last.LastActiveAt, Id: last.Id}.Encode()
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"data": gin.H{
"items": items,
"has_more": hasMore,
"next_cursor": nextCursor,
},
})
}

// AdminGetChatSessionDetail returns a session's metadata plus one page of
// turns (ascending). The newest page is served from the hot cache when
// available; older pages cold-load from the database via before_id.
func AdminGetChatSessionDetail(c *gin.Context) {
if !model.ChatLogDBEnabled() {
c.JSON(http.StatusOK, gin.H{"success": false, "message": "对话详情库未配置"})
Expand All @@ -55,15 +116,45 @@ func AdminGetChatSessionDetail(c *gin.Context) {
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "无效 ID"})
return
}
limit := normalizeChatLogPageLimit(c.Query("limit"))
beforeId, _ := strconv.Atoi(c.Query("before_id"))

session, err := model.GetChatSessionById(id)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"success": false, "message": "会话不存在"})
return
}
turns, err := model.GetChatTurnsBySessionId(id)
if err != nil {
c.JSON(http.StatusOK, gin.H{"success": false, "message": err.Error()})
return

var turns []*model.ChatTurn
var hasMore bool
servedFromHot := false
if beforeId == 0 {
if hot, hotHasMore, ok := model.GetHotChatTurns(id, session.TurnCount, limit); ok {
turns, hasMore, servedFromHot = hot, hotHasMore, true
}
}
if !servedFromHot {
turns, hasMore, err = model.GetChatTurnsPage(id, beforeId, limit)
if err != nil {
c.JSON(http.StatusOK, gin.H{"success": false, "message": err.Error()})
return
}
if beforeId == 0 {
model.AdmitChatTurns(id, turns, session.TurnCount)
}
}

var nextTurnId any
if hasMore && len(turns) > 0 {
nextTurnId = turns[0].Id
}
c.JSON(http.StatusOK, gin.H{"success": true, "data": gin.H{"session": session, "turns": turns}})
c.JSON(http.StatusOK, gin.H{
"success": true,
"data": gin.H{
"session": session,
"turns": turns,
"has_more": hasMore,
"next_turn_id": nextTurnId,
},
})
}
Loading
Loading