見出し画像

生成AI時代にあらかじめ登録したメッセージから適当な言葉を表示したりチャットで会話するデスクトップマスコットを作ってみた

注意:生成AIは搭載されていません

日常の仕事や作業中にちょっとした癒しやインタラクションを提供できるツールがあると便利です。生成AIの時代に今回は、生成AIの力を借りて長いコードを作成し、あらかじめ登録されたメッセージから適当な言葉を表示したり、チャットで会話したりする生成AIは搭載されていないデスクトップマスコットを作ってみました。このマスコットは、実行ファイルやDLLを用いず、純粋にPowerShellスクリプトのみで実現されています。
スクリプトだけでこんなものができてしまうとは、生成AIすごい。

画像は自分の用意したものをランダムに切り替えたりデスクトップをちょこまか動いたり適当に移動したり、、、
メッセージは時間帯ごとにあらかじめ登録したものが切り替わったり、
チャット画面では入力したことばに適当に返してきたりします。


正直なところ、生成AIがなければ、こんなに長いコードをスクリプトだけで書くのは非常に困難です。生成AIのおかげで、複雑な機能を持つこのデスクトップマスコットを実現することができました。

利便性も一応考えておりチャットの履歴はテキストで残りますし
右クリックからほかのウインドウを閉じたり(マスコットはそのまま消えません笑)
フォルダにいれたものを呼び出すランチャー機能、独自のPowerShellスクリプトを拡張機能に盛り込んで「特定のウインドウを最前面に表示する」「圧縮・分割」「Google検索」など以前に紹介したものなども組み込んでいます。

このデスクトップマスコットには、以下のような特徴があります。

  • ランダムメッセージ表示: あらかじめ登録されたメッセージから、現在の時間帯に応じて適当な言葉を表示します。

  • チャット機能: ユーザーとの簡単な会話をシミュレートすることができます。ユーザーが入力したテキストに対して、自動で応答を返します。

  • メモ機能: チャット画面でメモのみを入力することができ、メモがチャットログに保存されます。

  • ランダム移動機能: 画面上をランダムに移動し、デスクトップに動きのあるエフェクトを追加します。

  • スキン変更機能: 画像フォルダを変更することで、マスコットの見た目をカスタマイズできます。

  • 浮動モード: 画面下部のタスクバー上部を左右に動きます。

  • 複数起動: スクリプトを複数起動することで、複数のマスコットを同時に表示できます。

  • メッセージの時間帯別表示機能: メッセージを時間帯に応じて表示します。

  • 拡張スクリプト: Google検索機能、最前面表示機能、ファイルの圧縮・分割機能などが含まれます。

  • 圧縮・分割機能: ファイルを圧縮および分割するためには7-Zipが必要です。

  • ランチャー機能: お気に入りのアプリケーションやスクリプトを簡単に起動できます。

  • ランダム移動の癒し効果: マスコットがランダムに移動することで、デスクトップに動きが生まれ、癒し効果があります。

フォルダ構造


├── img/
│   ├── default/
│   │   ├── image1.png
│   │   ├── image2.gif
│   └── anotherSkin/
│       ├── image1.png
│       ├── image2.gif
├── extension/
│   ├── google_search.ps1
│   ├── chat.ps1
│   ├── topmost_window.ps1
│   └── compress_split.ps1
├── launch/
│   └── 起動したいショートカットなど
├── messages.json
├── 起動.vbs
├── call.bat
└── main.ps1


メッセージの内容の変更方法

生成AIに読み込んでもらい編集性もらってもいいですが

  1. スクリプトディレクトリにある `messages.json` ファイルをメモ帳などで開きます。

  2. モードと時間帯ごとにメッセージが区切られているのでメッセージを追加または編集します。ファイルは以下のような構造になっています。

{
  "チャット": {
    "morning": ["おはようございます!", "良い一日を!"],
    "lateMorning": ["こんにちは!", "元気ですか?"],
    "noon": ["お昼ですね!", "休憩しましょう!"],
    "earlyAfternoon": ["午後も頑張りましょう!", "調子はどうですか?"],
    "evening": ["こんばんは!", "お疲れ様です!"],
    "night": ["おやすみなさい!", "今日も一日お疲れ様でした!"]
  }
}

3.変更を保存し、スクリプトを再実行します。

スキンの変更方法

好きな画像に変更できます。切り替えも可能です。
1枚だけ入れるとそれだけに固定ができます。

  1. `img` フォルダ内に新しいフォルダを作成し、使用したい画像ファイル(PNG形式)をその中に保存します。

  2. スクリプト実行中に右クリックメニューから「スキン変更」を選択し、新しいスキンフォルダを選択します。

  3. スクリプトが新しいスキンを読み込み、マスコットの見た目が変更されます。

拡張スクリプトについて

このデスクトップマスコットは、Google検索機能や最前面表示機能、ファイルの圧縮・分割機能などの拡張スクリプトを含んでいます。これらの拡張機能は、右クリックメニューから簡単にアクセスできます。

  • Google検索: 右クリックメニューの「拡張」から「Google検索」を選択すると、Google検索ウィンドウが表示されます。

  • 最前面に表示: 右クリックメニューの「拡張」から「最前面に表示」を選択すると、指定したウィンドウを最前面に表示します。

  • 圧縮・分割: 右クリックメニューの「拡張」から「圧縮・分割」を選択すると、ファイルを圧縮・分割するツールが起動します。この機能には7-Zipが必要です。

ランチャー機能について

デスクトップマスコットにはランチャー機能があり、お気に入りのアプリケーションやスクリプトを簡単に起動することができます。ランチャーに登録されたアイテムは、右クリックメニューの「ランチャー」からアクセスできます。これにより、デスクトップの整理や効率的な作業が可能になります。

ランダム移動の癒し効果について

このデスクトップマスコットは、ランダムに画面上を移動する機能を持っています。
固定も可能です。

スクリプト

スクリプトを張ると長いのでダウンロードできるようにしています。

起動方法

ファイルを適当な個所に解凍し、中の「起動.vbs」を実行すると起動します。

まとめ

今回作成したデスクトップマスコットは、生成AIのサポートを受けて、純粋にPowerShellスクリプトのみで実現されました。あらかじめ登録されたメッセージを表示する機能、チャット機能、メモ機能、ランダム移動機能、スキン変更機能、浮動モードなど、多くの機能を備えています。複数起動することで、複数のマスコットを同時に表示することも可能です。皆さんもぜひ、自分だけのデスクトップマスコットを作成してみてください。

生成AIとPowerShellの力を活用すればかなりのことができますしアイデアをすぐ具現化できるのはたのしいですね。


コード(main.ps1)

Add-Type -AssemblyName PresentationCore,PresentationFramework,WindowsBase,System.Windows.Forms

# パス設定
$scriptDir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
$extensionFolderPath = Join-Path -Path $scriptDir -ChildPath "extension"
$paths = @{
    ImageFolder = Join-Path -Path $scriptDir -ChildPath "img"
    MessagesFile = Join-Path -Path $scriptDir -ChildPath "messages.json"
    LaunchFolder = Join-Path -Path $scriptDir -ChildPath "launch"
    ChatFolder = Join-Path -Path $extensionFolderPath -ChildPath "chat"
    SettingsFile = Join-Path -Path $scriptDir -ChildPath "settings.json"
    GoogleSearchScript = Join-Path -Path $extensionFolderPath -ChildPath "google_search.ps1"
    ChatScript = Join-Path -Path $extensionFolderPath -ChildPath "chat.ps1"
    TopmostWindowScript = Join-Path -Path $extensionFolderPath -ChildPath "topmost_window.ps1"
    CompressSplitScript = Join-Path -Path $extensionFolderPath -ChildPath "compress_split.ps1"
}

# 必要なスクリプトをインポート
. $paths.GoogleSearchScript
. $paths.ChatScript
. $paths.TopmostWindowScript

# 設定ファイルの読み込みまたは作成
if (-Not (Test-Path $paths.SettingsFile)) {
    $settings = @{
        LastSelectedMode = "チャット"
        FixedPosition = "右下"
        ImageSize = "中"
        MessageVisible = $true
        MessageSize = "中"
        SkinFolder = "default"
        MessageStyle = "Transparent"
        PrevMessageVisible = $true
        FloatingAboveTaskbar = $false
    }
    $settings | ConvertTo-Json | Set-Content -Path $paths.SettingsFile
} else {
    $settings = Get-Content -Path $paths.SettingsFile -Raw | ConvertFrom-Json
    foreach ($key in @("SkinFolder", "MessageStyle", "PrevMessageVisible", "FloatingAboveTaskbar")) {
        if (-Not $settings.PSObject.Properties.Match($key).Count) {
            $settings | Add-Member -MemberType NoteProperty -Name $key -Value (if ($key -eq "PrevMessageVisible" -or $key -eq "FloatingAboveTaskbar") { $false } else { "default" })
        }
    }
}

# 画像フォルダのパスを設定
$global:skinFolder = $settings.SkinFolder
$global:imageFolderPath = Join-Path -Path $paths.ImageFolder -ChildPath $global:skinFolder

# 画像ファイルのリスト取得
function LoadImageFiles {
    $global:imageFolderPath = Join-Path -Path $paths.ImageFolder -ChildPath $global:skinFolder
    $imageFiles = Get-ChildItem -Path $global:imageFolderPath | Where-Object { $_.Extension -match "\.(png|gif)$" }
    if ($imageFiles.Count -eq 0) { Write-Host "画像がありません"; exit }
    $global:imageFiles = $imageFiles
}

LoadImageFiles

# メッセージファイルの読み込み
if (-Not (Test-Path $paths.MessagesFile)) { Write-Host "メッセージファイルがありません"; exit }
$messagesJson = Get-Content -Path $paths.MessagesFile -Raw | ConvertFrom-Json

# グローバル変数
$global:currentMode = $settings.LastSelectedMode
$global:clickCount = 0
$maxClicks = 10
$global:fixedPosition = $settings.FixedPosition
$global:imageSize = $settings.ImageSize
$global:messageVisible = $settings.MessageVisible
$global:messageSize = $settings.MessageSize
$global:messageStyle = $settings.MessageStyle
$global:prevMessageVisible = $settings.PrevMessageVisible
$global:floatingAboveTaskbar = $settings.FloatingAboveTaskbar
$global:floatingDirection = "Right"
$global:clickTime = [DateTime]::Now

# メッセージ取得
function GetMessage {
    $hour = (Get-Date).Hour
    $messages = switch ($hour) {
        {$_ -ge 6 -and $_ -lt 9} { $messagesJson.$global:currentMode.morning }
        {$_ -ge 9 -and $_ -lt 12} { $messagesJson.$global:currentMode.lateMorning }
        {$_ -ge 12 -and $_ -lt 14} { $messagesJson.$global:currentMode.noon }
        {$_ -ge 14 -and $_ -lt 17} { $messagesJson.$global:currentMode.earlyAfternoon }
        {$_ -ge 17 -and $_ -lt 20} { $messagesJson.$global:currentMode.evening }
        default { $messagesJson.$global:currentMode.night }
    }
    return $messages[$random.Next($messages.Count)]
}

# ウィンドウ作成
[void][System.Reflection.Assembly]::LoadWithPartialName("PresentationFramework")
$window = New-Object Windows.Window -Property @{
    Title = "デスクトップペット"
    WindowStyle = "None"
    AllowsTransparency = $true
    Background = [Windows.Media.Brushes]::Transparent
    Topmost = $true
    ShowInTaskbar = $false
}

# 画像追加
$image = New-Object Windows.Controls.Image
$random = New-Object Random
$randomImagePath = $global:imageFiles[$random.Next($global:imageFiles.Count)].FullName
$image.Source = [Windows.Media.Imaging.BitmapImage]::new([Uri]::new($randomImagePath))

# 画像サイズ設定関数
$imageSizeDictionary = @{
    "特特大" = @{ Width = 337.5; Height = 337.5; WindowWidth = 450; WindowHeight = 475 }
    "特大"   = @{ Width = 225;   Height = 225;   WindowWidth = 300; WindowHeight = 325 }
    "大"     = @{ Width = 150;   Height = 150;   WindowWidth = 200; WindowHeight = 250 }
    "中"     = @{ Width = 100;   Height = 100;   WindowWidth = 200; WindowHeight = 200 }
    "小"     = @{ Width = 50;    Height = 50;    WindowWidth = 200; WindowHeight = 150 }
    "タスクバー" = @{ Width = 40; Height = 40; WindowWidth = 40; WindowHeight = 40 }
}

function SetImageSize($size) {
    if ($imageSizeDictionary.ContainsKey($size)) {
        $imageSize = $imageSizeDictionary[$size]
        $image.Width = $imageSize.Width
        $image.Height = $imageSize.Height
        $window.Width = $imageSize.WindowWidth
        $window.Height = $imageSize.WindowHeight
    }
}

# 初期画像サイズ設定
if ($global:floatingAboveTaskbar) {
    SetImageSize "タスクバー"
} else {
    SetImageSize $global:imageSize
}

# メッセージ表示用テキストブロック
$textBlock = New-Object Windows.Controls.TextBlock -Property @{
    Text = GetMessage
    HorizontalAlignment = "Center"
    VerticalAlignment = "Bottom"
    TextWrapping = "Wrap"
    Width = 180
}

# メッセージ表示用ボーダー
$border = New-Object Windows.Controls.Border -Property @{
    Child = $textBlock
    HorizontalAlignment = "Center"
    VerticalAlignment = "Bottom"
}

# メッセージスタイル設定関数
function SetMessageStyle($style) {
    switch ($style) {
        "Transparent" {
            $textBlock.Background = [Windows.Media.Brushes]::Transparent
            $textBlock.Foreground = [Windows.Media.Brushes]::Black
            $border.Background = [Windows.Media.Brushes]::Transparent
            $border.BorderThickness = [Windows.Thickness]::new(0)
        }
        "Bubble" {
            $textBlock.Background = [Windows.Media.Brushes]::White
            $textBlock.Foreground = [Windows.Media.Brushes]::Black
            $border.Background = [Windows.Media.Brushes]::White
            $border.BorderThickness = [Windows.Thickness]::new(1)
            $border.BorderBrush = [Windows.Media.Brushes]::Black
            $border.CornerRadius = [Windows.CornerRadius]::new(10)
            $textBlock.Padding = [Windows.Thickness]::new(5)
            $textBlock.Margin = [Windows.Thickness]::new(0, 0, 0, 10)
        }
    }
}

# メッセージサイズ設定関数
function SetMessageSize($size) {
    $textBlock.FontSize = switch ($size) {
        "大" { 20 }
        "中" { 14 }
        "小" { 10 }
    }
}

# 初期メッセージサイズ・スタイル設定
SetMessageSize $global:messageSize
SetMessageStyle $global:messageStyle

# 初期メッセージ表示設定
function UpdateMessageVisibility {
    $visibility = if ($global:floatingAboveTaskbar -or -not $global:messageVisible) { "Collapsed" } else { "Visible" }
    $textBlock.Visibility = $visibility
    $border.Visibility = $visibility
}

UpdateMessageVisibility

# スタックパネルに画像とテキストを追加
$stackPanel = New-Object Windows.Controls.StackPanel
$stackPanel.Children.Add($image)
$stackPanel.Children.Add($border)
$window.Content = $stackPanel

# ポジション設定関数
function SetPosition($position) {
    if ($global:floatingAboveTaskbar) {
        $window.Top = [System.Windows.SystemParameters]::PrimaryScreenHeight - 150 - $window.Height
        $window.Left = 10
    } else {
        switch ($position) {
            "右下" {
                $window.Left = [System.Windows.SystemParameters]::PrimaryScreenWidth - $window.Width - 10
                $window.Top = [System.Windows.SystemParameters]::PrimaryScreenHeight - $window.Height - 10
            }
            "右上" {
                $window.Left = [System.Windows.SystemParameters]::PrimaryScreenWidth - $window.Width - 10
                $window.Top = 10
            }
            "左上" {
                $window.Left = 10
                $window.Top = 10
            }
            "左下" {
                $window.Left = 10
                $window.Top = [System.Windows.SystemParameters]::PrimaryScreenHeight - $window.Height - 10
            }
        }
    }
}

# 浮遊モードの動き設定
$floatingTimer = New-Object System.Windows.Threading.DispatcherTimer
$floatingTimer.Interval = [TimeSpan]::FromMilliseconds(100)
$floatingTimer.add_Tick({
    if ($global:floatingAboveTaskbar) {
        if ($global:floatingDirection -eq "Right") {
            if ($window.Left + $window.Width + 5 -gt [System.Windows.SystemParameters]::PrimaryScreenWidth) {
                $global:floatingDirection = "Left"
            } else {
                $window.Left += 5
            }
        } else {
            if ($window.Left - 5 -lt 0) {
                $global:floatingDirection = "Right"
            } else {
                $window.Left -= 5
            }
        }
    }
})

# ランダム移動関数
function MoveAndChangeMessage {
    $window.Dispatcher.Invoke([action]{
        LoadImageFiles  # 現在のスキンフォルダの画像を再読み込み
        if ($global:fixedPosition -eq "ランダム" -and -not $global:floatingAboveTaskbar) {
            $window.Left = $random.Next([System.Windows.SystemParameters]::PrimaryScreenWidth - $window.Width)
            $window.Top = $random.Next([System.Windows.SystemParameters]::PrimaryScreenHeight - $window.Height)
        } else {
            SetPosition $global:fixedPosition
        }
        $textBlock.Text = GetMessage
        $randomImagePath = $global:imageFiles[$random.Next($global:imageFiles.Count)].FullName
        $image.Source = [Windows.Media.Imaging.BitmapImage]::new([Uri]::new($randomImagePath))
    })
}

# 初期ポジション設定
SetPosition $global:fixedPosition

# 5分ごとに移動
$moveTimer = New-Object System.Windows.Threading.DispatcherTimer
$moveTimer.Interval = [TimeSpan]::FromMinutes(5)
$moveTimer.add_Tick({ MoveAndChangeMessage })
$moveTimer.Start()

# 25分ごとに休憩メッセージ
$restTimer = New-Object System.Windows.Threading.DispatcherTimer
$restTimer.Interval = [TimeSpan]::FromMinutes(1)
$restTimer.add_Tick({
    $elapsedMinutes = ([DateTime]::Now - $global:clickTime).TotalMinutes
    if ($elapsedMinutes -ge 25 -and -not $global:floatingAboveTaskbar) {
        $window.Dispatcher.Invoke([action]{
            $textBlock.Text += " 25分経過しました。少し休憩しましょう!"
        })
        $global:clickTime = [DateTime]::Now
    }
})
$restTimer.Start()

# すべてのウィンドウを最小化
function MinimizeAllWindows {
    (New-Object -ComObject Shell.Application).MinimizeAll()
}

# ポジション固定
function FixPosition($position) {
    $global:fixedPosition = $position
    $settings.FixedPosition = $position
    $settings | ConvertTo-Json | Set-Content -Path $paths.SettingsFile
    SetPosition $position
}

# 固定解除
function UnfixPosition {
    $global:fixedPosition = "ランダム"
    $settings.FixedPosition = "ランダム"
    $settings | ConvertTo-Json | Set-Content -Path $paths.SettingsFile
    MoveAndChangeMessage
}

# 画像サイズ設定
function SetImageSizeAndSave($size) {
    $global:imageSize = $size
    $settings.ImageSize = $size
    $settings | ConvertTo-Json | Set-Content -Path $paths.SettingsFile
    SetImageSize $size
}

# メッセージ表示設定
function SetMessageVisibility($visible) {
    $global:messageVisible = $visible
    $settings.MessageVisible = $visible
    $settings | ConvertTo-Json | Set-Content -Path $paths.SettingsFile
    UpdateMessageVisibility
}

# メッセージスタイル設定
function SetMessageStyleAndSave($style) {
    $global:messageStyle = $style
    $settings.MessageStyle = $style
    $settings | ConvertTo-Json | Set-Content -Path $paths.SettingsFile
    SetMessageStyle $style
}

# メッセージサイズ設定
function SetMessageSizeAndSave($size) {
    $global:messageSize = $size
    $settings.MessageSize = $size
    $settings | ConvertTo-Json | Set-Content -Path $paths.SettingsFile
    SetMessageSize $size
}

# スキン設定関数
function SetSkinFolder($folder) {
    $global:skinFolder = $folder
    $settings.SkinFolder = $folder
    $settings | ConvertTo-Json | Set-Content -Path $paths.SettingsFile
    LoadImageFiles
    MoveAndChangeMessage
}

# 浮動モード設定
function SetFloatingAboveTaskbar($enabled) {
    $global:floatingAboveTaskbar = $enabled
    $settings.FloatingAboveTaskbar = $enabled
    if ($enabled) {
        $settings.PrevMessageVisible = $global:messageVisible
        SetImageSize "タスクバー"
        SetMessageVisibility $false
        $floatingTimer.Start()
        $window.Left = 0  # 左端から開始
    } else {
        $floatingTimer.Stop()
        SetImageSize $global:imageSize
        SetMessageVisibility $settings.PrevMessageVisible
    }
    $settings | ConvertTo-Json | Set-Content -Path $paths.SettingsFile
    MoveAndChangeMessage
}

# コンテキストメニューアイテム作成
function CreateMenuItem($header, $clickAction) {
    New-Object Windows.Controls.MenuItem -Property @{
        Header = $header
        Add_Click = $clickAction
    }
}

# コンテキストメニューアイテム作成(チェック可能)
function CreateCheckableMenuItem($header, $isChecked, $clickAction) {
    New-Object Windows.Controls.MenuItem -Property @{
        Header = $header
        IsCheckable = $true
        IsChecked = $isChecked
        Add_Click = $clickAction
    }
}

# ランチャー機能の読み込みと実行
function LoadLaunchItems {
    $launchFiles = Get-ChildItem -Path $paths.LaunchFolder
    foreach ($file in $launchFiles) {
        $menuItem = New-Object Windows.Controls.MenuItem -Property @{
            Header = [System.IO.Path]::GetFileNameWithoutExtension($file.Name)
            Tag = $file.FullName
        }
        $menuItem.Add_Click({
            param($source, $e)
            $filePath = $source.Tag
            if ($filePath) {
                Start-Process $filePath
            } else {
                Write-Host "ファイルパスが無効です: $filePath"
            }
        })
        $global:launchMenu.Items.Add($menuItem)
    }
}

# チャットウィンドウ作成
function CreateChatWindow {
    $chatWindow = New-Object Windows.Window -Property @{
        Title = "チャット"
        Width = 400
        Height = 300
        WindowStyle = "None"
        AllowsTransparency = $true
        Background = [Windows.Media.Brushes]::WhiteSmoke
        Topmost = $true
        ShowInTaskbar = $false
        ResizeMode = "CanResizeWithGrip"
    }

    # グリッド定義
    $grid = New-Object Windows.Controls.Grid
    $grid.RowDefinitions.Add((New-Object Windows.Controls.RowDefinition) -as [Windows.Controls.RowDefinition])
    $grid.RowDefinitions.Add((New-Object Windows.Controls.RowDefinition) -as [Windows.Controls.RowDefinition])
    $grid.RowDefinitions.Add((New-Object Windows.Controls.RowDefinition) -as [Windows.Controls.RowDefinition])
    $grid.RowDefinitions[0].Height = [Windows.GridLength]::Auto
    $grid.RowDefinitions[1].Height = [Windows.GridLength]::new(1, [Windows.GridUnitType]::Star)
    $grid.RowDefinitions[2].Height = [Windows.GridLength]::Auto

    # チャット表示用スクロールビューア
    $scrollViewer = New-Object Windows.Controls.ScrollViewer -Property @{
        VerticalScrollBarVisibility = "Auto"
        HorizontalScrollBarVisibility = "Disabled"
        Margin = [Windows.Thickness]::new(10,10,10,10)
    }
    [Windows.Controls.Grid]::SetRow($scrollViewer, 1)

    # チャット表示用スタックパネル
    $chatPanel = New-Object Windows.Controls.StackPanel
    $scrollViewer.Content = $chatPanel

    # ユーザー入力用テキストボックス
    $userInput = New-Object Windows.Controls.TextBox -Property @{
        Width = 260
        Height = 60
        TextWrapping = "Wrap"
        AcceptsReturn = $true
        VerticalAlignment = "Bottom"
        HorizontalAlignment = "Left"
    }
    [Windows.Controls.Grid]::SetRow($userInput, 2)

    # 送信ボタン
    $sendButton = New-Object Windows.Controls.Button -Property @{
        Content = "送信"
        Width = 75
        Margin = [Windows.Thickness]::new(280,10,10,10)
        VerticalAlignment = "Bottom"
        HorizontalAlignment = "Right"
    }
    [Windows.Controls.Grid]::SetRow($sendButton, 2)

    # 閉じるボタン
    $closeButton = New-Object Windows.Controls.Button -Property @{
        Content = "閉じる"
        Width = 75
        Margin = [Windows.Thickness]::new(10,10,10,10)
        VerticalAlignment = "Top"
        HorizontalAlignment = "Right"
    }
    [Windows.Controls.Grid]::SetRow($closeButton, 0)

    # モード切替ボタン
    $modeButton = New-Object Windows.Controls.Button -Property @{
        Content = "モード切替"
        Width = 75
        Margin = [Windows.Thickness]::new(10,10,10,10)
        VerticalAlignment = "Top"
        HorizontalAlignment = "Left"
    }
    [Windows.Controls.Grid]::SetRow($modeButton, 0)

    # メモのみチェックボックス
    $memoOnlyCheckBox = New-Object Windows.Controls.CheckBox -Property @{
        Content = "メモのみ"
        Margin = [Windows.Thickness]::new(95, 10, 10, 10)
        VerticalAlignment = "Top"
        HorizontalAlignment = "Left"
    }
    [Windows.Controls.Grid]::SetRow($memoOnlyCheckBox, 0)

    # モード切替メニュー
    $modeMenu = New-Object Windows.Controls.ContextMenu
    $modes = @("チャット", "日常", "仕事", "癒し", "格言", "動物豆知識")
    foreach ($m in $modes) {
        $modeMenuItem = New-Object Windows.Controls.MenuItem -Property @{
            Header = $m
            IsCheckable = $true
            IsChecked = ($m -eq $global:currentMode)
        }
        $modeMenuItem.Add_Click({
            param($source, $e)
            $global:currentMode = $source.Header
            # 設定を保存
            $settings.LastSelectedMode = $global:currentMode
            $settings | ConvertTo-Json | Set-Content -Path $paths.SettingsFile

            # 他のメニューアイテムのチェックを解除
            foreach ($item in $modeMenu.Items) {
                $item.IsChecked = $false
            }
            # クリックされたアイテムをチェック
            $source.IsChecked = $true
        })
        $modeMenu.Items.Add($modeMenuItem)
    }
    $modeButton.ContextMenu = $modeMenu
    $modeButton.Add_Click({
        $modeMenu.IsOpen = $true
    })

    # メッセージ送信処理
    function SendMessage {
        $userText = $userInput.Text
        if ($userText -ne "") {
            $timestamp = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")
            $userMessage = New-Object Windows.Controls.TextBlock -Property @{
                Text = "$userText ($timestamp)"
                HorizontalAlignment = "Right"
                Margin = [Windows.Thickness]::new(0, 0, 10, 5)
                TextWrapping = "Wrap"
            }
            $chatPanel.Children.Add($userMessage)
            
            if (-not $memoOnlyCheckBox.IsChecked) {
                Start-Sleep -Milliseconds 500  # 返信までの遅延を追加
                $response = GetMessage
                $botMessage = New-Object Windows.Controls.TextBlock -Property @{
                    Text = $response
                    HorizontalAlignment = "Left"
                    Margin = [Windows.Thickness]::new(10, 0, 0, 5)
                    TextWrapping = "Wrap"
                }
                $chatPanel.Children.Add($botMessage)

                # チャットログをファイルに保存
                $logDate = (Get-Date).ToString("yyyyMMdd")
                $chatFilePath = Join-Path -Path $paths.ChatFolder -ChildPath "$logDate.txt"
                Add-Content -Path $chatFilePath -Value "$userText ($timestamp)`n$response`n"
            } else {
                # チャットログをファイルに保存
                $logDate = (Get-Date).ToString("yyyyMMdd")
                $chatFilePath = Join-Path -Path $paths.ChatFolder -ChildPath "$logDate.txt"
                Add-Content -Path $chatFilePath -Value "$userText ($timestamp)`n"
            }

            # 自動スクロール
            $scrollViewer.ScrollToEnd()

            $userInput.Clear()
        }
    }

    # ボタンのクリックイベント
    $sendButton.Add_Click({ SendMessage })

    # 閉じるボタンのクリックイベント
    $closeButton.Add_Click({
        $chatWindow.Close()
    })

    # グリッドにコントロールを追加
    $grid.Children.Add($closeButton)
    $grid.Children.Add($modeButton)
    $grid.Children.Add($memoOnlyCheckBox)
    $grid.Children.Add($scrollViewer)
    $grid.Children.Add($userInput)
    $grid.Children.Add($sendButton)

    # チャットウィンドウにグリッドを設定
    $chatWindow.Content = $grid

    # ウィンドウをドラッグで移動できるようにする
    $chatWindow.Add_MouseLeftButtonDown({
        $chatWindow.DragMove()
    })

    # 当日のチャット履歴を読み込む
    $logDate = (Get-Date).ToString("yyyyMMdd")
    $chatFilePath = Join-Path -Path $paths.ChatFolder -ChildPath "$logDate.txt"
    if (Test-Path $chatFilePath) {
        $chatHistory = Get-Content -Path $chatFilePath
        foreach ($line in $chatHistory) {
            $message = New-Object Windows.Controls.TextBlock -Property @{
                Text = $line
                HorizontalAlignment = if ($line -match '\(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\)$') { "Right" } else { "Left" }
                TextWrapping = "Wrap"
            }
            $chatPanel.Children.Add($message)
        }
        $scrollViewer.ScrollToEnd()
    }

    $chatWindow.ShowDialog()
}

# マウスダウンイベント
$null = $window.Add_MouseDown({
    param($source, $e)
    if ($e.ChangedButton -eq [System.Windows.Input.MouseButton]::Left) {
        if ($global:floatingAboveTaskbar) {
            $window.DragMove()
        } else {
            MoveAndChangeMessage
        }
        $global:clickTime = [DateTime]::Now
        $global:clickCount++
        if ($global:clickCount -ge $maxClicks) {
            $window.Dispatcher.Invoke([action]{
                $textBlock.Text += " 触りすぎです!少し休憩しましょう。"
            })
            $global:clickCount = 0
        }
    } elseif ($e.ChangedButton -eq [System.Windows.Input.MouseButton]::Right) {
        $global:contextMenu = New-Object Windows.Controls.ContextMenu

        $global:contextMenu.Items.Add((CreateMenuItem "すべて最小化" { MinimizeAllWindows }))
        
        $fixMenuItem = New-Object Windows.Controls.MenuItem -Property @{ Header = "固定" }
        $fixMenuItem.Items.Add((CreateMenuItem "右下" { FixPosition "右下" }))
        $fixMenuItem.Items.Add((CreateMenuItem "右上" { FixPosition "右上" }))
        $fixMenuItem.Items.Add((CreateMenuItem "左上" { FixPosition "左上" }))
        $fixMenuItem.Items.Add((CreateMenuItem "左下" { FixPosition "左下" }))
        $fixMenuItem.Items.Add((CreateMenuItem "固定解除" { UnfixPosition }))
        $global:contextMenu.Items.Add($fixMenuItem)

        $imageSizeMenuItem = New-Object Windows.Controls.MenuItem -Property @{ Header = "画像サイズ" }
        $imageSizeMenuItem.Items.Add((CreateMenuItem "特特大" { SetImageSizeAndSave "特特大" }))
        $imageSizeMenuItem.Items.Add((CreateMenuItem "特大" { SetImageSizeAndSave "特大" }))
        $imageSizeMenuItem.Items.Add((CreateMenuItem "大" { SetImageSizeAndSave "大" }))
        $imageSizeMenuItem.Items.Add((CreateMenuItem "中" { SetImageSizeAndSave "中" }))
        $imageSizeMenuItem.Items.Add((CreateMenuItem "小" { SetImageSizeAndSave "小" }))
        $global:contextMenu.Items.Add($imageSizeMenuItem)

        $messageVisibilityMenuItem = New-Object Windows.Controls.MenuItem -Property @{ Header = "メッセージ表示" }
        $showMessageItem = New-Object Windows.Controls.MenuItem -Property @{ Header = "表示" }
        $showMessageItem.Items.Add((CreateMenuItem "大" { SetMessageSizeAndSave "大" }))
        $showMessageItem.Items.Add((CreateMenuItem "中" { SetMessageSizeAndSave "中" }))
        $showMessageItem.Items.Add((CreateMenuItem "小" { SetMessageSizeAndSave "小" }))
        $messageVisibilityMenuItem.Items.Add($showMessageItem)
        $messageVisibilityMenuItem.Items.Add((CreateMenuItem "非表示" { SetMessageVisibility $false }))
        $global:contextMenu.Items.Add($messageVisibilityMenuItem)

        $messageStyleMenuItem = New-Object Windows.Controls.MenuItem -Property @{ Header = "メッセージスタイル" }
        $messageStyleMenuItem.Items.Add((CreateMenuItem "透明" { SetMessageStyleAndSave "Transparent" }))
        $messageStyleMenuItem.Items.Add((CreateMenuItem "吹き出し" { SetMessageStyleAndSave "Bubble" }))
        $global:contextMenu.Items.Add($messageStyleMenuItem)

        $modeItem = New-Object Windows.Controls.MenuItem -Property @{ Header = "モード切替" }
        $modes = @("チャット", "日常", "仕事", "癒し", "格言", "動物豆知識")
        foreach ($m in $modes) {
            $modeSubItem = CreateCheckableMenuItem $m ($m -eq $global:currentMode) {
                param($source, $e)
                $global:currentMode = $source.Header
                $settings.LastSelectedMode = $global:currentMode
                $settings | ConvertTo-Json | Set-Content -Path $paths.SettingsFile
                foreach ($item in $modeItem.Items) { $item.IsChecked = $false }
                $source.IsChecked = $true
                MoveAndChangeMessage
            }
            $modeItem.Items.Add($modeSubItem)
        }
        $global:contextMenu.Items.Add($modeItem)

        $global:launchMenu = New-Object Windows.Controls.MenuItem -Property @{ Header = "ランチャー" }
        LoadLaunchItems
        $global:contextMenu.Items.Add($global:launchMenu)

        $global:contextMenu.Items.Add((CreateMenuItem "チャット" { 
            CreateChatWindow -chatFolderPath $paths.ChatFolder -settingsFilePath $paths.SettingsFile 
        }))

        $extensionMenuItem = New-Object Windows.Controls.MenuItem -Property @{ Header = "拡張" }
        $extensionMenuItem.Items.Add((CreateMenuItem "Google検索" { ShowGoogleSearchWindow }))
        $extensionMenuItem.Items.Add((CreateMenuItem "最前面に表示" { Set-WindowTopmost }))
        $extensionMenuItem.Items.Add((CreateMenuItem "圧縮・分割" { . $paths.CompressSplitScript }))
        $global:contextMenu.Items.Add($extensionMenuItem)

        # スキン変更メニュー
        $skinMenuItem = New-Object Windows.Controls.MenuItem -Property @{ Header = "スキン変更" }
        $skinFolders = Get-ChildItem -Path $paths.ImageFolder -Directory
        foreach ($folder in $skinFolders) {
            $skinSubItem = CreateCheckableMenuItem $folder.Name ($folder.Name -eq $global:skinFolder) {
                param($source, $e)
                $global:skinFolder = $source.Header
                SetSkinFolder $global:skinFolder
                foreach ($item in $skinMenuItem.Items) { $item.IsChecked = $false }
                $source.IsChecked = $true
            }
            $skinMenuItem.Items.Add($skinSubItem)
        }
        $global:contextMenu.Items.Add($skinMenuItem)

        # 浮動モード設定メニュー
        $global:contextMenu.Items.Add((CreateCheckableMenuItem "浮動モード" $global:floatingAboveTaskbar {
            $global:floatingAboveTaskbar = -not $global:floatingAboveTaskbar
            SetFloatingAboveTaskbar $global:floatingAboveTaskbar
        }))

        $global:contextMenu.Items.Add((CreateMenuItem "終了" {
            $moveTimer.Stop()
            $restTimer.Stop()
            $floatingTimer.Stop()
            $window.Close()
        }))

        $global:contextMenu.PlacementTarget = $window
        $global:contextMenu.IsOpen = $true
    }
})

if ($global:floatingAboveTaskbar) {
    $floatingTimer.Start()
}

$window.ShowDialog()

この記事が気に入ったらサポートをしてみませんか?