Files
writer-work-flow/apps/web/components/settings/CredentialsPanel.tsx
Yaojia Wang 67e30a6863 feat(ui): P3 页面级应用(作品库卡片/空态/加载/设置/次级页)
- 作品库卡片重设计:书脊封面字块 + 衬线书名 + 一句话故事(缺失给占位) +
  元信息行(题材徽章/StatusDot 待审/mono 时间),Card tone=card 浮起消塌陷
- EmptyState 中性化 + inline/card/bare 变体 + eyebrow(向后兼容)
- loading 骨架化(Skeleton 拼作品库结构,motion-safe)
- 设置页拆分 ProvidersSettings 489→198 + 5 个子组件,凭据点用 StatusDot
- 次级页(向导/模板/codex/大纲)排版字阶与圆角 token 收敛
2026-07-11 07:36:32 +02:00

86 lines
2.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import { PlugZap } from "lucide-react";
import {
CredentialRow,
type TestResult,
} from "@/components/settings/CredentialRow";
import { EmptyState } from "@/components/ui/EmptyState";
import { SectionHeader } from "@/components/ui/SectionHeader";
import { API_KEY_PROVIDERS } from "@/lib/settings/providers";
import type { ProviderView } from "@/lib/api/types";
interface CredentialsPanelProps {
providers: ProviderView[];
drafts: Record<string, string>;
savingId: string | null;
testingId: string | null;
results: Record<string, TestResult>;
onDraftChange: (providerId: string, value: string) => void;
onSave: (providerId: string) => void;
onTest: (providerId: string) => void;
}
// 提供商凭据面板API-key 提供商列表,逐行保存/测试UX §6.10)。
export function CredentialsPanel({
providers,
drafts,
savingId,
testingId,
results,
onDraftChange,
onSave,
onTest,
}: CredentialsPanelProps) {
const maskedFor = (id: string): string | null =>
providers.find((p) => p.provider === id)?.masked_key ?? null;
return (
<section className="min-w-0">
<SectionHeader
eyebrow="凭据"
title="提供商凭据"
description="API Key 只用于后端探活和调用,列表中只显示脱敏后的已保存凭据。"
/>
<div className="mt-3 grid gap-2 text-xs text-ink-soft sm:grid-cols-3">
<StatItem label="已保存" value={providers.length} />
<StatItem label="可配置" value={API_KEY_PROVIDERS.length} />
<StatItem label="测试结果" value={Object.keys(results).length} />
</div>
{providers.length === 0 ? (
<EmptyState
icon={PlugZap}
title="还没有可用提供商"
description="至少连接一个提供商即可开始写作。求质量可选 Anthropic求性价比可选 DeepSeek。"
className="mt-4"
/>
) : null}
<ul className="mt-4 divide-y divide-line rounded border border-line bg-panel">
{API_KEY_PROVIDERS.map((prov) => (
<CredentialRow
key={prov.id}
provider={prov}
masked={maskedFor(prov.id)}
draft={drafts[prov.id] ?? ""}
saving={savingId === prov.id}
testing={testingId === prov.id}
result={results[prov.id]}
onDraftChange={(value) => onDraftChange(prov.id, value)}
onSave={() => onSave(prov.id)}
onTest={() => onTest(prov.id)}
/>
))}
</ul>
</section>
);
}
function StatItem({ label, value }: { label: string; value: number }) {
return (
<div className="rounded border border-line bg-panel px-3 py-2">
{label} <span className="font-mono text-ink">{value}</span>
</div>
);
}