malt666 commited on
Commit
54dd917
·
verified ·
1 Parent(s): f43d42e

Upload 5 files

Browse files
Files changed (2) hide show
  1. app.py +240 -99
  2. templates/login.html +462 -461
app.py CHANGED
@@ -43,10 +43,6 @@ USER_DATA = []
43
  CURRENT_USER = -1
44
  MODELS = set()
45
 
46
- # 添加记录上一个conversation_id的变量和删除标记
47
- LAST_CONVERSATION_IDS = [None] * 100 # 为每个用户记录上一个conversation_id
48
- DELETE_CHAT = True # 是否在对话结束后删除上一个对话
49
-
50
  TRACE_ID = "3042e28b3abf475d8d973c7e904935af"
51
  SENTRY_TRACE = f"{TRACE_ID}-80d9d2538b2682d0"
52
 
@@ -598,98 +594,47 @@ def delete_conversation(session, cookies, session_token, conversation_id, deploy
598
  return False
599
 
600
 
601
- def is_conversation_valid(session, cookies, session_token, conversation_id, model_map, model):
602
- """检查会话ID是否有效"""
603
- if not conversation_id:
604
- return False
 
 
 
 
 
 
 
 
 
 
 
 
605
 
606
- # 如果没有这些信息,无法验证
607
- if not (model in model_map and len(model_map[model]) >= 2):
608
- return False
609
-
610
  external_app_id = model_map[model][0]
611
 
612
- # 尝试发送一个空消息来测试会话ID是否有效
613
- headers = {
614
- "accept": "text/event-stream",
615
- "content-type": "text/plain;charset=UTF-8",
616
- "cookie": cookies,
617
- "user-agent": random.choice(USER_AGENTS)
618
- }
619
-
620
- if session_token:
621
- headers["session-token"] = session_token
622
-
623
- payload = {
624
- "requestId": str(uuid.uuid4()),
625
- "deploymentConversationId": conversation_id,
626
- "message": "", # 空消息
627
- "isDesktop": False,
628
- "externalApplicationId": external_app_id
629
- }
630
 
631
- try:
632
- response = session.post(
633
- CHAT_URL,
634
- headers=headers,
635
- data=json.dumps(payload),
636
- stream=False
637
- )
638
 
639
- # 即使返回错误,只要不是缺少ID的错误,也说明ID是有效的
640
- if response.status_code == 200:
641
- return True
 
642
 
643
- error_text = response.text
644
- if "Missing required parameter" in error_text:
645
- return False
646
-
647
- # 其他类型的错误,可能ID是有效的但有其他问题
648
- return True
649
- except:
650
- # 如果请求出错,无法确定,返回False让系统创建新ID
651
- return False
652
-
653
-
654
- def get_or_create_conversation(session, cookies, session_token, conversation_id, model_map, model, user_index):
655
- """获取有效的会话ID,如果无效则创建新会话"""
656
- # 修改为总是创建新的conversation_id
657
- print("将为每次对话创建新会话")
658
- need_create = True
659
-
660
- # 如果需要创建新会话
661
- if need_create:
662
- if model in model_map and len(model_map[model]) >= 2:
663
- external_app_id = model_map[model][0]
664
- # 创建会话时需要deployment_id,我们先使用一个固定值
665
- # 在实际应用中应从API响应中获取
666
- deployment_id = "14b2a314cc" # 这是从您提供的请求中获取的
667
-
668
- new_conversation_id = create_conversation(
669
- session, cookies, session_token,
670
- external_application_id=external_app_id,
671
- deployment_id=deployment_id
672
- )
673
-
674
- if new_conversation_id:
675
- # 获取当前用户的上一个conversation_id
676
- global USER_DATA, CURRENT_USER, LAST_CONVERSATION_IDS
677
- last_conversation_id = LAST_CONVERSATION_IDS[user_index]
678
-
679
- # 更新全局存储的会话ID
680
- session, cookies, session_token, _, model_map, _ = USER_DATA[CURRENT_USER]
681
- USER_DATA[CURRENT_USER] = (session, cookies, session_token, new_conversation_id, model_map, user_index)
682
-
683
- # 保存到配置文件
684
- update_conversation_id(user_index, new_conversation_id)
685
-
686
- # 保存新的会话ID为下次调用时的"上一个ID"
687
- LAST_CONVERSATION_IDS[user_index] = new_conversation_id
688
-
689
- return new_conversation_id
690
 
691
- # 如果无法创建,返回原始ID
692
- return conversation_id
693
 
694
 
695
  def generate_trace_id():
@@ -708,12 +653,16 @@ def send_message(message, model, think=False):
708
  (session, cookies, session_token, conversation_id, model_map, user_index) = get_user_data()
709
  print(f"使用用户配置: {user_index + 1}")
710
 
711
- # 获取并保存当前的conversation_id(可能是旧的,用于稍后删除)
712
- last_conversation_id = conversation_id
 
 
713
 
714
- # 确保有有效的会话ID
715
- conversation_id = get_or_create_conversation(session, cookies, session_token, conversation_id, model_map, model, user_index)
716
- print(f"会话ID: {conversation_id}")
 
 
717
 
718
  trace_id, sentry_trace = generate_trace_id()
719
 
@@ -744,6 +693,7 @@ def send_message(message, model, think=False):
744
  if session_token:
745
  headers["session-token"] = session_token
746
 
 
747
  payload = {
748
  "requestId": str(uuid.uuid4()),
749
  "deploymentConversationId": conversation_id,
@@ -757,6 +707,12 @@ def send_message(message, model, think=False):
757
  "externalApplicationId": model_map[model][0]
758
  }
759
 
 
 
 
 
 
 
760
  if think:
761
  payload["useThinking"] = think
762
 
@@ -844,6 +800,93 @@ def send_message(message, model, think=False):
844
  if hasattr(e.response, 'text'):
845
  error_details += f" - Response: {e.response.text[:200]}"
846
  print(f"发送消息失败: {error_details}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
847
  return jsonify({"error": f"Failed to send message: {error_details}"}), 500
848
 
849
 
@@ -856,12 +899,16 @@ def send_message_non_stream(message, model, think=False):
856
  (session, cookies, session_token, conversation_id, model_map, user_index) = get_user_data()
857
  print(f"使用用户配置: {user_index + 1}")
858
 
859
- # 获取并保存当前的conversation_id(可能是旧的,用于稍后删除)
860
- last_conversation_id = conversation_id
 
 
861
 
862
- # 确保有有效的会话ID
863
- conversation_id = get_or_create_conversation(session, cookies, session_token, conversation_id, model_map, model, user_index)
864
- print(f"会话ID: {conversation_id}")
 
 
865
 
866
  trace_id, sentry_trace = generate_trace_id()
867
 
@@ -891,6 +938,7 @@ def send_message_non_stream(message, model, think=False):
891
  if session_token:
892
  headers["session-token"] = session_token
893
 
 
894
  payload = {
895
  "requestId": str(uuid.uuid4()),
896
  "deploymentConversationId": conversation_id,
@@ -904,6 +952,12 @@ def send_message_non_stream(message, model, think=False):
904
  "externalApplicationId": model_map[model][0]
905
  }
906
 
 
 
 
 
 
 
907
  if think:
908
  payload["useThinking"] = think
909
 
@@ -1038,6 +1092,93 @@ def send_message_non_stream(message, model, think=False):
1038
  if hasattr(e.response, 'text'):
1039
  error_details += f" - Response: {e.response.text[:200]}"
1040
  print(f"发送消息失败: {error_details}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1041
  return jsonify({"error": f"Failed to send message: {error_details}"}), 500
1042
 
1043
 
 
43
  CURRENT_USER = -1
44
  MODELS = set()
45
 
 
 
 
 
46
  TRACE_ID = "3042e28b3abf475d8d973c7e904935af"
47
  SENTRY_TRACE = f"{TRACE_ID}-80d9d2538b2682d0"
48
 
 
594
  return False
595
 
596
 
597
+ def get_or_create_conversation(session, cookies, session_token, conversation_id, model_map, model, user_index):
598
+ """获取对话ID,如果不存在则创建;返回是否是使用现有会话"""
599
+ print(f"\n----- 获取会话ID (用户 {user_index+1}) -----")
600
+ # 如果有现有的会话ID,直接使用
601
+ if conversation_id:
602
+ print(f"使用现有会话ID: {conversation_id}")
603
+ return conversation_id, True
604
+
605
+ # 如果没有会话ID,创建新的
606
+ print("没有会话ID,创建新会话...")
607
+ deployment_id = "14b2a314cc"
608
+
609
+ # 确保模型信息存在
610
+ if model not in model_map or len(model_map[model]) < 2:
611
+ print(f"错误: 无法获取模型 {model} 的信息")
612
+ return None, False
613
 
 
 
 
 
614
  external_app_id = model_map[model][0]
615
 
616
+ # 创建新会话
617
+ new_conversation_id = create_conversation(
618
+ session, cookies, session_token,
619
+ external_application_id=external_app_id,
620
+ deployment_id=deployment_id
621
+ )
 
 
 
 
 
 
 
 
 
 
 
 
622
 
623
+ if new_conversation_id:
624
+ print(f"成功创建新会话ID: {new_conversation_id}")
 
 
 
 
 
625
 
626
+ # 更新全局存储的会话ID
627
+ global USER_DATA, CURRENT_USER
628
+ session, cookies, session_token, _, model_map, _ = USER_DATA[CURRENT_USER]
629
+ USER_DATA[CURRENT_USER] = (session, cookies, session_token, new_conversation_id, model_map, user_index)
630
 
631
+ # 保存到配置文件
632
+ update_conversation_id(user_index, new_conversation_id)
633
+
634
+ return new_conversation_id, False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
635
 
636
+ print("创建新会话失败")
637
+ return None, False
638
 
639
 
640
  def generate_trace_id():
 
653
  (session, cookies, session_token, conversation_id, model_map, user_index) = get_user_data()
654
  print(f"使用用户配置: {user_index + 1}")
655
 
656
+ # 获取会话ID,并判断是否使用现有会话
657
+ conversation_id, is_existing = get_or_create_conversation(
658
+ session, cookies, session_token, conversation_id, model_map, model, user_index
659
+ )
660
 
661
+ # 如果没有有效的会话ID,返回错误
662
+ if not conversation_id:
663
+ return jsonify({"error": "Failed to get a valid conversation ID"}), 500
664
+
665
+ print(f"会话ID: {conversation_id} (是否为现有会话: {is_existing})")
666
 
667
  trace_id, sentry_trace = generate_trace_id()
668
 
 
693
  if session_token:
694
  headers["session-token"] = session_token
695
 
696
+ # 构建基础请求
697
  payload = {
698
  "requestId": str(uuid.uuid4()),
699
  "deploymentConversationId": conversation_id,
 
707
  "externalApplicationId": model_map[model][0]
708
  }
709
 
710
+ # 如果是使用现有会话,添加regenerate和editPrompt参数
711
+ if is_existing:
712
+ payload["regenerate"] = True
713
+ payload["editPrompt"] = True
714
+ print("为现有会话添加 regenerate=True 和 editPrompt=True")
715
+
716
  if think:
717
  payload["useThinking"] = think
718
 
 
800
  if hasattr(e.response, 'text'):
801
  error_details += f" - Response: {e.response.text[:200]}"
802
  print(f"发送消息失败: {error_details}")
803
+
804
+ # 如果是使用现有会话失败,尝试创建新会话重试一次
805
+ if is_existing:
806
+ print("使用现有会话失败,尝试创建新会话...")
807
+ # 创建新会话
808
+ deployment_id = "14b2a314cc"
809
+ external_app_id = model_map[model][0] if model in model_map and len(model_map[model]) >= 2 else None
810
+
811
+ if external_app_id:
812
+ new_conversation_id = create_conversation(
813
+ session, cookies, session_token,
814
+ external_application_id=external_app_id,
815
+ deployment_id=deployment_id
816
+ )
817
+
818
+ if new_conversation_id:
819
+ print(f"成功创建新会话ID: {new_conversation_id},重试请求")
820
+ # 更新全局存储的会话ID
821
+ global USER_DATA, CURRENT_USER
822
+ session, cookies, session_token, _, model_map, _ = USER_DATA[CURRENT_USER]
823
+ USER_DATA[CURRENT_USER] = (session, cookies, session_token, new_conversation_id, model_map, user_index)
824
+
825
+ # 保存到配置文件
826
+ update_conversation_id(user_index, new_conversation_id)
827
+
828
+ # 修改payload使用新会话ID,并移除regenerate和editPrompt
829
+ payload["deploymentConversationId"] = new_conversation_id
830
+ if "regenerate" in payload:
831
+ del payload["regenerate"]
832
+ if "editPrompt" in payload:
833
+ del payload["editPrompt"]
834
+
835
+ try:
836
+ # 非流式重试逻辑与流式类似,但需要重新提取响应内容
837
+ response = session.post(
838
+ CHAT_URL,
839
+ headers=headers,
840
+ data=json.dumps(payload),
841
+ stream=True,
842
+ cookies=None
843
+ )
844
+
845
+ response.raise_for_status()
846
+ # 重用现有提取逻辑...
847
+ # 但这里代码重复太多,实际应该重构为共享函数
848
+ buffer = io.StringIO()
849
+
850
+ for line in response.iter_lines():
851
+ if line:
852
+ decoded_line = line.decode("utf-8")
853
+ segment = extract_segment(decoded_line)
854
+ if segment:
855
+ buffer.write(segment)
856
+
857
+ response_content = buffer.getvalue()
858
+
859
+ # 计算输出token并更新统计信息
860
+ completion_result, _ = num_tokens_from_string(response_content, model)
861
+
862
+ # 保存对话历史并获取计算点数
863
+ _, compute_points_used = save_conversation_history(session, cookies, session_token, new_conversation_id)
864
+
865
+ # 更新统计信息
866
+ update_model_stats(model, prompt_tokens, completion_result, calculation_method, compute_points_used)
867
+
868
+ return jsonify({
869
+ "id": f"chatcmpl-{str(uuid.uuid4())}",
870
+ "object": "chat.completion",
871
+ "created": int(time.time()),
872
+ "model": model,
873
+ "choices": [{
874
+ "index": 0,
875
+ "message": {
876
+ "role": "assistant",
877
+ "content": response_content
878
+ },
879
+ "finish_reason": "stop"
880
+ }],
881
+ "usage": {
882
+ "prompt_tokens": prompt_tokens,
883
+ "completion_tokens": completion_result,
884
+ "total_tokens": prompt_tokens + completion_result
885
+ }
886
+ })
887
+ except Exception as retry_e:
888
+ print(f"重试失败: {retry_e}")
889
+
890
  return jsonify({"error": f"Failed to send message: {error_details}"}), 500
891
 
892
 
 
899
  (session, cookies, session_token, conversation_id, model_map, user_index) = get_user_data()
900
  print(f"使用用户配置: {user_index + 1}")
901
 
902
+ # 获取会话ID,并判断是否使用现有会话
903
+ conversation_id, is_existing = get_or_create_conversation(
904
+ session, cookies, session_token, conversation_id, model_map, model, user_index
905
+ )
906
 
907
+ # 如果没有有效的会话ID,返回错误
908
+ if not conversation_id:
909
+ return jsonify({"error": "Failed to get a valid conversation ID"}), 500
910
+
911
+ print(f"会话ID: {conversation_id} (是否为现有会话: {is_existing})")
912
 
913
  trace_id, sentry_trace = generate_trace_id()
914
 
 
938
  if session_token:
939
  headers["session-token"] = session_token
940
 
941
+ # 构建基础请求
942
  payload = {
943
  "requestId": str(uuid.uuid4()),
944
  "deploymentConversationId": conversation_id,
 
952
  "externalApplicationId": model_map[model][0]
953
  }
954
 
955
+ # 如果是使用现有会话,添加regenerate和editPrompt参数
956
+ if is_existing:
957
+ payload["regenerate"] = True
958
+ payload["editPrompt"] = True
959
+ print("为现有会话添加 regenerate=True 和 editPrompt=True")
960
+
961
  if think:
962
  payload["useThinking"] = think
963
 
 
1092
  if hasattr(e.response, 'text'):
1093
  error_details += f" - Response: {e.response.text[:200]}"
1094
  print(f"发送消息失败: {error_details}")
1095
+
1096
+ # 如果是使用现有会话失败,尝试创建新会话重试一次
1097
+ if is_existing:
1098
+ print("使用现有会话失败,尝试创建新会话...")
1099
+ # 创建新会话
1100
+ deployment_id = "14b2a314cc"
1101
+ external_app_id = model_map[model][0] if model in model_map and len(model_map[model]) >= 2 else None
1102
+
1103
+ if external_app_id:
1104
+ new_conversation_id = create_conversation(
1105
+ session, cookies, session_token,
1106
+ external_application_id=external_app_id,
1107
+ deployment_id=deployment_id
1108
+ )
1109
+
1110
+ if new_conversation_id:
1111
+ print(f"成功创建新会话ID: {new_conversation_id},��试请求")
1112
+ # 更新全局存储的会话ID
1113
+ global USER_DATA, CURRENT_USER
1114
+ session, cookies, session_token, _, model_map, _ = USER_DATA[CURRENT_USER]
1115
+ USER_DATA[CURRENT_USER] = (session, cookies, session_token, new_conversation_id, model_map, user_index)
1116
+
1117
+ # 保存到配置文件
1118
+ update_conversation_id(user_index, new_conversation_id)
1119
+
1120
+ # 修改payload使用新会话ID,并移除regenerate和editPrompt
1121
+ payload["deploymentConversationId"] = new_conversation_id
1122
+ if "regenerate" in payload:
1123
+ del payload["regenerate"]
1124
+ if "editPrompt" in payload:
1125
+ del payload["editPrompt"]
1126
+
1127
+ try:
1128
+ # 非流式重试逻辑与流式类似,但需要重新提取响应内容
1129
+ response = session.post(
1130
+ CHAT_URL,
1131
+ headers=headers,
1132
+ data=json.dumps(payload),
1133
+ stream=True,
1134
+ cookies=None
1135
+ )
1136
+
1137
+ response.raise_for_status()
1138
+ # 重用现有提取逻辑...
1139
+ # 但这里代码重复太多,实际应该重构为共享函数
1140
+ buffer = io.StringIO()
1141
+
1142
+ for line in response.iter_lines():
1143
+ if line:
1144
+ decoded_line = line.decode("utf-8")
1145
+ segment = extract_segment(decoded_line)
1146
+ if segment:
1147
+ buffer.write(segment)
1148
+
1149
+ response_content = buffer.getvalue()
1150
+
1151
+ # 计算输出token并更新统计信息
1152
+ completion_result, _ = num_tokens_from_string(response_content, model)
1153
+
1154
+ # 保存对话历史并获取计算点数
1155
+ _, compute_points_used = save_conversation_history(session, cookies, session_token, new_conversation_id)
1156
+
1157
+ # 更新统计信息
1158
+ update_model_stats(model, prompt_tokens, completion_result, calculation_method, compute_points_used)
1159
+
1160
+ return jsonify({
1161
+ "id": f"chatcmpl-{str(uuid.uuid4())}",
1162
+ "object": "chat.completion",
1163
+ "created": int(time.time()),
1164
+ "model": model,
1165
+ "choices": [{
1166
+ "index": 0,
1167
+ "message": {
1168
+ "role": "assistant",
1169
+ "content": response_content
1170
+ },
1171
+ "finish_reason": "stop"
1172
+ }],
1173
+ "usage": {
1174
+ "prompt_tokens": prompt_tokens,
1175
+ "completion_tokens": completion_result,
1176
+ "total_tokens": prompt_tokens + completion_result
1177
+ }
1178
+ })
1179
+ except Exception as retry_e:
1180
+ print(f"重试失败: {retry_e}")
1181
+
1182
  return jsonify({"error": f"Failed to send message: {error_details}"}), 500
1183
 
1184
 
templates/login.html CHANGED
@@ -1,462 +1,463 @@
1
- <!DOCTYPE html>
2
- <html lang="zh-CN">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Abacus Chat Proxy - 登录</title>
7
- <style>
8
- :root {
9
- --primary-color: #6f42c1;
10
- --secondary-color: #4a32a8;
11
- --bg-color: #0a0a1a;
12
- --text-color: #e6e6ff;
13
- --card-bg: rgba(30, 30, 60, 0.7);
14
- --input-bg: rgba(40, 40, 80, 0.6);
15
- --success-color: #36d399;
16
- --error-color: #f87272;
17
- }
18
-
19
- * {
20
- margin: 0;
21
- padding: 0;
22
- box-sizing: border-box;
23
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
24
- }
25
-
26
- body {
27
- min-height: 100vh;
28
- display: flex;
29
- align-items: center;
30
- justify-content: center;
31
- background-color: var(--bg-color);
32
- background-image:
33
- radial-gradient(circle at 20% 35%, rgba(111, 66, 193, 0.15) 0%, transparent 40%),
34
- radial-gradient(circle at 80% 10%, rgba(70, 111, 171, 0.1) 0%, transparent 40%);
35
- color: var(--text-color);
36
- position: relative;
37
- overflow: hidden;
38
- }
39
-
40
- /* 科幻蜘蛛网动画 */
41
- .web-container {
42
- position: absolute;
43
- top: 0;
44
- left: 0;
45
- width: 100%;
46
- height: 100%;
47
- z-index: -2;
48
- opacity: 0.6;
49
- }
50
-
51
- .web {
52
- width: 100%;
53
- height: 100%;
54
- }
55
-
56
- /* 动态背景网格 */
57
- .grid-background {
58
- position: absolute;
59
- top: 0;
60
- left: 0;
61
- width: 100%;
62
- height: 100%;
63
- background-image: linear-gradient(rgba(50, 50, 100, 0.05) 1px, transparent 1px),
64
- linear-gradient(90deg, rgba(50, 50, 100, 0.05) 1px, transparent 1px);
65
- background-size: 30px 30px;
66
- z-index: -1;
67
- animation: grid-move 20s linear infinite;
68
- }
69
-
70
- @keyframes grid-move {
71
- 0% {
72
- transform: translateY(0);
73
- }
74
- 100% {
75
- transform: translateY(30px);
76
- }
77
- }
78
-
79
- /* 浮动粒子效果 */
80
- .particles {
81
- position: absolute;
82
- top: 0;
83
- left: 0;
84
- width: 100%;
85
- height: 100%;
86
- overflow: hidden;
87
- z-index: -1;
88
- }
89
-
90
- .particle {
91
- position: absolute;
92
- display: block;
93
- pointer-events: none;
94
- width: 6px;
95
- height: 6px;
96
- background-color: rgba(111, 66, 193, 0.2);
97
- border-radius: 50%;
98
- animation: float 20s infinite ease-in-out;
99
- }
100
-
101
- @keyframes float {
102
- 0%, 100% {
103
- transform: translateY(0) translateX(0);
104
- opacity: 0;
105
- }
106
- 50% {
107
- opacity: 0.5;
108
- }
109
- 25%, 75% {
110
- transform: translateY(-100px) translateX(50px);
111
- }
112
- }
113
-
114
- .login-card {
115
- width: 420px;
116
- padding: 2.5rem;
117
- border-radius: 16px;
118
- background: var(--card-bg);
119
- box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
120
- backdrop-filter: blur(8px);
121
- border: 1px solid rgba(255, 255, 255, 0.1);
122
- z-index: 10;
123
- animation: card-fade-in 0.6s ease-out;
124
- }
125
-
126
- @keyframes card-fade-in {
127
- from {
128
- opacity: 0;
129
- transform: translateY(20px);
130
- }
131
- to {
132
- opacity: 1;
133
- transform: translateY(0);
134
- }
135
- }
136
-
137
- .login-header {
138
- text-align: center;
139
- margin-bottom: 2rem;
140
- }
141
-
142
- .login-header h1 {
143
- font-size: 2rem;
144
- font-weight: 600;
145
- margin-bottom: 0.5rem;
146
- background: linear-gradient(45deg, #6f42c1, #5181f1);
147
- -webkit-background-clip: text;
148
- -webkit-text-fill-color: transparent;
149
- letter-spacing: 0.5px;
150
- }
151
-
152
- .login-header p {
153
- color: rgba(230, 230, 255, 0.7);
154
- font-size: 0.95rem;
155
- }
156
-
157
- .space-info {
158
- text-align: center;
159
- background: rgba(50, 50, 150, 0.2);
160
- padding: 0.75rem;
161
- border-radius: 8px;
162
- margin-bottom: 1.5rem;
163
- font-size: 0.9rem;
164
- border: 1px solid rgba(111, 66, 193, 0.3);
165
- }
166
-
167
- .space-info a {
168
- color: var(--primary-color);
169
- text-decoration: none;
170
- font-weight: bold;
171
- transition: all 0.2s;
172
- }
173
-
174
- .space-info a:hover {
175
- text-decoration: underline;
176
- color: var(--secondary-color);
177
- }
178
-
179
- .login-form {
180
- display: flex;
181
- flex-direction: column;
182
- }
183
-
184
- .form-group {
185
- margin-bottom: 1.5rem;
186
- position: relative;
187
- }
188
-
189
- .form-group label {
190
- display: block;
191
- margin-bottom: 0.5rem;
192
- font-size: 0.9rem;
193
- font-weight: 500;
194
- color: rgba(230, 230, 255, 0.9);
195
- }
196
-
197
- .form-control {
198
- width: 100%;
199
- padding: 0.75rem 1rem;
200
- font-size: 1rem;
201
- line-height: 1.5;
202
- color: var(--text-color);
203
- background-color: var(--input-bg);
204
- border: 1px solid rgba(255, 255, 255, 0.1);
205
- border-radius: 8px;
206
- transition: all 0.2s ease;
207
- outline: none;
208
- }
209
-
210
- .form-control:focus {
211
- border-color: var(--primary-color);
212
- box-shadow: 0 0 0 3px rgba(111, 66, 193, 0.2);
213
- }
214
-
215
- .btn {
216
- display: inline-block;
217
- font-weight: 500;
218
- text-align: center;
219
- vertical-align: middle;
220
- cursor: pointer;
221
- padding: 0.75rem 1rem;
222
- font-size: 1rem;
223
- line-height: 1.5;
224
- border-radius: 8px;
225
- transition: all 0.15s ease-in-out;
226
- border: none;
227
- }
228
-
229
- .btn-primary {
230
- color: #fff;
231
- background: linear-gradient(45deg, var(--primary-color), var(--secondary-color));
232
- box-shadow: 0 4px 10px rgba(111, 66, 193, 0.3);
233
- position: relative;
234
- overflow: hidden;
235
- }
236
-
237
- .btn-primary:hover {
238
- transform: translateY(-2px);
239
- box-shadow: 0 6px 15px rgba(111, 66, 193, 0.4);
240
- }
241
-
242
- .btn-primary:active {
243
- transform: translateY(0);
244
- }
245
-
246
- /* 添加光效效果 */
247
- .btn-primary::before {
248
- content: '';
249
- position: absolute;
250
- top: -50%;
251
- left: -50%;
252
- width: 200%;
253
- height: 200%;
254
- background: linear-gradient(
255
- to bottom right,
256
- rgba(255, 255, 255, 0) 0%,
257
- rgba(255, 255, 255, 0.1) 50%,
258
- rgba(255, 255, 255, 0) 100%
259
- );
260
- transform: rotate(45deg);
261
- animation: btn-shine 3s infinite;
262
- }
263
-
264
- @keyframes btn-shine {
265
- 0% {
266
- left: -50%;
267
- }
268
- 100% {
269
- left: 150%;
270
- }
271
- }
272
-
273
- .error-message {
274
- background-color: rgba(248, 114, 114, 0.2);
275
- color: var(--error-color);
276
- padding: 0.75rem;
277
- border-radius: 6px;
278
- margin-bottom: 1.5rem;
279
- font-size: 0.9rem;
280
- border-left: 3px solid var(--error-color);
281
- display: {{ 'block' if error else 'none' }};
282
- }
283
-
284
- .logo {
285
- margin-bottom: 1rem;
286
- font-size: 3rem;
287
- animation: glow 2s infinite alternate;
288
- }
289
-
290
- @keyframes glow {
291
- from {
292
- text-shadow: 0 0 5px rgba(111, 66, 193, 0.5), 0 0 10px rgba(111, 66, 193, 0.5);
293
- }
294
- to {
295
- text-shadow: 0 0 10px rgba(111, 66, 193, 0.8), 0 0 20px rgba(111, 66, 193, 0.8);
296
- }
297
- }
298
- </style>
299
- </head>
300
- <body>
301
- <div class="grid-background"></div>
302
- <div class="particles">
303
- <!-- 粒子元素会由JS生成 -->
304
- </div>
305
- <div class="web-container">
306
- <canvas class="web" id="webCanvas"></canvas>
307
- </div>
308
-
309
- <div class="login-card">
310
- <div class="login-header">
311
- <div class="logo">🤖</div>
312
- <h1>Abacus Chat Proxy</h1>
313
- <p>请输入访问密码</p>
314
- </div>
315
-
316
- {% if space_url %}
317
- <div class="space-info">
318
- api接口为{{ space_url }}/v1,请点击 <a href="{{ space_url }}" target="_blank">{{ space_url }}</a> 来登录并查看使用情况,
319
- </div>
320
- {% endif %}
321
-
322
- <div class="error-message" id="error-message">
323
- {{ error }}
324
- </div>
325
-
326
- <form class="login-form" method="post" action="/login">
327
- <div class="form-group">
328
- <label for="password">密码</label>
329
- <input type="password" class="form-control" id="password" name="password" placeholder="请输入访问密码" required>
330
- </div>
331
-
332
- <button type="submit" class="btn btn-primary">登录</button>
333
- </form>
334
- </div>
335
-
336
- <script>
337
- // 创建浮动粒子
338
- function createParticles() {
339
- const particlesContainer = document.querySelector('.particles');
340
- const particleCount = 20;
341
-
342
- for (let i = 0; i < particleCount; i++) {
343
- const particle = document.createElement('div');
344
- particle.className = 'particle';
345
-
346
- // 随机位置和大小
347
- const size = Math.random() * 5 + 2;
348
- const x = Math.random() * 100;
349
- const y = Math.random() * 100;
350
-
351
- particle.style.width = `${size}px`;
352
- particle.style.height = `${size}px`;
353
- particle.style.left = `${x}%`;
354
- particle.style.top = `${y}%`;
355
-
356
- // 随机动画延迟
357
- particle.style.animationDelay = `${Math.random() * 10}s`;
358
- particle.style.animationDuration = `${Math.random() * 10 + 10}s`;
359
-
360
- // 随机透明度
361
- particle.style.opacity = Math.random() * 0.5;
362
-
363
- particlesContainer.appendChild(particle);
364
- }
365
- }
366
-
367
- // 科幻蜘蛛网效果
368
- function initWebCanvas() {
369
- const canvas = document.getElementById('webCanvas');
370
- const ctx = canvas.getContext('2d');
371
- let width = window.innerWidth;
372
- let height = window.innerHeight;
373
-
374
- // 设置canvas尺寸
375
- canvas.width = width;
376
- canvas.height = height;
377
-
378
- // 节点类
379
- class Node {
380
- constructor(x, y) {
381
- this.x = x;
382
- this.y = y;
383
- this.vx = (Math.random() - 0.5) * 0.5;
384
- this.vy = (Math.random() - 0.5) * 0.5;
385
- this.radius = Math.random() * 2 + 1;
386
- }
387
-
388
- update() {
389
- if (this.x < 0 || this.x > width) this.vx = -this.vx;
390
- if (this.y < 0 || this.y > height) this.vy = -this.vy;
391
-
392
- this.x += this.vx;
393
- this.y += this.vy;
394
- }
395
-
396
- draw() {
397
- ctx.beginPath();
398
- ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
399
- ctx.fillStyle = 'rgba(111, 66, 193, 0.4)';
400
- ctx.fill();
401
- }
402
- }
403
-
404
- // 创建节点
405
- const nodeCount = Math.floor(width * height / 15000);
406
- const nodes = [];
407
-
408
- for (let i = 0; i < nodeCount; i++) {
409
- nodes.push(new Node(Math.random() * width, Math.random() * height));
410
- }
411
-
412
- // 绘制线条
413
- function drawWeb() {
414
- ctx.clearRect(0, 0, width, height);
415
-
416
- // 更新节点
417
- nodes.forEach(node => {
418
- node.update();
419
- node.draw();
420
- });
421
-
422
- // 绘制连线
423
- for (let i = 0; i < nodes.length; i++) {
424
- for (let j = i + 1; j < nodes.length; j++) {
425
- const dx = nodes[i].x - nodes[j].x;
426
- const dy = nodes[i].y - nodes[j].y;
427
- const distance = Math.sqrt(dx * dx + dy * dy);
428
-
429
- if (distance < 150) {
430
- ctx.beginPath();
431
- ctx.moveTo(nodes[i].x, nodes[i].y);
432
- ctx.lineTo(nodes[j].x, nodes[j].y);
433
- ctx.strokeStyle = `rgba(111, 66, 193, ${0.2 * (1 - distance / 150)})`;
434
- ctx.lineWidth = 0.5;
435
- ctx.stroke();
436
- }
437
- }
438
- }
439
-
440
- requestAnimationFrame(drawWeb);
441
- }
442
-
443
- // 监听窗口大小变化
444
- window.addEventListener('resize', () => {
445
- width = window.innerWidth;
446
- height = window.innerHeight;
447
- canvas.width = width;
448
- canvas.height = height;
449
- });
450
-
451
- // 开始动画
452
- drawWeb();
453
- }
454
-
455
- // 页面加载时初始化效果
456
- window.addEventListener('load', () => {
457
- createParticles();
458
- initWebCanvas();
459
- });
460
- </script>
461
- </body>
 
462
  </html>
 
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Abacus Chat Proxy - 登录</title>
7
+ <style>
8
+ :root {
9
+ --primary-color: #6f42c1;
10
+ --secondary-color: #4a32a8;
11
+ --bg-color: #0a0a1a;
12
+ --text-color: #e6e6ff;
13
+ --card-bg: rgba(30, 30, 60, 0.7);
14
+ --input-bg: rgba(40, 40, 80, 0.6);
15
+ --success-color: #36d399;
16
+ --error-color: #f87272;
17
+ }
18
+
19
+ * {
20
+ margin: 0;
21
+ padding: 0;
22
+ box-sizing: border-box;
23
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
24
+ }
25
+
26
+ body {
27
+ min-height: 100vh;
28
+ display: flex;
29
+ align-items: center;
30
+ justify-content: center;
31
+ background-color: var(--bg-color);
32
+ background-image:
33
+ radial-gradient(circle at 20% 35%, rgba(111, 66, 193, 0.15) 0%, transparent 40%),
34
+ radial-gradient(circle at 80% 10%, rgba(70, 111, 171, 0.1) 0%, transparent 40%);
35
+ color: var(--text-color);
36
+ position: relative;
37
+ overflow: hidden;
38
+ }
39
+
40
+ /* 科幻蜘蛛网动画 */
41
+ .web-container {
42
+ position: absolute;
43
+ top: 0;
44
+ left: 0;
45
+ width: 100%;
46
+ height: 100%;
47
+ z-index: -2;
48
+ opacity: 0.6;
49
+ }
50
+
51
+ .web {
52
+ width: 100%;
53
+ height: 100%;
54
+ }
55
+
56
+ /* 动态背景网格 */
57
+ .grid-background {
58
+ position: absolute;
59
+ top: 0;
60
+ left: 0;
61
+ width: 100%;
62
+ height: 100%;
63
+ background-image: linear-gradient(rgba(50, 50, 100, 0.05) 1px, transparent 1px),
64
+ linear-gradient(90deg, rgba(50, 50, 100, 0.05) 1px, transparent 1px);
65
+ background-size: 30px 30px;
66
+ z-index: -1;
67
+ animation: grid-move 20s linear infinite;
68
+ }
69
+
70
+ @keyframes grid-move {
71
+ 0% {
72
+ transform: translateY(0);
73
+ }
74
+ 100% {
75
+ transform: translateY(30px);
76
+ }
77
+ }
78
+
79
+ /* 浮动粒子效果 */
80
+ .particles {
81
+ position: absolute;
82
+ top: 0;
83
+ left: 0;
84
+ width: 100%;
85
+ height: 100%;
86
+ overflow: hidden;
87
+ z-index: -1;
88
+ }
89
+
90
+ .particle {
91
+ position: absolute;
92
+ display: block;
93
+ pointer-events: none;
94
+ width: 6px;
95
+ height: 6px;
96
+ background-color: rgba(111, 66, 193, 0.2);
97
+ border-radius: 50%;
98
+ animation: float 20s infinite ease-in-out;
99
+ }
100
+
101
+ @keyframes float {
102
+ 0%, 100% {
103
+ transform: translateY(0) translateX(0);
104
+ opacity: 0;
105
+ }
106
+ 50% {
107
+ opacity: 0.5;
108
+ }
109
+ 25%, 75% {
110
+ transform: translateY(-100px) translateX(50px);
111
+ }
112
+ }
113
+
114
+ .login-card {
115
+ width: 420px;
116
+ padding: 2.5rem;
117
+ border-radius: 16px;
118
+ background: var(--card-bg);
119
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
120
+ backdrop-filter: blur(8px);
121
+ border: 1px solid rgba(255, 255, 255, 0.1);
122
+ z-index: 10;
123
+ animation: card-fade-in 0.6s ease-out;
124
+ }
125
+
126
+ @keyframes card-fade-in {
127
+ from {
128
+ opacity: 0;
129
+ transform: translateY(20px);
130
+ }
131
+ to {
132
+ opacity: 1;
133
+ transform: translateY(0);
134
+ }
135
+ }
136
+
137
+ .login-header {
138
+ text-align: center;
139
+ margin-bottom: 2rem;
140
+ }
141
+
142
+ .login-header h1 {
143
+ font-size: 2rem;
144
+ font-weight: 600;
145
+ margin-bottom: 0.5rem;
146
+ background: linear-gradient(45deg, #6f42c1, #5181f1);
147
+ -webkit-background-clip: text;
148
+ -webkit-text-fill-color: transparent;
149
+ letter-spacing: 0.5px;
150
+ }
151
+
152
+ .login-header p {
153
+ color: rgba(230, 230, 255, 0.7);
154
+ font-size: 0.95rem;
155
+ }
156
+
157
+ .space-info {
158
+ text-align: center;
159
+ background: rgba(50, 50, 150, 0.2);
160
+ padding: 0.75rem;
161
+ border-radius: 8px;
162
+ margin-bottom: 1.5rem;
163
+ font-size: 0.9rem;
164
+ border: 1px solid rgba(111, 66, 193, 0.3);
165
+ }
166
+
167
+ .space-info a {
168
+ color: var(--primary-color);
169
+ text-decoration: none;
170
+ font-weight: bold;
171
+ transition: all 0.2s;
172
+ }
173
+
174
+ .space-info a:hover {
175
+ text-decoration: underline;
176
+ color: var(--secondary-color);
177
+ }
178
+
179
+ .login-form {
180
+ display: flex;
181
+ flex-direction: column;
182
+ }
183
+
184
+ .form-group {
185
+ margin-bottom: 1.5rem;
186
+ position: relative;
187
+ }
188
+
189
+ .form-group label {
190
+ display: block;
191
+ margin-bottom: 0.5rem;
192
+ font-size: 0.9rem;
193
+ font-weight: 500;
194
+ color: rgba(230, 230, 255, 0.9);
195
+ }
196
+
197
+ .form-control {
198
+ width: 100%;
199
+ padding: 0.75rem 1rem;
200
+ font-size: 1rem;
201
+ line-height: 1.5;
202
+ color: var(--text-color);
203
+ background-color: var(--input-bg);
204
+ border: 1px solid rgba(255, 255, 255, 0.1);
205
+ border-radius: 8px;
206
+ transition: all 0.2s ease;
207
+ outline: none;
208
+ }
209
+
210
+ .form-control:focus {
211
+ border-color: var(--primary-color);
212
+ box-shadow: 0 0 0 3px rgba(111, 66, 193, 0.2);
213
+ }
214
+
215
+ .btn {
216
+ display: inline-block;
217
+ font-weight: 500;
218
+ text-align: center;
219
+ vertical-align: middle;
220
+ cursor: pointer;
221
+ padding: 0.75rem 1rem;
222
+ font-size: 1rem;
223
+ line-height: 1.5;
224
+ border-radius: 8px;
225
+ transition: all 0.15s ease-in-out;
226
+ border: none;
227
+ }
228
+
229
+ .btn-primary {
230
+ color: #fff;
231
+ background: linear-gradient(45deg, var(--primary-color), var(--secondary-color));
232
+ box-shadow: 0 4px 10px rgba(111, 66, 193, 0.3);
233
+ position: relative;
234
+ overflow: hidden;
235
+ }
236
+
237
+ .btn-primary:hover {
238
+ transform: translateY(-2px);
239
+ box-shadow: 0 6px 15px rgba(111, 66, 193, 0.4);
240
+ }
241
+
242
+ .btn-primary:active {
243
+ transform: translateY(0);
244
+ }
245
+
246
+ /* 添加光效效果 */
247
+ .btn-primary::before {
248
+ content: '';
249
+ position: absolute;
250
+ top: -50%;
251
+ left: -50%;
252
+ width: 200%;
253
+ height: 200%;
254
+ background: linear-gradient(
255
+ to bottom right,
256
+ rgba(255, 255, 255, 0) 0%,
257
+ rgba(255, 255, 255, 0.1) 50%,
258
+ rgba(255, 255, 255, 0) 100%
259
+ );
260
+ transform: rotate(45deg);
261
+ animation: btn-shine 3s infinite;
262
+ }
263
+
264
+ @keyframes btn-shine {
265
+ 0% {
266
+ left: -50%;
267
+ }
268
+ 100% {
269
+ left: 150%;
270
+ }
271
+ }
272
+
273
+ .error-message {
274
+ background-color: rgba(248, 114, 114, 0.2);
275
+ color: var(--error-color);
276
+ padding: 0.75rem;
277
+ border-radius: 6px;
278
+ margin-bottom: 1.5rem;
279
+ font-size: 0.9rem;
280
+ border-left: 3px solid var(--error-color);
281
+ }
282
+
283
+ .logo {
284
+ margin-bottom: 1rem;
285
+ font-size: 3rem;
286
+ animation: glow 2s infinite alternate;
287
+ }
288
+
289
+ @keyframes glow {
290
+ from {
291
+ text-shadow: 0 0 5px rgba(111, 66, 193, 0.5), 0 0 10px rgba(111, 66, 193, 0.5);
292
+ }
293
+ to {
294
+ text-shadow: 0 0 10px rgba(111, 66, 193, 0.8), 0 0 20px rgba(111, 66, 193, 0.8);
295
+ }
296
+ }
297
+ </style>
298
+ </head>
299
+ <body>
300
+ <div class="grid-background"></div>
301
+ <div class="particles">
302
+ <!-- 粒子元素会由JS生成 -->
303
+ </div>
304
+ <div class="web-container">
305
+ <canvas class="web" id="webCanvas"></canvas>
306
+ </div>
307
+
308
+ <div class="login-card">
309
+ <div class="login-header">
310
+ <div class="logo">🤖</div>
311
+ <h1>Abacus Chat Proxy</h1>
312
+ <p>请输入访问密码</p>
313
+ </div>
314
+
315
+ {% if space_url %}
316
+ <div class="space-info">
317
+ api接口为{{ space_url }}/v1,请点击 <a href="{{ space_url }}" target="_blank">{{ space_url }}</a> 来登录并查看使用情况,
318
+ </div>
319
+ {% endif %}
320
+
321
+ {% if error %}
322
+ <div class="error-message" id="error-message">
323
+ {{ error }}
324
+ </div>
325
+ {% endif %}
326
+
327
+ <form class="login-form" method="post" action="/login">
328
+ <div class="form-group">
329
+ <label for="password">密码</label>
330
+ <input type="password" class="form-control" id="password" name="password" placeholder="请输入访问密码" required>
331
+ </div>
332
+
333
+ <button type="submit" class="btn btn-primary">登录</button>
334
+ </form>
335
+ </div>
336
+
337
+ <script>
338
+ // 创建浮动粒子
339
+ function createParticles() {
340
+ const particlesContainer = document.querySelector('.particles');
341
+ const particleCount = 20;
342
+
343
+ for (let i = 0; i < particleCount; i++) {
344
+ const particle = document.createElement('div');
345
+ particle.className = 'particle';
346
+
347
+ // 随机位置和大小
348
+ const size = Math.random() * 5 + 2;
349
+ const x = Math.random() * 100;
350
+ const y = Math.random() * 100;
351
+
352
+ particle.style.width = `${size}px`;
353
+ particle.style.height = `${size}px`;
354
+ particle.style.left = `${x}%`;
355
+ particle.style.top = `${y}%`;
356
+
357
+ // 随机动画延迟
358
+ particle.style.animationDelay = `${Math.random() * 10}s`;
359
+ particle.style.animationDuration = `${Math.random() * 10 + 10}s`;
360
+
361
+ // 随机透明度
362
+ particle.style.opacity = Math.random() * 0.5;
363
+
364
+ particlesContainer.appendChild(particle);
365
+ }
366
+ }
367
+
368
+ // 科幻蜘蛛网效果
369
+ function initWebCanvas() {
370
+ const canvas = document.getElementById('webCanvas');
371
+ const ctx = canvas.getContext('2d');
372
+ let width = window.innerWidth;
373
+ let height = window.innerHeight;
374
+
375
+ // 设置canvas尺寸
376
+ canvas.width = width;
377
+ canvas.height = height;
378
+
379
+ // 节点类
380
+ class Node {
381
+ constructor(x, y) {
382
+ this.x = x;
383
+ this.y = y;
384
+ this.vx = (Math.random() - 0.5) * 0.5;
385
+ this.vy = (Math.random() - 0.5) * 0.5;
386
+ this.radius = Math.random() * 2 + 1;
387
+ }
388
+
389
+ update() {
390
+ if (this.x < 0 || this.x > width) this.vx = -this.vx;
391
+ if (this.y < 0 || this.y > height) this.vy = -this.vy;
392
+
393
+ this.x += this.vx;
394
+ this.y += this.vy;
395
+ }
396
+
397
+ draw() {
398
+ ctx.beginPath();
399
+ ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
400
+ ctx.fillStyle = 'rgba(111, 66, 193, 0.4)';
401
+ ctx.fill();
402
+ }
403
+ }
404
+
405
+ // 创建节点
406
+ const nodeCount = Math.floor(width * height / 15000);
407
+ const nodes = [];
408
+
409
+ for (let i = 0; i < nodeCount; i++) {
410
+ nodes.push(new Node(Math.random() * width, Math.random() * height));
411
+ }
412
+
413
+ // 绘制线条
414
+ function drawWeb() {
415
+ ctx.clearRect(0, 0, width, height);
416
+
417
+ // 更新节点
418
+ nodes.forEach(node => {
419
+ node.update();
420
+ node.draw();
421
+ });
422
+
423
+ // 绘制连线
424
+ for (let i = 0; i < nodes.length; i++) {
425
+ for (let j = i + 1; j < nodes.length; j++) {
426
+ const dx = nodes[i].x - nodes[j].x;
427
+ const dy = nodes[i].y - nodes[j].y;
428
+ const distance = Math.sqrt(dx * dx + dy * dy);
429
+
430
+ if (distance < 150) {
431
+ ctx.beginPath();
432
+ ctx.moveTo(nodes[i].x, nodes[i].y);
433
+ ctx.lineTo(nodes[j].x, nodes[j].y);
434
+ ctx.strokeStyle = `rgba(111, 66, 193, ${0.2 * (1 - distance / 150)})`;
435
+ ctx.lineWidth = 0.5;
436
+ ctx.stroke();
437
+ }
438
+ }
439
+ }
440
+
441
+ requestAnimationFrame(drawWeb);
442
+ }
443
+
444
+ // 监听窗口大小变化
445
+ window.addEventListener('resize', () => {
446
+ width = window.innerWidth;
447
+ height = window.innerHeight;
448
+ canvas.width = width;
449
+ canvas.height = height;
450
+ });
451
+
452
+ // 开始动画
453
+ drawWeb();
454
+ }
455
+
456
+ // 页面加载时初始化效果
457
+ window.addEventListener('load', () => {
458
+ createParticles();
459
+ initWebCanvas();
460
+ });
461
+ </script>
462
+ </body>
463
  </html>