@qlover/create-app 0.12.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/dist/index.cjs +1 -1
  3. package/dist/index.js +1 -1
  4. package/dist/templates/next-app/next.config.ts +4 -3
  5. package/dist/templates/next-app/package.json +14 -11
  6. package/dist/templates/next-app/src/app/[locale]/login/LoginForm.tsx +2 -3
  7. package/dist/templates/next-app/src/base/cases/DialogErrorPlugin.ts +6 -4
  8. package/dist/templates/next-app/src/base/cases/RequestEncryptPlugin.ts +14 -10
  9. package/dist/templates/next-app/src/base/cases/StringEncryptor.ts +2 -2
  10. package/dist/templates/next-app/src/base/port/I18nServiceInterface.ts +1 -4
  11. package/dist/templates/next-app/src/base/services/I18nService.ts +6 -2
  12. package/dist/templates/next-app/src/base/services/adminApi/AdminApiRequester.ts +11 -7
  13. package/dist/templates/next-app/src/base/services/adminApi/AdminLocalesApi.ts +11 -10
  14. package/dist/templates/next-app/src/base/services/adminApi/AdminUserApi.ts +16 -12
  15. package/dist/templates/next-app/src/base/services/appApi/AppApiPlugin.ts +19 -19
  16. package/dist/templates/next-app/src/base/services/appApi/AppApiRequester.ts +26 -21
  17. package/dist/templates/next-app/src/base/services/appApi/AppUserApi.ts +15 -6
  18. package/dist/templates/next-app/src/base/services/appApi/AppUserApiBootstrap.ts +12 -14
  19. package/dist/templates/next-app/src/core/bootstraps/BootstrapServer.ts +34 -17
  20. package/dist/templates/next-app/src/server/NextApiServer.ts +10 -4
  21. package/dist/templates/next-app/src/server/PasswordEncrypt.ts +2 -2
  22. package/dist/templates/next-app/src/server/controllers/UserController.ts +2 -2
  23. package/dist/templates/next-app/src/server/port/ServerInterface.ts +2 -2
  24. package/dist/templates/next-app/src/server/services/AdminAuthPlugin.ts +6 -7
  25. package/dist/templates/next-app/src/server/services/UserService.ts +2 -2
  26. package/dist/templates/next-app/src/uikit/components/BootstrapsProvider.tsx +11 -47
  27. package/dist/templates/react-app/__tests__/__mocks__/MockDialogHandler.ts +3 -2
  28. package/dist/templates/react-app/__tests__/__mocks__/createMockGlobals.ts +6 -1
  29. package/dist/templates/react-app/__tests__/src/base/cases/I18nKeyErrorPlugin.test.ts +21 -61
  30. package/dist/templates/react-app/__tests__/src/base/cases/RequestLogger.test.ts +29 -51
  31. package/dist/templates/react-app/__tests__/src/core/bootstraps/BootstrapClient.test.ts +8 -26
  32. package/dist/templates/react-app/__tests__/src/main.test.tsx +2 -2
  33. package/dist/templates/react-app/config/IOCIdentifier.ts +1 -1
  34. package/dist/templates/react-app/docs/en/test-guide.md +5 -5
  35. package/dist/templates/react-app/docs/zh/test-guide.md +5 -5
  36. package/dist/templates/react-app/package.json +2 -1
  37. package/dist/templates/react-app/src/base/apis/AiApi.ts +20 -12
  38. package/dist/templates/react-app/src/base/apis/feApi/FeApi.ts +14 -5
  39. package/dist/templates/react-app/src/base/apis/feApi/FeApiBootstarp.ts +26 -13
  40. package/dist/templates/react-app/src/base/apis/userApi/UserApi.ts +30 -34
  41. package/dist/templates/react-app/src/base/apis/userApi/UserApiBootstarp.ts +20 -17
  42. package/dist/templates/react-app/src/base/cases/I18nKeyErrorPlugin.ts +11 -5
  43. package/dist/templates/react-app/src/base/cases/RequestLanguages.ts +19 -6
  44. package/dist/templates/react-app/src/base/cases/RequestLogger.ts +11 -11
  45. package/dist/templates/react-app/src/base/services/BaseLayoutService.ts +11 -5
  46. package/dist/templates/react-app/src/base/services/UserBootstrap.ts +6 -7
  47. package/dist/templates/react-app/src/base/services/UserService.ts +1 -5
  48. package/dist/templates/react-app/src/core/clientIoc/ClientIOCRegister.ts +6 -7
  49. package/dist/templates/react-app/src/main.tsx +1 -1
  50. package/dist/templates/react-app/src/pages/auth/LoginPage.tsx +5 -5
  51. package/dist/templates/react-app/src/pages/base/ExecutorPage.tsx +3 -1
  52. package/dist/templates/react-app/src/pages/base/JSONStoragePage.tsx +6 -2
  53. package/dist/templates/react-app/src/pages/base/MessagePage.tsx +34 -3
  54. package/dist/templates/react-app/src/uikit/bridges/ExecutorPageBridge.ts +13 -7
  55. package/dist/templates/react-app/src/uikit/components/LogoutButton.tsx +6 -1
  56. package/dist/templates/react-app/src/uikit/components/MessageBaseList.tsx +26 -11
  57. package/dist/templates/react-app/src/uikit/components/chatMessage/ChatMessageBridge.ts +1 -1
  58. package/dist/templates/react-app/src/uikit/components/chatMessage/FocusBar.tsx +3 -1
  59. package/dist/templates/react-app/src/uikit/components/chatMessage/MessageApi.ts +16 -7
  60. package/dist/templates/react-app/src/vite-env.d.ts +1 -1
  61. package/dist/templates/react-app/tsconfig.e2e.json +5 -2
  62. package/dist/templates/react-app/tsconfig.json +7 -0
  63. package/dist/templates/react-app/tsconfig.node.json +4 -2
  64. package/dist/templates/react-app/tsconfig.test.json +4 -1
  65. package/package.json +3 -3
  66. package/dist/templates/react-app/src/base/cases/AppError.ts +0 -10
  67. package/dist/templates/react-app/src/base/port/ProcesserExecutorInterface.ts +0 -20
  68. package/dist/templates/react-app/src/base/services/UserGatewayPlugin.ts +0 -20
@@ -1,8 +1,8 @@
1
1
  import { IOCIdentifier } from '@config/IOCIdentifier';
2
2
  import {
3
+ ExecutorContextInterface,
3
4
  ExecutorError,
4
- type ExecutorContext,
5
- type ExecutorPlugin
5
+ LifecyclePluginInterface
6
6
  } from '@qlover/fe-corekit';
7
7
  import { inject, injectable } from 'inversify';
8
8
  import type { I18nServiceInterface } from '../port/I18nServiceInterface';
@@ -14,7 +14,9 @@ import type { LoggerInterface } from '@qlover/logger';
14
14
  * If the error thrown is an i18n key, it will be converted to the corresponding text
15
15
  */
16
16
  @injectable()
17
- export class I18nKeyErrorPlugin implements ExecutorPlugin {
17
+ export class I18nKeyErrorPlugin implements LifecyclePluginInterface<
18
+ ExecutorContextInterface<unknown, unknown>
19
+ > {
18
20
  public readonly pluginName = 'I18nKeyErrorPlugin';
19
21
 
20
22
  constructor(
@@ -30,7 +32,9 @@ export class I18nKeyErrorPlugin implements ExecutorPlugin {
30
32
  /**
31
33
  * @override
32
34
  */
33
- public onError(context: ExecutorContext<unknown>): Error | void {
35
+ public onError(
36
+ context: ExecutorContextInterface<unknown, unknown>
37
+ ): ExecutorError | void {
34
38
  const { error } = context;
35
39
 
36
40
  if (!error) {
@@ -39,6 +43,7 @@ export class I18nKeyErrorPlugin implements ExecutorPlugin {
39
43
 
40
44
  if (error instanceof ExecutorError) {
41
45
  const i18nText = this.translateById(error.id);
46
+ this.logger.error('i18nText', i18nText);
42
47
  return new ExecutorError(error.id, i18nText);
43
48
  }
44
49
 
@@ -50,7 +55,8 @@ export class I18nKeyErrorPlugin implements ExecutorPlugin {
50
55
 
51
56
  if (i18nText && i18nText !== i18nKey) {
52
57
  this.logger.debug('I18nKeyErrorPlugin Error:', i18nText);
53
- return new Error(i18nText);
58
+ // Use i18nText as cause (string) so ExecutorError.message will be the translated text
59
+ return new ExecutorError(i18nKey, i18nText);
54
60
  }
55
61
  }
56
62
  }
@@ -1,11 +1,13 @@
1
1
  import type { I18nServiceInterface } from '@/base/port/I18nServiceInterface';
2
2
  import type {
3
- ExecutorContext,
4
- ExecutorPlugin,
3
+ ExecutorContextInterface,
4
+ LifecyclePluginInterface,
5
5
  RequestAdapterConfig
6
6
  } from '@qlover/fe-corekit';
7
7
 
8
- export class RequestLanguages implements ExecutorPlugin {
8
+ export class RequestLanguages implements LifecyclePluginInterface<
9
+ ExecutorContextInterface<RequestAdapterConfig, unknown>
10
+ > {
9
11
  public readonly pluginName = 'RequestLanguages';
10
12
 
11
13
  constructor(
@@ -25,18 +27,29 @@ export class RequestLanguages implements ExecutorPlugin {
25
27
  /**
26
28
  * @override
27
29
  */
28
- public onBefore(context: ExecutorContext<RequestAdapterConfig>): void {
30
+ public onBefore(
31
+ context: ExecutorContextInterface<RequestAdapterConfig, unknown>
32
+ ): void {
29
33
  const currentLanguage = this.i18nService.getCurrentLanguage();
30
34
  const languages = this.i18nService.getSupportedLanguages();
31
35
 
32
36
  if (!context.parameters.headers) {
33
- context.parameters.headers = {};
37
+ context.setParameters({
38
+ ...context.parameters,
39
+ headers: {}
40
+ });
34
41
  }
35
42
 
36
43
  const languageValue = this.buildAcceptLanguage(
37
44
  Array.from(new Set([currentLanguage, ...languages]))
38
45
  );
39
46
 
40
- context.parameters.headers[this.headerName] = languageValue;
47
+ context.setParameters({
48
+ ...context.parameters,
49
+ headers: {
50
+ ...context.parameters.headers,
51
+ [this.headerName]: languageValue
52
+ }
53
+ });
41
54
  }
42
55
  }
@@ -1,11 +1,8 @@
1
1
  import { IOCIdentifier } from '@config/IOCIdentifier';
2
+ import { type ApiCatchPluginResponse } from '@qlover/corekit-bridge';
2
3
  import {
3
- type ApiCatchPluginConfig,
4
- type ApiCatchPluginResponse
5
- } from '@qlover/corekit-bridge';
6
- import {
7
- type ExecutorPlugin,
8
- type ExecutorContext,
4
+ type LifecyclePluginInterface,
5
+ type ExecutorContextInterface,
9
6
  type RequestAdapterFetchConfig,
10
7
  type RequestAdapterResponse
11
8
  } from '@qlover/fe-corekit';
@@ -13,7 +10,9 @@ import { injectable, inject } from 'inversify';
13
10
  import type { LoggerInterface } from '@qlover/logger';
14
11
 
15
12
  @injectable()
16
- export class RequestLogger implements ExecutorPlugin<RequestAdapterFetchConfig> {
13
+ export class RequestLogger implements LifecyclePluginInterface<
14
+ ExecutorContextInterface<RequestAdapterFetchConfig, unknown>
15
+ > {
17
16
  public readonly pluginName = 'RequestLogger';
18
17
 
19
18
  constructor(@inject(IOCIdentifier.Logger) public logger: LoggerInterface) {}
@@ -22,7 +21,7 @@ export class RequestLogger implements ExecutorPlugin<RequestAdapterFetchConfig>
22
21
  * @override
23
22
  */
24
23
  public onBefore(
25
- context: ExecutorContext<RequestAdapterFetchConfig<unknown>>
24
+ context: ExecutorContextInterface<RequestAdapterFetchConfig, unknown>
26
25
  ): void {
27
26
  this.logger.log(
28
27
  `%c[Request before]%c [${new Date().toLocaleString()}] ${context.parameters.method} ${context.parameters.url}`,
@@ -38,8 +37,9 @@ export class RequestLogger implements ExecutorPlugin<RequestAdapterFetchConfig>
38
37
  public async onSuccess({
39
38
  parameters,
40
39
  returnValue
41
- }: ExecutorContext<
42
- RequestAdapterFetchConfig & ApiCatchPluginConfig
40
+ }: ExecutorContextInterface<
41
+ RequestAdapterFetchConfig,
42
+ unknown
43
43
  >): Promise<void> {
44
44
  const _returnValue = returnValue as RequestAdapterResponse &
45
45
  ApiCatchPluginResponse;
@@ -65,7 +65,7 @@ export class RequestLogger implements ExecutorPlugin<RequestAdapterFetchConfig>
65
65
  public onError({
66
66
  parameters,
67
67
  error
68
- }: ExecutorContext<RequestAdapterFetchConfig>): void {
68
+ }: ExecutorContextInterface<RequestAdapterFetchConfig, unknown>): void {
69
69
  this.loggerError(parameters, error);
70
70
  }
71
71
 
@@ -1,18 +1,22 @@
1
1
  import { I } from '@config/IOCIdentifier';
2
- import { AsyncExecutor, ExecutorPlugin } from '@qlover/fe-corekit';
2
+ import {
3
+ ExecutorContextInterface,
4
+ LifecycleExecutor,
5
+ LifecyclePluginInterface
6
+ } from '@qlover/fe-corekit';
3
7
  import { injectable, inject } from 'inversify';
4
8
  import { UserBootstrap } from './UserBootstrap';
5
9
  import type { RouteServiceInterface } from '../port/RouteServiceInterface';
6
10
  import type { UserServiceInterface } from '../port/UserServiceInterface';
7
11
  import type {
8
- BootstrapContextValue,
12
+ BootstrapPluginOptions,
9
13
  IOCContainerInterface
10
14
  } from '@qlover/corekit-bridge';
11
15
  import type { LoggerInterface } from '@qlover/logger';
12
16
 
13
17
  @injectable()
14
18
  export class BaseLayoutService {
15
- protected executor: AsyncExecutor = new AsyncExecutor();
19
+ protected executor: LifecycleExecutor = new LifecycleExecutor();
16
20
 
17
21
  constructor(
18
22
  @inject(I.Logger) protected logger: LoggerInterface,
@@ -24,7 +28,9 @@ export class BaseLayoutService {
24
28
  this.use(new UserBootstrap(userService));
25
29
  }
26
30
 
27
- public use(plugin: ExecutorPlugin): this {
31
+ public use(
32
+ plugin: LifecyclePluginInterface<ExecutorContextInterface<unknown, unknown>>
33
+ ): this {
28
34
  this.executor.use(plugin);
29
35
  return this;
30
36
  }
@@ -36,7 +42,7 @@ export class BaseLayoutService {
36
42
  }
37
43
 
38
44
  public async starup(ioc: IOCContainerInterface): Promise<unknown> {
39
- const context: BootstrapContextValue = {
45
+ const context: BootstrapPluginOptions = {
40
46
  root: {},
41
47
  logger: this.logger,
42
48
  ioc: ioc
@@ -1,7 +1,7 @@
1
1
  import { LOCAL_NO_USER_TOKEN } from '@config/Identifier';
2
2
  import { I } from '@config/IOCIdentifier';
3
+ import { ExecutorError } from '@qlover/fe-corekit';
3
4
  import { inject, injectable } from 'inversify';
4
- import { AppError } from '../cases/AppError';
5
5
  import type { UserServiceInterface } from '../port/UserServiceInterface';
6
6
  import type { BootstrapExecutorPlugin } from '@qlover/corekit-bridge';
7
7
 
@@ -17,7 +17,7 @@ export class UserBootstrap implements BootstrapExecutorPlugin {
17
17
  /**
18
18
  * @override
19
19
  */
20
- public async onBefore(): Promise<void> {
20
+ public onBefore(): void {
21
21
  const userService = this.userService;
22
22
 
23
23
  if (userService.isAuthenticated()) {
@@ -29,18 +29,17 @@ export class UserBootstrap implements BootstrapExecutorPlugin {
29
29
  if (userService.isUserCredential(credential)) {
30
30
  userService.getStore().start();
31
31
 
32
- const user = await userService.refreshUserInfo();
33
-
34
- userService.getStore().success(user);
32
+ userService.refreshUserInfo().then((user) => {
33
+ userService.getStore().success(user);
34
+ });
35
35
 
36
36
  return;
37
37
  }
38
38
 
39
- const newError = new AppError(LOCAL_NO_USER_TOKEN);
39
+ const newError = new ExecutorError(LOCAL_NO_USER_TOKEN);
40
40
 
41
41
  userService.getStore().failed(newError);
42
42
 
43
- // 否则还是抛出错误
44
43
  throw newError;
45
44
  }
46
45
  }
@@ -1,8 +1,5 @@
1
1
  import { I } from '@config/IOCIdentifier';
2
- import {
3
- UserService as CorekitBridgeUserService,
4
- GatewayExecutor
5
- } from '@qlover/corekit-bridge';
2
+ import { UserService as CorekitBridgeUserService } from '@qlover/corekit-bridge';
6
3
  import { inject, injectable } from 'inversify';
7
4
  import { isObject, isString } from 'lodash';
8
5
  import { UserApi } from '@/base/apis/userApi/UserApi';
@@ -28,7 +25,6 @@ export class UserService
28
25
  logger: LoggerInterface
29
26
  ) {
30
27
  super({
31
- executor: new GatewayExecutor(),
32
28
  gateway: userApi,
33
29
  logger: logger,
34
30
  store: {
@@ -6,16 +6,15 @@ import { themeConfig } from '@config/theme';
6
6
  import {
7
7
  ApiCatchPlugin,
8
8
  ApiMockPlugin,
9
- RequestCommonPlugin,
10
9
  ThemeService
11
10
  } from '@qlover/corekit-bridge';
11
+ import { RequestPlugin } from '@qlover/fe-corekit';
12
12
  import { I18nKeyErrorPlugin } from '@/base/cases/I18nKeyErrorPlugin';
13
13
  import { RequestStatusCatcher } from '@/base/cases/RequestStatusCatcher';
14
14
  import type { IocRegisterOptions } from '@/base/port/IOCInterface';
15
15
  import type { UserServiceInterface } from '@/base/port/UserServiceInterface';
16
16
  import { I18nService } from '@/base/services/I18nService';
17
17
  import { RouteService } from '@/base/services/RouteService';
18
- import { UserGatewayPlugin } from '@/base/services/UserGatewayPlugin';
19
18
  import { UserService } from '@/base/services/UserService';
20
19
  import { ExecutorPageBridge } from '@/uikit/bridges/ExecutorPageBridge';
21
20
  import { JSONStoragePageBridge } from '@/uikit/bridges/JSONStoragePageBridge';
@@ -95,9 +94,10 @@ export class ClientIOCRegister implements IOCRegisterInterface<
95
94
 
96
95
  ioc.bind(
97
96
  I.UserServiceInterface,
98
- ioc
99
- .get<UserServiceInterface>(UserService)
100
- .use(new UserGatewayPlugin(routeService))
97
+ ioc.get<UserService>(UserService)
98
+ // TODO: use executor replace
99
+ // .use(new UserGatewayPlugin(routeService))
100
+ // .use(ioc.get(I.I18nKeyErrorPlugin))
101
101
  );
102
102
  ioc.bind(I.RequestCatcherInterface, ioc.get(RequestStatusCatcher));
103
103
  ioc.bind(I.ExecutorPageBridgeInterface, ioc.get(ExecutorPageBridge));
@@ -109,9 +109,8 @@ export class ClientIOCRegister implements IOCRegisterInterface<
109
109
  const { appConfig } = this.options;
110
110
  const logger = ioc.get<LoggerInterface>(I.Logger);
111
111
 
112
- const feApiRequestCommonPlugin = new RequestCommonPlugin({
112
+ const feApiRequestCommonPlugin = new RequestPlugin({
113
113
  tokenPrefix: appConfig.openAiTokenPrefix,
114
- requiredToken: true,
115
114
  token: () =>
116
115
  ioc.get<UserServiceInterface>(I.UserServiceInterface).getToken()
117
116
  });
@@ -2,7 +2,7 @@
2
2
  import 'reflect-metadata';
3
3
  import { StrictMode } from 'react';
4
4
  import { createRoot } from 'react-dom/client';
5
- import App from './App.tsx';
5
+ import App from './App';
6
6
  import { BootstrapClient } from './core/bootstraps/BootstrapClient';
7
7
  import { clientIOC } from './core/clientIoc/ClientIOC';
8
8
 
@@ -1,6 +1,6 @@
1
1
  import { UserOutlined, LockOutlined, GoogleOutlined } from '@ant-design/icons';
2
2
  import { login18n } from '@config/i18n/login18n';
3
- import { IOCIdentifier } from '@config/IOCIdentifier';
3
+ import { I } from '@config/IOCIdentifier';
4
4
  import { Form, Input, Button } from 'antd';
5
5
  import { useState } from 'react';
6
6
  import { LocaleLink } from '@/uikit/components/LocaleLink';
@@ -14,10 +14,10 @@ interface LoginFormData {
14
14
 
15
15
  export default function LoginPage() {
16
16
  const tt = useI18nInterface(login18n);
17
- const userService = useIOC(IOCIdentifier.UserServiceInterface);
18
- const AppConfig = useIOC(IOCIdentifier.AppConfig);
19
- const routeService = useIOC(IOCIdentifier.RouteServiceInterface);
20
- const logger = useIOC(IOCIdentifier.Logger);
17
+ const logger = useIOC(I.Logger);
18
+ const userService = useIOC(I.UserServiceInterface);
19
+ const AppConfig = useIOC(I.AppConfig);
20
+ const routeService = useIOC(I.RouteServiceInterface);
21
21
 
22
22
  const [loading, setLoading] = useState(false);
23
23
 
@@ -277,7 +277,9 @@ export default function ExecutorPage() {
277
277
  <Input
278
278
  placeholder={tt.enterUrl}
279
279
  value={customUrl}
280
- onChange={(e) => setCustomUrl(e.target.value)}
280
+ onChange={(e) =>
281
+ setCustomUrl((e.target as HTMLInputElement).value)
282
+ }
281
283
  className="flex-1"
282
284
  />
283
285
  <Select
@@ -74,7 +74,9 @@ export default function JSONStoragePage() {
74
74
  type="number"
75
75
  value={pageState.expireTime}
76
76
  onChange={(e) =>
77
- pageBridge.changeExpireTime(Number(e.target.value))
77
+ pageBridge.changeExpireTime(
78
+ Number((e.target as HTMLInputElement).value)
79
+ )
78
80
  }
79
81
  className="w-32"
80
82
  min="1000"
@@ -111,7 +113,9 @@ export default function JSONStoragePage() {
111
113
  type="number"
112
114
  value={pageState.requestTimeout}
113
115
  onChange={(e) =>
114
- pageBridge.changeRequestTimeout(Number(e.target.value))
116
+ pageBridge.changeRequestTimeout(
117
+ Number((e.target as HTMLInputElement).value)
118
+ )
115
119
  }
116
120
  className="w-32"
117
121
  min="1000"
@@ -3,8 +3,10 @@ import { chatMessageI18n } from '@config/i18n/chatMessageI18n';
3
3
  import {
4
4
  ChatMessageStore,
5
5
  ChatSenderStrategy,
6
+ MessageSenderExecutor,
6
7
  SendFailureStrategy
7
8
  } from '@qlover/corekit-bridge';
9
+ import { Aborter, AborterPlugin } from '@qlover/fe-corekit';
8
10
  import { useState } from 'react';
9
11
  import { logger } from '@/core/globals';
10
12
  import { ChatMessageBridge } from '@/uikit/components/chatMessage/ChatMessageBridge';
@@ -12,19 +14,48 @@ import { ChatRoot } from '@/uikit/components/chatMessage/ChatRoot';
12
14
  import { MessageApi } from '@/uikit/components/chatMessage/MessageApi';
13
15
  import { MessageBaseList } from '@/uikit/components/MessageBaseList';
14
16
  import { useI18nInterface } from '@/uikit/hooks/useI18nInterface';
17
+ import { useIOC } from '@/uikit/hooks/useIOC';
18
+ import type { ChatMessage, MessageSenderOptions } from '@qlover/corekit-bridge';
19
+ import type { AborterConfig } from '@qlover/fe-corekit';
20
+
21
+ // TODO: message sender getconfig type not match, need to fix
22
+ function toAborterConfig<T>(
23
+ parameters: unknown | MessageSenderOptions<ChatMessage<T>>
24
+ ): any {
25
+ const options = parameters as MessageSenderOptions<ChatMessage<T>>;
26
+ return {
27
+ abortId: options.currentMessage.id,
28
+ onAborted: options.gatewayOptions?.onAborted,
29
+ abortTimeout: options.gatewayOptions?.timeout,
30
+ signal: options.gatewayOptions?.signal
31
+ } as AborterConfig;
32
+ }
15
33
 
16
34
  export default function MessagePage() {
17
35
  const tt = useI18nInterface(chatMessageI18n);
18
36
  const messagesStore = useFactory(ChatMessageStore<string>);
19
37
  const messageApi = useFactory(MessageApi, messagesStore);
38
+ const aborter = useIOC(Aborter);
20
39
 
21
40
  const [bridge] = useState(() => {
22
- return new ChatMessageBridge<string>(messagesStore, {
41
+ const messageBridge = new ChatMessageBridge<string>(messagesStore, {
23
42
  gateway: messageApi,
24
43
  logger: logger,
44
+ aborter: aborter,
25
45
  senderName: 'ChatSender',
26
- gatewayOptions: { stream: true }
27
- }).use(new ChatSenderStrategy(SendFailureStrategy.KEEP_FAILED, logger));
46
+ gatewayOptions: { stream: true },
47
+ executor: new MessageSenderExecutor()
48
+ });
49
+ return messageBridge
50
+ .use(
51
+ new AborterPlugin<
52
+ AborterConfig & MessageSenderOptions<ChatMessage<string>>
53
+ >({
54
+ aborter: aborter,
55
+ getConfig: toAborterConfig
56
+ })
57
+ )
58
+ .use(new ChatSenderStrategy(SendFailureStrategy.KEEP_FAILED, logger));
28
59
  });
29
60
 
30
61
  return (
@@ -1,9 +1,4 @@
1
1
  import { RequestState } from '@qlover/corekit-bridge';
2
- import {
3
- ExecutorPlugin,
4
- RequestAdapterResponse,
5
- RequestAdapterFetchConfig
6
- } from '@qlover/fe-corekit';
7
2
  import { inject, injectable } from 'inversify';
8
3
  import cloneDeep from 'lodash/cloneDeep';
9
4
  import { FeApi } from '@/base/apis/feApi/FeApi';
@@ -11,12 +6,20 @@ import {
11
6
  ExecutorPageBridgeInterface,
12
7
  ExecutorPageStateInterface
13
8
  } from '@/base/port/ExecutorPageBridgeInterface';
9
+ import type {
10
+ RequestAdapterResponse,
11
+ RequestAdapterFetchConfig,
12
+ LifecyclePluginInterface,
13
+ ExecutorContextInterface
14
+ } from '@qlover/fe-corekit';
14
15
 
15
16
  class ExecutorPageBridgeState implements ExecutorPageStateInterface {
16
17
  public helloState = new RequestState();
17
18
  }
18
19
 
19
- const TestPlugin: ExecutorPlugin<RequestAdapterFetchConfig> = {
20
+ const TestPlugin: LifecyclePluginInterface<
21
+ ExecutorContextInterface<RequestAdapterFetchConfig, unknown>
22
+ > = {
20
23
  pluginName: 'test',
21
24
  async onSuccess({ parameters, returnValue }) {
22
25
  if (
@@ -38,7 +41,10 @@ export class ExecutorPageBridge extends ExecutorPageBridgeInterface {
38
41
  // FIXME: not cloneDeep, create a new instance
39
42
  this.feApi = cloneDeep(feApi);
40
43
 
41
- this.feApi.usePlugin(TestPlugin);
44
+ this.feApi.use({
45
+ pluginName: 'test',
46
+ onSuccess: TestPlugin.onSuccess
47
+ });
42
48
  }
43
49
 
44
50
  public override onTestPlugins = async () => {
@@ -15,6 +15,7 @@ export function LogoutButton() {
15
15
  const { t } = useAppTranslation();
16
16
  const dialogHandler = useIOC(IOCIdentifier.DialogHandler);
17
17
  const userService = useIOC(IOCIdentifier.UserServiceInterface);
18
+ const routeService = useIOC(IOCIdentifier.RouteServiceInterface);
18
19
  const breakpoint = useBreakpoint();
19
20
  const tTitle = t(AUTH_LOGOUT_DIALOG_TITLE);
20
21
  const tContent = t(AUTH_LOGOUT_DIALOG_CONTENT);
@@ -29,7 +30,11 @@ export function LogoutButton() {
29
30
  'data-testid': 'LogoutButton-CancelButton'
30
31
  },
31
32
  content: tContent,
32
- onOk: () => userService.logout()
33
+ onOk: async () => {
34
+ await userService.logout();
35
+ routeService.reset();
36
+ routeService.gotoLogin();
37
+ }
33
38
  });
34
39
  }, [tTitle, tContent]);
35
40
 
@@ -3,6 +3,7 @@ import { messageBaseListI18n } from '@config/i18n/messageBaseListI18n';
3
3
  import { I } from '@config/IOCIdentifier';
4
4
  import {
5
5
  MessageSender,
6
+ MessageSenderExecutor,
6
7
  MessagesStore,
7
8
  MessageStatus,
8
9
  SenderStrategyPlugin,
@@ -15,7 +16,9 @@ import { useCallback, useMemo, useState } from 'react';
15
16
  import { useI18nInterface } from '../hooks/useI18nInterface';
16
17
  import { useIOC } from '../hooks/useIOC';
17
18
  import type {
19
+ GatewayOptions,
18
20
  MessageGetwayInterface,
21
+ MessageInterface,
19
22
  MessagesStateInterface,
20
23
  MessageStoreMsg
21
24
  } from '@qlover/corekit-bridge';
@@ -32,37 +35,46 @@ class MessageBaseApi implements MessageGetwayInterface {
32
35
  /**
33
36
  * @override
34
37
  */
35
- public async sendMessage<M extends MessageStoreMsg<string>>(
36
- message: M
37
- ): Promise<unknown> {
38
+ public async sendMessage<M extends MessageInterface<unknown>>(
39
+ message: M,
40
+ options?: GatewayOptions<M>
41
+ ): Promise<unknown | M> {
38
42
  const times = random(200, 1000);
39
43
 
40
44
  await ThreadUtil.sleep(times);
41
45
 
42
- const messageContent = message.content ?? '';
46
+ const messageContent = (message as any).content ?? '';
43
47
  if (messageContent.includes('Failed') || messageContent.includes('error')) {
44
- throw new Error('Failed to send message');
48
+ const error = new Error('Failed to send message');
49
+ await options?.onError?.(error);
50
+ throw error;
45
51
  }
46
52
 
47
53
  if (times % 5 === 0) {
48
- throw new Error(`Network error(${times})`);
54
+ const error = new Error(`Network error(${times})`);
55
+ await options?.onError?.(error);
56
+ throw error;
49
57
  }
50
58
 
51
59
  // Return object response to demonstrate formatting
52
- return {
60
+ const result = {
53
61
  status: 'success',
54
62
  timestamp: new Date().toISOString(),
55
63
  delay: `${times}ms`,
56
- echo: message.content,
64
+ echo: messageContent,
57
65
  data: {
58
66
  message: 'Message received successfully',
59
67
  processed: true,
60
68
  metadata: {
61
- length: message.content?.length || 0,
69
+ length: messageContent?.length || 0,
62
70
  type: 'text'
63
71
  }
64
72
  }
65
73
  };
74
+
75
+ options?.onComplete?.(message);
76
+
77
+ return result;
66
78
  }
67
79
  }
68
80
 
@@ -79,7 +91,8 @@ export function MessageBaseList() {
79
91
  const [messagesSender] = useState(() =>
80
92
  new MessageSender<MessageBaseMsg>(messagesStore, {
81
93
  gateway: messageBaseApi,
82
- logger
94
+ logger,
95
+ executor: new MessageSenderExecutor()
83
96
  }).use(new SenderStrategyPlugin(SendFailureStrategy.KEEP_FAILED))
84
97
  );
85
98
 
@@ -220,7 +233,9 @@ export function MessageBaseList() {
220
233
  <Input
221
234
  value={inputValue}
222
235
  onPressEnter={onSend}
223
- onChange={(e) => setInputValue(e.target.value)}
236
+ onChange={(e) =>
237
+ setInputValue((e.target as HTMLInputElement).value)
238
+ }
224
239
  placeholder={tt.inputPlaceholder}
225
240
  size="large"
226
241
  className="flex-1"
@@ -22,7 +22,7 @@ export class ChatMessageBridge<
22
22
 
23
23
  constructor(
24
24
  protected readonly messages: ChatMessageStore<T>,
25
- config?: MessageSenderConfig
25
+ config?: MessageSenderConfig<ChatMessage<T>>
26
26
  ) {
27
27
  this.messageSender = new MessageSender(messages, config);
28
28
  }
@@ -80,7 +80,9 @@ export function FocusBar({ bridge, tt }: FocusBarProps) {
80
80
  autoFocus
81
81
  value={inputText}
82
82
  onKeyDown={handleKeyDown}
83
- onChange={(e) => bridge.onChangeContent(e.target.value)}
83
+ onChange={(e) =>
84
+ bridge.onChangeContent((e.target as HTMLTextAreaElement).value)
85
+ }
84
86
  placeholder={tt.inputPlaceholder}
85
87
  autoSize={{ minRows: 2, maxRows: 6 }}
86
88
  className="resize-none"
@@ -8,7 +8,8 @@ import type {
8
8
  MessageStoreMsg,
9
9
  ChatMessageStore,
10
10
  MessageGetwayInterface,
11
- GatewayOptions
11
+ GatewayOptions,
12
+ MessageInterface
12
13
  } from '@qlover/corekit-bridge';
13
14
 
14
15
  export class MessageApi implements MessageGetwayInterface {
@@ -24,11 +25,13 @@ export class MessageApi implements MessageGetwayInterface {
24
25
 
25
26
  * @override
26
27
  */
27
- public async sendMessage<M extends MessageStoreMsg<string>>(
28
+ public async sendMessage<M extends MessageInterface<unknown>>(
28
29
  message: M,
29
30
  options?: GatewayOptions<M>
30
- ): Promise<M> {
31
- const messageContent = message.content ?? '';
31
+ ): Promise<unknown | M> {
32
+ // Type assertion: we expect MessageStoreMsg<string> in this implementation
33
+ const storeMessage = message as unknown as MessageStoreMsg<string>;
34
+ const messageContent = storeMessage.content ?? '';
32
35
 
33
36
  // Check if error simulation is needed
34
37
  if (messageContent.includes('Failed') || messageContent.includes('error')) {
@@ -40,13 +43,19 @@ export class MessageApi implements MessageGetwayInterface {
40
43
  // Determine which mode to use
41
44
  if (options?.stream === true) {
42
45
  // Streaming mode: progressive output
43
- return this.sendStreamMode(message, options);
46
+ return this.sendStreamMode(
47
+ storeMessage,
48
+ options as GatewayOptions<MessageStoreMsg<string>>
49
+ ) as Promise<unknown | M>;
44
50
  } else if (options) {
45
51
  // Interruptible normal mode: one-time return, but supports stop
46
- return this.sendInterruptibleMode(message, options);
52
+ return this.sendInterruptibleMode(
53
+ storeMessage,
54
+ options as GatewayOptions<MessageStoreMsg<string>>
55
+ ) as Promise<unknown | M>;
47
56
  } else {
48
57
  // Fast normal mode: non-interruptible
49
- return this.sendNormalMode(message);
58
+ return this.sendNormalMode(storeMessage) as Promise<unknown | M>;
50
59
  }
51
60
  }
52
61