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
2 changes: 1 addition & 1 deletion .github/workflows/verify-platform-kernel.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: "20"
node-version: "24"
cache: npm
cache-dependency-path: |
image-studio/frontend/package-lock.json
Expand Down
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
24
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

Image Studio 面向 OpenAI 兼容图像上游,重点解决长时间图像推理在 Cloudflare / Nginx 后面容易遇到的 524/504 断连问题。Responses API 模式支持 `HTTP SSE` 与 `WebSocket mode` 两种传输;Images API 模式则兼容标准 `/v1/images/generations` 与 `/v1/images/edits`。

项目不内置任何默认上游。首次启动需要你自己填写 BASE_URL、API Key、文本模型与图像模型。
项目不内置任何默认上游。首次启动需要你自己填写 BASE_URL、API Key 与请求模型 ID。

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

Expand All @@ -28,7 +28,7 @@ Image Studio 面向 OpenAI 兼容图像上游,重点解决长时间图像推
- 抢先体验当前分支的最新改动:到 [DR-lin-eng/Image-Studio Actions · release.yml](https://github.com/DR-lin-eng/Image-Studio/actions/workflows/release.yml) 下载最近一次成功构建的 artifact。
Windows 上这类 CI `exe` 如果没有签名,可能会被 Win11 Smart App Control / SmartScreen 拦截,因此只建议用于内部测试。
- 各平台安装包区别、命名规则和选择建议见 [docs/packages.md](./docs/packages.md)。
2. 首次启动后打开「上游配置」,填写 API 形态、BASE_URL、API Key、文本模型 ID、图像模型 ID。
2. 首次启动后打开「上游配置」,填写 API 形态、BASE_URL、API Key、请求模型 ID。
3. 根据上游能力选择 API 形态
- Responses API:更适合长推理、抗 524/504。
- Images API:更适合只提供标准图像接口的兼容上游。
Expand All @@ -43,7 +43,7 @@ Image Studio 面向 OpenAI 兼容图像上游,重点解决长时间图像推

提 Issue 前建议先做这几步:

1. 在当前 profile 里点一次「测试连接」,确认 `BASE_URL`、`API Key`、文本模型 ID、图像模型 ID 真实可用。
1. 在当前 profile 里点一次「测试连接」,确认 `BASE_URL`、`API Key`、请求模型 ID 真实可用。
2. 对照 [docs/troubleshooting.md](./docs/troubleshooting.md) 自查 `524/504`、`401/403`、`model not found`、多参考图/蒙版不生效、Android 保存目录行为等常见非软件问题。
3. 从历史详情或 raw 响应里确认真实 HTTP 状态码和上游报错,不要只看页面 toast。
4. 如果同样的 `BASE_URL + Key + 模型 ID` 在 curl、Postman 或上游自带调试页里也失败,优先联系你的上游服务商,而不是提交本仓库 Issue。
Expand All @@ -58,6 +58,8 @@ Image Studio 面向 OpenAI 兼容图像上游,重点解决长时间图像推
| 功能清单、平台能力、快捷键 | [docs/features.md](./docs/features.md) |
| 图生图批处理的入口、流程、输入输出规则 | [docs/batch-img2img/README.md](./docs/batch-img2img/README.md) |
| 当前 issue 处理进展与待验证项 | [docs/issue-progress.md](./docs/issue-progress.md) |
| 本轮修复根因、维护约束和防回归检查 | [docs/issue-fixes-20261006.md](./docs/issue-fixes-20261006.md) |
| 客户端性能优化、前后基准与复验方法 | [docs/performance-20261006.md](./docs/performance-20261006.md) |
| 可直接复用的 issue 关单评论模板 | [docs/issue-close-comments.md](./docs/issue-close-comments.md) |
| 源码构建、验证脚本、CI 产物链路 | [docs/build.md](./docs/build.md) |
| 真机 / 真实上游手工验证矩阵 | [docs/manual-verification.md](./docs/manual-verification.md) |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -595,18 +595,24 @@ class AndroidImageStudioBridge(
val apiKey = payload.optString("apiKey").trim()
val proxyMode = payload.optString("proxyMode", "system")
val proxyUrl = payload.optString("proxyURL", "")
val provider = payload.optString("provider", "openai").lowercase(Locale.US).let {
if (it == "google" || it == "grok") it else "openai"
}
val apiMode = payload.optString("apiMode", "responses")
val responsesTransport = payload.optString("responsesTransport", "sse")
if (apiKey.isBlank()) throw IllegalArgumentException("API Key 不能为空")
thread(name = "image-studio-probe-${requestId.take(12)}") {
try {
val connection = openHttpConnection(openAIEndpoint(baseUrl, "models"), proxyMode, proxyUrl, allowInsecureConnection).apply {
val endpointLabel = if (provider == "google") "/v1beta/models" else "/v1/models"
val modelsEndpoint = if (provider == "google") googleEndpoint(baseUrl, "models") else openAIEndpoint(baseUrl, "models")
val connection = openHttpConnection(modelsEndpoint, proxyMode, proxyUrl, allowInsecureConnection).apply {
requestMethod = "GET"
instanceFollowRedirects = true
connectTimeout = 20_000
readTimeout = 20_000
doInput = true
setRequestProperty("Authorization", "Bearer $apiKey")
if (provider == "google") setRequestProperty("X-Goog-Api-Key", apiKey)
else setRequestProperty("Authorization", "Bearer $apiKey")
setRequestProperty("Accept", "application/json")
setRequestProperty("User-Agent", "image-studio-android")
}
Expand All @@ -617,30 +623,30 @@ class AndroidImageStudioBridge(
} ?: ""
connection.disconnect()
if (status !in 200..299) {
throw IllegalStateException("上游 /v1/models 返回 $status${summarizeProbeBody(body).let { if (it.isBlank()) "" else ": $it" }}")
throw IllegalStateException("上游 $endpointLabel 返回 $status${summarizeProbeBody(body).let { if (it.isBlank()) "" else ": $it" }}")
}
val parsed = JSONObject(body)
if (!parsed.has("data") || parsed.isNull("data")) {
throw IllegalStateException("上游 /v1/models 响应缺少 data 数组")
}
val data = parsed.optJSONArray("data") ?: throw IllegalStateException("上游 /v1/models 响应缺少 data 数组")
val data = if (provider == "google") parsed.optJSONArray("models")
?: throw IllegalStateException("上游 /v1beta/models 响应缺少 models 数组")
else parsed.optJSONArray("data")
?: throw IllegalStateException("上游 /v1/models 响应缺少 data 数组")
val models = mutableListOf<Map<String, String>>()
for (index in 0 until data.length()) {
val item = data.optJSONObject(index) ?: continue
val id = item.optString("id").trim()
val id = if (provider == "google") item.optString("name").trim().removePrefix("models/") else item.optString("id").trim()
if (id.isBlank()) continue
models += mapOf(
"id" to id,
"object" to item.optString("object").trim(),
"ownedBy" to item.optString("owned_by").trim(),
"displayName" to item.optString("name").trim(),
"object" to if (provider == "google") "model" else item.optString("object").trim(),
"ownedBy" to if (provider == "google") "google" else item.optString("owned_by").trim(),
"displayName" to if (provider == "google") item.optString("displayName").trim() else item.optString("name").trim(),
)
}
val result = mutableMapOf<String, Any>(
"modelCount" to data.length(),
"models" to models,
)
if (apiMode == "responses" && responsesTransport == "websocket") {
if (provider == "openai" && apiMode == "responses" && responsesTransport == "websocket") {
result["responsesTransport"] = "websocket"
val probePayload = JSONObject()
.put("type", "response.create")
Expand Down Expand Up @@ -994,6 +1000,17 @@ class AndroidImageStudioBridge(
return if (basePath.endsWith("/openai")) "$cleaned/$path" else "$cleaned/v1/$path"
}

private fun googleEndpoint(baseUrl: String, endpointPath: String): String {
var cleaned = baseUrl.trim().trimEnd('/')
val path = endpointPath.trim().trim('/')
cleaned = when {
cleaned.endsWith("/v1beta/openai", ignoreCase = true) -> cleaned.dropLast("/openai".length)
cleaned.endsWith("/v1beta", ignoreCase = true) -> cleaned
else -> "$cleaned/v1beta"
}
return if (path.isBlank()) cleaned else "$cleaned/$path"
}

private fun isProbeLoopbackHost(host: String): Boolean {
val lower = host.lowercase(Locale.US).trim('[', ']')
return lower == "localhost" || lower.endsWith(".localhost") || lower == "127.0.0.1" || lower == "::1" || lower == "0:0:0:0:0:0:0:1"
Expand Down
2 changes: 1 addition & 1 deletion docs/build.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
## 环境要求

- Go 1.25.x。当前 `go.mod` 使用 `go 1.25.5` 与 `toolchain go1.26.3`。
- Node.js 20 或更新版本。
- Node.js 24 或更新版本。前端测试直接加载 TypeScript,平台验证 CI 使用 Node 24;仓库 `.nvmrc` 与此一致。
- Wails CLI v2.12.0。非 macOS release workflow 使用 `go install github.com/wailsapp/wails/v2/cmd/wails@v2.12.0`。
- Android 构建需要 JDK 17、Android SDK 34、Build Tools 34.0.0、Gradle 8.7。

Expand Down
2 changes: 1 addition & 1 deletion docs/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@

## 设置

- 上游配置:API 形态、BASE_URL、API Key、文本模型 ID、图像模型 ID、连接测试。
- 上游配置:API 形态、BASE_URL、API Key、请求模型 ID、连接测试。Responses API 和 Images API 都只填写一个请求模型 ID。
- API Key:
- 桌面端使用系统安全存储(Keychain / Credential Manager / Secret Service)。
- Android 壳层使用应用私有 SharedPreferences。
Expand Down
61 changes: 52 additions & 9 deletions docs/issue-close-comments.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Issue 关单评论模板

更新时间:2026-07-10
更新时间:2026-10-06

本文档只覆盖当前 **GitHub 仍 open,但当前仓库代码与本地验证已经覆盖** 的 issue。

Expand All @@ -12,14 +12,17 @@
- `#50`
- `#51`
- `#53`
- `#57`
- `#58`

统一验证基线:

- 2026-07-10 已重新跑通本地平台总链:
- `npm test; go test ./... (image-studio, go-cli, gio-client, shared/compat-go); npm run build:windows; npm run build:android; npm run build:macos; ./gradlew :app:testDebugUnitTest; git diff --check`
- `platform-kernel-summary.json`:`status = passed`
- 2026-10-06 已重新完成本机验证:
- `npm test (frontend/worker); go test ./... (4 modules); 5 frontend builds; Android JVM/APK; macOS universal/runtime probe; Windows amd64/arm64 backend compile; local live/smoke; issue tooling; git diff --check`
- `docs/verification/issue-audit-20261006.json`:`status = passed`
- 结构化结果:
- 前端测试当前为 `162/162` 通过。
- `docs/verification/issue-audit-20261006.json`
- 前端测试当前为 `203/203` 通过。

建议用法:

Expand All @@ -44,13 +47,20 @@ node scripts/render-issue-close-comments.mjs --write
- `README.md`
- `plan.json`
- `plan.md`
- `issue-24.md` ... `issue-42.md`
- `issue-24.md`
- `issue-44.md`
- `issue-49.md`
- `issue-50.md`
- `issue-51.md`
- `issue-53.md`
- `issue-57.md`
- `issue-58.md`

如果只是想先确认“当前哪些 issue 会被处理、处理方式是什么”,优先用:

```bash
node scripts/issue-close-helper.mjs plan
node scripts/issue-close-helper.mjs plan 24 25 --comment-only
node scripts/issue-close-helper.mjs plan 24 44 --comment-only
```

`apply` 默认不会执行。只有显式提供 `--execute`,并且环境里存在 `GITHUB_TOKEN` 或 `GH_TOKEN` 时,才会真正对 GitHub 发评论或关闭 issue。例如:
Expand Down Expand Up @@ -84,7 +94,7 @@ Issue: [#24](https://github.com/RoseKhlifa/Image-Studio/issues/24)
- `image-studio/frontend/src/components/history/HistoryRail.tsx`
- `image-studio/frontend/src/components/history/WindowsHistoryRail.tsx`

最终前端全量测试为 162/162,这个 issue 可以关闭。
2026-10-06 重新完成本机回归;单条与日期删除也会清理旧库副本,避免迁移恢复记录。验证依据见 docs/verification/issue-audit-20261006.json。
```

## `#44` [Feature]: 添加批处理功能
Expand All @@ -111,6 +121,8 @@ Issue: [#44](https://github.com/RoseKhlifa/Image-Studio/issues/44)
- `docs/batch-img2img/README.md`

批处理路径已有前后端测试覆盖,这个 issue 可以关闭;更复杂的命名模板可以单独作为增强需求。

2026-10-06 补充修复:队列两轮之间不会因 runningJobs 暂时为空而提前清零;提交失败按任务 ID 正确结算,重复结算不增加计数。真实 store 多轮事件链及 650 次结算回归已覆盖。
```

## `#49` [Feature]: 模板/历史里面的历史条数混乱,不够清晰的看到每一条的内容
Expand Down Expand Up @@ -206,8 +218,39 @@ Issue: [#53](https://github.com/RoseKhlifa/Image-Studio/issues/53)
前端、Go、Gio 与 Android 单测均通过,这个 issue 可以关闭。官方上游若要求 PNG、同尺寸或 alpha 通道,仍需提供符合模型约束的蒙版。
```

## `#57` [Feature]: 声音提示

Issue: [#57](https://github.com/RoseKhlifa/Image-Studio/issues/57)

```md
生成失败反馈已补齐。

- 提交失败、生成错误和结果处理错误均显示可见错误提示。
- 按现有任务提示音开关播放声音,后台且通知已授权时发失败系统通知。
- 同次提交的并发错误只提醒一次;新提交可以再次提醒,主动取消保持静默。
- Gio 同步支持失败声音和通知。

真实 store 提交/事件链测试覆盖并发失败、提交拒绝和取消;音频拒绝播放时可见提示仍保留。验证记录:docs/verification/issue-audit-20261006.json。
```

## `#58` [Feature]: 生图历史似乎不能显示所有的图片

Issue: [#58](https://github.com/RoseKhlifa/Image-Studio/issues/58)

```md
历史保留与分页已修复。

- 移除前端/Gio 的 120 条截断;生成和导入不再按可见列表删除数据库。
- 按 createdAt + id 分页,支持同一天数百张与同毫秒结果,完整历史仍可复用提示词。
- 导出与共享配置包含未加载记录;导入旧客户端部分快照不会删除本地档案。
- 按日期清理覆盖未加载记录,并同步删除对应全图和旧库副本。

650 条 IndexedDB 分页、完整图保留及配置导入/导出回归通过。旧版本已经删除的提示词不能从图片文件自动恢复。维护约束与验证记录见 docs/issue-fixes-20261006.md、docs/verification/issue-audit-20261006.json。
```

## 暂不建议关闭的 open issue

- `#30`:原生 Wails light/dark 标题栏颜色与 CSS token 已有自动化一致性验证,但仍缺 Windows 真机视觉确认。
- `#52`:五个拖出入口已统一走 Windows 原生 OLE,IDataObject 也显式提供 Unicode CF_HDROP;仍缺 Windows + OneCommander 实机确认。
- `#52`:Unicode CF_HDROP 保留,五个入口统一为避免 HTML/native 重叠的手势路径;文件格式与内部 MIME 均有测试覆盖;仍需 Windows + OneCommander 实机验收。
- `#55`:五处入口改为鼠标手势启动原生拖动,处理 mouseup/Esc/失焦/卸载;Windows 添加真实按钮检查、定时退出与捕获释放,单测和双架构编译通过;仍需 Windows 上验证其他应用拖拽恢复。
- `#14`:这是已回答的产品问答;当前没有独立在线 Web 版,也没有可据此实现的功能验收标准。
Loading
Loading