@orbe-agro/client-core 5.6.259 → 5.6.261

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/dist/@ecme/assets/styles/app.css +23 -3
  2. package/dist/@ecme/assets/styles/tailwind/index.css +25 -3
  3. package/dist/@ecme/components/layouts/PostLoginLayout/PostLoginLayout.js +2 -1
  4. package/dist/@ecme/components/layouts/PostLoginLayout/PostLoginLayout.js.map +1 -1
  5. package/dist/@ecme/components/template/Footer.js +1 -1
  6. package/dist/@ecme/components/template/OrbeAiWidget/OrbeAiChatDrawer.js +121 -0
  7. package/dist/@ecme/components/template/OrbeAiWidget/OrbeAiChatDrawer.js.map +1 -0
  8. package/dist/@ecme/components/template/OrbeAiWidget/OrbeAiWidget.js +47 -18
  9. package/dist/@ecme/components/template/OrbeAiWidget/OrbeAiWidget.js.map +1 -1
  10. package/dist/@ecme/components/template/OrbeAiWidget/useResizablePanel.js +45 -31
  11. package/dist/@ecme/components/template/OrbeAiWidget/useResizablePanel.js.map +1 -1
  12. package/dist/@ecme/components/ui/Dropdown/DropdownMenu.js +5 -1
  13. package/dist/@ecme/components/ui/Dropdown/DropdownMenu.js.map +1 -1
  14. package/dist/@ecme/index.js +4 -1
  15. package/dist/@ecme/services/AiService.js +22 -0
  16. package/dist/@ecme/services/AiService.js.map +1 -1
  17. package/dist/@ecme/services/index.js +4 -1
  18. package/dist/@ecme/views/concepts/ai/Chat/components/ChatHistory.js +9 -23
  19. package/dist/@ecme/views/concepts/ai/Chat/components/ChatHistory.js.map +1 -1
  20. package/dist/@ecme/views/concepts/ai/Chat/components/ChatHistoryItem.js +6 -16
  21. package/dist/@ecme/views/concepts/ai/Chat/components/ChatHistoryItem.js.map +1 -1
  22. package/dist/@ecme/views/concepts/ai/Chat/components/ChatHistoryRenameDialog.js +10 -7
  23. package/dist/@ecme/views/concepts/ai/Chat/components/ChatHistoryRenameDialog.js.map +1 -1
  24. package/dist/@ecme/views/concepts/ai/Chat/components/ChatView.js +6 -4
  25. package/dist/@ecme/views/concepts/ai/Chat/components/ChatView.js.map +1 -1
  26. package/dist/@ecme/views/concepts/ai/Chat/hooks/useChatConversations.js +83 -0
  27. package/dist/@ecme/views/concepts/ai/Chat/hooks/useChatConversations.js.map +1 -0
  28. package/dist/@ecme/views/concepts/ai/Chat/hooks/useChatSend.js +3 -2
  29. package/dist/@ecme/views/concepts/ai/Chat/hooks/useChatSend.js.map +1 -1
  30. package/dist/@ecme/views/concepts/ai/Chat/hooks/useConversationMessages.js +37 -0
  31. package/dist/@ecme/views/concepts/ai/Chat/hooks/useConversationMessages.js.map +1 -0
  32. package/dist/@ecme/views/concepts/ai/Chat/store/generativeChatStore.js +25 -1
  33. package/dist/@ecme/views/concepts/ai/Chat/store/generativeChatStore.js.map +1 -1
  34. package/dist/@ecme/views/concepts/ai/Chat/utils.js +16 -1
  35. package/dist/@ecme/views/concepts/ai/Chat/utils.js.map +1 -1
  36. package/dist/@types/@ecme/components/layouts/PostLoginLayout/PostLoginLayout.d.ts.map +1 -1
  37. package/dist/@types/@ecme/components/template/OrbeAiWidget/OrbeAiChatDrawer.d.ts +14 -0
  38. package/dist/@types/@ecme/components/template/OrbeAiWidget/OrbeAiChatDrawer.d.ts.map +1 -0
  39. package/dist/@types/@ecme/components/template/OrbeAiWidget/OrbeAiWidget.d.ts.map +1 -1
  40. package/dist/@types/@ecme/components/template/OrbeAiWidget/useResizablePanel.d.ts +12 -5
  41. package/dist/@types/@ecme/components/template/OrbeAiWidget/useResizablePanel.d.ts.map +1 -1
  42. package/dist/@types/@ecme/components/ui/Dropdown/DropdownMenu.d.ts +2 -0
  43. package/dist/@types/@ecme/components/ui/Dropdown/DropdownMenu.d.ts.map +1 -1
  44. package/dist/@types/@ecme/services/AiService.d.ts +5 -0
  45. package/dist/@types/@ecme/services/AiService.d.ts.map +1 -1
  46. package/dist/@types/@ecme/views/concepts/ai/Chat/components/ChatHistory.d.ts +3 -1
  47. package/dist/@types/@ecme/views/concepts/ai/Chat/components/ChatHistory.d.ts.map +1 -1
  48. package/dist/@types/@ecme/views/concepts/ai/Chat/components/ChatHistoryItem.d.ts +1 -1
  49. package/dist/@types/@ecme/views/concepts/ai/Chat/components/ChatHistoryItem.d.ts.map +1 -1
  50. package/dist/@types/@ecme/views/concepts/ai/Chat/components/ChatHistoryRenameDialog.d.ts +5 -1
  51. package/dist/@types/@ecme/views/concepts/ai/Chat/components/ChatHistoryRenameDialog.d.ts.map +1 -1
  52. package/dist/@types/@ecme/views/concepts/ai/Chat/components/ChatView.d.ts +8 -1
  53. package/dist/@types/@ecme/views/concepts/ai/Chat/components/ChatView.d.ts.map +1 -1
  54. package/dist/@types/@ecme/views/concepts/ai/Chat/hooks/useChatConversations.d.ts +12 -0
  55. package/dist/@types/@ecme/views/concepts/ai/Chat/hooks/useChatConversations.d.ts.map +1 -0
  56. package/dist/@types/@ecme/views/concepts/ai/Chat/hooks/useChatSend.d.ts.map +1 -1
  57. package/dist/@types/@ecme/views/concepts/ai/Chat/hooks/useConversationMessages.d.ts +12 -0
  58. package/dist/@types/@ecme/views/concepts/ai/Chat/hooks/useConversationMessages.d.ts.map +1 -0
  59. package/dist/@types/@ecme/views/concepts/ai/Chat/store/generativeChatStore.d.ts +4 -0
  60. package/dist/@types/@ecme/views/concepts/ai/Chat/store/generativeChatStore.d.ts.map +1 -1
  61. package/dist/@types/@ecme/views/concepts/ai/Chat/types.d.ts +24 -0
  62. package/dist/@types/@ecme/views/concepts/ai/Chat/types.d.ts.map +1 -1
  63. package/dist/@types/@ecme/views/concepts/ai/Chat/utils.d.ts +13 -0
  64. package/dist/@types/@ecme/views/concepts/ai/Chat/utils.d.ts.map +1 -1
  65. package/dist/@types/base/configs/endpoints.config/endpoints.navigation.d.ts +8 -0
  66. package/dist/@types/base/configs/endpoints.config/endpoints.navigation.d.ts.map +1 -1
  67. package/dist/@types/base/configs/endpoints.config/transportadora/contratoFrete.d.ts +8 -0
  68. package/dist/@types/base/configs/endpoints.config/transportadora/contratoFrete.d.ts.map +1 -1
  69. package/dist/@types/base/services/modules/transportadora/ContratoFrete/ContratoFreteService.d.ts +2 -0
  70. package/dist/@types/base/services/modules/transportadora/ContratoFrete/ContratoFreteService.d.ts.map +1 -1
  71. package/dist/base/assets/styles/init.css +23 -4
  72. package/dist/base/configs/endpoints.config/transportadora/contratoFrete.js +3 -1
  73. package/dist/base/configs/endpoints.config/transportadora/contratoFrete.js.map +1 -1
  74. package/dist/base/index.js +3 -1
  75. package/dist/base/services/index.js +3 -1
  76. package/dist/base/services/modules/index.js +3 -1
  77. package/dist/base/services/modules/transportadora/ContratoFrete/ContratoFreteService.js +14 -0
  78. package/dist/base/services/modules/transportadora/ContratoFrete/ContratoFreteService.js.map +1 -1
  79. package/dist/base/services/modules/transportadora/index.js +3 -1
  80. package/lib/@ecme/components/layouts/PostLoginLayout/PostLoginLayout.tsx +4 -1
  81. package/lib/@ecme/components/template/OrbeAiWidget/OrbeAiChatDrawer.tsx +140 -0
  82. package/lib/@ecme/components/template/OrbeAiWidget/OrbeAiWidget.tsx +48 -16
  83. package/lib/@ecme/components/template/OrbeAiWidget/useResizablePanel.ts +72 -37
  84. package/lib/@ecme/components/ui/Dropdown/DropdownMenu.tsx +7 -1
  85. package/lib/@ecme/services/AiService.ts +24 -0
  86. package/lib/@ecme/views/concepts/ai/Chat/components/ChatHistory.tsx +11 -27
  87. package/lib/@ecme/views/concepts/ai/Chat/components/ChatHistoryItem.tsx +7 -13
  88. package/lib/@ecme/views/concepts/ai/Chat/components/ChatHistoryRenameDialog.tsx +17 -8
  89. package/lib/@ecme/views/concepts/ai/Chat/components/ChatView.tsx +17 -4
  90. package/lib/@ecme/views/concepts/ai/Chat/hooks/useChatConversations.tsx +128 -0
  91. package/lib/@ecme/views/concepts/ai/Chat/hooks/useChatSend.ts +3 -2
  92. package/lib/@ecme/views/concepts/ai/Chat/hooks/useConversationMessages.ts +68 -0
  93. package/lib/@ecme/views/concepts/ai/Chat/store/generativeChatStore.ts +44 -1
  94. package/lib/@ecme/views/concepts/ai/Chat/types.ts +28 -0
  95. package/lib/@ecme/views/concepts/ai/Chat/utils.ts +33 -0
  96. package/lib/base/configs/endpoints.config/transportadora/contratoFrete.ts +2 -0
  97. package/lib/base/services/modules/transportadora/ContratoFrete/ContratoFreteService.ts +14 -0
  98. package/package.json +1 -1
@@ -4,6 +4,7 @@ import { Form, FormItem } from '@/components/ui/Form'
4
4
  import Input from '@/components/ui/Input'
5
5
  import Button from '@/components/ui/Button'
6
6
  import { usGenerativeChatStore } from '../store/generativeChatStore'
7
+ import useChatConversations from '../hooks/useChatConversations'
7
8
  import { useForm, Controller } from 'react-hook-form'
8
9
  import { zodResolver } from '@hookform/resolvers/zod'
9
10
  import { z } from 'zod'
@@ -14,12 +15,19 @@ type FormSchema = {
14
15
  }
15
16
 
16
17
  const validationSchema: ZodType<FormSchema> = z.object({
17
- title: z.string().min(1, 'Please do not leave chat title blank!'),
18
+ title: z.string().trim().min(1, 'Informe um título para a conversa.'),
18
19
  })
19
20
 
20
- const ChatHistoryRenameDialog = () => {
21
- const { renameDialog, setRenameDialog, setChatHistoryName } =
22
- usGenerativeChatStore()
21
+ type ChatHistoryRenameDialogProps = {
22
+ /** Classes do overlay (ex.: subir o z-index acima do widget flutuante). */
23
+ overlayClassName?: string
24
+ }
25
+
26
+ const ChatHistoryRenameDialog = ({
27
+ overlayClassName,
28
+ }: ChatHistoryRenameDialogProps) => {
29
+ const { renameDialog, setRenameDialog } = usGenerativeChatStore()
30
+ const { renomear } = useChatConversations()
23
31
 
24
32
  const {
25
33
  control,
@@ -51,16 +59,17 @@ const ChatHistoryRenameDialog = () => {
51
59
  }
52
60
 
53
61
  const onFormSubmit = async ({ title }: FormSchema) => {
54
- setChatHistoryName({
55
- id: renameDialog.id,
56
- title,
57
- })
62
+ // Fecha antes da resposta: o rename é otimista e desfaz sozinho se o
63
+ // backend recusar.
64
+ const { id } = renameDialog
58
65
  handleDialogClose()
66
+ await renomear(id, title.trim())
59
67
  }
60
68
 
61
69
  return (
62
70
  <Dialog
63
71
  isOpen={renameDialog.open}
72
+ overlayClassName={overlayClassName}
64
73
  onClose={handleDialogClose}
65
74
  onRequestClose={handleDialogClose}
66
75
  >
@@ -12,6 +12,7 @@ import ApprovalCard from './ApprovalCard'
12
12
  import DataRequestCard from './DataRequestCard'
13
13
  import { usGenerativeChatStore } from '../store/generativeChatStore'
14
14
  import useChatSend from '../hooks/useChatSend'
15
+ import useConversationMessages from '../hooks/useConversationMessages'
15
16
  import type { Conversation } from '../types'
16
17
  import type { ScrollBarRef } from '@/components/ui/ScrollBar'
17
18
 
@@ -31,11 +32,21 @@ import type { ScrollBarRef } from '@/components/ui/ScrollBar'
31
32
  const BALAO_AGENTE =
32
33
  'prose rounded-xl bg-gray-100 px-5 py-2.5 text-sm text-gray-900 dark:bg-gray-700 dark:text-gray-100'
33
34
 
34
- const ChatView = () => {
35
+ type ChatViewProps = {
36
+ /**
37
+ * Mostra o drawer de histórico do ChatMobileNav abaixo de xl. O widget
38
+ * flutuante desliga, porque tem o próprio drawer dentro do painel.
39
+ */
40
+ showMobileNav?: boolean
41
+ }
42
+
43
+ const ChatView = ({ showMobileNav = true }: ChatViewProps) => {
35
44
  const scrollRef = useRef<ScrollBarRef>(null)
36
45
  const { selectedConversation, chatHistory, isTyping, disabledChatFresh } =
37
46
  usGenerativeChatStore()
38
47
  const { handleSend, handleApproval, handleDataSubmit } = useChatSend()
48
+ // Conversa aberta da lista: as mensagens vêm do backend na primeira vez.
49
+ const { carregando } = useConversationMessages()
39
50
 
40
51
  const scrollToBottom = () => {
41
52
  if (scrollRef.current?.getScrollElement) {
@@ -73,7 +84,7 @@ const ChatView = () => {
73
84
 
74
85
  return (
75
86
  <Card className="flex-1 h-full" bodyClass="h-full">
76
- <ChatMobileNav />
87
+ {showMobileNav && <ChatMobileNav />}
77
88
  <ChatBox
78
89
  ref={scrollRef}
79
90
  messageList={messageList}
@@ -89,10 +100,12 @@ const ChatView = () => {
89
100
  <AiThinkingIndicator />
90
101
  </div>
91
102
  )}
92
- containerClass="h-[calc(100%-30px)] xl:h-full"
103
+ containerClass={
104
+ showMobileNav ? 'h-[calc(100%-30px)] xl:h-full' : 'h-full'
105
+ }
93
106
  messageListClass="h-[calc(100%-100px)] xl:h-[calc(100%-70px)]"
94
107
  typing={
95
- isTyping
108
+ isTyping || carregando
96
109
  ? {
97
110
  id: 'ai',
98
111
  name: 'Chat AI',
@@ -0,0 +1,128 @@
1
+ import useSWR from 'swr'
2
+ import Notification from '@/components/ui/Notification'
3
+ import toast from '@/components/ui/toast'
4
+ import {
5
+ apiDeleteConversation,
6
+ apiGetConversations,
7
+ apiRenameConversation,
8
+ } from '@/services/AiService'
9
+ import { useSessionUser } from '@/store/authStore'
10
+ import { usGenerativeChatStore } from '../store/generativeChatStore'
11
+ import { resumoParaHistorico } from '../utils'
12
+ import type { ChatHistories, GetConversationsResponse } from '../types'
13
+
14
+ const avisarFalha = (mensagem: string) => {
15
+ toast.push(
16
+ <Notification type="danger" duration={3000}>
17
+ {mensagem}
18
+ </Notification>,
19
+ { placement: 'top-center' },
20
+ )
21
+ }
22
+
23
+ /**
24
+ * A lista de conversas do usuário, persistida no backend, e as ações sobre ela.
25
+ *
26
+ * Renomear e excluir são otimistas: a tela muda na hora e volta ao estado
27
+ * anterior se o backend recusar.
28
+ */
29
+ const useChatConversations = () => {
30
+ const {
31
+ setChatHistory,
32
+ setChatHistoryName,
33
+ setSelectedConversation,
34
+ } = usGenerativeChatStore()
35
+
36
+ const userId = useSessionUser((state) => state.user.userId)
37
+ const signedIn = useSessionUser((state) => state.session.signedIn)
38
+
39
+ // O usuário faz parte da chave: cada um tem o próprio cache, e o de quem
40
+ // saiu nunca é servido a quem entrou. Sem sessão, não busca nada.
41
+ //
42
+ // Sem `revalidateIfStale: false` de propósito: cada vez que a lista monta,
43
+ // ela confere o backend. Com ele, o SWR servia o cache antigo, não chamava
44
+ // o `onSuccess`, e a lista só aparecia depois de um F5.
45
+ useSWR(
46
+ signedIn && userId ? ['/ai/chat/conversations', userId] : null,
47
+ // O dono viaja junto com a resposta para o `onSuccess` conferir.
48
+ async ([, dono]: [string, string]) => ({
49
+ dono,
50
+ resumos: await apiGetConversations<GetConversationsResponse>(),
51
+ }),
52
+ {
53
+ revalidateOnFocus: false,
54
+ revalidateOnReconnect: false,
55
+ onSuccess: ({ dono, resumos }) => {
56
+ // Resposta atrasada de quem já saiu: descarta, senão a lista
57
+ // de um usuário cairia na tela do outro.
58
+ if (dono !== useSessionUser.getState().user.userId) {
59
+ return
60
+ }
61
+ // Lido na hora, e não da closure: a resposta pode chegar
62
+ // depois de o usuário já ter começado uma conversa.
63
+ const atual = usGenerativeChatStore.getState().chatHistory
64
+ setChatHistory(mesclar(atual, resumos))
65
+ },
66
+ },
67
+ )
68
+
69
+ const remover = async (id: string) => {
70
+ const { chatHistory, selectedConversation } =
71
+ usGenerativeChatStore.getState()
72
+
73
+ setChatHistory(chatHistory.filter((item) => item.id !== id))
74
+ if (selectedConversation === id) {
75
+ setSelectedConversation('')
76
+ }
77
+
78
+ try {
79
+ await apiDeleteConversation(id)
80
+ } catch {
81
+ setChatHistory(chatHistory)
82
+ avisarFalha('Não foi possível excluir a conversa.')
83
+ }
84
+ }
85
+
86
+ const renomear = async (id: string, title: string) => {
87
+ const anterior = usGenerativeChatStore
88
+ .getState()
89
+ .chatHistory.find((item) => item.id === id)?.title
90
+
91
+ setChatHistoryName({ id, title })
92
+
93
+ try {
94
+ await apiRenameConversation(id, title)
95
+ } catch {
96
+ if (anterior !== undefined) {
97
+ setChatHistoryName({ id, title: anterior })
98
+ }
99
+ avisarFalha('Não foi possível renomear a conversa.')
100
+ }
101
+ }
102
+
103
+ return { remover, renomear }
104
+ }
105
+
106
+ /**
107
+ * Junta a lista do backend com o que já está na tela.
108
+ *
109
+ * - Conversa que já existe no store fica como está: pode ter mensagens
110
+ * carregadas ou um stream em andamento.
111
+ * - Conversa criada nesta sessão e que o backend ainda não conhece não pode
112
+ * sumir: ela aparece primeiro, como ao ser criada.
113
+ */
114
+ function mesclar(
115
+ atual: ChatHistories,
116
+ resumos: GetConversationsResponse,
117
+ ): ChatHistories {
118
+ const noStore = new Map(atual.map((chat) => [chat.id, chat]))
119
+ const doBackend = new Set(resumos.map((resumo) => resumo.id))
120
+
121
+ const soNaTela = atual.filter((chat) => !doBackend.has(chat.id))
122
+ const daLista = resumos.map(
123
+ (resumo) => noStore.get(resumo.id) ?? resumoParaHistorico(resumo),
124
+ )
125
+ return [...soNaTela, ...daLista]
126
+ }
127
+
128
+ export default useChatConversations
@@ -3,6 +3,7 @@ import { resumeChat, streamChat } from '@/services/AiService'
3
3
  import { useAuth } from '@/auth'
4
4
  import dayjs from 'dayjs'
5
5
  import uniqueId from 'lodash/uniqueId'
6
+ import { novoIdDeConversa } from '../utils'
6
7
  import type { ChatEvent, Conversation, ToolCall } from '../types'
7
8
 
8
9
  /** Resposta genérica exibida no chat quando a comunicação com a IA falha. */
@@ -203,14 +204,14 @@ const useChatSend = () => {
203
204
  creteMyMessage(selectedConversation, prompt)
204
205
  await sendMessage(selectedConversation, prompt)
205
206
  } else {
206
- const newId = uniqueId('ai-chat-')
207
+ const newId = novoIdDeConversa()
207
208
  pushChatHistory({
208
209
  id: newId,
209
210
  title: prompt,
210
211
  lastConversation: '',
211
212
  createdTime: dayjs().unix(),
212
213
  updatedTime: dayjs().unix(),
213
- enable: false,
214
+ enable: true,
214
215
  })
215
216
  creteMyMessage(newId, prompt)
216
217
  await createConversation(newId, prompt)
@@ -0,0 +1,68 @@
1
+ import { useEffect, useState } from 'react'
2
+ import { apiGetChatHistory } from '@/services/AiService'
3
+ import { usGenerativeChatStore } from '../store/generativeChatStore'
4
+ import { mensagemDoHistorico } from '../utils'
5
+ import type { Conversation, HistoryChat } from '../types'
6
+
7
+ /**
8
+ * Busca as mensagens da conversa selecionada quando ela veio da lista do
9
+ * backend e ainda não foi aberta nesta sessão.
10
+ *
11
+ * Busca uma vez por conversa: depois disso `loaded` fica true e as mensagens
12
+ * novas chegam pelo streaming, como em qualquer conversa.
13
+ */
14
+ const useConversationMessages = () => {
15
+ const { selectedConversation, chatHistory, setConversationMessages } =
16
+ usGenerativeChatStore()
17
+ const [carregando, setCarregando] = useState(false)
18
+
19
+ const pendente =
20
+ chatHistory.find((chat) => chat.id === selectedConversation)?.loaded ===
21
+ false
22
+
23
+ useEffect(() => {
24
+ if (!selectedConversation || !pendente) {
25
+ return
26
+ }
27
+
28
+ // Trocar de conversa no meio da busca não pode gravar a resposta
29
+ // atrasada na tela errada.
30
+ let cancelado = false
31
+ setCarregando(true)
32
+
33
+ // O usuário pode ter enviado algo enquanto a busca corria: o que já
34
+ // está na tela vem depois do histórico, e não some.
35
+ const gravar = (antigas: Conversation[]) => {
36
+ const naTela =
37
+ usGenerativeChatStore
38
+ .getState()
39
+ .chatHistory.find((chat) => chat.id === selectedConversation)
40
+ ?.conversation ?? []
41
+ setConversationMessages(selectedConversation, [...antigas, ...naTela])
42
+ }
43
+
44
+ apiGetChatHistory<HistoryChat[]>(selectedConversation)
45
+ .then((resposta) => {
46
+ if (cancelado) return
47
+ gravar((resposta[0]?.conversation ?? []).map(mensagemDoHistorico))
48
+ })
49
+ .catch(() => {
50
+ // Marca como carregada mesmo assim: sem isto, cada render
51
+ // tentaria de novo. A conversa segue utilizável, porque a
52
+ // memória está no backend.
53
+ if (!cancelado) gravar([])
54
+ })
55
+ .finally(() => {
56
+ if (!cancelado) setCarregando(false)
57
+ })
58
+
59
+ return () => {
60
+ cancelado = true
61
+ setCarregando(false)
62
+ }
63
+ }, [selectedConversation, pendente, setConversationMessages])
64
+
65
+ return { carregando }
66
+ }
67
+
68
+ export default useConversationMessages
@@ -1,4 +1,5 @@
1
1
  import { create } from 'zustand'
2
+ import { useSessionUser } from '@/store/authStore'
2
3
  import type { ChatHistories, ChatHistory, Conversation } from '../types'
3
4
 
4
5
  type RenameDialog = {
@@ -24,12 +25,16 @@ type GenerativeChatAction = {
24
25
  setIsTyping: (payload: boolean) => void
25
26
  pushChatHistory: (payload: ChatHistory) => void
26
27
  pushConversation: (id: string, conversation: Conversation) => void
28
+ /** Grava as mensagens buscadas no backend e marca a conversa como carregada. */
29
+ setConversationMessages: (id: string, conversation: Conversation[]) => void
27
30
  updateConversation: (
28
31
  chatId: string,
29
32
  messageId: string,
30
33
  patch: Partial<Conversation>,
31
34
  ) => void
32
35
  disabledChatFresh: (id: string) => void
36
+ /** Volta ao estado inicial. Chamado sozinho quando o usuário da sessão muda. */
37
+ reset: () => void
33
38
  }
34
39
 
35
40
  const initialState: GenerativeChatState = {
@@ -48,6 +53,14 @@ export const usGenerativeChatStore = create<
48
53
  GenerativeChatState & GenerativeChatAction
49
54
  >((set, get) => ({
50
55
  ...initialState,
56
+ // Arrays novos: `setSelectedConversationRecord` muta o array no lugar, e
57
+ // reusar o do initialState traria de volta o que ele acumulou.
58
+ reset: () =>
59
+ set(() => ({
60
+ ...initialState,
61
+ selectedConversationRecord: [],
62
+ chatHistory: [],
63
+ })),
51
64
  setSelectedConversation: (payload) =>
52
65
  set(() => ({ selectedConversation: payload })),
53
66
  setChatHistory: (payload) => set(() => ({ chatHistory: payload })),
@@ -74,8 +87,18 @@ export const usGenerativeChatStore = create<
74
87
 
75
88
  return { selectedConversationRecord: record }
76
89
  }),
90
+ // Conversa criada nesta sessão já nasce carregada: as mensagens estão aqui,
91
+ // não há o que buscar no backend.
77
92
  pushChatHistory: (payload) =>
78
- set(() => ({ chatHistory: [payload, ...get().chatHistory] })),
93
+ set(() => ({
94
+ chatHistory: [{ loaded: true, ...payload }, ...get().chatHistory],
95
+ })),
96
+ setConversationMessages: (id, conversation) =>
97
+ set(() => ({
98
+ chatHistory: get().chatHistory.map((chat) =>
99
+ chat.id === id ? { ...chat, conversation, loaded: true } : chat,
100
+ ),
101
+ })),
79
102
  pushConversation: (id, conversation) =>
80
103
  set(() => ({
81
104
  chatHistory: get().chatHistory.map((chat) =>
@@ -131,3 +154,23 @@ export const usGenerativeChatStore = create<
131
154
  return { chatHistory }
132
155
  }),
133
156
  }))
157
+
158
+ /**
159
+ * O chat pertence ao usuário da sessão: trocou o usuário, zera o chat.
160
+ *
161
+ * Login e logout acontecem sem recarregar a página, e este store é um singleton
162
+ * do módulo. Sem isto, o próximo usuário via as conversas do anterior.
163
+ *
164
+ * Olha o `signedIn` além do `userId` porque o logout faz `setUser({})`, e o
165
+ * `setUser` faz MERGE: o `userId` antigo continua lá depois de sair.
166
+ *
167
+ * É uma assinatura, e não um efeito de componente, para zerar ANTES do próximo
168
+ * render: a lista antiga nem chega a piscar na tela.
169
+ */
170
+ useSessionUser.subscribe((atual, anterior) => {
171
+ const trocouUsuario = atual.user.userId !== anterior.user.userId
172
+ const saiu = anterior.session.signedIn && !atual.session.signedIn
173
+ if (trocouUsuario || saiu) {
174
+ usGenerativeChatStore.getState().reset()
175
+ }
176
+ })
@@ -131,8 +131,36 @@ export type ChatHistory = {
131
131
  createdTime: number
132
132
  updatedTime: number
133
133
  enable: boolean
134
+ /**
135
+ * false quando a conversa veio da lista do backend e as mensagens ainda
136
+ * não foram buscadas. Conversas criadas nesta sessão já nascem carregadas.
137
+ */
138
+ loaded?: boolean
134
139
  }
135
140
 
136
141
  export type ChatHistories = ChatHistory[]
137
142
 
138
143
  export type GetChatHistoryResponse = ChatHistories
144
+
145
+ /**
146
+ * Mensagem como o backend devolve no /ai/chat/history: o `timestamp` vem em
147
+ * segundos (epoch), e não como Date.
148
+ */
149
+ export type HistoryMessage = Omit<Conversation, 'timestamp'> & {
150
+ timestamp: number
151
+ }
152
+
153
+ /** Uma conversa do /ai/chat/history, com as mensagens no formato do backend. */
154
+ export type HistoryChat = Omit<ChatHistory, 'conversation'> & {
155
+ conversation: HistoryMessage[]
156
+ }
157
+
158
+ /** Resumo de uma conversa, como vem do GET /ai/chat/conversations. */
159
+ export type ConversationSummary = {
160
+ id: string
161
+ title: string
162
+ createdTime: number
163
+ updatedTime: number
164
+ }
165
+
166
+ export type GetConversationsResponse = ConversationSummary[]
@@ -1,3 +1,36 @@
1
+ import dayjs from 'dayjs'
2
+ import type {
3
+ ChatHistory,
4
+ Conversation,
5
+ ConversationSummary,
6
+ HistoryMessage,
7
+ } from './types'
8
+
9
+ /**
10
+ * Id de uma conversa nova.
11
+ *
12
+ * Precisa ser único entre recargas: o backend guarda a memória por este id.
13
+ * Um contador por página (como o `uniqueId` do lodash) recomeça no F5, e o
14
+ * "novo chat" herdaria a memória de uma conversa antiga.
15
+ */
16
+ export const novoIdDeConversa = (): string => crypto.randomUUID()
17
+
18
+ /** Conversa da lista do backend, ainda sem as mensagens. */
19
+ export const resumoParaHistorico = (
20
+ resumo: ConversationSummary,
21
+ ): ChatHistory => ({
22
+ ...resumo,
23
+ lastConversation: '',
24
+ enable: true,
25
+ loaded: false,
26
+ })
27
+
28
+ /** Mensagem do /ai/chat/history no formato do chat (timestamp em Date). */
29
+ export const mensagemDoHistorico = (mensagem: HistoryMessage): Conversation => ({
30
+ ...mensagem,
31
+ timestamp: dayjs.unix(mensagem.timestamp).toDate(),
32
+ })
33
+
1
34
  export const getStringPosition = (
2
35
  string: string,
3
36
  subString: string,
@@ -17,6 +17,8 @@ const contratoFrete = {
17
17
  enviarSap: {endpoint: `${CONTRATO_FRETE_URL}/enviar-sap/`, method: 'get'},
18
18
  uploadArquivo: {endpoint: `${CONTRATO_FRETE_URL}/upload-arquivo`, method: 'post'},
19
19
  findDocumento: {endpoint: `${CONTRATO_FRETE_URL}/find/documento/`, method: 'get'},
20
+ updateStatusPedagio: {endpoint: `${CONTRATO_FRETE_URL}/update/status-pedagio`, method: 'put'},
21
+ pagamentoPedagio: {endpoint: `${CONTRATO_FRETE_URL}/pagamento-pedagio`, method: 'post'},
20
22
  }
21
23
  export default contratoFrete
22
24
 
@@ -126,4 +126,18 @@ export async function apiFindDocumentoViagem(documento: string, tipo: string) {
126
126
  url: `${CONTRATO_FRETE_PROJECT_ENDPOINT.findDocumento.endpoint}${documento}/${tipo}`,
127
127
  method: CONTRATO_FRETE_PROJECT_ENDPOINT.findDocumento.method,
128
128
  })
129
+ }
130
+
131
+ export async function apiUpdateStatusPedagio(id: number, statusPedagio: string) {
132
+ return ApiService.fetchDataWithAxios<TContratoFrete>({
133
+ url: `${CONTRATO_FRETE_PROJECT_ENDPOINT.updateStatusPedagio.endpoint}/${id}/${statusPedagio}`,
134
+ method: CONTRATO_FRETE_PROJECT_ENDPOINT.updateStatusPedagio.method,
135
+ })
136
+ }
137
+
138
+ export async function apiPagamentoPedagio(id: number) {
139
+ return ApiService.fetchDataWithAxios<TContratoFrete>({
140
+ url: `${CONTRATO_FRETE_PROJECT_ENDPOINT.pagamentoPedagio.endpoint}/${id}`,
141
+ method: CONTRATO_FRETE_PROJECT_ENDPOINT.pagamentoPedagio.method,
142
+ })
129
143
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orbe-agro/client-core",
3
- "version": "5.6.259",
3
+ "version": "5.6.261",
4
4
  "description": "Biblioteca principal de componentes e utilidades para os microfrontends do Orbe Agro.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",