@tavus/cvi-ui 0.0.1-beta.1 → 0.0.1-beta.3

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 (68) hide show
  1. package/README.md +54 -31
  2. package/dev-components/components/README.md +162 -0
  3. package/dev-components/components/audio-wave/audio-wave.module.css +33 -0
  4. package/dev-components/components/audio-wave/index.tsx +55 -0
  5. package/dev-components/components/controls/controls.module.css +115 -0
  6. package/dev-components/components/controls/index.tsx +260 -0
  7. package/dev-components/components/conversation-01/conversation.module.css +222 -0
  8. package/dev-components/components/conversation-01/index.tsx +180 -0
  9. package/dev-components/components/hair-check-01/hair-check.module.css +212 -0
  10. package/dev-components/components/hair-check-01/index.tsx +184 -0
  11. package/dev-components/hooks/README.md +3 -2
  12. package/dev-components/hooks/cvi-events-hooks.tsx +1 -1
  13. package/dev-components/hooks/use-local-camera.tsx +8 -2
  14. package/dev-components/hooks/use-local-microphone.tsx +8 -2
  15. package/dev-components/hooks/use-local-screenshare.tsx +7 -2
  16. package/dev-components/hooks/use-remote-participant-ids.tsx +1 -1
  17. package/dev-components/hooks/use-replica-ids.tsx +1 -1
  18. package/dev-components/hooks/use-start-haircheck.tsx +7 -1
  19. package/dist/index.js +305 -101
  20. package/dist/templates/components/controls.tsx +279 -0
  21. package/dist/templates/components/cvi-provider.tsx +9 -0
  22. package/dist/templates/controls.tsx +279 -0
  23. package/dist/templates/cvi-hooks.tsx +38 -0
  24. package/dist/templates/cvi-provider.tsx +9 -0
  25. package/dist/templates/hooks/cvi-hooks.tsx +38 -0
  26. package/dist/templates/tsx/components/controls.tsx +279 -0
  27. package/dist/templates/tsx/components/cvi-provider.tsx +9 -0
  28. package/dist/types/templates/components.d.ts +44 -0
  29. package/dist/types/templates/index.d.ts +2 -0
  30. package/dist/types/templates/jsx/index.d.ts +14 -0
  31. package/dist/types/templates/tsx/index.d.ts +14 -0
  32. package/dist/types/utils/version-utils.d.ts +3 -0
  33. package/package.json +6 -4
  34. package/src/templates/components.ts +18 -0
  35. package/src/templates/index.ts +2 -0
  36. package/src/templates/jsx/components/audio-wave.json +5 -0
  37. package/src/templates/jsx/components/controls.json +10 -0
  38. package/src/templates/jsx/components/conversation-01.json +11 -0
  39. package/src/templates/jsx/components/cvi-provider.json +5 -0
  40. package/src/templates/jsx/components/hair-check-01.json +10 -0
  41. package/src/templates/jsx/hooks/cvi-events-hooks.json +5 -0
  42. package/src/templates/jsx/hooks/use-cvi-call.json +5 -0
  43. package/src/templates/jsx/hooks/use-local-camera.json +5 -0
  44. package/src/templates/jsx/hooks/use-local-microphone.json +5 -0
  45. package/src/templates/jsx/hooks/use-local-screenshare.json +5 -0
  46. package/src/templates/jsx/hooks/use-remote-participant-ids.json +5 -0
  47. package/src/templates/jsx/hooks/use-replica-ids.json +5 -0
  48. package/src/templates/jsx/hooks/use-request-permissions.json +5 -0
  49. package/src/templates/jsx/hooks/use-start-haircheck.json +5 -0
  50. package/src/templates/jsx/index.ts +14 -0
  51. package/src/templates/tsx/components/audio-wave.json +5 -0
  52. package/src/templates/tsx/components/controls.json +10 -0
  53. package/src/templates/tsx/components/conversation-01.json +11 -0
  54. package/src/templates/tsx/components/cvi-provider.json +5 -0
  55. package/src/templates/tsx/components/hair-check-01.json +10 -0
  56. package/src/templates/tsx/hooks/cvi-events-hooks.json +5 -0
  57. package/src/templates/tsx/hooks/use-cvi-call.json +5 -0
  58. package/src/templates/tsx/hooks/use-local-camera.json +5 -0
  59. package/src/templates/tsx/hooks/use-local-microphone.json +5 -0
  60. package/src/templates/tsx/hooks/use-local-screenshare.json +5 -0
  61. package/src/templates/tsx/hooks/use-remote-participant-ids.json +5 -0
  62. package/src/templates/tsx/hooks/use-replica-ids.json +5 -0
  63. package/src/templates/tsx/hooks/use-request-permissions.json +8 -0
  64. package/src/templates/tsx/hooks/use-start-haircheck.json +5 -0
  65. package/src/templates/tsx/index.ts +14 -0
  66. package/src/utils/resolve-components-tree.ts +59 -2
  67. package/src/utils/update-files.ts +38 -10
  68. package/src/utils/version-utils.ts +26 -0
@@ -0,0 +1,212 @@
1
+ /* Button Component */
2
+ /* Start of Selection */
3
+ .buttonCancel {
4
+ padding: 1rem;
5
+ border: 1px solid rgba(255, 255, 255, 0.1);
6
+ background-color: rgba(239, 68, 68, 0.8);
7
+ border-radius: 9999px;
8
+ color: white;
9
+ display: flex;
10
+ align-items: center;
11
+ justify-content: center;
12
+ font-weight: 500;
13
+ cursor: pointer;
14
+ transition: all 0.2s;
15
+ outline: none;
16
+ }
17
+
18
+ .buttonCancel:hover {
19
+ background-color: rgba(239, 68, 68, 1);
20
+ }
21
+ /* End of Selection */
22
+
23
+ /* ButtonJoin Component */
24
+ .buttonJoin {
25
+ padding: 5px;
26
+ border: 1px solid rgba(255, 255, 255, 0.1);
27
+ background-color: rgba(255, 255, 255, 0.1);
28
+ border-radius: 9999px;
29
+ color: white;
30
+ height: 3.625rem;
31
+ display: flex;
32
+ align-items: center;
33
+ justify-content: center;
34
+ font-weight: 500;
35
+ cursor: pointer;
36
+ }
37
+
38
+ .buttonJoinInner {
39
+ display: flex;
40
+ align-items: center;
41
+ justify-content: center;
42
+ background-color: #32c75c;
43
+ border-radius: 9999px;
44
+ padding: 1rem;
45
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
46
+ height: 3rem;
47
+ transition: background-color 0.2s;
48
+ font-weight: 500;
49
+ min-width: 8.5rem;
50
+ }
51
+
52
+ .buttonJoin:hover .buttonJoinInner {
53
+ background-color: rgba(62, 192, 97, 0.9);
54
+ }
55
+
56
+ .buttonJoin:disabled .buttonJoinInner {
57
+ background-color: rgba(34, 197, 94, 0.5);
58
+ cursor: not-allowed;
59
+ }
60
+
61
+ .buttonJoinDesktop {
62
+ display: none;
63
+ }
64
+
65
+ @media (min-width: 768px) {
66
+ .buttonJoinDesktop {
67
+ display: flex;
68
+ }
69
+ }
70
+
71
+ .buttonJoinMobile {
72
+ position: absolute;
73
+ top: -5.125rem;
74
+ left: 50%;
75
+ transform: translateX(-50%);
76
+ z-index: 20;
77
+ }
78
+
79
+ @media (min-width: 768px) {
80
+ .buttonJoinMobile {
81
+ display: none;
82
+ }
83
+ }
84
+
85
+ /* HaircheckScreen Component */
86
+ .haircheckBlock {
87
+ position: relative;
88
+ width: 100%;
89
+ text-align: center;
90
+ display: flex;
91
+ flex-direction: column;
92
+ align-items: center;
93
+ justify-content: center;
94
+ aspect-ratio: 9/16;
95
+ overflow: hidden;
96
+ border-radius: 0.5rem;
97
+ max-height: 90vh;
98
+ }
99
+
100
+ @media (min-width: 768px) {
101
+ .haircheckBlock {
102
+ aspect-ratio: 16/9;
103
+ }
104
+ }
105
+
106
+ .dailyVideo {
107
+ width: 100%;
108
+ height: 100%;
109
+ object-fit: cover !important;
110
+ position: absolute;
111
+ inset: 0;
112
+ transition: opacity 0.3s ease-in-out;
113
+ }
114
+
115
+ .haircheckUserPlaceholder {
116
+ position: absolute;
117
+ inset: 0;
118
+ background-color: #334155;
119
+ z-index: 5;
120
+ pointer-events: none;
121
+ display: flex;
122
+ align-items: center;
123
+ justify-content: center;
124
+ }
125
+
126
+ .haircheckUserIcon {
127
+ width: 12.5rem;
128
+ height: 12.5rem;
129
+ border-radius: 9999px;
130
+ background-color: rgba(255, 255, 255, 0.1);
131
+ display: flex;
132
+ align-items: center;
133
+ justify-content: center;
134
+ }
135
+
136
+ .haircheckSidebar {
137
+ position: absolute;
138
+ right: 0;
139
+ bottom: 0;
140
+ left: 0;
141
+ width: 100%;
142
+ background-color: rgba(2, 6, 23, 0.45);
143
+ backdrop-filter: blur(20px);
144
+ border-left: 1px solid rgba(255, 255, 255, 0.2);
145
+ z-index: 5;
146
+ }
147
+
148
+ @media (min-width: 768px) {
149
+ .haircheckSidebar {
150
+ left: auto;
151
+ top: 0;
152
+ bottom: 0;
153
+ width: 17.5rem;
154
+ }
155
+ }
156
+
157
+ @media (min-width: 1024px) {
158
+ .haircheckSidebar {
159
+ width: 23rem;
160
+ }
161
+ }
162
+
163
+ .haircheckSidebarContent {
164
+ position: relative;
165
+ display: flex;
166
+ flex-direction: column;
167
+ align-items: center;
168
+ justify-content: space-between;
169
+ padding: 1.5rem 1.25rem;
170
+ gap: 1.5rem;
171
+ width: 100%;
172
+ height: 100%;
173
+ }
174
+
175
+ @media (min-width: 768px) {
176
+ .haircheckSidebarContent {
177
+ padding: 1rem 1.25rem;
178
+ }
179
+ }
180
+
181
+ @media (min-width: 1024px) {
182
+ .haircheckSidebarContent {
183
+ padding: 2rem 1.25rem;
184
+ }
185
+ }
186
+
187
+ .haircheckContent {
188
+ display: flex;
189
+ flex-direction: column;
190
+ align-items: center;
191
+ justify-content: center;
192
+ gap: 2rem;
193
+ }
194
+
195
+ .haircheckDescription {
196
+ color: #ffffff;
197
+ font-size: 1.125rem;
198
+ font-weight: 500;
199
+ }
200
+
201
+ @media (min-width: 768px) {
202
+ .haircheckDescription {
203
+ font-size: 1.25rem;
204
+ }
205
+ }
206
+
207
+ .haircheckControls {
208
+ display: flex;
209
+ align-items: flex-end;
210
+ justify-content: space-between;
211
+ gap: 1rem;
212
+ }
@@ -0,0 +1,184 @@
1
+ import React, { memo, useEffect } from "react";
2
+ import {
3
+ DailyVideo,
4
+ useDaily,
5
+ } from "@daily-co/daily-react";
6
+ import { CameraButton, MicrophoneButton } from "../controls";
7
+ import { useStartHaircheck } from "../../hooks/use-start-haircheck";
8
+ import { useLocalCamera } from "../../hooks/use-local-camera";
9
+
10
+ import styles from "./hair-check.module.css";
11
+
12
+ const JoinBtn = ({
13
+ onClick,
14
+ disabled,
15
+ className,
16
+ loading,
17
+ }: {
18
+ loading?: boolean;
19
+ onClick: () => void;
20
+ disabled?: boolean;
21
+ className?: string;
22
+ }) => {
23
+ return (
24
+ <button
25
+ className={`${styles.buttonJoin} ${className || ''}`}
26
+ type="button"
27
+ onClick={onClick}
28
+ disabled={disabled || loading}
29
+ >
30
+ <div className={styles.buttonJoinInner}>
31
+ {loading ? "Joining..." : "Join Video Chat"}
32
+ </div>
33
+ </button>
34
+ );
35
+ };
36
+
37
+ export const HairCheck = memo(({
38
+ isJoinBtnLoading = false,
39
+ onJoin,
40
+ onCancel,
41
+ }: {
42
+ isJoinBtnLoading?: boolean;
43
+ onJoin: () => void;
44
+ onCancel?: () => void;
45
+ }) => {
46
+ const daily = useDaily();
47
+ const { localSessionId, isCamMuted } = useLocalCamera();
48
+
49
+ const { isPermissionsPrompt,
50
+ isPermissionsLoading,
51
+ isPermissionsGranted,
52
+ isPermissionsDenied,
53
+ requestPermissions
54
+ } = useStartHaircheck();
55
+
56
+ useEffect(() => {
57
+ requestPermissions();
58
+ }, []);
59
+
60
+ const onCancelHairCheck = () => {
61
+ if (daily) {
62
+ daily.leave();
63
+ }
64
+ onCancel?.();
65
+ };
66
+
67
+ const getDescription = () => {
68
+ if (isPermissionsPrompt) {
69
+ return "Make sure your camera and mic are ready!";
70
+ }
71
+ if (isPermissionsLoading) {
72
+ return "Getting your camera and mic ready...";
73
+ }
74
+ if (isPermissionsDenied) {
75
+ return "Camera and mic access denied. Allow permissions to continue.";
76
+ }
77
+ return "You're all set! Your device is ready.";
78
+ };
79
+ return (
80
+ <div className={styles.haircheckBlock}>
81
+ {isPermissionsGranted && !isCamMuted ? (
82
+ <DailyVideo
83
+ type="video"
84
+ sessionId={localSessionId}
85
+ mirror
86
+ className={styles.dailyVideo}
87
+ />
88
+ ) : (
89
+ <div className={styles.haircheckUserPlaceholder}>
90
+ <span className={styles.haircheckUserIcon}>
91
+ <svg
92
+ xmlns="http://www.w3.org/2000/svg"
93
+ width="88"
94
+ height="89"
95
+ viewBox="0 0 88 89"
96
+ fill="none"
97
+ aria-label="Haircheck User"
98
+ role="img"
99
+ >
100
+ <path
101
+ d="M44 48.6406C17.952 48.6406 8.80005 61.8406 8.80005 70.6406V83.8406H79.2001V70.6406C79.2001 61.8406 70.0481 48.6406 44 48.6406Z"
102
+ fill="url(#paint0_linear_7135_21737)"
103
+ />
104
+ <path
105
+ d="M44 44.2406C54.9352 44.2406 63.7999 35.3759 63.7999 24.4406C63.7999 13.5054 54.9352 4.64062 44 4.64062C33.0647 4.64062 24.2 13.5054 24.2 24.4406C24.2 35.3759 33.0647 44.2406 44 44.2406Z"
106
+ fill="url(#paint1_linear_7135_21737)"
107
+ />
108
+ <defs>
109
+ <linearGradient
110
+ id="paint0_linear_7135_21737"
111
+ x1="36.5001"
112
+ y1="43"
113
+ x2="44.0001"
114
+ y2="97.5"
115
+ gradientUnits="userSpaceOnUse"
116
+ >
117
+ <stop stopColor="white" />
118
+ <stop offset="1" stopColor="white" stopOpacity="0" />
119
+ </linearGradient>
120
+ <linearGradient
121
+ id="paint1_linear_7135_21737"
122
+ x1="44"
123
+ y1="4.64062"
124
+ x2="44"
125
+ y2="44.2406"
126
+ gradientUnits="userSpaceOnUse"
127
+ >
128
+ <stop stopColor="white" />
129
+ <stop offset="1" stopColor="white" stopOpacity="0" />
130
+ </linearGradient>
131
+ </defs>
132
+ </svg>
133
+ </span>
134
+ </div>
135
+ )}
136
+
137
+ <div className={styles.haircheckSidebar}>
138
+ <div className={styles.haircheckSidebarContent}>
139
+ {isPermissionsDenied ?
140
+ <button
141
+ type="button"
142
+ onClick={onCancelHairCheck}
143
+ className={`${styles.buttonCancel} ${styles.buttonJoinMobile}`}
144
+ >
145
+ Cancel
146
+ </button> :
147
+ <JoinBtn
148
+ loading={isJoinBtnLoading}
149
+ disabled={!isPermissionsGranted}
150
+ className={styles.buttonJoinMobile}
151
+ onClick={onJoin}
152
+ />
153
+ }
154
+ <div />
155
+ <div className={styles.haircheckContent}>
156
+ <div className={styles.haircheckDescription}>
157
+ {getDescription()}
158
+ </div>
159
+ {isPermissionsDenied ?
160
+ <button
161
+ type="button"
162
+ onClick={onCancelHairCheck}
163
+ className={`${styles.buttonCancel} ${styles.buttonJoinDesktop}`}
164
+ >
165
+ Cancel
166
+ </button> :
167
+ <JoinBtn
168
+ loading={isJoinBtnLoading}
169
+ disabled={!isPermissionsGranted}
170
+ className={styles.buttonJoinDesktop}
171
+ onClick={onJoin}
172
+ />}
173
+ </div>
174
+ <div className={styles.haircheckControls}>
175
+ <MicrophoneButton />
176
+ <CameraButton />
177
+ </div>
178
+ </div>
179
+ </div>
180
+ </div >
181
+ );
182
+ });
183
+
184
+ HairCheck.displayName = "HairCheck";
@@ -1,7 +1,5 @@
1
1
  # CVI Hooks Documentation
2
2
 
3
- This documentation is organized by functionality to help you find the right hook for your needs.
4
-
5
3
  ## Table of Contents
6
4
 
7
5
  ### 🔧 Core Call Management
@@ -137,6 +135,7 @@ A React hook that provides local camera state and toggle functionality.
137
135
  | `onToggleCamera` | `() => void` | Function to toggle camera on/off |
138
136
  | `isCamReady` | `boolean` | Camera permission is granted and ready |
139
137
  | `isCamMuted` | `boolean` | Camera is currently turned off |
138
+ | `localSessionId` | `string` | Local session ID |
140
139
 
141
140
  #### Usage
142
141
 
@@ -173,6 +172,7 @@ A React hook that provides local microphone state and toggle functionality.
173
172
  | `onToggleMicrophone` | `() => void` | Function to toggle microphone on/off |
174
173
  | `isMicReady` | `boolean` | Microphone permission is granted and ready |
175
174
  | `isMicMuted` | `boolean` | Microphone is currently turned off |
175
+ | `localSessionId` | `string` | Local session ID |
176
176
 
177
177
  #### Usage
178
178
 
@@ -209,6 +209,7 @@ A React hook that provides local screen sharing state and toggle functionality.
209
209
  | --------------------- | ------------ | ------------------------------------------ |
210
210
  | `onToggleScreenshare` | `() => void` | Function to toggle screen sharing on/off |
211
211
  | `isScreenSharing` | `boolean` | Whether screen sharing is currently active |
212
+ | `localSessionId` | `string` | Local session ID |
212
213
 
213
214
  #### Display Media Options
214
215
 
@@ -1,5 +1,5 @@
1
- import { useAppMessage, useDailyEvent } from "@daily-co/daily-react";
2
1
  import { useCallback } from "react";
2
+ import { useAppMessage, useDailyEvent } from "@daily-co/daily-react";
3
3
 
4
4
  type AppMessageUtterance = {
5
5
  "message_type": "conversation",
@@ -1,7 +1,12 @@
1
1
  import { useCallback, useMemo } from 'react';
2
2
  import { useDaily, useDevices, useLocalSessionId, useVideoTrack } from '@daily-co/daily-react';
3
3
 
4
- export const useLocalCamera = () => {
4
+ export const useLocalCamera = (): {
5
+ isCamReady: boolean;
6
+ isCamMuted: boolean;
7
+ localSessionId: string;
8
+ onToggleCamera: () => void;
9
+ } => {
5
10
  const daily = useDaily();
6
11
  const localSessionId = useLocalSessionId();
7
12
  const { isOff: isCamMuted } = useVideoTrack(localSessionId);
@@ -13,8 +18,9 @@ export const useLocalCamera = () => {
13
18
  }, [daily, isCamMuted]);
14
19
 
15
20
  return {
16
- onToggleCamera,
17
21
  isCamReady,
18
22
  isCamMuted,
23
+ localSessionId,
24
+ onToggleCamera,
19
25
  };
20
26
  };
@@ -1,7 +1,12 @@
1
1
  import { useCallback, useMemo } from 'react';
2
2
  import { useAudioTrack, useDaily, useDevices, useLocalSessionId } from '@daily-co/daily-react';
3
3
 
4
- export const useLocalMicrophone = () => {
4
+ export const useLocalMicrophone = (): {
5
+ isMicReady: boolean;
6
+ isMicMuted: boolean;
7
+ localSessionId: string;
8
+ onToggleMicrophone: () => void;
9
+ } => {
5
10
  const daily = useDaily();
6
11
  const localSessionId = useLocalSessionId();
7
12
  const { isOff: isMicMuted } = useAudioTrack(localSessionId);
@@ -13,8 +18,9 @@ export const useLocalMicrophone = () => {
13
18
  }, [daily, isMicMuted]);
14
19
 
15
20
  return {
16
- onToggleMicrophone,
17
21
  isMicReady,
18
22
  isMicMuted,
23
+ localSessionId,
24
+ onToggleMicrophone,
19
25
  };
20
26
  };
@@ -1,7 +1,11 @@
1
1
  import { useCallback } from 'react';
2
2
  import { useDaily, useLocalSessionId, useScreenVideoTrack } from '@daily-co/daily-react';
3
3
 
4
- export const useLocalScreenshare = () => {
4
+ export const useLocalScreenshare = (): {
5
+ isScreenSharing: boolean;
6
+ localSessionId: string;
7
+ onToggleScreenshare: () => void;
8
+ } => {
5
9
  const daily = useDaily();
6
10
  const localSessionId = useLocalSessionId();
7
11
  const { isOff } = useScreenVideoTrack(localSessionId);
@@ -26,7 +30,8 @@ export const useLocalScreenshare = () => {
26
30
  }, [daily, isScreenSharing]);
27
31
 
28
32
  return {
29
- onToggleScreenshare,
30
33
  isScreenSharing,
34
+ localSessionId,
35
+ onToggleScreenshare,
31
36
  };
32
37
  };
@@ -1,6 +1,6 @@
1
1
  import { useParticipantIds } from '@daily-co/daily-react';
2
2
 
3
- export const useRemoteParticipantIDs = () => {
3
+ export const useRemoteParticipantIDs = (): string[] => {
4
4
  const remoteParticipantIds = useParticipantIds({ filter: 'remote' });
5
5
 
6
6
  return remoteParticipantIds;
@@ -1,6 +1,6 @@
1
1
  import { useParticipantIds } from '@daily-co/daily-react';
2
2
 
3
- export const useReplicaIDs = () => {
3
+ export const useReplicaIDs = (): string[] => {
4
4
  const replicasIDs = useParticipantIds({
5
5
  filter: (participant) => participant.user_id.includes('tavus-replica'),
6
6
  });
@@ -1,7 +1,13 @@
1
1
  import { useCallback, useEffect, useMemo, useState } from 'react';
2
2
  import { useDaily, useDevices } from '@daily-co/daily-react';
3
3
 
4
- export const useStartHaircheck = () => {
4
+ export const useStartHaircheck = (): {
5
+ isPermissionsPrompt: boolean;
6
+ isPermissionsLoading: boolean;
7
+ isPermissionsGranted: boolean;
8
+ isPermissionsDenied: boolean;
9
+ requestPermissions: () => void;
10
+ } => {
5
11
  const daily = useDaily();
6
12
  const { micState } = useDevices();
7
13