fix(model-picker): 模型选择 - 修复长列表溢出与渠道面板随动 (#706)

Co-authored-by: 天道酬秦 <tdcq@tiandaochouqindeMacBook-Air.local>
This commit is contained in:
qihang ma
2026-10-08 14:28:40 +08:00
committed by GitHub
parent 9ef10b7b2d
commit 9be3aa8235
3 changed files with 37 additions and 18 deletions

View File

@@ -1,4 +1,4 @@
import { useEffect, useId, useMemo, useRef, useState, type KeyboardEvent } from "react";
import { useEffect, useId, useLayoutEffect, useMemo, useRef, useState, type CSSProperties, type KeyboardEvent } from "react";
import { Check, ChevronDown, Coins } from "lucide-react";
import { Popover } from "antd";
@@ -54,6 +54,7 @@ export function ModelPicker({
const rawTheme = useActiveTheme();
const theme = (canvasThemes[rawTheme as keyof typeof canvasThemes] ?? canvasThemes.dark) as CanvasTheme;
const [open, setOpen] = useState(false);
const [availableHeight, setAvailableHeight] = useState(0);
const [activeGroupKey, setActiveGroupKey] = useState<string | null>(null);
const menuRef = useRef<HTMLDivElement>(null);
const triggerRef = useRef<HTMLButtonElement>(null);
@@ -70,6 +71,21 @@ export function ModelPicker({
const [routeQuote, setRouteQuote] = useState<LogicalModelQuote | undefined>();
const creationVariant = variant === "creation";
useLayoutEffect(() => {
if (!open) return;
const updateAvailableHeight = () => {
const { top, bottom } = triggerRef.current!.getBoundingClientRect();
setAvailableHeight(Math.max(top, window.innerHeight - bottom));
};
updateAvailableHeight();
window.addEventListener("resize", updateAvailableHeight);
window.addEventListener("scroll", updateAvailableHeight, true);
return () => {
window.removeEventListener("resize", updateAvailableHeight);
window.removeEventListener("scroll", updateAvailableHeight, true);
};
}, [open]);
useEffect(() => {
if (!showSelectedPrice || !creditsEnabled || !quoteRequest) {
setRouteQuote(undefined);
@@ -156,7 +172,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 }}
style={{ background: theme.node.panel, color: theme.node.text, "--canvas-model-picker-available-height": `${availableHeight}px` } as CSSProperties}
role="listbox"
aria-label={placeholder}
onKeyDown={handleMenuKeyDown}
@@ -185,7 +201,7 @@ export function ModelPicker({
</button>;
})}
</div>
{optionGroups.filter((group) => group.key === activeGroupKey).map((group) => <section key={group.key} className="canvas-model-picker-group canvas-model-picker-model-pane min-w-0 overflow-hidden">
{optionGroups.filter((group) => group.key === activeGroupKey).map((group) => <section key={group.key} className="canvas-model-picker-group canvas-model-picker-model-pane min-w-0">
<div className="grid min-w-0 gap-1">
{group.models.map((modelGroup) => {
const selected = modelGroup.models.includes(current);
@@ -243,6 +259,7 @@ export function ModelPicker({
onOpenChange={setPickerOpen}
trigger="click"
placement="bottomLeft"
align={{ overflow: { adjustY: true, shiftX: true } }}
arrow={false}
content={content}
classNames={{

View File

@@ -66,7 +66,6 @@
}
.creation-model-picker-menu {
max-height: min(460px, calc(100vh - 24px));
overflow-x: hidden;
overflow-y: auto;
padding: 8px !important;
@@ -305,9 +304,11 @@ html:not(.dark) .creation-model-picker.canvas-composer-model-picker[aria-expande
.canvas-model-picker-menu {
box-sizing: border-box;
display: flex;
flex-direction: column;
min-width: 0;
max-width: calc(100vw - 24px);
max-height: min(420px, calc(100vh - 32px));
max-height: min(460px, calc(100dvh - 24px), calc(var(--canvas-model-picker-available-height) - 24px));
overflow-x: hidden;
overflow-y: auto;
padding: 4px;
@@ -522,7 +523,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 { max-height: min(420px, calc(100vh - 24px)); padding: 7px !important; background: transparent !important; color: #f0f1f2 !important; }
.creation-model-picker-menu { 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; }
@@ -632,19 +633,26 @@ html:not(.dark) .creation-home .creation-chat-composer .creation-model-picker.ca
}
.canvas-model-picker-menu.is-model-list {
width: min(800px, calc(100vw - 24px));
overflow: hidden;
}
.canvas-model-picker-two-pane {
display: grid;
flex: 1 1 auto;
width: 100%;
grid-template-columns: 250px minmax(0, 1fr);
grid-template-rows: minmax(0, 1fr);
gap: 14px;
min-width: 0;
align-items: start;
min-height: 0;
overflow: hidden;
}
.canvas-model-picker-menu.is-model-list .canvas-model-picker-brand-rail {
display: grid;
min-height: 0;
align-content: start;
gap: 5px;
overflow-x: hidden;
overflow-y: auto;
border-right: 1px solid var(--workspace-border);
padding-right: 14px;
}
@@ -653,6 +661,9 @@ html:not(.dark) .creation-home .creation-chat-composer .creation-model-picker.ca
}
.canvas-model-picker-model-pane {
min-width: 0;
min-height: 0;
overflow-x: hidden;
overflow-y: auto;
}
@media (max-width: 720px) {
.canvas-model-picker-menu:is(.is-brand-list, .is-model-list) {
@@ -893,10 +904,8 @@ html:not(.dark) .creation-home .creation-chat-composer .creation-model-picker.ca
.app-user-workspace .creation-launchpad .creation-model-picker.canvas-composer-model-picker {
width: min(232px, 60cqw) !important;
}
/* 模型选择器:先选品牌,再选模型;品牌列表保持完整可见,不用内部滚动条。 */
/* 模型与渠道共用菜单高度约束,长列表在各自区域滚动。 */
.app-user-workspace .creation-model-picker-menu.is-brand-list {
max-height: none !important;
overflow: visible !important;
padding: 10px !important;
}
.app-user-workspace .creation-model-picker-menu .canvas-model-picker-brands { display: grid; gap: 5px; }
@@ -928,14 +937,7 @@ html:not(.dark) .creation-home .creation-chat-composer .creation-model-picker.ca
min-height: 32px !important;
height: 32px !important;
}
.creation-model-picker-surface .creation-model-picker-menu.is-model-list {
max-height: min(460px, calc(100vh - 24px)) !important;
overflow-x: hidden !important;
overflow-y: auto !important;
}
.creation-model-picker-surface .creation-model-picker-menu.is-brand-list {
max-height: none !important;
overflow: visible !important;
padding: 10px !important;
}
.creation-model-picker-surface .canvas-model-picker-brands { display: grid; gap: 5px; }

View File

@@ -659,7 +659,7 @@
.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-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-two-pane { display: grid; grid-template-columns: 250px minmax(0, 1fr); gap: 14px; min-height: 0; }
.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; }