Files
names/src/app/settings/page.tsx
tonym c307a75880 feat: platform AI names behind Sign in with OTM (v0.3.0)
- OTM SSO: /api/auth/otm-sso verifies the control-plane ticket (vendored
  verifier), in-memory jti claim, mints an HMAC cookie (src/lib/session.ts);
  /api/auth/session + /api/auth/signout. No accounts, no DB.
- /api/ai-names: server-side call to the platform's metered gateway
  (ANTHROPIC_BASE_URL + per-app gateway token), 401 without a session,
  ~20 req/h per OTM account. Prompt/parser shared with the BYO-key path
  via src/lib/ai-prompt.ts.
- Finder: 'Sign in with OTM for AI names' link → 'AI names' toggle when
  signed in; own key still overrides.

Pairs with platform 0.116.0 (needsAnthropic + needsAuthSecret on names).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XStQKxPfEjrTvWo83KFCxG
2026-08-16 18:27:38 -05:00

109 lines
3.6 KiB
TypeScript

"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 &mdash; 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. Don&rsquo;t have a key? Sign in with a free
OTM account on the main page and AI names are included &mdash; a key here overrides that.
</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>
</>
);
}