@qlover/create-app 0.7.13 → 0.7.15
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 +89 -0
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/templates/next-app/README.en.md +131 -0
- package/dist/templates/next-app/README.md +115 -20
- package/dist/templates/next-app/config/IOCIdentifier.ts +14 -1
- package/dist/templates/next-app/config/Identifier/index.ts +1 -0
- package/dist/templates/next-app/config/Identifier/page.admin.ts +48 -0
- package/dist/templates/next-app/config/i18n/admin18n.ts +33 -0
- package/dist/templates/next-app/config/i18n/index.ts +3 -1
- package/dist/templates/next-app/docs/en/api.md +387 -0
- package/dist/templates/next-app/docs/en/component.md +544 -0
- package/dist/templates/next-app/docs/en/database.md +496 -0
- package/dist/templates/next-app/docs/en/development-guide.md +727 -0
- package/dist/templates/next-app/docs/en/env.md +563 -0
- package/dist/templates/next-app/docs/en/i18n.md +287 -0
- package/dist/templates/next-app/docs/en/index.md +166 -0
- package/dist/templates/next-app/docs/en/page.md +457 -0
- package/dist/templates/next-app/docs/en/project-structure.md +177 -0
- package/dist/templates/next-app/docs/en/router.md +427 -0
- package/dist/templates/next-app/docs/en/theme.md +532 -0
- package/dist/templates/next-app/docs/en/validator.md +478 -0
- package/dist/templates/next-app/docs/zh/api.md +387 -0
- package/dist/templates/next-app/docs/zh/component.md +544 -0
- package/dist/templates/next-app/docs/zh/database.md +496 -0
- package/dist/templates/next-app/docs/zh/development-guide.md +727 -0
- package/dist/templates/next-app/docs/zh/env.md +563 -0
- package/dist/templates/next-app/docs/zh/i18n.md +287 -0
- package/dist/templates/next-app/docs/zh/index.md +166 -0
- package/dist/templates/next-app/docs/zh/page.md +457 -0
- package/dist/templates/next-app/docs/zh/project-structure.md +177 -0
- package/dist/templates/next-app/docs/zh/router.md +427 -0
- package/dist/templates/next-app/docs/zh/theme.md +532 -0
- package/dist/templates/next-app/docs/zh/validator.md +476 -0
- package/dist/templates/next-app/migrations/schema/UserSchema.ts +2 -2
- package/dist/templates/next-app/next.config.ts +1 -1
- package/dist/templates/next-app/package.json +3 -1
- package/dist/templates/next-app/public/locales/en.json +8 -1
- package/dist/templates/next-app/public/locales/zh.json +8 -1
- package/dist/templates/next-app/src/app/[locale]/admin/layout.tsx +1 -1
- package/dist/templates/next-app/src/app/[locale]/admin/page.tsx +14 -16
- package/dist/templates/next-app/src/app/[locale]/admin/users/page.tsx +10 -3
- package/dist/templates/next-app/src/app/[locale]/layout.tsx +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 +2 -3
- package/dist/templates/next-app/src/app/[locale]/register/page.tsx +1 -1
- package/dist/templates/next-app/src/app/api/ai/completions/route.ts +32 -0
- package/dist/templates/next-app/src/base/cases/AppConfig.ts +3 -0
- package/dist/templates/next-app/src/base/cases/ChatAction.ts +21 -0
- package/dist/templates/next-app/src/base/cases/FocusBarAction.ts +36 -0
- package/dist/templates/next-app/src/base/port/AdminPageInterface.ts +1 -3
- package/dist/templates/next-app/src/base/services/AdminUserService.ts +1 -1
- package/dist/templates/next-app/src/base/services/adminApi/AdminUserApi.ts +1 -1
- package/dist/templates/next-app/src/base/services/appApi/AppApiPlugin.ts +23 -1
- package/dist/templates/next-app/src/base/services/appApi/AppUserApiBootstrap.ts +2 -2
- package/dist/templates/next-app/src/base/types/PageProps.ts +1 -1
- package/dist/templates/next-app/src/core/bootstraps/BootstrapClient.ts +1 -0
- package/dist/templates/next-app/src/core/bootstraps/BootstrapServer.ts +1 -0
- package/dist/templates/next-app/src/core/globals.ts +2 -0
- package/dist/templates/next-app/src/core/serverIoc/ServerIOCRegister.ts +4 -1
- package/dist/templates/next-app/src/{base/cases → server}/PageParams.ts +1 -1
- package/dist/templates/next-app/src/server/port/DBBridgeInterface.ts +31 -0
- package/dist/templates/next-app/src/server/port/DBTableInterface.ts +1 -1
- package/dist/templates/next-app/src/server/repositorys/UserRepository.ts +6 -4
- package/dist/templates/next-app/src/server/services/AIService.ts +43 -0
- package/dist/templates/next-app/src/server/services/ApiUserService.ts +1 -1
- package/dist/templates/next-app/src/server/{SupabaseBridge.ts → sqlBridges/SupabaseBridge.ts} +16 -11
- package/dist/templates/next-app/src/server/validators/LoginValidator.ts +4 -4
- package/dist/templates/next-app/src/server/validators/PaginationValidator.ts +1 -1
- package/dist/templates/next-app/src/uikit/components/AdminLayout.tsx +32 -25
- package/dist/templates/next-app/src/uikit/components/BaseHeader.tsx +12 -26
- package/dist/templates/next-app/src/uikit/components/BaseLayout.tsx +37 -5
- package/dist/templates/next-app/src/uikit/components/ChatRoot.tsx +17 -0
- package/dist/templates/next-app/src/uikit/components/ClientSeo.tsx +36 -0
- package/dist/templates/next-app/src/uikit/components/LanguageSwitcher.tsx +5 -6
- package/dist/templates/next-app/src/uikit/components/LogoutButton.tsx +2 -0
- package/dist/templates/next-app/src/uikit/components/With.tsx +17 -0
- package/dist/templates/next-app/src/uikit/components/chat/ChatActionInterface.ts +30 -0
- package/dist/templates/next-app/src/uikit/components/chat/ChatFocusBar.tsx +65 -0
- package/dist/templates/next-app/src/uikit/components/chat/ChatMessages.tsx +59 -0
- package/dist/templates/next-app/src/uikit/components/chat/ChatWrap.tsx +28 -0
- package/dist/templates/next-app/src/uikit/components/chat/FocusBarActionInterface.ts +19 -0
- package/package.json +1 -1
- package/dist/templates/next-app/docs/env.md +0 -94
- package/dist/templates/next-app/src/base/port/DBBridgeInterface.ts +0 -21
- package/dist/templates/next-app/src/base/port/DBMigrationInterface.ts +0 -92
- package/dist/templates/next-app/src/base/port/MigrationApiInterface.ts +0 -3
- package/dist/templates/next-app/src/base/port/ServerApiResponseInterface.ts +0 -6
- package/dist/templates/next-app/src/base/services/migrations/MigrationsApi.ts +0 -43
- package/dist/templates/next-app/config/i18n/{HomeI18n .ts → HomeI18n.ts} +0 -0
- package/dist/templates/next-app/{build → make}/generateLocales.ts +2 -2
- /package/dist/templates/next-app/src/{base → server}/port/PaginationInterface.ts +0 -0
- /package/dist/templates/next-app/src/{base → server}/port/ParamsHandlerInterface.ts +0 -0
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { clsx } from 'clsx';
|
|
4
4
|
import { useLocale, useTranslations } from 'next-intl';
|
|
5
5
|
import { useMemo } from 'react';
|
|
6
6
|
import { useIOC } from '@/uikit/hook/useIOC';
|
|
7
7
|
import { PAGE_HEAD_ADMIN_TITLE } from '@config/Identifier';
|
|
8
8
|
import { IOCIdentifier } from '@config/IOCIdentifier';
|
|
9
|
-
import { LanguageSwitcher } from './LanguageSwitcher';
|
|
10
9
|
import { LocaleLink } from './LocaleLink';
|
|
11
|
-
import { LogoutButton } from './LogoutButton';
|
|
12
|
-
import { ThemeSwitcher } from './ThemeSwitcher';
|
|
13
10
|
|
|
14
11
|
export type RenderLeftFunction = (props: {
|
|
15
12
|
locale: string;
|
|
@@ -18,13 +15,12 @@ export type RenderLeftFunction = (props: {
|
|
|
18
15
|
|
|
19
16
|
export function BaseHeader(props: {
|
|
20
17
|
href?: string;
|
|
21
|
-
showLogoutButton?: boolean;
|
|
22
|
-
showAdminButton?: boolean;
|
|
23
18
|
renderLeft?: React.ReactNode | RenderLeftFunction;
|
|
19
|
+
rightActions?: React.ReactNode;
|
|
20
|
+
className?: string;
|
|
24
21
|
}) {
|
|
25
|
-
const { href = '/',
|
|
22
|
+
const { href = '/', className, renderLeft, rightActions } = props;
|
|
26
23
|
const appConfig = useIOC(IOCIdentifier.AppConfig);
|
|
27
|
-
const i18nService = useIOC(IOCIdentifier.I18nServiceInterface);
|
|
28
24
|
const locale = useLocale();
|
|
29
25
|
const t = useTranslations();
|
|
30
26
|
|
|
@@ -44,7 +40,7 @@ export function BaseHeader(props: {
|
|
|
44
40
|
>
|
|
45
41
|
<span
|
|
46
42
|
data-testid="base-header-app-name"
|
|
47
|
-
className="
|
|
43
|
+
className="text-lg font-semibold text-text"
|
|
48
44
|
>
|
|
49
45
|
{tt.title}
|
|
50
46
|
</span>
|
|
@@ -69,24 +65,14 @@ export function BaseHeader(props: {
|
|
|
69
65
|
data-testid="BaseHeader"
|
|
70
66
|
className="h-14 bg-secondary border-b border-c-border sticky top-0 z-50"
|
|
71
67
|
>
|
|
72
|
-
<div
|
|
68
|
+
<div
|
|
69
|
+
className={clsx(
|
|
70
|
+
'flex items-center justify-between h-full px-4 mx-auto max-w-7xl',
|
|
71
|
+
className
|
|
72
|
+
)}
|
|
73
|
+
>
|
|
73
74
|
<div className="flex items-center">{RenderLeft}</div>
|
|
74
|
-
<div className="flex items-center gap-2 md:gap-4">
|
|
75
|
-
{showAdminButton && (
|
|
76
|
-
<LocaleLink
|
|
77
|
-
href="/admin"
|
|
78
|
-
title={tt.admin}
|
|
79
|
-
locale={locale}
|
|
80
|
-
className="text-text hover:text-text-hover cursor-pointer text-lg transition-colors"
|
|
81
|
-
>
|
|
82
|
-
<TeamOutlined className="text-lg text-text" />
|
|
83
|
-
</LocaleLink>
|
|
84
|
-
)}
|
|
85
|
-
|
|
86
|
-
<LanguageSwitcher i18nService={i18nService} />
|
|
87
|
-
<ThemeSwitcher />
|
|
88
|
-
{showLogoutButton && <LogoutButton />}
|
|
89
|
-
</div>
|
|
75
|
+
<div className="flex items-center gap-2 md:gap-4">{rightActions}</div>
|
|
90
76
|
</div>
|
|
91
77
|
</header>
|
|
92
78
|
);
|
|
@@ -1,5 +1,12 @@
|
|
|
1
|
+
import { TeamOutlined } from '@ant-design/icons';
|
|
2
|
+
import { useLocale, useTranslations } from 'next-intl';
|
|
3
|
+
import { useMemo, type HTMLAttributes } from 'react';
|
|
4
|
+
import { PAGE_HEAD_ADMIN_TITLE } from '@config/Identifier';
|
|
1
5
|
import { BaseHeader } from './BaseHeader';
|
|
2
|
-
import
|
|
6
|
+
import { LanguageSwitcher } from './LanguageSwitcher';
|
|
7
|
+
import { LocaleLink } from './LocaleLink';
|
|
8
|
+
import { LogoutButton } from './LogoutButton';
|
|
9
|
+
import { ThemeSwitcher } from './ThemeSwitcher';
|
|
3
10
|
|
|
4
11
|
export interface BaseLayoutProps extends HTMLAttributes<HTMLDivElement> {
|
|
5
12
|
showLogoutButton?: boolean;
|
|
@@ -14,16 +21,41 @@ export function BaseLayout({
|
|
|
14
21
|
mainProps,
|
|
15
22
|
...props
|
|
16
23
|
}: BaseLayoutProps) {
|
|
24
|
+
const locale = useLocale();
|
|
25
|
+
const t = useTranslations();
|
|
26
|
+
|
|
27
|
+
const tt = {
|
|
28
|
+
admin: t(PAGE_HEAD_ADMIN_TITLE)
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const actions = useMemo(
|
|
32
|
+
() =>
|
|
33
|
+
[
|
|
34
|
+
showAdminButton && (
|
|
35
|
+
<LocaleLink
|
|
36
|
+
key="admin-button"
|
|
37
|
+
href="/admin"
|
|
38
|
+
title={tt.admin}
|
|
39
|
+
locale={locale}
|
|
40
|
+
className="text-text hover:text-text-hover cursor-pointer text-lg transition-colors"
|
|
41
|
+
>
|
|
42
|
+
<TeamOutlined className="text-lg text-text" />
|
|
43
|
+
</LocaleLink>
|
|
44
|
+
),
|
|
45
|
+
<LanguageSwitcher key="language-switcher" />,
|
|
46
|
+
<ThemeSwitcher key="theme-switcher" />,
|
|
47
|
+
showLogoutButton && <LogoutButton key="logout-button" />
|
|
48
|
+
].filter(Boolean),
|
|
49
|
+
[showAdminButton, tt.admin, locale, showLogoutButton]
|
|
50
|
+
);
|
|
51
|
+
|
|
17
52
|
return (
|
|
18
53
|
<div
|
|
19
54
|
data-testid="BaseLayout"
|
|
20
55
|
className="flex flex-col min-h-screen"
|
|
21
56
|
{...props}
|
|
22
57
|
>
|
|
23
|
-
<BaseHeader
|
|
24
|
-
showLogoutButton={showLogoutButton}
|
|
25
|
-
showAdminButton={showAdminButton}
|
|
26
|
-
/>
|
|
58
|
+
<BaseHeader rightActions={actions} />
|
|
27
59
|
<main className="flex flex-1 flex-col bg-primary" {...mainProps}>
|
|
28
60
|
{children}
|
|
29
61
|
</main>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { ChatAction } from '@/base/cases/ChatAction';
|
|
4
|
+
import { FocusBarAction } from '@/base/cases/FocusBarAction';
|
|
5
|
+
import { useIOC } from '../hook/useIOC';
|
|
6
|
+
import { ChatWrap } from './chat/ChatWrap';
|
|
7
|
+
|
|
8
|
+
export function ChatRoot() {
|
|
9
|
+
const chatAction = useIOC(ChatAction);
|
|
10
|
+
const focusBarAction = useIOC(FocusBarAction);
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<div data-testid="ChatRoot" className="fixed bottom-0 right-0 ">
|
|
14
|
+
<ChatWrap chatAction={chatAction} focusBarAction={focusBarAction} />
|
|
15
|
+
</div>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { PageI18nInterface } from '@config/i18n';
|
|
2
|
+
import { With } from './With';
|
|
3
|
+
|
|
4
|
+
export function ClientSeo(props: {
|
|
5
|
+
i18nInterface: PageI18nInterface;
|
|
6
|
+
children?: React.ReactNode;
|
|
7
|
+
}) {
|
|
8
|
+
const { i18nInterface, children } = props;
|
|
9
|
+
|
|
10
|
+
return (
|
|
11
|
+
<>
|
|
12
|
+
<title>{i18nInterface.title}</title>
|
|
13
|
+
<meta name="description" content={i18nInterface.description} />
|
|
14
|
+
<meta name="keywords" content={i18nInterface.keywords} />
|
|
15
|
+
<meta name="author" content={i18nInterface.author} />
|
|
16
|
+
<meta name="publishedTime" content={i18nInterface.publishedTime} />
|
|
17
|
+
<meta name="modifiedTime" content={i18nInterface.modifiedTime} />
|
|
18
|
+
|
|
19
|
+
<With it={i18nInterface.canonical}>
|
|
20
|
+
<meta name="canonical" content={i18nInterface.canonical} />
|
|
21
|
+
</With>
|
|
22
|
+
|
|
23
|
+
<With it={i18nInterface.og}>
|
|
24
|
+
{({ title, description, image }) => (
|
|
25
|
+
<>
|
|
26
|
+
<meta name="og:title" content={title} />
|
|
27
|
+
<meta name="og:description" content={description} />
|
|
28
|
+
<meta name="og:image" content={image} />
|
|
29
|
+
</>
|
|
30
|
+
)}
|
|
31
|
+
</With>
|
|
32
|
+
|
|
33
|
+
{children}
|
|
34
|
+
</>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
@@ -4,17 +4,16 @@ import { TranslationOutlined } from '@ant-design/icons';
|
|
|
4
4
|
import { Dropdown } from 'antd';
|
|
5
5
|
import { useLocale } from 'next-intl';
|
|
6
6
|
import { useCallback, useMemo } from 'react';
|
|
7
|
-
import type {
|
|
8
|
-
I18nServiceInterface,
|
|
9
|
-
I18nServiceLocale
|
|
10
|
-
} from '@/base/port/I18nServiceInterface';
|
|
7
|
+
import type { I18nServiceLocale } from '@/base/port/I18nServiceInterface';
|
|
11
8
|
import { usePathname, useRouter } from '@/i18n/routing';
|
|
12
9
|
import { i18nConfig } from '@config/i18n';
|
|
13
10
|
import type { LocaleType } from '@config/i18n';
|
|
11
|
+
import { I } from '@config/IOCIdentifier';
|
|
12
|
+
import { useIOC } from '../hook/useIOC';
|
|
14
13
|
import type { ItemType } from 'antd/es/menu/interface';
|
|
15
14
|
|
|
16
|
-
export function LanguageSwitcher(
|
|
17
|
-
const
|
|
15
|
+
export function LanguageSwitcher() {
|
|
16
|
+
const i18nService = useIOC(I.I18nServiceInterface);
|
|
18
17
|
const pathname = usePathname(); // current pathname, aware of i18n
|
|
19
18
|
|
|
20
19
|
const router = useRouter(); // i18n-aware router instance
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export function With<T>(props: {
|
|
2
|
+
fallback?: React.ReactNode;
|
|
3
|
+
it: T;
|
|
4
|
+
children: React.ReactNode | ((it: NonNullable<T>) => React.ReactNode);
|
|
5
|
+
}) {
|
|
6
|
+
const { fallback, it, children } = props;
|
|
7
|
+
|
|
8
|
+
if (it) {
|
|
9
|
+
if (typeof children === 'function') {
|
|
10
|
+
return children(it);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return children;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return fallback ?? null;
|
|
17
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import {
|
|
2
|
+
StoreInterface,
|
|
3
|
+
type StoreStateInterface
|
|
4
|
+
} from '@qlover/corekit-bridge';
|
|
5
|
+
|
|
6
|
+
export const MessageType = Object.freeze({
|
|
7
|
+
USER: 'user',
|
|
8
|
+
ASSISTANT: 'assistant'
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
export type MessageTypeValue = (typeof MessageType)[keyof typeof MessageType];
|
|
12
|
+
|
|
13
|
+
export interface MessageInterface {
|
|
14
|
+
id: string;
|
|
15
|
+
content: unknown;
|
|
16
|
+
role: MessageTypeValue;
|
|
17
|
+
createdAt: string;
|
|
18
|
+
|
|
19
|
+
loading?: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface ChatStateInterface extends StoreStateInterface {
|
|
23
|
+
messages: MessageInterface[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export abstract class ChatActionInterface<
|
|
27
|
+
S extends ChatStateInterface
|
|
28
|
+
> extends StoreInterface<S> {
|
|
29
|
+
abstract focus(): void;
|
|
30
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { SendOutlined } from '@ant-design/icons';
|
|
2
|
+
import { Button, Input } from 'antd';
|
|
3
|
+
import { useCallback, useRef } from 'react';
|
|
4
|
+
import { useStore } from '../../hook/useStore';
|
|
5
|
+
import type {
|
|
6
|
+
FocusBarActionInterface,
|
|
7
|
+
FocusBarStateInterface
|
|
8
|
+
} from './FocusBarActionInterface';
|
|
9
|
+
|
|
10
|
+
export function ChatFocusBar({
|
|
11
|
+
focusBarAction
|
|
12
|
+
}: {
|
|
13
|
+
focusBarAction: FocusBarActionInterface<FocusBarStateInterface>;
|
|
14
|
+
}) {
|
|
15
|
+
const inputRef = useRef<HTMLTextAreaElement>(null);
|
|
16
|
+
const { inputValue } = useStore(focusBarAction);
|
|
17
|
+
const sendState = useStore(focusBarAction, (state) => state.sendState);
|
|
18
|
+
|
|
19
|
+
const handleInputChange = useCallback(
|
|
20
|
+
(e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
|
21
|
+
focusBarAction.setInputValue(e.target.value);
|
|
22
|
+
},
|
|
23
|
+
[focusBarAction]
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
const handleKeyDown = useCallback(
|
|
27
|
+
(e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
|
28
|
+
if (e.key === 'Enter' && !e.shiftKey) {
|
|
29
|
+
e.preventDefault();
|
|
30
|
+
focusBarAction.sendMessage(inputValue);
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
[focusBarAction, inputValue]
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
const sending = sendState.loading;
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<div
|
|
40
|
+
data-testid="ChatFocusBarInput"
|
|
41
|
+
className="flex items-end gap-2 p-4 bg-elevated border-t border-c-border"
|
|
42
|
+
>
|
|
43
|
+
<Input.TextArea
|
|
44
|
+
ref={inputRef}
|
|
45
|
+
disabled={sending}
|
|
46
|
+
value={inputValue}
|
|
47
|
+
onChange={handleInputChange}
|
|
48
|
+
onKeyDown={handleKeyDown}
|
|
49
|
+
placeholder="Type your message..."
|
|
50
|
+
rows={1}
|
|
51
|
+
/>
|
|
52
|
+
<Button
|
|
53
|
+
data-testid="ChatFocusBarSendButton"
|
|
54
|
+
onClick={() => {
|
|
55
|
+
focusBarAction.sendMessage(inputValue);
|
|
56
|
+
}}
|
|
57
|
+
type="primary"
|
|
58
|
+
className="flex items-center justify-center !h-10 !w-10 !rounded-full !bg-c-brand !text-white hover:!bg-c-brand-hover transition-colors"
|
|
59
|
+
icon={<SendOutlined />}
|
|
60
|
+
loading={sending}
|
|
61
|
+
disabled={sending}
|
|
62
|
+
/>
|
|
63
|
+
</div>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react';
|
|
2
|
+
import { MessageType } from './ChatActionInterface';
|
|
3
|
+
import { useStore } from '../../hook/useStore';
|
|
4
|
+
import type {
|
|
5
|
+
ChatActionInterface,
|
|
6
|
+
ChatStateInterface,
|
|
7
|
+
MessageInterface
|
|
8
|
+
} from './ChatActionInterface';
|
|
9
|
+
|
|
10
|
+
function MessageItem({ message }: { message: MessageInterface }) {
|
|
11
|
+
return (
|
|
12
|
+
<div
|
|
13
|
+
data-testid="MessageItem"
|
|
14
|
+
className={`flex ${
|
|
15
|
+
message.role === MessageType.USER ? 'justify-end' : 'justify-start'
|
|
16
|
+
} mb-4`}
|
|
17
|
+
>
|
|
18
|
+
<div
|
|
19
|
+
data-testid="MessageItemContent"
|
|
20
|
+
className={`max-w-[70%] rounded-lg p-3 ${
|
|
21
|
+
message.role === MessageType.USER
|
|
22
|
+
? 'bg-blue-500 text-white'
|
|
23
|
+
: 'bg-gray-100 dark:bg-gray-700'
|
|
24
|
+
}`}
|
|
25
|
+
>
|
|
26
|
+
<p className="whitespace-pre-wrap break-words">{message.content}</p>
|
|
27
|
+
<div className="mt-1 text-xs opacity-70">
|
|
28
|
+
{new Date(message.createdAt).toLocaleTimeString()}
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function ChatMessages({
|
|
36
|
+
chatAction
|
|
37
|
+
}: {
|
|
38
|
+
chatAction: ChatActionInterface<ChatStateInterface>;
|
|
39
|
+
}) {
|
|
40
|
+
const messagesEndRef = useRef<HTMLDivElement>(null);
|
|
41
|
+
const { messages } = useStore(chatAction);
|
|
42
|
+
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
|
45
|
+
}, [messages]);
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<div data-testid="ChatMessages" className="flex-1 overflow-y-auto p-4">
|
|
49
|
+
{messages.map((message: MessageInterface) => (
|
|
50
|
+
<MessageItem
|
|
51
|
+
data-testid="MessageItem"
|
|
52
|
+
key={message.id}
|
|
53
|
+
message={message}
|
|
54
|
+
/>
|
|
55
|
+
))}
|
|
56
|
+
<div ref={messagesEndRef} />
|
|
57
|
+
</div>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { ChatFocusBar } from './ChatFocusBar';
|
|
2
|
+
import { ChatMessages } from './ChatMessages';
|
|
3
|
+
import type {
|
|
4
|
+
ChatActionInterface,
|
|
5
|
+
ChatStateInterface
|
|
6
|
+
} from './ChatActionInterface';
|
|
7
|
+
import type {
|
|
8
|
+
FocusBarActionInterface,
|
|
9
|
+
FocusBarStateInterface
|
|
10
|
+
} from './FocusBarActionInterface';
|
|
11
|
+
|
|
12
|
+
export function ChatWrap({
|
|
13
|
+
chatAction,
|
|
14
|
+
focusBarAction
|
|
15
|
+
}: {
|
|
16
|
+
chatAction: ChatActionInterface<ChatStateInterface>;
|
|
17
|
+
focusBarAction: FocusBarActionInterface<FocusBarStateInterface>;
|
|
18
|
+
}) {
|
|
19
|
+
return (
|
|
20
|
+
<div
|
|
21
|
+
data-testid="ChatWrap"
|
|
22
|
+
className="flex h-full flex-col p-2 bg-primary shadow-2xl"
|
|
23
|
+
>
|
|
24
|
+
<ChatMessages chatAction={chatAction} />
|
|
25
|
+
<ChatFocusBar focusBarAction={focusBarAction} />
|
|
26
|
+
</div>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import {
|
|
2
|
+
StoreInterface,
|
|
3
|
+
type StoreStateInterface
|
|
4
|
+
} from '@qlover/corekit-bridge';
|
|
5
|
+
import type { AsyncStateInterface } from '@/base/port/AsyncStateInterface';
|
|
6
|
+
|
|
7
|
+
export interface FocusBarStateInterface extends StoreStateInterface {
|
|
8
|
+
showHistoryArea: boolean;
|
|
9
|
+
inputValue: string;
|
|
10
|
+
sendState: AsyncStateInterface<unknown>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export abstract class FocusBarActionInterface<
|
|
14
|
+
S extends FocusBarStateInterface
|
|
15
|
+
> extends StoreInterface<S> {
|
|
16
|
+
abstract setInputValue(value: string): void;
|
|
17
|
+
abstract clearInput(): void;
|
|
18
|
+
abstract sendMessage(message: string): void | Promise<void>;
|
|
19
|
+
}
|
package/package.json
CHANGED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
# 环境配置指南
|
|
2
|
-
|
|
3
|
-
本项目使用 Next.js 内置的环境变量系统来管理不同环境的配置。
|
|
4
|
-
|
|
5
|
-
## 环境文件
|
|
6
|
-
|
|
7
|
-
项目支持以下环境配置文件:
|
|
8
|
-
|
|
9
|
-
- `.env` - 默认环境配置,适用于所有环境
|
|
10
|
-
- `.env.local` - 本地环境配置,会覆盖 `.env`(不应提交到版本控制)
|
|
11
|
-
- `.env.development` - 开发环境配置
|
|
12
|
-
- `.env.production` - 生产环境配置
|
|
13
|
-
- `.env.test` - 测试环境配置
|
|
14
|
-
|
|
15
|
-
加载优先级(从高到低):
|
|
16
|
-
|
|
17
|
-
1. `.env.local`
|
|
18
|
-
2. `.env.[environment]`
|
|
19
|
-
3. `.env`
|
|
20
|
-
|
|
21
|
-
## 使用方法
|
|
22
|
-
|
|
23
|
-
1. 复制 `.env.template` 创建对应环境的配置文件:
|
|
24
|
-
|
|
25
|
-
```bash
|
|
26
|
-
# 开发环境
|
|
27
|
-
cp .env.template .env.development
|
|
28
|
-
|
|
29
|
-
# 生产环境
|
|
30
|
-
cp .env.template .env.production
|
|
31
|
-
|
|
32
|
-
# 测试环境
|
|
33
|
-
cp .env.template .env.test
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
2. 修改对应环境的配置文件内容
|
|
37
|
-
|
|
38
|
-
3. 启动应用时指定环境:
|
|
39
|
-
|
|
40
|
-
```bash
|
|
41
|
-
# 开发环境
|
|
42
|
-
APP_ENV=development npm run dev
|
|
43
|
-
|
|
44
|
-
# 生产环境
|
|
45
|
-
APP_ENV=production npm run build
|
|
46
|
-
APP_ENV=production npm run start
|
|
47
|
-
|
|
48
|
-
# 测试环境
|
|
49
|
-
APP_ENV=test npm run test
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
## 在代码中使用环境变量
|
|
53
|
-
|
|
54
|
-
1. 服务端和客户端都可访问的变量:
|
|
55
|
-
|
|
56
|
-
```typescript
|
|
57
|
-
// 在 .env 文件中定义
|
|
58
|
-
PUBLIC_API_URL=http://api.example.com
|
|
59
|
-
|
|
60
|
-
// 在代码中使用
|
|
61
|
-
console.log(process.env.PUBLIC_API_URL)
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
2. 仅服务端可访问的变量:
|
|
65
|
-
|
|
66
|
-
```typescript
|
|
67
|
-
// 在 .env 文件中定义(以 PRIVATE_ 开头)
|
|
68
|
-
PRIVATE_API_KEY = secret_key;
|
|
69
|
-
|
|
70
|
-
// 在服务端代码中使用
|
|
71
|
-
console.log(process.env.PRIVATE_API_KEY);
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
3. 使用运行时配置:
|
|
75
|
-
|
|
76
|
-
```typescript
|
|
77
|
-
import getConfig from 'next/config';
|
|
78
|
-
|
|
79
|
-
const { serverRuntimeConfig, publicRuntimeConfig } = getConfig();
|
|
80
|
-
|
|
81
|
-
// 服务端配置
|
|
82
|
-
console.log(serverRuntimeConfig.mySecret);
|
|
83
|
-
|
|
84
|
-
// 公共配置
|
|
85
|
-
console.log(publicRuntimeConfig.appEnv);
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
## 注意事项
|
|
89
|
-
|
|
90
|
-
1. 不要将包含敏感信息的 `.env` 文件提交到版本控制
|
|
91
|
-
2. 确保 `.env.local` 和 `*.env` 文件在 `.gitignore` 中
|
|
92
|
-
3. 使用 `.env.template` 作为配置模板
|
|
93
|
-
4. 环境变量命名推荐使用大写字母和下划线
|
|
94
|
-
5. 在 CI/CD 中使用环境变量时,直接在平台中配置,不要使用 .env 文件
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import type { PostgrestSingleResponse } from '@supabase/supabase-js';
|
|
2
|
-
|
|
3
|
-
export type WhereOperation = '=' | '!=' | '>' | '<' | '>=' | '<=';
|
|
4
|
-
export type Where = [string, WhereOperation, string | number];
|
|
5
|
-
|
|
6
|
-
export interface BridgeEvent {
|
|
7
|
-
table: string;
|
|
8
|
-
fields?: string | string[];
|
|
9
|
-
where?: Where[];
|
|
10
|
-
data?: unknown;
|
|
11
|
-
page?: number;
|
|
12
|
-
pageSize?: number;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface DBBridgeInterface {
|
|
16
|
-
add(event: BridgeEvent): Promise<PostgrestSingleResponse<unknown>>;
|
|
17
|
-
update(event: BridgeEvent): Promise<PostgrestSingleResponse<unknown>>;
|
|
18
|
-
delete(event: BridgeEvent): Promise<PostgrestSingleResponse<unknown>>;
|
|
19
|
-
get(event: BridgeEvent): Promise<PostgrestSingleResponse<unknown>>;
|
|
20
|
-
pagination(event: BridgeEvent): Promise<PostgrestSingleResponse<unknown>>;
|
|
21
|
-
}
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
export interface MigrationConfig {
|
|
2
|
-
/**
|
|
3
|
-
* Migration version number or identifier
|
|
4
|
-
*/
|
|
5
|
-
version: string | number;
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Description of what this migration does
|
|
9
|
-
*/
|
|
10
|
-
description: string;
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Timestamp when migration was created
|
|
14
|
-
*/
|
|
15
|
-
timestamp?: number;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface MigrationResult {
|
|
19
|
-
/**
|
|
20
|
-
* Whether the migration was successful
|
|
21
|
-
*/
|
|
22
|
-
success: boolean;
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Error message if migration failed
|
|
26
|
-
*/
|
|
27
|
-
error?: string;
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Migration version that was executed
|
|
31
|
-
*/
|
|
32
|
-
version: string | number;
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Time taken to execute the migration in milliseconds
|
|
36
|
-
*/
|
|
37
|
-
executionTime?: number;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export interface DBMigrationInterface {
|
|
41
|
-
/**
|
|
42
|
-
* Get current database version
|
|
43
|
-
*/
|
|
44
|
-
getCurrentVersion(): Promise<string | number>;
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Get list of all applied migrations
|
|
48
|
-
*/
|
|
49
|
-
getAppliedMigrations(): Promise<MigrationConfig[]>;
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Get list of pending migrations that need to be applied
|
|
53
|
-
*/
|
|
54
|
-
getPendingMigrations(): Promise<MigrationConfig[]>;
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Apply a specific migration
|
|
58
|
-
* @param migration Migration configuration
|
|
59
|
-
*/
|
|
60
|
-
applyMigration(migration: MigrationConfig): Promise<MigrationResult>;
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Rollback a specific migration
|
|
64
|
-
* @param migration Migration configuration
|
|
65
|
-
*/
|
|
66
|
-
rollbackMigration(migration: MigrationConfig): Promise<MigrationResult>;
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Apply all pending migrations
|
|
70
|
-
*/
|
|
71
|
-
migrateUp(): Promise<MigrationResult[]>;
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Rollback to a specific version
|
|
75
|
-
* @param targetVersion Version to rollback to
|
|
76
|
-
*/
|
|
77
|
-
migrateDown(targetVersion: string | number): Promise<MigrationResult[]>;
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Create a new migration file/record
|
|
81
|
-
* @param name Name/description of the migration
|
|
82
|
-
*/
|
|
83
|
-
createMigration(name: string): Promise<MigrationConfig>;
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Validate migration history and database state
|
|
87
|
-
*/
|
|
88
|
-
validateMigrations(): Promise<{
|
|
89
|
-
valid: boolean;
|
|
90
|
-
errors?: string[];
|
|
91
|
-
}>;
|
|
92
|
-
}
|