diff --git a/common/go/cookchatdb/chat.go b/common/go/cookchatdb/chat.go index 7bbabdde..6bc4ac91 100644 --- a/common/go/cookchatdb/chat.go +++ b/common/go/cookchatdb/chat.go @@ -21,6 +21,9 @@ type ChatMessage struct { // Content is the text content of the message. Content string `firestore:"content"` + + // CreatedAt is the timestamp when the message was created. + CreatedAt time.Time `firestore:"createdAt"` } type Chat struct { diff --git a/frontend/api/descriptors/descriptorset.pb b/frontend/api/descriptors/descriptorset.pb index 6cfb0e50..a6ad2c29 100644 Binary files a/frontend/api/descriptors/descriptorset.pb and b/frontend/api/descriptors/descriptorset.pb differ diff --git a/frontend/api/go/frontend.pb.go b/frontend/api/go/frontend.pb.go index 957d3887..e96dffc5 100644 --- a/frontend/api/go/frontend.pb.go +++ b/frontend/api/go/frontend.pb.go @@ -2863,7 +2863,9 @@ type GetChatMessagesResponse struct { // The ID of the chat. ChatId string `protobuf:"bytes,1,opt,name=chat_id,json=chatId,proto3" json:"chat_id,omitempty"` // The messages in the chat. - Messages []*ChatMessage `protobuf:"bytes,2,rep,name=messages,proto3" json:"messages,omitempty"` + Messages []*ChatMessage `protobuf:"bytes,2,rep,name=messages,proto3" json:"messages,omitempty"` + // The plan ID if the chat is already completed. + PlanId string `protobuf:"bytes,3,opt,name=plan_id,json=planId,proto3" json:"plan_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -2912,6 +2914,13 @@ func (x *GetChatMessagesResponse) GetMessages() []*ChatMessage { return nil } +func (x *GetChatMessagesResponse) GetPlanId() string { + if x != nil { + return x.PlanId + } + return "" +} + type AddRecipeRequest_AddRecipeStep struct { state protoimpl.MessageState `protogen:"open.v1"` // The description of the step. @@ -3140,10 +3149,11 @@ const file_frontendapi_frontend_proto_rawDesc = "" + "\achat_id\x18\x01 \x01(\tR\x06chatId\x124\n" + "\bmessages\x18\x02 \x03(\v2\x18.frontendapi.ChatMessageR\bmessages\x12\x17\n" + "\aplan_id\x18\x03 \x01(\tR\x06planId\"\x18\n" + - "\x16GetChatMessagesRequest\"h\n" + + "\x16GetChatMessagesRequest\"\x81\x01\n" + "\x17GetChatMessagesResponse\x12\x17\n" + "\achat_id\x18\x01 \x01(\tR\x06chatId\x124\n" + - "\bmessages\x18\x02 \x03(\v2\x18.frontendapi.ChatMessageR\bmessages*Q\n" + + "\bmessages\x18\x02 \x03(\v2\x18.frontendapi.ChatMessageR\bmessages\x12\x17\n" + + "\aplan_id\x18\x03 \x01(\tR\x06planId*Q\n" + "\bLanguage\x12\x18\n" + "\x14LANGUAGE_UNSPECIFIED\x10\x00\x12\x14\n" + "\x10LANGUAGE_ENGLISH\x10\x01\x12\x15\n" + diff --git a/frontend/api/proto/frontendapi/frontend.proto b/frontend/api/proto/frontendapi/frontend.proto index 3c965db1..16587bc0 100644 --- a/frontend/api/proto/frontendapi/frontend.proto +++ b/frontend/api/proto/frontendapi/frontend.proto @@ -508,6 +508,9 @@ message GetChatMessagesResponse { // The messages in the chat. repeated ChatMessage messages = 2; + + // The plan ID if the chat is already completed. + string plan_id = 3; } service FrontendService { diff --git a/frontend/api/typescript/frontendapi/frontend_pb.ts b/frontend/api/typescript/frontendapi/frontend_pb.ts index 14c5d106..8fb43d1d 100644 --- a/frontend/api/typescript/frontendapi/frontend_pb.ts +++ b/frontend/api/typescript/frontendapi/frontend_pb.ts @@ -13,7 +13,7 @@ import type { Message } from "@bufbuild/protobuf"; * Describes the file frontendapi/frontend.proto. */ export const file_frontendapi_frontend: GenFile = /*@__PURE__*/ - fileDesc("Chpmcm9udGVuZGFwaS9mcm9udGVuZC5wcm90bxILZnJvbnRlbmRhcGkiPAoLQ2hhdENvbnRlbnQSEQoHbWVzc2FnZRgBIAEoCUgAEg8KBWF1ZGlvGAIgASgMSABCCQoHcGF5bG9hZCJuCgtDaGF0UmVxdWVzdBIpCgdjb250ZW50GAEgASgLMhguZnJvbnRlbmRhcGkuQ2hhdENvbnRlbnQSFQoLcmVjaXBlX3RleHQYAiABKAlIABITCglyZWNpcGVfaWQYAyABKAlIAEIICgZyZWNpcGUiOQoMQ2hhdFJlc3BvbnNlEikKB2NvbnRlbnQYASABKAsyGC5mcm9udGVuZGFwaS5DaGF0Q29udGVudCIyChBSZWNpcGVJbmdyZWRpZW50EgwKBG5hbWUYASABKAkSEAoIcXVhbnRpdHkYAiABKAkiNAoKUmVjaXBlU3RlcBITCgtkZXNjcmlwdGlvbhgBIAEoCRIRCglpbWFnZV91cmwYAiABKAkiVgoRSW5ncmVkaWVudFNlY3Rpb24SDQoFdGl0bGUYASABKAkSMgoLaW5ncmVkaWVudHMYAiADKAsyHS5mcm9udGVuZGFwaS5SZWNpcGVJbmdyZWRpZW50IosDCgZSZWNpcGUSCgoCaWQYASABKAkSKQoGc291cmNlGAIgASgOMhkuZnJvbnRlbmRhcGkuUmVjaXBlU291cmNlEikKBnN0YXR1cxgDIAEoDjIZLmZyb250ZW5kYXBpLlJlY2lwZVN0YXR1cxINCgV0aXRsZRgEIAEoCRIRCglpbWFnZV91cmwYBSABKAkSEwoLZGVzY3JpcHRpb24YBiABKAkSMgoLaW5ncmVkaWVudHMYByADKAsyHS5mcm9udGVuZGFwaS5SZWNpcGVJbmdyZWRpZW50Ej4KFmFkZGl0aW9uYWxfaW5ncmVkaWVudHMYCCADKAsyHi5mcm9udGVuZGFwaS5JbmdyZWRpZW50U2VjdGlvbhImCgVzdGVwcxgJIAMoCzIXLmZyb250ZW5kYXBpLlJlY2lwZVN0ZXASDQoFbm90ZXMYCiABKAkSFAoMc2VydmluZ19zaXplGAsgASgJEicKCGxhbmd1YWdlGAwgASgOMhUuZnJvbnRlbmRhcGkuTGFuZ3VhZ2UiJQoQR2V0UmVjaXBlUmVxdWVzdBIRCglyZWNpcGVfaWQYASABKAkiYwoRR2V0UmVjaXBlUmVzcG9uc2USIwoGcmVjaXBlGAEgASgLMhMuZnJvbnRlbmRhcGkuUmVjaXBlEhIKCmxsbV9wcm9tcHQYAiABKAkSFQoNaXNfYm9va21hcmtlZBgDIAEoCCI7CgpQYWdpbmF0aW9uEg8KB2xhc3RfaWQYASABKAkSHAoUbGFzdF90aW1lc3RhbXBfbmFub3MYAiABKAMiTgoNUmVjaXBlU25pcHBldBIKCgJpZBgBIAEoCRINCgV0aXRsZRgCIAEoCRIPCgdzdW1tYXJ5GAMgASgJEhEKCWltYWdlX3VybBgEIAEoCSJjChJMaXN0UmVjaXBlc1JlcXVlc3QSDQoFcXVlcnkYASABKAkSEQoJYm9va21hcmtzGAMgASgIEisKCnBhZ2luYXRpb24YAiABKAsyFy5mcm9udGVuZGFwaS5QYWdpbmF0aW9uIm8KE0xpc3RSZWNpcGVzUmVzcG9uc2USKwoHcmVjaXBlcxgBIAMoCzIaLmZyb250ZW5kYXBpLlJlY2lwZVNuaXBwZXQSKwoKcGFnaW5hdGlvbhgCIAEoCzIXLmZyb250ZW5kYXBpLlBhZ2luYXRpb24isAIKEFN0YXJ0Q2hhdFJlcXVlc3QSFQoLcmVjaXBlX3RleHQYAiABKAlIABITCglyZWNpcGVfaWQYAyABKAlIABIRCgdwbGFuX2lkGAYgASgJSAASQwoObW9kZWxfcHJvdmlkZXIYBCABKA4yKy5mcm9udGVuZGFwaS5TdGFydENoYXRSZXF1ZXN0Lk1vZGVsUHJvdmlkZXISEgoKbGxtX3Byb21wdBgFIAEoCRINCgVtb2RlbBgHIAEoCSJrCg1Nb2RlbFByb3ZpZGVyEh4KGk1PREVMX1BST1ZJREVSX1VOU1BFQ0lGSUVEEAASHwobTU9ERUxfUFJPVklERVJfR09PR0xFX0dFTkFJEAESGQoVTU9ERUxfUFJPVklERVJfT1BFTkFJEAJCCAoGcmVjaXBlIm8KEVN0YXJ0Q2hhdFJlc3BvbnNlEhQKDGNoYXRfYXBpX2tleRgBIAEoCRISCgpjaGF0X21vZGVsGAIgASgJEhkKEWNoYXRfaW5zdHJ1Y3Rpb25zGAMgASgJEhUKDXN0YXJ0X21lc3NhZ2UYBCABKAkigAMKEEFkZFJlY2lwZVJlcXVlc3QSDQoFdGl0bGUYASABKAkSGwoTbWFpbl9pbWFnZV9kYXRhX3VybBgCIAEoCRITCgtkZXNjcmlwdGlvbhgDIAEoCRIyCgtpbmdyZWRpZW50cxgEIAMoCzIdLmZyb250ZW5kYXBpLlJlY2lwZUluZ3JlZGllbnQSPgoWYWRkaXRpb25hbF9pbmdyZWRpZW50cxgFIAMoCzIeLmZyb250ZW5kYXBpLkluZ3JlZGllbnRTZWN0aW9uEjoKBXN0ZXBzGAYgAygLMisuZnJvbnRlbmRhcGkuQWRkUmVjaXBlUmVxdWVzdC5BZGRSZWNpcGVTdGVwEhQKDHNlcnZpbmdfc2l6ZRgHIAEoCRInCghsYW5ndWFnZRgIIAEoDjIVLmZyb250ZW5kYXBpLkxhbmd1YWdlGjwKDUFkZFJlY2lwZVN0ZXASEwoLZGVzY3JpcHRpb24YASABKAkSFgoOaW1hZ2VfZGF0YV91cmwYAiABKAkiJgoRQWRkUmVjaXBlUmVzcG9uc2USEQoJcmVjaXBlX2lkGAEgASgJIicKFUdlbmVyYXRlUmVjaXBlUmVxdWVzdBIOCgZwcm9tcHQYASABKAkiUwoWR2VuZXJhdGVSZWNpcGVSZXNwb25zZRI5ChJhZGRfcmVjaXBlX3JlcXVlc3QYASABKAsyHS5mcm9udGVuZGFwaS5BZGRSZWNpcGVSZXF1ZXN0InoKE0dlbmVyYXRlUGxhblJlcXVlc3QSEAoIbnVtX2RheXMYASABKA0SEwoLaW5ncmVkaWVudHMYAiADKAkSKAoGZ2VucmVzGAMgAygOMhguZnJvbnRlbmRhcGkuUmVjaXBlR2VucmUSEgoKcmVjaXBlX2lkcxgEIAMoCSIWChRHZW5lcmF0ZVBsYW5SZXNwb25zZSJQCglTdGVwR3JvdXASDQoFbGFiZWwYASABKAkSJgoFc3RlcHMYAiADKAsyFy5mcm9udGVuZGFwaS5SZWNpcGVTdGVwEgwKBG5vdGUYAyABKAkieAoLUGxhblNuaXBwZXQSCgoCaWQYASABKAkSMAoEZGF0ZRgCIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXBCBrpIA8gBARIrCgdyZWNpcGVzGAMgAygLMhouZnJvbnRlbmRhcGkuUmVjaXBlU25pcHBldCJjCg9HZXRQbGFuc1JlcXVlc3QSNgoKc3RhcnRfZGF0ZRgBIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXBCBrpIA8gBARIYCghudW1fZGF5cxgCIAEoDUIGukgDyAEBIjsKEEdldFBsYW5zUmVzcG9uc2USJwoFcGxhbnMYASADKAsyGC5mcm9udGVuZGFwaS5QbGFuU25pcHBldCLwAQoEUGxhbhIKCgJpZBgBIAEoCRInCgZzdGF0dXMYAiABKA4yFy5mcm9udGVuZGFwaS5QbGFuU3RhdHVzEisKB3JlY2lwZXMYAyADKAsyGi5mcm9udGVuZGFwaS5SZWNpcGVTbmlwcGV0EisKC3N0ZXBfZ3JvdXBzGAQgAygLMhYuZnJvbnRlbmRhcGkuU3RlcEdyb3VwEg0KBW5vdGVzGAUgAygJEjMKC2luZ3JlZGllbnRzGAYgAygLMh4uZnJvbnRlbmRhcGkuSW5ncmVkaWVudFNlY3Rpb24SFQoNc2VydmluZ19zaXplcxgHIAMoCSIhCg5HZXRQbGFuUmVxdWVzdBIPCgdwbGFuX2lkGAEgASgJIk4KD0dldFBsYW5SZXNwb25zZRInCgRwbGFuGAEgASgLMhEuZnJvbnRlbmRhcGkuUGxhbkIGukgDyAEBEhIKCmxsbV9wcm9tcHQYAiABKAkiOAoRVXBkYXRlUGxhblJlcXVlc3QSDwoHcGxhbl9pZBgBIAEoCRISCgpyZWNpcGVfaWRzGAIgAygJIj0KElVwZGF0ZVBsYW5SZXNwb25zZRInCgRwbGFuGAEgASgLMhEuZnJvbnRlbmRhcGkuUGxhbkIGukgDyAEBIiQKEURlbGV0ZVBsYW5SZXF1ZXN0Eg8KB3BsYW5faWQYASABKAkiFAoSRGVsZXRlUGxhblJlc3BvbnNlIicKEkFkZEJvb2ttYXJrUmVxdWVzdBIRCglyZWNpcGVfaWQYASABKAkiFQoTQWRkQm9va21hcmtSZXNwb25zZSIqChVSZW1vdmVCb29rbWFya1JlcXVlc3QSEQoJcmVjaXBlX2lkGAEgASgJIhgKFlJlbW92ZUJvb2ttYXJrUmVzcG9uc2UimgEKC0NoYXRNZXNzYWdlEg8KB2NvbnRlbnQYASABKAkSKwoEcm9sZRgCIAEoDjIdLmZyb250ZW5kYXBpLkNoYXRNZXNzYWdlLlJvbGUSDAoEdXJscxgDIAMoCSI/CgRSb2xlEhQKEFJPTEVfVU5TUEVDSUZJRUQQABINCglST0xFX1VTRVIQARISCg5ST0xFX0FTU0lTVEFOVBACIkUKD0NoYXRQbGFuUmVxdWVzdBIPCgdjaGF0X2lkGAEgASgJEhAKCG5ld19jaGF0GAIgASgIEg8KB21lc3NhZ2UYAyABKAkiYAoQQ2hhdFBsYW5SZXNwb25zZRIPCgdjaGF0X2lkGAEgASgJEioKCG1lc3NhZ2VzGAIgAygLMhguZnJvbnRlbmRhcGkuQ2hhdE1lc3NhZ2USDwoHcGxhbl9pZBgDIAEoCSIYChZHZXRDaGF0TWVzc2FnZXNSZXF1ZXN0IlYKF0dldENoYXRNZXNzYWdlc1Jlc3BvbnNlEg8KB2NoYXRfaWQYASABKAkSKgoIbWVzc2FnZXMYAiADKAsyGC5mcm9udGVuZGFwaS5DaGF0TWVzc2FnZSpRCghMYW5ndWFnZRIYChRMQU5HVUFHRV9VTlNQRUNJRklFRBAAEhQKEExBTkdVQUdFX0VOR0xJU0gQARIVChFMQU5HVUFHRV9KQVBBTkVTRRACKsYBCgtSZWNpcGVHZW5yZRIcChhSRUNJUEVfR0VOUkVfVU5TUEVDSUZJRUQQABIZChVSRUNJUEVfR0VOUkVfSkFQQU5FU0UQARIYChRSRUNJUEVfR0VOUkVfQ0hJTkVTRRACEhgKFFJFQ0lQRV9HRU5SRV9XRVNURVJOEAMSFwoTUkVDSVBFX0dFTlJFX0tPUkVBThAEEhgKFFJFQ0lQRV9HRU5SRV9JVEFMSUFOEAUSFwoTUkVDSVBFX0dFTlJFX0VUSE5JQxAGKokBCgxSZWNpcGVTb3VyY2USHQoZUkVDSVBFX1NPVVJDRV9VTlNQRUNJRklFRBAAEhkKFVJFQ0lQRV9TT1VSQ0VfQ09PS1BBRBABEh0KGVJFQ0lQRV9TT1VSQ0VfT1JBTkdFX1BBR0UQAhIgChxSRUNJUEVfU09VUkNFX0RFTElTSF9LSVRDSEVOEAMqZQoMUmVjaXBlU3RhdHVzEh0KGVJFQ0lQRV9TVEFUVVNfVU5TUEVDSUZJRUQQABIcChhSRUNJUEVfU1RBVFVTX1BST0NFU1NJTkcQARIYChRSRUNJUEVfU1RBVFVTX0FDVElWRRACKl0KClBsYW5TdGF0dXMSGwoXUExBTl9TVEFUVVNfVU5TUEVDSUZJRUQQABIaChZQTEFOX1NUQVRVU19QUk9DRVNTSU5HEAESFgoSUExBTl9TVEFUVVNfQUNUSVZFEAIyTgoLQ2hhdFNlcnZpY2USPwoEQ2hhdBIYLmZyb250ZW5kYXBpLkNoYXRSZXF1ZXN0GhkuZnJvbnRlbmRhcGkuQ2hhdFJlc3BvbnNlKAEwATL4CAoPRnJvbnRlbmRTZXJ2aWNlEkoKCUdldFJlY2lwZRIdLmZyb250ZW5kYXBpLkdldFJlY2lwZVJlcXVlc3QaHi5mcm9udGVuZGFwaS5HZXRSZWNpcGVSZXNwb25zZRJQCgtMaXN0UmVjaXBlcxIfLmZyb250ZW5kYXBpLkxpc3RSZWNpcGVzUmVxdWVzdBogLmZyb250ZW5kYXBpLkxpc3RSZWNpcGVzUmVzcG9uc2USSgoJU3RhcnRDaGF0Eh0uZnJvbnRlbmRhcGkuU3RhcnRDaGF0UmVxdWVzdBoeLmZyb250ZW5kYXBpLlN0YXJ0Q2hhdFJlc3BvbnNlEkoKCUFkZFJlY2lwZRIdLmZyb250ZW5kYXBpLkFkZFJlY2lwZVJlcXVlc3QaHi5mcm9udGVuZGFwaS5BZGRSZWNpcGVSZXNwb25zZRJZCg5HZW5lcmF0ZVJlY2lwZRIiLmZyb250ZW5kYXBpLkdlbmVyYXRlUmVjaXBlUmVxdWVzdBojLmZyb250ZW5kYXBpLkdlbmVyYXRlUmVjaXBlUmVzcG9uc2USUwoMR2VuZXJhdGVQbGFuEiAuZnJvbnRlbmRhcGkuR2VuZXJhdGVQbGFuUmVxdWVzdBohLmZyb250ZW5kYXBpLkdlbmVyYXRlUGxhblJlc3BvbnNlEkcKCENoYXRQbGFuEhwuZnJvbnRlbmRhcGkuQ2hhdFBsYW5SZXF1ZXN0Gh0uZnJvbnRlbmRhcGkuQ2hhdFBsYW5SZXNwb25zZRJcCg9HZXRDaGF0TWVzc2FnZXMSIy5mcm9udGVuZGFwaS5HZXRDaGF0TWVzc2FnZXNSZXF1ZXN0GiQuZnJvbnRlbmRhcGkuR2V0Q2hhdE1lc3NhZ2VzUmVzcG9uc2USRwoIR2V0UGxhbnMSHC5mcm9udGVuZGFwaS5HZXRQbGFuc1JlcXVlc3QaHS5mcm9udGVuZGFwaS5HZXRQbGFuc1Jlc3BvbnNlEkQKB0dldFBsYW4SGy5mcm9udGVuZGFwaS5HZXRQbGFuUmVxdWVzdBocLmZyb250ZW5kYXBpLkdldFBsYW5SZXNwb25zZRJNCgpVcGRhdGVQbGFuEh4uZnJvbnRlbmRhcGkuVXBkYXRlUGxhblJlcXVlc3QaHy5mcm9udGVuZGFwaS5VcGRhdGVQbGFuUmVzcG9uc2USTQoKRGVsZXRlUGxhbhIeLmZyb250ZW5kYXBpLkRlbGV0ZVBsYW5SZXF1ZXN0Gh8uZnJvbnRlbmRhcGkuRGVsZXRlUGxhblJlc3BvbnNlElAKC0FkZEJvb2ttYXJrEh8uZnJvbnRlbmRhcGkuQWRkQm9va21hcmtSZXF1ZXN0GiAuZnJvbnRlbmRhcGkuQWRkQm9va21hcmtSZXNwb25zZRJZCg5SZW1vdmVCb29rbWFyaxIiLmZyb250ZW5kYXBpLlJlbW92ZUJvb2ttYXJrUmVxdWVzdBojLmZyb250ZW5kYXBpLlJlbW92ZUJvb2ttYXJrUmVzcG9uc2VCPVo7Z2l0aHViLmNvbS9jdXJpb3N3aXRjaC9jb29rY2hhdC9mcm9udGVuZC9hcGkvZ287ZnJvbnRlbmRhcGliBnByb3RvMw", [file_buf_validate_validate, file_google_protobuf_timestamp]); + fileDesc("Chpmcm9udGVuZGFwaS9mcm9udGVuZC5wcm90bxILZnJvbnRlbmRhcGkiPAoLQ2hhdENvbnRlbnQSEQoHbWVzc2FnZRgBIAEoCUgAEg8KBWF1ZGlvGAIgASgMSABCCQoHcGF5bG9hZCJuCgtDaGF0UmVxdWVzdBIpCgdjb250ZW50GAEgASgLMhguZnJvbnRlbmRhcGkuQ2hhdENvbnRlbnQSFQoLcmVjaXBlX3RleHQYAiABKAlIABITCglyZWNpcGVfaWQYAyABKAlIAEIICgZyZWNpcGUiOQoMQ2hhdFJlc3BvbnNlEikKB2NvbnRlbnQYASABKAsyGC5mcm9udGVuZGFwaS5DaGF0Q29udGVudCIyChBSZWNpcGVJbmdyZWRpZW50EgwKBG5hbWUYASABKAkSEAoIcXVhbnRpdHkYAiABKAkiNAoKUmVjaXBlU3RlcBITCgtkZXNjcmlwdGlvbhgBIAEoCRIRCglpbWFnZV91cmwYAiABKAkiVgoRSW5ncmVkaWVudFNlY3Rpb24SDQoFdGl0bGUYASABKAkSMgoLaW5ncmVkaWVudHMYAiADKAsyHS5mcm9udGVuZGFwaS5SZWNpcGVJbmdyZWRpZW50IosDCgZSZWNpcGUSCgoCaWQYASABKAkSKQoGc291cmNlGAIgASgOMhkuZnJvbnRlbmRhcGkuUmVjaXBlU291cmNlEikKBnN0YXR1cxgDIAEoDjIZLmZyb250ZW5kYXBpLlJlY2lwZVN0YXR1cxINCgV0aXRsZRgEIAEoCRIRCglpbWFnZV91cmwYBSABKAkSEwoLZGVzY3JpcHRpb24YBiABKAkSMgoLaW5ncmVkaWVudHMYByADKAsyHS5mcm9udGVuZGFwaS5SZWNpcGVJbmdyZWRpZW50Ej4KFmFkZGl0aW9uYWxfaW5ncmVkaWVudHMYCCADKAsyHi5mcm9udGVuZGFwaS5JbmdyZWRpZW50U2VjdGlvbhImCgVzdGVwcxgJIAMoCzIXLmZyb250ZW5kYXBpLlJlY2lwZVN0ZXASDQoFbm90ZXMYCiABKAkSFAoMc2VydmluZ19zaXplGAsgASgJEicKCGxhbmd1YWdlGAwgASgOMhUuZnJvbnRlbmRhcGkuTGFuZ3VhZ2UiJQoQR2V0UmVjaXBlUmVxdWVzdBIRCglyZWNpcGVfaWQYASABKAkiYwoRR2V0UmVjaXBlUmVzcG9uc2USIwoGcmVjaXBlGAEgASgLMhMuZnJvbnRlbmRhcGkuUmVjaXBlEhIKCmxsbV9wcm9tcHQYAiABKAkSFQoNaXNfYm9va21hcmtlZBgDIAEoCCI7CgpQYWdpbmF0aW9uEg8KB2xhc3RfaWQYASABKAkSHAoUbGFzdF90aW1lc3RhbXBfbmFub3MYAiABKAMiTgoNUmVjaXBlU25pcHBldBIKCgJpZBgBIAEoCRINCgV0aXRsZRgCIAEoCRIPCgdzdW1tYXJ5GAMgASgJEhEKCWltYWdlX3VybBgEIAEoCSJjChJMaXN0UmVjaXBlc1JlcXVlc3QSDQoFcXVlcnkYASABKAkSEQoJYm9va21hcmtzGAMgASgIEisKCnBhZ2luYXRpb24YAiABKAsyFy5mcm9udGVuZGFwaS5QYWdpbmF0aW9uIm8KE0xpc3RSZWNpcGVzUmVzcG9uc2USKwoHcmVjaXBlcxgBIAMoCzIaLmZyb250ZW5kYXBpLlJlY2lwZVNuaXBwZXQSKwoKcGFnaW5hdGlvbhgCIAEoCzIXLmZyb250ZW5kYXBpLlBhZ2luYXRpb24isAIKEFN0YXJ0Q2hhdFJlcXVlc3QSFQoLcmVjaXBlX3RleHQYAiABKAlIABITCglyZWNpcGVfaWQYAyABKAlIABIRCgdwbGFuX2lkGAYgASgJSAASQwoObW9kZWxfcHJvdmlkZXIYBCABKA4yKy5mcm9udGVuZGFwaS5TdGFydENoYXRSZXF1ZXN0Lk1vZGVsUHJvdmlkZXISEgoKbGxtX3Byb21wdBgFIAEoCRINCgVtb2RlbBgHIAEoCSJrCg1Nb2RlbFByb3ZpZGVyEh4KGk1PREVMX1BST1ZJREVSX1VOU1BFQ0lGSUVEEAASHwobTU9ERUxfUFJPVklERVJfR09PR0xFX0dFTkFJEAESGQoVTU9ERUxfUFJPVklERVJfT1BFTkFJEAJCCAoGcmVjaXBlIm8KEVN0YXJ0Q2hhdFJlc3BvbnNlEhQKDGNoYXRfYXBpX2tleRgBIAEoCRISCgpjaGF0X21vZGVsGAIgASgJEhkKEWNoYXRfaW5zdHJ1Y3Rpb25zGAMgASgJEhUKDXN0YXJ0X21lc3NhZ2UYBCABKAkigAMKEEFkZFJlY2lwZVJlcXVlc3QSDQoFdGl0bGUYASABKAkSGwoTbWFpbl9pbWFnZV9kYXRhX3VybBgCIAEoCRITCgtkZXNjcmlwdGlvbhgDIAEoCRIyCgtpbmdyZWRpZW50cxgEIAMoCzIdLmZyb250ZW5kYXBpLlJlY2lwZUluZ3JlZGllbnQSPgoWYWRkaXRpb25hbF9pbmdyZWRpZW50cxgFIAMoCzIeLmZyb250ZW5kYXBpLkluZ3JlZGllbnRTZWN0aW9uEjoKBXN0ZXBzGAYgAygLMisuZnJvbnRlbmRhcGkuQWRkUmVjaXBlUmVxdWVzdC5BZGRSZWNpcGVTdGVwEhQKDHNlcnZpbmdfc2l6ZRgHIAEoCRInCghsYW5ndWFnZRgIIAEoDjIVLmZyb250ZW5kYXBpLkxhbmd1YWdlGjwKDUFkZFJlY2lwZVN0ZXASEwoLZGVzY3JpcHRpb24YASABKAkSFgoOaW1hZ2VfZGF0YV91cmwYAiABKAkiJgoRQWRkUmVjaXBlUmVzcG9uc2USEQoJcmVjaXBlX2lkGAEgASgJIicKFUdlbmVyYXRlUmVjaXBlUmVxdWVzdBIOCgZwcm9tcHQYASABKAkiUwoWR2VuZXJhdGVSZWNpcGVSZXNwb25zZRI5ChJhZGRfcmVjaXBlX3JlcXVlc3QYASABKAsyHS5mcm9udGVuZGFwaS5BZGRSZWNpcGVSZXF1ZXN0InoKE0dlbmVyYXRlUGxhblJlcXVlc3QSEAoIbnVtX2RheXMYASABKA0SEwoLaW5ncmVkaWVudHMYAiADKAkSKAoGZ2VucmVzGAMgAygOMhguZnJvbnRlbmRhcGkuUmVjaXBlR2VucmUSEgoKcmVjaXBlX2lkcxgEIAMoCSIWChRHZW5lcmF0ZVBsYW5SZXNwb25zZSJQCglTdGVwR3JvdXASDQoFbGFiZWwYASABKAkSJgoFc3RlcHMYAiADKAsyFy5mcm9udGVuZGFwaS5SZWNpcGVTdGVwEgwKBG5vdGUYAyABKAkieAoLUGxhblNuaXBwZXQSCgoCaWQYASABKAkSMAoEZGF0ZRgCIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXBCBrpIA8gBARIrCgdyZWNpcGVzGAMgAygLMhouZnJvbnRlbmRhcGkuUmVjaXBlU25pcHBldCJjCg9HZXRQbGFuc1JlcXVlc3QSNgoKc3RhcnRfZGF0ZRgBIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXBCBrpIA8gBARIYCghudW1fZGF5cxgCIAEoDUIGukgDyAEBIjsKEEdldFBsYW5zUmVzcG9uc2USJwoFcGxhbnMYASADKAsyGC5mcm9udGVuZGFwaS5QbGFuU25pcHBldCLwAQoEUGxhbhIKCgJpZBgBIAEoCRInCgZzdGF0dXMYAiABKA4yFy5mcm9udGVuZGFwaS5QbGFuU3RhdHVzEisKB3JlY2lwZXMYAyADKAsyGi5mcm9udGVuZGFwaS5SZWNpcGVTbmlwcGV0EisKC3N0ZXBfZ3JvdXBzGAQgAygLMhYuZnJvbnRlbmRhcGkuU3RlcEdyb3VwEg0KBW5vdGVzGAUgAygJEjMKC2luZ3JlZGllbnRzGAYgAygLMh4uZnJvbnRlbmRhcGkuSW5ncmVkaWVudFNlY3Rpb24SFQoNc2VydmluZ19zaXplcxgHIAMoCSIhCg5HZXRQbGFuUmVxdWVzdBIPCgdwbGFuX2lkGAEgASgJIk4KD0dldFBsYW5SZXNwb25zZRInCgRwbGFuGAEgASgLMhEuZnJvbnRlbmRhcGkuUGxhbkIGukgDyAEBEhIKCmxsbV9wcm9tcHQYAiABKAkiOAoRVXBkYXRlUGxhblJlcXVlc3QSDwoHcGxhbl9pZBgBIAEoCRISCgpyZWNpcGVfaWRzGAIgAygJIj0KElVwZGF0ZVBsYW5SZXNwb25zZRInCgRwbGFuGAEgASgLMhEuZnJvbnRlbmRhcGkuUGxhbkIGukgDyAEBIiQKEURlbGV0ZVBsYW5SZXF1ZXN0Eg8KB3BsYW5faWQYASABKAkiFAoSRGVsZXRlUGxhblJlc3BvbnNlIicKEkFkZEJvb2ttYXJrUmVxdWVzdBIRCglyZWNpcGVfaWQYASABKAkiFQoTQWRkQm9va21hcmtSZXNwb25zZSIqChVSZW1vdmVCb29rbWFya1JlcXVlc3QSEQoJcmVjaXBlX2lkGAEgASgJIhgKFlJlbW92ZUJvb2ttYXJrUmVzcG9uc2UimgEKC0NoYXRNZXNzYWdlEg8KB2NvbnRlbnQYASABKAkSKwoEcm9sZRgCIAEoDjIdLmZyb250ZW5kYXBpLkNoYXRNZXNzYWdlLlJvbGUSDAoEdXJscxgDIAMoCSI/CgRSb2xlEhQKEFJPTEVfVU5TUEVDSUZJRUQQABINCglST0xFX1VTRVIQARISCg5ST0xFX0FTU0lTVEFOVBACIkUKD0NoYXRQbGFuUmVxdWVzdBIPCgdjaGF0X2lkGAEgASgJEhAKCG5ld19jaGF0GAIgASgIEg8KB21lc3NhZ2UYAyABKAkiYAoQQ2hhdFBsYW5SZXNwb25zZRIPCgdjaGF0X2lkGAEgASgJEioKCG1lc3NhZ2VzGAIgAygLMhguZnJvbnRlbmRhcGkuQ2hhdE1lc3NhZ2USDwoHcGxhbl9pZBgDIAEoCSIYChZHZXRDaGF0TWVzc2FnZXNSZXF1ZXN0ImcKF0dldENoYXRNZXNzYWdlc1Jlc3BvbnNlEg8KB2NoYXRfaWQYASABKAkSKgoIbWVzc2FnZXMYAiADKAsyGC5mcm9udGVuZGFwaS5DaGF0TWVzc2FnZRIPCgdwbGFuX2lkGAMgASgJKlEKCExhbmd1YWdlEhgKFExBTkdVQUdFX1VOU1BFQ0lGSUVEEAASFAoQTEFOR1VBR0VfRU5HTElTSBABEhUKEUxBTkdVQUdFX0pBUEFORVNFEAIqxgEKC1JlY2lwZUdlbnJlEhwKGFJFQ0lQRV9HRU5SRV9VTlNQRUNJRklFRBAAEhkKFVJFQ0lQRV9HRU5SRV9KQVBBTkVTRRABEhgKFFJFQ0lQRV9HRU5SRV9DSElORVNFEAISGAoUUkVDSVBFX0dFTlJFX1dFU1RFUk4QAxIXChNSRUNJUEVfR0VOUkVfS09SRUFOEAQSGAoUUkVDSVBFX0dFTlJFX0lUQUxJQU4QBRIXChNSRUNJUEVfR0VOUkVfRVRITklDEAYqiQEKDFJlY2lwZVNvdXJjZRIdChlSRUNJUEVfU09VUkNFX1VOU1BFQ0lGSUVEEAASGQoVUkVDSVBFX1NPVVJDRV9DT09LUEFEEAESHQoZUkVDSVBFX1NPVVJDRV9PUkFOR0VfUEFHRRACEiAKHFJFQ0lQRV9TT1VSQ0VfREVMSVNIX0tJVENIRU4QAyplCgxSZWNpcGVTdGF0dXMSHQoZUkVDSVBFX1NUQVRVU19VTlNQRUNJRklFRBAAEhwKGFJFQ0lQRV9TVEFUVVNfUFJPQ0VTU0lORxABEhgKFFJFQ0lQRV9TVEFUVVNfQUNUSVZFEAIqXQoKUGxhblN0YXR1cxIbChdQTEFOX1NUQVRVU19VTlNQRUNJRklFRBAAEhoKFlBMQU5fU1RBVFVTX1BST0NFU1NJTkcQARIWChJQTEFOX1NUQVRVU19BQ1RJVkUQAjJOCgtDaGF0U2VydmljZRI/CgRDaGF0EhguZnJvbnRlbmRhcGkuQ2hhdFJlcXVlc3QaGS5mcm9udGVuZGFwaS5DaGF0UmVzcG9uc2UoATABMvgICg9Gcm9udGVuZFNlcnZpY2USSgoJR2V0UmVjaXBlEh0uZnJvbnRlbmRhcGkuR2V0UmVjaXBlUmVxdWVzdBoeLmZyb250ZW5kYXBpLkdldFJlY2lwZVJlc3BvbnNlElAKC0xpc3RSZWNpcGVzEh8uZnJvbnRlbmRhcGkuTGlzdFJlY2lwZXNSZXF1ZXN0GiAuZnJvbnRlbmRhcGkuTGlzdFJlY2lwZXNSZXNwb25zZRJKCglTdGFydENoYXQSHS5mcm9udGVuZGFwaS5TdGFydENoYXRSZXF1ZXN0Gh4uZnJvbnRlbmRhcGkuU3RhcnRDaGF0UmVzcG9uc2USSgoJQWRkUmVjaXBlEh0uZnJvbnRlbmRhcGkuQWRkUmVjaXBlUmVxdWVzdBoeLmZyb250ZW5kYXBpLkFkZFJlY2lwZVJlc3BvbnNlElkKDkdlbmVyYXRlUmVjaXBlEiIuZnJvbnRlbmRhcGkuR2VuZXJhdGVSZWNpcGVSZXF1ZXN0GiMuZnJvbnRlbmRhcGkuR2VuZXJhdGVSZWNpcGVSZXNwb25zZRJTCgxHZW5lcmF0ZVBsYW4SIC5mcm9udGVuZGFwaS5HZW5lcmF0ZVBsYW5SZXF1ZXN0GiEuZnJvbnRlbmRhcGkuR2VuZXJhdGVQbGFuUmVzcG9uc2USRwoIQ2hhdFBsYW4SHC5mcm9udGVuZGFwaS5DaGF0UGxhblJlcXVlc3QaHS5mcm9udGVuZGFwaS5DaGF0UGxhblJlc3BvbnNlElwKD0dldENoYXRNZXNzYWdlcxIjLmZyb250ZW5kYXBpLkdldENoYXRNZXNzYWdlc1JlcXVlc3QaJC5mcm9udGVuZGFwaS5HZXRDaGF0TWVzc2FnZXNSZXNwb25zZRJHCghHZXRQbGFucxIcLmZyb250ZW5kYXBpLkdldFBsYW5zUmVxdWVzdBodLmZyb250ZW5kYXBpLkdldFBsYW5zUmVzcG9uc2USRAoHR2V0UGxhbhIbLmZyb250ZW5kYXBpLkdldFBsYW5SZXF1ZXN0GhwuZnJvbnRlbmRhcGkuR2V0UGxhblJlc3BvbnNlEk0KClVwZGF0ZVBsYW4SHi5mcm9udGVuZGFwaS5VcGRhdGVQbGFuUmVxdWVzdBofLmZyb250ZW5kYXBpLlVwZGF0ZVBsYW5SZXNwb25zZRJNCgpEZWxldGVQbGFuEh4uZnJvbnRlbmRhcGkuRGVsZXRlUGxhblJlcXVlc3QaHy5mcm9udGVuZGFwaS5EZWxldGVQbGFuUmVzcG9uc2USUAoLQWRkQm9va21hcmsSHy5mcm9udGVuZGFwaS5BZGRCb29rbWFya1JlcXVlc3QaIC5mcm9udGVuZGFwaS5BZGRCb29rbWFya1Jlc3BvbnNlElkKDlJlbW92ZUJvb2ttYXJrEiIuZnJvbnRlbmRhcGkuUmVtb3ZlQm9va21hcmtSZXF1ZXN0GiMuZnJvbnRlbmRhcGkuUmVtb3ZlQm9va21hcmtSZXNwb25zZUI9WjtnaXRodWIuY29tL2N1cmlvc3dpdGNoL2Nvb2tjaGF0L2Zyb250ZW5kL2FwaS9nbztmcm9udGVuZGFwaWIGcHJvdG8z", [file_buf_validate_validate, file_google_protobuf_timestamp]); /** * The content of a chat message. @@ -1541,6 +1541,13 @@ export type GetChatMessagesResponse = Message<"frontendapi.GetChatMessagesRespon * @generated from field: repeated frontendapi.ChatMessage messages = 2; */ messages: ChatMessage[]; + + /** + * The plan ID if the chat is already completed. + * + * @generated from field: string plan_id = 3; + */ + planId: string; }; export type GetChatMessagesResponseValid = GetChatMessagesResponse; diff --git a/frontend/client/messages/en.json b/frontend/client/messages/en.json index d8e4508d..dec9ecb0 100644 --- a/frontend/client/messages/en.json +++ b/frontend/client/messages/en.json @@ -12,6 +12,9 @@ "nav_plan": "Plan", "nav_bookmarks": "Bookmarks", "nav_cart": "Cart", + + "add_plan_new_chat": "New Chat", + "add_plan_view_plan": "View Plan", "home_search_placeholder": "What do you want to cook?", "home_recommended_recipes": "Recommended Recipes", "home_popular_recipes": "Popular Recipes", @@ -98,6 +101,7 @@ "input val", "local formattedDate = val: datetime weekday=short month=short day=numeric" ], + "selectors": ["val"], "match": { "val=*": "{formattedDate}" } diff --git a/frontend/client/messages/ja.json b/frontend/client/messages/ja.json index 0d265222..65db40a6 100644 --- a/frontend/client/messages/ja.json +++ b/frontend/client/messages/ja.json @@ -12,6 +12,9 @@ "nav_plan": "献立", "nav_bookmarks": "ブックマーク", "nav_cart": "カート", + + "add_plan_new_chat": "新しい献立", + "add_plan_view_plan": "献立を見る", "home_search_placeholder": "つくりたい料理は?", "home_recommended_recipes": "おすすめのレシピ", "home_popular_recipes": "人気レシピ", @@ -98,6 +101,7 @@ "input val", "local formattedDate = val: datetime weekday=short month=short day=numeric" ], + "selectors": ["val"], "match": { "val=*": "{formattedDate}" } diff --git a/frontend/client/src/pages/plans/add/ChatPlan.tsx b/frontend/client/src/pages/plans/add/ChatPlan.tsx index e8035140..28e8ccc6 100644 --- a/frontend/client/src/pages/plans/add/ChatPlan.tsx +++ b/frontend/client/src/pages/plans/add/ChatPlan.tsx @@ -5,6 +5,7 @@ import { ChatMessage_Role, ChatMessageSchema, chatPlan, + type GetChatMessagesResponse, GetChatMessagesResponseSchema, } from "@cookchat/frontend-api"; import { Button, Input, Link, TextField } from "@heroui/react"; @@ -72,29 +73,46 @@ const ChatBubble = forwardRef( }, ); -const loadingMessage = create(ChatMessageSchema, { - role: ChatMessage_Role.ASSISTANT, -}); - export function ChatPlan() { const queries = useFrontendQueries(); const getChatMessagesQuery = queries.getChatMessages(); const queryClient = useQueryClient(); - const { data: getChatMessagesRes, isPending } = - useQuery(getChatMessagesQuery); + const { data: getChatMessagesRes, isPending } = useQuery({ + ...getChatMessagesQuery, + refetchInterval: (query) => { + const messages = query.state.data?.messages ?? []; + const last = messages[messages.length - 1]; + + return last?.role === ChatMessage_Role.ASSISTANT && !last?.content + ? 3000 + : false; + }, + refetchIntervalInBackground: true, + }); const [loaded, setLoaded] = useState(false); const [inputText, setInputText] = useState(""); const doChatPlan = useMutation(chatPlan, { onMutate: (req) => { - const messages = getChatMessagesRes?.messages ?? []; - messages.push( - create(ChatMessageSchema, { - role: ChatMessage_Role.USER, - content: req.message, - }), + queryClient.setQueryData( + getChatMessagesQuery.queryKey, + (prev) => + create(GetChatMessagesResponseSchema, { + chatId: req.newChat ? "" : (prev?.chatId ?? ""), + planId: "", + messages: [ + ...(req.newChat ? [] : (prev?.messages ?? [])), + create(ChatMessageSchema, { + role: ChatMessage_Role.USER, + content: req.message, + }), + create(ChatMessageSchema, { + role: ChatMessage_Role.ASSISTANT, + }), + ], + }), ); }, onSuccess: (resp) => { @@ -112,6 +130,13 @@ export function ChatPlan() { }, }); + const startNewChat = useCallback(() => { + doChatPlan.mutate({ + newChat: true, + message: m.chat_greeting(), + }); + }, [doChatPlan]); + useEffect(() => { if (loaded || !getChatMessagesRes) { return; @@ -119,12 +144,13 @@ export function ChatPlan() { setLoaded(true); if (getChatMessagesRes.messages.length === 0) { - doChatPlan.mutate({ - chatId: getChatMessagesRes?.chatId, - message: m.chat_greeting(), - }); + startNewChat(); } - }, [doChatPlan, getChatMessagesRes, loaded]); + }, [startNewChat, getChatMessagesRes, loaded]); + + const onNewChatClick = useCallback(() => { + startNewChat(); + }, [startNewChat]); const onSendClick = useCallback(() => { const message = inputText; @@ -150,6 +176,9 @@ export function ChatPlan() { } const messages = getChatMessagesRes.messages; + const assistantPending = + messages[messages.length - 1]?.role === ChatMessage_Role.ASSISTANT && + !messages[messages.length - 1]?.content; return (
@@ -157,7 +186,23 @@ export function ChatPlan() { // biome-ignore lint/suspicious/noArrayIndexKey: ordered list of items ))} - {doChatPlan.isPending && } +
+ + {getChatMessagesRes.planId && ( + + + + )} +
diff --git a/frontend/server/internal/handler/chatplan/chatplan.go b/frontend/server/internal/handler/chatplan/chatplan.go index 31bf1f22..8e590b8c 100644 --- a/frontend/server/internal/handler/chatplan/chatplan.go +++ b/frontend/server/internal/handler/chatplan/chatplan.go @@ -110,6 +110,16 @@ func (h *Handler) ChatPlan(ctx context.Context, req *frontendapi.ChatPlanRequest content[i] = genai.NewContentFromText(message.Content, role) } + // Save so if user refreshes during the slow chat call we can continue in a pending state. + chat.Messages = append(chat.Messages, cookchatdb.ChatMessage{ + Role: cookchatdb.ChatRoleAssistant, + CreatedAt: time.Now(), + }) + if _, err := chats.Doc(chat.ID).Set(ctx, chat); err != nil { + return nil, fmt.Errorf("chatplan: saving chat document: %w", err) + } + ctx = context.WithoutCancel(ctx) + res, err := backoff.Retry(ctx, func() (*genai.GenerateContentResponse, error) { res, err := h.genAI.Models.GenerateContent(ctx, "gemini-3-flash-preview", content, &genai.GenerateContentConfig{ SystemInstruction: genai.NewContentFromText(llm.ChatPlanPrompt(strings.Join(recentRecipes, ", ")), genai.RoleModel), @@ -128,6 +138,11 @@ func (h *Handler) ChatPlan(ctx context.Context, req *frontendapi.ChatPlanRequest return res, nil }) if err != nil { + // Back out with best-effort + chat.Messages = chat.Messages[:len(chat.Messages)-2] + if _, err := chats.Doc(chat.ID).Set(ctx, chat); err != nil { + return nil, fmt.Errorf("chatplan: saving chat document: %w", err) + } return nil, err } @@ -183,6 +198,13 @@ func (h *Handler) ChatPlan(ctx context.Context, req *frontendapi.ChatPlanRequest if chat.PlanID == "" { chat.PlanID = planID } + // TODO: Do something better + switch i18n.UserLanguage(ctx) { + case "ja": + chat.Messages[len(chat.Messages)-1].Content = "あなたの献立を作成しました。" + default: + chat.Messages[len(chat.Messages)-1].Content = "Created your meal plan." + } if _, err := chats.Doc(chat.ID).Set(ctx, chat); err != nil { return nil, fmt.Errorf("chatplan: saving chat plan ID: %w", err) } @@ -193,10 +215,7 @@ func (h *Handler) ChatPlan(ctx context.Context, req *frontendapi.ChatPlanRequest }, nil } - chat.Messages = append(chat.Messages, cookchatdb.ChatMessage{ - Role: cookchatdb.ChatRoleAssistant, - Content: resText, - }) + chat.Messages[len(chat.Messages)-1].Content = resText if _, err := chats.Doc(chat.ID).Set(ctx, chat); err != nil { return nil, fmt.Errorf("chatplan: saving chat document: %w", err) diff --git a/frontend/server/internal/handler/getchatmessages/getchatmessages.go b/frontend/server/internal/handler/getchatmessages/getchatmessages.go index 9f70a5b4..36a02a67 100644 --- a/frontend/server/internal/handler/getchatmessages/getchatmessages.go +++ b/frontend/server/internal/handler/getchatmessages/getchatmessages.go @@ -7,6 +7,7 @@ import ( "context" "errors" "fmt" + "time" "cloud.google.com/go/firestore" "github.com/curioswitch/go-usegcp/middleware/firebaseauth" @@ -40,8 +41,12 @@ func (h *Handler) GetChatMessages(ctx context.Context, _ *frontendapi.GetChatMes return nil, fmt.Errorf("chatplan: decoding last chat document: %w", err) } } - if chat.PlanID != "" { - return &frontendapi.GetChatMessagesResponse{}, nil + if len(chat.Messages) > 0 && chat.Messages[len(chat.Messages)-1].Role == cookchatdb.ChatRoleAssistant { + lastMessage := chat.Messages[len(chat.Messages)-1] + if lastMessage.Content == "" && lastMessage.CreatedAt.Add(5*time.Minute).Before(time.Now()) { + // Assume chat failed if it's been 5 minutes pending on the assistant. + return &frontendapi.GetChatMessagesResponse{}, nil + } } messages := make([]*frontendapi.ChatMessage, len(chat.Messages)) @@ -58,5 +63,6 @@ func (h *Handler) GetChatMessages(ctx context.Context, _ *frontendapi.GetChatMes return &frontendapi.GetChatMessagesResponse{ ChatId: chat.ID, Messages: messages, + PlanId: chat.PlanID, }, nil }