mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: paginate chat messages endpoint with cursor-based infinite scroll (#23083)
Adds cursor-based pagination to the chat messages endpoint. ## Backend - New `GetChatMessagesByChatIDPaginated` SQL query: returns messages in `id DESC` order with a `before_id` keyset cursor and configurable `limit` - Handler parses `?before_id=N&limit=N` query params, uses the `LIMIT N+1` trick to set `has_more` without a separate COUNT query - Queued messages only returned on the first page (no cursor) since they're always the most recent - SDK client updated with `ChatMessagesPaginationOptions` - Fully backward compatible: omitting params returns the 50 newest messages ## Frontend - Switches `getChatMessages` from `useQuery` to `useInfiniteQuery` with cursor chaining via `getNextPageParam` - Pages flattened and sorted by `id` ascending for chronological display - `MessagesPaginationSentinel` component uses `IntersectionObserver` (200px rootMargin prefetch) inside the existing `flex-col-reverse` scroll container - `flex-col-reverse` handles scroll anchoring natively when older messages are prepended — no manual `scrollTop` adjustment needed (same pattern as coder/blink) ## Why cursor-based instead of offset/limit Offset-based pagination breaks when new messages arrive while paginating backward (offsets shift, causing duplicates or missed messages). The `before_id` cursor is stable regardless of inserts — each page is deterministic.
This commit is contained in:
@@ -1105,7 +1105,7 @@ func TestCreateWorkspaceTool_EndToEnd(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, workspaceName, workspace.Name)
|
||||
|
||||
chatMsgs, err := client.GetChatMessages(ctx, chat.ID)
|
||||
chatMsgs, err := client.GetChatMessages(ctx, chat.ID, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
var foundCreateWorkspaceResult bool
|
||||
@@ -1276,7 +1276,7 @@ func TestStartWorkspaceTool_EndToEnd(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, codersdk.WorkspaceTransitionStart, updatedWorkspace.LatestBuild.Transition)
|
||||
|
||||
chatMsgs, err := client.GetChatMessages(ctx, chat.ID)
|
||||
chatMsgs, err := client.GetChatMessages(ctx, chat.ID, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify start_workspace tool result exists in the chat messages.
|
||||
|
||||
@@ -92,7 +92,7 @@ func TestAnthropicWebSearchRoundTrip(t *testing.T) {
|
||||
// Verify the chat completed and messages were persisted.
|
||||
chatData, err := client.GetChat(ctx, chat.ID)
|
||||
require.NoError(t, err)
|
||||
chatMsgs, err := client.GetChatMessages(ctx, chat.ID)
|
||||
chatMsgs, err := client.GetChatMessages(ctx, chat.ID, nil)
|
||||
require.NoError(t, err)
|
||||
t.Logf("Chat status after step 1: %s, messages: %d",
|
||||
chatData.Status, len(chatMsgs.Messages))
|
||||
@@ -154,7 +154,7 @@ func TestAnthropicWebSearchRoundTrip(t *testing.T) {
|
||||
// Verify the follow-up completed and produced content.
|
||||
chatData2, err := client.GetChat(ctx, chat.ID)
|
||||
require.NoError(t, err)
|
||||
chatMsgs2, err := client.GetChatMessages(ctx, chat.ID)
|
||||
chatMsgs2, err := client.GetChatMessages(ctx, chat.ID, nil)
|
||||
require.NoError(t, err)
|
||||
t.Logf("Chat status after step 2: %s, messages: %d",
|
||||
chatData2.Status, len(chatMsgs2.Messages))
|
||||
|
||||
+40
-10
@@ -573,9 +573,29 @@ func (api *API) getChatMessages(rw http.ResponseWriter, r *http.Request) {
|
||||
chat := httpmw.ChatParam(r)
|
||||
chatID := chat.ID
|
||||
|
||||
messages, err := api.Database.GetChatMessagesByChatID(ctx, database.GetChatMessagesByChatIDParams{
|
||||
ChatID: chatID,
|
||||
AfterID: 0,
|
||||
// Parse optional cursor-based pagination parameters.
|
||||
queryParams := r.URL.Query()
|
||||
parser := httpapi.NewQueryParamParser()
|
||||
beforeID := parser.PositiveInt64(queryParams, 0, "before_id")
|
||||
limit := parser.PositiveInt32(queryParams, 50, "limit")
|
||||
if len(parser.Errors) > 0 {
|
||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
Message: "Query parameters have invalid values.",
|
||||
Validations: parser.Errors,
|
||||
})
|
||||
return
|
||||
}
|
||||
if limit < 1 || limit > 200 {
|
||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
Message: "Invalid limit parameter (1-200).",
|
||||
})
|
||||
return
|
||||
}
|
||||
// Fetch limit+1 rows to detect whether more pages exist.
|
||||
messages, err := api.Database.GetChatMessagesByChatIDDescPaginated(ctx, database.GetChatMessagesByChatIDDescPaginatedParams{
|
||||
ChatID: chatID,
|
||||
BeforeID: beforeID,
|
||||
LimitVal: limit + 1,
|
||||
})
|
||||
if err != nil {
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
@@ -585,18 +605,28 @@ func (api *API) getChatMessages(rw http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
queuedMessages, err := api.Database.GetChatQueuedMessages(ctx, chatID)
|
||||
if err != nil {
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Failed to get queued messages.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
hasMore := len(messages) > int(limit)
|
||||
if hasMore {
|
||||
messages = messages[:limit]
|
||||
}
|
||||
|
||||
// Only fetch queued messages on the first page (no cursor).
|
||||
var queuedMessages []database.ChatQueuedMessage
|
||||
if beforeID == 0 {
|
||||
queuedMessages, err = api.Database.GetChatQueuedMessages(ctx, chatID)
|
||||
if err != nil {
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Failed to get queued messages.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
httpapi.Write(ctx, rw, http.StatusOK, codersdk.ChatMessagesResponse{
|
||||
Messages: convertChatMessages(messages),
|
||||
QueuedMessages: convertChatQueuedMessages(queuedMessages),
|
||||
HasMore: hasMore,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
+17
-17
@@ -89,7 +89,7 @@ func TestPostChats(t *testing.T) {
|
||||
|
||||
chatResult, err := client.GetChat(ctx, chat.ID)
|
||||
require.NoError(t, err)
|
||||
messagesResult, err := client.GetChatMessages(ctx, chat.ID)
|
||||
messagesResult, err := client.GetChatMessages(ctx, chat.ID, nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, chat.ID, chatResult.ID)
|
||||
|
||||
@@ -127,7 +127,7 @@ func TestPostChats(t *testing.T) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
messagesResult, err := client.GetChatMessages(ctx, chat.ID)
|
||||
messagesResult, err := client.GetChatMessages(ctx, chat.ID, nil)
|
||||
require.NoError(t, err)
|
||||
for _, message := range messagesResult.Messages {
|
||||
require.NotEqual(t, codersdk.ChatMessageRoleSystem, message.Role)
|
||||
@@ -1484,7 +1484,7 @@ func TestGetChat(t *testing.T) {
|
||||
|
||||
chatResult, err := client.GetChat(ctx, createdChat.ID)
|
||||
require.NoError(t, err)
|
||||
messagesResult, err := client.GetChatMessages(ctx, createdChat.ID)
|
||||
messagesResult, err := client.GetChatMessages(ctx, createdChat.ID, nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, createdChat.ID, chatResult.ID)
|
||||
require.Equal(t, firstUser.UserID, chatResult.OwnerID)
|
||||
@@ -1808,7 +1808,7 @@ func TestPostChatMessages(t *testing.T) {
|
||||
require.True(t, hasTextPart(created.QueuedMessage.Content, messageText))
|
||||
|
||||
require.Eventually(t, func() bool {
|
||||
messagesResult, getErr := client.GetChatMessages(ctx, chat.ID)
|
||||
messagesResult, getErr := client.GetChatMessages(ctx, chat.ID, nil)
|
||||
if getErr != nil {
|
||||
return false
|
||||
}
|
||||
@@ -1836,7 +1836,7 @@ func TestPostChatMessages(t *testing.T) {
|
||||
require.True(t, hasTextPart(created.Message.Content, messageText))
|
||||
|
||||
require.Eventually(t, func() bool {
|
||||
messagesResult, getErr := client.GetChatMessages(ctx, chat.ID)
|
||||
messagesResult, getErr := client.GetChatMessages(ctx, chat.ID, nil)
|
||||
if getErr != nil {
|
||||
return false
|
||||
}
|
||||
@@ -1951,7 +1951,7 @@ func TestChatMessageWithFileReferences(t *testing.T) {
|
||||
|
||||
var found bool
|
||||
require.Eventually(t, func() bool {
|
||||
messagesResult, getErr := client.GetChatMessages(ctx, chat.ID)
|
||||
messagesResult, getErr := client.GetChatMessages(ctx, chat.ID, nil)
|
||||
if getErr != nil {
|
||||
return false
|
||||
}
|
||||
@@ -2011,7 +2011,7 @@ func TestChatMessageWithFileReferences(t *testing.T) {
|
||||
}
|
||||
|
||||
require.Eventually(t, func() bool {
|
||||
messagesResult, getErr := client.GetChatMessages(ctx, chat.ID)
|
||||
messagesResult, getErr := client.GetChatMessages(ctx, chat.ID, nil)
|
||||
if getErr != nil {
|
||||
return false
|
||||
}
|
||||
@@ -2064,7 +2064,7 @@ func TestChatMessageWithFileReferences(t *testing.T) {
|
||||
}
|
||||
|
||||
require.Eventually(t, func() bool {
|
||||
messagesResult, getErr := client.GetChatMessages(ctx, chat.ID)
|
||||
messagesResult, getErr := client.GetChatMessages(ctx, chat.ID, nil)
|
||||
if getErr != nil {
|
||||
return false
|
||||
}
|
||||
@@ -2117,7 +2117,7 @@ func TestChatMessageWithFileReferences(t *testing.T) {
|
||||
}
|
||||
|
||||
require.Eventually(t, func() bool {
|
||||
messagesResult, getErr := client.GetChatMessages(ctx, chat.ID)
|
||||
messagesResult, getErr := client.GetChatMessages(ctx, chat.ID, nil)
|
||||
if getErr != nil {
|
||||
return false
|
||||
}
|
||||
@@ -2207,7 +2207,7 @@ func TestChatMessageWithFileReferences(t *testing.T) {
|
||||
}
|
||||
|
||||
require.Eventually(t, func() bool {
|
||||
messagesResult, getErr := client.GetChatMessages(ctx, chat.ID)
|
||||
messagesResult, getErr := client.GetChatMessages(ctx, chat.ID, nil)
|
||||
if getErr != nil {
|
||||
return false
|
||||
}
|
||||
@@ -2397,7 +2397,7 @@ func TestChatMessageWithFiles(t *testing.T) {
|
||||
}
|
||||
|
||||
// Verify file parts omit inline data in the API response.
|
||||
messagesResult, err := client.GetChatMessages(ctx, chat.ID)
|
||||
messagesResult, err := client.GetChatMessages(ctx, chat.ID, nil)
|
||||
require.NoError(t, err)
|
||||
for _, msg := range messagesResult.Messages {
|
||||
for _, part := range msg.Content {
|
||||
@@ -2493,7 +2493,7 @@ func TestPatchChatMessage(t *testing.T) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
messagesResult, err := client.GetChatMessages(ctx, chat.ID)
|
||||
messagesResult, err := client.GetChatMessages(ctx, chat.ID, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
var userMessageID int64
|
||||
@@ -2525,7 +2525,7 @@ func TestPatchChatMessage(t *testing.T) {
|
||||
}
|
||||
require.True(t, foundEditedText)
|
||||
|
||||
messagesResult, err = client.GetChatMessages(ctx, chat.ID)
|
||||
messagesResult, err = client.GetChatMessages(ctx, chat.ID, nil)
|
||||
require.NoError(t, err)
|
||||
foundEditedInChat := false
|
||||
foundOriginalInChat := false
|
||||
@@ -2578,7 +2578,7 @@ func TestPatchChatMessage(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
// Find the user message ID.
|
||||
messagesResult, err := client.GetChatMessages(ctx, chat.ID)
|
||||
messagesResult, err := client.GetChatMessages(ctx, chat.ID, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
var userMessageID int64
|
||||
@@ -2621,7 +2621,7 @@ func TestPatchChatMessage(t *testing.T) {
|
||||
require.True(t, foundFile, "edited message should preserve file_id")
|
||||
|
||||
// GET the chat messages and verify the file_id persists.
|
||||
messagesResult, err = client.GetChatMessages(ctx, chat.ID)
|
||||
messagesResult, err = client.GetChatMessages(ctx, chat.ID, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
var foundTextInChat, foundFileInChat bool
|
||||
@@ -3236,7 +3236,7 @@ func TestDeleteChatQueuedMessage(t *testing.T) {
|
||||
res.Body.Close()
|
||||
require.Equal(t, http.StatusNoContent, res.StatusCode)
|
||||
|
||||
messagesResult, err := client.GetChatMessages(ctx, chat.ID)
|
||||
messagesResult, err := client.GetChatMessages(ctx, chat.ID, nil)
|
||||
require.NoError(t, err)
|
||||
for _, queued := range messagesResult.QueuedMessages {
|
||||
require.NotEqual(t, queuedMessage.ID, queued.ID)
|
||||
@@ -3339,7 +3339,7 @@ func TestPromoteChatQueuedMessage(t *testing.T) {
|
||||
}
|
||||
require.True(t, foundPromotedText)
|
||||
|
||||
messagesResult, err := client.GetChatMessages(ctx, chat.ID)
|
||||
messagesResult, err := client.GetChatMessages(ctx, chat.ID, nil)
|
||||
require.NoError(t, err)
|
||||
for _, queued := range messagesResult.QueuedMessages {
|
||||
require.NotEqual(t, queuedMessage.ID, queued.ID)
|
||||
|
||||
@@ -2532,6 +2532,14 @@ func (q *querier) GetChatMessagesByChatID(ctx context.Context, arg database.GetC
|
||||
return q.db.GetChatMessagesByChatID(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) GetChatMessagesByChatIDDescPaginated(ctx context.Context, arg database.GetChatMessagesByChatIDDescPaginatedParams) ([]database.ChatMessage, error) {
|
||||
_, err := q.GetChatByID(ctx, arg.ChatID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return q.db.GetChatMessagesByChatIDDescPaginated(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) GetChatMessagesForPromptByChatID(ctx context.Context, chatID uuid.UUID) ([]database.ChatMessage, error) {
|
||||
// Authorize read on the parent chat.
|
||||
_, err := q.GetChatByID(ctx, chatID)
|
||||
|
||||
@@ -558,6 +558,14 @@ func (s *MethodTestSuite) TestChats() {
|
||||
dbm.EXPECT().GetChatMessagesByChatID(gomock.Any(), arg).Return(msgs, nil).AnyTimes()
|
||||
check.Args(arg).Asserts(chat, policy.ActionRead).Returns(msgs)
|
||||
}))
|
||||
s.Run("GetChatMessagesByChatIDDescPaginated", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
|
||||
chat := testutil.Fake(s.T(), faker, database.Chat{})
|
||||
msgs := []database.ChatMessage{testutil.Fake(s.T(), faker, database.ChatMessage{ChatID: chat.ID})}
|
||||
arg := database.GetChatMessagesByChatIDDescPaginatedParams{ChatID: chat.ID, BeforeID: 0, LimitVal: 50}
|
||||
dbm.EXPECT().GetChatByID(gomock.Any(), chat.ID).Return(chat, nil).AnyTimes()
|
||||
dbm.EXPECT().GetChatMessagesByChatIDDescPaginated(gomock.Any(), arg).Return(msgs, nil).AnyTimes()
|
||||
check.Args(arg).Asserts(chat, policy.ActionRead).Returns(msgs)
|
||||
}))
|
||||
s.Run("GetLastChatMessageByRole", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
|
||||
chat := testutil.Fake(s.T(), faker, database.Chat{})
|
||||
msg := testutil.Fake(s.T(), faker, database.ChatMessage{ChatID: chat.ID})
|
||||
|
||||
@@ -1063,6 +1063,14 @@ func (m queryMetricsStore) GetChatMessagesByChatID(ctx context.Context, chatID d
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) GetChatMessagesByChatIDDescPaginated(ctx context.Context, arg database.GetChatMessagesByChatIDDescPaginatedParams) ([]database.ChatMessage, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.GetChatMessagesByChatIDDescPaginated(ctx, arg)
|
||||
m.queryLatencies.WithLabelValues("GetChatMessagesByChatIDDescPaginated").Observe(time.Since(start).Seconds())
|
||||
m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "GetChatMessagesByChatIDDescPaginated").Inc()
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) GetChatMessagesForPromptByChatID(ctx context.Context, chatID uuid.UUID) ([]database.ChatMessage, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.GetChatMessagesForPromptByChatID(ctx, chatID)
|
||||
|
||||
@@ -1928,6 +1928,21 @@ func (mr *MockStoreMockRecorder) GetChatMessagesByChatID(ctx, arg any) *gomock.C
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChatMessagesByChatID", reflect.TypeOf((*MockStore)(nil).GetChatMessagesByChatID), ctx, arg)
|
||||
}
|
||||
|
||||
// GetChatMessagesByChatIDDescPaginated mocks base method.
|
||||
func (m *MockStore) GetChatMessagesByChatIDDescPaginated(ctx context.Context, arg database.GetChatMessagesByChatIDDescPaginatedParams) ([]database.ChatMessage, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetChatMessagesByChatIDDescPaginated", ctx, arg)
|
||||
ret0, _ := ret[0].([]database.ChatMessage)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetChatMessagesByChatIDDescPaginated indicates an expected call of GetChatMessagesByChatIDDescPaginated.
|
||||
func (mr *MockStoreMockRecorder) GetChatMessagesByChatIDDescPaginated(ctx, arg any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChatMessagesByChatIDDescPaginated", reflect.TypeOf((*MockStore)(nil).GetChatMessagesByChatIDDescPaginated), ctx, arg)
|
||||
}
|
||||
|
||||
// GetChatMessagesForPromptByChatID mocks base method.
|
||||
func (m *MockStore) GetChatMessagesForPromptByChatID(ctx context.Context, chatID uuid.UUID) ([]database.ChatMessage, error) {
|
||||
m.ctrl.T.Helper()
|
||||
|
||||
@@ -234,6 +234,7 @@ type sqlcQuerier interface {
|
||||
GetChatFilesByIDs(ctx context.Context, ids []uuid.UUID) ([]ChatFile, error)
|
||||
GetChatMessageByID(ctx context.Context, id int64) (ChatMessage, error)
|
||||
GetChatMessagesByChatID(ctx context.Context, arg GetChatMessagesByChatIDParams) ([]ChatMessage, error)
|
||||
GetChatMessagesByChatIDDescPaginated(ctx context.Context, arg GetChatMessagesByChatIDDescPaginatedParams) ([]ChatMessage, error)
|
||||
GetChatMessagesForPromptByChatID(ctx context.Context, chatID uuid.UUID) ([]ChatMessage, error)
|
||||
GetChatModelConfigByID(ctx context.Context, id uuid.UUID) (ChatModelConfig, error)
|
||||
GetChatModelConfigs(ctx context.Context) ([]ChatModelConfig, error)
|
||||
|
||||
@@ -3814,6 +3814,72 @@ func (q *sqlQuerier) GetChatMessagesByChatID(ctx context.Context, arg GetChatMes
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getChatMessagesByChatIDDescPaginated = `-- name: GetChatMessagesByChatIDDescPaginated :many
|
||||
SELECT
|
||||
id, chat_id, model_config_id, created_at, role, content, visibility, input_tokens, output_tokens, total_tokens, reasoning_tokens, cache_creation_tokens, cache_read_tokens, context_limit, compressed, created_by, content_version, total_cost_micros
|
||||
FROM
|
||||
chat_messages
|
||||
WHERE
|
||||
chat_id = $1::uuid
|
||||
AND CASE
|
||||
WHEN $2::bigint > 0 THEN id < $2::bigint
|
||||
ELSE true
|
||||
END
|
||||
AND visibility IN ('user', 'both')
|
||||
ORDER BY
|
||||
id DESC
|
||||
LIMIT
|
||||
COALESCE(NULLIF($3::int, 0), 50)
|
||||
`
|
||||
|
||||
type GetChatMessagesByChatIDDescPaginatedParams struct {
|
||||
ChatID uuid.UUID `db:"chat_id" json:"chat_id"`
|
||||
BeforeID int64 `db:"before_id" json:"before_id"`
|
||||
LimitVal int32 `db:"limit_val" json:"limit_val"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) GetChatMessagesByChatIDDescPaginated(ctx context.Context, arg GetChatMessagesByChatIDDescPaginatedParams) ([]ChatMessage, error) {
|
||||
rows, err := q.db.QueryContext(ctx, getChatMessagesByChatIDDescPaginated, arg.ChatID, arg.BeforeID, arg.LimitVal)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []ChatMessage
|
||||
for rows.Next() {
|
||||
var i ChatMessage
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.ChatID,
|
||||
&i.ModelConfigID,
|
||||
&i.CreatedAt,
|
||||
&i.Role,
|
||||
&i.Content,
|
||||
&i.Visibility,
|
||||
&i.InputTokens,
|
||||
&i.OutputTokens,
|
||||
&i.TotalTokens,
|
||||
&i.ReasoningTokens,
|
||||
&i.CacheCreationTokens,
|
||||
&i.CacheReadTokens,
|
||||
&i.ContextLimit,
|
||||
&i.Compressed,
|
||||
&i.CreatedBy,
|
||||
&i.ContentVersion,
|
||||
&i.TotalCostMicros,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getChatMessagesForPromptByChatID = `-- name: GetChatMessagesForPromptByChatID :many
|
||||
WITH latest_compressed_summary AS (
|
||||
SELECT
|
||||
|
||||
@@ -40,6 +40,23 @@ WHERE
|
||||
ORDER BY
|
||||
created_at ASC;
|
||||
|
||||
-- name: GetChatMessagesByChatIDDescPaginated :many
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
chat_messages
|
||||
WHERE
|
||||
chat_id = @chat_id::uuid
|
||||
AND CASE
|
||||
WHEN @before_id::bigint > 0 THEN id < @before_id::bigint
|
||||
ELSE true
|
||||
END
|
||||
AND visibility IN ('user', 'both')
|
||||
ORDER BY
|
||||
id DESC
|
||||
LIMIT
|
||||
COALESCE(NULLIF(@limit_val::int, 0), 50);
|
||||
|
||||
-- name: GetChatMessagesForPromptByChatID :many
|
||||
WITH latest_compressed_summary AS (
|
||||
SELECT
|
||||
|
||||
Reference in New Issue
Block a user