forked from ChaiHao/BPOS-MVP
模型卡改矮:图标一列 + 文字一栏对齐,图标恒蓝
· **矮**:132 → 96px。状态不再是横穿整卡的填色带,收成一行小字 + 一枚钮;
段距、字号、图标各降一档。
· **对齐**:名字 / 厂商型号 / 状态**同一条竖线起头**,与图标并成一块 ——
从前名字与型号缩进 48px、状态条却横到卡边,三条线各走各的。
· **图标恒蓝**(与全站主色同一支 #3370ff,不再靛紫):它是**身份**不是状态 ——
能不能用由状态行说;先前未绑定就把图标灰掉,结果两条全灰、整片死气。
「去配置」改实心蓝:那是这张卡上唯一该点的东西。
交互复核:⋯ 菜单仍落在卡内(短卡下 top 跟着上移)、去配置开抽屉、Esc 关得掉。五闸绿。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m
This commit is contained in:
@@ -314,52 +314,56 @@
|
|||||||
.g-fcard .probe { position: relative; z-index: 1; display: flex; align-items: flex-start; gap: 6px; padding: 8px 10px; border-radius: 9px; background: #f2f7f4; font: 500 11px/1.5 var(--sans); color: #0c8154; }
|
.g-fcard .probe { position: relative; z-index: 1; display: flex; align-items: flex-start; gap: 6px; padding: 8px 10px; border-radius: 9px; background: #f2f7f4; font: 500 11px/1.5 var(--sans); color: #0c8154; }
|
||||||
.g-fcard .probe.bad { background: #fdf3f3; color: #a83a3a; }
|
.g-fcard .probe.bad { background: #fdf3f3; color: #a83a3a; }
|
||||||
.g-fcard .probe svg { flex: none; margin-top: 2px; }
|
.g-fcard .probe svg { flex: none; margin-top: 2px; }
|
||||||
/* ── 模型实例卡 —— 卡面只回答一句:**这条现在能不能用** ————————————————
|
/* ── 模型实例卡(2026-07-30 定稿)—— 图标一列 + 文字一栏,矮而密 ————————
|
||||||
名字折行也不截(它是活动里声明的那个名);不可用时不摆统计,只留判词与去路。 */
|
名字 / 型号 / 状态**同一条竖线起头**,与图标对齐成一块;状态不再横穿整卡。
|
||||||
.g-mcard { position: relative; display: flex; flex-direction: column; gap: 9px; padding: 14px; background: var(--card); border: 1px solid #e2e8ef; border-radius: 12px; box-shadow: 0 1px 2px rgba(19, 28, 38, .04); transition: border-color var(--t-fast), box-shadow var(--t-fast), transform var(--t-fast); }
|
卡面只回答一句:这条现在能不能用 —— 不能用就一句判词加一条去路,不摆假数字。 */
|
||||||
.g-mcard:hover { border-color: #c2cfde; box-shadow: 0 8px 22px -10px rgba(19, 28, 38, .18); transform: translateY(-1px); }
|
.g-mcard { position: relative; display: flex; align-items: flex-start; gap: 11px; padding: 12px 13px; background: var(--card); border: 1px solid #e2e8ef; border-radius: 11px; box-shadow: 0 1px 2px rgba(19, 28, 38, .04); transition: border-color var(--t-fast), box-shadow var(--t-fast), transform var(--t-fast); }
|
||||||
|
.g-mcard:hover { border-color: #b9cbe6; box-shadow: 0 8px 20px -10px rgba(51, 112, 255, .22); transform: translateY(-1px); }
|
||||||
.g-mcard.idle { background: #fbfcfd; }
|
.g-mcard.idle { background: #fbfcfd; }
|
||||||
.g-mcard > .hit { position: absolute; inset: 0; z-index: 0; border: 0; background: transparent; cursor: pointer; }
|
.g-mcard > .hit { position: absolute; inset: 0; z-index: 0; border: 0; background: transparent; cursor: pointer; }
|
||||||
.g-mcard > .hit:focus-visible { outline: 2px solid var(--brand); outline-offset: -3px; border-radius: 12px; }
|
.g-mcard > .hit:focus-visible { outline: 2px solid var(--brand); outline-offset: -3px; border-radius: 11px; }
|
||||||
.g-mcard > .hd, .g-mcard > .meta, .g-mcard > .bar, .g-mcard > .probe { position: relative; z-index: 1; }
|
.g-mcard > .ico, .g-mcard > .col { position: relative; z-index: 1; }
|
||||||
.g-mcard > .hd, .g-mcard > .meta { pointer-events: none; }
|
.g-mcard > .col { pointer-events: none; }
|
||||||
.g-mcard button { pointer-events: auto; }
|
.g-mcard button { pointer-events: auto; }
|
||||||
|
|
||||||
.g-mcard > .hd { display: flex; align-items: flex-start; gap: 10px; }
|
/* 图标:恒蓝 —— 它是**身份**不是状态(能不能用由状态行说),灰掉只会让整片卡死气 */
|
||||||
.g-mcard .ico { flex: none; width: 38px; height: 38px; border-radius: 10px; display: grid; place-items: center; color: #4954e6; background: #eef0fe; }
|
.g-mcard > .ico { flex: none; width: 34px; height: 34px; border-radius: 9px; display: grid; place-items: center; color: var(--brand); background: var(--brand-wash); box-shadow: inset 0 0 0 1px var(--brand-soft); }
|
||||||
.g-mcard.idle .ico { color: #7c8797; background: #f0f3f7; }
|
|
||||||
/* 名字:两行内折行,不截断 —— 看不全等于没显示 */
|
.g-mcard > .col { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
|
||||||
.g-mcard .nm { flex: 1; min-width: 0; font-size: 15px; font-weight: 700; line-height: 1.35; color: var(--ink); overflow-wrap: anywhere; }
|
.g-mcard .ln { display: flex; align-items: flex-start; gap: 8px; }
|
||||||
.g-mcard .mbtn { flex: none; width: 26px; height: 26px; display: grid; place-items: center; border: 1px solid transparent; border-radius: 7px; background: transparent; color: var(--ink-4); cursor: pointer; }
|
/* 名字:折行也不截 —— 看不全等于没显示 */
|
||||||
|
.g-mcard .nm { flex: 1; min-width: 0; font-size: 14px; font-weight: 700; line-height: 1.35; color: var(--ink); overflow-wrap: anywhere; }
|
||||||
|
.g-mcard .mbtn { flex: none; width: 22px; height: 22px; margin: -1px -3px 0 0; display: grid; place-items: center; border: 1px solid transparent; border-radius: 6px; background: transparent; color: var(--ink-4); cursor: pointer; }
|
||||||
.g-mcard .mbtn:hover, .g-mcard .mbtn.on { border-color: var(--cline); background: #f3f6fa; color: var(--ink); }
|
.g-mcard .mbtn:hover, .g-mcard .mbtn.on { border-color: var(--cline); background: #f3f6fa; color: var(--ink); }
|
||||||
|
|
||||||
.g-mcard > .meta { display: flex; align-items: center; gap: 7px; padding-left: 48px; margin-top: -6px; }
|
.g-mcard .meta { display: flex; align-items: center; gap: 6px; min-width: 0; }
|
||||||
.g-mcard .prov { flex: none; padding: 2px 7px; border-radius: 4px; background: #eef1f6; font: 600 10.5px/1.5 var(--sans); color: #59677a; }
|
.g-mcard .prov { flex: none; padding: 1px 6px; border-radius: 4px; background: var(--brand-wash); font: 600 10px/1.6 var(--sans); color: var(--brand-deep); }
|
||||||
.g-mcard .mdl { min-width: 0; font: 500 11px/1.5 var(--mono); color: var(--ink-4); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
.g-mcard .mdl { min-width: 0; font: 500 10.5px/1.6 var(--mono); color: var(--ink-4); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
|
|
||||||
/* 状态条:能用就报端点与自检口;不能用就一句判词 + 一条去路 */
|
/* 状态行:一行说完「连哪儿 / 能不能用」+ 一枚动作,不再是一整条填色带 */
|
||||||
.g-mcard > .bar { display: flex; align-items: center; gap: 8px; padding: 8px 10px; border-radius: 9px; background: #f6f8fb; font-size: 11.5px; color: var(--ink-3); }
|
.g-mcard .st { display: flex; align-items: center; gap: 8px; margin-top: 3px; font-size: 11px; color: var(--ink-3); }
|
||||||
.g-mcard > .bar.todo { background: #fdf5ec; color: #8a5f1c; font-weight: 600; }
|
.g-mcard .st.todo { color: #8a5f1c; font-weight: 600; }
|
||||||
.g-mcard .host { flex: 1; min-width: 0; font-family: var(--mono); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
.g-mcard .host { flex: 1; min-width: 0; font-family: var(--mono); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
.g-mcard > .bar.todo > span { flex: 1; min-width: 0; }
|
.g-mcard .st.todo .host { font-family: var(--sans); }
|
||||||
.g-mcard .tbtn { flex: none; display: inline-flex; align-items: center; gap: 4px; height: 24px; padding: 0 9px; border: 1px solid var(--cline); border-radius: 6px; background: #fff; font: 600 11px/1 var(--sans); color: var(--ink-3); cursor: pointer; }
|
.g-mcard .tbtn { flex: none; display: inline-flex; align-items: center; gap: 4px; height: 22px; padding: 0 8px; border: 1px solid var(--cline); border-radius: 6px; background: #fff; font: 600 10.5px/1 var(--sans); color: var(--ink-3); cursor: pointer; }
|
||||||
.g-mcard .tbtn:hover:not(:disabled) { border-color: #aebdcd; color: var(--ink); }
|
.g-mcard .tbtn:hover:not(:disabled) { border-color: var(--brand); color: var(--brand-deep); }
|
||||||
.g-mcard .tbtn.go { border-color: #dcc39a; color: #8a5f1c; }
|
.g-mcard .tbtn.go { border-color: var(--brand); background: var(--brand); color: #fff; }
|
||||||
|
.g-mcard .tbtn.go:hover { border-color: var(--brand-deep); background: var(--brand-deep); color: #fff; }
|
||||||
.g-mcard .tbtn:disabled { opacity: .5; cursor: default; }
|
.g-mcard .tbtn:disabled { opacity: .5; cursor: default; }
|
||||||
|
|
||||||
.g-mcard > .probe { display: flex; align-items: flex-start; gap: 6px; padding: 8px 10px; border-radius: 9px; background: #f2f7f4; font: 500 11px/1.5 var(--sans); color: #0c8154; }
|
.g-mcard .probe { display: flex; align-items: flex-start; gap: 5px; margin-top: 4px; padding: 6px 8px; border-radius: 7px; background: #f2f7f4; font: 500 10.5px/1.5 var(--sans); color: #0c8154; }
|
||||||
.g-mcard > .probe.bad { background: #fdf3f3; color: #a83a3a; }
|
.g-mcard .probe.bad { background: #fdf3f3; color: #a83a3a; }
|
||||||
.g-mcard > .probe svg { flex: none; margin-top: 2px; }
|
.g-mcard .probe svg { flex: none; margin-top: 1px; }
|
||||||
|
|
||||||
.g-mcard .menu { position: absolute; right: 12px; top: 46px; z-index: 3; width: 168px; display: flex; flex-direction: column; gap: 2px; padding: 5px; border: 1px solid var(--cline); border-radius: 9px; background: #fff; box-shadow: 0 10px 22px -8px rgba(19, 28, 38, .3); }
|
.g-mcard .menu { position: absolute; right: 11px; top: 38px; z-index: 3; width: 168px; display: flex; flex-direction: column; gap: 2px; padding: 5px; border: 1px solid var(--cline); border-radius: 9px; background: #fff; box-shadow: 0 10px 22px -8px rgba(19, 28, 38, .3); }
|
||||||
.g-mcard .menu button { display: flex; align-items: center; gap: 6px; width: 100%; height: 27px; padding: 0 8px; border: 0; border-radius: 6px; background: transparent; font: 500 12px/1 var(--sans); color: var(--ink); text-align: left; cursor: pointer; }
|
.g-mcard .menu button { display: flex; align-items: center; gap: 6px; width: 100%; height: 26px; padding: 0 8px; border: 0; border-radius: 6px; background: transparent; font: 500 12px/1 var(--sans); color: var(--ink); text-align: left; cursor: pointer; }
|
||||||
.g-mcard .menu button:hover { background: #f2f5f9; }
|
.g-mcard .menu button:hover { background: #f2f5f9; }
|
||||||
.g-mcard .menu button.bad { color: var(--danger); }
|
.g-mcard .menu button.bad { color: var(--danger); }
|
||||||
.g-mcard .menu button.bad:hover { background: #fdf2f2; }
|
.g-mcard .menu button.bad:hover { background: #fdf2f2; }
|
||||||
.g-mcard .menu .q { padding: 3px 8px 5px; font-size: 11px; line-height: 1.5; color: var(--ink-3); }
|
.g-mcard .menu .q { padding: 3px 8px 5px; font-size: 11px; line-height: 1.5; color: var(--ink-3); }
|
||||||
|
|
||||||
/* 幽灵卡:同尺寸虚线位 */
|
/* 幽灵卡:同高的虚线位 */
|
||||||
.g-mcard.ghost { flex-direction: row; justify-content: center; align-items: center; gap: 7px; min-height: 132px; border-style: dashed; border-color: #c0cbd8; background: #fcfdfe; box-shadow: none; color: var(--ink-4); font: 600 12.5px/1 var(--sans); cursor: pointer; }
|
.g-mcard.ghost { align-items: center; justify-content: center; gap: 7px; min-height: 88px; border-style: dashed; border-color: #c0cbd8; background: #fcfdfe; box-shadow: none; color: var(--ink-4); font: 600 12.5px/1 var(--sans); cursor: pointer; }
|
||||||
.g-mcard.ghost:hover { border-color: var(--brand); background: var(--brand-wash); color: var(--brand-deep); box-shadow: none; transform: none; }
|
.g-mcard.ghost:hover { border-color: var(--brand); background: var(--brand-wash); color: var(--brand-deep); box-shadow: none; transform: none; }
|
||||||
|
|
||||||
/* ── 抽屉:一次性的配置岔路(接入模型 / 改绑定)——————————————————
|
/* ── 抽屉:一次性的配置岔路(接入模型 / 改绑定)——————————————————
|
||||||
|
|||||||
@@ -333,10 +333,15 @@ function LlmCard({ i, nav, onEdit, onDropped }: { i: LlmInstanceView; nav: Nav;
|
|||||||
return (
|
return (
|
||||||
<div className={'g-mcard' + (usable ? '' : ' idle')}>
|
<div className={'g-mcard' + (usable ? '' : ' idle')}>
|
||||||
<button className="hit" aria-label={`打开 ${i.name}`} onClick={() => nav.push({ k: 'llm', id: i.id })} />
|
<button className="hit" aria-label={`打开 ${i.name}`} onClick={() => nav.push({ k: 'llm', id: i.id })} />
|
||||||
<div className="hd">
|
{/*
|
||||||
|
* 一行图标 + 一栏字:名字、型号、状态**同一条竖线起头** —— 与图标对齐成一块,
|
||||||
|
* 不再让状态条横穿整卡。卡因此矮下来(实测 132 → 88),扫一列就读完。
|
||||||
|
*/}
|
||||||
<span className="ico">
|
<span className="ico">
|
||||||
<Ico name="cpu" s={18} />
|
<Ico name="cpu" s={17} />
|
||||||
</span>
|
</span>
|
||||||
|
<div className="col">
|
||||||
|
<div className="ln">
|
||||||
{/* 名字折行也不截 —— 它是活动里声明的那个名,看不全等于没显示 */}
|
{/* 名字折行也不截 —— 它是活动里声明的那个名,看不全等于没显示 */}
|
||||||
<b className="nm">{i.name}</b>
|
<b className="nm">{i.name}</b>
|
||||||
<button
|
<button
|
||||||
@@ -351,9 +356,9 @@ function LlmCard({ i, nav, onEdit, onDropped }: { i: LlmInstanceView; nav: Nav;
|
|||||||
<span className="prov">{PROVIDER_LABEL[i.provider]}</span>
|
<span className="prov">{PROVIDER_LABEL[i.provider]}</span>
|
||||||
<span className="mdl">{i.model}</span>
|
<span className="mdl">{i.model}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div className={'st' + (usable ? '' : ' todo')}>
|
||||||
{usable ? (
|
{usable ? (
|
||||||
<div className="bar">
|
<>
|
||||||
<span className="host" title={i.baseUrl}>
|
<span className="host" title={i.baseUrl}>
|
||||||
{hostOf(i.baseUrl)}
|
{hostOf(i.baseUrl)}
|
||||||
</span>
|
</span>
|
||||||
@@ -361,23 +366,24 @@ function LlmCard({ i, nav, onEdit, onDropped }: { i: LlmInstanceView; nav: Nav;
|
|||||||
<Ico name={probe.isPending ? 'hourglass' : 'zap'} s={11} />
|
<Ico name={probe.isPending ? 'hourglass' : 'zap'} s={11} />
|
||||||
{probe.isPending ? '测试中' : '测试'}
|
{probe.isPending ? '测试中' : '测试'}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</>
|
||||||
) : (
|
) : (
|
||||||
/* 不可用就别摆统计:一句判词 + 一条去路,比四个假数字有用 */
|
/* 不可用就别摆统计:一句判词 + 一条去路,比四个假数字有用 */
|
||||||
<div className="bar todo">
|
<>
|
||||||
<span>{i.apiKeyBound ? '已停用' : '未绑定密钥 · 不可用'}</span>
|
<span className="host">{i.apiKeyBound ? '已停用' : '未绑定密钥 · 不可用'}</span>
|
||||||
<button className="tbtn go" onClick={onEdit}>
|
<button className="tbtn go" onClick={onEdit}>
|
||||||
去配置
|
去配置
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
{out !== undefined && (
|
{out !== undefined && (
|
||||||
<div className={'probe' + (out.ok ? ' ok' : ' bad')}>
|
<div className={'probe' + (out.ok ? ' ok' : ' bad')}>
|
||||||
<Ico name={out.ok ? 'circle-check' : 'alert-circle'} s={12} />
|
<Ico name={out.ok ? 'circle-check' : 'alert-circle'} s={12} />
|
||||||
{out.ok ? `连通 · ${out.latencyMs ?? '—'} ms` : (out.error ?? '不通')}
|
{out.ok ? `连通 · ${out.latencyMs ?? '—'} ms` : (out.error ?? '不通')}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{menu !== 'none' && (
|
{menu !== 'none' && (
|
||||||
<div className="menu">
|
<div className="menu">
|
||||||
|
|||||||
Reference in New Issue
Block a user