feat(canvas): 优化模型选择器和画布助手设置面板

- 模型选择器添加模糊匹配和图标显示支持OpenAI、Claude、Gemini
- 模型选择器下拉滚动条改为更窄的轻量样式
- 画布助手生图设置弹层调整为紧凑图像设置面板避免裁切问题
- 画布节点设置面板打开时隐藏悬浮工具条避免遮挡
This commit is contained in:
HouYunFei
2026-05-22 22:14:30 +08:00
parent 133861590e
commit cee08cb5a8
5 changed files with 51 additions and 6 deletions
@@ -404,7 +404,7 @@ function AssistantComposer({
{mode === "image" ? (
<>
<ModelPicker config={config} value={config.imageModel || config.model} onChange={(model) => onConfigChange("imageModel", model)} onMissingConfig={onMissingConfig} />
<CanvasImageSettingsPopover config={config} onConfigChange={onConfigChange} onMissingConfig={onMissingConfig} />
<CanvasImageSettingsPopover config={config} placement="topRight" getPopupContainer={() => document.body} onConfigChange={onConfigChange} onMissingConfig={onMissingConfig} />
</>
) : (
<ModelPicker config={config} value={config.textModel || config.model} onChange={(model) => onConfigChange("textModel", model)} onMissingConfig={onMissingConfig} />
@@ -152,7 +152,7 @@ function AspectIcon({ type, width, height, color }: { type: string; width: numbe
const boxHeight = ratio >= 1 ? Math.max(12, 28 / ratio) : 28;
return (
<span className="grid h-8 w-10 place-items-center">
<span className="rounded-md border-2" style={{ width: boxWidth, height: boxHeight, borderColor: color }} />
<span className="border-2" style={{ width: boxWidth, height: boxHeight, borderColor: color }} />
</span>
);
}
@@ -0,0 +1,29 @@
.dropdown :global(.ant-select-item) {
font-weight: 400;
min-height: 34px;
padding-inline: 10px 28px;
}
.dropdown :global(.ant-select-item-option-selected) {
font-weight: 500;
}
.dropdown :global(.rc-virtual-list-holder) {
scrollbar-width: none;
}
.dropdown :global(.rc-virtual-list-holder)::-webkit-scrollbar,
.dropdown :global(.rc-virtual-list-holder)::-webkit-scrollbar-button {
height: 0;
width: 0;
}
.dropdown :global(.rc-virtual-list-scrollbar) {
right: 4px !important;
width: 2px !important;
}
.dropdown :global(.rc-virtual-list-scrollbar-thumb) {
background: rgba(120, 113, 108, 0.42) !important;
border-radius: 999px !important;
}
+18 -3
View File
@@ -2,6 +2,7 @@
import { Select } from "antd";
import styles from "./model-picker.module.css";
import type { AiConfig } from "@/stores/use-config-store";
type ModelPickerProps = {
@@ -15,14 +16,15 @@ type ModelPickerProps = {
};
export function ModelPicker({ config, value, onChange, className, fullWidth = false, placeholder = "选择模型", onMissingConfig }: ModelPickerProps) {
const options = Array.from(new Set([value, ...config.models].filter(Boolean))).map((model) => ({ value: model, label: model }));
const options = Array.from(new Set([value, ...config.models].filter(Boolean))).map((model) => ({ value: model, label: <ModelLabel model={model} /> }));
const width = fullWidth ? "100%" : `min(${Math.max(156, (value || placeholder).length * 8 + 64)}px, 100%)`;
return (
<Select
showSearch
className={`canvas-control-select ${className || ""}`}
popupMatchSelectWidth={false}
classNames={{ popup: { root: styles.dropdown } }}
popupMatchSelectWidth
popupRender={(menu) => <div onMouseDown={(event) => event.stopPropagation()} onPointerDown={(event) => event.stopPropagation()}>{menu}</div>}
style={{ width, maxWidth: "100%", minWidth: 0, flexShrink: 1 }}
value={value || undefined}
@@ -35,7 +37,20 @@ export function ModelPicker({ config, value, onChange, className, fullWidth = fa
onClick={() => {
if (!options.length) onMissingConfig?.();
}}
filterOption={(input, option) => String(option?.label || "").toLowerCase().includes(input.toLowerCase())}
filterOption={(input, option) => String(option?.value || "").toLowerCase().includes(input.toLowerCase())}
/>
);
}
function ModelLabel({ model }: { model: string }) {
const icon = resolveModelIcon(model);
return <span className="flex min-w-0 items-center gap-2">{icon && <img src={icon} alt="" className="size-4 shrink-0" />}<span className="truncate">{model}</span></span>;
}
function resolveModelIcon(model: string) {
const name = model.toLowerCase();
if (name.includes("claude") || name.includes("anthropic")) return "/icons/claude.svg";
if (name.includes("gemini") || name.includes("google")) return "/icons/gemini.svg";
if (name.includes("gpt") || name.includes("openai")) return "/icons/openai.svg";
return "";
}