チャット送信時にフローティングウィンドウで進捗を表示した(ChatSpooler開発記)

  • ChatSpoolerでフローティングウィンドウ機能を追加しました。
  • 他の作業中でも送信状況を確認できる小さなウィンドウです。
  • macOSのアクセシビリティ権限エラーで詰まったのですが、AppleScriptからpyautoguiに変更したら解決。権限設定不要になって使いやすくなりました。
ChatSpooler機能拡張の開発体験 解決した課題 他作業中の状況確認 ドラッグ移動の実装 macOS権限エラー 作業効率の阻害要因 実装した機能 • フローティングウィンドウ • 座標計算の改善 • pyautogui移行 • 履歴先頭/末尾追加 • 待機継続機能 得られた成果 常時状況確認可能 直感的な操作性 権限設定不要 実用性向上 主要技術要素 tkinter pyautogui macOS Toplevel, ドラッグ処理, キー操作 UI設計の学び ✓ 情報の優先順位を明確化 ✓ ユーザー意図の正しい理解 ✓ 技術制約の回避方法検討 体験重視の設計アプローチ

機能追加のきっかけ

前回の記事でChatSpoolerというAIチャット自動送信ツールの基本機能を実装しました。今回は、そのツールに新しい機能を追加する過程で直面した課題と、その解決策を共有します。特に、ユーザーの操作性を向上させるためのUI設計について詳しく解説します。

ChatSpoolerの基本機能は動作していましたが、実際に使ってみると不便な点がいくつか見つかりました。最も大きな問題は、送信状況を確認するために常にアプリケーションウィンドウを表示しておく必要があることでした。

AIチャットツールで長文を送信する際、他の作業をしながら送信完了を待つことが多くあります。しかし、送信状況を確認するたびにアプリを前面に出す必要があり、作業の流れが中断されていました。これは料理をしながらタイマーを確認するようなもので、いちいちキッチンタイマーを見に行くのは効率的ではありません。

フローティングウィンドウの設計

この問題を解決するため、フローティングウィンドウ(画面に常時表示される小さなウィンドウ)を実装することにしました。フローティングウィンドウは、他のアプリケーションを使用中でも送信状況を一目で確認できる仕組みです。

設計段階で重要だったのは、表示する情報の選択でした。送信に関する情報はたくさんありますが、すべてを表示すると画面が煩雑になります。最終的に、現在の状態と残りキュー件数の2つに絞りました。これは腕時計の文字盤のように、必要最小限の情報を見やすく配置する考え方です。

class FloatingStatusWindow:
    def __init__(self, parent_root: tk.Tk, logger: Optional[Logger] = None):
        self.window = tk.Toplevel(parent_root)
        self.window.attributes('-topmost', True)  # 常に最前面
        self.window.attributes('-alpha', 0.9)     # 半透明
        self.window.overrideredirect(True)        # タイトルバー非表示

フローティングウィンドウの実装では、tkinterのToplevelウィジェットを使用しました。-topmost属性で常に最前面に表示し、-alpha属性で半透明にすることで他の作業の邪魔にならないようにしています。overrideredirect(True)でタイトルバーを非表示にし、コンパクトなデザインを実現しました。

ドラッグ移動機能の実装

フローティングウィンドウには移動機能が必要です。ユーザーが作業する画面の邪魔にならない位置に配置できるからです。ドラッグ移動の実装は、マウスイベントの処理が核心となります。

def start_drag(self, event):
    """ドラッグ開始"""
    self.drag_start_x = event.x_root - self.window.winfo_x()
    self.drag_start_y = event.y_root - self.window.winfo_y()

def on_drag(self, event):
    """ドラッグ中の処理"""
    new_x = event.x_root - self.drag_start_x
    new_y = event.y_root - self.drag_start_y
    self.window.geometry(f"+{new_x}+{new_y}")
Code language: PHP (php)

ドラッグ移動の仕組みは、マウスの位置とウィンドウの位置の相対関係を追跡することです。ドラッグ開始時にマウスとウィンドウの位置関係を記録し、ドラッグ中はその関係を保ちながらウィンドウを移動させます。これは、物を掴んで移動させる時の手の動きと同じ原理です。

履歴機能の使い勝手改善

ChatSpoolerには送信履歴機能がありましたが、履歴から再送信する際の選択肢が限られていました。従来は「選択項目を再送信」ボタンしかなく、履歴のアイテムは常にキューの先頭(次に送信される位置)に追加されていました。

しかし、実際の使用場面では、緊急で送りたいものと後で送りたいものを使い分ける必要があります。そこで「選択項目を末尾に追加」ボタンを追加し、ユーザーが用途に応じて選択できるようにしました。

def insert_item_at_front(self, text: str, wait_time: int) -> bool:
    """キューの先頭にアイテムを挿入"""
    item = QueueItemData(text.strip(), wait_time)
    self.queue_items.insert(0, item)  # 先頭に挿入
    return True

def add_item(self, text: str, wait_time: int) -> bool:
    """キューの末尾にアイテムを追加"""
    item = QueueItemData(text.strip(), wait_time)
    self.queue_items.append(item)  # 末尾に追加
    return True
Code language: PHP (php)

この機能は、キューデータ構造の基本的な操作である先頭挿入と末尾追加を使い分けています。再送信はinsert(0, item)で先頭に挿入し、末尾追加はappend(item)で末尾に追加します。これにより、ユーザーは送信の優先度を柔軟に制御できるようになりました。

送信遅延の操作性向上

送信遅延機能も使い勝手を改善しました。従来は数値を直接入力する必要がありましたが、よくある操作として「もう少し待ってから送信したい」という要望に対応するため、「+10秒」ボタンを追加しました。

def add_send_delay(self):
    """送信遅延に10秒を追加"""
    try:
        current_delay = int(self.send_delay_var.get())
        new_delay = current_delay + 10
        
        if new_delay > Config.MAX_SEND_DELAY:
            new_delay = Config.MAX_SEND_DELAY
        
        self.send_delay_var.set(str(new_delay))
    except ValueError:
        self.send_delay_var.set("10")
Code language: PHP (php)

この機能により、数値入力の手間を省き、直感的な操作で遅延時間を調整できるようになりました。また、送信遅延の最大値を60秒から1000秒に拡張し、より長時間の遅延設定にも対応しました。

待機処理の改善

送信システムの動作で重要な改善を行ったのが、待機処理の継続性です。従来の実装では、ユーザーが待機をスキップすると送信プロセス全体が停止していました。しかし、これは直感的な動作ではありません。

待機のスキップは「早く次に進みたい」という意味であり、「送信を停止したい」という意味ではないからです。そこで、待機をスキップしてもキューが残っていれば自動的に送信を継続するよう修正しました。

# 待機が中断された場合でも、キューが残っていれば継続
if not wait_completed and self.is_running and not self.queue_manager.is_empty():
    self.logger.log("待機が中断されましたが、キューが残っているため送信を継続します")
    continue  # ループを継続
elif not wait_completed and not self.is_running:
    break
Code language: PHP (php)

この修正により、ユーザーの意図に沿った動作を実現できました。停止ボタンは完全な停止、スキップボタンは待機時間の短縮という役割分担が明確になったのです。

macOSセキュリティ制限への対応

開発過程で遭遇した技術的な課題として、macOSのセキュリティ制限がありました。AppleScriptでキー操作を送信する際、「osascriptにはキー操作の送信は許可されません」というエラーが発生することがあります。

これはmacOSがセキュリティ強化のため、アプリケーションによるキー操作を制限しているためです。従来はアクセシビリティ設定でアプリケーションに権限を付与する必要がありましたが、ユーザーにとって設定が複雑でした。

そこで、AppleScriptの代わりにpyautoguiライブラリを使用するよう変更しました。pyautoguiは異なる仕組みでキー操作を実現するため、追加の権限設定が不要です。

@staticmethod
def paste_and_send() -> bool:
    """ペーストして送信(アクセシビリティ権限エラー対応版)"""
    try:
        import pyautogui
        pyautogui.hotkey('cmd', 'v')  # ペースト
        time.sleep(0.3)
        pyautogui.hotkey('cmd', 'return')  # 送信
        return True
    except Exception as e:
        return False
Code language: PHP (php)

この対応により、ユーザーは複雑な設定をせずにツールを使用できるようになりました。

まとめ

ChatSpoolerの機能拡張を通じて、実用的なデスクトップアプリケーションの開発における重要な要素を確認できました。フローティングウィンドウによる常時表示機能、履歴操作の柔軟性向上、送信遅延の操作性改善、待機処理の継続性確保、そしてmacOSセキュリティ制限への対応という5つの改善により、ツールの使い勝手が大幅に向上しました。

特に重要だったのは、ユーザーの実際の使用場面を想定し、技術的実装よりもユーザー体験を優先して設計することでした。UI設計においては、機能の完全性よりも操作の直感性を重視し、技術的制約に対しては代替手段を用意することで、ユーザーにとって使いやすいツールを実現できたのです。

  1. Python tkinter Documentation – フローティングウィンドウとUI実装で使用したtkinterライブラリの公式ドキュメント
  2. PyAutoGUI Documentation – macOSセキュリティ制限回避で採用したpyautoguiライブラリの使用方法とAPI仕様
  3. Apple Developer – Accessibility APIs – macOSアクセシビリティ権限とセキュリティ制限について理解するためのApple公式開発者向け資料
  4. Python Data Structures – キュー管理の先頭挿入・末尾追加で使用したPythonリスト操作の公式解説
  5. macOS Human Interface Guidelines – フローティングウィンドウのUI設計で参考にしたAppleのデザインガイドライン
  6. Threading in Python – 送信制御とUI更新で使用したマルチスレッド処理の公式ドキュメント
  7. JSON Data Interchange Format – 設定管理の永続化で使用したJSON形式の仕様と使用方法