-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain_window.py
More file actions
1031 lines (888 loc) · 42.8 KB
/
main_window.py
File metadata and controls
1031 lines (888 loc) · 42.8 KB
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
# main_window.py
import logging
import re
import datetime
import PyQt5.sip as sip
from PyQt5.QtCore import Qt, QTimer, QThread, QMetaObject, Q_ARG, pyqtSignal
from PyQt5.QtGui import QPixmap, QFont
from PyQt5.QtWidgets import QWidget, QStackedWidget, QHBoxLayout, QVBoxLayout, QSpacerItem, QSizePolicy, QMenu, QLabel
from ai_audio.audio_task import AudioTask
from ai_table.gen_report import generate_analyst_report
from ai_table.gen_table import generate_table
from ai_table.intro_ui import TableIntroPage
from ai_table.table_task import TableTask
from audio_transcription.intro_ui import AudioTranscriptionIntroPage
from ocr.intro_ui import OcrIntroPage
from ocr.ocr_task import OcrTask
from meeting.intro_ui import MeetingIntroPage
from meeting.meeting_box import MeetingWidget
from meeting.meeting_btn_widget import MeetingBottomWidget
from meeting.meeting_task import MeetingTask, MeetingFile
from mind_map.intro_ui import MindMapIntroPage
from mind_map.map_task import MapTask
from ppt.intro_ui import PPTIntroPage
from ppt.workflow.ppt_task import PPTTask
from ppt.makePPTByTemplate.mdtojson import PPTGenerator
from sys_agent.intro_ui import SysFuncIntro
from sys_agent.agent_controller import AgentController
from translation.intro_ui import TranslateIntroPage
from translation.translate_task import TranslateTask
from translation.translate_detect import TranslateDetect
from chat.chat_task import ChatTask
from chat.intro_page import ChatIntroPage
from speech.voice_recognition import VoiceRecognition
from speech.speech_task import Speech, SpeechTask
from ui.utils import AssistantMode, ViewMode
from utils.intro_ui import DocAnalysisIntroPage
from utils.open_local_app_task import OpenLocalAppTask
from draw.drawing_task import AiDrawingTask
from server_check import ServerCheck
from ui.button.knowledge_base_select_button import KnowledgeBaseSelectButton
from ui.button.model_select_button import ModelSelectButton
from ui.chat.bubble_message import BubbleMessage, MessageType, ThumbnailMessage, ButtonMessage, \
WorkflowContainerMessage
from ui.chat.chat_box import ChatBox
from ui.page.speech_page import SpeechPage
from ui.button.function_menu_button import FunctionMenuButton
from ui.input_field import InputField
from ui.knowledge_base.knowledge_base_home import KnowledgeBaseHome
from ui.button.new_dialog_button import NewDialogButton
from config.config_manager import ConfigManager
logging.basicConfig(level=logging.INFO)
class MainWin(QWidget):
title_change_requested = pyqtSignal(str)
def __init__(self, main_win_width, main_win_height):
super().__init__()
self.main_win_width = main_win_width
self.main_win_height = main_win_height
self.init_ui()
self.func_init()
def init_ui(self):
self.setObjectName('mainwindow')
self.setStyleSheet(
'''
#mainwindow{
border-top-left-radius: 15px;
border-top-right-radius: 15px;
border-bottom-left-radius: 15px;
border-bottom-right-radius: 15px;
}
'''
)
self.setAttribute(Qt.WA_TranslucentBackground, True) # 背景透明
self.resize(self.main_win_width, self.main_win_height)
# 中间内容栈 Start
self.contentStackWgt = QStackedWidget()
self.chat_intro = ChatIntroPage() # 智能助手,即主界面
self.ppt_intro = PPTIntroPage() # AI PPT
self.translate_intro = TranslateIntroPage() # 语种翻译
self.ocr_intro = OcrIntroPage() # OCR
self.doc_analysis_intro = DocAnalysisIntroPage() # 文档分析
self.knowledge_base_home = KnowledgeBaseHome() # 知识库
self.table_intro = TableIntroPage() # AI 表格
self.meeting_intro = MeetingIntroPage() # 会议记录
self.mind_map_intro = MindMapIntroPage() # 思维导图
self.audio_transcription_intro = AudioTranscriptionIntroPage() # 思维导图
self.chat_box = ChatBox(410, self.main_win_height - 250) # 聊天框
self.chat_box.delete_index_history.connect(self.delete_index_history)
self.chat_box.add_vertical_spacer()
self.chat_box.hide()
self.speech_page = SpeechPage() # speech
self.speech_page.collect_voice_signal.connect(self.handle_capture_voice)
self.speech_page.stop_speech_signal.connect(self.stop_speech_signal_exchange)
self.speech_page.hide()
self.meetingWgt = MeetingWidget() # 会议
self.meetingWgt.hide()
self.sys_func_intro = SysFuncIntro()
self.contentStackWgt.addWidget(self.chat_intro)
self.contentStackWgt.addWidget(self.ppt_intro)
self.contentStackWgt.addWidget(self.translate_intro)
self.contentStackWgt.addWidget(self.ocr_intro)
self.contentStackWgt.addWidget(self.doc_analysis_intro)
self.contentStackWgt.addWidget(self.table_intro)
self.contentStackWgt.addWidget(self.meeting_intro)
self.contentStackWgt.addWidget(self.mind_map_intro)
self.contentStackWgt.addWidget(self.audio_transcription_intro)
self.contentStackWgt.addWidget(self.chat_box)
self.contentStackWgt.addWidget(self.speech_page)
self.contentStackWgt.addWidget(self.meetingWgt)
self.contentStackWgt.addWidget(self.sys_func_intro)
self.contentStackWgt.setCurrentIndex(0) # 设置初始页面
# 中间内容栈 End
# 功能按钮 Start
self.new_dialog_btn = NewDialogButton() # 新建对话按钮
self.new_dialog_btn.clicked_signal.connect(self.new_dialog_handle)
self.knowledge_base_select_btn = KnowledgeBaseSelectButton()
self.knowledge_base_select_btn.selection_changed.connect(self.update_selected_kb)
self.model_select_btn = ModelSelectButton()
self.model_select_btn.model_switch.connect(self.switch_model)
self.function_menu_btn = FunctionMenuButton() # 新建菜单按钮
self.function_menu_btn.function_selected.connect(self.handle_function_selection)
hor_layout = QHBoxLayout()
hor_layout.setSpacing(8) # 设置按钮间距
hor_layout.addWidget(self.new_dialog_btn)
hor_layout.addWidget(self.knowledge_base_select_btn)
hor_layout.addWidget(self.model_select_btn)
hor_layout.addWidget(self.function_menu_btn)
# 功能按钮 End
# 底部按钮栈 Start
self.bottomStackWgt = QStackedWidget()
self.input_field = InputField()
self.input_field.hide()
self.input_field.send_signal.connect(self.handle_send_message)
self.input_field.websearch_signal.connect(self.set_websearch_enabled)
self.meeting_bottom_ui = MeetingBottomWidget()
self.meeting_bottom_ui.hide()
self.meeting_bottom_ui.switch_signal.connect(self.meeting_signal_handle)
self.bottomStackWgt.addWidget(self.input_field)
self.bottomStackWgt.addWidget(self.meeting_bottom_ui)
self.bottomStackWgt.setCurrentIndex(0)
# 底部按钮栈 End
# 窗口整体布局
layout = QVBoxLayout()
layout.setContentsMargins(18, 0, 18, 12)
layout.addWidget(self.contentStackWgt)
layout.addItem(QSpacerItem(10, 40, QSizePolicy.Minimum, QSizePolicy.Expanding))
layout.addLayout(hor_layout) # 加入水平布局
layout.addWidget(self.bottomStackWgt)
self.setLayout(layout)
# 功能组件映射
self.page_mapping = {
"智能助手": {
"main": self.chat_intro,
"chat": self.chat_box,
"bottom": self.input_field
},
"AI PPT": {
"main": self.ppt_intro,
"chat": self.chat_box,
"bottom": self.input_field,
},
"语种翻译": {
"main": self.translate_intro,
"chat": self.chat_box,
"bottom": self.input_field,
},
"AI 识图": {
"main": self.ocr_intro,
"chat": self.chat_box,
"bottom": self.input_field
},
# "文档分析": {
# "main": self.doc_analysis_intro,
# "chat": self.chat_box,
# "bottom": self.input_field,
# },
"知识库": {
"main": self.knowledge_base_home
},
"AI 表格": {
"main": self.table_intro,
"chat": self.chat_box,
"bottom": self.input_field,
},
"会议记录": {
"main": self.meeting_intro,
"chat": self.chat_box,
"bottom": self.input_field,
},
"思维导图": {
"main": self.mind_map_intro,
"chat": self.chat_box,
"bottom": self.input_field,
},
# "音频转写": {
# "main": self.audio_transcription_intro,
# "chat": self.chat_box,
# "bottom": self.input_field,
# },
"语音聊天": {
"main": self.speech_page
},
# "系统功能": {
# "main": self.sys_func_intro,
# "chat": self.chat_box,
# "bottom": self.input_field,
# },
"系统功能": { # only for demo
"main": self.chat_intro,
"chat": self.chat_box,
"bottom": self.input_field
},
}
# mode setting
self.mode = AssistantMode.CHAT
self.current_model = "DeepSeek-V3"
self.current_input = None # string
self.current_bubble_message = None
self.current_func = "智能助手"
# knowledge base setting
self.selected_kb_id_list = []
# system function setting
self.chain_step = 1
self.tool_result = []
def func_init(self):
self.serverCheck = ServerCheck()
self.serverCheck.internet_change_signal.connect(self.internet_change_handle)
self.serverCheck.start()
self.waitingMessage = BubbleMessage('', '', MessageType.WAITING, 12, user_send=False)
self.waitingMeetingMessage = BubbleMessage('', '', MessageType.WAITING, 12, user_send=False)
self.sendTask = ChatTask()
self.sendTask.complete_signal.connect(self.ai_callback)
self.sendTask.update_signal.connect(self.update_ai_message)
self.localAppTask = OpenLocalAppTask()
self.speechTask = SpeechTask()
self.speechTask.set_callback(self.speechTaskCallback)
self.voiceInput = VoiceRecognition()
self.voiceInput.VoiceRecognitionSignal.connect(self.receive_voice_message)
# self.voiceInput.VoiceRecognitionSignal.connect(self.handle_send_voice_message)
self.voice_message = ""
self.last_voice_message = ""
self.aiDrawingTask = AiDrawingTask()
self.aiDrawingTask.complete_signal.connect(self.complete_drawing_handle)
self.meetingTask = MeetingTask()
self.meetingTask.message_ready_signal.connect(self.meeting_message_ready_handle)
self.meetingTask.meeting_status_signal.connect(self.meeting_service_status_handle)
self.timer = QTimer(self) # 定义定时器
self.timer.timeout.connect(self.start_meeting) # 定时器信号连接到updateImage方法
self.timerSend = QTimer(self)
self.timerSend.timeout.connect(self.listeningToWaiting) # "聆听中"到"等待中"的自动转换
# --- Agent Controller Setup ---
self.agent_thread = QThread()
self.agent_controller = AgentController(self.current_model)
self.agent_controller.moveToThread(self.agent_thread)
# A dictionary to hold references to workflow step widgets
self.workflow_step_widgets = {}
self.current_workflow_container = None
# Connect AgentController signals
# For simple chats (fallback)
self.agent_controller.normal_update_signal.connect(self.update_ai_message)
self.agent_controller.normal_finished_signal.connect(self.ai_callback)
# For complex workflows
self.agent_controller.workflow_step_started.connect(self.handle_workflow_step_started)
self.agent_controller.workflow_step_finished.connect(self.handle_workflow_step_finished)
self.agent_controller.finished_signal.connect(self.handle_agent_finished)
self.agent_controller.error_signal.connect(self.handle_agent_error)
self.agent_thread.start()
def handle_function_selection(self, function_name):
"""集中处理功能切换时的UI"""
self.sendTask.stop_flag = True
self.agent_thread.quit()
if function_name == "智能助手": # only for demo
function_name = "系统功能"
config = self.page_mapping.get(function_name)
if function_name != "知识库":
self.title_change_requested.emit(function_name)
# self.parent().change_title_midlabel(function_name)
self.current_func = function_name
self.model_select_btn.set_current_model("DeepSeek-V3")
if config:
if function_name == "知识库":
main_widget = config["main"]
main_widget.show()
elif function_name == "语音聊天":
main_widget = config["main"]
main_widget.show()
if self.contentStackWgt.indexOf(main_widget) == -1:
self.contentStackWgt.addWidget(main_widget)
self.contentStackWgt.setCurrentWidget(main_widget)
else:
# 主显示区域操作
main_widget = config["main"]
main_widget.show()
if self.contentStackWgt.indexOf(main_widget) == -1:
self.contentStackWgt.addWidget(main_widget)
self.contentStackWgt.setCurrentWidget(main_widget)
chat_widget = config["chat"] # 获取chat组件
if chat_widget: # 如果存在chat组件
if self.contentStackWgt.indexOf(chat_widget) == -1: # 检查是否已添加
self.contentStackWgt.addWidget(chat_widget)
# 底部输入区域操作
bottom_widget = config["bottom"]
bottom_widget.show()
self.new_dialog_btn.show()
self.knowledge_base_select_btn.show()
self.model_select_btn.show()
self.function_menu_btn.show()
bottom_widget.init_status()
if function_name == '语种翻译':
bottom_widget.show_language_select_layout(bottom_widget.language_layout)
else:
bottom_widget.hide_language_select_layout(bottom_widget.language_layout)
if self.bottomStackWgt.indexOf(bottom_widget) == -1: # 假设存在底部堆栈布局
self.bottomStackWgt.addWidget(bottom_widget)
self.bottomStackWgt.setCurrentWidget(bottom_widget)
# 统一的初始化接口
if hasattr(main_widget, "switch_init"):
main_widget.switch_init(function_name)
if hasattr(chat_widget, "switch_init"):
chat_widget.switch_init(function_name)
if hasattr(bottom_widget, "switch_init"):
bottom_widget.switch_init(function_name)
self.function_menu_btn.show()
self.new_dialog_btn.show()
self.switch_init(function_name)
print(f"成功切换到:{function_name}(主组件+底部输入)")
else:
print(f"未定义的功能:{function_name}")
def switch_init(self, function_name):
if self.sendTask.isRunning():
self.sendTask.stop()
self.show_waiting_message(False)
self.current_bubble_message = None
self.input_field.set_send_button_status(True)
match function_name:
case "智能助手":
self.mode = AssistantMode.CHAT
self.sendTask = ChatTask()
case "AI PPT":
self.mode = AssistantMode.CHAT
self.sendTask = PPTTask()
self.pgen = PPTGenerator()
self.pgen.pptgen_complete_signal.connect(self.handle_gen_ppt)
case "AI 表格":
self.mode = AssistantMode.CHAT
self.sendTask = TableTask()
case "AI 识图":
self.mode = AssistantMode.CHAT
self.sendTask = OcrTask()
case "会议记录":
self.mode = AssistantMode.CHAT
self.sendTask = AudioTask()
case "语种翻译":
self.mode = AssistantMode.CHAT
self.sendTask = TranslateTask()
case "思维导图":
self.mode = AssistantMode.CHAT
self.sendTask = MapTask()
# case "音频转写":
# self.mode = AssistantMode.CHAT
# self.sendTask = TranscriptionTask()
# case "文档分析":
# self.mode = AssistantMode.CHAT
# self.sendTask = DocumentTask()
case _: # 【增加一个默认case】
if not hasattr(self, 'sendTask') or not isinstance(self.sendTask, ChatTask):
self.sendTask = ChatTask()
self.sendTask.assistant.set_selected_kb(self.selected_kb_id_list)
self.sendTask.complete_signal.connect(self.ai_callback)
self.sendTask.update_signal.connect(self.update_ai_message)
def show_waiting_message(self, show): # 总是和BubbleMessage成对出现,send为True则显示,否则隐藏,待优化
if show:
if sip.isdeleted(self.waitingMessage):
self.waitingMessage = BubbleMessage('', '', MessageType.WAITING, 12, user_send=False)
self.chat_box.add_message_item(self.waitingMessage)
self.waitingMessage.show()
else:
self.chat_box.remove_message_item(self.waitingMessage)
self.waitingMessage.hide()
self.input_field.set_send_button_status(False)
# Send Start
def update_ai_message(self, result):
"""更新AI消息内容,实现流式效果"""
self.input_field.set_send_button_status(False)
# 如果等待消息还在显示,先隐藏它
if self.waitingMessage.isVisible():
self.show_waiting_message(False)
# 检查是否已经创建了BubbleMessage
if not hasattr(self, 'current_bubble_message') or self.current_bubble_message is None:
# 创建新的BubbleMessage
if self.current_func == "AI 表格":
self.current_bubble_message = BubbleMessage(result, '', msg_type=MessageType.TABLE, font_size=12, user_send=False)
elif self.current_func == "AI 绘画":
self.current_bubble_message = BubbleMessage(result, '', msg_type=MessageType.IMAGE, font_size=12, user_send=False)
elif self.current_func == "系统功能":
self.current_bubble_message = BubbleMessage(result, '', msg_type=MessageType.TEXT, font_size=12, user_send=False)
else:
self.current_bubble_message = BubbleMessage(result, '', msg_type=MessageType.TEXT, font_size=12, user_send=False)
self.speech_page.updateStatus('应答中')
self.current_bubble_message.speech_signal.connect(self.handle_speech)
self.chat_box.add_message_item(self.current_bubble_message)
else:
# 更新现有BubbleMessage的内容
self.current_bubble_message.message.update_text(result)
def ai_callback(self, result):
"""处理AI响应完成信号"""
# 确保等待消息已隐藏
self.show_waiting_message(False)
bubble_message = None
# 如果已经有BubbleMessage,更新它
if hasattr(self, 'current_bubble_message') and self.current_bubble_message is not None:
# 更新最终内容
self.current_bubble_message.message.update_text(result)
# BubbleMessage功能按钮启用
self.current_bubble_message.update_button_status(True)
bubble_message = self.current_bubble_message
else:
# 如果没有BubbleMessage,创建一个新的BubbleMessage
if self.current_func == "AI 表格":
bubble_message = BubbleMessage(result, '', msg_type=MessageType.TABLE, font_size=12, user_send=False)
elif self.current_func == "AI 绘画":
bubble_message = BubbleMessage(result, '', msg_type=MessageType.IMAGE, font_size=12, user_send=False)
elif self.current_func == "系统功能":
self.current_bubble_message = BubbleMessage(result, '', msg_type=MessageType.TEXT, font_size=12, need_button=False, user_send=False)
else:
bubble_message = BubbleMessage(result, '', msg_type=MessageType.TEXT, font_size=12, user_send=False)
self.speech_page.updateStatus('应答中')
bubble_message.speech_signal.connect(self.handle_speech)
self.chat_box.add_message_item(bubble_message)
bubble_message.update_button_status(True)
if self.current_func == "AI 表格":
pattern = r"table>>(.*?)<<table"
match = re.search(pattern, result, re.DOTALL)
if match:
content = match.group(1).strip()
table_path = generate_table(content)
thumbnail_message = ThumbnailMessage(user_send=False, file_path=table_path)
self.chat_box.scrollArea.reset_auto_scroll()
self.chat_box.add_message_item(thumbnail_message)
else:
report_path = generate_analyst_report(result.strip())
thumbnail_message = ThumbnailMessage(user_send=False, file_path=report_path)
self.chat_box.scrollArea.reset_auto_scroll()
self.chat_box.add_message_item(thumbnail_message)
if self.current_func == "思维导图":
base_name="思维导图"
mindmap = self.sendTask.mind_map.parse_outline(text=result)
timestamp = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
file_name = f"{base_name}_{timestamp}.xmind"
full_path = self.sendTask.mind_map.build_xmind(mindmap,file_name)
if full_path is not None:
thumbnail_message = ThumbnailMessage(user_send=False,file_path =full_path)
print(full_path)
self.chat_box.scrollArea.reset_auto_scroll()
self.chat_box.add_message_item(thumbnail_message)
if self.current_func == "AI PPT":
base_name="AI PPT"
bubble_message = ButtonMessage("生成PPT", user_send=False)
self.chat_box.scrollArea.reset_auto_scroll()
self.chat_box.add_message_item(bubble_message)
bubble_message.button.clicked.connect(lambda: self.pgen.markdown_to_json(result))
# 重置当前BubbleMessage引用
self.current_bubble_message = None
# 启用发送按钮
self.input_field.set_send_button_status(True)
def handle_workflow_step_started(self, step_id: str, text: str):
"""Handles the start of a workflow step."""
self.show_waiting_message(False)
# 如果当前没有容器,说明这是工作流的第一步
if self.current_workflow_container is None:
# 创建一个新的容器并添加到聊天框
self.current_workflow_container = WorkflowContainerMessage()
self.chat_box.add_message_item(self.current_workflow_container)
# 在当前容器中添加一个新的步骤
step_widget = self.current_workflow_container.add_step(text)
# 存储这个步骤的引用,以便后续更新
self.workflow_step_widgets[step_id] = step_widget
def handle_workflow_step_finished(self, step_id: str, success: bool, message: str):
"""Finds an existing step widget and updates it to its 'finished' state."""
step_widget = self.workflow_step_widgets.get(step_id)
if step_widget:
step_widget.set_finished(success, message)
else:
# Fallback in case the 'start' signal was missed
print(f"Warning: Could not find a widget for step_id '{step_id}' to update.")
final_message = f"[{'SUCCESS' if success else 'FAIL'}] {message}"
# You could add a simple text bubble here as a fallback if you want
# self.add_bubble_message(final_message, user_send=False)
def handle_agent_finished(self, final_message):
"""Receives Agent successful completion signal."""
self.input_field.set_send_button_status(True)
self.current_workflow_container = None # 重置容器
# self.add_bubble_message(f"<b>✅ {final_message}</b>", user_send=False)
def handle_agent_error(self, error_message):
"""Receives Agent error signal."""
self.show_waiting_message(False)
self.add_bubble_message(f"<b>❌ Workflow Error:</b><br>{error_message}", user_send=False)
self.input_field.set_send_button_status(True)
self.current_workflow_container = None # 重置容器
def send_quest_to_ai(self, message):
self.current_input = message
self.speech_page.updateStatus('等待中')
if self.serverCheck.internet_status():
# Speech.short_text_play("好的,请稍等")
if self.current_func == "语种翻译":
if self.input_field.language_layout.itemAt(0).widget().current_language == "自动":
detected_lang = TranslateDetect.detect_language(message)
self.input_field.language_layout.itemAt(0).widget().set_current_language(detected_lang)
message = message+"[END]把用户输入翻译成"+self.input_field.language_layout.itemAt(2).widget().current_language
print("send_quest_to_ai model is" + self.sendTask.assistant.model)
self.sendTask.set_topic(message)
self.sendTask.start()
else:
bubble_message = self.add_bubble_message("服务异常", False)
bubble_message.play_button.hide()
self.input_field.set_send_button_status(True)
self.speech_page.updateStatus('休眠中')
def handle_send_message(self, user_input, full_message, thumbnail_list):
self.add_bubble_message(user_input, True, thumbnail_list=thumbnail_list)
if self.contentStackWgt.currentWidget() != self.chat_box:
self.contentStackWgt.setCurrentWidget(self.chat_box)
# 当有对话时,隐藏背景logo
self.chat_intro.hide()
if self.current_func == "系统功能":
# Show a generic waiting message immediately for better user feedback
self.show_waiting_message(True)
self.agent_thread.start()
# Start the agent controller workflow
QMetaObject.invokeMethod(self.agent_controller, "start_workflow", Qt.QueuedConnection,
Q_ARG(str, full_message))
return
# For all other functions, maintain the original logic
if self.mode == AssistantMode.CHAT or self.mode == AssistantMode.MEETING:
self.send_quest_to_ai(full_message)
elif self.mode == AssistantMode.PAINT:
self.send_paint_to_ai(full_message)
else:
self.add_bubble_message("会议纪要暂未实现", False)
def handle_send_voice_message(self, message):
if len(message) == 0:
self.speech_page.updateStatus('休眠中')
self.input_field.capture_voice_flag = False
return
self.add_bubble_message(message, True)
# 当有对话时,切换到聊天框并隐藏背景logo
if self.contentStackWgt.currentWidget() != self.chat_box:
self.contentStackWgt.setCurrentWidget(self.chat_box)
self.chat_intro.hide()
if self.mode == AssistantMode.CHAT or self.mode == AssistantMode.MEETING:
self.send_quest_to_ai(message) # AI模型检索
elif self.mode == AssistantMode.PAINT:
self.send_paint_to_ai(message) # 文字绘图
else:
print("Meeting")
def add_bubble_message(self, message, user_send, thumbnail_list=None):
self.chat_box.scrollArea.reset_auto_scroll()
if thumbnail_list is not None and len(thumbnail_list) > 0:
for thumbnail in thumbnail_list:
thumbnail_message = ThumbnailMessage(user_send=user_send,thumbnail = thumbnail)
self.chat_box.add_message_item(thumbnail_message)
bubble_message = BubbleMessage(message, '', msg_type=MessageType.TEXT, font_size=12, user_send=user_send)
self.chat_box.add_message_item(bubble_message)
self.show_waiting_message(user_send)
return bubble_message
# 新建对话处理逻辑(清空旧的ChatTask+新建新的ChatTask)
def new_dialog_handle(self):
# 清理旧的ChatTask
if self.sendTask.isRunning():
self.sendTask.stop()
self.show_waiting_message(False)
self.current_bubble_message = None
self.input_field.set_send_button_status(True)
# 创建新的ChatTask
self.sendTask = ChatTask() # 新实例化一个Assistant
self.sendTask.complete_signal.connect(self.ai_callback)
self.sendTask.update_signal.connect(self.update_ai_message)
# 重置界面
self.workflow_step_widgets.clear()
self.current_workflow_container = None
self.chat_box.clearLayout()
self.input_field.show()
self.chat_intro.show()
self.shift2home_page()
self.model_select_btn.set_current_model("DeepSeek-V3")
self.voice_message = ""
logging.info(f'新建对话会话ID:{self.sendTask.assistant.session_id}')
def update_selected_kb(self, selected_kb_id_list):
self.selected_kb_id_list = selected_kb_id_list
self.sendTask.assistant.set_selected_kb(selected_kb_id_list)
def delete_index_history(self, con):
# TODO
# if hasattr(self, "sendTask") and self.sendTask is not None:
# success = self.sendTask.assistant.model_manager.remove_session_history_by_content( self.sendTask.assistant.session_id,con)
# if success:
# print(f"已成功删除 sendTask 历史记录中{con}")
# else:
# print(f"删除 sendTask 历史记录失败,{con} 不存在")
return
# Send End
def set_chat_mode(self):
print("Chat mode now.")
self.mode = AssistantMode.CHAT
def set_draw_mode(self):
print("Paint mode now.")
self.mode = AssistantMode.PAINT
def set_meeting_mode(self):
print("Meeting mode now.")
self.mode = AssistantMode.MEETING
def shift2home_page(self):
self.set_chat_mode()
self.handle_function_selection("智能助手")
# Speech Start
def listeningToWaiting(self): # 聆听中到等待中的自动转换函数
if self.voice_message != self.last_voice_message:
self.last_voice_message = self.voice_message
else:
self.speech_page.capture_voice()
# self.speech_page.updateStatus('等待中')
def internet_change_handle(self, status):
if status:
self.input_field.set_microphone_button_status(True)
else:
self.input_field.set_microphone_button_status(False)
def handle_speech(self, flag, message, bubble_message):
if not flag:
self.speechTask.stop_speech()
else:
self.speechTask.set_message(message, bubble_message)
self.speechTask.start()
def stop_speech_signal_exchange(self):
self.speechTask.stop_speech()
def speechTaskCallback(self, bubble_message):
print("speechTaskCallback")
bubble_message.playComplete()
self.speech_page.updateStatus('休眠中')
# Speech End
# Voice Start
def receive_voice_message(self, message):
self.voice_message += message
self.input_field.set_input_text(self.voice_message)
def handle_capture_voice(self):
print("handle_capture_voice")
if self.input_field.capture_voice_flag:
self.input_field.capture_voice_flag = False
self.voiceInput.stop_recognition()
self.handle_send_voice_message(self.voice_message)
self.timerSend.stop()
self.voice_message = ""
self.input_field.set_input_text("")
else:
self.input_field.capture_voice_flag = True
self.voiceInput.start()
self.timerSend.start(3000)
self.voice_message = ""
self.input_field.set_input_text("")
# Voice End
# Drawing Start
def send_paint_to_ai(self, message):
if self.serverCheck.internet_status():
# 文字绘图
self.speechTask.set_message("好的,请稍等")
self.speechTask.start()
self.aiDrawingTask.set_prompt(message)
self.aiDrawingTask.start()
self.aiDrawingTask.start_timer(30000)
else:
bubble_message = self.add_bubble_message("无法连接互联网服务,请检查网络", False)
bubble_message.play_button.hide()
self.input_field.set_send_button_status(True)
def complete_drawing_handle(self, file_path):
self.show_waiting_message(False)
if "fail:" in file_path:
bubble_message = BubbleMessage(file_path, '', msg_type=MessageType.TEXT, font_size=12, user_send=False)
self.chat_box.add_message_item(bubble_message)
bubble_message.play_button.hide()
else:
bubble_message = BubbleMessage(file_path, '', msg_type=MessageType.IMAGE, font_size=12, user_send=False)
self.chat_box.add_message_item(bubble_message)
Speech.short_text_play("绘图完成")
self.input_field.set_send_button_status(True)
# Drawing End
# 会议功能 start
def show_meeting_waiting_message(self, show): # 总是和BubbleMessage成对出现,send为True则显示,否则隐藏,待优化
if show:
self.meetingWgt.add_message_item(self.waitingMeetingMessage)
self.waitingMeetingMessage.show()
else:
self.meetingWgt.remove_message_item(self.waitingMeetingMessage)
self.waitingMeetingMessage.hide()
def meeting_service_status_handle(self, status):
print(status)
message = status
self.meetingTask.status = "StartFail"
self.show_meeting_waiting_message(False)
if "Timeout" in status and "https://tingwu.cn-beijing.aliyuncs.com" in status:
message = "连接服务器超时,请检查网络环境..."
if "Failed to connect to host or proxy" in status:
message = "连接服务器失败,请检查网络环境..."
if "create transcriber request fail." in status:
message = "创建会议记录请求失败..."
if "transcriber request start fail." in status:
message = "会议记录请求启动失败..."
if "transcriber request start fail." in status:
message = "会议记录请求启动超时..."
if "open default PCM device fail." in status:
message = "打开语音输入设备失败..."
if "send audio fail" in status:
message = "发送音频流失败,请检查网络环境..."
bubble_message = BubbleMessage(message, '', msg_type=MessageType.TEXT, font_size=12, user_send=False)
bubble_message.message.meeting_content_adjust_size()
self.meetingWgt.add_message_item(bubble_message)
bubble_message.play_button.hide()
self.meeting_bottom_ui.meeting_switch_handel()
def meeting_message_ready_handle(self, message):
self.show_meeting_waiting_message(False)
if "StartFail" in self.meetingTask.status:
self.meetingTask.status = ""
return
else:
bubble_message = BubbleMessage(message, '', msg_type=MessageType.TEXT, font_size=12, user_send=False)
# bubble_message.speech_signal.connect(self.handle_speech)
bubble_message.message.meeting_content_adjust_size()
self.meetingWgt.add_message_item(bubble_message)
bubble_message.play_button.hide()
self.show_meeting_waiting_message(True)
if "您已结束会议,会议纪要文件" not in message:
MeetingFile.write_temp_file(message)
def start_meeting(self):
self.meetingTask.start()
self.meetingTask.start_task()
self.timer.stop()
def meeting_signal_handle(self, status):
if status:
self.meetingTask.close_bin_process()
self.meetingTask.exec_bin_process()
self.timer.start(500)
self.show_meeting_waiting_message(True)
else:
self.meetingTask.stop_task()
self.show_meeting_waiting_message(False)
# 会议功能 End
def set_websearch_enabled(self, enabled):
if hasattr(self, 'sendTask'):
self.sendTask.set_websearch_enabled(enabled)
def switch_model(self, model):
"""传入模型名,如Qwen-Max、DeepSeek-V3,非中文名"""
self.current_model = model
self.agent_controller.switch_model(model)
self.sendTask.assistant.switch_model(model)
def handle_gen_ppt(self,full_path):
thumbnail_message = ThumbnailMessage(user_send=False,file_path =full_path)
self.chat_box.scrollArea.reset_auto_scroll()
self.chat_box.add_message_item(thumbnail_message)
# 菜单
class SettingMenu(QMenu):
def __init__(self, *args, **kwargs):
super(SettingMenu, self).__init__()
self.radius = 10
self.setting_menu_sytle = '''
QMenu {{
/* 半透明效果 */
border-radius: {radius};
border: 2px solid rgb(255, 255, 255);
background-color: rgba(255, 255, 255, 230);
}}
QMenu::item {{
border-radius: {radius};
/* 这个距离很麻烦需要根据菜单的长度和图标等因素微调 */
padding: 8px 48px 8px 12px; /* 12px是文字距离左侧距离*/
background-color: transparent;
}}
/* 鼠标悬停和按下效果 */
QMenu::item:selected {{
border-radius: {radius};
/* 半透明效果 */
background-color: rgba(230, 240, 255, 232);
}}
/* 禁用效果 */
QMenu::item:disabled {{
border-radius: {radius};
background-color: transparent;
}}
/* 图标距离左侧距离 */
QMenu::icon {{
left: 15px;
}}
/* 分割线效果 */
QMenu::separator {{
height: 1px;
background-color: rgb(232, 236, 243);
}}'''.format(radius=self.radius)
self.setStyleSheet(self.setting_menu_sytle)
self.setAttribute(Qt.WA_TranslucentBackground, True)
self.setWindowFlags(self.windowFlags() | Qt.FramelessWindowHint)
def setSubMenu(self):
self.sub_menu.setStyleSheet(self.setting_menu_sytle)
self.sub_menu.setAttribute(Qt.WA_TranslucentBackground, True)
self.setWindowFlags(self.windowFlags() | Qt.FramelessWindowHint)
# 标题栏
class MainWinTitle(QWidget):
def __init__(self, title_height, parent=None):
super(MainWinTitle, self).__init__(parent)
self.title_height = title_height
self.init_ui()
def init_ui(self):
self.setFixedHeight(self.title_height)
self.setObjectName('title')
self.setStyleSheet(
'''
#title{
border-top-left-radius: 5px;
border-top-right-radius: 5px;
background:transparent;
}
'''
)
self.setAttribute(Qt.WA_StyledBackground)
self.installEventFilter(self)
self.icon = QLabel(self)
self.icon.setFixedSize(24, 24)
self.icon.setScaledContents(True)
self.icon.setPixmap(QPixmap(ConfigManager().app_config['logo']))
self.icon.mousePressEvent = lambda event: self.parent().icon_clicked_event()
self.title_name = QLabel(f"{ConfigManager().app_config['name']}")
self.title_name.mousePressEvent = lambda event: self.parent().icon_clicked_event()
font = QFont("Microsoft YaHei", 16)
font.setWeight(QFont.Bold)
self.title_name.setFont(font)
self.title_name.setStyleSheet(
'''
color:white;
'''
)
self.title_layout = QHBoxLayout(self)
self.title_layout.setContentsMargins(18, 12, 18, 0)
self.title_layout.addWidget(self.icon)
self.title_layout.addWidget(self.title_name)
self.title_layout.addItem(QSpacerItem(40, 10, QSizePolicy.Expanding, QSizePolicy.Minimum))
def switchViewType(self, mode: ViewMode):
"""根据显示模式调整标题栏外观"""
if mode == ViewMode.SIDEBAR:
# --- 侧边栏模式:显示自定义标题栏 ---
self.show()
self.setFixedHeight(self.title_height)
self.title_layout.setContentsMargins(18, 12, 18, 0)
self.setStyleSheet(
'''
#title{
border-top-left-radius: 5px;
border-top-right-radius: 5px;
background:transparent;
}
'''
)
self.icon.setFixedSize(24, 24)
self.icon.setPixmap(QPixmap(ConfigManager().app_config['logo']))
self.icon.setScaledContents(True)
self.title_name.setStyleSheet(