diff --git a/src/renderer/src/components/TopView/index.tsx b/src/renderer/src/components/TopView/index.tsx index 1a317235..4849b952 100644 --- a/src/renderer/src/components/TopView/index.tsx +++ b/src/renderer/src/components/TopView/index.tsx @@ -1,5 +1,6 @@ +import { message, Modal } from 'antd' import { findIndex, pullAt } from 'lodash' -import React, { useState } from 'react' +import React, { useEffect, useState } from 'react' let id = 0 let onPop = () => {} @@ -17,6 +18,8 @@ type ElementItem = { const TopViewContainer: React.FC = ({ children }) => { const [elements, setElements] = useState([]) + const [messageApi, messageContextHolder] = message.useMessage() + const [modal, modalContextHolder] = Modal.useModal() onPop = () => { const views = [...elements] @@ -34,9 +37,16 @@ const TopViewContainer: React.FC = ({ children }) => { setElements(views) } + useEffect(() => { + window.message = messageApi + window.modal = modal + }, [messageApi, modal]) + return ( <> {children} + {messageContextHolder} + {modalContextHolder} {elements.length > 0 && (
diff --git a/src/renderer/src/env.d.ts b/src/renderer/src/env.d.ts index 11f02fe2..1e06e658 100644 --- a/src/renderer/src/env.d.ts +++ b/src/renderer/src/env.d.ts @@ -1 +1,11 @@ /// + +import { MessageInstance } from 'antd/es/message/interface' +import { HookAPI } from 'antd/es/modal/useModal' + +declare global { + interface Window { + message: MessageInstance + modal: HookAPI + } +} diff --git a/src/renderer/src/pages/apps/AppsPage.tsx b/src/renderer/src/pages/apps/AppsPage.tsx index 01198e78..a75164e7 100644 --- a/src/renderer/src/pages/apps/AppsPage.tsx +++ b/src/renderer/src/pages/apps/AppsPage.tsx @@ -1,6 +1,6 @@ import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar' import { SYSTEM_ASSISTANTS } from '@renderer/config/assistant' -import { Button, Col, message, Row, Tooltip, Typography } from 'antd' +import { Button, Col, Row, Tooltip, Typography } from 'antd' import { find, groupBy } from 'lodash' import { FC } from 'react' import styled from 'styled-components' @@ -14,26 +14,21 @@ const { Title } = Typography const AppsPage: FC = () => { const { assistants, addAssistant } = useAssistants() const assistantGroups = groupBy(SYSTEM_ASSISTANTS, 'group') - const [messageApi, contextHolder] = message.useMessage() const onAddAssistant = (assistant: SystemAssistant) => { addAssistant({ ...getDefaultAssistant(), ...assistant }) - messageApi.destroy() - messageApi.open({ - type: 'success', + window.message.success({ content: 'Assistant added successfully', - style: { - marginTop: '5vh' - } + key: 'assistant-added', + style: { marginTop: '5vh' } }) } return ( - {contextHolder} Assistant Market diff --git a/src/renderer/src/pages/home/components/Message.tsx b/src/renderer/src/pages/home/components/Message.tsx index e4b000ba..ede6786f 100644 --- a/src/renderer/src/pages/home/components/Message.tsx +++ b/src/renderer/src/pages/home/components/Message.tsx @@ -5,14 +5,44 @@ import { FC } from 'react' import styled from 'styled-components' import Logo from '@renderer/assets/images/logo.png' import useAvatar from '@renderer/hooks/useAvatar' +import { CopyOutlined, DeleteOutlined } from '@ant-design/icons' -const MessageItem: FC<{ message: Message }> = ({ message }) => { +interface Props { + message: Message + showMenu?: boolean + onDeleteMessage?: (message: Message) => void +} + +const MessageItem: FC = ({ message, showMenu, onDeleteMessage }) => { const avatar = useAvatar() + const onCopy = () => { + navigator.clipboard.writeText(message.content) + window.message.success({ content: 'Copied!', key: 'copy-message' }) + } + + const onDelete = async () => { + const confirmed = await window.modal.confirm({ + title: 'Delete Message', + content: 'Are you sure you want to delete this message?', + okText: 'Delete', + okType: 'danger' + }) + confirmed && onDeleteMessage?.(message) + } + return ( {message.role === 'assistant' ? : } -
+ +
+ {showMenu && ( + + + + + )} + ) } @@ -28,4 +58,34 @@ const AvatarWrapper = styled.div` margin-right: 10px; ` +const MessageContent = styled.div` + display: flex; + flex-direction: column; + justify-content: space-between; + .menubar { + opacity: 0; + } + &:hover { + .menubar { + opacity: 1; + } + } +` + +const MenusBar = styled.div` + display: flex; + flex-direction: row; + justify-content: flex-start; + gap: 6px; + .anticon { + cursor: pointer; + margin-right: 8px; + font-size: 15px; + color: var(--color-icon); + &:hover { + color: var(--color-text-1); + } + } +` + export default MessageItem diff --git a/src/renderer/src/pages/home/components/Messages.tsx b/src/renderer/src/pages/home/components/Messages.tsx index b1debe7b..dceee778 100644 --- a/src/renderer/src/pages/home/components/Messages.tsx +++ b/src/renderer/src/pages/home/components/Messages.tsx @@ -52,12 +52,21 @@ const Messages: FC = ({ assistant, topic }) => { } }, [assistant, messages, topic, updateTopic]) + const onDeleteMessage = (message: Message) => { + const _messages = messages.filter((m) => m.id !== message.id) + setMessages(_messages) + localforage.setItem(`topic:${topic.id}`, { + ...topic, + messages: _messages + }) + } + useEffect(() => { const unsubscribes = [ EventEmitter.on(EVENT_NAMES.SEND_MESSAGE, async (msg: Message) => { console.debug({ assistant, provider, message: msg, topic }) onSendMessage(msg) - fetchChatCompletion({ assistant, messages: [messages, msg], topic, onResponse: setLastMessage }) + fetchChatCompletion({ assistant, messages: [...messages, msg], topic, onResponse: setLastMessage }) }), EventEmitter.on(EVENT_NAMES.AI_CHAT_COMPLETION, async (msg: Message) => { setLastMessage(null) @@ -84,10 +93,10 @@ const Messages: FC = ({ assistant, topic }) => { useEffect(() => hljs.highlightAll()) return ( - + {lastMessage && } {reverse([...messages]).map((message) => ( - + ))}