mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-10 15:49:29 +08:00
refactor(translate): streamline TranslatePage layout and component structure
- Removed unused imports and components to simplify the codebase. - Refactored the token count calculation for improved readability. - Adjusted the layout of the operation bar and input/output containers for better spacing and alignment. - Enhanced the copy button functionality and visibility within the output area. - Updated styles for consistency and improved user experience.
This commit is contained in:
parent
d68529096b
commit
30b7028dd8
@ -1,7 +1,6 @@
|
|||||||
import { CheckOutlined, HistoryOutlined, SendOutlined, SwapOutlined } from '@ant-design/icons'
|
import { CheckOutlined, SendOutlined, SwapOutlined } from '@ant-design/icons'
|
||||||
import { loggerService } from '@logger'
|
import { loggerService } from '@logger'
|
||||||
import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar'
|
import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar'
|
||||||
import CopyIcon from '@renderer/components/Icons/CopyIcon'
|
|
||||||
import LanguageSelect from '@renderer/components/LanguageSelect'
|
import LanguageSelect from '@renderer/components/LanguageSelect'
|
||||||
import ModelSelectButton from '@renderer/components/ModelSelectButton'
|
import ModelSelectButton from '@renderer/components/ModelSelectButton'
|
||||||
import { isEmbeddingModel, isRerankModel, isTextToImageModel } from '@renderer/config/models'
|
import { isEmbeddingModel, isRerankModel, isTextToImageModel } from '@renderer/config/models'
|
||||||
@ -26,7 +25,7 @@ import {
|
|||||||
import { Button, Flex, Popover, Tooltip, Typography } from 'antd'
|
import { Button, Flex, Popover, Tooltip, Typography } from 'antd'
|
||||||
import TextArea, { TextAreaRef } from 'antd/es/input/TextArea'
|
import TextArea, { TextAreaRef } from 'antd/es/input/TextArea'
|
||||||
import { isEmpty, throttle } from 'lodash'
|
import { isEmpty, throttle } from 'lodash'
|
||||||
import { Settings2 } from 'lucide-react'
|
import { CopyIcon, FolderClock, Settings2 } from 'lucide-react'
|
||||||
import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
@ -364,9 +363,7 @@ const TranslatePage: FC = () => {
|
|||||||
}, [bidirectionalPair, isBidirectional, sourceLanguage, targetLanguage.langCode, text])
|
}, [bidirectionalPair, isBidirectional, sourceLanguage, targetLanguage.langCode, text])
|
||||||
|
|
||||||
// 控制token估计
|
// 控制token估计
|
||||||
const tokenCount = useMemo(() => {
|
const tokenCount = useMemo(() => estimateTextTokens(text + prompt), [prompt, text])
|
||||||
return estimateTextTokens(text + prompt)
|
|
||||||
}, [prompt, text])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container id="translate-page">
|
<Container id="translate-page">
|
||||||
@ -381,29 +378,14 @@ const TranslatePage: FC = () => {
|
|||||||
/>
|
/>
|
||||||
<OperationBar>
|
<OperationBar>
|
||||||
<InnerOperationBar style={{ justifyContent: 'flex-start' }}>
|
<InnerOperationBar style={{ justifyContent: 'flex-start' }}>
|
||||||
<TranslateButton translating={translating} onTranslate={onTranslate} couldTranslate={couldTranslate} />
|
|
||||||
<ModelSelectButton
|
|
||||||
model={translateModel}
|
|
||||||
onSelectModel={handleModelChange}
|
|
||||||
modelFilter={modelPredicate}
|
|
||||||
tooltipProps={{ placement: 'bottom' }}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
type="text"
|
|
||||||
icon={<Settings2 size={18} />}
|
|
||||||
onClick={() => setSettingsVisible(true)}
|
|
||||||
style={{ color: 'var(--color-text-2)', display: 'flex' }}
|
|
||||||
/>
|
|
||||||
<Button
|
<Button
|
||||||
className="nodrag"
|
className="nodrag"
|
||||||
color="default"
|
color="default"
|
||||||
variant={historyDrawerVisible ? 'filled' : 'text'}
|
variant={historyDrawerVisible ? 'filled' : 'text'}
|
||||||
type="text"
|
type="text"
|
||||||
icon={<HistoryOutlined />}
|
icon={<FolderClock size={18} />}
|
||||||
onClick={() => setHistoryDrawerVisible(!historyDrawerVisible)}
|
onClick={() => setHistoryDrawerVisible(!historyDrawerVisible)}
|
||||||
/>
|
/>
|
||||||
</InnerOperationBar>
|
|
||||||
<InnerOperationBar style={{ justifyContent: 'center' }}>
|
|
||||||
<LanguageSelect
|
<LanguageSelect
|
||||||
showSearch
|
showSearch
|
||||||
style={{ width: 200 }}
|
style={{ width: 200 }}
|
||||||
@ -425,61 +407,69 @@ const TranslatePage: FC = () => {
|
|||||||
/>
|
/>
|
||||||
<Tooltip title={t('translate.exchange.label')} placement="bottom">
|
<Tooltip title={t('translate.exchange.label')} placement="bottom">
|
||||||
<Button
|
<Button
|
||||||
|
type="text"
|
||||||
icon={<SwapOutlined />}
|
icon={<SwapOutlined />}
|
||||||
style={{ aspectRatio: '1/1' }}
|
style={{ margin: '0 -2px' }}
|
||||||
onClick={handleExchange}
|
onClick={handleExchange}
|
||||||
disabled={!couldExchange}></Button>
|
disabled={!couldExchange}
|
||||||
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
{getLanguageDisplay()}
|
{getLanguageDisplay()}
|
||||||
|
<TranslateButton translating={translating} onTranslate={onTranslate} couldTranslate={couldTranslate} />
|
||||||
</InnerOperationBar>
|
</InnerOperationBar>
|
||||||
<InnerOperationBar style={{ justifyContent: 'flex-end' }}>
|
<InnerOperationBar style={{ justifyContent: 'flex-end' }}>
|
||||||
<Button
|
<ModelSelectButton
|
||||||
type="text"
|
model={translateModel}
|
||||||
onClick={onCopy}
|
onSelectModel={handleModelChange}
|
||||||
disabled={!translatedContent}
|
modelFilter={modelPredicate}
|
||||||
icon={copied ? <CheckOutlined style={{ color: 'var(--color-primary)' }} /> : <CopyIcon />}
|
tooltipProps={{ placement: 'bottom' }}
|
||||||
/>
|
/>
|
||||||
|
<Button type="text" icon={<Settings2 size={18} />} onClick={() => setSettingsVisible(true)} />
|
||||||
</InnerOperationBar>
|
</InnerOperationBar>
|
||||||
</OperationBar>
|
</OperationBar>
|
||||||
<AreaContainer>
|
<AreaContainer>
|
||||||
<InputContainer>
|
<InputContainer>
|
||||||
<InputAreaContainer>
|
<Textarea
|
||||||
<Textarea
|
ref={textAreaRef}
|
||||||
ref={textAreaRef}
|
variant="borderless"
|
||||||
variant="borderless"
|
placeholder={t('translate.input.placeholder')}
|
||||||
placeholder={t('translate.input.placeholder')}
|
value={text}
|
||||||
value={text}
|
onChange={(e) => setText(e.target.value)}
|
||||||
onChange={(e) => setText(e.target.value)}
|
onKeyDown={onKeyDown}
|
||||||
onKeyDown={onKeyDown}
|
onScroll={handleInputScroll}
|
||||||
onScroll={handleInputScroll}
|
disabled={translating}
|
||||||
disabled={translating}
|
spellCheck={false}
|
||||||
spellCheck={false}
|
allowClear
|
||||||
allowClear
|
/>
|
||||||
/>
|
<Footer>
|
||||||
<Footer>
|
<Popover content={t('chat.input.estimated_tokens.tip')}>
|
||||||
<Popover content={t('chat.input.estimated_tokens.tip')}>
|
<Typography.Text style={{ color: 'var(--color-text-3)', paddingRight: 8 }}>
|
||||||
<Typography.Text style={{ color: 'var(--color-text-3)', paddingRight: 8 }}>
|
{tokenCount}
|
||||||
{tokenCount}
|
</Typography.Text>
|
||||||
</Typography.Text>
|
</Popover>
|
||||||
</Popover>
|
</Footer>
|
||||||
</Footer>
|
|
||||||
</InputAreaContainer>
|
|
||||||
</InputContainer>
|
</InputContainer>
|
||||||
|
|
||||||
<OutputContainer>
|
<OutputContainer>
|
||||||
<OutputAreaContainer>
|
<CopyButton
|
||||||
<OutputText ref={outputTextRef} onScroll={handleOutputScroll} className={'selectable'}>
|
type="text"
|
||||||
{!translatedContent ? (
|
size="small"
|
||||||
<div style={{ color: 'var(--color-text-3)', userSelect: 'none' }}>
|
className="copy-button"
|
||||||
{t('translate.output.placeholder')}
|
onClick={onCopy}
|
||||||
</div>
|
disabled={!translatedContent}
|
||||||
) : enableMarkdown ? (
|
icon={copied ? <CheckOutlined style={{ color: 'var(--color-primary)' }} /> : <CopyIcon size={16} />}
|
||||||
<div className="markdown" dangerouslySetInnerHTML={{ __html: renderedMarkdown }} />
|
/>
|
||||||
) : (
|
<OutputText ref={outputTextRef} onScroll={handleOutputScroll} className={'selectable'}>
|
||||||
<div className="plain">{translatedContent}</div>
|
{!translatedContent ? (
|
||||||
)}
|
<div style={{ color: 'var(--color-text-3)', userSelect: 'none' }}>
|
||||||
</OutputText>
|
{t('translate.output.placeholder')}
|
||||||
</OutputAreaContainer>
|
</div>
|
||||||
|
) : enableMarkdown ? (
|
||||||
|
<div className="markdown" dangerouslySetInnerHTML={{ __html: renderedMarkdown }} />
|
||||||
|
) : (
|
||||||
|
<div className="plain">{translatedContent}</div>
|
||||||
|
)}
|
||||||
|
</OutputText>
|
||||||
</OutputContainer>
|
</OutputContainer>
|
||||||
</AreaContainer>
|
</AreaContainer>
|
||||||
</ContentContainer>
|
</ContentContainer>
|
||||||
@ -510,10 +500,13 @@ const ContentContainer = styled.div<{ $historyDrawerVisible: boolean }>`
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 4px;
|
gap: 6px;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding: 15px;
|
padding: 12px;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
[navbar-position='left'] & {
|
||||||
|
padding: 12px 16px;
|
||||||
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
const AreaContainer = styled.div`
|
const AreaContainer = styled.div`
|
||||||
@ -527,19 +520,11 @@ const InputContainer = styled.div`
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding-bottom: 5px;
|
padding: 10px 5px;
|
||||||
padding-right: 2px;
|
|
||||||
`
|
|
||||||
|
|
||||||
const InputAreaContainer = styled.div`
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
flex: 1;
|
|
||||||
flex-direction: column;
|
|
||||||
border: 1px solid var(--color-border-soft);
|
border: 1px solid var(--color-border-soft);
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
padding-bottom: 5px;
|
height: calc(100vh - var(--navbar-height) - 70px);
|
||||||
padding-right: 2px;
|
overflow: hidden;
|
||||||
`
|
`
|
||||||
|
|
||||||
const Textarea = styled(TextArea)`
|
const Textarea = styled(TextArea)`
|
||||||
@ -564,26 +549,32 @@ const Footer = styled.div`
|
|||||||
`
|
`
|
||||||
|
|
||||||
const OutputContainer = styled.div`
|
const OutputContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: column;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
flex: 1;
|
|
||||||
border-radius: 10px;
|
|
||||||
padding-bottom: 5px;
|
|
||||||
padding-right: 2px;
|
|
||||||
`
|
|
||||||
|
|
||||||
const OutputAreaContainer = styled.div`
|
|
||||||
min-height: 0;
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
flex: 1;
|
|
||||||
background-color: var(--color-background-soft);
|
background-color: var(--color-background-soft);
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
padding-bottom: 5px;
|
padding: 10px 5px;
|
||||||
padding-right: 2px;
|
height: calc(100vh - var(--navbar-height) - 70px);
|
||||||
|
|
||||||
|
&:hover .copy-button {
|
||||||
|
opacity: 1;
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const CopyButton = styled(Button)`
|
||||||
|
position: absolute;
|
||||||
|
top: 8px;
|
||||||
|
right: 8px;
|
||||||
|
z-index: 10;
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
transition:
|
||||||
|
opacity 0.2s ease-in-out,
|
||||||
|
visibility 0.2s ease-in-out;
|
||||||
`
|
`
|
||||||
|
|
||||||
const OutputText = styled.div`
|
const OutputText = styled.div`
|
||||||
@ -651,10 +642,10 @@ const BidirectionalLanguageDisplay = styled.div`
|
|||||||
`
|
`
|
||||||
|
|
||||||
const OperationBar = styled.div`
|
const OperationBar = styled.div`
|
||||||
display: grid;
|
display: flex;
|
||||||
grid-template-columns: repeat(3, 1fr);
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: space-between;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
padding-bottom: 4px;
|
padding-bottom: 4px;
|
||||||
`
|
`
|
||||||
@ -663,7 +654,7 @@ const InnerOperationBar = styled.div`
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 4px;
|
gap: 8px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
`
|
`
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user