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