feat: refactor select model

This commit is contained in:
butterfly
2024-04-29 16:29:47 +08:00
parent c34b8ab919
commit 8c28c408d8
18 changed files with 501 additions and 239 deletions

View File

@@ -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>
);
}

View File

@@ -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

View File

@@ -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 />

View File

@@ -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}

View 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;

View File

@@ -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,