生成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に読み込んでもらい編集性もらってもいいですが
スクリプトディレクトリにある `messages.json` ファイルをメモ帳などで開きます。
モードと時間帯ごとにメッセージが区切られているのでメッセージを追加または編集します。ファイルは以下のような構造になっています。
{
"チャット": {
"morning": ["おはようございます!", "良い一日を!"],
"lateMorning": ["こんにちは!", "元気ですか?"],
"noon": ["お昼ですね!", "休憩しましょう!"],
"earlyAfternoon": ["午後も頑張りましょう!", "調子はどうですか?"],
"evening": ["こんばんは!", "お疲れ様です!"],
"night": ["おやすみなさい!", "今日も一日お疲れ様でした!"]
}
}
3.変更を保存し、スクリプトを再実行します。
スキンの変更方法
好きな画像に変更できます。切り替えも可能です。
1枚だけ入れるとそれだけに固定ができます。
`img` フォルダ内に新しいフォルダを作成し、使用したい画像ファイル(PNG形式)をその中に保存します。
スクリプト実行中に右クリックメニューから「スキン変更」を選択し、新しいスキンフォルダを選択します。
スクリプトが新しいスキンを読み込み、マスコットの見た目が変更されます。
拡張スクリプトについて
このデスクトップマスコットは、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()