@orbe-agro/client-core 5.6.239 → 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/base/assets/styles/init.css +128 -3
- 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/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
|
@@ -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
|
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;"}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { create } from "zustand";
|
|
2
|
-
import { persist } from "zustand/middleware";
|
|
3
|
-
const useAiModelStore = create()(
|
|
4
|
-
persist(
|
|
5
|
-
(set) => ({
|
|
6
|
-
selectedModel: null,
|
|
7
|
-
setSelectedModel: (model) => set({ selectedModel: model })
|
|
8
|
-
}),
|
|
9
|
-
{ name: "orbe-ai-model" }
|
|
10
|
-
)
|
|
11
|
-
);
|
|
12
|
-
export {
|
|
13
|
-
useAiModelStore
|
|
14
|
-
};
|
|
15
|
-
//# sourceMappingURL=aiModelStore.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"aiModelStore.js","sources":["../../../../../../../lib/@ecme/views/concepts/ai/Chat/store/aiModelStore.ts"],"sourcesContent":["import { create } from 'zustand'\nimport { persist } from 'zustand/middleware'\n\ntype AiModelState = {\n selectedModel: string | null\n setSelectedModel: (model: string) => void\n}\n\n/**\n * Modelo de IA escolhido pelo usuario para o chat do Orbe AI.\n *\n * Persistido em localStorage: como todos os MFEs vivem na mesma origem, a\n * escolha acompanha o usuario entre os modulos e entre sessoes.\n */\nexport const useAiModelStore = create<AiModelState>()(\n persist(\n (set) => ({\n selectedModel: null,\n setSelectedModel: (model) => set({ selectedModel: model }),\n }),\n { name: 'orbe-ai-model' },\n ),\n)\n"],"names":[],"mappings":";;AAcO,MAAM,kBAAkB,OAAA;AAAA,EAC3B;AAAA,IACI,CAAC,SAAS;AAAA,MACN,eAAe;AAAA,MACf,kBAAkB,CAAC,UAAU,IAAI,EAAE,eAAe,OAAO;AAAA,IAAA;AAAA,IAE7D,EAAE,MAAM,gBAAA;AAAA,EAAgB;AAEhC;"}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Seletor do modelo de IA do chat (GET /ai/models). Se o backend estiver
|
|
3
|
-
* indisponivel ou nao expuser modelos, o seletor nao aparece e o backend usa o
|
|
4
|
-
* modelo default — por isso ele pode ser montado sem medo em qualquer MFE.
|
|
5
|
-
*/
|
|
6
|
-
declare const ModelSelect: () => import("react/jsx-runtime").JSX.Element | null;
|
|
7
|
-
export default ModelSelect;
|
|
8
|
-
//# sourceMappingURL=ModelSelect.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ModelSelect.d.ts","sourceRoot":"","sources":["../../../../../../../../lib/@ecme/views/concepts/ai/Chat/components/ModelSelect.tsx"],"names":[],"mappings":"AAKA;;;;GAIG;AACH,QAAA,MAAM,WAAW,sDAqDhB,CAAA;AAED,eAAe,WAAW,CAAA"}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
type AiModelState = {
|
|
2
|
-
selectedModel: string | null;
|
|
3
|
-
setSelectedModel: (model: string) => void;
|
|
4
|
-
};
|
|
5
|
-
/**
|
|
6
|
-
* Modelo de IA escolhido pelo usuario para o chat do Orbe AI.
|
|
7
|
-
*
|
|
8
|
-
* Persistido em localStorage: como todos os MFEs vivem na mesma origem, a
|
|
9
|
-
* escolha acompanha o usuario entre os modulos e entre sessoes.
|
|
10
|
-
*/
|
|
11
|
-
export declare const useAiModelStore: import("zustand").UseBoundStore<Omit<import("zustand").StoreApi<AiModelState>, "setState" | "persist"> & {
|
|
12
|
-
setState(partial: AiModelState | Partial<AiModelState> | ((state: AiModelState) => AiModelState | Partial<AiModelState>), replace?: false | undefined): unknown;
|
|
13
|
-
setState(state: AiModelState | ((state: AiModelState) => AiModelState), replace: true): unknown;
|
|
14
|
-
persist: {
|
|
15
|
-
setOptions: (options: Partial<import("zustand/middleware").PersistOptions<AiModelState, AiModelState, unknown>>) => void;
|
|
16
|
-
clearStorage: () => void;
|
|
17
|
-
rehydrate: () => Promise<void> | void;
|
|
18
|
-
hasHydrated: () => boolean;
|
|
19
|
-
onHydrate: (fn: (state: AiModelState) => void) => () => void;
|
|
20
|
-
onFinishHydration: (fn: (state: AiModelState) => void) => () => void;
|
|
21
|
-
getOptions: () => Partial<import("zustand/middleware").PersistOptions<AiModelState, AiModelState, unknown>>;
|
|
22
|
-
};
|
|
23
|
-
}>;
|
|
24
|
-
export {};
|
|
25
|
-
//# sourceMappingURL=aiModelStore.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"aiModelStore.d.ts","sourceRoot":"","sources":["../../../../../../../../lib/@ecme/views/concepts/ai/Chat/store/aiModelStore.ts"],"names":[],"mappings":"AAGA,KAAK,YAAY,GAAG;IAChB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,gBAAgB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;CAC5C,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,eAAe;;;;;;;;;;;;EAQ3B,CAAA"}
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { useEffect, useState } from 'react'
|
|
2
|
-
import { apiGetAiModels } from '@/services/AiService'
|
|
3
|
-
import { useAiModelStore } from '../store/aiModelStore'
|
|
4
|
-
import type { AiModel, GetAiModelsResponse } from '@/services/AiService'
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Seletor do modelo de IA do chat (GET /ai/models). Se o backend estiver
|
|
8
|
-
* indisponivel ou nao expuser modelos, o seletor nao aparece e o backend usa o
|
|
9
|
-
* modelo default — por isso ele pode ser montado sem medo em qualquer MFE.
|
|
10
|
-
*/
|
|
11
|
-
const ModelSelect = () => {
|
|
12
|
-
const [models, setModels] = useState<AiModel[]>([])
|
|
13
|
-
const selectedModel = useAiModelStore((state) => state.selectedModel)
|
|
14
|
-
const setSelectedModel = useAiModelStore((state) => state.setSelectedModel)
|
|
15
|
-
|
|
16
|
-
useEffect(() => {
|
|
17
|
-
let active = true
|
|
18
|
-
apiGetAiModels<GetAiModelsResponse>()
|
|
19
|
-
.then((resp) => {
|
|
20
|
-
if (!active) {
|
|
21
|
-
return
|
|
22
|
-
}
|
|
23
|
-
setModels(resp.models)
|
|
24
|
-
const current = useAiModelStore.getState().selectedModel
|
|
25
|
-
const exists = resp.models.some((model) => model.id === current)
|
|
26
|
-
if (!current || !exists) {
|
|
27
|
-
const fallback =
|
|
28
|
-
resp.models.find((model) => model.default) ??
|
|
29
|
-
resp.models[0]
|
|
30
|
-
if (fallback) {
|
|
31
|
-
useAiModelStore
|
|
32
|
-
.getState()
|
|
33
|
-
.setSelectedModel(fallback.id)
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
})
|
|
37
|
-
.catch(() => {
|
|
38
|
-
// Sem lista de modelos: o backend responde com o default.
|
|
39
|
-
})
|
|
40
|
-
return () => {
|
|
41
|
-
active = false
|
|
42
|
-
}
|
|
43
|
-
}, [])
|
|
44
|
-
|
|
45
|
-
if (models.length === 0) {
|
|
46
|
-
return null
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return (
|
|
50
|
-
<select
|
|
51
|
-
aria-label="Modelo de IA"
|
|
52
|
-
title="Modelo de IA"
|
|
53
|
-
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"
|
|
54
|
-
value={selectedModel ?? ''}
|
|
55
|
-
onChange={(event) => setSelectedModel(event.target.value)}
|
|
56
|
-
>
|
|
57
|
-
{models.map((model) => (
|
|
58
|
-
<option key={model.id} value={model.id}>
|
|
59
|
-
{model.label}
|
|
60
|
-
</option>
|
|
61
|
-
))}
|
|
62
|
-
</select>
|
|
63
|
-
)
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export default ModelSelect
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { create } from 'zustand'
|
|
2
|
-
import { persist } from 'zustand/middleware'
|
|
3
|
-
|
|
4
|
-
type AiModelState = {
|
|
5
|
-
selectedModel: string | null
|
|
6
|
-
setSelectedModel: (model: string) => void
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Modelo de IA escolhido pelo usuario para o chat do Orbe AI.
|
|
11
|
-
*
|
|
12
|
-
* Persistido em localStorage: como todos os MFEs vivem na mesma origem, a
|
|
13
|
-
* escolha acompanha o usuario entre os modulos e entre sessoes.
|
|
14
|
-
*/
|
|
15
|
-
export const useAiModelStore = create<AiModelState>()(
|
|
16
|
-
persist(
|
|
17
|
-
(set) => ({
|
|
18
|
-
selectedModel: null,
|
|
19
|
-
setSelectedModel: (model) => set({ selectedModel: model }),
|
|
20
|
-
}),
|
|
21
|
-
{ name: 'orbe-ai-model' },
|
|
22
|
-
),
|
|
23
|
-
)
|