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.
- {cnhkmcp-2.1.2.dist-info → cnhkmcp-2.1.3.dist-info}/METADATA +1 -1
- cnhkmcp-2.1.3.dist-info/RECORD +6 -0
- cnhkmcp-2.1.3.dist-info/top_level.txt +1 -0
- cnhkmcp/__init__.py +0 -125
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- cnhkmcp/untracked/APP/.gitignore +0 -32
- cnhkmcp/untracked/APP/MODULAR_STRUCTURE.md +0 -112
- cnhkmcp/untracked/APP/README.md +0 -309
- cnhkmcp/untracked/APP/Tranformer/Transformer.py +0 -4985
- cnhkmcp/untracked/APP/Tranformer/ace.log +0 -0
- cnhkmcp/untracked/APP/Tranformer/ace_lib.py +0 -1510
- cnhkmcp/untracked/APP/Tranformer/helpful_functions.py +0 -180
- cnhkmcp/untracked/APP/Tranformer/output/Alpha_candidates.json +0 -2421
- cnhkmcp/untracked/APP/Tranformer/output/Alpha_candidates_/321/207/320/264/342/225/221/321/204/342/225/233/320/233.json +0 -654
- cnhkmcp/untracked/APP/Tranformer/output/Alpha_generated_expressions_error.json +0 -1034
- cnhkmcp/untracked/APP/Tranformer/output/Alpha_generated_expressions_success.json +0 -444
- 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
- cnhkmcp/untracked/APP/Tranformer/parsetab.py +0 -60
- cnhkmcp/untracked/APP/Tranformer/template_summary.txt +0 -3182
- cnhkmcp/untracked/APP/Tranformer/transformer_config.json +0 -7
- cnhkmcp/untracked/APP/Tranformer/validator.py +0 -889
- cnhkmcp/untracked/APP/ace.log +0 -69
- cnhkmcp/untracked/APP/ace_lib.py +0 -1510
- cnhkmcp/untracked/APP/blueprints/__init__.py +0 -6
- cnhkmcp/untracked/APP/blueprints/feature_engineering.py +0 -347
- cnhkmcp/untracked/APP/blueprints/idea_house.py +0 -221
- cnhkmcp/untracked/APP/blueprints/inspiration_house.py +0 -432
- cnhkmcp/untracked/APP/blueprints/paper_analysis.py +0 -570
- cnhkmcp/untracked/APP/custom_templates/templates.json +0 -1257
- cnhkmcp/untracked/APP/give_me_idea/BRAIN_Alpha_Template_Expert_SystemPrompt.md +0 -400
- cnhkmcp/untracked/APP/give_me_idea/ace_lib.py +0 -1510
- cnhkmcp/untracked/APP/give_me_idea/alpha_data_specific_template_master.py +0 -252
- cnhkmcp/untracked/APP/give_me_idea/fetch_all_datasets.py +0 -157
- cnhkmcp/untracked/APP/give_me_idea/fetch_all_operators.py +0 -99
- cnhkmcp/untracked/APP/give_me_idea/helpful_functions.py +0 -180
- cnhkmcp/untracked/APP/give_me_idea/what_is_Alpha_template.md +0 -11
- cnhkmcp/untracked/APP/helpful_functions.py +0 -180
- cnhkmcp/untracked/APP/hkSimulator/ace_lib.py +0 -1497
- cnhkmcp/untracked/APP/hkSimulator/autosimulator.py +0 -447
- cnhkmcp/untracked/APP/hkSimulator/helpful_functions.py +0 -180
- cnhkmcp/untracked/APP/mirror_config.txt +0 -20
- cnhkmcp/untracked/APP/operaters.csv +0 -129
- cnhkmcp/untracked/APP/requirements.txt +0 -53
- cnhkmcp/untracked/APP/run_app.bat +0 -28
- cnhkmcp/untracked/APP/run_app.sh +0 -34
- cnhkmcp/untracked/APP/setup_tsinghua.bat +0 -39
- cnhkmcp/untracked/APP/setup_tsinghua.sh +0 -43
- cnhkmcp/untracked/APP/simulator/alpha_submitter.py +0 -404
- cnhkmcp/untracked/APP/simulator/simulator_wqb.py +0 -618
- cnhkmcp/untracked/APP/ssrn-3332513.pdf +6 -109201
- cnhkmcp/untracked/APP/static/brain.js +0 -589
- cnhkmcp/untracked/APP/static/decoder.js +0 -1540
- cnhkmcp/untracked/APP/static/feature_engineering.js +0 -1729
- cnhkmcp/untracked/APP/static/idea_house.js +0 -937
- cnhkmcp/untracked/APP/static/inspiration.js +0 -465
- cnhkmcp/untracked/APP/static/inspiration_house.js +0 -868
- cnhkmcp/untracked/APP/static/paper_analysis.js +0 -390
- cnhkmcp/untracked/APP/static/script.js +0 -3082
- cnhkmcp/untracked/APP/static/simulator.js +0 -597
- cnhkmcp/untracked/APP/static/styles.css +0 -3127
- cnhkmcp/untracked/APP/static/usage_widget.js +0 -508
- cnhkmcp/untracked/APP/templates/alpha_inspector.html +0 -511
- cnhkmcp/untracked/APP/templates/feature_engineering.html +0 -960
- cnhkmcp/untracked/APP/templates/idea_house.html +0 -564
- cnhkmcp/untracked/APP/templates/index.html +0 -932
- cnhkmcp/untracked/APP/templates/inspiration_house.html +0 -861
- cnhkmcp/untracked/APP/templates/paper_analysis.html +0 -91
- cnhkmcp/untracked/APP/templates/simulator.html +0 -343
- cnhkmcp/untracked/APP/templates/transformer_web.html +0 -580
- cnhkmcp/untracked/APP/usage.md +0 -351
- 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
- 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
- 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
- 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
- cnhkmcp/untracked/arXiv_API_Tool_Manual.md +0 -490
- cnhkmcp/untracked/arxiv_api.py +0 -229
- cnhkmcp/untracked/forum_functions.py +0 -998
- 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
- 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
- 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
- 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
- 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
- cnhkmcp/untracked/platform_functions.py +0 -2886
- cnhkmcp/untracked/sample_mcp_config.json +0 -11
- cnhkmcp/untracked/user_config.json +0 -31
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- cnhkmcp-2.1.2.dist-info/RECORD +0 -111
- cnhkmcp-2.1.2.dist-info/top_level.txt +0 -1
- {cnhkmcp-2.1.2.dist-info → cnhkmcp-2.1.3.dist-info}/WHEEL +0 -0
- {cnhkmcp-2.1.2.dist-info → cnhkmcp-2.1.3.dist-info}/entry_points.txt +0 -0
- {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()
|