@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.
Files changed (32) hide show
  1. package/.github/workflows/bundle-analyzer.yml +1 -1
  2. package/.github/workflows/e2e.yml +67 -52
  3. package/.github/workflows/manual-build-desktop.yml +5 -5
  4. package/.github/workflows/pr-build-desktop.yml +4 -4
  5. package/.github/workflows/pr-build-docker.yml +2 -2
  6. package/.github/workflows/release-desktop-beta.yml +4 -4
  7. package/.github/workflows/release-docker.yml +2 -2
  8. package/.github/workflows/test.yml +44 -7
  9. package/CHANGELOG.md +25 -0
  10. package/changelog/v1.json +5 -0
  11. package/docs/self-hosting/environment-variables/auth.mdx +7 -0
  12. package/docs/self-hosting/environment-variables/auth.zh-CN.mdx +7 -0
  13. package/package.json +1 -1
  14. package/packages/business/config/src/llm.ts +6 -1
  15. package/packages/const/src/settings/image.ts +1 -1
  16. package/packages/model-bank/src/aiModels/azure.ts +2 -2
  17. package/packages/model-bank/src/aiModels/google.ts +1 -0
  18. package/packages/model-bank/src/aiModels/lobehub.ts +33 -13
  19. package/packages/model-bank/src/aiModels/openai.ts +21 -4
  20. package/packages/model-runtime/src/core/openaiCompatibleFactory/createImage.ts +4 -1
  21. package/packages/model-runtime/src/providers/openai/__snapshots__/index.test.ts.snap +1 -1
  22. package/packages/ssrf-safe-fetch/index.test.ts +5 -34
  23. package/packages/ssrf-safe-fetch/index.ts +12 -2
  24. package/src/app/[variants]/(main)/image/_layout/ConfigPanel/components/MultiImagesUpload/index.tsx +3 -3
  25. package/src/app/[variants]/(main)/image/features/GenerationFeed/index.tsx +3 -10
  26. package/src/app/[variants]/(main)/image/index.tsx +1 -1
  27. package/src/envs/auth.ts +15 -0
  28. package/src/hooks/useFetchAiImageConfig.ts +54 -10
  29. package/src/libs/trpc/utils/internalJwt.ts +2 -2
  30. package/src/store/image/slices/generationConfig/initialState.ts +5 -5
  31. package/src/store/image/slices/generationConfig/selectors.test.ts +11 -4
  32. 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@v4
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
- group: e2e-${{ github.ref }}
11
- cancel-in-progress: true
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
- e2e:
28
- name: Test Web App
29
- runs-on: ubuntu-latest
30
- services:
31
- postgres:
32
- image: paradedb/paradedb:latest
33
- env:
34
- POSTGRES_PASSWORD: postgres
35
- options: >-
36
- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
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
- ports:
39
- - 5432:5432
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
- timeout-minutes: 30
42
- steps:
43
- - name: Checkout
44
- uses: actions/checkout@v6
56
+ timeout-minutes: 30
57
+ steps:
58
+ - name: Checkout
59
+ uses: actions/checkout@v6
45
60
 
46
- - name: Setup Bun
47
- uses: oven-sh/setup-bun@v2
48
- with:
49
- bun-version: latest
61
+ - name: Setup Bun
62
+ uses: oven-sh/setup-bun@v2
63
+ with:
64
+ bun-version: latest
50
65
 
51
- - name: Install dependencies (bun)
52
- run: bun install
66
+ - name: Install dependencies (bun)
67
+ run: bun install
53
68
 
54
- - name: Install Playwright browsers (with system deps)
55
- run: bunx playwright install --with-deps chromium
69
+ - name: Install Playwright browsers (with system deps)
70
+ run: bunx playwright install --with-deps chromium
56
71
 
57
- - name: Run database migrations
58
- run: bun run db:migrate
72
+ - name: Run database migrations
73
+ run: bun run db:migrate
59
74
 
60
- - name: Build application
61
- run: bun run build
62
- env:
63
- SKIP_LINT: '1'
75
+ - name: Build application
76
+ run: bun run build
77
+ env:
78
+ SKIP_LINT: '1'
64
79
 
65
- - name: Run E2E tests
66
- run: bun run e2e
80
+ - name: Run E2E tests
81
+ run: bun run e2e
67
82
 
68
- - name: Upload Cucumber HTML report (on failure)
69
- if: failure()
70
- uses: actions/upload-artifact@v5
71
- with:
72
- name: cucumber-report
73
- path: e2e/reports
74
- if-no-files-found: ignore
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
- - name: Upload screenshots (on failure)
77
- if: failure()
78
- uses: actions/upload-artifact@v5
79
- with:
80
- name: test-screenshots
81
- path: e2e/screenshots
82
- if-no-files-found: ignore
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@v5
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@v5
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@v5
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@v6
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@v5
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@v5
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@v6
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@v5
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@v6
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@v5
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@v6
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@v5
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@v6
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@v5
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@v6
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@v5
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@v6
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-website:
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
- name: Test Website
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: Test App Coverage
105
- run: bun run test-app:coverage
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
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#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
@@ -1,4 +1,9 @@
1
1
  [
2
+ {
3
+ "children": {},
4
+ "date": "2026-01-07",
5
+ "version": "2.0.0-next.233"
6
+ },
2
7
  {
3
8
  "children": {
4
9
  "fixes": [
@@ -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.232",
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: {
@@ -4,5 +4,5 @@ export const MIN_DEFAULT_IMAGE_NUM = 1;
4
4
  export const MAX_DEFAULT_IMAGE_NUM = 20;
5
5
 
6
6
  export const DEFAULT_IMAGE_CONFIG: UserImageConfig = {
7
- defaultImageNum: 4,
7
+ defaultImageNum: 2,
8
8
  };
@@ -218,7 +218,8 @@ const azureChatModels: AIChatModelCard[] = [
218
218
  deploymentName: 'gpt-4.1',
219
219
  },
220
220
  contextWindowTokens: 1_047_576,
221
- description: 'GPT-4.1 is our flagship model for complex tasks and cross-domain problem solving.',
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: false,
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: false,
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: false,
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: false,
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
- ...(isImageEdit && model === 'gpt-image-1' ? { input_fidelity: 'high' } : {}),
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": false,
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.objectContaining({
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.objectContaining({
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.objectContaining({
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 properly configured for HTTPS
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.objectContaining({
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 { RequestFilteringAgentOptions, useAgent as ssrfAgent } from 'request-filtering-agent';
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: ssrfAgent(url, agentOptions),
58
+ agent: (parsedURL: URL) => (parsedURL.protocol === 'https:' ? httpsAgent : httpAgent),
49
59
  } as any);
50
60
 
51
61
  // Convert node-fetch Response to standard Response
@@ -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, lastSelectedImageModel, lastSelectedImageProvider);
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 short expiration (3s)
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('3s')
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.OpenAI;
13
- export const DEFAULT_AI_IMAGE_MODEL = 'gpt-image-1';
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(gptImage1ParamsSchema);
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: gptImage1ParamsSchema,
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('gpt-image-1'); // Default model from initialState
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('openai'); // Default provider from initialState
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
- expect(result).toEqual(testParameters);
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
- expect(result).toEqual(testModelSchema);
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) return resolve(dirname(importer), 'style.js');
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
- '**/.temp/**',
86
+ '**/docs/**',
87
+ '**/locales/**',
88
+ '**/public/**',
85
89
  '**/apps/desktop/**',
86
90
  '**/apps/mobile/**',
87
91
  '**/packages/**',