mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
feat(chats): archive chats instead of hard-deleting them (#22406)
## Summary
The UI has always labeled the action as "Archive agent" but the backend
was performing a hard `DELETE`, permanently destroying chats and all
their messages.
This change replaces the hard delete with a soft archive, consistent
with the pattern used by template versions.
## Changes
### Database
- **Migration 000423**: Add `archived boolean DEFAULT false NOT NULL`
column to `chats` table
- Replace `DeleteChatByID` query with `ArchiveChatByID` (`UPDATE SET
archived = true`)
- Add `UnarchiveChatByID` query (`UPDATE SET archived = false`)
- Filter archived chats from `GetChatsByOwnerID` (`WHERE archived =
false`)
### API
- Remove `DELETE /api/experimental/chats/{chat}`
- Add `POST /api/experimental/chats/{chat}/archive` — archives a chat
and all its descendants
- Add `POST /api/experimental/chats/{chat}/unarchive` — unarchives a
single chat (API only, no UI yet)
### Backend
- `archiveChatTree()` recursively archives child chats (replaces
`deleteChatTree()` which hard-deleted)
- Chat daemon's `ArchiveChat()` archives the full chat tree in a
transaction
- Authorization uses `ActionUpdate` instead of `ActionDelete`
### SDK
- Replace `DeleteChat()` with `ArchiveChat()` and `UnarchiveChat()`
- Add `Archived` field to `Chat` struct
### Frontend
- `archiveChat` API call uses `POST .../archive` instead of `DELETE`
- No UI changes — the "Archive agent" button now actually archives
instead of deleting
## Design Decision
This follows the **template version archive pattern** (Pattern B in the
codebase):
- `archived boolean` column (not `deleted boolean`)
- Dedicated `POST .../archive` and `POST .../unarchive` routes (not
repurposing `DELETE`)
- Reversible — users can unarchive via the API (UI for this will come
later)
This commit is contained in:
Generated
+28
@@ -481,6 +481,34 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/chats/{chat}/archive": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"Chats"
|
||||
],
|
||||
"summary": "Archive a chat",
|
||||
"operationId": "archive-chat",
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "No Content"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/chats/{chat}/unarchive": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"Chats"
|
||||
],
|
||||
"summary": "Unarchive a chat",
|
||||
"operationId": "unarchive-chat",
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "No Content"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/connectionlog": {
|
||||
"get": {
|
||||
"security": [
|
||||
|
||||
Generated
+24
@@ -410,6 +410,30 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/chats/{chat}/archive": {
|
||||
"post": {
|
||||
"tags": ["Chats"],
|
||||
"summary": "Archive a chat",
|
||||
"operationId": "archive-chat",
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "No Content"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/chats/{chat}/unarchive": {
|
||||
"post": {
|
||||
"tags": ["Chats"],
|
||||
"summary": "Unarchive a chat",
|
||||
"operationId": "unarchive-chat",
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "No Content"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/connectionlog": {
|
||||
"get": {
|
||||
"security": [
|
||||
|
||||
@@ -505,8 +505,8 @@ func (p *Server) EditMessage(
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// DeleteChat removes a chat and all descendants, then broadcasts a deleted event.
|
||||
func (p *Server) DeleteChat(ctx context.Context, chatID uuid.UUID) error {
|
||||
// ArchiveChat archives a chat and all descendants, then broadcasts a deleted event.
|
||||
func (p *Server) ArchiveChat(ctx context.Context, chatID uuid.UUID) error {
|
||||
if chatID == uuid.Nil {
|
||||
return xerrors.New("chat_id is required")
|
||||
}
|
||||
@@ -517,7 +517,7 @@ func (p *Server) DeleteChat(ctx context.Context, chatID uuid.UUID) error {
|
||||
}
|
||||
|
||||
err = p.db.InTx(func(tx database.Store) error {
|
||||
// Collect descendants breadth-first, then delete from leaves upward.
|
||||
// Collect descendants breadth-first, then archive from leaves upward.
|
||||
descendantIDs := make([]uuid.UUID, 0)
|
||||
queue := []uuid.UUID{chatID}
|
||||
for len(queue) > 0 {
|
||||
@@ -535,13 +535,13 @@ func (p *Server) DeleteChat(ctx context.Context, chatID uuid.UUID) error {
|
||||
}
|
||||
|
||||
for i := len(descendantIDs) - 1; i >= 0; i-- {
|
||||
if err := tx.DeleteChatByID(ctx, descendantIDs[i]); err != nil {
|
||||
return xerrors.Errorf("delete descendant chat %s: %w", descendantIDs[i], err)
|
||||
if err := tx.ArchiveChatByID(ctx, descendantIDs[i]); err != nil {
|
||||
return xerrors.Errorf("archive descendant chat %s: %w", descendantIDs[i], err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := tx.DeleteChatByID(ctx, chatID); err != nil {
|
||||
return xerrors.Errorf("delete chat: %w", err)
|
||||
if err := tx.ArchiveChatByID(ctx, chatID); err != nil {
|
||||
return xerrors.Errorf("archive chat: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
+52
-44
@@ -394,21 +394,31 @@ func (api *API) getChat(rw http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
// EXPERIMENTAL: this endpoint is experimental and is subject to change.
|
||||
func (api *API) deleteChat(rw http.ResponseWriter, r *http.Request) {
|
||||
// @Summary Archive a chat
|
||||
// @ID archive-chat
|
||||
// @Tags Chats
|
||||
// @Success 204
|
||||
// @Router /chats/{chat}/archive [post]
|
||||
func (api *API) archiveChat(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
chat := httpmw.ChatParam(r)
|
||||
chatID := chat.ID
|
||||
|
||||
if chat.Archived {
|
||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
Message: "Chat is already archived.",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var err error
|
||||
if api.chatDaemon != nil {
|
||||
err = api.chatDaemon.DeleteChat(ctx, chatID)
|
||||
err = api.chatDaemon.ArchiveChat(ctx, chat.ID)
|
||||
} else {
|
||||
err = deleteChatTree(ctx, api.Database, chatID)
|
||||
err = archiveChatTree(ctx, api.Database, chat.ID)
|
||||
}
|
||||
if err != nil {
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Failed to delete chat.",
|
||||
Message: "Failed to archive chat.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
@@ -417,47 +427,45 @@ func (api *API) deleteChat(rw http.ResponseWriter, r *http.Request) {
|
||||
rw.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
func deleteChatTree(
|
||||
ctx context.Context,
|
||||
store database.Store,
|
||||
chatID uuid.UUID,
|
||||
) error {
|
||||
// Child chats (sub-agent chats) reference their parent via
|
||||
// parent_chat_id with ON DELETE SET NULL, so without explicit
|
||||
// cleanup they would become orphaned root-level items.
|
||||
return store.InTx(func(tx database.Store) error {
|
||||
// Recursively collect all descendant chat IDs.
|
||||
var descendantIDs []uuid.UUID
|
||||
queue := []uuid.UUID{chatID}
|
||||
for len(queue) > 0 {
|
||||
parentID := queue[0]
|
||||
queue = queue[1:]
|
||||
children, err := tx.ListChildChatsByParentID(ctx, parentID)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("list children of chat %s: %w", parentID, err)
|
||||
}
|
||||
for _, child := range children {
|
||||
descendantIDs = append(descendantIDs, child.ID)
|
||||
queue = append(queue, child.ID)
|
||||
}
|
||||
}
|
||||
// @Summary Unarchive a chat
|
||||
// @ID unarchive-chat
|
||||
// @Tags Chats
|
||||
// @Success 204
|
||||
// @Router /chats/{chat}/unarchive [post]
|
||||
func (api *API) unarchiveChat(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
chat := httpmw.ChatParam(r)
|
||||
|
||||
// Delete descendants first. The FK is ON DELETE SET NULL so
|
||||
// order doesn't strictly matter, but deleting children before
|
||||
// parents is cleaner.
|
||||
for i := len(descendantIDs) - 1; i >= 0; i-- {
|
||||
if err := tx.DeleteChatByID(ctx, descendantIDs[i]); err != nil {
|
||||
return xerrors.Errorf("delete descendant chat %s: %w", descendantIDs[i], err)
|
||||
}
|
||||
}
|
||||
if !chat.Archived {
|
||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
Message: "Chat is not archived.",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Delete the target chat itself.
|
||||
if err := tx.DeleteChatByID(ctx, chatID); err != nil {
|
||||
return xerrors.Errorf("delete chat: %w", err)
|
||||
}
|
||||
err := api.Database.UnarchiveChatByID(ctx, chat.ID)
|
||||
if err != nil {
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Failed to unarchive chat.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
return nil
|
||||
}, nil)
|
||||
rw.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
func archiveChatTree(ctx context.Context, store database.Store, chatID uuid.UUID) error {
|
||||
children, err := store.ListChildChatsByParentID(ctx, chatID)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("list child chats: %w", err)
|
||||
}
|
||||
for _, child := range children {
|
||||
if err := archiveChatTree(ctx, store, child.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return store.ArchiveChatByID(ctx, chatID)
|
||||
}
|
||||
|
||||
// EXPERIMENTAL: this endpoint is experimental and is subject to change.
|
||||
|
||||
+11
-13
@@ -1158,7 +1158,7 @@ func TestGetChat(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestDeleteChat(t *testing.T) {
|
||||
func TestArchiveChat(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
@@ -1169,11 +1169,11 @@ func TestDeleteChat(t *testing.T) {
|
||||
_ = coderdtest.CreateFirstUser(t, client)
|
||||
_ = createChatModelConfig(t, client)
|
||||
|
||||
chatToDelete, err := client.CreateChat(ctx, codersdk.CreateChatRequest{
|
||||
chatToArchive, err := client.CreateChat(ctx, codersdk.CreateChatRequest{
|
||||
Content: []codersdk.ChatInputPart{
|
||||
{
|
||||
Type: codersdk.ChatInputPartTypeText,
|
||||
Text: "delete me",
|
||||
Text: "archive me",
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -1189,20 +1189,18 @@ func TestDeleteChat(t *testing.T) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
chatsBeforeDelete, err := client.ListChats(ctx)
|
||||
chatsBeforeArchive, err := client.ListChats(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, chatsBeforeDelete, 2)
|
||||
require.Len(t, chatsBeforeArchive, 2)
|
||||
|
||||
err = client.DeleteChat(ctx, chatToDelete.ID)
|
||||
err = client.ArchiveChat(ctx, chatToArchive.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = client.GetChat(ctx, chatToDelete.ID)
|
||||
requireSDKError(t, err, http.StatusNotFound)
|
||||
|
||||
chatsAfterDelete, err := client.ListChats(ctx)
|
||||
// Archived chats should not appear in the list.
|
||||
chatsAfterArchive, err := client.ListChats(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, chatsAfterDelete, 1)
|
||||
require.Equal(t, chatToKeep.ID, chatsAfterDelete[0].ID)
|
||||
require.Len(t, chatsAfterArchive, 1)
|
||||
require.Equal(t, chatToKeep.ID, chatsAfterArchive[0].ID)
|
||||
})
|
||||
|
||||
t.Run("NotFound", func(t *testing.T) {
|
||||
@@ -1212,7 +1210,7 @@ func TestDeleteChat(t *testing.T) {
|
||||
client := newChatClient(t)
|
||||
_ = coderdtest.CreateFirstUser(t, client)
|
||||
|
||||
err := client.DeleteChat(ctx, uuid.New())
|
||||
err := client.ArchiveChat(ctx, uuid.New())
|
||||
requireSDKError(t, err, http.StatusNotFound)
|
||||
})
|
||||
}
|
||||
|
||||
+2
-1
@@ -1127,7 +1127,8 @@ func New(options *Options) *API {
|
||||
r.Route("/{chat}", func(r chi.Router) {
|
||||
r.Use(httpmw.ExtractChatParam(options.Database))
|
||||
r.Get("/", api.getChat)
|
||||
r.Delete("/", api.deleteChat)
|
||||
r.Post("/archive", api.archiveChat)
|
||||
r.Post("/unarchive", api.unarchiveChat)
|
||||
r.Post("/messages", api.postChatMessages)
|
||||
r.Patch("/messages/{message}", api.patchChatMessage)
|
||||
r.Get("/stream", api.streamChat)
|
||||
|
||||
@@ -1528,6 +1528,17 @@ func (q *querier) AllUserIDs(ctx context.Context, includeSystem bool) ([]uuid.UU
|
||||
return q.db.AllUserIDs(ctx, includeSystem)
|
||||
}
|
||||
|
||||
func (q *querier) ArchiveChatByID(ctx context.Context, id uuid.UUID) error {
|
||||
chat, err := q.db.GetChatByID(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := q.authorizeContext(ctx, policy.ActionUpdate, chat); err != nil {
|
||||
return err
|
||||
}
|
||||
return q.db.ArchiveChatByID(ctx, id)
|
||||
}
|
||||
|
||||
func (q *querier) ArchiveUnusedTemplateVersions(ctx context.Context, arg database.ArchiveUnusedTemplateVersionsParams) ([]uuid.UUID, error) {
|
||||
tpl, err := q.db.GetTemplateByID(ctx, arg.TemplateID)
|
||||
if err != nil {
|
||||
@@ -1757,17 +1768,6 @@ func (q *querier) DeleteApplicationConnectAPIKeysByUserID(ctx context.Context, u
|
||||
return q.db.DeleteApplicationConnectAPIKeysByUserID(ctx, userID)
|
||||
}
|
||||
|
||||
func (q *querier) DeleteChatByID(ctx context.Context, id uuid.UUID) error {
|
||||
chat, err := q.db.GetChatByID(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := q.authorizeContext(ctx, policy.ActionDelete, chat); err != nil {
|
||||
return err
|
||||
}
|
||||
return q.db.DeleteChatByID(ctx, id)
|
||||
}
|
||||
|
||||
func (q *querier) DeleteChatMessagesAfterID(ctx context.Context, arg database.DeleteChatMessagesAfterIDParams) error {
|
||||
// Authorize update on the parent chat.
|
||||
chat, err := q.db.GetChatByID(ctx, arg.ChatID)
|
||||
@@ -5259,6 +5259,17 @@ func (q *querier) TryAcquireLock(ctx context.Context, id int64) (bool, error) {
|
||||
return q.db.TryAcquireLock(ctx, id)
|
||||
}
|
||||
|
||||
func (q *querier) UnarchiveChatByID(ctx context.Context, id uuid.UUID) error {
|
||||
chat, err := q.db.GetChatByID(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := q.authorizeContext(ctx, policy.ActionUpdate, chat); err != nil {
|
||||
return err
|
||||
}
|
||||
return q.db.UnarchiveChatByID(ctx, id)
|
||||
}
|
||||
|
||||
func (q *querier) UnarchiveTemplateVersion(ctx context.Context, arg database.UnarchiveTemplateVersionParams) error {
|
||||
v, err := q.db.GetTemplateVersionByID(ctx, arg.TemplateVersionID)
|
||||
if err != nil {
|
||||
|
||||
@@ -387,11 +387,17 @@ func (s *MethodTestSuite) TestChats() {
|
||||
dbm.EXPECT().DeleteAllChatQueuedMessages(gomock.Any(), chat.ID).Return(nil).AnyTimes()
|
||||
check.Args(chat.ID).Asserts(chat, policy.ActionUpdate).Returns()
|
||||
}))
|
||||
s.Run("DeleteChatByID", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
|
||||
s.Run("ArchiveChatByID", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
|
||||
chat := testutil.Fake(s.T(), faker, database.Chat{})
|
||||
dbm.EXPECT().GetChatByID(gomock.Any(), chat.ID).Return(chat, nil).AnyTimes()
|
||||
dbm.EXPECT().DeleteChatByID(gomock.Any(), chat.ID).Return(nil).AnyTimes()
|
||||
check.Args(chat.ID).Asserts(chat, policy.ActionDelete).Returns()
|
||||
dbm.EXPECT().ArchiveChatByID(gomock.Any(), chat.ID).Return(nil).AnyTimes()
|
||||
check.Args(chat.ID).Asserts(chat, policy.ActionUpdate).Returns()
|
||||
}))
|
||||
s.Run("UnarchiveChatByID", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
|
||||
chat := testutil.Fake(s.T(), faker, database.Chat{})
|
||||
dbm.EXPECT().GetChatByID(gomock.Any(), chat.ID).Return(chat, nil).AnyTimes()
|
||||
dbm.EXPECT().UnarchiveChatByID(gomock.Any(), chat.ID).Return(nil).AnyTimes()
|
||||
check.Args(chat.ID).Asserts(chat, policy.ActionUpdate).Returns()
|
||||
}))
|
||||
s.Run("DeleteChatMessagesByChatID", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
|
||||
chat := testutil.Fake(s.T(), faker, database.Chat{})
|
||||
|
||||
@@ -152,6 +152,14 @@ func (m queryMetricsStore) AllUserIDs(ctx context.Context, includeSystem bool) (
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) ArchiveChatByID(ctx context.Context, id uuid.UUID) error {
|
||||
start := time.Now()
|
||||
r0 := m.s.ArchiveChatByID(ctx, id)
|
||||
m.queryLatencies.WithLabelValues("ArchiveChatByID").Observe(time.Since(start).Seconds())
|
||||
m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "ArchiveChatByID").Inc()
|
||||
return r0
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) ArchiveUnusedTemplateVersions(ctx context.Context, arg database.ArchiveUnusedTemplateVersionsParams) ([]uuid.UUID, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.ArchiveUnusedTemplateVersions(ctx, arg)
|
||||
@@ -352,14 +360,6 @@ func (m queryMetricsStore) DeleteApplicationConnectAPIKeysByUserID(ctx context.C
|
||||
return r0
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) DeleteChatByID(ctx context.Context, id uuid.UUID) error {
|
||||
start := time.Now()
|
||||
r0 := m.s.DeleteChatByID(ctx, id)
|
||||
m.queryLatencies.WithLabelValues("DeleteChatByID").Observe(time.Since(start).Seconds())
|
||||
m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "DeleteChatByID").Inc()
|
||||
return r0
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) DeleteChatMessagesAfterID(ctx context.Context, arg database.DeleteChatMessagesAfterIDParams) error {
|
||||
start := time.Now()
|
||||
r0 := m.s.DeleteChatMessagesAfterID(ctx, arg)
|
||||
@@ -3663,6 +3663,14 @@ func (m queryMetricsStore) TryAcquireLock(ctx context.Context, pgTryAdvisoryXact
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) UnarchiveChatByID(ctx context.Context, id uuid.UUID) error {
|
||||
start := time.Now()
|
||||
r0 := m.s.UnarchiveChatByID(ctx, id)
|
||||
m.queryLatencies.WithLabelValues("UnarchiveChatByID").Observe(time.Since(start).Seconds())
|
||||
m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "UnarchiveChatByID").Inc()
|
||||
return r0
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) UnarchiveTemplateVersion(ctx context.Context, arg database.UnarchiveTemplateVersionParams) error {
|
||||
start := time.Now()
|
||||
r0 := m.s.UnarchiveTemplateVersion(ctx, arg)
|
||||
|
||||
@@ -132,6 +132,20 @@ func (mr *MockStoreMockRecorder) AllUserIDs(ctx, includeSystem any) *gomock.Call
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AllUserIDs", reflect.TypeOf((*MockStore)(nil).AllUserIDs), ctx, includeSystem)
|
||||
}
|
||||
|
||||
// ArchiveChatByID mocks base method.
|
||||
func (m *MockStore) ArchiveChatByID(ctx context.Context, id uuid.UUID) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "ArchiveChatByID", ctx, id)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// ArchiveChatByID indicates an expected call of ArchiveChatByID.
|
||||
func (mr *MockStoreMockRecorder) ArchiveChatByID(ctx, id any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ArchiveChatByID", reflect.TypeOf((*MockStore)(nil).ArchiveChatByID), ctx, id)
|
||||
}
|
||||
|
||||
// ArchiveUnusedTemplateVersions mocks base method.
|
||||
func (m *MockStore) ArchiveUnusedTemplateVersions(ctx context.Context, arg database.ArchiveUnusedTemplateVersionsParams) ([]uuid.UUID, error) {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -540,20 +554,6 @@ func (mr *MockStoreMockRecorder) DeleteApplicationConnectAPIKeysByUserID(ctx, us
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteApplicationConnectAPIKeysByUserID", reflect.TypeOf((*MockStore)(nil).DeleteApplicationConnectAPIKeysByUserID), ctx, userID)
|
||||
}
|
||||
|
||||
// DeleteChatByID mocks base method.
|
||||
func (m *MockStore) DeleteChatByID(ctx context.Context, id uuid.UUID) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "DeleteChatByID", ctx, id)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// DeleteChatByID indicates an expected call of DeleteChatByID.
|
||||
func (mr *MockStoreMockRecorder) DeleteChatByID(ctx, id any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteChatByID", reflect.TypeOf((*MockStore)(nil).DeleteChatByID), ctx, id)
|
||||
}
|
||||
|
||||
// DeleteChatMessagesAfterID mocks base method.
|
||||
func (m *MockStore) DeleteChatMessagesAfterID(ctx context.Context, arg database.DeleteChatMessagesAfterIDParams) error {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -6901,6 +6901,20 @@ func (mr *MockStoreMockRecorder) TryAcquireLock(ctx, pgTryAdvisoryXactLock any)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TryAcquireLock", reflect.TypeOf((*MockStore)(nil).TryAcquireLock), ctx, pgTryAdvisoryXactLock)
|
||||
}
|
||||
|
||||
// UnarchiveChatByID mocks base method.
|
||||
func (m *MockStore) UnarchiveChatByID(ctx context.Context, id uuid.UUID) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "UnarchiveChatByID", ctx, id)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// UnarchiveChatByID indicates an expected call of UnarchiveChatByID.
|
||||
func (mr *MockStoreMockRecorder) UnarchiveChatByID(ctx, id any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UnarchiveChatByID", reflect.TypeOf((*MockStore)(nil).UnarchiveChatByID), ctx, id)
|
||||
}
|
||||
|
||||
// UnarchiveTemplateVersion mocks base method.
|
||||
func (m *MockStore) UnarchiveTemplateVersion(ctx context.Context, arg database.UnarchiveTemplateVersionParams) error {
|
||||
m.ctrl.T.Helper()
|
||||
|
||||
Generated
+2
-1
@@ -1273,7 +1273,8 @@ CREATE TABLE chats (
|
||||
updated_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
parent_chat_id uuid,
|
||||
root_chat_id uuid,
|
||||
last_model_config_id uuid NOT NULL
|
||||
last_model_config_id uuid NOT NULL,
|
||||
archived boolean DEFAULT false NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE connection_logs (
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE chats DROP COLUMN archived;
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE chats ADD COLUMN archived boolean DEFAULT false NOT NULL;
|
||||
@@ -3900,6 +3900,7 @@ type Chat struct {
|
||||
ParentChatID uuid.NullUUID `db:"parent_chat_id" json:"parent_chat_id"`
|
||||
RootChatID uuid.NullUUID `db:"root_chat_id" json:"root_chat_id"`
|
||||
LastModelConfigID uuid.UUID `db:"last_model_config_id" json:"last_model_config_id"`
|
||||
Archived bool `db:"archived" json:"archived"`
|
||||
}
|
||||
|
||||
type ChatDiffStatus struct {
|
||||
|
||||
@@ -53,6 +53,7 @@ type sqlcQuerier interface {
|
||||
ActivityBumpWorkspace(ctx context.Context, arg ActivityBumpWorkspaceParams) error
|
||||
// AllUserIDs returns all UserIDs regardless of user status or deletion.
|
||||
AllUserIDs(ctx context.Context, includeSystem bool) ([]uuid.UUID, error)
|
||||
ArchiveChatByID(ctx context.Context, id uuid.UUID) error
|
||||
// Archiving templates is a soft delete action, so is reversible.
|
||||
// Archiving prevents the version from being used and discovered
|
||||
// by listing.
|
||||
@@ -92,7 +93,6 @@ type sqlcQuerier interface {
|
||||
// be recreated.
|
||||
DeleteAllWebpushSubscriptions(ctx context.Context) error
|
||||
DeleteApplicationConnectAPIKeysByUserID(ctx context.Context, userID uuid.UUID) error
|
||||
DeleteChatByID(ctx context.Context, id uuid.UUID) error
|
||||
DeleteChatMessagesAfterID(ctx context.Context, arg DeleteChatMessagesAfterIDParams) error
|
||||
DeleteChatMessagesByChatID(ctx context.Context, chatID uuid.UUID) error
|
||||
DeleteChatModelConfigByID(ctx context.Context, id uuid.UUID) error
|
||||
@@ -727,6 +727,7 @@ type sqlcQuerier interface {
|
||||
// This must be called from within a transaction. The lock will be automatically
|
||||
// released when the transaction ends.
|
||||
TryAcquireLock(ctx context.Context, pgTryAdvisoryXactLock int64) (bool, error)
|
||||
UnarchiveChatByID(ctx context.Context, id uuid.UUID) error
|
||||
// This will always work regardless of the current state of the template version.
|
||||
UnarchiveTemplateVersion(ctx context.Context, arg UnarchiveTemplateVersionParams) error
|
||||
UnfavoriteWorkspace(ctx context.Context, id uuid.UUID) error
|
||||
|
||||
@@ -2842,7 +2842,7 @@ WHERE
|
||||
1
|
||||
)
|
||||
RETURNING
|
||||
id, owner_id, workspace_id, workspace_agent_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id
|
||||
id, owner_id, workspace_id, workspace_agent_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived
|
||||
`
|
||||
|
||||
type AcquireChatParams struct {
|
||||
@@ -2870,10 +2870,20 @@ func (q *sqlQuerier) AcquireChat(ctx context.Context, arg AcquireChatParams) (Ch
|
||||
&i.ParentChatID,
|
||||
&i.RootChatID,
|
||||
&i.LastModelConfigID,
|
||||
&i.Archived,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const archiveChatByID = `-- name: ArchiveChatByID :exec
|
||||
UPDATE chats SET archived = true, updated_at = NOW() WHERE id = $1::uuid
|
||||
`
|
||||
|
||||
func (q *sqlQuerier) ArchiveChatByID(ctx context.Context, id uuid.UUID) error {
|
||||
_, err := q.db.ExecContext(ctx, archiveChatByID, id)
|
||||
return err
|
||||
}
|
||||
|
||||
const deleteAllChatQueuedMessages = `-- name: DeleteAllChatQueuedMessages :exec
|
||||
DELETE FROM chat_queued_messages WHERE chat_id = $1
|
||||
`
|
||||
@@ -2883,18 +2893,6 @@ func (q *sqlQuerier) DeleteAllChatQueuedMessages(ctx context.Context, chatID uui
|
||||
return err
|
||||
}
|
||||
|
||||
const deleteChatByID = `-- name: DeleteChatByID :exec
|
||||
DELETE FROM
|
||||
chats
|
||||
WHERE
|
||||
id = $1::uuid
|
||||
`
|
||||
|
||||
func (q *sqlQuerier) DeleteChatByID(ctx context.Context, id uuid.UUID) error {
|
||||
_, err := q.db.ExecContext(ctx, deleteChatByID, id)
|
||||
return err
|
||||
}
|
||||
|
||||
const deleteChatMessagesAfterID = `-- name: DeleteChatMessagesAfterID :exec
|
||||
DELETE FROM
|
||||
chat_messages
|
||||
@@ -2941,7 +2939,7 @@ func (q *sqlQuerier) DeleteChatQueuedMessage(ctx context.Context, arg DeleteChat
|
||||
|
||||
const getChatByID = `-- name: GetChatByID :one
|
||||
SELECT
|
||||
id, owner_id, workspace_id, workspace_agent_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id
|
||||
id, owner_id, workspace_id, workspace_agent_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived
|
||||
FROM
|
||||
chats
|
||||
WHERE
|
||||
@@ -2966,12 +2964,13 @@ func (q *sqlQuerier) GetChatByID(ctx context.Context, id uuid.UUID) (Chat, error
|
||||
&i.ParentChatID,
|
||||
&i.RootChatID,
|
||||
&i.LastModelConfigID,
|
||||
&i.Archived,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getChatByIDForUpdate = `-- name: GetChatByIDForUpdate :one
|
||||
SELECT id, owner_id, workspace_id, workspace_agent_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id FROM chats WHERE id = $1::uuid FOR UPDATE
|
||||
SELECT id, owner_id, workspace_id, workspace_agent_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived FROM chats WHERE id = $1::uuid FOR UPDATE
|
||||
`
|
||||
|
||||
func (q *sqlQuerier) GetChatByIDForUpdate(ctx context.Context, id uuid.UUID) (Chat, error) {
|
||||
@@ -2992,6 +2991,7 @@ func (q *sqlQuerier) GetChatByIDForUpdate(ctx context.Context, id uuid.UUID) (Ch
|
||||
&i.ParentChatID,
|
||||
&i.RootChatID,
|
||||
&i.LastModelConfigID,
|
||||
&i.Archived,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
@@ -3288,11 +3288,12 @@ func (q *sqlQuerier) GetChatQueuedMessages(ctx context.Context, chatID uuid.UUID
|
||||
|
||||
const getChatsByOwnerID = `-- name: GetChatsByOwnerID :many
|
||||
SELECT
|
||||
id, owner_id, workspace_id, workspace_agent_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id
|
||||
id, owner_id, workspace_id, workspace_agent_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived
|
||||
FROM
|
||||
chats
|
||||
WHERE
|
||||
owner_id = $1::uuid
|
||||
AND archived = false
|
||||
ORDER BY
|
||||
updated_at DESC
|
||||
`
|
||||
@@ -3321,6 +3322,7 @@ func (q *sqlQuerier) GetChatsByOwnerID(ctx context.Context, ownerID uuid.UUID) (
|
||||
&i.ParentChatID,
|
||||
&i.RootChatID,
|
||||
&i.LastModelConfigID,
|
||||
&i.Archived,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -3337,7 +3339,7 @@ func (q *sqlQuerier) GetChatsByOwnerID(ctx context.Context, ownerID uuid.UUID) (
|
||||
|
||||
const getStaleChats = `-- name: GetStaleChats :many
|
||||
SELECT
|
||||
id, owner_id, workspace_id, workspace_agent_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id
|
||||
id, owner_id, workspace_id, workspace_agent_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived
|
||||
FROM
|
||||
chats
|
||||
WHERE
|
||||
@@ -3371,6 +3373,7 @@ func (q *sqlQuerier) GetStaleChats(ctx context.Context, staleThreshold time.Time
|
||||
&i.ParentChatID,
|
||||
&i.RootChatID,
|
||||
&i.LastModelConfigID,
|
||||
&i.Archived,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -3404,7 +3407,7 @@ INSERT INTO chats (
|
||||
$7::text
|
||||
)
|
||||
RETURNING
|
||||
id, owner_id, workspace_id, workspace_agent_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id
|
||||
id, owner_id, workspace_id, workspace_agent_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived
|
||||
`
|
||||
|
||||
type InsertChatParams struct {
|
||||
@@ -3443,6 +3446,7 @@ func (q *sqlQuerier) InsertChat(ctx context.Context, arg InsertChatParams) (Chat
|
||||
&i.ParentChatID,
|
||||
&i.RootChatID,
|
||||
&i.LastModelConfigID,
|
||||
&i.Archived,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
@@ -3568,7 +3572,7 @@ func (q *sqlQuerier) InsertChatQueuedMessage(ctx context.Context, arg InsertChat
|
||||
|
||||
const listChatsByRootID = `-- name: ListChatsByRootID :many
|
||||
SELECT
|
||||
id, owner_id, workspace_id, workspace_agent_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id
|
||||
id, owner_id, workspace_id, workspace_agent_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived
|
||||
FROM
|
||||
chats
|
||||
WHERE
|
||||
@@ -3601,6 +3605,7 @@ func (q *sqlQuerier) ListChatsByRootID(ctx context.Context, rootChatID uuid.UUID
|
||||
&i.ParentChatID,
|
||||
&i.RootChatID,
|
||||
&i.LastModelConfigID,
|
||||
&i.Archived,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -3617,7 +3622,7 @@ func (q *sqlQuerier) ListChatsByRootID(ctx context.Context, rootChatID uuid.UUID
|
||||
|
||||
const listChildChatsByParentID = `-- name: ListChildChatsByParentID :many
|
||||
SELECT
|
||||
id, owner_id, workspace_id, workspace_agent_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id
|
||||
id, owner_id, workspace_id, workspace_agent_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived
|
||||
FROM
|
||||
chats
|
||||
WHERE
|
||||
@@ -3650,6 +3655,7 @@ func (q *sqlQuerier) ListChildChatsByParentID(ctx context.Context, parentChatID
|
||||
&i.ParentChatID,
|
||||
&i.RootChatID,
|
||||
&i.LastModelConfigID,
|
||||
&i.Archived,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -3687,6 +3693,15 @@ func (q *sqlQuerier) PopNextQueuedMessage(ctx context.Context, chatID uuid.UUID)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const unarchiveChatByID = `-- name: UnarchiveChatByID :exec
|
||||
UPDATE chats SET archived = false, updated_at = NOW() WHERE id = $1::uuid
|
||||
`
|
||||
|
||||
func (q *sqlQuerier) UnarchiveChatByID(ctx context.Context, id uuid.UUID) error {
|
||||
_, err := q.db.ExecContext(ctx, unarchiveChatByID, id)
|
||||
return err
|
||||
}
|
||||
|
||||
const updateChatByID = `-- name: UpdateChatByID :one
|
||||
UPDATE
|
||||
chats
|
||||
@@ -3696,7 +3711,7 @@ SET
|
||||
WHERE
|
||||
id = $2::uuid
|
||||
RETURNING
|
||||
id, owner_id, workspace_id, workspace_agent_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id
|
||||
id, owner_id, workspace_id, workspace_agent_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived
|
||||
`
|
||||
|
||||
type UpdateChatByIDParams struct {
|
||||
@@ -3722,6 +3737,7 @@ func (q *sqlQuerier) UpdateChatByID(ctx context.Context, arg UpdateChatByIDParam
|
||||
&i.ParentChatID,
|
||||
&i.RootChatID,
|
||||
&i.LastModelConfigID,
|
||||
&i.Archived,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
@@ -3805,7 +3821,7 @@ SET
|
||||
WHERE
|
||||
id = $5::uuid
|
||||
RETURNING
|
||||
id, owner_id, workspace_id, workspace_agent_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id
|
||||
id, owner_id, workspace_id, workspace_agent_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived
|
||||
`
|
||||
|
||||
type UpdateChatStatusParams struct {
|
||||
@@ -3840,6 +3856,7 @@ func (q *sqlQuerier) UpdateChatStatus(ctx context.Context, arg UpdateChatStatusP
|
||||
&i.ParentChatID,
|
||||
&i.RootChatID,
|
||||
&i.LastModelConfigID,
|
||||
&i.Archived,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
@@ -3854,7 +3871,7 @@ SET
|
||||
WHERE
|
||||
id = $3::uuid
|
||||
RETURNING
|
||||
id, owner_id, workspace_id, workspace_agent_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id
|
||||
id, owner_id, workspace_id, workspace_agent_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived
|
||||
`
|
||||
|
||||
type UpdateChatWorkspaceParams struct {
|
||||
@@ -3881,6 +3898,7 @@ func (q *sqlQuerier) UpdateChatWorkspace(ctx context.Context, arg UpdateChatWork
|
||||
&i.ParentChatID,
|
||||
&i.RootChatID,
|
||||
&i.LastModelConfigID,
|
||||
&i.Archived,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
-- name: DeleteChatByID :exec
|
||||
DELETE FROM
|
||||
chats
|
||||
WHERE
|
||||
id = @id::uuid;
|
||||
-- name: ArchiveChatByID :exec
|
||||
UPDATE chats SET archived = true, updated_at = NOW() WHERE id = @id::uuid;
|
||||
|
||||
-- name: UnarchiveChatByID :exec
|
||||
UPDATE chats SET archived = false, updated_at = NOW() WHERE id = @id::uuid;
|
||||
|
||||
-- name: DeleteChatMessagesByChatID :exec
|
||||
DELETE FROM
|
||||
@@ -108,6 +108,7 @@ FROM
|
||||
chats
|
||||
WHERE
|
||||
owner_id = @owner_id::uuid
|
||||
AND archived = false
|
||||
ORDER BY
|
||||
updated_at DESC;
|
||||
|
||||
|
||||
+15
-3
@@ -41,6 +41,7 @@ type Chat struct {
|
||||
DiffStatus *ChatDiffStatus `json:"diff_status,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at" format:"date-time"`
|
||||
UpdatedAt time.Time `json:"updated_at" format:"date-time"`
|
||||
Archived bool `json:"archived"`
|
||||
}
|
||||
|
||||
// ChatMessage represents a single message in a chat.
|
||||
@@ -780,9 +781,20 @@ func (c *Client) GetChat(ctx context.Context, chatID uuid.UUID) (ChatWithMessage
|
||||
return chat, json.NewDecoder(res.Body).Decode(&chat)
|
||||
}
|
||||
|
||||
// DeleteChat deletes a chat by ID.
|
||||
func (c *Client) DeleteChat(ctx context.Context, chatID uuid.UUID) error {
|
||||
res, err := c.Request(ctx, http.MethodDelete, fmt.Sprintf("/api/experimental/chats/%s", chatID), nil)
|
||||
func (c *Client) ArchiveChat(ctx context.Context, chatID uuid.UUID) error {
|
||||
res, err := c.Request(ctx, http.MethodPost, fmt.Sprintf("/api/experimental/chats/%s/archive", chatID), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusNoContent {
|
||||
return ReadBodyAsError(res)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) UnarchiveChat(ctx context.Context, chatID uuid.UUID) error {
|
||||
res, err := c.Request(ctx, http.MethodPost, fmt.Sprintf("/api/experimental/chats/%s/unarchive", chatID), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1363,6 +1363,10 @@
|
||||
"title": "Builds",
|
||||
"path": "./reference/api/builds.md"
|
||||
},
|
||||
{
|
||||
"title": "Chats",
|
||||
"path": "./reference/api/chats.md"
|
||||
},
|
||||
{
|
||||
"title": "Debug",
|
||||
"path": "./reference/api/debug.md"
|
||||
|
||||
Generated
+37
@@ -0,0 +1,37 @@
|
||||
# Chats
|
||||
|
||||
## Archive a chat
|
||||
|
||||
### Code samples
|
||||
|
||||
```shell
|
||||
# Example request using curl
|
||||
curl -X POST http://coder-server:8080/api/v2/chats/{chat}/archive
|
||||
|
||||
```
|
||||
|
||||
`POST /chats/{chat}/archive`
|
||||
|
||||
### Responses
|
||||
|
||||
| Status | Meaning | Description | Schema |
|
||||
|--------|-----------------------------------------------------------------|-------------|--------|
|
||||
| 204 | [No Content](https://tools.ietf.org/html/rfc7231#section-6.3.5) | No Content | |
|
||||
|
||||
## Unarchive a chat
|
||||
|
||||
### Code samples
|
||||
|
||||
```shell
|
||||
# Example request using curl
|
||||
curl -X POST http://coder-server:8080/api/v2/chats/{chat}/unarchive
|
||||
|
||||
```
|
||||
|
||||
`POST /chats/{chat}/unarchive`
|
||||
|
||||
### Responses
|
||||
|
||||
| Status | Meaning | Description | Schema |
|
||||
|--------|-----------------------------------------------------------------|-------------|--------|
|
||||
| 204 | [No Content](https://tools.ietf.org/html/rfc7231#section-6.3.5) | No Content | |
|
||||
+2
-2
@@ -2930,8 +2930,8 @@ class ApiMethods {
|
||||
return response.data;
|
||||
};
|
||||
|
||||
deleteChat = async (chatId: string): Promise<void> => {
|
||||
await this.axios.delete(`/api/experimental/chats/${chatId}`);
|
||||
archiveChat = async (chatId: string): Promise<void> => {
|
||||
await this.axios.post(`/api/experimental/chats/${chatId}/archive`);
|
||||
};
|
||||
|
||||
createChatMessage = async (
|
||||
|
||||
@@ -22,8 +22,8 @@ export const createChat = (queryClient: QueryClient) => ({
|
||||
},
|
||||
});
|
||||
|
||||
export const deleteChat = (queryClient: QueryClient) => ({
|
||||
mutationFn: (chatId: string) => API.deleteChat(chatId),
|
||||
export const archiveChat = (queryClient: QueryClient) => ({
|
||||
mutationFn: (chatId: string) => API.archiveChat(chatId),
|
||||
onSuccess: async () => {
|
||||
await queryClient.invalidateQueries({ queryKey: chatsKey });
|
||||
},
|
||||
|
||||
Generated
+1
@@ -1063,6 +1063,7 @@ export interface Chat {
|
||||
readonly diff_status?: ChatDiffStatus;
|
||||
readonly created_at: string;
|
||||
readonly updated_at: string;
|
||||
readonly archived: boolean;
|
||||
}
|
||||
|
||||
// From codersdk/chats.go
|
||||
|
||||
@@ -121,6 +121,7 @@ const baseChatFields = {
|
||||
last_model_config_id: "model-config-1",
|
||||
created_at: "2026-02-18T00:00:00.000Z",
|
||||
updated_at: "2026-02-18T00:00:00.000Z",
|
||||
archived: false,
|
||||
} as const;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -112,6 +112,7 @@ const makeChat = (chatID: string): TypesGen.Chat => ({
|
||||
status: "running",
|
||||
created_at: "2025-01-01T00:00:00.000Z",
|
||||
updated_at: "2025-01-01T00:00:00.000Z",
|
||||
archived: false,
|
||||
});
|
||||
|
||||
const makeMessage = (
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { watchChats } from "api/api";
|
||||
import { getErrorMessage } from "api/errors";
|
||||
import {
|
||||
archiveChat,
|
||||
chatKey,
|
||||
chatModelConfigs,
|
||||
chatModels,
|
||||
chats,
|
||||
chatsKey,
|
||||
createChat,
|
||||
deleteChat,
|
||||
} from "api/queries/chats";
|
||||
import { workspaces } from "api/queries/workspaces";
|
||||
import type * as TypesGen from "api/typesGenerated";
|
||||
@@ -120,7 +120,7 @@ const AgentsPage: FC = () => {
|
||||
const chatModelsQuery = useQuery(chatModels());
|
||||
const chatModelConfigsQuery = useQuery(chatModelConfigs());
|
||||
const createMutation = useMutation(createChat(queryClient));
|
||||
const archiveMutation = useMutation(deleteChat(queryClient));
|
||||
const archiveMutation = useMutation(archiveChat(queryClient));
|
||||
const [archivingChatId, setArchivingChatId] = useState<string | null>(null);
|
||||
const [isRightPanelOpen, setIsRightPanelOpen] = useState(false);
|
||||
const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(false);
|
||||
|
||||
@@ -38,6 +38,7 @@ const buildChat = (overrides: Partial<Chat> = {}): Chat => ({
|
||||
last_model_config_id: defaultModelConfigs[0].id,
|
||||
created_at: "2026-02-18T00:00:00.000Z",
|
||||
updated_at: "2026-02-18T00:00:00.000Z",
|
||||
archived: false,
|
||||
...overrides,
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user