Initial WallMuse project
This commit is contained in:
108
apps/web/src/app/settings/api-keys/page.tsx
Executable file
108
apps/web/src/app/settings/api-keys/page.tsx
Executable file
@@ -0,0 +1,108 @@
|
||||
import { CheckCircle2, Eye, KeyRound, PlugZap, ShieldCheck } from "lucide-react";
|
||||
import { wallMuseApi } from "@wallmuse/api-client";
|
||||
import { AppShell } from "../../../components/app-shell";
|
||||
|
||||
export default async function ApiKeysPage() {
|
||||
const keys = await wallMuseApi.listApiKeys();
|
||||
|
||||
return (
|
||||
<AppShell>
|
||||
<div className="page-grid">
|
||||
<section className="page-title">
|
||||
<p className="eyebrow">
|
||||
<KeyRound size={18} strokeWidth={1.75} />
|
||||
User API Key
|
||||
</p>
|
||||
<h1>绑定自己的模型 API Key。</h1>
|
||||
<p>前端只提交到本站 API。Key 的加密保存、连通性测试和调用日志由后端接口处理。</p>
|
||||
</section>
|
||||
|
||||
<section className="settings-layout">
|
||||
<form className="glass-panel panel-pad">
|
||||
<div className="field">
|
||||
<label htmlFor="provider">Provider</label>
|
||||
<select id="provider" defaultValue="OpenAI Compatible">
|
||||
<option>OpenAI Compatible</option>
|
||||
<option>SiliconFlow</option>
|
||||
<option>Qwen Image</option>
|
||||
<option>Seedream</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label htmlFor="baseUrl">Base URL</label>
|
||||
<input id="baseUrl" defaultValue="https://api.example.com/v1" />
|
||||
</div>
|
||||
<div className="field">
|
||||
<label htmlFor="apiKey">API Key</label>
|
||||
<input id="apiKey" type="password" defaultValue="sk-wallmuse-demo-key" />
|
||||
</div>
|
||||
<div className="field">
|
||||
<label htmlFor="model">Model ID</label>
|
||||
<input id="model" defaultValue="gpt-image-1" />
|
||||
</div>
|
||||
|
||||
<div className="switch-row">
|
||||
<span>
|
||||
<strong>保存到账户</strong>
|
||||
<br />
|
||||
<small>服务端加密保存,前端不读取明文。</small>
|
||||
</span>
|
||||
<input type="checkbox" defaultChecked aria-label="Save key to account" />
|
||||
</div>
|
||||
|
||||
<div className="switch-row">
|
||||
<span>
|
||||
<strong>设为默认生成模型</strong>
|
||||
<br />
|
||||
<small>生成页默认选择此供应商和模型。</small>
|
||||
</span>
|
||||
<input type="checkbox" defaultChecked aria-label="Set as default key" />
|
||||
</div>
|
||||
|
||||
<div className="quick-specs">
|
||||
<button className="primary-button" type="button">
|
||||
<ShieldCheck size={18} strokeWidth={1.75} />
|
||||
Save key
|
||||
</button>
|
||||
<button className="glass-button" type="button">
|
||||
<PlugZap size={18} strokeWidth={1.75} />
|
||||
Test connection
|
||||
</button>
|
||||
<button className="glass-button" type="button" aria-label="Show key">
|
||||
<Eye size={18} strokeWidth={1.75} />
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div className="glass-panel panel-pad">
|
||||
<div className="section-heading">
|
||||
<div>
|
||||
<h2>已保存 Key</h2>
|
||||
<p>只展示 masked key,不暴露完整密钥。</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="task-list">
|
||||
{keys.map((key) => (
|
||||
<div className="key-row" key={key.id}>
|
||||
<div className="status-line">
|
||||
<strong>{key.provider}</strong>
|
||||
<span className="score">
|
||||
<CheckCircle2 size={16} strokeWidth={1.75} />
|
||||
{key.status}
|
||||
</span>
|
||||
</div>
|
||||
<span>{key.baseUrl}</span>
|
||||
<span>{key.model} · {key.maskedKey}</span>
|
||||
<div className="quick-specs">
|
||||
<span className="pill">{key.isDefault ? "Default" : "Optional"}</span>
|
||||
<span className="pill">{new Date(key.updatedAt).toLocaleString()}</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</AppShell>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user