Strata Tray — маленькая программа для Windows, которая висит в трее (возле часов) и:
Написана на PowerShell + WinForms: ничего доустанавливать не нужно. Работает поверх пресета Strata из инструкции Qwen3.8-Flash-Next на Strata + Goose Desktop.
%LOCALAPPDATA%\Strata\terminal\current\bin\strata.cmd).Goose IQ3_S official 8081 (порт 8081).| Значок | Состояние |
|---|---|
| зелёный «S» | Strata работает, модель загружена |
| жёлтый «S» | запускается / загружает модель / останавливается |
| серый «S» | остановлена |
Подсказка при наведении: Strata: работает · порт 8081. Двойной клик — уведомление с моделью, адресом API и размером контекста.
Меню по правому клику:
Strata: работает (статус) qwen3.8-flash-next-iq3_s · http://127.0.0.1:8081 ─────────────── Запустить (активно, когда Strata остановлена) Остановить (активно, когда Strata работает) ─────────────── Монитор Strata (strata monitor в окне консоли) Журнал запуска/остановки (actions.log в Блокноте) Папка логов движка ─────────────── ✓ Запускать вместе с Windows (включить/выключить автозапуск) Выход (Strata продолжит работать)
http://127.0.0.1:8081/v1/models. Ответ со статусом loaded — «работает»; сервер не отвечает — «остановлена». Ответ 401/403 (включён API-ключ) тоже считается «работает».strata launch –preset «Goose IQ3_S official 8081» –no-tui (загрузка ~1 минута).strata launch –stop –preset «Goose IQ3_S official 8081» — освобождает VRAM и ~50 GB RAM.%LOCALAPPDATA%\StrataTray\actions.log.Strata Tray.lnk в папке «Автозагрузка» (shell:startup), он запускает wscript.exe StrataTray.vbs, а тот — PowerShell без окна консоли.
Создайте папку, например F:\projects\inst\strata-tray, и сохраните в неё четыре файла ниже (у каждого блока кода есть ссылка для скачивания — имя файла над блоком) плюс settings.json.
Важно: Windows PowerShell 5.1 читает .ps1 без BOM в кодировке ANSI, и русские строки превращаются в кракозябры. После скачивания пересохраните .ps1 в UTF-8 с BOM (в Блокноте: «Сохранить как» → кодировка «UTF-8 с BOM») или выполните в папке с файлами:
Get-ChildItem *.ps1 | ForEach-Object { $t = [IO.File]::ReadAllText($_.FullName, [Text.Encoding]::UTF8); [IO.File]::WriteAllText($_.FullName, $t, (New-Object Text.UTF8Encoding($true))) }
{
"Preset": "Goose IQ3_S official 8081",
"Port": 8081,
"PollSeconds": 5
}
Preset — имя пресета Strata (список: strata launch –list-presets).Port — порт API этого пресета.PollSeconds — как часто проверять статус.
После установки настройки лежат в %LOCALAPPDATA%\StrataTray\app\settings.json; после правки — «Выход» в меню значка и запустить снова (или перезагрузить ПК).
# Strata Tray: значок в трее, который показывает статус Strata и запускает/останавливает её. # Запуск без окна: StrataTray.vbs. Проверка без интерфейса: StrataTray.ps1 -SelfTest [-Cycle] param( [switch]$SelfTest, # вывести статус в консоль и выйти [switch]$Cycle # вместе с -SelfTest: остановить и снова запустить Strata теми же командами, что и меню ) Add-Type -AssemblyName System.Windows.Forms Add-Type -AssemblyName System.Drawing # ---------- настройки ---------- $AppDir = Split-Path -Parent $MyInvocation.MyCommand.Path $Cfg = @{ Preset = 'Goose IQ3_S official 8081'; Port = 8081; PollSeconds = 5 } $CfgPath = Join-Path $AppDir 'settings.json' if (Test-Path $CfgPath) { try { $j = Get-Content $CfgPath -Raw -Encoding UTF8 | ConvertFrom-Json foreach ($p in $j.PSObject.Properties) { $Cfg[$p.Name] = $p.Value } } catch { } } $Strata = Join-Path $env:LOCALAPPDATA 'Strata\terminal\current\bin\strata.cmd' $BaseUrl = "http://127.0.0.1:$($Cfg.Port)" $DataDir = Join-Path $env:LOCALAPPDATA 'StrataTray' $ActionLog = Join-Path $DataDir 'actions.log' $EngineLogDir = Join-Path $env:LOCALAPPDATA 'Strata\launch' $StartupLnk = Join-Path ([Environment]::GetFolderPath('Startup')) 'Strata Tray.lnk' New-Item -ItemType Directory -Force $DataDir | Out-Null # ---------- состояние ---------- $script:Proc = $null # процесс strata launch / --stop, который сейчас выполняется $script:Pending = $null # 'start' | 'stop' | $null $script:LastState = $null # 'running' | 'loading' | 'stopped' | 'starting' | 'stopping' $script:LastStatus = @{ Up = $false } function Write-ActionLog([string]$text) { try { Add-Content -Path $ActionLog -Value $text -Encoding UTF8 } catch { } } function Get-StrataStatus { try { $req = [System.Net.HttpWebRequest]::Create("$BaseUrl/v1/models") $req.Timeout = 2000; $req.ReadWriteTimeout = 2000; $req.Proxy = $null $resp = $req.GetResponse() try { $body = (New-Object IO.StreamReader($resp.GetResponseStream())).ReadToEnd() } finally { $resp.Close() } $m = ($body | ConvertFrom-Json).data | Select-Object -First 1 return @{ Up = $true; Model = $m.id; Loaded = ($m.status.value -eq 'loaded'); Ctx = $m.meta.n_ctx } } catch { $e = $_.Exception while ($e -and -not ($e -is [System.Net.WebException])) { $e = $e.InnerException } if ($e -and $e.Response) { # сервер ответил ошибкой HTTP (например 401 при включённом API-ключе) — значит он работает return @{ Up = $true; Model = '?'; Loaded = $true; Ctx = $null } } return @{ Up = $false } } } function Start-StrataCommand([string]$argLine, [string]$kind) { if (-not (Test-Path $Strata)) { return "Не найден $Strata — Strata не установлена?" } Write-ActionLog "`r`n==== $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') ${kind}: strata $argLine" $psi = New-Object System.Diagnostics.ProcessStartInfo $psi.FileName = $env:ComSpec $psi.Arguments = '/d /c ""' + $Strata + '" ' + $argLine + ' >> "' + $ActionLog + '" 2>&1"' $psi.UseShellExecute = $false $psi.CreateNoWindow = $true $script:Proc = [System.Diagnostics.Process]::Start($psi) $script:Pending = $kind return $null } function Start-Strata { Start-StrataCommand ('launch --preset "' + $Cfg.Preset + '" --no-tui') 'start' } function Stop-Strata { Start-StrataCommand ('launch --stop --preset "' + $Cfg.Preset + '"') 'stop' } function Get-LogTail([int]$n = 4) { try { $lines = Get-Content $ActionLog -Tail 40 -Encoding UTF8 | Where-Object { $_.Trim() } return (($lines | Select-Object -Last $n) -join "`n") } catch { return '' } } function Set-Autostart([bool]$on) { if ($on) { $sh = New-Object -ComObject WScript.Shell $l = $sh.CreateShortcut($StartupLnk) $l.TargetPath = Join-Path $env:WINDIR 'System32\wscript.exe' $l.Arguments = '"' + (Join-Path $AppDir 'StrataTray.vbs') + '"' $l.WorkingDirectory = $AppDir $l.Description = 'Strata Tray — статус и запуск Strata' $l.Save() } elseif (Test-Path $StartupLnk) { Remove-Item $StartupLnk -Force } } # ---------- режим проверки без интерфейса ---------- if ($SelfTest) { function Wait-Proc { while ($script:Proc -and -not $script:Proc.HasExited) { Start-Sleep -Milliseconds 500 }; " код выхода: $($script:Proc.ExitCode)" } $s = Get-StrataStatus "Статус: Up=$($s.Up) Model=$($s.Model) Loaded=$($s.Loaded) Ctx=$($s.Ctx) ($BaseUrl, пресет '$($Cfg.Preset)')" "Автозапуск: $(Test-Path $StartupLnk) ($StartupLnk)" if ($Cycle) { "Остановка..."; $err = Stop-Strata; if ($err) { $err; exit 1 }; Wait-Proc $s = Get-StrataStatus; " после остановки: Up=$($s.Up)" "Запуск..."; $t0 = Get-Date; $err = Start-Strata; if ($err) { $err; exit 1 }; Wait-Proc $s = Get-StrataStatus; " после запуска: Up=$($s.Up) Loaded=$($s.Loaded) за $([int]((Get-Date) - $t0).TotalSeconds) с" "Хвост журнала:"; Get-LogTail 6 } exit 0 } # ---------- один экземпляр ---------- $created = $false $script:Mutex = New-Object System.Threading.Mutex($true, 'Local\StrataTray', [ref]$created) if (-not $created) { exit 0 } # ---------- значки ---------- function New-StatusIcon([System.Drawing.Color]$color) { $size = 32 $bmp = New-Object System.Drawing.Bitmap($size, $size) $g = [System.Drawing.Graphics]::FromImage($bmp) $g.SmoothingMode = 'AntiAlias' $g.TextRenderingHint = 'AntiAliasGridFit' $g.FillEllipse((New-Object System.Drawing.SolidBrush($color)), 1, 1, $size - 2, $size - 2) $g.DrawEllipse((New-Object System.Drawing.Pen([System.Drawing.Color]::FromArgb(90, 0, 0, 0), 1.5)), 1, 1, $size - 3, $size - 3) $font = New-Object System.Drawing.Font('Segoe UI', 17, [System.Drawing.FontStyle]::Bold, [System.Drawing.GraphicsUnit]::Pixel) $fmt = New-Object System.Drawing.StringFormat $fmt.Alignment = 'Center'; $fmt.LineAlignment = 'Center' $g.DrawString('S', $font, [System.Drawing.Brushes]::White, (New-Object System.Drawing.RectangleF(0, 1, $size, $size)), $fmt) $g.Dispose() return [System.Drawing.Icon]::FromHandle($bmp.GetHicon()) } $Icons = @{ running = New-StatusIcon ([System.Drawing.Color]::FromArgb(46, 160, 67)) # зелёный starting = New-StatusIcon ([System.Drawing.Color]::FromArgb(212, 160, 23)) # жёлтый loading = New-StatusIcon ([System.Drawing.Color]::FromArgb(212, 160, 23)) stopping = New-StatusIcon ([System.Drawing.Color]::FromArgb(212, 160, 23)) stopped = New-StatusIcon ([System.Drawing.Color]::FromArgb(128, 128, 128)) # серый } $StateText = @{ running = 'работает' loading = 'загружает модель' starting = 'запускается…' stopping = 'останавливается…' stopped = 'остановлена' } # ---------- значок и меню ---------- $Notify = New-Object System.Windows.Forms.NotifyIcon $Notify.Icon = $Icons.stopped $Notify.Text = 'Strata: проверка…' $Notify.Visible = $true $Menu = New-Object System.Windows.Forms.ContextMenuStrip $miStatus = $Menu.Items.Add('Strata: проверка…'); $miStatus.Enabled = $false $miModel = $Menu.Items.Add(''); $miModel.Enabled = $false [void]$Menu.Items.Add('-') $miStart = $Menu.Items.Add('Запустить') $miStop = $Menu.Items.Add('Остановить') [void]$Menu.Items.Add('-') $miMonitor = $Menu.Items.Add('Монитор Strata') $miLog = $Menu.Items.Add('Журнал запуска/остановки') $miEngine = $Menu.Items.Add('Папка логов движка') [void]$Menu.Items.Add('-') $miAuto = New-Object System.Windows.Forms.ToolStripMenuItem('Запускать вместе с Windows') $miAuto.CheckOnClick = $true $miAuto.Checked = (Test-Path $StartupLnk) [void]$Menu.Items.Add($miAuto) $miExit = $Menu.Items.Add('Выход (Strata продолжит работать)') $miStart.Font = New-Object System.Drawing.Font($miStart.Font, [System.Drawing.FontStyle]::Bold) $Notify.ContextMenuStrip = $Menu function Show-Balloon([string]$title, [string]$text, [string]$kind = 'Info') { if (-not $text) { $text = ' ' } $Notify.ShowBalloonTip(5000, $title, $text, [System.Windows.Forms.ToolTipIcon]::$kind) } function Update-Ui { try { $s = Get-StrataStatus $script:LastStatus = $s # завершилась команда запуска/остановки if ($script:Pending -and $script:Proc -and $script:Proc.HasExited) { $code = $script:Proc.ExitCode if ($script:Pending -eq 'start') { if ($s.Up) { Show-Balloon 'Strata запущена' "$($s.Model) — $BaseUrl" } else { Show-Balloon "Strata не запустилась (код $code)" (Get-LogTail 3) 'Error' } } else { if (-not $s.Up) { Show-Balloon 'Strata остановлена' 'Видеопамять и RAM освобождены' } else { Show-Balloon "Strata не остановилась (код $code)" (Get-LogTail 3) 'Warning' } } $script:Pending = $null $script:Proc = $null } $state = if ($script:Pending -eq 'start') { 'starting' } elseif ($script:Pending -eq 'stop') { 'stopping' } elseif ($s.Up -and $s.Loaded) { 'running' } elseif ($s.Up) { 'loading' } else { 'stopped' } # неожиданная остановка (упала или остановлена из консоли) if ($script:LastState -eq 'running' -and $state -eq 'stopped') { Show-Balloon 'Strata остановилась' 'Сервер больше не отвечает' 'Warning' } $script:LastState = $state $Notify.Icon = $Icons[$state] $tip = "Strata: $($StateText[$state])" if ($s.Up -and $s.Model) { $tip += " · порт $($Cfg.Port)" } $Notify.Text = if ($tip.Length -gt 63) { $tip.Substring(0, 63) } else { $tip } $miStatus.Text = "Strata: $($StateText[$state])" $miModel.Text = if ($s.Up) { "$($s.Model) · $BaseUrl" } else { "пресет: $($Cfg.Preset)" } $busy = [bool]$script:Pending $miStart.Enabled = (-not $busy) -and (-not $s.Up) $miStop.Enabled = (-not $busy) -and $s.Up $miAuto.Checked = (Test-Path $StartupLnk) } catch { Write-ActionLog "[tray] ошибка обновления: $($_.Exception.Message)" } } $miStart.add_Click({ $err = Start-Strata if ($err) { Show-Balloon 'Strata' $err 'Error' } else { Show-Balloon 'Strata' 'Запуск… загрузка модели занимает около минуты' } Update-Ui }) $miStop.add_Click({ $err = Stop-Strata if ($err) { Show-Balloon 'Strata' $err 'Error' } Update-Ui }) $miMonitor.add_Click({ if (Test-Path $Strata) { Start-Process -FilePath $env:ComSpec -ArgumentList ('/k ""' + $Strata + '" monitor --url ' + $BaseUrl + '"') } }) $miLog.add_Click({ if (-not (Test-Path $ActionLog)) { Write-ActionLog '' } Start-Process notepad.exe -ArgumentList ('"' + $ActionLog + '"') }) $miEngine.add_Click({ if (Test-Path $EngineLogDir) { Start-Process explorer.exe -ArgumentList ('"' + $EngineLogDir + '"') } }) $miAuto.add_Click({ try { Set-Autostart $miAuto.Checked } catch { Show-Balloon 'Strata Tray' "Не удалось изменить автозапуск: $($_.Exception.Message)" 'Error' } $miAuto.Checked = (Test-Path $StartupLnk) }) $miExit.add_Click({ $Timer.Stop() $Notify.Visible = $false $Notify.Dispose() [System.Windows.Forms.Application]::Exit() }) $Notify.add_DoubleClick({ $s = $script:LastStatus if ($s.Up) { Show-Balloon "Strata: $($StateText[$script:LastState])" "$($s.Model)`n$BaseUrl/v1 · контекст $($s.Ctx)" } else { Show-Balloon "Strata: $($StateText[$script:LastState])" "Пресет: $($Cfg.Preset)`nПравый клик → Запустить" } }) $Menu.add_Opening({ Update-Ui }) $Timer = New-Object System.Windows.Forms.Timer $Timer.Interval = [int]$Cfg.PollSeconds * 1000 $Timer.add_Tick({ Update-Ui }) $Timer.Start() Update-Ui [System.Windows.Forms.Application]::Run() $script:Mutex.ReleaseMutex()
' Runs StrataTray.ps1 without a console window (used by the autostart shortcut). Set fso = CreateObject("Scripting.FileSystemObject") dir = fso.GetParentFolderName(WScript.ScriptFullName) CreateObject("WScript.Shell").Run "powershell.exe -NoProfile -ExecutionPolicy Bypass -STA -WindowStyle Hidden -File """ & dir & "\StrataTray.ps1""", 0, False
# Установка Strata Tray: копирует программу в %LOCALAPPDATA%\StrataTray\app, включает автозапуск и запускает значок. # Запуск: powershell -ExecutionPolicy Bypass -File install.ps1 $ErrorActionPreference = 'Stop' $src = Split-Path -Parent $MyInvocation.MyCommand.Path $dst = Join-Path $env:LOCALAPPDATA 'StrataTray\app' $lnk = Join-Path ([Environment]::GetFolderPath('Startup')) 'Strata Tray.lnk' # остановить уже запущенный значок Get-CimInstance Win32_Process -Filter "Name='powershell.exe'" | Where-Object { $_.CommandLine -like '*StrataTray.ps1*' } | ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue } New-Item -ItemType Directory -Force $dst | Out-Null Copy-Item (Join-Path $src 'StrataTray.ps1'), (Join-Path $src 'StrataTray.vbs') $dst -Force if (-not (Test-Path (Join-Path $dst 'settings.json'))) { Copy-Item (Join-Path $src 'settings.json') $dst } $sh = New-Object -ComObject WScript.Shell $l = $sh.CreateShortcut($lnk) $l.TargetPath = Join-Path $env:WINDIR 'System32\wscript.exe' $l.Arguments = '"' + (Join-Path $dst 'StrataTray.vbs') + '"' $l.WorkingDirectory = $dst $l.Description = 'Strata Tray — статус и запуск Strata' $l.Save() Start-Process (Join-Path $env:WINDIR 'System32\wscript.exe') -ArgumentList ('"' + (Join-Path $dst 'StrataTray.vbs') + '"') Write-Host "Установлено в $dst" Write-Host "Автозапуск: $lnk" Write-Host 'Значок S появится в трее (может быть скрыт под стрелкой ^).'
# Удаление Strata Tray (сама Strata и модель не затрагиваются). $app = Join-Path $env:LOCALAPPDATA 'StrataTray' $lnk = Join-Path ([Environment]::GetFolderPath('Startup')) 'Strata Tray.lnk' Get-CimInstance Win32_Process -Filter "Name='powershell.exe'" | Where-Object { $_.CommandLine -like '*StrataTray.ps1*' } | ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue } if (Test-Path $lnk) { Remove-Item $lnk -Force } if (Test-Path $app) { Remove-Item $app -Recurse -Force } Write-Host 'Strata Tray удалён. Strata не остановлена и не удалена.'
В PowerShell, в папке с файлами:
powershell -NoProfile -ExecutionPolicy Bypass -File .\install.ps1
Установлено в C:\Users\<пользователь>\AppData\Local\StrataTray\app Автозапуск: C:\Users\<пользователь>\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup\Strata Tray.lnk Значок S появится в трее (может быть скрыт под стрелкой ^).
Установщик:
StrataTray.ps1 и StrataTray.vbs в %LOCALAPPDATA%\StrataTray\app (settings.json — только если его там ещё нет, ваши настройки не затираются);
Чтобы значок всегда был виден, а не прятался под стрелкой: Параметры → Персонализация → Панель задач → Другие значки в области уведомлений → включить Windows PowerShell (значок запускается от имени PowerShell).
Режим самопроверки печатает статус в консоль (Strata не трогается):
powershell -NoProfile -ExecutionPolicy Bypass -File "$env:LOCALAPPDATA\StrataTray\app\StrataTray.ps1" -SelfTest
Статус: Up=True Model=qwen3.8-flash-next-iq3_s Loaded=True Ctx=131072 (http://127.0.0.1:8081, пресет 'Goose IQ3_S official 8081') Автозапуск: True (C:\Users\...\Startup\Strata Tray.lnk)
С ключом -Cycle программа останавливает и снова запускает Strata теми же командами, что и меню (проверено: остановка — код 0, запуск — код 0, модель ответила через 66 с):
powershell -NoProfile -ExecutionPolicy Bypass -File "$env:LOCALAPPDATA\StrataTray\app\StrataTray.ps1" -SelfTest -Cycle
powershell -NoProfile -ExecutionPolicy Bypass -File .\uninstall.ps1
Удаляет ярлык автозапуска и папку %LOCALAPPDATA%\StrataTray. Strata и модель не затрагиваются.
^ в трее.Get-CimInstance Win32_Process -Filter "Name='powershell.exe'" | Where-Object { $_.CommandLine -like '*StrataTray.ps1*' } | Select-Object ProcessId
powershell -NoProfile -ExecutionPolicy Bypass -STA -File "$env:LOCALAPPDATA\StrataTray\app\StrataTray.ps1"
Файл .ps1 сохранён без BOM — см. «Шаг 1», пересохраните в UTF-8 с BOM.
Откройте «Журнал запуска/остановки» в меню — там полный вывод strata launch. Частые причины: порт занят другим процессом, не хватает свободной RAM (закройте тяжёлые программы), неверное имя пресета в settings.json. Подробнее — раздел «Проблемы и решения» на странице Strata + Goose.
Поменяйте Preset и Port в %LOCALAPPDATA%\StrataTray\app\settings.json, затем «Выход» в меню значка и запустите заново:
wscript.exe "$env:LOCALAPPDATA\StrataTray\app\StrataTray.vbs"
| Что | Где |
|---|---|
| Программа | %LOCALAPPDATA%\StrataTray\app\ |
| Настройки | %LOCALAPPDATA%\StrataTray\app\settings.json |
| Журнал запуска/остановки | %LOCALAPPDATA%\StrataTray\actions.log |
| Автозапуск | shell:startup\Strata Tray.lnk |
| Исходники | F:\projects\inst\strata-tray\ |