@qlover/create-app 0.8.0 → 0.10.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.
- package/CHANGELOG.md +32 -0
- package/dist/configs/_common/.github/workflows/general-check.yml +1 -1
- package/dist/configs/_common/.github/workflows/release.yml +2 -2
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/templates/next-app/.env.template +9 -10
- package/dist/templates/next-app/eslint.config.mjs +5 -0
- package/dist/templates/next-app/next.config.ts +1 -1
- package/dist/templates/next-app/src/app/[locale]/login/page.tsx +1 -1
- package/dist/templates/next-app/src/app/[locale]/page.tsx +1 -1
- package/dist/templates/next-app/src/app/[locale]/register/page.tsx +1 -1
- package/dist/templates/next-app/src/app/api/locales/json/route.ts +2 -1
- package/dist/templates/next-app/src/i18n/request.ts +2 -2
- package/dist/templates/react-app/__tests__/__mocks__/{MockAppConfit.ts → MockAppConfig.ts} +1 -1
- package/dist/templates/react-app/__tests__/__mocks__/components/TestApp.tsx +10 -17
- package/dist/templates/react-app/__tests__/__mocks__/components/TestBootstrapsProvider.tsx +27 -8
- package/dist/templates/react-app/__tests__/__mocks__/createMockGlobals.ts +1 -1
- package/dist/templates/react-app/__tests__/__mocks__/i18nextHttpBackend.ts +110 -0
- package/dist/templates/react-app/__tests__/__mocks__/testIOC/TestIOCRegister.ts +3 -2
- package/dist/templates/react-app/__tests__/setup/setupGlobal.ts +13 -0
- package/dist/templates/react-app/__tests__/src/base/services/I18nService.test.ts +3 -1
- package/dist/templates/react-app/__tests__/src/uikit/components/chatMessage/ChatRoot.test.tsx +274 -0
- package/dist/templates/react-app/config/IOCIdentifier.ts +9 -3
- package/dist/templates/react-app/config/Identifier/components/component.chatMessage.ts +56 -0
- package/dist/templates/react-app/config/Identifier/components/component.messageBaseList.ts +103 -0
- package/dist/templates/react-app/config/Identifier/pages/index.ts +1 -0
- package/dist/templates/react-app/config/Identifier/pages/page.message.ts +20 -0
- package/dist/templates/react-app/config/app.router.ts +23 -0
- package/dist/templates/react-app/config/common.ts +38 -0
- package/dist/templates/react-app/config/feapi.mock.json +5 -12
- package/dist/templates/react-app/config/i18n/chatMessageI18n.ts +17 -0
- package/dist/templates/react-app/config/i18n/messageBaseListI18n.ts +22 -0
- package/dist/templates/react-app/config/i18n/messageI18n.ts +14 -0
- package/dist/templates/react-app/docs/en/components/chat-message-component.md +314 -0
- package/dist/templates/react-app/docs/en/components/chat-message-refactor.md +270 -0
- package/dist/templates/react-app/docs/en/components/message-base-list-component.md +172 -0
- package/dist/templates/react-app/docs/zh/components/chat-message-component.md +314 -0
- package/dist/templates/react-app/docs/zh/components/chat-message-refactor.md +270 -0
- package/dist/templates/react-app/docs/zh/components/message-base-list-component.md +172 -0
- package/dist/templates/react-app/eslint.config.mjs +6 -5
- package/dist/templates/react-app/package.json +1 -1
- package/dist/templates/react-app/playwright.config.ts +6 -6
- package/dist/templates/react-app/public/locales/en/common.json +44 -1
- package/dist/templates/react-app/public/locales/zh/common.json +44 -1
- package/dist/templates/react-app/src/base/apis/userApi/UserApi.ts +22 -13
- package/dist/templates/react-app/src/base/apis/userApi/UserApiBootstarp.ts +3 -3
- package/dist/templates/react-app/src/base/apis/userApi/UserApiType.ts +17 -12
- package/dist/templates/react-app/src/base/cases/I18nKeyErrorPlugin.ts +19 -2
- package/dist/templates/react-app/src/base/port/RouteServiceInterface.ts +2 -4
- package/dist/templates/react-app/src/base/port/UserServiceInterface.ts +15 -9
- package/dist/templates/react-app/src/base/services/BaseLayoutService.ts +55 -0
- package/dist/templates/react-app/src/base/services/I18nService.ts +1 -0
- package/dist/templates/react-app/src/base/services/UserBootstrap.ts +43 -0
- package/dist/templates/react-app/src/base/services/UserGatewayPlugin.ts +16 -0
- package/dist/templates/react-app/src/base/services/UserService.ts +51 -80
- package/dist/templates/react-app/src/core/bootstraps/BootstrapClient.ts +8 -3
- package/dist/templates/react-app/src/core/bootstraps/BootstrapsRegistry.ts +6 -6
- package/dist/templates/react-app/src/core/bootstraps/SaveAppInfo.ts +28 -0
- package/dist/templates/react-app/src/core/clientIoc/ClientIOCRegister.ts +24 -18
- package/dist/templates/react-app/src/core/globals.ts +10 -11
- package/dist/templates/react-app/src/main.tsx +1 -1
- package/dist/templates/react-app/src/pages/auth/Layout.tsx +4 -4
- package/dist/templates/react-app/src/pages/auth/RegisterPage.tsx +1 -1
- package/dist/templates/react-app/src/pages/base/Layout.tsx +3 -3
- package/dist/templates/react-app/src/pages/base/MessagePage.tsx +40 -0
- package/dist/templates/react-app/src/uikit/components/BaseLayoutProvider.tsx +44 -0
- package/dist/templates/react-app/src/uikit/components/LogoutButton.tsx +1 -3
- package/dist/templates/react-app/src/uikit/components/MessageBaseList.tsx +240 -0
- package/dist/templates/react-app/src/uikit/components/chatMessage/ChatMessageBridge.ts +176 -0
- package/dist/templates/react-app/src/uikit/components/chatMessage/ChatRoot.tsx +21 -0
- package/dist/templates/react-app/src/uikit/components/chatMessage/FocusBar.tsx +106 -0
- package/dist/templates/react-app/src/uikit/components/chatMessage/MessageApi.ts +271 -0
- package/dist/templates/react-app/src/uikit/components/chatMessage/MessageItem.tsx +102 -0
- package/dist/templates/react-app/src/uikit/components/chatMessage/MessagesList.tsx +86 -0
- package/dist/templates/react-app/src/uikit/hooks/useNavigateBridge.ts +9 -0
- package/dist/templates/react-app/src/uikit/hooks/{useI18nGuard.ts → useRouterI18nGuard.ts} +7 -4
- package/dist/templates/react-app/tsconfig.app.json +4 -2
- package/dist/templates/react-app/tsconfig.node.json +4 -0
- package/dist/templates/react-app/tsconfig.test.json +3 -1
- package/package.json +3 -3
- package/dist/templates/react-app/__tests__/src/base/cases/AppError.test.ts +0 -102
- package/dist/templates/react-app/__tests__/src/main.integration.test.tsx +0 -61
- package/dist/templates/react-app/src/base/services/ProcesserExecutor.ts +0 -57
- package/dist/templates/react-app/src/uikit/components/ProcessExecutorProvider.tsx +0 -28
- package/dist/templates/react-app/src/uikit/components/UserAuthProvider.tsx +0 -16
|
@@ -14,8 +14,8 @@ 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
|
-
import { ProcesserExecutor } from '@/base/services/ProcesserExecutor';
|
|
18
17
|
import { RouteService } from '@/base/services/RouteService';
|
|
18
|
+
import { UserGatewayPlugin } from '@/base/services/UserGatewayPlugin';
|
|
19
19
|
import { UserService } from '@/base/services/UserService';
|
|
20
20
|
import { ExecutorPageBridge } from '@/uikit/bridges/ExecutorPageBridge';
|
|
21
21
|
import { JSONStoragePageBridge } from '@/uikit/bridges/JSONStoragePageBridge';
|
|
@@ -28,7 +28,6 @@ import type {
|
|
|
28
28
|
IOCContainerInterface,
|
|
29
29
|
IOCRegisterInterface
|
|
30
30
|
} from '@qlover/corekit-bridge';
|
|
31
|
-
import type { SyncStorageInterface } from '@qlover/fe-corekit';
|
|
32
31
|
import type { LoggerInterface } from '@qlover/logger';
|
|
33
32
|
|
|
34
33
|
export class ClientIOCRegister
|
|
@@ -71,30 +70,34 @@ export class ClientIOCRegister
|
|
|
71
70
|
*/
|
|
72
71
|
protected registerImplement(ioc: IOCContainerInterface): void {
|
|
73
72
|
ioc.bind(I.I18nServiceInterface, new I18nService(this.options.pathname));
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
85
|
-
)
|
|
73
|
+
|
|
74
|
+
const routeService = new RouteService(
|
|
75
|
+
ioc.get(NavigateBridge),
|
|
76
|
+
ioc.get(I.I18nServiceInterface),
|
|
77
|
+
{
|
|
78
|
+
routes: useLocaleRoutes ? baseRoutes : baseNoLocaleRoutes,
|
|
79
|
+
logger: ioc.get(I.Logger),
|
|
80
|
+
hasLocalRoutes: useLocaleRoutes,
|
|
81
|
+
routerPrefix: routerPrefix
|
|
82
|
+
}
|
|
86
83
|
);
|
|
84
|
+
ioc.bind(I.RouteServiceInterface, routeService);
|
|
87
85
|
ioc.bind(
|
|
88
86
|
I.ThemeService,
|
|
89
87
|
new ThemeService({
|
|
90
88
|
...(themeConfig as unknown as ThemeServiceProps),
|
|
91
|
-
storage: ioc.get
|
|
89
|
+
storage: ioc.get(I.LocalStorage)
|
|
92
90
|
})
|
|
93
91
|
);
|
|
94
92
|
|
|
95
93
|
ioc.bind(I.I18nKeyErrorPlugin, ioc.get(I18nKeyErrorPlugin));
|
|
96
|
-
|
|
97
|
-
ioc.bind(
|
|
94
|
+
|
|
95
|
+
ioc.bind(
|
|
96
|
+
I.UserServiceInterface,
|
|
97
|
+
ioc
|
|
98
|
+
.get<UserServiceInterface>(UserService)
|
|
99
|
+
.use(new UserGatewayPlugin(routeService))
|
|
100
|
+
);
|
|
98
101
|
ioc.bind(I.RequestCatcherInterface, ioc.get(RequestStatusCatcher));
|
|
99
102
|
ioc.bind(I.ExecutorPageBridgeInterface, ioc.get(ExecutorPageBridge));
|
|
100
103
|
ioc.bind(I.JSONStoragePageInterface, ioc.get(JSONStoragePageBridge));
|
|
@@ -111,7 +114,10 @@ export class ClientIOCRegister
|
|
|
111
114
|
token: () =>
|
|
112
115
|
ioc.get<UserServiceInterface>(I.UserServiceInterface).getToken()
|
|
113
116
|
});
|
|
114
|
-
const apiMockPlugin = new ApiMockPlugin(
|
|
117
|
+
const apiMockPlugin = new ApiMockPlugin({
|
|
118
|
+
mockData: mockDataJson,
|
|
119
|
+
logger: logger
|
|
120
|
+
});
|
|
115
121
|
const apiCatchPlugin = new ApiCatchPlugin(
|
|
116
122
|
logger,
|
|
117
123
|
ioc.get(RequestStatusCatcher)
|
|
@@ -1,17 +1,13 @@
|
|
|
1
1
|
// ! global variables, don't import any dependencies and don't have side effects
|
|
2
2
|
import { loggerStyles } from '@config/common';
|
|
3
|
-
import {
|
|
4
|
-
ColorFormatter,
|
|
5
|
-
ConsoleHandler,
|
|
6
|
-
CookieStorage,
|
|
7
|
-
Logger
|
|
8
|
-
} from '@qlover/corekit-bridge';
|
|
3
|
+
import { ColorFormatter, CookieStorage } from '@qlover/corekit-bridge';
|
|
9
4
|
import {
|
|
10
5
|
Base64Serializer,
|
|
11
6
|
JSONSerializer,
|
|
12
7
|
ObjectStorage,
|
|
13
8
|
SyncStorage
|
|
14
9
|
} from '@qlover/fe-corekit';
|
|
10
|
+
import { Logger, ConsoleHandler } from '@qlover/logger';
|
|
15
11
|
import { AppConfig } from '@/base/cases/AppConfig';
|
|
16
12
|
import { DialogHandler } from '@/base/cases/DialogHandler';
|
|
17
13
|
import type { SyncStorageInterface } from '@qlover/fe-corekit';
|
|
@@ -37,11 +33,14 @@ export const JSON = new JSONSerializer();
|
|
|
37
33
|
/**
|
|
38
34
|
* Override localStorage to use the global local storage
|
|
39
35
|
*/
|
|
40
|
-
export const localStorage = new SyncStorage(
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
36
|
+
export const localStorage = new SyncStorage<string>(
|
|
37
|
+
new ObjectStorage(),
|
|
38
|
+
[
|
|
39
|
+
JSON,
|
|
40
|
+
appConfig.isProduction ? new Base64Serializer() : undefined,
|
|
41
|
+
window.localStorage as unknown as SyncStorageInterface<string>
|
|
42
|
+
].filter(Boolean) as any[]
|
|
43
|
+
);
|
|
45
44
|
|
|
46
45
|
export const localStorageEncrypt = localStorage;
|
|
47
46
|
|
|
@@ -4,7 +4,7 @@ import { StrictMode } from 'react';
|
|
|
4
4
|
import { createRoot } from 'react-dom/client';
|
|
5
5
|
import App from './App.tsx';
|
|
6
6
|
import { BootstrapClient } from './core/bootstraps/BootstrapClient';
|
|
7
|
-
import { clientIOC } from './core/clientIoc/ClientIOC
|
|
7
|
+
import { clientIOC } from './core/clientIoc/ClientIOC';
|
|
8
8
|
|
|
9
9
|
BootstrapClient.main({
|
|
10
10
|
root: window,
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import { useStore } from '@brain-toolkit/react-kit/hooks/useStore';
|
|
2
2
|
import { IOCIdentifier } from '@config/IOCIdentifier';
|
|
3
3
|
import { Navigate, Outlet } from 'react-router-dom';
|
|
4
|
-
import { useI18nGuard } from '@/uikit/hooks/useI18nGuard';
|
|
5
4
|
import { useIOC } from '@/uikit/hooks/useIOC';
|
|
5
|
+
import { useRouterI18nGuard } from '@/uikit/hooks/useRouterI18nGuard';
|
|
6
6
|
import { BaseHeader } from '../../uikit/components/BaseHeader';
|
|
7
7
|
|
|
8
8
|
export default function Layout() {
|
|
9
9
|
const userService = useIOC(IOCIdentifier.UserServiceInterface);
|
|
10
|
-
useStore(userService.
|
|
10
|
+
const state = useStore(userService.getStore());
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
useRouterI18nGuard();
|
|
13
13
|
|
|
14
14
|
// If user is authenticated, redirect to home page
|
|
15
|
-
if (userService.isAuthenticated()) {
|
|
15
|
+
if (userService.isAuthenticated() && userService.isUserInfo(state.result)) {
|
|
16
16
|
return <Navigate data-testid="Layout" to="/" replace />;
|
|
17
17
|
}
|
|
18
18
|
|
|
@@ -3,7 +3,7 @@ import { register18n } from '@config/i18n/register18n';
|
|
|
3
3
|
import { IOCIdentifier } from '@config/IOCIdentifier';
|
|
4
4
|
import { Form, Input, Button, Checkbox } from 'antd';
|
|
5
5
|
import { useState } from 'react';
|
|
6
|
-
import type { RegisterFormData } from '@/base/
|
|
6
|
+
import type { RegisterFormData } from '@/base/apis/userApi/UserApiType';
|
|
7
7
|
import { useI18nInterface } from '@/uikit/hooks/useI18nInterface';
|
|
8
8
|
import { useIOC } from '@/uikit/hooks/useIOC';
|
|
9
9
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { Outlet } from 'react-router-dom';
|
|
2
|
-
import {
|
|
2
|
+
import { BaseLayoutProvider } from '@/uikit/components/BaseLayoutProvider';
|
|
3
3
|
import { BaseHeader } from '../../uikit/components/BaseHeader';
|
|
4
4
|
|
|
5
5
|
export default function Layout() {
|
|
6
6
|
return (
|
|
7
|
-
<
|
|
7
|
+
<BaseLayoutProvider data-testid="Layout">
|
|
8
8
|
<div
|
|
9
9
|
data-testid="basic-layout"
|
|
10
10
|
className="text-base min-h-screen bg-primary"
|
|
@@ -15,6 +15,6 @@ export default function Layout() {
|
|
|
15
15
|
<Outlet />
|
|
16
16
|
</div>
|
|
17
17
|
</div>
|
|
18
|
-
</
|
|
18
|
+
</BaseLayoutProvider>
|
|
19
19
|
);
|
|
20
20
|
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { useFactory } from '@brain-toolkit/react-kit';
|
|
2
|
+
import { chatMessageI18n } from '@config/i18n/chatMessageI18n';
|
|
3
|
+
import {
|
|
4
|
+
ChatMessageStore,
|
|
5
|
+
ChatSenderStrategy,
|
|
6
|
+
SendFailureStrategy
|
|
7
|
+
} from '@qlover/corekit-bridge';
|
|
8
|
+
import { useState } from 'react';
|
|
9
|
+
import { logger } from '@/core/globals';
|
|
10
|
+
import { ChatMessageBridge } from '@/uikit/components/chatMessage/ChatMessageBridge';
|
|
11
|
+
import { ChatRoot } from '@/uikit/components/chatMessage/ChatRoot';
|
|
12
|
+
import { MessageApi } from '@/uikit/components/chatMessage/MessageApi';
|
|
13
|
+
import { MessageBaseList } from '@/uikit/components/MessageBaseList';
|
|
14
|
+
import { useI18nInterface } from '@/uikit/hooks/useI18nInterface';
|
|
15
|
+
|
|
16
|
+
export default function MessagePage() {
|
|
17
|
+
const tt = useI18nInterface(chatMessageI18n);
|
|
18
|
+
const messagesStore = useFactory(ChatMessageStore<string>);
|
|
19
|
+
const messageApi = useFactory(MessageApi, messagesStore);
|
|
20
|
+
|
|
21
|
+
const [bridge] = useState(() => {
|
|
22
|
+
return new ChatMessageBridge<string>(messagesStore, {
|
|
23
|
+
gateway: messageApi,
|
|
24
|
+
logger: logger,
|
|
25
|
+
senderName: 'ChatSender',
|
|
26
|
+
gatewayOptions: { stream: true }
|
|
27
|
+
}).use(new ChatSenderStrategy(SendFailureStrategy.KEEP_FAILED, logger));
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<div
|
|
32
|
+
data-testid="MessagePage"
|
|
33
|
+
className="min-h-screen bg-primary py-8 px-4 sm:px-6 lg:px-8"
|
|
34
|
+
>
|
|
35
|
+
<MessageBaseList />
|
|
36
|
+
|
|
37
|
+
<ChatRoot bridge={bridge} tt={tt} />
|
|
38
|
+
</div>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { useStore } from '@brain-toolkit/react-kit';
|
|
2
|
+
import { I } from '@config/IOCIdentifier';
|
|
3
|
+
import { BaseLayoutService } from '@/base/services/BaseLayoutService';
|
|
4
|
+
import { Loading } from './Loading';
|
|
5
|
+
import { useIOC } from '../hooks/useIOC';
|
|
6
|
+
import { useNavigateBridge } from '../hooks/useNavigateBridge';
|
|
7
|
+
import { useRouterI18nGuard } from '../hooks/useRouterI18nGuard';
|
|
8
|
+
import { useStrictEffect } from '../hooks/useStrictEffect';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* BaseLayoutProvider
|
|
12
|
+
*
|
|
13
|
+
* /pages/base/Layout.tsx 布局组件的服务提供者
|
|
14
|
+
*
|
|
15
|
+
* - 验证用户信息渲染组件,如果用户未登录则渲染 loading
|
|
16
|
+
*
|
|
17
|
+
* @param children - The children to render
|
|
18
|
+
* @returns
|
|
19
|
+
*/
|
|
20
|
+
export function BaseLayoutProvider({
|
|
21
|
+
children
|
|
22
|
+
}: {
|
|
23
|
+
children: React.ReactNode;
|
|
24
|
+
}) {
|
|
25
|
+
const ioc = useIOC();
|
|
26
|
+
const baseLayoutService = ioc(BaseLayoutService);
|
|
27
|
+
const userService = ioc(I.UserServiceInterface);
|
|
28
|
+
|
|
29
|
+
useStore(userService.getStore());
|
|
30
|
+
|
|
31
|
+
useRouterI18nGuard();
|
|
32
|
+
|
|
33
|
+
useNavigateBridge();
|
|
34
|
+
|
|
35
|
+
useStrictEffect(() => {
|
|
36
|
+
baseLayoutService.use(ioc(I.I18nKeyErrorPlugin)).starup(ioc);
|
|
37
|
+
}, []);
|
|
38
|
+
|
|
39
|
+
if (!userService.isAuthenticated()) {
|
|
40
|
+
return <Loading data-testid="BaseLayoutProviderLoading" fullscreen />;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return children;
|
|
44
|
+
}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { useFactory, useStore } from '@brain-toolkit/react-kit';
|
|
2
|
+
import { messageBaseListI18n } from '@config/i18n/messageBaseListI18n';
|
|
3
|
+
import { I } from '@config/IOCIdentifier';
|
|
4
|
+
import {
|
|
5
|
+
MessageSender,
|
|
6
|
+
MessagesStore,
|
|
7
|
+
MessageStatus,
|
|
8
|
+
SenderStrategyPlugin,
|
|
9
|
+
SendFailureStrategy,
|
|
10
|
+
ThreadUtil
|
|
11
|
+
} from '@qlover/corekit-bridge';
|
|
12
|
+
import { Button, Input } from 'antd';
|
|
13
|
+
import { random } from 'lodash';
|
|
14
|
+
import { useCallback, useMemo, useState } from 'react';
|
|
15
|
+
import { useI18nInterface } from '../hooks/useI18nInterface';
|
|
16
|
+
import { useIOC } from '../hooks/useIOC';
|
|
17
|
+
import type {
|
|
18
|
+
MessageGetwayInterface,
|
|
19
|
+
MessagesStateInterface,
|
|
20
|
+
MessageStoreMsg
|
|
21
|
+
} from '@qlover/corekit-bridge';
|
|
22
|
+
|
|
23
|
+
interface MessageBaseMsg extends MessageStoreMsg<string, unknown> {}
|
|
24
|
+
|
|
25
|
+
function createMessagesState(): MessagesStateInterface<MessageBaseMsg> {
|
|
26
|
+
return {
|
|
27
|
+
messages: []
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
class MessageBaseApi implements MessageGetwayInterface {
|
|
32
|
+
async sendMessage<M extends MessageStoreMsg<string>>(
|
|
33
|
+
message: M
|
|
34
|
+
): Promise<unknown> {
|
|
35
|
+
const times = random(200, 1000);
|
|
36
|
+
|
|
37
|
+
await ThreadUtil.sleep(times);
|
|
38
|
+
|
|
39
|
+
const messageContent = message.content ?? '';
|
|
40
|
+
if (messageContent.includes('Failed') || messageContent.includes('error')) {
|
|
41
|
+
throw new Error('Failed to send message');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (times % 5 === 0) {
|
|
45
|
+
throw new Error(`Network error(${times})`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Return object response to demonstrate formatting
|
|
49
|
+
return {
|
|
50
|
+
status: 'success',
|
|
51
|
+
timestamp: new Date().toISOString(),
|
|
52
|
+
delay: `${times}ms`,
|
|
53
|
+
echo: message.content,
|
|
54
|
+
data: {
|
|
55
|
+
message: 'Message received successfully',
|
|
56
|
+
processed: true,
|
|
57
|
+
metadata: {
|
|
58
|
+
length: message.content?.length || 0,
|
|
59
|
+
type: 'text'
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function MessageBaseList() {
|
|
67
|
+
const [inputValue, setInputValue] = useState('');
|
|
68
|
+
const logger = useIOC(I.Logger);
|
|
69
|
+
const tt = useI18nInterface(messageBaseListI18n);
|
|
70
|
+
const messagesStore = useFactory(
|
|
71
|
+
MessagesStore<MessageBaseMsg>,
|
|
72
|
+
createMessagesState
|
|
73
|
+
);
|
|
74
|
+
const messageBaseApi = useFactory(MessageBaseApi);
|
|
75
|
+
|
|
76
|
+
const [messagesSender] = useState(() =>
|
|
77
|
+
new MessageSender<MessageBaseMsg>(messagesStore, {
|
|
78
|
+
gateway: messageBaseApi,
|
|
79
|
+
logger
|
|
80
|
+
}).use(new SenderStrategyPlugin(SendFailureStrategy.KEEP_FAILED))
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
const messages = useStore(messagesStore, (state) => state.messages);
|
|
84
|
+
|
|
85
|
+
const loadingMessage = useMemo(() => {
|
|
86
|
+
return messages.find((message) => message.loading);
|
|
87
|
+
}, [messages]);
|
|
88
|
+
|
|
89
|
+
const onSend = useCallback(() => {
|
|
90
|
+
messagesSender.send({
|
|
91
|
+
content: inputValue
|
|
92
|
+
});
|
|
93
|
+
setInputValue('');
|
|
94
|
+
}, [inputValue, messagesSender]);
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Render message result with proper formatting
|
|
98
|
+
* - For objects/arrays: display as formatted JSON
|
|
99
|
+
* - For strings: display as plain text
|
|
100
|
+
*/
|
|
101
|
+
const renderResult = (result: unknown) => {
|
|
102
|
+
if (!result) return null;
|
|
103
|
+
|
|
104
|
+
// Check if result is an object or array
|
|
105
|
+
if (typeof result === 'object') {
|
|
106
|
+
try {
|
|
107
|
+
const jsonString = JSON.stringify(result, null, 2);
|
|
108
|
+
return (
|
|
109
|
+
<pre
|
|
110
|
+
data-testid="MessageResultJson"
|
|
111
|
+
className="text-xs font-mono bg-green-100 p-2 rounded mt-1 overflow-x-auto whitespace-pre-wrap"
|
|
112
|
+
>
|
|
113
|
+
{jsonString}
|
|
114
|
+
</pre>
|
|
115
|
+
);
|
|
116
|
+
} catch {
|
|
117
|
+
return String(result);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// For strings and other primitives
|
|
122
|
+
return <span data-testid="MessageResultText">{String(result)}</span>;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
return (
|
|
126
|
+
<div data-testid="MessageBaseList" className="max-w-4xl mx-auto">
|
|
127
|
+
{/* Page Title */}
|
|
128
|
+
<div className="mb-6">
|
|
129
|
+
<h1 className="text-2xl font-bold text-text mb-2">{tt.title}</h1>
|
|
130
|
+
<p className="text-sm text-text-secondary">{tt.description}</p>
|
|
131
|
+
</div>
|
|
132
|
+
|
|
133
|
+
{/* Messages Container */}
|
|
134
|
+
<div className="bg-secondary rounded-lg shadow-sm border border-primary mb-4">
|
|
135
|
+
<div
|
|
136
|
+
data-testid="MessageBaseListItems"
|
|
137
|
+
className="flex flex-col gap-3 p-4 max-h-96 min-h-64 overflow-y-auto"
|
|
138
|
+
>
|
|
139
|
+
{messages.length === 0 ? (
|
|
140
|
+
<div className="flex items-center justify-center h-full text-text-secondary">
|
|
141
|
+
<div className="text-center">
|
|
142
|
+
<p className="text-base mb-1">{tt.noMessages}</p>
|
|
143
|
+
<p className="text-sm">{tt.getStarted}</p>
|
|
144
|
+
</div>
|
|
145
|
+
</div>
|
|
146
|
+
) : (
|
|
147
|
+
messages.map((message) => (
|
|
148
|
+
<div
|
|
149
|
+
key={message.id}
|
|
150
|
+
data-testid="MessageBaseListItem"
|
|
151
|
+
className="space-y-2"
|
|
152
|
+
>
|
|
153
|
+
{/* User Message */}
|
|
154
|
+
<div className="flex justify-end">
|
|
155
|
+
<div className="max-w-[70%] bg-blue-500 text-white rounded-lg px-4 py-2 shadow-sm">
|
|
156
|
+
<div className="text-sm font-medium mb-1">{tt.user}</div>
|
|
157
|
+
<div className="wrap-break-word">{message.content}</div>
|
|
158
|
+
</div>
|
|
159
|
+
</div>
|
|
160
|
+
|
|
161
|
+
{/* Gateway Response */}
|
|
162
|
+
<div className="flex justify-start">
|
|
163
|
+
<div className="max-w-[70%]">
|
|
164
|
+
{message.loading ? (
|
|
165
|
+
<div className="bg-base rounded-lg px-4 py-2 shadow-sm border border-primary">
|
|
166
|
+
<div className="text-sm font-medium text-text-secondary mb-1">
|
|
167
|
+
{tt.gateway}
|
|
168
|
+
</div>
|
|
169
|
+
<div className="flex items-center gap-2 text-text-secondary">
|
|
170
|
+
<div className="flex gap-1">
|
|
171
|
+
<span
|
|
172
|
+
className="animate-bounce inline-block w-1.5 h-1.5 bg-text-secondary rounded-full"
|
|
173
|
+
style={{ animationDelay: '0ms' }}
|
|
174
|
+
></span>
|
|
175
|
+
<span
|
|
176
|
+
className="animate-bounce inline-block w-1.5 h-1.5 bg-text-secondary rounded-full"
|
|
177
|
+
style={{ animationDelay: '150ms' }}
|
|
178
|
+
></span>
|
|
179
|
+
<span
|
|
180
|
+
className="animate-bounce inline-block w-1.5 h-1.5 bg-text-secondary rounded-full"
|
|
181
|
+
style={{ animationDelay: '300ms' }}
|
|
182
|
+
></span>
|
|
183
|
+
</div>
|
|
184
|
+
<span className="text-sm">{tt.processing}</span>
|
|
185
|
+
</div>
|
|
186
|
+
</div>
|
|
187
|
+
) : message.status === MessageStatus.FAILED ? (
|
|
188
|
+
<div className="bg-red-50 border border-red-200 rounded-lg px-4 py-2 shadow-sm">
|
|
189
|
+
<div className="text-sm font-medium text-red-800 mb-1">
|
|
190
|
+
{tt.gatewayFailed}
|
|
191
|
+
</div>
|
|
192
|
+
<div className="text-red-600 text-sm">
|
|
193
|
+
❌ {(message as any).error?.message || tt.sendFailed}
|
|
194
|
+
</div>
|
|
195
|
+
</div>
|
|
196
|
+
) : message.result ? (
|
|
197
|
+
<div className="bg-green-50 border border-green-200 rounded-lg px-4 py-2 shadow-sm">
|
|
198
|
+
<div className="text-sm font-medium text-green-800 mb-1">
|
|
199
|
+
✓ {tt.gatewayResponse}
|
|
200
|
+
</div>
|
|
201
|
+
<div className="text-green-900">
|
|
202
|
+
{renderResult(message.result)}
|
|
203
|
+
</div>
|
|
204
|
+
</div>
|
|
205
|
+
) : null}
|
|
206
|
+
</div>
|
|
207
|
+
</div>
|
|
208
|
+
</div>
|
|
209
|
+
))
|
|
210
|
+
)}
|
|
211
|
+
</div>
|
|
212
|
+
</div>
|
|
213
|
+
|
|
214
|
+
{/* Input Area */}
|
|
215
|
+
<div className="bg-secondary rounded-lg shadow-sm border border-primary p-4">
|
|
216
|
+
<div className="flex gap-2">
|
|
217
|
+
<Input
|
|
218
|
+
value={inputValue}
|
|
219
|
+
onPressEnter={onSend}
|
|
220
|
+
onChange={(e) => setInputValue(e.target.value)}
|
|
221
|
+
placeholder={tt.inputPlaceholder}
|
|
222
|
+
size="large"
|
|
223
|
+
className="flex-1"
|
|
224
|
+
disabled={loadingMessage?.loading}
|
|
225
|
+
/>
|
|
226
|
+
<Button
|
|
227
|
+
disabled={!inputValue || loadingMessage?.loading}
|
|
228
|
+
loading={loadingMessage?.loading}
|
|
229
|
+
type="primary"
|
|
230
|
+
size="large"
|
|
231
|
+
onClick={onSend}
|
|
232
|
+
>
|
|
233
|
+
{tt.sendButton}
|
|
234
|
+
</Button>
|
|
235
|
+
</div>
|
|
236
|
+
<div className="mt-2 text-xs text-text-secondary">{tt.errorTip}</div>
|
|
237
|
+
</div>
|
|
238
|
+
</div>
|
|
239
|
+
);
|
|
240
|
+
}
|