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' ) ;
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 * ?) < b r > ( [ \s \S ] * ?) < b r > ` ` ` / g, ( match , lang , code ) => `<pre><code class="language-${ lang || '' } ">${ code . replace ( / < b r > / 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