@tavus/cvi-ui 0.0.1-beta.2 → 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 (59) hide show
  1. package/README.md +44 -33
  2. package/dev-components/components/README.md +62 -1
  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 +75 -73
  6. package/dev-components/components/controls/index.tsx +2 -2
  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 → hair-check-01}/hair-check.module.css +2 -1
  10. package/dev-components/components/{hair-check → hair-check-01}/index.tsx +1 -1
  11. package/dist/index.js +412 -104
  12. package/dist/types/templates/components.d.ts +32 -2
  13. package/dist/types/templates/jsx/index.d.ts +11 -1
  14. package/dist/types/templates/tsx/index.d.ts +11 -1
  15. package/dist/types/utils/version-utils.d.ts +3 -0
  16. package/package.json +6 -4
  17. package/src/templates/components.ts +18 -0
  18. package/src/templates/index.ts +2 -0
  19. package/src/templates/jsx/components/audio-wave.json +5 -0
  20. package/src/templates/jsx/components/controls.json +10 -0
  21. package/src/templates/jsx/components/conversation-01.json +11 -0
  22. package/src/templates/jsx/components/cvi-provider.json +5 -0
  23. package/src/templates/jsx/components/hair-check-01.json +10 -0
  24. package/src/templates/jsx/hooks/cvi-events-hooks.json +5 -0
  25. package/src/templates/jsx/hooks/use-cvi-call.json +5 -0
  26. package/src/templates/jsx/hooks/use-local-camera.json +5 -0
  27. package/src/templates/jsx/hooks/use-local-microphone.json +5 -0
  28. package/src/templates/jsx/hooks/use-local-screenshare.json +5 -0
  29. package/src/templates/jsx/hooks/use-remote-participant-ids.json +5 -0
  30. package/src/templates/jsx/hooks/use-replica-ids.json +5 -0
  31. package/src/templates/jsx/hooks/use-request-permissions.json +5 -0
  32. package/src/templates/jsx/hooks/use-start-haircheck.json +5 -0
  33. package/src/templates/jsx/index.ts +14 -0
  34. package/src/templates/tsx/components/audio-wave.json +5 -0
  35. package/src/templates/tsx/components/controls.json +10 -0
  36. package/src/templates/tsx/components/conversation-01.json +11 -0
  37. package/src/templates/tsx/components/cvi-provider.json +5 -0
  38. package/src/templates/tsx/components/hair-check-01.json +10 -0
  39. package/src/templates/tsx/hooks/cvi-events-hooks.json +5 -0
  40. package/src/templates/tsx/hooks/use-cvi-call.json +5 -0
  41. package/src/templates/tsx/hooks/use-local-camera.json +5 -0
  42. package/src/templates/tsx/hooks/use-local-microphone.json +5 -0
  43. package/src/templates/tsx/hooks/use-local-screenshare.json +5 -0
  44. package/src/templates/tsx/hooks/use-remote-participant-ids.json +5 -0
  45. package/src/templates/tsx/hooks/use-replica-ids.json +5 -0
  46. package/src/templates/tsx/hooks/use-request-permissions.json +8 -0
  47. package/src/templates/tsx/hooks/use-start-haircheck.json +5 -0
  48. package/src/templates/tsx/index.ts +14 -0
  49. package/src/utils/resolve-components-tree.ts +59 -2
  50. package/src/utils/update-files.ts +38 -10
  51. package/src/utils/version-utils.ts +26 -0
  52. /package/dev-components/hooks/{use-cvi-call.ts → use-cvi-call.tsx} +0 -0
  53. /package/dev-components/hooks/{use-local-camera.ts → use-local-camera.tsx} +0 -0
  54. /package/dev-components/hooks/{use-local-microphone.ts → use-local-microphone.tsx} +0 -0
  55. /package/dev-components/hooks/{use-local-screenshare.ts → use-local-screenshare.tsx} +0 -0
  56. /package/dev-components/hooks/{use-remote-participant-ids.ts → use-remote-participant-ids.tsx} +0 -0
  57. /package/dev-components/hooks/{use-replica-ids.ts → use-replica-ids.tsx} +0 -0
  58. /package/dev-components/hooks/{use-request-permissions.ts → use-request-permissions.tsx} +0 -0
  59. /package/dev-components/hooks/{use-start-haircheck.ts → use-start-haircheck.tsx} +0 -0
@@ -0,0 +1,222 @@
1
+ .container {
2
+ position: relative;
3
+ width: 100%;
4
+ text-align: center;
5
+ display: flex;
6
+ flex-direction: column;
7
+ align-items: center;
8
+ justify-content: center;
9
+ aspect-ratio: 9/16;
10
+ overflow: hidden;
11
+ border-radius: 0.5rem;
12
+ max-height: 90vh;
13
+ background: linear-gradient(-45deg, #ee7752, #e73c7e, #23a6d5, #23d5ab);
14
+ background-size: 400% 400%;
15
+ animation: gradient 15s ease infinite;
16
+ }
17
+
18
+ @media (min-width: 768px) {
19
+ .container {
20
+ aspect-ratio: 16/9;
21
+ }
22
+ }
23
+
24
+ .errorContainer {
25
+ position: relative;
26
+ display: flex;
27
+ align-items: center;
28
+ justify-content: center;
29
+ background: rgba(248, 250, 252, 0.08);
30
+ color: white;
31
+ height: 100%;
32
+ font-size: 1.5rem;
33
+ font-weight: 600;
34
+ text-align: center;
35
+ }
36
+
37
+ .videoContainer {
38
+ position: relative;
39
+ z-index: 5;
40
+ width: 100%;
41
+ height: 100%;
42
+ }
43
+
44
+ .footer {
45
+ position: absolute;
46
+ bottom: 1.5rem;
47
+ left: 0;
48
+ right: 0;
49
+ z-index: 20;
50
+ }
51
+
52
+ .footerControls {
53
+ display: flex;
54
+ justify-content: center;
55
+ align-items: center;
56
+ gap: 16px;
57
+ }
58
+
59
+ .leaveButton {
60
+ background: linear-gradient(135deg, #ff6b6b 0%, #ee5a52 100%);
61
+ color: white;
62
+ border: none;
63
+ font-size: 16px;
64
+ cursor: pointer;
65
+ transition: all 0.3s ease;
66
+ height: 3rem;
67
+ width: 3rem;
68
+ border-radius: 9999px;
69
+ background-color: #ef4444;
70
+ display: flex;
71
+ align-items: center;
72
+ justify-content: center;
73
+ }
74
+
75
+ .leaveButton:hover {
76
+ opacity: 0.8;
77
+ }
78
+
79
+ /* ReplicaVideo styles */
80
+ .mainVideoContainer {
81
+ background: transparent;
82
+ width: 100%;
83
+ height: 100%;
84
+ position: relative;
85
+ }
86
+
87
+ .mainVideoContainerScreenSharing {
88
+ display: flex;
89
+ align-items: center;
90
+ justify-content: center;
91
+ }
92
+
93
+ .mainVideo {
94
+ position: absolute;
95
+ inset: 0;
96
+ object-position: center;
97
+ object-fit: cover !important;
98
+ height: 100%;
99
+ width: 100%;
100
+ transition: all 0.3s ease;
101
+ }
102
+
103
+ .mainVideoScreenSharing {
104
+ object-fit: contain !important;
105
+ }
106
+
107
+ .mainVideoHidden {
108
+ display: none;
109
+ }
110
+
111
+ /* PreviewVideo styles */
112
+ .previewVideoContainer {
113
+ position: relative;
114
+ background: rgba(2, 6, 23, 0.3);
115
+ aspect-ratio: 16/9;
116
+ width: 11rem;
117
+ border-radius: 1rem;
118
+ overflow: hidden;
119
+ max-height: 120px;
120
+ z-index: 10;
121
+ }
122
+
123
+ @media (min-width: 768px) {
124
+ .previewVideoContainer {
125
+ max-height: 100%;
126
+ }
127
+ }
128
+
129
+ @media (min-width: 1024px) {
130
+ .previewVideoContainer {
131
+ width: 17.875rem;
132
+ }
133
+ }
134
+
135
+ .previewVideoContainerVertical {
136
+ height: 40.5rem;
137
+ width: 6rem;
138
+ }
139
+
140
+ .previewVideoContainerHidden {
141
+ background: transparent;
142
+ display: none;
143
+ }
144
+
145
+ .previewVideo {
146
+ width: 100%;
147
+ height: auto;
148
+ max-height: 120px;
149
+ }
150
+
151
+ @media (min-width: 768px) {
152
+ .previewVideo {
153
+ max-height: 100%;
154
+ }
155
+ }
156
+
157
+ .previewVideoVertical {
158
+ height: 40.5rem;
159
+ width: 6rem;
160
+ object-fit: cover;
161
+ }
162
+
163
+ .previewVideoHidden {
164
+ display: none;
165
+ }
166
+
167
+ /* Main video container */
168
+ .mainVideoContainer {
169
+ width: 100%;
170
+ height: 100%;
171
+ }
172
+
173
+ /* Self view container */
174
+ .selfViewContainer {
175
+ position: absolute;
176
+ bottom: 5rem;
177
+ left: 1rem;
178
+ display: flex;
179
+ align-items: center;
180
+ justify-content: center;
181
+ flex-direction: column;
182
+ gap: 0.75rem;
183
+ }
184
+
185
+ @media (min-width: 768px) {
186
+ .selfViewContainer {
187
+ bottom: 1rem;
188
+ }
189
+ }
190
+
191
+ /* Waiting message container */
192
+ /* Start of Selection */
193
+ .waitingContainer {
194
+ position: relative;
195
+ display: flex;
196
+ align-items: center;
197
+ justify-content: center;
198
+ background: transparent;
199
+ color: white;
200
+ height: 100%;
201
+ font-size: 1.5rem;
202
+ font-weight: 600;
203
+ }
204
+
205
+ @keyframes gradient {
206
+ 0% {
207
+ background-position: 0% 50%;
208
+ }
209
+ 50% {
210
+ background-position: 100% 50%;
211
+ }
212
+ 100% {
213
+ background-position: 0% 50%;
214
+ }
215
+ }
216
+ /* End of Selection */
217
+
218
+ .audioWaveContainer {
219
+ position: absolute;
220
+ bottom: 0.5rem;
221
+ right: 0.5rem;
222
+ }
@@ -0,0 +1,180 @@
1
+ import React, { useEffect } from "react";
2
+ import {
3
+ DailyAudio,
4
+ DailyVideo,
5
+ useDaily,
6
+ useDevices,
7
+ useLocalSessionId,
8
+ useMeetingState,
9
+ useScreenVideoTrack,
10
+ useVideoTrack
11
+ } from "@daily-co/daily-react";
12
+ import { MicrophoneButton, CameraButton, ScreenShareButton } from '../controls'
13
+ import { useLocalScreenshare } from "../../hooks/use-local-screenshare";
14
+ import { useReplicaIDs } from "../../hooks/use-replica-ids";
15
+ import { AudioWave } from "../audio-wave";
16
+
17
+ import styles from "./conversation.module.css";
18
+
19
+ interface ConversationData {
20
+ conversation_id: string;
21
+ conversation_name: string;
22
+ status: 'active' | 'ended' | 'error';
23
+ conversation_url: string;
24
+ replica_id: string | null;
25
+ persona_id: string | null;
26
+ created_at: string;
27
+ }
28
+
29
+ interface ConversationProps {
30
+ onLeave: () => void;
31
+ conversation: ConversationData;
32
+ }
33
+
34
+ const VideoPreview = React.memo(({ id }: { id: string }) => {
35
+ const videoState = useVideoTrack(id);
36
+ const widthVideo = videoState.track?.getSettings()?.width;
37
+ const heightVideo = videoState.track?.getSettings()?.height;
38
+ const isVertical = widthVideo && heightVideo ? widthVideo < heightVideo : false;
39
+
40
+ return (
41
+ <div
42
+ className={`${styles.previewVideoContainer} ${isVertical ? styles.previewVideoContainerVertical : ''} ${videoState.isOff ? styles.previewVideoContainerHidden : ''}`}
43
+ >
44
+ <DailyVideo
45
+ automirror
46
+ sessionId={id}
47
+ type="video"
48
+ className={`${styles.previewVideo} ${isVertical ? styles.previewVideoVertical : ''} ${videoState.isOff ? styles.previewVideoHidden : ''}`}
49
+ />
50
+ <div className={styles.audioWaveContainer}>
51
+ <AudioWave id={id} />
52
+ </div>
53
+ </div>
54
+ );
55
+ });
56
+
57
+ const PreviewVideos = React.memo(() => {
58
+ const localId = useLocalSessionId();
59
+ const { isScreenSharing } = useLocalScreenshare();
60
+ const replicaIds = useReplicaIDs();
61
+ const replicaId = replicaIds[0];
62
+
63
+ return (
64
+ <>
65
+ {isScreenSharing && (
66
+ <VideoPreview id={replicaId} />
67
+ )}
68
+ <VideoPreview id={localId} />
69
+ </>
70
+ );
71
+ });
72
+
73
+ const MainVideo = React.memo(() => {
74
+ const replicaIds = useReplicaIDs();
75
+ const localId = useLocalSessionId();
76
+ const videoState = useVideoTrack(replicaIds[0]);
77
+ const screenVideoState = useScreenVideoTrack(localId);
78
+ const isScreenSharing = !screenVideoState.isOff;
79
+ // This is one to one call, so we can use the first replica id
80
+ const replicaId = replicaIds[0];
81
+
82
+ if (!replicaId) {
83
+ return (
84
+ <div className={styles.waitingContainer}>
85
+ <p>Connecting...</p>
86
+ </div>
87
+ );
88
+ }
89
+
90
+ // Switching between replica video and screen sharing video
91
+ return (
92
+ <div
93
+ className={`${styles.mainVideoContainer} ${isScreenSharing ? styles.mainVideoContainerScreenSharing : ''}`}
94
+ >
95
+ <DailyVideo
96
+ automirror
97
+ sessionId={isScreenSharing ? localId : replicaId}
98
+ type={isScreenSharing ? "screenVideo" : "video"}
99
+ className={`${styles.mainVideo}
100
+ ${isScreenSharing ? styles.mainVideoScreenSharing : ''}
101
+ ${videoState.isOff ? styles.mainVideoHidden : ''}`}
102
+ />
103
+ </div>
104
+ );
105
+ });
106
+
107
+ export const Conversation = React.memo(({ onLeave, conversation }: ConversationProps) => {
108
+ const daily = useDaily();
109
+ const meetingState = useMeetingState();
110
+ const { hasMicError } = useDevices()
111
+
112
+ useEffect(() => {
113
+ if (meetingState === 'error') {
114
+ onLeave();
115
+ }
116
+ }, [meetingState, onLeave]);
117
+
118
+ // Initialize call when conversation is available
119
+ useEffect(() => {
120
+ if (conversation?.conversation_url) {
121
+ daily?.join({
122
+ url: conversation.conversation_url,
123
+ });
124
+ }
125
+ }, [conversation, daily]);
126
+
127
+ return (
128
+ <div className={styles.container}>
129
+ <div className={styles.videoContainer}>
130
+ {
131
+ hasMicError && (
132
+ <div className={styles.errorContainer}>
133
+ <p>
134
+ Camera or microphone access denied. Please check your settings and try again.
135
+ </p>
136
+ </div>
137
+ )}
138
+
139
+ {/* Main video */}
140
+ <div className={styles.mainVideoContainer}>
141
+ <MainVideo />
142
+ </div>
143
+
144
+ {/* Self view */}
145
+ <div className={styles.selfViewContainer}>
146
+ <PreviewVideos />
147
+ </div>
148
+ </div>
149
+
150
+ <div className={styles.footer}>
151
+ <div className={styles.footerControls}>
152
+ <MicrophoneButton />
153
+ <CameraButton />
154
+ <ScreenShareButton />
155
+ <button type="button" className={styles.leaveButton} onClick={onLeave}>
156
+ <svg
157
+ xmlns="http://www.w3.org/2000/svg"
158
+ width="24"
159
+ height="24"
160
+ viewBox="0 0 24 24"
161
+ fill="none"
162
+ role="img"
163
+ aria-label="Leave Call"
164
+ >
165
+ <path
166
+ d="M18 6L6 18M6 6L18 18"
167
+ stroke="currentColor"
168
+ strokeWidth="2"
169
+ strokeLinecap="round"
170
+ strokeLinejoin="round"
171
+ />
172
+ </svg>
173
+ </button>
174
+ </div>
175
+ </div>
176
+
177
+ <DailyAudio />
178
+ </div>
179
+ );
180
+ });
@@ -46,6 +46,7 @@
46
46
  height: 3rem;
47
47
  transition: background-color 0.2s;
48
48
  font-weight: 500;
49
+ min-width: 8.5rem;
49
50
  }
50
51
 
51
52
  .buttonJoin:hover .buttonJoinInner {
@@ -207,5 +208,5 @@
207
208
  display: flex;
208
209
  align-items: flex-end;
209
210
  justify-content: space-between;
210
- gap: 0.5rem;
211
+ gap: 1rem;
211
212
  }
@@ -25,7 +25,7 @@ const JoinBtn = ({
25
25
  className={`${styles.buttonJoin} ${className || ''}`}
26
26
  type="button"
27
27
  onClick={onClick}
28
- disabled={disabled}
28
+ disabled={disabled || loading}
29
29
  >
30
30
  <div className={styles.buttonJoinInner}>
31
31
  {loading ? "Joining..." : "Join Video Chat"}