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
128 changes: 128 additions & 0 deletions .github/workflows/windows-portable-fixed-webview.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
name: Windows Portable Fixed WebView2

on:
workflow_dispatch:
inputs:
base_version:
description: "基础版本号,例如 0.1.6。手动构建前必须填写。"
required: true
type: string

permissions:
contents: read

jobs:
prepare-version:
name: Prepare version metadata
runs-on: ubuntu-24.04
outputs:
product_version: ${{ steps.version.outputs.product_version }}
app_version: ${{ steps.version.outputs.app_version }}
frontend_version: ${{ steps.version.outputs.frontend_version }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Compute version
id: version
shell: bash
env:
DISPATCH_BASE_VERSION: ${{ github.event.inputs.base_version }}
run: |
if [ -z "${DISPATCH_BASE_VERSION// }" ]; then
echo "base_version is required for manual portable builds" >&2
exit 1
fi
eval "$(./scripts/compute-version.sh "$DISPATCH_BASE_VERSION")"
{
echo "product_version=${PRODUCT_VERSION}"
echo "app_version=${APP_VERSION}"
echo "frontend_version=${FRONTEND_VERSION}"
} >> "$GITHUB_OUTPUT"

build-portable:
name: Build ${{ matrix.id }}
runs-on: ${{ matrix.runner }}
needs: prepare-version
strategy:
fail-fast: false
matrix:
include:
- id: windows-amd64-portable-fixed-webview
runner: windows-latest
wails_args: -platform windows/amd64 -clean
portable_arch: x64
artifact_suffix: windows-amd64-portable-fixed-webview.zip
- id: windows-arm64-portable-fixed-webview
runner: windows-11-arm
wails_args: -platform windows/arm64 -clean
portable_arch: arm64
artifact_suffix: windows-arm64-portable-fixed-webview.zip

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: "1.25"
check-latest: true
cache-dependency-path: image-studio/go.sum

- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: "20"
cache: npm
cache-dependency-path: image-studio/frontend/package-lock.json

- name: Sync version metadata
shell: bash
env:
IMAGE_STUDIO_PRODUCT_VERSION: ${{ needs.prepare-version.outputs.product_version }}
IMAGE_STUDIO_FRONTEND_VERSION: ${{ needs.prepare-version.outputs.frontend_version }}
run: node ./scripts/sync-version-metadata.mjs

- name: Install frontend dependencies
working-directory: image-studio/frontend
shell: bash
run: npm ci

- name: Install Wails CLI
shell: bash
run: |
go install github.com/wailsapp/wails/v2/cmd/wails@v2.12.0
echo "$(go env GOPATH)/bin" >> "$GITHUB_PATH"

- name: Build portable exe
working-directory: image-studio
shell: bash
env:
VITE_APP_VERSION: ${{ needs.prepare-version.outputs.app_version }}
run: |
wails build ${{ matrix.wails_args }} \
-ldflags "-X github.com/yuanhua/image-gptcodex/pkg/client.Version=${{ needs.prepare-version.outputs.app_version }}"

- name: Build portable fixed-webview zip
id: package
shell: pwsh
run: |
$artifactName = "image-studio-${{ needs.prepare-version.outputs.app_version }}-${{ matrix.artifact_suffix }}"
$artifactPath = "dist/$artifactName"
./scripts/build-windows-portable-fixed-webview.ps1 `
-Architecture "${{ matrix.portable_arch }}" `
-Version "${{ needs.prepare-version.outputs.app_version }}" `
-BinaryPath "image-studio/build/bin/image-studio.exe" `
-OutputZipPath $artifactPath
"artifact_name=$artifactName" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
"artifact_path=$artifactPath" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8

- name: Upload portable artifact
uses: actions/upload-artifact@v4
with:
name: ${{ steps.package.outputs.artifact_name }}
path: ${{ steps.package.outputs.artifact_path }}
retention-days: 14
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
/image-studio/build/darwin/build/
/image-studio/build/windows/build/
/image-studio/frontend/dist/
/image-studio/frontend/dist-file-preview/
/image-studio/frontend/node_modules/
/android-shell/.gradle/
/android-shell/.gradle-wrapper/
Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ Image Studio 面向 OpenAI 兼容图像上游,重点解决长时间图像推

当前没有独立部署的在线 Web 版。仓库里的浏览器预览主要用于前端调试和 target platform 预览,不等同于可直接对外提供服务的 SaaS Web 端。

桌面端现在支持提示词网站深链导入:

- macOS: 由 Wails 桌面版接收 `image-studio://import?...`
- Windows / Linux: 由 Gio 客户端接收 `image-studio://import?...`

对应站点上的 `Send to Image-Studio` 会把一次性 token 交给桌面端,再由客户端拉取提示词并弹出独立确认组件。

## 快速上手

1. 安装应用
Expand Down
29 changes: 23 additions & 6 deletions cloudflare-worker/src/index.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import {
DEFAULT_AUTO_RETRY_COUNT,
buildPromptOptimizePayload,
buildResponsesPayload,
describeProblem,
isRetryableRaw,
MAX_ATTEMPTS,
normalizeAutoRetryCount,
normalizeAPIMode,
normalizeBaseURL,
RETRY_BACKOFF_MS,
Expand Down Expand Up @@ -61,13 +62,14 @@ async function forwardRawWithRetry({
method,
headers,
bodyBuffer,
maxAttempts,
shouldRetry,
}) {
let lastRaw = "";
let lastStatus = 502;
let lastContentType = "application/json; charset=utf-8";

for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
const response = await fetch(upstreamURL, {
method,
headers,
Expand All @@ -84,7 +86,7 @@ async function forwardRawWithRetry({
},
});
}
if (attempt < MAX_ATTEMPTS && shouldRetry(lastRaw, response.status)) {
if (attempt < maxAttempts && shouldRetry(lastRaw, response.status)) {
await sleep(RETRY_BACKOFF_MS);
continue;
}
Expand All @@ -100,6 +102,10 @@ async function forwardRawWithRetry({
}, { status: lastStatus || 502 });
}

function resolveMaxAttempts(autoRetryCount) {
return normalizeAutoRetryCount(autoRetryCount ?? DEFAULT_AUTO_RETRY_COUNT) + 1;
}

function sanitizePayload(input) {
return {
apiKey: String(input?.apiKey || ""),
Expand All @@ -121,6 +127,7 @@ function sanitizePayload(input) {
requestPolicy: input?.requestPolicy === "compat" ? "compat" : "openai",
noPromptRevision: !!input?.noPromptRevision,
partialImages: Number(input?.partialImages || 0),
autoRetryCount: Number(input?.autoRetryCount || 0),
};
}

Expand All @@ -145,8 +152,9 @@ async function forwardResponses(env, payload, apiKey) {
const requestBody = buildResponsesPayload(payload, sourceDataURLs);
let lastRaw = "";
let lastStatus = 502;
const maxAttempts = resolveMaxAttempts(payload.autoRetryCount);

for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
const response = await fetch(`${upstreamBaseURL}/v1/responses`, {
method: "POST",
headers: {
Expand All @@ -166,7 +174,7 @@ async function forwardResponses(env, payload, apiKey) {
},
});
}
if (attempt < MAX_ATTEMPTS && isRetryableRaw(lastRaw)) {
if (attempt < maxAttempts && (isRetryableRaw(lastRaw) || [403, 502, 503, 504, 524].includes(response.status))) {
await sleep(RETRY_BACKOFF_MS);
continue;
}
Expand Down Expand Up @@ -195,12 +203,21 @@ async function forwardOpenAIPath(env, request, apiKey) {
const bodyBuffer = request.method === "GET" || request.method === "HEAD"
? null
: await request.arrayBuffer();
let parsedBody = null;
if (bodyBuffer) {
try {
parsedBody = JSON.parse(new TextDecoder().decode(bodyBuffer));
} catch {
parsedBody = null;
}
}
return forwardRawWithRetry({
upstreamURL,
method: request.method,
headers: makeUpstreamHeaders(request, apiKey),
bodyBuffer,
shouldRetry: (raw, status) => isRetryableRaw(raw) || [502, 503, 504, 524].includes(status),
maxAttempts: resolveMaxAttempts(parsedBody?.autoRetryCount),
shouldRetry: (raw, status) => isRetryableRaw(raw) || [403, 502, 503, 504, 524].includes(status),
});
}

Expand Down
8 changes: 8 additions & 0 deletions docs/build.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,14 @@ cd image-studio
wails build -platform windows/amd64 -clean
```

如果你要生成“用户直接解压双击 `exe` 也能运行”的 Windows 便携包,不要复用普通 release 的裸 `exe`。仓库提供了独立 workflow:

- GitHub Actions 工作流:`Windows Portable Fixed WebView2`
- 触发方式:手动 `workflow_dispatch`
- 产物:`image-studio-<version>-windows-amd64-portable-fixed-webview.zip` 与 `image-studio-<version>-windows-arm64-portable-fixed-webview.zip`

这两个 zip 会把 `image-studio.exe` 和微软 Fixed Version WebView2 Runtime 一起打包,并在应用启动时优先使用同目录下的 `WebView2FixedRuntime/`。

Linux Ubuntu 24.04 / Debian 新版本:

```bash
Expand Down
41 changes: 40 additions & 1 deletion docs/gio-client.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Gio 高性能测试客户端

Gio 客户端是 Windows / Linux 的独立测试版本,目录为 `gio-client/`。它的目标是验证不经过 WebView2/WebKitGTK 的原生 Gio 渲染路径,同时保持当前请求内核不变。
Gio 客户端是 Windows / Linux 的独立桌面版本,目录为 `gio-client/`。它继续承担原生 Gio 渲染路径验证,但现在也负责 Windows / Linux 下的 `image-studio://import?...` 网页导入协议入口。

## 边界

Expand All @@ -9,6 +9,45 @@ Gio 客户端是 Windows / Linux 的独立测试版本,目录为 `gio-client/`
- 复用 `go-cli/pkg/client` 的 Responses API、Images API、SSE、retry、proxy、模型默认值和请求字段策略。
- Gio 前端为新的 immediate-mode 架构,UI 结构沿用桌面端的控制面板、画布、历史记录和运行日志布局。
- GUI 入口仅面向 Windows / Linux;其他平台只编译 unsupported stub,避免误判为 macOS 支持。
- 网页深链协议按平台分流:
- macOS 由 `image-studio/` Wails 客户端处理
- Windows / Linux 由 `gio-client/` 处理

## 网页提示词导入协议

Image-Prompts 站点通过以下深链拉起桌面端:

```text
image-studio://import?token=XXXXXXXX
```

Gio 端实现了:

- 启动参数解析与运行中实例投递
- `promptimport` 共享拉取逻辑
- 独立导入确认弹层
- 首启显式协议注册提示

CLI:

```bash
go run ./cmd/image-studio-gio protocol register
go run ./cmd/image-studio-gio protocol unregister
go run ./cmd/image-studio-gio protocol status
go run ./cmd/image-studio-gio import-token TESTTEST
```

Linux 也可直接用仓库脚本写入 `.desktop` 文件并调用 `xdg-mime`:

```bash
bash ../scripts/register-gio-linux-scheme.sh /absolute/path/to/image-studio-gio
```

模板文件位于:

```text
gio-client/assets/image-studio-gio.desktop.in
```

## WebView2 兼容状态

Expand Down
14 changes: 10 additions & 4 deletions docs/packages.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ Windows 用户需要额外注意:Actions 里的 CI artifact 如果没有经过
|---|---|---|
| Windows x64 | `image-studio-<version>-windows-amd64.exe` | 裸 Wails 可执行文件,适合内部测试,不是安装器。 |
| Windows ARM64 | `image-studio-<version>-windows-arm64.exe` | 裸 Wails 可执行文件,适合内部测试,不是安装器。 |
| Windows Portable Fixed WebView2 x64 | `image-studio-<version>-windows-amd64-portable-fixed-webview.zip` | 便携压缩包,内置 Fixed Version WebView2 Runtime,适合用户直接解压后双击 `exe`。 |
| Windows Portable Fixed WebView2 ARM64 | `image-studio-<version>-windows-arm64-portable-fixed-webview.zip` | ARM64 便携压缩包,内置 Fixed Version WebView2 Runtime。 |
| Windows Installer | `image-studio-<version>-windows-installer.exe` | 单个 NSIS 安装器,内含 amd64 与 arm64 两套二进制,安装时按本机架构自动选择。 |
| Windows MSIX x64 | `image-studio-<version>-windows-x64.msix` | 面向 Microsoft Store / 企业分发的 x64 MSIX 包。 |
| Windows MSIX ARM64 | `image-studio-<version>-windows-arm64.msix` | 面向 Microsoft Store / 企业分发的 ARM64 MSIX 包。 |
Expand All @@ -32,25 +34,28 @@ Windows 用户需要额外注意:Actions 里的 CI artifact 如果没有经过
| Linux x64 | `image-studio-<version>-linux-amd64.tar.gz` | 标准 Wails 桌面版。 |
| Linux ARM64 | `image-studio-<version>-linux-arm64.tar.gz` | 面向 ARM64 Linux 桌面环境。 |
| Android | `image-studio-<version>-android-release.apk` | 单 APK,运行时自适应 phone / pad 布局。 |
| Gio Windows | `image-studio-gio-<version>-windows-*.exe` | Gio 原生 GUI 测试版,不依赖 WebView2。 |
| Gio Linux | `image-studio-gio-<version>-linux-*.tar.gz` | Gio 原生 GUI 测试版,不依赖 WebKitGTK。 |
| Gio Windows | `image-studio-gio-<version>-windows-*.exe` | Gio 原生 GUI 版,不依赖 WebView2;Windows 下可作为 `image-studio://` 网页导入默认处理器。 |
| Gio Linux | `image-studio-gio-<version>-linux-*.tar.gz` | Gio 原生 GUI 版,不依赖 WebKitGTK;Linux 下可通过 CLI / `.desktop` 注册网页导入协议。 |

## 各平台选择建议

| 场景 | 建议下载 |
|---|---|
| 普通桌面用户 | 对应平台的 `image-studio-<version>-...` Wails 版。 |
| Windows 上没有稳定 WebView2 环境,或想验证 Gio 原生界面 | `image-studio-gio-<version>-windows-...`。 |
| Linux 上只想试 Gio 原生渲染路径 | `image-studio-gio-<version>-linux-...`。 |
| Windows 用户会直接解压后双击 `exe`,且机器上可能没有可用 WebView2 | `image-studio-<version>-windows-*-portable-fixed-webview.zip`。 |
| Windows 上没有稳定 WebView2 环境,或需要接收提示词网站的 `Send to Image-Studio` 深链 | `image-studio-gio-<version>-windows-...`。 |
| Linux 上想用 Gio 原生渲染路径,或需要接收 `image-studio://import?...` 深链 | `image-studio-gio-<version>-linux-...`。 |
| 手机与平板统一安装 | `image-studio-<version>-android-release.apk`。 |

## 平台注意事项

### Windows

- Wails 裸 `exe` 依赖 WebView2 Runtime;安装器版本会在安装阶段检查并静默拉起 WebView2 Runtime 安装。
- `windows-*-portable-fixed-webview.zip` 会把 Fixed Version WebView2 Runtime 一起打包,适合直接解压运行;请保持 `image-studio.exe` 与 `WebView2FixedRuntime/` 在同一目录层级。
- ARM64 设备优先下载 `windows-arm64`,避免 x64 仿真带来的额外开销。
- 对外普通分发时请优先使用 `image-studio-<version>-windows-installer.exe`,不要直接使用裸 `exe`。
- 便携 Fixed WebView2 包是单独 workflow 产物,不替代正式安装器或 MSIX。
- 提交 Microsoft Store 时请优先使用 `image-studio-<version>-windows.msixbundle`,其次再按需要使用分架构 `.msix`。
- 对外分发请优先使用带有效 Authenticode 签名的正式 release;未签名的 CI `exe` 或安装器在 Windows 11 上可能被智能应用控制直接阻止运行。

Expand All @@ -69,6 +74,7 @@ xattr -dr com.apple.quarantine "Image Studio.app"

- 预编译包主要面向带 GTK / WebKitGTK 依赖的桌面环境。
- 如果你更想避免 WebKitGTK 依赖,可以优先试 Gio 测试版。
- Gio Linux 版支持通过 `go run ./cmd/image-studio-gio protocol register` 或 `bash scripts/register-gio-linux-scheme.sh /path/to/image-studio-gio` 注册 `image-studio://` 网页导入协议。

### Android

Expand Down
Loading
Loading