分享一个自己做的html网页文本翻译器

调用大模型来翻译文本,支持oneapi newapi等兼容openai的模型格式。支持拉取所有模型,然后自己选择用哪个模型来翻译。注意:由于html在前端输入url和key,安全性弱,所以只建议在自己的和自己信任的电脑上用。使用方法:新建记事本文件,把代码复制到里面,修改后缀名为html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>文本翻译工具</title>
    <style>
        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            background: linear-gradient(135deg, #e0f7fa, #f5f5f5);
            margin: 0;
            padding: 20px;
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 100vh;
        }
        .container {
            background: white;
            border-radius: 15px;
            box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
            padding: 30px;
            width: 100%;
            max-width: 800px;
        }
        h2 {
            color: #00796b;
            text-align: center;
            margin-bottom: 25px;
            font-size: 1.8rem;
        }
        .text-box {
            background-color: #f9f9f9;
            border: 1px solid #e0e0e0;
            border-radius: 8px;
            margin: 15px 0;
            overflow: hidden;
            display: flex;
            flex-direction: row;
            align-items: stretch;
            position: relative; /* 用于定位复制图标 */
        }
        textarea {
            width: 50%;
            height: 200px;
            padding: 15px;
            border: none;
            resize: none;
            font-size: 1rem;
            background-color: transparent;
            box-sizing: border-box;
        }
        textarea[readonly] {
            background-color: #f0f0f0;
            cursor: not-allowed;
        }
        .divider {
            width: 1px;
            background-color: #e0e0e0;
            margin: 10px 0;
        }
        button {
            display: block;
            width: 100%;
            padding: 12px;
            margin: 15px 0;
            background-color: #009688;
            color: white;
            border: none;
            border-radius: 8px;
            cursor: pointer;
            font-size: 1.1rem;
            transition: background-color 0.3s, transform 0.2s;
        }
        button:hover {
            background-color: #00796b;
            transform: translateY(-2px);
        }
        .config {
            margin: 20px 0;
            padding: 15px;
            background-color: #f5f5f5;
            border-radius: 10px;
        }
        .config label {
            display: block;
            margin-bottom: 8px;
            color: #555;
            font-weight: bold;
        }
        .config input, .config select {
            width: 100%;
            padding: 10px;
            margin-bottom: 10px;
            border: 1px solid #e0e0e0;
            border-radius: 6px;
            font-size: 1rem;
            background-color: white;
        }
        .config input[type="password"] {
            letter-spacing: 2px;
        }
        .small-btn {
            width: auto;
            display: inline-block;
            margin: 0 0 10px 0;
            padding: 8px 16px;
            font-size: 0.9rem;
        }
        .copy-icon {
            position: absolute;
            top: 10px;
            right: 10px;
            background-color: #607d8b;
            color: white;
            border: none;
            border-radius: 50%;
            width: 30px;
            height: 30px;
            display: flex;
            justify-content: center;
            align-items: center;
            cursor: pointer;
            transition: background-color 0.3s, transform 0.2s;
            font-size: 1rem;
        }
        .copy-icon:hover {
            background-color: #455a64;
            transform: scale(1.1);
        }
        @media (max-width: 600px) {
            .container {
                padding: 20px;
                margin: 10px;
            }
            h2 {
                font-size: 1.5rem;
            }
            .text-box {
                flex-direction: column;
            }
            textarea {
                width: 100%;
                height: 120px;
            }
            .divider {
                width: 100%;
                height: 1px;
                margin: 0 0;
            }
            .copy-icon {
                top: 140px; /* 调整位置以适应上下布局 */
            }
        }
    </style>
</head>
<body>
    <div class="container">
        <h2>智能文本翻译工具</h2>
        <div class="config">
            <label for="apiUrl">API 端点 URL:</label>
            <input type="text" id="apiUrl" value="https://api.openai.com/v1" placeholder="输入 API 端点 URL(如 https://api.openai.com/v1)">
            <label for="apiKey">API 密钥:</label>
            <input type="password" id="apiKey" placeholder="输入 API 密钥">
            <button class="small-btn" onclick="fetchModels()">拉取模型列表</button>
            <label for="modelInput">选择模型:</label>
            <select id="modelInput" disabled>
                <option value="">请先拉取模型列表</option>
            </select>
        </div>
        <div class="text-box">
            <textarea id="inputText" placeholder="请输入要翻译的文本..."></textarea>
            <div class="divider"></div>
            <textarea id="outputText" readonly placeholder="翻译结果会显示在这里..."></textarea>
            <button class="copy-icon" onclick="copyToClipboard()" title="一键复制翻译结果">📋</button>
        </div>
        <button onclick="translateText()">翻译</button>
    </div>

    <script>
        async function fetchModels() {
            const apiUrl = document.getElementById('apiUrl').value;
            const apiKey = document.getElementById('apiKey').value;
            const modelSelect = document.getElementById('modelInput');

            if (!apiUrl) {
                alert('请输入 API 端点 URL');
                return;
            }
            if (!apiKey) {
                alert('请输入 API 密钥');
                return;
            }

            try {
                const modelsUrl = apiUrl.endsWith('/v1') ? `${apiUrl}/models` : `${apiUrl}/v1/models`;
                const response = await fetch(modelsUrl, {
                    method: 'GET',
                    headers: {
                        'Authorization': `Bearer ${apiKey}`
                    }
                });

                if (!response.ok) {
                    throw new Error(`HTTP error! status: ${response.status}`);
                }

                const data = await response.json();
                if (data.data && data.data.length > 0) {
                    modelSelect.innerHTML = '';
                    data.data.forEach(model => {
                        const option = document.createElement('option');
                        option.value = model.id;
                        option.textContent = model.id;
                        modelSelect.appendChild(option);
                    });
                    modelSelect.disabled = false;
                    alert(`模型列表拉取成功!共找到 ${data.data.length} 个模型。`);
                } else {
                    alert('未找到可用模型,请检查 API 端点和密钥。');
                    modelSelect.innerHTML = '<option value="">未找到模型</option>';
                    modelSelect.disabled = true;
                }
            } catch (error) {
                console.error('拉取模型列表出错:', error);
                alert('拉取模型列表失败,请检查网络、API 端点或密钥是否正确。');
                modelSelect.innerHTML = '<option value="">拉取失败</option>';
                modelSelect.disabled = true;
            }
        }

        async function translateText() {
            const inputText = document.getElementById('inputText').value;
            const apiUrl = document.getElementById('apiUrl').value;
            const apiKey = document.getElementById('apiKey').value;
            const modelInput = document.getElementById('modelInput').value;
            const outputText = document.getElementById('outputText');

            if (!inputText) {
                alert('请输入要翻译的文本');
                return;
            }
            if (!apiUrl) {
                alert('请输入 API 端点 URL');
                return;
            }
            if (!apiKey) {
                alert('请输入 API 密钥');
                return;
            }
            if (!modelInput) {
                alert('请选择模型');
                return;
            }

            try {
                const chatUrl = apiUrl.endsWith('/v1') ? `${apiUrl}/chat/completions` : apiUrl.includes('/v1/') ? apiUrl : `${apiUrl}/v1/chat/completions`;
                const response = await fetch(chatUrl, {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                        'Authorization': `Bearer ${apiKey}`
                    },
                    body: JSON.stringify({
                        model: modelInput,
                        messages: [
                            { role: 'system', content: '你是一个专业的翻译助手,直接将用户输入的中文翻译成英文,不要添加任何前缀或额外说明。' },
                            { role: 'user', content: inputText }
                        ],
                        temperature: 0.3
                    })
                });

                const data = await response.json();
                if (data.choices && data.choices.length > 0) {
                    outputText.value = data.choices[0].message.content;
                } else {
                    outputText.value = '翻译失败,请重试。';
                }
            } catch (error) {
                console.error('翻译出错:', error);
                outputText.value = '翻译过程中出现错误,请检查网络、API 端点、密钥或模型名称是否正确。';
            }
        }

        function copyToClipboard() {
            const outputText = document.getElementById('outputText');
            if (outputText.value.trim() === '') {
                alert('翻译结果为空,无法复制!');
                return;
            }
            outputText.select();
            try {
                document.execCommand('copy');
                alert('翻译结果已复制到剪贴板!');
            } catch (err) {
                console.error('复制失败:', err);
                alert('复制失败,请手动复制!');
            }
        }
    </script>
</body>
</html>
1 个赞