mirror of
https://github.com/lobehub/lobe-chat.git
synced 2026-06-14 03:30:19 +00:00
Compare commits
34 Commits
improve-task
...
v2.1.15
| Author | SHA1 | Date | |
|---|---|---|---|
| 37609e42d6 | |||
| 3063ceef8c | |||
| f61ab26081 | |||
| 79712bd38c | |||
| 66caf30e7e | |||
| 0c855e44fc | |||
| e18b7a92c7 | |||
| 3f1fd102c5 | |||
| ccfaec2fdb | |||
| 8aba59bffd | |||
| 13e0652c59 | |||
| 4a6be92604 | |||
| c576a13a43 | |||
| 63a0464a83 | |||
| b1c6bdb192 | |||
| 74b9bd0bed | |||
| 6977c570e6 | |||
| 4efe60e9f7 | |||
| 336d10663c | |||
| 5f21aaf048 | |||
| e2fd28eece | |||
| a6a1fecae0 | |||
| fdee6b9aac | |||
| 98f93ef2f0 | |||
| df7e2800a7 | |||
| 4aac694364 | |||
| c7a06a4b62 | |||
| 2f21c15172 | |||
| de0ce799c7 | |||
| 3423ad1b15 | |||
| 5db07efe6b | |||
| f5d67a7385 | |||
| 99d4c02b9d | |||
| 182030f404 |
@@ -5,10 +5,11 @@ You are a support assistant for LobeChat authentication migration issues. Your j
|
||||
**IMPORTANT**: The official documentation website is `https://lobehub.com`. When providing documentation links, always use `https://lobehub.com/docs/...` format. Never use `lobechat.com` - that domain is incorrect.
|
||||
|
||||
Examples of correct documentation URLs:
|
||||
- `https://lobehub.com/docs/self-hosting/advanced/auth/nextauth-to-betterauth`
|
||||
- `https://lobehub.com/docs/self-hosting/advanced/auth/clerk-to-betterauth`
|
||||
- `https://lobehub.com/docs/self-hosting/advanced/auth`
|
||||
- `https://lobehub.com/docs/self-hosting/advanced/auth/providers/casdoor`
|
||||
|
||||
- `https://lobehub.com/docs/self-hosting/migration/v2/auth/nextauth-to-betterauth`
|
||||
- `https://lobehub.com/docs/self-hosting/migration/v2/auth/clerk-to-betterauth`
|
||||
- `https://lobehub.com/docs/self-hosting/auth`
|
||||
- `https://lobehub.com/docs/self-hosting/auth/providers/casdoor`
|
||||
|
||||
## Target Issues
|
||||
|
||||
|
||||
@@ -24,6 +24,17 @@ runs:
|
||||
shell: bash
|
||||
run: pnpm install --node-linker=hoisted
|
||||
|
||||
# 移除国内 electron 镜像配置,GitHub Actions 使用官方源更快
|
||||
- name: Remove China electron mirror from .npmrc
|
||||
shell: bash
|
||||
run: |
|
||||
NPMRC_FILE="./apps/desktop/.npmrc"
|
||||
if [ -f "$NPMRC_FILE" ]; then
|
||||
sed -i.bak '/^electron_mirror=/d; /^electron_builder_binaries_mirror=/d' "$NPMRC_FILE"
|
||||
rm -f "${NPMRC_FILE}.bak"
|
||||
echo "✅ Removed electron mirror config from .npmrc"
|
||||
fi
|
||||
|
||||
- name: Install deps on Desktop
|
||||
shell: bash
|
||||
run: npm run install-isolated --prefix=./apps/desktop
|
||||
|
||||
@@ -70,12 +70,12 @@ jobs:
|
||||
```
|
||||
|
||||
2. Read the latest migration documentation based on the issue:
|
||||
- If issue #11757 (NextAuth): `cat docs/self-hosting/advanced/auth/nextauth-to-betterauth.mdx`
|
||||
- If issue #11707 (Clerk): `cat docs/self-hosting/advanced/auth/clerk-to-betterauth.mdx`
|
||||
- If issue #11757 (NextAuth): `cat docs/self-hosting/migration/v2/auth/nextauth-to-betterauth.mdx`
|
||||
- If issue #11707 (Clerk): `cat docs/self-hosting/migration/v2/auth/clerk-to-betterauth.mdx`
|
||||
|
||||
3. Read additional reference files:
|
||||
- Main auth documentation: `cat docs/self-hosting/advanced/auth.mdx`
|
||||
- Migration internals: `cat docs/self-hosting/advanced/auth/migration-internals.mdx`
|
||||
- Main auth documentation: `cat docs/self-hosting/auth.mdx`
|
||||
- Migration internals: `cat docs/self-hosting/migration/v2/auth/migration-internals.mdx`
|
||||
- Deprecated env vars checker: `cat scripts/_shared/checkDeprecatedAuth.js`
|
||||
|
||||
4. Analyze the user's comment and determine:
|
||||
|
||||
@@ -109,6 +109,17 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: pnpm install --node-linker=hoisted
|
||||
|
||||
# 移除国内 electron 镜像配置,GitHub Actions 使用官方源更快
|
||||
- name: Remove China electron mirror from .npmrc
|
||||
shell: bash
|
||||
run: |
|
||||
NPMRC_FILE="./apps/desktop/.npmrc"
|
||||
if [ -f "$NPMRC_FILE" ]; then
|
||||
sed -i.bak '/^electron_mirror=/d; /^electron_builder_binaries_mirror=/d' "$NPMRC_FILE"
|
||||
rm -f "${NPMRC_FILE}.bak"
|
||||
echo "✅ Removed electron mirror config from .npmrc"
|
||||
fi
|
||||
|
||||
- name: Install deps on Desktop
|
||||
run: npm run install-isolated --prefix=./apps/desktop
|
||||
|
||||
|
||||
+225
@@ -2,6 +2,231 @@
|
||||
|
||||
# Changelog
|
||||
|
||||
### [Version 2.1.15](https://github.com/lobehub/lobe-chat/compare/v2.1.14...v2.1.15)
|
||||
|
||||
<sup>Released on **2026-02-04**</sup>
|
||||
|
||||
#### 🐛 Bug Fixes
|
||||
|
||||
- **misc**: Fixed the agents list the show updateAt time error.
|
||||
|
||||
<br/>
|
||||
|
||||
<details>
|
||||
<summary><kbd>Improvements and Fixes</kbd></summary>
|
||||
|
||||
#### What's fixed
|
||||
|
||||
- **misc**: Fixed the agents list the show updateAt time error, closes [#12103](https://github.com/lobehub/lobe-chat/issues/12103) ([3063cee](https://github.com/lobehub/lobe-chat/commit/3063cee))
|
||||
|
||||
</details>
|
||||
|
||||
<div align="right">
|
||||
|
||||
[](#readme-top)
|
||||
|
||||
</div>
|
||||
|
||||
### [Version 2.1.14](https://github.com/lobehub/lobe-chat/compare/v2.1.13...v2.1.14)
|
||||
|
||||
<sup>Released on **2026-02-04**</sup>
|
||||
|
||||
#### 🐛 Bug Fixes
|
||||
|
||||
- **misc**: Fix cannot uncompressed messages.
|
||||
|
||||
<br/>
|
||||
|
||||
<details>
|
||||
<summary><kbd>Improvements and Fixes</kbd></summary>
|
||||
|
||||
#### What's fixed
|
||||
|
||||
- **misc**: Fix cannot uncompressed messages, closes [#12086](https://github.com/lobehub/lobe-chat/issues/12086) ([ccfaec2](https://github.com/lobehub/lobe-chat/commit/ccfaec2))
|
||||
|
||||
</details>
|
||||
|
||||
<div align="right">
|
||||
|
||||
[](#readme-top)
|
||||
|
||||
</div>
|
||||
|
||||
### [Version 2.1.13](https://github.com/lobehub/lobe-chat/compare/v2.1.12...v2.1.13)
|
||||
|
||||
<sup>Released on **2026-02-03**</sup>
|
||||
|
||||
#### 🐛 Bug Fixes
|
||||
|
||||
- **docker**: Add librt.so.1 to fix PDF parsing.
|
||||
|
||||
<br/>
|
||||
|
||||
<details>
|
||||
<summary><kbd>Improvements and Fixes</kbd></summary>
|
||||
|
||||
#### What's fixed
|
||||
|
||||
- **docker**: Add librt.so.1 to fix PDF parsing, closes [#12039](https://github.com/lobehub/lobe-chat/issues/12039) ([4a6be92](https://github.com/lobehub/lobe-chat/commit/4a6be92))
|
||||
|
||||
</details>
|
||||
|
||||
<div align="right">
|
||||
|
||||
[](#readme-top)
|
||||
|
||||
</div>
|
||||
|
||||
### [Version 2.1.12](https://github.com/lobehub/lobe-chat/compare/v2.1.11...v2.1.12)
|
||||
|
||||
<sup>Released on **2026-02-03**</sup>
|
||||
|
||||
#### 🐛 Bug Fixes
|
||||
|
||||
- **changelog**: Normalize versionRange to valid semver.
|
||||
|
||||
<br/>
|
||||
|
||||
<details>
|
||||
<summary><kbd>Improvements and Fixes</kbd></summary>
|
||||
|
||||
#### What's fixed
|
||||
|
||||
- **changelog**: Normalize versionRange to valid semver, closes [#12049](https://github.com/lobehub/lobe-chat/issues/12049) ([74b9bd0](https://github.com/lobehub/lobe-chat/commit/74b9bd0))
|
||||
|
||||
</details>
|
||||
|
||||
<div align="right">
|
||||
|
||||
[](#readme-top)
|
||||
|
||||
</div>
|
||||
|
||||
### [Version 2.1.11](https://github.com/lobehub/lobe-chat/compare/v2.1.10...v2.1.11)
|
||||
|
||||
<sup>Released on **2026-02-02**</sup>
|
||||
|
||||
#### 🐛 Bug Fixes
|
||||
|
||||
- **misc**: Hide password features when AUTH_DISABLE_EMAIL_PASSWORD is set.
|
||||
|
||||
<br/>
|
||||
|
||||
<details>
|
||||
<summary><kbd>Improvements and Fixes</kbd></summary>
|
||||
|
||||
#### What's fixed
|
||||
|
||||
- **misc**: Hide password features when AUTH_DISABLE_EMAIL_PASSWORD is set, closes [#12023](https://github.com/lobehub/lobe-chat/issues/12023) ([e2fd28e](https://github.com/lobehub/lobe-chat/commit/e2fd28e))
|
||||
|
||||
</details>
|
||||
|
||||
<div align="right">
|
||||
|
||||
[](#readme-top)
|
||||
|
||||
</div>
|
||||
|
||||
### [Version 2.1.10](https://github.com/lobehub/lobe-chat/compare/v2.1.9...v2.1.10)
|
||||
|
||||
<sup>Released on **2026-02-02**</sup>
|
||||
|
||||
#### 🐛 Bug Fixes
|
||||
|
||||
- **auth**: Revert authority URL and tenant ID for Microsoft authentication..
|
||||
|
||||
<br/>
|
||||
|
||||
<details>
|
||||
<summary><kbd>Improvements and Fixes</kbd></summary>
|
||||
|
||||
#### What's fixed
|
||||
|
||||
- **auth**: Revert authority URL and tenant ID for Microsoft authentication., closes [#11930](https://github.com/lobehub/lobe-chat/issues/11930) ([98f93ef](https://github.com/lobehub/lobe-chat/commit/98f93ef))
|
||||
|
||||
</details>
|
||||
|
||||
<div align="right">
|
||||
|
||||
[](#readme-top)
|
||||
|
||||
</div>
|
||||
|
||||
### [Version 2.1.9](https://github.com/lobehub/lobe-chat/compare/v2.1.8...v2.1.9)
|
||||
|
||||
<sup>Released on **2026-02-02**</sup>
|
||||
|
||||
#### 🐛 Bug Fixes
|
||||
|
||||
- **misc**: Use oauth2.link for generic OIDC provider account linking.
|
||||
|
||||
<br/>
|
||||
|
||||
<details>
|
||||
<summary><kbd>Improvements and Fixes</kbd></summary>
|
||||
|
||||
#### What's fixed
|
||||
|
||||
- **misc**: Use oauth2.link for generic OIDC provider account linking, closes [#12024](https://github.com/lobehub/lobe-chat/issues/12024) ([c7a06a4](https://github.com/lobehub/lobe-chat/commit/c7a06a4))
|
||||
|
||||
</details>
|
||||
|
||||
<div align="right">
|
||||
|
||||
[](#readme-top)
|
||||
|
||||
</div>
|
||||
|
||||
### [Version 2.1.8](https://github.com/lobehub/lobe-chat/compare/v2.1.7...v2.1.8)
|
||||
|
||||
<sup>Released on **2026-02-01**</sup>
|
||||
|
||||
#### 💄 Styles
|
||||
|
||||
- **misc**: Improve tasks display.
|
||||
|
||||
<br/>
|
||||
|
||||
<details>
|
||||
<summary><kbd>Improvements and Fixes</kbd></summary>
|
||||
|
||||
#### Styles
|
||||
|
||||
- **misc**: Improve tasks display, closes [#12032](https://github.com/lobehub/lobe-chat/issues/12032) ([3423ad1](https://github.com/lobehub/lobe-chat/commit/3423ad1))
|
||||
|
||||
</details>
|
||||
|
||||
<div align="right">
|
||||
|
||||
[](#readme-top)
|
||||
|
||||
</div>
|
||||
|
||||
### [Version 2.1.7](https://github.com/lobehub/lobe-chat/compare/v2.1.6...v2.1.7)
|
||||
|
||||
<sup>Released on **2026-02-01**</sup>
|
||||
|
||||
#### 🐛 Bug Fixes
|
||||
|
||||
- **misc**: Add missing description parameter docs in Notebook system prompt.
|
||||
|
||||
<br/>
|
||||
|
||||
<details>
|
||||
<summary><kbd>Improvements and Fixes</kbd></summary>
|
||||
|
||||
#### What's fixed
|
||||
|
||||
- **misc**: Add missing description parameter docs in Notebook system prompt, closes [#12015](https://github.com/lobehub/lobe-chat/issues/12015) [#11391](https://github.com/lobehub/lobe-chat/issues/11391) ([182030f](https://github.com/lobehub/lobe-chat/commit/182030f))
|
||||
|
||||
</details>
|
||||
|
||||
<div align="right">
|
||||
|
||||
[](#readme-top)
|
||||
|
||||
</div>
|
||||
|
||||
### [Version 2.1.6](https://github.com/lobehub/lobe-chat/compare/v2.1.5...v2.1.6)
|
||||
|
||||
<sup>Released on **2026-02-01**</sup>
|
||||
|
||||
+4
-1
@@ -21,6 +21,7 @@ RUN set -e && \
|
||||
cp /etc/proxychains4.conf /distroless/etc/proxychains4.conf && \
|
||||
cp /usr/lib/$(arch)-linux-gnu/libstdc++.so.6 /distroless/lib/libstdc++.so.6 && \
|
||||
cp /usr/lib/$(arch)-linux-gnu/libgcc_s.so.1 /distroless/lib/libgcc_s.so.1 && \
|
||||
cp /usr/lib/$(arch)-linux-gnu/librt.so.1 /distroless/lib/librt.so.1 && \
|
||||
cp /usr/local/bin/node /distroless/bin/node && \
|
||||
cp /etc/ssl/certs/ca-certificates.crt /distroless/etc/ssl/certs/ca-certificates.crt && \
|
||||
rm -rf /tmp/* /var/lib/apt/lists/* /var/tmp/*
|
||||
@@ -186,7 +187,9 @@ ENV AUTH_SECRET="" \
|
||||
AUTH_GITHUB_SECRET="" \
|
||||
# Microsoft
|
||||
AUTH_MICROSOFT_ID="" \
|
||||
AUTH_MICROSOFT_SECRET=""
|
||||
AUTH_MICROSOFT_SECRET="" \
|
||||
AUTH_MICROSOFT_AUTHORITY_URL="" \
|
||||
AUTH_MICROSOFT_TENANT_ID=""
|
||||
|
||||
# Redis
|
||||
ENV REDIS_URL="" \
|
||||
|
||||
@@ -45,7 +45,7 @@ export class LinuxMenu extends BaseMenuPlatform implements IMenuPlatform {
|
||||
this.buildAndSetAppMenu(options);
|
||||
}
|
||||
|
||||
// --- 私有方法:定义菜单模板和逻辑 ---
|
||||
// --- Private methods: define menu templates and logic ---
|
||||
|
||||
private getAppMenuTemplate(options?: MenuOptions): MenuItemConstructorOptions[] {
|
||||
const showDev = isDev || options?.showDevItems;
|
||||
|
||||
@@ -48,23 +48,23 @@ export class MacOSMenu extends BaseMenuPlatform implements IMenuPlatform {
|
||||
}
|
||||
|
||||
refresh(options?: MenuOptions): void {
|
||||
// 重建Application menu
|
||||
// Rebuild Application menu
|
||||
this.buildAndSetAppMenu(options);
|
||||
// 如果托盘菜单存在,也重建它(如果需要动态更新)
|
||||
// If tray menu exists, rebuild it as well (if dynamic update is needed)
|
||||
// this.trayMenu = this.buildTrayMenu();
|
||||
// 需要考虑如何更新现有托盘图标的菜单
|
||||
// Need to consider how to update the menu for existing tray icons
|
||||
}
|
||||
|
||||
// --- 私有方法:定义菜单模板和逻辑 ---
|
||||
// --- Private methods: define menu templates and logic ---
|
||||
|
||||
private getAppMenuTemplate(options?: MenuOptions): MenuItemConstructorOptions[] {
|
||||
const appName = app.getName();
|
||||
const showDev = isDev || options?.showDevItems;
|
||||
// 创建命名空间翻译函数
|
||||
// Create namespaced translation function
|
||||
const t = this.app.i18n.ns('menu');
|
||||
|
||||
// 添加调试日志
|
||||
// console.log('[MacOSMenu] 菜单渲染, i18n实例:', !!this.app.i18n);
|
||||
// Add debug logging
|
||||
// console.log('[MacOSMenu] Menu rendering, i18n instance:', !!this.app.i18n);
|
||||
|
||||
const template: MenuItemConstructorOptions[] = [
|
||||
{
|
||||
@@ -324,7 +324,7 @@ export class MacOSMenu extends BaseMenuPlatform implements IMenuPlatform {
|
||||
},
|
||||
{
|
||||
click: () => {
|
||||
// @ts-expect-error cache 目录好像暂时不在类型定义里
|
||||
// @ts-expect-error cache directory seems to be temporarily missing from type definitions
|
||||
const cachePath = app.getPath('cache');
|
||||
|
||||
const updaterCachePath = path.join(cachePath, `${app.getName()}-updater`);
|
||||
|
||||
@@ -43,7 +43,7 @@ export class WindowsMenu extends BaseMenuPlatform implements IMenuPlatform {
|
||||
|
||||
refresh(options?: MenuOptions): void {
|
||||
this.buildAndSetAppMenu(options);
|
||||
// 如果有必要更新托盘菜单,可以在这里添加逻辑
|
||||
// If it's necessary to update tray menu, logic can be added here
|
||||
}
|
||||
|
||||
private getAppMenuTemplate(options?: MenuOptions): MenuItemConstructorOptions[] {
|
||||
|
||||
@@ -1,4 +1,54 @@
|
||||
[
|
||||
{
|
||||
"children": {
|
||||
"fixes": ["Fix cannot uncompressed messages."]
|
||||
},
|
||||
"date": "2026-02-04",
|
||||
"version": "2.1.14"
|
||||
},
|
||||
{
|
||||
"children": {},
|
||||
"date": "2026-02-03",
|
||||
"version": "2.1.13"
|
||||
},
|
||||
{
|
||||
"children": {},
|
||||
"date": "2026-02-03",
|
||||
"version": "2.1.12"
|
||||
},
|
||||
{
|
||||
"children": {
|
||||
"fixes": ["Hide password features when AUTH_DISABLE_EMAIL_PASSWORD is set."]
|
||||
},
|
||||
"date": "2026-02-02",
|
||||
"version": "2.1.11"
|
||||
},
|
||||
{
|
||||
"children": {},
|
||||
"date": "2026-02-02",
|
||||
"version": "2.1.10"
|
||||
},
|
||||
{
|
||||
"children": {
|
||||
"fixes": ["Use oauth2.link for generic OIDC provider account linking."]
|
||||
},
|
||||
"date": "2026-02-02",
|
||||
"version": "2.1.9"
|
||||
},
|
||||
{
|
||||
"children": {
|
||||
"improvements": ["Improve tasks display."]
|
||||
},
|
||||
"date": "2026-02-01",
|
||||
"version": "2.1.8"
|
||||
},
|
||||
{
|
||||
"children": {
|
||||
"fixes": ["Add missing description parameter docs in Notebook system prompt."]
|
||||
},
|
||||
"date": "2026-02-01",
|
||||
"version": "2.1.7"
|
||||
},
|
||||
{
|
||||
"children": {
|
||||
"improvements": ["Improve local-system tool implement."]
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
## chore(i18n): Adjust Latin language locales for terminology consistency
|
||||
|
||||
This comprehensive update ensures all Latin language locales (de-DE, fr-FR, es-ES, it-IT, pt-BR, nl-NL, pl-PL) follow the microcopy style guide's terminology requirements.
|
||||
|
||||
### Changes Made
|
||||
|
||||
**Total: 557 changes across 7 Latin locales**
|
||||
|
||||
#### Primary Terminology Updates
|
||||
1. **"Plugin" → "Skill"**
|
||||
- Fixed terminology inconsistency across all Latin languages
|
||||
- UI elements now consistently use "Skill" instead of localized equivalents
|
||||
- Includes both singular and plural forms: `plugin/Skill`, `plugins/Skills`
|
||||
|
||||
2. **"LobeChat" → "LobeHub"**
|
||||
- Updated brand name references to current branding
|
||||
|
||||
3. **"Agent" Terminology Consistency**
|
||||
- French: Fixed inconsistent "Assistant" → "Agent" usage in UI elements
|
||||
- Ensured consistent terminology across all languages
|
||||
|
||||
#### Per-Locale Breakdown
|
||||
- **de-DE (German)**: 267 changes
|
||||
- **fr-FR (French)**: 94 changes (including 7 Agent→Assistant fixes)
|
||||
- **es-ES (Spanish)**: 39 changes
|
||||
- **it-IT (Italian)**: 59 changes (including 18 plugin→skill fixes)
|
||||
- **pt-BR (Portuguese)**: 58 changes
|
||||
- **nl-NL (Dutch)**: 62 changes
|
||||
- **pl-PL (Polish)**: 28 changes
|
||||
|
||||
#### Files Modified
|
||||
- All 37 locale JSON files for each language (259 total files)
|
||||
- Includes: auth.json, chat.json, common.json, discover.json, plugin.json, setting.json, etc.
|
||||
|
||||
#### Key Improvements
|
||||
1. **Fixed Terminology**: Following microcopy guide's fixed terminology rules
|
||||
2. **Brand Consistency**: Changed all brand references to "LobeHub"
|
||||
3. **Natural Localization**: Maintained natural language patterns while ensuring consistency
|
||||
4. **User Experience**: Improved consistency across all Latin language interfaces
|
||||
|
||||
### Scripts Created
|
||||
Two utility scripts for future locale maintenance:
|
||||
- `scripts/adjust-latin-locales.py` - For common.json specific adjustments
|
||||
- `scripts/adjust-latin-locales-full.py` - For comprehensive adjustments across all files
|
||||
|
||||
### Review Notes
|
||||
- All changes maintain backward compatibility
|
||||
- No breaking changes to functionality
|
||||
- JSON files validated and remain syntactically correct
|
||||
- Changes reviewed against English base for consistency
|
||||
@@ -12,7 +12,7 @@ DATABASE_URL=postgresql://postgres:uWNZugjBqixf8dxC@postgresql:5432/lobechat
|
||||
AUTH_SECRET=NX2kaPE923dt6BL2U8e9oSre5RfoT7hg
|
||||
AUTH_SSO_PROVIDERS=zitadel
|
||||
# ZiTADEL provider configuration
|
||||
# Please refer to:https://lobehub.com/zh/docs/self-hosting/advanced/auth/providers/zitadel
|
||||
# Please refer to: https://lobehub.com/docs/self-hosting/auth/providers/zitadel
|
||||
AUTH_ZITADEL_ID=285945938244075523
|
||||
AUTH_ZITADEL_SECRET=hkbtzHLaCEIeHeFThym14UcydpmQiEB5JtAX08HSqSoJxhAlVVkyovTuNUZ5TNrT
|
||||
AUTH_ZITADEL_ISSUER=http://localhost:8080
|
||||
|
||||
@@ -11,7 +11,7 @@ DATABASE_URL=postgresql://postgres:uWNZugjBqixf8dxC@postgresql:5432/lobechat
|
||||
AUTH_SECRET=NX2kaPE923dt6BL2U8e9oSre5RfoT7hg
|
||||
AUTH_SSO_PROVIDERS=zitadel
|
||||
# ZiTADEL 鉴权服务提供商部分
|
||||
# 请参考:https://lobehub.com/zh/docs/self-hosting/advanced/auth/next-auth/zitadel
|
||||
# 请参考:https://lobehub.com/zh/docs/self-hosting/auth/providers/zitadel
|
||||
AUTH_ZITADEL_ID=285945938244075523
|
||||
AUTH_ZITADEL_SECRET=hkbtzHLaCEIeHeFThym14UcydpmQiEB5JtAX08HSqSoJxhAlVVkyovTuNUZ5TNrT
|
||||
AUTH_ZITADEL_ISSUER=http://localhost:8080
|
||||
|
||||
@@ -12,7 +12,7 @@ DATABASE_URL=postgresql://postgres:uWNZugjBqixf8dxC@postgresql:5432/lobe
|
||||
|
||||
# Authentication related environment variables
|
||||
# Supports Auth0, Azure AD, GitHub, Authentik, Zitadel, Logto, etc.
|
||||
# For supported providers, see: https://lobehub.com/docs/self-hosting/advanced/auth
|
||||
# For supported providers, see: https://lobehub.com/docs/self-hosting/auth
|
||||
# If you have ACCESS_CODE, please remove it. We use Better Auth as the sole authentication source
|
||||
# Required: Auth secret key. Generate with: openssl rand -base64 32
|
||||
AUTH_SECRET=NX2kaPE923dt6BL2U8e9oSre5RfoT7hg
|
||||
|
||||
@@ -11,7 +11,7 @@ DATABASE_URL=postgresql://postgres:uWNZugjBqixf8dxC@postgresql:5432/lobe
|
||||
|
||||
# 鉴权服务必需的环境变量
|
||||
# 可以使用 Auth0、Azure AD、GitHub、Authentik、Zitadel、Logto 等,如有其他接入诉求欢迎提 PR
|
||||
# 目前支持的鉴权服务提供商请参考:https://lobehub.com/zh/docs/self-hosting/advanced/auth
|
||||
# 目前支持的鉴权服务提供商请参考:https://lobehub.com/zh/docs/self-hosting/auth
|
||||
# 如果你有 ACCESS_CODE,请务必清空,我们以 Better Auth 作为唯一鉴权来源
|
||||
# 必填,用于鉴权的密钥,可以使用 openssl rand -base64 32 生成
|
||||
AUTH_SECRET=NX2kaPE923dt6BL2U8e9oSre5RfoT7hg
|
||||
|
||||
@@ -17,7 +17,7 @@ AUTH_SECRET=NX2kaPE923dt6BL2U8e9oSre5RfoT7hg
|
||||
AUTH_SSO_PROVIDERS=zitadel
|
||||
|
||||
# ZiTADEL provider configuration
|
||||
# Please refer to:https://lobehub.com/zh/docs/self-hosting/advanced/auth/providers/zitadel
|
||||
# Please refer to: https://lobehub.com/docs/self-hosting/auth/providers/zitadel
|
||||
AUTH_ZITADEL_ID=285934220675723622
|
||||
AUTH_ZITADEL_SECRET=pe7Nh3lopXkZkfqh5YEDYI2xsbIz08eZKqInOUZxssd3refRia518Apbv3DZ
|
||||
AUTH_ZITADEL_ISSUER=https://zitadel.example.com
|
||||
|
||||
@@ -16,7 +16,7 @@ AUTH_SECRET=NX2kaPE923dt6BL2U8e9oSre5RfoT7hg
|
||||
AUTH_SSO_PROVIDERS=zitadel
|
||||
|
||||
# ZiTADEL 鉴权服务提供商部分
|
||||
# 请参考:https://lobehub.com/zh/docs/self-hosting/advanced/auth/next-auth/zitadel
|
||||
# 请参考:https://lobehub.com/zh/docs/self-hosting/auth/providers/zitadel
|
||||
AUTH_ZITADEL_ID=285934220675723622
|
||||
AUTH_ZITADEL_SECRET=pe7Nh3lopXkZkfqh5YEDYI2xsbIz08eZKqInOUZxssd3refRia518Apbv3DZ
|
||||
AUTH_ZITADEL_ISSUER=https://zitadel.example.com
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"image": "/blog/assets7f3b38c1d76cceb91edb29d6b1eb60db.webp",
|
||||
"id": "2025-12-20-mcp",
|
||||
"date": "2025-12-20",
|
||||
"versionRange": ["1.142.8", "1.143"]
|
||||
"versionRange": ["1.142.8", "1.143.0"]
|
||||
},
|
||||
{
|
||||
"image": "/blog/assets3a7f0b29839603336e39e923b423409b.webp",
|
||||
|
||||
@@ -114,7 +114,7 @@ AUTH_OKTA_ISSUER: process.env.AUTH_OKTA_ISSUER,
|
||||
|
||||
### Step 4: Update Documentation (Optional)
|
||||
|
||||
Add provider documentation in `docs/self-hosting/advanced/auth.mdx` and `docs/self-hosting/advanced/auth.zh-CN.mdx`.
|
||||
Add provider documentation in `docs/self-hosting/auth.mdx` and `docs/self-hosting/auth.zh-CN.mdx`.
|
||||
|
||||
## Adding a Built-in Provider
|
||||
|
||||
|
||||
@@ -115,7 +115,7 @@ AUTH_OKTA_ISSUER: process.env.AUTH_OKTA_ISSUER,
|
||||
|
||||
### 步骤 4: 更新文档(可选)
|
||||
|
||||
在 `docs/self-hosting/advanced/auth.mdx` 和 `docs/self-hosting/advanced/auth.zh-CN.mdx` 中添加提供商文档。
|
||||
在 `docs/self-hosting/auth.mdx` 和 `docs/self-hosting/auth.zh-CN.mdx` 中添加提供商文档。
|
||||
|
||||
## 添加内置提供商
|
||||
|
||||
|
||||
@@ -10,7 +10,8 @@ tags:
|
||||
- PNPM
|
||||
- Bun
|
||||
- Git
|
||||
- VSCode
|
||||
- Docker
|
||||
- PostgreSQL
|
||||
---
|
||||
|
||||
# Environment Setup Guide
|
||||
@@ -35,6 +36,7 @@ First, you need to install the following software:
|
||||
- PNPM: We use PNPM as the preferred package manager. You can download and install it from the [PNPM official website](https://pnpm.io/installation).
|
||||
- Bun: We use Bun as the npm scripts runner. You can download and install it from the [Bun official website](https://bun.com/docs/installation).
|
||||
- Git: We use Git for version control. You can download and install it from the Git official website.
|
||||
- Docker: Required for running PostgreSQL, MinIO, and other services. You can download and install it from the [Docker official website](https://www.docker.com/get-started).
|
||||
- IDE: You can choose your preferred integrated development environment (IDE). We recommend using WebStorm/VSCode.
|
||||
|
||||
### VSCode Users
|
||||
@@ -45,20 +47,72 @@ We recommend installing the extensions listed in [.vscode/extensions.json](https
|
||||
|
||||
After installing the above software, you can start setting up the LobeHub project.
|
||||
|
||||
1. **Get the code**: First, you need to clone the LobeHub codebase from GitHub. Run the following command in the terminal:
|
||||
#### 1. Get the Code
|
||||
|
||||
First, you need to clone the LobeHub codebase from GitHub. Run the following command in the terminal:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/lobehub/lobehub.git
|
||||
cd lobehub
|
||||
```
|
||||
|
||||
2. **Install dependencies**: Then, navigate to the project directory and use PNPM to install the project's dependencies:
|
||||
#### 2. Install Dependencies
|
||||
|
||||
Use PNPM to install the project's dependencies:
|
||||
|
||||
```bash
|
||||
cd lobehub
|
||||
pnpm i
|
||||
```
|
||||
|
||||
3. **Start the development server**: After installing the dependencies, you can start the development server:
|
||||
#### 3. Configure Environment
|
||||
|
||||
Copy the example environment file to create your Docker Compose configuration:
|
||||
|
||||
```bash
|
||||
cp docker-compose/local/.env.example docker-compose/local/.env
|
||||
```
|
||||
|
||||
Edit `docker-compose/local/.env` as needed for your development setup. This file contains all necessary environment variables for the Docker services and configures:
|
||||
|
||||
- **Database**: PostgreSQL with connection string
|
||||
- **Authentication**: Better Auth with Casdoor SSO
|
||||
- **Storage**: MinIO S3-compatible storage
|
||||
- **Search**: SearXNG search engine
|
||||
|
||||
#### 4. Start Docker Services
|
||||
|
||||
Start all required services using Docker Compose:
|
||||
|
||||
```bash
|
||||
docker-compose -f docker-compose.development.yml up -d
|
||||
```
|
||||
|
||||
This will start the following services:
|
||||
|
||||
- PostgreSQL database (port 5432)
|
||||
- MinIO storage (port 9000)
|
||||
- Casdoor authentication (port 8000)
|
||||
- SearXNG search (port 8080)
|
||||
|
||||
You can check all Docker services are running by running:
|
||||
|
||||
```bash
|
||||
docker-compose -f docker-compose.development.yml ps
|
||||
```
|
||||
|
||||
#### 5. Run Database Migrations
|
||||
|
||||
Execute the database migration script to create all necessary tables:
|
||||
|
||||
```bash
|
||||
pnpm db:migrate
|
||||
```
|
||||
|
||||
You should see: `✅ database migration pass.`
|
||||
|
||||
#### 6. Start Development Server
|
||||
|
||||
Launch the LobeHub development server:
|
||||
|
||||
```bash
|
||||
bun run dev
|
||||
@@ -68,17 +122,125 @@ Now, you can open `http://localhost:3010` in your browser, and you should see th
|
||||
|
||||

|
||||
|
||||
## Working with Server-Side Features
|
||||
## Image Generation Development
|
||||
|
||||
The basic setup above uses LobeHub's client-side database mode. If you need to work with server-side features such as:
|
||||
When working with image generation features (text-to-image, image-to-image), the Docker Compose setup already includes all necessary storage services for handling generated images and user uploads.
|
||||
|
||||
- Database persistence
|
||||
- File uploads and storage
|
||||
- Image generation
|
||||
- Multi-user authentication
|
||||
- Advanced server-side integrations
|
||||
### Image Generation Configuration
|
||||
|
||||
Please refer to the [Work with Server-Side Database](/docs/development/basic/work-with-server-side-database) guide for complete setup instructions.
|
||||
The existing Docker Compose configuration already includes MinIO storage service and all necessary environment variables in `docker-compose/local/.env.example`. No additional setup is required.
|
||||
|
||||
### Image Generation Architecture
|
||||
|
||||
The image generation feature requires:
|
||||
|
||||
- **PostgreSQL**: Stores metadata about generated images
|
||||
- **MinIO/S3**: Stores the actual image files
|
||||
|
||||
### Storage Configuration
|
||||
|
||||
The `docker-compose/local/.env.example` file includes all necessary S3 environment variables:
|
||||
|
||||
```bash
|
||||
# S3 Storage Configuration (MinIO for local development)
|
||||
S3_ACCESS_KEY_ID=${MINIO_ROOT_USER}
|
||||
S3_SECRET_ACCESS_KEY=${MINIO_ROOT_PASSWORD}
|
||||
S3_ENDPOINT=http://localhost:${MINIO_PORT}
|
||||
S3_BUCKET=${MINIO_LOBE_BUCKET}
|
||||
S3_ENABLE_PATH_STYLE=1 # Required for MinIO
|
||||
S3_SET_ACL=0 # MinIO compatibility
|
||||
```
|
||||
|
||||
### File Storage Structure
|
||||
|
||||
Generated images and user uploads are organized in the MinIO bucket:
|
||||
|
||||
```
|
||||
lobe/ # S3 Bucket (MINIO_LOBE_BUCKET)
|
||||
├── generated/ # Generated images
|
||||
│ └── {userId}/
|
||||
│ └── {sessionId}/
|
||||
│ └── {imageId}.png
|
||||
└── uploads/ # User uploads for image-to-image
|
||||
└── {userId}/
|
||||
└── {fileId}.{ext}
|
||||
```
|
||||
|
||||
### Development Workflow for Images
|
||||
|
||||
When developing image generation features, generated images will be:
|
||||
|
||||
1. Created by the AI model
|
||||
2. Uploaded to S3/MinIO via presigned URLs
|
||||
3. Metadata stored in PostgreSQL
|
||||
4. Served via the public S3 URL
|
||||
|
||||
Example code for testing image upload:
|
||||
|
||||
```typescript
|
||||
// Example: Upload generated image
|
||||
const uploadUrl = await trpc.upload.createPresignedUrl.mutate({
|
||||
filename: 'generated-image.png',
|
||||
contentType: 'image/png',
|
||||
});
|
||||
|
||||
// Upload to S3
|
||||
await fetch(uploadUrl, {
|
||||
method: 'PUT',
|
||||
body: imageBlob,
|
||||
headers: { 'Content-Type': 'image/png' },
|
||||
});
|
||||
```
|
||||
|
||||
### Service URLs
|
||||
|
||||
When running with Docker Compose development setup:
|
||||
|
||||
- **PostgreSQL**: `postgres://postgres@localhost:5432/LobeHub`
|
||||
- **MinIO API**: `http://localhost:9000`
|
||||
- **MinIO Console**: `http://localhost:9001` (admin/CHANGE\_THIS\_PASSWORD\_IN\_PRODUCTION)
|
||||
- **Application**: `http://localhost:3010`
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Reset Services
|
||||
|
||||
If you encounter issues, you can reset the entire stack:
|
||||
|
||||
```bash
|
||||
# Stop and remove all containers
|
||||
docker-compose -f docker-compose.development.yml down
|
||||
|
||||
# Remove volumes (this will delete all data)
|
||||
docker-compose -f docker-compose.development.yml down -v
|
||||
|
||||
# Start fresh
|
||||
docker-compose -f docker-compose.development.yml up -d
|
||||
pnpm db:migrate
|
||||
```
|
||||
|
||||
### Port Conflicts
|
||||
|
||||
If ports are already in use:
|
||||
|
||||
```bash
|
||||
# Check what's using the ports
|
||||
lsof -i :5432 # PostgreSQL
|
||||
lsof -i :9000 # MinIO API
|
||||
lsof -i :9001 # MinIO Console
|
||||
```
|
||||
|
||||
### Database Migrations
|
||||
|
||||
The setup script runs migrations automatically. If you need to run them manually:
|
||||
|
||||
```bash
|
||||
pnpm db:migrate
|
||||
```
|
||||
|
||||
Note: In development mode with `pnpm dev:desktop`, migrations also run automatically on startup.
|
||||
|
||||
---
|
||||
|
||||
During the development process, if you encounter any issues with environment setup or have any questions about LobeHub development, feel free to ask us at any time. We look forward to seeing your contributions!
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@ tags:
|
||||
- Node.js
|
||||
- PNPM
|
||||
- Git
|
||||
- Docker
|
||||
- PostgreSQL
|
||||
---
|
||||
|
||||
# 环境设置指南
|
||||
@@ -29,8 +31,9 @@ tags:
|
||||
|
||||
- Node.js:LobeHub 是基于 Node.js 构建的,因此你需要安装 Node.js。我们建议安装最新的稳定版。
|
||||
- PNPM:我们使用 PNPM 作为管理器。你可以从 [pnpm 的官方网站](https://pnpm.io/installation) 上下载并安装。
|
||||
- Bun:我们使用 Bun 作为 npm scripts runner, 你可以从 [Bun 的官方网站](https://bun.com/docs/installation) 上下载并安装。
|
||||
- Bun:我们使用 Bun 作为 npm scripts runner,你可以从 [Bun 的官方网站](https://bun.com/docs/installation) 上下载并安装。
|
||||
- Git:我们使用 Git 进行版本控制。你可以从 Git 的官方网站上下载并安装。
|
||||
- Docker:用于运行 PostgreSQL、MinIO 等服务。你可以从 [Docker 官方网站](https://www.docker.com/get-started) 下载并安装。
|
||||
- IDE:你可以选择你喜欢的集成开发环境(IDE),我们推荐使用 WebStorm/VSCode。
|
||||
|
||||
### VSCode 用户
|
||||
@@ -41,20 +44,72 @@ tags:
|
||||
|
||||
完成上述软件的安装后,你可以开始设置 LobeHub 项目了。
|
||||
|
||||
1. **获取代码**:首先,你需要从 GitHub 上克隆 LobeHub 的代码库。在终端中运行以下命令:
|
||||
#### 1. 获取代码
|
||||
|
||||
首先,你需要从 GitHub 上克隆 LobeHub 的代码库。在终端中运行以下命令:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/lobehub/lobehub.git
|
||||
cd lobehub
|
||||
```
|
||||
|
||||
2. **安装依赖**:然后,进入项目目录,并使用 `pnpm` 安装项目的依赖包:
|
||||
#### 2. 安装依赖
|
||||
|
||||
使用 PNPM 安装项目的依赖包:
|
||||
|
||||
```bash
|
||||
cd lobehub
|
||||
pnpm i
|
||||
```
|
||||
|
||||
3. **启动开发服务器**:安装完依赖后,你可以启动开发服务器:
|
||||
#### 3. 配置环境
|
||||
|
||||
复制示例环境文件来创建你的 Docker Compose 配置:
|
||||
|
||||
```bash
|
||||
cp docker-compose/local/.env.example docker-compose/local/.env
|
||||
```
|
||||
|
||||
根据需要编辑 `docker-compose/local/.env` 文件以适应你的开发设置。此文件包含 Docker 服务所需的所有环境变量,配置了:
|
||||
|
||||
- **数据库**:带连接字符串的 PostgreSQL
|
||||
- **身份验证**:带 Casdoor SSO 的 Better Auth
|
||||
- **存储**:MinIO S3 兼容存储
|
||||
- **搜索**:SearXNG 搜索引擎
|
||||
|
||||
#### 4. 启动 Docker 服务
|
||||
|
||||
使用 Docker Compose 启动所有必需的服务:
|
||||
|
||||
```bash
|
||||
docker-compose -f docker-compose.development.yml up -d
|
||||
```
|
||||
|
||||
这将启动以下服务:
|
||||
|
||||
- PostgreSQL 数据库(端口 5432)
|
||||
- MinIO 存储(端口 9000)
|
||||
- Casdoor 身份验证(端口 8000)
|
||||
- SearXNG 搜索(端口 8080)
|
||||
|
||||
可以通过运行以下命令检查所有 Docker 服务运行状态:
|
||||
|
||||
```bash
|
||||
docker-compose -f docker-compose.development.yml ps
|
||||
```
|
||||
|
||||
#### 5. 运行数据库迁移
|
||||
|
||||
执行数据库迁移脚本以创建所有必要的表:
|
||||
|
||||
```bash
|
||||
pnpm db:migrate
|
||||
```
|
||||
|
||||
预期输出:`✅ database migration pass.`
|
||||
|
||||
#### 6. 启动开发服务器
|
||||
|
||||
启动 LobeHub 开发服务器:
|
||||
|
||||
```bash
|
||||
bun run dev
|
||||
@@ -64,17 +119,125 @@ bun run dev
|
||||
|
||||

|
||||
|
||||
## 使用服务端功能
|
||||
## 图像生成开发
|
||||
|
||||
上述基础设置使用 LobeHub 的客户端数据库模式。如果你需要开发服务端功能,如:
|
||||
在开发图像生成功能(文生图、图生图)时,Docker Compose 配置已经包含了处理生成图像和用户上传所需的所有存储服务。
|
||||
|
||||
- 数据库持久化
|
||||
- 文件上传和存储
|
||||
- 图像生成
|
||||
- 多用户身份验证
|
||||
- 高级服务端集成
|
||||
### 图像生成配置
|
||||
|
||||
请参考[使用服务端数据库](/docs/development/basic/work-with-server-side-database)指南获得完整的设置说明。
|
||||
现有的 Docker Compose 配置已经包含了 MinIO 存储服务以及 `docker-compose/local/.env.example` 中的所有必要环境变量。无需额外配置。
|
||||
|
||||
### 图像生成架构
|
||||
|
||||
图像生成功能需要:
|
||||
|
||||
- **PostgreSQL**:存储生成图像的元数据
|
||||
- **MinIO/S3**:存储实际的图像文件
|
||||
|
||||
### 存储配置
|
||||
|
||||
`docker-compose/local/.env.example` 文件包含所有必要的 S3 环境变量:
|
||||
|
||||
```bash
|
||||
# S3 存储配置(本地开发使用 MinIO)
|
||||
S3_ACCESS_KEY_ID=${MINIO_ROOT_USER}
|
||||
S3_SECRET_ACCESS_KEY=${MINIO_ROOT_PASSWORD}
|
||||
S3_ENDPOINT=http://localhost:${MINIO_PORT}
|
||||
S3_BUCKET=${MINIO_LOBE_BUCKET}
|
||||
S3_ENABLE_PATH_STYLE=1 # MinIO 必需
|
||||
S3_SET_ACL=0 # MinIO 兼容性
|
||||
```
|
||||
|
||||
### 文件存储结构
|
||||
|
||||
生成的图像和用户上传在 MinIO 存储桶中按以下方式组织:
|
||||
|
||||
```
|
||||
lobe/ # S3 存储桶 (MINIO_LOBE_BUCKET)
|
||||
├── generated/ # 生成的图像
|
||||
│ └── {userId}/
|
||||
│ └── {sessionId}/
|
||||
│ └── {imageId}.png
|
||||
└── uploads/ # 用户上传的图像处理文件
|
||||
└── {userId}/
|
||||
└── {fileId}.{ext}
|
||||
```
|
||||
|
||||
### 图像开发工作流
|
||||
|
||||
在开发图像生成功能时,生成的图像将:
|
||||
|
||||
1. 由 AI 模型创建
|
||||
2. 通过预签名 URL 上传到 S3/MinIO
|
||||
3. 元数据存储在 PostgreSQL 中
|
||||
4. 通过公共 S3 URL 提供服务
|
||||
|
||||
测试图像上传的示例代码:
|
||||
|
||||
```typescript
|
||||
// 示例:上传生成的图像
|
||||
const uploadUrl = await trpc.upload.createPresignedUrl.mutate({
|
||||
filename: 'generated-image.png',
|
||||
contentType: 'image/png',
|
||||
});
|
||||
|
||||
// 上传到 S3
|
||||
await fetch(uploadUrl, {
|
||||
method: 'PUT',
|
||||
body: imageBlob,
|
||||
headers: { 'Content-Type': 'image/png' },
|
||||
});
|
||||
```
|
||||
|
||||
### 服务地址
|
||||
|
||||
运行 Docker Compose 开发环境时:
|
||||
|
||||
- **PostgreSQL**:`postgres://postgres@localhost:5432/LobeHub`
|
||||
- **MinIO API**:`http://localhost:9000`
|
||||
- **MinIO 控制台**:`http://localhost:9001` (admin/CHANGE\_THIS\_PASSWORD\_IN\_PRODUCTION)
|
||||
- **应用程序**:`http://localhost:3010`
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 重置服务
|
||||
|
||||
如遇到问题,可以重置整个服务堆栈:
|
||||
|
||||
```bash
|
||||
# 停止并删除所有容器
|
||||
docker-compose -f docker-compose.development.yml down
|
||||
|
||||
# 删除卷(这将删除所有数据)
|
||||
docker-compose -f docker-compose.development.yml down -v
|
||||
|
||||
# 重新启动
|
||||
docker-compose -f docker-compose.development.yml up -d
|
||||
pnpm db:migrate
|
||||
```
|
||||
|
||||
### 端口冲突
|
||||
|
||||
如果端口已被占用:
|
||||
|
||||
```bash
|
||||
# 检查端口使用情况
|
||||
lsof -i :5432 # PostgreSQL
|
||||
lsof -i :9000 # MinIO API
|
||||
lsof -i :9001 # MinIO 控制台
|
||||
```
|
||||
|
||||
### 数据库迁移
|
||||
|
||||
配置脚本会自动运行迁移。如需手动运行:
|
||||
|
||||
```bash
|
||||
pnpm db:migrate
|
||||
```
|
||||
|
||||
注意:在使用 `pnpm dev:desktop` 的开发模式下,迁移也会在启动时自动运行。
|
||||
|
||||
---
|
||||
|
||||
在开发过程中,如果你在环境设置上遇到任何问题,或者有任何关于 LobeHub 开发的问题,欢迎随时向我们提问。我们期待看到你的贡献!
|
||||
|
||||
|
||||
@@ -1,195 +0,0 @@
|
||||
---
|
||||
title: Work with Server-Side Database
|
||||
description: Learn how to set up a server-side database for LobeHub with Docker.
|
||||
tags:
|
||||
- LobeHub
|
||||
- Server-Side Database
|
||||
- Docker
|
||||
- PostgreSQL
|
||||
- MinIO
|
||||
---
|
||||
|
||||
# Work with Server-Side Database
|
||||
|
||||
LobeHub provides a battery-included experience with its client-side database.
|
||||
While some features you really care about is only available at a server-side development.
|
||||
|
||||
In order to work with the aspect of server-side database,
|
||||
you can setup all the prerequisites by following the [Deploying Server-Side Database](https://lobehub.com/docs/self-hosting/server-database) story.
|
||||
But here is the easier approach that can reduce your pain.
|
||||
|
||||
## Quick Setup
|
||||
|
||||
### Environment Configuration
|
||||
|
||||
First, copy the example environment file to create your Docker Compose configuration:
|
||||
|
||||
```bash
|
||||
cp docker-compose/local/.env.example docker-compose/local/.env
|
||||
```
|
||||
|
||||
Edit `docker-compose/local/.env` as needed for your development setup. This file contains all necessary environment variables for the Docker services and configures:
|
||||
|
||||
- **Database**: PostgreSQL with connection string
|
||||
- **Authentication**: NextAuth with Casdoor SSO
|
||||
- **Storage**: MinIO S3-compatible storage
|
||||
- **Search**: SearXNG search engine
|
||||
|
||||
### Start Docker Services
|
||||
|
||||
Start all required services using Docker Compose:
|
||||
|
||||
```bash
|
||||
docker-compose -f docker-compose.development.yml up -d
|
||||
```
|
||||
|
||||
This will start the following services:
|
||||
|
||||
- PostgreSQL database (port 5432)
|
||||
- MinIO storage (port 9000)
|
||||
- Casdoor authentication (port 8000)
|
||||
- SearXNG search (port 8080)
|
||||
|
||||
### Run Database Migrations
|
||||
|
||||
Execute the database migration script to create all necessary tables:
|
||||
|
||||
```bash
|
||||
pnpm db:migrate
|
||||
```
|
||||
|
||||
You should see: `✅ database migration pass.`
|
||||
|
||||
### Start Development Server
|
||||
|
||||
Launch the LobeHub development server:
|
||||
|
||||
```bash
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
The server will start on `http://localhost:3010`
|
||||
|
||||
And you can check all Docker services are running by running:
|
||||
|
||||
```bash
|
||||
docker-compose -f docker-compose.development.yml ps
|
||||
```
|
||||
|
||||
## Image Generation Development
|
||||
|
||||
When working with image generation features (text-to-image, image-to-image), the Docker Compose setup already includes all necessary storage services for handling generated images and user uploads.
|
||||
|
||||
### Image Generation Configuration
|
||||
|
||||
The existing Docker Compose configuration already includes MinIO storage service and all necessary environment variables in `docker-compose/local/.env.example`. No additional setup is required.
|
||||
|
||||
### Image Generation Architecture
|
||||
|
||||
The image generation feature requires:
|
||||
|
||||
- **PostgreSQL**: Stores metadata about generated images
|
||||
- **MinIO/S3**: Stores the actual image files
|
||||
|
||||
### Storage Configuration
|
||||
|
||||
The `docker-compose/local/.env.example` file includes all necessary S3 environment variables:
|
||||
|
||||
```bash
|
||||
# S3 Storage Configuration (MinIO for local development)
|
||||
S3_ACCESS_KEY_ID=${MINIO_ROOT_USER}
|
||||
S3_SECRET_ACCESS_KEY=${MINIO_ROOT_PASSWORD}
|
||||
S3_ENDPOINT=http://localhost:${MINIO_PORT}
|
||||
S3_BUCKET=${MINIO_LOBE_BUCKET}
|
||||
S3_ENABLE_PATH_STYLE=1 # Required for MinIO
|
||||
S3_SET_ACL=0 # MinIO compatibility
|
||||
```
|
||||
|
||||
### File Storage Structure
|
||||
|
||||
Generated images and user uploads are organized in the MinIO bucket:
|
||||
|
||||
```
|
||||
lobe/ # S3 Bucket (MINIO_LOBE_BUCKET)
|
||||
├── generated/ # Generated images
|
||||
│ └── {userId}/
|
||||
│ └── {sessionId}/
|
||||
│ └── {imageId}.png
|
||||
└── uploads/ # User uploads for image-to-image
|
||||
└── {userId}/
|
||||
└── {fileId}.{ext}
|
||||
```
|
||||
|
||||
### Development Workflow for Images
|
||||
|
||||
When developing image generation features, generated images will be:
|
||||
|
||||
1. Created by the AI model
|
||||
2. Uploaded to S3/MinIO via presigned URLs
|
||||
3. Metadata stored in PostgreSQL
|
||||
4. Served via the public S3 URL
|
||||
|
||||
Example code for testing image upload:
|
||||
|
||||
```typescript
|
||||
// Example: Upload generated image
|
||||
const uploadUrl = await trpc.upload.createPresignedUrl.mutate({
|
||||
filename: 'generated-image.png',
|
||||
contentType: 'image/png',
|
||||
});
|
||||
|
||||
// Upload to S3
|
||||
await fetch(uploadUrl, {
|
||||
method: 'PUT',
|
||||
body: imageBlob,
|
||||
headers: { 'Content-Type': 'image/png' },
|
||||
});
|
||||
```
|
||||
|
||||
### Service URLs
|
||||
|
||||
When running with Docker Compose development setup:
|
||||
|
||||
- **PostgreSQL**: `postgres://postgres@localhost:5432/LobeHub`
|
||||
- **MinIO API**: `http://localhost:9000`
|
||||
- **MinIO Console**: `http://localhost:9001` (admin/CHANGE\_THIS\_PASSWORD\_IN\_PRODUCTION)
|
||||
- **Application**: `http://localhost:3010`
|
||||
|
||||
### Reset Services
|
||||
|
||||
If you encounter issues, you can reset the entire stack:
|
||||
|
||||
```bash
|
||||
# Stop and remove all containers
|
||||
docker-compose -f docker-compose.development.yml down
|
||||
|
||||
# Remove volumes (this will delete all data)
|
||||
docker-compose -f docker-compose.development.yml down -v
|
||||
|
||||
# Start fresh
|
||||
docker-compose -f docker-compose.development.yml up -d
|
||||
pnpm db:migrate
|
||||
```
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
#### Port Conflicts
|
||||
|
||||
If ports are already in use:
|
||||
|
||||
```bash
|
||||
# Check what's using the ports
|
||||
lsof -i :5432 # PostgreSQL
|
||||
lsof -i :9000 # MinIO API
|
||||
lsof -i :9001 # MinIO Console
|
||||
```
|
||||
|
||||
#### Database Migrations
|
||||
|
||||
The setup script runs migrations automatically. If you need to run them manually:
|
||||
|
||||
```bash
|
||||
pnpm db:migrate
|
||||
```
|
||||
|
||||
Note: In development mode with `pnpm dev:desktop`, migrations also run automatically on startup.
|
||||
@@ -1,195 +0,0 @@
|
||||
---
|
||||
title: 使用服务端数据库
|
||||
description: 快速设置 LobeHub 服务端数据库,支持 Docker 和图像生成。
|
||||
tags:
|
||||
- 服务端数据库
|
||||
- LobeHub
|
||||
- Docker
|
||||
- 图像生成
|
||||
- PostgreSQL
|
||||
---
|
||||
|
||||
# 使用服务端数据库
|
||||
|
||||
LobeHub 提供了内置的客户端数据库体验。
|
||||
但某些重要功能仅在服务端开发中可用。
|
||||
|
||||
为了使用服务端数据库功能,
|
||||
需要参考 [部署服务端数据库](https://lobehub.com/docs/self-hosting/server-database) 的说明来配置所有前置条件。
|
||||
本文档提供了一个更简化的配置方法,能够在本地开发时快速启动简化的服务端环境。
|
||||
|
||||
## 快速设置
|
||||
|
||||
### 环境配置
|
||||
|
||||
首先,复制示例环境文件来创建你的 Docker Compose 配置:
|
||||
|
||||
```bash
|
||||
cp docker-compose/local/.env.example docker-compose/local/.env
|
||||
```
|
||||
|
||||
根据需要编辑 `docker-compose/local/.env` 文件以适应你的开发设置。此文件包含 Docker 服务所需的所有环境变量,配置了:
|
||||
|
||||
- **数据库**: 带连接字符串的 PostgreSQL
|
||||
- **身份验证**: 带 Casdoor SSO 的 NextAuth
|
||||
- **存储**: MinIO S3 兼容存储
|
||||
- **搜索**: SearXNG 搜索引擎
|
||||
|
||||
### 启动 Docker 服务
|
||||
|
||||
使用 Docker Compose 启动所有必需的服务:
|
||||
|
||||
```bash
|
||||
docker-compose -f docker-compose.development.yml up -d
|
||||
```
|
||||
|
||||
这将启动以下服务:
|
||||
|
||||
- PostgreSQL 数据库(端口 5432)
|
||||
- MinIO 存储(端口 9000)
|
||||
- Casdoor 身份验证(端口 8000)
|
||||
- SearXNG 搜索(端口 8080)
|
||||
|
||||
### 运行数据库迁移
|
||||
|
||||
执行数据库迁移脚本以创建所有必要的表:
|
||||
|
||||
```bash
|
||||
pnpm db:migrate
|
||||
```
|
||||
|
||||
预期输出:`✅ database migration pass.`
|
||||
|
||||
### 启动开发服务器
|
||||
|
||||
启动 LobeHub 开发服务器:
|
||||
|
||||
```bash
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
服务器将在 `http://localhost:3010` 上启动
|
||||
|
||||
可以通过运行以下命令检查所有 Docker 服务运行状态:
|
||||
|
||||
```bash
|
||||
docker-compose -f docker-compose.development.yml ps
|
||||
```
|
||||
|
||||
## 图像生成开发
|
||||
|
||||
在开发图像生成功能(文生图、图生图)时,Docker Compose 配置已经包含了处理生成图像和用户上传所需的所有存储服务。
|
||||
|
||||
### 图像生成配置
|
||||
|
||||
现有的 Docker Compose 配置已经包含了 MinIO 存储服务以及 `docker-compose/local/.env.example` 中的所有必要环境变量。无需额外配置。
|
||||
|
||||
### 图像生成架构
|
||||
|
||||
图像生成功能需要:
|
||||
|
||||
- **PostgreSQL**:存储生成图像的元数据
|
||||
- **MinIO/S3**:存储实际的图像文件
|
||||
|
||||
### 存储配置
|
||||
|
||||
`docker-compose/local/.env.example` 文件包含所有必要的 S3 环境变量:
|
||||
|
||||
```bash
|
||||
# S3 存储配置(本地开发使用 MinIO)
|
||||
S3_ACCESS_KEY_ID=${MINIO_ROOT_USER}
|
||||
S3_SECRET_ACCESS_KEY=${MINIO_ROOT_PASSWORD}
|
||||
S3_ENDPOINT=http://localhost:${MINIO_PORT}
|
||||
S3_BUCKET=${MINIO_LOBE_BUCKET}
|
||||
S3_ENABLE_PATH_STYLE=1 # MinIO 必需
|
||||
S3_SET_ACL=0 # MinIO 兼容性
|
||||
```
|
||||
|
||||
### 文件存储结构
|
||||
|
||||
生成的图像和用户上传在 MinIO 存储桶中按以下方式组织:
|
||||
|
||||
```
|
||||
lobe/ # S3 存储桶 (MINIO_LOBE_BUCKET)
|
||||
├── generated/ # 生成的图像
|
||||
│ └── {userId}/
|
||||
│ └── {sessionId}/
|
||||
│ └── {imageId}.png
|
||||
└── uploads/ # 用户上传的图像处理文件
|
||||
└── {userId}/
|
||||
└── {fileId}.{ext}
|
||||
```
|
||||
|
||||
### 图像开发工作流
|
||||
|
||||
在开发图像生成功能时,生成的图像将:
|
||||
|
||||
1. 由 AI 模型创建
|
||||
2. 通过预签名 URL 上传到 S3/MinIO
|
||||
3. 元数据存储在 PostgreSQL 中
|
||||
4. 通过公共 S3 URL 提供服务
|
||||
|
||||
测试图像上传的示例代码:
|
||||
|
||||
```typescript
|
||||
// 示例:上传生成的图像
|
||||
const uploadUrl = await trpc.upload.createPresignedUrl.mutate({
|
||||
filename: 'generated-image.png',
|
||||
contentType: 'image/png',
|
||||
});
|
||||
|
||||
// 上传到 S3
|
||||
await fetch(uploadUrl, {
|
||||
method: 'PUT',
|
||||
body: imageBlob,
|
||||
headers: { 'Content-Type': 'image/png' },
|
||||
});
|
||||
```
|
||||
|
||||
### 服务地址
|
||||
|
||||
运行 Docker Compose 开发环境时:
|
||||
|
||||
- **PostgreSQL**:`postgres://postgres@localhost:5432/LobeHub`
|
||||
- **MinIO API**:`http://localhost:9000`
|
||||
- **MinIO 控制台**:`http://localhost:9001` (admin/CHANGE\_THIS\_PASSWORD\_IN\_PRODUCTION)
|
||||
- **应用程序**:`http://localhost:3010`
|
||||
|
||||
### 重置服务
|
||||
|
||||
如遇到问题,可以重置整个服务堆栈:
|
||||
|
||||
```bash
|
||||
# 停止并删除所有容器
|
||||
docker-compose -f docker-compose.development.yml down
|
||||
|
||||
# 删除卷(这将删除所有数据)
|
||||
docker-compose -f docker-compose.development.yml down -v
|
||||
|
||||
# 重新启动
|
||||
docker-compose -f docker-compose.development.yml up -d
|
||||
pnpm db:migrate
|
||||
```
|
||||
|
||||
### 故障排除
|
||||
|
||||
#### 端口冲突
|
||||
|
||||
如果端口已被占用:
|
||||
|
||||
```bash
|
||||
# 检查端口使用情况
|
||||
lsof -i :5432 # PostgreSQL
|
||||
lsof -i :9000 # MinIO API
|
||||
lsof -i :9001 # MinIO 控制台
|
||||
```
|
||||
|
||||
#### 数据库迁移
|
||||
|
||||
配置脚本会自动运行迁移。如需手动运行:
|
||||
|
||||
```bash
|
||||
pnpm db:migrate
|
||||
```
|
||||
|
||||
注意:在使用 `pnpm dev:desktop` 的开发模式下,迁移也会在启动时自动运行。
|
||||
@@ -643,6 +643,7 @@ table messages {
|
||||
thread_id [name: 'messages_thread_id_idx']
|
||||
agent_id [name: 'messages_agent_id_idx']
|
||||
group_id [name: 'messages_group_id_idx']
|
||||
message_group_id [name: 'messages_message_group_id_idx']
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+19
-19
@@ -42,7 +42,7 @@ To enable Better Auth in LobeHub, set the following environment variables:
|
||||
| --------------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------ |
|
||||
| Google | `google` | `AUTH_GOOGLE_ID`, `AUTH_GOOGLE_SECRET` |
|
||||
| GitHub | `github` | `AUTH_GITHUB_ID`, `AUTH_GITHUB_SECRET` |
|
||||
| Microsoft | `microsoft` | `AUTH_MICROSOFT_ID`, `AUTH_MICROSOFT_SECRET` |
|
||||
| Microsoft | `microsoft` | `AUTH_MICROSOFT_ID`, `AUTH_MICROSOFT_SECRET`, `AUTH_MICROSOFT_AUTHORITY_URL`, `AUTH_MICROSOFT_TENANT_ID` |
|
||||
| Apple | `apple` | `AUTH_APPLE_CLIENT_ID`, `AUTH_APPLE_CLIENT_SECRET` |
|
||||
| AWS Cognito | `cognito` | `AUTH_COGNITO_ID`, `AUTH_COGNITO_SECRET`, `AUTH_COGNITO_DOMAIN`, `AUTH_COGNITO_REGION`, `AUTH_COGNITO_USERPOOL_ID` |
|
||||
| Auth0 | `auth0` | `AUTH_AUTH0_ID`, `AUTH_AUTH0_SECRET`, `AUTH_AUTH0_ISSUER` |
|
||||
@@ -61,41 +61,41 @@ To enable Better Auth in LobeHub, set the following environment variables:
|
||||
Click on a provider below for detailed configuration guides:
|
||||
|
||||
<Cards>
|
||||
<Card href={'/docs/self-hosting/advanced/auth/providers/password'} title={'Email/Password'} />
|
||||
<Card href={'/docs/self-hosting/auth/providers/password'} title={'Email/Password'} />
|
||||
|
||||
<Card href={'/docs/self-hosting/advanced/auth/providers/github'} title={'GitHub'} />
|
||||
<Card href={'/docs/self-hosting/auth/providers/github'} title={'GitHub'} />
|
||||
|
||||
<Card href={'/docs/self-hosting/advanced/auth/providers/google'} title={'Google'} />
|
||||
<Card href={'/docs/self-hosting/auth/providers/google'} title={'Google'} />
|
||||
|
||||
<Card href={'/docs/self-hosting/advanced/auth/providers/microsoft'} title={'Microsoft'} />
|
||||
<Card href={'/docs/self-hosting/auth/providers/microsoft'} title={'Microsoft'} />
|
||||
|
||||
<Card href={'/docs/self-hosting/advanced/auth/providers/apple'} title={'Apple'} />
|
||||
<Card href={'/docs/self-hosting/auth/providers/apple'} title={'Apple'} />
|
||||
|
||||
<Card href={'/docs/self-hosting/advanced/auth/providers/cognito'} title={'AWS Cognito'} />
|
||||
<Card href={'/docs/self-hosting/auth/providers/cognito'} title={'AWS Cognito'} />
|
||||
|
||||
<Card href={'/docs/self-hosting/advanced/auth/providers/auth0'} title={'Auth0'} />
|
||||
<Card href={'/docs/self-hosting/auth/providers/auth0'} title={'Auth0'} />
|
||||
|
||||
<Card href={'/docs/self-hosting/advanced/auth/providers/authelia'} title={'Authelia'} />
|
||||
<Card href={'/docs/self-hosting/auth/providers/authelia'} title={'Authelia'} />
|
||||
|
||||
<Card href={'/docs/self-hosting/advanced/auth/providers/authentik'} title={'Authentik'} />
|
||||
<Card href={'/docs/self-hosting/auth/providers/authentik'} title={'Authentik'} />
|
||||
|
||||
<Card href={'/docs/self-hosting/advanced/auth/providers/casdoor'} title={'Casdoor'} />
|
||||
<Card href={'/docs/self-hosting/auth/providers/casdoor'} title={'Casdoor'} />
|
||||
|
||||
<Card href={'/docs/self-hosting/advanced/auth/providers/cloudflare-zero-trust'} title={'Cloudflare Zero Trust'} />
|
||||
<Card href={'/docs/self-hosting/auth/providers/cloudflare-zero-trust'} title={'Cloudflare Zero Trust'} />
|
||||
|
||||
<Card href={'/docs/self-hosting/advanced/auth/providers/keycloak'} title={'Keycloak'} />
|
||||
<Card href={'/docs/self-hosting/auth/providers/keycloak'} title={'Keycloak'} />
|
||||
|
||||
<Card href={'/docs/self-hosting/advanced/auth/providers/logto'} title={'Logto'} />
|
||||
<Card href={'/docs/self-hosting/auth/providers/logto'} title={'Logto'} />
|
||||
|
||||
<Card href={'/docs/self-hosting/advanced/auth/providers/okta'} title={'Okta'} />
|
||||
<Card href={'/docs/self-hosting/auth/providers/okta'} title={'Okta'} />
|
||||
|
||||
<Card href={'/docs/self-hosting/advanced/auth/providers/zitadel'} title={'ZITADEL'} />
|
||||
<Card href={'/docs/self-hosting/auth/providers/zitadel'} title={'ZITADEL'} />
|
||||
|
||||
<Card href={'/docs/self-hosting/advanced/auth/providers/generic-oidc'} title={'Generic OIDC'} />
|
||||
<Card href={'/docs/self-hosting/auth/providers/generic-oidc'} title={'Generic OIDC'} />
|
||||
|
||||
<Card href={'/docs/self-hosting/advanced/auth/providers/feishu'} title={'Feishu'} />
|
||||
<Card href={'/docs/self-hosting/auth/providers/feishu'} title={'Feishu'} />
|
||||
|
||||
<Card href={'/docs/self-hosting/advanced/auth/providers/wechat'} title={'WeChat'} />
|
||||
<Card href={'/docs/self-hosting/auth/providers/wechat'} title={'WeChat'} />
|
||||
</Cards>
|
||||
|
||||
## Callback URL Format
|
||||
|
||||
@@ -42,7 +42,7 @@ LobeHub 支持使用 Better Auth 配置外部身份验证服务,供企业 /
|
||||
| --------------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------ |
|
||||
| Google | `google` | `AUTH_GOOGLE_ID`, `AUTH_GOOGLE_SECRET` |
|
||||
| GitHub | `github` | `AUTH_GITHUB_ID`, `AUTH_GITHUB_SECRET` |
|
||||
| Microsoft | `microsoft` | `AUTH_MICROSOFT_ID`, `AUTH_MICROSOFT_SECRET` |
|
||||
| Microsoft | `microsoft` | `AUTH_MICROSOFT_ID`, `AUTH_MICROSOFT_SECRET`, `AUTH_MICROSOFT_AUTHORITY_URL`, `AUTH_MICROSOFT_TENANT_ID` |
|
||||
| Apple | `apple` | `AUTH_APPLE_CLIENT_ID`, `AUTH_APPLE_CLIENT_SECRET` |
|
||||
| AWS Cognito | `cognito` | `AUTH_COGNITO_ID`, `AUTH_COGNITO_SECRET`, `AUTH_COGNITO_DOMAIN`, `AUTH_COGNITO_REGION`, `AUTH_COGNITO_USERPOOL_ID` |
|
||||
| Auth0 | `auth0` | `AUTH_AUTH0_ID`, `AUTH_AUTH0_SECRET`, `AUTH_AUTH0_ISSUER` |
|
||||
@@ -61,41 +61,41 @@ LobeHub 支持使用 Better Auth 配置外部身份验证服务,供企业 /
|
||||
点击下方提供商查看详细配置指南:
|
||||
|
||||
<Cards>
|
||||
<Card href={'/zh/docs/self-hosting/advanced/auth/providers/password'} title={'邮箱密码'} />
|
||||
<Card href={'/zh/docs/self-hosting/auth/providers/password'} title={'邮箱密码'} />
|
||||
|
||||
<Card href={'/zh/docs/self-hosting/advanced/auth/providers/github'} title={'GitHub'} />
|
||||
<Card href={'/zh/docs/self-hosting/auth/providers/github'} title={'GitHub'} />
|
||||
|
||||
<Card href={'/zh/docs/self-hosting/advanced/auth/providers/google'} title={'Google'} />
|
||||
<Card href={'/zh/docs/self-hosting/auth/providers/google'} title={'Google'} />
|
||||
|
||||
<Card href={'/zh/docs/self-hosting/advanced/auth/providers/microsoft'} title={'Microsoft'} />
|
||||
<Card href={'/zh/docs/self-hosting/auth/providers/microsoft'} title={'Microsoft'} />
|
||||
|
||||
<Card href={'/zh/docs/self-hosting/advanced/auth/providers/apple'} title={'Apple'} />
|
||||
<Card href={'/zh/docs/self-hosting/auth/providers/apple'} title={'Apple'} />
|
||||
|
||||
<Card href={'/zh/docs/self-hosting/advanced/auth/providers/cognito'} title={'AWS Cognito'} />
|
||||
<Card href={'/zh/docs/self-hosting/auth/providers/cognito'} title={'AWS Cognito'} />
|
||||
|
||||
<Card href={'/zh/docs/self-hosting/advanced/auth/providers/auth0'} title={'Auth0'} />
|
||||
<Card href={'/zh/docs/self-hosting/auth/providers/auth0'} title={'Auth0'} />
|
||||
|
||||
<Card href={'/zh/docs/self-hosting/advanced/auth/providers/authelia'} title={'Authelia'} />
|
||||
<Card href={'/zh/docs/self-hosting/auth/providers/authelia'} title={'Authelia'} />
|
||||
|
||||
<Card href={'/zh/docs/self-hosting/advanced/auth/providers/authentik'} title={'Authentik'} />
|
||||
<Card href={'/zh/docs/self-hosting/auth/providers/authentik'} title={'Authentik'} />
|
||||
|
||||
<Card href={'/zh/docs/self-hosting/advanced/auth/providers/casdoor'} title={'Casdoor'} />
|
||||
<Card href={'/zh/docs/self-hosting/auth/providers/casdoor'} title={'Casdoor'} />
|
||||
|
||||
<Card href={'/zh/docs/self-hosting/advanced/auth/providers/cloudflare-zero-trust'} title={'Cloudflare Zero Trust'} />
|
||||
<Card href={'/zh/docs/self-hosting/auth/providers/cloudflare-zero-trust'} title={'Cloudflare Zero Trust'} />
|
||||
|
||||
<Card href={'/zh/docs/self-hosting/advanced/auth/providers/keycloak'} title={'Keycloak'} />
|
||||
<Card href={'/zh/docs/self-hosting/auth/providers/keycloak'} title={'Keycloak'} />
|
||||
|
||||
<Card href={'/zh/docs/self-hosting/advanced/auth/providers/logto'} title={'Logto'} />
|
||||
<Card href={'/zh/docs/self-hosting/auth/providers/logto'} title={'Logto'} />
|
||||
|
||||
<Card href={'/zh/docs/self-hosting/advanced/auth/providers/okta'} title={'Okta'} />
|
||||
<Card href={'/zh/docs/self-hosting/auth/providers/okta'} title={'Okta'} />
|
||||
|
||||
<Card href={'/zh/docs/self-hosting/advanced/auth/providers/zitadel'} title={'ZITADEL'} />
|
||||
<Card href={'/zh/docs/self-hosting/auth/providers/zitadel'} title={'ZITADEL'} />
|
||||
|
||||
<Card href={'/zh/docs/self-hosting/advanced/auth/providers/generic-oidc'} title={'Generic OIDC'} />
|
||||
<Card href={'/zh/docs/self-hosting/auth/providers/generic-oidc'} title={'Generic OIDC'} />
|
||||
|
||||
<Card href={'/zh/docs/self-hosting/advanced/auth/providers/feishu'} title={'飞书'} />
|
||||
<Card href={'/zh/docs/self-hosting/auth/providers/feishu'} title={'飞书'} />
|
||||
|
||||
<Card href={'/zh/docs/self-hosting/advanced/auth/providers/wechat'} title={'微信'} />
|
||||
<Card href={'/zh/docs/self-hosting/auth/providers/wechat'} title={'微信'} />
|
||||
</Cards>
|
||||
|
||||
## 回调 URL 格式
|
||||
|
||||
@@ -13,7 +13,7 @@ tags:
|
||||
# Legacy Authentication
|
||||
|
||||
<Callout type={'warning'}>
|
||||
**Legacy Notice**: NextAuth and Clerk are legacy authentication methods. For new deployments, we strongly recommend using [Better Auth](/docs/self-hosting/advanced/auth) for its simplicity and flexibility.
|
||||
**Legacy Notice**: NextAuth and Clerk are legacy authentication methods. For new deployments, we strongly recommend using [Better Auth](/docs/self-hosting/auth) for its simplicity and flexibility.
|
||||
</Callout>
|
||||
|
||||
This page documents the legacy authentication methods (NextAuth and Clerk) for users who are still using these services.
|
||||
@@ -27,17 +27,17 @@ LobeHub has deeply integrated with Clerk to provide users with a secure and conv
|
||||
By setting the environment variables `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY` and `CLERK_SECRET_KEY` in LobeHub's environment, you can enable and use Clerk.
|
||||
|
||||
<Callout type={'info'}>
|
||||
For detailed Clerk configuration, see [Clerk Configuration Guide](/docs/self-hosting/advanced/auth/clerk).
|
||||
For detailed Clerk configuration, see [Clerk Configuration Guide](/docs/self-hosting/auth/clerk).
|
||||
</Callout>
|
||||
|
||||
<Callout type={'tip'}>
|
||||
To migrate from Clerk to Better Auth, see the [Clerk Migration Guide](/docs/self-hosting/advanced/auth/clerk-to-betterauth).
|
||||
To migrate from Clerk to Better Auth, see the [Clerk Migration Guide](/docs/self-hosting/migration/v2/auth/clerk-to-betterauth).
|
||||
</Callout>
|
||||
|
||||
## Next Auth
|
||||
|
||||
<Callout type={'tip'}>
|
||||
To migrate from NextAuth to Better Auth, see the [NextAuth Migration Guide](/docs/self-hosting/advanced/auth/nextauth-to-betterauth).
|
||||
To migrate from NextAuth to Better Auth, see the [NextAuth Migration Guide](/docs/self-hosting/migration/v2/auth/nextauth-to-betterauth).
|
||||
</Callout>
|
||||
|
||||
Before using NextAuth, please set the following variables in LobeHub's environment variables:
|
||||
@@ -53,27 +53,27 @@ Before using NextAuth, please set the following variables in LobeHub's environme
|
||||
Currently supported identity verification services include:
|
||||
|
||||
<Cards>
|
||||
<Card href={'/docs/self-hosting/advanced/auth/next-auth/auth0'} title={'Auth0'} />
|
||||
<Card href={'/docs/self-hosting/auth/next-auth/auth0'} title={'Auth0'} />
|
||||
|
||||
<Card href={'/docs/self-hosting/advanced/auth/next-auth/microsoft-entra-id'} title={'Microsoft Entra ID'} />
|
||||
<Card href={'/docs/self-hosting/auth/next-auth/microsoft-entra-id'} title={'Microsoft Entra ID'} />
|
||||
|
||||
<Card href={'/docs/self-hosting/advanced/auth/next-auth/authentik'} title={'Authentik'} />
|
||||
<Card href={'/docs/self-hosting/auth/next-auth/authentik'} title={'Authentik'} />
|
||||
|
||||
<Card href={'/docs/self-hosting/advanced/auth/next-auth/github'} title={'Github'} />
|
||||
<Card href={'/docs/self-hosting/auth/next-auth/github'} title={'Github'} />
|
||||
|
||||
<Card href={'/docs/self-hosting/advanced/auth/next-auth/zitadel'} title={'ZITADEL'} />
|
||||
<Card href={'/docs/self-hosting/auth/next-auth/zitadel'} title={'ZITADEL'} />
|
||||
|
||||
<Card href={'/docs/self-hosting/advanced/auth/next-auth/cloudflare-zero-trust'} title={'Cloudflare Zero Trust'} />
|
||||
<Card href={'/docs/self-hosting/auth/next-auth/cloudflare-zero-trust'} title={'Cloudflare Zero Trust'} />
|
||||
|
||||
<Card href={'/docs/self-hosting/advanced/auth/next-auth/authelia'} title={'Authelia'} />
|
||||
<Card href={'/docs/self-hosting/auth/next-auth/authelia'} title={'Authelia'} />
|
||||
|
||||
<Card href={'/docs/self-hosting/advanced/auth/next-auth/logto'} title={'Logto'} />
|
||||
<Card href={'/docs/self-hosting/auth/next-auth/logto'} title={'Logto'} />
|
||||
|
||||
<Card href={'/docs/self-hosting/advanced/auth/next-auth/keycloak'} title={'Keycloak'} />
|
||||
<Card href={'/docs/self-hosting/auth/next-auth/keycloak'} title={'Keycloak'} />
|
||||
|
||||
<Card href={'/docs/self-hosting/advanced/auth/next-auth/google'} title={'Google'} />
|
||||
<Card href={'/docs/self-hosting/auth/next-auth/google'} title={'Google'} />
|
||||
|
||||
<Card href={'/docs/self-hosting/advanced/auth/next-auth/okta'} title={'Okta'} />
|
||||
<Card href={'/docs/self-hosting/auth/next-auth/okta'} title={'Okta'} />
|
||||
</Cards>
|
||||
|
||||
Click on the links to view the corresponding platform's configuration documentation.
|
||||
|
||||
@@ -11,7 +11,7 @@ tags:
|
||||
# 旧版身份验证
|
||||
|
||||
<Callout type={'warning'}>
|
||||
**旧版提示**:NextAuth 和 Clerk 是旧版身份验证方案。对于新部署,我们强烈建议使用 [Better Auth](/zh/docs/self-hosting/advanced/auth),它更简洁、更灵活。
|
||||
**旧版提示**:NextAuth 和 Clerk 是旧版身份验证方案。对于新部署,我们强烈建议使用 [Better Auth](/zh/docs/self-hosting/auth),它更简洁、更灵活。
|
||||
</Callout>
|
||||
|
||||
本页面为仍在使用这些服务的用户提供旧版身份验证方案(NextAuth 和 Clerk)的文档。
|
||||
@@ -25,17 +25,17 @@ LobeHub 与 Clerk 做了深度集成,能够为用户提供安全、便捷的
|
||||
在 LobeHub 的环境变量中设置 `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY` 和 `CLERK_SECRET_KEY`,即可开启和使用 Clerk。
|
||||
|
||||
<Callout type={'info'}>
|
||||
详细的 Clerk 配置请参阅 [Clerk 配置指南](/zh/docs/self-hosting/advanced/auth/clerk)。
|
||||
详细的 Clerk 配置请参阅 [Clerk 配置指南](/zh/docs/self-hosting/auth/clerk)。
|
||||
</Callout>
|
||||
|
||||
<Callout type={'tip'}>
|
||||
如需从 Clerk 迁移到 Better Auth,请参阅 [Clerk 迁移指南](/zh/docs/self-hosting/advanced/auth/clerk-to-betterauth)。
|
||||
如需从 Clerk 迁移到 Better Auth,请参阅 [Clerk 迁移指南](/zh/docs/self-hosting/migration/v2/auth/clerk-to-betterauth)。
|
||||
</Callout>
|
||||
|
||||
## Next Auth
|
||||
|
||||
<Callout type={'tip'}>
|
||||
如需从 NextAuth 迁移到 Better Auth,请参阅 [NextAuth 迁移指南](/zh/docs/self-hosting/advanced/auth/nextauth-to-betterauth)。
|
||||
如需从 NextAuth 迁移到 Better Auth,请参阅 [NextAuth 迁移指南](/zh/docs/self-hosting/migration/v2/auth/nextauth-to-betterauth)。
|
||||
</Callout>
|
||||
|
||||
在使用 NextAuth 之前,请先在 LobeHub 的环境变量中设置以下变量:
|
||||
@@ -51,25 +51,25 @@ LobeHub 与 Clerk 做了深度集成,能够为用户提供安全、便捷的
|
||||
目前支持的身份验证服务有:
|
||||
|
||||
<Cards>
|
||||
<Card href={'/zh/docs/self-hosting/advanced/auth/next-auth/auth0'} title={'Auth0'} />
|
||||
<Card href={'/zh/docs/self-hosting/auth/next-auth/auth0'} title={'Auth0'} />
|
||||
|
||||
<Card href={'/zh/docs/self-hosting/advanced/auth/next-auth/microsoft-entra-id'} title={'Microsoft Entra ID'} />
|
||||
<Card href={'/zh/docs/self-hosting/auth/next-auth/microsoft-entra-id'} title={'Microsoft Entra ID'} />
|
||||
|
||||
<Card href={'/zh/docs/self-hosting/advanced/auth/next-auth/authentik'} title={'Authentik'} />
|
||||
<Card href={'/zh/docs/self-hosting/auth/next-auth/authentik'} title={'Authentik'} />
|
||||
|
||||
<Card href={'/zh/docs/self-hosting/advanced/auth/next-auth/github'} title={'Github'} />
|
||||
<Card href={'/zh/docs/self-hosting/auth/next-auth/github'} title={'Github'} />
|
||||
|
||||
<Card href={'/zh/docs/self-hosting/advanced/auth/next-auth/zitadel'} title={'ZITADEL'} />
|
||||
<Card href={'/zh/docs/self-hosting/auth/next-auth/zitadel'} title={'ZITADEL'} />
|
||||
|
||||
<Card href={'/zh/docs/self-hosting/advanced/auth/next-auth/cloudflare-zero-trust'} title={'Cloudflare Zero Trust'} />
|
||||
<Card href={'/zh/docs/self-hosting/auth/next-auth/cloudflare-zero-trust'} title={'Cloudflare Zero Trust'} />
|
||||
|
||||
<Card href={'/zh/docs/self-hosting/advanced/auth/next-auth/authelia'} title={'Authelia'} />
|
||||
<Card href={'/zh/docs/self-hosting/auth/next-auth/authelia'} title={'Authelia'} />
|
||||
|
||||
<Card href={'/zh/docs/self-hosting/advanced/auth/next-auth/logto'} title={'Logto'} />
|
||||
<Card href={'/zh/docs/self-hosting/auth/next-auth/logto'} title={'Logto'} />
|
||||
|
||||
<Card href={'/zh/docs/self-hosting/advanced/auth/next-auth/keycloak'} title={'Keycloak'} />
|
||||
<Card href={'/zh/docs/self-hosting/auth/next-auth/keycloak'} title={'Keycloak'} />
|
||||
|
||||
<Card href={'/zh/docs/self-hosting/advanced/auth/next-auth/okta'} title={'Okta'} />
|
||||
<Card href={'/zh/docs/self-hosting/auth/next-auth/okta'} title={'Okta'} />
|
||||
</Cards>
|
||||
|
||||
点击即可查看对应平台的配置文档。
|
||||
|
||||
@@ -70,12 +70,14 @@ tags:
|
||||
|
||||
### Configure Environment Variables
|
||||
|
||||
| Environment Variable | Type | Description |
|
||||
| ----------------------- | -------- | --------------------------------------------------------------- |
|
||||
| `AUTH_SECRET` | Required | Session encryption key, generate with `openssl rand -base64 32` |
|
||||
| `AUTH_SSO_PROVIDERS` | Required | Set to `microsoft` |
|
||||
| `AUTH_MICROSOFT_ID` | Required | Application (client) ID |
|
||||
| `AUTH_MICROSOFT_SECRET` | Required | Client secret value |
|
||||
| Environment Variable | Type | Description |
|
||||
| ------------------------------ | -------- | --------------------------------------------------------------- |
|
||||
| `AUTH_SECRET` | Required | Session encryption key, generate with `openssl rand -base64 32` |
|
||||
| `AUTH_SSO_PROVIDERS` | Required | Set to `microsoft` |
|
||||
| `AUTH_MICROSOFT_ID` | Required | Application (client) ID |
|
||||
| `AUTH_MICROSOFT_SECRET` | Required | Client secret value |
|
||||
| `AUTH_MICROSOFT_AUTHORITY_URL` | Optional | Authority URL for Microsoft Entra ID |
|
||||
| `AUTH_MICROSOFT_TENANT_ID` | Optional | Directory (tenant) ID for single-tenant apps |
|
||||
|
||||
<Callout type={'info'}>
|
||||
**Alternative Environment Variables**: For backward compatibility, these
|
||||
@@ -99,10 +101,6 @@ tags:
|
||||
|
||||
## Common Issues
|
||||
|
||||
### Tenant Configuration
|
||||
|
||||
By default, LobeHub uses `common` tenant which allows both organizational and personal Microsoft accounts. If you need single-tenant configuration, you may need to customize the tenant settings.
|
||||
|
||||
### Client Secret Expiration
|
||||
|
||||
Microsoft client secrets have a maximum validity of 24 months. Remember to rotate secrets before they expire.
|
||||
|
||||
@@ -68,12 +68,14 @@ tags:
|
||||
|
||||
### 配置环境变量
|
||||
|
||||
| 环境变量 | 类型 | 描述 |
|
||||
| ----------------------- | -- | -------------------------------------- |
|
||||
| `AUTH_SECRET` | 必选 | 会话加密密钥,使用 `openssl rand -base64 32` 生成 |
|
||||
| `AUTH_SSO_PROVIDERS` | 必选 | 填写 `microsoft` |
|
||||
| `AUTH_MICROSOFT_ID` | 必选 | Application (client) ID |
|
||||
| `AUTH_MICROSOFT_SECRET` | 必选 | 客户端密钥值 |
|
||||
| 环境变量 | 类型 | 描述 |
|
||||
| ------------------------------ | -- | -------------------------------------- |
|
||||
| `AUTH_SECRET` | 必选 | 会话加密密钥,使用 `openssl rand -base64 32` 生成 |
|
||||
| `AUTH_SSO_PROVIDERS` | 必选 | 填写 `microsoft` |
|
||||
| `AUTH_MICROSOFT_ID` | 必选 | Application (client) ID |
|
||||
| `AUTH_MICROSOFT_SECRET` | 必选 | 客户端密钥值 |
|
||||
| `AUTH_MICROSOFT_AUTHORITY_URL` | 可选 | Microsoft Entra ID 的 Authority URL |
|
||||
| `AUTH_MICROSOFT_TENANT_ID` | 可选 | 单租户应用的 Directory (tenant) ID |
|
||||
|
||||
<Callout type={'info'}>
|
||||
**兼容的环境变量**:为了向后兼容,以下别名也支持:
|
||||
@@ -95,10 +97,6 @@ tags:
|
||||
|
||||
## 常见问题
|
||||
|
||||
### 租户配置
|
||||
|
||||
默认情况下,LobeHub 使用 `common` 租户,允许组织帐户和个人 Microsoft 帐户登录。如果需要单租户配置,可能需要自定义租户设置。
|
||||
|
||||
### 客户端密钥过期
|
||||
|
||||
Microsoft 客户端密钥最长有效期为 24 个月。请记得在过期前轮换密钥。
|
||||
|
||||
@@ -162,6 +162,20 @@ These settings are required for email verification and password reset features.
|
||||
- Default: `-`
|
||||
- Example: `xxxxxxxxxxxxxxxxxxxxxxxxxxxxx`
|
||||
|
||||
#### `AUTH_MICROSOFT_AUTHORITY_URL`
|
||||
|
||||
- Type: Optional
|
||||
- Description: Authority URL for the Microsoft Entra ID. This is used to specify the endpoint for authentication requests.
|
||||
- Default: `https://login.microsoftonline.com`
|
||||
- Example: `https://login.partner.microsoftonline.cn`
|
||||
|
||||
#### `AUTH_MICROSOFT_TENANT_ID`
|
||||
|
||||
- Type: Optional
|
||||
- Description: Directory (tenant) ID for single-tenant Microsoft Entra ID applications.
|
||||
- Default: `common`
|
||||
- Example: `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`
|
||||
|
||||
### AWS Cognito
|
||||
|
||||
#### `AUTH_COGNITO_ID`
|
||||
|
||||
@@ -160,6 +160,20 @@ LobeHub 在部署时提供了完善的身份验证服务能力,以下是相关
|
||||
- 默认值:`-`
|
||||
- 示例:`xxxxxxxxxxxxxxxxxxxxxxxxxxxxx`
|
||||
|
||||
#### `AUTH_MICROSOFT_AUTHORITY_URL`
|
||||
|
||||
- 类型:可选
|
||||
- 描述:Microsoft Entra ID 的 Authority URL。
|
||||
- 默认值:`https://login.microsoftonline.com`
|
||||
- 示例:`https://login.partner.microsoftonline.cn`
|
||||
|
||||
#### `AUTH_MICROSOFT_TENANT_ID`
|
||||
|
||||
- 类型:可选
|
||||
- 描述:单租户 Microsoft Entra ID 应用的 Directory (tenant) ID。
|
||||
- 默认值:`common`
|
||||
- 示例:`xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`
|
||||
|
||||
### AWS Cognito
|
||||
|
||||
#### `AUTH_COGNITO_ID`
|
||||
|
||||
@@ -57,7 +57,7 @@ For small self-hosted deployments, the simplest approach is to let users reset t
|
||||
|
||||
1. **Configure Email Service**
|
||||
|
||||
Set up email service for password reset functionality. See [Email Service Configuration](/docs/self-hosting/advanced/auth#email-service-configuration).
|
||||
Set up email service for password reset functionality. See [Email Service Configuration](/docs/self-hosting/auth#email-service-configuration).
|
||||
|
||||
2. **Update Environment Variables**
|
||||
|
||||
@@ -80,7 +80,7 @@ For small self-hosted deployments, the simplest approach is to let users reset t
|
||||
```
|
||||
|
||||
<Callout type={'tip'}>
|
||||
See [Authentication Service Configuration](/docs/self-hosting/advanced/auth) for complete environment variables and SSO provider setup.
|
||||
See [Authentication Service Configuration](/docs/self-hosting/auth) for complete environment variables and SSO provider setup.
|
||||
</Callout>
|
||||
|
||||
3. **Redeploy LobeHub**
|
||||
@@ -289,7 +289,7 @@ npx tsx scripts/clerk-to-betterauth/verify.ts
|
||||
After migration is complete, follow [Simple Migration - Step 2](#steps) to configure Better Auth environment variables and redeploy.
|
||||
|
||||
<Callout type={'tip'}>
|
||||
For complete Better Auth configuration, see [Authentication Service Configuration](/docs/self-hosting/advanced/auth).
|
||||
For complete Better Auth configuration, see [Authentication Service Configuration](/docs/self-hosting/auth).
|
||||
</Callout>
|
||||
|
||||
## What Gets Migrated
|
||||
@@ -339,11 +339,11 @@ This error occurs because the database schema is outdated. Run `pnpm db:migrate`
|
||||
## Related Reading
|
||||
|
||||
<Cards>
|
||||
<Card href={'/docs/self-hosting/advanced/auth/migration-internals'} title={'Migration Technical Deep Dive'} />
|
||||
<Card href={'/docs/self-hosting/migration/v2/auth/migration-internals'} title={'Migration Technical Deep Dive'} />
|
||||
|
||||
<Card href={'/docs/self-hosting/advanced/auth'} title={'Authentication Service Configuration'} />
|
||||
<Card href={'/docs/self-hosting/auth'} title={'Authentication Service Configuration'} />
|
||||
|
||||
<Card href={'/docs/self-hosting/environment-variables/auth'} title={'Auth Environment Variables'} />
|
||||
|
||||
<Card href={'/docs/self-hosting/advanced/auth/legacy'} title={'Legacy Authentication (NextAuth & Clerk)'} />
|
||||
<Card href={'/docs/self-hosting/auth/legacy'} title={'Legacy Authentication (NextAuth & Clerk)'} />
|
||||
</Cards>
|
||||
|
||||
@@ -55,7 +55,7 @@ tags:
|
||||
|
||||
1. **配置邮件服务**
|
||||
|
||||
设置邮件服务以支持密码重置功能。参阅 [邮件服务配置](/zh/docs/self-hosting/advanced/auth#邮件服务配置)。
|
||||
设置邮件服务以支持密码重置功能。参阅 [邮件服务配置](/zh/docs/self-hosting/auth#邮件服务配置)。
|
||||
|
||||
2. **更新环境变量**
|
||||
|
||||
@@ -78,7 +78,7 @@ tags:
|
||||
```
|
||||
|
||||
<Callout type={'tip'}>
|
||||
查阅 [身份验证服务配置](/zh/docs/self-hosting/advanced/auth) 了解完整的环境变量和 SSO 提供商配置。
|
||||
查阅 [身份验证服务配置](/zh/docs/self-hosting/auth) 了解完整的环境变量和 SSO 提供商配置。
|
||||
</Callout>
|
||||
|
||||
3. **重新部署 LobeHub**
|
||||
@@ -283,7 +283,7 @@ npx tsx scripts/clerk-to-betterauth/verify.ts
|
||||
迁移完成后,参照 [简单迁移 - 步骤 2](#步骤) 配置 Better Auth 环境变量并重新部署。
|
||||
|
||||
<Callout type={'tip'}>
|
||||
完整的 Better Auth 配置请参阅 [身份验证服务配置](/zh/docs/self-hosting/advanced/auth),包括所有支持的 SSO 提供商和邮件服务配置。
|
||||
完整的 Better Auth 配置请参阅 [身份验证服务配置](/zh/docs/self-hosting/auth),包括所有支持的 SSO 提供商和邮件服务配置。
|
||||
</Callout>
|
||||
|
||||
## 迁移内容对比
|
||||
@@ -333,11 +333,11 @@ npx tsx scripts/clerk-to-betterauth/verify.ts
|
||||
## 相关阅读
|
||||
|
||||
<Cards>
|
||||
<Card href={'/zh/docs/self-hosting/advanced/auth/migration-internals'} title={'迁移技术原理'} />
|
||||
<Card href={'/zh/docs/self-hosting/migration/v2/auth/migration-internals'} title={'迁移技术原理'} />
|
||||
|
||||
<Card href={'/zh/docs/self-hosting/advanced/auth'} title={'身份验证服务配置'} />
|
||||
<Card href={'/zh/docs/self-hosting/auth'} title={'身份验证服务配置'} />
|
||||
|
||||
<Card href={'/zh/docs/self-hosting/environment-variables/auth'} title={'认证相关环境变量'} />
|
||||
|
||||
<Card href={'/zh/docs/self-hosting/advanced/auth/legacy'} title={'旧版身份验证(NextAuth 和 Clerk)'} />
|
||||
<Card href={'/zh/docs/self-hosting/auth/legacy'} title={'旧版身份验证(NextAuth 和 Clerk)'} />
|
||||
</Cards>
|
||||
|
||||
@@ -15,7 +15,7 @@ tags:
|
||||
This document explains the technical principles behind authentication migration in LobeHub. It's intended for users with database and development experience who want to understand how migration works under the hood.
|
||||
|
||||
<Callout type={'info'}>
|
||||
For step-by-step migration instructions, see [NextAuth Migration](/docs/self-hosting/advanced/auth/nextauth-to-betterauth) or [Clerk Migration](/docs/self-hosting/advanced/auth/clerk-to-betterauth).
|
||||
For step-by-step migration instructions, see [NextAuth Migration](/docs/self-hosting/migration/v2/auth/nextauth-to-betterauth) or [Clerk Migration](/docs/self-hosting/migration/v2/auth/clerk-to-betterauth).
|
||||
</Callout>
|
||||
|
||||
## Core Database Schema
|
||||
@@ -193,9 +193,9 @@ This typically happens with simple migration when logging in with a secondary em
|
||||
## Related Reading
|
||||
|
||||
<Cards>
|
||||
<Card href={'/docs/self-hosting/advanced/auth/nextauth-to-betterauth'} title={'NextAuth Migration Guide'} />
|
||||
<Card href={'/docs/self-hosting/migration/v2/auth/nextauth-to-betterauth'} title={'NextAuth Migration Guide'} />
|
||||
|
||||
<Card href={'/docs/self-hosting/advanced/auth/clerk-to-betterauth'} title={'Clerk Migration Guide'} />
|
||||
<Card href={'/docs/self-hosting/migration/v2/auth/clerk-to-betterauth'} title={'Clerk Migration Guide'} />
|
||||
|
||||
<Card href={'/docs/self-hosting/advanced/auth'} title={'Authentication Configuration'} />
|
||||
<Card href={'/docs/self-hosting/auth'} title={'Authentication Configuration'} />
|
||||
</Cards>
|
||||
|
||||
@@ -14,7 +14,7 @@ tags:
|
||||
本文档解释 LobeHub 认证迁移的技术原理,适合有数据库和开发经验的用户,帮助理解迁移的底层逻辑。
|
||||
|
||||
<Callout type={'info'}>
|
||||
如需分步迁移指南,请参阅 [NextAuth 迁移](/docs/self-hosting/advanced/auth/nextauth-to-betterauth) 或 [Clerk 迁移](/docs/self-hosting/advanced/auth/clerk-to-betterauth)。
|
||||
如需分步迁移指南,请参阅 [NextAuth 迁移](/zh/docs/self-hosting/migration/v2/auth/nextauth-to-betterauth) 或 [Clerk 迁移](/zh/docs/self-hosting/migration/v2/auth/clerk-to-betterauth)。
|
||||
</Callout>
|
||||
|
||||
## 核心数据库 Schema
|
||||
@@ -192,9 +192,9 @@ tags:
|
||||
## 相关阅读
|
||||
|
||||
<Cards>
|
||||
<Card href={'/docs/self-hosting/advanced/auth/nextauth-to-betterauth'} title={'NextAuth 迁移指南'} />
|
||||
<Card href={'/zh/docs/self-hosting/migration/v2/auth/nextauth-to-betterauth'} title={'NextAuth 迁移指南'} />
|
||||
|
||||
<Card href={'/docs/self-hosting/advanced/auth/clerk-to-betterauth'} title={'Clerk 迁移指南'} />
|
||||
<Card href={'/zh/docs/self-hosting/migration/v2/auth/clerk-to-betterauth'} title={'Clerk 迁移指南'} />
|
||||
|
||||
<Card href={'/docs/self-hosting/advanced/auth'} title={'认证服务配置'} />
|
||||
<Card href={'/zh/docs/self-hosting/auth'} title={'认证服务配置'} />
|
||||
</Cards>
|
||||
|
||||
@@ -54,23 +54,23 @@ This guide helps you migrate your existing NextAuth-based LobeHub deployment to
|
||||
|
||||
SSO provider environment variables follow the same format: `AUTH_<PROVIDER>_ID` and `AUTH_<PROVIDER>_SECRET`.
|
||||
|
||||
| NextAuth (Old) | Better Auth (New) | Notes |
|
||||
| ----------------------------------- | ----------------------- | ------------------- |
|
||||
| `AUTH_GITHUB_ID` | `AUTH_GITHUB_ID` | ✅ Unchanged |
|
||||
| `AUTH_GITHUB_SECRET` | `AUTH_GITHUB_SECRET` | ✅ Unchanged |
|
||||
| `AUTH_GOOGLE_ID` | `AUTH_GOOGLE_ID` | ✅ Unchanged |
|
||||
| `AUTH_GOOGLE_SECRET` | `AUTH_GOOGLE_SECRET` | ✅ Unchanged |
|
||||
| `AUTH_AUTH0_ID` | `AUTH_AUTH0_ID` | ✅ Unchanged |
|
||||
| `AUTH_AUTH0_SECRET` | `AUTH_AUTH0_SECRET` | ✅ Unchanged |
|
||||
| `AUTH_AUTH0_ISSUER` | `AUTH_AUTH0_ISSUER` | ✅ Unchanged |
|
||||
| `AUTH_AUTHENTIK_ID` | `AUTH_AUTHENTIK_ID` | ✅ Unchanged |
|
||||
| `AUTH_AUTHENTIK_SECRET` | `AUTH_AUTHENTIK_SECRET` | ✅ Unchanged |
|
||||
| `AUTH_AUTHENTIK_ISSUER` | `AUTH_AUTHENTIK_ISSUER` | ✅ Unchanged |
|
||||
| `microsoft-entra-id` | `microsoft` | ⚠️ Provider renamed |
|
||||
| `AUTH_MICROSOFT_ENTRA_ID_ID` | `AUTH_MICROSOFT_ID` | ⚠️ Variable renamed |
|
||||
| `AUTH_MICROSOFT_ENTRA_ID_SECRET` | `AUTH_MICROSOFT_SECRET` | ⚠️ Variable renamed |
|
||||
| `AUTH_MICROSOFT_ENTRA_ID_TENANT_ID` | - | ❌ No longer needed |
|
||||
| `AUTH_MICROSOFT_ENTRA_ID_BASE_URL` | - | ❌ No longer needed |
|
||||
| NextAuth (Old) | Better Auth (New) | Notes |
|
||||
| ----------------------------------- | ------------------------------ | ------------------- |
|
||||
| `AUTH_GITHUB_ID` | `AUTH_GITHUB_ID` | ✅ Unchanged |
|
||||
| `AUTH_GITHUB_SECRET` | `AUTH_GITHUB_SECRET` | ✅ Unchanged |
|
||||
| `AUTH_GOOGLE_ID` | `AUTH_GOOGLE_ID` | ✅ Unchanged |
|
||||
| `AUTH_GOOGLE_SECRET` | `AUTH_GOOGLE_SECRET` | ✅ Unchanged |
|
||||
| `AUTH_AUTH0_ID` | `AUTH_AUTH0_ID` | ✅ Unchanged |
|
||||
| `AUTH_AUTH0_SECRET` | `AUTH_AUTH0_SECRET` | ✅ Unchanged |
|
||||
| `AUTH_AUTH0_ISSUER` | `AUTH_AUTH0_ISSUER` | ✅ Unchanged |
|
||||
| `AUTH_AUTHENTIK_ID` | `AUTH_AUTHENTIK_ID` | ✅ Unchanged |
|
||||
| `AUTH_AUTHENTIK_SECRET` | `AUTH_AUTHENTIK_SECRET` | ✅ Unchanged |
|
||||
| `AUTH_AUTHENTIK_ISSUER` | `AUTH_AUTHENTIK_ISSUER` | ✅ Unchanged |
|
||||
| `microsoft-entra-id` | `microsoft` | ⚠️ Provider renamed |
|
||||
| `AUTH_MICROSOFT_ENTRA_ID_ID` | `AUTH_MICROSOFT_ID` | ⚠️ Variable renamed |
|
||||
| `AUTH_MICROSOFT_ENTRA_ID_SECRET` | `AUTH_MICROSOFT_SECRET` | ⚠️ Variable renamed |
|
||||
| `AUTH_MICROSOFT_ENTRA_ID_TENANT_ID` | `AUTH_MICROSOFT_TENANT_ID` | ⚠️ Variable renamed |
|
||||
| `AUTH_MICROSOFT_ENTRA_ID_BASE_URL` | `AUTH_MICROSOFT_AUTHORITY_URL` | ⚠️ Variable renamed |
|
||||
|
||||
<Callout type={'warning'}>
|
||||
**Note**: Microsoft Entra ID provider name changed from `microsoft-entra-id` to `microsoft`, and the environment variable prefix changed from `AUTH_MICROSOFT_ENTRA_ID_` to `AUTH_MICROSOFT_`.
|
||||
@@ -133,7 +133,7 @@ For small self-hosted deployments, the simplest approach is to let users re-logi
|
||||
```
|
||||
|
||||
<Callout type={'tip'}>
|
||||
See [Authentication Service Configuration](/docs/self-hosting/advanced/auth) for complete environment variables and SSO provider setup.
|
||||
See [Authentication Service Configuration](/docs/self-hosting/auth) for complete environment variables and SSO provider setup.
|
||||
</Callout>
|
||||
|
||||
2. **Redeploy LobeHub**
|
||||
@@ -286,7 +286,7 @@ npx tsx scripts/nextauth-to-betterauth/verify.ts
|
||||
After migration is complete, follow [Simple Migration - Step 1](#steps) to configure Better Auth environment variables and redeploy.
|
||||
|
||||
<Callout type={'tip'}>
|
||||
For complete Better Auth configuration, see [Authentication Service Configuration](/docs/self-hosting/advanced/auth), including all supported SSO providers and email service configuration.
|
||||
For complete Better Auth configuration, see [Authentication Service Configuration](/docs/self-hosting/auth), including all supported SSO providers and email service configuration.
|
||||
</Callout>
|
||||
|
||||
## What Gets Migrated
|
||||
@@ -360,19 +360,19 @@ For identity providers like Casdoor or Logto, users may not have an email config
|
||||
Solution:
|
||||
|
||||
1. First configure the Webhook in LobeHub to sync user data from the identity provider:
|
||||
- [Casdoor Webhook Configuration](/docs/self-hosting/advanced/auth/providers/casdoor)
|
||||
- [Logto Webhook Configuration](/docs/self-hosting/advanced/auth/providers/logto)
|
||||
- [Casdoor Webhook Configuration](/docs/self-hosting/auth/providers/casdoor)
|
||||
- [Logto Webhook Configuration](/docs/self-hosting/auth/providers/logto)
|
||||
2. Then configure the user's email in the identity provider's admin console
|
||||
3. The user data will be synced to LobeHub via Webhook, and the user can then log in
|
||||
|
||||
## Related Reading
|
||||
|
||||
<Cards>
|
||||
<Card href={'/docs/self-hosting/advanced/auth/migration-internals'} title={'Migration Technical Deep Dive'} />
|
||||
<Card href={'/docs/self-hosting/migration/v2/auth/migration-internals'} title={'Migration Technical Deep Dive'} />
|
||||
|
||||
<Card href={'/docs/self-hosting/advanced/auth'} title={'Authentication Service Configuration'} />
|
||||
<Card href={'/docs/self-hosting/auth'} title={'Authentication Service Configuration'} />
|
||||
|
||||
<Card href={'/docs/self-hosting/environment-variables/auth'} title={'Auth Environment Variables'} />
|
||||
|
||||
<Card href={'/docs/self-hosting/advanced/auth/legacy'} title={'Legacy Authentication (NextAuth & Clerk)'} />
|
||||
<Card href={'/docs/self-hosting/auth/legacy'} title={'Legacy Authentication (NextAuth & Clerk)'} />
|
||||
</Cards>
|
||||
|
||||
@@ -52,21 +52,23 @@ tags:
|
||||
|
||||
SSO 提供商的环境变量格式保持一致:`AUTH_<PROVIDER>_ID` 和 `AUTH_<PROVIDER>_SECRET`。
|
||||
|
||||
| NextAuth (旧) | Better Auth (新) | 说明 |
|
||||
| -------------------------------- | ----------------------- | ---------------- |
|
||||
| `AUTH_GITHUB_ID` | `AUTH_GITHUB_ID` | ✅ 保持不变 |
|
||||
| `AUTH_GITHUB_SECRET` | `AUTH_GITHUB_SECRET` | ✅ 保持不变 |
|
||||
| `AUTH_GOOGLE_ID` | `AUTH_GOOGLE_ID` | ✅ 保持不变 |
|
||||
| `AUTH_GOOGLE_SECRET` | `AUTH_GOOGLE_SECRET` | ✅ 保持不变 |
|
||||
| `AUTH_AUTH0_ID` | `AUTH_AUTH0_ID` | ✅ 保持不变 |
|
||||
| `AUTH_AUTH0_SECRET` | `AUTH_AUTH0_SECRET` | ✅ 保持不变 |
|
||||
| `AUTH_AUTH0_ISSUER` | `AUTH_AUTH0_ISSUER` | ✅ 保持不变 |
|
||||
| `AUTH_AUTHENTIK_ID` | `AUTH_AUTHENTIK_ID` | ✅ 保持不变 |
|
||||
| `AUTH_AUTHENTIK_SECRET` | `AUTH_AUTHENTIK_SECRET` | ✅ 保持不变 |
|
||||
| `AUTH_AUTHENTIK_ISSUER` | `AUTH_AUTHENTIK_ISSUER` | ✅ 保持不变 |
|
||||
| `microsoft-entra-id` | `microsoft` | ⚠️ provider 名称变更 |
|
||||
| `AUTH_MICROSOFT_ENTRA_ID_ID` | `AUTH_MICROSOFT_ID` | ⚠️ 变量名变更 |
|
||||
| `AUTH_MICROSOFT_ENTRA_ID_SECRET` | `AUTH_MICROSOFT_SECRET` | ⚠️ 变量名变更 |
|
||||
| NextAuth (旧) | Better Auth (新) | 说明 |
|
||||
| ----------------------------------- | ------------------------------ | ---------------- |
|
||||
| `AUTH_GITHUB_ID` | `AUTH_GITHUB_ID` | ✅ 保持不变 |
|
||||
| `AUTH_GITHUB_SECRET` | `AUTH_GITHUB_SECRET` | ✅ 保持不变 |
|
||||
| `AUTH_GOOGLE_ID` | `AUTH_GOOGLE_ID` | ✅ 保持不变 |
|
||||
| `AUTH_GOOGLE_SECRET` | `AUTH_GOOGLE_SECRET` | ✅ 保持不变 |
|
||||
| `AUTH_AUTH0_ID` | `AUTH_AUTH0_ID` | ✅ 保持不变 |
|
||||
| `AUTH_AUTH0_SECRET` | `AUTH_AUTH0_SECRET` | ✅ 保持不变 |
|
||||
| `AUTH_AUTH0_ISSUER` | `AUTH_AUTH0_ISSUER` | ✅ 保持不变 |
|
||||
| `AUTH_AUTHENTIK_ID` | `AUTH_AUTHENTIK_ID` | ✅ 保持不变 |
|
||||
| `AUTH_AUTHENTIK_SECRET` | `AUTH_AUTHENTIK_SECRET` | ✅ 保持不变 |
|
||||
| `AUTH_AUTHENTIK_ISSUER` | `AUTH_AUTHENTIK_ISSUER` | ✅ 保持不变 |
|
||||
| `microsoft-entra-id` | `microsoft` | ⚠️ provider 名称变更 |
|
||||
| `AUTH_MICROSOFT_ENTRA_ID_ID` | `AUTH_MICROSOFT_ID` | ⚠️ 变量名变更 |
|
||||
| `AUTH_MICROSOFT_ENTRA_ID_SECRET` | `AUTH_MICROSOFT_SECRET` | ⚠️ 变量名变更 |
|
||||
| `AUTH_MICROSOFT_ENTRA_ID_TENANT_ID` | `AUTH_MICROSOFT_TENANT_ID` | ⚠️ 变量名变更 |
|
||||
| `AUTH_MICROSOFT_ENTRA_ID_BASE_URL` | `AUTH_MICROSOFT_AUTHORITY_URL` | ⚠️ 变量名变更 |
|
||||
|
||||
<Callout type={'warning'}>
|
||||
**注意**:Microsoft Entra ID 的 provider 名称从 `microsoft-entra-id` 改为 `microsoft`,相应的环境变量前缀也从 `AUTH_MICROSOFT_ENTRA_ID_` 改为 `AUTH_MICROSOFT_`。
|
||||
@@ -128,7 +130,7 @@ Better Auth 支持更多功能,以下是新增的环境变量:
|
||||
```
|
||||
|
||||
<Callout type={'tip'}>
|
||||
查阅 [身份验证服务配置](/zh/docs/self-hosting/advanced/auth) 了解完整的环境变量和 SSO 提供商配置。
|
||||
查阅 [身份验证服务配置](/zh/docs/self-hosting/auth) 了解完整的环境变量和 SSO 提供商配置。
|
||||
</Callout>
|
||||
|
||||
2. **重新部署 LobeHub**
|
||||
@@ -280,7 +282,7 @@ npx tsx scripts/nextauth-to-betterauth/verify.ts
|
||||
迁移完成后,参照 [简单迁移 - 步骤 1](#步骤) 配置 Better Auth 环境变量并重新部署。
|
||||
|
||||
<Callout type={'tip'}>
|
||||
完整的 Better Auth 配置请参阅 [身份验证服务配置](/zh/docs/self-hosting/advanced/auth),包括所有支持的 SSO 提供商和邮件服务配置。
|
||||
完整的 Better Auth 配置请参阅 [身份验证服务配置](/zh/docs/self-hosting/auth),包括所有支持的 SSO 提供商和邮件服务配置。
|
||||
</Callout>
|
||||
|
||||
## 迁移内容对比
|
||||
@@ -354,19 +356,19 @@ npx tsx scripts/nextauth-to-betterauth/verify.ts
|
||||
解决方案:
|
||||
|
||||
1. 先在 LobeHub 中配置身份提供商的 Webhook 以同步用户数据:
|
||||
- [Casdoor Webhook 配置](/zh/docs/self-hosting/advanced/auth/providers/casdoor)
|
||||
- [Logto Webhook 配置](/zh/docs/self-hosting/advanced/auth/providers/logto)
|
||||
- [Casdoor Webhook 配置](/zh/docs/self-hosting/auth/providers/casdoor)
|
||||
- [Logto Webhook 配置](/zh/docs/self-hosting/auth/providers/logto)
|
||||
2. 然后在身份提供商的管理后台为用户配置邮箱
|
||||
3. 用户数据通过 Webhook 同步到 LobeHub 后即可正常登录
|
||||
|
||||
## 相关阅读
|
||||
|
||||
<Cards>
|
||||
<Card href={'/zh/docs/self-hosting/advanced/auth/migration-internals'} title={'迁移技术原理'} />
|
||||
<Card href={'/zh/docs/self-hosting/migration/v2/auth/migration-internals'} title={'迁移技术原理'} />
|
||||
|
||||
<Card href={'/zh/docs/self-hosting/advanced/auth'} title={'身份验证服务配置'} />
|
||||
<Card href={'/zh/docs/self-hosting/auth'} title={'身份验证服务配置'} />
|
||||
|
||||
<Card href={'/zh/docs/self-hosting/environment-variables/auth'} title={'认证相关环境变量'} />
|
||||
|
||||
<Card href={'/zh/docs/self-hosting/advanced/auth/legacy'} title={'旧版身份验证(NextAuth 和 Clerk)'} />
|
||||
<Card href={'/zh/docs/self-hosting/auth/legacy'} title={'旧版身份验证(NextAuth 和 Clerk)'} />
|
||||
</Cards>
|
||||
|
||||
@@ -60,11 +60,11 @@ LobeHub 2.0 only supports Better Auth authentication system. NextAuth and Clerk
|
||||
|
||||
### Migrating from NextAuth
|
||||
|
||||
See the [NextAuth Migration Guide](/docs/self-hosting/advanced/auth/nextauth-to-betterauth).
|
||||
See the [NextAuth Migration Guide](/docs/self-hosting/migration/v2/auth/nextauth-to-betterauth).
|
||||
|
||||
### Migrating from Clerk
|
||||
|
||||
See the [Clerk Migration Guide](/docs/self-hosting/advanced/auth/clerk-to-betterauth).
|
||||
See the [Clerk Migration Guide](/docs/self-hosting/migration/v2/auth/clerk-to-betterauth).
|
||||
|
||||
## Database Mode Changes
|
||||
|
||||
|
||||
@@ -58,11 +58,11 @@ LobeHub 2.0 仅支持 Better Auth 认证系统,不再支持 NextAuth 和 Clerk
|
||||
|
||||
### 从 NextAuth 迁移
|
||||
|
||||
请参阅 [NextAuth 迁移指南](/zh/docs/self-hosting/advanced/auth/nextauth-to-betterauth)。
|
||||
请参阅 [NextAuth 迁移指南](/zh/docs/self-hosting/migration/v2/auth/nextauth-to-betterauth)。
|
||||
|
||||
### 从 Clerk 迁移
|
||||
|
||||
请参阅 [Clerk 迁移指南](/zh/docs/self-hosting/advanced/auth/clerk-to-betterauth)。
|
||||
请参阅 [Clerk 迁移指南](/zh/docs/self-hosting/migration/v2/auth/clerk-to-betterauth)。
|
||||
|
||||
## 数据库模式变更
|
||||
|
||||
|
||||
@@ -265,7 +265,7 @@ Generally, to fully run the LobeHub database version, you will need at least the
|
||||
|
||||
These services can be combined through self-hosting or online cloud services to meet various deployment needs. In this article, we provide a Docker Compose configuration entirely based on open-source self-hosted services, which can be used directly to start the LobeHub database version or modified to suit your requirements.
|
||||
|
||||
We use [RustFS](https://github.com/rustfs/rustfs) as the local S3 object storage service by default. To configure SSO authentication services, please refer to the [Authentication Services](/docs/self-hosting/advanced/auth) documentation.
|
||||
We use [RustFS](https://github.com/rustfs/rustfs) as the local S3 object storage service by default. To configure SSO authentication services, please refer to the [Authentication Services](/docs/self-hosting/auth) documentation.
|
||||
|
||||
<Callout type="warning">
|
||||
If your network topology is complex, please make sure these services can communicate properly
|
||||
@@ -350,7 +350,7 @@ If `INTERNAL_APP_URL` is not set, it defaults to `APP_URL`.
|
||||
|
||||
## Configuring Authentication
|
||||
|
||||
To configure SSO authentication services (such as Casdoor, Logto, etc.), please refer to the [Authentication Services](/docs/self-hosting/advanced/auth) documentation.
|
||||
To configure SSO authentication services (such as Casdoor, Logto, etc.), please refer to the [Authentication Services](/docs/self-hosting/auth) documentation.
|
||||
|
||||
[docker-pulls-link]: https://hub.docker.com/r/lobehub/lobehub
|
||||
[docker-pulls-shield]: https://img.shields.io/docker/pulls/lobehub/lobehub?color=45cc11&labelColor=black&style=flat-square
|
||||
|
||||
@@ -262,7 +262,7 @@ mv .env.zh-CN.example .env
|
||||
|
||||
这些服务可以通过自建或者在线云服务组合搭配,以满足不同层次的部署需求。本文中,我们提供了完全基于开源自建服务的 Docker Compose 配置,你可以直接使用这份配置文件来启动 LobeHub,也可以对之进行修改以适应你的需求。
|
||||
|
||||
我们默认使用 [RustFS](https://github.com/rustfs/rustfs) 作为本地 S3 对象存储服务。如需配置 SSO 登录鉴权服务,请参考 [身份验证服务](/zh/docs/self-hosting/advanced/auth) 文档。
|
||||
我们默认使用 [RustFS](https://github.com/rustfs/rustfs) 作为本地 S3 对象存储服务。如需配置 SSO 登录鉴权服务,请参考 [身份验证服务](/zh/docs/self-hosting/auth) 文档。
|
||||
|
||||
<Callout type="warning">
|
||||
如果你的网络拓扑较为复杂,请先确保在你的网络环境中这些服务能够正常通讯。
|
||||
@@ -346,7 +346,7 @@ environment:
|
||||
|
||||
## 配置身份验证
|
||||
|
||||
如需配置 SSO 登录鉴权服务(如 Casdoor、Logto 等),请参考 [身份验证服务](/zh/docs/self-hosting/advanced/auth) 文档。
|
||||
如需配置 SSO 登录鉴权服务(如 Casdoor、Logto 等),请参考 [身份验证服务](/zh/docs/self-hosting/auth) 文档。
|
||||
|
||||
[docker-pulls-link]: https://hub.docker.com/r/lobehub/lobehub
|
||||
[docker-pulls-shield]: https://img.shields.io/docker/pulls/lobehub/lobehub?color=45cc11&labelColor=black&style=flat-square
|
||||
|
||||
@@ -61,7 +61,7 @@ You also need to configure the `JWKS_KEY` environment variable for signing and v
|
||||
<GenerateJWKSKey />
|
||||
|
||||
<Callout type={'info'}>
|
||||
For advanced features like SSO providers, magic link login, and email verification, see [Authentication Service](/docs/self-hosting/advanced/auth).
|
||||
For advanced features like SSO providers, magic link login, and email verification, see [Authentication Service](/docs/self-hosting/auth).
|
||||
</Callout>
|
||||
|
||||
## 2. Deploying the database on Dokploy
|
||||
|
||||
@@ -62,7 +62,7 @@ S3_ENABLE_PATH_STYLE=
|
||||
<GenerateJWKSKey />
|
||||
|
||||
<Callout type={'info'}>
|
||||
如需 SSO 登录、魔法链接登录、邮箱验证等高级功能,请参阅 [身份验证服务](/zh/docs/self-hosting/advanced/auth)。
|
||||
如需 SSO 登录、魔法链接登录、邮箱验证等高级功能,请参阅 [身份验证服务](/zh/docs/self-hosting/auth)。
|
||||
</Callout>
|
||||
|
||||
## 二、在 Dokploy 上部署数据库
|
||||
|
||||
@@ -112,7 +112,7 @@ The server-side database needs to be paired with a user authentication service t
|
||||
With these variables, users can register and login with email and password.
|
||||
|
||||
<Callout type={'info'}>
|
||||
For advanced features like SSO providers, magic link login, and email verification, see [Authentication Service](/docs/self-hosting/advanced/auth).
|
||||
For advanced features like SSO providers, magic link login, and email verification, see [Authentication Service](/docs/self-hosting/auth).
|
||||
</Callout>
|
||||
</Steps>
|
||||
|
||||
|
||||
@@ -112,7 +112,7 @@ tags:
|
||||
配置这些变量后,用户即可使用邮箱和密码注册登录。
|
||||
|
||||
<Callout type={'info'}>
|
||||
如需 SSO 登录、魔法链接登录、邮箱验证等高级功能,请参阅 [身份验证服务](/zh/docs/self-hosting/advanced/auth)。
|
||||
如需 SSO 登录、魔法链接登录、邮箱验证等高级功能,请参阅 [身份验证服务](/zh/docs/self-hosting/auth)。
|
||||
</Callout>
|
||||
</Steps>
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ Here is the process for deploying the LobeHub server database version on Zeabur:
|
||||
|
||||
Fill in those variables into your LobeHub service on Zeabur, here is a more detailed guide for [editing environment variables on Zeabur](https://zeabur.com/docs/deploy/variables).
|
||||
|
||||
For detailed configuration of Logto, refer to [this document](/docs/self-hosting/advanced/auth/providers/logto).
|
||||
For detailed configuration of Logto, refer to [this document](/docs/self-hosting/auth/providers/logto).
|
||||
|
||||
### Access your LobeHub Instance
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ tags:
|
||||
|
||||
使用你刚绑定的域名来访问你的 Logto 控制台,创建一个新项目以获得对应的客户端 ID 与密钥,将它们填入你的 LobeHub 服务的变量中。关于如何填入变量,可以参照 [Zeabur 的官方文档](https://zeabur.com/docs/deploy/variables)。
|
||||
|
||||
Logto 的详细配置可以参考[这篇文档](/zh/docs/self-hosting/advanced/auth/providers/logto)。
|
||||
Logto 的详细配置可以参考[这篇文档](/zh/docs/self-hosting/auth/providers/logto)。
|
||||
|
||||
### 访问你的 LobeHub
|
||||
|
||||
|
||||
@@ -32,6 +32,8 @@
|
||||
"chatList.longMessageDetail": "View Details",
|
||||
"clearCurrentMessages": "Clear current session messages",
|
||||
"compressedHistory": "Compressed History",
|
||||
"compression.cancel": "Uncompress",
|
||||
"compression.cancelConfirm": "Are you sure you want to uncompress? This will restore the original messages.",
|
||||
"compression.history": "History",
|
||||
"compression.summary": "Summary",
|
||||
"confirmClearCurrentMessages": "You are about to clear the current session messages. Once cleared, they cannot be retrieved. Please confirm your action.",
|
||||
|
||||
@@ -490,6 +490,15 @@
|
||||
"user.noForkedAgentGroups": "No forked Agent Groups yet",
|
||||
"user.noForkedAgents": "No forked Agents yet",
|
||||
"user.publishedAgents": "Created Agents",
|
||||
"user.publishedGroups": "Created Groups",
|
||||
"user.searchPlaceholder": "Search by name or description...",
|
||||
"user.statusFilter.all": "All",
|
||||
"user.statusFilter.archived": "Archived",
|
||||
"user.statusFilter.deprecated": "Deprecated",
|
||||
"user.statusFilter.favorite": "Favorite",
|
||||
"user.statusFilter.forked": "Forked",
|
||||
"user.statusFilter.published": "Published",
|
||||
"user.statusFilter.unpublished": "Under Review",
|
||||
"user.tabs.favorites": "Favorites",
|
||||
"user.tabs.forkedAgents": "Forked",
|
||||
"user.tabs.publishedAgents": "Created",
|
||||
|
||||
@@ -32,6 +32,8 @@
|
||||
"chatList.longMessageDetail": "查看详情",
|
||||
"clearCurrentMessages": "清空当前会话消息",
|
||||
"compressedHistory": "压缩历史",
|
||||
"compression.cancel": "取消压缩",
|
||||
"compression.cancelConfirm": "确定要取消压缩吗?这将恢复原始消息。",
|
||||
"compression.history": "历史记录",
|
||||
"compression.summary": "摘要",
|
||||
"confirmClearCurrentMessages": "确认清空当前会话消息吗?清空后无法恢复",
|
||||
|
||||
@@ -490,6 +490,15 @@
|
||||
"user.noForkedAgentGroups": "尚无已派生的代理组",
|
||||
"user.noForkedAgents": "尚无已派生的代理",
|
||||
"user.publishedAgents": "创作的助理",
|
||||
"user.publishedGroups": "创作的群组",
|
||||
"user.searchPlaceholder": "搜索名称或描述...",
|
||||
"user.statusFilter.all": "全部",
|
||||
"user.statusFilter.archived": "已归档",
|
||||
"user.statusFilter.deprecated": "已废弃",
|
||||
"user.statusFilter.favorite": "已收藏",
|
||||
"user.statusFilter.forked": "已派生",
|
||||
"user.statusFilter.published": "已发布",
|
||||
"user.statusFilter.unpublished": "审核中",
|
||||
"user.tabs.favorites": "收藏",
|
||||
"user.tabs.forkedAgents": "已派生",
|
||||
"user.tabs.publishedAgents": "创作",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@lobehub/lobehub",
|
||||
"version": "2.1.6",
|
||||
"version": "2.1.15",
|
||||
"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.",
|
||||
"keywords": [
|
||||
"framework",
|
||||
|
||||
@@ -10,6 +10,26 @@ export const systemPrompt = `You have access to the Notebook tool for creating a
|
||||
Note: The list of existing documents is automatically provided in the context, so you don't need to query for it.
|
||||
</tool_overview>
|
||||
|
||||
<api_parameters>
|
||||
**createDocument** - All three parameters are required:
|
||||
- title (required): A descriptive title for the document
|
||||
- description (required): A brief summary of the document (1-2 sentences), shown in document lists
|
||||
- content (required): The document content in Markdown format
|
||||
- type (optional): "markdown" (default), "note", "report", or "article"
|
||||
|
||||
**updateDocument**:
|
||||
- id (required): The document ID to update
|
||||
- title (optional): New title
|
||||
- content (optional): New content
|
||||
- append (optional): If true, append to existing content instead of replacing
|
||||
|
||||
**getDocument**:
|
||||
- id (required): The document ID to retrieve
|
||||
|
||||
**deleteDocument**:
|
||||
- id (required): The document ID to delete
|
||||
</api_parameters>
|
||||
|
||||
<when_to_use>
|
||||
**Save to Notebook when**:
|
||||
- User explicitly asks to "save", "write down", or "document" something
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
CREATE INDEX IF NOT EXISTS "messages_message_group_id_idx" ON "messages" USING btree ("message_group_id");
|
||||
File diff suppressed because it is too large
Load Diff
@@ -532,7 +532,14 @@
|
||||
"when": 1769362978088,
|
||||
"tag": "0075_add_user_memory_persona",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 76,
|
||||
"version": "7",
|
||||
"when": 1770179814971,
|
||||
"tag": "0076_add_message_group_index",
|
||||
"breakpoints": true
|
||||
}
|
||||
],
|
||||
"version": "6"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,6 +149,7 @@ export const messages = pgTable(
|
||||
index('messages_thread_id_idx').on(table.threadId),
|
||||
index('messages_agent_id_idx').on(table.agentId),
|
||||
index('messages_group_id_idx').on(table.groupId),
|
||||
index('messages_message_group_id_idx').on(table.messageGroupId),
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@@ -62,6 +62,7 @@ export interface DiscoverAssistantItem extends Omit<LobeAgentSettings, 'meta'>,
|
||||
status?: AgentStatus;
|
||||
tokenUsage: number;
|
||||
type?: AgentType;
|
||||
updatedAt?: string;
|
||||
userName?: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -67,6 +67,14 @@ export interface DiscoverUserInfo {
|
||||
export interface DiscoverUserProfile {
|
||||
agentGroups?: DiscoverGroupAgentItem[];
|
||||
agents: DiscoverAssistantItem[];
|
||||
/**
|
||||
* Agent groups favorited by the user
|
||||
*/
|
||||
favoriteAgentGroups?: DiscoverGroupAgentItem[];
|
||||
/**
|
||||
* Agents favorited by the user
|
||||
*/
|
||||
favoriteAgents?: DiscoverAssistantItem[];
|
||||
/**
|
||||
* Agent groups forked by the user
|
||||
*/
|
||||
|
||||
@@ -202,7 +202,7 @@ export const handleSingle = async (
|
||||
* to avoid breaking structured XML/JSON-like content mid-way.
|
||||
*
|
||||
* Bisection example (8 segments, keep newest):
|
||||
* try 4 (fits?) → yes → try 6 → no → try 5 → yes ⇒ best=5
|
||||
* try 4 (fits?) -> yes -> try 6 -> no -> try 5 -> yes => best=5
|
||||
* if compact retry needed, repeat with build(true) and pick the better fit.
|
||||
*
|
||||
* This minimizes structural breakage by preferring whole built segments and only truncating the last one as a last resort.
|
||||
|
||||
@@ -2,9 +2,9 @@ import { Pricing, PricingUnit, PricingUnitName } from 'model-bank';
|
||||
|
||||
/**
|
||||
* Internal helper to extract the displayed unit rate from a pricing unit by strategy
|
||||
* - fixed → rate
|
||||
* - tiered → tiers[0].rate
|
||||
* - lookup → first price value
|
||||
* - fixed: rate
|
||||
* - tiered: tiers[0].rate
|
||||
* - lookup: first price value
|
||||
*/
|
||||
const getRateFromUnit = (unit: PricingUnit): number | undefined => {
|
||||
switch (unit.strategy) {
|
||||
@@ -39,9 +39,9 @@ export const getUnitRateByName = (
|
||||
|
||||
/**
|
||||
* Get text input unit rate from pricing
|
||||
* - fixed → rate
|
||||
* - tiered → tiers[0].rate
|
||||
* - lookup → Object.values(lookup.prices)[0]
|
||||
* - fixed: rate
|
||||
* - tiered: tiers[0].rate
|
||||
* - lookup: Object.values(lookup.prices)[0]
|
||||
*/
|
||||
export function getTextInputUnitRate(pricing?: Pricing): number | undefined {
|
||||
return getUnitRateByName(pricing, 'textInput');
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* IMPORTANT: Keep this file as CommonJS (.js) for compatibility with startServer.js
|
||||
*/
|
||||
|
||||
const MIGRATION_DOC_BASE = 'https://lobehub.com/docs/self-hosting/advanced/auth';
|
||||
const MIGRATION_DOC_BASE = 'https://lobehub.com/docs/self-hosting/migration/v2/auth';
|
||||
|
||||
/**
|
||||
* Deprecated environment variable checks configuration
|
||||
@@ -86,10 +86,10 @@ const DEPRECATED_CHECKS = [
|
||||
const mapping = {
|
||||
AUTH_AZURE_AD_ID: 'AUTH_MICROSOFT_ID',
|
||||
AUTH_AZURE_AD_SECRET: 'AUTH_MICROSOFT_SECRET',
|
||||
AUTH_AZURE_AD_TENANT_ID: 'No longer needed',
|
||||
AUTH_AZURE_AD_TENANT_ID: 'AUTH_MICROSOFT_TENANT_ID',
|
||||
AZURE_AD_CLIENT_ID: 'AUTH_MICROSOFT_ID',
|
||||
AZURE_AD_CLIENT_SECRET: 'AUTH_MICROSOFT_SECRET',
|
||||
AZURE_AD_TENANT_ID: 'No longer needed',
|
||||
AZURE_AD_TENANT_ID: 'AUTH_MICROSOFT_TENANT_ID',
|
||||
};
|
||||
return `${envVar} → ${mapping[envVar]}`;
|
||||
},
|
||||
@@ -167,10 +167,10 @@ const DEPRECATED_CHECKS = [
|
||||
docUrl: `${MIGRATION_DOC_BASE}/nextauth-to-betterauth`,
|
||||
formatVar: (envVar) => {
|
||||
const mapping = {
|
||||
AUTH_MICROSOFT_ENTRA_ID_BASE_URL: 'No longer needed',
|
||||
AUTH_MICROSOFT_ENTRA_ID_BASE_URL: 'AUTH_MICROSOFT_AUTHORITY_URL',
|
||||
AUTH_MICROSOFT_ENTRA_ID_ID: 'AUTH_MICROSOFT_ID',
|
||||
AUTH_MICROSOFT_ENTRA_ID_SECRET: 'AUTH_MICROSOFT_SECRET',
|
||||
AUTH_MICROSOFT_ENTRA_ID_TENANT_ID: 'No longer needed',
|
||||
AUTH_MICROSOFT_ENTRA_ID_TENANT_ID: 'AUTH_MICROSOFT_TENANT_ID',
|
||||
};
|
||||
return `${envVar} → ${mapping[envVar]}`;
|
||||
},
|
||||
@@ -213,7 +213,11 @@ function printIssueBlock(name, vars, message, docUrl, formatVar, severity = 'err
|
||||
|
||||
log(`\n${icon} ${name}`);
|
||||
log('─'.repeat(50));
|
||||
log(isWarning ? 'Missing recommended environment variables:' : 'Detected deprecated environment variables:');
|
||||
log(
|
||||
isWarning
|
||||
? 'Missing recommended environment variables:'
|
||||
: 'Detected deprecated environment variables:',
|
||||
);
|
||||
for (const envVar of vars) {
|
||||
log(` • ${formatVar ? formatVar(envVar) : envVar}`);
|
||||
}
|
||||
@@ -253,7 +257,14 @@ function checkDeprecatedAuth(options = {}) {
|
||||
console.warn('═'.repeat(70));
|
||||
|
||||
for (const issue of warnings) {
|
||||
printIssueBlock(issue.name, issue.foundVars, issue.message, issue.docUrl, issue.formatVar, 'warning');
|
||||
printIssueBlock(
|
||||
issue.name,
|
||||
issue.foundVars,
|
||||
issue.message,
|
||||
issue.docUrl,
|
||||
issue.formatVar,
|
||||
'warning',
|
||||
);
|
||||
}
|
||||
|
||||
console.warn('\n' + '═'.repeat(70));
|
||||
@@ -264,13 +275,18 @@ function checkDeprecatedAuth(options = {}) {
|
||||
// Print errors and exit (blocking)
|
||||
if (errors.length > 0) {
|
||||
console.error('\n' + '═'.repeat(70));
|
||||
console.error(
|
||||
`❌ ERROR: Found ${errors.length} deprecated environment variable issue(s)!`,
|
||||
);
|
||||
console.error(`❌ ERROR: Found ${errors.length} deprecated environment variable issue(s)!`);
|
||||
console.error('═'.repeat(70));
|
||||
|
||||
for (const issue of errors) {
|
||||
printIssueBlock(issue.name, issue.foundVars, issue.message, issue.docUrl, issue.formatVar, 'error');
|
||||
printIssueBlock(
|
||||
issue.name,
|
||||
issue.foundVars,
|
||||
issue.message,
|
||||
issue.docUrl,
|
||||
issue.formatVar,
|
||||
'error',
|
||||
);
|
||||
}
|
||||
|
||||
console.error('\n' + '═'.repeat(70));
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { redirect } from 'next/navigation';
|
||||
import { type PropsWithChildren } from 'react';
|
||||
|
||||
import { authEnv } from '@/envs/auth';
|
||||
|
||||
const ResetPasswordLayout = ({ children }: PropsWithChildren) => {
|
||||
if (authEnv.AUTH_DISABLE_EMAIL_PASSWORD) {
|
||||
redirect('/signin');
|
||||
}
|
||||
|
||||
return children;
|
||||
};
|
||||
|
||||
export default ResetPasswordLayout;
|
||||
@@ -33,7 +33,7 @@ const Nav = memo(() => {
|
||||
const switchTopic = useChatStore((s) => s.switchTopic);
|
||||
const [openNewTopicOrSaveTopic] = useChatStore((s) => [s.openNewTopicOrSaveTopic]);
|
||||
|
||||
const { mutate, isValidating } = useActionSWR('openNewTopicOrSaveTopic', openNewTopicOrSaveTopic);
|
||||
const { mutate } = useActionSWR('openNewTopicOrSaveTopic', openNewTopicOrSaveTopic);
|
||||
const handleNewTopic = () => {
|
||||
// If in agent sub-route, navigate back to agent chat first
|
||||
if (isProfileActive && agentId) {
|
||||
@@ -46,7 +46,6 @@ const Nav = memo(() => {
|
||||
<Flexbox gap={1} paddingInline={4}>
|
||||
<NavItem
|
||||
icon={MessageSquarePlusIcon}
|
||||
loading={isValidating}
|
||||
onClick={handleNewTopic}
|
||||
title={tTopic('actions.addNewTopic')}
|
||||
/>
|
||||
|
||||
@@ -75,7 +75,7 @@ const Header = memo<{ mobile?: boolean }>(({ mobile: isMobile }) => {
|
||||
// Fetch favorite status
|
||||
const { data: favoriteStatus, mutate: mutateFavorite } = useSWR(
|
||||
identifier && isAuthenticated ? ['favorite-status', 'agent', identifier] : null,
|
||||
() => socialService.checkFavoriteStatus('agent', identifier!),
|
||||
() => socialService.checkFavoriteStatus('agent-group', identifier!),
|
||||
{ revalidateOnFocus: false },
|
||||
);
|
||||
|
||||
@@ -100,10 +100,10 @@ const Header = memo<{ mobile?: boolean }>(({ mobile: isMobile }) => {
|
||||
setFavoriteLoading(true);
|
||||
try {
|
||||
if (isFavorited) {
|
||||
await socialService.removeFavorite('agent', identifier);
|
||||
await socialService.removeFavorite('agent-group', identifier);
|
||||
message.success(t('assistant.unfavoriteSuccess'));
|
||||
} else {
|
||||
await socialService.addFavorite('agent', identifier);
|
||||
await socialService.addFavorite('agent-group', identifier);
|
||||
message.success(t('assistant.favoriteSuccess'));
|
||||
}
|
||||
await mutateFavorite();
|
||||
|
||||
@@ -13,6 +13,8 @@ export interface UserDetailContextConfig {
|
||||
agentCount: number;
|
||||
agentGroups?: DiscoverGroupAgentItem[];
|
||||
agents: DiscoverAssistantItem[];
|
||||
favoriteAgentGroups?: DiscoverGroupAgentItem[];
|
||||
favoriteAgents?: DiscoverAssistantItem[];
|
||||
forkedAgentGroups?: DiscoverGroupAgentItem[];
|
||||
forkedAgents?: DiscoverAssistantItem[];
|
||||
groupCount: number;
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
'use client';
|
||||
|
||||
import { Select } from 'antd';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export type StatusFilterValue = 'published' | 'unpublished' | 'deprecated' | 'archived' | 'forked' | 'favorite';
|
||||
|
||||
interface StatusFilterProps {
|
||||
onChange: (value: StatusFilterValue) => void;
|
||||
value: StatusFilterValue;
|
||||
}
|
||||
|
||||
const StatusFilter = memo<StatusFilterProps>(({ value, onChange }) => {
|
||||
const { t } = useTranslation('discover');
|
||||
|
||||
const options = [
|
||||
{ label: t('user.statusFilter.published'), value: 'published' as const },
|
||||
{ label: t('user.statusFilter.unpublished'), value: 'unpublished' as const },
|
||||
{ label: t('user.statusFilter.deprecated'), value: 'deprecated' as const },
|
||||
{ label: t('user.statusFilter.archived'), value: 'archived' as const },
|
||||
{ label: t('user.statusFilter.forked'), value: 'forked' as const },
|
||||
{ label: t('user.statusFilter.favorite'), value: 'favorite' as const },
|
||||
];
|
||||
|
||||
return (
|
||||
<Select
|
||||
onChange={onChange}
|
||||
options={options}
|
||||
style={{ minWidth: 120 }}
|
||||
value={value}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export default StatusFilter;
|
||||
@@ -1,12 +1,13 @@
|
||||
'use client';
|
||||
|
||||
import { Flexbox, Grid, Tag, Text } from '@lobehub/ui';
|
||||
import { Pagination } from 'antd';
|
||||
import { Input, Pagination } from 'antd';
|
||||
import { memo, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import AssistantEmpty from '../../../features/AssistantEmpty';
|
||||
import { useUserDetailContext } from './DetailProvider';
|
||||
import StatusFilter, { type StatusFilterValue } from './StatusFilter';
|
||||
import UserAgentCard from './UserAgentCard';
|
||||
|
||||
interface UserAgentListProps {
|
||||
@@ -14,27 +15,82 @@ interface UserAgentListProps {
|
||||
rows?: number;
|
||||
}
|
||||
|
||||
const UserAgentList = memo<UserAgentListProps>(({ rows = 4, pageSize = 10 }) => {
|
||||
const UserAgentList = memo<UserAgentListProps>(({ rows = 4, pageSize = 8 }) => {
|
||||
const { t } = useTranslation('discover');
|
||||
const { agents, agentCount } = useUserDetailContext();
|
||||
const { agents, agentCount, forkedAgents = [], favoriteAgents = [], isOwner } = useUserDetailContext();
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [statusFilter, setStatusFilter] = useState<StatusFilterValue>('published');
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
|
||||
// Combine agents and forked agents, then filter based on status and search
|
||||
const filteredAgents = useMemo(() => {
|
||||
let allAgents = [...agents];
|
||||
|
||||
if (statusFilter === 'forked') {
|
||||
// Show only forked agents (those with forkedFromAgentId)
|
||||
allAgents = forkedAgents;
|
||||
} else if (statusFilter === 'favorite') {
|
||||
// Show only favorited agents
|
||||
allAgents = favoriteAgents;
|
||||
} else {
|
||||
// Filter by status for non-forked agents
|
||||
allAgents = allAgents.filter((agent) => {
|
||||
return agent.status === statusFilter;
|
||||
});
|
||||
}
|
||||
|
||||
// Apply search filter
|
||||
if (searchQuery.trim()) {
|
||||
const query = searchQuery.toLowerCase();
|
||||
allAgents = allAgents.filter((agent) => {
|
||||
console.log('agent', agent);
|
||||
const name = agent?.title?.toLowerCase() || '';
|
||||
const description = agent?.description?.toLowerCase() || '';
|
||||
return name.includes(query) || description.includes(query);
|
||||
});
|
||||
}
|
||||
|
||||
return allAgents;
|
||||
}, [agents, forkedAgents, statusFilter, searchQuery]);
|
||||
|
||||
const paginatedAgents = useMemo(() => {
|
||||
const startIndex = (currentPage - 1) * pageSize;
|
||||
return agents.slice(startIndex, startIndex + pageSize);
|
||||
}, [agents, currentPage, pageSize]);
|
||||
return filteredAgents.slice(startIndex, startIndex + pageSize);
|
||||
}, [filteredAgents, currentPage, pageSize]);
|
||||
|
||||
if (agents.length === 0) return <AssistantEmpty />;
|
||||
// Reset to page 1 when filter or search changes
|
||||
useMemo(() => {
|
||||
setCurrentPage(1);
|
||||
}, [statusFilter, searchQuery]);
|
||||
|
||||
const showPagination = agents.length > pageSize;
|
||||
if (agents.length === 0 && forkedAgents.length === 0) return <AssistantEmpty />;
|
||||
|
||||
const showPagination = filteredAgents.length > pageSize;
|
||||
|
||||
return (
|
||||
<Flexbox gap={16}>
|
||||
<Flexbox align={'center'} gap={8} horizontal>
|
||||
<Text fontSize={16} weight={500}>
|
||||
{t('user.publishedAgents')}
|
||||
</Text>
|
||||
{agentCount > 0 && <Tag>{agentCount}</Tag>}
|
||||
<Flexbox align={'center'} gap={8} horizontal justify={'space-between'}>
|
||||
<Flexbox align={'center'} gap={8} horizontal>
|
||||
<Text fontSize={16} weight={500}>
|
||||
{t('user.publishedAgents')}
|
||||
</Text>
|
||||
{agentCount > 0 && <Tag>{filteredAgents.length}</Tag>}
|
||||
</Flexbox>
|
||||
{isOwner && (
|
||||
<Flexbox align={'center'} gap={8} horizontal>
|
||||
<Input.Search
|
||||
allowClear
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
placeholder={t('user.searchPlaceholder')}
|
||||
style={{ width: 200 }}
|
||||
value={searchQuery}
|
||||
/>
|
||||
<StatusFilter
|
||||
onChange={(value) => setStatusFilter(value)}
|
||||
value={statusFilter}
|
||||
/>
|
||||
</Flexbox>
|
||||
)}
|
||||
</Flexbox>
|
||||
<Grid rows={rows} width={'100%'}>
|
||||
{paginatedAgents.map((item, index) => (
|
||||
@@ -48,7 +104,7 @@ const UserAgentList = memo<UserAgentListProps>(({ rows = 4, pageSize = 10 }) =>
|
||||
onChange={(page) => setCurrentPage(page)}
|
||||
pageSize={pageSize}
|
||||
showSizeChanger={false}
|
||||
total={agents.length}
|
||||
total={filteredAgents.length}
|
||||
/>
|
||||
</Flexbox>
|
||||
)}
|
||||
|
||||
@@ -3,25 +3,14 @@
|
||||
import { Flexbox } from '@lobehub/ui';
|
||||
import { memo } from 'react';
|
||||
|
||||
import { useUserDetailContext } from './DetailProvider';
|
||||
import UserAgentList from './UserAgentList';
|
||||
import UserFavoriteAgents from './UserFavoriteAgents';
|
||||
import UserFavoritePlugins from './UserFavoritePlugins';
|
||||
import UserForkedAgentGroups from './UserForkedAgentGroups';
|
||||
import UserForkedAgents from './UserForkedAgents';
|
||||
import UserGroupList from './UserGroupList';
|
||||
|
||||
const UserContent = memo(() => {
|
||||
const { forkedAgents, forkedAgentGroups } = useUserDetailContext();
|
||||
|
||||
return (
|
||||
<Flexbox gap={32}>
|
||||
<UserAgentList />
|
||||
<UserGroupList />
|
||||
<UserForkedAgents agents={forkedAgents} />
|
||||
<UserForkedAgentGroups agentGroups={forkedAgentGroups} />
|
||||
<UserFavoriteAgents />
|
||||
<UserFavoritePlugins />
|
||||
</Flexbox>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
'use client';
|
||||
|
||||
import { Flexbox, Grid, Tag, Text } from '@lobehub/ui';
|
||||
import { Pagination } from 'antd';
|
||||
import { Input, Pagination } from 'antd';
|
||||
import { memo, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { useUserDetailContext } from './DetailProvider';
|
||||
import StatusFilter, { type StatusFilterValue } from './StatusFilter';
|
||||
import UserGroupCard from './UserGroupCard';
|
||||
|
||||
interface UserGroupListProps {
|
||||
@@ -13,28 +14,81 @@ interface UserGroupListProps {
|
||||
rows?: number;
|
||||
}
|
||||
|
||||
const UserGroupList = memo<UserGroupListProps>(({ rows = 4, pageSize = 10 }) => {
|
||||
const UserGroupList = memo<UserGroupListProps>(({ rows = 4, pageSize = 8 }) => {
|
||||
const { t } = useTranslation('discover');
|
||||
const { agentGroups, groupCount } = useUserDetailContext();
|
||||
const { agentGroups = [], groupCount, forkedAgentGroups = [], favoriteAgentGroups = [], isOwner } = useUserDetailContext();
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [statusFilter, setStatusFilter] = useState<StatusFilterValue>('published');
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
|
||||
// Combine groups and forked groups, then filter based on status and search
|
||||
const filteredGroups = useMemo(() => {
|
||||
let allGroups = [...agentGroups];
|
||||
|
||||
if (statusFilter === 'forked') {
|
||||
// Show only forked groups (those with forkedFromAgentId)
|
||||
allGroups = forkedAgentGroups;
|
||||
} else if (statusFilter === 'favorite') {
|
||||
// Show only favorited groups
|
||||
allGroups = favoriteAgentGroups;
|
||||
} else {
|
||||
// Filter by status for non-forked groups
|
||||
allGroups = allGroups.filter((group) => {
|
||||
return group.status === statusFilter;
|
||||
});
|
||||
}
|
||||
|
||||
// Apply search filter
|
||||
if (searchQuery.trim()) {
|
||||
const query = searchQuery.toLowerCase();
|
||||
allGroups = allGroups.filter((group) => {
|
||||
const name = group?.title?.toLowerCase() || '';
|
||||
const description = group?.description?.toLowerCase() || '';
|
||||
return name.includes(query) || description.includes(query);
|
||||
});
|
||||
}
|
||||
|
||||
return allGroups;
|
||||
}, [agentGroups, forkedAgentGroups, statusFilter, searchQuery]);
|
||||
|
||||
const paginatedGroups = useMemo(() => {
|
||||
if (!agentGroups) return [];
|
||||
const startIndex = (currentPage - 1) * pageSize;
|
||||
return agentGroups.slice(startIndex, startIndex + pageSize);
|
||||
}, [agentGroups, currentPage, pageSize]);
|
||||
return filteredGroups.slice(startIndex, startIndex + pageSize);
|
||||
}, [filteredGroups, currentPage, pageSize]);
|
||||
|
||||
if (!agentGroups || agentGroups.length === 0) return null;
|
||||
// Reset to page 1 when filter or search changes
|
||||
useMemo(() => {
|
||||
setCurrentPage(1);
|
||||
}, [statusFilter, searchQuery]);
|
||||
|
||||
const showPagination = agentGroups.length > pageSize;
|
||||
if (agentGroups.length === 0 && forkedAgentGroups.length === 0) return null;
|
||||
|
||||
const showPagination = filteredGroups.length > pageSize;
|
||||
|
||||
return (
|
||||
<Flexbox gap={16}>
|
||||
<Flexbox align={'center'} gap={8} horizontal>
|
||||
<Text fontSize={16} weight={500}>
|
||||
{t('user.publishedGroups', { defaultValue: '创作的群组' })}
|
||||
</Text>
|
||||
{groupCount > 0 && <Tag>{groupCount}</Tag>}
|
||||
<Flexbox align={'center'} gap={8} horizontal justify={'space-between'}>
|
||||
<Flexbox align={'center'} gap={8} horizontal>
|
||||
<Text fontSize={16} weight={500}>
|
||||
{t('user.publishedGroups', { defaultValue: '创作的群组' })}
|
||||
</Text>
|
||||
{groupCount > 0 && <Tag>{filteredGroups.length}</Tag>}
|
||||
</Flexbox>
|
||||
{isOwner && (
|
||||
<Flexbox align={'center'} gap={8} horizontal>
|
||||
<Input.Search
|
||||
allowClear
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
placeholder={t('user.searchPlaceholder')}
|
||||
style={{ width: 200 }}
|
||||
value={searchQuery}
|
||||
/>
|
||||
<StatusFilter
|
||||
onChange={(value) => setStatusFilter(value)}
|
||||
value={statusFilter}
|
||||
/>
|
||||
</Flexbox>
|
||||
)}
|
||||
</Flexbox>
|
||||
<Grid rows={rows} width={'100%'}>
|
||||
{paginatedGroups.map((item, index) => (
|
||||
@@ -48,7 +102,7 @@ const UserGroupList = memo<UserGroupListProps>(({ rows = 4, pageSize = 10 }) =>
|
||||
onChange={(page) => setCurrentPage(page)}
|
||||
pageSize={pageSize}
|
||||
showSizeChanger={false}
|
||||
total={agentGroups.length}
|
||||
total={filteredGroups.length}
|
||||
/>
|
||||
</Flexbox>
|
||||
)}
|
||||
|
||||
@@ -61,12 +61,14 @@ const UserDetailPage = memo<UserDetailPageProps>(({ mobile }) => {
|
||||
|
||||
const contextConfig = useMemo(() => {
|
||||
if (!data || !data.user) return null;
|
||||
const { user, agents, agentGroups, forkedAgents, forkedAgentGroups } = data;
|
||||
const { user, agents, agentGroups, forkedAgents, forkedAgentGroups, favoriteAgents, favoriteAgentGroups } = data;
|
||||
const totalInstalls = agents.reduce((sum, agent) => sum + (agent.installCount || 0), 0);
|
||||
return {
|
||||
agentCount: agents.length,
|
||||
agentGroups: agentGroups || [],
|
||||
agents,
|
||||
favoriteAgentGroups: favoriteAgentGroups || [],
|
||||
favoriteAgents: favoriteAgents || [],
|
||||
forkedAgentGroups: forkedAgentGroups || [],
|
||||
forkedAgents: forkedAgents || [],
|
||||
groupCount: agentGroups?.length || 0,
|
||||
|
||||
@@ -56,6 +56,7 @@ const styles = createStaticStyles(({ css, cssVar }) => {
|
||||
const AssistantItem = memo<DiscoverAssistantItem>(
|
||||
({
|
||||
createdAt,
|
||||
updatedAt,
|
||||
author,
|
||||
avatar,
|
||||
title,
|
||||
@@ -225,7 +226,7 @@ const AssistantItem = memo<DiscoverAssistantItem>(
|
||||
<Icon icon={ClockIcon} size={14} />
|
||||
<PublishedTime
|
||||
className={styles.secondaryDesc}
|
||||
date={createdAt}
|
||||
date={updatedAt || createdAt}
|
||||
template={'MMM DD, YYYY'}
|
||||
/>
|
||||
</Flexbox>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { modal, notification } from '@/components/AntdStaticMethods';
|
||||
import AuthIcons from '@/components/AuthIcons';
|
||||
import { isBuiltinProvider, normalizeProviderId } from '@/libs/better-auth/utils/client';
|
||||
import { useServerConfigStore } from '@/store/serverConfig';
|
||||
import { serverConfigSelectors } from '@/store/serverConfig/selectors';
|
||||
import { useUserStore } from '@/store/user';
|
||||
@@ -33,8 +34,11 @@ export const SSOProvidersList = memo(() => {
|
||||
}, [providers]);
|
||||
|
||||
// Get available providers for linking (filter out already linked)
|
||||
// Normalize provider IDs when comparing to handle aliases (e.g. microsoft-entra-id → microsoft)
|
||||
const availableProviders = useMemo(() => {
|
||||
return (oAuthSSOProviders || []).filter((provider) => !linkedProviderIds.has(provider));
|
||||
return (oAuthSSOProviders || []).filter(
|
||||
(provider) => !linkedProviderIds.has(normalizeProviderId(provider)),
|
||||
);
|
||||
}, [oAuthSSOProviders, linkedProviderIds]);
|
||||
|
||||
const handleUnlinkSSO = async (provider: string) => {
|
||||
@@ -63,14 +67,24 @@ export const SSOProvidersList = memo(() => {
|
||||
};
|
||||
|
||||
const handleLinkSSO = async (provider: string) => {
|
||||
if (enableAuthActions) {
|
||||
// Use better-auth native linkSocial API
|
||||
const { linkSocial } = await import('@/libs/better-auth/auth-client');
|
||||
if (!enableAuthActions) return;
|
||||
|
||||
const normalizedProvider = normalizeProviderId(provider);
|
||||
const { linkSocial, oauth2 } = await import('@/libs/better-auth/auth-client');
|
||||
|
||||
if (isBuiltinProvider(normalizedProvider)) {
|
||||
// Use better-auth native linkSocial API for built-in providers
|
||||
await linkSocial({
|
||||
callbackURL: '/profile',
|
||||
provider: provider as any,
|
||||
provider: normalizedProvider as any,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await oauth2.link({
|
||||
callbackURL: '/profile',
|
||||
providerId: normalizedProvider,
|
||||
});
|
||||
};
|
||||
|
||||
// Dropdown menu items for linking new providers
|
||||
|
||||
@@ -59,6 +59,7 @@ const ProfileSetting = ({ mobile }: ProfileSettingProps) => {
|
||||
const isLoadedAuthProviders = useUserStore(authSelectors.isLoadedAuthProviders);
|
||||
const fetchAuthProviders = useUserStore((s) => s.fetchAuthProviders);
|
||||
const enableKlavis = useServerConfigStore(serverConfigSelectors.enableKlavis);
|
||||
const disableEmailPassword = useServerConfigStore(serverConfigSelectors.disableEmailPassword);
|
||||
const [servers, isServersInit, useFetchUserKlavisServers] = useToolStore((s) => [
|
||||
s.servers,
|
||||
s.isServersInit,
|
||||
@@ -113,7 +114,7 @@ const ProfileSetting = ({ mobile }: ProfileSettingProps) => {
|
||||
<InterestsRow mobile={mobile} />
|
||||
|
||||
{/* Password Row - For logged in users to change or set password */}
|
||||
{!isDesktop && isLogin && (
|
||||
{!isDesktop && isLogin && !disableEmailPassword && (
|
||||
<>
|
||||
<Divider style={{ margin: 0 }} />
|
||||
<PasswordRow mobile={mobile} />
|
||||
|
||||
@@ -33,8 +33,10 @@ declare global {
|
||||
AUTH_COGNITO_REGION?: string;
|
||||
AUTH_COGNITO_USERPOOL_ID?: string;
|
||||
|
||||
AUTH_MICROSOFT_AUTHORITY_URL?: string;
|
||||
AUTH_MICROSOFT_ID?: string;
|
||||
AUTH_MICROSOFT_SECRET?: string;
|
||||
AUTH_MICROSOFT_TENANT_ID?: string;
|
||||
|
||||
AUTH_AUTH0_ID?: string;
|
||||
AUTH_AUTH0_SECRET?: string;
|
||||
@@ -132,8 +134,10 @@ export const getAuthConfig = () => {
|
||||
AUTH_COGNITO_REGION: z.string().optional(),
|
||||
AUTH_COGNITO_USERPOOL_ID: z.string().optional(),
|
||||
|
||||
AUTH_MICROSOFT_AUTHORITY_URL: z.string().optional(),
|
||||
AUTH_MICROSOFT_ID: z.string().optional(),
|
||||
AUTH_MICROSOFT_SECRET: z.string().optional(),
|
||||
AUTH_MICROSOFT_TENANT_ID: z.string().optional(),
|
||||
|
||||
AUTH_AUTH0_ID: z.string().optional(),
|
||||
AUTH_AUTH0_SECRET: z.string().optional(),
|
||||
@@ -219,8 +223,10 @@ export const getAuthConfig = () => {
|
||||
AUTH_GITHUB_ID: process.env.AUTH_GITHUB_ID,
|
||||
AUTH_GITHUB_SECRET: process.env.AUTH_GITHUB_SECRET,
|
||||
|
||||
AUTH_MICROSOFT_AUTHORITY_URL: process.env.AUTH_MICROSOFT_AUTHORITY_URL,
|
||||
AUTH_MICROSOFT_ID: process.env.AUTH_MICROSOFT_ID,
|
||||
AUTH_MICROSOFT_SECRET: process.env.AUTH_MICROSOFT_SECRET,
|
||||
AUTH_MICROSOFT_TENANT_ID: process.env.AUTH_MICROSOFT_TENANT_ID,
|
||||
|
||||
AUTH_COGNITO_ID: process.env.AUTH_COGNITO_ID,
|
||||
AUTH_COGNITO_SECRET: process.env.AUTH_COGNITO_SECRET,
|
||||
|
||||
@@ -20,10 +20,12 @@ export const styles = createStaticStyles(({ css, cssVar }) => ({
|
||||
`,
|
||||
paramKey: css`
|
||||
font-family: ${cssVar.fontFamilyCode};
|
||||
font-size: 12px;
|
||||
color: ${cssVar.colorTextTertiary};
|
||||
`,
|
||||
paramValue: css`
|
||||
font-family: ${cssVar.fontFamilyCode};
|
||||
font-size: 12px;
|
||||
color: ${cssVar.colorTextSecondary};
|
||||
`,
|
||||
root: css`
|
||||
|
||||
@@ -10,9 +10,10 @@ import {
|
||||
Tabs,
|
||||
type TabsProps,
|
||||
} from '@lobehub/ui';
|
||||
import { App } from 'antd';
|
||||
import { createStaticStyles, cx } from 'antd-style';
|
||||
import isEqual from 'fast-deep-equal';
|
||||
import { ChevronDown, ChevronUp, History, Sparkles } from 'lucide-react';
|
||||
import { ChevronDown, ChevronUp, History, Sparkles, Undo2 } from 'lucide-react';
|
||||
import { memo, useCallback, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
@@ -66,6 +67,7 @@ export interface CompressedGroupMessageProps {
|
||||
|
||||
const CompressedGroupMessage = memo<CompressedGroupMessageProps>(({ id }) => {
|
||||
const { t } = useTranslation('chat');
|
||||
const { modal } = App.useApp();
|
||||
const [activeTab, setActiveTab] = useState<string>(() => getStoredTab(id));
|
||||
|
||||
const handleTabChange = useCallback(
|
||||
@@ -80,6 +82,16 @@ const CompressedGroupMessage = memo<CompressedGroupMessageProps>(({ id }) => {
|
||||
const toggleCompressedGroupExpanded = useConversationStore(
|
||||
(s) => s.toggleCompressedGroupExpanded,
|
||||
);
|
||||
const cancelCompression = useConversationStore((s) => s.cancelCompression);
|
||||
|
||||
const handleCancelCompression = useCallback(() => {
|
||||
modal.confirm({
|
||||
centered: true,
|
||||
content: t('compression.cancelConfirm'),
|
||||
onOk: () => cancelCompression(id),
|
||||
title: t('compression.cancel'),
|
||||
});
|
||||
}, [id, cancelCompression, modal, t]);
|
||||
|
||||
const content = message?.content;
|
||||
const rawCompressedMessages = (message as UIChatMessage)?.compressedMessages;
|
||||
@@ -145,11 +157,19 @@ const CompressedGroupMessage = memo<CompressedGroupMessageProps>(({ id }) => {
|
||||
onChange={handleTabChange}
|
||||
variant={'rounded'}
|
||||
/>
|
||||
<ActionIcon
|
||||
icon={expanded ? ChevronUp : ChevronDown}
|
||||
onClick={() => toggleCompressedGroupExpanded(id)}
|
||||
size={'small'}
|
||||
/>
|
||||
<Flexbox gap={4} horizontal>
|
||||
<ActionIcon
|
||||
icon={Undo2}
|
||||
onClick={handleCancelCompression}
|
||||
size={'small'}
|
||||
title={t('compression.cancel')}
|
||||
/>
|
||||
<ActionIcon
|
||||
icon={expanded ? ChevronUp : ChevronDown}
|
||||
onClick={() => toggleCompressedGroupExpanded(id)}
|
||||
size={'small'}
|
||||
/>
|
||||
</Flexbox>
|
||||
</Flexbox>
|
||||
)}
|
||||
{!showContent ? null : activeTab === 'summary' ? (
|
||||
|
||||
@@ -327,11 +327,37 @@ const TaskMessages = memo<TaskMessagesProps>(
|
||||
const assistantGroupMessage = messages.find((item) => item.role === 'assistantGroup');
|
||||
const userMessage = messages.find((item) => item.role === 'user');
|
||||
|
||||
return {
|
||||
assistantId: assistantGroupMessage?.id ?? '',
|
||||
blocks: assistantGroupMessage?.children ?? [],
|
||||
instruction: userMessage?.content,
|
||||
};
|
||||
// If assistantGroup exists, use its children as blocks
|
||||
if (assistantGroupMessage) {
|
||||
return {
|
||||
assistantId: assistantGroupMessage.id ?? '',
|
||||
blocks: assistantGroupMessage.children ?? [],
|
||||
instruction: userMessage?.content,
|
||||
};
|
||||
}
|
||||
|
||||
// Fallback: support plain assistant message (without tools)
|
||||
// This handles cases where SubAgent returns a simple text response
|
||||
const assistantMessage = messages.find((item) => item.role === 'assistant');
|
||||
if (assistantMessage) {
|
||||
// Convert plain assistant message to block format
|
||||
const block: AssistantContentBlock = {
|
||||
content: assistantMessage.content || '',
|
||||
id: assistantMessage.id,
|
||||
};
|
||||
|
||||
// Copy optional fields if they exist
|
||||
if (assistantMessage.error) block.error = assistantMessage.error;
|
||||
if (assistantMessage.reasoning) block.reasoning = assistantMessage.reasoning;
|
||||
|
||||
return {
|
||||
assistantId: assistantMessage.id ?? '',
|
||||
blocks: [block],
|
||||
instruction: userMessage?.content,
|
||||
};
|
||||
}
|
||||
|
||||
return { assistantId: '', blocks: [], instruction: undefined };
|
||||
}, [messages]);
|
||||
|
||||
// Calculate total tool calls
|
||||
|
||||
@@ -13,6 +13,11 @@ import { dataSelectors } from '../../data/selectors';
|
||||
* Handles message state operations like loading, collapsed, etc.
|
||||
*/
|
||||
export interface MessageStateAction {
|
||||
/**
|
||||
* Cancel compression and restore original messages
|
||||
*/
|
||||
cancelCompression: (id: string) => Promise<void>;
|
||||
|
||||
/**
|
||||
* Copy message content to clipboard
|
||||
*/
|
||||
@@ -50,6 +55,26 @@ export const messageStateSlice: StateCreator<
|
||||
[],
|
||||
MessageStateAction
|
||||
> = (set, get) => ({
|
||||
cancelCompression: async (id) => {
|
||||
const message = dataSelectors.getDisplayMessageById(id)(get());
|
||||
if (!message || message.role !== 'compressedGroup') return;
|
||||
|
||||
const { context, replaceMessages } = get();
|
||||
if (!context.agentId || !context.topicId) return;
|
||||
|
||||
// Call service to cancel compression
|
||||
const { messages } = await messageService.cancelCompression({
|
||||
agentId: context.agentId,
|
||||
groupId: context.groupId,
|
||||
messageGroupId: id,
|
||||
threadId: context.threadId,
|
||||
topicId: context.topicId,
|
||||
});
|
||||
|
||||
// Replace messages with restored original messages
|
||||
replaceMessages(messages);
|
||||
},
|
||||
|
||||
copyMessage: async (id, content) => {
|
||||
const { hooks } = get();
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ export const useFetchAgentList = () => {
|
||||
|
||||
const { isValidating, data } = useFetchAgentListHook(isLogin);
|
||||
|
||||
// isRevalidating: 有缓存数据,后台正在更新
|
||||
// isRevalidating: has cached data, updating in background
|
||||
return {
|
||||
isRevalidating: isValidating && !!data,
|
||||
};
|
||||
|
||||
@@ -49,7 +49,7 @@ export const useClearCurrentMessagesHotkey = () => {
|
||||
});
|
||||
};
|
||||
|
||||
// 注册聚合
|
||||
// Register aggregate
|
||||
|
||||
export const useRegisterChatHotkeys = () => {
|
||||
const { enableScope, disableScope } = useHotkeysContext();
|
||||
|
||||
@@ -7,7 +7,7 @@ import { useGlobalStore } from '@/store/global';
|
||||
|
||||
import { useHotkeyById } from './useHotkeyById';
|
||||
|
||||
// 切换到会话标签(并聚焦到Lobe AI)
|
||||
// Switch to chat tab (and focus on Lobe AI)
|
||||
export const useNavigateToChatHotkey = () => {
|
||||
const navigateToAgent = useNavigateToAgent();
|
||||
const [, { unpinAgent }] = usePinnedAgentState();
|
||||
@@ -58,7 +58,7 @@ export const useCommandPaletteHotkey = () => {
|
||||
};
|
||||
|
||||
export const useRegisterGlobalHotkeys = () => {
|
||||
// 全局自动注册不需要 enableScope
|
||||
// Global auto-registration doesn't need enableScope
|
||||
useToggleLeftPanelHotkey();
|
||||
useToggleRightPanelHotkey();
|
||||
useNavigateToChatHotkey();
|
||||
|
||||
@@ -19,7 +19,7 @@ export const useToggleImageRightPanelHotkey = () => {
|
||||
);
|
||||
};
|
||||
|
||||
// 注册聚合
|
||||
// Register aggregate
|
||||
|
||||
export const useRegisterImageHotkeys = () => {
|
||||
const { enableScope, disableScope } = useHotkeysContext();
|
||||
|
||||
@@ -19,7 +19,7 @@ export const usePWAInstall = () => {
|
||||
}, []);
|
||||
|
||||
const installCheck = () => {
|
||||
// 当在 PWA 或不支持 PWA 的环境中时,不显示安装按钮
|
||||
// Don't show install button when in PWA or environment that doesn't support PWA
|
||||
if (isPWA || !isSupportInstallPWA) return false;
|
||||
const pwa: any = document.querySelector(`#${PWA_INSTALL_ID}`);
|
||||
if (!pwa) return false;
|
||||
|
||||
@@ -10,6 +10,7 @@ import type { auth } from '@/auth';
|
||||
|
||||
export const {
|
||||
linkSocial,
|
||||
oauth2,
|
||||
accountInfo,
|
||||
listAccounts,
|
||||
requestPasswordReset,
|
||||
|
||||
@@ -107,6 +107,7 @@ export function defineConfig(customOptions: CustomBetterAuthOptions) {
|
||||
|
||||
emailAndPassword: {
|
||||
autoSignIn: true,
|
||||
disableSignUp: authEnv.AUTH_DISABLE_EMAIL_PASSWORD,
|
||||
enabled: !authEnv.AUTH_DISABLE_EMAIL_PASSWORD,
|
||||
maxPasswordLength: 64,
|
||||
minPasswordLength: 8,
|
||||
|
||||
@@ -3,23 +3,30 @@ import { authEnv } from '@/envs/auth';
|
||||
import type { BuiltinProviderDefinition } from '../types';
|
||||
|
||||
type MicrosoftEnv = {
|
||||
AUTH_MICROSOFT_ID?: string;
|
||||
AUTH_MICROSOFT_SECRET?: string;
|
||||
AUTH_MICROSOFT_AUTHORITY_URL?: string;
|
||||
AUTH_MICROSOFT_ID: string;
|
||||
AUTH_MICROSOFT_SECRET: string;
|
||||
AUTH_MICROSOFT_TENANT_ID?: string;
|
||||
};
|
||||
|
||||
const provider: BuiltinProviderDefinition<MicrosoftEnv, 'microsoft'> = {
|
||||
aliases: ['microsoft-entra-id'],
|
||||
build: (env) => ({
|
||||
clientId: env.AUTH_MICROSOFT_ID!,
|
||||
clientSecret: env.AUTH_MICROSOFT_SECRET!,
|
||||
authority: env.AUTH_MICROSOFT_AUTHORITY_URL,
|
||||
clientId: env.AUTH_MICROSOFT_ID,
|
||||
clientSecret: env.AUTH_MICROSOFT_SECRET,
|
||||
tenantId: env.AUTH_MICROSOFT_TENANT_ID,
|
||||
}),
|
||||
checkEnvs: () => {
|
||||
const clientId = authEnv.AUTH_MICROSOFT_ID;
|
||||
const clientSecret = authEnv.AUTH_MICROSOFT_SECRET;
|
||||
const tenantId = authEnv.AUTH_MICROSOFT_TENANT_ID;
|
||||
return !!(clientId && clientSecret)
|
||||
? {
|
||||
AUTH_MICROSOFT_AUTHORITY_URL: authEnv.AUTH_MICROSOFT_AUTHORITY_URL,
|
||||
AUTH_MICROSOFT_ID: clientId,
|
||||
AUTH_MICROSOFT_SECRET: clientSecret,
|
||||
AUTH_MICROSOFT_TENANT_ID: tenantId,
|
||||
}
|
||||
: false;
|
||||
},
|
||||
|
||||
@@ -41,6 +41,9 @@ export default {
|
||||
'chatList.longMessageDetail': 'View Details',
|
||||
'clearCurrentMessages': 'Clear current session messages',
|
||||
'compressedHistory': 'Compressed History',
|
||||
'compression.cancel': 'Uncompress',
|
||||
'compression.cancelConfirm':
|
||||
'Are you sure you want to uncompress? This will restore the original messages.',
|
||||
'compression.history': 'History',
|
||||
'compression.summary': 'Summary',
|
||||
'confirmClearCurrentMessages':
|
||||
|
||||
@@ -891,6 +891,18 @@ export default {
|
||||
|
||||
'user.publishedAgents': 'Created Agents',
|
||||
|
||||
'user.publishedGroups': 'Created Groups',
|
||||
|
||||
'user.searchPlaceholder': 'Search by name or description...',
|
||||
|
||||
'user.statusFilter.all': 'All',
|
||||
'user.statusFilter.archived': 'Archived',
|
||||
'user.statusFilter.deprecated': 'Deprecated',
|
||||
'user.statusFilter.favorite': 'Favorite',
|
||||
'user.statusFilter.forked': 'Forked',
|
||||
'user.statusFilter.published': 'Published',
|
||||
'user.statusFilter.unpublished': 'Under Review',
|
||||
|
||||
'user.tabs.favorites': 'Favorites',
|
||||
|
||||
'user.tabs.forkedAgents': 'Forked',
|
||||
|
||||
@@ -17,7 +17,7 @@ const socialPublicProcedure = publicProcedure
|
||||
.use(marketSDK);
|
||||
|
||||
// Schema definitions
|
||||
const targetTypeSchema = z.enum(['agent', 'plugin']);
|
||||
const targetTypeSchema = z.enum(['agent', 'plugin', 'agent-group']);
|
||||
|
||||
const paginationSchema = z.object({
|
||||
limit: z.number().optional(),
|
||||
|
||||
@@ -48,7 +48,32 @@ export const messageRouter = router({
|
||||
return ctx.messageService.addFilesToMessage(id, fileIds, resolved);
|
||||
}),
|
||||
|
||||
count: messageProcedure
|
||||
/**
|
||||
* Cancel compression by deleting the compression group and restoring original messages
|
||||
*/
|
||||
cancelCompression: messageProcedure
|
||||
.input(
|
||||
z.object({
|
||||
agentId: z.string(),
|
||||
groupId: z.string().nullable().optional(),
|
||||
messageGroupId: z.string(),
|
||||
threadId: z.string().nullable().optional(),
|
||||
topicId: z.string(),
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { messageGroupId, agentId, groupId, threadId, topicId } = input;
|
||||
|
||||
return ctx.messageService.cancelCompression(messageGroupId, {
|
||||
agentId,
|
||||
groupId,
|
||||
threadId,
|
||||
topicId,
|
||||
});
|
||||
}),
|
||||
|
||||
|
||||
count: messageProcedure
|
||||
.input(
|
||||
z
|
||||
.object({
|
||||
@@ -62,7 +87,9 @@ export const messageRouter = router({
|
||||
return ctx.messageModel.count(input);
|
||||
}),
|
||||
|
||||
countWords: messageProcedure
|
||||
|
||||
|
||||
countWords: messageProcedure
|
||||
.input(
|
||||
z
|
||||
.object({
|
||||
@@ -76,12 +103,13 @@ export const messageRouter = router({
|
||||
return ctx.messageModel.countWords(input);
|
||||
}),
|
||||
|
||||
/**
|
||||
|
||||
/**
|
||||
* Create a compression group for old messages
|
||||
* Creates a placeholder group, marks messages as compressed
|
||||
* Returns messages to summarize for frontend AI generation
|
||||
*/
|
||||
createCompressionGroup: messageProcedure
|
||||
createCompressionGroup: messageProcedure
|
||||
.input(
|
||||
z.object({
|
||||
agentId: z.string(),
|
||||
@@ -102,7 +130,9 @@ export const messageRouter = router({
|
||||
});
|
||||
}),
|
||||
|
||||
createMessage: messageProcedure
|
||||
|
||||
|
||||
createMessage: messageProcedure
|
||||
.input(CreateNewMessageParamsSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
// If there's no agentId but has sessionId, resolve agentId from sessionId
|
||||
@@ -115,10 +145,11 @@ export const messageRouter = router({
|
||||
return ctx.messageService.createMessage({ ...input, agentId } as any);
|
||||
}),
|
||||
|
||||
|
||||
/**
|
||||
* Finalize compression by updating the group with generated summary
|
||||
*/
|
||||
finalizeCompression: messageProcedure
|
||||
finalizeCompression: messageProcedure
|
||||
.input(
|
||||
z.object({
|
||||
agentId: z.string(),
|
||||
|
||||
@@ -745,6 +745,7 @@ export class DiscoverService {
|
||||
title: item.name || item.identifier,
|
||||
tokenUsage: item.tokenUsage || 0,
|
||||
type: item.type,
|
||||
updatedAt: item.updatedAt,
|
||||
userName: normalizedAuthor.userName,
|
||||
};
|
||||
});
|
||||
@@ -1710,6 +1711,8 @@ export class DiscoverService {
|
||||
locale,
|
||||
})) as UserInfoResponse & {
|
||||
agentGroups?: any[];
|
||||
favoriteAgentGroups?: any[];
|
||||
favoriteAgents?: any[];
|
||||
forkedAgentGroups?: any[];
|
||||
forkedAgents?: any[];
|
||||
};
|
||||
@@ -1719,7 +1722,7 @@ export class DiscoverService {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const { user, agents, agentGroups, forkedAgents, forkedAgentGroups } = response;
|
||||
const { user, agents, agentGroups, forkedAgents, forkedAgentGroups, favoriteAgents, favoriteAgentGroups } = response;
|
||||
|
||||
// Transform agents to DiscoverAssistantItem format
|
||||
const transformedAgents: DiscoverAssistantItem[] = (agents || []).map((agent: any) => ({
|
||||
@@ -1811,9 +1814,59 @@ export class DiscoverService {
|
||||
updatedAt: group.updatedAt,
|
||||
}));
|
||||
|
||||
// Transform favoriteAgents to DiscoverAssistantItem format
|
||||
const transformedFavoriteAgents: DiscoverAssistantItem[] = (favoriteAgents || []).map(
|
||||
(agent: any) => ({
|
||||
author: agent.author || '',
|
||||
avatar: agent.avatar || '',
|
||||
category: agent.category as any,
|
||||
config: {} as any,
|
||||
createdAt: agent.createdAt,
|
||||
description: agent.description || '',
|
||||
forkCount: agent.forkCount || 0,
|
||||
forkedFromAgentId: agent.forkedFromAgentId || null,
|
||||
homepage: `https://lobehub.com/discover/assistant/${agent.identifier}`,
|
||||
identifier: agent.identifier,
|
||||
installCount: agent.installCount,
|
||||
isValidated: agent.isValidated,
|
||||
knowledgeCount: agent.knowledgeCount || 0,
|
||||
pluginCount: agent.pluginCount || 0,
|
||||
schemaVersion: 1,
|
||||
status: agent.status,
|
||||
tags: agent.tags || [],
|
||||
title: agent.name || agent.identifier,
|
||||
tokenUsage: agent.tokenUsage || 0,
|
||||
}),
|
||||
);
|
||||
|
||||
// Transform favoriteAgentGroups to DiscoverGroupAgentItem format
|
||||
const transformedFavoriteAgentGroups = (favoriteAgentGroups || []).map((group: any) => ({
|
||||
author: group.author || '',
|
||||
avatar: group.avatar || '👥',
|
||||
category: group.category as any,
|
||||
createdAt: group.createdAt,
|
||||
description: group.description || '',
|
||||
forkCount: group.forkCount || 0,
|
||||
forkedFromGroupId: group.forkedFromGroupId || null,
|
||||
homepage: `https://lobehub.com/discover/group_agent/${group.identifier}`,
|
||||
identifier: group.identifier,
|
||||
installCount: group.installCount || 0,
|
||||
isFeatured: group.isFeatured || false,
|
||||
isOfficial: group.isOfficial || false,
|
||||
isValidated: group.isValidated,
|
||||
memberCount: 0, // Will be populated from memberAgents in detail view
|
||||
schemaVersion: 1,
|
||||
status: group.status,
|
||||
tags: group.tags || [],
|
||||
title: group.name || group.identifier,
|
||||
updatedAt: group.updatedAt,
|
||||
}));
|
||||
|
||||
const result: DiscoverUserProfile = {
|
||||
agentGroups: transformedAgentGroups,
|
||||
agents: transformedAgents,
|
||||
favoriteAgentGroups: transformedFavoriteAgentGroups,
|
||||
favoriteAgents: transformedFavoriteAgents,
|
||||
forkedAgentGroups: transformedForkedAgentGroups,
|
||||
forkedAgents: transformedForkedAgents,
|
||||
user: {
|
||||
@@ -1833,11 +1886,13 @@ export class DiscoverService {
|
||||
};
|
||||
|
||||
log(
|
||||
'getUserInfo: returning user profile with %d agents, %d groups, %d forked agents, %d forked groups',
|
||||
'getUserInfo: returning user profile with %d agents, %d groups, %d forked agents, %d forked groups, %d favorite agents, %d favorite groups',
|
||||
result.agents.length,
|
||||
result.agentGroups?.length || 0,
|
||||
result.forkedAgents?.length || 0,
|
||||
result.forkedAgentGroups?.length || 0,
|
||||
result.favoriteAgents?.length || 0,
|
||||
result.favoriteAgentGroups?.length || 0,
|
||||
);
|
||||
return result;
|
||||
} catch (error) {
|
||||
|
||||
@@ -359,4 +359,23 @@ export class MessageService {
|
||||
|
||||
return { messages };
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel compression by deleting the compression group and restoring original messages
|
||||
*
|
||||
* @param messageGroupId - The compression group ID to cancel
|
||||
* @param context - Query options for returning updated messages
|
||||
*/
|
||||
async cancelCompression(
|
||||
messageGroupId: string,
|
||||
context: QueryOptions,
|
||||
): Promise<{ messages: UIChatMessage[]; success: boolean }> {
|
||||
// Delete compression group (this also unmarks messages)
|
||||
await this.compressionRepository.deleteCompressionGroup(messageGroupId);
|
||||
|
||||
// Query updated messages
|
||||
const messages = await this.messageModel.query(context, this.getQueryOptions());
|
||||
|
||||
return { messages, success: true };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,21 +3,21 @@ import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
|
||||
/**
|
||||
* 安全存储临时文件工具类
|
||||
* Utility class for safely storing temporary files
|
||||
*/
|
||||
export class TempFileManager {
|
||||
private readonly tempDir: string;
|
||||
private filePaths: Set<string> = new Set();
|
||||
|
||||
constructor(dirname: string) {
|
||||
// 创建唯一临时目录 (跨平台安全)
|
||||
// Create unique temporary directory (cross-platform safe)
|
||||
this.tempDir = mkdtempSync(join(tmpdir(), dirname));
|
||||
// 注册退出清理钩子
|
||||
// Register cleanup hook for process exit
|
||||
this.registerCleanupHook();
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 Uint8Array 写入临时文件
|
||||
* Write Uint8Array data to a temporary file
|
||||
|
||||
*/
|
||||
async writeTempFile(data: Uint8Array, name: string): Promise<string> {
|
||||
@@ -28,35 +28,35 @@ export class TempFileManager {
|
||||
this.filePaths.add(filePath);
|
||||
return filePath;
|
||||
} catch (error) {
|
||||
this.cleanup(); // 写入失败时立即清理
|
||||
this.cleanup(); // Immediately cleanup on write failure
|
||||
throw new Error(`Failed to write temp file: ${(error as Error).message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全清理临时资源
|
||||
* Safely cleanup temporary resources
|
||||
*/
|
||||
cleanup(): void {
|
||||
if (existsSync(this.tempDir)) {
|
||||
// 递归删除目录及内容
|
||||
// Recursively delete directory and its contents
|
||||
rmSync(this.tempDir, { force: true, recursive: true });
|
||||
this.filePaths.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册进程退出/异常时的自动清理
|
||||
* Register automatic cleanup on process exit/exception
|
||||
*/
|
||||
private registerCleanupHook(): void {
|
||||
// 正常退出
|
||||
// Normal exit
|
||||
process.on('exit', () => this.cleanup());
|
||||
// 异常退出
|
||||
// Exception exit
|
||||
process.on('uncaughtException', (err) => {
|
||||
console.error('Uncaught exception, cleaning temp files:', err);
|
||||
this.cleanup();
|
||||
process.exit(1);
|
||||
});
|
||||
// 信号终止
|
||||
// Signal termination
|
||||
['SIGINT', 'SIGTERM'].forEach((signal) => {
|
||||
process.on(signal, () => {
|
||||
this.cleanup();
|
||||
|
||||
@@ -276,6 +276,20 @@ export class MessageService {
|
||||
messages: (result.messages || []) as unknown as UIChatMessage[],
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Cancel compression by deleting the compression group and restoring original messages
|
||||
*/
|
||||
cancelCompression = async (params: {
|
||||
agentId: string;
|
||||
groupId?: string | null;
|
||||
messageGroupId: string;
|
||||
threadId?: string | null;
|
||||
topicId: string;
|
||||
}): Promise<{ messages: UIChatMessage[] }> => {
|
||||
const result = await lambdaClient.message.cancelCompression.mutate(params);
|
||||
return { messages: (result.messages || []) as unknown as UIChatMessage[] };
|
||||
};
|
||||
}
|
||||
|
||||
export const messageService = new MessageService();
|
||||
|
||||
@@ -75,7 +75,6 @@ export class ModelsService {
|
||||
const runtimeProvider = resolveRuntimeProvider(provider);
|
||||
const enableFetchOnClient = isEnableFetchOnClient(provider);
|
||||
|
||||
console.log('enableFetchOnClient:', enableFetchOnClient);
|
||||
let res: Response;
|
||||
if (enableFetchOnClient) {
|
||||
const agentRuntime = await initializeWithClientStore({
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { lambdaClient } from '@/libs/trpc/client';
|
||||
|
||||
export type SocialTargetType = 'agent' | 'plugin';
|
||||
export type SocialTargetType = 'agent' | 'plugin' | 'agent-group';
|
||||
|
||||
export interface FollowStatus {
|
||||
isFollowing: boolean;
|
||||
|
||||
Reference in New Issue
Block a user