mirror of
https://github.com/Yidadaa/ChatGPT-Next-Web.git
synced 2025-09-08 01:06:58 +08:00
feat: refactor select model
This commit is contained in:
@@ -38,7 +38,6 @@ function _Chat() {
|
||||
const { isMobileScreen } = config;
|
||||
|
||||
const [showExport, setShowExport] = useState(false);
|
||||
const [showModelSelector, setShowModelSelector] = useState(false);
|
||||
|
||||
const inputRef = useRef<HTMLTextAreaElement>(null);
|
||||
const [userInput, setUserInput] = useState("");
|
||||
@@ -234,7 +233,6 @@ function _Chat() {
|
||||
setIsLoading,
|
||||
showChatSetting: setShowPromptModal,
|
||||
_setMsgRenderIndex,
|
||||
showModelSelector: setShowModelSelector,
|
||||
scrollDomToBottom,
|
||||
setAutoScroll,
|
||||
};
|
||||
@@ -256,23 +254,6 @@ function _Chat() {
|
||||
scrollDomToBottom,
|
||||
};
|
||||
|
||||
const currentModel = chatStore.currentSession().mask.modelConfig.model;
|
||||
const allModels = useAllModels();
|
||||
const models = useMemo(() => {
|
||||
const filteredModels = allModels.filter((m) => m.available);
|
||||
const defaultModel = filteredModels.find((m) => m.isDefault);
|
||||
|
||||
if (defaultModel) {
|
||||
const arr = [
|
||||
defaultModel,
|
||||
...filteredModels.filter((m) => m !== defaultModel),
|
||||
];
|
||||
return arr;
|
||||
} else {
|
||||
return filteredModels;
|
||||
}
|
||||
}, [allModels]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`
|
||||
@@ -286,7 +267,6 @@ function _Chat() {
|
||||
setIsEditingMessage={setIsEditingMessage}
|
||||
setShowExport={setShowExport}
|
||||
isMobileScreen={isMobileScreen}
|
||||
showModelSelector={setShowModelSelector}
|
||||
/>
|
||||
|
||||
<ChatMessagePanel {...chatMessagePanelProps} />
|
||||
@@ -310,25 +290,6 @@ function _Chat() {
|
||||
{showPromptModal && (
|
||||
<SessionConfigModel onClose={() => setShowPromptModal(false)} />
|
||||
)}
|
||||
|
||||
{showModelSelector && (
|
||||
<Selector
|
||||
defaultSelectedValue={currentModel}
|
||||
items={models.map((m) => ({
|
||||
title: m.displayName,
|
||||
value: m.name,
|
||||
}))}
|
||||
onClose={() => setShowModelSelector(false)}
|
||||
onSelection={(s) => {
|
||||
if (s.length === 0) return;
|
||||
chatStore.updateCurrentSession((session) => {
|
||||
session.mask.modelConfig.model = s[0] as ModelType;
|
||||
session.mask.syncGlobalConfig = false;
|
||||
});
|
||||
showToast(s[0]);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@@ -19,15 +19,15 @@ import BreakIcon from "@/app/icons/eraserIcon.svg";
|
||||
import SettingsIcon from "@/app/icons/configIcon.svg";
|
||||
import ImageIcon from "@/app/icons/uploadImgIcon.svg";
|
||||
import AddCircleIcon from "@/app/icons/addCircle.svg";
|
||||
import BottomArrow from "@/app/icons/downArrowLgIcon.svg";
|
||||
|
||||
import Popover from "@/app/components/Popover";
|
||||
import ModelSelect from "./ModelSelect";
|
||||
|
||||
export interface Action {
|
||||
onClick: () => void;
|
||||
onClick?: () => void;
|
||||
text: string;
|
||||
isShow: boolean;
|
||||
pcRender?: () => JSX.Element;
|
||||
render?: (key: string) => JSX.Element;
|
||||
icon?: JSX.Element;
|
||||
placement: "left" | "right";
|
||||
}
|
||||
@@ -39,7 +39,6 @@ export function ChatActions(props: {
|
||||
showChatSetting: () => void;
|
||||
scrollToBottom: () => void;
|
||||
showPromptHints: () => void;
|
||||
showModelSelector: (show: boolean) => void;
|
||||
hitBottom: boolean;
|
||||
uploading: boolean;
|
||||
isMobileScreen: boolean;
|
||||
@@ -101,15 +100,9 @@ export function ChatActions(props: {
|
||||
placement: "left",
|
||||
},
|
||||
{
|
||||
onClick: () => props.showModelSelector(true),
|
||||
text: currentModel,
|
||||
isShow: true,
|
||||
pcRender: () => (
|
||||
<div className="flex items-center justify-center gap-1 cursor-pointer rounded-chat-model-select pl-3 pr-2.5 py-2 font-common leading-4 bg-chat-actions-select-model">
|
||||
{currentModel}
|
||||
<BottomArrow />
|
||||
</div>
|
||||
),
|
||||
isShow: !props.isMobileScreen,
|
||||
render: (key: string) => <ModelSelect key={key} />,
|
||||
placement: "left",
|
||||
},
|
||||
{
|
||||
@@ -182,7 +175,7 @@ export function ChatActions(props: {
|
||||
icon: <SettingsIcon />,
|
||||
placement: "right",
|
||||
},
|
||||
] as const;
|
||||
];
|
||||
|
||||
if (props.isMobileScreen) {
|
||||
const content = (
|
||||
@@ -226,12 +219,8 @@ export function ChatActions(props: {
|
||||
{actions
|
||||
.filter((v) => v.placement === "left" && v.isShow)
|
||||
.map((act, ind) => {
|
||||
if (act.pcRender) {
|
||||
return (
|
||||
<div key={act.text} onClick={act.onClick}>
|
||||
{act.pcRender()}
|
||||
</div>
|
||||
);
|
||||
if (act.render) {
|
||||
return act.render(act.text);
|
||||
}
|
||||
return (
|
||||
<Popover
|
||||
|
@@ -12,16 +12,10 @@ export interface ChatHeaderProps {
|
||||
isMobileScreen: boolean;
|
||||
setIsEditingMessage: (v: boolean) => void;
|
||||
setShowExport: (v: boolean) => void;
|
||||
showModelSelector: (v: boolean) => void;
|
||||
}
|
||||
|
||||
export default function ChatHeader(props: ChatHeaderProps) {
|
||||
const {
|
||||
isMobileScreen,
|
||||
setIsEditingMessage,
|
||||
setShowExport,
|
||||
showModelSelector,
|
||||
} = props;
|
||||
const { isMobileScreen, setIsEditingMessage, setShowExport } = props;
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
@@ -78,7 +72,7 @@ export default function ChatHeader(props: ChatHeaderProps) {
|
||||
{isMobileScreen ? (
|
||||
<div
|
||||
className="flex items-center gap-1 cursor-pointer"
|
||||
onClick={() => showModelSelector(true)}
|
||||
onClick={() => {}}
|
||||
>
|
||||
{currentModel}
|
||||
<BottomArrow />
|
||||
|
@@ -35,7 +35,6 @@ export interface ChatInputPanelProps {
|
||||
setIsLoading: (value: boolean) => void;
|
||||
showChatSetting: (value: boolean) => void;
|
||||
_setMsgRenderIndex: (value: number) => void;
|
||||
showModelSelector: (value: boolean) => void;
|
||||
setAutoScroll: (value: boolean) => void;
|
||||
scrollDomToBottom: () => void;
|
||||
}
|
||||
@@ -64,7 +63,6 @@ export default forwardRef<ChatInputPanelInstance, ChatInputPanelProps>(
|
||||
_setMsgRenderIndex,
|
||||
hitBottom,
|
||||
inputRows,
|
||||
showModelSelector,
|
||||
setAutoScroll,
|
||||
scrollDomToBottom,
|
||||
} = props;
|
||||
@@ -226,7 +224,6 @@ export default forwardRef<ChatInputPanelInstance, ChatInputPanelProps>(
|
||||
`}
|
||||
>
|
||||
<ChatActions
|
||||
showModelSelector={showModelSelector}
|
||||
uploadImage={uploadImage}
|
||||
setAttachImages={setAttachImages}
|
||||
setUploading={setUploading}
|
||||
|
147
app/containers/Chat/components/ModelSelect.tsx
Normal file
147
app/containers/Chat/components/ModelSelect.tsx
Normal file
@@ -0,0 +1,147 @@
|
||||
import Popover from "@/app/components/Popover";
|
||||
import React, { useMemo, useRef } from "react";
|
||||
import useRelativePosition, {
|
||||
Orientation,
|
||||
} from "@/app/hooks/useRelativePosition";
|
||||
|
||||
import Selected from "@/app/icons/selectedIcon.svg";
|
||||
import { useChatStore } from "@/app/store/chat";
|
||||
import { useAllModels } from "@/app/utils/hooks";
|
||||
import { ModelType, useAppConfig } from "@/app/store/config";
|
||||
import { showToast } from "@/app/components/ui-lib";
|
||||
import BottomArrow from "@/app/icons/downArrowLgIcon.svg";
|
||||
import BottomArrowMobile from "@/app/icons/bottomArrow.svg";
|
||||
|
||||
const ModelSelect = () => {
|
||||
const config = useAppConfig();
|
||||
const { isMobileScreen } = config;
|
||||
const chatStore = useChatStore();
|
||||
const currentModel = chatStore.currentSession().mask.modelConfig.model;
|
||||
const allModels = useAllModels();
|
||||
const models = useMemo(() => {
|
||||
const filteredModels = allModels.filter((m) => m.available);
|
||||
const defaultModel = filteredModels.find((m) => m.isDefault);
|
||||
|
||||
if (defaultModel) {
|
||||
const arr = [
|
||||
defaultModel,
|
||||
...filteredModels.filter((m) => m !== defaultModel),
|
||||
];
|
||||
return arr;
|
||||
} else {
|
||||
return filteredModels;
|
||||
}
|
||||
}, [allModels]);
|
||||
|
||||
const rootRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const { position, getRelativePosition } = useRelativePosition({
|
||||
delay: 0,
|
||||
});
|
||||
|
||||
const contentRef = useMemo<{ current: HTMLDivElement | null }>(() => {
|
||||
return {
|
||||
current: null,
|
||||
};
|
||||
}, []);
|
||||
const selectedItemRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const autoScrollToSelectedModal = () => {
|
||||
window.setTimeout(() => {
|
||||
const distanceToParent = selectedItemRef.current?.offsetTop || 0;
|
||||
const childHeight = selectedItemRef.current?.offsetHeight || 0;
|
||||
const parentHeight = contentRef.current?.offsetHeight || 0;
|
||||
const distanceToParentCenter =
|
||||
distanceToParent + childHeight / 2 - parentHeight / 2;
|
||||
|
||||
if (distanceToParentCenter > 0 && contentRef.current) {
|
||||
contentRef.current.scrollTop = distanceToParentCenter;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const content = (
|
||||
<div
|
||||
className={` flex flex-col gap-1 overflow-y-auto overflow-x-hidden relative h-[100%]`}
|
||||
>
|
||||
{models?.map((o) => (
|
||||
<div
|
||||
key={o.displayName}
|
||||
className={`flex items-center px-3 py-2 gap-3 rounded-action-btn hover:bg-select-option-hovered cursor-pointer`}
|
||||
onClick={() => {
|
||||
chatStore.updateCurrentSession((session) => {
|
||||
session.mask.modelConfig.model = o.name as ModelType;
|
||||
session.mask.syncGlobalConfig = false;
|
||||
});
|
||||
showToast(o.name);
|
||||
}}
|
||||
ref={currentModel === o.name ? selectedItemRef : undefined}
|
||||
>
|
||||
<div className={`flex-1`}>{o.name}</div>
|
||||
<div
|
||||
className={currentModel === o.name ? "opacity-100" : "opacity-0"}
|
||||
>
|
||||
<Selected />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
if (isMobileScreen) {
|
||||
return (
|
||||
<Popover
|
||||
content={content}
|
||||
trigger="click"
|
||||
noArrow
|
||||
placement={
|
||||
position?.poi.relativePosition[1] !== Orientation.bottom ? "lb" : "lt"
|
||||
}
|
||||
popoverClassName="border border-select-popover rounded-lg shadow-select-popover-shadow w-actions-popover bg-select-popover-panel max-h-chat-actions-select-model-popover"
|
||||
onShow={(e) => {
|
||||
if (e) {
|
||||
autoScrollToSelectedModal();
|
||||
getRelativePosition(rootRef.current!, "");
|
||||
}
|
||||
}}
|
||||
getPopoverPanelRef={(ref) => (contentRef.current = ref.current)}
|
||||
>
|
||||
<div className="flex items-center gap-1 cursor-pointer" ref={rootRef}>
|
||||
{currentModel}
|
||||
<BottomArrowMobile />
|
||||
</div>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Popover
|
||||
content={content}
|
||||
trigger="click"
|
||||
noArrow
|
||||
placement={
|
||||
position?.poi.relativePosition[1] !== Orientation.bottom ? "lb" : "lt"
|
||||
}
|
||||
popoverClassName="border border-select-popover rounded-lg shadow-select-popover-shadow w-actions-popover bg-select-popover-panel max-h-chat-actions-select-model-popover"
|
||||
onShow={(e) => {
|
||||
if (e) {
|
||||
autoScrollToSelectedModal();
|
||||
getRelativePosition(rootRef.current!, "");
|
||||
}
|
||||
}}
|
||||
getPopoverPanelRef={(ref) => (contentRef.current = ref.current)}
|
||||
>
|
||||
<div
|
||||
className="flex items-center justify-center gap-1 cursor-pointer rounded-chat-model-select pl-3 pr-2.5 py-2 font-common leading-4 bg-chat-actions-select-model hover:bg-chat-actions-select-model-hover"
|
||||
ref={rootRef}
|
||||
>
|
||||
<div className="line-clamp-1 max-w-chat-actions-select-model">
|
||||
{currentModel}
|
||||
</div>
|
||||
<BottomArrow />
|
||||
</div>
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
|
||||
export default ModelSelect;
|
@@ -23,7 +23,7 @@ import LogIcon from "@/app/icons/logIcon.svg";
|
||||
|
||||
import MenuLayout from "@/app/components/MenuLayout";
|
||||
import Panel from "./ChatPanel";
|
||||
import Confirm from "@/app/components/Confirm";
|
||||
import Modal from "@/app/components/Modal";
|
||||
import HoverPopover from "@/app/components/HoverPopover";
|
||||
|
||||
export function SessionItem(props: {
|
||||
@@ -119,7 +119,12 @@ export function SessionItem(props: {
|
||||
align={props.isMobileScreen ? "end" : "start"}
|
||||
>
|
||||
<div
|
||||
className={`!absolute top-[50%] translate-y-[-50%] right-3 pointer-events-none opacity-0 group-hover/chat-menu-list:pointer-events-auto group-hover/chat-menu-list:opacity-100`}
|
||||
className={`
|
||||
!absolute top-[50%] translate-y-[-50%] right-3 pointer-events-none opacity-0
|
||||
group-hover/chat-menu-list:pointer-events-auto
|
||||
group-hover/chat-menu-list:opacity-100
|
||||
hover:bg-select-hover rounded-chat-img
|
||||
`}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</div>
|
||||
@@ -239,7 +244,7 @@ export default MenuLayout(function SessionList(props) {
|
||||
}}
|
||||
onDelete={async () => {
|
||||
if (
|
||||
await Confirm.show({
|
||||
await Modal.warn({
|
||||
okText: Locale.ChatItem.DeleteOkBtn,
|
||||
cancelText: Locale.ChatItem.DeleteCancelBtn,
|
||||
title: Locale.ChatItem.DeleteTitle,
|
||||
|
Reference in New Issue
Block a user