UNPKG

fnglish-notebook

Version:

A cross-platform CLI tool for English learning with translation, voice pronunciation, and Notion integration. Supports Windows, macOS, and Linux.

177 lines 9.27 kB
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; import { useState } from 'react'; import { Text, Box, useInput } from 'ink'; import Spinner from 'ink-spinner'; import { Logo } from './Logo.js'; import { ClaudeInput } from './ClaudeInput.js'; import { TencentTranslationService } from '../services/tencent.js'; import { GrokService } from '../services/grok.js'; import { NotionService } from '../services/notion.js'; import { SpeechService } from '../services/speech.js'; import { ClipboardManager } from '../utils/clipboard.js'; export const MainScreen = ({ config }) => { const [input, setInput] = useState(''); const [isLoading, setIsLoading] = useState(false); const [translationResult, setTranslationResult] = useState(null); const [wordInfo, setWordInfo] = useState(null); const [status, setStatus] = useState(''); const [error, setError] = useState(''); // Use config.notionDatabaseId directly. const databaseId = config.notionDatabaseId; const tencentService = new TencentTranslationService(); const grokService = new GrokService(config); const notionService = new NotionService(config); const clipboardManager = new ClipboardManager(); const speechService = new SpeechService('AIzaSyDBWchIinFsfLUD510QqvFzjvzSSZRglkw'); // Removed initializeNotionDatabase useEffect since we now use ID from config directly. // 监听Ctrl+V和Ctrl+S键盘事件 useInput((input, key) => { if (key.ctrl && input === 'v') { // 立即处理剪贴板图片 handleClipboardImage(); // 延迟清空输入框,确保'v'字符不会显示 setTimeout(() => { setInput(''); }, 0); return; } if (key.ctrl && input === 's') { // 播放输入框中的文本 handleSpeech(); // 延迟清空输入框中可能输入的's'字符,确保's'字符不会显示 setTimeout(() => { // 如果输入框最后一个字符是's',则移除它 setInput(prev => prev.endsWith('s') ? prev.slice(0, -1) : prev); }, 0); return; } }); const handleTranslation = async (text) => { if (!text.trim()) return; setIsLoading(true); setError(''); setTranslationResult(null); setWordInfo(null); try { // 1. 翻译文本 setStatus('正在翻译...'); const translation = await tencentService.translateText(text); setTranslationResult(translation); // 2. 如果是单个单词或连字符单词,生成详细信息 if (text.trim().split(' ').length === 1 && /^[a-zA-Z-]+$/.test(text.trim())) { setStatus('正在生成单词信息...'); const info = await grokService.generateWordInfo(text.trim(), config.englishLevel); setWordInfo(info); // 3. 添加到Notion if (databaseId) { setStatus('正在保存...'); const wordExists = await notionService.checkWordExists(databaseId, text.trim()); if (!wordExists) { await notionService.addWordEntry(databaseId, info); setStatus('保存成功'); } else { setStatus('单词已存在'); } } } setTimeout(() => setStatus(''), 3000); } catch (err) { setError(err instanceof Error ? err.message : '处理失败'); } setIsLoading(false); }; const handleClipboardImage = async () => { setIsLoading(true); setError(''); setTranslationResult(null); setWordInfo(null); try { setStatus('正在翻译图片...'); const imageBase64 = await clipboardManager.getImageBase64(); if (imageBase64) { const translation = await tencentService.translateImage(imageBase64); setTranslationResult(translation); // 图片翻译的内容处理逻辑 // 图片翻译时,我们需要检查原文中的英文单词,而不是翻译后的中文 // 因为腾讯API会将英文翻译成中文,我们需要从原图片中提取英文 // 先尝试用英文作为目标语言重新翻译,获取原始英文内容 setStatus('正在识别英文内容...'); const englishTranslation = await tencentService.translateImage(imageBase64, 'auto', 'en'); let originalEnglishText = ''; // 如果翻译结果包含英文,说明原图可能是其他语言,翻译成英文的结果就是我们要的 if (englishTranslation.translated && /[a-zA-Z]/.test(englishTranslation.translated)) { originalEnglishText = englishTranslation.translated; } else { // 如果翻译成英文没有英文内容,尝试从原始OCR结果中提取 // 这种情况下,原图片本身就包含英文,我们需要反向推导 const reverseTranslation = await tencentService.translateText(translation.translated, 'zh', 'en'); originalEnglishText = reverseTranslation.translated; } // 检查是否找到了英文内容 if (originalEnglishText && /[a-zA-Z]/.test(originalEnglishText)) { // 提取英文单词(包括连字符单词) const words = originalEnglishText.match(/\b[a-zA-Z]+(?:-[a-zA-Z]+)*\b/g); if (words && words.length > 0) { // 取第一个有效单词(长度大于2) const mainWord = words.find(word => word.length > 2)?.toLowerCase() || words[0].toLowerCase(); // 使用Grok AI获取单词信息 setStatus('正在获取单词信息...'); const info = await grokService.generateWordInfo(mainWord, config.englishLevel); setWordInfo(info); // 保存到Notion if (databaseId) { const wordExists = await notionService.checkWordExists(databaseId, mainWord); if (!wordExists) { await notionService.addWordEntry(databaseId, info); setStatus('保存成功'); } else { setStatus('单词已存在'); } } } else { setStatus('翻译完成'); } } else { setStatus('翻译完成'); } setTimeout(() => setStatus(''), 3000); } else { setError('剪贴板中没有图片数据'); } } catch (err) { setError(err instanceof Error ? err.message : '图片翻译失败'); } setIsLoading(false); }; const handleSpeech = async () => { if (!input.trim()) return; setIsLoading(true); setError(''); try { setStatus('正在播放语音...'); await speechService.speakText(input.trim()); setStatus('语音播放完成'); setTimeout(() => setStatus(''), 2000); } catch (err) { setError(err instanceof Error ? err.message : '语音播放失败'); } setIsLoading(false); }; const handleSubmit = (value) => { handleTranslation(value); setInput(''); }; return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Logo, {}), _jsx(Box, { justifyContent: "flex-end", children: _jsx(Text, { color: "gray", dimColor: true, children: "Ctrl+C \u9000\u51FA | Ctrl+S \u8BED\u97F3\u64AD\u653E" }) }), _jsx(ClaudeInput, { value: input, onChange: setInput, onSubmit: handleSubmit, placeholder: "\u952E\u5165\u5355\u8BCD/\u53E5\u5B50 | \u6216\u6309Ctrl+V\u7C98\u8D34\u56FE\u7247 | Ctrl+S\u53D1\u97F3\u64AD\u653E", label: "\uD83D\uDD24 \u8F93\u5165\u7FFB\u8BD1\u5185\u5BB9:" }), isLoading && (_jsxs(Box, { children: [_jsx(Spinner, { type: "dots" }), _jsxs(Text, { color: "cyan", children: [" ", status || '处理中...'] })] })), status && !isLoading && (_jsxs(Text, { color: "green", children: ["\u2728 ", status] })), error && (_jsxs(Text, { color: "red", children: ["\u274C ", error] })), translationResult && (_jsxs(Box, { flexDirection: "column", marginTop: 1, paddingX: 1, borderStyle: "round", borderColor: "cyan", children: [_jsxs(Text, { color: "cyan", bold: true, children: ["\uD83D\uDCDD ", translationResult.original] }), _jsxs(Text, { color: "green", children: ["\u279C ", translationResult.translated] })] }))] })); }; //# sourceMappingURL=MainScreen.js.map