home: turn the API peek into a live console (0.98.0)
the agent-native callout's API peek showed a curl line + the JSON, but felt static. made it feel like a real API client: - after each call it shows the response status and timing (green dot, '200 OK · 47 ms') measured with performance.now() — the visitor's actual request time - a '↻ run' button re-fetches the current endpoint (running… -> new status) - endpoint tabs still switch (stats / roster / activity), all read-only & no-auth - verified live: status '200 OK · 683 ms', run re-fetches (running… -> 200 OK), green dot on success - changelog entry (0.98) added with the bump
This commit is contained in:
parent
d05a5b110c
commit
9c1aa73742
@ -17,7 +17,7 @@
|
|||||||
@media (prefers-reduced-motion: reduce) { .app-loading-gem { animation: none; transform: rotate(45deg); } }
|
@media (prefers-reduced-motion: reduce) { .app-loading-gem { animation: none; transform: rotate(45deg); } }
|
||||||
</style>
|
</style>
|
||||||
<meta name="description" content="amerc is the agent mercenary tavern — hire, run, publish, and remotely deliver AI agents across software boundaries. No infrastructure, just skills." />
|
<meta name="description" content="amerc is the agent mercenary tavern — hire, run, publish, and remotely deliver AI agents across software boundaries. No infrastructure, just skills." />
|
||||||
<meta name="version" content="0.97.0-signin" />
|
<meta name="version" content="0.98.0-apiconsole" />
|
||||||
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
|
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
|
||||||
<link rel="icon" href="/app-192.png" type="image/png" sizes="192x192" />
|
<link rel="icon" href="/app-192.png" type="image/png" sizes="192x192" />
|
||||||
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
|
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "amerc-site",
|
"name": "amerc-site",
|
||||||
"version": "0.97.0-signin",
|
"version": "0.98.0-apiconsole",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import { api } from './api.js';
|
|||||||
const Menu = ({ size = 20 }) => (<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true"><line x1="3" y1="6" x2="21" y2="6" /><line x1="3" y1="12" x2="21" y2="12" /><line x1="3" y1="18" x2="21" y2="18" /></svg>);
|
const Menu = ({ size = 20 }) => (<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true"><line x1="3" y1="6" x2="21" y2="6" /><line x1="3" y1="12" x2="21" y2="12" /><line x1="3" y1="18" x2="21" y2="18" /></svg>);
|
||||||
const X = ({ size = 20 }) => (<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true"><line x1="18" y1="6" x2="6" y2="18" /><line x1="6" y1="6" x2="18" y2="18" /></svg>);
|
const X = ({ size = 20 }) => (<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true"><line x1="18" y1="6" x2="6" y2="18" /><line x1="6" y1="6" x2="18" y2="18" /></svg>);
|
||||||
|
|
||||||
const RELEASE = '0.97.0-signin';
|
const RELEASE = '0.98.0-apiconsole';
|
||||||
|
|
||||||
const NAV = [
|
const NAV = [
|
||||||
{ id: 'home', label: 'Tavern' },
|
{ id: 'home', label: 'Tavern' },
|
||||||
@ -250,17 +250,20 @@ function jsonHl(obj) {
|
|||||||
function ApiPeek() {
|
function ApiPeek() {
|
||||||
const [ep, setEp] = useState('stats');
|
const [ep, setEp] = useState('stats');
|
||||||
const [out, setOut] = useState(null);
|
const [out, setOut] = useState(null);
|
||||||
|
const [meta, setMeta] = useState(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
useEffect(() => {
|
const run = useCallback((id) => {
|
||||||
let live = true;
|
let cancelled = false;
|
||||||
setLoading(true); setOut(null);
|
setLoading(true); setOut(null); setMeta(null);
|
||||||
const e = PEEK.find((x) => x.id === ep);
|
const e = PEEK.find((x) => x.id === id);
|
||||||
|
const t0 = performance.now();
|
||||||
api(e.path)
|
api(e.path)
|
||||||
.then((d) => { if (live) setOut(d); })
|
.then((d) => { if (!cancelled) { setOut(d); setMeta({ ok: true, ms: Math.round(performance.now() - t0) }); } })
|
||||||
.catch((err) => { if (live) setOut({ error: err.message }); })
|
.catch((err) => { if (!cancelled) { setOut({ error: err.message }); setMeta({ ok: false, ms: Math.round(performance.now() - t0) }); } })
|
||||||
.finally(() => { if (live) setLoading(false); });
|
.finally(() => { if (!cancelled) setLoading(false); });
|
||||||
return () => { live = false; };
|
return () => { cancelled = true; };
|
||||||
}, [ep]);
|
}, []);
|
||||||
|
useEffect(() => run(ep), [ep, run]);
|
||||||
const cur = PEEK.find((x) => x.id === ep);
|
const cur = PEEK.find((x) => x.id === ep);
|
||||||
return (
|
return (
|
||||||
<div className="hf-peek">
|
<div className="hf-peek">
|
||||||
@ -271,6 +274,14 @@ function ApiPeek() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="hf-peek-term">
|
<div className="hf-peek-term">
|
||||||
<div className="hf-peek-cmd"><span className="hf-peek-dollar" aria-hidden="true">$</span> curl https://amerc.ai/api{cur.path}</div>
|
<div className="hf-peek-cmd"><span className="hf-peek-dollar" aria-hidden="true">$</span> curl https://amerc.ai/api{cur.path}</div>
|
||||||
|
<div className="hf-peek-bar">
|
||||||
|
<span className="hf-peek-status">
|
||||||
|
{loading
|
||||||
|
? <span className="hf-peek-muted">running…</span>
|
||||||
|
: meta && <><span className={`hf-peek-dot ${meta.ok ? 'ok' : 'err'}`} aria-hidden="true" />{meta.ok ? '200 OK' : 'failed'} · {meta.ms} ms</>}
|
||||||
|
</span>
|
||||||
|
<button type="button" className="hf-peek-run" onClick={() => run(ep)} disabled={loading} aria-label="Run the request again">↻ run</button>
|
||||||
|
</div>
|
||||||
{loading
|
{loading
|
||||||
? <pre className="hf-peek-out hf-peek-loading">fetching live response…</pre>
|
? <pre className="hf-peek-out hf-peek-loading">fetching live response…</pre>
|
||||||
: <pre className="hf-peek-out" aria-live="polite" dangerouslySetInnerHTML={{ __html: jsonHl(out) }} />}
|
: <pre className="hf-peek-out" aria-live="polite" dangerouslySetInnerHTML={{ __html: jsonHl(out) }} />}
|
||||||
@ -465,6 +476,7 @@ function StatusPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const CHANGELOG = [
|
const CHANGELOG = [
|
||||||
|
{ v: '0.98', date: 'Jun 2026', title: 'Live API console', notes: ['The home-page API peek now shows the response status and timing, with a run button to re-fetch'] },
|
||||||
{ v: '0.97', date: 'Jun 2026', title: 'Clearer sign-in', notes: ['The join form now uses plain labels (Email, Password) and a clear Log in button'] },
|
{ v: '0.97', date: 'Jun 2026', title: 'Clearer sign-in', notes: ['The join form now uses plain labels (Email, Password) and a clear Log in button'] },
|
||||||
{ v: '0.96', date: 'Jun 2026', title: 'Hero fits the fold', notes: ['The tagline, calls-to-action and live stats now sit above the fold on laptop screens'] },
|
{ v: '0.96', date: 'Jun 2026', title: 'Hero fits the fold', notes: ['The tagline, calls-to-action and live stats now sit above the fold on laptop screens'] },
|
||||||
{ v: '0.95', date: 'Jun 2026', title: 'Docs that flow', notes: ['Fixed dead Quickstart links and cross-linked the guides so you can click straight through'] },
|
{ v: '0.95', date: 'Jun 2026', title: 'Docs that flow', notes: ['Fixed dead Quickstart links and cross-linked the guides so you can click straight through'] },
|
||||||
|
|||||||
@ -1554,3 +1554,14 @@ button {
|
|||||||
.ap-keys-curl code { flex: 1; min-width: 0; font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: 11.5px; color: #cfe2f2; overflow-x: auto; white-space: nowrap; }
|
.ap-keys-curl code { flex: 1; min-width: 0; font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: 11.5px; color: #cfe2f2; overflow-x: auto; white-space: nowrap; }
|
||||||
.ap-keys-copy { flex: 0 0 auto; background: #46c8e0; color: #07111a; border: none; border-radius: 6px; padding: 5px 11px; font-size: 11px; font-weight: 600; cursor: pointer; }
|
.ap-keys-copy { flex: 0 0 auto; background: #46c8e0; color: #07111a; border: none; border-radius: 6px; padding: 5px 11px; font-size: 11px; font-weight: 600; cursor: pointer; }
|
||||||
.ap-keys-copy:hover { background: #6ad6ea; }
|
.ap-keys-copy:hover { background: #6ad6ea; }
|
||||||
|
|
||||||
|
/* api peek: response status/timing bar + run button (0.98) */
|
||||||
|
.hf-peek-bar { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin: 8px 0 9px; font-size: 11px; }
|
||||||
|
.hf-peek-status { display: flex; align-items: center; gap: 7px; color: #8fa3b8; min-height: 18px; }
|
||||||
|
.hf-peek-muted { color: #5b6b7d; }
|
||||||
|
.hf-peek-dot { width: 7px; height: 7px; border-radius: 50%; flex: 0 0 auto; }
|
||||||
|
.hf-peek-dot.ok { background: #4ee08a; box-shadow: 0 0 7px #4ee08a; }
|
||||||
|
.hf-peek-dot.err { background: #ff6b6b; box-shadow: 0 0 7px #ff6b6b; }
|
||||||
|
.hf-peek-run { background: none; border: 1px solid #2a4a5c; color: #7fd9ea; border-radius: 6px; padding: 3px 10px; font-size: 11px; cursor: pointer; font-family: inherit; flex: 0 0 auto; }
|
||||||
|
.hf-peek-run:hover:not(:disabled) { background: rgba(39, 216, 255, 0.1); color: #cfeff7; }
|
||||||
|
.hf-peek-run:disabled { opacity: 0.5; cursor: default; }
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user