@orbe-agro/client-core 5.6.238 → 5.6.240

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 (96) hide show
  1. package/dist/@ecme/assets/styles/app.css +116 -3
  2. package/dist/@ecme/assets/styles/tailwind/index.css +119 -3
  3. package/dist/@ecme/components/template/Footer.js +1 -1
  4. package/dist/@ecme/components/template/OrbeAiWidget/OrbeAiWidget.js +0 -2
  5. package/dist/@ecme/components/template/OrbeAiWidget/OrbeAiWidget.js.map +1 -1
  6. package/dist/@ecme/components/view/ChatBox/ChatBox.js +2 -0
  7. package/dist/@ecme/components/view/ChatBox/ChatBox.js.map +1 -1
  8. package/dist/@ecme/components/view/ChatBox/components/MessageList.js +2 -1
  9. package/dist/@ecme/components/view/ChatBox/components/MessageList.js.map +1 -1
  10. package/dist/@ecme/index.js +3 -4
  11. package/dist/@ecme/services/AiService.js +53 -24
  12. package/dist/@ecme/services/AiService.js.map +1 -1
  13. package/dist/@ecme/services/aiAuthHeaders.js +35 -0
  14. package/dist/@ecme/services/aiAuthHeaders.js.map +1 -0
  15. package/dist/@ecme/services/axios/AxiosRequestIntrceptorConfigCallback.js +4 -27
  16. package/dist/@ecme/services/axios/AxiosRequestIntrceptorConfigCallback.js.map +1 -1
  17. package/dist/@ecme/services/index.js +4 -5
  18. package/dist/@ecme/services/sseParser.js +42 -0
  19. package/dist/@ecme/services/sseParser.js.map +1 -0
  20. package/dist/@ecme/views/concepts/ai/Chat/components/AiErrorMessage.js +15 -0
  21. package/dist/@ecme/views/concepts/ai/Chat/components/AiErrorMessage.js.map +1 -0
  22. package/dist/@ecme/views/concepts/ai/Chat/components/ApprovalCard.js +73 -0
  23. package/dist/@ecme/views/concepts/ai/Chat/components/ApprovalCard.js.map +1 -0
  24. package/dist/@ecme/views/concepts/ai/Chat/components/ChatView.js +37 -10
  25. package/dist/@ecme/views/concepts/ai/Chat/components/ChatView.js.map +1 -1
  26. package/dist/@ecme/views/concepts/ai/Chat/components/ToolCallChip.js +153 -0
  27. package/dist/@ecme/views/concepts/ai/Chat/components/ToolCallChip.js.map +1 -0
  28. package/dist/@ecme/views/concepts/ai/Chat/components/toolPresentation.js +99 -0
  29. package/dist/@ecme/views/concepts/ai/Chat/components/toolPresentation.js.map +1 -0
  30. package/dist/@ecme/views/concepts/ai/Chat/hooks/useChatSend.js +111 -17
  31. package/dist/@ecme/views/concepts/ai/Chat/hooks/useChatSend.js.map +1 -1
  32. package/dist/@ecme/views/concepts/ai/Chat/store/generativeChatStore.js +29 -13
  33. package/dist/@ecme/views/concepts/ai/Chat/store/generativeChatStore.js.map +1 -1
  34. package/dist/@types/@ecme/components/template/OrbeAiWidget/OrbeAiWidget.d.ts.map +1 -1
  35. package/dist/@types/@ecme/components/view/ChatBox/ChatBox.d.ts.map +1 -1
  36. package/dist/@types/@ecme/components/view/ChatBox/components/MessageList.d.ts +2 -0
  37. package/dist/@types/@ecme/components/view/ChatBox/components/MessageList.d.ts.map +1 -1
  38. package/dist/@types/@ecme/services/AiService.d.ts +28 -15
  39. package/dist/@types/@ecme/services/AiService.d.ts.map +1 -1
  40. package/dist/@types/@ecme/services/aiAuthHeaders.d.ts +12 -0
  41. package/dist/@types/@ecme/services/aiAuthHeaders.d.ts.map +1 -0
  42. package/dist/@types/@ecme/services/axios/AxiosRequestIntrceptorConfigCallback.d.ts +6 -0
  43. package/dist/@types/@ecme/services/axios/AxiosRequestIntrceptorConfigCallback.d.ts.map +1 -1
  44. package/dist/@types/@ecme/services/sseParser.d.ts +20 -0
  45. package/dist/@types/@ecme/services/sseParser.d.ts.map +1 -0
  46. package/dist/@types/@ecme/views/concepts/ai/Chat/components/AiErrorMessage.d.ts +8 -0
  47. package/dist/@types/@ecme/views/concepts/ai/Chat/components/AiErrorMessage.d.ts.map +1 -0
  48. package/dist/@types/@ecme/views/concepts/ai/Chat/components/ApprovalCard.d.ts +15 -0
  49. package/dist/@types/@ecme/views/concepts/ai/Chat/components/ApprovalCard.d.ts.map +1 -0
  50. package/dist/@types/@ecme/views/concepts/ai/Chat/components/ChatView.d.ts.map +1 -1
  51. package/dist/@types/@ecme/views/concepts/ai/Chat/components/ToolCallChip.d.ts +7 -0
  52. package/dist/@types/@ecme/views/concepts/ai/Chat/components/ToolCallChip.d.ts.map +1 -0
  53. package/dist/@types/@ecme/views/concepts/ai/Chat/components/toolPresentation.d.ts +14 -0
  54. package/dist/@types/@ecme/views/concepts/ai/Chat/components/toolPresentation.d.ts.map +1 -0
  55. package/dist/@types/@ecme/views/concepts/ai/Chat/hooks/useChatSend.d.ts +2 -1
  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/store/generativeChatStore.d.ts +1 -0
  58. package/dist/@types/@ecme/views/concepts/ai/Chat/store/generativeChatStore.d.ts.map +1 -1
  59. package/dist/@types/@ecme/views/concepts/ai/Chat/types.d.ts +96 -23
  60. package/dist/@types/@ecme/views/concepts/ai/Chat/types.d.ts.map +1 -1
  61. package/dist/@types/base/configs/endpoints.config/HubFiscal/nfe.endpoint.config.d.ts +1 -1
  62. package/dist/@types/base/services/modules/HubFiscal/NfeService.d.ts +1 -1
  63. package/dist/@types/base/services/modules/HubFiscal/NfeService.d.ts.map +1 -1
  64. package/dist/base/assets/styles/init.css +128 -3
  65. package/dist/base/configs/endpoints.config/HubFiscal/nfe.endpoint.config.js +1 -1
  66. package/dist/base/configs/endpoints.config/HubFiscal/nfe.endpoint.config.js.map +1 -1
  67. package/dist/base/services/modules/HubFiscal/NfeService.js +3 -3
  68. package/dist/base/services/modules/HubFiscal/NfeService.js.map +1 -1
  69. package/lib/@ecme/components/template/OrbeAiWidget/OrbeAiWidget.tsx +0 -2
  70. package/lib/@ecme/components/view/ChatBox/ChatBox.tsx +2 -0
  71. package/lib/@ecme/components/view/ChatBox/components/MessageList.tsx +8 -4
  72. package/lib/@ecme/services/AiService.ts +89 -44
  73. package/lib/@ecme/services/aiAuthHeaders.ts +58 -0
  74. package/lib/@ecme/services/axios/AxiosRequestIntrceptorConfigCallback.ts +10 -39
  75. package/lib/@ecme/services/sseParser.ts +75 -0
  76. package/lib/@ecme/views/concepts/ai/Chat/components/AiErrorMessage.tsx +28 -0
  77. package/lib/@ecme/views/concepts/ai/Chat/components/ApprovalCard.tsx +103 -0
  78. package/lib/@ecme/views/concepts/ai/Chat/components/ChatView.tsx +74 -10
  79. package/lib/@ecme/views/concepts/ai/Chat/components/ToolCallChip.tsx +218 -0
  80. package/lib/@ecme/views/concepts/ai/Chat/components/toolPresentation.ts +137 -0
  81. package/lib/@ecme/views/concepts/ai/Chat/hooks/useChatSend.ts +175 -23
  82. package/lib/@ecme/views/concepts/ai/Chat/store/generativeChatStore.ts +41 -13
  83. package/lib/@ecme/views/concepts/ai/Chat/types.ts +82 -29
  84. package/lib/base/configs/endpoints.config/HubFiscal/nfe.endpoint.config.ts +1 -1
  85. package/lib/base/services/modules/HubFiscal/NfeService.ts +2 -2
  86. package/package.json +1 -1
  87. package/dist/@ecme/views/concepts/ai/Chat/components/ModelSelect.js +0 -48
  88. package/dist/@ecme/views/concepts/ai/Chat/components/ModelSelect.js.map +0 -1
  89. package/dist/@ecme/views/concepts/ai/Chat/store/aiModelStore.js +0 -15
  90. package/dist/@ecme/views/concepts/ai/Chat/store/aiModelStore.js.map +0 -1
  91. package/dist/@types/@ecme/views/concepts/ai/Chat/components/ModelSelect.d.ts +0 -8
  92. package/dist/@types/@ecme/views/concepts/ai/Chat/components/ModelSelect.d.ts.map +0 -1
  93. package/dist/@types/@ecme/views/concepts/ai/Chat/store/aiModelStore.d.ts +0 -25
  94. package/dist/@types/@ecme/views/concepts/ai/Chat/store/aiModelStore.d.ts.map +0 -1
  95. package/lib/@ecme/views/concepts/ai/Chat/components/ModelSelect.tsx +0 -66
  96. package/lib/@ecme/views/concepts/ai/Chat/store/aiModelStore.ts +0 -23
@@ -0,0 +1,137 @@
1
+ import { formatQuantity } from '@base/utils/functions/stringUtils'
2
+ import { TbFilePlus, TbListSearch, TbSearch, TbTool } from 'react-icons/tb'
3
+ import type { IconType } from 'react-icons'
4
+
5
+ /**
6
+ * Como as ferramentas do agente sao apresentadas ao usuario.
7
+ *
8
+ * Compartilhado pelo chip de execucao e pelo card de aprovacao, para os dois
9
+ * falarem a mesma lingua: o usuario ve "Consulta de contratos" e "Mercado
10
+ * interno", nunca `listar_contratos_armazenagem_graos` ou `MERCADO_INTERNO`.
11
+ *
12
+ * Tudo tem fallback: uma ferramenta ou campo novo no backend nao quebra a tela,
13
+ * so aparece com o nome tecnico humanizado ate ganhar entrada aqui.
14
+ */
15
+
16
+ type Ferramenta = {
17
+ rotulo: string
18
+ /** Frase no gerundio, para o estado "executando". */
19
+ executando: string
20
+ icone: IconType
21
+ }
22
+
23
+ const FERRAMENTAS: Record<string, Ferramenta> = {
24
+ buscar_contrato_armazenagem_graos: {
25
+ rotulo: 'Consulta de contrato',
26
+ executando: 'Consultando contrato',
27
+ icone: TbSearch,
28
+ },
29
+ listar_contratos_armazenagem_graos: {
30
+ rotulo: 'Consulta de contratos',
31
+ executando: 'Consultando contratos',
32
+ icone: TbListSearch,
33
+ },
34
+ criar_contrato_armazenagem_graos: {
35
+ rotulo: 'Criação de contrato de armazenagem',
36
+ executando: 'Criando contrato',
37
+ icone: TbFilePlus,
38
+ },
39
+ }
40
+
41
+ const humanizar = (texto: string) =>
42
+ texto
43
+ .replaceAll('_', ' ')
44
+ .toLowerCase()
45
+ .replace(/^./, (letra) => letra.toUpperCase())
46
+
47
+ export const rotularFerramenta = (nome: string) =>
48
+ FERRAMENTAS[nome]?.rotulo ?? humanizar(nome)
49
+
50
+ export const rotularExecucao = (nome: string) =>
51
+ FERRAMENTAS[nome]?.executando ?? humanizar(nome)
52
+
53
+ export const iconeFerramenta = (nome: string): IconType =>
54
+ FERRAMENTAS[nome]?.icone ?? TbTool
55
+
56
+ /** Rotulos dos parametros, na linguagem do negocio. */
57
+ const CAMPOS: Record<string, string> = {
58
+ business_partner_id: 'Parceiro de negócio',
59
+ produto_id: 'Produto',
60
+ safra_id: 'Safra',
61
+ saldo_negociado: 'Saldo negociado',
62
+ tipo_contrato: 'Tipo de contrato',
63
+ contrato_venda: 'Contrato de venda',
64
+ ordem_venda: 'Ordem de venda',
65
+ id: 'Contrato',
66
+ page_number: 'Página',
67
+ page_size: 'Itens por página',
68
+ sort_field: 'Ordenar por',
69
+ sort_order: 'Ordem',
70
+ filtro: 'Filtro',
71
+ }
72
+
73
+ export const rotularCampo = (chave: string) => CAMPOS[chave] ?? humanizar(chave)
74
+
75
+ /** Campos que sao referencia a um registro: exibidos como "#123". */
76
+ const REFERENCIAS = new Set([
77
+ 'business_partner_id',
78
+ 'produto_id',
79
+ 'safra_id',
80
+ 'contrato_venda',
81
+ 'ordem_venda',
82
+ 'id',
83
+ ])
84
+
85
+ const ORDENACAO: Record<string, string> = { ASC: 'Crescente', DESC: 'Decrescente' }
86
+
87
+ /** Converte o valor tecnico no que o usuario espera ler. */
88
+ export const formatarValor = (chave: string, valor: unknown): string => {
89
+ if (valor === null || valor === undefined || valor === '') {
90
+ return '—'
91
+ }
92
+ if (chave === 'saldo_negociado' && typeof valor === 'number') {
93
+ return formatQuantity(valor)
94
+ }
95
+ if (chave === 'sort_order' && typeof valor === 'string') {
96
+ return ORDENACAO[valor] ?? humanizar(valor)
97
+ }
98
+ if (typeof valor === 'number') {
99
+ return REFERENCIAS.has(chave) ? `#${valor}` : String(valor)
100
+ }
101
+ if (typeof valor === 'boolean') {
102
+ return valor ? 'Sim' : 'Não'
103
+ }
104
+ if (typeof valor === 'string') {
105
+ // Enums do backend chegam em CAIXA_ALTA_COM_UNDERSCORE.
106
+ return /^[A-Z][A-Z0-9_]+$/.test(valor) ? humanizar(valor) : valor
107
+ }
108
+ if (Array.isArray(valor)) {
109
+ return valor.length ? `${valor.length} item(ns)` : '—'
110
+ }
111
+ if (typeof valor === 'object') {
112
+ const entradas = Object.entries(valor as Record<string, unknown>)
113
+ if (!entradas.length) {
114
+ return '—'
115
+ }
116
+ return entradas
117
+ .map(([k, v]) => `${rotularCampo(k)}: ${formatarValor(k, v)}`)
118
+ .join(', ')
119
+ }
120
+ return String(valor)
121
+ }
122
+
123
+ /** Pares (rotulo, valor) prontos para exibir, ignorando o que esta vazio. */
124
+ export const camposVisiveis = (args: Record<string, unknown>) =>
125
+ Object.entries(args)
126
+ .filter(([, valor]) => {
127
+ if (valor === null || valor === undefined || valor === '') return false
128
+ if (typeof valor === 'object' && !Array.isArray(valor)) {
129
+ return Object.keys(valor as object).length > 0
130
+ }
131
+ return true
132
+ })
133
+ .map(([chave, valor]) => ({
134
+ chave,
135
+ rotulo: rotularCampo(chave),
136
+ valor: formatarValor(chave, valor),
137
+ }))
@@ -1,9 +1,13 @@
1
1
  import { usGenerativeChatStore } from '../store/generativeChatStore'
2
- import { apiPostChat } from '@/services/AiService'
2
+ 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 type { PostAiChatResponse } from '../types'
6
+ import type { ChatEvent, Conversation, ToolCall } from '../types'
7
+
8
+ /** Resposta genérica exibida no chat quando a comunicação com a IA falha. */
9
+ const AI_ERROR_MESSAGE =
10
+ 'Desculpe, estou com um pequeno problema de comunicação e não consigo te atender no momento. Tente novamente em instantes.'
7
11
 
8
12
  const useChatSend = () => {
9
13
  const { user } = useAuth()
@@ -12,6 +16,7 @@ const useChatSend = () => {
12
16
  setSelectedConversation,
13
17
  pushChatHistory,
14
18
  pushConversation,
19
+ updateConversation,
15
20
  setIsTyping,
16
21
  } = usGenerativeChatStore()
17
22
 
@@ -30,41 +35,153 @@ const useChatSend = () => {
30
35
  })
31
36
  }
32
37
 
33
- const sendMessage = async (
34
- id: string,
35
- prompt: string,
36
- attachments?: File[],
38
+ /**
39
+ * Cria a mensagem da IA vazia e devolve o id, para o streaming preenchê-la.
40
+ *
41
+ * `fresh: false` é obrigatório: `fresh` liga a animação de máquina de
42
+ * escrever do ChatCustomContent, que reinicia do zero a cada mudança de
43
+ * conteúdo — com streaming, o texto ficaria piscando e voltando ao início.
44
+ * A animação agora vem do servidor.
45
+ */
46
+ const createAiMessage = (
47
+ chatId: string,
48
+ options?: Partial<Conversation>,
37
49
  ) => {
38
- const resp = await apiPostChat<PostAiChatResponse>({
39
- prompt,
40
- attachments,
41
- })
42
- pushConversation(id, {
43
- id: uniqueId('ai-conversation-'),
50
+ const messageId = uniqueId('ai-conversation-')
51
+ pushConversation(chatId, {
52
+ id: messageId,
44
53
  sender: {
45
54
  id: 'ai',
46
55
  name: 'Chat AI',
47
56
  avatarImageUrl: '/img/thumbs/ai.jpg',
48
57
  },
49
- content: resp.choices[0].message.content || '',
58
+ content: '',
50
59
  timestamp: dayjs().toDate(),
51
60
  type: 'regular',
52
61
  isMyMessage: false,
53
- fresh: true,
62
+ fresh: false,
63
+ ...options,
54
64
  })
55
- setIsTyping(false)
65
+ return messageId
56
66
  }
57
67
 
58
- const createConversation = async (
59
- id: string,
60
- prompt: string,
61
- attachments?: File[],
68
+ /**
69
+ * Consome um stream escrevendo numa mensagem que já existe.
70
+ *
71
+ * Compartilhado pelo envio e pela retomada de aprovação: quando o usuário
72
+ * aprova, a resposta continua na MESMA mensagem, então o acumulador de
73
+ * texto e de ferramentas precisa partir do que já está lá.
74
+ */
75
+ const consumirStream = async (
76
+ chatId: string,
77
+ messageId: string,
78
+ executar: (onEvent: (evento: ChatEvent) => void) => Promise<void>,
79
+ inicial: { texto: string; tools: ToolCall[] },
62
80
  ) => {
63
- setSelectedConversation(id)
64
- await sendMessage(id, prompt, attachments)
81
+ // Acumulados localmente: o store guarda o resultado, mas ler de volta a
82
+ // cada token para concatenar seria uma corrida com o próprio set().
83
+ let texto = inicial.texto
84
+ let tools = inicial.tools
85
+
86
+ const aplicar = (evento: ChatEvent) => {
87
+ switch (evento.type) {
88
+ case 'token':
89
+ texto += evento.data.content
90
+ updateConversation(chatId, messageId, { content: texto })
91
+ break
92
+
93
+ case 'tool_start':
94
+ tools = [
95
+ ...tools,
96
+ {
97
+ id: evento.data.id,
98
+ name: evento.data.name,
99
+ args: evento.data.args,
100
+ running: true,
101
+ },
102
+ ]
103
+ updateConversation(chatId, messageId, { tools })
104
+ break
105
+
106
+ case 'tool_end':
107
+ tools = tools.map((tool) =>
108
+ tool.id === evento.data.id
109
+ ? {
110
+ ...tool,
111
+ running: false,
112
+ awaitingApproval: false,
113
+ result: evento.data.content,
114
+ isError: evento.data.is_error,
115
+ }
116
+ : tool,
117
+ )
118
+ updateConversation(chatId, messageId, { tools })
119
+ break
120
+
121
+ case 'approval_required': {
122
+ // O tool_start já marcou estas ferramentas como "running".
123
+ // Elas não estão executando: estão paradas esperando o
124
+ // clique — senão o chip giraria para sempre.
125
+ const pausadas = new Set(
126
+ evento.data.calls.map((call) => call.id),
127
+ )
128
+ tools = tools.map((tool) =>
129
+ pausadas.has(tool.id)
130
+ ? { ...tool, running: false, awaitingApproval: true }
131
+ : tool,
132
+ )
133
+ updateConversation(chatId, messageId, {
134
+ tools,
135
+ pendingApproval: evento.data,
136
+ })
137
+ break
138
+ }
139
+
140
+ case 'done':
141
+ // O done traz a resposta final completa e é a fonte
142
+ // autoritativa — se algum token se perdeu, ele corrige.
143
+ updateConversation(chatId, messageId, {
144
+ content: evento.data.content,
145
+ })
146
+ break
147
+
148
+ case 'error':
149
+ updateConversation(chatId, messageId, {
150
+ content: evento.data.message || AI_ERROR_MESSAGE,
151
+ isError: true,
152
+ })
153
+ break
154
+ }
155
+ }
156
+
157
+ try {
158
+ await executar(aplicar)
159
+ } catch {
160
+ updateConversation(chatId, messageId, {
161
+ content: texto || AI_ERROR_MESSAGE,
162
+ isError: !texto,
163
+ })
164
+ } finally {
165
+ setIsTyping(false)
166
+ }
167
+ }
168
+
169
+ const sendMessage = async (chatId: string, prompt: string) => {
170
+ const messageId = createAiMessage(chatId)
171
+ await consumirStream(
172
+ chatId,
173
+ messageId,
174
+ (onEvent) => streamChat({ prompt, conversationId: chatId, onEvent }),
175
+ { texto: '', tools: [] },
176
+ )
65
177
  }
66
178
 
67
- const handleSend = async (prompt: string, attachments?: File[]) => {
179
+ const createConversation = async (chatId: string, prompt: string) => {
180
+ setSelectedConversation(chatId)
181
+ await sendMessage(chatId, prompt)
182
+ }
183
+
184
+ const handleSend = async (prompt: string) => {
68
185
  setIsTyping(true)
69
186
  if (selectedConversation) {
70
187
  creteMyMessage(selectedConversation, prompt)
@@ -80,12 +197,47 @@ const useChatSend = () => {
80
197
  enable: false,
81
198
  })
82
199
  creteMyMessage(newId, prompt)
83
- await createConversation(newId, prompt, attachments)
200
+ await createConversation(newId, prompt)
84
201
  }
85
202
  }
86
203
 
204
+ /**
205
+ * Aprova ou nega a operação pendente e retoma a conversa.
206
+ *
207
+ * Continua escrevendo na mesma mensagem: por isso o acumulador parte do
208
+ * conteúdo e das ferramentas que já estão lá.
209
+ */
210
+ const handleApproval = async (
211
+ chatId: string,
212
+ messageId: string,
213
+ approved: boolean,
214
+ ) => {
215
+ const mensagem = usGenerativeChatStore
216
+ .getState()
217
+ .chatHistory.find((chat) => chat.id === chatId)
218
+ ?.conversation?.find((item) => item.id === messageId)
219
+
220
+ if (!mensagem?.pendingApproval) {
221
+ return
222
+ }
223
+
224
+ setIsTyping(true)
225
+ // Some com o card antes da chamada: dois cliques rápidos enviariam duas
226
+ // retomadas, e a segunda falha (o interrupt já foi consumido).
227
+ updateConversation(chatId, messageId, { pendingApproval: undefined })
228
+
229
+ await consumirStream(
230
+ chatId,
231
+ messageId,
232
+ (onEvent) =>
233
+ resumeChat({ approved, conversationId: chatId, onEvent }),
234
+ { texto: mensagem.content ?? '', tools: mensagem.tools ?? [] },
235
+ )
236
+ }
237
+
87
238
  return {
88
239
  handleSend,
240
+ handleApproval,
89
241
  }
90
242
  }
91
243
 
@@ -24,6 +24,11 @@ type GenerativeChatAction = {
24
24
  setIsTyping: (payload: boolean) => void
25
25
  pushChatHistory: (payload: ChatHistory) => void
26
26
  pushConversation: (id: string, conversation: Conversation) => void
27
+ updateConversation: (
28
+ chatId: string,
29
+ messageId: string,
30
+ patch: Partial<Conversation>,
31
+ ) => void
27
32
  disabledChatFresh: (id: string) => void
28
33
  }
29
34
 
@@ -70,20 +75,43 @@ export const usGenerativeChatStore = create<
70
75
  return { selectedConversationRecord: record }
71
76
  }),
72
77
  pushChatHistory: (payload) =>
73
- set(() => ({ chatHistory: [...[payload], ...get().chatHistory] })),
78
+ set(() => ({ chatHistory: [payload, ...get().chatHistory] })),
74
79
  pushConversation: (id, conversation) =>
75
- set(() => {
76
- const chatHistory = get().chatHistory.map((chat) => {
77
- if (chat.id === id) {
78
- if (!chat.conversation) {
79
- chat.conversation = []
80
- }
81
- chat.conversation?.push(conversation)
82
- }
83
- return chat
84
- })
85
- return { chatHistory }
86
- }),
80
+ set(() => ({
81
+ chatHistory: get().chatHistory.map((chat) =>
82
+ chat.id === id
83
+ ? {
84
+ ...chat,
85
+ conversation: [
86
+ ...(chat.conversation ?? []),
87
+ conversation,
88
+ ],
89
+ }
90
+ : chat,
91
+ ),
92
+ })),
93
+ /**
94
+ * Atualiza UMA mensagem de uma conversa.
95
+ *
96
+ * Imutavel de proposito: o streaming chama isto a cada token, e mutar o
97
+ * objeto no lugar faria o React perder atualizacoes — a re-renderizacao
98
+ * dependeria da identidade do array externo mudar por acaso.
99
+ */
100
+ updateConversation: (chatId, messageId, patch) =>
101
+ set(() => ({
102
+ chatHistory: get().chatHistory.map((chat) =>
103
+ chat.id === chatId
104
+ ? {
105
+ ...chat,
106
+ conversation: chat.conversation?.map((mensagem) =>
107
+ mensagem.id === messageId
108
+ ? { ...mensagem, ...patch }
109
+ : mensagem,
110
+ ),
111
+ }
112
+ : chat,
113
+ ),
114
+ })),
87
115
  setIsTyping: (payload) => set(() => ({ isTyping: payload })),
88
116
  disabledChatFresh: () =>
89
117
  set(() => {
@@ -1,3 +1,29 @@
1
+ /** Uma ferramenta que o agente chamou durante a resposta. */
2
+ export type ToolCall = {
3
+ id: string
4
+ name: string
5
+ args: Record<string, unknown>
6
+ /** Retorno da ferramenta; so existe depois do tool_end. */
7
+ result?: string
8
+ isError?: boolean
9
+ /** true enquanto a ferramenta esta executando. */
10
+ running: boolean
11
+ /**
12
+ * true quando a ferramenta esta parada esperando o usuario aprovar.
13
+ *
14
+ * O backend emite `tool_start` assim que o modelo DECIDE chamar a
15
+ * ferramenta — antes de saber se ela precisa de aprovacao. Sem esta flag, o
16
+ * chip ficaria girando "executando" enquanto na verdade espera um clique.
17
+ */
18
+ awaitingApproval?: boolean
19
+ }
20
+
21
+ /** Operacao de escrita parada, esperando o usuario aprovar ou negar. */
22
+ export type PendingApproval = {
23
+ id: string
24
+ calls: { id: string; name: string; args: Record<string, unknown> }[]
25
+ }
26
+
1
27
  export type Conversation = {
2
28
  id: string
3
29
  sender: {
@@ -14,38 +40,65 @@ export type Conversation = {
14
40
  mediaUrl: string
15
41
  }>
16
42
  isMyMessage?: boolean
43
+ /**
44
+ * Liga a animacao de maquina de escrever do ChatCustomContent.
45
+ *
46
+ * Mensagens vindas do streaming devem usar `false`: o texto ja chega
47
+ * progressivamente do servidor, e a animacao reinicia do zero a cada token
48
+ * novo (ela reseta a posicao quando o conteudo muda).
49
+ */
17
50
  fresh?: boolean
51
+ isError?: boolean
52
+ /** Ferramentas chamadas pelo agente nesta resposta. */
53
+ tools?: ToolCall[]
54
+ /** Preenchido quando o agente pausou esperando aprovacao humana. */
55
+ pendingApproval?: PendingApproval
18
56
  }
19
57
 
20
- export type PostAiChatResponse = {
21
- id: string
22
- choices: {
23
- finish_reason:
24
- | 'stop'
25
- | 'length'
26
- | 'tool_calls'
27
- | 'content_filter'
28
- | 'function_call'
29
- index: number
30
- logprobs: {
31
- content: Array<{
32
- token: string
33
- bytes: Array<number> | null
34
- top_logprobs: Array<{
35
- token: string
36
- bytes: Array<number> | null
37
- logprob: number
38
- }>
39
- }>
40
- } | null
41
- message: {
42
- content: string | null
43
- role: string
44
- }
45
- }[]
46
- created: number
47
- model: string
48
- }
58
+ /**
59
+ * Eventos do stream de /ai/chat/stream.
60
+ *
61
+ * Ordem tipica de uma resposta que consulta o ERP:
62
+ *
63
+ * token* tool_start tool_end token* done
64
+ *
65
+ * `done` sempre fecha o stream e traz a resposta final completa. Concatenar os
66
+ * `token` da o mesmo texto — o front pode usar qualquer um dos dois caminhos.
67
+ */
68
+ export type ChatEvent =
69
+ | { type: 'token'; data: { content: string } }
70
+ | {
71
+ type: 'tool_start'
72
+ data: { id: string; name: string; args: Record<string, unknown> }
73
+ }
74
+ | {
75
+ type: 'tool_end'
76
+ data: {
77
+ id: string
78
+ name: string
79
+ content: string
80
+ is_error: boolean
81
+ }
82
+ }
83
+ | {
84
+ /**
85
+ * O agente parou antes de uma operacao de escrita e espera decisao.
86
+ *
87
+ * Quando este evento chega, o `done` NAO vem: o turno nao terminou,
88
+ * esta pausado. Retomar e chamar `resumeChat`.
89
+ */
90
+ type: 'approval_required'
91
+ data: {
92
+ id: string
93
+ calls: {
94
+ id: string
95
+ name: string
96
+ args: Record<string, unknown>
97
+ }[]
98
+ }
99
+ }
100
+ | { type: 'done'; data: { content: string } }
101
+ | { type: 'error'; data: { message: string } }
49
102
 
50
103
  export type ChatHistory = {
51
104
  id: string
@@ -5,7 +5,7 @@ const nfeEndpointConfig = {
5
5
  getPdf: `${NFE_API_PATH}/pdf/`,
6
6
  addObservacao: `${NFE_API_PATH}/observacao`,
7
7
  findEventos: `${NFE_API_PATH}/eventos/`,
8
- addCartaCorrecao: `${NFE_API_PATH}/carta-correcao`,
8
+ addEvento: `${NFE_API_PATH}/evento`,
9
9
  montaRelatorioNfeRecebidas: `${NFE_API_PATH}/relatorio-nfe-recebidas/`,
10
10
  downloadRelatorioNfeRecebidas: (fileName) => `${NFE_API_PATH}/relatorio-nfe-recebidas/${fileName}/download`,
11
11
  }
@@ -31,9 +31,9 @@ export async function apiGetEventosNfe(chaveAcesso) {
31
31
  })
32
32
  }
33
33
 
34
- export async function apiAddCartaCorrecao(body) {
34
+ export async function apiAddEvento(body) {
35
35
  return ApiService.fetchDataWithAxios({
36
- url: nfeEndpointConfig.addCartaCorrecao,
36
+ url: nfeEndpointConfig.addEvento,
37
37
  method: 'post',
38
38
  data: body,
39
39
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orbe-agro/client-core",
3
- "version": "5.6.238",
3
+ "version": "5.6.240",
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",
@@ -1,48 +0,0 @@
1
- import { jsx } from "react/jsx-runtime";
2
- import { useState, useEffect } from "react";
3
- import { apiGetAiModels } from "../../../../../services/AiService.js";
4
- import { useAiModelStore } from "../store/aiModelStore.js";
5
- const ModelSelect = () => {
6
- const [models, setModels] = useState([]);
7
- const selectedModel = useAiModelStore((state) => state.selectedModel);
8
- const setSelectedModel = useAiModelStore((state) => state.setSelectedModel);
9
- useEffect(() => {
10
- let active = true;
11
- apiGetAiModels().then((resp) => {
12
- if (!active) {
13
- return;
14
- }
15
- setModels(resp.models);
16
- const current = useAiModelStore.getState().selectedModel;
17
- const exists = resp.models.some((model) => model.id === current);
18
- if (!current || !exists) {
19
- const fallback = resp.models.find((model) => model.default) ?? resp.models[0];
20
- if (fallback) {
21
- useAiModelStore.getState().setSelectedModel(fallback.id);
22
- }
23
- }
24
- }).catch(() => {
25
- });
26
- return () => {
27
- active = false;
28
- };
29
- }, []);
30
- if (models.length === 0) {
31
- return null;
32
- }
33
- return /* @__PURE__ */ jsx(
34
- "select",
35
- {
36
- "aria-label": "Modelo de IA",
37
- title: "Modelo de IA",
38
- className: "max-w-[9rem] truncate rounded-lg border border-gray-300 bg-white px-2 py-1 text-xs dark:border-gray-600 dark:bg-gray-800",
39
- value: selectedModel ?? "",
40
- onChange: (event) => setSelectedModel(event.target.value),
41
- children: models.map((model) => /* @__PURE__ */ jsx("option", { value: model.id, children: model.label }, model.id))
42
- }
43
- );
44
- };
45
- export {
46
- ModelSelect as default
47
- };
48
- //# sourceMappingURL=ModelSelect.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ModelSelect.js","sources":["../../../../../../../lib/@ecme/views/concepts/ai/Chat/components/ModelSelect.tsx"],"sourcesContent":["import { useEffect, useState } from 'react'\nimport { apiGetAiModels } from '@/services/AiService'\nimport { useAiModelStore } from '../store/aiModelStore'\nimport type { AiModel, GetAiModelsResponse } from '@/services/AiService'\n\n/**\n * Seletor do modelo de IA do chat (GET /ai/models). Se o backend estiver\n * indisponivel ou nao expuser modelos, o seletor nao aparece e o backend usa o\n * modelo default — por isso ele pode ser montado sem medo em qualquer MFE.\n */\nconst ModelSelect = () => {\n const [models, setModels] = useState<AiModel[]>([])\n const selectedModel = useAiModelStore((state) => state.selectedModel)\n const setSelectedModel = useAiModelStore((state) => state.setSelectedModel)\n\n useEffect(() => {\n let active = true\n apiGetAiModels<GetAiModelsResponse>()\n .then((resp) => {\n if (!active) {\n return\n }\n setModels(resp.models)\n const current = useAiModelStore.getState().selectedModel\n const exists = resp.models.some((model) => model.id === current)\n if (!current || !exists) {\n const fallback =\n resp.models.find((model) => model.default) ??\n resp.models[0]\n if (fallback) {\n useAiModelStore\n .getState()\n .setSelectedModel(fallback.id)\n }\n }\n })\n .catch(() => {\n // Sem lista de modelos: o backend responde com o default.\n })\n return () => {\n active = false\n }\n }, [])\n\n if (models.length === 0) {\n return null\n }\n\n return (\n <select\n aria-label=\"Modelo de IA\"\n title=\"Modelo de IA\"\n className=\"max-w-[9rem] truncate rounded-lg border border-gray-300 bg-white px-2 py-1 text-xs dark:border-gray-600 dark:bg-gray-800\"\n value={selectedModel ?? ''}\n onChange={(event) => setSelectedModel(event.target.value)}\n >\n {models.map((model) => (\n <option key={model.id} value={model.id}>\n {model.label}\n </option>\n ))}\n </select>\n )\n}\n\nexport default ModelSelect\n"],"names":[],"mappings":";;;;AAUA,MAAM,cAAc,MAAM;AACtB,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAoB,CAAA,CAAE;AAClD,QAAM,gBAAgB,gBAAgB,CAAC,UAAU,MAAM,aAAa;AACpE,QAAM,mBAAmB,gBAAgB,CAAC,UAAU,MAAM,gBAAgB;AAE1E,YAAU,MAAM;AACZ,QAAI,SAAS;AACb,mBAAA,EACK,KAAK,CAAC,SAAS;AACZ,UAAI,CAAC,QAAQ;AACT;AAAA,MACJ;AACA,gBAAU,KAAK,MAAM;AACrB,YAAM,UAAU,gBAAgB,SAAA,EAAW;AAC3C,YAAM,SAAS,KAAK,OAAO,KAAK,CAAC,UAAU,MAAM,OAAO,OAAO;AAC/D,UAAI,CAAC,WAAW,CAAC,QAAQ;AACrB,cAAM,WACF,KAAK,OAAO,KAAK,CAAC,UAAU,MAAM,OAAO,KACzC,KAAK,OAAO,CAAC;AACjB,YAAI,UAAU;AACV,0BACK,SAAA,EACA,iBAAiB,SAAS,EAAE;AAAA,QACrC;AAAA,MACJ;AAAA,IACJ,CAAC,EACA,MAAM,MAAM;AAAA,IAEb,CAAC;AACL,WAAO,MAAM;AACT,eAAS;AAAA,IACb;AAAA,EACJ,GAAG,CAAA,CAAE;AAEL,MAAI,OAAO,WAAW,GAAG;AACrB,WAAO;AAAA,EACX;AAEA,SACI;AAAA,IAAC;AAAA,IAAA;AAAA,MACG,cAAW;AAAA,MACX,OAAM;AAAA,MACN,WAAU;AAAA,MACV,OAAO,iBAAiB;AAAA,MACxB,UAAU,CAAC,UAAU,iBAAiB,MAAM,OAAO,KAAK;AAAA,MAEvD,UAAA,OAAO,IAAI,CAAC,UACT,oBAAC,UAAA,EAAsB,OAAO,MAAM,IAC/B,UAAA,MAAM,MAAA,GADE,MAAM,EAEnB,CACH;AAAA,IAAA;AAAA,EAAA;AAGb;"}