Здесь показаны различия между двумя версиями данной страницы.
| — |
truenas:application:strata_tray [2026/11/12 17:41] (текущий) werwolf Strata Tray; кванты: что можно на текущем ПК и после апгрейда |
||
|---|---|---|---|
| Строка 1: | Строка 1: | ||
| + | ====== Strata Tray: значок в трее для запуска и остановки Strata (Windows) ====== | ||
| + | |||
| + | ===== Введение ===== | ||
| + | |||
| + | **Strata Tray** — маленькая программа для Windows, которая висит в трее (возле часов) и: | ||
| + | * показывает статус локального сервера Strata цветом значка; | ||
| + | * по **правому клику** даёт меню **Запустить / Остановить**; | ||
| + | * запускается вместе с Windows (модель при этом **не** запускается — только значок); | ||
| + | * показывает уведомления: «Strata запущена», «остановлена», «не запустилась» с последними строками журнала, а также если сервер неожиданно упал. | ||
| + | |||
| + | Написана на PowerShell + WinForms: ничего доустанавливать не нужно. Работает поверх пресета Strata из инструкции [[:truenas:application:strata_qwen_goose|Qwen3.8-Flash-Next на Strata + Goose Desktop]]. | ||
| + | |||
| + | ===== Требования ===== | ||
| + | |||
| + | * Windows 10/11, Windows PowerShell 5.1 (есть в системе). | ||
| + | * Установленная Strata (''%LOCALAPPDATA%\Strata\terminal\current\bin\strata.cmd''). | ||
| + | * Сохранённый пресет Strata — по умолчанию ''Goose IQ3_S official 8081'' (порт 8081). | ||
| + | |||
| + | ===== Как это выглядит ===== | ||
| + | |||
| + | ^ Значок ^ Состояние ^ | ||
| + | | зелёный «S» | Strata работает, модель загружена | | ||
| + | | жёлтый «S» | запускается / загружает модель / останавливается | | ||
| + | | серый «S» | остановлена | | ||
| + | |||
| + | Подсказка при наведении: ''Strata: работает · порт 8081''. Двойной клик — уведомление с моделью, адресом API и размером контекста. | ||
| + | |||
| + | Меню по правому клику: | ||
| + | |||
| + | <code> | ||
| + | Strata: работает (статус) | ||
| + | qwen3.8-flash-next-iq3_s · http://127.0.0.1:8081 | ||
| + | ─────────────── | ||
| + | Запустить (активно, когда Strata остановлена) | ||
| + | Остановить (активно, когда Strata работает) | ||
| + | ─────────────── | ||
| + | Монитор Strata (strata monitor в окне консоли) | ||
| + | Журнал запуска/остановки (actions.log в Блокноте) | ||
| + | Папка логов движка | ||
| + | ─────────────── | ||
| + | ✓ Запускать вместе с Windows (включить/выключить автозапуск) | ||
| + | Выход (Strata продолжит работать) | ||
| + | </code> | ||
| + | |||
| + | ===== Как работает ===== | ||
| + | |||
| + | * Каждые 5 секунд запрашивает ''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 без окна консоли. | ||
| + | * Запущена только одна копия (повторный запуск ничего не делает). | ||
| + | * **Выход** закрывает только значок, сама Strata продолжает работать. | ||
| + | |||
| + | ===== Шаг 1: Файлы программы ===== | ||
| + | |||
| + | Создайте папку, например ''F:\projects\inst\strata-tray'', и сохраните в неё четыре файла ниже (у каждого блока кода есть ссылка для скачивания — имя файла над блоком) плюс ''settings.json''. | ||
| + | |||
| + | **Важно:** Windows PowerShell 5.1 читает ''.ps1'' без BOM в кодировке ANSI, и русские строки превращаются в кракозябры. После скачивания пересохраните ''.ps1'' в UTF-8 **с BOM** (в Блокноте: «Сохранить как» → кодировка «UTF-8 с BOM») или выполните в папке с файлами: | ||
| + | |||
| + | <code powershell> | ||
| + | Get-ChildItem *.ps1 | ForEach-Object { $t = [IO.File]::ReadAllText($_.FullName, [Text.Encoding]::UTF8); [IO.File]::WriteAllText($_.FullName, $t, (New-Object Text.UTF8Encoding($true))) } | ||
| + | </code> | ||
| + | |||
| + | ==== settings.json — настройки ==== | ||
| + | |||
| + | <code json settings.json> | ||
| + | { | ||
| + | "Preset": "Goose IQ3_S official 8081", | ||
| + | "Port": 8081, | ||
| + | "PollSeconds": 5 | ||
| + | } | ||
| + | </code> | ||
| + | |||
| + | * ''Preset'' — имя пресета Strata (список: ''strata launch --list-presets''). | ||
| + | * ''Port'' — порт API этого пресета. | ||
| + | * ''PollSeconds'' — как часто проверять статус. | ||
| + | |||
| + | После установки настройки лежат в ''%LOCALAPPDATA%\StrataTray\app\settings.json''; после правки — «Выход» в меню значка и запустить снова (или перезагрузить ПК). | ||
| + | |||
| + | ==== StrataTray.ps1 — основная программа ==== | ||
| + | |||
| + | <code powershell StrataTray.ps1> | ||
| + | # 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() | ||
| + | </code> | ||
| + | |||
| + | ==== StrataTray.vbs — запуск без окна консоли ==== | ||
| + | |||
| + | <code vb StrataTray.vbs> | ||
| + | ' 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 | ||
| + | </code> | ||
| + | |||
| + | ==== install.ps1 — установка и автозапуск ==== | ||
| + | |||
| + | <code powershell install.ps1> | ||
| + | # Установка 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 появится в трее (может быть скрыт под стрелкой ^).' | ||
| + | </code> | ||
| + | |||
| + | ==== uninstall.ps1 — удаление ==== | ||
| + | |||
| + | <code powershell uninstall.ps1> | ||
| + | # Удаление 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 не остановлена и не удалена.' | ||
| + | </code> | ||
| + | |||
| + | |||
| + | ===== Шаг 2: Установка ===== | ||
| + | |||
| + | В PowerShell, в папке с файлами: | ||
| + | |||
| + | <code powershell> | ||
| + | powershell -NoProfile -ExecutionPolicy Bypass -File .\install.ps1 | ||
| + | </code> | ||
| + | |||
| + | <code> | ||
| + | Установлено в C:\Users\<пользователь>\AppData\Local\StrataTray\app | ||
| + | Автозапуск: C:\Users\<пользователь>\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup\Strata Tray.lnk | ||
| + | Значок S появится в трее (может быть скрыт под стрелкой ^). | ||
| + | </code> | ||
| + | |||
| + | Установщик: | ||
| + | - останавливает уже запущенный значок (если был); | ||
| + | - копирует ''StrataTray.ps1'' и ''StrataTray.vbs'' в ''%LOCALAPPDATA%\StrataTray\app'' (''settings.json'' — только если его там ещё нет, ваши настройки не затираются); | ||
| + | - создаёт ярлык автозапуска; | ||
| + | - запускает значок. | ||
| + | |||
| + | Чтобы значок всегда был виден, а не прятался под стрелкой: Параметры → Персонализация → Панель задач → Другие значки в области уведомлений → включить ''Windows PowerShell'' (значок запускается от имени PowerShell). | ||
| + | |||
| + | ===== Шаг 3: Проверка без интерфейса ===== | ||
| + | |||
| + | Режим самопроверки печатает статус в консоль (Strata не трогается): | ||
| + | |||
| + | <code powershell> | ||
| + | powershell -NoProfile -ExecutionPolicy Bypass -File "$env:LOCALAPPDATA\StrataTray\app\StrataTray.ps1" -SelfTest | ||
| + | </code> | ||
| + | |||
| + | <code> | ||
| + | Статус: 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) | ||
| + | </code> | ||
| + | |||
| + | С ключом ''-Cycle'' программа **останавливает и снова запускает Strata** теми же командами, что и меню (проверено: остановка — код 0, запуск — код 0, модель ответила через 66 с): | ||
| + | |||
| + | <code powershell> | ||
| + | powershell -NoProfile -ExecutionPolicy Bypass -File "$env:LOCALAPPDATA\StrataTray\app\StrataTray.ps1" -SelfTest -Cycle | ||
| + | </code> | ||
| + | |||
| + | ===== Удаление ===== | ||
| + | |||
| + | <code powershell> | ||
| + | powershell -NoProfile -ExecutionPolicy Bypass -File .\uninstall.ps1 | ||
| + | </code> | ||
| + | |||
| + | Удаляет ярлык автозапуска и папку ''%LOCALAPPDATA%\StrataTray''. Strata и модель не затрагиваются. | ||
| + | |||
| + | ===== Проблемы и решения ===== | ||
| + | |||
| + | ==== Значок не появился ==== | ||
| + | |||
| + | * Посмотрите под стрелкой ''^'' в трее. | ||
| + | * Проверьте, что процесс запущен:<code powershell> | ||
| + | Get-CimInstance Win32_Process -Filter "Name='powershell.exe'" | Where-Object { $_.CommandLine -like '*StrataTray.ps1*' } | Select-Object ProcessId | ||
| + | </code> | ||
| + | * Запустите вручную с окном, чтобы увидеть ошибку:<code powershell> | ||
| + | powershell -NoProfile -ExecutionPolicy Bypass -STA -File "$env:LOCALAPPDATA\StrataTray\app\StrataTray.ps1" | ||
| + | </code> | ||
| + | |||
| + | ==== Русские надписи — кракозябры ==== | ||
| + | |||
| + | Файл ''.ps1'' сохранён без BOM — см. «Шаг 1», пересохраните в UTF-8 с BOM. | ||
| + | |||
| + | ==== «Strata не запустилась» ==== | ||
| + | |||
| + | Откройте «Журнал запуска/остановки» в меню — там полный вывод ''strata launch''. Частые причины: порт занят другим процессом, не хватает свободной RAM (закройте тяжёлые программы), неверное имя пресета в ''settings.json''. Подробнее — раздел «Проблемы и решения» на странице [[:truenas:application:strata_qwen_goose|Strata + Goose]]. | ||
| + | |||
| + | ==== Сменил модель / пресет ==== | ||
| + | |||
| + | Поменяйте ''Preset'' и ''Port'' в ''%LOCALAPPDATA%\StrataTray\app\settings.json'', затем «Выход» в меню значка и запустите заново: | ||
| + | |||
| + | <code powershell> | ||
| + | wscript.exe "$env:LOCALAPPDATA\StrataTray\app\StrataTray.vbs" | ||
| + | </code> | ||
| + | |||
| + | ===== Шпаргалка ===== | ||
| + | |||
| + | ^ Что ^ Где ^ | ||
| + | | Программа | ''%LOCALAPPDATA%\StrataTray\app\'' | | ||
| + | | Настройки | ''%LOCALAPPDATA%\StrataTray\app\settings.json'' | | ||
| + | | Журнал запуска/остановки | ''%LOCALAPPDATA%\StrataTray\actions.log'' | | ||
| + | | Автозапуск | ''shell:startup\Strata Tray.lnk'' | | ||
| + | | Исходники | ''F:\projects\inst\strata-tray\'' | | ||