@orbe-agro/client-core 5.6.242 → 5.6.244
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/components/template/Footer.js +1 -1
- package/dist/@ecme/services/AiService.js +3 -2
- package/dist/@ecme/services/AiService.js.map +1 -1
- package/dist/@ecme/views/concepts/ai/Chat/components/ChatView.js +14 -2
- package/dist/@ecme/views/concepts/ai/Chat/components/ChatView.js.map +1 -1
- package/dist/@ecme/views/concepts/ai/Chat/components/DataRequestCard.js +99 -0
- package/dist/@ecme/views/concepts/ai/Chat/components/DataRequestCard.js.map +1 -0
- package/dist/@ecme/views/concepts/ai/Chat/components/toolPresentation.js +15 -1
- package/dist/@ecme/views/concepts/ai/Chat/components/toolPresentation.js.map +1 -1
- package/dist/@ecme/views/concepts/ai/Chat/hooks/useChatSend.js +21 -1
- package/dist/@ecme/views/concepts/ai/Chat/hooks/useChatSend.js.map +1 -1
- package/dist/@types/@ecme/services/AiService.d.ts +13 -4
- package/dist/@types/@ecme/services/AiService.d.ts.map +1 -1
- package/dist/@types/@ecme/views/concepts/ai/Chat/components/ChatView.d.ts.map +1 -1
- package/dist/@types/@ecme/views/concepts/ai/Chat/components/DataRequestCard.d.ts +20 -0
- package/dist/@types/@ecme/views/concepts/ai/Chat/components/DataRequestCard.d.ts.map +1 -0
- package/dist/@types/@ecme/views/concepts/ai/Chat/components/toolPresentation.d.ts +12 -0
- package/dist/@types/@ecme/views/concepts/ai/Chat/components/toolPresentation.d.ts.map +1 -1
- package/dist/@types/@ecme/views/concepts/ai/Chat/hooks/useChatSend.d.ts +1 -0
- package/dist/@types/@ecme/views/concepts/ai/Chat/hooks/useChatSend.d.ts.map +1 -1
- package/dist/@types/@ecme/views/concepts/ai/Chat/types.d.ts +24 -0
- package/dist/@types/@ecme/views/concepts/ai/Chat/types.d.ts.map +1 -1
- package/lib/@ecme/services/AiService.ts +15 -5
- package/lib/@ecme/views/concepts/ai/Chat/components/ChatView.tsx +17 -3
- package/lib/@ecme/views/concepts/ai/Chat/components/DataRequestCard.tsx +144 -0
- package/lib/@ecme/views/concepts/ai/Chat/components/toolPresentation.ts +29 -0
- package/lib/@ecme/views/concepts/ai/Chat/hooks/useChatSend.ts +43 -0
- package/lib/@ecme/views/concepts/ai/Chat/types.ts +23 -0
- package/package.json +1 -1
|
@@ -3,7 +3,7 @@ import Container from "../shared/Container.js";
|
|
|
3
3
|
import classNames from "../../utils/classNames.js";
|
|
4
4
|
import { APP_NAME } from "../../constants/app.constant.js";
|
|
5
5
|
import { PAGE_CONTAINER_GUTTER_X } from "../../constants/theme.constant.js";
|
|
6
|
-
const version = true ? "5.6.
|
|
6
|
+
const version = true ? "5.6.244" : "0.0.0";
|
|
7
7
|
const FooterContent = () => {
|
|
8
8
|
return /* @__PURE__ */ jsxs("div", { className: "flex w-full flex-auto items-center justify-between", children: [
|
|
9
9
|
/* @__PURE__ */ jsxs("span", { children: [
|
|
@@ -2,7 +2,7 @@ import { authHeaders } from "./aiAuthHeaders.js";
|
|
|
2
2
|
import ApiService from "./ApiService.js";
|
|
3
3
|
import { readSse } from "./sseParser.js";
|
|
4
4
|
const AI_DEV_URL = "http://localhost:8000";
|
|
5
|
-
const AI_BUILD_URL = "https://orbe-ai
|
|
5
|
+
const AI_BUILD_URL = "https://orbe-ai.apps.qas.orbeagro.com.br";
|
|
6
6
|
function getAiApiUrl() {
|
|
7
7
|
const origin = globalThis?.location?.origin ?? "";
|
|
8
8
|
if (origin.includes("localhost") || origin.includes("127.0.0.1")) {
|
|
@@ -38,13 +38,14 @@ async function streamChat({
|
|
|
38
38
|
}
|
|
39
39
|
async function resumeChat({
|
|
40
40
|
approved,
|
|
41
|
+
dados,
|
|
41
42
|
conversationId,
|
|
42
43
|
signal,
|
|
43
44
|
onEvent
|
|
44
45
|
}) {
|
|
45
46
|
await postarEConsumir(
|
|
46
47
|
"/ai/chat/resume",
|
|
47
|
-
{ approved, conversation_id: conversationId },
|
|
48
|
+
{ approved, dados, conversation_id: conversationId },
|
|
48
49
|
onEvent,
|
|
49
50
|
signal
|
|
50
51
|
);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AiService.js","sources":["../../../lib/@ecme/services/AiService.ts"],"sourcesContent":["import { authHeaders } from './aiAuthHeaders'\nimport ApiService from './ApiService'\nimport { readSse } from './sseParser'\nimport type { ChatEvent } from '@/views/concepts/ai/Chat/types'\nimport type { AxiosRequestConfig } from 'axios'\n\nconst AI_DEV_URL = 'http://localhost:8000'\nconst AI_BUILD_URL = 'https://orbe-ai
|
|
1
|
+
{"version":3,"file":"AiService.js","sources":["../../../lib/@ecme/services/AiService.ts"],"sourcesContent":["import { authHeaders } from './aiAuthHeaders'\nimport ApiService from './ApiService'\nimport { readSse } from './sseParser'\nimport type { ChatEvent } from '@/views/concepts/ai/Chat/types'\nimport type { AxiosRequestConfig } from 'axios'\n\nconst AI_DEV_URL = 'http://localhost:8000'\nconst AI_BUILD_URL = 'https://orbe-ai.apps.qas.orbeagro.com.br'\n\n/**\n * Host da API de IA.\n *\n * O /ai/* nao vive no gateway do resto da aplicacao, entao nao pode usar o\n * baseURL do AxiosBase (API_URL). A resolucao e por origin em runtime, e nao\n * por import.meta.env.MODE, porque o MODE e congelado na build da lib como\n * \"production\" e nao reflete o dev do MFE consumidor.\n */\nfunction getAiApiUrl() {\n const origin = globalThis?.location?.origin ?? ''\n\n if (origin.includes('localhost') || origin.includes('127.0.0.1')) {\n return AI_DEV_URL\n }\n\n // QAS e PRD compartilham a mesma URL por enquanto. Quando houver uma URL\n // de PRD, o split entra aqui: if (origin.includes('.prd.')) return AI_PRD_URL\n return AI_BUILD_URL\n}\n\nexport const AI_API_URL = getAiApiUrl()\n\n/**\n * Toda chamada de IA sai pelo AI_API_URL (o baseURL por requisicao vence o da\n * instancia do AxiosBase) e fica fora do loading global — o chat mostra o\n * proprio balaozinho de \"digitando\".\n */\nconst aiRequest = <T>(config: AxiosRequestConfig) =>\n ApiService.fetchDataWithAxios<T>({\n ...config,\n baseURL: AI_API_URL,\n skipGlobalLoading: true,\n })\n\nexport async function apiGetChatHistory<T>(conversationId?: string) {\n return aiRequest<T>({\n url: '/ai/chat/history',\n method: 'get',\n params: conversationId ? { conversation_id: conversationId } : undefined,\n })\n}\n\nexport type StreamChatParams = {\n prompt: string\n /** Identifica a conversa; o backend usa como thread da memoria. */\n conversationId?: string\n signal?: AbortSignal\n onEvent: (evento: ChatEvent) => void\n}\n\n/**\n * Resposta a pausa atual do agente. Ha duas, e um endpoint so:\n * - `approved` responde ao `approval_required` (true aprova, false nega).\n * - `dados` responde ao `input_required`; `null` = o usuario fechou o\n * formulario sem preencher.\n */\nexport type ResumeChatParams = {\n approved?: boolean\n dados?: Record<string, unknown> | null\n conversationId?: string\n signal?: AbortSignal\n onEvent: (evento: ChatEvent) => void\n}\n\n/**\n * Envia a mensagem e consome a resposta em streaming (SSE).\n *\n * Usa `fetch`, e nao o Axios, porque o Axios no browser so resolve a Promise\n * com a resposta inteira — nao ha como ler o corpo enquanto ele chega.\n */\nexport async function streamChat({\n prompt,\n conversationId,\n signal,\n onEvent,\n}: StreamChatParams): Promise<void> {\n await postarEConsumir(\n '/ai/chat/stream',\n { prompt, conversation_id: conversationId },\n onEvent,\n signal,\n )\n}\n\n/**\n * Retoma uma conversa parada numa pausa (aprovacao ou coleta de dados).\n *\n * Devolve o mesmo tipo de stream do `streamChat`, e a resposta continua na\n * MESMA mensagem do chat — o backend so volta do ponto onde parou.\n *\n * O `JSON.stringify` descarta chaves `undefined`, entao o backend recebe apenas\n * o campo da pausa que esta sendo respondida.\n */\nexport async function resumeChat({\n approved,\n dados,\n conversationId,\n signal,\n onEvent,\n}: ResumeChatParams): Promise<void> {\n await postarEConsumir(\n '/ai/chat/resume',\n { approved, dados, conversation_id: conversationId },\n onEvent,\n signal,\n )\n}\n\n/** POST + leitura do SSE, comum ao envio e a retomada. */\nasync function postarEConsumir(\n caminho: string,\n corpo: Record<string, unknown>,\n onEvent: (evento: ChatEvent) => void,\n signal?: AbortSignal,\n): Promise<void> {\n const resposta = await fetch(`${AI_API_URL}${caminho}`, {\n method: 'POST',\n headers: {\n ...authHeaders(),\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(corpo),\n signal,\n })\n\n if (!resposta.ok || !resposta.body) {\n throw new Error(`Falha ao falar com a IA (HTTP ${resposta.status})`)\n }\n\n await readSse(resposta.body, ({ event, data }) => {\n try {\n onEvent({ type: event, data: JSON.parse(data) } as ChatEvent)\n } catch (error) {\n // Evento malformado nao pode derrubar o stream inteiro.\n console.debug('Evento SSE ignorado:', event, error)\n }\n })\n}\n\nexport async function apiGetImages<T, U extends Record<string, unknown>>(\n params: U,\n) {\n return aiRequest<T>({\n url: '/ai/images',\n method: 'get',\n params,\n })\n}\n\nexport async function apiPostImages<T, U extends Record<string, unknown>>(\n data: U,\n) {\n return aiRequest<T>({\n url: '/ai/images',\n method: 'post',\n data,\n })\n}\n"],"names":[],"mappings":";;;AAMA,MAAM,aAAa;AACnB,MAAM,eAAe;AAUrB,SAAS,cAAc;AACnB,QAAM,SAAS,YAAY,UAAU,UAAU;AAE/C,MAAI,OAAO,SAAS,WAAW,KAAK,OAAO,SAAS,WAAW,GAAG;AAC9D,WAAO;AAAA,EACX;AAIA,SAAO;AACX;AAEO,MAAM,aAAa,YAAA;AAO1B,MAAM,YAAY,CAAI,WAClB,WAAW,mBAAsB;AAAA,EAC7B,GAAG;AAAA,EACH,SAAS;AAAA,EACT,mBAAmB;AACvB,CAAC;AAEL,eAAsB,kBAAqB,gBAAyB;AAChE,SAAO,UAAa;AAAA,IAChB,KAAK;AAAA,IACL,QAAQ;AAAA,IACR,QAAQ,iBAAiB,EAAE,iBAAiB,mBAAmB;AAAA,EAAA,CAClE;AACL;AA8BA,eAAsB,WAAW;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACJ,GAAoC;AAChC,QAAM;AAAA,IACF;AAAA,IACA,EAAE,QAAQ,iBAAiB,eAAA;AAAA,IAC3B;AAAA,IACA;AAAA,EAAA;AAER;AAWA,eAAsB,WAAW;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACJ,GAAoC;AAChC,QAAM;AAAA,IACF;AAAA,IACA,EAAE,UAAU,OAAO,iBAAiB,eAAA;AAAA,IACpC;AAAA,IACA;AAAA,EAAA;AAER;AAGA,eAAe,gBACX,SACA,OACA,SACA,QACa;AACb,QAAM,WAAW,MAAM,MAAM,GAAG,UAAU,GAAG,OAAO,IAAI;AAAA,IACpD,QAAQ;AAAA,IACR,SAAS;AAAA,MACL,GAAG,YAAA;AAAA,MACH,gBAAgB;AAAA,IAAA;AAAA,IAEpB,MAAM,KAAK,UAAU,KAAK;AAAA,IAC1B;AAAA,EAAA,CACH;AAED,MAAI,CAAC,SAAS,MAAM,CAAC,SAAS,MAAM;AAChC,UAAM,IAAI,MAAM,iCAAiC,SAAS,MAAM,GAAG;AAAA,EACvE;AAEA,QAAM,QAAQ,SAAS,MAAM,CAAC,EAAE,OAAO,WAAW;AAC9C,QAAI;AACA,cAAQ,EAAE,MAAM,OAAO,MAAM,KAAK,MAAM,IAAI,GAAgB;AAAA,IAChE,SAAS,OAAO;AAEZ,cAAQ,MAAM,wBAAwB,OAAO,KAAK;AAAA,IACtD;AAAA,EACJ,CAAC;AACL;AAEA,eAAsB,aAClB,QACF;AACE,SAAO,UAAa;AAAA,IAChB,KAAK;AAAA,IACL,QAAQ;AAAA,IACR;AAAA,EAAA,CACH;AACL;AAEA,eAAsB,cAClB,MACF;AACE,SAAO,UAAa;AAAA,IAChB,KAAK;AAAA,IACL,QAAQ;AAAA,IACR;AAAA,EAAA,CACH;AACL;"}
|
|
@@ -10,6 +10,7 @@ import AiThinkingIndicator from "./AiThinkingIndicator.js";
|
|
|
10
10
|
import AiErrorMessage from "./AiErrorMessage.js";
|
|
11
11
|
import ToolCallChip from "./ToolCallChip.js";
|
|
12
12
|
import ApprovalCard from "./ApprovalCard.js";
|
|
13
|
+
import DataRequestCard from "./DataRequestCard.js";
|
|
13
14
|
import { usGenerativeChatStore } from "../store/generativeChatStore.js";
|
|
14
15
|
import useChatSend from "../hooks/useChatSend.js";
|
|
15
16
|
import Card from "../../../../../components/ui/Card/Card.js";
|
|
@@ -18,7 +19,7 @@ const BALAO_AGENTE = "prose rounded-xl bg-gray-100 px-5 py-2.5 text-sm text-gray
|
|
|
18
19
|
const ChatView = () => {
|
|
19
20
|
const scrollRef = useRef(null);
|
|
20
21
|
const { selectedConversation, chatHistory, isTyping, disabledChatFresh } = usGenerativeChatStore();
|
|
21
|
-
const { handleSend, handleApproval } = useChatSend();
|
|
22
|
+
const { handleSend, handleApproval, handleDataSubmit } = useChatSend();
|
|
22
23
|
const scrollToBottom = () => {
|
|
23
24
|
if (scrollRef.current?.getScrollElement) {
|
|
24
25
|
const scrollElement = scrollRef.current.getScrollElement();
|
|
@@ -72,7 +73,7 @@ const ChatView = () => {
|
|
|
72
73
|
return /* @__PURE__ */ jsx(AiErrorMessage, {});
|
|
73
74
|
}
|
|
74
75
|
if (message.sender.id === "ai") {
|
|
75
|
-
const { tools, pendingApproval } = message;
|
|
76
|
+
const { tools, pendingApproval, pendingInput } = message;
|
|
76
77
|
const temTexto = Boolean(
|
|
77
78
|
message.content?.trim()
|
|
78
79
|
);
|
|
@@ -86,6 +87,17 @@ const ChatView = () => {
|
|
|
86
87
|
onFinish: () => handleFinish(message.id)
|
|
87
88
|
}
|
|
88
89
|
) }),
|
|
90
|
+
pendingInput && /* @__PURE__ */ jsx(
|
|
91
|
+
DataRequestCard,
|
|
92
|
+
{
|
|
93
|
+
input: pendingInput,
|
|
94
|
+
onSubmit: (dados) => handleDataSubmit(
|
|
95
|
+
selectedConversation,
|
|
96
|
+
message.id,
|
|
97
|
+
dados
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
),
|
|
89
101
|
pendingApproval && /* @__PURE__ */ jsx(
|
|
90
102
|
ApprovalCard,
|
|
91
103
|
{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChatView.js","sources":["../../../../../../../lib/@ecme/views/concepts/ai/Chat/components/ChatView.tsx"],"sourcesContent":["import { useRef, useEffect, useMemo } from 'react'\nimport Card from '@/components/ui/Card'\nimport ChatBox from '@/components/view/ChatBox'\nimport ChatLandingView from './ChatLandingView'\nimport ChatMobileNav from './ChatMobileNav'\nimport ChatCustomContent from './ChatCustomContent'\nimport ChatCustomAction from './ChatCustomAction'\nimport AiThinkingIndicator from './AiThinkingIndicator'\nimport AiErrorMessage from './AiErrorMessage'\nimport ToolCallChip from './ToolCallChip'\nimport ApprovalCard from './ApprovalCard'\nimport { usGenerativeChatStore } from '../store/generativeChatStore'\nimport useChatSend from '../hooks/useChatSend'\nimport type { Conversation } from '../types'\nimport type { ScrollBarRef } from '@/components/ui/ScrollBar'\n\n/**\n * O balao de fala do agente, desenhado aqui em vez de vir do ChatBox.\n *\n * O `botBubbleClass` abaixo zera o balao que o Message desenharia, para os chips\n * de ferramenta e o card de aprovacao ficarem FORA dele — antes tudo dividia a\n * mesma caixa cinza e as ferramentas eram lidas como uma segunda mensagem.\n * Usado tambem pelo indicador de \"pensando\", que passa pelo mesmo balao zerado.\n *\n * Repete `prose text-sm` do balao original de proposito: o reset de margem do\n * @tailwindcss/typography e `.prose > :first-child`, filho DIRETO. Sem o `prose`\n * aqui, o markdown ficaria um nivel abaixo do container e o primeiro paragrafo\n * voltaria a ter margem, inflando todos os baloes.\n */\nconst BALAO_AGENTE =\n 'prose rounded-xl bg-gray-100 px-5 py-2.5 text-sm text-gray-900 dark:bg-gray-700 dark:text-gray-100'\n\nconst ChatView = () => {\n const scrollRef = useRef<ScrollBarRef>(null)\n const { selectedConversation, chatHistory, isTyping, disabledChatFresh } =\n usGenerativeChatStore()\n const { handleSend, handleApproval } = useChatSend()\n\n const scrollToBottom = () => {\n if (scrollRef.current?.getScrollElement) {\n const scrollElement = scrollRef.current.getScrollElement()\n if (scrollElement) {\n scrollElement.scrollTop = scrollElement.scrollHeight\n }\n }\n }\n\n useEffect(() => {\n scrollToBottom()\n }, [selectedConversation, chatHistory])\n\n const messageList = useMemo(() => {\n const chat = chatHistory.find(\n (chat) => chat.id === selectedConversation,\n )\n return chat?.conversation || []\n }, [selectedConversation, chatHistory])\n\n const handleInputChange = async ({\n value,\n }: {\n value: string\n attachments?: File[]\n }) => {\n await handleSend(value)\n }\n\n const handleFinish = (id: string) => {\n disabledChatFresh(id)\n scrollToBottom()\n }\n\n return (\n <Card className=\"flex-1 h-full\" bodyClass=\"h-full\">\n <ChatMobileNav />\n <ChatBox\n ref={scrollRef}\n messageList={messageList}\n placeholder=\"Pergunte algo sobre o Orbe Agro…\"\n showMessageList={Boolean(selectedConversation)}\n showAvatar={false}\n avatarGap={false}\n myBubbleClass=\"bg-primary text-white\"\n botBubbleClass=\"bg-transparent dark:bg-transparent p-0 rounded-none\"\n errorBubbleClass=\"bg-red-50 dark:bg-red-500/10 border border-red-200 dark:border-red-500/20\"\n renderTyping={() => (\n <div className={BALAO_AGENTE}>\n <AiThinkingIndicator />\n </div>\n )}\n containerClass=\"h-[calc(100%-30px)] xl:h-full\"\n messageListClass=\"h-[calc(100%-100px)] xl:h-[calc(100%-70px)]\"\n typing={\n isTyping\n ? {\n id: 'ai',\n name: 'Chat AI',\n avatarImageUrl: '/img/thumbs/ai.jpg',\n }\n : false\n }\n customRenderer={(message) => {\n if (message.isError) {\n return <AiErrorMessage />\n }\n\n if (message.sender.id === 'ai') {\n // As ferramentas e a aprovacao chegam pelo streaming e\n // vivem no Conversation; o tipo do ChatBox nao os conhece.\n const { tools, pendingApproval } = message as Conversation\n // Enquanto as ferramentas rodam o conteudo ainda e '':\n // sem esta guarda sobraria um balao cinza vazio.\n const temTexto = Boolean(\n (message.content as string)?.trim(),\n )\n\n return (\n <div className=\"flex flex-col items-start gap-1.5\">\n {tools?.map((tool) => (\n <ToolCallChip key={tool.id} tool={tool} />\n ))}\n {temTexto && (\n <div className={BALAO_AGENTE}>\n <ChatCustomContent\n content={message.content as string}\n triggerTyping={\n message.fresh\n ? message.fresh\n : false\n }\n onFinish={() =>\n handleFinish(message.id)\n }\n />\n </div>\n )}\n {pendingApproval && (\n <ApprovalCard\n approval={pendingApproval}\n onDecide={(aprovado) =>\n handleApproval(\n selectedConversation,\n message.id,\n aprovado,\n )\n }\n />\n )}\n </div>\n )\n }\n\n return message.content\n }}\n customAction={(message) => {\n if (message.sender.id === 'ai' && !message.isError) {\n return (\n <ChatCustomAction\n content={message.content as string}\n />\n )\n }\n\n return null\n }}\n onInputChange={handleInputChange}\n >\n {!selectedConversation && (\n <div className=\"flex-1 min-h-0 overflow-y-auto\">\n <ChatLandingView />\n </div>\n )}\n </ChatBox>\n </Card>\n )\n}\n\nexport default ChatView\n"],"names":["chat"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ChatView.js","sources":["../../../../../../../lib/@ecme/views/concepts/ai/Chat/components/ChatView.tsx"],"sourcesContent":["import { useRef, useEffect, useMemo } from 'react'\nimport Card from '@/components/ui/Card'\nimport ChatBox from '@/components/view/ChatBox'\nimport ChatLandingView from './ChatLandingView'\nimport ChatMobileNav from './ChatMobileNav'\nimport ChatCustomContent from './ChatCustomContent'\nimport ChatCustomAction from './ChatCustomAction'\nimport AiThinkingIndicator from './AiThinkingIndicator'\nimport AiErrorMessage from './AiErrorMessage'\nimport ToolCallChip from './ToolCallChip'\nimport ApprovalCard from './ApprovalCard'\nimport DataRequestCard from './DataRequestCard'\nimport { usGenerativeChatStore } from '../store/generativeChatStore'\nimport useChatSend from '../hooks/useChatSend'\nimport type { Conversation } from '../types'\nimport type { ScrollBarRef } from '@/components/ui/ScrollBar'\n\n/**\n * O balao de fala do agente, desenhado aqui em vez de vir do ChatBox.\n *\n * O `botBubbleClass` abaixo zera o balao que o Message desenharia, para os chips\n * de ferramenta e o card de aprovacao ficarem FORA dele — antes tudo dividia a\n * mesma caixa cinza e as ferramentas eram lidas como uma segunda mensagem.\n * Usado tambem pelo indicador de \"pensando\", que passa pelo mesmo balao zerado.\n *\n * Repete `prose text-sm` do balao original de proposito: o reset de margem do\n * @tailwindcss/typography e `.prose > :first-child`, filho DIRETO. Sem o `prose`\n * aqui, o markdown ficaria um nivel abaixo do container e o primeiro paragrafo\n * voltaria a ter margem, inflando todos os baloes.\n */\nconst BALAO_AGENTE =\n 'prose rounded-xl bg-gray-100 px-5 py-2.5 text-sm text-gray-900 dark:bg-gray-700 dark:text-gray-100'\n\nconst ChatView = () => {\n const scrollRef = useRef<ScrollBarRef>(null)\n const { selectedConversation, chatHistory, isTyping, disabledChatFresh } =\n usGenerativeChatStore()\n const { handleSend, handleApproval, handleDataSubmit } = useChatSend()\n\n const scrollToBottom = () => {\n if (scrollRef.current?.getScrollElement) {\n const scrollElement = scrollRef.current.getScrollElement()\n if (scrollElement) {\n scrollElement.scrollTop = scrollElement.scrollHeight\n }\n }\n }\n\n useEffect(() => {\n scrollToBottom()\n }, [selectedConversation, chatHistory])\n\n const messageList = useMemo(() => {\n const chat = chatHistory.find(\n (chat) => chat.id === selectedConversation,\n )\n return chat?.conversation || []\n }, [selectedConversation, chatHistory])\n\n const handleInputChange = async ({\n value,\n }: {\n value: string\n attachments?: File[]\n }) => {\n await handleSend(value)\n }\n\n const handleFinish = (id: string) => {\n disabledChatFresh(id)\n scrollToBottom()\n }\n\n return (\n <Card className=\"flex-1 h-full\" bodyClass=\"h-full\">\n <ChatMobileNav />\n <ChatBox\n ref={scrollRef}\n messageList={messageList}\n placeholder=\"Pergunte algo sobre o Orbe Agro…\"\n showMessageList={Boolean(selectedConversation)}\n showAvatar={false}\n avatarGap={false}\n myBubbleClass=\"bg-primary text-white\"\n botBubbleClass=\"bg-transparent dark:bg-transparent p-0 rounded-none\"\n errorBubbleClass=\"bg-red-50 dark:bg-red-500/10 border border-red-200 dark:border-red-500/20\"\n renderTyping={() => (\n <div className={BALAO_AGENTE}>\n <AiThinkingIndicator />\n </div>\n )}\n containerClass=\"h-[calc(100%-30px)] xl:h-full\"\n messageListClass=\"h-[calc(100%-100px)] xl:h-[calc(100%-70px)]\"\n typing={\n isTyping\n ? {\n id: 'ai',\n name: 'Chat AI',\n avatarImageUrl: '/img/thumbs/ai.jpg',\n }\n : false\n }\n customRenderer={(message) => {\n if (message.isError) {\n return <AiErrorMessage />\n }\n\n if (message.sender.id === 'ai') {\n // As ferramentas e as pausas chegam pelo streaming e\n // vivem no Conversation; o tipo do ChatBox nao os conhece.\n const { tools, pendingApproval, pendingInput } =\n message as Conversation\n // Enquanto as ferramentas rodam o conteudo ainda e '':\n // sem esta guarda sobraria um balao cinza vazio.\n const temTexto = Boolean(\n (message.content as string)?.trim(),\n )\n\n return (\n <div className=\"flex flex-col items-start gap-1.5\">\n {tools?.map((tool) => (\n <ToolCallChip key={tool.id} tool={tool} />\n ))}\n {temTexto && (\n <div className={BALAO_AGENTE}>\n <ChatCustomContent\n content={message.content as string}\n triggerTyping={\n message.fresh\n ? message.fresh\n : false\n }\n onFinish={() =>\n handleFinish(message.id)\n }\n />\n </div>\n )}\n {pendingInput && (\n <DataRequestCard\n input={pendingInput}\n onSubmit={(dados) =>\n handleDataSubmit(\n selectedConversation,\n message.id,\n dados,\n )\n }\n />\n )}\n {pendingApproval && (\n <ApprovalCard\n approval={pendingApproval}\n onDecide={(aprovado) =>\n handleApproval(\n selectedConversation,\n message.id,\n aprovado,\n )\n }\n />\n )}\n </div>\n )\n }\n\n return message.content\n }}\n customAction={(message) => {\n if (message.sender.id === 'ai' && !message.isError) {\n return (\n <ChatCustomAction\n content={message.content as string}\n />\n )\n }\n\n return null\n }}\n onInputChange={handleInputChange}\n >\n {!selectedConversation && (\n <div className=\"flex-1 min-h-0 overflow-y-auto\">\n <ChatLandingView />\n </div>\n )}\n </ChatBox>\n </Card>\n )\n}\n\nexport default ChatView\n"],"names":["chat"],"mappings":"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"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { TbForms } from "react-icons/tb";
|
|
4
|
+
import "../../../../../components/ui/Button/index.js";
|
|
5
|
+
import "../../../../../components/ui/Input/index.js";
|
|
6
|
+
import "../../../../../components/ui/Select/index.js";
|
|
7
|
+
import { tipoDoCampo, rotularCampo, opcoesDoCampo } from "./toolPresentation.js";
|
|
8
|
+
import Select from "../../../../../components/ui/Select/Select.js";
|
|
9
|
+
import Input from "../../../../../components/ui/Input/Input.js";
|
|
10
|
+
import Button from "../../../../../components/ui/Button/Button.js";
|
|
11
|
+
const DataRequestCard = ({ input, onSubmit }) => {
|
|
12
|
+
const [valores, setValores] = useState({});
|
|
13
|
+
const [enviando, setEnviando] = useState(null);
|
|
14
|
+
const preencher = (campo, valor) => setValores((atual) => ({ ...atual, [campo]: valor }));
|
|
15
|
+
const completo = input.campos.every((campo) => valores[campo]?.trim());
|
|
16
|
+
const enviar = () => {
|
|
17
|
+
setEnviando("enviar");
|
|
18
|
+
const dados = Object.fromEntries(
|
|
19
|
+
input.campos.map((campo) => {
|
|
20
|
+
const bruto = valores[campo].trim();
|
|
21
|
+
const numero = tipoDoCampo(campo) === "numero" ? Number(bruto) : NaN;
|
|
22
|
+
return [campo, Number.isNaN(numero) ? bruto : numero];
|
|
23
|
+
})
|
|
24
|
+
);
|
|
25
|
+
onSubmit(dados);
|
|
26
|
+
};
|
|
27
|
+
const cancelar = () => {
|
|
28
|
+
setEnviando("cancelar");
|
|
29
|
+
onSubmit(null);
|
|
30
|
+
};
|
|
31
|
+
return /* @__PURE__ */ jsxs("div", { className: "not-prose mt-1 overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm dark:border-gray-700 dark:bg-gray-800", children: [
|
|
32
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 border-b border-gray-200 px-4 py-2.5 dark:border-gray-700", children: [
|
|
33
|
+
/* @__PURE__ */ jsx(TbForms, { size: 16, className: "text-gray-400 dark:text-gray-500" }),
|
|
34
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400", children: "Dados necessários" })
|
|
35
|
+
] }),
|
|
36
|
+
/* @__PURE__ */ jsxs("div", { className: "px-4 py-3.5", children: [
|
|
37
|
+
/* @__PURE__ */ jsx("div", { className: "mb-3 text-sm text-gray-500 dark:text-gray-400", children: "Preencha os campos abaixo para eu continuar." }),
|
|
38
|
+
/* @__PURE__ */ jsx("div", { className: "flex flex-col gap-3", children: input.campos.map((campo) => {
|
|
39
|
+
const rotulo = rotularCampo(campo);
|
|
40
|
+
const opcoes = opcoesDoCampo(campo);
|
|
41
|
+
return /* @__PURE__ */ jsxs("label", { className: "flex flex-col gap-1", children: [
|
|
42
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm text-gray-600 dark:text-gray-300", children: rotulo }),
|
|
43
|
+
opcoes.length ? /* @__PURE__ */ jsx(
|
|
44
|
+
Select,
|
|
45
|
+
{
|
|
46
|
+
options: opcoes,
|
|
47
|
+
value: opcoes.filter(
|
|
48
|
+
(opcao) => opcao.value === valores[campo]
|
|
49
|
+
),
|
|
50
|
+
placeholder: `Selecione ${rotulo.toLowerCase()}`,
|
|
51
|
+
isDisabled: enviando !== null,
|
|
52
|
+
onChange: (opcao) => preencher(
|
|
53
|
+
campo,
|
|
54
|
+
opcao?.value ?? ""
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
) : /* @__PURE__ */ jsx(
|
|
58
|
+
Input,
|
|
59
|
+
{
|
|
60
|
+
type: tipoDoCampo(campo) === "numero" ? "number" : "text",
|
|
61
|
+
value: valores[campo] ?? "",
|
|
62
|
+
disabled: enviando !== null,
|
|
63
|
+
placeholder: `Informe ${rotulo.toLowerCase()}`,
|
|
64
|
+
onChange: (evento) => preencher(campo, evento.target.value)
|
|
65
|
+
}
|
|
66
|
+
)
|
|
67
|
+
] }, campo);
|
|
68
|
+
}) }),
|
|
69
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-4 flex items-center gap-2", children: [
|
|
70
|
+
/* @__PURE__ */ jsx(
|
|
71
|
+
Button,
|
|
72
|
+
{
|
|
73
|
+
variant: "solid",
|
|
74
|
+
size: "sm",
|
|
75
|
+
loading: enviando === "enviar",
|
|
76
|
+
disabled: !completo || enviando !== null,
|
|
77
|
+
onClick: enviar,
|
|
78
|
+
children: "Enviar"
|
|
79
|
+
}
|
|
80
|
+
),
|
|
81
|
+
/* @__PURE__ */ jsx(
|
|
82
|
+
Button,
|
|
83
|
+
{
|
|
84
|
+
size: "sm",
|
|
85
|
+
loading: enviando === "cancelar",
|
|
86
|
+
disabled: enviando !== null,
|
|
87
|
+
onClick: cancelar,
|
|
88
|
+
children: "Cancelar"
|
|
89
|
+
}
|
|
90
|
+
),
|
|
91
|
+
/* @__PURE__ */ jsx("span", { className: "ml-auto text-xs text-gray-400 dark:text-gray-500", children: "Você ainda confirma antes de gravar" })
|
|
92
|
+
] })
|
|
93
|
+
] })
|
|
94
|
+
] });
|
|
95
|
+
};
|
|
96
|
+
export {
|
|
97
|
+
DataRequestCard as default
|
|
98
|
+
};
|
|
99
|
+
//# sourceMappingURL=DataRequestCard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DataRequestCard.js","sources":["../../../../../../../lib/@ecme/views/concepts/ai/Chat/components/DataRequestCard.tsx"],"sourcesContent":["import { useState } from 'react'\nimport { TbForms } from 'react-icons/tb'\nimport Button from '@/components/ui/Button'\nimport Input from '@/components/ui/Input'\nimport Select from '@/components/ui/Select'\nimport { opcoesDoCampo, rotularCampo, tipoDoCampo } from './toolPresentation'\nimport type { PendingInput } from '../types'\n\ntype DataRequestCardProps = {\n input: PendingInput\n /** `null` = o usuario fechou sem preencher. */\n onSubmit: (dados: Record<string, unknown> | null) => void\n}\n\n/**\n * Formulario dos campos que faltam para criar um registro no ERP.\n *\n * O agente fica PARADO enquanto este card estiver na tela — ele so volta a\n * responder depois do envio. Por isso divide o visual do ApprovalCard: sao as\n * duas paradas do fluxo, e o usuario deve reconhece-las como a mesma coisa.\n *\n * Os valores vao para o modelo, que monta a chamada final. Por isso o envio nao\n * valida tipo: o `type=number` e o select sao conveniencia de digitacao, nao\n * regra de negocio — quem valida de verdade e o backend do ERP.\n */\nconst DataRequestCard = ({ input, onSubmit }: DataRequestCardProps) => {\n const [valores, setValores] = useState<Record<string, string>>({})\n const [enviando, setEnviando] = useState<'enviar' | 'cancelar' | null>(null)\n\n const preencher = (campo: string, valor: string) =>\n setValores((atual) => ({ ...atual, [campo]: valor }))\n\n // Exigir todos evita uma ida e volta: faltando um, o agente pediria de novo.\n const completo = input.campos.every((campo) => valores[campo]?.trim())\n\n const enviar = () => {\n setEnviando('enviar')\n // Numeros vao como number; o resto como texto. O modelo aceita os dois,\n // mas o tipo certo evita que ele precise adivinhar.\n const dados = Object.fromEntries(\n input.campos.map((campo) => {\n const bruto = valores[campo].trim()\n const numero =\n tipoDoCampo(campo) === 'numero' ? Number(bruto) : NaN\n return [campo, Number.isNaN(numero) ? bruto : numero]\n }),\n )\n onSubmit(dados)\n }\n\n const cancelar = () => {\n setEnviando('cancelar')\n onSubmit(null)\n }\n\n return (\n <div className=\"not-prose mt-1 overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm dark:border-gray-700 dark:bg-gray-800\">\n <div className=\"flex items-center gap-2 border-b border-gray-200 px-4 py-2.5 dark:border-gray-700\">\n <TbForms size={16} className=\"text-gray-400 dark:text-gray-500\" />\n <span className=\"text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400\">\n Dados necessários\n </span>\n </div>\n\n <div className=\"px-4 py-3.5\">\n <div className=\"mb-3 text-sm text-gray-500 dark:text-gray-400\">\n Preencha os campos abaixo para eu continuar.\n </div>\n\n <div className=\"flex flex-col gap-3\">\n {input.campos.map((campo) => {\n const rotulo = rotularCampo(campo)\n const opcoes = opcoesDoCampo(campo)\n\n return (\n <label key={campo} className=\"flex flex-col gap-1\">\n <span className=\"text-sm text-gray-600 dark:text-gray-300\">\n {rotulo}\n </span>\n {opcoes.length ? (\n <Select\n options={opcoes}\n value={opcoes.filter(\n (opcao) =>\n opcao.value === valores[campo],\n )}\n placeholder={`Selecione ${rotulo.toLowerCase()}`}\n isDisabled={enviando !== null}\n onChange={(opcao) =>\n preencher(\n campo,\n (opcao as { value: string } | null)\n ?.value ?? '',\n )\n }\n />\n ) : (\n <Input\n type={\n tipoDoCampo(campo) === 'numero'\n ? 'number'\n : 'text'\n }\n value={valores[campo] ?? ''}\n disabled={enviando !== null}\n placeholder={`Informe ${rotulo.toLowerCase()}`}\n onChange={(evento) =>\n preencher(campo, evento.target.value)\n }\n />\n )}\n </label>\n )\n })}\n </div>\n\n <div className=\"mt-4 flex items-center gap-2\">\n <Button\n variant=\"solid\"\n size=\"sm\"\n loading={enviando === 'enviar'}\n disabled={!completo || enviando !== null}\n onClick={enviar}\n >\n Enviar\n </Button>\n <Button\n size=\"sm\"\n loading={enviando === 'cancelar'}\n disabled={enviando !== null}\n onClick={cancelar}\n >\n Cancelar\n </Button>\n <span className=\"ml-auto text-xs text-gray-400 dark:text-gray-500\">\n Você ainda confirma antes de gravar\n </span>\n </div>\n </div>\n </div>\n )\n}\n\nexport default DataRequestCard\n"],"names":[],"mappings":"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"}
|
|
@@ -46,6 +46,18 @@ const REFERENCIAS = /* @__PURE__ */ new Set([
|
|
|
46
46
|
"id"
|
|
47
47
|
]);
|
|
48
48
|
const ORDENACAO = { ASC: "Crescente", DESC: "Decrescente" };
|
|
49
|
+
const OPCOES = {
|
|
50
|
+
tipo_contrato: ["MERCADO_INTERNO", "MERCADO_EXTERNO"]
|
|
51
|
+
};
|
|
52
|
+
const opcoesDoCampo = (chave) => (OPCOES[chave] ?? []).map((valor) => ({
|
|
53
|
+
value: valor,
|
|
54
|
+
label: humanizar(valor)
|
|
55
|
+
}));
|
|
56
|
+
const tipoDoCampo = (chave) => {
|
|
57
|
+
if (OPCOES[chave]) return "enum";
|
|
58
|
+
if (REFERENCIAS.has(chave) || chave === "saldo_negociado") return "numero";
|
|
59
|
+
return "texto";
|
|
60
|
+
};
|
|
49
61
|
const formatarValor = (chave, valor) => {
|
|
50
62
|
if (valor === null || valor === void 0 || valor === "") {
|
|
51
63
|
return "—";
|
|
@@ -92,8 +104,10 @@ export {
|
|
|
92
104
|
camposVisiveis,
|
|
93
105
|
formatarValor,
|
|
94
106
|
iconeFerramenta,
|
|
107
|
+
opcoesDoCampo,
|
|
95
108
|
rotularCampo,
|
|
96
109
|
rotularExecucao,
|
|
97
|
-
rotularFerramenta
|
|
110
|
+
rotularFerramenta,
|
|
111
|
+
tipoDoCampo
|
|
98
112
|
};
|
|
99
113
|
//# sourceMappingURL=toolPresentation.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toolPresentation.js","sources":["../../../../../../../lib/@ecme/views/concepts/ai/Chat/components/toolPresentation.ts"],"sourcesContent":["import { formatQuantity } from '@base/utils/functions/stringUtils'\nimport { TbFilePlus, TbListSearch, TbSearch, TbTool } from 'react-icons/tb'\nimport type { IconType } from 'react-icons'\n\n/**\n * Como as ferramentas do agente sao apresentadas ao usuario.\n *\n * Compartilhado pelo chip de execucao e pelo card de aprovacao, para os dois\n * falarem a mesma lingua: o usuario ve \"Consulta de contratos\" e \"Mercado\n * interno\", nunca `listar_contratos_armazenagem_graos` ou `MERCADO_INTERNO`.\n *\n * Tudo tem fallback: uma ferramenta ou campo novo no backend nao quebra a tela,\n * so aparece com o nome tecnico humanizado ate ganhar entrada aqui.\n */\n\ntype Ferramenta = {\n rotulo: string\n /** Frase no gerundio, para o estado \"executando\". */\n executando: string\n icone: IconType\n}\n\nconst FERRAMENTAS: Record<string, Ferramenta> = {\n buscar_contrato_armazenagem_graos: {\n rotulo: 'Consulta de contrato',\n executando: 'Consultando contrato',\n icone: TbSearch,\n },\n listar_contratos_armazenagem_graos: {\n rotulo: 'Consulta de contratos',\n executando: 'Consultando contratos',\n icone: TbListSearch,\n },\n criar_contrato_armazenagem_graos: {\n rotulo: 'Criação de contrato de armazenagem',\n executando: 'Criando contrato',\n icone: TbFilePlus,\n },\n}\n\nconst humanizar = (texto: string) =>\n texto\n .replaceAll('_', ' ')\n .toLowerCase()\n .replace(/^./, (letra) => letra.toUpperCase())\n\nexport const rotularFerramenta = (nome: string) =>\n FERRAMENTAS[nome]?.rotulo ?? humanizar(nome)\n\nexport const rotularExecucao = (nome: string) =>\n FERRAMENTAS[nome]?.executando ?? humanizar(nome)\n\nexport const iconeFerramenta = (nome: string): IconType =>\n FERRAMENTAS[nome]?.icone ?? TbTool\n\n/** Rotulos dos parametros, na linguagem do negocio. */\nconst CAMPOS: Record<string, string> = {\n business_partner_id: 'Parceiro de negócio',\n produto_id: 'Produto',\n safra_id: 'Safra',\n saldo_negociado: 'Saldo negociado',\n tipo_contrato: 'Tipo de contrato',\n contrato_venda: 'Contrato de venda',\n ordem_venda: 'Ordem de venda',\n id: 'Contrato',\n page_number: 'Página',\n page_size: 'Itens por página',\n sort_field: 'Ordenar por',\n sort_order: 'Ordem',\n filtro: 'Filtro',\n}\n\nexport const rotularCampo = (chave: string) => CAMPOS[chave] ?? humanizar(chave)\n\n/** Campos que sao referencia a um registro: exibidos como \"#123\". */\nconst REFERENCIAS = new Set([\n 'business_partner_id',\n 'produto_id',\n 'safra_id',\n 'contrato_venda',\n 'ordem_venda',\n 'id',\n])\n\nconst ORDENACAO: Record<string, string> = { ASC: 'Crescente', DESC: 'Decrescente' }\n\n/** Converte o valor tecnico no que o usuario espera ler. */\nexport const formatarValor = (chave: string, valor: unknown): string => {\n if (valor === null || valor === undefined || valor === '') {\n return '—'\n }\n if (chave === 'saldo_negociado' && typeof valor === 'number') {\n return formatQuantity(valor)\n }\n if (chave === 'sort_order' && typeof valor === 'string') {\n return ORDENACAO[valor] ?? humanizar(valor)\n }\n if (typeof valor === 'number') {\n return REFERENCIAS.has(chave) ? `#${valor}` : String(valor)\n }\n if (typeof valor === 'boolean') {\n return valor ? 'Sim' : 'Não'\n }\n if (typeof valor === 'string') {\n // Enums do backend chegam em CAIXA_ALTA_COM_UNDERSCORE.\n return /^[A-Z][A-Z0-9_]+$/.test(valor) ? humanizar(valor) : valor\n }\n if (Array.isArray(valor)) {\n return valor.length ? `${valor.length} item(ns)` : '—'\n }\n if (typeof valor === 'object') {\n const entradas = Object.entries(valor as Record<string, unknown>)\n if (!entradas.length) {\n return '—'\n }\n return entradas\n .map(([k, v]) => `${rotularCampo(k)}: ${formatarValor(k, v)}`)\n .join(', ')\n }\n return String(valor)\n}\n\n/** Pares (rotulo, valor) prontos para exibir, ignorando o que esta vazio. */\nexport const camposVisiveis = (args: Record<string, unknown>) =>\n Object.entries(args)\n .filter(([, valor]) => {\n if (valor === null || valor === undefined || valor === '') return false\n if (typeof valor === 'object' && !Array.isArray(valor)) {\n return Object.keys(valor as object).length > 0\n }\n return true\n })\n .map(([chave, valor]) => ({\n chave,\n rotulo: rotularCampo(chave),\n valor: formatarValor(chave, valor),\n }))\n"],"names":[],"mappings":";;AAsBA,MAAM,cAA0C;AAAA,EAC5C,mCAAmC;AAAA,IAC/B,QAAQ;AAAA,IACR,YAAY;AAAA,IACZ,OAAO;AAAA,EAAA;AAAA,EAEX,oCAAoC;AAAA,IAChC,QAAQ;AAAA,IACR,YAAY;AAAA,IACZ,OAAO;AAAA,EAAA;AAAA,EAEX,kCAAkC;AAAA,IAC9B,QAAQ;AAAA,IACR,YAAY;AAAA,IACZ,OAAO;AAAA,EAAA;AAEf;AAEA,MAAM,YAAY,CAAC,UACf,MACK,WAAW,KAAK,GAAG,EACnB,YAAA,EACA,QAAQ,MAAM,CAAC,UAAU,MAAM,aAAa;AAE9C,MAAM,oBAAoB,CAAC,SAC9B,YAAY,IAAI,GAAG,UAAU,UAAU,IAAI;AAExC,MAAM,kBAAkB,CAAC,SAC5B,YAAY,IAAI,GAAG,cAAc,UAAU,IAAI;AAE5C,MAAM,kBAAkB,CAAC,SAC5B,YAAY,IAAI,GAAG,SAAS;AAGhC,MAAM,SAAiC;AAAA,EACnC,qBAAqB;AAAA,EACrB,YAAY;AAAA,EACZ,UAAU;AAAA,EACV,iBAAiB;AAAA,EACjB,eAAe;AAAA,EACf,gBAAgB;AAAA,EAChB,aAAa;AAAA,EACb,IAAI;AAAA,EACJ,aAAa;AAAA,EACb,WAAW;AAAA,EACX,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ,QAAQ;AACZ;AAEO,MAAM,eAAe,CAAC,UAAkB,OAAO,KAAK,KAAK,UAAU,KAAK;AAG/E,MAAM,kCAAkB,IAAI;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACJ,CAAC;AAED,MAAM,YAAoC,EAAE,KAAK,aAAa,MAAM,cAAA;
|
|
1
|
+
{"version":3,"file":"toolPresentation.js","sources":["../../../../../../../lib/@ecme/views/concepts/ai/Chat/components/toolPresentation.ts"],"sourcesContent":["import { formatQuantity } from '@base/utils/functions/stringUtils'\nimport { TbFilePlus, TbListSearch, TbSearch, TbTool } from 'react-icons/tb'\nimport type { IconType } from 'react-icons'\n\n/**\n * Como as ferramentas do agente sao apresentadas ao usuario.\n *\n * Compartilhado pelo chip de execucao e pelo card de aprovacao, para os dois\n * falarem a mesma lingua: o usuario ve \"Consulta de contratos\" e \"Mercado\n * interno\", nunca `listar_contratos_armazenagem_graos` ou `MERCADO_INTERNO`.\n *\n * Tudo tem fallback: uma ferramenta ou campo novo no backend nao quebra a tela,\n * so aparece com o nome tecnico humanizado ate ganhar entrada aqui.\n */\n\ntype Ferramenta = {\n rotulo: string\n /** Frase no gerundio, para o estado \"executando\". */\n executando: string\n icone: IconType\n}\n\nconst FERRAMENTAS: Record<string, Ferramenta> = {\n buscar_contrato_armazenagem_graos: {\n rotulo: 'Consulta de contrato',\n executando: 'Consultando contrato',\n icone: TbSearch,\n },\n listar_contratos_armazenagem_graos: {\n rotulo: 'Consulta de contratos',\n executando: 'Consultando contratos',\n icone: TbListSearch,\n },\n criar_contrato_armazenagem_graos: {\n rotulo: 'Criação de contrato de armazenagem',\n executando: 'Criando contrato',\n icone: TbFilePlus,\n },\n}\n\nconst humanizar = (texto: string) =>\n texto\n .replaceAll('_', ' ')\n .toLowerCase()\n .replace(/^./, (letra) => letra.toUpperCase())\n\nexport const rotularFerramenta = (nome: string) =>\n FERRAMENTAS[nome]?.rotulo ?? humanizar(nome)\n\nexport const rotularExecucao = (nome: string) =>\n FERRAMENTAS[nome]?.executando ?? humanizar(nome)\n\nexport const iconeFerramenta = (nome: string): IconType =>\n FERRAMENTAS[nome]?.icone ?? TbTool\n\n/** Rotulos dos parametros, na linguagem do negocio. */\nconst CAMPOS: Record<string, string> = {\n business_partner_id: 'Parceiro de negócio',\n produto_id: 'Produto',\n safra_id: 'Safra',\n saldo_negociado: 'Saldo negociado',\n tipo_contrato: 'Tipo de contrato',\n contrato_venda: 'Contrato de venda',\n ordem_venda: 'Ordem de venda',\n id: 'Contrato',\n page_number: 'Página',\n page_size: 'Itens por página',\n sort_field: 'Ordenar por',\n sort_order: 'Ordem',\n filtro: 'Filtro',\n}\n\nexport const rotularCampo = (chave: string) => CAMPOS[chave] ?? humanizar(chave)\n\n/** Campos que sao referencia a um registro: exibidos como \"#123\". */\nconst REFERENCIAS = new Set([\n 'business_partner_id',\n 'produto_id',\n 'safra_id',\n 'contrato_venda',\n 'ordem_venda',\n 'id',\n])\n\nconst ORDENACAO: Record<string, string> = { ASC: 'Crescente', DESC: 'Decrescente' }\n\n/**\n * Opcoes dos campos que sao enum no backend.\n *\n * O rotulo sai do mesmo `humanizar` usado na exibicao, entao o formulario e o\n * card de confirmacao mostram exatamente o mesmo texto.\n */\nconst OPCOES: Record<string, string[]> = {\n tipo_contrato: ['MERCADO_INTERNO', 'MERCADO_EXTERNO'],\n}\n\nexport const opcoesDoCampo = (chave: string) =>\n (OPCOES[chave] ?? []).map((valor) => ({\n value: valor,\n label: humanizar(valor),\n }))\n\n/**\n * Que tipo de controle o campo pede no formulario de coleta.\n *\n * E so UX (teclado numerico, lista em vez de texto livre): os valores voltam\n * para o modelo, e e ele quem monta a chamada final — um tipo errado nao\n * quebra, so piora o preenchimento.\n */\nexport const tipoDoCampo = (chave: string): 'numero' | 'enum' | 'texto' => {\n if (OPCOES[chave]) return 'enum'\n if (REFERENCIAS.has(chave) || chave === 'saldo_negociado') return 'numero'\n return 'texto'\n}\n\n/** Converte o valor tecnico no que o usuario espera ler. */\nexport const formatarValor = (chave: string, valor: unknown): string => {\n if (valor === null || valor === undefined || valor === '') {\n return '—'\n }\n if (chave === 'saldo_negociado' && typeof valor === 'number') {\n return formatQuantity(valor)\n }\n if (chave === 'sort_order' && typeof valor === 'string') {\n return ORDENACAO[valor] ?? humanizar(valor)\n }\n if (typeof valor === 'number') {\n return REFERENCIAS.has(chave) ? `#${valor}` : String(valor)\n }\n if (typeof valor === 'boolean') {\n return valor ? 'Sim' : 'Não'\n }\n if (typeof valor === 'string') {\n // Enums do backend chegam em CAIXA_ALTA_COM_UNDERSCORE.\n return /^[A-Z][A-Z0-9_]+$/.test(valor) ? humanizar(valor) : valor\n }\n if (Array.isArray(valor)) {\n return valor.length ? `${valor.length} item(ns)` : '—'\n }\n if (typeof valor === 'object') {\n const entradas = Object.entries(valor as Record<string, unknown>)\n if (!entradas.length) {\n return '—'\n }\n return entradas\n .map(([k, v]) => `${rotularCampo(k)}: ${formatarValor(k, v)}`)\n .join(', ')\n }\n return String(valor)\n}\n\n/** Pares (rotulo, valor) prontos para exibir, ignorando o que esta vazio. */\nexport const camposVisiveis = (args: Record<string, unknown>) =>\n Object.entries(args)\n .filter(([, valor]) => {\n if (valor === null || valor === undefined || valor === '') return false\n if (typeof valor === 'object' && !Array.isArray(valor)) {\n return Object.keys(valor as object).length > 0\n }\n return true\n })\n .map(([chave, valor]) => ({\n chave,\n rotulo: rotularCampo(chave),\n valor: formatarValor(chave, valor),\n }))\n"],"names":[],"mappings":"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"}
|
|
@@ -93,6 +93,11 @@ const useChatSend = () => {
|
|
|
93
93
|
});
|
|
94
94
|
break;
|
|
95
95
|
}
|
|
96
|
+
case "input_required":
|
|
97
|
+
updateConversation(chatId, messageId, {
|
|
98
|
+
pendingInput: evento.data
|
|
99
|
+
});
|
|
100
|
+
break;
|
|
96
101
|
case "done":
|
|
97
102
|
updateConversation(chatId, messageId, {
|
|
98
103
|
content: evento.data.content
|
|
@@ -163,9 +168,24 @@ const useChatSend = () => {
|
|
|
163
168
|
{ texto: mensagem.content ?? "", tools: mensagem.tools ?? [] }
|
|
164
169
|
);
|
|
165
170
|
};
|
|
171
|
+
const handleDataSubmit = async (chatId, messageId, dados) => {
|
|
172
|
+
const mensagem = usGenerativeChatStore.getState().chatHistory.find((chat) => chat.id === chatId)?.conversation?.find((item) => item.id === messageId);
|
|
173
|
+
if (!mensagem?.pendingInput) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
setIsTyping(true);
|
|
177
|
+
updateConversation(chatId, messageId, { pendingInput: void 0 });
|
|
178
|
+
await consumirStream(
|
|
179
|
+
chatId,
|
|
180
|
+
messageId,
|
|
181
|
+
(onEvent) => resumeChat({ dados, conversationId: chatId, onEvent }),
|
|
182
|
+
{ texto: mensagem.content ?? "", tools: mensagem.tools ?? [] }
|
|
183
|
+
);
|
|
184
|
+
};
|
|
166
185
|
return {
|
|
167
186
|
handleSend,
|
|
168
|
-
handleApproval
|
|
187
|
+
handleApproval,
|
|
188
|
+
handleDataSubmit
|
|
169
189
|
};
|
|
170
190
|
};
|
|
171
191
|
export {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useChatSend.js","sources":["../../../../../../../lib/@ecme/views/concepts/ai/Chat/hooks/useChatSend.ts"],"sourcesContent":["import { usGenerativeChatStore } from '../store/generativeChatStore'\nimport { resumeChat, streamChat } from '@/services/AiService'\nimport { useAuth } from '@/auth'\nimport dayjs from 'dayjs'\nimport uniqueId from 'lodash/uniqueId'\nimport type { ChatEvent, Conversation, ToolCall } from '../types'\n\n/** Resposta genérica exibida no chat quando a comunicação com a IA falha. */\nconst AI_ERROR_MESSAGE =\n 'Desculpe, estou com um pequeno problema de comunicação e não consigo te atender no momento. Tente novamente em instantes.'\n\nconst useChatSend = () => {\n const { user } = useAuth()\n const {\n selectedConversation,\n setSelectedConversation,\n pushChatHistory,\n pushConversation,\n updateConversation,\n setIsTyping,\n } = usGenerativeChatStore()\n\n const creteMyMessage = (id: string, prompt: string) => {\n pushConversation(id, {\n id: uniqueId('ai-conversation-'),\n sender: {\n id: user.userId ?? 'me',\n name: user.userName ?? 'Você',\n avatarImageUrl: user.avatar ?? undefined,\n },\n content: prompt,\n timestamp: dayjs().toDate(),\n type: 'regular',\n isMyMessage: true,\n })\n }\n\n /**\n * Cria a mensagem da IA vazia e devolve o id, para o streaming preenchê-la.\n *\n * `fresh: false` é obrigatório: `fresh` liga a animação de máquina de\n * escrever do ChatCustomContent, que reinicia do zero a cada mudança de\n * conteúdo — com streaming, o texto ficaria piscando e voltando ao início.\n * A animação agora vem do servidor.\n */\n const createAiMessage = (\n chatId: string,\n options?: Partial<Conversation>,\n ) => {\n const messageId = uniqueId('ai-conversation-')\n pushConversation(chatId, {\n id: messageId,\n sender: {\n id: 'ai',\n name: 'Chat AI',\n avatarImageUrl: '/img/thumbs/ai.jpg',\n },\n content: '',\n timestamp: dayjs().toDate(),\n type: 'regular',\n isMyMessage: false,\n fresh: false,\n ...options,\n })\n return messageId\n }\n\n /**\n * Consome um stream escrevendo numa mensagem que já existe.\n *\n * Compartilhado pelo envio e pela retomada de aprovação: quando o usuário\n * aprova, a resposta continua na MESMA mensagem, então o acumulador de\n * texto e de ferramentas precisa partir do que já está lá.\n */\n const consumirStream = async (\n chatId: string,\n messageId: string,\n executar: (onEvent: (evento: ChatEvent) => void) => Promise<void>,\n inicial: { texto: string; tools: ToolCall[] },\n ) => {\n // Acumulados localmente: o store guarda o resultado, mas ler de volta a\n // cada token para concatenar seria uma corrida com o próprio set().\n let texto = inicial.texto\n let tools = inicial.tools\n\n const aplicar = (evento: ChatEvent) => {\n switch (evento.type) {\n case 'token':\n texto += evento.data.content\n updateConversation(chatId, messageId, { content: texto })\n break\n\n case 'tool_start':\n tools = [\n ...tools,\n {\n id: evento.data.id,\n name: evento.data.name,\n args: evento.data.args,\n running: true,\n },\n ]\n updateConversation(chatId, messageId, { tools })\n break\n\n case 'tool_end':\n tools = tools.map((tool) =>\n tool.id === evento.data.id\n ? {\n ...tool,\n running: false,\n awaitingApproval: false,\n result: evento.data.content,\n isError: evento.data.is_error,\n }\n : tool,\n )\n updateConversation(chatId, messageId, { tools })\n break\n\n case 'approval_required': {\n // O tool_start já marcou estas ferramentas como \"running\".\n // Elas não estão executando: estão paradas esperando o\n // clique — senão o chip giraria para sempre.\n const pausadas = new Set(\n evento.data.calls.map((call) => call.id),\n )\n tools = tools.map((tool) =>\n pausadas.has(tool.id)\n ? { ...tool, running: false, awaitingApproval: true }\n : tool,\n )\n updateConversation(chatId, messageId, {\n tools,\n pendingApproval: evento.data,\n })\n break\n }\n\n case 'done':\n // O done traz a resposta final completa e é a fonte\n // autoritativa — se algum token se perdeu, ele corrige.\n updateConversation(chatId, messageId, {\n content: evento.data.content,\n })\n break\n\n case 'error':\n updateConversation(chatId, messageId, {\n content: evento.data.message || AI_ERROR_MESSAGE,\n isError: true,\n })\n break\n }\n }\n\n try {\n await executar(aplicar)\n } catch {\n updateConversation(chatId, messageId, {\n content: texto || AI_ERROR_MESSAGE,\n isError: !texto,\n })\n } finally {\n setIsTyping(false)\n }\n }\n\n const sendMessage = async (chatId: string, prompt: string) => {\n const messageId = createAiMessage(chatId)\n await consumirStream(\n chatId,\n messageId,\n (onEvent) => streamChat({ prompt, conversationId: chatId, onEvent }),\n { texto: '', tools: [] },\n )\n }\n\n const createConversation = async (chatId: string, prompt: string) => {\n setSelectedConversation(chatId)\n await sendMessage(chatId, prompt)\n }\n\n const handleSend = async (prompt: string) => {\n setIsTyping(true)\n if (selectedConversation) {\n creteMyMessage(selectedConversation, prompt)\n await sendMessage(selectedConversation, prompt)\n } else {\n const newId = uniqueId('ai-chat-')\n pushChatHistory({\n id: newId,\n title: prompt,\n lastConversation: '',\n createdTime: dayjs().unix(),\n updatedTime: dayjs().unix(),\n enable: false,\n })\n creteMyMessage(newId, prompt)\n await createConversation(newId, prompt)\n }\n }\n\n /**\n * Aprova ou nega a operação pendente e retoma a conversa.\n *\n * Continua escrevendo na mesma mensagem: por isso o acumulador parte do\n * conteúdo e das ferramentas que já estão lá.\n */\n const handleApproval = async (\n chatId: string,\n messageId: string,\n approved: boolean,\n ) => {\n const mensagem = usGenerativeChatStore\n .getState()\n .chatHistory.find((chat) => chat.id === chatId)\n ?.conversation?.find((item) => item.id === messageId)\n\n if (!mensagem?.pendingApproval) {\n return\n }\n\n setIsTyping(true)\n // Some com o card antes da chamada: dois cliques rápidos enviariam duas\n // retomadas, e a segunda falha (o interrupt já foi consumido).\n updateConversation(chatId, messageId, { pendingApproval: undefined })\n\n await consumirStream(\n chatId,\n messageId,\n (onEvent) =>\n resumeChat({ approved, conversationId: chatId, onEvent }),\n { texto: mensagem.content ?? '', tools: mensagem.tools ?? [] },\n )\n }\n\n return {\n handleSend,\n handleApproval,\n }\n}\n\nexport default useChatSend\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useChatSend.js","sources":["../../../../../../../lib/@ecme/views/concepts/ai/Chat/hooks/useChatSend.ts"],"sourcesContent":["import { usGenerativeChatStore } from '../store/generativeChatStore'\nimport { resumeChat, streamChat } from '@/services/AiService'\nimport { useAuth } from '@/auth'\nimport dayjs from 'dayjs'\nimport uniqueId from 'lodash/uniqueId'\nimport type { ChatEvent, Conversation, ToolCall } from '../types'\n\n/** Resposta genérica exibida no chat quando a comunicação com a IA falha. */\nconst AI_ERROR_MESSAGE =\n 'Desculpe, estou com um pequeno problema de comunicação e não consigo te atender no momento. Tente novamente em instantes.'\n\nconst useChatSend = () => {\n const { user } = useAuth()\n const {\n selectedConversation,\n setSelectedConversation,\n pushChatHistory,\n pushConversation,\n updateConversation,\n setIsTyping,\n } = usGenerativeChatStore()\n\n const creteMyMessage = (id: string, prompt: string) => {\n pushConversation(id, {\n id: uniqueId('ai-conversation-'),\n sender: {\n id: user.userId ?? 'me',\n name: user.userName ?? 'Você',\n avatarImageUrl: user.avatar ?? undefined,\n },\n content: prompt,\n timestamp: dayjs().toDate(),\n type: 'regular',\n isMyMessage: true,\n })\n }\n\n /**\n * Cria a mensagem da IA vazia e devolve o id, para o streaming preenchê-la.\n *\n * `fresh: false` é obrigatório: `fresh` liga a animação de máquina de\n * escrever do ChatCustomContent, que reinicia do zero a cada mudança de\n * conteúdo — com streaming, o texto ficaria piscando e voltando ao início.\n * A animação agora vem do servidor.\n */\n const createAiMessage = (\n chatId: string,\n options?: Partial<Conversation>,\n ) => {\n const messageId = uniqueId('ai-conversation-')\n pushConversation(chatId, {\n id: messageId,\n sender: {\n id: 'ai',\n name: 'Chat AI',\n avatarImageUrl: '/img/thumbs/ai.jpg',\n },\n content: '',\n timestamp: dayjs().toDate(),\n type: 'regular',\n isMyMessage: false,\n fresh: false,\n ...options,\n })\n return messageId\n }\n\n /**\n * Consome um stream escrevendo numa mensagem que já existe.\n *\n * Compartilhado pelo envio e pela retomada de aprovação: quando o usuário\n * aprova, a resposta continua na MESMA mensagem, então o acumulador de\n * texto e de ferramentas precisa partir do que já está lá.\n */\n const consumirStream = async (\n chatId: string,\n messageId: string,\n executar: (onEvent: (evento: ChatEvent) => void) => Promise<void>,\n inicial: { texto: string; tools: ToolCall[] },\n ) => {\n // Acumulados localmente: o store guarda o resultado, mas ler de volta a\n // cada token para concatenar seria uma corrida com o próprio set().\n let texto = inicial.texto\n let tools = inicial.tools\n\n const aplicar = (evento: ChatEvent) => {\n switch (evento.type) {\n case 'token':\n texto += evento.data.content\n updateConversation(chatId, messageId, { content: texto })\n break\n\n case 'tool_start':\n tools = [\n ...tools,\n {\n id: evento.data.id,\n name: evento.data.name,\n args: evento.data.args,\n running: true,\n },\n ]\n updateConversation(chatId, messageId, { tools })\n break\n\n case 'tool_end':\n tools = tools.map((tool) =>\n tool.id === evento.data.id\n ? {\n ...tool,\n running: false,\n awaitingApproval: false,\n result: evento.data.content,\n isError: evento.data.is_error,\n }\n : tool,\n )\n updateConversation(chatId, messageId, { tools })\n break\n\n case 'approval_required': {\n // O tool_start já marcou estas ferramentas como \"running\".\n // Elas não estão executando: estão paradas esperando o\n // clique — senão o chip giraria para sempre.\n const pausadas = new Set(\n evento.data.calls.map((call) => call.id),\n )\n tools = tools.map((tool) =>\n pausadas.has(tool.id)\n ? { ...tool, running: false, awaitingApproval: true }\n : tool,\n )\n updateConversation(chatId, messageId, {\n tools,\n pendingApproval: evento.data,\n })\n break\n }\n\n case 'input_required':\n // Diferente da aprovação, aqui nenhuma ferramenta foi\n // pedida ao ERP: o agente só percebeu que faltam campos.\n // Por isso não há chip de ferramenta para ajustar.\n updateConversation(chatId, messageId, {\n pendingInput: evento.data,\n })\n break\n\n case 'done':\n // O done traz a resposta final completa e é a fonte\n // autoritativa — se algum token se perdeu, ele corrige.\n updateConversation(chatId, messageId, {\n content: evento.data.content,\n })\n break\n\n case 'error':\n updateConversation(chatId, messageId, {\n content: evento.data.message || AI_ERROR_MESSAGE,\n isError: true,\n })\n break\n }\n }\n\n try {\n await executar(aplicar)\n } catch {\n updateConversation(chatId, messageId, {\n content: texto || AI_ERROR_MESSAGE,\n isError: !texto,\n })\n } finally {\n setIsTyping(false)\n }\n }\n\n const sendMessage = async (chatId: string, prompt: string) => {\n const messageId = createAiMessage(chatId)\n await consumirStream(\n chatId,\n messageId,\n (onEvent) => streamChat({ prompt, conversationId: chatId, onEvent }),\n { texto: '', tools: [] },\n )\n }\n\n const createConversation = async (chatId: string, prompt: string) => {\n setSelectedConversation(chatId)\n await sendMessage(chatId, prompt)\n }\n\n const handleSend = async (prompt: string) => {\n setIsTyping(true)\n if (selectedConversation) {\n creteMyMessage(selectedConversation, prompt)\n await sendMessage(selectedConversation, prompt)\n } else {\n const newId = uniqueId('ai-chat-')\n pushChatHistory({\n id: newId,\n title: prompt,\n lastConversation: '',\n createdTime: dayjs().unix(),\n updatedTime: dayjs().unix(),\n enable: false,\n })\n creteMyMessage(newId, prompt)\n await createConversation(newId, prompt)\n }\n }\n\n /**\n * Aprova ou nega a operação pendente e retoma a conversa.\n *\n * Continua escrevendo na mesma mensagem: por isso o acumulador parte do\n * conteúdo e das ferramentas que já estão lá.\n */\n const handleApproval = async (\n chatId: string,\n messageId: string,\n approved: boolean,\n ) => {\n const mensagem = usGenerativeChatStore\n .getState()\n .chatHistory.find((chat) => chat.id === chatId)\n ?.conversation?.find((item) => item.id === messageId)\n\n if (!mensagem?.pendingApproval) {\n return\n }\n\n setIsTyping(true)\n // Some com o card antes da chamada: dois cliques rápidos enviariam duas\n // retomadas, e a segunda falha (o interrupt já foi consumido).\n updateConversation(chatId, messageId, { pendingApproval: undefined })\n\n await consumirStream(\n chatId,\n messageId,\n (onEvent) =>\n resumeChat({ approved, conversationId: chatId, onEvent }),\n { texto: mensagem.content ?? '', tools: mensagem.tools ?? [] },\n )\n }\n\n /**\n * Envia os dados que faltavam (ou o cancelamento) e retoma a conversa.\n *\n * Mesma mecânica do `handleApproval`: continua na MESMA mensagem, então o\n * acumulador parte do que já está lá.\n */\n const handleDataSubmit = async (\n chatId: string,\n messageId: string,\n dados: Record<string, unknown> | null,\n ) => {\n const mensagem = usGenerativeChatStore\n .getState()\n .chatHistory.find((chat) => chat.id === chatId)\n ?.conversation?.find((item) => item.id === messageId)\n\n if (!mensagem?.pendingInput) {\n return\n }\n\n setIsTyping(true)\n // Some com o formulário antes da chamada: dois envios mandariam duas\n // retomadas, e a segunda falha (o interrupt já foi consumido).\n updateConversation(chatId, messageId, { pendingInput: undefined })\n\n await consumirStream(\n chatId,\n messageId,\n (onEvent) => resumeChat({ dados, conversationId: chatId, onEvent }),\n { texto: mensagem.content ?? '', tools: mensagem.tools ?? [] },\n )\n }\n\n return {\n handleSend,\n handleApproval,\n handleDataSubmit,\n }\n}\n\nexport default useChatSend\n"],"names":[],"mappings":"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"}
|
|
@@ -8,9 +8,15 @@ export type StreamChatParams = {
|
|
|
8
8
|
signal?: AbortSignal;
|
|
9
9
|
onEvent: (evento: ChatEvent) => void;
|
|
10
10
|
};
|
|
11
|
+
/**
|
|
12
|
+
* Resposta a pausa atual do agente. Ha duas, e um endpoint so:
|
|
13
|
+
* - `approved` responde ao `approval_required` (true aprova, false nega).
|
|
14
|
+
* - `dados` responde ao `input_required`; `null` = o usuario fechou o
|
|
15
|
+
* formulario sem preencher.
|
|
16
|
+
*/
|
|
11
17
|
export type ResumeChatParams = {
|
|
12
|
-
|
|
13
|
-
|
|
18
|
+
approved?: boolean;
|
|
19
|
+
dados?: Record<string, unknown> | null;
|
|
14
20
|
conversationId?: string;
|
|
15
21
|
signal?: AbortSignal;
|
|
16
22
|
onEvent: (evento: ChatEvent) => void;
|
|
@@ -23,12 +29,15 @@ export type ResumeChatParams = {
|
|
|
23
29
|
*/
|
|
24
30
|
export declare function streamChat({ prompt, conversationId, signal, onEvent, }: StreamChatParams): Promise<void>;
|
|
25
31
|
/**
|
|
26
|
-
* Retoma uma conversa parada
|
|
32
|
+
* Retoma uma conversa parada numa pausa (aprovacao ou coleta de dados).
|
|
27
33
|
*
|
|
28
34
|
* Devolve o mesmo tipo de stream do `streamChat`, e a resposta continua na
|
|
29
35
|
* MESMA mensagem do chat — o backend so volta do ponto onde parou.
|
|
36
|
+
*
|
|
37
|
+
* O `JSON.stringify` descarta chaves `undefined`, entao o backend recebe apenas
|
|
38
|
+
* o campo da pausa que esta sendo respondida.
|
|
30
39
|
*/
|
|
31
|
-
export declare function resumeChat({ approved, conversationId, signal, onEvent, }: ResumeChatParams): Promise<void>;
|
|
40
|
+
export declare function resumeChat({ approved, dados, conversationId, signal, onEvent, }: ResumeChatParams): Promise<void>;
|
|
32
41
|
export declare function apiGetImages<T, U extends Record<string, unknown>>(params: U): Promise<T>;
|
|
33
42
|
export declare function apiPostImages<T, U extends Record<string, unknown>>(data: U): Promise<T>;
|
|
34
43
|
//# sourceMappingURL=AiService.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AiService.d.ts","sourceRoot":"","sources":["../../../../lib/@ecme/services/AiService.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAA;AA0B/D,eAAO,MAAM,UAAU,QAAgB,CAAA;AAcvC,wBAAsB,iBAAiB,CAAC,CAAC,EAAE,cAAc,CAAC,EAAE,MAAM,cAMjE;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC3B,MAAM,EAAE,MAAM,CAAA;IACd,mEAAmE;IACnE,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB,OAAO,EAAE,CAAC,MAAM,EAAE,SAAS,KAAK,IAAI,CAAA;CACvC,CAAA;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC3B,
|
|
1
|
+
{"version":3,"file":"AiService.d.ts","sourceRoot":"","sources":["../../../../lib/@ecme/services/AiService.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAA;AA0B/D,eAAO,MAAM,UAAU,QAAgB,CAAA;AAcvC,wBAAsB,iBAAiB,CAAC,CAAC,EAAE,cAAc,CAAC,EAAE,MAAM,cAMjE;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC3B,MAAM,EAAE,MAAM,CAAA;IACd,mEAAmE;IACnE,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB,OAAO,EAAE,CAAC,MAAM,EAAE,SAAS,KAAK,IAAI,CAAA;CACvC,CAAA;AAED;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC3B,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;IACtC,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB,OAAO,EAAE,CAAC,MAAM,EAAE,SAAS,KAAK,IAAI,CAAA;CACvC,CAAA;AAED;;;;;GAKG;AACH,wBAAsB,UAAU,CAAC,EAC7B,MAAM,EACN,cAAc,EACd,MAAM,EACN,OAAO,GACV,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAOlC;AAED;;;;;;;;GAQG;AACH,wBAAsB,UAAU,CAAC,EAC7B,QAAQ,EACR,KAAK,EACL,cAAc,EACd,MAAM,EACN,OAAO,GACV,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAOlC;AAiCD,wBAAsB,YAAY,CAAC,CAAC,EAAE,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnE,MAAM,EAAE,CAAC,cAOZ;AAED,wBAAsB,aAAa,CAAC,CAAC,EAAE,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACpE,IAAI,EAAE,CAAC,cAOV"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChatView.d.ts","sourceRoot":"","sources":["../../../../../../../../lib/@ecme/views/concepts/ai/Chat/components/ChatView.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ChatView.d.ts","sourceRoot":"","sources":["../../../../../../../../lib/@ecme/views/concepts/ai/Chat/components/ChatView.tsx"],"names":[],"mappings":"AAiCA,QAAA,MAAM,QAAQ,+CA4Jb,CAAA;AAED,eAAe,QAAQ,CAAA"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { PendingInput } from '../types';
|
|
2
|
+
type DataRequestCardProps = {
|
|
3
|
+
input: PendingInput;
|
|
4
|
+
/** `null` = o usuario fechou sem preencher. */
|
|
5
|
+
onSubmit: (dados: Record<string, unknown> | null) => void;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Formulario dos campos que faltam para criar um registro no ERP.
|
|
9
|
+
*
|
|
10
|
+
* O agente fica PARADO enquanto este card estiver na tela — ele so volta a
|
|
11
|
+
* responder depois do envio. Por isso divide o visual do ApprovalCard: sao as
|
|
12
|
+
* duas paradas do fluxo, e o usuario deve reconhece-las como a mesma coisa.
|
|
13
|
+
*
|
|
14
|
+
* Os valores vao para o modelo, que monta a chamada final. Por isso o envio nao
|
|
15
|
+
* valida tipo: o `type=number` e o select sao conveniencia de digitacao, nao
|
|
16
|
+
* regra de negocio — quem valida de verdade e o backend do ERP.
|
|
17
|
+
*/
|
|
18
|
+
declare const DataRequestCard: ({ input, onSubmit }: DataRequestCardProps) => import("react/jsx-runtime").JSX.Element;
|
|
19
|
+
export default DataRequestCard;
|
|
20
|
+
//# sourceMappingURL=DataRequestCard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DataRequestCard.d.ts","sourceRoot":"","sources":["../../../../../../../../lib/@ecme/views/concepts/ai/Chat/components/DataRequestCard.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AAE5C,KAAK,oBAAoB,GAAG;IACxB,KAAK,EAAE,YAAY,CAAA;IACnB,+CAA+C;IAC/C,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,KAAK,IAAI,CAAA;CAC5D,CAAA;AAED;;;;;;;;;;GAUG;AACH,QAAA,MAAM,eAAe,GAAI,qBAAqB,oBAAoB,4CAoHjE,CAAA;AAED,eAAe,eAAe,CAAA"}
|
|
@@ -3,6 +3,18 @@ export declare const rotularFerramenta: (nome: string) => string;
|
|
|
3
3
|
export declare const rotularExecucao: (nome: string) => string;
|
|
4
4
|
export declare const iconeFerramenta: (nome: string) => IconType;
|
|
5
5
|
export declare const rotularCampo: (chave: string) => string;
|
|
6
|
+
export declare const opcoesDoCampo: (chave: string) => {
|
|
7
|
+
value: string;
|
|
8
|
+
label: string;
|
|
9
|
+
}[];
|
|
10
|
+
/**
|
|
11
|
+
* Que tipo de controle o campo pede no formulario de coleta.
|
|
12
|
+
*
|
|
13
|
+
* E so UX (teclado numerico, lista em vez de texto livre): os valores voltam
|
|
14
|
+
* para o modelo, e e ele quem monta a chamada final — um tipo errado nao
|
|
15
|
+
* quebra, so piora o preenchimento.
|
|
16
|
+
*/
|
|
17
|
+
export declare const tipoDoCampo: (chave: string) => "numero" | "enum" | "texto";
|
|
6
18
|
/** Converte o valor tecnico no que o usuario espera ler. */
|
|
7
19
|
export declare const formatarValor: (chave: string, valor: unknown) => string;
|
|
8
20
|
/** Pares (rotulo, valor) prontos para exibir, ignorando o que esta vazio. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toolPresentation.d.ts","sourceRoot":"","sources":["../../../../../../../../lib/@ecme/views/concepts/ai/Chat/components/toolPresentation.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AA4C3C,eAAO,MAAM,iBAAiB,GAAI,MAAM,MAAM,WACE,CAAA;AAEhD,eAAO,MAAM,eAAe,GAAI,MAAM,MAAM,WACQ,CAAA;AAEpD,eAAO,MAAM,eAAe,GAAI,MAAM,MAAM,KAAG,QACT,CAAA;AAmBtC,eAAO,MAAM,YAAY,GAAI,OAAO,MAAM,WAAsC,CAAA;
|
|
1
|
+
{"version":3,"file":"toolPresentation.d.ts","sourceRoot":"","sources":["../../../../../../../../lib/@ecme/views/concepts/ai/Chat/components/toolPresentation.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AA4C3C,eAAO,MAAM,iBAAiB,GAAI,MAAM,MAAM,WACE,CAAA;AAEhD,eAAO,MAAM,eAAe,GAAI,MAAM,MAAM,WACQ,CAAA;AAEpD,eAAO,MAAM,eAAe,GAAI,MAAM,MAAM,KAAG,QACT,CAAA;AAmBtC,eAAO,MAAM,YAAY,GAAI,OAAO,MAAM,WAAsC,CAAA;AAwBhF,eAAO,MAAM,aAAa,GAAI,OAAO,MAAM;;;GAIpC,CAAA;AAEP;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,GAAI,OAAO,MAAM,KAAG,QAAQ,GAAG,MAAM,GAAG,OAI/D,CAAA;AAED,4DAA4D;AAC5D,eAAO,MAAM,aAAa,GAAI,OAAO,MAAM,EAAE,OAAO,OAAO,KAAG,MAiC7D,CAAA;AAED,6EAA6E;AAC7E,eAAO,MAAM,cAAc,GAAI,MAAM,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;;;GAajD,CAAA"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
declare const useChatSend: () => {
|
|
2
2
|
handleSend: (prompt: string) => Promise<void>;
|
|
3
3
|
handleApproval: (chatId: string, messageId: string, approved: boolean) => Promise<void>;
|
|
4
|
+
handleDataSubmit: (chatId: string, messageId: string, dados: Record<string, unknown> | null) => Promise<void>;
|
|
4
5
|
};
|
|
5
6
|
export default useChatSend;
|
|
6
7
|
//# sourceMappingURL=useChatSend.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useChatSend.d.ts","sourceRoot":"","sources":["../../../../../../../../lib/@ecme/views/concepts/ai/Chat/hooks/useChatSend.ts"],"names":[],"mappings":"AAWA,QAAA,MAAM,WAAW;
|
|
1
|
+
{"version":3,"file":"useChatSend.d.ts","sourceRoot":"","sources":["../../../../../../../../lib/@ecme/views/concepts/ai/Chat/hooks/useChatSend.ts"],"names":[],"mappings":"AAWA,QAAA,MAAM,WAAW;yBAqLqB,MAAM;6BA2B5B,MAAM,aACH,MAAM,YACP,OAAO;+BAgCT,MAAM,aACH,MAAM,SACV,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;CA6B5C,CAAA;AAED,eAAe,WAAW,CAAA"}
|
|
@@ -26,6 +26,16 @@ export type PendingApproval = {
|
|
|
26
26
|
args: Record<string, unknown>;
|
|
27
27
|
}[];
|
|
28
28
|
};
|
|
29
|
+
/**
|
|
30
|
+
* Criacao parada por falta de dados, esperando o usuario preencher.
|
|
31
|
+
*
|
|
32
|
+
* `campos` traz os nomes TECNICOS dos parametros que faltam (ex.:
|
|
33
|
+
* `produto_id`); quem exibe traduz com `rotularCampo`.
|
|
34
|
+
*/
|
|
35
|
+
export type PendingInput = {
|
|
36
|
+
id: string;
|
|
37
|
+
campos: string[];
|
|
38
|
+
};
|
|
29
39
|
export type Conversation = {
|
|
30
40
|
id: string;
|
|
31
41
|
sender: {
|
|
@@ -55,6 +65,8 @@ export type Conversation = {
|
|
|
55
65
|
tools?: ToolCall[];
|
|
56
66
|
/** Preenchido quando o agente pausou esperando aprovacao humana. */
|
|
57
67
|
pendingApproval?: PendingApproval;
|
|
68
|
+
/** Preenchido quando o agente pausou pedindo os dados que faltam. */
|
|
69
|
+
pendingInput?: PendingInput;
|
|
58
70
|
};
|
|
59
71
|
/**
|
|
60
72
|
* Eventos do stream de /ai/chat/stream.
|
|
@@ -102,6 +114,18 @@ export type ChatEvent = {
|
|
|
102
114
|
args: Record<string, unknown>;
|
|
103
115
|
}[];
|
|
104
116
|
};
|
|
117
|
+
} | {
|
|
118
|
+
/**
|
|
119
|
+
* Faltam campos para criar: o agente parou esperando o formulario.
|
|
120
|
+
*
|
|
121
|
+
* Como no `approval_required`, o `done` NAO vem — o turno esta
|
|
122
|
+
* pausado. Retomar e chamar `resumeChat({ dados })`.
|
|
123
|
+
*/
|
|
124
|
+
type: 'input_required';
|
|
125
|
+
data: {
|
|
126
|
+
id: string;
|
|
127
|
+
campos: string[];
|
|
128
|
+
};
|
|
105
129
|
} | {
|
|
106
130
|
type: 'done';
|
|
107
131
|
data: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../../../../lib/@ecme/views/concepts/ai/Chat/types.ts"],"names":[],"mappings":"AAAA,6DAA6D;AAC7D,MAAM,MAAM,QAAQ,GAAG;IACnB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC7B,2DAA2D;IAC3D,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,kDAAkD;IAClD,OAAO,EAAE,OAAO,CAAA;IAChB;;;;;;OAMG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAA;CAC7B,CAAA;AAED,wEAAwE;AACxE,MAAM,MAAM,eAAe,GAAG;IAC1B,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,EAAE,CAAA;CACvE,CAAA;AAED,MAAM,MAAM,YAAY,GAAG;IACvB,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,EAAE;QACJ,EAAE,EAAE,MAAM,CAAA;QACV,IAAI,EAAE,MAAM,CAAA;QACZ,cAAc,CAAC,EAAE,MAAM,CAAA;KAC1B,CAAA;IACD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,SAAS,CAAC,EAAE,IAAI,CAAA;IAChB,IAAI,EAAE,SAAS,GAAG,OAAO,GAAG,SAAS,GAAG,SAAS,CAAA;IACjD,WAAW,CAAC,EAAE,KAAK,CAAC;QAChB,IAAI,EAAE,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,CAAA;QAC1C,MAAM,EAAE,IAAI,CAAA;QACZ,QAAQ,EAAE,MAAM,CAAA;KACnB,CAAC,CAAA;IACF,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,uDAAuD;IACvD,KAAK,CAAC,EAAE,QAAQ,EAAE,CAAA;IAClB,oEAAoE;IACpE,eAAe,CAAC,EAAE,eAAe,CAAA;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../../../../lib/@ecme/views/concepts/ai/Chat/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,7 +5,7 @@ import type { ChatEvent } from '@/views/concepts/ai/Chat/types'
|
|
|
5
5
|
import type { AxiosRequestConfig } from 'axios'
|
|
6
6
|
|
|
7
7
|
const AI_DEV_URL = 'http://localhost:8000'
|
|
8
|
-
const AI_BUILD_URL = 'https://orbe-ai
|
|
8
|
+
const AI_BUILD_URL = 'https://orbe-ai.apps.qas.orbeagro.com.br'
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Host da API de IA.
|
|
@@ -57,9 +57,15 @@ export type StreamChatParams = {
|
|
|
57
57
|
onEvent: (evento: ChatEvent) => void
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Resposta a pausa atual do agente. Ha duas, e um endpoint so:
|
|
62
|
+
* - `approved` responde ao `approval_required` (true aprova, false nega).
|
|
63
|
+
* - `dados` responde ao `input_required`; `null` = o usuario fechou o
|
|
64
|
+
* formulario sem preencher.
|
|
65
|
+
*/
|
|
60
66
|
export type ResumeChatParams = {
|
|
61
|
-
|
|
62
|
-
|
|
67
|
+
approved?: boolean
|
|
68
|
+
dados?: Record<string, unknown> | null
|
|
63
69
|
conversationId?: string
|
|
64
70
|
signal?: AbortSignal
|
|
65
71
|
onEvent: (evento: ChatEvent) => void
|
|
@@ -86,20 +92,24 @@ export async function streamChat({
|
|
|
86
92
|
}
|
|
87
93
|
|
|
88
94
|
/**
|
|
89
|
-
* Retoma uma conversa parada
|
|
95
|
+
* Retoma uma conversa parada numa pausa (aprovacao ou coleta de dados).
|
|
90
96
|
*
|
|
91
97
|
* Devolve o mesmo tipo de stream do `streamChat`, e a resposta continua na
|
|
92
98
|
* MESMA mensagem do chat — o backend so volta do ponto onde parou.
|
|
99
|
+
*
|
|
100
|
+
* O `JSON.stringify` descarta chaves `undefined`, entao o backend recebe apenas
|
|
101
|
+
* o campo da pausa que esta sendo respondida.
|
|
93
102
|
*/
|
|
94
103
|
export async function resumeChat({
|
|
95
104
|
approved,
|
|
105
|
+
dados,
|
|
96
106
|
conversationId,
|
|
97
107
|
signal,
|
|
98
108
|
onEvent,
|
|
99
109
|
}: ResumeChatParams): Promise<void> {
|
|
100
110
|
await postarEConsumir(
|
|
101
111
|
'/ai/chat/resume',
|
|
102
|
-
{ approved, conversation_id: conversationId },
|
|
112
|
+
{ approved, dados, conversation_id: conversationId },
|
|
103
113
|
onEvent,
|
|
104
114
|
signal,
|
|
105
115
|
)
|
|
@@ -9,6 +9,7 @@ import AiThinkingIndicator from './AiThinkingIndicator'
|
|
|
9
9
|
import AiErrorMessage from './AiErrorMessage'
|
|
10
10
|
import ToolCallChip from './ToolCallChip'
|
|
11
11
|
import ApprovalCard from './ApprovalCard'
|
|
12
|
+
import DataRequestCard from './DataRequestCard'
|
|
12
13
|
import { usGenerativeChatStore } from '../store/generativeChatStore'
|
|
13
14
|
import useChatSend from '../hooks/useChatSend'
|
|
14
15
|
import type { Conversation } from '../types'
|
|
@@ -34,7 +35,7 @@ const ChatView = () => {
|
|
|
34
35
|
const scrollRef = useRef<ScrollBarRef>(null)
|
|
35
36
|
const { selectedConversation, chatHistory, isTyping, disabledChatFresh } =
|
|
36
37
|
usGenerativeChatStore()
|
|
37
|
-
const { handleSend, handleApproval } = useChatSend()
|
|
38
|
+
const { handleSend, handleApproval, handleDataSubmit } = useChatSend()
|
|
38
39
|
|
|
39
40
|
const scrollToBottom = () => {
|
|
40
41
|
if (scrollRef.current?.getScrollElement) {
|
|
@@ -105,9 +106,10 @@ const ChatView = () => {
|
|
|
105
106
|
}
|
|
106
107
|
|
|
107
108
|
if (message.sender.id === 'ai') {
|
|
108
|
-
// As ferramentas e
|
|
109
|
+
// As ferramentas e as pausas chegam pelo streaming e
|
|
109
110
|
// vivem no Conversation; o tipo do ChatBox nao os conhece.
|
|
110
|
-
const { tools, pendingApproval } =
|
|
111
|
+
const { tools, pendingApproval, pendingInput } =
|
|
112
|
+
message as Conversation
|
|
111
113
|
// Enquanto as ferramentas rodam o conteudo ainda e '':
|
|
112
114
|
// sem esta guarda sobraria um balao cinza vazio.
|
|
113
115
|
const temTexto = Boolean(
|
|
@@ -134,6 +136,18 @@ const ChatView = () => {
|
|
|
134
136
|
/>
|
|
135
137
|
</div>
|
|
136
138
|
)}
|
|
139
|
+
{pendingInput && (
|
|
140
|
+
<DataRequestCard
|
|
141
|
+
input={pendingInput}
|
|
142
|
+
onSubmit={(dados) =>
|
|
143
|
+
handleDataSubmit(
|
|
144
|
+
selectedConversation,
|
|
145
|
+
message.id,
|
|
146
|
+
dados,
|
|
147
|
+
)
|
|
148
|
+
}
|
|
149
|
+
/>
|
|
150
|
+
)}
|
|
137
151
|
{pendingApproval && (
|
|
138
152
|
<ApprovalCard
|
|
139
153
|
approval={pendingApproval}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { useState } from 'react'
|
|
2
|
+
import { TbForms } from 'react-icons/tb'
|
|
3
|
+
import Button from '@/components/ui/Button'
|
|
4
|
+
import Input from '@/components/ui/Input'
|
|
5
|
+
import Select from '@/components/ui/Select'
|
|
6
|
+
import { opcoesDoCampo, rotularCampo, tipoDoCampo } from './toolPresentation'
|
|
7
|
+
import type { PendingInput } from '../types'
|
|
8
|
+
|
|
9
|
+
type DataRequestCardProps = {
|
|
10
|
+
input: PendingInput
|
|
11
|
+
/** `null` = o usuario fechou sem preencher. */
|
|
12
|
+
onSubmit: (dados: Record<string, unknown> | null) => void
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Formulario dos campos que faltam para criar um registro no ERP.
|
|
17
|
+
*
|
|
18
|
+
* O agente fica PARADO enquanto este card estiver na tela — ele so volta a
|
|
19
|
+
* responder depois do envio. Por isso divide o visual do ApprovalCard: sao as
|
|
20
|
+
* duas paradas do fluxo, e o usuario deve reconhece-las como a mesma coisa.
|
|
21
|
+
*
|
|
22
|
+
* Os valores vao para o modelo, que monta a chamada final. Por isso o envio nao
|
|
23
|
+
* valida tipo: o `type=number` e o select sao conveniencia de digitacao, nao
|
|
24
|
+
* regra de negocio — quem valida de verdade e o backend do ERP.
|
|
25
|
+
*/
|
|
26
|
+
const DataRequestCard = ({ input, onSubmit }: DataRequestCardProps) => {
|
|
27
|
+
const [valores, setValores] = useState<Record<string, string>>({})
|
|
28
|
+
const [enviando, setEnviando] = useState<'enviar' | 'cancelar' | null>(null)
|
|
29
|
+
|
|
30
|
+
const preencher = (campo: string, valor: string) =>
|
|
31
|
+
setValores((atual) => ({ ...atual, [campo]: valor }))
|
|
32
|
+
|
|
33
|
+
// Exigir todos evita uma ida e volta: faltando um, o agente pediria de novo.
|
|
34
|
+
const completo = input.campos.every((campo) => valores[campo]?.trim())
|
|
35
|
+
|
|
36
|
+
const enviar = () => {
|
|
37
|
+
setEnviando('enviar')
|
|
38
|
+
// Numeros vao como number; o resto como texto. O modelo aceita os dois,
|
|
39
|
+
// mas o tipo certo evita que ele precise adivinhar.
|
|
40
|
+
const dados = Object.fromEntries(
|
|
41
|
+
input.campos.map((campo) => {
|
|
42
|
+
const bruto = valores[campo].trim()
|
|
43
|
+
const numero =
|
|
44
|
+
tipoDoCampo(campo) === 'numero' ? Number(bruto) : NaN
|
|
45
|
+
return [campo, Number.isNaN(numero) ? bruto : numero]
|
|
46
|
+
}),
|
|
47
|
+
)
|
|
48
|
+
onSubmit(dados)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const cancelar = () => {
|
|
52
|
+
setEnviando('cancelar')
|
|
53
|
+
onSubmit(null)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<div className="not-prose mt-1 overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm dark:border-gray-700 dark:bg-gray-800">
|
|
58
|
+
<div className="flex items-center gap-2 border-b border-gray-200 px-4 py-2.5 dark:border-gray-700">
|
|
59
|
+
<TbForms size={16} className="text-gray-400 dark:text-gray-500" />
|
|
60
|
+
<span className="text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">
|
|
61
|
+
Dados necessários
|
|
62
|
+
</span>
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
<div className="px-4 py-3.5">
|
|
66
|
+
<div className="mb-3 text-sm text-gray-500 dark:text-gray-400">
|
|
67
|
+
Preencha os campos abaixo para eu continuar.
|
|
68
|
+
</div>
|
|
69
|
+
|
|
70
|
+
<div className="flex flex-col gap-3">
|
|
71
|
+
{input.campos.map((campo) => {
|
|
72
|
+
const rotulo = rotularCampo(campo)
|
|
73
|
+
const opcoes = opcoesDoCampo(campo)
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<label key={campo} className="flex flex-col gap-1">
|
|
77
|
+
<span className="text-sm text-gray-600 dark:text-gray-300">
|
|
78
|
+
{rotulo}
|
|
79
|
+
</span>
|
|
80
|
+
{opcoes.length ? (
|
|
81
|
+
<Select
|
|
82
|
+
options={opcoes}
|
|
83
|
+
value={opcoes.filter(
|
|
84
|
+
(opcao) =>
|
|
85
|
+
opcao.value === valores[campo],
|
|
86
|
+
)}
|
|
87
|
+
placeholder={`Selecione ${rotulo.toLowerCase()}`}
|
|
88
|
+
isDisabled={enviando !== null}
|
|
89
|
+
onChange={(opcao) =>
|
|
90
|
+
preencher(
|
|
91
|
+
campo,
|
|
92
|
+
(opcao as { value: string } | null)
|
|
93
|
+
?.value ?? '',
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
/>
|
|
97
|
+
) : (
|
|
98
|
+
<Input
|
|
99
|
+
type={
|
|
100
|
+
tipoDoCampo(campo) === 'numero'
|
|
101
|
+
? 'number'
|
|
102
|
+
: 'text'
|
|
103
|
+
}
|
|
104
|
+
value={valores[campo] ?? ''}
|
|
105
|
+
disabled={enviando !== null}
|
|
106
|
+
placeholder={`Informe ${rotulo.toLowerCase()}`}
|
|
107
|
+
onChange={(evento) =>
|
|
108
|
+
preencher(campo, evento.target.value)
|
|
109
|
+
}
|
|
110
|
+
/>
|
|
111
|
+
)}
|
|
112
|
+
</label>
|
|
113
|
+
)
|
|
114
|
+
})}
|
|
115
|
+
</div>
|
|
116
|
+
|
|
117
|
+
<div className="mt-4 flex items-center gap-2">
|
|
118
|
+
<Button
|
|
119
|
+
variant="solid"
|
|
120
|
+
size="sm"
|
|
121
|
+
loading={enviando === 'enviar'}
|
|
122
|
+
disabled={!completo || enviando !== null}
|
|
123
|
+
onClick={enviar}
|
|
124
|
+
>
|
|
125
|
+
Enviar
|
|
126
|
+
</Button>
|
|
127
|
+
<Button
|
|
128
|
+
size="sm"
|
|
129
|
+
loading={enviando === 'cancelar'}
|
|
130
|
+
disabled={enviando !== null}
|
|
131
|
+
onClick={cancelar}
|
|
132
|
+
>
|
|
133
|
+
Cancelar
|
|
134
|
+
</Button>
|
|
135
|
+
<span className="ml-auto text-xs text-gray-400 dark:text-gray-500">
|
|
136
|
+
Você ainda confirma antes de gravar
|
|
137
|
+
</span>
|
|
138
|
+
</div>
|
|
139
|
+
</div>
|
|
140
|
+
</div>
|
|
141
|
+
)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export default DataRequestCard
|
|
@@ -84,6 +84,35 @@ const REFERENCIAS = new Set([
|
|
|
84
84
|
|
|
85
85
|
const ORDENACAO: Record<string, string> = { ASC: 'Crescente', DESC: 'Decrescente' }
|
|
86
86
|
|
|
87
|
+
/**
|
|
88
|
+
* Opcoes dos campos que sao enum no backend.
|
|
89
|
+
*
|
|
90
|
+
* O rotulo sai do mesmo `humanizar` usado na exibicao, entao o formulario e o
|
|
91
|
+
* card de confirmacao mostram exatamente o mesmo texto.
|
|
92
|
+
*/
|
|
93
|
+
const OPCOES: Record<string, string[]> = {
|
|
94
|
+
tipo_contrato: ['MERCADO_INTERNO', 'MERCADO_EXTERNO'],
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export const opcoesDoCampo = (chave: string) =>
|
|
98
|
+
(OPCOES[chave] ?? []).map((valor) => ({
|
|
99
|
+
value: valor,
|
|
100
|
+
label: humanizar(valor),
|
|
101
|
+
}))
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Que tipo de controle o campo pede no formulario de coleta.
|
|
105
|
+
*
|
|
106
|
+
* E so UX (teclado numerico, lista em vez de texto livre): os valores voltam
|
|
107
|
+
* para o modelo, e e ele quem monta a chamada final — um tipo errado nao
|
|
108
|
+
* quebra, so piora o preenchimento.
|
|
109
|
+
*/
|
|
110
|
+
export const tipoDoCampo = (chave: string): 'numero' | 'enum' | 'texto' => {
|
|
111
|
+
if (OPCOES[chave]) return 'enum'
|
|
112
|
+
if (REFERENCIAS.has(chave) || chave === 'saldo_negociado') return 'numero'
|
|
113
|
+
return 'texto'
|
|
114
|
+
}
|
|
115
|
+
|
|
87
116
|
/** Converte o valor tecnico no que o usuario espera ler. */
|
|
88
117
|
export const formatarValor = (chave: string, valor: unknown): string => {
|
|
89
118
|
if (valor === null || valor === undefined || valor === '') {
|
|
@@ -137,6 +137,15 @@ const useChatSend = () => {
|
|
|
137
137
|
break
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
+
case 'input_required':
|
|
141
|
+
// Diferente da aprovação, aqui nenhuma ferramenta foi
|
|
142
|
+
// pedida ao ERP: o agente só percebeu que faltam campos.
|
|
143
|
+
// Por isso não há chip de ferramenta para ajustar.
|
|
144
|
+
updateConversation(chatId, messageId, {
|
|
145
|
+
pendingInput: evento.data,
|
|
146
|
+
})
|
|
147
|
+
break
|
|
148
|
+
|
|
140
149
|
case 'done':
|
|
141
150
|
// O done traz a resposta final completa e é a fonte
|
|
142
151
|
// autoritativa — se algum token se perdeu, ele corrige.
|
|
@@ -235,9 +244,43 @@ const useChatSend = () => {
|
|
|
235
244
|
)
|
|
236
245
|
}
|
|
237
246
|
|
|
247
|
+
/**
|
|
248
|
+
* Envia os dados que faltavam (ou o cancelamento) e retoma a conversa.
|
|
249
|
+
*
|
|
250
|
+
* Mesma mecânica do `handleApproval`: continua na MESMA mensagem, então o
|
|
251
|
+
* acumulador parte do que já está lá.
|
|
252
|
+
*/
|
|
253
|
+
const handleDataSubmit = async (
|
|
254
|
+
chatId: string,
|
|
255
|
+
messageId: string,
|
|
256
|
+
dados: Record<string, unknown> | null,
|
|
257
|
+
) => {
|
|
258
|
+
const mensagem = usGenerativeChatStore
|
|
259
|
+
.getState()
|
|
260
|
+
.chatHistory.find((chat) => chat.id === chatId)
|
|
261
|
+
?.conversation?.find((item) => item.id === messageId)
|
|
262
|
+
|
|
263
|
+
if (!mensagem?.pendingInput) {
|
|
264
|
+
return
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
setIsTyping(true)
|
|
268
|
+
// Some com o formulário antes da chamada: dois envios mandariam duas
|
|
269
|
+
// retomadas, e a segunda falha (o interrupt já foi consumido).
|
|
270
|
+
updateConversation(chatId, messageId, { pendingInput: undefined })
|
|
271
|
+
|
|
272
|
+
await consumirStream(
|
|
273
|
+
chatId,
|
|
274
|
+
messageId,
|
|
275
|
+
(onEvent) => resumeChat({ dados, conversationId: chatId, onEvent }),
|
|
276
|
+
{ texto: mensagem.content ?? '', tools: mensagem.tools ?? [] },
|
|
277
|
+
)
|
|
278
|
+
}
|
|
279
|
+
|
|
238
280
|
return {
|
|
239
281
|
handleSend,
|
|
240
282
|
handleApproval,
|
|
283
|
+
handleDataSubmit,
|
|
241
284
|
}
|
|
242
285
|
}
|
|
243
286
|
|
|
@@ -24,6 +24,17 @@ export type PendingApproval = {
|
|
|
24
24
|
calls: { id: string; name: string; args: Record<string, unknown> }[]
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Criacao parada por falta de dados, esperando o usuario preencher.
|
|
29
|
+
*
|
|
30
|
+
* `campos` traz os nomes TECNICOS dos parametros que faltam (ex.:
|
|
31
|
+
* `produto_id`); quem exibe traduz com `rotularCampo`.
|
|
32
|
+
*/
|
|
33
|
+
export type PendingInput = {
|
|
34
|
+
id: string
|
|
35
|
+
campos: string[]
|
|
36
|
+
}
|
|
37
|
+
|
|
27
38
|
export type Conversation = {
|
|
28
39
|
id: string
|
|
29
40
|
sender: {
|
|
@@ -53,6 +64,8 @@ export type Conversation = {
|
|
|
53
64
|
tools?: ToolCall[]
|
|
54
65
|
/** Preenchido quando o agente pausou esperando aprovacao humana. */
|
|
55
66
|
pendingApproval?: PendingApproval
|
|
67
|
+
/** Preenchido quando o agente pausou pedindo os dados que faltam. */
|
|
68
|
+
pendingInput?: PendingInput
|
|
56
69
|
}
|
|
57
70
|
|
|
58
71
|
/**
|
|
@@ -97,6 +110,16 @@ export type ChatEvent =
|
|
|
97
110
|
}[]
|
|
98
111
|
}
|
|
99
112
|
}
|
|
113
|
+
| {
|
|
114
|
+
/**
|
|
115
|
+
* Faltam campos para criar: o agente parou esperando o formulario.
|
|
116
|
+
*
|
|
117
|
+
* Como no `approval_required`, o `done` NAO vem — o turno esta
|
|
118
|
+
* pausado. Retomar e chamar `resumeChat({ dados })`.
|
|
119
|
+
*/
|
|
120
|
+
type: 'input_required'
|
|
121
|
+
data: { id: string; campos: string[] }
|
|
122
|
+
}
|
|
100
123
|
| { type: 'done'; data: { content: string } }
|
|
101
124
|
| { type: 'error'; data: { message: string } }
|
|
102
125
|
|
package/package.json
CHANGED