@orbe-agro/client-core 5.6.201 → 5.6.202
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 +82 -5
- package/dist/@ecme/assets/styles/tailwind/index.css +89 -6
- package/dist/@ecme/components/layouts/PostLoginLayout/PostLoginLayout.js +3 -0
- package/dist/@ecme/components/layouts/PostLoginLayout/PostLoginLayout.js.map +1 -1
- package/dist/@ecme/components/template/Footer.js +1 -1
- package/dist/@ecme/components/template/OrbeAiWidget/OrbeAiWidget.js +100 -0
- package/dist/@ecme/components/template/OrbeAiWidget/OrbeAiWidget.js.map +1 -0
- package/dist/@ecme/components/template/OrbeAiWidget/ResizeHandles.js +37 -0
- package/dist/@ecme/components/template/OrbeAiWidget/ResizeHandles.js.map +1 -0
- package/dist/@ecme/components/template/OrbeAiWidget/index.js +5 -0
- package/dist/@ecme/components/template/OrbeAiWidget/index.js.map +1 -0
- package/dist/@ecme/components/template/OrbeAiWidget/useResizablePanel.js +77 -0
- package/dist/@ecme/components/template/OrbeAiWidget/useResizablePanel.js.map +1 -0
- package/dist/@ecme/components/view/ChatBox/ChatBox.js +7 -1
- package/dist/@ecme/components/view/ChatBox/ChatBox.js.map +1 -1
- package/dist/@ecme/components/view/ChatBox/components/MessageList.js +7 -4
- package/dist/@ecme/components/view/ChatBox/components/MessageList.js.map +1 -1
- package/dist/@ecme/configs/navigation.config/concepts.navigation.config.js +0 -16
- package/dist/@ecme/configs/navigation.config/concepts.navigation.config.js.map +1 -1
- package/dist/@ecme/configs/routes.config/conceptsRoute.js +0 -9
- package/dist/@ecme/configs/routes.config/conceptsRoute.js.map +1 -1
- package/dist/@ecme/index.js +3 -1
- package/dist/@ecme/services/AiService.js +24 -4
- package/dist/@ecme/services/AiService.js.map +1 -1
- package/dist/@ecme/services/axios/AxiosBase.js +30 -24
- package/dist/@ecme/services/axios/AxiosBase.js.map +1 -1
- package/dist/@ecme/services/index.js +3 -1
- package/dist/@ecme/views/concepts/ai/Chat/components/AiThinkingIndicator.js +23 -0
- package/dist/@ecme/views/concepts/ai/Chat/components/AiThinkingIndicator.js.map +1 -0
- package/dist/@ecme/views/concepts/ai/Chat/components/ChatLandingView.js +16 -16
- package/dist/@ecme/views/concepts/ai/Chat/components/ChatLandingView.js.map +1 -1
- package/dist/@ecme/views/concepts/ai/Chat/components/ChatView.js +7 -4
- package/dist/@ecme/views/concepts/ai/Chat/components/ChatView.js.map +1 -1
- package/dist/@ecme/views/concepts/ai/Chat/components/ModelSelect.js +48 -0
- package/dist/@ecme/views/concepts/ai/Chat/components/ModelSelect.js.map +1 -0
- package/dist/@ecme/views/concepts/ai/Chat/hooks/useChatSend.js +6 -3
- package/dist/@ecme/views/concepts/ai/Chat/hooks/useChatSend.js.map +1 -1
- package/dist/@ecme/views/concepts/ai/Chat/store/aiModelStore.js +15 -0
- package/dist/@ecme/views/concepts/ai/Chat/store/aiModelStore.js.map +1 -0
- package/dist/@types/@ecme/components/layouts/PostLoginLayout/PostLoginLayout.d.ts.map +1 -1
- package/dist/@types/@ecme/components/template/OrbeAiWidget/OrbeAiWidget.d.ts +3 -0
- package/dist/@types/@ecme/components/template/OrbeAiWidget/OrbeAiWidget.d.ts.map +1 -0
- package/dist/@types/@ecme/components/template/OrbeAiWidget/ResizeHandles.d.ts +9 -0
- package/dist/@types/@ecme/components/template/OrbeAiWidget/ResizeHandles.d.ts.map +1 -0
- package/dist/@types/@ecme/components/template/OrbeAiWidget/index.d.ts +2 -0
- package/dist/@types/@ecme/components/template/OrbeAiWidget/index.d.ts.map +1 -0
- package/dist/@types/@ecme/components/template/OrbeAiWidget/useResizablePanel.d.ts +19 -0
- package/dist/@types/@ecme/components/template/OrbeAiWidget/useResizablePanel.d.ts.map +1 -0
- package/dist/@types/@ecme/components/view/ChatBox/ChatBox.d.ts.map +1 -1
- package/dist/@types/@ecme/components/view/ChatBox/components/MessageList.d.ts +3 -0
- package/dist/@types/@ecme/components/view/ChatBox/components/MessageList.d.ts.map +1 -1
- package/dist/@types/@ecme/configs/navigation.config/concepts.navigation.config.d.ts.map +1 -1
- package/dist/@types/@ecme/configs/routes.config/conceptsRoute.d.ts.map +1 -1
- package/dist/@types/@ecme/services/AiService.d.ts +12 -0
- package/dist/@types/@ecme/services/AiService.d.ts.map +1 -1
- package/dist/@types/@ecme/services/axios/AxiosBase.d.ts +12 -0
- package/dist/@types/@ecme/services/axios/AxiosBase.d.ts.map +1 -1
- package/dist/@types/@ecme/views/concepts/ai/Chat/components/AiThinkingIndicator.d.ts +7 -0
- package/dist/@types/@ecme/views/concepts/ai/Chat/components/AiThinkingIndicator.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/ModelSelect.d.ts +8 -0
- package/dist/@types/@ecme/views/concepts/ai/Chat/components/ModelSelect.d.ts.map +1 -0
- package/dist/@types/@ecme/views/concepts/ai/Chat/hooks/useChatSend.d.ts.map +1 -1
- package/dist/@types/@ecme/views/concepts/ai/Chat/store/aiModelStore.d.ts +25 -0
- package/dist/@types/@ecme/views/concepts/ai/Chat/store/aiModelStore.d.ts.map +1 -0
- package/dist/@types/base/components/forms/GenericForms/GenericArrayFormSection.d.ts +2 -2
- package/dist/@types/base/components/forms/GenericForms/GenericArrayFormSection.d.ts.map +1 -1
- package/dist/base/assets/styles/init.css +88 -5
- package/dist/base/components/forms/GenericForms/GenericArrayFormSection.js.map +1 -1
- package/lib/@ecme/components/layouts/PostLoginLayout/PostLoginLayout.tsx +2 -1
- package/lib/@ecme/components/template/OrbeAiWidget/OrbeAiWidget.tsx +125 -0
- package/lib/@ecme/components/template/OrbeAiWidget/ResizeHandles.tsx +47 -0
- package/lib/@ecme/components/template/OrbeAiWidget/index.ts +1 -0
- package/lib/@ecme/components/template/OrbeAiWidget/useResizablePanel.ts +108 -0
- package/lib/@ecme/components/view/ChatBox/ChatBox.tsx +6 -0
- package/lib/@ecme/components/view/ChatBox/components/MessageList.tsx +21 -7
- package/lib/@ecme/configs/navigation.config/concepts.navigation.config.ts +0 -16
- package/lib/@ecme/configs/routes.config/conceptsRoute.ts +0 -9
- package/lib/@ecme/services/AiService.ts +40 -0
- package/lib/@ecme/services/axios/AxiosBase.ts +45 -24
- package/lib/@ecme/views/concepts/ai/Chat/components/AiThinkingIndicator.tsx +24 -0
- package/lib/@ecme/views/concepts/ai/Chat/components/ChatLandingView.tsx +17 -17
- package/lib/@ecme/views/concepts/ai/Chat/components/ChatView.tsx +11 -4
- package/lib/@ecme/views/concepts/ai/Chat/components/ModelSelect.tsx +66 -0
- package/lib/@ecme/views/concepts/ai/Chat/hooks/useChatSend.ts +5 -3
- package/lib/@ecme/views/concepts/ai/Chat/store/aiModelStore.ts +23 -0
- package/lib/base/components/forms/GenericForms/GenericArrayFormSection.tsx +1 -1
- package/package.json +1 -1
|
@@ -22,6 +22,9 @@ export type MessageListProps = {
|
|
|
22
22
|
customRenderer?: (message: List, index: number) => string | ReactNode
|
|
23
23
|
customAction?: (message: List, index: number) => string | ReactNode
|
|
24
24
|
bubbleClass?: string
|
|
25
|
+
myBubbleClass?: string
|
|
26
|
+
botBubbleClass?: string
|
|
27
|
+
renderTyping?: () => ReactNode
|
|
25
28
|
messageListClass?: string
|
|
26
29
|
}
|
|
27
30
|
|
|
@@ -34,6 +37,9 @@ const MessageList = forwardRef<ScrollBarRef, MessageListProps>((props, ref) => {
|
|
|
34
37
|
customAction,
|
|
35
38
|
messageListClass,
|
|
36
39
|
bubbleClass,
|
|
40
|
+
myBubbleClass,
|
|
41
|
+
botBubbleClass,
|
|
42
|
+
renderTyping,
|
|
37
43
|
} = props
|
|
38
44
|
|
|
39
45
|
return (
|
|
@@ -49,7 +55,11 @@ const MessageList = forwardRef<ScrollBarRef, MessageListProps>((props, ref) => {
|
|
|
49
55
|
key={message.id}
|
|
50
56
|
showAvatar={showAvatar}
|
|
51
57
|
avatarGap={avatarGap}
|
|
52
|
-
bubbleClass={
|
|
58
|
+
bubbleClass={
|
|
59
|
+
message.isMyMessage
|
|
60
|
+
? myBubbleClass ?? bubbleClass
|
|
61
|
+
: botBubbleClass ?? bubbleClass
|
|
62
|
+
}
|
|
53
63
|
{...message}
|
|
54
64
|
{...(customRenderer
|
|
55
65
|
? {
|
|
@@ -71,14 +81,18 @@ const MessageList = forwardRef<ScrollBarRef, MessageListProps>((props, ref) => {
|
|
|
71
81
|
sender={typing}
|
|
72
82
|
type="regular"
|
|
73
83
|
showAvatar={showAvatar}
|
|
74
|
-
bubbleClass={bubbleClass}
|
|
84
|
+
bubbleClass={botBubbleClass ?? bubbleClass}
|
|
75
85
|
avatarGap={avatarGap}
|
|
76
86
|
content={
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
<span className="
|
|
81
|
-
|
|
87
|
+
renderTyping ? (
|
|
88
|
+
renderTyping()
|
|
89
|
+
) : (
|
|
90
|
+
<span className="flex items-center gap-2">
|
|
91
|
+
<span className="size-1.5 rounded-full bg-gray-700 motion-safe:animate-[bounce_1s_ease-in-out_infinite] dark:bg-gray-300"></span>
|
|
92
|
+
<span className="size-1.5 rounded-full bg-gray-700 motion-safe:animate-[bounce_0.5s_ease-in-out_infinite] dark:bg-gray-300"></span>
|
|
93
|
+
<span className="size-1.5 rounded-full bg-gray-700 motion-safe:animate-[bounce_1s_ease-in-out_infinite] dark:bg-gray-300"></span>
|
|
94
|
+
</span>
|
|
95
|
+
)
|
|
82
96
|
}
|
|
83
97
|
/>
|
|
84
98
|
)}
|
|
@@ -38,22 +38,6 @@ const conceptsNavigationConfig: NavigationTree[] = [
|
|
|
38
38
|
},
|
|
39
39
|
},
|
|
40
40
|
subMenu: [
|
|
41
|
-
{
|
|
42
|
-
key: 'concepts.ai.chat',
|
|
43
|
-
path: `${CONCEPTS_PREFIX_PATH}/ai/chat`,
|
|
44
|
-
title: 'Chat',
|
|
45
|
-
translateKey: 'nav.conceptsAi.chat',
|
|
46
|
-
icon: 'aiChat',
|
|
47
|
-
type: NAV_ITEM_TYPE_ITEM,
|
|
48
|
-
authority: [ADMIN, USER],
|
|
49
|
-
meta: {
|
|
50
|
-
description: {
|
|
51
|
-
translateKey: 'nav.conceptsAi.chatDesc',
|
|
52
|
-
label: 'AI-powered chat systems',
|
|
53
|
-
},
|
|
54
|
-
},
|
|
55
|
-
subMenu: [],
|
|
56
|
-
},
|
|
57
41
|
{
|
|
58
42
|
key: 'concepts.ai.image',
|
|
59
43
|
path: `${CONCEPTS_PREFIX_PATH}/ai/image`,
|
|
@@ -4,15 +4,6 @@ import { ADMIN, USER } from '@/constants/roles.constant'
|
|
|
4
4
|
import type { Routes } from '@/@types/routes'
|
|
5
5
|
|
|
6
6
|
const conceptsRoute: Routes = [
|
|
7
|
-
{
|
|
8
|
-
key: 'concepts.ai.chat',
|
|
9
|
-
path: `${CONCEPTS_PREFIX_PATH}/ai/chat`,
|
|
10
|
-
component: lazy(() => import('@/views/concepts/ai/Chat')),
|
|
11
|
-
authority: [ADMIN, USER],
|
|
12
|
-
meta: {
|
|
13
|
-
pageContainerType: 'contained',
|
|
14
|
-
},
|
|
15
|
-
},
|
|
16
7
|
{
|
|
17
8
|
key: 'concepts.ai.image',
|
|
18
9
|
path: `${CONCEPTS_PREFIX_PATH}/ai/image`,
|
|
@@ -1,4 +1,31 @@
|
|
|
1
1
|
import ApiService from './ApiService'
|
|
2
|
+
import { useAiModelStore } from '@/views/concepts/ai/Chat/store/aiModelStore'
|
|
3
|
+
|
|
4
|
+
/** Informa ao backend qual modelo usar. Sem o header, ele aplica o default. */
|
|
5
|
+
export const AI_MODEL_HEADER = 'X-Ai-Model'
|
|
6
|
+
|
|
7
|
+
export type AiModel = {
|
|
8
|
+
id: string
|
|
9
|
+
label: string
|
|
10
|
+
default: boolean
|
|
11
|
+
cloud: boolean
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type GetAiModelsResponse = {
|
|
15
|
+
models: AiModel[]
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Header do modelo escolhido, montado por chamada.
|
|
20
|
+
*
|
|
21
|
+
* Deliberadamente NAO e um interceptor global do AxiosBase: o chat vive no
|
|
22
|
+
* layout de todos os MFEs, e um interceptor faria toda requisicao de todo
|
|
23
|
+
* modulo passar por essa checagem so para servir ao /ai/*.
|
|
24
|
+
*/
|
|
25
|
+
const aiModelHeader = () => {
|
|
26
|
+
const selectedModel = useAiModelStore.getState().selectedModel
|
|
27
|
+
return selectedModel ? { [AI_MODEL_HEADER]: selectedModel } : undefined
|
|
28
|
+
}
|
|
2
29
|
|
|
3
30
|
export async function apiPostChat<T>(data: {
|
|
4
31
|
prompt: string
|
|
@@ -8,6 +35,8 @@ export async function apiPostChat<T>(data: {
|
|
|
8
35
|
url: '/ai/chat',
|
|
9
36
|
method: 'post',
|
|
10
37
|
data,
|
|
38
|
+
headers: aiModelHeader(),
|
|
39
|
+
skipGlobalLoading: true,
|
|
11
40
|
})
|
|
12
41
|
}
|
|
13
42
|
|
|
@@ -15,6 +44,15 @@ export async function apiGetChatHistory<T>() {
|
|
|
15
44
|
return ApiService.fetchDataWithAxios<T>({
|
|
16
45
|
url: '/ai/chat/history',
|
|
17
46
|
method: 'get',
|
|
47
|
+
skipGlobalLoading: true,
|
|
48
|
+
})
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export async function apiGetAiModels<T>() {
|
|
52
|
+
return ApiService.fetchDataWithAxios<T>({
|
|
53
|
+
url: '/ai/models',
|
|
54
|
+
method: 'get',
|
|
55
|
+
skipGlobalLoading: true,
|
|
18
56
|
})
|
|
19
57
|
}
|
|
20
58
|
|
|
@@ -25,6 +63,7 @@ export async function apiGetImages<T, U extends Record<string, unknown>>(
|
|
|
25
63
|
url: '/ai/images',
|
|
26
64
|
method: 'get',
|
|
27
65
|
params,
|
|
66
|
+
skipGlobalLoading: true,
|
|
28
67
|
})
|
|
29
68
|
}
|
|
30
69
|
|
|
@@ -35,5 +74,6 @@ export async function apiPostImages<T, U extends Record<string, unknown>>(
|
|
|
35
74
|
url: '/ai/images',
|
|
36
75
|
method: 'post',
|
|
37
76
|
data,
|
|
77
|
+
skipGlobalLoading: true,
|
|
38
78
|
})
|
|
39
79
|
}
|
|
@@ -6,6 +6,19 @@ import { useLoadingConfigStore, getCurrentMicrofrontendId } from "@base/store";
|
|
|
6
6
|
import AxiosRequestIntrceptorConfigCallback from "./AxiosRequestIntrceptorConfigCallback";
|
|
7
7
|
import AxiosResponseIntrceptorErrorCallback from "./AxiosResponseIntrceptorErrorCallback";
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Permite que uma requisição específica seja excluída do loading global
|
|
11
|
+
* (overlay de tela cheia). Útil para chamadas que exibem seu próprio
|
|
12
|
+
* indicador de carregamento — ex.: o chat de IA, que mostra o balãozinho
|
|
13
|
+
* de "digitando". Comportamento padrão inalterado: só afeta requisições
|
|
14
|
+
* que marcarem explicitamente `skipGlobalLoading: true`.
|
|
15
|
+
*/
|
|
16
|
+
declare module "axios" {
|
|
17
|
+
interface AxiosRequestConfig {
|
|
18
|
+
skipGlobalLoading?: boolean;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
9
22
|
const AxiosBase = axios.create({
|
|
10
23
|
timeout: 600000,
|
|
11
24
|
baseURL: API_URL,
|
|
@@ -13,25 +26,37 @@ const AxiosBase = axios.create({
|
|
|
13
26
|
|
|
14
27
|
let activeRequests = 0;
|
|
15
28
|
|
|
29
|
+
const startGlobalLoading = () => {
|
|
30
|
+
const microfrontendId = getCurrentMicrofrontendId();
|
|
31
|
+
const loadingConfig = useLoadingConfigStore.getState();
|
|
32
|
+
if (loadingConfig.getGlobalLoadingEnabled(microfrontendId)) {
|
|
33
|
+
useThemeStore.getState().setLoading(true);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const stopGlobalLoading = () => {
|
|
38
|
+
const microfrontendId = getCurrentMicrofrontendId();
|
|
39
|
+
const loadingConfig = useLoadingConfigStore.getState();
|
|
40
|
+
if (loadingConfig.getGlobalLoadingEnabled(microfrontendId)) {
|
|
41
|
+
useThemeStore.getState().setLoading(false);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
16
45
|
AxiosBase.interceptors.request.use(
|
|
17
46
|
(config) => {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
if (loadingConfig.getGlobalLoadingEnabled(microfrontendId)) {
|
|
23
|
-
useThemeStore.getState().setLoading(true);
|
|
47
|
+
if (!config.skipGlobalLoading) {
|
|
48
|
+
activeRequests++;
|
|
49
|
+
if (activeRequests === 1) {
|
|
50
|
+
startGlobalLoading();
|
|
24
51
|
}
|
|
25
52
|
}
|
|
26
53
|
return AxiosRequestIntrceptorConfigCallback(config);
|
|
27
54
|
},
|
|
28
55
|
(error) => {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
if (loadingConfig.getGlobalLoadingEnabled(microfrontendId)) {
|
|
34
|
-
useThemeStore.getState().setLoading(false);
|
|
56
|
+
if (!error?.config?.skipGlobalLoading) {
|
|
57
|
+
activeRequests = Math.max(0, activeRequests - 1);
|
|
58
|
+
if (activeRequests === 0) {
|
|
59
|
+
stopGlobalLoading();
|
|
35
60
|
}
|
|
36
61
|
}
|
|
37
62
|
return Promise.reject(error);
|
|
@@ -40,23 +65,19 @@ AxiosBase.interceptors.request.use(
|
|
|
40
65
|
|
|
41
66
|
AxiosBase.interceptors.response.use(
|
|
42
67
|
(response) => {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if (loadingConfig.getGlobalLoadingEnabled(microfrontendId)) {
|
|
48
|
-
useThemeStore.getState().setLoading(false);
|
|
68
|
+
if (!response.config?.skipGlobalLoading) {
|
|
69
|
+
activeRequests = Math.max(0, activeRequests - 1);
|
|
70
|
+
if (activeRequests === 0) {
|
|
71
|
+
stopGlobalLoading();
|
|
49
72
|
}
|
|
50
73
|
}
|
|
51
74
|
return response;
|
|
52
75
|
},
|
|
53
76
|
(error: AxiosError) => {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
if (loadingConfig.getGlobalLoadingEnabled(microfrontendId)) {
|
|
59
|
-
useThemeStore.getState().setLoading(false);
|
|
77
|
+
if (!error.config?.skipGlobalLoading) {
|
|
78
|
+
activeRequests = Math.max(0, activeRequests - 1);
|
|
79
|
+
if (activeRequests === 0) {
|
|
80
|
+
stopGlobalLoading();
|
|
60
81
|
}
|
|
61
82
|
}
|
|
62
83
|
AxiosResponseIntrceptorErrorCallback(error);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { TbSparkles } from 'react-icons/tb'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Indicador de "pensando" do agente Orbe AI — loader profissional exibido
|
|
5
|
+
* enquanto a resposta está sendo gerada (estado isTyping do chat).
|
|
6
|
+
*/
|
|
7
|
+
const AiThinkingIndicator = () => {
|
|
8
|
+
return (
|
|
9
|
+
<span className="flex items-center gap-2 text-gray-500 dark:text-gray-400">
|
|
10
|
+
<TbSparkles
|
|
11
|
+
size={18}
|
|
12
|
+
className="text-primary motion-safe:animate-pulse"
|
|
13
|
+
/>
|
|
14
|
+
<span className="text-sm">Orbe AI está pensando</span>
|
|
15
|
+
<span className="inline-flex items-center gap-1">
|
|
16
|
+
<span className="size-1.5 rounded-full bg-primary motion-safe:animate-bounce [animation-delay:-0.3s]" />
|
|
17
|
+
<span className="size-1.5 rounded-full bg-primary motion-safe:animate-bounce [animation-delay:-0.15s]" />
|
|
18
|
+
<span className="size-1.5 rounded-full bg-primary motion-safe:animate-bounce" />
|
|
19
|
+
</span>
|
|
20
|
+
</span>
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default AiThinkingIndicator
|
|
@@ -22,23 +22,23 @@ const promptSuggestion: {
|
|
|
22
22
|
type: PromptType
|
|
23
23
|
}[] = [
|
|
24
24
|
{
|
|
25
|
-
title: '
|
|
26
|
-
prompt:
|
|
25
|
+
title: 'Como consultar um pedido de venda?',
|
|
26
|
+
prompt: 'Como faço para consultar um pedido de venda no Orbe Agro?',
|
|
27
27
|
type: 'guide',
|
|
28
28
|
},
|
|
29
29
|
{
|
|
30
|
-
title: '
|
|
31
|
-
prompt:
|
|
32
|
-
type: '
|
|
30
|
+
title: 'Para que serve o módulo de Trade?',
|
|
31
|
+
prompt: 'Explique para que serve o módulo de Trade no Orbe Agro.',
|
|
32
|
+
type: 'idea',
|
|
33
33
|
},
|
|
34
34
|
{
|
|
35
|
-
title: '
|
|
36
|
-
prompt:
|
|
37
|
-
type: '
|
|
35
|
+
title: 'Como abrir um processo de armazenagem?',
|
|
36
|
+
prompt: 'Quais são os passos para abrir um processo de armazenagem de grãos no Orbe Agro?',
|
|
37
|
+
type: 'writing',
|
|
38
38
|
},
|
|
39
39
|
{
|
|
40
|
-
title: '
|
|
41
|
-
prompt:
|
|
40
|
+
title: 'Onde vejo o limite de crédito de um cliente?',
|
|
41
|
+
prompt: 'Onde encontro o limite de crédito de um cliente no Orbe Agro?',
|
|
42
42
|
type: 'coding',
|
|
43
43
|
},
|
|
44
44
|
]
|
|
@@ -47,20 +47,20 @@ const ChatLandingView = () => {
|
|
|
47
47
|
const { handleSend } = useChatSend()
|
|
48
48
|
|
|
49
49
|
return (
|
|
50
|
-
<div className="max-w-[900px] w-full mx-auto mt-
|
|
50
|
+
<div className="max-w-[900px] w-full mx-auto mt-6">
|
|
51
51
|
<div>
|
|
52
|
-
<div className="heading-text text-
|
|
53
|
-
<span className="font-semibold bg-gradient-to-r from-indigo-500 to-red-400 bg-clip-text text-transparent text-
|
|
54
|
-
|
|
52
|
+
<div className="heading-text text-2xl leading-snug">
|
|
53
|
+
<span className="font-semibold bg-gradient-to-r from-indigo-500 to-red-400 bg-clip-text text-transparent text-3xl">
|
|
54
|
+
Olá!
|
|
55
55
|
</span>
|
|
56
56
|
<br />
|
|
57
|
-
<span>
|
|
57
|
+
<span>Como posso te ajudar hoje?</span>
|
|
58
58
|
</div>
|
|
59
|
-
<div className="mt-
|
|
59
|
+
<div className="mt-6 grid grid-cols-1 sm:grid-cols-2 gap-3">
|
|
60
60
|
{promptSuggestion.map((suggestion) => (
|
|
61
61
|
<div
|
|
62
62
|
key={suggestion.title}
|
|
63
|
-
className="flex flex-col gap-
|
|
63
|
+
className="flex flex-col gap-3 justify-between rounded-xl bg-gray-100 dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-gray-600 p-4 min-h-28 cursor-pointer"
|
|
64
64
|
role="button"
|
|
65
65
|
onClick={() => handleSend(suggestion.title)}
|
|
66
66
|
>
|
|
@@ -5,6 +5,7 @@ import ChatLandingView from './ChatLandingView'
|
|
|
5
5
|
import ChatMobileNav from './ChatMobileNav'
|
|
6
6
|
import ChatCustomContent from './ChatCustomContent'
|
|
7
7
|
import ChatCustomAction from './ChatCustomAction'
|
|
8
|
+
import AiThinkingIndicator from './AiThinkingIndicator'
|
|
8
9
|
import { usGenerativeChatStore } from '../store/generativeChatStore'
|
|
9
10
|
import useChatSend from '../hooks/useChatSend'
|
|
10
11
|
import type { ScrollBarRef } from '@/components/ui/ScrollBar'
|
|
@@ -55,10 +56,12 @@ const ChatView = () => {
|
|
|
55
56
|
<ChatBox
|
|
56
57
|
ref={scrollRef}
|
|
57
58
|
messageList={messageList}
|
|
58
|
-
placeholder="
|
|
59
|
+
placeholder="Pergunte algo sobre o Orbe Agro…"
|
|
59
60
|
showMessageList={Boolean(selectedConversation)}
|
|
60
|
-
showAvatar={
|
|
61
|
-
avatarGap={
|
|
61
|
+
showAvatar={false}
|
|
62
|
+
avatarGap={false}
|
|
63
|
+
myBubbleClass="bg-primary text-white"
|
|
64
|
+
renderTyping={() => <AiThinkingIndicator />}
|
|
62
65
|
containerClass="h-[calc(100%-30px)] xl:h-full"
|
|
63
66
|
messageListClass="h-[calc(100%-100px)] xl:h-[calc(100%-70px)]"
|
|
64
67
|
typing={
|
|
@@ -98,7 +101,11 @@ const ChatView = () => {
|
|
|
98
101
|
}}
|
|
99
102
|
onInputChange={handleInputChange}
|
|
100
103
|
>
|
|
101
|
-
{!selectedConversation &&
|
|
104
|
+
{!selectedConversation && (
|
|
105
|
+
<div className="flex-1 min-h-0 overflow-y-auto">
|
|
106
|
+
<ChatLandingView />
|
|
107
|
+
</div>
|
|
108
|
+
)}
|
|
102
109
|
</ChatBox>
|
|
103
110
|
</Card>
|
|
104
111
|
)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react'
|
|
2
|
+
import { apiGetAiModels } from '@/services/AiService'
|
|
3
|
+
import { useAiModelStore } from '../store/aiModelStore'
|
|
4
|
+
import type { AiModel, GetAiModelsResponse } from '@/services/AiService'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Seletor do modelo de IA do chat (GET /ai/models). Se o backend estiver
|
|
8
|
+
* indisponivel ou nao expuser modelos, o seletor nao aparece e o backend usa o
|
|
9
|
+
* modelo default — por isso ele pode ser montado sem medo em qualquer MFE.
|
|
10
|
+
*/
|
|
11
|
+
const ModelSelect = () => {
|
|
12
|
+
const [models, setModels] = useState<AiModel[]>([])
|
|
13
|
+
const selectedModel = useAiModelStore((state) => state.selectedModel)
|
|
14
|
+
const setSelectedModel = useAiModelStore((state) => state.setSelectedModel)
|
|
15
|
+
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
let active = true
|
|
18
|
+
apiGetAiModels<GetAiModelsResponse>()
|
|
19
|
+
.then((resp) => {
|
|
20
|
+
if (!active) {
|
|
21
|
+
return
|
|
22
|
+
}
|
|
23
|
+
setModels(resp.models)
|
|
24
|
+
const current = useAiModelStore.getState().selectedModel
|
|
25
|
+
const exists = resp.models.some((model) => model.id === current)
|
|
26
|
+
if (!current || !exists) {
|
|
27
|
+
const fallback =
|
|
28
|
+
resp.models.find((model) => model.default) ??
|
|
29
|
+
resp.models[0]
|
|
30
|
+
if (fallback) {
|
|
31
|
+
useAiModelStore
|
|
32
|
+
.getState()
|
|
33
|
+
.setSelectedModel(fallback.id)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
})
|
|
37
|
+
.catch(() => {
|
|
38
|
+
// Sem lista de modelos: o backend responde com o default.
|
|
39
|
+
})
|
|
40
|
+
return () => {
|
|
41
|
+
active = false
|
|
42
|
+
}
|
|
43
|
+
}, [])
|
|
44
|
+
|
|
45
|
+
if (models.length === 0) {
|
|
46
|
+
return null
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<select
|
|
51
|
+
aria-label="Modelo de IA"
|
|
52
|
+
title="Modelo de IA"
|
|
53
|
+
className="max-w-[9rem] truncate rounded-lg border border-gray-300 bg-white px-2 py-1 text-xs dark:border-gray-600 dark:bg-gray-800"
|
|
54
|
+
value={selectedModel ?? ''}
|
|
55
|
+
onChange={(event) => setSelectedModel(event.target.value)}
|
|
56
|
+
>
|
|
57
|
+
{models.map((model) => (
|
|
58
|
+
<option key={model.id} value={model.id}>
|
|
59
|
+
{model.label}
|
|
60
|
+
</option>
|
|
61
|
+
))}
|
|
62
|
+
</select>
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export default ModelSelect
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { usGenerativeChatStore } from '../store/generativeChatStore'
|
|
2
2
|
import { apiPostChat } from '@/services/AiService'
|
|
3
|
+
import { useAuth } from '@/auth'
|
|
3
4
|
import dayjs from 'dayjs'
|
|
4
5
|
import uniqueId from 'lodash/uniqueId'
|
|
5
6
|
import type { PostAiChatResponse } from '../types'
|
|
6
7
|
|
|
7
8
|
const useChatSend = () => {
|
|
9
|
+
const { user } = useAuth()
|
|
8
10
|
const {
|
|
9
11
|
selectedConversation,
|
|
10
12
|
setSelectedConversation,
|
|
@@ -17,9 +19,9 @@ const useChatSend = () => {
|
|
|
17
19
|
pushConversation(id, {
|
|
18
20
|
id: uniqueId('ai-conversation-'),
|
|
19
21
|
sender: {
|
|
20
|
-
id: '
|
|
21
|
-
name:
|
|
22
|
-
avatarImageUrl:
|
|
22
|
+
id: user.userId ?? 'me',
|
|
23
|
+
name: user.userName ?? 'Você',
|
|
24
|
+
avatarImageUrl: user.avatar ?? undefined,
|
|
23
25
|
},
|
|
24
26
|
content: prompt,
|
|
25
27
|
timestamp: dayjs().toDate(),
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { create } from 'zustand'
|
|
2
|
+
import { persist } from 'zustand/middleware'
|
|
3
|
+
|
|
4
|
+
type AiModelState = {
|
|
5
|
+
selectedModel: string | null
|
|
6
|
+
setSelectedModel: (model: string) => void
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Modelo de IA escolhido pelo usuario para o chat do Orbe AI.
|
|
11
|
+
*
|
|
12
|
+
* Persistido em localStorage: como todos os MFEs vivem na mesma origem, a
|
|
13
|
+
* escolha acompanha o usuario entre os modulos e entre sessoes.
|
|
14
|
+
*/
|
|
15
|
+
export const useAiModelStore = create<AiModelState>()(
|
|
16
|
+
persist(
|
|
17
|
+
(set) => ({
|
|
18
|
+
selectedModel: null,
|
|
19
|
+
setSelectedModel: (model) => set({ selectedModel: model }),
|
|
20
|
+
}),
|
|
21
|
+
{ name: 'orbe-ai-model' },
|
|
22
|
+
),
|
|
23
|
+
)
|
|
@@ -27,7 +27,7 @@ type GeneralFormSectionProps<
|
|
|
27
27
|
K extends ArrayPath<T>,
|
|
28
28
|
> = IFormSectionBaseProps<T> & {
|
|
29
29
|
nameArray: K
|
|
30
|
-
defaultAppendItem:
|
|
30
|
+
defaultAppendItem: any
|
|
31
31
|
nameCampoStringArray: string[]
|
|
32
32
|
nameCampoNumberArray: string[]
|
|
33
33
|
nameCampoSelectArray?: string[]
|
package/package.json
CHANGED