@lobehub/lobehub 2.0.0-next.232 → 2.0.0-next.233
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/bundle-analyzer.yml +1 -1
- package/.github/workflows/e2e.yml +67 -52
- package/.github/workflows/manual-build-desktop.yml +5 -5
- package/.github/workflows/pr-build-desktop.yml +4 -4
- package/.github/workflows/pr-build-docker.yml +2 -2
- package/.github/workflows/release-desktop-beta.yml +4 -4
- package/.github/workflows/release-docker.yml +2 -2
- package/.github/workflows/test.yml +44 -7
- package/CHANGELOG.md +25 -0
- package/changelog/v1.json +5 -0
- package/docs/self-hosting/environment-variables/auth.mdx +7 -0
- package/docs/self-hosting/environment-variables/auth.zh-CN.mdx +7 -0
- package/package.json +1 -1
- package/packages/business/config/src/llm.ts +6 -1
- package/packages/const/src/settings/image.ts +1 -1
- package/packages/model-bank/src/aiModels/azure.ts +2 -2
- package/packages/model-bank/src/aiModels/google.ts +1 -0
- package/packages/model-bank/src/aiModels/lobehub.ts +33 -13
- package/packages/model-bank/src/aiModels/openai.ts +21 -4
- package/packages/model-runtime/src/core/openaiCompatibleFactory/createImage.ts +4 -1
- package/packages/model-runtime/src/providers/openai/__snapshots__/index.test.ts.snap +1 -1
- package/packages/ssrf-safe-fetch/index.test.ts +5 -34
- package/packages/ssrf-safe-fetch/index.ts +12 -2
- package/src/app/[variants]/(main)/image/_layout/ConfigPanel/components/MultiImagesUpload/index.tsx +3 -3
- package/src/app/[variants]/(main)/image/features/GenerationFeed/index.tsx +3 -10
- package/src/app/[variants]/(main)/image/index.tsx +1 -1
- package/src/envs/auth.ts +15 -0
- package/src/hooks/useFetchAiImageConfig.ts +54 -10
- package/src/libs/trpc/utils/internalJwt.ts +2 -2
- package/src/store/image/slices/generationConfig/initialState.ts +5 -5
- package/src/store/image/slices/generationConfig/selectors.test.ts +11 -4
- package/vitest.config.mts +10 -6
|
@@ -98,7 +98,7 @@ jobs:
|
|
|
98
98
|
echo "- \`pnpm-lock.yaml\` - pnpm lockfile (for reproducible builds)" >> bundle-report/README.md
|
|
99
99
|
|
|
100
100
|
- name: Upload bundle analyzer reports
|
|
101
|
-
uses: actions/upload-artifact@
|
|
101
|
+
uses: actions/upload-artifact@v6
|
|
102
102
|
with:
|
|
103
103
|
name: bundle-report-${{ github.run_id }}
|
|
104
104
|
path: bundle-report/
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
name: E2E CI
|
|
2
|
+
|
|
3
|
+
on: [push, pull_request]
|
|
4
|
+
|
|
2
5
|
permissions:
|
|
6
|
+
actions: write
|
|
3
7
|
contents: read
|
|
4
8
|
|
|
5
|
-
on:
|
|
6
|
-
pull_request:
|
|
7
|
-
push:
|
|
8
|
-
|
|
9
9
|
concurrency:
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
11
|
+
cancel-in-progress: true
|
|
12
12
|
|
|
13
13
|
env:
|
|
14
14
|
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/postgres
|
|
@@ -24,59 +24,74 @@ env:
|
|
|
24
24
|
S3_ENDPOINT: https://e2e-mock-s3.localhost
|
|
25
25
|
|
|
26
26
|
jobs:
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
27
|
+
# Check for duplicate runs
|
|
28
|
+
check-duplicate-run:
|
|
29
|
+
name: Check Duplicate Run
|
|
30
|
+
runs-on: ubuntu-latest
|
|
31
|
+
outputs:
|
|
32
|
+
should_skip: ${{ steps.skip_check.outputs.should_skip }}
|
|
33
|
+
steps:
|
|
34
|
+
- id: skip_check
|
|
35
|
+
uses: fkirc/skip-duplicate-actions@v5
|
|
36
|
+
with:
|
|
37
|
+
concurrent_skipping: 'same_content_newer'
|
|
38
|
+
skip_after_successful_duplicate: 'true'
|
|
39
|
+
do_not_skip: '["workflow_dispatch", "schedule"]'
|
|
37
40
|
|
|
38
|
-
|
|
39
|
-
|
|
41
|
+
e2e:
|
|
42
|
+
needs: check-duplicate-run
|
|
43
|
+
if: needs.check-duplicate-run.outputs.should_skip != 'true'
|
|
44
|
+
name: Test Web App
|
|
45
|
+
runs-on: ubuntu-latest
|
|
46
|
+
services:
|
|
47
|
+
postgres:
|
|
48
|
+
image: paradedb/paradedb:latest
|
|
49
|
+
env:
|
|
50
|
+
POSTGRES_PASSWORD: postgres
|
|
51
|
+
options: >-
|
|
52
|
+
--health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
|
|
53
|
+
ports:
|
|
54
|
+
- 5432:5432
|
|
40
55
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
56
|
+
timeout-minutes: 30
|
|
57
|
+
steps:
|
|
58
|
+
- name: Checkout
|
|
59
|
+
uses: actions/checkout@v6
|
|
45
60
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
61
|
+
- name: Setup Bun
|
|
62
|
+
uses: oven-sh/setup-bun@v2
|
|
63
|
+
with:
|
|
64
|
+
bun-version: latest
|
|
50
65
|
|
|
51
|
-
|
|
52
|
-
|
|
66
|
+
- name: Install dependencies (bun)
|
|
67
|
+
run: bun install
|
|
53
68
|
|
|
54
|
-
|
|
55
|
-
|
|
69
|
+
- name: Install Playwright browsers (with system deps)
|
|
70
|
+
run: bunx playwright install --with-deps chromium
|
|
56
71
|
|
|
57
|
-
|
|
58
|
-
|
|
72
|
+
- name: Run database migrations
|
|
73
|
+
run: bun run db:migrate
|
|
59
74
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
75
|
+
- name: Build application
|
|
76
|
+
run: bun run build
|
|
77
|
+
env:
|
|
78
|
+
SKIP_LINT: '1'
|
|
64
79
|
|
|
65
|
-
|
|
66
|
-
|
|
80
|
+
- name: Run E2E tests
|
|
81
|
+
run: bun run e2e
|
|
67
82
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
83
|
+
- name: Upload Cucumber HTML report (on failure)
|
|
84
|
+
if: failure()
|
|
85
|
+
uses: actions/upload-artifact@v6
|
|
86
|
+
with:
|
|
87
|
+
name: cucumber-report
|
|
88
|
+
path: e2e/reports
|
|
89
|
+
if-no-files-found: ignore
|
|
75
90
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
91
|
+
- name: Upload screenshots (on failure)
|
|
92
|
+
if: failure()
|
|
93
|
+
uses: actions/upload-artifact@v6
|
|
94
|
+
with:
|
|
95
|
+
name: test-screenshots
|
|
96
|
+
path: e2e/screenshots
|
|
97
|
+
if-no-files-found: ignore
|
|
@@ -171,7 +171,7 @@ jobs:
|
|
|
171
171
|
fi
|
|
172
172
|
|
|
173
173
|
- name: Upload artifact
|
|
174
|
-
uses: actions/upload-artifact@
|
|
174
|
+
uses: actions/upload-artifact@v6
|
|
175
175
|
with:
|
|
176
176
|
name: release-${{ matrix.os }}
|
|
177
177
|
path: |
|
|
@@ -224,7 +224,7 @@ jobs:
|
|
|
224
224
|
TMP: C:\temp
|
|
225
225
|
|
|
226
226
|
- name: Upload artifact
|
|
227
|
-
uses: actions/upload-artifact@
|
|
227
|
+
uses: actions/upload-artifact@v6
|
|
228
228
|
with:
|
|
229
229
|
name: release-windows-2025
|
|
230
230
|
path: |
|
|
@@ -275,7 +275,7 @@ jobs:
|
|
|
275
275
|
NEXT_PUBLIC_DESKTOP_UMAMI_BASE_URL: ${{ inputs.channel == 'beta' && secrets.UMAMI_BETA_DESKTOP_BASE_URL || secrets.UMAMI_NIGHTLY_DESKTOP_BASE_URL }}
|
|
276
276
|
|
|
277
277
|
- name: Upload artifact
|
|
278
|
-
uses: actions/upload-artifact@
|
|
278
|
+
uses: actions/upload-artifact@v6
|
|
279
279
|
with:
|
|
280
280
|
name: release-ubuntu-latest
|
|
281
281
|
path: |
|
|
@@ -309,7 +309,7 @@ jobs:
|
|
|
309
309
|
package-manager-cache: 'false'
|
|
310
310
|
|
|
311
311
|
- name: Download artifacts
|
|
312
|
-
uses: actions/download-artifact@
|
|
312
|
+
uses: actions/download-artifact@v7
|
|
313
313
|
with:
|
|
314
314
|
path: release
|
|
315
315
|
pattern: release-*
|
|
@@ -330,7 +330,7 @@ jobs:
|
|
|
330
330
|
run: bun run scripts/electronWorkflow/mergeMacReleaseFiles.js
|
|
331
331
|
|
|
332
332
|
- name: Upload artifacts with merged macOS files
|
|
333
|
-
uses: actions/upload-artifact@
|
|
333
|
+
uses: actions/upload-artifact@v6
|
|
334
334
|
with:
|
|
335
335
|
name: merged-release-manual
|
|
336
336
|
path: release/
|
|
@@ -194,7 +194,7 @@ jobs:
|
|
|
194
194
|
|
|
195
195
|
# 上传构建产物
|
|
196
196
|
- name: Upload artifact
|
|
197
|
-
uses: actions/upload-artifact@
|
|
197
|
+
uses: actions/upload-artifact@v6
|
|
198
198
|
with:
|
|
199
199
|
name: release-${{ matrix.os }}
|
|
200
200
|
path: |
|
|
@@ -229,7 +229,7 @@ jobs:
|
|
|
229
229
|
|
|
230
230
|
# 下载所有平台的构建产物
|
|
231
231
|
- name: Download artifacts
|
|
232
|
-
uses: actions/download-artifact@
|
|
232
|
+
uses: actions/download-artifact@v7
|
|
233
233
|
with:
|
|
234
234
|
path: release
|
|
235
235
|
pattern: release-*
|
|
@@ -255,7 +255,7 @@ jobs:
|
|
|
255
255
|
|
|
256
256
|
# 上传合并后的构建产物
|
|
257
257
|
- name: Upload artifacts with merged macOS files
|
|
258
|
-
uses: actions/upload-artifact@
|
|
258
|
+
uses: actions/upload-artifact@v6
|
|
259
259
|
with:
|
|
260
260
|
name: merged-release-pr
|
|
261
261
|
path: release/
|
|
@@ -280,7 +280,7 @@ jobs:
|
|
|
280
280
|
|
|
281
281
|
# 下载合并后的构建产物
|
|
282
282
|
- name: Download merged artifacts
|
|
283
|
-
uses: actions/download-artifact@
|
|
283
|
+
uses: actions/download-artifact@v7
|
|
284
284
|
with:
|
|
285
285
|
name: merged-release-pr
|
|
286
286
|
path: release
|
|
@@ -91,7 +91,7 @@ jobs:
|
|
|
91
91
|
touch "/tmp/digests/${digest#sha256:}"
|
|
92
92
|
|
|
93
93
|
- name: Upload artifact
|
|
94
|
-
uses: actions/upload-artifact@
|
|
94
|
+
uses: actions/upload-artifact@v6
|
|
95
95
|
with:
|
|
96
96
|
name: digest-${{ env.PLATFORM_PAIR }}
|
|
97
97
|
path: /tmp/digests/*
|
|
@@ -111,7 +111,7 @@ jobs:
|
|
|
111
111
|
fetch-depth: 0
|
|
112
112
|
|
|
113
113
|
- name: Download digests
|
|
114
|
-
uses: actions/download-artifact@
|
|
114
|
+
uses: actions/download-artifact@v7
|
|
115
115
|
with:
|
|
116
116
|
path: /tmp/digests
|
|
117
117
|
pattern: digest-*
|
|
@@ -181,7 +181,7 @@ jobs:
|
|
|
181
181
|
|
|
182
182
|
# 上传构建产物 (工作流处理重命名,不依赖 electron-builder 钩子)
|
|
183
183
|
- name: Upload artifact
|
|
184
|
-
uses: actions/upload-artifact@
|
|
184
|
+
uses: actions/upload-artifact@v6
|
|
185
185
|
with:
|
|
186
186
|
name: release-${{ matrix.os }}
|
|
187
187
|
path: |
|
|
@@ -220,7 +220,7 @@ jobs:
|
|
|
220
220
|
|
|
221
221
|
# 下载所有平台的构建产物
|
|
222
222
|
- name: Download artifacts
|
|
223
|
-
uses: actions/download-artifact@
|
|
223
|
+
uses: actions/download-artifact@v7
|
|
224
224
|
with:
|
|
225
225
|
path: release
|
|
226
226
|
pattern: release-*
|
|
@@ -246,7 +246,7 @@ jobs:
|
|
|
246
246
|
|
|
247
247
|
# 上传合并后的构建产物
|
|
248
248
|
- name: Upload artifacts with merged macOS files
|
|
249
|
-
uses: actions/upload-artifact@
|
|
249
|
+
uses: actions/upload-artifact@v6
|
|
250
250
|
with:
|
|
251
251
|
name: merged-release
|
|
252
252
|
path: release/
|
|
@@ -262,7 +262,7 @@ jobs:
|
|
|
262
262
|
steps:
|
|
263
263
|
# 下载合并后的构建产物
|
|
264
264
|
- name: Download merged artifacts
|
|
265
|
-
uses: actions/download-artifact@
|
|
265
|
+
uses: actions/download-artifact@v7
|
|
266
266
|
with:
|
|
267
267
|
name: merged-release
|
|
268
268
|
path: release
|
|
@@ -80,7 +80,7 @@ jobs:
|
|
|
80
80
|
touch "/tmp/digests/${digest#sha256:}"
|
|
81
81
|
|
|
82
82
|
- name: Upload artifact
|
|
83
|
-
uses: actions/upload-artifact@
|
|
83
|
+
uses: actions/upload-artifact@v6
|
|
84
84
|
with:
|
|
85
85
|
name: digest-${{ env.PLATFORM_PAIR }}
|
|
86
86
|
path: /tmp/digests/*
|
|
@@ -98,7 +98,7 @@ jobs:
|
|
|
98
98
|
fetch-depth: 0
|
|
99
99
|
|
|
100
100
|
- name: Download digests
|
|
101
|
-
uses: actions/download-artifact@
|
|
101
|
+
uses: actions/download-artifact@v7
|
|
102
102
|
with:
|
|
103
103
|
path: /tmp/digests
|
|
104
104
|
pattern: digest-*
|
|
@@ -76,14 +76,15 @@ jobs:
|
|
|
76
76
|
fi
|
|
77
77
|
done
|
|
78
78
|
|
|
79
|
-
# App tests
|
|
80
|
-
test-
|
|
79
|
+
# App tests - run sharded tests
|
|
80
|
+
test-app:
|
|
81
81
|
needs: check-duplicate-run
|
|
82
82
|
if: needs.check-duplicate-run.outputs.should_skip != 'true'
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
strategy:
|
|
84
|
+
matrix:
|
|
85
|
+
shard: [1, 2]
|
|
86
|
+
name: Test App (shard ${{ matrix.shard }}/2)
|
|
85
87
|
runs-on: ubuntu-latest
|
|
86
|
-
|
|
87
88
|
steps:
|
|
88
89
|
- uses: actions/checkout@v6
|
|
89
90
|
|
|
@@ -101,8 +102,44 @@ jobs:
|
|
|
101
102
|
- name: Install deps
|
|
102
103
|
run: bun i
|
|
103
104
|
|
|
104
|
-
- name:
|
|
105
|
-
run:
|
|
105
|
+
- name: Run tests with blob reporter
|
|
106
|
+
run: bunx vitest --coverage --reporter=blob --silent='passed-only' --shard=${{ matrix.shard }}/2
|
|
107
|
+
|
|
108
|
+
- name: Upload blob report
|
|
109
|
+
if: ${{ !cancelled() }}
|
|
110
|
+
uses: actions/upload-artifact@v6
|
|
111
|
+
with:
|
|
112
|
+
name: blob-report-${{ matrix.shard }}
|
|
113
|
+
path: .vitest-reports
|
|
114
|
+
include-hidden-files: true
|
|
115
|
+
retention-days: 1
|
|
116
|
+
|
|
117
|
+
# Merge sharded test reports and upload coverage
|
|
118
|
+
merge-app-coverage:
|
|
119
|
+
needs: test-app
|
|
120
|
+
if: ${{ !cancelled() && needs.test-app.result == 'success' }}
|
|
121
|
+
name: Merge and Upload App Coverage
|
|
122
|
+
runs-on: ubuntu-latest
|
|
123
|
+
steps:
|
|
124
|
+
- uses: actions/checkout@v6
|
|
125
|
+
|
|
126
|
+
- name: Install bun
|
|
127
|
+
uses: oven-sh/setup-bun@v2
|
|
128
|
+
with:
|
|
129
|
+
bun-version: latest
|
|
130
|
+
|
|
131
|
+
- name: Install deps
|
|
132
|
+
run: bun i
|
|
133
|
+
|
|
134
|
+
- name: Download blob reports
|
|
135
|
+
uses: actions/download-artifact@v7
|
|
136
|
+
with:
|
|
137
|
+
path: .vitest-reports
|
|
138
|
+
pattern: blob-report-*
|
|
139
|
+
merge-multiple: true
|
|
140
|
+
|
|
141
|
+
- name: Merge reports
|
|
142
|
+
run: bunx vitest --merge-reports --coverage
|
|
106
143
|
|
|
107
144
|
- name: Upload App Coverage to Codecov
|
|
108
145
|
uses: codecov/codecov-action@v5
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
# Changelog
|
|
4
4
|
|
|
5
|
+
## [Version 2.0.0-next.233](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.232...v2.0.0-next.233)
|
|
6
|
+
|
|
7
|
+
<sup>Released on **2026-01-07**</sup>
|
|
8
|
+
|
|
9
|
+
#### ✨ Features
|
|
10
|
+
|
|
11
|
+
- **image**: Improve image generation with new models and bug fixes.
|
|
12
|
+
|
|
13
|
+
<br/>
|
|
14
|
+
|
|
15
|
+
<details>
|
|
16
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
17
|
+
|
|
18
|
+
#### What's improved
|
|
19
|
+
|
|
20
|
+
- **image**: Improve image generation with new models and bug fixes, closes [#11311](https://github.com/lobehub/lobe-chat/issues/11311) ([4fc03bb](https://github.com/lobehub/lobe-chat/commit/4fc03bb))
|
|
21
|
+
|
|
22
|
+
</details>
|
|
23
|
+
|
|
24
|
+
<div align="right">
|
|
25
|
+
|
|
26
|
+
[](#readme-top)
|
|
27
|
+
|
|
28
|
+
</div>
|
|
29
|
+
|
|
5
30
|
## [Version 2.0.0-next.232](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.231...v2.0.0-next.232)
|
|
6
31
|
|
|
7
32
|
<sup>Released on **2026-01-07**</sup>
|
package/changelog/v1.json
CHANGED
|
@@ -63,6 +63,13 @@ LobeChat provides a complete authentication service capability when deployed. Th
|
|
|
63
63
|
|
|
64
64
|
<OIDCJWKs />
|
|
65
65
|
|
|
66
|
+
#### `INTERNAL_JWT_EXPIRATION`
|
|
67
|
+
|
|
68
|
+
- Type: Optional
|
|
69
|
+
- Description: Expiration time for internal JWT tokens used in lambda → async calls. Format: number followed by unit (s=seconds, m=minutes, h=hours). Should be as short as possible for security, but long enough to account for network latency and server processing time.
|
|
70
|
+
- Default: `30s`
|
|
71
|
+
- Example: `30s`, `1m`, `1h`
|
|
72
|
+
|
|
66
73
|
### Email Service (SMTP)
|
|
67
74
|
|
|
68
75
|
These settings are required for email verification and password reset features.
|
|
@@ -61,6 +61,13 @@ LobeChat 在部署时提供了完善的身份验证服务能力,以下是相
|
|
|
61
61
|
|
|
62
62
|
<OIDCJWKs />
|
|
63
63
|
|
|
64
|
+
#### `INTERNAL_JWT_EXPIRATION`
|
|
65
|
+
|
|
66
|
+
- 类型:可选
|
|
67
|
+
- 描述:内部 JWT 令牌的过期时间,用于 lambda → async 调用。格式:数字后跟单位(s = 秒,m = 分钟,h = 小时)。为了安全性应尽可能短,但需要足够长以应对网络延迟和服务器处理时间。
|
|
68
|
+
- 默认值:`30s`
|
|
69
|
+
- 示例:`30s`、`1m`、`1h`
|
|
70
|
+
|
|
64
71
|
### 邮件服务(SMTP)
|
|
65
72
|
|
|
66
73
|
启用邮箱验证和密码重置功能需要配置以下设置。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lobehub/lobehub",
|
|
3
|
-
"version": "2.0.0-next.
|
|
3
|
+
"version": "2.0.0-next.233",
|
|
4
4
|
"description": "LobeHub - an open-source,comprehensive AI Agent framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"framework",
|
|
@@ -23,11 +23,16 @@ const genUserLLMConfig = (specificConfig: Record<any, any>): UserModelProviderCo
|
|
|
23
23
|
};
|
|
24
24
|
|
|
25
25
|
export const DEFAULT_LLM_CONFIG = genUserLLMConfig({
|
|
26
|
+
anthropic: {
|
|
27
|
+
enabled: true,
|
|
28
|
+
},
|
|
29
|
+
google: {
|
|
30
|
+
enabled: true,
|
|
31
|
+
},
|
|
26
32
|
lmstudio: {
|
|
27
33
|
fetchOnClient: true,
|
|
28
34
|
},
|
|
29
35
|
ollama: {
|
|
30
|
-
enabled: true,
|
|
31
36
|
fetchOnClient: true,
|
|
32
37
|
},
|
|
33
38
|
openai: {
|
|
@@ -218,7 +218,8 @@ const azureChatModels: AIChatModelCard[] = [
|
|
|
218
218
|
deploymentName: 'gpt-4.1',
|
|
219
219
|
},
|
|
220
220
|
contextWindowTokens: 1_047_576,
|
|
221
|
-
description:
|
|
221
|
+
description:
|
|
222
|
+
'GPT-4.1 is our flagship model for complex tasks and cross-domain problem solving.',
|
|
222
223
|
displayName: 'GPT-4.1',
|
|
223
224
|
enabled: true,
|
|
224
225
|
id: 'gpt-4.1',
|
|
@@ -467,7 +468,6 @@ const azureImageModels: AIImageModelCard[] = [
|
|
|
467
468
|
enum: ['auto', '1024x1024', '1792x1024', '1024x1792'],
|
|
468
469
|
},
|
|
469
470
|
},
|
|
470
|
-
resolutions: ['1024x1024', '1024x1792', '1792x1024'],
|
|
471
471
|
type: 'image',
|
|
472
472
|
},
|
|
473
473
|
{
|
|
@@ -951,6 +951,7 @@ const googleImageModels: AIImageModelCard[] = [
|
|
|
951
951
|
{
|
|
952
952
|
displayName: 'Nano Banana',
|
|
953
953
|
id: 'gemini-2.5-flash-image:image',
|
|
954
|
+
enabled: true,
|
|
954
955
|
type: 'image',
|
|
955
956
|
description:
|
|
956
957
|
'Nano Banana is Google’s newest, fastest, and most efficient native multimodal model, enabling conversational image generation and editing.',
|
|
@@ -1063,6 +1063,14 @@ export const nanoBananaProParameters: ModelParamsSchema = {
|
|
|
1063
1063
|
},
|
|
1064
1064
|
};
|
|
1065
1065
|
|
|
1066
|
+
const gptImage1Schema = {
|
|
1067
|
+
imageUrls: { default: [], maxCount: 1, maxFileSize: 5 },
|
|
1068
|
+
prompt: { default: '' },
|
|
1069
|
+
size: {
|
|
1070
|
+
default: 'auto',
|
|
1071
|
+
enum: ['auto', '1024x1024', '1536x1024', '1024x1536'],
|
|
1072
|
+
},
|
|
1073
|
+
};
|
|
1066
1074
|
const lobehubImageModels: AIImageModelCard[] = [
|
|
1067
1075
|
{
|
|
1068
1076
|
description:
|
|
@@ -1104,7 +1112,7 @@ const lobehubImageModels: AIImageModelCard[] = [
|
|
|
1104
1112
|
{
|
|
1105
1113
|
description: 'Imagen 4th generation text-to-image model series',
|
|
1106
1114
|
displayName: 'Imagen 4 Fast',
|
|
1107
|
-
enabled:
|
|
1115
|
+
enabled: true,
|
|
1108
1116
|
id: 'imagen-4.0-fast-generate-001',
|
|
1109
1117
|
organization: 'Deepmind',
|
|
1110
1118
|
parameters: imagenBaseParameters,
|
|
@@ -1117,7 +1125,7 @@ const lobehubImageModels: AIImageModelCard[] = [
|
|
|
1117
1125
|
{
|
|
1118
1126
|
description: 'Imagen 4th generation text-to-image model series',
|
|
1119
1127
|
displayName: 'Imagen 4',
|
|
1120
|
-
enabled:
|
|
1128
|
+
enabled: true,
|
|
1121
1129
|
id: 'imagen-4.0-generate-001',
|
|
1122
1130
|
organization: 'Deepmind',
|
|
1123
1131
|
parameters: imagenBaseParameters,
|
|
@@ -1130,7 +1138,7 @@ const lobehubImageModels: AIImageModelCard[] = [
|
|
|
1130
1138
|
{
|
|
1131
1139
|
description: 'Imagen 4th generation text-to-image model series Ultra version',
|
|
1132
1140
|
displayName: 'Imagen 4 Ultra',
|
|
1133
|
-
enabled:
|
|
1141
|
+
enabled: true,
|
|
1134
1142
|
id: 'imagen-4.0-ultra-generate-001',
|
|
1135
1143
|
organization: 'Deepmind',
|
|
1136
1144
|
parameters: imagenBaseParameters,
|
|
@@ -1140,19 +1148,32 @@ const lobehubImageModels: AIImageModelCard[] = [
|
|
|
1140
1148
|
releasedAt: '2025-08-15',
|
|
1141
1149
|
type: 'image',
|
|
1142
1150
|
},
|
|
1151
|
+
{
|
|
1152
|
+
description:
|
|
1153
|
+
'An enhanced GPT Image 1 model with 4× faster generation, more precise editing, and improved text rendering.',
|
|
1154
|
+
displayName: 'GPT Image 1.5',
|
|
1155
|
+
enabled: true,
|
|
1156
|
+
id: 'gpt-image-1.5',
|
|
1157
|
+
parameters: gptImage1Schema,
|
|
1158
|
+
pricing: {
|
|
1159
|
+
approximatePricePerImage: 0.034,
|
|
1160
|
+
units: [
|
|
1161
|
+
{ name: 'textInput', rate: 5, strategy: 'fixed', unit: 'millionTokens' },
|
|
1162
|
+
{ name: 'textInput_cacheRead', rate: 1.25, strategy: 'fixed', unit: 'millionTokens' },
|
|
1163
|
+
{ name: 'imageInput', rate: 8, strategy: 'fixed', unit: 'millionTokens' },
|
|
1164
|
+
{ name: 'imageInput_cacheRead', rate: 2, strategy: 'fixed', unit: 'millionTokens' },
|
|
1165
|
+
{ name: 'imageOutput', rate: 32, strategy: 'fixed', unit: 'millionTokens' },
|
|
1166
|
+
],
|
|
1167
|
+
},
|
|
1168
|
+
releasedAt: '2025-12-16',
|
|
1169
|
+
type: 'image',
|
|
1170
|
+
},
|
|
1143
1171
|
{
|
|
1144
1172
|
description: 'ChatGPT native multimodal image generation model.',
|
|
1145
1173
|
displayName: 'GPT Image 1',
|
|
1146
1174
|
enabled: true,
|
|
1147
1175
|
id: 'gpt-image-1',
|
|
1148
|
-
parameters:
|
|
1149
|
-
imageUrls: { default: [], maxCount: 1, maxFileSize: 5 },
|
|
1150
|
-
prompt: { default: '' },
|
|
1151
|
-
size: {
|
|
1152
|
-
default: 'auto',
|
|
1153
|
-
enum: ['auto', '1024x1024', '1536x1024', '1024x1536'],
|
|
1154
|
-
},
|
|
1155
|
-
},
|
|
1176
|
+
parameters: gptImage1Schema,
|
|
1156
1177
|
pricing: {
|
|
1157
1178
|
approximatePricePerImage: 0.042,
|
|
1158
1179
|
units: [
|
|
@@ -1169,7 +1190,7 @@ const lobehubImageModels: AIImageModelCard[] = [
|
|
|
1169
1190
|
description:
|
|
1170
1191
|
'The latest DALL·E model, released in November 2023, supports more realistic, accurate image generation with stronger detail.',
|
|
1171
1192
|
displayName: 'DALL·E 3',
|
|
1172
|
-
enabled:
|
|
1193
|
+
enabled: true,
|
|
1173
1194
|
id: 'dall-e-3',
|
|
1174
1195
|
parameters: {
|
|
1175
1196
|
prompt: { default: '' },
|
|
@@ -1203,7 +1224,6 @@ const lobehubImageModels: AIImageModelCard[] = [
|
|
|
1203
1224
|
},
|
|
1204
1225
|
],
|
|
1205
1226
|
},
|
|
1206
|
-
resolutions: ['1024x1024', '1024x1792', '1792x1024'],
|
|
1207
1227
|
type: 'image',
|
|
1208
1228
|
},
|
|
1209
1229
|
{
|
|
@@ -1219,6 +1219,26 @@ export const openaiSTTModels: AISTTModelCard[] = [
|
|
|
1219
1219
|
|
|
1220
1220
|
// Image generation models
|
|
1221
1221
|
export const openaiImageModels: AIImageModelCard[] = [
|
|
1222
|
+
{
|
|
1223
|
+
description:
|
|
1224
|
+
'An enhanced GPT Image 1 model with 4× faster generation, more precise editing, and improved text rendering.',
|
|
1225
|
+
displayName: 'GPT Image 1.5',
|
|
1226
|
+
enabled: true,
|
|
1227
|
+
id: 'gpt-image-1.5',
|
|
1228
|
+
parameters: gptImage1ParamsSchema,
|
|
1229
|
+
pricing: {
|
|
1230
|
+
approximatePricePerImage: 0.034,
|
|
1231
|
+
units: [
|
|
1232
|
+
{ name: 'textInput', rate: 5, strategy: 'fixed', unit: 'millionTokens' },
|
|
1233
|
+
{ name: 'textInput_cacheRead', rate: 1.25, strategy: 'fixed', unit: 'millionTokens' },
|
|
1234
|
+
{ name: 'imageInput', rate: 8, strategy: 'fixed', unit: 'millionTokens' },
|
|
1235
|
+
{ name: 'imageInput_cacheRead', rate: 2, strategy: 'fixed', unit: 'millionTokens' },
|
|
1236
|
+
{ name: 'imageOutput', rate: 32, strategy: 'fixed', unit: 'millionTokens' },
|
|
1237
|
+
],
|
|
1238
|
+
},
|
|
1239
|
+
releasedAt: '2025-12-16',
|
|
1240
|
+
type: 'image',
|
|
1241
|
+
},
|
|
1222
1242
|
// https://platform.openai.com/docs/models/gpt-image-1
|
|
1223
1243
|
{
|
|
1224
1244
|
description: 'ChatGPT native multimodal image generation model.',
|
|
@@ -1236,7 +1256,6 @@ export const openaiImageModels: AIImageModelCard[] = [
|
|
|
1236
1256
|
{ name: 'imageOutput', rate: 40, strategy: 'fixed', unit: 'millionTokens' },
|
|
1237
1257
|
],
|
|
1238
1258
|
},
|
|
1239
|
-
resolutions: ['1024x1024', '1024x1536', '1536x1024'],
|
|
1240
1259
|
type: 'image',
|
|
1241
1260
|
},
|
|
1242
1261
|
{
|
|
@@ -1257,13 +1276,13 @@ export const openaiImageModels: AIImageModelCard[] = [
|
|
|
1257
1276
|
],
|
|
1258
1277
|
},
|
|
1259
1278
|
releasedAt: '2025-10-06',
|
|
1260
|
-
resolutions: ['1024x1024', '1024x1536', '1536x1024'],
|
|
1261
1279
|
type: 'image',
|
|
1262
1280
|
},
|
|
1263
1281
|
{
|
|
1264
1282
|
description:
|
|
1265
1283
|
'The latest DALL·E model, released in November 2023, supports more realistic, accurate image generation with stronger detail.',
|
|
1266
1284
|
displayName: 'DALL·E 3',
|
|
1285
|
+
enabled: true,
|
|
1267
1286
|
id: 'dall-e-3',
|
|
1268
1287
|
parameters: {
|
|
1269
1288
|
prompt: { default: '' },
|
|
@@ -1296,7 +1315,6 @@ export const openaiImageModels: AIImageModelCard[] = [
|
|
|
1296
1315
|
},
|
|
1297
1316
|
],
|
|
1298
1317
|
},
|
|
1299
|
-
resolutions: ['1024x1024', '1024x1792', '1792x1024'],
|
|
1300
1318
|
type: 'image',
|
|
1301
1319
|
},
|
|
1302
1320
|
{
|
|
@@ -1329,7 +1347,6 @@ export const openaiImageModels: AIImageModelCard[] = [
|
|
|
1329
1347
|
},
|
|
1330
1348
|
],
|
|
1331
1349
|
},
|
|
1332
|
-
resolutions: ['256x256', '512x512', '1024x1024'],
|
|
1333
1350
|
type: 'image',
|
|
1334
1351
|
},
|
|
1335
1352
|
];
|
|
@@ -67,7 +67,10 @@ async function generateByImageMode(
|
|
|
67
67
|
const defaultInput = {
|
|
68
68
|
n: 1,
|
|
69
69
|
...(model.includes('dall-e') ? { response_format: 'b64_json' } : {}),
|
|
70
|
-
|
|
70
|
+
// https://platform.openai.com/docs/api-reference/images/createEdit#images_createedit-input_fidelity
|
|
71
|
+
...(isImageEdit && model.includes('gpt-image-') && !model.includes('mini')
|
|
72
|
+
? { input_fidelity: 'high' }
|
|
73
|
+
: {}),
|
|
71
74
|
};
|
|
72
75
|
|
|
73
76
|
const options = cleanObject({
|
|
@@ -311,7 +311,7 @@ exports[`LobeOpenAI > models > should get models 1`] = `
|
|
|
311
311
|
"contextWindowTokens": undefined,
|
|
312
312
|
"description": "The latest DALL·E model, released in November 2023, supports more realistic, accurate image generation with stronger detail.",
|
|
313
313
|
"displayName": "DALL·E 3",
|
|
314
|
-
"enabled":
|
|
314
|
+
"enabled": true,
|
|
315
315
|
"functionCall": false,
|
|
316
316
|
"id": "dall-e-3",
|
|
317
317
|
"imageOutput": false,
|
|
@@ -49,14 +49,7 @@ describe('ssrfSafeFetch', () => {
|
|
|
49
49
|
expect(mockFetch).toHaveBeenCalledWith(
|
|
50
50
|
'https://httpbin.org/get',
|
|
51
51
|
expect.objectContaining({
|
|
52
|
-
agent: expect.
|
|
53
|
-
requestFilterOptions: expect.objectContaining({
|
|
54
|
-
allowIPAddressList: [],
|
|
55
|
-
allowMetaIPAddress: false,
|
|
56
|
-
allowPrivateIPAddress: false,
|
|
57
|
-
denyIPAddressList: [],
|
|
58
|
-
}),
|
|
59
|
-
}),
|
|
52
|
+
agent: expect.any(Function),
|
|
60
53
|
}),
|
|
61
54
|
);
|
|
62
55
|
expect(response).toBeInstanceOf(Response);
|
|
@@ -80,14 +73,7 @@ describe('ssrfSafeFetch', () => {
|
|
|
80
73
|
'https://httpbin.org/post',
|
|
81
74
|
expect.objectContaining({
|
|
82
75
|
...requestOptions,
|
|
83
|
-
agent: expect.
|
|
84
|
-
requestFilterOptions: expect.objectContaining({
|
|
85
|
-
allowIPAddressList: [],
|
|
86
|
-
allowMetaIPAddress: false,
|
|
87
|
-
allowPrivateIPAddress: false,
|
|
88
|
-
denyIPAddressList: [],
|
|
89
|
-
}),
|
|
90
|
-
}),
|
|
76
|
+
agent: expect.any(Function),
|
|
91
77
|
}),
|
|
92
78
|
);
|
|
93
79
|
});
|
|
@@ -302,14 +288,7 @@ describe('ssrfSafeFetch', () => {
|
|
|
302
288
|
'https://api.example.com/data',
|
|
303
289
|
expect.objectContaining({
|
|
304
290
|
...requestOptions,
|
|
305
|
-
agent: expect.
|
|
306
|
-
requestFilterOptions: expect.objectContaining({
|
|
307
|
-
allowIPAddressList: ['127.0.0.1'],
|
|
308
|
-
allowMetaIPAddress: true,
|
|
309
|
-
allowPrivateIPAddress: true,
|
|
310
|
-
denyIPAddressList: [],
|
|
311
|
-
}),
|
|
312
|
-
}),
|
|
291
|
+
agent: expect.any(Function),
|
|
313
292
|
}),
|
|
314
293
|
);
|
|
315
294
|
|
|
@@ -323,19 +302,11 @@ describe('ssrfSafeFetch', () => {
|
|
|
323
302
|
|
|
324
303
|
await ssrfSafeFetch('https://secure.example.com/api');
|
|
325
304
|
|
|
326
|
-
// Verify that the agent is
|
|
305
|
+
// Verify that the agent function is passed
|
|
327
306
|
expect(mockFetch).toHaveBeenCalledWith(
|
|
328
307
|
'https://secure.example.com/api',
|
|
329
308
|
expect.objectContaining({
|
|
330
|
-
agent: expect.
|
|
331
|
-
protocol: 'https:',
|
|
332
|
-
requestFilterOptions: expect.objectContaining({
|
|
333
|
-
allowIPAddressList: [],
|
|
334
|
-
allowMetaIPAddress: false,
|
|
335
|
-
allowPrivateIPAddress: false,
|
|
336
|
-
denyIPAddressList: [],
|
|
337
|
-
}),
|
|
338
|
-
}),
|
|
309
|
+
agent: expect.any(Function),
|
|
339
310
|
}),
|
|
340
311
|
);
|
|
341
312
|
});
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import fetch from 'node-fetch';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
RequestFilteringAgentOptions,
|
|
4
|
+
RequestFilteringHttpAgent,
|
|
5
|
+
RequestFilteringHttpsAgent,
|
|
6
|
+
} from 'request-filtering-agent';
|
|
3
7
|
|
|
4
8
|
/**
|
|
5
9
|
* Options for per-call SSRF configuration overrides
|
|
@@ -42,10 +46,16 @@ export const ssrfSafeFetch = async (
|
|
|
42
46
|
denyIPAddressList: [],
|
|
43
47
|
};
|
|
44
48
|
|
|
49
|
+
// Create agents for both protocols
|
|
50
|
+
const httpAgent = new RequestFilteringHttpAgent(agentOptions);
|
|
51
|
+
const httpsAgent = new RequestFilteringHttpsAgent(agentOptions);
|
|
52
|
+
|
|
45
53
|
// Use node-fetch with SSRF protection agent
|
|
54
|
+
// Pass a function to dynamically select agent based on URL protocol
|
|
55
|
+
// This handles redirects from HTTP to HTTPS correctly
|
|
46
56
|
const response = await fetch(url, {
|
|
47
57
|
...options,
|
|
48
|
-
agent:
|
|
58
|
+
agent: (parsedURL: URL) => (parsedURL.protocol === 'https:' ? httpsAgent : httpAgent),
|
|
49
59
|
} as any);
|
|
50
60
|
|
|
51
61
|
// Convert node-fetch Response to standard Response
|
package/src/app/[variants]/(main)/image/_layout/ConfigPanel/components/MultiImagesUpload/index.tsx
CHANGED
|
@@ -93,8 +93,8 @@ const styles = createStaticStyles(({ css }) => {
|
|
|
93
93
|
|
|
94
94
|
overflow: hidden;
|
|
95
95
|
|
|
96
|
-
width: ${thumbnailSize};
|
|
97
|
-
height: ${thumbnailSize};
|
|
96
|
+
width: ${thumbnailSize}px;
|
|
97
|
+
height: ${thumbnailSize}px;
|
|
98
98
|
border-radius: ${cssVar.borderRadius};
|
|
99
99
|
|
|
100
100
|
background: ${cssVar.colorBgContainer};
|
|
@@ -112,7 +112,7 @@ const styles = createStaticStyles(({ css }) => {
|
|
|
112
112
|
gap: 8px;
|
|
113
113
|
|
|
114
114
|
width: 100%;
|
|
115
|
-
height: ${thumbnailSize};
|
|
115
|
+
height: ${thumbnailSize}px;
|
|
116
116
|
padding: 0;
|
|
117
117
|
border-radius: ${cssVar.borderRadiusLG};
|
|
118
118
|
|
|
@@ -77,15 +77,8 @@ const GenerationFeed = memo(() => {
|
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
return (
|
|
80
|
-
|
|
81
|
-
<Flexbox
|
|
82
|
-
gap={16}
|
|
83
|
-
ref={parent}
|
|
84
|
-
style={{
|
|
85
|
-
minHeight: 'calc(100vh - 180px)',
|
|
86
|
-
}}
|
|
87
|
-
width="100%"
|
|
88
|
-
>
|
|
80
|
+
<Flexbox flex={1}>
|
|
81
|
+
<Flexbox gap={16} ref={parent} width="100%">
|
|
89
82
|
{currentGenerationBatches.map((batch, index) => (
|
|
90
83
|
<Fragment key={batch.id}>
|
|
91
84
|
{Boolean(index !== 0) && <Divider dashed style={{ margin: 0 }} />}
|
|
@@ -95,7 +88,7 @@ const GenerationFeed = memo(() => {
|
|
|
95
88
|
</Flexbox>
|
|
96
89
|
{/* Invisible element for scroll target */}
|
|
97
90
|
<div ref={containerRef} style={{ height: 1 }} />
|
|
98
|
-
|
|
91
|
+
</Flexbox>
|
|
99
92
|
);
|
|
100
93
|
});
|
|
101
94
|
|
|
@@ -15,7 +15,7 @@ const DesktopImagePage = memo(() => {
|
|
|
15
15
|
<>
|
|
16
16
|
<NavHeader right={<WideScreenButton />} />
|
|
17
17
|
<Flexbox height={'100%'} style={{ overflowY: 'auto', position: 'relative' }} width={'100%'}>
|
|
18
|
-
<WideScreenContainer>
|
|
18
|
+
<WideScreenContainer height={'100%'} wrapperStyle={{ height: '100%' }}>
|
|
19
19
|
<Suspense fallback={<SkeletonList />}>
|
|
20
20
|
<ImageWorkspace />
|
|
21
21
|
</Suspense>
|
package/src/envs/auth.ts
CHANGED
|
@@ -158,6 +158,15 @@ declare global {
|
|
|
158
158
|
* Can be generated using `node scripts/generate-oidc-jwk.mjs`.
|
|
159
159
|
*/
|
|
160
160
|
JWKS_KEY?: string;
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Internal JWT expiration time for lambda → async calls.
|
|
164
|
+
* Format: number followed by unit (s=seconds, m=minutes, h=hours)
|
|
165
|
+
* Examples: '10s', '1m', '1h'
|
|
166
|
+
* Should be as short as possible for security, but long enough to account for network latency and server processing time.
|
|
167
|
+
* @default '30s'
|
|
168
|
+
*/
|
|
169
|
+
INTERNAL_JWT_EXPIRATION?: string;
|
|
161
170
|
}
|
|
162
171
|
}
|
|
163
172
|
}
|
|
@@ -285,6 +294,9 @@ export const getAuthConfig = () => {
|
|
|
285
294
|
// Generic JWKS key for signing/verifying JWTs
|
|
286
295
|
JWKS_KEY: z.string().optional(),
|
|
287
296
|
ENABLE_OIDC: z.boolean(),
|
|
297
|
+
|
|
298
|
+
// Internal JWT expiration time (e.g., '10s', '1m', '1h')
|
|
299
|
+
INTERNAL_JWT_EXPIRATION: z.string().default('30s'),
|
|
288
300
|
},
|
|
289
301
|
|
|
290
302
|
runtimeEnv: {
|
|
@@ -415,6 +427,9 @@ export const getAuthConfig = () => {
|
|
|
415
427
|
// Generic JWKS key (fallback to OIDC_JWKS_KEY for backward compatibility)
|
|
416
428
|
JWKS_KEY: process.env.JWKS_KEY || process.env.OIDC_JWKS_KEY,
|
|
417
429
|
ENABLE_OIDC: !!(process.env.JWKS_KEY || process.env.OIDC_JWKS_KEY),
|
|
430
|
+
|
|
431
|
+
// Internal JWT expiration time
|
|
432
|
+
INTERNAL_JWT_EXPIRATION: process.env.INTERNAL_JWT_EXPIRATION,
|
|
418
433
|
},
|
|
419
434
|
});
|
|
420
435
|
};
|
|
@@ -1,12 +1,26 @@
|
|
|
1
|
-
import { useEffect } from 'react';
|
|
1
|
+
import { useEffect, useMemo } from 'react';
|
|
2
2
|
|
|
3
3
|
import { aiProviderSelectors, useAiInfraStore } from '@/store/aiInfra';
|
|
4
4
|
import { useGlobalStore } from '@/store/global';
|
|
5
5
|
import { systemStatusSelectors } from '@/store/global/selectors';
|
|
6
6
|
import { useImageStore } from '@/store/image';
|
|
7
|
+
import {
|
|
8
|
+
DEFAULT_AI_IMAGE_MODEL,
|
|
9
|
+
DEFAULT_AI_IMAGE_PROVIDER,
|
|
10
|
+
} from '@/store/image/slices/generationConfig/initialState';
|
|
7
11
|
import { useUserStore } from '@/store/user';
|
|
8
12
|
import { authSelectors } from '@/store/user/selectors';
|
|
9
13
|
|
|
14
|
+
const checkModelEnabled = (
|
|
15
|
+
enabledImageModelList: ReturnType<typeof aiProviderSelectors.enabledImageModelList>,
|
|
16
|
+
provider: string,
|
|
17
|
+
model: string,
|
|
18
|
+
) => {
|
|
19
|
+
return enabledImageModelList.some(
|
|
20
|
+
(p) => p.id === provider && p.children.some((m) => m.id === model),
|
|
21
|
+
);
|
|
22
|
+
};
|
|
23
|
+
|
|
10
24
|
export const useFetchAiImageConfig = () => {
|
|
11
25
|
const isStatusInit = useGlobalStore(systemStatusSelectors.isStatusInit);
|
|
12
26
|
const isInitAiProviderRuntimeState = useAiInfraStore(
|
|
@@ -29,16 +43,46 @@ export const useFetchAiImageConfig = () => {
|
|
|
29
43
|
const isInitializedImageConfig = useImageStore((s) => s.isInit);
|
|
30
44
|
const initializeImageConfig = useImageStore((s) => s.initializeImageConfig);
|
|
31
45
|
|
|
46
|
+
const enabledImageModelList = useAiInfraStore(aiProviderSelectors.enabledImageModelList);
|
|
47
|
+
|
|
48
|
+
// Determine which model/provider to use for initialization
|
|
49
|
+
const initParams = useMemo(() => {
|
|
50
|
+
// 1. Try lastSelected if enabled
|
|
51
|
+
if (
|
|
52
|
+
lastSelectedImageModel &&
|
|
53
|
+
lastSelectedImageProvider &&
|
|
54
|
+
checkModelEnabled(enabledImageModelList, lastSelectedImageProvider, lastSelectedImageModel)
|
|
55
|
+
) {
|
|
56
|
+
return { model: lastSelectedImageModel, provider: lastSelectedImageProvider };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 2. Try default model from any enabled provider (prefer default provider first)
|
|
60
|
+
if (
|
|
61
|
+
checkModelEnabled(enabledImageModelList, DEFAULT_AI_IMAGE_PROVIDER, DEFAULT_AI_IMAGE_MODEL)
|
|
62
|
+
) {
|
|
63
|
+
return { model: undefined, provider: undefined }; // Use initialState defaults
|
|
64
|
+
}
|
|
65
|
+
const providerWithDefaultModel = enabledImageModelList.find((p) =>
|
|
66
|
+
p.children.some((m) => m.id === DEFAULT_AI_IMAGE_MODEL),
|
|
67
|
+
);
|
|
68
|
+
if (providerWithDefaultModel) {
|
|
69
|
+
return { model: DEFAULT_AI_IMAGE_MODEL, provider: providerWithDefaultModel.id };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// 3. Fallback to first enabled model
|
|
73
|
+
const firstProvider = enabledImageModelList[0];
|
|
74
|
+
const firstModel = firstProvider?.children[0];
|
|
75
|
+
if (firstProvider && firstModel) {
|
|
76
|
+
return { model: firstModel.id, provider: firstProvider.id };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// No enabled models
|
|
80
|
+
return { model: undefined, provider: undefined };
|
|
81
|
+
}, [lastSelectedImageModel, lastSelectedImageProvider, enabledImageModelList]);
|
|
82
|
+
|
|
32
83
|
useEffect(() => {
|
|
33
84
|
if (!isInitializedImageConfig && isReadyForInit) {
|
|
34
|
-
initializeImageConfig(isLogin,
|
|
85
|
+
initializeImageConfig(isLogin, initParams.model, initParams.provider);
|
|
35
86
|
}
|
|
36
|
-
}, [
|
|
37
|
-
isReadyForInit,
|
|
38
|
-
isInitializedImageConfig,
|
|
39
|
-
isLogin,
|
|
40
|
-
lastSelectedImageModel,
|
|
41
|
-
lastSelectedImageProvider,
|
|
42
|
-
initializeImageConfig,
|
|
43
|
-
]);
|
|
87
|
+
}, [isReadyForInit, isInitializedImageConfig, isLogin, initParams, initializeImageConfig]);
|
|
44
88
|
};
|
|
@@ -66,7 +66,7 @@ const getVerificationKey = async () => {
|
|
|
66
66
|
|
|
67
67
|
/**
|
|
68
68
|
* Sign JWT for internal lambda → async calls
|
|
69
|
-
* Uses JWKS private key with
|
|
69
|
+
* Uses JWKS private key with configurable expiration (default: 30s)
|
|
70
70
|
* The JWT only proves the request is from lambda, payload is sent via LOBE_CHAT_AUTH_HEADER
|
|
71
71
|
*/
|
|
72
72
|
export const signInternalJWT = async (): Promise<string> => {
|
|
@@ -75,7 +75,7 @@ export const signInternalJWT = async (): Promise<string> => {
|
|
|
75
75
|
return new SignJWT({ purpose: INTERNAL_JWT_PURPOSE })
|
|
76
76
|
.setProtectedHeader({ alg: 'RS256', kid })
|
|
77
77
|
.setIssuedAt()
|
|
78
|
-
.setExpirationTime(
|
|
78
|
+
.setExpirationTime(authEnv.INTERNAL_JWT_EXPIRATION)
|
|
79
79
|
.sign(key);
|
|
80
80
|
};
|
|
81
81
|
|
|
@@ -4,13 +4,13 @@ import {
|
|
|
4
4
|
ModelProvider,
|
|
5
5
|
type RuntimeImageGenParams,
|
|
6
6
|
extractDefaultValues,
|
|
7
|
-
gptImage1ParamsSchema,
|
|
8
7
|
} from 'model-bank';
|
|
8
|
+
import { nanoBananaProParameters } from 'model-bank/google';
|
|
9
9
|
|
|
10
10
|
import { DEFAULT_IMAGE_CONFIG } from '@/const/settings';
|
|
11
11
|
|
|
12
|
-
export const DEFAULT_AI_IMAGE_PROVIDER = ModelProvider.
|
|
13
|
-
export const DEFAULT_AI_IMAGE_MODEL = '
|
|
12
|
+
export const DEFAULT_AI_IMAGE_PROVIDER = ModelProvider.Google;
|
|
13
|
+
export const DEFAULT_AI_IMAGE_MODEL = 'gemini-3-pro-image-preview:image';
|
|
14
14
|
|
|
15
15
|
export interface GenerationConfigState {
|
|
16
16
|
parameters: RuntimeImageGenParams;
|
|
@@ -30,14 +30,14 @@ export interface GenerationConfigState {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
export const DEFAULT_IMAGE_GENERATION_PARAMETERS: RuntimeImageGenParams =
|
|
33
|
-
extractDefaultValues(
|
|
33
|
+
extractDefaultValues(nanoBananaProParameters);
|
|
34
34
|
|
|
35
35
|
export const initialGenerationConfigState: GenerationConfigState = {
|
|
36
36
|
model: DEFAULT_AI_IMAGE_MODEL,
|
|
37
37
|
provider: DEFAULT_AI_IMAGE_PROVIDER,
|
|
38
38
|
imageNum: DEFAULT_IMAGE_CONFIG.defaultImageNum,
|
|
39
39
|
parameters: DEFAULT_IMAGE_GENERATION_PARAMETERS,
|
|
40
|
-
parametersSchema:
|
|
40
|
+
parametersSchema: nanoBananaProParameters,
|
|
41
41
|
isAspectRatioLocked: false,
|
|
42
42
|
activeAspectRatio: null,
|
|
43
43
|
isInit: false,
|
|
@@ -6,6 +6,7 @@ import { ImageStore } from '@/store/image';
|
|
|
6
6
|
import { initialState } from '@/store/image/initialState';
|
|
7
7
|
import { merge } from '@/utils/merge';
|
|
8
8
|
|
|
9
|
+
import { DEFAULT_AI_IMAGE_MODEL, DEFAULT_AI_IMAGE_PROVIDER } from './initialState';
|
|
9
10
|
import { imageGenerationConfigSelectors } from './selectors';
|
|
10
11
|
|
|
11
12
|
// Mock external dependencies
|
|
@@ -57,7 +58,7 @@ describe('imageGenerationConfigSelectors', () => {
|
|
|
57
58
|
|
|
58
59
|
it('should return the default model from initial state', () => {
|
|
59
60
|
const result = imageGenerationConfigSelectors.model(initialStore);
|
|
60
|
-
expect(result).toBe(
|
|
61
|
+
expect(result).toBe(DEFAULT_AI_IMAGE_MODEL);
|
|
61
62
|
});
|
|
62
63
|
});
|
|
63
64
|
|
|
@@ -70,7 +71,7 @@ describe('imageGenerationConfigSelectors', () => {
|
|
|
70
71
|
|
|
71
72
|
it('should return the default provider from initial state', () => {
|
|
72
73
|
const result = imageGenerationConfigSelectors.provider(initialStore);
|
|
73
|
-
expect(result).toBe(
|
|
74
|
+
expect(result).toBe(DEFAULT_AI_IMAGE_PROVIDER);
|
|
74
75
|
});
|
|
75
76
|
});
|
|
76
77
|
|
|
@@ -98,7 +99,10 @@ describe('imageGenerationConfigSelectors', () => {
|
|
|
98
99
|
it('should return the current parameters', () => {
|
|
99
100
|
const state = merge(initialStore, { parameters: testParameters });
|
|
100
101
|
const result = imageGenerationConfigSelectors.parameters(state);
|
|
101
|
-
|
|
102
|
+
// merge does deep merge, so result contains both default and test values
|
|
103
|
+
expect(result.prompt).toBe(testParameters.prompt);
|
|
104
|
+
expect(result.size).toBe(testParameters.size);
|
|
105
|
+
expect(result.imageUrls).toEqual(testParameters.imageUrls);
|
|
102
106
|
});
|
|
103
107
|
|
|
104
108
|
it('should return the default parameters from initial state', () => {
|
|
@@ -120,7 +124,10 @@ describe('imageGenerationConfigSelectors', () => {
|
|
|
120
124
|
it('should return the current parametersSchema', () => {
|
|
121
125
|
const state = merge(initialStore, { parametersSchema: testModelSchema });
|
|
122
126
|
const result = imageGenerationConfigSelectors.parametersSchema(state);
|
|
123
|
-
|
|
127
|
+
// merge does deep merge, so result contains both default and test values
|
|
128
|
+
expect(result.prompt).toEqual(testModelSchema.prompt);
|
|
129
|
+
expect(result.size).toEqual(testModelSchema.size);
|
|
130
|
+
expect(result.imageUrls).toEqual(testModelSchema.imageUrls);
|
|
124
131
|
});
|
|
125
132
|
|
|
126
133
|
it('should return default parametersSchema when not explicitly overridden', () => {
|
package/vitest.config.mts
CHANGED
|
@@ -2,6 +2,10 @@ import { dirname, join, resolve } from 'node:path';
|
|
|
2
2
|
import { coverageConfigDefaults, defineConfig } from 'vitest/config';
|
|
3
3
|
|
|
4
4
|
export default defineConfig({
|
|
5
|
+
optimizeDeps: {
|
|
6
|
+
exclude: ['crypto', 'util', 'tty'],
|
|
7
|
+
include: ['@lobehub/tts'],
|
|
8
|
+
},
|
|
5
9
|
plugins: [
|
|
6
10
|
/**
|
|
7
11
|
* @lobehub/fluent-emoji@4.0.0 ships `es/FluentEmoji/style.js` but its `es/FluentEmoji/index.js`
|
|
@@ -27,16 +31,13 @@ export default defineConfig({
|
|
|
27
31
|
id.endsWith('/FluentEmoji/style/index.js') ||
|
|
28
32
|
id.endsWith('/FluentEmoji/style/index.js?');
|
|
29
33
|
|
|
30
|
-
if (isFluentEmojiEntry && isMissingStyleIndex)
|
|
34
|
+
if (isFluentEmojiEntry && isMissingStyleIndex)
|
|
35
|
+
return resolve(dirname(importer), 'style.js');
|
|
31
36
|
|
|
32
37
|
return null;
|
|
33
38
|
},
|
|
34
39
|
},
|
|
35
40
|
],
|
|
36
|
-
optimizeDeps: {
|
|
37
|
-
exclude: ['crypto', 'util', 'tty'],
|
|
38
|
-
include: ['@lobehub/tts'],
|
|
39
|
-
},
|
|
40
41
|
test: {
|
|
41
42
|
alias: {
|
|
42
43
|
/* eslint-disable sort-keys-fix/sort-keys-fix */
|
|
@@ -77,11 +78,14 @@ export default defineConfig({
|
|
|
77
78
|
environment: 'happy-dom',
|
|
78
79
|
exclude: [
|
|
79
80
|
'**/node_modules/**',
|
|
81
|
+
'**/.*/**',
|
|
80
82
|
'**/dist/**',
|
|
81
83
|
'**/build/**',
|
|
82
84
|
'**/tmp/**',
|
|
83
85
|
'**/temp/**',
|
|
84
|
-
'
|
|
86
|
+
'**/docs/**',
|
|
87
|
+
'**/locales/**',
|
|
88
|
+
'**/public/**',
|
|
85
89
|
'**/apps/desktop/**',
|
|
86
90
|
'**/apps/mobile/**',
|
|
87
91
|
'**/packages/**',
|