feat: added copy last message feature and translations

This commit is contained in:
kangfenmao 2025-01-20 11:07:08 +08:00
parent c510f5dcce
commit 749353f460
11 changed files with 65 additions and 36 deletions

View File

@ -667,7 +667,8 @@
"footer": {
"esc": "Press ESC {{action}}",
"esc_close": "close the window",
"esc_back": "back"
"esc_back": "back",
"copy_last_message": "Press C to copy"
}
}
}

View File

@ -652,7 +652,8 @@
"footer": {
"esc": "ESC キーを押して{{action}}",
"esc_close": "ウィンドウを閉じる",
"esc_back": "戻る"
"esc_back": "戻る",
"copy_last_message": "C キーを押してコピー"
}
}
}

View File

@ -666,7 +666,8 @@
"footer": {
"esc": "Нажмите ESC {{action}}",
"esc_close": "закрытия окна",
"esc_back": "возвращения"
"esc_back": "возвращения",
"copy_last_message": "Нажмите C для копирования"
}
}
}

View File

@ -655,7 +655,8 @@
"footer": {
"esc": "按 ESC {{action}}",
"esc_close": "关闭窗口",
"esc_back": "返回"
"esc_back": "返回",
"copy_last_message": "按 C 键复制"
}
}
}

View File

@ -654,7 +654,8 @@
"footer": {
"esc": "按 ESC {{action}}",
"esc_close": "關閉窗口",
"esc_back": "返回"
"esc_back": "返回",
"copy_last_message": "按 C 鍵複製"
}
}
}

View File

@ -133,6 +133,7 @@ const settingsSlice = createSlice({
},
setLanguage: (state, action: PayloadAction<LanguageVarious>) => {
state.language = action.payload
window.electron.ipcRenderer.send('miniwindow-reload')
},
setProxyMode: (state, action: PayloadAction<'system' | 'custom' | 'none'>) => {
state.proxyMode = action.payload

View File

@ -1,10 +1,10 @@
import Scrollbar from '@renderer/components/Scrollbar'
import { useShortcut } from '@renderer/hooks/useShortcuts'
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
import { getAssistantMessage } from '@renderer/services/MessagesService'
import { Assistant, Message } from '@renderer/types'
import { last } from 'lodash'
import { FC, useCallback, useEffect, useRef, useState } from 'react'
import { useHotkeys } from 'react-hotkeys-hook'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
@ -49,7 +49,7 @@ const Messages: FC<Props> = ({ assistant, route }) => {
return () => unsubscribes.forEach((unsub) => unsub())
}, [assistant.id, onSendMessage])
useShortcut('copy_last_message', () => {
useHotkeys('c', () => {
const lastMessage = last(messages)
if (lastMessage) {
navigator.clipboard.writeText(lastMessage.content)

View File

@ -7,7 +7,8 @@ import { EventEmitter } from '@renderer/services/EventService'
import { uuid } from '@renderer/utils'
import { Divider } from 'antd'
import dayjs from 'dayjs'
import { FC, useCallback, useEffect, useRef, useState } from 'react'
import { isEmpty } from 'lodash'
import { FC, useCallback, useEffect, useState } from 'react'
import { useHotkeys } from 'react-hotkeys-hook'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
@ -28,11 +29,10 @@ const HomeWindow: FC = () => {
const { defaultModel: model } = useDefaultModel()
const { language } = useSettings()
const { t } = useTranslation()
const textRef = useRef(text)
const referenceText = selectedText || clipboardText || text
textRef.current = referenceText === text ? text : `${referenceText}\n\n${text}`
const content = (referenceText === text ? text : `${referenceText}\n\n${text}`).trim()
const onReadClipboard = useCallback(async () => {
const text = await navigator.clipboard.readText()
@ -59,23 +59,25 @@ const HomeWindow: FC = () => {
if (e.key === 'Enter') {
e.preventDefault()
if (textRef.current.trim() === '') {
return
if (content) {
setRoute('chat')
onSendMessage()
setTimeout(() => setText(''), 100)
}
setRoute('chat')
onSendMessage()
setTimeout(() => setText(''), 100)
}
}
const onSendMessage = useCallback(
async (prompt?: string) => {
const text = textRef.current.trim()
if (isEmpty(content)) {
return
}
setTimeout(() => {
const message = {
id: uuid(),
role: 'user',
content: prompt ? `${prompt}\n\n${text}` : text,
content: prompt ? `${prompt}\n\n${content}` : content,
assistantId: defaultAssistant.id,
topicId: defaultAssistant.topics[0].id || uuid(),
createdAt: dayjs().format('YYYY-MM-DD HH:mm:ss'),
@ -85,7 +87,7 @@ const HomeWindow: FC = () => {
EventEmitter.emit(EVENT_NAMES.SEND_MESSAGE, message)
}, 0)
},
[defaultAssistant]
[content, defaultAssistant.id, defaultAssistant.topics]
)
const clearClipboard = () => {
@ -104,12 +106,8 @@ const HomeWindow: FC = () => {
})
useEffect(() => {
window.electron.ipcRenderer.on('show-mini-window', () => {
onReadClipboard()
})
window.electron.ipcRenderer.on('show-mini-window', onReadClipboard)
window.electron.ipcRenderer.on('selection-action', (_, { action, selectedText }) => {
console.debug('[HomeWindow] selection-action', action, selectedText)
selectedText && setSelectedText(selectedText)
action && setRoute(action)
action === 'chat' && onSendMessage()
@ -176,7 +174,7 @@ const HomeWindow: FC = () => {
<Divider style={{ margin: '10px 0' }} />
<ClipboardPreview referenceText={referenceText} clearClipboard={clearClipboard} t={t} />
<Main>
<FeatureMenus setRoute={setRoute} onSendMessage={onSendMessage} />
<FeatureMenus setRoute={setRoute} onSendMessage={onSendMessage} text={content} />
</Main>
<Divider style={{ margin: '10px 0' }} />
<Footer

View File

@ -6,11 +6,12 @@ import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
interface FeatureMenusProps {
text: string
setRoute: Dispatch<SetStateAction<'translate' | 'summary' | 'chat' | 'explanation' | 'home'>>
onSendMessage: (prompt?: string) => void
}
const FeatureMenus: FC<FeatureMenusProps> = ({ setRoute, onSendMessage }) => {
const FeatureMenus: FC<FeatureMenusProps> = ({ text, setRoute, onSendMessage }) => {
const { t } = useTranslation()
const features = [
@ -19,29 +20,35 @@ const FeatureMenus: FC<FeatureMenusProps> = ({ setRoute, onSendMessage }) => {
title: t('miniwindow.feature.chat'),
active: true,
onClick: () => {
setRoute('chat')
onSendMessage()
if (text) {
setRoute('chat')
onSendMessage()
}
}
},
{
icon: <TranslationOutlined style={{ fontSize: '16px', color: 'var(--color-text)' }} />,
title: t('miniwindow.feature.translate'),
onClick: () => setRoute('translate')
onClick: () => text && setRoute('translate')
},
{
icon: <FileTextOutlined style={{ fontSize: '16px', color: 'var(--color-text)' }} />,
title: t('miniwindow.feature.summary'),
onClick: () => {
setRoute('summary')
onSendMessage(t('prompts.summarize'))
if (text) {
setRoute('summary')
onSendMessage(t('prompts.summarize'))
}
}
},
{
icon: <BulbOutlined style={{ fontSize: '16px', color: 'var(--color-text)' }} />,
title: t('miniwindow.feature.explanation'),
onClick: () => {
setRoute('explanation')
onSendMessage(t('prompts.explanation'))
if (text) {
setRoute('explanation')
onSendMessage(t('prompts.explanation'))
}
}
}
]

View File

@ -1,3 +1,5 @@
import { CopyOutlined, LoginOutlined } from '@ant-design/icons'
import { Tag } from 'antd'
import { FC } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
@ -13,9 +15,16 @@ const Footer: FC<FooterProps> = ({ route, onExit }) => {
return (
<WindowFooter onClick={() => onExit()}>
<FooterText className="nodrag">
{t('miniwindow.footer.esc', {
action: route === 'home' ? t('miniwindow.footer.esc_close') : t('miniwindow.footer.esc_back')
})}
<Tag bordered={false} icon={<LoginOutlined />}>
{t('miniwindow.footer.esc', {
action: route === 'home' ? t('miniwindow.footer.esc_close') : t('miniwindow.footer.esc_back')
})}
</Tag>
{route !== 'home' && (
<Tag bordered={false} icon={<CopyOutlined />}>
{t('miniwindow.footer.copy_last_message')}
</Tag>
)}
</FooterText>
</WindowFooter>
)
@ -29,7 +38,11 @@ const WindowFooter = styled.div`
cursor: pointer;
`
const FooterText = styled.span`
const FooterText = styled.div`
display: flex;
align-items: center;
justify-content: center;
gap: 5px;
color: var(--color-text-secondary);
font-size: 12px;
`

View File

@ -10,6 +10,7 @@ import { runAsyncFunction, uuid } from '@renderer/utils'
import { Select, Space } from 'antd'
import { isEmpty } from 'lodash'
import { FC, useCallback, useEffect, useRef, useState } from 'react'
import { useHotkeys } from 'react-hotkeys-hook'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
@ -71,6 +72,10 @@ const Translate: FC<Props> = ({ text }) => {
translate()
}, [translate])
useHotkeys('c', () => {
navigator.clipboard.writeText(result)
})
return (
<Container>
<MenuContainer>