diff --git a/.env.example b/.env.example deleted file mode 100644 index fe178b9..0000000 --- a/.env.example +++ /dev/null @@ -1,70 +0,0 @@ -# AI Service API Keys -# このファイルを.envにコピーして、実際のAPIキーを設定してください - -# Google AI Studio API -<<<<<<< HEAD -GOOGLE_AI_API_KEY=AIzaSyC-Zheqmv6aGmaN1rDouHSCtFFH8y8VhNY -======= -GOOGLE_AI_API_KEY=your_google_ai_api_key_here ->>>>>>> 42de7d643d987d98855d441372a3931e7de31809 -GOOGLE_AI_PROJECT_ID=your_project_id_here - -# OpenAI API -OPENAI_API_KEY=your_openai_api_key_here -OPENAI_ORG_ID=your_organization_id_here # オプション - -# Anthropic Claude API -CLAUDE_API_KEY=your_claude_api_key_here - -# Local LLM Settings (LM Studio) -LM_STUDIO_API_BASE=http://localhost:1234/v1 # デフォルトのLM StudioのエンドポイントURL -LM_STUDIO_MODEL=local_model_name # 使用するモデル名 - -# モデル選択設定 -# 使用するAIプロバイダーを選択(google, openai, claude, local) -DEFAULT_AI_PROVIDER=google - -# その他の設定 -DEBUG_MODE=true -LOG_LEVEL=DEBUG - -# セキュリティ設定 -API_KEY_ENCRYPTION_KEY=your_encryption_key_here # APIキーの暗号化用(オプション) - -# プロキシ設定(必要な場合) -# HTTP_PROXY=http://proxy.example.com:8080 -# HTTPS_PROXY=http://proxy.example.com:8080 - -# タイムアウト設定(秒) -API_TIMEOUT=30 -RETRY_ATTEMPTS=3 - -# キャッシュ設定 -ENABLE_RESPONSE_CACHE=true -CACHE_DIR=./cache -MAX_CACHE_SIZE=1000 # エントリー数 - -# レート制限設定 -RATE_LIMIT_REQUESTS=60 -RATE_LIMIT_PERIOD=60 # 秒 - -# モデル固有の設定 -# Google AI -GOOGLE_AI_MODEL=gemini-pro # デフォルトモデル -GOOGLE_AI_TEMPERATURE=0.7 -GOOGLE_AI_MAX_TOKENS=1000 - -# OpenAI -OPENAI_MODEL=gpt-4 # デフォルトモデル -OPENAI_TEMPERATURE=0.7 -OPENAI_MAX_TOKENS=2000 - -# Claude -CLAUDE_MODEL=claude-3-opus-20240229 # デフォルトモデル -CLAUDE_TEMPERATURE=0.7 -CLAUDE_MAX_TOKENS=2000 - -# LM Studio -LM_STUDIO_TEMPERATURE=0.7 -LM_STUDIO_MAX_TOKENS=2000 -LM_STUDIO_CONTEXT_SIZE=4096 \ No newline at end of file diff --git a/.env.txt b/.env.txt deleted file mode 100644 index b5e5e09..0000000 --- a/.env.txt +++ /dev/null @@ -1,66 +0,0 @@ -# AI Service API Keys -# このファイルを.envにコピーして、実際のAPIキーを設定してください - -# Google AI Studio API -GOOGLE_AI_API_KEY=AIzaSyC-Zheqmv6aGmaN1rDouHSCtFFH8y8VhNY -GOOGLE_AI_PROJECT_ID=your_project_id_here - -# OpenAI API -OPENAI_API_KEY=your_openai_api_key_here -OPENAI_ORG_ID=your_organization_id_here # オプション - -# Anthropic Claude API -CLAUDE_API_KEY=your_claude_api_key_here - -# Local LLM Settings (LM Studio) -LM_STUDIO_API_BASE=http://localhost:1234/v1 # デフォルトのLM StudioのエンドポイントURL -LM_STUDIO_MODEL=local_model_name # 使用するモデル名 - -# モデル選択設定 -# 使用するAIプロバイダーを選択(google, openai, claude, local) -DEFAULT_AI_PROVIDER=google - -# その他の設定 -DEBUG_MODE=true -LOG_LEVEL=DEBUG - -# セキュリティ設定 -API_KEY_ENCRYPTION_KEY=your_encryption_key_here # APIキーの暗号化用(オプション) - -# プロキシ設定(必要な場合) -# HTTP_PROXY=http://proxy.example.com:8080 -# HTTPS_PROXY=http://proxy.example.com:8080 - -# タイムアウト設定(秒) -API_TIMEOUT=30 -RETRY_ATTEMPTS=3 - -# キャッシュ設定 -ENABLE_RESPONSE_CACHE=true -CACHE_DIR=./cache -MAX_CACHE_SIZE=1000 # エントリー数 - -# レート制限設定 -RATE_LIMIT_REQUESTS=60 -RATE_LIMIT_PERIOD=60 # 秒 - -# モデル固有の設定 -# Google AI -GOOGLE_AI_MODEL=gemini-pro # デフォルトモデル -GOOGLE_AI_TEMPERATURE=0.7 -GOOGLE_AI_MAX_TOKENS=1000 - -# OpenAI -OPENAI_MODEL=gpt-4 # デフォルトモデル -OPENAI_TEMPERATURE=0.7 -OPENAI_MAX_TOKENS=2000 - -# Claude -CLAUDE_MODEL=claude-3-opus-20240229 # デフォルトモデル -CLAUDE_TEMPERATURE=0.7 -CLAUDE_MAX_TOKENS=2000 - -# LM Studio -LM_STUDIO_TEMPERATURE=0.7 -LM_STUDIO_MAX_TOKENS=2000 -LM_STUDIO_CONTEXT_SIZE=4096 \ No newline at end of file diff --git a/.venv/Scripts/markdownify.exe b/.venv/Scripts/markdownify.exe new file mode 100644 index 0000000..6ac0711 Binary files /dev/null and b/.venv/Scripts/markdownify.exe differ diff --git a/.venv/Scripts/playwright.exe b/.venv/Scripts/playwright.exe new file mode 100644 index 0000000..d7266d2 Binary files /dev/null and b/.venv/Scripts/playwright.exe differ diff --git a/DesktopAgent b/DesktopAgent index e7b1966..ad5b565 160000 --- a/DesktopAgent +++ b/DesktopAgent @@ -1 +1 @@ -Subproject commit e7b1966bb1887504c455381d214b9e86df3c4302 +Subproject commit ad5b56525826954dcab8c378354e5c4297f7e704 diff --git a/README.md b/README.md index de3827f..79d0565 100644 --- a/README.md +++ b/README.md @@ -1,86 +1,58 @@ # デスクトップエージェント -<<<<<<< HEAD -デスクトップエージェントは、自然言語でコンピュータを操作できるPythonアプリケーションです。コマンドの実行、システムモニタリング、ポモドーロタイマーなどの機能を提供します。 +マルチモーダルな自律型AIエージェントシステム。複数のAIエージェントが協調して動作し、デスクトップ操作の自動化と支援を行います。 +音声認識とAI駆動のブラウザ制御により、自然な対話を通じてブラウザ操作や様々なタスクの自動化を実現します。 ## 主な機能 -- 自然言語によるコマンド実行 -- システムリソースのモニタリング(CPU、GPU、メモリ使用率) -- ポモドーロタイマー -- タスク管理 -- システムトレイ常駐 -- コマンド履歴の記録 - -## 必要条件 - -- Python 3.8以上 -- Windows 10/11 -- Google AI (Gemini Pro) APIキー - -## インストール方法 - -1. リポジトリをクローン: -```bash -git clone https://github.com/yourusername/DesktopAgent.git -cd DesktopAgent -``` - -2. 仮想環境を作成して有効化: -```bash -python -m venv .venv -.venv\Scripts\activate # Windows -``` - -3. 必要なパッケージをインストール: -```bash -pip install -r requirements.txt -``` +### 高度なブラウザ操作 +- browser-useパッケージによるインテリジェントなウェブ自動化 +- 自然言語指示によるウェブサイト操作(「YouTubeで猫の動画を再生して」など) +- 要素の探索とクリック、フォーム入力、スクリーンショット撮影機能 +- Playwrightを活用した高レベルなブラウザ制御 -4. 環境変数の設定: -`.env`ファイルを作成し、以下の内容を設定: -``` -GOOGLE_API_KEY=your_api_key_here -======= -マルチモーダルな自律型AIエージェントシステム。複数のAIエージェントが協調して動作し、デスクトップ操作の自動化と支援を行います。 - -## Features -- Autonomous Agent Management -- Real-time System Monitoring -- Cross-platform GUI Interface - -## 主な機能 +### 音声認識と音声コマンド +- 音声コマンドによるブラウザ操作とシステム制御 +- Whisperモデルによるローカルでのオフライン音声認識 +- GPU加速による高速かつ正確な認識 +- 複数言語対応(日本語・英語) ### マルチエージェントシステム - 複数の子エージェントによる並行処理 - エージェント間の自律的な通信と協調 - リソース使用の最適化と負荷分散 -### 学習機能 -- Transformerベースの行動学習 -- マウス操作とボタン入力の予測 -- RAGによるコンテキスト理解 - ### AIモデル統合 - Google AI Studio(デフォルト) - OpenAI(オプション) - Anthropic(オプション) - 自動フォールバックとロードバランシング -### データ管理 -- SQLiteによる永続化 -- セキュアなデータ保存 +### システム監視と制御 +- CPUやGPUの使用率、温度監視 +- メモリ使用状況のリアルタイムモニタリング +- システム操作の自動化(音量調整など) +- OpenHardwareMonitorによる詳細な温度監視(オプション) +- リソース使用率の時系列データ収集と分析機能 +- 異常値検出と自動アラート通知 + +### データ管理とUI +- PyQt6ベースのモダンなインターフェース +- タスク管理とポモドーロタイマー +- SQLiteによるデータの永続化 - 構造化されたログ管理 ## 必要要件 - Python 3.10以上 -- CUDA対応GPU(推奨) +- CUDA対応GPU(音声認識と高度な機能に推奨) +- Windows 10/11 - 必要なPythonパッケージ: - - PyQt6 - - torch - - numpy - - langchain + - PyQt6とPyQt6-WebEngine + - browser-use + - Playwright + - Torch (CUDA対応) + - Transformers - その他(requirements.txtを参照) ## インストール & クイックスタート @@ -92,14 +64,23 @@ cd DesktopAgent python -m pip install -r requirements.txt ``` -2. 設定ファイルの作成: +2. Playwrightブラウザドライバーのインストール: +```bash +python -m playwright install +``` + +3. 設定ファイルの作成: ```bash cp config.example.yaml config.yaml # 使用するAIプロバイダーのAPIキーを設定 ``` -3. アプリケーションの実行: +4. アプリケーションの実行: ```bash +# Windowsの場合 +start.bat + +# または python src/main.py ``` @@ -115,107 +96,83 @@ ai_providers: use_anthropic: false # Anthropic (オプション) ``` +### ブラウザ設定 +```yaml +browser_paths: + chrome: "C:/Program Files/Google/Chrome/Application/chrome.exe" + edge: "C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe" + firefox: "C:/Program Files/Mozilla Firefox/firefox.exe" +``` + ### システム設定 ```yaml system_settings: cpu_threshold: 80 # CPU使用率の閾値 memory_threshold: 85 # メモリ使用率の閾値 ->>>>>>> 42de7d643d987d98855d441372a3931e7de31809 + voice_recognition: + enabled: true + model: "tiny" # tiny, base, small, medium + device: "cuda" # cuda, cpu ``` ## 使用方法 -<<<<<<< HEAD -1. アプリケーションを起動: -```bash -python DesktopAgent/src/main.py -``` - -2. コマンド入力欄に自然言語でコマンドを入力(例:「ブラウザでGoogleを開いて」) - -3. システムトレイアイコンから各種機能にアクセス可能 - -## 利用可能なコマンド - -- ブラウザ操作(Edge, Chrome, デフォルトブラウザ) -- ファイル操作(作成、移動、削除) -- ウィンドウ操作(最小化) -- アプリケーション起動 -- マウス・キーボード操作 -- 画面分析 - -## 開発者向け情報 - -プロジェクト構造: -``` -DesktopAgent/ -├── src/ -│ ├── agent/ -│ │ ├── autonomous_agent.py -│ │ ├── command_interpreter.py -│ │ └── keyboard_monitor.py -│ ├── db/ -│ │ └── models.py -│ ├── gui/ -│ │ └── main_window.py -│ └── main.py -├── requirements.txt -└── README.md -``` - -## ライセンス - -MITライセンス - -## 注意事項 - -- システム操作を行うため、管理者権限が必要な場合があります -- APIキーは適切に管理してください -- キーボード・マウス操作の自動化は慎重に行ってください -======= +### GUI操作 1. アプリケーションの起動: - `start.bat`をダブルクリック - または、コマンドラインで`start.bat`を実行 2. メインウィンドウの操作: - - エージェントの状態監視 - - タスクの割り当て - - メトリクスの確認 - -3. エージェントの管理: - - 新規エージェントの追加 - - 既存エージェントの一時停止/再開 - - タスクの優先順位付け + - ブラウザ操作タブでウェブ自動化 + - 音声認識ボタンで音声コマンドの開始/停止 + - システムモニタリングとタスク管理 + +### 音声コマンド例 +- 「YouTubeで猫の動画を再生して」 +- 「ブラウザでGoogleを開いて」 +- 「Gmailを開いて」 +- 「音量を上げて」 + +### ブラウザ操作コマンド例 +- 「ブラウザでYahooを開いて」 +- 「ブラウザで要素ログインボタンをクリック」 +- 「ブラウザでスクリーンショットを撮る」 +- 「Googleでデスクトップエージェントを検索」 ## 開発者ガイド ### プロジェクト構造 ``` src/ -├── DesktopAgent/ -│ ├── agent/ # エージェント関連 -│ ├── ai/ # AIモデル管理 -│ ├── database/ # データベース -│ ├── gui/ # GUI -│ ├── models/ # 機械学習モデル -│ ├── monitoring/ # システム監視 -│ └── rag/ # RAG実装 +├── agent/ # エージェント関連 +│ ├── command_interpreter.py # コマンド解釈 +│ ├── voice_recognizer.py # 音声認識 +│ └── keyboard_monitor.py # キーボード監視 +├── desktop/ # デスクトップ制御 +│ ├── browser_controller.py # 基本ブラウザ制御 +│ └── advanced_browser_controller.py # 高度ブラウザ制御 +├── db/ # データベース +│ └── models.py # データモデル +├── gui/ # GUI +│ └── main_window.py # メインウィンドウ +├── models/ # 機械学習モデル ├── main.py # エントリーポイント └── config.yaml # 設定ファイル ``` ### 拡張方法 -1. 新しいエージェントの追加: - - `agent/`ディレクトリに新しいエージェントクラスを作成 - - `ChildAgent`クラスを継承 +1. 新しいコマンドの追加: + - `command_interpreter.py`にコマンドパターンとハンドラを追加 -2. 新しいAIモデルの追加: - - `ai/model_manager.py`にプロバイダーを追加 - - 必要なインターフェースを実装 +2. ブラウザ機能の拡張: + - `advanced_browser_controller.py`に新しいブラウザ操作メソッドを追加 + +3. 音声認識の調整: + - `voice_recognizer.py`でモデルサイズや設定をカスタマイズ ## ライセンス -Apache2.0 License +MIT License ## 貢献 @@ -229,8 +186,26 @@ Apache2.0 License 問題が発生した場合は、以下を確認してください: 1. ログファイル(`logs/`ディレクトリ) -2. システムリソースの使用状況 -3. AIプロバイダーの設定 +2. GPUドライバが最新かどうか確認 +3. AIプロバイダーの設定とAPIキーの有効性 詳細なトラブルシューティングは[Wiki](https://github.com/zapabob/DesktopAgent/wiki)を参照してください。 ->>>>>>> 42de7d643d987d98855d441372a3931e7de31809 + +## インストール時のトラブルシューティング + +以下の問題が発生した場合の対処法です: + +### OpenHardwareMonitorの接続エラー +- OpenHardwareMonitorがインストールされていない場合、システム監視の温度表示機能は制限されます +- [OpenHardwareMonitor](https://openhardwaremonitor.org/downloads/)をインストールすることで解決できます +- アプリケーションは管理者権限で実行する必要があります + +### 音声認識関連の問題 +- CUDA対応GPUが必要です +- PyTorch CUDAバージョンが正しくインストールされていることを確認してください +- CPUモードで実行する場合は、`config.yaml`で`device: "cpu"`に設定してください + +### ブラウザ制御の問題 +- Playwrightの最新バージョンがインストールされていることを確認してください +- ブラウザパスが正しく設定されていることを確認してください +- 必要に応じて`browser-use`パッケージを更新してください:`pip install -U browser-use` diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..034e848 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,21 @@ +# Security Policy + +## Supported Versions + +Use this section to tell people about which versions of your project are +currently being supported with security updates. + +| Version | Supported | +| ------- | ------------------ | +| 5.1.x | :white_check_mark: | +| 5.0.x | :x: | +| 4.0.x | :white_check_mark: | +| < 4.0 | :x: | + +## Reporting a Vulnerability + +Use this section to tell people how to report a vulnerability. + +Tell them where to go, how often they can expect to get an update on a +reported vulnerability, what to expect if the vulnerability is accepted or +declined, etc. diff --git a/browser_integration/browser_use_browser.py b/browser_integration/browser_use_browser.py new file mode 100644 index 0000000..8a398da Binary files /dev/null and b/browser_integration/browser_use_browser.py differ diff --git a/src/agent/__init__.py b/src/agent/__init__.py index b427ca1..2af9f26 100644 --- a/src/agent/__init__.py +++ b/src/agent/__init__.py @@ -1,4 +1,3 @@ -<<<<<<< HEAD """ 自律型デスクトップエージェントのコアモジュール """ @@ -6,13 +5,4 @@ from .autonomous_agent import AutonomousAgent from .command_interpreter import CommandInterpreter -======= -""" -自律型デスクトップエージェントのコアモジュール -""" - -from .autonomous_agent import AutonomousAgent -from .command_interpreter import CommandInterpreter - ->>>>>>> 42de7d643d987d98855d441372a3931e7de31809 __all__ = ['AutonomousAgent', 'CommandInterpreter'] \ No newline at end of file diff --git a/src/agent/autonomous_agent.py b/src/agent/autonomous_agent.py index b7f7b62..7d3ca1b 100644 --- a/src/agent/autonomous_agent.py +++ b/src/agent/autonomous_agent.py @@ -1,413 +1,5 @@ -<<<<<<< HEAD import logging -from typing import Dict, Any, List -from langchain_core.language_models import BaseChatModel -from langchain_core.prompts import ChatPromptTemplate -from langchain_core.output_parsers import JsonOutputParser -from langchain_google_genai import ChatGoogleGenerativeAI, GoogleGenerativeAI -import google.generativeai as genai -from PIL import Image -import io -import os -from dotenv import load_dotenv -import webbrowser -import subprocess -import pyautogui -import keyboard -import time -from .keyboard_monitor import KeyboardMonitor - -class AutonomousAgent: - def __init__(self, db_logger): - self.logger = logging.getLogger(__name__) - self.db_logger = db_logger - self.keyboard_monitor = KeyboardMonitor() - - # 環境変数の読み込み - load_dotenv() - - # Gemini Proの初期化 - genai.configure(api_key=os.getenv("GOOGLE_API_KEY")) - - # テキスト用モデル - self.llm = ChatGoogleGenerativeAI(model="gemini-pro") - - # マルチモーダル用モデル - self.vision_model = GoogleGenerativeAI(model="gemini-pro-vision") - - # 出力パーサーの設定 - self.parser = JsonOutputParser() - - # プロンプトテンプレートの設定 - self.prompt = ChatPromptTemplate.from_messages([ - ("system", """あなたはデスクトップ操作を支援するAIアシスタントです。 - ユーザーの要求を理解し、適切なコマンドに変換してください。 - - 出力は以下のJSON形式で返してください: - { - "command_type": "BROWSER|FILE|DESKTOP|MOUSE|KEYBOARD|VISION", - "parameters": { - "action": "実行するアクション", - "browser_type": "edge|chrome|browser", - "url": "開くURL", - "path": "ファイルパス", - "window": "ウィンドウ名", - "application": "アプリケーション名", - "x": "X座標", - "y": "Y座標", - "clicks": "クリック回数", - "button": "left|right|middle", - "duration": "操作時間(秒)", - "keys": "キー操作シーケンス", - "speed": "再生速度", - "screenshot": "スクリーンショットの有無", - "region": "キャプチャ領域" - } - } - """), - ("human", "{input}") - ]) - - # チェーンの構築 - self.chain = self.prompt | self.llm | self.parser - - # ブラウザパスの設定 - self.browser_paths = { - 'edge': r'C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe', - 'chrome': r'C:\Program Files\Google\Chrome\Application\chrome.exe' - } - - # マウス操作の安全性確保 - pyautogui.FAILSAFE = True # 画面端に移動でプログラム停止 - pyautogui.PAUSE = 0.5 # 操作間の待機時間 - - # キーボードモニターの開始 - self.keyboard_monitor.start() - - def execute_command(self, command_type: str, params: Dict[str, Any]) -> bool: - """コマンドを実行し、結果をログに記録""" - try: - # コマンド実行の開始をログに記録 - self.db_logger.log_operation( - operation_type=command_type, - details=f"コマンド実行開始: {params}", - status="RUNNING" - ) - - # コマンドタイプに応じた処理 - if command_type == "BROWSER": - success = self._handle_browser_command(params) - elif command_type == "FILE": - success = self._handle_file_command(params) - elif command_type == "DESKTOP": - success = self._handle_desktop_command(params) - elif command_type == "MOUSE": - success = self._handle_mouse_command(params) - elif command_type == "KEYBOARD": - success = self._handle_keyboard_command(params) - elif command_type == "VISION": - success = self._handle_vision_command(params) - else: - raise ValueError(f"不明なコマンドタイプ: {command_type}") - - # 実行結果をログに記録 - status = "SUCCESS" if success else "FAILURE" - self.db_logger.log_operation( - operation_type=command_type, - details=f"コマンド実行完了: {params}", - status=status - ) - - return success - - except Exception as e: - self.logger.error(f"コマンド実行エラー: {e}") - self.db_logger.log_operation( - operation_type=command_type, - details=f"コマンド実行エラー: {params}", - status="ERROR", - error_message=str(e) - ) - return False - - def _handle_browser_command(self, params: Dict[str, Any]) -> bool: - """ブラウザ操作コマンドの処理""" - try: - browser_type = params.get("browser_type", "browser") - url = params.get("url") - if not url: - return False - - return self._open_browser(browser_type, url) - except Exception as e: - self.logger.error(f"ブラウザ操作エラー: {e}") - return False - - def _handle_file_command(self, params: Dict[str, Any]) -> bool: - """ファイル操作コマンドの処理""" - try: - import shutil - from pathlib import Path - - action = params.get("action") - path = params.get("path") - if not action or not path: - return False - - if action == "mkdir": - Path(path).mkdir(parents=True, exist_ok=True) - return True - elif action == "move": - source = params.get("source") - destination = params.get("destination") - if not source or not destination: - return False - shutil.move(source, destination) - return True - elif action == "delete": - path_obj = Path(path) - if path_obj.is_file(): - path_obj.unlink() - elif path_obj.is_dir(): - shutil.rmtree(path_obj) - return True - return False - except Exception as e: - self.logger.error(f"ファイル操作エラー: {e}") - return False - - def _handle_desktop_command(self, params: Dict[str, Any]) -> bool: - """デスクトップ操作コマンドの処理""" - try: - action = params.get("action") - if not action: - return False - - if action == "minimize": - window = params.get("window") - if not window: - return False - # TODO: ウィンドウの最小化処理を実装 - return True - elif action == "launch": - app = params.get("application") - if not app: - return False - subprocess.Popen(app) - return True - return False - except Exception as e: - self.logger.error(f"デスクトップ操作エラー: {e}") - return False - - def _handle_mouse_command(self, params: Dict[str, Any]) -> bool: - """マウス操作コマンドの処理""" - try: - action = params.get("action") - if not action: - return False - - if action == "move": - # 座標移動 - x = params.get("x") - y = params.get("y") - duration = float(params.get("duration", 0.5)) - if x is not None and y is not None: - pyautogui.moveTo(x, y, duration=duration) - return True - - elif action == "click": - # クリック - button = params.get("button", "left") - clicks = int(params.get("clicks", 1)) - x = params.get("x") - y = params.get("y") - - if x is not None and y is not None: - pyautogui.click(x, y, clicks=clicks, button=button) - else: - pyautogui.click(clicks=clicks, button=button) - return True - - elif action == "drag": - # ドラッグ - start_x = params.get("start_x") - start_y = params.get("start_y") - end_x = params.get("end_x") - end_y = params.get("end_y") - duration = float(params.get("duration", 0.5)) - - if all(v is not None for v in [start_x, start_y, end_x, end_y]): - pyautogui.moveTo(start_x, start_y) - pyautogui.dragTo(end_x, end_y, duration=duration) - return True - - elif action == "scroll": - # スクロール - amount = int(params.get("amount", 0)) - pyautogui.scroll(amount) - return True - - return False - - except Exception as e: - self.logger.error(f"マウス操作エラー: {e}") - return False - - def _handle_keyboard_command(self, params: Dict[str, Any]) -> bool: - """キーボード操作コマンドの処理""" - try: - action = params.get("action") - if not action: - return False - - if action == "record": - # キー操作の記録を開始 - self.keyboard_monitor.start_recording() - return True - - elif action == "stop": - # キー操作の記録を停止 - events = self.keyboard_monitor.stop_recording() - self.logger.info(f"記録したキー操作: {self.keyboard_monitor.get_key_sequence()}") - return True - - elif action == "replay": - # 記録したキー操作を再生 - events = params.get("events", []) - speed = float(params.get("speed", 1.0)) - if events: - self.keyboard_monitor.replay_events(events, speed) - return True - - elif action == "type": - # テキストを入力 - text = params.get("text") - if text: - keyboard.write(text) - return True - - elif action == "hotkey": - # ホットキーを実行 - keys = params.get("keys", "").split("+") - if keys: - keyboard.press_and_release("+".join(keys)) - return True - - return False - - except Exception as e: - self.logger.error(f"キーボード操作エラー: {e}") - return False - - def _handle_vision_command(self, params: Dict[str, Any]) -> bool: - """画像認識コマンドの処理""" - try: - action = params.get("action") - if not action: - return False - - if action == "analyze": - # スクリーンショットを取得 - if params.get("region"): - x1, y1, x2, y2 = params["region"] - screenshot = pyautogui.screenshot(region=(x1, y1, x2-x1, y2-y1)) - else: - screenshot = pyautogui.screenshot() - - # PILイメージをバイトストリームに変換 - img_byte_arr = io.BytesIO() - screenshot.save(img_byte_arr, format='PNG') - img_byte_arr = img_byte_arr.getvalue() - - # Geminiで画像を分析 - response = self.vision_model.generate_content([ - "画面の内容を分析して、何が表示されているか説明してください。", - img_byte_arr - ]) - - self.logger.info(f"画像分析結果: {response.text}") - return True - - elif action == "analyze_video": - # 動画ファイルを読み込み - video_path = params.get("video_path") - if not video_path: - return False - - import cv2 - cap = cv2.VideoCapture(video_path) - frames = [] - - # 1秒ごとにフレームを抽出 - fps = cap.get(cv2.CAP_PROP_FPS) - frame_interval = int(fps) - frame_count = 0 - - while cap.isOpened(): - ret, frame = cap.read() - if not ret: - break - - if frame_count % frame_interval == 0: - # OpenCV形式からPIL形式に変換 - frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) - frame_pil = Image.fromarray(frame_rgb) - - # フレームをバイトストリームに変換 - frame_byte_arr = io.BytesIO() - frame_pil.save(frame_byte_arr, format='PNG') - frames.append(frame_byte_arr.getvalue()) - - frame_count += 1 - - cap.release() - - # Geminiで動画を分析 - response = self.vision_model.generate_content([ - "この動画の内容を時系列で説明してください。", - *frames - ]) - - self.logger.info(f"動画分析結果: {response.text}") - return True - - return False - - except Exception as e: - self.logger.error(f"画像認識エラー: {e}") - return False - - def _open_browser(self, browser_type: str, url: str) -> bool: - """指定されたブラウザでURLを開く""" - try: - if browser_type == 'browser': - # デフォルトブラウザで開く - webbrowser.open(url) - return True - - browser_path = self.browser_paths.get(browser_type) - if not browser_path: - self.logger.error(f"未サポートのブラウザ: {browser_type}") - return False - - # 指定されたブラウザで開く - subprocess.Popen([browser_path, url]) - self.logger.info(f"{browser_type}で{url}を開きました") - return True - - except Exception as e: - self.logger.error(f"ブラウザ起動エラー: {e}") - return False - - def cleanup(self): - """リソースのクリーンアップ""" - try: - self.keyboard_monitor.stop() - except Exception as e: -======= -import logging -from typing import Dict, Any, List +from typing import Dict, Any, List, Optional, Tuple from langchain_core.language_models import BaseChatModel from langchain_core.prompts import ChatPromptTemplate from langchain_core.output_parsers import JsonOutputParser @@ -426,12 +18,14 @@ def cleanup(self): import keyboard import time from .keyboard_monitor import KeyboardMonitor +from .command_interpreter import CommandInterpreter class AutonomousAgent: def __init__(self, db_logger): self.logger = logging.getLogger(__name__) self.db_logger = db_logger self.keyboard_monitor = KeyboardMonitor() + self.command_interpreter = CommandInterpreter() # 環境変数の読み込み load_dotenv() @@ -493,10 +87,6 @@ def __init__(self, db_logger): except Exception as e: self.logger.error(f"Anthropic初期化エラー: {e}") - # モデルが初期化されていない場合はエラーログ - if self.llm is None: - self.logger.error("有効なAIモデルが初期化されませんでした。APIキーを確認してください。") - # 出力パーサーの設定 self.parser = JsonOutputParser() @@ -530,8 +120,14 @@ def __init__(self, db_logger): ("human", "{input}") ]) - # チェーンの構築 - self.chain = self.prompt | self.llm | self.parser + # モデルが初期化されていない場合はエラーログ + if self.llm is None: + self.logger.error("有効なAIモデルが初期化されませんでした。APIキーを確認してください。") + # APIキーがない場合はチェーンを構築しない + self.chain = None + else: + # チェーンの構築 + self.chain = self.prompt | self.llm | self.parser # ブラウザパスの設定 self.browser_paths = { @@ -556,7 +152,8 @@ def execute_command(self, command_type: str, params: Dict[str, Any]) -> bool: status="RUNNING" ) - # コマンドタイプに応じた処理 + # コマンドタイプに応じた処理を実行 + success = False if command_type == "BROWSER": success = self._handle_browser_command(params) elif command_type == "FILE": @@ -570,28 +167,62 @@ def execute_command(self, command_type: str, params: Dict[str, Any]) -> bool: elif command_type == "VISION": success = self._handle_vision_command(params) else: - raise ValueError(f"不明なコマンドタイプ: {command_type}") - + self.logger.warning(f"不明なコマンドタイプ: {command_type}") + # 実行結果をログに記録 - status = "SUCCESS" if success else "FAILURE" self.db_logger.log_operation( operation_type=command_type, details=f"コマンド実行完了: {params}", - status=status + status="SUCCESS" if success else "FAILURE" ) return success except Exception as e: self.logger.error(f"コマンド実行エラー: {e}") + + # エラーをログに記録 self.db_logger.log_operation( operation_type=command_type, - details=f"コマンド実行エラー: {params}", - status="ERROR", + details=f"コマンド実行エラー: {params}, {str(e)}", + status="FAILURE", error_message=str(e) ) + return False + def process_natural_language(self, text: str) -> Optional[Tuple[str, Dict[str, Any]]]: + """ + 自然言語のテキストを処理し、コマンドタイプとパラメータを生成 + + Args: + text (str): 処理する自然言語テキスト + + Returns: + Optional[Tuple[str, Dict[str, Any]]]: コマンドタイプとパラメータ、または解析できない場合はNone + """ + try: + # LLMチェーンが利用可能な場合は自然言語処理を実行 + if self.chain is not None: + result = self.chain.invoke({"input": text}) + command_type = result.get("command_type") + parameters = result.get("parameters", {}) + + if command_type: + return command_type, parameters + + # LLMが利用できない場合は、CommandInterpreterを使用 + result = self.command_interpreter.interpret(text) + if result: + return result + + self.logger.warning(f"自然言語を解析できませんでした: {text}") + return None + + except Exception as e: + self.logger.error(f"自然言語処理エラー: {e}") + return None + def _handle_browser_command(self, params: Dict[str, Any]) -> bool: """ブラウザ操作コマンドの処理""" try: @@ -867,5 +498,4 @@ def cleanup(self): try: self.keyboard_monitor.stop() except Exception as e: ->>>>>>> 42de7d643d987d98855d441372a3931e7de31809 self.logger.error(f"クリーンアップエラー: {e}") \ No newline at end of file diff --git a/src/agent/command_interpreter.py b/src/agent/command_interpreter.py index 38b00fa..59edcd5 100644 --- a/src/agent/command_interpreter.py +++ b/src/agent/command_interpreter.py @@ -11,7 +11,7 @@ import asyncio from .keyboard_monitor import KeyboardMonitor -from ..desktop.browser_controller import BrowserController +from src.desktop.browser_controller import BrowserController from browser_use import Browser class CommandInterpreter: @@ -47,6 +47,51 @@ def __init__(self): (r'(音|サウンド)(を)?(ミュート|消す)', self._mute), ] + def interpret(self, command: str) -> Optional[Tuple[str, Dict[str, Any]]]: + """ + コマンドを解釈して、コマンドタイプとパラメータを返す + + Args: + command (str): 解釈するコマンド文字列 + + Returns: + Optional[Tuple[str, Dict[str, Any]]]: コマンドタイプとパラメータのタプル、 + または解釈できない場合はNone + """ + for pattern, handler in self.command_patterns: + match = re.search(pattern, command, re.IGNORECASE) + if match: + try: + result = handler(match) + if result: + command_type, params = result + return command_type, params + except Exception as e: + self.logger.error(f"コマンド解釈エラー: {e}") + return None + + # デフォルトのコマンド解釈ロジック + # ブラウザコマンド + if re.search(r'(edge|chrome|firefox|ブラウザ)\s+(https?://.+)', command, re.IGNORECASE): + match = re.search(r'(edge|chrome|firefox|ブラウザ)\s+(https?://.+)', command, re.IGNORECASE) + browser = match.group(1).lower() + url = match.group(2).strip() + return 'browser', {'browser': browser, 'url': url} + + # 最小化コマンド + if re.search(r'(.+)を?(最小化|最小化して)', command): + match = re.search(r'(.+)を?(最小化|最小化して)', command) + window = match.group(1).strip() + return 'minimize', {'window': window} + + # アプリケーション起動コマンド + if re.search(r'(.+)を?(起動|開いて|実行して)', command): + match = re.search(r'(.+)を?(起動|開いて|実行して)', command) + app = match.group(1).strip() + return 'launch', {'app': app} + + return None + def start_monitoring(self): """キーボードの監視を開始""" self.keyboard_monitor.start(self._on_key_press) @@ -71,7 +116,7 @@ def stop_recording(self) -> str: command = ''.join(self.command_buffer) self.command_buffer.clear() return command - + def initialize_browser(self): """browser-useのブラウザを初期化""" if self.browser is None: @@ -336,3 +381,47 @@ def close_browser(self): pass finally: self.browser_loop = None + + def _open_browser(self, match): + """ブラウザを開くコマンドを処理""" + url = match.group(3).strip() + return 'browser', {'browser': 'default', 'url': url} + + def _search_youtube(self, match): + """YouTubeで検索するコマンドを処理""" + query = match.group(3).strip() + return 'youtube', {'query': query} + + def _search_google(self, match): + """Googleで検索するコマンドを処理""" + query = match.group(3).strip() + return 'google', {'query': query} + + def _open_gmail(self, match): + """Gmailを開くコマンドを処理""" + return 'gmail', {} + + def _open_calendar(self, match): + """Googleカレンダーを開くコマンドを処理""" + return 'calendar', {} + + def _click_element(self, match): + """ブラウザ要素をクリックするコマンドを処理""" + element = match.group(3).strip() + return 'click_element', {'element': element} + + def _take_screenshot(self, match): + """スクリーンショットを撮るコマンドを処理""" + return 'screenshot', {} + + def _volume_up(self, match): + """音量を上げるコマンドを処理""" + return 'volume', {'action': 'up'} + + def _volume_down(self, match): + """音量を下げるコマンドを処理""" + return 'volume', {'action': 'down'} + + def _mute(self, match): + """ミュートするコマンドを処理""" + return 'volume', {'action': 'mute'} diff --git a/src/agent/keyboard_monitor.py b/src/agent/keyboard_monitor.py index 3b44515..ecfb4b6 100644 --- a/src/agent/keyboard_monitor.py +++ b/src/agent/keyboard_monitor.py @@ -1,83 +1,3 @@ -<<<<<<< HEAD -import keyboard -import logging -from typing import List, Optional, Callable -import time -from threading import Thread, Event - -class KeyboardMonitor(Thread): - def __init__(self): - super().__init__() - self.logger = logging.getLogger(__name__) - self.recording = False - self.key_events: List[dict] = [] - self.stop_event = Event() - self.callback: Optional[Callable] = None - - def start_recording(self, callback: Optional[Callable] = None): - """キーボード操作の記録を開始""" - self.recording = True - self.key_events = [] - self.callback = callback - if not self.is_alive(): - self.start() - - def stop_recording(self) -> List[dict]: - """キーボード操作の記録を停止して記録を返す""" - self.recording = False - return self.key_events - - def run(self): - """キーボード操作の監視を実行""" - try: - while not self.stop_event.is_set(): - if self.recording: - event = keyboard.read_event() - if event.event_type == 'down': - key_event = { - 'key': event.name, - 'time': time.time() - } - self.key_events.append(key_event) - self.logger.debug(f"キー入力: {event.name}") - - if self.callback: - self.callback(key_event) - time.sleep(0.01) # CPU負荷軽減 - - except Exception as e: - self.logger.error(f"キーボード監視エラー: {e}") - - def stop(self): - """監視を停止""" - self.stop_event.set() - if self.is_alive(): - self.join() - - def replay_events(self, events: List[dict], speed: float = 1.0): - """記録したキーボード操作を再生""" - try: - if not events: - return - - start_time = events[0]['time'] - for event in events: - # 前のイベントとの時間差を計算 - wait_time = (event['time'] - start_time) / speed - time.sleep(max(0, wait_time)) - - # キーを押下 - keyboard.press_and_release(event['key']) - self.logger.debug(f"キー再生: {event['key']}") - - start_time = event['time'] - - except Exception as e: - self.logger.error(f"キーボード再生エラー: {e}") - - def get_key_sequence(self) -> str: - """記録したキー操作を文字列として取得""" -======= import keyboard import logging from typing import List, Optional, Callable @@ -111,19 +31,23 @@ def start(self, callback=None): def stop(self): """Stop monitoring keyboard events""" self._is_running = False + self.stop_event.set() if self._thread: - self._thread.join() + self._thread.join(timeout=1.0) # 1秒でタイムアウト self._thread = None def _monitor_loop(self): """Main monitoring loop""" while self._is_running: - event = keyboard.read_event() - if event.event_type == keyboard.KEY_DOWN: - with self._buffer_lock: - self._buffer.append(event.name) - if self._callback: - self._callback(event.name) + try: + event = keyboard.read_event() + if event.event_type == keyboard.KEY_DOWN: + with self._buffer_lock: + self._buffer.append(event.name) + if self._callback: + self._callback(event.name) + except Exception as e: + self.logger.error(f"キーボード監視ループエラー: {e}") time.sleep(0.01) # Small delay to prevent high CPU usage def get_buffer(self): @@ -167,12 +91,6 @@ def run(self): except Exception as e: self.logger.error(f"キーボード監視エラー: {e}") - def stop(self): - """監視を停止""" - self.stop_event.set() - if self._is_running: - self.stop() - def replay_events(self, events: List[dict], speed: float = 1.0): """記録したキーボード操作を再生""" try: @@ -196,5 +114,4 @@ def replay_events(self, events: List[dict], speed: float = 1.0): def get_key_sequence(self) -> str: """記録したキー操作を文字列として取得""" ->>>>>>> 42de7d643d987d98855d441372a3931e7de31809 return ' + '.join([event['key'] for event in self.key_events]) \ No newline at end of file diff --git a/src/db/__init__.py b/src/db/__init__.py index f8a1797..247bcf6 100644 --- a/src/db/__init__.py +++ b/src/db/__init__.py @@ -1,20 +1,9 @@ -<<<<<<< HEAD """ データベース操作、ログ記録、分析機能を提供するモジュール """ -from db.models import DatabaseManager -from db.logger import DatabaseLogger -from db.analyzer import OperationAnalyzer +from .models import DatabaseManager +from .logger import DatabaseLogger +from .analyzer import OperationAnalyzer -======= -""" -データベース操作、ログ記録、分析機能を提供するモジュール -""" - -from db.models import DatabaseManager -from db.logger import DatabaseLogger -from db.analyzer import OperationAnalyzer - ->>>>>>> 42de7d643d987d98855d441372a3931e7de31809 __all__ = ['DatabaseManager', 'DatabaseLogger', 'OperationAnalyzer'] \ No newline at end of file diff --git a/src/db/analyzer.py b/src/db/analyzer.py index 6afac1c..7952c43 100644 --- a/src/db/analyzer.py +++ b/src/db/analyzer.py @@ -1,4 +1,3 @@ -<<<<<<< HEAD import sqlite3 from pathlib import Path from datetime import datetime, timedelta @@ -50,57 +49,4 @@ def analyze_system_performance(self, hours=24): return pd.read_sql_query(query, conn, params=(f'-{hours} hours',)) finally: -======= -import sqlite3 -from pathlib import Path -from datetime import datetime, timedelta -import pandas as pd - -class OperationAnalyzer: - def __init__(self, db_path): - self.db_path = db_path - - def get_connection(self): - return sqlite3.connect(str(self.db_path)) - - def analyze_operation_history(self, days=7): - """直近の操作履歴を分析""" - conn = self.get_connection() - try: - query = ''' - SELECT - date(timestamp) as date, - operation_type, - count(*) as count, - sum(case when status = 'SUCCESS' then 1 else 0 end) as success_count, - sum(case when status = 'FAILURE' then 1 else 0 end) as failure_count - FROM operation_history - WHERE timestamp >= date('now', ?) - GROUP BY date(timestamp), operation_type - ORDER BY date(timestamp) DESC - ''' - - return pd.read_sql_query(query, conn, params=(f'-{days} days',)) - finally: - conn.close() - - def analyze_system_performance(self, hours=24): - """システムパフォーマンスの分析""" - conn = self.get_connection() - try: - query = ''' - SELECT - strftime('%Y-%m-%d %H:00:00', timestamp) as hour, - avg(cpu_usage) as avg_cpu, - avg(memory_usage) as avg_memory, - count(distinct active_window) as unique_windows - FROM system_state - WHERE timestamp >= datetime('now', ?) - GROUP BY strftime('%Y-%m-%d %H:00:00', timestamp) - ORDER BY hour DESC - ''' - - return pd.read_sql_query(query, conn, params=(f'-{hours} hours',)) - finally: ->>>>>>> 42de7d643d987d98855d441372a3931e7de31809 conn.close() \ No newline at end of file diff --git a/src/db/logger.py b/src/db/logger.py index 9aad31c..21b86f4 100644 --- a/src/db/logger.py +++ b/src/db/logger.py @@ -1,4 +1,3 @@ -<<<<<<< HEAD import logging import threading from typing import Optional @@ -49,56 +48,4 @@ def close(self): with self._lock: if self.conn: self.cursor.close() -======= -import logging -import threading -from typing import Optional - -class DatabaseLogger: - _instance = None - _lock = threading.Lock() - - def __init__(self, conn): - self.conn = conn - self.cursor = conn.cursor() - self.logger = logging.getLogger(__name__) - - @classmethod - def get_instance(cls, conn=None): - if not cls._instance and conn: - with cls._lock: - if not cls._instance: - cls._instance = cls(conn) - return cls._instance - - def log_operation(self, operation_type: str, details: str, status: str = "SUCCESS", error_message: Optional[str] = None): - with self._lock: - try: - self.cursor.execute(''' - INSERT INTO operation_history - (operation_type, operation_details, status, error_message) - VALUES (?, ?, ?, ?) - ''', (operation_type, details, status, error_message)) - self.conn.commit() - except Exception as e: - self.logger.error(f"操作ログの記録に失敗: {e}") - - def log_system_state(self, cpu_usage: float, memory_usage: float, active_window: str): - with self._lock: - try: - self.cursor.execute(''' - INSERT INTO system_state - (cpu_usage, memory_usage, active_window) - VALUES (?, ?, ?) - ''', (cpu_usage, memory_usage, active_window)) - self.conn.commit() - except Exception as e: - self.logger.error(f"システム状態の記録に失敗: {e}") - - def close(self): - """データベース接続を閉じる""" - with self._lock: - if self.conn: - self.cursor.close() ->>>>>>> 42de7d643d987d98855d441372a3931e7de31809 self.conn.close() \ No newline at end of file diff --git a/src/db/models.py b/src/db/models.py index 1041f21..b71b538 100644 --- a/src/db/models.py +++ b/src/db/models.py @@ -1,4 +1,3 @@ -<<<<<<< HEAD from pathlib import Path import sqlite3 import logging @@ -103,110 +102,4 @@ def close_all(self): self._local.connection.close() del self._local.connection if hasattr(self._local, 'logger'): -======= -from pathlib import Path -import sqlite3 -import logging -import threading -from typing import Optional -from datetime import datetime - -class DatabaseLogger: - def __init__(self, conn: sqlite3.Connection): - self.conn = conn - self.cursor = conn.cursor() - self.logger = logging.getLogger(__name__) - - def log_operation(self, operation_type: str, details: str, status: str = "SUCCESS", error_message: Optional[str] = None): - """操作をログに記録""" - try: - self.cursor.execute(''' - INSERT INTO operation_history - (operation_type, operation_details, status, error_message) - VALUES (?, ?, ?, ?) - ''', (operation_type, details, status, error_message)) - self.conn.commit() - except Exception as e: - self.logger.error(f"操作ログの記録に失敗: {e}") - - def log_system_state(self, cpu_usage: float, memory_usage: float, active_window: str): - """システム状態をログに記録""" - try: - self.cursor.execute(''' - INSERT INTO system_state - (cpu_usage, memory_usage, active_window) - VALUES (?, ?, ?) - ''', (cpu_usage, memory_usage, active_window)) - self.conn.commit() - except Exception as e: - self.logger.error(f"システム状態の記録に失敗: {e}") - -class DatabaseManager: - _instance = None - _lock = threading.Lock() - _local = threading.local() - - def __new__(cls): - if cls._instance is None: - with cls._lock: - if cls._instance is None: - cls._instance = super().__new__(cls) - return cls._instance - - def __init__(self): - if not hasattr(self, 'initialized'): - self.logger = logging.getLogger(__name__) - self.db_path = Path('data/agent.db') - self.db_path.parent.mkdir(parents=True, exist_ok=True) - self.initialized = True - - def get_connection(self) -> sqlite3.Connection: - """スレッドローカルなデータベース接続を取得""" - if not hasattr(self._local, 'connection'): - self._local.connection = sqlite3.connect(self.db_path) - return self._local.connection - - def get_logger(self) -> DatabaseLogger: - """データベースロガーを取得""" - if not hasattr(self._local, 'logger'): - self._local.logger = DatabaseLogger(self.get_connection()) - return self._local.logger - - def initialize_database(self): - """データベースの初期化""" - conn = self.get_connection() - cursor = conn.cursor() - - # システム状態テーブル - cursor.execute(''' - CREATE TABLE IF NOT EXISTS system_state ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, - cpu_usage REAL, - memory_usage REAL, - active_window TEXT - ) - ''') - - # 操作履歴テーブル - cursor.execute(''' - CREATE TABLE IF NOT EXISTS operation_history ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, - operation_type TEXT, - operation_details TEXT, - status TEXT, - error_message TEXT - ) - ''') - - conn.commit() - - def close_all(self): - """全てのデータベース接続を閉じる""" - if hasattr(self._local, 'connection'): - self._local.connection.close() - del self._local.connection - if hasattr(self._local, 'logger'): ->>>>>>> 42de7d643d987d98855d441372a3931e7de31809 del self._local.logger \ No newline at end of file diff --git a/src/desktop/__init__.py b/src/desktop/__init__.py index 224418d..c24d2ca 100644 --- a/src/desktop/__init__.py +++ b/src/desktop/__init__.py @@ -1,4 +1,3 @@ -<<<<<<< HEAD """ デスクトップ操作と監視機能を提供するモジュール """ @@ -6,13 +5,4 @@ from .desktop_controller import DesktopController from .system_monitor import SystemMonitor -======= -""" -デスクトップ操作と監視機能を提供するモジュール -""" - -from .desktop_controller import DesktopController -from .system_monitor import SystemMonitor - ->>>>>>> 42de7d643d987d98855d441372a3931e7de31809 __all__ = ['DesktopController', 'SystemMonitor'] \ No newline at end of file diff --git a/src/desktop/desktop_controller.py b/src/desktop/desktop_controller.py index 0ced255..b5e3800 100644 --- a/src/desktop/desktop_controller.py +++ b/src/desktop/desktop_controller.py @@ -1,4 +1,3 @@ -<<<<<<< HEAD import pyautogui import win32gui import win32con @@ -74,81 +73,4 @@ def get_system_state(self) -> Dict[str, Any]: except Exception as e: self.logger.error(f"システム状態取得エラー: {e}") -======= -import pyautogui -import win32gui -import win32con -import psutil -import logging -from typing import Optional, Dict, Any - -class DesktopController: - def __init__(self, db_logger): - self.logger = logging.getLogger(__name__) - self.db_logger = db_logger - pyautogui.FAILSAFE = True - - def minimize_window(self, window_title: str) -> bool: - """指定したウィンドウを最小化""" - try: - def callback(hwnd, window_title): - if win32gui.IsWindowVisible(hwnd) and window_title.lower() in win32gui.GetWindowText(hwnd).lower(): - win32gui.ShowWindow(hwnd, win32con.SW_MINIMIZE) - return True - return False - - found = win32gui.EnumWindows(lambda hwnd, _: callback(hwnd, window_title), window_title) - - if found: - self.db_logger.log_operation( - "DESKTOP_MINIMIZE", - f"ウィンドウを最小化: {window_title}", - "SUCCESS" - ) - return True - else: - self.logger.warning(f"ウィンドウが見つかりません: {window_title}") - return False - - except Exception as e: - self.logger.error(f"ウィンドウ最小化エラー: {e}") - return False - - def launch_application(self, app_name: str) -> bool: - """アプリケーションを起動""" - try: - # TODO: アプリケーションパスの解決ロジックを実装 - import subprocess - subprocess.Popen(app_name) - - self.db_logger.log_operation( - "DESKTOP_LAUNCH", - f"アプリケーションを起動: {app_name}", - "SUCCESS" - ) - return True - - except Exception as e: - self.logger.error(f"アプリケーション起動エラー: {e}") - return False - - def get_system_state(self) -> Dict[str, Any]: - """システム状態を取得""" - try: - cpu_usage = psutil.cpu_percent() - memory = psutil.virtual_memory() - active_window = win32gui.GetWindowText(win32gui.GetForegroundWindow()) - - state = { - 'cpu_usage': cpu_usage, - 'memory_usage': memory.percent, - 'active_window': active_window - } - - self.db_logger.log_system_state(**state) - return state - - except Exception as e: - self.logger.error(f"システム状態取得エラー: {e}") ->>>>>>> 42de7d643d987d98855d441372a3931e7de31809 return {} \ No newline at end of file diff --git a/src/desktop/keyboard_monitor.py b/src/desktop/keyboard_monitor.py index fcfb784..825128e 100644 --- a/src/desktop/keyboard_monitor.py +++ b/src/desktop/keyboard_monitor.py @@ -1,4 +1,3 @@ -<<<<<<< HEAD import keyboard import logging from typing import List, Optional, Callable @@ -77,84 +76,4 @@ def replay_events(self, events: List[dict], speed: float = 1.0): def get_key_sequence(self) -> str: """記録したキー操作を文字列として取得""" -======= -import keyboard -import logging -from typing import List, Optional, Callable -import time -from threading import Thread, Event - -class KeyboardMonitor(Thread): - def __init__(self): - super().__init__() - self.logger = logging.getLogger(__name__) - self.recording = False - self.key_events: List[dict] = [] - self.stop_event = Event() - self.callback: Optional[Callable] = None - - def start_recording(self, callback: Optional[Callable] = None): - """キーボード操作の記録を開始""" - self.recording = True - self.key_events = [] - self.callback = callback - if not self.is_alive(): - self.start() - - def stop_recording(self) -> List[dict]: - """キーボード操作の記録を停止して記録を返す""" - self.recording = False - return self.key_events - - def run(self): - """キーボード操作の監視を実行""" - try: - while not self.stop_event.is_set(): - if self.recording: - event = keyboard.read_event() - if event.event_type == 'down': - key_event = { - 'key': event.name, - 'time': time.time() - } - self.key_events.append(key_event) - self.logger.debug(f"キー入力: {event.name}") - - if self.callback: - self.callback(key_event) - time.sleep(0.01) # CPU負荷軽減 - - except Exception as e: - self.logger.error(f"キーボード監視エラー: {e}") - - def stop(self): - """監視を停止""" - self.stop_event.set() - if self.is_alive(): - self.join() - - def replay_events(self, events: List[dict], speed: float = 1.0): - """記録したキーボード操作を再生""" - try: - if not events: - return - - start_time = events[0]['time'] - for event in events: - # 前のイベントとの時間差を計算 - wait_time = (event['time'] - start_time) / speed - time.sleep(max(0, wait_time)) - - # キーを押下 - keyboard.press_and_release(event['key']) - self.logger.debug(f"キー再生: {event['key']}") - - start_time = event['time'] - - except Exception as e: - self.logger.error(f"キーボード再生エラー: {e}") - - def get_key_sequence(self) -> str: - """記録したキー操作を文字列として取得""" ->>>>>>> 42de7d643d987d98855d441372a3931e7de31809 return ' + '.join([event['key'] for event in self.key_events]) \ No newline at end of file diff --git a/src/desktop/system_monitor.py b/src/desktop/system_monitor.py index 0530df2..391a434 100644 --- a/src/desktop/system_monitor.py +++ b/src/desktop/system_monitor.py @@ -1,4 +1,3 @@ -<<<<<<< HEAD import psutil import win32gui import logging @@ -109,116 +108,4 @@ def _collect_system_metrics(self) -> Dict[str, Any]: except Exception as e: self.logger.error(f"メトリクス収集エラー: {e}") -======= -import psutil -import win32gui -import logging -import time -import threading -import queue -from threading import Thread -from typing import Optional, Dict, Any -from db.models import DatabaseManager - -class SystemMonitor(Thread): - def __init__(self): - super().__init__() - self.logger = logging.getLogger(__name__) - self.db_manager = DatabaseManager() - self.interval = 60 - self.running = False - self.metrics_queue = queue.Queue() - - # メインスレッドでのデータベース更新用スレッド - self.db_thread = Thread(target=self._process_metrics) - self.db_thread.daemon = True - - def run(self): - """モニタリングループを実行""" - self.running = True - self.db_thread.start() - - while self.running: - try: - metrics = self._collect_system_metrics() - self.metrics_queue.put(metrics) - time.sleep(self.interval) - except Exception as e: - self.logger.error(f"システムメトリクス収集エラー: {e}") - - def stop(self): - """モニタリングを停止""" - self.running = False - self.metrics_queue.put(None) # 終了シグナル - self.db_thread.join() - self.db_manager.close_all() - - def _process_metrics(self): - """メインスレッドでメトリクスを処理""" - conn = self.db_manager.get_connection() - cursor = conn.cursor() - - while True: - metrics = self.metrics_queue.get() - if metrics is None: # 終了シグナル - break - - try: - cursor.execute(''' - INSERT INTO system_state (cpu_usage, memory_usage, active_window) - VALUES (?, ?, ?) - ''', (metrics['cpu_usage'], metrics['memory_usage'], metrics['active_window'])) - conn.commit() - except Exception as e: - self.logger.error(f"メトリクス記録エラー: {e}") - - self.metrics_queue.task_done() - - conn.close() - - def _collect_system_metrics(self) -> Dict[str, Any]: - """システムメトリクスを収集""" - try: - # CPU使用率 - cpu_usage = psutil.cpu_percent(interval=1) - - # メモリ使用率 - memory = psutil.virtual_memory() - memory_usage = memory.percent - - # アクティブウィンドウ - active_window = win32gui.GetWindowText(win32gui.GetForegroundWindow()) - - # プロセス情報 - processes = [] - for proc in psutil.process_iter(['pid', 'name', 'cpu_percent', 'memory_percent']): - try: - processes.append(proc.info) - except (psutil.NoSuchProcess, psutil.AccessDenied): - pass - - # 上位5つのCPU使用プロセスを取得 - top_cpu_processes = sorted(processes, key=lambda x: x['cpu_percent'], reverse=True)[:5] - - # メトリクスを返す - metrics = { - 'cpu_usage': cpu_usage, - 'memory_usage': memory_usage, - 'active_window': active_window, - 'top_processes': top_cpu_processes - } - - # デバッグ情報を記録 - self.logger.debug(f"CPU使用率: {cpu_usage}%") - self.logger.debug(f"メモリ使用率: {memory_usage}%") - self.logger.debug(f"アクティブウィンドウ: {active_window}") - self.logger.debug("Top CPU使用プロセス:") - for proc in top_cpu_processes: - self.logger.debug(f" {proc['name']}: {proc['cpu_percent']}%") - - return metrics - - except Exception as e: - self.logger.error(f"メトリクス収集エラー: {e}") ->>>>>>> 42de7d643d987d98855d441372a3931e7de31809 raise \ No newline at end of file diff --git a/src/gui/__init__.py b/src/gui/__init__.py index 9974cbb..4c88acd 100644 --- a/src/gui/__init__.py +++ b/src/gui/__init__.py @@ -1,16 +1,7 @@ -<<<<<<< HEAD """ GUI package initialization """ from .main_window import MainWindow -======= -""" -GUI package initialization -""" - -from .main_window import MainWindow - ->>>>>>> 42de7d643d987d98855d441372a3931e7de31809 __all__ = ['MainWindow'] \ No newline at end of file diff --git a/src/gui/main_window.py b/src/gui/main_window.py index 86271cd..15f4b15 100644 --- a/src/gui/main_window.py +++ b/src/gui/main_window.py @@ -12,6 +12,7 @@ import GPUtil import wmi import threading +import os class TaskDialog(QDialog): def __init__(self, parent=None): @@ -73,7 +74,12 @@ def __init__(self, agent, command_interpreter, system): self.init_ui() # WMIの初期化 - self.wmi_interface = wmi.WMI(namespace="root\\OpenHardwareMonitor") + try: + self.wmi_interface = wmi.WMI(namespace="root\\OpenHardwareMonitor") + self.ohm_available = True + except Exception as e: + self.logger.warning(f"OpenHardwareMonitorに接続できません。システム監視の一部機能が制限されます: {e}") + self.ohm_available = False self.setup_system_tray() self.setup_timers() @@ -83,13 +89,49 @@ def init_ui(self): self.setWindowTitle('デスクトップエージェント') self.setGeometry(100, 100, 1000, 700) + # 時計ラベルの作成 + self.clock_label = QLabel() + self.clock_label.setAlignment(Qt.AlignmentFlag.AlignRight) + self.clock_label.setStyleSheet("font-size: 14pt; font-weight: bold;") + + # システム監視コンポーネントの初期化 + self.setup_system_monitor() + self.create_tabs() + def setup_system_monitor(self): + """システム監視用のUIコンポーネントを初期化""" + # CPU使用率表示 + self.cpu_bar = QProgressBar() + self.cpu_bar.setMaximum(100) + self.cpu_bar.setFormat("%v%") + self.cpu_bar.setTextVisible(True) + + # GPU使用率表示 + self.gpu_bar = QProgressBar() + self.gpu_bar.setMaximum(100) + self.gpu_bar.setFormat("%v%") + self.gpu_bar.setTextVisible(True) + + # メモリ使用率表示 + self.memory_bar = QProgressBar() + self.memory_bar.setMaximum(100) + self.memory_bar.setFormat("%v%") + self.memory_bar.setTextVisible(True) + + # 温度表示ラベル + self.cpu_temp_label = QLabel("CPU温度: N/A") + self.gpu_temp_label = QLabel("GPU温度: N/A") + def create_tabs(self): """メインのタブを作成""" self.tabs = QTabWidget() self.setCentralWidget(self.tabs) + # ステータスバーに時計を表示 + status_bar = self.statusBar() + status_bar.addPermanentWidget(self.clock_label) + # メイン機能タブ self.main_tab = QWidget() self.tabs.addTab(self.main_tab, "メイン") @@ -112,6 +154,33 @@ def setup_main_tab(self): """メインタブの作成""" layout = QVBoxLayout() + # システム情報セクション + system_group = QGroupBox("システム情報") + system_layout = QVBoxLayout() + + # CPU情報 + cpu_layout = QHBoxLayout() + cpu_layout.addWidget(QLabel("CPU使用率:")) + cpu_layout.addWidget(self.cpu_bar) + cpu_layout.addWidget(self.cpu_temp_label) + system_layout.addLayout(cpu_layout) + + # GPU情報 + gpu_layout = QHBoxLayout() + gpu_layout.addWidget(QLabel("GPU使用率:")) + gpu_layout.addWidget(self.gpu_bar) + gpu_layout.addWidget(self.gpu_temp_label) + system_layout.addLayout(gpu_layout) + + # メモリ情報 + memory_layout = QHBoxLayout() + memory_layout.addWidget(QLabel("メモリ使用率:")) + memory_layout.addWidget(self.memory_bar) + system_layout.addLayout(memory_layout) + + system_group.setLayout(system_layout) + layout.addWidget(system_group) + # ログ表示エリア self.log_display = QTextEdit() self.log_display.setReadOnly(True) @@ -417,7 +486,16 @@ def log(self, message): def setup_system_tray(self): self.tray_icon = QSystemTrayIcon(self) - self.tray_icon.setIcon(QIcon("icon.png")) + + # アイコンファイルの存在チェック + icon_path = "icon.png" + if os.path.exists(icon_path): + self.tray_icon.setIcon(QIcon(icon_path)) + else: + # アイコンがない場合はデフォルトアイコンを使用 + self.logger.warning("アイコンファイルが見つかりません: " + icon_path) + # PyQt6のデフォルトアイコンを使用 + self.tray_icon.setIcon(self.style().standardIcon(self.style().StandardPixmap.SP_ComputerIcon)) tray_menu = QMenu() show_action = QAction("表示", self) @@ -456,16 +534,21 @@ def update_system_stats(self): self.cpu_bar.setValue(int(cpu_percent)) try: - temperature_infos = self.wmi_interface.Sensor() - for sensor in temperature_infos: - if sensor.SensorType == 'Temperature': - if 'CPU' in sensor.Name: - self.cpu_temp_label.setText(f"CPU温度: {sensor.Value}°C") - elif 'GPU' in sensor.Name: - self.gpu_temp_label.setText(f"GPU温度: {sensor.Value}°C") + if hasattr(self, 'ohm_available') and self.ohm_available: + temperature_infos = self.wmi_interface.Sensor() + for sensor in temperature_infos: + if sensor.SensorType == 'Temperature': + if 'CPU' in sensor.Name: + self.cpu_temp_label.setText(f"CPU温度: {sensor.Value}°C") + elif 'GPU' in sensor.Name: + self.gpu_temp_label.setText(f"GPU温度: {sensor.Value}°C") + else: + self.cpu_temp_label.setText("CPU温度: N/A (OHM未接続)") + self.gpu_temp_label.setText("GPU温度: N/A (OHM未接続)") except Exception as e: - self.cpu_temp_label.setText("CPU温度: N/A") - self.gpu_temp_label.setText("GPU温度: N/A") + self.logger.warning(f"温度情報取得エラー: {e}") + self.cpu_temp_label.setText("CPU温度: エラー") + self.gpu_temp_label.setText("GPU温度: エラー") # メモリ使用率 memory = psutil.virtual_memory() diff --git a/src/main.py b/src/main.py index 60cb765..e0dd059 100644 --- a/src/main.py +++ b/src/main.py @@ -1,11 +1,17 @@ -<<<<<<< HEAD # -*- coding: utf-8 -*- import logging import sys +import socket +import os from pathlib import Path -from PyQt6.QtWidgets import QApplication +from PyQt6.QtWidgets import QApplication, QMessageBox from PyQt6.QtCore import Qt -from dotenv import load_dotenv # 追加 +from dotenv import load_dotenv +import wmi + +# シングルインスタンス制御のための設定 +SINGLE_INSTANCE_PORT = 47789 # 使用するポート番号 +single_instance_socket = None # DPI設定を調整 if hasattr(Qt, 'AA_EnableHighDpiScaling'): @@ -24,9 +30,10 @@ ) # プロジェクトルートとsrcディレクトリをPythonパスに追加 -project_root = Path(__file__).parent.parent +project_root = Path(__file__).resolve().parent.parent src_path = project_root / 'src' -sys.path.insert(0, str(src_path)) +sys.path.insert(0, str(project_root)) # プロジェクトルートを追加 +sys.path.insert(0, str(src_path)) # srcディレクトリを追加 # 必要なモジュールのインポート from gui.main_window import MainWindow @@ -34,66 +41,47 @@ from agent.command_interpreter import CommandInterpreter from db.models import DatabaseManager -def main(): - load_dotenv() # .envの値を読み込む - # データベースの初期化 - db_manager = DatabaseManager() - db_manager.initialize_database() - - # アプリケーションの初期化 - app = QApplication(sys.argv) - - # エージェントとインタープリタの初期化 - agent = AutonomousAgent(db_manager.get_logger()) - interpreter = CommandInterpreter() +def is_already_running(): + """既に同じアプリケーションが起動しているかチェック""" + global single_instance_socket - # メインウィンドウの作成と表示 - window = MainWindow(agent, interpreter) - window.show() + logger = logging.getLogger(__name__) + logger.info("他のインスタンスが実行中かチェックしています...") - # アプリケーションの実行 - return app.exec() - -if __name__ == '__main__': -======= -# -*- coding: utf-8 -*- -import logging -import sys -from pathlib import Path -from PyQt6.QtWidgets import QApplication -from PyQt6.QtCore import Qt -from dotenv import load_dotenv # 追加 -import wmi - -# DPI設定を調整 -if hasattr(Qt, 'AA_EnableHighDpiScaling'): - QApplication.setAttribute(Qt.ApplicationAttribute.AA_EnableHighDpiScaling, True) -if hasattr(Qt, 'AA_UseHighDpiPixmaps'): - QApplication.setAttribute(Qt.ApplicationAttribute.AA_UseHighDpiPixmaps, True) - -# ロギングの設定 -logging.basicConfig( - level=logging.DEBUG, - format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', - handlers=[ - logging.StreamHandler(sys.stdout), # 標準出力へのハンドラ - logging.FileHandler('app.log', encoding='utf-8') # ファイルへのハンドラ(UTF-8指定) - ] -) - -# プロジェクトルートとsrcディレクトリをPythonパスに追加 -project_root = Path(__file__).parent.parent -src_path = project_root / 'src' -sys.path.insert(0, str(src_path)) - -# 必要なモジュールのインポート -from gui.main_window import MainWindow -from agent.autonomous_agent import AutonomousAgent -from agent.command_interpreter import CommandInterpreter -from db.models import DatabaseManager + try: + # ソケットの作成とバインド + single_instance_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + single_instance_socket.bind(('localhost', SINGLE_INSTANCE_PORT)) + single_instance_socket.setblocking(False) + logger.info("アプリケーションの新規インスタンスを起動します") + return False + except socket.error: + logger.warning("アプリケーションのインスタンスは既に実行中です") + return True def main(): + """メイン関数""" + logger = logging.getLogger(__name__) + + # 多重起動のチェック + if is_already_running(): + # QApplicationがまだ作成されていない場合は作成 + app = QApplication.instance() + if app is None: + app = QApplication(sys.argv) + + # 既に起動しているというメッセージを表示 + QMessageBox.warning( + None, + "デスクトップエージェント - 多重起動の警告", + "デスクトップエージェントは既に起動しています。\n" + "複数のインスタンスを同時に実行することはできません。", + QMessageBox.StandardButton.Ok + ) + return 1 # エラーコードを返して終了 + load_dotenv() # .envの値を読み込む + # データベースの初期化 db_manager = DatabaseManager() db_manager.initialize_database() @@ -115,6 +103,19 @@ def main(): # アプリケーションの実行 return app.exec() +def cleanup(): + """終了時の後片付け""" + global single_instance_socket + + if single_instance_socket: + try: + single_instance_socket.close() + except: + pass + if __name__ == '__main__': ->>>>>>> 42de7d643d987d98855d441372a3931e7de31809 - sys.exit(main()) \ No newline at end of file + try: + exit_code = main() + finally: + cleanup() + sys.exit(exit_code) \ No newline at end of file diff --git a/src/main_window.py b/src/main_window.py index a50f5aa..7d40c0a 100644 --- a/src/main_window.py +++ b/src/main_window.py @@ -1,4 +1,3 @@ -<<<<<<< HEAD from PyQt6.QtWidgets import (QMainWindow, QWidget, QVBoxLayout, QTextEdit, QLineEdit, QPushButton, QLabel, QTabWidget, QTableWidget, QTableWidgetItem, QHeaderView) @@ -135,142 +134,4 @@ def execute_command(self): # スクロールを最下部に移動 self.log_display.verticalScrollBar().setValue( self.log_display.verticalScrollBar().maximum() -======= -from PyQt6.QtWidgets import (QMainWindow, QWidget, QVBoxLayout, - QTextEdit, QLineEdit, QPushButton, QLabel, QTabWidget, QTableWidget, - QTableWidgetItem, QHeaderView) -from PyQt6.QtCore import Qt, pyqtSlot -import logging - -class MainWindow(QMainWindow): - def __init__(self, agent, command_interpreter): - super().__init__() - self.agent = agent - self.command_interpreter = command_interpreter - self.logger = logging.getLogger(__name__) - self.init_ui() - - def init_ui(self): - """UIの初期化""" - self.setWindowTitle('デスクトップエージェント') - self.setGeometry(100, 100, 1000, 700) - - # タブウィジェットの作成 - self.tab_widget = QTabWidget() - self.setCentralWidget(self.tab_widget) - - # メインタブの追加 - main_tab = self._create_main_tab() - self.tab_widget.addTab(main_tab, "メイン") - - # コマンド一覧タブの追加 - commands_tab = self._create_commands_tab() - self.tab_widget.addTab(commands_tab, "コマンド一覧") - - def _create_main_tab(self): - """メインタブの作成""" - tab = QWidget() - layout = QVBoxLayout() - - # ログ表示エリア - self.log_display = QTextEdit() - self.log_display.setReadOnly(True) - layout.addWidget(QLabel('操作ログ:')) - layout.addWidget(self.log_display) - - # コマンド入力エリア - self.command_input = QLineEdit() - self.command_input.setPlaceholderText('コマンドを入力してください...') - self.command_input.returnPressed.connect(self.execute_command) - layout.addWidget(QLabel('コマンド入力:')) - layout.addWidget(self.command_input) - - # 実行ボタン - execute_button = QPushButton('実行') - execute_button.clicked.connect(self.execute_command) - layout.addWidget(execute_button) - - tab.setLayout(layout) - return tab - - def _create_commands_tab(self): - """コマンド一覧タブの作成""" - tab = QWidget() - layout = QVBoxLayout() - - # コマンド一覧テーブル - table = QTableWidget() - table.setColumnCount(3) - table.setHorizontalHeaderLabels(['コマンド', '説明', '例']) - - # コマンド一覧データ - commands = [ - ('edge URL', 'Microsoft EdgeでURLを開く', 'edge google.com'), - ('chrome URL', 'Google ChromeでURLを開く', 'chrome youtube.com'), - ('browser URL', 'デフォルトブラウザでURLを開く', 'browser github.com'), - ('フォルダ作成', 'フォルダを作成', 'documentsフォルダを作成して'), - ('ファイル移動', 'ファイルを移動', 'test.txtをdocumentsに移動して'), - ('削除', 'ファイルまたはフォルダを削除', 'temp.txtを削除して'), - ('最小化', 'ウィンドウを最小化', 'Chromeを最小化して'), - ('起動', 'アプリケーションを起動', 'メモ帳を起動して'), - ('マウス移動', 'マウスを指定座標に移動', 'マウスを100, 200に移動して'), - ('マウスクリック', '指定座標をクリック', '100, 200を2回右クリックして'), - ('ドラッグ', '指定座標間をドラッグ', '100, 200から300, 400までドラッグして'), - ('スクロール', '指定量スクロール', '下に300スクロールして'), - ('キー記録', 'キーボード操作の記録を開始', 'キー操作を記録して'), - ('キー停止', 'キーボード操作の記録を停止', 'キー操作を停止して'), - ('キー再生', '記録したキーボード操作を再生', 'キー操作を再生して'), - ('テキスト入力', '指定したテキストを入力', '「Hello, World!」と入力して'), - ('ホットキー', '指定したホットキーを実行', 'ホットキーctrl+cを実行して'), - ('画面分析', '画面全体を分析', '画面を分析して'), - ('領域分析', '指定領域を分析', '100, 200から300, 400の範囲を分析して') - ] - - # テーブルにデータを設定 - table.setRowCount(len(commands)) - for i, (cmd, desc, example) in enumerate(commands): - table.setItem(i, 0, QTableWidgetItem(cmd)) - table.setItem(i, 1, QTableWidgetItem(desc)) - table.setItem(i, 2, QTableWidgetItem(example)) - - # テーブルの設定 - table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.ResizeToContents) - table.verticalHeader().setVisible(False) - table.setEditTriggers(QTableWidget.EditTrigger.NoEditTriggers) - - layout.addWidget(table) - tab.setLayout(layout) - return tab - - @pyqtSlot() - def execute_command(self): - """コマンドの実行""" - command = self.command_input.text().strip() - if not command: - return - - self.log_display.append(f"\n> {command}") - self.command_input.clear() - - try: - # コマンドの解釈と実行 - result = self.command_interpreter.interpret(command) - if result: - command_type, params = result - success = self.agent.execute_command(command_type, params) - if success: - self.log_display.append("✓ コマンドを実行しました") - else: - self.log_display.append("✗ コマンドの実行に失敗しました") - else: - self.log_display.append("? コマンドを理解できませんでした") - - except Exception as e: - self.logger.error(f"コマンド実行エラー: {e}") - self.log_display.append(f"⚠ エラー: {str(e)}") - - # スクロールを最下部に移動 - self.log_display.verticalScrollBar().setValue( - self.log_display.verticalScrollBar().maximum() ->>>>>>> 42de7d643d987d98855d441372a3931e7de31809 ) \ No newline at end of file diff --git a/src/monitoring/hardware_monitor.py b/src/monitoring/hardware_monitor.py index 473c9ce..2f586e6 100644 --- a/src/monitoring/hardware_monitor.py +++ b/src/monitoring/hardware_monitor.py @@ -1,12 +1,18 @@ -<<<<<<< HEAD import psutil import GPUtil import logging -from typing import Dict, Any +from typing import Dict, Any, List, Optional import time +import json from dataclasses import dataclass from datetime import datetime -from src.exceptions import HardwareMonitorError +from pathlib import Path +import os + +# 例外クラスの定義 +class HardwareMonitorError(Exception): + """ハードウェアモニタリングに関するエラー""" + pass @dataclass class HardwareMetrics: @@ -16,15 +22,34 @@ class HardwareMetrics: cpu_usage: float = 0.0 gpu_usage: float = 0.0 memory_usage: float = 0.0 + gpu_memory_usage: float = 0.0 timestamp: datetime = datetime.now() + + def to_dict(self) -> Dict[str, Any]: + """メトリクスを辞書形式に変換""" + return { + "cpu_temp": self.cpu_temp, + "gpu_temp": self.gpu_temp, + "cpu_usage": self.cpu_usage, + "gpu_usage": self.gpu_usage, + "memory_usage": self.memory_usage, + "gpu_memory_usage": self.gpu_memory_usage, + "timestamp": self.timestamp.isoformat() + } class HardwareMonitor: """ハードウェアモニタリングクラス""" - def __init__(self): + def __init__(self, max_history_size: int = 100): self.logger = logging.getLogger(__name__) - self.metrics_history = [] + self.metrics_history: List[HardwareMetrics] = [] self.warning_temp_threshold = 80.0 # 警告温度閾値 self.critical_temp_threshold = 90.0 # 危険温度閾値 + self.max_history_size = max_history_size + self.export_directory = Path("monitoring_data") + + # エクスポートディレクトリがなければ作成 + if not self.export_directory.exists(): + os.makedirs(self.export_directory, exist_ok=True) def get_cpu_temperature(self) -> float: """CPU温度の取得""" @@ -51,262 +76,180 @@ def get_gpu_temperature(self) -> float: try: gpus = GPUtil.getGPUs() if not gpus: - self.logger.warning("GPUが見つかりません") + self.logger.warning("利用可能なGPUが見つかりません") return 0.0 - - # 最も高い温度を返す - return max(gpu.temperature for gpu in gpus) + + # 最初のGPUの温度を返す + return gpus[0].temperature except Exception as e: self.logger.error(f"GPU温度取得エラー: {e}") raise HardwareMonitorError(f"GPU温度の取得に失敗: {e}") - def get_metrics(self) -> HardwareMetrics: - """ハードウェアメトリクスの取得""" - try: - # エラーが発生しても処理を継続 - try: - cpu_temp = self.get_cpu_temperature() - except HardwareMonitorError as e: - self.logger.error(str(e)) - cpu_temp = 0.0 - - try: - gpu_temp = self.get_gpu_temperature() - except HardwareMonitorError as e: - self.logger.error(str(e)) - gpu_temp = 0.0 - - # 温度警告のチェック - self._check_temperature_warnings(cpu_temp, gpu_temp) - - metrics = HardwareMetrics( - cpu_temp=cpu_temp, - gpu_temp=gpu_temp, - cpu_usage=self._get_cpu_usage(), - gpu_usage=self._get_gpu_usage(), - memory_usage=self._get_memory_usage(), - timestamp=datetime.now() - ) - - self.metrics_history.append(metrics) - if len(self.metrics_history) > 1000: # 履歴を1000件に制限 - self.metrics_history.pop(0) - - return metrics - - except Exception as e: - self.logger.error(f"メトリクス取得エラー: {e}") - return HardwareMetrics() - - def _get_cpu_usage(self) -> float: - """CPU使用率の取得""" - try: - return psutil.cpu_percent() - except Exception as e: - self.logger.error(f"CPU使用率取得エラー: {e}") - return 0.0 - - def _get_gpu_usage(self) -> float: - """GPU使用率の取得""" + def get_gpu_details(self) -> Dict[str, Any]: + """GPUの詳細情報を取得""" try: gpus = GPUtil.getGPUs() if not gpus: - return 0.0 - return max(gpu.load * 100 for gpu in gpus) - except Exception as e: - self.logger.error(f"GPU使用率取得エラー: {e}") - return 0.0 - - def _get_memory_usage(self) -> float: - """メモリ使用率の取得""" - try: - return psutil.virtual_memory().percent + self.logger.warning("利用可能なGPUが見つかりません") + return {} + + gpu = gpus[0] # 最初のGPUを使用 + + return { + "name": gpu.name, + "driver": gpu.driver, + "memory_total": gpu.memoryTotal, + "memory_used": gpu.memoryUsed, + "memory_free": gpu.memoryFree, + "temperature": gpu.temperature, + "load": gpu.load, + "uuid": gpu.uuid + } except Exception as e: - self.logger.error(f"メモリ使用率取得エラー: {e}") - return 0.0 + self.logger.error(f"GPU詳細情報取得エラー: {e}") + return {} - def _check_temperature_warnings(self, cpu_temp: float, gpu_temp: float) -> None: - """温度警告のチェック""" - for device, temp in [("CPU", cpu_temp), ("GPU", gpu_temp)]: - if temp >= self.critical_temp_threshold: - self.logger.critical( - f"{device}温度が危険値に達しています: {temp}°C" - ) - elif temp >= self.warning_temp_threshold: - self.logger.warning( - f"{device}温度が警告値を超えています: {temp}°C" - ) - - def get_temperature_history(self) -> Dict[str, list]: - """温度履歴の取得""" - return { - "timestamps": [m.timestamp for m in self.metrics_history], - "cpu_temps": [m.cpu_temp for m in self.metrics_history], - "gpu_temps": [m.gpu_temp for m in self.metrics_history] - } - - def get_usage_history(self) -> Dict[str, list]: - """使用率履歴の取得""" - return { - "timestamps": [m.timestamp for m in self.metrics_history], - "cpu_usage": [m.cpu_usage for m in self.metrics_history], - "gpu_usage": [m.gpu_usage for m in self.metrics_history], - "memory_usage": [m.memory_usage for m in self.metrics_history] -======= -import psutil -import GPUtil -import logging -from typing import Dict, Any -import time -from dataclasses import dataclass -from datetime import datetime -from src.exceptions import HardwareMonitorError - -@dataclass -class HardwareMetrics: - """ハードウェアメトリクス""" - cpu_temp: float = 0.0 - gpu_temp: float = 0.0 - cpu_usage: float = 0.0 - gpu_usage: float = 0.0 - memory_usage: float = 0.0 - timestamp: datetime = datetime.now() - -class HardwareMonitor: - """ハードウェアモニタリングクラス""" - def __init__(self): - self.logger = logging.getLogger(__name__) - self.metrics_history = [] - self.warning_temp_threshold = 80.0 # 警告温度閾値 - self.critical_temp_threshold = 90.0 # 危険温度閾値 - - def get_cpu_temperature(self) -> float: - """CPU温度の取得""" + def get_metrics(self) -> HardwareMetrics: + """すべてのハードウェアメトリクスを取得""" try: - temps = psutil.sensors_temperatures() - if not temps: - self.logger.warning("CPU温度センサーが見つかりません") - return 0.0 - - # 最も高い温度を返す - max_temp = 0.0 - for name, entries in temps.items(): - for entry in entries: - if entry.current > max_temp: - max_temp = entry.current - return max_temp - - except Exception as e: - self.logger.error(f"CPU温度取得エラー: {e}") - raise HardwareMonitorError(f"CPU温度の取得に失敗: {e}") + # CPU温度 + cpu_temp = self.get_cpu_temperature() - def get_gpu_temperature(self) -> float: - """GPU温度の取得""" - try: - gpus = GPUtil.getGPUs() - if not gpus: - self.logger.warning("GPUが見つかりません") - return 0.0 + # GPU温度 + gpu_temp = self.get_gpu_temperature() - # 最も高い温度を返す - return max(gpu.temperature for gpu in gpus) + # CPU使用率 + cpu_usage = self._get_cpu_usage() - except Exception as e: - self.logger.error(f"GPU温度取得エラー: {e}") - raise HardwareMonitorError(f"GPU温度の取得に失敗: {e}") + # GPU使用率 + gpu_usage, gpu_memory_usage = self._get_gpu_usage() - def get_metrics(self) -> HardwareMetrics: - """ハードウェアメトリクスの取得""" - try: - # エラーが発生しても処理を継続 - try: - cpu_temp = self.get_cpu_temperature() - except HardwareMonitorError as e: - self.logger.error(str(e)) - cpu_temp = 0.0 - - try: - gpu_temp = self.get_gpu_temperature() - except HardwareMonitorError as e: - self.logger.error(str(e)) - gpu_temp = 0.0 + # メモリ使用率 + memory_usage = self._get_memory_usage() - # 温度警告のチェック + # 温度警告チェック self._check_temperature_warnings(cpu_temp, gpu_temp) + # メトリクスオブジェクト作成 metrics = HardwareMetrics( cpu_temp=cpu_temp, gpu_temp=gpu_temp, - cpu_usage=self._get_cpu_usage(), - gpu_usage=self._get_gpu_usage(), - memory_usage=self._get_memory_usage(), + cpu_usage=cpu_usage, + gpu_usage=gpu_usage, + memory_usage=memory_usage, + gpu_memory_usage=gpu_memory_usage, timestamp=datetime.now() ) + # 履歴に追加 self.metrics_history.append(metrics) - if len(self.metrics_history) > 1000: # 履歴を1000件に制限 + + # 履歴サイズの管理 + if len(self.metrics_history) > self.max_history_size: self.metrics_history.pop(0) return metrics except Exception as e: self.logger.error(f"メトリクス取得エラー: {e}") - return HardwareMetrics() + raise HardwareMonitorError(f"メトリクスの取得に失敗: {e}") def _get_cpu_usage(self) -> float: """CPU使用率の取得""" try: - return psutil.cpu_percent() + return psutil.cpu_percent(interval=0.1) except Exception as e: self.logger.error(f"CPU使用率取得エラー: {e}") return 0.0 - def _get_gpu_usage(self) -> float: - """GPU使用率の取得""" + def _get_gpu_usage(self) -> tuple: + """GPU使用率とメモリ使用率の取得""" try: gpus = GPUtil.getGPUs() if not gpus: - return 0.0 - return max(gpu.load * 100 for gpu in gpus) + return 0.0, 0.0 + + gpu = gpus[0] # 最初のGPUを使用 + return gpu.load * 100, (gpu.memoryUsed / gpu.memoryTotal) * 100 except Exception as e: self.logger.error(f"GPU使用率取得エラー: {e}") - return 0.0 + return 0.0, 0.0 def _get_memory_usage(self) -> float: """メモリ使用率の取得""" try: - return psutil.virtual_memory().percent + memory = psutil.virtual_memory() + return memory.percent except Exception as e: self.logger.error(f"メモリ使用率取得エラー: {e}") return 0.0 def _check_temperature_warnings(self, cpu_temp: float, gpu_temp: float) -> None: """温度警告のチェック""" - for device, temp in [("CPU", cpu_temp), ("GPU", gpu_temp)]: - if temp >= self.critical_temp_threshold: - self.logger.critical( - f"{device}温度が危険値に達しています: {temp}°C" - ) - elif temp >= self.warning_temp_threshold: - self.logger.warning( - f"{device}温度が警告値を超えています: {temp}°C" - ) - + # CPU温度チェック + if cpu_temp >= self.critical_temp_threshold: + self.logger.critical(f"CPU温度が危険レベルです: {cpu_temp}°C") + elif cpu_temp >= self.warning_temp_threshold: + self.logger.warning(f"CPU温度が高温です: {cpu_temp}°C") + + # GPU温度チェック + if gpu_temp >= self.critical_temp_threshold: + self.logger.critical(f"GPU温度が危険レベルです: {gpu_temp}°C") + elif gpu_temp >= self.warning_temp_threshold: + self.logger.warning(f"GPU温度が高温です: {gpu_temp}°C") + def get_temperature_history(self) -> Dict[str, list]: - """温度履歴の取得""" + """温度履歴を取得""" + cpu_temps = [m.cpu_temp for m in self.metrics_history] + gpu_temps = [m.gpu_temp for m in self.metrics_history] + timestamps = [m.timestamp.strftime("%H:%M:%S") for m in self.metrics_history] + return { - "timestamps": [m.timestamp for m in self.metrics_history], - "cpu_temps": [m.cpu_temp for m in self.metrics_history], - "gpu_temps": [m.gpu_temp for m in self.metrics_history] + "timestamps": timestamps, + "cpu_temps": cpu_temps, + "gpu_temps": gpu_temps } def get_usage_history(self) -> Dict[str, list]: - """使用率履歴の取得""" + """使用率履歴を取得""" + cpu_usage = [m.cpu_usage for m in self.metrics_history] + gpu_usage = [m.gpu_usage for m in self.metrics_history] + memory_usage = [m.memory_usage for m in self.metrics_history] + gpu_memory_usage = [m.gpu_memory_usage for m in self.metrics_history] + timestamps = [m.timestamp.strftime("%H:%M:%S") for m in self.metrics_history] + return { - "timestamps": [m.timestamp for m in self.metrics_history], - "cpu_usage": [m.cpu_usage for m in self.metrics_history], - "gpu_usage": [m.gpu_usage for m in self.metrics_history], - "memory_usage": [m.memory_usage for m in self.metrics_history] ->>>>>>> 42de7d643d987d98855d441372a3931e7de31809 - } \ No newline at end of file + "timestamps": timestamps, + "cpu_usage": cpu_usage, + "gpu_usage": gpu_usage, + "memory_usage": memory_usage, + "gpu_memory_usage": gpu_memory_usage + } + + def export_metrics_to_json(self, filename: Optional[str] = None) -> str: + """メトリクス履歴をJSONファイルにエクスポート""" + if not filename: + filename = f"metrics_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json" + + filepath = self.export_directory / filename + + try: + # メトリクスを辞書リストに変換 + metrics_data = [m.to_dict() for m in self.metrics_history] + + # JSON形式で保存 + with open(filepath, 'w', encoding='utf-8') as f: + json.dump(metrics_data, f, ensure_ascii=False, indent=2) + + self.logger.info(f"メトリクスデータをエクスポートしました: {filepath}") + return str(filepath) + + except Exception as e: + self.logger.error(f"メトリクスのエクスポートに失敗: {e}") + raise HardwareMonitorError(f"メトリクスのエクスポートに失敗: {e}") + + def clear_history(self) -> None: + """メトリクス履歴をクリア""" + self.metrics_history.clear() + self.logger.info("メトリクス履歴をクリアしました") \ No newline at end of file diff --git a/src/monitoring/metrics_exporter.py b/src/monitoring/metrics_exporter.py index e515db5..835f83d 100644 --- a/src/monitoring/metrics_exporter.py +++ b/src/monitoring/metrics_exporter.py @@ -1,24 +1,52 @@ -<<<<<<< HEAD import pandas as pd -from typing import Dict, Any, List +from typing import Dict, Any, List, Optional, Union, Callable import json from pathlib import Path import csv -from datetime import datetime +from datetime import datetime, timedelta import logging import asyncio from concurrent.futures import ThreadPoolExecutor +import matplotlib.pyplot as plt +import seaborn as sns +import os +import time +import threading +from dataclasses import asdict class MetricsExporter: - """メトリクスエクスポートクラス""" + """メトリクスデータをさまざまな形式でエクスポートするクラス""" + def __init__(self, export_dir: str = "metrics_export"): + """初期化 + + Args: + export_dir: エクスポート先ディレクトリ + """ self.export_dir = Path(export_dir) self.export_dir.mkdir(parents=True, exist_ok=True) + + # 視覚化エクスポート用ディレクトリ + self.visualizations_dir = self.export_dir / "visualizations" + self.visualizations_dir.mkdir(parents=True, exist_ok=True) + self.logger = logging.getLogger(__name__) self.executor = ThreadPoolExecutor(max_workers=4) - def export_to_csv(self, metrics: Dict[str, Any], file_name: str = None) -> str: - """CSVフォーマットでエクスポート""" + # 定期的なエクスポートのスケジュールフラグ + self._scheduled_export_running = False + self._scheduled_export_thread = None + + def export_to_csv(self, metrics: Dict[str, Any], file_name: Optional[str] = None) -> str: + """メトリクスをCSVファイルとしてエクスポート + + Args: + metrics: エクスポートするメトリクスデータ + file_name: 出力ファイル名(省略時は自動生成) + + Returns: + エクスポート先のファイルパス + """ try: if file_name is None: file_name = f"metrics_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv" @@ -32,44 +60,60 @@ def export_to_csv(self, metrics: Dict[str, Any], file_name: str = None) -> str: df = pd.DataFrame([flat_metrics]) df.to_csv(file_path, index=False) + self.logger.info(f"メトリクスをCSVファイルにエクスポートしました: {file_path}") return str(file_path) except Exception as e: self.logger.error(f"CSVエクスポートエラー: {e}") raise - def export_to_json(self, metrics: Dict[str, Any], file_name: str = None) -> str: - """JSONフォーマットでエクスポート""" + def export_to_json(self, metrics: Dict[str, Any], file_name: Optional[str] = None) -> str: + """メトリクスをJSONファイルとしてエクスポート + + Args: + metrics: エクスポートするメトリクスデータ + file_name: 出力ファイル名(省略時は自動生成) + + Returns: + エクスポート先のファイルパス + """ try: if file_name is None: file_name = f"metrics_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json" file_path = self.export_dir / file_name - with open(file_path, "w") as f: - json.dump(metrics, f, indent=2, default=str) + with open(file_path, 'w', encoding='utf-8') as f: + json.dump(metrics, f, ensure_ascii=False, indent=2) + self.logger.info(f"メトリクスをJSONファイルにエクスポートしました: {file_path}") return str(file_path) except Exception as e: self.logger.error(f"JSONエクスポートエラー: {e}") raise - def export_history_to_csv(self, history: List[Dict[str, Any]], file_name: str = None) -> str: - """履歴データをCSVフォーマットでエクスポート""" + def export_history_to_csv(self, history: List[Dict[str, Any]], file_name: Optional[str] = None) -> str: + """メトリクス履歴をCSVファイルとしてエクスポート + + Args: + history: エクスポートする履歴データ + file_name: 出力ファイル名(省略時は自動生成) + + Returns: + エクスポート先のファイルパス + """ try: if file_name is None: - file_name = f"history_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv" + file_name = f"metrics_history_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv" file_path = self.export_dir / file_name - # 履歴データをフラット化 - flat_history = [self._flatten_metrics(m) for m in history] - # DataFrameに変換してCSV出力 - df = pd.DataFrame(flat_history) + df = pd.DataFrame([self._flatten_metrics(item) for item in history]) df.to_csv(file_path, index=False) + self.logger.info(f"メトリクス履歴をCSVファイルにエクスポートしました: {file_path}") return str(file_path) except Exception as e: @@ -77,322 +121,263 @@ def export_history_to_csv(self, history: List[Dict[str, Any]], file_name: str = raise def _flatten_metrics(self, metrics: Dict[str, Any], parent_key: str = "") -> Dict[str, Any]: - """メトリクスの階層構造をフラット化""" - items = [] + """ネストされたメトリクスをフラット化 + + Args: + metrics: フラット化するメトリクスデータ + parent_key: 親キー(再帰呼び出し用) + + Returns: + フラット化されたメトリクスデータ + """ + items = {} for k, v in metrics.items(): new_key = f"{parent_key}_{k}" if parent_key else k if isinstance(v, dict): - items.extend(self._flatten_metrics(v, new_key).items()) - elif isinstance(v, list): - # リストの場合は統計値を計算 - if all(isinstance(x, (int, float)) for x in v): - items.extend([ - (f"{new_key}_mean", sum(v) / len(v) if v else 0), - (f"{new_key}_max", max(v) if v else 0), - (f"{new_key}_min", min(v) if v else 0) - ]) - else: - items.append((new_key, str(v))) + # 再帰的にフラット化 + items.update(self._flatten_metrics(v, new_key)) else: - items.append((new_key, v)) + items[new_key] = v - return dict(items) + return items + + def export_alert_history(self, alerts: List[Dict[str, Any]], file_name: Optional[str] = None) -> str: + """アラート履歴をCSVファイルとしてエクスポート - def export_alert_history(self, alerts: List[Dict[str, Any]], file_name: str = None) -> str: - """アラート履歴のエクスポート""" + Args: + alerts: エクスポートするアラートデータ + file_name: 出力ファイル名(省略時は自動生成) + + Returns: + エクスポート先のファイルパス + """ try: if file_name is None: file_name = f"alerts_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv" file_path = self.export_dir / file_name - # 必要なフィールドを抽出 - alert_data = [] - for alert in alerts: - alert_data.append({ - "timestamp": alert["timestamp"], - "type": alert["type"], - "severity": alert["severity"], - "message": alert["message"] - }) - - # DataFrameに変換してCSV出力 - df = pd.DataFrame(alert_data) - df.to_csv(file_path, index=False) - + # アラートデータをDataFrameに変換 + if alerts: + df = pd.DataFrame(alerts) + if 'timestamp' in df.columns: + df['timestamp'] = pd.to_datetime(df['timestamp']) + + df.to_csv(file_path, index=False) + else: + # 空のファイルを作成 + with open(file_path, 'w') as f: + f.write("timestamp,alert_type,message,severity,value,threshold\n") + + self.logger.info(f"アラート履歴をCSVファイルにエクスポートしました: {file_path}") return str(file_path) except Exception as e: self.logger.error(f"アラート履歴エクスポートエラー: {e}") raise + + def export_optimization_history(self, history: Dict[str, Any], file_name: Optional[str] = None) -> str: + """最適化履歴をCSVファイルとしてエクスポート + + Args: + history: エクスポートする最適化履歴データ + file_name: 出力ファイル名(省略時は自動生成) - def export_optimization_history(self, history: Dict[str, Any], file_name: str = None) -> str: - """最適化履歴のエクスポート""" + Returns: + エクスポート先のファイルパス + """ try: if file_name is None: - file_name = f"optimization_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json" + file_name = f"optimization_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv" file_path = self.export_dir / file_name - with open(file_path, "w") as f: - json.dump(history, f, indent=2, default=str) + # DataFrameに変換 + flattened_data = [] + for timestamp, data in history.items(): + data["timestamp"] = timestamp + flattened_data.append(data) - return str(file_path) - - except Exception as e: - self.logger.error(f"最適化履歴エクスポートエラー: {e}") - raise - - async def async_export_to_csv(self, metrics: Dict[str, Any], file_name: str = None) -> str: - """CSVフォーマットで非同期エクスポート""" - loop = asyncio.get_event_loop() - return await loop.run_in_executor( - self.executor, - self.export_to_csv, - metrics, - file_name - ) - - async def async_export_to_json(self, metrics: Dict[str, Any], file_name: str = None) -> str: - """JSONフォーマットで非同期エクスポート""" - loop = asyncio.get_event_loop() - return await loop.run_in_executor( - self.executor, - self.export_to_json, - metrics, - file_name - ) - - async def async_export_history_to_csv(self, history: List[Dict[str, Any]], file_name: str = None) -> str: - """履歴データを非同期でCSVエクスポート""" - loop = asyncio.get_event_loop() - return await loop.run_in_executor( - self.executor, - self.export_history_to_csv, - history, - file_name - ) - - async def async_export_alert_history(self, alerts: List[Dict[str, Any]], file_name: str = None) -> str: - """アラート履歴を非同期でエクスポート""" - loop = asyncio.get_event_loop() - return await loop.run_in_executor( - self.executor, - self.export_alert_history, - alerts, - file_name - ) - - async def async_export_optimization_history(self, history: Dict[str, Any], file_name: str = None) -> str: - """最適化履歴を非同期でエクスポート""" - loop = asyncio.get_event_loop() - return await loop.run_in_executor( - self.executor, - self.export_optimization_history, - history, - file_name - ) - - def __del__(self): - """デストラクタ""" -======= -import pandas as pd -from typing import Dict, Any, List -import json -from pathlib import Path -import csv -from datetime import datetime -import logging -import asyncio -from concurrent.futures import ThreadPoolExecutor - -class MetricsExporter: - """メトリクスエクスポートクラス""" - def __init__(self, export_dir: str = "metrics_export"): - self.export_dir = Path(export_dir) - self.export_dir.mkdir(parents=True, exist_ok=True) - self.logger = logging.getLogger(__name__) - self.executor = ThreadPoolExecutor(max_workers=4) - - def export_to_csv(self, metrics: Dict[str, Any], file_name: str = None) -> str: - """CSVフォーマットでエクスポート""" - try: - if file_name is None: - file_name = f"metrics_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv" - - file_path = self.export_dir / file_name - - # メトリクスをフラット化 - flat_metrics = self._flatten_metrics(metrics) - - # DataFrameに変換してCSV出力 - df = pd.DataFrame([flat_metrics]) + df = pd.DataFrame(flattened_data) df.to_csv(file_path, index=False) + self.logger.info(f"最適化履歴をCSVファイルにエクスポートしました: {file_path}") return str(file_path) except Exception as e: - self.logger.error(f"CSVエクスポートエラー: {e}") + self.logger.error(f"最適化履歴エクスポートエラー: {e}") raise + + def visualize_hardware_metrics(self, metrics_history: List[Dict[str, Any]], + file_name: Optional[str] = None) -> str: + """ハードウェアメトリクスの視覚化 + + Args: + metrics_history: 視覚化するメトリクス履歴 + file_name: 出力ファイル名(省略時は自動生成) - def export_to_json(self, metrics: Dict[str, Any], file_name: str = None) -> str: - """JSONフォーマットでエクスポート""" + Returns: + 視覚化ファイルのパス + """ try: - if file_name is None: - file_name = f"metrics_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json" + if not metrics_history: + self.logger.warning("視覚化するメトリクスデータがありません") + return "" - file_path = self.export_dir / file_name - - with open(file_path, "w") as f: - json.dump(metrics, f, indent=2, default=str) - - return str(file_path) - - except Exception as e: - self.logger.error(f"JSONエクスポートエラー: {e}") - raise - - def export_history_to_csv(self, history: List[Dict[str, Any]], file_name: str = None) -> str: - """履歴データをCSVフォーマットでエクスポート""" - try: if file_name is None: - file_name = f"history_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv" + file_name = f"hardware_metrics_{datetime.now().strftime('%Y%m%d_%H%M%S')}.png" - file_path = self.export_dir / file_name - - # 履歴データをフラット化 - flat_history = [self._flatten_metrics(m) for m in history] - - # DataFrameに変換してCSV出力 - df = pd.DataFrame(flat_history) - df.to_csv(file_path, index=False) - + file_path = self.visualizations_dir / file_name + + # DataFrameに変換 + df = pd.DataFrame(metrics_history) + if 'timestamp' in df.columns: + df['timestamp'] = pd.to_datetime(df['timestamp']) + df.set_index('timestamp', inplace=True) + + # プロットの設定 + sns.set_theme(style="darkgrid") + fig, axes = plt.subplots(3, 1, figsize=(12, 15), sharex=True) + + # CPU・GPU温度のプロット + if 'cpu_temp' in df.columns and 'gpu_temp' in df.columns: + df[['cpu_temp', 'gpu_temp']].plot(ax=axes[0], marker='o', alpha=0.7) + axes[0].set_title('CPU・GPU温度') + axes[0].set_ylabel('温度 (°C)') + axes[0].grid(True) + + # CPU・GPU使用率のプロット + if 'cpu_usage' in df.columns and 'gpu_usage' in df.columns: + df[['cpu_usage', 'gpu_usage']].plot(ax=axes[1], marker='o', alpha=0.7) + axes[1].set_title('CPU・GPU使用率') + axes[1].set_ylabel('使用率 (%)') + axes[1].grid(True) + + # メモリ使用率のプロット + if 'memory_usage' in df.columns and 'gpu_memory_usage' in df.columns: + df[['memory_usage', 'gpu_memory_usage']].plot(ax=axes[2], marker='o', alpha=0.7) + axes[2].set_title('メモリ使用率') + axes[2].set_ylabel('使用率 (%)') + axes[2].grid(True) + axes[2].set_xlabel('時間') + + plt.tight_layout() + plt.savefig(file_path) + plt.close(fig) + + self.logger.info(f"ハードウェアメトリクスを視覚化しました: {file_path}") return str(file_path) except Exception as e: - self.logger.error(f"履歴CSVエクスポートエラー: {e}") + self.logger.error(f"メトリクス視覚化エラー: {e}") raise - - def _flatten_metrics(self, metrics: Dict[str, Any], parent_key: str = "") -> Dict[str, Any]: - """メトリクスの階層構造をフラット化""" - items = [] - for k, v in metrics.items(): - new_key = f"{parent_key}_{k}" if parent_key else k - - if isinstance(v, dict): - items.extend(self._flatten_metrics(v, new_key).items()) - elif isinstance(v, list): - # リストの場合は統計値を計算 - if all(isinstance(x, (int, float)) for x in v): - items.extend([ - (f"{new_key}_mean", sum(v) / len(v) if v else 0), - (f"{new_key}_max", max(v) if v else 0), - (f"{new_key}_min", min(v) if v else 0) - ]) - else: - items.append((new_key, str(v))) - else: - items.append((new_key, v)) - - return dict(items) + + def start_scheduled_export(self, + data_provider: Callable[[], Union[Dict[str, Any], List[Dict[str, Any]]]], + interval_seconds: int = 3600, + export_format: str = 'json', + visualize: bool = True) -> None: + """定期的なエクスポートを開始 - def export_alert_history(self, alerts: List[Dict[str, Any]], file_name: str = None) -> str: - """アラート履歴のエクスポート""" - try: - if file_name is None: - file_name = f"alerts_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv" - - file_path = self.export_dir / file_name - - # 必要なフィールドを抽出 - alert_data = [] - for alert in alerts: - alert_data.append({ - "timestamp": alert["timestamp"], - "type": alert["type"], - "severity": alert["severity"], - "message": alert["message"] - }) - - # DataFrameに変換してCSV出力 - df = pd.DataFrame(alert_data) - df.to_csv(file_path, index=False) - - return str(file_path) - - except Exception as e: - self.logger.error(f"アラート履歴エクスポートエラー: {e}") - raise - - def export_optimization_history(self, history: Dict[str, Any], file_name: str = None) -> str: - """最適化履歴のエクスポート""" - try: - if file_name is None: - file_name = f"optimization_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json" + Args: + data_provider: データを提供する関数 + interval_seconds: エクスポート間隔(秒) + export_format: エクスポート形式('json'または'csv') + visualize: 視覚化を行うかどうか + """ + if self._scheduled_export_running: + self.logger.warning("定期的なエクスポートはすでに実行中です") + return + + self._scheduled_export_running = True + + def export_job(): + while self._scheduled_export_running: + try: + # データの取得 + data = data_provider() + + # データのエクスポート + timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') + if export_format == 'json': + if isinstance(data, list): + self.export_history_to_csv(data, f"scheduled_metrics_{timestamp}.csv") + else: + self.export_to_json(data, f"scheduled_metrics_{timestamp}.json") + else: + if isinstance(data, list): + self.export_history_to_csv(data, f"scheduled_metrics_{timestamp}.csv") + else: + self.export_to_csv(data, f"scheduled_metrics_{timestamp}.csv") + + # 視覚化が有効で、データがリスト形式の場合 + if visualize and isinstance(data, list): + self.visualize_hardware_metrics(data, f"scheduled_visualization_{timestamp}.png") + + except Exception as e: + self.logger.error(f"定期的なエクスポート中にエラーが発生しました: {e}") - file_path = self.export_dir / file_name - - with open(file_path, "w") as f: - json.dump(history, f, indent=2, default=str) + # 次のエクスポートまで待機 + time.sleep(interval_seconds) - return str(file_path) - - except Exception as e: - self.logger.error(f"最適化履歴エクスポートエラー: {e}") - raise + # バックグラウンドスレッドでエクスポートジョブを実行 + self._scheduled_export_thread = threading.Thread(target=export_job, daemon=True) + self._scheduled_export_thread.start() - async def async_export_to_csv(self, metrics: Dict[str, Any], file_name: str = None) -> str: - """CSVフォーマットで非同期エクスポート""" + self.logger.info(f"定期的なエクスポートを開始しました(間隔: {interval_seconds}秒)") + + def stop_scheduled_export(self) -> None: + """定期的なエクスポートを停止""" + if not self._scheduled_export_running: + self.logger.warning("定期的なエクスポートは実行されていません") + return + + self._scheduled_export_running = False + if self._scheduled_export_thread: + # スレッドが終了するのを待機(最大5秒) + self._scheduled_export_thread.join(5.0) + self._scheduled_export_thread = None + + self.logger.info("定期的なエクスポートを停止しました") + + # 非同期メソッド + async def async_export_to_csv(self, metrics: Dict[str, Any], file_name: Optional[str] = None) -> str: + """非同期でCSVエクスポートを実行""" loop = asyncio.get_event_loop() - return await loop.run_in_executor( - self.executor, - self.export_to_csv, - metrics, - file_name - ) + return await loop.run_in_executor(self.executor, self.export_to_csv, metrics, file_name) - async def async_export_to_json(self, metrics: Dict[str, Any], file_name: str = None) -> str: - """JSONフォーマットで非同期エクスポート""" + async def async_export_to_json(self, metrics: Dict[str, Any], file_name: Optional[str] = None) -> str: + """非同期でJSONエクスポートを実行""" loop = asyncio.get_event_loop() - return await loop.run_in_executor( - self.executor, - self.export_to_json, - metrics, - file_name - ) + return await loop.run_in_executor(self.executor, self.export_to_json, metrics, file_name) - async def async_export_history_to_csv(self, history: List[Dict[str, Any]], file_name: str = None) -> str: - """履歴データを非同期でCSVエクスポート""" + async def async_export_history_to_csv(self, history: List[Dict[str, Any]], file_name: Optional[str] = None) -> str: + """非同期で履歴CSVエクスポートを実行""" loop = asyncio.get_event_loop() - return await loop.run_in_executor( - self.executor, - self.export_history_to_csv, - history, - file_name - ) + return await loop.run_in_executor(self.executor, self.export_history_to_csv, history, file_name) - async def async_export_alert_history(self, alerts: List[Dict[str, Any]], file_name: str = None) -> str: - """アラート履歴を非同期でエクスポート""" + async def async_export_alert_history(self, alerts: List[Dict[str, Any]], file_name: Optional[str] = None) -> str: + """非同期でアラート履歴エクスポートを実行""" loop = asyncio.get_event_loop() - return await loop.run_in_executor( - self.executor, - self.export_alert_history, - alerts, - file_name - ) + return await loop.run_in_executor(self.executor, self.export_alert_history, alerts, file_name) - async def async_export_optimization_history(self, history: Dict[str, Any], file_name: str = None) -> str: - """最適化履歴を非同期でエクスポート""" + async def async_export_optimization_history(self, history: Dict[str, Any], file_name: Optional[str] = None) -> str: + """非同期で最適化履歴エクスポートを実行""" loop = asyncio.get_event_loop() - return await loop.run_in_executor( - self.executor, - self.export_optimization_history, - history, - file_name - ) + return await loop.run_in_executor(self.executor, self.export_optimization_history, history, file_name) + async def async_visualize_hardware_metrics(self, metrics_history: List[Dict[str, Any]], file_name: Optional[str] = None) -> str: + """非同期でハードウェアメトリクス視覚化を実行""" + loop = asyncio.get_event_loop() + return await loop.run_in_executor(self.executor, self.visualize_hardware_metrics, metrics_history, file_name) + def __del__(self): """デストラクタ""" ->>>>>>> 42de7d643d987d98855d441372a3931e7de31809 - self.executor.shutdown(wait=False) \ No newline at end of file + # 定期的なエクスポートを停止 + if self._scheduled_export_running: + self.stop_scheduled_export() + + # エグゼキューターを終了 + if hasattr(self, 'executor') and self.executor: + self.executor.shutdown(wait=False) \ No newline at end of file diff --git a/src/monitoring/system_monitor_integration.py b/src/monitoring/system_monitor_integration.py new file mode 100644 index 0000000..1db2d0d --- /dev/null +++ b/src/monitoring/system_monitor_integration.py @@ -0,0 +1,302 @@ +import logging +from typing import Dict, Any, List, Optional +import time +import threading +import os +from pathlib import Path +import json +from datetime import datetime, timedelta + +from .hardware_monitor import HardwareMonitor, HardwareMetrics +from .metrics_exporter import MetricsExporter +from .alert_manager import AlertManager + +class SystemMonitorIntegration: + """システムモニタリング統合クラス + + ハードウェアモニターとメトリクスエクスポーターを統合し、 + システムモニタリングの全体フローを管理します。 + """ + + def __init__(self, monitoring_interval: int = 5, + export_interval: int = 3600, + max_history_size: int = 500, + export_dir: str = "monitoring_data"): + """初期化 + + Args: + monitoring_interval: モニタリング間隔(秒) + export_interval: エクスポート間隔(秒) + max_history_size: 履歴の最大保持サイズ + export_dir: データエクスポート先ディレクトリ + """ + self.logger = logging.getLogger(__name__) + + # 各コンポーネントの初期化 + self.hardware_monitor = HardwareMonitor(max_history_size=max_history_size) + self.metrics_exporter = MetricsExporter(export_dir=export_dir) + self.alert_manager = AlertManager() + + # モニタリング設定 + self.monitoring_interval = monitoring_interval + self.export_interval = export_interval + self.max_history_size = max_history_size + + # 監視スレッド + self._monitoring_running = False + self._monitoring_thread = None + + # 前回のエクスポート時刻 + self.last_export_time = datetime.now() + + # 初期化ログ + self.logger.info(f"システムモニタリング統合を初期化しました(監視間隔: {monitoring_interval}秒, エクスポート間隔: {export_interval}秒)") + + def start_monitoring(self) -> None: + """モニタリングの開始""" + if self._monitoring_running: + self.logger.warning("モニタリングはすでに実行中です") + return + + self._monitoring_running = True + + def monitoring_job(): + """モニタリングジョブのメイン処理""" + self.logger.info("システムモニタリングを開始しました") + + alerts_buffer = [] + + while self._monitoring_running: + try: + # ハードウェアメトリクスの取得 + metrics = self.hardware_monitor.get_metrics() + + # アラートチェック + alerts = self._check_for_alerts(metrics) + if alerts: + alerts_buffer.extend(alerts) + + # アラートが蓄積されたらエクスポート + if len(alerts_buffer) >= 10: + self.metrics_exporter.export_alert_history( + alerts_buffer, + f"alerts_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv" + ) + alerts_buffer = [] + + # 定期的なエクスポート + now = datetime.now() + if (now - self.last_export_time).total_seconds() >= self.export_interval: + self._export_metrics() + self.last_export_time = now + + except Exception as e: + self.logger.error(f"モニタリング中にエラーが発生しました: {e}") + + # 次の監視まで待機 + time.sleep(self.monitoring_interval) + + # 残りのアラートをエクスポート + if alerts_buffer: + try: + self.metrics_exporter.export_alert_history( + alerts_buffer, + f"alerts_final_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv" + ) + except Exception as e: + self.logger.error(f"最終アラートエクスポート中にエラーが発生しました: {e}") + + self.logger.info("システムモニタリングを停止しました") + + # バックグラウンドスレッドで監視ジョブを実行 + self._monitoring_thread = threading.Thread(target=monitoring_job, daemon=True) + self._monitoring_thread.start() + + def stop_monitoring(self) -> None: + """モニタリングの停止""" + if not self._monitoring_running: + self.logger.warning("モニタリングは実行されていません") + return + + self._monitoring_running = False + if self._monitoring_thread: + # スレッドが終了するのを待機(最大10秒) + self._monitoring_thread.join(10.0) + self._monitoring_thread = None + + # 最終的なメトリクスをエクスポート + self._export_metrics(is_final=True) + + self.logger.info("システムモニタリングを停止しました") + + def _check_for_alerts(self, metrics: HardwareMetrics) -> List[Dict[str, Any]]: + """メトリクスからアラートを検出 + + Args: + metrics: 検査するハードウェアメトリクス + + Returns: + 検出されたアラートのリスト + """ + alerts = [] + + # CPU温度アラート + if metrics.cpu_temp >= 85: + alerts.append(self.alert_manager.create_alert( + alert_type="temperature", + message=f"CPU温度が危険レベルに達しています: {metrics.cpu_temp:.1f}°C", + severity="critical", + value=metrics.cpu_temp, + threshold=85 + )) + elif metrics.cpu_temp >= 75: + alerts.append(self.alert_manager.create_alert( + alert_type="temperature", + message=f"CPU温度が高温です: {metrics.cpu_temp:.1f}°C", + severity="warning", + value=metrics.cpu_temp, + threshold=75 + )) + + # GPU温度アラート + if metrics.gpu_temp >= 85: + alerts.append(self.alert_manager.create_alert( + alert_type="temperature", + message=f"GPU温度が危険レベルに達しています: {metrics.gpu_temp:.1f}°C", + severity="critical", + value=metrics.gpu_temp, + threshold=85 + )) + elif metrics.gpu_temp >= 75: + alerts.append(self.alert_manager.create_alert( + alert_type="temperature", + message=f"GPU温度が高温です: {metrics.gpu_temp:.1f}°C", + severity="warning", + value=metrics.gpu_temp, + threshold=75 + )) + + # CPU使用率アラート + if metrics.cpu_usage >= 90: + alerts.append(self.alert_manager.create_alert( + alert_type="usage", + message=f"CPU使用率が非常に高くなっています: {metrics.cpu_usage:.1f}%", + severity="warning", + value=metrics.cpu_usage, + threshold=90 + )) + + # メモリ使用率アラート + if metrics.memory_usage >= 95: + alerts.append(self.alert_manager.create_alert( + alert_type="memory", + message=f"メモリ使用率が危険レベルに達しています: {metrics.memory_usage:.1f}%", + severity="critical", + value=metrics.memory_usage, + threshold=95 + )) + elif metrics.memory_usage >= 85: + alerts.append(self.alert_manager.create_alert( + alert_type="memory", + message=f"メモリ使用率が高くなっています: {metrics.memory_usage:.1f}%", + severity="warning", + value=metrics.memory_usage, + threshold=85 + )) + + return alerts + + def _export_metrics(self, is_final: bool = False) -> None: + """メトリクスのエクスポート処理 + + Args: + is_final: 最終エクスポートかどうか + """ + try: + # メトリクス履歴の取得 + temp_history = self.hardware_monitor.get_temperature_history() + usage_history = self.hardware_monitor.get_usage_history() + + # 履歴データをマージ + merged_history = [] + for i in range(len(self.hardware_monitor.metrics_history)): + metrics = self.hardware_monitor.metrics_history[i] + merged_history.append(metrics.to_dict()) + + # CSVエクスポート + timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') + prefix = "final_" if is_final else "" + + if merged_history: + # メトリクスデータCSV + self.metrics_exporter.export_history_to_csv( + merged_history, + f"{prefix}metrics_history_{timestamp}.csv" + ) + + # JSONエクスポート + self.metrics_exporter.export_to_json( + {"temperature": temp_history, "usage": usage_history}, + f"{prefix}metrics_summary_{timestamp}.json" + ) + + # 視覚化 + self.metrics_exporter.visualize_hardware_metrics( + merged_history, + f"{prefix}hardware_metrics_{timestamp}.png" + ) + + self.logger.info(f"メトリクスデータをエクスポートしました (final={is_final})") + + except Exception as e: + self.logger.error(f"メトリクスエクスポート中にエラーが発生しました: {e}") + + def get_current_metrics(self) -> HardwareMetrics: + """現在のメトリクスを取得 + + Returns: + 現在のハードウェアメトリクス + """ + return self.hardware_monitor.get_metrics() + + def get_metrics_history(self) -> List[Dict[str, Any]]: + """メトリクス履歴を取得 + + Returns: + メトリクス履歴のリスト + """ + return [m.to_dict() for m in self.hardware_monitor.metrics_history] + + def get_gpu_details(self) -> Dict[str, Any]: + """GPUの詳細情報を取得 + + Returns: + GPU情報の辞書 + """ + return self.hardware_monitor.get_gpu_details() + + def export_current_metrics(self, format: str = 'json') -> str: + """現在のメトリクスをエクスポート + + Args: + format: エクスポート形式 ('json'または'csv') + + Returns: + エクスポートされたファイルのパス + """ + metrics = self.hardware_monitor.get_metrics() + + if format.lower() == 'json': + return self.metrics_exporter.export_to_json(metrics.to_dict()) + else: + return self.metrics_exporter.export_to_csv(metrics.to_dict()) + + def clear_metrics_history(self) -> None: + """メトリクス履歴のクリア""" + self.hardware_monitor.clear_history() + self.logger.info("メトリクス履歴をクリアしました") + + def __del__(self): + """デストラクタ""" + if self._monitoring_running: + self.stop_monitoring() \ No newline at end of file diff --git a/start.bat b/start.bat index ff9ae92..c604936 100644 --- a/start.bat +++ b/start.bat @@ -1,20 +1,20 @@ -<<<<<<< HEAD @echo off setlocal :: Pythonの仮想環境が存在しない場合は作成 -if not exist "venv" ( +if not exist ".venv" ( echo 仮想環境を作成中... - python -m venv venv - call venv\Scripts\activate + python -m venv .venv + call .venv\Scripts\activate python -m pip install --upgrade pip pip install -r requirements.txt ) else ( - call venv\Scripts\activate + call .venv\Scripts\activate ) :: アプリケーションの起動 echo デスクトップエージェントを起動中... +echo 注意: 既に別のインスタンスが実行中の場合は起動できません python src/main.py :: エラーが発生した場合は表示 @@ -23,30 +23,4 @@ if errorlevel 1 ( pause ) -======= -@echo off -setlocal - -:: Pythonの仮想環境が存在しない場合は作成 -if not exist "venv" ( - echo 仮想環境を作成中... - python -m venv venv - call venv\Scripts\activate - python -m pip install --upgrade pip - pip install -r requirements.txt -) else ( - call venv\Scripts\activate -) - -:: アプリケーションの起動 -echo デスクトップエージェントを起動中... -python src/main.py - -:: エラーが発生した場合は表示 -if errorlevel 1 ( - echo エラーが発生しました。 - pause -) - ->>>>>>> 42de7d643d987d98855d441372a3931e7de31809 endlocal \ No newline at end of file diff --git a/start_app.bat b/start_app.bat index 733f7be..583be7e 100644 --- a/start_app.bat +++ b/start_app.bat @@ -1,10 +1,4 @@ -<<<<<<< HEAD @echo off cd %~dp0 python src/main.py -======= -@echo off -cd %~dp0 -python src/main.py ->>>>>>> 42de7d643d987d98855d441372a3931e7de31809 pause \ No newline at end of file