TidGi-Desktop/src/main.ts
lin onetwo a712b2ff51
Fix/misc bug (#679)
* Create ErrorDuringRelease.md

* Enforce test timeouts and add root tiddler scenario

Set global and step timeouts to 5s (local) and 10s (CI) across cucumber config and step definitions to standardize test execution times. Add a new scenario to verify root tiddler configuration and content loading after restart. Enhance start-e2e-app script to accept or auto-detect test scenario names and pass them to the app.

* Improve error handling for window and view initialization

Enhanced error reporting and handling when browser windows are not ready or fail to register in windowService. Updated focus logic to dynamically retrieve the current browser window, improving reliability during workspace hibernation and wake-up scenarios.

* Remove AfterAll hook and add --exit to e2e tests

Eliminates the AfterAll hook that forced process exit in cleanup.ts to prevent hanging after tests. Adds the --exit flag to the cucumber-js command in the e2e test script to ensure proper test process termination.

* Add step to restart workspace in wiki tests

Introduces a new step definition 'I restart workspace {string}' to programmatically restart a wiki workspace during tests. Updates the root tiddler scenario to use this step for verifying lazy-load behavior after workspace restart, improving test reliability and clarity.

* Centralize and standardize E2E test timeouts

Extracted timeout values into features/supports/timeouts.ts and replaced hardcoded timeouts in step definitions with named constants. This ensures consistent timeout handling across local and CI environments, reduces duplication, and clarifies intent. Also improved workspace update logic to check watch-fs state before restart and cleaned up related log marker handling.

* Improve i18n coverage and add Windows installer log access

Expanded and unified i18n keys for error messages and UI labels across multiple languages. Refactored code to remove hardcoded default values from translation calls. Added a Developer Tools option to open the Windows installer log folder (SquirrelTemp) when running on Windows. Introduced a placeholder file to preserve dynamic i18n keys for error messages.

* Initialize Tidgi mini window before workspace views

Moved the initialization of the Tidgi mini window to occur before initializing all workspace views in main.ts to ensure correct view creation. Added a clarifying comment in DeveloperTools.tsx regarding the SquirrelSetup.log path.

* Refactor Tidgi mini window initialization logic

Tidgi mini window creation now only creates the window; view creation is deferred to initializeAllWorkspaceView. Updated related comments and logging for clarity. Also fixed formatting in French translations and improved documentation for error handling during release.

* Add model feature chips to model selection UI

Introduces a ModelFeatureChip component to visually display model features in the model selector and new model dialog. Updates defaultProviders to include new models with features, and enhances the UI to show feature chips for each model, improving clarity for users selecting models.

* Add image attachment support to chat messages

This update enables users to attach image files to chat messages, including UI changes for file selection and preview, backend persistence of attachments, and prompt concatenation logic to include images in AI requests. It also adds error handling and i18n for model vision support, updates message rendering to display images, and improves logging and API validation for vision-capable models.

* Improve streaming status handling for agent messages

Adds a failsafe to clear streaming status when an agent reaches a terminal state and refines logic to prevent marking completed messages as streaming. Also updates message stream completion in AgentInstanceService to ensure proper cleanup and delivery of IPC messages. Includes new feature tests for message streaming status and image upload scenarios.

* Add cross-window sync feature and test steps

Introduces a new feature file for cross-window synchronization scenarios. Adds step definitions to open workspaces in new windows and execute TiddlyWiki code programmatically. Removes obsolete wiki.ts.backup file and updates agentActions for related actions.

* feat(sync): fix cross-window synchronization via SSE

- Remove overly aggressive echo prevention in backend that blocked all SSE updates
- Backend now forwards all wiki change events to subscribers
- Add comprehensive cross-window sync tests verifying bidirectional updates
- Test main->new window sync and new->main window sync scenarios
- Version bump to 0.13.0-prerelease19

* Improve file attachment handling in chat and tests

Refactors file input handling in chat tests to use Playwright's setInputFiles, updates message sending types to support optional file attachments, and enhances file metadata persistence and logging. Adjusts test expectations and UI logic to better handle and display image attachments, and clarifies combobox value assertions in ExternalAPI tests.

* Add file input validation and improve i18n messages

Added image type and size validation (10MB limit) to file input in InputContainer. Improved image preview logic. Updated French, Japanese, and Russian translations with new error messages for missing/default model and vision support. Enhanced type safety in promptConcatWithImage tests and messagePersistence logging. Fixed race condition in ExternalAPIService lazy initialization. Updated CommitDetailsPanel to use common cancel translation key.

* review

* Update browserView.ts

* Update timeouts.ts

* Update cucumber.config.js

* Update cucumber.config.js

* Move global timeout config to separate module

Extracted global timeout setup from cucumber.config.js to features/supports/timeout-config.ts using setDefaultTimeout. This ensures the timeout is set via code rather than config, improving clarity and maintainability.

* Update newAgent.feature
2026-01-26 02:43:27 +08:00

258 lines
12 KiB
TypeScript
Executable file

import { uninstall } from './helpers/installV8Cache';
import 'source-map-support/register';
import 'reflect-metadata';
import './helpers/singleInstance';
import './services/database/configSetting';
import { app, ipcMain, powerMonitor, protocol } from 'electron';
import unhandled from 'electron-unhandled';
import inspector from 'node:inspector';
import { initJsonRepairLogger, initTidgiConfigLogger } from './services/database/configSetting';
import { MainChannel } from '@/constants/channels';
import { isDevelopmentOrTest, isTest } from '@/constants/environment';
import { TIDGI_PROTOCOL_SCHEME } from '@/constants/protocol';
import { container } from '@services/container';
import { initRendererI18NHandler } from '@services/libs/i18n';
import { destroyLogger, logger } from '@services/libs/log';
import { buildLanguageMenu } from '@services/menu/buildLanguageMenu';
// Initialize loggers for modules that can't directly import logger (to avoid electron in worker bundles)
initJsonRepairLogger(logger);
initTidgiConfigLogger(logger);
import { bindServiceAndProxy } from '@services/libs/bindServiceAndProxy';
import serviceIdentifier from '@services/serviceIdentifier';
import { WindowNames } from '@services/windows/WindowProperties';
import type { IAgentDefinitionService } from '@services/agentDefinition/interface';
import type { IContextService } from '@services/context/interface';
import type { IDatabaseService } from '@services/database/interface';
import type { IDeepLinkService } from '@services/deepLink/interface';
import type { IExternalAPIService } from '@services/externalAPI/interface';
import type { IGitService } from '@services/git/interface';
import { initializeObservables } from '@services/libs/initializeObservables';
import type { INativeService } from '@services/native/interface';
import { reportErrorToGithubWithTemplates } from '@services/native/reportError';
import type { IThemeService } from '@services/theme/interface';
import type { IUpdaterService } from '@services/updater/interface';
import type { IViewService } from '@services/view/interface';
import type { IWikiService } from '@services/wiki/interface';
import type { IWikiEmbeddingService } from '@services/wikiEmbedding/interface';
import type { IWikiGitWorkspaceService } from '@services/wikiGitWorkspace/interface';
import EventEmitter from 'events';
import { initDevelopmentExtension } from './debug';
import { isLinux } from './helpers/system';
import type { IPreferenceService } from './services/preferences/interface';
import type { IWindowService } from './services/windows/interface';
import type { IWorkspaceService } from './services/workspaces/interface';
import type { IWorkspaceViewService } from './services/workspacesView/interface';
logger.info('App booting');
if (process.env.DEBUG_MAIN === 'true') {
inspector.open();
inspector.waitForDebugger();
// eslint-disable-next-line no-debugger
debugger;
}
// fix (node:9024) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 destroyed listeners added to [WebContents]. Use emitter.setMaxListeners() to increase limit (node:9024) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 devtools-reload-page listeners added to [WebContents]. Use emitter.setMaxListeners() to increase limit
EventEmitter.defaultMaxListeners = 150;
app.commandLine.appendSwitch('--disable-web-security');
app.commandLine.appendSwitch('--unsafely-disable-devtools-self-xss-warnings');
// Use different protocol scheme for test mode to avoid conflicts
protocol.registerSchemesAsPrivileged([
{ scheme: 'http', privileges: { standard: true, bypassCSP: true, allowServiceWorkers: true, supportFetchAPI: true, corsEnabled: true, stream: true } },
{ scheme: 'https', privileges: { standard: true, bypassCSP: true, allowServiceWorkers: true, supportFetchAPI: true, corsEnabled: true, stream: true } },
{ scheme: TIDGI_PROTOCOL_SCHEME, privileges: { standard: true, bypassCSP: true, allowServiceWorkers: true, supportFetchAPI: true, corsEnabled: true, stream: true } },
{ scheme: 'open', privileges: { bypassCSP: true, allowServiceWorkers: true, supportFetchAPI: true, corsEnabled: true, stream: true } },
{ scheme: 'file', privileges: { bypassCSP: true, allowServiceWorkers: true, supportFetchAPI: true, corsEnabled: true, stream: true } },
{ scheme: 'mailto', privileges: { standard: true } },
]);
bindServiceAndProxy();
// Get services - DO NOT use them until commonInit() is called
const contextService = container.get<IContextService>(serviceIdentifier.Context);
const databaseService = container.get<IDatabaseService>(serviceIdentifier.Database);
const preferenceService = container.get<IPreferenceService>(serviceIdentifier.Preference);
const updaterService = container.get<IUpdaterService>(serviceIdentifier.Updater);
const wikiGitWorkspaceService = container.get<IWikiGitWorkspaceService>(serviceIdentifier.WikiGitWorkspace);
const wikiService = container.get<IWikiService>(serviceIdentifier.Wiki);
const wikiEmbeddingService = container.get<IWikiEmbeddingService>(serviceIdentifier.WikiEmbedding);
const windowService = container.get<IWindowService>(serviceIdentifier.Window);
const workspaceService = container.get<IWorkspaceService>(serviceIdentifier.Workspace);
const workspaceViewService = container.get<IWorkspaceViewService>(serviceIdentifier.WorkspaceView);
const deepLinkService = container.get<IDeepLinkService>(serviceIdentifier.DeepLink);
const agentDefinitionService = container.get<IAgentDefinitionService>(serviceIdentifier.AgentDefinition);
const externalAPIService = container.get<IExternalAPIService>(serviceIdentifier.ExternalAPI);
const gitService = container.get<IGitService>(serviceIdentifier.Git);
const themeService = container.get<IThemeService>(serviceIdentifier.ThemeService);
const viewService = container.get<IViewService>(serviceIdentifier.View);
const nativeService = container.get<INativeService>(serviceIdentifier.NativeService);
app.on('second-instance', async () => {
// see also src/helpers/singleInstance.ts
// Someone tried to run a second instance, for example, when `runOnBackground` is true, we should focus our window.
await windowService.open(WindowNames.main);
});
app.on('activate', async () => {
await windowService.open(WindowNames.main);
});
const commonInit = async (): Promise<void> => {
await app.whenReady();
await initDevelopmentExtension();
// Initialize context service - loads language maps after app is ready. This ensures LOCALIZATION_FOLDER path is correct (process.resourcesPath is stable)
await contextService.initialize();
// Initialize database - all other services depend on it
await databaseService.initializeForApp();
// Initialize i18n early so error messages can be translated
await initRendererI18NHandler();
// Apply preferences that need to be set early
const useHardwareAcceleration = await preferenceService.get('useHardwareAcceleration');
if (!useHardwareAcceleration) {
app.disableHardwareAcceleration();
}
const ignoreCertificateErrors = await preferenceService.get('ignoreCertificateErrors');
if (ignoreCertificateErrors) {
// https://www.electronjs.org/docs/api/command-line-switches
app.commandLine.appendSwitch('ignore-certificate-errors');
}
// Initialize agent-related services after database is ready
await Promise.all([
agentDefinitionService.initialize(),
wikiEmbeddingService.initialize(),
externalAPIService.initialize(),
]);
// if user want a tidgi mini window, we create a new window for that
// handle workspace name + tiddler name in uri https://www.electronjs.org/docs/latest/tutorial/launch-app-from-url-in-another-app
// Use different protocol for test mode to avoid conflicts with production
deepLinkService.initializeDeepLink(TIDGI_PROTOCOL_SCHEME);
await windowService.open(WindowNames.main);
// Initialize services that depend on windows being created
await Promise.all([
gitService.initialize(),
themeService.initialize(),
viewService.initialize(),
nativeService.initialize(),
]);
initializeObservables();
// Auto-create default wiki workspace if none exists. Create wiki workspace first, so it is on first one
await wikiGitWorkspaceService.initialize();
// Create default page workspaces before initializing all workspace views
await workspaceService.initializeDefaultPageWorkspaces();
// Initialize tidgi mini window if enabled (must be done BEFORE initializeAllWorkspaceView)
// This only creates the window, views will be created by initializeAllWorkspaceView
await windowService.initializeTidgiMiniWindow();
// perform wiki startup and git sync for each workspace
// This will also create views for tidgi mini window (in addViewForAllBrowserViews)
await workspaceViewService.initializeAllWorkspaceView();
logger.info('[test-id-ALL_WORKSPACE_VIEW_INITIALIZED] All workspace views initialized');
// Process any pending deep link after workspaces are initialized
await deepLinkService.processPendingDeepLink();
ipcMain.emit('request-update-pause-notifications-info');
// Fix webview is not resized automatically
// when window is maximized on Linux
// https://github.com/atomery/webcatalog/issues/561
// run it here not in mainWindow.createAsync()
// because if the `mainWindow` is maximized or minimized
// before the workspaces's WebContentsView fully loaded
// error will occur
// see https://github.com/atomery/webcatalog/issues/637
if (isLinux) {
const mainWindow = windowService.get(WindowNames.main);
if (mainWindow !== undefined) {
const handleMaximize = (): void => {
// getContentSize is not updated immediately
// try once after 0.2s (for fast computer), another one after 1s (to be sure)
setTimeout(() => {
void workspaceViewService.realignActiveWorkspace();
}, 200);
setTimeout(() => {
void workspaceViewService.realignActiveWorkspace();
}, 1000);
};
mainWindow.on('maximize', handleMaximize);
mainWindow.on('unmaximize', handleMaximize);
}
}
// trigger whenTrulyReady
ipcMain.emit(MainChannel.commonInitFinished);
};
/**
* When loading wiki with https, we need to allow insecure https
* // TODO: ask user upload certificate to be used by browser view
* @url https://stackoverflow.com/questions/44658269/electron-how-to-allow-insecure-https
*/
app.on('certificate-error', (event, _webContents, _url, _error, _certificate, callback) => {
// Prevent having error
event.preventDefault();
// and continue
callback(true);
});
app.on('ready', async () => {
powerMonitor.on('shutdown', () => {
app.quit();
});
await commonInit();
try {
// buildLanguageMenu needs menuService which is initialized in commonInit
await buildLanguageMenu();
if (await preferenceService.get('syncBeforeShutdown')) {
wikiGitWorkspaceService.registerSyncBeforeShutdown();
}
await updaterService.checkForUpdates();
} catch (error) {
logger.error('Error during app ready handler', { function: "app.on('ready')", error });
}
});
app.on(MainChannel.windowAllClosed, async () => {
// prevent quit on MacOS. But also quit if we are in test.
if (isTest || !(await preferenceService.get('runOnBackground'))) {
app.quit();
}
});
app.on(
'before-quit',
async (): Promise<void> => {
logger.info('App before-quit');
// Clean up tidgi mini window before quit to ensure tray is destroyed
await windowService.closeTidgiMiniWindow(true);
destroyLogger();
await Promise.all([
databaseService.immediatelyStoreSettingsToFile(),
wikiService.stopAllWiki(),
windowService.clearWindowsReference(),
]);
uninstall?.uninstall();
},
);
unhandled({
showDialog: !isDevelopmentOrTest,
logger: (error: Error) => {
logger.error('unhandled', { error });
},
reportButton: (error) => {
reportErrorToGithubWithTemplates(error);
},
});
// Handle Windows Squirrel events (install/update/uninstall)
// Using inline implementation to avoid ESM/CommonJS compatibility issues
import squirrelStartup from './helpers/squirrelStartup';
if (squirrelStartup) {
app.quit();
}