feat(models): 桌面端模型卡紧凑化+一行多张(自适应网格)
- 模型卡: 缩内边距/高度, 加彩色首字母图标+名称+id, 能力徽标, 紧凑底部(启用/管理) - 容器改 CSS Grid auto-fill(minmax 160px), 一行自适应多张, 去掉固定宽 - 解决卡片大而内容小/单行一张
This commit is contained in:
@@ -20,6 +20,21 @@ interface Props {
|
||||
onOpenModels: (provider: ProviderInfo) => void;
|
||||
}
|
||||
|
||||
// 图标主题色(按模型 id 首字符给一个稳定渐变色)
|
||||
const PALETTE = [
|
||||
"linear-gradient(135deg,#6366f1,#8b5cf6)",
|
||||
"linear-gradient(135deg,#0ea5e9,#22d3ee)",
|
||||
"linear-gradient(135deg,#f59e0b,#f97316)",
|
||||
"linear-gradient(135deg,#10b981,#34d399)",
|
||||
"linear-gradient(135deg,#ef4444,#f87171)",
|
||||
"linear-gradient(135deg,#ec4899,#f472b6)",
|
||||
];
|
||||
function iconBg(id: string) {
|
||||
let h = 0;
|
||||
for (let i = 0; i < id.length; i++) h = (h * 31 + id.charCodeAt(i)) >>> 0;
|
||||
return PALETTE[h % PALETTE.length];
|
||||
}
|
||||
|
||||
export const PineAgentsModelCards = React.memo(function PineAgentsModelCards({
|
||||
providers,
|
||||
onOpenModels,
|
||||
@@ -44,13 +59,13 @@ export const PineAgentsModelCards = React.memo(function PineAgentsModelCards({
|
||||
try { localStorage.setItem(LS_KEY, JSON.stringify(next)); } catch { /* ignore */ }
|
||||
};
|
||||
|
||||
const tags = (m: ModelInfo) => {
|
||||
const tgs: string[] = [];
|
||||
if (m.supports_multimodal) tgs.push(t("models.tagMultimodal", "多模态"));
|
||||
if (m.supports_image) tgs.push(t("models.tagVision", "视觉"));
|
||||
if (m.supports_video) tgs.push(t("models.tagVideo", "视频"));
|
||||
if (tgs.length === 0) tgs.push(t("models.tagText", "文本"));
|
||||
return tgs;
|
||||
const chips = (m: ModelInfo) => {
|
||||
const c: string[] = [];
|
||||
if (m.supports_multimodal) c.push(t("models.tagMultimodal", "多模态"));
|
||||
if (m.supports_image) c.push(t("models.tagVision", "视觉"));
|
||||
if (m.supports_video) c.push(t("models.tagVideo", "视频"));
|
||||
if (c.length === 0) c.push(t("models.tagText", "文本"));
|
||||
return c;
|
||||
};
|
||||
|
||||
if (models.length === 0) {
|
||||
@@ -58,37 +73,37 @@ export const PineAgentsModelCards = React.memo(function PineAgentsModelCards({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.providerCards}>
|
||||
<div className={styles.providerCards} style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(160px, 1fr))", gap: 10 }}>
|
||||
{models.map(({ provider, model }) => {
|
||||
const pid = provider.id, mid = model.id;
|
||||
return (
|
||||
<div key={keyOf(pid, mid)} className={styles.groupCardGlass} style={{ padding: 14, minWidth: 220 }}>
|
||||
<div className={styles.groupCardHeader}>
|
||||
<span className={styles.groupCardName}>{model.name || model.id}</span>
|
||||
{isEnabled(pid, mid) && (
|
||||
<span className={styles.groupCardLiveBadge}><span className={styles.groupCardPulse} />Live</span>
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.groupCardContent}>
|
||||
<div className={styles.groupCardField}>
|
||||
<span className={styles.groupCardFieldValue}>
|
||||
<code>{model.id}</code>
|
||||
</span>
|
||||
<div className={styles.groupCardFieldValue}>
|
||||
{tags(model).map((tg) => (
|
||||
<span key={tg} className={styles.customTag} style={{ marginRight: 4 }}>{tg}</span>
|
||||
))}
|
||||
<div key={keyOf(pid, mid)}
|
||||
className={styles.groupCardGlass}
|
||||
style={{ padding: 10, border: "1px solid var(--ant-color-border)" }}>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
|
||||
<span style={{ width: 30, height: 30, borderRadius: 7, display: "inline-flex", alignItems: "center",
|
||||
justifyContent: "center", background: iconBg(mid), color: "#fff", fontWeight: 700, fontSize: 15, flexShrink: 0 }}>
|
||||
{(model.name || mid || "?").charAt(0).toUpperCase()}
|
||||
</span>
|
||||
<div style={{ minWidth: 0, flex: 1 }}>
|
||||
<div style={{ fontWeight: 600, fontSize: 13, lineHeight: 1.2, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
|
||||
{model.name || mid}
|
||||
</div>
|
||||
<code style={{ fontSize: 11, color: "var(--ant-color-text-secondary)", display: "block", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{mid}</code>
|
||||
</div>
|
||||
{isEnabled(pid, mid) && <span className={styles.groupCardLiveBadge} style={{ flexShrink: 0 }}><span className={styles.groupCardPulse} />Live</span>}
|
||||
</div>
|
||||
<div className={styles.groupCardActions}>
|
||||
<label className={styles.groupCardField} style={{ alignItems: "center", gap: 8 }}>
|
||||
<div style={{ display: "flex", flexWrap: "wrap", gap: 4, marginTop: 8 }}>
|
||||
{chips(model).map((tg) => (
|
||||
<span key={tg} className={styles.customTag} style={{ fontSize: 11, padding: "1px 7px", borderRadius: 999, background: "var(--ant-color-fill)" }}>{tg}</span>
|
||||
))}
|
||||
</div>
|
||||
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginTop: 8, paddingTop: 8, borderTop: "1px solid var(--ant-color-border)" }}>
|
||||
<label style={{ display: "flex", alignItems: "center", gap: 6, fontSize: 12 }}>
|
||||
<Switch checked={isEnabled(pid, mid)} onChange={(v) => toggle(pid, mid, !!v)} />
|
||||
{isEnabled(pid, mid) ? t("models.enabled", "启用") : t("models.disabled", "禁用")}
|
||||
</label>
|
||||
<button className={styles.groupCardActBtn} onClick={() => onOpenModels(provider)}>
|
||||
{t("models.settings", "管理")}
|
||||
</button>
|
||||
<button className={styles.groupCardActBtn} onClick={() => onOpenModels(provider)} style={{ fontSize: 12 }}>{t("models.settings", "管理")}</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user