@orbe-agro/client-core 5.6.243 → 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.
Files changed (29) hide show
  1. package/dist/@ecme/components/template/Footer.js +1 -1
  2. package/dist/@ecme/services/AiService.js +2 -1
  3. package/dist/@ecme/services/AiService.js.map +1 -1
  4. package/dist/@ecme/views/concepts/ai/Chat/components/ChatView.js +14 -2
  5. package/dist/@ecme/views/concepts/ai/Chat/components/ChatView.js.map +1 -1
  6. package/dist/@ecme/views/concepts/ai/Chat/components/DataRequestCard.js +99 -0
  7. package/dist/@ecme/views/concepts/ai/Chat/components/DataRequestCard.js.map +1 -0
  8. package/dist/@ecme/views/concepts/ai/Chat/components/toolPresentation.js +15 -1
  9. package/dist/@ecme/views/concepts/ai/Chat/components/toolPresentation.js.map +1 -1
  10. package/dist/@ecme/views/concepts/ai/Chat/hooks/useChatSend.js +21 -1
  11. package/dist/@ecme/views/concepts/ai/Chat/hooks/useChatSend.js.map +1 -1
  12. package/dist/@types/@ecme/services/AiService.d.ts +13 -4
  13. package/dist/@types/@ecme/services/AiService.d.ts.map +1 -1
  14. package/dist/@types/@ecme/views/concepts/ai/Chat/components/ChatView.d.ts.map +1 -1
  15. package/dist/@types/@ecme/views/concepts/ai/Chat/components/DataRequestCard.d.ts +20 -0
  16. package/dist/@types/@ecme/views/concepts/ai/Chat/components/DataRequestCard.d.ts.map +1 -0
  17. package/dist/@types/@ecme/views/concepts/ai/Chat/components/toolPresentation.d.ts +12 -0
  18. package/dist/@types/@ecme/views/concepts/ai/Chat/components/toolPresentation.d.ts.map +1 -1
  19. package/dist/@types/@ecme/views/concepts/ai/Chat/hooks/useChatSend.d.ts +1 -0
  20. package/dist/@types/@ecme/views/concepts/ai/Chat/hooks/useChatSend.d.ts.map +1 -1
  21. package/dist/@types/@ecme/views/concepts/ai/Chat/types.d.ts +24 -0
  22. package/dist/@types/@ecme/views/concepts/ai/Chat/types.d.ts.map +1 -1
  23. package/lib/@ecme/services/AiService.ts +14 -4
  24. package/lib/@ecme/views/concepts/ai/Chat/components/ChatView.tsx +17 -3
  25. package/lib/@ecme/views/concepts/ai/Chat/components/DataRequestCard.tsx +144 -0
  26. package/lib/@ecme/views/concepts/ai/Chat/components/toolPresentation.ts +29 -0
  27. package/lib/@ecme/views/concepts/ai/Chat/hooks/useChatSend.ts +43 -0
  28. package/lib/@ecme/views/concepts/ai/Chat/types.ts +23 -0
  29. 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.243" : "0.0.0";
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: [
@@ -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.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\nexport type ResumeChatParams = {\n /** true = aprovar a operacao pendente, false = negar. */\n approved: boolean\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 num pedido de aprovacao.\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 */\nexport async function resumeChat({\n approved,\n conversationId,\n signal,\n onEvent,\n}: ResumeChatParams): Promise<void> {\n await postarEConsumir(\n '/ai/chat/resume',\n { approved, 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;AAwBA,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;AAQA,eAAsB,WAAW;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACJ,GAAoC;AAChC,QAAM;AAAA,IACF;AAAA,IACA,EAAE,UAAU,iBAAiB,eAAA;AAAA,IAC7B;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;"}
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":";;;;;;;;;;AAyBA,MAAM,kBAAkB,CAAC,EAAE,OAAO,eAAqC;AACnE,QAAM,CAAC,SAAS,UAAU,IAAI,SAAiC,CAAA,CAAE;AACjE,QAAM,CAAC,UAAU,WAAW,IAAI,SAAuC,IAAI;AAE3E,QAAM,YAAY,CAAC,OAAe,UAC9B,WAAW,CAAC,WAAW,EAAE,GAAG,OAAO,CAAC,KAAK,GAAG,QAAQ;AAGxD,QAAM,WAAW,MAAM,OAAO,MAAM,CAAC,UAAU,QAAQ,KAAK,GAAG,MAAM;AAErE,QAAM,SAAS,MAAM;AACjB,gBAAY,QAAQ;AAGpB,UAAM,QAAQ,OAAO;AAAA,MACjB,MAAM,OAAO,IAAI,CAAC,UAAU;AACxB,cAAM,QAAQ,QAAQ,KAAK,EAAE,KAAA;AAC7B,cAAM,SACF,YAAY,KAAK,MAAM,WAAW,OAAO,KAAK,IAAI;AACtD,eAAO,CAAC,OAAO,OAAO,MAAM,MAAM,IAAI,QAAQ,MAAM;AAAA,MACxD,CAAC;AAAA,IAAA;AAEL,aAAS,KAAK;AAAA,EAClB;AAEA,QAAM,WAAW,MAAM;AACnB,gBAAY,UAAU;AACtB,aAAS,IAAI;AAAA,EACjB;AAEA,SACI,qBAAC,OAAA,EAAI,WAAU,6HACX,UAAA;AAAA,IAAA,qBAAC,OAAA,EAAI,WAAU,qFACX,UAAA;AAAA,MAAA,oBAAC,SAAA,EAAQ,MAAM,IAAI,WAAU,oCAAmC;AAAA,MAChE,oBAAC,QAAA,EAAK,WAAU,kFAAiF,UAAA,oBAAA,CAEjG;AAAA,IAAA,GACJ;AAAA,IAEA,qBAAC,OAAA,EAAI,WAAU,eACX,UAAA;AAAA,MAAA,oBAAC,OAAA,EAAI,WAAU,iDAAgD,UAAA,gDAE/D;AAAA,MAEA,oBAAC,SAAI,WAAU,uBACV,gBAAM,OAAO,IAAI,CAAC,UAAU;AACzB,cAAM,SAAS,aAAa,KAAK;AACjC,cAAM,SAAS,cAAc,KAAK;AAElC,eACI,qBAAC,SAAA,EAAkB,WAAU,uBACzB,UAAA;AAAA,UAAA,oBAAC,QAAA,EAAK,WAAU,4CACX,UAAA,QACL;AAAA,UACC,OAAO,SACJ;AAAA,YAAC;AAAA,YAAA;AAAA,cACG,SAAS;AAAA,cACT,OAAO,OAAO;AAAA,gBACV,CAAC,UACG,MAAM,UAAU,QAAQ,KAAK;AAAA,cAAA;AAAA,cAErC,aAAa,aAAa,OAAO,YAAA,CAAa;AAAA,cAC9C,YAAY,aAAa;AAAA,cACzB,UAAU,CAAC,UACP;AAAA,gBACI;AAAA,gBACC,OACK,SAAS;AAAA,cAAA;AAAA,YACnB;AAAA,UAAA,IAIR;AAAA,YAAC;AAAA,YAAA;AAAA,cACG,MACI,YAAY,KAAK,MAAM,WACjB,WACA;AAAA,cAEV,OAAO,QAAQ,KAAK,KAAK;AAAA,cACzB,UAAU,aAAa;AAAA,cACvB,aAAa,WAAW,OAAO,YAAA,CAAa;AAAA,cAC5C,UAAU,CAAC,WACP,UAAU,OAAO,OAAO,OAAO,KAAK;AAAA,YAAA;AAAA,UAAA;AAAA,QAE5C,EAAA,GAlCI,KAoCZ;AAAA,MAER,CAAC,EAAA,CACL;AAAA,MAEA,qBAAC,OAAA,EAAI,WAAU,gCACX,UAAA;AAAA,QAAA;AAAA,UAAC;AAAA,UAAA;AAAA,YACG,SAAQ;AAAA,YACR,MAAK;AAAA,YACL,SAAS,aAAa;AAAA,YACtB,UAAU,CAAC,YAAY,aAAa;AAAA,YACpC,SAAS;AAAA,YACZ,UAAA;AAAA,UAAA;AAAA,QAAA;AAAA,QAGD;AAAA,UAAC;AAAA,UAAA;AAAA,YACG,MAAK;AAAA,YACL,SAAS,aAAa;AAAA,YACtB,UAAU,aAAa;AAAA,YACvB,SAAS;AAAA,YACZ,UAAA;AAAA,UAAA;AAAA,QAAA;AAAA,QAGD,oBAAC,QAAA,EAAK,WAAU,oDAAmD,UAAA,sCAAA,CAEnE;AAAA,MAAA,EAAA,CACJ;AAAA,IAAA,EAAA,CACJ;AAAA,EAAA,GACJ;AAER;"}
@@ -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":"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"}
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
- /** true = aprovar a operacao pendente, false = negar. */
13
- approved: boolean;
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 num pedido de aprovacao.
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,yDAAyD;IACzD,QAAQ,EAAE,OAAO,CAAA;IACjB,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;;;;;GAKG;AACH,wBAAsB,UAAU,CAAC,EAC7B,QAAQ,EACR,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
+ {"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":"AAgCA,QAAA,MAAM,QAAQ,+CA+Ib,CAAA;AAED,eAAe,QAAQ,CAAA"}
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;AAchF,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
+ {"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;yBA4KqB,MAAM;6BA2B5B,MAAM,aACH,MAAM,YACP,OAAO;CA6BxB,CAAA;AAED,eAAe,WAAW,CAAA"}
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":"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"}
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;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GAAG;IACvB,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,EAAE,MAAM,EAAE,CAAA;CACnB,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;IACjC,qEAAqE;IACrE,YAAY,CAAC,EAAE,YAAY,CAAA;CAC9B,CAAA;AAED;;;;;;;;;GASG;AACH,MAAM,MAAM,SAAS,GACf;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,GAC5C;IACI,IAAI,EAAE,YAAY,CAAA;IAClB,IAAI,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAA;CACpE,GACD;IACI,IAAI,EAAE,UAAU,CAAA;IAChB,IAAI,EAAE;QACF,EAAE,EAAE,MAAM,CAAA;QACV,IAAI,EAAE,MAAM,CAAA;QACZ,OAAO,EAAE,MAAM,CAAA;QACf,QAAQ,EAAE,OAAO,CAAA;KACpB,CAAA;CACJ,GACD;IACI;;;;;OAKG;IACH,IAAI,EAAE,mBAAmB,CAAA;IACzB,IAAI,EAAE;QACF,EAAE,EAAE,MAAM,CAAA;QACV,KAAK,EAAE;YACH,EAAE,EAAE,MAAM,CAAA;YACV,IAAI,EAAE,MAAM,CAAA;YACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;SAChC,EAAE,CAAA;KACN,CAAA;CACJ,GACD;IACI;;;;;OAKG;IACH,IAAI,EAAE,gBAAgB,CAAA;IACtB,IAAI,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,EAAE,CAAA;KAAE,CAAA;CACzC,GACD;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,GAC3C;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAAA;AAElD,MAAM,MAAM,WAAW,GAAG;IACtB,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,YAAY,CAAC,EAAE,YAAY,EAAE,CAAA;IAC7B,gBAAgB,EAAE,MAAM,CAAA;IACxB,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,MAAM,CAAA;IACnB,MAAM,EAAE,OAAO,CAAA;CAClB,CAAA;AAED,MAAM,MAAM,aAAa,GAAG,WAAW,EAAE,CAAA;AAEzC,MAAM,MAAM,sBAAsB,GAAG,aAAa,CAAA"}
@@ -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
- /** true = aprovar a operacao pendente, false = negar. */
62
- approved: boolean
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 num pedido de aprovacao.
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 a aprovacao chegam pelo streaming 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 } = message as Conversation
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orbe-agro/client-core",
3
- "version": "5.6.243",
3
+ "version": "5.6.244",
4
4
  "description": "Biblioteca principal de componentes e utilidades para os microfrontends do Orbe Agro.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",