cnhkmcp 2.1.2__py3-none-any.whl → 2.1.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. {cnhkmcp-2.1.2.dist-info → cnhkmcp-2.1.3.dist-info}/METADATA +1 -1
  2. cnhkmcp-2.1.3.dist-info/RECORD +6 -0
  3. cnhkmcp-2.1.3.dist-info/top_level.txt +1 -0
  4. cnhkmcp/__init__.py +0 -125
  5. cnhkmcp/untracked/AI/321/206/320/261/320/234/321/211/320/255/320/262/321/206/320/237/320/242/321/204/342/225/227/342/225/242/README.md +0 -38
  6. cnhkmcp/untracked/AI/321/206/320/261/320/234/321/211/320/255/320/262/321/206/320/237/320/242/321/204/342/225/227/342/225/242/ace.log +0 -0
  7. cnhkmcp/untracked/AI/321/206/320/261/320/234/321/211/320/255/320/262/321/206/320/237/320/242/321/204/342/225/227/342/225/242/config.json +0 -6
  8. cnhkmcp/untracked/AI/321/206/320/261/320/234/321/211/320/255/320/262/321/206/320/237/320/242/321/204/342/225/227/342/225/242/get_knowledgeBase_tool/ace_lib.py +0 -1510
  9. cnhkmcp/untracked/AI/321/206/320/261/320/234/321/211/320/255/320/262/321/206/320/237/320/242/321/204/342/225/227/342/225/242/get_knowledgeBase_tool/fetch_all_datasets.py +0 -157
  10. cnhkmcp/untracked/AI/321/206/320/261/320/234/321/211/320/255/320/262/321/206/320/237/320/242/321/204/342/225/227/342/225/242/get_knowledgeBase_tool/fetch_all_documentation.py +0 -132
  11. cnhkmcp/untracked/AI/321/206/320/261/320/234/321/211/320/255/320/262/321/206/320/237/320/242/321/204/342/225/227/342/225/242/get_knowledgeBase_tool/fetch_all_operators.py +0 -99
  12. cnhkmcp/untracked/AI/321/206/320/261/320/234/321/211/320/255/320/262/321/206/320/237/320/242/321/204/342/225/227/342/225/242/get_knowledgeBase_tool/helpful_functions.py +0 -180
  13. cnhkmcp/untracked/AI/321/206/320/261/320/234/321/211/320/255/320/262/321/206/320/237/320/242/321/204/342/225/227/342/225/242/icon.ico +0 -0
  14. cnhkmcp/untracked/AI/321/206/320/261/320/234/321/211/320/255/320/262/321/206/320/237/320/242/321/204/342/225/227/342/225/242/icon.png +0 -0
  15. cnhkmcp/untracked/AI/321/206/320/261/320/234/321/211/320/255/320/262/321/206/320/237/320/242/321/204/342/225/227/342/225/242/knowledge/test.txt +0 -1
  16. cnhkmcp/untracked/AI/321/206/320/261/320/234/321/211/320/255/320/262/321/206/320/237/320/242/321/204/342/225/227/342/225/242/main.py +0 -576
  17. cnhkmcp/untracked/AI/321/206/320/261/320/234/321/211/320/255/320/262/321/206/320/237/320/242/321/204/342/225/227/342/225/242/process_knowledge_base.py +0 -281
  18. cnhkmcp/untracked/AI/321/206/320/261/320/234/321/211/320/255/320/262/321/206/320/237/320/242/321/204/342/225/227/342/225/242/rag_engine.py +0 -408
  19. cnhkmcp/untracked/AI/321/206/320/261/320/234/321/211/320/255/320/262/321/206/320/237/320/242/321/204/342/225/227/342/225/242/requirements.txt +0 -7
  20. cnhkmcp/untracked/AI/321/206/320/261/320/234/321/211/320/255/320/262/321/206/320/237/320/242/321/204/342/225/227/342/225/242/run.bat +0 -3
  21. cnhkmcp/untracked/AI/321/206/320/261/320/234/321/211/320/255/320/262/321/206/320/237/320/242/321/204/342/225/227/342/225/242//321/211/320/266/320/246/321/206/320/274/320/261/321/210/342/224/220/320/240/321/210/320/261/320/234/321/206/320/231/320/243/321/205/342/225/235/320/220/321/206/320/230/320/241.py +0 -265
  22. cnhkmcp/untracked/APP/.gitignore +0 -32
  23. cnhkmcp/untracked/APP/MODULAR_STRUCTURE.md +0 -112
  24. cnhkmcp/untracked/APP/README.md +0 -309
  25. cnhkmcp/untracked/APP/Tranformer/Transformer.py +0 -4985
  26. cnhkmcp/untracked/APP/Tranformer/ace.log +0 -0
  27. cnhkmcp/untracked/APP/Tranformer/ace_lib.py +0 -1510
  28. cnhkmcp/untracked/APP/Tranformer/helpful_functions.py +0 -180
  29. cnhkmcp/untracked/APP/Tranformer/output/Alpha_candidates.json +0 -2421
  30. cnhkmcp/untracked/APP/Tranformer/output/Alpha_candidates_/321/207/320/264/342/225/221/321/204/342/225/233/320/233.json +0 -654
  31. cnhkmcp/untracked/APP/Tranformer/output/Alpha_generated_expressions_error.json +0 -1034
  32. cnhkmcp/untracked/APP/Tranformer/output/Alpha_generated_expressions_success.json +0 -444
  33. cnhkmcp/untracked/APP/Tranformer/output/Alpha_generated_expressions_/321/207/320/264/342/225/221/321/204/342/225/233/320/233/321/205/320/237/320/277/321/207/320/253/342/224/244/321/206/320/236/320/265/321/210/342/225/234/342/225/234/321/205/320/225/320/265Machine_lib.json +0 -22
  34. cnhkmcp/untracked/APP/Tranformer/parsetab.py +0 -60
  35. cnhkmcp/untracked/APP/Tranformer/template_summary.txt +0 -3182
  36. cnhkmcp/untracked/APP/Tranformer/transformer_config.json +0 -7
  37. cnhkmcp/untracked/APP/Tranformer/validator.py +0 -889
  38. cnhkmcp/untracked/APP/ace.log +0 -69
  39. cnhkmcp/untracked/APP/ace_lib.py +0 -1510
  40. cnhkmcp/untracked/APP/blueprints/__init__.py +0 -6
  41. cnhkmcp/untracked/APP/blueprints/feature_engineering.py +0 -347
  42. cnhkmcp/untracked/APP/blueprints/idea_house.py +0 -221
  43. cnhkmcp/untracked/APP/blueprints/inspiration_house.py +0 -432
  44. cnhkmcp/untracked/APP/blueprints/paper_analysis.py +0 -570
  45. cnhkmcp/untracked/APP/custom_templates/templates.json +0 -1257
  46. cnhkmcp/untracked/APP/give_me_idea/BRAIN_Alpha_Template_Expert_SystemPrompt.md +0 -400
  47. cnhkmcp/untracked/APP/give_me_idea/ace_lib.py +0 -1510
  48. cnhkmcp/untracked/APP/give_me_idea/alpha_data_specific_template_master.py +0 -252
  49. cnhkmcp/untracked/APP/give_me_idea/fetch_all_datasets.py +0 -157
  50. cnhkmcp/untracked/APP/give_me_idea/fetch_all_operators.py +0 -99
  51. cnhkmcp/untracked/APP/give_me_idea/helpful_functions.py +0 -180
  52. cnhkmcp/untracked/APP/give_me_idea/what_is_Alpha_template.md +0 -11
  53. cnhkmcp/untracked/APP/helpful_functions.py +0 -180
  54. cnhkmcp/untracked/APP/hkSimulator/ace_lib.py +0 -1497
  55. cnhkmcp/untracked/APP/hkSimulator/autosimulator.py +0 -447
  56. cnhkmcp/untracked/APP/hkSimulator/helpful_functions.py +0 -180
  57. cnhkmcp/untracked/APP/mirror_config.txt +0 -20
  58. cnhkmcp/untracked/APP/operaters.csv +0 -129
  59. cnhkmcp/untracked/APP/requirements.txt +0 -53
  60. cnhkmcp/untracked/APP/run_app.bat +0 -28
  61. cnhkmcp/untracked/APP/run_app.sh +0 -34
  62. cnhkmcp/untracked/APP/setup_tsinghua.bat +0 -39
  63. cnhkmcp/untracked/APP/setup_tsinghua.sh +0 -43
  64. cnhkmcp/untracked/APP/simulator/alpha_submitter.py +0 -404
  65. cnhkmcp/untracked/APP/simulator/simulator_wqb.py +0 -618
  66. cnhkmcp/untracked/APP/ssrn-3332513.pdf +6 -109201
  67. cnhkmcp/untracked/APP/static/brain.js +0 -589
  68. cnhkmcp/untracked/APP/static/decoder.js +0 -1540
  69. cnhkmcp/untracked/APP/static/feature_engineering.js +0 -1729
  70. cnhkmcp/untracked/APP/static/idea_house.js +0 -937
  71. cnhkmcp/untracked/APP/static/inspiration.js +0 -465
  72. cnhkmcp/untracked/APP/static/inspiration_house.js +0 -868
  73. cnhkmcp/untracked/APP/static/paper_analysis.js +0 -390
  74. cnhkmcp/untracked/APP/static/script.js +0 -3082
  75. cnhkmcp/untracked/APP/static/simulator.js +0 -597
  76. cnhkmcp/untracked/APP/static/styles.css +0 -3127
  77. cnhkmcp/untracked/APP/static/usage_widget.js +0 -508
  78. cnhkmcp/untracked/APP/templates/alpha_inspector.html +0 -511
  79. cnhkmcp/untracked/APP/templates/feature_engineering.html +0 -960
  80. cnhkmcp/untracked/APP/templates/idea_house.html +0 -564
  81. cnhkmcp/untracked/APP/templates/index.html +0 -932
  82. cnhkmcp/untracked/APP/templates/inspiration_house.html +0 -861
  83. cnhkmcp/untracked/APP/templates/paper_analysis.html +0 -91
  84. cnhkmcp/untracked/APP/templates/simulator.html +0 -343
  85. cnhkmcp/untracked/APP/templates/transformer_web.html +0 -580
  86. cnhkmcp/untracked/APP/usage.md +0 -351
  87. cnhkmcp/untracked/APP//321/207/342/225/235/320/250/321/205/320/230/320/226/321/204/342/225/225/320/220/321/211/320/221/320/243/321/206/320/261/320/265/ace_lib.py +0 -1510
  88. cnhkmcp/untracked/APP//321/207/342/225/235/320/250/321/205/320/230/320/226/321/204/342/225/225/320/220/321/211/320/221/320/243/321/206/320/261/320/265/brain_alpha_inspector.py +0 -712
  89. cnhkmcp/untracked/APP//321/207/342/225/235/320/250/321/205/320/230/320/226/321/204/342/225/225/320/220/321/211/320/221/320/243/321/206/320/261/320/265/helpful_functions.py +0 -180
  90. cnhkmcp/untracked/APP//321/210/342/224/220/320/240/321/210/320/261/320/234/321/206/320/231/320/243/321/205/342/225/235/320/220/321/206/320/230/320/241.py +0 -2456
  91. cnhkmcp/untracked/arXiv_API_Tool_Manual.md +0 -490
  92. cnhkmcp/untracked/arxiv_api.py +0 -229
  93. cnhkmcp/untracked/forum_functions.py +0 -998
  94. cnhkmcp/untracked/mcp/321/206/320/246/320/227/321/204/342/225/227/342/225/242/321/210/320/276/342/225/221/321/205/320/255/320/253/321/207/320/231/320/2302_/321/205/320/266/320/222/321/206/320/256/320/254/321/205/320/236/320/257/321/207/320/231/320/230/321/205/320/240/320/277/321/205/320/232/320/270/321/204/342/225/225/320/235/321/204/342/225/221/320/226/321/206/342/225/241/320/237/321/210/320/267/320/230/321/205/320/251/320/270/321/205/342/226/221/342/226/222/321/210/320/277/320/245/321/210/342/224/220/320/251/321/204/342/225/225/320/272/forum_functions.py +0 -407
  95. cnhkmcp/untracked/mcp/321/206/320/246/320/227/321/204/342/225/227/342/225/242/321/210/320/276/342/225/221/321/205/320/255/320/253/321/207/320/231/320/2302_/321/205/320/266/320/222/321/206/320/256/320/254/321/205/320/236/320/257/321/207/320/231/320/230/321/205/320/240/320/277/321/205/320/232/320/270/321/204/342/225/225/320/235/321/204/342/225/221/320/226/321/206/342/225/241/320/237/321/210/320/267/320/230/321/205/320/251/320/270/321/205/342/226/221/342/226/222/321/210/320/277/320/245/321/210/342/224/220/320/251/321/204/342/225/225/320/272/platform_functions.py +0 -2415
  96. cnhkmcp/untracked/mcp/321/206/320/246/320/227/321/204/342/225/227/342/225/242/321/210/320/276/342/225/221/321/205/320/255/320/253/321/207/320/231/320/2302_/321/205/320/266/320/222/321/206/320/256/320/254/321/205/320/236/320/257/321/207/320/231/320/230/321/205/320/240/320/277/321/205/320/232/320/270/321/204/342/225/225/320/235/321/204/342/225/221/320/226/321/206/342/225/241/320/237/321/210/320/267/320/230/321/205/320/251/320/270/321/205/342/226/221/342/226/222/321/210/320/277/320/245/321/210/342/224/220/320/251/321/204/342/225/225/320/272/user_config.json +0 -31
  97. cnhkmcp/untracked/mcp/321/206/320/246/320/227/321/204/342/225/227/342/225/242/321/210/320/276/342/225/221/321/205/320/255/320/253/321/207/320/231/320/2302_/321/205/320/266/320/222/321/206/320/256/320/254/321/205/320/236/320/257/321/207/320/231/320/230/321/205/320/240/320/277/321/205/320/232/320/270/321/204/342/225/225/320/235/321/204/342/225/221/320/226/321/206/342/225/241/320/237/321/210/320/267/320/230/321/205/320/251/320/270/321/205/342/226/221/342/226/222/321/210/320/277/320/245/321/210/342/224/220/320/251/321/204/342/225/225/320/272//321/210/320/276/320/271AI/321/210/320/277/342/225/227/321/210/342/224/220/320/251/321/204/342/225/225/320/272/321/206/320/246/320/227/321/206/320/261/320/263/321/206/320/255/320/265/321/205/320/275/320/266/321/204/342/225/235/320/252/321/204/342/225/225/320/233/321/210/342/225/234/342/225/234/321/206/342/225/241/320/237/321/210/320/267/320/230/321/205/320/251/320/270.md +0 -101
  98. cnhkmcp/untracked/mcp/321/206/320/246/320/227/321/204/342/225/227/342/225/242/321/210/320/276/342/225/221/321/205/320/255/320/253/321/207/320/231/320/2302_/321/205/320/266/320/222/321/206/320/256/320/254/321/205/320/236/320/257/321/207/320/231/320/230/321/205/320/240/320/277/321/205/320/232/320/270/321/204/342/225/225/320/235/321/204/342/225/221/320/226/321/206/342/225/241/320/237/321/210/320/267/320/230/321/205/320/251/320/270/321/205/342/226/221/342/226/222/321/210/320/277/320/245/321/210/342/224/220/320/251/321/204/342/225/225/320/272//321/211/320/225/320/235/321/207/342/225/234/320/276/321/205/320/231/320/235/321/210/342/224/220/320/240/321/210/320/261/320/234/321/206/320/230/320/241_/321/205/320/276/320/231/321/210/320/263/320/225/321/205/342/224/220/320/225/321/210/320/266/320/221/321/204/342/225/233/320/255/321/210/342/225/241/320/246/321/205/320/234/320/225.py +0 -190
  99. cnhkmcp/untracked/platform_functions.py +0 -2886
  100. cnhkmcp/untracked/sample_mcp_config.json +0 -11
  101. cnhkmcp/untracked/user_config.json +0 -31
  102. cnhkmcp/untracked//321/207/320/264/342/225/221/321/204/342/225/233/320/233/321/205/320/237/320/222/321/210/320/220/320/223/321/206/320/246/320/227/321/206/320/261/320/263_BRAIN_Alpha_Test_Requirements_and_Tips.md +0 -202
  103. cnhkmcp/untracked//321/207/320/264/342/225/221/321/204/342/225/233/320/233/321/205/342/225/226/320/265/321/204/342/225/234/320/254/321/206/342/225/241/320/221_Alpha_explaination_workflow.md +0 -56
  104. cnhkmcp/untracked//321/207/320/264/342/225/221/321/204/342/225/233/320/233/321/205/342/225/226/320/265/321/204/342/225/234/320/254/321/206/342/225/241/320/221_BRAIN_6_Tips_Datafield_Exploration_Guide.md +0 -194
  105. cnhkmcp/untracked//321/207/320/264/342/225/221/321/204/342/225/233/320/233/321/205/342/225/226/320/265/321/204/342/225/234/320/254/321/206/342/225/241/320/221_BRAIN_Alpha_Improvement_Workflow.md +0 -101
  106. cnhkmcp/untracked//321/207/320/264/342/225/221/321/204/342/225/233/320/233/321/205/342/225/226/320/265/321/204/342/225/234/320/254/321/206/342/225/241/320/221_Dataset_Exploration_Expert_Manual.md +0 -436
  107. cnhkmcp/untracked//321/207/320/264/342/225/221/321/204/342/225/233/320/233/321/205/342/225/226/320/265/321/204/342/225/234/320/254/321/206/342/225/241/320/221_daily_report_workflow.md +0 -128
  108. cnhkmcp/untracked//321/211/320/225/320/235/321/207/342/225/234/320/276/321/205/320/231/320/235/321/210/342/224/220/320/240/321/210/320/261/320/234/321/206/320/230/320/241_/321/205/320/276/320/231/321/210/320/263/320/225/321/205/342/224/220/320/225/321/210/320/266/320/221/321/204/342/225/233/320/255/321/210/342/225/241/320/246/321/205/320/234/320/225.py +0 -190
  109. cnhkmcp-2.1.2.dist-info/RECORD +0 -111
  110. cnhkmcp-2.1.2.dist-info/top_level.txt +0 -1
  111. {cnhkmcp-2.1.2.dist-info → cnhkmcp-2.1.3.dist-info}/WHEEL +0 -0
  112. {cnhkmcp-2.1.2.dist-info → cnhkmcp-2.1.3.dist-info}/entry_points.txt +0 -0
  113. {cnhkmcp-2.1.2.dist-info → cnhkmcp-2.1.3.dist-info}/licenses/LICENSE +0 -0
@@ -1,576 +0,0 @@
1
- import os
2
- import json
3
- import base64
4
- import tkinter as tk
5
- from tkinter import scrolledtext, messagebox, Toplevel
6
- from PIL import Image, ImageTk, ImageGrab
7
- from openai import OpenAI
8
- import threading
9
- import io
10
- import time
11
- import ctypes
12
- import subprocess
13
- import sys
14
-
15
- # --- Auto-Install Dependencies ---
16
- def install_dependencies():
17
- import importlib.util
18
- import importlib.metadata
19
-
20
- # Mapping of package names to their import names (if different)
21
- packages = {
22
- "openai": "openai",
23
- "Pillow": "PIL",
24
- "fastembed": "fastembed",
25
- "chromadb": "chromadb",
26
- "watchdog": "watchdog",
27
- "pypdf": "pypdf",
28
- "python-docx": "docx"
29
- }
30
-
31
- missing = []
32
- for pkg_name, import_name in packages.items():
33
- if importlib.util.find_spec(import_name) is None:
34
- missing.append(pkg_name)
35
-
36
- if missing:
37
- print(f"Missing dependencies: {missing}. Installing...")
38
- # Try Tsinghua source first
39
- tsinghua_url = "https://pypi.tuna.tsinghua.edu.cn/simple"
40
- try:
41
- print(f"Attempting to install via Tsinghua mirror: {tsinghua_url}")
42
- subprocess.check_call([sys.executable, "-m", "pip", "install", *missing, "-i", tsinghua_url])
43
- print("Dependencies installed successfully via Tsinghua.")
44
- except Exception as e:
45
- print(f"Tsinghua mirror failed, falling back to default source: {e}")
46
- try:
47
- subprocess.check_call([sys.executable, "-m", "pip", "install", *missing])
48
- print("Dependencies installed successfully via default source.")
49
- except Exception as e2:
50
- print(f"Failed to install dependencies: {e2}")
51
- messagebox.showwarning("Warning", f"Failed to auto-install some dependencies: {e2}\nPlease run 'pip install -r requirements.txt' manually.")
52
-
53
- # Run install check before other imports that might fail
54
- install_dependencies()
55
-
56
- # Now import our custom RAG engine
57
- try:
58
- from rag_engine import KnowledgeBase
59
- except ImportError:
60
- KnowledgeBase = None
61
- print("KnowledgeBase module not found or dependencies missing.")
62
-
63
- # Set DPI Awareness (Windows) to ensure high-resolution screenshots
64
- try:
65
- ctypes.windll.shcore.SetProcessDpiAwareness(1)
66
- except Exception:
67
- try:
68
- ctypes.windll.user32.SetProcessDPIAware()
69
- except Exception:
70
- pass
71
-
72
- # Load Configuration
73
- CONFIG_PATH = os.path.join(os.path.dirname(__file__), 'config.json')
74
- ICON_PATH = os.path.join(os.path.dirname(__file__), 'icon.png')
75
-
76
- def load_config():
77
- if not os.path.exists(CONFIG_PATH):
78
- messagebox.showerror("Error", "Config file not found!")
79
- return None
80
- with open(CONFIG_PATH, 'r', encoding='utf-8') as f:
81
- return json.load(f)
82
-
83
- CONFIG = load_config()
84
-
85
- class BrainConsultantApp:
86
- def __init__(self, root):
87
- self.root = root
88
- self.root.title("BRAIN Consultant Assistant")
89
-
90
- # Floating Icon Setup
91
- self.root.overrideredirect(True) # Frameless
92
- self.root.attributes("-topmost", True) # Always on top
93
- self.root.geometry("64x64+100+100") # Small size, initial position
94
-
95
- # Transparency setup (Windows only hack)
96
- transparent_color = '#ff00ff'
97
- self.root.configure(bg=transparent_color)
98
- self.root.wm_attributes("-transparentcolor", transparent_color)
99
-
100
- # Load Icon
101
- self.icon_image = None
102
- if os.path.exists(ICON_PATH):
103
- try:
104
- # Force RGBA to ensure we can handle transparency
105
- img = Image.open(ICON_PATH).convert("RGBA")
106
- img = img.resize((64, 64), Image.Resampling.LANCZOS)
107
-
108
- # Fix for halo effect: Strict binary alpha
109
- # Any pixel that is not fully opaque becomes fully transparent
110
- # This removes the semi-transparent edges that blend with the background color
111
- datas = img.getdata()
112
- new_data = []
113
- for item in datas:
114
- if item[3] < 200: # Threshold: if alpha < 200, make it transparent
115
- new_data.append((0, 0, 0, 0))
116
- else:
117
- # Keep original color, force full opacity
118
- new_data.append((item[0], item[1], item[2], 255))
119
- img.putdata(new_data)
120
-
121
- self.icon_image = ImageTk.PhotoImage(img)
122
- # Set window icon if possible (though frameless windows don't show it usually)
123
- self.root.iconphoto(False, self.icon_image)
124
- except Exception as e:
125
- print(f"Failed to load icon: {e}")
126
-
127
- # Create a label as the button
128
- self.icon_label = tk.Label(root, image=self.icon_image, bg=transparent_color, cursor="hand2")
129
- if not self.icon_image:
130
- self.icon_label.config(text="BRAIN", fg="white", font=("Arial", 10, "bold"))
131
- self.icon_label.pack(fill=tk.BOTH, expand=True)
132
-
133
- # Bind events
134
- self.icon_label.bind("<Button-3>", self.show_context_menu) # Right click menu
135
- self.icon_label.bind("<ButtonPress-1>", self.start_move)
136
- self.icon_label.bind("<ButtonRelease-1>", self.stop_move)
137
- self.icon_label.bind("<B1-Motion>", self.do_move)
138
-
139
- # Initialize OpenAI Client
140
- self.client = OpenAI(
141
- api_key=CONFIG['api_key'],
142
- base_url=CONFIG['base_url']
143
- )
144
- self.model = CONFIG['model']
145
- self.system_prompt = CONFIG.get('system_prompt', "You are a helpful assistant.")
146
-
147
- # Initialize Knowledge Base
148
- self.kb = None
149
- if KnowledgeBase:
150
- try:
151
- self.kb = KnowledgeBase()
152
- except Exception as e:
153
- print(f"Failed to initialize Knowledge Base: {e}")
154
-
155
- self.knowledge_dir = os.path.join(os.path.dirname(__file__), "knowledge")
156
-
157
- # Last KB retrieval (for UI display)
158
- self.last_kb_query = ""
159
- self.last_kb_context = ""
160
- self.last_kb_hits = []
161
-
162
- self.current_screenshot = None
163
- self.chat_window = None
164
- self.history = [{"role": "system", "content": self.system_prompt}]
165
-
166
- # Dragging state
167
- self.x = 0
168
- self.y = 0
169
- self.dragging = False
170
-
171
- def start_move(self, event):
172
- self.x = event.x
173
- self.y = event.y
174
- self.dragging = False # Initialize as false, set to true if moved
175
-
176
- def stop_move(self, event):
177
- if not self.dragging:
178
- self.start_snip()
179
- self.dragging = False
180
-
181
- def do_move(self, event):
182
- self.dragging = True
183
- deltax = event.x - self.x
184
- deltay = event.y - self.y
185
- x = self.root.winfo_x() + deltax
186
- y = self.root.winfo_y() + deltay
187
- self.root.geometry(f"+{x}+{y}")
188
-
189
- def show_context_menu(self, event):
190
- menu = tk.Menu(self.root, tearoff=0)
191
- menu.add_command(label="💬 Chat Only", command=self.open_chat_window)
192
- menu.add_separator()
193
- menu.add_command(label="❌ Exit", command=self.root.quit)
194
- menu.post(event.x_root, event.y_root)
195
-
196
- def start_snip(self):
197
- """Hides the window and takes a screenshot after a short delay."""
198
- self.root.withdraw() # Hide main window
199
- if self.chat_window and tk.Toplevel.winfo_exists(self.chat_window):
200
- self.chat_window.withdraw()
201
- self.root.after(500, self.take_screenshot)
202
-
203
- def take_screenshot(self):
204
- """Captures the full screen."""
205
- try:
206
- # Capture full screen
207
- screenshot = ImageGrab.grab()
208
- self.current_screenshot = screenshot
209
-
210
- # Show the chat window with the screenshot
211
- self.open_chat_window(with_screenshot=True)
212
- except Exception as e:
213
- messagebox.showerror("Error", f"Failed to take screenshot: {e}")
214
- self.root.deiconify()
215
-
216
- def open_chat_window(self, with_screenshot=False):
217
- """Opens the chat interface."""
218
- if self.chat_window is None or not tk.Toplevel.winfo_exists(self.chat_window):
219
- self.chat_window = Toplevel(self.root)
220
- self.chat_window.title("BRAIN Consultant Assistant - Chat")
221
- self.chat_window.geometry("600x700")
222
- self.chat_window.configure(bg="#1e1e1e") # Dark background
223
- self.chat_window.attributes("-topmost", True) # Always on top
224
- self.chat_window.protocol("WM_DELETE_WINDOW", self.on_chat_close)
225
-
226
- if self.icon_image:
227
- self.chat_window.iconphoto(False, self.icon_image)
228
-
229
- # --- Layout Strategy: Pack Bottom-Up ---
230
-
231
- # 1. Input Area (Bottom)
232
- input_frame = tk.Frame(self.chat_window, bg="#1e1e1e")
233
- input_frame.pack(side=tk.BOTTOM, fill=tk.X, padx=10, pady=10)
234
-
235
- # Button Frame
236
- btn_frame = tk.Frame(input_frame, bg="#1e1e1e")
237
- btn_frame.pack(side=tk.BOTTOM, fill=tk.X, pady=(5, 0))
238
-
239
- # Buttons aligned to the right (visual order left→right): Open KB, KB hits, Resnip, Send
240
- send_btn = tk.Button(
241
- btn_frame,
242
- text="提问",
243
- command=self.send_message,
244
- bg="#007acc",
245
- fg="white",
246
- font=("Segoe UI", 10, "bold"),
247
- relief=tk.FLAT,
248
- padx=15
249
- )
250
- send_btn.pack(side=tk.RIGHT, padx=5)
251
-
252
- continue_snip_btn = tk.Button(
253
- btn_frame,
254
- text="📸 重新截屏",
255
- command=self.start_snip,
256
- bg="#3c3c3c",
257
- fg="white",
258
- font=("Segoe UI", 10),
259
- relief=tk.FLAT,
260
- padx=10
261
- )
262
- continue_snip_btn.pack(side=tk.RIGHT, padx=5)
263
-
264
- self.kb_hits_btn = tk.Button(
265
- btn_frame,
266
- text="📚 命中内容",
267
- command=self.show_kb_hits,
268
- bg="#3c3c3c",
269
- fg="white",
270
- font=("Segoe UI", 10),
271
- relief=tk.FLAT,
272
- padx=10,
273
- state=tk.DISABLED
274
- )
275
- self.kb_hits_btn.pack(side=tk.RIGHT, padx=5)
276
-
277
- open_kb_btn = tk.Button(
278
- btn_frame,
279
- text="📂 打开知识库",
280
- command=self.open_knowledge_folder,
281
- bg="#3c3c3c",
282
- fg="white",
283
- font=("Segoe UI", 10),
284
- relief=tk.FLAT,
285
- padx=10
286
- )
287
- open_kb_btn.pack(side=tk.RIGHT, padx=5)
288
-
289
- # Text Entry (Multi-line, Full Width)
290
- self.msg_entry = tk.Text(
291
- input_frame,
292
- height=4, # Slightly taller
293
- font=("Consolas", 11),
294
- bg="#3c3c3c",
295
- fg="white",
296
- insertbackground="white",
297
- relief=tk.FLAT,
298
- padx=5,
299
- pady=5
300
- )
301
- self.msg_entry.pack(side=tk.BOTTOM, fill=tk.X)
302
- self.msg_entry.bind("<Return>", self.handle_return)
303
- self.msg_entry.bind("<Shift-Return>", lambda e: None)
304
-
305
- # 2. Screenshot Preview (Above Input)
306
- self.image_label = tk.Label(self.chat_window, bg="#1e1e1e")
307
- self.image_label.pack(side=tk.BOTTOM, pady=5)
308
-
309
- # 3. Chat History (Top, fills remaining space)
310
- chat_frame = tk.Frame(self.chat_window, bg="#252526")
311
- chat_frame.pack(side=tk.TOP, expand=True, fill='both', padx=10, pady=10)
312
-
313
- # Chat History Display (High-tech style)
314
- self.chat_display = tk.Text(
315
- chat_frame,
316
- state='disabled',
317
- wrap=tk.WORD,
318
- bg="#252526",
319
- fg="#d4d4d4",
320
- font=("Consolas", 10),
321
- insertbackground="white",
322
- relief=tk.FLAT,
323
- padx=10,
324
- pady=10
325
- )
326
- self.chat_display.pack(side=tk.LEFT, expand=True, fill='both')
327
-
328
- self.chat_display.tag_config("user", foreground="#569cd6", font=("Consolas", 10, "bold")) # Blue
329
- self.chat_display.tag_config("assistant", foreground="#4ec9b0", font=("Consolas", 10)) # Teal
330
- self.chat_display.tag_config("system", foreground="#6a9955", font=("Consolas", 9, "italic")) # Green
331
-
332
- # Reset KB hit state for this window
333
- self.last_kb_query = ""
334
- self.last_kb_context = ""
335
- if hasattr(self, "kb_hits_btn"):
336
- self.kb_hits_btn.config(state=tk.DISABLED)
337
-
338
-
339
-
340
- # If we just took a screenshot, display it
341
- if with_screenshot and self.current_screenshot:
342
- # Resize for preview
343
- preview_img = self.current_screenshot.copy()
344
- preview_img.thumbnail((500, 250))
345
- self.photo = ImageTk.PhotoImage(preview_img)
346
- self.image_label.config(image=self.photo)
347
- self.image_label.image = self.photo
348
- self.append_to_chat("System", "已截屏,顾问助手已准备好帮助您进行Alpha研究", "system")
349
-
350
- # Auto-trigger analysis if user wants (optional, but "提问屏幕内容" implies user action)
351
- # For now, we wait for user input or they can just click send with empty text to trigger analysis?
352
- # Let's allow empty text to trigger "Analyze this"
353
-
354
- elif not with_screenshot:
355
- self.image_label.config(image='')
356
- self.current_screenshot = None
357
-
358
- self.chat_window.deiconify()
359
- self.root.withdraw() # Keep main window hidden while chatting
360
-
361
- def on_chat_close(self):
362
- self.chat_window.destroy()
363
- self.chat_window = None
364
- self.current_screenshot = None
365
- self.root.deiconify() # Show main window again
366
-
367
- def append_to_chat(self, role, text, tag):
368
- self.chat_display.config(state='normal')
369
- self.chat_display.insert(tk.END, f"[{role}]: {text}\n\n", tag)
370
- self.chat_display.see(tk.END)
371
- self.chat_display.config(state='disabled')
372
-
373
- def handle_return(self, event):
374
- # If Shift is pressed, let default behavior happen (newline)
375
- if event.state & 0x0001:
376
- return None
377
- # Otherwise send message
378
- self.send_message()
379
- return "break" # Prevent default newline
380
-
381
- def send_message(self, event=None):
382
- user_text = self.msg_entry.get("1.0", tk.END).strip()
383
- user_typed_text = bool(user_text)
384
-
385
- # Allow sending if there is a screenshot, even if text is empty (implies "Analyze this")
386
- if not user_text and not self.current_screenshot:
387
- return
388
-
389
- if not user_text and self.current_screenshot:
390
- user_text = "Please analyze this screenshot and guide me on the next steps."
391
-
392
- # --- RAG: Query Knowledge Base ---
393
- context = ""
394
- hit_details = []
395
- used_kb = False
396
- if self.kb and user_typed_text:
397
- try:
398
- res = self.kb.query(user_text)
399
- used_kb = bool(res.get("hit"))
400
- context = res.get("context", "") if used_kb else ""
401
- hit_details = res.get("hits", []) or []
402
- except Exception as e:
403
- print(f"KB query failed: {e}")
404
- used_kb = False
405
- context = ""
406
- hit_details = []
407
-
408
- # Save last KB retrieval and toggle button
409
- self.last_kb_query = user_text
410
- self.last_kb_context = context or ""
411
- self.last_kb_hits = hit_details
412
- if hasattr(self, "kb_hits_btn"):
413
- self.kb_hits_btn.config(state=(tk.NORMAL if used_kb and context else tk.DISABLED))
414
-
415
- # Show user message first
416
- self.msg_entry.delete("1.0", tk.END)
417
- self.append_to_chat("User", user_text, "user")
418
-
419
- # Let user know whether KB was used (only when user actually typed text)
420
- if user_typed_text:
421
- if self.kb:
422
- if used_kb:
423
- self.append_to_chat("System", "已检索本地知识库:命中相关内容,将结合回答。", "system")
424
- else:
425
- self.append_to_chat("System", "已检索本地知识库:未命中,将直接基于模型回答。", "system")
426
- else:
427
- self.append_to_chat("System", "本地知识库未启用(依赖缺失或初始化失败),将直接基于模型回答。", "system")
428
-
429
- # Augment user text with context if available
430
- api_user_text = user_text
431
- if context:
432
- api_user_text = f"【参考本地知识库内容】:\n{context}\n\n【用户问题】:\n{user_text}"
433
-
434
- # Prepare messages for API
435
- messages = list(self.history) # Copy existing history
436
-
437
- new_message = {"role": "user", "content": []}
438
-
439
- # Add text (using the augmented text for the API, but showing original in UI)
440
- if api_user_text:
441
- new_message["content"].append({"type": "text", "text": api_user_text})
442
-
443
- # Add image if it's the FIRST message about this screenshot
444
- if self.current_screenshot:
445
- base64_image = self.encode_image(self.current_screenshot)
446
- new_message["content"].append({
447
- "type": "image_url",
448
- "image_url": {
449
- "url": f"data:image/jpeg;base64,{base64_image}"
450
- }
451
- })
452
- self.current_screenshot = None
453
- self.image_label.config(image='') # Hide preview after sending
454
-
455
- # Simplify content if just text
456
- if len(new_message["content"]) == 1 and new_message["content"][0]["type"] == "text":
457
- new_message["content"] = api_user_text
458
-
459
- messages.append(new_message)
460
-
461
- # Start thread for API call
462
- threading.Thread(target=self.run_api_call, args=(messages,)).start()
463
-
464
- def open_knowledge_folder(self):
465
- target_dir = self.knowledge_dir or os.path.join(os.path.dirname(__file__), "knowledge")
466
- if not os.path.exists(target_dir):
467
- messagebox.showinfo("知识库", "知识库文件夹不存在。")
468
- return
469
- try:
470
- if sys.platform.startswith("win"):
471
- os.startfile(target_dir)
472
- elif sys.platform == "darwin":
473
- subprocess.Popen(["open", target_dir])
474
- else:
475
- subprocess.Popen(["xdg-open", target_dir])
476
- except Exception as e:
477
- messagebox.showerror("知识库", f"无法打开知识库文件夹:{e}")
478
-
479
- def show_kb_hits(self):
480
- """Show the last retrieved KB context in a separate window."""
481
- if not self.kb:
482
- messagebox.showinfo("知识库", "本地知识库未启用或初始化失败。")
483
- return
484
- if not getattr(self, "last_kb_context", ""):
485
- messagebox.showinfo("知识库", "本次提问未命中知识库内容。")
486
- return
487
-
488
- win = Toplevel(self.chat_window if self.chat_window else self.root)
489
- win.title("知识库命中内容")
490
- win.geometry("700x500")
491
- win.configure(bg="#1e1e1e")
492
- win.attributes("-topmost", True)
493
- if self.icon_image:
494
- win.iconphoto(False, self.icon_image)
495
-
496
- header = tk.Label(
497
- win,
498
- text=f"查询:{self.last_kb_query}",
499
- bg="#1e1e1e",
500
- fg="#d4d4d4",
501
- font=("Segoe UI", 10, "bold"),
502
- anchor="w",
503
- justify="left",
504
- padx=10,
505
- pady=10
506
- )
507
- header.pack(side=tk.TOP, fill=tk.X)
508
-
509
- text_box = scrolledtext.ScrolledText(
510
- win,
511
- wrap=tk.WORD,
512
- bg="#252526",
513
- fg="#d4d4d4",
514
- insertbackground="white",
515
- font=("Consolas", 10)
516
- )
517
- text_box.pack(side=tk.TOP, expand=True, fill="both", padx=10, pady=(0, 10))
518
-
519
- # Prefer structured hits if available (shows source + score)
520
- hits = getattr(self, "last_kb_hits", None) or []
521
- if hits:
522
- lines = []
523
- for i, h in enumerate(hits, start=1):
524
- src = h.get("source", "")
525
- dist = h.get("distance", None)
526
- dist_str = f"{dist:.4f}" if isinstance(dist, (int, float)) else "N/A"
527
- lines.append(f"--- Hit {i} | source={src} | distance={dist_str} ---\n")
528
- lines.append((h.get("text") or "") + "\n\n")
529
- text_box.insert(tk.END, "".join(lines).strip())
530
- else:
531
- text_box.insert(tk.END, self.last_kb_context)
532
- text_box.config(state='disabled')
533
-
534
- def run_api_call(self, messages):
535
- try:
536
- # Create an empty message for the assistant first
537
- self.root.after(0, self.append_to_chat, "顾问助手", "", "assistant")
538
-
539
- stream = self.client.chat.completions.create(
540
- model=self.model,
541
- messages=messages,
542
- temperature=0.6,
543
- stream=True
544
- )
545
-
546
- full_response = ""
547
- for chunk in stream:
548
- if chunk.choices[0].delta.content:
549
- content = chunk.choices[0].delta.content
550
- full_response += content
551
- # Update UI with the new chunk
552
- self.root.after(0, self.update_last_message, content)
553
-
554
- # Update history with full response
555
- self.history.append(messages[-1]) # User msg
556
- self.history.append({"role": "assistant", "content": full_response})
557
-
558
- except Exception as e:
559
- error_msg = str(e)
560
- self.root.after(0, self.append_to_chat, "Error", error_msg, "system")
561
-
562
- def update_last_message(self, text_chunk):
563
- self.chat_display.config(state='normal')
564
- self.chat_display.insert(tk.END, text_chunk, "assistant")
565
- self.chat_display.see(tk.END)
566
- self.chat_display.config(state='disabled')
567
-
568
- def encode_image(self, image):
569
- buffered = io.BytesIO()
570
- image.save(buffered, format="JPEG")
571
- return base64.b64encode(buffered.getvalue()).decode('utf-8')
572
-
573
- if __name__ == "__main__":
574
- root = tk.Tk()
575
- app = BrainConsultantApp(root)
576
- root.mainloop()