feat: add message and modal api to window object

This commit is contained in:
kangfenmao 2024-07-06 21:02:47 +08:00
parent a2ec18d9be
commit f8d9b437c2
5 changed files with 99 additions and 15 deletions

View File

@ -1,5 +1,6 @@
import { message, Modal } from 'antd'
import { findIndex, pullAt } from 'lodash' import { findIndex, pullAt } from 'lodash'
import React, { useState } from 'react' import React, { useEffect, useState } from 'react'
let id = 0 let id = 0
let onPop = () => {} let onPop = () => {}
@ -17,6 +18,8 @@ type ElementItem = {
const TopViewContainer: React.FC<Props> = ({ children }) => { const TopViewContainer: React.FC<Props> = ({ children }) => {
const [elements, setElements] = useState<ElementItem[]>([]) const [elements, setElements] = useState<ElementItem[]>([])
const [messageApi, messageContextHolder] = message.useMessage()
const [modal, modalContextHolder] = Modal.useModal()
onPop = () => { onPop = () => {
const views = [...elements] const views = [...elements]
@ -34,9 +37,16 @@ const TopViewContainer: React.FC<Props> = ({ children }) => {
setElements(views) setElements(views)
} }
useEffect(() => {
window.message = messageApi
window.modal = modal
}, [messageApi, modal])
return ( return (
<> <>
{children} {children}
{messageContextHolder}
{modalContextHolder}
{elements.length > 0 && ( {elements.length > 0 && (
<div style={{ display: 'flex', flex: 1, position: 'absolute', width: '100%', height: '100%' }}> <div style={{ display: 'flex', flex: 1, position: 'absolute', width: '100%', height: '100%' }}>
<div style={{ position: 'absolute', width: '100%', height: '100%' }} onClick={onPop} /> <div style={{ position: 'absolute', width: '100%', height: '100%' }} onClick={onPop} />

View File

@ -1 +1,11 @@
/// <reference types="vite/client" /> /// <reference types="vite/client" />
import { MessageInstance } from 'antd/es/message/interface'
import { HookAPI } from 'antd/es/modal/useModal'
declare global {
interface Window {
message: MessageInstance
modal: HookAPI
}
}

View File

@ -1,6 +1,6 @@
import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar' import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar'
import { SYSTEM_ASSISTANTS } from '@renderer/config/assistant' 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 { find, groupBy } from 'lodash'
import { FC } from 'react' import { FC } from 'react'
import styled from 'styled-components' import styled from 'styled-components'
@ -14,26 +14,21 @@ const { Title } = Typography
const AppsPage: FC = () => { const AppsPage: FC = () => {
const { assistants, addAssistant } = useAssistants() const { assistants, addAssistant } = useAssistants()
const assistantGroups = groupBy(SYSTEM_ASSISTANTS, 'group') const assistantGroups = groupBy(SYSTEM_ASSISTANTS, 'group')
const [messageApi, contextHolder] = message.useMessage()
const onAddAssistant = (assistant: SystemAssistant) => { const onAddAssistant = (assistant: SystemAssistant) => {
addAssistant({ addAssistant({
...getDefaultAssistant(), ...getDefaultAssistant(),
...assistant ...assistant
}) })
messageApi.destroy() window.message.success({
messageApi.open({
type: 'success',
content: 'Assistant added successfully', content: 'Assistant added successfully',
style: { key: 'assistant-added',
marginTop: '5vh' style: { marginTop: '5vh' }
}
}) })
} }
return ( return (
<Container> <Container>
{contextHolder}
<Navbar> <Navbar>
<NavbarCenter>Assistant Market</NavbarCenter> <NavbarCenter>Assistant Market</NavbarCenter>
</Navbar> </Navbar>

View File

@ -5,14 +5,44 @@ import { FC } from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import Logo from '@renderer/assets/images/logo.png' import Logo from '@renderer/assets/images/logo.png'
import useAvatar from '@renderer/hooks/useAvatar' 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<Props> = ({ message, showMenu, onDeleteMessage }) => {
const avatar = useAvatar() 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 ( return (
<MessageContainer key={message.id}> <MessageContainer key={message.id}>
<AvatarWrapper>{message.role === 'assistant' ? <Avatar src={Logo} /> : <Avatar src={avatar} />}</AvatarWrapper> <AvatarWrapper>{message.role === 'assistant' ? <Avatar src={Logo} /> : <Avatar src={avatar} />}</AvatarWrapper>
<div className="markdown" dangerouslySetInnerHTML={{ __html: marked(message.content) }} /> <MessageContent>
<div className="markdown" dangerouslySetInnerHTML={{ __html: marked(message.content) }} />
{showMenu && (
<MenusBar className="menubar">
<CopyOutlined onClick={onCopy} />
<DeleteOutlined onClick={onDelete} />
</MenusBar>
)}
</MessageContent>
</MessageContainer> </MessageContainer>
) )
} }
@ -28,4 +58,34 @@ const AvatarWrapper = styled.div`
margin-right: 10px; 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 export default MessageItem

View File

@ -52,12 +52,21 @@ const Messages: FC<Props> = ({ assistant, topic }) => {
} }
}, [assistant, messages, topic, updateTopic]) }, [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(() => { useEffect(() => {
const unsubscribes = [ const unsubscribes = [
EventEmitter.on(EVENT_NAMES.SEND_MESSAGE, async (msg: Message) => { EventEmitter.on(EVENT_NAMES.SEND_MESSAGE, async (msg: Message) => {
console.debug({ assistant, provider, message: msg, topic }) console.debug({ assistant, provider, message: msg, topic })
onSendMessage(msg) 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) => { EventEmitter.on(EVENT_NAMES.AI_CHAT_COMPLETION, async (msg: Message) => {
setLastMessage(null) setLastMessage(null)
@ -84,10 +93,10 @@ const Messages: FC<Props> = ({ assistant, topic }) => {
useEffect(() => hljs.highlightAll()) useEffect(() => hljs.highlightAll())
return ( return (
<Container id="topics"> <Container id="messages">
{lastMessage && <MessageItem message={lastMessage} />} {lastMessage && <MessageItem message={lastMessage} />}
{reverse([...messages]).map((message) => ( {reverse([...messages]).map((message) => (
<MessageItem message={message} key={message.id} /> <MessageItem message={message} key={message.id} showMenu onDeleteMessage={onDeleteMessage} />
))} ))}
<MessageItem message={assistantDefaultMessage} /> <MessageItem message={assistantDefaultMessage} />
</Container> </Container>