many updates to AI Assistant plugin. Improved UI, hopefully improved accuracy as well.
This commit is contained in:
@@ -100,20 +100,21 @@ class APIHelper:
|
|||||||
|
|
||||||
# Create a keybinding handler
|
# Create a keybinding handler
|
||||||
class GestureHandler:
|
class GestureHandler:
|
||||||
def __init__(self, function, description):
|
def __init__(self, function, description, learnModeEnabled=True):
|
||||||
self.function = function
|
self.function = function
|
||||||
self.description = description
|
self.description = description
|
||||||
|
self.learnModeEnabled = learnModeEnabled
|
||||||
|
|
||||||
def __call__(self, script, inputEvent):
|
def __call__(self, script, inputEvent):
|
||||||
try:
|
try:
|
||||||
logger.info(f"=== DisplayVersion keybinding handler called! ===")
|
logger.info(f"=== Plugin keybinding handler called! ===")
|
||||||
return function(script, inputEvent)
|
return function(script, inputEvent)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
import logging
|
import logging
|
||||||
logging.getLogger(__name__).error(f"Error in keybinding handler: {e}")
|
logging.getLogger(__name__).error(f"Error in keybinding handler: {e}")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
handler = GestureHandler(function, name)
|
handler = GestureHandler(function, name, learnModeEnabled)
|
||||||
logger.info(f"Created handler: {handler}")
|
logger.info(f"Created handler: {handler}")
|
||||||
|
|
||||||
# Create the binding object regardless of whether there's an active script
|
# Create the binding object regardless of whether there's an active script
|
||||||
|
|||||||
@@ -23,5 +23,5 @@
|
|||||||
# Fork of Orca Screen Reader (GNOME)
|
# Fork of Orca Screen Reader (GNOME)
|
||||||
# Original source: https://gitlab.gnome.org/GNOME/orca
|
# Original source: https://gitlab.gnome.org/GNOME/orca
|
||||||
|
|
||||||
version = "2025.08.03"
|
version = "2025.08.05"
|
||||||
codeName = "testing"
|
codeName = "testing"
|
||||||
|
|||||||
@@ -339,7 +339,9 @@ class KeyboardEvent(InputEvent):
|
|||||||
if KeyboardEvent.lastCthulhuModifierAlone:
|
if KeyboardEvent.lastCthulhuModifierAlone:
|
||||||
if _isPressed:
|
if _isPressed:
|
||||||
KeyboardEvent.secondCthulhuModifierTime = now
|
KeyboardEvent.secondCthulhuModifierTime = now
|
||||||
if (KeyboardEvent.secondCthulhuModifierTime <
|
if (KeyboardEvent.secondCthulhuModifierTime is not None and
|
||||||
|
KeyboardEvent.lastCthulhuModifierAloneTime is not None and
|
||||||
|
KeyboardEvent.secondCthulhuModifierTime <
|
||||||
KeyboardEvent.lastCthulhuModifierAloneTime + 0.5):
|
KeyboardEvent.lastCthulhuModifierAloneTime + 0.5):
|
||||||
# double-cthulhu, let the real action happen
|
# double-cthulhu, let the real action happen
|
||||||
self._bypassCthulhu = True
|
self._bypassCthulhu = True
|
||||||
|
|||||||
@@ -191,10 +191,18 @@ class LearnModePresenter:
|
|||||||
|
|
||||||
handler = event.getHandler()
|
handler = event.getHandler()
|
||||||
if handler is None:
|
if handler is None:
|
||||||
|
debug.printMessage(debug.LEVEL_INFO, "LEARN MODE PRESENTER: No handler found for event", True)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
debug.printMessage(debug.LEVEL_INFO, f"LEARN MODE PRESENTER: Handler found: {handler}", True)
|
||||||
|
debug.printMessage(debug.LEVEL_INFO, f"LEARN MODE PRESENTER: Handler description: {getattr(handler, 'description', 'No description')}", True)
|
||||||
|
debug.printMessage(debug.LEVEL_INFO, f"LEARN MODE PRESENTER: Handler learnModeEnabled: {getattr(handler, 'learnModeEnabled', 'No learnModeEnabled')}", True)
|
||||||
|
|
||||||
if handler.learnModeEnabled and handler.description:
|
if handler.learnModeEnabled and handler.description:
|
||||||
|
debug.printMessage(debug.LEVEL_INFO, f"LEARN MODE PRESENTER: Presenting message: {handler.description}", True)
|
||||||
cthulhu_state.activeScript.presentMessage(handler.description)
|
cthulhu_state.activeScript.presentMessage(handler.description)
|
||||||
|
else:
|
||||||
|
debug.printMessage(debug.LEVEL_INFO, "LEARN MODE PRESENTER: Handler missing required properties for learn mode", True)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -254,6 +262,23 @@ class LearnModePresenter:
|
|||||||
bindings[guilabels.KB_GROUP_ACTIONS] = bound
|
bindings[guilabels.KB_GROUP_ACTIONS] = bound
|
||||||
items += len(bound)
|
items += len(bound)
|
||||||
|
|
||||||
|
# Add plugin keybindings
|
||||||
|
try:
|
||||||
|
debug.printMessage(debug.LEVEL_INFO, "LEARN MODE PRESENTER: Getting plugin keybindings", True)
|
||||||
|
plugin_bindings = script.getPluginKeyBindings()
|
||||||
|
debug.printMessage(debug.LEVEL_INFO, f"LEARN MODE PRESENTER: Got plugin bindings object: {plugin_bindings}", True)
|
||||||
|
bound = plugin_bindings.getBoundBindings()
|
||||||
|
debug.printMessage(debug.LEVEL_INFO, f"LEARN MODE PRESENTER: Got {len(bound)} bound plugin keybindings", True)
|
||||||
|
if bound:
|
||||||
|
bindings["Plugins"] = bound
|
||||||
|
items += len(bound)
|
||||||
|
debug.printMessage(debug.LEVEL_INFO, f"LEARN MODE PRESENTER: Added {len(bound)} plugin keybindings to learn mode", True)
|
||||||
|
except Exception as e:
|
||||||
|
msg = f"LEARN MODE PRESENTER: Could not get plugin keybindings: {e}"
|
||||||
|
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||||
|
import traceback
|
||||||
|
debug.printMessage(debug.LEVEL_INFO, traceback.format_exc(), True)
|
||||||
|
|
||||||
title = messages.shortcutsFoundCthulhu(items)
|
title = messages.shortcutsFoundCthulhu(items)
|
||||||
else:
|
else:
|
||||||
app_name = AXObject.get_name(script.app) or messages.APPLICATION_NO_NAME
|
app_name = AXObject.get_name(script.app) or messages.APPLICATION_NO_NAME
|
||||||
|
|||||||
@@ -395,9 +395,19 @@ class PluginSystemManager:
|
|||||||
"""Get the list of active plugin names."""
|
"""Get the list of active plugin names."""
|
||||||
return self._active_plugins
|
return self._active_plugins
|
||||||
|
|
||||||
|
def get_active_plugins(self):
|
||||||
|
"""Get the list of active plugin instances."""
|
||||||
|
active_instances = []
|
||||||
|
for plugin_name in self._active_plugins:
|
||||||
|
if plugin_name in self._plugins:
|
||||||
|
plugin_info = self._plugins[plugin_name]
|
||||||
|
if plugin_info.loaded and plugin_info.instance:
|
||||||
|
active_instances.append(plugin_info.instance)
|
||||||
|
return active_instances
|
||||||
|
|
||||||
def setActivePlugins(self, activePlugins):
|
def setActivePlugins(self, activePlugins):
|
||||||
"""Set active plugins and sync their state."""
|
"""Set active plugins and sync their state."""
|
||||||
logger.info(f"=== PluginSystemManager.setActivePlugins called ===")
|
logger.info(f"PLUGIN SYSTEM: setActivePlugins called with: {activePlugins}")
|
||||||
logger.info(f"Setting active plugins: {activePlugins}")
|
logger.info(f"Setting active plugins: {activePlugins}")
|
||||||
|
|
||||||
# Make sure we have scanned for plugins first
|
# Make sure we have scanned for plugins first
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
name = AI Assistant
|
[Plugin]
|
||||||
version = 1.0.0
|
Name = AI Assistant
|
||||||
description = AI-powered accessibility assistant for analyzing screens and taking actions
|
Module = AIAssistant
|
||||||
authors = Stormux <storm_dragon@stormux.org>
|
Description = AI-powered accessibility assistant for analyzing screens and taking actions
|
||||||
website = https://stormux.org
|
Authors = Stormux <storm_dragon@stormux.org>
|
||||||
copyright = Copyright 2025
|
Version = 1.0.0
|
||||||
builtin = false
|
Category = Accessibility
|
||||||
hidden = false
|
|
||||||
@@ -46,15 +46,11 @@ class AIAssistant(Plugin):
|
|||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
"""Initialize the AI Assistant plugin."""
|
"""Initialize the AI Assistant plugin."""
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
logger.info("AI Assistant plugin initialized")
|
|
||||||
print("DEBUG: AI Assistant plugin __init__ called")
|
|
||||||
|
|
||||||
# Write to a debug file so we can see if the plugin is being loaded
|
# Use print to ensure we see this message
|
||||||
try:
|
print("DEBUG: AI ASSISTANT __init__ called")
|
||||||
with open('/tmp/ai_assistant_debug.log', 'a') as f:
|
logger.info("AI ASSISTANT: Plugin __init__ starting")
|
||||||
f.write("AI Assistant plugin __init__ called\n")
|
logger.info("AI ASSISTANT: Plugin initialized successfully")
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Menu and keybinding storage
|
# Menu and keybinding storage
|
||||||
self._kb_binding_menu = None
|
self._kb_binding_menu = None
|
||||||
@@ -79,31 +75,45 @@ class AIAssistant(Plugin):
|
|||||||
if plugin is not None and plugin is not self:
|
if plugin is not None and plugin is not self:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Prevent multiple activations
|
||||||
|
if self._enabled:
|
||||||
|
logger.info("AI ASSISTANT: Already activated, skipping")
|
||||||
|
print("DEBUG: AI ASSISTANT already activated, skipping")
|
||||||
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
logger.info("=== AI Assistant plugin activation starting ===")
|
logger.info("AI ASSISTANT: === Plugin activation starting ===")
|
||||||
print("DEBUG: AI Assistant plugin activation starting")
|
print("DEBUG: AI ASSISTANT activation starting")
|
||||||
|
|
||||||
# Check if AI Assistant is enabled in settings
|
# Check if AI Assistant is enabled in settings
|
||||||
enabled = self._settings_manager.getSetting('aiAssistantEnabled')
|
enabled = self._settings_manager.getSetting('aiAssistantEnabled')
|
||||||
print(f"DEBUG: AI Assistant enabled setting: {enabled}")
|
logger.info(f"AI ASSISTANT: Enabled setting: {enabled}")
|
||||||
|
print(f"DEBUG: AI ASSISTANT enabled setting: {enabled}")
|
||||||
if not enabled:
|
if not enabled:
|
||||||
logger.info("AI Assistant is disabled in settings, skipping activation")
|
logger.info("AI Assistant is disabled in settings, skipping activation")
|
||||||
print("DEBUG: AI Assistant is disabled in settings, skipping activation")
|
print("DEBUG: AI Assistant disabled, skipping activation")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Load AI settings
|
# Load AI settings
|
||||||
self._load_ai_settings()
|
self._load_ai_settings()
|
||||||
|
print(f"DEBUG: AI settings loaded - provider: {self._provider_type}")
|
||||||
|
|
||||||
# Check if we have valid configuration
|
# Check if we have valid configuration
|
||||||
if not self._validate_configuration():
|
config_valid = self._validate_configuration()
|
||||||
|
logger.info(f"AI Assistant configuration valid: {config_valid}")
|
||||||
|
print(f"DEBUG: AI Assistant configuration valid: {config_valid}")
|
||||||
|
if not config_valid:
|
||||||
logger.warning("AI Assistant configuration invalid, skipping activation")
|
logger.warning("AI Assistant configuration invalid, skipping activation")
|
||||||
|
print("DEBUG: AI Assistant configuration invalid, skipping activation")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Initialize AI provider
|
# Initialize AI provider
|
||||||
self._initialize_ai_provider()
|
self._initialize_ai_provider()
|
||||||
|
print("DEBUG: AI provider initialized")
|
||||||
|
|
||||||
# Register keybindings only if configuration is valid
|
# Register keybindings only if configuration is valid
|
||||||
self._register_keybindings()
|
self._register_keybindings()
|
||||||
|
print("DEBUG: AI keybindings registered")
|
||||||
|
|
||||||
self._enabled = True
|
self._enabled = True
|
||||||
logger.info("AI Assistant plugin activated successfully")
|
logger.info("AI Assistant plugin activated successfully")
|
||||||
@@ -111,6 +121,7 @@ class AIAssistant(Plugin):
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error activating AI Assistant plugin: {e}")
|
logger.error(f"Error activating AI Assistant plugin: {e}")
|
||||||
|
print(f"DEBUG: Error activating AI Assistant plugin: {e}")
|
||||||
import traceback
|
import traceback
|
||||||
logger.error(traceback.format_exc())
|
logger.error(traceback.format_exc())
|
||||||
|
|
||||||
@@ -156,21 +167,28 @@ class AIAssistant(Plugin):
|
|||||||
|
|
||||||
def _validate_configuration(self):
|
def _validate_configuration(self):
|
||||||
"""Validate AI Assistant configuration."""
|
"""Validate AI Assistant configuration."""
|
||||||
|
logger.info(f"Validating AI configuration - provider_type: {self._provider_type}")
|
||||||
if not self._provider_type:
|
if not self._provider_type:
|
||||||
logger.warning("No AI provider configured")
|
logger.warning("No AI provider configured")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Providers that don't need API keys
|
# Providers that don't need API keys
|
||||||
if self._provider_type == settings.AI_PROVIDER_OLLAMA:
|
if self._provider_type == settings.AI_PROVIDER_OLLAMA:
|
||||||
|
logger.info("Checking Ollama availability")
|
||||||
return self._check_ollama_availability()
|
return self._check_ollama_availability()
|
||||||
elif self._provider_type == settings.AI_PROVIDER_CLAUDE_CODE:
|
elif self._provider_type == settings.AI_PROVIDER_CLAUDE_CODE:
|
||||||
return self._check_claude_code_availability()
|
logger.info("Checking Claude Code availability")
|
||||||
|
result = self._check_claude_code_availability()
|
||||||
|
logger.info(f"Claude Code availability check result: {result}")
|
||||||
|
return result
|
||||||
|
|
||||||
# Other providers need API keys
|
# Other providers need API keys
|
||||||
|
logger.info(f"Checking API key for provider {self._provider_type}")
|
||||||
if not self._api_key:
|
if not self._api_key:
|
||||||
logger.warning(f"No API key configured for provider {self._provider_type}")
|
logger.warning(f"No API key configured for provider {self._provider_type}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
logger.info("Configuration validation passed")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _check_ollama_availability(self):
|
def _check_ollama_availability(self):
|
||||||
@@ -243,7 +261,7 @@ class AIAssistant(Plugin):
|
|||||||
self._kb_binding_menu = self.registerGestureByString(
|
self._kb_binding_menu = self.registerGestureByString(
|
||||||
self._show_ai_menu,
|
self._show_ai_menu,
|
||||||
"Show AI Assistant menu",
|
"Show AI Assistant menu",
|
||||||
'kb:cthulhu+control+shift+a'
|
'kb:cthulhu+shift+control+a'
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.info("AI Assistant menu keybinding registered")
|
logger.info("AI Assistant menu keybinding registered")
|
||||||
@@ -261,19 +279,23 @@ class AIAssistant(Plugin):
|
|||||||
def _show_ai_menu(self, script=None, inputEvent=None):
|
def _show_ai_menu(self, script=None, inputEvent=None):
|
||||||
"""Show the AI Assistant menu."""
|
"""Show the AI Assistant menu."""
|
||||||
try:
|
try:
|
||||||
logger.info("Showing AI Assistant menu")
|
logger.info("AI ASSISTANT: _show_ai_menu called!")
|
||||||
|
print("DEBUG: AI ASSISTANT _show_ai_menu called!")
|
||||||
|
|
||||||
# IMPORTANT: Capture screen data BEFORE showing menu
|
# IMPORTANT: Capture screen data BEFORE showing menu
|
||||||
# This ensures we get the actual screen content, not the menu itself
|
# This ensures we get the actual screen content, not the menu itself
|
||||||
self._pre_menu_screen_data = self._collect_ai_data()
|
self._pre_menu_screen_data = self._collect_ai_data()
|
||||||
logger.info("Pre-captured screen data for menu actions")
|
logger.info("Pre-captured screen data for menu actions")
|
||||||
|
print("DEBUG: Pre-captured screen data for menu actions")
|
||||||
|
|
||||||
# Now show the menu
|
# Now show the menu
|
||||||
self._menu_gui = AIAssistantMenu(self._handle_menu_selection)
|
self._menu_gui = AIAssistantMenu(self._handle_menu_selection)
|
||||||
self._menu_gui.show_gui()
|
self._menu_gui.show_gui()
|
||||||
|
print("DEBUG: AI menu GUI shown")
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error showing AI menu: {e}")
|
logger.error(f"Error showing AI menu: {e}")
|
||||||
|
print(f"DEBUG: Error showing AI menu: {e}")
|
||||||
import traceback
|
import traceback
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
return False
|
return False
|
||||||
@@ -309,7 +331,8 @@ class AIAssistant(Plugin):
|
|||||||
self._present_message("AI provider not available. Check configuration.")
|
self._present_message("AI provider not available. Check configuration.")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
self._present_message("AI Assistant analyzing screen...")
|
provider_name = self._provider_type.replace('_', ' ').title()
|
||||||
|
self._present_message(f"AI Assistant ({provider_name}) analyzing screen...")
|
||||||
|
|
||||||
# Use pre-captured data
|
# Use pre-captured data
|
||||||
if data:
|
if data:
|
||||||
@@ -453,7 +476,8 @@ class AIAssistant(Plugin):
|
|||||||
self._present_message("AI provider not available. Check configuration.")
|
self._present_message("AI provider not available. Check configuration.")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
self._present_message("AI Assistant analyzing screen...")
|
provider_name = self._provider_type.replace('_', ' ').title()
|
||||||
|
self._present_message(f"AI Assistant ({provider_name}) analyzing screen...")
|
||||||
|
|
||||||
# Collect data and get AI description
|
# Collect data and get AI description
|
||||||
data = self._collect_ai_data()
|
data = self._collect_ai_data()
|
||||||
@@ -745,8 +769,9 @@ class AIAssistant(Plugin):
|
|||||||
def _show_question_dialog(self):
|
def _show_question_dialog(self):
|
||||||
"""Show a dialog for the user to enter their question."""
|
"""Show a dialog for the user to enter their question."""
|
||||||
try:
|
try:
|
||||||
|
provider_name = self._provider_type.replace('_', ' ').title()
|
||||||
dialog = Gtk.Dialog(
|
dialog = Gtk.Dialog(
|
||||||
title="AI Assistant Question",
|
title=f"AI Assistant Question ({provider_name})",
|
||||||
parent=None,
|
parent=None,
|
||||||
flags=Gtk.DialogFlags.MODAL,
|
flags=Gtk.DialogFlags.MODAL,
|
||||||
buttons=(
|
buttons=(
|
||||||
@@ -807,7 +832,8 @@ class AIAssistant(Plugin):
|
|||||||
dialog.get_action_area().remove(child)
|
dialog.get_action_area().remove(child)
|
||||||
|
|
||||||
# Change title
|
# Change title
|
||||||
dialog.set_title("AI Assistant Response")
|
provider_name = self._provider_type.replace('_', ' ').title()
|
||||||
|
dialog.set_title(f"AI Assistant Response ({provider_name})")
|
||||||
|
|
||||||
# Show question and processing message
|
# Show question and processing message
|
||||||
question_label = Gtk.Label()
|
question_label = Gtk.Label()
|
||||||
@@ -816,12 +842,22 @@ class AIAssistant(Plugin):
|
|||||||
question_label.set_halign(Gtk.Align.START)
|
question_label.set_halign(Gtk.Align.START)
|
||||||
content_area.pack_start(question_label, False, False, 10)
|
content_area.pack_start(question_label, False, False, 10)
|
||||||
|
|
||||||
# Processing label (will be updated with response)
|
# Create scrollable text view for response (same as description dialog)
|
||||||
self._response_label = Gtk.Label(label="Processing your question...")
|
scrolled_window = Gtk.ScrolledWindow()
|
||||||
self._response_label.set_line_wrap(True)
|
scrolled_window.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
|
||||||
self._response_label.set_halign(Gtk.Align.START)
|
scrolled_window.set_shadow_type(Gtk.ShadowType.IN)
|
||||||
self._response_label.set_selectable(True) # Allow text selection
|
|
||||||
content_area.pack_start(self._response_label, True, True, 10)
|
self._response_text_view = Gtk.TextView()
|
||||||
|
self._response_text_view.set_editable(False) # Read-only
|
||||||
|
self._response_text_view.set_cursor_visible(True) # Allow cursor navigation for screen readers
|
||||||
|
self._response_text_view.set_wrap_mode(Gtk.WrapMode.WORD)
|
||||||
|
|
||||||
|
# Set initial processing text
|
||||||
|
text_buffer = self._response_text_view.get_buffer()
|
||||||
|
text_buffer.set_text("Processing your question...")
|
||||||
|
|
||||||
|
scrolled_window.add(self._response_text_view)
|
||||||
|
content_area.pack_start(scrolled_window, True, True, 10)
|
||||||
|
|
||||||
# Add close button
|
# Add close button
|
||||||
close_button = dialog.add_button(Gtk.STOCK_CLOSE, Gtk.ResponseType.CLOSE)
|
close_button = dialog.add_button(Gtk.STOCK_CLOSE, Gtk.ResponseType.CLOSE)
|
||||||
@@ -831,8 +867,8 @@ class AIAssistant(Plugin):
|
|||||||
dialog.set_default_size(600, 400)
|
dialog.set_default_size(600, 400)
|
||||||
dialog.show_all()
|
dialog.show_all()
|
||||||
|
|
||||||
# Focus the response label so screen reader announces it
|
# Focus the response text view so screen reader announces it
|
||||||
self._response_label.grab_focus()
|
self._response_text_view.grab_focus()
|
||||||
|
|
||||||
# Process question asynchronously
|
# Process question asynchronously
|
||||||
self._process_user_question_async(dialog, question)
|
self._process_user_question_async(dialog, question)
|
||||||
@@ -855,8 +891,9 @@ class AIAssistant(Plugin):
|
|||||||
data.get('accessibility')
|
data.get('accessibility')
|
||||||
)
|
)
|
||||||
|
|
||||||
# Update the response label
|
# Update the response text view
|
||||||
self._response_label.set_markup(f"<b>Response:</b>\n{response}")
|
text_buffer = self._response_text_view.get_buffer()
|
||||||
|
text_buffer.set_text(f"Response:\n{response}")
|
||||||
|
|
||||||
# Also speak the response
|
# Also speak the response
|
||||||
self._present_message(response)
|
self._present_message(response)
|
||||||
@@ -869,23 +906,27 @@ class AIAssistant(Plugin):
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error getting AI response: {e}")
|
logger.error(f"Error getting AI response: {e}")
|
||||||
self._response_label.set_markup(f"<b>Error:</b> {e}")
|
text_buffer = self._response_text_view.get_buffer()
|
||||||
|
text_buffer.set_text(f"Error: {e}")
|
||||||
self._present_message(f"Error getting AI response: {e}")
|
self._present_message(f"Error getting AI response: {e}")
|
||||||
else:
|
else:
|
||||||
self._response_label.set_markup("<b>Error:</b> No screen data available")
|
text_buffer = self._response_text_view.get_buffer()
|
||||||
|
text_buffer.set_text("Error: No screen data available")
|
||||||
self._present_message("No screen data available")
|
self._present_message("No screen data available")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error processing user question: {e}")
|
logger.error(f"Error processing user question: {e}")
|
||||||
self._response_label.set_markup(f"<b>Error:</b> {e}")
|
text_buffer = self._response_text_view.get_buffer()
|
||||||
|
text_buffer.set_text(f"Error: {e}")
|
||||||
self._present_message(f"Error processing question: {e}")
|
self._present_message(f"Error processing question: {e}")
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
def _show_description_dialog(self, description):
|
def _show_description_dialog(self, description):
|
||||||
"""Show a read-only dialog with the screen description."""
|
"""Show a read-only dialog with the screen description."""
|
||||||
try:
|
try:
|
||||||
|
provider_name = self._provider_type.replace('_', ' ').title()
|
||||||
dialog = Gtk.Dialog(
|
dialog = Gtk.Dialog(
|
||||||
title="AI Screen Description",
|
title=f"AI Screen Description ({provider_name})",
|
||||||
parent=None,
|
parent=None,
|
||||||
flags=Gtk.DialogFlags.MODAL,
|
flags=Gtk.DialogFlags.MODAL,
|
||||||
buttons=(Gtk.STOCK_CLOSE, Gtk.ResponseType.CLOSE)
|
buttons=(Gtk.STOCK_CLOSE, Gtk.ResponseType.CLOSE)
|
||||||
@@ -902,7 +943,7 @@ class AIAssistant(Plugin):
|
|||||||
|
|
||||||
text_view = Gtk.TextView()
|
text_view = Gtk.TextView()
|
||||||
text_view.set_editable(False) # Read-only
|
text_view.set_editable(False) # Read-only
|
||||||
text_view.set_cursor_visible(False)
|
text_view.set_cursor_visible(True) # Allow cursor navigation for screen readers
|
||||||
text_view.set_wrap_mode(Gtk.WrapMode.WORD)
|
text_view.set_wrap_mode(Gtk.WrapMode.WORD)
|
||||||
|
|
||||||
# Set the description text
|
# Set the description text
|
||||||
@@ -1694,54 +1735,91 @@ class AIAssistant(Plugin):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
class AIAssistantMenu(Gtk.Menu):
|
class AIAssistantMenu(Gtk.Dialog):
|
||||||
"""A menu containing AI Assistant options."""
|
"""A dialog containing AI Assistant options."""
|
||||||
|
|
||||||
def __init__(self, handler):
|
def __init__(self, handler):
|
||||||
super().__init__()
|
super().__init__(title="AI Assistant", transient_for=None, flags=0)
|
||||||
self.connect("popped-up", self._on_popped_up)
|
|
||||||
self.on_option_selected = handler
|
self.on_option_selected = handler
|
||||||
|
|
||||||
# AI Assistant menu options
|
# Set dialog properties for better screen reader accessibility
|
||||||
options = [
|
self.set_modal(True)
|
||||||
("ask_question", "Ask Question"),
|
self.set_position(Gtk.WindowPosition.CENTER)
|
||||||
("describe_screen", "Describe Screen"),
|
self.set_default_size(350, 250)
|
||||||
("request_action", "Request Action")
|
|
||||||
]
|
|
||||||
|
|
||||||
for action_id, label in options:
|
# Add OK and Cancel buttons
|
||||||
menu_item = Gtk.MenuItem(label=label)
|
self.add_button("OK", Gtk.ResponseType.OK)
|
||||||
menu_item.connect("activate", self._on_activate, action_id)
|
self.add_button("Cancel", Gtk.ResponseType.CANCEL)
|
||||||
self.append(menu_item)
|
|
||||||
|
|
||||||
def _on_activate(self, widget, action_id):
|
# Connect response signal
|
||||||
"""Handler for menu item activation."""
|
self.connect("response", self._on_response)
|
||||||
|
|
||||||
|
# Add content to dialog
|
||||||
|
content_area = self.get_content_area()
|
||||||
|
|
||||||
|
# Add label
|
||||||
|
label = Gtk.Label(label="Choose an AI Assistant action:")
|
||||||
|
content_area.pack_start(label, False, False, 10)
|
||||||
|
|
||||||
|
# Create radio button group for options
|
||||||
|
self.radio_ask = Gtk.RadioButton.new_with_label(None, "Ask Question")
|
||||||
|
self.radio_describe = Gtk.RadioButton.new_with_label_from_widget(self.radio_ask, "Describe Screen")
|
||||||
|
self.radio_action = Gtk.RadioButton.new_with_label_from_widget(self.radio_ask, "Request Action")
|
||||||
|
|
||||||
|
# Pack radio buttons
|
||||||
|
content_area.pack_start(self.radio_ask, False, False, 5)
|
||||||
|
content_area.pack_start(self.radio_describe, False, False, 5)
|
||||||
|
content_area.pack_start(self.radio_action, False, False, 5)
|
||||||
|
|
||||||
|
# Set first option as selected by default
|
||||||
|
self.radio_ask.set_active(True)
|
||||||
|
|
||||||
|
# Connect keyboard events for Enter key handling
|
||||||
|
self.connect("key-press-event", self._on_key_press)
|
||||||
|
|
||||||
|
print("DEBUG: AIAssistantMenu dialog created with radio buttons")
|
||||||
|
|
||||||
|
def _on_response(self, dialog, response_id):
|
||||||
|
"""Handler for dialog response."""
|
||||||
|
print(f"DEBUG: Dialog response: {response_id}")
|
||||||
|
|
||||||
|
if response_id == Gtk.ResponseType.OK:
|
||||||
|
# Determine which radio button is selected
|
||||||
|
if self.radio_ask.get_active():
|
||||||
|
action_id = "ask_question"
|
||||||
|
elif self.radio_describe.get_active():
|
||||||
|
action_id = "describe_screen"
|
||||||
|
elif self.radio_action.get_active():
|
||||||
|
action_id = "request_action"
|
||||||
|
else:
|
||||||
|
action_id = None
|
||||||
|
|
||||||
|
if action_id:
|
||||||
|
print(f"DEBUG: Selected action: {action_id}")
|
||||||
self.on_option_selected(action_id)
|
self.on_option_selected(action_id)
|
||||||
|
|
||||||
def _on_popped_up(self, *args):
|
self.destroy()
|
||||||
"""Handler for menu popup."""
|
|
||||||
logger.info("AI Assistant menu popped up")
|
def _on_key_press(self, widget, event):
|
||||||
|
"""Handle key press events."""
|
||||||
|
# Allow Enter key to activate OK button
|
||||||
|
if event.keyval == 65293: # Enter key
|
||||||
|
self.response(Gtk.ResponseType.OK)
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
def show_gui(self):
|
def show_gui(self):
|
||||||
"""Shows the AI Assistant menu."""
|
"""Shows the AI Assistant dialog."""
|
||||||
|
try:
|
||||||
|
print("DEBUG: Starting dialog show_gui")
|
||||||
self.show_all()
|
self.show_all()
|
||||||
display = Gdk.Display.get_default()
|
print("DEBUG: Dialog show_all() called - should be visible and accessible now")
|
||||||
seat = display.get_default_seat()
|
|
||||||
device = seat.get_pointer()
|
|
||||||
screen, x, y = device.get_position()
|
|
||||||
|
|
||||||
event = Gdk.Event.new(Gdk.EventType.BUTTON_PRESS)
|
# Present the dialog to ensure it gets focus
|
||||||
event.set_screen(screen)
|
self.present()
|
||||||
event.set_device(device)
|
print("DEBUG: Dialog presented")
|
||||||
event.time = Gtk.get_current_event_time()
|
|
||||||
event.x = x
|
|
||||||
event.y = y
|
|
||||||
|
|
||||||
rect = Gdk.Rectangle()
|
except Exception as e:
|
||||||
rect.x = x
|
print(f"DEBUG: Error in show_gui: {e}")
|
||||||
rect.y = y
|
import traceback
|
||||||
rect.width = 1
|
traceback.print_exc()
|
||||||
rect.height = 1
|
|
||||||
|
|
||||||
window = Gdk.get_default_root_window()
|
|
||||||
self.popup_at_rect(window, rect, Gdk.Gravity.NORTH_WEST, Gdk.Gravity.NORTH_WEST, event)
|
|
||||||
|
|||||||
@@ -180,6 +180,40 @@ class Script:
|
|||||||
|
|
||||||
return keybindings.KeyBindings()
|
return keybindings.KeyBindings()
|
||||||
|
|
||||||
|
def getPluginKeyBindings(self):
|
||||||
|
"""Returns the plugin keybindings for this script."""
|
||||||
|
|
||||||
|
from . import debug
|
||||||
|
debug.printMessage(debug.LEVEL_INFO, "SCRIPT: getPluginKeyBindings() called", True)
|
||||||
|
|
||||||
|
plugin_bindings = keybindings.KeyBindings()
|
||||||
|
|
||||||
|
# Get the plugin system manager
|
||||||
|
try:
|
||||||
|
from . import plugin_system_manager
|
||||||
|
manager = plugin_system_manager.getManager()
|
||||||
|
debug.printMessage(debug.LEVEL_INFO, f"SCRIPT: Plugin manager: {manager}", True)
|
||||||
|
if manager:
|
||||||
|
# Get all active plugins
|
||||||
|
active_plugins = manager.get_active_plugins()
|
||||||
|
debug.printMessage(debug.LEVEL_INFO, f"SCRIPT: Found {len(active_plugins)} active plugins", True)
|
||||||
|
for plugin in active_plugins:
|
||||||
|
# Get bindings from each plugin
|
||||||
|
bindings = plugin.get_bindings()
|
||||||
|
debug.printMessage(debug.LEVEL_INFO, f"SCRIPT: Plugin {plugin.name} has bindings: {bindings}", True)
|
||||||
|
if bindings:
|
||||||
|
# Add each binding from the plugin to our collection
|
||||||
|
for binding in bindings.keyBindings:
|
||||||
|
plugin_bindings.add(binding)
|
||||||
|
debug.printMessage(debug.LEVEL_INFO, f"SCRIPT: Added plugin binding: {binding.asString()}", True)
|
||||||
|
except Exception as e:
|
||||||
|
debug.printMessage(debug.LEVEL_INFO, f"Could not get plugin keybindings: {e}", True)
|
||||||
|
import traceback
|
||||||
|
debug.printMessage(debug.LEVEL_INFO, traceback.format_exc(), True)
|
||||||
|
|
||||||
|
debug.printMessage(debug.LEVEL_INFO, f"SCRIPT: Returning {len(plugin_bindings.keyBindings)} plugin bindings", True)
|
||||||
|
return plugin_bindings
|
||||||
|
|
||||||
def getBrailleBindings(self):
|
def getBrailleBindings(self):
|
||||||
"""Defines the braille bindings for this script.
|
"""Defines the braille bindings for this script.
|
||||||
|
|
||||||
|
|||||||
@@ -435,7 +435,7 @@ presentLiveRegionFromInactiveTab = False
|
|||||||
activePlugins = ['AIAssistant', 'DisplayVersion', 'PluginManager', 'HelloCthulhu', 'ByeCthulhu']
|
activePlugins = ['AIAssistant', 'DisplayVersion', 'PluginManager', 'HelloCthulhu', 'ByeCthulhu']
|
||||||
|
|
||||||
# AI Assistant settings (disabled by default for opt-in behavior)
|
# AI Assistant settings (disabled by default for opt-in behavior)
|
||||||
aiAssistantEnabled = False
|
aiAssistantEnabled = True
|
||||||
aiProvider = AI_PROVIDER_CLAUDE_CODE
|
aiProvider = AI_PROVIDER_CLAUDE_CODE
|
||||||
aiApiKeyFile = ""
|
aiApiKeyFile = ""
|
||||||
aiOllamaModel = "llama3.2-vision"
|
aiOllamaModel = "llama3.2-vision"
|
||||||
|
|||||||
Reference in New Issue
Block a user