From 5ea620631992f9e5ab78bfd19130dfe38e71bd78 Mon Sep 17 00:00:00 2001 From: butterfly Date: Mon, 29 Apr 2024 20:37:27 +0800 Subject: [PATCH] feat: select model done --- app/components/Modal/index.tsx | 100 +++++++++++--- app/containers/Chat/components/ChatHeader.tsx | 9 +- .../Chat/components/ModelSelect.tsx | 43 +++--- .../Chat/components/SessionItem.tsx | 124 ++++++++++++++++++ app/containers/Chat/index.tsx | 124 +----------------- app/locales/cn.ts | 1 + app/locales/en.ts | 1 + app/styles/globals.css | 4 + tailwind.config.js | 1 + 9 files changed, 242 insertions(+), 165 deletions(-) create mode 100644 app/containers/Chat/components/SessionItem.tsx diff --git a/app/components/Modal/index.tsx b/app/components/Modal/index.tsx index 9aea50510..b6d328291 100644 --- a/app/components/Modal/index.tsx +++ b/app/components/Modal/index.tsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React, { useLayoutEffect, useState } from "react"; import { createRoot } from "react-dom/client"; import * as AlertDialog from "@radix-ui/react-alert-dialog"; import Btn, { BtnProps } from "@/app/components/Btn"; @@ -13,7 +13,9 @@ export interface ModalProps { cancelText?: string; okBtnProps?: BtnProps; cancelBtnProps?: BtnProps; - content?: React.ReactNode; + content?: + | React.ReactNode + | ((handlers: { close: () => void }) => JSX.Element); title?: React.ReactNode; visible?: boolean; noFooter?: boolean; @@ -22,6 +24,8 @@ export interface ModalProps { closeble?: boolean; type?: "modal" | "bottom-drawer"; headerBordered?: boolean; + modelClassName?: string; + onOpen?: (v: boolean) => void; } export interface WarnProps @@ -34,8 +38,16 @@ export interface WarnProps | "onOk" | "okBtnProps" | "cancelBtnProps" + | "content" > { onOk?: () => Promise | void; + content?: React.ReactNode; +} + +export interface TriggerProps + extends Omit { + children: JSX.Element; + className?: string; } const baseZIndex = 150; @@ -56,9 +68,11 @@ const Modal = (props: ModalProps) => { cancelBtnProps, type = "modal", headerBordered, + modelClassName, + onOpen, } = props; - const [open, setOpen] = useState(false); + const [open, setOpen] = useState(!!visible); const mergeOpen = visible ?? open; @@ -67,6 +81,15 @@ const Modal = (props: ModalProps) => { onCancel?.(); }; + const handleOk = () => { + setOpen(false); + onOk?.(); + }; + + useLayoutEffect(() => { + onOpen?.(mergeOpen); + }, [mergeOpen]); + let layoutClassName = ""; let panelClassName = ""; let titleClassName = ""; @@ -74,11 +97,11 @@ const Modal = (props: ModalProps) => { switch (type) { case "bottom-drawer": - layoutClassName = - "fixed inset-0 flex flex-col item-start top-0 left-[50vw] translate-x-[-50%] md:"; - panelClassName = ""; - titleClassName = ""; - footerClassName = ""; + layoutClassName = "fixed inset-0 flex flex-col w-[100%] bottom-0"; + panelClassName = + "rounded-t-chat-model-select overflow-y-auto overflow-x-hidden"; + titleClassName = "px-4 py-3"; + footerClassName = "absolute w-[100%]"; break; case "modal": default: @@ -107,9 +130,9 @@ const Modal = (props: ModalProps) => { >
 
@@ -118,6 +141,11 @@ const Modal = (props: ModalProps) => { className={` flex items-center justify-between gap-3 font-common md:text-chat-header-title md:font-bold md:leading-5 + ${ + headerBordered + ? " border-b border-modal-header-bottom" + : "" + } ${titleClassName} `} > @@ -136,7 +164,15 @@ const Modal = (props: ModalProps) => { )} )} - {content} +
+ {typeof content === "function" + ? content({ + close: () => { + handleClose(); + }, + }) + : content} +
{!noFooter && (
{ { - setOpen(false); - onOk?.(); - }} + onClick={handleOk} text={okText} className={`${btnCommonClass} ${okBtnClass}`} /> @@ -166,7 +199,7 @@ const Modal = (props: ModalProps) => {
)}
-
 
+ {type === "modal" &&
 
} @@ -252,8 +285,39 @@ Modal.warn = (props: Omit) => { }); }; -const Trigger = (props: ModalProps) => { - return <>; +export const Trigger = (props: TriggerProps) => { + const { children, className, content, ...rest } = props; + + const [internalVisible, setVisible] = useState(false); + + return ( + <> +
{ + setVisible(true); + }} + > + {children} +
+ { + setVisible(false); + }} + content={ + typeof content === "function" + ? content({ + close: () => { + setVisible(false); + }, + }) + : content + } + /> + + ); }; Modal.Trigger = Trigger; diff --git a/app/containers/Chat/components/ChatHeader.tsx b/app/containers/Chat/components/ChatHeader.tsx index ad7d48361..a6608f523 100644 --- a/app/containers/Chat/components/ChatHeader.tsx +++ b/app/containers/Chat/components/ChatHeader.tsx @@ -7,6 +7,7 @@ import LogIcon from "@/app/icons/logIcon.svg"; import GobackIcon from "@/app/icons/goback.svg"; import ShareIcon from "@/app/icons/shareIcon.svg"; import BottomArrow from "@/app/icons/bottomArrow.svg"; +import ModelSelect from "./ModelSelect"; export interface ChatHeaderProps { isMobileScreen: boolean; @@ -70,13 +71,7 @@ export default function ChatHeader(props: ChatHeaderProps) { `} > {isMobileScreen ? ( -
{}} - > - {currentModel} - -
+ ) : ( Locale.Chat.SubTitle(session.messages.length) )} diff --git a/app/containers/Chat/components/ModelSelect.tsx b/app/containers/Chat/components/ModelSelect.tsx index f917a76f1..b3bbc16f5 100644 --- a/app/containers/Chat/components/ModelSelect.tsx +++ b/app/containers/Chat/components/ModelSelect.tsx @@ -3,14 +3,16 @@ import React, { useMemo, useRef } from "react"; import useRelativePosition, { Orientation, } from "@/app/hooks/useRelativePosition"; - -import Selected from "@/app/icons/selectedIcon.svg"; +import Locale from "@/app/locales"; 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"; +import Modal, { TriggerProps } from "@/app/components/Modal"; + +import Selected from "@/app/icons/selectedIcon.svg"; const ModelSelect = () => { const config = useAppConfig(); @@ -60,15 +62,14 @@ const ModelSelect = () => { }); }; - const content = ( -
+ const content: TriggerProps["content"] = ({ close }) => ( +
{models?.map((o) => (
{ + close(); chatStore.updateCurrentSession((session) => { session.mask.modelConfig.model = o.name as ModelType; session.mask.syncGlobalConfig = false; @@ -90,39 +91,41 @@ const ModelSelect = () => { if (isMobileScreen) { return ( - { + ( +
+ {content(e)} +
+ )} + type="bottom-drawer" + onOpen={(e) => { if (e) { autoScrollToSelectedModal(); getRelativePosition(rootRef.current!, ""); } }} - getPopoverPanelRef={(ref) => (contentRef.current = ref.current)} + title={Locale.Chat.SelectModel} + headerBordered + noFooter + modelClassName="h-model-bottom-drawer" >
{currentModel}
-
+ ); } return ( {} })} 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" + 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 !py-0" onShow={(e) => { if (e) { autoScrollToSelectedModal(); @@ -135,7 +138,7 @@ const ModelSelect = () => { 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} > -
+
{currentModel}
diff --git a/app/containers/Chat/components/SessionItem.tsx b/app/containers/Chat/components/SessionItem.tsx new file mode 100644 index 000000000..54871f652 --- /dev/null +++ b/app/containers/Chat/components/SessionItem.tsx @@ -0,0 +1,124 @@ +import { Draggable } from "@hello-pangea/dnd"; + +import Locale from "@/app/locales"; +import { useLocation } from "react-router-dom"; +import { Path } from "@/app/constant"; +import { Mask } from "@/app/store/mask"; +import { useRef, useEffect } from "react"; + +import DeleteChatIcon from "@/app/icons/deleteChatIcon.svg"; + +import { getTime } from "@/app/utils"; +import DeleteIcon from "@/app/icons/deleteIcon.svg"; +import LogIcon from "@/app/icons/logIcon.svg"; + +import HoverPopover from "@/app/components/HoverPopover"; + +export default function SessionItem(props: { + onClick?: () => void; + onDelete?: () => void; + title: string; + count: number; + time: string; + selected: boolean; + id: string; + index: number; + narrow?: boolean; + mask: Mask; + isMobileScreen: boolean; +}) { + const draggableRef = useRef(null); + useEffect(() => { + if (props.selected && draggableRef.current) { + draggableRef.current?.scrollIntoView({ + block: "center", + }); + } + }, [props.selected]); + + const { pathname: currentPath } = useLocation(); + + return ( + + {(provided) => ( +
{ + // draggableRef.current = ele; + // provided.innerRef(ele); + // }} + // {...provided.draggableProps} + // {...provided.dragHandleProps} + title={`${props.title}\n${Locale.ChatItem.ChatItemCount( + props.count, + )}`} + > +
+ +
+
+
+
+ {props.title} +
+
+ {getTime(props.time)} +
+
+
+ {Locale.ChatItem.ChatItemCount(props.count)} +
+
+ { + props.onDelete?.(); + e.preventDefault(); + e.stopPropagation(); + }} + > + +
+ {Locale.Chat.Actions.Delete} +
+
+ } + popoverClassName={` + px-2 py-1 border-delete-chat-popover bg-delete-chat-popover-panel rounded-md shadow-delete-chat-popover-shadow + `} + noArrow + align={props.isMobileScreen ? "end" : "start"} + > +
+ +
+ +
+ )} + + ); +} diff --git a/app/containers/Chat/index.tsx b/app/containers/Chat/index.tsx index 0cc705119..84d5c673c 100644 --- a/app/containers/Chat/index.tsx +++ b/app/containers/Chat/index.tsx @@ -1,7 +1,6 @@ import { DragDropContext, Droppable, - Draggable, OnDragEndResponder, } from "@hello-pangea/dnd"; @@ -10,130 +9,15 @@ import { useAppConfig, useChatStore } from "@/app/store"; import Locale from "@/app/locales"; import { useLocation, useNavigate } from "react-router-dom"; import { Path } from "@/app/constant"; -import { Mask } from "@/app/store/mask"; -import { useRef, useEffect } from "react"; +import { useEffect } from "react"; import AddIcon from "@/app/icons/addIcon.svg"; import NextChatTitle from "@/app/icons/nextchatTitle.svg"; -import DeleteChatIcon from "@/app/icons/deleteChatIcon.svg"; - -import { getTime } from "@/app/utils"; -import DeleteIcon from "@/app/icons/deleteIcon.svg"; -import LogIcon from "@/app/icons/logIcon.svg"; import MenuLayout from "@/app/components/MenuLayout"; import Panel from "./ChatPanel"; import Modal from "@/app/components/Modal"; -import HoverPopover from "@/app/components/HoverPopover"; - -export function SessionItem(props: { - onClick?: () => void; - onDelete?: () => void; - title: string; - count: number; - time: string; - selected: boolean; - id: string; - index: number; - narrow?: boolean; - mask: Mask; - isMobileScreen: boolean; -}) { - const draggableRef = useRef(null); - useEffect(() => { - if (props.selected && draggableRef.current) { - draggableRef.current?.scrollIntoView({ - block: "center", - }); - } - }, [props.selected]); - - const { pathname: currentPath } = useLocation(); - - return ( - - {(provided) => ( -
{ - draggableRef.current = ele; - provided.innerRef(ele); - }} - {...provided.draggableProps} - {...provided.dragHandleProps} - title={`${props.title}\n${Locale.ChatItem.ChatItemCount( - props.count, - )}`} - > -
- -
-
-
-
- {props.title} -
-
- {getTime(props.time)} -
-
-
- {Locale.ChatItem.ChatItemCount(props.count)} -
-
- { - props.onDelete?.(); - e.preventDefault(); - e.stopPropagation(); - }} - > - -
- {Locale.Chat.Actions.Delete} -
-
- } - popoverClassName={` - px-2 py-1 border-delete-chat-popover bg-delete-chat-popover-panel rounded-md shadow-delete-chat-popover-shadow - `} - noArrow - align={props.isMobileScreen ? "end" : "start"} - > -
- -
- -
- )} - - ); -} +import SessionItem from "./components/SessionItem"; export default MenuLayout(function SessionList(props) { const { setShowPanel } = props; @@ -225,8 +109,8 @@ export default MenuLayout(function SessionList(props) { {(provided) => (
{sessions.map((item, i) => ( diff --git a/app/locales/cn.ts b/app/locales/cn.ts index a8ec4ad31..e2b07e285 100644 --- a/app/locales/cn.ts +++ b/app/locales/cn.ts @@ -84,6 +84,7 @@ const cn = { SaveAs: "存为面具", }, IsContext: "预设提示词", + SelectModel: "选择模型", }, Export: { Title: "分享聊天记录", diff --git a/app/locales/en.ts b/app/locales/en.ts index 4454ef13d..11c6e3493 100644 --- a/app/locales/en.ts +++ b/app/locales/en.ts @@ -87,6 +87,7 @@ const en: LocaleType = { SaveAs: "Save as Mask", }, IsContext: "Contextual Prompt", + SelectModel: "Choose model", }, Export: { Title: "Export Messages", diff --git a/app/styles/globals.css b/app/styles/globals.css index 5e60d3497..cacf4208a 100644 --- a/app/styles/globals.css +++ b/app/styles/globals.css @@ -30,6 +30,10 @@ body { outline: none; } +* { + font-weight: 400; +} + .light-new { --global-bg: #e3e3ed; --actions-bar-btn-default-bg: #2e42f3; diff --git a/tailwind.config.js b/tailwind.config.js index eb97efc60..21dd120e5 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -54,6 +54,7 @@ module.exports = { 'slide-btn': '18px', 'switch': '1rem', 'chat-header-title-mobile': '19px', + 'model-bottom-drawer': 'calc(100vh - 110px)', }, minWidth: { 'select-mobile-lg': '200px',