@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.
Files changed (88) hide show
  1. package/dist/@ecme/assets/styles/app.css +82 -5
  2. package/dist/@ecme/assets/styles/tailwind/index.css +89 -6
  3. package/dist/@ecme/components/layouts/PostLoginLayout/PostLoginLayout.js +3 -0
  4. package/dist/@ecme/components/layouts/PostLoginLayout/PostLoginLayout.js.map +1 -1
  5. package/dist/@ecme/components/template/Footer.js +1 -1
  6. package/dist/@ecme/components/template/OrbeAiWidget/OrbeAiWidget.js +100 -0
  7. package/dist/@ecme/components/template/OrbeAiWidget/OrbeAiWidget.js.map +1 -0
  8. package/dist/@ecme/components/template/OrbeAiWidget/ResizeHandles.js +37 -0
  9. package/dist/@ecme/components/template/OrbeAiWidget/ResizeHandles.js.map +1 -0
  10. package/dist/@ecme/components/template/OrbeAiWidget/index.js +5 -0
  11. package/dist/@ecme/components/template/OrbeAiWidget/index.js.map +1 -0
  12. package/dist/@ecme/components/template/OrbeAiWidget/useResizablePanel.js +77 -0
  13. package/dist/@ecme/components/template/OrbeAiWidget/useResizablePanel.js.map +1 -0
  14. package/dist/@ecme/components/view/ChatBox/ChatBox.js +7 -1
  15. package/dist/@ecme/components/view/ChatBox/ChatBox.js.map +1 -1
  16. package/dist/@ecme/components/view/ChatBox/components/MessageList.js +7 -4
  17. package/dist/@ecme/components/view/ChatBox/components/MessageList.js.map +1 -1
  18. package/dist/@ecme/configs/navigation.config/concepts.navigation.config.js +0 -16
  19. package/dist/@ecme/configs/navigation.config/concepts.navigation.config.js.map +1 -1
  20. package/dist/@ecme/configs/routes.config/conceptsRoute.js +0 -9
  21. package/dist/@ecme/configs/routes.config/conceptsRoute.js.map +1 -1
  22. package/dist/@ecme/index.js +3 -1
  23. package/dist/@ecme/services/AiService.js +24 -4
  24. package/dist/@ecme/services/AiService.js.map +1 -1
  25. package/dist/@ecme/services/axios/AxiosBase.js +30 -24
  26. package/dist/@ecme/services/axios/AxiosBase.js.map +1 -1
  27. package/dist/@ecme/services/index.js +3 -1
  28. package/dist/@ecme/views/concepts/ai/Chat/components/AiThinkingIndicator.js +23 -0
  29. package/dist/@ecme/views/concepts/ai/Chat/components/AiThinkingIndicator.js.map +1 -0
  30. package/dist/@ecme/views/concepts/ai/Chat/components/ChatLandingView.js +16 -16
  31. package/dist/@ecme/views/concepts/ai/Chat/components/ChatLandingView.js.map +1 -1
  32. package/dist/@ecme/views/concepts/ai/Chat/components/ChatView.js +7 -4
  33. package/dist/@ecme/views/concepts/ai/Chat/components/ChatView.js.map +1 -1
  34. package/dist/@ecme/views/concepts/ai/Chat/components/ModelSelect.js +48 -0
  35. package/dist/@ecme/views/concepts/ai/Chat/components/ModelSelect.js.map +1 -0
  36. package/dist/@ecme/views/concepts/ai/Chat/hooks/useChatSend.js +6 -3
  37. package/dist/@ecme/views/concepts/ai/Chat/hooks/useChatSend.js.map +1 -1
  38. package/dist/@ecme/views/concepts/ai/Chat/store/aiModelStore.js +15 -0
  39. package/dist/@ecme/views/concepts/ai/Chat/store/aiModelStore.js.map +1 -0
  40. package/dist/@types/@ecme/components/layouts/PostLoginLayout/PostLoginLayout.d.ts.map +1 -1
  41. package/dist/@types/@ecme/components/template/OrbeAiWidget/OrbeAiWidget.d.ts +3 -0
  42. package/dist/@types/@ecme/components/template/OrbeAiWidget/OrbeAiWidget.d.ts.map +1 -0
  43. package/dist/@types/@ecme/components/template/OrbeAiWidget/ResizeHandles.d.ts +9 -0
  44. package/dist/@types/@ecme/components/template/OrbeAiWidget/ResizeHandles.d.ts.map +1 -0
  45. package/dist/@types/@ecme/components/template/OrbeAiWidget/index.d.ts +2 -0
  46. package/dist/@types/@ecme/components/template/OrbeAiWidget/index.d.ts.map +1 -0
  47. package/dist/@types/@ecme/components/template/OrbeAiWidget/useResizablePanel.d.ts +19 -0
  48. package/dist/@types/@ecme/components/template/OrbeAiWidget/useResizablePanel.d.ts.map +1 -0
  49. package/dist/@types/@ecme/components/view/ChatBox/ChatBox.d.ts.map +1 -1
  50. package/dist/@types/@ecme/components/view/ChatBox/components/MessageList.d.ts +3 -0
  51. package/dist/@types/@ecme/components/view/ChatBox/components/MessageList.d.ts.map +1 -1
  52. package/dist/@types/@ecme/configs/navigation.config/concepts.navigation.config.d.ts.map +1 -1
  53. package/dist/@types/@ecme/configs/routes.config/conceptsRoute.d.ts.map +1 -1
  54. package/dist/@types/@ecme/services/AiService.d.ts +12 -0
  55. package/dist/@types/@ecme/services/AiService.d.ts.map +1 -1
  56. package/dist/@types/@ecme/services/axios/AxiosBase.d.ts +12 -0
  57. package/dist/@types/@ecme/services/axios/AxiosBase.d.ts.map +1 -1
  58. package/dist/@types/@ecme/views/concepts/ai/Chat/components/AiThinkingIndicator.d.ts +7 -0
  59. package/dist/@types/@ecme/views/concepts/ai/Chat/components/AiThinkingIndicator.d.ts.map +1 -0
  60. package/dist/@types/@ecme/views/concepts/ai/Chat/components/ChatView.d.ts.map +1 -1
  61. package/dist/@types/@ecme/views/concepts/ai/Chat/components/ModelSelect.d.ts +8 -0
  62. package/dist/@types/@ecme/views/concepts/ai/Chat/components/ModelSelect.d.ts.map +1 -0
  63. package/dist/@types/@ecme/views/concepts/ai/Chat/hooks/useChatSend.d.ts.map +1 -1
  64. package/dist/@types/@ecme/views/concepts/ai/Chat/store/aiModelStore.d.ts +25 -0
  65. package/dist/@types/@ecme/views/concepts/ai/Chat/store/aiModelStore.d.ts.map +1 -0
  66. package/dist/@types/base/components/forms/GenericForms/GenericArrayFormSection.d.ts +2 -2
  67. package/dist/@types/base/components/forms/GenericForms/GenericArrayFormSection.d.ts.map +1 -1
  68. package/dist/base/assets/styles/init.css +88 -5
  69. package/dist/base/components/forms/GenericForms/GenericArrayFormSection.js.map +1 -1
  70. package/lib/@ecme/components/layouts/PostLoginLayout/PostLoginLayout.tsx +2 -1
  71. package/lib/@ecme/components/template/OrbeAiWidget/OrbeAiWidget.tsx +125 -0
  72. package/lib/@ecme/components/template/OrbeAiWidget/ResizeHandles.tsx +47 -0
  73. package/lib/@ecme/components/template/OrbeAiWidget/index.ts +1 -0
  74. package/lib/@ecme/components/template/OrbeAiWidget/useResizablePanel.ts +108 -0
  75. package/lib/@ecme/components/view/ChatBox/ChatBox.tsx +6 -0
  76. package/lib/@ecme/components/view/ChatBox/components/MessageList.tsx +21 -7
  77. package/lib/@ecme/configs/navigation.config/concepts.navigation.config.ts +0 -16
  78. package/lib/@ecme/configs/routes.config/conceptsRoute.ts +0 -9
  79. package/lib/@ecme/services/AiService.ts +40 -0
  80. package/lib/@ecme/services/axios/AxiosBase.ts +45 -24
  81. package/lib/@ecme/views/concepts/ai/Chat/components/AiThinkingIndicator.tsx +24 -0
  82. package/lib/@ecme/views/concepts/ai/Chat/components/ChatLandingView.tsx +17 -17
  83. package/lib/@ecme/views/concepts/ai/Chat/components/ChatView.tsx +11 -4
  84. package/lib/@ecme/views/concepts/ai/Chat/components/ModelSelect.tsx +66 -0
  85. package/lib/@ecme/views/concepts/ai/Chat/hooks/useChatSend.ts +5 -3
  86. package/lib/@ecme/views/concepts/ai/Chat/store/aiModelStore.ts +23 -0
  87. package/lib/base/components/forms/GenericForms/GenericArrayFormSection.tsx +1 -1
  88. 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={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
- <span className="flex items-center gap-2">
78
- <span className="size-1.5 rounded-full bg-gray-700 motion-safe:animate-[bounce_1s_ease-in-out_infinite] dark:bg-gray-300"></span>
79
- <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>
80
- <span className="size-1.5 rounded-full bg-gray-700 motion-safe:animate-[bounce_1s_ease-in-out_infinite] dark:bg-gray-300"></span>
81
- </span>
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
- activeRequests++;
19
- if (activeRequests === 1) {
20
- const microfrontendId = getCurrentMicrofrontendId();
21
- const loadingConfig = useLoadingConfigStore.getState();
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
- activeRequests = Math.max(0, activeRequests - 1);
30
- if (activeRequests === 0) {
31
- const microfrontendId = getCurrentMicrofrontendId();
32
- const loadingConfig = useLoadingConfigStore.getState();
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
- activeRequests = Math.max(0, activeRequests - 1);
44
- if (activeRequests === 0) {
45
- const microfrontendId = getCurrentMicrofrontendId();
46
- const loadingConfig = useLoadingConfigStore.getState();
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
- activeRequests = Math.max(0, activeRequests - 1);
55
- if (activeRequests === 0) {
56
- const microfrontendId = getCurrentMicrofrontendId();
57
- const loadingConfig = useLoadingConfigStore.getState();
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: 'Help me sound like an expert for an upcoming trip',
26
- prompt: `I'm going whale watching this weekend and want to appear knowledgeable about killer whales. Tell me a few unique and interesting facts I can share.`,
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: 'Outline an logical sales pitch for a new product',
31
- prompt: `Structure a sales pitch for a hair dryer that's also a microphone. Be concise and organize the sales pitch logically. `,
32
- type: 'writing',
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: 'Help me get organized with a list of 10 tips',
36
- prompt: `Give me 10 tips for room organization. `,
37
- type: 'idea',
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: 'Write code for a specific task, including edge cases',
41
- prompt: `Write a Java function that takes a path as an input and creates a file storing the current system date. Consider edge cases.`,
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-20">
50
+ <div className="max-w-[900px] w-full mx-auto mt-6">
51
51
  <div>
52
- <div className="heading-text text-4xl leading-snug">
53
- <span className="font-semibold bg-gradient-to-r from-indigo-500 to-red-400 bg-clip-text text-transparent text-5xl">
54
- Hello, there
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>How can I help you today?</span>
57
+ <span>Como posso te ajudar hoje?</span>
58
58
  </div>
59
- <div className="mt-8 grid grid-cols-2 xl:grid-cols-4 gap-4">
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-4 justify-between rounded-xl bg-gray-100 dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-gray-600 p-5 min-h-40 2xl:min-h-60 cursor-pointer"
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="Enter a prompt here"
59
+ placeholder="Pergunte algo sobre o Orbe Agro…"
59
60
  showMessageList={Boolean(selectedConversation)}
60
- showAvatar={true}
61
- avatarGap={true}
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 && <ChatLandingView />}
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: '1',
21
- name: 'Angelina Gotelli',
22
- avatarImageUrl: '/img/avatars/thumb-1.jpg',
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: FieldArray<T, K>
30
+ defaultAppendItem: any
31
31
  nameCampoStringArray: string[]
32
32
  nameCampoNumberArray: string[]
33
33
  nameCampoSelectArray?: string[]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orbe-agro/client-core",
3
- "version": "5.6.201",
3
+ "version": "5.6.202",
4
4
  "description": "Biblioteca principal de componentes e utilidades para os microfrontends do Orbe Agro.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",