app-tutor-ai-consumer 1.5.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 (42) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/config/vitest/__mocks__/sparkie.tsx +9 -0
  3. package/eslint.config.mjs +27 -0
  4. package/package.json +6 -2
  5. package/src/@types/index.d.ts +5 -2
  6. package/src/config/tanstack/query-client.ts +1 -1
  7. package/src/development-bootstrap.tsx +15 -15
  8. package/src/index.tsx +15 -5
  9. package/src/lib/components/icons/ai-color.svg +17 -0
  10. package/src/lib/components/icons/icon-names.d.ts +1 -1
  11. package/src/lib/utils/is-text-empty.ts +3 -0
  12. package/src/main/main.spec.tsx +0 -8
  13. package/src/modules/messages/components/chat-input/chat-input.spec.tsx +72 -0
  14. package/src/modules/messages/components/chat-input/chat-input.tsx +52 -6
  15. package/src/modules/messages/components/index.ts +1 -0
  16. package/src/modules/messages/components/message-skeleton/index.ts +1 -0
  17. package/src/modules/messages/components/message-skeleton/message-skeleton.tsx +23 -0
  18. package/src/modules/messages/components/messages-list/messages-list.tsx +14 -1
  19. package/src/modules/messages/hooks/index.ts +2 -0
  20. package/src/modules/messages/hooks/use-infinite-get-messages/use-infinite-get-messages.spec.tsx +7 -0
  21. package/src/modules/messages/hooks/use-infinite-get-messages/use-infinite-get-messages.tsx +7 -23
  22. package/src/modules/messages/hooks/use-manage-scroll/use-manage-scroll.tsx +5 -1
  23. package/src/modules/messages/hooks/use-send-text-message/index.ts +2 -0
  24. package/src/modules/messages/hooks/use-send-text-message/use-send-text-message.spec.tsx +86 -0
  25. package/src/modules/messages/hooks/use-send-text-message/use-send-text-message.tsx +60 -0
  26. package/src/modules/messages/hooks/use-skeleton-ref/index.ts +2 -0
  27. package/src/modules/messages/hooks/use-skeleton-ref/use-skeleton-ref.tsx +34 -0
  28. package/src/modules/messages/hooks/use-subscribe-message-received-event/index.ts +2 -0
  29. package/src/modules/messages/hooks/use-subscribe-message-received-event/use-subscribe-message-received-event.tsx +80 -0
  30. package/src/modules/messages/service.ts +8 -7
  31. package/src/modules/sparkie/service.ts +182 -35
  32. package/src/modules/sparkie/types.ts +10 -2
  33. package/src/modules/widget/__tests__/widget-settings-props.builder.ts +23 -1
  34. package/src/modules/widget/components/ai-avatar/ai-avatar.tsx +11 -53
  35. package/src/modules/widget/components/chat-page/chat-page.tsx +22 -1
  36. package/src/modules/widget/components/container/container.tsx +4 -24
  37. package/src/modules/widget/components/scroll-to-bottom-button/scroll-to-bottom-button.tsx +1 -1
  38. package/src/modules/widget/store/index.ts +2 -0
  39. package/src/modules/widget/store/widget-loading.atom.ts +11 -0
  40. package/src/modules/widget/store/widget-scrolling.atom.ts +11 -0
  41. package/src/modules/widget/store/widget-tabs.atom.ts +2 -1
  42. package/src/types.ts +4 -1
@@ -1,57 +1,15 @@
1
- function AIAvatarIcon() {
1
+ import clsx from 'clsx'
2
+
3
+ import { Icon } from '@/src/lib/components'
4
+
5
+ function AIAvatarIcon({
6
+ className = 'rounded-full border-4 border-neutral-900 bg-neutral-800'
7
+ }: {
8
+ className?: string
9
+ }) {
2
10
  return (
3
- <div className='flex h-11 w-11 items-center justify-center rounded-full border-4 border-neutral-900 bg-neutral-800'>
4
- <svg
5
- width='25'
6
- height='24'
7
- viewBox='0 0 25 24'
8
- fill='none'
9
- xmlns='http://www.w3.org/2000/svg'>
10
- <path
11
- d='M8.21062 3.77518L11.05 9.08404L16.3739 11.9154L11.05 14.7469L8.21062 20.0557L5.37122 14.7469L0.0473633 11.9154L5.37122 9.08404L8.21062 3.77518Z'
12
- fill='url(#paint0_linear_18316_181)'
13
- />
14
- <path
15
- d='M17.8806 0.590271L19.2848 3.21586L21.9178 4.61617L19.2848 6.01648L17.8806 8.64206L16.4762 6.01648L13.8433 4.61617L16.4762 3.21586L17.8806 0.590271Z'
16
- fill='url(#paint1_linear_18316_181)'
17
- />
18
- <path
19
- d='M20.7199 16.7845L18.9453 13.4665L17.1707 16.7845L13.8433 18.5541L17.1707 20.3237L18.9453 23.6418L20.7199 20.3237L24.0473 18.5541L20.7199 16.7845Z'
20
- fill='url(#paint2_linear_18316_181)'
21
- />
22
- <defs>
23
- <linearGradient
24
- id='paint0_linear_18316_181'
25
- x1='0.0473633'
26
- y1='11.9154'
27
- x2='16.3739'
28
- y2='11.9154'
29
- gradientUnits='userSpaceOnUse'>
30
- <stop stopColor='#44D0FF' />
31
- <stop offset='1' stopColor='#B48EFF' />
32
- </linearGradient>
33
- <linearGradient
34
- id='paint1_linear_18316_181'
35
- x1='13.8433'
36
- y1='4.61617'
37
- x2='21.9178'
38
- y2='4.61617'
39
- gradientUnits='userSpaceOnUse'>
40
- <stop stopColor='#44D0FF' />
41
- <stop offset='1' stopColor='#B48EFF' />
42
- </linearGradient>
43
- <linearGradient
44
- id='paint2_linear_18316_181'
45
- x1='13.8433'
46
- y1='18.5541'
47
- x2='24.0473'
48
- y2='18.5541'
49
- gradientUnits='userSpaceOnUse'>
50
- <stop stopColor='#44D0FF' />
51
- <stop offset='1' stopColor='#B48EFF' />
52
- </linearGradient>
53
- </defs>
54
- </svg>
11
+ <div className={clsx('flex h-11 w-11 items-center justify-center', className)}>
12
+ <Icon name='ai-color' className='h-6 w-6' aria-label='AI avatar Icon' />
55
13
  </div>
56
14
  )
57
15
  }
@@ -1,15 +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
27
  <MessagesList />
11
28
  <div className='border-t border-t-neutral-700 px-5 py-4'>
12
- <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
+ />
13
34
  </div>
14
35
  </>
15
36
  )
@@ -1,30 +1,10 @@
1
- import { Spinner } from '@/src/lib/components'
2
- import { useInitSparkie } from '../../hooks'
3
- import { useWidgetSettingsAtom, useWidgetTabsAtom } from '../../store'
1
+ import { useSubscribeMessageReceivedEvent } from '@/src/modules/messages/hooks'
2
+ import { useWidgetTabsValueAtom } from '../../store'
4
3
  import { WIDGET_TABS } from '../constants'
5
4
 
6
5
  function WidgetContainer() {
7
- const [settings] = useWidgetSettingsAtom()
8
- const sparkieQuery = useInitSparkie(settings)
9
- const [widgetTabs] = useWidgetTabsAtom()
10
-
11
- if (!settings?.tutorName) return null
12
-
13
- // TODO: change it for the general API error design from FIGMA as soon as it is available
14
- if (sparkieQuery.isError)
15
- return (
16
- <div className='text-neutral-50'>
17
- <span>Error initializing sparkie</span>
18
- <button onClick={() => void sparkieQuery.refetch()}>Try again</button>
19
- </div>
20
- )
21
-
22
- if (sparkieQuery.isLoading)
23
- return (
24
- <div className='flex flex-col items-center justify-center p-8'>
25
- <Spinner className='inline-flex h-6 w-6 animate-spin text-neutral-200' />
26
- </div>
27
- )
6
+ const widgetTabs = useWidgetTabsValueAtom()
7
+ useSubscribeMessageReceivedEvent()
28
8
 
29
9
  return (
30
10
  <div className='flex min-h-svh flex-col items-center justify-center'>
@@ -13,7 +13,7 @@ const ScrollToBottomButton = forwardRef<HTMLButtonElement, IScrollToBottomButton
13
13
  ({ show = false, top = 0, onClick, className, ...props }, ref) => (
14
14
  <button
15
15
  {...props}
16
- style={{ top }}
16
+ style={isNaN(Number(top)) ? undefined : { top }}
17
17
  ref={ref}
18
18
  className={clsx(
19
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',
@@ -1,3 +1,5 @@
1
1
  export * from './widget-container-intrinsic-height.atom'
2
+ export * from './widget-loading.atom'
3
+ export * from './widget-scrolling.atom'
2
4
  export * from './widget-settings.atom'
3
5
  export * from './widget-tabs.atom'
@@ -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
  }