app-tutor-ai-consumer 1.4.0 → 1.6.0

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 (102) hide show
  1. package/.github/workflows/staging-staging.yml +148 -0
  2. package/.github/workflows/staging.yml +1 -2
  3. package/CHANGELOG.md +19 -0
  4. package/config/rspack/rspack.config.js +5 -1
  5. package/config/vitest/__mocks__/icons.tsx +3 -0
  6. package/config/vitest/__mocks__/intersection-observer.ts +10 -0
  7. package/config/vitest/__mocks__/sparkie.tsx +9 -9
  8. package/config/vitest/__mocks__/use-init-sparkie.tsx +14 -0
  9. package/config/vitest/vitest.config.mts +13 -8
  10. package/environments/.env.test +2 -0
  11. package/eslint.config.mjs +27 -0
  12. package/package.json +8 -4
  13. package/public/index.html +3 -4
  14. package/src/@types/index.d.ts +5 -2
  15. package/src/config/styles/global.css +2 -2
  16. package/src/config/tanstack/query-client.ts +3 -2
  17. package/src/config/tests/utils.tsx +3 -2
  18. package/src/config/tests/wrappers.tsx +4 -1
  19. package/src/development-bootstrap.tsx +15 -15
  20. package/src/index.tsx +37 -5
  21. package/src/lib/components/icons/ai-color.svg +17 -0
  22. package/src/lib/components/icons/arrow-down.svg +5 -0
  23. package/src/lib/components/icons/chevron-down.svg +4 -0
  24. package/src/lib/components/icons/icon-names.d.ts +1 -1
  25. package/src/lib/components/markdownrenderer/markdownrenderer.tsx +7 -9
  26. package/src/lib/hooks/index.ts +3 -0
  27. package/src/lib/hooks/use-intersection-observer-reverse-scroll/index.ts +2 -0
  28. package/src/lib/hooks/use-intersection-observer-reverse-scroll/use-intersection-observer-reverse-scroll.tsx +147 -0
  29. package/src/lib/hooks/use-ref-client-height/index.ts +2 -0
  30. package/src/lib/hooks/use-ref-client-height/use-ref-client-height.tsx +38 -0
  31. package/src/lib/hooks/use-scroll-to-ref/index.ts +2 -0
  32. package/src/lib/hooks/use-scroll-to-ref/use-scroll-to-ref.tsx +14 -0
  33. package/src/lib/hooks/use-throttle/index.ts +3 -0
  34. package/src/lib/hooks/use-throttle/types.ts +13 -0
  35. package/src/lib/hooks/use-throttle/use-throttle.spec.tsx +296 -0
  36. package/src/lib/hooks/use-throttle/use-throttle.tsx +91 -0
  37. package/src/lib/utils/is-text-empty.ts +3 -0
  38. package/src/main/main.spec.tsx +7 -6
  39. package/src/modules/cursor/__tests__/icursor-update.builder.ts +42 -0
  40. package/src/modules/cursor/hooks/index.ts +1 -0
  41. package/src/modules/cursor/hooks/use-update-cursor/index.ts +2 -0
  42. package/src/modules/cursor/hooks/use-update-cursor/use-update-cursor.spec.tsx +23 -0
  43. package/src/modules/cursor/hooks/use-update-cursor/use-update-cursor.ts +11 -0
  44. package/src/modules/cursor/index.ts +2 -0
  45. package/src/modules/cursor/service.ts +15 -0
  46. package/src/modules/cursor/types.ts +9 -0
  47. package/src/modules/global-providers/index.ts +1 -0
  48. package/src/modules/messages/__tests__/parsed-message.builder.ts +164 -0
  49. package/src/modules/messages/components/chat-input/chat-input.spec.tsx +72 -0
  50. package/src/modules/messages/components/chat-input/chat-input.tsx +52 -6
  51. package/src/modules/messages/components/index.ts +1 -0
  52. package/src/modules/messages/components/message-item/message-item.spec.tsx +2 -2
  53. package/src/modules/messages/components/message-item/message-item.tsx +14 -1
  54. package/src/modules/messages/components/message-item-end-of-scroll/index.ts +2 -0
  55. package/src/modules/messages/components/message-item-end-of-scroll/message-item-end-of-scroll.tsx +14 -0
  56. package/src/modules/messages/components/message-item-error/index.ts +2 -0
  57. package/src/modules/messages/components/message-item-error/message-item-error.tsx +25 -0
  58. package/src/modules/messages/components/message-item-loading/index.ts +2 -0
  59. package/src/modules/messages/components/message-item-loading/message-item-loading.tsx +16 -0
  60. package/src/modules/messages/components/message-skeleton/index.ts +1 -0
  61. package/src/modules/messages/components/message-skeleton/message-skeleton.tsx +23 -0
  62. package/src/modules/messages/components/messages-list/index.ts +1 -1
  63. package/src/modules/messages/components/messages-list/messages-list.tsx +82 -39
  64. package/src/modules/messages/constants.ts +1 -0
  65. package/src/modules/messages/hooks/index.ts +5 -0
  66. package/src/modules/messages/hooks/use-all-messages/index.ts +2 -0
  67. package/src/modules/messages/hooks/use-all-messages/use-all-messages.tsx +30 -0
  68. package/src/modules/messages/hooks/use-infinite-get-messages/index.ts +2 -0
  69. package/src/modules/messages/hooks/use-infinite-get-messages/use-infinite-get-messages.spec.tsx +65 -0
  70. package/src/modules/messages/hooks/use-infinite-get-messages/use-infinite-get-messages.tsx +81 -0
  71. package/src/modules/messages/hooks/use-manage-scroll/index.ts +2 -0
  72. package/src/modules/messages/hooks/use-manage-scroll/use-manage-scroll.tsx +70 -0
  73. package/src/modules/messages/hooks/use-send-text-message/index.ts +2 -0
  74. package/src/modules/messages/hooks/use-send-text-message/use-send-text-message.spec.tsx +86 -0
  75. package/src/modules/messages/hooks/use-send-text-message/use-send-text-message.tsx +60 -0
  76. package/src/modules/messages/hooks/use-skeleton-ref/index.ts +2 -0
  77. package/src/modules/messages/hooks/use-skeleton-ref/use-skeleton-ref.tsx +34 -0
  78. package/src/modules/messages/hooks/use-subscribe-message-received-event/index.ts +2 -0
  79. package/src/modules/messages/hooks/use-subscribe-message-received-event/use-subscribe-message-received-event.tsx +80 -0
  80. package/src/modules/messages/service.ts +8 -7
  81. package/src/modules/messages/utils/has-to-update-cursor/has-to-update-cursor.spec.tsx +58 -0
  82. package/src/modules/messages/utils/has-to-update-cursor/has-to-update-cursor.ts +30 -0
  83. package/src/modules/messages/utils/has-to-update-cursor/index.ts +2 -0
  84. package/src/modules/sparkie/__tests__/sparkie.mock.ts +33 -0
  85. package/src/modules/sparkie/service.ts +182 -35
  86. package/src/modules/sparkie/types.ts +10 -2
  87. package/src/modules/widget/__tests__/widget-settings-props.builder.ts +29 -1
  88. package/src/modules/widget/components/ai-avatar/ai-avatar.tsx +11 -53
  89. package/src/modules/widget/components/chat-page/chat-page.spec.tsx +28 -0
  90. package/src/modules/widget/components/chat-page/chat-page.tsx +23 -4
  91. package/src/modules/widget/components/container/container.tsx +5 -19
  92. package/src/modules/widget/components/index.ts +1 -0
  93. package/src/modules/widget/components/scroll-to-bottom-button/index.ts +2 -0
  94. package/src/modules/widget/components/scroll-to-bottom-button/scroll-to-bottom-button.tsx +32 -0
  95. package/src/modules/widget/events.ts +4 -0
  96. package/src/modules/widget/hooks/use-init-sparkie/use-init-sparkie.tsx +8 -6
  97. package/src/modules/widget/store/index.ts +3 -0
  98. package/src/modules/widget/store/widget-container-intrinsic-height.atom.ts +13 -0
  99. package/src/modules/widget/store/widget-loading.atom.ts +11 -0
  100. package/src/modules/widget/store/widget-scrolling.atom.ts +11 -0
  101. package/src/modules/widget/store/widget-tabs.atom.ts +2 -1
  102. package/src/types.ts +4 -1
@@ -1,17 +1,36 @@
1
1
  import { useRef } from 'react'
2
2
 
3
+ import { isTextEmpty } from '@/src/lib/utils/is-text-empty'
3
4
  import { ChatInput, MessagesList } from '@/src/modules/messages/components'
5
+ import { useSendTextMessage } from '@/src/modules/messages/hooks'
6
+ import { useWidgetTabsValueAtom } from '../../store'
4
7
 
5
8
  function ChatPage() {
9
+ const widgetTabs = useWidgetTabsValueAtom()
6
10
  const chatInputRef = useRef<HTMLInputElement>(null)
11
+ const sendTextMessageMutation = useSendTextMessage()
12
+
13
+ const handleSendMessage = () => {
14
+ const text = chatInputRef.current?.value ?? ''
15
+
16
+ if (!isTextEmpty(text)) return
17
+
18
+ sendTextMessageMutation.mutate(text, {
19
+ onSuccess() {
20
+ if (chatInputRef.current?.value) chatInputRef.current.value = ''
21
+ }
22
+ })
23
+ }
7
24
 
8
25
  return (
9
26
  <>
10
- <div className='overflow-auto px-5 py-4'>
11
- <MessagesList />
12
- </div>
27
+ <MessagesList />
13
28
  <div className='border-t border-t-neutral-700 px-5 py-4'>
14
- <ChatInput name='new-chat-msg-input' ref={chatInputRef} />
29
+ <ChatInput
30
+ name='new-chat-msg-input'
31
+ ref={chatInputRef}
32
+ onSend={widgetTabs.currentTab === 'chat' ? handleSendMessage : undefined}
33
+ />
15
34
  </div>
16
35
  </>
17
36
  )
@@ -1,27 +1,13 @@
1
- import { useEffect } from 'react'
2
- import { useQueryClient } from '@tanstack/react-query'
3
-
4
- import { getInitSparkieQuery } from '../../hooks'
5
- import { useWidgetSettingsAtom, useWidgetTabsAtom } from '../../store'
1
+ import { useSubscribeMessageReceivedEvent } from '@/src/modules/messages/hooks'
2
+ import { useWidgetTabsValueAtom } from '../../store'
6
3
  import { WIDGET_TABS } from '../constants'
7
4
 
8
5
  function WidgetContainer() {
9
- const [settings] = useWidgetSettingsAtom()
10
- const initSparkieQuery = settings ? getInitSparkieQuery(settings) : null
11
- const queryClient = useQueryClient()
12
- const [widgetTabs] = useWidgetTabsAtom()
13
-
14
- useEffect(() => {
15
- if (!initSparkieQuery) return
16
- void (async () => {
17
- await queryClient.prefetchQuery(initSparkieQuery)
18
- })()
19
- }, [initSparkieQuery, queryClient])
20
-
21
- if (!settings?.tutorName) return null
6
+ const widgetTabs = useWidgetTabsValueAtom()
7
+ useSubscribeMessageReceivedEvent()
22
8
 
23
9
  return (
24
- <div className='flex min-h-svh flex-col items-center justify-center bg-neutral-900'>
10
+ <div className='flex min-h-svh flex-col items-center justify-center'>
25
11
  <div className='grid h-svh w-full grid-rows-[1fr_max-content]'>
26
12
  {WIDGET_TABS[widgetTabs.currentTab]}
27
13
  </div>
@@ -2,3 +2,4 @@ export * from './ai-avatar'
2
2
  export * from './chat-page'
3
3
  export * from './container'
4
4
  export * from './greetings-card'
5
+ export * from './scroll-to-bottom-button'
@@ -0,0 +1,2 @@
1
+ export * from './scroll-to-bottom-button'
2
+ export { default as ScrollToBottomButton } from './scroll-to-bottom-button'
@@ -0,0 +1,32 @@
1
+ import { type ButtonHTMLAttributes, type DetailedHTMLProps, forwardRef } from 'react'
2
+ import clsx from 'clsx'
3
+
4
+ import { Icon } from '@/src/lib/components'
5
+
6
+ export interface IScrollToBottomButtonProps
7
+ extends DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement> {
8
+ show?: boolean
9
+ top?: number
10
+ }
11
+
12
+ const ScrollToBottomButton = forwardRef<HTMLButtonElement, IScrollToBottomButtonProps>(
13
+ ({ show = false, top = 0, onClick, className, ...props }, ref) => (
14
+ <button
15
+ {...props}
16
+ style={isNaN(Number(top)) ? undefined : { top }}
17
+ ref={ref}
18
+ className={clsx(
19
+ 'fixed inset-x-1/2 flex size-7 cursor-pointer flex-col items-center justify-center rounded-full bg-neutral-600 text-sm text-neutral-50 outline-none transition-colors duration-300 ease-in hover:scale-110 hover:bg-neutral-700 focus:outline-none focus:ring-2 focus:ring-neutral-500 focus:ring-offset-2',
20
+ { 'pointer-events-none opacity-0': !show },
21
+ className
22
+ )}
23
+ onClick={onClick}
24
+ aria-label='Scroller Button'>
25
+ <Icon name='arrow-down' className='size-4' aria-label='Scroller Button Icon' />
26
+ </button>
27
+ )
28
+ )
29
+
30
+ ScrollToBottomButton.displayName = 'ScrollToBottomButton'
31
+
32
+ export default ScrollToBottomButton
@@ -19,3 +19,7 @@ const TutorWidgetEventsList: Array<ITutorWidgetEvent> = [
19
19
  ] as const
20
20
 
21
21
  export const TutorWidgetEvents = new Map(TutorWidgetEventsList.map((e) => [e.name, e]))
22
+
23
+ export const ACTION_EVENTS = {
24
+ SCROLL: 'c3po-app-widget-scroll-to-bottom'
25
+ }
@@ -4,15 +4,17 @@ import { SparkieService } from '@/src/modules/sparkie'
4
4
  import type { WidgetSettingProps } from '@/src/types'
5
5
 
6
6
  export const getInitSparkieQuery = (settings: WidgetSettingProps) => ({
7
- queryKey: ['SparkieService:initializeSparkie', settings.hotmartToken],
7
+ queryKey: ['SparkieService:initializeSparkie', settings?.hotmartToken ?? ''],
8
8
  queryFn: () =>
9
9
  SparkieService.initSparkie({
10
- token: settings.hotmartToken,
10
+ token: settings?.hotmartToken,
11
11
  skipPresenceSetup: true
12
- }),
13
- enabled: Boolean(settings?.hotmartToken?.trim())
12
+ })
14
13
  })
15
14
 
16
- export function useInitSparkie(settings: WidgetSettingProps) {
17
- return useQuery(getInitSparkieQuery(settings))
15
+ export function useInitSparkie(settings: WidgetSettingProps | null) {
16
+ return useQuery({
17
+ ...getInitSparkieQuery(settings as WidgetSettingProps),
18
+ enabled: Boolean(settings?.hotmartToken?.trim())
19
+ })
18
20
  }
@@ -1,2 +1,5 @@
1
+ export * from './widget-container-intrinsic-height.atom'
2
+ export * from './widget-loading.atom'
3
+ export * from './widget-scrolling.atom'
1
4
  export * from './widget-settings.atom'
2
5
  export * from './widget-tabs.atom'
@@ -0,0 +1,13 @@
1
+ import { atom, useAtom, useAtomValue } from 'jotai'
2
+
3
+ const intrinsicHeightAtom = atom('100svh')
4
+ const setIntrinsicHeightAtom = atom(
5
+ (get) => get(intrinsicHeightAtom),
6
+ (_, set, value: string) => {
7
+ set(intrinsicHeightAtom, value)
8
+ }
9
+ )
10
+
11
+ export const useIntrinsicHeightAtom = () => useAtom(setIntrinsicHeightAtom)
12
+
13
+ export const useIntrinsicHeightAtomValue = () => useAtomValue(intrinsicHeightAtom)
@@ -0,0 +1,11 @@
1
+ import { atom, useAtom, useAtomValue } from 'jotai'
2
+
3
+ const widgetLoadingAtom = atom<boolean>(false)
4
+
5
+ const setWidgetLoadingAtom = atom(
6
+ (get) => get(widgetLoadingAtom),
7
+ (_, set, isLoading: boolean) => set(widgetLoadingAtom, isLoading)
8
+ )
9
+
10
+ export const useWidgetLoadingAtom = () => useAtom(setWidgetLoadingAtom)
11
+ export const useWidgetLoadingAtomValue = () => useAtomValue(setWidgetLoadingAtom)
@@ -0,0 +1,11 @@
1
+ import { atom, useAtom, useAtomValue } from 'jotai'
2
+
3
+ const widgetScrollingAtom = atom<boolean>(false)
4
+
5
+ const setWidgetScrollingAtom = atom(
6
+ (get) => get(widgetScrollingAtom),
7
+ (_, set, isScrolling: boolean) => set(widgetScrollingAtom, isScrolling)
8
+ )
9
+
10
+ export const useWidgetScrollingAtom = () => useAtom(setWidgetScrollingAtom)
11
+ export const useWidgetScrollingAtomValue = () => useAtomValue(setWidgetScrollingAtom)
@@ -1,4 +1,4 @@
1
- import { atom, useAtom } from 'jotai'
1
+ import { atom, useAtom, useAtomValue } from 'jotai'
2
2
 
3
3
  import type { CurrentTabKey } from '../components'
4
4
 
@@ -50,4 +50,5 @@ export const goBackTabAtom = atom(null, (get, set) => {
50
50
 
51
51
  export const useGetWidgetTabsAtom = () => useAtom(widgetTabsAtom)
52
52
  export const useWidgetTabsAtom = () => useAtom(setWidgetTabsAtom)
53
+ export const useWidgetTabsValueAtom = () => useAtomValue(setWidgetTabsAtom)
53
54
  export const useWidgetGoBackTabAton = () => useAtom(goBackTabAtom)
package/src/types.ts CHANGED
@@ -26,10 +26,13 @@ export type WidgetSettingProps = {
26
26
  namespace?: string
27
27
  productId: number
28
28
  productName: string
29
- sessionId?: string
29
+ sessionId: string
30
30
  membershipId?: string
31
31
  membershipSlug?: string
32
32
  userId?: string
33
33
  tutorName?: string
34
34
  user?: User
35
+ classHashId?: string
36
+ owner_id?: string
37
+ current_media_codes?: string
35
38
  }