cnhkmcp 2.1.1__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 (117) hide show
  1. {cnhkmcp-2.1.1.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 -280
  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 -356
  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/vector_db/_manifest.json +0 -326
  22. 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/vector_db/_meta.json +0 -1
  23. 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/vector_db/be5d957c-b724-46e3-91d1-999e9f5f7d28/index_metadata.pickle +0 -0
  24. 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/vector_db/chroma.sqlite3 +0 -0
  25. 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
  26. cnhkmcp/untracked/APP/.gitignore +0 -32
  27. cnhkmcp/untracked/APP/MODULAR_STRUCTURE.md +0 -112
  28. cnhkmcp/untracked/APP/README.md +0 -309
  29. cnhkmcp/untracked/APP/Tranformer/Transformer.py +0 -4985
  30. cnhkmcp/untracked/APP/Tranformer/ace.log +0 -0
  31. cnhkmcp/untracked/APP/Tranformer/ace_lib.py +0 -1510
  32. cnhkmcp/untracked/APP/Tranformer/helpful_functions.py +0 -180
  33. cnhkmcp/untracked/APP/Tranformer/output/Alpha_candidates.json +0 -2421
  34. cnhkmcp/untracked/APP/Tranformer/output/Alpha_candidates_/321/207/320/264/342/225/221/321/204/342/225/233/320/233.json +0 -654
  35. cnhkmcp/untracked/APP/Tranformer/output/Alpha_generated_expressions_error.json +0 -1034
  36. cnhkmcp/untracked/APP/Tranformer/output/Alpha_generated_expressions_success.json +0 -444
  37. 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
  38. cnhkmcp/untracked/APP/Tranformer/parsetab.py +0 -60
  39. cnhkmcp/untracked/APP/Tranformer/template_summary.txt +0 -3182
  40. cnhkmcp/untracked/APP/Tranformer/transformer_config.json +0 -7
  41. cnhkmcp/untracked/APP/Tranformer/validator.py +0 -889
  42. cnhkmcp/untracked/APP/ace.log +0 -69
  43. cnhkmcp/untracked/APP/ace_lib.py +0 -1510
  44. cnhkmcp/untracked/APP/blueprints/__init__.py +0 -6
  45. cnhkmcp/untracked/APP/blueprints/feature_engineering.py +0 -347
  46. cnhkmcp/untracked/APP/blueprints/idea_house.py +0 -221
  47. cnhkmcp/untracked/APP/blueprints/inspiration_house.py +0 -432
  48. cnhkmcp/untracked/APP/blueprints/paper_analysis.py +0 -570
  49. cnhkmcp/untracked/APP/custom_templates/templates.json +0 -1257
  50. cnhkmcp/untracked/APP/give_me_idea/BRAIN_Alpha_Template_Expert_SystemPrompt.md +0 -400
  51. cnhkmcp/untracked/APP/give_me_idea/ace_lib.py +0 -1510
  52. cnhkmcp/untracked/APP/give_me_idea/alpha_data_specific_template_master.py +0 -252
  53. cnhkmcp/untracked/APP/give_me_idea/fetch_all_datasets.py +0 -157
  54. cnhkmcp/untracked/APP/give_me_idea/fetch_all_operators.py +0 -99
  55. cnhkmcp/untracked/APP/give_me_idea/helpful_functions.py +0 -180
  56. cnhkmcp/untracked/APP/give_me_idea/what_is_Alpha_template.md +0 -11
  57. cnhkmcp/untracked/APP/helpful_functions.py +0 -180
  58. cnhkmcp/untracked/APP/hkSimulator/ace_lib.py +0 -1497
  59. cnhkmcp/untracked/APP/hkSimulator/autosimulator.py +0 -447
  60. cnhkmcp/untracked/APP/hkSimulator/helpful_functions.py +0 -180
  61. cnhkmcp/untracked/APP/mirror_config.txt +0 -20
  62. cnhkmcp/untracked/APP/operaters.csv +0 -129
  63. cnhkmcp/untracked/APP/requirements.txt +0 -53
  64. cnhkmcp/untracked/APP/run_app.bat +0 -28
  65. cnhkmcp/untracked/APP/run_app.sh +0 -34
  66. cnhkmcp/untracked/APP/setup_tsinghua.bat +0 -39
  67. cnhkmcp/untracked/APP/setup_tsinghua.sh +0 -43
  68. cnhkmcp/untracked/APP/simulator/alpha_submitter.py +0 -404
  69. cnhkmcp/untracked/APP/simulator/simulator_wqb.py +0 -618
  70. cnhkmcp/untracked/APP/ssrn-3332513.pdf +6 -109201
  71. cnhkmcp/untracked/APP/static/brain.js +0 -589
  72. cnhkmcp/untracked/APP/static/decoder.js +0 -1540
  73. cnhkmcp/untracked/APP/static/feature_engineering.js +0 -1729
  74. cnhkmcp/untracked/APP/static/idea_house.js +0 -937
  75. cnhkmcp/untracked/APP/static/inspiration.js +0 -465
  76. cnhkmcp/untracked/APP/static/inspiration_house.js +0 -868
  77. cnhkmcp/untracked/APP/static/paper_analysis.js +0 -390
  78. cnhkmcp/untracked/APP/static/script.js +0 -3082
  79. cnhkmcp/untracked/APP/static/simulator.js +0 -597
  80. cnhkmcp/untracked/APP/static/styles.css +0 -3127
  81. cnhkmcp/untracked/APP/static/usage_widget.js +0 -508
  82. cnhkmcp/untracked/APP/templates/alpha_inspector.html +0 -511
  83. cnhkmcp/untracked/APP/templates/feature_engineering.html +0 -960
  84. cnhkmcp/untracked/APP/templates/idea_house.html +0 -564
  85. cnhkmcp/untracked/APP/templates/index.html +0 -932
  86. cnhkmcp/untracked/APP/templates/inspiration_house.html +0 -861
  87. cnhkmcp/untracked/APP/templates/paper_analysis.html +0 -91
  88. cnhkmcp/untracked/APP/templates/simulator.html +0 -343
  89. cnhkmcp/untracked/APP/templates/transformer_web.html +0 -580
  90. cnhkmcp/untracked/APP/usage.md +0 -351
  91. 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
  92. 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
  93. 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
  94. 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
  95. cnhkmcp/untracked/arXiv_API_Tool_Manual.md +0 -490
  96. cnhkmcp/untracked/arxiv_api.py +0 -229
  97. cnhkmcp/untracked/forum_functions.py +0 -998
  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/forum_functions.py +0 -407
  99. 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
  100. 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
  101. 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
  102. 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
  103. cnhkmcp/untracked/platform_functions.py +0 -2886
  104. cnhkmcp/untracked/sample_mcp_config.json +0 -11
  105. cnhkmcp/untracked/user_config.json +0 -31
  106. 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
  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_Alpha_explaination_workflow.md +0 -56
  108. 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
  109. 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
  110. 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
  111. 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
  112. 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
  113. cnhkmcp-2.1.1.dist-info/RECORD +0 -115
  114. cnhkmcp-2.1.1.dist-info/top_level.txt +0 -1
  115. {cnhkmcp-2.1.1.dist-info → cnhkmcp-2.1.3.dist-info}/WHEEL +0 -0
  116. {cnhkmcp-2.1.1.dist-info → cnhkmcp-2.1.3.dist-info}/entry_points.txt +0 -0
  117. {cnhkmcp-2.1.1.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()