chore(web): 前端质量检查 - 规范模型编辑格式并消除回归测试缩进依赖

This commit is contained in:
ddCat
2026-09-06 07:42:02 +08:00
parent d920649387
commit df94c3bdf1
11 changed files with 305 additions and 136 deletions

View File

@@ -7,12 +7,18 @@
}
.choice-browser-panels {
display: grid;
grid-template-columns: minmax(0, 1.35fr) minmax(0, .65fr);
grid-template-columns: minmax(0, 1.35fr) minmax(0, 0.65fr);
overflow: hidden;
border: 1px solid var(--workspace-border);
border-radius: var(--r-md);
}
.choice-browser-list { height: 248px; overflow-y: auto; overscroll-behavior: contain; padding: 4px; scrollbar-gutter: stable; }
.choice-browser-list {
height: 248px;
overflow-y: auto;
overscroll-behavior: contain;
padding: 4px;
scrollbar-gutter: stable;
}
.choice-browser-option {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(0, 120px) 16px;
@@ -23,26 +29,105 @@
border-radius: var(--r-sm);
cursor: pointer;
}
.choice-browser-option:hover { background: var(--surface-hover); }
.choice-browser-option[data-selected="true"] { background: var(--workspace-accent-soft); box-shadow: inset 0 0 0 1px var(--workspace-border-strong); }
.choice-browser-option:has(input:disabled) { opacity: .5; cursor: not-allowed; }
.choice-browser-option:has(input:focus-visible) { outline: 2px solid var(--ring); outline-offset: -2px; }
.choice-browser-option input { accent-color: var(--workspace-accent); width: 14px; height: 14px; }
.choice-browser-main { display: grid; min-width: 0; gap: 2px; }
.choice-browser-main strong { font-size: var(--fs-caption); font-weight: 600; overflow-wrap: anywhere; }
.choice-browser-main > span, .choice-browser-vendor { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: var(--fs-label); color: var(--muted-foreground); }
.choice-browser-main > span { font-family: monospace; }
.choice-browser-detail { min-width: 0; max-height: 248px; overflow-y: auto; overscroll-behavior: contain; padding: 16px; border-left: 1px solid var(--workspace-border); }
.choice-browser-detail > span { display: block; margin-bottom: 4px; color: var(--muted-foreground); font-size: var(--fs-label); }
.choice-browser-detail > strong { font-size: var(--fs-caption); overflow-wrap: anywhere; }
.choice-browser-detail dl { display: grid; gap: 8px; margin-top: 12px; font-size: var(--fs-caption); }
.choice-browser-detail dl > div { display: grid; grid-template-columns: 4em minmax(0, 1fr); gap: 8px; }
.choice-browser-detail dt, .choice-browser-detail p { color: var(--muted-foreground); }
.choice-browser-detail dd, .choice-browser-detail p { margin: 0; overflow-wrap: anywhere; }
.choice-browser-state { display: grid; min-height: 100px; place-items: center; color: var(--muted-foreground); font-size: var(--fs-caption); padding: 16px; }
@container (max-width: 620px) {
.choice-browser-panels { grid-template-columns: minmax(0, 1fr); }
.choice-browser-detail { border-left: 0; border-top: 1px solid var(--workspace-border); }
.choice-browser-option { grid-template-columns: minmax(0, 1fr) 16px; }
.choice-browser-vendor { display: none; }
.choice-browser-option:hover {
background: var(--surface-hover);
}
.choice-browser-option[data-selected="true"] {
background: var(--workspace-accent-soft);
box-shadow: inset 0 0 0 1px var(--workspace-border-strong);
}
.choice-browser-option:has(input:disabled) {
opacity: 0.5;
cursor: not-allowed;
}
.choice-browser-option:has(input:focus-visible) {
outline: 2px solid var(--ring);
outline-offset: -2px;
}
.choice-browser-option input {
accent-color: var(--workspace-accent);
width: 14px;
height: 14px;
}
.choice-browser-main {
display: grid;
min-width: 0;
gap: 2px;
}
.choice-browser-main strong {
font-size: var(--fs-caption);
font-weight: 600;
overflow-wrap: anywhere;
}
.choice-browser-main > span,
.choice-browser-vendor {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: var(--fs-label);
color: var(--muted-foreground);
}
.choice-browser-main > span {
font-family: monospace;
}
.choice-browser-detail {
min-width: 0;
max-height: 248px;
overflow-y: auto;
overscroll-behavior: contain;
padding: 16px;
border-left: 1px solid var(--workspace-border);
}
.choice-browser-detail > span {
display: block;
margin-bottom: 4px;
color: var(--muted-foreground);
font-size: var(--fs-label);
}
.choice-browser-detail > strong {
font-size: var(--fs-caption);
overflow-wrap: anywhere;
}
.choice-browser-detail dl {
display: grid;
gap: 8px;
margin-top: 12px;
font-size: var(--fs-caption);
}
.choice-browser-detail dl > div {
display: grid;
grid-template-columns: 4em minmax(0, 1fr);
gap: 8px;
}
.choice-browser-detail dt,
.choice-browser-detail p {
color: var(--muted-foreground);
}
.choice-browser-detail dd,
.choice-browser-detail p {
margin: 0;
overflow-wrap: anywhere;
}
.choice-browser-state {
display: grid;
min-height: 100px;
place-items: center;
color: var(--muted-foreground);
font-size: var(--fs-caption);
padding: 16px;
}
@container (max-width: 620px) {
.choice-browser-panels {
grid-template-columns: minmax(0, 1fr);
}
.choice-browser-detail {
border-left: 0;
border-top: 1px solid var(--workspace-border);
}
.choice-browser-option {
grid-template-columns: minmax(0, 1fr) 16px;
}
.choice-browser-vendor {
display: none;
}
}

View File

@@ -42,24 +42,12 @@ export function ChoiceBrowser({
const [query, setQuery] = useState("");
const selected = options.find((option) => option.value === value);
const normalizedQuery = query.trim().toLowerCase();
const visible = options.filter((option) =>
[option.label, option.subtitle, option.vendor, option.searchText].join(" ").toLowerCase().includes(normalizedQuery),
);
const visible = options.filter((option) => [option.label, option.subtitle, option.vendor, option.searchText].join(" ").toLowerCase().includes(normalizedQuery));
const unavailable = disabled || loading || Boolean(error);
return (
<div className="choice-browser">
<Input
id={id}
{...aria}
allowClear
aria-label={`搜索${label}`}
disabled={unavailable}
prefix={<Search className="size-3.5" aria-hidden="true" />}
placeholder={placeholder}
value={query}
onChange={(event) => setQuery(event.target.value)}
/>
<Input id={id} {...aria} allowClear aria-label={`搜索${label}`} disabled={unavailable} prefix={<Search className="size-3.5" aria-hidden="true" />} placeholder={placeholder} value={query} onChange={(event) => setQuery(event.target.value)} />
{loading || error || !options.length ? (
<div className="choice-browser-state" role={error ? "alert" : "status"}>
{loading ? "正在读取可选项…" : error || "暂无可用选项"}
@@ -76,14 +64,7 @@ export function ChoiceBrowser({
<span className="choice-browser-vendor" title={option.vendor}>
{option.vendor}
</span>
<input
type="radio"
name={name}
value={option.value}
checked={option.value === value}
disabled={unavailable || option.disabled}
onChange={() => onChange?.(option.value)}
/>
<input type="radio" name={name} value={option.value} checked={option.value === value} disabled={unavailable || option.disabled} onChange={() => onChange?.(option.value)} />
</label>
))}
{!visible.length && (

View File

@@ -1,23 +1,103 @@
.model-editor-modal .ant-modal-container { display: flex; flex-direction: column; height: min(880px, calc(100dvh - 48px)); }
.model-editor-modal .ant-modal-header, .model-editor-modal .ant-modal-footer { padding: 18px 24px; flex-shrink: 0; }
.model-editor-modal .ant-modal-footer { border-top: 1px solid var(--workspace-border); }
.model-editor-modal .ant-modal-body { display: flex; flex-direction: column; overflow: hidden; }
.model-editor-modal .ant-modal-body > .ant-form { display: flex; flex-direction: column; height: 100%; min-height: 0; }
.model-editor-modal .ant-tabs { display: flex; flex: 1; min-height: 0; }
.model-editor-modal .ant-tabs-nav { flex-shrink: 0; margin: 0; padding-inline: 24px; }
.model-editor-modal .ant-tabs-body-holder { min-height: 0; overflow-y: auto; overscroll-behavior: contain; scrollbar-gutter: stable; }
.model-editor-modal .ant-modal-body > .model-editor-panel { flex: 1; overflow-y: auto; overscroll-behavior: contain; scrollbar-gutter: stable; }
.model-editor-panel { display: grid; gap: 16px; padding: 20px 24px; min-width: 0; }
.model-editor-panel .ant-form-item { margin-bottom: 0; }
.model-editor-connection-fields > .ant-form-item { grid-column: auto; min-width: 0; }
.model-editor-subtitle { margin: 4px 0 0; color: var(--muted-foreground); font-size: var(--fs-caption); font-weight: 400; }
.model-editor-section { display: grid; gap: 12px; min-width: 0; }
.model-editor-section + .model-editor-section { border-top: 1px solid var(--workspace-border); padding-top: 16px; }
.model-editor-section h2 { margin: 0; font-size: var(--fs-body); font-weight: 600; }
.model-editor-footer { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.model-editor-footer-actions { display: flex; gap: 8px; margin-left: auto; }
@media (max-width: 600px) {
.model-editor-modal .ant-modal-container { height: calc(100dvh - 24px); }
.model-editor-panel { padding: 16px; }
.model-editor-modal .ant-modal-header, .model-editor-modal .ant-modal-footer { padding: 16px; }
.model-editor-modal .ant-modal-container {
display: flex;
flex-direction: column;
height: min(880px, calc(100dvh - 48px));
}
.model-editor-modal .ant-modal-header,
.model-editor-modal .ant-modal-footer {
padding: 18px 24px;
flex-shrink: 0;
}
.model-editor-modal .ant-modal-footer {
border-top: 1px solid var(--workspace-border);
}
.model-editor-modal .ant-modal-body {
display: flex;
flex-direction: column;
overflow: hidden;
}
.model-editor-modal .ant-modal-body > .ant-form {
display: flex;
flex-direction: column;
height: 100%;
min-height: 0;
}
.model-editor-modal .ant-tabs {
display: flex;
flex: 1;
min-height: 0;
}
.model-editor-modal .ant-tabs-nav {
flex-shrink: 0;
margin: 0;
padding-inline: 24px;
}
.model-editor-modal .ant-tabs-body-holder {
min-height: 0;
overflow-y: auto;
overscroll-behavior: contain;
scrollbar-gutter: stable;
}
.model-editor-modal .ant-modal-body > .model-editor-panel {
flex: 1;
overflow-y: auto;
overscroll-behavior: contain;
scrollbar-gutter: stable;
}
.model-editor-panel {
display: grid;
gap: 16px;
padding: 20px 24px;
min-width: 0;
}
.model-editor-panel .ant-form-item {
margin-bottom: 0;
}
.model-editor-connection-fields > .ant-form-item {
grid-column: auto;
min-width: 0;
}
.model-editor-subtitle {
margin: 4px 0 0;
color: var(--muted-foreground);
font-size: var(--fs-caption);
font-weight: 400;
}
.model-editor-section {
display: grid;
gap: 12px;
min-width: 0;
}
.model-editor-section + .model-editor-section {
border-top: 1px solid var(--workspace-border);
padding-top: 16px;
}
.model-editor-section h2 {
margin: 0;
font-size: var(--fs-body);
font-weight: 600;
}
.model-editor-footer {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
flex-wrap: wrap;
}
.model-editor-footer-actions {
display: flex;
gap: 8px;
margin-left: auto;
}
@media (max-width: 600px) {
.model-editor-modal .ant-modal-container {
height: calc(100dvh - 24px);
}
.model-editor-panel {
padding: 16px;
}
.model-editor-modal .ant-modal-header,
.model-editor-modal .ant-modal-footer {
padding: 16px;
}
}

View File

@@ -27,14 +27,7 @@ export function ModelEditorModal({
admin?: boolean;
children?: ReactNode | ((tabs: ReactNode) => ReactNode);
}) {
const tabs = items?.length ? (
<Tabs
activeKey={activeKey}
onChange={onTabChange}
animated={false}
items={items.map((item) => ({ ...item, forceRender: true, children: <div className="model-editor-panel">{item.children}</div> }))}
/>
) : null;
const tabs = items?.length ? <Tabs activeKey={activeKey} onChange={onTabChange} animated={false} items={items.map((item) => ({ ...item, forceRender: true, children: <div className="model-editor-panel">{item.children}</div> }))} /> : null;
const content = typeof children === "function" ? children(tabs) : children || tabs;
return (

View File

@@ -1,7 +1,11 @@
import { ChoiceBrowser } from "./choice-browser";
import type { ModelProtocolDefinition, ProtocolCapability } from "@/lib/model-protocols";
export function ModelProtocolBrowser({ capability, protocols, ...props }: {
export function ModelProtocolBrowser({
capability,
protocols,
...props
}: {
capability?: ProtocolCapability;
protocols: ModelProtocolDefinition[];
value?: string;