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 { 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;
` `