@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.
- package/dist/@ecme/assets/styles/app.css +116 -3
- package/dist/@ecme/assets/styles/tailwind/index.css +119 -3
- package/dist/@ecme/components/template/Footer.js +1 -1
- package/dist/@ecme/components/template/OrbeAiWidget/OrbeAiWidget.js +0 -2
- package/dist/@ecme/components/template/OrbeAiWidget/OrbeAiWidget.js.map +1 -1
- package/dist/@ecme/components/view/ChatBox/ChatBox.js +2 -0
- package/dist/@ecme/components/view/ChatBox/ChatBox.js.map +1 -1
- package/dist/@ecme/components/view/ChatBox/components/MessageList.js +2 -1
- package/dist/@ecme/components/view/ChatBox/components/MessageList.js.map +1 -1
- package/dist/@ecme/index.js +3 -4
- package/dist/@ecme/services/AiService.js +53 -24
- package/dist/@ecme/services/AiService.js.map +1 -1
- package/dist/@ecme/services/aiAuthHeaders.js +35 -0
- package/dist/@ecme/services/aiAuthHeaders.js.map +1 -0
- package/dist/@ecme/services/axios/AxiosRequestIntrceptorConfigCallback.js +4 -27
- package/dist/@ecme/services/axios/AxiosRequestIntrceptorConfigCallback.js.map +1 -1
- package/dist/@ecme/services/index.js +4 -5
- package/dist/@ecme/services/sseParser.js +42 -0
- package/dist/@ecme/services/sseParser.js.map +1 -0
- package/dist/@ecme/views/concepts/ai/Chat/components/AiErrorMessage.js +15 -0
- package/dist/@ecme/views/concepts/ai/Chat/components/AiErrorMessage.js.map +1 -0
- package/dist/@ecme/views/concepts/ai/Chat/components/ApprovalCard.js +73 -0
- package/dist/@ecme/views/concepts/ai/Chat/components/ApprovalCard.js.map +1 -0
- package/dist/@ecme/views/concepts/ai/Chat/components/ChatView.js +37 -10
- package/dist/@ecme/views/concepts/ai/Chat/components/ChatView.js.map +1 -1
- package/dist/@ecme/views/concepts/ai/Chat/components/ToolCallChip.js +153 -0
- package/dist/@ecme/views/concepts/ai/Chat/components/ToolCallChip.js.map +1 -0
- package/dist/@ecme/views/concepts/ai/Chat/components/toolPresentation.js +99 -0
- package/dist/@ecme/views/concepts/ai/Chat/components/toolPresentation.js.map +1 -0
- package/dist/@ecme/views/concepts/ai/Chat/hooks/useChatSend.js +111 -17
- package/dist/@ecme/views/concepts/ai/Chat/hooks/useChatSend.js.map +1 -1
- package/dist/@ecme/views/concepts/ai/Chat/store/generativeChatStore.js +29 -13
- package/dist/@ecme/views/concepts/ai/Chat/store/generativeChatStore.js.map +1 -1
- package/dist/@types/@ecme/components/template/OrbeAiWidget/OrbeAiWidget.d.ts.map +1 -1
- package/dist/@types/@ecme/components/view/ChatBox/ChatBox.d.ts.map +1 -1
- package/dist/@types/@ecme/components/view/ChatBox/components/MessageList.d.ts +2 -0
- package/dist/@types/@ecme/components/view/ChatBox/components/MessageList.d.ts.map +1 -1
- package/dist/@types/@ecme/services/AiService.d.ts +28 -15
- package/dist/@types/@ecme/services/AiService.d.ts.map +1 -1
- package/dist/@types/@ecme/services/aiAuthHeaders.d.ts +12 -0
- package/dist/@types/@ecme/services/aiAuthHeaders.d.ts.map +1 -0
- package/dist/@types/@ecme/services/axios/AxiosRequestIntrceptorConfigCallback.d.ts +6 -0
- package/dist/@types/@ecme/services/axios/AxiosRequestIntrceptorConfigCallback.d.ts.map +1 -1
- package/dist/@types/@ecme/services/sseParser.d.ts +20 -0
- package/dist/@types/@ecme/services/sseParser.d.ts.map +1 -0
- package/dist/@types/@ecme/views/concepts/ai/Chat/components/AiErrorMessage.d.ts +8 -0
- package/dist/@types/@ecme/views/concepts/ai/Chat/components/AiErrorMessage.d.ts.map +1 -0
- package/dist/@types/@ecme/views/concepts/ai/Chat/components/ApprovalCard.d.ts +15 -0
- package/dist/@types/@ecme/views/concepts/ai/Chat/components/ApprovalCard.d.ts.map +1 -0
- package/dist/@types/@ecme/views/concepts/ai/Chat/components/ChatView.d.ts.map +1 -1
- package/dist/@types/@ecme/views/concepts/ai/Chat/components/ToolCallChip.d.ts +7 -0
- package/dist/@types/@ecme/views/concepts/ai/Chat/components/ToolCallChip.d.ts.map +1 -0
- package/dist/@types/@ecme/views/concepts/ai/Chat/components/toolPresentation.d.ts +14 -0
- package/dist/@types/@ecme/views/concepts/ai/Chat/components/toolPresentation.d.ts.map +1 -0
- package/dist/@types/@ecme/views/concepts/ai/Chat/hooks/useChatSend.d.ts +2 -1
- package/dist/@types/@ecme/views/concepts/ai/Chat/hooks/useChatSend.d.ts.map +1 -1
- package/dist/@types/@ecme/views/concepts/ai/Chat/store/generativeChatStore.d.ts +1 -0
- package/dist/@types/@ecme/views/concepts/ai/Chat/store/generativeChatStore.d.ts.map +1 -1
- package/dist/@types/@ecme/views/concepts/ai/Chat/types.d.ts +96 -23
- package/dist/@types/@ecme/views/concepts/ai/Chat/types.d.ts.map +1 -1
- package/dist/@types/base/configs/endpoints.config/HubFiscal/nfe.endpoint.config.d.ts +1 -1
- package/dist/@types/base/services/modules/HubFiscal/NfeService.d.ts +1 -1
- package/dist/@types/base/services/modules/HubFiscal/NfeService.d.ts.map +1 -1
- package/dist/base/assets/styles/init.css +128 -3
- package/dist/base/configs/endpoints.config/HubFiscal/nfe.endpoint.config.js +1 -1
- package/dist/base/configs/endpoints.config/HubFiscal/nfe.endpoint.config.js.map +1 -1
- package/dist/base/services/modules/HubFiscal/NfeService.js +3 -3
- package/dist/base/services/modules/HubFiscal/NfeService.js.map +1 -1
- package/lib/@ecme/components/template/OrbeAiWidget/OrbeAiWidget.tsx +0 -2
- package/lib/@ecme/components/view/ChatBox/ChatBox.tsx +2 -0
- package/lib/@ecme/components/view/ChatBox/components/MessageList.tsx +8 -4
- package/lib/@ecme/services/AiService.ts +89 -44
- package/lib/@ecme/services/aiAuthHeaders.ts +58 -0
- package/lib/@ecme/services/axios/AxiosRequestIntrceptorConfigCallback.ts +10 -39
- package/lib/@ecme/services/sseParser.ts +75 -0
- package/lib/@ecme/views/concepts/ai/Chat/components/AiErrorMessage.tsx +28 -0
- package/lib/@ecme/views/concepts/ai/Chat/components/ApprovalCard.tsx +103 -0
- package/lib/@ecme/views/concepts/ai/Chat/components/ChatView.tsx +74 -10
- package/lib/@ecme/views/concepts/ai/Chat/components/ToolCallChip.tsx +218 -0
- package/lib/@ecme/views/concepts/ai/Chat/components/toolPresentation.ts +137 -0
- package/lib/@ecme/views/concepts/ai/Chat/hooks/useChatSend.ts +175 -23
- package/lib/@ecme/views/concepts/ai/Chat/store/generativeChatStore.ts +41 -13
- package/lib/@ecme/views/concepts/ai/Chat/types.ts +82 -29
- package/lib/base/configs/endpoints.config/HubFiscal/nfe.endpoint.config.ts +1 -1
- package/lib/base/services/modules/HubFiscal/NfeService.ts +2 -2
- package/package.json +1 -1
- package/dist/@ecme/views/concepts/ai/Chat/components/ModelSelect.js +0 -48
- package/dist/@ecme/views/concepts/ai/Chat/components/ModelSelect.js.map +0 -1
- package/dist/@ecme/views/concepts/ai/Chat/store/aiModelStore.js +0 -15
- package/dist/@ecme/views/concepts/ai/Chat/store/aiModelStore.js.map +0 -1
- package/dist/@types/@ecme/views/concepts/ai/Chat/components/ModelSelect.d.ts +0 -8
- package/dist/@types/@ecme/views/concepts/ai/Chat/components/ModelSelect.d.ts.map +0 -1
- package/dist/@types/@ecme/views/concepts/ai/Chat/store/aiModelStore.d.ts +0 -25
- package/dist/@types/@ecme/views/concepts/ai/Chat/store/aiModelStore.d.ts.map +0 -1
- package/lib/@ecme/views/concepts/ai/Chat/components/ModelSelect.tsx +0 -66
- 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 {
|
|
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 {
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
|
39
|
-
|
|
40
|
-
|
|
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:
|
|
58
|
+
content: '',
|
|
50
59
|
timestamp: dayjs().toDate(),
|
|
51
60
|
type: 'regular',
|
|
52
61
|
isMyMessage: false,
|
|
53
|
-
fresh:
|
|
62
|
+
fresh: false,
|
|
63
|
+
...options,
|
|
54
64
|
})
|
|
55
|
-
|
|
65
|
+
return messageId
|
|
56
66
|
}
|
|
57
67
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
64
|
-
|
|
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
|
|
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
|
|
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: [
|
|
78
|
+
set(() => ({ chatHistory: [payload, ...get().chatHistory] })),
|
|
74
79
|
pushConversation: (id, conversation) =>
|
|
75
|
-
set(() => {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
|
|
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
|
|
34
|
+
export async function apiAddEvento(body) {
|
|
35
35
|
return ApiService.fetchDataWithAxios({
|
|
36
|
-
url: nfeEndpointConfig.
|
|
36
|
+
url: nfeEndpointConfig.addEvento,
|
|
37
37
|
method: 'post',
|
|
38
38
|
data: body,
|
|
39
39
|
})
|
package/package.json
CHANGED
|
@@ -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;"}
|