@orbe-agro/client-core 5.6.253 → 5.6.254

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 (21) hide show
  1. package/dist/@ecme/components/template/Footer.js +1 -1
  2. package/dist/@ecme/views/concepts/ai/Chat/components/ApprovalCard.js +1 -1
  3. package/dist/@ecme/views/concepts/ai/Chat/components/ApprovalCard.js.map +1 -1
  4. package/dist/@ecme/views/concepts/ai/Chat/components/ChatLandingView.js +33 -32
  5. package/dist/@ecme/views/concepts/ai/Chat/components/ChatLandingView.js.map +1 -1
  6. package/dist/@ecme/views/concepts/ai/Chat/components/ToolCallChip.js +1 -1
  7. package/dist/@ecme/views/concepts/ai/Chat/components/ToolCallChip.js.map +1 -1
  8. package/dist/@ecme/views/concepts/ai/Chat/components/toolPresentation.js +90 -8
  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 +2 -3
  11. package/dist/@ecme/views/concepts/ai/Chat/hooks/useChatSend.js.map +1 -1
  12. package/dist/@types/@ecme/views/concepts/ai/Chat/components/ChatLandingView.d.ts.map +1 -1
  13. package/dist/@types/@ecme/views/concepts/ai/Chat/components/toolPresentation.d.ts +9 -4
  14. package/dist/@types/@ecme/views/concepts/ai/Chat/components/toolPresentation.d.ts.map +1 -1
  15. package/dist/@types/@ecme/views/concepts/ai/Chat/hooks/useChatSend.d.ts.map +1 -1
  16. package/lib/@ecme/views/concepts/ai/Chat/components/ApprovalCard.tsx +1 -1
  17. package/lib/@ecme/views/concepts/ai/Chat/components/ChatLandingView.tsx +52 -44
  18. package/lib/@ecme/views/concepts/ai/Chat/components/ToolCallChip.tsx +1 -1
  19. package/lib/@ecme/views/concepts/ai/Chat/components/toolPresentation.ts +126 -9
  20. package/lib/@ecme/views/concepts/ai/Chat/hooks/useChatSend.ts +12 -5
  21. 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.253" : "0.0.0";
6
+ const version = true ? "5.6.254" : "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: [
@@ -18,7 +18,7 @@ const ApprovalCard = ({ approval, onDecide }) => {
18
18
  /* @__PURE__ */ jsxs("div", { className: "px-4 py-3.5", children: [
19
19
  approval.calls.map((call) => {
20
20
  const Icone = iconeFerramenta(call.name);
21
- const campos = camposVisiveis(call.args);
21
+ const campos = camposVisiveis(call.args, call.name);
22
22
  return /* @__PURE__ */ jsxs("div", { className: "mb-4 last:mb-0", children: [
23
23
  /* @__PURE__ */ jsxs("div", { className: "mb-3 flex items-start gap-2.5", children: [
24
24
  /* @__PURE__ */ jsx("span", { className: "mt-0.5 flex size-8 shrink-0 items-center justify-center rounded-lg bg-primary-subtle text-primary", children: /* @__PURE__ */ jsx(Icone, { size: 17 }) }),
@@ -1 +1 @@
1
- {"version":3,"file":"ApprovalCard.js","sources":["../../../../../../../lib/@ecme/views/concepts/ai/Chat/components/ApprovalCard.tsx"],"sourcesContent":["import { useState } from 'react'\nimport { TbLock } from 'react-icons/tb'\nimport Button from '@/components/ui/Button'\nimport { camposVisiveis, iconeFerramenta, rotularFerramenta } from './toolPresentation'\nimport type { PendingApproval } from '../types'\n\ntype ApprovalCardProps = {\n approval: PendingApproval\n onDecide: (approved: boolean) => void\n}\n\n/**\n * Confirmacao de uma operacao que grava no ERP.\n *\n * O agente fica PARADO enquanto este card estiver na tela — so volta a\n * responder depois do clique. Por isso ele nao usa a paleta de alerta: nao e um\n * aviso de erro, e uma decisao de negocio que o usuario precisa tomar.\n */\nconst ApprovalCard = ({ approval, onDecide }: ApprovalCardProps) => {\n const [decisao, setDecisao] = useState<'aprovar' | 'negar' | null>(null)\n\n const decidir = (aprovado: boolean) => {\n setDecisao(aprovado ? 'aprovar' : 'negar')\n onDecide(aprovado)\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 <TbLock 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 Confirmação necessária\n </span>\n </div>\n\n <div className=\"px-4 py-3.5\">\n {approval.calls.map((call) => {\n const Icone = iconeFerramenta(call.name)\n const campos = camposVisiveis(call.args)\n\n return (\n <div key={call.id} className=\"mb-4 last:mb-0\">\n <div className=\"mb-3 flex items-start gap-2.5\">\n <span className=\"mt-0.5 flex size-8 shrink-0 items-center justify-center rounded-lg bg-primary-subtle text-primary\">\n <Icone size={17} />\n </span>\n <div className=\"min-w-0\">\n <div className=\"font-semibold text-gray-900 dark:text-gray-100\">\n {rotularFerramenta(call.name)}\n </div>\n <div className=\"text-sm text-gray-500 dark:text-gray-400\">\n Revise os dados antes de confirmar.\n </div>\n </div>\n </div>\n\n <dl className=\"divide-y divide-gray-100 rounded-lg border border-gray-100 dark:divide-gray-700 dark:border-gray-700\">\n {campos.map((campo) => (\n <div\n key={campo.chave}\n className=\"flex items-baseline justify-between gap-4 px-3 py-2\"\n >\n <dt className=\"shrink-0 text-sm text-gray-500 dark:text-gray-400\">\n {campo.rotulo}\n </dt>\n <dd className=\"truncate text-sm font-medium text-gray-900 dark:text-gray-100\">\n {campo.valor}\n </dd>\n </div>\n ))}\n </dl>\n </div>\n )\n })}\n\n <div className=\"mt-4 flex items-center gap-2\">\n <Button\n variant=\"solid\"\n size=\"sm\"\n loading={decisao === 'aprovar'}\n disabled={decisao !== null}\n onClick={() => decidir(true)}\n >\n Confirmar\n </Button>\n <Button\n size=\"sm\"\n loading={decisao === 'negar'}\n disabled={decisao !== null}\n onClick={() => decidir(false)}\n >\n Cancelar\n </Button>\n <span className=\"ml-auto text-xs text-gray-400 dark:text-gray-500\">\n Nada é gravado até você confirmar\n </span>\n </div>\n </div>\n </div>\n )\n}\n\nexport default ApprovalCard\n"],"names":[],"mappings":";;;;;;AAkBA,MAAM,eAAe,CAAC,EAAE,UAAU,eAAkC;AAChE,QAAM,CAAC,SAAS,UAAU,IAAI,SAAqC,IAAI;AAEvE,QAAM,UAAU,CAAC,aAAsB;AACnC,eAAW,WAAW,YAAY,OAAO;AACzC,aAAS,QAAQ;AAAA,EACrB;AAEA,SACI,qBAAC,OAAA,EAAI,WAAU,6HACX,UAAA;AAAA,IAAA,qBAAC,OAAA,EAAI,WAAU,qFACX,UAAA;AAAA,MAAA,oBAAC,QAAA,EAAO,MAAM,IAAI,WAAU,oCAAmC;AAAA,MAC/D,oBAAC,QAAA,EAAK,WAAU,kFAAiF,UAAA,yBAAA,CAEjG;AAAA,IAAA,GACJ;AAAA,IAEA,qBAAC,OAAA,EAAI,WAAU,eACV,UAAA;AAAA,MAAA,SAAS,MAAM,IAAI,CAAC,SAAS;AAC1B,cAAM,QAAQ,gBAAgB,KAAK,IAAI;AACvC,cAAM,SAAS,eAAe,KAAK,IAAI;AAEvC,eACI,qBAAC,OAAA,EAAkB,WAAU,kBACzB,UAAA;AAAA,UAAA,qBAAC,OAAA,EAAI,WAAU,iCACX,UAAA;AAAA,YAAA,oBAAC,UAAK,WAAU,qGACZ,8BAAC,OAAA,EAAM,MAAM,IAAI,EAAA,CACrB;AAAA,YACA,qBAAC,OAAA,EAAI,WAAU,WACX,UAAA;AAAA,cAAA,oBAAC,SAAI,WAAU,kDACV,UAAA,kBAAkB,KAAK,IAAI,GAChC;AAAA,cACA,oBAAC,OAAA,EAAI,WAAU,4CAA2C,UAAA,sCAAA,CAE1D;AAAA,YAAA,EAAA,CACJ;AAAA,UAAA,GACJ;AAAA,8BAEC,MAAA,EAAG,WAAU,wGACT,UAAA,OAAO,IAAI,CAAC,UACT;AAAA,YAAC;AAAA,YAAA;AAAA,cAEG,WAAU;AAAA,cAEV,UAAA;AAAA,gBAAA,oBAAC,MAAA,EAAG,WAAU,qDACT,UAAA,MAAM,QACX;AAAA,gBACA,oBAAC,MAAA,EAAG,WAAU,iEACT,gBAAM,MAAA,CACX;AAAA,cAAA;AAAA,YAAA;AAAA,YARK,MAAM;AAAA,UAAA,CAUlB,EAAA,CACL;AAAA,QAAA,EAAA,GA7BM,KAAK,EA8Bf;AAAA,MAER,CAAC;AAAA,MAED,qBAAC,OAAA,EAAI,WAAU,gCACX,UAAA;AAAA,QAAA;AAAA,UAAC;AAAA,UAAA;AAAA,YACG,SAAQ;AAAA,YACR,MAAK;AAAA,YACL,SAAS,YAAY;AAAA,YACrB,UAAU,YAAY;AAAA,YACtB,SAAS,MAAM,QAAQ,IAAI;AAAA,YAC9B,UAAA;AAAA,UAAA;AAAA,QAAA;AAAA,QAGD;AAAA,UAAC;AAAA,UAAA;AAAA,YACG,MAAK;AAAA,YACL,SAAS,YAAY;AAAA,YACrB,UAAU,YAAY;AAAA,YACtB,SAAS,MAAM,QAAQ,KAAK;AAAA,YAC/B,UAAA;AAAA,UAAA;AAAA,QAAA;AAAA,QAGD,oBAAC,QAAA,EAAK,WAAU,oDAAmD,UAAA,oCAAA,CAEnE;AAAA,MAAA,EAAA,CACJ;AAAA,IAAA,EAAA,CACJ;AAAA,EAAA,GACJ;AAER;"}
1
+ {"version":3,"file":"ApprovalCard.js","sources":["../../../../../../../lib/@ecme/views/concepts/ai/Chat/components/ApprovalCard.tsx"],"sourcesContent":["import { useState } from 'react'\nimport { TbLock } from 'react-icons/tb'\nimport Button from '@/components/ui/Button'\nimport { camposVisiveis, iconeFerramenta, rotularFerramenta } from './toolPresentation'\nimport type { PendingApproval } from '../types'\n\ntype ApprovalCardProps = {\n approval: PendingApproval\n onDecide: (approved: boolean) => void\n}\n\n/**\n * Confirmacao de uma operacao que grava no ERP.\n *\n * O agente fica PARADO enquanto este card estiver na tela — so volta a\n * responder depois do clique. Por isso ele nao usa a paleta de alerta: nao e um\n * aviso de erro, e uma decisao de negocio que o usuario precisa tomar.\n */\nconst ApprovalCard = ({ approval, onDecide }: ApprovalCardProps) => {\n const [decisao, setDecisao] = useState<'aprovar' | 'negar' | null>(null)\n\n const decidir = (aprovado: boolean) => {\n setDecisao(aprovado ? 'aprovar' : 'negar')\n onDecide(aprovado)\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 <TbLock 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 Confirmação necessária\n </span>\n </div>\n\n <div className=\"px-4 py-3.5\">\n {approval.calls.map((call) => {\n const Icone = iconeFerramenta(call.name)\n const campos = camposVisiveis(call.args, call.name)\n\n return (\n <div key={call.id} className=\"mb-4 last:mb-0\">\n <div className=\"mb-3 flex items-start gap-2.5\">\n <span className=\"mt-0.5 flex size-8 shrink-0 items-center justify-center rounded-lg bg-primary-subtle text-primary\">\n <Icone size={17} />\n </span>\n <div className=\"min-w-0\">\n <div className=\"font-semibold text-gray-900 dark:text-gray-100\">\n {rotularFerramenta(call.name)}\n </div>\n <div className=\"text-sm text-gray-500 dark:text-gray-400\">\n Revise os dados antes de confirmar.\n </div>\n </div>\n </div>\n\n <dl className=\"divide-y divide-gray-100 rounded-lg border border-gray-100 dark:divide-gray-700 dark:border-gray-700\">\n {campos.map((campo) => (\n <div\n key={campo.chave}\n className=\"flex items-baseline justify-between gap-4 px-3 py-2\"\n >\n <dt className=\"shrink-0 text-sm text-gray-500 dark:text-gray-400\">\n {campo.rotulo}\n </dt>\n <dd className=\"truncate text-sm font-medium text-gray-900 dark:text-gray-100\">\n {campo.valor}\n </dd>\n </div>\n ))}\n </dl>\n </div>\n )\n })}\n\n <div className=\"mt-4 flex items-center gap-2\">\n <Button\n variant=\"solid\"\n size=\"sm\"\n loading={decisao === 'aprovar'}\n disabled={decisao !== null}\n onClick={() => decidir(true)}\n >\n Confirmar\n </Button>\n <Button\n size=\"sm\"\n loading={decisao === 'negar'}\n disabled={decisao !== null}\n onClick={() => decidir(false)}\n >\n Cancelar\n </Button>\n <span className=\"ml-auto text-xs text-gray-400 dark:text-gray-500\">\n Nada é gravado até você confirmar\n </span>\n </div>\n </div>\n </div>\n )\n}\n\nexport default ApprovalCard\n"],"names":[],"mappings":";;;;;;AAkBA,MAAM,eAAe,CAAC,EAAE,UAAU,eAAkC;AAChE,QAAM,CAAC,SAAS,UAAU,IAAI,SAAqC,IAAI;AAEvE,QAAM,UAAU,CAAC,aAAsB;AACnC,eAAW,WAAW,YAAY,OAAO;AACzC,aAAS,QAAQ;AAAA,EACrB;AAEA,SACI,qBAAC,OAAA,EAAI,WAAU,6HACX,UAAA;AAAA,IAAA,qBAAC,OAAA,EAAI,WAAU,qFACX,UAAA;AAAA,MAAA,oBAAC,QAAA,EAAO,MAAM,IAAI,WAAU,oCAAmC;AAAA,MAC/D,oBAAC,QAAA,EAAK,WAAU,kFAAiF,UAAA,yBAAA,CAEjG;AAAA,IAAA,GACJ;AAAA,IAEA,qBAAC,OAAA,EAAI,WAAU,eACV,UAAA;AAAA,MAAA,SAAS,MAAM,IAAI,CAAC,SAAS;AAC1B,cAAM,QAAQ,gBAAgB,KAAK,IAAI;AACvC,cAAM,SAAS,eAAe,KAAK,MAAM,KAAK,IAAI;AAElD,eACI,qBAAC,OAAA,EAAkB,WAAU,kBACzB,UAAA;AAAA,UAAA,qBAAC,OAAA,EAAI,WAAU,iCACX,UAAA;AAAA,YAAA,oBAAC,UAAK,WAAU,qGACZ,8BAAC,OAAA,EAAM,MAAM,IAAI,EAAA,CACrB;AAAA,YACA,qBAAC,OAAA,EAAI,WAAU,WACX,UAAA;AAAA,cAAA,oBAAC,SAAI,WAAU,kDACV,UAAA,kBAAkB,KAAK,IAAI,GAChC;AAAA,cACA,oBAAC,OAAA,EAAI,WAAU,4CAA2C,UAAA,sCAAA,CAE1D;AAAA,YAAA,EAAA,CACJ;AAAA,UAAA,GACJ;AAAA,8BAEC,MAAA,EAAG,WAAU,wGACT,UAAA,OAAO,IAAI,CAAC,UACT;AAAA,YAAC;AAAA,YAAA;AAAA,cAEG,WAAU;AAAA,cAEV,UAAA;AAAA,gBAAA,oBAAC,MAAA,EAAG,WAAU,qDACT,UAAA,MAAM,QACX;AAAA,gBACA,oBAAC,MAAA,EAAG,WAAU,iEACT,gBAAM,MAAA,CACX;AAAA,cAAA;AAAA,YAAA;AAAA,YARK,MAAM;AAAA,UAAA,CAUlB,EAAA,CACL;AAAA,QAAA,EAAA,GA7BM,KAAK,EA8Bf;AAAA,MAER,CAAC;AAAA,MAED,qBAAC,OAAA,EAAI,WAAU,gCACX,UAAA;AAAA,QAAA;AAAA,UAAC;AAAA,UAAA;AAAA,YACG,SAAQ;AAAA,YACR,MAAK;AAAA,YACL,SAAS,YAAY;AAAA,YACrB,UAAU,YAAY;AAAA,YACtB,SAAS,MAAM,QAAQ,IAAI;AAAA,YAC9B,UAAA;AAAA,UAAA;AAAA,QAAA;AAAA,QAGD;AAAA,UAAC;AAAA,UAAA;AAAA,YACG,MAAK;AAAA,YACL,SAAS,YAAY;AAAA,YACrB,UAAU,YAAY;AAAA,YACtB,SAAS,MAAM,QAAQ,KAAK;AAAA,YAC/B,UAAA;AAAA,UAAA;AAAA,QAAA;AAAA,QAGD,oBAAC,QAAA,EAAK,WAAU,oDAAmD,UAAA,oCAAA,CAEnE;AAAA,MAAA,EAAA,CACJ;AAAA,IAAA,EAAA,CACJ;AAAA,EAAA,GACJ;AAER;"}
@@ -1,32 +1,30 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import useChatSend from "../hooks/useChatSend.js";
3
- import { PiCodeDuotone, PiBookOpenTextDuotone, PiCompassDuotone, PiLightbulbDuotone } from "react-icons/pi";
4
- const suggeustionIcon = {
5
- idea: /* @__PURE__ */ jsx(PiLightbulbDuotone, { className: "text-blue-500" }),
6
- guide: /* @__PURE__ */ jsx(PiCompassDuotone, { className: "text-emerald-500" }),
7
- writing: /* @__PURE__ */ jsx(PiBookOpenTextDuotone, { className: "text-amber-500" }),
8
- coding: /* @__PURE__ */ jsx(PiCodeDuotone, { className: "text-indigo-500" })
9
- };
3
+ import { iconeFerramenta } from "./toolPresentation.js";
10
4
  const promptSuggestion = [
11
5
  {
12
- title: "Como consultar um pedido de venda?",
13
- prompt: "Como faço para consultar um pedido de venda no Orbe Agro?",
14
- type: "guide"
6
+ title: "Buscar um usuário pelo ID",
7
+ prompt: "Quem é o usuário de um dado id?",
8
+ tool: "buscar_usuario",
9
+ cor: "text-blue-500"
15
10
  },
16
11
  {
17
- title: "Para que serve o módulo de Trade?",
18
- prompt: "Explique para que serve o módulo de Trade no Orbe Agro.",
19
- type: "idea"
12
+ title: "Consultar uma área pelo código",
13
+ prompt: "Me mostra a área de um dado id?",
14
+ tool: "buscar_area",
15
+ cor: "text-emerald-500"
20
16
  },
21
17
  {
22
- title: "Como abrir um processo de armazenagem?",
23
- prompt: "Quais são os passos para abrir um processo de armazenagem de grãos no Orbe Agro?",
24
- type: "writing"
18
+ title: "Criar uma nova área",
19
+ prompt: "Quero criar uma área nova",
20
+ tool: "criar_area",
21
+ cor: "text-amber-500"
25
22
  },
26
23
  {
27
- title: "Onde vejo o limite de crédito de um cliente?",
28
- prompt: "Onde encontro o limite de crédito de um cliente no Orbe Agro?",
29
- type: "coding"
24
+ title: "Criar um contrato de compra de grãos",
25
+ prompt: "Quero criar um contrato de compra de armazenagem de grãos",
26
+ tool: "criar_contrato_armazenagem_graos",
27
+ cor: "text-indigo-500"
30
28
  }
31
29
  ];
32
30
  const ChatLandingView = () => {
@@ -37,19 +35,22 @@ const ChatLandingView = () => {
37
35
  /* @__PURE__ */ jsx("br", {}),
38
36
  /* @__PURE__ */ jsx("span", { children: "Como posso te ajudar hoje?" })
39
37
  ] }),
40
- /* @__PURE__ */ jsx("div", { className: "mt-6 grid grid-cols-1 sm:grid-cols-2 gap-3", children: promptSuggestion.map((suggestion) => /* @__PURE__ */ jsxs(
41
- "div",
42
- {
43
- className: "flex flex-col gap-3 justify-between rounded-xl bg-gray-100 dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-gray-600 p-4 min-h-28 cursor-pointer",
44
- role: "button",
45
- onClick: () => handleSend(suggestion.title),
46
- children: [
47
- /* @__PURE__ */ jsx("h6", { className: "font-normal", children: suggestion.title }),
48
- /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx("div", { className: "bg-white dark:bg-gray-800 rounded-full p-2 inline-flex", children: /* @__PURE__ */ jsx("span", { className: "text-2xl", children: suggeustionIcon[suggestion.type] }) }) })
49
- ]
50
- },
51
- suggestion.title
52
- )) })
38
+ /* @__PURE__ */ jsx("div", { className: "mt-6 grid grid-cols-1 sm:grid-cols-2 gap-3", children: promptSuggestion.map((suggestion) => {
39
+ const Icone = iconeFerramenta(suggestion.tool);
40
+ return /* @__PURE__ */ jsxs(
41
+ "div",
42
+ {
43
+ className: "flex flex-col gap-3 justify-between rounded-xl bg-gray-100 dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-gray-600 p-4 min-h-28 cursor-pointer",
44
+ role: "button",
45
+ onClick: () => handleSend(suggestion.prompt),
46
+ children: [
47
+ /* @__PURE__ */ jsx("h6", { className: "font-normal", children: suggestion.title }),
48
+ /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx("div", { className: "bg-white dark:bg-gray-800 rounded-full p-2 inline-flex", children: /* @__PURE__ */ jsx("span", { className: "text-2xl", children: /* @__PURE__ */ jsx(Icone, { className: suggestion.cor }) }) }) })
49
+ ]
50
+ },
51
+ suggestion.title
52
+ );
53
+ }) })
53
54
  ] }) });
54
55
  };
55
56
  export {
@@ -1 +1 @@
1
- {"version":3,"file":"ChatLandingView.js","sources":["../../../../../../../lib/@ecme/views/concepts/ai/Chat/components/ChatLandingView.tsx"],"sourcesContent":["import useChatSend from '../hooks/useChatSend'\nimport {\n PiLightbulbDuotone,\n PiBookOpenTextDuotone,\n PiCompassDuotone,\n PiCodeDuotone,\n} from 'react-icons/pi'\nimport type { ReactNode } from 'react'\n\ntype PromptType = 'idea' | 'guide' | 'writing' | 'coding'\n\nconst suggeustionIcon: Record<PromptType, ReactNode> = {\n idea: <PiLightbulbDuotone className=\"text-blue-500\" />,\n guide: <PiCompassDuotone className=\"text-emerald-500\" />,\n writing: <PiBookOpenTextDuotone className=\"text-amber-500\" />,\n coding: <PiCodeDuotone className=\"text-indigo-500\" />,\n}\n\nconst promptSuggestion: {\n title: string\n prompt: string\n type: PromptType\n}[] = [\n {\n title: 'Como consultar um pedido de venda?',\n prompt: 'Como faço para consultar um pedido de venda no Orbe Agro?',\n type: 'guide',\n },\n {\n title: 'Para que serve o módulo de Trade?',\n prompt: 'Explique para que serve o módulo de Trade no Orbe Agro.',\n type: 'idea',\n },\n {\n title: 'Como abrir um processo de armazenagem?',\n prompt: 'Quais são os passos para abrir um processo de armazenagem de grãos no Orbe Agro?',\n type: 'writing',\n },\n {\n title: 'Onde vejo o limite de crédito de um cliente?',\n prompt: 'Onde encontro o limite de crédito de um cliente no Orbe Agro?',\n type: 'coding',\n },\n]\n\nconst ChatLandingView = () => {\n const { handleSend } = useChatSend()\n\n return (\n <div className=\"max-w-[900px] w-full mx-auto mt-6\">\n <div>\n <div className=\"heading-text text-2xl leading-snug\">\n <span className=\"font-semibold bg-gradient-to-r from-indigo-500 to-red-400 bg-clip-text text-transparent text-3xl\">\n Olá!\n </span>\n <br />\n <span>Como posso te ajudar hoje?</span>\n </div>\n <div className=\"mt-6 grid grid-cols-1 sm:grid-cols-2 gap-3\">\n {promptSuggestion.map((suggestion) => (\n <div\n key={suggestion.title}\n className=\"flex flex-col gap-3 justify-between rounded-xl bg-gray-100 dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-gray-600 p-4 min-h-28 cursor-pointer\"\n role=\"button\"\n onClick={() => handleSend(suggestion.title)}\n >\n <h6 className=\"font-normal\">{suggestion.title}</h6>\n <div>\n <div className=\"bg-white dark:bg-gray-800 rounded-full p-2 inline-flex\">\n <span className=\"text-2xl\">\n {suggeustionIcon[suggestion.type]}\n </span>\n </div>\n </div>\n </div>\n ))}\n </div>\n </div>\n </div>\n )\n}\n\nexport default ChatLandingView\n"],"names":[],"mappings":";;;AAWA,MAAM,kBAAiD;AAAA,EACnD,MAAM,oBAAC,oBAAA,EAAmB,WAAU,gBAAA,CAAgB;AAAA,EACpD,OAAO,oBAAC,kBAAA,EAAiB,WAAU,mBAAA,CAAmB;AAAA,EACtD,SAAS,oBAAC,uBAAA,EAAsB,WAAU,iBAAA,CAAiB;AAAA,EAC3D,QAAQ,oBAAC,eAAA,EAAc,WAAU,kBAAA,CAAkB;AACvD;AAEA,MAAM,mBAIA;AAAA,EACF;AAAA,IACI,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,MAAM;AAAA,EAAA;AAAA,EAEV;AAAA,IACI,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,MAAM;AAAA,EAAA;AAAA,EAEV;AAAA,IACI,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,MAAM;AAAA,EAAA;AAAA,EAEV;AAAA,IACI,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,MAAM;AAAA,EAAA;AAEd;AAEA,MAAM,kBAAkB,MAAM;AAC1B,QAAM,EAAE,WAAA,IAAe,YAAA;AAEvB,SACI,oBAAC,OAAA,EAAI,WAAU,qCACX,+BAAC,OAAA,EACG,UAAA;AAAA,IAAA,qBAAC,OAAA,EAAI,WAAU,sCACX,UAAA;AAAA,MAAA,oBAAC,QAAA,EAAK,WAAU,oGAAmG,UAAA,QAEnH;AAAA,0BACC,MAAA,EAAG;AAAA,MACJ,oBAAC,UAAK,UAAA,6BAAA,CAA0B;AAAA,IAAA,GACpC;AAAA,wBACC,OAAA,EAAI,WAAU,8CACV,UAAA,iBAAiB,IAAI,CAAC,eACnB;AAAA,MAAC;AAAA,MAAA;AAAA,QAEG,WAAU;AAAA,QACV,MAAK;AAAA,QACL,SAAS,MAAM,WAAW,WAAW,KAAK;AAAA,QAE1C,UAAA;AAAA,UAAA,oBAAC,MAAA,EAAG,WAAU,eAAe,UAAA,WAAW,OAAM;AAAA,UAC9C,oBAAC,OAAA,EACG,UAAA,oBAAC,OAAA,EAAI,WAAU,0DACX,UAAA,oBAAC,QAAA,EAAK,WAAU,YACX,UAAA,gBAAgB,WAAW,IAAI,GACpC,GACJ,EAAA,CACJ;AAAA,QAAA;AAAA,MAAA;AAAA,MAZK,WAAW;AAAA,IAAA,CAcvB,EAAA,CACL;AAAA,EAAA,EAAA,CACJ,EAAA,CACJ;AAER;"}
1
+ {"version":3,"file":"ChatLandingView.js","sources":["../../../../../../../lib/@ecme/views/concepts/ai/Chat/components/ChatLandingView.tsx"],"sourcesContent":["import useChatSend from '../hooks/useChatSend'\nimport { iconeFerramenta } from './toolPresentation'\n\n/**\n * O que o agente sabe fazer, em quatro exemplos que executam de verdade.\n *\n * `title` e o rotulo curto do card; `prompt` e a frase enviada ao agente.\n * `tool` nomeia a ferramenta que o exemplo exercita e de onde sai o icone —\n * o MESMO que aparecera no chip quando ela rodar, entao o card e a execucao\n * falam a mesma lingua e ha um registro so (ver toolPresentation.ts).\n *\n * Os dois exemplos de criacao mandam a intencao SEM os dados de proposito: o\n * agente pede os campos que faltam por formulario e so entao abre a tela de\n * aprovacao — o fluxo humano inteiro em um clique. Nada e gravado sem confirmar.\n */\nconst promptSuggestion: {\n title: string\n prompt: string\n tool: string\n cor: string\n}[] = [\n {\n title: 'Buscar um usuário pelo ID',\n prompt: 'Quem é o usuário de um dado id?',\n tool: 'buscar_usuario',\n cor: 'text-blue-500',\n },\n {\n title: 'Consultar uma área pelo código',\n prompt: 'Me mostra a área de um dado id?',\n tool: 'buscar_area',\n cor: 'text-emerald-500',\n },\n {\n title: 'Criar uma nova área',\n prompt: 'Quero criar uma área nova',\n tool: 'criar_area',\n cor: 'text-amber-500',\n },\n {\n title: 'Criar um contrato de compra de grãos',\n prompt: 'Quero criar um contrato de compra de armazenagem de grãos',\n tool: 'criar_contrato_armazenagem_graos',\n cor: 'text-indigo-500',\n },\n]\n\nconst ChatLandingView = () => {\n const { handleSend } = useChatSend()\n\n return (\n <div className=\"max-w-[900px] w-full mx-auto mt-6\">\n <div>\n <div className=\"heading-text text-2xl leading-snug\">\n <span className=\"font-semibold bg-gradient-to-r from-indigo-500 to-red-400 bg-clip-text text-transparent text-3xl\">\n Olá!\n </span>\n <br />\n <span>Como posso te ajudar hoje?</span>\n </div>\n <div className=\"mt-6 grid grid-cols-1 sm:grid-cols-2 gap-3\">\n {promptSuggestion.map((suggestion) => {\n const Icone = iconeFerramenta(suggestion.tool)\n\n return (\n <div\n key={suggestion.title}\n className=\"flex flex-col gap-3 justify-between rounded-xl bg-gray-100 dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-gray-600 p-4 min-h-28 cursor-pointer\"\n role=\"button\"\n onClick={() => handleSend(suggestion.prompt)}\n >\n <h6 className=\"font-normal\">\n {suggestion.title}\n </h6>\n <div>\n <div className=\"bg-white dark:bg-gray-800 rounded-full p-2 inline-flex\">\n <span className=\"text-2xl\">\n <Icone className={suggestion.cor} />\n </span>\n </div>\n </div>\n </div>\n )\n })}\n </div>\n </div>\n </div>\n )\n}\n\nexport default ChatLandingView\n"],"names":[],"mappings":";;;AAeA,MAAM,mBAKA;AAAA,EACF;AAAA,IACI,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,MAAM;AAAA,IACN,KAAK;AAAA,EAAA;AAAA,EAET;AAAA,IACI,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,MAAM;AAAA,IACN,KAAK;AAAA,EAAA;AAAA,EAET;AAAA,IACI,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,MAAM;AAAA,IACN,KAAK;AAAA,EAAA;AAAA,EAET;AAAA,IACI,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,MAAM;AAAA,IACN,KAAK;AAAA,EAAA;AAEb;AAEA,MAAM,kBAAkB,MAAM;AAC1B,QAAM,EAAE,WAAA,IAAe,YAAA;AAEvB,SACI,oBAAC,OAAA,EAAI,WAAU,qCACX,+BAAC,OAAA,EACG,UAAA;AAAA,IAAA,qBAAC,OAAA,EAAI,WAAU,sCACX,UAAA;AAAA,MAAA,oBAAC,QAAA,EAAK,WAAU,oGAAmG,UAAA,QAEnH;AAAA,0BACC,MAAA,EAAG;AAAA,MACJ,oBAAC,UAAK,UAAA,6BAAA,CAA0B;AAAA,IAAA,GACpC;AAAA,wBACC,OAAA,EAAI,WAAU,8CACV,UAAA,iBAAiB,IAAI,CAAC,eAAe;AAClC,YAAM,QAAQ,gBAAgB,WAAW,IAAI;AAE7C,aACI;AAAA,QAAC;AAAA,QAAA;AAAA,UAEG,WAAU;AAAA,UACV,MAAK;AAAA,UACL,SAAS,MAAM,WAAW,WAAW,MAAM;AAAA,UAE3C,UAAA;AAAA,YAAA,oBAAC,MAAA,EAAG,WAAU,eACT,UAAA,WAAW,OAChB;AAAA,gCACC,OAAA,EACG,UAAA,oBAAC,OAAA,EAAI,WAAU,0DACX,UAAA,oBAAC,QAAA,EAAK,WAAU,YACZ,8BAAC,OAAA,EAAM,WAAW,WAAW,IAAA,CAAK,EAAA,CACtC,GACJ,EAAA,CACJ;AAAA,UAAA;AAAA,QAAA;AAAA,QAdK,WAAW;AAAA,MAAA;AAAA,IAiB5B,CAAC,EAAA,CACL;AAAA,EAAA,EAAA,CACJ,EAAA,CACJ;AAER;"}
@@ -38,7 +38,7 @@ const ToolCallChip = ({ tool }) => {
38
38
  const estado = estadoDe(tool);
39
39
  const IconeFerramenta = iconeFerramenta(tool.name);
40
40
  const emAndamento = tool.running && !tool.awaitingApproval;
41
- const campos = camposVisiveis(tool.args);
41
+ const campos = camposVisiveis(tool.args, tool.name);
42
42
  const rotulo = emAndamento ? `${rotularExecucao(tool.name)}…` : rotularFerramenta(tool.name);
43
43
  return (
44
44
  // not-prose: o balao do agente e `prose`, que aplicaria margens de
@@ -1 +1 @@
1
- {"version":3,"file":"ToolCallChip.js","sources":["../../../../../../../lib/@ecme/views/concepts/ai/Chat/components/ToolCallChip.tsx"],"sourcesContent":["import { useState } from 'react'\nimport {\n TbAlertTriangle,\n TbCheck,\n TbChevronDown,\n TbClock,\n TbLoader2,\n} from 'react-icons/tb'\nimport classNames from '@/components/ui/utils/classNames'\nimport {\n camposVisiveis,\n iconeFerramenta,\n rotularExecucao,\n rotularFerramenta,\n} from './toolPresentation'\nimport type { ToolCall } from '../types'\n\n/**\n * Uma consulta ou operacao que o agente executou, no meio da conversa.\n *\n * Deliberadamente discreto: e informacao de apoio, nao a resposta. Por isso vive\n * FORA do balao de fala (ver ChatView) e tem forma de pilula — contorno fino,\n * largura do conteudo, letra menor — para nunca ser lido como uma segunda\n * mensagem do agente. Fechado mostra so o que aconteceu e em que estado esta;\n * aberto, os parametros ja traduzidos para a linguagem do negocio.\n */\n\ntype Estado = {\n texto: string\n Icone: typeof TbCheck\n classe: string\n /**\n * Se o texto do estado acompanha o icone na pilula.\n *\n * So quando o icone sozinho nao basta: \"aguardando confirmacao\" e \"nao\n * concluida\" pedem uma acao ou explicam uma falha. Em execucao o rotulo ja\n * esta no gerundio (\"Consultando contratos…\") e o ✓ de concluida e obvio —\n * escrever isso de novo so engorda a pilula.\n */\n mostrarTexto?: boolean\n}\n\nconst estadoDe = (tool: ToolCall): Estado => {\n if (tool.awaitingApproval) {\n return {\n texto: 'Aguardando confirmação',\n Icone: TbClock,\n classe: 'text-amber-600 dark:text-amber-400',\n mostrarTexto: true,\n }\n }\n if (tool.running) {\n return {\n texto: 'Em andamento',\n Icone: TbLoader2,\n classe: 'text-blue-600 dark:text-blue-400',\n }\n }\n if (tool.isError) {\n return {\n texto: 'Não concluída',\n Icone: TbAlertTriangle,\n classe: 'text-red-600 dark:text-red-400',\n mostrarTexto: true,\n }\n }\n return {\n texto: 'Concluída',\n Icone: TbCheck,\n classe: 'text-emerald-600 dark:text-emerald-400',\n }\n}\n\ntype ToolCallChipProps = {\n tool: ToolCall\n}\n\nconst ToolCallChip = ({ tool }: ToolCallChipProps) => {\n const [aberto, setAberto] = useState(false)\n const estado = estadoDe(tool)\n const IconeFerramenta = iconeFerramenta(tool.name)\n const emAndamento = tool.running && !tool.awaitingApproval\n const campos = camposVisiveis(tool.args)\n const rotulo = emAndamento\n ? `${rotularExecucao(tool.name)}…`\n : rotularFerramenta(tool.name)\n\n return (\n // not-prose: o balao do agente e `prose`, que aplicaria margens de\n // tipografia no dl/dt/dd/pre daqui e desmontaria a compactacao.\n <div className=\"not-prose max-w-full\">\n <button\n type=\"button\"\n className=\"inline-flex w-fit max-w-full items-center gap-1.5 rounded-full border border-gray-200 px-2.5 py-1 text-left transition-colors hover:border-gray-300 hover:bg-gray-50 dark:border-gray-700 dark:hover:border-gray-600 dark:hover:bg-gray-800/60\"\n aria-expanded={aberto}\n title={`${rotulo} — ${estado.texto}`}\n onClick={() => setAberto((atual) => !atual)}\n >\n <IconeFerramenta\n size={13}\n className=\"shrink-0 text-gray-400 dark:text-gray-500\"\n />\n\n <span className=\"truncate text-xs font-medium text-gray-600 dark:text-gray-300\">\n {rotulo}\n </span>\n\n <span\n className={classNames(\n 'flex shrink-0 items-center gap-1 text-xs font-medium',\n estado.classe,\n )}\n >\n <estado.Icone\n size={12}\n className={emAndamento ? 'motion-safe:animate-spin' : ''}\n />\n {estado.mostrarTexto && estado.texto}\n </span>\n\n <TbChevronDown\n size={12}\n className={classNames(\n 'shrink-0 text-gray-400 transition-transform',\n aberto && 'rotate-180',\n )}\n />\n </button>\n\n {aberto && (\n <div className=\"mt-1 rounded-lg border border-gray-200 px-2.5 py-2 dark:border-gray-700\">\n {campos.length > 0 && (\n <dl className=\"grid grid-cols-[minmax(0,auto)_minmax(0,1fr)] gap-x-3 gap-y-1 text-xs\">\n {campos.map((campo) => (\n <div key={campo.chave} className=\"contents\">\n <dt className=\"text-gray-500 dark:text-gray-400\">\n {campo.rotulo}\n </dt>\n <dd className=\"truncate font-medium text-gray-800 dark:text-gray-100\">\n {campo.valor}\n </dd>\n </div>\n ))}\n </dl>\n )}\n\n {tool.result !== undefined && (\n <Resultado\n texto={tool.result}\n erro={tool.isError}\n separar={campos.length > 0}\n />\n )}\n </div>\n )}\n </div>\n )\n}\n\n/**\n * O retorno da ferramenta.\n *\n * Quando e JSON (uma listagem, por exemplo), fica atras de um toggle: e dado\n * tecnico que so interessa para conferencia, e o usuario ja tem a resposta do\n * agente logo abaixo. Mensagens em texto — erros de permissao, cancelamentos —\n * aparecem direto, porque explicam o que aconteceu.\n */\nconst Resultado = ({\n texto,\n erro,\n separar,\n}: {\n texto: string\n erro?: boolean\n /** Linha divisoria: so faz sentido quando ha parametros logo acima. */\n separar?: boolean\n}) => {\n const [mostrarBruto, setMostrarBruto] = useState(false)\n const ehJson = /^\\s*[[{]/.test(texto)\n const separador = separar\n ? 'mt-2 border-t border-gray-200 pt-2 dark:border-gray-700'\n : ''\n\n if (!ehJson) {\n return (\n <p\n className={classNames(\n 'text-xs',\n separador,\n erro\n ? 'text-red-600 dark:text-red-400'\n : 'text-gray-600 dark:text-gray-300',\n )}\n >\n {texto}\n </p>\n )\n }\n\n return (\n <div className={separador}>\n <button\n type=\"button\"\n className=\"text-xs font-medium text-gray-500 underline-offset-2 hover:underline dark:text-gray-400\"\n onClick={() => setMostrarBruto((atual) => !atual)}\n >\n {mostrarBruto ? 'Ocultar' : 'Ver'} dados retornados\n </button>\n {mostrarBruto && (\n <pre className=\"mt-1.5 max-h-40 overflow-auto whitespace-pre-wrap break-words rounded border border-gray-200 bg-white p-2 text-xs text-gray-600 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300\">\n {texto}\n </pre>\n )}\n </div>\n )\n}\n\nexport default ToolCallChip\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ToolCallChip.js","sources":["../../../../../../../lib/@ecme/views/concepts/ai/Chat/components/ToolCallChip.tsx"],"sourcesContent":["import { useState } from 'react'\nimport {\n TbAlertTriangle,\n TbCheck,\n TbChevronDown,\n TbClock,\n TbLoader2,\n} from 'react-icons/tb'\nimport classNames from '@/components/ui/utils/classNames'\nimport {\n camposVisiveis,\n iconeFerramenta,\n rotularExecucao,\n rotularFerramenta,\n} from './toolPresentation'\nimport type { ToolCall } from '../types'\n\n/**\n * Uma consulta ou operacao que o agente executou, no meio da conversa.\n *\n * Deliberadamente discreto: e informacao de apoio, nao a resposta. Por isso vive\n * FORA do balao de fala (ver ChatView) e tem forma de pilula — contorno fino,\n * largura do conteudo, letra menor — para nunca ser lido como uma segunda\n * mensagem do agente. Fechado mostra so o que aconteceu e em que estado esta;\n * aberto, os parametros ja traduzidos para a linguagem do negocio.\n */\n\ntype Estado = {\n texto: string\n Icone: typeof TbCheck\n classe: string\n /**\n * Se o texto do estado acompanha o icone na pilula.\n *\n * So quando o icone sozinho nao basta: \"aguardando confirmacao\" e \"nao\n * concluida\" pedem uma acao ou explicam uma falha. Em execucao o rotulo ja\n * esta no gerundio (\"Consultando contratos…\") e o ✓ de concluida e obvio —\n * escrever isso de novo so engorda a pilula.\n */\n mostrarTexto?: boolean\n}\n\nconst estadoDe = (tool: ToolCall): Estado => {\n if (tool.awaitingApproval) {\n return {\n texto: 'Aguardando confirmação',\n Icone: TbClock,\n classe: 'text-amber-600 dark:text-amber-400',\n mostrarTexto: true,\n }\n }\n if (tool.running) {\n return {\n texto: 'Em andamento',\n Icone: TbLoader2,\n classe: 'text-blue-600 dark:text-blue-400',\n }\n }\n if (tool.isError) {\n return {\n texto: 'Não concluída',\n Icone: TbAlertTriangle,\n classe: 'text-red-600 dark:text-red-400',\n mostrarTexto: true,\n }\n }\n return {\n texto: 'Concluída',\n Icone: TbCheck,\n classe: 'text-emerald-600 dark:text-emerald-400',\n }\n}\n\ntype ToolCallChipProps = {\n tool: ToolCall\n}\n\nconst ToolCallChip = ({ tool }: ToolCallChipProps) => {\n const [aberto, setAberto] = useState(false)\n const estado = estadoDe(tool)\n const IconeFerramenta = iconeFerramenta(tool.name)\n const emAndamento = tool.running && !tool.awaitingApproval\n const campos = camposVisiveis(tool.args, tool.name)\n const rotulo = emAndamento\n ? `${rotularExecucao(tool.name)}…`\n : rotularFerramenta(tool.name)\n\n return (\n // not-prose: o balao do agente e `prose`, que aplicaria margens de\n // tipografia no dl/dt/dd/pre daqui e desmontaria a compactacao.\n <div className=\"not-prose max-w-full\">\n <button\n type=\"button\"\n className=\"inline-flex w-fit max-w-full items-center gap-1.5 rounded-full border border-gray-200 px-2.5 py-1 text-left transition-colors hover:border-gray-300 hover:bg-gray-50 dark:border-gray-700 dark:hover:border-gray-600 dark:hover:bg-gray-800/60\"\n aria-expanded={aberto}\n title={`${rotulo} — ${estado.texto}`}\n onClick={() => setAberto((atual) => !atual)}\n >\n <IconeFerramenta\n size={13}\n className=\"shrink-0 text-gray-400 dark:text-gray-500\"\n />\n\n <span className=\"truncate text-xs font-medium text-gray-600 dark:text-gray-300\">\n {rotulo}\n </span>\n\n <span\n className={classNames(\n 'flex shrink-0 items-center gap-1 text-xs font-medium',\n estado.classe,\n )}\n >\n <estado.Icone\n size={12}\n className={emAndamento ? 'motion-safe:animate-spin' : ''}\n />\n {estado.mostrarTexto && estado.texto}\n </span>\n\n <TbChevronDown\n size={12}\n className={classNames(\n 'shrink-0 text-gray-400 transition-transform',\n aberto && 'rotate-180',\n )}\n />\n </button>\n\n {aberto && (\n <div className=\"mt-1 rounded-lg border border-gray-200 px-2.5 py-2 dark:border-gray-700\">\n {campos.length > 0 && (\n <dl className=\"grid grid-cols-[minmax(0,auto)_minmax(0,1fr)] gap-x-3 gap-y-1 text-xs\">\n {campos.map((campo) => (\n <div key={campo.chave} className=\"contents\">\n <dt className=\"text-gray-500 dark:text-gray-400\">\n {campo.rotulo}\n </dt>\n <dd className=\"truncate font-medium text-gray-800 dark:text-gray-100\">\n {campo.valor}\n </dd>\n </div>\n ))}\n </dl>\n )}\n\n {tool.result !== undefined && (\n <Resultado\n texto={tool.result}\n erro={tool.isError}\n separar={campos.length > 0}\n />\n )}\n </div>\n )}\n </div>\n )\n}\n\n/**\n * O retorno da ferramenta.\n *\n * Quando e JSON (uma listagem, por exemplo), fica atras de um toggle: e dado\n * tecnico que so interessa para conferencia, e o usuario ja tem a resposta do\n * agente logo abaixo. Mensagens em texto — erros de permissao, cancelamentos —\n * aparecem direto, porque explicam o que aconteceu.\n */\nconst Resultado = ({\n texto,\n erro,\n separar,\n}: {\n texto: string\n erro?: boolean\n /** Linha divisoria: so faz sentido quando ha parametros logo acima. */\n separar?: boolean\n}) => {\n const [mostrarBruto, setMostrarBruto] = useState(false)\n const ehJson = /^\\s*[[{]/.test(texto)\n const separador = separar\n ? 'mt-2 border-t border-gray-200 pt-2 dark:border-gray-700'\n : ''\n\n if (!ehJson) {\n return (\n <p\n className={classNames(\n 'text-xs',\n separador,\n erro\n ? 'text-red-600 dark:text-red-400'\n : 'text-gray-600 dark:text-gray-300',\n )}\n >\n {texto}\n </p>\n )\n }\n\n return (\n <div className={separador}>\n <button\n type=\"button\"\n className=\"text-xs font-medium text-gray-500 underline-offset-2 hover:underline dark:text-gray-400\"\n onClick={() => setMostrarBruto((atual) => !atual)}\n >\n {mostrarBruto ? 'Ocultar' : 'Ver'} dados retornados\n </button>\n {mostrarBruto && (\n <pre className=\"mt-1.5 max-h-40 overflow-auto whitespace-pre-wrap break-words rounded border border-gray-200 bg-white p-2 text-xs text-gray-600 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300\">\n {texto}\n </pre>\n )}\n </div>\n )\n}\n\nexport default ToolCallChip\n"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { formatQuantity } from "../../../../../../base/utils/functions/stringUtils.js";
2
- import { TbFilePlus, TbListSearch, TbSearch, TbTool } from "react-icons/tb";
2
+ import { TbForms, TbPackage, TbPackages, TbBriefcase, TbCalendarEvent, TbUserSearch, TbBuildingPlus, TbListSearch, TbSearch, TbFilePlus, TbTool } from "react-icons/tb";
3
3
  const FERRAMENTAS = {
4
4
  buscar_contrato_armazenagem_graos: {
5
5
  rotulo: "Consulta de contrato",
@@ -15,6 +15,53 @@ const FERRAMENTAS = {
15
15
  rotulo: "Criação de contrato de armazenagem",
16
16
  executando: "Criando contrato",
17
17
  icone: TbFilePlus
18
+ },
19
+ buscar_area: {
20
+ rotulo: "Consulta de área",
21
+ executando: "Consultando área",
22
+ icone: TbSearch
23
+ },
24
+ listar_areas: {
25
+ rotulo: "Consulta de áreas",
26
+ executando: "Consultando áreas",
27
+ icone: TbListSearch
28
+ },
29
+ criar_area: {
30
+ rotulo: "Criação de área",
31
+ executando: "Criando área",
32
+ icone: TbBuildingPlus
33
+ },
34
+ buscar_usuario: {
35
+ rotulo: "Consulta de usuário",
36
+ executando: "Consultando usuário",
37
+ icone: TbUserSearch
38
+ },
39
+ listar_safras: {
40
+ rotulo: "Consulta de safras",
41
+ executando: "Consultando safras",
42
+ icone: TbCalendarEvent
43
+ },
44
+ listar_parceiros: {
45
+ rotulo: "Consulta de parceiros de negócio",
46
+ executando: "Consultando parceiros",
47
+ icone: TbBriefcase
48
+ },
49
+ listar_produtos: {
50
+ rotulo: "Consulta de produtos",
51
+ executando: "Consultando produtos",
52
+ icone: TbPackages
53
+ },
54
+ buscar_produto: {
55
+ rotulo: "Consulta de produto",
56
+ executando: "Consultando produto",
57
+ icone: TbPackage
58
+ },
59
+ // Nao executa nada no ERP: e o sinal de que faltam campos. Mas o agente a
60
+ // pede como qualquer outra, entao um chip aparece junto do formulario.
61
+ solicitar_dados: {
62
+ rotulo: "Dados necessários",
63
+ executando: "Verificando os dados",
64
+ icone: TbForms
18
65
  }
19
66
  };
20
67
  const humanizar = (texto) => texto.replaceAll("_", " ").toLowerCase().replace(/^./, (letra) => letra.toUpperCase());
@@ -29,20 +76,53 @@ const CAMPOS = {
29
76
  tipo_contrato: "Tipo de contrato",
30
77
  contrato_venda: "Contrato de venda",
31
78
  ordem_venda: "Ordem de venda",
32
- id: "Contrato",
79
+ id: "Código",
80
+ nome: "Nome",
81
+ responsavel_id: "Responsável",
82
+ responsavel_nome: "Nome do responsável",
83
+ // Safra, produto e parceiro de negócio
84
+ safra: "Safra",
85
+ ids: "Códigos",
86
+ search: "Busca",
87
+ codigo: "Código",
88
+ descricao: "Descrição",
89
+ tipo_produto: "Tipo do produto",
90
+ is_cultura: "É cultura",
91
+ status_material: "Situação",
92
+ business_partner: "Código do parceiro",
93
+ nome_completo: "Nome do parceiro",
94
+ cpf_ou_cnpj: "CPF ou CNPJ",
95
+ cpf: "CPF",
96
+ cnpj: "CNPJ",
97
+ is_cliente: "É cliente",
98
+ is_produtor: "É produtor",
99
+ is_transportadora: "É transportadora",
100
+ is_armazem: "É armazém",
101
+ status_elegibilidade: "Elegibilidade",
33
102
  page_number: "Página",
34
103
  page_size: "Itens por página",
35
104
  sort_field: "Ordenar por",
36
105
  sort_order: "Ordem",
37
106
  filtro: "Filtro"
38
107
  };
39
- const rotularCampo = (chave) => CAMPOS[chave] ?? humanizar(chave);
108
+ const CAMPO_POR_FERRAMENTA = {
109
+ buscar_contrato_armazenagem_graos: { id: "Contrato" },
110
+ buscar_area: { id: "Área" },
111
+ listar_areas: { id: "Área" },
112
+ buscar_usuario: { id: "Usuário" },
113
+ listar_safras: { id: "Safra" },
114
+ listar_parceiros: { id: "Parceiro" },
115
+ listar_produtos: { id: "Produto" },
116
+ buscar_produto: { id: "Produto" }
117
+ };
118
+ const rotularCampo = (chave, ferramenta) => CAMPO_POR_FERRAMENTA[ferramenta ?? ""]?.[chave] ?? CAMPOS[chave] ?? humanizar(chave);
40
119
  const REFERENCIAS = /* @__PURE__ */ new Set([
41
120
  "business_partner_id",
42
121
  "produto_id",
43
122
  "safra_id",
44
123
  "contrato_venda",
45
124
  "ordem_venda",
125
+ "responsavel_id",
46
126
  "id"
47
127
  ]);
48
128
  const ORDENACAO = { ASC: "Crescente", DESC: "Decrescente" };
@@ -58,7 +138,7 @@ const tipoDoCampo = (chave) => {
58
138
  if (REFERENCIAS.has(chave) || chave === "saldo_negociado") return "numero";
59
139
  return "texto";
60
140
  };
61
- const formatarValor = (chave, valor) => {
141
+ const formatarValor = (chave, valor, ferramenta) => {
62
142
  if (valor === null || valor === void 0 || valor === "") {
63
143
  return "—";
64
144
  }
@@ -85,11 +165,13 @@ const formatarValor = (chave, valor) => {
85
165
  if (!entradas.length) {
86
166
  return "—";
87
167
  }
88
- return entradas.map(([k, v]) => `${rotularCampo(k)}: ${formatarValor(k, v)}`).join(", ");
168
+ return entradas.map(
169
+ ([k, v]) => `${rotularCampo(k, ferramenta)}: ${formatarValor(k, v, ferramenta)}`
170
+ ).join(", ");
89
171
  }
90
172
  return String(valor);
91
173
  };
92
- const camposVisiveis = (args) => Object.entries(args).filter(([, valor]) => {
174
+ const camposVisiveis = (args, ferramenta) => Object.entries(args).filter(([, valor]) => {
93
175
  if (valor === null || valor === void 0 || valor === "") return false;
94
176
  if (typeof valor === "object" && !Array.isArray(valor)) {
95
177
  return Object.keys(valor).length > 0;
@@ -97,8 +179,8 @@ const camposVisiveis = (args) => Object.entries(args).filter(([, valor]) => {
97
179
  return true;
98
180
  }).map(([chave, valor]) => ({
99
181
  chave,
100
- rotulo: rotularCampo(chave),
101
- valor: formatarValor(chave, valor)
182
+ rotulo: rotularCampo(chave, ferramenta),
183
+ valor: formatarValor(chave, valor, ferramenta)
102
184
  }));
103
185
  export {
104
186
  camposVisiveis,
@@ -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/**\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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"}
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 {\n TbBriefcase,\n TbBuildingPlus,\n TbCalendarEvent,\n TbFilePlus,\n TbForms,\n TbListSearch,\n TbPackage,\n TbPackages,\n TbSearch,\n TbTool,\n TbUserSearch,\n} 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 buscar_area: {\n rotulo: 'Consulta de área',\n executando: 'Consultando área',\n icone: TbSearch,\n },\n listar_areas: {\n rotulo: 'Consulta de áreas',\n executando: 'Consultando áreas',\n icone: TbListSearch,\n },\n criar_area: {\n rotulo: 'Criação de área',\n executando: 'Criando área',\n icone: TbBuildingPlus,\n },\n buscar_usuario: {\n rotulo: 'Consulta de usuário',\n executando: 'Consultando usuário',\n icone: TbUserSearch,\n },\n listar_safras: {\n rotulo: 'Consulta de safras',\n executando: 'Consultando safras',\n icone: TbCalendarEvent,\n },\n listar_parceiros: {\n rotulo: 'Consulta de parceiros de negócio',\n executando: 'Consultando parceiros',\n icone: TbBriefcase,\n },\n listar_produtos: {\n rotulo: 'Consulta de produtos',\n executando: 'Consultando produtos',\n icone: TbPackages,\n },\n buscar_produto: {\n rotulo: 'Consulta de produto',\n executando: 'Consultando produto',\n icone: TbPackage,\n },\n // Nao executa nada no ERP: e o sinal de que faltam campos. Mas o agente a\n // pede como qualquer outra, entao um chip aparece junto do formulario.\n solicitar_dados: {\n rotulo: 'Dados necessários',\n executando: 'Verificando os dados',\n icone: TbForms,\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: 'Código',\n nome: 'Nome',\n responsavel_id: 'Responsável',\n responsavel_nome: 'Nome do responsável',\n // Safra, produto e parceiro de negócio\n safra: 'Safra',\n ids: 'Códigos',\n search: 'Busca',\n codigo: 'Código',\n descricao: 'Descrição',\n tipo_produto: 'Tipo do produto',\n is_cultura: 'É cultura',\n status_material: 'Situação',\n business_partner: 'Código do parceiro',\n nome_completo: 'Nome do parceiro',\n cpf_ou_cnpj: 'CPF ou CNPJ',\n cpf: 'CPF',\n cnpj: 'CNPJ',\n is_cliente: 'É cliente',\n is_produtor: 'É produtor',\n is_transportadora: 'É transportadora',\n is_armazem: 'É armazém',\n status_elegibilidade: 'Elegibilidade',\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\n/**\n * Rotulos que dependem da ferramenta.\n *\n * O `id` e o caso: o mesmo parametro nomeia entidades diferentes conforme quem\n * o recebe. Sem isto, buscar o usuario 5 exibiria \"Contrato: #5\".\n */\nconst CAMPO_POR_FERRAMENTA: Record<string, Record<string, string>> = {\n buscar_contrato_armazenagem_graos: { id: 'Contrato' },\n buscar_area: { id: 'Área' },\n listar_areas: { id: 'Área' },\n buscar_usuario: { id: 'Usuário' },\n listar_safras: { id: 'Safra' },\n listar_parceiros: { id: 'Parceiro' },\n listar_produtos: { id: 'Produto' },\n buscar_produto: { id: 'Produto' },\n}\n\nexport const rotularCampo = (chave: string, ferramenta?: string) =>\n CAMPO_POR_FERRAMENTA[ferramenta ?? '']?.[chave] ??\n CAMPOS[chave] ??\n 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 'responsavel_id',\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 = (\n chave: string,\n valor: unknown,\n ferramenta?: string,\n): 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(\n ([k, v]) =>\n `${rotularCampo(k, ferramenta)}: ${formatarValor(k, v, ferramenta)}`,\n )\n .join(', ')\n }\n return String(valor)\n}\n\n/**\n * Pares (rotulo, valor) prontos para exibir, ignorando o que esta vazio.\n *\n * `ferramenta` resolve os rotulos ambiguos (ver CAMPO_POR_FERRAMENTA); quem nao\n * a conhece — o formulario de coleta, por exemplo — pode omitir.\n */\nexport const camposVisiveis = (\n args: Record<string, unknown>,\n ferramenta?: string,\n) =>\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, ferramenta),\n valor: formatarValor(chave, valor, ferramenta),\n }))\n"],"names":[],"mappings":"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"}
@@ -99,9 +99,8 @@ const useChatSend = () => {
99
99
  });
100
100
  break;
101
101
  case "done":
102
- updateConversation(chatId, messageId, {
103
- content: evento.data.content
104
- });
102
+ texto = inicial.texto + evento.data.content;
103
+ updateConversation(chatId, messageId, { content: texto });
105
104
  break;
106
105
  case "error":
107
106
  updateConversation(chatId, messageId, {
@@ -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 '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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"}
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 é a fonte autoritativa do texto DESTA requisição —\n // se algum token se perdeu, ele corrige.\n //\n // Numa retomada, porém, o que o agente disse ANTES da pausa\n // não vem nele (o backend acumula as falas por requisição):\n // veio em `inicial.texto`. Sem concatenar, a bolha encolhia\n // para só a frase final — \"Vou criar a área para você…\"\n // virava \"Nada foi criado\" — e o agente parecia ter\n // esquecido tudo. No primeiro envio `inicial.texto` é '',\n // então nada muda ali.\n texto = inicial.texto + evento.data.content\n updateConversation(chatId, messageId, { content: texto })\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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"}
@@ -1 +1 @@
1
- {"version":3,"file":"ChatLandingView.d.ts","sourceRoot":"","sources":["../../../../../../../../lib/@ecme/views/concepts/ai/Chat/components/ChatLandingView.tsx"],"names":[],"mappings":"AA6CA,QAAA,MAAM,eAAe,+CAmCpB,CAAA;AAED,eAAe,eAAe,CAAA"}
1
+ {"version":3,"file":"ChatLandingView.d.ts","sourceRoot":"","sources":["../../../../../../../../lib/@ecme/views/concepts/ai/Chat/components/ChatLandingView.tsx"],"names":[],"mappings":"AA+CA,QAAA,MAAM,eAAe,+CAyCpB,CAAA;AAED,eAAe,eAAe,CAAA"}
@@ -2,7 +2,7 @@ import type { IconType } from 'react-icons';
2
2
  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
- export declare const rotularCampo: (chave: string) => string;
5
+ export declare const rotularCampo: (chave: string, ferramenta?: string) => string;
6
6
  export declare const opcoesDoCampo: (chave: string) => {
7
7
  value: string;
8
8
  label: string;
@@ -16,9 +16,14 @@ export declare const opcoesDoCampo: (chave: string) => {
16
16
  */
17
17
  export declare const tipoDoCampo: (chave: string) => "numero" | "enum" | "texto";
18
18
  /** Converte o valor tecnico no que o usuario espera ler. */
19
- export declare const formatarValor: (chave: string, valor: unknown) => string;
20
- /** Pares (rotulo, valor) prontos para exibir, ignorando o que esta vazio. */
21
- export declare const camposVisiveis: (args: Record<string, unknown>) => {
19
+ export declare const formatarValor: (chave: string, valor: unknown, ferramenta?: string) => string;
20
+ /**
21
+ * Pares (rotulo, valor) prontos para exibir, ignorando o que esta vazio.
22
+ *
23
+ * `ferramenta` resolve os rotulos ambiguos (ver CAMPO_POR_FERRAMENTA); quem nao
24
+ * a conhece — o formulario de coleta, por exemplo — pode omitir.
25
+ */
26
+ export declare const camposVisiveis: (args: Record<string, unknown>, ferramenta?: string) => {
22
27
  chave: string;
23
28
  rotulo: string;
24
29
  valor: string;
@@ -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;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
+ {"version":3,"file":"toolPresentation.d.ts","sourceRoot":"","sources":["../../../../../../../../lib/@ecme/views/concepts/ai/Chat/components/toolPresentation.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AA2F3C,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;AA0DtC,eAAO,MAAM,YAAY,GAAI,OAAO,MAAM,EAAE,aAAa,MAAM,WAG3C,CAAA;AAyBpB,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,GACtB,OAAO,MAAM,EACb,OAAO,OAAO,EACd,aAAa,MAAM,KACpB,MAoCF,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,cAAc,GACvB,MAAM,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,aAAa,MAAM;;;;GAcZ,CAAA"}
@@ -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;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"}
1
+ {"version":3,"file":"useChatSend.d.ts","sourceRoot":"","sources":["../../../../../../../../lib/@ecme/views/concepts/ai/Chat/hooks/useChatSend.ts"],"names":[],"mappings":"AAWA,QAAA,MAAM,WAAW;yBA4LqB,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"}
@@ -36,7 +36,7 @@ const ApprovalCard = ({ approval, onDecide }: ApprovalCardProps) => {
36
36
  <div className="px-4 py-3.5">
37
37
  {approval.calls.map((call) => {
38
38
  const Icone = iconeFerramenta(call.name)
39
- const campos = camposVisiveis(call.args)
39
+ const campos = camposVisiveis(call.args, call.name)
40
40
 
41
41
  return (
42
42
  <div key={call.id} className="mb-4 last:mb-0">
@@ -1,45 +1,47 @@
1
1
  import useChatSend from '../hooks/useChatSend'
2
- import {
3
- PiLightbulbDuotone,
4
- PiBookOpenTextDuotone,
5
- PiCompassDuotone,
6
- PiCodeDuotone,
7
- } from 'react-icons/pi'
8
- import type { ReactNode } from 'react'
9
-
10
- type PromptType = 'idea' | 'guide' | 'writing' | 'coding'
11
-
12
- const suggeustionIcon: Record<PromptType, ReactNode> = {
13
- idea: <PiLightbulbDuotone className="text-blue-500" />,
14
- guide: <PiCompassDuotone className="text-emerald-500" />,
15
- writing: <PiBookOpenTextDuotone className="text-amber-500" />,
16
- coding: <PiCodeDuotone className="text-indigo-500" />,
17
- }
2
+ import { iconeFerramenta } from './toolPresentation'
18
3
 
4
+ /**
5
+ * O que o agente sabe fazer, em quatro exemplos que executam de verdade.
6
+ *
7
+ * `title` e o rotulo curto do card; `prompt` e a frase enviada ao agente.
8
+ * `tool` nomeia a ferramenta que o exemplo exercita e de onde sai o icone —
9
+ * o MESMO que aparecera no chip quando ela rodar, entao o card e a execucao
10
+ * falam a mesma lingua e ha um registro so (ver toolPresentation.ts).
11
+ *
12
+ * Os dois exemplos de criacao mandam a intencao SEM os dados de proposito: o
13
+ * agente pede os campos que faltam por formulario e so entao abre a tela de
14
+ * aprovacao — o fluxo humano inteiro em um clique. Nada e gravado sem confirmar.
15
+ */
19
16
  const promptSuggestion: {
20
17
  title: string
21
18
  prompt: string
22
- type: PromptType
19
+ tool: string
20
+ cor: string
23
21
  }[] = [
24
22
  {
25
- title: 'Como consultar um pedido de venda?',
26
- prompt: 'Como faço para consultar um pedido de venda no Orbe Agro?',
27
- type: 'guide',
23
+ title: 'Buscar um usuário pelo ID',
24
+ prompt: 'Quem é o usuário de um dado id?',
25
+ tool: 'buscar_usuario',
26
+ cor: 'text-blue-500',
28
27
  },
29
28
  {
30
- title: 'Para que serve o módulo de Trade?',
31
- prompt: 'Explique para que serve o módulo de Trade no Orbe Agro.',
32
- type: 'idea',
29
+ title: 'Consultar uma área pelo código',
30
+ prompt: 'Me mostra a área de um dado id?',
31
+ tool: 'buscar_area',
32
+ cor: 'text-emerald-500',
33
33
  },
34
34
  {
35
- title: 'Como abrir um processo de armazenagem?',
36
- prompt: 'Quais são os passos para abrir um processo de armazenagem de grãos no Orbe Agro?',
37
- type: 'writing',
35
+ title: 'Criar uma nova área',
36
+ prompt: 'Quero criar uma área nova',
37
+ tool: 'criar_area',
38
+ cor: 'text-amber-500',
38
39
  },
39
40
  {
40
- title: 'Onde vejo o limite de crédito de um cliente?',
41
- prompt: 'Onde encontro o limite de crédito de um cliente no Orbe Agro?',
42
- type: 'coding',
41
+ title: 'Criar um contrato de compra de grãos',
42
+ prompt: 'Quero criar um contrato de compra de armazenagem de grãos',
43
+ tool: 'criar_contrato_armazenagem_graos',
44
+ cor: 'text-indigo-500',
43
45
  },
44
46
  ]
45
47
 
@@ -57,23 +59,29 @@ const ChatLandingView = () => {
57
59
  <span>Como posso te ajudar hoje?</span>
58
60
  </div>
59
61
  <div className="mt-6 grid grid-cols-1 sm:grid-cols-2 gap-3">
60
- {promptSuggestion.map((suggestion) => (
61
- <div
62
- key={suggestion.title}
63
- className="flex flex-col gap-3 justify-between rounded-xl bg-gray-100 dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-gray-600 p-4 min-h-28 cursor-pointer"
64
- role="button"
65
- onClick={() => handleSend(suggestion.title)}
66
- >
67
- <h6 className="font-normal">{suggestion.title}</h6>
68
- <div>
69
- <div className="bg-white dark:bg-gray-800 rounded-full p-2 inline-flex">
70
- <span className="text-2xl">
71
- {suggeustionIcon[suggestion.type]}
72
- </span>
62
+ {promptSuggestion.map((suggestion) => {
63
+ const Icone = iconeFerramenta(suggestion.tool)
64
+
65
+ return (
66
+ <div
67
+ key={suggestion.title}
68
+ className="flex flex-col gap-3 justify-between rounded-xl bg-gray-100 dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-gray-600 p-4 min-h-28 cursor-pointer"
69
+ role="button"
70
+ onClick={() => handleSend(suggestion.prompt)}
71
+ >
72
+ <h6 className="font-normal">
73
+ {suggestion.title}
74
+ </h6>
75
+ <div>
76
+ <div className="bg-white dark:bg-gray-800 rounded-full p-2 inline-flex">
77
+ <span className="text-2xl">
78
+ <Icone className={suggestion.cor} />
79
+ </span>
80
+ </div>
73
81
  </div>
74
82
  </div>
75
- </div>
76
- ))}
83
+ )
84
+ })}
77
85
  </div>
78
86
  </div>
79
87
  </div>
@@ -80,7 +80,7 @@ const ToolCallChip = ({ tool }: ToolCallChipProps) => {
80
80
  const estado = estadoDe(tool)
81
81
  const IconeFerramenta = iconeFerramenta(tool.name)
82
82
  const emAndamento = tool.running && !tool.awaitingApproval
83
- const campos = camposVisiveis(tool.args)
83
+ const campos = camposVisiveis(tool.args, tool.name)
84
84
  const rotulo = emAndamento
85
85
  ? `${rotularExecucao(tool.name)}…`
86
86
  : rotularFerramenta(tool.name)
@@ -1,5 +1,17 @@
1
1
  import { formatQuantity } from '@base/utils/functions/stringUtils'
2
- import { TbFilePlus, TbListSearch, TbSearch, TbTool } from 'react-icons/tb'
2
+ import {
3
+ TbBriefcase,
4
+ TbBuildingPlus,
5
+ TbCalendarEvent,
6
+ TbFilePlus,
7
+ TbForms,
8
+ TbListSearch,
9
+ TbPackage,
10
+ TbPackages,
11
+ TbSearch,
12
+ TbTool,
13
+ TbUserSearch,
14
+ } from 'react-icons/tb'
3
15
  import type { IconType } from 'react-icons'
4
16
 
5
17
  /**
@@ -36,6 +48,53 @@ const FERRAMENTAS: Record<string, Ferramenta> = {
36
48
  executando: 'Criando contrato',
37
49
  icone: TbFilePlus,
38
50
  },
51
+ buscar_area: {
52
+ rotulo: 'Consulta de área',
53
+ executando: 'Consultando área',
54
+ icone: TbSearch,
55
+ },
56
+ listar_areas: {
57
+ rotulo: 'Consulta de áreas',
58
+ executando: 'Consultando áreas',
59
+ icone: TbListSearch,
60
+ },
61
+ criar_area: {
62
+ rotulo: 'Criação de área',
63
+ executando: 'Criando área',
64
+ icone: TbBuildingPlus,
65
+ },
66
+ buscar_usuario: {
67
+ rotulo: 'Consulta de usuário',
68
+ executando: 'Consultando usuário',
69
+ icone: TbUserSearch,
70
+ },
71
+ listar_safras: {
72
+ rotulo: 'Consulta de safras',
73
+ executando: 'Consultando safras',
74
+ icone: TbCalendarEvent,
75
+ },
76
+ listar_parceiros: {
77
+ rotulo: 'Consulta de parceiros de negócio',
78
+ executando: 'Consultando parceiros',
79
+ icone: TbBriefcase,
80
+ },
81
+ listar_produtos: {
82
+ rotulo: 'Consulta de produtos',
83
+ executando: 'Consultando produtos',
84
+ icone: TbPackages,
85
+ },
86
+ buscar_produto: {
87
+ rotulo: 'Consulta de produto',
88
+ executando: 'Consultando produto',
89
+ icone: TbPackage,
90
+ },
91
+ // Nao executa nada no ERP: e o sinal de que faltam campos. Mas o agente a
92
+ // pede como qualquer outra, entao um chip aparece junto do formulario.
93
+ solicitar_dados: {
94
+ rotulo: 'Dados necessários',
95
+ executando: 'Verificando os dados',
96
+ icone: TbForms,
97
+ },
39
98
  }
40
99
 
41
100
  const humanizar = (texto: string) =>
@@ -62,7 +121,29 @@ const CAMPOS: Record<string, string> = {
62
121
  tipo_contrato: 'Tipo de contrato',
63
122
  contrato_venda: 'Contrato de venda',
64
123
  ordem_venda: 'Ordem de venda',
65
- id: 'Contrato',
124
+ id: 'Código',
125
+ nome: 'Nome',
126
+ responsavel_id: 'Responsável',
127
+ responsavel_nome: 'Nome do responsável',
128
+ // Safra, produto e parceiro de negócio
129
+ safra: 'Safra',
130
+ ids: 'Códigos',
131
+ search: 'Busca',
132
+ codigo: 'Código',
133
+ descricao: 'Descrição',
134
+ tipo_produto: 'Tipo do produto',
135
+ is_cultura: 'É cultura',
136
+ status_material: 'Situação',
137
+ business_partner: 'Código do parceiro',
138
+ nome_completo: 'Nome do parceiro',
139
+ cpf_ou_cnpj: 'CPF ou CNPJ',
140
+ cpf: 'CPF',
141
+ cnpj: 'CNPJ',
142
+ is_cliente: 'É cliente',
143
+ is_produtor: 'É produtor',
144
+ is_transportadora: 'É transportadora',
145
+ is_armazem: 'É armazém',
146
+ status_elegibilidade: 'Elegibilidade',
66
147
  page_number: 'Página',
67
148
  page_size: 'Itens por página',
68
149
  sort_field: 'Ordenar por',
@@ -70,7 +151,27 @@ const CAMPOS: Record<string, string> = {
70
151
  filtro: 'Filtro',
71
152
  }
72
153
 
73
- export const rotularCampo = (chave: string) => CAMPOS[chave] ?? humanizar(chave)
154
+ /**
155
+ * Rotulos que dependem da ferramenta.
156
+ *
157
+ * O `id` e o caso: o mesmo parametro nomeia entidades diferentes conforme quem
158
+ * o recebe. Sem isto, buscar o usuario 5 exibiria "Contrato: #5".
159
+ */
160
+ const CAMPO_POR_FERRAMENTA: Record<string, Record<string, string>> = {
161
+ buscar_contrato_armazenagem_graos: { id: 'Contrato' },
162
+ buscar_area: { id: 'Área' },
163
+ listar_areas: { id: 'Área' },
164
+ buscar_usuario: { id: 'Usuário' },
165
+ listar_safras: { id: 'Safra' },
166
+ listar_parceiros: { id: 'Parceiro' },
167
+ listar_produtos: { id: 'Produto' },
168
+ buscar_produto: { id: 'Produto' },
169
+ }
170
+
171
+ export const rotularCampo = (chave: string, ferramenta?: string) =>
172
+ CAMPO_POR_FERRAMENTA[ferramenta ?? '']?.[chave] ??
173
+ CAMPOS[chave] ??
174
+ humanizar(chave)
74
175
 
75
176
  /** Campos que sao referencia a um registro: exibidos como "#123". */
76
177
  const REFERENCIAS = new Set([
@@ -79,6 +180,7 @@ const REFERENCIAS = new Set([
79
180
  'safra_id',
80
181
  'contrato_venda',
81
182
  'ordem_venda',
183
+ 'responsavel_id',
82
184
  'id',
83
185
  ])
84
186
 
@@ -114,7 +216,11 @@ export const tipoDoCampo = (chave: string): 'numero' | 'enum' | 'texto' => {
114
216
  }
115
217
 
116
218
  /** Converte o valor tecnico no que o usuario espera ler. */
117
- export const formatarValor = (chave: string, valor: unknown): string => {
219
+ export const formatarValor = (
220
+ chave: string,
221
+ valor: unknown,
222
+ ferramenta?: string,
223
+ ): string => {
118
224
  if (valor === null || valor === undefined || valor === '') {
119
225
  return '—'
120
226
  }
@@ -143,14 +249,25 @@ export const formatarValor = (chave: string, valor: unknown): string => {
143
249
  return '—'
144
250
  }
145
251
  return entradas
146
- .map(([k, v]) => `${rotularCampo(k)}: ${formatarValor(k, v)}`)
252
+ .map(
253
+ ([k, v]) =>
254
+ `${rotularCampo(k, ferramenta)}: ${formatarValor(k, v, ferramenta)}`,
255
+ )
147
256
  .join(', ')
148
257
  }
149
258
  return String(valor)
150
259
  }
151
260
 
152
- /** Pares (rotulo, valor) prontos para exibir, ignorando o que esta vazio. */
153
- export const camposVisiveis = (args: Record<string, unknown>) =>
261
+ /**
262
+ * Pares (rotulo, valor) prontos para exibir, ignorando o que esta vazio.
263
+ *
264
+ * `ferramenta` resolve os rotulos ambiguos (ver CAMPO_POR_FERRAMENTA); quem nao
265
+ * a conhece — o formulario de coleta, por exemplo — pode omitir.
266
+ */
267
+ export const camposVisiveis = (
268
+ args: Record<string, unknown>,
269
+ ferramenta?: string,
270
+ ) =>
154
271
  Object.entries(args)
155
272
  .filter(([, valor]) => {
156
273
  if (valor === null || valor === undefined || valor === '') return false
@@ -161,6 +278,6 @@ export const camposVisiveis = (args: Record<string, unknown>) =>
161
278
  })
162
279
  .map(([chave, valor]) => ({
163
280
  chave,
164
- rotulo: rotularCampo(chave),
165
- valor: formatarValor(chave, valor),
281
+ rotulo: rotularCampo(chave, ferramenta),
282
+ valor: formatarValor(chave, valor, ferramenta),
166
283
  }))
@@ -147,11 +147,18 @@ const useChatSend = () => {
147
147
  break
148
148
 
149
149
  case 'done':
150
- // O done traz a resposta final completa e é a fonte
151
- // autoritativa — se algum token se perdeu, ele corrige.
152
- updateConversation(chatId, messageId, {
153
- content: evento.data.content,
154
- })
150
+ // O done é a fonte autoritativa do texto DESTA requisição
151
+ // se algum token se perdeu, ele corrige.
152
+ //
153
+ // Numa retomada, porém, o que o agente disse ANTES da pausa
154
+ // não vem nele (o backend acumula as falas por requisição):
155
+ // veio em `inicial.texto`. Sem concatenar, a bolha encolhia
156
+ // para só a frase final — "Vou criar a área para você…"
157
+ // virava "Nada foi criado" — e o agente parecia ter
158
+ // esquecido tudo. No primeiro envio `inicial.texto` é '',
159
+ // então nada muda ali.
160
+ texto = inicial.texto + evento.data.content
161
+ updateConversation(chatId, messageId, { content: texto })
155
162
  break
156
163
 
157
164
  case 'error':
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orbe-agro/client-core",
3
- "version": "5.6.253",
3
+ "version": "5.6.254",
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",