1
+ <template >
2
+ <div class =" min-h-screen bg-gray-50 dark:bg-gray-900" >
3
+ <div class =" flex min-h-screen items-center justify-center p-6" >
4
+ <div class =" w-full max-w-md" >
5
+ <!-- Logo and App Name -->
6
+ <div class =" mb-8 text-center" >
7
+ <div class =" mb-4 flex items-center justify-center" >
8
+ <div class =" rounded-xl bg-gray-900 p-3 dark:bg-gray-100" >
9
+ <svg class =" h-10 w-10 text-white dark:text-gray-900" fill =" none" stroke =" currentColor" viewBox =" 0 0 24 24" >
10
+ <path stroke-linecap =" round" stroke-linejoin =" round" stroke-width =" 2" d =" M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" />
11
+ </svg >
12
+ </div >
13
+ </div >
14
+ <h1 class =" text-3xl font-semibold text-gray-900 dark:text-white" >Welcome to Clueless</h1 >
15
+ <p class =" mt-2 text-gray-600 dark:text-gray-400" >AI-powered meeting assistant</p >
16
+ </div >
17
+
18
+ <!-- Progress Indicator -->
19
+ <div v-if =" showSteps" class =" mb-6 flex items-center justify-center space-x-2" >
20
+ <div v-for =" i in 2" :key =" i"
21
+ :class =" ['h-1.5 w-20 rounded-full transition-all duration-300',
22
+ i <= currentStep ? 'bg-gray-900 dark:bg-gray-100' : 'bg-gray-200 dark:bg-gray-700']" >
23
+ </div >
24
+ </div >
25
+
26
+ <!-- Main Card -->
27
+ <div class =" rounded-xl bg-white shadow-sm ring-1 ring-gray-200 dark:bg-gray-800 dark:ring-gray-700" >
28
+ <div class =" p-6" >
29
+ <!-- Step 1: API Key -->
30
+ <div v-if =" currentStep === 1" >
31
+ <h2 class =" mb-6 text-xl font-medium text-gray-900 dark:text-white" >Setup OpenAI API</h2 >
32
+
33
+ <div class =" space-y-4" >
34
+ <div >
35
+ <label class =" mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300" >
36
+ API Key
37
+ </label >
38
+ <div class =" relative" >
39
+ <input
40
+ v-model =" apiKey"
41
+ :type =" showApiKey ? 'text' : 'password'"
42
+ placeholder =" sk-..."
43
+ class =" w-full rounded-lg border border-gray-300 bg-white px-4 py-2.5 text-gray-900 placeholder-gray-400 focus:border-gray-400 focus:outline-none focus:ring-1 focus:ring-gray-400 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-500"
44
+ @input =" validateApiKey"
45
+ />
46
+ <button
47
+ @click =" showApiKey = !showApiKey"
48
+ type =" button"
49
+ class =" absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
50
+ >
51
+ <svg v-if =" !showApiKey" class =" h-5 w-5" fill =" none" stroke =" currentColor" viewBox =" 0 0 24 24" >
52
+ <path stroke-linecap =" round" stroke-linejoin =" round" stroke-width =" 2" d =" M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
53
+ <path stroke-linecap =" round" stroke-linejoin =" round" stroke-width =" 2" d =" M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
54
+ </svg >
55
+ <svg v-else class =" h-5 w-5" fill =" none" stroke =" currentColor" viewBox =" 0 0 24 24" >
56
+ <path stroke-linecap =" round" stroke-linejoin =" round" stroke-width =" 2" d =" M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21" />
57
+ </svg >
58
+ </button >
59
+ </div >
60
+ <Transition
61
+ enter-active-class =" transition duration-200 ease-out"
62
+ enter-from-class =" opacity-0"
63
+ enter-to-class =" opacity-100"
64
+ leave-active-class =" transition duration-150 ease-in"
65
+ leave-from-class =" opacity-100"
66
+ leave-to-class =" opacity-0"
67
+ >
68
+ <p v-if =" apiKeyError" class =" mt-1.5 text-sm text-red-600 dark:text-red-400" >
69
+ {{ apiKeyError }}
70
+ </p >
71
+ <p v-else-if =" apiKeyValid" class =" mt-1.5 text-sm text-green-600 dark:text-green-400" >
72
+ ✓ Valid API key format
73
+ </p >
74
+ </Transition >
75
+ </div >
76
+
77
+ <div class =" rounded-lg bg-gray-50 p-3 dark:bg-gray-900/50" >
78
+ <p class =" text-sm text-gray-600 dark:text-gray-400" >
79
+ Need an API key?
80
+ <a href =" https://platform.openai.com/api-keys" target =" _blank" class =" font-medium text-gray-900 underline hover:text-gray-700 dark:text-gray-100 dark:hover:text-gray-300" >
81
+ Get one from OpenAI
82
+ </a >
83
+ </p >
84
+ </div >
85
+ </div >
86
+
87
+ <div class =" mt-6 flex justify-end" >
88
+ <button
89
+ @click =" saveApiKey"
90
+ :disabled =" !apiKeyValid || isValidating"
91
+ :class =" ['rounded-lg px-6 py-2.5 text-sm font-medium transition-all',
92
+ apiKeyValid && !isValidating
93
+ ? 'bg-gray-900 text-white hover:bg-gray-800 dark:bg-gray-100 dark:text-gray-900 dark:hover:bg-gray-200'
94
+ : 'bg-gray-100 text-gray-400 cursor-not-allowed dark:bg-gray-800 dark:text-gray-600']"
95
+ >
96
+ <span v-if =" isValidating" class =" flex items-center" >
97
+ <svg class =" mr-2 h-4 w-4 animate-spin" fill =" none" viewBox =" 0 0 24 24" >
98
+ <circle class =" opacity-25" cx =" 12" cy =" 12" r =" 10" stroke =" currentColor" stroke-width =" 4" ></circle >
99
+ <path class =" opacity-75" fill =" currentColor" d =" M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" ></path >
100
+ </svg >
101
+ Validating...
102
+ </span >
103
+ <span v-else >Continue</span >
104
+ </button >
105
+ </div >
106
+ </div >
107
+
108
+ <!-- Step 2: GitHub Star -->
109
+ <div v-else-if =" currentStep === 2" >
110
+ <div class =" text-center" >
111
+ <div class =" mb-4 inline-flex rounded-full bg-gray-100 p-3 dark:bg-gray-700" >
112
+ <svg class =" h-8 w-8 text-gray-700 dark:text-gray-300" fill =" currentColor" viewBox =" 0 0 24 24" >
113
+ <path d =" M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
114
+ </svg >
115
+ </div >
116
+ <h2 class =" mb-2 text-xl font-medium text-gray-900 dark:text-white" >Support Clueless</h2 >
117
+ <p class =" mb-6 text-sm text-gray-600 dark:text-gray-400" >
118
+ If you find Clueless helpful, please consider starring our repository on GitHub. It helps others discover the project!
119
+ </p >
120
+
121
+ <button
122
+ @click =" openGitHub"
123
+ class =" mb-4 inline-flex items-center rounded-lg bg-gray-900 px-4 py-2.5 text-sm font-medium text-white hover:bg-gray-800 dark:bg-gray-100 dark:text-gray-900 dark:hover:bg-gray-200"
124
+ >
125
+ <svg class =" mr-2 h-4 w-4" fill =" currentColor" viewBox =" 0 0 16 16" >
126
+ <path d =" M8 .25a.75.75 0 01.673.418l1.882 3.815 4.21.612a.75.75 0 01.416 1.279l-3.046 2.97.719 4.192a.75.75 0 01-1.088.791L8 12.347l-3.766 1.98a.75.75 0 01-1.088-.79l.72-4.194L.818 6.374a.75.75 0 01.416-1.28l4.21-.611L7.327.668A.75.75 0 018 .25z" />
127
+ </svg >
128
+ Star on GitHub
129
+ </button >
130
+
131
+ <Transition
132
+ enter-active-class =" transition duration-200 ease-out"
133
+ enter-from-class =" opacity-0 scale-95"
134
+ enter-to-class =" opacity-100 scale-100"
135
+ >
136
+ <p v-if =" hasStarred" class =" mb-4 text-sm text-green-600 dark:text-green-400" >
137
+ ✓ Thank you for your support!
138
+ </p >
139
+ </Transition >
140
+ </div >
141
+
142
+ <div class =" mt-6 flex items-center justify-between" >
143
+ <button
144
+ @click =" currentStep = 1"
145
+ class =" text-sm text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
146
+ >
147
+ Back
148
+ </button >
149
+ <button
150
+ @click =" completeOnboarding"
151
+ class =" rounded-lg bg-gray-900 px-6 py-2.5 text-sm font-medium text-white hover:bg-gray-800 dark:bg-gray-100 dark:text-gray-900 dark:hover:bg-gray-200"
152
+ >
153
+ Get Started
154
+ </button >
155
+ </div >
156
+ </div >
157
+ </div >
158
+ </div >
159
+ </div >
160
+ </div >
161
+ </div >
162
+ </template >
163
+
164
+ <script setup lang="ts">
165
+ import { ref , computed } from ' vue'
166
+ import { router } from ' @inertiajs/vue3'
167
+ import axios from ' axios'
168
+
169
+ const currentStep = ref (1 )
170
+ const apiKey = ref (' ' )
171
+ const showApiKey = ref (false )
172
+ const apiKeyValid = ref (false )
173
+ const apiKeyError = ref (' ' )
174
+ const isValidating = ref (false )
175
+ const hasStarred = ref (false )
176
+
177
+ const showSteps = computed (() => currentStep .value > 0 )
178
+
179
+ const validateApiKey = () => {
180
+ const key = apiKey .value .trim ()
181
+
182
+ if (! key ) {
183
+ apiKeyValid .value = false
184
+ apiKeyError .value = ' '
185
+ return
186
+ }
187
+
188
+ if (! key .startsWith (' sk-' )) {
189
+ apiKeyValid .value = false
190
+ apiKeyError .value = ' API key should start with "sk-"'
191
+ return
192
+ }
193
+
194
+ if (key .length < 20 ) {
195
+ apiKeyValid .value = false
196
+ apiKeyError .value = ' API key seems too short'
197
+ return
198
+ }
199
+
200
+ apiKeyValid .value = true
201
+ apiKeyError .value = ' '
202
+ }
203
+
204
+ const saveApiKey = async () => {
205
+ if (! apiKeyValid .value || isValidating .value ) return
206
+
207
+ isValidating .value = true
208
+
209
+ try {
210
+ const response = await axios .post (' /api/openai/api-key' , {
211
+ api_key: apiKey .value
212
+ })
213
+
214
+ if (response .data .success ) {
215
+ currentStep .value = 2
216
+ } else {
217
+ apiKeyError .value = ' Failed to save API key. Please try again.'
218
+ }
219
+ } catch (error ) {
220
+ apiKeyError .value = ' Invalid API key or connection error. Please check and try again.'
221
+ } finally {
222
+ isValidating .value = false
223
+ }
224
+ }
225
+
226
+ const openGitHub = async () => {
227
+ try {
228
+ // Use NativePHP API endpoint to open in default browser
229
+ await axios .post (' /api/open-external' , {
230
+ url: ' https://github.com/vijaythecoder/clueless'
231
+ })
232
+ hasStarred .value = true
233
+ } catch (error ) {
234
+ console .error (' Failed to open GitHub:' , error )
235
+ // Fallback for web browser
236
+ window .open (' https://github.com/vijaythecoder/clueless' , ' _blank' )
237
+ hasStarred .value = true
238
+ }
239
+ }
240
+
241
+ const completeOnboarding = () => {
242
+ // Navigate to realtime agent
243
+ router .visit (' /realtime-agent' )
244
+ }
245
+ </script >
0 commit comments