7
7
<div class =" mb-4 flex items-center justify-center" >
8
8
<div class =" rounded-xl bg-gray-900 p-3 dark:bg-gray-100" >
9
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" />
10
+ <path
11
+ stroke-linecap =" round"
12
+ stroke-linejoin =" round"
13
+ stroke-width =" 2"
14
+ 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"
15
+ />
11
16
</svg >
12
17
</div >
13
18
</div >
17
22
18
23
<!-- Progress Indicator -->
19
24
<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 >
25
+ <div
26
+ v-for =" i in 2"
27
+ :key =" i"
28
+ :class =" [
29
+ 'h-1.5 w-20 rounded-full transition-all duration-300',
30
+ i <= currentStep ? 'bg-gray-900 dark:bg-gray-100' : 'bg-gray-200 dark:bg-gray-700',
31
+ ]"
32
+ ></div >
24
33
</div >
25
34
26
35
<!-- Main Card -->
29
38
<!-- Step 1: API Key -->
30
39
<div v-if =" currentStep === 1" >
31
40
<h2 class =" mb-6 text-xl font-medium text-gray-900 dark:text-white" >Setup OpenAI API</h2 >
32
-
41
+
33
42
<div class =" space-y-4" >
34
43
<div >
35
- <label class =" mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300" >
36
- API Key
37
- </label >
44
+ <label class =" mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300" > API Key </label >
38
45
<div class =" relative" >
39
46
<input
40
47
v-model =" apiKey"
41
48
:type =" showApiKey ? 'text' : 'password'"
42
49
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"
50
+ 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:ring-1 focus:ring-gray-400 focus:outline-none dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-500"
44
51
@input =" validateApiKey"
45
52
/>
46
53
<button
47
54
@click =" showApiKey = !showApiKey"
48
55
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"
56
+ class =" absolute top-1/2 right-3 -translate-y-1/2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
50
57
>
51
58
<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" />
59
+ <path
60
+ stroke-linecap =" round"
61
+ stroke-linejoin =" round"
62
+ stroke-width =" 2"
63
+ d =" M15 12a3 3 0 11-6 0 3 3 0 016 0z"
64
+ />
65
+ <path
66
+ stroke-linecap =" round"
67
+ stroke-linejoin =" round"
68
+ stroke-width =" 2"
69
+ 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"
70
+ />
54
71
</svg >
55
72
<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" />
73
+ <path
74
+ stroke-linecap =" round"
75
+ stroke-linejoin =" round"
76
+ stroke-width =" 2"
77
+ 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"
78
+ />
57
79
</svg >
58
80
</button >
59
81
</div >
76
98
77
99
<div class =" rounded-lg bg-gray-50 p-3 dark:bg-gray-900/50" >
78
100
<p class =" text-sm text-gray-600 dark:text-gray-400" >
79
- Need an API key?
80
- <button @click =" openOpenAI" class =" font-medium text-gray-900 underline hover:text-gray-700 dark:text-gray-100 dark:hover:text-gray-300" >
101
+ Need an API key?
102
+ <button
103
+ @click =" openOpenAI"
104
+ class =" font-medium text-gray-900 underline hover:text-gray-700 dark:text-gray-100 dark:hover:text-gray-300"
105
+ >
81
106
Get one from OpenAI
82
107
</button >
83
108
</p >
88
113
<button
89
114
@click =" saveApiKey"
90
115
: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']"
116
+ :class =" [
117
+ 'rounded-lg px-6 py-2.5 text-sm font-medium transition-all',
118
+ apiKeyValid && !isValidating
119
+ ? 'bg-gray-900 text-white hover:bg-gray-800 dark:bg-gray-100 dark:text-gray-900 dark:hover:bg-gray-200'
120
+ : 'cursor-not-allowed bg-gray-100 text-gray-400 dark:bg-gray-800 dark:text-gray-600',
121
+ ]"
95
122
>
96
123
<span v-if =" isValidating" class =" flex items-center" >
97
124
<svg class =" mr-2 h-4 w-4 animate-spin" fill =" none" viewBox =" 0 0 24 24" >
98
125
<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 >
126
+ <path
127
+ class =" opacity-75"
128
+ fill =" currentColor"
129
+ 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"
130
+ ></path >
100
131
</svg >
101
132
Validating...
102
133
</span >
110
141
<div class =" text-center" >
111
142
<div class =" mb-4 inline-flex rounded-full bg-gray-100 p-3 dark:bg-gray-700" >
112
143
<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" />
144
+ <path
145
+ 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"
146
+ />
114
147
</svg >
115
148
</div >
116
149
<h2 class =" mb-2 text-xl font-medium text-gray-900 dark:text-white" >Support Clueless</h2 >
117
150
<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!
151
+ If you find Clueless helpful, please consider starring our repository on GitHub. It helps others discover the
152
+ project!
119
153
</p >
120
-
154
+
121
155
<button
122
156
@click =" openGitHub"
123
157
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
158
>
125
159
<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" />
160
+ <path
161
+ 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"
162
+ />
127
163
</svg >
128
164
Star on GitHub
129
165
</button >
133
169
enter-from-class =" opacity-0 scale-95"
134
170
enter-to-class =" opacity-100 scale-100"
135
171
>
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 >
172
+ <p v-if =" hasStarred" class =" mb-4 text-sm text-green-600 dark:text-green-400" >✓ Thank you for your support!</p >
139
173
</Transition >
140
174
</div >
141
175
162
196
</template >
163
197
164
198
<script setup lang="ts">
165
- import { ref , computed } from ' vue '
166
- import { router } from ' @inertiajs/vue3 '
167
- import axios from ' axios '
199
+ import { router } from ' @inertiajs/vue3 ' ;
200
+ import axios from ' axios ' ;
201
+ import { computed , ref } from ' vue ' ;
168
202
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 )
203
+ const currentStep = ref (1 );
204
+ const apiKey = ref (' ' );
205
+ const showApiKey = ref (false );
206
+ const apiKeyValid = ref (false );
207
+ const apiKeyError = ref (' ' );
208
+ const isValidating = ref (false );
209
+ const hasStarred = ref (false );
176
210
177
- const showSteps = computed (() => currentStep .value > 0 )
211
+ const showSteps = computed (() => currentStep .value > 0 );
178
212
179
213
const validateApiKey = () => {
180
- const key = apiKey .value .trim ()
181
-
214
+ const key = apiKey .value .trim ();
215
+
182
216
if (! key ) {
183
- apiKeyValid .value = false
184
- apiKeyError .value = ' '
185
- return
217
+ apiKeyValid .value = false ;
218
+ apiKeyError .value = ' ' ;
219
+ return ;
186
220
}
187
-
221
+
188
222
if (! key .startsWith (' sk-' )) {
189
- apiKeyValid .value = false
190
- apiKeyError .value = ' API key should start with "sk-"'
191
- return
223
+ apiKeyValid .value = false ;
224
+ apiKeyError .value = ' API key should start with "sk-"' ;
225
+ return ;
192
226
}
193
-
227
+
194
228
if (key .length < 20 ) {
195
- apiKeyValid .value = false
196
- apiKeyError .value = ' API key seems too short'
197
- return
229
+ apiKeyValid .value = false ;
230
+ apiKeyError .value = ' API key seems too short' ;
231
+ return ;
198
232
}
199
-
200
- apiKeyValid .value = true
201
- apiKeyError .value = ' '
202
- }
233
+
234
+ apiKeyValid .value = true ;
235
+ apiKeyError .value = ' ' ;
236
+ };
203
237
204
238
const saveApiKey = async () => {
205
- if (! apiKeyValid .value || isValidating .value ) return
206
-
207
- isValidating .value = true
208
-
239
+ if (! apiKeyValid .value || isValidating .value ) return ;
240
+
241
+ isValidating .value = true ;
242
+
209
243
try {
210
244
const response = await axios .post (' /api/openai/api-key' , {
211
- api_key: apiKey .value
212
- })
213
-
245
+ api_key: apiKey .value ,
246
+ });
247
+
214
248
if (response .data .success ) {
215
- currentStep .value = 2
249
+ currentStep .value = 2 ;
216
250
} else {
217
- apiKeyError .value = ' Failed to save API key. Please try again.'
251
+ apiKeyError .value = ' Failed to save API key. Please try again.' ;
218
252
}
219
- } catch ( error ) {
220
- apiKeyError .value = ' Invalid API key or connection error. Please check and try again.'
253
+ } catch {
254
+ apiKeyError .value = ' Invalid API key or connection error. Please check and try again.' ;
221
255
} finally {
222
- isValidating .value = false
256
+ isValidating .value = false ;
223
257
}
224
- }
258
+ };
225
259
226
260
const openGitHub = async () => {
227
261
try {
228
262
// Use NativePHP API endpoint to open in default browser
229
263
await axios .post (' /api/open-external' , {
230
- url: ' https://github.com/vijaythecoder/clueless'
231
- })
232
- hasStarred .value = true
264
+ url: ' https://github.com/vijaythecoder/clueless' ,
265
+ });
266
+ hasStarred .value = true ;
233
267
} catch (error ) {
234
- console .error (' Failed to open GitHub:' , error )
268
+ console .error (' Failed to open GitHub:' , error );
235
269
// Fallback for web browser
236
- window .open (' https://github.com/vijaythecoder/clueless' , ' _blank' )
237
- hasStarred .value = true
270
+ window .open (' https://github.com/vijaythecoder/clueless' , ' _blank' );
271
+ hasStarred .value = true ;
238
272
}
239
- }
273
+ };
240
274
241
275
const openOpenAI = async () => {
242
276
try {
243
277
// Use NativePHP API endpoint to open in default browser
244
278
await axios .post (' /api/open-external' , {
245
- url: ' https://platform.openai.com/api-keys'
246
- })
279
+ url: ' https://platform.openai.com/api-keys' ,
280
+ });
247
281
} catch (error ) {
248
- console .error (' Failed to open OpenAI:' , error )
282
+ console .error (' Failed to open OpenAI:' , error );
249
283
// Fallback for web browser
250
- window .open (' https://platform.openai.com/api-keys' , ' _blank' )
284
+ window .open (' https://platform.openai.com/api-keys' , ' _blank' );
251
285
}
252
- }
286
+ };
253
287
254
288
const completeOnboarding = () => {
255
289
// Navigate to realtime agent
256
- router .visit (' /realtime-agent' )
257
- }
258
- </script >
290
+ router .visit (' /realtime-agent' );
291
+ };
292
+ </script >
0 commit comments