perryrperry commited on
Commit
89f97da
·
verified ·
1 Parent(s): d197fa8

Upload 18 files

Browse files
app.py ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, render_template, request, Response
2
+ from openai import OpenAI
3
+ from dotenv import load_dotenv
4
+ import os
5
+ import json
6
+
7
+ load_dotenv()
8
+
9
+ app = Flask(__name__,
10
+ static_folder='static',
11
+ template_folder='templates'
12
+ )
13
+ client = OpenAI(
14
+ api_key=os.getenv('API_KEY'),
15
+ base_url=os.getenv('BASE_URL')
16
+ )
17
+
18
+ class ChatBot:
19
+ def __init__(self):
20
+ self.messages = []
21
+
22
+ def get_stream_response(self, user_input):
23
+ self.messages.append({"role": "user", "content": user_input})
24
+
25
+ try:
26
+ response = client.chat.completions.create(
27
+ model=os.getenv('MODEL'),
28
+ messages=self.messages,
29
+ stream=True
30
+ )
31
+
32
+ for chunk in response:
33
+ if chunk.choices[0].delta.content:
34
+ yield f"data: {json.dumps({'content': chunk.choices[0].delta.content})}\n\n"
35
+
36
+ # 保存完整对话到历史记录
37
+ full_response = ''.join(chunk.choices[0].delta.content or ''
38
+ for chunk in response)
39
+ self.messages.append({"role": "assistant", "content": full_response})
40
+
41
+ except Exception as e:
42
+ yield f"data: {json.dumps({'error': str(e)})}\n\n"
43
+
44
+ chatbot = ChatBot()
45
+
46
+ @app.route('/')
47
+ def home():
48
+ return render_template('index.html')
49
+
50
+ @app.route('/chat', methods=['POST'])
51
+ def chat():
52
+ user_message = request.json.get('message', '')
53
+ return Response(
54
+ chatbot.get_stream_response(user_message),
55
+ mimetype='text/event-stream'
56
+ )
57
+
58
+ if __name__ == '__main__':
59
+ app.run(host='0.0.0.0', port=7860, debug=True)
static/images/arrow-down.svg ADDED
static/images/copy-icon.svg ADDED
static/images/delete.png ADDED
static/images/edit-icon.svg ADDED
static/images/placeholder-icon.svg ADDED
static/images/placeholder-icon2.svg ADDED
static/images/profile.svg ADDED
static/images/regenerate-icon.svg ADDED
static/images/上传文件.svg ADDED
static/images/下载app.svg ADDED
static/images/侧边栏deepseek字样.svg ADDED
static/images/展开侧边栏.svg ADDED
static/images/收起侧边栏.svg ADDED
static/images/新对话.svg ADDED
static/images/深度思考.svg ADDED
static/images/联网搜索.svg ADDED
templates/index.html ADDED
@@ -0,0 +1,1460 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN" class="notranslate" translate="no">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <link rel="icon" type="image/png"
8
+ href="https://www.praktizijn.nl/wp-content/uploads/2025/01/deepseek-logo-300x300.png">
9
+ <title>DeepSeek - 探索未至之境</title>
10
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/styles/github.min.css">
11
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/markdown-it/13.0.1/markdown-it.min.js"></script>
12
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/highlight.min.js"></script>
13
+
14
+ <style>
15
+ .markdown-content {
16
+ line-height: 1;
17
+ white-space: pre-line;
18
+ }
19
+
20
+ .thinking-content.markdown-content {
21
+ line-height: 1.5;
22
+ font-size: 14px;
23
+ color: rgb(115, 115, 115);
24
+ }
25
+
26
+ .message.user-message .markdown-content {
27
+ line-height: 1.5;
28
+ font-size: 16px;
29
+ padding: 2px 0;
30
+ }
31
+
32
+ .message-content .markdown-content {
33
+ line-height: 1.5;
34
+ font-size: 16px;
35
+ }
36
+
37
+ .markdown-content p,
38
+ .markdown-content ul,
39
+ .markdown-content ol,
40
+ .markdown-content blockquote,
41
+ .markdown-content h1,
42
+ .markdown-content h2,
43
+ .markdown-content h3,
44
+ .markdown-content h4,
45
+ .markdown-content h5,
46
+ .markdown-content h6 {
47
+ margin: 0;
48
+ }
49
+
50
+
51
+ .markdown-content pre,
52
+ .markdown-content table {
53
+ margin: 4px 0;
54
+ }
55
+
56
+
57
+ .markdown-content pre {
58
+ padding: 8px;
59
+ background-color: #f6f8fa;
60
+ border-radius: 6px;
61
+ overflow-x: auto;
62
+ }
63
+
64
+ .markdown-content code {
65
+ font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace;
66
+ font-size: 0.9em;
67
+ padding: 0.2em 0.4em;
68
+ background-color: rgba(175, 184, 193, 0.2);
69
+ border-radius: 4px;
70
+ }
71
+
72
+ .markdown-content pre code {
73
+ padding: 0;
74
+ background-color: transparent;
75
+ }
76
+ </style>
77
+ <style>
78
+ :root {
79
+ --ds-rgb-primary: 77, 107, 254;
80
+ --ds-rgb-label-1: 38, 38, 38;
81
+ --ds-rgb-label-2: 139, 139, 139;
82
+ --ds-rgb-label-3: 115, 115, 115;
83
+ --ds-rgb-elevated: 255, 255, 255;
84
+ --ds-rgb-input: 245, 245, 245;
85
+ --border-color: #dce0e9;
86
+ --side-bg: #f9fbff;
87
+ }
88
+
89
+ body {
90
+ margin: 0;
91
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, sans-serif;
92
+ display: flex;
93
+ height: 100vh;
94
+ overflow: hidden;
95
+ background: #fff;
96
+ }
97
+
98
+ .layout {
99
+ display: flex;
100
+ width: 100%;
101
+ }
102
+
103
+ .sidebar {
104
+ width: 260px;
105
+ background: var(--side-bg);
106
+ border-right: 1px solid var(--border-color);
107
+ display: flex;
108
+ flex-direction: column;
109
+ height: 100vh;
110
+ z-index: 1100;
111
+ }
112
+
113
+ .logo-container {
114
+ height: 56px;
115
+ padding: 0 16px;
116
+ display: flex;
117
+ align-items: center;
118
+ justify-content: space-between;
119
+ border-bottom: 1px solid var(--border-color);
120
+ }
121
+
122
+ .logo-text {
123
+ display: flex;
124
+ align-items: center;
125
+ }
126
+
127
+ .sidebar-toggle {
128
+ width: 28px;
129
+ height: 28px;
130
+ display: flex;
131
+ align-items: center;
132
+ justify-content: center;
133
+ cursor: pointer;
134
+ opacity: 0.6;
135
+ z-index: 1001;
136
+ }
137
+
138
+ .new-chat-btn {
139
+ margin: 12px;
140
+ padding: 8px 16px;
141
+ background: rgb(239, 246, 255);
142
+ border: none;
143
+ border-radius: 8px;
144
+ color: rgb(var(--ds-rgb-primary));
145
+ display: flex;
146
+ align-items: center;
147
+ gap: 8px;
148
+ cursor: pointer;
149
+ font-size: 14px;
150
+ width: calc(100% - 24px);
151
+ }
152
+
153
+ .chat-history {
154
+ flex: 1;
155
+ padding: 0 12px;
156
+ overflow-y: auto;
157
+ }
158
+
159
+ .history-date {
160
+ padding: 8px 4px;
161
+ color: #666;
162
+ font-size: 13px;
163
+ font-weight: 500;
164
+ }
165
+
166
+ .chat-item {
167
+ padding: 8px 12px;
168
+ border-radius: 8px;
169
+ color: rgb(var(--ds-rgb-label-1));
170
+ cursor: pointer;
171
+ font-size: 14px;
172
+ line-height: 1.5;
173
+ }
174
+
175
+ .chat-item:hover {
176
+ background: rgba(77, 107, 254, 0.1);
177
+ }
178
+
179
+ .bottom-tools {
180
+ padding: 12px;
181
+ border-top: 1px solid var(--border-color);
182
+ }
183
+
184
+ .tool-button {
185
+ display: flex;
186
+ align-items: center;
187
+ padding: 8px 12px;
188
+ border-radius: 12px;
189
+ cursor: pointer;
190
+ margin-bottom: 8px;
191
+ color: rgb(var(--ds-rgb-label-2));
192
+ }
193
+
194
+ .tool-button:hover {
195
+ background: rgba(0, 0, 0, 0.05);
196
+ }
197
+
198
+ .new-tag {
199
+ background: linear-gradient(90deg, #4D6BFE 0%, #6948D6 100%);
200
+ color: white;
201
+ padding: 2px 8px;
202
+ border-radius: 8px;
203
+ font-size: 12px;
204
+ margin-left: 8px;
205
+ }
206
+
207
+ .main-content {
208
+ flex: 1;
209
+ display: flex;
210
+ flex-direction: column;
211
+ height: 100vh;
212
+ position: relative;
213
+ }
214
+
215
+ .welcome-container {
216
+ flex: 1;
217
+ display: flex;
218
+ flex-direction: column;
219
+ justify-content: center;
220
+ align-items: center;
221
+ padding: 20px;
222
+ margin-bottom: 250px;
223
+ transition: opacity 0.3s ease;
224
+ }
225
+
226
+ .welcome-message {
227
+ display: flex;
228
+ align-items: center;
229
+ gap: 12px;
230
+ margin-bottom: 12px;
231
+ }
232
+
233
+ .welcome-logo {
234
+ width: 40px;
235
+ height: 40px;
236
+ object-fit: contain;
237
+ }
238
+
239
+ .welcome-title {
240
+ font-size: 24px;
241
+ color: rgb(var(--ds-rgb-label-1));
242
+ font-weight: normal;
243
+ margin: 0;
244
+ }
245
+
246
+ .welcome-subtitle {
247
+ color: rgb(var(--ds-rgb-label-2));
248
+ font-size: 14px;
249
+ margin-bottom: 32px;
250
+ text-align: center;
251
+ }
252
+
253
+ .chat-container {
254
+ display: none;
255
+ flex: 1;
256
+ overflow-y: auto;
257
+ padding: 20px;
258
+ margin-bottom: 180px;
259
+ margin-top: 50px;
260
+ }
261
+
262
+ .message {
263
+ display: flex;
264
+ margin-bottom: 20px;
265
+ animation: fadeIn 0.3s ease;
266
+ }
267
+
268
+ .message.user-message {
269
+ justify-content: flex-end;
270
+ padding-right: 15%;
271
+ }
272
+
273
+ .message-wrapper {
274
+ display: flex;
275
+ flex-direction: column;
276
+ max-width: 80%;
277
+ }
278
+
279
+ .message.user-message .message-wrapper {
280
+ flex-direction: row-reverse;
281
+ }
282
+
283
+ .message-content {
284
+ font-size: 16px;
285
+ line-height: 1.5;
286
+ word-break: break-word;
287
+ }
288
+
289
+ .message.user-message .message-content {
290
+ background: rgb(239, 246, 255);
291
+ color: rgb(38, 38, 38);
292
+ border-radius: 12px;
293
+ padding: 8px 16px;
294
+ margin-right: 12px;
295
+ }
296
+
297
+ .message.ai-message {
298
+ margin-left: 0;
299
+ padding-left: 15%;
300
+ }
301
+
302
+ .message.ai-message .message-content {
303
+ background: transparent;
304
+ color: rgb(38, 38, 38);
305
+ padding: 0;
306
+ margin-left: 12px;
307
+ }
308
+
309
+ .ai-avatar {
310
+ width: 32px;
311
+ height: 32px;
312
+ border-radius: 50%;
313
+ margin-right: 12px;
314
+ flex-shrink: 0;
315
+ }
316
+
317
+
318
+ .thinking-block {
319
+ margin-bottom: 12px;
320
+ width: 100%;
321
+ }
322
+
323
+ .thinking-header {
324
+ display: flex;
325
+ align-items: center;
326
+ padding: 8px 12px;
327
+ cursor: pointer;
328
+ user-select: none;
329
+ background: rgb(250, 250, 250);
330
+ border-radius: 8px;
331
+ margin-bottom: 8px;
332
+ width: 160px;
333
+ }
334
+
335
+ .thinking-icon {
336
+ width: 20px;
337
+ height: 20px;
338
+ margin-right: 8px;
339
+ opacity: 0.6;
340
+ }
341
+
342
+ .thinking-header-text {
343
+ font-size: 14px;
344
+ color: rgb(115, 115, 115);
345
+ }
346
+
347
+ .thinking-content {
348
+ padding: 12px 16px;
349
+ color: rgb(115, 115, 115);
350
+ font-size: 14px;
351
+ display: none;
352
+ line-height: 1.6;
353
+ background: transparent;
354
+ white-space: pre-wrap;
355
+ }
356
+
357
+ .thinking-block.expanded .thinking-content {
358
+ display: block;
359
+ }
360
+
361
+ .thinking-toggle {
362
+ margin-left: auto;
363
+ width: 16px;
364
+ height: 16px;
365
+ transition: transform 0.2s ease;
366
+ background: url("{{ url_for('static', filename='images/arrow-down.svg') }}") no-repeat center;
367
+ background-size: contain;
368
+ }
369
+
370
+
371
+ .thinking-block.expanded .thinking-toggle {
372
+ transform: rotate(180deg);
373
+ }
374
+
375
+
376
+ .input-container {
377
+ position: absolute;
378
+ left: 50%;
379
+ transform: translateX(-50%);
380
+ width: 672px;
381
+ padding: 20px;
382
+ background: white;
383
+ transition: all 0.3s ease;
384
+ margin-bottom: 40px;
385
+ }
386
+
387
+
388
+ .input-container.bottom {
389
+ bottom: 0;
390
+ }
391
+
392
+ .input-container.middle {
393
+ top: 50%;
394
+ transform: translate(-50%, -50%);
395
+ }
396
+
397
+ .input-box {
398
+ background: rgb(var(--ds-rgb-input));
399
+ border: 1px solid var(--border-color);
400
+ border-radius: 30px;
401
+ padding: 12px 16px;
402
+ display: flex;
403
+ flex-direction: column;
404
+ }
405
+
406
+ .input-area {
407
+ width: 100%;
408
+ min-height: 24px;
409
+ max-height: 200px;
410
+ color: rgb(var(--ds-rgb-label-1));
411
+ font-size: 16px;
412
+ padding: 8px 0;
413
+ border: none;
414
+ background: transparent;
415
+ outline: none;
416
+ resize: none;
417
+ line-height: 1.5;
418
+ }
419
+
420
+ .button-group {
421
+ display: flex;
422
+ align-items: center;
423
+ gap: 8px;
424
+ padding: 0 4px;
425
+ margin-top: 8px;
426
+ }
427
+
428
+ .action-button {
429
+ display: flex;
430
+ align-items: center;
431
+ padding: 6px 12px;
432
+ border: 1px solid rgba(0, 0, 0, 0.12);
433
+ border-radius: 12px;
434
+ font-size: 12px;
435
+ gap: 6px;
436
+ cursor: pointer;
437
+ background: white;
438
+ color: #666;
439
+ }
440
+
441
+ .action-button.primary {
442
+ background: rgb(219, 234, 254);
443
+ color: rgb(var(--ds-rgb-primary));
444
+ border-color: rgba(0, 122, 255, 0.15);
445
+ }
446
+
447
+ .action-tools {
448
+ margin-left: auto;
449
+ display: flex;
450
+ gap: 12px;
451
+ align-items: center;
452
+ }
453
+
454
+ .tool-icon {
455
+ width: 32px;
456
+ height: 32px;
457
+ display: flex;
458
+ align-items: center;
459
+ justify-content: center;
460
+ cursor: pointer;
461
+ border-radius: 50%;
462
+ background: rgb(245, 245, 245);
463
+ }
464
+
465
+ .tool-icon:hover {
466
+ background: rgb(239, 246, 255);
467
+ }
468
+
469
+ .send-button {
470
+ width: 32px;
471
+ height: 32px;
472
+ display: flex;
473
+ align-items: center;
474
+ justify-content: center;
475
+ cursor: pointer;
476
+ background-color: #DDD;
477
+ border-radius: 50%;
478
+ transition: background-color 0.2s ease;
479
+ }
480
+
481
+ .send-button:hover {
482
+ background-color: rgb(191, 219, 254);
483
+ }
484
+
485
+ .ds-icon {
486
+ font-size: 16px;
487
+ width: 14px;
488
+ height: 14px;
489
+ color: #fafafa;
490
+ display: flex;
491
+ align-items: center;
492
+ justify-content: center;
493
+ }
494
+
495
+
496
+ .send-icon {
497
+ width: 16px;
498
+ height: 16px;
499
+ color: rgb(115, 115, 115);
500
+ }
501
+
502
+ .footer-text {
503
+ text-align: center;
504
+ color: rgb(var(--ds-rgb-label-2));
505
+ font-size: 12px;
506
+ padding: 12px 0;
507
+ position: absolute;
508
+ bottom: 0;
509
+ width: 100%;
510
+ background: white;
511
+ }
512
+
513
+ @keyframes fadeIn {
514
+ from {
515
+ opacity: 0;
516
+ transform: translateY(10px);
517
+ }
518
+
519
+ to {
520
+ opacity: 1;
521
+ transform: translateY(0);
522
+ }
523
+ }
524
+
525
+
526
+ @keyframes typing {
527
+ from {
528
+ width: 0;
529
+ }
530
+
531
+ to {
532
+ width: 100%;
533
+ }
534
+ }
535
+
536
+ .typing {
537
+ overflow: hidden;
538
+ white-space: pre-wrap;
539
+ animation: typing 0.05s steps(1, end);
540
+ }
541
+
542
+ .cursor {
543
+ border-right: 2px solid #666;
544
+ animation: blink 0.75s step-end infinite;
545
+ }
546
+
547
+ @keyframes blink {
548
+
549
+ from,
550
+ to {
551
+ border-color: transparent;
552
+ }
553
+
554
+ 50% {
555
+ border-color: #666;
556
+ }
557
+ }
558
+
559
+ @media screen and (max-width: 768px) {
560
+ .layout {
561
+ position: relative;
562
+ }
563
+
564
+ .sidebar {
565
+ position: fixed;
566
+ left: 0;
567
+ top: 0;
568
+ bottom: 0;
569
+ z-index: 1100;
570
+ transform: translateX(0);
571
+ transition: transform 0.3s ease;
572
+ }
573
+
574
+ .sidebar.collapsed {
575
+ transform: translateX(-100%);
576
+ }
577
+
578
+ .main-content {
579
+ margin-left: 0;
580
+ width: 100%;
581
+ }
582
+
583
+ .input-container {
584
+ width: 80%;
585
+ padding: 12px;
586
+ }
587
+
588
+ .input-box {
589
+ max-width: 100%;
590
+ }
591
+
592
+ .chat-container {
593
+ padding: 16px;
594
+ }
595
+
596
+ .message.ai-message {
597
+ margin-left: 0;
598
+ padding-left: 5%;
599
+ }
600
+
601
+ .message.user-message {
602
+ justify-content: flex-end;
603
+ padding-right: 5%;
604
+ }
605
+ }
606
+
607
+
608
+
609
+
610
+ .mobile-menu-trigger {
611
+ display: none;
612
+ position: fixed;
613
+ top: 12px;
614
+ left: 12px;
615
+ z-index: 999;
616
+ padding: 8px;
617
+ background: white;
618
+ border-radius: 8px;
619
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
620
+ }
621
+
622
+ @media screen and (max-width: 768px) {
623
+ .mobile-menu-trigger {
624
+ display: block;
625
+ pointer-events: auto;
626
+ }
627
+
628
+ .sidebar.collapsed+.main-content .mobile-menu-trigger {
629
+ left: 12px;
630
+ }
631
+ }
632
+ </style>
633
+ </head>
634
+
635
+ <body>
636
+ <div class="layout">
637
+ <!-- 侧边栏部分 -->
638
+ <div class="sidebar">
639
+ <div class="logo-container">
640
+ <div class="logo-text">
641
+ <img src="{{ url_for('static', filename='images/侧边栏deepseek字样.svg') }}" alt="DeepSeek"
642
+ height="22">
643
+ </div>
644
+ <div class="sidebar-toggle">
645
+ <img src="{{ url_for('static', filename='images/收起侧边栏.svg') }}" alt="收起侧边栏" width="28"
646
+ height="28">
647
+ </div>
648
+ </div>
649
+
650
+ <button class="new-chat-btn">
651
+ <img src="{{ url_for('static', filename='images/新对话.svg') }}" alt="新对话" class="new-chat-icon">
652
+ 开启新对话
653
+ </button>
654
+
655
+ <div class="chat-history">
656
+ </div>
657
+
658
+ <div class="bottom-tools">
659
+ <div class="tool-button">
660
+ <img src="{{ url_for('static', filename='images/下载app.svg') }}"alt="下载App"
661
+ style="width: 20px; height: 20px; margin-right: 8px;">
662
+ 下载 App
663
+ <span class="new-tag">NEW</span>
664
+ </div>
665
+ <div class="tool-button">
666
+ <img src="{{ url_for('static', filename='images/profile.svg') }}" alt="个人信息"
667
+ style="width: 20px; height: 20px; margin-right: 8px;">
668
+ 个人信息
669
+ </div>
670
+ </div>
671
+ </div>
672
+
673
+ <!-- 主要内容区域 -->
674
+ <div class="main-content">
675
+ <div class="welcome-container">
676
+ <div class="welcome-message">
677
+ <img src="https://www.praktizijn.nl/wp-content/uploads/2025/01/deepseek-logo-300x300.png"
678
+ alt="DeepSeek Logo" class="welcome-logo">
679
+ <h1 class="welcome-title">我是 DeepSeek, 很高兴见到你!</h1>
680
+ </div>
681
+ <p class="welcome-subtitle">我可以帮你写代码、读文件、写作各种创意内容,请把你的任务交给我吧~</p>
682
+ </div>
683
+
684
+ <div class="chat-container">
685
+ <!-- Messages will be inserted here -->
686
+ </div>
687
+
688
+ <div class="input-container middle">
689
+ <div class="input-box">
690
+ <textarea class="input-area" placeholder="给 DeepSeek 发送消息"></textarea>
691
+ <div class="button-group">
692
+ <button class="action-button primary">
693
+ <img src="{{ url_for('static', filename='images/深度思考.svg') }}"alt="深度思考">
694
+ 深度思考 (R1)
695
+ </button>
696
+ <button class="action-button">
697
+ <img src="{{ url_for('static', filename='images/联网搜索.svg') }}"alt="联网搜索">
698
+ 联网搜索
699
+ </button>
700
+ <div class="action-tools">
701
+ <div class="tool-icon">
702
+ <img src="{{ url_for('static', filename='images/上传文件.svg') }}" alt="上传文件"
703
+ width="20" height="20">
704
+ </div>
705
+ <div class="send-button">
706
+ <svg class="send-icon" viewBox="0 0 14 16" fill="none"
707
+ xmlns="http://www.w3.org/2000/svg">
708
+ <path fill-rule="evenodd" clip-rule="evenodd"
709
+ d="M7 16c-.595 0-1.077-.462-1.077-1.032V1.032C5.923.462 6.405 0 7 0s1.077.462 1.077 1.032v13.936C8.077 15.538 7.595 16 7 16z"
710
+ fill="currentColor" />
711
+ <path fill-rule="evenodd" clip-rule="evenodd"
712
+ d="M.315 7.44a1.002 1.002 0 0 1 0-1.46L6.238.302a1.11 1.11 0 0 1 1.523 0c.421.403.421 1.057 0 1.46L1.838 7.44a1.11 1.11 0 0 1-1.523 0z"
713
+ fill="currentColor" />
714
+ <path fill-rule="evenodd" clip-rule="evenodd"
715
+ d="M13.685 7.44a1.11 1.11 0 0 1-1.523 0L6.238 1.762a1.002 1.002 0 0 1 0-1.46 1.11 1.11 0 0 1 1.523 0l5.924 5.678c.42.403.42 1.056 0 1.46z"
716
+ fill="currentColor" />
717
+ </svg>
718
+ </div>
719
+ </div>
720
+ </div>
721
+ </div>
722
+ </div>
723
+ <div class="footer-text">内容由 AI 生成,请仔细甄别,这个页面也是假的,请大家仔细甄别</div>
724
+ </div>
725
+ </div>
726
+
727
+ <script>
728
+ const STATIC_URL = "{{ url_for('static', filename='images') }}";
729
+ class ChatManager {
730
+ constructor() {
731
+ this.chatContainer = document.querySelector('.chat-container');
732
+ this.inputArea = document.querySelector('.input-area');
733
+ this.sendButton = document.querySelector('.send-button');
734
+ this.welcomeContainer = document.querySelector('.welcome-container');
735
+ this.inputContainer = document.querySelector('.input-container');
736
+ this.aiAvatar = document.querySelector('.welcome-logo').src;
737
+ this.sidebar = document.querySelector('.sidebar');
738
+ this.sidebarToggle = document.querySelector('.sidebar-toggle');
739
+
740
+ // 初始化 markdown-it
741
+ this.md = window.markdownit({
742
+ html: false,
743
+ breaks: true,
744
+ linkify: true,
745
+ highlight: function (str, lang) {
746
+ if (lang && hljs.getLanguage(lang)) {
747
+ try {
748
+ return hljs.highlight(str, { language: lang }).value;
749
+ } catch (__) { }
750
+ }
751
+ return '';
752
+ }
753
+ });
754
+ this.md.renderer.rules.paragraph_open = () => '';
755
+ this.md.renderer.rules.paragraph_close = () => '';
756
+ this.md.renderer.rules.softbreak = () => ' ';
757
+ // 状态机相关属性
758
+ this.isInThinkBlock = false;
759
+ this.thinkTagBuffer = '';
760
+ this.currentMessageElement = null;
761
+ this.thinkingElement = null;
762
+ this.responseElement = null;
763
+ this.isWaitingForResponse = false;
764
+
765
+ // 初始化事件监听器
766
+ this.initializeEventListeners();
767
+ this.initializeSidebarToggle();
768
+ this.initializeResponsiveLayout();
769
+ }
770
+
771
+ initializeEventListeners() {
772
+ this.sendButton.addEventListener('click', () => this.handleSendMessage());
773
+ this.inputArea.addEventListener('keydown', (e) => {
774
+ if (e.key === 'Enter' && !e.shiftKey) {
775
+ e.preventDefault();
776
+ this.handleSendMessage();
777
+ }
778
+ });
779
+
780
+ this.inputArea.addEventListener('input', () => {
781
+ this.inputArea.style.height = 'auto';
782
+ this.inputArea.style.height = Math.min(this.inputArea.scrollHeight, 200) + 'px';
783
+ });
784
+ }
785
+
786
+ initializeSidebarToggle() {
787
+ this.sidebarToggle.addEventListener('click', () => {
788
+ this.sidebar.classList.toggle('collapsed');
789
+ });
790
+ }
791
+
792
+ initializeResponsiveLayout() {
793
+ const menuTrigger = document.createElement('div');
794
+ menuTrigger.className = 'mobile-menu-trigger';
795
+ menuTrigger.innerHTML = `<img src="${STATIC_URL}/展开侧边栏.svg" alt="菜单" width="24" height="24">`;
796
+ document.querySelector('.main-content').prepend(menuTrigger);
797
+ if (window.innerWidth <= 768) {
798
+ this.sidebar.classList.add('collapsed');
799
+ }
800
+
801
+ menuTrigger.addEventListener('click', () => {
802
+ this.sidebar.classList.remove('collapsed');
803
+ });
804
+
805
+ // 移动端侧边栏处理
806
+ document.addEventListener('click', (e) => {
807
+ if (window.innerWidth <= 768 &&
808
+ !this.sidebar.contains(e.target) &&
809
+ !menuTrigger.contains(e.target)) {
810
+ this.sidebar.classList.add('collapsed');
811
+ }
812
+ });
813
+ }
814
+ async handleSendMessage() {
815
+ const message = this.inputArea.value.trim();
816
+ if (!message || this.isWaitingForResponse) return;
817
+
818
+ this.moveInputToBottom();
819
+ this.addUserMessage(message);
820
+ this.clearInput();
821
+
822
+ // 创建消息容器
823
+ const messageHTML = `
824
+ <div class="message ai-message">
825
+ <img src="${this.aiAvatar}" alt="AI" class="ai-avatar">
826
+ <div class="message-wrapper">
827
+ <div class="thinking-block expanded">
828
+ <div class="thinking-header">
829
+ <img src="${STATIC_URL}/深度思考.svg" alt="思考" class="thinking-icon">
830
+ <span class="thinking-header-text">深度思考</span>
831
+ <img src="${STATIC_URL}/arrow-down.svg" alt="展开" class="thinking-toggle">
832
+ </div>
833
+ <div class="thinking-content markdown-content typing"></div>
834
+ </div>
835
+ <div class="message-content">
836
+ <div class="markdown-content typing"></div>
837
+ </div>
838
+ </div>
839
+ </div>
840
+ `;
841
+
842
+ this.chatContainer.insertAdjacentHTML('beforeend', messageHTML);
843
+ const messageElement = this.chatContainer.lastElementChild;
844
+ const thinkingContent = messageElement.querySelector('.thinking-content');
845
+ const responseContent = messageElement.querySelector('.message-content .markdown-content');
846
+
847
+ this.initializeThinkingBlocks();
848
+
849
+ try {
850
+ const response = await fetch('/chat', {
851
+ method: 'POST',
852
+ headers: {
853
+ 'Content-Type': 'application/json',
854
+ },
855
+ body: JSON.stringify({ message: message })
856
+ });
857
+
858
+ const reader = response.body.getReader();
859
+ const decoder = new TextDecoder();
860
+ let isInThinkBlock = false;
861
+ let thinkText = '';
862
+ let responseText = '';
863
+
864
+ while (true) {
865
+ const { done, value } = await reader.read();
866
+ if (done) break;
867
+
868
+ const chunk = decoder.decode(value);
869
+ const lines = chunk.split('\n');
870
+
871
+ for (const line of lines) {
872
+ if (line.startsWith('data: ')) {
873
+ try {
874
+ const data = JSON.parse(line.slice(6));
875
+ if (data.content) {
876
+ // 检查是否是标签
877
+ if (data.content.includes('<think>')) {
878
+ isInThinkBlock = true;
879
+ continue;
880
+ }
881
+ if (data.content.includes('</think>')) {
882
+ isInThinkBlock = false;
883
+ continue;
884
+ }
885
+
886
+ // 根据当前状态追加内容
887
+ if (isInThinkBlock) {
888
+ thinkText += data.content;
889
+ thinkingContent.innerHTML = this.md.render(thinkText);
890
+ } else {
891
+ responseText += data.content;
892
+ responseContent.innerHTML = this.md.render(responseText);
893
+ }
894
+
895
+ // 代码高亮
896
+ messageElement.querySelectorAll('pre code').forEach((block) => {
897
+ hljs.highlightElement(block);
898
+ });
899
+
900
+ this.scrollToBottom();
901
+ }
902
+ } catch (e) {
903
+ console.error('Error processing stream:', e);
904
+ }
905
+ }
906
+ }
907
+ }
908
+
909
+ // 完成后移除打字机效果
910
+ thinkingContent.classList.remove('typing');
911
+ responseContent.classList.remove('typing');
912
+
913
+ } catch (error) {
914
+ console.error('Request failed:', error);
915
+ this.handleError(error);
916
+ } finally {
917
+ this.isWaitingForResponse = false;
918
+ this.sendButton.style.opacity = '1';
919
+ this.sendButton.style.pointerEvents = 'auto';
920
+ }
921
+ }
922
+ processStreamChar(char) {
923
+ // 处理标签检测缓冲区
924
+ this.thinkTagBuffer += char;
925
+
926
+ // 检查开始标签
927
+ if (this.thinkTagBuffer.includes('<think>')) {
928
+ this.isInThinkBlock = true;
929
+ this.thinkTagBuffer = '';
930
+ return;
931
+ }
932
+
933
+ // 检查结束标签
934
+ if (this.thinkTagBuffer.includes('</think>')) {
935
+ this.isInThinkBlock = false;
936
+ this.thinkTagBuffer = '';
937
+ return;
938
+ }
939
+
940
+ // 维护标签检测缓冲区大小
941
+ if (this.thinkTagBuffer.length > 7) {
942
+ this.outputChar(this.thinkTagBuffer[0]);
943
+ this.thinkTagBuffer = this.thinkTagBuffer.slice(1);
944
+ }
945
+ }
946
+
947
+ outputChar(char) {
948
+ if (!char) return;
949
+
950
+ const targetElement = this.isInThinkBlock ? this.thinkingElement : this.responseElement;
951
+ if (!targetElement) return;
952
+
953
+ targetElement.textContent += char;
954
+ this.scrollToBottom();
955
+ }
956
+
957
+ createNewMessageContainer() {
958
+ const messageHTML = `
959
+ <div class="message ai-message">
960
+ <img src="${this.aiAvatar}" alt="AI" class="ai-avatar">
961
+ <div class="message-wrapper">
962
+ <div class="thinking-block expanded">
963
+ <div class="thinking-header">
964
+ <img src="${STATIC_URL}/深度思考.svg" alt="思考" class="thinking-icon">
965
+ <span class="thinking-header-text">深度思考</span>
966
+ <img src="${STATIC_URL}/arrow-down.svg" alt="展开" class="thinking-toggle">
967
+ </div>
968
+ <div class="thinking-content"></div>
969
+ </div>
970
+ <div class="message-content">
971
+ <div class="markdown-content"></div>
972
+ </div>
973
+ </div>
974
+ </div>
975
+ `;
976
+
977
+ this.chatContainer.insertAdjacentHTML('beforeend', messageHTML);
978
+ this.currentMessageElement = this.chatContainer.lastElementChild;
979
+ this.thinkingElement = this.currentMessageElement.querySelector('.thinking-content');
980
+ this.responseElement = this.currentMessageElement.querySelector('.markdown-content');
981
+
982
+ this.initializeThinkingBlocks();
983
+ }
984
+
985
+ initializeThinkingBlocks() {
986
+ const thinkingBlocks = document.querySelectorAll('.thinking-header');
987
+ thinkingBlocks.forEach(block => {
988
+ if (!block.hasListener) {
989
+ block.addEventListener('click', () => {
990
+ block.parentElement.classList.toggle('expanded');
991
+ const toggle = block.querySelector('.thinking-toggle');
992
+ if (toggle) {
993
+ toggle.style.transform = block.parentElement.classList.contains('expanded')
994
+ ? 'rotate(180deg)'
995
+ : 'rotate(0deg)';
996
+ }
997
+ });
998
+ block.hasListener = true;
999
+ }
1000
+ });
1001
+ }
1002
+
1003
+ moveInputToBottom() {
1004
+ if (this.inputContainer.classList.contains('middle')) {
1005
+ this.inputContainer.classList.remove('middle');
1006
+ this.inputContainer.classList.add('bottom');
1007
+
1008
+ this.welcomeContainer.style.opacity = '0';
1009
+ setTimeout(() => {
1010
+ this.welcomeContainer.style.display = 'none';
1011
+ this.chatContainer.style.display = 'block';
1012
+ }, 300);
1013
+ }
1014
+ }
1015
+
1016
+ addUserMessage(message) {
1017
+ const messageHTML = `
1018
+ <div class="message user-message">
1019
+ <div class="message-wrapper">
1020
+ <div class="message-content">
1021
+ ${this.md.render(this.escapeHtml(message))}
1022
+ </div>
1023
+ </div>
1024
+ </div>
1025
+ `;
1026
+ this.chatContainer.insertAdjacentHTML('beforeend', messageHTML);
1027
+ this.scrollToBottom();
1028
+ }
1029
+
1030
+ handleError(error) {
1031
+ const errorMessage = `
1032
+ <div class="message system-message error">
1033
+ <div class="message-content">
1034
+ 抱歉,发生了错误:${error.message}
1035
+ </div>
1036
+ </div>
1037
+ `;
1038
+ this.chatContainer.insertAdjacentHTML('beforeend', errorMessage);
1039
+ this.scrollToBottom();
1040
+ }
1041
+
1042
+ clearInput() {
1043
+ this.inputArea.value = '';
1044
+ this.inputArea.style.height = 'auto';
1045
+ }
1046
+
1047
+ scrollToBottom() {
1048
+ this.chatContainer.scrollTop = this.chatContainer.scrollHeight;
1049
+ }
1050
+
1051
+ escapeHtml(unsafe) {
1052
+ return unsafe
1053
+ .replace(/&/g, "&amp;")
1054
+ .replace(/</g, "&lt;")
1055
+ .replace(/>/g, "&gt;")
1056
+ .replace(/"/g, "&quot;")
1057
+ .replace(/'/g, "&#039;");
1058
+ }
1059
+ }
1060
+ class ChatHistoryManager {
1061
+ constructor() {
1062
+ this.chatContainer = document.querySelector('.chat-container');
1063
+ this.historyContainer = document.querySelector('.chat-history');
1064
+ this.newChatBtn = document.querySelector('.new-chat-btn');
1065
+ this.inputContainer = document.querySelector('.input-container');
1066
+ this.welcomeContainer = document.querySelector('.welcome-container');
1067
+ this.sidebar = document.querySelector('.sidebar');
1068
+ this.sidebarToggle = document.querySelector('.sidebar-toggle');
1069
+ this.mobileMenuTrigger = document.querySelector('.mobile-menu-trigger');
1070
+
1071
+ this.initializeEventListeners();
1072
+ this.loadHistory();
1073
+ this.initializeSidebar();
1074
+ }
1075
+
1076
+ initializeSidebar() {
1077
+ this.sidebarToggle.addEventListener('click', () => {
1078
+ this.sidebar.classList.toggle('collapsed');
1079
+ });
1080
+
1081
+ if (this.mobileMenuTrigger) {
1082
+ this.mobileMenuTrigger.addEventListener('click', () => {
1083
+ this.sidebar.classList.remove('collapsed');
1084
+ });
1085
+
1086
+ document.addEventListener('click', (e) => {
1087
+ if (window.innerWidth <= 768 &&
1088
+ !this.sidebar.contains(e.target) &&
1089
+ !this.mobileMenuTrigger.contains(e.target)) {
1090
+ this.sidebar.classList.add('collapsed');
1091
+ }
1092
+ });
1093
+ }
1094
+ }
1095
+
1096
+ initializeEventListeners() {
1097
+ this.newChatBtn.addEventListener('click', () => {
1098
+ if (this.chatContainer.children.length > 0) {
1099
+ this.saveCurrentChat();
1100
+ }
1101
+
1102
+ this.chatContainer.innerHTML = '';
1103
+ this.chatContainer.style.display = 'none';
1104
+ this.welcomeContainer.style.display = 'flex';
1105
+ this.welcomeContainer.style.opacity = '1';
1106
+ this.inputContainer.classList.remove('bottom');
1107
+ this.inputContainer.classList.add('middle');
1108
+
1109
+ if (window.innerWidth <= 768) {
1110
+ this.sidebar.classList.add('collapsed');
1111
+ }
1112
+ });
1113
+ }
1114
+
1115
+ loadHistory() {
1116
+ const history = this.getHistoryFromStorage();
1117
+ this.renderHistory(history);
1118
+ }
1119
+
1120
+ getHistoryFromStorage() {
1121
+ const history = localStorage.getItem('chatHistory');
1122
+ return history ? JSON.parse(history) : [];
1123
+ }
1124
+
1125
+ saveHistoryToStorage(history) {
1126
+ localStorage.setItem('chatHistory', JSON.stringify(history));
1127
+ }
1128
+
1129
+ renderHistory(history) {
1130
+ this.historyContainer.innerHTML = '';
1131
+
1132
+ const today = new Date().toDateString();
1133
+ const yesterday = new Date(Date.now() - 86400000).toDateString();
1134
+
1135
+ const dateGroups = {
1136
+ '今天': [],
1137
+ '昨天': [],
1138
+ '7 天内': [],
1139
+ '更早': []
1140
+ };
1141
+
1142
+ history.forEach(chat => {
1143
+ const chatDate = new Date(chat.timestamp).toDateString();
1144
+
1145
+ if (chatDate === today) {
1146
+ dateGroups['今天'].push(chat);
1147
+ } else if (chatDate === yesterday) {
1148
+ dateGroups['昨天'].push(chat);
1149
+ } else if (new Date() - new Date(chat.timestamp) < 7 * 86400000) {
1150
+ dateGroups['7 天内'].push(chat);
1151
+ } else {
1152
+ dateGroups['更早'].push(chat);
1153
+ }
1154
+ });
1155
+
1156
+ Object.entries(dateGroups).forEach(([date, chats]) => {
1157
+ if (chats.length > 0) {
1158
+ const dateHeader = document.createElement('div');
1159
+ dateHeader.className = 'history-date';
1160
+ dateHeader.textContent = date;
1161
+ this.historyContainer.appendChild(dateHeader);
1162
+
1163
+ chats.forEach(chat => {
1164
+ const chatItem = this.createHistoryItem(chat);
1165
+ this.historyContainer.appendChild(chatItem);
1166
+ });
1167
+ }
1168
+ });
1169
+ }
1170
+
1171
+ createHistoryItem(chat) {
1172
+ const item = document.createElement('div');
1173
+ item.className = 'chat-item';
1174
+ item.dataset.id = chat.id;
1175
+
1176
+ const content = document.createElement('div');
1177
+ content.className = 'chat-item-title';
1178
+ content.textContent = chat.title;
1179
+
1180
+ const actions = document.createElement('div');
1181
+ actions.className = 'chat-item-actions';
1182
+
1183
+ const editBtn = document.createElement('button');
1184
+ editBtn.className = 'history-action-btn';
1185
+ editBtn.innerHTML = `<img src="${STATIC_URL}/edit-icon.svg" alt="编辑">`;
1186
+ editBtn.onclick = (e) => {
1187
+ e.stopPropagation();
1188
+ this.editHistoryItem(chat.id);
1189
+ };
1190
+
1191
+ const deleteBtn = document.createElement('button');
1192
+ deleteBtn.className = 'history-action-btn';
1193
+ deleteBtn.innerHTML = `<img src="${STATIC_URL}/delete.png" alt="编辑">`;
1194
+ deleteBtn.onclick = (e) => {
1195
+ e.stopPropagation();
1196
+ this.deleteHistoryItem(chat.id);
1197
+ };
1198
+
1199
+ actions.appendChild(editBtn);
1200
+ actions.appendChild(deleteBtn);
1201
+ item.appendChild(content);
1202
+ item.appendChild(actions);
1203
+
1204
+ item.addEventListener('click', () => {
1205
+ this.loadChat(chat.id);
1206
+ if (window.innerWidth <= 768) {
1207
+ this.sidebar.classList.add('collapsed');
1208
+ }
1209
+ });
1210
+
1211
+ return item;
1212
+ }
1213
+
1214
+ loadChat(chatId) {
1215
+ const history = this.getHistoryFromStorage();
1216
+ const chat = history.find(c => c.id === chatId);
1217
+
1218
+ if (chat) {
1219
+ this.chatContainer.innerHTML = '';
1220
+ this.chatContainer.style.display = 'block';
1221
+ this.welcomeContainer.style.display = 'none';
1222
+ this.welcomeContainer.style.opacity = '0';
1223
+ this.inputContainer.classList.remove('middle');
1224
+ this.inputContainer.classList.add('bottom');
1225
+
1226
+ chat.messages.forEach(msg => this.renderMessage(msg));
1227
+ }
1228
+ }
1229
+
1230
+ renderMessage(msg) {
1231
+ if (msg.type === 'user') {
1232
+ this.chatContainer.insertAdjacentHTML('beforeend', `
1233
+ <div class="message user-message">
1234
+ <div class="message-wrapper">
1235
+ <div class="message-content markdown-content">${msg.content.trim()}</div>
1236
+ </div>
1237
+ </div>
1238
+ `);
1239
+ } else {
1240
+ this.chatContainer.insertAdjacentHTML('beforeend', `
1241
+ <div class="message ai-message">
1242
+ <img src="${document.querySelector('.welcome-logo').src}" alt="AI" class="ai-avatar">
1243
+ <div class="message-wrapper">
1244
+ ${msg.thinking ? `
1245
+ <div class="thinking-block">
1246
+ <div class="thinking-header" role="button" aria-expanded="false">
1247
+ <img src="${STATIC_URL}/深度思考.svg" alt="思考" class="thinking-icon">
1248
+ <span class="thinking-header-text">深度思考</span>
1249
+ <div class="thinking-toggle"></div>
1250
+ </div>
1251
+ <div class="thinking-content markdown-content">${msg.thinking.content.trim()}</div>
1252
+ </div>
1253
+ ` : ''}
1254
+ <div class="message-content">
1255
+ <div class="markdown-content">${msg.content.trim()}</div>
1256
+ </div>
1257
+ </div>
1258
+ </div>
1259
+ `);
1260
+
1261
+ // Initialize thinking block functionality for the newly added message
1262
+ const latestMessage = this.chatContainer.lastElementChild;
1263
+ const thinkingBlock = latestMessage.querySelector('.thinking-block');
1264
+ if (thinkingBlock) {
1265
+ const header = thinkingBlock.querySelector('.thinking-header');
1266
+ header.addEventListener('click', () => {
1267
+ thinkingBlock.classList.toggle('expanded');
1268
+ header.setAttribute('aria-expanded', thinkingBlock.classList.contains('expanded'));
1269
+ });
1270
+ }
1271
+ }
1272
+ }
1273
+ saveCurrentChat() {
1274
+ const messages = Array.from(this.chatContainer.children);
1275
+ if (messages.length === 0) return;
1276
+
1277
+ const firstMessage = messages[0].querySelector('.message-content').textContent.trim();
1278
+ const title = firstMessage.length > 30 ? firstMessage.substring(0, 30) + '...' : firstMessage;
1279
+
1280
+ const chat = {
1281
+ id: Date.now().toString(),
1282
+ title: title,
1283
+ timestamp: new Date().toISOString(),
1284
+ messages: messages.map(msg => {
1285
+ const messageData = {
1286
+ type: msg.classList.contains('user-message') ? 'user' : 'ai',
1287
+ content: msg.querySelector('.message-content').innerHTML.trim()
1288
+ };
1289
+
1290
+ if (!msg.classList.contains('user-message')) {
1291
+ const thinkingBlock = msg.querySelector('.thinking-content');
1292
+ if (thinkingBlock) {
1293
+ messageData.thinking = {
1294
+ content: thinkingBlock.innerHTML.trim()
1295
+ };
1296
+ }
1297
+ }
1298
+
1299
+ return messageData;
1300
+ })
1301
+ };
1302
+
1303
+ const history = this.getHistoryFromStorage();
1304
+ history.unshift(chat);
1305
+ this.saveHistoryToStorage(history);
1306
+ this.renderHistory(history);
1307
+ }
1308
+ editHistoryItem(chatId) {
1309
+ const history = this.getHistoryFromStorage();
1310
+ const chat = history.find(c => c.id === chatId);
1311
+
1312
+ if (chat) {
1313
+ const newTitle = prompt('编辑聊天标题:', chat.title);
1314
+ if (newTitle && newTitle.trim()) {
1315
+ chat.title = newTitle.trim();
1316
+ this.saveHistoryToStorage(history);
1317
+ this.renderHistory(history);
1318
+ }
1319
+ }
1320
+ }
1321
+
1322
+ deleteHistoryItem(chatId) {
1323
+ if (confirm('确定要删除这条聊天记录吗?')) {
1324
+ const history = this.getHistoryFromStorage();
1325
+ const filteredHistory = history.filter(c => c.id !== chatId);
1326
+ this.saveHistoryToStorage(filteredHistory);
1327
+ this.renderHistory(filteredHistory);
1328
+
1329
+ // If the deleted chat is currently open, clear the chat container
1330
+ const currentChat = document.querySelector('.chat-container').children.length > 0;
1331
+ if (currentChat) {
1332
+ this.chatContainer.innerHTML = '';
1333
+ this.chatContainer.style.display = 'none';
1334
+ this.welcomeContainer.style.display = 'flex';
1335
+ this.welcomeContainer.style.opacity = '1';
1336
+ this.inputContainer.classList.remove('bottom');
1337
+ this.inputContainer.classList.add('middle');
1338
+ }
1339
+ }
1340
+ }
1341
+ }
1342
+
1343
+ const styles = `
1344
+ .sidebar {
1345
+ width: 260px;
1346
+ background: var(--side-bg);
1347
+ border-right: 1px solid var(--border-color);
1348
+ display: flex;
1349
+ flex-direction: column;
1350
+ height: 100vh;
1351
+ transition: transform 0.3s ease;
1352
+ }
1353
+
1354
+ .sidebar.collapsed {
1355
+ transform: translateX(-100%);
1356
+ }
1357
+
1358
+ @media (max-width: 768px) {
1359
+ .sidebar {
1360
+ position: fixed;
1361
+ left: 0;
1362
+ top: 0;
1363
+ z-index: 1000;
1364
+ }
1365
+ }
1366
+
1367
+ .history-date {
1368
+ padding: 8px 4px;
1369
+ color: rgb(115, 115, 115);
1370
+ font-size: 13px;
1371
+ font-weight: 500;
1372
+ }
1373
+
1374
+ .chat-item {
1375
+ display: flex;
1376
+ align-items: center;
1377
+ justify-content: space-between;
1378
+ padding: 8px 12px;
1379
+ cursor: pointer;
1380
+ border-radius: 8px;
1381
+ transition: background-color 0.2s ease;
1382
+ }
1383
+
1384
+ .chat-item:hover {
1385
+ background-color: rgba(77, 107, 254, 0.1);
1386
+ }
1387
+
1388
+ .chat-item-title {
1389
+ flex: 1;
1390
+ overflow: hidden;
1391
+ text-overflow: ellipsis;
1392
+ white-space: nowrap;
1393
+ font-size: 14px;
1394
+ color: rgb(38, 38, 38);
1395
+ }
1396
+
1397
+ .chat-item-actions {
1398
+ display: none;
1399
+ gap: 8px;
1400
+ }
1401
+
1402
+ .chat-item:hover .chat-item-actions {
1403
+ display: flex;
1404
+ }
1405
+
1406
+ .history-action-btn {
1407
+ background: none;
1408
+ border: none;
1409
+ padding: 4px;
1410
+ cursor: pointer;
1411
+ opacity: 0.6;
1412
+ transition: opacity 0.2s ease;
1413
+ }
1414
+
1415
+ .history-action-btn:hover {
1416
+ opacity: 1;
1417
+ }
1418
+
1419
+ .history-action-btn img,
1420
+ .history-action-btn i {
1421
+ width: 16px;
1422
+ height: 16px;
1423
+ color: rgb(115, 115, 115);
1424
+ }
1425
+
1426
+ .mobile-menu-trigger {
1427
+ display: none;
1428
+ position: fixed;
1429
+ top: 12px;
1430
+ left: 12px;
1431
+ z-index: 999;
1432
+ padding: 8px;
1433
+ background: white;
1434
+ border-radius: 8px;
1435
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
1436
+ }
1437
+
1438
+ @media (max-width: 768px) {
1439
+ .mobile-menu-trigger {
1440
+ display: block;
1441
+ }
1442
+ }
1443
+ `;
1444
+
1445
+ const styleSheet = document.createElement('style');
1446
+ styleSheet.textContent = styles;
1447
+ document.head.appendChild(styleSheet);
1448
+
1449
+ document.addEventListener('DOMContentLoaded', () => {
1450
+ const chatHistoryManager = new ChatHistoryManager();
1451
+ });
1452
+ // 初始化聊天管理器
1453
+ document.addEventListener('DOMContentLoaded', () => {
1454
+ const chatManager = new ChatManager();
1455
+ });
1456
+
1457
+ </script>
1458
+ </body>
1459
+
1460
+ </html>