Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,36 @@
- [ ] 📝 docs
- [ ] 🔨 chore

#### 🔗 Related Issue

<!-- Link to the issue that is fixed by this PR -->

<!-- Example: Fixes #123, Closes #456, Related to #789 -->

#### 🔀 Description of Change

<!-- Thank you for your Pull Request. Please provide a description above. -->

#### 🧪 How to Test

<!-- Please describe how you tested your changes -->

<!-- For AI features, please include test prompts or scenarios -->

- [ ] Tested locally
- [ ] Added/updated tests
- [ ] No tests needed

#### 📸 Screenshots / Videos

<!-- If this PR includes UI changes, please provide screenshots or videos -->

| Before | After |
| ------ | ----- |
| ... | ... |

#### 📝 Additional Information

<!-- Add any other context about the Pull Request here. -->

<!-- Breaking changes? Migration guide? Performance impact? -->
12 changes: 6 additions & 6 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,18 +35,18 @@ jobs:
PORT: 3010
run: bun run e2e

- name: Upload Playwright HTML report (on failure)
- name: Upload Cucumber HTML report (on failure)
if: failure()
uses: actions/upload-artifact@v4
with:
name: playwright-report
path: playwright-report
name: cucumber-report
path: e2e/reports
if-no-files-found: ignore

- name: Upload Playwright traces (on failure)
- name: Upload screenshots (on failure)
if: failure()
uses: actions/upload-artifact@v4
with:
name: test-results
path: test-results
name: test-screenshots
path: e2e/screenshots
if-no-files-found: ignore
14 changes: 6 additions & 8 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -145,26 +145,24 @@ jobs:
node-version: 22
package-manager-cache: false

- name: Install bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.2.23
- name: Install pnpm
uses: pnpm/action-setup@v4

- name: Install deps
run: bun i
run: pnpm i

- name: Lint
run: bun run lint
run: npm run lint

- name: Test Client DB
run: bun run --filter @lobechat/database test:client-db
run: pnpm --filter @lobechat/database test:client-db
env:
KEY_VAULTS_SECRET: LA7n9k3JdEcbSgml2sxfw+4TV1AzaaFU5+R176aQz4s=
S3_PUBLIC_DOMAIN: https://example.com
APP_URL: https://home.com

- name: Test Coverage
run: bun run --filter @lobechat/database test:coverage
run: pnpm --filter @lobechat/database test:coverage
env:
DATABASE_TEST_URL: postgresql://postgres:postgres@localhost:5432/postgres
DATABASE_DRIVER: node
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -117,3 +117,4 @@ CLAUDE.local.md

prd
GEMINI.md
e2e/reports
42 changes: 42 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,48 @@

# Changelog

### [Version 1.141.6](https://github.com/lobehub/lobe-chat/compare/v1.141.5...v1.141.6)

<sup>Released on **2025-10-22**</sup>

<br/>

<details>
<summary><kbd>Improvements and Fixes</kbd></summary>

</details>

<div align="right">

[![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)

</div>

### [Version 1.141.5](https://github.com/lobehub/lobe-chat/compare/v1.141.4...v1.141.5)

<sup>Released on **2025-10-22**</sup>

#### ♻ Code Refactoring

- **misc**: Change discover page from RSC to SPA to improve performance.

<br/>

<details>
<summary><kbd>Improvements and Fixes</kbd></summary>

#### Code refactoring

- **misc**: Change discover page from RSC to SPA to improve performance, closes [#9828](https://github.com/lobehub/lobe-chat/issues/9828) ([b59ee0a](https://github.com/lobehub/lobe-chat/commit/b59ee0a))

</details>

<div align="right">

[![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)

</div>

### [Version 1.141.4](https://github.com/lobehub/lobe-chat/compare/v1.141.3...v1.141.4)

<sup>Released on **2025-10-22**</sup>
Expand Down
51 changes: 41 additions & 10 deletions apps/desktop/src/main/controllers/BrowserWindowsCtr.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { InterceptRouteParams } from '@lobechat/electron-client-ipc';
import { InterceptRouteParams, OpenSettingsWindowOptions } from '@lobechat/electron-client-ipc';
import { extractSubPath, findMatchingRoute } from '~common/routes';

import { AppBrowsersIdentifiers, BrowsersIdentifiers, WindowTemplateIdentifiers } from '@/appBrowsers';
import {
AppBrowsersIdentifiers,
BrowsersIdentifiers,
WindowTemplateIdentifiers,
} from '@/appBrowsers';
import { IpcClientEventSender } from '@/types/ipcClientEvent';

import { ControllerModule, ipcClientEvent, shortcut } from './index';
Expand All @@ -14,11 +18,16 @@ export default class BrowserWindowsCtr extends ControllerModule {
}

@ipcClientEvent('openSettingsWindow')
async openSettingsWindow(tab?: string) {
console.log('[BrowserWindowsCtr] Received request to open settings window', tab);
async openSettingsWindow(options?: string | OpenSettingsWindowOptions) {
const normalizedOptions: OpenSettingsWindowOptions =
typeof options === 'string' || options === undefined
? { tab: typeof options === 'string' ? options : undefined }
: options;

console.log('[BrowserWindowsCtr] Received request to open settings window', normalizedOptions);

try {
await this.app.browserManager.showSettingsWindowWithTab(tab);
await this.app.browserManager.showSettingsWindowWithTab(normalizedOptions);

return { success: true };
} catch (error) {
Expand Down Expand Up @@ -68,15 +77,37 @@ export default class BrowserWindowsCtr extends ControllerModule {

try {
if (matchedRoute.targetWindow === BrowsersIdentifiers.settings) {
const subPath = extractSubPath(path, matchedRoute.pathPrefix);

await this.app.browserManager.showSettingsWindowWithTab(subPath);
const extractedSubPath = extractSubPath(path, matchedRoute.pathPrefix);
const sanitizedSubPath =
extractedSubPath && !extractedSubPath.startsWith('?') ? extractedSubPath : undefined;
let searchParams: Record<string, string> | undefined;
try {
const url = new URL(params.url);
const entries = Array.from(url.searchParams.entries());
if (entries.length > 0) {
searchParams = entries.reduce<Record<string, string>>((acc, [key, value]) => {
acc[key] = value;
return acc;
}, {});
}
} catch (error) {
console.warn(
'[BrowserWindowsCtr] Failed to parse URL for settings route interception:',
params.url,
error,
);
}

await this.app.browserManager.showSettingsWindowWithTab({
searchParams,
tab: sanitizedSubPath,
});

return {
intercepted: true,
path,
source,
subPath,
subPath: sanitizedSubPath,
targetWindow: matchedRoute.targetWindow,
};
} else {
Expand Down Expand Up @@ -105,8 +136,8 @@ export default class BrowserWindowsCtr extends ControllerModule {
*/
@ipcClientEvent('createMultiInstanceWindow')
async createMultiInstanceWindow(params: {
templateId: WindowTemplateIdentifiers;
path: string;
templateId: WindowTemplateIdentifiers;
uniqueId?: string;
}) {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ describe('BrowserWindowsCtr', () => {
it('should show the settings window with the specified tab', async () => {
const tab = 'appearance';
const result = await browserWindowsCtr.openSettingsWindow(tab);
expect(mockShowSettingsWindowWithTab).toHaveBeenCalledWith(tab);
expect(mockShowSettingsWindowWithTab).toHaveBeenCalledWith({ tab });
expect(result).toEqual({ success: true });
});

Expand Down Expand Up @@ -120,19 +120,22 @@ describe('BrowserWindowsCtr', () => {
it('should show settings window if matched route target is settings', async () => {
const params: InterceptRouteParams = {
...baseParams,
path: '/settings?active=common',
url: 'app://host/settings?active=common',
path: '/settings/provider',
url: 'app://host/settings/provider?active=provider&provider=ollama',
};
const matchedRoute = { targetWindow: BrowsersIdentifiers.settings, pathPrefix: '/settings' };
const subPath = 'common';
const subPath = 'provider';
(findMatchingRoute as Mock).mockReturnValue(matchedRoute);
(extractSubPath as Mock).mockReturnValue(subPath);

const result = await browserWindowsCtr.interceptRoute(params);

expect(findMatchingRoute).toHaveBeenCalledWith(params.path);
expect(extractSubPath).toHaveBeenCalledWith(params.path, matchedRoute.pathPrefix);
expect(mockShowSettingsWindowWithTab).toHaveBeenCalledWith(subPath);
expect(mockShowSettingsWindowWithTab).toHaveBeenCalledWith({
searchParams: { active: 'provider', provider: 'ollama' },
tab: subPath,
});
expect(result).toEqual({
intercepted: true,
path: params.path,
Expand Down Expand Up @@ -170,18 +173,22 @@ describe('BrowserWindowsCtr', () => {
it('should return error if processing route interception fails for settings', async () => {
const params: InterceptRouteParams = {
...baseParams,
path: '/settings?active=general',
path: '/settings',
url: 'app://host/settings?active=general',
};
const matchedRoute = { targetWindow: BrowsersIdentifiers.settings, pathPrefix: '/settings' };
const subPath = 'general';
const subPath = undefined;
const errorMessage = 'Processing error for settings';
(findMatchingRoute as Mock).mockReturnValue(matchedRoute);
(extractSubPath as Mock).mockReturnValue(subPath);
mockShowSettingsWindowWithTab.mockRejectedValueOnce(new Error(errorMessage));

const result = await browserWindowsCtr.interceptRoute(params);

expect(mockShowSettingsWindowWithTab).toHaveBeenCalledWith({
searchParams: { active: 'general' },
tab: subPath,
});
expect(result).toEqual({
error: errorMessage,
intercepted: false,
Expand Down
Loading
Loading