fix(model-picker): 模型选择 - 限制弹窗宽度并改善描述可读性

This commit is contained in:
medivh
2026-09-29 18:52:02 +08:00
parent 3fb363aad2
commit dd500601ec
4 changed files with 57 additions and 62 deletions

View File

@@ -5,6 +5,13 @@ description: 当前项目已实现功能的手工验证清单与未确认边界
# 待测试
## 模型选择弹窗长描述与视口边界
- 已调整:共用 `ModelPicker` 的宽度由共享样式统一控制,移除按内容撑宽及触发器最小宽度约束;渠道描述支持换行,字号统一使用说明文字 token(12px),行高为 1.5,移除旧 9px / 10px 覆盖;模型名称与价格在空间不足时分行显示,长价格文本可换行。
- 调用方排查:画布与创作、短剧新建/章节提取/分镜制作/项目默认模型、Agent 对话与设置、提示词优化、批量生成、图片编辑/蒙版/图层拆解及视频分段等入口复用同一组件,均继承修复。
- [x] 现有模型分组、价格展示、共享样式、Agent 和分镜选择器共 20 项专项测试通过;不代表浏览器布局验收通过。
- [ ] 真实页面验证 500 字中英文描述、连续无空格文本、长价格、禁用原因与无描述选项;检查 1440px / 768px / 390px 视口、明暗主题、靠近浏览器左右边缘的弹窗、键盘切换和关闭。本次未完成浏览器验收。
## 分镜视频预览画幅
- 已调整:分镜制作右侧的视频播放器使用媒体原始比例和完整画面适配,避免共享预览样式的固定 16:9 与 `object-fit: cover` 裁剪竖屏视频。

View File

@@ -1,4 +1,4 @@
import { useEffect, useId, useLayoutEffect, useMemo, useRef, useState, type CSSProperties, type KeyboardEvent } from "react";
import { useEffect, useId, useMemo, useRef, useState, type KeyboardEvent } from "react";
import { Check, ChevronDown, Coins } from "lucide-react";
import { Popover } from "antd";
@@ -55,7 +55,6 @@ export function ModelPicker({
const theme = (canvasThemes[rawTheme as keyof typeof canvasThemes] ?? canvasThemes.dark) as CanvasTheme;
const [open, setOpen] = useState(false);
const [activeGroupKey, setActiveGroupKey] = useState<string | null>(null);
const [triggerWidth, setTriggerWidth] = useState<number | null>(null);
const menuRef = useRef<HTMLDivElement>(null);
const triggerRef = useRef<HTMLButtonElement>(null);
const options = useMemo(() => Array.from(new Set(selectableModelsByCapability(config, capability).filter(Boolean))), [capability, config]);
@@ -71,16 +70,6 @@ export function ModelPicker({
const [routeQuote, setRouteQuote] = useState<LogicalModelQuote | undefined>();
const creationVariant = variant === "creation";
useLayoutEffect(() => {
const trigger = triggerRef.current;
if (!trigger) return;
const updateTriggerWidth = () => setTriggerWidth(Math.ceil(trigger.getBoundingClientRect().width));
updateTriggerWidth();
const observer = new ResizeObserver(updateTriggerWidth);
observer.observe(trigger);
return () => observer.disconnect();
}, [className, fullWidth, showSelectedPrice, variant, value]);
useEffect(() => {
if (!showSelectedPrice || !creditsEnabled || !quoteRequest) {
setRouteQuote(undefined);
@@ -167,13 +156,7 @@ export function ModelPicker({
"canvas-model-picker-menu creation-model-picker-menu max-w-[calc(100vw-24px)]",
activeGroupKey === null ? "is-brand-list" : "is-model-list",
)}
style={
{
background: theme.node.panel,
color: theme.node.text,
"--canvas-model-picker-trigger-width": triggerWidth ? String(triggerWidth) + "px" : undefined,
} as CSSProperties
}
style={{ background: theme.node.panel, color: theme.node.text }}
role="listbox"
aria-label={placeholder}
onKeyDown={handleMenuKeyDown}
@@ -337,19 +320,21 @@ export function ModelLabel({
<span className="grid size-6 shrink-0 place-items-center rounded-md" style={{ background: theme.toolbar.itemHover }}>
<ModelIcon config={config} model={model} />
</span>
<span className="min-w-44 flex-1 overflow-hidden">
<span className="block min-w-0 truncate text-[var(--fs-label)] font-medium leading-none">{label || pickerModelDisplayName(config, model, showConfiguredModelName)}</span>
<span className={cn("canvas-model-picker-description mt-1 block truncate text-[var(--fs-tiny)]", showDescription && "is-visible")} style={{ color: theme.node.muted }}>
<span className="min-w-0 flex-1 overflow-hidden">
<span className="canvas-model-picker-option-heading">
<span className="canvas-model-picker-option-name text-[var(--fs-label)] font-medium leading-none" title={label || pickerModelDisplayName(config, model, showConfiguredModelName)}>{label || pickerModelDisplayName(config, model, showConfiguredModelName)}</span>
{showPrice ? (
<span className="canvas-model-picker-option-price">
{/* 候选模型展示自身价目;当前参数的精确报价只在选中后的触发器显示。 */}
<ModelPrice price={modelMenuPrice(config, model, capability, true, requirements)} />
</span>
) : null}
</span>
<span className={cn("canvas-model-picker-description mt-1 block", showDescription && "is-visible")} style={{ color: theme.node.muted }}>
{capabilitySummary}
</span>
<ModelTags tags={logicalCost?.tags} />
</span>
{showPrice ? (
<span className="ml-auto shrink-0 pl-2">
{/* 候选模型展示自身价目;当前参数的精确报价只在选中后的触发器显示。 */}
<ModelPrice price={modelMenuPrice(config, model, capability, true, requirements)} />
</span>
) : null}
{!creationVariant && meta.time ? (
<span className="shrink-0 rounded-full px-1.5 py-0.5 text-[var(--fs-tiny)] tabular-nums" style={{ background: theme.toolbar.itemHover, color: theme.node.muted }}>
{meta.time}

View File

@@ -66,7 +66,6 @@
}
.creation-model-picker-menu {
width: min(384px, calc(100vw - 24px)) !important;
max-height: min(460px, calc(100vh - 24px));
overflow-x: hidden;
overflow-y: auto;
@@ -141,11 +140,6 @@
font-size: 13px;
}
.creation-model-picker-menu .canvas-model-picker-option > span:first-child > span:nth-child(2) > span:nth-child(2) {
margin-top: 5px;
font-size: 10px;
}
.creation-model-picker.canvas-composer-model-picker {
display: inline-flex;
height: 48px;
@@ -222,9 +216,6 @@ html:not(.dark) .creation-model-picker.canvas-composer-model-picker[aria-expande
height: 34px;
}
.creation-model-picker-menu {
width: min(440px, calc(100vw - 96px)) !important;
}
.creation-model-picker.canvas-composer-model-picker { height: 42px; border-radius: 12px; padding: 0 11px; font-size: 12px; }
.creation-model-picker.canvas-composer-model-picker { width: min(150px, 44vw); }
}
@@ -309,11 +300,12 @@ html:not(.dark) .creation-model-picker.canvas-composer-model-picker[aria-expande
.canvas-model-picker-popover {
filter: none !important;
max-width: calc(100vw - 24px);
}
.canvas-model-picker-menu {
width: max-content !important;
min-width: max(var(--canvas-model-picker-trigger-width, 0px), 260px);
box-sizing: border-box;
min-width: 0;
max-width: calc(100vw - 24px);
max-height: min(420px, calc(100vh - 32px));
overflow-x: hidden;
@@ -375,17 +367,6 @@ html:not(.dark) .creation-model-picker.canvas-composer-model-picker[aria-expande
width: 100%;
}
.canvas-model-picker-popover .canvas-model-picker-option > span:first-child > span:nth-child(2) {
flex: 1 1 auto;
min-width: 0;
overflow: visible;
}
.canvas-model-picker-popover .canvas-model-picker-option > span:first-child > span:nth-child(2) > span {
overflow: visible;
text-overflow: clip;
}
.canvas-model-picker-option-check {
flex: 0 0 14px;
height: 14px;
@@ -541,7 +522,7 @@ html:not(.dark) .creation-chat-composer .creation-model-picker.canvas-composer-m
.creation-model-picker-surface .ant-popover-inner-content { padding: 0 !important; }
.creation-model-picker-menu { width: min(332px, calc(100vw - 24px)) !important; max-height: min(420px, calc(100vh - 24px)); padding: 7px !important; background: transparent !important; color: #f0f1f2 !important; }
.creation-model-picker-menu { max-height: min(420px, calc(100vh - 24px)); padding: 7px !important; background: transparent !important; color: #f0f1f2 !important; }
.creation-model-picker-heading { display: flex; align-items: center; gap: 6px; padding: 4px 7px 7px; color: #8e949d; font-size: 10px; }
.creation-model-picker-heading strong { color: #e6e8eb; font-weight: 500; }
.creation-model-picker-menu .canvas-model-picker-group-label { padding: 6px 7px 5px; font-size: 10px; }
@@ -549,7 +530,6 @@ html:not(.dark) .creation-chat-composer .creation-model-picker.canvas-composer-m
.creation-model-picker-menu .canvas-model-picker-option > span:first-child { gap: 8px; }
.creation-model-picker-menu .canvas-model-picker-option > span:first-child > span:first-child { width: 26px; height: 26px; border: 0; border-radius: 5px; background: rgba(255, 255, 255, .07) !important; }
.creation-model-picker-menu .canvas-model-picker-option > span:first-child > span:nth-child(2) > span:first-child { font-size: 11px; }
.creation-model-picker-menu .canvas-model-picker-option > span:first-child > span:nth-child(2) > span:nth-child(2) { margin-top: 3px; font-size: 9px; }
html:not(.dark) .creation-model-picker-surface { background: rgba(255, 255, 255, .94) !important; box-shadow: 0 16px 38px rgba(28, 32, 37, .12) !important; }
html:not(.dark) .creation-model-picker-menu { color: #727980 !important; }
@@ -648,10 +628,10 @@ html:not(.dark) .creation-home .creation-chat-composer .creation-model-picker.ca
}
/* 品牌/模型两级菜单是 ModelPicker 默认结构,不能只写在 creation variant 下。 */
.canvas-model-picker-menu.is-brand-list {
width: min(360px, calc(100vw - 24px)) !important;
width: min(360px, calc(100vw - 24px));
}
.canvas-model-picker-menu.is-model-list {
width: min(800px, calc(100vw - 24px)) !important;
width: min(800px, calc(100vw - 24px));
}
.canvas-model-picker-two-pane {
display: grid;
@@ -676,7 +656,7 @@ html:not(.dark) .creation-home .creation-chat-composer .creation-model-picker.ca
}
@media (max-width: 720px) {
.canvas-model-picker-menu:is(.is-brand-list, .is-model-list) {
width: calc(100vw - 24px) !important;
width: calc(100vw - 24px);
}
.canvas-model-picker-two-pane {
grid-template-columns: minmax(0, 1fr);
@@ -723,10 +703,33 @@ html:not(.dark) .creation-home .creation-chat-composer .creation-model-picker.ca
color: color-mix(in srgb, currentColor 52%, transparent);
font-size: 10px;
}
.canvas-model-picker-option-heading {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 6px 8px;
}
.canvas-model-picker-option-name {
flex: 1 1 10rem;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.canvas-model-picker-option-price {
min-width: 0;
max-width: 100%;
}
.canvas-model-picker-description {
height: 16px;
line-height: 16px;
font-size: var(--fs-caption);
min-height: 1.5em;
line-height: 1.5;
margin-top: 5px !important;
white-space: normal;
overflow-wrap: anywhere;
visibility: hidden;
opacity: 0;
}
@@ -761,6 +764,10 @@ html:not(.dark) .creation-home .creation-chat-composer .creation-model-picker.ca
border-radius: var(--r-sm);
background: color-mix(in srgb, var(--model-price-ink) 9%, transparent);
padding: 2px 5px;
max-width: 100%;
flex-wrap: wrap;
white-space: normal;
overflow-wrap: anywhere;
}
@media (prefers-reduced-motion: reduce) {
@@ -795,7 +802,6 @@ html:not(.dark) .creation-home .creation-chat-composer .creation-model-picker.ca
.app-user-workspace .creation-model-picker-menu.is-brand-list {
max-height: none !important;
overflow: visible !important;
width: min(360px, calc(100vw - 24px)) !important;
padding: 10px !important;
}
.app-user-workspace .creation-model-picker-menu .canvas-model-picker-brands { display: grid; gap: 5px; }
@@ -835,7 +841,6 @@ html:not(.dark) .creation-home .creation-chat-composer .creation-model-picker.ca
.creation-model-picker-surface .creation-model-picker-menu.is-brand-list {
max-height: none !important;
overflow: visible !important;
width: min(360px, calc(100vw - 24px)) !important;
padding: 10px !important;
}
.creation-model-picker-surface .canvas-model-picker-brands { display: grid; gap: 5px; }

View File

@@ -656,14 +656,12 @@
.app-user-workspace .creation-composer-stage .creation-empty-composer { min-width: 0; flex: 1 1 auto; }
.app-user-workspace .creation-composer-stage.is-agent-mode .creation-agent-entry { min-width: 0; flex: 1 1 auto; }
.creation-model-picker-surface .creation-model-picker-menu.is-brand-list { width: min(360px, calc(100vw - 24px)) !important; }
.creation-model-picker-surface .creation-model-picker-menu.is-model-list { width: min(720px, calc(100vw - 24px)) !important; }
.creation-model-picker-surface .creation-model-picker-menu.is-model-list .canvas-model-picker-two-pane { display: grid; grid-template-columns: 250px minmax(0, 1fr); gap: 14px; min-height: 0; align-items: start; }
.creation-model-picker-surface .creation-model-picker-menu.is-model-list .canvas-model-picker-brand-rail { display: grid; align-content: start; gap: 5px; border-right: 1px solid var(--workspace-border); padding-right: 14px; }
.creation-model-picker-surface .creation-model-picker-menu.is-model-list .canvas-model-picker-brand { width: 100%; }
.creation-model-picker-surface .creation-model-picker-menu.is-model-list .canvas-model-picker-model-pane { border: 0; margin: 0; padding: 0; }
@media (max-width: 720px) { .creation-floating-prompt { top: 8px; width: calc(100% - 24px); min-height: 60px; gap: 10px; padding: 8px 9px 8px 16px; border-radius: 19px; } .creation-floating-prompt input { font-size: 14px; } .creation-floating-prompt button { width: 40px; height: 40px; flex-basis: 40px; } .creation-model-picker-surface .creation-model-picker-menu:is(.is-brand-list, .is-model-list) { width: calc(100vw - 24px) !important; } .creation-model-picker-surface .creation-model-picker-menu.is-model-list .canvas-model-picker-two-pane { grid-template-columns: minmax(0, 1fr); min-height: 0; } .creation-model-picker-surface .creation-model-picker-menu.is-model-list .canvas-model-picker-brand-rail { display: none; } .app-user-workspace .creation-chat-composer.is-thread .creation-chat-dock .creation-mode-tabs { height: 30px; } .app-user-workspace .creation-chat-composer.is-thread .creation-chat-dock .creation-mode-button { height: 26px; min-height: 26px; padding: 0 6px; font-size: 10px; } }
@media (max-width: 720px) { .creation-floating-prompt { top: 8px; width: calc(100% - 24px); min-height: 60px; gap: 10px; padding: 8px 9px 8px 16px; border-radius: 19px; } .creation-floating-prompt input { font-size: 14px; } .creation-floating-prompt button { width: 40px; height: 40px; flex-basis: 40px; } .creation-model-picker-surface .creation-model-picker-menu.is-model-list .canvas-model-picker-two-pane { grid-template-columns: minmax(0, 1fr); min-height: 0; } .creation-model-picker-surface .creation-model-picker-menu.is-model-list .canvas-model-picker-brand-rail { display: none; } .app-user-workspace .creation-chat-composer.is-thread .creation-chat-dock .creation-mode-tabs { height: 30px; } .app-user-workspace .creation-chat-composer.is-thread .creation-chat-dock .creation-mode-button { height: 26px; min-height: 26px; padding: 0 6px; font-size: 10px; } }
/* Shared controls: one size system and one state vocabulary. */
.app-product-workspace :where(.ant-btn, .ant-input-affix-wrapper, .ant-select-selector, .ant-picker, .ant-input-number),