@orbe-agro/client-core 5.6.239 → 5.6.241
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/@ecme/assets/styles/app.css +116 -3
- package/dist/@ecme/assets/styles/tailwind/index.css +119 -3
- package/dist/@ecme/components/template/Footer.js +1 -1
- package/dist/@ecme/components/template/OrbeAiWidget/OrbeAiWidget.js +0 -2
- package/dist/@ecme/components/template/OrbeAiWidget/OrbeAiWidget.js.map +1 -1
- package/dist/@ecme/components/view/ChatBox/ChatBox.js +2 -0
- package/dist/@ecme/components/view/ChatBox/ChatBox.js.map +1 -1
- package/dist/@ecme/components/view/ChatBox/components/MessageList.js +2 -1
- package/dist/@ecme/components/view/ChatBox/components/MessageList.js.map +1 -1
- package/dist/@ecme/index.js +3 -4
- package/dist/@ecme/services/AiService.js +53 -24
- package/dist/@ecme/services/AiService.js.map +1 -1
- package/dist/@ecme/services/aiAuthHeaders.js +35 -0
- package/dist/@ecme/services/aiAuthHeaders.js.map +1 -0
- package/dist/@ecme/services/axios/AxiosRequestIntrceptorConfigCallback.js +4 -27
- package/dist/@ecme/services/axios/AxiosRequestIntrceptorConfigCallback.js.map +1 -1
- package/dist/@ecme/services/index.js +4 -5
- package/dist/@ecme/services/sseParser.js +42 -0
- package/dist/@ecme/services/sseParser.js.map +1 -0
- package/dist/@ecme/views/concepts/ai/Chat/components/AiErrorMessage.js +15 -0
- package/dist/@ecme/views/concepts/ai/Chat/components/AiErrorMessage.js.map +1 -0
- package/dist/@ecme/views/concepts/ai/Chat/components/ApprovalCard.js +73 -0
- package/dist/@ecme/views/concepts/ai/Chat/components/ApprovalCard.js.map +1 -0
- package/dist/@ecme/views/concepts/ai/Chat/components/ChatView.js +37 -10
- package/dist/@ecme/views/concepts/ai/Chat/components/ChatView.js.map +1 -1
- package/dist/@ecme/views/concepts/ai/Chat/components/ToolCallChip.js +153 -0
- package/dist/@ecme/views/concepts/ai/Chat/components/ToolCallChip.js.map +1 -0
- package/dist/@ecme/views/concepts/ai/Chat/components/toolPresentation.js +99 -0
- package/dist/@ecme/views/concepts/ai/Chat/components/toolPresentation.js.map +1 -0
- package/dist/@ecme/views/concepts/ai/Chat/hooks/useChatSend.js +111 -17
- package/dist/@ecme/views/concepts/ai/Chat/hooks/useChatSend.js.map +1 -1
- package/dist/@ecme/views/concepts/ai/Chat/store/generativeChatStore.js +29 -13
- package/dist/@ecme/views/concepts/ai/Chat/store/generativeChatStore.js.map +1 -1
- package/dist/@types/@ecme/components/template/OrbeAiWidget/OrbeAiWidget.d.ts.map +1 -1
- package/dist/@types/@ecme/components/view/ChatBox/ChatBox.d.ts.map +1 -1
- package/dist/@types/@ecme/components/view/ChatBox/components/MessageList.d.ts +2 -0
- package/dist/@types/@ecme/components/view/ChatBox/components/MessageList.d.ts.map +1 -1
- package/dist/@types/@ecme/services/AiService.d.ts +28 -15
- package/dist/@types/@ecme/services/AiService.d.ts.map +1 -1
- package/dist/@types/@ecme/services/aiAuthHeaders.d.ts +12 -0
- package/dist/@types/@ecme/services/aiAuthHeaders.d.ts.map +1 -0
- package/dist/@types/@ecme/services/axios/AxiosRequestIntrceptorConfigCallback.d.ts +6 -0
- package/dist/@types/@ecme/services/axios/AxiosRequestIntrceptorConfigCallback.d.ts.map +1 -1
- package/dist/@types/@ecme/services/sseParser.d.ts +20 -0
- package/dist/@types/@ecme/services/sseParser.d.ts.map +1 -0
- package/dist/@types/@ecme/views/concepts/ai/Chat/components/AiErrorMessage.d.ts +8 -0
- package/dist/@types/@ecme/views/concepts/ai/Chat/components/AiErrorMessage.d.ts.map +1 -0
- package/dist/@types/@ecme/views/concepts/ai/Chat/components/ApprovalCard.d.ts +15 -0
- package/dist/@types/@ecme/views/concepts/ai/Chat/components/ApprovalCard.d.ts.map +1 -0
- package/dist/@types/@ecme/views/concepts/ai/Chat/components/ChatView.d.ts.map +1 -1
- package/dist/@types/@ecme/views/concepts/ai/Chat/components/ToolCallChip.d.ts +7 -0
- package/dist/@types/@ecme/views/concepts/ai/Chat/components/ToolCallChip.d.ts.map +1 -0
- package/dist/@types/@ecme/views/concepts/ai/Chat/components/toolPresentation.d.ts +14 -0
- package/dist/@types/@ecme/views/concepts/ai/Chat/components/toolPresentation.d.ts.map +1 -0
- package/dist/@types/@ecme/views/concepts/ai/Chat/hooks/useChatSend.d.ts +2 -1
- package/dist/@types/@ecme/views/concepts/ai/Chat/hooks/useChatSend.d.ts.map +1 -1
- package/dist/@types/@ecme/views/concepts/ai/Chat/store/generativeChatStore.d.ts +1 -0
- package/dist/@types/@ecme/views/concepts/ai/Chat/store/generativeChatStore.d.ts.map +1 -1
- package/dist/@types/@ecme/views/concepts/ai/Chat/types.d.ts +96 -23
- package/dist/@types/@ecme/views/concepts/ai/Chat/types.d.ts.map +1 -1
- package/dist/@types/base/@types/models/transportadora/irpf.d.ts +1 -0
- package/dist/@types/base/@types/models/transportadora/irpf.d.ts.map +1 -1
- package/dist/@types/base/@types/models/transportadora/irpfFaixaDesconto.d.ts +0 -1
- package/dist/@types/base/@types/models/transportadora/irpfFaixaDesconto.d.ts.map +1 -1
- package/dist/base/assets/styles/init.css +128 -3
- package/lib/@ecme/components/template/OrbeAiWidget/OrbeAiWidget.tsx +0 -2
- package/lib/@ecme/components/view/ChatBox/ChatBox.tsx +2 -0
- package/lib/@ecme/components/view/ChatBox/components/MessageList.tsx +8 -4
- package/lib/@ecme/services/AiService.ts +89 -44
- package/lib/@ecme/services/aiAuthHeaders.ts +58 -0
- package/lib/@ecme/services/axios/AxiosRequestIntrceptorConfigCallback.ts +10 -39
- package/lib/@ecme/services/sseParser.ts +75 -0
- package/lib/@ecme/views/concepts/ai/Chat/components/AiErrorMessage.tsx +28 -0
- package/lib/@ecme/views/concepts/ai/Chat/components/ApprovalCard.tsx +103 -0
- package/lib/@ecme/views/concepts/ai/Chat/components/ChatView.tsx +74 -10
- package/lib/@ecme/views/concepts/ai/Chat/components/ToolCallChip.tsx +218 -0
- package/lib/@ecme/views/concepts/ai/Chat/components/toolPresentation.ts +137 -0
- package/lib/@ecme/views/concepts/ai/Chat/hooks/useChatSend.ts +175 -23
- package/lib/@ecme/views/concepts/ai/Chat/store/generativeChatStore.ts +41 -13
- package/lib/@ecme/views/concepts/ai/Chat/types.ts +82 -29
- package/lib/base/@types/models/transportadora/irpf.ts +1 -0
- package/lib/base/@types/models/transportadora/irpfFaixaDesconto.ts +0 -1
- package/package.json +1 -1
- package/dist/@ecme/views/concepts/ai/Chat/components/ModelSelect.js +0 -48
- package/dist/@ecme/views/concepts/ai/Chat/components/ModelSelect.js.map +0 -1
- package/dist/@ecme/views/concepts/ai/Chat/store/aiModelStore.js +0 -15
- package/dist/@ecme/views/concepts/ai/Chat/store/aiModelStore.js.map +0 -1
- package/dist/@types/@ecme/views/concepts/ai/Chat/components/ModelSelect.d.ts +0 -8
- package/dist/@types/@ecme/views/concepts/ai/Chat/components/ModelSelect.d.ts.map +0 -1
- package/dist/@types/@ecme/views/concepts/ai/Chat/store/aiModelStore.d.ts +0 -25
- package/dist/@types/@ecme/views/concepts/ai/Chat/store/aiModelStore.d.ts.map +0 -1
- package/lib/@ecme/views/concepts/ai/Chat/components/ModelSelect.tsx +0 -66
- package/lib/@ecme/views/concepts/ai/Chat/store/aiModelStore.ts +0 -23
|
@@ -7,14 +7,18 @@ import ChatMobileNav from "./ChatMobileNav.js";
|
|
|
7
7
|
import ChatCustomContent from "./ChatCustomContent.js";
|
|
8
8
|
import ChatCustomAction from "./ChatCustomAction.js";
|
|
9
9
|
import AiThinkingIndicator from "./AiThinkingIndicator.js";
|
|
10
|
+
import AiErrorMessage from "./AiErrorMessage.js";
|
|
11
|
+
import ToolCallChip from "./ToolCallChip.js";
|
|
12
|
+
import ApprovalCard from "./ApprovalCard.js";
|
|
10
13
|
import { usGenerativeChatStore } from "../store/generativeChatStore.js";
|
|
11
14
|
import useChatSend from "../hooks/useChatSend.js";
|
|
12
15
|
import Card from "../../../../../components/ui/Card/Card.js";
|
|
13
16
|
import ChatBox from "../../../../../components/view/ChatBox/ChatBox.js";
|
|
17
|
+
const BALAO_AGENTE = "prose rounded-xl bg-gray-100 px-5 py-2.5 text-sm text-gray-900 dark:bg-gray-700 dark:text-gray-100";
|
|
14
18
|
const ChatView = () => {
|
|
15
19
|
const scrollRef = useRef(null);
|
|
16
20
|
const { selectedConversation, chatHistory, isTyping, disabledChatFresh } = usGenerativeChatStore();
|
|
17
|
-
const { handleSend } = useChatSend();
|
|
21
|
+
const { handleSend, handleApproval } = useChatSend();
|
|
18
22
|
const scrollToBottom = () => {
|
|
19
23
|
if (scrollRef.current?.getScrollElement) {
|
|
20
24
|
const scrollElement = scrollRef.current.getScrollElement();
|
|
@@ -53,7 +57,9 @@ const ChatView = () => {
|
|
|
53
57
|
showAvatar: false,
|
|
54
58
|
avatarGap: false,
|
|
55
59
|
myBubbleClass: "bg-primary text-white",
|
|
56
|
-
|
|
60
|
+
botBubbleClass: "bg-transparent dark:bg-transparent p-0 rounded-none",
|
|
61
|
+
errorBubbleClass: "bg-red-50 dark:bg-red-500/10 border border-red-200 dark:border-red-500/20",
|
|
62
|
+
renderTyping: () => /* @__PURE__ */ jsx("div", { className: BALAO_AGENTE, children: /* @__PURE__ */ jsx(AiThinkingIndicator, {}) }),
|
|
57
63
|
containerClass: "h-[calc(100%-30px)] xl:h-full",
|
|
58
64
|
messageListClass: "h-[calc(100%-100px)] xl:h-[calc(100%-70px)]",
|
|
59
65
|
typing: isTyping ? {
|
|
@@ -62,20 +68,41 @@ const ChatView = () => {
|
|
|
62
68
|
avatarImageUrl: "/img/thumbs/ai.jpg"
|
|
63
69
|
} : false,
|
|
64
70
|
customRenderer: (message) => {
|
|
71
|
+
if (message.isError) {
|
|
72
|
+
return /* @__PURE__ */ jsx(AiErrorMessage, {});
|
|
73
|
+
}
|
|
65
74
|
if (message.sender.id === "ai") {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
content: message.content,
|
|
70
|
-
triggerTyping: message.fresh ? message.fresh : false,
|
|
71
|
-
onFinish: () => handleFinish(message.id)
|
|
72
|
-
}
|
|
75
|
+
const { tools, pendingApproval } = message;
|
|
76
|
+
const temTexto = Boolean(
|
|
77
|
+
message.content?.trim()
|
|
73
78
|
);
|
|
79
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-start gap-1.5", children: [
|
|
80
|
+
tools?.map((tool) => /* @__PURE__ */ jsx(ToolCallChip, { tool }, tool.id)),
|
|
81
|
+
temTexto && /* @__PURE__ */ jsx("div", { className: BALAO_AGENTE, children: /* @__PURE__ */ jsx(
|
|
82
|
+
ChatCustomContent,
|
|
83
|
+
{
|
|
84
|
+
content: message.content,
|
|
85
|
+
triggerTyping: message.fresh ? message.fresh : false,
|
|
86
|
+
onFinish: () => handleFinish(message.id)
|
|
87
|
+
}
|
|
88
|
+
) }),
|
|
89
|
+
pendingApproval && /* @__PURE__ */ jsx(
|
|
90
|
+
ApprovalCard,
|
|
91
|
+
{
|
|
92
|
+
approval: pendingApproval,
|
|
93
|
+
onDecide: (aprovado) => handleApproval(
|
|
94
|
+
selectedConversation,
|
|
95
|
+
message.id,
|
|
96
|
+
aprovado
|
|
97
|
+
)
|
|
98
|
+
}
|
|
99
|
+
)
|
|
100
|
+
] });
|
|
74
101
|
}
|
|
75
102
|
return message.content;
|
|
76
103
|
},
|
|
77
104
|
customAction: (message) => {
|
|
78
|
-
if (message.sender.id === "ai") {
|
|
105
|
+
if (message.sender.id === "ai" && !message.isError) {
|
|
79
106
|
return /* @__PURE__ */ jsx(
|
|
80
107
|
ChatCustomAction,
|
|
81
108
|
{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChatView.js","sources":["../../../../../../../lib/@ecme/views/concepts/ai/Chat/components/ChatView.tsx"],"sourcesContent":["import { useRef, useEffect, useMemo } from 'react'\nimport Card from '@/components/ui/Card'\nimport ChatBox from '@/components/view/ChatBox'\nimport ChatLandingView from './ChatLandingView'\nimport ChatMobileNav from './ChatMobileNav'\nimport ChatCustomContent from './ChatCustomContent'\nimport ChatCustomAction from './ChatCustomAction'\nimport AiThinkingIndicator from './AiThinkingIndicator'\nimport { usGenerativeChatStore } from '../store/generativeChatStore'\nimport useChatSend from '../hooks/useChatSend'\nimport type { ScrollBarRef } from '@/components/ui/ScrollBar'\n\nconst ChatView = () => {\n const scrollRef = useRef<ScrollBarRef>(null)\n const { selectedConversation, chatHistory, isTyping, disabledChatFresh } =\n usGenerativeChatStore()\n const { handleSend } = useChatSend()\n\n const scrollToBottom = () => {\n if (scrollRef.current?.getScrollElement) {\n const scrollElement = scrollRef.current.getScrollElement()\n if (scrollElement) {\n scrollElement.scrollTop = scrollElement.scrollHeight\n }\n }\n }\n\n useEffect(() => {\n scrollToBottom()\n }, [selectedConversation, chatHistory])\n\n const messageList = useMemo(() => {\n const chat = chatHistory.find(\n (chat) => chat.id === selectedConversation,\n )\n return chat?.conversation || []\n }, [selectedConversation, chatHistory])\n\n const handleInputChange = async ({\n value,\n }: {\n value: string\n attachments?: File[]\n }) => {\n await handleSend(value)\n }\n\n const handleFinish = (id: string) => {\n disabledChatFresh(id)\n scrollToBottom()\n }\n\n return (\n <Card className=\"flex-1 h-full\" bodyClass=\"h-full\">\n <ChatMobileNav />\n <ChatBox\n ref={scrollRef}\n messageList={messageList}\n placeholder=\"Pergunte algo sobre o Orbe Agro…\"\n showMessageList={Boolean(selectedConversation)}\n showAvatar={false}\n avatarGap={false}\n myBubbleClass=\"bg-primary text-white\"\n renderTyping={() => <AiThinkingIndicator
|
|
1
|
+
{"version":3,"file":"ChatView.js","sources":["../../../../../../../lib/@ecme/views/concepts/ai/Chat/components/ChatView.tsx"],"sourcesContent":["import { useRef, useEffect, useMemo } from 'react'\nimport Card from '@/components/ui/Card'\nimport ChatBox from '@/components/view/ChatBox'\nimport ChatLandingView from './ChatLandingView'\nimport ChatMobileNav from './ChatMobileNav'\nimport ChatCustomContent from './ChatCustomContent'\nimport ChatCustomAction from './ChatCustomAction'\nimport AiThinkingIndicator from './AiThinkingIndicator'\nimport AiErrorMessage from './AiErrorMessage'\nimport ToolCallChip from './ToolCallChip'\nimport ApprovalCard from './ApprovalCard'\nimport { usGenerativeChatStore } from '../store/generativeChatStore'\nimport useChatSend from '../hooks/useChatSend'\nimport type { Conversation } from '../types'\nimport type { ScrollBarRef } from '@/components/ui/ScrollBar'\n\n/**\n * O balao de fala do agente, desenhado aqui em vez de vir do ChatBox.\n *\n * O `botBubbleClass` abaixo zera o balao que o Message desenharia, para os chips\n * de ferramenta e o card de aprovacao ficarem FORA dele — antes tudo dividia a\n * mesma caixa cinza e as ferramentas eram lidas como uma segunda mensagem.\n * Usado tambem pelo indicador de \"pensando\", que passa pelo mesmo balao zerado.\n *\n * Repete `prose text-sm` do balao original de proposito: o reset de margem do\n * @tailwindcss/typography e `.prose > :first-child`, filho DIRETO. Sem o `prose`\n * aqui, o markdown ficaria um nivel abaixo do container e o primeiro paragrafo\n * voltaria a ter margem, inflando todos os baloes.\n */\nconst BALAO_AGENTE =\n 'prose rounded-xl bg-gray-100 px-5 py-2.5 text-sm text-gray-900 dark:bg-gray-700 dark:text-gray-100'\n\nconst ChatView = () => {\n const scrollRef = useRef<ScrollBarRef>(null)\n const { selectedConversation, chatHistory, isTyping, disabledChatFresh } =\n usGenerativeChatStore()\n const { handleSend, handleApproval } = useChatSend()\n\n const scrollToBottom = () => {\n if (scrollRef.current?.getScrollElement) {\n const scrollElement = scrollRef.current.getScrollElement()\n if (scrollElement) {\n scrollElement.scrollTop = scrollElement.scrollHeight\n }\n }\n }\n\n useEffect(() => {\n scrollToBottom()\n }, [selectedConversation, chatHistory])\n\n const messageList = useMemo(() => {\n const chat = chatHistory.find(\n (chat) => chat.id === selectedConversation,\n )\n return chat?.conversation || []\n }, [selectedConversation, chatHistory])\n\n const handleInputChange = async ({\n value,\n }: {\n value: string\n attachments?: File[]\n }) => {\n await handleSend(value)\n }\n\n const handleFinish = (id: string) => {\n disabledChatFresh(id)\n scrollToBottom()\n }\n\n return (\n <Card className=\"flex-1 h-full\" bodyClass=\"h-full\">\n <ChatMobileNav />\n <ChatBox\n ref={scrollRef}\n messageList={messageList}\n placeholder=\"Pergunte algo sobre o Orbe Agro…\"\n showMessageList={Boolean(selectedConversation)}\n showAvatar={false}\n avatarGap={false}\n myBubbleClass=\"bg-primary text-white\"\n botBubbleClass=\"bg-transparent dark:bg-transparent p-0 rounded-none\"\n errorBubbleClass=\"bg-red-50 dark:bg-red-500/10 border border-red-200 dark:border-red-500/20\"\n renderTyping={() => (\n <div className={BALAO_AGENTE}>\n <AiThinkingIndicator />\n </div>\n )}\n containerClass=\"h-[calc(100%-30px)] xl:h-full\"\n messageListClass=\"h-[calc(100%-100px)] xl:h-[calc(100%-70px)]\"\n typing={\n isTyping\n ? {\n id: 'ai',\n name: 'Chat AI',\n avatarImageUrl: '/img/thumbs/ai.jpg',\n }\n : false\n }\n customRenderer={(message) => {\n if (message.isError) {\n return <AiErrorMessage />\n }\n\n if (message.sender.id === 'ai') {\n // As ferramentas e a aprovacao chegam pelo streaming e\n // vivem no Conversation; o tipo do ChatBox nao os conhece.\n const { tools, pendingApproval } = message as Conversation\n // Enquanto as ferramentas rodam o conteudo ainda e '':\n // sem esta guarda sobraria um balao cinza vazio.\n const temTexto = Boolean(\n (message.content as string)?.trim(),\n )\n\n return (\n <div className=\"flex flex-col items-start gap-1.5\">\n {tools?.map((tool) => (\n <ToolCallChip key={tool.id} tool={tool} />\n ))}\n {temTexto && (\n <div className={BALAO_AGENTE}>\n <ChatCustomContent\n content={message.content as string}\n triggerTyping={\n message.fresh\n ? message.fresh\n : false\n }\n onFinish={() =>\n handleFinish(message.id)\n }\n />\n </div>\n )}\n {pendingApproval && (\n <ApprovalCard\n approval={pendingApproval}\n onDecide={(aprovado) =>\n handleApproval(\n selectedConversation,\n message.id,\n aprovado,\n )\n }\n />\n )}\n </div>\n )\n }\n\n return message.content\n }}\n customAction={(message) => {\n if (message.sender.id === 'ai' && !message.isError) {\n return (\n <ChatCustomAction\n content={message.content as string}\n />\n )\n }\n\n return null\n }}\n onInputChange={handleInputChange}\n >\n {!selectedConversation && (\n <div className=\"flex-1 min-h-0 overflow-y-auto\">\n <ChatLandingView />\n </div>\n )}\n </ChatBox>\n </Card>\n )\n}\n\nexport default ChatView\n"],"names":["chat"],"mappings":"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"}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { TbClock, TbLoader2, TbAlertTriangle, TbCheck, TbChevronDown } from "react-icons/tb";
|
|
4
|
+
import classNames from "../../../../../components/ui/utils/classNames.js";
|
|
5
|
+
import { iconeFerramenta, camposVisiveis, rotularExecucao, rotularFerramenta } from "./toolPresentation.js";
|
|
6
|
+
const estadoDe = (tool) => {
|
|
7
|
+
if (tool.awaitingApproval) {
|
|
8
|
+
return {
|
|
9
|
+
texto: "Aguardando confirmação",
|
|
10
|
+
Icone: TbClock,
|
|
11
|
+
classe: "text-amber-600 dark:text-amber-400",
|
|
12
|
+
mostrarTexto: true
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
if (tool.running) {
|
|
16
|
+
return {
|
|
17
|
+
texto: "Em andamento",
|
|
18
|
+
Icone: TbLoader2,
|
|
19
|
+
classe: "text-blue-600 dark:text-blue-400"
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
if (tool.isError) {
|
|
23
|
+
return {
|
|
24
|
+
texto: "Não concluída",
|
|
25
|
+
Icone: TbAlertTriangle,
|
|
26
|
+
classe: "text-red-600 dark:text-red-400",
|
|
27
|
+
mostrarTexto: true
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
texto: "Concluída",
|
|
32
|
+
Icone: TbCheck,
|
|
33
|
+
classe: "text-emerald-600 dark:text-emerald-400"
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
const ToolCallChip = ({ tool }) => {
|
|
37
|
+
const [aberto, setAberto] = useState(false);
|
|
38
|
+
const estado = estadoDe(tool);
|
|
39
|
+
const IconeFerramenta = iconeFerramenta(tool.name);
|
|
40
|
+
const emAndamento = tool.running && !tool.awaitingApproval;
|
|
41
|
+
const campos = camposVisiveis(tool.args);
|
|
42
|
+
const rotulo = emAndamento ? `${rotularExecucao(tool.name)}…` : rotularFerramenta(tool.name);
|
|
43
|
+
return (
|
|
44
|
+
// not-prose: o balao do agente e `prose`, que aplicaria margens de
|
|
45
|
+
// tipografia no dl/dt/dd/pre daqui e desmontaria a compactacao.
|
|
46
|
+
/* @__PURE__ */ jsxs("div", { className: "not-prose max-w-full", children: [
|
|
47
|
+
/* @__PURE__ */ jsxs(
|
|
48
|
+
"button",
|
|
49
|
+
{
|
|
50
|
+
type: "button",
|
|
51
|
+
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",
|
|
52
|
+
"aria-expanded": aberto,
|
|
53
|
+
title: `${rotulo} — ${estado.texto}`,
|
|
54
|
+
onClick: () => setAberto((atual) => !atual),
|
|
55
|
+
children: [
|
|
56
|
+
/* @__PURE__ */ jsx(
|
|
57
|
+
IconeFerramenta,
|
|
58
|
+
{
|
|
59
|
+
size: 13,
|
|
60
|
+
className: "shrink-0 text-gray-400 dark:text-gray-500"
|
|
61
|
+
}
|
|
62
|
+
),
|
|
63
|
+
/* @__PURE__ */ jsx("span", { className: "truncate text-xs font-medium text-gray-600 dark:text-gray-300", children: rotulo }),
|
|
64
|
+
/* @__PURE__ */ jsxs(
|
|
65
|
+
"span",
|
|
66
|
+
{
|
|
67
|
+
className: classNames(
|
|
68
|
+
"flex shrink-0 items-center gap-1 text-xs font-medium",
|
|
69
|
+
estado.classe
|
|
70
|
+
),
|
|
71
|
+
children: [
|
|
72
|
+
/* @__PURE__ */ jsx(
|
|
73
|
+
estado.Icone,
|
|
74
|
+
{
|
|
75
|
+
size: 12,
|
|
76
|
+
className: emAndamento ? "motion-safe:animate-spin" : ""
|
|
77
|
+
}
|
|
78
|
+
),
|
|
79
|
+
estado.mostrarTexto && estado.texto
|
|
80
|
+
]
|
|
81
|
+
}
|
|
82
|
+
),
|
|
83
|
+
/* @__PURE__ */ jsx(
|
|
84
|
+
TbChevronDown,
|
|
85
|
+
{
|
|
86
|
+
size: 12,
|
|
87
|
+
className: classNames(
|
|
88
|
+
"shrink-0 text-gray-400 transition-transform",
|
|
89
|
+
aberto && "rotate-180"
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
)
|
|
93
|
+
]
|
|
94
|
+
}
|
|
95
|
+
),
|
|
96
|
+
aberto && /* @__PURE__ */ jsxs("div", { className: "mt-1 rounded-lg border border-gray-200 px-2.5 py-2 dark:border-gray-700", children: [
|
|
97
|
+
campos.length > 0 && /* @__PURE__ */ jsx("dl", { className: "grid grid-cols-[minmax(0,auto)_minmax(0,1fr)] gap-x-3 gap-y-1 text-xs", children: campos.map((campo) => /* @__PURE__ */ jsxs("div", { className: "contents", children: [
|
|
98
|
+
/* @__PURE__ */ jsx("dt", { className: "text-gray-500 dark:text-gray-400", children: campo.rotulo }),
|
|
99
|
+
/* @__PURE__ */ jsx("dd", { className: "truncate font-medium text-gray-800 dark:text-gray-100", children: campo.valor })
|
|
100
|
+
] }, campo.chave)) }),
|
|
101
|
+
tool.result !== void 0 && /* @__PURE__ */ jsx(
|
|
102
|
+
Resultado,
|
|
103
|
+
{
|
|
104
|
+
texto: tool.result,
|
|
105
|
+
erro: tool.isError,
|
|
106
|
+
separar: campos.length > 0
|
|
107
|
+
}
|
|
108
|
+
)
|
|
109
|
+
] })
|
|
110
|
+
] })
|
|
111
|
+
);
|
|
112
|
+
};
|
|
113
|
+
const Resultado = ({
|
|
114
|
+
texto,
|
|
115
|
+
erro,
|
|
116
|
+
separar
|
|
117
|
+
}) => {
|
|
118
|
+
const [mostrarBruto, setMostrarBruto] = useState(false);
|
|
119
|
+
const ehJson = /^\s*[[{]/.test(texto);
|
|
120
|
+
const separador = separar ? "mt-2 border-t border-gray-200 pt-2 dark:border-gray-700" : "";
|
|
121
|
+
if (!ehJson) {
|
|
122
|
+
return /* @__PURE__ */ jsx(
|
|
123
|
+
"p",
|
|
124
|
+
{
|
|
125
|
+
className: classNames(
|
|
126
|
+
"text-xs",
|
|
127
|
+
separador,
|
|
128
|
+
erro ? "text-red-600 dark:text-red-400" : "text-gray-600 dark:text-gray-300"
|
|
129
|
+
),
|
|
130
|
+
children: texto
|
|
131
|
+
}
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
return /* @__PURE__ */ jsxs("div", { className: separador, children: [
|
|
135
|
+
/* @__PURE__ */ jsxs(
|
|
136
|
+
"button",
|
|
137
|
+
{
|
|
138
|
+
type: "button",
|
|
139
|
+
className: "text-xs font-medium text-gray-500 underline-offset-2 hover:underline dark:text-gray-400",
|
|
140
|
+
onClick: () => setMostrarBruto((atual) => !atual),
|
|
141
|
+
children: [
|
|
142
|
+
mostrarBruto ? "Ocultar" : "Ver",
|
|
143
|
+
" dados retornados"
|
|
144
|
+
]
|
|
145
|
+
}
|
|
146
|
+
),
|
|
147
|
+
mostrarBruto && /* @__PURE__ */ jsx("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", children: texto })
|
|
148
|
+
] });
|
|
149
|
+
};
|
|
150
|
+
export {
|
|
151
|
+
ToolCallChip as default
|
|
152
|
+
};
|
|
153
|
+
//# sourceMappingURL=ToolCallChip.js.map
|
|
@@ -0,0 +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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"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { formatQuantity } from "../../../../../../base/utils/functions/stringUtils.js";
|
|
2
|
+
import { TbFilePlus, TbListSearch, TbSearch, TbTool } from "react-icons/tb";
|
|
3
|
+
const FERRAMENTAS = {
|
|
4
|
+
buscar_contrato_armazenagem_graos: {
|
|
5
|
+
rotulo: "Consulta de contrato",
|
|
6
|
+
executando: "Consultando contrato",
|
|
7
|
+
icone: TbSearch
|
|
8
|
+
},
|
|
9
|
+
listar_contratos_armazenagem_graos: {
|
|
10
|
+
rotulo: "Consulta de contratos",
|
|
11
|
+
executando: "Consultando contratos",
|
|
12
|
+
icone: TbListSearch
|
|
13
|
+
},
|
|
14
|
+
criar_contrato_armazenagem_graos: {
|
|
15
|
+
rotulo: "Criação de contrato de armazenagem",
|
|
16
|
+
executando: "Criando contrato",
|
|
17
|
+
icone: TbFilePlus
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
const humanizar = (texto) => texto.replaceAll("_", " ").toLowerCase().replace(/^./, (letra) => letra.toUpperCase());
|
|
21
|
+
const rotularFerramenta = (nome) => FERRAMENTAS[nome]?.rotulo ?? humanizar(nome);
|
|
22
|
+
const rotularExecucao = (nome) => FERRAMENTAS[nome]?.executando ?? humanizar(nome);
|
|
23
|
+
const iconeFerramenta = (nome) => FERRAMENTAS[nome]?.icone ?? TbTool;
|
|
24
|
+
const CAMPOS = {
|
|
25
|
+
business_partner_id: "Parceiro de negócio",
|
|
26
|
+
produto_id: "Produto",
|
|
27
|
+
safra_id: "Safra",
|
|
28
|
+
saldo_negociado: "Saldo negociado",
|
|
29
|
+
tipo_contrato: "Tipo de contrato",
|
|
30
|
+
contrato_venda: "Contrato de venda",
|
|
31
|
+
ordem_venda: "Ordem de venda",
|
|
32
|
+
id: "Contrato",
|
|
33
|
+
page_number: "Página",
|
|
34
|
+
page_size: "Itens por página",
|
|
35
|
+
sort_field: "Ordenar por",
|
|
36
|
+
sort_order: "Ordem",
|
|
37
|
+
filtro: "Filtro"
|
|
38
|
+
};
|
|
39
|
+
const rotularCampo = (chave) => CAMPOS[chave] ?? humanizar(chave);
|
|
40
|
+
const REFERENCIAS = /* @__PURE__ */ new Set([
|
|
41
|
+
"business_partner_id",
|
|
42
|
+
"produto_id",
|
|
43
|
+
"safra_id",
|
|
44
|
+
"contrato_venda",
|
|
45
|
+
"ordem_venda",
|
|
46
|
+
"id"
|
|
47
|
+
]);
|
|
48
|
+
const ORDENACAO = { ASC: "Crescente", DESC: "Decrescente" };
|
|
49
|
+
const formatarValor = (chave, valor) => {
|
|
50
|
+
if (valor === null || valor === void 0 || valor === "") {
|
|
51
|
+
return "—";
|
|
52
|
+
}
|
|
53
|
+
if (chave === "saldo_negociado" && typeof valor === "number") {
|
|
54
|
+
return formatQuantity(valor);
|
|
55
|
+
}
|
|
56
|
+
if (chave === "sort_order" && typeof valor === "string") {
|
|
57
|
+
return ORDENACAO[valor] ?? humanizar(valor);
|
|
58
|
+
}
|
|
59
|
+
if (typeof valor === "number") {
|
|
60
|
+
return REFERENCIAS.has(chave) ? `#${valor}` : String(valor);
|
|
61
|
+
}
|
|
62
|
+
if (typeof valor === "boolean") {
|
|
63
|
+
return valor ? "Sim" : "Não";
|
|
64
|
+
}
|
|
65
|
+
if (typeof valor === "string") {
|
|
66
|
+
return /^[A-Z][A-Z0-9_]+$/.test(valor) ? humanizar(valor) : valor;
|
|
67
|
+
}
|
|
68
|
+
if (Array.isArray(valor)) {
|
|
69
|
+
return valor.length ? `${valor.length} item(ns)` : "—";
|
|
70
|
+
}
|
|
71
|
+
if (typeof valor === "object") {
|
|
72
|
+
const entradas = Object.entries(valor);
|
|
73
|
+
if (!entradas.length) {
|
|
74
|
+
return "—";
|
|
75
|
+
}
|
|
76
|
+
return entradas.map(([k, v]) => `${rotularCampo(k)}: ${formatarValor(k, v)}`).join(", ");
|
|
77
|
+
}
|
|
78
|
+
return String(valor);
|
|
79
|
+
};
|
|
80
|
+
const camposVisiveis = (args) => Object.entries(args).filter(([, valor]) => {
|
|
81
|
+
if (valor === null || valor === void 0 || valor === "") return false;
|
|
82
|
+
if (typeof valor === "object" && !Array.isArray(valor)) {
|
|
83
|
+
return Object.keys(valor).length > 0;
|
|
84
|
+
}
|
|
85
|
+
return true;
|
|
86
|
+
}).map(([chave, valor]) => ({
|
|
87
|
+
chave,
|
|
88
|
+
rotulo: rotularCampo(chave),
|
|
89
|
+
valor: formatarValor(chave, valor)
|
|
90
|
+
}));
|
|
91
|
+
export {
|
|
92
|
+
camposVisiveis,
|
|
93
|
+
formatarValor,
|
|
94
|
+
iconeFerramenta,
|
|
95
|
+
rotularCampo,
|
|
96
|
+
rotularExecucao,
|
|
97
|
+
rotularFerramenta
|
|
98
|
+
};
|
|
99
|
+
//# sourceMappingURL=toolPresentation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toolPresentation.js","sources":["../../../../../../../lib/@ecme/views/concepts/ai/Chat/components/toolPresentation.ts"],"sourcesContent":["import { formatQuantity } from '@base/utils/functions/stringUtils'\nimport { TbFilePlus, TbListSearch, TbSearch, TbTool } from 'react-icons/tb'\nimport type { IconType } from 'react-icons'\n\n/**\n * Como as ferramentas do agente sao apresentadas ao usuario.\n *\n * Compartilhado pelo chip de execucao e pelo card de aprovacao, para os dois\n * falarem a mesma lingua: o usuario ve \"Consulta de contratos\" e \"Mercado\n * interno\", nunca `listar_contratos_armazenagem_graos` ou `MERCADO_INTERNO`.\n *\n * Tudo tem fallback: uma ferramenta ou campo novo no backend nao quebra a tela,\n * so aparece com o nome tecnico humanizado ate ganhar entrada aqui.\n */\n\ntype Ferramenta = {\n rotulo: string\n /** Frase no gerundio, para o estado \"executando\". */\n executando: string\n icone: IconType\n}\n\nconst FERRAMENTAS: Record<string, Ferramenta> = {\n buscar_contrato_armazenagem_graos: {\n rotulo: 'Consulta de contrato',\n executando: 'Consultando contrato',\n icone: TbSearch,\n },\n listar_contratos_armazenagem_graos: {\n rotulo: 'Consulta de contratos',\n executando: 'Consultando contratos',\n icone: TbListSearch,\n },\n criar_contrato_armazenagem_graos: {\n rotulo: 'Criação de contrato de armazenagem',\n executando: 'Criando contrato',\n icone: TbFilePlus,\n },\n}\n\nconst humanizar = (texto: string) =>\n texto\n .replaceAll('_', ' ')\n .toLowerCase()\n .replace(/^./, (letra) => letra.toUpperCase())\n\nexport const rotularFerramenta = (nome: string) =>\n FERRAMENTAS[nome]?.rotulo ?? humanizar(nome)\n\nexport const rotularExecucao = (nome: string) =>\n FERRAMENTAS[nome]?.executando ?? humanizar(nome)\n\nexport const iconeFerramenta = (nome: string): IconType =>\n FERRAMENTAS[nome]?.icone ?? TbTool\n\n/** Rotulos dos parametros, na linguagem do negocio. */\nconst CAMPOS: Record<string, string> = {\n business_partner_id: 'Parceiro de negócio',\n produto_id: 'Produto',\n safra_id: 'Safra',\n saldo_negociado: 'Saldo negociado',\n tipo_contrato: 'Tipo de contrato',\n contrato_venda: 'Contrato de venda',\n ordem_venda: 'Ordem de venda',\n id: 'Contrato',\n page_number: 'Página',\n page_size: 'Itens por página',\n sort_field: 'Ordenar por',\n sort_order: 'Ordem',\n filtro: 'Filtro',\n}\n\nexport const rotularCampo = (chave: string) => CAMPOS[chave] ?? humanizar(chave)\n\n/** Campos que sao referencia a um registro: exibidos como \"#123\". */\nconst REFERENCIAS = new Set([\n 'business_partner_id',\n 'produto_id',\n 'safra_id',\n 'contrato_venda',\n 'ordem_venda',\n 'id',\n])\n\nconst ORDENACAO: Record<string, string> = { ASC: 'Crescente', DESC: 'Decrescente' }\n\n/** Converte o valor tecnico no que o usuario espera ler. */\nexport const formatarValor = (chave: string, valor: unknown): string => {\n if (valor === null || valor === undefined || valor === '') {\n return '—'\n }\n if (chave === 'saldo_negociado' && typeof valor === 'number') {\n return formatQuantity(valor)\n }\n if (chave === 'sort_order' && typeof valor === 'string') {\n return ORDENACAO[valor] ?? humanizar(valor)\n }\n if (typeof valor === 'number') {\n return REFERENCIAS.has(chave) ? `#${valor}` : String(valor)\n }\n if (typeof valor === 'boolean') {\n return valor ? 'Sim' : 'Não'\n }\n if (typeof valor === 'string') {\n // Enums do backend chegam em CAIXA_ALTA_COM_UNDERSCORE.\n return /^[A-Z][A-Z0-9_]+$/.test(valor) ? humanizar(valor) : valor\n }\n if (Array.isArray(valor)) {\n return valor.length ? `${valor.length} item(ns)` : '—'\n }\n if (typeof valor === 'object') {\n const entradas = Object.entries(valor as Record<string, unknown>)\n if (!entradas.length) {\n return '—'\n }\n return entradas\n .map(([k, v]) => `${rotularCampo(k)}: ${formatarValor(k, v)}`)\n .join(', ')\n }\n return String(valor)\n}\n\n/** Pares (rotulo, valor) prontos para exibir, ignorando o que esta vazio. */\nexport const camposVisiveis = (args: Record<string, unknown>) =>\n Object.entries(args)\n .filter(([, valor]) => {\n if (valor === null || valor === undefined || valor === '') return false\n if (typeof valor === 'object' && !Array.isArray(valor)) {\n return Object.keys(valor as object).length > 0\n }\n return true\n })\n .map(([chave, valor]) => ({\n chave,\n rotulo: rotularCampo(chave),\n valor: formatarValor(chave, valor),\n }))\n"],"names":[],"mappings":"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"}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { usGenerativeChatStore } from "../store/generativeChatStore.js";
|
|
2
|
-
import {
|
|
2
|
+
import { streamChat, resumeChat } from "../../../../../services/AiService.js";
|
|
3
3
|
import "../../../../../auth/index.js";
|
|
4
4
|
import dayjs from "dayjs";
|
|
5
5
|
import uniqueId from "lodash/uniqueId";
|
|
6
6
|
import useAuth from "../../../../../auth/useAuth.js";
|
|
7
|
+
const AI_ERROR_MESSAGE = "Desculpe, estou com um pequeno problema de comunicação e não consigo te atender no momento. Tente novamente em instantes.";
|
|
7
8
|
const useChatSend = () => {
|
|
8
9
|
const { user } = useAuth();
|
|
9
10
|
const {
|
|
@@ -11,6 +12,7 @@ const useChatSend = () => {
|
|
|
11
12
|
setSelectedConversation,
|
|
12
13
|
pushChatHistory,
|
|
13
14
|
pushConversation,
|
|
15
|
+
updateConversation,
|
|
14
16
|
setIsTyping
|
|
15
17
|
} = usGenerativeChatStore();
|
|
16
18
|
const creteMyMessage = (id, prompt) => {
|
|
@@ -27,31 +29,108 @@ const useChatSend = () => {
|
|
|
27
29
|
isMyMessage: true
|
|
28
30
|
});
|
|
29
31
|
};
|
|
30
|
-
const
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
});
|
|
35
|
-
pushConversation(id, {
|
|
36
|
-
id: uniqueId("ai-conversation-"),
|
|
32
|
+
const createAiMessage = (chatId, options) => {
|
|
33
|
+
const messageId = uniqueId("ai-conversation-");
|
|
34
|
+
pushConversation(chatId, {
|
|
35
|
+
id: messageId,
|
|
37
36
|
sender: {
|
|
38
37
|
id: "ai",
|
|
39
38
|
name: "Chat AI",
|
|
40
39
|
avatarImageUrl: "/img/thumbs/ai.jpg"
|
|
41
40
|
},
|
|
42
|
-
content:
|
|
41
|
+
content: "",
|
|
43
42
|
timestamp: dayjs().toDate(),
|
|
44
43
|
type: "regular",
|
|
45
44
|
isMyMessage: false,
|
|
46
|
-
fresh:
|
|
45
|
+
fresh: false,
|
|
46
|
+
...options
|
|
47
47
|
});
|
|
48
|
-
|
|
48
|
+
return messageId;
|
|
49
|
+
};
|
|
50
|
+
const consumirStream = async (chatId, messageId, executar, inicial) => {
|
|
51
|
+
let texto = inicial.texto;
|
|
52
|
+
let tools = inicial.tools;
|
|
53
|
+
const aplicar = (evento) => {
|
|
54
|
+
switch (evento.type) {
|
|
55
|
+
case "token":
|
|
56
|
+
texto += evento.data.content;
|
|
57
|
+
updateConversation(chatId, messageId, { content: texto });
|
|
58
|
+
break;
|
|
59
|
+
case "tool_start":
|
|
60
|
+
tools = [
|
|
61
|
+
...tools,
|
|
62
|
+
{
|
|
63
|
+
id: evento.data.id,
|
|
64
|
+
name: evento.data.name,
|
|
65
|
+
args: evento.data.args,
|
|
66
|
+
running: true
|
|
67
|
+
}
|
|
68
|
+
];
|
|
69
|
+
updateConversation(chatId, messageId, { tools });
|
|
70
|
+
break;
|
|
71
|
+
case "tool_end":
|
|
72
|
+
tools = tools.map(
|
|
73
|
+
(tool) => tool.id === evento.data.id ? {
|
|
74
|
+
...tool,
|
|
75
|
+
running: false,
|
|
76
|
+
awaitingApproval: false,
|
|
77
|
+
result: evento.data.content,
|
|
78
|
+
isError: evento.data.is_error
|
|
79
|
+
} : tool
|
|
80
|
+
);
|
|
81
|
+
updateConversation(chatId, messageId, { tools });
|
|
82
|
+
break;
|
|
83
|
+
case "approval_required": {
|
|
84
|
+
const pausadas = new Set(
|
|
85
|
+
evento.data.calls.map((call) => call.id)
|
|
86
|
+
);
|
|
87
|
+
tools = tools.map(
|
|
88
|
+
(tool) => pausadas.has(tool.id) ? { ...tool, running: false, awaitingApproval: true } : tool
|
|
89
|
+
);
|
|
90
|
+
updateConversation(chatId, messageId, {
|
|
91
|
+
tools,
|
|
92
|
+
pendingApproval: evento.data
|
|
93
|
+
});
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
case "done":
|
|
97
|
+
updateConversation(chatId, messageId, {
|
|
98
|
+
content: evento.data.content
|
|
99
|
+
});
|
|
100
|
+
break;
|
|
101
|
+
case "error":
|
|
102
|
+
updateConversation(chatId, messageId, {
|
|
103
|
+
content: evento.data.message || AI_ERROR_MESSAGE,
|
|
104
|
+
isError: true
|
|
105
|
+
});
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
try {
|
|
110
|
+
await executar(aplicar);
|
|
111
|
+
} catch {
|
|
112
|
+
updateConversation(chatId, messageId, {
|
|
113
|
+
content: texto || AI_ERROR_MESSAGE,
|
|
114
|
+
isError: !texto
|
|
115
|
+
});
|
|
116
|
+
} finally {
|
|
117
|
+
setIsTyping(false);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
const sendMessage = async (chatId, prompt) => {
|
|
121
|
+
const messageId = createAiMessage(chatId);
|
|
122
|
+
await consumirStream(
|
|
123
|
+
chatId,
|
|
124
|
+
messageId,
|
|
125
|
+
(onEvent) => streamChat({ prompt, conversationId: chatId, onEvent }),
|
|
126
|
+
{ texto: "", tools: [] }
|
|
127
|
+
);
|
|
49
128
|
};
|
|
50
|
-
const createConversation = async (
|
|
51
|
-
setSelectedConversation(
|
|
52
|
-
await sendMessage(
|
|
129
|
+
const createConversation = async (chatId, prompt) => {
|
|
130
|
+
setSelectedConversation(chatId);
|
|
131
|
+
await sendMessage(chatId, prompt);
|
|
53
132
|
};
|
|
54
|
-
const handleSend = async (prompt
|
|
133
|
+
const handleSend = async (prompt) => {
|
|
55
134
|
setIsTyping(true);
|
|
56
135
|
if (selectedConversation) {
|
|
57
136
|
creteMyMessage(selectedConversation, prompt);
|
|
@@ -67,11 +146,26 @@ const useChatSend = () => {
|
|
|
67
146
|
enable: false
|
|
68
147
|
});
|
|
69
148
|
creteMyMessage(newId, prompt);
|
|
70
|
-
await createConversation(newId, prompt
|
|
149
|
+
await createConversation(newId, prompt);
|
|
71
150
|
}
|
|
72
151
|
};
|
|
152
|
+
const handleApproval = async (chatId, messageId, approved) => {
|
|
153
|
+
const mensagem = usGenerativeChatStore.getState().chatHistory.find((chat) => chat.id === chatId)?.conversation?.find((item) => item.id === messageId);
|
|
154
|
+
if (!mensagem?.pendingApproval) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
setIsTyping(true);
|
|
158
|
+
updateConversation(chatId, messageId, { pendingApproval: void 0 });
|
|
159
|
+
await consumirStream(
|
|
160
|
+
chatId,
|
|
161
|
+
messageId,
|
|
162
|
+
(onEvent) => resumeChat({ approved, conversationId: chatId, onEvent }),
|
|
163
|
+
{ texto: mensagem.content ?? "", tools: mensagem.tools ?? [] }
|
|
164
|
+
);
|
|
165
|
+
};
|
|
73
166
|
return {
|
|
74
|
-
handleSend
|
|
167
|
+
handleSend,
|
|
168
|
+
handleApproval
|
|
75
169
|
};
|
|
76
170
|
};
|
|
77
171
|
export {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useChatSend.js","sources":["../../../../../../../lib/@ecme/views/concepts/ai/Chat/hooks/useChatSend.ts"],"sourcesContent":["import { usGenerativeChatStore } from '../store/generativeChatStore'\nimport { apiPostChat } from '@/services/AiService'\nimport { useAuth } from '@/auth'\nimport dayjs from 'dayjs'\nimport uniqueId from 'lodash/uniqueId'\nimport type { PostAiChatResponse } from '../types'\n\nconst useChatSend = () => {\n const { user } = useAuth()\n const {\n selectedConversation,\n setSelectedConversation,\n pushChatHistory,\n pushConversation,\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 const sendMessage = async (\n id: string,\n prompt: string,\n attachments?: File[],\n ) => {\n const resp = await apiPostChat<PostAiChatResponse>({\n prompt,\n attachments,\n })\n pushConversation(id, {\n id: uniqueId('ai-conversation-'),\n sender: {\n id: 'ai',\n name: 'Chat AI',\n avatarImageUrl: '/img/thumbs/ai.jpg',\n },\n content: resp.choices[0].message.content || '',\n timestamp: dayjs().toDate(),\n type: 'regular',\n isMyMessage: false,\n fresh: true,\n })\n setIsTyping(false)\n }\n\n const createConversation = async (\n id: string,\n prompt: string,\n attachments?: File[],\n ) => {\n setSelectedConversation(id)\n await sendMessage(id, prompt, attachments)\n }\n\n const handleSend = async (prompt: string, attachments?: File[]) => {\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, attachments)\n }\n }\n\n return {\n handleSend,\n }\n}\n\nexport default useChatSend\n"],"names":[],"mappings":";;;;;;AAOA,MAAM,cAAc,MAAM;AACtB,QAAM,EAAE,KAAA,IAAS,QAAA;AACjB,QAAM;AAAA,IACF;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA,IACA,sBAAA;AAEJ,QAAM,iBAAiB,CAAC,IAAY,WAAmB;AACnD,qBAAiB,IAAI;AAAA,MACjB,IAAI,SAAS,kBAAkB;AAAA,MAC/B,QAAQ;AAAA,QACJ,IAAI,KAAK,UAAU;AAAA,QACnB,MAAM,KAAK,YAAY;AAAA,QACvB,gBAAgB,KAAK,UAAU;AAAA,MAAA;AAAA,MAEnC,SAAS;AAAA,MACT,WAAW,MAAA,EAAQ,OAAA;AAAA,MACnB,MAAM;AAAA,MACN,aAAa;AAAA,IAAA,CAChB;AAAA,EACL;AAEA,QAAM,cAAc,OAChB,IACA,QACA,gBACC;AACD,UAAM,OAAO,MAAM,YAAgC;AAAA,MAC/C;AAAA,MACA;AAAA,IAAA,CACH;AACD,qBAAiB,IAAI;AAAA,MACjB,IAAI,SAAS,kBAAkB;AAAA,MAC/B,QAAQ;AAAA,QACJ,IAAI;AAAA,QACJ,MAAM;AAAA,QACN,gBAAgB;AAAA,MAAA;AAAA,MAEpB,SAAS,KAAK,QAAQ,CAAC,EAAE,QAAQ,WAAW;AAAA,MAC5C,WAAW,MAAA,EAAQ,OAAA;AAAA,MACnB,MAAM;AAAA,MACN,aAAa;AAAA,MACb,OAAO;AAAA,IAAA,CACV;AACD,gBAAY,KAAK;AAAA,EACrB;AAEA,QAAM,qBAAqB,OACvB,IACA,QACA,gBACC;AACD,4BAAwB,EAAE;AAC1B,UAAM,YAAY,IAAI,QAAQ,WAAW;AAAA,EAC7C;AAEA,QAAM,aAAa,OAAO,QAAgB,gBAAyB;AAC/D,gBAAY,IAAI;AAChB,QAAI,sBAAsB;AACtB,qBAAe,sBAAsB,MAAM;AAC3C,YAAM,YAAY,sBAAsB,MAAM;AAAA,IAClD,OAAO;AACH,YAAM,QAAQ,SAAS,UAAU;AACjC,sBAAgB;AAAA,QACZ,IAAI;AAAA,QACJ,OAAO;AAAA,QACP,kBAAkB;AAAA,QAClB,aAAa,MAAA,EAAQ,KAAA;AAAA,QACrB,aAAa,MAAA,EAAQ,KAAA;AAAA,QACrB,QAAQ;AAAA,MAAA,CACX;AACD,qBAAe,OAAO,MAAM;AAC5B,YAAM,mBAAmB,OAAO,QAAQ,WAAW;AAAA,IACvD;AAAA,EACJ;AAEA,SAAO;AAAA,IACH;AAAA,EAAA;AAER;"}
|
|
1
|
+
{"version":3,"file":"useChatSend.js","sources":["../../../../../../../lib/@ecme/views/concepts/ai/Chat/hooks/useChatSend.ts"],"sourcesContent":["import { usGenerativeChatStore } from '../store/generativeChatStore'\nimport { resumeChat, streamChat } from '@/services/AiService'\nimport { useAuth } from '@/auth'\nimport dayjs from 'dayjs'\nimport uniqueId from 'lodash/uniqueId'\nimport type { ChatEvent, Conversation, ToolCall } from '../types'\n\n/** Resposta genérica exibida no chat quando a comunicação com a IA falha. */\nconst AI_ERROR_MESSAGE =\n 'Desculpe, estou com um pequeno problema de comunicação e não consigo te atender no momento. Tente novamente em instantes.'\n\nconst useChatSend = () => {\n const { user } = useAuth()\n const {\n selectedConversation,\n setSelectedConversation,\n pushChatHistory,\n pushConversation,\n updateConversation,\n setIsTyping,\n } = usGenerativeChatStore()\n\n const creteMyMessage = (id: string, prompt: string) => {\n pushConversation(id, {\n id: uniqueId('ai-conversation-'),\n sender: {\n id: user.userId ?? 'me',\n name: user.userName ?? 'Você',\n avatarImageUrl: user.avatar ?? undefined,\n },\n content: prompt,\n timestamp: dayjs().toDate(),\n type: 'regular',\n isMyMessage: true,\n })\n }\n\n /**\n * Cria a mensagem da IA vazia e devolve o id, para o streaming preenchê-la.\n *\n * `fresh: false` é obrigatório: `fresh` liga a animação de máquina de\n * escrever do ChatCustomContent, que reinicia do zero a cada mudança de\n * conteúdo — com streaming, o texto ficaria piscando e voltando ao início.\n * A animação agora vem do servidor.\n */\n const createAiMessage = (\n chatId: string,\n options?: Partial<Conversation>,\n ) => {\n const messageId = uniqueId('ai-conversation-')\n pushConversation(chatId, {\n id: messageId,\n sender: {\n id: 'ai',\n name: 'Chat AI',\n avatarImageUrl: '/img/thumbs/ai.jpg',\n },\n content: '',\n timestamp: dayjs().toDate(),\n type: 'regular',\n isMyMessage: false,\n fresh: false,\n ...options,\n })\n return messageId\n }\n\n /**\n * Consome um stream escrevendo numa mensagem que já existe.\n *\n * Compartilhado pelo envio e pela retomada de aprovação: quando o usuário\n * aprova, a resposta continua na MESMA mensagem, então o acumulador de\n * texto e de ferramentas precisa partir do que já está lá.\n */\n const consumirStream = async (\n chatId: string,\n messageId: string,\n executar: (onEvent: (evento: ChatEvent) => void) => Promise<void>,\n inicial: { texto: string; tools: ToolCall[] },\n ) => {\n // Acumulados localmente: o store guarda o resultado, mas ler de volta a\n // cada token para concatenar seria uma corrida com o próprio set().\n let texto = inicial.texto\n let tools = inicial.tools\n\n const aplicar = (evento: ChatEvent) => {\n switch (evento.type) {\n case 'token':\n texto += evento.data.content\n updateConversation(chatId, messageId, { content: texto })\n break\n\n case 'tool_start':\n tools = [\n ...tools,\n {\n id: evento.data.id,\n name: evento.data.name,\n args: evento.data.args,\n running: true,\n },\n ]\n updateConversation(chatId, messageId, { tools })\n break\n\n case 'tool_end':\n tools = tools.map((tool) =>\n tool.id === evento.data.id\n ? {\n ...tool,\n running: false,\n awaitingApproval: false,\n result: evento.data.content,\n isError: evento.data.is_error,\n }\n : tool,\n )\n updateConversation(chatId, messageId, { tools })\n break\n\n case 'approval_required': {\n // O tool_start já marcou estas ferramentas como \"running\".\n // Elas não estão executando: estão paradas esperando o\n // clique — senão o chip giraria para sempre.\n const pausadas = new Set(\n evento.data.calls.map((call) => call.id),\n )\n tools = tools.map((tool) =>\n pausadas.has(tool.id)\n ? { ...tool, running: false, awaitingApproval: true }\n : tool,\n )\n updateConversation(chatId, messageId, {\n tools,\n pendingApproval: evento.data,\n })\n break\n }\n\n case 'done':\n // O done traz a resposta final completa e é a fonte\n // autoritativa — se algum token se perdeu, ele corrige.\n updateConversation(chatId, messageId, {\n content: evento.data.content,\n })\n break\n\n case 'error':\n updateConversation(chatId, messageId, {\n content: evento.data.message || AI_ERROR_MESSAGE,\n isError: true,\n })\n break\n }\n }\n\n try {\n await executar(aplicar)\n } catch {\n updateConversation(chatId, messageId, {\n content: texto || AI_ERROR_MESSAGE,\n isError: !texto,\n })\n } finally {\n setIsTyping(false)\n }\n }\n\n const sendMessage = async (chatId: string, prompt: string) => {\n const messageId = createAiMessage(chatId)\n await consumirStream(\n chatId,\n messageId,\n (onEvent) => streamChat({ prompt, conversationId: chatId, onEvent }),\n { texto: '', tools: [] },\n )\n }\n\n const createConversation = async (chatId: string, prompt: string) => {\n setSelectedConversation(chatId)\n await sendMessage(chatId, prompt)\n }\n\n const handleSend = async (prompt: string) => {\n setIsTyping(true)\n if (selectedConversation) {\n creteMyMessage(selectedConversation, prompt)\n await sendMessage(selectedConversation, prompt)\n } else {\n const newId = uniqueId('ai-chat-')\n pushChatHistory({\n id: newId,\n title: prompt,\n lastConversation: '',\n createdTime: dayjs().unix(),\n updatedTime: dayjs().unix(),\n enable: false,\n })\n creteMyMessage(newId, prompt)\n await createConversation(newId, prompt)\n }\n }\n\n /**\n * Aprova ou nega a operação pendente e retoma a conversa.\n *\n * Continua escrevendo na mesma mensagem: por isso o acumulador parte do\n * conteúdo e das ferramentas que já estão lá.\n */\n const handleApproval = async (\n chatId: string,\n messageId: string,\n approved: boolean,\n ) => {\n const mensagem = usGenerativeChatStore\n .getState()\n .chatHistory.find((chat) => chat.id === chatId)\n ?.conversation?.find((item) => item.id === messageId)\n\n if (!mensagem?.pendingApproval) {\n return\n }\n\n setIsTyping(true)\n // Some com o card antes da chamada: dois cliques rápidos enviariam duas\n // retomadas, e a segunda falha (o interrupt já foi consumido).\n updateConversation(chatId, messageId, { pendingApproval: undefined })\n\n await consumirStream(\n chatId,\n messageId,\n (onEvent) =>\n resumeChat({ approved, conversationId: chatId, onEvent }),\n { texto: mensagem.content ?? '', tools: mensagem.tools ?? [] },\n )\n }\n\n return {\n handleSend,\n handleApproval,\n }\n}\n\nexport default useChatSend\n"],"names":[],"mappings":";;;;;;AAQA,MAAM,mBACF;AAEJ,MAAM,cAAc,MAAM;AACtB,QAAM,EAAE,KAAA,IAAS,QAAA;AACjB,QAAM;AAAA,IACF;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA,IACA,sBAAA;AAEJ,QAAM,iBAAiB,CAAC,IAAY,WAAmB;AACnD,qBAAiB,IAAI;AAAA,MACjB,IAAI,SAAS,kBAAkB;AAAA,MAC/B,QAAQ;AAAA,QACJ,IAAI,KAAK,UAAU;AAAA,QACnB,MAAM,KAAK,YAAY;AAAA,QACvB,gBAAgB,KAAK,UAAU;AAAA,MAAA;AAAA,MAEnC,SAAS;AAAA,MACT,WAAW,MAAA,EAAQ,OAAA;AAAA,MACnB,MAAM;AAAA,MACN,aAAa;AAAA,IAAA,CAChB;AAAA,EACL;AAUA,QAAM,kBAAkB,CACpB,QACA,YACC;AACD,UAAM,YAAY,SAAS,kBAAkB;AAC7C,qBAAiB,QAAQ;AAAA,MACrB,IAAI;AAAA,MACJ,QAAQ;AAAA,QACJ,IAAI;AAAA,QACJ,MAAM;AAAA,QACN,gBAAgB;AAAA,MAAA;AAAA,MAEpB,SAAS;AAAA,MACT,WAAW,MAAA,EAAQ,OAAA;AAAA,MACnB,MAAM;AAAA,MACN,aAAa;AAAA,MACb,OAAO;AAAA,MACP,GAAG;AAAA,IAAA,CACN;AACD,WAAO;AAAA,EACX;AASA,QAAM,iBAAiB,OACnB,QACA,WACA,UACA,YACC;AAGD,QAAI,QAAQ,QAAQ;AACpB,QAAI,QAAQ,QAAQ;AAEpB,UAAM,UAAU,CAAC,WAAsB;AACnC,cAAQ,OAAO,MAAA;AAAA,QACX,KAAK;AACD,mBAAS,OAAO,KAAK;AACrB,6BAAmB,QAAQ,WAAW,EAAE,SAAS,OAAO;AACxD;AAAA,QAEJ,KAAK;AACD,kBAAQ;AAAA,YACJ,GAAG;AAAA,YACH;AAAA,cACI,IAAI,OAAO,KAAK;AAAA,cAChB,MAAM,OAAO,KAAK;AAAA,cAClB,MAAM,OAAO,KAAK;AAAA,cAClB,SAAS;AAAA,YAAA;AAAA,UACb;AAEJ,6BAAmB,QAAQ,WAAW,EAAE,MAAA,CAAO;AAC/C;AAAA,QAEJ,KAAK;AACD,kBAAQ,MAAM;AAAA,YAAI,CAAC,SACf,KAAK,OAAO,OAAO,KAAK,KAClB;AAAA,cACI,GAAG;AAAA,cACH,SAAS;AAAA,cACT,kBAAkB;AAAA,cAClB,QAAQ,OAAO,KAAK;AAAA,cACpB,SAAS,OAAO,KAAK;AAAA,YAAA,IAEzB;AAAA,UAAA;AAEV,6BAAmB,QAAQ,WAAW,EAAE,MAAA,CAAO;AAC/C;AAAA,QAEJ,KAAK,qBAAqB;AAItB,gBAAM,WAAW,IAAI;AAAA,YACjB,OAAO,KAAK,MAAM,IAAI,CAAC,SAAS,KAAK,EAAE;AAAA,UAAA;AAE3C,kBAAQ,MAAM;AAAA,YAAI,CAAC,SACf,SAAS,IAAI,KAAK,EAAE,IACd,EAAE,GAAG,MAAM,SAAS,OAAO,kBAAkB,SAC7C;AAAA,UAAA;AAEV,6BAAmB,QAAQ,WAAW;AAAA,YAClC;AAAA,YACA,iBAAiB,OAAO;AAAA,UAAA,CAC3B;AACD;AAAA,QACJ;AAAA,QAEA,KAAK;AAGD,6BAAmB,QAAQ,WAAW;AAAA,YAClC,SAAS,OAAO,KAAK;AAAA,UAAA,CACxB;AACD;AAAA,QAEJ,KAAK;AACD,6BAAmB,QAAQ,WAAW;AAAA,YAClC,SAAS,OAAO,KAAK,WAAW;AAAA,YAChC,SAAS;AAAA,UAAA,CACZ;AACD;AAAA,MAAA;AAAA,IAEZ;AAEA,QAAI;AACA,YAAM,SAAS,OAAO;AAAA,IAC1B,QAAQ;AACJ,yBAAmB,QAAQ,WAAW;AAAA,QAClC,SAAS,SAAS;AAAA,QAClB,SAAS,CAAC;AAAA,MAAA,CACb;AAAA,IACL,UAAA;AACI,kBAAY,KAAK;AAAA,IACrB;AAAA,EACJ;AAEA,QAAM,cAAc,OAAO,QAAgB,WAAmB;AAC1D,UAAM,YAAY,gBAAgB,MAAM;AACxC,UAAM;AAAA,MACF;AAAA,MACA;AAAA,MACA,CAAC,YAAY,WAAW,EAAE,QAAQ,gBAAgB,QAAQ,SAAS;AAAA,MACnE,EAAE,OAAO,IAAI,OAAO,CAAA,EAAC;AAAA,IAAE;AAAA,EAE/B;AAEA,QAAM,qBAAqB,OAAO,QAAgB,WAAmB;AACjE,4BAAwB,MAAM;AAC9B,UAAM,YAAY,QAAQ,MAAM;AAAA,EACpC;AAEA,QAAM,aAAa,OAAO,WAAmB;AACzC,gBAAY,IAAI;AAChB,QAAI,sBAAsB;AACtB,qBAAe,sBAAsB,MAAM;AAC3C,YAAM,YAAY,sBAAsB,MAAM;AAAA,IAClD,OAAO;AACH,YAAM,QAAQ,SAAS,UAAU;AACjC,sBAAgB;AAAA,QACZ,IAAI;AAAA,QACJ,OAAO;AAAA,QACP,kBAAkB;AAAA,QAClB,aAAa,MAAA,EAAQ,KAAA;AAAA,QACrB,aAAa,MAAA,EAAQ,KAAA;AAAA,QACrB,QAAQ;AAAA,MAAA,CACX;AACD,qBAAe,OAAO,MAAM;AAC5B,YAAM,mBAAmB,OAAO,MAAM;AAAA,IAC1C;AAAA,EACJ;AAQA,QAAM,iBAAiB,OACnB,QACA,WACA,aACC;AACD,UAAM,WAAW,sBACZ,SAAA,EACA,YAAY,KAAK,CAAC,SAAS,KAAK,OAAO,MAAM,GAC5C,cAAc,KAAK,CAAC,SAAS,KAAK,OAAO,SAAS;AAExD,QAAI,CAAC,UAAU,iBAAiB;AAC5B;AAAA,IACJ;AAEA,gBAAY,IAAI;AAGhB,uBAAmB,QAAQ,WAAW,EAAE,iBAAiB,QAAW;AAEpE,UAAM;AAAA,MACF;AAAA,MACA;AAAA,MACA,CAAC,YACG,WAAW,EAAE,UAAU,gBAAgB,QAAQ,SAAS;AAAA,MAC5D,EAAE,OAAO,SAAS,WAAW,IAAI,OAAO,SAAS,SAAS,CAAA,EAAC;AAAA,IAAE;AAAA,EAErE;AAEA,SAAO;AAAA,IACH;AAAA,IACA;AAAA,EAAA;AAER;"}
|