File size: 45,661 Bytes
3808745
 
 
 
 
 
 
 
 
 
 
 
 
538cdc0
a11b407
538cdc0
 
d4ada08
 
9dffbe6
 
3808745
538cdc0
d4ada08
9dffbe6
 
3808745
 
 
538cdc0
a1520e7
9dffbe6
 
d2960de
9dffbe6
 
 
3808745
9dffbe6
 
 
3808745
9dffbe6
 
3808745
9dffbe6
 
 
3808745
9dffbe6
3808745
9dffbe6
3808745
9dffbe6
3808745
d2960de
 
 
3808745
9dffbe6
3808745
 
 
9dffbe6
d2960de
3808745
9dffbe6
 
d2960de
3808745
9dffbe6
 
 
3808745
9dffbe6
 
3808745
9dffbe6
27f82ca
 
538cdc0
3808745
9dffbe6
 
 
 
3808745
9dffbe6
 
3808745
9dffbe6
 
 
3808745
9dffbe6
 
 
 
 
 
3808745
d2960de
9dffbe6
3808745
9dffbe6
 
d2960de
3808745
9dffbe6
 
 
 
d2960de
3808745
 
 
 
 
9dffbe6
 
3808745
 
 
 
9dffbe6
 
3808745
 
 
 
9dffbe6
 
 
3808745
9dffbe6
 
 
3808745
 
 
 
 
 
9dffbe6
 
 
 
 
 
 
3808745
9dffbe6
 
9be7074
3808745
 
 
d2960de
3808745
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48678c8
 
 
 
3808745
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d2960de
 
3808745
d2960de
3808745
 
 
 
 
48678c8
538cdc0
3808745
d4ada08
 
 
3808745
 
3cbd9b0
3808745
 
 
97a6fba
 
35edd4a
 
 
d4ada08
9be7074
 
 
 
538cdc0
9be7074
 
d4ada08
538cdc0
9be7074
 
 
 
 
 
 
d4ada08
9be7074
538cdc0
d4ada08
9be7074
35edd4a
d4ada08
 
 
35edd4a
9be7074
 
 
a1520e7
d4ada08
9be7074
538cdc0
d4ada08
9be7074
d4ada08
538cdc0
 
d2960de
a1520e7
 
3808745
a1520e7
 
3808745
a1520e7
 
 
 
 
 
 
 
3808745
d2960de
538cdc0
3808745
538cdc0
 
 
3808745
538cdc0
d2960de
538cdc0
d2960de
3808745
a1520e7
 
9dffbe6
9be7074
3808745
538cdc0
d4ada08
 
9dffbe6
3808745
a1520e7
 
9dffbe6
a1520e7
3808745
 
 
a1520e7
3808745
538cdc0
9be7074
a1520e7
9dffbe6
a1520e7
 
9dffbe6
a1520e7
3808745
 
 
a1520e7
 
9dffbe6
3808745
 
538cdc0
 
9dffbe6
a1520e7
3808745
 
 
a1520e7
3808745
538cdc0
a1520e7
9dffbe6
a1520e7
d2960de
a1520e7
9dffbe6
a1520e7
3808745
 
 
a1520e7
3808745
a1520e7
 
9dffbe6
a1520e7
9dffbe6
a1520e7
9dffbe6
a1520e7
3808745
 
 
a1520e7
d2960de
a1520e7
 
 
3808745
a1520e7
 
 
3808745
 
 
 
 
538cdc0
3808745
 
 
538cdc0
3808745
538cdc0
9dffbe6
3808745
 
538cdc0
 
 
 
 
3808745
 
 
 
 
d4ada08
 
 
 
 
 
 
 
 
3808745
d4ada08
3808745
 
 
 
 
d4ada08
 
3808745
 
 
d4ada08
 
 
 
 
 
3808745
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d4ada08
 
3808745
d4ada08
3808745
 
 
 
 
d4ada08
 
3808745
d4ada08
3808745
d4ada08
3808745
 
 
 
 
 
d4ada08
3808745
d4ada08
 
3808745
d4ada08
 
 
 
3808745
 
 
 
 
 
 
 
 
d4ada08
 
 
3808745
 
 
 
 
 
 
 
 
 
 
d4ada08
3808745
d4ada08
3808745
d4ada08
 
 
 
3808745
d4ada08
 
 
 
 
3808745
 
 
 
d4ada08
3808745
 
 
 
 
 
d4ada08
 
 
3808745
d4ada08
 
 
 
3808745
 
 
 
d4ada08
 
3808745
d4ada08
3808745
d4ada08
3808745
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9be7074
 
 
3808745
 
 
 
 
 
 
d4ada08
 
3808745
d4ada08
3808745
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9be7074
3808745
9be7074
 
 
3808745
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d4ada08
538cdc0
 
3808745
1c4eee7
 
 
 
 
3808745
1c4eee7
 
 
 
 
 
 
3808745
1c4eee7
3808745
1c4eee7
 
3808745
 
051b4e3
 
d2960de
 
3808745
9be7074
 
 
051b4e3
 
3808745
051b4e3
 
 
 
 
d2960de
3808745
051b4e3
 
27f82ca
051b4e3
3808745
d2960de
051b4e3
3808745
 
 
d2960de
3808745
 
 
 
 
 
051b4e3
3808745
27f82ca
 
 
d2960de
3808745
 
 
 
6893a36
 
3808745
 
 
6893a36
3808745
 
 
 
 
27f82ca
3808745
 
 
 
 
 
 
d4ada08
3808745
 
7eda262
 
 
499ce9d
9be7074
499ce9d
9be7074
7eda262
3808745
 
 
 
6893a36
 
 
 
 
 
 
 
 
d2960de
6893a36
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9a22c3f
 
 
 
 
 
 
 
 
 
48678c8
 
 
 
 
 
 
 
5816c60
 
 
 
 
 
 
 
3808745
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d2960de
6893a36
 
 
 
 
 
 
3808745
 
48678c8
3808745
 
 
 
 
 
9be7074
3808745
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
538cdc0
3808745
 
 
 
 
 
 
 
 
48678c8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3808745
 
 
48678c8
 
d824351
48678c8
 
3808745
27f82ca
3808745
 
 
d4ada08
3808745
d2960de
3808745
 
d4ada08
3808745
d4ada08
9be7074
3808745
d4ada08
3808745
 
 
 
 
 
 
 
9be7074
3808745
 
d4ada08
3808745
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d4ada08
3808745
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
538cdc0
 
3808745
538cdc0
 
a1520e7
9dffbe6
 
 
 
3808745
9dffbe6
3808745
 
 
9dffbe6
 
3808745
d4ada08
 
a1520e7
3808745
 
0f5a716
a1520e7
9dffbe6
a1520e7
 
3808745
a1520e7
3808745
9dffbe6
 
 
27f82ca
9dffbe6
538cdc0
3808745
538cdc0
3808745
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
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
# Import from the correct module path
from utils import run_society
import os
import gradio as gr
import time
import json
import logging
import datetime
from typing import Tuple
import importlib
from dotenv import load_dotenv, set_key, find_dotenv, unset_key
import threading
import queue
import re  # For regular expression operations

os.environ["PYTHONIOENCODING"] = "utf-8"


# 配置日志系统
def setup_logging():
    """配置日志系统,将日志输出到文件和内存队列以及控制台"""
    # 创建logs目录(如果不存在)
    logs_dir = os.path.join(os.path.dirname(__file__), "logs")
    os.makedirs(logs_dir, exist_ok=True)

    # 生成日志文件名(使用当前日期)
    current_date = datetime.datetime.now().strftime("%Y-%m-%d")
    log_file = os.path.join(logs_dir, f"gradio_log_{current_date}.txt")

    # 配置根日志记录器(捕获所有日志)
    root_logger = logging.getLogger()

    # 清除现有的处理器,避免重复日志
    for handler in root_logger.handlers[:]:
        root_logger.removeHandler(handler)

    root_logger.setLevel(logging.INFO)

    # 创建文件处理器
    file_handler = logging.FileHandler(log_file, encoding="utf-8", mode="a")
    file_handler.setLevel(logging.INFO)

    # 创建控制台处理器
    console_handler = logging.StreamHandler()
    console_handler.setLevel(logging.INFO)

    # 创建格式化器
    formatter = logging.Formatter(
        "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
    )
    file_handler.setFormatter(formatter)
    console_handler.setFormatter(formatter)

    # 添加处理器到根日志记录器
    root_logger.addHandler(file_handler)
    root_logger.addHandler(console_handler)

    logging.info("日志系统已初始化,日志文件: %s", log_file)
    return log_file


# 全局变量
LOG_FILE = None
LOG_QUEUE: queue.Queue = queue.Queue()  # 日志队列
STOP_LOG_THREAD = threading.Event()
CURRENT_PROCESS = None  # 用于跟踪当前运行的进程
STOP_REQUESTED = threading.Event()  # 用于标记是否请求停止


# 日志读取和更新函数
def log_reader_thread(log_file):
    """后台线程,持续读取日志文件并将新行添加到队列中"""
    try:
        with open(log_file, "r", encoding="utf-8") as f:
            # 移动到文件末尾
            f.seek(0, 2)

            while not STOP_LOG_THREAD.is_set():
                line = f.readline()
                if line:
                    LOG_QUEUE.put(line)  # 添加到对话记录队列
                else:
                    # 没有新行,等待一小段时间
                    time.sleep(0.1)
    except Exception as e:
        logging.error(f"日志读取线程出错: {str(e)}")


def get_latest_logs(max_lines=100, queue_source=None):
    """从队列中获取最新的日志行,如果队列为空则直接从文件读取

    Args:
        max_lines: 最大返回行数
        queue_source: 指定使用哪个队列,默认为LOG_QUEUE

    Returns:
        str: 日志内容
    """
    logs = []
    log_queue = queue_source if queue_source else LOG_QUEUE

    # 创建一个临时队列来存储日志,以便我们可以处理它们而不会从原始队列中删除它们
    temp_queue = queue.Queue()
    temp_logs = []

    try:
        # 尝试从队列中获取所有可用的日志行
        while not log_queue.empty() and len(temp_logs) < max_lines:
            log = log_queue.get_nowait()
            temp_logs.append(log)
            temp_queue.put(log)  # 将日志放回临时队列
    except queue.Empty:
        pass

    # 处理对话记录
    logs = temp_logs

    # 如果没有新日志或日志不足,尝试直接从文件读取最后几行
    if len(logs) < max_lines and LOG_FILE and os.path.exists(LOG_FILE):
        try:
            with open(LOG_FILE, "r", encoding="utf-8") as f:
                all_lines = f.readlines()
                # 如果队列中已有一些日志,只读取剩余需要的行数
                remaining_lines = max_lines - len(logs)
                file_logs = (
                    all_lines[-remaining_lines:]
                    if len(all_lines) > remaining_lines
                    else all_lines
                )

                # 将文件日志添加到队列日志之前
                logs = file_logs + logs
        except Exception as e:
            error_msg = f"读取日志文件出错: {str(e)}"
            logging.error(error_msg)
            if not logs:  # 只有在没有任何日志的情况下才添加错误消息
                logs = [error_msg]

    # 如果仍然没有日志,返回提示信息
    if not logs:
        return "初始化运行中..."

    # 过滤日志,只保留 camel.agents.chat_agent - INFO 的日志
    filtered_logs = []
    for log in logs:
        if "camel.agents.chat_agent - INFO" in log:
            filtered_logs.append(log)

    # 如果过滤后没有日志,返回提示信息
    if not filtered_logs:
        return "暂无对话记录。"

    # 处理日志内容,提取最新的用户和助手消息
    simplified_logs = []

    # 使用集合来跟踪已经处理过的消息,避免重复
    processed_messages = set()

    def process_message(role, content):
        # 创建一个唯一标识符来跟踪消息
        msg_id = f"{role}:{content}"
        if msg_id in processed_messages:
            return None

        processed_messages.add(msg_id)
        content = content.replace("\\n", "\n")
        lines = [line.strip() for line in content.split("\n")]
        content = "\n".join(lines)

        role_emoji = "🙋" if role.lower() == "user" else "🤖"
        return f"""### {role_emoji} {role.title()} Agent

{content}"""

    for log in filtered_logs:
        formatted_messages = []
        # 尝试提取消息数组
        messages_match = re.search(
            r"Model (.*?), index (\d+), processed these messages: (\[.*\])", log
        )

        if messages_match:
            try:
                messages = json.loads(messages_match.group(3))
                for msg in messages:
                    if msg.get("role") in ["user", "assistant"]:
                        formatted_msg = process_message(
                            msg.get("role"), msg.get("content", "")
                        )
                        if formatted_msg:
                            formatted_messages.append(formatted_msg)
            except json.JSONDecodeError:
                pass

        # 如果JSON解析失败或没有找到消息数组,尝试直接提取对话内容
        if not formatted_messages:
            user_pattern = re.compile(r"\{'role': 'user', 'content': '(.*?)'\}")
            assistant_pattern = re.compile(
                r"\{'role': 'assistant', 'content': '(.*?)'\}"
            )

            for content in user_pattern.findall(log):
                formatted_msg = process_message("user", content)
                if formatted_msg:
                    formatted_messages.append(formatted_msg)

            for content in assistant_pattern.findall(log):
                formatted_msg = process_message("assistant", content)
                if formatted_msg:
                    formatted_messages.append(formatted_msg)

        if formatted_messages:
            simplified_logs.append("\n\n".join(formatted_messages))

    # 格式化日志输出,确保每个对话记录之间有适当的分隔
    formatted_logs = []
    for i, log in enumerate(simplified_logs):
        # 移除开头和结尾的多余空白字符
        log = log.strip()

        formatted_logs.append(log)

        # 确保每个对话记录以换行符结束
        if not log.endswith("\n"):
            formatted_logs.append("\n")

    return "\n".join(formatted_logs)


# Dictionary containing module descriptions
MODULE_DESCRIPTIONS = {
    "run": "默认模式:使用OpenAI模型的默认的智能体协作模式,适合大多数任务。",
    "run_mini": "使用使用OpenAI模型最小化配置处理任务",
    "run_deepseek_zh": "使用deepseek模型处理中文任务",
    "run_openai_compatible_model": "使用openai兼容模型处理任务",
    "run_ollama": "使用本地ollama模型处理任务",
    "run_qwen_mini_zh": "使用qwen模型最小化配置处理任务",
    "run_qwen_zh": "使用qwen模型处理任务",
    "run_azure_openai": "使用azure openai模型处理任务",
    "run_groq": "使用groq模型处理任务",
}


# 默认环境变量模板
DEFAULT_ENV_TEMPLATE = """#===========================================
# MODEL & API 
# (See https://docs.camel-ai.org/key_modules/models.html#)
#===========================================

# OPENAI API (https://platform.openai.com/api-keys)
OPENAI_API_KEY='Your_Key'
# OPENAI_API_BASE_URL=""

# Azure OpenAI API
# AZURE_OPENAI_BASE_URL=""
# AZURE_API_VERSION=""
# AZURE_OPENAI_API_KEY=""
# AZURE_DEPLOYMENT_NAME=""


# Qwen API (https://help.aliyun.com/zh/model-studio/developer-reference/get-api-key)
QWEN_API_KEY='Your_Key'

# DeepSeek API (https://platform.deepseek.com/api_keys)
DEEPSEEK_API_KEY='Your_Key'

#===========================================
# Tools & Services API
#===========================================

# Google Search API (https://coda.io/@jon-dallas/google-image-search-pack-example/search-engine-id-and-google-api-key-3)
GOOGLE_API_KEY='Your_Key'
SEARCH_ENGINE_ID='Your_ID'

# Chunkr API (https://chunkr.ai/)
CHUNKR_API_KEY='Your_Key'

# Firecrawl API (https://www.firecrawl.dev/)
FIRECRAWL_API_KEY='Your_Key'
#FIRECRAWL_API_URL="https://api.firecrawl.dev"
"""


def validate_input(question: str) -> bool:
    """验证用户输入是否有效

    Args:
        question: 用户问题

    Returns:
        bool: 输入是否有效
    """
    # 检查输入是否为空或只包含空格
    if not question or question.strip() == "":
        return False
    return True


def run_owl(question: str, example_module: str) -> Tuple[str, str, str]:
    """运行OWL系统并返回结果

    Args:
        question: 用户问题
        example_module: 要导入的示例模块名(如 "run_terminal_zh" 或 "run_deep")

    Returns:
        Tuple[...]: 回答、令牌计数、状态
    """
    global CURRENT_PROCESS

    # 验证输入
    if not validate_input(question):
        logging.warning("用户提交了无效的输入")
        return ("请输入有效的问题", "0", "❌ 错误: 输入问题无效")

    try:
        # 确保环境变量已加载
        load_dotenv(find_dotenv(), override=True)
        logging.info(f"处理问题: '{question}', 使用模块: {example_module}")

        # 检查模块是否在MODULE_DESCRIPTIONS中
        if example_module not in MODULE_DESCRIPTIONS:
            logging.error(f"用户选择了不支持的模块: {example_module}")
            return (
                f"所选模块 '{example_module}' 不受支持",
                "0",
                "❌ 错误: 不支持的模块",
            )

        # 动态导入目标模块
        module_path = f"examples.{example_module}"
        try:
            logging.info(f"正在导入模块: {module_path}")
            module = importlib.import_module(module_path)
        except ImportError as ie:
            logging.error(f"无法导入模块 {module_path}: {str(ie)}")
            return (
                f"无法导入模块: {module_path}",
                "0",
                f"❌ 错误: 模块 {example_module} 不存在或无法加载 - {str(ie)}",
            )
        except Exception as e:
            logging.error(f"导入模块 {module_path} 时发生错误: {str(e)}")
            return (f"导入模块时发生错误: {module_path}", "0", f"❌ 错误: {str(e)}")

        # 检查是否包含construct_society函数
        if not hasattr(module, "construct_society"):
            logging.error(f"模块 {module_path} 中未找到 construct_society 函数")
            return (
                f"模块 {module_path} 中未找到 construct_society 函数",
                "0",
                "❌ 错误: 模块接口不兼容",
            )

        # 构建社会模拟
        try:
            logging.info("正在构建社会模拟...")
            society = module.construct_society(question)

        except Exception as e:
            logging.error(f"构建社会模拟时发生错误: {str(e)}")
            return (
                f"构建社会模拟时发生错误: {str(e)}",
                "0",
                f"❌ 错误: 构建失败 - {str(e)}",
            )

        # 运行社会模拟
        try:
            logging.info("正在运行社会模拟...")
            answer, chat_history, token_info = run_society(society)
            logging.info("社会模拟运行完成")
        except Exception as e:
            logging.error(f"运行社会模拟时发生错误: {str(e)}")
            return (
                f"运行社会模拟时发生错误: {str(e)}",
                "0",
                f"❌ 错误: 运行失败 - {str(e)}",
            )

        # 安全地获取令牌计数
        if not isinstance(token_info, dict):
            token_info = {}

        completion_tokens = token_info.get("completion_token_count", 0)
        prompt_tokens = token_info.get("prompt_token_count", 0)
        total_tokens = completion_tokens + prompt_tokens

        logging.info(
            f"处理完成,令牌使用: 完成={completion_tokens}, 提示={prompt_tokens}, 总计={total_tokens}"
        )

        return (
            answer,
            f"完成令牌: {completion_tokens:,} | 提示令牌: {prompt_tokens:,} | 总计: {total_tokens:,}",
            "✅ 成功完成",
        )

    except Exception as e:
        logging.error(f"处理问题时发生未捕获的错误: {str(e)}")
        return (f"发生错误: {str(e)}", "0", f"❌ 错误: {str(e)}")


def update_module_description(module_name: str) -> str:
    """返回所选模块的描述"""
    return MODULE_DESCRIPTIONS.get(module_name, "无可用描述")


# 存储前端配置的环境变量
WEB_FRONTEND_ENV_VARS: dict[str, str] = {}


def init_env_file():
    """初始化.env文件如果不存在"""
    dotenv_path = find_dotenv()
    if not dotenv_path:
        with open(".env", "w") as f:
            f.write(DEFAULT_ENV_TEMPLATE)
        dotenv_path = find_dotenv()
    return dotenv_path


def load_env_vars():
    """加载环境变量并返回字典格式

    Returns:
        dict: 环境变量字典,每个值为一个包含值和来源的元组 (value, source)
    """
    dotenv_path = init_env_file()
    load_dotenv(dotenv_path, override=True)

    # 从.env文件读取环境变量
    env_file_vars = {}
    with open(dotenv_path, "r") as f:
        for line in f:
            line = line.strip()
            if line and not line.startswith("#"):
                if "=" in line:
                    key, value = line.split("=", 1)
                    env_file_vars[key.strip()] = value.strip().strip("\"'")

    # 从系统环境变量中获取
    system_env_vars = {
        k: v
        for k, v in os.environ.items()
        if k not in env_file_vars and k not in WEB_FRONTEND_ENV_VARS
    }

    # 合并环境变量,并标记来源
    env_vars = {}

    # 添加系统环境变量(最低优先级)
    for key, value in system_env_vars.items():
        env_vars[key] = (value, "系统")

    # 添加.env文件环境变量(中等优先级)
    for key, value in env_file_vars.items():
        env_vars[key] = (value, ".env文件")

    # 添加前端配置的环境变量(最高优先级)
    for key, value in WEB_FRONTEND_ENV_VARS.items():
        env_vars[key] = (value, "前端配置")
        # 确保操作系统环境变量也被更新
        os.environ[key] = value

    return env_vars


def save_env_vars(env_vars):
    """保存环境变量到.env文件

    Args:
        env_vars: 字典,键为环境变量名,值可以是字符串或(值,来源)元组
    """
    try:
        dotenv_path = init_env_file()

        # 保存每个环境变量
        for key, value_data in env_vars.items():
            if key and key.strip():  # 确保键不为空
                # 处理值可能是元组的情况
                if isinstance(value_data, tuple):
                    value = value_data[0]
                else:
                    value = value_data

                set_key(dotenv_path, key.strip(), value.strip())

        # 重新加载环境变量以确保生效
        load_dotenv(dotenv_path, override=True)

        return True, "环境变量已成功保存!"
    except Exception as e:
        return False, f"保存环境变量时出错: {str(e)}"


def add_env_var(key, value, from_frontend=True):
    """添加或更新单个环境变量

    Args:
        key: 环境变量名
        value: 环境变量值
        from_frontend: 是否来自前端配置,默认为True
    """
    try:
        if not key or not key.strip():
            return False, "变量名不能为空"

        key = key.strip()
        value = value.strip()

        # 如果来自前端,则添加到前端环境变量字典
        if from_frontend:
            WEB_FRONTEND_ENV_VARS[key] = value
            # 直接更新系统环境变量
            os.environ[key] = value

        # 同时更新.env文件
        dotenv_path = init_env_file()
        set_key(dotenv_path, key, value)
        load_dotenv(dotenv_path, override=True)

        return True, f"环境变量 {key} 已成功添加/更新!"
    except Exception as e:
        return False, f"添加环境变量时出错: {str(e)}"


def delete_env_var(key):
    """删除环境变量"""
    try:
        if not key or not key.strip():
            return False, "变量名不能为空"

        key = key.strip()

        # 从.env文件中删除
        dotenv_path = init_env_file()
        unset_key(dotenv_path, key)

        # 从前端环境变量字典中删除
        if key in WEB_FRONTEND_ENV_VARS:
            del WEB_FRONTEND_ENV_VARS[key]

        # 从当前进程环境中也删除
        if key in os.environ:
            del os.environ[key]

        return True, f"环境变量 {key} 已成功删除!"
    except Exception as e:
        return False, f"删除环境变量时出错: {str(e)}"


def is_api_related(key: str) -> bool:
    """判断环境变量是否与API相关

    Args:
        key: 环境变量名

    Returns:
        bool: 是否与API相关
    """
    # API相关的关键词
    api_keywords = [
        "api",
        "key",
        "token",
        "secret",
        "password",
        "openai",
        "qwen",
        "deepseek",
        "google",
        "search",
        "hf",
        "hugging",
        "chunkr",
        "firecrawl",
    ]

    # 检查是否包含API相关关键词(不区分大小写)
    return any(keyword in key.lower() for keyword in api_keywords)


def get_api_guide(key: str) -> str:
    """根据环境变量名返回对应的API获取指南

    Args:
        key: 环境变量名

    Returns:
        str: API获取指南链接或说明
    """
    key_lower = key.lower()
    if "openai" in key_lower:
        return "https://platform.openai.com/api-keys"
    elif "qwen" in key_lower or "dashscope" in key_lower:
        return "https://help.aliyun.com/zh/model-studio/developer-reference/get-api-key"
    elif "deepseek" in key_lower:
        return "https://platform.deepseek.com/api_keys"
    elif "google" in key_lower:
        return "https://coda.io/@jon-dallas/google-image-search-pack-example/search-engine-id-and-google-api-key-3"
    elif "search_engine_id" in key_lower:
        return "https://coda.io/@jon-dallas/google-image-search-pack-example/search-engine-id-and-google-api-key-3"
    elif "chunkr" in key_lower:
        return "https://chunkr.ai/"
    elif "firecrawl" in key_lower:
        return "https://www.firecrawl.dev/"
    else:
        return ""


def update_env_table():
    """更新环境变量表格显示,只显示API相关的环境变量"""
    env_vars = load_env_vars()
    # 过滤出API相关的环境变量
    api_env_vars = {k: v for k, v in env_vars.items() if is_api_related(k)}
    # 转换为列表格式,以符合Gradio Dataframe的要求
    # 格式: [变量名, 变量值, 获取指南链接]
    result = []
    for k, v in api_env_vars.items():
        guide = get_api_guide(k)
        # 如果有指南链接,创建一个可点击的链接
        guide_link = (
            f"<a href='{guide}' target='_blank' class='guide-link'>🔗 获取</a>"
            if guide
            else ""
        )
        result.append([k, v[0], guide_link])
    return result


def save_env_table_changes(data):
    """保存环境变量表格的更改

    Args:
        data: Dataframe数据,可能是pandas DataFrame对象

    Returns:
        str: 操作状态信息,包含HTML格式的状态消息
    """
    try:
        logging.info(f"开始处理环境变量表格数据,类型: {type(data)}")

        # 获取当前所有环境变量
        current_env_vars = load_env_vars()
        processed_keys = set()  # 记录已处理的键,用于检测删除的变量

        # 处理pandas DataFrame对象
        import pandas as pd

        if isinstance(data, pd.DataFrame):
            # 获取列名信息
            columns = data.columns.tolist()
            logging.info(f"DataFrame列名: {columns}")

            # 遍历DataFrame的每一行
            for index, row in data.iterrows():
                # 使用列名访问数据
                if len(columns) >= 3:
                    # 获取变量名和值 (第0列是变量名,第1列是值)
                    key = row[0] if isinstance(row, pd.Series) else row.iloc[0]
                    value = row[1] if isinstance(row, pd.Series) else row.iloc[1]

                    # 检查是否为空行或已删除的变量
                    if key and str(key).strip():  # 如果键名不为空,则添加或更新
                        logging.info(f"处理环境变量: {key} = {value}")
                        add_env_var(key, str(value))
                        processed_keys.add(key)
        # 处理其他格式
        elif isinstance(data, dict):
            logging.info(f"字典格式数据的键: {list(data.keys())}")
            # 如果是字典格式,尝试不同的键
            if "data" in data:
                rows = data["data"]
            elif "values" in data:
                rows = data["values"]
            elif "value" in data:
                rows = data["value"]
            else:
                # 尝试直接使用字典作为行数据
                rows = []
                for key, value in data.items():
                    if key not in ["headers", "types", "columns"]:
                        rows.append([key, value])

            if isinstance(rows, list):
                for row in rows:
                    if isinstance(row, list) and len(row) >= 2:
                        key, value = row[0], row[1]
                        if key and str(key).strip():
                            add_env_var(key, str(value))
                            processed_keys.add(key)
        elif isinstance(data, list):
            # 列表格式
            for row in data:
                if isinstance(row, list) and len(row) >= 2:
                    key, value = row[0], row[1]
                    if key and str(key).strip():
                        add_env_var(key, str(value))
                        processed_keys.add(key)
        else:
            logging.error(f"未知的数据格式: {type(data)}")
            return f"❌ 保存失败: 未知的数据格式 {type(data)}"

        # 处理删除的变量 - 检查当前环境变量中是否有未在表格中出现的变量
        api_related_keys = {k for k in current_env_vars.keys() if is_api_related(k)}
        keys_to_delete = api_related_keys - processed_keys

        # 删除不再表格中的变量
        for key in keys_to_delete:
            logging.info(f"删除环境变量: {key}")
            delete_env_var(key)

        return "✅ 环境变量已成功保存"
    except Exception as e:
        import traceback

        error_details = traceback.format_exc()
        logging.error(f"保存环境变量时出错: {str(e)}\n{error_details}")
        return f"❌ 保存失败: {str(e)}"


def get_env_var_value(key):
    """获取环境变量的实际值

    优先级:前端配置 > .env文件 > 系统环境变量
    """
    # 检查前端配置的环境变量
    if key in WEB_FRONTEND_ENV_VARS:
        return WEB_FRONTEND_ENV_VARS[key]

    # 检查系统环境变量(包括从.env加载的)
    return os.environ.get(key, "")


def create_ui():
    """创建增强版Gradio界面"""

    def clear_log_file():
        """清空日志文件内容"""
        try:
            if LOG_FILE and os.path.exists(LOG_FILE):
                # 清空日志文件内容而不是删除文件
                open(LOG_FILE, "w").close()
                logging.info("日志文件已清空")
                # 清空日志队列
                while not LOG_QUEUE.empty():
                    try:
                        LOG_QUEUE.get_nowait()
                    except queue.Empty:
                        break
                return ""
            else:
                return ""
        except Exception as e:
            logging.error(f"清空日志文件时出错: {str(e)}")
            return ""

    # 创建一个实时日志更新函数
    def process_with_live_logs(question, module_name):
        """处理问题并实时更新日志"""
        global CURRENT_PROCESS

        # 清空日志文件
        clear_log_file()

        # 创建一个后台线程来处理问题
        result_queue = queue.Queue()

        def process_in_background():
            try:
                result = run_owl(question, module_name)
                result_queue.put(result)
            except Exception as e:
                result_queue.put((f"发生错误: {str(e)}", "0", f"❌ 错误: {str(e)}"))

        # 启动后台处理线程
        bg_thread = threading.Thread(target=process_in_background)
        CURRENT_PROCESS = bg_thread  # 记录当前进程
        bg_thread.start()

        # 在等待处理完成的同时,每秒更新一次日志
        while bg_thread.is_alive():
            # 更新对话记录显示
            logs2 = get_latest_logs(100, LOG_QUEUE)

            # 始终更新状态
            yield (
                "0",
                "<span class='status-indicator status-running'></span> 处理中...",
                logs2,
            )

            time.sleep(1)

        # 处理完成,获取结果
        if not result_queue.empty():
            result = result_queue.get()
            answer, token_count, status = result

            # 最后一次更新对话记录
            logs2 = get_latest_logs(100, LOG_QUEUE)

            # 根据状态设置不同的指示器
            if "错误" in status:
                status_with_indicator = (
                    f"<span class='status-indicator status-error'></span> {status}"
                )
            else:
                status_with_indicator = (
                    f"<span class='status-indicator status-success'></span> {status}"
                )

            yield token_count, status_with_indicator, logs2
        else:
            logs2 = get_latest_logs(100, LOG_QUEUE)
            yield (
                "0",
                "<span class='status-indicator status-error'></span> 已终止",
                logs2,
            )

    with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue")) as app:
        gr.Markdown(
            """
                # 🦉 OWL 多智能体协作系统

                基于CAMEL框架开发的先进多智能体协作系统,旨在通过智能体协作解决复杂问题。

                可以通过修改本地脚本自定义模型和工具。
                
                本网页应用目前处于测试阶段,仅供演示和测试使用,尚未推荐用于生产环境。
                """
        )

        # 添加自定义CSS
        gr.HTML("""
            <style>
            /* 聊天容器样式 */
            .chat-container .chatbot {
                height: 500px;
                overflow-y: auto;
                border-radius: 10px;
                box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
            }
            

            /* 改进标签页样式 */
            .tabs .tab-nav {
                background-color: #f5f5f5;
                border-radius: 8px 8px 0 0;
                padding: 5px;
            }
            
            .tabs .tab-nav button {
                border-radius: 5px;
                margin: 0 3px;
                padding: 8px 15px;
                font-weight: 500;
            }
            
            .tabs .tab-nav button.selected {
                background-color: #2c7be5;
                color: white;
            }
            
            /* 状态指示器样式 */
            .status-indicator {
                display: inline-block;
                width: 10px;
                height: 10px;
                border-radius: 50%;
                margin-right: 5px;
            }
            
            .status-running {
                background-color: #ffc107;
                animation: pulse 1.5s infinite;
            }
            
            .status-success {
                background-color: #28a745;
            }
            
            .status-error {
                background-color: #dc3545;
            }
            
            /* 日志显示区域样式 */
            .log-display textarea {
                height: 400px !important;
                max-height: 400px !important;
                overflow-y: auto !important;
                font-family: monospace;
                font-size: 0.9em;
                white-space: pre-wrap;
                line-height: 1.4;
            }

            .log-display {
                border-radius: 10px;
                padding: 15px;
                margin-bottom: 20px;
                min-height: 50vh;
                max-height: 75vh;
            }
            
            /* 环境变量管理样式 */
            .env-manager-container {
                border-radius: 10px;
                padding: 15px;
                background-color: #f9f9f9;
                margin-bottom: 20px;
            }
            
            .env-controls, .api-help-container {
                border-radius: 8px;
                padding: 15px;
                background-color: white;
                box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
                height: 100%;
            }
            
            .env-add-group, .env-delete-group {
                margin-top: 20px;
                padding: 15px;
                border-radius: 8px;
                background-color: #f5f8ff;
                border: 1px solid #e0e8ff;
            }
            
            .env-delete-group {
                background-color: #fff5f5;
                border: 1px solid #ffe0e0;
            }
            
            .env-buttons {
                justify-content: flex-start;
                gap: 10px;
                margin-top: 10px;
            }
            
            .env-button {
                min-width: 100px;
            }
            
            .delete-button {
                background-color: #dc3545;
                color: white;
            }
            
            .env-table {
                margin-bottom: 15px;
            }
            
            /* 改进环境变量表格样式 */
            .env-table table {
                border-collapse: separate;
                border-spacing: 0;
                width: 100%;
                border-radius: 8px;
                overflow: hidden;
                box-shadow: 0 2px 8px rgba(0,0,0,0.05);
            }
            
            .env-table th {
                background-color: #f0f7ff;
                padding: 12px 15px;
                text-align: left;
                font-weight: 600;
                color: #2c7be5;
                border-bottom: 2px solid #e0e8ff;
            }
            
            .env-table td {
                padding: 10px 15px;
                border-bottom: 1px solid #f0f0f0;
            }
            
            .env-table tr:hover td {
                background-color: #f9fbff;
            }
            
            .env-table tr:last-child td {
                border-bottom: none;
            }
            
            /* 状态图标样式 */
            .status-icon-cell {
                text-align: center;
                font-size: 1.2em;
            }
            
            /* 链接样式 */
            .guide-link {
                color: #2c7be5;
                text-decoration: none;
                cursor: pointer;
                font-weight: 500;
            }
            
            .guide-link:hover {
                text-decoration: underline;
            }
            
            .env-status {
                margin-top: 15px;
                font-weight: 500;
                padding: 10px;
                border-radius: 6px;
                transition: all 0.3s ease;
            }
            
            .env-status-success {
                background-color: #d4edda;
                color: #155724;
                border: 1px solid #c3e6cb;
            }
            
            .env-status-error {
                background-color: #f8d7da;
                color: #721c24;
                border: 1px solid #f5c6cb;
            }
            
            .api-help-accordion {
                margin-bottom: 8px;
                border-radius: 6px;
                overflow: hidden;
            }
            

            @keyframes pulse {
                0% { opacity: 1; }
                50% { opacity: 0.5; }
                100% { opacity: 1; }
            }
            </style>
            """)

        with gr.Row():
            with gr.Column(scale=0.5):
                question_input = gr.Textbox(
                    lines=5,
                    placeholder="请输入您的问题...",
                    label="问题",
                    elem_id="question_input",
                    show_copy_button=True,
                    value="打开百度搜索,总结一下camel-ai的camel框架的github star、fork数目等,并把数字用plot包写成python文件保存到本地,并运行生成的python文件。",
                )

                # 增强版模块选择下拉菜单
                # 只包含MODULE_DESCRIPTIONS中定义的模块
                module_dropdown = gr.Dropdown(
                    choices=list(MODULE_DESCRIPTIONS.keys()),
                    value="run_qwen_zh",
                    label="选择功能模块",
                    interactive=True,
                )

                # 模块描述文本框
                module_description = gr.Textbox(
                    value=MODULE_DESCRIPTIONS["run_qwen_zh"],
                    label="模块描述",
                    interactive=False,
                    elem_classes="module-info",
                )

                with gr.Row():
                    run_button = gr.Button(
                        "运行", variant="primary", elem_classes="primary"
                    )

                status_output = gr.HTML(
                    value="<span class='status-indicator status-success'></span> 已就绪",
                    label="状态",
                )
                token_count_output = gr.Textbox(
                    label="令牌计数", interactive=False, elem_classes="token-count"
                )

                # 示例问题
                examples = [
                    "打开百度搜索,总结一下camel-ai的camel框架的github star、fork数目等,并把数字用plot包写成python文件保存到本地,并运行生成的python文件。",
                    "浏览亚马逊并找出一款对程序员有吸引力的产品。请提供产品名称和价格",
                    "写一个hello world的python文件,保存到本地",
                ]

                gr.Examples(examples=examples, inputs=question_input)

                gr.HTML("""
                        <div class="footer" id="about">
                            <h3>关于 OWL 多智能体协作系统</h3>
                            <p>OWL 是一个基于CAMEL框架开发的先进多智能体协作系统,旨在通过智能体协作解决复杂问题。</p>
                            <p>© 2025 CAMEL-AI.org. 基于Apache License 2.0开源协议</p>
                            <p><a href="https://github.com/camel-ai/owl" target="_blank">GitHub</a></p>
                        </div>
                    """)

            with gr.Tabs():  # 设置对话记录为默认选中的标签页
                with gr.TabItem("对话记录"):
                    # 添加对话记录显示区域
                    with gr.Box():
                        log_display2 = gr.Markdown(
                            value="暂无对话记录。",
                            elem_classes="log-display",
                        )

                    with gr.Row():
                        refresh_logs_button2 = gr.Button("刷新记录")
                        auto_refresh_checkbox2 = gr.Checkbox(
                            label="自动刷新", value=True, interactive=True
                        )
                        clear_logs_button2 = gr.Button("清空记录", variant="secondary")

                with gr.TabItem("环境变量管理", id="env-settings"):
                    with gr.Box(elem_classes="env-manager-container"):
                        gr.Markdown("""
                            ## 环境变量管理
                            
                            在此处设置模型API密钥和其他服务凭证。这些信息将保存在本地的`.env`文件中,确保您的API密钥安全存储且不会上传到网络。正确设置API密钥对于OWL系统的功能至关重要, 可以按找工具需求灵活配置环境变量。
                            """)

                        # 主要内容分为两列布局
                        with gr.Row():
                            # 左侧列:环境变量管理控件
                            with gr.Column(scale=3):
                                with gr.Box(elem_classes="env-controls"):
                                    # 环境变量表格 - 设置为可交互以直接编辑
                                    gr.Markdown("""
                                    <div style="background-color: #e7f3fe; border-left: 6px solid #2196F3; padding: 10px; margin: 15px 0; border-radius: 4px;">
                                      <strong>提示:</strong> 请确保运行cp .env_template .env创建本地.env文件,根据运行模块灵活配置所需环境变量
                                    </div>
                                    """)

                                    # 增强版环境变量表格,支持添加和删除行
                                    env_table = gr.Dataframe(
                                        headers=["变量名", "值", "获取指南"],
                                        datatype=[
                                            "str",
                                            "str",
                                            "html",
                                        ],  # 将最后一列设置为html类型以支持链接
                                        row_count=10,  # 增加行数,以便添加新变量
                                        col_count=(3, "fixed"),
                                        value=update_env_table,
                                        label="API密钥和环境变量",
                                        interactive=True,  # 设置为可交互,允许直接编辑
                                        elem_classes="env-table",
                                    )

                                    # 操作说明
                                    gr.Markdown(
                                        """
                                    <div style="background-color: #fff3cd; border-left: 6px solid #ffc107; padding: 10px; margin: 15px 0; border-radius: 4px;">
                                    <strong>操作指南</strong>:
                                    <ul style="margin-top: 8px; margin-bottom: 8px;">
                                      <li><strong>编辑变量</strong>: 直接点击表格中的"值"单元格进行编辑</li>
                                      <li><strong>添加变量</strong>: 在空白行中输入新的变量名和值</li>
                                      <li><strong>删除变量</strong>: 清空变量名即可删除该行</li>
                                      <li><strong>获取API密钥</strong>: 点击"获取指南"列中的链接获取相应API密钥</li>
                                    </ul>
                                    </div>
                                    """,
                                        elem_classes="env-instructions",
                                    )

                                    # 环境变量操作按钮
                                    with gr.Row(elem_classes="env-buttons"):
                                        save_env_button = gr.Button(
                                            "💾 保存更改",
                                            variant="primary",
                                            elem_classes="env-button",
                                        )
                                        refresh_button = gr.Button(
                                            "🔄 刷新列表", elem_classes="env-button"
                                        )

                                    # 状态显示
                                    env_status = gr.HTML(
                                        label="操作状态",
                                        value="",
                                        elem_classes="env-status",
                                    )

                    # 连接事件处理函数
                    save_env_button.click(
                        fn=save_env_table_changes,
                        inputs=[env_table],
                        outputs=[env_status],
                    ).then(fn=update_env_table, outputs=[env_table])

                    refresh_button.click(fn=update_env_table, outputs=[env_table])

        # 设置事件处理
        run_button.click(
            fn=process_with_live_logs,
            inputs=[question_input, module_dropdown],
            outputs=[token_count_output, status_output, log_display2],
        )

        # 模块选择更新描述
        module_dropdown.change(
            fn=update_module_description,
            inputs=module_dropdown,
            outputs=module_description,
        )

        # 对话记录相关事件处理
        refresh_logs_button2.click(
            fn=lambda: get_latest_logs(100, LOG_QUEUE), outputs=[log_display2]
        )

        clear_logs_button2.click(fn=clear_log_file, outputs=[log_display2])

        # 自动刷新控制
        def toggle_auto_refresh(enabled):
            if enabled:
                return gr.update(every=3)
            else:
                return gr.update(every=0)

        auto_refresh_checkbox2.change(
            fn=toggle_auto_refresh,
            inputs=[auto_refresh_checkbox2],
            outputs=[log_display2],
        )

        # 不再默认自动刷新日志

    return app


# 主函数
def main():
    try:
        # 初始化日志系统
        global LOG_FILE
        LOG_FILE = setup_logging()
        logging.info("OWL Web应用程序启动")

        # 启动日志读取线程
        log_thread = threading.Thread(
            target=log_reader_thread, args=(LOG_FILE,), daemon=True
        )
        log_thread.start()
        logging.info("日志读取线程已启动")

        # 初始化.env文件(如果不存在)
        init_env_file()
        app = create_ui()

        app.queue()
        app.launch(share=False)
    except Exception as e:
        logging.error(f"启动应用程序时发生错误: {str(e)}")
        print(f"启动应用程序时发生错误: {str(e)}")
        import traceback

        traceback.print_exc()

    finally:
        # 确保日志线程停止
        STOP_LOG_THREAD.set()
        STOP_REQUESTED.set()
        logging.info("应用程序关闭")


if __name__ == "__main__":
    main()