diff --git a/app/components/home.module.scss b/app/components/home.module.scss index 362c5fbf..730c05ef 100644 --- a/app/components/home.module.scss +++ b/app/components/home.module.scss @@ -237,6 +237,14 @@ flex-direction: column; align-items: flex-start; animation: slide-in ease 0.3s; + + &:hover { + .chat-message-top-actions { + opacity: 1; + right: 10px; + pointer-events: all; + } + } } .chat-message-user > .chat-message-container { @@ -276,6 +284,34 @@ user-select: text; word-break: break-word; border: var(--border-in-light); + position: relative; +} + +.chat-message-top-actions { + font-size: 12px; + position: absolute; + right: 20px; + top: -26px; + transition: all ease 0.3s; + opacity: 0; + pointer-events: none; + + display: flex; + flex-direction: row-reverse; + + .chat-message-top-action { + opacity: 0.5; + color: var(--black); + cursor: pointer; + + &:hover { + opacity: 1; + } + + &:not(:first-child) { + margin-right: 10px; + } + } } .chat-message-user > .chat-message-container > .chat-message-item { @@ -288,10 +324,10 @@ width: 100%; padding-top: 5px; box-sizing: border-box; + font-size: 12px; } .chat-message-action-date { - font-size: 12px; color: #aaa; } diff --git a/app/components/home.tsx b/app/components/home.tsx index 5087f5c7..1c665f87 100644 --- a/app/components/home.tsx +++ b/app/components/home.tsx @@ -21,7 +21,7 @@ import CopyIcon from "../icons/copy.svg"; import DownloadIcon from "../icons/download.svg"; import { Message, SubmitKey, useChatStore, ChatSession } from "../store"; -import { showModal } from "./ui-lib"; +import { showModal, showToast } from "./ui-lib"; import { copyToClipboard, downloadAs, isIOS, selectOrCopy } from "../utils"; import Locale from "../locales"; @@ -166,6 +166,8 @@ export function Chat(props: { showSideBar?: () => void }) { }; const latestMessageRef = useRef(null); + const [hoveringMessage, setHoveringMessage] = useState(false); + const messages = (session.messages as RenderMessage[]) .concat( isLoading @@ -195,7 +197,7 @@ export function Chat(props: { showSideBar?: () => void }) { useLayoutEffect(() => { setTimeout(() => { const dom = latestMessageRef.current; - if (dom && !isIOS()) { + if (dom && !isIOS() && !hoveringMessage) { dom.scrollIntoView({ behavior: "smooth", block: "end", @@ -250,7 +252,15 @@ export function Chat(props: { showSideBar?: () => void }) { -
+
{ + setHoveringMessage(true); + }} + onMouseOut={() => { + setHoveringMessage(false); + }} + > {messages.map((message, i) => { const isUser = message.role === "user"; @@ -271,6 +281,25 @@ export function Chat(props: { showSideBar?: () => void }) {
)}
+ {!isUser && ( +
+ {message.streaming && ( +
showToast(Locale.WIP)} + > + {Locale.Chat.Actions.Stop} +
+ )} + +
copyToClipboard(message.content)} + > + {Locale.Chat.Actions.Copy} +
+
+ )} {(message.preview || message.content.length === 0) && !isUser ? ( diff --git a/app/locales/cn.ts b/app/locales/cn.ts index a67d7695..a6ff4556 100644 --- a/app/locales/cn.ts +++ b/app/locales/cn.ts @@ -1,4 +1,5 @@ const cn = { + WIP: "该功能仍在开发中……", ChatItem: { ChatItemCount: (count: number) => `${count} 条对话`, }, @@ -8,6 +9,8 @@ const cn = { ChatList: "查看消息列表", CompressedHistory: "查看压缩后的历史 Prompt", Export: "导出聊天记录", + Copy: "复制", + Stop: "停止", }, Typing: "正在输入…", Input: (submitKey: string) => `输入消息,${submitKey} 发送`, diff --git a/app/locales/en.ts b/app/locales/en.ts index 7e17a2ab..50597014 100644 --- a/app/locales/en.ts +++ b/app/locales/en.ts @@ -1,6 +1,7 @@ import type { LocaleType } from "./index"; const en: LocaleType = { + WIP: "WIP...", ChatItem: { ChatItemCount: (count: number) => `${count} messages`, }, @@ -10,6 +11,8 @@ const en: LocaleType = { ChatList: "Go To Chat List", CompressedHistory: "Compressed History Memory Prompt", Export: "Export All Messages as Markdown", + Copy: "Copy", + Stop: "Stop", }, Typing: "Typing…", Input: (submitKey: string) =>