feat(other): guide standalone users to settings.json from the no-paths empty state

The standalone empty state showed the folder_paths keys but not where to
put them, and its Open Settings button led to a modal that cannot edit
primary folder paths. Now the page shows the real settings.json path and
an Open Settings Folder button backed by the existing open-location API.

Also stop open_settings_location from claiming success on headless Linux
sessions: with no DISPLAY/WAYLAND_DISPLAY, xdg-open cannot work, so the
handler now returns clipboard mode and the browser copies/shows the path
instead.
This commit is contained in:
Will Miao
2026-09-17 10:30:20 +08:00
parent 9eeebac40b
commit 369613f811
17 changed files with 231 additions and 16 deletions
@@ -25,6 +25,7 @@ describe('Other Models disabled page', () => {
document.body.innerHTML = [
'<button id="enableOtherModelsBtn"></button>',
'<button id="openOtherModelsSettingsBtn"></button>',
'<button id="openSettingsFolderBtn"></button>',
].join('');
Object.defineProperty(window, 'location', {
@@ -64,6 +65,52 @@ describe('Other Models disabled page', () => {
expect(showModal).toHaveBeenCalledWith('settingsModal');
});
it('reveals the settings.json location from the standalone no-folders state', async () => {
global.fetch = vi.fn().mockResolvedValue({
ok: true,
json: async () => ({ success: true, message: 'Opened settings folder' }),
});
const button = document.getElementById('openSettingsFolderBtn');
button.dispatchEvent(new MouseEvent('click', { bubbles: true }));
await vi.waitFor(() => expect(showToastMock).toHaveBeenCalled());
expect(global.fetch).toHaveBeenCalledWith(
'/api/lm/settings/open-location',
expect.objectContaining({ method: 'POST' }),
);
expect(showToastMock).toHaveBeenCalledWith(
'settings.openSettingsFileLocation.success',
{},
'success',
);
expect(button.disabled).toBe(false);
});
it('copies the settings path to the clipboard in Docker mode', async () => {
const writeText = vi.fn().mockResolvedValue(undefined);
Object.defineProperty(navigator, 'clipboard', {
value: { writeText },
configurable: true,
});
global.fetch = vi.fn().mockResolvedValue({
ok: true,
json: async () => ({ success: true, mode: 'clipboard', path: '/data/settings.json' }),
});
document.getElementById('openSettingsFolderBtn').dispatchEvent(
new MouseEvent('click', { bubbles: true }),
);
await vi.waitFor(() => expect(showToastMock).toHaveBeenCalled());
expect(writeText).toHaveBeenCalledWith('/data/settings.json');
expect(showToastMock).toHaveBeenCalledWith(
'settings.openSettingsFileLocation.copied',
{ path: '/data/settings.json' },
'success',
);
});
it('enables Other Models through the settings API and reloads', async () => {
global.fetch = vi.fn().mockResolvedValue({
ok: true,