Initial release: business-name finder with live domain availability
Free, no-account tool for naming a business and finding a domain you can actually register. Availability uses no paid API and no key. IANA's RDAP bootstrap maps ~1,200 TLDs to their authoritative registry servers (404 = free, 200 = taken); the handful with no RDAP at all (.io, .co, .me, .sh, .gg, .us) fall back to a DNS NS lookup and are reported as "probably free" rather than confirmed, because a registered-but-undelegated domain is indistinguishable that way. Name generation is pure, deterministic, and client-side — eight strategies over a curated word bank, ranked by a scorer that does the real quality work. Three of its guards (prefix-only hint matching, -er-only syncopation, truncation rejection) exist because of specific bad output and should not be relaxed. Optional AI suggestions use the visitor's OWN Anthropic/Gemini key from localStorage, called browser-direct, so this stays free to run. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JChWdFJPCRxMBxErb8kUVK
This commit is contained in:
107
src/app/settings/page.tsx
Normal file
107
src/app/settings/page.tsx
Normal file
@@ -0,0 +1,107 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { loadAiSettings, saveAiSettings, type AiProvider } from "@/lib/ai";
|
||||
|
||||
export default function SettingsPage() {
|
||||
const [provider, setProvider] = useState<AiProvider>("anthropic");
|
||||
const [key, setKey] = useState("");
|
||||
const [saved, setSaved] = useState(false);
|
||||
const [hasStored, setHasStored] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const existing = loadAiSettings();
|
||||
if (existing) {
|
||||
setProvider(existing.provider);
|
||||
setKey(existing.key);
|
||||
setHasStored(true);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const onSave = () => {
|
||||
const trimmed = key.trim();
|
||||
saveAiSettings(trimmed ? { provider, key: trimmed } : null);
|
||||
setHasStored(Boolean(trimmed));
|
||||
setSaved(true);
|
||||
window.setTimeout(() => setSaved(false), 2500);
|
||||
};
|
||||
|
||||
const onClear = () => {
|
||||
saveAiSettings(null);
|
||||
setKey("");
|
||||
setHasStored(false);
|
||||
setSaved(true);
|
||||
window.setTimeout(() => setSaved(false), 2500);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<h1>Settings</h1>
|
||||
<p className="tagline">
|
||||
<Link href="/">← Back to the name finder</Link>
|
||||
</p>
|
||||
|
||||
<h2>Bring your own AI key (optional)</h2>
|
||||
<p className="tagline">
|
||||
The name finder works fully offline without this — the built-in generator needs no
|
||||
key and costs nothing. Adding your own API key gets you a second, usually more
|
||||
imaginative set of suggestions alongside it.
|
||||
</p>
|
||||
|
||||
<p className="notice info">
|
||||
Your key is stored in <strong>this browser only</strong> and the request goes straight
|
||||
from your browser to {provider === "anthropic" ? "Anthropic" : "Google"}. It is never sent
|
||||
to our server and never appears in our logs. Usage is billed to your own account, so use
|
||||
a key with a spending limit set.
|
||||
</p>
|
||||
|
||||
<div className="field">
|
||||
<label htmlFor="provider">Provider</label>
|
||||
<select id="provider" value={provider} onChange={(e) => setProvider(e.target.value as AiProvider)}>
|
||||
<option value="anthropic">Anthropic (Claude)</option>
|
||||
<option value="gemini">Google (Gemini)</option>
|
||||
</select>
|
||||
<p className="hint">
|
||||
{provider === "anthropic" ? (
|
||||
<>
|
||||
Create a key at{" "}
|
||||
<a href="https://console.anthropic.com/settings/keys" target="_blank" rel="noopener noreferrer">
|
||||
console.anthropic.com
|
||||
</a>
|
||||
. Starts with <code>sk-ant-</code>.
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
Create a key at{" "}
|
||||
<a href="https://aistudio.google.com/apikey" target="_blank" rel="noopener noreferrer">
|
||||
aistudio.google.com
|
||||
</a>
|
||||
. Gemini has a free tier that is plenty for this.
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="field">
|
||||
<label htmlFor="key">API key</label>
|
||||
<input
|
||||
id="key"
|
||||
type="password"
|
||||
value={key}
|
||||
autoComplete="off"
|
||||
placeholder={provider === "anthropic" ? "sk-ant-…" : "AIza…"}
|
||||
onChange={(e) => setKey(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="controls">
|
||||
<button className="primary" onClick={onSave}>
|
||||
Save key
|
||||
</button>
|
||||
{hasStored ? <button onClick={onClear}>Remove key</button> : null}
|
||||
{saved ? <span style={{ color: "var(--ok)", fontSize: 14 }}>Saved to this browser.</span> : null}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user