feat: add chat summary tab in the right sidebar and per-chat cost endpoint (#26649)

Stacked on #26657 (the persisted whole-chat summary backend). Base
branch is `chat-summary-62j9`; review/merge that first.

Adds a reusable `ChatSummary` component.

The summary text is the persisted whole-chat summary (`chat.summary`)
introduced by #26657. It is generated asynchronously and may be `null`
until the first summary is produced, in which case the popover renders a
muted empty state. Live updates arrive via that PR's
`chat_summary_change` watch event, which is already merged into the chat
caches.

Cost is served by a new per-chat endpoint, `GET
/api/experimental/chats/{chat}/cost`, which rolls up assistant-message
cost across a chat's root and child (subagent) chats and is authorized
like the other `{chat}` routes (read on the chat, 404 otherwise).

Visual and interaction coverage lives in `ChatSummary.stories.tsx` and
`ChatSummaryPopover.stories.tsx` (including populated-summary,
empty-state, and cost-loading cases).

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jaayden Halko
2026-07-27 10:05:05 +01:00
committed by GitHub
co-authored by Cursor
parent 88c7304e0b
commit 6f2011af88
38 changed files with 1744 additions and 87 deletions
+15
View File
@@ -3163,6 +3163,21 @@ func (mr *MockStoreMockRecorder) GetChatModelConfigsForTelemetry(ctx any) *gomoc
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChatModelConfigsForTelemetry", reflect.TypeOf((*MockStore)(nil).GetChatModelConfigsForTelemetry), ctx)
}
// GetChatModelUsageCostByChatID mocks base method.
func (m *MockStore) GetChatModelUsageCostByChatID(ctx context.Context, chatID uuid.UUID) (database.GetChatModelUsageCostByChatIDRow, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetChatModelUsageCostByChatID", ctx, chatID)
ret0, _ := ret[0].(database.GetChatModelUsageCostByChatIDRow)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetChatModelUsageCostByChatID indicates an expected call of GetChatModelUsageCostByChatID.
func (mr *MockStoreMockRecorder) GetChatModelUsageCostByChatID(ctx, chatID any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChatModelUsageCostByChatID", reflect.TypeOf((*MockStore)(nil).GetChatModelUsageCostByChatID), ctx, chatID)
}
// GetChatPersonalModelOverridesEnabled mocks base method.
func (m *MockStore) GetChatPersonalModelOverridesEnabled(ctx context.Context) (bool, error) {
m.ctrl.T.Helper()