feat: add Chinese UI, Responses models, and mirrored Windows setup
Localize the web console and persist custom model and capability settings. Use OpenAI Responses exclusively and move runtime limits into environment configuration. Bootstrap uv and the virtual environment on Windows, support optional China mirrors, and package source distributions without environments, build output, or credentials. Update regression tests and repository quality checks for the new behavior.
This commit is contained in:
1 parent
371aa6df56
commit
2ddec53954
183 files changed
+3427
-11857
No files matched your search
@@ -0,0 +1,103 @@
|
||||
# Download settings are process-local; never edit global pip/npm/uv settings.
|
||||
# Keep this file ASCII for Windows PowerShell 5.1.
|
||||
|
||||
function Initialize-ArtemisDownloads {
|
||||
param([string]$ProjectRoot, [string]$Mode)
|
||||
$path = Join-Path $ProjectRoot 'config\downloads.json'
|
||||
$config = Get-Content -LiteralPath $path -Raw -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop
|
||||
if (-not $Mode) { $Mode = $config.mode }
|
||||
if ($Mode -notin @('Official', 'China')) { throw 'Download mode must be Official or China.' }
|
||||
$timeout = 0
|
||||
if (-not [int]::TryParse([string]$config.timeout_seconds, [ref]$timeout) -or $timeout -lt 10 -or $timeout -gt 3600) {
|
||||
throw 'config/downloads.json: timeout_seconds must be between 10 and 3600.'
|
||||
}
|
||||
$values = @{
|
||||
python_index = ''; python_download_mirror = ''; npm_registry = ''
|
||||
node_mirror = 'https://nodejs.org/dist'
|
||||
adb_url = 'https://dl.google.com/android/repository/platform-tools-latest-windows.zip'
|
||||
scrcpy_url = 'https://github.com/Genymobile/scrcpy/releases/download/v4.1/scrcpy-win64-v4.1.zip'
|
||||
uv_installer_url = 'https://astral.sh/uv/install.ps1'
|
||||
}
|
||||
foreach ($name in @($values.Keys)) {
|
||||
if ($Mode -eq 'China' -and $config.china.$name) { $values[$name] = [string]$config.china.$name }
|
||||
if ($config.overrides.$name) { $values[$name] = [string]$config.overrides.$name }
|
||||
if ($values[$name]) {
|
||||
$uri = $null
|
||||
if (-not [Uri]::TryCreate($values[$name], [UriKind]::Absolute, [ref]$uri) -or $uri.Scheme -ne 'https') {
|
||||
throw "config/downloads.json: $name must be an HTTPS URL."
|
||||
}
|
||||
}
|
||||
}
|
||||
# Explicit caller environment settings take priority over the selected preset.
|
||||
if ($env:UV_DEFAULT_INDEX) { $values.python_index = $env:UV_DEFAULT_INDEX }
|
||||
elseif ($env:UV_INDEX_URL) { $values.python_index = $env:UV_INDEX_URL }
|
||||
if ($env:UV_PYTHON_INSTALL_MIRROR) { $values.python_download_mirror = $env:UV_PYTHON_INSTALL_MIRROR }
|
||||
if ($env:npm_config_registry) { $values.npm_registry = $env:npm_config_registry }
|
||||
if ($values.python_download_mirror) { $env:UV_PYTHON_INSTALL_MIRROR = $values.python_download_mirror }
|
||||
if ($values.npm_registry) { $env:npm_config_registry = $values.npm_registry }
|
||||
$env:UV_HTTP_TIMEOUT = [string]$timeout
|
||||
$env:npm_config_fetch_timeout = [string]($timeout * 1000)
|
||||
$values.mode = $Mode
|
||||
$values.timeout_seconds = $timeout
|
||||
$script:ArtemisDownloads = [pscustomobject]$values
|
||||
Write-Host " [INFO] Download mode: $Mode (config/downloads.json)." -ForegroundColor Cyan
|
||||
}
|
||||
|
||||
function Install-ArtemisUvFromIndex {
|
||||
param([string]$IndexUrl, [string]$InstallDir = (Join-Path $env:USERPROFILE '.local\bin'))
|
||||
# A uv wheel contains the standalone executable; no preinstalled Python is needed.
|
||||
$work = Join-Path ([IO.Path]::GetTempPath()) ('artemis-uv-' + [guid]::NewGuid().ToString('N'))
|
||||
New-Item -ItemType Directory -Path $work -ErrorAction Stop | Out-Null
|
||||
try {
|
||||
$index = $IndexUrl.TrimEnd('/') + '/uv/'
|
||||
$page = Join-Path $work 'index.html'
|
||||
if (-not (Invoke-DownloadFile -Uri $index -OutFile $page)) { throw 'Could not download the uv package index.' }
|
||||
$platform = if ($env:PROCESSOR_ARCHITECTURE -eq 'ARM64' -or $env:PROCESSOR_ARCHITEW6432 -eq 'ARM64') { 'win_arm64' } else { 'win_amd64' }
|
||||
$pattern = 'href=["''](?<link>[^"'']*uv-(?<version>\d+\.\d+\.\d+)-py3-none-' + $platform + '\.whl#sha256=(?<hash>[a-fA-F0-9]{64}))["'']'
|
||||
$candidates = [regex]::Matches((Get-Content -LiteralPath $page -Raw), $pattern)
|
||||
$candidate = $candidates | Sort-Object { [version]$_.Groups['version'].Value } -Descending | Select-Object -First 1
|
||||
if (-not $candidate) { throw "No stable uv wheel for $platform was found on the selected index." }
|
||||
$url = [Uri]::new([Uri]$index, [System.Net.WebUtility]::HtmlDecode($candidate.Groups['link'].Value))
|
||||
if ($url.Scheme -ne 'https') { throw 'The uv download must use HTTPS.' }
|
||||
$zip = Join-Path $work 'uv.zip'
|
||||
if (-not (Invoke-DownloadFile -Uri $url.AbsoluteUri.Split('#')[0] -OutFile $zip)) { throw 'Could not download uv from the selected index.' }
|
||||
$hasher = [Security.Cryptography.SHA256]::Create()
|
||||
$stream = [IO.File]::OpenRead($zip)
|
||||
try { $digest = [BitConverter]::ToString($hasher.ComputeHash($stream)).Replace('-', '') }
|
||||
finally { $stream.Dispose(); $hasher.Dispose() }
|
||||
if ($digest -ne $candidate.Groups['hash'].Value) {
|
||||
throw 'The downloaded uv package failed SHA256 verification.'
|
||||
}
|
||||
$extract = Join-Path $work 'wheel'
|
||||
Add-Type -AssemblyName System.IO.Compression.FileSystem
|
||||
[IO.Compression.ZipFile]::ExtractToDirectory($zip, $extract)
|
||||
$executable = Get-ChildItem -LiteralPath $extract -Filter uv.exe -Recurse -File | Select-Object -First 1
|
||||
if (-not $executable) { throw 'The uv package did not contain uv.exe.' }
|
||||
New-Item -ItemType Directory -Path $InstallDir -Force -ErrorAction Stop | Out-Null
|
||||
Copy-Item -LiteralPath $executable.FullName -Destination (Join-Path $InstallDir 'uv.exe') -Force -ErrorAction Stop
|
||||
$uvx = Join-Path $executable.DirectoryName 'uvx.exe'
|
||||
if (Test-Path -LiteralPath $uvx) { Copy-Item -LiteralPath $uvx -Destination $InstallDir -Force -ErrorAction Stop }
|
||||
$env:PATH = "$InstallDir;$env:PATH"
|
||||
} finally {
|
||||
$resolved = (Resolve-Path -LiteralPath $work).Path
|
||||
$tempRoot = [IO.Path]::GetFullPath([IO.Path]::GetTempPath()).TrimEnd('\') + '\'
|
||||
if ($resolved.StartsWith($tempRoot, [StringComparison]::OrdinalIgnoreCase) -and (Split-Path $resolved -Leaf) -like 'artemis-uv-*') {
|
||||
Remove-Item -LiteralPath $resolved -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Install-ArtemisLockedDependencies {
|
||||
param([string]$PythonExe, [string]$IndexUrl)
|
||||
# uv sync uses the original artifact URLs in uv.lock. Export exact versions and
|
||||
# hashes, then let uv pip fetch those same artifacts through the chosen index.
|
||||
$requirements = Join-Path ([IO.Path]::GetTempPath()) ('artemis-requirements-' + [guid]::NewGuid().ToString('N') + '.txt')
|
||||
try {
|
||||
& uv export --frozen --no-dev --format requirements-txt --output-file $requirements 1>$null
|
||||
if ($LASTEXITCODE -ne 0) { throw 'Could not export the locked dependencies.' }
|
||||
& uv pip install --python $PythonExe --index-url $IndexUrl --requirements $requirements | Out-Host
|
||||
if ($LASTEXITCODE -ne 0) { throw 'Dependency installation failed. Check mirror availability or select another source in config/downloads.json.' }
|
||||
} finally {
|
||||
Remove-Item -LiteralPath $requirements -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user