Spaces:
Sleeping
Sleeping
| <html lang="zh-CN"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> | |
| <title>Chat Helper</title> | |
| <style> | |
| :root { | |
| --primary-color: #007AFF; | |
| --bg-color: #F2F2F7; | |
| --card-bg: #FFFFFF; | |
| --text-color: #000000; | |
| } | |
| body { | |
| font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; | |
| background-color: var(--bg-color); | |
| margin: 0; | |
| padding: 20px; | |
| color: var(--text-color); | |
| padding-bottom: 80px; | |
| } | |
| .container { | |
| max-width: 600px; | |
| margin: 0 auto; | |
| } | |
| h1 { | |
| text-align: center; | |
| font-size: 20px; | |
| margin-bottom: 20px; | |
| color: #333; | |
| } | |
| .card { | |
| background: var(--card-bg); | |
| border-radius: 12px; | |
| padding: 16px; | |
| margin-bottom: 16px; | |
| box-shadow: 0 2px 8px rgba(0,0,0,0.05); | |
| } | |
| .form-group { | |
| margin-bottom: 15px; | |
| } | |
| label { | |
| display: block; | |
| margin-bottom: 8px; | |
| font-weight: 500; | |
| font-size: 14px; | |
| color: #666; | |
| } | |
| input[type="text"], textarea, select { | |
| width: 100%; | |
| padding: 12px; | |
| border: 1px solid #E5E5EA; | |
| border-radius: 10px; | |
| font-size: 16px; | |
| box-sizing: border-box; | |
| background: #F9F9F9; | |
| -webkit-appearance: none; | |
| } | |
| textarea { | |
| resize: vertical; | |
| min-height: 100px; | |
| } | |
| .role-selector { | |
| display: flex; | |
| gap: 10px; | |
| overflow-x: auto; | |
| padding-bottom: 5px; | |
| -webkit-overflow-scrolling: touch; | |
| } | |
| .role-btn { | |
| flex: 0 0 auto; | |
| padding: 8px 16px; | |
| border-radius: 20px; | |
| background: #E5E5EA; | |
| color: #333; | |
| border: none; | |
| font-size: 14px; | |
| cursor: pointer; | |
| transition: all 0.2s; | |
| } | |
| .role-btn.active { | |
| background: var(--primary-color); | |
| color: white; | |
| } | |
| .btn-primary { | |
| background: var(--primary-color); | |
| color: white; | |
| border: none; | |
| padding: 14px; | |
| border-radius: 12px; | |
| width: 100%; | |
| font-size: 16px; | |
| font-weight: 600; | |
| cursor: pointer; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| } | |
| .btn-primary:active { | |
| opacity: 0.8; | |
| } | |
| .btn-primary:disabled { | |
| background: #ccc; | |
| } | |
| .file-upload { | |
| position: relative; | |
| display: inline-block; | |
| width: 100%; | |
| } | |
| .file-upload input[type="file"] { | |
| display: none; | |
| } | |
| .file-upload-label { | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| padding: 12px; | |
| border: 2px dashed #E5E5EA; | |
| border-radius: 10px; | |
| color: var(--primary-color); | |
| cursor: pointer; | |
| background: #fff; | |
| } | |
| #preview-image { | |
| max-width: 100%; | |
| border-radius: 8px; | |
| margin-top: 10px; | |
| display: none; | |
| } | |
| .result-area { | |
| display: none; | |
| } | |
| .reply-card { | |
| background: #fff; | |
| padding: 15px; | |
| border-radius: 10px; | |
| margin-bottom: 10px; | |
| border-left: 4px solid var(--primary-color); | |
| cursor: pointer; | |
| position: relative; | |
| } | |
| .reply-card:active { | |
| background: #f0f0f0; | |
| } | |
| .reply-card::after { | |
| content: '点击复制'; | |
| position: absolute; | |
| right: 10px; | |
| top: 10px; | |
| font-size: 10px; | |
| color: #999; | |
| } | |
| .loading { | |
| display: none; | |
| text-align: center; | |
| margin: 20px 0; | |
| } | |
| .spinner { | |
| border: 4px solid rgba(0, 0, 0, 0.1); | |
| width: 36px; | |
| height: 36px; | |
| border-radius: 50%; | |
| border-left-color: var(--primary-color); | |
| animation: spin 1s linear infinite; | |
| display: inline-block; | |
| } | |
| @keyframes spin { | |
| 0% { transform: rotate(0deg); } | |
| 100% { transform: rotate(360deg); } | |
| } | |
| /* Float Button (Simulation of AssistiveTouch) */ | |
| .float-btn { | |
| position: fixed; | |
| bottom: 20px; | |
| right: 20px; | |
| width: 50px; | |
| height: 50px; | |
| border-radius: 25px; | |
| background: var(--primary-color); | |
| color: white; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| box-shadow: 0 4px 12px rgba(0,0,0,0.3); | |
| z-index: 1000; | |
| cursor: pointer; | |
| font-size: 24px; | |
| } | |
| .api-key-section { | |
| font-size: 12px; | |
| color: #888; | |
| margin-top: 10px; | |
| text-align: center; | |
| cursor: pointer; | |
| text-decoration: underline; | |
| } | |
| #apiKeyInput { | |
| margin-top: 5px; | |
| display: none; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <h1>💬 聊天回复助手</h1> | |
| <div class="card"> | |
| <label>对方是谁?</label> | |
| <div class="role-selector" id="roleSelector"> | |
| <button class="role-btn active" data-role="boss">领导</button> | |
| <button class="role-btn" data-role="client">客户</button> | |
| <button class="role-btn" data-role="colleague">同事</button> | |
| <button class="role-btn" data-role="good_friend">好朋友</button> | |
| <button class="role-btn" data-role="normal_friend">普通朋友</button> | |
| </div> | |
| </div> | |
| <div class="card"> | |
| <div class="form-group"> | |
| <label>聊天内容 (截图或文字)</label> | |
| <div class="file-upload"> | |
| <label for="imageInput" class="file-upload-label"> | |
| 📷 上传聊天截图 | |
| </label> | |
| <input type="file" id="imageInput" accept="image/*"> | |
| </div> | |
| <img id="preview-image" alt="Preview"> | |
| </div> | |
| <div class="form-group"> | |
| <textarea id="textInput" placeholder="或者直接粘贴对方说的话..."></textarea> | |
| </div> | |
| <button class="btn-primary" id="generateBtn">生成回复</button> | |
| <!-- | |
| <div class="api-key-section" id="toggleApiKey">设置 API Key</div> | |
| <input type="text" id="apiKeyInput" placeholder="sk-..." value=""> | |
| --> | |
| </div> | |
| <div class="loading" id="loading"> | |
| <div class="spinner"></div> | |
| <p>正在思考高情商回复...</p> | |
| </div> | |
| <div class="result-area" id="resultArea"> | |
| <h3>💡 建议回复 (点击复制)</h3> | |
| <div id="replyContainer"></div> | |
| </div> | |
| </div> | |
| <script> | |
| // State | |
| let currentRole = 'boss'; | |
| // DOM Elements | |
| const roleBtns = document.querySelectorAll('.role-btn'); | |
| const imageInput = document.getElementById('imageInput'); | |
| const previewImage = document.getElementById('preview-image'); | |
| const textInput = document.getElementById('textInput'); | |
| const generateBtn = document.getElementById('generateBtn'); | |
| const loading = document.getElementById('loading'); | |
| const resultArea = document.getElementById('resultArea'); | |
| const replyContainer = document.getElementById('replyContainer'); | |
| // const apiKeyInput = document.getElementById('apiKeyInput'); | |
| // const toggleApiKey = document.getElementById('toggleApiKey'); | |
| // Load API Key | |
| /* | |
| const savedKey = localStorage.getItem('siliconflow_api_key'); | |
| if (savedKey) { | |
| apiKeyInput.value = savedKey; | |
| } else { | |
| apiKeyInput.style.display = 'block'; // Show if empty | |
| } | |
| */ | |
| // Role Selection | |
| roleBtns.forEach(btn => { | |
| btn.addEventListener('click', () => { | |
| roleBtns.forEach(b => b.classList.remove('active')); | |
| btn.classList.add('active'); | |
| currentRole = btn.dataset.role; | |
| }); | |
| }); | |
| // Toggle API Key Input | |
| /* | |
| toggleApiKey.addEventListener('click', () => { | |
| apiKeyInput.style.display = apiKeyInput.style.display === 'none' ? 'block' : 'none'; | |
| }); | |
| // Save API Key on change | |
| apiKeyInput.addEventListener('change', () => { | |
| localStorage.setItem('siliconflow_api_key', apiKeyInput.value.trim()); | |
| }); | |
| */ | |
| // Image Preview | |
| imageInput.addEventListener('change', function(e) { | |
| const file = e.target.files[0]; | |
| if (file) { | |
| const reader = new FileReader(); | |
| reader.onload = function(e) { | |
| previewImage.src = e.target.result; | |
| previewImage.style.display = 'block'; | |
| } | |
| reader.readAsDataURL(file); | |
| } | |
| }); | |
| // Generate | |
| generateBtn.addEventListener('click', async () => { | |
| /* | |
| const apiKey = apiKeyInput.value.trim(); | |
| if (!apiKey) { | |
| alert('请先输入 SiliconFlow API Key'); | |
| apiKeyInput.style.display = 'block'; | |
| return; | |
| } | |
| */ | |
| const text = textInput.value.trim(); | |
| const image = imageInput.files[0]; | |
| if (!text && !image) { | |
| alert('请提供聊天内容(截图或文字)'); | |
| return; | |
| } | |
| // UI Update | |
| loading.style.display = 'block'; | |
| resultArea.style.display = 'none'; | |
| generateBtn.disabled = true; | |
| replyContainer.innerHTML = ''; | |
| // Prepare Data | |
| const formData = new FormData(); | |
| formData.append('role', currentRole); | |
| // formData.append('api_key', apiKey); // No longer needed | |
| if (text) formData.append('text', text); | |
| if (image) formData.append('image', image); | |
| try { | |
| const response = await fetch('/api/chat', { | |
| method: 'POST', | |
| body: formData | |
| }); | |
| const data = await response.json(); | |
| if (data.error) { | |
| throw new Error(data.error); | |
| } | |
| // Parse and display replies | |
| // The AI now returns a list of replies in "replies" field | |
| const replies = data.replies || [data.reply]; // Backwards compatibility | |
| if (Array.isArray(replies) && replies.length > 0) { | |
| replies.forEach(r => addReplyCard(r.trim())); | |
| } else { | |
| addReplyCard("未生成有效回复,请重试。"); | |
| } | |
| resultArea.style.display = 'block'; | |
| } catch (err) { | |
| alert('Error: ' + err.message); | |
| } finally { | |
| loading.style.display = 'none'; | |
| generateBtn.disabled = false; | |
| } | |
| }); | |
| function addReplyCard(text) { | |
| const div = document.createElement('div'); | |
| div.className = 'reply-card'; | |
| div.textContent = text; | |
| div.onclick = () => { | |
| navigator.clipboard.writeText(text); | |
| const original = div.textContent; | |
| div.textContent = '已复制!'; | |
| setTimeout(() => div.textContent = original, 1000); | |
| }; | |
| replyContainer.appendChild(div); | |
| } | |
| </script> | |
| </body> | |
| </html> | |