feat: select model done
This commit is contained in:
parent
8c28c408d8
commit
5ea6206319
|
@ -1,4 +1,4 @@
|
||||||
import React, { useState } from "react";
|
import React, { useLayoutEffect, useState } from "react";
|
||||||
import { createRoot } from "react-dom/client";
|
import { createRoot } from "react-dom/client";
|
||||||
import * as AlertDialog from "@radix-ui/react-alert-dialog";
|
import * as AlertDialog from "@radix-ui/react-alert-dialog";
|
||||||
import Btn, { BtnProps } from "@/app/components/Btn";
|
import Btn, { BtnProps } from "@/app/components/Btn";
|
||||||
|
@ -13,7 +13,9 @@ export interface ModalProps {
|
||||||
cancelText?: string;
|
cancelText?: string;
|
||||||
okBtnProps?: BtnProps;
|
okBtnProps?: BtnProps;
|
||||||
cancelBtnProps?: BtnProps;
|
cancelBtnProps?: BtnProps;
|
||||||
content?: React.ReactNode;
|
content?:
|
||||||
|
| React.ReactNode
|
||||||
|
| ((handlers: { close: () => void }) => JSX.Element);
|
||||||
title?: React.ReactNode;
|
title?: React.ReactNode;
|
||||||
visible?: boolean;
|
visible?: boolean;
|
||||||
noFooter?: boolean;
|
noFooter?: boolean;
|
||||||
|
@ -22,6 +24,8 @@ export interface ModalProps {
|
||||||
closeble?: boolean;
|
closeble?: boolean;
|
||||||
type?: "modal" | "bottom-drawer";
|
type?: "modal" | "bottom-drawer";
|
||||||
headerBordered?: boolean;
|
headerBordered?: boolean;
|
||||||
|
modelClassName?: string;
|
||||||
|
onOpen?: (v: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WarnProps
|
export interface WarnProps
|
||||||
|
@ -34,8 +38,16 @@ export interface WarnProps
|
||||||
| "onOk"
|
| "onOk"
|
||||||
| "okBtnProps"
|
| "okBtnProps"
|
||||||
| "cancelBtnProps"
|
| "cancelBtnProps"
|
||||||
|
| "content"
|
||||||
> {
|
> {
|
||||||
onOk?: () => Promise<void> | void;
|
onOk?: () => Promise<void> | void;
|
||||||
|
content?: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TriggerProps
|
||||||
|
extends Omit<ModalProps, "visible" | "onOk" | "onCancel"> {
|
||||||
|
children: JSX.Element;
|
||||||
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const baseZIndex = 150;
|
const baseZIndex = 150;
|
||||||
|
@ -56,9 +68,11 @@ const Modal = (props: ModalProps) => {
|
||||||
cancelBtnProps,
|
cancelBtnProps,
|
||||||
type = "modal",
|
type = "modal",
|
||||||
headerBordered,
|
headerBordered,
|
||||||
|
modelClassName,
|
||||||
|
onOpen,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(!!visible);
|
||||||
|
|
||||||
const mergeOpen = visible ?? open;
|
const mergeOpen = visible ?? open;
|
||||||
|
|
||||||
|
@ -67,6 +81,15 @@ const Modal = (props: ModalProps) => {
|
||||||
onCancel?.();
|
onCancel?.();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleOk = () => {
|
||||||
|
setOpen(false);
|
||||||
|
onOk?.();
|
||||||
|
};
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
onOpen?.(mergeOpen);
|
||||||
|
}, [mergeOpen]);
|
||||||
|
|
||||||
let layoutClassName = "";
|
let layoutClassName = "";
|
||||||
let panelClassName = "";
|
let panelClassName = "";
|
||||||
let titleClassName = "";
|
let titleClassName = "";
|
||||||
|
@ -74,11 +97,11 @@ const Modal = (props: ModalProps) => {
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "bottom-drawer":
|
case "bottom-drawer":
|
||||||
layoutClassName =
|
layoutClassName = "fixed inset-0 flex flex-col w-[100%] bottom-0";
|
||||||
"fixed inset-0 flex flex-col item-start top-0 left-[50vw] translate-x-[-50%] md:";
|
panelClassName =
|
||||||
panelClassName = "";
|
"rounded-t-chat-model-select overflow-y-auto overflow-x-hidden";
|
||||||
titleClassName = "";
|
titleClassName = "px-4 py-3";
|
||||||
footerClassName = "";
|
footerClassName = "absolute w-[100%]";
|
||||||
break;
|
break;
|
||||||
case "modal":
|
case "modal":
|
||||||
default:
|
default:
|
||||||
|
@ -107,9 +130,9 @@ const Modal = (props: ModalProps) => {
|
||||||
>
|
>
|
||||||
<div className="flex-1"> </div>
|
<div className="flex-1"> </div>
|
||||||
<div
|
<div
|
||||||
className={`flex flex-col
|
className={`flex flex-col flex-0
|
||||||
bg-moda-panel text-modal-mask
|
bg-moda-panel text-modal-mask
|
||||||
${headerBordered ? " border-b border-modal-header-bottom" : ""}
|
${modelClassName}
|
||||||
${panelClassName}
|
${panelClassName}
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
|
@ -118,6 +141,11 @@ const Modal = (props: ModalProps) => {
|
||||||
className={`
|
className={`
|
||||||
flex items-center justify-between gap-3 font-common
|
flex items-center justify-between gap-3 font-common
|
||||||
md:text-chat-header-title md:font-bold md:leading-5
|
md:text-chat-header-title md:font-bold md:leading-5
|
||||||
|
${
|
||||||
|
headerBordered
|
||||||
|
? " border-b border-modal-header-bottom"
|
||||||
|
: ""
|
||||||
|
}
|
||||||
${titleClassName}
|
${titleClassName}
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
|
@ -136,7 +164,15 @@ const Modal = (props: ModalProps) => {
|
||||||
)}
|
)}
|
||||||
</AlertDialog.Title>
|
</AlertDialog.Title>
|
||||||
)}
|
)}
|
||||||
{content}
|
<div className="flex-1 overflow-hidden">
|
||||||
|
{typeof content === "function"
|
||||||
|
? content({
|
||||||
|
close: () => {
|
||||||
|
handleClose();
|
||||||
|
},
|
||||||
|
})
|
||||||
|
: content}
|
||||||
|
</div>
|
||||||
{!noFooter && (
|
{!noFooter && (
|
||||||
<div
|
<div
|
||||||
className={`
|
className={`
|
||||||
|
@ -155,10 +191,7 @@ const Modal = (props: ModalProps) => {
|
||||||
<AlertDialog.Action asChild>
|
<AlertDialog.Action asChild>
|
||||||
<Btn
|
<Btn
|
||||||
{...okBtnProps}
|
{...okBtnProps}
|
||||||
onClick={() => {
|
onClick={handleOk}
|
||||||
setOpen(false);
|
|
||||||
onOk?.();
|
|
||||||
}}
|
|
||||||
text={okText}
|
text={okText}
|
||||||
className={`${btnCommonClass} ${okBtnClass}`}
|
className={`${btnCommonClass} ${okBtnClass}`}
|
||||||
/>
|
/>
|
||||||
|
@ -166,7 +199,7 @@ const Modal = (props: ModalProps) => {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1"> </div>
|
{type === "modal" && <div className="flex-1"> </div>}
|
||||||
</AlertDialog.Content>
|
</AlertDialog.Content>
|
||||||
</AlertDialog.Portal>
|
</AlertDialog.Portal>
|
||||||
</AlertDialog.Root>
|
</AlertDialog.Root>
|
||||||
|
@ -252,8 +285,39 @@ Modal.warn = (props: Omit<WarnProps, "visible" | "onCancel" | "onOk">) => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const Trigger = (props: ModalProps) => {
|
export const Trigger = (props: TriggerProps) => {
|
||||||
return <></>;
|
const { children, className, content, ...rest } = props;
|
||||||
|
|
||||||
|
const [internalVisible, setVisible] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
className={className}
|
||||||
|
onClick={() => {
|
||||||
|
setVisible(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
<Modal
|
||||||
|
{...rest}
|
||||||
|
visible={internalVisible}
|
||||||
|
onCancel={() => {
|
||||||
|
setVisible(false);
|
||||||
|
}}
|
||||||
|
content={
|
||||||
|
typeof content === "function"
|
||||||
|
? content({
|
||||||
|
close: () => {
|
||||||
|
setVisible(false);
|
||||||
|
},
|
||||||
|
})
|
||||||
|
: content
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
Modal.Trigger = Trigger;
|
Modal.Trigger = Trigger;
|
||||||
|
|
|
@ -7,6 +7,7 @@ import LogIcon from "@/app/icons/logIcon.svg";
|
||||||
import GobackIcon from "@/app/icons/goback.svg";
|
import GobackIcon from "@/app/icons/goback.svg";
|
||||||
import ShareIcon from "@/app/icons/shareIcon.svg";
|
import ShareIcon from "@/app/icons/shareIcon.svg";
|
||||||
import BottomArrow from "@/app/icons/bottomArrow.svg";
|
import BottomArrow from "@/app/icons/bottomArrow.svg";
|
||||||
|
import ModelSelect from "./ModelSelect";
|
||||||
|
|
||||||
export interface ChatHeaderProps {
|
export interface ChatHeaderProps {
|
||||||
isMobileScreen: boolean;
|
isMobileScreen: boolean;
|
||||||
|
@ -70,13 +71,7 @@ export default function ChatHeader(props: ChatHeaderProps) {
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
{isMobileScreen ? (
|
{isMobileScreen ? (
|
||||||
<div
|
<ModelSelect />
|
||||||
className="flex items-center gap-1 cursor-pointer"
|
|
||||||
onClick={() => {}}
|
|
||||||
>
|
|
||||||
{currentModel}
|
|
||||||
<BottomArrow />
|
|
||||||
</div>
|
|
||||||
) : (
|
) : (
|
||||||
Locale.Chat.SubTitle(session.messages.length)
|
Locale.Chat.SubTitle(session.messages.length)
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -3,14 +3,16 @@ import React, { useMemo, useRef } from "react";
|
||||||
import useRelativePosition, {
|
import useRelativePosition, {
|
||||||
Orientation,
|
Orientation,
|
||||||
} from "@/app/hooks/useRelativePosition";
|
} from "@/app/hooks/useRelativePosition";
|
||||||
|
import Locale from "@/app/locales";
|
||||||
import Selected from "@/app/icons/selectedIcon.svg";
|
|
||||||
import { useChatStore } from "@/app/store/chat";
|
import { useChatStore } from "@/app/store/chat";
|
||||||
import { useAllModels } from "@/app/utils/hooks";
|
import { useAllModels } from "@/app/utils/hooks";
|
||||||
import { ModelType, useAppConfig } from "@/app/store/config";
|
import { ModelType, useAppConfig } from "@/app/store/config";
|
||||||
import { showToast } from "@/app/components/ui-lib";
|
import { showToast } from "@/app/components/ui-lib";
|
||||||
import BottomArrow from "@/app/icons/downArrowLgIcon.svg";
|
import BottomArrow from "@/app/icons/downArrowLgIcon.svg";
|
||||||
import BottomArrowMobile from "@/app/icons/bottomArrow.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 ModelSelect = () => {
|
||||||
const config = useAppConfig();
|
const config = useAppConfig();
|
||||||
|
@ -60,15 +62,14 @@ const ModelSelect = () => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const content = (
|
const content: TriggerProps["content"] = ({ close }) => (
|
||||||
<div
|
<div className={`flex flex-col gap-1 overflow-x-hidden relative`}>
|
||||||
className={` flex flex-col gap-1 overflow-y-auto overflow-x-hidden relative h-[100%]`}
|
|
||||||
>
|
|
||||||
{models?.map((o) => (
|
{models?.map((o) => (
|
||||||
<div
|
<div
|
||||||
key={o.displayName}
|
key={o.displayName}
|
||||||
className={`flex items-center px-3 py-2 gap-3 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={() => {
|
onClick={() => {
|
||||||
|
close();
|
||||||
chatStore.updateCurrentSession((session) => {
|
chatStore.updateCurrentSession((session) => {
|
||||||
session.mask.modelConfig.model = o.name as ModelType;
|
session.mask.modelConfig.model = o.name as ModelType;
|
||||||
session.mask.syncGlobalConfig = false;
|
session.mask.syncGlobalConfig = false;
|
||||||
|
@ -90,39 +91,41 @@ const ModelSelect = () => {
|
||||||
|
|
||||||
if (isMobileScreen) {
|
if (isMobileScreen) {
|
||||||
return (
|
return (
|
||||||
<Popover
|
<Modal.Trigger
|
||||||
content={content}
|
content={(e) => (
|
||||||
trigger="click"
|
<div className="h-[100%] overflow-y-auto" ref={contentRef}>
|
||||||
noArrow
|
{content(e)}
|
||||||
placement={
|
</div>
|
||||||
position?.poi.relativePosition[1] !== Orientation.bottom ? "lb" : "lt"
|
)}
|
||||||
}
|
type="bottom-drawer"
|
||||||
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"
|
onOpen={(e) => {
|
||||||
onShow={(e) => {
|
|
||||||
if (e) {
|
if (e) {
|
||||||
autoScrollToSelectedModal();
|
autoScrollToSelectedModal();
|
||||||
getRelativePosition(rootRef.current!, "");
|
getRelativePosition(rootRef.current!, "");
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
getPopoverPanelRef={(ref) => (contentRef.current = ref.current)}
|
title={Locale.Chat.SelectModel}
|
||||||
|
headerBordered
|
||||||
|
noFooter
|
||||||
|
modelClassName="h-model-bottom-drawer"
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-1 cursor-pointer" ref={rootRef}>
|
<div className="flex items-center gap-1 cursor-pointer" ref={rootRef}>
|
||||||
{currentModel}
|
{currentModel}
|
||||||
<BottomArrowMobile />
|
<BottomArrowMobile />
|
||||||
</div>
|
</div>
|
||||||
</Popover>
|
</Modal.Trigger>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover
|
<Popover
|
||||||
content={content}
|
content={content({ close: () => {} })}
|
||||||
trigger="click"
|
trigger="click"
|
||||||
noArrow
|
noArrow
|
||||||
placement={
|
placement={
|
||||||
position?.poi.relativePosition[1] !== Orientation.bottom ? "lb" : "lt"
|
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) => {
|
onShow={(e) => {
|
||||||
if (e) {
|
if (e) {
|
||||||
autoScrollToSelectedModal();
|
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"
|
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}
|
ref={rootRef}
|
||||||
>
|
>
|
||||||
<div className="line-clamp-1 max-w-chat-actions-select-model">
|
<div className="line-clamp-1 max-w-chat-actions-select-model text-sm-title">
|
||||||
{currentModel}
|
{currentModel}
|
||||||
</div>
|
</div>
|
||||||
<BottomArrow />
|
<BottomArrow />
|
||||||
|
|
|
@ -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<HTMLDivElement | null>(null);
|
||||||
|
useEffect(() => {
|
||||||
|
if (props.selected && draggableRef.current) {
|
||||||
|
draggableRef.current?.scrollIntoView({
|
||||||
|
block: "center",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [props.selected]);
|
||||||
|
|
||||||
|
const { pathname: currentPath } = useLocation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Draggable draggableId={`${props.id}`} index={props.index}>
|
||||||
|
{(provided) => (
|
||||||
|
<div
|
||||||
|
className={`
|
||||||
|
group/chat-menu-list relative flex p-3 items-center gap-2 self-stretch rounded-md mb-2
|
||||||
|
border
|
||||||
|
bg-chat-menu-session-unselected border-chat-menu-session-unselected cursor-pointer
|
||||||
|
${
|
||||||
|
props.selected &&
|
||||||
|
(currentPath === Path.Chat || currentPath === Path.Home)
|
||||||
|
? `!bg-chat-menu-session-selected !border-chat-menu-session-selected`
|
||||||
|
: `hover:bg-chat-menu-session-hovered hover:chat-menu-session-hovered`
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
onClick={props.onClick}
|
||||||
|
// ref={(ele) => {
|
||||||
|
// draggableRef.current = ele;
|
||||||
|
// provided.innerRef(ele);
|
||||||
|
// }}
|
||||||
|
// {...provided.draggableProps}
|
||||||
|
// {...provided.dragHandleProps}
|
||||||
|
title={`${props.title}\n${Locale.ChatItem.ChatItemCount(
|
||||||
|
props.count,
|
||||||
|
)}`}
|
||||||
|
>
|
||||||
|
<div className=" flex-shrink-0">
|
||||||
|
<LogIcon />
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col flex-1">
|
||||||
|
<div className={`flex justify-between items-center`}>
|
||||||
|
<div
|
||||||
|
className={` text-text-chat-menu-item-title text-sm-title line-clamp-1 flex-1`}
|
||||||
|
>
|
||||||
|
{props.title}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={`text-text-chat-menu-item-time text-sm group-hover/chat-menu-list:opacity-0 pl-3`}
|
||||||
|
>
|
||||||
|
{getTime(props.time)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={`text-text-chat-menu-item-description text-sm`}>
|
||||||
|
{Locale.ChatItem.ChatItemCount(props.count)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<HoverPopover
|
||||||
|
content={
|
||||||
|
<div
|
||||||
|
className={`flex items-center gap-3 p-3 rounded-action-btn leading-6 cursor-pointer`}
|
||||||
|
onClickCapture={(e) => {
|
||||||
|
props.onDelete?.();
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DeleteChatIcon />
|
||||||
|
<div className="flex-1 font-common text-actions-popover-menu-item">
|
||||||
|
{Locale.Chat.Actions.Delete}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
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"}
|
||||||
|
>
|
||||||
|
<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
|
||||||
|
hover:bg-select-hover rounded-chat-img
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<DeleteIcon />
|
||||||
|
</div>
|
||||||
|
</HoverPopover>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Draggable>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,7 +1,6 @@
|
||||||
import {
|
import {
|
||||||
DragDropContext,
|
DragDropContext,
|
||||||
Droppable,
|
Droppable,
|
||||||
Draggable,
|
|
||||||
OnDragEndResponder,
|
OnDragEndResponder,
|
||||||
} from "@hello-pangea/dnd";
|
} from "@hello-pangea/dnd";
|
||||||
|
|
||||||
|
@ -10,130 +9,15 @@ import { useAppConfig, useChatStore } from "@/app/store";
|
||||||
import Locale from "@/app/locales";
|
import Locale from "@/app/locales";
|
||||||
import { useLocation, useNavigate } from "react-router-dom";
|
import { useLocation, useNavigate } from "react-router-dom";
|
||||||
import { Path } from "@/app/constant";
|
import { Path } from "@/app/constant";
|
||||||
import { Mask } from "@/app/store/mask";
|
import { useEffect } from "react";
|
||||||
import { useRef, useEffect } from "react";
|
|
||||||
|
|
||||||
import AddIcon from "@/app/icons/addIcon.svg";
|
import AddIcon from "@/app/icons/addIcon.svg";
|
||||||
import NextChatTitle from "@/app/icons/nextchatTitle.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 MenuLayout from "@/app/components/MenuLayout";
|
||||||
import Panel from "./ChatPanel";
|
import Panel from "./ChatPanel";
|
||||||
import Modal from "@/app/components/Modal";
|
import Modal from "@/app/components/Modal";
|
||||||
import HoverPopover from "@/app/components/HoverPopover";
|
import SessionItem from "./components/SessionItem";
|
||||||
|
|
||||||
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<HTMLDivElement | null>(null);
|
|
||||||
useEffect(() => {
|
|
||||||
if (props.selected && draggableRef.current) {
|
|
||||||
draggableRef.current?.scrollIntoView({
|
|
||||||
block: "center",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [props.selected]);
|
|
||||||
|
|
||||||
const { pathname: currentPath } = useLocation();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Draggable draggableId={`${props.id}`} index={props.index}>
|
|
||||||
{(provided) => (
|
|
||||||
<div
|
|
||||||
className={`
|
|
||||||
group/chat-menu-list relative flex p-3 items-center gap-2 self-stretch rounded-md mb-2
|
|
||||||
border
|
|
||||||
bg-chat-menu-session-unselected border-chat-menu-session-unselected
|
|
||||||
${
|
|
||||||
props.selected &&
|
|
||||||
(currentPath === Path.Chat || currentPath === Path.Home)
|
|
||||||
? `!bg-chat-menu-session-selected !border-chat-menu-session-selected`
|
|
||||||
: `hover:bg-chat-menu-session-hovered hover:chat-menu-session-hovered`
|
|
||||||
}
|
|
||||||
`}
|
|
||||||
onClick={props.onClick}
|
|
||||||
ref={(ele) => {
|
|
||||||
draggableRef.current = ele;
|
|
||||||
provided.innerRef(ele);
|
|
||||||
}}
|
|
||||||
{...provided.draggableProps}
|
|
||||||
{...provided.dragHandleProps}
|
|
||||||
title={`${props.title}\n${Locale.ChatItem.ChatItemCount(
|
|
||||||
props.count,
|
|
||||||
)}`}
|
|
||||||
>
|
|
||||||
<div className=" flex-shrink-0">
|
|
||||||
<LogIcon />
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col flex-1">
|
|
||||||
<div className={`flex justify-between items-center`}>
|
|
||||||
<div
|
|
||||||
className={` text-text-chat-menu-item-title text-sm-title line-clamp-1 flex-1`}
|
|
||||||
>
|
|
||||||
{props.title}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className={`text-text-chat-menu-item-time text-sm group-hover/chat-menu-list:opacity-0 pl-3`}
|
|
||||||
>
|
|
||||||
{getTime(props.time)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className={`text-text-chat-menu-item-description text-sm`}>
|
|
||||||
{Locale.ChatItem.ChatItemCount(props.count)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<HoverPopover
|
|
||||||
content={
|
|
||||||
<div
|
|
||||||
className={`flex items-center gap-3 p-3 rounded-action-btn leading-6 cursor-pointer`}
|
|
||||||
onClickCapture={(e) => {
|
|
||||||
props.onDelete?.();
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<DeleteChatIcon />
|
|
||||||
<div className="flex-1 font-common text-actions-popover-menu-item">
|
|
||||||
{Locale.Chat.Actions.Delete}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
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"}
|
|
||||||
>
|
|
||||||
<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
|
|
||||||
hover:bg-select-hover rounded-chat-img
|
|
||||||
`}
|
|
||||||
>
|
|
||||||
<DeleteIcon />
|
|
||||||
</div>
|
|
||||||
</HoverPopover>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Draggable>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default MenuLayout(function SessionList(props) {
|
export default MenuLayout(function SessionList(props) {
|
||||||
const { setShowPanel } = props;
|
const { setShowPanel } = props;
|
||||||
|
@ -225,8 +109,8 @@ export default MenuLayout(function SessionList(props) {
|
||||||
<Droppable droppableId="chat-list">
|
<Droppable droppableId="chat-list">
|
||||||
{(provided) => (
|
{(provided) => (
|
||||||
<div
|
<div
|
||||||
ref={provided.innerRef}
|
// ref={provided.innerRef}
|
||||||
{...provided.droppableProps}
|
// {...provided.droppableProps}
|
||||||
className={`w-[100%]`}
|
className={`w-[100%]`}
|
||||||
>
|
>
|
||||||
{sessions.map((item, i) => (
|
{sessions.map((item, i) => (
|
||||||
|
|
|
@ -84,6 +84,7 @@ const cn = {
|
||||||
SaveAs: "存为面具",
|
SaveAs: "存为面具",
|
||||||
},
|
},
|
||||||
IsContext: "预设提示词",
|
IsContext: "预设提示词",
|
||||||
|
SelectModel: "选择模型",
|
||||||
},
|
},
|
||||||
Export: {
|
Export: {
|
||||||
Title: "分享聊天记录",
|
Title: "分享聊天记录",
|
||||||
|
|
|
@ -87,6 +87,7 @@ const en: LocaleType = {
|
||||||
SaveAs: "Save as Mask",
|
SaveAs: "Save as Mask",
|
||||||
},
|
},
|
||||||
IsContext: "Contextual Prompt",
|
IsContext: "Contextual Prompt",
|
||||||
|
SelectModel: "Choose model",
|
||||||
},
|
},
|
||||||
Export: {
|
Export: {
|
||||||
Title: "Export Messages",
|
Title: "Export Messages",
|
||||||
|
|
|
@ -30,6 +30,10 @@ body {
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
.light-new {
|
.light-new {
|
||||||
--global-bg: #e3e3ed;
|
--global-bg: #e3e3ed;
|
||||||
--actions-bar-btn-default-bg: #2e42f3;
|
--actions-bar-btn-default-bg: #2e42f3;
|
||||||
|
|
|
@ -54,6 +54,7 @@ module.exports = {
|
||||||
'slide-btn': '18px',
|
'slide-btn': '18px',
|
||||||
'switch': '1rem',
|
'switch': '1rem',
|
||||||
'chat-header-title-mobile': '19px',
|
'chat-header-title-mobile': '19px',
|
||||||
|
'model-bottom-drawer': 'calc(100vh - 110px)',
|
||||||
},
|
},
|
||||||
minWidth: {
|
minWidth: {
|
||||||
'select-mobile-lg': '200px',
|
'select-mobile-lg': '200px',
|
||||||
|
|
Loading…
Reference in New Issue