DeepSeek R1
- After OpenAI released the inference model O1, it deliberately hid the reasoning chain, while DeepSeek reproduced the ability of OpenAI o1 through pure reinforcement learning technology;
- With very limited computing resources, DeepSeek broke through the computing power bottleneck through powerful algorithm innovation, which is a milestone for the development of AI;
- At present, DeepSeek has become very popular, so I can’t help but want to try the programming ability of DeepSeek R1.
Task
- Deploy a simple DeepSeek R1 chat program on CloudFlare workers;
- The chat model is @cf/deepseek-ai/deepseek-r1-distill-qwen-32b in Workers AI provided by CloudFlare. This is not the full version of 671b size DeepSeek R1, but a 32b size version distilled from the Qwen model;
- It is not written from scratch, but modified on a previously deployed version. The modification is to add support for rendering mathematical formulas in Latex format;
- Since it is a relatively simple task, I want to see which model can succeed at one time.
Model
- The programming tool is still the free version of Cursor;
- Since it is a free version, the full-blooded version of the O1 model cannot be used;
- The three models compared are: DeepSeek R1, Claude 3.5 Sonnet, chatGPT O3 mini;
Result
- Claude 3.5 Sonnet not only failed to complete the task, but also broke the program;
- Although DeepSeek R1 failed to complete the task, the program can still work, and the formula entered by the user can be rendered, but the content output by the AI cannot be rendered successfully;
- chatGPT O3 mini successfully completed the task;
- It has to be said that OpenAI is still the benchmark.
Chat Program URL
DeepSeek R1 Chat
Source Code
- The following code is generated by Cursor. I don’t understand JS and can’t understand the details at all. I can only guess the function based on the variable name 🤣, so optimization is out of the question, as long as it works 😎
// Helper function to generate a unique ID
function generateId() {
return Date.now().toString(36) + Math.random().toString(36).substr(2);
}
// Add local storage class
class LocalStorage {
constructor() {
this.store = new Map();
}
async put(key, value) {
this.store.set(key, value);
}
async get(key) {
return this.store.get(key);
}
async delete(key) {
this.store.delete(key);
}
async list() {
return {
keys: Array.from(this.store.keys()).map(name => ({ name }))
};
}
}
// Modify the main entry point, add local storage
export default {
async fetch(request, env) {
// If CHAT_HISTORY is not in the environment, use local storage
if (!env.CHAT_HISTORY) {
env.CHAT_HISTORY = new LocalStorage();
}
try {
if (request.method === 'GET') {
return await handleGet(request, env);
}
if (request.method === 'POST') {
return await handlePost(request, env);
}
return new Response('Unsupported request method', { status: 405 });
} catch (error) {
console.error('Request handling error:', error);
return Response.json({ error: error.message }, { status: 500 });
}
}
};
// GET request handler
async function handleGet(request, env) {
const url = new URL(request.url);
const path = url.pathname;
// Unauthenticated users will be redirected to the login page
if (path !== '/login' && !await checkAuth(request)) {
return Response.redirect(`${url.origin}/login`, 302);
}
// Login page route
if (path === '/login') {
return new Response(loginHtml, {
headers: { 'content-type': 'text/html;charset=UTF-8' },
});
}
switch (path) {
case '/conversations':
return await getConversations(env);
case '/history':
return await getHistory(url.searchParams.get('id'), env);
default:
return new Response(html, {
headers: { 'content-type': 'text/html;charset=UTF-8' },
});
}
}
// POST request handler
async function handlePost(request, env) {
const { message, conversationId, action, title, password, history } = await request.json();
// Handle login request
if (action === 'login') {
if (!env.APP_PASSWORD) {
console.error('APP_PASSWORD environment variable not set');
return Response.json({ error: 'System configuration error' }, { status: 500 });
}
if (password === env.APP_PASSWORD) {
return new Response(null, {
status: 200,
headers: {
'Set-Cookie': 'auth_token=valid_token; Path=/; HttpOnly; SameSite=Strict',
}
});
}
return Response.json({ error: 'Incorrect password' }, { status: 401 });
}
// Verify access rights for other requests
if (!await checkAuth(request)) {
return Response.json({ error: 'Not logged in' }, { status: 401 });
}
switch (action) {
case 'updateTitle':
return await updateTitle(conversationId, title, env);
case 'new':
return Response.json({ conversationId: generateId() });
case 'delete':
return await deleteConversation(conversationId, env);
case 'import':
// Handle imported conversation history
if (!conversationId || !history) {
return Response.json({ error: 'Missing necessary parameters' }, { status: 400 });
}
await env.CHAT_HISTORY.put(conversationId, JSON.stringify(history));
return Response.json({ success: true });
default:
return await handleChat(message, conversationId, env);
}
}
// Conversation-related handler functions
async function getConversations(env) {
const list = await env.CHAT_HISTORY.list();
const conversations = [];
for (const key of list.keys) {
const data = await env.CHAT_HISTORY.get(key.name);
const history = JSON.parse(data);
conversations.push({
id: key.name,
title: history.title || 'Untitled Conversation',
lastMessage: history.messages?.[history.messages.length - 1]?.content || '',
timestamp: history.timestamp || Date.now()
});
}
conversations.sort((a, b) => b.timestamp - a.timestamp);
return Response.json(conversations);
}
async function getHistory(conversationId, env) {
if (!conversationId) return Response.json([]);
const savedHistory = await env.CHAT_HISTORY.get(conversationId);
return Response.json(savedHistory ? JSON.parse(savedHistory) : []);
}
async function updateTitle(conversationId, title, env) {
if (!conversationId || !title) {
return Response.json({ error: 'Missing necessary parameters' }, { status: 400 });
}
const savedHistory = await env.CHAT_HISTORY.get(conversationId);
if (!savedHistory) {
return Response.json({ error: 'Conversation not found' }, { status: 404 });
}
const history = JSON.parse(savedHistory);
history.title = title;
history.timestamp = Date.now();
await env.CHAT_HISTORY.put(conversationId, JSON.stringify(history));
return Response.json({ success: true });
}
async function deleteConversation(conversationId, env) {
if (!conversationId) {
return Response.json({ error: 'Missing conversation ID' }, { status: 400 });
}
await env.CHAT_HISTORY.delete(conversationId);
return Response.json({ success: true });
}
// Handle chat messages
async function handleChat(message, conversationId, env) {
if (!message || !conversationId) {
return Response.json({ error: 'Missing necessary parameters' }, { status: 400 });
}
const chat = {
messages: [
{ role: 'system', content: 'You are a knowledgeable expert who provides concise and useful answers in Chinese, and uses latex format to output mathematical formulas.' },
{ role: 'user', content: message }
],
stream: true,
max_tokens: 4096,
temperature: 0.5
};
try {
return await processChat(message, conversationId, chat, env);
} catch (error) {
console.error('Error handling chat message:', error);
return Response.json({ error: error.message }, { status: 500 });
}
}
// Add a function to handle chat messages
async function processChat(message, conversationId, chat, env) {
// Get history
const savedHistory = await env.CHAT_HISTORY.get(conversationId) || '{"messages":[], "title":"Untitled Conversation"}';
const history = JSON.parse(savedHistory);
if (!history.messages) history.messages = [];
// Save user message
history.messages.push({ role: 'user', content: message });
history.timestamp = Date.now();
await env.CHAT_HISTORY.put(conversationId, JSON.stringify(history));
// Get AI response
const stream = await env.AI.run('@cf/deepseek-ai/deepseek-r1-distill-qwen-32b', chat);
// Handle streaming response
const transformStream = new TransformStream({
async transform(chunk, controller) {
const text = new TextDecoder().decode(chunk);
controller.enqueue(chunk);
const lines = text.split('\n').filter(line => line.trim() !== '');
for (const line of lines) {
if (line.startsWith('data: ')) {
const jsonStr = line.slice(6);
if (jsonStr.trim() === '[DONE]') continue;
try {
const data = JSON.parse(jsonStr);
if (data.response) {
// Accumulate AI response
if (!history.messages[history.messages.length - 1] ||
history.messages[history.messages.length - 1].role !== 'assistant') {
history.messages.push({ role: 'assistant', content: data.response });
} else {
history.messages[history.messages.length - 1].content += data.response;
}
// Periodically save to KV
await env.CHAT_HISTORY.put(conversationId, JSON.stringify(history));
}
} catch (e) {
console.error('Failed to parse response JSON:', e);
}
}
}
},
async flush() {
// Ensure the last save
await env.CHAT_HISTORY.put(conversationId, JSON.stringify(history));
}
});
// Return the transformed stream
return new Response(stream.pipeThrough(transformStream), {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
},
});
}
// HTML template
const html = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DeepSeek R1 32B</title>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<link href="https://cdn.jsdelivr.net/npm/toastify-js/src/toastify.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/toastify-js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.4/dist/katex.min.css">
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.4/dist/katex.min.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.4/dist/contrib/auto-render.min.js"></script>
<style>
body {
margin: 0;
padding: 0;
font-family: Arial, sans-serif;
height: 100vh;
}
#app-container {
display: flex;
height: 100vh;
width: 100%;
}
#sidebar {
width: 280px;
border-right: 1px solid #ccc;
padding: 20px;
background-color: #f8f9fa;
height: 100%;
overflow-y: auto;
box-sizing: border-box;
}
#main-content {
flex: 1;
padding: 20px;
display: flex;
flex-direction: column;
height: 100%;
box-sizing: border-box;
}
#chat-container {
flex: 1;
border: 1px solid #ccc;
border-radius: 5px;
overflow-y: auto;
padding: 20px;
margin-bottom: 20px;
background-color: white;
}
.message {
margin: 10px 0;
padding: 10px;
border-radius: 5px;
}
.message pre {
background-color: #f0f0f0;
padding: 10px;
border-radius: 4px;
overflow-x: auto;
}
.message code {
font-family: monospace;
background-color: #f0f0f0;
padding: 2px 4px;
border-radius: 3px;
}
.user-message {
background-color: #e3f2fd;
margin-left: 20%;
}
.ai-message {
background-color: #f5f5f5;
margin-right: 20%;
}
#input-container {
display: flex;
gap: 10px;
padding: 10px 0;
}
#message-input {
flex: 1;
padding: 10px;
border: 1px solid #ccc;
border-radius: 5px;
}
button {
padding: 10px 20px;
background-color: #0070f3;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
}
button:hover {
background-color: #0051a2;
}
.loading {
position: relative;
opacity: 0.7;
pointer-events: none;
}
.loading::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 20px;
height: 20px;
margin: -10px 0 0 -10px;
border: 2px solid #ccc;
border-top-color: #333;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.search-container {
padding: 10px 0;
margin-bottom: 10px;
}
#search-input {
width: 100%;
padding: 8px;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
}
.conversation-item {
padding: 10px;
margin: 5px 0;
border-radius: 5px;
cursor: pointer;
transition: background-color 0.2s;
position: relative;
}
.conversation-item:hover {
background-color: #f0f0f0;
}
.conversation-item.active {
background-color: #e3f2fd;
}
.conversation-title {
font-weight: bold;
margin-bottom: 5px;
padding-right: 25px;
}
.conversation-preview {
font-size: 0.9em;
color: #666;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.delete-button {
position: absolute;
right: 5px;
top: 5px;
padding: 2px 6px;
background: #ff4444;
color: white;
border: none;
border-radius: 3px;
cursor: pointer;
opacity: 0;
transition: opacity 0.2s;
}
.conversation-item:hover .delete-button {
opacity: 1;
}
#new-chat-button {
width: 100%;
margin-bottom: 10px;
background-color: #4CAF50;
}
#new-chat-button:hover {
background-color: #45a049;
}
.edit-icon {
margin-left: 5px;
opacity: 0;
cursor: pointer;
font-size: 14px;
color: #666;
transition: opacity 0.2s;
}
.conversation-item:hover .edit-icon {
opacity: 1;
}
.edit-title-input {
width: calc(100% - 30px);
padding: 4px 8px;
border: 1px solid #0070f3;
border-radius: 4px;
font-size: inherit;
font-weight: bold;
outline: none;
}
.edit-title-input:focus {
box-shadow: 0 0 0 2px rgba(0,112,243,0.2);
}
.import-export-buttons {
display: flex;
gap: 10px;
margin-bottom: 10px;
}
.import-export-buttons button {
flex: 1;
padding: 8px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.import-export-buttons button:hover {
background-color: #45a049;
}
</style>
</head>
<body>
<div id="app-container">
<div id="sidebar">
<button id="new-chat-button">New Conversation</button>
<div class="import-export-buttons">
<button id="export-button" onclick="exportHistory()">Export History</button>
<input type="file" id="import-file" accept=".json" style="display: none" onchange="importHistory(event)">
<button onclick="document.getElementById('import-file').click()">Import History</button>
</div>
<div class="search-container">
<input type="text" id="search-input" placeholder="Search conversations...">
</div>
<div id="conversations-list"></div>
</div>
<div id="main-content">
<h1>DeepSeek R1 32B</h1>
<div id="chat-container"></div>
<div id="input-container">
<input type="text" id="message-input" placeholder="Enter your message...">
<button id="send-button">Send</button>
</div>
</div>
</div>
<script>
// Utility functions
function showToast(message, type = 'info') {
Toastify({
text: message,
duration: 3000,
gravity: "top",
position: "right",
style: {
background: type === 'error' ? "#ff4444" : "#4CAF50"
}
}).showToast();
}
function debounce(func, wait) {
let timeout;
return function(...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func(...args), wait);
};
}
// Initialize variables
let currentConversationId = new URLSearchParams(window.location.search).get('id');
const chatContainer = document.getElementById('chat-container');
const messageInput = document.getElementById('message-input');
const sendButton = document.getElementById('send-button');
const searchInput = document.getElementById('search-input');
// Message handler function
function addMessage(text, type) {
const messageDiv = document.createElement('div');
messageDiv.className = 'message ' + (type === 'user' ? 'user-message' : 'ai-message');
if (type === 'ai') {
// Render Markdown content
messageDiv.innerHTML = marked.parse(text);
} else {
messageDiv.textContent = text;
}
// Use KaTeX to automatically render mathematical formulas
if (typeof renderMathInElement === 'function') {
renderMathInElement(messageDiv, {
delimiters: [
{left: "$$", right: "$$", display: true},
{left: "$", right: "$", display: false},
{left: "\\(", right: "\\)", display: false},
{left: "[", right: "]", display: true}
]
});
}
chatContainer.appendChild(messageDiv);
chatContainer.scrollTop = chatContainer.scrollHeight;
}
// Conversation list management
async function loadConversations() {
try {
const response = await fetch('/conversations');
const conversations = await response.json();
const listElement = document.getElementById('conversations-list');
listElement.innerHTML = '';
conversations.forEach(conv => {
const item = document.createElement('div');
item.className = 'conversation-item' + (conv.id === currentConversationId ? ' active' : '');
item.dataset.id = conv.id;
item.innerHTML = '<button class="delete-button" onclick="deleteConversation(\'" + conv.id + "\', event)">×</button>' +
'<div class="conversation-title" ondblclick="editTitle(\'" + conv.id + "\', this)">' +
(conv.title || 'Untitled Conversation') +
'<span class="edit-icon">✎</span></div>' +
'<div class="conversation-preview">' + (conv.lastMessage || '') + '</div>';
item.onclick = (e) => {
if (!e.target.classList.contains('delete-button') &&
!e.target.classList.contains('edit-icon')) {
loadConversation(conv.id);
}
};
listElement.appendChild(item);
});
} catch (error) {
console.error('Failed to load conversation list:', error);
showToast('Failed to load conversation list', 'error');
}
}
async function loadConversation(id) {
if (id === currentConversationId) return;
try {
currentConversationId = id;
const newUrl = `${window.location.pathname}?id=${id}`;
window.history.pushState({ id }, '', newUrl);
const response = await fetch(`/history?id=${id}`);
const history = await response.json();
chatContainer.innerHTML = '';
if (history.messages) {
history.messages.forEach(msg => {
if (msg.role !== 'system') {
addMessage(msg.content, msg.role === 'user' ? 'user' : 'ai');
}
});
}
// Update active state
document.querySelectorAll('.conversation-item').forEach(item => {
item.classList.toggle('active', item.dataset.id === id);
});
} catch (error) {
console.error('Failed to load conversation:', error);
showToast('Failed to load conversation', 'error');
}
}
async function createNewConversation() {
try {
const response = await fetch('/', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'new' })
});
const data = await response.json();
await loadConversation(data.conversationId);
loadConversations();
} catch (error) {
console.error('Failed to create new conversation:', error);
showToast('Failed to create new conversation', 'error');
}
}
async function deleteConversation(id, event) {
event.stopPropagation();
if (!confirm('Are you sure you want to delete this conversation?')) return;
try {
await fetch('/', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'delete', conversationId: id })
});
if (id === currentConversationId) {
currentConversationId = null;
chatContainer.innerHTML = '';
window.history.pushState({}, '', window.location.pathname);
}
loadConversations();
showToast('Conversation deleted');
} catch (error) {
console.error('Failed to delete conversation:', error);
showToast('Failed to delete conversation', 'error');
}
}
// Modify title editing function
async function editTitle(convId, element) {
// Define currentTitle in the outermost scope of the function to ensure it is accessible in all closures
const currentTitle = element.textContent.trim().replace('✎', '').trim();
// Create input box
const input = document.createElement('input');
input.type = 'text';
input.className = 'edit-title-input';
input.value = currentTitle;
// Define save function
const saveTitle = async () => {
const newTitle = input.value.trim();
if (!newTitle) {
showToast('Title cannot be empty', 'error');
element.innerHTML = currentTitle + '<span class="edit-icon">✎</span>';
return;
}
if (newTitle !== currentTitle) {
try {
const response = await fetch('/', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
action: 'updateTitle',
conversationId: convId,
title: newTitle
})
});
if (!response.ok) throw new Error('Update failed');
showToast('Title updated');
loadConversations();
} catch (error) {
console.error('Failed to update title:', error);
showToast('Failed to update title', 'error');
element.innerHTML = currentTitle + '<span class="edit-icon">✎</span>';
}
} else {
element.innerHTML = currentTitle + '<span class="edit-icon">✎</span>';
}
};
// Bind events
input.onblur = saveTitle;
input.onkeydown = (e) => { // Using onkeydown is more reliable
if (e.key === 'Enter') {
e.preventDefault();
input.blur();
} else if (e.key === 'Escape') {
element.innerHTML = currentTitle + '<span class="edit-icon">✎</span>';
}
};
// Replace content and focus
element.textContent = '';
element.appendChild(input);
input.focus();
input.select();
}
// Search function
const handleSearch = debounce((query) => {
const items = document.querySelectorAll('.conversation-item');
items.forEach(item => {
const title = item.querySelector('.conversation-title').textContent;
const preview = item.querySelector('.conversation-preview').textContent;
const matches = title.toLowerCase().includes(query.toLowerCase()) ||
preview.toLowerCase().includes(query.toLowerCase());
item.style.display = matches ? 'block' : 'none';
});
}, 300);
// Send message
async function sendMessage() {
const message = messageInput.value.trim();
if (!message) return;
if (!currentConversationId) {
await createNewConversation();
}
messageInput.value = '';
addMessage(message, 'user');
const aiMessageDiv = document.createElement('div');
aiMessageDiv.className = 'message ai-message';
chatContainer.appendChild(aiMessageDiv);
let currentResponse = '';
try {
const response = await fetch('/', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message, conversationId: currentConversationId })
});
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { value, done } = await reader.read();
if (done) break;
const text = decoder.decode(value);
const lines = text.split('\n').filter(line => line.trim() !== '');
for (const line of lines) {
if (line.startsWith('data: ')) {
const jsonStr = line.slice(6);
if (jsonStr.trim() === '[DONE]') break;
try {
const data = JSON.parse(jsonStr);
if (data.response) {
currentResponse += data.response;
aiMessageDiv.innerHTML = marked.parse(currentResponse);
// Call KaTeX to automatically render mathematical formulas
if (typeof renderMathInElement === 'function') {
renderMathInElement(aiMessageDiv, {
delimiters: [
{left: "$$", right: "$$", display: true},
{left: "$", right: "$", display: false},
{left: "\\(", right: "\\)", display: false},
{left: "[", right: "]", display: true}
]
});
}
chatContainer.scrollTop = chatContainer.scrollHeight;
}
} catch (e) {
console.error('Failed to parse JSON:', e);
}
}
}
}
// Update conversation list
loadConversations();
} catch (error) {
console.error('Failed to send message:', error);
aiMessageDiv.innerHTML = marked.parse('Error: ' + error.message);
showToast('Failed to send message', 'error');
}
}
// Event listeners
document.getElementById('new-chat-button').onclick = createNewConversation;
sendButton.onclick = sendMessage;
messageInput.onkeypress = (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
};
searchInput.oninput = (e) => handleSearch(e.target.value);
// Initialization
loadConversations();
if (currentConversationId) {
loadConversation(currentConversationId);
}
// Export conversation history
async function exportHistory() {
try {
const response = await fetch('/conversations');
const conversations = await response.json();
// Get the full history of each conversation
const fullHistory = await Promise.all(
conversations.map(async conv => {
const historyResponse = await fetch(`/history?id=${conv.id}`);
const history = await historyResponse.json();
return {
id: conv.id,
history: history
};
})
);
// Create download
const blob = new Blob([JSON.stringify(fullHistory, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `chat-history-${new Date().toISOString().slice(0,10)}.json`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
showToast('Conversation history exported');
} catch (error) {
console.error('Export failed:', error);
showToast('Export failed', 'error');
}
}
// Import conversation history
async function importHistory(event) {
const file = event.target.files[0];
if (!file) return;
try {
const text = await file.text();
const histories = JSON.parse(text);
// Import conversations one by one
for (const item of histories) {
try {
// Create new conversation
const response = await fetch('/', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'new' })
});
const { conversationId } = await response.json();
// Save conversation history
await fetch('/', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
action: 'import',
conversationId,
history: item.history
})
});
} catch (e) {
console.error('Failed to import single conversation:', e);
}
}
// Refresh conversation list
loadConversations();
showToast('Conversation history imported');
} catch (error) {
console.error('Import failed:', error);
showToast('Import failed', 'error');
}
// Clear file selection to allow re-importing the same file
event.target.value = '';
}
</script>
</body>
</html>`;
// Add password verification middleware
async function checkAuth(request) {
const cookie = request.headers.get('cookie') || '';
const token = cookie.split(';')
.find(c => c.trim().startsWith('auth_token='))
?.split('=')[1];
return token === 'valid_token';
}
// Add login page HTML
const loginHtml = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login - DeepSeek R1</title>
<style>
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background-color: #f5f5f5;
font-family: Arial, sans-serif;
}
.login-container {
background: white;
padding: 2rem;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
width: 100%;
max-width: 320px;
}
h1 {
text-align: center;
margin-bottom: 2rem;
}
input {
width: 100%;
padding: 0.5rem;
margin-bottom: 1rem;
border: 1px solid #ddd;
border-radius: 4px;
box-sizing: border-box;
}
button {
width: 100%;
padding: 0.75rem;
background-color: #0070f3;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background-color: #0051a2;
}
.error {
color: red;
text-align: center;
margin-top: 1rem;
display: none;
}
</style>
</head>
<body>
<div class="login-container">
<h1>Login</h1>
<input type="password" id="password" placeholder="Enter password">
<button onclick="login()">Login</button>
<div id="error" class="error"></div>
</div>
<script>
async function login() {
const password = document.getElementById('password').value;
const error = document.getElementById('error');
try {
const response = await fetch('/', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'login', password })
});
if (response.ok) {
window.location.href = '/';
} else {
error.textContent = 'Incorrect password';
error.style.display = 'block';
}
} catch (e) {
error.textContent = 'Login failed, please try again';
error.style.display = 'block';
}
}
// Support Enter key to log in
document.getElementById('password').addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
login();
}
});
</script>
</body>
</html>`;