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:
kangfenmao 2025-08-11 16:11:39 +08:00
parent d68529096b
commit 30b7028dd8

View File

@ -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 { Navbar, NavbarCenter } from '@renderer/components/app/Navbar'
import CopyIcon from '@renderer/components/Icons/CopyIcon'
import LanguageSelect from '@renderer/components/LanguageSelect'
import ModelSelectButton from '@renderer/components/ModelSelectButton'
import { isEmbeddingModel, isRerankModel, isTextToImageModel } from '@renderer/config/models'
@ -26,7 +25,7 @@ import {
import { Button, Flex, Popover, Tooltip, Typography } from 'antd'
import TextArea, { TextAreaRef } from 'antd/es/input/TextArea'
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 { useTranslation } from 'react-i18next'
import styled from 'styled-components'
@ -364,9 +363,7 @@ const TranslatePage: FC = () => {
}, [bidirectionalPair, isBidirectional, sourceLanguage, targetLanguage.langCode, text])
// 控制token估计
const tokenCount = useMemo(() => {
return estimateTextTokens(text + prompt)
}, [prompt, text])
const tokenCount = useMemo(() => estimateTextTokens(text + prompt), [prompt, text])
return (
<Container id="translate-page">
@ -381,29 +378,14 @@ const TranslatePage: FC = () => {
/>
<OperationBar>
<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
className="nodrag"
color="default"
variant={historyDrawerVisible ? 'filled' : 'text'}
type="text"
icon={<HistoryOutlined />}
icon={<FolderClock size={18} />}
onClick={() => setHistoryDrawerVisible(!historyDrawerVisible)}
/>
</InnerOperationBar>
<InnerOperationBar style={{ justifyContent: 'center' }}>
<LanguageSelect
showSearch
style={{ width: 200 }}
@ -425,61 +407,69 @@ const TranslatePage: FC = () => {
/>
<Tooltip title={t('translate.exchange.label')} placement="bottom">
<Button
type="text"
icon={<SwapOutlined />}
style={{ aspectRatio: '1/1' }}
style={{ margin: '0 -2px' }}
onClick={handleExchange}
disabled={!couldExchange}></Button>
disabled={!couldExchange}
/>
</Tooltip>
{getLanguageDisplay()}
<TranslateButton translating={translating} onTranslate={onTranslate} couldTranslate={couldTranslate} />
</InnerOperationBar>
<InnerOperationBar style={{ justifyContent: 'flex-end' }}>
<Button
type="text"
onClick={onCopy}
disabled={!translatedContent}
icon={copied ? <CheckOutlined style={{ color: 'var(--color-primary)' }} /> : <CopyIcon />}
<ModelSelectButton
model={translateModel}
onSelectModel={handleModelChange}
modelFilter={modelPredicate}
tooltipProps={{ placement: 'bottom' }}
/>
<Button type="text" icon={<Settings2 size={18} />} onClick={() => setSettingsVisible(true)} />
</InnerOperationBar>
</OperationBar>
<AreaContainer>
<InputContainer>
<InputAreaContainer>
<Textarea
ref={textAreaRef}
variant="borderless"
placeholder={t('translate.input.placeholder')}
value={text}
onChange={(e) => setText(e.target.value)}
onKeyDown={onKeyDown}
onScroll={handleInputScroll}
disabled={translating}
spellCheck={false}
allowClear
/>
<Footer>
<Popover content={t('chat.input.estimated_tokens.tip')}>
<Typography.Text style={{ color: 'var(--color-text-3)', paddingRight: 8 }}>
{tokenCount}
</Typography.Text>
</Popover>
</Footer>
</InputAreaContainer>
<Textarea
ref={textAreaRef}
variant="borderless"
placeholder={t('translate.input.placeholder')}
value={text}
onChange={(e) => setText(e.target.value)}
onKeyDown={onKeyDown}
onScroll={handleInputScroll}
disabled={translating}
spellCheck={false}
allowClear
/>
<Footer>
<Popover content={t('chat.input.estimated_tokens.tip')}>
<Typography.Text style={{ color: 'var(--color-text-3)', paddingRight: 8 }}>
{tokenCount}
</Typography.Text>
</Popover>
</Footer>
</InputContainer>
<OutputContainer>
<OutputAreaContainer>
<OutputText ref={outputTextRef} onScroll={handleOutputScroll} className={'selectable'}>
{!translatedContent ? (
<div style={{ color: 'var(--color-text-3)', userSelect: 'none' }}>
{t('translate.output.placeholder')}
</div>
) : enableMarkdown ? (
<div className="markdown" dangerouslySetInnerHTML={{ __html: renderedMarkdown }} />
) : (
<div className="plain">{translatedContent}</div>
)}
</OutputText>
</OutputAreaContainer>
<CopyButton
type="text"
size="small"
className="copy-button"
onClick={onCopy}
disabled={!translatedContent}
icon={copied ? <CheckOutlined style={{ color: 'var(--color-primary)' }} /> : <CopyIcon size={16} />}
/>
<OutputText ref={outputTextRef} onScroll={handleOutputScroll} className={'selectable'}>
{!translatedContent ? (
<div style={{ color: 'var(--color-text-3)', userSelect: 'none' }}>
{t('translate.output.placeholder')}
</div>
) : enableMarkdown ? (
<div className="markdown" dangerouslySetInnerHTML={{ __html: renderedMarkdown }} />
) : (
<div className="plain">{translatedContent}</div>
)}
</OutputText>
</OutputContainer>
</AreaContainer>
</ContentContainer>
@ -510,10 +500,13 @@ const ContentContainer = styled.div<{ $historyDrawerVisible: boolean }>`
width: 100%;
display: flex;
flex-direction: column;
gap: 4px;
gap: 6px;
flex: 1;
padding: 15px;
padding: 12px;
position: relative;
[navbar-position='left'] & {
padding: 12px 16px;
}
`
const AreaContainer = styled.div`
@ -527,19 +520,11 @@ const InputContainer = styled.div`
display: flex;
flex: 1;
flex-direction: column;
padding-bottom: 5px;
padding-right: 2px;
`
const InputAreaContainer = styled.div`
position: relative;
display: flex;
flex: 1;
flex-direction: column;
padding: 10px 5px;
border: 1px solid var(--color-border-soft);
border-radius: 10px;
padding-bottom: 5px;
padding-right: 2px;
height: calc(100vh - var(--navbar-height) - 70px);
overflow: hidden;
`
const Textarea = styled(TextArea)`
@ -564,26 +549,32 @@ const Footer = styled.div`
`
const OutputContainer = styled.div`
display: flex;
flex: 1;
flex-direction: column;
min-height: 0;
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);
border-radius: 10px;
padding-bottom: 5px;
padding-right: 2px;
padding: 10px 5px;
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`
@ -651,10 +642,10 @@ const BidirectionalLanguageDisplay = styled.div`
`
const OperationBar = styled.div`
display: grid;
grid-template-columns: repeat(3, 1fr);
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
justify-content: space-between;
gap: 4px;
padding-bottom: 4px;
`
@ -663,7 +654,7 @@ const InnerOperationBar = styled.div`
display: flex;
flex-direction: row;
align-items: center;
gap: 4px;
gap: 8px;
overflow: hidden;
`