File size: 38,359 Bytes
6cbcde1
f02942c
c08e2dd
 
f02942c
 
ac5dc36
16cba3a
 
 
c858b85
a7d5e8a
c08e2dd
 
6cbcde1
c08e2dd
 
 
799f19e
c08e2dd
 
 
 
799f19e
 
c08e2dd
 
 
 
 
 
 
 
 
 
 
 
8f1694b
c08e2dd
 
 
8f1694b
c08e2dd
 
 
 
c6477c9
c08e2dd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5caa3d6
 
 
 
 
c08e2dd
 
 
 
 
 
 
 
 
 
16cba3a
bb229d8
16cba3a
 
 
a8e88a8
2cb5d00
5f92d1f
 
05c0451
 
 
f02942c
16cba3a
 
 
 
 
 
 
 
 
 
 
 
05c0451
16cba3a
 
 
 
 
 
 
 
 
f02942c
 
f292941
a8e88a8
 
5f92d1f
f292941
a7d5e8a
ac5dc36
5f92d1f
f292941
 
 
 
 
 
5f92d1f
a8e88a8
f292941
5f92d1f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
bb229d8
ac5dc36
bb229d8
 
f292941
ac5dc36
 
230ffde
 
5f9a8cf
 
a7d5e8a
5f9a8cf
 
 
a7d5e8a
 
 
1d16a46
a7d5e8a
 
 
5f9a8cf
 
 
 
1d16a46
 
 
c08e2dd
1d16a46
230ffde
 
 
a7d5e8a
 
 
 
5f9a8cf
230ffde
 
 
1d16a46
bb229d8
 
230ffde
bb229d8
a7d5e8a
bb229d8
16cba3a
c08e2dd
 
 
 
 
 
 
 
 
 
 
5f92d1f
c08e2dd
 
 
 
 
 
 
 
 
a7d5e8a
bb229d8
 
 
a7d5e8a
 
 
f02942c
a7d5e8a
 
 
bb229d8
a7d5e8a
 
 
1d16a46
 
 
 
 
 
 
16cba3a
a7d5e8a
 
 
16cba3a
 
a7d5e8a
 
 
 
 
5f92d1f
16cba3a
a7d5e8a
 
 
 
 
 
 
 
 
 
 
 
 
16cba3a
 
 
a8e88a8
 
bb229d8
 
 
16cba3a
 
 
bb229d8
16cba3a
a8e88a8
5f92d1f
eef30ab
f292941
eef30ab
5f92d1f
 
 
eef30ab
 
 
f292941
eef30ab
 
f292941
eef30ab
a7d5e8a
5f92d1f
eef30ab
 
5f92d1f
 
 
 
 
 
 
eef30ab
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5f92d1f
 
bb229d8
f292941
5caa3d6
 
f292941
5caa3d6
 
 
 
 
 
 
f292941
eef30ab
 
f292941
eef30ab
5caa3d6
 
 
 
f292941
 
 
 
 
5caa3d6
 
 
 
 
f292941
 
5caa3d6
 
 
 
 
 
 
 
 
f292941
 
 
 
 
5caa3d6
f292941
 
eef30ab
f292941
 
 
 
eef30ab
5caa3d6
 
 
 
 
 
 
 
 
 
eef30ab
f292941
5caa3d6
 
 
 
 
 
eef30ab
5caa3d6
 
 
 
 
 
 
 
 
 
 
 
 
 
c858b85
eef30ab
f292941
eef30ab
230ffde
 
 
a7d5e8a
eef30ab
f292941
eef30ab
 
f292941
eef30ab
f02942c
c858b85
ac5dc36
 
 
c858b85
ac5dc36
c858b85
ac5dc36
c858b85
 
 
 
a7d5e8a
 
 
bb229d8
f02942c
bb229d8
 
 
230ffde
 
ac5dc36
c858b85
ac5dc36
a7d5e8a
c858b85
 
bb229d8
 
a7d5e8a
 
1d16a46
 
bb229d8
 
f02942c
bb229d8
 
16cba3a
c08e2dd
a7d5e8a
 
 
 
 
c08e2dd
a7d5e8a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1d16a46
a7d5e8a
 
 
 
 
 
 
 
 
1d16a46
a7d5e8a
 
 
 
1d16a46
 
c08e2dd
 
a7d5e8a
 
 
c08e2dd
f292941
c08e2dd
 
8f1694b
c08e2dd
 
 
 
f292941
 
 
c08e2dd
 
 
 
 
 
 
 
 
 
46294ab
 
c08e2dd
 
 
 
 
 
 
 
 
 
a8e88a8
 
5caa3d6
 
c08e2dd
f292941
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a8e88a8
 
c08e2dd
a8e88a8
bb229d8
eef30ab
a8e88a8
c08e2dd
a8e88a8
 
 
 
 
 
5caa3d6
a8e88a8
 
 
 
5caa3d6
a8e88a8
 
 
 
 
5caa3d6
a8e88a8
 
 
 
 
 
 
 
 
 
 
 
f292941
eef30ab
a8e88a8
5caa3d6
a8e88a8
 
 
 
 
 
 
 
 
 
 
 
f292941
 
 
 
 
 
 
 
 
f02942c
f292941
c08e2dd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
bb229d8
c08e2dd
 
 
 
 
 
 
 
 
 
 
 
 
bb229d8
c08e2dd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f292941
 
c08e2dd
f292941
a8e88a8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f292941
 
 
 
a8e88a8
f292941
c08e2dd
 
 
bb229d8
c08e2dd
 
 
 
 
bb229d8
c08e2dd
 
 
46294ab
8cc0fed
c08e2dd
 
8cc0fed
c08e2dd
 
8cc0fed
c08e2dd
 
8cc0fed
c08e2dd
 
8cc0fed
c08e2dd
 
 
 
 
 
 
 
 
 
 
 
 
 
f292941
c08e2dd
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
import streamlit as st
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
import folium
from streamlit_folium import st_folium
from folium.plugins import HeatMap
import requests
from openai import OpenAI
import json
import ast
from io import BytesIO
import random
from streamlit_option_menu import option_menu

# ---------------------------------------------------------
# 1. 页面配置与 CSS 样式 (云墨·太白风格)
# ---------------------------------------------------------
st.set_page_config(
    page_title="云墨·太白 | 李白情感GIS与RAG系统",
    page_icon="🍶",
    layout="wide",
    initial_sidebar_state="expanded"
)

# 自定义 CSS 注入 - 水墨风格
def local_css():
    st.markdown("""
    <style>
    /* 全局字体设置:优先使用楷体/宋体 */
    html, body, [class*="css"] {
        font-family: "KaiTi", "STKaiti", "SimSun", "Times New Roman", serif;
    }
    
    /* 背景图片:水墨山水风格 */
    .stApp {
        background-image: linear-gradient(rgba(255,255,255,0.9), rgba(255,255,255,0.9)), 
                          url("https://raw.githubusercontent.com/seblee424/libai_emotin_data/main/background.jpg");
        background-size: cover;
        background-attachment: fixed;
        background-position: center;
        background-repeat: no-repeat;
    }

    /* 遮罩层:让文字在背景上更清晰 */
    .main .block-container {
        background-color: rgba(255, 255, 255, 0.01);
        padding: 2rem;
        border-radius: 15px;
        box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
        margin-top: 1rem;
        border: 1px solid #e0e0e0;
    }

    /* 标题样式 */
    h1, h2, h3 {
        color: #2c3e50;
        text-align: center;
        font-weight: bold;
        margin-bottom: 1rem;
    }
    
    h1 {
        text-shadow: 2px 2px 4px #cccccc;
        font-size: 3rem !important;
        background: linear-gradient(45deg, #2c3e50, #34495e);
        -webkit-background-clip: text;
        -webkit-text-fill-color: transparent;
    }

    /* 侧边栏美化 */
    section[data-testid="stSidebar"] {
        background-color: rgba(240, 242, 246, 0.95);
        border-right: 2px solid #b8c6db;
        backdrop-filter: blur(5px);
    }

    /* 按钮样式 */
    .stButton>button {
        background: linear-gradient(45deg, #2c3e50, #34495e);
        color: white;
        border-radius: 25px;
        border: none;
        padding: 12px 28px;
        transition: all 0.3s ease;
        font-weight: bold;
        box-shadow: 0 4px 8px rgba(0,0,0,0.1);
    }
    .stButton>button:hover {
        background: linear-gradient(45deg, #34495e, #2c3e50);
        transform: translateY(-2px);
        box-shadow: 0 6px 12px rgba(0,0,0,0.2);
    }
    
    /* 诗词卡片样式 */
    .poem-card {
        background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
        border-left: 5px solid #2c3e50;
        padding: 20px;
        margin: 15px 0;
        font-size: 1.2rem;
        font-style: italic;
        color: #444;
        border-radius: 0 10px 10px 0;
        box-shadow: 0 2px 8px rgba(0,0,0,0.1);
    }
    
    /* 数据框样式 */
    .stDataFrame {
        border-radius: 10px;
        overflow: hidden;
    }
    
    /* 选择框样式 */
    .stSelectbox, .stTextInput {
        border-radius: 8px;
    }
    
    /* 聊天消息样式 */
    .stChatMessage {
        border-radius: 15px;
        margin: 10px 0;
    }
    
    /* 进度条样式 */
    .stSlider > div > div > div {
        background: linear-gradient(45deg, #2c3e50, #34495e);
    }
    </style>
    """, unsafe_allow_html=True)

local_css()

# ---------------------------------------------------------
# 2. 数据准备与功能函数
# ---------------------------------------------------------

# 初始化OpenAI客户端
client = OpenAI(
    api_key="sk-72997944466a4af2bcd52a068895f8cf",
    base_url="https://api.deepseek.com"
)

# 全局变量与配置 - 更新数据源
AI_DATA_URL = "https://raw.githubusercontent.com/seblee424/libai_emotin_data/main/libai_location.xlsx"
EMOTION_DATA_URL = "https://raw.githubusercontent.com/seblee424/libai_emotin_data/main/%E6%9D%8E%E7%99%BD%E8%AF%97%E6%AD%8C%E6%95%B0%E6%8D%AE%E6%95%B4%E7%90%86%20%E5%B9%B4%E4%BB%BD%2B%E5%9C%B0%E7%82%B9%2B%E7%AE%80%E4%BD%93%2B%E7%BB%8F%E7%BA%AC%E5%BA%A6%2B%E6%83%85%E6%84%9F%EF%BC%88%E7%AE%80%E4%BD%93%2B%E7%B9%81%E4%BD%93%E6%A0%87%E9%A2%98%2B%E7%B9%81%E4%BD%93%E6%AD%A3%E6%96%87%EF%BC%89.xlsx"

location_col = '地点(古称/今称)'
summary_col = '诗作/事件摘要'

LOCATION_COORDS = {
    "碎叶城": {"lat": 42.8447, "lon": 75.1648, "match_keys": ["碎叶城"]},
    "峨眉山": {"lat": 29.5807, "lon": 103.3592, "match_keys": ["峨眉山"]},
    "蜀中": {"lat": 31.7828, "lon": 104.7570, "match_keys": ["蜀中", "江油"]},
    "荆门/南津关": {"lat": 30.5667, "lon": 111.4500, "match_keys": ["荆门", "南津关"]},
    "岳阳楼": {"lat": 29.3879, "lon": 113.1092, "match_keys": ["岳阳楼", "岳阳"]},
    "安陆": {"lat": 31.3653, "lon": 113.7077, "match_keys": ["安陆"]},
    "黄鹤楼": {"lat": 30.5484, "lon": 114.3168, "match_keys": ["黄鹤楼", "武汉"]},
    "金陵(凤凰台)": {"lat": 32.0415, "lon": 118.7781, "match_keys": ["金陵", "凤凰台", "南京"]},
    "庐山": {"lat": 29.5910, "lon": 115.9922, "match_keys": ["庐山", "九江"]},
    "天姥山": {"lat": 29.5000, "lon": 120.8900, "match_keys": ["天姥山"]},
    "金陵/长干里": {"lat": 32.0298, "lon": 118.7900, "match_keys": ["长干里"]},
    "长安": {"lat": 34.2652, "lon": 108.9500, "match_keys": ["长安", "西安"]},
    "长安/宫廷": {"lat": 34.2652, "lon": 108.9500, "match_keys": ["宫廷"]},
    "长安/洛阳": {"lat": 34.6859, "lon": 112.4600, "match_keys": ["洛阳"]},
    "桃花潭": {"lat": 30.4079, "lon": 118.4230, "match_keys": ["桃花潭", "泾县"]},
    "敬亭山": {"lat": 30.9822, "lon": 118.7844, "match_keys": ["敬亭山", "宣城"]},
    "天门山": {"lat": 31.4285, "lon": 118.3970, "match_keys": ["天门山", "芜湖"]},
    "扬州/旅店": {"lat": 32.3934, "lon": 119.4290, "match_keys": ["扬州"]},
    "夜郎": {"lat": 27.6888, "lon": 106.3773, "match_keys": ["夜郎", "桐梓"]},
    "白帝城": {"lat": 31.0450, "lon": 109.5780, "match_keys": ["白帝城", "奉节"]},
    "秋浦": {"lat": 30.6500, "lon": 117.4800, "match_keys": ["秋浦", "池州"]},
    "当涂": {"lat": 31.5453, "lon": 118.4870, "match_keys": ["当涂", "马鞍山"]},
}

# 数据加载函数 - 增强错误处理
@st.cache_data(ttl=3600, show_spinner="正在从 GitHub 下载AI对话数据...")
def load_ai_data():
    try:
        response = requests.get(AI_DATA_URL, timeout=30)
        response.raise_for_status()
        df = pd.read_excel(BytesIO(response.content), sheet_name=0)
        df.columns = df.columns.str.strip()
        
        # 检查必要列是否存在
        if location_col not in df.columns:
            st.error(f"❌ 数据中缺少必要的列: {location_col}")
            return pd.DataFrame()
            
    except Exception as e:
        st.error(f"❌ AI对话数据下载失败: {str(e)}")
        st.info("💡 提示: 请检查网络连接或数据文件是否可访问")
        return pd.DataFrame()
    
    df['coords_key'] = '未知'
    df['Latitude'] = 34.0478
    df['Longitude'] = 108.4357
    
    for idx, row in df.iterrows():
        location_str = str(row.get(location_col, '')).strip()
        for key, data in LOCATION_COORDS.items():
            if location_str == key or any(k in location_str for k in data.get('match_keys', [])):
                df.at[idx, 'coords_key'] = key
                df.at[idx, 'Latitude'] = data['lat']
                df.at[idx, 'Longitude'] = data['lon']
                break
    return df

@st.cache_data(ttl=3600, show_spinner="正在从 GitHub 下载情感数据...")
def load_emotion_data_from_github():
    try:
        response = requests.get(EMOTION_DATA_URL, timeout=30)
        response.raise_for_status()
        df = pd.read_excel(BytesIO(response.content))
        df.columns = df.columns.astype(str).str.strip()
        
        def get_first_matching_col(keywords):
            for col in df.columns:
                if any(k in col.lower() for k in keywords): return col
            return None

        rename_map = {}
        if c := get_first_matching_col(['经', 'lon', 'longitude']): rename_map[c] = 'Longitude'
        if c := get_first_matching_col(['纬', 'lat', 'latitude']): rename_map[c] = 'Latitude'
        if c := get_first_matching_col(['year', '年', 'time']): rename_map[c] = 'Year'
        if c := get_first_matching_col(['诗名', 'title', '题', '标题', 'name', '诗歌']): rename_map[c] = 'Title'
        if c := get_first_matching_col(['地点', 'location', 'place', 'city']): rename_map[c] = 'Location'

        emo_col = get_first_matching_col(['emotion_top3', 'top3']) or get_first_matching_col(['emotion', '情', 'sentiment'])
        if emo_col: rename_map[emo_col] = 'Emotion_Raw'

        df = df.rename(columns=rename_map)
        
        required_cols = ['Title', 'Location', 'Emotion', 'Year', 'Latitude', 'Longitude']
        for col in required_cols:
            if col not in df.columns:
                df[col] = '未知'

        def extract_primary_emotion(val):
            try:
                if isinstance(val, str):
                    parsed = ast.literal_eval(val)
                    if isinstance(parsed, list) and len(parsed) > 0: return parsed[0][0]
                elif isinstance(val, list) and len(val) > 0: return val[0][0]
            except: pass
            return str(val).split(' ')[0] if val else "未知"

        if 'Emotion_Raw' in df.columns:
            df['Emotion'] = df['Emotion_Raw'].apply(extract_primary_emotion)
        
        df = df.dropna(subset=['Latitude', 'Longitude'])
        return df

    except Exception as e:
        st.error(f"❌ 情感数据下载失败: {str(e)}")
        return pd.DataFrame()

# 模拟数据函数(用于保持原有UI)
@st.cache_data
def get_travel_data():
    data = {
        '地点': ['长安', '成都', '洛阳', '金陵 (南京)', '扬州', '庐山', '宣城'],
        'lat': [34.3416, 30.5728, 34.6197, 32.0603, 32.3945, 29.5643, 30.9407],
        'lon': [108.9398, 104.0668, 112.4540, 118.7969, 119.4122, 115.9881, 118.7587],
        '诗作数': [50, 20, 35, 45, 30, 15, 25],
        '代表作': ['长相思', '蜀道难', '春夜洛城闻笛', '登金陵凤凰台', '黄鹤楼送孟浩然之广陵', '望庐山瀑布', '独坐敬亭山']
    }
    return pd.DataFrame(data)

@st.cache_data
def get_emotion_data():
    return pd.DataFrame({
        '意象': ['月亮', '酒', '剑', '水', '山', '花', '孤', '梦'],
        '频率': [120, 95, 40, 85, 110, 60, 55, 30],
        '情感色彩': ['思乡/孤独', '豪迈/解忧', '侠客/抱负', '流逝/愁苦', '归隐/壮阔', '美好/易逝', '寂寞', '虚幻']
    })

# RAG Chatbot 函数
@st.cache_data(ttl=3600)
def get_cbdb_data(name="李白"):
    try:
        url = f"https://cbdb.fas.harvard.edu/cbdbapi/person.php?name={name}&o=json"
        r = requests.get(url, headers={"User-Agent": "Streamlit App"}, timeout=5)
        return r.json() if r.status_code == 200 else None
    except: return None

def generate_poem_analysis(year, location, emotion, title, cbdb_data):
    cbdb_text = json.dumps(cbdb_data, ensure_ascii=False)[:1000] if cbdb_data else "无"
    
    system_prompt = (
        "你是一位精通唐代文学与李白生平的专家AI。\n"
        f"参考史料:{cbdb_text}\n"
        "任务:用户将提供李白的一首诗及其背景(年份、地点、情感标签)。\n"
        "请按以下格式输出(使用Markdown):\n"
        "### 📜 全诗呈现\n"
        "(请默写全诗,若不确定则注明)\n\n"
        "### 🎭 情感深度解析\n"
        "(结合标签分析诗句如何体现该情感)\n\n"
        "### 🌍 时空与历史背景\n"
        f"(简述李白在{year}年于{location}的人生境遇)\n"
    )
    
    user_prompt = f"请分析李白在 {year} 年,于 {location} 创作的《{title}》。该诗被标记为【{emotion}】情感。"
    
    try:
        response = client.chat.completions.create(
            model="deepseek-chat",
            messages=[
                {"role": "system", "content": system_prompt},
                {"role": "user", "content": user_prompt}
            ],
            temperature=0.7
        )
        return response.choices[0].message.content.strip()
    except Exception as e:
        return f"AI 分析服务暂时不可用: {str(e)}"

def run_main_chatbot(cbdb_data, prompt):
    if not prompt: return "请输入有效的问题"
    cbdb_text = json.dumps(cbdb_data, ensure_ascii=False)[:3000] if cbdb_data else "无CBDB资料"
    system_prompt = f"你是李白研究专家。史料参考:{cbdb_text}"
    try:
        messages = [{"role": "system", "content": system_prompt}]
        messages.extend([msg for msg in st.session_state.chat_history[-5:] if msg.get("role") in ["user", "assistant"]])
        messages.append({"role": "user", "content": prompt})
        response = client.chat.completions.create(model="deepseek-chat", messages=messages, temperature=0.7)
        answer = response.choices[0].message.content.strip()
        
        highlight_key = None
        if not st.session_state.ai_data_df.empty:
            for key in st.session_state.ai_data_df['coords_key'].unique():
                if key != '未知' and key in answer:
                    highlight_key = key
                    break
        st.session_state.highlight_location_key = highlight_key
        return answer
    except Exception as e:
        return f"Chatbot错误:{str(e)}"

# 地图绘制函数 - 中文版 (用于AI对话页面)
def create_main_map(df, highlight_key):
    if df.empty: 
        return folium.Map(location=[34.0, 108.0], zoom_start=4)
    
    try:
        center_lat = df['Latitude'].mean()
        center_lon = df['Longitude'].mean()
    except: 
        center_lat, center_lon = 34.0, 108.0
    
    # 使用标准地图底图
    m = folium.Map(
        location=[center_lat, center_lon], 
        zoom_start=4.5
    )
    
    points = df[['Latitude', 'Longitude']].dropna().values.tolist()
    if len(points) > 1: 
        folium.PolyLine(points, color="#00AEEF", weight=3, opacity=0.5).add_to(m)

    for idx, row in df.iterrows():
        try:
            if pd.isna(row['Latitude']): continue
            is_highlighted = (row['coords_key'] == highlight_key)
            color = 'orange' if is_highlighted else 'blue'
            icon = 'fire' if is_highlighted else 'user'
            
            # 创建详细的信息弹窗
            popup_html = f"""
            <div style="width: 250px;">
                <h4 style="color: #2c3e50; margin-bottom: 10px;">{row.get(location_col, '未知地点')}</h4>
                <p><strong>📅 时间:</strong> {row.get('时间', '未知')}</p>
                <p><strong>📖 诗作/事件:</strong> {row.get(summary_col, '未知')}</p>
                <p><strong>📍 坐标:</strong> {row['Latitude']:.4f}, {row['Longitude']:.4f}</p>
            </div>
            """
            folium.Marker(
                [row['Latitude'], row['Longitude']], 
                popup=folium.Popup(popup_html, max_width=300), 
                icon=folium.Icon(color=color, icon=icon, prefix='fa'),
                tooltip=row.get(location_col, '未知地点')
            ).add_to(m)
        except: continue
    return m

# 新增:时序地图函数 - 改进版 (用于足迹漫游页面)
def create_temporal_map(df, selected_year):
    if df.empty: 
        return folium.Map(location=[34.0, 108.0], zoom_start=4)
    
    try:
        center_lat = df['Latitude'].mean()
        center_lon = df['Longitude'].mean()
    except: 
        center_lat, center_lon = 34.0, 108.0
        
    # 使用标准地图底图
    m = folium.Map(
        location=[center_lat, center_lon], 
        zoom_start=4.5
    )
    
    # 过滤出选定年份及之前的所有数据点
    filtered_df = df[df['Year'] <= selected_year]
    
    # 按地点和年份分组,显示同年同地的所有诗名
    grouped_data = filtered_df.groupby(['Latitude', 'Longitude', 'Location', 'Year']).agg({
        'Title': lambda x: list(x.unique())
    }).reset_index()
    
    # 添加轨迹线(按时间顺序)
    if len(filtered_df) > 1:
        points = filtered_df.sort_values('Year')[['Latitude', 'Longitude']].values.tolist()
        folium.PolyLine(points, color="#00AEEF", weight=3, opacity=0.5, popup=f"截至 {selected_year} 年的游历路线").add_to(m)
    
    # 为每个地点添加标记
    for idx, row in grouped_data.iterrows():
        try:
            if pd.isna(row['Latitude']): continue
            
            # 根据年份设置颜色渐变(越晚越红)
            year_norm = (row['Year'] - df['Year'].min()) / (df['Year'].max() - df['Year'].min()) if df['Year'].max() != df['Year'].min() else 0.5
            red = int(255 * year_norm)
            blue = int(255 * (1 - year_norm))
            color = f'#{red:02x}00{blue:02x}'
            
            # 显示同年同地的所有诗名
            poem_list = "<br>".join([f"• {poem}" for poem in row['Title'][:5]])  # 最多显示5首诗
            if len(row['Title']) > 5:
                poem_list += f"<br>• ...等 {len(row['Title'])} 首诗"
            
            popup_html = f"""
            <div style="width: 280px;">
                <h4 style="color: #2c3e50; margin-bottom: 10px;">{row.get('Location', '未知地点')}</h4>
                <p><strong>📅 年份:</strong> {int(row['Year'])}</p>
                <p><strong>📖 同年诗作:</strong></p>
                <div style="max-height: 150px; overflow-y: auto;">
                    {poem_list}
                </div>
            </div>
            """
            
            folium.CircleMarker(
                location=[row['Latitude'], row['Longitude']],
                radius=8,
                popup=folium.Popup(popup_html, max_width=300),
                color=color,
                fill=True,
                fillColor=color,
                fillOpacity=0.7,
                weight=2,
                tooltip=f"{row.get('Location', '未知地点')} ({int(row['Year'])})"
            ).add_to(m)
            
            # 添加文字标注
            folium.Marker(
                location=[row['Latitude'] + 0.1, row['Longitude'] + 0.1],
                icon=folium.DivIcon(
                    html=f'<div style="font-size: 12px; color: {color}; font-weight: bold;">{int(row["Year"])}</div>'
                )
            ).add_to(m)
            
        except Exception as e:
            continue
    
    # 添加当前年份的标题
    title_html = f'''
                 <h3 align="center" style="font-size:20px"><b>李白足迹时序图 (截至 {selected_year} 年)</b></h3>
                 '''
    m.get_root().html.add_child(folium.Element(title_html))
    
    return m

def create_emotion_heatmap(df, period_name):
    if df.empty: 
        return folium.Map(location=[34.0, 108.0], zoom_start=4)
    
    try:
        center_lat = df['Latitude'].mean()
        center_lon = df['Longitude'].mean()
    except: center_lat, center_lon = 34.0, 108.0
    
    # 使用标准地图底图
    m = folium.Map(
        location=[center_lat, center_lon], 
        zoom_start=5
    )
    
    heatmap_gradients = {
        "豪放与激昂": {0.2: 'orange', 0.6: 'red', 1.0: 'darkred'},
        "喜悦与欢快": {0.2: 'yellow', 0.6: 'orange', 1.0: '#d35400'},
        "哀怨与悲伤": {0.2: 'cyan', 0.6: 'blue', 1.0: 'navy'},
        "忧愁与苦闷": {0.2: 'lightblue', 0.6: 'royalblue', 1.0: '#1a5276'},
        "孤独与寂寞": {0.2: 'plum', 0.6: 'purple', 1.0: '#4a235a'},
        "思乡与怀古": {0.2: '#d7bde2', 0.6: '#8e44ad', 1.0: '#5b2c6f'},
        "友情与知己": {0.2: 'lightgreen', 0.6: 'green', 1.0: 'darkgreen'},
        "闲适与隐逸": {0.2: '#a3e4d7', 0.6: '#16a085', 1.0: '#0e6251'},
        "未知": {0.4: 'gray', 0.8: 'white', 1.0: 'white'}
    }
    marker_colors = {
        "豪放与激昂": "#e74c3c", "喜悦与欢快": "#e67e22", "哀怨与悲伤": "#3498db",
        "忧愁与苦闷": "#2980b9", "孤独与寂寞": "#9b59b6", "思乡与怀古": "#8e44ad",
        "友情与知己": "#2ecc71", "闲适与隐逸": "#1abc9c", "未知": "#95a5a6"
    }

    unique_emotions = df['Emotion'].fillna("未知").unique()

    for emotion in unique_emotions:
        fg = folium.FeatureGroup(name=str(emotion))
        subset = df[df['Emotion'] == emotion]
        if subset.empty: continue
        
        heat_data = [[row['Latitude'], row['Longitude'], 1] for _, row in subset.iterrows()]
        HeatMap(heat_data, radius=20, blur=15, min_opacity=0.4, gradient=heatmap_gradients.get(emotion, None), name=f"{emotion} (热力)").add_to(fg)
        
        marker_color = marker_colors.get(emotion, "#ecf0f1")
        for _, row in subset.iterrows():
            folium.CircleMarker(
                location=[row['Latitude'], row['Longitude']], radius=3, color=marker_color,
                fill=True, fill_color=marker_color, fill_opacity=0.8, weight=0,
                popup=folium.Popup(f"<b>{row.get('Title', '无题')}</b><br>{emotion}", max_width=200),
                tooltip=f"{row.get('Title', '无题')}"
            ).add_to(fg)
        fg.add_to(m)

    folium.LayerControl(collapsed=False).add_to(m)
    return m

# AI分析卡片组件
def render_ai_analysis_card(df, period_name):
    st.markdown("---")
    st.subheader("🤖 智能诗歌检索与情感解析")
    st.caption("选择下方的年份、地点与情感,AI 将为您深度解读李白的心境。")
    
    with st.container():
        col1, col2, col3, col4 = st.columns([1, 1, 1, 1])
        
        available_years = sorted(df['Year'].dropna().unique())
        with col1:
            selected_year = st.selectbox("1️⃣ 选择年份", available_years, key=f"year_{period_name}")
        
        year_subset = df[df['Year'] == selected_year]
        available_locs = sorted(year_subset['Location'].dropna().unique())
        with col2:
            selected_loc = st.selectbox("2️⃣ 选择地点", available_locs, key=f"loc_{period_name}")
            
        loc_subset = year_subset[year_subset['Location'] == selected_loc]
        available_emotions = sorted(loc_subset['Emotion'].dropna().unique())
        with col3:
            selected_emotion = st.selectbox("3️⃣ 选择情感", available_emotions, key=f"emo_{period_name}")
            
        final_subset = loc_subset[loc_subset['Emotion'] == selected_emotion]
        available_titles = sorted(final_subset['Title'].astype(str).unique().tolist())
        
        with col4:
            if not available_titles:
                st.warning("该组合下暂无数据")
                selected_title = None
            else:
                selected_title = st.selectbox("4️⃣ 选择诗歌", available_titles, key=f"title_{period_name}")
        
        if st.button("✨ 生成 AI 深度解析", key=f"btn_{period_name}", use_container_width=True):
            if selected_title and selected_title != 'nan' and selected_title != '未知':
                with st.spinner(f"DeepSeek 正在阅读《{selected_title}》并分析历史背景..."):
                    cbdb_data = get_cbdb_data("李白")
                    analysis = generate_poem_analysis(selected_year, selected_loc, selected_emotion, selected_title, cbdb_data)
                    
                    st.markdown("---")
                    st.success("✅ 分析完成")
                    with st.container():
                        st.markdown(analysis)
            else:
                st.error("请先选择一首有效的诗歌。")

# ---------------------------------------------------------
# 3. 侧边栏导航 (云墨风格) - 调整顺序,太白生平在首页
# ---------------------------------------------------------
with st.sidebar:
    st.image("https://raw.githubusercontent.com/seblee424/libai_emotin_data/main/libai.jpg", 
             width=150, caption="诗仙·李白")
    
    selected = option_menu(
        "导航", 
        ["太白生平", "AI对话", "情感图谱", "足迹漫游", "与仙对饮"],  # 太白生平在首页
        icons=['book', 'robot', 'bar-chart', 'map', 'chat-quote'],  # 对应调整图标顺序
        menu_icon="cast", default_index=0,  # 默认选择太白生平
        styles={
            "container": {"padding": "0!important", "background-color": "transparent"},
            "icon": {"color": "#2c3e50", "font-size": "18px"}, 
            "nav-link": {"font-size": "16px", "text-align": "left", "margin":"0px", "--hover-color": "#eee"},
            "nav-link-selected": {"background-color": "#2c3e50"},
        }
    )
    
    st.markdown("---")
    st.markdown("### 🎵 听琴")
    st.audio("https://raw.githubusercontent.com/seblee424/libai_emotin_data/main/chunjianghuayueye.mp3")
    st.caption("古琴曲:春江花月夜")

# ---------------------------------------------------------
# 4. 主页面逻辑
# ---------------------------------------------------------

# 初始化session state
if "chat_history" not in st.session_state: 
    st.session_state.chat_history = []
if "highlight_location_key" not in st.session_state: 
    st.session_state.highlight_location_key = None
if "ai_data_df" not in st.session_state: 
    st.session_state.ai_data_df = load_ai_data()
if "selected_year" not in st.session_state:
    st.session_state.selected_year = 701  # 李白出生年份

# --- 页面 1: 太白生平 (首页) ---
if selected == "太白生平":
    st.title("☁️ 谪仙人:李白")
    st.markdown("**\"绣口一吐,就半个盛唐。\"**")
    
    col1, col2 = st.columns([1, 2])
    
    with col1:
        st.info("字:太白")
        st.info("号:青莲居士")
        st.info("朝代:唐朝")
        st.info("评价:诗仙")
        
    with col2:
        st.markdown("""
        <div class="poem-card">
        君不见,黄河之水天上来,奔流到海不复回。<br>
        君不见,高堂明镜悲白发,朝如青丝暮成雪。<br>
        人生得意须尽欢,莫使金樽空对月。
        </div>
        """, unsafe_allow_html=True)
        st.write("李白(701年-762年),字太白,号青莲居士,又号\"谪仙人\"。他是唐代伟大的浪漫主义诗人,被后人誉为\"诗仙\"。其诗以七言古诗和绝句成就最高,风格豪迈奔放,清新飘逸,想象丰富,意境奇妙,语言奇采,浪漫主义色彩浓厚。")

# --- 页面 2: AI对话 (RAG Chatbot) ---
elif selected == "AI对话":
    st.title("🤖 AI太白对话")
    st.write("与基于李白知识库的AI智能助手对话,探索诗仙的内心世界。")
    
    cbdb_data = get_cbdb_data("李白")
    
    # 使用李白人生重要节点数据
    df_main = st.session_state.ai_data_df
    
    col1, col2 = st.columns([1, 1.5], gap="large")
    
    with col1:
        st.subheader("💬 与AI太白对话")
        if not cbdb_data: 
            st.warning("CBDB 连接失败,使用通用知识库。")
        
        # 显示聊天历史
        for msg in st.session_state.chat_history:
            with st.chat_message(msg["role"]):
                st.markdown(msg["content"])
        
        # 聊天输入
        if prompt := st.chat_input("请输入问题(例如:李白在安陆有哪些经历?)"):
            st.session_state.chat_history.append({"role": "user", "content": prompt})
            with st.chat_message("user"):
                st.markdown(prompt)
            
            with st.chat_message("assistant"):
                with st.spinner("AI正在思考..."):
                    answer = run_main_chatbot(cbdb_data, prompt)
                    st.markdown(answer)
                    st.session_state.chat_history.append({"role": "assistant", "content": answer})
                    if st.session_state.highlight_location_key: 
                        st.success(f"🗺️ 地图已高亮:{st.session_state.highlight_location_key}")
    
    with col2:
        st.subheader("🗺️ 实时足迹高亮")
        
        if not df_main.empty:
            # 创建地图
            current_map = create_main_map(df_main, st.session_state.highlight_location_key)
            st_folium(current_map, width=700, height=600)
            
            # 显示高亮节点信息
            if st.session_state.highlight_location_key:
                highlight_df = df_main[df_main['coords_key'] == st.session_state.highlight_location_key]
                if not highlight_df.empty:
                    with st.expander(f"📋 {st.session_state.highlight_location_key} 详情"):
                        for idx, row in highlight_df.iterrows():
                            st.markdown(f"""
                            **📍 {row.get(location_col, '未知地点')}**
                            - 📅 时间: {row.get('时间', '未知')}
                            - 📖 诗作/事件: {row.get(summary_col, '未知')}
                            """)
        else:
            st.error("❌ AI对话数据加载失败,无法显示地图")
            st.info("💡 可能的原因:")
            st.info("• 网络连接问题")
            st.info("• GitHub 数据文件暂时不可访问")
            st.info("• 数据文件格式发生变化")
            
            if st.button("🔄 重新加载数据"):
                st.session_state.ai_data_df = load_ai_data()
                st.rerun()

# --- 页面 3: 情感图谱 (增强版可视化) ---
elif selected == "情感图谱":
    st.title("📊 诗中的情感密码")
    
    # 时期选择
    period = st.radio("选择时期:", ["🌱 青年期 (<742年)", "🔥 中年期 (742-755年)", "🍂 晚年期 (>755年)"], horizontal=True)
    
    df_emotion_full = load_emotion_data_from_github()
    
    if not df_emotion_full.empty:
        df_emotion_full['Year'] = pd.to_numeric(df_emotion_full['Year'], errors='coerce')
        df_emotion_full = df_emotion_full.dropna(subset=['Year'])
        
        if "青年期" in period:
            filtered_df = df_emotion_full[df_emotion_full['Year'] < 742]
            period_key = "youth"
        elif "中年期" in period:
            filtered_df = df_emotion_full[(df_emotion_full['Year'] >= 742) & (df_emotion_full['Year'] <= 755)]
            period_key = "middle"
        else:
            filtered_df = df_emotion_full[df_emotion_full['Year'] > 755]
            period_key = "old"
        
        st.info(f"共检索到 {len(filtered_df)} 首相关诗作。")
        
        # 情感热力图
        emotion_map = create_emotion_heatmap(filtered_df, period_key)
        st_folium(emotion_map, width="100%", height=500)
        
        # 传统图表
        col1, col2 = st.columns(2)
        
        with col1:
            st.subheader("高频意象统计")
            df_simple_emotion = get_emotion_data()
            fig_pie = px.pie(
                df_simple_emotion, 
                values='频率', 
                names='意象', 
                title='李白最爱用的词',
                color_discrete_sequence=px.colors.sequential.Teal,
                hole=0.4
            )
            fig_pie.update_layout(paper_bgcolor='rgba(0,0,0,0)')
            st.plotly_chart(fig_pie, use_container_width=True)
            
        with col2:
            st.subheader("意象背后的情感")
            fig_bar = px.bar(
                df_simple_emotion, 
                x='意象', 
                y='频率', 
                color='频率', 
                text='情感色彩',
                title='意象与情感关联',
                color_continuous_scale='Blues'
            )
            fig_bar.update_traces(textposition='outside')
            fig_bar.update_layout(paper_bgcolor='rgba(0,0,0,0)', plot_bgcolor='rgba(0,0,0,0)')
            st.plotly_chart(fig_bar, use_container_width=True)
        
        # AI分析卡片
        render_ai_analysis_card(filtered_df, period_key)
        
        st.markdown("### 情感解读")
        st.markdown("""
        > **月亮** 是李白诗中最孤独的伴侣,出现了 120 次以上。它代表了乡愁与超越世俗的渴望。
        >
        > **酒** 则是他通向自由的钥匙,"百年三万六千日,一日须倾三百杯"。
        """)
    else:
        st.error("❌ 情感数据加载失败")

# --- 页面 4: 足迹漫游 (时序地图可视化) ---
elif selected == "足迹漫游":
    st.title("🗺️ 仗剑走天涯")
    st.write("李白一生足迹遍布半个中国,从西域碎叶城到长安,从黄河到长江。")
    
    # 使用情感数据用于时序地图
    df_emotion = load_emotion_data_from_github()
    
    if not df_emotion.empty:
        # 处理年份数据
        df_emotion['Year'] = pd.to_numeric(df_emotion['Year'], errors='coerce')
        df_emotion = df_emotion.dropna(subset=['Year'])
        
        # 双地图展示
        col1, col2 = st.columns(2)
        
        with col1:
            st.subheader("📊 诗作分布热力图")
            df_travel = get_travel_data()
            fig = px.scatter_geo(
                df_travel,
                lat='lat',
                lon='lon',
                size='诗作数',
                hover_name='地点',
                hover_data=['代表作'],
                scope='asia',
                center=dict(lat=33, lon=110),
                projection="natural earth",
                color='诗作数',
                color_continuous_scale='Tealgrn',
                template='plotly_white',
                title="李白游历热力图"
            )
            fig.update_layout(
                geo=dict(
                    showland=True, landcolor="rgb(240, 240, 240)",
                    showcountries=True, countrycolor="rgb(200, 200, 200)",
                    fitbounds="locations"
                ),
                margin={"r":0,"t":40,"l":0,"b":0},
                paper_bgcolor='rgba(0,0,0,0)',
                plot_bgcolor='rgba(0,0,0,0)'
            )
            st.plotly_chart(fig, use_container_width=True)
        
        with col2:
            st.subheader("🗺️ 时序GIS地图")
            
            # 年份选择滑块
            min_year = int(df_emotion['Year'].min())
            max_year = int(df_emotion['Year'].max())
            
            selected_year = st.slider(
                "选择年份进度",
                min_value=min_year,
                max_value=max_year,
                value=st.session_state.selected_year,
                key="year_slider_travel"
            )
            
            st.session_state.selected_year = selected_year
            
            # 显示当前年份的诗作数量
            current_count = len(df_emotion[df_emotion['Year'] <= selected_year])
            total_count = len(df_emotion)
            st.info(f"**{selected_year}年** - 已创作 {current_count} 首诗作 (总计 {total_count} 首)")
            
            # 创建时序地图
            temporal_map = create_temporal_map(df_emotion, selected_year)
            st_folium(temporal_map, width=600, height=500)
    
    # 数据详情 - 使用AI数据源
    with st.expander("📋 查看详细游历数据"):
        if not st.session_state.ai_data_df.empty:
            st.dataframe(st.session_state.ai_data_df, use_container_width=True)
        else:
            st.error("❌ 数据加载失败")

# --- 页面 5: 与仙对饮 (飞花令互动) ---
elif selected == "与仙对饮":
    st.title("🍶 飞花令·互动")
    st.write("告诉李白你现在的心情,他会回赠你一句诗。")
    
    mood = st.selectbox("你现在的心情如何?", ["豪情万丈", "思念故乡", "怀才不遇", "享受自然", "感叹时光"])
    
    if st.button("向太白敬酒", use_container_width=True):
        st.toast("举杯邀明月,对影成三人...", icon="🥂")
        
        st.markdown("---")
        st.markdown("### 李白的回应:")
        
        if mood == "豪情万丈":
            st.success("飞流直下三千尺,疑是银河落九天!")
            st.image("https://raw.githubusercontent.com/seblee424/libai_emotin_data/main/飞流直下.jpg", caption="豪情万丈")
        elif mood == "思念故乡":
            st.info("举头望明月,低头思故乡。")
            st.image("https://raw.githubusercontent.com/seblee424/libai_emotin_data/main/明月思想.jpg", caption="明月寄相思")
        elif mood == "怀才不遇":
            st.warning("天生我材必有用,千金散尽还复来。")
            st.image("https://raw.githubusercontent.com/seblee424/libai_emotin_data/main/怀才不遇.jpg", caption="怀才不遇")
        elif mood == "享受自然":
            st.success("两岸猿声啼不住,轻舟已过万重山。")
            st.image("https://raw.githubusercontent.com/seblee424/libai_emotin_data/main/轻舟已过.jpg", caption="轻舟万重山")
        elif mood == "感叹时光":
            st.error("弃我去者,昨日之日不可留;乱我心者,今日之日多烦忧。")
            st.image("https://raw.githubusercontent.com/seblee424/libai_emotin_data/main/感叹时光.jpg", caption = "感叹时光")
            
    st.markdown("---")
    st.caption("输入框:写下你想对李白说的话")
    user_input = st.text_area("", placeholder="太白兄,我想对你说...")
    if user_input:
        st.write(f"李白收到了你的信:*{user_input}*")

# ---------------------------------------------------------
# 页脚
# ---------------------------------------------------------
st.markdown("---")
st.markdown("""
<div style="text-align: center; color: grey;">
    Designed for Li Bai Emotion Data Project | Created with Streamlit <br>
    UI Design Style: Ink & Cloud (云墨) | 融合GIS与RAG技术
</div>
""", unsafe_allow_html=True)