Skip to content

Commit f5c679f

Browse files
committed
Add CONTRIBUTING.md and LICENSE files; enhance web.py and chat.html
- Created CONTRIBUTING.md to outline contribution guidelines for the project. - Added LICENSE file with Apache 2.0 terms for project distribution and usage. - Updated web.py to support text extraction from uploaded files (PDF, DOCX, TXT) and improved chat history management. - Enhanced chat.html with new settings modal for user preferences, including username, avatar, and system prompt. - Integrated Marked.js for better markdown rendering and added scroll-to-bottom functionality for chat history.
1 parent 6a64bd6 commit f5c679f

4 files changed

Lines changed: 159 additions & 41 deletions

File tree

File renamed without changes.
File renamed without changes.

src/deepseek_wrapper/templates/chat.html

Lines changed: 114 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,50 @@
88
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
99
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@500;600;700&family=Roboto+Mono:wght@400;500&family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">
1010
<link rel="stylesheet" href="/static/style.css">
11+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/highlight.js@11.9.0/styles/github.min.css">
12+
<script src="https://cdn.jsdelivr.net/npm/highlight.js@11.9.0/lib/highlight.min.js"></script>
13+
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
14+
<style>
15+
.modal-backdrop { position: fixed; top:0; left:0; width:100vw; height:100vh; background:rgba(0,0,0,0.3); z-index:1000; display:none; }
16+
.modal { position: fixed; top:50%; left:50%; transform:translate(-50%,-50%); background:#fff; color:#222; border-radius:8px; box-shadow:0 2px 16px rgba(0,0,0,0.2); z-index:1001; min-width:320px; max-width:90vw; padding:2em 1.5em; display:none; }
17+
.modal h2 { margin-bottom:1em; font-size:1.2em; }
18+
.modal label { display:block; margin:0.5em 0 0.2em; font-weight:500; }
19+
.modal input, .modal textarea { width:100%; margin-bottom:1em; padding:0.5em; border-radius:4px; border:1px solid #ccc; font-size:1em; }
20+
.modal-actions { text-align:right; }
21+
.modal-actions button { margin-left:0.5em; }
22+
#scroll-bottom {
23+
transition: opacity 0.2s;
24+
opacity: 0;
25+
pointer-events: none;
26+
display: flex;
27+
align-items: center;
28+
justify-content: center;
29+
position: absolute;
30+
right: 2em;
31+
bottom: 6em;
32+
z-index: 10;
33+
}
34+
#scroll-bottom.visible {
35+
opacity: 1;
36+
pointer-events: auto;
37+
}
38+
</style>
1139
<script>
1240
function scrollChat() {
13-
var chat = document.getElementById('chat-history');
14-
if (chat) chat.scrollTop = chat.scrollHeight;
41+
const chatHistoryElem = document.getElementById('chat-history');
42+
const scrollBottomBtn = document.getElementById('scroll-bottom');
43+
// Hide if no messages or empty chat
44+
if (!chatHistoryElem || chatHistoryElem.children.length === 0 || chatHistoryElem.querySelector('.empty-chat')) {
45+
scrollBottomBtn.classList.remove('visible');
46+
return;
47+
}
48+
// Show only if not at bottom
49+
const isScrolledUp = chatHistoryElem.scrollTop < chatHistoryElem.scrollHeight - chatHistoryElem.clientHeight - 100;
50+
if (isScrolledUp && chatHistoryElem.scrollHeight > chatHistoryElem.clientHeight) {
51+
scrollBottomBtn.classList.add('visible');
52+
} else {
53+
scrollBottomBtn.classList.remove('visible');
54+
}
1555
}
1656
function focusInput() {
1757
var input = document.getElementById('user_message');
@@ -187,19 +227,58 @@
187227
}
188228

189229
function renderMarkdown(text) {
190-
text = text.replace(/\n/g, '<br>');
191-
// Simplified: In a real app, use a library like Marked.js or DOMPurify after Marked.js
192-
text = text
193-
.replace(/```(\w*?)<br>([\s\S]*?)<br>```/g, (match, lang, code) => `<pre><code class="language-${lang || ''}">${code.replace(/<br>/g, '\n')}</code></pre>`)
194-
.replace(/`([^`]+?)`/g, '<code>$1</code>')
195-
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
196-
.replace(/\*(.*?)\*/g, '<em>$1</em>')
197-
.replace(/\[(.*?)\]\((.*?)\)/g, '<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>');
198-
return text;
230+
// Use Marked.js for full markdown support
231+
const html = marked.parse(text);
232+
// Highlight code blocks after rendering
233+
setTimeout(() => { if (window.hljs) hljs.highlightAll(); }, 0);
234+
return html;
235+
}
236+
// Settings modal logic
237+
function openSettings() {
238+
document.getElementById('modal-backdrop').style.display = 'block';
239+
document.getElementById('settings-modal').style.display = 'block';
240+
// Load from localStorage
241+
document.getElementById('settings-username').value = localStorage.getItem('ds_username') || 'User';
242+
document.getElementById('settings-avatar').value = localStorage.getItem('ds_avatar') || 'U';
243+
document.getElementById('settings-system-prompt').value = localStorage.getItem('ds_system_prompt') || 'You are DeepSeek Chat, a helpful AI assistant.';
244+
}
245+
function closeSettings() {
246+
document.getElementById('modal-backdrop').style.display = 'none';
247+
document.getElementById('settings-modal').style.display = 'none';
199248
}
249+
function saveSettings() {
250+
localStorage.setItem('ds_username', document.getElementById('settings-username').value);
251+
localStorage.setItem('ds_avatar', document.getElementById('settings-avatar').value);
252+
localStorage.setItem('ds_system_prompt', document.getElementById('settings-system-prompt').value);
253+
document.getElementById('user_name_hidden').value = document.getElementById('settings-username').value;
254+
document.getElementById('user_avatar_hidden').value = document.getElementById('settings-avatar').value;
255+
document.getElementById('system_prompt_hidden').value = document.getElementById('settings-system-prompt').value;
256+
closeSettings();
257+
}
258+
window.addEventListener('DOMContentLoaded', function() {
259+
// ... existing code ...
260+
// Set hidden fields from localStorage
261+
document.getElementById('user_name_hidden').value = localStorage.getItem('ds_username') || 'User';
262+
document.getElementById('user_avatar_hidden').value = localStorage.getItem('ds_avatar') || 'U';
263+
document.getElementById('system_prompt_hidden').value = localStorage.getItem('ds_system_prompt') || 'You are DeepSeek Chat, a helpful AI assistant.';
264+
});
200265
</script>
201266
</head>
202267
<body>
268+
<div class="modal-backdrop" id="modal-backdrop" onclick="closeSettings()"></div>
269+
<div class="modal" id="settings-modal">
270+
<h2>Chat Settings</h2>
271+
<label for="settings-username">Your Name</label>
272+
<input id="settings-username" maxlength="32" />
273+
<label for="settings-avatar">Avatar (letter or emoji)</label>
274+
<input id="settings-avatar" maxlength="2" />
275+
<label for="settings-system-prompt">System Prompt</label>
276+
<textarea id="settings-system-prompt" rows="4"></textarea>
277+
<div class="modal-actions">
278+
<button onclick="closeSettings()">Cancel</button>
279+
<button onclick="saveSettings()">Save</button>
280+
</div>
281+
</div>
203282
<div class="container">
204283
<header class="top-bar">
205284
<h1 class="app-title">DeepSeek Chat</h1>
@@ -208,23 +287,26 @@ <h1 class="app-title">DeepSeek Chat</h1>
208287
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg>
209288
<span>Export</span>
210289
</button>
290+
<button id="settings-btn" class="theme-toggle" title="Settings" aria-label="Settings" onclick="openSettings()">
291+
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 8 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 5 15.4a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 8a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 8 4.6a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 8c.14.31.22.65.22 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>
292+
</button>
211293
<button id="theme-toggle" class="theme-toggle" title="Toggle theme (Alt+T)" aria-label="Toggle dark/light theme">
212294
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="light-icon"><circle cx="12" cy="12" r="5"></circle><line x1="12" y1="1" x2="12" y2="3"></line><line x1="12" y1="21" x2="12" y2="23"></line><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line><line x1="1" y1="12" x2="3" y2="12"></line><line x1="21" y1="12" x2="23" y2="12"></line><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line></svg>
213295
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="dark-icon" style="display:none;"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path></svg>
214296
</button>
215297
</div>
216298
</header>
217299
<main class="chat-container">
218-
<div id="chat-history" class="chat-history">
300+
<div id="chat-history" class="chat-history">
219301
{% if messages %}
220-
{% for msg in messages %}
221-
<div class="msg-row {{ msg.role }}">
302+
{% for msg in messages %}
303+
<div class="msg-row {{ msg.role }}">
222304
{% if msg.role == 'assistant' %}
223305
<div class="avatar">
224306
<span class="avatar-letter">A</span>
225307
</div>
226308
{% endif %}
227-
<div class="bubble {{ msg.role }}-msg">
309+
<div class="bubble {{ msg.role }}-msg">
228310
<!-- <span class="role">{{ msg.role.title() }}</span> -->
229311
<span class="content">{{ msg.content_html|safe }}</span>
230312
<span class="timestamp">{{ msg.timestamp }}</span>
@@ -242,15 +324,15 @@ <h1 class="app-title">DeepSeek Chat</h1>
242324
{% endif %}
243325
</div>
244326
{% endfor %}
245-
{% else %}
327+
{% else %}
246328
<div class="empty-chat">
247329
<div class="empty-chat-icon">
248330
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20.25c4.97 0 9-3.694 9-8.25s-4.03-8.25-9-8.25S3 7.056 3 11.61c0 1.64.506 3.192 1.397 4.512L3 20.25l5.25-1.837A8.963 8.963 0 0 0 12 20.25z"></path></svg>
249331
</div>
250332
<h2>Welcome to DeepSeek Chat</h2>
251333
<p>Type a message or upload a file to begin.</p>
252334
</div>
253-
{% endif %}
335+
{% endif %}
254336
{% if loading %}
255337
<div class="msg-row assistant loading-placeholder">
256338
<div class="avatar">
@@ -259,10 +341,10 @@ <h2>Welcome to DeepSeek Chat</h2>
259341
<div class="bubble assistant-msg loading-bubble">
260342
<div class="typing-indicator">
261343
<span class="typing-dot"></span><span class="typing-dot"></span><span class="typing-dot"></span>
262-
</div>
263-
</div>
264344
</div>
265-
{% endif %}
345+
</div>
346+
</div>
347+
{% endif %}
266348
</div>
267349
<div id="scroll-bottom" class="scroll-bottom-btn" title="Scroll to bottom">
268350
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>
@@ -271,6 +353,9 @@ <h2>Welcome to DeepSeek Chat</h2>
271353

272354
<form id="chat-form" method="post" onsubmit="sendChat(); return false;">
273355
<textarea id="user_message" name="user_message" placeholder="Type your message... (Shift+Enter for newline)" required rows="1" oninput="autoResizeTextarea(this)" onkeydown="handleKey(event)" aria-label="Message input"></textarea>
356+
<input type="hidden" id="user_name_hidden" name="user_name" />
357+
<input type="hidden" id="user_avatar_hidden" name="user_avatar" />
358+
<input type="hidden" id="system_prompt_hidden" name="system_prompt" />
274359
<button id="send-btn" type="submit" {% if loading %}disabled{% endif %} aria-label="Send message">
275360
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"></line><polygon points="22 2 15 22 11 13 2 9 22 2"></polygon></svg>
276361
</button>
@@ -428,10 +513,16 @@ <h2>Welcome to DeepSeek Chat</h2>
428513
});
429514

430515
function scrollChat() {
431-
if (chatHistoryElem) chatHistoryElem.scrollTop = chatHistoryElem.scrollHeight;
432-
// Check if we need to show the scroll button after new message
516+
const chatHistoryElem = document.getElementById('chat-history');
517+
const scrollBottomBtn = document.getElementById('scroll-bottom');
518+
// Hide if no messages or empty chat
519+
if (!chatHistoryElem || chatHistoryElem.children.length === 0 || chatHistoryElem.querySelector('.empty-chat')) {
520+
scrollBottomBtn.classList.remove('visible');
521+
return;
522+
}
523+
// Show only if not at bottom
433524
const isScrolledUp = chatHistoryElem.scrollTop < chatHistoryElem.scrollHeight - chatHistoryElem.clientHeight - 100;
434-
if (isScrolledUp && chatHistoryElem.scrollHeight > chatHistoryElem.clientHeight) { // Only if scrollable
525+
if (isScrolledUp && chatHistoryElem.scrollHeight > chatHistoryElem.clientHeight) {
435526
scrollBottomBtn.classList.add('visible');
436527
} else {
437528
scrollBottomBtn.classList.remove('visible');

0 commit comments

Comments
 (0)