Download app/templates/article.html from mistpe/newBlog: direct link, hf CLI and curl.
- Browser
- Download file 18.9 kB
-
https://huggingface.co/spaces/mistpe/newBlog/resolve/main/app/templates/article.html
- Command line
-
hf download hf://spaces/mistpe/newBlog/app/templates/article.html
-
curl -L -o article.html https://huggingface.co/spaces/mistpe/newBlog/resolve/main/app/templates/article.html
18.9 kB
| {% extends "base.html" %} | |
| {% block title %}{{ article.title }} - 个人博客{% endblock %} | |
| {% block extra_css %} | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github.min.css"> | |
| <style> | |
| :root { | |
| --primary-blue: #4A90E2; | |
| --light-blue: #63C2DE; | |
| --soft-purple: #9B59B6; | |
| --text-dark: #2C3E50; | |
| --warm-cream: #FFF9E6; | |
| } | |
| /* 文章容器样式 */ | |
| .article-container { | |
| max-width: 110vh; | |
| margin: 0 auto; | |
| background: white; | |
| border-radius: 20px; | |
| box-shadow: 0 2px 12px rgba(99, 145, 197, 0.08); | |
| border: 2px solid var(--light-blue); | |
| padding: 2.5rem; | |
| } | |
| /* 文章头部样式 */ | |
| .article-header { | |
| margin-bottom: 2.5rem; | |
| padding-bottom: 1.5rem; | |
| border-bottom: 1px solid var(--light-blue); | |
| } | |
| .article-title { | |
| font-size: 2.5rem; | |
| font-weight: 700; | |
| color: var(--text-dark); | |
| line-height: 1.3; | |
| margin-bottom: 1rem; | |
| background: linear-gradient(135deg, var(--primary-blue), var(--soft-purple)); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| } | |
| .article-meta { | |
| display: flex; | |
| align-items: center; | |
| gap: 1.5rem; | |
| color: #64748B; | |
| } | |
| .meta-item { | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| } | |
| .meta-item i { | |
| color: var(--primary-blue); | |
| } | |
| /* AI 摘要样式 */ | |
| .article-summary { | |
| background: var(--warm-cream); | |
| border-radius: 16px; | |
| padding: 1.5rem; | |
| margin: 2rem 0; | |
| position: relative; | |
| } | |
| .summary-label { | |
| position: absolute; | |
| top: -12px; | |
| left: 16px; | |
| background: var(--primary-blue); | |
| color: white; | |
| padding: 0.25rem 1rem; | |
| border-radius: 20px; | |
| font-size: 0.875rem; | |
| font-weight: 500; | |
| } | |
| /* 文章内容样式 */ | |
| .article-content { | |
| line-height: 1.8; | |
| color: var(--text-dark); | |
| } | |
| .markdown-body { | |
| font-size: 1.1rem; | |
| } | |
| .markdown-body h1, | |
| .markdown-body h2, | |
| .markdown-body h3 { | |
| color: var(--primary-blue); | |
| margin-top: 2em; | |
| margin-bottom: 1em; | |
| font-weight: 600; | |
| } | |
| .markdown-body p { | |
| margin-bottom: 1.5em; | |
| } | |
| .markdown-body a { | |
| color: var(--primary-blue); | |
| text-decoration: none; | |
| border-bottom: 1px dashed var(--light-blue); | |
| transition: all 0.3s; | |
| } | |
| .markdown-body a:hover { | |
| border-bottom-style: solid; | |
| color: var(--soft-purple); | |
| } | |
| .markdown-body code { | |
| background: #F8FAFC; | |
| padding: 0.2em 0.4em; | |
| border-radius: 4px; | |
| font-size: 0.9em; | |
| color: var(--primary-blue); | |
| } | |
| .markdown-body pre { | |
| background: #F8FAFC; | |
| border-radius: 12px; | |
| padding: 1rem; | |
| overflow-x: auto; | |
| border: 1px solid var(--light-blue); | |
| } | |
| .markdown-body pre code { | |
| background: none; | |
| padding: 0; | |
| color: inherit; | |
| } | |
| .markdown-body blockquote { | |
| border-left: 4px solid var(--light-blue); | |
| padding: 0.5rem 0 0.5rem 1rem; | |
| margin: 1.5rem 0; | |
| color: #64748B; | |
| background: #F8FAFC; | |
| } | |
| .markdown-body img { | |
| max-width: 100%; | |
| border-radius: 12px; | |
| margin: 1.5rem 0; | |
| } | |
| /* AI 聊天窗口样式 */ | |
| .chat-toggle { | |
| position: fixed; | |
| right: 2rem; | |
| bottom: 2rem; | |
| width: 56px; | |
| height: 56px; | |
| border-radius: 28px; | |
| background: linear-gradient(135deg, var(--primary-blue), var(--light-blue)); | |
| color: white; | |
| border: none; | |
| cursor: pointer; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 1.5rem; | |
| box-shadow: 0 4px 12px rgba(99, 145, 197, 0.2); | |
| transition: all 0.3s; | |
| z-index: 998; | |
| } | |
| .chat-toggle:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 6px 16px rgba(99, 145, 197, 0.3); | |
| } | |
| .chat-window { | |
| position: fixed; | |
| right: 2rem; | |
| bottom: 2rem; | |
| width: 380px; | |
| height: 600px; | |
| background: white; | |
| border-radius: 20px; | |
| box-shadow: 0 4px 20px rgba(99, 145, 197, 0.15); | |
| display: flex; | |
| flex-direction: column; | |
| transform: scale(0); | |
| opacity: 0; | |
| transform-origin: bottom right; | |
| transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1); | |
| z-index: 999; | |
| border: 2px solid var(--light-blue); | |
| } | |
| .chat-window.active { | |
| transform: scale(1); | |
| opacity: 1; | |
| } | |
| .chat-header { | |
| padding: 1.25rem; | |
| background: linear-gradient(135deg, var(--primary-blue), var(--light-blue)); | |
| color: white; | |
| border-radius: 20px 20px 0 0; | |
| display: flex; | |
| align-items: center; | |
| gap: 0.75rem; | |
| } | |
| .chat-title { | |
| font-weight: 600; | |
| flex: 1; | |
| } | |
| .chat-close { | |
| background: none; | |
| border: none; | |
| color: white; | |
| cursor: pointer; | |
| width: 32px; | |
| height: 32px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| border-radius: 16px; | |
| transition: all 0.3s; | |
| } | |
| .chat-close:hover { | |
| background: rgba(255, 255, 255, 0.2); | |
| } | |
| /* Enhanced Chat Messages Styles */ | |
| .chat-messages { | |
| flex: 1; | |
| overflow-y: auto; | |
| overflow-x: hidden; | |
| padding: 1.5rem; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 1rem; | |
| background: #f8f9fa; | |
| } | |
| .chat-message { | |
| max-width: 85%; | |
| padding: 1rem 1.25rem; | |
| border-radius: 16px; | |
| line-height: 1.5; | |
| animation: messageSlide 0.3s ease; | |
| word-wrap: break-word; | |
| overflow-wrap: break-word; | |
| width: fit-content; | |
| border: 2px solid var(--light-blue); | |
| background: white; | |
| color: var(--text-dark); | |
| box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); | |
| } | |
| .chat-message.user { | |
| margin-left: auto; | |
| border-radius: 16px 16px 4px 16px; | |
| background: #f8f9fa; | |
| } | |
| .chat-message.assistant { | |
| margin-right: auto; | |
| border-radius: 16px 16px 16px 4px; | |
| } | |
| .chat-message p { | |
| margin: 0; | |
| margin-bottom: 0.75rem; | |
| } | |
| .chat-message p:last-child { | |
| margin-bottom: 0; | |
| } | |
| /* Chat Message Content Styling */ | |
| .chat-message pre { | |
| background: #f1f5f9; | |
| border-radius: 8px; | |
| padding: 1rem; | |
| margin: 0.75rem 0; | |
| overflow-x: auto; | |
| border: 1px solid var(--light-blue); | |
| } | |
| .chat-message pre code { | |
| font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace; | |
| font-size: 0.9rem; | |
| line-height: 1.5; | |
| color: var(--text-dark); | |
| background: transparent; | |
| padding: 0; | |
| } | |
| .chat-message code { | |
| background: #f1f5f9; | |
| padding: 0.2em 0.4em; | |
| border-radius: 4px; | |
| font-size: 0.9em; | |
| color: var(--primary-blue); | |
| font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace; | |
| } | |
| .chat-message .katex-display { | |
| margin: 0.75rem 0; | |
| overflow-x: auto; | |
| padding: 0.5rem 0; | |
| } | |
| .chat-message .katex { | |
| font-size: 1.1em; | |
| } | |
| .chat-input-container { | |
| padding: 1.25rem; | |
| border-top: 1px solid var(--light-blue); | |
| } | |
| .chat-input-wrapper { | |
| display: flex; | |
| gap: 0.75rem; | |
| align-items: flex-end; | |
| } | |
| .chat-input { | |
| flex: 1; | |
| min-height: 44px; | |
| max-height: 120px; | |
| padding: 0.75rem 1rem; | |
| border: 2px solid var(--light-blue); | |
| border-radius: 12px; | |
| resize: none; | |
| font-size: 1rem; | |
| line-height: 1.5; | |
| transition: all 0.3s; | |
| } | |
| .chat-input:focus { | |
| outline: none; | |
| border-color: var(--primary-blue); | |
| box-shadow: 0 0 0 3px rgba(99, 145, 197, 0.1); | |
| } | |
| .chat-send { | |
| background: var(--primary-blue); | |
| color: white; | |
| width: 44px; | |
| height: 44px; | |
| border: none; | |
| border-radius: 12px; | |
| cursor: pointer; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| transition: all 0.3s; | |
| } | |
| .chat-send:hover { | |
| background: var(--light-blue); | |
| transform: translateY(-2px); | |
| } | |
| /* Animations */ | |
| @keyframes messageSlide { | |
| from { | |
| opacity: 0; | |
| transform: translateY(10px); | |
| } | |
| to { | |
| opacity: 1; | |
| transform: translateY(0); | |
| } | |
| } | |
| /* Responsive Design */ | |
| @media (max-width: 768px) { | |
| /* 文章区域移动端优化 */ | |
| .article-container { | |
| max-width: 60vh; | |
| padding: 1.25rem; | |
| border-radius: 12px; | |
| margin: 1rem; | |
| border-width: 1px; | |
| } | |
| .article-header { | |
| margin-bottom: 1.5rem; | |
| padding-bottom: 1rem; | |
| } | |
| .article-title { | |
| font-size: 1.75rem; | |
| line-height: 1.4; | |
| } | |
| .article-meta { | |
| flex-wrap: wrap; | |
| gap: 1rem; | |
| } | |
| .article-summary { | |
| margin: 1.5rem 0; | |
| padding: 1.25rem; | |
| } | |
| /* 聊天窗口移动端优化 */ | |
| .chat-window { | |
| position: fixed; | |
| bottom: 20px; | |
| width: 65vh; | |
| height: 90vh; | |
| left: 0; | |
| right: 0; | |
| margin-left: auto; | |
| margin-right: auto; | |
| border-radius: 20px 20px 0 0; | |
| transform-origin: bottom center; | |
| } | |
| .chat-messages { | |
| padding: 1rem; | |
| } | |
| .chat-message { | |
| max-width: 90%; | |
| padding: 0.875rem 1rem; | |
| font-size: 0.95rem; | |
| } | |
| .chat-message pre { | |
| margin: 0.5rem 0; | |
| padding: 0.875rem; | |
| font-size: 0.85rem; | |
| } | |
| .chat-input-wrapper { | |
| padding: 0.875rem; | |
| } | |
| .chat-input { | |
| min-height: 40px; | |
| padding: 0.625rem 0.875rem; | |
| font-size: 0.95rem; | |
| } | |
| .chat-send { | |
| width: 40px; | |
| height: 40px; | |
| } | |
| .chat-toggle { | |
| right: 1rem; | |
| bottom: 1rem; | |
| width: 48px; | |
| height: 48px; | |
| font-size: 1.25rem; | |
| } | |
| /* 触摸设备优化 */ | |
| .chat-message, | |
| .chat-input, | |
| .chat-send, | |
| .chat-toggle { | |
| touch-action: manipulation; | |
| -webkit-tap-highlight-color: transparent; | |
| } | |
| /* 提高移动端可读性 */ | |
| .markdown-body { | |
| font-size: 1rem; | |
| line-height: 1.6; | |
| } | |
| .markdown-body pre { | |
| -webkit-overflow-scrolling: touch; | |
| } | |
| /* 优化键盘弹出时的布局 */ | |
| @media (max-height: 600px) { | |
| .chat-window { | |
| height: 85vh; | |
| width: 65vh; | |
| } | |
| } | |
| } | |
| </style> | |
| {% endblock %} | |
| {% block content %} | |
| <!-- 文章内容 --> | |
| <article class="article-container"> | |
| <header class="article-header"> | |
| <h1 class="article-title">{{ article.title }}</h1> | |
| <div class="article-meta"> | |
| <div class="meta-item"> | |
| <i class="fas fa-calendar"></i> | |
| <span>{{ article.created_at.strftime('%Y-%m-%d') }}</span> | |
| </div> | |
| </div> | |
| </header> | |
| {% if article.summary %} | |
| <div class="article-summary"> | |
| <span class="summary-label">AI 摘要</span> | |
| <p>{{ article.summary }}</p> | |
| </div> | |
| {% endif %} | |
| <div class="article-content markdown-body"> | |
| {{ article.content|markdown }} | |
| </div> | |
| </article> | |
| <!-- AI 聊天窗口 --> | |
| <button class="chat-toggle" id="chatToggle"> | |
| <i class="fas fa-robot"></i> | |
| </button> | |
| <div class="chat-window" id="chatWindow"> | |
| <div class="chat-header"> | |
| <i class="fas fa-robot"></i> | |
| <span class="chat-title">AI 智能助手</span> | |
| <button class="chat-close" id="chatClose"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| </div> | |
| <div class="chat-messages" id="chatMessages"></div> | |
| <div class="chat-input-wrapper"> | |
| <textarea | |
| id="chatInput" | |
| class="chat-input" | |
| placeholder="输入您的问题..." | |
| rows="1" | |
| ></textarea> | |
| <button class="chat-send" onclick="sendMessage()"> | |
| <i class="fas fa-paper-plane"></i> | |
| </button> | |
| </div> | |
| </div> | |
| {% endblock %} | |
| {% block extra_js %} | |
| <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> | |
| <script> | |
| // 初始化文章上下文 | |
| window.articleContext = { | |
| title: {{ article.title|tojson|safe }}, | |
| content: {{ article.content|tojson|safe }} | |
| }; | |
| // 配置Marked | |
| marked.setOptions({ | |
| breaks: true, | |
| gfm: true | |
| }); | |
| // 聊天窗口控制 | |
| const chatToggle = document.getElementById('chatToggle'); | |
| const chatWindow = document.getElementById('chatWindow'); | |
| const chatClose = document.getElementById('chatClose'); | |
| const chatInput = document.getElementById('chatInput'); | |
| const chatMessages = document.getElementById('chatMessages'); | |
| // 模型上下文 | |
| const modelContext = `这是一篇关于"${window.articleContext.title}"的文章。文章内容:\n\n${window.articleContext.content}\n\n请基于以上文章内容来回答用户的问题。`; | |
| // 欢迎消息 | |
| const welcomeMessage = `您好!我是这篇《${window.articleContext.title}》的AI助手。我已经仔细阅读了全文,可以解答您关于文章内容的任何问题,也提供更深入的讨论和见解,从而帮助您更好地理解文章要点 | |
| 让我们开始对话吧!`; | |
| // 初始化消息数组 | |
| let messages = [{ | |
| role: 'system', | |
| content: modelContext | |
| }]; | |
| // 初始化聊天 | |
| function initializeChat() { | |
| displayMessage('assistant', welcomeMessage); | |
| } | |
| // 切换聊天窗口 | |
| function toggleChat() { | |
| chatWindow.classList.toggle('active'); | |
| if (chatWindow.classList.contains('active')) { | |
| chatToggle.style.display = 'none'; | |
| chatInput.focus(); | |
| if (chatMessages.children.length === 0) { | |
| initializeChat(); | |
| } | |
| } else { | |
| chatToggle.style.display = 'flex'; | |
| } | |
| } | |
| chatToggle.addEventListener('click', toggleChat); | |
| chatClose.addEventListener('click', toggleChat); | |
| // 自动调整文本框高度 | |
| chatInput.addEventListener('input', function() { | |
| this.style.height = 'auto'; | |
| this.style.height = Math.min(this.scrollHeight, 120) + 'px'; | |
| }); | |
| // 发送消息 | |
| async function sendMessage() { | |
| const messageText = chatInput.value.trim(); | |
| if (!messageText) return; | |
| const userMessage = { | |
| role: 'user', | |
| content: messageText | |
| }; | |
| // 重置输入框 | |
| chatInput.value = ''; | |
| chatInput.style.height = 'auto'; | |
| // 显示用户消息 | |
| displayMessage('user', messageText); | |
| try { | |
| const currentMessages = [...messages, userMessage]; | |
| const response = await fetch('/api/chat', { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json' | |
| }, | |
| body: JSON.stringify({ messages: currentMessages }) | |
| }); | |
| if (response.ok) { | |
| const data = await response.json(); | |
| // 更新消息历史 | |
| messages.push(userMessage); | |
| messages.push({ | |
| role: 'assistant', | |
| content: data.response | |
| }); | |
| // 显示AI响应 | |
| displayMessage('assistant', data.response); | |
| } else { | |
| throw new Error('Network response was not ok'); | |
| } | |
| } catch (error) { | |
| console.error('Error:', error); | |
| displayMessage('assistant', '抱歉,发生了错误,请稍后再试。'); | |
| } | |
| } | |
| // 显示消息 | |
| function displayMessage(role, content) { | |
| const messageDiv = document.createElement('div'); | |
| messageDiv.className = `chat-message ${role}`; | |
| // 使用marked渲染Markdown内容 | |
| messageDiv.innerHTML = marked.parse(content); | |
| chatMessages.appendChild(messageDiv); | |
| chatMessages.scrollTop = chatMessages.scrollHeight; | |
| } | |
| // 回车发送消息 | |
| chatInput.addEventListener('keypress', function(event) { | |
| if (event.key === 'Enter' && !event.shiftKey) { | |
| event.preventDefault(); | |
| sendMessage(); | |
| } | |
| }); | |
| // 聊天窗口拖动功能 | |
| let isDragging = false; | |
| let currentX; | |
| let currentY; | |
| let initialX; | |
| let initialY; | |
| let xOffset = 0; | |
| let yOffset = 0; | |
| chatWindow.addEventListener('mousedown', dragStart); | |
| document.addEventListener('mousemove', drag); | |
| document.addEventListener('mouseup', dragEnd); | |
| function dragStart(e) { | |
| if (e.target.closest('.chat-header') && !e.target.closest('.chat-close')) { | |
| initialX = e.clientX - xOffset; | |
| initialY = e.clientY - yOffset; | |
| isDragging = true; | |
| chatWindow.style.cursor = 'grabbing'; | |
| } | |
| } | |
| function drag(e) { | |
| if (isDragging) { | |
| e.preventDefault(); | |
| currentX = e.clientX - initialX; | |
| currentY = e.clientY - initialY; | |
| xOffset = currentX; | |
| yOffset = currentY; | |
| // 确保窗口不会超出视口边界 | |
| const rect = chatWindow.getBoundingClientRect(); | |
| const viewportWidth = window.innerWidth; | |
| const viewportHeight = window.innerHeight; | |
| // 限制X轴移动 | |
| if (rect.left < 0) { | |
| currentX -= rect.left; | |
| } | |
| if (rect.right > viewportWidth) { | |
| currentX -= (rect.right - viewportWidth); | |
| } | |
| // 限制Y轴移动 | |
| if (rect.top < 0) { | |
| currentY -= rect.top; | |
| } | |
| if (rect.bottom > viewportHeight) { | |
| currentY -= (rect.bottom - viewportHeight); | |
| } | |
| setTranslate(currentX, currentY, chatWindow); | |
| } | |
| } | |
| function dragEnd() { | |
| initialX = currentX; | |
| initialY = currentY; | |
| isDragging = false; | |
| chatWindow.style.cursor = 'default'; | |
| } | |
| function setTranslate(xPos, yPos, el) { | |
| el.style.transform = `translate(${xPos}px, ${yPos}px)`; | |
| } | |
| </script> | |
| {% endblock %} |