mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-24 18:50:56 +08:00
feat: add "Regenerate" in action window
This commit is contained in:
parent
d18436578d
commit
75d2eacf7c
@ -1849,9 +1849,10 @@
|
||||
"original_show": "Show Original",
|
||||
"original_hide": "Hide Original",
|
||||
"original_copy": "Copy Original",
|
||||
"esc_close": "Esc to Close",
|
||||
"esc_stop": "Esc to Stop",
|
||||
"c_copy": "C to Copy"
|
||||
"esc_close": "Esc: Close",
|
||||
"esc_stop": "Esc: Stop",
|
||||
"c_copy": "C: Copy",
|
||||
"r_regenerate": "R: Regenerate"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
|
||||
@ -1851,7 +1851,8 @@
|
||||
"original_copy": "原文をコピー",
|
||||
"esc_close": "Escで閉じる",
|
||||
"esc_stop": "Escで停止",
|
||||
"c_copy": "Cでコピー"
|
||||
"c_copy": "Cでコピー",
|
||||
"r_regenerate": "Rで再生成"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
|
||||
@ -1851,7 +1851,8 @@
|
||||
"original_copy": "Копировать оригинал",
|
||||
"esc_close": "Esc - закрыть",
|
||||
"esc_stop": "Esc - остановить",
|
||||
"c_copy": "C - копировать"
|
||||
"c_copy": "C - копировать",
|
||||
"r_regenerate": "R - перегенерировать"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
|
||||
@ -1851,7 +1851,8 @@
|
||||
"original_copy": "复制原文",
|
||||
"esc_close": "Esc 关闭",
|
||||
"esc_stop": "Esc 停止",
|
||||
"c_copy": "C 复制"
|
||||
"c_copy": "C 复制",
|
||||
"r_regenerate": "R 重新生成"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
|
||||
@ -1851,7 +1851,8 @@
|
||||
"original_copy": "複製原文",
|
||||
"esc_close": "Esc 關閉",
|
||||
"esc_stop": "Esc 停止",
|
||||
"c_copy": "C 複製"
|
||||
"c_copy": "C 複製",
|
||||
"r_regenerate": "R 重新生成"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
|
||||
@ -220,6 +220,12 @@ const ActionGeneral: FC<Props> = React.memo(({ action, scrollToBottom }) => {
|
||||
}
|
||||
}
|
||||
|
||||
const handleRegenerate = () => {
|
||||
setContentToCopy('')
|
||||
setIsLoading(true)
|
||||
fetchResult()
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Container>
|
||||
@ -250,7 +256,7 @@ const ActionGeneral: FC<Props> = React.memo(({ action, scrollToBottom }) => {
|
||||
{error && <ErrorMsg>{error}</ErrorMsg>}
|
||||
</Container>
|
||||
<FooterPadding />
|
||||
<WindowFooter loading={isLoading} onPause={handlePause} content={contentToCopy} />
|
||||
<WindowFooter loading={isLoading} onPause={handlePause} onRegenerate={handleRegenerate} content={contentToCopy} />
|
||||
</>
|
||||
)
|
||||
})
|
||||
@ -315,7 +321,7 @@ const OriginalContentCopyWrapper = styled.div`
|
||||
`
|
||||
|
||||
const FooterPadding = styled.div`
|
||||
min-height: 32px;
|
||||
min-height: 12px;
|
||||
`
|
||||
|
||||
const ErrorMsg = styled.div`
|
||||
|
||||
@ -204,7 +204,7 @@ const OriginalContentCopyWrapper = styled.div`
|
||||
`
|
||||
|
||||
const FooterPadding = styled.div`
|
||||
min-height: 32px;
|
||||
min-height: 12px;
|
||||
`
|
||||
|
||||
const ErrorMsg = styled.div`
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { LoadingOutlined } from '@ant-design/icons'
|
||||
import { CircleX, Copy, Pause } from 'lucide-react'
|
||||
import { CircleX, Copy, Pause, RefreshCw } from 'lucide-react'
|
||||
import { FC, useEffect, useState } from 'react'
|
||||
import { useHotkeys } from 'react-hotkeys-hook'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@ -8,14 +8,22 @@ interface FooterProps {
|
||||
content?: string
|
||||
loading?: boolean
|
||||
onPause?: () => void
|
||||
onRegenerate?: () => void
|
||||
}
|
||||
|
||||
const WindowFooter: FC<FooterProps> = ({ content = '', loading = false, onPause = () => {} }) => {
|
||||
const WindowFooter: FC<FooterProps> = ({
|
||||
content = '',
|
||||
loading = false,
|
||||
onPause = undefined,
|
||||
onRegenerate = undefined
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const [isWindowFocus, setIsWindowFocus] = useState(true)
|
||||
const [isCopyHovered, setIsCopyHovered] = useState(false)
|
||||
const [isEscHovered, setIsEscHovered] = useState(false)
|
||||
const [isRegenerateHovered, setIsRegenerateHovered] = useState(false)
|
||||
const [isContainerHovered, setIsContainerHovered] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('focus', handleWindowFocus)
|
||||
@ -31,6 +39,10 @@ const WindowFooter: FC<FooterProps> = ({ content = '', loading = false, onPause
|
||||
handleCopy()
|
||||
})
|
||||
|
||||
useHotkeys('r', () => {
|
||||
handleRegenerate()
|
||||
})
|
||||
|
||||
useHotkeys('esc', () => {
|
||||
handleEsc()
|
||||
})
|
||||
@ -48,8 +60,26 @@ const WindowFooter: FC<FooterProps> = ({ content = '', loading = false, onPause
|
||||
}
|
||||
}
|
||||
|
||||
const handleRegenerate = () => {
|
||||
setIsRegenerateHovered(true)
|
||||
setTimeout(() => {
|
||||
setIsRegenerateHovered(false)
|
||||
}, 200)
|
||||
|
||||
if (loading && onPause) {
|
||||
onPause()
|
||||
}
|
||||
|
||||
if (onRegenerate) {
|
||||
//wait for a little time
|
||||
setTimeout(() => {
|
||||
onRegenerate()
|
||||
}, 200)
|
||||
}
|
||||
}
|
||||
|
||||
const handleCopy = () => {
|
||||
if (!content) return
|
||||
if (!content || loading) return
|
||||
|
||||
navigator.clipboard
|
||||
.writeText(content)
|
||||
@ -74,7 +104,10 @@ const WindowFooter: FC<FooterProps> = ({ content = '', loading = false, onPause
|
||||
}
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Container
|
||||
onMouseEnter={() => setIsContainerHovered(true)}
|
||||
onMouseLeave={() => setIsContainerHovered(false)}
|
||||
$isHovered={isContainerHovered}>
|
||||
<OpButtonWrapper>
|
||||
<OpButton onClick={handleEsc} $isWindowFocus={isWindowFocus} data-hovered={isEscHovered}>
|
||||
{loading ? (
|
||||
@ -96,6 +129,12 @@ const WindowFooter: FC<FooterProps> = ({ content = '', loading = false, onPause
|
||||
</>
|
||||
)}
|
||||
</OpButton>
|
||||
{onRegenerate && (
|
||||
<OpButton onClick={handleRegenerate} $isWindowFocus={isWindowFocus} data-hovered={isRegenerateHovered}>
|
||||
<RefreshCw size={14} className="btn-icon" />
|
||||
{t('selection.action.window.r_regenerate')}
|
||||
</OpButton>
|
||||
)}
|
||||
<OpButton onClick={handleCopy} $isWindowFocus={isWindowFocus && !!content} data-hovered={isCopyHovered}>
|
||||
<Copy size={14} className="btn-icon" />
|
||||
{t('selection.action.window.c_copy')}
|
||||
@ -105,19 +144,28 @@ const WindowFooter: FC<FooterProps> = ({ content = '', loading = false, onPause
|
||||
)
|
||||
}
|
||||
|
||||
const Container = styled.div`
|
||||
const Container = styled.div<{ $isHovered: boolean }>`
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 8px;
|
||||
right: 8px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
max-width: 480px;
|
||||
min-width: min-content;
|
||||
width: calc(100% - 16px);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 5px 0;
|
||||
padding: 5px 8px;
|
||||
height: 32px;
|
||||
backdrop-filter: blur(8px);
|
||||
border-radius: 8px;
|
||||
opacity: 0;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
`
|
||||
|
||||
const OpButtonWrapper = styled.div`
|
||||
@ -144,6 +192,9 @@ const OpButton = styled.div<{ $isWindowFocus: boolean; $isHovered?: boolean }>`
|
||||
opacity: ${(props) => (props.$isWindowFocus ? 1 : 0.2)};
|
||||
transition: opacity 0.3s ease;
|
||||
transition: color 0.2s ease;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
.btn-icon {
|
||||
color: var(--color-text-secondary);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user