1
+ import tkinter as tk
2
+ from tkinter import messagebox
3
+ from PIL import ImageGrab , Image
4
+ import openai
5
+ import base64
6
+ import io
7
+ from config import get_openai_api_key
8
+
9
+ class AIScreenReader :
10
+
11
+ def __init__ (self , parent_app , logger ):
12
+ self .parent_app = parent_app
13
+ self .logger = logger
14
+ try :
15
+ self .client = openai .OpenAI (api_key = get_openai_api_key ())
16
+ except Exception as e :
17
+ self .logger .error (f"Failed to initialize OpenAI client: { e } " )
18
+ messagebox .showerror ("OpenAI Error" , f"Failed to initialize OpenAI client. Please check your API key.\n \n { e } " )
19
+ self .client = None
20
+
21
+ def _analyze_image (self , image : Image .Image , model : str , prompt : str ):
22
+ if not self .client :
23
+ return "OpenAI client not initialized."
24
+
25
+ self .logger .info (f"Sending image to OpenAI for analysis using model: { model } ..." )
26
+
27
+ buffered = io .BytesIO ()
28
+ image .save (buffered , format = "PNG" )
29
+ base64_image = base64 .b64encode (buffered .getvalue ()).decode ('utf-8' )
30
+
31
+ try :
32
+ response = self .client .chat .completions .create (
33
+ model = model ,
34
+ messages = [
35
+ {
36
+ "role" : "user" ,
37
+ "content" : [
38
+ {"type" : "text" , "text" : prompt },
39
+ {"type" : "image_url" , "image_url" : {"url" : f"data:image/png;base64,{ base64_image } " }},
40
+ ],
41
+ }
42
+ ],
43
+ max_tokens = 1000 ,
44
+ )
45
+ self .logger .info ("Received analysis from OpenAI." )
46
+ return response .choices [0 ].message .content
47
+ except Exception as e :
48
+ self .logger .error (f"OpenAI API call failed: { e } " )
49
+ messagebox .showerror ("OpenAI Error" , f"API call failed: { e } " )
50
+ return f"Error analyzing image: { e } "
51
+
52
+ def analyze_full_screen (self , model : str , prompt : str ):
53
+ self .logger .info ("Capturing full screen..." )
54
+
55
+ self .parent_app .withdraw ()
56
+ self .parent_app .update_idletasks ()
57
+
58
+ screenshot = ImageGrab .grab (all_screens = True )
59
+
60
+ self .parent_app .deiconify ()
61
+
62
+ return self ._analyze_image (screenshot , model , prompt )
63
+
64
+ def analyze_screen_area (self , model : str , prompt : str ):
65
+ self .logger .info ("Starting screen area selection..." )
66
+ selector = ScreenAreaSelector (self .parent_app )
67
+
68
+ self .parent_app .withdraw ()
69
+ self .parent_app .wait_window (selector .master )
70
+
71
+ screenshot = None
72
+ if selector .bbox :
73
+ self .logger .info (f"Area selected: { selector .bbox } " )
74
+
75
+ screen_width = self .parent_app .winfo_screenwidth ()
76
+ screen_height = self .parent_app .winfo_screenheight ()
77
+
78
+ x1 = max (0 , selector .bbox [0 ])
79
+ y1 = max (0 , selector .bbox [1 ])
80
+ x2 = min (screen_width , selector .bbox [2 ])
81
+ y2 = min (screen_height , selector .bbox [3 ])
82
+
83
+ if x1 >= x2 or y1 >= y2 :
84
+ self .logger .warning ("Invalid area selected (zero or negative size). Aborting." )
85
+ else :
86
+ clamped_bbox = (x1 , y1 , x2 , y2 )
87
+ self .logger .info (f"Clamped bbox to: { clamped_bbox } " )
88
+ full_screenshot = ImageGrab .grab (all_screens = True )
89
+ screenshot = full_screenshot .crop (clamped_bbox )
90
+
91
+ self .parent_app .deiconify ()
92
+
93
+ if screenshot :
94
+ return self ._analyze_image (screenshot , model , prompt )
95
+ else :
96
+ self .logger .info ("Area selection cancelled." )
97
+ return None
98
+
99
+
100
+ class ScreenAreaSelector :
101
+ def __init__ (self , parent_app ):
102
+ self .parent_app = parent_app
103
+ self .master = tk .Toplevel (parent_app )
104
+ self .master .title ("Screen Selector" )
105
+ self .master .attributes ("-fullscreen" , True )
106
+ self .master .attributes ("-alpha" , 0.3 )
107
+ self .master .bind ("<ButtonPress-1>" , self .on_press )
108
+ self .master .bind ("<B1-Motion>" , self .on_drag )
109
+ self .master .bind ("<ButtonRelease-1>" , self .on_release )
110
+
111
+ self .canvas = tk .Canvas (self .master , cursor = "cross" , bg = "grey" )
112
+ self .canvas .pack (fill = tk .BOTH , expand = True )
113
+
114
+ if hasattr (self .parent_app , 'apply_privacy_settings_to_window' ):
115
+ self .parent_app .apply_privacy_settings_to_window (self .master , apply_transparency = False )
116
+
117
+ self .start_x = None
118
+ self .start_y = None
119
+ self .rect = None
120
+ self .bbox = None
121
+
122
+ def on_press (self , event ):
123
+ self .start_x = self .canvas .canvasx (event .x )
124
+ self .start_y = self .canvas .canvasy (event .y )
125
+ if self .rect :
126
+ self .canvas .delete (self .rect )
127
+ self .rect = self .canvas .create_rectangle (self .start_x , self .start_y , self .start_x , self .start_y , outline = 'red' , width = 2 )
128
+
129
+ def on_drag (self , event ):
130
+ cur_x , cur_y = (self .canvas .canvasx (event .x ), self .canvas .canvasy (event .y ))
131
+ self .canvas .coords (self .rect , self .start_x , self .start_y , cur_x , cur_y )
132
+
133
+ def on_release (self , event ):
134
+ end_x , end_y = (self .canvas .canvasx (event .x ), self .canvas .canvasy (event .y ))
135
+ self .bbox = (min (self .start_x , end_x ), min (self .start_y , end_y ),
136
+ max (self .start_x , end_x ), max (self .start_y , end_y ))
137
+ self .master .destroy ()
0 commit comments