Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ This gives JIN observable short-term memory and behavior adaptation without intr
|-- config.example.py # Runtime configuration template
|-- config_loader.py # Local config module loader
|-- app_settings.py # Typed settings wrapper
|-- launch_jin.bat # Windows one-click launcher
|-- launch_jin.ps1 # LM Studio readiness check and startup script
|-- package.json # Local command shortcuts
|-- requirements.txt # Pinned Python dependencies
|-- saved_runtime.example.txt # Template for persisted L3 session memory
Expand All @@ -112,6 +114,45 @@ This gives JIN observable short-term memory and behavior adaptation without intr
- `POST /v1/chat/completions`
- `GET /v1/models`

## Windows One-Click Launcher

Windows users can start JIN with LM Studio through:

```text
launch_jin.bat
```

The launcher uses LM Studio as the default provider and checks the OpenAI-compatible API at:

```text
http://localhost:1234/v1/models
```

Before running it:

- Install and open LM Studio.
- Download a supported Gemma model in LM Studio. Recommended default: `google/gemma-3-12b-it`.
- Start the LM Studio Local Server.

The launcher does not download models automatically. LM Studio downloads are intentionally left to the LM Studio UI.

When the Local Server is reachable, the launcher reads the returned model IDs, prints them, chooses a supported Gemma model, logs the exact selected `model_id`, and writes it into local `config.py` for the brain, service, and translator model settings. It also points the provider base URLs at `http://localhost:1234`.

If LM Studio is not running, it prints:

```text
LM Studio is not running.
Open LM Studio, start Local Server, then run this script again.
```

If no supported Gemma model is returned, it prints the recommended model ID and asks you to download it in LM Studio, then rerun the launcher.

After the readiness check passes, the launcher creates `.venv` if needed, installs `requirements.txt`, starts the backend, and opens:

```text
http://127.0.0.1:8000
```

## Quick Start

Create and activate a virtual environment:
Expand Down
10 changes: 10 additions & 0 deletions launch_jin.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
@echo off
setlocal

cd /d "%~dp0"

powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dp0launch_jin.ps1"

echo.
echo JIN launcher finished. Press any key to close this window.
pause >nul
256 changes: 256 additions & 0 deletions launch_jin.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
param(
[string]$LmStudioBaseUrl = "http://localhost:1234",
[string]$AppUrl = "http://127.0.0.1:8000"
)

$ErrorActionPreference = "Stop"

$Root = Split-Path -Parent $MyInvocation.MyCommand.Path
$ModelsUrl = "$LmStudioBaseUrl/v1/models"
$RecommendedModel = "google/gemma-3-12b-it"

function Write-Step {
param([string]$Message)
Write-Host ""
Write-Host "==> $Message"
}

function Fail-WithMessage {
param([string]$Message)
Write-Host ""
Write-Host $Message
exit 1
}

function Get-ModelIds {
param($Response)

$ids = New-Object System.Collections.Generic.List[string]

if ($null -eq $Response) {
return $ids
}

$items = @()

if ($Response.PSObject.Properties.Name -contains "data") {
$items = @($Response.data)
}
else {
$items = @($Response)
}

foreach ($item in $items) {
if ($null -eq $item) {
continue
}

if ($item -is [string]) {
if ($item.Trim().Length -gt 0) {
$ids.Add($item.Trim())
}
continue
}

if ($item.PSObject.Properties.Name -contains "id") {
$id = [string]$item.id
if ($id.Trim().Length -gt 0) {
$ids.Add($id.Trim())
}
}
}

return $ids
}

function Find-GemmaModel {
param([string[]]$ModelIds)

$preferredPatterns = @(
"gemma-4",
"gemma-3-27b",
"gemma-3-12b",
"gemma-3",
"gemma"
)

foreach ($pattern in $preferredPatterns) {
foreach ($modelId in $ModelIds) {
if ($modelId.ToLowerInvariant().Contains($pattern)) {
return $modelId
}
}
}

return $null
}

function Set-PythonConfigValue {
param(
[string]$Path,
[string]$Name,
[object]$Value
)

$content = Get-Content -Raw -Path $Path

if ($Value -is [bool]) {
$renderedValue = if ($Value) { "True" } else { "False" }
}
elseif ($Value -is [int] -or $Value -is [double]) {
$renderedValue = [string]$Value
}
else {
$escaped = ([string]$Value).Replace("\", "\\").Replace('"', '\"')
$renderedValue = '"' + $escaped + '"'
}

$pattern = "(?m)^$Name\s*=.*$"
$replacement = "$Name = $renderedValue"

if ([regex]::IsMatch($content, $pattern)) {
$content = [regex]::Replace($content, $pattern, $replacement, 1)
}
else {
$content = $content.TrimEnd() + "`r`n`r`n" + $replacement + "`r`n"
}

Set-Content -Path $Path -Value $content -Encoding UTF8
}

function Write-JinConfig {
param([string]$ModelId)

$configPath = Join-Path $Root "config.py"
$examplePath = Join-Path $Root "config.example.py"

if (-not (Test-Path $configPath)) {
if (-not (Test-Path $examplePath)) {
Fail-WithMessage "Cannot find config.py or config.example.py."
}

Write-Host "config.py is missing. Creating it from config.example.py..."
Copy-Item -Path $examplePath -Destination $configPath
}

Set-PythonConfigValue -Path $configPath -Name "USE_SERVICE_AS_BRAIN" -Value $true
Set-PythonConfigValue -Path $configPath -Name "TRANSLATION_ENABLED" -Value $false
Set-PythonConfigValue -Path $configPath -Name "BRAIN_API_BASE" -Value $LmStudioBaseUrl
Set-PythonConfigValue -Path $configPath -Name "SERVICE_API_BASE" -Value $LmStudioBaseUrl
Set-PythonConfigValue -Path $configPath -Name "TRANSLATOR_API_BASE" -Value $LmStudioBaseUrl
Set-PythonConfigValue -Path $configPath -Name "BRAIN_MODEL_UID" -Value $ModelId
Set-PythonConfigValue -Path $configPath -Name "SERVICE_MODEL_UID" -Value $ModelId
Set-PythonConfigValue -Path $configPath -Name "TRANSLATOR_MODEL_UID" -Value $ModelId
Set-PythonConfigValue -Path $configPath -Name "CHAT_ENDPOINT" -Value "/v1/chat/completions"
Set-PythonConfigValue -Path $configPath -Name "MODELS_ENDPOINT" -Value "/v1/models"
Set-PythonConfigValue -Path $configPath -Name "NATIVE_MODELS_ENDPOINT" -Value "/api/v0/models"
}

function Get-PythonCommand {
$python = Get-Command python -ErrorAction SilentlyContinue
if ($python) {
return @($python.Source)
}

$py = Get-Command py -ErrorAction SilentlyContinue
if ($py) {
return @($py.Source, "-3")
}

Fail-WithMessage "Python was not found. Install Python 3, then run this script again."
}

Set-Location $Root

Write-Host "JIN one-click launcher"
Write-Host "LM Studio API: $ModelsUrl"

Write-Step "Checking LM Studio Local Server..."

try {
$modelsResponse = Invoke-RestMethod -Method Get -Uri $ModelsUrl -TimeoutSec 5
}
catch {
Fail-WithMessage "LM Studio is not running.`r`nOpen LM Studio, start Local Server, then run this script again."
}

$modelIds = @(Get-ModelIds -Response $modelsResponse)

if ($modelIds.Count -eq 0) {
Fail-WithMessage "No models returned by LM Studio.`r`nRecommended default: $RecommendedModel`r`nPlease download it in LM Studio, then run this script again."
}

Write-Host "Models returned by LM Studio:"
foreach ($modelId in $modelIds) {
Write-Host " - $modelId"
}

$selectedModel = Find-GemmaModel -ModelIds $modelIds

if (-not $selectedModel) {
Fail-WithMessage "No supported Gemma model found.`r`nRecommended default: $RecommendedModel`r`nPlease download it in LM Studio, then run this script again."
}

Write-Host ""
Write-Host "Found model: $selectedModel"
Write-Host "Writing it to config..."
Write-JinConfig -ModelId $selectedModel

$venvPath = Join-Path $Root ".venv"
$venvPython = Join-Path $venvPath "Scripts\python.exe"

if (-not (Test-Path $venvPython)) {
Write-Step "Creating .venv..."
$pythonCommand = Get-PythonCommand
$pythonExe = $pythonCommand[0]
$pythonArgs = @()

if ($pythonCommand.Length -gt 1) {
$pythonArgs += $pythonCommand[1..($pythonCommand.Length - 1)]
}

& $pythonExe @pythonArgs -m venv $venvPath
}
else {
Write-Step ".venv already exists."
}

if (-not (Test-Path $venvPython)) {
Fail-WithMessage "Virtual environment was not created correctly."
}

Write-Step "Installing requirements..."
& $venvPython -m pip install -r (Join-Path $Root "requirements.txt")

Write-Step "Starting JIN backend..."
Write-Host "Backend URL: $AppUrl"
Write-Host "Opening browser shortly. Keep this window open while using JIN."

$browserJob = Start-Job -ScriptBlock {
param([string]$Url)

for ($i = 0; $i -lt 30; $i++) {
try {
$response = Invoke-WebRequest -Uri $Url -UseBasicParsing -TimeoutSec 1
if ($response.StatusCode -lt 500) {
break
}
}
catch {
Start-Sleep -Seconds 1
}
}

Start-Process $Url
} -ArgumentList $AppUrl

try {
& $venvPython (Join-Path $Root "app.py")
}
finally {
if ($browserJob.State -eq "Running") {
Stop-Job $browserJob | Out-Null
}

Remove-Job $browserJob -Force -ErrorAction SilentlyContinue
}