feat: refactor select model
This commit is contained in:
parent
c34b8ab919
commit
8c28c408d8
|
@ -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,
|
||||
|
|
|
@ -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"> </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"> </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;
|
|
@ -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) => {
|
||||
|
|
|
@ -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"> </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"> </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;
|
|
@ -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}
|
||||
>
|
||||
|
|
|
@ -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`}>
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}}
|
||||
|
|
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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;
|
||||
|
|
|
@ -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)',
|
||||
|
|
Loading…
Reference in New Issue