@lobehub/chat 1.114.5 → 1.115.0
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/.cursor/rules/project-introduce.mdc +1 -15
- package/.cursor/rules/project-structure.mdc +227 -0
- package/.cursor/rules/testing-guide/db-model-test.mdc +5 -3
- package/.cursor/rules/testing-guide/testing-guide.mdc +153 -168
- package/.github/workflows/claude.yml +1 -1
- package/.github/workflows/test.yml +9 -0
- package/.prettierignore +0 -1
- package/.vscode/settings.json +86 -80
- package/CHANGELOG.md +50 -0
- package/CLAUDE.md +11 -27
- package/changelog/v1.json +10 -0
- package/docs/development/basic/feature-development.mdx +1 -1
- package/docs/development/basic/feature-development.zh-CN.mdx +1 -1
- package/package.json +5 -5
- package/packages/const/src/image.ts +28 -0
- package/packages/const/src/index.ts +1 -0
- package/packages/database/package.json +4 -2
- package/packages/database/src/repositories/aiInfra/index.ts +1 -1
- package/packages/database/tests/setup-db.ts +3 -0
- package/packages/database/vitest.config.mts +33 -0
- package/packages/model-runtime/src/utils/modelParse.ts +1 -1
- package/packages/utils/src/client/imageDimensions.test.ts +95 -0
- package/packages/utils/src/client/imageDimensions.ts +54 -0
- package/packages/utils/src/number.test.ts +3 -1
- package/packages/utils/src/number.ts +1 -2
- package/src/app/[variants]/(main)/files/[id]/page.tsx +0 -2
- package/src/app/[variants]/(main)/image/@menu/components/SeedNumberInput/index.tsx +1 -1
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/DimensionControlGroup.tsx +0 -1
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/ImageUpload.tsx +206 -185
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/ImageUrl.tsx +16 -4
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/ImageUrlsUpload.tsx +52 -3
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/MultiImagesUpload/ImageManageModal.tsx +33 -19
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/MultiImagesUpload/index.tsx +40 -12
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/hooks/useAutoDimensions.ts +56 -0
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/hooks/useUploadFilesValidation.ts +77 -0
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/index.tsx +82 -5
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/utils/__tests__/dimensionConstraints.test.ts +235 -0
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/utils/__tests__/imageValidation.test.ts +401 -0
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/utils/dimensionConstraints.ts +54 -0
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/utils/imageValidation.ts +117 -0
- package/src/app/[variants]/(main)/image/@topic/features/Topics/TopicItem.tsx +3 -1
- package/src/app/[variants]/(main)/image/@topic/features/Topics/TopicList.tsx +15 -2
- package/src/app/[variants]/(main)/image/features/GenerationFeed/GenerationItem/utils.ts +5 -4
- package/src/libs/standard-parameters/index.ts +4 -1
- package/src/locales/default/components.ts +8 -0
- package/src/server/services/generation/index.ts +1 -1
- package/src/store/aiInfra/slices/aiProvider/__tests__/action.test.ts +29 -29
- package/src/store/aiInfra/slices/aiProvider/action.ts +80 -36
- package/src/store/chat/slices/builtinTool/actions/dalle.test.ts +20 -13
- package/src/store/file/slices/upload/action.ts +18 -7
- package/src/store/image/slices/generationConfig/hooks.ts +11 -1
- package/tsconfig.json +1 -10
- package/packages/const/src/imageGeneration.ts +0 -16
- package/src/app/(backend)/trpc/desktop/[trpc]/route.ts +0 -26
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/AspectRatioSelect.tsx +0 -24
- package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/SizeSliderInput.tsx +0 -15
- package/src/app/[variants]/(main)/image/@topic/features/Topics/TopicItemContainer.tsx +0 -91
- package/src/app/desktop/devtools/page.tsx +0 -89
- package/src/app/desktop/layout.tsx +0 -31
- /package/apps/desktop/{vitest.config.ts → vitest.config.mts} +0 -0
- /package/packages/database/{vitest.config.ts → vitest.config.server.mts} +0 -0
- /package/packages/electron-server-ipc/{vitest.config.ts → vitest.config.mts} +0 -0
- /package/packages/file-loaders/{vitest.config.ts → vitest.config.mts} +0 -0
- /package/packages/model-runtime/{vitest.config.ts → vitest.config.mts} +0 -0
- /package/packages/prompts/{vitest.config.ts → vitest.config.mts} +0 -0
- /package/packages/utils/{vitest.config.ts → vitest.config.mts} +0 -0
- /package/packages/web-crawler/{vitest.config.ts → vitest.config.mts} +0 -0
- /package/{vitest.config.ts → vitest.config.mts} +0 -0
package/.vscode/settings.json
CHANGED
@@ -1,89 +1,95 @@
|
|
1
1
|
{
|
2
|
-
|
3
|
-
|
4
|
-
"
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
"
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
"
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
"
|
24
|
-
|
25
|
-
|
26
|
-
|
2
|
+
"editor.codeActionsOnSave": {
|
3
|
+
"source.addMissingImports": "explicit",
|
4
|
+
"source.fixAll.eslint": "explicit",
|
5
|
+
"source.fixAll.stylelint": "explicit"
|
6
|
+
},
|
7
|
+
"editor.formatOnSave": true,
|
8
|
+
// don't show errors, but fix when save and git pre commit
|
9
|
+
"eslint.rules.customizations": [
|
10
|
+
{ "rule": "import/order", "severity": "off" },
|
11
|
+
{ "rule": "prettier/prettier", "severity": "off" },
|
12
|
+
{ "rule": "react/jsx-sort-props", "severity": "off" },
|
13
|
+
{ "rule": "sort-keys-fix/sort-keys-fix", "severity": "off" },
|
14
|
+
{ "rule": "typescript-sort-keys/interface", "severity": "off" }
|
15
|
+
],
|
16
|
+
"eslint.validate": [
|
17
|
+
"javascript",
|
18
|
+
"javascriptreact",
|
19
|
+
"typescript",
|
20
|
+
"typescriptreact",
|
21
|
+
"markdown",
|
22
|
+
// support mdx
|
23
|
+
"mdx"
|
24
|
+
],
|
25
|
+
"npm.packageManager": "pnpm",
|
26
|
+
"search.exclude": {
|
27
|
+
"**/node_modules": true,
|
28
|
+
// useless to search this big folder
|
29
|
+
"locales": true
|
30
|
+
},
|
31
|
+
"stylelint.validate": [
|
32
|
+
"css",
|
33
|
+
"postcss",
|
34
|
+
// make stylelint work with tsx antd-style css template string
|
35
|
+
"typescriptreact"
|
36
|
+
],
|
37
|
+
"vitest.maximumConfigs": 10,
|
38
|
+
"workbench.editor.customLabels.patterns": {
|
39
|
+
"**/app/**/[[]*[]]/[[]*[]]/page.tsx": "${dirname(2)}/${dirname(1)}/${dirname} • page component",
|
40
|
+
"**/app/**/[[]*[]]/page.tsx": "${dirname(1)}/${dirname} • page component",
|
41
|
+
"**/app/**/page.tsx": "${dirname} • page component",
|
27
42
|
|
28
|
-
|
29
|
-
|
30
|
-
|
43
|
+
"**/app/**/[[]*[]]/[[]*[]]/layout.tsx": "${dirname(2)}/${dirname(1)}/${dirname} • page layout",
|
44
|
+
"**/app/**/[[]*[]]/layout.tsx": "${dirname(1)}/${dirname} • page layout",
|
45
|
+
"**/app/**/layout.tsx": "${dirname} • page layout",
|
31
46
|
|
32
|
-
|
33
|
-
|
34
|
-
|
47
|
+
"**/app/**/[[]*[]]/[[]*[]]/default.tsx": "${dirname(2)}/${dirname(1)}/${dirname} • slot default",
|
48
|
+
"**/app/**/[[]*[]]/default.tsx": "${dirname(1)}/${dirname} • slot default",
|
49
|
+
"**/app/**/default.tsx": "${dirname} • slot default",
|
35
50
|
|
36
|
-
|
37
|
-
|
38
|
-
|
51
|
+
"**/app/**/[[]*[]]/[[]*[]]/error.tsx": "${dirname(2)}/${dirname(1)}/${dirname} • error component",
|
52
|
+
"**/app/**/[[]*[]]/error.tsx": "${dirname(1)}/${dirname} • error component",
|
53
|
+
"**/app/**/error.tsx": "${dirname} • error component",
|
39
54
|
|
40
|
-
|
41
|
-
|
42
|
-
|
55
|
+
"**/app/**/[[]*[]]/[[]*[]]/loading.tsx": "${dirname(2)}/${dirname(1)}/${dirname} • loading component",
|
56
|
+
"**/app/**/[[]*[]]/loading.tsx": "${dirname(1)}/${dirname} • loading component",
|
57
|
+
"**/app/**/loading.tsx": "${dirname} • loading component",
|
43
58
|
|
44
|
-
|
45
|
-
|
59
|
+
"**/src/**/route.ts": "${dirname(1)}/${dirname} • route",
|
60
|
+
"**/src/**/index.tsx": "${dirname} • component",
|
46
61
|
|
47
|
-
|
48
|
-
|
49
|
-
|
62
|
+
"**/src/database/repositories/*/index.ts": "${dirname} • db repository",
|
63
|
+
"**/src/database/models/*.ts": "${filename} • db model",
|
64
|
+
"**/src/database/schemas/*.ts": "${filename} • db schema",
|
50
65
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
"**/src/store/*/action.ts": "${dirname} • action",
|
56
|
-
"**/src/store/*/slices/*/action.ts": "${dirname(2)}/${dirname} • action",
|
57
|
-
"**/src/store/*/slices/*/actions/*.ts": "${dirname(1)}/${dirname}/${filename} • action",
|
58
|
-
|
59
|
-
"**/src/store/*/initialState.ts": "${dirname} • state",
|
60
|
-
"**/src/store/*/slices/*/initialState.ts": "${dirname(2)}/${dirname} • state",
|
61
|
-
|
62
|
-
"**/src/store/*/selectors.ts": "${dirname} • selectors",
|
63
|
-
"**/src/store/*/slices/*/selectors.ts": "${dirname(2)}/${dirname} • selectors",
|
64
|
-
|
65
|
-
"**/src/store/*/reducer.ts": "${dirname} • reducer",
|
66
|
-
"**/src/store/*/slices/*/reducer.ts": "${dirname(2)}/${dirname} • reducer",
|
67
|
-
|
68
|
-
"**/src/config/modelProviders/*.ts": "${filename} • provider",
|
69
|
-
"**/src/config/aiModels/*.ts": "${filename} • model",
|
70
|
-
"**/src/config/paramsSchemas/*/*.json": "${dirname(1)}/${filename} • params",
|
71
|
-
"**/packages/model-runtime/src/*/index.ts": "${dirname} • runtime",
|
72
|
-
|
73
|
-
"**/src/server/services/*/index.ts": "${dirname} • server/service",
|
74
|
-
"**/src/server/routers/lambda/*.ts": "${filename} • lambda",
|
75
|
-
"**/src/server/routers/async/*.ts": "${filename} • async",
|
76
|
-
"**/src/server/routers/edge/*.ts": "${filename} • edge",
|
66
|
+
"**/src/services/*.ts": "${filename} • service",
|
67
|
+
"**/src/services/*/client.ts": "${dirname} • client service",
|
68
|
+
"**/src/services/*/server.ts": "${dirname} • server service",
|
77
69
|
|
78
|
-
|
79
|
-
},
|
80
|
-
"
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
}
|
70
|
+
"**/src/store/*/action.ts": "${dirname} • action",
|
71
|
+
"**/src/store/*/slices/*/action.ts": "${dirname(2)}/${dirname} • action",
|
72
|
+
"**/src/store/*/slices/*/actions/*.ts": "${dirname(1)}/${dirname}/${filename} • action",
|
73
|
+
|
74
|
+
"**/src/store/*/initialState.ts": "${dirname} • state",
|
75
|
+
"**/src/store/*/slices/*/initialState.ts": "${dirname(2)}/${dirname} • state",
|
76
|
+
|
77
|
+
"**/src/store/*/selectors.ts": "${dirname} • selectors",
|
78
|
+
"**/src/store/*/slices/*/selectors.ts": "${dirname(2)}/${dirname} • selectors",
|
79
|
+
|
80
|
+
"**/src/store/*/reducer.ts": "${dirname} • reducer",
|
81
|
+
"**/src/store/*/slices/*/reducer.ts": "${dirname(2)}/${dirname} • reducer",
|
82
|
+
|
83
|
+
"**/src/config/modelProviders/*.ts": "${filename} • provider",
|
84
|
+
"**/src/config/aiModels/*.ts": "${filename} • model",
|
85
|
+
"**/src/config/paramsSchemas/*/*.json": "${dirname(1)}/${filename} • params",
|
86
|
+
"**/packages/model-runtime/src/*/index.ts": "${dirname} • runtime",
|
87
|
+
|
88
|
+
"**/src/server/services/*/index.ts": "${dirname} • server/service",
|
89
|
+
"**/src/server/routers/lambda/*.ts": "${filename} • lambda",
|
90
|
+
"**/src/server/routers/async/*.ts": "${filename} • async",
|
91
|
+
"**/src/server/routers/edge/*.ts": "${filename} • edge",
|
92
|
+
|
93
|
+
"**/src/locales/default/*.ts": "${filename} • locale"
|
94
|
+
}
|
95
|
+
}
|
package/CHANGELOG.md
CHANGED
@@ -2,6 +2,56 @@
|
|
2
2
|
|
3
3
|
# Changelog
|
4
4
|
|
5
|
+
## [Version 1.115.0](https://github.com/lobehub/lobe-chat/compare/v1.114.6...v1.115.0)
|
6
|
+
|
7
|
+
<sup>Released on **2025-08-26**</sup>
|
8
|
+
|
9
|
+
#### ✨ Features
|
10
|
+
|
11
|
+
- **image**: Polish ai image.
|
12
|
+
|
13
|
+
<br/>
|
14
|
+
|
15
|
+
<details>
|
16
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
17
|
+
|
18
|
+
#### What's improved
|
19
|
+
|
20
|
+
- **image**: Polish ai image, closes [#8915](https://github.com/lobehub/lobe-chat/issues/8915) ([0efe28d](https://github.com/lobehub/lobe-chat/commit/0efe28d))
|
21
|
+
|
22
|
+
</details>
|
23
|
+
|
24
|
+
<div align="right">
|
25
|
+
|
26
|
+
[](#readme-top)
|
27
|
+
|
28
|
+
</div>
|
29
|
+
|
30
|
+
### [Version 1.114.6](https://github.com/lobehub/lobe-chat/compare/v1.114.5...v1.114.6)
|
31
|
+
|
32
|
+
<sup>Released on **2025-08-22**</sup>
|
33
|
+
|
34
|
+
#### 🐛 Bug Fixes
|
35
|
+
|
36
|
+
- **files**: Remove force-static rendering to enable session access.
|
37
|
+
|
38
|
+
<br/>
|
39
|
+
|
40
|
+
<details>
|
41
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
42
|
+
|
43
|
+
#### What's fixed
|
44
|
+
|
45
|
+
- **files**: Remove force-static rendering to enable session access, closes [#8900](https://github.com/lobehub/lobe-chat/issues/8900) ([6100d21](https://github.com/lobehub/lobe-chat/commit/6100d21))
|
46
|
+
|
47
|
+
</details>
|
48
|
+
|
49
|
+
<div align="right">
|
50
|
+
|
51
|
+
[](#readme-top)
|
52
|
+
|
53
|
+
</div>
|
54
|
+
|
5
55
|
### [Version 1.114.5](https://github.com/lobehub/lobe-chat/compare/v1.114.4...v1.114.5)
|
6
56
|
|
7
57
|
<sup>Released on **2025-08-22**</sup>
|
package/CLAUDE.md
CHANGED
@@ -2,39 +2,20 @@
|
|
2
2
|
|
3
3
|
This document serves as a shared guideline for all team members when using Claude Code in this repository.
|
4
4
|
|
5
|
-
##
|
5
|
+
## Tech Stack
|
6
6
|
|
7
|
-
|
8
|
-
- Please store all temporary scripts (such as migration and refactoring scripts) in the `docs/.local/` directory; the contents of this folder will not be committed.
|
7
|
+
read @.cursor/rules/project-introduce.mdc
|
9
8
|
|
10
|
-
##
|
9
|
+
## Directory Structure
|
11
10
|
|
12
|
-
read @.cursor/rules/project-
|
13
|
-
|
14
|
-
### Directory Structure
|
15
|
-
|
16
|
-
```plaintext
|
17
|
-
src/
|
18
|
-
├── app/ # Next.js App Router
|
19
|
-
├── features/ # Feature-based UI components
|
20
|
-
├── store/ # Zustand state stores
|
21
|
-
├── services/ # Client services (tRPC/Model calls)
|
22
|
-
├── server/ # Server-side (tRPC routers, services)
|
23
|
-
├── database/ # Schemas, models, repositories
|
24
|
-
├── libs/ # External library integrations
|
25
|
-
```
|
26
|
-
|
27
|
-
### Data Flow
|
28
|
-
|
29
|
-
- **Client DB Version**: UI → Zustand → Service → Model → PGLite
|
30
|
-
- **Server DB Version**: UI → Zustand → Service → tRPC → Repository/Model → PostgreSQL
|
11
|
+
read @.cursor/rules/project-structure.mdc
|
31
12
|
|
32
13
|
## Development
|
33
14
|
|
34
15
|
### Git Workflow
|
35
16
|
|
36
|
-
- use rebase for git pull
|
37
|
-
- git commit message should prefix with gitmoji
|
17
|
+
- use rebase for git pull
|
18
|
+
- git commit message should prefix with gitmoji
|
38
19
|
- git branch name format example: tj/feat/feature-name
|
39
20
|
- use .github/PULL_REQUEST_TEMPLATE.md to generate pull request description
|
40
21
|
|
@@ -59,10 +40,13 @@ see @.cursor/rules/typescript.mdc
|
|
59
40
|
Testing work follows the Rule-Aware Task Execution system above.
|
60
41
|
|
61
42
|
- **Required Rule**: `testing-guide/testing-guide.mdc`
|
62
|
-
- **Command**:
|
43
|
+
- **Command**:
|
44
|
+
- web: `bunx vitest run --silent='passed-only' '[file-path-pattern]'`
|
45
|
+
- packages(eg: database): `cd packages/database && bunx vitest run --silent='passed-only' '[file-path-pattern]'`
|
63
46
|
|
64
47
|
**Important**:
|
65
48
|
|
49
|
+
- wrapped the file path in single quotes to avoid shell expansion
|
66
50
|
- Never run `bun run test` etc to run tests, this will run all tests and cost about 10mins
|
67
51
|
- If try to fix the same test twice, but still failed, stop and ask for help.
|
68
52
|
|
@@ -96,7 +80,7 @@ Some useful rules of this project. Read them when needed.
|
|
96
80
|
|
97
81
|
- `zustand-slice-organization.mdc` - Store organization
|
98
82
|
- `zustand-action-patterns.mdc` - Action patterns
|
99
|
-
- `packages/react-layout-kit.mdc` -
|
83
|
+
- `packages/react-layout-kit.mdc` - flex layout components usage
|
100
84
|
|
101
85
|
**Testing & Quality**
|
102
86
|
|
package/changelog/v1.json
CHANGED
@@ -456,7 +456,7 @@ The project uses vitest for unit testing.
|
|
456
456
|
|
457
457
|
Since our two new configuration fields are both optional, theoretically you could pass the tests without updating them. However, since we added the `openingQuestions` field to the `DEFAULT_AGENT_CONFIG` mentioned earlier, this causes many tests to calculate configurations that include this field, so we still need to update some test snapshots.
|
458
458
|
|
459
|
-
For the current scenario, I recommend running the tests locally to see which tests fail, and then update them as needed. For example, for the test file `src/store/agent/slices/chat/selectors/agent.test.ts`, you need to run `
|
459
|
+
For the current scenario, I recommend running the tests locally to see which tests fail, and then update them as needed. For example, for the test file `src/store/agent/slices/chat/selectors/agent.test.ts`, you need to run `bunx vitest -u src/store/agent/slices/chat/selectors/agent.test.ts` to update the snapshot.
|
460
460
|
|
461
461
|
## Summary
|
462
462
|
|
@@ -456,7 +456,7 @@ export default WelcomeMessage;
|
|
456
456
|
|
457
457
|
由于我们目前两个新的配置字段都是可选的,所以理论上你不更新测试也能跑通,不过由于我们把前面提到的默认配置 `DEFAULT_AGENT_CONFIG` 增加了 `openingQuestions` 字段,这导致很多测试计算出的配置都是有这个字段的,因此我们还是需要更新一部分测试的快照。
|
458
458
|
|
459
|
-
对于当前这个场景,我建议是本地直接跑下测试,看哪些测试失败了,针对需要更新,例如测试文件 `src/store/agent/slices/chat/selectors/agent.test.ts` 需要执行一下 `
|
459
|
+
对于当前这个场景,我建议是本地直接跑下测试,看哪些测试失败了,针对需要更新,例如测试文件 `src/store/agent/slices/chat/selectors/agent.test.ts` 需要执行一下 `bunx vitest -u src/store/agent/slices/chat/selectors/agent.test.ts` 更新快照。
|
460
460
|
|
461
461
|
## 总结
|
462
462
|
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@lobehub/chat",
|
3
|
-
"version": "1.
|
3
|
+
"version": "1.115.0",
|
4
4
|
"description": "Lobe Chat - an open-source, high-performance chatbot 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",
|
@@ -71,8 +71,8 @@
|
|
71
71
|
"start": "next start -p 3210",
|
72
72
|
"stylelint": "stylelint \"src/**/*.{js,jsx,ts,tsx}\" --fix",
|
73
73
|
"test": "npm run test-app && npm run test-server",
|
74
|
-
"test-app": "vitest run
|
75
|
-
"test-app:coverage": "vitest run --
|
74
|
+
"test-app": "vitest run",
|
75
|
+
"test-app:coverage": "vitest run --coverage",
|
76
76
|
"test:update": "vitest -u",
|
77
77
|
"type-check": "tsgo --noEmit",
|
78
78
|
"webhook:ngrok": "ngrok http http://localhost:3011",
|
@@ -206,7 +206,7 @@
|
|
206
206
|
"langfuse": "^3.38.4",
|
207
207
|
"langfuse-core": "^3.38.4",
|
208
208
|
"lodash-es": "^4.17.21",
|
209
|
-
"lucide-react": "^0.
|
209
|
+
"lucide-react": "^0.541.0",
|
210
210
|
"mammoth": "^1.10.0",
|
211
211
|
"markdown-to-txt": "^2.0.1",
|
212
212
|
"mdast-util-to-markdown": "^2.1.2",
|
@@ -364,7 +364,7 @@
|
|
364
364
|
"vite": "^5.4.19",
|
365
365
|
"vitest": "^3.2.4"
|
366
366
|
},
|
367
|
-
"packageManager": "pnpm@10.
|
367
|
+
"packageManager": "pnpm@10.15.0",
|
368
368
|
"publishConfig": {
|
369
369
|
"access": "public",
|
370
370
|
"registry": "https://registry.npmjs.org"
|
@@ -12,3 +12,31 @@ export const PRESET_ASPECT_RATIOS = [
|
|
12
12
|
'3:2', // 经典照片比例横屏
|
13
13
|
'2:3', // 经典照片比例竖屏
|
14
14
|
];
|
15
|
+
|
16
|
+
/**
|
17
|
+
* Image generation and processing configuration constants
|
18
|
+
*/
|
19
|
+
export const IMAGE_GENERATION_CONFIG = {
|
20
|
+
/**
|
21
|
+
* Maximum cover image size in pixels (longest edge)
|
22
|
+
* Used for generating cover images from source images
|
23
|
+
*/
|
24
|
+
COVER_MAX_SIZE: 256,
|
25
|
+
|
26
|
+
/**
|
27
|
+
* Maximum thumbnail size in pixels (longest edge)
|
28
|
+
* Used for generating thumbnail images from original images
|
29
|
+
*/
|
30
|
+
THUMBNAIL_MAX_SIZE: 512,
|
31
|
+
} as const;
|
32
|
+
|
33
|
+
/**
|
34
|
+
* Default dimension constraints for image upload auto-setting
|
35
|
+
* Used when model schema doesn't provide min/max values
|
36
|
+
*/
|
37
|
+
export const DEFAULT_DIMENSION_CONSTRAINTS = {
|
38
|
+
MAX_SIZE: 1024,
|
39
|
+
MIN_SIZE: 512,
|
40
|
+
} as const;
|
41
|
+
|
42
|
+
export const MAX_SEED = 2 ** 31 - 1;
|
@@ -5,8 +5,10 @@
|
|
5
5
|
"main": "src/index.ts",
|
6
6
|
"types": "src/index.ts",
|
7
7
|
"scripts": {
|
8
|
-
"test": "
|
9
|
-
"test:
|
8
|
+
"test": "npm run test:client-db && npm run test:server-db",
|
9
|
+
"test:client-db": "vitest run",
|
10
|
+
"test:coverage": "vitest --coverage --config vitest.config.server.mts",
|
11
|
+
"test:server-db": "vitest run --config vitest.config.server.mts"
|
10
12
|
},
|
11
13
|
"dependencies": {
|
12
14
|
"@electric-sql/pglite": "^0.2.17",
|
@@ -201,7 +201,7 @@ export class AiInfraRepos {
|
|
201
201
|
providerId: string,
|
202
202
|
): Promise<AiProviderModelListItem[] | undefined> => {
|
203
203
|
try {
|
204
|
-
const { default: providerModels } = await import(`@/config/aiModels/${providerId}`);
|
204
|
+
const { default: providerModels } = await import(`@/config/aiModels/${providerId}.ts`);
|
205
205
|
|
206
206
|
// use the serverModelLists as the defined server model list
|
207
207
|
const presetList = this.providerConfigs[providerId]?.serverModelLists || providerModels;
|
@@ -0,0 +1,33 @@
|
|
1
|
+
import { resolve } from 'node:path';
|
2
|
+
import { defineConfig } from 'vitest/config';
|
3
|
+
|
4
|
+
export default defineConfig({
|
5
|
+
optimizeDeps: {
|
6
|
+
exclude: ['crypto', 'util', 'tty'],
|
7
|
+
include: ['@lobehub/tts'],
|
8
|
+
},
|
9
|
+
test: {
|
10
|
+
alias: {
|
11
|
+
/* eslint-disable sort-keys-fix/sort-keys-fix */
|
12
|
+
'@/const': resolve(__dirname, '../const/src'),
|
13
|
+
'@/utils/errorResponse': resolve(__dirname, '../../src/utils/errorResponse'),
|
14
|
+
'@/utils': resolve(__dirname, '../utils/src'),
|
15
|
+
'@/database': resolve(__dirname, '../database/src'),
|
16
|
+
'@/types': resolve(__dirname, '../types/src'),
|
17
|
+
'@': resolve(__dirname, '../../src'),
|
18
|
+
/* eslint-enable */
|
19
|
+
},
|
20
|
+
environment: 'happy-dom',
|
21
|
+
exclude: [
|
22
|
+
'node_modules/**/**',
|
23
|
+
'src/server/**/**',
|
24
|
+
'src/repositories/dataImporter/deprecated/**/**',
|
25
|
+
],
|
26
|
+
server: {
|
27
|
+
deps: {
|
28
|
+
inline: ['vitest-canvas-mock'],
|
29
|
+
},
|
30
|
+
},
|
31
|
+
setupFiles: './tests/setup-db.ts',
|
32
|
+
},
|
33
|
+
});
|
@@ -358,7 +358,7 @@ export const processMultiProviderModelList = async (
|
|
358
358
|
let providerLocalConfig: any[] | null = null;
|
359
359
|
if (providerid) {
|
360
360
|
try {
|
361
|
-
const moduleImport = await import(`@/config/aiModels/${providerid}`);
|
361
|
+
const moduleImport = await import(`@/config/aiModels/${providerid}.ts`);
|
362
362
|
providerLocalConfig = moduleImport.default;
|
363
363
|
} catch {
|
364
364
|
// 如果配置文件不存在或导入失败,保持为 null
|
@@ -0,0 +1,95 @@
|
|
1
|
+
/**
|
2
|
+
* @vitest-environment happy-dom
|
3
|
+
*/
|
4
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
5
|
+
|
6
|
+
import { getImageDimensions } from './imageDimensions';
|
7
|
+
|
8
|
+
// Mock functions - need to be accessible in tests
|
9
|
+
const mockAddEventListener = vi.fn();
|
10
|
+
let mockImage: ReturnType<typeof vi.fn>;
|
11
|
+
let mockCreateObjectURL: any;
|
12
|
+
let mockRevokeObjectURL: any;
|
13
|
+
|
14
|
+
// Store event handlers for manual triggering
|
15
|
+
let loadHandler: (() => void) | null = null;
|
16
|
+
let errorHandler: (() => void) | null = null;
|
17
|
+
|
18
|
+
beforeEach(() => {
|
19
|
+
vi.clearAllMocks();
|
20
|
+
loadHandler = null;
|
21
|
+
errorHandler = null;
|
22
|
+
|
23
|
+
// Mock Image constructor using vi.stubGlobal (modern approach)
|
24
|
+
const mockImageInstance = {
|
25
|
+
addEventListener: mockAddEventListener.mockImplementation(
|
26
|
+
(event: string, handler: () => void) => {
|
27
|
+
if (event === 'load') loadHandler = handler;
|
28
|
+
if (event === 'error') errorHandler = handler;
|
29
|
+
},
|
30
|
+
),
|
31
|
+
naturalHeight: 600,
|
32
|
+
naturalWidth: 800,
|
33
|
+
src: '',
|
34
|
+
};
|
35
|
+
|
36
|
+
mockImage = vi.fn().mockImplementation(() => mockImageInstance);
|
37
|
+
vi.stubGlobal('Image', mockImage);
|
38
|
+
|
39
|
+
// Mock URL methods using vi.spyOn (preserves other URL functionality)
|
40
|
+
mockCreateObjectURL = vi.spyOn(URL, 'createObjectURL').mockReturnValue('blob:mock-url');
|
41
|
+
mockRevokeObjectURL = vi.spyOn(URL, 'revokeObjectURL').mockImplementation(() => {});
|
42
|
+
});
|
43
|
+
|
44
|
+
describe('getImageDimensions', () => {
|
45
|
+
it('should return correct dimensions for valid File object', async () => {
|
46
|
+
const imageFile = new File(['fake image data'], 'test.png', { type: 'image/png' });
|
47
|
+
|
48
|
+
const resultPromise = getImageDimensions(imageFile);
|
49
|
+
loadHandler?.();
|
50
|
+
const result = await resultPromise;
|
51
|
+
|
52
|
+
expect(result).toEqual({ height: 600, width: 800 });
|
53
|
+
expect(mockImage).toHaveBeenCalledTimes(1);
|
54
|
+
expect(mockCreateObjectURL).toHaveBeenCalledWith(imageFile);
|
55
|
+
expect(mockRevokeObjectURL).toHaveBeenCalledWith('blob:mock-url');
|
56
|
+
});
|
57
|
+
|
58
|
+
it('should return correct dimensions for valid data URI', async () => {
|
59
|
+
const dataUri =
|
60
|
+
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==';
|
61
|
+
|
62
|
+
const resultPromise = getImageDimensions(dataUri);
|
63
|
+
loadHandler?.();
|
64
|
+
const result = await resultPromise;
|
65
|
+
|
66
|
+
expect(result).toEqual({ height: 600, width: 800 });
|
67
|
+
expect(mockImage).toHaveBeenCalledTimes(1);
|
68
|
+
// Data URI should not use createObjectURL
|
69
|
+
expect(mockCreateObjectURL).not.toHaveBeenCalled();
|
70
|
+
expect(mockRevokeObjectURL).not.toHaveBeenCalled();
|
71
|
+
});
|
72
|
+
|
73
|
+
it('should return undefined for invalid inputs', async () => {
|
74
|
+
// Test non-image file
|
75
|
+
const textFile = new File(['content'], 'test.txt', { type: 'text/plain' });
|
76
|
+
const result1 = await getImageDimensions(textFile);
|
77
|
+
expect(result1).toBeUndefined();
|
78
|
+
|
79
|
+
// Test non-data URI string
|
80
|
+
const result2 = await getImageDimensions('https://example.com/image.jpg');
|
81
|
+
expect(result2).toBeUndefined();
|
82
|
+
});
|
83
|
+
|
84
|
+
it('should return undefined when image fails to load', async () => {
|
85
|
+
const imageFile = new File(['fake image data'], 'test.png', { type: 'image/png' });
|
86
|
+
|
87
|
+
const resultPromise = getImageDimensions(imageFile);
|
88
|
+
errorHandler?.(); // Simulate load error
|
89
|
+
const result = await resultPromise;
|
90
|
+
|
91
|
+
expect(result).toBeUndefined();
|
92
|
+
expect(mockCreateObjectURL).toHaveBeenCalledWith(imageFile);
|
93
|
+
expect(mockRevokeObjectURL).toHaveBeenCalledWith('blob:mock-url');
|
94
|
+
});
|
95
|
+
});
|
@@ -0,0 +1,54 @@
|
|
1
|
+
/**
|
2
|
+
* Helper function to extract image dimensions from File objects or base64 data URIs
|
3
|
+
* @param source The image source - either a File object or base64 data URI string
|
4
|
+
* @returns Promise resolving to dimensions or undefined if not an image or error occurs
|
5
|
+
*/
|
6
|
+
export const getImageDimensions = async (
|
7
|
+
source: File | string,
|
8
|
+
): Promise<{ height: number; width: number } | undefined> => {
|
9
|
+
// Type guard and validation
|
10
|
+
if (typeof source === 'string') {
|
11
|
+
// Handle base64 data URI
|
12
|
+
if (!source.startsWith('data:image/')) return undefined;
|
13
|
+
} else {
|
14
|
+
// Handle File object
|
15
|
+
if (!source.type.startsWith('image/')) return undefined;
|
16
|
+
}
|
17
|
+
|
18
|
+
return new Promise((resolve) => {
|
19
|
+
const img = new Image();
|
20
|
+
let objectUrl: string | null = null;
|
21
|
+
|
22
|
+
const handleLoad = () => {
|
23
|
+
resolve({
|
24
|
+
height: img.naturalHeight,
|
25
|
+
width: img.naturalWidth,
|
26
|
+
});
|
27
|
+
// Clean up object URL if created
|
28
|
+
if (objectUrl) {
|
29
|
+
URL.revokeObjectURL(objectUrl);
|
30
|
+
}
|
31
|
+
};
|
32
|
+
|
33
|
+
const handleError = () => {
|
34
|
+
// Clean up object URL if created
|
35
|
+
if (objectUrl) {
|
36
|
+
URL.revokeObjectURL(objectUrl);
|
37
|
+
}
|
38
|
+
resolve(undefined);
|
39
|
+
};
|
40
|
+
|
41
|
+
img.addEventListener('load', handleLoad);
|
42
|
+
img.addEventListener('error', handleError);
|
43
|
+
|
44
|
+
// Set source based on input type
|
45
|
+
if (typeof source === 'string') {
|
46
|
+
// Base64 data URI - use directly
|
47
|
+
img.src = source;
|
48
|
+
} else {
|
49
|
+
// File object - create object URL
|
50
|
+
objectUrl = URL.createObjectURL(source);
|
51
|
+
img.src = objectUrl;
|
52
|
+
}
|
53
|
+
});
|
54
|
+
};
|