@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.
- package/dist/@ecme/components/template/Footer.js +1 -1
- package/dist/@ecme/views/concepts/ai/Chat/components/ApprovalCard.js +1 -1
- package/dist/@ecme/views/concepts/ai/Chat/components/ApprovalCard.js.map +1 -1
- package/dist/@ecme/views/concepts/ai/Chat/components/ChatLandingView.js +33 -32
- package/dist/@ecme/views/concepts/ai/Chat/components/ChatLandingView.js.map +1 -1
- package/dist/@ecme/views/concepts/ai/Chat/components/ToolCallChip.js +1 -1
- package/dist/@ecme/views/concepts/ai/Chat/components/ToolCallChip.js.map +1 -1
- package/dist/@ecme/views/concepts/ai/Chat/components/toolPresentation.js +90 -8
- package/dist/@ecme/views/concepts/ai/Chat/components/toolPresentation.js.map +1 -1
- package/dist/@ecme/views/concepts/ai/Chat/hooks/useChatSend.js +2 -3
- package/dist/@ecme/views/concepts/ai/Chat/hooks/useChatSend.js.map +1 -1
- package/dist/@types/@ecme/views/concepts/ai/Chat/components/ChatLandingView.d.ts.map +1 -1
- package/dist/@types/@ecme/views/concepts/ai/Chat/components/toolPresentation.d.ts +9 -4
- package/dist/@types/@ecme/views/concepts/ai/Chat/components/toolPresentation.d.ts.map +1 -1
- package/dist/@types/@ecme/views/concepts/ai/Chat/hooks/useChatSend.d.ts.map +1 -1
- package/lib/@ecme/views/concepts/ai/Chat/components/ApprovalCard.tsx +1 -1
- package/lib/@ecme/views/concepts/ai/Chat/components/ChatLandingView.tsx +52 -44
- package/lib/@ecme/views/concepts/ai/Chat/components/ToolCallChip.tsx +1 -1
- package/lib/@ecme/views/concepts/ai/Chat/components/toolPresentation.ts +126 -9
- package/lib/@ecme/views/concepts/ai/Chat/hooks/useChatSend.ts +12 -5
- package/package.json +1 -1
|
@@ -3,7 +3,7 @@ import Container from "../shared/Container.js";
|
|
|
3
3
|
import classNames from "../../utils/classNames.js";
|
|
4
4
|
import { APP_NAME } from "../../constants/app.constant.js";
|
|
5
5
|
import { PAGE_CONTAINER_GUTTER_X } from "../../constants/theme.constant.js";
|
|
6
|
-
const version = true ? "5.6.
|
|
6
|
+
const version = true ? "5.6.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;
|
|
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 {
|
|
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: "
|
|
13
|
-
prompt: "
|
|
14
|
-
|
|
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: "
|
|
18
|
-
prompt: "
|
|
19
|
-
|
|
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: "
|
|
23
|
-
prompt: "
|
|
24
|
-
|
|
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: "
|
|
28
|
-
prompt: "
|
|
29
|
-
|
|
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) =>
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
|
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 {
|
|
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: "
|
|
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
|
|
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(
|
|
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
|
-
|
|
103
|
-
|
|
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":"
|
|
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
|
-
/**
|
|
21
|
-
|
|
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":"
|
|
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;
|
|
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
|
-
|
|
19
|
+
tool: string
|
|
20
|
+
cor: string
|
|
23
21
|
}[] = [
|
|
24
22
|
{
|
|
25
|
-
title: '
|
|
26
|
-
prompt: '
|
|
27
|
-
|
|
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: '
|
|
31
|
-
prompt: '
|
|
32
|
-
|
|
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: '
|
|
36
|
-
prompt: '
|
|
37
|
-
|
|
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: '
|
|
41
|
-
prompt: '
|
|
42
|
-
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
|
|
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 {
|
|
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: '
|
|
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
|
-
|
|
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 = (
|
|
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(
|
|
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
|
-
/**
|
|
153
|
-
|
|
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
|
|
151
|
-
//
|
|
152
|
-
|
|
153
|
-
|
|
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