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

@ -2,7 +2,7 @@ import * as React from "react";
export type ButtonType = "primary" | "danger" | null;
export default function Btn(props: {
export interface BtnProps {
onClick?: () => void;
icon?: JSX.Element;
type?: ButtonType;
@ -14,7 +14,9 @@ export default function Btn(props: {
disabled?: boolean;
tabIndex?: number;
autoFocus?: boolean;
}) {
}
export default function Btn(props: BtnProps) {
const {
onClick,
icon,

View File

@ -1,136 +0,0 @@
import React, { useState } from "react";
import { createRoot } from "react-dom/client";
import * as AlertDialog from "@radix-ui/react-alert-dialog";
import Warning from "@/app/icons/warning.svg";
import Btn from "@/app/components/Btn";
interface ConfirmProps {
onOk?: () => Promise<void> | void;
onCancel?: () => void;
okText: string;
cancelText: string;
content: React.ReactNode;
title: React.ReactNode;
visible?: boolean;
}
const baseZIndex = 150;
const Confirm = (props: ConfirmProps) => {
const { visible, onOk, onCancel, okText, cancelText, content, title } = props;
const [open, setOpen] = useState(false);
const mergeOpen = visible ?? open;
return (
<AlertDialog.Root open={mergeOpen} onOpenChange={setOpen}>
<AlertDialog.Portal>
<AlertDialog.Overlay
className="bg-confirm-mask fixed inset-0 animate-mask "
style={{ zIndex: baseZIndex - 1 }}
/>
<AlertDialog.Content
className={`
fixed inset-0 flex flex-col item-start top-0 left-[50vw] translate-x-[-50%]
`}
style={{ zIndex: baseZIndex - 1 }}
>
<div className="flex-1">&nbsp;</div>
<div
className={`flex flex-col
bg-confirm-panel text-confirm-mask
md:p-6 md:w-confirm md:gap-6 md:rounded-lg
`}
>
<AlertDialog.Title
className={`
flex items-center justify-start gap-3 font-common
md:text-chat-header-title md:font-bold md:leading-5
`}
>
<Warning />
{title}
</AlertDialog.Title>
<AlertDialog.Description
className={`
font-common font-normal
md:text-sm-title md:leading-[158%]
`}
>
{content}
</AlertDialog.Description>
<div
style={{ display: "flex", gap: 10, justifyContent: "flex-end" }}
>
<AlertDialog.Cancel asChild>
<Btn
className={`
md:px-4 md:py-2.5 bg-delete-chat-cancel-btn border border-delete-chat-cancel-btn text-text-delete-chat-cancel-btn rounded-md
`}
onClick={() => {
setOpen(false);
onCancel?.();
}}
text={cancelText}
/>
</AlertDialog.Cancel>
<AlertDialog.Action asChild>
<Btn
className={`
md:px-4 md:py-2.5 bg-delete-chat-ok-btn text-text-delete-chat-ok-btn rounded-md
`}
onClick={() => {
const toDo = onOk?.();
if (toDo instanceof Promise) {
toDo.then(() => {
setOpen(false);
});
} else {
setOpen(false);
}
}}
text={okText}
/>
</AlertDialog.Action>
</div>
</div>
<div className="flex-1">&nbsp;</div>
</AlertDialog.Content>
</AlertDialog.Portal>
</AlertDialog.Root>
);
};
const div = document.createElement("div");
div.id = "confirm-root";
div.style.height = "0px";
document.body.appendChild(div);
const show = (props: Omit<ConfirmProps, "visible" | "onCancel" | "onOk">) => {
const root = createRoot(div);
const closeModal = () => {
root.unmount();
};
return new Promise<boolean>((resolve) => {
root.render(
<Confirm
{...props}
visible={true}
onCancel={() => {
closeModal();
resolve(false);
}}
onOk={() => {
closeModal();
resolve(true);
}}
/>,
);
});
};
Confirm.show = show;
export default Confirm;

View File

@ -55,11 +55,11 @@ export default function Input(props: CommonInputProps & InputProps) {
return (
<div
className={`w-[100%] rounded-chat-input bg-input flex gap-3 items-center px-3 py-2 ${className}`}
className={` group/input w-[100%] rounded-chat-input bg-input flex gap-3 items-center px-3 py-2 ${className} hover:bg-select-hover`}
>
<input
{...rest}
className=" overflow-hidden text-text-input text-sm-title leading-input outline-none flex-1"
className=" overflow-hidden text-text-input text-sm-title leading-input outline-none flex-1 group-hover/input:bg-input-input-ele-hover"
type={internalType}
value={value}
onChange={(e) => {

View File

@ -0,0 +1,261 @@
import React, { 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";
import Warning from "@/app/icons/warning.svg";
import Close from "@/app/icons/closeIcon.svg";
export interface ModalProps {
onOk?: () => void;
onCancel?: () => void;
okText?: string;
cancelText?: string;
okBtnProps?: BtnProps;
cancelBtnProps?: BtnProps;
content?: React.ReactNode;
title?: React.ReactNode;
visible?: boolean;
noFooter?: boolean;
noHeader?: boolean;
isMobile?: boolean;
closeble?: boolean;
type?: "modal" | "bottom-drawer";
headerBordered?: boolean;
}
export interface WarnProps
extends Omit<
ModalProps,
| "closeble"
| "isMobile"
| "noHeader"
| "noFooter"
| "onOk"
| "okBtnProps"
| "cancelBtnProps"
> {
onOk?: () => Promise<void> | void;
}
const baseZIndex = 150;
const Modal = (props: ModalProps) => {
const {
onOk,
onCancel,
okText,
cancelText,
content,
title,
visible,
noFooter,
noHeader,
closeble = true,
okBtnProps,
cancelBtnProps,
type = "modal",
headerBordered,
} = props;
const [open, setOpen] = useState(false);
const mergeOpen = visible ?? open;
const handleClose = () => {
setOpen(false);
onCancel?.();
};
let layoutClassName = "";
let panelClassName = "";
let titleClassName = "";
let footerClassName = "";
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 = "";
break;
case "modal":
default:
layoutClassName =
"fixed inset-0 flex flex-col item-start top-0 left-[50vw] translate-x-[-50%] max-sm:w-modal-modal-type-mobile";
panelClassName = "rounded-lg px-6 sm:w-modal-modal-type";
titleClassName = "py-6 max-sm:pb-3";
footerClassName = "py-6";
}
const btnCommonClass = "px-4 py-2.5 rounded-md max-sm:flex-1";
const { className: okBtnClass } = okBtnProps || {};
const { className: cancelBtnClass } = cancelBtnProps || {};
return (
<AlertDialog.Root open={mergeOpen} onOpenChange={setOpen}>
<AlertDialog.Portal>
<AlertDialog.Overlay
className="bg-modal-mask fixed inset-0 animate-mask "
style={{ zIndex: baseZIndex - 1 }}
/>
<AlertDialog.Content
className={`
${layoutClassName}
`}
style={{ zIndex: baseZIndex - 1 }}
>
<div className="flex-1">&nbsp;</div>
<div
className={`flex flex-col
bg-moda-panel text-modal-mask
${headerBordered ? " border-b border-modal-header-bottom" : ""}
${panelClassName}
`}
>
{!noHeader && (
<AlertDialog.Title
className={`
flex items-center justify-between gap-3 font-common
md:text-chat-header-title md:font-bold md:leading-5
${titleClassName}
`}
>
<div className="flex gap-3 justify-start flex-1 items-center">
{title}
</div>
{closeble && (
<div
className="items-center"
onClick={() => {
handleClose();
}}
>
<Close />
</div>
)}
</AlertDialog.Title>
)}
{content}
{!noFooter && (
<div
className={`
flex gap-3 sm:justify-end max-sm:justify-between
${footerClassName}
`}
>
<AlertDialog.Cancel asChild>
<Btn
{...cancelBtnProps}
onClick={() => handleClose()}
text={cancelText}
className={`${btnCommonClass} ${cancelBtnClass}`}
/>
</AlertDialog.Cancel>
<AlertDialog.Action asChild>
<Btn
{...okBtnProps}
onClick={() => {
setOpen(false);
onOk?.();
}}
text={okText}
className={`${btnCommonClass} ${okBtnClass}`}
/>
</AlertDialog.Action>
</div>
)}
</div>
<div className="flex-1">&nbsp;</div>
</AlertDialog.Content>
</AlertDialog.Portal>
</AlertDialog.Root>
);
};
export const Warn = ({
title,
onOk,
visible,
content,
...props
}: WarnProps) => {
const [internalVisible, setVisible] = useState(visible);
return (
<Modal
{...props}
title={
<>
<Warning />
{title}
</>
}
content={
<AlertDialog.Description
className={`
font-common font-normal
md:text-sm-title md:leading-[158%]
`}
>
{content}
</AlertDialog.Description>
}
closeble={false}
onOk={() => {
const toDo = onOk?.();
if (toDo instanceof Promise) {
toDo.then(() => {
setVisible(false);
});
} else {
setVisible(false);
}
}}
visible={internalVisible}
okBtnProps={{
className: `bg-delete-chat-ok-btn text-text-delete-chat-ok-btn `,
}}
cancelBtnProps={{
className: `bg-delete-chat-cancel-btn border border-delete-chat-cancel-btn text-text-delete-chat-cancel-btn`,
}}
/>
);
};
const div = document.createElement("div");
div.id = "confirm-root";
div.style.height = "0px";
document.body.appendChild(div);
Modal.warn = (props: Omit<WarnProps, "visible" | "onCancel" | "onOk">) => {
const root = createRoot(div);
const closeModal = () => {
root.unmount();
};
return new Promise<boolean>((resolve) => {
root.render(
<Warn
{...props}
visible={true}
onCancel={() => {
closeModal();
resolve(false);
}}
onOk={() => {
closeModal();
resolve(true);
}}
/>,
);
});
};
const Trigger = (props: ModalProps) => {
return <></>;
};
Modal.Trigger = Trigger;
export default Modal;

View File

@ -1,5 +1,12 @@
import useRelativePosition from "@/app/hooks/useRelativePosition";
import { RefObject, useEffect, useMemo, useRef, useState } from "react";
import {
RefObject,
useEffect,
useLayoutEffect,
useMemo,
useRef,
useState,
} from "react";
import { createPortal } from "react-dom";
const ArrowIcon = ({ sibling }: { sibling: RefObject<HTMLDivElement> }) => {
@ -55,6 +62,7 @@ export interface PopoverProps {
noArrow?: boolean;
delayClose?: number;
useGlobalRoot?: boolean;
getPopoverPanelRef?: (ref: RefObject<HTMLDivElement>) => void;
}
export default function Popover(props: PopoverProps) {
@ -70,6 +78,7 @@ export default function Popover(props: PopoverProps) {
noArrow = false,
delayClose = 0,
useGlobalRoot,
getPopoverPanelRef,
} = props;
const [internalShow, setShow] = useState(false);
@ -175,10 +184,14 @@ export default function Popover(props: PopoverProps) {
const popoverRef = useRef<HTMLDivElement>(null);
const closeTimer = useRef<number>(0);
useLayoutEffect(() => {
getPopoverPanelRef?.(popoverRef);
onShow?.(internalShow);
}, [internalShow]);
if (trigger === "click") {
const handleOpen = (e: { currentTarget: any }) => {
clearTimeout(closeTimer.current);
onShow?.(true);
setShow(true);
getRelativePosition(e.currentTarget, "");
window.document.documentElement.style.overflow = "hidden";
@ -186,11 +199,9 @@ export default function Popover(props: PopoverProps) {
const handleClose = () => {
if (delayClose) {
closeTimer.current = window.setTimeout(() => {
onShow?.(false);
setShow(false);
}, delayClose);
} else {
onShow?.(false);
setShow(false);
}
window.document.documentElement.style.overflow = "auto";
@ -219,7 +230,7 @@ export default function Popover(props: PopoverProps) {
)}
{createPortal(
<div
className={`${popoverCommonClass} ${popoverClassName} cursor-pointer`}
className={`${popoverCommonClass} ${popoverClassName} cursor-pointer overflow-auto`}
style={{ zIndex: baseZIndex + 1, ...placementStyle }}
ref={popoverRef}
>

View File

@ -58,14 +58,22 @@ const Select = <Value extends number | string>(props: SearchProps<Value>) => {
{options?.map((o) => (
<div
key={o.value}
className={`flex items-center p-3 gap-2 rounded-action-btn hover:bg-select-option-hovered cursor-pointer`}
className={`flex items-center px-3 py-2 gap-3 rounded-action-btn hover:bg-select-option-hovered cursor-pointer`}
onClick={() => {
onSelect?.(o.value);
}}
>
{!!o.icon && <div className="">{o.icon}</div>}
<div className={`flex-1`}>{o.label}</div>
{selectedOption?.value === o.value && <Selected />}
<div className="flex gap-2 flex-1">
{!!o.icon && <div className="">{o.icon}</div>}
<div className={`flex-1`}>{o.label}</div>
</div>
<div
className={
selectedOption?.value === o.value ? "opacity-100" : "opacity-0"
}
>
<Selected />
</div>
</div>
))}
</div>
@ -86,7 +94,7 @@ const Select = <Value extends number | string>(props: SearchProps<Value>) => {
className={selectClassName}
>
<div
className={`flex items-center gap-3 py-2 px-3 bg-select rounded-action-btn font-time text-sm-title cursor-pointer`}
className={`flex items-center gap-3 py-2 px-3 bg-select rounded-action-btn font-time text-sm-title cursor-pointer hover:bg-select-hover`}
ref={contentRef}
>
<div className={`flex items-center gap-2 flex-1`}>

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,

View File

@ -6,7 +6,12 @@ import styles from "../index.module.scss";
import { useEffect, useState } from "react";
import { Avatar, AvatarPicker } from "@/app/components/emoji";
import { Popover } from "@/app/components/ui-lib";
import Locale, { AllLangs, changeLang, getLang } from "@/app/locales";
import Locale, {
ALL_LANG_OPTIONS,
AllLangs,
changeLang,
getLang,
} from "@/app/locales";
import Link from "next/link";
import { IconButton } from "@/app/components/button";
import { useUpdateStore } from "@/app/store/update";
@ -140,7 +145,10 @@ export default function AppSetting(props: AppSettingProps) {
<ListItem title={Locale.Settings.Lang.Name}>
<Select
value={getLang()}
options={AllLangs.map((lang) => ({ value: lang, label: lang }))}
options={AllLangs.map((lang) => ({
value: lang,
label: ALL_LANG_OPTIONS[lang],
}))}
onSelect={(e) => {
changeLang(e);
}}

3
app/icons/closeIcon.svg Normal file
View File

@ -0,0 +1,3 @@
<svg width="16" height="28" viewBox="0 0 16 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.1486 20.0156C13.388 20.255 13.7762 20.255 14.0156 20.0156C14.255 19.7762 14.255 19.388 14.0156 19.1486L8.86702 14L14.0157 8.85133C14.2551 8.6119 14.2551 8.22371 14.0157 7.98428C13.7762 7.74486 13.3881 7.74486 13.1486 7.98428L7.99998 13.1329L2.8513 7.98426C2.61187 7.74483 2.22368 7.74483 1.98426 7.98426C1.74483 8.22368 1.74483 8.61187 1.98426 8.8513L7.13294 14L1.98432 19.1486C1.74489 19.388 1.74489 19.7762 1.98432 20.0156C2.22374 20.2551 2.61193 20.2551 2.85136 20.0156L7.99998 14.867L13.1486 20.0156Z" fill="#606078"/>
</svg>

After

Width:  |  Height:  |  Size: 679 B

View File

@ -1,4 +1,4 @@
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="28" height="28" rx="8" fill="black" fill-opacity="0.05"/>
<rect width="28" height="28" rx="8" fill="none" fill-opacity="0.05"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.334 8.65915C15.334 7.92647 14.7371 7.33252 14.0007 7.33252C13.2644 7.33252 12.6675 7.92647 12.6675 8.65915L12.6675 8.67239C12.6675 9.40507 13.2644 9.99902 14.0007 9.99902C14.7371 9.99902 15.334 9.40507 15.334 8.67239L15.334 8.65915ZM14.0007 18.0011C14.7371 18.0011 15.334 18.5951 15.334 19.3278L15.334 19.341C15.334 20.0737 14.7371 20.6676 14.0007 20.6676C13.2644 20.6676 12.6675 20.0737 12.6675 19.341L12.6675 19.3278C12.6675 18.5951 13.2644 18.0011 14.0007 18.0011ZM14.0007 12.6668C14.7371 12.6668 15.334 13.2608 15.334 13.9935L15.334 14.0067C15.334 14.7394 14.7371 15.3333 14.0007 15.3333C13.2644 15.3333 12.6675 14.7394 12.6675 14.0067L12.6675 13.9935C12.6675 13.2608 13.2644 12.6668 14.0007 12.6668Z" fill="#606078"/>
</svg>

Before

Width:  |  Height:  |  Size: 950 B

After

Width:  |  Height:  |  Size: 949 B

View File

@ -1,3 +1,3 @@
<svg width="21" height="16" viewBox="0 0 21 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.1645 2.89288C20.4997 3.25987 20.474 3.82914 20.107 4.16437L10.254 13.1644C9.89015 13.4967 9.32664 13.4747 8.98991 13.1148L3.84285 7.61482C3.50321 7.2519 3.52209 6.68236 3.88502 6.34273C4.24794 6.00309 4.81748 6.02197 5.15711 6.3849L9.69659 11.2357L18.893 2.83535C19.26 2.50012 19.8293 2.52588 20.1645 2.89288Z" fill="black"/>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none">
<path d="M14.0005 4L6.00018 12L2 8.00018" stroke="#2E42F3" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 482 B

After

Width:  |  Height:  |  Size: 230 B

View File

@ -75,13 +75,16 @@ body {
--sidebar-mobile-bg: #fff;
--sidebar-btn-hovered-bg: rgba(0, 0, 0, 0.05);
--delete-chat-popover-panel-bg: #fff;
--confirm-mask-bg: rgba(0, 0, 0, 0.7);
--confirm-panel-bg: #fff;
--modal-mask-bg: rgba(0, 0, 0, 0.7);
--moda-panel-bg: #fff;
--delete-chat-ok-btn-bg: #ff5454;
--delete-chat-cancel-btn-bg: #fff;
--chat-message-actions-bg: #fff;
--menu-dragger-bg: blue;
--chat-actions-select-model-bg: rgba(0, 0, 0, 0.1);
--chat-actions-select-model-bg: rgba(0, 0, 0, 0.05);
--chat-actions-select-model-hover-bg: rgba(0, 0, 0, 0.1);
--select-hover-bg: rgba(0, 0, 0, 0.05);
--input-input-ele-hover-bg: rgba(0, 0, 0, 0);
--select-popover-border: rgba(0, 0, 0, 0.1);
--slider-block-border: #c9c9d1;
@ -98,6 +101,7 @@ body {
--delete-chat-cancel-btn-border: #e2e2e6;
--chat-menu-session-unselected-border: #f0f0f3;
--chat-menu-session-hovered-border: #e2e2e6;
--modal-header-bottom-border: #f0f0f3;
--sidebar-tab-mobile-active-text: #2e42f3;
--sidebar-tab-mobile-inactive-text: #a5a5b3;
@ -122,7 +126,7 @@ body {
--settings-menu-title-text: #18182a;
--settings-menu-item-title-text: #18182a;
--settings-panel-header-title-text: #18182a;
--confirm-mask-text: #18182a;
--modal-mask-text: #18182a;
--delete-chat-ok-btn-text: #fff;
--delete-chat-cancel-btn-text: #18182a;
@ -185,8 +189,8 @@ body {
--sidebar-mobile-bg: #fff;
--sidebar-btn-hovered-bg: rgba(0, 0, 0, 0.05);
--delete-chat-popover-panel-bg: #fff;
--confirm-mask-bg: rgba(0, 0, 0, 0.7);
--confirm-panel-bg: #fff;
--modal-mask-bg: rgba(0, 0, 0, 0.7);
--moda-panel-bg: #fff;
--delete-chat-ok-btn-bg: #ff5454;
--delete-chat-cancel-btn-bg: #fff;
--chat-message-actions-bg: #fff;
@ -230,7 +234,7 @@ body {
--settings-menu-title-text: #18182a;
--settings-menu-item-title-text: #18182a;
--settings-panel-header-title-text: #18182a;
--confirm-mask-text: #18182a;
--modal-mask-text: #18182a;
--delete-chat-ok-btn-text: #fff;
--delete-chat-cancel-btn-text: #18182a;
--primary-btn-disabled-dark-text: #fafafa;

View File

@ -71,7 +71,8 @@ module.exports = {
'thumbnail': '5rem',
'actions-popover': '203px',
'switch': '2.25rem',
'confirm': '26.25rem',
'modal-modal-type': '26.25rem',
'modal-modal-type-mobile': 'calc(100vw - 2 * 44px)',
},
flexBasis: {
'sidebar': 'var(--menu-width)',
@ -129,12 +130,15 @@ module.exports = {
'sidebar-mobile': 'var(--sidebar-mobile-bg)',
'sidebar-btn-hovered': 'var(--sidebar-btn-hovered-bg)',
'delete-chat-popover-panel': 'var(--delete-chat-popover-panel-bg)',
'confirm-mask': 'var(--confirm-mask-bg)',
'confirm-panel': 'var(--confirm-panel-bg)',
'modal-mask': 'var(--modal-mask-bg)',
'moda-panel': 'var(--moda-panel-bg)',
'delete-chat-ok-btn': 'var(--delete-chat-ok-btn-bg)',
'delete-chat-cancel-btn': 'var(--delete-chat-cancel-btn-bg)',
'menu-dragger': 'var(--menu-dragger-bg)',
'chat-actions-select-model': 'var(--chat-actions-select-model-bg)',
'chat-actions-select-model-hover': 'var(--chat-actions-select-model-hover-bg)',
'select-hover': 'var(--select-hover-bg)',
'input-input-ele-hover': 'var(--input-input-ele-hover-bg)',
},
backgroundImage: {
// 'chat-panel-message-user': 'linear-gradient(259deg, #9786FF 8.42%, #4A5CFF 90.13%)',
@ -144,10 +148,13 @@ module.exports = {
'time': 'all ease 0.6s',
'message': 'all ease 0.3s',
},
maxHeight: {},
maxHeight: {
'chat-actions-select-model-popover': '340px',
},
maxWidth: {
'message-width': 'var(--max-message-width)',
'setting-list': '710px',
'chat-actions-select-model': '82px',
},
boxShadow: {
'btn': 'var(--btn-shadow)',
@ -175,6 +182,7 @@ module.exports = {
'delete-chat-cancel-btn': 'var(--delete-chat-cancel-btn-border)',
'chat-menu-session-unselected': 'var(--chat-menu-session-unselected-border)',
'chat-menu-session-hovered': 'var(--chat-menu-session-hovered-border)',
'modal-header-bottom': 'var(--modal-header-bottom-border)',
'text-sidebar-tab-mobile-active': 'var(--sidebar-tab-mobile-active-text)',
'text-sidebar-tab-mobile-inactive': 'var(--sidebar-tab-mobile-inactive-text)',
@ -199,7 +207,7 @@ module.exports = {
'text-settings-menu-title': 'var(--settings-menu-title-text)',
'text-settings-menu-item-title': 'var(--settings-menu-item-title-text)',
'text-settings-panel-header-title': 'var(--settings-panel-header-title-text)',
'text-confirm-mask': 'var(--confirm-mask-text)',
'text-modal-mask': 'var(--modal-mask-text)',
'text-delete-chat-ok-btn': 'var(--delete-chat-ok-btn-text)',
'text-delete-chat-cancel-btn': 'var(--delete-chat-cancel-btn-text)',
'text-primary-btn-disabled-dark': 'var(--primary-btn-disabled-dark-text)',