diff --git a/app/components/chat.module.scss b/app/components/chat.module.scss index 0e2741e7..3a8d3cda 100644 --- a/app/components/chat.module.scss +++ b/app/components/chat.module.scss @@ -17,10 +17,35 @@ transition: all ease 0.3s; margin-bottom: 10px; align-items: center; + height: 16px; &:not(:last-child) { margin-right: 5px; } + + .text { + white-space: nowrap; + padding-left: 5px; + opacity: 0; + transform: translateX(-5px); + transition: all ease 0.3s; + transition-delay: 0.1s; + pointer-events: none; + } + + &:hover { + .text { + opacity: 1; + transform: translate(0); + } + } + + .text, + .icon { + display: flex; + align-items: center; + justify-content: center; + } } } diff --git a/app/components/chat.tsx b/app/components/chat.tsx index 70fd462d..15784861 100644 --- a/app/components/chat.tsx +++ b/app/components/chat.tsx @@ -279,6 +279,52 @@ function ClearContextDivider() { ); } +function ChatAction(props: { + text: string; + icon: JSX.Element; + onClick: () => void; +}) { + const iconRef = useRef(null); + const textRef = useRef(null); + const [hovering, setHovering] = useState(false); + const [width, setWidth] = useState(20); + + const updateWidth = () => { + if (!iconRef.current || !textRef.current) return; + const getWidth = (dom: HTMLDivElement) => dom.getBoundingClientRect().width; + const textWidth = getWidth(textRef.current); + const iconWidth = getWidth(iconRef.current); + setWidth(hovering ? textWidth + iconWidth : iconWidth); + }; + + useEffect(() => { + updateWidth(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [hovering]); + + return ( +
setHovering(true)} + onMouseLeave={() => setHovering(false)} + style={{ + width, + }} + onClick={() => { + props.onClick(); + setTimeout(updateWidth, 1); + }} + > +
+ {props.icon} +
+
+ {props.text} +
+
+ ); +} + function useScrollToBottom() { // for auto-scroll const scrollRef = useRef(null); @@ -330,61 +376,60 @@ export function ChatActions(props: { return (
{couldStop && ( -
- -
+ text={Locale.Chat.InputActions.Stop} + icon={} + /> )} {!props.hitBottom && ( -
- -
+ text={Locale.Chat.InputActions.ToBottom} + icon={} + /> )} {props.hitBottom && ( -
- -
+ text={Locale.Chat.InputActions.Settings} + icon={} + /> )} -
- {theme === Theme.Auto ? ( - - ) : theme === Theme.Light ? ( - - ) : theme === Theme.Dark ? ( - - ) : null} -
+ text={Locale.Chat.InputActions.Theme[theme]} + icon={ + <> + {theme === Theme.Auto ? ( + + ) : theme === Theme.Light ? ( + + ) : theme === Theme.Dark ? ( + + ) : null} + + } + /> -
- -
+ text={Locale.Chat.InputActions.Prompt} + icon={} + /> -
{ navigate(Path.Masks); }} - > - -
+ text={Locale.Chat.InputActions.Masks} + icon={} + /> -
} onClick={() => { chatStore.updateCurrentSession((session) => { if (session.clearContextIndex === session.messages.length) { @@ -395,9 +440,7 @@ export function ChatActions(props: { } }); }} - > - -
+ />
); } diff --git a/app/locales/cn.ts b/app/locales/cn.ts index c3cd8f45..d33ba101 100644 --- a/app/locales/cn.ts +++ b/app/locales/cn.ts @@ -27,6 +27,19 @@ const cn = { Retry: "重试", Delete: "删除", }, + InputActions: { + Stop: "停止响应", + ToBottom: "滚到最新", + Theme: { + auto: "自动主题", + light: "亮色模式", + dark: "深色模式", + }, + Prompt: "快捷指令", + Masks: "所有面具", + Clear: "清除聊天", + Settings: "对话设置", + }, Rename: "重命名对话", Typing: "正在输入…", Input: (submitKey: string) => { diff --git a/app/locales/en.ts b/app/locales/en.ts index 068b2e58..9c8bc2a7 100644 --- a/app/locales/en.ts +++ b/app/locales/en.ts @@ -28,6 +28,19 @@ const en: RequiredLocaleType = { Retry: "Retry", Delete: "Delete", }, + InputActions: { + Stop: "Stop", + ToBottom: "To Latest", + Theme: { + auto: "Auto", + light: "Light Theme", + dark: "Dark Theme", + }, + Prompt: "Prompts", + Masks: "Masks", + Clear: "Clear Context", + Settings: "Settings", + }, Rename: "Rename Chat", Typing: "Typing…", Input: (submitKey: string) => {