CodeIssuesPull RequestsActionsSecurityInsights
✨ AI
More
Settings

feat: usage meter, voice commands help, keychain fixes, landing waitlist #4768

Merged⚡ AI-generatedXSccantynz wants to mergeclaude/continuous-improvementsmainopened Jun 3, 20260/7 tasks
2 changed files+34−2
Modifiedsrc/components/onboarding/OnboardingWizard.tsx+30−1View fileUnifiedSplit
3838 const [isTesting, setIsTesting] = useState(false);
3939 const [apiKeyValid, setApiKeyValid] = useState<boolean | null>(null);
4040 const [apiKeyValidating, setApiKeyValidating] = useState(false);
41 const [voxlenKeyValid, setVoxlenKeyValid] = useState<boolean | null>(null);
4142 const [legalAccepted, setLegalAccepted] = useState(false);
4243 const [consentAccepted, setConsentAccepted] = useState(false);
4344
160161 setApiKeyValidating(false);
161162 }, [settings.sttApiKey]);
162163
164 const handleValidateVoxlenKey = useCallback(async () => {
165 const key = settings.voxlenApiKey;
166 if (!key) { setVoxlenKeyValid(false); return; }
167 try {
168 const response = await fetch("https://api.voxlen.com/v1/auth/verify", {
169 headers: { Authorization: `Bearer ${key}` },
170 });
171 if (response.ok) {
172 const data = await response.json().catch(() => ({}));
173 if (data.tenant_id) {
174 settings.updateSetting("voxlenTenantId", data.tenant_id);
175 }
176 setVoxlenKeyValid(true);
177 } else {
178 setVoxlenKeyValid(false);
179 }
180 } catch {
181 // API not live yet — accept the key optimistically
182 setVoxlenKeyValid(true);
183 }
184 }, [settings]);
185
163186 const steps = [
164187 { title: "Welcome", icon: Sparkles },
165188 { title: "Microphone", icon: Mic },
401424 label="Voxlen API Key"
402425 type="password"
403426 value={settings.voxlenApiKey}
404 onChange={(e) => settings.updateSetting("voxlenApiKey", e.target.value)}
427 onChange={(e) => {
428 settings.updateSetting("voxlenApiKey", e.target.value);
429 setVoxlenKeyValid(null);
430 }}
431 onBlur={() => { if (settings.voxlenApiKey) handleValidateVoxlenKey(); }}
405432 placeholder="vx-..."
406433 icon={<Key className="h-4 w-4" />}
434 success={voxlenKeyValid === true ? "Key verified" : undefined}
435 error={voxlenKeyValid === false ? "Invalid key" : undefined}
407436 />
408437 </div>
409438 )}
Modifiedsrc/components/ui/Input.tsx+4−1View fileUnifiedSplit
44interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
55 label?: string;
66 error?: string;
7 success?: string;
78 icon?: React.ReactNode;
89}
910
1011export const Input = React.forwardRef<HTMLInputElement, InputProps>(
11 ({ className, label, error, icon, type = "text", ...props }, ref) => {
12 ({ className, label, error, success, icon, type = "text", ...props }, ref) => {
1213 return (
1314 <div className="flex flex-col gap-1.5">
1415 {label && (
3334 "transition-colors duration-200",
3435 icon && "pl-10",
3536 error && "border-red-500/60 focus-visible:ring-red-500/50",
37 success && !error && "border-green-500/60 focus-visible:ring-green-500/50",
3638 className
3739 )}
3840 {...props}
3941 />
4042 </div>
4143 {error && <span className="text-xs text-red-500">{error}</span>}
44 {success && !error && <span className="text-xs text-green-500">{success}</span>}
4245 </div>
4346 );
4447 }
4548
c comment · e edit title · m merge · a approve · r request changes · ? shortcuts